aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc
diff options
context:
space:
mode:
authorroot <root@artemis.panaceas.org>2015-12-25 04:40:36 +0000
committerroot <root@artemis.panaceas.org>2015-12-25 04:40:36 +0000
commit849369d6c66d3054688672f97d31fceb8e8230fb (patch)
tree6135abc790ca67dedbe07c39806591e70eda81ce /sound/soc
downloadlinux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.gz
linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.bz2
linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.zip
initial_commit
Diffstat (limited to 'sound/soc')
-rw-r--r--sound/soc/Kconfig65
-rw-r--r--sound/soc/Makefile22
-rw-r--r--sound/soc/atmel/Kconfig52
-rw-r--r--sound/soc/atmel/Makefile16
-rw-r--r--sound/soc/atmel/atmel-pcm.c510
-rw-r--r--sound/soc/atmel/atmel-pcm.h83
-rw-r--r--sound/soc/atmel/atmel_ssc_dai.c879
-rw-r--r--sound/soc/atmel/atmel_ssc_dai.h122
-rw-r--r--sound/soc/atmel/playpaq_wm8510.c471
-rw-r--r--sound/soc/atmel/sam9g20_wm8731.c280
-rw-r--r--sound/soc/atmel/snd-soc-afeb9260.c185
-rw-r--r--sound/soc/au1x/Kconfig34
-rw-r--r--sound/soc/au1x/Makefile13
-rw-r--r--sound/soc/au1x/db1200.c129
-rw-r--r--sound/soc/au1x/dbdma2.c460
-rw-r--r--sound/soc/au1x/psc-ac97.c516
-rw-r--r--sound/soc/au1x/psc-i2s.c440
-rw-r--r--sound/soc/au1x/psc.h52
-rw-r--r--sound/soc/blackfin/Kconfig145
-rw-r--r--sound/soc/blackfin/Makefile29
-rw-r--r--sound/soc/blackfin/bf5xx-ac97-pcm.c490
-rw-r--r--sound/soc/blackfin/bf5xx-ac97-pcm.h26
-rw-r--r--sound/soc/blackfin/bf5xx-ac97.c393
-rw-r--r--sound/soc/blackfin/bf5xx-ac97.h59
-rw-r--r--sound/soc/blackfin/bf5xx-ad1836.c129
-rw-r--r--sound/soc/blackfin/bf5xx-ad193x.c157
-rw-r--r--sound/soc/blackfin/bf5xx-ad1980.c111
-rw-r--r--sound/soc/blackfin/bf5xx-ad73311.c233
-rw-r--r--sound/soc/blackfin/bf5xx-i2s-pcm.c329
-rw-r--r--sound/soc/blackfin/bf5xx-i2s-pcm.h26
-rw-r--r--sound/soc/blackfin/bf5xx-i2s.c308
-rw-r--r--sound/soc/blackfin/bf5xx-sport.c1093
-rw-r--r--sound/soc/blackfin/bf5xx-sport.h174
-rw-r--r--sound/soc/blackfin/bf5xx-ssm2602.c165
-rw-r--r--sound/soc/blackfin/bf5xx-tdm-pcm.c354
-rw-r--r--sound/soc/blackfin/bf5xx-tdm-pcm.h18
-rw-r--r--sound/soc/blackfin/bf5xx-tdm.c333
-rw-r--r--sound/soc/blackfin/bf5xx-tdm.h23
-rw-r--r--sound/soc/codecs/88pm860x-codec.c1490
-rw-r--r--sound/soc/codecs/88pm860x-codec.h97
-rw-r--r--sound/soc/codecs/Kconfig402
-rw-r--r--sound/soc/codecs/Makefile189
-rw-r--r--sound/soc/codecs/ac97.c165
-rw-r--r--sound/soc/codecs/ad1836.c338
-rw-r--r--sound/soc/codecs/ad1836.h69
-rw-r--r--sound/soc/codecs/ad193x.c517
-rw-r--r--sound/soc/codecs/ad193x.h84
-rw-r--r--sound/soc/codecs/ad1980.c291
-rw-r--r--sound/soc/codecs/ad1980.h26
-rw-r--r--sound/soc/codecs/ad73311.c80
-rw-r--r--sound/soc/codecs/ad73311.h88
-rw-r--r--sound/soc/codecs/ads117x.c74
-rw-r--r--sound/soc/codecs/ads117x.h13
-rw-r--r--sound/soc/codecs/ak4104.c310
-rw-r--r--sound/soc/codecs/ak4535.c546
-rw-r--r--sound/soc/codecs/ak4535.h39
-rw-r--r--sound/soc/codecs/ak4641.c664
-rw-r--r--sound/soc/codecs/ak4641.h47
-rw-r--r--sound/soc/codecs/ak4642.c564
-rw-r--r--sound/soc/codecs/ak4671.c725
-rw-r--r--sound/soc/codecs/ak4671.h153
-rw-r--r--sound/soc/codecs/alc5623.c1117
-rw-r--r--sound/soc/codecs/alc5623.h161
-rw-r--r--sound/soc/codecs/cq93vc.c223
-rw-r--r--sound/soc/codecs/cs4270.c758
-rw-r--r--sound/soc/codecs/cs4271.c667
-rw-r--r--sound/soc/codecs/cs42888.c1112
-rw-r--r--sound/soc/codecs/cs42888.h31
-rw-r--r--sound/soc/codecs/cs42l51.c653
-rw-r--r--sound/soc/codecs/cs42l51.h161
-rw-r--r--sound/soc/codecs/cx20442.c408
-rw-r--r--sound/soc/codecs/cx20442.h18
-rw-r--r--sound/soc/codecs/da7210.c626
-rw-r--r--sound/soc/codecs/dfbmcs320.c72
-rw-r--r--sound/soc/codecs/dmic.c105
-rw-r--r--sound/soc/codecs/jz4740.c442
-rw-r--r--sound/soc/codecs/l3.c91
-rw-r--r--sound/soc/codecs/lm4857.c276
-rw-r--r--sound/soc/codecs/max98088.c2125
-rw-r--r--sound/soc/codecs/max98088.h206
-rw-r--r--sound/soc/codecs/max98095.c2396
-rw-r--r--sound/soc/codecs/max98095.h299
-rw-r--r--sound/soc/codecs/max9850.c389
-rw-r--r--sound/soc/codecs/max9850.h38
-rw-r--r--sound/soc/codecs/max9877.c308
-rw-r--r--sound/soc/codecs/max9877.h37
-rw-r--r--sound/soc/codecs/mxc_hdmi.c638
-rw-r--r--sound/soc/codecs/mxc_spdif.c1392
-rw-r--r--sound/soc/codecs/mxc_spdif.h159
-rw-r--r--sound/soc/codecs/pcm3008.c188
-rw-r--r--sound/soc/codecs/pcm3008.h22
-rw-r--r--sound/soc/codecs/sgtl5000.c1750
-rw-r--r--sound/soc/codecs/sgtl5000.h400
-rw-r--r--sound/soc/codecs/si4763.c109
-rw-r--r--sound/soc/codecs/sn95031.c944
-rw-r--r--sound/soc/codecs/sn95031.h132
-rw-r--r--sound/soc/codecs/spdif_transciever.c80
-rw-r--r--sound/soc/codecs/ssm2602.c737
-rw-r--r--sound/soc/codecs/ssm2602.h121
-rw-r--r--sound/soc/codecs/stac9766.c425
-rw-r--r--sound/soc/codecs/stac9766.h17
-rw-r--r--sound/soc/codecs/tlv320aic23.c771
-rw-r--r--sound/soc/codecs/tlv320aic23.h119
-rw-r--r--sound/soc/codecs/tlv320aic26.c464
-rw-r--r--sound/soc/codecs/tlv320aic26.h93
-rw-r--r--sound/soc/codecs/tlv320aic32x4.c794
-rw-r--r--sound/soc/codecs/tlv320aic32x4.h143
-rw-r--r--sound/soc/codecs/tlv320aic3x.c1582
-rw-r--r--sound/soc/codecs/tlv320aic3x.h261
-rw-r--r--sound/soc/codecs/tlv320dac33.c1662
-rw-r--r--sound/soc/codecs/tlv320dac33.h264
-rw-r--r--sound/soc/codecs/tpa6130a2.c503
-rw-r--r--sound/soc/codecs/tpa6130a2.h62
-rw-r--r--sound/soc/codecs/twl4030.c2342
-rw-r--r--sound/soc/codecs/twl6040.c1789
-rw-r--r--sound/soc/codecs/twl6040.h146
-rw-r--r--sound/soc/codecs/uda134x.c642
-rw-r--r--sound/soc/codecs/uda134x.h34
-rw-r--r--sound/soc/codecs/uda1380.c886
-rw-r--r--sound/soc/codecs/uda1380.h79
-rw-r--r--sound/soc/codecs/wl1273.c527
-rw-r--r--sound/soc/codecs/wl1273.h30
-rw-r--r--sound/soc/codecs/wm1250-ev1.c108
-rw-r--r--sound/soc/codecs/wm2000.c890
-rw-r--r--sound/soc/codecs/wm2000.h76
-rw-r--r--sound/soc/codecs/wm8350.c1738
-rw-r--r--sound/soc/codecs/wm8350.h29
-rw-r--r--sound/soc/codecs/wm8400.c1495
-rw-r--r--sound/soc/codecs/wm8400.h59
-rw-r--r--sound/soc/codecs/wm8510.c715
-rw-r--r--sound/soc/codecs/wm8510.h102
-rw-r--r--sound/soc/codecs/wm8523.c587
-rw-r--r--sound/soc/codecs/wm8523.h157
-rw-r--r--sound/soc/codecs/wm8580.c980
-rw-r--r--sound/soc/codecs/wm8580.h35
-rw-r--r--sound/soc/codecs/wm8711.c533
-rw-r--r--sound/soc/codecs/wm8711.h39
-rw-r--r--sound/soc/codecs/wm8727.c84
-rw-r--r--sound/soc/codecs/wm8728.c388
-rw-r--r--sound/soc/codecs/wm8728.h21
-rw-r--r--sound/soc/codecs/wm8731.c727
-rw-r--r--sound/soc/codecs/wm8731.h39
-rw-r--r--sound/soc/codecs/wm8737.c754
-rw-r--r--sound/soc/codecs/wm8737.h322
-rw-r--r--sound/soc/codecs/wm8741.c562
-rw-r--r--sound/soc/codecs/wm8741.h211
-rw-r--r--sound/soc/codecs/wm8750.c871
-rw-r--r--sound/soc/codecs/wm8750.h60
-rw-r--r--sound/soc/codecs/wm8753.c1612
-rw-r--r--sound/soc/codecs/wm8753.h118
-rw-r--r--sound/soc/codecs/wm8770.c749
-rw-r--r--sound/soc/codecs/wm8770.h51
-rw-r--r--sound/soc/codecs/wm8776.c571
-rw-r--r--sound/soc/codecs/wm8776.h48
-rw-r--r--sound/soc/codecs/wm8804.c837
-rw-r--r--sound/soc/codecs/wm8804.h61
-rw-r--r--sound/soc/codecs/wm8900.c1373
-rw-r--r--sound/soc/codecs/wm8900.h55
-rw-r--r--sound/soc/codecs/wm8903.c2140
-rw-r--r--sound/soc/codecs/wm8903.h1225
-rw-r--r--sound/soc/codecs/wm8904.c2602
-rw-r--r--sound/soc/codecs/wm8904.h1581
-rw-r--r--sound/soc/codecs/wm8915.c2931
-rw-r--r--sound/soc/codecs/wm8915.h3717
-rw-r--r--sound/soc/codecs/wm8940.c828
-rw-r--r--sound/soc/codecs/wm8940.h102
-rw-r--r--sound/soc/codecs/wm8955.c1074
-rw-r--r--sound/soc/codecs/wm8955.h486
-rw-r--r--sound/soc/codecs/wm8958-dsp2.c1051
-rw-r--r--sound/soc/codecs/wm8960.c1075
-rw-r--r--sound/soc/codecs/wm8960.h113
-rw-r--r--sound/soc/codecs/wm8961.c1151
-rw-r--r--sound/soc/codecs/wm8961.h863
-rw-r--r--sound/soc/codecs/wm8962.c4316
-rw-r--r--sound/soc/codecs/wm8962.h3780
-rw-r--r--sound/soc/codecs/wm8971.c781
-rw-r--r--sound/soc/codecs/wm8971.h56
-rw-r--r--sound/soc/codecs/wm8974.c717
-rw-r--r--sound/soc/codecs/wm8974.h86
-rw-r--r--sound/soc/codecs/wm8978.c1076
-rw-r--r--sound/soc/codecs/wm8978.h83
-rw-r--r--sound/soc/codecs/wm8985.c1192
-rw-r--r--sound/soc/codecs/wm8985.h1045
-rw-r--r--sound/soc/codecs/wm8988.c933
-rw-r--r--sound/soc/codecs/wm8988.h57
-rw-r--r--sound/soc/codecs/wm8990.c1465
-rw-r--r--sound/soc/codecs/wm8990.h835
-rw-r--r--sound/soc/codecs/wm8991.c1426
-rw-r--r--sound/soc/codecs/wm8991.h833
-rw-r--r--sound/soc/codecs/wm8993.c1672
-rw-r--r--sound/soc/codecs/wm8993.h2129
-rw-r--r--sound/soc/codecs/wm8994-tables.c3147
-rw-r--r--sound/soc/codecs/wm8994.c3259
-rw-r--r--sound/soc/codecs/wm8994.h145
-rw-r--r--sound/soc/codecs/wm8995.c1911
-rw-r--r--sound/soc/codecs/wm8995.h4269
-rw-r--r--sound/soc/codecs/wm9081.c1395
-rw-r--r--sound/soc/codecs/wm9081.h784
-rw-r--r--sound/soc/codecs/wm9090.c715
-rw-r--r--sound/soc/codecs/wm9090.h713
-rw-r--r--sound/soc/codecs/wm9705.c423
-rw-r--r--sound/soc/codecs/wm9705.h11
-rw-r--r--sound/soc/codecs/wm9712.c711
-rw-r--r--sound/soc/codecs/wm9712.h11
-rw-r--r--sound/soc/codecs/wm9713.c1294
-rw-r--r--sound/soc/codecs/wm9713.h50
-rw-r--r--sound/soc/codecs/wm_hubs.c946
-rw-r--r--sound/soc/codecs/wm_hubs.h39
-rw-r--r--sound/soc/davinci/Kconfig86
-rw-r--r--sound/soc/davinci/Makefile20
-rw-r--r--sound/soc/davinci/davinci-evm.c335
-rw-r--r--sound/soc/davinci/davinci-i2s.c788
-rw-r--r--sound/soc/davinci/davinci-i2s.h20
-rw-r--r--sound/soc/davinci/davinci-mcasp.c1003
-rw-r--r--sound/soc/davinci/davinci-mcasp.h59
-rw-r--r--sound/soc/davinci/davinci-pcm.c884
-rw-r--r--sound/soc/davinci/davinci-pcm.h31
-rw-r--r--sound/soc/davinci/davinci-sffsdr.c180
-rw-r--r--sound/soc/davinci/davinci-vcif.c282
-rw-r--r--sound/soc/ep93xx/Kconfig41
-rw-r--r--sound/soc/ep93xx/Makefile17
-rw-r--r--sound/soc/ep93xx/edb93xx.c142
-rw-r--r--sound/soc/ep93xx/ep93xx-ac97.c467
-rw-r--r--sound/soc/ep93xx/ep93xx-i2s.c482
-rw-r--r--sound/soc/ep93xx/ep93xx-pcm.c338
-rw-r--r--sound/soc/ep93xx/ep93xx-pcm.h20
-rw-r--r--sound/soc/ep93xx/simone.c91
-rw-r--r--sound/soc/ep93xx/snappercl15.c146
-rw-r--r--sound/soc/fsl/Kconfig67
-rw-r--r--sound/soc/fsl/Makefile22
-rw-r--r--sound/soc/fsl/efika-audio-fabric.c91
-rw-r--r--sound/soc/fsl/fsl_dma.c1009
-rw-r--r--sound/soc/fsl/fsl_dma.h129
-rw-r--r--sound/soc/fsl/fsl_ssi.c804
-rw-r--r--sound/soc/fsl/fsl_ssi.h200
-rw-r--r--sound/soc/fsl/mpc5200_dma.c542
-rw-r--r--sound/soc/fsl/mpc5200_dma.h84
-rw-r--r--sound/soc/fsl/mpc5200_psc_ac97.c347
-rw-r--r--sound/soc/fsl/mpc5200_psc_ac97.h13
-rw-r--r--sound/soc/fsl/mpc5200_psc_i2s.c244
-rw-r--r--sound/soc/fsl/mpc8610_hpcd.c588
-rw-r--r--sound/soc/fsl/p1022_ds.c590
-rw-r--r--sound/soc/fsl/pcm030-audio-fabric.c91
-rw-r--r--sound/soc/imx/Kconfig133
-rw-r--r--sound/soc/imx/Makefile37
-rw-r--r--sound/soc/imx/eukrea-tlv320.c131
-rw-r--r--sound/soc/imx/hdmi_pcm.S238
-rw-r--r--sound/soc/imx/imx-cs42888.c444
-rw-r--r--sound/soc/imx/imx-esai.c757
-rw-r--r--sound/soc/imx/imx-esai.h334
-rw-r--r--sound/soc/imx/imx-hdmi-dai.c129
-rw-r--r--sound/soc/imx/imx-hdmi-dma.c1436
-rw-r--r--sound/soc/imx/imx-hdmi.c90
-rw-r--r--sound/soc/imx/imx-hdmi.h95
-rw-r--r--sound/soc/imx/imx-pcm-dma-mx2.c543
-rw-r--r--sound/soc/imx/imx-pcm-fiq.c345
-rw-r--r--sound/soc/imx/imx-pcm.h76
-rw-r--r--sound/soc/imx/imx-sgtl5000.c398
-rw-r--r--sound/soc/imx/imx-si4763.c198
-rw-r--r--sound/soc/imx/imx-si4763.h19
-rw-r--r--sound/soc/imx/imx-spdif-dai.c137
-rw-r--r--sound/soc/imx/imx-spdif.c149
-rw-r--r--sound/soc/imx/imx-ssi.c968
-rw-r--r--sound/soc/imx/imx-ssi.h225
-rwxr-xr-xsound/soc/imx/imx-wm8958.c362
-rw-r--r--sound/soc/imx/imx-wm8962.c678
-rw-r--r--sound/soc/imx/mx27vis-aic32x4.c137
-rw-r--r--sound/soc/imx/phycore-ac97.c99
-rw-r--r--sound/soc/imx/wm1133-ev1.c303
-rw-r--r--sound/soc/jz4740/Kconfig23
-rw-r--r--sound/soc/jz4740/Makefile13
-rw-r--r--sound/soc/jz4740/jz4740-i2s.c537
-rw-r--r--sound/soc/jz4740/jz4740-i2s.h16
-rw-r--r--sound/soc/jz4740/jz4740-pcm.c371
-rw-r--r--sound/soc/jz4740/jz4740-pcm.h20
-rw-r--r--sound/soc/jz4740/qi_lb60.c145
-rw-r--r--sound/soc/kirkwood/Kconfig29
-rw-r--r--sound/soc/kirkwood/Makefile11
-rw-r--r--sound/soc/kirkwood/kirkwood-dma.c404
-rw-r--r--sound/soc/kirkwood/kirkwood-i2s.c502
-rw-r--r--sound/soc/kirkwood/kirkwood-openrd.c120
-rw-r--r--sound/soc/kirkwood/kirkwood-t5325.c141
-rw-r--r--sound/soc/kirkwood/kirkwood.h129
-rw-r--r--sound/soc/mid-x86/Kconfig14
-rw-r--r--sound/soc/mid-x86/Makefile5
-rw-r--r--sound/soc/mid-x86/mfld_machine.c452
-rw-r--r--sound/soc/mid-x86/sst_platform.c486
-rw-r--r--sound/soc/mid-x86/sst_platform.h63
-rw-r--r--sound/soc/nuc900/Kconfig27
-rw-r--r--sound/soc/nuc900/Makefile11
-rw-r--r--sound/soc/nuc900/nuc900-ac97.c424
-rw-r--r--sound/soc/nuc900/nuc900-audio.c74
-rw-r--r--sound/soc/nuc900/nuc900-audio.h115
-rw-r--r--sound/soc/nuc900/nuc900-pcm.c373
-rw-r--r--sound/soc/omap/Kconfig135
-rw-r--r--sound/soc/omap/Makefile38
-rw-r--r--sound/soc/omap/am3517evm.c195
-rw-r--r--sound/soc/omap/ams-delta.c660
-rw-r--r--sound/soc/omap/igep0020.c137
-rw-r--r--sound/soc/omap/mcpdm.c470
-rw-r--r--sound/soc/omap/mcpdm.h153
-rw-r--r--sound/soc/omap/n810.c407
-rw-r--r--sound/soc/omap/omap-mcbsp.c791
-rw-r--r--sound/soc/omap/omap-mcbsp.h64
-rw-r--r--sound/soc/omap/omap-mcpdm.c272
-rw-r--r--sound/soc/omap/omap-pcm.c439
-rw-r--r--sound/soc/omap/omap-pcm.h38
-rw-r--r--sound/soc/omap/omap3beagle.c149
-rw-r--r--sound/soc/omap/omap3evm.c135
-rw-r--r--sound/soc/omap/omap3pandora.c339
-rw-r--r--sound/soc/omap/osk5912.c223
-rw-r--r--sound/soc/omap/overo.c139
-rw-r--r--sound/soc/omap/rx51.c468
-rw-r--r--sound/soc/omap/sdp3430.c345
-rw-r--r--sound/soc/omap/sdp4430.c261
-rw-r--r--sound/soc/omap/zoom2.c291
-rw-r--r--sound/soc/pxa/Kconfig193
-rw-r--r--sound/soc/pxa/Makefile49
-rw-r--r--sound/soc/pxa/corgi.c359
-rw-r--r--sound/soc/pxa/e740_wm9705.c212
-rw-r--r--sound/soc/pxa/e750_wm9705.c185
-rw-r--r--sound/soc/pxa/e800_wm9712.c172
-rw-r--r--sound/soc/pxa/em-x270.c95
-rw-r--r--sound/soc/pxa/hx4700.c255
-rw-r--r--sound/soc/pxa/imote2.c108
-rw-r--r--sound/soc/pxa/magician.c564
-rw-r--r--sound/soc/pxa/mioa701_wm9713.c247
-rw-r--r--sound/soc/pxa/palm27x.c224
-rw-r--r--sound/soc/pxa/poodle.c333
-rw-r--r--sound/soc/pxa/pxa-ssp.c854
-rw-r--r--sound/soc/pxa/pxa-ssp.h45
-rw-r--r--sound/soc/pxa/pxa2xx-ac97.c280
-rw-r--r--sound/soc/pxa/pxa2xx-ac97.h20
-rw-r--r--sound/soc/pxa/pxa2xx-i2s.c401
-rw-r--r--sound/soc/pxa/pxa2xx-i2s.h18
-rw-r--r--sound/soc/pxa/pxa2xx-pcm.c156
-rw-r--r--sound/soc/pxa/raumfeld.c335
-rw-r--r--sound/soc/pxa/saarb.c200
-rw-r--r--sound/soc/pxa/spitz.c382
-rw-r--r--sound/soc/pxa/tavorevb3.c200
-rw-r--r--sound/soc/pxa/tosa.c306
-rw-r--r--sound/soc/pxa/z2.c246
-rw-r--r--sound/soc/pxa/zylonite.c291
-rw-r--r--sound/soc/s6000/Kconfig19
-rw-r--r--sound/soc/s6000/Makefile11
-rw-r--r--sound/soc/s6000/s6000-i2s.c621
-rw-r--r--sound/soc/s6000/s6000-i2s.h23
-rw-r--r--sound/soc/s6000/s6000-pcm.c537
-rw-r--r--sound/soc/s6000/s6000-pcm.h33
-rw-r--r--sound/soc/s6000/s6105-ipcam.c243
-rw-r--r--sound/soc/samsung/Kconfig179
-rw-r--r--sound/soc/samsung/Makefile57
-rw-r--r--sound/soc/samsung/ac97.c520
-rw-r--r--sound/soc/samsung/dma.c499
-rw-r--r--sound/soc/samsung/dma.h22
-rw-r--r--sound/soc/samsung/goni_wm8994.c305
-rw-r--r--sound/soc/samsung/h1940_uda1380.c287
-rw-r--r--sound/soc/samsung/i2s.c1257
-rw-r--r--sound/soc/samsung/i2s.h29
-rw-r--r--sound/soc/samsung/jive_wm8750.c180
-rw-r--r--sound/soc/samsung/ln2440sbc_alc650.c70
-rw-r--r--sound/soc/samsung/neo1973_wm8753.c538
-rw-r--r--sound/soc/samsung/pcm.c650
-rw-r--r--sound/soc/samsung/pcm.h17
-rw-r--r--sound/soc/samsung/regs-i2s-v2.h115
-rw-r--r--sound/soc/samsung/rx1950_uda1380.c309
-rw-r--r--sound/soc/samsung/s3c-i2s-v2.c756
-rw-r--r--sound/soc/samsung/s3c-i2s-v2.h106
-rw-r--r--sound/soc/samsung/s3c2412-i2s.c202
-rw-r--r--sound/soc/samsung/s3c2412-i2s.h27
-rw-r--r--sound/soc/samsung/s3c24xx-i2s.c507
-rw-r--r--sound/soc/samsung/s3c24xx-i2s.h35
-rw-r--r--sound/soc/samsung/s3c24xx_simtec.c387
-rw-r--r--sound/soc/samsung/s3c24xx_simtec.h22
-rw-r--r--sound/soc/samsung/s3c24xx_simtec_hermes.c134
-rw-r--r--sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c122
-rw-r--r--sound/soc/samsung/s3c24xx_uda134x.c361
-rw-r--r--sound/soc/samsung/smartq_wm8987.c284
-rw-r--r--sound/soc/samsung/smdk2443_wm9710.c66
-rw-r--r--sound/soc/samsung/smdk_spdif.c221
-rw-r--r--sound/soc/samsung/smdk_wm8580.c287
-rw-r--r--sound/soc/samsung/smdk_wm8580pcm.c206
-rw-r--r--sound/soc/samsung/smdk_wm8994.c176
-rw-r--r--sound/soc/samsung/smdk_wm9713.c106
-rw-r--r--sound/soc/samsung/spdif.c500
-rw-r--r--sound/soc/samsung/spdif.h19
-rw-r--r--sound/soc/samsung/speyside.c332
-rw-r--r--sound/soc/sh/Kconfig80
-rw-r--r--sound/soc/sh/Makefile26
-rw-r--r--sound/soc/sh/dma-sh7760.c386
-rw-r--r--sound/soc/sh/fsi-ak4642.c209
-rw-r--r--sound/soc/sh/fsi-da7210.c79
-rw-r--r--sound/soc/sh/fsi-hdmi.c127
-rw-r--r--sound/soc/sh/fsi.c1455
-rw-r--r--sound/soc/sh/hac.c349
-rw-r--r--sound/soc/sh/migor.c224
-rw-r--r--sound/soc/sh/sh7760-ac97.c83
-rw-r--r--sound/soc/sh/siu.h194
-rw-r--r--sound/soc/sh/siu_dai.c869
-rw-r--r--sound/soc/sh/siu_pcm.c616
-rw-r--r--sound/soc/sh/ssi.c418
-rw-r--r--sound/soc/soc-cache.c1363
-rw-r--r--sound/soc/soc-core.c3962
-rw-r--r--sound/soc/soc-dapm.c2648
-rw-r--r--sound/soc/soc-jack.c386
-rw-r--r--sound/soc/soc-utils.c139
-rw-r--r--sound/soc/tegra/Kconfig40
-rw-r--r--sound/soc/tegra/Makefile17
-rw-r--r--sound/soc/tegra/tegra_asoc_utils.c156
-rw-r--r--sound/soc/tegra/tegra_asoc_utils.h45
-rw-r--r--sound/soc/tegra/tegra_das.c265
-rw-r--r--sound/soc/tegra/tegra_das.h135
-rw-r--r--sound/soc/tegra/tegra_i2s.c509
-rw-r--r--sound/soc/tegra/tegra_i2s.h165
-rw-r--r--sound/soc/tegra/tegra_pcm.c409
-rw-r--r--sound/soc/tegra/tegra_pcm.h55
-rw-r--r--sound/soc/tegra/tegra_wm8903.c482
-rw-r--r--sound/soc/tegra/trimslice.c228
-rw-r--r--sound/soc/txx9/Kconfig29
-rw-r--r--sound/soc/txx9/Makefile11
-rw-r--r--sound/soc/txx9/txx9aclc-ac97.c242
-rw-r--r--sound/soc/txx9/txx9aclc-generic.c89
-rw-r--r--sound/soc/txx9/txx9aclc.c453
-rw-r--r--sound/soc/txx9/txx9aclc.h74
424 files changed, 193714 insertions, 0 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
new file mode 100644
index 00000000..8224db5f
--- /dev/null
+++ b/sound/soc/Kconfig
@@ -0,0 +1,65 @@
+#
+# SoC audio configuration
+#
+
+menuconfig SND_SOC
+ tristate "ALSA for SoC audio support"
+ select SND_PCM
+ select AC97_BUS if SND_SOC_AC97_BUS
+ select SND_JACK if INPUT=y || INPUT=SND
+ ---help---
+
+ If you want ASoC support, you should say Y here and also to the
+ specific driver for your SoC platform below.
+
+ ASoC provides power efficient ALSA support for embedded battery powered
+ SoC based systems like PDA's, Phones and Personal Media Players.
+
+ This ASoC audio support can also be built as a module. If so, the module
+ will be called snd-soc-core.
+
+if SND_SOC
+
+config SND_SOC_CACHE_LZO
+ bool "Support LZO compression for register caches"
+ select LZO_COMPRESS
+ select LZO_DECOMPRESS
+ ---help---
+ Select this to enable LZO compression for register caches.
+ This will allow machine or CODEC drivers to compress register
+ caches in memory, reducing the memory consumption at the
+ expense of performance. If this is not present and is used
+ the system will fall back to uncompressed caches.
+
+ Usually it is safe to disable this option, where cache
+ compression in used the rbtree option will typically perform
+ better.
+
+config SND_SOC_AC97_BUS
+ bool
+
+# All the supported SoCs
+source "sound/soc/atmel/Kconfig"
+source "sound/soc/au1x/Kconfig"
+source "sound/soc/blackfin/Kconfig"
+source "sound/soc/davinci/Kconfig"
+source "sound/soc/ep93xx/Kconfig"
+source "sound/soc/fsl/Kconfig"
+source "sound/soc/imx/Kconfig"
+source "sound/soc/jz4740/Kconfig"
+source "sound/soc/nuc900/Kconfig"
+source "sound/soc/omap/Kconfig"
+source "sound/soc/kirkwood/Kconfig"
+source "sound/soc/mid-x86/Kconfig"
+source "sound/soc/pxa/Kconfig"
+source "sound/soc/samsung/Kconfig"
+source "sound/soc/s6000/Kconfig"
+source "sound/soc/sh/Kconfig"
+source "sound/soc/tegra/Kconfig"
+source "sound/soc/txx9/Kconfig"
+
+# Supported codecs
+source "sound/soc/codecs/Kconfig"
+
+endif # SND_SOC
+
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
new file mode 100644
index 00000000..1ed61c5d
--- /dev/null
+++ b/sound/soc/Makefile
@@ -0,0 +1,22 @@
+snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-cache.o soc-utils.o
+
+obj-$(CONFIG_SND_SOC) += snd-soc-core.o
+obj-$(CONFIG_SND_SOC) += codecs/
+obj-$(CONFIG_SND_SOC) += atmel/
+obj-$(CONFIG_SND_SOC) += au1x/
+obj-$(CONFIG_SND_SOC) += blackfin/
+obj-$(CONFIG_SND_SOC) += davinci/
+obj-$(CONFIG_SND_SOC) += ep93xx/
+obj-$(CONFIG_SND_SOC) += fsl/
+obj-$(CONFIG_SND_SOC) += imx/
+obj-$(CONFIG_SND_SOC) += jz4740/
+obj-$(CONFIG_SND_SOC) += mid-x86/
+obj-$(CONFIG_SND_SOC) += nuc900/
+obj-$(CONFIG_SND_SOC) += omap/
+obj-$(CONFIG_SND_SOC) += kirkwood/
+obj-$(CONFIG_SND_SOC) += pxa/
+obj-$(CONFIG_SND_SOC) += samsung/
+obj-$(CONFIG_SND_SOC) += s6000/
+obj-$(CONFIG_SND_SOC) += sh/
+obj-$(CONFIG_SND_SOC) += tegra/
+obj-$(CONFIG_SND_SOC) += txx9/
diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig
new file mode 100644
index 00000000..bee3c94f
--- /dev/null
+++ b/sound/soc/atmel/Kconfig
@@ -0,0 +1,52 @@
+config SND_ATMEL_SOC
+ tristate "SoC Audio for the Atmel System-on-Chip"
+ depends on ARCH_AT91 || AVR32
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the ATMEL SSC interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_ATMEL_SOC_SSC
+ tristate
+ depends on SND_ATMEL_SOC
+ help
+ Say Y or M if you want to add support for codecs the
+ ATMEL SSC interface. You will also needs to select the individual
+ machine drivers to support below.
+
+config SND_AT91_SOC_SAM9G20_WM8731
+ tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board"
+ depends on ATMEL_SSC && ARCH_AT91SAM9G20 && SND_ATMEL_SOC && \
+ AT91_PROGRAMMABLE_CLOCKS
+ select SND_ATMEL_SOC_SSC
+ select SND_SOC_WM8731
+ help
+ Say Y if you want to add support for SoC audio on WM8731-based
+ AT91sam9g20 evaluation board.
+
+config SND_AT32_SOC_PLAYPAQ
+ tristate "SoC Audio support for PlayPaq with WM8510"
+ depends on SND_ATMEL_SOC && BOARD_PLAYPAQ && AT91_PROGRAMMABLE_CLOCKS
+ select SND_ATMEL_SOC_SSC
+ select SND_SOC_WM8510
+ help
+ Say Y or M here if you want to add support for SoC audio
+ on the LRS PlayPaq.
+
+config SND_AT32_SOC_PLAYPAQ_SLAVE
+ bool "Run CODEC on PlayPaq in slave mode"
+ depends on SND_AT32_SOC_PLAYPAQ
+ default n
+ help
+ Say Y if you want to run with the AT32 SSC generating the BCLK
+ and FRAME signals on the PlayPaq. Unless you want to play
+ with the AT32 as the SSC master, you probably want to say N here,
+ as this will give you better sound quality.
+
+config SND_AT91_SOC_AFEB9260
+ tristate "SoC Audio support for AFEB9260 board"
+ depends on ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC
+ select SND_ATMEL_SOC_SSC
+ select SND_SOC_TLV320AIC23
+ help
+ Say Y here to support sound on AFEB9260 board.
diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile
new file mode 100644
index 00000000..e7ea56bd
--- /dev/null
+++ b/sound/soc/atmel/Makefile
@@ -0,0 +1,16 @@
+# AT91 Platform Support
+snd-soc-atmel-pcm-objs := atmel-pcm.o
+snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o
+
+obj-$(CONFIG_SND_ATMEL_SOC) += snd-soc-atmel-pcm.o
+obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o
+
+# AT91 Machine Support
+snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o
+
+# AT32 Machine Support
+snd-soc-playpaq-objs := playpaq_wm8510.o
+
+obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
+obj-$(CONFIG_SND_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.o
+obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o
diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c
new file mode 100644
index 00000000..d0e75323
--- /dev/null
+++ b/sound/soc/atmel/atmel-pcm.c
@@ -0,0 +1,510 @@
+/*
+ * atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC.
+ *
+ * Copyright (C) 2005 SAN People
+ * Copyright (C) 2008 Atmel
+ *
+ * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
+ *
+ * Based on at91-pcm. by:
+ * Frank Mandarino <fmandarino@endrelia.com>
+ * Copyright 2006 Endrelia Technologies Inc.
+ *
+ * Based on pxa2xx-pcm.c by:
+ *
+ * Author: Nicolas Pitre
+ * Created: Nov 30, 2004
+ * Copyright: (C) 2004 MontaVista Software, Inc.
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/atmel_pdc.h>
+#include <linux/atmel-ssc.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "atmel-pcm.h"
+
+
+/*--------------------------------------------------------------------------*\
+ * Hardware definition
+\*--------------------------------------------------------------------------*/
+/* TODO: These values were taken from the AT91 platform driver, check
+ * them against real values for AT32
+ */
+static const struct snd_pcm_hardware atmel_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8192,
+ .periods_min = 2,
+ .periods_max = 1024,
+ .buffer_bytes_max = 32 * 1024,
+};
+
+
+/*--------------------------------------------------------------------------*\
+ * Data types
+\*--------------------------------------------------------------------------*/
+struct atmel_runtime_data {
+ struct atmel_pcm_dma_params *params;
+ dma_addr_t dma_buffer; /* physical address of dma buffer */
+ dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
+ size_t period_size;
+
+ dma_addr_t period_ptr; /* physical address of next period */
+
+ /* PDC register save */
+ u32 pdc_xpr_save;
+ u32 pdc_xcr_save;
+ u32 pdc_xnpr_save;
+ u32 pdc_xncr_save;
+};
+
+
+/*--------------------------------------------------------------------------*\
+ * Helper functions
+\*--------------------------------------------------------------------------*/
+static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
+ int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = atmel_pcm_hardware.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ pr_debug("atmel-pcm:"
+ "preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
+ (void *) buf->area,
+ (void *) buf->addr,
+ size);
+
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+ return 0;
+}
+/*--------------------------------------------------------------------------*\
+ * ISR
+\*--------------------------------------------------------------------------*/
+static void atmel_pcm_dma_irq(u32 ssc_sr,
+ struct snd_pcm_substream *substream)
+{
+ struct atmel_runtime_data *prtd = substream->runtime->private_data;
+ struct atmel_pcm_dma_params *params = prtd->params;
+ static int count;
+
+ count++;
+
+ if (ssc_sr & params->mask->ssc_endbuf) {
+ pr_warning("atmel-pcm: buffer %s on %s"
+ " (SSC_SR=%#x, count=%d)\n",
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+ ? "underrun" : "overrun",
+ params->name, ssc_sr, count);
+
+ /* re-start the PDC */
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_disable);
+ prtd->period_ptr += prtd->period_size;
+ if (prtd->period_ptr >= prtd->dma_buffer_end)
+ prtd->period_ptr = prtd->dma_buffer;
+
+ ssc_writex(params->ssc->regs, params->pdc->xpr,
+ prtd->period_ptr);
+ ssc_writex(params->ssc->regs, params->pdc->xcr,
+ prtd->period_size / params->pdc_xfer_size);
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_enable);
+ }
+
+ if (ssc_sr & params->mask->ssc_endx) {
+ /* Load the PDC next pointer and counter registers */
+ prtd->period_ptr += prtd->period_size;
+ if (prtd->period_ptr >= prtd->dma_buffer_end)
+ prtd->period_ptr = prtd->dma_buffer;
+
+ ssc_writex(params->ssc->regs, params->pdc->xnpr,
+ prtd->period_ptr);
+ ssc_writex(params->ssc->regs, params->pdc->xncr,
+ prtd->period_size / params->pdc_xfer_size);
+ }
+
+ snd_pcm_period_elapsed(substream);
+}
+
+
+/*--------------------------------------------------------------------------*\
+ * PCM operations
+\*--------------------------------------------------------------------------*/
+static int atmel_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct atmel_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ /* this may get called several times by oss emulation
+ * with different params */
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ prtd->params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
+
+ prtd->dma_buffer = runtime->dma_addr;
+ prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
+ prtd->period_size = params_period_bytes(params);
+
+ pr_debug("atmel-pcm: "
+ "hw_params: DMA for %s initialized "
+ "(dma_bytes=%u, period_size=%u)\n",
+ prtd->params->name,
+ runtime->dma_bytes,
+ prtd->period_size);
+ return 0;
+}
+
+static int atmel_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct atmel_runtime_data *prtd = substream->runtime->private_data;
+ struct atmel_pcm_dma_params *params = prtd->params;
+
+ if (params != NULL) {
+ ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
+ params->mask->pdc_disable);
+ prtd->params->dma_intr_handler = NULL;
+ }
+
+ return 0;
+}
+
+static int atmel_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct atmel_runtime_data *prtd = substream->runtime->private_data;
+ struct atmel_pcm_dma_params *params = prtd->params;
+
+ ssc_writex(params->ssc->regs, SSC_IDR,
+ params->mask->ssc_endx | params->mask->ssc_endbuf);
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_disable);
+ return 0;
+}
+
+static int atmel_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_pcm_runtime *rtd = substream->runtime;
+ struct atmel_runtime_data *prtd = rtd->private_data;
+ struct atmel_pcm_dma_params *params = prtd->params;
+ int ret = 0;
+
+ pr_debug("atmel-pcm:buffer_size = %ld,"
+ "dma_area = %p, dma_bytes = %u\n",
+ rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ prtd->period_ptr = prtd->dma_buffer;
+
+ ssc_writex(params->ssc->regs, params->pdc->xpr,
+ prtd->period_ptr);
+ ssc_writex(params->ssc->regs, params->pdc->xcr,
+ prtd->period_size / params->pdc_xfer_size);
+
+ prtd->period_ptr += prtd->period_size;
+ ssc_writex(params->ssc->regs, params->pdc->xnpr,
+ prtd->period_ptr);
+ ssc_writex(params->ssc->regs, params->pdc->xncr,
+ prtd->period_size / params->pdc_xfer_size);
+
+ pr_debug("atmel-pcm: trigger: "
+ "period_ptr=%lx, xpr=%u, "
+ "xcr=%u, xnpr=%u, xncr=%u\n",
+ (unsigned long)prtd->period_ptr,
+ ssc_readx(params->ssc->regs, params->pdc->xpr),
+ ssc_readx(params->ssc->regs, params->pdc->xcr),
+ ssc_readx(params->ssc->regs, params->pdc->xnpr),
+ ssc_readx(params->ssc->regs, params->pdc->xncr));
+
+ ssc_writex(params->ssc->regs, SSC_IER,
+ params->mask->ssc_endx | params->mask->ssc_endbuf);
+ ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
+ params->mask->pdc_enable);
+
+ pr_debug("sr=%u imr=%u\n",
+ ssc_readx(params->ssc->regs, SSC_SR),
+ ssc_readx(params->ssc->regs, SSC_IER));
+ break; /* SNDRV_PCM_TRIGGER_START */
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_disable);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_enable);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t atmel_pcm_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct atmel_runtime_data *prtd = runtime->private_data;
+ struct atmel_pcm_dma_params *params = prtd->params;
+ dma_addr_t ptr;
+ snd_pcm_uframes_t x;
+
+ ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
+ x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
+
+ if (x == runtime->buffer_size)
+ x = 0;
+
+ return x;
+}
+
+static int atmel_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct atmel_runtime_data *prtd;
+ int ret = 0;
+
+ snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
+
+ /* ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);
+ if (prtd == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ runtime->private_data = prtd;
+
+ out:
+ return ret;
+}
+
+static int atmel_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct atmel_runtime_data *prtd = substream->runtime->private_data;
+
+ kfree(prtd);
+ return 0;
+}
+
+static int atmel_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ return remap_pfn_range(vma, vma->vm_start,
+ substream->dma_buffer.addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+static struct snd_pcm_ops atmel_pcm_ops = {
+ .open = atmel_pcm_open,
+ .close = atmel_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = atmel_pcm_hw_params,
+ .hw_free = atmel_pcm_hw_free,
+ .prepare = atmel_pcm_prepare,
+ .trigger = atmel_pcm_trigger,
+ .pointer = atmel_pcm_pointer,
+ .mmap = atmel_pcm_mmap,
+};
+
+
+/*--------------------------------------------------------------------------*\
+ * ASoC platform driver
+\*--------------------------------------------------------------------------*/
+static u64 atmel_pcm_dmamask = 0xffffffff;
+
+static int atmel_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &atmel_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->driver->playback.channels_min) {
+ ret = atmel_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ pr_debug("at32-pcm:"
+ "Allocating PCM capture DMA buffer\n");
+ ret = atmel_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+ dma_free_coherent(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+#ifdef CONFIG_PM
+static int atmel_pcm_suspend(struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = dai->runtime;
+ struct atmel_runtime_data *prtd;
+ struct atmel_pcm_dma_params *params;
+
+ if (!runtime)
+ return 0;
+
+ prtd = runtime->private_data;
+ params = prtd->params;
+
+ /* disable the PDC and save the PDC registers */
+
+ ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_disable);
+
+ prtd->pdc_xpr_save = ssc_readx(params->ssc->regs, params->pdc->xpr);
+ prtd->pdc_xcr_save = ssc_readx(params->ssc->regs, params->pdc->xcr);
+ prtd->pdc_xnpr_save = ssc_readx(params->ssc->regs, params->pdc->xnpr);
+ prtd->pdc_xncr_save = ssc_readx(params->ssc->regs, params->pdc->xncr);
+
+ return 0;
+}
+
+static int atmel_pcm_resume(struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = dai->runtime;
+ struct atmel_runtime_data *prtd;
+ struct atmel_pcm_dma_params *params;
+
+ if (!runtime)
+ return 0;
+
+ prtd = runtime->private_data;
+ params = prtd->params;
+
+ /* restore the PDC registers and enable the PDC */
+ ssc_writex(params->ssc->regs, params->pdc->xpr, prtd->pdc_xpr_save);
+ ssc_writex(params->ssc->regs, params->pdc->xcr, prtd->pdc_xcr_save);
+ ssc_writex(params->ssc->regs, params->pdc->xnpr, prtd->pdc_xnpr_save);
+ ssc_writex(params->ssc->regs, params->pdc->xncr, prtd->pdc_xncr_save);
+
+ ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_enable);
+ return 0;
+}
+#else
+#define atmel_pcm_suspend NULL
+#define atmel_pcm_resume NULL
+#endif
+
+static struct snd_soc_platform_driver atmel_soc_platform = {
+ .ops = &atmel_pcm_ops,
+ .pcm_new = atmel_pcm_new,
+ .pcm_free = atmel_pcm_free_dma_buffers,
+ .suspend = atmel_pcm_suspend,
+ .resume = atmel_pcm_resume,
+};
+
+static int __devinit atmel_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &atmel_soc_platform);
+}
+
+static int __devexit atmel_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver atmel_pcm_driver = {
+ .driver = {
+ .name = "atmel-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = atmel_soc_platform_probe,
+ .remove = __devexit_p(atmel_soc_platform_remove),
+};
+
+static int __init snd_atmel_pcm_init(void)
+{
+ return platform_driver_register(&atmel_pcm_driver);
+}
+module_init(snd_atmel_pcm_init);
+
+static void __exit snd_atmel_pcm_exit(void)
+{
+ platform_driver_unregister(&atmel_pcm_driver);
+}
+module_exit(snd_atmel_pcm_exit);
+
+MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
+MODULE_DESCRIPTION("Atmel PCM module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/atmel/atmel-pcm.h b/sound/soc/atmel/atmel-pcm.h
new file mode 100644
index 00000000..25973293
--- /dev/null
+++ b/sound/soc/atmel/atmel-pcm.h
@@ -0,0 +1,83 @@
+/*
+ * at91-pcm.h - ALSA PCM interface for the Atmel AT91 SoC.
+ *
+ * Copyright (C) 2005 SAN People
+ * Copyright (C) 2008 Atmel
+ *
+ * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
+ *
+ * Based on at91-pcm. by:
+ * Frank Mandarino <fmandarino@endrelia.com>
+ * Copyright 2006 Endrelia Technologies Inc.
+ *
+ * Based on pxa2xx-pcm.c by:
+ *
+ * Author: Nicolas Pitre
+ * Created: Nov 30, 2004
+ * Copyright: (C) 2004 MontaVista Software, Inc.
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _ATMEL_PCM_H
+#define _ATMEL_PCM_H
+
+#include <linux/atmel-ssc.h>
+
+/*
+ * Registers and status bits that are required by the PCM driver.
+ */
+struct atmel_pdc_regs {
+ unsigned int xpr; /* PDC recv/trans pointer */
+ unsigned int xcr; /* PDC recv/trans counter */
+ unsigned int xnpr; /* PDC next recv/trans pointer */
+ unsigned int xncr; /* PDC next recv/trans counter */
+ unsigned int ptcr; /* PDC transfer control */
+};
+
+struct atmel_ssc_mask {
+ u32 ssc_enable; /* SSC recv/trans enable */
+ u32 ssc_disable; /* SSC recv/trans disable */
+ u32 ssc_endx; /* SSC ENDTX or ENDRX */
+ u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */
+ u32 pdc_enable; /* PDC recv/trans enable */
+ u32 pdc_disable; /* PDC recv/trans disable */
+};
+
+/*
+ * This structure, shared between the PCM driver and the interface,
+ * contains all information required by the PCM driver to perform the
+ * PDC DMA operation. All fields except dma_intr_handler() are initialized
+ * by the interface. The dms_intr_handler() pointer is set by the PCM
+ * driver and called by the interface SSC interrupt handler if it is
+ * non-NULL.
+ */
+struct atmel_pcm_dma_params {
+ char *name; /* stream identifier */
+ int pdc_xfer_size; /* PDC counter increment in bytes */
+ struct ssc_device *ssc; /* SSC device for stream */
+ struct atmel_pdc_regs *pdc; /* PDC receive or transmit registers */
+ struct atmel_ssc_mask *mask; /* SSC & PDC status bits */
+ struct snd_pcm_substream *substream;
+ void (*dma_intr_handler)(u32, struct snd_pcm_substream *);
+};
+
+/*
+ * SSC register access (since ssc_writel() / ssc_readl() require literal name)
+ */
+#define ssc_readx(base, reg) (__raw_readl((base) + (reg)))
+#define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg))
+
+#endif /* _ATMEL_PCM_H */
diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c
new file mode 100644
index 00000000..eda955b1
--- /dev/null
+++ b/sound/soc/atmel/atmel_ssc_dai.c
@@ -0,0 +1,879 @@
+/*
+ * atmel_ssc_dai.c -- ALSA SoC ATMEL SSC Audio Layer Platform driver
+ *
+ * Copyright (C) 2005 SAN People
+ * Copyright (C) 2008 Atmel
+ *
+ * Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
+ * ATMEL CORP.
+ *
+ * Based on at91-ssc.c by
+ * Frank Mandarino <fmandarino@endrelia.com>
+ * Based on pxa2xx Platform drivers by
+ * Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/atmel_pdc.h>
+
+#include <linux/atmel-ssc.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+
+#include "atmel-pcm.h"
+#include "atmel_ssc_dai.h"
+
+
+#if defined(CONFIG_ARCH_AT91SAM9260) || defined(CONFIG_ARCH_AT91SAM9G20)
+#define NUM_SSC_DEVICES 1
+#else
+#define NUM_SSC_DEVICES 3
+#endif
+
+/*
+ * SSC PDC registers required by the PCM DMA engine.
+ */
+static struct atmel_pdc_regs pdc_tx_reg = {
+ .xpr = ATMEL_PDC_TPR,
+ .xcr = ATMEL_PDC_TCR,
+ .xnpr = ATMEL_PDC_TNPR,
+ .xncr = ATMEL_PDC_TNCR,
+};
+
+static struct atmel_pdc_regs pdc_rx_reg = {
+ .xpr = ATMEL_PDC_RPR,
+ .xcr = ATMEL_PDC_RCR,
+ .xnpr = ATMEL_PDC_RNPR,
+ .xncr = ATMEL_PDC_RNCR,
+};
+
+/*
+ * SSC & PDC status bits for transmit and receive.
+ */
+static struct atmel_ssc_mask ssc_tx_mask = {
+ .ssc_enable = SSC_BIT(CR_TXEN),
+ .ssc_disable = SSC_BIT(CR_TXDIS),
+ .ssc_endx = SSC_BIT(SR_ENDTX),
+ .ssc_endbuf = SSC_BIT(SR_TXBUFE),
+ .pdc_enable = ATMEL_PDC_TXTEN,
+ .pdc_disable = ATMEL_PDC_TXTDIS,
+};
+
+static struct atmel_ssc_mask ssc_rx_mask = {
+ .ssc_enable = SSC_BIT(CR_RXEN),
+ .ssc_disable = SSC_BIT(CR_RXDIS),
+ .ssc_endx = SSC_BIT(SR_ENDRX),
+ .ssc_endbuf = SSC_BIT(SR_RXBUFF),
+ .pdc_enable = ATMEL_PDC_RXTEN,
+ .pdc_disable = ATMEL_PDC_RXTDIS,
+};
+
+
+/*
+ * DMA parameters.
+ */
+static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
+ {{
+ .name = "SSC0 PCM out",
+ .pdc = &pdc_tx_reg,
+ .mask = &ssc_tx_mask,
+ },
+ {
+ .name = "SSC0 PCM in",
+ .pdc = &pdc_rx_reg,
+ .mask = &ssc_rx_mask,
+ } },
+#if NUM_SSC_DEVICES == 3
+ {{
+ .name = "SSC1 PCM out",
+ .pdc = &pdc_tx_reg,
+ .mask = &ssc_tx_mask,
+ },
+ {
+ .name = "SSC1 PCM in",
+ .pdc = &pdc_rx_reg,
+ .mask = &ssc_rx_mask,
+ } },
+ {{
+ .name = "SSC2 PCM out",
+ .pdc = &pdc_tx_reg,
+ .mask = &ssc_tx_mask,
+ },
+ {
+ .name = "SSC2 PCM in",
+ .pdc = &pdc_rx_reg,
+ .mask = &ssc_rx_mask,
+ } },
+#endif
+};
+
+
+static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = {
+ {
+ .name = "ssc0",
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[0].lock),
+ .dir_mask = SSC_DIR_MASK_UNUSED,
+ .initialized = 0,
+ },
+#if NUM_SSC_DEVICES == 3
+ {
+ .name = "ssc1",
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock),
+ .dir_mask = SSC_DIR_MASK_UNUSED,
+ .initialized = 0,
+ },
+ {
+ .name = "ssc2",
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[2].lock),
+ .dir_mask = SSC_DIR_MASK_UNUSED,
+ .initialized = 0,
+ },
+#endif
+};
+
+
+/*
+ * SSC interrupt handler. Passes PDC interrupts to the DMA
+ * interrupt handler in the PCM driver.
+ */
+static irqreturn_t atmel_ssc_interrupt(int irq, void *dev_id)
+{
+ struct atmel_ssc_info *ssc_p = dev_id;
+ struct atmel_pcm_dma_params *dma_params;
+ u32 ssc_sr;
+ u32 ssc_substream_mask;
+ int i;
+
+ ssc_sr = (unsigned long)ssc_readl(ssc_p->ssc->regs, SR)
+ & (unsigned long)ssc_readl(ssc_p->ssc->regs, IMR);
+
+ /*
+ * Loop through the substreams attached to this SSC. If
+ * a DMA-related interrupt occurred on that substream, call
+ * the DMA interrupt handler function, if one has been
+ * registered in the dma_params structure by the PCM driver.
+ */
+ for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) {
+ dma_params = ssc_p->dma_params[i];
+
+ if ((dma_params != NULL) &&
+ (dma_params->dma_intr_handler != NULL)) {
+ ssc_substream_mask = (dma_params->mask->ssc_endx |
+ dma_params->mask->ssc_endbuf);
+ if (ssc_sr & ssc_substream_mask) {
+ dma_params->dma_intr_handler(ssc_sr,
+ dma_params->
+ substream);
+ }
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+
+/*-------------------------------------------------------------------------*\
+ * DAI functions
+\*-------------------------------------------------------------------------*/
+/*
+ * Startup. Only that one substream allowed in each direction.
+ */
+static int atmel_ssc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
+ int dir_mask;
+
+ pr_debug("atmel_ssc_startup: SSC_SR=0x%u\n",
+ ssc_readl(ssc_p->ssc->regs, SR));
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dir_mask = SSC_DIR_MASK_PLAYBACK;
+ else
+ dir_mask = SSC_DIR_MASK_CAPTURE;
+
+ spin_lock_irq(&ssc_p->lock);
+ if (ssc_p->dir_mask & dir_mask) {
+ spin_unlock_irq(&ssc_p->lock);
+ return -EBUSY;
+ }
+ ssc_p->dir_mask |= dir_mask;
+ spin_unlock_irq(&ssc_p->lock);
+
+ return 0;
+}
+
+/*
+ * Shutdown. Clear DMA parameters and shutdown the SSC if there
+ * are no other substreams open.
+ */
+static void atmel_ssc_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
+ struct atmel_pcm_dma_params *dma_params;
+ int dir, dir_mask;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dir = 0;
+ else
+ dir = 1;
+
+ dma_params = ssc_p->dma_params[dir];
+
+ if (dma_params != NULL) {
+ ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable);
+ pr_debug("atmel_ssc_shutdown: %s disabled SSC_SR=0x%08x\n",
+ (dir ? "receive" : "transmit"),
+ ssc_readl(ssc_p->ssc->regs, SR));
+
+ dma_params->ssc = NULL;
+ dma_params->substream = NULL;
+ ssc_p->dma_params[dir] = NULL;
+ }
+
+ dir_mask = 1 << dir;
+
+ spin_lock_irq(&ssc_p->lock);
+ ssc_p->dir_mask &= ~dir_mask;
+ if (!ssc_p->dir_mask) {
+ if (ssc_p->initialized) {
+ /* Shutdown the SSC clock. */
+ pr_debug("atmel_ssc_dau: Stopping clock\n");
+ clk_disable(ssc_p->ssc->clk);
+
+ free_irq(ssc_p->ssc->irq, ssc_p);
+ ssc_p->initialized = 0;
+ }
+
+ /* Reset the SSC */
+ ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
+ /* Clear the SSC dividers */
+ ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0;
+ }
+ spin_unlock_irq(&ssc_p->lock);
+}
+
+
+/*
+ * Record the DAI format for use in hw_params().
+ */
+static int atmel_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct atmel_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
+
+ ssc_p->daifmt = fmt;
+ return 0;
+}
+
+/*
+ * Record SSC clock dividers for use in hw_params().
+ */
+static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct atmel_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
+
+ switch (div_id) {
+ case ATMEL_SSC_CMR_DIV:
+ /*
+ * The same master clock divider is used for both
+ * transmit and receive, so if a value has already
+ * been set, it must match this value.
+ */
+ if (ssc_p->cmr_div == 0)
+ ssc_p->cmr_div = div;
+ else
+ if (div != ssc_p->cmr_div)
+ return -EBUSY;
+ break;
+
+ case ATMEL_SSC_TCMR_PERIOD:
+ ssc_p->tcmr_period = div;
+ break;
+
+ case ATMEL_SSC_RCMR_PERIOD:
+ ssc_p->rcmr_period = div;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Configure the SSC.
+ */
+static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
+ int id = dai->id;
+ struct atmel_ssc_info *ssc_p = &ssc_info[id];
+ struct atmel_pcm_dma_params *dma_params;
+ int dir, channels, bits;
+ u32 tfmr, rfmr, tcmr, rcmr;
+ int start_event;
+ int ret;
+
+ /*
+ * Currently, there is only one set of dma params for
+ * each direction. If more are added, this code will
+ * have to be changed to select the proper set.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dir = 0;
+ else
+ dir = 1;
+
+ dma_params = &ssc_dma_params[id][dir];
+ dma_params->ssc = ssc_p->ssc;
+ dma_params->substream = substream;
+
+ ssc_p->dma_params[dir] = dma_params;
+
+ /*
+ * The snd_soc_pcm_stream->dma_data field is only used to communicate
+ * the appropriate DMA parameters to the pcm driver hw_params()
+ * function. It should not be used for other purposes
+ * as it is common to all substreams.
+ */
+ snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_params);
+
+ channels = params_channels(params);
+
+ /*
+ * Determine sample size in bits and the PDC increment.
+ */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ bits = 8;
+ dma_params->pdc_xfer_size = 1;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bits = 16;
+ dma_params->pdc_xfer_size = 2;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ bits = 24;
+ dma_params->pdc_xfer_size = 4;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bits = 32;
+ dma_params->pdc_xfer_size = 4;
+ break;
+ default:
+ printk(KERN_WARNING "atmel_ssc_dai: unsupported PCM format");
+ return -EINVAL;
+ }
+
+ /*
+ * The SSC only supports up to 16-bit samples in I2S format, due
+ * to the size of the Frame Mode Register FSLEN field.
+ */
+ if ((ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S
+ && bits > 16) {
+ printk(KERN_WARNING
+ "atmel_ssc_dai: sample size %d"
+ "is too large for I2S\n", bits);
+ return -EINVAL;
+ }
+
+ /*
+ * Compute SSC register settings.
+ */
+ switch (ssc_p->daifmt
+ & (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+ /*
+ * I2S format, SSC provides BCLK and LRC clocks.
+ *
+ * The SSC transmit and receive clocks are generated
+ * from the MCK divider, and the BCLK signal
+ * is output on the SSC TK line.
+ */
+ rcmr = SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
+ | SSC_BF(RCMR_STTDLY, START_DELAY)
+ | SSC_BF(RCMR_START, SSC_START_FALLING_RF)
+ | SSC_BF(RCMR_CKI, SSC_CKI_RISING)
+ | SSC_BF(RCMR_CKO, SSC_CKO_NONE)
+ | SSC_BF(RCMR_CKS, SSC_CKS_DIV);
+
+ rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+ | SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE)
+ | SSC_BF(RFMR_FSLEN, (bits - 1))
+ | SSC_BF(RFMR_DATNB, (channels - 1))
+ | SSC_BIT(RFMR_MSBF)
+ | SSC_BF(RFMR_LOOP, 0)
+ | SSC_BF(RFMR_DATLEN, (bits - 1));
+
+ tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
+ | SSC_BF(TCMR_STTDLY, START_DELAY)
+ | SSC_BF(TCMR_START, SSC_START_FALLING_RF)
+ | SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
+ | SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS)
+ | SSC_BF(TCMR_CKS, SSC_CKS_DIV);
+
+ tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+ | SSC_BF(TFMR_FSDEN, 0)
+ | SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE)
+ | SSC_BF(TFMR_FSLEN, (bits - 1))
+ | SSC_BF(TFMR_DATNB, (channels - 1))
+ | SSC_BIT(TFMR_MSBF)
+ | SSC_BF(TFMR_DATDEF, 0)
+ | SSC_BF(TFMR_DATLEN, (bits - 1));
+ break;
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
+ /*
+ * I2S format, CODEC supplies BCLK and LRC clocks.
+ *
+ * The SSC transmit clock is obtained from the BCLK signal on
+ * on the TK line, and the SSC receive clock is
+ * generated from the transmit clock.
+ *
+ * For single channel data, one sample is transferred
+ * on the falling edge of the LRC clock.
+ * For two channel data, one sample is
+ * transferred on both edges of the LRC clock.
+ */
+ start_event = ((channels == 1)
+ ? SSC_START_FALLING_RF
+ : SSC_START_EDGE_RF);
+
+ rcmr = SSC_BF(RCMR_PERIOD, 0)
+ | SSC_BF(RCMR_STTDLY, START_DELAY)
+ | SSC_BF(RCMR_START, start_event)
+ | SSC_BF(RCMR_CKI, SSC_CKI_RISING)
+ | SSC_BF(RCMR_CKO, SSC_CKO_NONE)
+ | SSC_BF(RCMR_CKS, SSC_CKS_CLOCK);
+
+ rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+ | SSC_BF(RFMR_FSOS, SSC_FSOS_NONE)
+ | SSC_BF(RFMR_FSLEN, 0)
+ | SSC_BF(RFMR_DATNB, 0)
+ | SSC_BIT(RFMR_MSBF)
+ | SSC_BF(RFMR_LOOP, 0)
+ | SSC_BF(RFMR_DATLEN, (bits - 1));
+
+ tcmr = SSC_BF(TCMR_PERIOD, 0)
+ | SSC_BF(TCMR_STTDLY, START_DELAY)
+ | SSC_BF(TCMR_START, start_event)
+ | SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
+ | SSC_BF(TCMR_CKO, SSC_CKO_NONE)
+ | SSC_BF(TCMR_CKS, SSC_CKS_PIN);
+
+ tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+ | SSC_BF(TFMR_FSDEN, 0)
+ | SSC_BF(TFMR_FSOS, SSC_FSOS_NONE)
+ | SSC_BF(TFMR_FSLEN, 0)
+ | SSC_BF(TFMR_DATNB, 0)
+ | SSC_BIT(TFMR_MSBF)
+ | SSC_BF(TFMR_DATDEF, 0)
+ | SSC_BF(TFMR_DATLEN, (bits - 1));
+ break;
+
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
+ /*
+ * DSP/PCM Mode A format, SSC provides BCLK and LRC clocks.
+ *
+ * The SSC transmit and receive clocks are generated from the
+ * MCK divider, and the BCLK signal is output
+ * on the SSC TK line.
+ */
+ rcmr = SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
+ | SSC_BF(RCMR_STTDLY, 1)
+ | SSC_BF(RCMR_START, SSC_START_RISING_RF)
+ | SSC_BF(RCMR_CKI, SSC_CKI_RISING)
+ | SSC_BF(RCMR_CKO, SSC_CKO_NONE)
+ | SSC_BF(RCMR_CKS, SSC_CKS_DIV);
+
+ rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+ | SSC_BF(RFMR_FSOS, SSC_FSOS_POSITIVE)
+ | SSC_BF(RFMR_FSLEN, 0)
+ | SSC_BF(RFMR_DATNB, (channels - 1))
+ | SSC_BIT(RFMR_MSBF)
+ | SSC_BF(RFMR_LOOP, 0)
+ | SSC_BF(RFMR_DATLEN, (bits - 1));
+
+ tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
+ | SSC_BF(TCMR_STTDLY, 1)
+ | SSC_BF(TCMR_START, SSC_START_RISING_RF)
+ | SSC_BF(TCMR_CKI, SSC_CKI_RISING)
+ | SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS)
+ | SSC_BF(TCMR_CKS, SSC_CKS_DIV);
+
+ tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+ | SSC_BF(TFMR_FSDEN, 0)
+ | SSC_BF(TFMR_FSOS, SSC_FSOS_POSITIVE)
+ | SSC_BF(TFMR_FSLEN, 0)
+ | SSC_BF(TFMR_DATNB, (channels - 1))
+ | SSC_BIT(TFMR_MSBF)
+ | SSC_BF(TFMR_DATDEF, 0)
+ | SSC_BF(TFMR_DATLEN, (bits - 1));
+ break;
+
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
+ default:
+ printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n",
+ ssc_p->daifmt);
+ return -EINVAL;
+ }
+ pr_debug("atmel_ssc_hw_params: "
+ "RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n",
+ rcmr, rfmr, tcmr, tfmr);
+
+ if (!ssc_p->initialized) {
+
+ /* Enable PMC peripheral clock for this SSC */
+ pr_debug("atmel_ssc_dai: Starting clock\n");
+ clk_enable(ssc_p->ssc->clk);
+
+ /* Reset the SSC and its PDC registers */
+ ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
+
+ ssc_writel(ssc_p->ssc->regs, PDC_RPR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_RCR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_RNPR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_RNCR, 0);
+
+ ssc_writel(ssc_p->ssc->regs, PDC_TPR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_TCR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_TNPR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_TNCR, 0);
+
+ ret = request_irq(ssc_p->ssc->irq, atmel_ssc_interrupt, 0,
+ ssc_p->name, ssc_p);
+ if (ret < 0) {
+ printk(KERN_WARNING
+ "atmel_ssc_dai: request_irq failure\n");
+ pr_debug("Atmel_ssc_dai: Stoping clock\n");
+ clk_disable(ssc_p->ssc->clk);
+ return ret;
+ }
+
+ ssc_p->initialized = 1;
+ }
+
+ /* set SSC clock mode register */
+ ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->cmr_div);
+
+ /* set receive clock mode and format */
+ ssc_writel(ssc_p->ssc->regs, RCMR, rcmr);
+ ssc_writel(ssc_p->ssc->regs, RFMR, rfmr);
+
+ /* set transmit clock mode and format */
+ ssc_writel(ssc_p->ssc->regs, TCMR, tcmr);
+ ssc_writel(ssc_p->ssc->regs, TFMR, tfmr);
+
+ pr_debug("atmel_ssc_dai,hw_params: SSC initialized\n");
+ return 0;
+}
+
+
+static int atmel_ssc_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
+ struct atmel_pcm_dma_params *dma_params;
+ int dir;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dir = 0;
+ else
+ dir = 1;
+
+ dma_params = ssc_p->dma_params[dir];
+
+ ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_enable);
+
+ pr_debug("%s enabled SSC_SR=0x%08x\n",
+ dir ? "receive" : "transmit",
+ ssc_readl(ssc_p->ssc->regs, SR));
+ return 0;
+}
+
+
+#ifdef CONFIG_PM
+static int atmel_ssc_suspend(struct snd_soc_dai *cpu_dai)
+{
+ struct atmel_ssc_info *ssc_p;
+
+ if (!cpu_dai->active)
+ return 0;
+
+ ssc_p = &ssc_info[cpu_dai->id];
+
+ /* Save the status register before disabling transmit and receive */
+ ssc_p->ssc_state.ssc_sr = ssc_readl(ssc_p->ssc->regs, SR);
+ ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS));
+
+ /* Save the current interrupt mask, then disable unmasked interrupts */
+ ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR);
+ ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr);
+
+ ssc_p->ssc_state.ssc_cmr = ssc_readl(ssc_p->ssc->regs, CMR);
+ ssc_p->ssc_state.ssc_rcmr = ssc_readl(ssc_p->ssc->regs, RCMR);
+ ssc_p->ssc_state.ssc_rfmr = ssc_readl(ssc_p->ssc->regs, RFMR);
+ ssc_p->ssc_state.ssc_tcmr = ssc_readl(ssc_p->ssc->regs, TCMR);
+ ssc_p->ssc_state.ssc_tfmr = ssc_readl(ssc_p->ssc->regs, TFMR);
+
+ return 0;
+}
+
+
+
+static int atmel_ssc_resume(struct snd_soc_dai *cpu_dai)
+{
+ struct atmel_ssc_info *ssc_p;
+ u32 cr;
+
+ if (!cpu_dai->active)
+ return 0;
+
+ ssc_p = &ssc_info[cpu_dai->id];
+
+ /* restore SSC register settings */
+ ssc_writel(ssc_p->ssc->regs, TFMR, ssc_p->ssc_state.ssc_tfmr);
+ ssc_writel(ssc_p->ssc->regs, TCMR, ssc_p->ssc_state.ssc_tcmr);
+ ssc_writel(ssc_p->ssc->regs, RFMR, ssc_p->ssc_state.ssc_rfmr);
+ ssc_writel(ssc_p->ssc->regs, RCMR, ssc_p->ssc_state.ssc_rcmr);
+ ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->ssc_state.ssc_cmr);
+
+ /* re-enable interrupts */
+ ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr);
+
+ /* Re-enable receive and transmit as appropriate */
+ cr = 0;
+ cr |=
+ (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_RXEN)) ? SSC_BIT(CR_RXEN) : 0;
+ cr |=
+ (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_TXEN)) ? SSC_BIT(CR_TXEN) : 0;
+ ssc_writel(ssc_p->ssc->regs, CR, cr);
+
+ return 0;
+}
+#else /* CONFIG_PM */
+# define atmel_ssc_suspend NULL
+# define atmel_ssc_resume NULL
+#endif /* CONFIG_PM */
+
+static int atmel_ssc_probe(struct snd_soc_dai *dai)
+{
+ struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
+ int ret = 0;
+
+ snd_soc_dai_set_drvdata(dai, ssc_p);
+
+ /*
+ * Request SSC device
+ */
+ ssc_p->ssc = ssc_request(dai->id);
+ if (IS_ERR(ssc_p->ssc)) {
+ printk(KERN_ERR "ASoC: Failed to request SSC %d\n", dai->id);
+ ret = PTR_ERR(ssc_p->ssc);
+ }
+
+ return ret;
+}
+
+static int atmel_ssc_remove(struct snd_soc_dai *dai)
+{
+ struct atmel_ssc_info *ssc_p = snd_soc_dai_get_drvdata(dai);
+
+ ssc_free(ssc_p->ssc);
+ return 0;
+}
+
+#define ATMEL_SSC_RATES (SNDRV_PCM_RATE_8000_96000)
+
+#define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops atmel_ssc_dai_ops = {
+ .startup = atmel_ssc_startup,
+ .shutdown = atmel_ssc_shutdown,
+ .prepare = atmel_ssc_prepare,
+ .hw_params = atmel_ssc_hw_params,
+ .set_fmt = atmel_ssc_set_dai_fmt,
+ .set_clkdiv = atmel_ssc_set_dai_clkdiv,
+};
+
+static struct snd_soc_dai_driver atmel_ssc_dai[NUM_SSC_DEVICES] = {
+ {
+ .name = "atmel-ssc-dai.0",
+ .probe = atmel_ssc_probe,
+ .remove = atmel_ssc_remove,
+ .suspend = atmel_ssc_suspend,
+ .resume = atmel_ssc_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = ATMEL_SSC_RATES,
+ .formats = ATMEL_SSC_FORMATS,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = ATMEL_SSC_RATES,
+ .formats = ATMEL_SSC_FORMATS,},
+ .ops = &atmel_ssc_dai_ops,
+ },
+#if NUM_SSC_DEVICES == 3
+ {
+ .name = "atmel-ssc-dai.1",
+ .probe = atmel_ssc_probe,
+ .remove = atmel_ssc_remove,
+ .suspend = atmel_ssc_suspend,
+ .resume = atmel_ssc_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = ATMEL_SSC_RATES,
+ .formats = ATMEL_SSC_FORMATS,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = ATMEL_SSC_RATES,
+ .formats = ATMEL_SSC_FORMATS,},
+ .ops = &atmel_ssc_dai_ops,
+ },
+ {
+ .name = "atmel-ssc-dai.2",
+ .probe = atmel_ssc_probe,
+ .remove = atmel_ssc_remove,
+ .suspend = atmel_ssc_suspend,
+ .resume = atmel_ssc_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = ATMEL_SSC_RATES,
+ .formats = ATMEL_SSC_FORMATS,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = ATMEL_SSC_RATES,
+ .formats = ATMEL_SSC_FORMATS,},
+ .ops = &atmel_ssc_dai_ops,
+ },
+#endif
+};
+
+static __devinit int asoc_ssc_probe(struct platform_device *pdev)
+{
+ BUG_ON(pdev->id < 0);
+ BUG_ON(pdev->id >= ARRAY_SIZE(atmel_ssc_dai));
+ return snd_soc_register_dai(&pdev->dev, &atmel_ssc_dai[pdev->id]);
+}
+
+static int __devexit asoc_ssc_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver asoc_ssc_driver = {
+ .driver = {
+ .name = "atmel-ssc-dai",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = asoc_ssc_probe,
+ .remove = __devexit_p(asoc_ssc_remove),
+};
+
+/**
+ * atmel_ssc_set_audio - Allocate the specified SSC for audio use.
+ */
+int atmel_ssc_set_audio(int ssc_id)
+{
+ struct ssc_device *ssc;
+ static struct platform_device *dma_pdev;
+ struct platform_device *ssc_pdev;
+ int ret;
+
+ if (ssc_id < 0 || ssc_id >= ARRAY_SIZE(atmel_ssc_dai))
+ return -EINVAL;
+
+ /* Allocate a dummy device for DMA if we don't have one already */
+ if (!dma_pdev) {
+ dma_pdev = platform_device_alloc("atmel-pcm-audio", -1);
+ if (!dma_pdev)
+ return -ENOMEM;
+
+ ret = platform_device_add(dma_pdev);
+ if (ret < 0) {
+ platform_device_put(dma_pdev);
+ dma_pdev = NULL;
+ return ret;
+ }
+ }
+
+ ssc_pdev = platform_device_alloc("atmel-ssc-dai", ssc_id);
+ if (!ssc_pdev) {
+ ssc_free(ssc);
+ return -ENOMEM;
+ }
+
+ /* If we can grab the SSC briefly to parent the DAI device off it */
+ ssc = ssc_request(ssc_id);
+ if (IS_ERR(ssc))
+ pr_warn("Unable to parent ASoC SSC DAI on SSC: %ld\n",
+ PTR_ERR(ssc));
+ else {
+ ssc_pdev->dev.parent = &(ssc->pdev->dev);
+ ssc_free(ssc);
+ }
+
+ ret = platform_device_add(ssc_pdev);
+ if (ret < 0)
+ platform_device_put(ssc_pdev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(atmel_ssc_set_audio);
+
+static int __init snd_atmel_ssc_init(void)
+{
+ return platform_driver_register(&asoc_ssc_driver);
+}
+module_init(snd_atmel_ssc_init);
+
+static void __exit snd_atmel_ssc_exit(void)
+{
+ platform_driver_unregister(&asoc_ssc_driver);
+}
+module_exit(snd_atmel_ssc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com");
+MODULE_DESCRIPTION("ATMEL SSC ASoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/atmel/atmel_ssc_dai.h b/sound/soc/atmel/atmel_ssc_dai.h
new file mode 100644
index 00000000..5d4f0f9b
--- /dev/null
+++ b/sound/soc/atmel/atmel_ssc_dai.h
@@ -0,0 +1,122 @@
+/*
+ * atmel_ssc_dai.h - ALSA SSC interface for the Atmel SoC
+ *
+ * Copyright (C) 2005 SAN People
+ * Copyright (C) 2008 Atmel
+ *
+ * Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
+ * ATMEL CORP.
+ *
+ * Based on at91-ssc.c by
+ * Frank Mandarino <fmandarino@endrelia.com>
+ * Based on pxa2xx Platform drivers by
+ * Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _ATMEL_SSC_DAI_H
+#define _ATMEL_SSC_DAI_H
+
+#include <linux/types.h>
+#include <linux/atmel-ssc.h>
+
+#include "atmel-pcm.h"
+
+/* SSC system clock ids */
+#define ATMEL_SYSCLK_MCK 0 /* SSC uses AT91 MCK as system clock */
+
+/* SSC divider ids */
+#define ATMEL_SSC_CMR_DIV 0 /* MCK divider for BCLK */
+#define ATMEL_SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */
+#define ATMEL_SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */
+/*
+ * SSC direction masks
+ */
+#define SSC_DIR_MASK_UNUSED 0
+#define SSC_DIR_MASK_PLAYBACK 1
+#define SSC_DIR_MASK_CAPTURE 2
+
+/*
+ * SSC register values that Atmel left out of <linux/atmel-ssc.h>. These
+ * are expected to be used with SSC_BF
+ */
+/* START bit field values */
+#define SSC_START_CONTINUOUS 0
+#define SSC_START_TX_RX 1
+#define SSC_START_LOW_RF 2
+#define SSC_START_HIGH_RF 3
+#define SSC_START_FALLING_RF 4
+#define SSC_START_RISING_RF 5
+#define SSC_START_LEVEL_RF 6
+#define SSC_START_EDGE_RF 7
+#define SSS_START_COMPARE_0 8
+
+/* CKI bit field values */
+#define SSC_CKI_FALLING 0
+#define SSC_CKI_RISING 1
+
+/* CKO bit field values */
+#define SSC_CKO_NONE 0
+#define SSC_CKO_CONTINUOUS 1
+#define SSC_CKO_TRANSFER 2
+
+/* CKS bit field values */
+#define SSC_CKS_DIV 0
+#define SSC_CKS_CLOCK 1
+#define SSC_CKS_PIN 2
+
+/* FSEDGE bit field values */
+#define SSC_FSEDGE_POSITIVE 0
+#define SSC_FSEDGE_NEGATIVE 1
+
+/* FSOS bit field values */
+#define SSC_FSOS_NONE 0
+#define SSC_FSOS_NEGATIVE 1
+#define SSC_FSOS_POSITIVE 2
+#define SSC_FSOS_LOW 3
+#define SSC_FSOS_HIGH 4
+#define SSC_FSOS_TOGGLE 5
+
+#define START_DELAY 1
+
+struct atmel_ssc_state {
+ u32 ssc_cmr;
+ u32 ssc_rcmr;
+ u32 ssc_rfmr;
+ u32 ssc_tcmr;
+ u32 ssc_tfmr;
+ u32 ssc_sr;
+ u32 ssc_imr;
+};
+
+
+struct atmel_ssc_info {
+ char *name;
+ struct ssc_device *ssc;
+ spinlock_t lock; /* lock for dir_mask */
+ unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */
+ unsigned short initialized; /* true if SSC has been initialized */
+ unsigned short daifmt;
+ unsigned short cmr_div;
+ unsigned short tcmr_period;
+ unsigned short rcmr_period;
+ struct atmel_pcm_dma_params *dma_params[2];
+ struct atmel_ssc_state ssc_state;
+};
+
+int atmel_ssc_set_audio(int ssc);
+
+#endif /* _AT91_SSC_DAI_H */
diff --git a/sound/soc/atmel/playpaq_wm8510.c b/sound/soc/atmel/playpaq_wm8510.c
new file mode 100644
index 00000000..1aac2f4d
--- /dev/null
+++ b/sound/soc/atmel/playpaq_wm8510.c
@@ -0,0 +1,471 @@
+/* sound/soc/at32/playpaq_wm8510.c
+ * ASoC machine driver for PlayPaq using WM8510 codec
+ *
+ * Copyright (C) 2008 Long Range Systems
+ * Geoffrey Wossum <gwossum@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This code is largely inspired by sound/soc/at91/eti_b1_wm8731.c
+ *
+ * NOTE: If you don't have the AT32 enhanced portmux configured (which
+ * isn't currently in the mainline or Atmel patched kernel), you will
+ * need to set the MCLK pin (PA30) to peripheral A in your board initialization
+ * code. Something like:
+ * at32_select_periph(GPIO_PIN_PA(30), GPIO_PERIPH_A, 0);
+ *
+ */
+
+/* #define DEBUG */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/at32ap700x.h>
+#include <mach/portmux.h>
+
+#include "../codecs/wm8510.h"
+#include "atmel-pcm.h"
+#include "atmel_ssc_dai.h"
+
+
+/*-------------------------------------------------------------------------*\
+ * constants
+\*-------------------------------------------------------------------------*/
+#define MCLK_PIN GPIO_PIN_PA(30)
+#define MCLK_PERIPH GPIO_PERIPH_A
+
+
+/*-------------------------------------------------------------------------*\
+ * data types
+\*-------------------------------------------------------------------------*/
+/* SSC clocking data */
+struct ssc_clock_data {
+ /* CMR div */
+ unsigned int cmr_div;
+
+ /* Frame period (as needed by xCMR.PERIOD) */
+ unsigned int period;
+
+ /* The SSC clock rate these settings where calculated for */
+ unsigned long ssc_rate;
+};
+
+
+/*-------------------------------------------------------------------------*\
+ * module data
+\*-------------------------------------------------------------------------*/
+static struct clk *_gclk0;
+static struct clk *_pll0;
+
+#define CODEC_CLK (_gclk0)
+
+
+/*-------------------------------------------------------------------------*\
+ * Sound SOC operations
+\*-------------------------------------------------------------------------*/
+#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssc_device *ssc = ssc_p->ssc;
+ struct ssc_clock_data cd;
+ unsigned int rate, width_bits, channels;
+ unsigned int bitrate, ssc_div;
+ unsigned actual_rate;
+
+
+ /*
+ * Figure out required bitrate
+ */
+ rate = params_rate(params);
+ channels = params_channels(params);
+ width_bits = snd_pcm_format_physical_width(params_format(params));
+ bitrate = rate * width_bits * channels;
+
+
+ /*
+ * Figure out required SSC divider and period for required bitrate
+ */
+ cd.ssc_rate = clk_get_rate(ssc->clk);
+ ssc_div = cd.ssc_rate / bitrate;
+ cd.cmr_div = ssc_div / 2;
+ if (ssc_div & 1) {
+ /* round cmr_div up */
+ cd.cmr_div++;
+ }
+ cd.period = width_bits - 1;
+
+
+ /*
+ * Find actual rate, compare to requested rate
+ */
+ actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));
+ pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n",
+ rate, actual_rate);
+
+
+ return cd;
+}
+#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
+
+
+
+static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssc_device *ssc = ssc_p->ssc;
+ unsigned int pll_out = 0, bclk = 0, mclk_div = 0;
+ int ret;
+
+
+ /* Due to difficulties with getting the correct clocks from the AT32's
+ * PLL0, we're going to let the CODEC be in charge of all the clocks
+ */
+#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+ const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+#else
+ struct ssc_clock_data cd;
+ const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+#endif
+
+ if (ssc == NULL) {
+ pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");
+ return -EINVAL;
+ }
+
+
+ /*
+ * Figure out PLL and BCLK dividers for WM8510
+ */
+ switch (params_rate(params)) {
+ case 48000:
+ pll_out = 24576000;
+ mclk_div = WM8510_MCLKDIV_2;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ case 44100:
+ pll_out = 22579200;
+ mclk_div = WM8510_MCLKDIV_2;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ case 22050:
+ pll_out = 22579200;
+ mclk_div = WM8510_MCLKDIV_4;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ case 16000:
+ pll_out = 24576000;
+ mclk_div = WM8510_MCLKDIV_6;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ case 11025:
+ pll_out = 22579200;
+ mclk_div = WM8510_MCLKDIV_8;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ case 8000:
+ pll_out = 24576000;
+ mclk_div = WM8510_MCLKDIV_12;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ default:
+ pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+
+ /*
+ * set CPU and CODEC DAI configuration
+ */
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: "
+ "Failed to set CODEC DAI format (%d)\n",
+ ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: "
+ "Failed to set CPU DAI format (%d)\n",
+ ret);
+ return ret;
+ }
+
+
+ /*
+ * Set CPU clock configuration
+ */
+#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+ cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai);
+ pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n",
+ cd.cmr_div, cd.period);
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
+ ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
+ cd.period);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: "
+ "Failed to set CPU transmit period (%d)\n",
+ ret);
+ return ret;
+ }
+#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
+
+
+ /*
+ * Set CODEC clock configuration
+ */
+ pr_debug("playpaq_wm8510: "
+ "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n",
+ clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div);
+
+
+#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
+ if (ret < 0) {
+ pr_warning
+ ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",
+ ret);
+ return ret;
+ }
+#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
+
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0, 0,
+ clk_get_rate(CODEC_CLK), pll_out);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",
+ ret);
+ return ret;
+ }
+
+
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",
+ ret);
+ return ret;
+ }
+
+
+ return 0;
+}
+
+
+
+static struct snd_soc_ops playpaq_wm8510_ops = {
+ .hw_params = playpaq_wm8510_hw_params,
+};
+
+
+
+static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Int Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* speaker connected to SPKOUT */
+ {"Ext Spk", NULL, "SPKOUTP"},
+ {"Ext Spk", NULL, "SPKOUTN"},
+
+ {"Mic Bias", NULL, "Int Mic"},
+ {"MICN", NULL, "Mic Bias"},
+ {"MICP", NULL, "Mic Bias"},
+};
+
+
+
+static int playpaq_wm8510_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int i;
+
+ /*
+ * Add DAPM widgets
+ */
+ for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
+ snd_soc_dapm_new_control(dapm, &playpaq_dapm_widgets[i]);
+
+
+
+ /*
+ * Setup audio path interconnects
+ */
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+
+
+ /* always connected pins */
+ snd_soc_dapm_enable_pin(dapm, "Int Mic");
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ snd_soc_dapm_sync(dapm);
+
+
+
+ /* Make CSB show PLL rate */
+ snd_soc_dai_set_clkdiv(rtd->codec_dai, WM8510_OPCLKDIV,
+ WM8510_OPCLKDIV_1 | 4);
+
+ return 0;
+}
+
+
+
+static struct snd_soc_dai_link playpaq_wm8510_dai = {
+ .name = "WM8510",
+ .stream_name = "WM8510 PCM",
+ .cpu_dai_name= "atmel-ssc-dai.0",
+ .platform_name = "atmel-pcm-audio",
+ .codec_name = "wm8510-codec.0-0x1a",
+ .codec_dai_name = "wm8510-hifi",
+ .init = playpaq_wm8510_init,
+ .ops = &playpaq_wm8510_ops,
+};
+
+
+
+static struct snd_soc_card snd_soc_playpaq = {
+ .name = "LRS_PlayPaq_WM8510",
+ .dai_link = &playpaq_wm8510_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *playpaq_snd_device;
+
+
+static int __init playpaq_asoc_init(void)
+{
+ int ret = 0;
+
+ /*
+ * Configure MCLK for WM8510
+ */
+ _gclk0 = clk_get(NULL, "gclk0");
+ if (IS_ERR(_gclk0)) {
+ _gclk0 = NULL;
+ goto err_gclk0;
+ }
+ _pll0 = clk_get(NULL, "pll0");
+ if (IS_ERR(_pll0)) {
+ _pll0 = NULL;
+ goto err_pll0;
+ }
+ if (clk_set_parent(_gclk0, _pll0)) {
+ pr_warning("snd-soc-playpaq: "
+ "Failed to set PLL0 as parent for DAC clock\n");
+ goto err_set_clk;
+ }
+ clk_set_rate(CODEC_CLK, 12000000);
+ clk_enable(CODEC_CLK);
+
+#if defined CONFIG_AT32_ENHANCED_PORTMUX
+ at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
+#endif
+
+
+ /*
+ * Create and register platform device
+ */
+ playpaq_snd_device = platform_device_alloc("soc-audio", 0);
+ if (playpaq_snd_device == NULL) {
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
+
+ platform_set_drvdata(playpaq_snd_device, &snd_soc_playpaq);
+
+ ret = platform_device_add(playpaq_snd_device);
+ if (ret) {
+ pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
+ ret);
+ goto err_device_add;
+ }
+
+ return 0;
+
+
+err_device_add:
+ if (playpaq_snd_device != NULL) {
+ platform_device_put(playpaq_snd_device);
+ playpaq_snd_device = NULL;
+ }
+err_device_alloc:
+err_set_clk:
+ if (_pll0 != NULL) {
+ clk_put(_pll0);
+ _pll0 = NULL;
+ }
+err_pll0:
+ if (_gclk0 != NULL) {
+ clk_put(_gclk0);
+ _gclk0 = NULL;
+ }
+ return ret;
+}
+
+
+static void __exit playpaq_asoc_exit(void)
+{
+ if (_gclk0 != NULL) {
+ clk_put(_gclk0);
+ _gclk0 = NULL;
+ }
+ if (_pll0 != NULL) {
+ clk_put(_pll0);
+ _pll0 = NULL;
+ }
+
+#if defined CONFIG_AT32_ENHANCED_PORTMUX
+ at32_free_pin(MCLK_PIN);
+#endif
+
+ platform_device_unregister(playpaq_snd_device);
+ playpaq_snd_device = NULL;
+}
+
+module_init(playpaq_asoc_init);
+module_exit(playpaq_asoc_exit);
+
+MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
+MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c
new file mode 100644
index 00000000..95572d29
--- /dev/null
+++ b/sound/soc/atmel/sam9g20_wm8731.c
@@ -0,0 +1,280 @@
+/*
+ * sam9g20_wm8731 -- SoC audio for AT91SAM9G20-based
+ * ATMEL AT91SAM9G20ek board.
+ *
+ * Copyright (C) 2005 SAN People
+ * Copyright (C) 2008 Atmel
+ *
+ * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
+ *
+ * Based on ati_b1_wm8731.c by:
+ * Frank Mandarino <fmandarino@endrelia.com>
+ * Copyright 2006 Endrelia Technologies Inc.
+ * Based on corgi.c by:
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+
+#include <linux/atmel-ssc.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+
+#include "../codecs/wm8731.h"
+#include "atmel-pcm.h"
+#include "atmel_ssc_dai.h"
+
+#define MCLK_RATE 12000000
+
+/*
+ * As shipped the board does not have inputs. However, it is relatively
+ * straightforward to modify the board to hook them up so support is left
+ * in the driver.
+ */
+#undef ENABLE_MIC_INPUT
+
+static struct clk *mclk;
+
+static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops at91sam9g20ek_ops = {
+ .hw_params = at91sam9g20ek_hw_params,
+};
+
+static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card,
+ enum snd_soc_bias_level level)
+{
+ static int mclk_on;
+ int ret = 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ if (!mclk_on)
+ ret = clk_enable(mclk);
+ if (ret == 0)
+ mclk_on = 1;
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ case SND_SOC_BIAS_STANDBY:
+ if (mclk_on)
+ clk_disable(mclk);
+ mclk_on = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Int Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+
+ /* speaker connected to LHPOUT */
+ {"Ext Spk", NULL, "LHPOUT"},
+
+ /* mic is connected to Mic Jack, with WM8731 Mic Bias */
+ {"MICIN", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Int Mic"},
+};
+
+/*
+ * Logic for a wm8731 as connected on a at91sam9g20ek board.
+ */
+static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ printk(KERN_DEBUG
+ "at91sam9g20ek_wm8731 "
+ ": at91sam9g20ek_wm8731_init() called\n");
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_MCLK,
+ MCLK_RATE, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "Failed to set WM8731 SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ /* Add specific widgets */
+ snd_soc_dapm_new_controls(dapm, at91sam9g20ek_dapm_widgets,
+ ARRAY_SIZE(at91sam9g20ek_dapm_widgets));
+ /* Set up specific audio path interconnects */
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ /* not connected */
+ snd_soc_dapm_nc_pin(dapm, "RLINEIN");
+ snd_soc_dapm_nc_pin(dapm, "LLINEIN");
+
+#ifdef ENABLE_MIC_INPUT
+ snd_soc_dapm_enable_pin(dapm, "Int Mic");
+#else
+ snd_soc_dapm_nc_pin(dapm, "Int Mic");
+#endif
+
+ /* always connected */
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link at91sam9g20ek_dai = {
+ .name = "WM8731",
+ .stream_name = "WM8731 PCM",
+ .cpu_dai_name = "atmel-ssc-dai.0",
+ .codec_dai_name = "wm8731-hifi",
+ .init = at91sam9g20ek_wm8731_init,
+ .platform_name = "atmel-pcm-audio",
+ .codec_name = "wm8731.0-001b",
+ .ops = &at91sam9g20ek_ops,
+};
+
+static struct snd_soc_card snd_soc_at91sam9g20ek = {
+ .name = "AT91SAMG20-EK",
+ .dai_link = &at91sam9g20ek_dai,
+ .num_links = 1,
+ .set_bias_level = at91sam9g20ek_set_bias_level,
+};
+
+static struct platform_device *at91sam9g20ek_snd_device;
+
+static int __init at91sam9g20ek_init(void)
+{
+ struct clk *pllb;
+ int ret;
+
+ if (!(machine_is_at91sam9g20ek() || machine_is_at91sam9g20ek_2mmc()))
+ return -ENODEV;
+
+ ret = atmel_ssc_set_audio(0);
+ if (ret != 0) {
+ pr_err("Failed to set SSC 0 for audio: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Codec MCLK is supplied by PCK0 - set it up.
+ */
+ mclk = clk_get(NULL, "pck0");
+ if (IS_ERR(mclk)) {
+ printk(KERN_ERR "ASoC: Failed to get MCLK\n");
+ ret = PTR_ERR(mclk);
+ goto err;
+ }
+
+ pllb = clk_get(NULL, "pllb");
+ if (IS_ERR(pllb)) {
+ printk(KERN_ERR "ASoC: Failed to get PLLB\n");
+ ret = PTR_ERR(pllb);
+ goto err_mclk;
+ }
+ ret = clk_set_parent(mclk, pllb);
+ clk_put(pllb);
+ if (ret != 0) {
+ printk(KERN_ERR "ASoC: Failed to set MCLK parent\n");
+ goto err_mclk;
+ }
+
+ clk_set_rate(mclk, MCLK_RATE);
+
+ at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!at91sam9g20ek_snd_device) {
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+ ret = -ENOMEM;
+ goto err_mclk;
+ }
+
+ platform_set_drvdata(at91sam9g20ek_snd_device,
+ &snd_soc_at91sam9g20ek);
+
+ ret = platform_device_add(at91sam9g20ek_snd_device);
+ if (ret) {
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+ goto err_device_add;
+ }
+
+ return ret;
+
+err_device_add:
+ platform_device_put(at91sam9g20ek_snd_device);
+err_mclk:
+ clk_put(mclk);
+ mclk = NULL;
+err:
+ return ret;
+}
+
+static void __exit at91sam9g20ek_exit(void)
+{
+ platform_device_unregister(at91sam9g20ek_snd_device);
+ at91sam9g20ek_snd_device = NULL;
+ clk_put(mclk);
+ mclk = NULL;
+}
+
+module_init(at91sam9g20ek_init);
+module_exit(at91sam9g20ek_exit);
+
+/* Module information */
+MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
+MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/atmel/snd-soc-afeb9260.c b/sound/soc/atmel/snd-soc-afeb9260.c
new file mode 100644
index 00000000..5e4d499d
--- /dev/null
+++ b/sound/soc/atmel/snd-soc-afeb9260.c
@@ -0,0 +1,185 @@
+/*
+ * afeb9260.c -- SoC audio for AFEB9260
+ *
+ * Copyright (C) 2009 Sergey Lapin <slapin@ossfans.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+
+#include <linux/atmel-ssc.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <linux/gpio.h>
+
+#include "../codecs/tlv320aic23.h"
+#include "atmel-pcm.h"
+#include "atmel_ssc_dai.h"
+
+#define CODEC_CLOCK 12000000
+
+static int afeb9260_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int err;
+
+ /* Set codec DAI configuration */
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S|
+ SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return err;
+ }
+
+ /* Set cpu DAI configuration */
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return err;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ err =
+ snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN);
+
+ if (err < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return err;
+ }
+
+ return err;
+}
+
+static struct snd_soc_ops afeb9260_ops = {
+ .hw_params = afeb9260_hw_params,
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Jack", NULL, "LHPOUT"},
+ {"Headphone Jack", NULL, "RHPOUT"},
+
+ {"LLINEIN", NULL, "Line In"},
+ {"RLINEIN", NULL, "Line In"},
+
+ {"MICIN", NULL, "Mic Jack"},
+};
+
+static int afeb9260_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* Add afeb9260 specific widgets */
+ snd_soc_dapm_new_controls(dapm, tlv320aic23_dapm_widgets,
+ ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+ /* Set up afeb9260 specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Line In");
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link afeb9260_dai = {
+ .name = "TLV320AIC23",
+ .stream_name = "AIC23",
+ .cpu_dai_name = "atmel-ssc-dai.0",
+ .codec_dai_name = "tlv320aic23-hifi",
+ .platform_name = "atmel_pcm-audio",
+ .codec_name = "tlv320aic23-codec.0-001a",
+ .init = afeb9260_tlv320aic23_init,
+ .ops = &afeb9260_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_machine_afeb9260 = {
+ .name = "AFEB9260",
+ .dai_link = &afeb9260_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *afeb9260_snd_device;
+
+static int __init afeb9260_soc_init(void)
+{
+ int err;
+ struct device *dev;
+
+ if (!(machine_is_afeb9260()))
+ return -ENODEV;
+
+
+ afeb9260_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!afeb9260_snd_device) {
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(afeb9260_snd_device, &snd_soc_machine_afeb9260);
+ err = platform_device_add(afeb9260_snd_device);
+ if (err)
+ goto err1;
+
+ dev = &afeb9260_snd_device->dev;
+
+ return 0;
+err1:
+ platform_device_put(afeb9260_snd_device);
+ return err;
+}
+
+static void __exit afeb9260_soc_exit(void)
+{
+ platform_device_unregister(afeb9260_snd_device);
+}
+
+module_init(afeb9260_soc_init);
+module_exit(afeb9260_soc_exit);
+
+MODULE_AUTHOR("Sergey Lapin <slapin@ossfans.org>");
+MODULE_DESCRIPTION("ALSA SoC for AFEB9260");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig
new file mode 100644
index 00000000..4b67140f
--- /dev/null
+++ b/sound/soc/au1x/Kconfig
@@ -0,0 +1,34 @@
+##
+## Au1200/Au1550 PSC + DBDMA
+##
+config SND_SOC_AU1XPSC
+ tristate "SoC Audio for Au1200/Au1250/Au1550"
+ depends on SOC_AU1200 || SOC_AU1550
+ help
+ This option enables support for the Programmable Serial
+ Controllers in AC97 and I2S mode, and the Descriptor-Based DMA
+ Controller (DBDMA) as found on the Au1200/Au1250/Au1550 SoC.
+
+config SND_SOC_AU1XPSC_I2S
+ tristate
+
+config SND_SOC_AU1XPSC_AC97
+ tristate
+ select AC97_BUS
+ select SND_AC97_CODEC
+ select SND_SOC_AC97_BUS
+
+
+##
+## Boards
+##
+config SND_SOC_DB1200
+ tristate "DB1200 AC97+I2S audio support"
+ depends on SND_SOC_AU1XPSC
+ select SND_SOC_AU1XPSC_AC97
+ select SND_SOC_AC97_CODEC
+ select SND_SOC_AU1XPSC_I2S
+ select SND_SOC_WM8731
+ help
+ Select this option to enable audio (AC97 or I2S) on the
+ Alchemy/AMD/RMI DB1200 demoboard.
diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile
new file mode 100644
index 00000000..16873076
--- /dev/null
+++ b/sound/soc/au1x/Makefile
@@ -0,0 +1,13 @@
+# Au1200/Au1550 PSC audio
+snd-soc-au1xpsc-dbdma-objs := dbdma2.o
+snd-soc-au1xpsc-i2s-objs := psc-i2s.o
+snd-soc-au1xpsc-ac97-objs := psc-ac97.o
+
+obj-$(CONFIG_SND_SOC_AU1XPSC) += snd-soc-au1xpsc-dbdma.o
+obj-$(CONFIG_SND_SOC_AU1XPSC_I2S) += snd-soc-au1xpsc-i2s.o
+obj-$(CONFIG_SND_SOC_AU1XPSC_AC97) += snd-soc-au1xpsc-ac97.o
+
+# Boards
+snd-soc-db1200-objs := db1200.o
+
+obj-$(CONFIG_SND_SOC_DB1200) += snd-soc-db1200.o
diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c
new file mode 100644
index 00000000..1d3e258c
--- /dev/null
+++ b/sound/soc/au1x/db1200.c
@@ -0,0 +1,129 @@
+/*
+ * DB1200 ASoC audio fabric support code.
+ *
+ * (c) 2008-9 Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+#include <asm/mach-au1x00/au1xxx_dbdma.h>
+#include <asm/mach-db1x00/bcsr.h>
+
+#include "../codecs/wm8731.h"
+#include "psc.h"
+
+/*------------------------- AC97 PART ---------------------------*/
+
+static struct snd_soc_dai_link db1200_ac97_dai = {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .codec_dai_name = "ac97-hifi",
+ .cpu_dai_name = "au1xpsc_ac97.1",
+ .platform_name = "au1xpsc-pcm.1",
+ .codec_name = "ac97-codec.1",
+};
+
+static struct snd_soc_card db1200_ac97_machine = {
+ .name = "DB1200_AC97",
+ .dai_link = &db1200_ac97_dai,
+ .num_links = 1,
+};
+
+/*------------------------- I2S PART ---------------------------*/
+
+static int db1200_i2s_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* WM8731 has its own 12MHz crystal */
+ snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
+ 12000000, SND_SOC_CLOCK_IN);
+
+ /* codec is bitclock and lrclk master */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ goto out;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ goto out;
+
+ ret = 0;
+out:
+ return ret;
+}
+
+static struct snd_soc_ops db1200_i2s_wm8731_ops = {
+ .startup = db1200_i2s_startup,
+};
+
+static struct snd_soc_dai_link db1200_i2s_dai = {
+ .name = "WM8731",
+ .stream_name = "WM8731 PCM",
+ .codec_dai_name = "wm8731-hifi",
+ .cpu_dai_name = "au1xpsc_i2s.1",
+ .platform_name = "au1xpsc-pcm.1",
+ .codec_name = "wm8731.0-001b",
+ .ops = &db1200_i2s_wm8731_ops,
+};
+
+static struct snd_soc_card db1200_i2s_machine = {
+ .name = "DB1200_I2S",
+ .dai_link = &db1200_i2s_dai,
+ .num_links = 1,
+};
+
+/*------------------------- COMMON PART ---------------------------*/
+
+static struct platform_device *db1200_asoc_dev;
+
+static int __init db1200_audio_load(void)
+{
+ int ret;
+
+ ret = -ENOMEM;
+ db1200_asoc_dev = platform_device_alloc("soc-audio", 1); /* PSC1 */
+ if (!db1200_asoc_dev)
+ goto out;
+
+ /* DB1200 board setup set PSC1MUX to preferred audio device */
+ if (bcsr_read(BCSR_RESETS) & BCSR_RESETS_PSC1MUX)
+ platform_set_drvdata(db1200_asoc_dev, &db1200_i2s_machine);
+ else
+ platform_set_drvdata(db1200_asoc_dev, &db1200_ac97_machine);
+
+ ret = platform_device_add(db1200_asoc_dev);
+
+ if (ret) {
+ platform_device_put(db1200_asoc_dev);
+ db1200_asoc_dev = NULL;
+ }
+out:
+ return ret;
+}
+
+static void __exit db1200_audio_unload(void)
+{
+ platform_device_unregister(db1200_asoc_dev);
+}
+
+module_init(db1200_audio_load);
+module_exit(db1200_audio_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DB1200 ASoC audio support");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/sound/soc/au1x/dbdma2.c b/sound/soc/au1x/dbdma2.c
new file mode 100644
index 00000000..10fdd285
--- /dev/null
+++ b/sound/soc/au1x/dbdma2.c
@@ -0,0 +1,460 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ * Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * DMA glue for Au1x-PSC audio.
+ *
+ */
+
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_dbdma.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+
+#include "psc.h"
+
+/*#define PCM_DEBUG*/
+
+#define MSG(x...) printk(KERN_INFO "au1xpsc_pcm: " x)
+#ifdef PCM_DEBUG
+#define DBG MSG
+#else
+#define DBG(x...) do {} while (0)
+#endif
+
+struct au1xpsc_audio_dmadata {
+ /* DDMA control data */
+ unsigned int ddma_id; /* DDMA direction ID for this PSC */
+ u32 ddma_chan; /* DDMA context */
+
+ /* PCM context (for irq handlers) */
+ struct snd_pcm_substream *substream;
+ unsigned long curr_period; /* current segment DDMA is working on */
+ unsigned long q_period; /* queue period(s) */
+ dma_addr_t dma_area; /* address of queued DMA area */
+ dma_addr_t dma_area_s; /* start address of DMA area */
+ unsigned long pos; /* current byte position being played */
+ unsigned long periods; /* number of SG segments in total */
+ unsigned long period_bytes; /* size in bytes of one SG segment */
+
+ /* runtime data */
+ int msbits;
+};
+
+/*
+ * These settings are somewhat okay, at least on my machine audio plays
+ * almost skip-free. Especially the 64kB buffer seems to help a LOT.
+ */
+#define AU1XPSC_PERIOD_MIN_BYTES 1024
+#define AU1XPSC_BUFFER_MIN_BYTES 65536
+
+#define AU1XPSC_PCM_FMTS \
+ (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE | \
+ SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE | \
+ 0)
+
+/* PCM hardware DMA capabilities - platform specific */
+static const struct snd_pcm_hardware au1xpsc_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH,
+ .formats = AU1XPSC_PCM_FMTS,
+ .period_bytes_min = AU1XPSC_PERIOD_MIN_BYTES,
+ .period_bytes_max = 4096 * 1024 - 1,
+ .periods_min = 2,
+ .periods_max = 4096, /* 2 to as-much-as-you-like */
+ .buffer_bytes_max = 4096 * 1024 - 1,
+ .fifo_size = 16, /* fifo entries of AC97/I2S PSC */
+};
+
+static void au1x_pcm_queue_tx(struct au1xpsc_audio_dmadata *cd)
+{
+ au1xxx_dbdma_put_source(cd->ddma_chan, cd->dma_area,
+ cd->period_bytes, DDMA_FLAGS_IE);
+
+ /* update next-to-queue period */
+ ++cd->q_period;
+ cd->dma_area += cd->period_bytes;
+ if (cd->q_period >= cd->periods) {
+ cd->q_period = 0;
+ cd->dma_area = cd->dma_area_s;
+ }
+}
+
+static void au1x_pcm_queue_rx(struct au1xpsc_audio_dmadata *cd)
+{
+ au1xxx_dbdma_put_dest(cd->ddma_chan, cd->dma_area,
+ cd->period_bytes, DDMA_FLAGS_IE);
+
+ /* update next-to-queue period */
+ ++cd->q_period;
+ cd->dma_area += cd->period_bytes;
+ if (cd->q_period >= cd->periods) {
+ cd->q_period = 0;
+ cd->dma_area = cd->dma_area_s;
+ }
+}
+
+static void au1x_pcm_dmatx_cb(int irq, void *dev_id)
+{
+ struct au1xpsc_audio_dmadata *cd = dev_id;
+
+ cd->pos += cd->period_bytes;
+ if (++cd->curr_period >= cd->periods) {
+ cd->pos = 0;
+ cd->curr_period = 0;
+ }
+ snd_pcm_period_elapsed(cd->substream);
+ au1x_pcm_queue_tx(cd);
+}
+
+static void au1x_pcm_dmarx_cb(int irq, void *dev_id)
+{
+ struct au1xpsc_audio_dmadata *cd = dev_id;
+
+ cd->pos += cd->period_bytes;
+ if (++cd->curr_period >= cd->periods) {
+ cd->pos = 0;
+ cd->curr_period = 0;
+ }
+ snd_pcm_period_elapsed(cd->substream);
+ au1x_pcm_queue_rx(cd);
+}
+
+static void au1x_pcm_dbdma_free(struct au1xpsc_audio_dmadata *pcd)
+{
+ if (pcd->ddma_chan) {
+ au1xxx_dbdma_stop(pcd->ddma_chan);
+ au1xxx_dbdma_reset(pcd->ddma_chan);
+ au1xxx_dbdma_chan_free(pcd->ddma_chan);
+ pcd->ddma_chan = 0;
+ pcd->msbits = 0;
+ }
+}
+
+/* in case of missing DMA ring or changed TX-source / RX-dest bit widths,
+ * allocate (or reallocate) a 2-descriptor DMA ring with bit depth according
+ * to ALSA-supplied sample depth. This is due to limitations in the dbdma api
+ * (cannot adjust source/dest widths of already allocated descriptor ring).
+ */
+static int au1x_pcm_dbdma_realloc(struct au1xpsc_audio_dmadata *pcd,
+ int stype, int msbits)
+{
+ /* DMA only in 8/16/32 bit widths */
+ if (msbits == 24)
+ msbits = 32;
+
+ /* check current config: correct bits and descriptors allocated? */
+ if ((pcd->ddma_chan) && (msbits == pcd->msbits))
+ goto out; /* all ok! */
+
+ au1x_pcm_dbdma_free(pcd);
+
+ if (stype == PCM_RX)
+ pcd->ddma_chan = au1xxx_dbdma_chan_alloc(pcd->ddma_id,
+ DSCR_CMD0_ALWAYS,
+ au1x_pcm_dmarx_cb, (void *)pcd);
+ else
+ pcd->ddma_chan = au1xxx_dbdma_chan_alloc(DSCR_CMD0_ALWAYS,
+ pcd->ddma_id,
+ au1x_pcm_dmatx_cb, (void *)pcd);
+
+ if (!pcd->ddma_chan)
+ return -ENOMEM;
+
+ au1xxx_dbdma_set_devwidth(pcd->ddma_chan, msbits);
+ au1xxx_dbdma_ring_alloc(pcd->ddma_chan, 2);
+
+ pcd->msbits = msbits;
+
+ au1xxx_dbdma_stop(pcd->ddma_chan);
+ au1xxx_dbdma_reset(pcd->ddma_chan);
+
+out:
+ return 0;
+}
+
+static inline struct au1xpsc_audio_dmadata *to_dmadata(struct snd_pcm_substream *ss)
+{
+ struct snd_soc_pcm_runtime *rtd = ss->private_data;
+ struct au1xpsc_audio_dmadata *pcd =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ return &pcd[SUBSTREAM_TYPE(ss)];
+}
+
+static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct au1xpsc_audio_dmadata *pcd;
+ int stype, ret;
+
+ ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ if (ret < 0)
+ goto out;
+
+ stype = SUBSTREAM_TYPE(substream);
+ pcd = to_dmadata(substream);
+
+ DBG("runtime->dma_area = 0x%08lx dma_addr_t = 0x%08lx dma_size = %d "
+ "runtime->min_align %d\n",
+ (unsigned long)runtime->dma_area,
+ (unsigned long)runtime->dma_addr, runtime->dma_bytes,
+ runtime->min_align);
+
+ DBG("bits %d frags %d frag_bytes %d is_rx %d\n", params->msbits,
+ params_periods(params), params_period_bytes(params), stype);
+
+ ret = au1x_pcm_dbdma_realloc(pcd, stype, params->msbits);
+ if (ret) {
+ MSG("DDMA channel (re)alloc failed!\n");
+ goto out;
+ }
+
+ pcd->substream = substream;
+ pcd->period_bytes = params_period_bytes(params);
+ pcd->periods = params_periods(params);
+ pcd->dma_area_s = pcd->dma_area = runtime->dma_addr;
+ pcd->q_period = 0;
+ pcd->curr_period = 0;
+ pcd->pos = 0;
+
+ ret = 0;
+out:
+ return ret;
+}
+
+static int au1xpsc_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_lib_free_pages(substream);
+ return 0;
+}
+
+static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct au1xpsc_audio_dmadata *pcd = to_dmadata(substream);
+
+ au1xxx_dbdma_reset(pcd->ddma_chan);
+
+ if (SUBSTREAM_TYPE(substream) == PCM_RX) {
+ au1x_pcm_queue_rx(pcd);
+ au1x_pcm_queue_rx(pcd);
+ } else {
+ au1x_pcm_queue_tx(pcd);
+ au1x_pcm_queue_tx(pcd);
+ }
+
+ return 0;
+}
+
+static int au1xpsc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ u32 c = to_dmadata(substream)->ddma_chan;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ au1xxx_dbdma_start(c);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ au1xxx_dbdma_stop(c);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static snd_pcm_uframes_t
+au1xpsc_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ return bytes_to_frames(substream->runtime, to_dmadata(substream)->pos);
+}
+
+static int au1xpsc_pcm_open(struct snd_pcm_substream *substream)
+{
+ snd_soc_set_runtime_hwparams(substream, &au1xpsc_pcm_hardware);
+ return 0;
+}
+
+static int au1xpsc_pcm_close(struct snd_pcm_substream *substream)
+{
+ au1x_pcm_dbdma_free(to_dmadata(substream));
+ return 0;
+}
+
+static struct snd_pcm_ops au1xpsc_pcm_ops = {
+ .open = au1xpsc_pcm_open,
+ .close = au1xpsc_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = au1xpsc_pcm_hw_params,
+ .hw_free = au1xpsc_pcm_hw_free,
+ .prepare = au1xpsc_pcm_prepare,
+ .trigger = au1xpsc_pcm_trigger,
+ .pointer = au1xpsc_pcm_pointer,
+};
+
+static void au1xpsc_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int au1xpsc_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev, AU1XPSC_BUFFER_MIN_BYTES, (4096 * 1024) - 1);
+
+ return 0;
+}
+
+/* au1xpsc audio platform */
+struct snd_soc_platform_driver au1xpsc_soc_platform = {
+ .ops = &au1xpsc_pcm_ops,
+ .pcm_new = au1xpsc_pcm_new,
+ .pcm_free = au1xpsc_pcm_free_dma_buffers,
+};
+
+static int __devinit au1xpsc_pcm_drvprobe(struct platform_device *pdev)
+{
+ struct au1xpsc_audio_dmadata *dmadata;
+ struct resource *r;
+ int ret;
+
+ dmadata = kzalloc(2 * sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL);
+ if (!dmadata)
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!r) {
+ ret = -ENODEV;
+ goto out1;
+ }
+ dmadata[PCM_TX].ddma_id = r->start;
+
+ /* RX DMA */
+ r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!r) {
+ ret = -ENODEV;
+ goto out1;
+ }
+ dmadata[PCM_RX].ddma_id = r->start;
+
+ platform_set_drvdata(pdev, dmadata);
+
+ ret = snd_soc_register_platform(&pdev->dev, &au1xpsc_soc_platform);
+ if (!ret)
+ return ret;
+
+out1:
+ kfree(dmadata);
+ return ret;
+}
+
+static int __devexit au1xpsc_pcm_drvremove(struct platform_device *pdev)
+{
+ struct au1xpsc_audio_dmadata *dmadata = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_platform(&pdev->dev);
+ kfree(dmadata);
+
+ return 0;
+}
+
+static struct platform_driver au1xpsc_pcm_driver = {
+ .driver = {
+ .name = "au1xpsc-pcm",
+ .owner = THIS_MODULE,
+ },
+ .probe = au1xpsc_pcm_drvprobe,
+ .remove = __devexit_p(au1xpsc_pcm_drvremove),
+};
+
+static int __init au1xpsc_audio_dbdma_load(void)
+{
+ return platform_driver_register(&au1xpsc_pcm_driver);
+}
+
+static void __exit au1xpsc_audio_dbdma_unload(void)
+{
+ platform_driver_unregister(&au1xpsc_pcm_driver);
+}
+
+module_init(au1xpsc_audio_dbdma_load);
+module_exit(au1xpsc_audio_dbdma_unload);
+
+
+struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev)
+{
+ struct resource *res, *r;
+ struct platform_device *pd;
+ int id[2];
+ int ret;
+
+ r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!r)
+ return NULL;
+ id[0] = r->start;
+
+ r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!r)
+ return NULL;
+ id[1] = r->start;
+
+ res = kzalloc(sizeof(struct resource) * 2, GFP_KERNEL);
+ if (!res)
+ return NULL;
+
+ res[0].start = res[0].end = id[0];
+ res[1].start = res[1].end = id[1];
+ res[0].flags = res[1].flags = IORESOURCE_DMA;
+
+ pd = platform_device_alloc("au1xpsc-pcm", pdev->id);
+ if (!pd)
+ goto out;
+
+ pd->resource = res;
+ pd->num_resources = 2;
+
+ ret = platform_device_add(pd);
+ if (!ret)
+ return pd;
+
+ platform_device_put(pd);
+out:
+ kfree(res);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(au1xpsc_pcm_add);
+
+void au1xpsc_pcm_destroy(struct platform_device *dmapd)
+{
+ if (dmapd)
+ platform_device_unregister(dmapd);
+}
+EXPORT_SYMBOL_GPL(au1xpsc_pcm_destroy);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au12x0/Au1550 PSC Audio DMA driver");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c
new file mode 100644
index 00000000..d0db66f2
--- /dev/null
+++ b/sound/soc/au1x/psc-ac97.c
@@ -0,0 +1,516 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2009 MSC Vertriebsges.m.b.H.,
+ * Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Au1xxx-PSC AC97 glue.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/suspend.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+
+#include "psc.h"
+
+/* how often to retry failed codec register reads/writes */
+#define AC97_RW_RETRIES 5
+
+#define AC97_DIR \
+ (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
+
+#define AC97_RATES \
+ SNDRV_PCM_RATE_8000_48000
+
+#define AC97_FMTS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3BE)
+
+#define AC97PCR_START(stype) \
+ ((stype) == PCM_TX ? PSC_AC97PCR_TS : PSC_AC97PCR_RS)
+#define AC97PCR_STOP(stype) \
+ ((stype) == PCM_TX ? PSC_AC97PCR_TP : PSC_AC97PCR_RP)
+#define AC97PCR_CLRFIFO(stype) \
+ ((stype) == PCM_TX ? PSC_AC97PCR_TC : PSC_AC97PCR_RC)
+
+#define AC97STAT_BUSY(stype) \
+ ((stype) == PCM_TX ? PSC_AC97STAT_TB : PSC_AC97STAT_RB)
+
+/* instance data. There can be only one, MacLeod!!!! */
+static struct au1xpsc_audio_data *au1xpsc_ac97_workdata;
+
+#if 0
+
+/* this could theoretically work, but ac97->bus->card->private_data can be NULL
+ * when snd_ac97_mixer() is called; I don't know if the rest further down the
+ * chain are always valid either.
+ */
+static inline struct au1xpsc_audio_data *ac97_to_pscdata(struct snd_ac97 *x)
+{
+ struct snd_soc_card *c = x->bus->card->private_data;
+ return snd_soc_dai_get_drvdata(c->rtd->cpu_dai);
+}
+
+#else
+
+#define ac97_to_pscdata(x) au1xpsc_ac97_workdata
+
+#endif
+
+/* AC97 controller reads codec register */
+static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
+ unsigned short retry, tmo;
+ unsigned long data;
+
+ au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
+ au_sync();
+
+ retry = AC97_RW_RETRIES;
+ do {
+ mutex_lock(&pscdata->lock);
+
+ au_writel(PSC_AC97CDC_RD | PSC_AC97CDC_INDX(reg),
+ AC97_CDC(pscdata));
+ au_sync();
+
+ tmo = 20;
+ do {
+ udelay(21);
+ if (au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)
+ break;
+ } while (--tmo);
+
+ data = au_readl(AC97_CDC(pscdata));
+
+ au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
+ au_sync();
+
+ mutex_unlock(&pscdata->lock);
+
+ if (reg != ((data >> 16) & 0x7f))
+ tmo = 1; /* wrong register, try again */
+
+ } while (--retry && !tmo);
+
+ return retry ? data & 0xffff : 0xffff;
+}
+
+/* AC97 controller writes to codec register */
+static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
+ unsigned int tmo, retry;
+
+ au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
+ au_sync();
+
+ retry = AC97_RW_RETRIES;
+ do {
+ mutex_lock(&pscdata->lock);
+
+ au_writel(PSC_AC97CDC_INDX(reg) | (val & 0xffff),
+ AC97_CDC(pscdata));
+ au_sync();
+
+ tmo = 20;
+ do {
+ udelay(21);
+ if (au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)
+ break;
+ } while (--tmo);
+
+ au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
+ au_sync();
+
+ mutex_unlock(&pscdata->lock);
+ } while (--retry && !tmo);
+}
+
+/* AC97 controller asserts a warm reset */
+static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
+
+ au_writel(PSC_AC97RST_SNC, AC97_RST(pscdata));
+ au_sync();
+ msleep(10);
+ au_writel(0, AC97_RST(pscdata));
+ au_sync();
+}
+
+static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
+ int i;
+
+ /* disable PSC during cold reset */
+ au_writel(0, AC97_CFG(au1xpsc_ac97_workdata));
+ au_sync();
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(pscdata));
+ au_sync();
+
+ /* issue cold reset */
+ au_writel(PSC_AC97RST_RST, AC97_RST(pscdata));
+ au_sync();
+ msleep(500);
+ au_writel(0, AC97_RST(pscdata));
+ au_sync();
+
+ /* enable PSC */
+ au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));
+ au_sync();
+
+ /* wait for PSC to indicate it's ready */
+ i = 1000;
+ while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_SR)) && (--i))
+ msleep(1);
+
+ if (i == 0) {
+ printk(KERN_ERR "au1xpsc-ac97: PSC not ready!\n");
+ return;
+ }
+
+ /* enable the ac97 function */
+ au_writel(pscdata->cfg | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
+ au_sync();
+
+ /* wait for AC97 core to become ready */
+ i = 1000;
+ while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && (--i))
+ msleep(1);
+ if (i == 0)
+ printk(KERN_ERR "au1xpsc-ac97: AC97 ctrl not ready\n");
+}
+
+/* AC97 controller operations */
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = au1xpsc_ac97_read,
+ .write = au1xpsc_ac97_write,
+ .reset = au1xpsc_ac97_cold_reset,
+ .warm_reset = au1xpsc_ac97_warm_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
+ unsigned long r, ro, stat;
+ int chans, t, stype = SUBSTREAM_TYPE(substream);
+
+ chans = params_channels(params);
+
+ r = ro = au_readl(AC97_CFG(pscdata));
+ stat = au_readl(AC97_STAT(pscdata));
+
+ /* already active? */
+ if (stat & (PSC_AC97STAT_TB | PSC_AC97STAT_RB)) {
+ /* reject parameters not currently set up */
+ if ((PSC_AC97CFG_GET_LEN(r) != params->msbits) ||
+ (pscdata->rate != params_rate(params)))
+ return -EINVAL;
+ } else {
+
+ /* set sample bitdepth: REG[24:21]=(BITS-2)/2 */
+ r &= ~PSC_AC97CFG_LEN_MASK;
+ r |= PSC_AC97CFG_SET_LEN(params->msbits);
+
+ /* channels: enable slots for front L/R channel */
+ if (stype == PCM_TX) {
+ r &= ~PSC_AC97CFG_TXSLOT_MASK;
+ r |= PSC_AC97CFG_TXSLOT_ENA(3);
+ r |= PSC_AC97CFG_TXSLOT_ENA(4);
+ } else {
+ r &= ~PSC_AC97CFG_RXSLOT_MASK;
+ r |= PSC_AC97CFG_RXSLOT_ENA(3);
+ r |= PSC_AC97CFG_RXSLOT_ENA(4);
+ }
+
+ /* do we need to poke the hardware? */
+ if (!(r ^ ro))
+ goto out;
+
+ /* ac97 engine is about to be disabled */
+ mutex_lock(&pscdata->lock);
+
+ /* disable AC97 device controller first... */
+ au_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
+ au_sync();
+
+ /* ...wait for it... */
+ t = 100;
+ while ((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR) && --t)
+ msleep(1);
+
+ if (!t)
+ printk(KERN_ERR "PSC-AC97: can't disable!\n");
+
+ /* ...write config... */
+ au_writel(r, AC97_CFG(pscdata));
+ au_sync();
+
+ /* ...enable the AC97 controller again... */
+ au_writel(r | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
+ au_sync();
+
+ /* ...and wait for ready bit */
+ t = 100;
+ while ((!(au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && --t)
+ msleep(1);
+
+ if (!t)
+ printk(KERN_ERR "PSC-AC97: can't enable!\n");
+
+ mutex_unlock(&pscdata->lock);
+
+ pscdata->cfg = r;
+ pscdata->rate = params_rate(params);
+ }
+
+out:
+ return 0;
+}
+
+static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
+ int ret, stype = SUBSTREAM_TYPE(substream);
+
+ ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ au_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata));
+ au_sync();
+ au_writel(AC97PCR_START(stype), AC97_PCR(pscdata));
+ au_sync();
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ au_writel(AC97PCR_STOP(stype), AC97_PCR(pscdata));
+ au_sync();
+
+ while (au_readl(AC97_STAT(pscdata)) & AC97STAT_BUSY(stype))
+ asm volatile ("nop");
+
+ au_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata));
+ au_sync();
+
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static int au1xpsc_ac97_probe(struct snd_soc_dai *dai)
+{
+ return au1xpsc_ac97_workdata ? 0 : -ENODEV;
+}
+
+static struct snd_soc_dai_ops au1xpsc_ac97_dai_ops = {
+ .trigger = au1xpsc_ac97_trigger,
+ .hw_params = au1xpsc_ac97_hw_params,
+};
+
+static const struct snd_soc_dai_driver au1xpsc_ac97_dai_template = {
+ .ac97_control = 1,
+ .probe = au1xpsc_ac97_probe,
+ .playback = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .capture = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = &au1xpsc_ac97_dai_ops,
+};
+
+static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *r;
+ unsigned long sel;
+ struct au1xpsc_audio_data *wd;
+
+ wd = kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL);
+ if (!wd)
+ return -ENOMEM;
+
+ mutex_init(&wd->lock);
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r) {
+ ret = -ENODEV;
+ goto out0;
+ }
+
+ ret = -EBUSY;
+ if (!request_mem_region(r->start, resource_size(r), pdev->name))
+ goto out0;
+
+ wd->mmio = ioremap(r->start, resource_size(r));
+ if (!wd->mmio)
+ goto out1;
+
+ /* configuration: max dma trigger threshold, enable ac97 */
+ wd->cfg = PSC_AC97CFG_RT_FIFO8 | PSC_AC97CFG_TT_FIFO8 |
+ PSC_AC97CFG_DE_ENABLE;
+
+ /* preserve PSC clock source set up by platform */
+ sel = au_readl(PSC_SEL(wd)) & PSC_SEL_CLK_MASK;
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+ au_sync();
+ au_writel(0, PSC_SEL(wd));
+ au_sync();
+ au_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(wd));
+ au_sync();
+
+ /* name the DAI like this device instance ("au1xpsc-ac97.PSCINDEX") */
+ memcpy(&wd->dai_drv, &au1xpsc_ac97_dai_template,
+ sizeof(struct snd_soc_dai_driver));
+ wd->dai_drv.name = dev_name(&pdev->dev);
+
+ platform_set_drvdata(pdev, wd);
+
+ ret = snd_soc_register_dai(&pdev->dev, &wd->dai_drv);
+ if (ret)
+ goto out1;
+
+ wd->dmapd = au1xpsc_pcm_add(pdev);
+ if (wd->dmapd) {
+ au1xpsc_ac97_workdata = wd;
+ return 0;
+ }
+
+ snd_soc_unregister_dai(&pdev->dev);
+out1:
+ release_mem_region(r->start, resource_size(r));
+out0:
+ kfree(wd);
+ return ret;
+}
+
+static int __devexit au1xpsc_ac97_drvremove(struct platform_device *pdev)
+{
+ struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
+ struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ if (wd->dmapd)
+ au1xpsc_pcm_destroy(wd->dmapd);
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ /* disable PSC completely */
+ au_writel(0, AC97_CFG(wd));
+ au_sync();
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+ au_sync();
+
+ iounmap(wd->mmio);
+ release_mem_region(r->start, resource_size(r));
+ kfree(wd);
+
+ au1xpsc_ac97_workdata = NULL; /* MDEV */
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int au1xpsc_ac97_drvsuspend(struct device *dev)
+{
+ struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
+
+ /* save interesting registers and disable PSC */
+ wd->pm[0] = au_readl(PSC_SEL(wd));
+
+ au_writel(0, AC97_CFG(wd));
+ au_sync();
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+ au_sync();
+
+ return 0;
+}
+
+static int au1xpsc_ac97_drvresume(struct device *dev)
+{
+ struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
+
+ /* restore PSC clock config */
+ au_writel(wd->pm[0] | PSC_SEL_PS_AC97MODE, PSC_SEL(wd));
+ au_sync();
+
+ /* after this point the ac97 core will cold-reset the codec.
+ * During cold-reset the PSC is reinitialized and the last
+ * configuration set up in hw_params() is restored.
+ */
+ return 0;
+}
+
+static struct dev_pm_ops au1xpscac97_pmops = {
+ .suspend = au1xpsc_ac97_drvsuspend,
+ .resume = au1xpsc_ac97_drvresume,
+};
+
+#define AU1XPSCAC97_PMOPS &au1xpscac97_pmops
+
+#else
+
+#define AU1XPSCAC97_PMOPS NULL
+
+#endif
+
+static struct platform_driver au1xpsc_ac97_driver = {
+ .driver = {
+ .name = "au1xpsc_ac97",
+ .owner = THIS_MODULE,
+ .pm = AU1XPSCAC97_PMOPS,
+ },
+ .probe = au1xpsc_ac97_drvprobe,
+ .remove = __devexit_p(au1xpsc_ac97_drvremove),
+};
+
+static int __init au1xpsc_ac97_load(void)
+{
+ au1xpsc_ac97_workdata = NULL;
+ return platform_driver_register(&au1xpsc_ac97_driver);
+}
+
+static void __exit au1xpsc_ac97_unload(void)
+{
+ platform_driver_unregister(&au1xpsc_ac97_driver);
+}
+
+module_init(au1xpsc_ac97_load);
+module_exit(au1xpsc_ac97_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver");
+MODULE_AUTHOR("Manuel Lauss");
+
diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c
new file mode 100644
index 00000000..fca09127
--- /dev/null
+++ b/sound/soc/au1x/psc-i2s.c
@@ -0,0 +1,440 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ * Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Au1xxx-PSC I2S glue.
+ *
+ * NOTE: so far only PSC slave mode (bit- and frameclock) is supported.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+
+#include "psc.h"
+
+/* supported I2S DAI hardware formats */
+#define AU1XPSC_I2S_DAIFMT \
+ (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | \
+ SND_SOC_DAIFMT_NB_NF)
+
+/* supported I2S direction */
+#define AU1XPSC_I2S_DIR \
+ (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
+
+#define AU1XPSC_I2S_RATES \
+ SNDRV_PCM_RATE_8000_192000
+
+#define AU1XPSC_I2S_FMTS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+#define I2SSTAT_BUSY(stype) \
+ ((stype) == PCM_TX ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB)
+#define I2SPCR_START(stype) \
+ ((stype) == PCM_TX ? PSC_I2SPCR_TS : PSC_I2SPCR_RS)
+#define I2SPCR_STOP(stype) \
+ ((stype) == PCM_TX ? PSC_I2SPCR_TP : PSC_I2SPCR_RP)
+#define I2SPCR_CLRFIFO(stype) \
+ ((stype) == PCM_TX ? PSC_I2SPCR_TC : PSC_I2SPCR_RC)
+
+
+static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned long ct;
+ int ret;
+
+ ret = -EINVAL;
+
+ ct = pscdata->cfg;
+
+ ct &= ~(PSC_I2SCFG_XM | PSC_I2SCFG_MLJ); /* left-justified */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ ct |= PSC_I2SCFG_XM; /* enable I2S mode */
+ break;
+ case SND_SOC_DAIFMT_MSB:
+ break;
+ case SND_SOC_DAIFMT_LSB:
+ ct |= PSC_I2SCFG_MLJ; /* LSB (right-) justified */
+ break;
+ default:
+ goto out;
+ }
+
+ ct &= ~(PSC_I2SCFG_BI | PSC_I2SCFG_WI); /* IB-IF */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ ct |= PSC_I2SCFG_BI | PSC_I2SCFG_WI;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ ct |= PSC_I2SCFG_BI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ ct |= PSC_I2SCFG_WI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ break;
+ default:
+ goto out;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM: /* CODEC master */
+ ct |= PSC_I2SCFG_MS; /* PSC I2S slave mode */
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */
+ ct &= ~PSC_I2SCFG_MS; /* PSC I2S Master mode */
+ break;
+ default:
+ goto out;
+ }
+
+ pscdata->cfg = ct;
+ ret = 0;
+out:
+ return ret;
+}
+
+static int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
+
+ int cfgbits;
+ unsigned long stat;
+
+ /* check if the PSC is already streaming data */
+ stat = au_readl(I2S_STAT(pscdata));
+ if (stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB)) {
+ /* reject parameters not currently set up in hardware */
+ cfgbits = au_readl(I2S_CFG(pscdata));
+ if ((PSC_I2SCFG_GET_LEN(cfgbits) != params->msbits) ||
+ (params_rate(params) != pscdata->rate))
+ return -EINVAL;
+ } else {
+ /* set sample bitdepth */
+ pscdata->cfg &= ~(0x1f << 4);
+ pscdata->cfg |= PSC_I2SCFG_SET_LEN(params->msbits);
+ /* remember current rate for other stream */
+ pscdata->rate = params_rate(params);
+ }
+ return 0;
+}
+
+/* Configure PSC late: on my devel systems the codec is I2S master and
+ * supplies the i2sbitclock __AND__ i2sMclk (!) to the PSC unit. ASoC
+ * uses aggressive PM and switches the codec off when it is not in use
+ * which also means the PSC unit doesn't get any clocks and is therefore
+ * dead. That's why this chunk here gets called from the trigger callback
+ * because I can be reasonably certain the codec is driving the clocks.
+ */
+static int au1xpsc_i2s_configure(struct au1xpsc_audio_data *pscdata)
+{
+ unsigned long tmo;
+
+ /* bring PSC out of sleep, and configure I2S unit */
+ au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));
+ au_sync();
+
+ tmo = 1000000;
+ while (!(au_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_SR) && tmo)
+ tmo--;
+
+ if (!tmo)
+ goto psc_err;
+
+ au_writel(0, I2S_CFG(pscdata));
+ au_sync();
+ au_writel(pscdata->cfg | PSC_I2SCFG_DE_ENABLE, I2S_CFG(pscdata));
+ au_sync();
+
+ /* wait for I2S controller to become ready */
+ tmo = 1000000;
+ while (!(au_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_DR) && tmo)
+ tmo--;
+
+ if (tmo)
+ return 0;
+
+psc_err:
+ au_writel(0, I2S_CFG(pscdata));
+ au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
+ au_sync();
+ return -ETIMEDOUT;
+}
+
+static int au1xpsc_i2s_start(struct au1xpsc_audio_data *pscdata, int stype)
+{
+ unsigned long tmo, stat;
+ int ret;
+
+ ret = 0;
+
+ /* if both TX and RX are idle, configure the PSC */
+ stat = au_readl(I2S_STAT(pscdata));
+ if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {
+ ret = au1xpsc_i2s_configure(pscdata);
+ if (ret)
+ goto out;
+ }
+
+ au_writel(I2SPCR_CLRFIFO(stype), I2S_PCR(pscdata));
+ au_sync();
+ au_writel(I2SPCR_START(stype), I2S_PCR(pscdata));
+ au_sync();
+
+ /* wait for start confirmation */
+ tmo = 1000000;
+ while (!(au_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)
+ tmo--;
+
+ if (!tmo) {
+ au_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));
+ au_sync();
+ ret = -ETIMEDOUT;
+ }
+out:
+ return ret;
+}
+
+static int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int stype)
+{
+ unsigned long tmo, stat;
+
+ au_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));
+ au_sync();
+
+ /* wait for stop confirmation */
+ tmo = 1000000;
+ while ((au_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)
+ tmo--;
+
+ /* if both TX and RX are idle, disable PSC */
+ stat = au_readl(I2S_STAT(pscdata));
+ if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {
+ au_writel(0, I2S_CFG(pscdata));
+ au_sync();
+ au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
+ au_sync();
+ }
+ return 0;
+}
+
+static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
+ int ret, stype = SUBSTREAM_TYPE(substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ ret = au1xpsc_i2s_start(pscdata, stype);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ ret = au1xpsc_i2s_stop(pscdata, stype);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = {
+ .trigger = au1xpsc_i2s_trigger,
+ .hw_params = au1xpsc_i2s_hw_params,
+ .set_fmt = au1xpsc_i2s_set_fmt,
+};
+
+static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = {
+ .playback = {
+ .rates = AU1XPSC_I2S_RATES,
+ .formats = AU1XPSC_I2S_FMTS,
+ .channels_min = 2,
+ .channels_max = 8, /* 2 without external help */
+ },
+ .capture = {
+ .rates = AU1XPSC_I2S_RATES,
+ .formats = AU1XPSC_I2S_FMTS,
+ .channels_min = 2,
+ .channels_max = 8, /* 2 without external help */
+ },
+ .ops = &au1xpsc_i2s_dai_ops,
+};
+
+static int __devinit au1xpsc_i2s_drvprobe(struct platform_device *pdev)
+{
+ struct resource *r;
+ unsigned long sel;
+ int ret;
+ struct au1xpsc_audio_data *wd;
+
+ wd = kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL);
+ if (!wd)
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r) {
+ ret = -ENODEV;
+ goto out0;
+ }
+
+ ret = -EBUSY;
+ if (!request_mem_region(r->start, resource_size(r), pdev->name))
+ goto out0;
+
+ wd->mmio = ioremap(r->start, resource_size(r));
+ if (!wd->mmio)
+ goto out1;
+
+ /* preserve PSC clock source set up by platform (dev.platform_data
+ * is already occupied by soc layer)
+ */
+ sel = au_readl(PSC_SEL(wd)) & PSC_SEL_CLK_MASK;
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+ au_sync();
+ au_writel(PSC_SEL_PS_I2SMODE | sel, PSC_SEL(wd));
+ au_writel(0, I2S_CFG(wd));
+ au_sync();
+
+ /* preconfigure: set max rx/tx fifo depths */
+ wd->cfg |= PSC_I2SCFG_RT_FIFO8 | PSC_I2SCFG_TT_FIFO8;
+
+ /* don't wait for I2S core to become ready now; clocks may not
+ * be running yet; depending on clock input for PSC a wait might
+ * time out.
+ */
+
+ /* name the DAI like this device instance ("au1xpsc-i2s.PSCINDEX") */
+ memcpy(&wd->dai_drv, &au1xpsc_i2s_dai_template,
+ sizeof(struct snd_soc_dai_driver));
+ wd->dai_drv.name = dev_name(&pdev->dev);
+
+ platform_set_drvdata(pdev, wd);
+
+ ret = snd_soc_register_dai(&pdev->dev, &wd->dai_drv);
+ if (ret)
+ goto out1;
+
+ /* finally add the DMA device for this PSC */
+ wd->dmapd = au1xpsc_pcm_add(pdev);
+ if (wd->dmapd)
+ return 0;
+
+ snd_soc_unregister_dai(&pdev->dev);
+out1:
+ release_mem_region(r->start, resource_size(r));
+out0:
+ kfree(wd);
+ return ret;
+}
+
+static int __devexit au1xpsc_i2s_drvremove(struct platform_device *pdev)
+{
+ struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
+ struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ if (wd->dmapd)
+ au1xpsc_pcm_destroy(wd->dmapd);
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ au_writel(0, I2S_CFG(wd));
+ au_sync();
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+ au_sync();
+
+ iounmap(wd->mmio);
+ release_mem_region(r->start, resource_size(r));
+ kfree(wd);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int au1xpsc_i2s_drvsuspend(struct device *dev)
+{
+ struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
+
+ /* save interesting register and disable PSC */
+ wd->pm[0] = au_readl(PSC_SEL(wd));
+
+ au_writel(0, I2S_CFG(wd));
+ au_sync();
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+ au_sync();
+
+ return 0;
+}
+
+static int au1xpsc_i2s_drvresume(struct device *dev)
+{
+ struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
+
+ /* select I2S mode and PSC clock */
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+ au_sync();
+ au_writel(0, PSC_SEL(wd));
+ au_sync();
+ au_writel(wd->pm[0], PSC_SEL(wd));
+ au_sync();
+
+ return 0;
+}
+
+static struct dev_pm_ops au1xpsci2s_pmops = {
+ .suspend = au1xpsc_i2s_drvsuspend,
+ .resume = au1xpsc_i2s_drvresume,
+};
+
+#define AU1XPSCI2S_PMOPS &au1xpsci2s_pmops
+
+#else
+
+#define AU1XPSCI2S_PMOPS NULL
+
+#endif
+
+static struct platform_driver au1xpsc_i2s_driver = {
+ .driver = {
+ .name = "au1xpsc_i2s",
+ .owner = THIS_MODULE,
+ .pm = AU1XPSCI2S_PMOPS,
+ },
+ .probe = au1xpsc_i2s_drvprobe,
+ .remove = __devexit_p(au1xpsc_i2s_drvremove),
+};
+
+static int __init au1xpsc_i2s_load(void)
+{
+ return platform_driver_register(&au1xpsc_i2s_driver);
+}
+
+static void __exit au1xpsc_i2s_unload(void)
+{
+ platform_driver_unregister(&au1xpsc_i2s_driver);
+}
+
+module_init(au1xpsc_i2s_load);
+module_exit(au1xpsc_i2s_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au12x0/Au1550 PSC I2S ALSA ASoC audio driver");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h
new file mode 100644
index 00000000..b30eadd4
--- /dev/null
+++ b/sound/soc/au1x/psc.h
@@ -0,0 +1,52 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ * Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _AU1X_PCM_H
+#define _AU1X_PCM_H
+
+/* DBDMA helpers */
+extern struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev);
+extern void au1xpsc_pcm_destroy(struct platform_device *dmapd);
+
+struct au1xpsc_audio_data {
+ void __iomem *mmio;
+
+ unsigned long cfg;
+ unsigned long rate;
+
+ struct snd_soc_dai_driver dai_drv;
+
+ unsigned long pm[2];
+ struct mutex lock;
+ struct platform_device *dmapd;
+};
+
+#define PCM_TX 0
+#define PCM_RX 1
+
+#define SUBSTREAM_TYPE(substream) \
+ ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
+
+/* easy access macros */
+#define PSC_CTRL(x) ((unsigned long)((x)->mmio) + PSC_CTRL_OFFSET)
+#define PSC_SEL(x) ((unsigned long)((x)->mmio) + PSC_SEL_OFFSET)
+#define I2S_STAT(x) ((unsigned long)((x)->mmio) + PSC_I2SSTAT_OFFSET)
+#define I2S_CFG(x) ((unsigned long)((x)->mmio) + PSC_I2SCFG_OFFSET)
+#define I2S_PCR(x) ((unsigned long)((x)->mmio) + PSC_I2SPCR_OFFSET)
+#define AC97_CFG(x) ((unsigned long)((x)->mmio) + PSC_AC97CFG_OFFSET)
+#define AC97_CDC(x) ((unsigned long)((x)->mmio) + PSC_AC97CDC_OFFSET)
+#define AC97_EVNT(x) ((unsigned long)((x)->mmio) + PSC_AC97EVNT_OFFSET)
+#define AC97_PCR(x) ((unsigned long)((x)->mmio) + PSC_AC97PCR_OFFSET)
+#define AC97_RST(x) ((unsigned long)((x)->mmio) + PSC_AC97RST_OFFSET)
+#define AC97_STAT(x) ((unsigned long)((x)->mmio) + PSC_AC97STAT_OFFSET)
+
+#endif
diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig
new file mode 100644
index 00000000..ae403597
--- /dev/null
+++ b/sound/soc/blackfin/Kconfig
@@ -0,0 +1,145 @@
+config SND_BF5XX_I2S
+ tristate "SoC I2S Audio for the ADI BF5xx chip"
+ depends on BLACKFIN
+ select SND_BF5XX_SOC_SPORT
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the Blackfin SPORT (synchronous serial ports) interface in I2S
+ mode (supports single stereo In/Out).
+ You will also need to select the audio interfaces to support below.
+
+config SND_BF5XX_SOC_SSM2602
+ tristate "SoC SSM2602 Audio support for BF52x ezkit"
+ depends on SND_BF5XX_I2S
+ select SND_BF5XX_SOC_I2S
+ select SND_SOC_SSM2602
+ select I2C
+ help
+ Say Y if you want to add support for SoC audio on BF527-EZKIT.
+
+config SND_BF5XX_SOC_AD73311
+ tristate "SoC AD73311 Audio support for Blackfin"
+ depends on SND_BF5XX_I2S
+ select SND_BF5XX_SOC_I2S
+ select SND_SOC_AD73311
+ help
+ Say Y if you want to add support for AD73311 codec on Blackfin.
+
+config SND_BFIN_AD73311_SE
+ int "PF pin for AD73311L Chip Select"
+ depends on SND_BF5XX_SOC_AD73311
+ default 4
+ help
+ Enter the GPIO used to control AD73311's SE pin. Acceptable
+ values are 0 to 7
+
+config SND_BF5XX_TDM
+ tristate "SoC I2S(TDM mode) Audio for the ADI BF5xx chip"
+ depends on (BLACKFIN && SND_SOC)
+ select SND_BF5XX_SOC_SPORT
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the Blackfin SPORT (synchronous serial ports) interface in TDM
+ mode.
+ You will also need to select the audio interfaces to support below.
+
+config SND_BF5XX_SOC_AD1836
+ tristate "SoC AD1836 Audio support for BF5xx"
+ depends on SND_BF5XX_TDM
+ select SND_BF5XX_SOC_TDM
+ select SND_SOC_AD1836
+ help
+ Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT.
+
+config SND_BF5XX_SOC_AD193X
+ tristate "SoC AD193X Audio support for Blackfin"
+ depends on SND_BF5XX_TDM
+ select SND_BF5XX_SOC_TDM
+ select SND_SOC_AD193X
+ help
+ Say Y if you want to add support for AD193X codec on Blackfin.
+ This driver supports AD1936, AD1937, AD1938 and AD1939.
+
+config SND_BF5XX_AC97
+ tristate "SoC AC97 Audio for the ADI BF5xx chip"
+ depends on BLACKFIN
+ select AC97_BUS
+ select SND_SOC_AC97_BUS
+ select SND_BF5XX_SOC_SPORT
+ select SND_BF5XX_SOC_AC97
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the Blackfin SPORT (synchronous serial ports) interface in slot 16
+ mode (pseudo AC97 interface).
+ You will also need to select the audio interfaces to support below.
+
+ Note:
+ AC97 codecs which do not implement the slot-16 mode will not function
+ properly with this driver. This driver is known to work with the
+ Analog Devices line of AC97 codecs.
+
+config SND_BF5XX_MMAP_SUPPORT
+ bool "Enable MMAP Support"
+ depends on SND_BF5XX_AC97
+ default y
+ help
+ Say y if you want AC97 driver to support mmap mode.
+ We introduce an intermediate buffer to simulate mmap.
+
+config SND_BF5XX_MULTICHAN_SUPPORT
+ bool "Enable Multichannel Support"
+ depends on SND_BF5XX_AC97
+ default n
+ help
+ Say y if you want AC97 driver to support up to 5.1 channel audio.
+ this mode will consume much more memory for DMA.
+
+config SND_BF5XX_HAVE_COLD_RESET
+ bool "BOARD has COLD Reset GPIO"
+ depends on SND_BF5XX_AC97
+ default y if BFIN548_EZKIT
+ default n if !BFIN548_EZKIT
+
+config SND_BF5XX_RESET_GPIO_NUM
+ int "Set a GPIO for cold reset"
+ depends on SND_BF5XX_HAVE_COLD_RESET
+ range 0 159
+ default 19 if BFIN548_EZKIT
+ default 5 if BFIN537_STAMP
+ default 0
+ help
+ Set the correct GPIO for RESET the sound chip.
+
+config SND_BF5XX_SOC_AD1980
+ tristate "SoC AD1980/1 Audio support for BF5xx (Obsolete)"
+ depends on SND_BF5XX_AC97
+ select SND_BF5XX_SOC_AC97
+ select SND_SOC_AD1980
+ help
+ Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT.
+
+ Warning:
+ Because Analog Devices Inc. discontinued the ad1980 sound chip since
+ Sep. 2009, this ad1980 driver is not maintained, tested and supported
+ by ADI now.
+
+config SND_BF5XX_SOC_SPORT
+ tristate
+
+config SND_BF5XX_SOC_I2S
+ tristate
+
+config SND_BF5XX_SOC_TDM
+ tristate
+
+config SND_BF5XX_SOC_AC97
+ tristate
+
+config SND_BF5XX_SPORT_NUM
+ int "Set a SPORT for Sound chip"
+ depends on (SND_BF5XX_I2S || SND_BF5XX_AC97 || SND_BF5XX_TDM)
+ range 0 3 if BF54x
+ range 0 1 if !BF54x
+ default 0
+ help
+ Set the correct SPORT for sound chip.
diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile
new file mode 100644
index 00000000..49af3f32
--- /dev/null
+++ b/sound/soc/blackfin/Makefile
@@ -0,0 +1,29 @@
+# Blackfin Platform Support
+snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o
+snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o
+snd-bf5xx-tdm-objs := bf5xx-tdm-pcm.o
+snd-soc-bf5xx-sport-objs := bf5xx-sport.o
+snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o
+snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o
+snd-soc-bf5xx-tdm-objs := bf5xx-tdm.o
+
+obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o
+obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o
+obj-$(CONFIG_SND_BF5XX_TDM) += snd-bf5xx-tdm.o
+obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o
+obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o
+obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o
+obj-$(CONFIG_SND_BF5XX_SOC_TDM) += snd-soc-bf5xx-tdm.o
+
+# Blackfin Machine Support
+snd-ad1836-objs := bf5xx-ad1836.o
+snd-ad1980-objs := bf5xx-ad1980.o
+snd-ssm2602-objs := bf5xx-ssm2602.o
+snd-ad73311-objs := bf5xx-ad73311.o
+snd-ad193x-objs := bf5xx-ad193x.o
+
+obj-$(CONFIG_SND_BF5XX_SOC_AD1836) += snd-ad1836.o
+obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o
+obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o
+obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o
+obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o
diff --git a/sound/soc/blackfin/bf5xx-ac97-pcm.c b/sound/soc/blackfin/bf5xx-ac97-pcm.c
new file mode 100644
index 00000000..98b44b31
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ac97-pcm.c
@@ -0,0 +1,490 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-ac97-pcm.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: DMA Driver for AC97 sound chip
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/gfp.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+
+#include "bf5xx-ac97-pcm.h"
+#include "bf5xx-ac97.h"
+#include "bf5xx-sport.h"
+
+static unsigned int ac97_chan_mask[] = {
+ SP_FL, /* Mono */
+ SP_STEREO, /* Stereo */
+ SP_2DOT1, /* 2.1*/
+ SP_QUAD,/*Quadraquic*/
+ SP_FL | SP_FR | SP_FC | SP_SL | SP_SR,/*5 channels */
+ SP_5DOT1, /* 5.1 */
+};
+
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+static void bf5xx_mmap_copy(struct snd_pcm_substream *substream,
+ snd_pcm_uframes_t count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1];
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ bf5xx_pcm_to_ac97((struct ac97_frame *)sport->tx_dma_buf +
+ sport->tx_pos, (__u16 *)runtime->dma_area + sport->tx_pos *
+ runtime->channels, count, chan_mask);
+ sport->tx_pos += runtime->period_size;
+ if (sport->tx_pos >= runtime->buffer_size)
+ sport->tx_pos %= runtime->buffer_size;
+ sport->tx_delay_pos = sport->tx_pos;
+ } else {
+ bf5xx_ac97_to_pcm((struct ac97_frame *)sport->rx_dma_buf +
+ sport->rx_pos, (__u16 *)runtime->dma_area + sport->rx_pos *
+ runtime->channels, count);
+ sport->rx_pos += runtime->period_size;
+ if (sport->rx_pos >= runtime->buffer_size)
+ sport->rx_pos %= runtime->buffer_size;
+ }
+}
+#endif
+
+static void bf5xx_dma_irq(void *data)
+{
+ struct snd_pcm_substream *pcm = data;
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ struct sport_device *sport = runtime->private_data;
+ bf5xx_mmap_copy(pcm, runtime->period_size);
+ if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (sport->once == 0) {
+ snd_pcm_period_elapsed(pcm);
+ bf5xx_mmap_copy(pcm, runtime->period_size);
+ sport->once = 1;
+ }
+ }
+#endif
+ snd_pcm_period_elapsed(pcm);
+}
+
+/* The memory size for pure pcm data is 128*1024 = 0x20000 bytes.
+ * The total rx/tx buffer is for ac97 frame to hold all pcm data
+ * is 0x20000 * sizeof(struct ac97_frame) / 4.
+ */
+static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+#endif
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .period_bytes_min = 32,
+ .period_bytes_max = 0x10000,
+ .periods_min = 1,
+ .periods_max = PAGE_SIZE/32,
+ .buffer_bytes_max = 0x20000, /* 128 kbytes */
+ .fifo_size = 16,
+};
+
+static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ size_t size = bf5xx_pcm_hardware.buffer_bytes_max
+ * sizeof(struct ac97_frame) / 4;
+
+ snd_pcm_lib_malloc_pages(substream, size);
+
+ return 0;
+}
+
+static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ sport->once = 0;
+ if (runtime->dma_area)
+ memset(runtime->dma_area, 0, runtime->buffer_size);
+ memset(sport->tx_dma_buf, 0, runtime->buffer_size *
+ sizeof(struct ac97_frame));
+ } else
+ memset(sport->rx_dma_buf, 0, runtime->buffer_size *
+ sizeof(struct ac97_frame));
+#endif
+ snd_pcm_lib_free_pages(substream);
+ return 0;
+}
+
+static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+
+ /* An intermediate buffer is introduced for implementing mmap for
+ * SPORT working in TMD mode(include AC97).
+ */
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_tx_dma(sport, sport->tx_dma_buf, runtime->periods,
+ runtime->period_size * sizeof(struct ac97_frame));
+ } else {
+ sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_rx_dma(sport, sport->rx_dma_buf, runtime->periods,
+ runtime->period_size * sizeof(struct ac97_frame));
+ }
+#else
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_tx_dma(sport, runtime->dma_area, runtime->periods,
+ runtime->period_size * sizeof(struct ac97_frame));
+ } else {
+ sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_rx_dma(sport, runtime->dma_area, runtime->periods,
+ runtime->period_size * sizeof(struct ac97_frame));
+ }
+#endif
+ return 0;
+}
+
+static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ int ret = 0;
+
+ pr_debug("%s enter\n", __func__);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ bf5xx_mmap_copy(substream, runtime->period_size);
+ sport->tx_delay_pos = 0;
+#endif
+ sport_tx_start(sport);
+ } else
+ sport_rx_start(sport);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ sport->tx_pos = 0;
+#endif
+ sport_tx_stop(sport);
+ } else {
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ sport->rx_pos = 0;
+#endif
+ sport_rx_stop(sport);
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ unsigned int curr;
+
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ curr = sport->tx_delay_pos;
+ else
+ curr = sport->rx_pos;
+#else
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ curr = sport_curr_offset_tx(sport) / sizeof(struct ac97_frame);
+ else
+ curr = sport_curr_offset_rx(sport) / sizeof(struct ac97_frame);
+
+#endif
+ return curr;
+}
+
+static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ pr_debug("%s enter\n", __func__);
+ snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ if (sport_handle != NULL)
+ runtime->private_data = sport_handle;
+ else {
+ pr_err("sport_handle is NULL\n");
+ return -1;
+ }
+ return 0;
+
+ out:
+ return ret;
+}
+
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ size_t size = vma->vm_end - vma->vm_start;
+ vma->vm_start = (unsigned long)runtime->dma_area;
+ vma->vm_end = vma->vm_start + size;
+ vma->vm_flags |= VM_SHARED;
+ return 0 ;
+}
+#else
+static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
+ snd_pcm_uframes_t pos,
+ void __user *buf, snd_pcm_uframes_t count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1];
+ pr_debug("%s copy pos:0x%lx count:0x%lx\n",
+ substream->stream ? "Capture" : "Playback", pos, count);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ bf5xx_pcm_to_ac97((struct ac97_frame *)runtime->dma_area + pos,
+ (__u16 *)buf, count, chan_mask);
+ else
+ bf5xx_ac97_to_pcm((struct ac97_frame *)runtime->dma_area + pos,
+ (__u16 *)buf, count);
+ return 0;
+}
+#endif
+
+static struct snd_pcm_ops bf5xx_pcm_ac97_ops = {
+ .open = bf5xx_pcm_open,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = bf5xx_pcm_hw_params,
+ .hw_free = bf5xx_pcm_hw_free,
+ .prepare = bf5xx_pcm_prepare,
+ .trigger = bf5xx_pcm_trigger,
+ .pointer = bf5xx_pcm_pointer,
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ .mmap = bf5xx_pcm_mmap,
+#else
+ .copy = bf5xx_pcm_copy,
+#endif
+};
+
+static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = bf5xx_pcm_hardware.buffer_bytes_max
+ * sizeof(struct ac97_frame) / 4;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area) {
+ pr_err("Failed to allocate dma memory\n");
+ pr_err("Please increase uncached DMA memory region\n");
+ return -ENOMEM;
+ }
+ buf->bytes = size;
+
+ pr_debug("%s, area:%p, size:0x%08lx\n", __func__,
+ buf->area, buf->bytes);
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_handle->tx_buf = buf->area;
+ else
+ sport_handle->rx_buf = buf->area;
+
+/*
+ * Need to allocate local buffer when enable
+ * MMAP for SPORT working in TMD mode (include AC97).
+ */
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (!sport_handle->tx_dma_buf) {
+ sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \
+ size, &sport_handle->tx_dma_phy, GFP_KERNEL);
+ if (!sport_handle->tx_dma_buf) {
+ pr_err("Failed to allocate memory for tx dma buf - Please increase uncached DMA memory region\n");
+ return -ENOMEM;
+ } else
+ memset(sport_handle->tx_dma_buf, 0, size);
+ } else
+ memset(sport_handle->tx_dma_buf, 0, size);
+ } else {
+ if (!sport_handle->rx_dma_buf) {
+ sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \
+ size, &sport_handle->rx_dma_phy, GFP_KERNEL);
+ if (!sport_handle->rx_dma_buf) {
+ pr_err("Failed to allocate memory for rx dma buf - Please increase uncached DMA memory region\n");
+ return -ENOMEM;
+ } else
+ memset(sport_handle->rx_dma_buf, 0, size);
+ } else
+ memset(sport_handle->rx_dma_buf, 0, size);
+ }
+#endif
+ return 0;
+}
+
+static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
+ size_t size = bf5xx_pcm_hardware.buffer_bytes_max *
+ sizeof(struct ac97_frame) / 4;
+#endif
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+ dma_free_coherent(NULL, buf->bytes, buf->area, 0);
+ buf->area = NULL;
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (sport_handle->tx_dma_buf)
+ dma_free_coherent(NULL, size, \
+ sport_handle->tx_dma_buf, 0);
+ sport_handle->tx_dma_buf = NULL;
+ } else {
+
+ if (sport_handle->rx_dma_buf)
+ dma_free_coherent(NULL, size, \
+ sport_handle->rx_dma_buf, 0);
+ sport_handle->rx_dma_buf = NULL;
+ }
+#endif
+ }
+}
+
+static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
+
+int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ pr_debug("%s enter\n", __func__);
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &bf5xx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ if (dai->driver->playback.channels_min) {
+ ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+static struct snd_soc_platform_driver bf5xx_ac97_soc_platform = {
+ .ops = &bf5xx_pcm_ac97_ops,
+ .pcm_new = bf5xx_pcm_ac97_new,
+ .pcm_free = bf5xx_pcm_free_dma_buffers,
+};
+
+static int __devinit bf5xx_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &bf5xx_ac97_soc_platform);
+}
+
+static int __devexit bf5xx_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver bf5xx_pcm_driver = {
+ .driver = {
+ .name = "bfin-ac97-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = bf5xx_soc_platform_probe,
+ .remove = __devexit_p(bf5xx_soc_platform_remove),
+};
+
+static int __init snd_bf5xx_pcm_init(void)
+{
+ return platform_driver_register(&bf5xx_pcm_driver);
+}
+module_init(snd_bf5xx_pcm_init);
+
+static void __exit snd_bf5xx_pcm_exit(void)
+{
+ platform_driver_unregister(&bf5xx_pcm_driver);
+}
+module_exit(snd_bf5xx_pcm_exit);
+
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-ac97-pcm.h b/sound/soc/blackfin/bf5xx-ac97-pcm.h
new file mode 100644
index 00000000..d324d582
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ac97-pcm.h
@@ -0,0 +1,26 @@
+/*
+ * linux/sound/arm/bf5xx-ac97-pcm.h -- ALSA PCM interface for the Blackfin
+ *
+ * Copyright 2007 Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_AC97_PCM_H
+#define _BF5XX_AC97_PCM_H
+
+struct bf5xx_pcm_dma_params {
+ char *name; /* stream identifier */
+};
+
+struct bf5xx_gpio {
+ u32 sys;
+ u32 rx;
+ u32 tx;
+ u32 clk;
+ u32 frm;
+};
+
+#endif
diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c
new file mode 100644
index 00000000..6d216259
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ac97.c
@@ -0,0 +1,393 @@
+/*
+ * bf5xx-ac97.c -- AC97 support for the ADI blackfin chip.
+ *
+ * Author: Roy Huang
+ * Created: 11th. June 2007
+ * Copyright: Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/irq.h>
+#include <asm/portmux.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+
+#include "bf5xx-sport.h"
+#include "bf5xx-ac97.h"
+
+/* Anomaly notes:
+ * 05000250 - AD1980 is running in TDM mode and RFS/TFS are generated by SPORT
+ * contrtoller. But, RFSDIV and TFSDIV are always set to 16*16-1,
+ * while the max AC97 data size is 13*16. The DIV is always larger
+ * than data size. AD73311 and ad2602 are not running in TDM mode.
+ * AD1836 and AD73322 depend on external RFS/TFS only. So, this
+ * anomaly does not affect blackfin sound drivers.
+*/
+
+static struct sport_device *ac97_sport_handle;
+
+void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u16 *src,
+ size_t count, unsigned int chan_mask)
+{
+ while (count--) {
+ dst->ac97_tag = TAG_VALID;
+ if (chan_mask & SP_FL) {
+ dst->ac97_pcm_r = *src++;
+ dst->ac97_tag |= TAG_PCM_RIGHT;
+ }
+ if (chan_mask & SP_FR) {
+ dst->ac97_pcm_l = *src++;
+ dst->ac97_tag |= TAG_PCM_LEFT;
+
+ }
+#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
+ if (chan_mask & SP_SR) {
+ dst->ac97_sl = *src++;
+ dst->ac97_tag |= TAG_PCM_SL;
+ }
+ if (chan_mask & SP_SL) {
+ dst->ac97_sr = *src++;
+ dst->ac97_tag |= TAG_PCM_SR;
+ }
+ if (chan_mask & SP_LFE) {
+ dst->ac97_lfe = *src++;
+ dst->ac97_tag |= TAG_PCM_LFE;
+ }
+ if (chan_mask & SP_FC) {
+ dst->ac97_center = *src++;
+ dst->ac97_tag |= TAG_PCM_CENTER;
+ }
+#endif
+ dst++;
+ }
+}
+EXPORT_SYMBOL(bf5xx_pcm_to_ac97);
+
+void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u16 *dst,
+ size_t count)
+{
+ while (count--) {
+ *(dst++) = src->ac97_pcm_l;
+ *(dst++) = src->ac97_pcm_r;
+ src++;
+ }
+}
+EXPORT_SYMBOL(bf5xx_ac97_to_pcm);
+
+static unsigned int sport_tx_curr_frag(struct sport_device *sport)
+{
+ return sport->tx_curr_frag = sport_curr_offset_tx(sport) /
+ sport->tx_fragsize;
+}
+
+static void enqueue_cmd(struct snd_ac97 *ac97, __u16 addr, __u16 data)
+{
+ struct sport_device *sport = ac97_sport_handle;
+ int *cmd_count = sport->private_data;
+ int nextfrag = sport_tx_curr_frag(sport);
+ struct ac97_frame *nextwrite;
+
+ sport_incfrag(sport, &nextfrag, 1);
+
+ nextwrite = (struct ac97_frame *)(sport->tx_buf +
+ nextfrag * sport->tx_fragsize);
+ pr_debug("sport->tx_buf:%p, nextfrag:0x%x nextwrite:%p, cmd_count:%d\n",
+ sport->tx_buf, nextfrag, nextwrite, cmd_count[nextfrag]);
+ nextwrite[cmd_count[nextfrag]].ac97_tag |= TAG_CMD;
+ nextwrite[cmd_count[nextfrag]].ac97_addr = addr;
+ nextwrite[cmd_count[nextfrag]].ac97_data = data;
+ ++cmd_count[nextfrag];
+ pr_debug("ac97_sport: Inserting %02x/%04x into fragment %d\n",
+ addr >> 8, data, nextfrag);
+}
+
+static unsigned short bf5xx_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ struct sport_device *sport_handle = ac97_sport_handle;
+ struct ac97_frame out_frame[2], in_frame[2];
+
+ pr_debug("%s enter 0x%x\n", __func__, reg);
+
+ /* When dma descriptor is enabled, the register should not be read */
+ if (sport_handle->tx_run || sport_handle->rx_run) {
+ pr_err("Could you send a mail to cliff.cai@analog.com "
+ "to report this?\n");
+ return -EFAULT;
+ }
+
+ memset(&out_frame, 0, 2 * sizeof(struct ac97_frame));
+ memset(&in_frame, 0, 2 * sizeof(struct ac97_frame));
+ out_frame[0].ac97_tag = TAG_VALID | TAG_CMD;
+ out_frame[0].ac97_addr = ((reg << 8) | 0x8000);
+ sport_send_and_recv(sport_handle, (unsigned char *)&out_frame,
+ (unsigned char *)&in_frame,
+ 2 * sizeof(struct ac97_frame));
+ return in_frame[1].ac97_data;
+}
+
+void bf5xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ struct sport_device *sport_handle = ac97_sport_handle;
+
+ pr_debug("%s enter 0x%x:0x%04x\n", __func__, reg, val);
+
+ if (sport_handle->tx_run) {
+ enqueue_cmd(ac97, (reg << 8), val); /* write */
+ enqueue_cmd(ac97, (reg << 8) | 0x8000, 0); /* read back */
+ } else {
+ struct ac97_frame frame;
+ memset(&frame, 0, sizeof(struct ac97_frame));
+ frame.ac97_tag = TAG_VALID | TAG_CMD;
+ frame.ac97_addr = (reg << 8);
+ frame.ac97_data = val;
+ sport_send_and_recv(sport_handle, (unsigned char *)&frame, \
+ NULL, sizeof(struct ac97_frame));
+ }
+}
+
+static void bf5xx_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ struct sport_device *sport_handle = ac97_sport_handle;
+ u16 gpio = P_IDENT(sport_handle->pin_req[3]);
+
+ pr_debug("%s enter\n", __func__);
+
+ peripheral_free_list(sport_handle->pin_req);
+ gpio_request(gpio, "bf5xx-ac97");
+ gpio_direction_output(gpio, 1);
+ udelay(2);
+ gpio_set_value(gpio, 0);
+ udelay(1);
+ gpio_free(gpio);
+ peripheral_request_list(sport_handle->pin_req, "soc-audio");
+}
+
+static void bf5xx_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ pr_debug("%s enter\n", __func__);
+
+ /* It is specified for bf548-ezkit */
+ gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 0);
+ /* Keep reset pin low for 1 ms */
+ mdelay(1);
+ gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1);
+ /* Wait for bit clock recover */
+ mdelay(1);
+#else
+ pr_info("%s: Not implemented\n", __func__);
+#endif
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = bf5xx_ac97_read,
+ .write = bf5xx_ac97_write,
+ .warm_reset = bf5xx_ac97_warm_reset,
+ .reset = bf5xx_ac97_cold_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+#ifdef CONFIG_PM
+static int bf5xx_ac97_suspend(struct snd_soc_dai *dai)
+{
+ struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
+
+ pr_debug("%s : sport %d\n", __func__, dai->id);
+ if (!dai->active)
+ return 0;
+ if (dai->capture_active)
+ sport_rx_stop(sport);
+ if (dai->playback_active)
+ sport_tx_stop(sport);
+ return 0;
+}
+
+static int bf5xx_ac97_resume(struct snd_soc_dai *dai)
+{
+ int ret;
+ struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
+
+ pr_debug("%s : sport %d\n", __func__, dai->id);
+ if (!dai->active)
+ return 0;
+
+#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
+ ret = sport_set_multichannel(sport, 16, 0x3FF, 1);
+#else
+ ret = sport_set_multichannel(sport, 16, 0x1F, 1);
+#endif
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ ret = sport_config_rx(sport, IRFS, 0xF, 0, (16*16-1));
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ ret = sport_config_tx(sport, ITFS, 0xF, 0, (16*16-1));
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+#else
+#define bf5xx_ac97_suspend NULL
+#define bf5xx_ac97_resume NULL
+#endif
+
+static struct snd_soc_dai_driver bfin_ac97_dai = {
+ .ac97_control = 1,
+ .suspend = bf5xx_ac97_suspend,
+ .resume = bf5xx_ac97_resume,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 2,
+#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
+ .channels_max = 6,
+#else
+ .channels_max = 2,
+#endif
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+};
+
+static int __devinit asoc_bfin_ac97_probe(struct platform_device *pdev)
+{
+ struct sport_device *sport_handle;
+ int ret;
+
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ /* Request PB3 as reset pin */
+ if (gpio_request(CONFIG_SND_BF5XX_RESET_GPIO_NUM, "SND_AD198x RESET")) {
+ pr_err("Failed to request GPIO_%d for reset\n",
+ CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+ ret = -1;
+ goto gpio_err;
+ }
+ gpio_direction_output(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1);
+#endif
+
+ sport_handle = sport_init(pdev, 2, sizeof(struct ac97_frame),
+ PAGE_SIZE);
+ if (!sport_handle) {
+ ret = -ENODEV;
+ goto sport_err;
+ }
+
+ /*SPORT works in TDM mode to simulate AC97 transfers*/
+#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
+ ret = sport_set_multichannel(sport_handle, 16, 0x3FF, 1);
+#else
+ ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1);
+#endif
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ ret = -EBUSY;
+ goto sport_config_err;
+ }
+
+ ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1));
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ ret = -EBUSY;
+ goto sport_config_err;
+ }
+
+ ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1));
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ ret = -EBUSY;
+ goto sport_config_err;
+ }
+
+ ret = snd_soc_register_dai(&pdev->dev, &bfin_ac97_dai);
+ if (ret) {
+ pr_err("Failed to register DAI: %d\n", ret);
+ goto sport_config_err;
+ }
+
+ ac97_sport_handle = sport_handle;
+
+ return 0;
+
+sport_config_err:
+ sport_done(sport_handle);
+sport_err:
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+gpio_err:
+#endif
+
+ return ret;
+}
+
+static int __devexit asoc_bfin_ac97_remove(struct platform_device *pdev)
+{
+ struct sport_device *sport_handle = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_dai(&pdev->dev);
+ sport_done(sport_handle);
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+#endif
+
+ return 0;
+}
+
+static struct platform_driver asoc_bfin_ac97_driver = {
+ .driver = {
+ .name = "bfin-ac97",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = asoc_bfin_ac97_probe,
+ .remove = __devexit_p(asoc_bfin_ac97_remove),
+};
+
+static int __init bfin_ac97_init(void)
+{
+ return platform_driver_register(&asoc_bfin_ac97_driver);
+}
+module_init(bfin_ac97_init);
+
+static void __exit bfin_ac97_exit(void)
+{
+ platform_driver_unregister(&asoc_bfin_ac97_driver);
+}
+module_exit(bfin_ac97_exit);
+
+
+MODULE_AUTHOR("Roy Huang");
+MODULE_DESCRIPTION("AC97 driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-ac97.h b/sound/soc/blackfin/bf5xx-ac97.h
new file mode 100644
index 00000000..15c635e3
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ac97.h
@@ -0,0 +1,59 @@
+/*
+ * sound/soc/blackfin/bf5xx-ac97.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_AC97_H
+#define _BF5XX_AC97_H
+
+extern struct snd_ac97_bus_ops bf5xx_ac97_ops;
+extern struct snd_ac97 *ac97;
+/* Frame format in memory, only support stereo currently */
+struct ac97_frame {
+ u16 ac97_tag; /* slot 0 */
+ u16 ac97_addr; /* slot 1 */
+ u16 ac97_data; /* slot 2 */
+ u16 ac97_pcm_l; /*slot 3:front left*/
+ u16 ac97_pcm_r; /*slot 4:front left*/
+#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
+ u16 ac97_mdm_l1;
+ u16 ac97_center; /*slot 6:center*/
+ u16 ac97_sl; /*slot 7:surround left*/
+ u16 ac97_sr; /*slot 8:surround right*/
+ u16 ac97_lfe; /*slot 9:lfe*/
+#endif
+} __attribute__ ((packed));
+
+/* Speaker location */
+#define SP_FL 0x0001
+#define SP_FR 0x0010
+#define SP_FC 0x0002
+#define SP_LFE 0x0020
+#define SP_SL 0x0004
+#define SP_SR 0x0040
+
+#define SP_STEREO (SP_FL | SP_FR)
+#define SP_2DOT1 (SP_FL | SP_FR | SP_LFE)
+#define SP_QUAD (SP_FL | SP_FR | SP_SL | SP_SR)
+#define SP_5DOT1 (SP_FL | SP_FR | SP_FC | SP_LFE | SP_SL | SP_SR)
+
+#define TAG_VALID 0x8000
+#define TAG_CMD 0x6000
+#define TAG_PCM_LEFT 0x1000
+#define TAG_PCM_RIGHT 0x0800
+#define TAG_PCM_MDM_L1 0x0400
+#define TAG_PCM_CENTER 0x0200
+#define TAG_PCM_SL 0x0100
+#define TAG_PCM_SR 0x0080
+#define TAG_PCM_LFE 0x0040
+
+void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u16 *src, \
+ size_t count, unsigned int chan_mask);
+
+void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u16 *dst, \
+ size_t count);
+
+#endif
diff --git a/sound/soc/blackfin/bf5xx-ad1836.c b/sound/soc/blackfin/bf5xx-ad1836.c
new file mode 100644
index 00000000..f79d1655
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ad1836.c
@@ -0,0 +1,129 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-ad1836.c
+ * Author: Barry Song <Barry.Song@analog.com>
+ *
+ * Created: Aug 4 2009
+ * Description: Board driver for ad1836 sound chip
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <asm/blackfin.h>
+#include <asm/cacheflush.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/portmux.h>
+
+#include "../codecs/ad1836.h"
+
+#include "bf5xx-tdm-pcm.h"
+#include "bf5xx-tdm.h"
+
+static struct snd_soc_card bf5xx_ad1836;
+
+static int bf5xx_ad1836_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ unsigned int channel_map[] = {0, 4, 1, 5, 2, 6, 3, 7};
+ int ret = 0;
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI channel mapping */
+ ret = snd_soc_dai_set_channel_map(cpu_dai, ARRAY_SIZE(channel_map),
+ channel_map, ARRAY_SIZE(channel_map), channel_map);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops bf5xx_ad1836_ops = {
+ .hw_params = bf5xx_ad1836_hw_params,
+};
+
+static struct snd_soc_dai_link bf5xx_ad1836_dai[] = {
+ {
+ .name = "ad1836",
+ .stream_name = "AD1836",
+ .cpu_dai_name = "bfin-tdm.0",
+ .codec_dai_name = "ad1836-hifi",
+ .platform_name = "bfin-tdm-pcm-audio",
+ .codec_name = "spi0.4",
+ .ops = &bf5xx_ad1836_ops,
+ },
+ {
+ .name = "ad1836",
+ .stream_name = "AD1836",
+ .cpu_dai_name = "bfin-tdm.1",
+ .codec_dai_name = "ad1836-hifi",
+ .platform_name = "bfin-tdm-pcm-audio",
+ .codec_name = "spi0.4",
+ .ops = &bf5xx_ad1836_ops,
+ },
+};
+
+static struct snd_soc_card bf5xx_ad1836 = {
+ .name = "bfin-ad1836",
+ .dai_link = &bf5xx_ad1836_dai[CONFIG_SND_BF5XX_SPORT_NUM],
+ .num_links = 1,
+};
+
+static struct platform_device *bfxx_ad1836_snd_device;
+
+static int __init bf5xx_ad1836_init(void)
+{
+ int ret;
+
+ bfxx_ad1836_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!bfxx_ad1836_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(bfxx_ad1836_snd_device, &bf5xx_ad1836);
+ ret = platform_device_add(bfxx_ad1836_snd_device);
+
+ if (ret)
+ platform_device_put(bfxx_ad1836_snd_device);
+
+ return ret;
+}
+
+static void __exit bf5xx_ad1836_exit(void)
+{
+ platform_device_unregister(bfxx_ad1836_snd_device);
+}
+
+module_init(bf5xx_ad1836_init);
+module_exit(bf5xx_ad1836_exit);
+
+/* Module information */
+MODULE_AUTHOR("Barry Song");
+MODULE_DESCRIPTION("ALSA SoC AD1836 board driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/blackfin/bf5xx-ad193x.c b/sound/soc/blackfin/bf5xx-ad193x.c
new file mode 100644
index 00000000..2f0f8365
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ad193x.c
@@ -0,0 +1,157 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-ad193x.c
+ * Author: Barry Song <Barry.Song@analog.com>
+ *
+ * Created: Thur June 4 2009
+ * Description: Board driver for ad193x sound chip
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <asm/blackfin.h>
+#include <asm/cacheflush.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/portmux.h>
+
+#include "../codecs/ad193x.h"
+
+#include "bf5xx-tdm-pcm.h"
+#include "bf5xx-tdm.h"
+
+static struct snd_soc_card bf5xx_ad193x;
+
+static int bf5xx_ad193x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ unsigned int clk = 0;
+ unsigned int channel_map[] = {0, 1, 2, 3, 4, 5, 6, 7};
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 48000:
+ clk = 12288000;
+ break;
+ }
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set codec DAI slots, 8 channels, all channels are enabled */
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xFF, 0xFF, 8, 32);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI channel mapping */
+ ret = snd_soc_dai_set_channel_map(cpu_dai, ARRAY_SIZE(channel_map),
+ channel_map, ARRAY_SIZE(channel_map), channel_map);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops bf5xx_ad193x_ops = {
+ .hw_params = bf5xx_ad193x_hw_params,
+};
+
+static struct snd_soc_dai_link bf5xx_ad193x_dai[] = {
+ {
+ .name = "ad193x",
+ .stream_name = "AD193X",
+ .cpu_dai_name = "bfin-tdm.0",
+ .codec_dai_name ="ad193x-hifi",
+ .platform_name = "bfin-tdm-pcm-audio",
+ .codec_name = "spi0.5",
+ .ops = &bf5xx_ad193x_ops,
+ },
+ {
+ .name = "ad193x",
+ .stream_name = "AD193X",
+ .cpu_dai_name = "bfin-tdm.1",
+ .codec_dai_name ="ad193x-hifi",
+ .platform_name = "bfin-tdm-pcm-audio",
+ .codec_name = "spi0.5",
+ .ops = &bf5xx_ad193x_ops,
+ },
+};
+
+static struct snd_soc_card bf5xx_ad193x = {
+ .name = "bfin-ad193x",
+ .dai_link = &bf5xx_ad193x_dai[CONFIG_SND_BF5XX_SPORT_NUM],
+ .num_links = 1,
+};
+
+static struct platform_device *bfxx_ad193x_snd_device;
+
+static int __init bf5xx_ad193x_init(void)
+{
+ int ret;
+
+ bfxx_ad193x_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!bfxx_ad193x_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(bfxx_ad193x_snd_device, &bf5xx_ad193x);
+ ret = platform_device_add(bfxx_ad193x_snd_device);
+
+ if (ret)
+ platform_device_put(bfxx_ad193x_snd_device);
+
+ return ret;
+}
+
+static void __exit bf5xx_ad193x_exit(void)
+{
+ platform_device_unregister(bfxx_ad193x_snd_device);
+}
+
+module_init(bf5xx_ad193x_init);
+module_exit(bf5xx_ad193x_exit);
+
+/* Module information */
+MODULE_AUTHOR("Barry Song");
+MODULE_DESCRIPTION("ALSA SoC AD193X board driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/blackfin/bf5xx-ad1980.c b/sound/soc/blackfin/bf5xx-ad1980.c
new file mode 100644
index 00000000..06a84b21
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ad1980.c
@@ -0,0 +1,111 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-ad1980.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: Board driver for AD1980/1 audio codec
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * WARNING:
+ *
+ * Because Analog Devices Inc. discontinued the ad1980 sound chip since
+ * Sep. 2009, this ad1980 driver is not maintained, tested and supported
+ * by ADI now.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <asm/dma.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <linux/gpio.h>
+#include <asm/portmux.h>
+
+#include "../codecs/ad1980.h"
+
+#include "bf5xx-ac97-pcm.h"
+#include "bf5xx-ac97.h"
+
+static struct snd_soc_card bf5xx_board;
+
+static struct snd_soc_dai_link bf5xx_board_dai[] = {
+ {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "bfin-ac97.0",
+ .codec_dai_name = "ad1980-hifi",
+ .platform_name = "bfin-ac97-pcm-audio",
+ .codec_name = "ad1980",
+ },
+ {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "bfin-ac97.1",
+ .codec_dai_name = "ad1980-hifi",
+ .platform_name = "bfin-ac97-pcm-audio",
+ .codec_name = "ad1980",
+ },
+};
+
+static struct snd_soc_card bf5xx_board = {
+ .name = "bfin-ad1980",
+ .dai_link = &bf5xx_board_dai[CONFIG_SND_BF5XX_SPORT_NUM],
+ .num_links = 1,
+};
+
+static struct platform_device *bf5xx_board_snd_device;
+
+static int __init bf5xx_board_init(void)
+{
+ int ret;
+
+ bf5xx_board_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!bf5xx_board_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(bf5xx_board_snd_device, &bf5xx_board);
+ ret = platform_device_add(bf5xx_board_snd_device);
+
+ if (ret)
+ platform_device_put(bf5xx_board_snd_device);
+
+ return ret;
+}
+
+static void __exit bf5xx_board_exit(void)
+{
+ platform_device_unregister(bf5xx_board_snd_device);
+}
+
+module_init(bf5xx_board_init);
+module_exit(bf5xx_board_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ALSA SoC AD1980/1 BF5xx board (Obsolete)");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-ad73311.c b/sound/soc/blackfin/bf5xx-ad73311.c
new file mode 100644
index 00000000..732a247f
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ad73311.c
@@ -0,0 +1,233 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-ad73311.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Thur Sep 25 2008
+ * Description: Board driver for ad73311 sound chip
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <asm/blackfin.h>
+#include <asm/cacheflush.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/portmux.h>
+
+#include "../codecs/ad73311.h"
+#include "bf5xx-sport.h"
+#include "bf5xx-i2s-pcm.h"
+
+#if CONFIG_SND_BF5XX_SPORT_NUM == 0
+#define bfin_write_SPORT_TCR1 bfin_write_SPORT0_TCR1
+#define bfin_read_SPORT_TCR1 bfin_read_SPORT0_TCR1
+#define bfin_write_SPORT_TCR2 bfin_write_SPORT0_TCR2
+#define bfin_write_SPORT_TX16 bfin_write_SPORT0_TX16
+#define bfin_read_SPORT_STAT bfin_read_SPORT0_STAT
+#else
+#define bfin_write_SPORT_TCR1 bfin_write_SPORT1_TCR1
+#define bfin_read_SPORT_TCR1 bfin_read_SPORT1_TCR1
+#define bfin_write_SPORT_TCR2 bfin_write_SPORT1_TCR2
+#define bfin_write_SPORT_TX16 bfin_write_SPORT1_TX16
+#define bfin_read_SPORT_STAT bfin_read_SPORT1_STAT
+#endif
+
+#define GPIO_SE CONFIG_SND_BFIN_AD73311_SE
+
+static struct snd_soc_card bf5xx_ad73311;
+
+static int snd_ad73311_startup(void)
+{
+ pr_debug("%s enter\n", __func__);
+
+ /* Pull up SE pin on AD73311L */
+ gpio_set_value(GPIO_SE, 1);
+ return 0;
+}
+
+static int snd_ad73311_configure(void)
+{
+ unsigned short ctrl_regs[6];
+ unsigned short status = 0;
+ int count = 0;
+
+ /* DMCLK = MCLK = 16.384 MHz
+ * SCLK = DMCLK/8 = 2.048 MHz
+ * Sample Rate = DMCLK/2048 = 8 KHz
+ */
+ ctrl_regs[0] = AD_CONTROL | AD_WRITE | CTRL_REG_B | REGB_MCDIV(0) | \
+ REGB_SCDIV(0) | REGB_DIRATE(0);
+ ctrl_regs[1] = AD_CONTROL | AD_WRITE | CTRL_REG_C | REGC_PUDEV | \
+ REGC_PUADC | REGC_PUDAC | REGC_PUREF | REGC_REFUSE ;
+ ctrl_regs[2] = AD_CONTROL | AD_WRITE | CTRL_REG_D | REGD_OGS(2) | \
+ REGD_IGS(2);
+ ctrl_regs[3] = AD_CONTROL | AD_WRITE | CTRL_REG_E | REGE_DA(0x1f);
+ ctrl_regs[4] = AD_CONTROL | AD_WRITE | CTRL_REG_F | REGF_SEEN ;
+ ctrl_regs[5] = AD_CONTROL | AD_WRITE | CTRL_REG_A | REGA_MODE_DATA;
+
+ local_irq_disable();
+ snd_ad73311_startup();
+ udelay(1);
+
+ bfin_write_SPORT_TCR1(TFSR);
+ bfin_write_SPORT_TCR2(0xF);
+ SSYNC();
+
+ /* SPORT Tx Register is a 8 x 16 FIFO, all the data can be put to
+ * FIFO before enable SPORT to transfer the data
+ */
+ for (count = 0; count < 6; count++)
+ bfin_write_SPORT_TX16(ctrl_regs[count]);
+ SSYNC();
+ bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() | TSPEN);
+ SSYNC();
+
+ /* When TUVF is set, the data is already send out */
+ while (!(status & TUVF) && ++count < 10000) {
+ udelay(1);
+ status = bfin_read_SPORT_STAT();
+ SSYNC();
+ }
+ bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() & ~TSPEN);
+ SSYNC();
+ local_irq_enable();
+
+ if (count >= 10000) {
+ printk(KERN_ERR "ad73311: failed to configure codec\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int bf5xx_probe(struct platform_device *pdev)
+{
+ int err;
+ if (gpio_request(GPIO_SE, "AD73311_SE")) {
+ printk(KERN_ERR "%s: Failed ro request GPIO_%d\n", __func__, GPIO_SE);
+ return -EBUSY;
+ }
+
+ gpio_direction_output(GPIO_SE, 0);
+
+ err = snd_ad73311_configure();
+ if (err < 0)
+ return -EFAULT;
+
+ return 0;
+}
+
+static int bf5xx_ad73311_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret = 0;
+
+ pr_debug("%s rate %d format %x\n", __func__, params_rate(params),
+ params_format(params));
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+
+static struct snd_soc_ops bf5xx_ad73311_ops = {
+ .hw_params = bf5xx_ad73311_hw_params,
+};
+
+static struct snd_soc_dai_link bf5xx_ad73311_dai[] = {
+ {
+ .name = "ad73311",
+ .stream_name = "AD73311",
+ .cpu_dai_name = "bfin-i2s.0",
+ .codec_dai_name = "ad73311-hifi",
+ .platform_name = "bfin-i2s-pcm-audio",
+ .codec_name = "ad73311",
+ .ops = &bf5xx_ad73311_ops,
+ },
+ {
+ .name = "ad73311",
+ .stream_name = "AD73311",
+ .cpu_dai_name = "bfin-i2s.1",
+ .codec_dai_name = "ad73311-hifi",
+ .platform_name = "bfin-i2s-pcm-audio",
+ .codec_name = "ad73311",
+ .ops = &bf5xx_ad73311_ops,
+ },
+};
+
+static struct snd_soc_card bf5xx_ad73311 = {
+ .name = "bfin-ad73311",
+ .probe = bf5xx_probe,
+ .dai_link = &bf5xx_ad73311_dai[CONFIG_SND_BF5XX_SPORT_NUM],
+ .num_links = 1,
+};
+
+static struct platform_device *bf5xx_ad73311_snd_device;
+
+static int __init bf5xx_ad73311_init(void)
+{
+ int ret;
+
+ pr_debug("%s enter\n", __func__);
+ bf5xx_ad73311_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!bf5xx_ad73311_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(bf5xx_ad73311_snd_device, &bf5xx_ad73311);
+ ret = platform_device_add(bf5xx_ad73311_snd_device);
+
+ if (ret)
+ platform_device_put(bf5xx_ad73311_snd_device);
+
+ return ret;
+}
+
+static void __exit bf5xx_ad73311_exit(void)
+{
+ pr_debug("%s enter\n", __func__);
+ platform_device_unregister(bf5xx_ad73311_snd_device);
+}
+
+module_init(bf5xx_ad73311_init);
+module_exit(bf5xx_ad73311_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ALSA SoC AD73311 Blackfin");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.c b/sound/soc/blackfin/bf5xx-i2s-pcm.c
new file mode 100644
index 00000000..f1fd95bb
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-i2s-pcm.c
@@ -0,0 +1,329 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-i2s-pcm.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: DMA driver for i2s codec
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/gfp.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+
+#include "bf5xx-i2s-pcm.h"
+#include "bf5xx-sport.h"
+
+static void bf5xx_dma_irq(void *data)
+{
+ struct snd_pcm_substream *pcm = data;
+ snd_pcm_period_elapsed(pcm);
+}
+
+static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .period_bytes_min = 32,
+ .period_bytes_max = 0x10000,
+ .periods_min = 1,
+ .periods_max = PAGE_SIZE/32,
+ .buffer_bytes_max = 0x20000, /* 128 kbytes */
+ .fifo_size = 16,
+};
+
+static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
+ snd_pcm_lib_malloc_pages(substream, size);
+
+ return 0;
+}
+
+static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_lib_free_pages(substream);
+
+ return 0;
+}
+
+static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ int period_bytes = frames_to_bytes(runtime, runtime->period_size);
+
+ pr_debug("%s enter\n", __func__);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_tx_dma(sport, runtime->dma_area,
+ runtime->periods, period_bytes);
+ } else {
+ sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_rx_dma(sport, runtime->dma_area,
+ runtime->periods, period_bytes);
+ }
+
+ return 0;
+}
+
+static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ int ret = 0;
+
+ pr_debug("%s enter\n", __func__);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_tx_start(sport);
+ else
+ sport_rx_start(sport);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_tx_stop(sport);
+ else
+ sport_rx_stop(sport);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ unsigned int diff;
+ snd_pcm_uframes_t frames;
+ pr_debug("%s enter\n", __func__);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ diff = sport_curr_offset_tx(sport);
+ } else {
+ diff = sport_curr_offset_rx(sport);
+ }
+
+ /*
+ * TX at least can report one frame beyond the end of the
+ * buffer if we hit the wraparound case - clamp to within the
+ * buffer as the ALSA APIs require.
+ */
+ if (diff == snd_pcm_lib_buffer_bytes(substream))
+ diff = 0;
+
+ frames = bytes_to_frames(substream->runtime, diff);
+
+ return frames;
+}
+
+static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ int ret;
+
+ pr_debug("%s enter\n", __func__);
+
+ snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(runtime, \
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ if (sport_handle != NULL) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_handle->tx_buf = buf->area;
+ else
+ sport_handle->rx_buf = buf->area;
+
+ runtime->private_data = sport_handle;
+ } else {
+ pr_err("sport_handle is NULL\n");
+ return -1;
+ }
+ return 0;
+
+ out:
+ return ret;
+}
+
+static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ size_t size = vma->vm_end - vma->vm_start;
+ vma->vm_start = (unsigned long)runtime->dma_area;
+ vma->vm_end = vma->vm_start + size;
+ vma->vm_flags |= VM_SHARED;
+
+ return 0 ;
+}
+
+static struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
+ .open = bf5xx_pcm_open,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = bf5xx_pcm_hw_params,
+ .hw_free = bf5xx_pcm_hw_free,
+ .prepare = bf5xx_pcm_prepare,
+ .trigger = bf5xx_pcm_trigger,
+ .pointer = bf5xx_pcm_pointer,
+ .mmap = bf5xx_pcm_mmap,
+};
+
+static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area) {
+ pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
+ return -ENOMEM;
+ }
+ buf->bytes = size;
+
+ pr_debug("%s, area:%p, size:0x%08lx\n", __func__,
+ buf->area, buf->bytes);
+
+ return 0;
+}
+
+static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+ dma_free_coherent(NULL, buf->bytes, buf->area, 0);
+ buf->area = NULL;
+ }
+}
+
+static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
+
+int bf5xx_pcm_i2s_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ pr_debug("%s enter\n", __func__);
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &bf5xx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ if (dai->driver->playback.channels_min) {
+ ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+static struct snd_soc_platform_driver bf5xx_i2s_soc_platform = {
+ .ops = &bf5xx_pcm_i2s_ops,
+ .pcm_new = bf5xx_pcm_i2s_new,
+ .pcm_free = bf5xx_pcm_free_dma_buffers,
+};
+
+static int __devinit bfin_i2s_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &bf5xx_i2s_soc_platform);
+}
+
+static int __devexit bfin_i2s_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver bfin_i2s_pcm_driver = {
+ .driver = {
+ .name = "bfin-i2s-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = bfin_i2s_soc_platform_probe,
+ .remove = __devexit_p(bfin_i2s_soc_platform_remove),
+};
+
+static int __init snd_bfin_i2s_pcm_init(void)
+{
+ return platform_driver_register(&bfin_i2s_pcm_driver);
+}
+module_init(snd_bfin_i2s_pcm_init);
+
+static void __exit snd_bfin_i2s_pcm_exit(void)
+{
+ platform_driver_unregister(&bfin_i2s_pcm_driver);
+}
+module_exit(snd_bfin_i2s_pcm_exit);
+
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.h b/sound/soc/blackfin/bf5xx-i2s-pcm.h
new file mode 100644
index 00000000..0c2c5a68
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-i2s-pcm.h
@@ -0,0 +1,26 @@
+/*
+ * linux/sound/arm/bf5xx-i2s-pcm.h -- ALSA PCM interface for the Blackfin
+ *
+ * Copyright 2007 Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_I2S_PCM_H
+#define _BF5XX_I2S_PCM_H
+
+struct bf5xx_pcm_dma_params {
+ char *name; /* stream identifier */
+};
+
+struct bf5xx_gpio {
+ u32 sys;
+ u32 rx;
+ u32 tx;
+ u32 clk;
+ u32 frm;
+};
+
+#endif
diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c
new file mode 100644
index 00000000..00cc3e00
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-i2s.c
@@ -0,0 +1,308 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-i2s.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: Blackfin I2S CPU DAI driver
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/irq.h>
+#include <asm/portmux.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+
+#include "bf5xx-sport.h"
+
+struct bf5xx_i2s_port {
+ u16 tcr1;
+ u16 rcr1;
+ u16 tcr2;
+ u16 rcr2;
+ int configured;
+};
+
+static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
+ struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
+ int ret = 0;
+
+ /* interface format:support I2S,slave mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ bf5xx_i2s->tcr1 |= TFSR | TCKFE;
+ bf5xx_i2s->rcr1 |= RFSR | RCKFE;
+ bf5xx_i2s->tcr2 |= TSFSE;
+ bf5xx_i2s->rcr2 |= RSFSE;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ bf5xx_i2s->tcr1 |= TFSR;
+ bf5xx_i2s->rcr1 |= RFSR;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ret = -EINVAL;
+ break;
+ default:
+ printk(KERN_ERR "%s: Unknown DAI format type\n", __func__);
+ ret = -EINVAL;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ ret = -EINVAL;
+ break;
+ default:
+ printk(KERN_ERR "%s: Unknown DAI master type\n", __func__);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
+ struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
+ int ret = 0;
+
+ bf5xx_i2s->tcr2 &= ~0x1f;
+ bf5xx_i2s->rcr2 &= ~0x1f;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ bf5xx_i2s->tcr2 |= 7;
+ bf5xx_i2s->rcr2 |= 7;
+ sport_handle->wdsize = 1;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bf5xx_i2s->tcr2 |= 15;
+ bf5xx_i2s->rcr2 |= 15;
+ sport_handle->wdsize = 2;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ bf5xx_i2s->tcr2 |= 23;
+ bf5xx_i2s->rcr2 |= 23;
+ sport_handle->wdsize = 3;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bf5xx_i2s->tcr2 |= 31;
+ bf5xx_i2s->rcr2 |= 31;
+ sport_handle->wdsize = 4;
+ break;
+ }
+
+ if (!bf5xx_i2s->configured) {
+ /*
+ * TX and RX are not independent,they are enabled at the
+ * same time, even if only one side is running. So, we
+ * need to configure both of them at the time when the first
+ * stream is opened.
+ *
+ * CPU DAI:slave mode.
+ */
+ bf5xx_i2s->configured = 1;
+ ret = sport_config_rx(sport_handle, bf5xx_i2s->rcr1,
+ bf5xx_i2s->rcr2, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ ret = sport_config_tx(sport_handle, bf5xx_i2s->tcr1,
+ bf5xx_i2s->tcr2, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+
+static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
+ struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
+
+ pr_debug("%s enter\n", __func__);
+ /* No active stream, SPORT is allowed to be configured again. */
+ if (!dai->active)
+ bf5xx_i2s->configured = 0;
+}
+
+#ifdef CONFIG_PM
+static int bf5xx_i2s_suspend(struct snd_soc_dai *dai)
+{
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
+
+ pr_debug("%s : sport %d\n", __func__, dai->id);
+
+ if (dai->capture_active)
+ sport_rx_stop(sport_handle);
+ if (dai->playback_active)
+ sport_tx_stop(sport_handle);
+ return 0;
+}
+
+static int bf5xx_i2s_resume(struct snd_soc_dai *dai)
+{
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
+ struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
+ int ret;
+
+ pr_debug("%s : sport %d\n", __func__, dai->id);
+
+ ret = sport_config_rx(sport_handle, bf5xx_i2s->rcr1,
+ bf5xx_i2s->rcr2, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ ret = sport_config_tx(sport_handle, bf5xx_i2s->tcr1,
+ bf5xx_i2s->tcr2, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+#else
+#define bf5xx_i2s_suspend NULL
+#define bf5xx_i2s_resume NULL
+#endif
+
+#define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_96000)
+
+#define BF5XX_I2S_FORMATS \
+ (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops bf5xx_i2s_dai_ops = {
+ .shutdown = bf5xx_i2s_shutdown,
+ .hw_params = bf5xx_i2s_hw_params,
+ .set_fmt = bf5xx_i2s_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver bf5xx_i2s_dai = {
+ .suspend = bf5xx_i2s_suspend,
+ .resume = bf5xx_i2s_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = BF5XX_I2S_RATES,
+ .formats = BF5XX_I2S_FORMATS,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = BF5XX_I2S_RATES,
+ .formats = BF5XX_I2S_FORMATS,},
+ .ops = &bf5xx_i2s_dai_ops,
+};
+
+static int __devinit bf5xx_i2s_probe(struct platform_device *pdev)
+{
+ struct sport_device *sport_handle;
+ int ret;
+
+ /* configure SPORT for I2S */
+ sport_handle = sport_init(pdev, 4, 2 * sizeof(u32),
+ sizeof(struct bf5xx_i2s_port));
+ if (!sport_handle)
+ return -ENODEV;
+
+ /* register with the ASoC layers */
+ ret = snd_soc_register_dai(&pdev->dev, &bf5xx_i2s_dai);
+ if (ret) {
+ pr_err("Failed to register DAI: %d\n", ret);
+ sport_done(sport_handle);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit bf5xx_i2s_remove(struct platform_device *pdev)
+{
+ struct sport_device *sport_handle = platform_get_drvdata(pdev);
+
+ pr_debug("%s enter\n", __func__);
+
+ snd_soc_unregister_dai(&pdev->dev);
+ sport_done(sport_handle);
+
+ return 0;
+}
+
+static struct platform_driver bfin_i2s_driver = {
+ .probe = bf5xx_i2s_probe,
+ .remove = __devexit_p(bf5xx_i2s_remove),
+ .driver = {
+ .name = "bfin-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init bfin_i2s_init(void)
+{
+ return platform_driver_register(&bfin_i2s_driver);
+}
+
+static void __exit bfin_i2s_exit(void)
+{
+ platform_driver_unregister(&bfin_i2s_driver);
+}
+
+module_init(bfin_i2s_init);
+module_exit(bfin_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("I2S driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/blackfin/bf5xx-sport.c b/sound/soc/blackfin/bf5xx-sport.c
new file mode 100644
index 00000000..a2d40349
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-sport.c
@@ -0,0 +1,1093 @@
+/*
+ * File: bf5xx_sport.c
+ * Based on:
+ * Author: Roy Huang <roy.huang@analog.com>
+ *
+ * Created: Tue Sep 21 10:52:42 CEST 2004
+ * Description:
+ * Blackfin SPORT Driver
+ *
+ * Copyright 2004-2007 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/gpio.h>
+#include <linux/bug.h>
+#include <asm/portmux.h>
+#include <asm/dma.h>
+#include <asm/blackfin.h>
+#include <asm/cacheflush.h>
+
+#include "bf5xx-sport.h"
+/* delay between frame sync pulse and first data bit in multichannel mode */
+#define FRAME_DELAY (1<<12)
+
+/* note: multichannel is in units of 8 channels,
+ * tdm_count is # channels NOT / 8 ! */
+int sport_set_multichannel(struct sport_device *sport,
+ int tdm_count, u32 mask, int packed)
+{
+ pr_debug("%s tdm_count=%d mask:0x%08x packed=%d\n", __func__,
+ tdm_count, mask, packed);
+
+ if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
+ return -EBUSY;
+
+ if (tdm_count & 0x7)
+ return -EINVAL;
+
+ if (tdm_count > 32)
+ return -EINVAL; /* Only support less than 32 channels now */
+
+ if (tdm_count) {
+ sport->regs->mcmc1 = ((tdm_count>>3)-1) << 12;
+ sport->regs->mcmc2 = FRAME_DELAY | MCMEN | \
+ (packed ? (MCDTXPE|MCDRXPE) : 0);
+
+ sport->regs->mtcs0 = mask;
+ sport->regs->mrcs0 = mask;
+ sport->regs->mtcs1 = 0;
+ sport->regs->mrcs1 = 0;
+ sport->regs->mtcs2 = 0;
+ sport->regs->mrcs2 = 0;
+ sport->regs->mtcs3 = 0;
+ sport->regs->mrcs3 = 0;
+ } else {
+ sport->regs->mcmc1 = 0;
+ sport->regs->mcmc2 = 0;
+
+ sport->regs->mtcs0 = 0;
+ sport->regs->mrcs0 = 0;
+ }
+
+ sport->regs->mtcs1 = 0; sport->regs->mtcs2 = 0; sport->regs->mtcs3 = 0;
+ sport->regs->mrcs1 = 0; sport->regs->mrcs2 = 0; sport->regs->mrcs3 = 0;
+
+ SSYNC();
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_set_multichannel);
+
+int sport_config_rx(struct sport_device *sport, unsigned int rcr1,
+ unsigned int rcr2, unsigned int clkdiv, unsigned int fsdiv)
+{
+ if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
+ return -EBUSY;
+
+ sport->regs->rcr1 = rcr1;
+ sport->regs->rcr2 = rcr2;
+ sport->regs->rclkdiv = clkdiv;
+ sport->regs->rfsdiv = fsdiv;
+
+ SSYNC();
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_config_rx);
+
+int sport_config_tx(struct sport_device *sport, unsigned int tcr1,
+ unsigned int tcr2, unsigned int clkdiv, unsigned int fsdiv)
+{
+ if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
+ return -EBUSY;
+
+ sport->regs->tcr1 = tcr1;
+ sport->regs->tcr2 = tcr2;
+ sport->regs->tclkdiv = clkdiv;
+ sport->regs->tfsdiv = fsdiv;
+
+ SSYNC();
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_config_tx);
+
+static void setup_desc(struct dmasg *desc, void *buf, int fragcount,
+ size_t fragsize, unsigned int cfg,
+ unsigned int x_count, unsigned int ycount, size_t wdsize)
+{
+
+ int i;
+
+ for (i = 0; i < fragcount; ++i) {
+ desc[i].next_desc_addr = &(desc[i + 1]);
+ desc[i].start_addr = (unsigned long)buf + i*fragsize;
+ desc[i].cfg = cfg;
+ desc[i].x_count = x_count;
+ desc[i].x_modify = wdsize;
+ desc[i].y_count = ycount;
+ desc[i].y_modify = wdsize;
+ }
+
+ /* make circular */
+ desc[fragcount-1].next_desc_addr = desc;
+
+ pr_debug("setup desc: desc0=%p, next0=%p, desc1=%p,"
+ "next1=%p\nx_count=%x,y_count=%x,addr=0x%lx,cfs=0x%x\n",
+ desc, desc[0].next_desc_addr,
+ desc+1, desc[1].next_desc_addr,
+ desc[0].x_count, desc[0].y_count,
+ desc[0].start_addr, desc[0].cfg);
+}
+
+static int sport_start(struct sport_device *sport)
+{
+ enable_dma(sport->dma_rx_chan);
+ enable_dma(sport->dma_tx_chan);
+ sport->regs->rcr1 |= RSPEN;
+ sport->regs->tcr1 |= TSPEN;
+ SSYNC();
+
+ return 0;
+}
+
+static int sport_stop(struct sport_device *sport)
+{
+ sport->regs->tcr1 &= ~TSPEN;
+ sport->regs->rcr1 &= ~RSPEN;
+ SSYNC();
+
+ disable_dma(sport->dma_rx_chan);
+ disable_dma(sport->dma_tx_chan);
+ return 0;
+}
+
+static inline int sport_hook_rx_dummy(struct sport_device *sport)
+{
+ struct dmasg *desc, temp_desc;
+ unsigned long flags;
+
+ BUG_ON(sport->dummy_rx_desc == NULL);
+ BUG_ON(sport->curr_rx_desc == sport->dummy_rx_desc);
+
+ /* Maybe the dummy buffer descriptor ring is damaged */
+ sport->dummy_rx_desc->next_desc_addr = sport->dummy_rx_desc + 1;
+
+ local_irq_save(flags);
+ desc = get_dma_next_desc_ptr(sport->dma_rx_chan);
+ /* Copy the descriptor which will be damaged to backup */
+ temp_desc = *desc;
+ desc->x_count = sport->dummy_count / 2;
+ desc->y_count = 0;
+ desc->next_desc_addr = sport->dummy_rx_desc;
+ local_irq_restore(flags);
+ /* Waiting for dummy buffer descriptor is already hooked*/
+ while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) -
+ sizeof(struct dmasg)) != sport->dummy_rx_desc)
+ continue;
+ sport->curr_rx_desc = sport->dummy_rx_desc;
+ /* Restore the damaged descriptor */
+ *desc = temp_desc;
+
+ return 0;
+}
+
+static inline int sport_rx_dma_start(struct sport_device *sport, int dummy)
+{
+ if (dummy) {
+ sport->dummy_rx_desc->next_desc_addr = sport->dummy_rx_desc;
+ sport->curr_rx_desc = sport->dummy_rx_desc;
+ } else
+ sport->curr_rx_desc = sport->dma_rx_desc;
+
+ set_dma_next_desc_addr(sport->dma_rx_chan, sport->curr_rx_desc);
+ set_dma_x_count(sport->dma_rx_chan, 0);
+ set_dma_x_modify(sport->dma_rx_chan, 0);
+ set_dma_config(sport->dma_rx_chan, (DMAFLOW_LARGE | NDSIZE_9 | \
+ WDSIZE_32 | WNR));
+ set_dma_curr_addr(sport->dma_rx_chan, sport->curr_rx_desc->start_addr);
+ SSYNC();
+
+ return 0;
+}
+
+static inline int sport_tx_dma_start(struct sport_device *sport, int dummy)
+{
+ if (dummy) {
+ sport->dummy_tx_desc->next_desc_addr = sport->dummy_tx_desc;
+ sport->curr_tx_desc = sport->dummy_tx_desc;
+ } else
+ sport->curr_tx_desc = sport->dma_tx_desc;
+
+ set_dma_next_desc_addr(sport->dma_tx_chan, sport->curr_tx_desc);
+ set_dma_x_count(sport->dma_tx_chan, 0);
+ set_dma_x_modify(sport->dma_tx_chan, 0);
+ set_dma_config(sport->dma_tx_chan,
+ (DMAFLOW_LARGE | NDSIZE_9 | WDSIZE_32));
+ set_dma_curr_addr(sport->dma_tx_chan, sport->curr_tx_desc->start_addr);
+ SSYNC();
+
+ return 0;
+}
+
+int sport_rx_start(struct sport_device *sport)
+{
+ unsigned long flags;
+ pr_debug("%s enter\n", __func__);
+ if (sport->rx_run)
+ return -EBUSY;
+ if (sport->tx_run) {
+ /* tx is running, rx is not running */
+ BUG_ON(sport->dma_rx_desc == NULL);
+ BUG_ON(sport->curr_rx_desc != sport->dummy_rx_desc);
+ local_irq_save(flags);
+ while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) -
+ sizeof(struct dmasg)) != sport->dummy_rx_desc)
+ continue;
+ sport->dummy_rx_desc->next_desc_addr = sport->dma_rx_desc;
+ local_irq_restore(flags);
+ sport->curr_rx_desc = sport->dma_rx_desc;
+ } else {
+ sport_tx_dma_start(sport, 1);
+ sport_rx_dma_start(sport, 0);
+ sport_start(sport);
+ }
+
+ sport->rx_run = 1;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_rx_start);
+
+int sport_rx_stop(struct sport_device *sport)
+{
+ pr_debug("%s enter\n", __func__);
+
+ if (!sport->rx_run)
+ return 0;
+ if (sport->tx_run) {
+ /* TX dma is still running, hook the dummy buffer */
+ sport_hook_rx_dummy(sport);
+ } else {
+ /* Both rx and tx dma will be stopped */
+ sport_stop(sport);
+ sport->curr_rx_desc = NULL;
+ sport->curr_tx_desc = NULL;
+ }
+
+ sport->rx_run = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_rx_stop);
+
+static inline int sport_hook_tx_dummy(struct sport_device *sport)
+{
+ struct dmasg *desc, temp_desc;
+ unsigned long flags;
+
+ BUG_ON(sport->dummy_tx_desc == NULL);
+ BUG_ON(sport->curr_tx_desc == sport->dummy_tx_desc);
+
+ sport->dummy_tx_desc->next_desc_addr = sport->dummy_tx_desc + 1;
+
+ /* Shorten the time on last normal descriptor */
+ local_irq_save(flags);
+ desc = get_dma_next_desc_ptr(sport->dma_tx_chan);
+ /* Store the descriptor which will be damaged */
+ temp_desc = *desc;
+ desc->x_count = sport->dummy_count / 2;
+ desc->y_count = 0;
+ desc->next_desc_addr = sport->dummy_tx_desc;
+ local_irq_restore(flags);
+ /* Waiting for dummy buffer descriptor is already hooked*/
+ while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) - \
+ sizeof(struct dmasg)) != sport->dummy_tx_desc)
+ continue;
+ sport->curr_tx_desc = sport->dummy_tx_desc;
+ /* Restore the damaged descriptor */
+ *desc = temp_desc;
+
+ return 0;
+}
+
+int sport_tx_start(struct sport_device *sport)
+{
+ unsigned long flags;
+ pr_debug("%s: tx_run:%d, rx_run:%d\n", __func__,
+ sport->tx_run, sport->rx_run);
+ if (sport->tx_run)
+ return -EBUSY;
+ if (sport->rx_run) {
+ BUG_ON(sport->dma_tx_desc == NULL);
+ BUG_ON(sport->curr_tx_desc != sport->dummy_tx_desc);
+ /* Hook the normal buffer descriptor */
+ local_irq_save(flags);
+ while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) -
+ sizeof(struct dmasg)) != sport->dummy_tx_desc)
+ continue;
+ sport->dummy_tx_desc->next_desc_addr = sport->dma_tx_desc;
+ local_irq_restore(flags);
+ sport->curr_tx_desc = sport->dma_tx_desc;
+ } else {
+
+ sport_tx_dma_start(sport, 0);
+ /* Let rx dma run the dummy buffer */
+ sport_rx_dma_start(sport, 1);
+ sport_start(sport);
+ }
+ sport->tx_run = 1;
+ return 0;
+}
+EXPORT_SYMBOL(sport_tx_start);
+
+int sport_tx_stop(struct sport_device *sport)
+{
+ if (!sport->tx_run)
+ return 0;
+ if (sport->rx_run) {
+ /* RX is still running, hook the dummy buffer */
+ sport_hook_tx_dummy(sport);
+ } else {
+ /* Both rx and tx dma stopped */
+ sport_stop(sport);
+ sport->curr_rx_desc = NULL;
+ sport->curr_tx_desc = NULL;
+ }
+
+ sport->tx_run = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_tx_stop);
+
+static inline int compute_wdsize(size_t wdsize)
+{
+ switch (wdsize) {
+ case 1:
+ return WDSIZE_8;
+ case 2:
+ return WDSIZE_16;
+ case 4:
+ default:
+ return WDSIZE_32;
+ }
+}
+
+int sport_config_rx_dma(struct sport_device *sport, void *buf,
+ int fragcount, size_t fragsize)
+{
+ unsigned int x_count;
+ unsigned int y_count;
+ unsigned int cfg;
+ dma_addr_t addr;
+
+ pr_debug("%s buf:%p, frag:%d, fragsize:0x%lx\n", __func__, \
+ buf, fragcount, fragsize);
+
+ x_count = fragsize / sport->wdsize;
+ y_count = 0;
+
+ /* for fragments larger than 64k words we use 2d dma,
+ * denote fragecount as two numbers' mutliply and both of them
+ * are less than 64k.*/
+ if (x_count >= 0x10000) {
+ int i, count = x_count;
+
+ for (i = 16; i > 0; i--) {
+ x_count = 1 << i;
+ if ((count & (x_count - 1)) == 0) {
+ y_count = count >> i;
+ if (y_count < 0x10000)
+ break;
+ }
+ }
+ if (i == 0)
+ return -EINVAL;
+ }
+ pr_debug("%s(x_count:0x%x, y_count:0x%x)\n", __func__,
+ x_count, y_count);
+
+ if (sport->dma_rx_desc)
+ dma_free_coherent(NULL, sport->rx_desc_bytes,
+ sport->dma_rx_desc, 0);
+
+ /* Allocate a new descritor ring as current one. */
+ sport->dma_rx_desc = dma_alloc_coherent(NULL, \
+ fragcount * sizeof(struct dmasg), &addr, 0);
+ sport->rx_desc_bytes = fragcount * sizeof(struct dmasg);
+
+ if (!sport->dma_rx_desc) {
+ pr_err("Failed to allocate memory for rx desc\n");
+ return -ENOMEM;
+ }
+
+ sport->rx_buf = buf;
+ sport->rx_fragsize = fragsize;
+ sport->rx_frags = fragcount;
+
+ cfg = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | WNR | \
+ (DESC_ELEMENT_COUNT << 8); /* large descriptor mode */
+
+ if (y_count != 0)
+ cfg |= DMA2D;
+
+ setup_desc(sport->dma_rx_desc, buf, fragcount, fragsize,
+ cfg|DMAEN, x_count, y_count, sport->wdsize);
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_config_rx_dma);
+
+int sport_config_tx_dma(struct sport_device *sport, void *buf, \
+ int fragcount, size_t fragsize)
+{
+ unsigned int x_count;
+ unsigned int y_count;
+ unsigned int cfg;
+ dma_addr_t addr;
+
+ pr_debug("%s buf:%p, fragcount:%d, fragsize:0x%lx\n",
+ __func__, buf, fragcount, fragsize);
+
+ x_count = fragsize/sport->wdsize;
+ y_count = 0;
+
+ /* for fragments larger than 64k words we use 2d dma,
+ * denote fragecount as two numbers' mutliply and both of them
+ * are less than 64k.*/
+ if (x_count >= 0x10000) {
+ int i, count = x_count;
+
+ for (i = 16; i > 0; i--) {
+ x_count = 1 << i;
+ if ((count & (x_count - 1)) == 0) {
+ y_count = count >> i;
+ if (y_count < 0x10000)
+ break;
+ }
+ }
+ if (i == 0)
+ return -EINVAL;
+ }
+ pr_debug("%s x_count:0x%x, y_count:0x%x\n", __func__,
+ x_count, y_count);
+
+
+ if (sport->dma_tx_desc) {
+ dma_free_coherent(NULL, sport->tx_desc_bytes, \
+ sport->dma_tx_desc, 0);
+ }
+
+ sport->dma_tx_desc = dma_alloc_coherent(NULL, \
+ fragcount * sizeof(struct dmasg), &addr, 0);
+ sport->tx_desc_bytes = fragcount * sizeof(struct dmasg);
+ if (!sport->dma_tx_desc) {
+ pr_err("Failed to allocate memory for tx desc\n");
+ return -ENOMEM;
+ }
+
+ sport->tx_buf = buf;
+ sport->tx_fragsize = fragsize;
+ sport->tx_frags = fragcount;
+ cfg = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | \
+ (DESC_ELEMENT_COUNT << 8); /* large descriptor mode */
+
+ if (y_count != 0)
+ cfg |= DMA2D;
+
+ setup_desc(sport->dma_tx_desc, buf, fragcount, fragsize,
+ cfg|DMAEN, x_count, y_count, sport->wdsize);
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_config_tx_dma);
+
+/* setup dummy dma descriptor ring, which don't generate interrupts,
+ * the x_modify is set to 0 */
+static int sport_config_rx_dummy(struct sport_device *sport)
+{
+ struct dmasg *desc;
+ unsigned config;
+
+ pr_debug("%s entered\n", __func__);
+ if (L1_DATA_A_LENGTH)
+ desc = l1_data_sram_zalloc(2 * sizeof(*desc));
+ else {
+ dma_addr_t addr;
+ desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0);
+ memset(desc, 0, 2 * sizeof(*desc));
+ }
+ if (desc == NULL) {
+ pr_err("Failed to allocate memory for dummy rx desc\n");
+ return -ENOMEM;
+ }
+ sport->dummy_rx_desc = desc;
+ desc->start_addr = (unsigned long)sport->dummy_buf;
+ config = DMAFLOW_LARGE | NDSIZE_9 | compute_wdsize(sport->wdsize)
+ | WNR | DMAEN;
+ desc->cfg = config;
+ desc->x_count = sport->dummy_count/sport->wdsize;
+ desc->x_modify = sport->wdsize;
+ desc->y_count = 0;
+ desc->y_modify = 0;
+ memcpy(desc+1, desc, sizeof(*desc));
+ desc->next_desc_addr = desc + 1;
+ desc[1].next_desc_addr = desc;
+ return 0;
+}
+
+static int sport_config_tx_dummy(struct sport_device *sport)
+{
+ struct dmasg *desc;
+ unsigned int config;
+
+ pr_debug("%s entered\n", __func__);
+
+ if (L1_DATA_A_LENGTH)
+ desc = l1_data_sram_zalloc(2 * sizeof(*desc));
+ else {
+ dma_addr_t addr;
+ desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0);
+ memset(desc, 0, 2 * sizeof(*desc));
+ }
+ if (!desc) {
+ pr_err("Failed to allocate memory for dummy tx desc\n");
+ return -ENOMEM;
+ }
+ sport->dummy_tx_desc = desc;
+ desc->start_addr = (unsigned long)sport->dummy_buf + \
+ sport->dummy_count;
+ config = DMAFLOW_LARGE | NDSIZE_9 |
+ compute_wdsize(sport->wdsize) | DMAEN;
+ desc->cfg = config;
+ desc->x_count = sport->dummy_count/sport->wdsize;
+ desc->x_modify = sport->wdsize;
+ desc->y_count = 0;
+ desc->y_modify = 0;
+ memcpy(desc+1, desc, sizeof(*desc));
+ desc->next_desc_addr = desc + 1;
+ desc[1].next_desc_addr = desc;
+ return 0;
+}
+
+unsigned long sport_curr_offset_rx(struct sport_device *sport)
+{
+ unsigned long curr = get_dma_curr_addr(sport->dma_rx_chan);
+
+ return (unsigned char *)curr - sport->rx_buf;
+}
+EXPORT_SYMBOL(sport_curr_offset_rx);
+
+unsigned long sport_curr_offset_tx(struct sport_device *sport)
+{
+ unsigned long curr = get_dma_curr_addr(sport->dma_tx_chan);
+
+ return (unsigned char *)curr - sport->tx_buf;
+}
+EXPORT_SYMBOL(sport_curr_offset_tx);
+
+void sport_incfrag(struct sport_device *sport, int *frag, int tx)
+{
+ ++(*frag);
+ if (tx == 1 && *frag == sport->tx_frags)
+ *frag = 0;
+
+ if (tx == 0 && *frag == sport->rx_frags)
+ *frag = 0;
+}
+EXPORT_SYMBOL(sport_incfrag);
+
+void sport_decfrag(struct sport_device *sport, int *frag, int tx)
+{
+ --(*frag);
+ if (tx == 1 && *frag == 0)
+ *frag = sport->tx_frags;
+
+ if (tx == 0 && *frag == 0)
+ *frag = sport->rx_frags;
+}
+EXPORT_SYMBOL(sport_decfrag);
+
+static int sport_check_status(struct sport_device *sport,
+ unsigned int *sport_stat,
+ unsigned int *rx_stat,
+ unsigned int *tx_stat)
+{
+ int status = 0;
+
+ if (sport_stat) {
+ SSYNC();
+ status = sport->regs->stat;
+ if (status & (TOVF|TUVF|ROVF|RUVF))
+ sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF));
+ SSYNC();
+ *sport_stat = status;
+ }
+
+ if (rx_stat) {
+ SSYNC();
+ status = get_dma_curr_irqstat(sport->dma_rx_chan);
+ if (status & (DMA_DONE|DMA_ERR))
+ clear_dma_irqstat(sport->dma_rx_chan);
+ SSYNC();
+ *rx_stat = status;
+ }
+
+ if (tx_stat) {
+ SSYNC();
+ status = get_dma_curr_irqstat(sport->dma_tx_chan);
+ if (status & (DMA_DONE|DMA_ERR))
+ clear_dma_irqstat(sport->dma_tx_chan);
+ SSYNC();
+ *tx_stat = status;
+ }
+
+ return 0;
+}
+
+int sport_dump_stat(struct sport_device *sport, char *buf, size_t len)
+{
+ int ret;
+
+ ret = snprintf(buf, len,
+ "sts: 0x%04x\n"
+ "rx dma %d sts: 0x%04x tx dma %d sts: 0x%04x\n",
+ sport->regs->stat,
+ sport->dma_rx_chan,
+ get_dma_curr_irqstat(sport->dma_rx_chan),
+ sport->dma_tx_chan,
+ get_dma_curr_irqstat(sport->dma_tx_chan));
+ buf += ret;
+ len -= ret;
+
+ ret += snprintf(buf, len,
+ "curr_rx_desc:0x%p, curr_tx_desc:0x%p\n"
+ "dma_rx_desc:0x%p, dma_tx_desc:0x%p\n"
+ "dummy_rx_desc:0x%p, dummy_tx_desc:0x%p\n",
+ sport->curr_rx_desc, sport->curr_tx_desc,
+ sport->dma_rx_desc, sport->dma_tx_desc,
+ sport->dummy_rx_desc, sport->dummy_tx_desc);
+
+ return ret;
+}
+
+static irqreturn_t rx_handler(int irq, void *dev_id)
+{
+ unsigned int rx_stat;
+ struct sport_device *sport = dev_id;
+
+ pr_debug("%s enter\n", __func__);
+ sport_check_status(sport, NULL, &rx_stat, NULL);
+ if (!(rx_stat & DMA_DONE))
+ pr_err("rx dma is already stopped\n");
+
+ if (sport->rx_callback) {
+ sport->rx_callback(sport->rx_data);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t tx_handler(int irq, void *dev_id)
+{
+ unsigned int tx_stat;
+ struct sport_device *sport = dev_id;
+ pr_debug("%s enter\n", __func__);
+ sport_check_status(sport, NULL, NULL, &tx_stat);
+ if (!(tx_stat & DMA_DONE)) {
+ pr_err("tx dma is already stopped\n");
+ return IRQ_HANDLED;
+ }
+ if (sport->tx_callback) {
+ sport->tx_callback(sport->tx_data);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t err_handler(int irq, void *dev_id)
+{
+ unsigned int status = 0;
+ struct sport_device *sport = dev_id;
+
+ pr_debug("%s\n", __func__);
+ if (sport_check_status(sport, &status, NULL, NULL)) {
+ pr_err("error checking status ??");
+ return IRQ_NONE;
+ }
+
+ if (status & (TOVF|TUVF|ROVF|RUVF)) {
+ pr_info("sport status error:%s%s%s%s\n",
+ status & TOVF ? " TOVF" : "",
+ status & TUVF ? " TUVF" : "",
+ status & ROVF ? " ROVF" : "",
+ status & RUVF ? " RUVF" : "");
+ if (status & TOVF || status & TUVF) {
+ disable_dma(sport->dma_tx_chan);
+ if (sport->tx_run)
+ sport_tx_dma_start(sport, 0);
+ else
+ sport_tx_dma_start(sport, 1);
+ enable_dma(sport->dma_tx_chan);
+ } else {
+ disable_dma(sport->dma_rx_chan);
+ if (sport->rx_run)
+ sport_rx_dma_start(sport, 0);
+ else
+ sport_rx_dma_start(sport, 1);
+ enable_dma(sport->dma_rx_chan);
+ }
+ }
+ status = sport->regs->stat;
+ if (status & (TOVF|TUVF|ROVF|RUVF))
+ sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF));
+ SSYNC();
+
+ if (sport->err_callback)
+ sport->err_callback(sport->err_data);
+
+ return IRQ_HANDLED;
+}
+
+int sport_set_rx_callback(struct sport_device *sport,
+ void (*rx_callback)(void *), void *rx_data)
+{
+ BUG_ON(rx_callback == NULL);
+ sport->rx_callback = rx_callback;
+ sport->rx_data = rx_data;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_set_rx_callback);
+
+int sport_set_tx_callback(struct sport_device *sport,
+ void (*tx_callback)(void *), void *tx_data)
+{
+ BUG_ON(tx_callback == NULL);
+ sport->tx_callback = tx_callback;
+ sport->tx_data = tx_data;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_set_tx_callback);
+
+int sport_set_err_callback(struct sport_device *sport,
+ void (*err_callback)(void *), void *err_data)
+{
+ BUG_ON(err_callback == NULL);
+ sport->err_callback = err_callback;
+ sport->err_data = err_data;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_set_err_callback);
+
+static int sport_config_pdev(struct platform_device *pdev, struct sport_param *param)
+{
+ /* Extract settings from platform data */
+ struct device *dev = &pdev->dev;
+ struct bfin_snd_platform_data *pdata = dev->platform_data;
+ struct resource *res;
+
+ param->num = pdev->id;
+
+ if (!pdata) {
+ dev_err(dev, "no platform_data\n");
+ return -ENODEV;
+ }
+ param->pin_req = pdata->pin_req;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "no MEM resource\n");
+ return -ENODEV;
+ }
+ param->regs = (struct sport_register *)res->start;
+
+ /* first RX, then TX */
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!res) {
+ dev_err(dev, "no rx DMA resource\n");
+ return -ENODEV;
+ }
+ param->dma_rx_chan = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!res) {
+ dev_err(dev, "no tx DMA resource\n");
+ return -ENODEV;
+ }
+ param->dma_tx_chan = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(dev, "no irq resource\n");
+ return -ENODEV;
+ }
+ param->err_irq = res->start;
+
+ return 0;
+}
+
+struct sport_device *sport_init(struct platform_device *pdev,
+ unsigned int wdsize, unsigned int dummy_count, size_t priv_size)
+{
+ struct device *dev = &pdev->dev;
+ struct sport_param param;
+ struct sport_device *sport;
+ int ret;
+
+ dev_dbg(dev, "%s enter\n", __func__);
+
+ param.wdsize = wdsize;
+ param.dummy_count = dummy_count;
+ BUG_ON(param.wdsize == 0 || param.dummy_count == 0);
+
+ ret = sport_config_pdev(pdev, &param);
+ if (ret)
+ return NULL;
+
+ if (peripheral_request_list(param.pin_req, "soc-audio")) {
+ dev_err(dev, "requesting Peripherals failed\n");
+ return NULL;
+ }
+
+ sport = kzalloc(sizeof(*sport), GFP_KERNEL);
+ if (!sport) {
+ dev_err(dev, "failed to allocate for sport device\n");
+ goto __init_err0;
+ }
+
+ sport->num = param.num;
+ sport->dma_rx_chan = param.dma_rx_chan;
+ sport->dma_tx_chan = param.dma_tx_chan;
+ sport->err_irq = param.err_irq;
+ sport->regs = param.regs;
+ sport->pin_req = param.pin_req;
+
+ if (request_dma(sport->dma_rx_chan, "SPORT RX Data") == -EBUSY) {
+ dev_err(dev, "failed to request RX dma %d\n", sport->dma_rx_chan);
+ goto __init_err1;
+ }
+ if (set_dma_callback(sport->dma_rx_chan, rx_handler, sport) != 0) {
+ dev_err(dev, "failed to request RX irq %d\n", sport->dma_rx_chan);
+ goto __init_err2;
+ }
+
+ if (request_dma(sport->dma_tx_chan, "SPORT TX Data") == -EBUSY) {
+ dev_err(dev, "failed to request TX dma %d\n", sport->dma_tx_chan);
+ goto __init_err2;
+ }
+
+ if (set_dma_callback(sport->dma_tx_chan, tx_handler, sport) != 0) {
+ dev_err(dev, "failed to request TX irq %d\n", sport->dma_tx_chan);
+ goto __init_err3;
+ }
+
+ if (request_irq(sport->err_irq, err_handler, IRQF_SHARED, "SPORT err",
+ sport) < 0) {
+ dev_err(dev, "failed to request err irq %d\n", sport->err_irq);
+ goto __init_err3;
+ }
+
+ dev_info(dev, "dma rx:%d tx:%d, err irq:%d, regs:%p\n",
+ sport->dma_rx_chan, sport->dma_tx_chan,
+ sport->err_irq, sport->regs);
+
+ sport->wdsize = param.wdsize;
+ sport->dummy_count = param.dummy_count;
+
+ sport->private_data = kzalloc(priv_size, GFP_KERNEL);
+ if (!sport->private_data) {
+ dev_err(dev, "could not alloc priv data %zu bytes\n", priv_size);
+ goto __init_err4;
+ }
+
+ if (L1_DATA_A_LENGTH)
+ sport->dummy_buf = l1_data_sram_zalloc(param.dummy_count * 2);
+ else
+ sport->dummy_buf = kzalloc(param.dummy_count * 2, GFP_KERNEL);
+ if (sport->dummy_buf == NULL) {
+ dev_err(dev, "failed to allocate dummy buffer\n");
+ goto __error1;
+ }
+
+ ret = sport_config_rx_dummy(sport);
+ if (ret) {
+ dev_err(dev, "failed to config rx dummy ring\n");
+ goto __error2;
+ }
+ ret = sport_config_tx_dummy(sport);
+ if (ret) {
+ dev_err(dev, "failed to config tx dummy ring\n");
+ goto __error3;
+ }
+
+ platform_set_drvdata(pdev, sport);
+
+ return sport;
+__error3:
+ if (L1_DATA_A_LENGTH)
+ l1_data_sram_free(sport->dummy_rx_desc);
+ else
+ dma_free_coherent(NULL, 2*sizeof(struct dmasg),
+ sport->dummy_rx_desc, 0);
+__error2:
+ if (L1_DATA_A_LENGTH)
+ l1_data_sram_free(sport->dummy_buf);
+ else
+ kfree(sport->dummy_buf);
+__error1:
+ kfree(sport->private_data);
+__init_err4:
+ free_irq(sport->err_irq, sport);
+__init_err3:
+ free_dma(sport->dma_tx_chan);
+__init_err2:
+ free_dma(sport->dma_rx_chan);
+__init_err1:
+ kfree(sport);
+__init_err0:
+ peripheral_free_list(param.pin_req);
+ return NULL;
+}
+EXPORT_SYMBOL(sport_init);
+
+void sport_done(struct sport_device *sport)
+{
+ if (sport == NULL)
+ return;
+
+ sport_stop(sport);
+ if (sport->dma_rx_desc)
+ dma_free_coherent(NULL, sport->rx_desc_bytes,
+ sport->dma_rx_desc, 0);
+ if (sport->dma_tx_desc)
+ dma_free_coherent(NULL, sport->tx_desc_bytes,
+ sport->dma_tx_desc, 0);
+
+#if L1_DATA_A_LENGTH != 0
+ l1_data_sram_free(sport->dummy_rx_desc);
+ l1_data_sram_free(sport->dummy_tx_desc);
+ l1_data_sram_free(sport->dummy_buf);
+#else
+ dma_free_coherent(NULL, 2*sizeof(struct dmasg),
+ sport->dummy_rx_desc, 0);
+ dma_free_coherent(NULL, 2*sizeof(struct dmasg),
+ sport->dummy_tx_desc, 0);
+ kfree(sport->dummy_buf);
+#endif
+ free_dma(sport->dma_rx_chan);
+ free_dma(sport->dma_tx_chan);
+ free_irq(sport->err_irq, sport);
+
+ kfree(sport->private_data);
+ peripheral_free_list(sport->pin_req);
+ kfree(sport);
+}
+EXPORT_SYMBOL(sport_done);
+
+/*
+* It is only used to send several bytes when dma is not enabled
+ * sport controller is configured but not enabled.
+ * Multichannel cannot works with pio mode */
+/* Used by ac97 to write and read codec register */
+int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \
+ u8 *in_data, int len)
+{
+ unsigned short dma_config;
+ unsigned short status;
+ unsigned long flags;
+ unsigned long wait = 0;
+
+ pr_debug("%s enter, out_data:%p, in_data:%p len:%d\n", \
+ __func__, out_data, in_data, len);
+ pr_debug("tcr1:0x%04x, tcr2:0x%04x, tclkdiv:0x%04x, tfsdiv:0x%04x\n"
+ "mcmc1:0x%04x, mcmc2:0x%04x\n",
+ sport->regs->tcr1, sport->regs->tcr2,
+ sport->regs->tclkdiv, sport->regs->tfsdiv,
+ sport->regs->mcmc1, sport->regs->mcmc2);
+ flush_dcache_range((unsigned)out_data, (unsigned)(out_data + len));
+
+ /* Enable tx dma */
+ dma_config = (RESTART | WDSIZE_16 | DI_EN);
+ set_dma_start_addr(sport->dma_tx_chan, (unsigned long)out_data);
+ set_dma_x_count(sport->dma_tx_chan, len/2);
+ set_dma_x_modify(sport->dma_tx_chan, 2);
+ set_dma_config(sport->dma_tx_chan, dma_config);
+ enable_dma(sport->dma_tx_chan);
+
+ if (in_data != NULL) {
+ invalidate_dcache_range((unsigned)in_data, \
+ (unsigned)(in_data + len));
+ /* Enable rx dma */
+ dma_config = (RESTART | WDSIZE_16 | WNR | DI_EN);
+ set_dma_start_addr(sport->dma_rx_chan, (unsigned long)in_data);
+ set_dma_x_count(sport->dma_rx_chan, len/2);
+ set_dma_x_modify(sport->dma_rx_chan, 2);
+ set_dma_config(sport->dma_rx_chan, dma_config);
+ enable_dma(sport->dma_rx_chan);
+ }
+
+ local_irq_save(flags);
+ sport->regs->tcr1 |= TSPEN;
+ sport->regs->rcr1 |= RSPEN;
+ SSYNC();
+
+ status = get_dma_curr_irqstat(sport->dma_tx_chan);
+ while (status & DMA_RUN) {
+ udelay(1);
+ status = get_dma_curr_irqstat(sport->dma_tx_chan);
+ pr_debug("DMA status:0x%04x\n", status);
+ if (wait++ > 100)
+ goto __over;
+ }
+ status = sport->regs->stat;
+ wait = 0;
+
+ while (!(status & TXHRE)) {
+ pr_debug("sport status:0x%04x\n", status);
+ udelay(1);
+ status = *(unsigned short *)&sport->regs->stat;
+ if (wait++ > 1000)
+ goto __over;
+ }
+ /* Wait for the last byte sent out */
+ udelay(20);
+ pr_debug("sport status:0x%04x\n", status);
+
+__over:
+ sport->regs->tcr1 &= ~TSPEN;
+ sport->regs->rcr1 &= ~RSPEN;
+ SSYNC();
+ disable_dma(sport->dma_tx_chan);
+ /* Clear the status */
+ clear_dma_irqstat(sport->dma_tx_chan);
+ if (in_data != NULL) {
+ disable_dma(sport->dma_rx_chan);
+ clear_dma_irqstat(sport->dma_rx_chan);
+ }
+ SSYNC();
+ local_irq_restore(flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_send_and_recv);
+
+MODULE_AUTHOR("Roy Huang");
+MODULE_DESCRIPTION("SPORT driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-sport.h b/sound/soc/blackfin/bf5xx-sport.h
new file mode 100644
index 00000000..5ab60bd6
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-sport.h
@@ -0,0 +1,174 @@
+/*
+ * File: bf5xx_sport.h
+ * Based on:
+ * Author: Roy Huang <roy.huang@analog.com>
+ *
+ * Created:
+ * Description:
+ *
+ * Copyright 2004-2007 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#ifndef __BF5XX_SPORT_H__
+#define __BF5XX_SPORT_H__
+
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <asm/dma.h>
+#include <asm/bfin_sport.h>
+
+#define DESC_ELEMENT_COUNT 9
+
+struct sport_device {
+ int num;
+ int dma_rx_chan;
+ int dma_tx_chan;
+ int err_irq;
+ const unsigned short *pin_req;
+ struct sport_register *regs;
+
+ unsigned char *rx_buf;
+ unsigned char *tx_buf;
+ unsigned int rx_fragsize;
+ unsigned int tx_fragsize;
+ unsigned int rx_frags;
+ unsigned int tx_frags;
+ unsigned int wdsize;
+
+ /* for dummy dma transfer */
+ void *dummy_buf;
+ unsigned int dummy_count;
+
+ /* DMA descriptor ring head of current audio stream*/
+ struct dmasg *dma_rx_desc;
+ struct dmasg *dma_tx_desc;
+ unsigned int rx_desc_bytes;
+ unsigned int tx_desc_bytes;
+
+ unsigned int rx_run:1; /* rx is running */
+ unsigned int tx_run:1; /* tx is running */
+
+ struct dmasg *dummy_rx_desc;
+ struct dmasg *dummy_tx_desc;
+
+ struct dmasg *curr_rx_desc;
+ struct dmasg *curr_tx_desc;
+
+ int rx_curr_frag;
+ int tx_curr_frag;
+
+ unsigned int rcr1;
+ unsigned int rcr2;
+ int rx_tdm_count;
+
+ unsigned int tcr1;
+ unsigned int tcr2;
+ int tx_tdm_count;
+
+ void (*rx_callback)(void *data);
+ void *rx_data;
+ void (*tx_callback)(void *data);
+ void *tx_data;
+ void (*err_callback)(void *data);
+ void *err_data;
+ unsigned char *tx_dma_buf;
+ unsigned char *rx_dma_buf;
+#ifdef CONFIG_SND_BF5XX_MMAP_SUPPORT
+ dma_addr_t tx_dma_phy;
+ dma_addr_t rx_dma_phy;
+ int tx_pos;/*pcm sample count*/
+ int rx_pos;
+ unsigned int tx_buffer_size;
+ unsigned int rx_buffer_size;
+ int tx_delay_pos;
+ int once;
+#endif
+ void *private_data;
+};
+
+struct sport_param {
+ int num;
+ int dma_rx_chan;
+ int dma_tx_chan;
+ int err_irq;
+ const unsigned short *pin_req;
+ struct sport_register *regs;
+ unsigned int wdsize;
+ unsigned int dummy_count;
+ void *private_data;
+};
+
+struct sport_device *sport_init(struct platform_device *pdev,
+ unsigned int wdsize, unsigned int dummy_count, size_t priv_size);
+
+void sport_done(struct sport_device *sport);
+
+/* first use these ...*/
+
+/* note: multichannel is in units of 8 channels, tdm_count is number of channels
+ * NOT / 8 ! all channels are enabled by default */
+int sport_set_multichannel(struct sport_device *sport, int tdm_count,
+ u32 mask, int packed);
+
+int sport_config_rx(struct sport_device *sport,
+ unsigned int rcr1, unsigned int rcr2,
+ unsigned int clkdiv, unsigned int fsdiv);
+
+int sport_config_tx(struct sport_device *sport,
+ unsigned int tcr1, unsigned int tcr2,
+ unsigned int clkdiv, unsigned int fsdiv);
+
+/* ... then these: */
+
+/* buffer size (in bytes) == fragcount * fragsize_bytes */
+
+/* this is not a very general api, it sets the dma to 2d autobuffer mode */
+
+int sport_config_rx_dma(struct sport_device *sport, void *buf,
+ int fragcount, size_t fragsize_bytes);
+
+int sport_config_tx_dma(struct sport_device *sport, void *buf,
+ int fragcount, size_t fragsize_bytes);
+
+int sport_tx_start(struct sport_device *sport);
+int sport_tx_stop(struct sport_device *sport);
+int sport_rx_start(struct sport_device *sport);
+int sport_rx_stop(struct sport_device *sport);
+
+/* for use in interrupt handler */
+unsigned long sport_curr_offset_rx(struct sport_device *sport);
+unsigned long sport_curr_offset_tx(struct sport_device *sport);
+
+void sport_incfrag(struct sport_device *sport, int *frag, int tx);
+void sport_decfrag(struct sport_device *sport, int *frag, int tx);
+
+int sport_set_rx_callback(struct sport_device *sport,
+ void (*rx_callback)(void *), void *rx_data);
+int sport_set_tx_callback(struct sport_device *sport,
+ void (*tx_callback)(void *), void *tx_data);
+int sport_set_err_callback(struct sport_device *sport,
+ void (*err_callback)(void *), void *err_data);
+
+int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \
+ u8 *in_data, int len);
+#endif /* BF53X_SPORT_H */
diff --git a/sound/soc/blackfin/bf5xx-ssm2602.c b/sound/soc/blackfin/bf5xx-ssm2602.c
new file mode 100644
index 00000000..767e772a
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ssm2602.c
@@ -0,0 +1,165 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-ssm2602.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: board driver for SSM2602 sound chip
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <asm/dma.h>
+#include <asm/portmux.h>
+#include <linux/gpio.h>
+#include "../codecs/ssm2602.h"
+#include "bf5xx-sport.h"
+#include "bf5xx-i2s-pcm.h"
+
+static struct snd_soc_card bf5xx_ssm2602;
+
+static int bf5xx_ssm2602_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ pr_debug("%s rate %d format %x\n", __func__, params_rate(params),
+ params_format(params));
+ /*
+ * If you are using a crystal source which frequency is not 12MHz
+ * then modify the below case statement with frequency of the crystal.
+ *
+ * If you are using the SPORT to generate clocking then this is
+ * where to do it.
+ */
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 12000000;
+ break;
+ }
+
+ /*
+ * CODEC is master for BCLK and LRC in this configuration.
+ */
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, SSM2602_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops bf5xx_ssm2602_ops = {
+ .hw_params = bf5xx_ssm2602_hw_params,
+};
+
+static struct snd_soc_dai_link bf5xx_ssm2602_dai[] = {
+ {
+ .name = "ssm2602",
+ .stream_name = "SSM2602",
+ .cpu_dai_name = "bfin-i2s.0",
+ .codec_dai_name = "ssm2602-hifi",
+ .platform_name = "bfin-i2s-pcm-audio",
+ .codec_name = "ssm2602.0-001b",
+ .ops = &bf5xx_ssm2602_ops,
+ },
+ {
+ .name = "ssm2602",
+ .stream_name = "SSM2602",
+ .cpu_dai_name = "bfin-i2s.1",
+ .codec_dai_name = "ssm2602-hifi",
+ .platform_name = "bfin-i2s-pcm-audio",
+ .codec_name = "ssm2602.0-001b",
+ .ops = &bf5xx_ssm2602_ops,
+ },
+};
+
+static struct snd_soc_card bf5xx_ssm2602 = {
+ .name = "bfin-ssm2602",
+ .dai_link = &bf5xx_ssm2602_dai[CONFIG_SND_BF5XX_SPORT_NUM],
+ .num_links = 1,
+};
+
+static struct platform_device *bf5xx_ssm2602_snd_device;
+
+static int __init bf5xx_ssm2602_init(void)
+{
+ int ret;
+
+ pr_debug("%s enter\n", __func__);
+ bf5xx_ssm2602_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!bf5xx_ssm2602_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(bf5xx_ssm2602_snd_device, &bf5xx_ssm2602);
+ ret = platform_device_add(bf5xx_ssm2602_snd_device);
+
+ if (ret)
+ platform_device_put(bf5xx_ssm2602_snd_device);
+
+ return ret;
+}
+
+static void __exit bf5xx_ssm2602_exit(void)
+{
+ pr_debug("%s enter\n", __func__);
+ platform_device_unregister(bf5xx_ssm2602_snd_device);
+}
+
+module_init(bf5xx_ssm2602_init);
+module_exit(bf5xx_ssm2602_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ALSA SoC SSM2602 BF527-EZKIT");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.c b/sound/soc/blackfin/bf5xx-tdm-pcm.c
new file mode 100644
index 00000000..07cfc7a9
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-tdm-pcm.c
@@ -0,0 +1,354 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-tdm-pcm.c
+ * Author: Barry Song <Barry.Song@analog.com>
+ *
+ * Created: Tue June 06 2009
+ * Description: DMA driver for tdm codec
+ *
+ * Modified:
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/gfp.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+
+#include "bf5xx-tdm-pcm.h"
+#include "bf5xx-tdm.h"
+#include "bf5xx-sport.h"
+
+#define PCM_BUFFER_MAX 0x8000
+#define FRAGMENT_SIZE_MIN (4*1024)
+#define FRAGMENTS_MIN 2
+#define FRAGMENTS_MAX 32
+
+static void bf5xx_dma_irq(void *data)
+{
+ struct snd_pcm_substream *pcm = data;
+ snd_pcm_period_elapsed(pcm);
+}
+
+static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_RESUME),
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .channels_min = 2,
+ .channels_max = 8,
+ .buffer_bytes_max = PCM_BUFFER_MAX,
+ .period_bytes_min = FRAGMENT_SIZE_MIN,
+ .period_bytes_max = PCM_BUFFER_MAX/2,
+ .periods_min = FRAGMENTS_MIN,
+ .periods_max = FRAGMENTS_MAX,
+};
+
+static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
+ snd_pcm_lib_malloc_pages(substream, size * 4);
+
+ return 0;
+}
+
+static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_lib_free_pages(substream);
+
+ return 0;
+}
+
+static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size);
+
+ fragsize_bytes /= runtime->channels;
+ /* inflate the fragsize to match the dma width of SPORT */
+ fragsize_bytes *= 8;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_tx_dma(sport, runtime->dma_area,
+ runtime->periods, fragsize_bytes);
+ } else {
+ sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_rx_dma(sport, runtime->dma_area,
+ runtime->periods, fragsize_bytes);
+ }
+
+ return 0;
+}
+
+static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_tx_start(sport);
+ else
+ sport_rx_start(sport);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_tx_stop(sport);
+ else
+ sport_rx_stop(sport);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ unsigned int diff;
+ snd_pcm_uframes_t frames;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ diff = sport_curr_offset_tx(sport);
+ frames = diff / (8*4); /* 32 bytes per frame */
+ } else {
+ diff = sport_curr_offset_rx(sport);
+ frames = diff / (8*4);
+ }
+ return frames;
+}
+
+static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ int ret = 0;
+
+ snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ if (sport_handle != NULL) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_handle->tx_buf = buf->area;
+ else
+ sport_handle->rx_buf = buf->area;
+
+ runtime->private_data = sport_handle;
+ } else {
+ pr_err("sport_handle is NULL\n");
+ ret = -ENODEV;
+ }
+out:
+ return ret;
+}
+
+static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
+ snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ struct bf5xx_tdm_port *tdm_port = sport->private_data;
+ unsigned int *src;
+ unsigned int *dst;
+ int i;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ src = buf;
+ dst = (unsigned int *)substream->runtime->dma_area;
+
+ dst += pos * 8;
+ while (count--) {
+ for (i = 0; i < substream->runtime->channels; i++)
+ *(dst + tdm_port->tx_map[i]) = *src++;
+ dst += 8;
+ }
+ } else {
+ src = (unsigned int *)substream->runtime->dma_area;
+ dst = buf;
+
+ src += pos * 8;
+ while (count--) {
+ for (i = 0; i < substream->runtime->channels; i++)
+ *dst++ = *(src + tdm_port->rx_map[i]);
+ src += 8;
+ }
+ }
+
+ return 0;
+}
+
+static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
+ int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
+{
+ unsigned char *buf = substream->runtime->dma_area;
+ buf += pos * 8 * 4;
+ memset(buf, '\0', count * 8 * 4);
+
+ return 0;
+}
+
+
+struct snd_pcm_ops bf5xx_pcm_tdm_ops = {
+ .open = bf5xx_pcm_open,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = bf5xx_pcm_hw_params,
+ .hw_free = bf5xx_pcm_hw_free,
+ .prepare = bf5xx_pcm_prepare,
+ .trigger = bf5xx_pcm_trigger,
+ .pointer = bf5xx_pcm_pointer,
+ .copy = bf5xx_pcm_copy,
+ .silence = bf5xx_pcm_silence,
+};
+
+static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size * 4,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area) {
+ pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
+ return -ENOMEM;
+ }
+ buf->bytes = size;
+
+ return 0;
+}
+
+static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+ dma_free_coherent(NULL, buf->bytes, buf->area, 0);
+ buf->area = NULL;
+ }
+}
+
+static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
+
+static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &bf5xx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ if (dai->driver->playback.channels_min) {
+ ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+out:
+ return ret;
+}
+
+static struct snd_soc_platform_driver bf5xx_tdm_soc_platform = {
+ .ops = &bf5xx_pcm_tdm_ops,
+ .pcm_new = bf5xx_pcm_tdm_new,
+ .pcm_free = bf5xx_pcm_free_dma_buffers,
+};
+
+static int __devinit bf5xx_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &bf5xx_tdm_soc_platform);
+}
+
+static int __devexit bf5xx_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver bfin_tdm_driver = {
+ .driver = {
+ .name = "bfin-tdm-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = bf5xx_soc_platform_probe,
+ .remove = __devexit_p(bf5xx_soc_platform_remove),
+};
+
+static int __init snd_bfin_tdm_init(void)
+{
+ return platform_driver_register(&bfin_tdm_driver);
+}
+module_init(snd_bfin_tdm_init);
+
+static void __exit snd_bfin_tdm_exit(void)
+{
+ platform_driver_unregister(&bfin_tdm_driver);
+}
+module_exit(snd_bfin_tdm_exit);
+
+MODULE_AUTHOR("Barry Song");
+MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.h b/sound/soc/blackfin/bf5xx-tdm-pcm.h
new file mode 100644
index 00000000..7f8cc01c
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-tdm-pcm.h
@@ -0,0 +1,18 @@
+/*
+ * sound/soc/blackfin/bf5xx-tdm-pcm.h -- ALSA PCM interface for the Blackfin
+ *
+ * Copyright 2009 Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_TDM_PCM_H
+#define _BF5XX_TDM_PCM_H
+
+struct bf5xx_pcm_dma_params {
+ char *name; /* stream identifier */
+};
+
+#endif
diff --git a/sound/soc/blackfin/bf5xx-tdm.c b/sound/soc/blackfin/bf5xx-tdm.c
new file mode 100644
index 00000000..a822d1ee
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-tdm.c
@@ -0,0 +1,333 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-tdm.c
+ * Author: Barry Song <Barry.Song@analog.com>
+ *
+ * Created: Thurs June 04 2009
+ * Description: Blackfin I2S(TDM) CPU DAI driver
+ * Even though TDM mode can be as part of I2S DAI, but there
+ * are so much difference in configuration and data flow,
+ * it's very ugly to integrate I2S and TDM into a module
+ *
+ * Modified:
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/irq.h>
+#include <asm/portmux.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+
+#include "bf5xx-sport.h"
+#include "bf5xx-tdm.h"
+
+static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ int ret = 0;
+
+ /* interface format:support TDM,slave mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ break;
+ default:
+ printk(KERN_ERR "%s: Unknown DAI format type\n", __func__);
+ ret = -EINVAL;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ ret = -EINVAL;
+ break;
+ default:
+ printk(KERN_ERR "%s: Unknown DAI master type\n", __func__);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
+ struct bf5xx_tdm_port *bf5xx_tdm = sport_handle->private_data;
+ int ret = 0;
+
+ bf5xx_tdm->tcr2 &= ~0x1f;
+ bf5xx_tdm->rcr2 &= ~0x1f;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bf5xx_tdm->tcr2 |= 31;
+ bf5xx_tdm->rcr2 |= 31;
+ sport_handle->wdsize = 4;
+ break;
+ /* at present, we only support 32bit transfer */
+ default:
+ pr_err("not supported PCM format yet\n");
+ return -EINVAL;
+ break;
+ }
+
+ if (!bf5xx_tdm->configured) {
+ /*
+ * TX and RX are not independent,they are enabled at the
+ * same time, even if only one side is running. So, we
+ * need to configure both of them at the time when the first
+ * stream is opened.
+ *
+ * CPU DAI:slave mode.
+ */
+ ret = sport_config_rx(sport_handle, bf5xx_tdm->rcr1,
+ bf5xx_tdm->rcr2, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ ret = sport_config_tx(sport_handle, bf5xx_tdm->tcr1,
+ bf5xx_tdm->tcr2, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ bf5xx_tdm->configured = 1;
+ }
+
+ return 0;
+}
+
+static void bf5xx_tdm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
+ struct bf5xx_tdm_port *bf5xx_tdm = sport_handle->private_data;
+
+ /* No active stream, SPORT is allowed to be configured again. */
+ if (!dai->active)
+ bf5xx_tdm->configured = 0;
+}
+
+static int bf5xx_tdm_set_channel_map(struct snd_soc_dai *dai,
+ unsigned int tx_num, unsigned int *tx_slot,
+ unsigned int rx_num, unsigned int *rx_slot)
+{
+ struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
+ struct bf5xx_tdm_port *bf5xx_tdm = sport_handle->private_data;
+ int i;
+ unsigned int slot;
+ unsigned int tx_mapped = 0, rx_mapped = 0;
+
+ if ((tx_num > BFIN_TDM_DAI_MAX_SLOTS) ||
+ (rx_num > BFIN_TDM_DAI_MAX_SLOTS))
+ return -EINVAL;
+
+ for (i = 0; i < tx_num; i++) {
+ slot = tx_slot[i];
+ if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
+ (!(tx_mapped & (1 << slot)))) {
+ bf5xx_tdm->tx_map[i] = slot;
+ tx_mapped |= 1 << slot;
+ } else
+ return -EINVAL;
+ }
+ for (i = 0; i < rx_num; i++) {
+ slot = rx_slot[i];
+ if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
+ (!(rx_mapped & (1 << slot)))) {
+ bf5xx_tdm->rx_map[i] = slot;
+ rx_mapped |= 1 << slot;
+ } else
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int bf5xx_tdm_suspend(struct snd_soc_dai *dai)
+{
+ struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
+
+ if (dai->playback_active)
+ sport_tx_stop(sport);
+ if (dai->capture_active)
+ sport_rx_stop(sport);
+
+ /* isolate sync/clock pins from codec while sports resume */
+ peripheral_free_list(sport->pin_req);
+
+ return 0;
+}
+
+static int bf5xx_tdm_resume(struct snd_soc_dai *dai)
+{
+ int ret;
+ struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
+
+ ret = sport_set_multichannel(sport, 8, 0xFF, 1);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ ret = -EBUSY;
+ }
+
+ ret = sport_config_rx(sport, 0, 0x1F, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ ret = -EBUSY;
+ }
+
+ ret = sport_config_tx(sport, 0, 0x1F, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ ret = -EBUSY;
+ }
+
+ peripheral_request_list(sport->pin_req, "soc-audio");
+
+ return 0;
+}
+
+#else
+#define bf5xx_tdm_suspend NULL
+#define bf5xx_tdm_resume NULL
+#endif
+
+static struct snd_soc_dai_ops bf5xx_tdm_dai_ops = {
+ .hw_params = bf5xx_tdm_hw_params,
+ .set_fmt = bf5xx_tdm_set_dai_fmt,
+ .shutdown = bf5xx_tdm_shutdown,
+ .set_channel_map = bf5xx_tdm_set_channel_map,
+};
+
+static struct snd_soc_dai_driver bf5xx_tdm_dai = {
+ .suspend = bf5xx_tdm_suspend,
+ .resume = bf5xx_tdm_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,},
+ .ops = &bf5xx_tdm_dai_ops,
+};
+
+static int __devinit bfin_tdm_probe(struct platform_device *pdev)
+{
+ struct sport_device *sport_handle;
+ int ret;
+
+ /* configure SPORT for TDM */
+ sport_handle = sport_init(pdev, 4, 8 * sizeof(u32),
+ sizeof(struct bf5xx_tdm_port));
+ if (!sport_handle)
+ return -ENODEV;
+
+ /* SPORT works in TDM mode */
+ ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ ret = -EBUSY;
+ goto sport_config_err;
+ }
+
+ ret = sport_config_rx(sport_handle, 0, 0x1F, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ ret = -EBUSY;
+ goto sport_config_err;
+ }
+
+ ret = sport_config_tx(sport_handle, 0, 0x1F, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ ret = -EBUSY;
+ goto sport_config_err;
+ }
+
+ ret = snd_soc_register_dai(&pdev->dev, &bf5xx_tdm_dai);
+ if (ret) {
+ pr_err("Failed to register DAI: %d\n", ret);
+ goto sport_config_err;
+ }
+
+ return 0;
+
+sport_config_err:
+ sport_done(sport_handle);
+ return ret;
+}
+
+static int __devexit bfin_tdm_remove(struct platform_device *pdev)
+{
+ struct sport_device *sport_handle = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_dai(&pdev->dev);
+ sport_done(sport_handle);
+
+ return 0;
+}
+
+static struct platform_driver bfin_tdm_driver = {
+ .probe = bfin_tdm_probe,
+ .remove = __devexit_p(bfin_tdm_remove),
+ .driver = {
+ .name = "bfin-tdm",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init bfin_tdm_init(void)
+{
+ return platform_driver_register(&bfin_tdm_driver);
+}
+module_init(bfin_tdm_init);
+
+static void __exit bfin_tdm_exit(void)
+{
+ platform_driver_unregister(&bfin_tdm_driver);
+}
+module_exit(bfin_tdm_exit);
+
+/* Module information */
+MODULE_AUTHOR("Barry Song");
+MODULE_DESCRIPTION("TDM driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/blackfin/bf5xx-tdm.h b/sound/soc/blackfin/bf5xx-tdm.h
new file mode 100644
index 00000000..e986a3ea
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-tdm.h
@@ -0,0 +1,23 @@
+/*
+ * sound/soc/blackfin/bf5xx-tdm.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_TDM_H
+#define _BF5XX_TDM_H
+
+#define BFIN_TDM_DAI_MAX_SLOTS 8
+struct bf5xx_tdm_port {
+ u16 tcr1;
+ u16 rcr1;
+ u16 tcr2;
+ u16 rcr2;
+ unsigned int tx_map[BFIN_TDM_DAI_MAX_SLOTS];
+ unsigned int rx_map[BFIN_TDM_DAI_MAX_SLOTS];
+ int configured;
+};
+
+#endif
diff --git a/sound/soc/codecs/88pm860x-codec.c b/sound/soc/codecs/88pm860x-codec.c
new file mode 100644
index 00000000..19241576
--- /dev/null
+++ b/sound/soc/codecs/88pm860x-codec.c
@@ -0,0 +1,1490 @@
+/*
+ * 88pm860x-codec.c -- 88PM860x ALSA SoC Audio Driver
+ *
+ * Copyright 2010 Marvell International Ltd.
+ * Author: Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/88pm860x.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <sound/jack.h>
+#include <trace/events/asoc.h>
+
+#include "88pm860x-codec.h"
+
+#define MAX_NAME_LEN 20
+#define REG_CACHE_SIZE 0x40
+#define REG_CACHE_BASE 0xb0
+
+/* Status Register 1 (0x01) */
+#define REG_STATUS_1 0x01
+#define MIC_STATUS (1 << 7)
+#define HOOK_STATUS (1 << 6)
+#define HEADSET_STATUS (1 << 5)
+
+/* Mic Detection Register (0x37) */
+#define REG_MIC_DET 0x37
+#define CONTINUOUS_POLLING (3 << 1)
+#define EN_MIC_DET (1 << 0)
+#define MICDET_MASK 0x07
+
+/* Headset Detection Register (0x38) */
+#define REG_HS_DET 0x38
+#define EN_HS_DET (1 << 0)
+
+/* Misc2 Register (0x42) */
+#define REG_MISC2 0x42
+#define AUDIO_PLL (1 << 5)
+#define AUDIO_SECTION_RESET (1 << 4)
+#define AUDIO_SECTION_ON (1 << 3)
+
+/* PCM Interface Register 2 (0xb1) */
+#define PCM_INF2_BCLK (1 << 6) /* Bit clock polarity */
+#define PCM_INF2_FS (1 << 5) /* Frame Sync polarity */
+#define PCM_INF2_MASTER (1 << 4) /* Master / Slave */
+#define PCM_INF2_18WL (1 << 3) /* 18 / 16 bits */
+#define PCM_GENERAL_I2S 0
+#define PCM_EXACT_I2S 1
+#define PCM_LEFT_I2S 2
+#define PCM_RIGHT_I2S 3
+#define PCM_SHORT_FS 4
+#define PCM_LONG_FS 5
+#define PCM_MODE_MASK 7
+
+/* I2S Interface Register 4 (0xbe) */
+#define I2S_EQU_BYP (1 << 6)
+
+/* DAC Offset Register (0xcb) */
+#define DAC_MUTE (1 << 7)
+#define MUTE_LEFT (1 << 6)
+#define MUTE_RIGHT (1 << 2)
+
+/* ADC Analog Register 1 (0xd0) */
+#define REG_ADC_ANA_1 0xd0
+#define MIC1BIAS_MASK 0x60
+
+/* Earpiece/Speaker Control Register 2 (0xda) */
+#define REG_EAR2 0xda
+#define RSYNC_CHANGE (1 << 2)
+
+/* Audio Supplies Register 2 (0xdc) */
+#define REG_SUPPLIES2 0xdc
+#define LDO15_READY (1 << 4)
+#define LDO15_EN (1 << 3)
+#define CPUMP_READY (1 << 2)
+#define CPUMP_EN (1 << 1)
+#define AUDIO_EN (1 << 0)
+#define SUPPLY_MASK (LDO15_EN | CPUMP_EN | AUDIO_EN)
+
+/* Audio Enable Register 1 (0xdd) */
+#define ADC_MOD_RIGHT (1 << 1)
+#define ADC_MOD_LEFT (1 << 0)
+
+/* Audio Enable Register 2 (0xde) */
+#define ADC_LEFT (1 << 5)
+#define ADC_RIGHT (1 << 4)
+
+/* DAC Enable Register 2 (0xe1) */
+#define DAC_LEFT (1 << 5)
+#define DAC_RIGHT (1 << 4)
+#define MODULATOR (1 << 3)
+
+/* Shorts Register (0xeb) */
+#define REG_SHORTS 0xeb
+#define CLR_SHORT_LO2 (1 << 7)
+#define SHORT_LO2 (1 << 6)
+#define CLR_SHORT_LO1 (1 << 5)
+#define SHORT_LO1 (1 << 4)
+#define CLR_SHORT_HS2 (1 << 3)
+#define SHORT_HS2 (1 << 2)
+#define CLR_SHORT_HS1 (1 << 1)
+#define SHORT_HS1 (1 << 0)
+
+/*
+ * This widget should be just after DAC & PGA in DAPM power-on sequence and
+ * before DAC & PGA in DAPM power-off sequence.
+ */
+#define PM860X_DAPM_OUTPUT(wname, wevent) \
+{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, \
+ .shift = 0, .invert = 0, .kcontrol_news = NULL, \
+ .num_kcontrols = 0, .event = wevent, \
+ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD, }
+
+struct pm860x_det {
+ struct snd_soc_jack *hp_jack;
+ struct snd_soc_jack *mic_jack;
+ int hp_det;
+ int mic_det;
+ int hook_det;
+ int hs_shrt;
+ int lo_shrt;
+};
+
+struct pm860x_priv {
+ unsigned int sysclk;
+ unsigned int pcmclk;
+ unsigned int dir;
+ unsigned int filter;
+ struct snd_soc_codec *codec;
+ struct i2c_client *i2c;
+ struct pm860x_chip *chip;
+ struct pm860x_det det;
+
+ int irq[4];
+ unsigned char name[4][MAX_NAME_LEN];
+};
+
+/* -9450dB to 0dB in 150dB steps ( mute instead of -9450dB) */
+static const DECLARE_TLV_DB_SCALE(dpga_tlv, -9450, 150, 1);
+
+/* -9dB to 0db in 3dB steps */
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -900, 300, 0);
+
+/* {-23, -17, -13.5, -11, -9, -6, -3, 0}dB */
+static const unsigned int mic_tlv[] = {
+ TLV_DB_RANGE_HEAD(5),
+ 0, 0, TLV_DB_SCALE_ITEM(-2300, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(-1700, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(-1350, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(-1100, 0, 0),
+ 4, 7, TLV_DB_SCALE_ITEM(-900, 300, 0),
+};
+
+/* {0, 0, 0, -6, 0, 6, 12, 18}dB */
+static const unsigned int aux_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 2, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 3, 7, TLV_DB_SCALE_ITEM(-600, 600, 0),
+};
+
+/* {-16, -13, -10, -7, -5.2, -3,3, -2.2, 0}dB, mute instead of -16dB */
+static const unsigned int out_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 3, TLV_DB_SCALE_ITEM(-1600, 300, 1),
+ 4, 4, TLV_DB_SCALE_ITEM(-520, 0, 0),
+ 5, 5, TLV_DB_SCALE_ITEM(-330, 0, 0),
+ 6, 7, TLV_DB_SCALE_ITEM(-220, 220, 0),
+};
+
+static const unsigned int st_tlv[] = {
+ TLV_DB_RANGE_HEAD(8),
+ 0, 1, TLV_DB_SCALE_ITEM(-12041, 602, 0),
+ 2, 3, TLV_DB_SCALE_ITEM(-11087, 250, 0),
+ 4, 5, TLV_DB_SCALE_ITEM(-10643, 158, 0),
+ 6, 7, TLV_DB_SCALE_ITEM(-10351, 116, 0),
+ 8, 9, TLV_DB_SCALE_ITEM(-10133, 92, 0),
+ 10, 13, TLV_DB_SCALE_ITEM(-9958, 70, 0),
+ 14, 17, TLV_DB_SCALE_ITEM(-9689, 53, 0),
+ 18, 271, TLV_DB_SCALE_ITEM(-9484, 37, 0),
+};
+
+/* Sidetone Gain = M * 2^(-5-N) */
+struct st_gain {
+ unsigned int db;
+ unsigned int m;
+ unsigned int n;
+};
+
+static struct st_gain st_table[] = {
+ {-12041, 1, 15}, {-11439, 1, 14}, {-11087, 3, 15}, {-10837, 1, 13},
+ {-10643, 5, 15}, {-10485, 3, 14}, {-10351, 7, 15}, {-10235, 1, 12},
+ {-10133, 9, 15}, {-10041, 5, 14}, { -9958, 11, 15}, { -9883, 3, 13},
+ { -9813, 13, 15}, { -9749, 7, 14}, { -9689, 15, 15}, { -9633, 1, 11},
+ { -9580, 17, 15}, { -9531, 9, 14}, { -9484, 19, 15}, { -9439, 5, 13},
+ { -9397, 21, 15}, { -9356, 11, 14}, { -9318, 23, 15}, { -9281, 3, 12},
+ { -9245, 25, 15}, { -9211, 13, 14}, { -9178, 27, 15}, { -9147, 7, 13},
+ { -9116, 29, 15}, { -9087, 15, 14}, { -9058, 31, 15}, { -9031, 1, 10},
+ { -8978, 17, 14}, { -8929, 9, 13}, { -8882, 19, 14}, { -8837, 5, 12},
+ { -8795, 21, 14}, { -8754, 11, 13}, { -8716, 23, 14}, { -8679, 3, 11},
+ { -8643, 25, 14}, { -8609, 13, 13}, { -8576, 27, 14}, { -8545, 7, 12},
+ { -8514, 29, 14}, { -8485, 15, 13}, { -8456, 31, 14}, { -8429, 1, 9},
+ { -8376, 17, 13}, { -8327, 9, 12}, { -8280, 19, 13}, { -8235, 5, 11},
+ { -8193, 21, 13}, { -8152, 11, 12}, { -8114, 23, 13}, { -8077, 3, 10},
+ { -8041, 25, 13}, { -8007, 13, 12}, { -7974, 27, 13}, { -7943, 7, 11},
+ { -7912, 29, 13}, { -7883, 15, 12}, { -7854, 31, 13}, { -7827, 1, 8},
+ { -7774, 17, 12}, { -7724, 9, 11}, { -7678, 19, 12}, { -7633, 5, 10},
+ { -7591, 21, 12}, { -7550, 11, 11}, { -7512, 23, 12}, { -7475, 3, 9},
+ { -7439, 25, 12}, { -7405, 13, 11}, { -7372, 27, 12}, { -7341, 7, 10},
+ { -7310, 29, 12}, { -7281, 15, 11}, { -7252, 31, 12}, { -7225, 1, 7},
+ { -7172, 17, 11}, { -7122, 9, 10}, { -7075, 19, 11}, { -7031, 5, 9},
+ { -6989, 21, 11}, { -6948, 11, 10}, { -6910, 23, 11}, { -6873, 3, 8},
+ { -6837, 25, 11}, { -6803, 13, 10}, { -6770, 27, 11}, { -6739, 7, 9},
+ { -6708, 29, 11}, { -6679, 15, 10}, { -6650, 31, 11}, { -6623, 1, 6},
+ { -6570, 17, 10}, { -6520, 9, 9}, { -6473, 19, 10}, { -6429, 5, 8},
+ { -6386, 21, 10}, { -6346, 11, 9}, { -6307, 23, 10}, { -6270, 3, 7},
+ { -6235, 25, 10}, { -6201, 13, 9}, { -6168, 27, 10}, { -6137, 7, 8},
+ { -6106, 29, 10}, { -6077, 15, 9}, { -6048, 31, 10}, { -6021, 1, 5},
+ { -5968, 17, 9}, { -5918, 9, 8}, { -5871, 19, 9}, { -5827, 5, 7},
+ { -5784, 21, 9}, { -5744, 11, 8}, { -5705, 23, 9}, { -5668, 3, 6},
+ { -5633, 25, 9}, { -5599, 13, 8}, { -5566, 27, 9}, { -5535, 7, 7},
+ { -5504, 29, 9}, { -5475, 15, 8}, { -5446, 31, 9}, { -5419, 1, 4},
+ { -5366, 17, 8}, { -5316, 9, 7}, { -5269, 19, 8}, { -5225, 5, 6},
+ { -5182, 21, 8}, { -5142, 11, 7}, { -5103, 23, 8}, { -5066, 3, 5},
+ { -5031, 25, 8}, { -4997, 13, 7}, { -4964, 27, 8}, { -4932, 7, 6},
+ { -4902, 29, 8}, { -4873, 15, 7}, { -4844, 31, 8}, { -4816, 1, 3},
+ { -4764, 17, 7}, { -4714, 9, 6}, { -4667, 19, 7}, { -4623, 5, 5},
+ { -4580, 21, 7}, { -4540, 11, 6}, { -4501, 23, 7}, { -4464, 3, 4},
+ { -4429, 25, 7}, { -4395, 13, 6}, { -4362, 27, 7}, { -4330, 7, 5},
+ { -4300, 29, 7}, { -4270, 15, 6}, { -4242, 31, 7}, { -4214, 1, 2},
+ { -4162, 17, 6}, { -4112, 9, 5}, { -4065, 19, 6}, { -4021, 5, 4},
+ { -3978, 21, 6}, { -3938, 11, 5}, { -3899, 23, 6}, { -3862, 3, 3},
+ { -3827, 25, 6}, { -3793, 13, 5}, { -3760, 27, 6}, { -3728, 7, 4},
+ { -3698, 29, 6}, { -3668, 15, 5}, { -3640, 31, 6}, { -3612, 1, 1},
+ { -3560, 17, 5}, { -3510, 9, 4}, { -3463, 19, 5}, { -3419, 5, 3},
+ { -3376, 21, 5}, { -3336, 11, 4}, { -3297, 23, 5}, { -3260, 3, 2},
+ { -3225, 25, 5}, { -3191, 13, 4}, { -3158, 27, 5}, { -3126, 7, 3},
+ { -3096, 29, 5}, { -3066, 15, 4}, { -3038, 31, 5}, { -3010, 1, 0},
+ { -2958, 17, 4}, { -2908, 9, 3}, { -2861, 19, 4}, { -2816, 5, 2},
+ { -2774, 21, 4}, { -2734, 11, 3}, { -2695, 23, 4}, { -2658, 3, 1},
+ { -2623, 25, 4}, { -2589, 13, 3}, { -2556, 27, 4}, { -2524, 7, 2},
+ { -2494, 29, 4}, { -2464, 15, 3}, { -2436, 31, 4}, { -2408, 2, 0},
+ { -2356, 17, 3}, { -2306, 9, 2}, { -2259, 19, 3}, { -2214, 5, 1},
+ { -2172, 21, 3}, { -2132, 11, 2}, { -2093, 23, 3}, { -2056, 3, 0},
+ { -2021, 25, 3}, { -1987, 13, 2}, { -1954, 27, 3}, { -1922, 7, 1},
+ { -1892, 29, 3}, { -1862, 15, 2}, { -1834, 31, 3}, { -1806, 4, 0},
+ { -1754, 17, 2}, { -1704, 9, 1}, { -1657, 19, 2}, { -1612, 5, 0},
+ { -1570, 21, 2}, { -1530, 11, 1}, { -1491, 23, 2}, { -1454, 6, 0},
+ { -1419, 25, 2}, { -1384, 13, 1}, { -1352, 27, 2}, { -1320, 7, 0},
+ { -1290, 29, 2}, { -1260, 15, 1}, { -1232, 31, 2}, { -1204, 8, 0},
+ { -1151, 17, 1}, { -1102, 9, 0}, { -1055, 19, 1}, { -1010, 10, 0},
+ { -968, 21, 1}, { -928, 11, 0}, { -889, 23, 1}, { -852, 12, 0},
+ { -816, 25, 1}, { -782, 13, 0}, { -750, 27, 1}, { -718, 14, 0},
+ { -688, 29, 1}, { -658, 15, 0}, { -630, 31, 1}, { -602, 16, 0},
+ { -549, 17, 0}, { -500, 18, 0}, { -453, 19, 0}, { -408, 20, 0},
+ { -366, 21, 0}, { -325, 22, 0}, { -287, 23, 0}, { -250, 24, 0},
+ { -214, 25, 0}, { -180, 26, 0}, { -148, 27, 0}, { -116, 28, 0},
+ { -86, 29, 0}, { -56, 30, 0}, { -28, 31, 0}, { 0, 0, 0},
+};
+
+static int pm860x_volatile(unsigned int reg)
+{
+ BUG_ON(reg >= REG_CACHE_SIZE);
+
+ switch (reg) {
+ case PM860X_AUDIO_SUPPLIES_2:
+ return 1;
+ }
+
+ return 0;
+}
+
+static unsigned int pm860x_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned char *cache = codec->reg_cache;
+
+ BUG_ON(reg >= REG_CACHE_SIZE);
+
+ if (pm860x_volatile(reg))
+ return cache[reg];
+
+ reg += REG_CACHE_BASE;
+
+ return pm860x_reg_read(codec->control_data, reg);
+}
+
+static int pm860x_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ unsigned char *cache = codec->reg_cache;
+
+ BUG_ON(reg >= REG_CACHE_SIZE);
+
+ if (!pm860x_volatile(reg))
+ cache[reg] = (unsigned char)value;
+
+ reg += REG_CACHE_BASE;
+
+ return pm860x_reg_write(codec->control_data, reg, value);
+}
+
+static int snd_soc_get_volsw_2r_st(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ int val[2], val2[2], i;
+
+ val[0] = snd_soc_read(codec, reg) & 0x3f;
+ val[1] = (snd_soc_read(codec, PM860X_SIDETONE_SHIFT) >> 4) & 0xf;
+ val2[0] = snd_soc_read(codec, reg2) & 0x3f;
+ val2[1] = (snd_soc_read(codec, PM860X_SIDETONE_SHIFT)) & 0xf;
+
+ for (i = 0; i < ARRAY_SIZE(st_table); i++) {
+ if ((st_table[i].m == val[0]) && (st_table[i].n == val[1]))
+ ucontrol->value.integer.value[0] = i;
+ if ((st_table[i].m == val2[0]) && (st_table[i].n == val2[1]))
+ ucontrol->value.integer.value[1] = i;
+ }
+ return 0;
+}
+
+static int snd_soc_put_volsw_2r_st(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ int err;
+ unsigned int val, val2;
+
+ val = ucontrol->value.integer.value[0];
+ val2 = ucontrol->value.integer.value[1];
+
+ err = snd_soc_update_bits(codec, reg, 0x3f, st_table[val].m);
+ if (err < 0)
+ return err;
+ err = snd_soc_update_bits(codec, PM860X_SIDETONE_SHIFT, 0xf0,
+ st_table[val].n << 4);
+ if (err < 0)
+ return err;
+
+ err = snd_soc_update_bits(codec, reg2, 0x3f, st_table[val2].m);
+ if (err < 0)
+ return err;
+ err = snd_soc_update_bits(codec, PM860X_SIDETONE_SHIFT, 0x0f,
+ st_table[val2].n);
+ return err;
+}
+
+static int snd_soc_get_volsw_2r_out(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ unsigned int shift = mc->shift;
+ int max = mc->max, val, val2;
+ unsigned int mask = (1 << fls(max)) - 1;
+
+ val = snd_soc_read(codec, reg) >> shift;
+ val2 = snd_soc_read(codec, reg2) >> shift;
+ ucontrol->value.integer.value[0] = (max - val) & mask;
+ ucontrol->value.integer.value[1] = (max - val2) & mask;
+
+ return 0;
+}
+
+static int snd_soc_put_volsw_2r_out(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ int err;
+ unsigned int val, val2, val_mask;
+
+ val_mask = mask << shift;
+ val = ((max - ucontrol->value.integer.value[0]) & mask);
+ val2 = ((max - ucontrol->value.integer.value[1]) & mask);
+
+ val = val << shift;
+ val2 = val2 << shift;
+
+ err = snd_soc_update_bits(codec, reg, val_mask, val);
+ if (err < 0)
+ return err;
+
+ err = snd_soc_update_bits(codec, reg2, val_mask, val2);
+ return err;
+}
+
+/* DAPM Widget Events */
+/*
+ * A lot registers are belong to RSYNC domain. It requires enabling RSYNC bit
+ * after updating these registers. Otherwise, these updated registers won't
+ * be effective.
+ */
+static int pm860x_rsync_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ /*
+ * In order to avoid current on the load, mute power-on and power-off
+ * should be transients.
+ * Unmute by DAC_MUTE. It should be unmuted when DAPM sequence is
+ * finished.
+ */
+ snd_soc_update_bits(codec, PM860X_DAC_OFFSET, DAC_MUTE, 0);
+ snd_soc_update_bits(codec, PM860X_EAR_CTRL_2,
+ RSYNC_CHANGE, RSYNC_CHANGE);
+ return 0;
+}
+
+static int pm860x_dac_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ unsigned int dac = 0;
+ int data;
+
+ if (!strcmp(w->name, "Left DAC"))
+ dac = DAC_LEFT;
+ if (!strcmp(w->name, "Right DAC"))
+ dac = DAC_RIGHT;
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (dac) {
+ /* Auto mute in power-on sequence. */
+ dac |= MODULATOR;
+ snd_soc_update_bits(codec, PM860X_DAC_OFFSET,
+ DAC_MUTE, DAC_MUTE);
+ snd_soc_update_bits(codec, PM860X_EAR_CTRL_2,
+ RSYNC_CHANGE, RSYNC_CHANGE);
+ /* update dac */
+ snd_soc_update_bits(codec, PM860X_DAC_EN_2,
+ dac, dac);
+ }
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ if (dac) {
+ /* Auto mute in power-off sequence. */
+ snd_soc_update_bits(codec, PM860X_DAC_OFFSET,
+ DAC_MUTE, DAC_MUTE);
+ snd_soc_update_bits(codec, PM860X_EAR_CTRL_2,
+ RSYNC_CHANGE, RSYNC_CHANGE);
+ /* update dac */
+ data = snd_soc_read(codec, PM860X_DAC_EN_2);
+ data &= ~dac;
+ if (!(data & (DAC_LEFT | DAC_RIGHT)))
+ data &= ~MODULATOR;
+ snd_soc_write(codec, PM860X_DAC_EN_2, data);
+ }
+ break;
+ }
+ return 0;
+}
+
+static const char *pm860x_opamp_texts[] = {"-50%", "-25%", "0%", "75%"};
+
+static const char *pm860x_pa_texts[] = {"-33%", "0%", "33%", "66%"};
+
+static const struct soc_enum pm860x_hs1_opamp_enum =
+ SOC_ENUM_SINGLE(PM860X_HS1_CTRL, 5, 4, pm860x_opamp_texts);
+
+static const struct soc_enum pm860x_hs2_opamp_enum =
+ SOC_ENUM_SINGLE(PM860X_HS2_CTRL, 5, 4, pm860x_opamp_texts);
+
+static const struct soc_enum pm860x_hs1_pa_enum =
+ SOC_ENUM_SINGLE(PM860X_HS1_CTRL, 3, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_hs2_pa_enum =
+ SOC_ENUM_SINGLE(PM860X_HS2_CTRL, 3, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_lo1_opamp_enum =
+ SOC_ENUM_SINGLE(PM860X_LO1_CTRL, 5, 4, pm860x_opamp_texts);
+
+static const struct soc_enum pm860x_lo2_opamp_enum =
+ SOC_ENUM_SINGLE(PM860X_LO2_CTRL, 5, 4, pm860x_opamp_texts);
+
+static const struct soc_enum pm860x_lo1_pa_enum =
+ SOC_ENUM_SINGLE(PM860X_LO1_CTRL, 3, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_lo2_pa_enum =
+ SOC_ENUM_SINGLE(PM860X_LO2_CTRL, 3, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_spk_pa_enum =
+ SOC_ENUM_SINGLE(PM860X_EAR_CTRL_1, 5, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_ear_pa_enum =
+ SOC_ENUM_SINGLE(PM860X_EAR_CTRL_2, 0, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_spk_ear_opamp_enum =
+ SOC_ENUM_SINGLE(PM860X_EAR_CTRL_1, 3, 4, pm860x_opamp_texts);
+
+static const struct snd_kcontrol_new pm860x_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("ADC Capture Volume", PM860X_ADC_ANA_2,
+ PM860X_ADC_ANA_3, 6, 3, 0, adc_tlv),
+ SOC_DOUBLE_TLV("AUX Capture Volume", PM860X_ADC_ANA_3, 0, 3, 7, 0,
+ aux_tlv),
+ SOC_SINGLE_TLV("MIC1 Capture Volume", PM860X_ADC_ANA_2, 0, 7, 0,
+ mic_tlv),
+ SOC_SINGLE_TLV("MIC3 Capture Volume", PM860X_ADC_ANA_2, 3, 7, 0,
+ mic_tlv),
+ SOC_DOUBLE_R_EXT_TLV("Sidetone Volume", PM860X_SIDETONE_L_GAIN,
+ PM860X_SIDETONE_R_GAIN, 0, ARRAY_SIZE(st_table)-1,
+ 0, snd_soc_get_volsw_2r_st,
+ snd_soc_put_volsw_2r_st, st_tlv),
+ SOC_SINGLE_TLV("Speaker Playback Volume", PM860X_EAR_CTRL_1,
+ 0, 7, 0, out_tlv),
+ SOC_DOUBLE_R_TLV("Line Playback Volume", PM860X_LO1_CTRL,
+ PM860X_LO2_CTRL, 0, 7, 0, out_tlv),
+ SOC_DOUBLE_R_TLV("Headset Playback Volume", PM860X_HS1_CTRL,
+ PM860X_HS2_CTRL, 0, 7, 0, out_tlv),
+ SOC_DOUBLE_R_EXT_TLV("Hifi Left Playback Volume",
+ PM860X_HIFIL_GAIN_LEFT,
+ PM860X_HIFIL_GAIN_RIGHT, 0, 63, 0,
+ snd_soc_get_volsw_2r_out,
+ snd_soc_put_volsw_2r_out, dpga_tlv),
+ SOC_DOUBLE_R_EXT_TLV("Hifi Right Playback Volume",
+ PM860X_HIFIR_GAIN_LEFT,
+ PM860X_HIFIR_GAIN_RIGHT, 0, 63, 0,
+ snd_soc_get_volsw_2r_out,
+ snd_soc_put_volsw_2r_out, dpga_tlv),
+ SOC_DOUBLE_R_EXT_TLV("Lofi Playback Volume", PM860X_LOFI_GAIN_LEFT,
+ PM860X_LOFI_GAIN_RIGHT, 0, 63, 0,
+ snd_soc_get_volsw_2r_out,
+ snd_soc_put_volsw_2r_out, dpga_tlv),
+ SOC_ENUM("Headset1 Operational Amplifier Current",
+ pm860x_hs1_opamp_enum),
+ SOC_ENUM("Headset2 Operational Amplifier Current",
+ pm860x_hs2_opamp_enum),
+ SOC_ENUM("Headset1 Amplifier Current", pm860x_hs1_pa_enum),
+ SOC_ENUM("Headset2 Amplifier Current", pm860x_hs2_pa_enum),
+ SOC_ENUM("Lineout1 Operational Amplifier Current",
+ pm860x_lo1_opamp_enum),
+ SOC_ENUM("Lineout2 Operational Amplifier Current",
+ pm860x_lo2_opamp_enum),
+ SOC_ENUM("Lineout1 Amplifier Current", pm860x_lo1_pa_enum),
+ SOC_ENUM("Lineout2 Amplifier Current", pm860x_lo2_pa_enum),
+ SOC_ENUM("Speaker Operational Amplifier Current",
+ pm860x_spk_ear_opamp_enum),
+ SOC_ENUM("Speaker Amplifier Current", pm860x_spk_pa_enum),
+ SOC_ENUM("Earpiece Amplifier Current", pm860x_ear_pa_enum),
+};
+
+/*
+ * DAPM Controls
+ */
+
+/* PCM Switch / PCM Interface */
+static const struct snd_kcontrol_new pcm_switch_controls =
+ SOC_DAPM_SINGLE("Switch", PM860X_ADC_EN_2, 0, 1, 0);
+
+/* AUX1 Switch */
+static const struct snd_kcontrol_new aux1_switch_controls =
+ SOC_DAPM_SINGLE("Switch", PM860X_ANA_TO_ANA, 4, 1, 0);
+
+/* AUX2 Switch */
+static const struct snd_kcontrol_new aux2_switch_controls =
+ SOC_DAPM_SINGLE("Switch", PM860X_ANA_TO_ANA, 5, 1, 0);
+
+/* Left Ex. PA Switch */
+static const struct snd_kcontrol_new lepa_switch_controls =
+ SOC_DAPM_SINGLE("Switch", PM860X_DAC_EN_2, 2, 1, 0);
+
+/* Right Ex. PA Switch */
+static const struct snd_kcontrol_new repa_switch_controls =
+ SOC_DAPM_SINGLE("Switch", PM860X_DAC_EN_2, 1, 1, 0);
+
+/* PCM Mux / Mux7 */
+static const char *aif1_text[] = {
+ "PCM L", "PCM R",
+};
+
+static const struct soc_enum aif1_enum =
+ SOC_ENUM_SINGLE(PM860X_PCM_IFACE_3, 6, 2, aif1_text);
+
+static const struct snd_kcontrol_new aif1_mux =
+ SOC_DAPM_ENUM("PCM Mux", aif1_enum);
+
+/* I2S Mux / Mux9 */
+static const char *i2s_din_text[] = {
+ "DIN", "DIN1",
+};
+
+static const struct soc_enum i2s_din_enum =
+ SOC_ENUM_SINGLE(PM860X_I2S_IFACE_3, 1, 2, i2s_din_text);
+
+static const struct snd_kcontrol_new i2s_din_mux =
+ SOC_DAPM_ENUM("I2S DIN Mux", i2s_din_enum);
+
+/* I2S Mic Mux / Mux8 */
+static const char *i2s_mic_text[] = {
+ "Ex PA", "ADC",
+};
+
+static const struct soc_enum i2s_mic_enum =
+ SOC_ENUM_SINGLE(PM860X_I2S_IFACE_3, 4, 2, i2s_mic_text);
+
+static const struct snd_kcontrol_new i2s_mic_mux =
+ SOC_DAPM_ENUM("I2S Mic Mux", i2s_mic_enum);
+
+/* ADCL Mux / Mux2 */
+static const char *adcl_text[] = {
+ "ADCR", "ADCL",
+};
+
+static const struct soc_enum adcl_enum =
+ SOC_ENUM_SINGLE(PM860X_PCM_IFACE_3, 4, 2, adcl_text);
+
+static const struct snd_kcontrol_new adcl_mux =
+ SOC_DAPM_ENUM("ADC Left Mux", adcl_enum);
+
+/* ADCR Mux / Mux3 */
+static const char *adcr_text[] = {
+ "ADCL", "ADCR",
+};
+
+static const struct soc_enum adcr_enum =
+ SOC_ENUM_SINGLE(PM860X_PCM_IFACE_3, 2, 2, adcr_text);
+
+static const struct snd_kcontrol_new adcr_mux =
+ SOC_DAPM_ENUM("ADC Right Mux", adcr_enum);
+
+/* ADCR EC Mux / Mux6 */
+static const char *adcr_ec_text[] = {
+ "ADCR", "EC",
+};
+
+static const struct soc_enum adcr_ec_enum =
+ SOC_ENUM_SINGLE(PM860X_ADC_EN_2, 3, 2, adcr_ec_text);
+
+static const struct snd_kcontrol_new adcr_ec_mux =
+ SOC_DAPM_ENUM("ADCR EC Mux", adcr_ec_enum);
+
+/* EC Mux / Mux4 */
+static const char *ec_text[] = {
+ "Left", "Right", "Left + Right",
+};
+
+static const struct soc_enum ec_enum =
+ SOC_ENUM_SINGLE(PM860X_EC_PATH, 1, 3, ec_text);
+
+static const struct snd_kcontrol_new ec_mux =
+ SOC_DAPM_ENUM("EC Mux", ec_enum);
+
+static const char *dac_text[] = {
+ "No input", "Right", "Left", "No input",
+};
+
+/* DAC Headset 1 Mux / Mux10 */
+static const struct soc_enum dac_hs1_enum =
+ SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 0, 4, dac_text);
+
+static const struct snd_kcontrol_new dac_hs1_mux =
+ SOC_DAPM_ENUM("DAC HS1 Mux", dac_hs1_enum);
+
+/* DAC Headset 2 Mux / Mux11 */
+static const struct soc_enum dac_hs2_enum =
+ SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 2, 4, dac_text);
+
+static const struct snd_kcontrol_new dac_hs2_mux =
+ SOC_DAPM_ENUM("DAC HS2 Mux", dac_hs2_enum);
+
+/* DAC Lineout 1 Mux / Mux12 */
+static const struct soc_enum dac_lo1_enum =
+ SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 4, 4, dac_text);
+
+static const struct snd_kcontrol_new dac_lo1_mux =
+ SOC_DAPM_ENUM("DAC LO1 Mux", dac_lo1_enum);
+
+/* DAC Lineout 2 Mux / Mux13 */
+static const struct soc_enum dac_lo2_enum =
+ SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 6, 4, dac_text);
+
+static const struct snd_kcontrol_new dac_lo2_mux =
+ SOC_DAPM_ENUM("DAC LO2 Mux", dac_lo2_enum);
+
+/* DAC Spearker Earphone Mux / Mux14 */
+static const struct soc_enum dac_spk_ear_enum =
+ SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_2, 0, 4, dac_text);
+
+static const struct snd_kcontrol_new dac_spk_ear_mux =
+ SOC_DAPM_ENUM("DAC SP Mux", dac_spk_ear_enum);
+
+/* Headset 1 Mux / Mux15 */
+static const char *in_text[] = {
+ "Digital", "Analog",
+};
+
+static const struct soc_enum hs1_enum =
+ SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 0, 2, in_text);
+
+static const struct snd_kcontrol_new hs1_mux =
+ SOC_DAPM_ENUM("Headset1 Mux", hs1_enum);
+
+/* Headset 2 Mux / Mux16 */
+static const struct soc_enum hs2_enum =
+ SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 1, 2, in_text);
+
+static const struct snd_kcontrol_new hs2_mux =
+ SOC_DAPM_ENUM("Headset2 Mux", hs2_enum);
+
+/* Lineout 1 Mux / Mux17 */
+static const struct soc_enum lo1_enum =
+ SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 2, 2, in_text);
+
+static const struct snd_kcontrol_new lo1_mux =
+ SOC_DAPM_ENUM("Lineout1 Mux", lo1_enum);
+
+/* Lineout 2 Mux / Mux18 */
+static const struct soc_enum lo2_enum =
+ SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 3, 2, in_text);
+
+static const struct snd_kcontrol_new lo2_mux =
+ SOC_DAPM_ENUM("Lineout2 Mux", lo2_enum);
+
+/* Speaker Earpiece Demux */
+static const char *spk_text[] = {
+ "Earpiece", "Speaker",
+};
+
+static const struct soc_enum spk_enum =
+ SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 6, 2, spk_text);
+
+static const struct snd_kcontrol_new spk_demux =
+ SOC_DAPM_ENUM("Speaker Earpiece Demux", spk_enum);
+
+/* MIC Mux / Mux1 */
+static const char *mic_text[] = {
+ "Mic 1", "Mic 2",
+};
+
+static const struct soc_enum mic_enum =
+ SOC_ENUM_SINGLE(PM860X_ADC_ANA_4, 4, 2, mic_text);
+
+static const struct snd_kcontrol_new mic_mux =
+ SOC_DAPM_ENUM("MIC Mux", mic_enum);
+
+static const struct snd_soc_dapm_widget pm860x_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("PCM SDI", "PCM Playback", 0,
+ PM860X_ADC_EN_2, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PCM SDO", "PCM Capture", 0,
+ PM860X_PCM_IFACE_3, 1, 1),
+
+
+ SND_SOC_DAPM_AIF_IN("I2S DIN", "I2S Playback", 0,
+ PM860X_DAC_EN_2, 0, 0),
+ SND_SOC_DAPM_AIF_IN("I2S DIN1", "I2S Playback", 0,
+ PM860X_DAC_EN_2, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("I2S DOUT", "I2S Capture", 0,
+ PM860X_I2S_IFACE_3, 5, 1),
+ SND_SOC_DAPM_MUX("I2S Mic Mux", SND_SOC_NOPM, 0, 0, &i2s_mic_mux),
+ SND_SOC_DAPM_MUX("ADC Left Mux", SND_SOC_NOPM, 0, 0, &adcl_mux),
+ SND_SOC_DAPM_MUX("ADC Right Mux", SND_SOC_NOPM, 0, 0, &adcr_mux),
+ SND_SOC_DAPM_MUX("EC Mux", SND_SOC_NOPM, 0, 0, &ec_mux),
+ SND_SOC_DAPM_MUX("ADCR EC Mux", SND_SOC_NOPM, 0, 0, &adcr_ec_mux),
+ SND_SOC_DAPM_SWITCH("Left EPA", SND_SOC_NOPM, 0, 0,
+ &lepa_switch_controls),
+ SND_SOC_DAPM_SWITCH("Right EPA", SND_SOC_NOPM, 0, 0,
+ &repa_switch_controls),
+
+ SND_SOC_DAPM_REG(snd_soc_dapm_supply, "Left ADC MOD", PM860X_ADC_EN_1,
+ 0, 1, 1, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_supply, "Right ADC MOD", PM860X_ADC_EN_1,
+ 1, 1, 1, 0),
+ SND_SOC_DAPM_ADC("Left ADC", NULL, PM860X_ADC_EN_2, 5, 0),
+ SND_SOC_DAPM_ADC("Right ADC", NULL, PM860X_ADC_EN_2, 4, 0),
+
+ SND_SOC_DAPM_SWITCH("AUX1 Switch", SND_SOC_NOPM, 0, 0,
+ &aux1_switch_controls),
+ SND_SOC_DAPM_SWITCH("AUX2 Switch", SND_SOC_NOPM, 0, 0,
+ &aux2_switch_controls),
+
+ SND_SOC_DAPM_MUX("MIC Mux", SND_SOC_NOPM, 0, 0, &mic_mux),
+ SND_SOC_DAPM_MICBIAS("Mic1 Bias", PM860X_ADC_ANA_1, 2, 0),
+ SND_SOC_DAPM_MICBIAS("Mic3 Bias", PM860X_ADC_ANA_1, 7, 0),
+ SND_SOC_DAPM_PGA("MIC1 Volume", PM860X_ADC_EN_1, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("MIC3 Volume", PM860X_ADC_EN_1, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("AUX1 Volume", PM860X_ADC_EN_1, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("AUX2 Volume", PM860X_ADC_EN_1, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Sidetone PGA", PM860X_ADC_EN_2, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Lofi PGA", PM860X_ADC_EN_2, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_INPUT("AUX1"),
+ SND_SOC_DAPM_INPUT("AUX2"),
+ SND_SOC_DAPM_INPUT("MIC1P"),
+ SND_SOC_DAPM_INPUT("MIC1N"),
+ SND_SOC_DAPM_INPUT("MIC2P"),
+ SND_SOC_DAPM_INPUT("MIC2N"),
+ SND_SOC_DAPM_INPUT("MIC3P"),
+ SND_SOC_DAPM_INPUT("MIC3N"),
+
+ SND_SOC_DAPM_DAC_E("Left DAC", NULL, SND_SOC_NOPM, 0, 0,
+ pm860x_dac_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_DAC_E("Right DAC", NULL, SND_SOC_NOPM, 0, 0,
+ pm860x_dac_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_MUX("I2S DIN Mux", SND_SOC_NOPM, 0, 0, &i2s_din_mux),
+ SND_SOC_DAPM_MUX("DAC HS1 Mux", SND_SOC_NOPM, 0, 0, &dac_hs1_mux),
+ SND_SOC_DAPM_MUX("DAC HS2 Mux", SND_SOC_NOPM, 0, 0, &dac_hs2_mux),
+ SND_SOC_DAPM_MUX("DAC LO1 Mux", SND_SOC_NOPM, 0, 0, &dac_lo1_mux),
+ SND_SOC_DAPM_MUX("DAC LO2 Mux", SND_SOC_NOPM, 0, 0, &dac_lo2_mux),
+ SND_SOC_DAPM_MUX("DAC SP Mux", SND_SOC_NOPM, 0, 0, &dac_spk_ear_mux),
+ SND_SOC_DAPM_MUX("Headset1 Mux", SND_SOC_NOPM, 0, 0, &hs1_mux),
+ SND_SOC_DAPM_MUX("Headset2 Mux", SND_SOC_NOPM, 0, 0, &hs2_mux),
+ SND_SOC_DAPM_MUX("Lineout1 Mux", SND_SOC_NOPM, 0, 0, &lo1_mux),
+ SND_SOC_DAPM_MUX("Lineout2 Mux", SND_SOC_NOPM, 0, 0, &lo2_mux),
+ SND_SOC_DAPM_MUX("Speaker Earpiece Demux", SND_SOC_NOPM, 0, 0,
+ &spk_demux),
+
+
+ SND_SOC_DAPM_PGA("Headset1 PGA", PM860X_DAC_EN_1, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Headset2 PGA", PM860X_DAC_EN_1, 1, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("HS1"),
+ SND_SOC_DAPM_OUTPUT("HS2"),
+ SND_SOC_DAPM_PGA("Lineout1 PGA", PM860X_DAC_EN_1, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Lineout2 PGA", PM860X_DAC_EN_1, 3, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("LINEOUT1"),
+ SND_SOC_DAPM_OUTPUT("LINEOUT2"),
+ SND_SOC_DAPM_PGA("Earpiece PGA", PM860X_DAC_EN_1, 4, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("EARP"),
+ SND_SOC_DAPM_OUTPUT("EARN"),
+ SND_SOC_DAPM_PGA("Speaker PGA", PM860X_DAC_EN_1, 5, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("LSP"),
+ SND_SOC_DAPM_OUTPUT("LSN"),
+ SND_SOC_DAPM_REG(snd_soc_dapm_supply, "VCODEC", PM860X_AUDIO_SUPPLIES_2,
+ 0, SUPPLY_MASK, SUPPLY_MASK, 0),
+
+ PM860X_DAPM_OUTPUT("RSYNC", pm860x_rsync_event),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* supply */
+ {"Left DAC", NULL, "VCODEC"},
+ {"Right DAC", NULL, "VCODEC"},
+ {"Left ADC", NULL, "VCODEC"},
+ {"Right ADC", NULL, "VCODEC"},
+ {"Left ADC", NULL, "Left ADC MOD"},
+ {"Right ADC", NULL, "Right ADC MOD"},
+
+ /* PCM/AIF1 Inputs */
+ {"PCM SDO", NULL, "ADC Left Mux"},
+ {"PCM SDO", NULL, "ADCR EC Mux"},
+
+ /* PCM/AFI2 Outputs */
+ {"Lofi PGA", NULL, "PCM SDI"},
+ {"Lofi PGA", NULL, "Sidetone PGA"},
+ {"Left DAC", NULL, "Lofi PGA"},
+ {"Right DAC", NULL, "Lofi PGA"},
+
+ /* I2S/AIF2 Inputs */
+ {"MIC Mux", "Mic 1", "MIC1P"},
+ {"MIC Mux", "Mic 1", "MIC1N"},
+ {"MIC Mux", "Mic 2", "MIC2P"},
+ {"MIC Mux", "Mic 2", "MIC2N"},
+ {"MIC1 Volume", NULL, "MIC Mux"},
+ {"MIC3 Volume", NULL, "MIC3P"},
+ {"MIC3 Volume", NULL, "MIC3N"},
+ {"Left ADC", NULL, "MIC1 Volume"},
+ {"Right ADC", NULL, "MIC3 Volume"},
+ {"ADC Left Mux", "ADCR", "Right ADC"},
+ {"ADC Left Mux", "ADCL", "Left ADC"},
+ {"ADC Right Mux", "ADCL", "Left ADC"},
+ {"ADC Right Mux", "ADCR", "Right ADC"},
+ {"Left EPA", "Switch", "Left DAC"},
+ {"Right EPA", "Switch", "Right DAC"},
+ {"EC Mux", "Left", "Left DAC"},
+ {"EC Mux", "Right", "Right DAC"},
+ {"EC Mux", "Left + Right", "Left DAC"},
+ {"EC Mux", "Left + Right", "Right DAC"},
+ {"ADCR EC Mux", "ADCR", "ADC Right Mux"},
+ {"ADCR EC Mux", "EC", "EC Mux"},
+ {"I2S Mic Mux", "Ex PA", "Left EPA"},
+ {"I2S Mic Mux", "Ex PA", "Right EPA"},
+ {"I2S Mic Mux", "ADC", "ADC Left Mux"},
+ {"I2S Mic Mux", "ADC", "ADCR EC Mux"},
+ {"I2S DOUT", NULL, "I2S Mic Mux"},
+
+ /* I2S/AIF2 Outputs */
+ {"I2S DIN Mux", "DIN", "I2S DIN"},
+ {"I2S DIN Mux", "DIN1", "I2S DIN1"},
+ {"Left DAC", NULL, "I2S DIN Mux"},
+ {"Right DAC", NULL, "I2S DIN Mux"},
+ {"DAC HS1 Mux", "Left", "Left DAC"},
+ {"DAC HS1 Mux", "Right", "Right DAC"},
+ {"DAC HS2 Mux", "Left", "Left DAC"},
+ {"DAC HS2 Mux", "Right", "Right DAC"},
+ {"DAC LO1 Mux", "Left", "Left DAC"},
+ {"DAC LO1 Mux", "Right", "Right DAC"},
+ {"DAC LO2 Mux", "Left", "Left DAC"},
+ {"DAC LO2 Mux", "Right", "Right DAC"},
+ {"Headset1 Mux", "Digital", "DAC HS1 Mux"},
+ {"Headset2 Mux", "Digital", "DAC HS2 Mux"},
+ {"Lineout1 Mux", "Digital", "DAC LO1 Mux"},
+ {"Lineout2 Mux", "Digital", "DAC LO2 Mux"},
+ {"Headset1 PGA", NULL, "Headset1 Mux"},
+ {"Headset2 PGA", NULL, "Headset2 Mux"},
+ {"Lineout1 PGA", NULL, "Lineout1 Mux"},
+ {"Lineout2 PGA", NULL, "Lineout2 Mux"},
+ {"DAC SP Mux", "Left", "Left DAC"},
+ {"DAC SP Mux", "Right", "Right DAC"},
+ {"Speaker Earpiece Demux", "Speaker", "DAC SP Mux"},
+ {"Speaker PGA", NULL, "Speaker Earpiece Demux"},
+ {"Earpiece PGA", NULL, "Speaker Earpiece Demux"},
+
+ {"RSYNC", NULL, "Headset1 PGA"},
+ {"RSYNC", NULL, "Headset2 PGA"},
+ {"RSYNC", NULL, "Lineout1 PGA"},
+ {"RSYNC", NULL, "Lineout2 PGA"},
+ {"RSYNC", NULL, "Speaker PGA"},
+ {"RSYNC", NULL, "Speaker PGA"},
+ {"RSYNC", NULL, "Earpiece PGA"},
+ {"RSYNC", NULL, "Earpiece PGA"},
+
+ {"HS1", NULL, "RSYNC"},
+ {"HS2", NULL, "RSYNC"},
+ {"LINEOUT1", NULL, "RSYNC"},
+ {"LINEOUT2", NULL, "RSYNC"},
+ {"LSP", NULL, "RSYNC"},
+ {"LSN", NULL, "RSYNC"},
+ {"EARP", NULL, "RSYNC"},
+ {"EARN", NULL, "RSYNC"},
+};
+
+/*
+ * Use MUTE_LEFT & MUTE_RIGHT to implement digital mute.
+ * These bits can also be used to mute.
+ */
+static int pm860x_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int data = 0, mask = MUTE_LEFT | MUTE_RIGHT;
+
+ if (mute)
+ data = mask;
+ snd_soc_update_bits(codec, PM860X_DAC_OFFSET, mask, data);
+ snd_soc_update_bits(codec, PM860X_EAR_CTRL_2,
+ RSYNC_CHANGE, RSYNC_CHANGE);
+ return 0;
+}
+
+static int pm860x_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ unsigned char inf = 0, mask = 0;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ inf &= ~PCM_INF2_18WL;
+ break;
+ case SNDRV_PCM_FORMAT_S18_3LE:
+ inf |= PCM_INF2_18WL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ mask |= PCM_INF2_18WL;
+ snd_soc_update_bits(codec, PM860X_PCM_IFACE_2, mask, inf);
+
+ /* sample rate */
+ switch (params_rate(params)) {
+ case 8000:
+ inf = 0;
+ break;
+ case 16000:
+ inf = 3;
+ break;
+ case 32000:
+ inf = 6;
+ break;
+ case 48000:
+ inf = 8;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(codec, PM860X_PCM_RATE, 0x0f, inf);
+
+ return 0;
+}
+
+static int pm860x_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+ unsigned char inf = 0, mask = 0;
+ int ret = -EINVAL;
+
+ mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ if (pm860x->dir == PM860X_CLK_DIR_OUT) {
+ inf |= PCM_INF2_MASTER;
+ ret = 0;
+ }
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ if (pm860x->dir == PM860X_CLK_DIR_IN) {
+ inf &= ~PCM_INF2_MASTER;
+ ret = 0;
+ }
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ inf |= PCM_EXACT_I2S;
+ ret = 0;
+ break;
+ }
+ mask |= PCM_MODE_MASK;
+ if (ret)
+ return ret;
+ snd_soc_update_bits(codec, PM860X_PCM_IFACE_2, mask, inf);
+ return 0;
+}
+
+static int pm860x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+
+ if (dir == PM860X_CLK_DIR_OUT)
+ pm860x->dir = PM860X_CLK_DIR_OUT;
+ else {
+ pm860x->dir = PM860X_CLK_DIR_IN;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int pm860x_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ unsigned char inf;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ inf = 0;
+ break;
+ case SNDRV_PCM_FORMAT_S18_3LE:
+ inf = PCM_INF2_18WL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(codec, PM860X_I2S_IFACE_2, PCM_INF2_18WL, inf);
+
+ /* sample rate */
+ switch (params_rate(params)) {
+ case 8000:
+ inf = 0;
+ break;
+ case 11025:
+ inf = 1;
+ break;
+ case 16000:
+ inf = 3;
+ break;
+ case 22050:
+ inf = 4;
+ break;
+ case 32000:
+ inf = 6;
+ break;
+ case 44100:
+ inf = 7;
+ break;
+ case 48000:
+ inf = 8;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(codec, PM860X_I2S_IFACE_4, 0xf, inf);
+
+ return 0;
+}
+
+static int pm860x_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+ unsigned char inf = 0, mask = 0;
+
+ mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ if (pm860x->dir == PM860X_CLK_DIR_OUT)
+ inf |= PCM_INF2_MASTER;
+ else
+ return -EINVAL;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ if (pm860x->dir == PM860X_CLK_DIR_IN)
+ inf &= ~PCM_INF2_MASTER;
+ else
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ inf |= PCM_EXACT_I2S;
+ break;
+ default:
+ return -EINVAL;
+ }
+ mask |= PCM_MODE_MASK;
+ snd_soc_update_bits(codec, PM860X_I2S_IFACE_2, mask, inf);
+ return 0;
+}
+
+static int pm860x_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int data;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Enable Audio PLL & Audio section */
+ data = AUDIO_PLL | AUDIO_SECTION_RESET
+ | AUDIO_SECTION_ON;
+ pm860x_reg_write(codec->control_data, REG_MISC2, data);
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ data = AUDIO_PLL | AUDIO_SECTION_RESET | AUDIO_SECTION_ON;
+ pm860x_set_bits(codec->control_data, REG_MISC2, data, 0);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static struct snd_soc_dai_ops pm860x_pcm_dai_ops = {
+ .digital_mute = pm860x_digital_mute,
+ .hw_params = pm860x_pcm_hw_params,
+ .set_fmt = pm860x_pcm_set_dai_fmt,
+ .set_sysclk = pm860x_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_ops pm860x_i2s_dai_ops = {
+ .digital_mute = pm860x_digital_mute,
+ .hw_params = pm860x_i2s_hw_params,
+ .set_fmt = pm860x_i2s_set_dai_fmt,
+ .set_sysclk = pm860x_set_dai_sysclk,
+};
+
+#define PM860X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_driver pm860x_dai[] = {
+ {
+ /* DAI PCM */
+ .name = "88pm860x-pcm",
+ .id = 1,
+ .playback = {
+ .stream_name = "PCM Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PM860X_RATES,
+ .formats = SNDRV_PCM_FORMAT_S16_LE | \
+ SNDRV_PCM_FORMAT_S18_3LE,
+ },
+ .capture = {
+ .stream_name = "PCM Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PM860X_RATES,
+ .formats = SNDRV_PCM_FORMAT_S16_LE | \
+ SNDRV_PCM_FORMAT_S18_3LE,
+ },
+ .ops = &pm860x_pcm_dai_ops,
+ }, {
+ /* DAI I2S */
+ .name = "88pm860x-i2s",
+ .id = 2,
+ .playback = {
+ .stream_name = "I2S Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FORMAT_S16_LE | \
+ SNDRV_PCM_FORMAT_S18_3LE,
+ },
+ .capture = {
+ .stream_name = "I2S Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FORMAT_S16_LE | \
+ SNDRV_PCM_FORMAT_S18_3LE,
+ },
+ .ops = &pm860x_i2s_dai_ops,
+ },
+};
+
+static irqreturn_t pm860x_codec_handler(int irq, void *data)
+{
+ struct pm860x_priv *pm860x = data;
+ int status, shrt, report = 0, mic_report = 0;
+ int mask;
+
+ status = pm860x_reg_read(pm860x->i2c, REG_STATUS_1);
+ shrt = pm860x_reg_read(pm860x->i2c, REG_SHORTS);
+ mask = pm860x->det.hs_shrt | pm860x->det.hook_det | pm860x->det.lo_shrt
+ | pm860x->det.hp_det;
+
+#ifndef CONFIG_SND_SOC_88PM860X_MODULE
+ if (status & (HEADSET_STATUS | MIC_STATUS | SHORT_HS1 | SHORT_HS2 |
+ SHORT_LO1 | SHORT_LO2))
+ trace_snd_soc_jack_irq(dev_name(pm860x->codec->dev));
+#endif
+
+ if ((pm860x->det.hp_det & SND_JACK_HEADPHONE)
+ && (status & HEADSET_STATUS))
+ report |= SND_JACK_HEADPHONE;
+
+ if ((pm860x->det.mic_det & SND_JACK_MICROPHONE)
+ && (status & MIC_STATUS))
+ mic_report |= SND_JACK_MICROPHONE;
+
+ if (pm860x->det.hs_shrt && (shrt & (SHORT_HS1 | SHORT_HS2)))
+ report |= pm860x->det.hs_shrt;
+
+ if (pm860x->det.hook_det && (status & HOOK_STATUS))
+ report |= pm860x->det.hook_det;
+
+ if (pm860x->det.lo_shrt && (shrt & (SHORT_LO1 | SHORT_LO2)))
+ report |= pm860x->det.lo_shrt;
+
+ if (report)
+ snd_soc_jack_report(pm860x->det.hp_jack, report, mask);
+ if (mic_report)
+ snd_soc_jack_report(pm860x->det.mic_jack, SND_JACK_MICROPHONE,
+ SND_JACK_MICROPHONE);
+
+ dev_dbg(pm860x->codec->dev, "headphone report:0x%x, mask:%x\n",
+ report, mask);
+ dev_dbg(pm860x->codec->dev, "microphone report:0x%x\n", mic_report);
+ return IRQ_HANDLED;
+}
+
+int pm860x_hs_jack_detect(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack,
+ int det, int hook, int hs_shrt, int lo_shrt)
+{
+ struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+ int data;
+
+ pm860x->det.hp_jack = jack;
+ pm860x->det.hp_det = det;
+ pm860x->det.hook_det = hook;
+ pm860x->det.hs_shrt = hs_shrt;
+ pm860x->det.lo_shrt = lo_shrt;
+
+ if (det & SND_JACK_HEADPHONE)
+ pm860x_set_bits(codec->control_data, REG_HS_DET,
+ EN_HS_DET, EN_HS_DET);
+ /* headset short detect */
+ if (hs_shrt) {
+ data = CLR_SHORT_HS2 | CLR_SHORT_HS1;
+ pm860x_set_bits(codec->control_data, REG_SHORTS, data, data);
+ }
+ /* Lineout short detect */
+ if (lo_shrt) {
+ data = CLR_SHORT_LO2 | CLR_SHORT_LO1;
+ pm860x_set_bits(codec->control_data, REG_SHORTS, data, data);
+ }
+
+ /* sync status */
+ pm860x_codec_handler(0, pm860x);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pm860x_hs_jack_detect);
+
+int pm860x_mic_jack_detect(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack, int det)
+{
+ struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+
+ pm860x->det.mic_jack = jack;
+ pm860x->det.mic_det = det;
+
+ if (det & SND_JACK_MICROPHONE)
+ pm860x_set_bits(codec->control_data, REG_MIC_DET,
+ MICDET_MASK, MICDET_MASK);
+
+ /* sync status */
+ pm860x_codec_handler(0, pm860x);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pm860x_mic_jack_detect);
+
+static int pm860x_probe(struct snd_soc_codec *codec)
+{
+ struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int i, ret;
+
+ pm860x->codec = codec;
+
+ codec->control_data = pm860x->i2c;
+
+ for (i = 0; i < 4; i++) {
+ ret = request_threaded_irq(pm860x->irq[i], NULL,
+ pm860x_codec_handler, IRQF_ONESHOT,
+ pm860x->name[i], pm860x);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to request IRQ!\n");
+ goto out;
+ }
+ }
+
+ pm860x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ ret = pm860x_bulk_read(codec->control_data, REG_CACHE_BASE,
+ REG_CACHE_SIZE, codec->reg_cache);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to fill register cache: %d\n",
+ ret);
+ goto out;
+ }
+
+ snd_soc_add_controls(codec, pm860x_snd_controls,
+ ARRAY_SIZE(pm860x_snd_controls));
+ snd_soc_dapm_new_controls(dapm, pm860x_dapm_widgets,
+ ARRAY_SIZE(pm860x_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+ return 0;
+
+out:
+ while (--i >= 0)
+ free_irq(pm860x->irq[i], pm860x);
+ return ret;
+}
+
+static int pm860x_remove(struct snd_soc_codec *codec)
+{
+ struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ for (i = 3; i >= 0; i--)
+ free_irq(pm860x->irq[i], pm860x);
+ pm860x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_pm860x = {
+ .probe = pm860x_probe,
+ .remove = pm860x_remove,
+ .read = pm860x_read_reg_cache,
+ .write = pm860x_write_reg_cache,
+ .reg_cache_size = REG_CACHE_SIZE,
+ .reg_word_size = sizeof(u8),
+ .set_bias_level = pm860x_set_bias_level,
+};
+
+static int __devinit pm860x_codec_probe(struct platform_device *pdev)
+{
+ struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct pm860x_priv *pm860x;
+ struct resource *res;
+ int i, ret;
+
+ pm860x = kzalloc(sizeof(struct pm860x_priv), GFP_KERNEL);
+ if (pm860x == NULL)
+ return -ENOMEM;
+
+ pm860x->chip = chip;
+ pm860x->i2c = (chip->id == CHIP_PM8607) ? chip->client
+ : chip->companion;
+ platform_set_drvdata(pdev, pm860x);
+
+ for (i = 0; i < 4; i++) {
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get IRQ resources\n");
+ goto out;
+ }
+ pm860x->irq[i] = res->start + chip->irq_base;
+ strncpy(pm860x->name[i], res->name, MAX_NAME_LEN);
+ }
+
+ ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_pm860x,
+ pm860x_dai, ARRAY_SIZE(pm860x_dai));
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register codec\n");
+ goto out;
+ }
+ return ret;
+
+out:
+ platform_set_drvdata(pdev, NULL);
+ kfree(pm860x);
+ return -EINVAL;
+}
+
+static int __devexit pm860x_codec_remove(struct platform_device *pdev)
+{
+ struct pm860x_priv *pm860x = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_codec(&pdev->dev);
+ platform_set_drvdata(pdev, NULL);
+ kfree(pm860x);
+ return 0;
+}
+
+static struct platform_driver pm860x_codec_driver = {
+ .driver = {
+ .name = "88pm860x-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = pm860x_codec_probe,
+ .remove = __devexit_p(pm860x_codec_remove),
+};
+
+static __init int pm860x_init(void)
+{
+ return platform_driver_register(&pm860x_codec_driver);
+}
+module_init(pm860x_init);
+
+static __exit void pm860x_exit(void)
+{
+ platform_driver_unregister(&pm860x_codec_driver);
+}
+module_exit(pm860x_exit);
+
+MODULE_DESCRIPTION("ASoC 88PM860x driver");
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:88pm860x-codec");
+
diff --git a/sound/soc/codecs/88pm860x-codec.h b/sound/soc/codecs/88pm860x-codec.h
new file mode 100644
index 00000000..3364ba4a
--- /dev/null
+++ b/sound/soc/codecs/88pm860x-codec.h
@@ -0,0 +1,97 @@
+/*
+ * 88pm860x-codec.h -- 88PM860x ALSA SoC Audio Driver
+ *
+ * Copyright 2010 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __88PM860X_H
+#define __88PM860X_H
+
+/* The offset of these registers are 0xb0 */
+#define PM860X_PCM_IFACE_1 0x00
+#define PM860X_PCM_IFACE_2 0x01
+#define PM860X_PCM_IFACE_3 0x02
+#define PM860X_PCM_RATE 0x03
+#define PM860X_EC_PATH 0x04
+#define PM860X_SIDETONE_L_GAIN 0x05
+#define PM860X_SIDETONE_R_GAIN 0x06
+#define PM860X_SIDETONE_SHIFT 0x07
+#define PM860X_ADC_OFFSET_1 0x08
+#define PM860X_ADC_OFFSET_2 0x09
+#define PM860X_DMIC_DELAY 0x0a
+
+#define PM860X_I2S_IFACE_1 0x0b
+#define PM860X_I2S_IFACE_2 0x0c
+#define PM860X_I2S_IFACE_3 0x0d
+#define PM860X_I2S_IFACE_4 0x0e
+#define PM860X_EQUALIZER_N0_1 0x0f
+#define PM860X_EQUALIZER_N0_2 0x10
+#define PM860X_EQUALIZER_N1_1 0x11
+#define PM860X_EQUALIZER_N1_2 0x12
+#define PM860X_EQUALIZER_D1_1 0x13
+#define PM860X_EQUALIZER_D1_2 0x14
+#define PM860X_LOFI_GAIN_LEFT 0x15
+#define PM860X_LOFI_GAIN_RIGHT 0x16
+#define PM860X_HIFIL_GAIN_LEFT 0x17
+#define PM860X_HIFIL_GAIN_RIGHT 0x18
+#define PM860X_HIFIR_GAIN_LEFT 0x19
+#define PM860X_HIFIR_GAIN_RIGHT 0x1a
+#define PM860X_DAC_OFFSET 0x1b
+#define PM860X_OFFSET_LEFT_1 0x1c
+#define PM860X_OFFSET_LEFT_2 0x1d
+#define PM860X_OFFSET_RIGHT_1 0x1e
+#define PM860X_OFFSET_RIGHT_2 0x1f
+#define PM860X_ADC_ANA_1 0x20
+#define PM860X_ADC_ANA_2 0x21
+#define PM860X_ADC_ANA_3 0x22
+#define PM860X_ADC_ANA_4 0x23
+#define PM860X_ANA_TO_ANA 0x24
+#define PM860X_HS1_CTRL 0x25
+#define PM860X_HS2_CTRL 0x26
+#define PM860X_LO1_CTRL 0x27
+#define PM860X_LO2_CTRL 0x28
+#define PM860X_EAR_CTRL_1 0x29
+#define PM860X_EAR_CTRL_2 0x2a
+#define PM860X_AUDIO_SUPPLIES_1 0x2b
+#define PM860X_AUDIO_SUPPLIES_2 0x2c
+#define PM860X_ADC_EN_1 0x2d
+#define PM860X_ADC_EN_2 0x2e
+#define PM860X_DAC_EN_1 0x2f
+#define PM860X_DAC_EN_2 0x31
+#define PM860X_AUDIO_CAL_1 0x32
+#define PM860X_AUDIO_CAL_2 0x33
+#define PM860X_AUDIO_CAL_3 0x34
+#define PM860X_AUDIO_CAL_4 0x35
+#define PM860X_AUDIO_CAL_5 0x36
+#define PM860X_ANA_INPUT_SEL_1 0x37
+#define PM860X_ANA_INPUT_SEL_2 0x38
+
+#define PM860X_PCM_IFACE_4 0x39
+#define PM860X_I2S_IFACE_5 0x3a
+
+#define PM860X_SHORTS 0x3b
+#define PM860X_PLL_ADJ_1 0x3c
+#define PM860X_PLL_ADJ_2 0x3d
+
+/* bits definition */
+#define PM860X_CLK_DIR_IN 0
+#define PM860X_CLK_DIR_OUT 1
+
+#define PM860X_DET_HEADSET (1 << 0)
+#define PM860X_DET_MIC (1 << 1)
+#define PM860X_DET_HOOK (1 << 2)
+#define PM860X_SHORT_HEADSET (1 << 3)
+#define PM860X_SHORT_LINEOUT (1 << 4)
+#define PM860X_DET_MASK 0x1F
+
+extern int pm860x_hs_jack_detect(struct snd_soc_codec *, struct snd_soc_jack *,
+ int, int, int, int);
+extern int pm860x_mic_jack_detect(struct snd_soc_codec *, struct snd_soc_jack *,
+ int);
+
+#endif /* __88PM860X_H */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
new file mode 100644
index 00000000..af1c3a94
--- /dev/null
+++ b/sound/soc/codecs/Kconfig
@@ -0,0 +1,402 @@
+# Helper to resolve issues with configs that have SPI enabled but I2C
+# modular, meaning we can't build the codec driver in with I2C support.
+# We use an ordered list of conditional defaults to pick the appropriate
+# setting - SPI can't be modular so that case doesn't need to be covered.
+config SND_SOC_I2C_AND_SPI
+ tristate
+ default m if I2C=m
+ default y if I2C=y
+ default y if SPI_MASTER=y
+
+config SND_SOC_ALL_CODECS
+ tristate "Build all ASoC CODEC drivers"
+ select SND_SOC_88PM860X if MFD_88PM860X
+ select SND_SOC_L3
+ select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS
+ select SND_SOC_AD1836 if SPI_MASTER
+ select SND_SOC_AD193X if SND_SOC_I2C_AND_SPI
+ select SND_SOC_AD1980 if SND_SOC_AC97_BUS
+ select SND_SOC_AD73311
+ select SND_SOC_ADS117X
+ select SND_SOC_AK4104 if SPI_MASTER
+ select SND_SOC_AK4535 if I2C
+ select SND_SOC_AK4641 if I2C
+ select SND_SOC_AK4642 if I2C
+ select SND_SOC_AK4671 if I2C
+ select SND_SOC_ALC5623 if I2C
+ select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
+ select SND_SOC_CS42L51 if I2C
+ select SND_SOC_CS4270 if I2C
+ select SND_SOC_CS4271 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_CX20442
+ select SND_SOC_DA7210 if I2C
+ select SND_SOC_DFBMCS320
+ select SND_SOC_JZ4740_CODEC if SOC_JZ4740
+ select SND_SOC_LM4857 if I2C
+ select SND_SOC_CS42888 if I2C
+ select SND_SOC_MAX98088 if I2C
+ select SND_SOC_MAX98095 if I2C
+ select SND_SOC_MAX9850 if I2C
+ select SND_SOC_MAX9877 if I2C
+ select SND_SOC_PCM3008
+ select SND_SOC_SGTL5000 if I2C
+ select SND_SOC_SN95031 if INTEL_SCU_IPC
+ select SND_SOC_SPDIF
+ select SND_SOC_SSM2602 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
+ select SND_SOC_TLV320AIC23 if I2C
+ select SND_SOC_TLV320AIC26 if SPI_MASTER
+ select SND_SOC_TVL320AIC32X4 if I2C
+ select SND_SOC_TLV320AIC3X if I2C
+ select SND_SOC_TPA6130A2 if I2C
+ select SND_SOC_TLV320DAC33 if I2C
+ select SND_SOC_TWL4030 if TWL4030_CORE
+ select SND_SOC_TWL6040 if TWL4030_CORE
+ select SND_SOC_UDA134X
+ select SND_SOC_UDA1380 if I2C
+ select SND_SOC_WL1273 if MFD_WL1273_CORE
+ select SND_SOC_WM1250_EV1 if I2C
+ select SND_SOC_WM2000 if I2C
+ select SND_SOC_WM8350 if MFD_WM8350
+ select SND_SOC_WM8400 if MFD_WM8400
+ select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8523 if I2C
+ select SND_SOC_WM8580 if I2C
+ select SND_SOC_WM8711 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8727
+ select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8737 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8741 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8770 if SPI_MASTER
+ select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8804 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8900 if I2C
+ select SND_SOC_WM8903 if I2C
+ select SND_SOC_WM8904 if I2C
+ select SND_SOC_WM8915 if I2C
+ select SND_SOC_WM8940 if I2C
+ select SND_SOC_WM8955 if I2C
+ select SND_SOC_WM8960 if I2C
+ select SND_SOC_WM8961 if I2C
+ select SND_SOC_WM8962 if I2C
+ select SND_SOC_WM8971 if I2C
+ select SND_SOC_WM8974 if I2C
+ select SND_SOC_WM8978 if I2C
+ select SND_SOC_WM8985 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8990 if I2C
+ select SND_SOC_WM8991 if I2C
+ select SND_SOC_WM8993 if I2C
+ select SND_SOC_WM8994 if MFD_WM8994
+ select SND_SOC_WM8995 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM9081 if I2C
+ select SND_SOC_WM9090 if I2C
+ select SND_SOC_WM9705 if SND_SOC_AC97_BUS
+ select SND_SOC_WM9712 if SND_SOC_AC97_BUS
+ select SND_SOC_WM9713 if SND_SOC_AC97_BUS
+ help
+ Normally ASoC codec drivers are only built if a machine driver which
+ uses them is also built since they are only usable with a machine
+ driver. Selecting this option will allow these drivers to be built
+ without an explicit machine driver for test and development purposes.
+
+ Support for the bus types used to access the codecs to be built must
+ be selected separately.
+
+ If unsure select "N".
+
+config SND_SOC_88PM860X
+ tristate
+
+config SND_SOC_WM_HUBS
+ tristate
+ default y if SND_SOC_WM8993=y || SND_SOC_WM8994=y
+ default m if SND_SOC_WM8993=m || SND_SOC_WM8994=m
+
+config SND_SOC_AC97_CODEC
+ tristate
+ select SND_AC97_CODEC
+
+config SND_SOC_AD1836
+ tristate
+
+config SND_SOC_AD193X
+ tristate
+
+config SND_SOC_AD1980
+ tristate
+
+config SND_SOC_AD73311
+ tristate
+
+config SND_SOC_ADS117X
+ tristate
+
+config SND_SOC_AK4104
+ tristate
+
+config SND_SOC_AK4535
+ tristate
+
+config SND_SOC_AK4641
+ tristate
+
+config SND_SOC_AK4642
+ tristate
+
+config SND_SOC_AK4671
+ tristate
+
+config SND_SOC_ALC5623
+ tristate
+
+config SND_SOC_CQ0093VC
+ tristate
+
+config SND_SOC_CS42L51
+ tristate
+
+# Cirrus Logic CS4270 Codec
+config SND_SOC_CS4270
+ tristate
+
+# Cirrus Logic CS4270 Codec VD = 3.3V Errata
+# Select if you are affected by the errata where the part will not function
+# if MCLK divide-by-1.5 is selected and VD is set to 3.3V. The driver will
+# not select any sample rates that require MCLK to be divided by 1.5.
+config SND_SOC_CS4270_VD33_ERRATA
+ bool
+ depends on SND_SOC_CS4270
+
+config SND_SOC_CS4271
+ tristate
+
+config SND_SOC_CX20442
+ tristate
+
+config SND_SOC_JZ4740_CODEC
+ tristate
+
+config SND_SOC_L3
+ tristate
+
+config SND_SOC_DA7210
+ tristate
+
+config SND_SOC_DFBMCS320
+ tristate
+
+config SND_SOC_DMIC
+ tristate
+
+config SND_SOC_MAX98088
+ tristate
+
+config SND_SOC_MAX98095
+ tristate
+
+config SND_SOC_MAX9850
+ tristate
+
+config SND_SOC_MXC_HDMI
+ tristate
+
+config SND_SOC_MXC_SPDIF
+ tristate
+
+config SND_SOC_PCM3008
+ tristate
+
+#Freescale sgtl5000 codec
+config SND_SOC_SGTL5000
+ tristate
+
+config SND_SOC_SN95031
+ tristate
+
+#CLI CS42888 codec
+config SND_SOC_CS42888
+ tristate
+
+config SND_SOC_SI4763
+ tristate
+
+config SND_SOC_SPDIF
+ tristate
+
+config SND_SOC_SSM2602
+ tristate
+
+config SND_SOC_STAC9766
+ tristate
+
+config SND_SOC_TLV320AIC23
+ tristate
+
+config SND_SOC_TLV320AIC26
+ tristate "TI TLV320AIC26 Codec support" if SND_SOC_OF_SIMPLE
+ depends on SPI
+
+config SND_SOC_TVL320AIC32X4
+ tristate
+
+config SND_SOC_TLV320AIC3X
+ tristate
+
+config SND_SOC_TLV320DAC33
+ tristate
+
+config SND_SOC_TWL4030
+ select TWL4030_CODEC
+ tristate
+
+config SND_SOC_TWL6040
+ tristate
+
+config SND_SOC_UDA134X
+ tristate
+
+config SND_SOC_UDA1380
+ tristate
+
+config SND_SOC_WL1273
+ tristate
+
+config SND_SOC_WM1250_EV1
+ tristate
+
+config SND_SOC_WM8350
+ tristate
+
+config SND_SOC_WM8400
+ tristate
+
+config SND_SOC_WM8510
+ tristate
+
+config SND_SOC_WM8523
+ tristate
+
+config SND_SOC_WM8580
+ tristate
+
+config SND_SOC_WM8711
+ tristate
+
+config SND_SOC_WM8727
+ tristate
+
+config SND_SOC_WM8728
+ tristate
+
+config SND_SOC_WM8731
+ tristate
+
+config SND_SOC_WM8737
+ tristate
+
+config SND_SOC_WM8741
+ tristate
+
+config SND_SOC_WM8750
+ tristate
+
+config SND_SOC_WM8753
+ tristate
+
+config SND_SOC_WM8770
+ tristate
+
+config SND_SOC_WM8776
+ tristate
+
+config SND_SOC_WM8804
+ tristate
+
+config SND_SOC_WM8900
+ tristate
+
+config SND_SOC_WM8903
+ tristate
+
+config SND_SOC_WM8904
+ tristate
+
+config SND_SOC_WM8915
+ tristate
+
+config SND_SOC_WM8940
+ tristate
+
+config SND_SOC_WM8955
+ tristate
+
+config SND_SOC_WM8960
+ tristate
+
+config SND_SOC_WM8961
+ tristate
+
+config SND_SOC_WM8962
+ tristate
+
+config SND_SOC_WM8971
+ tristate
+
+config SND_SOC_WM8974
+ tristate
+
+config SND_SOC_WM8978
+ tristate
+
+config SND_SOC_WM8985
+ tristate
+
+config SND_SOC_WM8988
+ tristate
+
+config SND_SOC_WM8990
+ tristate
+
+config SND_SOC_WM8991
+ tristate
+
+config SND_SOC_WM8993
+ tristate
+
+config SND_SOC_WM8994
+ tristate
+
+config SND_SOC_WM8995
+ tristate
+
+config SND_SOC_WM9081
+ tristate
+
+config SND_SOC_WM9705
+ tristate
+
+config SND_SOC_WM9712
+ tristate
+
+config SND_SOC_WM9713
+ tristate
+
+# Amp
+config SND_SOC_LM4857
+ tristate
+
+config SND_SOC_MAX9877
+ tristate
+
+config SND_SOC_TPA6130A2
+ tristate
+
+config SND_SOC_WM2000
+ tristate
+
+config SND_SOC_WM9090
+ tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
new file mode 100644
index 00000000..e08ff304
--- /dev/null
+++ b/sound/soc/codecs/Makefile
@@ -0,0 +1,189 @@
+snd-soc-88pm860x-objs := 88pm860x-codec.o
+snd-soc-ac97-objs := ac97.o
+snd-soc-ad1836-objs := ad1836.o
+snd-soc-ad193x-objs := ad193x.o
+snd-soc-ad1980-objs := ad1980.o
+snd-soc-ad73311-objs := ad73311.o
+snd-soc-ads117x-objs := ads117x.o
+snd-soc-ak4104-objs := ak4104.o
+snd-soc-ak4535-objs := ak4535.o
+snd-soc-ak4641-objs := ak4641.o
+snd-soc-ak4642-objs := ak4642.o
+snd-soc-ak4671-objs := ak4671.o
+snd-soc-cq93vc-objs := cq93vc.o
+snd-soc-cs42l51-objs := cs42l51.o
+snd-soc-cs4270-objs := cs4270.o
+snd-soc-cs4271-objs := cs4271.o
+snd-soc-cx20442-objs := cx20442.o
+snd-soc-da7210-objs := da7210.o
+snd-soc-dfbmcs320-objs := dfbmcs320.o
+snd-soc-mxc-hdmi-objs := mxc_hdmi.o
+snd-soc-mxc-spdif-objs := mxc_spdif.o
+snd-soc-dmic-objs := dmic.o
+snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
+snd-soc-max98095-objs := max98095.o
+snd-soc-max9850-objs := max9850.o
+snd-soc-pcm3008-objs := pcm3008.o
+snd-soc-sgtl5000-objs := sgtl5000.o
+snd-soc-cs42888-objs := cs42888.o
+snd-soc-si4763-objs := si4763.o
+snd-soc-alc5623-objs := alc5623.o
+snd-soc-sn95031-objs := sn95031.o
+snd-soc-spdif-objs := spdif_transciever.o
+snd-soc-ssm2602-objs := ssm2602.o
+snd-soc-stac9766-objs := stac9766.o
+snd-soc-tlv320aic23-objs := tlv320aic23.o
+snd-soc-tlv320aic26-objs := tlv320aic26.o
+snd-soc-tlv320aic3x-objs := tlv320aic3x.o
+snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o
+snd-soc-tlv320dac33-objs := tlv320dac33.o
+snd-soc-twl4030-objs := twl4030.o
+snd-soc-twl6040-objs := twl6040.o
+snd-soc-uda134x-objs := uda134x.o
+snd-soc-uda1380-objs := uda1380.o
+snd-soc-wl1273-objs := wl1273.o
+snd-soc-wm1250-ev1-objs := wm1250-ev1.o
+snd-soc-wm8350-objs := wm8350.o
+snd-soc-wm8400-objs := wm8400.o
+snd-soc-wm8510-objs := wm8510.o
+snd-soc-wm8523-objs := wm8523.o
+snd-soc-wm8580-objs := wm8580.o
+snd-soc-wm8711-objs := wm8711.o
+snd-soc-wm8727-objs := wm8727.o
+snd-soc-wm8728-objs := wm8728.o
+snd-soc-wm8731-objs := wm8731.o
+snd-soc-wm8737-objs := wm8737.o
+snd-soc-wm8741-objs := wm8741.o
+snd-soc-wm8750-objs := wm8750.o
+snd-soc-wm8753-objs := wm8753.o
+snd-soc-wm8770-objs := wm8770.o
+snd-soc-wm8776-objs := wm8776.o
+snd-soc-wm8804-objs := wm8804.o
+snd-soc-wm8900-objs := wm8900.o
+snd-soc-wm8903-objs := wm8903.o
+snd-soc-wm8904-objs := wm8904.o
+snd-soc-wm8915-objs := wm8915.o
+snd-soc-wm8940-objs := wm8940.o
+snd-soc-wm8955-objs := wm8955.o
+snd-soc-wm8960-objs := wm8960.o
+snd-soc-wm8961-objs := wm8961.o
+snd-soc-wm8962-objs := wm8962.o
+snd-soc-wm8971-objs := wm8971.o
+snd-soc-wm8974-objs := wm8974.o
+snd-soc-wm8978-objs := wm8978.o
+snd-soc-wm8985-objs := wm8985.o
+snd-soc-wm8988-objs := wm8988.o
+snd-soc-wm8990-objs := wm8990.o
+snd-soc-wm8991-objs := wm8991.o
+snd-soc-wm8993-objs := wm8993.o
+snd-soc-wm8994-objs := wm8994.o wm8994-tables.o wm8958-dsp2.o
+snd-soc-wm8995-objs := wm8995.o
+snd-soc-wm9081-objs := wm9081.o
+snd-soc-wm9705-objs := wm9705.o
+snd-soc-wm9712-objs := wm9712.o
+snd-soc-wm9713-objs := wm9713.o
+snd-soc-wm-hubs-objs := wm_hubs.o
+snd-soc-jz4740-codec-objs := jz4740.o
+
+# Amp
+snd-soc-lm4857-objs := lm4857.o
+snd-soc-max9877-objs := max9877.o
+snd-soc-tpa6130a2-objs := tpa6130a2.o
+snd-soc-wm2000-objs := wm2000.o
+snd-soc-wm9090-objs := wm9090.o
+
+obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o
+obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
+obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o
+obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o
+obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
+obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
+obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o
+obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o
+obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
+obj-$(CONFIG_SND_SOC_MXC_HDMI) += snd-soc-mxc-hdmi.o
+obj-$(CONFIG_SND_SOC_MXC_SPDIF) += snd-soc-mxc-spdif.o
+obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o
+obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
+obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
+obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o
+obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
+obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
+obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
+obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o
+obj-$(CONFIG_SND_SOC_CS42888) += snd-soc-cs42888.o
+obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
+obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
+obj-$(CONFIG_SND_SOC_DFBMCS320) += snd-soc-dfbmcs320.o
+obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o
+obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
+obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o
+obj-$(CONFIG_SND_SOC_MAX98095) += snd-soc-max98095.o
+obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o
+obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
+obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
+obj-$(CONFIG_SND_SOC_SI4763) += snd-soc-si4763.o
+obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o
+obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
+obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
+obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o
+obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
+obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
+obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o
+obj-$(CONFIG_SND_SOC_TVL320AIC32X4) += snd-soc-tlv320aic32x4.o
+obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o
+obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o
+obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o
+obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o
+obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o
+obj-$(CONFIG_SND_SOC_WL1273) += snd-soc-wl1273.o
+obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o
+obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o
+obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o
+obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o
+obj-$(CONFIG_SND_SOC_WM8523) += snd-soc-wm8523.o
+obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o
+obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o
+obj-$(CONFIG_SND_SOC_WM8727) += snd-soc-wm8727.o
+obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o
+obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o
+obj-$(CONFIG_SND_SOC_WM8737) += snd-soc-wm8737.o
+obj-$(CONFIG_SND_SOC_WM8741) += snd-soc-wm8741.o
+obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o
+obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
+obj-$(CONFIG_SND_SOC_WM8770) += snd-soc-wm8770.o
+obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o
+obj-$(CONFIG_SND_SOC_WM8804) += snd-soc-wm8804.o
+obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o
+obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o
+obj-$(CONFIG_SND_SOC_WM8904) += snd-soc-wm8904.o
+obj-$(CONFIG_SND_SOC_WM8915) += snd-soc-wm8915.o
+obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o
+obj-$(CONFIG_SND_SOC_WM8955) += snd-soc-wm8955.o
+obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o
+obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o
+obj-$(CONFIG_SND_SOC_WM8962) += snd-soc-wm8962.o
+obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o
+obj-$(CONFIG_SND_SOC_WM8974) += snd-soc-wm8974.o
+obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o
+obj-$(CONFIG_SND_SOC_WM8985) += snd-soc-wm8985.o
+obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o
+obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
+obj-$(CONFIG_SND_SOC_WM8991) += snd-soc-wm8991.o
+obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o
+obj-$(CONFIG_SND_SOC_WM8994) += snd-soc-wm8994.o
+obj-$(CONFIG_SND_SOC_WM8995) += snd-soc-wm8995.o
+obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o
+obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o
+obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
+obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
+obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
+
+# Amp
+obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o
+obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
+obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o
+obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o
+obj-$(CONFIG_SND_SOC_WM9090) += snd-soc-wm9090.o
diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c
new file mode 100644
index 00000000..3c087936
--- /dev/null
+++ b/sound/soc/codecs/ac97.c
@@ -0,0 +1,165 @@
+/*
+ * ac97.c -- ALSA Soc AC97 codec support
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ * Generic AC97 support.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+static int ac97_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE;
+ return snd_ac97_set_rate(codec->ac97, reg, runtime->rate);
+}
+
+#define STD_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops ac97_dai_ops = {
+ .prepare = ac97_prepare,
+};
+
+static struct snd_soc_dai_driver ac97_dai = {
+ .name = "ac97-hifi",
+ .ac97_control = 1,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = STD_AC97_RATES,
+ .formats = SND_SOC_STD_AC97_FMTS,},
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = STD_AC97_RATES,
+ .formats = SND_SOC_STD_AC97_FMTS,},
+ .ops = &ac97_dai_ops,
+};
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return soc_ac97_ops.read(codec->ac97, reg);
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ return 0;
+}
+
+static int ac97_soc_probe(struct snd_soc_codec *codec)
+{
+ struct snd_ac97_bus *ac97_bus;
+ struct snd_ac97_template ac97_template;
+ int ret;
+
+ /* add codec as bus device for standard ac97 */
+ ret = snd_ac97_bus(codec->card->snd_card, 0, &soc_ac97_ops, NULL, &ac97_bus);
+ if (ret < 0)
+ return ret;
+
+ memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
+ ret = snd_ac97_mixer(ac97_bus, &ac97_template, &codec->ac97);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int ac97_soc_remove(struct snd_soc_codec *codec)
+{
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ac97_soc_suspend(struct snd_soc_codec *codec, pm_message_t msg)
+{
+ snd_ac97_suspend(codec->ac97);
+
+ return 0;
+}
+
+static int ac97_soc_resume(struct snd_soc_codec *codec)
+{
+ snd_ac97_resume(codec->ac97);
+
+ return 0;
+}
+#else
+#define ac97_soc_suspend NULL
+#define ac97_soc_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_ac97 = {
+ .write = ac97_write,
+ .read = ac97_read,
+ .probe = ac97_soc_probe,
+ .remove = ac97_soc_remove,
+ .suspend = ac97_soc_suspend,
+ .resume = ac97_soc_resume,
+};
+
+static __devinit int ac97_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_ac97, &ac97_dai, 1);
+}
+
+static int __devexit ac97_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver ac97_codec_driver = {
+ .driver = {
+ .name = "ac97-codec",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = ac97_probe,
+ .remove = __devexit_p(ac97_remove),
+};
+
+static int __init ac97_init(void)
+{
+ return platform_driver_register(&ac97_codec_driver);
+}
+module_init(ac97_init);
+
+static void __exit ac97_exit(void)
+{
+ platform_driver_unregister(&ac97_codec_driver);
+}
+module_exit(ac97_exit);
+
+MODULE_DESCRIPTION("Soc Generic AC97 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ac97-codec");
diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c
new file mode 100644
index 00000000..754c4964
--- /dev/null
+++ b/sound/soc/codecs/ad1836.c
@@ -0,0 +1,338 @@
+/*
+ * File: sound/soc/codecs/ad1836.c
+ * Author: Barry Song <Barry.Song@analog.com>
+ *
+ * Created: Aug 04 2009
+ * Description: Driver for AD1836 sound chip
+ *
+ * Modified:
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <linux/spi/spi.h>
+#include "ad1836.h"
+
+/* codec private data */
+struct ad1836_priv {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+};
+
+/*
+ * AD1836 volume/mute/de-emphasis etc. controls
+ */
+static const char *ad1836_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"};
+
+static const struct soc_enum ad1836_deemp_enum =
+ SOC_ENUM_SINGLE(AD1836_DAC_CTRL1, 8, 4, ad1836_deemp);
+
+static const struct snd_kcontrol_new ad1836_snd_controls[] = {
+ /* DAC volume control */
+ SOC_DOUBLE_R("DAC1 Volume", AD1836_DAC_L1_VOL,
+ AD1836_DAC_R1_VOL, 0, 0x3FF, 0),
+ SOC_DOUBLE_R("DAC2 Volume", AD1836_DAC_L2_VOL,
+ AD1836_DAC_R2_VOL, 0, 0x3FF, 0),
+ SOC_DOUBLE_R("DAC3 Volume", AD1836_DAC_L3_VOL,
+ AD1836_DAC_R3_VOL, 0, 0x3FF, 0),
+
+ /* ADC switch control */
+ SOC_DOUBLE("ADC1 Switch", AD1836_ADC_CTRL2, AD1836_ADCL1_MUTE,
+ AD1836_ADCR1_MUTE, 1, 1),
+ SOC_DOUBLE("ADC2 Switch", AD1836_ADC_CTRL2, AD1836_ADCL2_MUTE,
+ AD1836_ADCR2_MUTE, 1, 1),
+
+ /* DAC switch control */
+ SOC_DOUBLE("DAC1 Switch", AD1836_DAC_CTRL2, AD1836_DACL1_MUTE,
+ AD1836_DACR1_MUTE, 1, 1),
+ SOC_DOUBLE("DAC2 Switch", AD1836_DAC_CTRL2, AD1836_DACL2_MUTE,
+ AD1836_DACR2_MUTE, 1, 1),
+ SOC_DOUBLE("DAC3 Switch", AD1836_DAC_CTRL2, AD1836_DACL3_MUTE,
+ AD1836_DACR3_MUTE, 1, 1),
+
+ /* ADC high-pass filter */
+ SOC_SINGLE("ADC High Pass Filter Switch", AD1836_ADC_CTRL1,
+ AD1836_ADC_HIGHPASS_FILTER, 1, 0),
+
+ /* DAC de-emphasis */
+ SOC_ENUM("Playback Deemphasis", ad1836_deemp_enum),
+};
+
+static const struct snd_soc_dapm_widget ad1836_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DAC", "Playback", AD1836_DAC_CTRL1,
+ AD1836_DAC_POWERDOWN, 1),
+ SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1836_ADC_CTRL1,
+ AD1836_ADC_POWERDOWN, 1, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("DAC1OUT"),
+ SND_SOC_DAPM_OUTPUT("DAC2OUT"),
+ SND_SOC_DAPM_OUTPUT("DAC3OUT"),
+ SND_SOC_DAPM_INPUT("ADC1IN"),
+ SND_SOC_DAPM_INPUT("ADC2IN"),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+ { "DAC", NULL, "ADC_PWR" },
+ { "ADC", NULL, "ADC_PWR" },
+ { "DAC1OUT", "DAC1 Switch", "DAC" },
+ { "DAC2OUT", "DAC2 Switch", "DAC" },
+ { "DAC3OUT", "DAC3 Switch", "DAC" },
+ { "ADC", "ADC1 Switch", "ADC1IN" },
+ { "ADC", "ADC2 Switch", "ADC2IN" },
+};
+
+/*
+ * DAI ops entries
+ */
+
+static int ad1836_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ /* at present, we support adc aux mode to interface with
+ * blackfin sport tdm mode
+ */
+ case SND_SOC_DAIFMT_DSP_A:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ /* ALCLK,ABCLK are both output, AD1836 can only be master */
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ad1836_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ int word_len = 0;
+
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ word_len = AD1836_WORD_LEN_16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ word_len = AD1836_WORD_LEN_20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S32_LE:
+ word_len = AD1836_WORD_LEN_24;
+ break;
+ }
+
+ snd_soc_update_bits(codec, AD1836_DAC_CTRL1, AD1836_DAC_WORD_LEN_MASK,
+ word_len << AD1836_DAC_WORD_LEN_OFFSET);
+
+ snd_soc_update_bits(codec, AD1836_ADC_CTRL2, AD1836_ADC_WORD_LEN_MASK,
+ word_len << AD1836_ADC_WORD_OFFSET);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ad1836_soc_suspend(struct snd_soc_codec *codec,
+ pm_message_t state)
+{
+ /* reset clock control mode */
+ u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
+ adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK;
+
+ return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
+}
+
+static int ad1836_soc_resume(struct snd_soc_codec *codec)
+{
+ /* restore clock control mode */
+ u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
+ adc_ctrl2 |= AD1836_ADC_AUX;
+
+ return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
+}
+#else
+#define ad1836_soc_suspend NULL
+#define ad1836_soc_resume NULL
+#endif
+
+static struct snd_soc_dai_ops ad1836_dai_ops = {
+ .hw_params = ad1836_hw_params,
+ .set_fmt = ad1836_set_dai_fmt,
+};
+
+/* codec DAI instance */
+static struct snd_soc_dai_driver ad1836_dai = {
+ .name = "ad1836-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 6,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 4,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .ops = &ad1836_dai_ops,
+};
+
+static int ad1836_probe(struct snd_soc_codec *codec)
+{
+ struct ad1836_priv *ad1836 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret = 0;
+
+ codec->control_data = ad1836->control_data;
+ ret = snd_soc_codec_set_cache_io(codec, 4, 12, SND_SOC_SPI);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to set cache I/O: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* default setting for ad1836 */
+ /* de-emphasis: 48kHz, power-on dac */
+ snd_soc_write(codec, AD1836_DAC_CTRL1, 0x300);
+ /* unmute dac channels */
+ snd_soc_write(codec, AD1836_DAC_CTRL2, 0x0);
+ /* high-pass filter enable, power-on adc */
+ snd_soc_write(codec, AD1836_ADC_CTRL1, 0x100);
+ /* unmute adc channles, adc aux mode */
+ snd_soc_write(codec, AD1836_ADC_CTRL2, 0x180);
+ /* left/right diff:PGA/MUX */
+ snd_soc_write(codec, AD1836_ADC_CTRL3, 0x3A);
+ /* volume */
+ snd_soc_write(codec, AD1836_DAC_L1_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_R1_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_L2_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_R2_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_L3_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_R3_VOL, 0x3FF);
+
+ snd_soc_add_controls(codec, ad1836_snd_controls,
+ ARRAY_SIZE(ad1836_snd_controls));
+ snd_soc_dapm_new_controls(dapm, ad1836_dapm_widgets,
+ ARRAY_SIZE(ad1836_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
+
+ return ret;
+}
+
+/* power down chip */
+static int ad1836_remove(struct snd_soc_codec *codec)
+{
+ /* reset clock control mode */
+ u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
+ adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK;
+
+ return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ad1836 = {
+ .probe = ad1836_probe,
+ .remove = ad1836_remove,
+ .suspend = ad1836_soc_suspend,
+ .resume = ad1836_soc_resume,
+ .reg_cache_size = AD1836_NUM_REGS,
+ .reg_word_size = sizeof(u16),
+};
+
+static int __devinit ad1836_spi_probe(struct spi_device *spi)
+{
+ struct ad1836_priv *ad1836;
+ int ret;
+
+ ad1836 = kzalloc(sizeof(struct ad1836_priv), GFP_KERNEL);
+ if (ad1836 == NULL)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, ad1836);
+ ad1836->control_data = spi;
+ ad1836->control_type = SND_SOC_SPI;
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_ad1836, &ad1836_dai, 1);
+ if (ret < 0)
+ kfree(ad1836);
+ return ret;
+}
+
+static int __devexit ad1836_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver ad1836_spi_driver = {
+ .driver = {
+ .name = "ad1836-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ad1836_spi_probe,
+ .remove = __devexit_p(ad1836_spi_remove),
+};
+
+static int __init ad1836_init(void)
+{
+ int ret;
+
+ ret = spi_register_driver(&ad1836_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register ad1836 SPI driver: %d\n",
+ ret);
+ }
+
+ return ret;
+}
+module_init(ad1836_init);
+
+static void __exit ad1836_exit(void)
+{
+ spi_unregister_driver(&ad1836_spi_driver);
+}
+module_exit(ad1836_exit);
+
+MODULE_DESCRIPTION("ASoC ad1836 driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ad1836.h b/sound/soc/codecs/ad1836.h
new file mode 100644
index 00000000..9d6a3f8f
--- /dev/null
+++ b/sound/soc/codecs/ad1836.h
@@ -0,0 +1,69 @@
+/*
+ * File: sound/soc/codecs/ad1836.h
+ * Based on:
+ * Author: Barry Song <Barry.Song@analog.com>
+ *
+ * Created: Aug 04, 2009
+ * Description: definitions for AD1836 registers
+ *
+ * Modified:
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __AD1836_H__
+#define __AD1836_H__
+
+#define AD1836_DAC_CTRL1 0
+#define AD1836_DAC_POWERDOWN 2
+#define AD1836_DAC_SERFMT_MASK 0xE0
+#define AD1836_DAC_SERFMT_PCK256 (0x4 << 5)
+#define AD1836_DAC_SERFMT_PCK128 (0x5 << 5)
+#define AD1836_DAC_WORD_LEN_MASK 0x18
+#define AD1836_DAC_WORD_LEN_OFFSET 3
+
+#define AD1836_DAC_CTRL2 1
+#define AD1836_DACL1_MUTE 0
+#define AD1836_DACR1_MUTE 1
+#define AD1836_DACL2_MUTE 2
+#define AD1836_DACR2_MUTE 3
+#define AD1836_DACL3_MUTE 4
+#define AD1836_DACR3_MUTE 5
+
+#define AD1836_DAC_L1_VOL 2
+#define AD1836_DAC_R1_VOL 3
+#define AD1836_DAC_L2_VOL 4
+#define AD1836_DAC_R2_VOL 5
+#define AD1836_DAC_L3_VOL 6
+#define AD1836_DAC_R3_VOL 7
+
+#define AD1836_ADC_CTRL1 12
+#define AD1836_ADC_POWERDOWN 7
+#define AD1836_ADC_HIGHPASS_FILTER 8
+
+#define AD1836_ADC_CTRL2 13
+#define AD1836_ADCL1_MUTE 0
+#define AD1836_ADCR1_MUTE 1
+#define AD1836_ADCL2_MUTE 2
+#define AD1836_ADCR2_MUTE 3
+#define AD1836_ADC_WORD_LEN_MASK 0x30
+#define AD1836_ADC_WORD_OFFSET 5
+#define AD1836_ADC_SERFMT_MASK (7 << 6)
+#define AD1836_ADC_SERFMT_PCK256 (0x4 << 6)
+#define AD1836_ADC_SERFMT_PCK128 (0x5 << 6)
+#define AD1836_ADC_AUX (0x6 << 6)
+
+#define AD1836_ADC_CTRL3 14
+
+#define AD1836_NUM_REGS 16
+
+#define AD1836_WORD_LEN_24 0x0
+#define AD1836_WORD_LEN_20 0x1
+#define AD1836_WORD_LEN_16 0x2
+
+#endif
diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c
new file mode 100644
index 00000000..f1a8be58
--- /dev/null
+++ b/sound/soc/codecs/ad193x.c
@@ -0,0 +1,517 @@
+/*
+ * AD193X Audio Codec driver supporting AD1936/7/8/9
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include "ad193x.h"
+
+/* codec private data */
+struct ad193x_priv {
+ enum snd_soc_control_type control_type;
+ int sysclk;
+};
+
+/* ad193x register cache & default register settings */
+static const u8 ad193x_reg[AD193X_NUM_REGS] = {
+ 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0,
+};
+
+/*
+ * AD193X volume/mute/de-emphasis etc. controls
+ */
+static const char *ad193x_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"};
+
+static const struct soc_enum ad193x_deemp_enum =
+ SOC_ENUM_SINGLE(AD193X_DAC_CTRL2, 1, 4, ad193x_deemp);
+
+static const struct snd_kcontrol_new ad193x_snd_controls[] = {
+ /* DAC volume control */
+ SOC_DOUBLE_R("DAC1 Volume", AD193X_DAC_L1_VOL,
+ AD193X_DAC_R1_VOL, 0, 0xFF, 1),
+ SOC_DOUBLE_R("DAC2 Volume", AD193X_DAC_L2_VOL,
+ AD193X_DAC_R2_VOL, 0, 0xFF, 1),
+ SOC_DOUBLE_R("DAC3 Volume", AD193X_DAC_L3_VOL,
+ AD193X_DAC_R3_VOL, 0, 0xFF, 1),
+ SOC_DOUBLE_R("DAC4 Volume", AD193X_DAC_L4_VOL,
+ AD193X_DAC_R4_VOL, 0, 0xFF, 1),
+
+ /* ADC switch control */
+ SOC_DOUBLE("ADC1 Switch", AD193X_ADC_CTRL0, AD193X_ADCL1_MUTE,
+ AD193X_ADCR1_MUTE, 1, 1),
+ SOC_DOUBLE("ADC2 Switch", AD193X_ADC_CTRL0, AD193X_ADCL2_MUTE,
+ AD193X_ADCR2_MUTE, 1, 1),
+
+ /* DAC switch control */
+ SOC_DOUBLE("DAC1 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL1_MUTE,
+ AD193X_DACR1_MUTE, 1, 1),
+ SOC_DOUBLE("DAC2 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL2_MUTE,
+ AD193X_DACR2_MUTE, 1, 1),
+ SOC_DOUBLE("DAC3 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL3_MUTE,
+ AD193X_DACR3_MUTE, 1, 1),
+ SOC_DOUBLE("DAC4 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL4_MUTE,
+ AD193X_DACR4_MUTE, 1, 1),
+
+ /* ADC high-pass filter */
+ SOC_SINGLE("ADC High Pass Filter Switch", AD193X_ADC_CTRL0,
+ AD193X_ADC_HIGHPASS_FILTER, 1, 0),
+
+ /* DAC de-emphasis */
+ SOC_ENUM("Playback Deemphasis", ad193x_deemp_enum),
+};
+
+static const struct snd_soc_dapm_widget ad193x_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DAC", "Playback", AD193X_DAC_CTRL0, 0, 1),
+ SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_SUPPLY("PLL_PWR", AD193X_PLL_CLK_CTRL0, 0, 1, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("ADC_PWR", AD193X_ADC_CTRL0, 0, 1, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("DAC1OUT"),
+ SND_SOC_DAPM_OUTPUT("DAC2OUT"),
+ SND_SOC_DAPM_OUTPUT("DAC3OUT"),
+ SND_SOC_DAPM_OUTPUT("DAC4OUT"),
+ SND_SOC_DAPM_INPUT("ADC1IN"),
+ SND_SOC_DAPM_INPUT("ADC2IN"),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+ { "DAC", NULL, "PLL_PWR" },
+ { "ADC", NULL, "PLL_PWR" },
+ { "DAC", NULL, "ADC_PWR" },
+ { "ADC", NULL, "ADC_PWR" },
+ { "DAC1OUT", "DAC1 Switch", "DAC" },
+ { "DAC2OUT", "DAC2 Switch", "DAC" },
+ { "DAC3OUT", "DAC3 Switch", "DAC" },
+ { "DAC4OUT", "DAC4 Switch", "DAC" },
+ { "ADC", "ADC1 Switch", "ADC1IN" },
+ { "ADC", "ADC2 Switch", "ADC2IN" },
+};
+
+/*
+ * DAI ops entries
+ */
+
+static int ad193x_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int reg;
+
+ reg = snd_soc_read(codec, AD193X_DAC_CTRL2);
+ reg = (mute > 0) ? reg | AD193X_DAC_MASTER_MUTE : reg &
+ (~AD193X_DAC_MASTER_MUTE);
+ snd_soc_write(codec, AD193X_DAC_CTRL2, reg);
+
+ return 0;
+}
+
+static int ad193x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int width)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int dac_reg = snd_soc_read(codec, AD193X_DAC_CTRL1);
+ int adc_reg = snd_soc_read(codec, AD193X_ADC_CTRL2);
+
+ dac_reg &= ~AD193X_DAC_CHAN_MASK;
+ adc_reg &= ~AD193X_ADC_CHAN_MASK;
+
+ switch (slots) {
+ case 2:
+ dac_reg |= AD193X_DAC_2_CHANNELS << AD193X_DAC_CHAN_SHFT;
+ adc_reg |= AD193X_ADC_2_CHANNELS << AD193X_ADC_CHAN_SHFT;
+ break;
+ case 4:
+ dac_reg |= AD193X_DAC_4_CHANNELS << AD193X_DAC_CHAN_SHFT;
+ adc_reg |= AD193X_ADC_4_CHANNELS << AD193X_ADC_CHAN_SHFT;
+ break;
+ case 8:
+ dac_reg |= AD193X_DAC_8_CHANNELS << AD193X_DAC_CHAN_SHFT;
+ adc_reg |= AD193X_ADC_8_CHANNELS << AD193X_ADC_CHAN_SHFT;
+ break;
+ case 16:
+ dac_reg |= AD193X_DAC_16_CHANNELS << AD193X_DAC_CHAN_SHFT;
+ adc_reg |= AD193X_ADC_16_CHANNELS << AD193X_ADC_CHAN_SHFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AD193X_DAC_CTRL1, dac_reg);
+ snd_soc_write(codec, AD193X_ADC_CTRL2, adc_reg);
+
+ return 0;
+}
+
+static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int adc_reg1, adc_reg2, dac_reg;
+
+ adc_reg1 = snd_soc_read(codec, AD193X_ADC_CTRL1);
+ adc_reg2 = snd_soc_read(codec, AD193X_ADC_CTRL2);
+ dac_reg = snd_soc_read(codec, AD193X_DAC_CTRL1);
+
+ /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S
+ * with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A)
+ */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ adc_reg1 &= ~AD193X_ADC_SERFMT_MASK;
+ adc_reg1 |= AD193X_ADC_SERFMT_TDM;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ adc_reg1 &= ~AD193X_ADC_SERFMT_MASK;
+ adc_reg1 |= AD193X_ADC_SERFMT_AUX;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
+ adc_reg2 &= ~AD193X_ADC_LEFT_HIGH;
+ adc_reg2 &= ~AD193X_ADC_BCLK_INV;
+ dac_reg &= ~AD193X_DAC_LEFT_HIGH;
+ dac_reg &= ~AD193X_DAC_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */
+ adc_reg2 |= AD193X_ADC_LEFT_HIGH;
+ adc_reg2 &= ~AD193X_ADC_BCLK_INV;
+ dac_reg |= AD193X_DAC_LEFT_HIGH;
+ dac_reg &= ~AD193X_DAC_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */
+ adc_reg2 &= ~AD193X_ADC_LEFT_HIGH;
+ adc_reg2 |= AD193X_ADC_BCLK_INV;
+ dac_reg &= ~AD193X_DAC_LEFT_HIGH;
+ dac_reg |= AD193X_DAC_BCLK_INV;
+ break;
+
+ case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */
+ adc_reg2 |= AD193X_ADC_LEFT_HIGH;
+ adc_reg2 |= AD193X_ADC_BCLK_INV;
+ dac_reg |= AD193X_DAC_LEFT_HIGH;
+ dac_reg |= AD193X_DAC_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */
+ adc_reg2 |= AD193X_ADC_LCR_MASTER;
+ adc_reg2 |= AD193X_ADC_BCLK_MASTER;
+ dac_reg |= AD193X_DAC_LCR_MASTER;
+ dac_reg |= AD193X_DAC_BCLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */
+ adc_reg2 |= AD193X_ADC_LCR_MASTER;
+ adc_reg2 &= ~AD193X_ADC_BCLK_MASTER;
+ dac_reg |= AD193X_DAC_LCR_MASTER;
+ dac_reg &= ~AD193X_DAC_BCLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */
+ adc_reg2 &= ~AD193X_ADC_LCR_MASTER;
+ adc_reg2 |= AD193X_ADC_BCLK_MASTER;
+ dac_reg &= ~AD193X_DAC_LCR_MASTER;
+ dac_reg |= AD193X_DAC_BCLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */
+ adc_reg2 &= ~AD193X_ADC_LCR_MASTER;
+ adc_reg2 &= ~AD193X_ADC_BCLK_MASTER;
+ dac_reg &= ~AD193X_DAC_LCR_MASTER;
+ dac_reg &= ~AD193X_DAC_BCLK_MASTER;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AD193X_ADC_CTRL1, adc_reg1);
+ snd_soc_write(codec, AD193X_ADC_CTRL2, adc_reg2);
+ snd_soc_write(codec, AD193X_DAC_CTRL1, dac_reg);
+
+ return 0;
+}
+
+static int ad193x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
+ switch (freq) {
+ case 12288000:
+ case 18432000:
+ case 24576000:
+ case 36864000:
+ ad193x->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int ad193x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ int word_len = 0, reg = 0, master_rate = 0;
+
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ word_len = 3;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ word_len = 1;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S32_LE:
+ word_len = 0;
+ break;
+ }
+
+ switch (ad193x->sysclk) {
+ case 12288000:
+ master_rate = AD193X_PLL_INPUT_256;
+ break;
+ case 18432000:
+ master_rate = AD193X_PLL_INPUT_384;
+ break;
+ case 24576000:
+ master_rate = AD193X_PLL_INPUT_512;
+ break;
+ case 36864000:
+ master_rate = AD193X_PLL_INPUT_768;
+ break;
+ }
+
+ reg = snd_soc_read(codec, AD193X_PLL_CLK_CTRL0);
+ reg = (reg & AD193X_PLL_INPUT_MASK) | master_rate;
+ snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, reg);
+
+ reg = snd_soc_read(codec, AD193X_DAC_CTRL2);
+ reg = (reg & (~AD193X_DAC_WORD_LEN_MASK))
+ | (word_len << AD193X_DAC_WORD_LEN_SHFT);
+ snd_soc_write(codec, AD193X_DAC_CTRL2, reg);
+
+ reg = snd_soc_read(codec, AD193X_ADC_CTRL1);
+ reg = (reg & (~AD193X_ADC_WORD_LEN_MASK)) | word_len;
+ snd_soc_write(codec, AD193X_ADC_CTRL1, reg);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops ad193x_dai_ops = {
+ .hw_params = ad193x_hw_params,
+ .digital_mute = ad193x_mute,
+ .set_tdm_slot = ad193x_set_tdm_slot,
+ .set_sysclk = ad193x_set_dai_sysclk,
+ .set_fmt = ad193x_set_dai_fmt,
+};
+
+/* codec DAI instance */
+static struct snd_soc_dai_driver ad193x_dai = {
+ .name = "ad193x-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 4,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .ops = &ad193x_dai_ops,
+};
+
+static int ad193x_probe(struct snd_soc_codec *codec)
+{
+ struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ if (ad193x->control_type == SND_SOC_I2C)
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, ad193x->control_type);
+ else
+ ret = snd_soc_codec_set_cache_io(codec, 16, 8, ad193x->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ /* default setting for ad193x */
+
+ /* unmute dac channels */
+ snd_soc_write(codec, AD193X_DAC_CHNL_MUTE, 0x0);
+ /* de-emphasis: 48kHz, powedown dac */
+ snd_soc_write(codec, AD193X_DAC_CTRL2, 0x1A);
+ /* powerdown dac, dac in tdm mode */
+ snd_soc_write(codec, AD193X_DAC_CTRL0, 0x41);
+ /* high-pass filter enable */
+ snd_soc_write(codec, AD193X_ADC_CTRL0, 0x3);
+ /* sata delay=1, adc aux mode */
+ snd_soc_write(codec, AD193X_ADC_CTRL1, 0x43);
+ /* pll input: mclki/xi */
+ snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */
+ snd_soc_write(codec, AD193X_PLL_CLK_CTRL1, 0x04);
+
+ snd_soc_add_controls(codec, ad193x_snd_controls,
+ ARRAY_SIZE(ad193x_snd_controls));
+ snd_soc_dapm_new_controls(dapm, ad193x_dapm_widgets,
+ ARRAY_SIZE(ad193x_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
+
+ return ret;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ad193x = {
+ .probe = ad193x_probe,
+ .reg_cache_default = ad193x_reg,
+ .reg_cache_size = AD193X_NUM_REGS,
+ .reg_word_size = sizeof(u16),
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit ad193x_spi_probe(struct spi_device *spi)
+{
+ struct ad193x_priv *ad193x;
+ int ret;
+
+ ad193x = kzalloc(sizeof(struct ad193x_priv), GFP_KERNEL);
+ if (ad193x == NULL)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, ad193x);
+ ad193x->control_type = SND_SOC_SPI;
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_ad193x, &ad193x_dai, 1);
+ if (ret < 0)
+ kfree(ad193x);
+ return ret;
+}
+
+static int __devexit ad193x_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver ad193x_spi_driver = {
+ .driver = {
+ .name = "ad193x",
+ .owner = THIS_MODULE,
+ },
+ .probe = ad193x_spi_probe,
+ .remove = __devexit_p(ad193x_spi_remove),
+};
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static const struct i2c_device_id ad193x_id[] = {
+ { "ad1936", 0 },
+ { "ad1937", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ad193x_id);
+
+static int __devinit ad193x_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ad193x_priv *ad193x;
+ int ret;
+
+ ad193x = kzalloc(sizeof(struct ad193x_priv), GFP_KERNEL);
+ if (ad193x == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, ad193x);
+ ad193x->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&client->dev,
+ &soc_codec_dev_ad193x, &ad193x_dai, 1);
+ if (ret < 0)
+ kfree(ad193x);
+ return ret;
+}
+
+static int __devexit ad193x_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static struct i2c_driver ad193x_i2c_driver = {
+ .driver = {
+ .name = "ad193x",
+ },
+ .probe = ad193x_i2c_probe,
+ .remove = __devexit_p(ad193x_i2c_remove),
+ .id_table = ad193x_id,
+};
+#endif
+
+static int __init ad193x_modinit(void)
+{
+ int ret;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&ad193x_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register AD193X I2C driver: %d\n",
+ ret);
+ }
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&ad193x_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register AD193X SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(ad193x_modinit);
+
+static void __exit ad193x_modexit(void)
+{
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&ad193x_spi_driver);
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&ad193x_i2c_driver);
+#endif
+}
+module_exit(ad193x_modexit);
+
+MODULE_DESCRIPTION("ASoC ad193x driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ad193x.h b/sound/soc/codecs/ad193x.h
new file mode 100644
index 00000000..cccc2e8e
--- /dev/null
+++ b/sound/soc/codecs/ad193x.h
@@ -0,0 +1,84 @@
+/*
+ * AD193X Audio Codec driver
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef __AD193X_H__
+#define __AD193X_H__
+
+#define AD193X_PLL_CLK_CTRL0 0x800
+#define AD193X_PLL_POWERDOWN 0x01
+#define AD193X_PLL_INPUT_MASK (~0x6)
+#define AD193X_PLL_INPUT_256 (0 << 1)
+#define AD193X_PLL_INPUT_384 (1 << 1)
+#define AD193X_PLL_INPUT_512 (2 << 1)
+#define AD193X_PLL_INPUT_768 (3 << 1)
+#define AD193X_PLL_CLK_CTRL1 0x801
+#define AD193X_DAC_CTRL0 0x802
+#define AD193X_DAC_POWERDOWN 0x01
+#define AD193X_DAC_SERFMT_MASK 0xC0
+#define AD193X_DAC_SERFMT_STEREO (0 << 6)
+#define AD193X_DAC_SERFMT_TDM (1 << 6)
+#define AD193X_DAC_CTRL1 0x803
+#define AD193X_DAC_2_CHANNELS 0
+#define AD193X_DAC_4_CHANNELS 1
+#define AD193X_DAC_8_CHANNELS 2
+#define AD193X_DAC_16_CHANNELS 3
+#define AD193X_DAC_CHAN_SHFT 1
+#define AD193X_DAC_CHAN_MASK (3 << AD193X_DAC_CHAN_SHFT)
+#define AD193X_DAC_LCR_MASTER (1 << 4)
+#define AD193X_DAC_BCLK_MASTER (1 << 5)
+#define AD193X_DAC_LEFT_HIGH (1 << 3)
+#define AD193X_DAC_BCLK_INV (1 << 7)
+#define AD193X_DAC_CTRL2 0x804
+#define AD193X_DAC_WORD_LEN_SHFT 3
+#define AD193X_DAC_WORD_LEN_MASK 0x18
+#define AD193X_DAC_MASTER_MUTE 1
+#define AD193X_DAC_CHNL_MUTE 0x805
+#define AD193X_DACL1_MUTE 0
+#define AD193X_DACR1_MUTE 1
+#define AD193X_DACL2_MUTE 2
+#define AD193X_DACR2_MUTE 3
+#define AD193X_DACL3_MUTE 4
+#define AD193X_DACR3_MUTE 5
+#define AD193X_DACL4_MUTE 6
+#define AD193X_DACR4_MUTE 7
+#define AD193X_DAC_L1_VOL 0x806
+#define AD193X_DAC_R1_VOL 0x807
+#define AD193X_DAC_L2_VOL 0x808
+#define AD193X_DAC_R2_VOL 0x809
+#define AD193X_DAC_L3_VOL 0x80a
+#define AD193X_DAC_R3_VOL 0x80b
+#define AD193X_DAC_L4_VOL 0x80c
+#define AD193X_DAC_R4_VOL 0x80d
+#define AD193X_ADC_CTRL0 0x80e
+#define AD193X_ADC_POWERDOWN 0x01
+#define AD193X_ADC_HIGHPASS_FILTER 1
+#define AD193X_ADCL1_MUTE 2
+#define AD193X_ADCR1_MUTE 3
+#define AD193X_ADCL2_MUTE 4
+#define AD193X_ADCR2_MUTE 5
+#define AD193X_ADC_CTRL1 0x80f
+#define AD193X_ADC_SERFMT_MASK 0x60
+#define AD193X_ADC_SERFMT_STEREO (0 << 5)
+#define AD193X_ADC_SERFMT_TDM (1 << 5)
+#define AD193X_ADC_SERFMT_AUX (2 << 5)
+#define AD193X_ADC_WORD_LEN_MASK 0x3
+#define AD193X_ADC_CTRL2 0x810
+#define AD193X_ADC_2_CHANNELS 0
+#define AD193X_ADC_4_CHANNELS 1
+#define AD193X_ADC_8_CHANNELS 2
+#define AD193X_ADC_16_CHANNELS 3
+#define AD193X_ADC_CHAN_SHFT 4
+#define AD193X_ADC_CHAN_MASK (3 << AD193X_ADC_CHAN_SHFT)
+#define AD193X_ADC_LCR_MASTER (1 << 3)
+#define AD193X_ADC_BCLK_MASTER (1 << 6)
+#define AD193X_ADC_LEFT_HIGH (1 << 2)
+#define AD193X_ADC_BCLK_INV (1 << 1)
+
+#define AD193X_NUM_REGS 17
+
+#endif
diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c
new file mode 100644
index 00000000..923b364a
--- /dev/null
+++ b/sound/soc/codecs/ad1980.c
@@ -0,0 +1,291 @@
+/*
+ * ad1980.c -- ALSA Soc AD1980 codec support
+ *
+ * Copyright: Analog Device Inc.
+ * Author: Roy Huang <roy.huang@analog.com>
+ * Cliff Cai <cliff.cai@analog.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 of the License, or (at your
+ * option) any later version.
+ */
+
+/*
+ * WARNING:
+ *
+ * Because Analog Devices Inc. discontinued the ad1980 sound chip since
+ * Sep. 2009, this ad1980 driver is not maintained, tested and supported
+ * by ADI now.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "ad1980.h"
+
+/*
+ * AD1980 register cache
+ */
+static const u16 ad1980_reg[] = {
+ 0x0090, 0x8000, 0x8000, 0x8000, /* 0 - 6 */
+ 0x0000, 0x0000, 0x8008, 0x8008, /* 8 - e */
+ 0x8808, 0x8808, 0x0000, 0x8808, /* 10 - 16 */
+ 0x8808, 0x0000, 0x8000, 0x0000, /* 18 - 1e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 20 - 26 */
+ 0x03c7, 0x0000, 0xbb80, 0xbb80, /* 28 - 2e */
+ 0xbb80, 0xbb80, 0x0000, 0x8080, /* 30 - 36 */
+ 0x8080, 0x2000, 0x0000, 0x0000, /* 38 - 3e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x8080, 0x0000, 0x0000, 0x0000, /* 60 - 66 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x1001, 0x0000, /* 70 - 76 */
+ 0x0000, 0x0000, 0x4144, 0x5370 /* 78 - 7e */
+};
+
+static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line",
+ "Stereo Mix", "Mono Mix", "Phone"};
+
+static const struct soc_enum ad1980_cap_src =
+ SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 7, ad1980_rec_sel);
+
+static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = {
+SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1),
+
+SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
+
+SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
+SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1),
+
+SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0),
+SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1),
+
+SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1),
+SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1),
+
+SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1),
+SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
+
+SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0),
+SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0),
+
+SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1),
+SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1),
+
+SOC_DOUBLE("Center/LFE Playback Volume", AC97_CENTER_LFE_MASTER, 8, 0, 31, 1),
+SOC_DOUBLE("Center/LFE Playback Switch", AC97_CENTER_LFE_MASTER, 15, 7, 1, 1),
+
+SOC_ENUM("Capture Source", ad1980_cap_src),
+
+SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0),
+};
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ switch (reg) {
+ case AC97_RESET:
+ case AC97_INT_PAGING:
+ case AC97_POWERDOWN:
+ case AC97_EXTENDED_STATUS:
+ case AC97_VENDOR_ID1:
+ case AC97_VENDOR_ID2:
+ return soc_ac97_ops.read(codec->ac97, reg);
+ default:
+ reg = reg >> 1;
+
+ if (reg >= ARRAY_SIZE(ad1980_reg))
+ return -EINVAL;
+
+ return cache[reg];
+ }
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ reg = reg >> 1;
+ if (reg < ARRAY_SIZE(ad1980_reg))
+ cache[reg] = val;
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver ad1980_dai = {
+ .name = "ad1980-hifi",
+ .ac97_control = 1,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 6,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SND_SOC_STD_AC97_FMTS, },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SND_SOC_STD_AC97_FMTS, },
+};
+EXPORT_SYMBOL_GPL(ad1980_dai);
+
+static int ad1980_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ u16 retry_cnt = 0;
+
+retry:
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, AC97_RESET) == 0x0090)
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ /* Set bit 16slot in register 74h, then every slot will has only 16
+ * bits. This command is sent out in 20bit mode, in which case the
+ * first nibble of data is eaten by the addr. (Tag is always 16 bit)*/
+ ac97_write(codec, AC97_AD_SERIAL_CFG, 0x9900);
+
+ if (ac97_read(codec, AC97_RESET) != 0x0090)
+ goto err;
+ return 0;
+
+err:
+ while (retry_cnt++ < 10)
+ goto retry;
+
+ printk(KERN_ERR "AD1980 AC97 reset failed\n");
+ return -EIO;
+}
+
+static int ad1980_soc_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+ u16 vendor_id2;
+ u16 ext_status;
+
+ printk(KERN_INFO "AD1980 SoC Audio Codec\n");
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "ad1980: failed to register AC97 codec\n");
+ return ret;
+ }
+
+ ret = ad1980_reset(codec, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "Failed to reset AD1980: AC97 link error\n");
+ goto reset_err;
+ }
+
+ /* Read out vendor ID to make sure it is ad1980 */
+ if (ac97_read(codec, AC97_VENDOR_ID1) != 0x4144)
+ goto reset_err;
+
+ vendor_id2 = ac97_read(codec, AC97_VENDOR_ID2);
+
+ if (vendor_id2 != 0x5370) {
+ if (vendor_id2 != 0x5374)
+ goto reset_err;
+ else
+ printk(KERN_WARNING "ad1980: "
+ "Found AD1981 - only 2/2 IN/OUT Channels "
+ "supported\n");
+ }
+
+ /* unmute captures and playbacks volume */
+ ac97_write(codec, AC97_MASTER, 0x0000);
+ ac97_write(codec, AC97_PCM, 0x0000);
+ ac97_write(codec, AC97_REC_GAIN, 0x0000);
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, 0x0000);
+ ac97_write(codec, AC97_SURROUND_MASTER, 0x0000);
+
+ /*power on LFE/CENTER/Surround DACs*/
+ ext_status = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, ext_status&~0x3800);
+
+ snd_soc_add_controls(codec, ad1980_snd_ac97_controls,
+ ARRAY_SIZE(ad1980_snd_ac97_controls));
+
+ return 0;
+
+reset_err:
+ snd_soc_free_ac97_codec(codec);
+ return ret;
+}
+
+static int ad1980_soc_remove(struct snd_soc_codec *codec)
+{
+ snd_soc_free_ac97_codec(codec);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ad1980 = {
+ .probe = ad1980_soc_probe,
+ .remove = ad1980_soc_remove,
+ .reg_cache_size = ARRAY_SIZE(ad1980_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = ad1980_reg,
+ .reg_cache_step = 2,
+ .write = ac97_write,
+ .read = ac97_read,
+};
+
+static __devinit int ad1980_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_ad1980, &ad1980_dai, 1);
+}
+
+static int __devexit ad1980_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver ad1980_codec_driver = {
+ .driver = {
+ .name = "ad1980",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = ad1980_probe,
+ .remove = __devexit_p(ad1980_remove),
+};
+
+static int __init ad1980_init(void)
+{
+ return platform_driver_register(&ad1980_codec_driver);
+}
+module_init(ad1980_init);
+
+static void __exit ad1980_exit(void)
+{
+ platform_driver_unregister(&ad1980_codec_driver);
+}
+module_exit(ad1980_exit);
+
+MODULE_DESCRIPTION("ASoC ad1980 driver (Obsolete)");
+MODULE_AUTHOR("Roy Huang, Cliff Cai");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ad1980.h b/sound/soc/codecs/ad1980.h
new file mode 100644
index 00000000..eb0af44a
--- /dev/null
+++ b/sound/soc/codecs/ad1980.h
@@ -0,0 +1,26 @@
+/*
+ * ad1980.h -- ad1980 Soc Audio driver
+ *
+ * WARNING:
+ *
+ * Because Analog Devices Inc. discontinued the ad1980 sound chip since
+ * Sep. 2009, this ad1980 driver is not maintained, tested and supported
+ * by ADI now.
+ */
+
+#ifndef _AD1980_H
+#define _AD1980_H
+/* Bit definition of Power-Down Control/Status Register */
+#define ADC 0x0001
+#define DAC 0x0002
+#define ANL 0x0004
+#define REF 0x0008
+#define PR0 0x0100
+#define PR1 0x0200
+#define PR2 0x0400
+#define PR3 0x0800
+#define PR4 0x1000
+#define PR5 0x2000
+#define PR6 0x4000
+
+#endif
diff --git a/sound/soc/codecs/ad73311.c b/sound/soc/codecs/ad73311.c
new file mode 100644
index 00000000..8d793e99
--- /dev/null
+++ b/sound/soc/codecs/ad73311.c
@@ -0,0 +1,80 @@
+/*
+ * ad73311.c -- ALSA Soc AD73311 codec support
+ *
+ * Copyright: Analog Device Inc.
+ * Author: Cliff Cai <cliff.cai@analog.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "ad73311.h"
+
+static struct snd_soc_dai_driver ad73311_dai = {
+ .name = "ad73311-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_ad73311;
+
+static int ad73311_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_ad73311, &ad73311_dai, 1);
+}
+
+static int __devexit ad73311_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver ad73311_codec_driver = {
+ .driver = {
+ .name = "ad73311",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = ad73311_probe,
+ .remove = __devexit_p(ad73311_remove),
+};
+
+static int __init ad73311_init(void)
+{
+ return platform_driver_register(&ad73311_codec_driver);
+}
+module_init(ad73311_init);
+
+static void __exit ad73311_exit(void)
+{
+ platform_driver_unregister(&ad73311_codec_driver);
+}
+module_exit(ad73311_exit);
+
+MODULE_DESCRIPTION("ASoC ad73311 driver");
+MODULE_AUTHOR("Cliff Cai ");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ad73311.h b/sound/soc/codecs/ad73311.h
new file mode 100644
index 00000000..4b353eef
--- /dev/null
+++ b/sound/soc/codecs/ad73311.h
@@ -0,0 +1,88 @@
+/*
+ * File: sound/soc/codec/ad73311.h
+ * Based on:
+ * Author: Cliff Cai <cliff.cai@analog.com>
+ *
+ * Created: Thur Sep 25, 2008
+ * Description: definitions for AD73311 registers
+ *
+ *
+ * Modified:
+ * Copyright 2006 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __AD73311_H__
+#define __AD73311_H__
+
+#define AD_CONTROL 0x8000
+#define AD_DATA 0x0000
+#define AD_READ 0x4000
+#define AD_WRITE 0x0000
+
+/* Control register A */
+#define CTRL_REG_A (0 << 8)
+
+#define REGA_MODE_PRO 0x00
+#define REGA_MODE_DATA 0x01
+#define REGA_MODE_MIXED 0x03
+#define REGA_DLB 0x04
+#define REGA_SLB 0x08
+#define REGA_DEVC(x) ((x & 0x7) << 4)
+#define REGA_RESET 0x80
+
+/* Control register B */
+#define CTRL_REG_B (1 << 8)
+
+#define REGB_DIRATE(x) (x & 0x3)
+#define REGB_SCDIV(x) ((x & 0x3) << 2)
+#define REGB_MCDIV(x) ((x & 0x7) << 4)
+#define REGB_CEE (1 << 7)
+
+/* Control register C */
+#define CTRL_REG_C (2 << 8)
+
+#define REGC_PUDEV (1 << 0)
+#define REGC_PUADC (1 << 3)
+#define REGC_PUDAC (1 << 4)
+#define REGC_PUREF (1 << 5)
+#define REGC_REFUSE (1 << 6)
+
+/* Control register D */
+#define CTRL_REG_D (3 << 8)
+
+#define REGD_IGS(x) (x & 0x7)
+#define REGD_RMOD (1 << 3)
+#define REGD_OGS(x) ((x & 0x7) << 4)
+#define REGD_MUTE (1 << 7)
+
+/* Control register E */
+#define CTRL_REG_E (4 << 8)
+
+#define REGE_DA(x) (x & 0x1f)
+#define REGE_IBYP (1 << 5)
+
+/* Control register F */
+#define CTRL_REG_F (5 << 8)
+
+#define REGF_SEEN (1 << 5)
+#define REGF_INV (1 << 6)
+#define REGF_ALB (1 << 7)
+
+#endif
diff --git a/sound/soc/codecs/ads117x.c b/sound/soc/codecs/ads117x.c
new file mode 100644
index 00000000..8402854e
--- /dev/null
+++ b/sound/soc/codecs/ads117x.c
@@ -0,0 +1,74 @@
+/*
+ * ads117x.c -- Driver for ads1174/8 ADC chips
+ *
+ * Copyright 2009 ShotSpotter Inc.
+ * Author: Graeme Gregory <gg@slimlogic.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#define ADS117X_RATES (SNDRV_PCM_RATE_8000_48000)
+#define ADS117X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE)
+
+static struct snd_soc_dai_driver ads117x_dai = {
+/* ADC */
+ .name = "ads117x-hifi",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 32,
+ .rates = ADS117X_RATES,
+ .formats = ADS117X_FORMATS,},
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_ads117x;
+
+static __devinit int ads117x_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_ads117x, &ads117x_dai, 1);
+}
+
+static int __devexit ads117x_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver ads117x_codec_driver = {
+ .driver = {
+ .name = "ads117x-codec",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = ads117x_probe,
+ .remove = __devexit_p(ads117x_remove),
+};
+
+static int __init ads117x_init(void)
+{
+ return platform_driver_register(&ads117x_codec_driver);
+}
+module_init(ads117x_init);
+
+static void __exit ads117x_exit(void)
+{
+ platform_driver_unregister(&ads117x_codec_driver);
+}
+module_exit(ads117x_exit);
+
+MODULE_DESCRIPTION("ASoC ads117x driver");
+MODULE_AUTHOR("Graeme Gregory");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ads117x.h b/sound/soc/codecs/ads117x.h
new file mode 100644
index 00000000..3ce02861
--- /dev/null
+++ b/sound/soc/codecs/ads117x.h
@@ -0,0 +1,13 @@
+/*
+ * ads117x.h -- Driver for ads1174/8 ADC chips
+ *
+ * Copyright 2009 ShotSpotter Inc.
+ * Author: Graeme Gregory <gg@slimlogic.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+extern struct snd_soc_dai_driver ads117x_dai;
+extern struct snd_soc_codec_driver soc_codec_dev_ads117x;
diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c
new file mode 100644
index 00000000..cbf0b6d4
--- /dev/null
+++ b/sound/soc/codecs/ak4104.c
@@ -0,0 +1,310 @@
+/*
+ * AK4104 ALSA SoC (ASoC) driver
+ *
+ * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <linux/spi/spi.h>
+#include <sound/asoundef.h>
+
+/* AK4104 registers addresses */
+#define AK4104_REG_CONTROL1 0x00
+#define AK4104_REG_RESERVED 0x01
+#define AK4104_REG_CONTROL2 0x02
+#define AK4104_REG_TX 0x03
+#define AK4104_REG_CHN_STATUS(x) ((x) + 0x04)
+#define AK4104_NUM_REGS 10
+
+#define AK4104_REG_MASK 0x1f
+#define AK4104_READ 0xc0
+#define AK4104_WRITE 0xe0
+#define AK4104_RESERVED_VAL 0x5b
+
+/* Bit masks for AK4104 registers */
+#define AK4104_CONTROL1_RSTN (1 << 0)
+#define AK4104_CONTROL1_PW (1 << 1)
+#define AK4104_CONTROL1_DIF0 (1 << 2)
+#define AK4104_CONTROL1_DIF1 (1 << 3)
+
+#define AK4104_CONTROL2_SEL0 (1 << 0)
+#define AK4104_CONTROL2_SEL1 (1 << 1)
+#define AK4104_CONTROL2_MODE (1 << 2)
+
+#define AK4104_TX_TXE (1 << 0)
+#define AK4104_TX_V (1 << 1)
+
+#define DRV_NAME "ak4104-codec"
+
+struct ak4104_private {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+};
+
+static int ak4104_fill_cache(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 *reg_cache = codec->reg_cache;
+ struct spi_device *spi = codec->control_data;
+
+ for (i = 0; i < codec->driver->reg_cache_size; i++) {
+ int ret = spi_w8r8(spi, i | AK4104_READ);
+ if (ret < 0) {
+ dev_err(&spi->dev, "SPI write failure\n");
+ return ret;
+ }
+
+ reg_cache[i] = ret;
+ }
+
+ return 0;
+}
+
+static unsigned int ak4104_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *reg_cache = codec->reg_cache;
+
+ if (reg >= codec->driver->reg_cache_size)
+ return -EINVAL;
+
+ return reg_cache[reg];
+}
+
+static int ak4104_spi_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 *cache = codec->reg_cache;
+ struct spi_device *spi = codec->control_data;
+
+ if (reg >= codec->driver->reg_cache_size)
+ return -EINVAL;
+
+ /* only write to the hardware if value has changed */
+ if (cache[reg] != value) {
+ u8 tmp[2] = { (reg & AK4104_REG_MASK) | AK4104_WRITE, value };
+
+ if (spi_write(spi, tmp, sizeof(tmp))) {
+ dev_err(&spi->dev, "SPI write failed\n");
+ return -EIO;
+ }
+
+ cache[reg] = value;
+ }
+
+ return 0;
+}
+
+static int ak4104_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int val = 0;
+
+ val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1);
+ if (val < 0)
+ return val;
+
+ val &= ~(AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1);
+
+ /* set DAI format */
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ val |= AK4104_CONTROL1_DIF0;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val |= AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1;
+ break;
+ default:
+ dev_err(codec->dev, "invalid dai format\n");
+ return -EINVAL;
+ }
+
+ /* This device can only be slave */
+ if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+ return -EINVAL;
+
+ return ak4104_spi_write(codec, AK4104_REG_CONTROL1, val);
+}
+
+static int ak4104_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ int val = 0;
+
+ /* set the IEC958 bits: consumer mode, no copyright bit */
+ val |= IEC958_AES0_CON_NOT_COPYRIGHT;
+ ak4104_spi_write(codec, AK4104_REG_CHN_STATUS(0), val);
+
+ val = 0;
+
+ switch (params_rate(params)) {
+ case 44100:
+ val |= IEC958_AES3_CON_FS_44100;
+ break;
+ case 48000:
+ val |= IEC958_AES3_CON_FS_48000;
+ break;
+ case 32000:
+ val |= IEC958_AES3_CON_FS_32000;
+ break;
+ default:
+ dev_err(codec->dev, "unsupported sampling rate\n");
+ return -EINVAL;
+ }
+
+ return ak4104_spi_write(codec, AK4104_REG_CHN_STATUS(3), val);
+}
+
+static struct snd_soc_dai_ops ak4101_dai_ops = {
+ .hw_params = ak4104_hw_params,
+ .set_fmt = ak4104_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver ak4104_dai = {
+ .name = "ak4104-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_3LE |
+ SNDRV_PCM_FMTBIT_S24_LE
+ },
+ .ops = &ak4101_dai_ops,
+};
+
+static int ak4104_probe(struct snd_soc_codec *codec)
+{
+ struct ak4104_private *ak4104 = snd_soc_codec_get_drvdata(codec);
+ int ret, val;
+
+ codec->control_data = ak4104->control_data;
+
+ /* read all regs and fill the cache */
+ ret = ak4104_fill_cache(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to fill register cache\n");
+ return ret;
+ }
+
+ /* read the 'reserved' register - according to the datasheet, it
+ * should contain 0x5b. Not a good way to verify the presence of
+ * the device, but there is no hardware ID register. */
+ if (ak4104_read_reg_cache(codec, AK4104_REG_RESERVED) !=
+ AK4104_RESERVED_VAL)
+ return -ENODEV;
+
+ /* set power-up and non-reset bits */
+ val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1);
+ val |= AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN;
+ ret = ak4104_spi_write(codec, AK4104_REG_CONTROL1, val);
+ if (ret < 0)
+ return ret;
+
+ /* enable transmitter */
+ val = ak4104_read_reg_cache(codec, AK4104_REG_TX);
+ val |= AK4104_TX_TXE;
+ ret = ak4104_spi_write(codec, AK4104_REG_TX, val);
+ if (ret < 0)
+ return ret;
+
+ dev_info(codec->dev, "SPI device initialized\n");
+ return 0;
+}
+
+static int ak4104_remove(struct snd_soc_codec *codec)
+{
+ int val, ret;
+
+ val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1);
+ if (val < 0)
+ return val;
+
+ /* clear power-up and non-reset bits */
+ val &= ~(AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN);
+ ret = ak4104_spi_write(codec, AK4104_REG_CONTROL1, val);
+
+ return ret;
+}
+
+static struct snd_soc_codec_driver soc_codec_device_ak4104 = {
+ .probe = ak4104_probe,
+ .remove = ak4104_remove,
+ .reg_cache_size = AK4104_NUM_REGS,
+ .reg_word_size = sizeof(u16),
+};
+
+static int ak4104_spi_probe(struct spi_device *spi)
+{
+ struct ak4104_private *ak4104;
+ int ret;
+
+ spi->bits_per_word = 8;
+ spi->mode = SPI_MODE_0;
+ ret = spi_setup(spi);
+ if (ret < 0)
+ return ret;
+
+ ak4104 = kzalloc(sizeof(struct ak4104_private), GFP_KERNEL);
+ if (ak4104 == NULL)
+ return -ENOMEM;
+
+ ak4104->control_data = spi;
+ ak4104->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, ak4104);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_device_ak4104, &ak4104_dai, 1);
+ if (ret < 0)
+ kfree(ak4104);
+ return ret;
+}
+
+static int __devexit ak4104_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver ak4104_spi_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ak4104_spi_probe,
+ .remove = __devexit_p(ak4104_spi_remove),
+};
+
+static int __init ak4104_init(void)
+{
+ return spi_register_driver(&ak4104_spi_driver);
+}
+module_init(ak4104_init);
+
+static void __exit ak4104_exit(void)
+{
+ spi_unregister_driver(&ak4104_spi_driver);
+}
+module_exit(ak4104_exit);
+
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("Asahi Kasei AK4104 ALSA SoC driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c
new file mode 100644
index 00000000..65abd09e
--- /dev/null
+++ b/sound/soc/codecs/ak4535.c
@@ -0,0 +1,546 @@
+/*
+ * ak4535.c -- AK4535 ALSA Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "ak4535.h"
+
+#define AK4535_VERSION "0.3"
+
+/* codec private data */
+struct ak4535_priv {
+ unsigned int sysclk;
+ enum snd_soc_control_type control_type;
+ void *control_data;
+};
+
+/*
+ * ak4535 register cache
+ */
+static const u8 ak4535_reg[AK4535_CACHEREGNUM] = {
+ 0x00, 0x80, 0x00, 0x03,
+ 0x02, 0x00, 0x11, 0x01,
+ 0x00, 0x40, 0x36, 0x10,
+ 0x00, 0x00, 0x57, 0x00,
+};
+
+/*
+ * read ak4535 register cache
+ */
+static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= AK4535_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write ak4535 register cache
+ */
+static inline void ak4535_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= AK4535_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the AK4535 register space
+ */
+static int ak4535_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D8 AK4535 register offset
+ * D7...D0 register data
+ */
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ ak4535_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+static int ak4535_sync(struct snd_soc_codec *codec)
+{
+ u16 *cache = codec->reg_cache;
+ int i, r = 0;
+
+ for (i = 0; i < AK4535_CACHEREGNUM; i++)
+ r |= ak4535_write(codec, i, cache[i]);
+
+ return r;
+};
+
+static const char *ak4535_mono_gain[] = {"+6dB", "-17dB"};
+static const char *ak4535_mono_out[] = {"(L + R)/2", "Hi-Z"};
+static const char *ak4535_hp_out[] = {"Stereo", "Mono"};
+static const char *ak4535_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"};
+static const char *ak4535_mic_select[] = {"Internal", "External"};
+
+static const struct soc_enum ak4535_enum[] = {
+ SOC_ENUM_SINGLE(AK4535_SIG1, 7, 2, ak4535_mono_gain),
+ SOC_ENUM_SINGLE(AK4535_SIG1, 6, 2, ak4535_mono_out),
+ SOC_ENUM_SINGLE(AK4535_MODE2, 2, 2, ak4535_hp_out),
+ SOC_ENUM_SINGLE(AK4535_DAC, 0, 4, ak4535_deemp),
+ SOC_ENUM_SINGLE(AK4535_MIC, 1, 2, ak4535_mic_select),
+};
+
+static const struct snd_kcontrol_new ak4535_snd_controls[] = {
+ SOC_SINGLE("ALC2 Switch", AK4535_SIG1, 1, 1, 0),
+ SOC_ENUM("Mono 1 Output", ak4535_enum[1]),
+ SOC_ENUM("Mono 1 Gain", ak4535_enum[0]),
+ SOC_ENUM("Headphone Output", ak4535_enum[2]),
+ SOC_ENUM("Playback Deemphasis", ak4535_enum[3]),
+ SOC_SINGLE("Bass Volume", AK4535_DAC, 2, 3, 0),
+ SOC_SINGLE("Mic Boost (+20dB) Switch", AK4535_MIC, 0, 1, 0),
+ SOC_ENUM("Mic Select", ak4535_enum[4]),
+ SOC_SINGLE("ALC Operation Time", AK4535_TIMER, 0, 3, 0),
+ SOC_SINGLE("ALC Recovery Time", AK4535_TIMER, 2, 3, 0),
+ SOC_SINGLE("ALC ZC Time", AK4535_TIMER, 4, 3, 0),
+ SOC_SINGLE("ALC 1 Switch", AK4535_ALC1, 5, 1, 0),
+ SOC_SINGLE("ALC 2 Switch", AK4535_ALC1, 6, 1, 0),
+ SOC_SINGLE("ALC Volume", AK4535_ALC2, 0, 127, 0),
+ SOC_SINGLE("Capture Volume", AK4535_PGA, 0, 127, 0),
+ SOC_SINGLE("Left Playback Volume", AK4535_LATT, 0, 127, 1),
+ SOC_SINGLE("Right Playback Volume", AK4535_RATT, 0, 127, 1),
+ SOC_SINGLE("AUX Bypass Volume", AK4535_VOL, 0, 15, 0),
+ SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0),
+};
+
+/* Mono 1 Mixer */
+static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0),
+ SOC_DAPM_SINGLE("Mono Playback Switch", AK4535_SIG1, 5, 1, 0),
+};
+
+/* Stereo Mixer */
+static const struct snd_kcontrol_new ak4535_stereo_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG2, 4, 1, 0),
+ SOC_DAPM_SINGLE("Playback Switch", AK4535_SIG2, 7, 1, 0),
+ SOC_DAPM_SINGLE("Aux Bypass Switch", AK4535_SIG2, 5, 1, 0),
+};
+
+/* Input Mixer */
+static const struct snd_kcontrol_new ak4535_input_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Capture Switch", AK4535_MIC, 2, 1, 0),
+ SOC_DAPM_SINGLE("Aux Capture Switch", AK4535_MIC, 5, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new ak4535_input_mux_control =
+ SOC_DAPM_ENUM("Input Select", ak4535_enum[4]);
+
+/* HP L switch */
+static const struct snd_kcontrol_new ak4535_hpl_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 1, 1, 1);
+
+/* HP R switch */
+static const struct snd_kcontrol_new ak4535_hpr_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 0, 1, 1);
+
+/* mono 2 switch */
+static const struct snd_kcontrol_new ak4535_mono2_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG1, 0, 1, 0);
+
+/* Line out switch */
+static const struct snd_kcontrol_new ak4535_line_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 6, 1, 0);
+
+/* ak4535 dapm widgets */
+static const struct snd_soc_dapm_widget ak4535_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4535_stereo_mixer_controls[0],
+ ARRAY_SIZE(ak4535_stereo_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4535_mono1_mixer_controls[0],
+ ARRAY_SIZE(ak4535_mono1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4535_input_mixer_controls[0],
+ ARRAY_SIZE(ak4535_input_mixer_controls)),
+ SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0,
+ &ak4535_input_mux_control),
+ SND_SOC_DAPM_DAC("DAC", "Playback", AK4535_PM2, 0, 0),
+ SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_mono2_control),
+ /* speaker powersave bit */
+ SND_SOC_DAPM_PGA("Speaker Enable", AK4535_MODE2, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_line_control),
+ SND_SOC_DAPM_SWITCH("Left HP Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_hpl_control),
+ SND_SOC_DAPM_SWITCH("Right HP Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_hpr_control),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("SPP"),
+ SND_SOC_DAPM_OUTPUT("SPN"),
+ SND_SOC_DAPM_OUTPUT("MOUT1"),
+ SND_SOC_DAPM_OUTPUT("MOUT2"),
+ SND_SOC_DAPM_OUTPUT("MICOUT"),
+ SND_SOC_DAPM_ADC("ADC", "Capture", AK4535_PM1, 0, 0),
+ SND_SOC_DAPM_PGA("Spk Amp", AK4535_PM2, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HP R Amp", AK4535_PM2, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HP L Amp", AK4535_PM2, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic", AK4535_PM1, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Line Out", AK4535_PM1, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mono Out", AK4535_PM1, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("AUX In", AK4535_PM1, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4535_MIC, 3, 0),
+ SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4535_MIC, 4, 0),
+ SND_SOC_DAPM_INPUT("MICIN"),
+ SND_SOC_DAPM_INPUT("MICEXT"),
+ SND_SOC_DAPM_INPUT("AUX"),
+ SND_SOC_DAPM_INPUT("MIN"),
+ SND_SOC_DAPM_INPUT("AIN"),
+};
+
+static const struct snd_soc_dapm_route ak4535_audio_map[] = {
+ /*stereo mixer */
+ {"Stereo Mixer", "Playback Switch", "DAC"},
+ {"Stereo Mixer", "Mic Sidetone Switch", "Mic"},
+ {"Stereo Mixer", "Aux Bypass Switch", "AUX In"},
+
+ /* mono1 mixer */
+ {"Mono1 Mixer", "Mic Sidetone Switch", "Mic"},
+ {"Mono1 Mixer", "Mono Playback Switch", "DAC"},
+
+ /* Mic */
+ {"Mic", NULL, "AIN"},
+ {"Input Mux", "Internal", "Mic Int Bias"},
+ {"Input Mux", "External", "Mic Ext Bias"},
+ {"Mic Int Bias", NULL, "MICIN"},
+ {"Mic Ext Bias", NULL, "MICEXT"},
+ {"MICOUT", NULL, "Input Mux"},
+
+ /* line out */
+ {"LOUT", NULL, "Line Out Enable"},
+ {"ROUT", NULL, "Line Out Enable"},
+ {"Line Out Enable", "Switch", "Line Out"},
+ {"Line Out", NULL, "Stereo Mixer"},
+
+ /* mono1 out */
+ {"MOUT1", NULL, "Mono Out"},
+ {"Mono Out", NULL, "Mono1 Mixer"},
+
+ /* left HP */
+ {"HPL", NULL, "Left HP Enable"},
+ {"Left HP Enable", "Switch", "HP L Amp"},
+ {"HP L Amp", NULL, "Stereo Mixer"},
+
+ /* right HP */
+ {"HPR", NULL, "Right HP Enable"},
+ {"Right HP Enable", "Switch", "HP R Amp"},
+ {"HP R Amp", NULL, "Stereo Mixer"},
+
+ /* speaker */
+ {"SPP", NULL, "Speaker Enable"},
+ {"SPN", NULL, "Speaker Enable"},
+ {"Speaker Enable", "Switch", "Spk Amp"},
+ {"Spk Amp", NULL, "MIN"},
+
+ /* mono 2 */
+ {"MOUT2", NULL, "Mono 2 Enable"},
+ {"Mono 2 Enable", "Switch", "Stereo Mixer"},
+
+ /* Aux In */
+ {"Aux In", NULL, "AUX"},
+
+ /* ADC */
+ {"ADC", NULL, "Input Mixer"},
+ {"Input Mixer", "Mic Capture Switch", "Mic"},
+ {"Input Mixer", "Aux Capture Switch", "Aux In"},
+};
+
+static int ak4535_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec);
+
+ ak4535->sysclk = freq;
+ return 0;
+}
+
+static int ak4535_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec);
+ u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5);
+ int rate = params_rate(params), fs = 256;
+
+ if (rate)
+ fs = ak4535->sysclk / rate;
+
+ /* set fs */
+ switch (fs) {
+ case 1024:
+ mode2 |= (0x2 << 5);
+ break;
+ case 512:
+ mode2 |= (0x1 << 5);
+ break;
+ case 256:
+ break;
+ }
+
+ /* set rate */
+ ak4535_write(codec, AK4535_MODE2, mode2);
+ return 0;
+}
+
+static int ak4535_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 mode1 = 0;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ mode1 = 0x0002;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ mode1 = 0x0001;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* use 32 fs for BCLK to save power */
+ mode1 |= 0x4;
+
+ ak4535_write(codec, AK4535_MODE1, mode1);
+ return 0;
+}
+
+static int ak4535_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC);
+ if (!mute)
+ ak4535_write(codec, AK4535_DAC, mute_reg & ~0x20);
+ else
+ ak4535_write(codec, AK4535_DAC, mute_reg | 0x20);
+ return 0;
+}
+
+static int ak4535_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 i, mute_reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC);
+ ak4535_write(codec, AK4535_DAC, mute_reg & ~0x20);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC);
+ ak4535_write(codec, AK4535_DAC, mute_reg | 0x20);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ i = ak4535_read_reg_cache(codec, AK4535_PM1);
+ ak4535_write(codec, AK4535_PM1, i | 0x80);
+ i = ak4535_read_reg_cache(codec, AK4535_PM2);
+ ak4535_write(codec, AK4535_PM2, i & (~0x80));
+ break;
+ case SND_SOC_BIAS_OFF:
+ i = ak4535_read_reg_cache(codec, AK4535_PM1);
+ ak4535_write(codec, AK4535_PM1, i & (~0x80));
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define AK4535_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops ak4535_dai_ops = {
+ .hw_params = ak4535_hw_params,
+ .set_fmt = ak4535_set_dai_fmt,
+ .digital_mute = ak4535_mute,
+ .set_sysclk = ak4535_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver ak4535_dai = {
+ .name = "ak4535-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4535_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4535_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &ak4535_dai_ops,
+};
+
+static int ak4535_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ak4535_resume(struct snd_soc_codec *codec)
+{
+ ak4535_sync(codec);
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+static int ak4535_probe(struct snd_soc_codec *codec)
+{
+ struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec);
+
+ printk(KERN_INFO "AK4535 Audio Codec %s", AK4535_VERSION);
+
+ codec->control_data = ak4535->control_data;
+
+ /* power on device */
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_add_controls(codec, ak4535_snd_controls,
+ ARRAY_SIZE(ak4535_snd_controls));
+ return 0;
+}
+
+/* power down chip */
+static int ak4535_remove(struct snd_soc_codec *codec)
+{
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ak4535 = {
+ .probe = ak4535_probe,
+ .remove = ak4535_remove,
+ .suspend = ak4535_suspend,
+ .resume = ak4535_resume,
+ .read = ak4535_read_reg_cache,
+ .write = ak4535_write,
+ .set_bias_level = ak4535_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(ak4535_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = ak4535_reg,
+ .dapm_widgets = ak4535_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ak4535_dapm_widgets),
+ .dapm_routes = ak4535_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(ak4535_audio_map),
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int ak4535_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct ak4535_priv *ak4535;
+ int ret;
+
+ ak4535 = kzalloc(sizeof(struct ak4535_priv), GFP_KERNEL);
+ if (ak4535 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, ak4535);
+ ak4535->control_data = i2c;
+ ak4535->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_ak4535, &ak4535_dai, 1);
+ if (ret < 0)
+ kfree(ak4535);
+ return ret;
+}
+
+static __devexit int ak4535_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id ak4535_i2c_id[] = {
+ { "ak4535", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak4535_i2c_id);
+
+static struct i2c_driver ak4535_i2c_driver = {
+ .driver = {
+ .name = "ak4535-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ak4535_i2c_probe,
+ .remove = __devexit_p(ak4535_i2c_remove),
+ .id_table = ak4535_i2c_id,
+};
+#endif
+
+static int __init ak4535_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&ak4535_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register AK4535 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(ak4535_modinit);
+
+static void __exit ak4535_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&ak4535_i2c_driver);
+#endif
+}
+module_exit(ak4535_exit);
+
+MODULE_DESCRIPTION("Soc AK4535 driver");
+MODULE_AUTHOR("Richard Purdie");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4535.h b/sound/soc/codecs/ak4535.h
new file mode 100644
index 00000000..0431e5f6
--- /dev/null
+++ b/sound/soc/codecs/ak4535.h
@@ -0,0 +1,39 @@
+/*
+ * ak4535.h -- AK4535 Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AK4535_H
+#define _AK4535_H
+
+/* AK4535 register space */
+
+#define AK4535_PM1 0x0
+#define AK4535_PM2 0x1
+#define AK4535_SIG1 0x2
+#define AK4535_SIG2 0x3
+#define AK4535_MODE1 0x4
+#define AK4535_MODE2 0x5
+#define AK4535_DAC 0x6
+#define AK4535_MIC 0x7
+#define AK4535_TIMER 0x8
+#define AK4535_ALC1 0x9
+#define AK4535_ALC2 0xa
+#define AK4535_PGA 0xb
+#define AK4535_LATT 0xc
+#define AK4535_RATT 0xd
+#define AK4535_VOL 0xe
+#define AK4535_STATUS 0xf
+
+#define AK4535_CACHEREGNUM 0x10
+
+#endif
diff --git a/sound/soc/codecs/ak4641.c b/sound/soc/codecs/ak4641.c
new file mode 100644
index 00000000..ed96f247
--- /dev/null
+++ b/sound/soc/codecs/ak4641.c
@@ -0,0 +1,664 @@
+/*
+ * ak4641.c -- AK4641 ALSA Soc Audio driver
+ *
+ * Copyright (C) 2008 Harald Welte <laforge@gnufiish.org>
+ * Copyright (C) 2011 Dmitry Artamonow <mad_soft@inbox.ru>
+ *
+ * Based on ak4535.c by Richard Purdie
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/ak4641.h>
+
+#include "ak4641.h"
+
+/* codec private data */
+struct ak4641_priv {
+ struct snd_soc_codec *codec;
+ unsigned int sysclk;
+ int deemph;
+ int playback_fs;
+};
+
+/*
+ * ak4641 register cache
+ */
+static const u8 ak4641_reg[AK4641_CACHEREGNUM] = {
+ 0x00, 0x80, 0x00, 0x80,
+ 0x02, 0x00, 0x11, 0x05,
+ 0x00, 0x00, 0x36, 0x10,
+ 0x00, 0x00, 0x57, 0x00,
+ 0x88, 0x88, 0x08, 0x08
+};
+
+static const int deemph_settings[] = {44100, 0, 48000, 32000};
+
+static int ak4641_set_deemph(struct snd_soc_codec *codec)
+{
+ struct ak4641_priv *ak4641 = snd_soc_codec_get_drvdata(codec);
+ int i, best = 0;
+
+ for (i = 0 ; i < ARRAY_SIZE(deemph_settings); i++) {
+ /* if deemphasis is on, select the nearest available rate */
+ if (ak4641->deemph && deemph_settings[i] != 0 &&
+ abs(deemph_settings[i] - ak4641->playback_fs) <
+ abs(deemph_settings[best] - ak4641->playback_fs))
+ best = i;
+
+ if (!ak4641->deemph && deemph_settings[i] == 0)
+ best = i;
+ }
+
+ dev_dbg(codec->dev, "Set deemphasis %d\n", best);
+
+ return snd_soc_update_bits(codec, AK4641_DAC, 0x3, best);
+}
+
+static int ak4641_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ak4641_priv *ak4641 = snd_soc_codec_get_drvdata(codec);
+ int deemph = ucontrol->value.enumerated.item[0];
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ ak4641->deemph = deemph;
+
+ return ak4641_set_deemph(codec);
+}
+
+static int ak4641_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ak4641_priv *ak4641 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = ak4641->deemph;
+ return 0;
+};
+
+static const char *ak4641_mono_out[] = {"(L + R)/2", "Hi-Z"};
+static const char *ak4641_hp_out[] = {"Stereo", "Mono"};
+static const char *ak4641_mic_select[] = {"Internal", "External"};
+static const char *ak4641_mic_or_dac[] = {"Microphone", "Voice DAC"};
+
+
+static const DECLARE_TLV_DB_SCALE(mono_gain_tlv, -1700, 2300, 0);
+static const DECLARE_TLV_DB_SCALE(mic_boost_tlv, 0, 2000, 0);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1050, 150, 0);
+static const DECLARE_TLV_DB_SCALE(master_tlv, -12750, 50, 0);
+static const DECLARE_TLV_DB_SCALE(mic_stereo_sidetone_tlv, -2700, 300, 0);
+static const DECLARE_TLV_DB_SCALE(mic_mono_sidetone_tlv, -400, 400, 0);
+static const DECLARE_TLV_DB_SCALE(capture_tlv, -800, 50, 0);
+static const DECLARE_TLV_DB_SCALE(alc_tlv, -800, 50, 0);
+static const DECLARE_TLV_DB_SCALE(aux_in_tlv, -2100, 300, 0);
+
+
+static const struct soc_enum ak4641_mono_out_enum =
+ SOC_ENUM_SINGLE(AK4641_SIG1, 6, 2, ak4641_mono_out);
+static const struct soc_enum ak4641_hp_out_enum =
+ SOC_ENUM_SINGLE(AK4641_MODE2, 2, 2, ak4641_hp_out);
+static const struct soc_enum ak4641_mic_select_enum =
+ SOC_ENUM_SINGLE(AK4641_MIC, 1, 2, ak4641_mic_select);
+static const struct soc_enum ak4641_mic_or_dac_enum =
+ SOC_ENUM_SINGLE(AK4641_BTIF, 4, 2, ak4641_mic_or_dac);
+
+static const struct snd_kcontrol_new ak4641_snd_controls[] = {
+ SOC_ENUM("Mono 1 Output", ak4641_mono_out_enum),
+ SOC_SINGLE_TLV("Mono 1 Gain Volume", AK4641_SIG1, 7, 1, 1,
+ mono_gain_tlv),
+ SOC_ENUM("Headphone Output", ak4641_hp_out_enum),
+ SOC_SINGLE_BOOL_EXT("Playback Deemphasis Switch", 0,
+ ak4641_get_deemph, ak4641_put_deemph),
+
+ SOC_SINGLE_TLV("Mic Boost Volume", AK4641_MIC, 0, 1, 0, mic_boost_tlv),
+
+ SOC_SINGLE("ALC Operation Time", AK4641_TIMER, 0, 3, 0),
+ SOC_SINGLE("ALC Recovery Time", AK4641_TIMER, 2, 3, 0),
+ SOC_SINGLE("ALC ZC Time", AK4641_TIMER, 4, 3, 0),
+
+ SOC_SINGLE("ALC 1 Switch", AK4641_ALC1, 5, 1, 0),
+
+ SOC_SINGLE_TLV("ALC Volume", AK4641_ALC2, 0, 71, 0, alc_tlv),
+ SOC_SINGLE("Left Out Enable Switch", AK4641_SIG2, 1, 1, 0),
+ SOC_SINGLE("Right Out Enable Switch", AK4641_SIG2, 0, 1, 0),
+
+ SOC_SINGLE_TLV("Capture Volume", AK4641_PGA, 0, 71, 0, capture_tlv),
+
+ SOC_DOUBLE_R_TLV("Master Playback Volume", AK4641_LATT,
+ AK4641_RATT, 0, 255, 1, master_tlv),
+
+ SOC_SINGLE_TLV("AUX In Volume", AK4641_VOL, 0, 15, 0, aux_in_tlv),
+
+ SOC_SINGLE("Equalizer Switch", AK4641_DAC, 2, 1, 0),
+ SOC_SINGLE_TLV("EQ1 100 Hz Volume", AK4641_EQLO, 0, 15, 1, eq_tlv),
+ SOC_SINGLE_TLV("EQ2 250 Hz Volume", AK4641_EQLO, 4, 15, 1, eq_tlv),
+ SOC_SINGLE_TLV("EQ3 1 kHz Volume", AK4641_EQMID, 0, 15, 1, eq_tlv),
+ SOC_SINGLE_TLV("EQ4 3.5 kHz Volume", AK4641_EQMID, 4, 15, 1, eq_tlv),
+ SOC_SINGLE_TLV("EQ5 10 kHz Volume", AK4641_EQHI, 0, 15, 1, eq_tlv),
+};
+
+/* Mono 1 Mixer */
+static const struct snd_kcontrol_new ak4641_mono1_mixer_controls[] = {
+ SOC_DAPM_SINGLE_TLV("Mic Mono Sidetone Volume", AK4641_VOL, 7, 1, 0,
+ mic_mono_sidetone_tlv),
+ SOC_DAPM_SINGLE("Mic Mono Sidetone Switch", AK4641_SIG1, 4, 1, 0),
+ SOC_DAPM_SINGLE("Mono Playback Switch", AK4641_SIG1, 5, 1, 0),
+};
+
+/* Stereo Mixer */
+static const struct snd_kcontrol_new ak4641_stereo_mixer_controls[] = {
+ SOC_DAPM_SINGLE_TLV("Mic Sidetone Volume", AK4641_VOL, 4, 7, 0,
+ mic_stereo_sidetone_tlv),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4641_SIG2, 4, 1, 0),
+ SOC_DAPM_SINGLE("Playback Switch", AK4641_SIG2, 7, 1, 0),
+ SOC_DAPM_SINGLE("Aux Bypass Switch", AK4641_SIG2, 5, 1, 0),
+};
+
+/* Input Mixer */
+static const struct snd_kcontrol_new ak4641_input_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Capture Switch", AK4641_MIC, 2, 1, 0),
+ SOC_DAPM_SINGLE("Aux Capture Switch", AK4641_MIC, 5, 1, 0),
+};
+
+/* Mic mux */
+static const struct snd_kcontrol_new ak4641_mic_mux_control =
+ SOC_DAPM_ENUM("Mic Select", ak4641_mic_select_enum);
+
+/* Input mux */
+static const struct snd_kcontrol_new ak4641_input_mux_control =
+ SOC_DAPM_ENUM("Input Select", ak4641_mic_or_dac_enum);
+
+/* mono 2 switch */
+static const struct snd_kcontrol_new ak4641_mono2_control =
+ SOC_DAPM_SINGLE("Switch", AK4641_SIG1, 0, 1, 0);
+
+/* ak4641 dapm widgets */
+static const struct snd_soc_dapm_widget ak4641_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4641_stereo_mixer_controls[0],
+ ARRAY_SIZE(ak4641_stereo_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4641_mono1_mixer_controls[0],
+ ARRAY_SIZE(ak4641_mono1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4641_input_mixer_controls[0],
+ ARRAY_SIZE(ak4641_input_mixer_controls)),
+ SND_SOC_DAPM_MUX("Mic Mux", SND_SOC_NOPM, 0, 0,
+ &ak4641_mic_mux_control),
+ SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0,
+ &ak4641_input_mux_control),
+ SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0,
+ &ak4641_mono2_control),
+
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+ SND_SOC_DAPM_OUTPUT("MOUT1"),
+ SND_SOC_DAPM_OUTPUT("MOUT2"),
+ SND_SOC_DAPM_OUTPUT("MICOUT"),
+
+ SND_SOC_DAPM_ADC("ADC", "HiFi Capture", AK4641_PM1, 0, 0),
+ SND_SOC_DAPM_PGA("Mic", AK4641_PM1, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("AUX In", AK4641_PM1, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mono Out", AK4641_PM1, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Line Out", AK4641_PM1, 4, 0, NULL, 0),
+
+ SND_SOC_DAPM_DAC("DAC", "HiFi Playback", AK4641_PM2, 0, 0),
+ SND_SOC_DAPM_PGA("Mono Out 2", AK4641_PM2, 3, 0, NULL, 0),
+
+ SND_SOC_DAPM_ADC("Voice ADC", "Voice Capture", AK4641_BTIF, 0, 0),
+ SND_SOC_DAPM_ADC("Voice DAC", "Voice Playback", AK4641_BTIF, 1, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4641_MIC, 3, 0),
+ SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4641_MIC, 4, 0),
+
+ SND_SOC_DAPM_INPUT("MICIN"),
+ SND_SOC_DAPM_INPUT("MICEXT"),
+ SND_SOC_DAPM_INPUT("AUX"),
+ SND_SOC_DAPM_INPUT("AIN"),
+};
+
+static const struct snd_soc_dapm_route ak4641_audio_map[] = {
+ /* Stereo Mixer */
+ {"Stereo Mixer", "Playback Switch", "DAC"},
+ {"Stereo Mixer", "Mic Sidetone Switch", "Input Mux"},
+ {"Stereo Mixer", "Aux Bypass Switch", "AUX In"},
+
+ /* Mono 1 Mixer */
+ {"Mono1 Mixer", "Mic Mono Sidetone Switch", "Input Mux"},
+ {"Mono1 Mixer", "Mono Playback Switch", "DAC"},
+
+ /* Mic */
+ {"Mic", NULL, "AIN"},
+ {"Mic Mux", "Internal", "Mic Int Bias"},
+ {"Mic Mux", "External", "Mic Ext Bias"},
+ {"Mic Int Bias", NULL, "MICIN"},
+ {"Mic Ext Bias", NULL, "MICEXT"},
+ {"MICOUT", NULL, "Mic Mux"},
+
+ /* Input Mux */
+ {"Input Mux", "Microphone", "Mic"},
+ {"Input Mux", "Voice DAC", "Voice DAC"},
+
+ /* Line Out */
+ {"LOUT", NULL, "Line Out"},
+ {"ROUT", NULL, "Line Out"},
+ {"Line Out", NULL, "Stereo Mixer"},
+
+ /* Mono 1 Out */
+ {"MOUT1", NULL, "Mono Out"},
+ {"Mono Out", NULL, "Mono1 Mixer"},
+
+ /* Mono 2 Out */
+ {"MOUT2", NULL, "Mono 2 Enable"},
+ {"Mono 2 Enable", "Switch", "Mono Out 2"},
+ {"Mono Out 2", NULL, "Stereo Mixer"},
+
+ {"Voice ADC", NULL, "Mono 2 Enable"},
+
+ /* Aux In */
+ {"AUX In", NULL, "AUX"},
+
+ /* ADC */
+ {"ADC", NULL, "Input Mixer"},
+ {"Input Mixer", "Mic Capture Switch", "Mic"},
+ {"Input Mixer", "Aux Capture Switch", "AUX In"},
+};
+
+static int ak4641_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct ak4641_priv *ak4641 = snd_soc_codec_get_drvdata(codec);
+
+ ak4641->sysclk = freq;
+ return 0;
+}
+
+static int ak4641_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct ak4641_priv *ak4641 = snd_soc_codec_get_drvdata(codec);
+ int rate = params_rate(params), fs = 256;
+ u8 mode2;
+
+ if (rate)
+ fs = ak4641->sysclk / rate;
+ else
+ return -EINVAL;
+
+ /* set fs */
+ switch (fs) {
+ case 1024:
+ mode2 = (0x2 << 5);
+ break;
+ case 512:
+ mode2 = (0x1 << 5);
+ break;
+ case 256:
+ mode2 = (0x0 << 5);
+ break;
+ default:
+ dev_err(codec->dev, "Error: unsupported fs=%d\n", fs);
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, AK4641_MODE2, (0x3 << 5), mode2);
+
+ /* Update de-emphasis filter for the new rate */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ak4641->playback_fs = rate;
+ ak4641_set_deemph(codec);
+ };
+
+ return 0;
+}
+
+static int ak4641_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 btif;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ btif = (0x3 << 5);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ btif = (0x2 << 5);
+ break;
+ case SND_SOC_DAIFMT_DSP_A: /* MSB after FRM */
+ btif = (0x0 << 5);
+ break;
+ case SND_SOC_DAIFMT_DSP_B: /* MSB during FRM */
+ btif = (0x1 << 5);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return snd_soc_update_bits(codec, AK4641_BTIF, (0x3 << 5), btif);
+}
+
+static int ak4641_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 mode1 = 0;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ mode1 = 0x02;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ mode1 = 0x01;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return snd_soc_write(codec, AK4641_MODE1, mode1);
+}
+
+static int ak4641_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+
+ return snd_soc_update_bits(codec, AK4641_DAC, 0x20, mute ? 0x20 : 0);
+}
+
+static int ak4641_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct ak4641_platform_data *pdata = codec->dev->platform_data;
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* unmute */
+ snd_soc_update_bits(codec, AK4641_DAC, 0x20, 0);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ /* mute */
+ snd_soc_update_bits(codec, AK4641_DAC, 0x20, 0x20);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ if (pdata && gpio_is_valid(pdata->gpio_power))
+ gpio_set_value(pdata->gpio_power, 1);
+ mdelay(1);
+ if (pdata && gpio_is_valid(pdata->gpio_npdn))
+ gpio_set_value(pdata->gpio_npdn, 1);
+ mdelay(1);
+
+ ret = snd_soc_cache_sync(codec);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to sync cache: %d\n", ret);
+ return ret;
+ }
+ }
+ snd_soc_update_bits(codec, AK4641_PM1, 0x80, 0x80);
+ snd_soc_update_bits(codec, AK4641_PM2, 0x80, 0);
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, AK4641_PM1, 0x80, 0);
+ if (pdata && gpio_is_valid(pdata->gpio_npdn))
+ gpio_set_value(pdata->gpio_npdn, 0);
+ if (pdata && gpio_is_valid(pdata->gpio_power))
+ gpio_set_value(pdata->gpio_power, 0);
+ codec->cache_sync = 1;
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define AK4641_RATES (SNDRV_PCM_RATE_8000_48000)
+#define AK4641_RATES_BT (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000)
+#define AK4641_FORMATS (SNDRV_PCM_FMTBIT_S16_LE)
+
+static struct snd_soc_dai_ops ak4641_i2s_dai_ops = {
+ .hw_params = ak4641_i2s_hw_params,
+ .set_fmt = ak4641_i2s_set_dai_fmt,
+ .digital_mute = ak4641_mute,
+ .set_sysclk = ak4641_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_ops ak4641_pcm_dai_ops = {
+ .hw_params = NULL, /* rates are controlled by BT chip */
+ .set_fmt = ak4641_pcm_set_dai_fmt,
+ .digital_mute = ak4641_mute,
+ .set_sysclk = ak4641_set_dai_sysclk,
+};
+
+struct snd_soc_dai_driver ak4641_dai[] = {
+{
+ .name = "ak4641-hifi",
+ .id = 1,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4641_RATES,
+ .formats = AK4641_FORMATS,
+ },
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4641_RATES,
+ .formats = AK4641_FORMATS,
+ },
+ .ops = &ak4641_i2s_dai_ops,
+ .symmetric_rates = 1,
+},
+{
+ .name = "ak4641-voice",
+ .id = 1,
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = AK4641_RATES_BT,
+ .formats = AK4641_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Voice Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = AK4641_RATES_BT,
+ .formats = AK4641_FORMATS,
+ },
+ .ops = &ak4641_pcm_dai_ops,
+ .symmetric_rates = 1,
+},
+};
+
+static int ak4641_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ ak4641_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ak4641_resume(struct snd_soc_codec *codec)
+{
+ ak4641_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+static int ak4641_probe(struct snd_soc_codec *codec)
+{
+ struct ak4641_platform_data *pdata = codec->dev->platform_data;
+ int ret;
+
+
+ if (pdata) {
+ if (gpio_is_valid(pdata->gpio_power)) {
+ ret = gpio_request_one(pdata->gpio_power,
+ GPIOF_OUT_INIT_LOW, "ak4641 power");
+ if (ret)
+ goto err_out;
+ }
+ if (gpio_is_valid(pdata->gpio_npdn)) {
+ ret = gpio_request_one(pdata->gpio_npdn,
+ GPIOF_OUT_INIT_LOW, "ak4641 npdn");
+ if (ret)
+ goto err_gpio;
+
+ udelay(1); /* > 150 ns */
+ gpio_set_value(pdata->gpio_npdn, 1);
+ }
+ }
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ goto err_register;
+ }
+
+ /* power on device */
+ ak4641_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+
+err_register:
+ if (pdata) {
+ if (gpio_is_valid(pdata->gpio_power))
+ gpio_set_value(pdata->gpio_power, 0);
+ if (gpio_is_valid(pdata->gpio_npdn))
+ gpio_free(pdata->gpio_npdn);
+ }
+err_gpio:
+ if (pdata && gpio_is_valid(pdata->gpio_power))
+ gpio_free(pdata->gpio_power);
+err_out:
+ return ret;
+}
+
+static int ak4641_remove(struct snd_soc_codec *codec)
+{
+ struct ak4641_platform_data *pdata = codec->dev->platform_data;
+
+ ak4641_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ if (pdata) {
+ if (gpio_is_valid(pdata->gpio_power)) {
+ gpio_set_value(pdata->gpio_power, 0);
+ gpio_free(pdata->gpio_power);
+ }
+ if (gpio_is_valid(pdata->gpio_npdn))
+ gpio_free(pdata->gpio_npdn);
+ }
+ return 0;
+}
+
+
+static struct snd_soc_codec_driver soc_codec_dev_ak4641 = {
+ .probe = ak4641_probe,
+ .remove = ak4641_remove,
+ .suspend = ak4641_suspend,
+ .resume = ak4641_resume,
+ .controls = ak4641_snd_controls,
+ .num_controls = ARRAY_SIZE(ak4641_snd_controls),
+ .dapm_widgets = ak4641_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ak4641_dapm_widgets),
+ .dapm_routes = ak4641_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(ak4641_audio_map),
+ .set_bias_level = ak4641_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(ak4641_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = ak4641_reg,
+ .reg_cache_step = 1,
+};
+
+
+static int __devinit ak4641_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct ak4641_priv *ak4641;
+ int ret;
+
+ ak4641 = kzalloc(sizeof(struct ak4641_priv), GFP_KERNEL);
+ if (!ak4641)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, ak4641);
+
+ ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_ak4641,
+ ak4641_dai, ARRAY_SIZE(ak4641_dai));
+ if (ret < 0)
+ kfree(ak4641);
+
+ return ret;
+}
+
+static int __devexit ak4641_i2c_remove(struct i2c_client *i2c)
+{
+ snd_soc_unregister_codec(&i2c->dev);
+ kfree(i2c_get_clientdata(i2c));
+ return 0;
+}
+
+static const struct i2c_device_id ak4641_i2c_id[] = {
+ { "ak4641", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak4641_i2c_id);
+
+static struct i2c_driver ak4641_i2c_driver = {
+ .driver = {
+ .name = "ak4641",
+ .owner = THIS_MODULE,
+ },
+ .probe = ak4641_i2c_probe,
+ .remove = __devexit_p(ak4641_i2c_remove),
+ .id_table = ak4641_i2c_id,
+};
+
+static int __init ak4641_modinit(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&ak4641_i2c_driver);
+ if (ret != 0)
+ pr_err("Failed to register AK4641 I2C driver: %d\n", ret);
+
+ return ret;
+}
+module_init(ak4641_modinit);
+
+static void __exit ak4641_exit(void)
+{
+ i2c_del_driver(&ak4641_i2c_driver);
+}
+module_exit(ak4641_exit);
+
+MODULE_DESCRIPTION("SoC AK4641 driver");
+MODULE_AUTHOR("Harald Welte <laforge@gnufiish.org>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4641.h b/sound/soc/codecs/ak4641.h
new file mode 100644
index 00000000..4a263248
--- /dev/null
+++ b/sound/soc/codecs/ak4641.h
@@ -0,0 +1,47 @@
+/*
+ * ak4641.h -- AK4641 SoC Audio driver
+ *
+ * Copyright 2008 Harald Welte <laforge@gnufiish.org>
+ *
+ * Based on ak4535.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AK4641_H
+#define _AK4641_H
+
+/* AK4641 register space */
+
+#define AK4641_PM1 0x00
+#define AK4641_PM2 0x01
+#define AK4641_SIG1 0x02
+#define AK4641_SIG2 0x03
+#define AK4641_MODE1 0x04
+#define AK4641_MODE2 0x05
+#define AK4641_DAC 0x06
+#define AK4641_MIC 0x07
+#define AK4641_TIMER 0x08
+#define AK4641_ALC1 0x09
+#define AK4641_ALC2 0x0a
+#define AK4641_PGA 0x0b
+#define AK4641_LATT 0x0c
+#define AK4641_RATT 0x0d
+#define AK4641_VOL 0x0e
+#define AK4641_STATUS 0x0f
+#define AK4641_EQLO 0x10
+#define AK4641_EQMID 0x11
+#define AK4641_EQHI 0x12
+#define AK4641_BTIF 0x13
+
+#define AK4641_CACHEREGNUM 0x14
+
+
+
+#define AK4641_DAI_HIFI 0
+#define AK4641_DAI_VOICE 1
+
+
+#endif
diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c
new file mode 100644
index 00000000..7d45197f
--- /dev/null
+++ b/sound/soc/codecs/ak4642.c
@@ -0,0 +1,564 @@
+/*
+ * ak4642.c -- AK4642/AK4643 ALSA Soc Audio driver
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * Based on wm8731.c by Richard Purdie
+ * Based on ak4535.c by Richard Purdie
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* ** CAUTION **
+ *
+ * This is very simple driver.
+ * It can use headphone output / stereo input only
+ *
+ * AK4642 is not tested.
+ * AK4643 is tested.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#define AK4642_VERSION "0.0.1"
+
+#define PW_MGMT1 0x00
+#define PW_MGMT2 0x01
+#define SG_SL1 0x02
+#define SG_SL2 0x03
+#define MD_CTL1 0x04
+#define MD_CTL2 0x05
+#define TIMER 0x06
+#define ALC_CTL1 0x07
+#define ALC_CTL2 0x08
+#define L_IVC 0x09
+#define L_DVC 0x0a
+#define ALC_CTL3 0x0b
+#define R_IVC 0x0c
+#define R_DVC 0x0d
+#define MD_CTL3 0x0e
+#define MD_CTL4 0x0f
+#define PW_MGMT3 0x10
+#define DF_S 0x11
+#define FIL3_0 0x12
+#define FIL3_1 0x13
+#define FIL3_2 0x14
+#define FIL3_3 0x15
+#define EQ_0 0x16
+#define EQ_1 0x17
+#define EQ_2 0x18
+#define EQ_3 0x19
+#define EQ_4 0x1a
+#define EQ_5 0x1b
+#define FIL1_0 0x1c
+#define FIL1_1 0x1d
+#define FIL1_2 0x1e
+#define FIL1_3 0x1f
+#define PW_MGMT4 0x20
+#define MD_CTL5 0x21
+#define LO_MS 0x22
+#define HP_MS 0x23
+#define SPK_MS 0x24
+
+#define AK4642_CACHEREGNUM 0x25
+
+/* PW_MGMT1*/
+#define PMVCM (1 << 6) /* VCOM Power Management */
+#define PMMIN (1 << 5) /* MIN Input Power Management */
+#define PMDAC (1 << 2) /* DAC Power Management */
+#define PMADL (1 << 0) /* MIC Amp Lch and ADC Lch Power Management */
+
+/* PW_MGMT2 */
+#define HPMTN (1 << 6)
+#define PMHPL (1 << 5)
+#define PMHPR (1 << 4)
+#define MS (1 << 3) /* master/slave select */
+#define MCKO (1 << 1)
+#define PMPLL (1 << 0)
+
+#define PMHP_MASK (PMHPL | PMHPR)
+#define PMHP PMHP_MASK
+
+/* PW_MGMT3 */
+#define PMADR (1 << 0) /* MIC L / ADC R Power Management */
+
+/* SG_SL1 */
+#define MINS (1 << 6) /* Switch from MIN to Speaker */
+#define DACL (1 << 4) /* Switch from DAC to Stereo or Receiver */
+#define PMMP (1 << 2) /* MPWR pin Power Management */
+#define MGAIN0 (1 << 0) /* MIC amp gain*/
+
+/* TIMER */
+#define ZTM(param) ((param & 0x3) << 4) /* ALC Zoro Crossing TimeOut */
+#define WTM(param) (((param & 0x4) << 4) | ((param & 0x3) << 2))
+
+/* ALC_CTL1 */
+#define ALC (1 << 5) /* ALC Enable */
+#define LMTH0 (1 << 0) /* ALC Limiter / Recovery Level */
+
+/* MD_CTL1 */
+#define PLL3 (1 << 7)
+#define PLL2 (1 << 6)
+#define PLL1 (1 << 5)
+#define PLL0 (1 << 4)
+#define PLL_MASK (PLL3 | PLL2 | PLL1 | PLL0)
+
+#define BCKO_MASK (1 << 3)
+#define BCKO_64 BCKO_MASK
+
+#define DIF_MASK (3 << 0)
+#define DSP (0 << 0)
+#define RIGHT_J (1 << 0)
+#define LEFT_J (2 << 0)
+#define I2S (3 << 0)
+
+/* MD_CTL2 */
+#define FS0 (1 << 0)
+#define FS1 (1 << 1)
+#define FS2 (1 << 2)
+#define FS3 (1 << 5)
+#define FS_MASK (FS0 | FS1 | FS2 | FS3)
+
+/* MD_CTL3 */
+#define BST1 (1 << 3)
+
+/* MD_CTL4 */
+#define DACH (1 << 0)
+
+/*
+ * Playback Volume (table 39)
+ *
+ * max : 0x00 : +12.0 dB
+ * ( 0.5 dB step )
+ * min : 0xFE : -115.0 dB
+ * mute: 0xFF
+ */
+static const DECLARE_TLV_DB_SCALE(out_tlv, -11550, 50, 1);
+
+static const struct snd_kcontrol_new ak4642_snd_controls[] = {
+
+ SOC_DOUBLE_R_TLV("Digital Playback Volume", L_DVC, R_DVC,
+ 0, 0xFF, 1, out_tlv),
+};
+
+
+/* codec private data */
+struct ak4642_priv {
+ unsigned int sysclk;
+ enum snd_soc_control_type control_type;
+ void *control_data;
+};
+
+/*
+ * ak4642 register cache
+ */
+static const u8 ak4642_reg[AK4642_CACHEREGNUM] = {
+ 0x00, 0x00, 0x01, 0x00,
+ 0x02, 0x00, 0x00, 0x00,
+ 0xe1, 0xe1, 0x18, 0x00,
+ 0xe1, 0x18, 0x11, 0x08,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+};
+
+/*
+ * read ak4642 register cache
+ */
+static inline unsigned int ak4642_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= AK4642_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write ak4642 register cache
+ */
+static inline void ak4642_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= AK4642_CACHEREGNUM)
+ return;
+
+ cache[reg] = value;
+}
+
+/*
+ * write to the AK4642 register space
+ */
+static int ak4642_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D8 AK4642 register offset
+ * D7...D0 register data
+ */
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ if (codec->hw_write(codec->control_data, data, 2) == 2) {
+ ak4642_write_reg_cache(codec, reg, value);
+ return 0;
+ } else
+ return -EIO;
+}
+
+static int ak4642_sync(struct snd_soc_codec *codec)
+{
+ u16 *cache = codec->reg_cache;
+ int i, r = 0;
+
+ for (i = 0; i < AK4642_CACHEREGNUM; i++)
+ r |= ak4642_write(codec, i, cache[i]);
+
+ return r;
+};
+
+static int ak4642_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_soc_codec *codec = dai->codec;
+
+ if (is_play) {
+ /*
+ * start headphone output
+ *
+ * PLL, Master Mode
+ * Audio I/F Format :MSB justified (ADC & DAC)
+ * Bass Boost Level : Middle
+ *
+ * This operation came from example code of
+ * "ASAHI KASEI AK4642" (japanese) manual p97.
+ */
+ snd_soc_update_bits(codec, MD_CTL4, DACH, DACH);
+ snd_soc_update_bits(codec, MD_CTL3, BST1, BST1);
+ ak4642_write(codec, L_IVC, 0x91); /* volume */
+ ak4642_write(codec, R_IVC, 0x91); /* volume */
+ snd_soc_update_bits(codec, PW_MGMT1, PMVCM | PMMIN | PMDAC,
+ PMVCM | PMMIN | PMDAC);
+ snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK, PMHP);
+ snd_soc_update_bits(codec, PW_MGMT2, HPMTN, HPMTN);
+ } else {
+ /*
+ * start stereo input
+ *
+ * PLL Master Mode
+ * Audio I/F Format:MSB justified (ADC & DAC)
+ * Pre MIC AMP:+20dB
+ * MIC Power On
+ * ALC setting:Refer to Table 35
+ * ALC bit=“1”
+ *
+ * This operation came from example code of
+ * "ASAHI KASEI AK4642" (japanese) manual p94.
+ */
+ ak4642_write(codec, SG_SL1, PMMP | MGAIN0);
+ ak4642_write(codec, TIMER, ZTM(0x3) | WTM(0x3));
+ ak4642_write(codec, ALC_CTL1, ALC | LMTH0);
+ snd_soc_update_bits(codec, PW_MGMT1, PMVCM | PMADL,
+ PMVCM | PMADL);
+ snd_soc_update_bits(codec, PW_MGMT3, PMADR, PMADR);
+ }
+
+ return 0;
+}
+
+static void ak4642_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_soc_codec *codec = dai->codec;
+
+ if (is_play) {
+ /* stop headphone output */
+ snd_soc_update_bits(codec, PW_MGMT2, HPMTN, 0);
+ snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK, 0);
+ snd_soc_update_bits(codec, PW_MGMT1, PMMIN | PMDAC, 0);
+ snd_soc_update_bits(codec, MD_CTL3, BST1, 0);
+ snd_soc_update_bits(codec, MD_CTL4, DACH, 0);
+ } else {
+ /* stop stereo input */
+ snd_soc_update_bits(codec, PW_MGMT1, PMADL, 0);
+ snd_soc_update_bits(codec, PW_MGMT3, PMADR, 0);
+ snd_soc_update_bits(codec, ALC_CTL1, ALC, 0);
+ }
+}
+
+static int ak4642_dai_set_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 pll;
+
+ switch (freq) {
+ case 11289600:
+ pll = PLL2;
+ break;
+ case 12288000:
+ pll = PLL2 | PLL0;
+ break;
+ case 12000000:
+ pll = PLL2 | PLL1;
+ break;
+ case 24000000:
+ pll = PLL2 | PLL1 | PLL0;
+ break;
+ case 13500000:
+ pll = PLL3 | PLL2;
+ break;
+ case 27000000:
+ pll = PLL3 | PLL2 | PLL0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(codec, MD_CTL1, PLL_MASK, pll);
+
+ return 0;
+}
+
+static int ak4642_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 data;
+ u8 bcko;
+
+ data = MCKO | PMPLL; /* use MCKO */
+ bcko = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ data |= MS;
+ bcko = BCKO_64;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(codec, PW_MGMT2, MS | MCKO | PMPLL, data);
+ snd_soc_update_bits(codec, MD_CTL1, BCKO_MASK, bcko);
+
+ /* format type */
+ data = 0;
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ data = LEFT_J;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ data = I2S;
+ break;
+ /* FIXME
+ * Please add RIGHT_J / DSP support here
+ */
+ default:
+ return -EINVAL;
+ break;
+ }
+ snd_soc_update_bits(codec, MD_CTL1, DIF_MASK, data);
+
+ return 0;
+}
+
+static int ak4642_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 rate;
+
+ switch (params_rate(params)) {
+ case 7350:
+ rate = FS2;
+ break;
+ case 8000:
+ rate = 0;
+ break;
+ case 11025:
+ rate = FS2 | FS0;
+ break;
+ case 12000:
+ rate = FS0;
+ break;
+ case 14700:
+ rate = FS2 | FS1;
+ break;
+ case 16000:
+ rate = FS1;
+ break;
+ case 22050:
+ rate = FS2 | FS1 | FS0;
+ break;
+ case 24000:
+ rate = FS1 | FS0;
+ break;
+ case 29400:
+ rate = FS3 | FS2 | FS1;
+ break;
+ case 32000:
+ rate = FS3 | FS1;
+ break;
+ case 44100:
+ rate = FS3 | FS2 | FS1 | FS0;
+ break;
+ case 48000:
+ rate = FS3 | FS1 | FS0;
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+ snd_soc_update_bits(codec, MD_CTL2, FS_MASK, rate);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops ak4642_dai_ops = {
+ .startup = ak4642_dai_startup,
+ .shutdown = ak4642_dai_shutdown,
+ .set_sysclk = ak4642_dai_set_sysclk,
+ .set_fmt = ak4642_dai_set_fmt,
+ .hw_params = ak4642_dai_hw_params,
+};
+
+static struct snd_soc_dai_driver ak4642_dai = {
+ .name = "ak4642-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE },
+ .ops = &ak4642_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int ak4642_resume(struct snd_soc_codec *codec)
+{
+ ak4642_sync(codec);
+ return 0;
+}
+
+
+static int ak4642_probe(struct snd_soc_codec *codec)
+{
+ struct ak4642_priv *ak4642 = snd_soc_codec_get_drvdata(codec);
+
+ dev_info(codec->dev, "AK4642 Audio Codec %s", AK4642_VERSION);
+
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ codec->control_data = ak4642->control_data;
+
+ snd_soc_add_controls(codec, ak4642_snd_controls,
+ ARRAY_SIZE(ak4642_snd_controls));
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ak4642 = {
+ .probe = ak4642_probe,
+ .resume = ak4642_resume,
+ .read = ak4642_read_reg_cache,
+ .write = ak4642_write,
+ .reg_cache_size = ARRAY_SIZE(ak4642_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = ak4642_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int ak4642_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct ak4642_priv *ak4642;
+ int ret;
+
+ ak4642 = kzalloc(sizeof(struct ak4642_priv), GFP_KERNEL);
+ if (!ak4642)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, ak4642);
+ ak4642->control_data = i2c;
+ ak4642->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_ak4642, &ak4642_dai, 1);
+ if (ret < 0)
+ kfree(ak4642);
+ return ret;
+}
+
+static __devexit int ak4642_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id ak4642_i2c_id[] = {
+ { "ak4642", 0 },
+ { "ak4643", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak4642_i2c_id);
+
+static struct i2c_driver ak4642_i2c_driver = {
+ .driver = {
+ .name = "ak4642-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ak4642_i2c_probe,
+ .remove = __devexit_p(ak4642_i2c_remove),
+ .id_table = ak4642_i2c_id,
+};
+#endif
+
+static int __init ak4642_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&ak4642_i2c_driver);
+#endif
+ return ret;
+
+}
+module_init(ak4642_modinit);
+
+static void __exit ak4642_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&ak4642_i2c_driver);
+#endif
+
+}
+module_exit(ak4642_exit);
+
+MODULE_DESCRIPTION("Soc AK4642 driver");
+MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4671.c b/sound/soc/codecs/ak4671.c
new file mode 100644
index 00000000..88b29f8c
--- /dev/null
+++ b/sound/soc/codecs/ak4671.c
@@ -0,0 +1,725 @@
+/*
+ * ak4671.c -- audio driver for AK4671
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "ak4671.h"
+
+
+/* codec private data */
+struct ak4671_priv {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+};
+
+/* ak4671 register cache & default register settings */
+static const u8 ak4671_reg[AK4671_CACHEREGNUM] = {
+ 0x00, /* AK4671_AD_DA_POWER_MANAGEMENT (0x00) */
+ 0xf6, /* AK4671_PLL_MODE_SELECT0 (0x01) */
+ 0x00, /* AK4671_PLL_MODE_SELECT1 (0x02) */
+ 0x02, /* AK4671_FORMAT_SELECT (0x03) */
+ 0x00, /* AK4671_MIC_SIGNAL_SELECT (0x04) */
+ 0x55, /* AK4671_MIC_AMP_GAIN (0x05) */
+ 0x00, /* AK4671_MIXING_POWER_MANAGEMENT0 (0x06) */
+ 0x00, /* AK4671_MIXING_POWER_MANAGEMENT1 (0x07) */
+ 0xb5, /* AK4671_OUTPUT_VOLUME_CONTROL (0x08) */
+ 0x00, /* AK4671_LOUT1_SIGNAL_SELECT (0x09) */
+ 0x00, /* AK4671_ROUT1_SIGNAL_SELECT (0x0a) */
+ 0x00, /* AK4671_LOUT2_SIGNAL_SELECT (0x0b) */
+ 0x00, /* AK4671_ROUT2_SIGNAL_SELECT (0x0c) */
+ 0x00, /* AK4671_LOUT3_SIGNAL_SELECT (0x0d) */
+ 0x00, /* AK4671_ROUT3_SIGNAL_SELECT (0x0e) */
+ 0x00, /* AK4671_LOUT1_POWER_MANAGERMENT (0x0f) */
+ 0x00, /* AK4671_LOUT2_POWER_MANAGERMENT (0x10) */
+ 0x80, /* AK4671_LOUT3_POWER_MANAGERMENT (0x11) */
+ 0x91, /* AK4671_LCH_INPUT_VOLUME_CONTROL (0x12) */
+ 0x91, /* AK4671_RCH_INPUT_VOLUME_CONTROL (0x13) */
+ 0xe1, /* AK4671_ALC_REFERENCE_SELECT (0x14) */
+ 0x00, /* AK4671_DIGITAL_MIXING_CONTROL (0x15) */
+ 0x00, /* AK4671_ALC_TIMER_SELECT (0x16) */
+ 0x00, /* AK4671_ALC_MODE_CONTROL (0x17) */
+ 0x02, /* AK4671_MODE_CONTROL1 (0x18) */
+ 0x01, /* AK4671_MODE_CONTROL2 (0x19) */
+ 0x18, /* AK4671_LCH_OUTPUT_VOLUME_CONTROL (0x1a) */
+ 0x18, /* AK4671_RCH_OUTPUT_VOLUME_CONTROL (0x1b) */
+ 0x00, /* AK4671_SIDETONE_A_CONTROL (0x1c) */
+ 0x02, /* AK4671_DIGITAL_FILTER_SELECT (0x1d) */
+ 0x00, /* AK4671_FIL3_COEFFICIENT0 (0x1e) */
+ 0x00, /* AK4671_FIL3_COEFFICIENT1 (0x1f) */
+ 0x00, /* AK4671_FIL3_COEFFICIENT2 (0x20) */
+ 0x00, /* AK4671_FIL3_COEFFICIENT3 (0x21) */
+ 0x00, /* AK4671_EQ_COEFFICIENT0 (0x22) */
+ 0x00, /* AK4671_EQ_COEFFICIENT1 (0x23) */
+ 0x00, /* AK4671_EQ_COEFFICIENT2 (0x24) */
+ 0x00, /* AK4671_EQ_COEFFICIENT3 (0x25) */
+ 0x00, /* AK4671_EQ_COEFFICIENT4 (0x26) */
+ 0x00, /* AK4671_EQ_COEFFICIENT5 (0x27) */
+ 0xa9, /* AK4671_FIL1_COEFFICIENT0 (0x28) */
+ 0x1f, /* AK4671_FIL1_COEFFICIENT1 (0x29) */
+ 0xad, /* AK4671_FIL1_COEFFICIENT2 (0x2a) */
+ 0x20, /* AK4671_FIL1_COEFFICIENT3 (0x2b) */
+ 0x00, /* AK4671_FIL2_COEFFICIENT0 (0x2c) */
+ 0x00, /* AK4671_FIL2_COEFFICIENT1 (0x2d) */
+ 0x00, /* AK4671_FIL2_COEFFICIENT2 (0x2e) */
+ 0x00, /* AK4671_FIL2_COEFFICIENT3 (0x2f) */
+ 0x00, /* AK4671_DIGITAL_FILTER_SELECT2 (0x30) */
+ 0x00, /* this register not used */
+ 0x00, /* AK4671_E1_COEFFICIENT0 (0x32) */
+ 0x00, /* AK4671_E1_COEFFICIENT1 (0x33) */
+ 0x00, /* AK4671_E1_COEFFICIENT2 (0x34) */
+ 0x00, /* AK4671_E1_COEFFICIENT3 (0x35) */
+ 0x00, /* AK4671_E1_COEFFICIENT4 (0x36) */
+ 0x00, /* AK4671_E1_COEFFICIENT5 (0x37) */
+ 0x00, /* AK4671_E2_COEFFICIENT0 (0x38) */
+ 0x00, /* AK4671_E2_COEFFICIENT1 (0x39) */
+ 0x00, /* AK4671_E2_COEFFICIENT2 (0x3a) */
+ 0x00, /* AK4671_E2_COEFFICIENT3 (0x3b) */
+ 0x00, /* AK4671_E2_COEFFICIENT4 (0x3c) */
+ 0x00, /* AK4671_E2_COEFFICIENT5 (0x3d) */
+ 0x00, /* AK4671_E3_COEFFICIENT0 (0x3e) */
+ 0x00, /* AK4671_E3_COEFFICIENT1 (0x3f) */
+ 0x00, /* AK4671_E3_COEFFICIENT2 (0x40) */
+ 0x00, /* AK4671_E3_COEFFICIENT3 (0x41) */
+ 0x00, /* AK4671_E3_COEFFICIENT4 (0x42) */
+ 0x00, /* AK4671_E3_COEFFICIENT5 (0x43) */
+ 0x00, /* AK4671_E4_COEFFICIENT0 (0x44) */
+ 0x00, /* AK4671_E4_COEFFICIENT1 (0x45) */
+ 0x00, /* AK4671_E4_COEFFICIENT2 (0x46) */
+ 0x00, /* AK4671_E4_COEFFICIENT3 (0x47) */
+ 0x00, /* AK4671_E4_COEFFICIENT4 (0x48) */
+ 0x00, /* AK4671_E4_COEFFICIENT5 (0x49) */
+ 0x00, /* AK4671_E5_COEFFICIENT0 (0x4a) */
+ 0x00, /* AK4671_E5_COEFFICIENT1 (0x4b) */
+ 0x00, /* AK4671_E5_COEFFICIENT2 (0x4c) */
+ 0x00, /* AK4671_E5_COEFFICIENT3 (0x4d) */
+ 0x00, /* AK4671_E5_COEFFICIENT4 (0x4e) */
+ 0x00, /* AK4671_E5_COEFFICIENT5 (0x4f) */
+ 0x88, /* AK4671_EQ_CONTROL_250HZ_100HZ (0x50) */
+ 0x88, /* AK4671_EQ_CONTROL_3500HZ_1KHZ (0x51) */
+ 0x08, /* AK4671_EQ_CONTRO_10KHZ (0x52) */
+ 0x00, /* AK4671_PCM_IF_CONTROL0 (0x53) */
+ 0x00, /* AK4671_PCM_IF_CONTROL1 (0x54) */
+ 0x00, /* AK4671_PCM_IF_CONTROL2 (0x55) */
+ 0x18, /* AK4671_DIGITAL_VOLUME_B_CONTROL (0x56) */
+ 0x18, /* AK4671_DIGITAL_VOLUME_C_CONTROL (0x57) */
+ 0x00, /* AK4671_SIDETONE_VOLUME_CONTROL (0x58) */
+ 0x00, /* AK4671_DIGITAL_MIXING_CONTROL2 (0x59) */
+ 0x00, /* AK4671_SAR_ADC_CONTROL (0x5a) */
+};
+
+/*
+ * LOUT1/ROUT1 output volume control:
+ * from -24 to 6 dB in 6 dB steps (mute instead of -30 dB)
+ */
+static DECLARE_TLV_DB_SCALE(out1_tlv, -3000, 600, 1);
+
+/*
+ * LOUT2/ROUT2 output volume control:
+ * from -33 to 6 dB in 3 dB steps (mute instead of -33 dB)
+ */
+static DECLARE_TLV_DB_SCALE(out2_tlv, -3300, 300, 1);
+
+/*
+ * LOUT3/ROUT3 output volume control:
+ * from -6 to 3 dB in 3 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(out3_tlv, -600, 300, 0);
+
+/*
+ * Mic amp gain control:
+ * from -15 to 30 dB in 3 dB steps
+ * REVISIT: The actual min value(0x01) is -12 dB and the reg value 0x00 is not
+ * available
+ */
+static DECLARE_TLV_DB_SCALE(mic_amp_tlv, -1500, 300, 0);
+
+static const struct snd_kcontrol_new ak4671_snd_controls[] = {
+ /* Common playback gain controls */
+ SOC_SINGLE_TLV("Line Output1 Playback Volume",
+ AK4671_OUTPUT_VOLUME_CONTROL, 0, 0x6, 0, out1_tlv),
+ SOC_SINGLE_TLV("Headphone Output2 Playback Volume",
+ AK4671_OUTPUT_VOLUME_CONTROL, 4, 0xd, 0, out2_tlv),
+ SOC_SINGLE_TLV("Line Output3 Playback Volume",
+ AK4671_LOUT3_POWER_MANAGERMENT, 6, 0x3, 0, out3_tlv),
+
+ /* Common capture gain controls */
+ SOC_DOUBLE_TLV("Mic Amp Capture Volume",
+ AK4671_MIC_AMP_GAIN, 0, 4, 0xf, 0, mic_amp_tlv),
+};
+
+/* event handlers */
+static int ak4671_out2_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u8 reg;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ reg = snd_soc_read(codec, AK4671_LOUT2_POWER_MANAGERMENT);
+ reg |= AK4671_MUTEN;
+ snd_soc_write(codec, AK4671_LOUT2_POWER_MANAGERMENT, reg);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ reg = snd_soc_read(codec, AK4671_LOUT2_POWER_MANAGERMENT);
+ reg &= ~AK4671_MUTEN;
+ snd_soc_write(codec, AK4671_LOUT2_POWER_MANAGERMENT, reg);
+ break;
+ }
+
+ return 0;
+}
+
+/* Output Mixers */
+static const struct snd_kcontrol_new ak4671_lout1_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACL", AK4671_LOUT1_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("LINL1", AK4671_LOUT1_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("LINL2", AK4671_LOUT1_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("LINL3", AK4671_LOUT1_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("LINL4", AK4671_LOUT1_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPL", AK4671_LOUT1_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_rout1_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACR", AK4671_ROUT1_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("RINR1", AK4671_ROUT1_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("RINR2", AK4671_ROUT1_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("RINR3", AK4671_ROUT1_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("RINR4", AK4671_ROUT1_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPR", AK4671_ROUT1_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_lout2_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACHL", AK4671_LOUT2_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("LINH1", AK4671_LOUT2_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("LINH2", AK4671_LOUT2_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("LINH3", AK4671_LOUT2_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("LINH4", AK4671_LOUT2_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPHL", AK4671_LOUT2_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_rout2_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACHR", AK4671_ROUT2_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("RINH1", AK4671_ROUT2_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("RINH2", AK4671_ROUT2_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("RINH3", AK4671_ROUT2_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("RINH4", AK4671_ROUT2_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPHR", AK4671_ROUT2_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_lout3_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACSL", AK4671_LOUT3_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("LINS1", AK4671_LOUT3_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("LINS2", AK4671_LOUT3_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("LINS3", AK4671_LOUT3_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("LINS4", AK4671_LOUT3_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPSL", AK4671_LOUT3_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_rout3_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACSR", AK4671_ROUT3_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("RINS1", AK4671_ROUT3_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("RINS2", AK4671_ROUT3_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("RINS3", AK4671_ROUT3_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("RINS4", AK4671_ROUT3_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPSR", AK4671_ROUT3_SIGNAL_SELECT, 5, 1, 0),
+};
+
+/* Input MUXs */
+static const char *ak4671_lin_mux_texts[] =
+ {"LIN1", "LIN2", "LIN3", "LIN4"};
+static const struct soc_enum ak4671_lin_mux_enum =
+ SOC_ENUM_SINGLE(AK4671_MIC_SIGNAL_SELECT, 0,
+ ARRAY_SIZE(ak4671_lin_mux_texts),
+ ak4671_lin_mux_texts);
+static const struct snd_kcontrol_new ak4671_lin_mux_control =
+ SOC_DAPM_ENUM("Route", ak4671_lin_mux_enum);
+
+static const char *ak4671_rin_mux_texts[] =
+ {"RIN1", "RIN2", "RIN3", "RIN4"};
+static const struct soc_enum ak4671_rin_mux_enum =
+ SOC_ENUM_SINGLE(AK4671_MIC_SIGNAL_SELECT, 2,
+ ARRAY_SIZE(ak4671_rin_mux_texts),
+ ak4671_rin_mux_texts);
+static const struct snd_kcontrol_new ak4671_rin_mux_control =
+ SOC_DAPM_ENUM("Route", ak4671_rin_mux_enum);
+
+static const struct snd_soc_dapm_widget ak4671_dapm_widgets[] = {
+ /* Inputs */
+ SND_SOC_DAPM_INPUT("LIN1"),
+ SND_SOC_DAPM_INPUT("RIN1"),
+ SND_SOC_DAPM_INPUT("LIN2"),
+ SND_SOC_DAPM_INPUT("RIN2"),
+ SND_SOC_DAPM_INPUT("LIN3"),
+ SND_SOC_DAPM_INPUT("RIN3"),
+ SND_SOC_DAPM_INPUT("LIN4"),
+ SND_SOC_DAPM_INPUT("RIN4"),
+
+ /* Outputs */
+ SND_SOC_DAPM_OUTPUT("LOUT1"),
+ SND_SOC_DAPM_OUTPUT("ROUT1"),
+ SND_SOC_DAPM_OUTPUT("LOUT2"),
+ SND_SOC_DAPM_OUTPUT("ROUT2"),
+ SND_SOC_DAPM_OUTPUT("LOUT3"),
+ SND_SOC_DAPM_OUTPUT("ROUT3"),
+
+ /* DAC */
+ SND_SOC_DAPM_DAC("DAC Left", "Left HiFi Playback",
+ AK4671_AD_DA_POWER_MANAGEMENT, 6, 0),
+ SND_SOC_DAPM_DAC("DAC Right", "Right HiFi Playback",
+ AK4671_AD_DA_POWER_MANAGEMENT, 7, 0),
+
+ /* ADC */
+ SND_SOC_DAPM_ADC("ADC Left", "Left HiFi Capture",
+ AK4671_AD_DA_POWER_MANAGEMENT, 4, 0),
+ SND_SOC_DAPM_ADC("ADC Right", "Right HiFi Capture",
+ AK4671_AD_DA_POWER_MANAGEMENT, 5, 0),
+
+ /* PGA */
+ SND_SOC_DAPM_PGA("LOUT2 Mix Amp",
+ AK4671_LOUT2_POWER_MANAGERMENT, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("ROUT2 Mix Amp",
+ AK4671_LOUT2_POWER_MANAGERMENT, 6, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("LIN1 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("RIN1 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("LIN2 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("RIN2 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("LIN3 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("RIN3 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("LIN4 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 6, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("RIN4 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 7, 0, NULL, 0),
+
+ /* Output Mixers */
+ SND_SOC_DAPM_MIXER("LOUT1 Mixer", AK4671_LOUT1_POWER_MANAGERMENT, 0, 0,
+ &ak4671_lout1_mixer_controls[0],
+ ARRAY_SIZE(ak4671_lout1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("ROUT1 Mixer", AK4671_LOUT1_POWER_MANAGERMENT, 1, 0,
+ &ak4671_rout1_mixer_controls[0],
+ ARRAY_SIZE(ak4671_rout1_mixer_controls)),
+ SND_SOC_DAPM_MIXER_E("LOUT2 Mixer", AK4671_LOUT2_POWER_MANAGERMENT,
+ 0, 0, &ak4671_lout2_mixer_controls[0],
+ ARRAY_SIZE(ak4671_lout2_mixer_controls),
+ ak4671_out2_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_MIXER_E("ROUT2 Mixer", AK4671_LOUT2_POWER_MANAGERMENT,
+ 1, 0, &ak4671_rout2_mixer_controls[0],
+ ARRAY_SIZE(ak4671_rout2_mixer_controls),
+ ak4671_out2_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_MIXER("LOUT3 Mixer", AK4671_LOUT3_POWER_MANAGERMENT, 0, 0,
+ &ak4671_lout3_mixer_controls[0],
+ ARRAY_SIZE(ak4671_lout3_mixer_controls)),
+ SND_SOC_DAPM_MIXER("ROUT3 Mixer", AK4671_LOUT3_POWER_MANAGERMENT, 1, 0,
+ &ak4671_rout3_mixer_controls[0],
+ ARRAY_SIZE(ak4671_rout3_mixer_controls)),
+
+ /* Input MUXs */
+ SND_SOC_DAPM_MUX("LIN MUX", AK4671_AD_DA_POWER_MANAGEMENT, 2, 0,
+ &ak4671_lin_mux_control),
+ SND_SOC_DAPM_MUX("RIN MUX", AK4671_AD_DA_POWER_MANAGEMENT, 3, 0,
+ &ak4671_rin_mux_control),
+
+ /* Mic Power */
+ SND_SOC_DAPM_MICBIAS("Mic Bias", AK4671_AD_DA_POWER_MANAGEMENT, 1, 0),
+
+ /* Supply */
+ SND_SOC_DAPM_SUPPLY("PMPLL", AK4671_PLL_MODE_SELECT1, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route ak4671_intercon[] = {
+ {"DAC Left", "NULL", "PMPLL"},
+ {"DAC Right", "NULL", "PMPLL"},
+ {"ADC Left", "NULL", "PMPLL"},
+ {"ADC Right", "NULL", "PMPLL"},
+
+ /* Outputs */
+ {"LOUT1", "NULL", "LOUT1 Mixer"},
+ {"ROUT1", "NULL", "ROUT1 Mixer"},
+ {"LOUT2", "NULL", "LOUT2 Mix Amp"},
+ {"ROUT2", "NULL", "ROUT2 Mix Amp"},
+ {"LOUT3", "NULL", "LOUT3 Mixer"},
+ {"ROUT3", "NULL", "ROUT3 Mixer"},
+
+ {"LOUT1 Mixer", "DACL", "DAC Left"},
+ {"ROUT1 Mixer", "DACR", "DAC Right"},
+ {"LOUT2 Mixer", "DACHL", "DAC Left"},
+ {"ROUT2 Mixer", "DACHR", "DAC Right"},
+ {"LOUT2 Mix Amp", "NULL", "LOUT2 Mixer"},
+ {"ROUT2 Mix Amp", "NULL", "ROUT2 Mixer"},
+ {"LOUT3 Mixer", "DACSL", "DAC Left"},
+ {"ROUT3 Mixer", "DACSR", "DAC Right"},
+
+ /* Inputs */
+ {"LIN MUX", "LIN1", "LIN1"},
+ {"LIN MUX", "LIN2", "LIN2"},
+ {"LIN MUX", "LIN3", "LIN3"},
+ {"LIN MUX", "LIN4", "LIN4"},
+
+ {"RIN MUX", "RIN1", "RIN1"},
+ {"RIN MUX", "RIN2", "RIN2"},
+ {"RIN MUX", "RIN3", "RIN3"},
+ {"RIN MUX", "RIN4", "RIN4"},
+
+ {"LIN1", NULL, "Mic Bias"},
+ {"RIN1", NULL, "Mic Bias"},
+ {"LIN2", NULL, "Mic Bias"},
+ {"RIN2", NULL, "Mic Bias"},
+
+ {"ADC Left", "NULL", "LIN MUX"},
+ {"ADC Right", "NULL", "RIN MUX"},
+
+ /* Analog Loops */
+ {"LIN1 Mixing Circuit", "NULL", "LIN1"},
+ {"RIN1 Mixing Circuit", "NULL", "RIN1"},
+ {"LIN2 Mixing Circuit", "NULL", "LIN2"},
+ {"RIN2 Mixing Circuit", "NULL", "RIN2"},
+ {"LIN3 Mixing Circuit", "NULL", "LIN3"},
+ {"RIN3 Mixing Circuit", "NULL", "RIN3"},
+ {"LIN4 Mixing Circuit", "NULL", "LIN4"},
+ {"RIN4 Mixing Circuit", "NULL", "RIN4"},
+
+ {"LOUT1 Mixer", "LINL1", "LIN1 Mixing Circuit"},
+ {"ROUT1 Mixer", "RINR1", "RIN1 Mixing Circuit"},
+ {"LOUT2 Mixer", "LINH1", "LIN1 Mixing Circuit"},
+ {"ROUT2 Mixer", "RINH1", "RIN1 Mixing Circuit"},
+ {"LOUT3 Mixer", "LINS1", "LIN1 Mixing Circuit"},
+ {"ROUT3 Mixer", "RINS1", "RIN1 Mixing Circuit"},
+
+ {"LOUT1 Mixer", "LINL2", "LIN2 Mixing Circuit"},
+ {"ROUT1 Mixer", "RINR2", "RIN2 Mixing Circuit"},
+ {"LOUT2 Mixer", "LINH2", "LIN2 Mixing Circuit"},
+ {"ROUT2 Mixer", "RINH2", "RIN2 Mixing Circuit"},
+ {"LOUT3 Mixer", "LINS2", "LIN2 Mixing Circuit"},
+ {"ROUT3 Mixer", "RINS2", "RIN2 Mixing Circuit"},
+
+ {"LOUT1 Mixer", "LINL3", "LIN3 Mixing Circuit"},
+ {"ROUT1 Mixer", "RINR3", "RIN3 Mixing Circuit"},
+ {"LOUT2 Mixer", "LINH3", "LIN3 Mixing Circuit"},
+ {"ROUT2 Mixer", "RINH3", "RIN3 Mixing Circuit"},
+ {"LOUT3 Mixer", "LINS3", "LIN3 Mixing Circuit"},
+ {"ROUT3 Mixer", "RINS3", "RIN3 Mixing Circuit"},
+
+ {"LOUT1 Mixer", "LINL4", "LIN4 Mixing Circuit"},
+ {"ROUT1 Mixer", "RINR4", "RIN4 Mixing Circuit"},
+ {"LOUT2 Mixer", "LINH4", "LIN4 Mixing Circuit"},
+ {"ROUT2 Mixer", "RINH4", "RIN4 Mixing Circuit"},
+ {"LOUT3 Mixer", "LINS4", "LIN4 Mixing Circuit"},
+ {"ROUT3 Mixer", "RINS4", "RIN4 Mixing Circuit"},
+};
+
+static int ak4671_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 fs;
+
+ fs = snd_soc_read(codec, AK4671_PLL_MODE_SELECT0);
+ fs &= ~AK4671_FS;
+
+ switch (params_rate(params)) {
+ case 8000:
+ fs |= AK4671_FS_8KHZ;
+ break;
+ case 12000:
+ fs |= AK4671_FS_12KHZ;
+ break;
+ case 16000:
+ fs |= AK4671_FS_16KHZ;
+ break;
+ case 24000:
+ fs |= AK4671_FS_24KHZ;
+ break;
+ case 11025:
+ fs |= AK4671_FS_11_025KHZ;
+ break;
+ case 22050:
+ fs |= AK4671_FS_22_05KHZ;
+ break;
+ case 32000:
+ fs |= AK4671_FS_32KHZ;
+ break;
+ case 44100:
+ fs |= AK4671_FS_44_1KHZ;
+ break;
+ case 48000:
+ fs |= AK4671_FS_48KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AK4671_PLL_MODE_SELECT0, fs);
+
+ return 0;
+}
+
+static int ak4671_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 pll;
+
+ pll = snd_soc_read(codec, AK4671_PLL_MODE_SELECT0);
+ pll &= ~AK4671_PLL;
+
+ switch (freq) {
+ case 11289600:
+ pll |= AK4671_PLL_11_2896MHZ;
+ break;
+ case 12000000:
+ pll |= AK4671_PLL_12MHZ;
+ break;
+ case 12288000:
+ pll |= AK4671_PLL_12_288MHZ;
+ break;
+ case 13000000:
+ pll |= AK4671_PLL_13MHZ;
+ break;
+ case 13500000:
+ pll |= AK4671_PLL_13_5MHZ;
+ break;
+ case 19200000:
+ pll |= AK4671_PLL_19_2MHZ;
+ break;
+ case 24000000:
+ pll |= AK4671_PLL_24MHZ;
+ break;
+ case 26000000:
+ pll |= AK4671_PLL_26MHZ;
+ break;
+ case 27000000:
+ pll |= AK4671_PLL_27MHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AK4671_PLL_MODE_SELECT0, pll);
+
+ return 0;
+}
+
+static int ak4671_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 mode;
+ u8 format;
+
+ /* set master/slave audio interface */
+ mode = snd_soc_read(codec, AK4671_PLL_MODE_SELECT1);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ mode |= AK4671_M_S;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ mode &= ~(AK4671_M_S);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ format = snd_soc_read(codec, AK4671_FORMAT_SELECT);
+ format &= ~AK4671_DIF;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ format |= AK4671_DIF_I2S_MODE;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ format |= AK4671_DIF_MSB_MODE;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ format |= AK4671_DIF_DSP_MODE;
+ format |= AK4671_BCKP;
+ format |= AK4671_MSBS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set mode and format */
+ snd_soc_write(codec, AK4671_PLL_MODE_SELECT1, mode);
+ snd_soc_write(codec, AK4671_FORMAT_SELECT, format);
+
+ return 0;
+}
+
+static int ak4671_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u8 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ reg = snd_soc_read(codec, AK4671_AD_DA_POWER_MANAGEMENT);
+ snd_soc_write(codec, AK4671_AD_DA_POWER_MANAGEMENT,
+ reg | AK4671_PMVCM);
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, AK4671_AD_DA_POWER_MANAGEMENT, 0x00);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define AK4671_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+
+#define AK4671_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+static struct snd_soc_dai_ops ak4671_dai_ops = {
+ .hw_params = ak4671_hw_params,
+ .set_sysclk = ak4671_set_dai_sysclk,
+ .set_fmt = ak4671_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver ak4671_dai = {
+ .name = "ak4671-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4671_RATES,
+ .formats = AK4671_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4671_RATES,
+ .formats = AK4671_FORMATS,},
+ .ops = &ak4671_dai_ops,
+};
+
+static int ak4671_probe(struct snd_soc_codec *codec)
+{
+ struct ak4671_priv *ak4671 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ codec->hw_write = (hw_write_t)i2c_master_send;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, ak4671->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ snd_soc_add_controls(codec, ak4671_snd_controls,
+ ARRAY_SIZE(ak4671_snd_controls));
+
+ ak4671_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return ret;
+}
+
+static int ak4671_remove(struct snd_soc_codec *codec)
+{
+ ak4671_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ak4671 = {
+ .probe = ak4671_probe,
+ .remove = ak4671_remove,
+ .set_bias_level = ak4671_set_bias_level,
+ .reg_cache_size = AK4671_CACHEREGNUM,
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = ak4671_reg,
+ .dapm_widgets = ak4671_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ak4671_dapm_widgets),
+ .dapm_routes = ak4671_intercon,
+ .num_dapm_routes = ARRAY_SIZE(ak4671_intercon),
+};
+
+static int __devinit ak4671_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ak4671_priv *ak4671;
+ int ret;
+
+ ak4671 = kzalloc(sizeof(struct ak4671_priv), GFP_KERNEL);
+ if (ak4671 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, ak4671);
+ ak4671->control_data = client;
+ ak4671->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&client->dev,
+ &soc_codec_dev_ak4671, &ak4671_dai, 1);
+ if (ret < 0)
+ kfree(ak4671);
+ return ret;
+}
+
+static __devexit int ak4671_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id ak4671_i2c_id[] = {
+ { "ak4671", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak4671_i2c_id);
+
+static struct i2c_driver ak4671_i2c_driver = {
+ .driver = {
+ .name = "ak4671-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ak4671_i2c_probe,
+ .remove = __devexit_p(ak4671_i2c_remove),
+ .id_table = ak4671_i2c_id,
+};
+
+static int __init ak4671_modinit(void)
+{
+ return i2c_add_driver(&ak4671_i2c_driver);
+}
+module_init(ak4671_modinit);
+
+static void __exit ak4671_exit(void)
+{
+ i2c_del_driver(&ak4671_i2c_driver);
+}
+module_exit(ak4671_exit);
+
+MODULE_DESCRIPTION("ASoC AK4671 codec driver");
+MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4671.h b/sound/soc/codecs/ak4671.h
new file mode 100644
index 00000000..61cb7ab7
--- /dev/null
+++ b/sound/soc/codecs/ak4671.h
@@ -0,0 +1,153 @@
+/*
+ * ak4671.h -- audio driver for AK4671
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _AK4671_H
+#define _AK4671_H
+
+#define AK4671_AD_DA_POWER_MANAGEMENT 0x00
+#define AK4671_PLL_MODE_SELECT0 0x01
+#define AK4671_PLL_MODE_SELECT1 0x02
+#define AK4671_FORMAT_SELECT 0x03
+#define AK4671_MIC_SIGNAL_SELECT 0x04
+#define AK4671_MIC_AMP_GAIN 0x05
+#define AK4671_MIXING_POWER_MANAGEMENT0 0x06
+#define AK4671_MIXING_POWER_MANAGEMENT1 0x07
+#define AK4671_OUTPUT_VOLUME_CONTROL 0x08
+#define AK4671_LOUT1_SIGNAL_SELECT 0x09
+#define AK4671_ROUT1_SIGNAL_SELECT 0x0a
+#define AK4671_LOUT2_SIGNAL_SELECT 0x0b
+#define AK4671_ROUT2_SIGNAL_SELECT 0x0c
+#define AK4671_LOUT3_SIGNAL_SELECT 0x0d
+#define AK4671_ROUT3_SIGNAL_SELECT 0x0e
+#define AK4671_LOUT1_POWER_MANAGERMENT 0x0f
+#define AK4671_LOUT2_POWER_MANAGERMENT 0x10
+#define AK4671_LOUT3_POWER_MANAGERMENT 0x11
+#define AK4671_LCH_INPUT_VOLUME_CONTROL 0x12
+#define AK4671_RCH_INPUT_VOLUME_CONTROL 0x13
+#define AK4671_ALC_REFERENCE_SELECT 0x14
+#define AK4671_DIGITAL_MIXING_CONTROL 0x15
+#define AK4671_ALC_TIMER_SELECT 0x16
+#define AK4671_ALC_MODE_CONTROL 0x17
+#define AK4671_MODE_CONTROL1 0x18
+#define AK4671_MODE_CONTROL2 0x19
+#define AK4671_LCH_OUTPUT_VOLUME_CONTROL 0x1a
+#define AK4671_RCH_OUTPUT_VOLUME_CONTROL 0x1b
+#define AK4671_SIDETONE_A_CONTROL 0x1c
+#define AK4671_DIGITAL_FILTER_SELECT 0x1d
+#define AK4671_FIL3_COEFFICIENT0 0x1e
+#define AK4671_FIL3_COEFFICIENT1 0x1f
+#define AK4671_FIL3_COEFFICIENT2 0x20
+#define AK4671_FIL3_COEFFICIENT3 0x21
+#define AK4671_EQ_COEFFICIENT0 0x22
+#define AK4671_EQ_COEFFICIENT1 0x23
+#define AK4671_EQ_COEFFICIENT2 0x24
+#define AK4671_EQ_COEFFICIENT3 0x25
+#define AK4671_EQ_COEFFICIENT4 0x26
+#define AK4671_EQ_COEFFICIENT5 0x27
+#define AK4671_FIL1_COEFFICIENT0 0x28
+#define AK4671_FIL1_COEFFICIENT1 0x29
+#define AK4671_FIL1_COEFFICIENT2 0x2a
+#define AK4671_FIL1_COEFFICIENT3 0x2b
+#define AK4671_FIL2_COEFFICIENT0 0x2c
+#define AK4671_FIL2_COEFFICIENT1 0x2d
+#define AK4671_FIL2_COEFFICIENT2 0x2e
+#define AK4671_FIL2_COEFFICIENT3 0x2f
+#define AK4671_DIGITAL_FILTER_SELECT2 0x30
+#define AK4671_E1_COEFFICIENT0 0x32
+#define AK4671_E1_COEFFICIENT1 0x33
+#define AK4671_E1_COEFFICIENT2 0x34
+#define AK4671_E1_COEFFICIENT3 0x35
+#define AK4671_E1_COEFFICIENT4 0x36
+#define AK4671_E1_COEFFICIENT5 0x37
+#define AK4671_E2_COEFFICIENT0 0x38
+#define AK4671_E2_COEFFICIENT1 0x39
+#define AK4671_E2_COEFFICIENT2 0x3a
+#define AK4671_E2_COEFFICIENT3 0x3b
+#define AK4671_E2_COEFFICIENT4 0x3c
+#define AK4671_E2_COEFFICIENT5 0x3d
+#define AK4671_E3_COEFFICIENT0 0x3e
+#define AK4671_E3_COEFFICIENT1 0x3f
+#define AK4671_E3_COEFFICIENT2 0x40
+#define AK4671_E3_COEFFICIENT3 0x41
+#define AK4671_E3_COEFFICIENT4 0x42
+#define AK4671_E3_COEFFICIENT5 0x43
+#define AK4671_E4_COEFFICIENT0 0x44
+#define AK4671_E4_COEFFICIENT1 0x45
+#define AK4671_E4_COEFFICIENT2 0x46
+#define AK4671_E4_COEFFICIENT3 0x47
+#define AK4671_E4_COEFFICIENT4 0x48
+#define AK4671_E4_COEFFICIENT5 0x49
+#define AK4671_E5_COEFFICIENT0 0x4a
+#define AK4671_E5_COEFFICIENT1 0x4b
+#define AK4671_E5_COEFFICIENT2 0x4c
+#define AK4671_E5_COEFFICIENT3 0x4d
+#define AK4671_E5_COEFFICIENT4 0x4e
+#define AK4671_E5_COEFFICIENT5 0x4f
+#define AK4671_EQ_CONTROL_250HZ_100HZ 0x50
+#define AK4671_EQ_CONTROL_3500HZ_1KHZ 0x51
+#define AK4671_EQ_CONTRO_10KHZ 0x52
+#define AK4671_PCM_IF_CONTROL0 0x53
+#define AK4671_PCM_IF_CONTROL1 0x54
+#define AK4671_PCM_IF_CONTROL2 0x55
+#define AK4671_DIGITAL_VOLUME_B_CONTROL 0x56
+#define AK4671_DIGITAL_VOLUME_C_CONTROL 0x57
+#define AK4671_SIDETONE_VOLUME_CONTROL 0x58
+#define AK4671_DIGITAL_MIXING_CONTROL2 0x59
+#define AK4671_SAR_ADC_CONTROL 0x5a
+
+#define AK4671_CACHEREGNUM (AK4671_SAR_ADC_CONTROL + 1)
+
+/* Bitfield Definitions */
+
+/* AK4671_AD_DA_POWER_MANAGEMENT (0x00) Fields */
+#define AK4671_PMVCM 0x01
+
+/* AK4671_PLL_MODE_SELECT0 (0x01) Fields */
+#define AK4671_PLL 0x0f
+#define AK4671_PLL_11_2896MHZ (4 << 0)
+#define AK4671_PLL_12_288MHZ (5 << 0)
+#define AK4671_PLL_12MHZ (6 << 0)
+#define AK4671_PLL_24MHZ (7 << 0)
+#define AK4671_PLL_19_2MHZ (8 << 0)
+#define AK4671_PLL_13_5MHZ (12 << 0)
+#define AK4671_PLL_27MHZ (13 << 0)
+#define AK4671_PLL_13MHZ (14 << 0)
+#define AK4671_PLL_26MHZ (15 << 0)
+#define AK4671_FS 0xf0
+#define AK4671_FS_8KHZ (0 << 4)
+#define AK4671_FS_12KHZ (1 << 4)
+#define AK4671_FS_16KHZ (2 << 4)
+#define AK4671_FS_24KHZ (3 << 4)
+#define AK4671_FS_11_025KHZ (5 << 4)
+#define AK4671_FS_22_05KHZ (7 << 4)
+#define AK4671_FS_32KHZ (10 << 4)
+#define AK4671_FS_48KHZ (11 << 4)
+#define AK4671_FS_44_1KHZ (15 << 4)
+
+/* AK4671_PLL_MODE_SELECT1 (0x02) Fields */
+#define AK4671_PMPLL 0x01
+#define AK4671_M_S 0x02
+
+/* AK4671_FORMAT_SELECT (0x03) Fields */
+#define AK4671_DIF 0x03
+#define AK4671_DIF_DSP_MODE (0 << 0)
+#define AK4671_DIF_MSB_MODE (2 << 0)
+#define AK4671_DIF_I2S_MODE (3 << 0)
+#define AK4671_BCKP 0x04
+#define AK4671_MSBS 0x08
+#define AK4671_SDOD 0x10
+
+/* AK4671_LOUT2_POWER_MANAGEMENT (0x10) Fields */
+#define AK4671_MUTEN 0x04
+
+#endif
diff --git a/sound/soc/codecs/alc5623.c b/sound/soc/codecs/alc5623.c
new file mode 100644
index 00000000..eecffb54
--- /dev/null
+++ b/sound/soc/codecs/alc5623.c
@@ -0,0 +1,1117 @@
+/*
+ * alc5623.c -- alc562[123] ALSA Soc Audio driver
+ *
+ * Copyright 2008 Realtek Microelectronics
+ * Author: flove <flove@realtek.com> Ethan <eku@marvell.com>
+ *
+ * Copyright 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
+ *
+ *
+ * Based on WM8753.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/alc5623.h>
+
+#include "alc5623.h"
+
+static int caps_charge = 2000;
+module_param(caps_charge, int, 0);
+MODULE_PARM_DESC(caps_charge, "ALC5623 cap charge time (msecs)");
+
+/* codec private data */
+struct alc5623_priv {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+ struct mutex mutex;
+ u8 id;
+ unsigned int sysclk;
+ u16 reg_cache[ALC5623_VENDOR_ID2+2];
+ unsigned int add_ctrl;
+ unsigned int jack_det_ctrl;
+};
+
+static void alc5623_fill_cache(struct snd_soc_codec *codec)
+{
+ int i, step = codec->driver->reg_cache_step;
+ u16 *cache = codec->reg_cache;
+
+ /* not really efficient ... */
+ for (i = 0 ; i < codec->driver->reg_cache_size ; i += step)
+ cache[i] = codec->hw_read(codec, i);
+}
+
+static inline int alc5623_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, ALC5623_RESET, 0);
+}
+
+static int amp_mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ /* to power-on/off class-d amp generators/speaker */
+ /* need to write to 'index-46h' register : */
+ /* so write index num (here 0x46) to reg 0x6a */
+ /* and then 0xffff/0 to reg 0x6c */
+ snd_soc_write(w->codec, ALC5623_HID_CTRL_INDEX, 0x46);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ snd_soc_write(w->codec, ALC5623_HID_CTRL_DATA, 0xFFFF);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_write(w->codec, ALC5623_HID_CTRL_DATA, 0);
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * ALC5623 Controls
+ */
+
+static const DECLARE_TLV_DB_SCALE(vol_tlv, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(hp_tlv, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(adc_rec_tlv, -1650, 150, 0);
+static const unsigned int boost_tlv[] = {
+ TLV_DB_RANGE_HEAD(3),
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(dig_tlv, 0, 600, 0);
+
+static const struct snd_kcontrol_new rt5621_vol_snd_controls[] = {
+ SOC_DOUBLE_TLV("Speaker Playback Volume",
+ ALC5623_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Speaker Playback Switch",
+ ALC5623_SPK_OUT_VOL, 15, 7, 1, 1),
+ SOC_DOUBLE_TLV("Headphone Playback Volume",
+ ALC5623_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Headphone Playback Switch",
+ ALC5623_HP_OUT_VOL, 15, 7, 1, 1),
+};
+
+static const struct snd_kcontrol_new rt5622_vol_snd_controls[] = {
+ SOC_DOUBLE_TLV("Speaker Playback Volume",
+ ALC5623_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Speaker Playback Switch",
+ ALC5623_SPK_OUT_VOL, 15, 7, 1, 1),
+ SOC_DOUBLE_TLV("Line Playback Volume",
+ ALC5623_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Line Playback Switch",
+ ALC5623_HP_OUT_VOL, 15, 7, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5623_vol_snd_controls[] = {
+ SOC_DOUBLE_TLV("Line Playback Volume",
+ ALC5623_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Line Playback Switch",
+ ALC5623_SPK_OUT_VOL, 15, 7, 1, 1),
+ SOC_DOUBLE_TLV("Headphone Playback Volume",
+ ALC5623_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Headphone Playback Switch",
+ ALC5623_HP_OUT_VOL, 15, 7, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5623_snd_controls[] = {
+ SOC_DOUBLE_TLV("Auxout Playback Volume",
+ ALC5623_MONO_AUX_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Auxout Playback Switch",
+ ALC5623_MONO_AUX_OUT_VOL, 15, 7, 1, 1),
+ SOC_DOUBLE_TLV("PCM Playback Volume",
+ ALC5623_STEREO_DAC_VOL, 8, 0, 31, 1, vol_tlv),
+ SOC_DOUBLE_TLV("AuxI Capture Volume",
+ ALC5623_AUXIN_VOL, 8, 0, 31, 1, vol_tlv),
+ SOC_DOUBLE_TLV("LineIn Capture Volume",
+ ALC5623_LINE_IN_VOL, 8, 0, 31, 1, vol_tlv),
+ SOC_SINGLE_TLV("Mic1 Capture Volume",
+ ALC5623_MIC_VOL, 8, 31, 1, vol_tlv),
+ SOC_SINGLE_TLV("Mic2 Capture Volume",
+ ALC5623_MIC_VOL, 0, 31, 1, vol_tlv),
+ SOC_DOUBLE_TLV("Rec Capture Volume",
+ ALC5623_ADC_REC_GAIN, 7, 0, 31, 0, adc_rec_tlv),
+ SOC_SINGLE_TLV("Mic 1 Boost Volume",
+ ALC5623_MIC_CTRL, 10, 2, 0, boost_tlv),
+ SOC_SINGLE_TLV("Mic 2 Boost Volume",
+ ALC5623_MIC_CTRL, 8, 2, 0, boost_tlv),
+ SOC_SINGLE_TLV("Digital Boost Volume",
+ ALC5623_ADD_CTRL_REG, 4, 3, 0, dig_tlv),
+};
+
+/*
+ * DAPM Controls
+ */
+static const struct snd_kcontrol_new alc5623_hp_mixer_controls[] = {
+SOC_DAPM_SINGLE("LI2HP Playback Switch", ALC5623_LINE_IN_VOL, 15, 1, 1),
+SOC_DAPM_SINGLE("AUXI2HP Playback Switch", ALC5623_AUXIN_VOL, 15, 1, 1),
+SOC_DAPM_SINGLE("MIC12HP Playback Switch", ALC5623_MIC_ROUTING_CTRL, 15, 1, 1),
+SOC_DAPM_SINGLE("MIC22HP Playback Switch", ALC5623_MIC_ROUTING_CTRL, 7, 1, 1),
+SOC_DAPM_SINGLE("DAC2HP Playback Switch", ALC5623_STEREO_DAC_VOL, 15, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5623_hpl_mixer_controls[] = {
+SOC_DAPM_SINGLE("ADC2HP_L Playback Switch", ALC5623_ADC_REC_GAIN, 15, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5623_hpr_mixer_controls[] = {
+SOC_DAPM_SINGLE("ADC2HP_R Playback Switch", ALC5623_ADC_REC_GAIN, 14, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5623_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("ADC2MONO_L Playback Switch", ALC5623_ADC_REC_GAIN, 13, 1, 1),
+SOC_DAPM_SINGLE("ADC2MONO_R Playback Switch", ALC5623_ADC_REC_GAIN, 12, 1, 1),
+SOC_DAPM_SINGLE("LI2MONO Playback Switch", ALC5623_LINE_IN_VOL, 13, 1, 1),
+SOC_DAPM_SINGLE("AUXI2MONO Playback Switch", ALC5623_AUXIN_VOL, 13, 1, 1),
+SOC_DAPM_SINGLE("MIC12MONO Playback Switch", ALC5623_MIC_ROUTING_CTRL, 13, 1, 1),
+SOC_DAPM_SINGLE("MIC22MONO Playback Switch", ALC5623_MIC_ROUTING_CTRL, 5, 1, 1),
+SOC_DAPM_SINGLE("DAC2MONO Playback Switch", ALC5623_STEREO_DAC_VOL, 13, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5623_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("LI2SPK Playback Switch", ALC5623_LINE_IN_VOL, 14, 1, 1),
+SOC_DAPM_SINGLE("AUXI2SPK Playback Switch", ALC5623_AUXIN_VOL, 14, 1, 1),
+SOC_DAPM_SINGLE("MIC12SPK Playback Switch", ALC5623_MIC_ROUTING_CTRL, 14, 1, 1),
+SOC_DAPM_SINGLE("MIC22SPK Playback Switch", ALC5623_MIC_ROUTING_CTRL, 6, 1, 1),
+SOC_DAPM_SINGLE("DAC2SPK Playback Switch", ALC5623_STEREO_DAC_VOL, 14, 1, 1),
+};
+
+/* Left Record Mixer */
+static const struct snd_kcontrol_new alc5623_captureL_mixer_controls[] = {
+SOC_DAPM_SINGLE("Mic1 Capture Switch", ALC5623_ADC_REC_MIXER, 14, 1, 1),
+SOC_DAPM_SINGLE("Mic2 Capture Switch", ALC5623_ADC_REC_MIXER, 13, 1, 1),
+SOC_DAPM_SINGLE("LineInL Capture Switch", ALC5623_ADC_REC_MIXER, 12, 1, 1),
+SOC_DAPM_SINGLE("Left AuxI Capture Switch", ALC5623_ADC_REC_MIXER, 11, 1, 1),
+SOC_DAPM_SINGLE("HPMixerL Capture Switch", ALC5623_ADC_REC_MIXER, 10, 1, 1),
+SOC_DAPM_SINGLE("SPKMixer Capture Switch", ALC5623_ADC_REC_MIXER, 9, 1, 1),
+SOC_DAPM_SINGLE("MonoMixer Capture Switch", ALC5623_ADC_REC_MIXER, 8, 1, 1),
+};
+
+/* Right Record Mixer */
+static const struct snd_kcontrol_new alc5623_captureR_mixer_controls[] = {
+SOC_DAPM_SINGLE("Mic1 Capture Switch", ALC5623_ADC_REC_MIXER, 6, 1, 1),
+SOC_DAPM_SINGLE("Mic2 Capture Switch", ALC5623_ADC_REC_MIXER, 5, 1, 1),
+SOC_DAPM_SINGLE("LineInR Capture Switch", ALC5623_ADC_REC_MIXER, 4, 1, 1),
+SOC_DAPM_SINGLE("Right AuxI Capture Switch", ALC5623_ADC_REC_MIXER, 3, 1, 1),
+SOC_DAPM_SINGLE("HPMixerR Capture Switch", ALC5623_ADC_REC_MIXER, 2, 1, 1),
+SOC_DAPM_SINGLE("SPKMixer Capture Switch", ALC5623_ADC_REC_MIXER, 1, 1, 1),
+SOC_DAPM_SINGLE("MonoMixer Capture Switch", ALC5623_ADC_REC_MIXER, 0, 1, 1),
+};
+
+static const char *alc5623_spk_n_sour_sel[] = {
+ "RN/-R", "RP/+R", "LN/-R", "Vmid" };
+static const char *alc5623_hpl_out_input_sel[] = {
+ "Vmid", "HP Left Mix"};
+static const char *alc5623_hpr_out_input_sel[] = {
+ "Vmid", "HP Right Mix"};
+static const char *alc5623_spkout_input_sel[] = {
+ "Vmid", "HPOut Mix", "Speaker Mix", "Mono Mix"};
+static const char *alc5623_aux_out_input_sel[] = {
+ "Vmid", "HPOut Mix", "Speaker Mix", "Mono Mix"};
+
+/* auxout output mux */
+static const struct soc_enum alc5623_aux_out_input_enum =
+SOC_ENUM_SINGLE(ALC5623_OUTPUT_MIXER_CTRL, 6, 4, alc5623_aux_out_input_sel);
+static const struct snd_kcontrol_new alc5623_auxout_mux_controls =
+SOC_DAPM_ENUM("Route", alc5623_aux_out_input_enum);
+
+/* speaker output mux */
+static const struct soc_enum alc5623_spkout_input_enum =
+SOC_ENUM_SINGLE(ALC5623_OUTPUT_MIXER_CTRL, 10, 4, alc5623_spkout_input_sel);
+static const struct snd_kcontrol_new alc5623_spkout_mux_controls =
+SOC_DAPM_ENUM("Route", alc5623_spkout_input_enum);
+
+/* headphone left output mux */
+static const struct soc_enum alc5623_hpl_out_input_enum =
+SOC_ENUM_SINGLE(ALC5623_OUTPUT_MIXER_CTRL, 9, 2, alc5623_hpl_out_input_sel);
+static const struct snd_kcontrol_new alc5623_hpl_out_mux_controls =
+SOC_DAPM_ENUM("Route", alc5623_hpl_out_input_enum);
+
+/* headphone right output mux */
+static const struct soc_enum alc5623_hpr_out_input_enum =
+SOC_ENUM_SINGLE(ALC5623_OUTPUT_MIXER_CTRL, 8, 2, alc5623_hpr_out_input_sel);
+static const struct snd_kcontrol_new alc5623_hpr_out_mux_controls =
+SOC_DAPM_ENUM("Route", alc5623_hpr_out_input_enum);
+
+/* speaker output N select */
+static const struct soc_enum alc5623_spk_n_sour_enum =
+SOC_ENUM_SINGLE(ALC5623_OUTPUT_MIXER_CTRL, 14, 4, alc5623_spk_n_sour_sel);
+static const struct snd_kcontrol_new alc5623_spkoutn_mux_controls =
+SOC_DAPM_ENUM("Route", alc5623_spk_n_sour_enum);
+
+static const struct snd_soc_dapm_widget alc5623_dapm_widgets[] = {
+/* Muxes */
+SND_SOC_DAPM_MUX("AuxOut Mux", SND_SOC_NOPM, 0, 0,
+ &alc5623_auxout_mux_controls),
+SND_SOC_DAPM_MUX("SpeakerOut Mux", SND_SOC_NOPM, 0, 0,
+ &alc5623_spkout_mux_controls),
+SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0,
+ &alc5623_hpl_out_mux_controls),
+SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0,
+ &alc5623_hpr_out_mux_controls),
+SND_SOC_DAPM_MUX("SpeakerOut N Mux", SND_SOC_NOPM, 0, 0,
+ &alc5623_spkoutn_mux_controls),
+
+/* output mixers */
+SND_SOC_DAPM_MIXER("HP Mix", SND_SOC_NOPM, 0, 0,
+ &alc5623_hp_mixer_controls[0],
+ ARRAY_SIZE(alc5623_hp_mixer_controls)),
+SND_SOC_DAPM_MIXER("HPR Mix", ALC5623_PWR_MANAG_ADD2, 4, 0,
+ &alc5623_hpr_mixer_controls[0],
+ ARRAY_SIZE(alc5623_hpr_mixer_controls)),
+SND_SOC_DAPM_MIXER("HPL Mix", ALC5623_PWR_MANAG_ADD2, 5, 0,
+ &alc5623_hpl_mixer_controls[0],
+ ARRAY_SIZE(alc5623_hpl_mixer_controls)),
+SND_SOC_DAPM_MIXER("HPOut Mix", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Mono Mix", ALC5623_PWR_MANAG_ADD2, 2, 0,
+ &alc5623_mono_mixer_controls[0],
+ ARRAY_SIZE(alc5623_mono_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mix", ALC5623_PWR_MANAG_ADD2, 3, 0,
+ &alc5623_speaker_mixer_controls[0],
+ ARRAY_SIZE(alc5623_speaker_mixer_controls)),
+
+/* input mixers */
+SND_SOC_DAPM_MIXER("Left Capture Mix", ALC5623_PWR_MANAG_ADD2, 1, 0,
+ &alc5623_captureL_mixer_controls[0],
+ ARRAY_SIZE(alc5623_captureL_mixer_controls)),
+SND_SOC_DAPM_MIXER("Right Capture Mix", ALC5623_PWR_MANAG_ADD2, 0, 0,
+ &alc5623_captureR_mixer_controls[0],
+ ARRAY_SIZE(alc5623_captureR_mixer_controls)),
+
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback",
+ ALC5623_PWR_MANAG_ADD2, 9, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback",
+ ALC5623_PWR_MANAG_ADD2, 8, 0),
+SND_SOC_DAPM_MIXER("I2S Mix", ALC5623_PWR_MANAG_ADD1, 15, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("AuxI Mix", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Line Mix", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture",
+ ALC5623_PWR_MANAG_ADD2, 7, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture",
+ ALC5623_PWR_MANAG_ADD2, 6, 0),
+SND_SOC_DAPM_PGA("Left Headphone", ALC5623_PWR_MANAG_ADD3, 10, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Headphone", ALC5623_PWR_MANAG_ADD3, 9, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpeakerOut", ALC5623_PWR_MANAG_ADD3, 12, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left AuxOut", ALC5623_PWR_MANAG_ADD3, 14, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right AuxOut", ALC5623_PWR_MANAG_ADD3, 13, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left LineIn", ALC5623_PWR_MANAG_ADD3, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right LineIn", ALC5623_PWR_MANAG_ADD3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left AuxI", ALC5623_PWR_MANAG_ADD3, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right AuxI", ALC5623_PWR_MANAG_ADD3, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC1 PGA", ALC5623_PWR_MANAG_ADD3, 3, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC2 PGA", ALC5623_PWR_MANAG_ADD3, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC1 Pre Amp", ALC5623_PWR_MANAG_ADD3, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC2 Pre Amp", ALC5623_PWR_MANAG_ADD3, 0, 0, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias1", ALC5623_PWR_MANAG_ADD1, 11, 0),
+
+SND_SOC_DAPM_OUTPUT("AUXOUTL"),
+SND_SOC_DAPM_OUTPUT("AUXOUTR"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_OUTPUT("SPKOUT"),
+SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+SND_SOC_DAPM_INPUT("LINEINL"),
+SND_SOC_DAPM_INPUT("LINEINR"),
+SND_SOC_DAPM_INPUT("AUXINL"),
+SND_SOC_DAPM_INPUT("AUXINR"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2"),
+SND_SOC_DAPM_VMID("Vmid"),
+};
+
+static const char *alc5623_amp_names[] = {"AB Amp", "D Amp"};
+static const struct soc_enum alc5623_amp_enum =
+ SOC_ENUM_SINGLE(ALC5623_OUTPUT_MIXER_CTRL, 13, 2, alc5623_amp_names);
+static const struct snd_kcontrol_new alc5623_amp_mux_controls =
+ SOC_DAPM_ENUM("Route", alc5623_amp_enum);
+
+static const struct snd_soc_dapm_widget alc5623_dapm_amp_widgets[] = {
+SND_SOC_DAPM_PGA_E("D Amp", ALC5623_PWR_MANAG_ADD2, 14, 0, NULL, 0,
+ amp_mixer_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_PGA("AB Amp", ALC5623_PWR_MANAG_ADD2, 15, 0, NULL, 0),
+SND_SOC_DAPM_MUX("AB-D Amp Mux", SND_SOC_NOPM, 0, 0,
+ &alc5623_amp_mux_controls),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* virtual mixer - mixes left & right channels */
+ {"I2S Mix", NULL, "Left DAC"},
+ {"I2S Mix", NULL, "Right DAC"},
+ {"Line Mix", NULL, "Right LineIn"},
+ {"Line Mix", NULL, "Left LineIn"},
+ {"AuxI Mix", NULL, "Left AuxI"},
+ {"AuxI Mix", NULL, "Right AuxI"},
+ {"AUXOUTL", NULL, "Left AuxOut"},
+ {"AUXOUTR", NULL, "Right AuxOut"},
+
+ /* HP mixer */
+ {"HPL Mix", "ADC2HP_L Playback Switch", "Left Capture Mix"},
+ {"HPL Mix", NULL, "HP Mix"},
+ {"HPR Mix", "ADC2HP_R Playback Switch", "Right Capture Mix"},
+ {"HPR Mix", NULL, "HP Mix"},
+ {"HP Mix", "LI2HP Playback Switch", "Line Mix"},
+ {"HP Mix", "AUXI2HP Playback Switch", "AuxI Mix"},
+ {"HP Mix", "MIC12HP Playback Switch", "MIC1 PGA"},
+ {"HP Mix", "MIC22HP Playback Switch", "MIC2 PGA"},
+ {"HP Mix", "DAC2HP Playback Switch", "I2S Mix"},
+
+ /* speaker mixer */
+ {"Speaker Mix", "LI2SPK Playback Switch", "Line Mix"},
+ {"Speaker Mix", "AUXI2SPK Playback Switch", "AuxI Mix"},
+ {"Speaker Mix", "MIC12SPK Playback Switch", "MIC1 PGA"},
+ {"Speaker Mix", "MIC22SPK Playback Switch", "MIC2 PGA"},
+ {"Speaker Mix", "DAC2SPK Playback Switch", "I2S Mix"},
+
+ /* mono mixer */
+ {"Mono Mix", "ADC2MONO_L Playback Switch", "Left Capture Mix"},
+ {"Mono Mix", "ADC2MONO_R Playback Switch", "Right Capture Mix"},
+ {"Mono Mix", "LI2MONO Playback Switch", "Line Mix"},
+ {"Mono Mix", "AUXI2MONO Playback Switch", "AuxI Mix"},
+ {"Mono Mix", "MIC12MONO Playback Switch", "MIC1 PGA"},
+ {"Mono Mix", "MIC22MONO Playback Switch", "MIC2 PGA"},
+ {"Mono Mix", "DAC2MONO Playback Switch", "I2S Mix"},
+
+ /* Left record mixer */
+ {"Left Capture Mix", "LineInL Capture Switch", "LINEINL"},
+ {"Left Capture Mix", "Left AuxI Capture Switch", "AUXINL"},
+ {"Left Capture Mix", "Mic1 Capture Switch", "MIC1 Pre Amp"},
+ {"Left Capture Mix", "Mic2 Capture Switch", "MIC2 Pre Amp"},
+ {"Left Capture Mix", "HPMixerL Capture Switch", "HPL Mix"},
+ {"Left Capture Mix", "SPKMixer Capture Switch", "Speaker Mix"},
+ {"Left Capture Mix", "MonoMixer Capture Switch", "Mono Mix"},
+
+ /*Right record mixer */
+ {"Right Capture Mix", "LineInR Capture Switch", "LINEINR"},
+ {"Right Capture Mix", "Right AuxI Capture Switch", "AUXINR"},
+ {"Right Capture Mix", "Mic1 Capture Switch", "MIC1 Pre Amp"},
+ {"Right Capture Mix", "Mic2 Capture Switch", "MIC2 Pre Amp"},
+ {"Right Capture Mix", "HPMixerR Capture Switch", "HPR Mix"},
+ {"Right Capture Mix", "SPKMixer Capture Switch", "Speaker Mix"},
+ {"Right Capture Mix", "MonoMixer Capture Switch", "Mono Mix"},
+
+ /* headphone left mux */
+ {"Left Headphone Mux", "HP Left Mix", "HPL Mix"},
+ {"Left Headphone Mux", "Vmid", "Vmid"},
+
+ /* headphone right mux */
+ {"Right Headphone Mux", "HP Right Mix", "HPR Mix"},
+ {"Right Headphone Mux", "Vmid", "Vmid"},
+
+ /* speaker out mux */
+ {"SpeakerOut Mux", "Vmid", "Vmid"},
+ {"SpeakerOut Mux", "HPOut Mix", "HPOut Mix"},
+ {"SpeakerOut Mux", "Speaker Mix", "Speaker Mix"},
+ {"SpeakerOut Mux", "Mono Mix", "Mono Mix"},
+
+ /* Mono/Aux Out mux */
+ {"AuxOut Mux", "Vmid", "Vmid"},
+ {"AuxOut Mux", "HPOut Mix", "HPOut Mix"},
+ {"AuxOut Mux", "Speaker Mix", "Speaker Mix"},
+ {"AuxOut Mux", "Mono Mix", "Mono Mix"},
+
+ /* output pga */
+ {"HPL", NULL, "Left Headphone"},
+ {"Left Headphone", NULL, "Left Headphone Mux"},
+ {"HPR", NULL, "Right Headphone"},
+ {"Right Headphone", NULL, "Right Headphone Mux"},
+ {"Left AuxOut", NULL, "AuxOut Mux"},
+ {"Right AuxOut", NULL, "AuxOut Mux"},
+
+ /* input pga */
+ {"Left LineIn", NULL, "LINEINL"},
+ {"Right LineIn", NULL, "LINEINR"},
+ {"Left AuxI", NULL, "AUXINL"},
+ {"Right AuxI", NULL, "AUXINR"},
+ {"MIC1 Pre Amp", NULL, "MIC1"},
+ {"MIC2 Pre Amp", NULL, "MIC2"},
+ {"MIC1 PGA", NULL, "MIC1 Pre Amp"},
+ {"MIC2 PGA", NULL, "MIC2 Pre Amp"},
+
+ /* left ADC */
+ {"Left ADC", NULL, "Left Capture Mix"},
+
+ /* right ADC */
+ {"Right ADC", NULL, "Right Capture Mix"},
+
+ {"SpeakerOut N Mux", "RN/-R", "SpeakerOut"},
+ {"SpeakerOut N Mux", "RP/+R", "SpeakerOut"},
+ {"SpeakerOut N Mux", "LN/-R", "SpeakerOut"},
+ {"SpeakerOut N Mux", "Vmid", "Vmid"},
+
+ {"SPKOUT", NULL, "SpeakerOut"},
+ {"SPKOUTN", NULL, "SpeakerOut N Mux"},
+};
+
+static const struct snd_soc_dapm_route intercon_spk[] = {
+ {"SpeakerOut", NULL, "SpeakerOut Mux"},
+};
+
+static const struct snd_soc_dapm_route intercon_amp_spk[] = {
+ {"AB Amp", NULL, "SpeakerOut Mux"},
+ {"D Amp", NULL, "SpeakerOut Mux"},
+ {"AB-D Amp Mux", "AB Amp", "AB Amp"},
+ {"AB-D Amp Mux", "D Amp", "D Amp"},
+ {"SpeakerOut", NULL, "AB-D Amp Mux"},
+};
+
+/* PLL divisors */
+struct _pll_div {
+ u32 pll_in;
+ u32 pll_out;
+ u16 regvalue;
+};
+
+/* Note : pll code from original alc5623 driver. Not sure of how good it is */
+/* useful only for master mode */
+static const struct _pll_div codec_master_pll_div[] = {
+
+ { 2048000, 8192000, 0x0ea0},
+ { 3686400, 8192000, 0x4e27},
+ { 12000000, 8192000, 0x456b},
+ { 13000000, 8192000, 0x495f},
+ { 13100000, 8192000, 0x0320},
+ { 2048000, 11289600, 0xf637},
+ { 3686400, 11289600, 0x2f22},
+ { 12000000, 11289600, 0x3e2f},
+ { 13000000, 11289600, 0x4d5b},
+ { 13100000, 11289600, 0x363b},
+ { 2048000, 16384000, 0x1ea0},
+ { 3686400, 16384000, 0x9e27},
+ { 12000000, 16384000, 0x452b},
+ { 13000000, 16384000, 0x542f},
+ { 13100000, 16384000, 0x03a0},
+ { 2048000, 16934400, 0xe625},
+ { 3686400, 16934400, 0x9126},
+ { 12000000, 16934400, 0x4d2c},
+ { 13000000, 16934400, 0x742f},
+ { 13100000, 16934400, 0x3c27},
+ { 2048000, 22579200, 0x2aa0},
+ { 3686400, 22579200, 0x2f20},
+ { 12000000, 22579200, 0x7e2f},
+ { 13000000, 22579200, 0x742f},
+ { 13100000, 22579200, 0x3c27},
+ { 2048000, 24576000, 0x2ea0},
+ { 3686400, 24576000, 0xee27},
+ { 12000000, 24576000, 0x2915},
+ { 13000000, 24576000, 0x772e},
+ { 13100000, 24576000, 0x0d20},
+};
+
+static const struct _pll_div codec_slave_pll_div[] = {
+
+ { 1024000, 16384000, 0x3ea0},
+ { 1411200, 22579200, 0x3ea0},
+ { 1536000, 24576000, 0x3ea0},
+ { 2048000, 16384000, 0x1ea0},
+ { 2822400, 22579200, 0x1ea0},
+ { 3072000, 24576000, 0x1ea0},
+
+};
+
+static int alc5623_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ int i;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int gbl_clk = 0, pll_div = 0;
+ u16 reg;
+
+ if (pll_id < ALC5623_PLL_FR_MCLK || pll_id > ALC5623_PLL_FR_BCK)
+ return -ENODEV;
+
+ /* Disable PLL power */
+ snd_soc_update_bits(codec, ALC5623_PWR_MANAG_ADD2,
+ ALC5623_PWR_ADD2_PLL,
+ 0);
+
+ /* pll is not used in slave mode */
+ reg = snd_soc_read(codec, ALC5623_DAI_CONTROL);
+ if (reg & ALC5623_DAI_SDP_SLAVE_MODE)
+ return 0;
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ switch (pll_id) {
+ case ALC5623_PLL_FR_MCLK:
+ for (i = 0; i < ARRAY_SIZE(codec_master_pll_div); i++) {
+ if (codec_master_pll_div[i].pll_in == freq_in
+ && codec_master_pll_div[i].pll_out == freq_out) {
+ /* PLL source from MCLK */
+ pll_div = codec_master_pll_div[i].regvalue;
+ break;
+ }
+ }
+ break;
+ case ALC5623_PLL_FR_BCK:
+ for (i = 0; i < ARRAY_SIZE(codec_slave_pll_div); i++) {
+ if (codec_slave_pll_div[i].pll_in == freq_in
+ && codec_slave_pll_div[i].pll_out == freq_out) {
+ /* PLL source from Bitclk */
+ gbl_clk = ALC5623_GBL_CLK_PLL_SOUR_SEL_BITCLK;
+ pll_div = codec_slave_pll_div[i].regvalue;
+ break;
+ }
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!pll_div)
+ return -EINVAL;
+
+ snd_soc_write(codec, ALC5623_GLOBAL_CLK_CTRL_REG, gbl_clk);
+ snd_soc_write(codec, ALC5623_PLL_CTRL, pll_div);
+ snd_soc_update_bits(codec, ALC5623_PWR_MANAG_ADD2,
+ ALC5623_PWR_ADD2_PLL,
+ ALC5623_PWR_ADD2_PLL);
+ gbl_clk |= ALC5623_GBL_CLK_SYS_SOUR_SEL_PLL;
+ snd_soc_write(codec, ALC5623_GLOBAL_CLK_CTRL_REG, gbl_clk);
+
+ return 0;
+}
+
+struct _coeff_div {
+ u16 fs;
+ u16 regvalue;
+};
+
+/* codec hifi mclk (after PLL) clock divider coefficients */
+/* values inspired from column BCLK=32Fs of Appendix A table */
+static const struct _coeff_div coeff_div[] = {
+ {256*8, 0x3a69},
+ {384*8, 0x3c6b},
+ {256*4, 0x2a69},
+ {384*4, 0x2c6b},
+ {256*2, 0x1a69},
+ {384*2, 0x1c6b},
+ {256*1, 0x0a69},
+ {384*1, 0x0c6b},
+};
+
+static int get_coeff(struct snd_soc_codec *codec, int rate)
+{
+ struct alc5623_priv *alc5623 = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].fs * rate == alc5623->sysclk)
+ return i;
+ }
+ return -EINVAL;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int alc5623_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct alc5623_priv *alc5623 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case 8192000:
+ case 11289600:
+ case 12288000:
+ case 16384000:
+ case 16934400:
+ case 18432000:
+ case 22579200:
+ case 24576000:
+ alc5623->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int alc5623_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface = ALC5623_DAI_SDP_MASTER_MODE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ iface = ALC5623_DAI_SDP_SLAVE_MODE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= ALC5623_DAI_I2S_DF_I2S;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ iface |= ALC5623_DAI_I2S_DF_RIGHT;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= ALC5623_DAI_I2S_DF_LEFT;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= ALC5623_DAI_I2S_DF_PCM;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= ALC5623_DAI_I2S_DF_PCM | ALC5623_DAI_I2S_PCM_MODE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= ALC5623_DAI_MAIN_I2S_BCLK_POL_CTRL;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= ALC5623_DAI_MAIN_I2S_BCLK_POL_CTRL;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return snd_soc_write(codec, ALC5623_DAI_CONTROL, iface);
+}
+
+static int alc5623_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct alc5623_priv *alc5623 = snd_soc_codec_get_drvdata(codec);
+ int coeff, rate;
+ u16 iface;
+
+ iface = snd_soc_read(codec, ALC5623_DAI_CONTROL);
+ iface &= ~ALC5623_DAI_I2S_DL_MASK;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iface |= ALC5623_DAI_I2S_DL_16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= ALC5623_DAI_I2S_DL_20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= ALC5623_DAI_I2S_DL_24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= ALC5623_DAI_I2S_DL_32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface & srate */
+ snd_soc_write(codec, ALC5623_DAI_CONTROL, iface);
+ rate = params_rate(params);
+ coeff = get_coeff(codec, rate);
+ if (coeff < 0)
+ return -EINVAL;
+
+ coeff = coeff_div[coeff].regvalue;
+ dev_dbg(codec->dev, "%s: sysclk=%d,rate=%d,coeff=0x%04x\n",
+ __func__, alc5623->sysclk, rate, coeff);
+ snd_soc_write(codec, ALC5623_STEREO_AD_DA_CLK_CTRL, coeff);
+
+ return 0;
+}
+
+static int alc5623_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 hp_mute = ALC5623_MISC_M_DAC_L_INPUT | ALC5623_MISC_M_DAC_R_INPUT;
+ u16 mute_reg = snd_soc_read(codec, ALC5623_MISC_CTRL) & ~hp_mute;
+
+ if (mute)
+ mute_reg |= hp_mute;
+
+ return snd_soc_write(codec, ALC5623_MISC_CTRL, mute_reg);
+}
+
+#define ALC5623_ADD2_POWER_EN (ALC5623_PWR_ADD2_VREF \
+ | ALC5623_PWR_ADD2_DAC_REF_CIR)
+
+#define ALC5623_ADD3_POWER_EN (ALC5623_PWR_ADD3_MAIN_BIAS \
+ | ALC5623_PWR_ADD3_MIC1_BOOST_AD)
+
+#define ALC5623_ADD1_POWER_EN \
+ (ALC5623_PWR_ADD1_SHORT_CURR_DET_EN | ALC5623_PWR_ADD1_SOFTGEN_EN \
+ | ALC5623_PWR_ADD1_DEPOP_BUF_HP | ALC5623_PWR_ADD1_HP_OUT_AMP \
+ | ALC5623_PWR_ADD1_HP_OUT_ENH_AMP)
+
+#define ALC5623_ADD1_POWER_EN_5622 \
+ (ALC5623_PWR_ADD1_SHORT_CURR_DET_EN \
+ | ALC5623_PWR_ADD1_HP_OUT_AMP)
+
+static void enable_power_depop(struct snd_soc_codec *codec)
+{
+ struct alc5623_priv *alc5623 = snd_soc_codec_get_drvdata(codec);
+
+ snd_soc_update_bits(codec, ALC5623_PWR_MANAG_ADD1,
+ ALC5623_PWR_ADD1_SOFTGEN_EN,
+ ALC5623_PWR_ADD1_SOFTGEN_EN);
+
+ snd_soc_write(codec, ALC5623_PWR_MANAG_ADD3, ALC5623_ADD3_POWER_EN);
+
+ snd_soc_update_bits(codec, ALC5623_MISC_CTRL,
+ ALC5623_MISC_HP_DEPOP_MODE2_EN,
+ ALC5623_MISC_HP_DEPOP_MODE2_EN);
+
+ msleep(500);
+
+ snd_soc_write(codec, ALC5623_PWR_MANAG_ADD2, ALC5623_ADD2_POWER_EN);
+
+ /* avoid writing '1' into 5622 reserved bits */
+ if (alc5623->id == 0x22)
+ snd_soc_write(codec, ALC5623_PWR_MANAG_ADD1,
+ ALC5623_ADD1_POWER_EN_5622);
+ else
+ snd_soc_write(codec, ALC5623_PWR_MANAG_ADD1,
+ ALC5623_ADD1_POWER_EN);
+
+ /* disable HP Depop2 */
+ snd_soc_update_bits(codec, ALC5623_MISC_CTRL,
+ ALC5623_MISC_HP_DEPOP_MODE2_EN,
+ 0);
+
+}
+
+static int alc5623_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ enable_power_depop(codec);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ snd_soc_write(codec, ALC5623_PWR_MANAG_ADD2,
+ ALC5623_PWR_ADD2_VREF);
+ snd_soc_write(codec, ALC5623_PWR_MANAG_ADD3,
+ ALC5623_PWR_ADD3_MAIN_BIAS);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ snd_soc_write(codec, ALC5623_PWR_MANAG_ADD2, 0);
+ snd_soc_write(codec, ALC5623_PWR_MANAG_ADD3, 0);
+ snd_soc_write(codec, ALC5623_PWR_MANAG_ADD1, 0);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define ALC5623_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \
+ | SNDRV_PCM_FMTBIT_S24_LE \
+ | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops alc5623_dai_ops = {
+ .hw_params = alc5623_pcm_hw_params,
+ .digital_mute = alc5623_mute,
+ .set_fmt = alc5623_set_dai_fmt,
+ .set_sysclk = alc5623_set_dai_sysclk,
+ .set_pll = alc5623_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver alc5623_dai = {
+ .name = "alc5623-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = ALC5623_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = ALC5623_FORMATS,},
+
+ .ops = &alc5623_dai_ops,
+};
+
+static int alc5623_suspend(struct snd_soc_codec *codec, pm_message_t mesg)
+{
+ alc5623_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int alc5623_resume(struct snd_soc_codec *codec)
+{
+ int i, step = codec->driver->reg_cache_step;
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 2 ; i < codec->driver->reg_cache_size ; i += step)
+ snd_soc_write(codec, i, cache[i]);
+
+ alc5623_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge alc5623 caps */
+ if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) {
+ alc5623_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ codec->dapm.bias_level = SND_SOC_BIAS_ON;
+ alc5623_set_bias_level(codec, codec->dapm.bias_level);
+ }
+
+ return 0;
+}
+
+static int alc5623_probe(struct snd_soc_codec *codec)
+{
+ struct alc5623_priv *alc5623 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, alc5623->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ alc5623_reset(codec);
+ alc5623_fill_cache(codec);
+
+ /* power on device */
+ alc5623_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ if (alc5623->add_ctrl) {
+ snd_soc_write(codec, ALC5623_ADD_CTRL_REG,
+ alc5623->add_ctrl);
+ }
+
+ if (alc5623->jack_det_ctrl) {
+ snd_soc_write(codec, ALC5623_JACK_DET_CTRL,
+ alc5623->jack_det_ctrl);
+ }
+
+ switch (alc5623->id) {
+ case 0x21:
+ snd_soc_add_controls(codec, rt5621_vol_snd_controls,
+ ARRAY_SIZE(rt5621_vol_snd_controls));
+ break;
+ case 0x22:
+ snd_soc_add_controls(codec, rt5622_vol_snd_controls,
+ ARRAY_SIZE(rt5622_vol_snd_controls));
+ break;
+ case 0x23:
+ snd_soc_add_controls(codec, alc5623_vol_snd_controls,
+ ARRAY_SIZE(alc5623_vol_snd_controls));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_add_controls(codec, alc5623_snd_controls,
+ ARRAY_SIZE(alc5623_snd_controls));
+
+ snd_soc_dapm_new_controls(dapm, alc5623_dapm_widgets,
+ ARRAY_SIZE(alc5623_dapm_widgets));
+
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ switch (alc5623->id) {
+ case 0x21:
+ case 0x22:
+ snd_soc_dapm_new_controls(dapm, alc5623_dapm_amp_widgets,
+ ARRAY_SIZE(alc5623_dapm_amp_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon_amp_spk,
+ ARRAY_SIZE(intercon_amp_spk));
+ break;
+ case 0x23:
+ snd_soc_dapm_add_routes(dapm, intercon_spk,
+ ARRAY_SIZE(intercon_spk));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+/* power down chip */
+static int alc5623_remove(struct snd_soc_codec *codec)
+{
+ alc5623_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_device_alc5623 = {
+ .probe = alc5623_probe,
+ .remove = alc5623_remove,
+ .suspend = alc5623_suspend,
+ .resume = alc5623_resume,
+ .set_bias_level = alc5623_set_bias_level,
+ .reg_cache_size = ALC5623_VENDOR_ID2+2,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+};
+
+/*
+ * ALC5623 2 wire address is determined by A1 pin
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+static int alc5623_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct alc5623_platform_data *pdata;
+ struct alc5623_priv *alc5623;
+ int ret, vid1, vid2;
+
+ vid1 = i2c_smbus_read_word_data(client, ALC5623_VENDOR_ID1);
+ if (vid1 < 0) {
+ dev_err(&client->dev, "failed to read I2C\n");
+ return -EIO;
+ }
+ vid1 = ((vid1 & 0xff) << 8) | (vid1 >> 8);
+
+ vid2 = i2c_smbus_read_byte_data(client, ALC5623_VENDOR_ID2);
+ if (vid2 < 0) {
+ dev_err(&client->dev, "failed to read I2C\n");
+ return -EIO;
+ }
+
+ if ((vid1 != 0x10ec) || (vid2 != id->driver_data)) {
+ dev_err(&client->dev, "unknown or wrong codec\n");
+ dev_err(&client->dev, "Expected %x:%lx, got %x:%x\n",
+ 0x10ec, id->driver_data,
+ vid1, vid2);
+ return -ENODEV;
+ }
+
+ dev_dbg(&client->dev, "Found codec id : alc56%02x\n", vid2);
+
+ alc5623 = kzalloc(sizeof(struct alc5623_priv), GFP_KERNEL);
+ if (alc5623 == NULL)
+ return -ENOMEM;
+
+ pdata = client->dev.platform_data;
+ if (pdata) {
+ alc5623->add_ctrl = pdata->add_ctrl;
+ alc5623->jack_det_ctrl = pdata->jack_det_ctrl;
+ }
+
+ alc5623->id = vid2;
+ switch (alc5623->id) {
+ case 0x21:
+ alc5623_dai.name = "alc5621-hifi";
+ break;
+ case 0x22:
+ alc5623_dai.name = "alc5622-hifi";
+ break;
+ case 0x23:
+ alc5623_dai.name = "alc5623-hifi";
+ break;
+ default:
+ kfree(alc5623);
+ return -EINVAL;
+ }
+
+ i2c_set_clientdata(client, alc5623);
+ alc5623->control_data = client;
+ alc5623->control_type = SND_SOC_I2C;
+ mutex_init(&alc5623->mutex);
+
+ ret = snd_soc_register_codec(&client->dev,
+ &soc_codec_device_alc5623, &alc5623_dai, 1);
+ if (ret != 0) {
+ dev_err(&client->dev, "Failed to register codec: %d\n", ret);
+ kfree(alc5623);
+ }
+
+ return ret;
+}
+
+static int alc5623_i2c_remove(struct i2c_client *client)
+{
+ struct alc5623_priv *alc5623 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_codec(&client->dev);
+ kfree(alc5623);
+ return 0;
+}
+
+static const struct i2c_device_id alc5623_i2c_table[] = {
+ {"alc5621", 0x21},
+ {"alc5622", 0x22},
+ {"alc5623", 0x23},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, alc5623_i2c_table);
+
+/* i2c codec control layer */
+static struct i2c_driver alc5623_i2c_driver = {
+ .driver = {
+ .name = "alc562x-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = alc5623_i2c_probe,
+ .remove = __devexit_p(alc5623_i2c_remove),
+ .id_table = alc5623_i2c_table,
+};
+
+static int __init alc5623_modinit(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&alc5623_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "%s: can't add i2c driver", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+module_init(alc5623_modinit);
+
+static void __exit alc5623_modexit(void)
+{
+ i2c_del_driver(&alc5623_i2c_driver);
+}
+module_exit(alc5623_modexit);
+
+MODULE_DESCRIPTION("ASoC alc5621/2/3 driver");
+MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/alc5623.h b/sound/soc/codecs/alc5623.h
new file mode 100644
index 00000000..f3d68260
--- /dev/null
+++ b/sound/soc/codecs/alc5623.h
@@ -0,0 +1,161 @@
+/*
+ * alc5623.h -- alc562[123] ALSA Soc Audio driver
+ *
+ * Copyright 2008 Realtek Microelectronics
+ * Copyright 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
+ *
+ * Author: flove <flove@realtek.com>
+ * Arnaud Patard <arnaud.patard@rtp-net.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _ALC5623_H
+#define _ALC5623_H
+
+#define ALC5623_RESET 0x00
+/* 5621 5622 5623 */
+/* speaker output vol 2 2 */
+/* line output vol 4 2 */
+/* HP output vol 4 0 4 */
+#define ALC5623_SPK_OUT_VOL 0x02
+#define ALC5623_HP_OUT_VOL 0x04
+#define ALC5623_MONO_AUX_OUT_VOL 0x06
+#define ALC5623_AUXIN_VOL 0x08
+#define ALC5623_LINE_IN_VOL 0x0A
+#define ALC5623_STEREO_DAC_VOL 0x0C
+#define ALC5623_MIC_VOL 0x0E
+#define ALC5623_MIC_ROUTING_CTRL 0x10
+#define ALC5623_ADC_REC_GAIN 0x12
+#define ALC5623_ADC_REC_MIXER 0x14
+#define ALC5623_SOFT_VOL_CTRL_TIME 0x16
+/* ALC5623_OUTPUT_MIXER_CTRL : */
+/* same remark as for reg 2 line vs speaker */
+#define ALC5623_OUTPUT_MIXER_CTRL 0x1C
+#define ALC5623_MIC_CTRL 0x22
+
+#define ALC5623_DAI_CONTROL 0x34
+#define ALC5623_DAI_SDP_MASTER_MODE (0 << 15)
+#define ALC5623_DAI_SDP_SLAVE_MODE (1 << 15)
+#define ALC5623_DAI_I2S_PCM_MODE (1 << 14)
+#define ALC5623_DAI_MAIN_I2S_BCLK_POL_CTRL (1 << 7)
+#define ALC5623_DAI_ADC_DATA_L_R_SWAP (1 << 5)
+#define ALC5623_DAI_DAC_DATA_L_R_SWAP (1 << 4)
+#define ALC5623_DAI_I2S_DL_MASK (3 << 2)
+#define ALC5623_DAI_I2S_DL_32 (3 << 2)
+#define ALC5623_DAI_I2S_DL_24 (2 << 2)
+#define ALC5623_DAI_I2S_DL_20 (1 << 2)
+#define ALC5623_DAI_I2S_DL_16 (0 << 2)
+#define ALC5623_DAI_I2S_DF_PCM (3 << 0)
+#define ALC5623_DAI_I2S_DF_LEFT (2 << 0)
+#define ALC5623_DAI_I2S_DF_RIGHT (1 << 0)
+#define ALC5623_DAI_I2S_DF_I2S (0 << 0)
+
+#define ALC5623_STEREO_AD_DA_CLK_CTRL 0x36
+#define ALC5623_COMPANDING_CTRL 0x38
+
+#define ALC5623_PWR_MANAG_ADD1 0x3A
+#define ALC5623_PWR_ADD1_MAIN_I2S_EN (1 << 15)
+#define ALC5623_PWR_ADD1_ZC_DET_PD_EN (1 << 14)
+#define ALC5623_PWR_ADD1_MIC1_BIAS_EN (1 << 11)
+#define ALC5623_PWR_ADD1_SHORT_CURR_DET_EN (1 << 10)
+#define ALC5623_PWR_ADD1_SOFTGEN_EN (1 << 8) /* rsvd on 5622 */
+#define ALC5623_PWR_ADD1_DEPOP_BUF_HP (1 << 6) /* rsvd on 5622 */
+#define ALC5623_PWR_ADD1_HP_OUT_AMP (1 << 5)
+#define ALC5623_PWR_ADD1_HP_OUT_ENH_AMP (1 << 4) /* rsvd on 5622 */
+#define ALC5623_PWR_ADD1_DEPOP_BUF_AUX (1 << 2)
+#define ALC5623_PWR_ADD1_AUX_OUT_AMP (1 << 1)
+#define ALC5623_PWR_ADD1_AUX_OUT_ENH_AMP (1 << 0) /* rsvd on 5622 */
+
+#define ALC5623_PWR_MANAG_ADD2 0x3C
+#define ALC5623_PWR_ADD2_LINEOUT (1 << 15) /* rt5623 */
+#define ALC5623_PWR_ADD2_CLASS_AB (1 << 15) /* rt5621 */
+#define ALC5623_PWR_ADD2_CLASS_D (1 << 14) /* rt5621 */
+#define ALC5623_PWR_ADD2_VREF (1 << 13)
+#define ALC5623_PWR_ADD2_PLL (1 << 12)
+#define ALC5623_PWR_ADD2_DAC_REF_CIR (1 << 10)
+#define ALC5623_PWR_ADD2_L_DAC_CLK (1 << 9)
+#define ALC5623_PWR_ADD2_R_DAC_CLK (1 << 8)
+#define ALC5623_PWR_ADD2_L_ADC_CLK_GAIN (1 << 7)
+#define ALC5623_PWR_ADD2_R_ADC_CLK_GAIN (1 << 6)
+#define ALC5623_PWR_ADD2_L_HP_MIXER (1 << 5)
+#define ALC5623_PWR_ADD2_R_HP_MIXER (1 << 4)
+#define ALC5623_PWR_ADD2_SPK_MIXER (1 << 3)
+#define ALC5623_PWR_ADD2_MONO_MIXER (1 << 2)
+#define ALC5623_PWR_ADD2_L_ADC_REC_MIXER (1 << 1)
+#define ALC5623_PWR_ADD2_R_ADC_REC_MIXER (1 << 0)
+
+#define ALC5623_PWR_MANAG_ADD3 0x3E
+#define ALC5623_PWR_ADD3_MAIN_BIAS (1 << 15)
+#define ALC5623_PWR_ADD3_AUXOUT_L_VOL_AMP (1 << 14)
+#define ALC5623_PWR_ADD3_AUXOUT_R_VOL_AMP (1 << 13)
+#define ALC5623_PWR_ADD3_SPK_OUT (1 << 12)
+#define ALC5623_PWR_ADD3_HP_L_OUT_VOL (1 << 10)
+#define ALC5623_PWR_ADD3_HP_R_OUT_VOL (1 << 9)
+#define ALC5623_PWR_ADD3_LINEIN_L_VOL (1 << 7)
+#define ALC5623_PWR_ADD3_LINEIN_R_VOL (1 << 6)
+#define ALC5623_PWR_ADD3_AUXIN_L_VOL (1 << 5)
+#define ALC5623_PWR_ADD3_AUXIN_R_VOL (1 << 4)
+#define ALC5623_PWR_ADD3_MIC1_FUN_CTRL (1 << 3)
+#define ALC5623_PWR_ADD3_MIC2_FUN_CTRL (1 << 2)
+#define ALC5623_PWR_ADD3_MIC1_BOOST_AD (1 << 1)
+#define ALC5623_PWR_ADD3_MIC2_BOOST_AD (1 << 0)
+
+#define ALC5623_ADD_CTRL_REG 0x40
+
+#define ALC5623_GLOBAL_CLK_CTRL_REG 0x42
+#define ALC5623_GBL_CLK_SYS_SOUR_SEL_PLL (1 << 15)
+#define ALC5623_GBL_CLK_SYS_SOUR_SEL_MCLK (0 << 15)
+#define ALC5623_GBL_CLK_PLL_SOUR_SEL_BITCLK (1 << 14)
+#define ALC5623_GBL_CLK_PLL_SOUR_SEL_MCLK (0 << 14)
+#define ALC5623_GBL_CLK_PLL_DIV_RATIO_DIV8 (3 << 1)
+#define ALC5623_GBL_CLK_PLL_DIV_RATIO_DIV4 (2 << 1)
+#define ALC5623_GBL_CLK_PLL_DIV_RATIO_DIV2 (1 << 1)
+#define ALC5623_GBL_CLK_PLL_DIV_RATIO_DIV1 (0 << 1)
+#define ALC5623_GBL_CLK_PLL_PRE_DIV2 (1 << 0)
+#define ALC5623_GBL_CLK_PLL_PRE_DIV1 (0 << 0)
+
+#define ALC5623_PLL_CTRL 0x44
+#define ALC5623_PLL_CTRL_N_VAL(n) (((n)&0xff) << 8)
+#define ALC5623_PLL_CTRL_K_VAL(k) (((k)&0x7) << 4)
+#define ALC5623_PLL_CTRL_M_VAL(m) ((m)&0xf)
+
+#define ALC5623_GPIO_OUTPUT_PIN_CTRL 0x4A
+#define ALC5623_GPIO_PIN_CONFIG 0x4C
+#define ALC5623_GPIO_PIN_POLARITY 0x4E
+#define ALC5623_GPIO_PIN_STICKY 0x50
+#define ALC5623_GPIO_PIN_WAKEUP 0x52
+#define ALC5623_GPIO_PIN_STATUS 0x54
+#define ALC5623_GPIO_PIN_SHARING 0x56
+#define ALC5623_OVER_CURR_STATUS 0x58
+#define ALC5623_JACK_DET_CTRL 0x5A
+
+#define ALC5623_MISC_CTRL 0x5E
+#define ALC5623_MISC_DISABLE_FAST_VREG (1 << 15)
+#define ALC5623_MISC_SPK_CLASS_AB_OC_PD (1 << 13) /* 5621 */
+#define ALC5623_MISC_SPK_CLASS_AB_OC_DET (1 << 12) /* 5621 */
+#define ALC5623_MISC_HP_DEPOP_MODE3_EN (1 << 10)
+#define ALC5623_MISC_HP_DEPOP_MODE2_EN (1 << 9)
+#define ALC5623_MISC_HP_DEPOP_MODE1_EN (1 << 8)
+#define ALC5623_MISC_AUXOUT_DEPOP_MODE3_EN (1 << 6)
+#define ALC5623_MISC_AUXOUT_DEPOP_MODE2_EN (1 << 5)
+#define ALC5623_MISC_AUXOUT_DEPOP_MODE1_EN (1 << 4)
+#define ALC5623_MISC_M_DAC_L_INPUT (1 << 3)
+#define ALC5623_MISC_M_DAC_R_INPUT (1 << 2)
+#define ALC5623_MISC_IRQOUT_INV_CTRL (1 << 0)
+
+#define ALC5623_PSEDUEO_SPATIAL_CTRL 0x60
+#define ALC5623_EQ_CTRL 0x62
+#define ALC5623_EQ_MODE_ENABLE 0x66
+#define ALC5623_AVC_CTRL 0x68
+#define ALC5623_HID_CTRL_INDEX 0x6A
+#define ALC5623_HID_CTRL_DATA 0x6C
+#define ALC5623_VENDOR_ID1 0x7C
+#define ALC5623_VENDOR_ID2 0x7E
+
+#define ALC5623_PLL_FR_MCLK 0
+#define ALC5623_PLL_FR_BCK 1
+#endif
diff --git a/sound/soc/codecs/cq93vc.c b/sound/soc/codecs/cq93vc.c
new file mode 100644
index 00000000..46dbfd06
--- /dev/null
+++ b/sound/soc/codecs/cq93vc.c
@@ -0,0 +1,223 @@
+/*
+ * ALSA SoC CQ0093 Voice Codec Driver for DaVinci platforms
+ *
+ * Copyright (C) 2010 Texas Instruments, Inc
+ *
+ * Author: Miguel Aguilar <miguel.aguilar@ridgerun.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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/mfd/davinci_voicecodec.h>
+#include <linux/spi/spi.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include <mach/dm365.h>
+
+static inline unsigned int cq93vc_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct davinci_vc *davinci_vc = codec->control_data;
+
+ return readl(davinci_vc->base + reg);
+}
+
+static inline int cq93vc_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct davinci_vc *davinci_vc = codec->control_data;
+
+ writel(value, davinci_vc->base + reg);
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new cq93vc_snd_controls[] = {
+ SOC_SINGLE("PGA Capture Volume", DAVINCI_VC_REG05, 0, 0x03, 0),
+ SOC_SINGLE("Mono DAC Playback Volume", DAVINCI_VC_REG09, 0, 0x3f, 0),
+};
+
+static int cq93vc_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 reg = cq93vc_read(codec, DAVINCI_VC_REG09) & ~DAVINCI_VC_REG09_MUTE;
+
+ if (mute)
+ cq93vc_write(codec, DAVINCI_VC_REG09,
+ reg | DAVINCI_VC_REG09_MUTE);
+ else
+ cq93vc_write(codec, DAVINCI_VC_REG09, reg);
+
+ return 0;
+}
+
+static int cq93vc_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct davinci_vc *davinci_vc = codec->control_data;
+
+ switch (freq) {
+ case 22579200:
+ case 27000000:
+ case 33868800:
+ davinci_vc->cq93vc.sysclk = freq;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int cq93vc_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ cq93vc_write(codec, DAVINCI_VC_REG12,
+ DAVINCI_VC_REG12_POWER_ALL_ON);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ cq93vc_write(codec, DAVINCI_VC_REG12,
+ DAVINCI_VC_REG12_POWER_ALL_OFF);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* force all power off */
+ cq93vc_write(codec, DAVINCI_VC_REG12,
+ DAVINCI_VC_REG12_POWER_ALL_OFF);
+ break;
+ }
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+#define CQ93VC_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000)
+#define CQ93VC_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE)
+
+static struct snd_soc_dai_ops cq93vc_dai_ops = {
+ .digital_mute = cq93vc_mute,
+ .set_sysclk = cq93vc_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver cq93vc_dai = {
+ .name = "cq93vc-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = CQ93VC_RATES,
+ .formats = CQ93VC_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = CQ93VC_RATES,
+ .formats = CQ93VC_FORMATS,},
+ .ops = &cq93vc_dai_ops,
+};
+
+static int cq93vc_resume(struct snd_soc_codec *codec)
+{
+ cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int cq93vc_probe(struct snd_soc_codec *codec)
+{
+ struct davinci_vc *davinci_vc = codec->dev->platform_data;
+
+ davinci_vc->cq93vc.codec = codec;
+ codec->control_data = davinci_vc;
+
+ /* Set controls */
+ snd_soc_add_controls(codec, cq93vc_snd_controls,
+ ARRAY_SIZE(cq93vc_snd_controls));
+
+ /* Off, with power on */
+ cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int cq93vc_remove(struct snd_soc_codec *codec)
+{
+ cq93vc_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_cq93vc = {
+ .read = cq93vc_read,
+ .write = cq93vc_write,
+ .set_bias_level = cq93vc_set_bias_level,
+ .probe = cq93vc_probe,
+ .remove = cq93vc_remove,
+ .resume = cq93vc_resume,
+};
+
+static int cq93vc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_cq93vc, &cq93vc_dai, 1);
+}
+
+static int cq93vc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver cq93vc_codec_driver = {
+ .driver = {
+ .name = "cq93vc-codec",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = cq93vc_platform_probe,
+ .remove = __devexit_p(cq93vc_platform_remove),
+};
+
+static int __init cq93vc_init(void)
+{
+ return platform_driver_register(&cq93vc_codec_driver);
+}
+module_init(cq93vc_init);
+
+static void __exit cq93vc_exit(void)
+{
+ platform_driver_unregister(&cq93vc_codec_driver);
+}
+module_exit(cq93vc_exit);
+
+MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC CQ0093 Voice Codec Driver");
+MODULE_AUTHOR("Miguel Aguilar");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c
new file mode 100644
index 00000000..0206a17d
--- /dev/null
+++ b/sound/soc/codecs/cs4270.c
@@ -0,0 +1,758 @@
+/*
+ * CS4270 ALSA SoC (ASoC) codec driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2009 Freescale Semiconductor, Inc. This file is licensed
+ * under the terms of the GNU General Public License version 2. This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ *
+ * This is an ASoC device driver for the Cirrus Logic CS4270 codec.
+ *
+ * Current features/limitations:
+ *
+ * - Software mode is supported. Stand-alone mode is not supported.
+ * - Only I2C is supported, not SPI
+ * - Support for master and slave mode
+ * - The machine driver's 'startup' function must call
+ * cs4270_set_dai_sysclk() with the value of MCLK.
+ * - Only I2S and left-justified modes are supported
+ * - Power management is supported
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+
+/*
+ * The codec isn't really big-endian or little-endian, since the I2S
+ * interface requires data to be sent serially with the MSbit first.
+ * However, to support BE and LE I2S devices, we specify both here. That
+ * way, ALSA will always match the bit patterns.
+ */
+#define CS4270_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE)
+
+/* CS4270 registers addresses */
+#define CS4270_CHIPID 0x01 /* Chip ID */
+#define CS4270_PWRCTL 0x02 /* Power Control */
+#define CS4270_MODE 0x03 /* Mode Control */
+#define CS4270_FORMAT 0x04 /* Serial Format, ADC/DAC Control */
+#define CS4270_TRANS 0x05 /* Transition Control */
+#define CS4270_MUTE 0x06 /* Mute Control */
+#define CS4270_VOLA 0x07 /* DAC Channel A Volume Control */
+#define CS4270_VOLB 0x08 /* DAC Channel B Volume Control */
+
+#define CS4270_FIRSTREG 0x01
+#define CS4270_LASTREG 0x08
+#define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1)
+#define CS4270_I2C_INCR 0x80
+
+/* Bit masks for the CS4270 registers */
+#define CS4270_CHIPID_ID 0xF0
+#define CS4270_CHIPID_REV 0x0F
+#define CS4270_PWRCTL_FREEZE 0x80
+#define CS4270_PWRCTL_PDN_ADC 0x20
+#define CS4270_PWRCTL_PDN_DAC 0x02
+#define CS4270_PWRCTL_PDN 0x01
+#define CS4270_PWRCTL_PDN_ALL \
+ (CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC | CS4270_PWRCTL_PDN)
+#define CS4270_MODE_SPEED_MASK 0x30
+#define CS4270_MODE_1X 0x00
+#define CS4270_MODE_2X 0x10
+#define CS4270_MODE_4X 0x20
+#define CS4270_MODE_SLAVE 0x30
+#define CS4270_MODE_DIV_MASK 0x0E
+#define CS4270_MODE_DIV1 0x00
+#define CS4270_MODE_DIV15 0x02
+#define CS4270_MODE_DIV2 0x04
+#define CS4270_MODE_DIV3 0x06
+#define CS4270_MODE_DIV4 0x08
+#define CS4270_MODE_POPGUARD 0x01
+#define CS4270_FORMAT_FREEZE_A 0x80
+#define CS4270_FORMAT_FREEZE_B 0x40
+#define CS4270_FORMAT_LOOPBACK 0x20
+#define CS4270_FORMAT_DAC_MASK 0x18
+#define CS4270_FORMAT_DAC_LJ 0x00
+#define CS4270_FORMAT_DAC_I2S 0x08
+#define CS4270_FORMAT_DAC_RJ16 0x18
+#define CS4270_FORMAT_DAC_RJ24 0x10
+#define CS4270_FORMAT_ADC_MASK 0x01
+#define CS4270_FORMAT_ADC_LJ 0x00
+#define CS4270_FORMAT_ADC_I2S 0x01
+#define CS4270_TRANS_ONE_VOL 0x80
+#define CS4270_TRANS_SOFT 0x40
+#define CS4270_TRANS_ZERO 0x20
+#define CS4270_TRANS_INV_ADC_A 0x08
+#define CS4270_TRANS_INV_ADC_B 0x10
+#define CS4270_TRANS_INV_DAC_A 0x02
+#define CS4270_TRANS_INV_DAC_B 0x04
+#define CS4270_TRANS_DEEMPH 0x01
+#define CS4270_MUTE_AUTO 0x20
+#define CS4270_MUTE_ADC_A 0x08
+#define CS4270_MUTE_ADC_B 0x10
+#define CS4270_MUTE_POLARITY 0x04
+#define CS4270_MUTE_DAC_A 0x01
+#define CS4270_MUTE_DAC_B 0x02
+
+/* Power-on default values for the registers
+ *
+ * This array contains the power-on default values of the registers, with the
+ * exception of the "CHIPID" register (01h). The lower four bits of that
+ * register contain the hardware revision, so it is treated as volatile.
+ *
+ * Also note that on the CS4270, the first readable register is 1, but ASoC
+ * assumes the first register is 0. Therfore, the array must have an entry for
+ * register 0, but we use cs4270_reg_is_readable() to tell ASoC that it can't
+ * be read.
+ */
+static const u8 cs4270_default_reg_cache[CS4270_LASTREG + 1] = {
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x60, 0x20, 0x00, 0x00
+};
+
+static const char *supply_names[] = {
+ "va", "vd", "vlc"
+};
+
+/* Private data for the CS4270 */
+struct cs4270_private {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+ unsigned int mclk; /* Input frequency of the MCLK pin */
+ unsigned int mode; /* The mode (I2S or left-justified) */
+ unsigned int slave_mode;
+ unsigned int manual_mute;
+
+ /* power domain regulators */
+ struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
+};
+
+/**
+ * struct cs4270_mode_ratios - clock ratio tables
+ * @ratio: the ratio of MCLK to the sample rate
+ * @speed_mode: the Speed Mode bits to set in the Mode Control register for
+ * this ratio
+ * @mclk: the Ratio Select bits to set in the Mode Control register for this
+ * ratio
+ *
+ * The data for this chart is taken from Table 5 of the CS4270 reference
+ * manual.
+ *
+ * This table is used to determine how to program the Mode Control register.
+ * It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling
+ * rates the CS4270 currently supports.
+ *
+ * @speed_mode is the corresponding bit pattern to be written to the
+ * MODE bits of the Mode Control Register
+ *
+ * @mclk is the corresponding bit pattern to be wirten to the MCLK bits of
+ * the Mode Control Register.
+ *
+ * In situations where a single ratio is represented by multiple speed
+ * modes, we favor the slowest speed. E.g, for a ratio of 128, we pick
+ * double-speed instead of quad-speed. However, the CS4270 errata states
+ * that divide-By-1.5 can cause failures, so we avoid that mode where
+ * possible.
+ *
+ * Errata: There is an errata for the CS4270 where divide-by-1.5 does not
+ * work if Vd is 3.3V. If this effects you, select the
+ * CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will
+ * never select any sample rates that require divide-by-1.5.
+ */
+struct cs4270_mode_ratios {
+ unsigned int ratio;
+ u8 speed_mode;
+ u8 mclk;
+};
+
+static struct cs4270_mode_ratios cs4270_mode_ratios[] = {
+ {64, CS4270_MODE_4X, CS4270_MODE_DIV1},
+#ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA
+ {96, CS4270_MODE_4X, CS4270_MODE_DIV15},
+#endif
+ {128, CS4270_MODE_2X, CS4270_MODE_DIV1},
+ {192, CS4270_MODE_4X, CS4270_MODE_DIV3},
+ {256, CS4270_MODE_1X, CS4270_MODE_DIV1},
+ {384, CS4270_MODE_2X, CS4270_MODE_DIV3},
+ {512, CS4270_MODE_1X, CS4270_MODE_DIV2},
+ {768, CS4270_MODE_1X, CS4270_MODE_DIV3},
+ {1024, CS4270_MODE_1X, CS4270_MODE_DIV4}
+};
+
+/* The number of MCLK/LRCK ratios supported by the CS4270 */
+#define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios)
+
+static int cs4270_reg_is_readable(struct snd_soc_codec *codec, unsigned int reg)
+{
+ return (reg >= CS4270_FIRSTREG) && (reg <= CS4270_LASTREG);
+}
+
+static int cs4270_reg_is_volatile(struct snd_soc_codec *codec, unsigned int reg)
+{
+ /* Unreadable registers are considered volatile */
+ if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG))
+ return 1;
+
+ return reg == CS4270_CHIPID;
+}
+
+/**
+ * cs4270_set_dai_sysclk - determine the CS4270 samples rates.
+ * @codec_dai: the codec DAI
+ * @clk_id: the clock ID (ignored)
+ * @freq: the MCLK input frequency
+ * @dir: the clock direction (ignored)
+ *
+ * This function is used to tell the codec driver what the input MCLK
+ * frequency is.
+ *
+ * The value of MCLK is used to determine which sample rates are supported
+ * by the CS4270. The ratio of MCLK / Fs must be equal to one of nine
+ * supported values - 64, 96, 128, 192, 256, 384, 512, 768, and 1024.
+ *
+ * This function calculates the nine ratios and determines which ones match
+ * a standard sample rate. If there's a match, then it is added to the list
+ * of supported sample rates.
+ *
+ * This function must be called by the machine driver's 'startup' function,
+ * otherwise the list of supported sample rates will not be available in
+ * time for ALSA.
+ *
+ * For setups with variable MCLKs, pass 0 as 'freq' argument. This will cause
+ * theoretically possible sample rates to be enabled. Call it again with a
+ * proper value set one the external clock is set (most probably you would do
+ * that from a machine's driver 'hw_param' hook.
+ */
+static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+
+ cs4270->mclk = freq;
+ return 0;
+}
+
+/**
+ * cs4270_set_dai_fmt - configure the codec for the selected audio format
+ * @codec_dai: the codec DAI
+ * @format: a SND_SOC_DAIFMT_x value indicating the data format
+ *
+ * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the
+ * codec accordingly.
+ *
+ * Currently, this function only supports SND_SOC_DAIFMT_I2S and
+ * SND_SOC_DAIFMT_LEFT_J. The CS4270 codec also supports right-justified
+ * data for playback only, but ASoC currently does not support different
+ * formats for playback vs. record.
+ */
+static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ /* set DAI format */
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_LEFT_J:
+ cs4270->mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ default:
+ dev_err(codec->dev, "invalid dai format\n");
+ ret = -EINVAL;
+ }
+
+ /* set master/slave audio interface */
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs4270->slave_mode = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs4270->slave_mode = 0;
+ break;
+ default:
+ /* all other modes are unsupported by the hardware */
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/**
+ * cs4270_hw_params - program the CS4270 with the given hardware parameters.
+ * @substream: the audio stream
+ * @params: the hardware parameters to set
+ * @dai: the SOC DAI (ignored)
+ *
+ * This function programs the hardware with the values provided.
+ * Specifically, the sample rate and the data format.
+ *
+ * The .ops functions are used to provide board-specific data, like input
+ * frequencies, to this driver. This function takes that information,
+ * combines it with the hardware parameters provided, and programs the
+ * hardware accordingly.
+ */
+static int cs4270_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+ unsigned int i;
+ unsigned int rate;
+ unsigned int ratio;
+ int reg;
+
+ /* Figure out which MCLK/LRCK ratio to use */
+
+ rate = params_rate(params); /* Sampling rate, in Hz */
+ ratio = cs4270->mclk / rate; /* MCLK/LRCK ratio */
+
+ for (i = 0; i < NUM_MCLK_RATIOS; i++) {
+ if (cs4270_mode_ratios[i].ratio == ratio)
+ break;
+ }
+
+ if (i == NUM_MCLK_RATIOS) {
+ /* We did not find a matching ratio */
+ dev_err(codec->dev, "could not find matching ratio\n");
+ return -EINVAL;
+ }
+
+ /* Set the sample rate */
+
+ reg = snd_soc_read(codec, CS4270_MODE);
+ reg &= ~(CS4270_MODE_SPEED_MASK | CS4270_MODE_DIV_MASK);
+ reg |= cs4270_mode_ratios[i].mclk;
+
+ if (cs4270->slave_mode)
+ reg |= CS4270_MODE_SLAVE;
+ else
+ reg |= cs4270_mode_ratios[i].speed_mode;
+
+ ret = snd_soc_write(codec, CS4270_MODE, reg);
+ if (ret < 0) {
+ dev_err(codec->dev, "i2c write failed\n");
+ return ret;
+ }
+
+ /* Set the DAI format */
+
+ reg = snd_soc_read(codec, CS4270_FORMAT);
+ reg &= ~(CS4270_FORMAT_DAC_MASK | CS4270_FORMAT_ADC_MASK);
+
+ switch (cs4270->mode) {
+ case SND_SOC_DAIFMT_I2S:
+ reg |= CS4270_FORMAT_DAC_I2S | CS4270_FORMAT_ADC_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ reg |= CS4270_FORMAT_DAC_LJ | CS4270_FORMAT_ADC_LJ;
+ break;
+ default:
+ dev_err(codec->dev, "unknown dai format\n");
+ return -EINVAL;
+ }
+
+ ret = snd_soc_write(codec, CS4270_FORMAT, reg);
+ if (ret < 0) {
+ dev_err(codec->dev, "i2c write failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * cs4270_dai_mute - enable/disable the CS4270 external mute
+ * @dai: the SOC DAI
+ * @mute: 0 = disable mute, 1 = enable mute
+ *
+ * This function toggles the mute bits in the MUTE register. The CS4270's
+ * mute capability is intended for external muting circuitry, so if the
+ * board does not have the MUTEA or MUTEB pins connected to such circuitry,
+ * then this function will do nothing.
+ */
+static int cs4270_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+ int reg6;
+
+ reg6 = snd_soc_read(codec, CS4270_MUTE);
+
+ if (mute)
+ reg6 |= CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B;
+ else {
+ reg6 &= ~(CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B);
+ reg6 |= cs4270->manual_mute;
+ }
+
+ return snd_soc_write(codec, CS4270_MUTE, reg6);
+}
+
+/**
+ * cs4270_soc_put_mute - put callback for the 'Master Playback switch'
+ * alsa control.
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * This function basically passes the arguments on to the generic
+ * snd_soc_put_volsw() function and saves the mute information in
+ * our private data structure. This is because we want to prevent
+ * cs4270_dai_mute() neglecting the user's decision to manually
+ * mute the codec's output.
+ *
+ * Returns 0 for success.
+ */
+static int cs4270_soc_put_mute(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+ int left = !ucontrol->value.integer.value[0];
+ int right = !ucontrol->value.integer.value[1];
+
+ cs4270->manual_mute = (left ? CS4270_MUTE_DAC_A : 0) |
+ (right ? CS4270_MUTE_DAC_B : 0);
+
+ return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+/* A list of non-DAPM controls that the CS4270 supports */
+static const struct snd_kcontrol_new cs4270_snd_controls[] = {
+ SOC_DOUBLE_R("Master Playback Volume",
+ CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1),
+ SOC_SINGLE("Digital Sidetone Switch", CS4270_FORMAT, 5, 1, 0),
+ SOC_SINGLE("Soft Ramp Switch", CS4270_TRANS, 6, 1, 0),
+ SOC_SINGLE("Zero Cross Switch", CS4270_TRANS, 5, 1, 0),
+ SOC_SINGLE("De-emphasis filter", CS4270_TRANS, 0, 1, 0),
+ SOC_SINGLE("Popguard Switch", CS4270_MODE, 0, 1, 1),
+ SOC_SINGLE("Auto-Mute Switch", CS4270_MUTE, 5, 1, 0),
+ SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 1),
+ SOC_DOUBLE_EXT("Master Playback Switch", CS4270_MUTE, 0, 1, 1, 1,
+ snd_soc_get_volsw, cs4270_soc_put_mute),
+};
+
+static struct snd_soc_dai_ops cs4270_dai_ops = {
+ .hw_params = cs4270_hw_params,
+ .set_sysclk = cs4270_set_dai_sysclk,
+ .set_fmt = cs4270_set_dai_fmt,
+ .digital_mute = cs4270_dai_mute,
+};
+
+static struct snd_soc_dai_driver cs4270_dai = {
+ .name = "cs4270-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 4000,
+ .rate_max = 216000,
+ .formats = CS4270_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 4000,
+ .rate_max = 216000,
+ .formats = CS4270_FORMATS,
+ },
+ .ops = &cs4270_dai_ops,
+};
+
+/**
+ * cs4270_probe - ASoC probe function
+ * @pdev: platform device
+ *
+ * This function is called when ASoC has all the pieces it needs to
+ * instantiate a sound driver.
+ */
+static int cs4270_probe(struct snd_soc_codec *codec)
+{
+ struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+ int i, ret;
+
+ codec->control_data = cs4270->control_data;
+
+ /* Tell ASoC what kind of I/O to use to read the registers. ASoC will
+ * then do the I2C transactions itself.
+ */
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, cs4270->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to set cache I/O (ret=%i)\n", ret);
+ return ret;
+ }
+
+ /* Disable auto-mute. This feature appears to be buggy. In some
+ * situations, auto-mute will not deactivate when it should, so we want
+ * this feature disabled by default. An application (e.g. alsactl) can
+ * re-enabled it by using the controls.
+ */
+ ret = snd_soc_update_bits(codec, CS4270_MUTE, CS4270_MUTE_AUTO, 0);
+ if (ret < 0) {
+ dev_err(codec->dev, "i2c write failed\n");
+ return ret;
+ }
+
+ /* Disable automatic volume control. The hardware enables, and it
+ * causes volume change commands to be delayed, sometimes until after
+ * playback has started. An application (e.g. alsactl) can
+ * re-enabled it by using the controls.
+ */
+ ret = snd_soc_update_bits(codec, CS4270_TRANS,
+ CS4270_TRANS_SOFT | CS4270_TRANS_ZERO, 0);
+ if (ret < 0) {
+ dev_err(codec->dev, "i2c write failed\n");
+ return ret;
+ }
+
+ /* Add the non-DAPM controls */
+ ret = snd_soc_add_controls(codec, cs4270_snd_controls,
+ ARRAY_SIZE(cs4270_snd_controls));
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to add controls\n");
+ return ret;
+ }
+
+ /* get the power supply regulators */
+ for (i = 0; i < ARRAY_SIZE(supply_names); i++)
+ cs4270->supplies[i].supply = supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(cs4270->supplies),
+ cs4270->supplies);
+ if (ret < 0)
+ return ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies),
+ cs4270->supplies);
+ if (ret < 0)
+ goto error_free_regulators;
+
+ return 0;
+
+error_free_regulators:
+ regulator_bulk_free(ARRAY_SIZE(cs4270->supplies),
+ cs4270->supplies);
+
+ return ret;
+}
+
+/**
+ * cs4270_remove - ASoC remove function
+ * @pdev: platform device
+ *
+ * This function is the counterpart to cs4270_probe().
+ */
+static int cs4270_remove(struct snd_soc_codec *codec)
+{
+ struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+
+ regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies), cs4270->supplies);
+ regulator_bulk_free(ARRAY_SIZE(cs4270->supplies), cs4270->supplies);
+
+ return 0;
+};
+
+#ifdef CONFIG_PM
+
+/* This suspend/resume implementation can handle both - a simple standby
+ * where the codec remains powered, and a full suspend, where the voltage
+ * domain the codec is connected to is teared down and/or any other hardware
+ * reset condition is asserted.
+ *
+ * The codec's own power saving features are enabled in the suspend callback,
+ * and all registers are written back to the hardware when resuming.
+ */
+
+static int cs4270_soc_suspend(struct snd_soc_codec *codec, pm_message_t mesg)
+{
+ struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+ int reg, ret;
+
+ reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
+ if (reg < 0)
+ return reg;
+
+ ret = snd_soc_write(codec, CS4270_PWRCTL, reg);
+ if (ret < 0)
+ return ret;
+
+ regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies),
+ cs4270->supplies);
+
+ return 0;
+}
+
+static int cs4270_soc_resume(struct snd_soc_codec *codec)
+{
+ struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+ struct i2c_client *i2c_client = codec->control_data;
+ int reg;
+
+ regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies),
+ cs4270->supplies);
+
+ /* In case the device was put to hard reset during sleep, we need to
+ * wait 500ns here before any I2C communication. */
+ ndelay(500);
+
+ /* first restore the entire register cache ... */
+ for (reg = CS4270_FIRSTREG; reg <= CS4270_LASTREG; reg++) {
+ u8 val = snd_soc_read(codec, reg);
+
+ if (i2c_smbus_write_byte_data(i2c_client, reg, val)) {
+ dev_err(codec->dev, "i2c write failed\n");
+ return -EIO;
+ }
+ }
+
+ /* ... then disable the power-down bits */
+ reg = snd_soc_read(codec, CS4270_PWRCTL);
+ reg &= ~CS4270_PWRCTL_PDN_ALL;
+
+ return snd_soc_write(codec, CS4270_PWRCTL, reg);
+}
+#else
+#define cs4270_soc_suspend NULL
+#define cs4270_soc_resume NULL
+#endif /* CONFIG_PM */
+
+/*
+ * ASoC codec device structure
+ *
+ * Assign this variable to the codec_dev field of the machine driver's
+ * snd_soc_device structure.
+ */
+static const struct snd_soc_codec_driver soc_codec_device_cs4270 = {
+ .probe = cs4270_probe,
+ .remove = cs4270_remove,
+ .suspend = cs4270_soc_suspend,
+ .resume = cs4270_soc_resume,
+ .volatile_register = cs4270_reg_is_volatile,
+ .readable_register = cs4270_reg_is_readable,
+ .reg_cache_size = CS4270_LASTREG + 1,
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = cs4270_default_reg_cache,
+};
+
+/**
+ * cs4270_i2c_probe - initialize the I2C interface of the CS4270
+ * @i2c_client: the I2C client object
+ * @id: the I2C device ID (ignored)
+ *
+ * This function is called whenever the I2C subsystem finds a device that
+ * matches the device ID given via a prior call to i2c_add_driver().
+ */
+static int cs4270_i2c_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct cs4270_private *cs4270;
+ int ret;
+
+ /* Verify that we have a CS4270 */
+
+ ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "failed to read i2c at addr %X\n",
+ i2c_client->addr);
+ return ret;
+ }
+ /* The top four bits of the chip ID should be 1100. */
+ if ((ret & 0xF0) != 0xC0) {
+ dev_err(&i2c_client->dev, "device at addr %X is not a CS4270\n",
+ i2c_client->addr);
+ return -ENODEV;
+ }
+
+ dev_info(&i2c_client->dev, "found device at i2c address %X\n",
+ i2c_client->addr);
+ dev_info(&i2c_client->dev, "hardware revision %X\n", ret & 0xF);
+
+ cs4270 = kzalloc(sizeof(struct cs4270_private), GFP_KERNEL);
+ if (!cs4270) {
+ dev_err(&i2c_client->dev, "could not allocate codec\n");
+ return -ENOMEM;
+ }
+
+ i2c_set_clientdata(i2c_client, cs4270);
+ cs4270->control_data = i2c_client;
+ cs4270->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c_client->dev,
+ &soc_codec_device_cs4270, &cs4270_dai, 1);
+ if (ret < 0)
+ kfree(cs4270);
+ return ret;
+}
+
+/**
+ * cs4270_i2c_remove - remove an I2C device
+ * @i2c_client: the I2C client object
+ *
+ * This function is the counterpart to cs4270_i2c_probe().
+ */
+static int cs4270_i2c_remove(struct i2c_client *i2c_client)
+{
+ snd_soc_unregister_codec(&i2c_client->dev);
+ kfree(i2c_get_clientdata(i2c_client));
+ return 0;
+}
+
+/*
+ * cs4270_id - I2C device IDs supported by this driver
+ */
+static const struct i2c_device_id cs4270_id[] = {
+ {"cs4270", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs4270_id);
+
+/*
+ * cs4270_i2c_driver - I2C device identification
+ *
+ * This structure tells the I2C subsystem how to identify and support a
+ * given I2C device type.
+ */
+static struct i2c_driver cs4270_i2c_driver = {
+ .driver = {
+ .name = "cs4270-codec",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs4270_id,
+ .probe = cs4270_i2c_probe,
+ .remove = cs4270_i2c_remove,
+};
+
+static int __init cs4270_init(void)
+{
+ return i2c_add_driver(&cs4270_i2c_driver);
+}
+module_init(cs4270_init);
+
+static void __exit cs4270_exit(void)
+{
+ i2c_del_driver(&cs4270_i2c_driver);
+}
+module_exit(cs4270_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Cirrus Logic CS4270 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c
new file mode 100644
index 00000000..083aab96
--- /dev/null
+++ b/sound/soc/codecs/cs4271.c
@@ -0,0 +1,667 @@
+/*
+ * CS4271 ASoC codec driver
+ *
+ * Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru>
+ *
+ * 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
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * This driver support CS4271 codec being master or slave, working
+ * in control port mode, connected either via SPI or I2C.
+ * The data format accepted is I2S or left-justified.
+ * DAPM support not implemented.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <sound/cs4271.h>
+
+#define CS4271_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+#define CS4271_PCM_RATES SNDRV_PCM_RATE_8000_192000
+
+/*
+ * CS4271 registers
+ * High byte represents SPI chip address (0x10) + write command (0)
+ * Low byte - codec register address
+ */
+#define CS4271_MODE1 0x2001 /* Mode Control 1 */
+#define CS4271_DACCTL 0x2002 /* DAC Control */
+#define CS4271_DACVOL 0x2003 /* DAC Volume & Mixing Control */
+#define CS4271_VOLA 0x2004 /* DAC Channel A Volume Control */
+#define CS4271_VOLB 0x2005 /* DAC Channel B Volume Control */
+#define CS4271_ADCCTL 0x2006 /* ADC Control */
+#define CS4271_MODE2 0x2007 /* Mode Control 2 */
+#define CS4271_CHIPID 0x2008 /* Chip ID */
+
+#define CS4271_FIRSTREG CS4271_MODE1
+#define CS4271_LASTREG CS4271_MODE2
+#define CS4271_NR_REGS ((CS4271_LASTREG & 0xFF) + 1)
+
+/* Bit masks for the CS4271 registers */
+#define CS4271_MODE1_MODE_MASK 0xC0
+#define CS4271_MODE1_MODE_1X 0x00
+#define CS4271_MODE1_MODE_2X 0x80
+#define CS4271_MODE1_MODE_4X 0xC0
+
+#define CS4271_MODE1_DIV_MASK 0x30
+#define CS4271_MODE1_DIV_1 0x00
+#define CS4271_MODE1_DIV_15 0x10
+#define CS4271_MODE1_DIV_2 0x20
+#define CS4271_MODE1_DIV_3 0x30
+
+#define CS4271_MODE1_MASTER 0x08
+
+#define CS4271_MODE1_DAC_DIF_MASK 0x07
+#define CS4271_MODE1_DAC_DIF_LJ 0x00
+#define CS4271_MODE1_DAC_DIF_I2S 0x01
+#define CS4271_MODE1_DAC_DIF_RJ16 0x02
+#define CS4271_MODE1_DAC_DIF_RJ24 0x03
+#define CS4271_MODE1_DAC_DIF_RJ20 0x04
+#define CS4271_MODE1_DAC_DIF_RJ18 0x05
+
+#define CS4271_DACCTL_AMUTE 0x80
+#define CS4271_DACCTL_IF_SLOW 0x40
+
+#define CS4271_DACCTL_DEM_MASK 0x30
+#define CS4271_DACCTL_DEM_DIS 0x00
+#define CS4271_DACCTL_DEM_441 0x10
+#define CS4271_DACCTL_DEM_48 0x20
+#define CS4271_DACCTL_DEM_32 0x30
+
+#define CS4271_DACCTL_SVRU 0x08
+#define CS4271_DACCTL_SRD 0x04
+#define CS4271_DACCTL_INVA 0x02
+#define CS4271_DACCTL_INVB 0x01
+
+#define CS4271_DACVOL_BEQUA 0x40
+#define CS4271_DACVOL_SOFT 0x20
+#define CS4271_DACVOL_ZEROC 0x10
+
+#define CS4271_DACVOL_ATAPI_MASK 0x0F
+#define CS4271_DACVOL_ATAPI_M_M 0x00
+#define CS4271_DACVOL_ATAPI_M_BR 0x01
+#define CS4271_DACVOL_ATAPI_M_BL 0x02
+#define CS4271_DACVOL_ATAPI_M_BLR2 0x03
+#define CS4271_DACVOL_ATAPI_AR_M 0x04
+#define CS4271_DACVOL_ATAPI_AR_BR 0x05
+#define CS4271_DACVOL_ATAPI_AR_BL 0x06
+#define CS4271_DACVOL_ATAPI_AR_BLR2 0x07
+#define CS4271_DACVOL_ATAPI_AL_M 0x08
+#define CS4271_DACVOL_ATAPI_AL_BR 0x09
+#define CS4271_DACVOL_ATAPI_AL_BL 0x0A
+#define CS4271_DACVOL_ATAPI_AL_BLR2 0x0B
+#define CS4271_DACVOL_ATAPI_ALR2_M 0x0C
+#define CS4271_DACVOL_ATAPI_ALR2_BR 0x0D
+#define CS4271_DACVOL_ATAPI_ALR2_BL 0x0E
+#define CS4271_DACVOL_ATAPI_ALR2_BLR2 0x0F
+
+#define CS4271_VOLA_MUTE 0x80
+#define CS4271_VOLA_VOL_MASK 0x7F
+#define CS4271_VOLB_MUTE 0x80
+#define CS4271_VOLB_VOL_MASK 0x7F
+
+#define CS4271_ADCCTL_DITHER16 0x20
+
+#define CS4271_ADCCTL_ADC_DIF_MASK 0x10
+#define CS4271_ADCCTL_ADC_DIF_LJ 0x00
+#define CS4271_ADCCTL_ADC_DIF_I2S 0x10
+
+#define CS4271_ADCCTL_MUTEA 0x08
+#define CS4271_ADCCTL_MUTEB 0x04
+#define CS4271_ADCCTL_HPFDA 0x02
+#define CS4271_ADCCTL_HPFDB 0x01
+
+#define CS4271_MODE2_LOOP 0x10
+#define CS4271_MODE2_MUTECAEQUB 0x08
+#define CS4271_MODE2_FREEZE 0x04
+#define CS4271_MODE2_CPEN 0x02
+#define CS4271_MODE2_PDN 0x01
+
+#define CS4271_CHIPID_PART_MASK 0xF0
+#define CS4271_CHIPID_REV_MASK 0x0F
+
+/*
+ * Default CS4271 power-up configuration
+ * Array contains non-existing in hw register at address 0
+ * Array do not include Chip ID, as codec driver does not use
+ * registers read operations at all
+ */
+static const u8 cs4271_dflt_reg[CS4271_NR_REGS] = {
+ 0,
+ 0,
+ CS4271_DACCTL_AMUTE,
+ CS4271_DACVOL_SOFT | CS4271_DACVOL_ATAPI_AL_BR,
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+struct cs4271_private {
+ /* SND_SOC_I2C or SND_SOC_SPI */
+ enum snd_soc_control_type bus_type;
+ void *control_data;
+ unsigned int mclk;
+ bool master;
+ bool deemph;
+ /* Current sample rate for de-emphasis control */
+ int rate;
+ /* GPIO driving Reset pin, if any */
+ int gpio_nreset;
+ /* GPIO that disable serial bus, if any */
+ int gpio_disable;
+};
+
+/*
+ * @freq is the desired MCLK rate
+ * MCLK rate should (c) be the sample rate, multiplied by one of the
+ * ratios listed in cs4271_mclk_fs_ratios table
+ */
+static int cs4271_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+
+ cs4271->mclk = freq;
+ return 0;
+}
+
+static int cs4271_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ unsigned int val = 0;
+ int ret;
+
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs4271->master = 0;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs4271->master = 1;
+ val |= CS4271_MODE1_MASTER;
+ break;
+ default:
+ dev_err(codec->dev, "Invalid DAI format\n");
+ return -EINVAL;
+ }
+
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ val |= CS4271_MODE1_DAC_DIF_LJ;
+ ret = snd_soc_update_bits(codec, CS4271_ADCCTL,
+ CS4271_ADCCTL_ADC_DIF_MASK, CS4271_ADCCTL_ADC_DIF_LJ);
+ if (ret < 0)
+ return ret;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val |= CS4271_MODE1_DAC_DIF_I2S;
+ ret = snd_soc_update_bits(codec, CS4271_ADCCTL,
+ CS4271_ADCCTL_ADC_DIF_MASK, CS4271_ADCCTL_ADC_DIF_I2S);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ dev_err(codec->dev, "Invalid DAI format\n");
+ return -EINVAL;
+ }
+
+ ret = snd_soc_update_bits(codec, CS4271_MODE1,
+ CS4271_MODE1_DAC_DIF_MASK | CS4271_MODE1_MASTER, val);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int cs4271_deemph[] = {0, 44100, 48000, 32000};
+
+static int cs4271_set_deemph(struct snd_soc_codec *codec)
+{
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ int i, ret;
+ int val = CS4271_DACCTL_DEM_DIS;
+
+ if (cs4271->deemph) {
+ /* Find closest de-emphasis freq */
+ val = 1;
+ for (i = 2; i < ARRAY_SIZE(cs4271_deemph); i++)
+ if (abs(cs4271_deemph[i] - cs4271->rate) <
+ abs(cs4271_deemph[val] - cs4271->rate))
+ val = i;
+ val <<= 4;
+ }
+
+ ret = snd_soc_update_bits(codec, CS4271_DACCTL,
+ CS4271_DACCTL_DEM_MASK, val);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int cs4271_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = cs4271->deemph;
+ return 0;
+}
+
+static int cs4271_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+
+ cs4271->deemph = ucontrol->value.enumerated.item[0];
+ return cs4271_set_deemph(codec);
+}
+
+struct cs4271_clk_cfg {
+ bool master; /* codec mode */
+ u8 speed_mode; /* codec speed mode: 1x, 2x, 4x */
+ unsigned short ratio; /* MCLK / sample rate */
+ u8 ratio_mask; /* ratio bit mask for Master mode */
+};
+
+static struct cs4271_clk_cfg cs4271_clk_tab[] = {
+ {1, CS4271_MODE1_MODE_1X, 256, CS4271_MODE1_DIV_1},
+ {1, CS4271_MODE1_MODE_1X, 384, CS4271_MODE1_DIV_15},
+ {1, CS4271_MODE1_MODE_1X, 512, CS4271_MODE1_DIV_2},
+ {1, CS4271_MODE1_MODE_1X, 768, CS4271_MODE1_DIV_3},
+ {1, CS4271_MODE1_MODE_2X, 128, CS4271_MODE1_DIV_1},
+ {1, CS4271_MODE1_MODE_2X, 192, CS4271_MODE1_DIV_15},
+ {1, CS4271_MODE1_MODE_2X, 256, CS4271_MODE1_DIV_2},
+ {1, CS4271_MODE1_MODE_2X, 384, CS4271_MODE1_DIV_3},
+ {1, CS4271_MODE1_MODE_4X, 64, CS4271_MODE1_DIV_1},
+ {1, CS4271_MODE1_MODE_4X, 96, CS4271_MODE1_DIV_15},
+ {1, CS4271_MODE1_MODE_4X, 128, CS4271_MODE1_DIV_2},
+ {1, CS4271_MODE1_MODE_4X, 192, CS4271_MODE1_DIV_3},
+ {0, CS4271_MODE1_MODE_1X, 256, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_1X, 384, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_1X, 512, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_1X, 768, CS4271_MODE1_DIV_2},
+ {0, CS4271_MODE1_MODE_1X, 1024, CS4271_MODE1_DIV_2},
+ {0, CS4271_MODE1_MODE_2X, 128, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_2X, 192, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_2X, 256, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_2X, 384, CS4271_MODE1_DIV_2},
+ {0, CS4271_MODE1_MODE_2X, 512, CS4271_MODE1_DIV_2},
+ {0, CS4271_MODE1_MODE_4X, 64, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_4X, 96, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_4X, 128, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_4X, 192, CS4271_MODE1_DIV_2},
+ {0, CS4271_MODE1_MODE_4X, 256, CS4271_MODE1_DIV_2},
+};
+
+#define CS4171_NR_RATIOS ARRAY_SIZE(cs4271_clk_tab)
+
+static int cs4271_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ int i, ret;
+ unsigned int ratio, val;
+
+ cs4271->rate = params_rate(params);
+
+ /* Configure DAC */
+ if (cs4271->rate < 50000)
+ val = CS4271_MODE1_MODE_1X;
+ else if (cs4271->rate < 100000)
+ val = CS4271_MODE1_MODE_2X;
+ else
+ val = CS4271_MODE1_MODE_4X;
+
+ ratio = cs4271->mclk / cs4271->rate;
+ for (i = 0; i < CS4171_NR_RATIOS; i++)
+ if ((cs4271_clk_tab[i].master == cs4271->master) &&
+ (cs4271_clk_tab[i].speed_mode == val) &&
+ (cs4271_clk_tab[i].ratio == ratio))
+ break;
+
+ if (i == CS4171_NR_RATIOS) {
+ dev_err(codec->dev, "Invalid sample rate\n");
+ return -EINVAL;
+ }
+
+ val |= cs4271_clk_tab[i].ratio_mask;
+
+ ret = snd_soc_update_bits(codec, CS4271_MODE1,
+ CS4271_MODE1_MODE_MASK | CS4271_MODE1_DIV_MASK, val);
+ if (ret < 0)
+ return ret;
+
+ return cs4271_set_deemph(codec);
+}
+
+static int cs4271_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int ret;
+ int val_a = 0;
+ int val_b = 0;
+
+ if (mute) {
+ val_a = CS4271_VOLA_MUTE;
+ val_b = CS4271_VOLB_MUTE;
+ }
+
+ ret = snd_soc_update_bits(codec, CS4271_VOLA, CS4271_VOLA_MUTE, val_a);
+ if (ret < 0)
+ return ret;
+ ret = snd_soc_update_bits(codec, CS4271_VOLB, CS4271_VOLB_MUTE, val_b);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* CS4271 controls */
+static DECLARE_TLV_DB_SCALE(cs4271_dac_tlv, -12700, 100, 0);
+
+static const struct snd_kcontrol_new cs4271_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("Master Playback Volume", CS4271_VOLA, CS4271_VOLB,
+ 0, 0x7F, 1, cs4271_dac_tlv),
+ SOC_SINGLE("Digital Loopback Switch", CS4271_MODE2, 4, 1, 0),
+ SOC_SINGLE("Soft Ramp Switch", CS4271_DACVOL, 5, 1, 0),
+ SOC_SINGLE("Zero Cross Switch", CS4271_DACVOL, 4, 1, 0),
+ SOC_SINGLE_BOOL_EXT("De-emphasis Switch", 0,
+ cs4271_get_deemph, cs4271_put_deemph),
+ SOC_SINGLE("Auto-Mute Switch", CS4271_DACCTL, 7, 1, 0),
+ SOC_SINGLE("Slow Roll Off Filter Switch", CS4271_DACCTL, 6, 1, 0),
+ SOC_SINGLE("Soft Volume Ramp-Up Switch", CS4271_DACCTL, 3, 1, 0),
+ SOC_SINGLE("Soft Ramp-Down Switch", CS4271_DACCTL, 2, 1, 0),
+ SOC_SINGLE("Left Channel Inversion Switch", CS4271_DACCTL, 1, 1, 0),
+ SOC_SINGLE("Right Channel Inversion Switch", CS4271_DACCTL, 0, 1, 0),
+ SOC_DOUBLE("Master Capture Switch", CS4271_ADCCTL, 3, 2, 1, 1),
+ SOC_SINGLE("Dither 16-Bit Data Switch", CS4271_ADCCTL, 5, 1, 0),
+ SOC_DOUBLE("High Pass Filter Switch", CS4271_ADCCTL, 1, 0, 1, 1),
+ SOC_DOUBLE_R("Master Playback Switch", CS4271_VOLA, CS4271_VOLB,
+ 7, 1, 1),
+};
+
+static struct snd_soc_dai_ops cs4271_dai_ops = {
+ .hw_params = cs4271_hw_params,
+ .set_sysclk = cs4271_set_dai_sysclk,
+ .set_fmt = cs4271_set_dai_fmt,
+ .digital_mute = cs4271_digital_mute,
+};
+
+static struct snd_soc_dai_driver cs4271_dai = {
+ .name = "cs4271-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = CS4271_PCM_RATES,
+ .formats = CS4271_PCM_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = CS4271_PCM_RATES,
+ .formats = CS4271_PCM_FORMATS,
+ },
+ .ops = &cs4271_dai_ops,
+ .symmetric_rates = 1,
+};
+
+#ifdef CONFIG_PM
+static int cs4271_soc_suspend(struct snd_soc_codec *codec, pm_message_t mesg)
+{
+ int ret;
+ /* Set power-down bit */
+ ret = snd_soc_update_bits(codec, CS4271_MODE2, 0, CS4271_MODE2_PDN);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int cs4271_soc_resume(struct snd_soc_codec *codec)
+{
+ int ret;
+ /* Restore codec state */
+ ret = snd_soc_cache_sync(codec);
+ if (ret < 0)
+ return ret;
+ /* then disable the power-down bit */
+ ret = snd_soc_update_bits(codec, CS4271_MODE2, CS4271_MODE2_PDN, 0);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+#else
+#define cs4271_soc_suspend NULL
+#define cs4271_soc_resume NULL
+#endif /* CONFIG_PM */
+
+static int cs4271_probe(struct snd_soc_codec *codec)
+{
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ struct cs4271_platform_data *cs4271plat = codec->dev->platform_data;
+ int ret;
+ int gpio_nreset = -EINVAL;
+
+ codec->control_data = cs4271->control_data;
+
+ if (cs4271plat && gpio_is_valid(cs4271plat->gpio_nreset))
+ gpio_nreset = cs4271plat->gpio_nreset;
+
+ if (gpio_nreset >= 0)
+ if (gpio_request(gpio_nreset, "CS4271 Reset"))
+ gpio_nreset = -EINVAL;
+ if (gpio_nreset >= 0) {
+ /* Reset codec */
+ gpio_direction_output(gpio_nreset, 0);
+ udelay(1);
+ gpio_set_value(gpio_nreset, 1);
+ /* Give the codec time to wake up */
+ udelay(1);
+ }
+
+ cs4271->gpio_nreset = gpio_nreset;
+
+ /*
+ * In case of I2C, chip address specified in board data.
+ * So cache IO operations use 8 bit codec register address.
+ * In case of SPI, chip address and register address
+ * passed together as 16 bit value.
+ * Anyway, register address is masked with 0xFF inside
+ * soc-cache code.
+ */
+ if (cs4271->bus_type == SND_SOC_SPI)
+ ret = snd_soc_codec_set_cache_io(codec, 16, 8,
+ cs4271->bus_type);
+ else
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8,
+ cs4271->bus_type);
+ if (ret) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_update_bits(codec, CS4271_MODE2, 0,
+ CS4271_MODE2_PDN | CS4271_MODE2_CPEN);
+ if (ret < 0)
+ return ret;
+ ret = snd_soc_update_bits(codec, CS4271_MODE2, CS4271_MODE2_PDN, 0);
+ if (ret < 0)
+ return ret;
+ /* Power-up sequence requires 85 uS */
+ udelay(85);
+
+ return snd_soc_add_controls(codec, cs4271_snd_controls,
+ ARRAY_SIZE(cs4271_snd_controls));
+}
+
+static int cs4271_remove(struct snd_soc_codec *codec)
+{
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ int gpio_nreset;
+
+ gpio_nreset = cs4271->gpio_nreset;
+
+ if (gpio_is_valid(gpio_nreset)) {
+ /* Set codec to the reset state */
+ gpio_set_value(gpio_nreset, 0);
+ gpio_free(gpio_nreset);
+ }
+
+ return 0;
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_cs4271 = {
+ .probe = cs4271_probe,
+ .remove = cs4271_remove,
+ .suspend = cs4271_soc_suspend,
+ .resume = cs4271_soc_resume,
+ .reg_cache_default = cs4271_dflt_reg,
+ .reg_cache_size = ARRAY_SIZE(cs4271_dflt_reg),
+ .reg_word_size = sizeof(cs4271_dflt_reg[0]),
+ .compress_type = SND_SOC_FLAT_COMPRESSION,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit cs4271_spi_probe(struct spi_device *spi)
+{
+ struct cs4271_private *cs4271;
+
+ cs4271 = devm_kzalloc(&spi->dev, sizeof(*cs4271), GFP_KERNEL);
+ if (!cs4271)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, cs4271);
+ cs4271->control_data = spi;
+ cs4271->bus_type = SND_SOC_SPI;
+
+ return snd_soc_register_codec(&spi->dev, &soc_codec_dev_cs4271,
+ &cs4271_dai, 1);
+}
+
+static int __devexit cs4271_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ return 0;
+}
+
+static struct spi_driver cs4271_spi_driver = {
+ .driver = {
+ .name = "cs4271",
+ .owner = THIS_MODULE,
+ },
+ .probe = cs4271_spi_probe,
+ .remove = __devexit_p(cs4271_spi_remove),
+};
+#endif /* defined(CONFIG_SPI_MASTER) */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static const struct i2c_device_id cs4271_i2c_id[] = {
+ {"cs4271", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs4271_i2c_id);
+
+static int __devinit cs4271_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct cs4271_private *cs4271;
+
+ cs4271 = devm_kzalloc(&client->dev, sizeof(*cs4271), GFP_KERNEL);
+ if (!cs4271)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, cs4271);
+ cs4271->control_data = client;
+ cs4271->bus_type = SND_SOC_I2C;
+
+ return snd_soc_register_codec(&client->dev, &soc_codec_dev_cs4271,
+ &cs4271_dai, 1);
+}
+
+static int __devexit cs4271_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ return 0;
+}
+
+static struct i2c_driver cs4271_i2c_driver = {
+ .driver = {
+ .name = "cs4271",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs4271_i2c_id,
+ .probe = cs4271_i2c_probe,
+ .remove = __devexit_p(cs4271_i2c_remove),
+};
+#endif /* defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) */
+
+/*
+ * We only register our serial bus driver here without
+ * assignment to particular chip. So if any of the below
+ * fails, there is some problem with I2C or SPI subsystem.
+ * In most cases this module will be compiled with support
+ * of only one serial bus.
+ */
+static int __init cs4271_modinit(void)
+{
+ int ret;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&cs4271_i2c_driver);
+ if (ret) {
+ pr_err("Failed to register CS4271 I2C driver: %d\n", ret);
+ return ret;
+ }
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&cs4271_spi_driver);
+ if (ret) {
+ pr_err("Failed to register CS4271 SPI driver: %d\n", ret);
+ return ret;
+ }
+#endif
+
+ return 0;
+}
+module_init(cs4271_modinit);
+
+static void __exit cs4271_modexit(void)
+{
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&cs4271_spi_driver);
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&cs4271_i2c_driver);
+#endif
+}
+module_exit(cs4271_modexit);
+
+MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>");
+MODULE_DESCRIPTION("Cirrus Logic CS4271 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs42888.c b/sound/soc/codecs/cs42888.c
new file mode 100644
index 00000000..03f160b8
--- /dev/null
+++ b/sound/soc/codecs/cs42888.c
@@ -0,0 +1,1112 @@
+/*
+ * cs42888.c -- CS42888 ALSA SoC Audio Driver
+ * Copyright (C) 2010-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/fsl_devices.h>
+#include <mach/hardware.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <asm/div64.h>
+#include "cs42888.h"
+
+#include "../imx/imx-pcm.h"
+#define CS42888_NUM_SUPPLIES 4
+static const char *cs42888_supply_names[CS42888_NUM_SUPPLIES] = {
+ "VA",
+ "VD",
+ "VLS",
+ "VLC",
+};
+
+#define CS42888_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+/* CS42888 registers addresses */
+#define CS42888_CHIPID 0x01 /* Chip ID */
+#define CS42888_PWRCTL 0x02 /* Power Control */
+#define CS42888_MODE 0x03 /* Functional Mode */
+#define CS42888_FORMAT 0x04 /* Interface Formats */
+#define CS42888_ADCCTL 0x05 /* ADC Control */
+#define CS42888_TRANS 0x06 /* Transition Control */
+#define CS42888_MUTE 0x07 /* Mute Control */
+#define CS42888_VOLAOUT1 0x08 /* Volume Control AOUT1*/
+#define CS42888_VOLAOUT2 0x09 /* Volume Control AOUT2*/
+#define CS42888_VOLAOUT3 0x0A /* Volume Control AOUT3*/
+#define CS42888_VOLAOUT4 0x0B /* Volume Control AOUT4*/
+#define CS42888_VOLAOUT5 0x0C /* Volume Control AOUT5*/
+#define CS42888_VOLAOUT6 0x0D /* Volume Control AOUT6*/
+#define CS42888_VOLAOUT7 0x0E /* Volume Control AOUT7*/
+#define CS42888_VOLAOUT8 0x0F /* Volume Control AOUT8*/
+#define CS42888_DACINV 0x10 /* DAC Channel Invert */
+#define CS42888_VOLAIN1 0x11 /* Volume Control AIN1 */
+#define CS42888_VOLAIN2 0x12 /* Volume Control AIN2 */
+#define CS42888_VOLAIN3 0x13 /* Volume Control AIN3 */
+#define CS42888_VOLAIN4 0x14 /* Volume Control AIN4 */
+#define CS42888_ADCINV 0x17 /* ADC Channel Invert */
+#define CS42888_STATUSCTL 0x18 /* Status Control */
+#define CS42888_STATUS 0x19 /* Status */
+#define CS42888_STATUSMASK 0x1A /* Status Mask */
+
+#define CS42888_FIRSTREG 0x01
+#define CS42888_LASTREG 0x1A
+#define CS42888_NUMREGS (CS42888_LASTREG - CS42888_FIRSTREG + 1)
+#define CS42888_I2C_INCR 0x80
+
+/* Bit masks for the CS42888 registers */
+#define CS42888_CHIPID_ID_MASK 0xF0
+#define CS42888_CHIPID_REV 0x0F
+#define CS42888_PWRCTL_PDN_ADC2_OFFSET 6
+#define CS42888_PWRCTL_PDN_ADC1_OFFSET 5
+#define CS42888_PWRCTL_PDN_DAC4_OFFSET 4
+#define CS42888_PWRCTL_PDN_DAC3_OFFSET 3
+#define CS42888_PWRCTL_PDN_DAC2_OFFSET 2
+#define CS42888_PWRCTL_PDN_DAC1_OFFSET 1
+#define CS42888_PWRCTL_PDN_OFFSET 0
+#define CS42888_PWRCTL_PDN_ADC2_MASK (1 << CS42888_PWRCTL_PDN_ADC2_OFFSET)
+#define CS42888_PWRCTL_PDN_ADC1_MASK (1 << CS42888_PWRCTL_PDN_ADC1_OFFSET)
+#define CS42888_PWRCTL_PDN_DAC4_MASK (1 << CS42888_PWRCTL_PDN_DAC4_OFFSET)
+#define CS42888_PWRCTL_PDN_DAC3_MASK (1 << CS42888_PWRCTL_PDN_DAC3_OFFSET)
+#define CS42888_PWRCTL_PDN_DAC2_MASK (1 << CS42888_PWRCTL_PDN_DAC2_OFFSET)
+#define CS42888_PWRCTL_PDN_DAC1_MASK (1 << CS42888_PWRCTL_PDN_DAC1_OFFSET)
+#define CS42888_PWRCTL_PDN_MASK (1 << CS42888_PWRCTL_PDN_OFFSET)
+
+#define CS42888_MODE_SPEED_MASK 0xF0
+#define CS42888_MODE_1X 0x00
+#define CS42888_MODE_2X 0x50
+#define CS42888_MODE_4X 0xA0
+#define CS42888_MODE_SLAVE 0xF0
+#define CS42888_MODE_DIV_MASK 0x0E
+#define CS42888_MODE_DIV1 0x00
+#define CS42888_MODE_DIV2 0x02
+#define CS42888_MODE_DIV3 0x04
+#define CS42888_MODE_DIV4 0x06
+#define CS42888_MODE_DIV5 0x08
+
+#define CS42888_FORMAT_FREEZE_OFFSET 7
+#define CS42888_FORMAT_AUX_DIF_OFFSET 6
+#define CS42888_FORMAT_DAC_DIF_OFFSET 3
+#define CS42888_FORMAT_ADC_DIF_OFFSET 0
+#define CS42888_FORMAT_FREEZE_MASK (1 << CS42888_FORMAT_FREEZE_OFFSET)
+#define CS42888_FORMAT_AUX_DIF_MASK (1 << CS42888_FORMAT_AUX_DIF_OFFSET)
+#define CS42888_FORMAT_DAC_DIF_MASK (7 << CS42888_FORMAT_DAC_DIF_OFFSET)
+#define CS42888_FORMAT_ADC_DIF_MASK (7 << CS42888_FORMAT_ADC_DIF_OFFSET)
+
+#define CS42888_TRANS_DAC_SNGVOL_OFFSET 7
+#define CS42888_TRANS_DAC_SZC_OFFSET 5
+#define CS42888_TRANS_AMUTE_OFFSET 4
+#define CS42888_TRANS_MUTE_ADC_SP_OFFSET 3
+#define CS42888_TRANS_ADC_SNGVOL_OFFSET 2
+#define CS42888_TRANS_ADC_SZC_OFFSET 0
+#define CS42888_TRANS_DAC_SNGVOL_MASK (1 << CS42888_TRANS_DAC_SNGVOL_OFFSET)
+#define CS42888_TRANS_DAC_SZC_MASK (3 << CS42888_TRANS_DAC_SZC_OFFSET)
+#define CS42888_TRANS_AMUTE_MASK (1 << CS42888_TRANS_AMUTE_OFFSET)
+#define CS42888_TRANS_MUTE_ADC_SP_MASK (1 << CS42888_TRANS_MUTE_ADC_SP_OFFSET)
+#define CS42888_TRANS_ADC_SNGVOL_MASK (1 << CS42888_TRANS_ADC_SNGVOL_OFFSET)
+#define CS42888_TRANS_ADC_SZC_MASK (3 << CS42888_TRANS_ADC_SZC_OFFSET)
+
+#define CS42888_TRANS_DAC_SZC_IC (0 << CS42888_TRANS_DAC_SZC_OFFSET)
+#define CS42888_TRANS_DAC_SZC_ZC (1 << CS42888_TRANS_DAC_SZC_OFFSET)
+#define CS42888_TRANS_DAC_SZC_SR (2 << CS42888_TRANS_DAC_SZC_OFFSET)
+#define CS42888_TRANS_DAC_SZC_SRZC (3 << CS42888_TRANS_DAC_SZC_OFFSET)
+
+#define CS42888_MUTE_AOUT8 (0x1 << 7)
+#define CS42888_MUTE_AOUT7 (0x1 << 6)
+#define CS42888_MUTE_AOUT6 (0x1 << 5)
+#define CS42888_MUTE_AOUT5 (0x1 << 4)
+#define CS42888_MUTE_AOUT4 (0x1 << 3)
+#define CS42888_MUTE_AOUT3 (0x1 << 2)
+#define CS42888_MUTE_AOUT2 (0x1 << 1)
+#define CS42888_MUTE_AOUT1 (0x1 << 0)
+#define CS42888_MUTE_ALL (CS42888_MUTE_AOUT1 | CS42888_MUTE_AOUT2 | \
+ CS42888_MUTE_AOUT3 | CS42888_MUTE_AOUT4 | \
+ CS42888_MUTE_AOUT5 | CS42888_MUTE_AOUT6 | \
+ CS42888_MUTE_AOUT7 | CS42888_MUTE_AOUT8)
+
+#define DIF_LEFT_J 0
+#define DIF_I2S 1
+#define DIF_RIGHT_J 2
+#define DIF_TDM 6
+
+/* Private data for the CS42888 */
+struct cs42888_private {
+ struct snd_soc_codec *codec;
+ u8 reg_cache[CS42888_NUMREGS + 1];
+ unsigned int mclk; /* Input frequency of the MCLK pin */
+ unsigned int mode; /* The mode (I2S or left-justified) */
+ unsigned int slave_mode;
+ unsigned int manual_mute;
+ struct regulator_bulk_data supplies[CS42888_NUM_SUPPLIES];
+ struct mxc_audio_codec_platform_data pdata;
+};
+
+/**
+ * cs42888_fill_cache - pre-fill the CS42888 register cache.
+ * @codec: the codec for this CS42888
+ *
+ * This function fills in the CS42888 register cache by reading the register
+ * values from the hardware.
+ *
+ * This CS42888 registers are cached to avoid excessive I2C I/O operations.
+ * After the initial read to pre-fill the cache, the CS42888 never updates
+ * the register values, so we won't have a cache coherency problem.
+ *
+ * We use the auto-increment feature of the CS42888 to read all registers in
+ * one shot.
+ */
+static int cs42888_fill_cache(struct snd_soc_codec *codec)
+{
+ u8 *cache = codec->reg_cache;
+ struct i2c_client *i2c_client = codec->control_data;
+ s32 length;
+
+ length = i2c_smbus_read_i2c_block_data(i2c_client,
+ CS42888_FIRSTREG | CS42888_I2C_INCR, CS42888_NUMREGS, \
+ cache + 1);
+
+ if (length != CS42888_NUMREGS) {
+ dev_err(codec->dev, "i2c read failure, addr=0x%x\n",
+ i2c_client->addr);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+
+#ifdef CS42888_DEBUG
+static void dump_reg(struct snd_soc_codec *codec)
+{
+ int i, reg;
+ int ret;
+ u8 *cache = codec->reg_cache + 1;
+
+ printk(KERN_DEBUG "dump begin\n");
+ printk(KERN_DEBUG "reg value in cache\n");
+ for (i = 0; i < CS42888_NUMREGS; i++)
+ printk(KERN_DEBUG "reg[%d] = 0x%x\n", i, cache[i]);
+
+ printk(KERN_DEBUG "real reg value\n");
+ ret = cs42888_fill_cache(codec);
+ if (ret < 0) {
+ pr_err("failed to fill register cache\n");
+ return ret;
+ }
+ for (i = 0; i < CS42888_NUMREGS; i++)
+ printk(KERN_DEBUG "reg[%d] = 0x%x\n", i, cache[i]);
+
+ printk(KERN_DEBUG "dump end\n");
+}
+#else
+static void dump_reg(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+/* -127.5dB to 0dB with step of 0.5dB */
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+/* -64dB to 24dB with step of 0.5dB */
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 1);
+
+static int cs42888_out_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ int ret;
+ u16 val;
+
+ ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* Now write again with the volume update bit set */
+ val = snd_soc_read(codec, reg);
+ ret = snd_soc_write(codec, reg, val);
+
+ val = snd_soc_read(codec, reg2);
+ ret = snd_soc_write(codec, reg2, val);
+ return 0;
+}
+
+int cs42888_info_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int max = mc->max;
+ int min = mc->min;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = max-min;
+ return 0;
+}
+
+int cs42888_get_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ int min = mc->min;
+ int val = snd_soc_read(codec, reg);
+
+ ucontrol->value.integer.value[0] =
+ ((signed char)(val))-min;
+
+ val = snd_soc_read(codec, reg2);
+ ucontrol->value.integer.value[1] =
+ ((signed char)(val))-min;
+ return 0;
+}
+
+int cs42888_put_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ int min = mc->min;
+ unsigned short val;
+ int ret;
+
+ val = (ucontrol->value.integer.value[0]+min);
+ ret = snd_soc_write(codec, reg, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ val = ((ucontrol->value.integer.value[1]+min));
+ ret = snd_soc_write(codec, reg2, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+#define SOC_CS42888_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, \
+ xinvert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_2r, \
+ .get = snd_soc_get_volsw_2r, \
+ .put = cs42888_out_vu, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = reg_left, \
+ .rreg = reg_right, \
+ .shift = xshift, \
+ .max = xmax, \
+ .invert = xinvert} \
+}
+
+#define SOC_CS42888_DOUBLE_R_S8_TLV(xname, reg_left, reg_right, xmin, xmax, \
+ tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = cs42888_info_volsw_s8, .get = cs42888_get_volsw_s8, \
+ .put = cs42888_put_volsw_s8, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = reg_left, \
+ .rreg = reg_right, \
+ .min = xmin, \
+ .max = xmax} \
+}
+
+static const char *cs42888_adcfilter[] = { "None", "High Pass" };
+static const char *cs42888_dacinvert[] = { "Disabled", "Enabled" };
+static const char *cs42888_adcinvert[] = { "Disabled", "Enabled" };
+static const char *cs42888_dacamute[] = { "Disabled", "AutoMute" };
+static const char *cs42888_dac_sngvol[] = { "Disabled", "Enabled" };
+static const char *cs42888_dac_szc[] = { "Immediate Change", "Zero Cross",
+ "Soft Ramp", "Soft Ramp on Zero Cross" };
+static const char *cs42888_mute_adc[] = { "UnMute", "Mute" };
+static const char *cs42888_adc_sngvol[] = { "Disabled", "Enabled" };
+static const char *cs42888_adc_szc[] = { "Immediate Change", "Zero Cross",
+ "Soft Ramp", "Soft Ramp on Zero Cross" };
+static const char *cs42888_dac_dem[] = { "No-De-Emphasis", "De-Emphasis" };
+static const char *cs42888_adc_single[] = { "Differential", "Single-Ended" };
+
+static const struct soc_enum cs42888_enum[] = {
+ SOC_ENUM_SINGLE(CS42888_ADCCTL, 7, 2, cs42888_adcfilter),
+ SOC_ENUM_DOUBLE(CS42888_DACINV, 0, 1, 2, cs42888_dacinvert),
+ SOC_ENUM_DOUBLE(CS42888_DACINV, 2, 3, 2, cs42888_dacinvert),
+ SOC_ENUM_DOUBLE(CS42888_DACINV, 4, 5, 2, cs42888_dacinvert),
+ SOC_ENUM_DOUBLE(CS42888_DACINV, 6, 7, 2, cs42888_dacinvert),
+ SOC_ENUM_DOUBLE(CS42888_ADCINV, 0, 1, 2, cs42888_adcinvert),
+ SOC_ENUM_DOUBLE(CS42888_ADCINV, 2, 3, 2, cs42888_adcinvert),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 4, 2, cs42888_dacamute),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 7, 2, cs42888_dac_sngvol),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 5, 4, cs42888_dac_szc),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 3, 2, cs42888_mute_adc),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 2, 2, cs42888_adc_sngvol),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 0, 4, cs42888_adc_szc),
+ SOC_ENUM_SINGLE(CS42888_ADCCTL, 5, 2, cs42888_dac_dem),
+ SOC_ENUM_SINGLE(CS42888_ADCCTL, 4, 2, cs42888_adc_single),
+ SOC_ENUM_SINGLE(CS42888_ADCCTL, 3, 2, cs42888_adc_single),
+};
+
+static const struct snd_kcontrol_new cs42888_snd_controls[] = {
+SOC_CS42888_DOUBLE_R_TLV("DAC1 Playback Volume",
+ CS42888_VOLAOUT1,
+ CS42888_VOLAOUT2,
+ 0, 0xff, 1, dac_tlv),
+SOC_CS42888_DOUBLE_R_TLV("DAC2 Playback Volume",
+ CS42888_VOLAOUT3,
+ CS42888_VOLAOUT4,
+ 0, 0xff, 1, dac_tlv),
+SOC_CS42888_DOUBLE_R_TLV("DAC3 Playback Volume",
+ CS42888_VOLAOUT5,
+ CS42888_VOLAOUT6,
+ 0, 0xff, 1, dac_tlv),
+SOC_CS42888_DOUBLE_R_TLV("DAC4 Playback Volume",
+ CS42888_VOLAOUT7,
+ CS42888_VOLAOUT8,
+ 0, 0xff, 1, dac_tlv),
+SOC_CS42888_DOUBLE_R_S8_TLV("ADC1 Capture Volume",
+ CS42888_VOLAIN1,
+ CS42888_VOLAIN2,
+ -128, 48, adc_tlv),
+SOC_CS42888_DOUBLE_R_S8_TLV("ADC2 Capture Volume",
+ CS42888_VOLAIN3,
+ CS42888_VOLAIN4,
+ -128, 48, adc_tlv),
+SOC_ENUM("ADC High-Pass Filter Switch", cs42888_enum[0]),
+SOC_ENUM("DAC1 Invert Switch", cs42888_enum[1]),
+SOC_ENUM("DAC2 Invert Switch", cs42888_enum[2]),
+SOC_ENUM("DAC3 Invert Switch", cs42888_enum[3]),
+SOC_ENUM("DAC4 Invert Switch", cs42888_enum[4]),
+SOC_ENUM("ADC1 Invert Switch", cs42888_enum[5]),
+SOC_ENUM("ADC2 Invert Switch", cs42888_enum[6]),
+SOC_ENUM("DAC Auto Mute Switch", cs42888_enum[7]),
+SOC_ENUM("DAC Single Volume Control Switch", cs42888_enum[8]),
+SOC_ENUM("DAC Soft Ramp and Zero Cross Control Switch", cs42888_enum[9]),
+SOC_ENUM("Mute ADC Serial Port Switch", cs42888_enum[10]),
+SOC_ENUM("ADC Single Volume Control Switch", cs42888_enum[11]),
+SOC_ENUM("ADC Soft Ramp and Zero Cross Control Switch", cs42888_enum[12]),
+SOC_ENUM("DAC Deemphasis Switch", cs42888_enum[13]),
+SOC_ENUM("ADC1 Single Ended Mode Switch", cs42888_enum[14]),
+SOC_ENUM("ADC2 Single Ended Mode Switch", cs42888_enum[15]),
+};
+
+
+static const struct snd_soc_dapm_widget cs42888_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC1", "Playback", CS42888_PWRCTL, 1, 1),
+SND_SOC_DAPM_DAC("DAC2", "Playback", CS42888_PWRCTL, 2, 1),
+SND_SOC_DAPM_DAC("DAC3", "Playback", CS42888_PWRCTL, 3, 1),
+SND_SOC_DAPM_DAC("DAC4", "Playback", CS42888_PWRCTL, 4, 1),
+
+SND_SOC_DAPM_OUTPUT("AOUT1L"),
+SND_SOC_DAPM_OUTPUT("AOUT1R"),
+SND_SOC_DAPM_OUTPUT("AOUT2L"),
+SND_SOC_DAPM_OUTPUT("AOUT2R"),
+SND_SOC_DAPM_OUTPUT("AOUT3L"),
+SND_SOC_DAPM_OUTPUT("AOUT3R"),
+SND_SOC_DAPM_OUTPUT("AOUT4L"),
+SND_SOC_DAPM_OUTPUT("AOUT4R"),
+
+SND_SOC_DAPM_ADC("ADC1", "Capture", CS42888_PWRCTL, 5, 1),
+SND_SOC_DAPM_ADC("ADC2", "Capture", CS42888_PWRCTL, 6, 1),
+
+SND_SOC_DAPM_INPUT("AIN1L"),
+SND_SOC_DAPM_INPUT("AIN1R"),
+SND_SOC_DAPM_INPUT("AIN2L"),
+SND_SOC_DAPM_INPUT("AIN2R"),
+
+SND_SOC_DAPM_PGA_E("PWR", CS42888_PWRCTL, 0, 1, NULL, 0,
+ NULL, 0),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Playback */
+ { "PWR", NULL, "DAC1" },
+ { "PWR", NULL, "DAC1" },
+
+ { "PWR", NULL, "DAC2" },
+ { "PWR", NULL, "DAC2" },
+
+ { "PWR", NULL, "DAC3" },
+ { "PWR", NULL, "DAC3" },
+
+ { "PWR", NULL, "DAC4" },
+ { "PWR", NULL, "DAC4" },
+
+ { "AOUT1L", NULL, "PWR" },
+ { "AOUT1R", NULL, "PWR" },
+
+ { "AOUT2L", NULL, "PWR" },
+ { "AOUT2R", NULL, "PWR" },
+
+ { "AOUT3L", NULL, "PWR" },
+ { "AOUT3R", NULL, "PWR" },
+
+ { "AOUT4L", NULL, "PWR" },
+ { "AOUT4R", NULL, "PWR" },
+
+ /* Capture */
+ { "PWR", NULL, "AIN1L" },
+ { "PWR", NULL, "AIN1R" },
+
+ { "PWR", NULL, "AIN2L" },
+ { "PWR", NULL, "AIN2R" },
+
+ { "ADC1", NULL, "PWR" },
+ { "ADC1", NULL, "PWR" },
+
+ { "ADC2", NULL, "PWR" },
+ { "ADC2", NULL, "PWR" },
+};
+
+
+static int cs42888_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(&codec->dapm, cs42888_dapm_widgets,
+ ARRAY_SIZE(cs42888_dapm_widgets));
+
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(&codec->dapm);
+ return 0;
+}
+
+/**
+ * struct cs42888_mode_ratios - clock ratio tables
+ * @ratio: the ratio of MCLK to the sample rate
+ * @speed_mode: the Speed Mode bits to set in the Mode Control register for
+ * this ratio
+ * @mclk: the Ratio Select bits to set in the Mode Control register for this
+ * ratio
+ *
+ * The data for this chart is taken from Table 10 of the CS42888 reference
+ * manual.
+ *
+ * This table is used to determine how to program the Functional Mode register.
+ * It is also used by cs42888_set_dai_sysclk() to tell ALSA which sampling
+ * rates the CS42888 currently supports.
+ *
+ * @speed_mode is the corresponding bit pattern to be written to the
+ * MODE bits of the Mode Control Register
+ *
+ * @mclk is the corresponding bit pattern to be wirten to the MCLK bits of
+ * the Mode Control Register.
+ *
+ */
+struct cs42888_mode_ratios {
+ unsigned int ratio;
+ u8 speed_mode;
+ u8 mclk;
+};
+
+static struct cs42888_mode_ratios cs42888_mode_ratios[] = {
+ {64, CS42888_MODE_4X, CS42888_MODE_DIV1},
+ {96, CS42888_MODE_4X, CS42888_MODE_DIV2},
+ {128, CS42888_MODE_2X, CS42888_MODE_DIV1},
+ {192, CS42888_MODE_2X, CS42888_MODE_DIV2},
+ {256, CS42888_MODE_1X, CS42888_MODE_DIV1},
+ {384, CS42888_MODE_2X, CS42888_MODE_DIV4},
+ {512, CS42888_MODE_1X, CS42888_MODE_DIV3},
+ {768, CS42888_MODE_1X, CS42888_MODE_DIV4},
+ {1024, CS42888_MODE_1X, CS42888_MODE_DIV5}
+};
+
+/* The number of MCLK/LRCK ratios supported by the CS42888 */
+#define NUM_MCLK_RATIOS ARRAY_SIZE(cs42888_mode_ratios)
+
+/**
+ * cs42888_set_dai_sysclk - determine the CS42888 samples rates.
+ * @codec_dai: the codec DAI
+ * @clk_id: the clock ID (ignored)
+ * @freq: the MCLK input frequency
+ * @dir: the clock direction (ignored)
+ *
+ * This function is used to tell the codec driver what the input MCLK
+ * frequency is.
+ *
+ */
+static int cs42888_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs42888_private *cs42888 = snd_soc_codec_get_drvdata(codec);
+
+ cs42888->mclk = freq;
+ return 0;
+}
+
+/**
+ * cs42888_set_dai_fmt - configure the codec for the selected audio format
+ * @codec_dai: the codec DAI
+ * @format: a SND_SOC_DAIFMT_x value indicating the data format
+ *
+ * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the
+ * codec accordingly.
+ *
+ * Currently, this function only supports SND_SOC_DAIFMT_I2S and
+ * SND_SOC_DAIFMT_LEFT_J. The CS42888 codec also supports right-justified
+ * data for playback only, but ASoC currently does not support different
+ * formats for playback vs. record.
+ */
+static int cs42888_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs42888_private *cs42888 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+ u8 val;
+
+ val = snd_soc_read(codec, CS42888_FORMAT);
+ val &= ~CS42888_FORMAT_DAC_DIF_MASK;
+ val &= ~CS42888_FORMAT_ADC_DIF_MASK;
+ /* set DAI format */
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ val |= DIF_LEFT_J << CS42888_FORMAT_DAC_DIF_OFFSET;
+ val |= DIF_LEFT_J << CS42888_FORMAT_ADC_DIF_OFFSET;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val |= DIF_I2S << CS42888_FORMAT_DAC_DIF_OFFSET;
+ val |= DIF_I2S << CS42888_FORMAT_ADC_DIF_OFFSET;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ val |= DIF_RIGHT_J << CS42888_FORMAT_DAC_DIF_OFFSET;
+ val |= DIF_RIGHT_J << CS42888_FORMAT_ADC_DIF_OFFSET;
+ break;
+ default:
+ dev_err(codec->dev, "invalid dai format\n");
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = snd_soc_write(codec, CS42888_FORMAT, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ val = snd_soc_read(codec, CS42888_MODE);
+ /* set master/slave audio interface */
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs42888->slave_mode = 1;
+ val &= ~CS42888_MODE_SPEED_MASK;
+ val |= CS42888_MODE_SLAVE;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs42888->slave_mode = 0;
+ break;
+ default:
+ /* all other modes are unsupported by the hardware */
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = snd_soc_write(codec, CS42888_MODE, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ dump_reg(codec);
+ return ret;
+}
+
+/**
+ * cs42888_hw_params - program the CS42888 with the given hardware parameters.
+ * @substream: the audio stream
+ * @params: the hardware parameters to set
+
+ * @dai: the SOC DAI (ignored)
+ *
+ * This function programs the hardware with the values provided.
+ * Specifically, the sample rate and the data format.
+ *
+ * The .ops functions are used to provide board-specific data, like input
+ * frequencies, to this driver. This function takes that information,
+ * combines it with the hardware parameters provided, and programs the
+ * hardware accordingly.
+ */
+static int cs42888_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_pcm_runtime_data *iprtd = substream->runtime->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct cs42888_private *cs42888 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+ unsigned int i;
+ unsigned int rate;
+ unsigned int ratio;
+ u32 val;
+
+ if (iprtd->asrc_enable)
+ rate = iprtd->p2p->p2p_rate;
+ else
+ rate = params_rate(params); /* Sampling rate, in Hz */
+ ratio = cs42888->mclk / rate; /* MCLK/LRCK ratio */
+ for (i = 0; i < NUM_MCLK_RATIOS; i++) {
+ if (cs42888_mode_ratios[i].ratio == ratio)
+ break;
+ }
+
+ if (i == NUM_MCLK_RATIOS) {
+ /* We did not find a matching ratio */
+ dev_err(codec->dev, "could not find matching ratio\n");
+ return -EINVAL;
+ }
+
+ if (!cs42888->slave_mode) {
+ val = snd_soc_read(codec, CS42888_MODE);
+ val &= ~CS42888_MODE_SPEED_MASK;
+ val |= cs42888_mode_ratios[i].speed_mode;
+ val &= ~CS42888_MODE_DIV_MASK;
+ val |= cs42888_mode_ratios[i].mclk;
+ } else {
+ val = snd_soc_read(codec, CS42888_MODE);
+ val &= ~CS42888_MODE_SPEED_MASK;
+ val |= CS42888_MODE_SLAVE;
+ val &= ~CS42888_MODE_DIV_MASK;
+ val |= cs42888_mode_ratios[i].mclk;
+ }
+ ret = snd_soc_write(codec, CS42888_MODE, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ /* Unmute all the channels */
+ val = snd_soc_read(codec, CS42888_MUTE);
+ val &= ~CS42888_MUTE_ALL;
+ ret = snd_soc_write(codec, CS42888_MUTE, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ ret = cs42888_fill_cache(codec);
+ if (ret < 0) {
+ pr_err("failed to fill register cache\n");
+ return ret;
+ }
+
+ dump_reg(codec);
+ return ret;
+}
+
+/**
+ * cs42888_shutdown - cs42888 enters into low power mode again.
+ * @substream: the audio stream
+ * @dai: the SOC DAI (ignored)
+ *
+ * The .ops functions are used to provide board-specific data, like input
+ * frequencies, to this driver. This function takes that information,
+ * combines it with the hardware parameters provided, and programs the
+ * hardware accordingly.
+ */
+static void cs42888_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ int ret;
+ u8 val;
+
+ /* Mute all the channels */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ val = snd_soc_read(codec, CS42888_MUTE);
+ val |= CS42888_MUTE_ALL;
+ ret = snd_soc_write(codec, CS42888_MUTE, val);
+ if (ret < 0)
+ pr_err("i2c write failed\n");
+ }
+
+}
+
+static int cs42888_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dai *tmp_codec_dai;
+ struct snd_soc_pcm_runtime *tmp_rtd;
+ u32 i;
+
+ for (i = 0; i < card->num_rtd; i++) {
+ tmp_codec_dai = card->rtd[i].codec_dai;
+ tmp_rtd = (struct snd_soc_pcm_runtime *)(card->rtd + i);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ tmp_codec_dai->pop_wait) {
+ tmp_codec_dai->pop_wait = 0;
+ cancel_delayed_work(&tmp_rtd->delayed_work);
+ }
+ }
+ return 0;
+}
+
+static struct snd_soc_dai_ops cs42888_dai_ops = {
+ .set_fmt = cs42888_set_dai_fmt,
+ .set_sysclk = cs42888_set_dai_sysclk,
+ .hw_params = cs42888_hw_params,
+ .shutdown = cs42888_shutdown,
+ .prepare = cs42888_prepare,
+};
+
+
+struct snd_soc_dai_driver cs42888_dai[] = {
+ {
+ .name = "CS42888",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000),
+ .formats = CS42888_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 4,
+ .rates = (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000),
+ .formats = CS42888_FORMATS,
+ },
+ .ops = &cs42888_dai_ops,
+ },
+ {
+ .name = "CS42888_ASRC",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = CS42888_FORMATS,
+ },
+ .ops = &cs42888_dai_ops,
+ },
+};
+
+/**
+ * cs42888_probe - ASoC probe function
+ * @pdev: platform device
+ *
+ * This function is called when ASoC has all the pieces it needs to
+ * instantiate a sound driver.
+ */
+static int cs42888_probe(struct snd_soc_codec *codec)
+{
+ int ret, i;
+ int val;
+
+ struct cs42888_private *cs42888 = snd_soc_codec_get_drvdata(codec);
+ cs42888->codec = codec;
+
+ /* setup i2c data ops */
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+ if (cpu_is_mx6q() || cpu_is_mx6dl()) {
+ for (i = 0; i < ARRAY_SIZE(cs42888->supplies); i++)
+ cs42888->supplies[i].supply = cs42888_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev,
+ ARRAY_SIZE(cs42888->supplies), cs42888->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs42888->supplies),
+ cs42888->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n",
+ ret);
+ goto err;
+ }
+ }
+ msleep(1);
+
+ /* The I2C interface is set up, so pre-fill our register cache */
+ ret = cs42888_fill_cache(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to fill register cache\n");
+ goto err;
+ }
+
+ /* Enter low power state */
+ val = snd_soc_read(codec, CS42888_PWRCTL);
+ val |= CS42888_PWRCTL_PDN_MASK;
+ ret = snd_soc_write(codec, CS42888_PWRCTL, val);
+ if (ret < 0) {
+ dev_err(codec->dev, "i2c write failed\n");
+ return ret;
+ }
+
+ /* Disable auto-mute */
+ val = snd_soc_read(codec, CS42888_TRANS);
+ val &= ~CS42888_TRANS_AMUTE_MASK;
+ val &= ~CS42888_TRANS_DAC_SZC_MASK;
+ val |= CS42888_TRANS_DAC_SZC_SR;
+ ret = snd_soc_write(codec, CS42888_TRANS, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+ /* Add the non-DAPM controls */
+ snd_soc_add_controls(codec, cs42888_snd_controls,
+ ARRAY_SIZE(cs42888_snd_controls));
+
+ /* Add DAPM controls */
+ cs42888_add_widgets(codec);
+
+ return 0;
+err:
+ regulator_bulk_disable(ARRAY_SIZE(cs42888->supplies),
+ cs42888->supplies);
+ regulator_bulk_free(ARRAY_SIZE(cs42888->supplies),
+ cs42888->supplies);
+ return ret;
+}
+
+/**
+ * cs42888_remove - ASoC remove function
+ * @pdev: platform device
+ *
+ * This function is the counterpart to cs42888_probe().
+ */
+static int cs42888_remove(struct snd_soc_codec *codec)
+{
+ struct cs42888_private *cs42888 = snd_soc_codec_get_drvdata(codec);
+
+ regulator_bulk_disable(ARRAY_SIZE(cs42888->supplies),
+ cs42888->supplies);
+ regulator_bulk_free(ARRAY_SIZE(cs42888->supplies),
+ cs42888->supplies);
+
+ return 0;
+};
+
+/*
+ * ASoC codec device structure
+ *
+ * Assign this variable to the codec_dev field of the machine driver's
+ * snd_soc_device structure.
+ */
+struct snd_soc_codec_driver cs42888_driver = {
+ .probe = cs42888_probe,
+ .remove = cs42888_remove,
+ .reg_cache_size = CS42888_NUMREGS + 1,
+ .reg_word_size = sizeof(u8),
+ .reg_cache_step = 1,
+};
+
+/**
+ * cs42888_i2c_probe - initialize the I2C interface of the CS42888
+ * @i2c_client: the I2C client object
+ * @id: the I2C device ID (ignored)
+ *
+ * This function is called whenever the I2C subsystem finds a device that
+ * matches the device ID given via a prior call to i2c_add_driver().
+ */
+static int cs42888_i2c_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct cs42888_private *cs42888;
+ int ret;
+ int val;
+
+ /* Verify that we have a CS42888 */
+ val = i2c_smbus_read_byte_data(i2c_client, CS42888_CHIPID);
+ if (val < 0) {
+ pr_err("Device with ID register %x is not a CS42888", val);
+ return -ENODEV;
+ }
+ /* The top four bits of the chip ID should be 0000. */
+ if ((val & CS42888_CHIPID_ID_MASK) != 0x00) {
+ dev_err(&i2c_client->dev, "device is not a CS42888\n");
+ return -ENODEV;
+ }
+
+ dev_info(&i2c_client->dev, "found device at i2c address %X\n",
+ i2c_client->addr);
+ dev_info(&i2c_client->dev, "hardware revision %X\n", val & 0xF);
+
+ /* Allocate enough space for the snd_soc_codec structure
+ and our private data together. */
+ cs42888 = kzalloc(sizeof(struct cs42888_private), GFP_KERNEL);
+ if (!cs42888) {
+ dev_err(&i2c_client->dev, "could not allocate codec\n");
+ return -ENOMEM;
+ }
+
+ if (i2c_client->dev.platform_data) {
+ memcpy(&cs42888->pdata, i2c_client->dev.platform_data,
+ sizeof(cs42888->pdata));
+ cs42888_dai[0].playback.rates = cs42888->pdata.rates;
+ cs42888_dai[0].capture.rates = cs42888->pdata.rates;
+ }
+
+ i2c_set_clientdata(i2c_client, cs42888);
+
+ ret = snd_soc_register_codec(&i2c_client->dev,
+ &cs42888_driver, cs42888_dai, 2);
+ if (ret) {
+ dev_err(&i2c_client->dev, "Failed to register codec:%d\n", ret);
+ kfree(cs42888);
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * cs42888_i2c_remove - remove an I2C device
+ * @i2c_client: the I2C client object
+ *
+ * This function is the counterpart to cs42888_i2c_probe().
+ */
+static int cs42888_i2c_remove(struct i2c_client *i2c_client)
+{
+ struct cs42888_private *cs42888 = i2c_get_clientdata(i2c_client);
+
+ snd_soc_unregister_codec(&i2c_client->dev);
+ kfree(cs42888);
+
+ return 0;
+}
+
+/*
+ * cs42888_i2c_id - I2C device IDs supported by this driver
+ */
+static struct i2c_device_id cs42888_i2c_id[] = {
+ {"cs42888", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs42888_i2c_id);
+
+#ifdef CONFIG_PM
+/* This suspend/resume implementation can handle both - a simple standby
+ * where the codec remains powered, and a full suspend, where the voltage
+ * domain the codec is connected to is teared down and/or any other hardware
+ * reset condition is asserted.
+ *
+ * The codec's own power saving features are enabled in the suspend callback,
+ * and all registers are written back to the hardware when resuming.
+ */
+
+static int cs42888_i2c_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct cs42888_private *cs42888 = i2c_get_clientdata(client);
+ struct snd_soc_codec *codec = cs42888->codec;
+ int reg = snd_soc_read(codec, CS42888_PWRCTL) | CS42888_PWRCTL_PDN_MASK;
+
+ return snd_soc_write(codec, CS42888_PWRCTL, reg);
+}
+
+static int cs42888_i2c_resume(struct i2c_client *client)
+{
+ struct cs42888_private *cs42888 = i2c_get_clientdata(client);
+ struct snd_soc_codec *codec = cs42888->codec;
+ int reg;
+
+ /* In case the device was put to hard reset during sleep, we need to
+ * wait 500ns here before any I2C communication. */
+ ndelay(500);
+
+ /* first restore the entire register cache ... */
+ for (reg = CS42888_FIRSTREG; reg <= CS42888_LASTREG; reg++) {
+ u8 val = snd_soc_read(codec, reg);
+
+ if (i2c_smbus_write_byte_data(client, reg, val)) {
+ dev_err(codec->dev, "i2c write failed\n");
+ return -EIO;
+ }
+ }
+
+ /* ... then disable the power-down bits */
+ reg = snd_soc_read(codec, CS42888_PWRCTL);
+ reg &= ~CS42888_PWRCTL_PDN_MASK;
+
+ return snd_soc_write(codec, CS42888_PWRCTL, reg);
+}
+#else
+#define cs42888_i2c_suspend NULL
+#define cs42888_i2c_resume NULL
+#endif /* CONFIG_PM */
+
+/*
+ * cs42888_i2c_driver - I2C device identification
+ *
+ * This structure tells the I2C subsystem how to identify and support a
+ * given I2C device type.
+ */
+static struct i2c_driver cs42888_i2c_driver = {
+ .driver = {
+ .name = "cs42888",
+ .owner = THIS_MODULE,
+ },
+ .probe = cs42888_i2c_probe,
+ .remove = cs42888_i2c_remove,
+ .suspend = cs42888_i2c_suspend,
+ .resume = cs42888_i2c_resume,
+ .id_table = cs42888_i2c_id,
+};
+
+static int __init cs42888_init(void)
+{
+ pr_info("Cirrus Logic CS42888 ALSA SoC Codec Driver\n");
+ return i2c_add_driver(&cs42888_i2c_driver);
+}
+module_init(cs42888_init);
+
+static void __exit cs42888_exit(void)
+{
+ i2c_del_driver(&cs42888_i2c_driver);
+}
+module_exit(cs42888_exit);
+
+MODULE_AUTHOR("Xu Lionel <R63889@freescale.com>");
+MODULE_DESCRIPTION("Cirrus Logic CS42888 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs42888.h b/sound/soc/codecs/cs42888.h
new file mode 100644
index 00000000..a7c92d56
--- /dev/null
+++ b/sound/soc/codecs/cs42888.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2010-2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef _CS42888_H
+#define _CS42888_H
+
+/*
+ * The ASoC codec DAI structure for the CS42888. Assign this structure to
+ * the .codec_dai field of your machine driver's snd_soc_dai_link structure.
+ */
+extern struct snd_soc_dai_driver cs42888_dai[];
+
+/*
+ * The ASoC codec device structure for the CS42888. Assign this structure
+ * to the .codec_dev field of your machine driver's snd_soc_device
+ * structure.
+ */
+extern struct snd_soc_codec_device soc_codec_device_cs42888;
+
+extern void gpio_cs42888_pdwn(int pdwn);
+#endif
diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c
new file mode 100644
index 00000000..8fb70701
--- /dev/null
+++ b/sound/soc/codecs/cs42l51.c
@@ -0,0 +1,653 @@
+/*
+ * cs42l51.c
+ *
+ * ASoC Driver for Cirrus Logic CS42L51 codecs
+ *
+ * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ *
+ * Based on cs4270.c - Copyright (c) Freescale Semiconductor
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * For now:
+ * - Only I2C is support. Not SPI
+ * - master mode *NOT* supported
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm.h>
+#include <linux/i2c.h>
+
+#include "cs42l51.h"
+
+enum master_slave_mode {
+ MODE_SLAVE,
+ MODE_SLAVE_AUTO,
+ MODE_MASTER,
+};
+
+struct cs42l51_private {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+ unsigned int mclk;
+ unsigned int audio_mode; /* The mode (I2S or left-justified) */
+ enum master_slave_mode func;
+};
+
+#define CS42L51_FORMATS ( \
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE)
+
+static int cs42l51_fill_cache(struct snd_soc_codec *codec)
+{
+ u8 *cache = codec->reg_cache + 1;
+ struct i2c_client *i2c_client = codec->control_data;
+ s32 length;
+
+ length = i2c_smbus_read_i2c_block_data(i2c_client,
+ CS42L51_FIRSTREG | 0x80, CS42L51_NUMREGS, cache);
+ if (length != CS42L51_NUMREGS) {
+ dev_err(&i2c_client->dev,
+ "I2C read failure, addr=0x%x (ret=%d vs %d)\n",
+ i2c_client->addr, length, CS42L51_NUMREGS);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3;
+
+ switch (value) {
+ default:
+ case 0:
+ ucontrol->value.integer.value[0] = 0;
+ break;
+ /* same value : (L+R)/2 and (R+L)/2 */
+ case 1:
+ case 2:
+ ucontrol->value.integer.value[0] = 1;
+ break;
+ case 3:
+ ucontrol->value.integer.value[0] = 2;
+ break;
+ }
+
+ return 0;
+}
+
+#define CHAN_MIX_NORMAL 0x00
+#define CHAN_MIX_BOTH 0x55
+#define CHAN_MIX_SWAP 0xFF
+
+static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+
+ switch (ucontrol->value.integer.value[0]) {
+ default:
+ case 0:
+ val = CHAN_MIX_NORMAL;
+ break;
+ case 1:
+ val = CHAN_MIX_BOTH;
+ break;
+ case 2:
+ val = CHAN_MIX_SWAP;
+ break;
+ }
+
+ snd_soc_write(codec, CS42L51_PCM_MIXER, val);
+
+ return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0);
+static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0);
+/* This is a lie. after -102 db, it stays at -102 */
+/* maybe a range would be better */
+static const DECLARE_TLV_DB_SCALE(aout_tlv, -11550, 50, 0);
+
+static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0);
+static const char *chan_mix[] = {
+ "L R",
+ "L+R",
+ "R L",
+};
+
+static const struct soc_enum cs42l51_chan_mix =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(chan_mix), chan_mix);
+
+static const struct snd_kcontrol_new cs42l51_snd_controls[] = {
+ SOC_DOUBLE_R_SX_TLV("PCM Playback Volume",
+ CS42L51_PCMA_VOL, CS42L51_PCMB_VOL,
+ 7, 0xffffff99, 0x18, adc_pcm_tlv),
+ SOC_DOUBLE_R("PCM Playback Switch",
+ CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1),
+ SOC_DOUBLE_R_SX_TLV("Analog Playback Volume",
+ CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL,
+ 8, 0xffffff19, 0x18, aout_tlv),
+ SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume",
+ CS42L51_ADCA_VOL, CS42L51_ADCB_VOL,
+ 7, 0xffffff99, 0x18, adc_pcm_tlv),
+ SOC_DOUBLE_R("ADC Mixer Switch",
+ CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1),
+ SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0),
+ SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0),
+ SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0),
+ SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0),
+ SOC_DOUBLE_TLV("Mic Boost Volume",
+ CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv),
+ SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv),
+ SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv),
+ SOC_ENUM_EXT("PCM channel mixer",
+ cs42l51_chan_mix,
+ cs42l51_get_chan_mix, cs42l51_set_chan_mix),
+};
+
+/*
+ * to power down, one must:
+ * 1.) Enable the PDN bit
+ * 2.) enable power-down for the select channels
+ * 3.) disable the PDN bit.
+ */
+static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ unsigned long value;
+
+ value = snd_soc_read(w->codec, CS42L51_POWER_CTL1);
+ value &= ~CS42L51_POWER_CTL1_PDN;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMD:
+ value |= CS42L51_POWER_CTL1_PDN;
+ break;
+ default:
+ case SND_SOC_DAPM_POST_PMD:
+ break;
+ }
+ snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1,
+ CS42L51_POWER_CTL1_PDN, value);
+
+ return 0;
+}
+
+static const char *cs42l51_dac_names[] = {"Direct PCM",
+ "DSP PCM", "ADC"};
+static const struct soc_enum cs42l51_dac_mux_enum =
+ SOC_ENUM_SINGLE(CS42L51_DAC_CTL, 6, 3, cs42l51_dac_names);
+static const struct snd_kcontrol_new cs42l51_dac_mux_controls =
+ SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum);
+
+static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left",
+ "MIC Left", "MIC+preamp Left"};
+static const struct soc_enum cs42l51_adcl_mux_enum =
+ SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 4, 4, cs42l51_adcl_names);
+static const struct snd_kcontrol_new cs42l51_adcl_mux_controls =
+ SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum);
+
+static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right",
+ "MIC Right", "MIC+preamp Right"};
+static const struct soc_enum cs42l51_adcr_mux_enum =
+ SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 6, 4, cs42l51_adcr_names);
+static const struct snd_kcontrol_new cs42l51_adcr_mux_controls =
+ SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum);
+
+static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = {
+ SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1),
+ SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0,
+ cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+ SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0,
+ cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+ SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture",
+ CS42L51_POWER_CTL1, 1, 1,
+ cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+ SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture",
+ CS42L51_POWER_CTL1, 2, 1,
+ cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+ SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback",
+ CS42L51_POWER_CTL1, 5, 1,
+ cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+ SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback",
+ CS42L51_POWER_CTL1, 6, 1,
+ cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+
+ /* analog/mic */
+ SND_SOC_DAPM_INPUT("AIN1L"),
+ SND_SOC_DAPM_INPUT("AIN1R"),
+ SND_SOC_DAPM_INPUT("AIN2L"),
+ SND_SOC_DAPM_INPUT("AIN2R"),
+ SND_SOC_DAPM_INPUT("MICL"),
+ SND_SOC_DAPM_INPUT("MICR"),
+
+ SND_SOC_DAPM_MIXER("Mic Preamp Left",
+ CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0),
+ SND_SOC_DAPM_MIXER("Mic Preamp Right",
+ CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0),
+
+ /* HP */
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+
+ /* mux */
+ SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0,
+ &cs42l51_dac_mux_controls),
+ SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0,
+ &cs42l51_adcl_mux_controls),
+ SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0,
+ &cs42l51_adcr_mux_controls),
+};
+
+static const struct snd_soc_dapm_route cs42l51_routes[] = {
+ {"HPL", NULL, "Left DAC"},
+ {"HPR", NULL, "Right DAC"},
+
+ {"Left ADC", NULL, "Left PGA"},
+ {"Right ADC", NULL, "Right PGA"},
+
+ {"Mic Preamp Left", NULL, "MICL"},
+ {"Mic Preamp Right", NULL, "MICR"},
+
+ {"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" },
+ {"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" },
+ {"PGA-ADC Mux Left", "MIC Left", "MICL" },
+ {"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" },
+ {"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" },
+ {"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" },
+ {"PGA-ADC Mux Right", "MIC Right", "MICR" },
+ {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" },
+
+ {"Left PGA", NULL, "PGA-ADC Mux Left"},
+ {"Right PGA", NULL, "PGA-ADC Mux Right"},
+};
+
+static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_LEFT_J:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ default:
+ dev_err(codec->dev, "invalid DAI format\n");
+ ret = -EINVAL;
+ }
+
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs42l51->func = MODE_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs42l51->func = MODE_SLAVE_AUTO;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+struct cs42l51_ratios {
+ unsigned int ratio;
+ unsigned char speed_mode;
+ unsigned char mclk;
+};
+
+static struct cs42l51_ratios slave_ratios[] = {
+ { 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 },
+ { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 },
+ { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 },
+ { 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 },
+ { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 },
+ { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 },
+ { 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 },
+ { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 },
+ { 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 },
+ { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 },
+ { 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 },
+};
+
+static struct cs42l51_ratios slave_auto_ratios[] = {
+ { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 },
+ { 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 },
+ { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 },
+ { 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 },
+ { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 },
+ { 512, CS42L51_SSM_MODE, 1 }, { 768, CS42L51_SSM_MODE, 1 },
+ { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 },
+ { 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 },
+};
+
+static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+
+ cs42l51->mclk = freq;
+ return 0;
+}
+
+static int cs42l51_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+ unsigned int i;
+ unsigned int rate;
+ unsigned int ratio;
+ struct cs42l51_ratios *ratios = NULL;
+ int nr_ratios = 0;
+ int intf_ctl, power_ctl, fmt;
+
+ switch (cs42l51->func) {
+ case MODE_MASTER:
+ return -EINVAL;
+ case MODE_SLAVE:
+ ratios = slave_ratios;
+ nr_ratios = ARRAY_SIZE(slave_ratios);
+ break;
+ case MODE_SLAVE_AUTO:
+ ratios = slave_auto_ratios;
+ nr_ratios = ARRAY_SIZE(slave_auto_ratios);
+ break;
+ }
+
+ /* Figure out which MCLK/LRCK ratio to use */
+ rate = params_rate(params); /* Sampling rate, in Hz */
+ ratio = cs42l51->mclk / rate; /* MCLK/LRCK ratio */
+ for (i = 0; i < nr_ratios; i++) {
+ if (ratios[i].ratio == ratio)
+ break;
+ }
+
+ if (i == nr_ratios) {
+ /* We did not find a matching ratio */
+ dev_err(codec->dev, "could not find matching ratio\n");
+ return -EINVAL;
+ }
+
+ intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL);
+ power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL);
+
+ intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S
+ | CS42L51_INTF_CTL_DAC_FORMAT(7));
+ power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3)
+ | CS42L51_MIC_POWER_CTL_MCLK_DIV2);
+
+ switch (cs42l51->func) {
+ case MODE_MASTER:
+ intf_ctl |= CS42L51_INTF_CTL_MASTER;
+ power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
+ break;
+ case MODE_SLAVE:
+ power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
+ break;
+ case MODE_SLAVE_AUTO:
+ power_ctl |= CS42L51_MIC_POWER_CTL_AUTO;
+ break;
+ }
+
+ switch (cs42l51->audio_mode) {
+ case SND_SOC_DAIFMT_I2S:
+ intf_ctl |= CS42L51_INTF_CTL_ADC_I2S;
+ intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24);
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ case SNDRV_PCM_FORMAT_S16_BE:
+ fmt = CS42L51_DAC_DIF_RJ16;
+ break;
+ case SNDRV_PCM_FORMAT_S18_3LE:
+ case SNDRV_PCM_FORMAT_S18_3BE:
+ fmt = CS42L51_DAC_DIF_RJ18;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ case SNDRV_PCM_FORMAT_S20_3BE:
+ fmt = CS42L51_DAC_DIF_RJ20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S24_BE:
+ fmt = CS42L51_DAC_DIF_RJ24;
+ break;
+ default:
+ dev_err(codec->dev, "unknown format\n");
+ return -EINVAL;
+ }
+ intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt);
+ break;
+ default:
+ dev_err(codec->dev, "unknown format\n");
+ return -EINVAL;
+ }
+
+ if (ratios[i].mclk)
+ power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2;
+
+ ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int reg;
+ int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE;
+
+ reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL);
+
+ if (mute)
+ reg |= mask;
+ else
+ reg &= ~mask;
+
+ return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg);
+}
+
+static struct snd_soc_dai_ops cs42l51_dai_ops = {
+ .hw_params = cs42l51_hw_params,
+ .set_sysclk = cs42l51_set_dai_sysclk,
+ .set_fmt = cs42l51_set_dai_fmt,
+ .digital_mute = cs42l51_dai_mute,
+};
+
+static struct snd_soc_dai_driver cs42l51_dai = {
+ .name = "cs42l51-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = CS42L51_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = CS42L51_FORMATS,
+ },
+ .ops = &cs42l51_dai_ops,
+};
+
+static int cs42l51_probe(struct snd_soc_codec *codec)
+{
+ struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret, reg;
+
+ codec->control_data = cs42l51->control_data;
+
+ ret = cs42l51_fill_cache(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to fill register cache\n");
+ return ret;
+ }
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, cs42l51->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * DAC configuration
+ * - Use signal processor
+ * - auto mute
+ * - vol changes immediate
+ * - no de-emphasize
+ */
+ reg = CS42L51_DAC_CTL_DATA_SEL(1)
+ | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0);
+ ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg);
+ if (ret < 0)
+ return ret;
+
+ snd_soc_add_controls(codec, cs42l51_snd_controls,
+ ARRAY_SIZE(cs42l51_snd_controls));
+ snd_soc_dapm_new_controls(dapm, cs42l51_dapm_widgets,
+ ARRAY_SIZE(cs42l51_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, cs42l51_routes,
+ ARRAY_SIZE(cs42l51_routes));
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_device_cs42l51 = {
+ .probe = cs42l51_probe,
+ .reg_cache_size = CS42L51_NUMREGS,
+ .reg_word_size = sizeof(u8),
+};
+
+static int cs42l51_i2c_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct cs42l51_private *cs42l51;
+ int ret;
+
+ /* Verify that we have a CS42L51 */
+ ret = i2c_smbus_read_byte_data(i2c_client, CS42L51_CHIP_REV_ID);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "failed to read I2C\n");
+ goto error;
+ }
+
+ if ((ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) &&
+ (ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) {
+ dev_err(&i2c_client->dev, "Invalid chip id\n");
+ ret = -ENODEV;
+ goto error;
+ }
+
+ dev_info(&i2c_client->dev, "found device cs42l51 rev %d\n",
+ ret & 7);
+
+ cs42l51 = kzalloc(sizeof(struct cs42l51_private), GFP_KERNEL);
+ if (!cs42l51) {
+ dev_err(&i2c_client->dev, "could not allocate codec\n");
+ return -ENOMEM;
+ }
+
+ i2c_set_clientdata(i2c_client, cs42l51);
+ cs42l51->control_data = i2c_client;
+ cs42l51->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c_client->dev,
+ &soc_codec_device_cs42l51, &cs42l51_dai, 1);
+ if (ret < 0)
+ kfree(cs42l51);
+error:
+ return ret;
+}
+
+static int cs42l51_i2c_remove(struct i2c_client *client)
+{
+ struct cs42l51_private *cs42l51 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_codec(&client->dev);
+ kfree(cs42l51);
+ return 0;
+}
+
+static const struct i2c_device_id cs42l51_id[] = {
+ {"cs42l51", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs42l51_id);
+
+static struct i2c_driver cs42l51_i2c_driver = {
+ .driver = {
+ .name = "cs42l51-codec",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs42l51_id,
+ .probe = cs42l51_i2c_probe,
+ .remove = cs42l51_i2c_remove,
+};
+
+static int __init cs42l51_init(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&cs42l51_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "%s: can't add i2c driver\n", __func__);
+ return ret;
+ }
+ return 0;
+}
+module_init(cs42l51_init);
+
+static void __exit cs42l51_exit(void)
+{
+ i2c_del_driver(&cs42l51_i2c_driver);
+}
+module_exit(cs42l51_exit);
+
+MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
+MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs42l51.h b/sound/soc/codecs/cs42l51.h
new file mode 100644
index 00000000..2beeb171
--- /dev/null
+++ b/sound/soc/codecs/cs42l51.h
@@ -0,0 +1,161 @@
+/*
+ * cs42l51.h
+ *
+ * ASoC Driver for Cirrus Logic CS42L51 codecs
+ *
+ * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+#ifndef _CS42L51_H
+#define _CS42L51_H
+
+#define CS42L51_CHIP_ID 0x1B
+#define CS42L51_CHIP_REV_A 0x00
+#define CS42L51_CHIP_REV_B 0x01
+
+#define CS42L51_CHIP_REV_ID 0x01
+#define CS42L51_MK_CHIP_REV(a, b) ((a)<<3|(b))
+
+#define CS42L51_POWER_CTL1 0x02
+#define CS42L51_POWER_CTL1_PDN_DACB (1<<6)
+#define CS42L51_POWER_CTL1_PDN_DACA (1<<5)
+#define CS42L51_POWER_CTL1_PDN_PGAB (1<<4)
+#define CS42L51_POWER_CTL1_PDN_PGAA (1<<3)
+#define CS42L51_POWER_CTL1_PDN_ADCB (1<<2)
+#define CS42L51_POWER_CTL1_PDN_ADCA (1<<1)
+#define CS42L51_POWER_CTL1_PDN (1<<0)
+
+#define CS42L51_MIC_POWER_CTL 0x03
+#define CS42L51_MIC_POWER_CTL_AUTO (1<<7)
+#define CS42L51_MIC_POWER_CTL_SPEED(x) (((x)&3)<<5)
+#define CS42L51_QSM_MODE 3
+#define CS42L51_HSM_MODE 2
+#define CS42L51_SSM_MODE 1
+#define CS42L51_DSM_MODE 0
+#define CS42L51_MIC_POWER_CTL_3ST_SP (1<<4)
+#define CS42L51_MIC_POWER_CTL_PDN_MICB (1<<3)
+#define CS42L51_MIC_POWER_CTL_PDN_MICA (1<<2)
+#define CS42L51_MIC_POWER_CTL_PDN_BIAS (1<<1)
+#define CS42L51_MIC_POWER_CTL_MCLK_DIV2 (1<<0)
+
+#define CS42L51_INTF_CTL 0x04
+#define CS42L51_INTF_CTL_LOOPBACK (1<<7)
+#define CS42L51_INTF_CTL_MASTER (1<<6)
+#define CS42L51_INTF_CTL_DAC_FORMAT(x) (((x)&7)<<3)
+#define CS42L51_DAC_DIF_LJ24 0x00
+#define CS42L51_DAC_DIF_I2S 0x01
+#define CS42L51_DAC_DIF_RJ24 0x02
+#define CS42L51_DAC_DIF_RJ20 0x03
+#define CS42L51_DAC_DIF_RJ18 0x04
+#define CS42L51_DAC_DIF_RJ16 0x05
+#define CS42L51_INTF_CTL_ADC_I2S (1<<2)
+#define CS42L51_INTF_CTL_DIGMIX (1<<1)
+#define CS42L51_INTF_CTL_MICMIX (1<<0)
+
+#define CS42L51_MIC_CTL 0x05
+#define CS42L51_MIC_CTL_ADC_SNGVOL (1<<7)
+#define CS42L51_MIC_CTL_ADCD_DBOOST (1<<6)
+#define CS42L51_MIC_CTL_ADCA_DBOOST (1<<5)
+#define CS42L51_MIC_CTL_MICBIAS_SEL (1<<4)
+#define CS42L51_MIC_CTL_MICBIAS_LVL(x) (((x)&3)<<2)
+#define CS42L51_MIC_CTL_MICB_BOOST (1<<1)
+#define CS42L51_MIC_CTL_MICA_BOOST (1<<0)
+
+#define CS42L51_ADC_CTL 0x06
+#define CS42L51_ADC_CTL_ADCB_HPFEN (1<<7)
+#define CS42L51_ADC_CTL_ADCB_HPFRZ (1<<6)
+#define CS42L51_ADC_CTL_ADCA_HPFEN (1<<5)
+#define CS42L51_ADC_CTL_ADCA_HPFRZ (1<<4)
+#define CS42L51_ADC_CTL_SOFTB (1<<3)
+#define CS42L51_ADC_CTL_ZCROSSB (1<<2)
+#define CS42L51_ADC_CTL_SOFTA (1<<1)
+#define CS42L51_ADC_CTL_ZCROSSA (1<<0)
+
+#define CS42L51_ADC_INPUT 0x07
+#define CS42L51_ADC_INPUT_AINB_MUX(x) (((x)&3)<<6)
+#define CS42L51_ADC_INPUT_AINA_MUX(x) (((x)&3)<<4)
+#define CS42L51_ADC_INPUT_INV_ADCB (1<<3)
+#define CS42L51_ADC_INPUT_INV_ADCA (1<<2)
+#define CS42L51_ADC_INPUT_ADCB_MUTE (1<<1)
+#define CS42L51_ADC_INPUT_ADCA_MUTE (1<<0)
+
+#define CS42L51_DAC_OUT_CTL 0x08
+#define CS42L51_DAC_OUT_CTL_HP_GAIN(x) (((x)&7)<<5)
+#define CS42L51_DAC_OUT_CTL_DAC_SNGVOL (1<<4)
+#define CS42L51_DAC_OUT_CTL_INV_PCMB (1<<3)
+#define CS42L51_DAC_OUT_CTL_INV_PCMA (1<<2)
+#define CS42L51_DAC_OUT_CTL_DACB_MUTE (1<<1)
+#define CS42L51_DAC_OUT_CTL_DACA_MUTE (1<<0)
+
+#define CS42L51_DAC_CTL 0x09
+#define CS42L51_DAC_CTL_DATA_SEL(x) (((x)&3)<<6)
+#define CS42L51_DAC_CTL_FREEZE (1<<5)
+#define CS42L51_DAC_CTL_DEEMPH (1<<3)
+#define CS42L51_DAC_CTL_AMUTE (1<<2)
+#define CS42L51_DAC_CTL_DACSZ(x) (((x)&3)<<0)
+
+#define CS42L51_ALC_PGA_CTL 0x0A
+#define CS42L51_ALC_PGB_CTL 0x0B
+#define CS42L51_ALC_PGX_ALCX_SRDIS (1<<7)
+#define CS42L51_ALC_PGX_ALCX_ZCDIS (1<<6)
+#define CS42L51_ALC_PGX_PGX_VOL(x) (((x)&0x1f)<<0)
+
+#define CS42L51_ADCA_ATT 0x0C
+#define CS42L51_ADCB_ATT 0x0D
+
+#define CS42L51_ADCA_VOL 0x0E
+#define CS42L51_ADCB_VOL 0x0F
+#define CS42L51_PCMA_VOL 0x10
+#define CS42L51_PCMB_VOL 0x11
+#define CS42L51_MIX_MUTE_ADCMIX (1<<7)
+#define CS42L51_MIX_VOLUME(x) (((x)&0x7f)<<0)
+
+#define CS42L51_BEEP_FREQ 0x12
+#define CS42L51_BEEP_VOL 0x13
+#define CS42L51_BEEP_CONF 0x14
+
+#define CS42L51_TONE_CTL 0x15
+#define CS42L51_TONE_CTL_TREB(x) (((x)&0xf)<<4)
+#define CS42L51_TONE_CTL_BASS(x) (((x)&0xf)<<0)
+
+#define CS42L51_AOUTA_VOL 0x16
+#define CS42L51_AOUTB_VOL 0x17
+#define CS42L51_PCM_MIXER 0x18
+#define CS42L51_LIMIT_THRES_DIS 0x19
+#define CS42L51_LIMIT_REL 0x1A
+#define CS42L51_LIMIT_ATT 0x1B
+#define CS42L51_ALC_EN 0x1C
+#define CS42L51_ALC_REL 0x1D
+#define CS42L51_ALC_THRES 0x1E
+#define CS42L51_NOISE_CONF 0x1F
+
+#define CS42L51_STATUS 0x20
+#define CS42L51_STATUS_SP_CLKERR (1<<6)
+#define CS42L51_STATUS_SPEA_OVFL (1<<5)
+#define CS42L51_STATUS_SPEB_OVFL (1<<4)
+#define CS42L51_STATUS_PCMA_OVFL (1<<3)
+#define CS42L51_STATUS_PCMB_OVFL (1<<2)
+#define CS42L51_STATUS_ADCA_OVFL (1<<1)
+#define CS42L51_STATUS_ADCB_OVFL (1<<0)
+
+#define CS42L51_CHARGE_FREQ 0x21
+
+#define CS42L51_FIRSTREG 0x01
+/*
+ * Hack: with register 0x21, it makes 33 registers. Looks like someone in the
+ * i2c layer doesn't like i2c smbus block read of 33 regs. Workaround by using
+ * 32 regs
+ */
+#define CS42L51_LASTREG 0x20
+#define CS42L51_NUMREGS (CS42L51_LASTREG - CS42L51_FIRSTREG + 1)
+
+#endif
diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c
new file mode 100644
index 00000000..d68ea532
--- /dev/null
+++ b/sound/soc/codecs/cx20442.c
@@ -0,0 +1,408 @@
+/*
+ * cx20442.c -- CX20442 ALSA Soc Audio driver
+ *
+ * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
+ *
+ * Initially based on sound/soc/codecs/wm8400.c
+ * Copyright 2008, 2009 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/tty.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "cx20442.h"
+
+
+struct cx20442_priv {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+};
+
+#define CX20442_PM 0x0
+
+#define CX20442_TELIN 0
+#define CX20442_TELOUT 1
+#define CX20442_MIC 2
+#define CX20442_SPKOUT 3
+#define CX20442_AGC 4
+
+static const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = {
+ SND_SOC_DAPM_OUTPUT("TELOUT"),
+ SND_SOC_DAPM_OUTPUT("SPKOUT"),
+ SND_SOC_DAPM_OUTPUT("AGCOUT"),
+
+ SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
+
+ SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0),
+ SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0),
+
+ SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
+
+ SND_SOC_DAPM_INPUT("TELIN"),
+ SND_SOC_DAPM_INPUT("MIC"),
+ SND_SOC_DAPM_INPUT("AGCIN"),
+};
+
+static const struct snd_soc_dapm_route cx20442_audio_map[] = {
+ {"TELOUT", NULL, "TELOUT Amp"},
+
+ {"SPKOUT", NULL, "SPKOUT Mixer"},
+ {"SPKOUT Mixer", NULL, "SPKOUT Amp"},
+
+ {"TELOUT Amp", NULL, "DAC"},
+ {"SPKOUT Amp", NULL, "DAC"},
+
+ {"SPKOUT Mixer", NULL, "SPKOUT AGC"},
+ {"SPKOUT AGC", NULL, "AGCIN"},
+
+ {"AGCOUT", NULL, "MIC AGC"},
+ {"MIC AGC", NULL, "MIC"},
+
+ {"MIC Bias", NULL, "MIC"},
+ {"Input Mixer", NULL, "MIC Bias"},
+
+ {"TELIN Bias", NULL, "TELIN"},
+ {"Input Mixer", NULL, "TELIN Bias"},
+
+ {"ADC", NULL, "Input Mixer"},
+};
+
+static unsigned int cx20442_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *reg_cache = codec->reg_cache;
+
+ if (reg >= codec->driver->reg_cache_size)
+ return -EINVAL;
+
+ return reg_cache[reg];
+}
+
+enum v253_vls {
+ V253_VLS_NONE = 0,
+ V253_VLS_T,
+ V253_VLS_L,
+ V253_VLS_LT,
+ V253_VLS_S,
+ V253_VLS_ST,
+ V253_VLS_M,
+ V253_VLS_MST,
+ V253_VLS_S1,
+ V253_VLS_S1T,
+ V253_VLS_MS1T,
+ V253_VLS_M1,
+ V253_VLS_M1ST,
+ V253_VLS_M1S1T,
+ V253_VLS_H,
+ V253_VLS_HT,
+ V253_VLS_MS,
+ V253_VLS_MS1,
+ V253_VLS_M1S,
+ V253_VLS_M1S1,
+ V253_VLS_TEST,
+};
+
+static int cx20442_pm_to_v253_vls(u8 value)
+{
+ switch (value & ~(1 << CX20442_AGC)) {
+ case 0:
+ return V253_VLS_T;
+ case (1 << CX20442_SPKOUT):
+ case (1 << CX20442_MIC):
+ case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
+ return V253_VLS_M1S1;
+ case (1 << CX20442_TELOUT):
+ case (1 << CX20442_TELIN):
+ case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN):
+ return V253_VLS_L;
+ case (1 << CX20442_TELOUT) | (1 << CX20442_MIC):
+ return V253_VLS_NONE;
+ }
+ return -EINVAL;
+}
+static int cx20442_pm_to_v253_vsp(u8 value)
+{
+ switch (value & ~(1 << CX20442_AGC)) {
+ case (1 << CX20442_SPKOUT):
+ case (1 << CX20442_MIC):
+ case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
+ return (bool)(value & (1 << CX20442_AGC));
+ }
+ return (value & (1 << CX20442_AGC)) ? -EINVAL : 0;
+}
+
+static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct cx20442_priv *cx20442 = snd_soc_codec_get_drvdata(codec);
+ u8 *reg_cache = codec->reg_cache;
+ int vls, vsp, old, len;
+ char buf[18];
+
+ if (reg >= codec->driver->reg_cache_size)
+ return -EINVAL;
+
+ /* hw_write and control_data pointers required for talking to the modem
+ * are expected to be set by the line discipline initialization code */
+ if (!codec->hw_write || !cx20442->control_data)
+ return -EIO;
+
+ old = reg_cache[reg];
+ reg_cache[reg] = value;
+
+ vls = cx20442_pm_to_v253_vls(value);
+ if (vls < 0)
+ return vls;
+
+ vsp = cx20442_pm_to_v253_vsp(value);
+ if (vsp < 0)
+ return vsp;
+
+ if ((vls == V253_VLS_T) ||
+ (vls == cx20442_pm_to_v253_vls(old))) {
+ if (vsp == cx20442_pm_to_v253_vsp(old))
+ return 0;
+ len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp);
+ } else if (vsp == cx20442_pm_to_v253_vsp(old))
+ len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls);
+ else
+ len = snprintf(buf, ARRAY_SIZE(buf),
+ "at+vls=%d;+vsp=%d\r", vls, vsp);
+
+ if (unlikely(len > (ARRAY_SIZE(buf) - 1)))
+ return -ENOMEM;
+
+ dev_dbg(codec->dev, "%s: %s\n", __func__, buf);
+ if (codec->hw_write(cx20442->control_data, buf, len) != len)
+ return -EIO;
+
+ return 0;
+}
+
+
+/*
+ * Line discpline related code
+ *
+ * Any of the callback functions below can be used in two ways:
+ * 1) registerd by a machine driver as one of line discipline operations,
+ * 2) called from a machine's provided line discipline callback function
+ * in case when extra machine specific code must be run as well.
+ */
+
+/* Modem init: echo off, digital speaker off, quiet off, voice mode */
+static const char *v253_init = "ate0m0q0+fclass=8\r";
+
+/* Line discipline .open() */
+static int v253_open(struct tty_struct *tty)
+{
+ int ret, len = strlen(v253_init);
+
+ /* Doesn't make sense without write callback */
+ if (!tty->ops->write)
+ return -EINVAL;
+
+ /* Won't work if no codec pointer has been passed by a card driver */
+ if (!tty->disc_data)
+ return -ENODEV;
+
+ if (tty->ops->write(tty, v253_init, len) != len) {
+ ret = -EIO;
+ goto err;
+ }
+ /* Actual setup will be performed after the modem responds. */
+ return 0;
+err:
+ tty->disc_data = NULL;
+ return ret;
+}
+
+/* Line discipline .close() */
+static void v253_close(struct tty_struct *tty)
+{
+ struct snd_soc_codec *codec = tty->disc_data;
+ struct cx20442_priv *cx20442;
+
+ tty->disc_data = NULL;
+
+ if (!codec)
+ return;
+
+ cx20442 = snd_soc_codec_get_drvdata(codec);
+
+ /* Prevent the codec driver from further accessing the modem */
+ codec->hw_write = NULL;
+ cx20442->control_data = NULL;
+ codec->card->pop_time = 0;
+}
+
+/* Line discipline .hangup() */
+static int v253_hangup(struct tty_struct *tty)
+{
+ v253_close(tty);
+ return 0;
+}
+
+/* Line discipline .receive_buf() */
+static void v253_receive(struct tty_struct *tty,
+ const unsigned char *cp, char *fp, int count)
+{
+ struct snd_soc_codec *codec = tty->disc_data;
+ struct cx20442_priv *cx20442;
+
+ if (!codec)
+ return;
+
+ cx20442 = snd_soc_codec_get_drvdata(codec);
+
+ if (!cx20442->control_data) {
+ /* First modem response, complete setup procedure */
+
+ /* Set up codec driver access to modem controls */
+ cx20442->control_data = tty;
+ codec->hw_write = (hw_write_t)tty->ops->write;
+ codec->card->pop_time = 1;
+ }
+}
+
+/* Line discipline .write_wakeup() */
+static void v253_wakeup(struct tty_struct *tty)
+{
+}
+
+struct tty_ldisc_ops v253_ops = {
+ .magic = TTY_LDISC_MAGIC,
+ .name = "cx20442",
+ .owner = THIS_MODULE,
+ .open = v253_open,
+ .close = v253_close,
+ .hangup = v253_hangup,
+ .receive_buf = v253_receive,
+ .write_wakeup = v253_wakeup,
+};
+EXPORT_SYMBOL_GPL(v253_ops);
+
+
+/*
+ * Codec DAI
+ */
+
+static struct snd_soc_dai_driver cx20442_dai = {
+ .name = "cx20442-voice",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+};
+
+static int cx20442_codec_probe(struct snd_soc_codec *codec)
+{
+ struct cx20442_priv *cx20442;
+
+ cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL);
+ if (cx20442 == NULL)
+ return -ENOMEM;
+ snd_soc_codec_set_drvdata(codec, cx20442);
+
+ cx20442->control_data = NULL;
+ codec->hw_write = NULL;
+ codec->card->pop_time = 0;
+
+ return 0;
+}
+
+/* power down chip */
+static int cx20442_codec_remove(struct snd_soc_codec *codec)
+{
+ struct cx20442_priv *cx20442 = snd_soc_codec_get_drvdata(codec);
+
+ if (cx20442->control_data) {
+ struct tty_struct *tty = cx20442->control_data;
+ tty_hangup(tty);
+ }
+
+ kfree(cx20442);
+ return 0;
+}
+
+static const u8 cx20442_reg;
+
+static struct snd_soc_codec_driver cx20442_codec_dev = {
+ .probe = cx20442_codec_probe,
+ .remove = cx20442_codec_remove,
+ .reg_cache_default = &cx20442_reg,
+ .reg_cache_size = 1,
+ .reg_word_size = sizeof(u8),
+ .read = cx20442_read_reg_cache,
+ .write = cx20442_write,
+ .dapm_widgets = cx20442_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cx20442_dapm_widgets),
+ .dapm_routes = cx20442_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(cx20442_audio_map),
+};
+
+static int cx20442_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &cx20442_codec_dev, &cx20442_dai, 1);
+}
+
+static int __exit cx20442_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver cx20442_platform_driver = {
+ .driver = {
+ .name = "cx20442-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = cx20442_platform_probe,
+ .remove = __exit_p(cx20442_platform_remove),
+};
+
+static int __init cx20442_init(void)
+{
+ return platform_driver_register(&cx20442_platform_driver);
+}
+module_init(cx20442_init);
+
+static void __exit cx20442_exit(void)
+{
+ platform_driver_unregister(&cx20442_platform_driver);
+}
+module_exit(cx20442_exit);
+
+MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver");
+MODULE_AUTHOR("Janusz Krzysztofik");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cx20442-codec");
diff --git a/sound/soc/codecs/cx20442.h b/sound/soc/codecs/cx20442.h
new file mode 100644
index 00000000..c7a7c79e
--- /dev/null
+++ b/sound/soc/codecs/cx20442.h
@@ -0,0 +1,18 @@
+/*
+ * cx20442.h -- audio driver for CX20442
+ *
+ * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _CX20442_CODEC_H
+#define _CX20442_CODEC_H
+
+extern struct tty_ldisc_ops v253_ops;
+
+#endif
diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c
new file mode 100644
index 00000000..92fd9d7a
--- /dev/null
+++ b/sound/soc/codecs/da7210.c
@@ -0,0 +1,626 @@
+/*
+ * DA7210 ALSA Soc codec driver
+ *
+ * Copyright (c) 2009 Dialog Semiconductor
+ * Written by David Chen <Dajun.chen@diasemi.com>
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Cleanups by Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * Tested on SuperH Ecovec24 board with S16/S24 LE in 48KHz using I2S
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+/* DA7210 register space */
+#define DA7210_STATUS 0x02
+#define DA7210_STARTUP1 0x03
+#define DA7210_MIC_L 0x07
+#define DA7210_MIC_R 0x08
+#define DA7210_INMIX_L 0x0D
+#define DA7210_INMIX_R 0x0E
+#define DA7210_ADC_HPF 0x0F
+#define DA7210_ADC 0x10
+#define DA7210_DAC_HPF 0x14
+#define DA7210_DAC_L 0x15
+#define DA7210_DAC_R 0x16
+#define DA7210_DAC_SEL 0x17
+#define DA7210_OUTMIX_L 0x1C
+#define DA7210_OUTMIX_R 0x1D
+#define DA7210_HP_L_VOL 0x21
+#define DA7210_HP_R_VOL 0x22
+#define DA7210_HP_CFG 0x23
+#define DA7210_DAI_SRC_SEL 0x25
+#define DA7210_DAI_CFG1 0x26
+#define DA7210_DAI_CFG3 0x28
+#define DA7210_PLL_DIV1 0x29
+#define DA7210_PLL_DIV2 0x2A
+#define DA7210_PLL_DIV3 0x2B
+#define DA7210_PLL 0x2C
+#define DA7210_A_HID_UNLOCK 0x8A
+#define DA7210_A_TEST_UNLOCK 0x8B
+#define DA7210_A_PLL1 0x90
+#define DA7210_A_CP_MODE 0xA7
+
+/* STARTUP1 bit fields */
+#define DA7210_SC_MST_EN (1 << 0)
+
+/* MIC_L bit fields */
+#define DA7210_MICBIAS_EN (1 << 6)
+#define DA7210_MIC_L_EN (1 << 7)
+
+/* MIC_R bit fields */
+#define DA7210_MIC_R_EN (1 << 7)
+
+/* INMIX_L bit fields */
+#define DA7210_IN_L_EN (1 << 7)
+
+/* INMIX_R bit fields */
+#define DA7210_IN_R_EN (1 << 7)
+
+/* ADC bit fields */
+#define DA7210_ADC_L_EN (1 << 3)
+#define DA7210_ADC_R_EN (1 << 7)
+
+/* DAC/ADC HPF fields */
+#define DA7210_VOICE_F0_MASK (0x7 << 4)
+#define DA7210_VOICE_F0_25 (1 << 4)
+#define DA7210_VOICE_EN (1 << 7)
+
+/* DAC_SEL bit fields */
+#define DA7210_DAC_L_SRC_DAI_L (4 << 0)
+#define DA7210_DAC_L_EN (1 << 3)
+#define DA7210_DAC_R_SRC_DAI_R (5 << 4)
+#define DA7210_DAC_R_EN (1 << 7)
+
+/* OUTMIX_L bit fields */
+#define DA7210_OUT_L_EN (1 << 7)
+
+/* OUTMIX_R bit fields */
+#define DA7210_OUT_R_EN (1 << 7)
+
+/* HP_CFG bit fields */
+#define DA7210_HP_2CAP_MODE (1 << 1)
+#define DA7210_HP_SENSE_EN (1 << 2)
+#define DA7210_HP_L_EN (1 << 3)
+#define DA7210_HP_MODE (1 << 6)
+#define DA7210_HP_R_EN (1 << 7)
+
+/* DAI_SRC_SEL bit fields */
+#define DA7210_DAI_OUT_L_SRC (6 << 0)
+#define DA7210_DAI_OUT_R_SRC (7 << 4)
+
+/* DAI_CFG1 bit fields */
+#define DA7210_DAI_WORD_S16_LE (0 << 0)
+#define DA7210_DAI_WORD_S24_LE (2 << 0)
+#define DA7210_DAI_FLEN_64BIT (1 << 2)
+#define DA7210_DAI_MODE_MASTER (1 << 7)
+
+/* DAI_CFG3 bit fields */
+#define DA7210_DAI_FORMAT_I2SMODE (0 << 0)
+#define DA7210_DAI_OE (1 << 3)
+#define DA7210_DAI_EN (1 << 7)
+
+/*PLL_DIV3 bit fields */
+#define DA7210_MCLK_RANGE_10_20_MHZ (1 << 4)
+#define DA7210_PLL_BYP (1 << 6)
+
+/* PLL bit fields */
+#define DA7210_PLL_FS_MASK (0xF << 0)
+#define DA7210_PLL_FS_8000 (0x1 << 0)
+#define DA7210_PLL_FS_11025 (0x2 << 0)
+#define DA7210_PLL_FS_12000 (0x3 << 0)
+#define DA7210_PLL_FS_16000 (0x5 << 0)
+#define DA7210_PLL_FS_22050 (0x6 << 0)
+#define DA7210_PLL_FS_24000 (0x7 << 0)
+#define DA7210_PLL_FS_32000 (0x9 << 0)
+#define DA7210_PLL_FS_44100 (0xA << 0)
+#define DA7210_PLL_FS_48000 (0xB << 0)
+#define DA7210_PLL_FS_88200 (0xE << 0)
+#define DA7210_PLL_FS_96000 (0xF << 0)
+#define DA7210_PLL_EN (0x1 << 7)
+
+#define DA7210_VERSION "0.0.1"
+
+/*
+ * Playback Volume
+ *
+ * max : 0x3F (+15.0 dB)
+ * (1.5 dB step)
+ * min : 0x11 (-54.0 dB)
+ * mute : 0x10
+ * reserved : 0x00 - 0x0F
+ *
+ * ** FIXME **
+ *
+ * Reserved area are considered as "mute".
+ * -> min = -79.5 dB
+ */
+static const DECLARE_TLV_DB_SCALE(hp_out_tlv, -7950, 150, 1);
+
+static const struct snd_kcontrol_new da7210_snd_controls[] = {
+
+ SOC_DOUBLE_R_TLV("HeadPhone Playback Volume",
+ DA7210_HP_L_VOL, DA7210_HP_R_VOL,
+ 0, 0x3F, 0, hp_out_tlv),
+};
+
+/* Codec private data */
+struct da7210_priv {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+};
+
+/*
+ * Register cache
+ */
+static const u8 da7210_reg[] = {
+ 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R0 - R7 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, /* R8 - RF */
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x10, 0x54, /* R10 - R17 */
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R18 - R1F */
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00, /* R20 - R27 */
+ 0x04, 0x00, 0x00, 0x30, 0x2A, 0x00, 0x40, 0x00, /* R28 - R2F */
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, /* R30 - R37 */
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, /* R38 - R3F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R40 - R4F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R48 - R4F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R50 - R57 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R58 - R5F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R60 - R67 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R68 - R6F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R70 - R77 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x54, 0x00, /* R78 - R7F */
+ 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, /* R80 - R87 */
+ 0x00, /* R88 */
+};
+
+/*
+ * Read da7210 register cache
+ */
+static inline u32 da7210_read_reg_cache(struct snd_soc_codec *codec, u32 reg)
+{
+ u8 *cache = codec->reg_cache;
+ BUG_ON(reg >= ARRAY_SIZE(da7210_reg));
+ return cache[reg];
+}
+
+/*
+ * Write to the da7210 register space
+ */
+static int da7210_write(struct snd_soc_codec *codec, u32 reg, u32 value)
+{
+ u8 *cache = codec->reg_cache;
+ u8 data[2];
+
+ BUG_ON(codec->driver->volatile_register);
+
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ if (reg >= codec->driver->reg_cache_size)
+ return -EIO;
+
+ if (2 != codec->hw_write(codec->control_data, data, 2))
+ return -EIO;
+
+ cache[reg] = value;
+ return 0;
+}
+
+/*
+ * Read from the da7210 register space.
+ */
+static inline u32 da7210_read(struct snd_soc_codec *codec, u32 reg)
+{
+ if (DA7210_STATUS == reg)
+ return i2c_smbus_read_byte_data(codec->control_data, reg);
+
+ return da7210_read_reg_cache(codec, reg);
+}
+
+static int da7210_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_soc_codec *codec = dai->codec;
+
+ if (is_play) {
+ /* Enable Out */
+ snd_soc_update_bits(codec, DA7210_OUTMIX_L, 0x1F, 0x10);
+ snd_soc_update_bits(codec, DA7210_OUTMIX_R, 0x1F, 0x10);
+
+ } else {
+ /* Volume 7 */
+ snd_soc_update_bits(codec, DA7210_MIC_L, 0x7, 0x7);
+ snd_soc_update_bits(codec, DA7210_MIC_R, 0x7, 0x7);
+
+ /* Enable Mic */
+ snd_soc_update_bits(codec, DA7210_INMIX_L, 0x1F, 0x1);
+ snd_soc_update_bits(codec, DA7210_INMIX_R, 0x1F, 0x1);
+ }
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI word length.
+ */
+static int da7210_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u32 dai_cfg1;
+ u32 hpf_reg, hpf_mask, hpf_value;
+ u32 fs, bypass;
+
+ /* set DAI source to Left and Right ADC */
+ da7210_write(codec, DA7210_DAI_SRC_SEL,
+ DA7210_DAI_OUT_R_SRC | DA7210_DAI_OUT_L_SRC);
+
+ /* Enable DAI */
+ da7210_write(codec, DA7210_DAI_CFG3, DA7210_DAI_OE | DA7210_DAI_EN);
+
+ dai_cfg1 = 0xFC & da7210_read(codec, DA7210_DAI_CFG1);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dai_cfg1 |= DA7210_DAI_WORD_S16_LE;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ dai_cfg1 |= DA7210_DAI_WORD_S24_LE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1);
+
+ hpf_reg = (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) ?
+ DA7210_DAC_HPF : DA7210_ADC_HPF;
+
+ switch (params_rate(params)) {
+ case 8000:
+ fs = DA7210_PLL_FS_8000;
+ hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN;
+ hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN;
+ bypass = DA7210_PLL_BYP;
+ break;
+ case 11025:
+ fs = DA7210_PLL_FS_11025;
+ hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN;
+ hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN;
+ bypass = 0;
+ break;
+ case 12000:
+ fs = DA7210_PLL_FS_12000;
+ hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN;
+ hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN;
+ bypass = DA7210_PLL_BYP;
+ break;
+ case 16000:
+ fs = DA7210_PLL_FS_16000;
+ hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN;
+ hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN;
+ bypass = DA7210_PLL_BYP;
+ break;
+ case 22050:
+ fs = DA7210_PLL_FS_22050;
+ hpf_mask = DA7210_VOICE_EN;
+ hpf_value = 0;
+ bypass = 0;
+ break;
+ case 32000:
+ fs = DA7210_PLL_FS_32000;
+ hpf_mask = DA7210_VOICE_EN;
+ hpf_value = 0;
+ bypass = DA7210_PLL_BYP;
+ break;
+ case 44100:
+ fs = DA7210_PLL_FS_44100;
+ hpf_mask = DA7210_VOICE_EN;
+ hpf_value = 0;
+ bypass = 0;
+ break;
+ case 48000:
+ fs = DA7210_PLL_FS_48000;
+ hpf_mask = DA7210_VOICE_EN;
+ hpf_value = 0;
+ bypass = DA7210_PLL_BYP;
+ break;
+ case 88200:
+ fs = DA7210_PLL_FS_88200;
+ hpf_mask = DA7210_VOICE_EN;
+ hpf_value = 0;
+ bypass = 0;
+ break;
+ case 96000:
+ fs = DA7210_PLL_FS_96000;
+ hpf_mask = DA7210_VOICE_EN;
+ hpf_value = 0;
+ bypass = DA7210_PLL_BYP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Disable active mode */
+ snd_soc_update_bits(codec, DA7210_STARTUP1, DA7210_SC_MST_EN, 0);
+
+ snd_soc_update_bits(codec, hpf_reg, hpf_mask, hpf_value);
+ snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_FS_MASK, fs);
+ snd_soc_update_bits(codec, DA7210_PLL_DIV3, DA7210_PLL_BYP, bypass);
+
+ /* Enable active mode */
+ snd_soc_update_bits(codec, DA7210_STARTUP1,
+ DA7210_SC_MST_EN, DA7210_SC_MST_EN);
+
+ return 0;
+}
+
+/*
+ * Set DAI mode and Format
+ */
+static int da7210_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u32 dai_cfg1;
+ u32 dai_cfg3;
+
+ dai_cfg1 = 0x7f & da7210_read(codec, DA7210_DAI_CFG1);
+ dai_cfg3 = 0xfc & da7210_read(codec, DA7210_DAI_CFG3);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ dai_cfg1 |= DA7210_DAI_MODE_MASTER;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* FIXME
+ *
+ * It support I2S only now
+ */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ dai_cfg3 |= DA7210_DAI_FORMAT_I2SMODE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* FIXME
+ *
+ * It support 64bit data transmission only now
+ */
+ dai_cfg1 |= DA7210_DAI_FLEN_64BIT;
+
+ da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1);
+ da7210_write(codec, DA7210_DAI_CFG3, dai_cfg3);
+
+ return 0;
+}
+
+#define DA7210_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+/* DAI operations */
+static struct snd_soc_dai_ops da7210_dai_ops = {
+ .startup = da7210_startup,
+ .hw_params = da7210_hw_params,
+ .set_fmt = da7210_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver da7210_dai = {
+ .name = "da7210-hifi",
+ /* playback capabilities */
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = DA7210_FORMATS,
+ },
+ /* capture capabilities */
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = DA7210_FORMATS,
+ },
+ .ops = &da7210_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int da7210_probe(struct snd_soc_codec *codec)
+{
+ struct da7210_priv *da7210 = snd_soc_codec_get_drvdata(codec);
+
+ dev_info(codec->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION);
+
+ codec->control_data = da7210->control_data;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+
+ /* FIXME
+ *
+ * This driver use fixed value here
+ * And below settings expects MCLK = 12.288MHz
+ *
+ * When you select different MCLK, please check...
+ * DA7210_PLL_DIV1 val
+ * DA7210_PLL_DIV2 val
+ * DA7210_PLL_DIV3 val
+ * DA7210_PLL_DIV3 :: DA7210_MCLK_RANGExxx
+ */
+
+ /*
+ * make sure that DA7210 use bypass mode before start up
+ */
+ da7210_write(codec, DA7210_STARTUP1, 0);
+ da7210_write(codec, DA7210_PLL_DIV3,
+ DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP);
+
+ /*
+ * ADC settings
+ */
+
+ /* Enable Left & Right MIC PGA and Mic Bias */
+ da7210_write(codec, DA7210_MIC_L, DA7210_MIC_L_EN | DA7210_MICBIAS_EN);
+ da7210_write(codec, DA7210_MIC_R, DA7210_MIC_R_EN);
+
+ /* Enable Left and Right input PGA */
+ da7210_write(codec, DA7210_INMIX_L, DA7210_IN_L_EN);
+ da7210_write(codec, DA7210_INMIX_R, DA7210_IN_R_EN);
+
+ /* Enable Left and Right ADC */
+ da7210_write(codec, DA7210_ADC, DA7210_ADC_L_EN | DA7210_ADC_R_EN);
+
+ /*
+ * DAC settings
+ */
+
+ /* Enable Left and Right DAC */
+ da7210_write(codec, DA7210_DAC_SEL,
+ DA7210_DAC_L_SRC_DAI_L | DA7210_DAC_L_EN |
+ DA7210_DAC_R_SRC_DAI_R | DA7210_DAC_R_EN);
+
+ /* Enable Left and Right out PGA */
+ da7210_write(codec, DA7210_OUTMIX_L, DA7210_OUT_L_EN);
+ da7210_write(codec, DA7210_OUTMIX_R, DA7210_OUT_R_EN);
+
+ /* Enable Left and Right HeadPhone PGA */
+ da7210_write(codec, DA7210_HP_CFG,
+ DA7210_HP_2CAP_MODE | DA7210_HP_SENSE_EN |
+ DA7210_HP_L_EN | DA7210_HP_MODE | DA7210_HP_R_EN);
+
+ /* Diable PLL and bypass it */
+ da7210_write(codec, DA7210_PLL, DA7210_PLL_FS_48000);
+
+ /*
+ * If 48kHz sound came, it use bypass mode,
+ * and when it is 44.1kHz, it use PLL.
+ *
+ * This time, this driver sets PLL always ON
+ * and controls bypass/PLL mode by switching
+ * DA7210_PLL_DIV3 :: DA7210_PLL_BYP bit.
+ * see da7210_hw_params
+ */
+ da7210_write(codec, DA7210_PLL_DIV1, 0xE5); /* MCLK = 12.288MHz */
+ da7210_write(codec, DA7210_PLL_DIV2, 0x99);
+ da7210_write(codec, DA7210_PLL_DIV3, 0x0A |
+ DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP);
+ snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_EN, DA7210_PLL_EN);
+
+ /* As suggested by Dialog */
+ da7210_write(codec, DA7210_A_HID_UNLOCK, 0x8B); /* unlock */
+ da7210_write(codec, DA7210_A_TEST_UNLOCK, 0xB4);
+ da7210_write(codec, DA7210_A_PLL1, 0x01);
+ da7210_write(codec, DA7210_A_CP_MODE, 0x7C);
+ da7210_write(codec, DA7210_A_HID_UNLOCK, 0x00); /* re-lock */
+ da7210_write(codec, DA7210_A_TEST_UNLOCK, 0x00);
+
+ /* Activate all enabled subsystem */
+ da7210_write(codec, DA7210_STARTUP1, DA7210_SC_MST_EN);
+
+ snd_soc_add_controls(codec, da7210_snd_controls,
+ ARRAY_SIZE(da7210_snd_controls));
+
+ dev_info(codec->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_da7210 = {
+ .probe = da7210_probe,
+ .read = da7210_read,
+ .write = da7210_write,
+ .reg_cache_size = ARRAY_SIZE(da7210_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = da7210_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int __devinit da7210_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct da7210_priv *da7210;
+ int ret;
+
+ da7210 = kzalloc(sizeof(struct da7210_priv), GFP_KERNEL);
+ if (!da7210)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, da7210);
+ da7210->control_data = i2c;
+ da7210->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_da7210, &da7210_dai, 1);
+ if (ret < 0)
+ kfree(da7210);
+
+ return ret;
+}
+
+static int __devexit da7210_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id da7210_i2c_id[] = {
+ { "da7210", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, da7210_i2c_id);
+
+/* I2C codec control layer */
+static struct i2c_driver da7210_i2c_driver = {
+ .driver = {
+ .name = "da7210-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = da7210_i2c_probe,
+ .remove = __devexit_p(da7210_i2c_remove),
+ .id_table = da7210_i2c_id,
+};
+#endif
+
+static int __init da7210_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&da7210_i2c_driver);
+#endif
+ return ret;
+}
+module_init(da7210_modinit);
+
+static void __exit da7210_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&da7210_i2c_driver);
+#endif
+}
+module_exit(da7210_exit);
+
+MODULE_DESCRIPTION("ASoC DA7210 driver");
+MODULE_AUTHOR("David Chen, Kuninori Morimoto");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/dfbmcs320.c b/sound/soc/codecs/dfbmcs320.c
new file mode 100644
index 00000000..704bbde6
--- /dev/null
+++ b/sound/soc/codecs/dfbmcs320.c
@@ -0,0 +1,72 @@
+/*
+ * Driver for the DFBM-CS320 bluetooth module
+ * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/soc.h>
+
+static struct snd_soc_dai_driver dfbmcs320_dai = {
+ .name = "dfbmcs320-pcm",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_dfbmcs320;
+
+static int __devinit dfbmcs320_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_dfbmcs320,
+ &dfbmcs320_dai, 1);
+}
+
+static int __devexit dfbmcs320_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver dfmcs320_driver = {
+ .driver = {
+ .name = "dfbmcs320",
+ .owner = THIS_MODULE,
+ },
+ .probe = dfbmcs320_probe,
+ .remove = __devexit_p(dfbmcs320_remove),
+};
+
+static int __init dfbmcs320_init(void)
+{
+ return platform_driver_register(&dfmcs320_driver);
+}
+module_init(dfbmcs320_init);
+
+static void __exit dfbmcs320_exit(void)
+{
+ platform_driver_unregister(&dfmcs320_driver);
+}
+module_exit(dfbmcs320_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("ASoC DFBM-CS320 bluethooth module driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/dmic.c b/sound/soc/codecs/dmic.c
new file mode 100644
index 00000000..f9a87737
--- /dev/null
+++ b/sound/soc/codecs/dmic.c
@@ -0,0 +1,105 @@
+/*
+ * dmic.c -- SoC audio for Generic Digital MICs
+ *
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+static struct snd_soc_dai_driver dmic_dai = {
+ .name = "dmic-hifi",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE
+ | SNDRV_PCM_FMTBIT_S24_LE
+ | SNDRV_PCM_FMTBIT_S16_LE,
+ },
+};
+
+static const struct snd_soc_dapm_widget dmic_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_OUT("DMIC AIF", "Capture", 0,
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_INPUT("DMic"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ {"DMIC AIF", NULL, "DMic"},
+};
+
+static int dmic_probe(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, dmic_dapm_widgets,
+ ARRAY_SIZE(dmic_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+ snd_soc_dapm_new_widgets(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_dmic = {
+ .probe = dmic_probe,
+};
+
+static int __devinit dmic_dev_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_dmic, &dmic_dai, 1);
+}
+
+static int __devexit dmic_dev_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+MODULE_ALIAS("platform:dmic-codec");
+
+static struct platform_driver dmic_driver = {
+ .driver = {
+ .name = "dmic-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = dmic_dev_probe,
+ .remove = __devexit_p(dmic_dev_remove),
+};
+
+static int __init dmic_init(void)
+{
+ return platform_driver_register(&dmic_driver);
+}
+module_init(dmic_init);
+
+static void __exit dmic_exit(void)
+{
+ platform_driver_unregister(&dmic_driver);
+}
+module_exit(dmic_exit);
+
+MODULE_DESCRIPTION("Generic DMIC driver");
+MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/jz4740.c b/sound/soc/codecs/jz4740.c
new file mode 100644
index 00000000..e373f8f0
--- /dev/null
+++ b/sound/soc/codecs/jz4740.c
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#define JZ4740_REG_CODEC_1 0x0
+#define JZ4740_REG_CODEC_2 0x1
+
+#define JZ4740_CODEC_1_LINE_ENABLE BIT(29)
+#define JZ4740_CODEC_1_MIC_ENABLE BIT(28)
+#define JZ4740_CODEC_1_SW1_ENABLE BIT(27)
+#define JZ4740_CODEC_1_ADC_ENABLE BIT(26)
+#define JZ4740_CODEC_1_SW2_ENABLE BIT(25)
+#define JZ4740_CODEC_1_DAC_ENABLE BIT(24)
+#define JZ4740_CODEC_1_VREF_DISABLE BIT(20)
+#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19)
+#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18)
+#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17)
+#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16)
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14)
+#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13)
+#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12)
+#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10))
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9)
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8)
+#define JZ4740_CODEC_1_SUSPEND BIT(1)
+#define JZ4740_CODEC_1_RESET BIT(0)
+
+#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29
+#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28
+#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27
+#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26
+#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25
+#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000
+#define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16
+#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0
+
+static const uint32_t jz4740_codec_regs[] = {
+ 0x021b2302, 0x00170803,
+};
+
+struct jz4740_codec {
+ void __iomem *base;
+ struct resource *mem;
+};
+
+static unsigned int jz4740_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct jz4740_codec *jz4740_codec = snd_soc_codec_get_drvdata(codec);
+ return readl(jz4740_codec->base + (reg << 2));
+}
+
+static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ struct jz4740_codec *jz4740_codec = snd_soc_codec_get_drvdata(codec);
+ u32 *cache = codec->reg_cache;
+
+ cache[reg] = val;
+ writel(val, jz4740_codec->base + (reg << 2));
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new jz4740_codec_controls[] = {
+ SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0),
+ SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0),
+ SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1),
+ SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_output_controls[] = {
+ SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0),
+ SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_input_controls[] = {
+ SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0),
+ SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = {
+ SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0),
+ SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0),
+
+ SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1,
+ jz4740_codec_output_controls,
+ ARRAY_SIZE(jz4740_codec_output_controls)),
+
+ SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0,
+ jz4740_codec_input_controls,
+ ARRAY_SIZE(jz4740_codec_input_controls)),
+ SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+
+ SND_SOC_DAPM_INPUT("MIC"),
+ SND_SOC_DAPM_INPUT("LIN"),
+ SND_SOC_DAPM_INPUT("RIN"),
+};
+
+static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = {
+ {"Line Input", NULL, "LIN"},
+ {"Line Input", NULL, "RIN"},
+
+ {"Input Mixer", "Line Capture Switch", "Line Input"},
+ {"Input Mixer", "Mic Capture Switch", "MIC"},
+
+ {"ADC", NULL, "Input Mixer"},
+
+ {"Output Mixer", "Bypass Switch", "Input Mixer"},
+ {"Output Mixer", "DAC Switch", "DAC"},
+
+ {"LOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+};
+
+static int jz4740_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ uint32_t val;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec =rtd->codec;
+
+ switch (params_rate(params)) {
+ case 8000:
+ val = 0;
+ break;
+ case 11025:
+ val = 1;
+ break;
+ case 12000:
+ val = 2;
+ break;
+ case 16000:
+ val = 3;
+ break;
+ case 22050:
+ val = 4;
+ break;
+ case 24000:
+ val = 5;
+ break;
+ case 32000:
+ val = 6;
+ break;
+ case 44100:
+ val = 7;
+ break;
+ case 48000:
+ val = 8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_SAMPLE_RATE_MASK, val);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_codec_dai_ops = {
+ .hw_params = jz4740_codec_hw_params,
+};
+
+static struct snd_soc_dai_driver jz4740_codec_dai = {
+ .name = "jz4740-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+ },
+ .ops = &jz4740_codec_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static void jz4740_codec_wakeup(struct snd_soc_codec *codec)
+{
+ int i;
+ uint32_t *cache = codec->reg_cache;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET);
+ udelay(2);
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i)
+ jz4740_codec_write(codec, i, cache[i]);
+}
+
+static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ unsigned int mask;
+ unsigned int value;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ mask = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+ value = 0;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* The only way to clear the suspend flag is to reset the codec */
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF)
+ jz4740_codec_wakeup(codec);
+
+ mask = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+ value = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ case SND_SOC_BIAS_OFF:
+ mask = JZ4740_CODEC_1_SUSPEND;
+ value = JZ4740_CODEC_1_SUSPEND;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ default:
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static int jz4740_codec_dev_probe(struct snd_soc_codec *codec)
+{
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE);
+
+ jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int jz4740_codec_dev_remove(struct snd_soc_codec *codec)
+{
+ jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int jz4740_codec_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int jz4740_codec_resume(struct snd_soc_codec *codec)
+{
+ return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+}
+
+#else
+#define jz4740_codec_suspend NULL
+#define jz4740_codec_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_jz4740_codec = {
+ .probe = jz4740_codec_dev_probe,
+ .remove = jz4740_codec_dev_remove,
+ .suspend = jz4740_codec_suspend,
+ .resume = jz4740_codec_resume,
+ .read = jz4740_codec_read,
+ .write = jz4740_codec_write,
+ .set_bias_level = jz4740_codec_set_bias_level,
+ .reg_cache_default = jz4740_codec_regs,
+ .reg_word_size = sizeof(u32),
+ .reg_cache_size = 2,
+
+ .controls = jz4740_codec_controls,
+ .num_controls = ARRAY_SIZE(jz4740_codec_controls),
+ .dapm_widgets = jz4740_codec_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(jz4740_codec_dapm_widgets),
+ .dapm_routes = jz4740_codec_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(jz4740_codec_dapm_routes),
+};
+
+static int __devinit jz4740_codec_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_codec *jz4740_codec;
+ struct resource *mem;
+
+ jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL);
+ if (!jz4740_codec)
+ return -ENOMEM;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to get mmio memory resource\n");
+ ret = -ENOENT;
+ goto err_free_codec;
+ }
+
+ mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ ret = -EBUSY;
+ goto err_free_codec;
+ }
+
+ jz4740_codec->base = ioremap(mem->start, resource_size(mem));
+ if (!jz4740_codec->base) {
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ ret = -EBUSY;
+ goto err_release_mem_region;
+ }
+ jz4740_codec->mem = mem;
+
+ platform_set_drvdata(pdev, jz4740_codec);
+
+ ret = snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_jz4740_codec, &jz4740_codec_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register codec\n");
+ goto err_iounmap;
+ }
+
+ return 0;
+
+err_iounmap:
+ iounmap(jz4740_codec->base);
+err_release_mem_region:
+ release_mem_region(mem->start, resource_size(mem));
+err_free_codec:
+ kfree(jz4740_codec);
+
+ return ret;
+}
+
+static int __devexit jz4740_codec_remove(struct platform_device *pdev)
+{
+ struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev);
+ struct resource *mem = jz4740_codec->mem;
+
+ snd_soc_unregister_codec(&pdev->dev);
+
+ iounmap(jz4740_codec->base);
+ release_mem_region(mem->start, resource_size(mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(jz4740_codec);
+
+ return 0;
+}
+
+static struct platform_driver jz4740_codec_driver = {
+ .probe = jz4740_codec_probe,
+ .remove = __devexit_p(jz4740_codec_remove),
+ .driver = {
+ .name = "jz4740-codec",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_codec_init(void)
+{
+ return platform_driver_register(&jz4740_codec_driver);
+}
+module_init(jz4740_codec_init);
+
+static void __exit jz4740_codec_exit(void)
+{
+ platform_driver_unregister(&jz4740_codec_driver);
+}
+module_exit(jz4740_codec_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC internal codec driver");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:jz4740-codec");
diff --git a/sound/soc/codecs/l3.c b/sound/soc/codecs/l3.c
new file mode 100644
index 00000000..5353af58
--- /dev/null
+++ b/sound/soc/codecs/l3.c
@@ -0,0 +1,91 @@
+/*
+ * L3 code
+ *
+ * Copyright (C) 2008, Christian Pellegrin <chripell@evolware.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *
+ * based on:
+ *
+ * L3 bus algorithm module.
+ *
+ * Copyright (C) 2001 Russell King, All Rights Reserved.
+ *
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include <sound/l3.h>
+
+/*
+ * Send one byte of data to the chip. Data is latched into the chip on
+ * the rising edge of the clock.
+ */
+static void sendbyte(struct l3_pins *adap, unsigned int byte)
+{
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ adap->setclk(0);
+ udelay(adap->data_hold);
+ adap->setdat(byte & 1);
+ udelay(adap->data_setup);
+ adap->setclk(1);
+ udelay(adap->clock_high);
+ byte >>= 1;
+ }
+}
+
+/*
+ * Send a set of bytes to the chip. We need to pulse the MODE line
+ * between each byte, but never at the start nor at the end of the
+ * transfer.
+ */
+static void sendbytes(struct l3_pins *adap, const u8 *buf,
+ int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (i) {
+ udelay(adap->mode_hold);
+ adap->setmode(0);
+ udelay(adap->mode);
+ }
+ adap->setmode(1);
+ udelay(adap->mode_setup);
+ sendbyte(adap, buf[i]);
+ }
+}
+
+int l3_write(struct l3_pins *adap, u8 addr, u8 *data, int len)
+{
+ adap->setclk(1);
+ adap->setdat(1);
+ adap->setmode(1);
+ udelay(adap->mode);
+
+ adap->setmode(0);
+ udelay(adap->mode_setup);
+ sendbyte(adap, addr);
+ udelay(adap->mode_hold);
+
+ sendbytes(adap, data, len);
+
+ adap->setclk(1);
+ adap->setdat(1);
+ adap->setmode(0);
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(l3_write);
+
+MODULE_DESCRIPTION("L3 bit-banging driver");
+MODULE_AUTHOR("Christian Pellegrin <chripell@evolware.org>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/lm4857.c b/sound/soc/codecs/lm4857.c
new file mode 100644
index 00000000..2c2a681d
--- /dev/null
+++ b/sound/soc/codecs/lm4857.c
@@ -0,0 +1,276 @@
+/*
+ * LM4857 AMP driver
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+struct lm4857 {
+ struct i2c_client *i2c;
+ uint8_t mode;
+};
+
+static const uint8_t lm4857_default_regs[] = {
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/* The register offsets in the cache array */
+#define LM4857_MVOL 0
+#define LM4857_LVOL 1
+#define LM4857_RVOL 2
+#define LM4857_CTRL 3
+
+/* the shifts required to set these bits */
+#define LM4857_3D 5
+#define LM4857_WAKEUP 5
+#define LM4857_EPGAIN 4
+
+static int lm4857_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ uint8_t data;
+ int ret;
+
+ ret = snd_soc_cache_write(codec, reg, value);
+ if (ret < 0)
+ return ret;
+
+ data = (reg << 6) | value;
+ ret = i2c_master_send(codec->control_data, &data, 1);
+ if (ret != 1) {
+ dev_err(codec->dev, "Failed to write register: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static unsigned int lm4857_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int val;
+ int ret;
+
+ ret = snd_soc_cache_read(codec, reg, &val);
+ if (ret)
+ return -1;
+
+ return val;
+}
+
+static int lm4857_get_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = lm4857->mode;
+
+ return 0;
+}
+
+static int lm4857_set_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
+ uint8_t value = ucontrol->value.integer.value[0];
+
+ lm4857->mode = value;
+
+ if (codec->dapm.bias_level == SND_SOC_BIAS_ON)
+ snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, value + 6);
+
+ return 1;
+}
+
+static int lm4857_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, lm4857->mode + 6);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, 0);
+ break;
+ default:
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static const char *lm4857_mode[] = {
+ "Earpiece",
+ "Loudspeaker",
+ "Loudspeaker + Headphone",
+ "Headphone",
+};
+
+static const struct soc_enum lm4857_mode_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode);
+
+static const struct snd_soc_dapm_widget lm4857_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("IN"),
+
+ SND_SOC_DAPM_OUTPUT("LS"),
+ SND_SOC_DAPM_OUTPUT("HP"),
+ SND_SOC_DAPM_OUTPUT("EP"),
+};
+
+static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0);
+static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0);
+
+static const struct snd_kcontrol_new lm4857_controls[] = {
+ SOC_SINGLE_TLV("Left Playback Volume", LM4857_LVOL, 0, 31, 0,
+ stereo_tlv),
+ SOC_SINGLE_TLV("Right Playback Volume", LM4857_RVOL, 0, 31, 0,
+ stereo_tlv),
+ SOC_SINGLE_TLV("Mono Playback Volume", LM4857_MVOL, 0, 31, 0,
+ mono_tlv),
+ SOC_SINGLE("Spk 3D Playback Switch", LM4857_LVOL, LM4857_3D, 1, 0),
+ SOC_SINGLE("HP 3D Playback Switch", LM4857_RVOL, LM4857_3D, 1, 0),
+ SOC_SINGLE("Fast Wakeup Playback Switch", LM4857_CTRL,
+ LM4857_WAKEUP, 1, 0),
+ SOC_SINGLE("Earpiece 6dB Playback Switch", LM4857_CTRL,
+ LM4857_EPGAIN, 1, 0),
+
+ SOC_ENUM_EXT("Mode", lm4857_mode_enum,
+ lm4857_get_mode, lm4857_set_mode),
+};
+
+/* There is a demux between the input signal and the output signals.
+ * Currently there is no easy way to model it in ASoC and since it does not make
+ * much of a difference in practice simply connect the input direclty to the
+ * outputs. */
+static const struct snd_soc_dapm_route lm4857_routes[] = {
+ {"LS", NULL, "IN"},
+ {"HP", NULL, "IN"},
+ {"EP", NULL, "IN"},
+};
+
+static int lm4857_probe(struct snd_soc_codec *codec)
+{
+ struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ codec->control_data = lm4857->i2c;
+
+ ret = snd_soc_add_controls(codec, lm4857_controls,
+ ARRAY_SIZE(lm4857_controls));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, lm4857_dapm_widgets,
+ ARRAY_SIZE(lm4857_dapm_widgets));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_add_routes(dapm, lm4857_routes,
+ ARRAY_SIZE(lm4857_routes));
+ if (ret)
+ return ret;
+
+ snd_soc_dapm_new_widgets(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_lm4857 = {
+ .write = lm4857_write,
+ .read = lm4857_read,
+ .probe = lm4857_probe,
+ .reg_cache_size = ARRAY_SIZE(lm4857_default_regs),
+ .reg_word_size = sizeof(uint8_t),
+ .reg_cache_default = lm4857_default_regs,
+ .set_bias_level = lm4857_set_bias_level,
+};
+
+static int __devinit lm4857_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct lm4857 *lm4857;
+ int ret;
+
+ lm4857 = kzalloc(sizeof(*lm4857), GFP_KERNEL);
+ if (!lm4857)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, lm4857);
+
+ lm4857->i2c = i2c;
+
+ ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_lm4857, NULL, 0);
+
+ if (ret) {
+ kfree(lm4857);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit lm4857_i2c_remove(struct i2c_client *i2c)
+{
+ struct lm4857 *lm4857 = i2c_get_clientdata(i2c);
+
+ snd_soc_unregister_codec(&i2c->dev);
+ kfree(lm4857);
+
+ return 0;
+}
+
+static const struct i2c_device_id lm4857_i2c_id[] = {
+ { "lm4857", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lm4857_i2c_id);
+
+static struct i2c_driver lm4857_i2c_driver = {
+ .driver = {
+ .name = "lm4857",
+ .owner = THIS_MODULE,
+ },
+ .probe = lm4857_i2c_probe,
+ .remove = __devexit_p(lm4857_i2c_remove),
+ .id_table = lm4857_i2c_id,
+};
+
+static int __init lm4857_init(void)
+{
+ return i2c_add_driver(&lm4857_i2c_driver);
+}
+module_init(lm4857_init);
+
+static void __exit lm4857_exit(void)
+{
+ i2c_del_driver(&lm4857_i2c_driver);
+}
+module_exit(lm4857_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("LM4857 amplifier driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
new file mode 100644
index 00000000..4173b67c
--- /dev/null
+++ b/sound/soc/codecs/max98088.c
@@ -0,0 +1,2125 @@
+/*
+ * max98088.c -- MAX98088 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/slab.h>
+#include <asm/div64.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+enum max98088_type {
+ MAX98088,
+ MAX98089,
+};
+
+struct max98088_cdata {
+ unsigned int rate;
+ unsigned int fmt;
+ int eq_sel;
+};
+
+struct max98088_priv {
+ enum max98088_type devtype;
+ void *control_data;
+ struct max98088_pdata *pdata;
+ unsigned int sysclk;
+ struct max98088_cdata dai[2];
+ int eq_textcnt;
+ const char **eq_texts;
+ struct soc_enum eq_enum;
+ u8 ina_state;
+ u8 inb_state;
+ unsigned int ex_mode;
+ unsigned int digmic;
+ unsigned int mic1pre;
+ unsigned int mic2pre;
+ unsigned int extmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+ 0x00, /* 00 IRQ status */
+ 0x00, /* 01 MIC status */
+ 0x00, /* 02 jack status */
+ 0x00, /* 03 battery voltage */
+ 0x00, /* 04 */
+ 0x00, /* 05 */
+ 0x00, /* 06 */
+ 0x00, /* 07 */
+ 0x00, /* 08 */
+ 0x00, /* 09 */
+ 0x00, /* 0A */
+ 0x00, /* 0B */
+ 0x00, /* 0C */
+ 0x00, /* 0D */
+ 0x00, /* 0E */
+ 0x00, /* 0F interrupt enable */
+
+ 0x00, /* 10 master clock */
+ 0x00, /* 11 DAI1 clock mode */
+ 0x00, /* 12 DAI1 clock control */
+ 0x00, /* 13 DAI1 clock control */
+ 0x00, /* 14 DAI1 format */
+ 0x00, /* 15 DAI1 clock */
+ 0x00, /* 16 DAI1 config */
+ 0x00, /* 17 DAI1 TDM */
+ 0x00, /* 18 DAI1 filters */
+ 0x00, /* 19 DAI2 clock mode */
+ 0x00, /* 1A DAI2 clock control */
+ 0x00, /* 1B DAI2 clock control */
+ 0x00, /* 1C DAI2 format */
+ 0x00, /* 1D DAI2 clock */
+ 0x00, /* 1E DAI2 config */
+ 0x00, /* 1F DAI2 TDM */
+
+ 0x00, /* 20 DAI2 filters */
+ 0x00, /* 21 data config */
+ 0x00, /* 22 DAC mixer */
+ 0x00, /* 23 left ADC mixer */
+ 0x00, /* 24 right ADC mixer */
+ 0x00, /* 25 left HP mixer */
+ 0x00, /* 26 right HP mixer */
+ 0x00, /* 27 HP control */
+ 0x00, /* 28 left REC mixer */
+ 0x00, /* 29 right REC mixer */
+ 0x00, /* 2A REC control */
+ 0x00, /* 2B left SPK mixer */
+ 0x00, /* 2C right SPK mixer */
+ 0x00, /* 2D SPK control */
+ 0x00, /* 2E sidetone */
+ 0x00, /* 2F DAI1 playback level */
+
+ 0x00, /* 30 DAI1 playback level */
+ 0x00, /* 31 DAI2 playback level */
+ 0x00, /* 32 DAI2 playbakc level */
+ 0x00, /* 33 left ADC level */
+ 0x00, /* 34 right ADC level */
+ 0x00, /* 35 MIC1 level */
+ 0x00, /* 36 MIC2 level */
+ 0x00, /* 37 INA level */
+ 0x00, /* 38 INB level */
+ 0x00, /* 39 left HP volume */
+ 0x00, /* 3A right HP volume */
+ 0x00, /* 3B left REC volume */
+ 0x00, /* 3C right REC volume */
+ 0x00, /* 3D left SPK volume */
+ 0x00, /* 3E right SPK volume */
+ 0x00, /* 3F MIC config */
+
+ 0x00, /* 40 MIC threshold */
+ 0x00, /* 41 excursion limiter filter */
+ 0x00, /* 42 excursion limiter threshold */
+ 0x00, /* 43 ALC */
+ 0x00, /* 44 power limiter threshold */
+ 0x00, /* 45 power limiter config */
+ 0x00, /* 46 distortion limiter config */
+ 0x00, /* 47 audio input */
+ 0x00, /* 48 microphone */
+ 0x00, /* 49 level control */
+ 0x00, /* 4A bypass switches */
+ 0x00, /* 4B jack detect */
+ 0x00, /* 4C input enable */
+ 0x00, /* 4D output enable */
+ 0xF0, /* 4E bias control */
+ 0x00, /* 4F DAC power */
+
+ 0x0F, /* 50 DAC power */
+ 0x00, /* 51 system */
+ 0x00, /* 52 DAI1 EQ1 */
+ 0x00, /* 53 DAI1 EQ1 */
+ 0x00, /* 54 DAI1 EQ1 */
+ 0x00, /* 55 DAI1 EQ1 */
+ 0x00, /* 56 DAI1 EQ1 */
+ 0x00, /* 57 DAI1 EQ1 */
+ 0x00, /* 58 DAI1 EQ1 */
+ 0x00, /* 59 DAI1 EQ1 */
+ 0x00, /* 5A DAI1 EQ1 */
+ 0x00, /* 5B DAI1 EQ1 */
+ 0x00, /* 5C DAI1 EQ2 */
+ 0x00, /* 5D DAI1 EQ2 */
+ 0x00, /* 5E DAI1 EQ2 */
+ 0x00, /* 5F DAI1 EQ2 */
+
+ 0x00, /* 60 DAI1 EQ2 */
+ 0x00, /* 61 DAI1 EQ2 */
+ 0x00, /* 62 DAI1 EQ2 */
+ 0x00, /* 63 DAI1 EQ2 */
+ 0x00, /* 64 DAI1 EQ2 */
+ 0x00, /* 65 DAI1 EQ2 */
+ 0x00, /* 66 DAI1 EQ3 */
+ 0x00, /* 67 DAI1 EQ3 */
+ 0x00, /* 68 DAI1 EQ3 */
+ 0x00, /* 69 DAI1 EQ3 */
+ 0x00, /* 6A DAI1 EQ3 */
+ 0x00, /* 6B DAI1 EQ3 */
+ 0x00, /* 6C DAI1 EQ3 */
+ 0x00, /* 6D DAI1 EQ3 */
+ 0x00, /* 6E DAI1 EQ3 */
+ 0x00, /* 6F DAI1 EQ3 */
+
+ 0x00, /* 70 DAI1 EQ4 */
+ 0x00, /* 71 DAI1 EQ4 */
+ 0x00, /* 72 DAI1 EQ4 */
+ 0x00, /* 73 DAI1 EQ4 */
+ 0x00, /* 74 DAI1 EQ4 */
+ 0x00, /* 75 DAI1 EQ4 */
+ 0x00, /* 76 DAI1 EQ4 */
+ 0x00, /* 77 DAI1 EQ4 */
+ 0x00, /* 78 DAI1 EQ4 */
+ 0x00, /* 79 DAI1 EQ4 */
+ 0x00, /* 7A DAI1 EQ5 */
+ 0x00, /* 7B DAI1 EQ5 */
+ 0x00, /* 7C DAI1 EQ5 */
+ 0x00, /* 7D DAI1 EQ5 */
+ 0x00, /* 7E DAI1 EQ5 */
+ 0x00, /* 7F DAI1 EQ5 */
+
+ 0x00, /* 80 DAI1 EQ5 */
+ 0x00, /* 81 DAI1 EQ5 */
+ 0x00, /* 82 DAI1 EQ5 */
+ 0x00, /* 83 DAI1 EQ5 */
+ 0x00, /* 84 DAI2 EQ1 */
+ 0x00, /* 85 DAI2 EQ1 */
+ 0x00, /* 86 DAI2 EQ1 */
+ 0x00, /* 87 DAI2 EQ1 */
+ 0x00, /* 88 DAI2 EQ1 */
+ 0x00, /* 89 DAI2 EQ1 */
+ 0x00, /* 8A DAI2 EQ1 */
+ 0x00, /* 8B DAI2 EQ1 */
+ 0x00, /* 8C DAI2 EQ1 */
+ 0x00, /* 8D DAI2 EQ1 */
+ 0x00, /* 8E DAI2 EQ2 */
+ 0x00, /* 8F DAI2 EQ2 */
+
+ 0x00, /* 90 DAI2 EQ2 */
+ 0x00, /* 91 DAI2 EQ2 */
+ 0x00, /* 92 DAI2 EQ2 */
+ 0x00, /* 93 DAI2 EQ2 */
+ 0x00, /* 94 DAI2 EQ2 */
+ 0x00, /* 95 DAI2 EQ2 */
+ 0x00, /* 96 DAI2 EQ2 */
+ 0x00, /* 97 DAI2 EQ2 */
+ 0x00, /* 98 DAI2 EQ3 */
+ 0x00, /* 99 DAI2 EQ3 */
+ 0x00, /* 9A DAI2 EQ3 */
+ 0x00, /* 9B DAI2 EQ3 */
+ 0x00, /* 9C DAI2 EQ3 */
+ 0x00, /* 9D DAI2 EQ3 */
+ 0x00, /* 9E DAI2 EQ3 */
+ 0x00, /* 9F DAI2 EQ3 */
+
+ 0x00, /* A0 DAI2 EQ3 */
+ 0x00, /* A1 DAI2 EQ3 */
+ 0x00, /* A2 DAI2 EQ4 */
+ 0x00, /* A3 DAI2 EQ4 */
+ 0x00, /* A4 DAI2 EQ4 */
+ 0x00, /* A5 DAI2 EQ4 */
+ 0x00, /* A6 DAI2 EQ4 */
+ 0x00, /* A7 DAI2 EQ4 */
+ 0x00, /* A8 DAI2 EQ4 */
+ 0x00, /* A9 DAI2 EQ4 */
+ 0x00, /* AA DAI2 EQ4 */
+ 0x00, /* AB DAI2 EQ4 */
+ 0x00, /* AC DAI2 EQ5 */
+ 0x00, /* AD DAI2 EQ5 */
+ 0x00, /* AE DAI2 EQ5 */
+ 0x00, /* AF DAI2 EQ5 */
+
+ 0x00, /* B0 DAI2 EQ5 */
+ 0x00, /* B1 DAI2 EQ5 */
+ 0x00, /* B2 DAI2 EQ5 */
+ 0x00, /* B3 DAI2 EQ5 */
+ 0x00, /* B4 DAI2 EQ5 */
+ 0x00, /* B5 DAI2 EQ5 */
+ 0x00, /* B6 DAI1 biquad */
+ 0x00, /* B7 DAI1 biquad */
+ 0x00, /* B8 DAI1 biquad */
+ 0x00, /* B9 DAI1 biquad */
+ 0x00, /* BA DAI1 biquad */
+ 0x00, /* BB DAI1 biquad */
+ 0x00, /* BC DAI1 biquad */
+ 0x00, /* BD DAI1 biquad */
+ 0x00, /* BE DAI1 biquad */
+ 0x00, /* BF DAI1 biquad */
+
+ 0x00, /* C0 DAI2 biquad */
+ 0x00, /* C1 DAI2 biquad */
+ 0x00, /* C2 DAI2 biquad */
+ 0x00, /* C3 DAI2 biquad */
+ 0x00, /* C4 DAI2 biquad */
+ 0x00, /* C5 DAI2 biquad */
+ 0x00, /* C6 DAI2 biquad */
+ 0x00, /* C7 DAI2 biquad */
+ 0x00, /* C8 DAI2 biquad */
+ 0x00, /* C9 DAI2 biquad */
+ 0x00, /* CA */
+ 0x00, /* CB */
+ 0x00, /* CC */
+ 0x00, /* CD */
+ 0x00, /* CE */
+ 0x00, /* CF */
+
+ 0x00, /* D0 */
+ 0x00, /* D1 */
+ 0x00, /* D2 */
+ 0x00, /* D3 */
+ 0x00, /* D4 */
+ 0x00, /* D5 */
+ 0x00, /* D6 */
+ 0x00, /* D7 */
+ 0x00, /* D8 */
+ 0x00, /* D9 */
+ 0x00, /* DA */
+ 0x70, /* DB */
+ 0x00, /* DC */
+ 0x00, /* DD */
+ 0x00, /* DE */
+ 0x00, /* DF */
+
+ 0x00, /* E0 */
+ 0x00, /* E1 */
+ 0x00, /* E2 */
+ 0x00, /* E3 */
+ 0x00, /* E4 */
+ 0x00, /* E5 */
+ 0x00, /* E6 */
+ 0x00, /* E7 */
+ 0x00, /* E8 */
+ 0x00, /* E9 */
+ 0x00, /* EA */
+ 0x00, /* EB */
+ 0x00, /* EC */
+ 0x00, /* ED */
+ 0x00, /* EE */
+ 0x00, /* EF */
+
+ 0x00, /* F0 */
+ 0x00, /* F1 */
+ 0x00, /* F2 */
+ 0x00, /* F3 */
+ 0x00, /* F4 */
+ 0x00, /* F5 */
+ 0x00, /* F6 */
+ 0x00, /* F7 */
+ 0x00, /* F8 */
+ 0x00, /* F9 */
+ 0x00, /* FA */
+ 0x00, /* FB */
+ 0x00, /* FC */
+ 0x00, /* FD */
+ 0x00, /* FE */
+ 0x00, /* FF */
+};
+
+static struct {
+ int readable;
+ int writable;
+ int vol;
+} max98088_access[M98088_REG_CNT] = {
+ { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+ { 0xFF, 0x00, 1 }, /* 01 MIC status */
+ { 0xFF, 0x00, 1 }, /* 02 jack status */
+ { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+ { 0xFF, 0xFF, 0 }, /* 04 */
+ { 0xFF, 0xFF, 0 }, /* 05 */
+ { 0xFF, 0xFF, 0 }, /* 06 */
+ { 0xFF, 0xFF, 0 }, /* 07 */
+ { 0xFF, 0xFF, 0 }, /* 08 */
+ { 0xFF, 0xFF, 0 }, /* 09 */
+ { 0xFF, 0xFF, 0 }, /* 0A */
+ { 0xFF, 0xFF, 0 }, /* 0B */
+ { 0xFF, 0xFF, 0 }, /* 0C */
+ { 0xFF, 0xFF, 0 }, /* 0D */
+ { 0xFF, 0xFF, 0 }, /* 0E */
+ { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+ { 0xFF, 0xFF, 0 }, /* 10 master clock */
+ { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+ { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+ { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+ { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+ { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+ { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+ { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+ { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+ { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+ { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+ { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+ { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+ { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+ { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+ { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+ { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+ { 0xFF, 0xFF, 0 }, /* 21 data config */
+ { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+ { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+ { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+ { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+ { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+ { 0xFF, 0xFF, 0 }, /* 27 HP control */
+ { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+ { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+ { 0xFF, 0xFF, 0 }, /* 2A REC control */
+ { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+ { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+ { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+ { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+ { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+ { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+ { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+ { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+ { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+ { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+ { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+ { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+ { 0xFF, 0xFF, 0 }, /* 37 INA level */
+ { 0xFF, 0xFF, 0 }, /* 38 INB level */
+ { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+ { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+ { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+ { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+ { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+ { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+ { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+ { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+ { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+ { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+ { 0xFF, 0xFF, 0 }, /* 43 ALC */
+ { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+ { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+ { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+ { 0xFF, 0xFF, 0 }, /* 47 audio input */
+ { 0xFF, 0xFF, 0 }, /* 48 microphone */
+ { 0xFF, 0xFF, 0 }, /* 49 level control */
+ { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+ { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+ { 0xFF, 0xFF, 0 }, /* 4C input enable */
+ { 0xFF, 0xFF, 0 }, /* 4D output enable */
+ { 0xFF, 0xFF, 0 }, /* 4E bias control */
+ { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+ { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+ { 0xFF, 0xFF, 0 }, /* 51 system */
+ { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+ { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+ { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+ { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+ { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+ { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+ { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+ { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+ { 0x00, 0x00, 0 }, /* CA */
+ { 0x00, 0x00, 0 }, /* CB */
+ { 0x00, 0x00, 0 }, /* CC */
+ { 0x00, 0x00, 0 }, /* CD */
+ { 0x00, 0x00, 0 }, /* CE */
+ { 0x00, 0x00, 0 }, /* CF */
+
+ { 0x00, 0x00, 0 }, /* D0 */
+ { 0x00, 0x00, 0 }, /* D1 */
+ { 0x00, 0x00, 0 }, /* D2 */
+ { 0x00, 0x00, 0 }, /* D3 */
+ { 0x00, 0x00, 0 }, /* D4 */
+ { 0x00, 0x00, 0 }, /* D5 */
+ { 0x00, 0x00, 0 }, /* D6 */
+ { 0x00, 0x00, 0 }, /* D7 */
+ { 0x00, 0x00, 0 }, /* D8 */
+ { 0x00, 0x00, 0 }, /* D9 */
+ { 0x00, 0x00, 0 }, /* DA */
+ { 0x00, 0x00, 0 }, /* DB */
+ { 0x00, 0x00, 0 }, /* DC */
+ { 0x00, 0x00, 0 }, /* DD */
+ { 0x00, 0x00, 0 }, /* DE */
+ { 0x00, 0x00, 0 }, /* DF */
+
+ { 0x00, 0x00, 0 }, /* E0 */
+ { 0x00, 0x00, 0 }, /* E1 */
+ { 0x00, 0x00, 0 }, /* E2 */
+ { 0x00, 0x00, 0 }, /* E3 */
+ { 0x00, 0x00, 0 }, /* E4 */
+ { 0x00, 0x00, 0 }, /* E5 */
+ { 0x00, 0x00, 0 }, /* E6 */
+ { 0x00, 0x00, 0 }, /* E7 */
+ { 0x00, 0x00, 0 }, /* E8 */
+ { 0x00, 0x00, 0 }, /* E9 */
+ { 0x00, 0x00, 0 }, /* EA */
+ { 0x00, 0x00, 0 }, /* EB */
+ { 0x00, 0x00, 0 }, /* EC */
+ { 0x00, 0x00, 0 }, /* ED */
+ { 0x00, 0x00, 0 }, /* EE */
+ { 0x00, 0x00, 0 }, /* EF */
+
+ { 0x00, 0x00, 0 }, /* F0 */
+ { 0x00, 0x00, 0 }, /* F1 */
+ { 0x00, 0x00, 0 }, /* F2 */
+ { 0x00, 0x00, 0 }, /* F3 */
+ { 0x00, 0x00, 0 }, /* F4 */
+ { 0x00, 0x00, 0 }, /* F5 */
+ { 0x00, 0x00, 0 }, /* F6 */
+ { 0x00, 0x00, 0 }, /* F7 */
+ { 0x00, 0x00, 0 }, /* F8 */
+ { 0x00, 0x00, 0 }, /* F9 */
+ { 0x00, 0x00, 0 }, /* FA */
+ { 0x00, 0x00, 0 }, /* FB */
+ { 0x00, 0x00, 0 }, /* FC */
+ { 0x00, 0x00, 0 }, /* FD */
+ { 0x00, 0x00, 0 }, /* FE */
+ { 0xFF, 0x00, 1 }, /* FF */
+};
+
+static int max98088_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ return max98088_access[reg].vol;
+}
+
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+static void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+ unsigned int band, u16 *coefs)
+{
+ unsigned int eq_reg;
+ unsigned int i;
+
+ BUG_ON(band > 4);
+ BUG_ON(dai > 1);
+
+ /* Load the base register address */
+ eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+ /* Add the band address offset, note adjustment for word address */
+ eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+ /* Step through the registers and coefs */
+ for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+ snd_soc_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+ snd_soc_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+ }
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_exmode_texts[] = {
+ "Off", "100Hz", "400Hz", "600Hz", "800Hz", "1000Hz", "200-400Hz",
+ "400-600Hz", "400-800Hz",
+};
+
+static const unsigned int max98088_exmode_values[] = {
+ 0x00, 0x43, 0x10, 0x20, 0x30, 0x40, 0x11, 0x22, 0x32
+};
+
+static const struct soc_enum max98088_exmode_enum =
+ SOC_VALUE_ENUM_SINGLE(M98088_REG_41_SPKDHP, 0, 127,
+ ARRAY_SIZE(max98088_exmode_texts),
+ max98088_exmode_texts,
+ max98088_exmode_values);
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+ "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+ SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+ max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+ SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_extmic_text[] = { "None", "MIC1", "MIC2" };
+
+static const struct soc_enum max98088_extmic_enum =
+ SOC_ENUM_SINGLE(M98088_REG_48_CFG_MIC, 0, 3, max98088_extmic_text);
+
+static const struct snd_kcontrol_new max98088_extmic_mux =
+ SOC_DAPM_ENUM("External MIC Mux", max98088_extmic_enum);
+
+static const char *max98088_dai1_fltr[] = {
+ "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+ "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+ SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+ SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ unsigned int sel = ucontrol->value.integer.value[0];
+
+ max98088->mic1pre = sel;
+ snd_soc_update_bits(codec, M98088_REG_35_LVL_MIC1, M98088_MICPRE_MASK,
+ (1+sel)<<M98088_MICPRE_SHIFT);
+
+ return 0;
+}
+
+static int max98088_mic1pre_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = max98088->mic1pre;
+ return 0;
+}
+
+static int max98088_mic2pre_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ unsigned int sel = ucontrol->value.integer.value[0];
+
+ max98088->mic2pre = sel;
+ snd_soc_update_bits(codec, M98088_REG_36_LVL_MIC2, M98088_MICPRE_MASK,
+ (1+sel)<<M98088_MICPRE_SHIFT);
+
+ return 0;
+}
+
+static int max98088_mic2pre_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = max98088->mic2pre;
+ return 0;
+}
+
+static const unsigned int max98088_micboost_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 1, TLV_DB_SCALE_ITEM(0, 2000, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+ SOC_DOUBLE_R("Headphone Volume", M98088_REG_39_LVL_HP_L,
+ M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+ SOC_DOUBLE_R("Speaker Volume", M98088_REG_3D_LVL_SPK_L,
+ M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+ SOC_DOUBLE_R("Receiver Volume", M98088_REG_3B_LVL_REC_L,
+ M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+ SOC_DOUBLE_R("Headphone Switch", M98088_REG_39_LVL_HP_L,
+ M98088_REG_3A_LVL_HP_R, 7, 1, 1),
+ SOC_DOUBLE_R("Speaker Switch", M98088_REG_3D_LVL_SPK_L,
+ M98088_REG_3E_LVL_SPK_R, 7, 1, 1),
+ SOC_DOUBLE_R("Receiver Switch", M98088_REG_3B_LVL_REC_L,
+ M98088_REG_3C_LVL_REC_R, 7, 1, 1),
+
+ SOC_SINGLE("MIC1 Volume", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+ SOC_SINGLE("MIC2 Volume", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+ SOC_SINGLE_EXT_TLV("MIC1 Boost Volume",
+ M98088_REG_35_LVL_MIC1, 5, 2, 0,
+ max98088_mic1pre_get, max98088_mic1pre_set,
+ max98088_micboost_tlv),
+ SOC_SINGLE_EXT_TLV("MIC2 Boost Volume",
+ M98088_REG_36_LVL_MIC2, 5, 2, 0,
+ max98088_mic2pre_get, max98088_mic2pre_set,
+ max98088_micboost_tlv),
+
+ SOC_SINGLE("INA Volume", M98088_REG_37_LVL_INA, 0, 7, 1),
+ SOC_SINGLE("INB Volume", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+ SOC_SINGLE("ADCL Volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+ SOC_SINGLE("ADCR Volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+ SOC_SINGLE("ADCL Boost Volume", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+ SOC_SINGLE("ADCR Boost Volume", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+ SOC_SINGLE("EQ1 Switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+ SOC_SINGLE("EQ2 Switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+ SOC_ENUM("EX Limiter Mode", max98088_exmode_enum),
+ SOC_ENUM("EX Limiter Threshold", max98088_ex_thresh_enum),
+
+ SOC_ENUM("DAI1 Filter Mode", max98088_filter_mode_enum),
+ SOC_ENUM("DAI1 DAC Filter", max98088_dai1_dac_filter_enum),
+ SOC_ENUM("DAI1 ADC Filter", max98088_dai1_adc_filter_enum),
+ SOC_SINGLE("DAI2 DC Block Switch", M98088_REG_20_DAI2_FILTERS,
+ 0, 1, 0),
+
+ SOC_SINGLE("ALC Switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+ SOC_SINGLE("ALC Threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+ SOC_SINGLE("ALC Multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+ SOC_SINGLE("ALC Release Time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+ SOC_SINGLE("PWR Limiter Threshold", M98088_REG_44_PWRLMT_CFG,
+ 4, 15, 0),
+ SOC_SINGLE("PWR Limiter Weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+ SOC_SINGLE("PWR Limiter Time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+ SOC_SINGLE("PWR Limiter Time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+ SOC_SINGLE("THD Limiter Threshold", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+ SOC_SINGLE("THD Limiter Time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_rec_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_mic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ if (w->reg == M98088_REG_35_LVL_MIC1) {
+ snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+ (1+max98088->mic1pre)<<M98088_MICPRE_SHIFT);
+ } else {
+ snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+ (1+max98088->mic2pre)<<M98088_MICPRE_SHIFT);
+ }
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * The line inputs are 2-channel stereo inputs with the left
+ * and right channels sharing a common PGA power control signal.
+ */
+static int max98088_line_pga(struct snd_soc_dapm_widget *w,
+ int event, int line, u8 channel)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ u8 *state;
+
+ BUG_ON(!((channel == 1) || (channel == 2)));
+
+ switch (line) {
+ case LINE_INA:
+ state = &max98088->ina_state;
+ break;
+ case LINE_INB:
+ state = &max98088->inb_state;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ *state |= channel;
+ snd_soc_update_bits(codec, w->reg,
+ (1 << w->shift), (1 << w->shift));
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ *state &= ~channel;
+ if (*state == 0) {
+ snd_soc_update_bits(codec, w->reg,
+ (1 << w->shift), 0);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int max98088_pga_ina1_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ return max98088_line_pga(w, event, LINE_INA, 1);
+}
+
+static int max98088_pga_ina2_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ return max98088_line_pga(w, event, LINE_INA, 2);
+}
+
+static int max98088_pga_inb1_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ return max98088_line_pga(w, event, LINE_INB, 1);
+}
+
+static int max98088_pga_inb2_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ return max98088_line_pga(w, event, LINE_INB, 2);
+}
+
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+ SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+ SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+ SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+ M98088_REG_4D_PWR_EN_OUT, 1, 0),
+ SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+ M98088_REG_4D_PWR_EN_OUT, 0, 0),
+ SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+ M98088_REG_4D_PWR_EN_OUT, 1, 0),
+ SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+ M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+ SND_SOC_DAPM_PGA("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+ 7, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+ 6, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+ 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+ 4, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+ 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+ 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("External MIC", SND_SOC_NOPM, 0, 0,
+ &max98088_extmic_mux),
+
+ SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_left_hp_mixer_controls[0],
+ ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_right_hp_mixer_controls[0],
+ ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_left_speaker_mixer_controls[0],
+ ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_right_speaker_mixer_controls[0],
+ ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_left_rec_mixer_controls[0],
+ ARRAY_SIZE(max98088_left_rec_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_right_rec_mixer_controls[0],
+ ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_left_ADC_mixer_controls[0],
+ ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_right_ADC_mixer_controls[0],
+ ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+ SND_SOC_DAPM_PGA_E("MIC1 Input", M98088_REG_35_LVL_MIC1,
+ 5, 0, NULL, 0, max98088_mic_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("MIC2 Input", M98088_REG_36_LVL_MIC2,
+ 5, 0, NULL, 0, max98088_mic_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+ 7, 0, NULL, 0, max98088_pga_ina1_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+ 7, 0, NULL, 0, max98088_pga_ina2_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+ 6, 0, NULL, 0, max98088_pga_inb1_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+ 6, 0, NULL, 0, max98088_pga_inb2_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_MICBIAS("MICBIAS", M98088_REG_4C_PWR_EN_IN, 3, 0),
+
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("SPKL"),
+ SND_SOC_DAPM_OUTPUT("SPKR"),
+ SND_SOC_DAPM_OUTPUT("RECL"),
+ SND_SOC_DAPM_OUTPUT("RECR"),
+
+ SND_SOC_DAPM_INPUT("MIC1"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+ SND_SOC_DAPM_INPUT("INA1"),
+ SND_SOC_DAPM_INPUT("INA2"),
+ SND_SOC_DAPM_INPUT("INB1"),
+ SND_SOC_DAPM_INPUT("INB2"),
+};
+
+static const struct snd_soc_dapm_route max98088_audio_map[] = {
+ /* Left headphone output mixer */
+ {"Left HP Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Left HP Mixer", "Left DAC2 Switch", "DACL2"},
+ {"Left HP Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Left HP Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Left HP Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left HP Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left HP Mixer", "INA1 Switch", "INA1 Input"},
+ {"Left HP Mixer", "INA2 Switch", "INA2 Input"},
+ {"Left HP Mixer", "INB1 Switch", "INB1 Input"},
+ {"Left HP Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Right headphone output mixer */
+ {"Right HP Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Right HP Mixer", "Left DAC2 Switch", "DACL2" },
+ {"Right HP Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Right HP Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Right HP Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right HP Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right HP Mixer", "INA1 Switch", "INA1 Input"},
+ {"Right HP Mixer", "INA2 Switch", "INA2 Input"},
+ {"Right HP Mixer", "INB1 Switch", "INB1 Input"},
+ {"Right HP Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Left speaker output mixer */
+ {"Left SPK Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Left SPK Mixer", "Left DAC2 Switch", "DACL2"},
+ {"Left SPK Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Left SPK Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Left SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left SPK Mixer", "INA1 Switch", "INA1 Input"},
+ {"Left SPK Mixer", "INA2 Switch", "INA2 Input"},
+ {"Left SPK Mixer", "INB1 Switch", "INB1 Input"},
+ {"Left SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Right speaker output mixer */
+ {"Right SPK Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Right SPK Mixer", "Left DAC2 Switch", "DACL2"},
+ {"Right SPK Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Right SPK Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Right SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right SPK Mixer", "INA1 Switch", "INA1 Input"},
+ {"Right SPK Mixer", "INA2 Switch", "INA2 Input"},
+ {"Right SPK Mixer", "INB1 Switch", "INB1 Input"},
+ {"Right SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Earpiece/Receiver output mixer */
+ {"Left REC Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Left REC Mixer", "Left DAC2 Switch", "DACL2"},
+ {"Left REC Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Left REC Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Left REC Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left REC Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left REC Mixer", "INA1 Switch", "INA1 Input"},
+ {"Left REC Mixer", "INA2 Switch", "INA2 Input"},
+ {"Left REC Mixer", "INB1 Switch", "INB1 Input"},
+ {"Left REC Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Earpiece/Receiver output mixer */
+ {"Right REC Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Right REC Mixer", "Left DAC2 Switch", "DACL2"},
+ {"Right REC Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Right REC Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Right REC Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right REC Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right REC Mixer", "INA1 Switch", "INA1 Input"},
+ {"Right REC Mixer", "INA2 Switch", "INA2 Input"},
+ {"Right REC Mixer", "INB1 Switch", "INB1 Input"},
+ {"Right REC Mixer", "INB2 Switch", "INB2 Input"},
+
+ {"HP Left Out", NULL, "Left HP Mixer"},
+ {"HP Right Out", NULL, "Right HP Mixer"},
+ {"SPK Left Out", NULL, "Left SPK Mixer"},
+ {"SPK Right Out", NULL, "Right SPK Mixer"},
+ {"REC Left Out", NULL, "Left REC Mixer"},
+ {"REC Right Out", NULL, "Right REC Mixer"},
+
+ {"HPL", NULL, "HP Left Out"},
+ {"HPR", NULL, "HP Right Out"},
+ {"SPKL", NULL, "SPK Left Out"},
+ {"SPKR", NULL, "SPK Right Out"},
+ {"RECL", NULL, "REC Left Out"},
+ {"RECR", NULL, "REC Right Out"},
+
+ /* Left ADC input mixer */
+ {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left ADC Mixer", "INA1 Switch", "INA1 Input"},
+ {"Left ADC Mixer", "INA2 Switch", "INA2 Input"},
+ {"Left ADC Mixer", "INB1 Switch", "INB1 Input"},
+ {"Left ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Right ADC input mixer */
+ {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right ADC Mixer", "INA1 Switch", "INA1 Input"},
+ {"Right ADC Mixer", "INA2 Switch", "INA2 Input"},
+ {"Right ADC Mixer", "INB1 Switch", "INB1 Input"},
+ {"Right ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Inputs */
+ {"ADCL", NULL, "Left ADC Mixer"},
+ {"ADCR", NULL, "Right ADC Mixer"},
+ {"INA1 Input", NULL, "INA1"},
+ {"INA2 Input", NULL, "INA2"},
+ {"INB1 Input", NULL, "INB1"},
+ {"INB2 Input", NULL, "INB2"},
+ {"MIC1 Input", NULL, "MIC1"},
+ {"MIC2 Input", NULL, "MIC2"},
+};
+
+/* codec mclk clock divider coefficients */
+static const struct {
+ u32 rate;
+ u8 sr;
+} rate_table[] = {
+ {8000, 0x10},
+ {11025, 0x20},
+ {16000, 0x30},
+ {22050, 0x40},
+ {24000, 0x50},
+ {32000, 0x60},
+ {44100, 0x70},
+ {48000, 0x80},
+ {88200, 0x90},
+ {96000, 0xA0},
+};
+
+static inline int rate_value(int rate, u8 *value)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+ if (rate_table[i].rate >= rate) {
+ *value = rate_table[i].sr;
+ return 0;
+ }
+ }
+ *value = rate_table[0].sr;
+ return -EINVAL;
+}
+
+static int max98088_dai1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+ unsigned long long ni;
+ unsigned int rate;
+ u8 regval;
+
+ cdata = &max98088->dai[0];
+
+ rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+ M98088_DAI_WS, 0);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+ M98088_DAI_WS, M98088_DAI_WS);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+ if (rate_value(rate, &regval))
+ return -EINVAL;
+
+ snd_soc_update_bits(codec, M98088_REG_11_DAI1_CLKMODE,
+ M98088_CLKMODE_MASK, regval);
+ cdata->rate = rate;
+
+ /* Configure NI when operating as master */
+ if (snd_soc_read(codec, M98088_REG_14_DAI1_FORMAT)
+ & M98088_DAI_MAS) {
+ if (max98088->sysclk == 0) {
+ dev_err(codec->dev, "Invalid system clock frequency\n");
+ return -EINVAL;
+ }
+ ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+ * (unsigned long long int)rate;
+ do_div(ni, (unsigned long long int)max98088->sysclk);
+ snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+ (ni >> 8) & 0x7F);
+ snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+ ni & 0xFF);
+ }
+
+ /* Update sample rate mode */
+ if (rate < 50000)
+ snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+ M98088_DAI_DHF, 0);
+ else
+ snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+ M98088_DAI_DHF, M98088_DAI_DHF);
+
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN,
+ M98088_SHDNRUN);
+
+ return 0;
+}
+
+static int max98088_dai2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+ unsigned long long ni;
+ unsigned int rate;
+ u8 regval;
+
+ cdata = &max98088->dai[1];
+
+ rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+ M98088_DAI_WS, 0);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+ M98088_DAI_WS, M98088_DAI_WS);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+ if (rate_value(rate, &regval))
+ return -EINVAL;
+
+ snd_soc_update_bits(codec, M98088_REG_19_DAI2_CLKMODE,
+ M98088_CLKMODE_MASK, regval);
+ cdata->rate = rate;
+
+ /* Configure NI when operating as master */
+ if (snd_soc_read(codec, M98088_REG_1C_DAI2_FORMAT)
+ & M98088_DAI_MAS) {
+ if (max98088->sysclk == 0) {
+ dev_err(codec->dev, "Invalid system clock frequency\n");
+ return -EINVAL;
+ }
+ ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+ * (unsigned long long int)rate;
+ do_div(ni, (unsigned long long int)max98088->sysclk);
+ snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+ (ni >> 8) & 0x7F);
+ snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+ ni & 0xFF);
+ }
+
+ /* Update sample rate mode */
+ if (rate < 50000)
+ snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+ M98088_DAI_DHF, 0);
+ else
+ snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+ M98088_DAI_DHF, M98088_DAI_DHF);
+
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN,
+ M98088_SHDNRUN);
+
+ return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+ /* Requested clock frequency is already setup */
+ if (freq == max98088->sysclk)
+ return 0;
+
+ max98088->sysclk = freq; /* remember current sysclk */
+
+ /* Setup clocks for slave mode, and using the PLL
+ * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+ * 0x02 (when master clk is 20MHz to 30MHz)..
+ */
+ if ((freq >= 10000000) && (freq < 20000000)) {
+ snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+ } else if ((freq >= 20000000) && (freq < 30000000)) {
+ snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+ } else {
+ dev_err(codec->dev, "Invalid master clock frequency\n");
+ return -EINVAL;
+ }
+
+ if (snd_soc_read(codec, M98088_REG_51_PWR_SYS) & M98088_SHDNRUN) {
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+ M98088_SHDNRUN, 0);
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+ M98088_SHDNRUN, M98088_SHDNRUN);
+ }
+
+ dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+ max98088->sysclk = freq;
+ return 0;
+}
+
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+ u8 reg15val;
+ u8 reg14val = 0;
+
+ cdata = &max98088->dai[0];
+
+ if (fmt != cdata->fmt) {
+ cdata->fmt = fmt;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* Slave mode PLL */
+ snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+ 0x80);
+ snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+ 0x00);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* Set to master mode */
+ reg14val |= M98088_DAI_MAS;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(codec->dev, "Clock mode unsupported");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ reg14val |= M98088_DAI_DLY;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ reg14val |= M98088_DAI_WCI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ reg14val |= M98088_DAI_BCI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ reg14val |= M98088_DAI_BCI|M98088_DAI_WCI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+ M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI |
+ M98088_DAI_WCI, reg14val);
+
+ reg15val = M98088_DAI_BSEL64;
+ if (max98088->digmic)
+ reg15val |= M98088_DAI_OSR64;
+ snd_soc_write(codec, M98088_REG_15_DAI1_CLOCK, reg15val);
+ }
+
+ return 0;
+}
+
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+ u8 reg1Cval = 0;
+
+ cdata = &max98088->dai[1];
+
+ if (fmt != cdata->fmt) {
+ cdata->fmt = fmt;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* Slave mode PLL */
+ snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+ 0x80);
+ snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+ 0x00);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* Set to master mode */
+ reg1Cval |= M98088_DAI_MAS;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(codec->dev, "Clock mode unsupported");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ reg1Cval |= M98088_DAI_DLY;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ reg1Cval |= M98088_DAI_WCI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ reg1Cval |= M98088_DAI_BCI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ reg1Cval |= M98088_DAI_BCI|M98088_DAI_WCI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+ M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI |
+ M98088_DAI_WCI, reg1Cval);
+
+ snd_soc_write(codec, M98088_REG_1D_DAI2_CLOCK,
+ M98088_DAI_BSEL64);
+ }
+
+ return 0;
+}
+
+static int max98088_dai1_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int reg;
+
+ if (mute)
+ reg = M98088_DAI_MUTE;
+ else
+ reg = 0;
+
+ snd_soc_update_bits(codec, M98088_REG_2F_LVL_DAI1_PLAY,
+ M98088_DAI_MUTE_MASK, reg);
+ return 0;
+}
+
+static int max98088_dai2_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int reg;
+
+ if (mute)
+ reg = M98088_DAI_MUTE;
+ else
+ reg = 0;
+
+ snd_soc_update_bits(codec, M98088_REG_31_LVL_DAI2_PLAY,
+ M98088_DAI_MUTE_MASK, reg);
+ return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+ u16 *reg_cache = codec->reg_cache;
+ int i;
+
+ if (!codec->cache_sync)
+ return;
+
+ codec->cache_only = 0;
+
+ /* write back cached values if they're writeable and
+ * different from the hardware default.
+ */
+ for (i = 1; i < codec->driver->reg_cache_size; i++) {
+ if (!max98088_access[i].writable)
+ continue;
+
+ if (reg_cache[i] == max98088_reg[i])
+ continue;
+
+ snd_soc_write(codec, i, reg_cache[i]);
+ }
+
+ codec->cache_sync = 0;
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF)
+ max98088_sync_cache(codec);
+
+ snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+ M98088_MBEN, M98088_MBEN);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+ M98088_MBEN, 0);
+ codec->cache_sync = 1;
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+ .set_sysclk = max98088_dai_set_sysclk,
+ .set_fmt = max98088_dai1_set_fmt,
+ .hw_params = max98088_dai1_hw_params,
+ .digital_mute = max98088_dai1_digital_mute,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+ .set_sysclk = max98088_dai_set_sysclk,
+ .set_fmt = max98088_dai2_set_fmt,
+ .hw_params = max98088_dai2_hw_params,
+ .digital_mute = max98088_dai2_digital_mute,
+};
+
+static struct snd_soc_dai_driver max98088_dai[] = {
+{
+ .name = "HiFi",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX98088_RATES,
+ .formats = MAX98088_FORMATS,
+ },
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX98088_RATES,
+ .formats = MAX98088_FORMATS,
+ },
+ .ops = &max98088_dai1_ops,
+},
+{
+ .name = "Aux",
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX98088_RATES,
+ .formats = MAX98088_FORMATS,
+ },
+ .ops = &max98088_dai2_ops,
+}
+};
+
+static int max98088_get_channel(const char *name)
+{
+ if (strcmp(name, "EQ1 Mode") == 0)
+ return 0;
+ if (strcmp(name, "EQ2 Mode") == 0)
+ return 1;
+ return -EINVAL;
+}
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ struct max98088_eq_cfg *coef_set;
+ int best, best_val, save, i, sel, fs;
+ struct max98088_cdata *cdata;
+
+ cdata = &max98088->dai[0];
+
+ if (!pdata || !max98088->eq_textcnt)
+ return;
+
+ /* Find the selected configuration with nearest sample rate */
+ fs = cdata->rate;
+ sel = cdata->eq_sel;
+
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < pdata->eq_cfgcnt; i++) {
+ if (strcmp(pdata->eq_cfg[i].name, max98088->eq_texts[sel]) == 0 &&
+ abs(pdata->eq_cfg[i].rate - fs) < best_val) {
+ best = i;
+ best_val = abs(pdata->eq_cfg[i].rate - fs);
+ }
+ }
+
+ dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+ pdata->eq_cfg[best].name,
+ pdata->eq_cfg[best].rate, fs);
+
+ /* Disable EQ while configuring, and save current on/off state */
+ save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+ snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+ coef_set = &pdata->eq_cfg[sel];
+
+ m98088_eq_band(codec, 0, 0, coef_set->band1);
+ m98088_eq_band(codec, 0, 1, coef_set->band2);
+ m98088_eq_band(codec, 0, 2, coef_set->band3);
+ m98088_eq_band(codec, 0, 3, coef_set->band4);
+ m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+ /* Restore the original on/off state */
+ snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ struct max98088_eq_cfg *coef_set;
+ int best, best_val, save, i, sel, fs;
+ struct max98088_cdata *cdata;
+
+ cdata = &max98088->dai[1];
+
+ if (!pdata || !max98088->eq_textcnt)
+ return;
+
+ /* Find the selected configuration with nearest sample rate */
+ fs = cdata->rate;
+
+ sel = cdata->eq_sel;
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < pdata->eq_cfgcnt; i++) {
+ if (strcmp(pdata->eq_cfg[i].name, max98088->eq_texts[sel]) == 0 &&
+ abs(pdata->eq_cfg[i].rate - fs) < best_val) {
+ best = i;
+ best_val = abs(pdata->eq_cfg[i].rate - fs);
+ }
+ }
+
+ dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+ pdata->eq_cfg[best].name,
+ pdata->eq_cfg[best].rate, fs);
+
+ /* Disable EQ while configuring, and save current on/off state */
+ save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+ snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+ coef_set = &pdata->eq_cfg[sel];
+
+ m98088_eq_band(codec, 1, 0, coef_set->band1);
+ m98088_eq_band(codec, 1, 1, coef_set->band2);
+ m98088_eq_band(codec, 1, 2, coef_set->band3);
+ m98088_eq_band(codec, 1, 3, coef_set->band4);
+ m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+ /* Restore the original on/off state */
+ snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+ save);
+}
+
+static int max98088_put_eq_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ int channel = max98088_get_channel(kcontrol->id.name);
+ struct max98088_cdata *cdata;
+ int sel = ucontrol->value.integer.value[0];
+
+ cdata = &max98088->dai[channel];
+
+ if (sel >= pdata->eq_cfgcnt)
+ return -EINVAL;
+
+ cdata->eq_sel = sel;
+
+ switch (channel) {
+ case 0:
+ max98088_setup_eq1(codec);
+ break;
+ case 1:
+ max98088_setup_eq2(codec);
+ break;
+ }
+
+ return 0;
+}
+
+static int max98088_get_eq_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ int channel = max98088_get_channel(kcontrol->id.name);
+ struct max98088_cdata *cdata;
+
+ cdata = &max98088->dai[channel];
+ ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+ return 0;
+}
+
+static void max98088_handle_eq_pdata(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ struct max98088_eq_cfg *cfg;
+ unsigned int cfgcnt;
+ int i, j;
+ const char **t;
+ int ret;
+
+ struct snd_kcontrol_new controls[] = {
+ SOC_ENUM_EXT("EQ1 Mode",
+ max98088->eq_enum,
+ max98088_get_eq_enum,
+ max98088_put_eq_enum),
+ SOC_ENUM_EXT("EQ2 Mode",
+ max98088->eq_enum,
+ max98088_get_eq_enum,
+ max98088_put_eq_enum),
+ };
+
+ cfg = pdata->eq_cfg;
+ cfgcnt = pdata->eq_cfgcnt;
+
+ /* Setup an array of texts for the equalizer enum.
+ * This is based on Mark Brown's equalizer driver code.
+ */
+ max98088->eq_textcnt = 0;
+ max98088->eq_texts = NULL;
+ for (i = 0; i < cfgcnt; i++) {
+ for (j = 0; j < max98088->eq_textcnt; j++) {
+ if (strcmp(cfg[i].name, max98088->eq_texts[j]) == 0)
+ break;
+ }
+
+ if (j != max98088->eq_textcnt)
+ continue;
+
+ /* Expand the array */
+ t = krealloc(max98088->eq_texts,
+ sizeof(char *) * (max98088->eq_textcnt + 1),
+ GFP_KERNEL);
+ if (t == NULL)
+ continue;
+
+ /* Store the new entry */
+ t[max98088->eq_textcnt] = cfg[i].name;
+ max98088->eq_textcnt++;
+ max98088->eq_texts = t;
+ }
+
+ /* Now point the soc_enum to .texts array items */
+ max98088->eq_enum.texts = max98088->eq_texts;
+ max98088->eq_enum.max = max98088->eq_textcnt;
+
+ ret = snd_soc_add_controls(codec, controls, ARRAY_SIZE(controls));
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_pdata(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ u8 regval = 0;
+
+ if (!pdata) {
+ dev_dbg(codec->dev, "No platform data\n");
+ return;
+ }
+
+ /* Configure mic for analog/digital mic mode */
+ if (pdata->digmic_left_mode)
+ regval |= M98088_DIGMIC_L;
+
+ if (pdata->digmic_right_mode)
+ regval |= M98088_DIGMIC_R;
+
+ max98088->digmic = (regval ? 1 : 0);
+
+ snd_soc_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+ /* Configure receiver output */
+ regval = ((pdata->receiver_mode) ? M98088_REC_LINEMODE : 0);
+ snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+ M98088_REC_LINEMODE_MASK, regval);
+
+ /* Configure equalizers */
+ if (pdata->eq_cfgcnt)
+ max98088_handle_eq_pdata(codec);
+}
+
+#ifdef CONFIG_PM
+static int max98088_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int max98088_resume(struct snd_soc_codec *codec)
+{
+ max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define max98088_suspend NULL
+#define max98088_resume NULL
+#endif
+
+static int max98088_probe(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+ int ret = 0;
+
+ codec->cache_sync = 1;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ /* initialize private data */
+
+ max98088->sysclk = (unsigned)-1;
+ max98088->eq_textcnt = 0;
+
+ cdata = &max98088->dai[0];
+ cdata->rate = (unsigned)-1;
+ cdata->fmt = (unsigned)-1;
+ cdata->eq_sel = 0;
+
+ cdata = &max98088->dai[1];
+ cdata->rate = (unsigned)-1;
+ cdata->fmt = (unsigned)-1;
+ cdata->eq_sel = 0;
+
+ max98088->ina_state = 0;
+ max98088->inb_state = 0;
+ max98088->ex_mode = 0;
+ max98088->digmic = 0;
+ max98088->mic1pre = 0;
+ max98088->mic2pre = 0;
+
+ ret = snd_soc_read(codec, M98088_REG_FF_REV_ID);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read device revision: %d\n",
+ ret);
+ goto err_access;
+ }
+ dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+ snd_soc_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+
+ /* initialize registers cache to hardware default */
+ max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+ snd_soc_write(codec, M98088_REG_22_MIX_DAC,
+ M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+ M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+ snd_soc_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+ snd_soc_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+ snd_soc_write(codec, M98088_REG_16_DAI1_IOCFG,
+ M98088_S1NORMAL|M98088_SDATA);
+
+ snd_soc_write(codec, M98088_REG_1E_DAI2_IOCFG,
+ M98088_S2NORMAL|M98088_SDATA);
+
+ max98088_handle_pdata(codec);
+
+ snd_soc_add_controls(codec, max98088_snd_controls,
+ ARRAY_SIZE(max98088_snd_controls));
+
+err_access:
+ return ret;
+}
+
+static int max98088_remove(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+ max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ kfree(max98088->eq_texts);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_max98088 = {
+ .probe = max98088_probe,
+ .remove = max98088_remove,
+ .suspend = max98088_suspend,
+ .resume = max98088_resume,
+ .set_bias_level = max98088_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(max98088_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = max98088_reg,
+ .volatile_register = max98088_volatile_register,
+ .dapm_widgets = max98088_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(max98088_dapm_widgets),
+ .dapm_routes = max98088_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(max98088_audio_map),
+};
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct max98088_priv *max98088;
+ int ret;
+
+ max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+ if (max98088 == NULL)
+ return -ENOMEM;
+
+ max98088->devtype = id->driver_data;
+
+ i2c_set_clientdata(i2c, max98088);
+ max98088->control_data = i2c;
+ max98088->pdata = i2c->dev.platform_data;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_max98088, &max98088_dai[0], 2);
+ if (ret < 0)
+ kfree(max98088);
+ return ret;
+}
+
+static int __devexit max98088_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+ { "max98088", MAX98088 },
+ { "max98089", MAX98089 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+static struct i2c_driver max98088_i2c_driver = {
+ .driver = {
+ .name = "max98088",
+ .owner = THIS_MODULE,
+ },
+ .probe = max98088_i2c_probe,
+ .remove = __devexit_p(max98088_i2c_remove),
+ .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&max98088_i2c_driver);
+ if (ret)
+ pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+ return ret;
+}
+module_init(max98088_init);
+
+static void __exit max98088_exit(void)
+{
+ i2c_del_driver(&max98088_i2c_driver);
+}
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang, Jesse Marroquin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h
new file mode 100644
index 00000000..be89a4f4
--- /dev/null
+++ b/sound/soc/codecs/max98088.h
@@ -0,0 +1,206 @@
+/*
+ * max98088.h -- MAX98088 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS 0x00
+#define M98088_REG_01_MIC_STATUS 0x01
+#define M98088_REG_02_JACK_STAUS 0x02
+#define M98088_REG_03_BATTERY_VOLTAGE 0x03
+#define M98088_REG_0F_IRQ_ENABLE 0x0F
+#define M98088_REG_10_SYS_CLK 0x10
+#define M98088_REG_11_DAI1_CLKMODE 0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI 0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO 0x13
+#define M98088_REG_14_DAI1_FORMAT 0x14
+#define M98088_REG_15_DAI1_CLOCK 0x15
+#define M98088_REG_16_DAI1_IOCFG 0x16
+#define M98088_REG_17_DAI1_TDM 0x17
+#define M98088_REG_18_DAI1_FILTERS 0x18
+#define M98088_REG_19_DAI2_CLKMODE 0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI 0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO 0x1B
+#define M98088_REG_1C_DAI2_FORMAT 0x1C
+#define M98088_REG_1D_DAI2_CLOCK 0x1D
+#define M98088_REG_1E_DAI2_IOCFG 0x1E
+#define M98088_REG_1F_DAI2_TDM 0x1F
+#define M98088_REG_20_DAI2_FILTERS 0x20
+#define M98088_REG_21_SRC 0x21
+#define M98088_REG_22_MIX_DAC 0x22
+#define M98088_REG_23_MIX_ADC_LEFT 0x23
+#define M98088_REG_24_MIX_ADC_RIGHT 0x24
+#define M98088_REG_25_MIX_HP_LEFT 0x25
+#define M98088_REG_26_MIX_HP_RIGHT 0x26
+#define M98088_REG_27_MIX_HP_CNTL 0x27
+#define M98088_REG_28_MIX_REC_LEFT 0x28
+#define M98088_REG_29_MIX_REC_RIGHT 0x29
+#define M98088_REG_2A_MIC_REC_CNTL 0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT 0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT 0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL 0x2D
+#define M98088_REG_2E_LVL_SIDETONE 0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY 0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ 0x30
+#define M98088_REG_31_LVL_DAI2_PLAY 0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ 0x32
+#define M98088_REG_33_LVL_ADC_L 0x33
+#define M98088_REG_34_LVL_ADC_R 0x34
+#define M98088_REG_35_LVL_MIC1 0x35
+#define M98088_REG_36_LVL_MIC2 0x36
+#define M98088_REG_37_LVL_INA 0x37
+#define M98088_REG_38_LVL_INB 0x38
+#define M98088_REG_39_LVL_HP_L 0x39
+#define M98088_REG_3A_LVL_HP_R 0x3A
+#define M98088_REG_3B_LVL_REC_L 0x3B
+#define M98088_REG_3C_LVL_REC_R 0x3C
+#define M98088_REG_3D_LVL_SPK_L 0x3D
+#define M98088_REG_3E_LVL_SPK_R 0x3E
+#define M98088_REG_3F_MICAGC_CFG 0x3F
+#define M98088_REG_40_MICAGC_THRESH 0x40
+#define M98088_REG_41_SPKDHP 0x41
+#define M98088_REG_42_SPKDHP_THRESH 0x42
+#define M98088_REG_43_SPKALC_COMP 0x43
+#define M98088_REG_44_PWRLMT_CFG 0x44
+#define M98088_REG_45_PWRLMT_TIME 0x45
+#define M98088_REG_46_THDLMT_CFG 0x46
+#define M98088_REG_47_CFG_AUDIO_IN 0x47
+#define M98088_REG_48_CFG_MIC 0x48
+#define M98088_REG_49_CFG_LEVEL 0x49
+#define M98088_REG_4A_CFG_BYPASS 0x4A
+#define M98088_REG_4B_CFG_JACKDET 0x4B
+#define M98088_REG_4C_PWR_EN_IN 0x4C
+#define M98088_REG_4D_PWR_EN_OUT 0x4D
+#define M98088_REG_4E_BIAS_CNTL 0x4E
+#define M98088_REG_4F_DAC_BIAS1 0x4F
+#define M98088_REG_50_DAC_BIAS2 0x50
+#define M98088_REG_51_PWR_SYS 0x51
+#define M98088_REG_52_DAI1_EQ_BASE 0x52
+#define M98088_REG_84_DAI2_EQ_BASE 0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE 0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE 0xC0
+#define M98088_REG_FF_REV_ID 0xFF
+
+#define M98088_REG_CNT (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_11_DAI1_CLKMODE, M98088_REG_19_DAI2_CLKMODE */
+ #define M98088_CLKMODE_MASK 0xFF
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+ #define M98088_DAI_MAS (1<<7)
+ #define M98088_DAI_WCI (1<<6)
+ #define M98088_DAI_BCI (1<<5)
+ #define M98088_DAI_DLY (1<<4)
+ #define M98088_DAI_TDM (1<<2)
+ #define M98088_DAI_FSW (1<<1)
+ #define M98088_DAI_WS (1<<0)
+
+/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */
+ #define M98088_DAI_BSEL64 (1<<0)
+ #define M98088_DAI_OSR64 (1<<6)
+
+/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */
+ #define M98088_S1NORMAL (1<<6)
+ #define M98088_S2NORMAL (2<<6)
+ #define M98088_SDATA (3<<0)
+
+/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */
+ #define M98088_DAI_DHF (1<<3)
+
+/* M98088_REG_22_MIX_DAC */
+ #define M98088_DAI1L_TO_DACL (1<<7)
+ #define M98088_DAI1R_TO_DACL (1<<6)
+ #define M98088_DAI2L_TO_DACL (1<<5)
+ #define M98088_DAI2R_TO_DACL (1<<4)
+ #define M98088_DAI1L_TO_DACR (1<<3)
+ #define M98088_DAI1R_TO_DACR (1<<2)
+ #define M98088_DAI2L_TO_DACR (1<<1)
+ #define M98088_DAI2R_TO_DACR (1<<0)
+
+/* M98088_REG_2A_MIC_REC_CNTL */
+ #define M98088_REC_LINEMODE (1<<7)
+ #define M98088_REC_LINEMODE_MASK (1<<7)
+
+/* M98088_REG_2D_MIX_SPK_CNTL */
+ #define M98088_MIX_SPKR_GAIN_MASK (3<<2)
+ #define M98088_MIX_SPKR_GAIN_SHIFT 2
+ #define M98088_MIX_SPKL_GAIN_MASK (3<<0)
+ #define M98088_MIX_SPKL_GAIN_SHIFT 0
+
+/* M98088_REG_2F_LVL_DAI1_PLAY, M98088_REG_31_LVL_DAI2_PLAY */
+ #define M98088_DAI_MUTE (1<<7)
+ #define M98088_DAI_MUTE_MASK (1<<7)
+ #define M98088_DAI_VOICE_GAIN_MASK (3<<4)
+ #define M98088_DAI_ATTENUATION_MASK (0xF<<0)
+ #define M98088_DAI_ATTENUATION_SHIFT 0
+
+/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */
+ #define M98088_MICPRE_MASK (3<<5)
+ #define M98088_MICPRE_SHIFT 5
+
+/* M98088_REG_3A_LVL_HP_R */
+ #define M98088_HP_MUTE (1<<7)
+
+/* M98088_REG_3C_LVL_REC_R */
+ #define M98088_REC_MUTE (1<<7)
+
+/* M98088_REG_3E_LVL_SPK_R */
+ #define M98088_SP_MUTE (1<<7)
+
+/* M98088_REG_48_CFG_MIC */
+ #define M98088_EXTMIC_MASK (3<<0)
+ #define M98088_DIGMIC_L (1<<5)
+ #define M98088_DIGMIC_R (1<<4)
+
+/* M98088_REG_49_CFG_LEVEL */
+ #define M98088_VSEN (1<<6)
+ #define M98088_ZDEN (1<<5)
+ #define M98088_EQ2EN (1<<1)
+ #define M98088_EQ1EN (1<<0)
+
+/* M98088_REG_4C_PWR_EN_IN */
+ #define M98088_INAEN (1<<7)
+ #define M98088_INBEN (1<<6)
+ #define M98088_MBEN (1<<3)
+ #define M98088_ADLEN (1<<1)
+ #define M98088_ADREN (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+ #define M98088_HPLEN (1<<7)
+ #define M98088_HPREN (1<<6)
+ #define M98088_HPEN ((1<<7)|(1<<6))
+ #define M98088_SPLEN (1<<5)
+ #define M98088_SPREN (1<<4)
+ #define M98088_RECEN (1<<3)
+ #define M98088_DALEN (1<<1)
+ #define M98088_DAREN (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+ #define M98088_SHDNRUN (1<<7)
+ #define M98088_PERFMODE (1<<3)
+ #define M98088_HPPLYBACK (1<<2)
+ #define M98088_PWRSV8K (1<<1)
+ #define M98088_PWRSV (1<<0)
+
+/* Line inputs */
+#define LINE_INA 0
+#define LINE_INB 1
+
+#define M98088_COEFS_PER_BAND 5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+#endif
diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c
new file mode 100644
index 00000000..e1d282d4
--- /dev/null
+++ b/sound/soc/codecs/max98095.c
@@ -0,0 +1,2396 @@
+/*
+ * max98095.c -- MAX98095 ALSA SoC Audio driver
+ *
+ * Copyright 2011 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/slab.h>
+#include <asm/div64.h>
+#include <sound/max98095.h>
+#include "max98095.h"
+
+enum max98095_type {
+ MAX98095,
+};
+
+struct max98095_cdata {
+ unsigned int rate;
+ unsigned int fmt;
+ int eq_sel;
+ int bq_sel;
+};
+
+struct max98095_priv {
+ enum max98095_type devtype;
+ void *control_data;
+ struct max98095_pdata *pdata;
+ unsigned int sysclk;
+ struct max98095_cdata dai[3];
+ const char **eq_texts;
+ const char **bq_texts;
+ struct soc_enum eq_enum;
+ struct soc_enum bq_enum;
+ int eq_textcnt;
+ int bq_textcnt;
+ u8 lin_state;
+ unsigned int mic1pre;
+ unsigned int mic2pre;
+};
+
+static const u8 max98095_reg_def[M98095_REG_CNT] = {
+ 0x00, /* 00 */
+ 0x00, /* 01 */
+ 0x00, /* 02 */
+ 0x00, /* 03 */
+ 0x00, /* 04 */
+ 0x00, /* 05 */
+ 0x00, /* 06 */
+ 0x00, /* 07 */
+ 0x00, /* 08 */
+ 0x00, /* 09 */
+ 0x00, /* 0A */
+ 0x00, /* 0B */
+ 0x00, /* 0C */
+ 0x00, /* 0D */
+ 0x00, /* 0E */
+ 0x00, /* 0F */
+ 0x00, /* 10 */
+ 0x00, /* 11 */
+ 0x00, /* 12 */
+ 0x00, /* 13 */
+ 0x00, /* 14 */
+ 0x00, /* 15 */
+ 0x00, /* 16 */
+ 0x00, /* 17 */
+ 0x00, /* 18 */
+ 0x00, /* 19 */
+ 0x00, /* 1A */
+ 0x00, /* 1B */
+ 0x00, /* 1C */
+ 0x00, /* 1D */
+ 0x00, /* 1E */
+ 0x00, /* 1F */
+ 0x00, /* 20 */
+ 0x00, /* 21 */
+ 0x00, /* 22 */
+ 0x00, /* 23 */
+ 0x00, /* 24 */
+ 0x00, /* 25 */
+ 0x00, /* 26 */
+ 0x00, /* 27 */
+ 0x00, /* 28 */
+ 0x00, /* 29 */
+ 0x00, /* 2A */
+ 0x00, /* 2B */
+ 0x00, /* 2C */
+ 0x00, /* 2D */
+ 0x00, /* 2E */
+ 0x00, /* 2F */
+ 0x00, /* 30 */
+ 0x00, /* 31 */
+ 0x00, /* 32 */
+ 0x00, /* 33 */
+ 0x00, /* 34 */
+ 0x00, /* 35 */
+ 0x00, /* 36 */
+ 0x00, /* 37 */
+ 0x00, /* 38 */
+ 0x00, /* 39 */
+ 0x00, /* 3A */
+ 0x00, /* 3B */
+ 0x00, /* 3C */
+ 0x00, /* 3D */
+ 0x00, /* 3E */
+ 0x00, /* 3F */
+ 0x00, /* 40 */
+ 0x00, /* 41 */
+ 0x00, /* 42 */
+ 0x00, /* 43 */
+ 0x00, /* 44 */
+ 0x00, /* 45 */
+ 0x00, /* 46 */
+ 0x00, /* 47 */
+ 0x00, /* 48 */
+ 0x00, /* 49 */
+ 0x00, /* 4A */
+ 0x00, /* 4B */
+ 0x00, /* 4C */
+ 0x00, /* 4D */
+ 0x00, /* 4E */
+ 0x00, /* 4F */
+ 0x00, /* 50 */
+ 0x00, /* 51 */
+ 0x00, /* 52 */
+ 0x00, /* 53 */
+ 0x00, /* 54 */
+ 0x00, /* 55 */
+ 0x00, /* 56 */
+ 0x00, /* 57 */
+ 0x00, /* 58 */
+ 0x00, /* 59 */
+ 0x00, /* 5A */
+ 0x00, /* 5B */
+ 0x00, /* 5C */
+ 0x00, /* 5D */
+ 0x00, /* 5E */
+ 0x00, /* 5F */
+ 0x00, /* 60 */
+ 0x00, /* 61 */
+ 0x00, /* 62 */
+ 0x00, /* 63 */
+ 0x00, /* 64 */
+ 0x00, /* 65 */
+ 0x00, /* 66 */
+ 0x00, /* 67 */
+ 0x00, /* 68 */
+ 0x00, /* 69 */
+ 0x00, /* 6A */
+ 0x00, /* 6B */
+ 0x00, /* 6C */
+ 0x00, /* 6D */
+ 0x00, /* 6E */
+ 0x00, /* 6F */
+ 0x00, /* 70 */
+ 0x00, /* 71 */
+ 0x00, /* 72 */
+ 0x00, /* 73 */
+ 0x00, /* 74 */
+ 0x00, /* 75 */
+ 0x00, /* 76 */
+ 0x00, /* 77 */
+ 0x00, /* 78 */
+ 0x00, /* 79 */
+ 0x00, /* 7A */
+ 0x00, /* 7B */
+ 0x00, /* 7C */
+ 0x00, /* 7D */
+ 0x00, /* 7E */
+ 0x00, /* 7F */
+ 0x00, /* 80 */
+ 0x00, /* 81 */
+ 0x00, /* 82 */
+ 0x00, /* 83 */
+ 0x00, /* 84 */
+ 0x00, /* 85 */
+ 0x00, /* 86 */
+ 0x00, /* 87 */
+ 0x00, /* 88 */
+ 0x00, /* 89 */
+ 0x00, /* 8A */
+ 0x00, /* 8B */
+ 0x00, /* 8C */
+ 0x00, /* 8D */
+ 0x00, /* 8E */
+ 0x00, /* 8F */
+ 0x00, /* 90 */
+ 0x00, /* 91 */
+ 0x30, /* 92 */
+ 0xF0, /* 93 */
+ 0x00, /* 94 */
+ 0x00, /* 95 */
+ 0x3F, /* 96 */
+ 0x00, /* 97 */
+ 0x00, /* 98 */
+ 0x00, /* 99 */
+ 0x00, /* 9A */
+ 0x00, /* 9B */
+ 0x00, /* 9C */
+ 0x00, /* 9D */
+ 0x00, /* 9E */
+ 0x00, /* 9F */
+ 0x00, /* A0 */
+ 0x00, /* A1 */
+ 0x00, /* A2 */
+ 0x00, /* A3 */
+ 0x00, /* A4 */
+ 0x00, /* A5 */
+ 0x00, /* A6 */
+ 0x00, /* A7 */
+ 0x00, /* A8 */
+ 0x00, /* A9 */
+ 0x00, /* AA */
+ 0x00, /* AB */
+ 0x00, /* AC */
+ 0x00, /* AD */
+ 0x00, /* AE */
+ 0x00, /* AF */
+ 0x00, /* B0 */
+ 0x00, /* B1 */
+ 0x00, /* B2 */
+ 0x00, /* B3 */
+ 0x00, /* B4 */
+ 0x00, /* B5 */
+ 0x00, /* B6 */
+ 0x00, /* B7 */
+ 0x00, /* B8 */
+ 0x00, /* B9 */
+ 0x00, /* BA */
+ 0x00, /* BB */
+ 0x00, /* BC */
+ 0x00, /* BD */
+ 0x00, /* BE */
+ 0x00, /* BF */
+ 0x00, /* C0 */
+ 0x00, /* C1 */
+ 0x00, /* C2 */
+ 0x00, /* C3 */
+ 0x00, /* C4 */
+ 0x00, /* C5 */
+ 0x00, /* C6 */
+ 0x00, /* C7 */
+ 0x00, /* C8 */
+ 0x00, /* C9 */
+ 0x00, /* CA */
+ 0x00, /* CB */
+ 0x00, /* CC */
+ 0x00, /* CD */
+ 0x00, /* CE */
+ 0x00, /* CF */
+ 0x00, /* D0 */
+ 0x00, /* D1 */
+ 0x00, /* D2 */
+ 0x00, /* D3 */
+ 0x00, /* D4 */
+ 0x00, /* D5 */
+ 0x00, /* D6 */
+ 0x00, /* D7 */
+ 0x00, /* D8 */
+ 0x00, /* D9 */
+ 0x00, /* DA */
+ 0x00, /* DB */
+ 0x00, /* DC */
+ 0x00, /* DD */
+ 0x00, /* DE */
+ 0x00, /* DF */
+ 0x00, /* E0 */
+ 0x00, /* E1 */
+ 0x00, /* E2 */
+ 0x00, /* E3 */
+ 0x00, /* E4 */
+ 0x00, /* E5 */
+ 0x00, /* E6 */
+ 0x00, /* E7 */
+ 0x00, /* E8 */
+ 0x00, /* E9 */
+ 0x00, /* EA */
+ 0x00, /* EB */
+ 0x00, /* EC */
+ 0x00, /* ED */
+ 0x00, /* EE */
+ 0x00, /* EF */
+ 0x00, /* F0 */
+ 0x00, /* F1 */
+ 0x00, /* F2 */
+ 0x00, /* F3 */
+ 0x00, /* F4 */
+ 0x00, /* F5 */
+ 0x00, /* F6 */
+ 0x00, /* F7 */
+ 0x00, /* F8 */
+ 0x00, /* F9 */
+ 0x00, /* FA */
+ 0x00, /* FB */
+ 0x00, /* FC */
+ 0x00, /* FD */
+ 0x00, /* FE */
+ 0x00, /* FF */
+};
+
+static struct {
+ int readable;
+ int writable;
+} max98095_access[M98095_REG_CNT] = {
+ { 0x00, 0x00 }, /* 00 */
+ { 0xFF, 0x00 }, /* 01 */
+ { 0xFF, 0x00 }, /* 02 */
+ { 0xFF, 0x00 }, /* 03 */
+ { 0xFF, 0x00 }, /* 04 */
+ { 0xFF, 0x00 }, /* 05 */
+ { 0xFF, 0x00 }, /* 06 */
+ { 0xFF, 0x00 }, /* 07 */
+ { 0xFF, 0x00 }, /* 08 */
+ { 0xFF, 0x00 }, /* 09 */
+ { 0xFF, 0x00 }, /* 0A */
+ { 0xFF, 0x00 }, /* 0B */
+ { 0xFF, 0x00 }, /* 0C */
+ { 0xFF, 0x00 }, /* 0D */
+ { 0xFF, 0x00 }, /* 0E */
+ { 0xFF, 0x9F }, /* 0F */
+ { 0xFF, 0xFF }, /* 10 */
+ { 0xFF, 0xFF }, /* 11 */
+ { 0xFF, 0xFF }, /* 12 */
+ { 0xFF, 0xFF }, /* 13 */
+ { 0xFF, 0xFF }, /* 14 */
+ { 0xFF, 0xFF }, /* 15 */
+ { 0xFF, 0xFF }, /* 16 */
+ { 0xFF, 0xFF }, /* 17 */
+ { 0xFF, 0xFF }, /* 18 */
+ { 0xFF, 0xFF }, /* 19 */
+ { 0xFF, 0xFF }, /* 1A */
+ { 0xFF, 0xFF }, /* 1B */
+ { 0xFF, 0xFF }, /* 1C */
+ { 0xFF, 0xFF }, /* 1D */
+ { 0xFF, 0x77 }, /* 1E */
+ { 0xFF, 0x77 }, /* 1F */
+ { 0xFF, 0x77 }, /* 20 */
+ { 0xFF, 0x77 }, /* 21 */
+ { 0xFF, 0x77 }, /* 22 */
+ { 0xFF, 0x77 }, /* 23 */
+ { 0xFF, 0xFF }, /* 24 */
+ { 0xFF, 0x7F }, /* 25 */
+ { 0xFF, 0x31 }, /* 26 */
+ { 0xFF, 0xFF }, /* 27 */
+ { 0xFF, 0xFF }, /* 28 */
+ { 0xFF, 0xFF }, /* 29 */
+ { 0xFF, 0xF7 }, /* 2A */
+ { 0xFF, 0x2F }, /* 2B */
+ { 0xFF, 0xEF }, /* 2C */
+ { 0xFF, 0xFF }, /* 2D */
+ { 0xFF, 0xFF }, /* 2E */
+ { 0xFF, 0xFF }, /* 2F */
+ { 0xFF, 0xFF }, /* 30 */
+ { 0xFF, 0xFF }, /* 31 */
+ { 0xFF, 0xFF }, /* 32 */
+ { 0xFF, 0xFF }, /* 33 */
+ { 0xFF, 0xF7 }, /* 34 */
+ { 0xFF, 0x2F }, /* 35 */
+ { 0xFF, 0xCF }, /* 36 */
+ { 0xFF, 0xFF }, /* 37 */
+ { 0xFF, 0xFF }, /* 38 */
+ { 0xFF, 0xFF }, /* 39 */
+ { 0xFF, 0xFF }, /* 3A */
+ { 0xFF, 0xFF }, /* 3B */
+ { 0xFF, 0xFF }, /* 3C */
+ { 0xFF, 0xFF }, /* 3D */
+ { 0xFF, 0xF7 }, /* 3E */
+ { 0xFF, 0x2F }, /* 3F */
+ { 0xFF, 0xCF }, /* 40 */
+ { 0xFF, 0xFF }, /* 41 */
+ { 0xFF, 0x77 }, /* 42 */
+ { 0xFF, 0xFF }, /* 43 */
+ { 0xFF, 0xFF }, /* 44 */
+ { 0xFF, 0xFF }, /* 45 */
+ { 0xFF, 0xFF }, /* 46 */
+ { 0xFF, 0xFF }, /* 47 */
+ { 0xFF, 0xFF }, /* 48 */
+ { 0xFF, 0x0F }, /* 49 */
+ { 0xFF, 0xFF }, /* 4A */
+ { 0xFF, 0xFF }, /* 4B */
+ { 0xFF, 0x3F }, /* 4C */
+ { 0xFF, 0x3F }, /* 4D */
+ { 0xFF, 0x3F }, /* 4E */
+ { 0xFF, 0xFF }, /* 4F */
+ { 0xFF, 0x7F }, /* 50 */
+ { 0xFF, 0x7F }, /* 51 */
+ { 0xFF, 0x0F }, /* 52 */
+ { 0xFF, 0x3F }, /* 53 */
+ { 0xFF, 0x3F }, /* 54 */
+ { 0xFF, 0x3F }, /* 55 */
+ { 0xFF, 0xFF }, /* 56 */
+ { 0xFF, 0xFF }, /* 57 */
+ { 0xFF, 0xBF }, /* 58 */
+ { 0xFF, 0x1F }, /* 59 */
+ { 0xFF, 0xBF }, /* 5A */
+ { 0xFF, 0x1F }, /* 5B */
+ { 0xFF, 0xBF }, /* 5C */
+ { 0xFF, 0x3F }, /* 5D */
+ { 0xFF, 0x3F }, /* 5E */
+ { 0xFF, 0x7F }, /* 5F */
+ { 0xFF, 0x7F }, /* 60 */
+ { 0xFF, 0x47 }, /* 61 */
+ { 0xFF, 0x9F }, /* 62 */
+ { 0xFF, 0x9F }, /* 63 */
+ { 0xFF, 0x9F }, /* 64 */
+ { 0xFF, 0x9F }, /* 65 */
+ { 0xFF, 0x9F }, /* 66 */
+ { 0xFF, 0xBF }, /* 67 */
+ { 0xFF, 0xBF }, /* 68 */
+ { 0xFF, 0xFF }, /* 69 */
+ { 0xFF, 0xFF }, /* 6A */
+ { 0xFF, 0x7F }, /* 6B */
+ { 0xFF, 0xF7 }, /* 6C */
+ { 0xFF, 0xFF }, /* 6D */
+ { 0xFF, 0xFF }, /* 6E */
+ { 0xFF, 0x1F }, /* 6F */
+ { 0xFF, 0xF7 }, /* 70 */
+ { 0xFF, 0xFF }, /* 71 */
+ { 0xFF, 0xFF }, /* 72 */
+ { 0xFF, 0x1F }, /* 73 */
+ { 0xFF, 0xF7 }, /* 74 */
+ { 0xFF, 0xFF }, /* 75 */
+ { 0xFF, 0xFF }, /* 76 */
+ { 0xFF, 0x1F }, /* 77 */
+ { 0xFF, 0xF7 }, /* 78 */
+ { 0xFF, 0xFF }, /* 79 */
+ { 0xFF, 0xFF }, /* 7A */
+ { 0xFF, 0x1F }, /* 7B */
+ { 0xFF, 0xF7 }, /* 7C */
+ { 0xFF, 0xFF }, /* 7D */
+ { 0xFF, 0xFF }, /* 7E */
+ { 0xFF, 0x1F }, /* 7F */
+ { 0xFF, 0xF7 }, /* 80 */
+ { 0xFF, 0xFF }, /* 81 */
+ { 0xFF, 0xFF }, /* 82 */
+ { 0xFF, 0x1F }, /* 83 */
+ { 0xFF, 0x7F }, /* 84 */
+ { 0xFF, 0x0F }, /* 85 */
+ { 0xFF, 0xD8 }, /* 86 */
+ { 0xFF, 0xFF }, /* 87 */
+ { 0xFF, 0xEF }, /* 88 */
+ { 0xFF, 0xFE }, /* 89 */
+ { 0xFF, 0xFE }, /* 8A */
+ { 0xFF, 0xFF }, /* 8B */
+ { 0xFF, 0xFF }, /* 8C */
+ { 0xFF, 0x3F }, /* 8D */
+ { 0xFF, 0xFF }, /* 8E */
+ { 0xFF, 0x3F }, /* 8F */
+ { 0xFF, 0x8F }, /* 90 */
+ { 0xFF, 0xFF }, /* 91 */
+ { 0xFF, 0x3F }, /* 92 */
+ { 0xFF, 0xFF }, /* 93 */
+ { 0xFF, 0xFF }, /* 94 */
+ { 0xFF, 0x0F }, /* 95 */
+ { 0xFF, 0x3F }, /* 96 */
+ { 0xFF, 0x8C }, /* 97 */
+ { 0x00, 0x00 }, /* 98 */
+ { 0x00, 0x00 }, /* 99 */
+ { 0x00, 0x00 }, /* 9A */
+ { 0x00, 0x00 }, /* 9B */
+ { 0x00, 0x00 }, /* 9C */
+ { 0x00, 0x00 }, /* 9D */
+ { 0x00, 0x00 }, /* 9E */
+ { 0x00, 0x00 }, /* 9F */
+ { 0x00, 0x00 }, /* A0 */
+ { 0x00, 0x00 }, /* A1 */
+ { 0x00, 0x00 }, /* A2 */
+ { 0x00, 0x00 }, /* A3 */
+ { 0x00, 0x00 }, /* A4 */
+ { 0x00, 0x00 }, /* A5 */
+ { 0x00, 0x00 }, /* A6 */
+ { 0x00, 0x00 }, /* A7 */
+ { 0x00, 0x00 }, /* A8 */
+ { 0x00, 0x00 }, /* A9 */
+ { 0x00, 0x00 }, /* AA */
+ { 0x00, 0x00 }, /* AB */
+ { 0x00, 0x00 }, /* AC */
+ { 0x00, 0x00 }, /* AD */
+ { 0x00, 0x00 }, /* AE */
+ { 0x00, 0x00 }, /* AF */
+ { 0x00, 0x00 }, /* B0 */
+ { 0x00, 0x00 }, /* B1 */
+ { 0x00, 0x00 }, /* B2 */
+ { 0x00, 0x00 }, /* B3 */
+ { 0x00, 0x00 }, /* B4 */
+ { 0x00, 0x00 }, /* B5 */
+ { 0x00, 0x00 }, /* B6 */
+ { 0x00, 0x00 }, /* B7 */
+ { 0x00, 0x00 }, /* B8 */
+ { 0x00, 0x00 }, /* B9 */
+ { 0x00, 0x00 }, /* BA */
+ { 0x00, 0x00 }, /* BB */
+ { 0x00, 0x00 }, /* BC */
+ { 0x00, 0x00 }, /* BD */
+ { 0x00, 0x00 }, /* BE */
+ { 0x00, 0x00 }, /* BF */
+ { 0x00, 0x00 }, /* C0 */
+ { 0x00, 0x00 }, /* C1 */
+ { 0x00, 0x00 }, /* C2 */
+ { 0x00, 0x00 }, /* C3 */
+ { 0x00, 0x00 }, /* C4 */
+ { 0x00, 0x00 }, /* C5 */
+ { 0x00, 0x00 }, /* C6 */
+ { 0x00, 0x00 }, /* C7 */
+ { 0x00, 0x00 }, /* C8 */
+ { 0x00, 0x00 }, /* C9 */
+ { 0x00, 0x00 }, /* CA */
+ { 0x00, 0x00 }, /* CB */
+ { 0x00, 0x00 }, /* CC */
+ { 0x00, 0x00 }, /* CD */
+ { 0x00, 0x00 }, /* CE */
+ { 0x00, 0x00 }, /* CF */
+ { 0x00, 0x00 }, /* D0 */
+ { 0x00, 0x00 }, /* D1 */
+ { 0x00, 0x00 }, /* D2 */
+ { 0x00, 0x00 }, /* D3 */
+ { 0x00, 0x00 }, /* D4 */
+ { 0x00, 0x00 }, /* D5 */
+ { 0x00, 0x00 }, /* D6 */
+ { 0x00, 0x00 }, /* D7 */
+ { 0x00, 0x00 }, /* D8 */
+ { 0x00, 0x00 }, /* D9 */
+ { 0x00, 0x00 }, /* DA */
+ { 0x00, 0x00 }, /* DB */
+ { 0x00, 0x00 }, /* DC */
+ { 0x00, 0x00 }, /* DD */
+ { 0x00, 0x00 }, /* DE */
+ { 0x00, 0x00 }, /* DF */
+ { 0x00, 0x00 }, /* E0 */
+ { 0x00, 0x00 }, /* E1 */
+ { 0x00, 0x00 }, /* E2 */
+ { 0x00, 0x00 }, /* E3 */
+ { 0x00, 0x00 }, /* E4 */
+ { 0x00, 0x00 }, /* E5 */
+ { 0x00, 0x00 }, /* E6 */
+ { 0x00, 0x00 }, /* E7 */
+ { 0x00, 0x00 }, /* E8 */
+ { 0x00, 0x00 }, /* E9 */
+ { 0x00, 0x00 }, /* EA */
+ { 0x00, 0x00 }, /* EB */
+ { 0x00, 0x00 }, /* EC */
+ { 0x00, 0x00 }, /* ED */
+ { 0x00, 0x00 }, /* EE */
+ { 0x00, 0x00 }, /* EF */
+ { 0x00, 0x00 }, /* F0 */
+ { 0x00, 0x00 }, /* F1 */
+ { 0x00, 0x00 }, /* F2 */
+ { 0x00, 0x00 }, /* F3 */
+ { 0x00, 0x00 }, /* F4 */
+ { 0x00, 0x00 }, /* F5 */
+ { 0x00, 0x00 }, /* F6 */
+ { 0x00, 0x00 }, /* F7 */
+ { 0x00, 0x00 }, /* F8 */
+ { 0x00, 0x00 }, /* F9 */
+ { 0x00, 0x00 }, /* FA */
+ { 0x00, 0x00 }, /* FB */
+ { 0x00, 0x00 }, /* FC */
+ { 0x00, 0x00 }, /* FD */
+ { 0x00, 0x00 }, /* FE */
+ { 0xFF, 0x00 }, /* FF */
+};
+
+static int max98095_readable(struct snd_soc_codec *codec, unsigned int reg)
+{
+ if (reg >= M98095_REG_CNT)
+ return 0;
+ return max98095_access[reg].readable != 0;
+}
+
+static int max98095_volatile(struct snd_soc_codec *codec, unsigned int reg)
+{
+ if (reg > M98095_REG_MAX_CACHED)
+ return 1;
+
+ switch (reg) {
+ case M98095_000_HOST_DATA:
+ case M98095_001_HOST_INT_STS:
+ case M98095_002_HOST_RSP_STS:
+ case M98095_003_HOST_CMD_STS:
+ case M98095_004_CODEC_STS:
+ case M98095_005_DAI1_ALC_STS:
+ case M98095_006_DAI2_ALC_STS:
+ case M98095_007_JACK_AUTO_STS:
+ case M98095_008_JACK_MANUAL_STS:
+ case M98095_009_JACK_VBAT_STS:
+ case M98095_00A_ACC_ADC_STS:
+ case M98095_00B_MIC_NG_AGC_STS:
+ case M98095_00C_SPK_L_VOLT_STS:
+ case M98095_00D_SPK_R_VOLT_STS:
+ case M98095_00E_TEMP_SENSOR_STS:
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Filter coefficients are in a separate register segment
+ * and they share the address space of the normal registers.
+ * The coefficient registers do not need or share the cache.
+ */
+static int max98095_hw_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ data[0] = reg;
+ data[1] = value;
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+static void m98095_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+ unsigned int band, u16 *coefs)
+{
+ unsigned int eq_reg;
+ unsigned int i;
+
+ BUG_ON(band > 4);
+ BUG_ON(dai > 1);
+
+ /* Load the base register address */
+ eq_reg = dai ? M98095_142_DAI2_EQ_BASE : M98095_110_DAI1_EQ_BASE;
+
+ /* Add the band address offset, note adjustment for word address */
+ eq_reg += band * (M98095_COEFS_PER_BAND << 1);
+
+ /* Step through the registers and coefs */
+ for (i = 0; i < M98095_COEFS_PER_BAND; i++) {
+ max98095_hw_write(codec, eq_reg++, M98095_BYTE1(coefs[i]));
+ max98095_hw_write(codec, eq_reg++, M98095_BYTE0(coefs[i]));
+ }
+}
+
+/*
+ * Load biquad filter coefficient configurations registers
+ */
+static void m98095_biquad_band(struct snd_soc_codec *codec, unsigned int dai,
+ unsigned int band, u16 *coefs)
+{
+ unsigned int bq_reg;
+ unsigned int i;
+
+ BUG_ON(band > 1);
+ BUG_ON(dai > 1);
+
+ /* Load the base register address */
+ bq_reg = dai ? M98095_17E_DAI2_BQ_BASE : M98095_174_DAI1_BQ_BASE;
+
+ /* Add the band address offset, note adjustment for word address */
+ bq_reg += band * (M98095_COEFS_PER_BAND << 1);
+
+ /* Step through the registers and coefs */
+ for (i = 0; i < M98095_COEFS_PER_BAND; i++) {
+ max98095_hw_write(codec, bq_reg++, M98095_BYTE1(coefs[i]));
+ max98095_hw_write(codec, bq_reg++, M98095_BYTE0(coefs[i]));
+ }
+}
+
+static const char * const max98095_fltr_mode[] = { "Voice", "Music" };
+static const struct soc_enum max98095_dai1_filter_mode_enum[] = {
+ SOC_ENUM_SINGLE(M98095_02E_DAI1_FILTERS, 7, 2, max98095_fltr_mode),
+};
+static const struct soc_enum max98095_dai2_filter_mode_enum[] = {
+ SOC_ENUM_SINGLE(M98095_038_DAI2_FILTERS, 7, 2, max98095_fltr_mode),
+};
+
+static const char * const max98095_extmic_text[] = { "None", "MIC1", "MIC2" };
+
+static const struct soc_enum max98095_extmic_enum =
+ SOC_ENUM_SINGLE(M98095_087_CFG_MIC, 0, 3, max98095_extmic_text);
+
+static const struct snd_kcontrol_new max98095_extmic_mux =
+ SOC_DAPM_ENUM("External MIC Mux", max98095_extmic_enum);
+
+static const char * const max98095_linein_text[] = { "INA", "INB" };
+
+static const struct soc_enum max98095_linein_enum =
+ SOC_ENUM_SINGLE(M98095_086_CFG_LINE, 6, 2, max98095_linein_text);
+
+static const struct snd_kcontrol_new max98095_linein_mux =
+ SOC_DAPM_ENUM("Linein Input Mux", max98095_linein_enum);
+
+static const char * const max98095_line_mode_text[] = {
+ "Stereo", "Differential"};
+
+static const struct soc_enum max98095_linein_mode_enum =
+ SOC_ENUM_SINGLE(M98095_086_CFG_LINE, 7, 2, max98095_line_mode_text);
+
+static const struct soc_enum max98095_lineout_mode_enum =
+ SOC_ENUM_SINGLE(M98095_086_CFG_LINE, 4, 2, max98095_line_mode_text);
+
+static const char * const max98095_dai_fltr[] = {
+ "Off", "Elliptical-HPF-16k", "Butterworth-HPF-16k",
+ "Elliptical-HPF-8k", "Butterworth-HPF-8k", "Butterworth-HPF-Fs/240"};
+static const struct soc_enum max98095_dai1_dac_filter_enum[] = {
+ SOC_ENUM_SINGLE(M98095_02E_DAI1_FILTERS, 0, 6, max98095_dai_fltr),
+};
+static const struct soc_enum max98095_dai2_dac_filter_enum[] = {
+ SOC_ENUM_SINGLE(M98095_038_DAI2_FILTERS, 0, 6, max98095_dai_fltr),
+};
+static const struct soc_enum max98095_dai3_dac_filter_enum[] = {
+ SOC_ENUM_SINGLE(M98095_042_DAI3_FILTERS, 0, 6, max98095_dai_fltr),
+};
+
+static int max98095_mic1pre_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ unsigned int sel = ucontrol->value.integer.value[0];
+
+ max98095->mic1pre = sel;
+ snd_soc_update_bits(codec, M98095_05F_LVL_MIC1, M98095_MICPRE_MASK,
+ (1+sel)<<M98095_MICPRE_SHIFT);
+
+ return 0;
+}
+
+static int max98095_mic1pre_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = max98095->mic1pre;
+ return 0;
+}
+
+static int max98095_mic2pre_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ unsigned int sel = ucontrol->value.integer.value[0];
+
+ max98095->mic2pre = sel;
+ snd_soc_update_bits(codec, M98095_060_LVL_MIC2, M98095_MICPRE_MASK,
+ (1+sel)<<M98095_MICPRE_SHIFT);
+
+ return 0;
+}
+
+static int max98095_mic2pre_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = max98095->mic2pre;
+ return 0;
+}
+
+static const unsigned int max98095_micboost_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 1, TLV_DB_SCALE_ITEM(0, 2000, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+
+static const DECLARE_TLV_DB_SCALE(max98095_mic_tlv, 0, 100, 0);
+static const DECLARE_TLV_DB_SCALE(max98095_adc_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(max98095_adcboost_tlv, 0, 600, 0);
+
+static const unsigned int max98095_hp_tlv[] = {
+ TLV_DB_RANGE_HEAD(5),
+ 0, 6, TLV_DB_SCALE_ITEM(-6700, 400, 0),
+ 7, 14, TLV_DB_SCALE_ITEM(-4000, 300, 0),
+ 15, 21, TLV_DB_SCALE_ITEM(-1700, 200, 0),
+ 22, 27, TLV_DB_SCALE_ITEM(-400, 100, 0),
+ 28, 31, TLV_DB_SCALE_ITEM(150, 50, 0),
+};
+
+static const unsigned int max98095_spk_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 10, TLV_DB_SCALE_ITEM(-5900, 400, 0),
+ 11, 18, TLV_DB_SCALE_ITEM(-1700, 200, 0),
+ 19, 27, TLV_DB_SCALE_ITEM(-200, 100, 0),
+ 28, 39, TLV_DB_SCALE_ITEM(650, 50, 0),
+};
+
+static const unsigned int max98095_rcv_lout_tlv[] = {
+ TLV_DB_RANGE_HEAD(5),
+ 0, 6, TLV_DB_SCALE_ITEM(-6200, 400, 0),
+ 7, 14, TLV_DB_SCALE_ITEM(-3500, 300, 0),
+ 15, 21, TLV_DB_SCALE_ITEM(-1200, 200, 0),
+ 22, 27, TLV_DB_SCALE_ITEM(100, 100, 0),
+ 28, 31, TLV_DB_SCALE_ITEM(650, 50, 0),
+};
+
+static const unsigned int max98095_lin_tlv[] = {
+ TLV_DB_RANGE_HEAD(3),
+ 0, 2, TLV_DB_SCALE_ITEM(-600, 300, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(300, 1100, 0),
+ 4, 5, TLV_DB_SCALE_ITEM(1400, 600, 0),
+};
+
+static const struct snd_kcontrol_new max98095_snd_controls[] = {
+
+ SOC_DOUBLE_R_TLV("Headphone Volume", M98095_064_LVL_HP_L,
+ M98095_065_LVL_HP_R, 0, 31, 0, max98095_hp_tlv),
+
+ SOC_DOUBLE_R_TLV("Speaker Volume", M98095_067_LVL_SPK_L,
+ M98095_068_LVL_SPK_R, 0, 39, 0, max98095_spk_tlv),
+
+ SOC_SINGLE_TLV("Receiver Volume", M98095_066_LVL_RCV,
+ 0, 31, 0, max98095_rcv_lout_tlv),
+
+ SOC_DOUBLE_R_TLV("Lineout Volume", M98095_062_LVL_LINEOUT1,
+ M98095_063_LVL_LINEOUT2, 0, 31, 0, max98095_rcv_lout_tlv),
+
+ SOC_DOUBLE_R("Headphone Switch", M98095_064_LVL_HP_L,
+ M98095_065_LVL_HP_R, 7, 1, 1),
+
+ SOC_DOUBLE_R("Speaker Switch", M98095_067_LVL_SPK_L,
+ M98095_068_LVL_SPK_R, 7, 1, 1),
+
+ SOC_SINGLE("Receiver Switch", M98095_066_LVL_RCV, 7, 1, 1),
+
+ SOC_DOUBLE_R("Lineout Switch", M98095_062_LVL_LINEOUT1,
+ M98095_063_LVL_LINEOUT2, 7, 1, 1),
+
+ SOC_SINGLE_TLV("MIC1 Volume", M98095_05F_LVL_MIC1, 0, 20, 1,
+ max98095_mic_tlv),
+
+ SOC_SINGLE_TLV("MIC2 Volume", M98095_060_LVL_MIC2, 0, 20, 1,
+ max98095_mic_tlv),
+
+ SOC_SINGLE_EXT_TLV("MIC1 Boost Volume",
+ M98095_05F_LVL_MIC1, 5, 2, 0,
+ max98095_mic1pre_get, max98095_mic1pre_set,
+ max98095_micboost_tlv),
+ SOC_SINGLE_EXT_TLV("MIC2 Boost Volume",
+ M98095_060_LVL_MIC2, 5, 2, 0,
+ max98095_mic2pre_get, max98095_mic2pre_set,
+ max98095_micboost_tlv),
+
+ SOC_SINGLE_TLV("Linein Volume", M98095_061_LVL_LINEIN, 0, 5, 1,
+ max98095_lin_tlv),
+
+ SOC_SINGLE_TLV("ADCL Volume", M98095_05D_LVL_ADC_L, 0, 15, 1,
+ max98095_adc_tlv),
+ SOC_SINGLE_TLV("ADCR Volume", M98095_05E_LVL_ADC_R, 0, 15, 1,
+ max98095_adc_tlv),
+
+ SOC_SINGLE_TLV("ADCL Boost Volume", M98095_05D_LVL_ADC_L, 4, 3, 0,
+ max98095_adcboost_tlv),
+ SOC_SINGLE_TLV("ADCR Boost Volume", M98095_05E_LVL_ADC_R, 4, 3, 0,
+ max98095_adcboost_tlv),
+
+ SOC_SINGLE("EQ1 Switch", M98095_088_CFG_LEVEL, 0, 1, 0),
+ SOC_SINGLE("EQ2 Switch", M98095_088_CFG_LEVEL, 1, 1, 0),
+
+ SOC_SINGLE("Biquad1 Switch", M98095_088_CFG_LEVEL, 2, 1, 0),
+ SOC_SINGLE("Biquad2 Switch", M98095_088_CFG_LEVEL, 3, 1, 0),
+
+ SOC_ENUM("DAI1 Filter Mode", max98095_dai1_filter_mode_enum),
+ SOC_ENUM("DAI2 Filter Mode", max98095_dai2_filter_mode_enum),
+ SOC_ENUM("DAI1 DAC Filter", max98095_dai1_dac_filter_enum),
+ SOC_ENUM("DAI2 DAC Filter", max98095_dai2_dac_filter_enum),
+ SOC_ENUM("DAI3 DAC Filter", max98095_dai3_dac_filter_enum),
+
+ SOC_ENUM("Linein Mode", max98095_linein_mode_enum),
+ SOC_ENUM("Lineout Mode", max98095_lineout_mode_enum),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98095_left_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_050_MIX_SPK_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_050_MIX_SPK_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("Mono DAC2 Switch", M98095_050_MIX_SPK_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("Mono DAC3 Switch", M98095_050_MIX_SPK_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98095_050_MIX_SPK_LEFT, 4, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98095_050_MIX_SPK_LEFT, 5, 1, 0),
+ SOC_DAPM_SINGLE("IN1 Switch", M98095_050_MIX_SPK_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Switch", M98095_050_MIX_SPK_LEFT, 2, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98095_right_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_051_MIX_SPK_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_051_MIX_SPK_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Mono DAC2 Switch", M98095_051_MIX_SPK_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("Mono DAC3 Switch", M98095_051_MIX_SPK_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98095_051_MIX_SPK_RIGHT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98095_051_MIX_SPK_RIGHT, 4, 1, 0),
+ SOC_DAPM_SINGLE("IN1 Switch", M98095_051_MIX_SPK_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Switch", M98095_051_MIX_SPK_RIGHT, 2, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98095_left_hp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_04C_MIX_HP_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_04C_MIX_HP_LEFT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98095_04C_MIX_HP_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98095_04C_MIX_HP_LEFT, 4, 1, 0),
+ SOC_DAPM_SINGLE("IN1 Switch", M98095_04C_MIX_HP_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Switch", M98095_04C_MIX_HP_LEFT, 2, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98095_right_hp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_04D_MIX_HP_RIGHT, 5, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_04D_MIX_HP_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98095_04D_MIX_HP_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98095_04D_MIX_HP_RIGHT, 4, 1, 0),
+ SOC_DAPM_SINGLE("IN1 Switch", M98095_04D_MIX_HP_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Switch", M98095_04D_MIX_HP_RIGHT, 2, 1, 0),
+};
+
+/* Receiver earpiece mixer switch */
+static const struct snd_kcontrol_new max98095_mono_rcv_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_04F_MIX_RCV, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_04F_MIX_RCV, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98095_04F_MIX_RCV, 3, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98095_04F_MIX_RCV, 4, 1, 0),
+ SOC_DAPM_SINGLE("IN1 Switch", M98095_04F_MIX_RCV, 1, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Switch", M98095_04F_MIX_RCV, 2, 1, 0),
+};
+
+/* Left lineout mixer switch */
+static const struct snd_kcontrol_new max98095_left_lineout_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_053_MIX_LINEOUT1, 5, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_053_MIX_LINEOUT1, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98095_053_MIX_LINEOUT1, 3, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98095_053_MIX_LINEOUT1, 4, 1, 0),
+ SOC_DAPM_SINGLE("IN1 Switch", M98095_053_MIX_LINEOUT1, 1, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Switch", M98095_053_MIX_LINEOUT1, 2, 1, 0),
+};
+
+/* Right lineout mixer switch */
+static const struct snd_kcontrol_new max98095_right_lineout_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_054_MIX_LINEOUT2, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_054_MIX_LINEOUT2, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98095_054_MIX_LINEOUT2, 3, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98095_054_MIX_LINEOUT2, 4, 1, 0),
+ SOC_DAPM_SINGLE("IN1 Switch", M98095_054_MIX_LINEOUT2, 1, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Switch", M98095_054_MIX_LINEOUT2, 2, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98095_left_ADC_mixer_controls[] = {
+ SOC_DAPM_SINGLE("MIC1 Switch", M98095_04A_MIX_ADC_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98095_04A_MIX_ADC_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("IN1 Switch", M98095_04A_MIX_ADC_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Switch", M98095_04A_MIX_ADC_LEFT, 2, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98095_right_ADC_mixer_controls[] = {
+ SOC_DAPM_SINGLE("MIC1 Switch", M98095_04B_MIX_ADC_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98095_04B_MIX_ADC_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("IN1 Switch", M98095_04B_MIX_ADC_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Switch", M98095_04B_MIX_ADC_RIGHT, 2, 1, 0),
+};
+
+static int max98095_mic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ if (w->reg == M98095_05F_LVL_MIC1) {
+ snd_soc_update_bits(codec, w->reg, M98095_MICPRE_MASK,
+ (1+max98095->mic1pre)<<M98095_MICPRE_SHIFT);
+ } else {
+ snd_soc_update_bits(codec, w->reg, M98095_MICPRE_MASK,
+ (1+max98095->mic2pre)<<M98095_MICPRE_SHIFT);
+ }
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_update_bits(codec, w->reg, M98095_MICPRE_MASK, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * The line inputs are stereo inputs with the left and right
+ * channels sharing a common PGA power control signal.
+ */
+static int max98095_line_pga(struct snd_soc_dapm_widget *w,
+ int event, u8 channel)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ u8 *state;
+
+ BUG_ON(!((channel == 1) || (channel == 2)));
+
+ state = &max98095->lin_state;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ *state |= channel;
+ snd_soc_update_bits(codec, w->reg,
+ (1 << w->shift), (1 << w->shift));
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ *state &= ~channel;
+ if (*state == 0) {
+ snd_soc_update_bits(codec, w->reg,
+ (1 << w->shift), 0);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int max98095_pga_in1_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ return max98095_line_pga(w, event, 1);
+}
+
+static int max98095_pga_in2_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ return max98095_line_pga(w, event, 2);
+}
+
+/*
+ * The stereo line out mixer outputs to two stereo line outs.
+ * The 2nd pair has a separate set of enables.
+ */
+static int max98095_lineout_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ snd_soc_update_bits(codec, w->reg,
+ (1 << (w->shift+2)), (1 << (w->shift+2)));
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_update_bits(codec, w->reg,
+ (1 << (w->shift+2)), 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget max98095_dapm_widgets[] = {
+
+ SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98095_090_PWR_EN_IN, 0, 0),
+ SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98095_090_PWR_EN_IN, 1, 0),
+
+ SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+ M98095_091_PWR_EN_OUT, 0, 0),
+ SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+ M98095_091_PWR_EN_OUT, 1, 0),
+ SND_SOC_DAPM_DAC("DACM2", "Aux Playback",
+ M98095_091_PWR_EN_OUT, 2, 0),
+ SND_SOC_DAPM_DAC("DACM3", "Voice Playback",
+ M98095_091_PWR_EN_OUT, 2, 0),
+
+ SND_SOC_DAPM_PGA("HP Left Out", M98095_091_PWR_EN_OUT,
+ 6, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HP Right Out", M98095_091_PWR_EN_OUT,
+ 7, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("SPK Left Out", M98095_091_PWR_EN_OUT,
+ 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("SPK Right Out", M98095_091_PWR_EN_OUT,
+ 5, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("RCV Mono Out", M98095_091_PWR_EN_OUT,
+ 3, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA_E("LINE Left Out", M98095_092_PWR_EN_OUT,
+ 0, 0, NULL, 0, max98095_lineout_event, SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("LINE Right Out", M98095_092_PWR_EN_OUT,
+ 1, 0, NULL, 0, max98095_lineout_event, SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_MUX("External MIC", SND_SOC_NOPM, 0, 0,
+ &max98095_extmic_mux),
+
+ SND_SOC_DAPM_MUX("Linein Mux", SND_SOC_NOPM, 0, 0,
+ &max98095_linein_mux),
+
+ SND_SOC_DAPM_MIXER("Left Headphone Mixer", SND_SOC_NOPM, 0, 0,
+ &max98095_left_hp_mixer_controls[0],
+ ARRAY_SIZE(max98095_left_hp_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right Headphone Mixer", SND_SOC_NOPM, 0, 0,
+ &max98095_right_hp_mixer_controls[0],
+ ARRAY_SIZE(max98095_right_hp_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left Speaker Mixer", SND_SOC_NOPM, 0, 0,
+ &max98095_left_speaker_mixer_controls[0],
+ ARRAY_SIZE(max98095_left_speaker_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right Speaker Mixer", SND_SOC_NOPM, 0, 0,
+ &max98095_right_speaker_mixer_controls[0],
+ ARRAY_SIZE(max98095_right_speaker_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Receiver Mixer", SND_SOC_NOPM, 0, 0,
+ &max98095_mono_rcv_mixer_controls[0],
+ ARRAY_SIZE(max98095_mono_rcv_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left Lineout Mixer", SND_SOC_NOPM, 0, 0,
+ &max98095_left_lineout_mixer_controls[0],
+ ARRAY_SIZE(max98095_left_lineout_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right Lineout Mixer", SND_SOC_NOPM, 0, 0,
+ &max98095_right_lineout_mixer_controls[0],
+ ARRAY_SIZE(max98095_right_lineout_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+ &max98095_left_ADC_mixer_controls[0],
+ ARRAY_SIZE(max98095_left_ADC_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+ &max98095_right_ADC_mixer_controls[0],
+ ARRAY_SIZE(max98095_right_ADC_mixer_controls)),
+
+ SND_SOC_DAPM_PGA_E("MIC1 Input", M98095_05F_LVL_MIC1,
+ 5, 0, NULL, 0, max98095_mic_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("MIC2 Input", M98095_060_LVL_MIC2,
+ 5, 0, NULL, 0, max98095_mic_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("IN1 Input", M98095_090_PWR_EN_IN,
+ 7, 0, NULL, 0, max98095_pga_in1_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("IN2 Input", M98095_090_PWR_EN_IN,
+ 7, 0, NULL, 0, max98095_pga_in2_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_MICBIAS("MICBIAS1", M98095_090_PWR_EN_IN, 2, 0),
+ SND_SOC_DAPM_MICBIAS("MICBIAS2", M98095_090_PWR_EN_IN, 3, 0),
+
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("SPKL"),
+ SND_SOC_DAPM_OUTPUT("SPKR"),
+ SND_SOC_DAPM_OUTPUT("RCV"),
+ SND_SOC_DAPM_OUTPUT("OUT1"),
+ SND_SOC_DAPM_OUTPUT("OUT2"),
+ SND_SOC_DAPM_OUTPUT("OUT3"),
+ SND_SOC_DAPM_OUTPUT("OUT4"),
+
+ SND_SOC_DAPM_INPUT("MIC1"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+ SND_SOC_DAPM_INPUT("INA1"),
+ SND_SOC_DAPM_INPUT("INA2"),
+ SND_SOC_DAPM_INPUT("INB1"),
+ SND_SOC_DAPM_INPUT("INB2"),
+};
+
+static const struct snd_soc_dapm_route max98095_audio_map[] = {
+ /* Left headphone output mixer */
+ {"Left Headphone Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Left Headphone Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Left Headphone Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left Headphone Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left Headphone Mixer", "IN1 Switch", "IN1 Input"},
+ {"Left Headphone Mixer", "IN2 Switch", "IN2 Input"},
+
+ /* Right headphone output mixer */
+ {"Right Headphone Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Right Headphone Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Right Headphone Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right Headphone Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right Headphone Mixer", "IN1 Switch", "IN1 Input"},
+ {"Right Headphone Mixer", "IN2 Switch", "IN2 Input"},
+
+ /* Left speaker output mixer */
+ {"Left Speaker Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Left Speaker Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Left Speaker Mixer", "Mono DAC2 Switch", "DACM2"},
+ {"Left Speaker Mixer", "Mono DAC3 Switch", "DACM3"},
+ {"Left Speaker Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left Speaker Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left Speaker Mixer", "IN1 Switch", "IN1 Input"},
+ {"Left Speaker Mixer", "IN2 Switch", "IN2 Input"},
+
+ /* Right speaker output mixer */
+ {"Right Speaker Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Right Speaker Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Right Speaker Mixer", "Mono DAC2 Switch", "DACM2"},
+ {"Right Speaker Mixer", "Mono DAC3 Switch", "DACM3"},
+ {"Right Speaker Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right Speaker Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right Speaker Mixer", "IN1 Switch", "IN1 Input"},
+ {"Right Speaker Mixer", "IN2 Switch", "IN2 Input"},
+
+ /* Earpiece/Receiver output mixer */
+ {"Receiver Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Receiver Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Receiver Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Receiver Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Receiver Mixer", "IN1 Switch", "IN1 Input"},
+ {"Receiver Mixer", "IN2 Switch", "IN2 Input"},
+
+ /* Left Lineout output mixer */
+ {"Left Lineout Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Left Lineout Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Left Lineout Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left Lineout Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left Lineout Mixer", "IN1 Switch", "IN1 Input"},
+ {"Left Lineout Mixer", "IN2 Switch", "IN2 Input"},
+
+ /* Right lineout output mixer */
+ {"Right Lineout Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Right Lineout Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Right Lineout Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right Lineout Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right Lineout Mixer", "IN1 Switch", "IN1 Input"},
+ {"Right Lineout Mixer", "IN2 Switch", "IN2 Input"},
+
+ {"HP Left Out", NULL, "Left Headphone Mixer"},
+ {"HP Right Out", NULL, "Right Headphone Mixer"},
+ {"SPK Left Out", NULL, "Left Speaker Mixer"},
+ {"SPK Right Out", NULL, "Right Speaker Mixer"},
+ {"RCV Mono Out", NULL, "Receiver Mixer"},
+ {"LINE Left Out", NULL, "Left Lineout Mixer"},
+ {"LINE Right Out", NULL, "Right Lineout Mixer"},
+
+ {"HPL", NULL, "HP Left Out"},
+ {"HPR", NULL, "HP Right Out"},
+ {"SPKL", NULL, "SPK Left Out"},
+ {"SPKR", NULL, "SPK Right Out"},
+ {"RCV", NULL, "RCV Mono Out"},
+ {"OUT1", NULL, "LINE Left Out"},
+ {"OUT2", NULL, "LINE Right Out"},
+ {"OUT3", NULL, "LINE Left Out"},
+ {"OUT4", NULL, "LINE Right Out"},
+
+ /* Left ADC input mixer */
+ {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left ADC Mixer", "IN1 Switch", "IN1 Input"},
+ {"Left ADC Mixer", "IN2 Switch", "IN2 Input"},
+
+ /* Right ADC input mixer */
+ {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right ADC Mixer", "IN1 Switch", "IN1 Input"},
+ {"Right ADC Mixer", "IN2 Switch", "IN2 Input"},
+
+ /* Inputs */
+ {"ADCL", NULL, "Left ADC Mixer"},
+ {"ADCR", NULL, "Right ADC Mixer"},
+
+ {"IN1 Input", NULL, "INA1"},
+ {"IN2 Input", NULL, "INA2"},
+
+ {"MIC1 Input", NULL, "MIC1"},
+ {"MIC2 Input", NULL, "MIC2"},
+};
+
+static int max98095_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_add_controls(codec, max98095_snd_controls,
+ ARRAY_SIZE(max98095_snd_controls));
+
+ return 0;
+}
+
+/* codec mclk clock divider coefficients */
+static const struct {
+ u32 rate;
+ u8 sr;
+} rate_table[] = {
+ {8000, 0x01},
+ {11025, 0x02},
+ {16000, 0x03},
+ {22050, 0x04},
+ {24000, 0x05},
+ {32000, 0x06},
+ {44100, 0x07},
+ {48000, 0x08},
+ {88200, 0x09},
+ {96000, 0x0A},
+};
+
+static int rate_value(int rate, u8 *value)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+ if (rate_table[i].rate >= rate) {
+ *value = rate_table[i].sr;
+ return 0;
+ }
+ }
+ *value = rate_table[0].sr;
+ return -EINVAL;
+}
+
+static int max98095_dai1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_cdata *cdata;
+ unsigned long long ni;
+ unsigned int rate;
+ u8 regval;
+
+ cdata = &max98095->dai[0];
+
+ rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ snd_soc_update_bits(codec, M98095_02A_DAI1_FORMAT,
+ M98095_DAI_WS, 0);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ snd_soc_update_bits(codec, M98095_02A_DAI1_FORMAT,
+ M98095_DAI_WS, M98095_DAI_WS);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (rate_value(rate, &regval))
+ return -EINVAL;
+
+ snd_soc_update_bits(codec, M98095_027_DAI1_CLKMODE,
+ M98095_CLKMODE_MASK, regval);
+ cdata->rate = rate;
+
+ /* Configure NI when operating as master */
+ if (snd_soc_read(codec, M98095_02A_DAI1_FORMAT) & M98095_DAI_MAS) {
+ if (max98095->sysclk == 0) {
+ dev_err(codec->dev, "Invalid system clock frequency\n");
+ return -EINVAL;
+ }
+ ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+ * (unsigned long long int)rate;
+ do_div(ni, (unsigned long long int)max98095->sysclk);
+ snd_soc_write(codec, M98095_028_DAI1_CLKCFG_HI,
+ (ni >> 8) & 0x7F);
+ snd_soc_write(codec, M98095_029_DAI1_CLKCFG_LO,
+ ni & 0xFF);
+ }
+
+ /* Update sample rate mode */
+ if (rate < 50000)
+ snd_soc_update_bits(codec, M98095_02E_DAI1_FILTERS,
+ M98095_DAI_DHF, 0);
+ else
+ snd_soc_update_bits(codec, M98095_02E_DAI1_FILTERS,
+ M98095_DAI_DHF, M98095_DAI_DHF);
+
+ return 0;
+}
+
+static int max98095_dai2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_cdata *cdata;
+ unsigned long long ni;
+ unsigned int rate;
+ u8 regval;
+
+ cdata = &max98095->dai[1];
+
+ rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ snd_soc_update_bits(codec, M98095_034_DAI2_FORMAT,
+ M98095_DAI_WS, 0);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ snd_soc_update_bits(codec, M98095_034_DAI2_FORMAT,
+ M98095_DAI_WS, M98095_DAI_WS);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (rate_value(rate, &regval))
+ return -EINVAL;
+
+ snd_soc_update_bits(codec, M98095_031_DAI2_CLKMODE,
+ M98095_CLKMODE_MASK, regval);
+ cdata->rate = rate;
+
+ /* Configure NI when operating as master */
+ if (snd_soc_read(codec, M98095_034_DAI2_FORMAT) & M98095_DAI_MAS) {
+ if (max98095->sysclk == 0) {
+ dev_err(codec->dev, "Invalid system clock frequency\n");
+ return -EINVAL;
+ }
+ ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+ * (unsigned long long int)rate;
+ do_div(ni, (unsigned long long int)max98095->sysclk);
+ snd_soc_write(codec, M98095_032_DAI2_CLKCFG_HI,
+ (ni >> 8) & 0x7F);
+ snd_soc_write(codec, M98095_033_DAI2_CLKCFG_LO,
+ ni & 0xFF);
+ }
+
+ /* Update sample rate mode */
+ if (rate < 50000)
+ snd_soc_update_bits(codec, M98095_038_DAI2_FILTERS,
+ M98095_DAI_DHF, 0);
+ else
+ snd_soc_update_bits(codec, M98095_038_DAI2_FILTERS,
+ M98095_DAI_DHF, M98095_DAI_DHF);
+
+ return 0;
+}
+
+static int max98095_dai3_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_cdata *cdata;
+ unsigned long long ni;
+ unsigned int rate;
+ u8 regval;
+
+ cdata = &max98095->dai[2];
+
+ rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ snd_soc_update_bits(codec, M98095_03E_DAI3_FORMAT,
+ M98095_DAI_WS, 0);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ snd_soc_update_bits(codec, M98095_03E_DAI3_FORMAT,
+ M98095_DAI_WS, M98095_DAI_WS);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (rate_value(rate, &regval))
+ return -EINVAL;
+
+ snd_soc_update_bits(codec, M98095_03B_DAI3_CLKMODE,
+ M98095_CLKMODE_MASK, regval);
+ cdata->rate = rate;
+
+ /* Configure NI when operating as master */
+ if (snd_soc_read(codec, M98095_03E_DAI3_FORMAT) & M98095_DAI_MAS) {
+ if (max98095->sysclk == 0) {
+ dev_err(codec->dev, "Invalid system clock frequency\n");
+ return -EINVAL;
+ }
+ ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+ * (unsigned long long int)rate;
+ do_div(ni, (unsigned long long int)max98095->sysclk);
+ snd_soc_write(codec, M98095_03C_DAI3_CLKCFG_HI,
+ (ni >> 8) & 0x7F);
+ snd_soc_write(codec, M98095_03D_DAI3_CLKCFG_LO,
+ ni & 0xFF);
+ }
+
+ /* Update sample rate mode */
+ if (rate < 50000)
+ snd_soc_update_bits(codec, M98095_042_DAI3_FILTERS,
+ M98095_DAI_DHF, 0);
+ else
+ snd_soc_update_bits(codec, M98095_042_DAI3_FILTERS,
+ M98095_DAI_DHF, M98095_DAI_DHF);
+
+ return 0;
+}
+
+static int max98095_dai_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+
+ /* Requested clock frequency is already setup */
+ if (freq == max98095->sysclk)
+ return 0;
+
+ max98095->sysclk = freq; /* remember current sysclk */
+
+ /* Setup clocks for slave mode, and using the PLL
+ * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+ * 0x02 (when master clk is 20MHz to 40MHz)..
+ * 0x03 (when master clk is 40MHz to 60MHz)..
+ */
+ if ((freq >= 10000000) && (freq < 20000000)) {
+ snd_soc_write(codec, M98095_026_SYS_CLK, 0x10);
+ } else if ((freq >= 20000000) && (freq < 40000000)) {
+ snd_soc_write(codec, M98095_026_SYS_CLK, 0x20);
+ } else if ((freq >= 40000000) && (freq < 60000000)) {
+ snd_soc_write(codec, M98095_026_SYS_CLK, 0x30);
+ } else {
+ dev_err(codec->dev, "Invalid master clock frequency\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+ max98095->sysclk = freq;
+ return 0;
+}
+
+static int max98095_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_cdata *cdata;
+ u8 regval = 0;
+
+ cdata = &max98095->dai[0];
+
+ if (fmt != cdata->fmt) {
+ cdata->fmt = fmt;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* Slave mode PLL */
+ snd_soc_write(codec, M98095_028_DAI1_CLKCFG_HI,
+ 0x80);
+ snd_soc_write(codec, M98095_029_DAI1_CLKCFG_LO,
+ 0x00);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* Set to master mode */
+ regval |= M98095_DAI_MAS;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(codec->dev, "Clock mode unsupported");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ regval |= M98095_DAI_DLY;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ regval |= M98095_DAI_WCI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ regval |= M98095_DAI_BCI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ regval |= M98095_DAI_BCI|M98095_DAI_WCI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98095_02A_DAI1_FORMAT,
+ M98095_DAI_MAS | M98095_DAI_DLY | M98095_DAI_BCI |
+ M98095_DAI_WCI, regval);
+
+ snd_soc_write(codec, M98095_02B_DAI1_CLOCK, M98095_DAI_BSEL64);
+ }
+
+ return 0;
+}
+
+static int max98095_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_cdata *cdata;
+ u8 regval = 0;
+
+ cdata = &max98095->dai[1];
+
+ if (fmt != cdata->fmt) {
+ cdata->fmt = fmt;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* Slave mode PLL */
+ snd_soc_write(codec, M98095_032_DAI2_CLKCFG_HI,
+ 0x80);
+ snd_soc_write(codec, M98095_033_DAI2_CLKCFG_LO,
+ 0x00);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* Set to master mode */
+ regval |= M98095_DAI_MAS;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(codec->dev, "Clock mode unsupported");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ regval |= M98095_DAI_DLY;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ regval |= M98095_DAI_WCI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ regval |= M98095_DAI_BCI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ regval |= M98095_DAI_BCI|M98095_DAI_WCI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98095_034_DAI2_FORMAT,
+ M98095_DAI_MAS | M98095_DAI_DLY | M98095_DAI_BCI |
+ M98095_DAI_WCI, regval);
+
+ snd_soc_write(codec, M98095_035_DAI2_CLOCK,
+ M98095_DAI_BSEL64);
+ }
+
+ return 0;
+}
+
+static int max98095_dai3_set_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_cdata *cdata;
+ u8 regval = 0;
+
+ cdata = &max98095->dai[2];
+
+ if (fmt != cdata->fmt) {
+ cdata->fmt = fmt;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* Slave mode PLL */
+ snd_soc_write(codec, M98095_03C_DAI3_CLKCFG_HI,
+ 0x80);
+ snd_soc_write(codec, M98095_03D_DAI3_CLKCFG_LO,
+ 0x00);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* Set to master mode */
+ regval |= M98095_DAI_MAS;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(codec->dev, "Clock mode unsupported");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ regval |= M98095_DAI_DLY;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ regval |= M98095_DAI_WCI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ regval |= M98095_DAI_BCI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ regval |= M98095_DAI_BCI|M98095_DAI_WCI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98095_03E_DAI3_FORMAT,
+ M98095_DAI_MAS | M98095_DAI_DLY | M98095_DAI_BCI |
+ M98095_DAI_WCI, regval);
+
+ snd_soc_write(codec, M98095_03F_DAI3_CLOCK,
+ M98095_DAI_BSEL64);
+ }
+
+ return 0;
+}
+
+static int max98095_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = snd_soc_cache_sync(codec);
+
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to sync cache: %d\n", ret);
+ return ret;
+ }
+ }
+
+ snd_soc_update_bits(codec, M98095_090_PWR_EN_IN,
+ M98095_MBEN, M98095_MBEN);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, M98095_090_PWR_EN_IN,
+ M98095_MBEN, 0);
+ codec->cache_sync = 1;
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define MAX98095_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98095_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98095_dai1_ops = {
+ .set_sysclk = max98095_dai_set_sysclk,
+ .set_fmt = max98095_dai1_set_fmt,
+ .hw_params = max98095_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98095_dai2_ops = {
+ .set_sysclk = max98095_dai_set_sysclk,
+ .set_fmt = max98095_dai2_set_fmt,
+ .hw_params = max98095_dai2_hw_params,
+};
+
+static struct snd_soc_dai_ops max98095_dai3_ops = {
+ .set_sysclk = max98095_dai_set_sysclk,
+ .set_fmt = max98095_dai3_set_fmt,
+ .hw_params = max98095_dai3_hw_params,
+};
+
+static struct snd_soc_dai_driver max98095_dai[] = {
+{
+ .name = "HiFi",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX98095_RATES,
+ .formats = MAX98095_FORMATS,
+ },
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX98095_RATES,
+ .formats = MAX98095_FORMATS,
+ },
+ .ops = &max98095_dai1_ops,
+},
+{
+ .name = "Aux",
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = MAX98095_RATES,
+ .formats = MAX98095_FORMATS,
+ },
+ .ops = &max98095_dai2_ops,
+},
+{
+ .name = "Voice",
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = MAX98095_RATES,
+ .formats = MAX98095_FORMATS,
+ },
+ .ops = &max98095_dai3_ops,
+}
+
+};
+
+static int max98095_get_eq_channel(const char *name)
+{
+ if (strcmp(name, "EQ1 Mode") == 0)
+ return 0;
+ if (strcmp(name, "EQ2 Mode") == 0)
+ return 1;
+ return -EINVAL;
+}
+
+static int max98095_put_eq_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_pdata *pdata = max98095->pdata;
+ int channel = max98095_get_eq_channel(kcontrol->id.name);
+ struct max98095_cdata *cdata;
+ int sel = ucontrol->value.integer.value[0];
+ struct max98095_eq_cfg *coef_set;
+ int fs, best, best_val, i;
+ int regmask, regsave;
+
+ BUG_ON(channel > 1);
+
+ if (!pdata || !max98095->eq_textcnt)
+ return 0;
+
+ if (sel >= pdata->eq_cfgcnt)
+ return -EINVAL;
+
+ cdata = &max98095->dai[channel];
+ cdata->eq_sel = sel;
+ fs = cdata->rate;
+
+ /* Find the selected configuration with nearest sample rate */
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < pdata->eq_cfgcnt; i++) {
+ if (strcmp(pdata->eq_cfg[i].name, max98095->eq_texts[sel]) == 0 &&
+ abs(pdata->eq_cfg[i].rate - fs) < best_val) {
+ best = i;
+ best_val = abs(pdata->eq_cfg[i].rate - fs);
+ }
+ }
+
+ dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+ pdata->eq_cfg[best].name,
+ pdata->eq_cfg[best].rate, fs);
+
+ coef_set = &pdata->eq_cfg[best];
+
+ regmask = (channel == 0) ? M98095_EQ1EN : M98095_EQ2EN;
+
+ /* Disable filter while configuring, and save current on/off state */
+ regsave = snd_soc_read(codec, M98095_088_CFG_LEVEL);
+ snd_soc_update_bits(codec, M98095_088_CFG_LEVEL, regmask, 0);
+
+ mutex_lock(&codec->mutex);
+ snd_soc_update_bits(codec, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG);
+ m98095_eq_band(codec, channel, 0, coef_set->band1);
+ m98095_eq_band(codec, channel, 1, coef_set->band2);
+ m98095_eq_band(codec, channel, 2, coef_set->band3);
+ m98095_eq_band(codec, channel, 3, coef_set->band4);
+ m98095_eq_band(codec, channel, 4, coef_set->band5);
+ snd_soc_update_bits(codec, M98095_00F_HOST_CFG, M98095_SEG, 0);
+ mutex_unlock(&codec->mutex);
+
+ /* Restore the original on/off state */
+ snd_soc_update_bits(codec, M98095_088_CFG_LEVEL, regmask, regsave);
+ return 0;
+}
+
+static int max98095_get_eq_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ int channel = max98095_get_eq_channel(kcontrol->id.name);
+ struct max98095_cdata *cdata;
+
+ cdata = &max98095->dai[channel];
+ ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+
+ return 0;
+}
+
+static void max98095_handle_eq_pdata(struct snd_soc_codec *codec)
+{
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_pdata *pdata = max98095->pdata;
+ struct max98095_eq_cfg *cfg;
+ unsigned int cfgcnt;
+ int i, j;
+ const char **t;
+ int ret;
+
+ struct snd_kcontrol_new controls[] = {
+ SOC_ENUM_EXT("EQ1 Mode",
+ max98095->eq_enum,
+ max98095_get_eq_enum,
+ max98095_put_eq_enum),
+ SOC_ENUM_EXT("EQ2 Mode",
+ max98095->eq_enum,
+ max98095_get_eq_enum,
+ max98095_put_eq_enum),
+ };
+
+ cfg = pdata->eq_cfg;
+ cfgcnt = pdata->eq_cfgcnt;
+
+ /* Setup an array of texts for the equalizer enum.
+ * This is based on Mark Brown's equalizer driver code.
+ */
+ max98095->eq_textcnt = 0;
+ max98095->eq_texts = NULL;
+ for (i = 0; i < cfgcnt; i++) {
+ for (j = 0; j < max98095->eq_textcnt; j++) {
+ if (strcmp(cfg[i].name, max98095->eq_texts[j]) == 0)
+ break;
+ }
+
+ if (j != max98095->eq_textcnt)
+ continue;
+
+ /* Expand the array */
+ t = krealloc(max98095->eq_texts,
+ sizeof(char *) * (max98095->eq_textcnt + 1),
+ GFP_KERNEL);
+ if (t == NULL)
+ continue;
+
+ /* Store the new entry */
+ t[max98095->eq_textcnt] = cfg[i].name;
+ max98095->eq_textcnt++;
+ max98095->eq_texts = t;
+ }
+
+ /* Now point the soc_enum to .texts array items */
+ max98095->eq_enum.texts = max98095->eq_texts;
+ max98095->eq_enum.max = max98095->eq_textcnt;
+
+ ret = snd_soc_add_controls(codec, controls, ARRAY_SIZE(controls));
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static int max98095_get_bq_channel(const char *name)
+{
+ if (strcmp(name, "Biquad1 Mode") == 0)
+ return 0;
+ if (strcmp(name, "Biquad2 Mode") == 0)
+ return 1;
+ return -EINVAL;
+}
+
+static int max98095_put_bq_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_pdata *pdata = max98095->pdata;
+ int channel = max98095_get_bq_channel(kcontrol->id.name);
+ struct max98095_cdata *cdata;
+ int sel = ucontrol->value.integer.value[0];
+ struct max98095_biquad_cfg *coef_set;
+ int fs, best, best_val, i;
+ int regmask, regsave;
+
+ BUG_ON(channel > 1);
+
+ if (!pdata || !max98095->bq_textcnt)
+ return 0;
+
+ if (sel >= pdata->bq_cfgcnt)
+ return -EINVAL;
+
+ cdata = &max98095->dai[channel];
+ cdata->bq_sel = sel;
+ fs = cdata->rate;
+
+ /* Find the selected configuration with nearest sample rate */
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < pdata->bq_cfgcnt; i++) {
+ if (strcmp(pdata->bq_cfg[i].name, max98095->bq_texts[sel]) == 0 &&
+ abs(pdata->bq_cfg[i].rate - fs) < best_val) {
+ best = i;
+ best_val = abs(pdata->bq_cfg[i].rate - fs);
+ }
+ }
+
+ dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+ pdata->bq_cfg[best].name,
+ pdata->bq_cfg[best].rate, fs);
+
+ coef_set = &pdata->bq_cfg[best];
+
+ regmask = (channel == 0) ? M98095_BQ1EN : M98095_BQ2EN;
+
+ /* Disable filter while configuring, and save current on/off state */
+ regsave = snd_soc_read(codec, M98095_088_CFG_LEVEL);
+ snd_soc_update_bits(codec, M98095_088_CFG_LEVEL, regmask, 0);
+
+ mutex_lock(&codec->mutex);
+ snd_soc_update_bits(codec, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG);
+ m98095_biquad_band(codec, channel, 0, coef_set->band1);
+ m98095_biquad_band(codec, channel, 1, coef_set->band2);
+ snd_soc_update_bits(codec, M98095_00F_HOST_CFG, M98095_SEG, 0);
+ mutex_unlock(&codec->mutex);
+
+ /* Restore the original on/off state */
+ snd_soc_update_bits(codec, M98095_088_CFG_LEVEL, regmask, regsave);
+ return 0;
+}
+
+static int max98095_get_bq_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ int channel = max98095_get_bq_channel(kcontrol->id.name);
+ struct max98095_cdata *cdata;
+
+ cdata = &max98095->dai[channel];
+ ucontrol->value.enumerated.item[0] = cdata->bq_sel;
+
+ return 0;
+}
+
+static void max98095_handle_bq_pdata(struct snd_soc_codec *codec)
+{
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_pdata *pdata = max98095->pdata;
+ struct max98095_biquad_cfg *cfg;
+ unsigned int cfgcnt;
+ int i, j;
+ const char **t;
+ int ret;
+
+ struct snd_kcontrol_new controls[] = {
+ SOC_ENUM_EXT("Biquad1 Mode",
+ max98095->bq_enum,
+ max98095_get_bq_enum,
+ max98095_put_bq_enum),
+ SOC_ENUM_EXT("Biquad2 Mode",
+ max98095->bq_enum,
+ max98095_get_bq_enum,
+ max98095_put_bq_enum),
+ };
+
+ cfg = pdata->bq_cfg;
+ cfgcnt = pdata->bq_cfgcnt;
+
+ /* Setup an array of texts for the biquad enum.
+ * This is based on Mark Brown's equalizer driver code.
+ */
+ max98095->bq_textcnt = 0;
+ max98095->bq_texts = NULL;
+ for (i = 0; i < cfgcnt; i++) {
+ for (j = 0; j < max98095->bq_textcnt; j++) {
+ if (strcmp(cfg[i].name, max98095->bq_texts[j]) == 0)
+ break;
+ }
+
+ if (j != max98095->bq_textcnt)
+ continue;
+
+ /* Expand the array */
+ t = krealloc(max98095->bq_texts,
+ sizeof(char *) * (max98095->bq_textcnt + 1),
+ GFP_KERNEL);
+ if (t == NULL)
+ continue;
+
+ /* Store the new entry */
+ t[max98095->bq_textcnt] = cfg[i].name;
+ max98095->bq_textcnt++;
+ max98095->bq_texts = t;
+ }
+
+ /* Now point the soc_enum to .texts array items */
+ max98095->bq_enum.texts = max98095->bq_texts;
+ max98095->bq_enum.max = max98095->bq_textcnt;
+
+ ret = snd_soc_add_controls(codec, controls, ARRAY_SIZE(controls));
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add Biquad control: %d\n", ret);
+}
+
+static void max98095_handle_pdata(struct snd_soc_codec *codec)
+{
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_pdata *pdata = max98095->pdata;
+ u8 regval = 0;
+
+ if (!pdata) {
+ dev_dbg(codec->dev, "No platform data\n");
+ return;
+ }
+
+ /* Configure mic for analog/digital mic mode */
+ if (pdata->digmic_left_mode)
+ regval |= M98095_DIGMIC_L;
+
+ if (pdata->digmic_right_mode)
+ regval |= M98095_DIGMIC_R;
+
+ snd_soc_write(codec, M98095_087_CFG_MIC, regval);
+
+ /* Configure equalizers */
+ if (pdata->eq_cfgcnt)
+ max98095_handle_eq_pdata(codec);
+
+ /* Configure bi-quad filters */
+ if (pdata->bq_cfgcnt)
+ max98095_handle_bq_pdata(codec);
+}
+
+#ifdef CONFIG_PM
+static int max98095_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ max98095_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int max98095_resume(struct snd_soc_codec *codec)
+{
+ max98095_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define max98095_suspend NULL
+#define max98095_resume NULL
+#endif
+
+static int max98095_reset(struct snd_soc_codec *codec)
+{
+ int i, ret;
+
+ /* Gracefully reset the DSP core and the codec hardware
+ * in a proper sequence */
+ ret = snd_soc_write(codec, M98095_00F_HOST_CFG, 0);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to reset DSP: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_write(codec, M98095_097_PWR_SYS, 0);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to reset codec: %d\n", ret);
+ return ret;
+ }
+
+ /* Reset to hardware default for registers, as there is not
+ * a soft reset hardware control register */
+ for (i = M98095_010_HOST_INT_CFG; i < M98095_REG_MAX_CACHED; i++) {
+ ret = snd_soc_write(codec, i, max98095_reg_def[i]);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to reset: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static int max98095_probe(struct snd_soc_codec *codec)
+{
+ struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec);
+ struct max98095_cdata *cdata;
+ int ret = 0;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ /* reset the codec, the DSP core, and disable all interrupts */
+ max98095_reset(codec);
+
+ /* initialize private data */
+
+ max98095->sysclk = (unsigned)-1;
+ max98095->eq_textcnt = 0;
+ max98095->bq_textcnt = 0;
+
+ cdata = &max98095->dai[0];
+ cdata->rate = (unsigned)-1;
+ cdata->fmt = (unsigned)-1;
+ cdata->eq_sel = 0;
+ cdata->bq_sel = 0;
+
+ cdata = &max98095->dai[1];
+ cdata->rate = (unsigned)-1;
+ cdata->fmt = (unsigned)-1;
+ cdata->eq_sel = 0;
+ cdata->bq_sel = 0;
+
+ cdata = &max98095->dai[2];
+ cdata->rate = (unsigned)-1;
+ cdata->fmt = (unsigned)-1;
+ cdata->eq_sel = 0;
+ cdata->bq_sel = 0;
+
+ max98095->lin_state = 0;
+ max98095->mic1pre = 0;
+ max98095->mic2pre = 0;
+
+ ret = snd_soc_read(codec, M98095_0FF_REV_ID);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read device revision: %d\n",
+ ret);
+ goto err_access;
+ }
+ dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+ snd_soc_write(codec, M98095_097_PWR_SYS, M98095_PWRSV);
+
+ /* initialize registers cache to hardware default */
+ max98095_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_write(codec, M98095_048_MIX_DAC_LR,
+ M98095_DAI1L_TO_DACL|M98095_DAI1R_TO_DACR);
+
+ snd_soc_write(codec, M98095_049_MIX_DAC_M,
+ M98095_DAI2M_TO_DACM|M98095_DAI3M_TO_DACM);
+
+ snd_soc_write(codec, M98095_092_PWR_EN_OUT, M98095_SPK_SPREADSPECTRUM);
+ snd_soc_write(codec, M98095_045_CFG_DSP, M98095_DSPNORMAL);
+ snd_soc_write(codec, M98095_04E_CFG_HP, M98095_HPNORMAL);
+
+ snd_soc_write(codec, M98095_02C_DAI1_IOCFG,
+ M98095_S1NORMAL|M98095_SDATA);
+
+ snd_soc_write(codec, M98095_036_DAI2_IOCFG,
+ M98095_S2NORMAL|M98095_SDATA);
+
+ snd_soc_write(codec, M98095_040_DAI3_IOCFG,
+ M98095_S3NORMAL|M98095_SDATA);
+
+ max98095_handle_pdata(codec);
+
+ /* take the codec out of the shut down */
+ snd_soc_update_bits(codec, M98095_097_PWR_SYS, M98095_SHDNRUN,
+ M98095_SHDNRUN);
+
+ max98095_add_widgets(codec);
+
+err_access:
+ return ret;
+}
+
+static int max98095_remove(struct snd_soc_codec *codec)
+{
+ max98095_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_max98095 = {
+ .probe = max98095_probe,
+ .remove = max98095_remove,
+ .suspend = max98095_suspend,
+ .resume = max98095_resume,
+ .set_bias_level = max98095_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(max98095_reg_def),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = max98095_reg_def,
+ .readable_register = max98095_readable,
+ .volatile_register = max98095_volatile,
+ .dapm_widgets = max98095_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(max98095_dapm_widgets),
+ .dapm_routes = max98095_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(max98095_audio_map),
+};
+
+static int max98095_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct max98095_priv *max98095;
+ int ret;
+
+ max98095 = kzalloc(sizeof(struct max98095_priv), GFP_KERNEL);
+ if (max98095 == NULL)
+ return -ENOMEM;
+
+ max98095->devtype = id->driver_data;
+ i2c_set_clientdata(i2c, max98095);
+ max98095->control_data = i2c;
+ max98095->pdata = i2c->dev.platform_data;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_max98095, &max98095_dai[0], 3);
+ if (ret < 0)
+ kfree(max98095);
+ return ret;
+}
+
+static int __devexit max98095_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+
+ return 0;
+}
+
+static const struct i2c_device_id max98095_i2c_id[] = {
+ { "max98095", MAX98095 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max98095_i2c_id);
+
+static struct i2c_driver max98095_i2c_driver = {
+ .driver = {
+ .name = "max98095",
+ .owner = THIS_MODULE,
+ },
+ .probe = max98095_i2c_probe,
+ .remove = __devexit_p(max98095_i2c_remove),
+ .id_table = max98095_i2c_id,
+};
+
+static int __init max98095_init(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&max98095_i2c_driver);
+ if (ret)
+ pr_err("Failed to register max98095 I2C driver: %d\n", ret);
+
+ return ret;
+}
+module_init(max98095_init);
+
+static void __exit max98095_exit(void)
+{
+ i2c_del_driver(&max98095_i2c_driver);
+}
+module_exit(max98095_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98095 driver");
+MODULE_AUTHOR("Peter Hsiang");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98095.h b/sound/soc/codecs/max98095.h
new file mode 100644
index 00000000..891584a0
--- /dev/null
+++ b/sound/soc/codecs/max98095.h
@@ -0,0 +1,299 @@
+/*
+ * max98095.h -- MAX98095 ALSA SoC Audio driver
+ *
+ * Copyright 2011 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _MAX98095_H
+#define _MAX98095_H
+
+/*
+ * MAX98095 Registers Definition
+ */
+
+#define M98095_000_HOST_DATA 0x00
+#define M98095_001_HOST_INT_STS 0x01
+#define M98095_002_HOST_RSP_STS 0x02
+#define M98095_003_HOST_CMD_STS 0x03
+#define M98095_004_CODEC_STS 0x04
+#define M98095_005_DAI1_ALC_STS 0x05
+#define M98095_006_DAI2_ALC_STS 0x06
+#define M98095_007_JACK_AUTO_STS 0x07
+#define M98095_008_JACK_MANUAL_STS 0x08
+#define M98095_009_JACK_VBAT_STS 0x09
+#define M98095_00A_ACC_ADC_STS 0x0A
+#define M98095_00B_MIC_NG_AGC_STS 0x0B
+#define M98095_00C_SPK_L_VOLT_STS 0x0C
+#define M98095_00D_SPK_R_VOLT_STS 0x0D
+#define M98095_00E_TEMP_SENSOR_STS 0x0E
+#define M98095_00F_HOST_CFG 0x0F
+#define M98095_010_HOST_INT_CFG 0x10
+#define M98095_011_HOST_INT_EN 0x11
+#define M98095_012_CODEC_INT_EN 0x12
+#define M98095_013_JACK_INT_EN 0x13
+#define M98095_014_JACK_INT_EN 0x14
+#define M98095_015_DEC 0x15
+#define M98095_016_RESERVED 0x16
+#define M98095_017_RESERVED 0x17
+#define M98095_018_KEYCODE3 0x18
+#define M98095_019_KEYCODE2 0x19
+#define M98095_01A_KEYCODE1 0x1A
+#define M98095_01B_KEYCODE0 0x1B
+#define M98095_01C_OEMCODE1 0x1C
+#define M98095_01D_OEMCODE0 0x1D
+#define M98095_01E_XCFG1 0x1E
+#define M98095_01F_XCFG2 0x1F
+#define M98095_020_XCFG3 0x20
+#define M98095_021_XCFG4 0x21
+#define M98095_022_XCFG5 0x22
+#define M98095_023_XCFG6 0x23
+#define M98095_024_XGPIO 0x24
+#define M98095_025_XCLKCFG 0x25
+#define M98095_026_SYS_CLK 0x26
+#define M98095_027_DAI1_CLKMODE 0x27
+#define M98095_028_DAI1_CLKCFG_HI 0x28
+#define M98095_029_DAI1_CLKCFG_LO 0x29
+#define M98095_02A_DAI1_FORMAT 0x2A
+#define M98095_02B_DAI1_CLOCK 0x2B
+#define M98095_02C_DAI1_IOCFG 0x2C
+#define M98095_02D_DAI1_TDM 0x2D
+#define M98095_02E_DAI1_FILTERS 0x2E
+#define M98095_02F_DAI1_LVL1 0x2F
+#define M98095_030_DAI1_LVL2 0x30
+#define M98095_031_DAI2_CLKMODE 0x31
+#define M98095_032_DAI2_CLKCFG_HI 0x32
+#define M98095_033_DAI2_CLKCFG_LO 0x33
+#define M98095_034_DAI2_FORMAT 0x34
+#define M98095_035_DAI2_CLOCK 0x35
+#define M98095_036_DAI2_IOCFG 0x36
+#define M98095_037_DAI2_TDM 0x37
+#define M98095_038_DAI2_FILTERS 0x38
+#define M98095_039_DAI2_LVL1 0x39
+#define M98095_03A_DAI2_LVL2 0x3A
+#define M98095_03B_DAI3_CLKMODE 0x3B
+#define M98095_03C_DAI3_CLKCFG_HI 0x3C
+#define M98095_03D_DAI3_CLKCFG_LO 0x3D
+#define M98095_03E_DAI3_FORMAT 0x3E
+#define M98095_03F_DAI3_CLOCK 0x3F
+#define M98095_040_DAI3_IOCFG 0x40
+#define M98095_041_DAI3_TDM 0x41
+#define M98095_042_DAI3_FILTERS 0x42
+#define M98095_043_DAI3_LVL1 0x43
+#define M98095_044_DAI3_LVL2 0x44
+#define M98095_045_CFG_DSP 0x45
+#define M98095_046_DAC_CTRL1 0x46
+#define M98095_047_DAC_CTRL2 0x47
+#define M98095_048_MIX_DAC_LR 0x48
+#define M98095_049_MIX_DAC_M 0x49
+#define M98095_04A_MIX_ADC_LEFT 0x4A
+#define M98095_04B_MIX_ADC_RIGHT 0x4B
+#define M98095_04C_MIX_HP_LEFT 0x4C
+#define M98095_04D_MIX_HP_RIGHT 0x4D
+#define M98095_04E_CFG_HP 0x4E
+#define M98095_04F_MIX_RCV 0x4F
+#define M98095_050_MIX_SPK_LEFT 0x50
+#define M98095_051_MIX_SPK_RIGHT 0x51
+#define M98095_052_MIX_SPK_CFG 0x52
+#define M98095_053_MIX_LINEOUT1 0x53
+#define M98095_054_MIX_LINEOUT2 0x54
+#define M98095_055_MIX_LINEOUT_CFG 0x55
+#define M98095_056_LVL_SIDETONE_DAI12 0x56
+#define M98095_057_LVL_SIDETONE_DAI3 0x57
+#define M98095_058_LVL_DAI1_PLAY 0x58
+#define M98095_059_LVL_DAI1_EQ 0x59
+#define M98095_05A_LVL_DAI2_PLAY 0x5A
+#define M98095_05B_LVL_DAI2_EQ 0x5B
+#define M98095_05C_LVL_DAI3_PLAY 0x5C
+#define M98095_05D_LVL_ADC_L 0x5D
+#define M98095_05E_LVL_ADC_R 0x5E
+#define M98095_05F_LVL_MIC1 0x5F
+#define M98095_060_LVL_MIC2 0x60
+#define M98095_061_LVL_LINEIN 0x61
+#define M98095_062_LVL_LINEOUT1 0x62
+#define M98095_063_LVL_LINEOUT2 0x63
+#define M98095_064_LVL_HP_L 0x64
+#define M98095_065_LVL_HP_R 0x65
+#define M98095_066_LVL_RCV 0x66
+#define M98095_067_LVL_SPK_L 0x67
+#define M98095_068_LVL_SPK_R 0x68
+#define M98095_069_MICAGC_CFG 0x69
+#define M98095_06A_MICAGC_THRESH 0x6A
+#define M98095_06B_SPK_NOISEGATE 0x6B
+#define M98095_06C_DAI1_ALC1_TIME 0x6C
+#define M98095_06D_DAI1_ALC1_COMP 0x6D
+#define M98095_06E_DAI1_ALC1_EXPN 0x6E
+#define M98095_06F_DAI1_ALC1_GAIN 0x6F
+#define M98095_070_DAI1_ALC2_TIME 0x70
+#define M98095_071_DAI1_ALC2_COMP 0x71
+#define M98095_072_DAI1_ALC2_EXPN 0x72
+#define M98095_073_DAI1_ALC2_GAIN 0x73
+#define M98095_074_DAI1_ALC3_TIME 0x74
+#define M98095_075_DAI1_ALC3_COMP 0x75
+#define M98095_076_DAI1_ALC3_EXPN 0x76
+#define M98095_077_DAI1_ALC3_GAIN 0x77
+#define M98095_078_DAI2_ALC1_TIME 0x78
+#define M98095_079_DAI2_ALC1_COMP 0x79
+#define M98095_07A_DAI2_ALC1_EXPN 0x7A
+#define M98095_07B_DAI2_ALC1_GAIN 0x7B
+#define M98095_07C_DAI2_ALC2_TIME 0x7C
+#define M98095_07D_DAI2_ALC2_COMP 0x7D
+#define M98095_07E_DAI2_ALC2_EXPN 0x7E
+#define M98095_07F_DAI2_ALC2_GAIN 0x7F
+#define M98095_080_DAI2_ALC3_TIME 0x80
+#define M98095_081_DAI2_ALC3_COMP 0x81
+#define M98095_082_DAI2_ALC3_EXPN 0x82
+#define M98095_083_DAI2_ALC3_GAIN 0x83
+#define M98095_084_HP_NOISE_GATE 0x84
+#define M98095_085_AUX_ADC 0x85
+#define M98095_086_CFG_LINE 0x86
+#define M98095_087_CFG_MIC 0x87
+#define M98095_088_CFG_LEVEL 0x88
+#define M98095_089_JACK_DET_AUTO 0x89
+#define M98095_08A_JACK_DET_MANUAL 0x8A
+#define M98095_08B_JACK_KEYSCAN_DBC 0x8B
+#define M98095_08C_JACK_KEYSCAN_DLY 0x8C
+#define M98095_08D_JACK_KEY_THRESH 0x8D
+#define M98095_08E_JACK_DC_SLEW 0x8E
+#define M98095_08F_JACK_TEST_CFG 0x8F
+#define M98095_090_PWR_EN_IN 0x90
+#define M98095_091_PWR_EN_OUT 0x91
+#define M98095_092_PWR_EN_OUT 0x92
+#define M98095_093_BIAS_CTRL 0x93
+#define M98095_094_PWR_DAC_21 0x94
+#define M98095_095_PWR_DAC_03 0x95
+#define M98095_096_PWR_DAC_CK 0x96
+#define M98095_097_PWR_SYS 0x97
+
+#define M98095_0FF_REV_ID 0xFF
+
+#define M98095_REG_CNT (0xFF+1)
+#define M98095_REG_MAX_CACHED 0X97
+
+/* MAX98095 Registers Bit Fields */
+
+/* M98095_00F_HOST_CFG */
+ #define M98095_SEG (1<<0)
+ #define M98095_XTEN (1<<1)
+ #define M98095_MDLLEN (1<<2)
+
+/* M98095_027_DAI1_CLKMODE, M98095_031_DAI2_CLKMODE, M98095_03B_DAI3_CLKMODE */
+ #define M98095_CLKMODE_MASK 0xFF
+
+/* M98095_02A_DAI1_FORMAT, M98095_034_DAI2_FORMAT, M98095_03E_DAI3_FORMAT */
+ #define M98095_DAI_MAS (1<<7)
+ #define M98095_DAI_WCI (1<<6)
+ #define M98095_DAI_BCI (1<<5)
+ #define M98095_DAI_DLY (1<<4)
+ #define M98095_DAI_TDM (1<<2)
+ #define M98095_DAI_FSW (1<<1)
+ #define M98095_DAI_WS (1<<0)
+
+/* M98095_02B_DAI1_CLOCK, M98095_035_DAI2_CLOCK, M98095_03F_DAI3_CLOCK */
+ #define M98095_DAI_BSEL64 (1<<0)
+ #define M98095_DAI_DOSR_DIV2 (0<<5)
+ #define M98095_DAI_DOSR_DIV4 (1<<5)
+
+/* M98095_02C_DAI1_IOCFG, M98095_036_DAI2_IOCFG, M98095_040_DAI3_IOCFG */
+ #define M98095_S1NORMAL (1<<6)
+ #define M98095_S2NORMAL (2<<6)
+ #define M98095_S3NORMAL (3<<6)
+ #define M98095_SDATA (3<<0)
+
+/* M98095_02E_DAI1_FILTERS, M98095_038_DAI2_FILTERS, M98095_042_DAI3_FILTERS */
+ #define M98095_DAI_DHF (1<<3)
+
+/* M98095_045_DSP_CFG */
+ #define M98095_DSPNORMAL (5<<4)
+
+/* M98095_048_MIX_DAC_LR */
+ #define M98095_DAI1L_TO_DACR (1<<7)
+ #define M98095_DAI1R_TO_DACR (1<<6)
+ #define M98095_DAI2M_TO_DACR (1<<5)
+ #define M98095_DAI1L_TO_DACL (1<<3)
+ #define M98095_DAI1R_TO_DACL (1<<2)
+ #define M98095_DAI2M_TO_DACL (1<<1)
+ #define M98095_DAI3M_TO_DACL (1<<0)
+
+/* M98095_049_MIX_DAC_M */
+ #define M98095_DAI1L_TO_DACM (1<<3)
+ #define M98095_DAI1R_TO_DACM (1<<2)
+ #define M98095_DAI2M_TO_DACM (1<<1)
+ #define M98095_DAI3M_TO_DACM (1<<0)
+
+/* M98095_04E_MIX_HP_CFG */
+ #define M98095_HPNORMAL (3<<4)
+
+/* M98095_05F_LVL_MIC1, M98095_060_LVL_MIC2 */
+ #define M98095_MICPRE_MASK (3<<5)
+ #define M98095_MICPRE_SHIFT 5
+
+/* M98095_064_LVL_HP_L, M98095_065_LVL_HP_R */
+ #define M98095_HP_MUTE (1<<7)
+
+/* M98095_066_LVL_RCV */
+ #define M98095_REC_MUTE (1<<7)
+
+/* M98095_067_LVL_SPK_L, M98095_068_LVL_SPK_R */
+ #define M98095_SP_MUTE (1<<7)
+
+/* M98095_087_CFG_MIC */
+ #define M98095_MICSEL_MASK (3<<0)
+ #define M98095_DIGMIC_L (1<<2)
+ #define M98095_DIGMIC_R (1<<3)
+ #define M98095_DIGMIC2L (1<<4)
+ #define M98095_DIGMIC2R (1<<5)
+
+/* M98095_088_CFG_LEVEL */
+ #define M98095_VSEN (1<<6)
+ #define M98095_ZDEN (1<<5)
+ #define M98095_BQ2EN (1<<3)
+ #define M98095_BQ1EN (1<<2)
+ #define M98095_EQ2EN (1<<1)
+ #define M98095_EQ1EN (1<<0)
+
+/* M98095_090_PWR_EN_IN */
+ #define M98095_INEN (1<<7)
+ #define M98095_MB2EN (1<<3)
+ #define M98095_MB1EN (1<<2)
+ #define M98095_MBEN (3<<2)
+ #define M98095_ADREN (1<<1)
+ #define M98095_ADLEN (1<<0)
+
+/* M98095_091_PWR_EN_OUT */
+ #define M98095_HPLEN (1<<7)
+ #define M98095_HPREN (1<<6)
+ #define M98095_SPLEN (1<<5)
+ #define M98095_SPREN (1<<4)
+ #define M98095_RECEN (1<<3)
+ #define M98095_DALEN (1<<1)
+ #define M98095_DAREN (1<<0)
+
+/* M98095_092_PWR_EN_OUT */
+ #define M98095_SPK_FIXEDSPECTRUM (0<<4)
+ #define M98095_SPK_SPREADSPECTRUM (1<<4)
+
+/* M98095_097_PWR_SYS */
+ #define M98095_SHDNRUN (1<<7)
+ #define M98095_PERFMODE (1<<3)
+ #define M98095_HPPLYBACK (1<<2)
+ #define M98095_PWRSV8K (1<<1)
+ #define M98095_PWRSV (1<<0)
+
+#define M98095_COEFS_PER_BAND 5
+
+#define M98095_BYTE1(w) ((w >> 8) & 0xff)
+#define M98095_BYTE0(w) (w & 0xff)
+
+/* Equalizer filter coefficients */
+#define M98095_110_DAI1_EQ_BASE 0x10
+#define M98095_142_DAI2_EQ_BASE 0x42
+
+/* Biquad filter coefficients */
+#define M98095_174_DAI1_BQ_BASE 0x74
+#define M98095_17E_DAI2_BQ_BASE 0x7E
+
+#endif
diff --git a/sound/soc/codecs/max9850.c b/sound/soc/codecs/max9850.c
new file mode 100644
index 00000000..208d2ee6
--- /dev/null
+++ b/sound/soc/codecs/max9850.c
@@ -0,0 +1,389 @@
+/*
+ * max9850.c -- codec driver for max9850
+ *
+ * Copyright (C) 2011 taskit GmbH
+ *
+ * Author: Christian Glindkamp <christian.glindkamp@taskit.de>
+ *
+ * Initial development of this code was funded by
+ * MICRONIC Computer Systeme GmbH, http://www.mcsberlin.de/
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "max9850.h"
+
+struct max9850_priv {
+ unsigned int sysclk;
+};
+
+/* max9850 register cache */
+static const u8 max9850_reg[MAX9850_CACHEREGNUM] = {
+ 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* these registers are not used at the moment but provided for the sake of
+ * completeness */
+static int max9850_volatile_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ case MAX9850_STATUSA:
+ case MAX9850_STATUSB:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static const unsigned int max9850_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0x18, 0x1f, TLV_DB_SCALE_ITEM(-7450, 400, 0),
+ 0x20, 0x33, TLV_DB_SCALE_ITEM(-4150, 200, 0),
+ 0x34, 0x37, TLV_DB_SCALE_ITEM(-150, 100, 0),
+ 0x38, 0x3f, TLV_DB_SCALE_ITEM(250, 50, 0),
+};
+
+static const struct snd_kcontrol_new max9850_controls[] = {
+SOC_SINGLE_TLV("Headphone Volume", MAX9850_VOLUME, 0, 0x3f, 1, max9850_tlv),
+SOC_SINGLE("Headphone Switch", MAX9850_VOLUME, 7, 1, 1),
+SOC_SINGLE("Mono Switch", MAX9850_GENERAL_PURPOSE, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new max9850_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line In Switch", MAX9850_ENABLE, 1, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget max9850_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("Charge Pump 1", MAX9850_ENABLE, 4, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("Charge Pump 2", MAX9850_ENABLE, 5, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("MCLK", MAX9850_ENABLE, 6, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("SHDN", MAX9850_ENABLE, 7, 0, NULL, 0),
+SND_SOC_DAPM_MIXER_NAMED_CTL("Output Mixer", MAX9850_ENABLE, 2, 0,
+ &max9850_mixer_controls[0],
+ ARRAY_SIZE(max9850_mixer_controls)),
+SND_SOC_DAPM_PGA("Headphone Output", MAX9850_ENABLE, 3, 0, NULL, 0),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", MAX9850_ENABLE, 0, 0),
+SND_SOC_DAPM_OUTPUT("OUTL"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("OUTR"),
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_INPUT("INL"),
+SND_SOC_DAPM_INPUT("INR"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* output mixer */
+ {"Output Mixer", NULL, "DAC"},
+ {"Output Mixer", "Line In Switch", "Line Input"},
+
+ /* outputs */
+ {"Headphone Output", NULL, "Output Mixer"},
+ {"HPL", NULL, "Headphone Output"},
+ {"HPR", NULL, "Headphone Output"},
+ {"OUTL", NULL, "Output Mixer"},
+ {"OUTR", NULL, "Output Mixer"},
+
+ /* inputs */
+ {"Line Input", NULL, "INL"},
+ {"Line Input", NULL, "INR"},
+
+ /* supplies */
+ {"Output Mixer", NULL, "Charge Pump 1"},
+ {"Output Mixer", NULL, "Charge Pump 2"},
+ {"Output Mixer", NULL, "SHDN"},
+ {"DAC", NULL, "MCLK"},
+};
+
+static int max9850_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max9850_priv *max9850 = snd_soc_codec_get_drvdata(codec);
+ u64 lrclk_div;
+ u8 sf, da;
+
+ if (!max9850->sysclk)
+ return -EINVAL;
+
+ /* lrclk_div = 2^22 * rate / iclk with iclk = mclk / sf */
+ sf = (snd_soc_read(codec, MAX9850_CLOCK) >> 2) + 1;
+ lrclk_div = (1 << 22);
+ lrclk_div *= params_rate(params);
+ lrclk_div *= sf;
+ do_div(lrclk_div, max9850->sysclk);
+
+ snd_soc_write(codec, MAX9850_LRCLK_MSB, (lrclk_div >> 8) & 0x7f);
+ snd_soc_write(codec, MAX9850_LRCLK_LSB, lrclk_div & 0xff);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ da = 0;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ da = 0x2;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ da = 0x3;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(codec, MAX9850_DIGITAL_AUDIO, 0x3, da);
+
+ return 0;
+}
+
+static int max9850_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max9850_priv *max9850 = snd_soc_codec_get_drvdata(codec);
+
+ /* calculate mclk -> iclk divider */
+ if (freq <= 13000000)
+ snd_soc_write(codec, MAX9850_CLOCK, 0x0);
+ else if (freq <= 26000000)
+ snd_soc_write(codec, MAX9850_CLOCK, 0x4);
+ else if (freq <= 40000000)
+ snd_soc_write(codec, MAX9850_CLOCK, 0x8);
+ else
+ return -EINVAL;
+
+ max9850->sysclk = freq;
+ return 0;
+}
+
+static int max9850_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 da = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ da |= MAX9850_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ da |= MAX9850_DLY;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ da |= MAX9850_RTJ;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ da |= MAX9850_BCINV | MAX9850_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ da |= MAX9850_BCINV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ da |= MAX9850_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set da */
+ snd_soc_write(codec, MAX9850_DIGITAL_AUDIO, da);
+
+ return 0;
+}
+
+static int max9850_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = snd_soc_cache_sync(codec);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to sync cache: %d\n", ret);
+ return ret;
+ }
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define MAX9850_RATES SNDRV_PCM_RATE_8000_48000
+
+#define MAX9850_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max9850_dai_ops = {
+ .hw_params = max9850_hw_params,
+ .set_sysclk = max9850_set_dai_sysclk,
+ .set_fmt = max9850_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver max9850_dai = {
+ .name = "max9850-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX9850_RATES,
+ .formats = MAX9850_FORMATS
+ },
+ .ops = &max9850_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int max9850_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ max9850_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int max9850_resume(struct snd_soc_codec *codec)
+{
+ max9850_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define max9850_suspend NULL
+#define max9850_resume NULL
+#endif
+
+static int max9850_probe(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ /* enable zero-detect */
+ snd_soc_update_bits(codec, MAX9850_GENERAL_PURPOSE, 1, 1);
+ /* enable slew-rate control */
+ snd_soc_update_bits(codec, MAX9850_VOLUME, 0x40, 0x40);
+ /* set slew-rate 125ms */
+ snd_soc_update_bits(codec, MAX9850_CHARGE_PUMP, 0xff, 0xc0);
+
+ snd_soc_dapm_new_controls(dapm, max9850_dapm_widgets,
+ ARRAY_SIZE(max9850_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_add_controls(codec, max9850_controls,
+ ARRAY_SIZE(max9850_controls));
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_max9850 = {
+ .probe = max9850_probe,
+ .suspend = max9850_suspend,
+ .resume = max9850_resume,
+ .set_bias_level = max9850_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(max9850_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = max9850_reg,
+ .volatile_register = max9850_volatile_register,
+};
+
+static int __devinit max9850_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct max9850_priv *max9850;
+ int ret;
+
+ max9850 = kzalloc(sizeof(struct max9850_priv), GFP_KERNEL);
+ if (max9850 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, max9850);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_max9850, &max9850_dai, 1);
+ if (ret < 0)
+ kfree(max9850);
+ return ret;
+}
+
+static __devexit int max9850_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id max9850_i2c_id[] = {
+ { "max9850", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max9850_i2c_id);
+
+static struct i2c_driver max9850_i2c_driver = {
+ .driver = {
+ .name = "max9850",
+ .owner = THIS_MODULE,
+ },
+ .probe = max9850_i2c_probe,
+ .remove = __devexit_p(max9850_i2c_remove),
+ .id_table = max9850_i2c_id,
+};
+
+static int __init max9850_init(void)
+{
+ return i2c_add_driver(&max9850_i2c_driver);
+}
+module_init(max9850_init);
+
+static void __exit max9850_exit(void)
+{
+ i2c_del_driver(&max9850_i2c_driver);
+}
+module_exit(max9850_exit);
+
+MODULE_AUTHOR("Christian Glindkamp <christian.glindkamp@taskit.de>");
+MODULE_DESCRIPTION("ASoC MAX9850 codec driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max9850.h b/sound/soc/codecs/max9850.h
new file mode 100644
index 00000000..72b1ddb0
--- /dev/null
+++ b/sound/soc/codecs/max9850.h
@@ -0,0 +1,38 @@
+/*
+ * max9850.h -- codec driver for max9850
+ *
+ * Copyright (C) 2011 taskit GmbH
+ * Author: Christian Glindkamp <christian.glindkamp@taskit.de>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _MAX9850_H
+#define _MAX9850_H
+
+#define MAX9850_STATUSA 0x00
+#define MAX9850_STATUSB 0x01
+#define MAX9850_VOLUME 0x02
+#define MAX9850_GENERAL_PURPOSE 0x03
+#define MAX9850_INTERRUPT 0x04
+#define MAX9850_ENABLE 0x05
+#define MAX9850_CLOCK 0x06
+#define MAX9850_CHARGE_PUMP 0x07
+#define MAX9850_LRCLK_MSB 0x08
+#define MAX9850_LRCLK_LSB 0x09
+#define MAX9850_DIGITAL_AUDIO 0x0a
+
+#define MAX9850_CACHEREGNUM 11
+
+/* MAX9850_DIGITAL_AUDIO */
+#define MAX9850_MASTER (1<<7)
+#define MAX9850_INV (1<<6)
+#define MAX9850_BCINV (1<<5)
+#define MAX9850_DLY (1<<3)
+#define MAX9850_RTJ (1<<2)
+
+#endif
diff --git a/sound/soc/codecs/max9877.c b/sound/soc/codecs/max9877.c
new file mode 100644
index 00000000..9e7e964a
--- /dev/null
+++ b/sound/soc/codecs/max9877.c
@@ -0,0 +1,308 @@
+/*
+ * max9877.c -- amp driver for max9877
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "max9877.h"
+
+static struct i2c_client *i2c;
+
+static u8 max9877_regs[5] = { 0x40, 0x00, 0x00, 0x00, 0x49 };
+
+static void max9877_write_regs(void)
+{
+ unsigned int i;
+ u8 data[6];
+
+ data[0] = MAX9877_INPUT_MODE;
+ for (i = 0; i < ARRAY_SIZE(max9877_regs); i++)
+ data[i + 1] = max9877_regs[i];
+
+ if (i2c_master_send(i2c, data, 6) != 6)
+ dev_err(&i2c->dev, "i2c write failed\n");
+}
+
+static int max9877_get_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int mask = mc->max;
+ unsigned int invert = mc->invert;
+
+ ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask;
+
+ if (invert)
+ ucontrol->value.integer.value[0] =
+ mask - ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int max9877_set_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int mask = mc->max;
+ unsigned int invert = mc->invert;
+ unsigned int val = (ucontrol->value.integer.value[0] & mask);
+
+ if (invert)
+ val = mask - val;
+
+ if (((max9877_regs[reg] >> shift) & mask) == val)
+ return 0;
+
+ max9877_regs[reg] &= ~(mask << shift);
+ max9877_regs[reg] |= val << shift;
+ max9877_write_regs();
+
+ return 1;
+}
+
+static int max9877_get_2reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ unsigned int shift = mc->shift;
+ unsigned int mask = mc->max;
+
+ ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask;
+ ucontrol->value.integer.value[1] = (max9877_regs[reg2] >> shift) & mask;
+
+ return 0;
+}
+
+static int max9877_set_2reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ unsigned int shift = mc->shift;
+ unsigned int mask = mc->max;
+ unsigned int val = (ucontrol->value.integer.value[0] & mask);
+ unsigned int val2 = (ucontrol->value.integer.value[1] & mask);
+ unsigned int change = 1;
+
+ if (((max9877_regs[reg] >> shift) & mask) == val)
+ change = 0;
+
+ if (((max9877_regs[reg2] >> shift) & mask) == val2)
+ change = 0;
+
+ if (change) {
+ max9877_regs[reg] &= ~(mask << shift);
+ max9877_regs[reg] |= val << shift;
+ max9877_regs[reg2] &= ~(mask << shift);
+ max9877_regs[reg2] |= val2 << shift;
+ max9877_write_regs();
+ }
+
+ return change;
+}
+
+static int max9877_get_out_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ u8 value = max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK;
+
+ if (value)
+ value -= 1;
+
+ ucontrol->value.integer.value[0] = value;
+ return 0;
+}
+
+static int max9877_set_out_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ u8 value = ucontrol->value.integer.value[0];
+
+ value += 1;
+
+ if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK) == value)
+ return 0;
+
+ max9877_regs[MAX9877_OUTPUT_MODE] &= ~MAX9877_OUTMODE_MASK;
+ max9877_regs[MAX9877_OUTPUT_MODE] |= value;
+ max9877_write_regs();
+ return 1;
+}
+
+static int max9877_get_osc_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ u8 value = (max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK);
+
+ value = value >> MAX9877_OSC_OFFSET;
+
+ ucontrol->value.integer.value[0] = value;
+ return 0;
+}
+
+static int max9877_set_osc_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ u8 value = ucontrol->value.integer.value[0];
+
+ value = value << MAX9877_OSC_OFFSET;
+ if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK) == value)
+ return 0;
+
+ max9877_regs[MAX9877_OUTPUT_MODE] &= ~MAX9877_OSC_MASK;
+ max9877_regs[MAX9877_OUTPUT_MODE] |= value;
+ max9877_write_regs();
+ return 1;
+}
+
+static const unsigned int max9877_pgain_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 1, TLV_DB_SCALE_ITEM(0, 900, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(2000, 0, 0),
+};
+
+static const unsigned int max9877_output_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1),
+ 8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0),
+ 16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0),
+ 24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0),
+};
+
+static const char *max9877_out_mode[] = {
+ "INA -> SPK",
+ "INA -> HP",
+ "INA -> SPK and HP",
+ "INB -> SPK",
+ "INB -> HP",
+ "INB -> SPK and HP",
+ "INA + INB -> SPK",
+ "INA + INB -> HP",
+ "INA + INB -> SPK and HP",
+};
+
+static const char *max9877_osc_mode[] = {
+ "1176KHz",
+ "1100KHz",
+ "700KHz",
+};
+
+static const struct soc_enum max9877_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_out_mode), max9877_out_mode),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_osc_mode), max9877_osc_mode),
+};
+
+static const struct snd_kcontrol_new max9877_controls[] = {
+ SOC_SINGLE_EXT_TLV("MAX9877 PGAINA Playback Volume",
+ MAX9877_INPUT_MODE, 0, 2, 0,
+ max9877_get_reg, max9877_set_reg, max9877_pgain_tlv),
+ SOC_SINGLE_EXT_TLV("MAX9877 PGAINB Playback Volume",
+ MAX9877_INPUT_MODE, 2, 2, 0,
+ max9877_get_reg, max9877_set_reg, max9877_pgain_tlv),
+ SOC_SINGLE_EXT_TLV("MAX9877 Amp Speaker Playback Volume",
+ MAX9877_SPK_VOLUME, 0, 31, 0,
+ max9877_get_reg, max9877_set_reg, max9877_output_tlv),
+ SOC_DOUBLE_R_EXT_TLV("MAX9877 Amp HP Playback Volume",
+ MAX9877_HPL_VOLUME, MAX9877_HPR_VOLUME, 0, 31, 0,
+ max9877_get_2reg, max9877_set_2reg, max9877_output_tlv),
+ SOC_SINGLE_EXT("MAX9877 INB Stereo Switch",
+ MAX9877_INPUT_MODE, 4, 1, 1,
+ max9877_get_reg, max9877_set_reg),
+ SOC_SINGLE_EXT("MAX9877 INA Stereo Switch",
+ MAX9877_INPUT_MODE, 5, 1, 1,
+ max9877_get_reg, max9877_set_reg),
+ SOC_SINGLE_EXT("MAX9877 Zero-crossing detection Switch",
+ MAX9877_INPUT_MODE, 6, 1, 0,
+ max9877_get_reg, max9877_set_reg),
+ SOC_SINGLE_EXT("MAX9877 Bypass Mode Switch",
+ MAX9877_OUTPUT_MODE, 6, 1, 0,
+ max9877_get_reg, max9877_set_reg),
+ SOC_SINGLE_EXT("MAX9877 Shutdown Mode Switch",
+ MAX9877_OUTPUT_MODE, 7, 1, 1,
+ max9877_get_reg, max9877_set_reg),
+ SOC_ENUM_EXT("MAX9877 Output Mode", max9877_enum[0],
+ max9877_get_out_mode, max9877_set_out_mode),
+ SOC_ENUM_EXT("MAX9877 Oscillator Mode", max9877_enum[1],
+ max9877_get_osc_mode, max9877_set_osc_mode),
+};
+
+/* This function is called from ASoC machine driver */
+int max9877_add_controls(struct snd_soc_codec *codec)
+{
+ return snd_soc_add_controls(codec, max9877_controls,
+ ARRAY_SIZE(max9877_controls));
+}
+EXPORT_SYMBOL_GPL(max9877_add_controls);
+
+static int __devinit max9877_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ i2c = client;
+
+ max9877_write_regs();
+
+ return 0;
+}
+
+static __devexit int max9877_i2c_remove(struct i2c_client *client)
+{
+ i2c = NULL;
+
+ return 0;
+}
+
+static const struct i2c_device_id max9877_i2c_id[] = {
+ { "max9877", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max9877_i2c_id);
+
+static struct i2c_driver max9877_i2c_driver = {
+ .driver = {
+ .name = "max9877",
+ .owner = THIS_MODULE,
+ },
+ .probe = max9877_i2c_probe,
+ .remove = __devexit_p(max9877_i2c_remove),
+ .id_table = max9877_i2c_id,
+};
+
+static int __init max9877_init(void)
+{
+ return i2c_add_driver(&max9877_i2c_driver);
+}
+module_init(max9877_init);
+
+static void __exit max9877_exit(void)
+{
+ i2c_del_driver(&max9877_i2c_driver);
+}
+module_exit(max9877_exit);
+
+MODULE_DESCRIPTION("ASoC MAX9877 amp driver");
+MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max9877.h b/sound/soc/codecs/max9877.h
new file mode 100644
index 00000000..6da72290
--- /dev/null
+++ b/sound/soc/codecs/max9877.h
@@ -0,0 +1,37 @@
+/*
+ * max9877.h -- amp driver for max9877
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _MAX9877_H
+#define _MAX9877_H
+
+#define MAX9877_INPUT_MODE 0x00
+#define MAX9877_SPK_VOLUME 0x01
+#define MAX9877_HPL_VOLUME 0x02
+#define MAX9877_HPR_VOLUME 0x03
+#define MAX9877_OUTPUT_MODE 0x04
+
+/* MAX9877_INPUT_MODE */
+#define MAX9877_INB (1 << 4)
+#define MAX9877_INA (1 << 5)
+#define MAX9877_ZCD (1 << 6)
+
+/* MAX9877_OUTPUT_MODE */
+#define MAX9877_OUTMODE_MASK (15 << 0)
+#define MAX9877_OSC_MASK (3 << 4)
+#define MAX9877_OSC_OFFSET 4
+#define MAX9877_BYPASS (1 << 6)
+#define MAX9877_SHDN (1 << 7)
+
+extern int max9877_add_controls(struct snd_soc_codec *codec);
+
+#endif
diff --git a/sound/soc/codecs/mxc_hdmi.c b/sound/soc/codecs/mxc_hdmi.c
new file mode 100644
index 00000000..9590181d
--- /dev/null
+++ b/sound/soc/codecs/mxc_hdmi.c
@@ -0,0 +1,638 @@
+/*
+ * MXC HDMI ALSA Soc Codec Driver
+ *
+ * Copyright (C) 2011-2012 Freescale Semiconductor, Inc.
+ *
+ * Some code from patch_hdmi.c
+ * Copyright (c) 2008-2010 Intel Corporation. All rights reserved.
+ * Copyright (c) 2006 ATI Technologies Inc.
+ * Copyright (c) 2008 NVIDIA Corp. All rights reserved.
+ * Copyright (c) 2008 Wei Ni <wni@nvidia.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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/fsl_devices.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/asoundef.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <mach/hardware.h>
+
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <mach/mxc_edid.h>
+#include <mach/mxc_hdmi.h>
+#include "../imx/imx-hdmi.h"
+
+struct mxc_hdmi_priv {
+ struct snd_soc_codec codec;
+ struct snd_card *card; /* ALSA HDMI sound card handle */
+ struct snd_pcm *pcm; /* ALSA hdmi driver type handle */
+ struct clk *isfr_clk;
+ struct clk *iahb_clk;
+};
+
+static struct mxc_edid_cfg edid_cfg;
+
+static unsigned int playback_rates[HDMI_MAX_RATES];
+static unsigned int playback_sample_size[HDMI_MAX_SAMPLE_SIZE];
+static unsigned int playback_channels[HDMI_MAX_CHANNEL_CONSTRAINTS];
+
+static struct snd_pcm_hw_constraint_list playback_constraint_rates;
+static struct snd_pcm_hw_constraint_list playback_constraint_bits;
+static struct snd_pcm_hw_constraint_list playback_constraint_channels;
+
+#ifdef DEBUG
+static void dumpregs(void)
+{
+ int n, cts;
+
+ cts = (hdmi_readb(HDMI_AUD_CTS3) << 16) |
+ (hdmi_readb(HDMI_AUD_CTS2) << 8) |
+ hdmi_readb(HDMI_AUD_CTS1);
+
+ n = (hdmi_readb(HDMI_AUD_N3) << 16) |
+ (hdmi_readb(HDMI_AUD_N2) << 8) |
+ hdmi_readb(HDMI_AUD_N1);
+
+ pr_debug("\n");
+ pr_debug("HDMI_PHY_CONF0 0x%02x\n", hdmi_readb(HDMI_PHY_CONF0));
+ pr_debug("HDMI_MC_CLKDIS 0x%02x\n", hdmi_readb(HDMI_MC_CLKDIS));
+ pr_debug("HDMI_AUD_N[1-3] 0x%06x (decimal %d)\n", n, n);
+ pr_debug("HDMI_AUD_CTS[1-3] 0x%06x (decimal %d)\n", cts, cts);
+ pr_debug("HDMI_FC_AUDSCONF 0x%02x\n", hdmi_readb(HDMI_FC_AUDSCONF));
+}
+#else
+static void dumpregs(void) {}
+#endif
+
+enum cea_speaker_placement {
+ FL = (1 << 0), /* Front Left */
+ FC = (1 << 1), /* Front Center */
+ FR = (1 << 2), /* Front Right */
+ FLC = (1 << 3), /* Front Left Center */
+ FRC = (1 << 4), /* Front Right Center */
+ RL = (1 << 5), /* Rear Left */
+ RC = (1 << 6), /* Rear Center */
+ RR = (1 << 7), /* Rear Right */
+ RLC = (1 << 8), /* Rear Left Center */
+ RRC = (1 << 9), /* Rear Right Center */
+ LFE = (1 << 10), /* Low Frequency Effect */
+ FLW = (1 << 11), /* Front Left Wide */
+ FRW = (1 << 12), /* Front Right Wide */
+ FLH = (1 << 13), /* Front Left High */
+ FCH = (1 << 14), /* Front Center High */
+ FRH = (1 << 15), /* Front Right High */
+ TC = (1 << 16), /* Top Center */
+};
+
+/*
+ * EDID SA bits in the CEA Speaker Allocation data block
+ */
+static int edid_speaker_allocation_bits[] = {
+ [0] = FL | FR,
+ [1] = LFE,
+ [2] = FC,
+ [3] = RL | RR,
+ [4] = RC,
+ [5] = FLC | FRC,
+ [6] = RLC | RRC,
+ [7] = FLW | FRW,
+ [8] = FLH | FRH,
+ [9] = TC,
+ [10] = FCH,
+};
+
+struct cea_channel_speaker_allocation {
+ int ca_index;
+ int speakers[8];
+
+ /* derived values, just for convenience */
+ int channels;
+ int spk_mask;
+};
+
+/*
+ * This is an ordered list!
+ *
+ * The preceding ones have better chances to be selected by
+ * hdmi_channel_allocation().
+ */
+static struct cea_channel_speaker_allocation channel_allocations[] = {
+/* channel: 7 6 5 4 3 2 1 0 */
+{ .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL } },
+ /* 2.1 */
+{ .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL } },
+ /* Dolby Surround */
+{ .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL } },
+{ .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL } },
+{ .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL } },
+{ .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL } },
+{ .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL } },
+{ .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL } },
+{ .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL } },
+ /* surround51 */
+{ .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL } },
+ /* 6.1 */
+{ .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL } },
+ /* surround71 */
+{ .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL } },
+{ .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL } },
+{ .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL } },
+{ .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL } },
+{ .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL } },
+{ .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL } },
+{ .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL } },
+{ .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL } },
+{ .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL } },
+};
+
+/*
+ * Compute derived values in channel_allocations[].
+ */
+static void init_channel_allocations(void)
+{
+ int i, j;
+ struct cea_channel_speaker_allocation *p;
+
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ p = channel_allocations + i;
+ p->channels = 0;
+ p->spk_mask = 0;
+ for (j = 0; j < ARRAY_SIZE(p->speakers); j++)
+ if (p->speakers[j]) {
+ p->channels++;
+ p->spk_mask |= p->speakers[j];
+ }
+ }
+}
+
+/*
+ * The transformation takes two steps:
+ *
+ * speaker_alloc => (edid_speaker_allocation_bits[]) => spk_mask
+ * spk_mask => (channel_allocations[]) => CA
+ *
+ * TODO: it could select the wrong CA from multiple candidates.
+*/
+static int hdmi_channel_allocation(int channels)
+{
+ int i;
+ int ca = 0;
+ int spk_mask = 0;
+
+ /*
+ * CA defaults to 0 for basic stereo audio
+ */
+ if (channels <= 2)
+ return 0;
+
+ /*
+ * expand EDID's speaker allocation mask
+ *
+ * EDID tells the speaker mask in a compact(paired) form,
+ * expand EDID's notions to match the ones used by Audio InfoFrame.
+ */
+ for (i = 0; i < ARRAY_SIZE(edid_speaker_allocation_bits); i++) {
+ if (edid_cfg.speaker_alloc & (1 << i))
+ spk_mask |= edid_speaker_allocation_bits[i];
+ }
+
+ /* search for the first working match in the CA table */
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ if (channels == channel_allocations[i].channels &&
+ (spk_mask & channel_allocations[i].spk_mask) ==
+ channel_allocations[i].spk_mask) {
+ ca = channel_allocations[i].ca_index;
+ break;
+ }
+ }
+
+ return ca;
+}
+
+static void hdmi_set_audio_flat(u8 value)
+{
+ /* Indicates the subpacket represents a flatline sample */
+ hdmi_mask_writeb(value, HDMI_FC_AUDSCONF,
+ HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_OFFSET,
+ HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_MASK);
+}
+
+static void hdmi_set_layout(unsigned int channels)
+{
+ hdmi_mask_writeb((channels > 2) ? 1 : 0, HDMI_FC_AUDSCONF,
+ HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_OFFSET,
+ HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK);
+}
+
+static void hdmi_set_audio_infoframe(unsigned int channels)
+{
+ unsigned char audiconf0, audiconf2;
+
+ /* From CEA-861-D spec:
+ * NOTE: HDMI requires the CT, SS and SF fields to be set to 0 ("Refer
+ * to Stream Header") as these items are carried in the audio stream.
+ *
+ * So we only set the CC and CA fields.
+ */
+ audiconf0 = ((channels - 1) << HDMI_FC_AUDICONF0_CC_OFFSET) &
+ HDMI_FC_AUDICONF0_CC_MASK;
+
+ audiconf2 = hdmi_channel_allocation(channels);
+
+ hdmi_writeb(audiconf0, HDMI_FC_AUDICONF0);
+ hdmi_writeb(0, HDMI_FC_AUDICONF1);
+ hdmi_writeb(audiconf2, HDMI_FC_AUDICONF2);
+ hdmi_writeb(0, HDMI_FC_AUDICONF3);
+}
+
+static int cea_audio_rates[HDMI_MAX_RATES] = {
+ 32000,
+ 44100,
+ 48000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+};
+
+static void mxc_hdmi_get_playback_rates(void)
+{
+ int i, count = 0;
+ u8 rates;
+
+ /* always assume basic audio support */
+ rates = edid_cfg.sample_rates | 0x7;
+
+ for (i = 0 ; i < HDMI_MAX_RATES ; i++)
+ if ((rates & (1 << i)) != 0)
+ playback_rates[count++] = cea_audio_rates[i];
+
+ playback_constraint_rates.list = playback_rates;
+ playback_constraint_rates.count = count;
+
+#ifdef DEBUG
+ for (i = 0 ; i < playback_constraint_rates.count ; i++)
+ pr_debug("%s: constraint = %d Hz\n", __func__,
+ playback_rates[i]);
+#endif
+}
+
+static void mxc_hdmi_get_playback_sample_size(void)
+{
+ int i = 0;
+
+ /* always assume basic audio support */
+ playback_sample_size[i++] = 16;
+
+ if (edid_cfg.sample_sizes & 0x4)
+ playback_sample_size[i++] = 24;
+
+ playback_constraint_bits.list = playback_sample_size;
+ playback_constraint_bits.count = i;
+
+#ifdef DEBUG
+ for (i = 0 ; i < playback_constraint_bits.count ; i++)
+ pr_debug("%s: constraint = %d bits\n", __func__,
+ playback_sample_size[i]);
+#endif
+}
+
+static void mxc_hdmi_get_playback_channels(void)
+{
+ int channels = 2, i = 0;
+
+ /* always assume basic audio support */
+ playback_channels[i++] = channels;
+ channels += 2;
+
+ while ((i < HDMI_MAX_CHANNEL_CONSTRAINTS) &&
+ (channels <= edid_cfg.max_channels)) {
+ playback_channels[i++] = channels;
+ channels += 2;
+ }
+
+ playback_constraint_channels.list = playback_channels;
+ playback_constraint_channels.count = i;
+
+#ifdef DEBUG
+ for (i = 0 ; i < playback_constraint_channels.count ; i++)
+ pr_debug("%s: constraint = %d channels\n", __func__,
+ playback_channels[i]);
+#endif
+}
+
+static int mxc_hdmi_update_constraints(struct mxc_hdmi_priv *priv,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ hdmi_get_edid_cfg(&edid_cfg);
+
+ mxc_hdmi_get_playback_rates();
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &playback_constraint_rates);
+ if (ret < 0)
+ return ret;
+
+ mxc_hdmi_get_playback_sample_size();
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ &playback_constraint_bits);
+ if (ret < 0)
+ return ret;
+
+ mxc_hdmi_get_playback_channels();
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &playback_constraint_channels);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int mxc_hdmi_codec_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mxc_hdmi_priv *hdmi_priv = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ clk_enable(hdmi_priv->isfr_clk);
+ clk_enable(hdmi_priv->iahb_clk);
+
+ pr_debug("%s hdmi clks: isfr:%d iahb:%d\n", __func__,
+ (int)clk_get_rate(hdmi_priv->isfr_clk),
+ (int)clk_get_rate(hdmi_priv->iahb_clk));
+
+ ret = mxc_hdmi_update_constraints(hdmi_priv, substream);
+ if (ret < 0)
+ return ret;
+
+ hdmi_set_audio_flat(0);
+
+ return 0;
+}
+
+static int mxc_hdmi_codec_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ hdmi_set_audio_infoframe(runtime->channels);
+ hdmi_set_layout(runtime->channels);
+ hdmi_set_sample_rate(runtime->rate);
+ dumpregs();
+
+ return 0;
+}
+
+static void mxc_hdmi_codec_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mxc_hdmi_priv *hdmi_priv = snd_soc_dai_get_drvdata(dai);
+
+ clk_disable(hdmi_priv->iahb_clk);
+ clk_disable(hdmi_priv->isfr_clk);
+}
+
+/*
+ * IEC60958 status functions
+ */
+static int mxc_hdmi_iec_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+
+ return 0;
+}
+
+static int mxc_hdmi_iec_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+
+ for (i = 0 ; i < 4 ; i++)
+ uvalue->value.iec958.status[i] = iec_header.status[i];
+
+ return 0;
+}
+
+static int mxc_hdmi_iec_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+
+ /* Do not allow professional mode */
+ if (uvalue->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL)
+ return -EPERM;
+
+ for (i = 0 ; i < 4 ; i++) {
+ iec_header.status[i] = uvalue->value.iec958.status[i];
+ pr_debug("%s status[%d]=0x%02x\n", __func__, i,
+ iec_header.status[i]);
+ }
+
+ return 0;
+}
+
+static struct snd_kcontrol_new mxc_hdmi_ctrls[] = {
+ /* status cchanel controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mxc_hdmi_iec_info,
+ .get = mxc_hdmi_iec_get,
+ .put = mxc_hdmi_iec_put,
+ },
+};
+
+static struct snd_soc_dai_ops mxc_hdmi_codec_dai_ops = {
+ .startup = mxc_hdmi_codec_startup,
+ .prepare = mxc_hdmi_codec_prepare,
+ .shutdown = mxc_hdmi_codec_shutdown,
+};
+
+static struct snd_soc_dai_driver mxc_hdmi_codec_dai = {
+ .name = "mxc-hdmi-soc",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = MXC_HDMI_RATES_PLAYBACK,
+ .formats = MXC_HDMI_FORMATS_PLAYBACK,
+ },
+ .ops = &mxc_hdmi_codec_dai_ops,
+};
+
+static int mxc_hdmi_codec_soc_probe(struct snd_soc_codec *codec)
+{
+ struct mxc_hdmi_priv *hdmi_priv;
+ int ret = 0;
+
+ if (!hdmi_get_registered())
+ return -ENOMEM;
+
+ hdmi_priv = kzalloc(sizeof(struct mxc_hdmi_priv), GFP_KERNEL);
+ if (hdmi_priv == NULL)
+ return -ENOMEM;
+
+ hdmi_priv->isfr_clk = clk_get(NULL, "hdmi_isfr_clk");
+ if (IS_ERR(hdmi_priv->isfr_clk)) {
+ ret = PTR_ERR(hdmi_priv->isfr_clk);
+ pr_err("%s Unable to get HDMI isfr clk: %d\n", __func__, ret);
+ goto e_clk_get1;
+ }
+
+ hdmi_priv->iahb_clk = clk_get(NULL, "hdmi_iahb_clk");
+ if (IS_ERR(hdmi_priv->iahb_clk)) {
+ ret = PTR_ERR(hdmi_priv->iahb_clk);
+ pr_err("%s Unable to get HDMI iahb clk: %d\n", __func__, ret);
+ goto e_clk_get2;
+ }
+
+ ret = snd_soc_add_controls(codec, mxc_hdmi_ctrls,
+ ARRAY_SIZE(mxc_hdmi_ctrls));
+ if (ret)
+ goto e_add_ctrls;
+
+ init_channel_allocations();
+
+ snd_soc_codec_set_drvdata(codec, hdmi_priv);
+
+ return 0;
+
+e_add_ctrls:
+ clk_put(hdmi_priv->iahb_clk);
+e_clk_get2:
+ clk_put(hdmi_priv->isfr_clk);
+e_clk_get1:
+ kfree(hdmi_priv);
+ return ret;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_hdmi = {
+ .probe = mxc_hdmi_codec_soc_probe,
+};
+
+static int __devinit mxc_hdmi_codec_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ dev_info(&pdev->dev, "MXC HDMI Audio\n");
+
+ ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_hdmi,
+ &mxc_hdmi_codec_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register codec\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit mxc_hdmi_codec_remove(struct platform_device *pdev)
+{
+ struct mxc_hdmi_priv *hdmi_priv = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_codec(&pdev->dev);
+ platform_set_drvdata(pdev, NULL);
+ kfree(hdmi_priv);
+
+ return 0;
+}
+
+struct platform_driver mxc_hdmi_driver = {
+ .driver = {
+ .name = "mxc_hdmi_soc",
+ .owner = THIS_MODULE,
+ },
+ .probe = mxc_hdmi_codec_probe,
+ .remove = __devexit_p(mxc_hdmi_codec_remove),
+};
+
+static int __init mxc_hdmi_codec_init(void)
+{
+ return platform_driver_register(&mxc_hdmi_driver);
+}
+
+static void __exit mxc_hdmi_codec_exit(void)
+{
+ return platform_driver_unregister(&mxc_hdmi_driver);
+}
+
+module_init(mxc_hdmi_codec_init);
+module_exit(mxc_hdmi_codec_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC HDMI Audio");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/mxc_spdif.c b/sound/soc/codecs/mxc_spdif.c
new file mode 100644
index 00000000..77e62aef
--- /dev/null
+++ b/sound/soc/codecs/mxc_spdif.c
@@ -0,0 +1,1392 @@
+/*
+ * MXC SPDIF ALSA Soc Codec Driver
+ *
+ * Copyright (C) 2007-2013 Freescale Semiconductor, Inc.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/fsl_devices.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/asoundef.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <mach/hardware.h>
+
+#include "mxc_spdif.h"
+
+#define MXC_SPDIF_DEBUG 0
+
+static unsigned int gainsel_multi[GAINSEL_MULTI_MAX] = {
+ 24 * 1024, 16 * 1024, 12 * 1024,
+ 8 * 1024, 6 * 1024, 4 * 1024,
+ 3 * 1024,
+};
+
+/*
+ * SPDIF control structure
+ * Defines channel status, subcode and Q sub
+ */
+struct spdif_mixer_control {
+ /* spinlock to access control data */
+ spinlock_t ctl_lock;
+ /* IEC958 channel tx status bit */
+ unsigned char ch_status[4];
+ /* User bits */
+ unsigned char subcode[2 * SPDIF_UBITS_SIZE];
+ /* Q subcode part of user bits */
+ unsigned char qsub[2 * SPDIF_QSUB_SIZE];
+ /* buffer ptrs for writer */
+ unsigned int upos;
+ unsigned int qpos;
+ /* ready buffer index of the two buffers */
+ unsigned int ready_buf;
+};
+
+struct mxc_spdif_priv {
+ struct snd_soc_codec codec;
+ struct mxc_spdif_platform_data *plat_data;
+ unsigned long __iomem *reg_base;
+ unsigned long reg_phys_base;
+ struct snd_card *card; /* ALSA SPDIF sound card handle */
+ struct snd_pcm *pcm; /* ALSA spdif driver type handle */
+ atomic_t dpll_locked; /* DPLL locked status */
+ bool tx_active;
+ bool rx_active;
+};
+
+struct spdif_mixer_control mxc_spdif_control;
+
+static unsigned long spdif_base_addr;
+
+#if MXC_SPDIF_DEBUG
+static void dumpregs(struct mxc_spdif_priv *priv)
+{
+ unsigned int value, i;
+
+ if (!priv->tx_active && !priv->rx_active)
+ clk_enable(priv->plat_data->spdif_core_clk);
+
+ for (i = 0 ; i <= 0x38 ; i += 4) {
+ value = readl(spdif_base_addr + i) & 0xffffff;
+ pr_debug("reg 0x%02x = 0x%06x\n", i, value);
+ }
+
+ i = 0x44;
+ value = readl(spdif_base_addr + i) & 0xffffff;
+ pr_debug("reg 0x%02x = 0x%06x\n", i, value);
+
+ i = 0x50;
+ value = readl(spdif_base_addr + i) & 0xffffff;
+ pr_debug("reg 0x%02x = 0x%06x\n", i, value);
+
+ if (!priv->tx_active && !priv->rx_active)
+ clk_disable(priv->plat_data->spdif_core_clk);
+}
+#else
+static void dumpregs(struct mxc_spdif_priv *priv) {}
+#endif
+
+/* define each spdif interrupt handlers */
+typedef void (*spdif_irq_func_t) (unsigned int bit, void *desc);
+
+/* spdif irq functions declare */
+static void spdif_irq_fifo(unsigned int bit, void *devid);
+static void spdif_irq_dpll_lock(unsigned int bit, void *devid);
+static void spdif_irq_uq(unsigned int bit, void *devid);
+static void spdif_irq_bit_error(unsigned int bit, void *devid);
+static void spdif_irq_sym_error(unsigned int bit, void *devid);
+static void spdif_irq_valnogood(unsigned int bit, void *devid);
+static void spdif_irq_cnew(unsigned int bit, void *devid);
+
+/* irq function list */
+static spdif_irq_func_t spdif_irq_handlers[] = {
+ spdif_irq_fifo,
+ spdif_irq_fifo,
+ spdif_irq_dpll_lock,
+ NULL,
+ spdif_irq_fifo,
+ spdif_irq_uq,
+ spdif_irq_uq,
+ spdif_irq_uq,
+ spdif_irq_uq,
+ spdif_irq_uq,
+ spdif_irq_uq,
+ NULL,
+ NULL,
+ NULL,
+ spdif_irq_bit_error,
+ spdif_irq_sym_error,
+ spdif_irq_valnogood,
+ spdif_irq_cnew,
+ NULL,
+ spdif_irq_fifo,
+ spdif_irq_dpll_lock,
+};
+
+static void spdif_intr_enable(unsigned long intr, int enable)
+{
+ unsigned long value;
+
+ value = __raw_readl(spdif_base_addr + SPDIF_REG_SIE) & 0xffffff;
+
+ if (enable)
+ value |= intr;
+ else
+ value &= ~intr;
+
+ __raw_writel(value, spdif_base_addr + SPDIF_REG_SIE);
+}
+
+/*
+ * Get spdif interrupt status and clear the interrupt
+ */
+static int spdif_intr_status(void)
+{
+ unsigned long value;
+
+ value = __raw_readl(SPDIF_REG_SIS + spdif_base_addr) & 0xffffff;
+ value &= __raw_readl(spdif_base_addr + SPDIF_REG_SIE);
+ __raw_writel(value, SPDIF_REG_SIC + spdif_base_addr);
+
+ return value;
+}
+
+static irqreturn_t spdif_isr(int irq, void *dev_id)
+{
+ unsigned long int_stat;
+ int line;
+
+ int_stat = spdif_intr_status();
+
+ while ((line = ffs(int_stat)) != 0) {
+ int_stat &= ~(1UL << (line - 1));
+ if (spdif_irq_handlers[line - 1] != NULL)
+ spdif_irq_handlers[line - 1] (line - 1, dev_id);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Rx FIFO Full, Underrun/Overrun interrupts
+ * Tx FIFO Empty, Underrun/Overrun interrupts
+ */
+static void spdif_irq_fifo(unsigned int bit, void *devid)
+{
+ pr_debug("irq %s\n", __func__);
+}
+
+/*
+ * DPLL locked and lock loss interrupt handler
+ */
+static void spdif_irq_dpll_lock(unsigned int bit, void *devid)
+{
+ struct mxc_spdif_priv *spdif_priv = (struct mxc_spdif_priv *)devid;
+ unsigned int locked = __raw_readl(spdif_base_addr + SPDIF_REG_SRPC);
+
+ if (locked & SRPC_DPLL_LOCKED) {
+ pr_debug("SPDIF Rx dpll locked\n");
+ atomic_set(&spdif_priv->dpll_locked, 1);
+ } else {
+ /* INT_LOSS_LOCK */
+ pr_debug("SPDIF Rx dpll loss lock\n");
+ atomic_set(&spdif_priv->dpll_locked, 0);
+ }
+}
+
+/*
+ * U/Q channel related interrupts handler
+ *
+ * U/QChannel full, overrun interrupts
+ * U/QChannel sync error and frame error interrupts
+ */
+static void spdif_irq_uq(unsigned int bit, void *devid)
+{
+ unsigned long val;
+ int index;
+ struct spdif_mixer_control *ctrl = &mxc_spdif_control;
+
+ bit = 1 << bit;
+ /* get U/Q channel datas */
+ switch (bit) {
+
+ case INT_URX_OV: /* read U data */
+ pr_debug("User bit receive overrun\n");
+ case INT_URX_FUL:
+ pr_debug("U bit receive full\n");
+
+ if (ctrl->upos >= SPDIF_UBITS_SIZE * 2) {
+ ctrl->upos = 0;
+ } else if (unlikely((ctrl->upos % SPDIF_UBITS_SIZE) + 3
+ > SPDIF_UBITS_SIZE)) {
+ pr_err("User bit receivce buffer overflow\n");
+ break;
+ }
+ val = __raw_readl(spdif_base_addr + SPDIF_REG_SQU);
+ ctrl->subcode[ctrl->upos++] = val >> 16;
+ ctrl->subcode[ctrl->upos++] = val >> 8;
+ ctrl->subcode[ctrl->upos++] = val;
+
+ break;
+
+ case INT_QRX_OV: /* read Q data */
+ pr_debug("Q bit receive overrun\n");
+ case INT_QRX_FUL:
+ pr_debug("Q bit receive full\n");
+
+ if (ctrl->qpos >= SPDIF_QSUB_SIZE * 2) {
+ ctrl->qpos = 0;
+ } else if (unlikely((ctrl->qpos % SPDIF_QSUB_SIZE) + 3
+ > SPDIF_QSUB_SIZE)) {
+ pr_debug("Q bit receivce buffer overflow\n");
+ break;
+ }
+ val = __raw_readl(spdif_base_addr + SPDIF_REG_SRQ);
+ ctrl->qsub[ctrl->qpos++] = val >> 16;
+ ctrl->qsub[ctrl->qpos++] = val >> 8;
+ ctrl->qsub[ctrl->qpos++] = val;
+
+ break;
+
+ case INT_UQ_ERR: /* read U/Q data and do buffer reset */
+ pr_debug("U/Q bit receive error\n");
+ val = __raw_readl(spdif_base_addr + SPDIF_REG_SQU);
+ val = __raw_readl(spdif_base_addr + SPDIF_REG_SRQ);
+ /* drop this U/Q buffer */
+ ctrl->ready_buf = 0;
+ ctrl->upos = 0;
+ ctrl->qpos = 0;
+ break;
+
+ case INT_UQ_SYNC: /* U/Q buffer reset */
+ pr_debug("U/Q sync receive\n");
+
+ if (ctrl->qpos == 0)
+ break;
+ index = (ctrl->qpos - 1) / SPDIF_QSUB_SIZE;
+ /* set ready to this buffer */
+ ctrl->ready_buf = index + 1;
+ break;
+ }
+}
+
+/*
+ * SPDIF receiver found parity bit error interrupt handler
+ */
+static void spdif_irq_bit_error(unsigned int bit, void *devid)
+{
+ pr_debug("SPDIF interrupt parity bit error\n");
+}
+
+/*
+ * SPDIF receiver found illegal symbol interrupt handler
+ */
+static void spdif_irq_sym_error(unsigned int bit, void *devid)
+{
+ struct mxc_spdif_priv *spdif_priv = (struct mxc_spdif_priv *)devid;
+
+ pr_debug("SPDIF interrupt symbol error\n");
+ if (!atomic_read(&spdif_priv->dpll_locked)) {
+ /* dpll unlocked seems no audio stream */
+ spdif_intr_enable(INT_SYM_ERR, 0);
+ }
+}
+
+/*
+ * SPDIF validity flag no good interrupt handler
+ */
+static void spdif_irq_valnogood(unsigned int bit, void *devid)
+{
+ pr_debug("SPDIF interrupt validate is not good\n");
+}
+
+/*
+ * SPDIF receive change in value of control channel
+ */
+static void spdif_irq_cnew(unsigned int bit, void *devid)
+{
+ pr_debug("SPDIF interrupt cstatus new\n");
+}
+
+static void spdif_softreset(void)
+{
+ unsigned long value = 1;
+ int cycle = 0;
+
+ __raw_writel(SCR_SOFT_RESET, spdif_base_addr + SPDIF_REG_SCR);
+
+ while (value && (cycle++ < 10))
+ value = __raw_readl(spdif_base_addr + SPDIF_REG_SCR) & 0x1000;
+}
+
+static void spdif_set_clk_accuracy(u8 level)
+{
+ mxc_spdif_control.ch_status[3] &= ~IEC958_AES3_CON_CLOCK;
+ mxc_spdif_control.ch_status[3] |= level & IEC958_AES3_CON_CLOCK;
+}
+
+static void spdif_set_cstatus_fs(u8 cstatus_fs)
+{
+ /* set fs field in consumer channel status */
+ mxc_spdif_control.ch_status[3] &= ~IEC958_AES3_CON_FS;
+ mxc_spdif_control.ch_status[3] |= cstatus_fs & IEC958_AES3_CON_FS;
+}
+
+static u8 reverse_bits(u8 input)
+{
+ u8 i, output = 0;
+ for (i = 8 ; i > 0 ; i--) {
+ output <<= 1;
+ output |= input & 0x01;
+ input >>= 1;
+ }
+ return output;
+}
+
+static void spdif_write_channel_status(void)
+{
+ unsigned int ch_status;
+
+ ch_status =
+ (reverse_bits(mxc_spdif_control.ch_status[0]) << 16) |
+ (reverse_bits(mxc_spdif_control.ch_status[1]) << 8) |
+ reverse_bits(mxc_spdif_control.ch_status[2]);
+
+ __raw_writel(ch_status, SPDIF_REG_STCSCH + spdif_base_addr);
+
+ ch_status = reverse_bits(mxc_spdif_control.ch_status[3]) << 16;
+ __raw_writel(ch_status, SPDIF_REG_STCSCL + spdif_base_addr);
+
+ pr_debug("STCSCH: 0x%06x\n", __raw_readl(spdif_base_addr + SPDIF_REG_STCSCH));
+ pr_debug("STCSCL: 0x%06x\n", __raw_readl(spdif_base_addr + SPDIF_REG_STCSCL));
+}
+
+/*
+ * set SPDIF PhaseConfig register for rx clock
+ */
+static int spdif_set_rx_clksrc(enum spdif_clk_src clksrc,
+ enum spdif_gainsel gainsel, int dpll_locked)
+{
+ unsigned long value;
+
+ if (clksrc > SPDIF_CLK_SRC5 || gainsel > GAINSEL_MULTI_3)
+ return 1;
+
+ if (dpll_locked)
+ value = clksrc;
+ else
+ value = clksrc + SRPC_CLKSRC_SEL_LOCKED;
+
+ value = (value << SRPC_CLKSRC_SEL_OFFSET) |
+ (gainsel << SRPC_GAINSEL_OFFSET);
+
+ __raw_writel(value, spdif_base_addr + SPDIF_REG_SRPC);
+
+ return 0;
+}
+
+/*
+ * Get RX data clock rate given the SPDIF bus_clk
+ */
+static int spdif_get_rxclk_rate(struct clk *bus_clk, enum spdif_gainsel gainsel)
+{
+ unsigned long freqmeas, phaseconf, busclk_freq = 0;
+ u64 tmpval64;
+ enum spdif_clk_src clksrc;
+
+ freqmeas = __raw_readl(spdif_base_addr + SPDIF_REG_SRFM);
+ phaseconf = __raw_readl(spdif_base_addr + SPDIF_REG_SRPC);
+
+ clksrc = (phaseconf >> SRPC_CLKSRC_SEL_OFFSET) & 0x0F;
+ if (clksrc < 5 && (phaseconf & SRPC_DPLL_LOCKED)) {
+ /* get bus clock from system */
+ busclk_freq = clk_get_rate(bus_clk);
+ }
+
+ /* FreqMeas_CLK = (BUS_CLK*FreqMeas[23:0])/2^10/GAINSEL/128 */
+ tmpval64 = (u64) busclk_freq * freqmeas;
+ do_div(tmpval64, gainsel_multi[gainsel]);
+ do_div(tmpval64, 128 * 1024);
+
+ pr_debug("FreqMeas: %d\n", (int)freqmeas);
+ pr_debug("busclk_freq: %d\n", (int)busclk_freq);
+ pr_debug("rx rate: %d\n", (int)tmpval64);
+
+ return (int)tmpval64;
+}
+
+static int spdif_set_sample_rate(struct snd_soc_codec *codec, int sample_rate)
+{
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ unsigned long stc, clk = -1, div = 1, cstatus_fs = 0;
+ int clk_fs;
+
+ switch (sample_rate) {
+ case 44100:
+ clk_fs = 44100;
+ clk = plat_data->spdif_clk_44100;
+ div = plat_data->spdif_div_44100;
+ cstatus_fs = IEC958_AES3_CON_FS_44100;
+ break;
+
+ case 48000:
+ clk_fs = 48000;
+ clk = plat_data->spdif_clk_48000;
+ div = plat_data->spdif_div_48000;
+ cstatus_fs = IEC958_AES3_CON_FS_48000;
+ break;
+
+ case 32000:
+ /* use 48K clk for 32K */
+ clk_fs = 48000;
+ clk = plat_data->spdif_clk_48000;
+ div = plat_data->spdif_div_32000;
+ cstatus_fs = IEC958_AES3_CON_FS_32000;
+ break;
+
+ default:
+ pr_err("%s: unsupported sample rate %d\n", __func__, sample_rate);
+ return -EINVAL;
+ }
+
+ if (clk < 0) {
+ pr_info("%s: no defined %d clk src\n", __func__, clk_fs);
+ return -EINVAL;
+ }
+
+ /*
+ * The S/PDIF block needs a clock of 64 * fs * div. The S/PDIF block
+ * will divide by (div). So request 64 * fs * (div+1) which will
+ * get rounded.
+ */
+ if (plat_data->spdif_clk_set_rate)
+ plat_data->spdif_clk_set_rate(plat_data->spdif_clk,
+ 64 * sample_rate * (div + 1));
+
+#if MXC_SPDIF_DEBUG
+ pr_debug("%s wanted spdif clock rate = %d\n", __func__,
+ (int)(64 * sample_rate * div));
+ pr_debug("%s got spdif clock rate = %d\n", __func__,
+ (int)clk_get_rate(plat_data->spdif_clk));
+#endif
+
+ spdif_set_cstatus_fs(cstatus_fs);
+
+ /* select clock source and divisor */
+ stc = __raw_readl(SPDIF_REG_STC + spdif_base_addr) & ~0x7FF;
+ stc |= STC_TXCLK_SRC_EN | (clk << STC_TXCLK_SRC_OFFSET) | (div - 1);
+ __raw_writel(stc, SPDIF_REG_STC + spdif_base_addr);
+ pr_debug("set sample rate to %d\n", sample_rate);
+
+ return 0;
+}
+
+static unsigned int spdif_playback_rates[] = { 32000, 44100, 48000 };
+
+static unsigned int spdif_capture_rates[] = {
+ 16000, 32000, 44100, 48000, 64000, 96000
+};
+
+static struct snd_pcm_hw_constraint_list hw_playback_rates_stereo = {
+ .count = ARRAY_SIZE(spdif_playback_rates),
+ .list = spdif_playback_rates,
+ .mask = 0,
+};
+
+static struct snd_pcm_hw_constraint_list hw_capture_rates_stereo = {
+ .count = ARRAY_SIZE(spdif_capture_rates),
+ .list = spdif_capture_rates,
+ .mask = 0,
+};
+
+static int mxc_spdif_playback_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ int err = 0;
+
+ if (!plat_data->spdif_tx)
+ return -EINVAL;
+
+ spdif_priv->tx_active = true;
+ err = clk_enable(plat_data->spdif_clk);
+ if (err < 0)
+ goto failed_clk;
+
+ err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_playback_rates_stereo);
+ if (err < 0)
+ goto failed;
+ err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (err < 0)
+ goto failed;
+
+ return 0;
+
+failed:
+ clk_disable(plat_data->spdif_clk);
+failed_clk:
+ spdif_priv->tx_active = false;
+ return err;
+}
+
+static int mxc_spdif_playback_start(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long regval;
+ int err;
+
+ if (!plat_data->spdif_tx)
+ return -EINVAL;
+
+ spdif_priv->tx_active = true;
+ regval = __raw_readl(spdif_base_addr + SPDIF_REG_SCR);
+ regval &= 0xfc33e3;
+ regval &= ~SCR_LOW_POWER;
+ regval |= SCR_TXFIFO_AUTOSYNC | SCR_TXFIFO_NORMAL |
+ SCR_TXSEL_NORMAL | SCR_USRC_SEL_CHIP | (2 << SCR_TXFIFO_ESEL_BIT);
+ __raw_writel(regval, SPDIF_REG_SCR + spdif_base_addr);
+
+ /* Default clock source from EXTAL, divider by 8, generate 44.1kHz
+ sample rate */
+ __raw_writel(0x07, SPDIF_REG_STC + spdif_base_addr);
+
+ spdif_intr_enable(INT_TXFIFO_RESYNC, 1);
+
+ err = spdif_set_sample_rate(codec, runtime->rate);
+ if (err < 0) {
+ pr_info("%s - err < 0\n", __func__);
+ return err;
+ }
+ spdif_set_clk_accuracy(IEC958_AES3_CON_CLOCK_1000PPM);
+ spdif_write_channel_status();
+
+ pr_debug("SCR: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SCR));
+ pr_debug("SIE: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIE));
+ pr_debug("STC: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_STC));
+
+ regval = __raw_readl(SPDIF_REG_SCR + spdif_base_addr);
+ regval |= SCR_DMA_TX_EN;
+ __raw_writel(regval, SPDIF_REG_SCR + spdif_base_addr);
+
+ dumpregs(spdif_priv);
+ return 0;
+}
+
+static int mxc_spdif_playback_stop(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ unsigned long regval;
+
+ if (!plat_data->spdif_tx)
+ return -EINVAL;
+
+ dumpregs(spdif_priv);
+ pr_debug("SIS: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIS));
+
+ spdif_intr_status();
+ spdif_intr_enable(INT_TXFIFO_RESYNC, 0);
+
+ regval = __raw_readl(SPDIF_REG_SCR + spdif_base_addr) & 0xffffe3;
+ regval |= SCR_TXSEL_OFF;
+ __raw_writel(regval, SPDIF_REG_SCR + spdif_base_addr);
+ regval = __raw_readl(SPDIF_REG_STC + spdif_base_addr) & ~0x7FF;
+ regval |= (0x7 << STC_TXCLK_SRC_OFFSET);
+ __raw_writel(regval, SPDIF_REG_STC + spdif_base_addr);
+
+ regval = __raw_readl(SPDIF_REG_SCR + spdif_base_addr);
+ regval &= ~SCR_DMA_TX_EN;
+ regval |= SCR_LOW_POWER;
+ __raw_writel(regval, SPDIF_REG_SCR + spdif_base_addr);
+
+ spdif_priv->tx_active = false;
+
+ return 0;
+}
+
+static int mxc_spdif_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ int ret = 0;
+
+ if (!plat_data->spdif_tx)
+ return -EINVAL;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = mxc_spdif_playback_start(substream, dai);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = mxc_spdif_playback_stop(substream, dai);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int mxc_spdif_capture_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ int err = 0;
+
+ if (!plat_data->spdif_rx)
+ return -EINVAL;
+
+ spdif_priv->rx_active = true;
+
+ /* enable rx bus clock */
+ err = clk_enable(plat_data->spdif_clk);
+ if (err < 0)
+ goto failed_clk;
+
+ /* set hw param constraints */
+ err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_capture_rates_stereo);
+ if (err < 0)
+ goto failed;
+
+ err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (err < 0)
+ goto failed;
+
+ return 0;
+
+failed:
+ clk_disable(plat_data->spdif_clk);
+failed_clk:
+ spdif_priv->rx_active = false;
+ return err;
+}
+
+static int mxc_spdif_capture_start(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ unsigned long regval;
+
+ if (!plat_data->spdif_rx)
+ return -EINVAL;
+
+ spdif_priv->rx_active = true;
+
+ regval = __raw_readl(spdif_base_addr + SPDIF_REG_SCR);
+ /*
+ * initial and reset SPDIF configuration:
+ * RxFIFO off
+ * RxFIFO sel to 8 sample
+ * Autosync
+ * Valid bit set
+ */
+ regval &= ~(SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO | SCR_LOW_POWER);
+ regval |= (2 << SCR_RXFIFO_FSEL_BIT) | SCR_RXFIFO_AUTOSYNC;
+ __raw_writel(regval, spdif_base_addr + SPDIF_REG_SCR);
+
+ /* enable interrupts, include DPLL lock */
+ spdif_intr_enable(INT_SYM_ERR | INT_BIT_ERR | INT_URX_FUL |
+ INT_URX_OV | INT_QRX_FUL | INT_QRX_OV |
+ INT_UQ_SYNC | INT_UQ_ERR | INT_RX_RESYNC |
+ INT_LOSS_LOCK | INT_DPLL_LOCKED, 1);
+
+ /* setup rx clock source */
+ spdif_set_rx_clksrc(plat_data->spdif_rx_clk, SPDIF_DEFAULT_GAINSEL, 1);
+
+ regval = __raw_readl(SPDIF_REG_SCR + spdif_base_addr);
+ regval |= SCR_DMA_RX_EN;
+ __raw_writel(regval, SPDIF_REG_SCR + spdif_base_addr);
+
+ /* Debug: dump registers */
+ pr_debug("SCR: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SCR));
+ pr_debug("SIE: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIE));
+ pr_debug("SRPC: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SRPC));
+ pr_debug("FreqMeas: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SRFM));
+
+ return 0;
+}
+
+static int mxc_spdif_capture_stop(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ unsigned long regval;
+
+ if (!plat_data->spdif_rx || !spdif_priv->rx_active)
+ return -EINVAL;
+
+ pr_debug("SIS: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIS));
+ pr_debug("SRPC: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SRPC));
+ pr_debug("FreqMeas: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SRFM));
+
+ spdif_intr_enable(INT_DPLL_LOCKED | INT_SYM_ERR | INT_BIT_ERR |
+ INT_URX_FUL | INT_URX_OV | INT_QRX_FUL | INT_QRX_OV |
+ INT_UQ_SYNC | INT_UQ_ERR | INT_RX_RESYNC |
+ INT_LOSS_LOCK, 0);
+
+ regval = __raw_readl(SPDIF_REG_SCR + spdif_base_addr);
+ regval &= SCR_DMA_RX_EN;
+ __raw_writel(regval, SPDIF_REG_SCR + spdif_base_addr);
+
+ /* turn off RX fifo, disable dma and autosync */
+ regval = __raw_readl(spdif_base_addr + SPDIF_REG_SCR);
+ regval |= SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO | SCR_LOW_POWER;
+ regval &= ~(SCR_DMA_RX_EN | SCR_RXFIFO_AUTOSYNC);
+ __raw_writel(regval, spdif_base_addr + SPDIF_REG_SCR);
+
+ spdif_priv->rx_active = false;
+
+ return 0;
+}
+
+static int mxc_spdif_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ int ret = 0;
+
+ if (!plat_data->spdif_rx)
+ return -EINVAL;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = mxc_spdif_capture_start(substream, dai);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = mxc_spdif_capture_stop(substream, dai);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+/*
+ * MXC SPDIF IEC958 controller(mixer) functions
+ *
+ * Channel status get/put control
+ * User bit value get/put control
+ * Valid bit value get control
+ * DPLL lock status get control
+ * User bit sync mode selection control
+ */
+static int mxc_pb_spdif_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int mxc_pb_spdif_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ uvalue->value.iec958.status[0] = mxc_spdif_control.ch_status[0];
+ uvalue->value.iec958.status[1] = mxc_spdif_control.ch_status[1];
+ uvalue->value.iec958.status[2] = mxc_spdif_control.ch_status[2];
+ uvalue->value.iec958.status[3] = mxc_spdif_control.ch_status[3];
+ return 0;
+}
+
+static int mxc_pb_spdif_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+
+ mxc_spdif_control.ch_status[0] = uvalue->value.iec958.status[0];
+ mxc_spdif_control.ch_status[1] = uvalue->value.iec958.status[1];
+ mxc_spdif_control.ch_status[2] = uvalue->value.iec958.status[2];
+ mxc_spdif_control.ch_status[3] = uvalue->value.iec958.status[3];
+
+ clk_enable(plat_data->spdif_clk);
+
+ spdif_write_channel_status();
+
+ clk_disable(plat_data->spdif_clk);
+
+ return 0;
+}
+
+static int mxc_spdif_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+/*
+ * Get channel status from SPDIF_RX_CCHAN register
+ */
+static int mxc_spdif_capture_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ unsigned int cstatus;
+
+ clk_enable(plat_data->spdif_clk);
+
+ if (!(__raw_readl(spdif_base_addr + SPDIF_REG_SIS) & INT_CNEW))
+ return -EAGAIN;
+
+ cstatus = __raw_readl(spdif_base_addr + SPDIF_REG_SRCSLH);
+ ucontrol->value.iec958.status[0] = (cstatus >> 16) & 0xFF;
+ ucontrol->value.iec958.status[1] = (cstatus >> 8) & 0xFF;
+ ucontrol->value.iec958.status[2] = cstatus & 0xFF;
+ cstatus = __raw_readl(spdif_base_addr + SPDIF_REG_SRCSLL);
+ ucontrol->value.iec958.status[3] = (cstatus >> 16) & 0xFF;
+ ucontrol->value.iec958.status[4] = (cstatus >> 8) & 0xFF;
+ ucontrol->value.iec958.status[5] = cstatus & 0xFF;
+
+ /* clear intr */
+ __raw_writel(INT_CNEW, spdif_base_addr + SPDIF_REG_SIC);
+
+ clk_disable(plat_data->spdif_clk);
+
+ return 0;
+}
+
+/*
+ * Get User bits (subcode) from chip value which readed out
+ * in UChannel register.
+ */
+static int mxc_spdif_subcode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&mxc_spdif_control.ctl_lock, flags);
+ if (mxc_spdif_control.ready_buf) {
+ memcpy(&ucontrol->value.iec958.subcode[0],
+ &mxc_spdif_control.subcode[(mxc_spdif_control.ready_buf -
+ 1) * SPDIF_UBITS_SIZE],
+ SPDIF_UBITS_SIZE);
+ } else {
+ ret = -EAGAIN;
+ }
+ spin_unlock_irqrestore(&mxc_spdif_control.ctl_lock, flags);
+
+ return ret;
+}
+
+/*
+ * Q-subcode infomation.
+ * the byte size is SPDIF_UBITS_SIZE/8
+ */
+static int mxc_spdif_qinfo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = SPDIF_QSUB_SIZE;
+ return 0;
+}
+
+/*
+ * Get Q subcode from chip value which readed out
+ * in QChannel register.
+ */
+static int mxc_spdif_qget(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&mxc_spdif_control.ctl_lock, flags);
+ if (mxc_spdif_control.ready_buf) {
+ memcpy(&ucontrol->value.bytes.data[0],
+ &mxc_spdif_control.qsub[(mxc_spdif_control.ready_buf -
+ 1) * SPDIF_QSUB_SIZE],
+ SPDIF_QSUB_SIZE);
+ } else {
+ ret = -EAGAIN;
+ }
+ spin_unlock_irqrestore(&mxc_spdif_control.ctl_lock, flags);
+
+ return ret;
+}
+
+/*
+ * Valid bit infomation.
+ */
+static int mxc_spdif_vbit_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+/*
+ * Get valid good bit from interrupt status register.
+ */
+static int mxc_spdif_vbit_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ unsigned int int_val;
+
+ clk_enable(plat_data->spdif_clk);
+
+ int_val = __raw_readl(spdif_base_addr + SPDIF_REG_SIS);
+ ucontrol->value.integer.value[0] = (int_val & INT_VAL_NOGOOD) != 0;
+ __raw_writel(INT_VAL_NOGOOD, spdif_base_addr + SPDIF_REG_SIC);
+
+ clk_disable(plat_data->spdif_clk);
+
+ return 0;
+}
+
+/*
+ * DPLL lock infomation.
+ */
+static int mxc_spdif_rxrate_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 16000;
+ uinfo->value.integer.max = 96000;
+ return 0;
+}
+
+/*
+ * Get DPLL lock or not info from stable interrupt status register.
+ * User application must use this control to get locked,
+ * then can do next PCM operation
+ */
+static int mxc_spdif_rxrate_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+
+ if (atomic_read(&spdif_priv->dpll_locked)) {
+ clk_enable(plat_data->spdif_clk);
+ ucontrol->value.integer.value[0] =
+ spdif_get_rxclk_rate(plat_data->spdif_clk,
+ SPDIF_DEFAULT_GAINSEL);
+ clk_disable(plat_data->spdif_clk);
+ } else {
+ ucontrol->value.integer.value[0] = 0;
+ }
+ return 0;
+}
+
+/*
+ * User bit sync mode info
+ */
+static int mxc_spdif_usync_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+/*
+ * User bit sync mode:
+ * 1 CD User channel subcode
+ * 0 Non-CD data
+ */
+static int mxc_spdif_usync_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ unsigned int int_val;
+
+ clk_enable(plat_data->spdif_clk);
+
+ int_val = __raw_readl(spdif_base_addr + SPDIF_REG_SRCD);
+ ucontrol->value.integer.value[0] = (int_val & SRCD_CD_USER) != 0;
+
+ clk_disable(plat_data->spdif_clk);
+
+ return 0;
+}
+
+/*
+ * User bit sync mode:
+ * 1 CD User channel subcode
+ * 0 Non-CD data
+ */
+static int mxc_spdif_usync_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ unsigned int int_val;
+
+ clk_enable(plat_data->spdif_clk);
+
+ int_val = ucontrol->value.integer.value[0] << SRCD_CD_USER_OFFSET;
+ __raw_writel(int_val, spdif_base_addr + SPDIF_REG_SRCD);
+
+ clk_disable(plat_data->spdif_clk);
+
+ return 0;
+}
+
+/*
+ * MXC SPDIF IEC958 controller defines
+ */
+static struct snd_kcontrol_new mxc_spdif_ctrls[] = {
+ /* status cchanel controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mxc_pb_spdif_info,
+ .get = mxc_pb_spdif_get,
+ .put = mxc_pb_spdif_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mxc_spdif_info,
+ .get = mxc_spdif_capture_get,
+ },
+ /* user bits controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Subcode Capture Default",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mxc_spdif_info,
+ .get = mxc_spdif_subcode_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Q-subcode Capture Default",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mxc_spdif_qinfo,
+ .get = mxc_spdif_qget,
+ },
+ /* valid bit error controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 V-Bit Errors",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mxc_spdif_vbit_info,
+ .get = mxc_spdif_vbit_get,
+ },
+ /* DPLL lock info get controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "RX Sample Rate",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mxc_spdif_rxrate_info,
+ .get = mxc_spdif_rxrate_get,
+ },
+ /* User bit sync mode set/get controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 USyncMode CDText",
+ .access =
+ SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mxc_spdif_usync_info,
+ .get = mxc_spdif_usync_get,
+ .put = mxc_spdif_usync_put,
+ },
+};
+
+static int mxc_spdif_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+ int ret = 0;
+
+ /* enable spdif_xtal_clk */
+ ret = clk_enable(plat_data->spdif_core_clk);
+ if (ret < 0)
+ goto failed_clk;
+
+ spdif_softreset();
+ /* disable all the interrupts */
+ spdif_intr_enable(0xffffff, 0);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = mxc_spdif_playback_startup(substream, dai);
+ else
+ ret = mxc_spdif_capture_startup(substream, dai);
+
+failed_clk:
+ return ret;
+}
+
+static void mxc_spdif_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct mxc_spdif_priv *spdif_priv = snd_soc_codec_get_drvdata(codec);
+ struct mxc_spdif_platform_data *plat_data = spdif_priv->plat_data;
+
+ /* disable spdif_core clock */
+ clk_disable(plat_data->spdif_core_clk);
+
+ clk_disable(plat_data->spdif_clk);
+}
+
+static int mxc_spdif_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ int ret = 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = mxc_spdif_playback_trigger(substream, cmd, dai);
+ else
+ ret = mxc_spdif_capture_trigger(substream, cmd, dai);
+
+ return ret;
+}
+
+struct snd_soc_dai_ops mxc_spdif_codec_dai_ops = {
+ .startup = mxc_spdif_startup,
+ .trigger = mxc_spdif_trigger,
+ .shutdown = mxc_spdif_shutdown,
+};
+
+struct snd_soc_dai_driver mxc_spdif_codec_dai = {
+ .name = "mxc-spdif",
+ .ops = &mxc_spdif_codec_dai_ops,
+};
+
+static int mxc_spdif_soc_probe(struct snd_soc_codec *codec)
+{
+ snd_soc_add_controls(codec, mxc_spdif_ctrls,
+ ARRAY_SIZE(mxc_spdif_ctrls));
+ return 0;
+}
+
+static int mxc_spdif_soc_remove(struct snd_soc_codec *codec)
+{
+ /* all done in mxc_spdif_remove */
+ return 0;
+}
+
+struct snd_soc_codec_driver soc_codec_dev_spdif = {
+ .probe = mxc_spdif_soc_probe,
+ .remove = mxc_spdif_soc_remove,
+};
+
+static int __devinit mxc_spdif_probe(struct platform_device *pdev)
+{
+ struct mxc_spdif_platform_data *plat_data =
+ (struct mxc_spdif_platform_data *)pdev->dev.platform_data;
+ struct resource *res;
+ struct mxc_spdif_priv *spdif_priv;
+ int err, irq;
+ int ret = 0;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+
+ dev_info(&pdev->dev, "MXC SPDIF Audio\n");
+
+ spdif_priv = kzalloc(sizeof(struct mxc_spdif_priv), GFP_KERNEL);
+ if (spdif_priv == NULL)
+ return -ENOMEM;
+
+ if (plat_data->spdif_tx) {
+ mxc_spdif_codec_dai.playback.stream_name = "Playback";
+ mxc_spdif_codec_dai.playback.channels_min = 2;
+ mxc_spdif_codec_dai.playback.channels_max = 2;
+
+ if (plat_data->spdif_clk_44100 >= 0)
+ mxc_spdif_codec_dai.playback.rates |= SNDRV_PCM_RATE_44100;
+ if (plat_data->spdif_clk_48000 >= 0)
+ mxc_spdif_codec_dai.playback.rates |= SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_48000;
+
+ mxc_spdif_codec_dai.playback.formats = MXC_SPDIF_FORMATS_PLAYBACK;
+ }
+
+ if (plat_data->spdif_rx) {
+ mxc_spdif_codec_dai.capture.stream_name = "Capture";
+ mxc_spdif_codec_dai.capture.channels_min = 2;
+ mxc_spdif_codec_dai.capture.channels_max = 2;
+
+ /* rx clock is recovered from audio stream, so it is not
+ dependent on tx clocks available */
+ mxc_spdif_codec_dai.capture.rates = MXC_SPDIF_RATES_CAPTURE;
+
+ mxc_spdif_codec_dai.capture.formats = MXC_SPDIF_FORMATS_CAPTURE;
+ }
+
+ spdif_priv->tx_active = false;
+ spdif_priv->rx_active = false;
+ platform_set_drvdata(pdev, spdif_priv);
+
+ spdif_priv->reg_phys_base = res->start;
+ spdif_priv->reg_base = ioremap(res->start, res->end - res->start + 1);
+ spdif_priv->plat_data = plat_data;
+
+ spdif_base_addr = (unsigned long)spdif_priv->reg_base;
+
+ /* initial spinlock for control data */
+ spin_lock_init(&mxc_spdif_control.ctl_lock);
+
+ if (plat_data->spdif_tx) {
+ /* init tx channel status default value */
+ mxc_spdif_control.ch_status[0] =
+ IEC958_AES0_CON_NOT_COPYRIGHT |
+ IEC958_AES0_CON_EMPHASIS_5015;
+ mxc_spdif_control.ch_status[1] = IEC958_AES1_CON_DIGDIGCONV_ID;
+ mxc_spdif_control.ch_status[2] = 0x00;
+ mxc_spdif_control.ch_status[3] =
+ IEC958_AES3_CON_FS_44100 | IEC958_AES3_CON_CLOCK_1000PPM;
+ }
+
+ plat_data->spdif_clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(plat_data->spdif_clk)) {
+ ret = PTR_ERR(plat_data->spdif_clk);
+ dev_err(&pdev->dev, "can't get clock: %d\n", ret);
+ goto failed_clk;
+ }
+
+ atomic_set(&spdif_priv->dpll_locked, 0);
+
+ /* spdif interrupt register and disable */
+ irq = platform_get_irq(pdev, 0);
+ if ((irq <= 0) || request_irq(irq, spdif_isr, 0, "spdif", spdif_priv)) {
+ pr_err("MXC spdif: failed to request irq\n");
+ err = -EBUSY;
+ goto card_err;
+ }
+
+ ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_spdif,
+ &mxc_spdif_codec_dai, 1);
+
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register codec\n");
+ goto card_err;
+ }
+
+ dumpregs(spdif_priv);
+
+ return 0;
+
+card_err:
+ clk_put(plat_data->spdif_clk);
+failed_clk:
+ platform_set_drvdata(pdev, NULL);
+ kfree(spdif_priv);
+
+ return ret;
+}
+
+static int __devexit mxc_spdif_remove(struct platform_device *pdev)
+{
+ struct mxc_spdif_priv *spdif_priv = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_codec(&pdev->dev);
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(spdif_priv);
+
+ return 0;
+}
+
+struct platform_driver mxc_spdif_driver = {
+ .driver = {
+ .name = "mxc_spdif",
+ .owner = THIS_MODULE,
+ },
+ .probe = mxc_spdif_probe,
+ .remove = __devexit_p(mxc_spdif_remove),
+};
+
+static int __init mxc_spdif_init(void)
+{
+ return platform_driver_register(&mxc_spdif_driver);
+}
+
+static void __exit mxc_spdif_exit(void)
+{
+ return platform_driver_unregister(&mxc_spdif_driver);
+}
+
+module_init(mxc_spdif_init);
+module_exit(mxc_spdif_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC SPDIF transmitter");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/mxc_spdif.h b/sound/soc/codecs/mxc_spdif.h
new file mode 100644
index 00000000..18ec3a7d
--- /dev/null
+++ b/sound/soc/codecs/mxc_spdif.h
@@ -0,0 +1,159 @@
+/*
+ * ALSA SoC MXC SPDIF codec driver
+ *
+ * Copyright (C) 2008-2012 Freescale Semiconductor, Inc.
+ *
+ * Based on stmp3xxx_spdif.h by:
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ * Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#ifndef __MXC_SPDIF_CODEC_H
+#define __MXC_SPDIF_CODEC_H
+
+#define SPDIF_REG_SCR 0x00
+#define SPDIF_REG_SRCD 0x04
+#define SPDIF_REG_SRPC 0x08
+#define SPDIF_REG_SIE 0x0C
+#define SPDIF_REG_SIS 0x10
+#define SPDIF_REG_SIC 0x10
+#define SPDIF_REG_SRL 0x14
+#define SPDIF_REG_SRR 0x18
+#define SPDIF_REG_SRCSLH 0x1C
+#define SPDIF_REG_SRCSLL 0x20
+#define SPDIF_REG_SQU 0x24
+#define SPDIF_REG_SRQ 0x28
+#define SPDIF_REG_STL 0x2C
+#define SPDIF_REG_STR 0x30
+#define SPDIF_REG_STCSCH 0x34
+#define SPDIF_REG_STCSCL 0x38
+#define SPDIF_REG_SRFM 0x44
+#define SPDIF_REG_STC 0x50
+
+/* SPDIF Configuration register */
+#define SCR_RXFIFO_CTL_ZERO (1 << 23)
+#define SCR_RXFIFO_OFF (1 << 22)
+#define SCR_RXFIFO_RST (1 << 21)
+#define SCR_RXFIFO_FSEL_BIT (19)
+#define SCR_RXFIFO_FSEL_MASK (0x3 << SCR_RXFIFO_FSEL_BIT)
+#define SCR_RXFIFO_AUTOSYNC (1 << 18)
+#define SCR_TXFIFO_AUTOSYNC (1 << 17)
+#define SCR_TXFIFO_ESEL_BIT (15)
+#define SCR_TXFIFO_ESEL_MASK (0x3 << SCR_TXFIFO_FSEL_BIT)
+#define SCR_LOW_POWER (1 << 13)
+#define SCR_SOFT_RESET (1 << 12)
+#define SCR_TXFIFO_ZERO (0 << 10)
+#define SCR_TXFIFO_NORMAL (1 << 10)
+#define SCR_TXFIFO_ONESAMPLE (1 << 11)
+#define SCR_DMA_RX_EN (1 << 9)
+#define SCR_DMA_TX_EN (1 << 8)
+#define SCR_VAL_CLEAR (1 << 5)
+#define SCR_TXSEL_OFF (0 << 2)
+#define SCR_TXSEL_RX (1 << 2)
+#define SCR_TXSEL_NORMAL (0x5 << 2)
+#define SCR_USRC_SEL_NONE (0x0)
+#define SCR_USRC_SEL_RECV (0x1)
+#define SCR_USRC_SEL_CHIP (0x3)
+
+/* SPDIF CDText control */
+#define SRCD_CD_USER_OFFSET 1
+#define SRCD_CD_USER (1 << SRCD_CD_USER_OFFSET)
+
+/* SPDIF Phase Configuration register */
+#define SRPC_DPLL_LOCKED (1 << 6)
+#define SRPC_CLKSRC_SEL_OFFSET 7
+#define SRPC_CLKSRC_SEL_LOCKED 5
+/* gain sel */
+#define SRPC_GAINSEL_OFFSET 3
+
+enum spdif_gainsel {
+ GAINSEL_MULTI_24 = 0,
+ GAINSEL_MULTI_16,
+ GAINSEL_MULTI_12,
+ GAINSEL_MULTI_8,
+ GAINSEL_MULTI_6,
+ GAINSEL_MULTI_4,
+ GAINSEL_MULTI_3,
+ GAINSEL_MULTI_MAX,
+};
+
+#define SPDIF_DEFAULT_GAINSEL GAINSEL_MULTI_8
+
+/* SPDIF interrupt mask define */
+#define INT_DPLL_LOCKED (1 << 20)
+#define INT_TXFIFO_UNOV (1 << 19)
+#define INT_TXFIFO_RESYNC (1 << 18)
+#define INT_CNEW (1 << 17)
+#define INT_VAL_NOGOOD (1 << 16)
+#define INT_SYM_ERR (1 << 15)
+#define INT_BIT_ERR (1 << 14)
+#define INT_URX_FUL (1 << 10)
+#define INT_URX_OV (1 << 9)
+#define INT_QRX_FUL (1 << 8)
+#define INT_QRX_OV (1 << 7)
+#define INT_UQ_SYNC (1 << 6)
+#define INT_UQ_ERR (1 << 5)
+#define INT_RX_UNOV (1 << 4)
+#define INT_RX_RESYNC (1 << 3)
+#define INT_LOSS_LOCK (1 << 2)
+#define INT_TX_EMPTY (1 << 1)
+#define INT_RXFIFO_FUL (1 << 0)
+
+/* SPDIF Clock register */
+#define STC_SYSCLK_DIV_OFFSET 11
+#define STC_TXCLK_SRC_OFFSET 8
+#define STC_TXCLK_SRC_EN_OFFSET 7
+#define STC_TXCLK_SRC_EN (1 << 7)
+#define STC_TXCLK_DIV_OFFSET 0
+
+#define SPDIF_CSTATUS_BYTE 6
+#define SPDIF_UBITS_SIZE 96
+#define SPDIF_QSUB_SIZE (SPDIF_UBITS_SIZE/8)
+
+/* SPDIF clock source */
+enum spdif_clk_src {
+ SPDIF_CLK_SRC1 = 0,
+ SPDIF_CLK_SRC2,
+ SPDIF_CLK_SRC3,
+ SPDIF_CLK_SRC4,
+ SPDIF_CLK_SRC5,
+};
+
+enum spdif_max_wdl {
+ SPDIF_MAX_WDL_20,
+ SPDIF_MAX_WDL_24
+};
+
+enum spdif_wdl {
+ SPDIF_WDL_DEFAULT = 0,
+ SPDIF_WDL_FIFTH = 4,
+ SPDIF_WDL_FOURTH = 3,
+ SPDIF_WDL_THIRD = 2,
+ SPDIF_WDL_SECOND = 1,
+ SPDIF_WDL_MAX = 5
+};
+
+#define MXC_SPDIF_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define MXC_SPDIF_RATES_CAPTURE (SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_64000 | \
+ SNDRV_PCM_RATE_96000)
+
+#define MXC_SPDIF_FORMATS_PLAYBACK (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+#define MXC_SPDIF_FORMATS_CAPTURE (SNDRV_PCM_FMTBIT_S24_LE)
+
+#endif /* __MXC_SPDIF_CODEC_H */
diff --git a/sound/soc/codecs/pcm3008.c b/sound/soc/codecs/pcm3008.c
new file mode 100644
index 00000000..bd8f26e4
--- /dev/null
+++ b/sound/soc/codecs/pcm3008.c
@@ -0,0 +1,188 @@
+/*
+ * ALSA Soc PCM3008 codec support
+ *
+ * Author: Hugo Villeneuve
+ * Copyright (C) 2008 Lyrtech inc
+ *
+ * Based on AC97 Soc codec, original copyright follow:
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ * Generic PCM3008 support.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "pcm3008.h"
+
+#define PCM3008_VERSION "0.2"
+
+#define PCM3008_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_driver pcm3008_dai = {
+ .name = "pcm3008-hifi",
+ .playback = {
+ .stream_name = "PCM3008 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = PCM3008_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "PCM3008 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = PCM3008_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+};
+
+static void pcm3008_gpio_free(struct pcm3008_setup_data *setup)
+{
+ gpio_free(setup->dem0_pin);
+ gpio_free(setup->dem1_pin);
+ gpio_free(setup->pdad_pin);
+ gpio_free(setup->pdda_pin);
+}
+
+static int pcm3008_soc_probe(struct snd_soc_codec *codec)
+{
+ struct pcm3008_setup_data *setup = codec->dev->platform_data;
+ int ret = 0;
+
+ printk(KERN_INFO "PCM3008 SoC Audio Codec %s\n", PCM3008_VERSION);
+
+ /* DEM1 DEM0 DE-EMPHASIS_MODE
+ * Low Low De-emphasis 44.1 kHz ON
+ * Low High De-emphasis OFF
+ * High Low De-emphasis 48 kHz ON
+ * High High De-emphasis 32 kHz ON
+ */
+
+ /* Configure DEM0 GPIO (turning OFF DAC De-emphasis). */
+ ret = gpio_request(setup->dem0_pin, "codec_dem0");
+ if (ret == 0)
+ ret = gpio_direction_output(setup->dem0_pin, 1);
+ if (ret != 0)
+ goto gpio_err;
+
+ /* Configure DEM1 GPIO (turning OFF DAC De-emphasis). */
+ ret = gpio_request(setup->dem1_pin, "codec_dem1");
+ if (ret == 0)
+ ret = gpio_direction_output(setup->dem1_pin, 0);
+ if (ret != 0)
+ goto gpio_err;
+
+ /* Configure PDAD GPIO. */
+ ret = gpio_request(setup->pdad_pin, "codec_pdad");
+ if (ret == 0)
+ ret = gpio_direction_output(setup->pdad_pin, 1);
+ if (ret != 0)
+ goto gpio_err;
+
+ /* Configure PDDA GPIO. */
+ ret = gpio_request(setup->pdda_pin, "codec_pdda");
+ if (ret == 0)
+ ret = gpio_direction_output(setup->pdda_pin, 1);
+ if (ret != 0)
+ goto gpio_err;
+
+ return ret;
+
+gpio_err:
+ pcm3008_gpio_free(setup);
+
+ return ret;
+}
+
+static int pcm3008_soc_remove(struct snd_soc_codec *codec)
+{
+ struct pcm3008_setup_data *setup = codec->dev->platform_data;
+
+ pcm3008_gpio_free(setup);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pcm3008_soc_suspend(struct snd_soc_codec *codec, pm_message_t msg)
+{
+ struct pcm3008_setup_data *setup = codec->dev->platform_data;
+
+ gpio_set_value(setup->pdad_pin, 0);
+ gpio_set_value(setup->pdda_pin, 0);
+
+ return 0;
+}
+
+static int pcm3008_soc_resume(struct snd_soc_codec *codec)
+{
+ struct pcm3008_setup_data *setup = codec->dev->platform_data;
+
+ gpio_set_value(setup->pdad_pin, 1);
+ gpio_set_value(setup->pdda_pin, 1);
+
+ return 0;
+}
+#else
+#define pcm3008_soc_suspend NULL
+#define pcm3008_soc_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_pcm3008 = {
+ .probe = pcm3008_soc_probe,
+ .remove = pcm3008_soc_remove,
+ .suspend = pcm3008_soc_suspend,
+ .resume = pcm3008_soc_resume,
+};
+
+static int __devinit pcm3008_codec_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_pcm3008, &pcm3008_dai, 1);
+}
+
+static int __devexit pcm3008_codec_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+MODULE_ALIAS("platform:pcm3008-codec");
+
+static struct platform_driver pcm3008_codec_driver = {
+ .probe = pcm3008_codec_probe,
+ .remove = __devexit_p(pcm3008_codec_remove),
+ .driver = {
+ .name = "pcm3008-codec",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pcm3008_modinit(void)
+{
+ return platform_driver_register(&pcm3008_codec_driver);
+}
+module_init(pcm3008_modinit);
+
+static void __exit pcm3008_exit(void)
+{
+ platform_driver_unregister(&pcm3008_codec_driver);
+}
+module_exit(pcm3008_exit);
+
+MODULE_DESCRIPTION("Soc PCM3008 driver");
+MODULE_AUTHOR("Hugo Villeneuve");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/pcm3008.h b/sound/soc/codecs/pcm3008.h
new file mode 100644
index 00000000..7e5489ab
--- /dev/null
+++ b/sound/soc/codecs/pcm3008.h
@@ -0,0 +1,22 @@
+/*
+ * PCM3008 ALSA SoC Layer
+ *
+ * Author: Hugo Villeneuve
+ * Copyright (C) 2008 Lyrtech inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_SND_SOC_PCM3008_H
+#define __LINUX_SND_SOC_PCM3008_H
+
+struct pcm3008_setup_data {
+ unsigned dem0_pin;
+ unsigned dem1_pin;
+ unsigned pdad_pin;
+ unsigned pdda_pin;
+};
+
+#endif
diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c
new file mode 100644
index 00000000..fd055146
--- /dev/null
+++ b/sound/soc/codecs/sgtl5000.c
@@ -0,0 +1,1750 @@
+/*
+ * sgtl5000.c -- SGTL5000 ALSA SoC Audio driver
+ *
+ * Copyright 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/consumer.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "sgtl5000.h"
+
+#define SGTL5000_DAP_REG_OFFSET 0x0100
+#define SGTL5000_MAX_REG_OFFSET 0x013A
+
+/* default value of sgtl5000 registers */
+static const u16 sgtl5000_regs[SGTL5000_MAX_REG_OFFSET] = {
+ [SGTL5000_CHIP_CLK_CTRL] = 0x0008,
+ [SGTL5000_CHIP_I2S_CTRL] = 0x0010,
+ [SGTL5000_CHIP_SSS_CTRL] = 0x0008,
+ [SGTL5000_CHIP_DAC_VOL] = 0x3c3c,
+ [SGTL5000_CHIP_PAD_STRENGTH] = 0x015f,
+ [SGTL5000_CHIP_ANA_HP_CTRL] = 0x1818,
+ [SGTL5000_CHIP_ANA_CTRL] = 0x0111,
+ [SGTL5000_CHIP_LINE_OUT_VOL] = 0x0404,
+ [SGTL5000_CHIP_ANA_POWER] = 0x7060,
+ [SGTL5000_CHIP_PLL_CTRL] = 0x5000,
+ [SGTL5000_DAP_BASS_ENHANCE] = 0x0040,
+ [SGTL5000_DAP_BASS_ENHANCE_CTRL] = 0x051f,
+ [SGTL5000_DAP_SURROUND] = 0x0040,
+ [SGTL5000_DAP_EQ_BASS_BAND0] = 0x002f,
+ [SGTL5000_DAP_EQ_BASS_BAND1] = 0x002f,
+ [SGTL5000_DAP_EQ_BASS_BAND2] = 0x002f,
+ [SGTL5000_DAP_EQ_BASS_BAND3] = 0x002f,
+ [SGTL5000_DAP_EQ_BASS_BAND4] = 0x002f,
+ [SGTL5000_DAP_MAIN_CHAN] = 0x8000,
+ [SGTL5000_DAP_AVC_CTRL] = 0x0510,
+ [SGTL5000_DAP_AVC_THRESHOLD] = 0x1473,
+ [SGTL5000_DAP_AVC_ATTACK] = 0x0028,
+ [SGTL5000_DAP_AVC_DECAY] = 0x0050,
+};
+
+/* regulator supplies for sgtl5000, VDDD is an optional external supply */
+enum sgtl5000_regulator_supplies {
+ VDDA,
+ VDDIO,
+ VDDD,
+ SGTL5000_SUPPLY_NUM
+};
+
+/* vddd is optional supply */
+static const char *supply_names[SGTL5000_SUPPLY_NUM] = {
+ "VDDA",
+ "VDDIO",
+ "VDDD"
+};
+
+#define LDO_CONSUMER_NAME "VDDD_LDO"
+#define LDO_VOLTAGE 1200000
+
+static struct regulator_consumer_supply ldo_consumer[] = {
+ REGULATOR_SUPPLY(LDO_CONSUMER_NAME, NULL),
+};
+
+static struct regulator_init_data ldo_init_data = {
+ .constraints = {
+ .min_uV = 850000,
+ .max_uV = 1600000,
+ .valid_modes_mask = REGULATOR_MODE_NORMAL,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = 1,
+ .consumer_supplies = &ldo_consumer[0],
+};
+
+/*
+ * sgtl5000 internal ldo regulator,
+ * enabled when VDDD not provided
+ */
+struct ldo_regulator {
+ struct regulator_desc desc;
+ struct regulator_dev *dev;
+ int voltage;
+ void *codec_data;
+ bool enabled;
+};
+
+/* sgtl5000 private structure in codec */
+struct sgtl5000_priv {
+ int sysclk; /* sysclk rate */
+ int master; /* i2s master or not */
+ int fmt; /* i2s data format */
+ struct regulator_bulk_data supplies[SGTL5000_SUPPLY_NUM];
+ struct ldo_regulator *ldo;
+};
+
+/*
+ * mic_bias power on/off share the same register bits with
+ * output impedance of mic bias, when power on mic bias, we
+ * need reclaim it to impedance value.
+ * 0x0 = Powered off
+ * 0x1 = 2Kohm
+ * 0x2 = 4Kohm
+ * 0x3 = 8Kohm
+ */
+static int mic_bias_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ /* change mic bias resistor to 4Kohm */
+ snd_soc_update_bits(w->codec, SGTL5000_CHIP_MIC_CTRL,
+ SGTL5000_BIAS_R_MASK,
+ SGTL5000_BIAS_R_4k << SGTL5000_BIAS_R_SHIFT);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ /*
+ * SGTL5000_BIAS_R_8k as mask to clean the two bits
+ * of mic bias and output impedance
+ */
+ snd_soc_update_bits(w->codec, SGTL5000_CHIP_MIC_CTRL,
+ SGTL5000_BIAS_R_MASK,
+ SGTL5000_BIAS_R_off << SGTL5000_BIAS_R_SHIFT);
+ break;
+ }
+ return 0;
+}
+
+/*
+ * using codec assist to small pop, hp_powerup or lineout_powerup
+ * should stay setting until vag_powerup is fully ramped down,
+ * vag fully ramped down require 400ms.
+ */
+static int small_pop_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int dap_enable_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ snd_soc_update_bits(w->codec, SGTL5000_DAP_CTRL,
+ 0x1, 0x1);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_update_bits(w->codec, SGTL5000_DAP_CTRL,
+ 0x1, 0);
+ snd_soc_dapm_disable_pin(&w->codec->dapm, "DAP_MIXER Mixer Channel");
+ snd_soc_dapm_sync(&w->codec->dapm);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* input sources for ADC */
+static const char *adc_mux_text[] = {
+ "MIC_IN", "LINE_IN"
+};
+
+static const struct soc_enum adc_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 2, 2, adc_mux_text);
+
+static const struct snd_kcontrol_new adc_mux =
+SOC_DAPM_ENUM("Capture Mux", adc_enum);
+
+/* input sources for DAC */
+static const char *hp_mux_text[] = {
+ "DAC", "LINE_IN"
+};
+
+static const struct soc_enum hp_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 6, 2, hp_mux_text);
+
+static const struct snd_kcontrol_new hp_mux =
+SOC_DAPM_ENUM("Headphone Mux", hp_enum);
+
+static const char *dap_in_mux_text[] = {
+ "ADC", "I2S_IN"
+};
+static const struct soc_enum dap_in_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_SSS_CTRL, 6, 2, dap_in_mux_text);
+
+static const struct snd_kcontrol_new dap_in_mux =
+SOC_DAPM_ENUM("DAP input mux", dap_in_enum);
+
+static const struct soc_enum dap_mixer_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_SSS_CTRL, 8, 2, dap_in_mux_text);
+
+static const struct snd_kcontrol_new dap_mixer_mux =
+SOC_DAPM_ENUM("DAP mixer channel mux", dap_mixer_enum);
+
+static const char *dap_out_mux_text[] = {
+ "ADC", "I2S_IN", "reserved", "DAP"
+};
+
+static const struct soc_enum dac_in_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_SSS_CTRL, 4, 4, dap_out_mux_text);
+
+static const struct snd_kcontrol_new dac_in_mux =
+SOC_DAPM_ENUM("DAC input mux", dac_in_enum);
+
+static const struct soc_enum i2s_out_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_SSS_CTRL, 0, 4, dap_out_mux_text);
+
+static const struct snd_kcontrol_new i2s_out_mux =
+SOC_DAPM_ENUM("i2s output mux", i2s_out_enum);
+
+static const struct snd_kcontrol_new dap_mixer_controls[] = {
+SOC_DAPM_SINGLE("Mixer Channel", SGTL5000_DAP_CTRL, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("LINE_IN"),
+ SND_SOC_DAPM_INPUT("MIC_IN"),
+
+ SND_SOC_DAPM_OUTPUT("HP_OUT"),
+ SND_SOC_DAPM_OUTPUT("LINE_OUT"),
+
+ /* aif for i2s input */
+ SND_SOC_DAPM_AIF_IN("AIFIN", "Playback",
+ 0, SGTL5000_CHIP_DIG_POWER,
+ 0, 0),
+
+ /* aif for i2s output */
+ SND_SOC_DAPM_AIF_OUT("AIFOUT", "Capture",
+ 0, SGTL5000_CHIP_DIG_POWER,
+ 1, 0),
+
+ SND_SOC_DAPM_MICBIAS_E("Mic Bias", SND_SOC_NOPM, 0, 0,
+ mic_bias_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_PGA_E("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0,
+ small_pop_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("LO", SGTL5000_CHIP_ANA_POWER, 0, 0, NULL, 0,
+ small_pop_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux),
+ SND_SOC_DAPM_MUX("Headphone Mux", SND_SOC_NOPM, 0, 0, &hp_mux),
+ SND_SOC_DAPM_MUX_E("DAP_IN", SGTL5000_CHIP_DIG_POWER, 4, 0, &dap_in_mux,
+ dap_enable_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_MUX("MIX_MUX", SND_SOC_NOPM, 0, 0, &dap_mixer_mux),
+ SND_SOC_DAPM_MUX("DAC_IN", SND_SOC_NOPM, 0, 0, &dac_in_mux),
+ SND_SOC_DAPM_MUX("I2S_OUT", SND_SOC_NOPM, 0, 0, &i2s_out_mux),
+
+ SND_SOC_DAPM_MIXER("DAP_MIXER", SND_SOC_NOPM, 0, 0,
+ &dap_mixer_controls[0],
+ ARRAY_SIZE(dap_mixer_controls)),
+
+ SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_ANA_POWER, 1, 0),
+ SND_SOC_DAPM_DAC("DAC", "Playback", SGTL5000_CHIP_ANA_POWER, 3, 0),
+};
+
+/* routes for sgtl5000 */
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Capture Mux", "LINE_IN", "LINE_IN"}, /* line_in --> adc_mux */
+ {"Mic Bias", NULL, "MIC_IN"}, /* mic_in --> mic bias */
+ {"Capture Mux", "MIC_IN", "Mic Bias"}, /* mic bias --> adc_mux */
+
+ {"ADC", NULL, "Capture Mux"}, /* adc_mux --> adc */
+
+ {"DAP_IN", "ADC", "ADC"},
+ {"DAP_IN", "I2S_IN", "AIFIN"},
+
+ {"MIX_MUX", "ADC", "ADC"},
+ {"MIX_MUX", "I2S_IN", "AIFIN"},
+
+ {"DAP_MIXER", NULL, "DAP_IN"},
+ {"DAP_MIXER", "Mixer Channel", "MIX_MUX"},
+
+ {"I2S_OUT", "ADC", "ADC"}, /* ADC --> I2S_OUT */
+ {"I2S_OUT", "I2S_IN", "AIFIN"}, /* ADC --> Audio Switch */
+ {"I2S_OUT", "DAP", "DAP_MIXER"}, /* ADC --> Audio Switch */
+ {"AIFOUT", NULL, "I2S_OUT"}, /* ADC --> Audio Switch */
+
+ {"DAC_IN", "ADC", "ADC"}, /* i2s-->dac,skip audio mux */
+ {"DAC_IN", "I2S_IN", "AIFIN"}, /* i2s-->dac,skip audio mux */
+ {"DAC_IN", "DAP", "DAP_MIXER"}, /* i2s-->dac,skip audio mux */
+ {"DAC", NULL, "DAC_IN"},
+
+ {"Headphone Mux", "DAC", "DAC"}, /* dac --> hp_mux */
+ {"LO", NULL, "DAC"}, /* dac --> line_out */
+
+ {"Headphone Mux", "LINE_IN", "LINE_IN"},/* line_in --> hp_mux */
+ {"HP", NULL, "Headphone Mux"}, /* hp_mux --> hp */
+
+ {"LINE_OUT", NULL, "LO"},
+ {"HP_OUT", NULL, "HP"},
+};
+
+/* custom function to fetch info of PCM playback volume */
+static int dac_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0xfc - 0x60;
+ return 0;
+}
+
+/*
+ * custom function to get of PCM playback volume
+ *
+ * dac volume register
+ * 15-------------8-7--------------0
+ * | R channel vol | L channel vol |
+ * -------------------------------
+ *
+ * PCM volume with 0.5017 dB steps from 0 to -90 dB
+ *
+ * register values map to dB
+ * 0x3B and less = Reserved
+ * 0x3C = 0 dB
+ * 0x3D = -0.5 dB
+ * 0xF0 = -90 dB
+ * 0xFC and greater = Muted
+ *
+ * register value map to userspace value
+ *
+ * register value 0x3c(0dB) 0xf0(-90dB)0xfc
+ * ------------------------------
+ * userspace value 0xc0 0
+ */
+static int dac_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg;
+ int l;
+ int r;
+
+ reg = snd_soc_read(codec, SGTL5000_CHIP_DAC_VOL);
+
+ /* get left channel volume */
+ l = (reg & SGTL5000_DAC_VOL_LEFT_MASK) >> SGTL5000_DAC_VOL_LEFT_SHIFT;
+
+ /* get right channel volume */
+ r = (reg & SGTL5000_DAC_VOL_RIGHT_MASK) >> SGTL5000_DAC_VOL_RIGHT_SHIFT;
+
+ /* make sure value fall in (0x60,0xfc) */
+ l = clamp(l, 0x60, 0xfc);
+ r = clamp(r, 0x60, 0xfc);
+
+ /* invert it and map to userspace value */
+ l = 0xfc - l;
+ r = 0xfc - r;
+
+ ucontrol->value.integer.value[0] = l;
+ ucontrol->value.integer.value[1] = r;
+
+ return 0;
+}
+
+/*
+ * custom function to put of PCM playback volume
+ *
+ * dac volume register
+ * 15-------------8-7--------------0
+ * | R channel vol | L channel vol |
+ * -------------------------------
+ *
+ * PCM volume with 0.5017 dB steps from 0 to -90 dB
+ *
+ * register values map to dB
+ * 0x3B and less = Reserved
+ * 0x3C = 0 dB
+ * 0x3D = -0.5 dB
+ * 0xF0 = -90 dB
+ * 0xFC and greater = Muted
+ *
+ * userspace value map to register value
+ *
+ * userspace value 0xc0 0
+ * ------------------------------
+ * register value 0x3c(0dB) 0xf0(-90dB)0xfc
+ */
+static int dac_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg;
+ int l;
+ int r;
+
+ l = ucontrol->value.integer.value[0];
+ r = ucontrol->value.integer.value[1];
+
+ /* make sure userspace volume fall in (0, 0xfc-0x60) */
+ l = clamp(l, 0, 0xfc - 0x60);
+ r = clamp(r, 0, 0xfc - 0x60);
+
+ /* invert it, get the value can be set to register */
+ l = 0xfc - l;
+ r = 0xfc - r;
+
+ /* shift to get the register value */
+ reg = l << SGTL5000_DAC_VOL_LEFT_SHIFT |
+ r << SGTL5000_DAC_VOL_RIGHT_SHIFT;
+
+ snd_soc_write(codec, SGTL5000_CHIP_DAC_VOL, reg);
+
+ return 0;
+}
+
+/*
+ * we need to caculator threshold dB by:
+ * register_value = ((10^(dB/20))*0.636)*2^15 ==>
+ * dB = (fls(register_value) - 14.347)*6.02
+ */
+static int avc_thrd_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 reg = snd_soc_read(codec, SGTL5000_DAP_AVC_THRESHOLD);
+ int l;
+ /* 1/1000 of dB */
+ int fraction[5] = {585, 322, 170, 87, 44};
+ int i;
+ int tmp;
+
+ if (!reg) {
+ ucontrol->value.integer.value[0] = 96;
+ ucontrol->value.integer.value[1] = 96;
+ return 0;
+ }
+
+ /* caculate fls(register_value) */
+ l = fls(reg) - 1;
+ tmp = l * 1000; /* scale-up 1000 times */
+ for (i = l-1; i >= 0 && i >= l-5; i--) {
+ if (reg & 0x1 << i)
+ tmp += fraction[l-1-i];
+ }
+ tmp = ((tmp - 14347) * 6) / 1000;
+ tmp = clamp(-tmp, 0, 96);
+
+ ucontrol->value.integer.value[0] = tmp;
+ ucontrol->value.integer.value[1] = tmp;
+
+ return 0;
+}
+
+/*
+ * we need to caculator threshold dB by:
+ * register_value = ((10^(dB/20))*0.636)*2^15
+ * = 2^(3.322*db/20 + 15) * 0.636
+ * = 2^(3.322*db/20 + 15 - 0.653)
+ */
+static int avc_thrd_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 reg;
+ int db;
+ int tmp;
+ int remain;
+ int i;
+ int fraction[5] = {1414, 1189, 1091, 1044, 1022};
+
+ db = -(ucontrol->value.integer.value[0]);
+
+ tmp = ((3322 * db) / 20 + 15000 - 653);
+ tmp = clamp(tmp, 0, 16000);
+
+ remain = tmp % 1000;
+ tmp = tmp / 1000;
+
+ reg = 0x1 << tmp;
+
+ for (i = 0; i < 5; i++) {
+ int level = 1000 / (0x1 << (i+1));
+ if (remain >= level) {
+ remain -= level;
+ reg = reg * fraction[i] / 1000;
+ }
+ }
+
+ snd_soc_write(codec, SGTL5000_DAP_AVC_THRESHOLD, reg);
+
+ return 0;
+}
+
+static int mixer_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *control =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ u16 reg = snd_soc_read(codec, control->reg);
+ int vol;
+
+ vol = (reg * 100) >> 15;
+
+ ucontrol->value.integer.value[0] = vol;
+ ucontrol->value.integer.value[1] = vol;
+ return 0;
+}
+
+static int mixer_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *control =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ u16 reg;
+ int vol;
+
+ vol = ucontrol->value.integer.value[0];
+ reg = (vol << 15) / 100;
+ snd_soc_write(codec, control->reg, reg);
+
+ return 0;
+}
+
+
+static const DECLARE_TLV_DB_SCALE(capture_6db_attenuate, -600, 600, 0);
+
+/* tlv for mic gain, 0db 20db 30db 40db */
+static const unsigned int mic_gain_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 3, TLV_DB_SCALE_ITEM(2000, 1000, 0),
+};
+
+/* tlv for hp volume, -51.5db to 12.0db, step .5db */
+static const DECLARE_TLV_DB_SCALE(headphone_volume, -5150, 50, 0);
+static const unsigned int bass_high_filter_freq[] = {
+ TLV_DB_RANGE_HEAD(7),
+ 0, 0, TLV_DB_SCALE_ITEM(80, 0, 0),
+ 1, 6, TLV_DB_SCALE_ITEM(100, 25, 0),
+};
+static const DECLARE_TLV_DB_SCALE(avc_max_gain, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(avc_threshold, 0, 1, 0);
+static const DECLARE_TLV_DB_SCALE(mixer_volume, 0, 1, 0);
+
+static const struct snd_kcontrol_new sgtl5000_snd_controls[] = {
+ /* SOC_DOUBLE_S8_TLV with invert */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = dac_info_volsw,
+ .get = dac_get_volsw,
+ .put = dac_put_volsw,
+ },
+
+ SOC_DOUBLE("Capture Volume", SGTL5000_CHIP_ANA_ADC_CTRL, 0, 4, 0xf, 0),
+ SOC_SINGLE_TLV("Capture Attenuate Switch (-6dB)",
+ SGTL5000_CHIP_ANA_ADC_CTRL,
+ 8, 2, 0, capture_6db_attenuate),
+ SOC_SINGLE("Capture ZC Switch", SGTL5000_CHIP_ANA_CTRL, 1, 1, 0),
+
+ SOC_DOUBLE_TLV("Headphone Playback Volume",
+ SGTL5000_CHIP_ANA_HP_CTRL,
+ 0, 8,
+ 0x7f, 1,
+ headphone_volume),
+ SOC_SINGLE("Headphone Playback ZC Switch", SGTL5000_CHIP_ANA_CTRL,
+ 5, 1, 0),
+
+ SOC_SINGLE_TLV("Mic Volume", SGTL5000_CHIP_MIC_CTRL,
+ 0, 4, 0, mic_gain_tlv),
+
+ /* Bass Enhance enable */
+ SOC_SINGLE("Bass Enable", SGTL5000_DAP_BASS_ENHANCE,
+ 0, 1, 0),
+ SOC_SINGLE_TLV("Bass Filter Feq", SGTL5000_DAP_BASS_ENHANCE,
+ 6, 7, 0, bass_high_filter_freq),
+ SOC_SINGLE("Bass Volume", SGTL5000_DAP_BASS_ENHANCE_CTRL,
+ 8, 0x3f, 1),
+ /* Bass Harmonic Level Control */
+ SOC_SINGLE("Bass Level", SGTL5000_DAP_BASS_ENHANCE_CTRL,
+ 0, 0x7f, 1),
+
+ /* DAP Surround */
+ SOC_SINGLE("Surround Width", SGTL5000_DAP_SURROUND,
+ 4, 0x7, 0),
+
+ /* DAP MAIN Channel Voluem */
+ SOC_SINGLE_EXT_TLV("Main Channel Volume", SGTL5000_DAP_MAIN_CHAN,
+ 0, 200, 0, mixer_vol_get, mixer_vol_put, mixer_volume),
+ SOC_SINGLE_EXT_TLV("Mixer Channel Volume", SGTL5000_DAP_MIX_CHAN,
+ 0, 200, 0, mixer_vol_get, mixer_vol_put, mixer_volume),
+
+ /* DAP AVC */
+ SOC_SINGLE("AVC Enable", SGTL5000_DAP_AVC_CTRL,
+ 0, 1, 0),
+ SOC_SINGLE("AVC Hard Limit", SGTL5000_DAP_AVC_CTRL,
+ 5, 1, 0),
+ SOC_SINGLE_TLV("AVC Max Gain", SGTL5000_DAP_AVC_CTRL,
+ 12, 2, 0, avc_max_gain),
+ SOC_SINGLE_EXT_TLV("AVC Threshold (-dB)", SGTL5000_DAP_AVC_THRESHOLD,
+ 0, 96, 0, avc_thrd_get, avc_thrd_put, avc_threshold),
+};
+
+/* mute the codec used by alsa core */
+static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 adcdac_ctrl = SGTL5000_DAC_MUTE_LEFT | SGTL5000_DAC_MUTE_RIGHT;
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ADCDAC_CTRL,
+ adcdac_ctrl, mute ? adcdac_ctrl : 0);
+
+ return 0;
+}
+
+/* set codec format */
+static int sgtl5000_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ u16 i2sctl = 0;
+
+ sgtl5000->master = 0;
+ /*
+ * i2s clock and frame master setting.
+ * ONLY support:
+ * - clock and frame slave,
+ * - clock and frame master
+ */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ i2sctl |= SGTL5000_I2S_MASTER;
+ sgtl5000->master = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* setting i2s data format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ i2sctl |= SGTL5000_I2S_MODE_PCM;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ i2sctl |= SGTL5000_I2S_MODE_PCM;
+ i2sctl |= SGTL5000_I2S_LRALIGN;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ i2sctl |= SGTL5000_I2S_MODE_I2S_LJ;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ i2sctl |= SGTL5000_I2S_MODE_RJ;
+ i2sctl |= SGTL5000_I2S_LRPOL;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ i2sctl |= SGTL5000_I2S_MODE_I2S_LJ;
+ i2sctl |= SGTL5000_I2S_LRALIGN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ sgtl5000->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+ /* Clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ i2sctl |= SGTL5000_I2S_SCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, SGTL5000_CHIP_I2S_CTRL, i2sctl);
+
+ return 0;
+}
+
+/* set codec sysclk */
+static int sgtl5000_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ switch (clk_id) {
+ case SGTL5000_SYSCLK:
+ sgtl5000->sysclk = freq;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * set clock according to i2s frame clock,
+ * sgtl5000 provide 2 clock sources.
+ * 1. sys_mclk. sample freq can only configure to
+ * 1/256, 1/384, 1/512 of sys_mclk.
+ * 2. pll. can derive any audio clocks.
+ *
+ * clock setting rules:
+ * 1. in slave mode, only sys_mclk can use.
+ * 2. as constraint by sys_mclk, sample freq should
+ * set to 32k, 44.1k and above.
+ * 3. using sys_mclk prefer to pll to save power.
+ */
+static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate)
+{
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ int clk_ctl = 0;
+ int sys_fs; /* sample freq */
+
+ /*
+ * sample freq should be divided by frame clock,
+ * if frame clock lower than 44.1khz, sample feq should set to
+ * 32khz or 44.1khz.
+ */
+ switch (frame_rate) {
+ case 8000:
+ case 16000:
+ sys_fs = 32000;
+ break;
+ case 11025:
+ case 22050:
+ sys_fs = 44100;
+ break;
+ default:
+ sys_fs = frame_rate;
+ break;
+ }
+
+ /* set divided factor of frame clock */
+ switch (sys_fs / frame_rate) {
+ case 4:
+ clk_ctl |= SGTL5000_RATE_MODE_DIV_4 << SGTL5000_RATE_MODE_SHIFT;
+ break;
+ case 2:
+ clk_ctl |= SGTL5000_RATE_MODE_DIV_2 << SGTL5000_RATE_MODE_SHIFT;
+ break;
+ case 1:
+ clk_ctl |= SGTL5000_RATE_MODE_DIV_1 << SGTL5000_RATE_MODE_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set the sys_fs according to frame rate */
+ switch (sys_fs) {
+ case 32000:
+ clk_ctl |= SGTL5000_SYS_FS_32k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 44100:
+ clk_ctl |= SGTL5000_SYS_FS_44_1k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 48000:
+ clk_ctl |= SGTL5000_SYS_FS_48k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 96000:
+ clk_ctl |= SGTL5000_SYS_FS_96k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ default:
+ dev_err(codec->dev, "frame rate %d not supported\n",
+ frame_rate);
+ return -EINVAL;
+ }
+
+ /*
+ * calculate the divider of mclk/sample_freq,
+ * factor of freq =96k can only be 256, since mclk in range (12m,27m)
+ */
+ switch (sgtl5000->sysclk / sys_fs) {
+ case 256:
+ clk_ctl |= SGTL5000_MCLK_FREQ_256FS <<
+ SGTL5000_MCLK_FREQ_SHIFT;
+ break;
+ case 384:
+ clk_ctl |= SGTL5000_MCLK_FREQ_384FS <<
+ SGTL5000_MCLK_FREQ_SHIFT;
+ break;
+ case 512:
+ clk_ctl |= SGTL5000_MCLK_FREQ_512FS <<
+ SGTL5000_MCLK_FREQ_SHIFT;
+ break;
+ default:
+ /* if mclk not satisify the divider, use pll */
+ if (sgtl5000->master) {
+ clk_ctl |= SGTL5000_MCLK_FREQ_PLL <<
+ SGTL5000_MCLK_FREQ_SHIFT;
+ } else {
+ dev_err(codec->dev,
+ "PLL not supported in slave mode\n");
+ return -EINVAL;
+ }
+ }
+
+ /* if using pll, please check manual 6.4.2 for detail */
+ if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) {
+ u64 out, t;
+ int div2;
+ int pll_ctl;
+ unsigned int in, int_div, frac_div;
+
+ if (sgtl5000->sysclk > 17000000) {
+ div2 = 1;
+ in = sgtl5000->sysclk / 2;
+ } else {
+ div2 = 0;
+ in = sgtl5000->sysclk;
+ }
+ if (sys_fs == 44100)
+ out = 180633600;
+ else
+ out = 196608000;
+ t = do_div(out, in);
+ int_div = out;
+ t *= 2048;
+ do_div(t, in);
+ frac_div = t;
+ pll_ctl = int_div << SGTL5000_PLL_INT_DIV_SHIFT |
+ frac_div << SGTL5000_PLL_FRAC_DIV_SHIFT;
+
+ snd_soc_write(codec, SGTL5000_CHIP_PLL_CTRL, pll_ctl);
+ if (div2)
+ snd_soc_update_bits(codec,
+ SGTL5000_CHIP_CLK_TOP_CTRL,
+ SGTL5000_INPUT_FREQ_DIV2,
+ SGTL5000_INPUT_FREQ_DIV2);
+ else
+ snd_soc_update_bits(codec,
+ SGTL5000_CHIP_CLK_TOP_CTRL,
+ SGTL5000_INPUT_FREQ_DIV2,
+ 0);
+
+ /* power up pll */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP,
+ SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP);
+ } else {
+ /* power down pll */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP,
+ 0);
+ }
+
+ /* if using pll, clk_ctrl must be set after pll power up */
+ snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL, clk_ctl);
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ * input: params_rate, params_fmt
+ */
+static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ int channels = params_channels(params);
+ int i2s_ctl = 0;
+ int stereo;
+ int ret;
+
+ /* sysclk should already set */
+ if (!sgtl5000->sysclk) {
+ dev_err(codec->dev, "%s: set sysclk first!\n", __func__);
+ return -EFAULT;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stereo = SGTL5000_DAC_STEREO;
+ else
+ stereo = SGTL5000_ADC_STEREO;
+
+ /* set mono to save power */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, stereo,
+ channels == 1 ? 0 : stereo);
+
+ /* set codec clock base on lrclk */
+ ret = sgtl5000_set_clock(codec, params_rate(params));
+ if (ret)
+ return ret;
+
+ /* set i2s data format */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ return -EINVAL;
+ i2s_ctl |= SGTL5000_I2S_DLEN_16 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_32FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ i2s_ctl |= SGTL5000_I2S_DLEN_20 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ i2s_ctl |= SGTL5000_I2S_DLEN_24 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ return -EINVAL;
+ i2s_ctl |= SGTL5000_I2S_DLEN_32 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_I2S_CTRL,
+ SGTL5000_I2S_DLEN_MASK | SGTL5000_I2S_SCLKFREQ_MASK,
+ i2s_ctl);
+
+ return 0;
+}
+
+#ifdef CONFIG_REGULATOR
+static int ldo_regulator_is_enabled(struct regulator_dev *dev)
+{
+ struct ldo_regulator *ldo = rdev_get_drvdata(dev);
+
+ return ldo->enabled;
+}
+
+/*
+ * enable internal VDDD power supply. Since register
+ * cache not fill yet, we have to use hw_read and write
+ * instead of snd_soc_read and snd_soc_write.
+ */
+static int ldo_regulator_enable(struct regulator_dev *dev)
+{
+ struct ldo_regulator *ldo = rdev_get_drvdata(dev);
+ struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data;
+ int reg;
+
+ if (ldo_regulator_is_enabled(dev))
+ return 0;
+
+ /* set regulator value firstly */
+ reg = (1600 - ldo->voltage / 1000) / 50;
+ reg = clamp(reg, 0x0, 0xf);
+
+ /* amend the voltage value, unit: uV */
+ ldo->voltage = (1600 - reg * 50) * 1000;
+
+ /* set voltage to register */
+ codec->write(codec, SGTL5000_CHIP_LINREG_CTRL, reg);
+
+ reg = codec->hw_read(codec, SGTL5000_CHIP_ANA_POWER);
+ reg |= SGTL5000_LINEREG_D_POWERUP;
+ codec->write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+
+ reg &= ~SGTL5000_LINREG_SIMPLE_POWERUP;
+ /* when internal ldo enabled, simple digital power can be disabled */
+ codec->write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+
+ udelay(10);
+
+ ldo->enabled = 1;
+ return 0;
+}
+
+static int ldo_regulator_disable(struct regulator_dev *dev)
+{
+ struct ldo_regulator *ldo = rdev_get_drvdata(dev);
+ struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data;
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_LINEREG_D_POWERUP,
+ 0);
+
+ /* clear voltage info */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
+ (0x1 << 4) - 1, 0);
+
+ ldo->enabled = 0;
+
+ return 0;
+}
+
+static int ldo_regulator_get_voltage(struct regulator_dev *dev)
+{
+ struct ldo_regulator *ldo = rdev_get_drvdata(dev);
+
+ return ldo->voltage;
+}
+
+static struct regulator_ops ldo_regulator_ops = {
+ .is_enabled = ldo_regulator_is_enabled,
+ .enable = ldo_regulator_enable,
+ .disable = ldo_regulator_disable,
+ .get_voltage = ldo_regulator_get_voltage,
+};
+
+static int ldo_regulator_register(struct snd_soc_codec *codec,
+ struct regulator_init_data *init_data,
+ int voltage)
+{
+ struct ldo_regulator *ldo;
+
+ ldo = kzalloc(sizeof(struct ldo_regulator), GFP_KERNEL);
+
+ if (!ldo) {
+ dev_err(codec->dev, "failed to allocate ldo_regulator\n");
+ return -ENOMEM;
+ }
+
+ ldo->desc.name = kstrdup(dev_name(codec->dev), GFP_KERNEL);
+ if (!ldo->desc.name) {
+ kfree(ldo);
+ dev_err(codec->dev, "failed to allocate decs name memory\n");
+ return -ENOMEM;
+ }
+
+ ldo->desc.type = REGULATOR_VOLTAGE;
+ ldo->desc.owner = THIS_MODULE;
+ ldo->desc.ops = &ldo_regulator_ops;
+ ldo->desc.n_voltages = 1;
+
+ ldo->codec_data = codec;
+ ldo->voltage = voltage;
+
+ ldo->dev = regulator_register(&ldo->desc, codec->dev,
+ init_data, ldo);
+ if (IS_ERR(ldo->dev)) {
+ int ret = PTR_ERR(ldo->dev);
+
+ dev_err(codec->dev, "failed to register regulator\n");
+ kfree(ldo->desc.name);
+ kfree(ldo);
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ldo_regulator_remove(struct snd_soc_codec *codec)
+{
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ struct ldo_regulator *ldo = sgtl5000->ldo;
+
+ if (!ldo)
+ return 0;
+
+ regulator_unregister(ldo->dev);
+ kfree(ldo->desc.name);
+ kfree(ldo);
+
+ return 0;
+}
+#else
+static int ldo_regulator_register(struct snd_soc_codec *codec,
+ struct regulator_init_data *init_data,
+ int voltage)
+{
+ return -EINVAL;
+}
+
+static int ldo_regulator_remove(struct snd_soc_codec *codec)
+{
+ return 0;
+}
+#endif
+
+/*
+ * set dac bias
+ * common state changes:
+ * startup:
+ * off --> standby --> prepare --> on
+ * standby --> prepare --> on
+ *
+ * stop:
+ * on --> prepare --> standby
+ */
+static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ if (codec->dapm.bias_level == level)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+
+ ret = snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP);
+ if (ret)
+ msleep(400);
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ ret = snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_VAG_POWERUP, 0);
+ if (ret)
+ msleep(600);
+
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(
+ ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (ret)
+ return ret;
+ udelay(10);
+ }
+
+ ret = snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_VAG_POWERUP, 0);
+ if (ret)
+ msleep(600);
+ break;
+ case SND_SOC_BIAS_OFF:
+ ret = snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_VAG_POWERUP, 0);
+ if (ret)
+ msleep(600);
+
+ regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define SGTL5000_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops sgtl5000_ops = {
+ .hw_params = sgtl5000_pcm_hw_params,
+ .digital_mute = sgtl5000_digital_mute,
+ .set_fmt = sgtl5000_set_dai_fmt,
+ .set_sysclk = sgtl5000_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver sgtl5000_dai = {
+ .name = "sgtl5000",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ /*
+ * only support 8~48K + 96K,
+ * TODO modify hw_param to support more
+ */
+ .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000,
+ .formats = SGTL5000_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000,
+ .formats = SGTL5000_FORMATS,
+ },
+ .ops = &sgtl5000_ops,
+ .symmetric_rates = 1,
+};
+
+static int sgtl5000_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case SGTL5000_CHIP_ID:
+ case SGTL5000_CHIP_ADCDAC_CTRL:
+ case SGTL5000_CHIP_ANA_STATUS:
+ return 1;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_SUSPEND
+static int sgtl5000_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+/*
+ * restore all sgtl5000 registers,
+ * since a big hole between dap and regular registers,
+ * we will restore them respectively.
+ */
+static int sgtl5000_restore_regs(struct snd_soc_codec *codec)
+{
+ u16 *cache = codec->reg_cache;
+ u16 reg;
+
+ /* restore regular registers */
+ for (reg = 0; reg <= SGTL5000_CHIP_SHORT_CTRL; reg += 2) {
+
+ /* this regs depends on the others */
+ if (reg == SGTL5000_CHIP_ANA_POWER ||
+ reg == SGTL5000_CHIP_CLK_CTRL ||
+ reg == SGTL5000_CHIP_LINREG_CTRL ||
+ reg == SGTL5000_CHIP_LINE_OUT_CTRL ||
+ reg == SGTL5000_CHIP_CLK_CTRL)
+ continue;
+
+ snd_soc_write(codec, reg, cache[reg]);
+ }
+
+ /* restore dap registers */
+ for (reg = SGTL5000_DAP_REG_OFFSET; reg < SGTL5000_MAX_REG_OFFSET; reg += 2)
+ snd_soc_write(codec, reg, cache[reg]);
+
+ /*
+ * restore power and other regs according
+ * to set_power() and set_clock()
+ */
+ snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL,
+ cache[SGTL5000_CHIP_LINREG_CTRL]);
+
+ snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER,
+ cache[SGTL5000_CHIP_ANA_POWER]);
+
+ snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL,
+ cache[SGTL5000_CHIP_CLK_CTRL]);
+
+ snd_soc_write(codec, SGTL5000_CHIP_REF_CTRL,
+ cache[SGTL5000_CHIP_REF_CTRL]);
+
+ snd_soc_write(codec, SGTL5000_CHIP_LINE_OUT_CTRL,
+ cache[SGTL5000_CHIP_LINE_OUT_CTRL]);
+ return 0;
+}
+
+static int sgtl5000_resume(struct snd_soc_codec *codec)
+{
+ /* Bring the codec back up to standby to enable regulators */
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Restore registers by cached in memory */
+ sgtl5000_restore_regs(codec);
+ return 0;
+}
+#else
+#define sgtl5000_suspend NULL
+#define sgtl5000_resume NULL
+#endif /* CONFIG_SUSPEND */
+
+/*
+ * sgtl5000 has 3 internal power supplies:
+ * 1. VAG, normally set to vdda/2
+ * 2. chargepump, set to different value
+ * according to voltage of vdda and vddio
+ * 3. line out VAG, normally set to vddio/2
+ *
+ * and should be set according to:
+ * 1. vddd provided by external or not
+ * 2. vdda and vddio voltage value. > 3.1v or not
+ * 3. chip revision >=0x11 or not. If >=0x11, not use external vddd.
+ */
+static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
+{
+ int vddd;
+ int vdda;
+ int vddio;
+ u16 ana_pwr;
+ u16 lreg_ctrl;
+ int vag;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ vdda = regulator_get_voltage(sgtl5000->supplies[VDDA].consumer);
+ vddio = regulator_get_voltage(sgtl5000->supplies[VDDIO].consumer);
+ vddd = regulator_get_voltage(sgtl5000->supplies[VDDD].consumer);
+
+ vdda = vdda / 1000;
+ vddio = vddio / 1000;
+ vddd = vddd / 1000;
+
+ if (vdda <= 0 || vddio <= 0 || vddd < 0) {
+ dev_err(codec->dev, "regulator voltage not set correctly\n");
+
+ return -EINVAL;
+ }
+
+ /* according to datasheet, maximum voltage of supplies */
+ if (vdda > 3600 || vddio > 3600 || vddd > 1980) {
+ dev_err(codec->dev,
+ "exceed max voltage vdda %dmv vddio %dma vddd %dma\n",
+ vdda, vddio, vddd);
+
+ return -EINVAL;
+ }
+
+ /* reset value */
+ ana_pwr = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
+ ana_pwr |= SGTL5000_DAC_STEREO |
+ SGTL5000_ADC_STEREO |
+ SGTL5000_REFTOP_POWERUP;
+ lreg_ctrl = snd_soc_read(codec, SGTL5000_CHIP_LINREG_CTRL);
+
+ if (vddio < 3100 && vdda < 3100) {
+ /* enable internal oscillator used for charge pump */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_CLK_TOP_CTRL,
+ SGTL5000_INT_OSC_EN,
+ SGTL5000_INT_OSC_EN);
+ /* Enable VDDC charge pump */
+ ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP;
+ } else if (vddio >= 3100 && vdda >= 3100) {
+ /*
+ * if vddio and vddd > 3.1v,
+ * charge pump should be clean before set ana_pwr
+ */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_VDDC_CHRGPMP_POWERUP, 0);
+
+ /* VDDC use VDDIO rail */
+ lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD;
+ lreg_ctrl |= SGTL5000_VDDC_MAN_ASSN_VDDIO <<
+ SGTL5000_VDDC_MAN_ASSN_SHIFT;
+ }
+
+ snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl);
+
+ snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
+
+ /* set voltage to register */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
+ (0x1 << 4) - 1, 0x8);
+
+ /*
+ * if vddd linear reg has been enabled,
+ * simple digital supply should be clear to get
+ * proper VDDD voltage.
+ */
+ if (ana_pwr & SGTL5000_LINEREG_D_POWERUP)
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_LINREG_SIMPLE_POWERUP,
+ 0);
+ else
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_LINREG_SIMPLE_POWERUP |
+ SGTL5000_STARTUP_POWERUP,
+ 0);
+
+ /*
+ * set ADC/DAC VAG to vdda / 2,
+ * should stay in range (0.8v, 1.575v)
+ */
+ vag = vdda / 2;
+ if (vag <= SGTL5000_ANA_GND_BASE)
+ vag = 0;
+ else if (vag >= SGTL5000_ANA_GND_BASE + SGTL5000_ANA_GND_STP *
+ (SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT))
+ vag = SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT;
+ else
+ vag = (vag - SGTL5000_ANA_GND_BASE) / SGTL5000_ANA_GND_STP;
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL,
+ SGTL5000_ANA_GND_MASK,
+ vag << SGTL5000_ANA_GND_SHIFT);
+
+ /* set line out VAG to vddio / 2, in range (0.8v, 1.675v) */
+ vag = vddio / 2;
+ if (vag <= SGTL5000_LINE_OUT_GND_BASE)
+ vag = 0;
+ else if (vag >= SGTL5000_LINE_OUT_GND_BASE +
+ SGTL5000_LINE_OUT_GND_STP * SGTL5000_LINE_OUT_GND_MAX)
+ vag = SGTL5000_LINE_OUT_GND_MAX;
+ else
+ vag = (vag - SGTL5000_LINE_OUT_GND_BASE) /
+ SGTL5000_LINE_OUT_GND_STP;
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_LINE_OUT_CTRL,
+ SGTL5000_LINE_OUT_GND_MASK |
+ SGTL5000_LINE_OUT_CURRENT_MASK,
+ vag << SGTL5000_LINE_OUT_GND_SHIFT |
+ SGTL5000_LINE_OUT_CURRENT_360u <<
+ SGTL5000_LINE_OUT_CURRENT_SHIFT);
+
+ return 0;
+}
+
+static int sgtl5000_enable_regulators(struct snd_soc_codec *codec)
+{
+ u16 reg;
+ int ret;
+ int rev;
+ int i;
+ int external_vddd = 0;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ for (i = 0; i < ARRAY_SIZE(sgtl5000->supplies); i++)
+ sgtl5000->supplies[i].supply = supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (!ret)
+ external_vddd = 1;
+ else {
+ /* set internal ldo to 1.2v */
+ int voltage = LDO_VOLTAGE;
+
+ ret = ldo_regulator_register(codec, &ldo_init_data, voltage);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to register vddd internal supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME;
+ ret = regulator_bulk_get(codec->dev,
+ ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+
+ if (ret) {
+ ldo_regulator_remove(codec);
+ dev_err(codec->dev,
+ "Failed to request supplies: %d\n", ret);
+
+ return ret;
+ }
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (ret)
+ goto err_regulator_free;
+
+ /* wait for all power rails bring up */
+ udelay(10);
+
+ /* read chip information */
+ reg = snd_soc_read(codec, SGTL5000_CHIP_ID);
+ if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) !=
+ SGTL5000_PARTID_PART_ID) {
+ dev_err(codec->dev,
+ "Device with ID register %x is not a sgtl5000\n", reg);
+ ret = -ENODEV;
+ goto err_regulator_disable;
+ }
+
+ rev = (reg & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT;
+ dev_info(codec->dev, "sgtl5000 revision %d\n", rev);
+
+ /*
+ * workaround for revision 0x11 and later,
+ * roll back to use internal LDO
+ */
+ if (external_vddd && rev >= 0x11) {
+ int voltage = LDO_VOLTAGE;
+ /* disable all regulator first */
+ regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ /* free VDDD regulator */
+ regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+
+ ret = ldo_regulator_register(codec, &ldo_init_data, voltage);
+ if (ret)
+ return ret;
+
+ sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME;
+
+ ret = regulator_bulk_get(codec->dev,
+ ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (ret) {
+ ldo_regulator_remove(codec);
+ dev_err(codec->dev,
+ "Failed to request supplies: %d\n", ret);
+
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (ret)
+ goto err_regulator_free;
+
+ /* wait for all power rails bring up */
+ udelay(10);
+ }
+
+ return 0;
+
+err_regulator_disable:
+ regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+err_regulator_free:
+ regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (external_vddd)
+ ldo_regulator_remove(codec);
+ return ret;
+
+}
+
+static int sgtl5000_fill_reg_cache(struct snd_soc_codec *codec)
+{
+ int reg;
+ int step = codec->driver->reg_cache_step;
+ u16 *cache = codec->reg_cache;
+
+ for (reg = SGTL5000_DAP_REG_OFFSET;
+ reg <= SGTL5000_MAX_REG_OFFSET; reg += step)
+ cache[reg] = codec->hw_read(codec, reg);
+
+ for (reg = 0; reg <= SGTL5000_CHIP_SHORT_CTRL; reg += step)
+ cache[reg] = codec->hw_read(codec, reg);
+
+ return 0;
+}
+
+static int sgtl5000_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ /* setup i2c data ops */
+ ret = snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_I2C);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = sgtl5000_enable_regulators(codec);
+ if (ret)
+ return ret;
+
+ sgtl5000_fill_reg_cache(codec);
+
+ /* power up sgtl5000 */
+ ret = sgtl5000_set_power_regs(codec);
+ if (ret)
+ goto err;
+
+ /* enable small pop, introduce 400ms delay in turning off */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL,
+ SGTL5000_SMALL_POP,
+ SGTL5000_SMALL_POP);
+
+ /* disable short cut detector */
+ snd_soc_write(codec, SGTL5000_CHIP_SHORT_CTRL, 0);
+
+ /*
+ * set i2s as default input of sound switch
+ * TODO: add sound switch to control and dapm widge.
+ */
+ snd_soc_write(codec, SGTL5000_CHIP_SSS_CTRL,
+ SGTL5000_DAC_SEL_I2S_IN << SGTL5000_DAC_SEL_SHIFT);
+ snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER,
+ SGTL5000_ADC_EN | SGTL5000_DAC_EN);
+
+ /* enable dac volume ramp by default */
+ snd_soc_write(codec, SGTL5000_CHIP_ADCDAC_CTRL,
+ SGTL5000_DAC_VOL_RAMP_EN |
+ SGTL5000_DAC_MUTE_RIGHT |
+ SGTL5000_DAC_MUTE_LEFT);
+
+ snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f);
+
+ snd_soc_write(codec, SGTL5000_CHIP_ANA_CTRL,
+ SGTL5000_HP_ZCD_EN |
+ SGTL5000_ADC_ZCD_EN);
+
+ snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, 0);
+
+ snd_soc_write(codec, SGTL5000_CHIP_DAC_VOL, 0x6060);
+ snd_soc_write(codec, SGTL5000_CHIP_ANA_ADC_CTRL,
+ (0xf << SGTL5000_ADC_VOL_LEFT_SHIFT) |\
+ (0xf << SGTL5000_ADC_VOL_RIGHT_SHIFT));
+
+ /*
+ * disable DAP
+ * TODO:
+ * Enable DAP in kcontrol and dapm.
+ */
+ snd_soc_write(codec, SGTL5000_DAP_CTRL, 0);
+
+ /* leading to standby state */
+ ret = sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ if (ret)
+ goto err;
+
+ snd_soc_add_controls(codec, sgtl5000_snd_controls,
+ ARRAY_SIZE(sgtl5000_snd_controls));
+
+ snd_soc_dapm_new_controls(&codec->dapm, sgtl5000_dapm_widgets,
+ ARRAY_SIZE(sgtl5000_dapm_widgets));
+
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map,
+ ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(&codec->dapm);
+
+ return 0;
+
+err:
+ regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ ldo_regulator_remove(codec);
+
+ return ret;
+}
+
+static int sgtl5000_remove(struct snd_soc_codec *codec)
+{
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ ldo_regulator_remove(codec);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver sgtl5000_driver = {
+ .probe = sgtl5000_probe,
+ .remove = sgtl5000_remove,
+ .suspend = sgtl5000_suspend,
+ .resume = sgtl5000_resume,
+ .set_bias_level = sgtl5000_set_bias_level,
+ .reg_cache_size = SGTL5000_MAX_REG_OFFSET,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+ .volatile_register = sgtl5000_volatile_register,
+};
+
+static __devinit int sgtl5000_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct sgtl5000_priv *sgtl5000;
+ int ret;
+
+ sgtl5000 = kzalloc(sizeof(struct sgtl5000_priv), GFP_KERNEL);
+ if (!sgtl5000)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, sgtl5000);
+
+ ret = snd_soc_register_codec(&client->dev,
+ &sgtl5000_driver, &sgtl5000_dai, 1);
+ if (ret) {
+ dev_err(&client->dev, "Failed to register codec: %d\n", ret);
+ kfree(sgtl5000);
+ return ret;
+ }
+
+ return 0;
+}
+
+static __devexit int sgtl5000_i2c_remove(struct i2c_client *client)
+{
+ struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_codec(&client->dev);
+
+ kfree(sgtl5000);
+ return 0;
+}
+
+static const struct i2c_device_id sgtl5000_id[] = {
+ {"sgtl5000", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, sgtl5000_id);
+
+static struct i2c_driver sgtl5000_i2c_driver = {
+ .driver = {
+ .name = "sgtl5000",
+ .owner = THIS_MODULE,
+ },
+ .probe = sgtl5000_i2c_probe,
+ .remove = __devexit_p(sgtl5000_i2c_remove),
+ .id_table = sgtl5000_id,
+};
+
+static int __init sgtl5000_modinit(void)
+{
+ return i2c_add_driver(&sgtl5000_i2c_driver);
+}
+module_init(sgtl5000_modinit);
+
+static void __exit sgtl5000_exit(void)
+{
+ i2c_del_driver(&sgtl5000_i2c_driver);
+}
+module_exit(sgtl5000_exit);
+
+MODULE_DESCRIPTION("Freescale SGTL5000 ALSA SoC Codec Driver");
+MODULE_AUTHOR("Zeng Zhaoming <zhaoming.zeng@freescale.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h
new file mode 100644
index 00000000..8a9f4353
--- /dev/null
+++ b/sound/soc/codecs/sgtl5000.h
@@ -0,0 +1,400 @@
+/*
+ * sgtl5000.h - SGTL5000 audio codec interface
+ *
+ * Copyright 2010-2011 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _SGTL5000_H
+#define _SGTL5000_H
+
+/*
+ * Register values.
+ */
+#define SGTL5000_CHIP_ID 0x0000
+#define SGTL5000_CHIP_DIG_POWER 0x0002
+#define SGTL5000_CHIP_CLK_CTRL 0x0004
+#define SGTL5000_CHIP_I2S_CTRL 0x0006
+#define SGTL5000_CHIP_SSS_CTRL 0x000a
+#define SGTL5000_CHIP_ADCDAC_CTRL 0x000e
+#define SGTL5000_CHIP_DAC_VOL 0x0010
+#define SGTL5000_CHIP_PAD_STRENGTH 0x0014
+#define SGTL5000_CHIP_ANA_ADC_CTRL 0x0020
+#define SGTL5000_CHIP_ANA_HP_CTRL 0x0022
+#define SGTL5000_CHIP_ANA_CTRL 0x0024
+#define SGTL5000_CHIP_LINREG_CTRL 0x0026
+#define SGTL5000_CHIP_REF_CTRL 0x0028
+#define SGTL5000_CHIP_MIC_CTRL 0x002a
+#define SGTL5000_CHIP_LINE_OUT_CTRL 0x002c
+#define SGTL5000_CHIP_LINE_OUT_VOL 0x002e
+#define SGTL5000_CHIP_ANA_POWER 0x0030
+#define SGTL5000_CHIP_PLL_CTRL 0x0032
+#define SGTL5000_CHIP_CLK_TOP_CTRL 0x0034
+#define SGTL5000_CHIP_ANA_STATUS 0x0036
+#define SGTL5000_CHIP_SHORT_CTRL 0x003c
+#define SGTL5000_CHIP_ANA_TEST2 0x003a
+#define SGTL5000_DAP_CTRL 0x0100
+#define SGTL5000_DAP_PEQ 0x0102
+#define SGTL5000_DAP_BASS_ENHANCE 0x0104
+#define SGTL5000_DAP_BASS_ENHANCE_CTRL 0x0106
+#define SGTL5000_DAP_AUDIO_EQ 0x0108
+#define SGTL5000_DAP_SURROUND 0x010a
+#define SGTL5000_DAP_FLT_COEF_ACCESS 0x010c
+#define SGTL5000_DAP_COEF_WR_B0_MSB 0x010e
+#define SGTL5000_DAP_COEF_WR_B0_LSB 0x0110
+#define SGTL5000_DAP_EQ_BASS_BAND0 0x0116
+#define SGTL5000_DAP_EQ_BASS_BAND1 0x0118
+#define SGTL5000_DAP_EQ_BASS_BAND2 0x011a
+#define SGTL5000_DAP_EQ_BASS_BAND3 0x011c
+#define SGTL5000_DAP_EQ_BASS_BAND4 0x011e
+#define SGTL5000_DAP_MAIN_CHAN 0x0120
+#define SGTL5000_DAP_MIX_CHAN 0x0122
+#define SGTL5000_DAP_AVC_CTRL 0x0124
+#define SGTL5000_DAP_AVC_THRESHOLD 0x0126
+#define SGTL5000_DAP_AVC_ATTACK 0x0128
+#define SGTL5000_DAP_AVC_DECAY 0x012a
+#define SGTL5000_DAP_COEF_WR_B1_MSB 0x012c
+#define SGTL5000_DAP_COEF_WR_B1_LSB 0x012e
+#define SGTL5000_DAP_COEF_WR_B2_MSB 0x0130
+#define SGTL5000_DAP_COEF_WR_B2_LSB 0x0132
+#define SGTL5000_DAP_COEF_WR_A1_MSB 0x0134
+#define SGTL5000_DAP_COEF_WR_A1_LSB 0x0136
+#define SGTL5000_DAP_COEF_WR_A2_MSB 0x0138
+#define SGTL5000_DAP_COEF_WR_A2_LSB 0x013a
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * SGTL5000_CHIP_ID
+ */
+#define SGTL5000_PARTID_MASK 0xff00
+#define SGTL5000_PARTID_SHIFT 8
+#define SGTL5000_PARTID_WIDTH 8
+#define SGTL5000_PARTID_PART_ID 0xa0
+#define SGTL5000_REVID_MASK 0x00ff
+#define SGTL5000_REVID_SHIFT 0
+#define SGTL5000_REVID_WIDTH 8
+
+/*
+ * SGTL5000_CHIP_DIG_POWER
+ */
+#define SGTL5000_ADC_EN 0x0040
+#define SGTL5000_DAC_EN 0x0020
+#define SGTL5000_DAP_POWERUP 0x0010
+#define SGTL5000_I2S_OUT_POWERUP 0x0002
+#define SGTL5000_I2S_IN_POWERUP 0x0001
+
+/*
+ * SGTL5000_CHIP_CLK_CTRL
+ */
+#define SGTL5000_RATE_MODE_MASK 0x0030
+#define SGTL5000_RATE_MODE_SHIFT 4
+#define SGTL5000_RATE_MODE_WIDTH 2
+#define SGTL5000_RATE_MODE_DIV_1 0
+#define SGTL5000_RATE_MODE_DIV_2 1
+#define SGTL5000_RATE_MODE_DIV_4 2
+#define SGTL5000_RATE_MODE_DIV_6 3
+#define SGTL5000_SYS_FS_MASK 0x000c
+#define SGTL5000_SYS_FS_SHIFT 2
+#define SGTL5000_SYS_FS_WIDTH 2
+#define SGTL5000_SYS_FS_32k 0x0
+#define SGTL5000_SYS_FS_44_1k 0x1
+#define SGTL5000_SYS_FS_48k 0x2
+#define SGTL5000_SYS_FS_96k 0x3
+#define SGTL5000_MCLK_FREQ_MASK 0x0003
+#define SGTL5000_MCLK_FREQ_SHIFT 0
+#define SGTL5000_MCLK_FREQ_WIDTH 2
+#define SGTL5000_MCLK_FREQ_256FS 0x0
+#define SGTL5000_MCLK_FREQ_384FS 0x1
+#define SGTL5000_MCLK_FREQ_512FS 0x2
+#define SGTL5000_MCLK_FREQ_PLL 0x3
+
+/*
+ * SGTL5000_CHIP_I2S_CTRL
+ */
+#define SGTL5000_I2S_SCLKFREQ_MASK 0x0100
+#define SGTL5000_I2S_SCLKFREQ_SHIFT 8
+#define SGTL5000_I2S_SCLKFREQ_WIDTH 1
+#define SGTL5000_I2S_SCLKFREQ_64FS 0x0
+#define SGTL5000_I2S_SCLKFREQ_32FS 0x1 /* Not for RJ mode */
+#define SGTL5000_I2S_MASTER 0x0080
+#define SGTL5000_I2S_SCLK_INV 0x0040
+#define SGTL5000_I2S_DLEN_MASK 0x0030
+#define SGTL5000_I2S_DLEN_SHIFT 4
+#define SGTL5000_I2S_DLEN_WIDTH 2
+#define SGTL5000_I2S_DLEN_32 0x0
+#define SGTL5000_I2S_DLEN_24 0x1
+#define SGTL5000_I2S_DLEN_20 0x2
+#define SGTL5000_I2S_DLEN_16 0x3
+#define SGTL5000_I2S_MODE_MASK 0x000c
+#define SGTL5000_I2S_MODE_SHIFT 2
+#define SGTL5000_I2S_MODE_WIDTH 2
+#define SGTL5000_I2S_MODE_I2S_LJ 0x0
+#define SGTL5000_I2S_MODE_RJ 0x1
+#define SGTL5000_I2S_MODE_PCM 0x2
+#define SGTL5000_I2S_LRALIGN 0x0002
+#define SGTL5000_I2S_LRPOL 0x0001 /* set for which mode */
+
+/*
+ * SGTL5000_CHIP_SSS_CTRL
+ */
+#define SGTL5000_DAP_MIX_LRSWAP 0x4000
+#define SGTL5000_DAP_LRSWAP 0x2000
+#define SGTL5000_DAC_LRSWAP 0x1000
+#define SGTL5000_I2S_OUT_LRSWAP 0x0400
+#define SGTL5000_DAP_MIX_SEL_MASK 0x0300
+#define SGTL5000_DAP_MIX_SEL_SHIFT 8
+#define SGTL5000_DAP_MIX_SEL_WIDTH 2
+#define SGTL5000_DAP_MIX_SEL_ADC 0x0
+#define SGTL5000_DAP_MIX_SEL_I2S_IN 0x1
+#define SGTL5000_DAP_SEL_MASK 0x00c0
+#define SGTL5000_DAP_SEL_SHIFT 6
+#define SGTL5000_DAP_SEL_WIDTH 2
+#define SGTL5000_DAP_SEL_ADC 0x0
+#define SGTL5000_DAP_SEL_I2S_IN 0x1
+#define SGTL5000_DAC_SEL_MASK 0x0030
+#define SGTL5000_DAC_SEL_SHIFT 4
+#define SGTL5000_DAC_SEL_WIDTH 2
+#define SGTL5000_DAC_SEL_ADC 0x0
+#define SGTL5000_DAC_SEL_I2S_IN 0x1
+#define SGTL5000_DAC_SEL_DAP 0x3
+#define SGTL5000_I2S_OUT_SEL_MASK 0x0003
+#define SGTL5000_I2S_OUT_SEL_SHIFT 0
+#define SGTL5000_I2S_OUT_SEL_WIDTH 2
+#define SGTL5000_I2S_OUT_SEL_ADC 0x0
+#define SGTL5000_I2S_OUT_SEL_I2S_IN 0x1
+#define SGTL5000_I2S_OUT_SEL_DAP 0x3
+
+/*
+ * SGTL5000_CHIP_ADCDAC_CTRL
+ */
+#define SGTL5000_VOL_BUSY_DAC_RIGHT 0x2000
+#define SGTL5000_VOL_BUSY_DAC_LEFT 0x1000
+#define SGTL5000_DAC_VOL_RAMP_EN 0x0200
+#define SGTL5000_DAC_VOL_RAMP_EXPO 0x0100
+#define SGTL5000_DAC_MUTE_RIGHT 0x0008
+#define SGTL5000_DAC_MUTE_LEFT 0x0004
+#define SGTL5000_ADC_HPF_FREEZE 0x0002
+#define SGTL5000_ADC_HPF_BYPASS 0x0001
+
+/*
+ * SGTL5000_CHIP_DAC_VOL
+ */
+#define SGTL5000_DAC_VOL_RIGHT_MASK 0xff00
+#define SGTL5000_DAC_VOL_RIGHT_SHIFT 8
+#define SGTL5000_DAC_VOL_RIGHT_WIDTH 8
+#define SGTL5000_DAC_VOL_LEFT_MASK 0x00ff
+#define SGTL5000_DAC_VOL_LEFT_SHIFT 0
+#define SGTL5000_DAC_VOL_LEFT_WIDTH 8
+
+/*
+ * SGTL5000_CHIP_PAD_STRENGTH
+ */
+#define SGTL5000_PAD_I2S_LRCLK_MASK 0x0300
+#define SGTL5000_PAD_I2S_LRCLK_SHIFT 8
+#define SGTL5000_PAD_I2S_LRCLK_WIDTH 2
+#define SGTL5000_PAD_I2S_SCLK_MASK 0x00c0
+#define SGTL5000_PAD_I2S_SCLK_SHIFT 6
+#define SGTL5000_PAD_I2S_SCLK_WIDTH 2
+#define SGTL5000_PAD_I2S_DOUT_MASK 0x0030
+#define SGTL5000_PAD_I2S_DOUT_SHIFT 4
+#define SGTL5000_PAD_I2S_DOUT_WIDTH 2
+#define SGTL5000_PAD_I2C_SDA_MASK 0x000c
+#define SGTL5000_PAD_I2C_SDA_SHIFT 2
+#define SGTL5000_PAD_I2C_SDA_WIDTH 2
+#define SGTL5000_PAD_I2C_SCL_MASK 0x0003
+#define SGTL5000_PAD_I2C_SCL_SHIFT 0
+#define SGTL5000_PAD_I2C_SCL_WIDTH 2
+
+/*
+ * SGTL5000_CHIP_ANA_ADC_CTRL
+ */
+#define SGTL5000_ADC_VOL_M6DB 0x0100
+#define SGTL5000_ADC_VOL_RIGHT_MASK 0x00f0
+#define SGTL5000_ADC_VOL_RIGHT_SHIFT 4
+#define SGTL5000_ADC_VOL_RIGHT_WIDTH 4
+#define SGTL5000_ADC_VOL_LEFT_MASK 0x000f
+#define SGTL5000_ADC_VOL_LEFT_SHIFT 0
+#define SGTL5000_ADC_VOL_LEFT_WIDTH 4
+
+/*
+ * SGTL5000_CHIP_ANA_HP_CTRL
+ */
+#define SGTL5000_HP_VOL_RIGHT_MASK 0x7f00
+#define SGTL5000_HP_VOL_RIGHT_SHIFT 8
+#define SGTL5000_HP_VOL_RIGHT_WIDTH 7
+#define SGTL5000_HP_VOL_LEFT_MASK 0x007f
+#define SGTL5000_HP_VOL_LEFT_SHIFT 0
+#define SGTL5000_HP_VOL_LEFT_WIDTH 7
+
+/*
+ * SGTL5000_CHIP_ANA_CTRL
+ */
+#define SGTL5000_LINE_OUT_MUTE 0x0100
+#define SGTL5000_HP_SEL_MASK 0x0040
+#define SGTL5000_HP_SEL_SHIFT 6
+#define SGTL5000_HP_SEL_WIDTH 1
+#define SGTL5000_HP_SEL_DAC 0x0
+#define SGTL5000_HP_SEL_LINE_IN 0x1
+#define SGTL5000_HP_ZCD_EN 0x0020
+#define SGTL5000_HP_MUTE 0x0010
+#define SGTL5000_ADC_SEL_MASK 0x0004
+#define SGTL5000_ADC_SEL_SHIFT 2
+#define SGTL5000_ADC_SEL_WIDTH 1
+#define SGTL5000_ADC_SEL_MIC 0x0
+#define SGTL5000_ADC_SEL_LINE_IN 0x1
+#define SGTL5000_ADC_ZCD_EN 0x0002
+#define SGTL5000_ADC_MUTE 0x0001
+
+/*
+ * SGTL5000_CHIP_LINREG_CTRL
+ */
+#define SGTL5000_VDDC_MAN_ASSN_MASK 0x0040
+#define SGTL5000_VDDC_MAN_ASSN_SHIFT 6
+#define SGTL5000_VDDC_MAN_ASSN_WIDTH 1
+#define SGTL5000_VDDC_MAN_ASSN_VDDA 0x0
+#define SGTL5000_VDDC_MAN_ASSN_VDDIO 0x1
+#define SGTL5000_VDDC_ASSN_OVRD 0x0020
+#define SGTL5000_LINREG_VDDD_MASK 0x000f
+#define SGTL5000_LINREG_VDDD_SHIFT 0
+#define SGTL5000_LINREG_VDDD_WIDTH 4
+
+/*
+ * SGTL5000_CHIP_REF_CTRL
+ */
+#define SGTL5000_ANA_GND_MASK 0x01f0
+#define SGTL5000_ANA_GND_SHIFT 4
+#define SGTL5000_ANA_GND_WIDTH 5
+#define SGTL5000_ANA_GND_BASE 800 /* mv */
+#define SGTL5000_ANA_GND_STP 25 /*mv */
+#define SGTL5000_BIAS_CTRL_MASK 0x000e
+#define SGTL5000_BIAS_CTRL_SHIFT 1
+#define SGTL5000_BIAS_CTRL_WIDTH 3
+#define SGTL5000_SMALL_POP 0x0001
+
+/*
+ * SGTL5000_CHIP_MIC_CTRL
+ */
+#define SGTL5000_BIAS_R_MASK 0x0300
+#define SGTL5000_BIAS_R_SHIFT 8
+#define SGTL5000_BIAS_R_WIDTH 2
+#define SGTL5000_BIAS_R_off 0x0
+#define SGTL5000_BIAS_R_2K 0x1
+#define SGTL5000_BIAS_R_4k 0x2
+#define SGTL5000_BIAS_R_8k 0x3
+#define SGTL5000_BIAS_VOLT_MASK 0x0070
+#define SGTL5000_BIAS_VOLT_SHIFT 4
+#define SGTL5000_BIAS_VOLT_WIDTH 3
+#define SGTL5000_MIC_GAIN_MASK 0x0003
+#define SGTL5000_MIC_GAIN_SHIFT 0
+#define SGTL5000_MIC_GAIN_WIDTH 2
+
+/*
+ * SGTL5000_CHIP_LINE_OUT_CTRL
+ */
+#define SGTL5000_LINE_OUT_CURRENT_MASK 0x0f00
+#define SGTL5000_LINE_OUT_CURRENT_SHIFT 8
+#define SGTL5000_LINE_OUT_CURRENT_WIDTH 4
+#define SGTL5000_LINE_OUT_CURRENT_180u 0x0
+#define SGTL5000_LINE_OUT_CURRENT_270u 0x1
+#define SGTL5000_LINE_OUT_CURRENT_360u 0x3
+#define SGTL5000_LINE_OUT_CURRENT_450u 0x7
+#define SGTL5000_LINE_OUT_CURRENT_540u 0xf
+#define SGTL5000_LINE_OUT_GND_MASK 0x003f
+#define SGTL5000_LINE_OUT_GND_SHIFT 0
+#define SGTL5000_LINE_OUT_GND_WIDTH 6
+#define SGTL5000_LINE_OUT_GND_BASE 800 /* mv */
+#define SGTL5000_LINE_OUT_GND_STP 25
+#define SGTL5000_LINE_OUT_GND_MAX 0x23
+
+/*
+ * SGTL5000_CHIP_LINE_OUT_VOL
+ */
+#define SGTL5000_LINE_OUT_VOL_RIGHT_MASK 0x1f00
+#define SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT 8
+#define SGTL5000_LINE_OUT_VOL_RIGHT_WIDTH 5
+#define SGTL5000_LINE_OUT_VOL_LEFT_MASK 0x001f
+#define SGTL5000_LINE_OUT_VOL_LEFT_SHIFT 0
+#define SGTL5000_LINE_OUT_VOL_LEFT_WIDTH 5
+
+/*
+ * SGTL5000_CHIP_ANA_POWER
+ */
+#define SGTL5000_DAC_STEREO 0x4000
+#define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000
+#define SGTL5000_STARTUP_POWERUP 0x1000
+#define SGTL5000_VDDC_CHRGPMP_POWERUP 0x0800
+#define SGTL5000_PLL_POWERUP 0x0400
+#define SGTL5000_LINEREG_D_POWERUP 0x0200
+#define SGTL5000_VCOAMP_POWERUP 0x0100
+#define SGTL5000_VAG_POWERUP 0x0080
+#define SGTL5000_ADC_STEREO 0x0040
+#define SGTL5000_REFTOP_POWERUP 0x0020
+#define SGTL5000_HP_POWERUP 0x0010
+#define SGTL5000_DAC_POWERUP 0x0008
+#define SGTL5000_CAPLESS_HP_POWERUP 0x0004
+#define SGTL5000_ADC_POWERUP 0x0002
+#define SGTL5000_LINE_OUT_POWERUP 0x0001
+
+/*
+ * SGTL5000_CHIP_PLL_CTRL
+ */
+#define SGTL5000_PLL_INT_DIV_MASK 0xf800
+#define SGTL5000_PLL_INT_DIV_SHIFT 11
+#define SGTL5000_PLL_INT_DIV_WIDTH 5
+#define SGTL5000_PLL_FRAC_DIV_MASK 0x0700
+#define SGTL5000_PLL_FRAC_DIV_SHIFT 0
+#define SGTL5000_PLL_FRAC_DIV_WIDTH 11
+
+/*
+ * SGTL5000_CHIP_CLK_TOP_CTRL
+ */
+#define SGTL5000_INT_OSC_EN 0x0800
+#define SGTL5000_INPUT_FREQ_DIV2 0x0008
+
+/*
+ * SGTL5000_CHIP_ANA_STATUS
+ */
+#define SGTL5000_HP_LRSHORT 0x0200
+#define SGTL5000_CAPLESS_SHORT 0x0100
+#define SGTL5000_PLL_LOCKED 0x0010
+
+/*
+ * SGTL5000_CHIP_SHORT_CTRL
+ */
+#define SGTL5000_LVLADJR_MASK 0x7000
+#define SGTL5000_LVLADJR_SHIFT 12
+#define SGTL5000_LVLADJR_WIDTH 3
+#define SGTL5000_LVLADJL_MASK 0x0700
+#define SGTL5000_LVLADJL_SHIFT 8
+#define SGTL5000_LVLADJL_WIDTH 3
+#define SGTL5000_LVLADJC_MASK 0x0070
+#define SGTL5000_LVLADJC_SHIFT 4
+#define SGTL5000_LVLADJC_WIDTH 3
+#define SGTL5000_LR_SHORT_MOD_MASK 0x000c
+#define SGTL5000_LR_SHORT_MOD_SHIFT 2
+#define SGTL5000_LR_SHORT_MOD_WIDTH 2
+#define SGTL5000_CM_SHORT_MOD_MASK 0x0003
+#define SGTL5000_CM_SHORT_MOD_SHIFT 0
+#define SGTL5000_CM_SHORT_MOD_WIDTH 2
+
+/*
+ *SGTL5000_CHIP_ANA_TEST2
+ */
+#define SGTL5000_MONO_DAC 0x1000
+
+/*
+ * SGTL5000_DAP_CTRL
+ */
+#define SGTL5000_DAP_MIX_EN 0x0010
+#define SGTL5000_DAP_EN 0x0001
+
+#define SGTL5000_SYSCLK 0x00
+#define SGTL5000_LRCLK 0x01
+
+#endif
diff --git a/sound/soc/codecs/si4763.c b/sound/soc/codecs/si4763.c
new file mode 100644
index 00000000..b2c5dc99
--- /dev/null
+++ b/sound/soc/codecs/si4763.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2008-2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#define SI4763_RATES SNDRV_PCM_RATE_48000
+
+#define SI4763_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+static struct snd_soc_dai_driver si4763_codec_dai = {
+ .name = "si4763",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SI4763_RATES,
+ .formats = SI4763_FORMATS,
+ },
+};
+
+static int mxc_si4763_codec_soc_probe(struct snd_soc_codec *codec)
+{
+ return 0;
+}
+static struct snd_soc_codec_driver soc_codec_dev_si4763 = {
+ .probe = mxc_si4763_codec_soc_probe,
+};
+
+static int si4763_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ dev_info(&pdev->dev, "SI4763 Audio codec\n");
+
+ ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_si4763,
+ &si4763_codec_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register codec\n");
+ return ret;
+ }
+
+ return 0;
+
+}
+
+/* power down chip */
+static int si4763_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static int si4763_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+static int si4763_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+struct platform_driver si4763_driver = {
+ .driver = {
+ .name = "si4763",
+ .owner = THIS_MODULE,
+ },
+ .probe = si4763_probe,
+ .remove = si4763_remove,
+ .suspend = si4763_suspend,
+ .resume = si4763_resume,
+};
+
+static int __init si4763_codec_init(void)
+{
+ return platform_driver_register(&si4763_driver);
+}
+
+static void __exit si4763_codec_exit(void)
+{
+ return platform_driver_unregister(&si4763_driver);
+}
+
+module_init(si4763_codec_init);
+module_exit(si4763_codec_exit);
+
+MODULE_DESCRIPTION("ASoC si4763 codec driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/sn95031.c b/sound/soc/codecs/sn95031.c
new file mode 100644
index 00000000..84ffdebb
--- /dev/null
+++ b/sound/soc/codecs/sn95031.c
@@ -0,0 +1,944 @@
+/*
+ * sn95031.c - TI sn95031 Codec driver
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@intel.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; version 2 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, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <asm/intel_scu_ipc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/jack.h>
+#include "sn95031.h"
+
+#define SN95031_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100)
+#define SN95031_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
+
+/* adc helper functions */
+
+/* enables mic bias voltage */
+static void sn95031_enable_mic_bias(struct snd_soc_codec *codec)
+{
+ snd_soc_write(codec, SN95031_VAUD, BIT(2)|BIT(1)|BIT(0));
+ snd_soc_update_bits(codec, SN95031_MICBIAS, BIT(2), BIT(2));
+}
+
+/* Enable/Disable the ADC depending on the argument */
+static void configure_adc(struct snd_soc_codec *sn95031_codec, int val)
+{
+ int value = snd_soc_read(sn95031_codec, SN95031_ADC1CNTL1);
+
+ if (val) {
+ /* Enable and start the ADC */
+ value |= (SN95031_ADC_ENBL | SN95031_ADC_START);
+ value &= (~SN95031_ADC_NO_LOOP);
+ } else {
+ /* Just stop the ADC */
+ value &= (~SN95031_ADC_START);
+ }
+ snd_soc_write(sn95031_codec, SN95031_ADC1CNTL1, value);
+}
+
+/*
+ * finds an empty channel for conversion
+ * If the ADC is not enabled then start using 0th channel
+ * itself. Otherwise find an empty channel by looking for a
+ * channel in which the stopbit is set to 1. returns the index
+ * of the first free channel if succeeds or an error code.
+ *
+ * Context: can sleep
+ *
+ */
+static int find_free_channel(struct snd_soc_codec *sn95031_codec)
+{
+ int ret = 0, i, value;
+
+ /* check whether ADC is enabled */
+ value = snd_soc_read(sn95031_codec, SN95031_ADC1CNTL1);
+
+ if ((value & SN95031_ADC_ENBL) == 0)
+ return 0;
+
+ /* ADC is already enabled; Looking for an empty channel */
+ for (i = 0; i < SN95031_ADC_CHANLS_MAX; i++) {
+ value = snd_soc_read(sn95031_codec,
+ SN95031_ADC_CHNL_START_ADDR + i);
+ if (value & SN95031_STOPBIT_MASK) {
+ ret = i;
+ break;
+ }
+ }
+ return (ret > SN95031_ADC_LOOP_MAX) ? (-EINVAL) : ret;
+}
+
+/* Initialize the ADC for reading micbias values. Can sleep. */
+static int sn95031_initialize_adc(struct snd_soc_codec *sn95031_codec)
+{
+ int base_addr, chnl_addr;
+ int value;
+ static int channel_index;
+
+ /* Index of the first channel in which the stop bit is set */
+ channel_index = find_free_channel(sn95031_codec);
+ if (channel_index < 0) {
+ pr_err("No free ADC channels");
+ return channel_index;
+ }
+
+ base_addr = SN95031_ADC_CHNL_START_ADDR + channel_index;
+
+ if (!(channel_index == 0 || channel_index == SN95031_ADC_LOOP_MAX)) {
+ /* Reset stop bit for channels other than 0 and 12 */
+ value = snd_soc_read(sn95031_codec, base_addr);
+ /* Set the stop bit to zero */
+ snd_soc_write(sn95031_codec, base_addr, value & 0xEF);
+ /* Index of the first free channel */
+ base_addr++;
+ channel_index++;
+ }
+
+ /* Since this is the last channel, set the stop bit
+ to 1 by ORing the DIE_SENSOR_CODE with 0x10 */
+ snd_soc_write(sn95031_codec, base_addr,
+ SN95031_AUDIO_DETECT_CODE | 0x10);
+
+ chnl_addr = SN95031_ADC_DATA_START_ADDR + 2 * channel_index;
+ pr_debug("mid_initialize : %x", chnl_addr);
+ configure_adc(sn95031_codec, 1);
+ return chnl_addr;
+}
+
+
+/* reads the ADC registers and gets the mic bias value in mV. */
+static unsigned int sn95031_get_mic_bias(struct snd_soc_codec *codec)
+{
+ u16 adc_adr = sn95031_initialize_adc(codec);
+ u16 adc_val1, adc_val2;
+ unsigned int mic_bias;
+
+ sn95031_enable_mic_bias(codec);
+
+ /* Enable the sound card for conversion before reading */
+ snd_soc_write(codec, SN95031_ADC1CNTL3, 0x05);
+ /* Re-toggle the RRDATARD bit */
+ snd_soc_write(codec, SN95031_ADC1CNTL3, 0x04);
+
+ /* Read the higher bits of data */
+ msleep(1000);
+ adc_val1 = snd_soc_read(codec, adc_adr);
+ adc_adr++;
+ adc_val2 = snd_soc_read(codec, adc_adr);
+
+ /* Adding lower two bits to the higher bits */
+ mic_bias = (adc_val1 << 2) + (adc_val2 & 3);
+ mic_bias = (mic_bias * SN95031_ADC_ONE_LSB_MULTIPLIER) / 1000;
+ pr_debug("mic bias = %dmV\n", mic_bias);
+ return mic_bias;
+}
+EXPORT_SYMBOL_GPL(sn95031_get_mic_bias);
+/*end - adc helper functions */
+
+static inline unsigned int sn95031_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 value = 0;
+ int ret;
+
+ ret = intel_scu_ipc_ioread8(reg, &value);
+ if (ret)
+ pr_err("read of %x failed, err %d\n", reg, ret);
+ return value;
+
+}
+
+static inline int sn95031_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ int ret;
+
+ ret = intel_scu_ipc_iowrite8(reg, value);
+ if (ret)
+ pr_err("write of %x failed, err %d\n", reg, ret);
+ return ret;
+}
+
+static int sn95031_set_vaud_bias(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) {
+ pr_debug("vaud_bias powering up pll\n");
+ /* power up the pll */
+ snd_soc_write(codec, SN95031_AUDPLLCTRL, BIT(5));
+ /* enable pcm 2 */
+ snd_soc_update_bits(codec, SN95031_PCM2C2,
+ BIT(0), BIT(0));
+ }
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ pr_debug("vaud_bias power up rail\n");
+ /* power up the rail */
+ snd_soc_write(codec, SN95031_VAUD,
+ BIT(2)|BIT(1)|BIT(0));
+ msleep(1);
+ } else if (codec->dapm.bias_level == SND_SOC_BIAS_PREPARE) {
+ /* turn off pcm */
+ pr_debug("vaud_bias power dn pcm\n");
+ snd_soc_update_bits(codec, SN95031_PCM2C2, BIT(0), 0);
+ snd_soc_write(codec, SN95031_AUDPLLCTRL, 0);
+ }
+ break;
+
+
+ case SND_SOC_BIAS_OFF:
+ pr_debug("vaud_bias _OFF doing rail shutdown\n");
+ snd_soc_write(codec, SN95031_VAUD, BIT(3));
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int sn95031_vhs_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ pr_debug("VHS SND_SOC_DAPM_EVENT_ON doing rail startup now\n");
+ /* power up the rail */
+ snd_soc_write(w->codec, SN95031_VHSP, 0x3D);
+ snd_soc_write(w->codec, SN95031_VHSN, 0x3F);
+ msleep(1);
+ } else if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ pr_debug("VHS SND_SOC_DAPM_EVENT_OFF doing rail shutdown\n");
+ snd_soc_write(w->codec, SN95031_VHSP, 0xC4);
+ snd_soc_write(w->codec, SN95031_VHSN, 0x04);
+ }
+ return 0;
+}
+
+static int sn95031_vihf_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ pr_debug("VIHF SND_SOC_DAPM_EVENT_ON doing rail startup now\n");
+ /* power up the rail */
+ snd_soc_write(w->codec, SN95031_VIHF, 0x27);
+ msleep(1);
+ } else if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ pr_debug("VIHF SND_SOC_DAPM_EVENT_OFF doing rail shutdown\n");
+ snd_soc_write(w->codec, SN95031_VIHF, 0x24);
+ }
+ return 0;
+}
+
+static int sn95031_dmic12_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ unsigned int ldo = 0, clk_dir = 0, data_dir = 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ldo = BIT(5)|BIT(4);
+ clk_dir = BIT(0);
+ data_dir = BIT(7);
+ }
+ /* program DMIC LDO, clock and set clock */
+ snd_soc_update_bits(w->codec, SN95031_MICBIAS, BIT(5)|BIT(4), ldo);
+ snd_soc_update_bits(w->codec, SN95031_DMICBUF0123, BIT(0), clk_dir);
+ snd_soc_update_bits(w->codec, SN95031_DMICBUF0123, BIT(7), data_dir);
+ return 0;
+}
+
+static int sn95031_dmic34_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ unsigned int ldo = 0, clk_dir = 0, data_dir = 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ldo = BIT(5)|BIT(4);
+ clk_dir = BIT(2);
+ data_dir = BIT(1);
+ }
+ /* program DMIC LDO, clock and set clock */
+ snd_soc_update_bits(w->codec, SN95031_MICBIAS, BIT(5)|BIT(4), ldo);
+ snd_soc_update_bits(w->codec, SN95031_DMICBUF0123, BIT(2), clk_dir);
+ snd_soc_update_bits(w->codec, SN95031_DMICBUF45, BIT(1), data_dir);
+ return 0;
+}
+
+static int sn95031_dmic56_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ unsigned int ldo = 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ ldo = BIT(7)|BIT(6);
+
+ /* program DMIC LDO */
+ snd_soc_update_bits(w->codec, SN95031_MICBIAS, BIT(7)|BIT(6), ldo);
+ return 0;
+}
+
+/* mux controls */
+static const char *sn95031_mic_texts[] = { "AMIC", "LineIn" };
+
+static const struct soc_enum sn95031_micl_enum =
+ SOC_ENUM_SINGLE(SN95031_ADCCONFIG, 1, 2, sn95031_mic_texts);
+
+static const struct snd_kcontrol_new sn95031_micl_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_micl_enum);
+
+static const struct soc_enum sn95031_micr_enum =
+ SOC_ENUM_SINGLE(SN95031_ADCCONFIG, 3, 2, sn95031_mic_texts);
+
+static const struct snd_kcontrol_new sn95031_micr_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_micr_enum);
+
+static const char *sn95031_input_texts[] = { "DMIC1", "DMIC2", "DMIC3",
+ "DMIC4", "DMIC5", "DMIC6",
+ "ADC Left", "ADC Right" };
+
+static const struct soc_enum sn95031_input1_enum =
+ SOC_ENUM_SINGLE(SN95031_AUDIOMUX12, 0, 8, sn95031_input_texts);
+
+static const struct snd_kcontrol_new sn95031_input1_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_input1_enum);
+
+static const struct soc_enum sn95031_input2_enum =
+ SOC_ENUM_SINGLE(SN95031_AUDIOMUX12, 4, 8, sn95031_input_texts);
+
+static const struct snd_kcontrol_new sn95031_input2_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_input2_enum);
+
+static const struct soc_enum sn95031_input3_enum =
+ SOC_ENUM_SINGLE(SN95031_AUDIOMUX34, 0, 8, sn95031_input_texts);
+
+static const struct snd_kcontrol_new sn95031_input3_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_input3_enum);
+
+static const struct soc_enum sn95031_input4_enum =
+ SOC_ENUM_SINGLE(SN95031_AUDIOMUX34, 4, 8, sn95031_input_texts);
+
+static const struct snd_kcontrol_new sn95031_input4_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_input4_enum);
+
+/* capture path controls */
+
+static const char *sn95031_micmode_text[] = {"Single Ended", "Differential"};
+
+/* 0dB to 30dB in 10dB steps */
+static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 10, 0);
+
+static const struct soc_enum sn95031_micmode1_enum =
+ SOC_ENUM_SINGLE(SN95031_MICAMP1, 1, 2, sn95031_micmode_text);
+static const struct soc_enum sn95031_micmode2_enum =
+ SOC_ENUM_SINGLE(SN95031_MICAMP2, 1, 2, sn95031_micmode_text);
+
+static const char *sn95031_dmic_cfg_text[] = {"GPO", "DMIC"};
+
+static const struct soc_enum sn95031_dmic12_cfg_enum =
+ SOC_ENUM_SINGLE(SN95031_DMICMUX, 0, 2, sn95031_dmic_cfg_text);
+static const struct soc_enum sn95031_dmic34_cfg_enum =
+ SOC_ENUM_SINGLE(SN95031_DMICMUX, 1, 2, sn95031_dmic_cfg_text);
+static const struct soc_enum sn95031_dmic56_cfg_enum =
+ SOC_ENUM_SINGLE(SN95031_DMICMUX, 2, 2, sn95031_dmic_cfg_text);
+
+static const struct snd_kcontrol_new sn95031_snd_controls[] = {
+ SOC_ENUM("Mic1Mode Capture Route", sn95031_micmode1_enum),
+ SOC_ENUM("Mic2Mode Capture Route", sn95031_micmode2_enum),
+ SOC_ENUM("DMIC12 Capture Route", sn95031_dmic12_cfg_enum),
+ SOC_ENUM("DMIC34 Capture Route", sn95031_dmic34_cfg_enum),
+ SOC_ENUM("DMIC56 Capture Route", sn95031_dmic56_cfg_enum),
+ SOC_SINGLE_TLV("Mic1 Capture Volume", SN95031_MICAMP1,
+ 2, 4, 0, mic_tlv),
+ SOC_SINGLE_TLV("Mic2 Capture Volume", SN95031_MICAMP2,
+ 2, 4, 0, mic_tlv),
+};
+
+/* DAPM widgets */
+static const struct snd_soc_dapm_widget sn95031_dapm_widgets[] = {
+
+ /* all end points mic, hs etc */
+ SND_SOC_DAPM_OUTPUT("HPOUTL"),
+ SND_SOC_DAPM_OUTPUT("HPOUTR"),
+ SND_SOC_DAPM_OUTPUT("EPOUT"),
+ SND_SOC_DAPM_OUTPUT("IHFOUTL"),
+ SND_SOC_DAPM_OUTPUT("IHFOUTR"),
+ SND_SOC_DAPM_OUTPUT("LINEOUTL"),
+ SND_SOC_DAPM_OUTPUT("LINEOUTR"),
+ SND_SOC_DAPM_OUTPUT("VIB1OUT"),
+ SND_SOC_DAPM_OUTPUT("VIB2OUT"),
+
+ SND_SOC_DAPM_INPUT("AMIC1"), /* headset mic */
+ SND_SOC_DAPM_INPUT("AMIC2"),
+ SND_SOC_DAPM_INPUT("DMIC1"),
+ SND_SOC_DAPM_INPUT("DMIC2"),
+ SND_SOC_DAPM_INPUT("DMIC3"),
+ SND_SOC_DAPM_INPUT("DMIC4"),
+ SND_SOC_DAPM_INPUT("DMIC5"),
+ SND_SOC_DAPM_INPUT("DMIC6"),
+ SND_SOC_DAPM_INPUT("LINEINL"),
+ SND_SOC_DAPM_INPUT("LINEINR"),
+
+ SND_SOC_DAPM_MICBIAS("AMIC1Bias", SN95031_MICBIAS, 2, 0),
+ SND_SOC_DAPM_MICBIAS("AMIC2Bias", SN95031_MICBIAS, 3, 0),
+ SND_SOC_DAPM_MICBIAS("DMIC12Bias", SN95031_DMICMUX, 3, 0),
+ SND_SOC_DAPM_MICBIAS("DMIC34Bias", SN95031_DMICMUX, 4, 0),
+ SND_SOC_DAPM_MICBIAS("DMIC56Bias", SN95031_DMICMUX, 5, 0),
+
+ SND_SOC_DAPM_SUPPLY("DMIC12supply", SN95031_DMICLK, 0, 0,
+ sn95031_dmic12_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("DMIC34supply", SN95031_DMICLK, 1, 0,
+ sn95031_dmic34_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("DMIC56supply", SN95031_DMICLK, 2, 0,
+ sn95031_dmic56_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_AIF_OUT("PCM_Out", "Capture", 0,
+ SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_SUPPLY("Headset Rail", SND_SOC_NOPM, 0, 0,
+ sn95031_vhs_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("Speaker Rail", SND_SOC_NOPM, 0, 0,
+ sn95031_vihf_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* playback path driver enables */
+ SND_SOC_DAPM_PGA("Headset Left Playback",
+ SN95031_DRIVEREN, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Headset Right Playback",
+ SN95031_DRIVEREN, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Speaker Left Playback",
+ SN95031_DRIVEREN, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Speaker Right Playback",
+ SN95031_DRIVEREN, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Vibra1 Playback",
+ SN95031_DRIVEREN, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Vibra2 Playback",
+ SN95031_DRIVEREN, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Earpiece Playback",
+ SN95031_DRIVEREN, 6, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Lineout Left Playback",
+ SN95031_LOCTL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Lineout Right Playback",
+ SN95031_LOCTL, 4, 0, NULL, 0),
+
+ /* playback path filter enable */
+ SND_SOC_DAPM_PGA("Headset Left Filter",
+ SN95031_HSEPRXCTRL, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Headset Right Filter",
+ SN95031_HSEPRXCTRL, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Speaker Left Filter",
+ SN95031_IHFRXCTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Speaker Right Filter",
+ SN95031_IHFRXCTRL, 1, 0, NULL, 0),
+
+ /* DACs */
+ SND_SOC_DAPM_DAC("HSDAC Left", "Headset",
+ SN95031_DACCONFIG, 0, 0),
+ SND_SOC_DAPM_DAC("HSDAC Right", "Headset",
+ SN95031_DACCONFIG, 1, 0),
+ SND_SOC_DAPM_DAC("IHFDAC Left", "Speaker",
+ SN95031_DACCONFIG, 2, 0),
+ SND_SOC_DAPM_DAC("IHFDAC Right", "Speaker",
+ SN95031_DACCONFIG, 3, 0),
+ SND_SOC_DAPM_DAC("Vibra1 DAC", "Vibra1",
+ SN95031_VIB1C5, 1, 0),
+ SND_SOC_DAPM_DAC("Vibra2 DAC", "Vibra2",
+ SN95031_VIB2C5, 1, 0),
+
+ /* capture widgets */
+ SND_SOC_DAPM_PGA("LineIn Enable Left", SN95031_MICAMP1,
+ 7, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("LineIn Enable Right", SN95031_MICAMP2,
+ 7, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("MIC1 Enable", SN95031_MICAMP1, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("MIC2 Enable", SN95031_MICAMP2, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("TX1 Enable", SN95031_AUDIOTXEN, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("TX2 Enable", SN95031_AUDIOTXEN, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("TX3 Enable", SN95031_AUDIOTXEN, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("TX4 Enable", SN95031_AUDIOTXEN, 5, 0, NULL, 0),
+
+ /* ADC have null stream as they will be turned ON by TX path */
+ SND_SOC_DAPM_ADC("ADC Left", NULL,
+ SN95031_ADCCONFIG, 0, 0),
+ SND_SOC_DAPM_ADC("ADC Right", NULL,
+ SN95031_ADCCONFIG, 2, 0),
+
+ SND_SOC_DAPM_MUX("Mic_InputL Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_micl_mux_control),
+ SND_SOC_DAPM_MUX("Mic_InputR Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_micr_mux_control),
+
+ SND_SOC_DAPM_MUX("Txpath1 Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_input1_mux_control),
+ SND_SOC_DAPM_MUX("Txpath2 Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_input2_mux_control),
+ SND_SOC_DAPM_MUX("Txpath3 Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_input3_mux_control),
+ SND_SOC_DAPM_MUX("Txpath4 Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_input4_mux_control),
+
+};
+
+static const struct snd_soc_dapm_route sn95031_audio_map[] = {
+ /* headset and earpiece map */
+ { "HPOUTL", NULL, "Headset Rail"},
+ { "HPOUTR", NULL, "Headset Rail"},
+ { "HPOUTL", NULL, "Headset Left Playback" },
+ { "HPOUTR", NULL, "Headset Right Playback" },
+ { "EPOUT", NULL, "Earpiece Playback" },
+ { "Headset Left Playback", NULL, "Headset Left Filter"},
+ { "Headset Right Playback", NULL, "Headset Right Filter"},
+ { "Earpiece Playback", NULL, "Headset Left Filter"},
+ { "Headset Left Filter", NULL, "HSDAC Left"},
+ { "Headset Right Filter", NULL, "HSDAC Right"},
+
+ /* speaker map */
+ { "IHFOUTL", NULL, "Speaker Rail"},
+ { "IHFOUTR", NULL, "Speaker Rail"},
+ { "IHFOUTL", "NULL", "Speaker Left Playback"},
+ { "IHFOUTR", "NULL", "Speaker Right Playback"},
+ { "Speaker Left Playback", NULL, "Speaker Left Filter"},
+ { "Speaker Right Playback", NULL, "Speaker Right Filter"},
+ { "Speaker Left Filter", NULL, "IHFDAC Left"},
+ { "Speaker Right Filter", NULL, "IHFDAC Right"},
+
+ /* vibra map */
+ { "VIB1OUT", NULL, "Vibra1 Playback"},
+ { "Vibra1 Playback", NULL, "Vibra1 DAC"},
+
+ { "VIB2OUT", NULL, "Vibra2 Playback"},
+ { "Vibra2 Playback", NULL, "Vibra2 DAC"},
+
+ /* lineout */
+ { "LINEOUTL", NULL, "Lineout Left Playback"},
+ { "LINEOUTR", NULL, "Lineout Right Playback"},
+ { "Lineout Left Playback", NULL, "Headset Left Filter"},
+ { "Lineout Left Playback", NULL, "Speaker Left Filter"},
+ { "Lineout Left Playback", NULL, "Vibra1 DAC"},
+ { "Lineout Right Playback", NULL, "Headset Right Filter"},
+ { "Lineout Right Playback", NULL, "Speaker Right Filter"},
+ { "Lineout Right Playback", NULL, "Vibra2 DAC"},
+
+ /* Headset (AMIC1) mic */
+ { "AMIC1Bias", NULL, "AMIC1"},
+ { "MIC1 Enable", NULL, "AMIC1Bias"},
+ { "Mic_InputL Capture Route", "AMIC", "MIC1 Enable"},
+
+ /* AMIC2 */
+ { "AMIC2Bias", NULL, "AMIC2"},
+ { "MIC2 Enable", NULL, "AMIC2Bias"},
+ { "Mic_InputR Capture Route", "AMIC", "MIC2 Enable"},
+
+
+ /* Linein */
+ { "LineIn Enable Left", NULL, "LINEINL"},
+ { "LineIn Enable Right", NULL, "LINEINR"},
+ { "Mic_InputL Capture Route", "LineIn", "LineIn Enable Left"},
+ { "Mic_InputR Capture Route", "LineIn", "LineIn Enable Right"},
+
+ /* ADC connection */
+ { "ADC Left", NULL, "Mic_InputL Capture Route"},
+ { "ADC Right", NULL, "Mic_InputR Capture Route"},
+
+ /*DMIC connections */
+ { "DMIC1", NULL, "DMIC12supply"},
+ { "DMIC2", NULL, "DMIC12supply"},
+ { "DMIC3", NULL, "DMIC34supply"},
+ { "DMIC4", NULL, "DMIC34supply"},
+ { "DMIC5", NULL, "DMIC56supply"},
+ { "DMIC6", NULL, "DMIC56supply"},
+
+ { "DMIC12Bias", NULL, "DMIC1"},
+ { "DMIC12Bias", NULL, "DMIC2"},
+ { "DMIC34Bias", NULL, "DMIC3"},
+ { "DMIC34Bias", NULL, "DMIC4"},
+ { "DMIC56Bias", NULL, "DMIC5"},
+ { "DMIC56Bias", NULL, "DMIC6"},
+
+ /*TX path inputs*/
+ { "Txpath1 Capture Route", "ADC Left", "ADC Left"},
+ { "Txpath2 Capture Route", "ADC Left", "ADC Left"},
+ { "Txpath3 Capture Route", "ADC Left", "ADC Left"},
+ { "Txpath4 Capture Route", "ADC Left", "ADC Left"},
+ { "Txpath1 Capture Route", "ADC Right", "ADC Right"},
+ { "Txpath2 Capture Route", "ADC Right", "ADC Right"},
+ { "Txpath3 Capture Route", "ADC Right", "ADC Right"},
+ { "Txpath4 Capture Route", "ADC Right", "ADC Right"},
+ { "Txpath1 Capture Route", "DMIC1", "DMIC1"},
+ { "Txpath2 Capture Route", "DMIC1", "DMIC1"},
+ { "Txpath3 Capture Route", "DMIC1", "DMIC1"},
+ { "Txpath4 Capture Route", "DMIC1", "DMIC1"},
+ { "Txpath1 Capture Route", "DMIC2", "DMIC2"},
+ { "Txpath2 Capture Route", "DMIC2", "DMIC2"},
+ { "Txpath3 Capture Route", "DMIC2", "DMIC2"},
+ { "Txpath4 Capture Route", "DMIC2", "DMIC2"},
+ { "Txpath1 Capture Route", "DMIC3", "DMIC3"},
+ { "Txpath2 Capture Route", "DMIC3", "DMIC3"},
+ { "Txpath3 Capture Route", "DMIC3", "DMIC3"},
+ { "Txpath4 Capture Route", "DMIC3", "DMIC3"},
+ { "Txpath1 Capture Route", "DMIC4", "DMIC4"},
+ { "Txpath2 Capture Route", "DMIC4", "DMIC4"},
+ { "Txpath3 Capture Route", "DMIC4", "DMIC4"},
+ { "Txpath4 Capture Route", "DMIC4", "DMIC4"},
+ { "Txpath1 Capture Route", "DMIC5", "DMIC5"},
+ { "Txpath2 Capture Route", "DMIC5", "DMIC5"},
+ { "Txpath3 Capture Route", "DMIC5", "DMIC5"},
+ { "Txpath4 Capture Route", "DMIC5", "DMIC5"},
+ { "Txpath1 Capture Route", "DMIC6", "DMIC6"},
+ { "Txpath2 Capture Route", "DMIC6", "DMIC6"},
+ { "Txpath3 Capture Route", "DMIC6", "DMIC6"},
+ { "Txpath4 Capture Route", "DMIC6", "DMIC6"},
+
+ /* tx path */
+ { "TX1 Enable", NULL, "Txpath1 Capture Route"},
+ { "TX2 Enable", NULL, "Txpath2 Capture Route"},
+ { "TX3 Enable", NULL, "Txpath3 Capture Route"},
+ { "TX4 Enable", NULL, "Txpath4 Capture Route"},
+ { "PCM_Out", NULL, "TX1 Enable"},
+ { "PCM_Out", NULL, "TX2 Enable"},
+ { "PCM_Out", NULL, "TX3 Enable"},
+ { "PCM_Out", NULL, "TX4 Enable"},
+
+};
+
+/* speaker and headset mutes, for audio pops and clicks */
+static int sn95031_pcm_hs_mute(struct snd_soc_dai *dai, int mute)
+{
+ snd_soc_update_bits(dai->codec,
+ SN95031_HSLVOLCTRL, BIT(7), (!mute << 7));
+ snd_soc_update_bits(dai->codec,
+ SN95031_HSRVOLCTRL, BIT(7), (!mute << 7));
+ return 0;
+}
+
+static int sn95031_pcm_spkr_mute(struct snd_soc_dai *dai, int mute)
+{
+ snd_soc_update_bits(dai->codec,
+ SN95031_IHFLVOLCTRL, BIT(7), (!mute << 7));
+ snd_soc_update_bits(dai->codec,
+ SN95031_IHFRVOLCTRL, BIT(7), (!mute << 7));
+ return 0;
+}
+
+int sn95031_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ unsigned int format, rate;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ format = BIT(4)|BIT(5);
+ break;
+
+ case SNDRV_PCM_FORMAT_S24_LE:
+ format = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(dai->codec, SN95031_PCM2C2,
+ BIT(4)|BIT(5), format);
+
+ switch (params_rate(params)) {
+ case 48000:
+ pr_debug("RATE_48000\n");
+ rate = 0;
+ break;
+
+ case 44100:
+ pr_debug("RATE_44100\n");
+ rate = BIT(7);
+ break;
+
+ default:
+ pr_err("ERR rate %d\n", params_rate(params));
+ return -EINVAL;
+ }
+ snd_soc_update_bits(dai->codec, SN95031_PCM1C1, BIT(7), rate);
+
+ return 0;
+}
+
+/* Codec DAI section */
+static struct snd_soc_dai_ops sn95031_headset_dai_ops = {
+ .digital_mute = sn95031_pcm_hs_mute,
+ .hw_params = sn95031_pcm_hw_params,
+};
+
+static struct snd_soc_dai_ops sn95031_speaker_dai_ops = {
+ .digital_mute = sn95031_pcm_spkr_mute,
+ .hw_params = sn95031_pcm_hw_params,
+};
+
+static struct snd_soc_dai_ops sn95031_vib1_dai_ops = {
+ .hw_params = sn95031_pcm_hw_params,
+};
+
+static struct snd_soc_dai_ops sn95031_vib2_dai_ops = {
+ .hw_params = sn95031_pcm_hw_params,
+};
+
+struct snd_soc_dai_driver sn95031_dais[] = {
+{
+ .name = "SN95031 Headset",
+ .playback = {
+ .stream_name = "Headset",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SN95031_RATES,
+ .formats = SN95031_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 5,
+ .rates = SN95031_RATES,
+ .formats = SN95031_FORMATS,
+ },
+ .ops = &sn95031_headset_dai_ops,
+},
+{ .name = "SN95031 Speaker",
+ .playback = {
+ .stream_name = "Speaker",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SN95031_RATES,
+ .formats = SN95031_FORMATS,
+ },
+ .ops = &sn95031_speaker_dai_ops,
+},
+{ .name = "SN95031 Vibra1",
+ .playback = {
+ .stream_name = "Vibra1",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SN95031_RATES,
+ .formats = SN95031_FORMATS,
+ },
+ .ops = &sn95031_vib1_dai_ops,
+},
+{ .name = "SN95031 Vibra2",
+ .playback = {
+ .stream_name = "Vibra2",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SN95031_RATES,
+ .formats = SN95031_FORMATS,
+ },
+ .ops = &sn95031_vib2_dai_ops,
+},
+};
+
+static inline void sn95031_disable_jack_btn(struct snd_soc_codec *codec)
+{
+ snd_soc_write(codec, SN95031_BTNCTRL2, 0x00);
+}
+
+static inline void sn95031_enable_jack_btn(struct snd_soc_codec *codec)
+{
+ snd_soc_write(codec, SN95031_BTNCTRL1, 0x77);
+ snd_soc_write(codec, SN95031_BTNCTRL2, 0x01);
+}
+
+static int sn95031_get_headset_state(struct snd_soc_jack *mfld_jack)
+{
+ int micbias = sn95031_get_mic_bias(mfld_jack->codec);
+
+ int jack_type = snd_soc_jack_get_type(mfld_jack, micbias);
+
+ pr_debug("jack type detected = %d\n", jack_type);
+ if (jack_type == SND_JACK_HEADSET)
+ sn95031_enable_jack_btn(mfld_jack->codec);
+ return jack_type;
+}
+
+void sn95031_jack_detection(struct mfld_jack_data *jack_data)
+{
+ unsigned int status;
+ unsigned int mask = SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_HEADSET;
+
+ pr_debug("interrupt id read in sram = 0x%x\n", jack_data->intr_id);
+ if (jack_data->intr_id & 0x1) {
+ pr_debug("short_push detected\n");
+ status = SND_JACK_HEADSET | SND_JACK_BTN_0;
+ } else if (jack_data->intr_id & 0x2) {
+ pr_debug("long_push detected\n");
+ status = SND_JACK_HEADSET | SND_JACK_BTN_1;
+ } else if (jack_data->intr_id & 0x4) {
+ pr_debug("headset or headphones inserted\n");
+ status = sn95031_get_headset_state(jack_data->mfld_jack);
+ } else if (jack_data->intr_id & 0x8) {
+ pr_debug("headset or headphones removed\n");
+ status = 0;
+ sn95031_disable_jack_btn(jack_data->mfld_jack->codec);
+ } else {
+ pr_err("unidentified interrupt\n");
+ return;
+ }
+
+ snd_soc_jack_report(jack_data->mfld_jack, status, mask);
+ /*button pressed and released so we send explicit button release */
+ if ((status & SND_JACK_BTN_0) | (status & SND_JACK_BTN_1))
+ snd_soc_jack_report(jack_data->mfld_jack,
+ SND_JACK_HEADSET, mask);
+}
+EXPORT_SYMBOL_GPL(sn95031_jack_detection);
+
+/* codec registration */
+static int sn95031_codec_probe(struct snd_soc_codec *codec)
+{
+ pr_debug("codec_probe called\n");
+
+ codec->dapm.bias_level = SND_SOC_BIAS_OFF;
+ codec->dapm.idle_bias_off = 1;
+
+ /* PCM interface config
+ * This sets the pcm rx slot conguration to max 6 slots
+ * for max 4 dais (2 stereo and 2 mono)
+ */
+ snd_soc_write(codec, SN95031_PCM2RXSLOT01, 0x10);
+ snd_soc_write(codec, SN95031_PCM2RXSLOT23, 0x32);
+ snd_soc_write(codec, SN95031_PCM2RXSLOT45, 0x54);
+ snd_soc_write(codec, SN95031_PCM2TXSLOT01, 0x10);
+ snd_soc_write(codec, SN95031_PCM2TXSLOT23, 0x32);
+ /* pcm port setting
+ * This sets the pcm port to slave and clock at 19.2Mhz which
+ * can support 6slots, sampling rate set per stream in hw-params
+ */
+ snd_soc_write(codec, SN95031_PCM1C1, 0x00);
+ snd_soc_write(codec, SN95031_PCM2C1, 0x01);
+ snd_soc_write(codec, SN95031_PCM2C2, 0x0A);
+ snd_soc_write(codec, SN95031_HSMIXER, BIT(0)|BIT(4));
+ /* vendor vibra workround, the vibras are muted by
+ * custom register so unmute them
+ */
+ snd_soc_write(codec, SN95031_SSR5, 0x80);
+ snd_soc_write(codec, SN95031_SSR6, 0x80);
+ snd_soc_write(codec, SN95031_VIB1C5, 0x00);
+ snd_soc_write(codec, SN95031_VIB2C5, 0x00);
+ /* configure vibras for pcm port */
+ snd_soc_write(codec, SN95031_VIB1C3, 0x00);
+ snd_soc_write(codec, SN95031_VIB2C3, 0x00);
+
+ /* soft mute ramp time */
+ snd_soc_write(codec, SN95031_SOFTMUTE, 0x3);
+ /* fix the initial volume at 1dB,
+ * default in +9dB,
+ * 1dB give optimal swing on DAC, amps
+ */
+ snd_soc_write(codec, SN95031_HSLVOLCTRL, 0x08);
+ snd_soc_write(codec, SN95031_HSRVOLCTRL, 0x08);
+ snd_soc_write(codec, SN95031_IHFLVOLCTRL, 0x08);
+ snd_soc_write(codec, SN95031_IHFRVOLCTRL, 0x08);
+ /* dac mode and lineout workaround */
+ snd_soc_write(codec, SN95031_SSR2, 0x10);
+ snd_soc_write(codec, SN95031_SSR3, 0x40);
+
+ snd_soc_add_controls(codec, sn95031_snd_controls,
+ ARRAY_SIZE(sn95031_snd_controls));
+
+ return 0;
+}
+
+static int sn95031_codec_remove(struct snd_soc_codec *codec)
+{
+ pr_debug("codec_remove called\n");
+ sn95031_set_vaud_bias(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+struct snd_soc_codec_driver sn95031_codec = {
+ .probe = sn95031_codec_probe,
+ .remove = sn95031_codec_remove,
+ .read = sn95031_read,
+ .write = sn95031_write,
+ .set_bias_level = sn95031_set_vaud_bias,
+ .dapm_widgets = sn95031_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sn95031_dapm_widgets),
+ .dapm_routes = sn95031_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(sn95031_audio_map),
+};
+
+static int __devinit sn95031_device_probe(struct platform_device *pdev)
+{
+ pr_debug("codec device probe called for %s\n", dev_name(&pdev->dev));
+ return snd_soc_register_codec(&pdev->dev, &sn95031_codec,
+ sn95031_dais, ARRAY_SIZE(sn95031_dais));
+}
+
+static int __devexit sn95031_device_remove(struct platform_device *pdev)
+{
+ pr_debug("codec device remove called\n");
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver sn95031_codec_driver = {
+ .driver = {
+ .name = "sn95031",
+ .owner = THIS_MODULE,
+ },
+ .probe = sn95031_device_probe,
+ .remove = __devexit_p(sn95031_device_remove),
+};
+
+static int __init sn95031_init(void)
+{
+ pr_debug("driver init called\n");
+ return platform_driver_register(&sn95031_codec_driver);
+}
+module_init(sn95031_init);
+
+static void __exit sn95031_exit(void)
+{
+ pr_debug("driver exit called\n");
+ platform_driver_unregister(&sn95031_codec_driver);
+}
+module_exit(sn95031_exit);
+
+MODULE_DESCRIPTION("ASoC TI SN95031 codec driver");
+MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sn95031");
diff --git a/sound/soc/codecs/sn95031.h b/sound/soc/codecs/sn95031.h
new file mode 100644
index 00000000..20376d23
--- /dev/null
+++ b/sound/soc/codecs/sn95031.h
@@ -0,0 +1,132 @@
+/*
+ * sn95031.h - TI sn95031 Codec driver
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@intel.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; version 2 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, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *
+ */
+#ifndef _SN95031_H
+#define _SN95031_H
+
+/*register map*/
+#define SN95031_VAUD 0xDB
+#define SN95031_VHSP 0xDC
+#define SN95031_VHSN 0xDD
+#define SN95031_VIHF 0xC9
+
+#define SN95031_AUDPLLCTRL 0x240
+#define SN95031_DMICBUF0123 0x241
+#define SN95031_DMICBUF45 0x242
+#define SN95031_DMICGPO 0x244
+#define SN95031_DMICMUX 0x245
+#define SN95031_DMICLK 0x246
+#define SN95031_MICBIAS 0x247
+#define SN95031_ADCCONFIG 0x248
+#define SN95031_MICAMP1 0x249
+#define SN95031_MICAMP2 0x24A
+#define SN95031_NOISEMUX 0x24B
+#define SN95031_AUDIOMUX12 0x24C
+#define SN95031_AUDIOMUX34 0x24D
+#define SN95031_AUDIOSINC 0x24E
+#define SN95031_AUDIOTXEN 0x24F
+#define SN95031_HSEPRXCTRL 0x250
+#define SN95031_IHFRXCTRL 0x251
+#define SN95031_HSMIXER 0x256
+#define SN95031_DACCONFIG 0x257
+#define SN95031_SOFTMUTE 0x258
+#define SN95031_HSLVOLCTRL 0x259
+#define SN95031_HSRVOLCTRL 0x25A
+#define SN95031_IHFLVOLCTRL 0x25B
+#define SN95031_IHFRVOLCTRL 0x25C
+#define SN95031_DRIVEREN 0x25D
+#define SN95031_LOCTL 0x25E
+#define SN95031_VIB1C1 0x25F
+#define SN95031_VIB1C2 0x260
+#define SN95031_VIB1C3 0x261
+#define SN95031_VIB1SPIPCM1 0x262
+#define SN95031_VIB1SPIPCM2 0x263
+#define SN95031_VIB1C5 0x264
+#define SN95031_VIB2C1 0x265
+#define SN95031_VIB2C2 0x266
+#define SN95031_VIB2C3 0x267
+#define SN95031_VIB2SPIPCM1 0x268
+#define SN95031_VIB2SPIPCM2 0x269
+#define SN95031_VIB2C5 0x26A
+#define SN95031_BTNCTRL1 0x26B
+#define SN95031_BTNCTRL2 0x26C
+#define SN95031_PCM1TXSLOT01 0x26D
+#define SN95031_PCM1TXSLOT23 0x26E
+#define SN95031_PCM1TXSLOT45 0x26F
+#define SN95031_PCM1RXSLOT0_3 0x270
+#define SN95031_PCM1RXSLOT45 0x271
+#define SN95031_PCM2TXSLOT01 0x272
+#define SN95031_PCM2TXSLOT23 0x273
+#define SN95031_PCM2TXSLOT45 0x274
+#define SN95031_PCM2RXSLOT01 0x275
+#define SN95031_PCM2RXSLOT23 0x276
+#define SN95031_PCM2RXSLOT45 0x277
+#define SN95031_PCM1C1 0x278
+#define SN95031_PCM1C2 0x279
+#define SN95031_PCM1C3 0x27A
+#define SN95031_PCM2C1 0x27B
+#define SN95031_PCM2C2 0x27C
+/*end codec register defn*/
+
+/*vendor defn these are not part of avp*/
+#define SN95031_SSR2 0x381
+#define SN95031_SSR3 0x382
+#define SN95031_SSR5 0x384
+#define SN95031_SSR6 0x385
+
+/* ADC registers */
+
+#define SN95031_ADC1CNTL1 0x1C0
+#define SN95031_ADC_ENBL 0x10
+#define SN95031_ADC_START 0x08
+#define SN95031_ADC1CNTL3 0x1C2
+#define SN95031_ADCTHERM_ENBL 0x04
+#define SN95031_ADCRRDATA_ENBL 0x05
+#define SN95031_STOPBIT_MASK 16
+#define SN95031_ADCTHERM_MASK 4
+#define SN95031_ADC_CHANLS_MAX 15 /* Number of ADC channels */
+#define SN95031_ADC_LOOP_MAX (SN95031_ADC_CHANLS_MAX - 1)
+#define SN95031_ADC_NO_LOOP 0x07
+#define SN95031_AUDIO_GPIO_CTRL 0x070
+
+/* ADC channel code values */
+#define SN95031_AUDIO_DETECT_CODE 0x06
+
+/* ADC base addresses */
+#define SN95031_ADC_CHNL_START_ADDR 0x1C5 /* increments by 1 */
+#define SN95031_ADC_DATA_START_ADDR 0x1D4 /* increments by 2 */
+/* multipier to convert to mV */
+#define SN95031_ADC_ONE_LSB_MULTIPLIER 2346
+
+
+struct mfld_jack_data {
+ int intr_id;
+ int micbias_vol;
+ struct snd_soc_jack *mfld_jack;
+};
+
+extern void sn95031_jack_detection(struct mfld_jack_data *jack_data);
+
+#endif
diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c
new file mode 100644
index 00000000..6a1a7e70
--- /dev/null
+++ b/sound/soc/codecs/spdif_transciever.c
@@ -0,0 +1,80 @@
+/*
+ * ALSA SoC SPDIF DIT driver
+ *
+ * This driver is used by controllers which can operate in DIT (SPDI/F) where
+ * no codec is needed. This file provides stub codec that can be used
+ * in these configurations. TI DaVinci Audio controller uses this driver.
+ *
+ * Author: Steve Chen, <schen@mvista.com>
+ * Copyright: (C) 2009 MontaVista Software, Inc., <source@mvista.com>
+ * Copyright: (C) 2009 Texas Instruments, India
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+
+#define DRV_NAME "spdif-dit"
+
+#define STUB_RATES SNDRV_PCM_RATE_8000_96000
+#define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+
+static struct snd_soc_codec_driver soc_codec_spdif_dit;
+
+static struct snd_soc_dai_driver dit_stub_dai = {
+ .name = "dit-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 384,
+ .rates = STUB_RATES,
+ .formats = STUB_FORMATS,
+ },
+};
+
+static int spdif_dit_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_spdif_dit,
+ &dit_stub_dai, 1);
+}
+
+static int spdif_dit_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver spdif_dit_driver = {
+ .probe = spdif_dit_probe,
+ .remove = spdif_dit_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init dit_modinit(void)
+{
+ return platform_driver_register(&spdif_dit_driver);
+}
+
+static void __exit dit_exit(void)
+{
+ platform_driver_unregister(&spdif_dit_driver);
+}
+
+module_init(dit_modinit);
+module_exit(dit_exit);
+
+MODULE_AUTHOR("Steve Chen <schen@mvista.com>");
+MODULE_DESCRIPTION("SPDIF dummy codec driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c
new file mode 100644
index 00000000..9801cd7c
--- /dev/null
+++ b/sound/soc/codecs/ssm2602.c
@@ -0,0 +1,737 @@
+/*
+ * File: sound/soc/codecs/ssm2602.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: Driver for ssm2602 sound chip
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "ssm2602.h"
+
+#define SSM2602_VERSION "0.1"
+
+enum ssm2602_type {
+ SSM2602,
+ SSM2604,
+};
+
+/* codec private data */
+struct ssm2602_priv {
+ unsigned int sysclk;
+ enum snd_soc_control_type control_type;
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+
+ enum ssm2602_type type;
+};
+
+/*
+ * ssm2602 register cache
+ * We can't read the ssm2602 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 ssm2602_reg[SSM2602_CACHEREGNUM] = {
+ 0x0097, 0x0097, 0x0079, 0x0079,
+ 0x000a, 0x0008, 0x009f, 0x000a,
+ 0x0000, 0x0000
+};
+
+#define ssm2602_reset(c) snd_soc_write(c, SSM2602_RESET, 0)
+
+/*Appending several "None"s just for OSS mixer use*/
+static const char *ssm2602_input_select[] = {
+ "Line", "Mic", "None", "None", "None",
+ "None", "None", "None",
+};
+
+static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum ssm2602_enum[] = {
+ SOC_ENUM_SINGLE(SSM2602_APANA, 2, 2, ssm2602_input_select),
+ SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, 4, ssm2602_deemph),
+};
+
+static const unsigned int ssm260x_outmix_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 47, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0),
+ 48, 127, TLV_DB_SCALE_ITEM(-7400, 100, 0),
+};
+
+static const DECLARE_TLV_DB_SCALE(ssm260x_inpga_tlv, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(ssm260x_sidetone_tlv, -1500, 300, 0);
+
+static const struct snd_kcontrol_new ssm260x_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 45, 0,
+ ssm260x_inpga_tlv),
+SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1),
+
+SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1),
+SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0),
+
+SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]),
+};
+
+static const struct snd_kcontrol_new ssm2602_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V,
+ 0, 127, 0, ssm260x_outmix_tlv),
+SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V,
+ 7, 1, 0),
+SOC_SINGLE_TLV("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1,
+ ssm260x_sidetone_tlv),
+
+SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0),
+SOC_SINGLE("Mic Boost2 (+20dB)", SSM2602_APANA, 8, 1, 0),
+SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1),
+};
+
+/* Output Mixer */
+static const struct snd_kcontrol_new ssm260x_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0),
+SOC_DAPM_SINGLE("HiFi Playback Switch", SSM2602_APANA, 4, 1, 0),
+SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new ssm2602_input_mux_controls =
+SOC_DAPM_ENUM("Input Select", ssm2602_enum[0]);
+
+static const struct snd_soc_dapm_widget ssm260x_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1),
+SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("Digital Core Power", SSM2602_ACTIVE, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_INPUT("RLINEIN"),
+SND_SOC_DAPM_INPUT("LLINEIN"),
+};
+
+static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1,
+ ssm260x_output_mixer_controls,
+ ARRAY_SIZE(ssm260x_output_mixer_controls)),
+
+SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls),
+SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1),
+
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+SND_SOC_DAPM_INPUT("MICIN"),
+};
+
+static const struct snd_soc_dapm_widget ssm2604_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0,
+ ssm260x_output_mixer_controls,
+ ARRAY_SIZE(ssm260x_output_mixer_controls) - 1), /* Last element is the mic */
+};
+
+static const struct snd_soc_dapm_route ssm260x_routes[] = {
+ {"DAC", NULL, "Digital Core Power"},
+ {"ADC", NULL, "Digital Core Power"},
+
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "HiFi Playback Switch", "DAC"},
+
+ {"ROUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+
+ {"Line Input", NULL, "LLINEIN"},
+ {"Line Input", NULL, "RLINEIN"},
+};
+
+static const struct snd_soc_dapm_route ssm2602_routes[] = {
+ {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
+
+ {"RHPOUT", NULL, "Output Mixer"},
+ {"LHPOUT", NULL, "Output Mixer"},
+
+ {"Input Mux", "Line", "Line Input"},
+ {"Input Mux", "Mic", "Mic Bias"},
+ {"ADC", NULL, "Input Mux"},
+
+ {"Mic Bias", NULL, "MICIN"},
+};
+
+static const struct snd_soc_dapm_route ssm2604_routes[] = {
+ {"ADC", NULL, "Line Input"},
+};
+
+struct ssm2602_coeff {
+ u32 mclk;
+ u32 rate;
+ u8 srate;
+};
+
+#define SSM2602_COEFF_SRATE(sr, bosr, usb) (((sr) << 2) | ((bosr) << 1) | (usb))
+
+/* codec mclk clock coefficients */
+static const struct ssm2602_coeff ssm2602_coeff_table[] = {
+ /* 48k */
+ {12288000, 48000, SSM2602_COEFF_SRATE(0x0, 0x0, 0x0)},
+ {18432000, 48000, SSM2602_COEFF_SRATE(0x0, 0x1, 0x0)},
+ {12000000, 48000, SSM2602_COEFF_SRATE(0x0, 0x0, 0x1)},
+
+ /* 32k */
+ {12288000, 32000, SSM2602_COEFF_SRATE(0x6, 0x0, 0x0)},
+ {18432000, 32000, SSM2602_COEFF_SRATE(0x6, 0x1, 0x0)},
+ {12000000, 32000, SSM2602_COEFF_SRATE(0x6, 0x0, 0x1)},
+
+ /* 8k */
+ {12288000, 8000, SSM2602_COEFF_SRATE(0x3, 0x0, 0x0)},
+ {18432000, 8000, SSM2602_COEFF_SRATE(0x3, 0x1, 0x0)},
+ {11289600, 8000, SSM2602_COEFF_SRATE(0xb, 0x0, 0x0)},
+ {16934400, 8000, SSM2602_COEFF_SRATE(0xb, 0x1, 0x0)},
+ {12000000, 8000, SSM2602_COEFF_SRATE(0x3, 0x0, 0x1)},
+
+ /* 96k */
+ {12288000, 96000, SSM2602_COEFF_SRATE(0x7, 0x0, 0x0)},
+ {18432000, 96000, SSM2602_COEFF_SRATE(0x7, 0x1, 0x0)},
+ {12000000, 96000, SSM2602_COEFF_SRATE(0x7, 0x0, 0x1)},
+
+ /* 44.1k */
+ {11289600, 44100, SSM2602_COEFF_SRATE(0x8, 0x0, 0x0)},
+ {16934400, 44100, SSM2602_COEFF_SRATE(0x8, 0x1, 0x0)},
+ {12000000, 44100, SSM2602_COEFF_SRATE(0x8, 0x1, 0x1)},
+
+ /* 88.2k */
+ {11289600, 88200, SSM2602_COEFF_SRATE(0xf, 0x0, 0x0)},
+ {16934400, 88200, SSM2602_COEFF_SRATE(0xf, 0x1, 0x0)},
+ {12000000, 88200, SSM2602_COEFF_SRATE(0xf, 0x1, 0x1)},
+};
+
+static inline int ssm2602_get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ssm2602_coeff_table); i++) {
+ if (ssm2602_coeff_table[i].rate == rate &&
+ ssm2602_coeff_table[i].mclk == mclk)
+ return ssm2602_coeff_table[i].srate;
+ }
+ return -EINVAL;
+}
+
+static int ssm2602_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec);
+ u16 iface = snd_soc_read(codec, SSM2602_IFACE) & 0xfff3;
+ int srate = ssm2602_get_coeff(ssm2602->sysclk, params_rate(params));
+
+ if (substream == ssm2602->slave_substream) {
+ dev_dbg(codec->dev, "Ignoring hw_params for slave substream\n");
+ return 0;
+ }
+
+ if (srate < 0)
+ return srate;
+
+ snd_soc_write(codec, SSM2602_SRATE, srate);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+ snd_soc_write(codec, SSM2602_IFACE, iface);
+ return 0;
+}
+
+static int ssm2602_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec);
+ struct i2c_client *i2c = codec->control_data;
+ struct snd_pcm_runtime *master_runtime;
+
+ /* The DAI has shared clocks so if we already have a playback or
+ * capture going then constrain this substream to match it.
+ * TODO: the ssm2602 allows pairs of non-matching PB/REC rates
+ */
+ if (ssm2602->master_substream) {
+ master_runtime = ssm2602->master_substream->runtime;
+ dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n",
+ master_runtime->sample_bits,
+ master_runtime->rate);
+
+ if (master_runtime->rate != 0)
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ master_runtime->rate,
+ master_runtime->rate);
+
+ if (master_runtime->sample_bits != 0)
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
+
+ ssm2602->slave_substream = substream;
+ } else
+ ssm2602->master_substream = substream;
+
+ return 0;
+}
+
+static void ssm2602_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec);
+
+ if (ssm2602->master_substream == substream)
+ ssm2602->master_substream = ssm2602->slave_substream;
+
+ ssm2602->slave_substream = NULL;
+}
+
+
+static int ssm2602_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, SSM2602_APDIGI) & ~APDIGI_ENABLE_DAC_MUTE;
+ if (mute)
+ snd_soc_write(codec, SSM2602_APDIGI,
+ mute_reg | APDIGI_ENABLE_DAC_MUTE);
+ else
+ snd_soc_write(codec, SSM2602_APDIGI, mute_reg);
+ return 0;
+}
+
+static int ssm2602_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec);
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ ssm2602->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0013;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0003;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ snd_soc_write(codec, SSM2602_IFACE, iface);
+ return 0;
+}
+
+static int ssm2602_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg = snd_soc_read(codec, SSM2602_PWR);
+ reg &= ~(PWR_POWER_OFF | PWR_OSC_PDN);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* vref/mid, osc on, dac unmute */
+ snd_soc_write(codec, SSM2602_PWR, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ snd_soc_write(codec, SSM2602_PWR, reg | PWR_CLK_OUT_PDN);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ snd_soc_write(codec, SSM2602_PWR, 0xffff);
+ break;
+
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops ssm2602_dai_ops = {
+ .startup = ssm2602_startup,
+ .hw_params = ssm2602_hw_params,
+ .shutdown = ssm2602_shutdown,
+ .digital_mute = ssm2602_mute,
+ .set_sysclk = ssm2602_set_dai_sysclk,
+ .set_fmt = ssm2602_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver ssm2602_dai = {
+ .name = "ssm2602-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SSM2602_RATES,
+ .formats = SSM2602_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SSM2602_RATES,
+ .formats = SSM2602_FORMATS,},
+ .ops = &ssm2602_dai_ops,
+};
+
+static int ssm2602_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ssm2602_resume(struct snd_soc_codec *codec)
+{
+ snd_soc_cache_sync(codec);
+
+ ssm2602_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int ssm2602_probe(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret, reg;
+
+ reg = snd_soc_read(codec, SSM2602_LOUT1V);
+ snd_soc_write(codec, SSM2602_LOUT1V, reg | LOUT1V_LRHP_BOTH);
+ reg = snd_soc_read(codec, SSM2602_ROUT1V);
+ snd_soc_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH);
+
+ ret = snd_soc_add_controls(codec, ssm2602_snd_controls,
+ ARRAY_SIZE(ssm2602_snd_controls));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, ssm2602_dapm_widgets,
+ ARRAY_SIZE(ssm2602_dapm_widgets));
+ if (ret)
+ return ret;
+
+ return snd_soc_dapm_add_routes(dapm, ssm2602_routes,
+ ARRAY_SIZE(ssm2602_routes));
+}
+
+static int ssm2604_probe(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, ssm2604_dapm_widgets,
+ ARRAY_SIZE(ssm2604_dapm_widgets));
+ if (ret)
+ return ret;
+
+ return snd_soc_dapm_add_routes(dapm, ssm2604_routes,
+ ARRAY_SIZE(ssm2604_routes));
+}
+
+static int ssm260x_probe(struct snd_soc_codec *codec)
+{
+ struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec);
+ int ret, reg;
+
+ pr_info("ssm2602 Audio Codec %s", SSM2602_VERSION);
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, ssm2602->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = ssm2602_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ return ret;
+ }
+
+ /* set the update bits */
+ reg = snd_soc_read(codec, SSM2602_LINVOL);
+ snd_soc_write(codec, SSM2602_LINVOL, reg | LINVOL_LRIN_BOTH);
+ reg = snd_soc_read(codec, SSM2602_RINVOL);
+ snd_soc_write(codec, SSM2602_RINVOL, reg | RINVOL_RLIN_BOTH);
+ /*select Line in as default input*/
+ snd_soc_write(codec, SSM2602_APANA, APANA_SELECT_DAC |
+ APANA_ENABLE_MIC_BOOST);
+
+ switch (ssm2602->type) {
+ case SSM2602:
+ ret = ssm2602_probe(codec);
+ break;
+ case SSM2604:
+ ret = ssm2604_probe(codec);
+ break;
+ }
+
+ return ret;
+}
+
+/* remove everything here */
+static int ssm2602_remove(struct snd_soc_codec *codec)
+{
+ ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ssm2602 = {
+ .probe = ssm260x_probe,
+ .remove = ssm2602_remove,
+ .suspend = ssm2602_suspend,
+ .resume = ssm2602_resume,
+ .set_bias_level = ssm2602_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(ssm2602_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = ssm2602_reg,
+
+ .controls = ssm260x_snd_controls,
+ .num_controls = ARRAY_SIZE(ssm260x_snd_controls),
+ .dapm_widgets = ssm260x_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ssm260x_dapm_widgets),
+ .dapm_routes = ssm260x_routes,
+ .num_dapm_routes = ARRAY_SIZE(ssm260x_routes),
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit ssm2602_spi_probe(struct spi_device *spi)
+{
+ struct ssm2602_priv *ssm2602;
+ int ret;
+
+ ssm2602 = kzalloc(sizeof(struct ssm2602_priv), GFP_KERNEL);
+ if (ssm2602 == NULL)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, ssm2602);
+ ssm2602->control_type = SND_SOC_SPI;
+ ssm2602->type = SSM2602;
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_ssm2602, &ssm2602_dai, 1);
+ if (ret < 0)
+ kfree(ssm2602);
+ return ret;
+}
+
+static int __devexit ssm2602_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver ssm2602_spi_driver = {
+ .driver = {
+ .name = "ssm2602",
+ .owner = THIS_MODULE,
+ },
+ .probe = ssm2602_spi_probe,
+ .remove = __devexit_p(ssm2602_spi_remove),
+};
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * ssm2602 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+static int __devinit ssm2602_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct ssm2602_priv *ssm2602;
+ int ret;
+
+ ssm2602 = kzalloc(sizeof(struct ssm2602_priv), GFP_KERNEL);
+ if (ssm2602 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, ssm2602);
+ ssm2602->control_type = SND_SOC_I2C;
+ ssm2602->type = id->driver_data;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_ssm2602, &ssm2602_dai, 1);
+ if (ret < 0)
+ kfree(ssm2602);
+ return ret;
+}
+
+static int __devexit ssm2602_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id ssm2602_i2c_id[] = {
+ { "ssm2602", SSM2602 },
+ { "ssm2603", SSM2602 },
+ { "ssm2604", SSM2604 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ssm2602_i2c_id);
+
+/* corgi i2c codec control layer */
+static struct i2c_driver ssm2602_i2c_driver = {
+ .driver = {
+ .name = "ssm2602",
+ .owner = THIS_MODULE,
+ },
+ .probe = ssm2602_i2c_probe,
+ .remove = __devexit_p(ssm2602_i2c_remove),
+ .id_table = ssm2602_i2c_id,
+};
+#endif
+
+
+static int __init ssm2602_modinit(void)
+{
+ int ret = 0;
+
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&ssm2602_spi_driver);
+ if (ret)
+ return ret;
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&ssm2602_i2c_driver);
+ if (ret)
+ return ret;
+#endif
+
+ return ret;
+}
+module_init(ssm2602_modinit);
+
+static void __exit ssm2602_exit(void)
+{
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&ssm2602_spi_driver);
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&ssm2602_i2c_driver);
+#endif
+}
+module_exit(ssm2602_exit);
+
+MODULE_DESCRIPTION("ASoC SSM2602/SSM2603/SSM2604 driver");
+MODULE_AUTHOR("Cliff Cai");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ssm2602.h b/sound/soc/codecs/ssm2602.h
new file mode 100644
index 00000000..b98c6916
--- /dev/null
+++ b/sound/soc/codecs/ssm2602.h
@@ -0,0 +1,121 @@
+/*
+ * File: sound/soc/codecs/ssm2602.h
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _SSM2602_H
+#define _SSM2602_H
+
+/* SSM2602 Codec Register definitions */
+
+#define SSM2602_LINVOL 0x00
+#define SSM2602_RINVOL 0x01
+#define SSM2602_LOUT1V 0x02
+#define SSM2602_ROUT1V 0x03
+#define SSM2602_APANA 0x04
+#define SSM2602_APDIGI 0x05
+#define SSM2602_PWR 0x06
+#define SSM2602_IFACE 0x07
+#define SSM2602_SRATE 0x08
+#define SSM2602_ACTIVE 0x09
+#define SSM2602_RESET 0x0f
+
+/*SSM2602 Codec Register Field definitions
+ *(Mask value to extract the corresponding Register field)
+ */
+
+/*Left ADC Volume Control (SSM2602_REG_LEFT_ADC_VOL)*/
+#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */
+#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */
+#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */
+
+/*Right ADC Volume Control (SSM2602_REG_RIGHT_ADC_VOL)*/
+#define RINVOL_RIN_VOL 0x01F /* Right Channel PGA Volume control */
+#define RINVOL_RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */
+#define RINVOL_RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */
+
+/*Left DAC Volume Control (SSM2602_REG_LEFT_DAC_VOL)*/
+#define LOUT1V_LHP_VOL 0x07F /* Left Channel Headphone volume control */
+#define LOUT1V_ENABLE_LZC 0x080 /* Left Channel Zero cross detect enable */
+#define LOUT1V_LRHP_BOTH 0x100 /* Left Channel Headphone volume update */
+
+/*Right DAC Volume Control (SSM2602_REG_RIGHT_DAC_VOL)*/
+#define ROUT1V_RHP_VOL 0x07F /* Right Channel Headphone volume control */
+#define ROUT1V_ENABLE_RZC 0x080 /* Right Channel Zero cross detect enable */
+#define ROUT1V_RLHP_BOTH 0x100 /* Right Channel Headphone volume update */
+
+/*Analogue Audio Path Control (SSM2602_REG_ANALOGUE_PATH)*/
+#define APANA_ENABLE_MIC_BOOST 0x001 /* Primary Microphone Amplifier gain booster control */
+#define APANA_ENABLE_MIC_MUTE 0x002 /* Microphone Mute Control */
+#define APANA_ADC_IN_SELECT 0x004 /* Microphone/Line IN select to ADC (1=MIC, 0=Line In) */
+#define APANA_ENABLE_BYPASS 0x008 /* Line input bypass to line output */
+#define APANA_SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */
+#define APANA_ENABLE_SIDETONE 0x020 /* Enable/Disable Side Tone */
+#define APANA_SIDETONE_ATTN 0x0C0 /* Side Tone Attenuation */
+#define APANA_ENABLE_MIC_BOOST2 0x100 /* Secondary Microphone Amplifier gain booster control */
+
+/*Digital Audio Path Control (SSM2602_REG_DIGITAL_PATH)*/
+#define APDIGI_ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */
+#define APDIGI_DE_EMPHASIS 0x006 /* De-Emphasis Control */
+#define APDIGI_ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */
+#define APDIGI_STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */
+
+/*Power Down Control (SSM2602_REG_POWER)
+ *(1=Enable PowerDown, 0=Disable PowerDown)
+ */
+#define PWR_LINE_IN_PDN 0x001 /* Line Input Power Down */
+#define PWR_MIC_PDN 0x002 /* Microphone Input & Bias Power Down */
+#define PWR_ADC_PDN 0x004 /* ADC Power Down */
+#define PWR_DAC_PDN 0x008 /* DAC Power Down */
+#define PWR_OUT_PDN 0x010 /* Outputs Power Down */
+#define PWR_OSC_PDN 0x020 /* Oscillator Power Down */
+#define PWR_CLK_OUT_PDN 0x040 /* CLKOUT Power Down */
+#define PWR_POWER_OFF 0x080 /* POWEROFF Mode */
+
+/*Digital Audio Interface Format (SSM2602_REG_DIGITAL_IFACE)*/
+#define IFACE_IFACE_FORMAT 0x003 /* Digital Audio input format control */
+#define IFACE_AUDIO_DATA_LEN 0x00C /* Audio Data word length control */
+#define IFACE_DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */
+#define IFACE_DAC_LR_SWAP 0x020 /* Swap DAC data control */
+#define IFACE_ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */
+#define IFACE_BCLK_INVERT 0x080 /* Bit Clock Inversion control */
+
+/*Sampling Control (SSM2602_REG_SAMPLING_CTRL)*/
+#define SRATE_ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */
+#define SRATE_BOS_RATE 0x002 /* Base Over-Sampling rate */
+#define SRATE_SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */
+#define SRATE_CORECLK_DIV2 0x040 /* Core Clock divider select */
+#define SRATE_CLKOUT_DIV2 0x080 /* Clock Out divider select */
+
+/*Active Control (SSM2602_REG_ACTIVE_CTRL)*/
+#define ACTIVE_ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */
+
+/*********************************************************************/
+
+#define SSM2602_CACHEREGNUM 10
+
+#define SSM2602_SYSCLK 0
+
+#endif
diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c
new file mode 100644
index 00000000..78b2b502
--- /dev/null
+++ b/sound/soc/codecs/stac9766.c
@@ -0,0 +1,425 @@
+/*
+ * stac9766.c -- ALSA SoC STAC9766 codec support
+ *
+ * Copyright 2009 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.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 of the License, or (at your
+ * option) any later version.
+ *
+ * Features:-
+ *
+ * o Support for AC97 Codec, S/PDIF
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "stac9766.h"
+
+#define STAC9766_VERSION "0.10"
+
+/*
+ * STAC9766 register cache
+ */
+static const u16 stac9766_reg[] = {
+ 0x6A90, 0x8000, 0x8000, 0x8000, /* 6 */
+ 0x0000, 0x0000, 0x8008, 0x8008, /* e */
+ 0x8808, 0x8808, 0x8808, 0x8808, /* 16 */
+ 0x8808, 0x0000, 0x8000, 0x0000, /* 1e */
+ 0x0000, 0x0000, 0x0000, 0x000f, /* 26 */
+ 0x0a05, 0x0400, 0xbb80, 0x0000, /* 2e */
+ 0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */
+ 0x0000, 0x2000, 0x0000, 0x0100, /* 3e */
+ 0x0000, 0x0000, 0x0080, 0x0000, /* 46 */
+ 0x0000, 0x0000, 0x0003, 0xffff, /* 4e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 56 */
+ 0x4000, 0x0000, 0x0000, 0x0000, /* 5e */
+ 0x1201, 0xFFFF, 0xFFFF, 0x0000, /* 66 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
+ 0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 7e */
+};
+
+static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX",
+ "Line", "Stereo Mix", "Mono Mix", "Phone"};
+static const char *stac9766_mono_mux[] = {"Mix", "Mic"};
+static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"};
+static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"};
+static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"};
+static const char *stac9766_record_all_mux[] = {"All analog",
+ "Analog plus DAC"};
+static const char *stac9766_boost1[] = {"0dB", "10dB"};
+static const char *stac9766_boost2[] = {"0dB", "20dB"};
+static const char *stac9766_stereo_mic[] = {"Off", "On"};
+
+static const struct soc_enum stac9766_record_enum =
+ SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux);
+static const struct soc_enum stac9766_mono_enum =
+ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, stac9766_mono_mux);
+static const struct soc_enum stac9766_mic_enum =
+ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, stac9766_mic_mux);
+static const struct soc_enum stac9766_SPDIF_enum =
+ SOC_ENUM_SINGLE(AC97_STAC_DA_CONTROL, 1, 2, stac9766_SPDIF_mux);
+static const struct soc_enum stac9766_popbypass_enum =
+ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux);
+static const struct soc_enum stac9766_record_all_enum =
+ SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2,
+ stac9766_record_all_mux);
+static const struct soc_enum stac9766_boost1_enum =
+ SOC_ENUM_SINGLE(AC97_MIC, 6, 2, stac9766_boost1); /* 0/10dB */
+static const struct soc_enum stac9766_boost2_enum =
+ SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 2, 2, stac9766_boost2); /* 0/20dB */
+static const struct soc_enum stac9766_stereo_mic_enum =
+ SOC_ENUM_SINGLE(AC97_STAC_STEREO_MIC, 2, 1, stac9766_stereo_mic);
+
+static const DECLARE_TLV_DB_LINEAR(master_tlv, -4600, 0);
+static const DECLARE_TLV_DB_LINEAR(record_tlv, 0, 2250);
+static const DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0);
+static const DECLARE_TLV_DB_LINEAR(mix_tlv, -3450, 1200);
+
+static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = {
+ SOC_DOUBLE_TLV("Speaker Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv),
+ SOC_SINGLE("Speaker Switch", AC97_MASTER, 15, 1, 1),
+ SOC_DOUBLE_TLV("Headphone Volume", AC97_HEADPHONE, 8, 0, 31, 1,
+ master_tlv),
+ SOC_SINGLE("Headphone Switch", AC97_HEADPHONE, 15, 1, 1),
+ SOC_SINGLE_TLV("Mono Out Volume", AC97_MASTER_MONO, 0, 31, 1,
+ master_tlv),
+ SOC_SINGLE("Mono Out Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+ SOC_DOUBLE_TLV("Record Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv),
+ SOC_SINGLE("Record Switch", AC97_REC_GAIN, 15, 1, 1),
+
+
+ SOC_SINGLE_TLV("Beep Volume", AC97_PC_BEEP, 1, 15, 1, beep_tlv),
+ SOC_SINGLE("Beep Switch", AC97_PC_BEEP, 15, 1, 1),
+ SOC_SINGLE("Beep Frequency", AC97_PC_BEEP, 5, 127, 1),
+ SOC_SINGLE_TLV("Phone Volume", AC97_PHONE, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1),
+
+ SOC_ENUM("Mic Boost1", stac9766_boost1_enum),
+ SOC_ENUM("Mic Boost2", stac9766_boost2_enum),
+ SOC_SINGLE_TLV("Mic Volume", AC97_MIC, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
+ SOC_ENUM("Stereo Mic", stac9766_stereo_mic_enum),
+
+ SOC_DOUBLE_TLV("Line Volume", AC97_LINE, 8, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("Line Switch", AC97_LINE, 15, 1, 1),
+ SOC_DOUBLE_TLV("CD Volume", AC97_CD, 8, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("CD Switch", AC97_CD, 15, 1, 1),
+ SOC_DOUBLE_TLV("AUX Volume", AC97_AUX, 8, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1),
+ SOC_DOUBLE_TLV("Video Volume", AC97_VIDEO, 8, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1),
+
+ SOC_DOUBLE_TLV("DAC Volume", AC97_PCM, 8, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("DAC Switch", AC97_PCM, 15, 1, 1),
+ SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0),
+ SOC_SINGLE("3D Volume", AC97_3D_CONTROL, 3, 2, 1),
+ SOC_SINGLE("3D Switch", AC97_GENERAL_PURPOSE, 13, 1, 0),
+
+ SOC_ENUM("SPDIF Mux", stac9766_SPDIF_enum),
+ SOC_ENUM("Mic1/2 Mux", stac9766_mic_enum),
+ SOC_ENUM("Record All Mux", stac9766_record_all_enum),
+ SOC_ENUM("Record Mux", stac9766_record_enum),
+ SOC_ENUM("Mono Mux", stac9766_mono_enum),
+ SOC_ENUM("Pop Bypass Mux", stac9766_popbypass_enum),
+};
+
+static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg > AC97_STAC_PAGE0) {
+ stac9766_ac97_write(codec, AC97_INT_PAGING, 0);
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
+ return 0;
+ }
+ if (reg / 2 >= ARRAY_SIZE(stac9766_reg))
+ return -EIO;
+
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ cache[reg / 2] = val;
+ return 0;
+}
+
+static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 val = 0, *cache = codec->reg_cache;
+
+ if (reg > AC97_STAC_PAGE0) {
+ stac9766_ac97_write(codec, AC97_INT_PAGING, 0);
+ val = soc_ac97_ops.read(codec->ac97, reg - AC97_STAC_PAGE0);
+ stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
+ return val;
+ }
+ if (reg / 2 >= ARRAY_SIZE(stac9766_reg))
+ return -EIO;
+
+ if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+ reg == AC97_INT_PAGING || reg == AC97_VENDOR_ID1 ||
+ reg == AC97_VENDOR_ID2) {
+
+ val = soc_ac97_ops.read(codec->ac97, reg);
+ return val;
+ }
+ return cache[reg / 2];
+}
+
+static int ac97_analog_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned short reg, vra;
+
+ vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
+
+ vra |= 0x1; /* enable variable rate audio */
+ vra &= ~0x4; /* disable SPDIF output */
+
+ stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = AC97_PCM_FRONT_DAC_RATE;
+ else
+ reg = AC97_PCM_LR_ADC_RATE;
+
+ return stac9766_ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_digital_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned short reg, vra;
+
+ stac9766_ac97_write(codec, AC97_SPDIF, 0x2002);
+
+ vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
+ vra |= 0x5; /* Enable VRA and SPDIF out */
+
+ stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
+
+ reg = AC97_PCM_FRONT_DAC_RATE;
+
+ return stac9766_ac97_write(codec, reg, runtime->rate);
+}
+
+static int stac9766_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON: /* full On */
+ case SND_SOC_BIAS_PREPARE: /* partial On */
+ case SND_SOC_BIAS_STANDBY: /* Off, with power */
+ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000);
+ break;
+ case SND_SOC_BIAS_OFF: /* Off, without power */
+ /* disable everything including AC link */
+ stac9766_ac97_write(codec, AC97_POWERDOWN, 0xffff);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int stac9766_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (stac9766_ac97_read(codec, 0) == stac9766_reg[0])
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ if (soc_ac97_ops.warm_reset)
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (stac9766_ac97_read(codec, 0) != stac9766_reg[0])
+ return -EIO;
+ return 0;
+}
+
+static int stac9766_codec_suspend(struct snd_soc_codec *codec,
+ pm_message_t state)
+{
+ stac9766_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int stac9766_codec_resume(struct snd_soc_codec *codec)
+{
+ u16 id, reset;
+
+ reset = 0;
+ /* give the codec an AC97 warm reset to start the link */
+reset:
+ if (reset > 5) {
+ printk(KERN_ERR "stac9766 failed to resume");
+ return -EIO;
+ }
+ codec->ac97->bus->ops->warm_reset(codec->ac97);
+ id = soc_ac97_ops.read(codec->ac97, AC97_VENDOR_ID2);
+ if (id != 0x4c13) {
+ stac9766_reset(codec, 0);
+ reset++;
+ goto reset;
+ }
+ stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops stac9766_dai_ops_analog = {
+ .prepare = ac97_analog_prepare,
+};
+
+static struct snd_soc_dai_ops stac9766_dai_ops_digital = {
+ .prepare = ac97_digital_prepare,
+};
+
+static struct snd_soc_dai_driver stac9766_dai[] = {
+{
+ .name = "stac9766-hifi-analog",
+ .ac97_control = 1,
+
+ /* stream cababilities */
+ .playback = {
+ .stream_name = "stac9766 analog",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SND_SOC_STD_AC97_FMTS,
+ },
+ .capture = {
+ .stream_name = "stac9766 analog",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SND_SOC_STD_AC97_FMTS,
+ },
+ /* alsa ops */
+ .ops = &stac9766_dai_ops_analog,
+},
+{
+ .name = "stac9766-hifi-IEC958",
+ .ac97_control = 1,
+
+ /* stream cababilities */
+ .playback = {
+ .stream_name = "stac9766 IEC958",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE,
+ },
+ /* alsa ops */
+ .ops = &stac9766_dai_ops_digital,
+}
+};
+
+static int stac9766_codec_probe(struct snd_soc_codec *codec)
+{
+ int ret = 0;
+
+ printk(KERN_INFO "STAC9766 SoC Audio Codec %s\n", STAC9766_VERSION);
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0)
+ goto codec_err;
+
+ /* do a cold reset for the controller and then try
+ * a warm reset followed by an optional cold reset for codec */
+ stac9766_reset(codec, 0);
+ ret = stac9766_reset(codec, 1);
+ if (ret < 0) {
+ printk(KERN_ERR "Failed to reset STAC9766: AC97 link error\n");
+ goto codec_err;
+ }
+
+ stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_add_controls(codec, stac9766_snd_ac97_controls,
+ ARRAY_SIZE(stac9766_snd_ac97_controls));
+
+ return 0;
+
+codec_err:
+ snd_soc_free_ac97_codec(codec);
+ return ret;
+}
+
+static int stac9766_codec_remove(struct snd_soc_codec *codec)
+{
+ snd_soc_free_ac97_codec(codec);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_stac9766 = {
+ .write = stac9766_ac97_write,
+ .read = stac9766_ac97_read,
+ .set_bias_level = stac9766_set_bias_level,
+ .probe = stac9766_codec_probe,
+ .remove = stac9766_codec_remove,
+ .suspend = stac9766_codec_suspend,
+ .resume = stac9766_codec_resume,
+ .reg_cache_size = sizeof(stac9766_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+ .reg_cache_default = stac9766_reg,
+};
+
+static __devinit int stac9766_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_stac9766, stac9766_dai, ARRAY_SIZE(stac9766_dai));
+}
+
+static int __devexit stac9766_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver stac9766_codec_driver = {
+ .driver = {
+ .name = "stac9766-codec",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = stac9766_probe,
+ .remove = __devexit_p(stac9766_remove),
+};
+
+static int __init stac9766_init(void)
+{
+ return platform_driver_register(&stac9766_codec_driver);
+}
+module_init(stac9766_init);
+
+static void __exit stac9766_exit(void)
+{
+ platform_driver_unregister(&stac9766_codec_driver);
+}
+module_exit(stac9766_exit);
+
+MODULE_DESCRIPTION("ASoC stac9766 driver");
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/stac9766.h b/sound/soc/codecs/stac9766.h
new file mode 100644
index 00000000..c726f907
--- /dev/null
+++ b/sound/soc/codecs/stac9766.h
@@ -0,0 +1,17 @@
+/*
+ * stac9766.h -- STAC9766 Soc Audio driver
+ */
+
+#ifndef _STAC9766_H
+#define _STAC9766_H
+
+#define AC97_STAC_PAGE0 0x1000
+#define AC97_STAC_DA_CONTROL (AC97_STAC_PAGE0 | 0x6A)
+#define AC97_STAC_ANALOG_SPECIAL (AC97_STAC_PAGE0 | 0x6E)
+#define AC97_STAC_STEREO_MIC 0x78
+
+/* STAC9766 DAI ID's */
+#define STAC9766_DAI_AC97_ANALOG 0
+#define STAC9766_DAI_AC97_DIGITAL 1
+
+#endif
diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c
new file mode 100644
index 00000000..33bb52f3
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic23.c
@@ -0,0 +1,771 @@
+/*
+ * ALSA SoC TLV320AIC23 codec driver
+ *
+ * Author: Arun KS, <arunks@mistralsolutions.com>
+ * Copyright: (C) 2008 Mistral Solutions Pvt Ltd.,
+ *
+ * Based on sound/soc/codecs/wm8731.c by Richard Purdie
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Notes:
+ * The AIC23 is a driver for a low power stereo audio
+ * codec tlv320aic23
+ *
+ * The machine layer should disable unsupported inputs/outputs by
+ * snd_soc_dapm_disable_pin(codec, "LHPOUT"), etc.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+
+#include "tlv320aic23.h"
+
+#define AIC23_VERSION "0.1"
+
+/*
+ * AIC23 register cache
+ */
+static const u16 tlv320aic23_reg[] = {
+ 0x0097, 0x0097, 0x00F9, 0x00F9, /* 0 */
+ 0x001A, 0x0004, 0x0007, 0x0001, /* 4 */
+ 0x0020, 0x0000, 0x0000, 0x0000, /* 8 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 12 */
+};
+
+/*
+ * read tlv320aic23 register cache
+ */
+static inline unsigned int tlv320aic23_read_reg_cache(struct snd_soc_codec
+ *codec, unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= ARRAY_SIZE(tlv320aic23_reg))
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write tlv320aic23 register cache
+ */
+static inline void tlv320aic23_write_reg_cache(struct snd_soc_codec *codec,
+ u8 reg, u16 value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= ARRAY_SIZE(tlv320aic23_reg))
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the tlv320aic23 register space
+ */
+static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+
+ u8 data[2];
+
+ /* TLV320AIC23 has 7 bit address and 9 bits of data
+ * so we need to switch one data bit into reg and rest
+ * of data into val
+ */
+
+ if (reg > 9 && reg != 15) {
+ printk(KERN_WARNING "%s Invalid register R%u\n", __func__, reg);
+ return -1;
+ }
+
+ data[0] = (reg << 1) | (value >> 8 & 0x01);
+ data[1] = value & 0xff;
+
+ tlv320aic23_write_reg_cache(codec, reg, value);
+
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+
+ printk(KERN_ERR "%s cannot write %03x to register R%u\n", __func__,
+ value, reg);
+
+ return -EIO;
+}
+
+static const char *rec_src_text[] = { "Line", "Mic" };
+static const char *deemph_text[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum rec_src_enum =
+ SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text);
+
+static const struct snd_kcontrol_new tlv320aic23_rec_src_mux_controls =
+SOC_DAPM_ENUM("Input Select", rec_src_enum);
+
+static const struct soc_enum tlv320aic23_rec_src =
+ SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text);
+static const struct soc_enum tlv320aic23_deemph =
+ SOC_ENUM_SINGLE(TLV320AIC23_DIGT, 1, 4, deemph_text);
+
+static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -12100, 100, 0);
+static const DECLARE_TLV_DB_SCALE(input_gain_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(sidetone_vol_tlv, -1800, 300, 0);
+
+static int snd_soc_tlv320aic23_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 val, reg;
+
+ val = (ucontrol->value.integer.value[0] & 0x07);
+
+ /* linear conversion to userspace
+ * 000 = -6db
+ * 001 = -9db
+ * 010 = -12db
+ * 011 = -18db (Min)
+ * 100 = 0db (Max)
+ */
+ val = (val >= 4) ? 4 : (3 - val);
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (~0x1C0);
+ tlv320aic23_write(codec, TLV320AIC23_ANLG, reg | (val << 6));
+
+ return 0;
+}
+
+static int snd_soc_tlv320aic23_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 val;
+
+ val = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (0x1C0);
+ val = val >> 6;
+ val = (val >= 4) ? 4 : (3 - val);
+ ucontrol->value.integer.value[0] = val;
+ return 0;
+
+}
+
+#define SOC_TLV320AIC23_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, .get = snd_soc_tlv320aic23_get_volsw,\
+ .put = snd_soc_tlv320aic23_put_volsw, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+static const struct snd_kcontrol_new tlv320aic23_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("Digital Playback Volume", TLV320AIC23_LCHNVOL,
+ TLV320AIC23_RCHNVOL, 0, 127, 0, out_gain_tlv),
+ SOC_SINGLE("Digital Playback Switch", TLV320AIC23_DIGT, 3, 1, 1),
+ SOC_DOUBLE_R("Line Input Switch", TLV320AIC23_LINVOL,
+ TLV320AIC23_RINVOL, 7, 1, 0),
+ SOC_DOUBLE_R_TLV("Line Input Volume", TLV320AIC23_LINVOL,
+ TLV320AIC23_RINVOL, 0, 31, 0, input_gain_tlv),
+ SOC_SINGLE("Mic Input Switch", TLV320AIC23_ANLG, 1, 1, 1),
+ SOC_SINGLE("Mic Booster Switch", TLV320AIC23_ANLG, 0, 1, 0),
+ SOC_TLV320AIC23_SINGLE_TLV("Sidetone Volume", TLV320AIC23_ANLG,
+ 6, 4, 0, sidetone_vol_tlv),
+ SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph),
+};
+
+/* PGA Mixer controls for Line and Mic switch */
+static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", TLV320AIC23_ANLG, 5, 1, 0),
+ SOC_DAPM_SINGLE("Playback Switch", TLV320AIC23_ANLG, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1),
+ SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1),
+ SND_SOC_DAPM_MUX("Capture Source", SND_SOC_NOPM, 0, 0,
+ &tlv320aic23_rec_src_mux_controls),
+ SND_SOC_DAPM_MIXER("Output Mixer", TLV320AIC23_PWR, 4, 1,
+ &tlv320aic23_output_mixer_controls[0],
+ ARRAY_SIZE(tlv320aic23_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("Line Input", TLV320AIC23_PWR, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic Input", TLV320AIC23_PWR, 1, 1, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("LHPOUT"),
+ SND_SOC_DAPM_OUTPUT("RHPOUT"),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+
+ SND_SOC_DAPM_INPUT("LLINEIN"),
+ SND_SOC_DAPM_INPUT("RLINEIN"),
+
+ SND_SOC_DAPM_INPUT("MICIN"),
+};
+
+static const struct snd_soc_dapm_route tlv320aic23_intercon[] = {
+ /* Output Mixer */
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "Playback Switch", "DAC"},
+ {"Output Mixer", "Mic Sidetone Switch", "Mic Input"},
+
+ /* Outputs */
+ {"RHPOUT", NULL, "Output Mixer"},
+ {"LHPOUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+
+ /* Inputs */
+ {"Line Input", "NULL", "LLINEIN"},
+ {"Line Input", "NULL", "RLINEIN"},
+
+ {"Mic Input", "NULL", "MICIN"},
+
+ /* input mux */
+ {"Capture Source", "Line", "Line Input"},
+ {"Capture Source", "Mic", "Mic Input"},
+ {"ADC", NULL, "Capture Source"},
+
+};
+
+/* AIC23 driver data */
+struct aic23 {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+ int mclk;
+ int requested_adc;
+ int requested_dac;
+};
+
+/*
+ * Common Crystals used
+ * 11.2896 Mhz /128 = *88.2k /192 = 58.8k
+ * 12.0000 Mhz /125 = *96k /136 = 88.235K
+ * 12.2880 Mhz /128 = *96k /192 = 64k
+ * 16.9344 Mhz /128 = 132.3k /192 = *88.2k
+ * 18.4320 Mhz /128 = 144k /192 = *96k
+ */
+
+/*
+ * Normal BOSR 0-256/2 = 128, 1-384/2 = 192
+ * USB BOSR 0-250/2 = 125, 1-272/2 = 136
+ */
+static const int bosr_usb_divisor_table[] = {
+ 128, 125, 192, 136
+};
+#define LOWER_GROUP ((1<<0) | (1<<1) | (1<<2) | (1<<3) | (1<<6) | (1<<7))
+#define UPPER_GROUP ((1<<8) | (1<<9) | (1<<10) | (1<<11) | (1<<15))
+static const unsigned short sr_valid_mask[] = {
+ LOWER_GROUP|UPPER_GROUP, /* Normal, bosr - 0*/
+ LOWER_GROUP, /* Usb, bosr - 0*/
+ LOWER_GROUP|UPPER_GROUP, /* Normal, bosr - 1*/
+ UPPER_GROUP, /* Usb, bosr - 1*/
+};
+/*
+ * Every divisor is a factor of 11*12
+ */
+#define SR_MULT (11*12)
+#define A(x) (SR_MULT/x)
+static const unsigned char sr_adc_mult_table[] = {
+ A(2), A(2), A(12), A(12), 0, 0, A(3), A(1),
+ A(2), A(2), A(11), A(11), 0, 0, 0, A(1)
+};
+static const unsigned char sr_dac_mult_table[] = {
+ A(2), A(12), A(2), A(12), 0, 0, A(3), A(1),
+ A(2), A(11), A(2), A(11), 0, 0, 0, A(1)
+};
+
+static unsigned get_score(int adc, int adc_l, int adc_h, int need_adc,
+ int dac, int dac_l, int dac_h, int need_dac)
+{
+ if ((adc >= adc_l) && (adc <= adc_h) &&
+ (dac >= dac_l) && (dac <= dac_h)) {
+ int diff_adc = need_adc - adc;
+ int diff_dac = need_dac - dac;
+ return abs(diff_adc) + abs(diff_dac);
+ }
+ return UINT_MAX;
+}
+
+static int find_rate(int mclk, u32 need_adc, u32 need_dac)
+{
+ int i, j;
+ int best_i = -1;
+ int best_j = -1;
+ int best_div = 0;
+ unsigned best_score = UINT_MAX;
+ int adc_l, adc_h, dac_l, dac_h;
+
+ need_adc *= SR_MULT;
+ need_dac *= SR_MULT;
+ /*
+ * rates given are +/- 1/32
+ */
+ adc_l = need_adc - (need_adc >> 5);
+ adc_h = need_adc + (need_adc >> 5);
+ dac_l = need_dac - (need_dac >> 5);
+ dac_h = need_dac + (need_dac >> 5);
+ for (i = 0; i < ARRAY_SIZE(bosr_usb_divisor_table); i++) {
+ int base = mclk / bosr_usb_divisor_table[i];
+ int mask = sr_valid_mask[i];
+ for (j = 0; j < ARRAY_SIZE(sr_adc_mult_table);
+ j++, mask >>= 1) {
+ int adc;
+ int dac;
+ int score;
+ if ((mask & 1) == 0)
+ continue;
+ adc = base * sr_adc_mult_table[j];
+ dac = base * sr_dac_mult_table[j];
+ score = get_score(adc, adc_l, adc_h, need_adc,
+ dac, dac_l, dac_h, need_dac);
+ if (best_score > score) {
+ best_score = score;
+ best_i = i;
+ best_j = j;
+ best_div = 0;
+ }
+ score = get_score((adc >> 1), adc_l, adc_h, need_adc,
+ (dac >> 1), dac_l, dac_h, need_dac);
+ /* prefer to have a /2 */
+ if ((score != UINT_MAX) && (best_score >= score)) {
+ best_score = score;
+ best_i = i;
+ best_j = j;
+ best_div = 1;
+ }
+ }
+ }
+ return (best_j << 2) | best_i | (best_div << TLV320AIC23_CLKIN_SHIFT);
+}
+
+#ifdef DEBUG
+static void get_current_sample_rates(struct snd_soc_codec *codec, int mclk,
+ u32 *sample_rate_adc, u32 *sample_rate_dac)
+{
+ int src = tlv320aic23_read_reg_cache(codec, TLV320AIC23_SRATE);
+ int sr = (src >> 2) & 0x0f;
+ int val = (mclk / bosr_usb_divisor_table[src & 3]);
+ int adc = (val * sr_adc_mult_table[sr]) / SR_MULT;
+ int dac = (val * sr_dac_mult_table[sr]) / SR_MULT;
+ if (src & TLV320AIC23_CLKIN_HALF) {
+ adc >>= 1;
+ dac >>= 1;
+ }
+ *sample_rate_adc = adc;
+ *sample_rate_dac = dac;
+}
+#endif
+
+static int set_sample_rate_control(struct snd_soc_codec *codec, int mclk,
+ u32 sample_rate_adc, u32 sample_rate_dac)
+{
+ /* Search for the right sample rate */
+ int data = find_rate(mclk, sample_rate_adc, sample_rate_dac);
+ if (data < 0) {
+ printk(KERN_ERR "%s:Invalid rate %u,%u requested\n",
+ __func__, sample_rate_adc, sample_rate_dac);
+ return -EINVAL;
+ }
+ tlv320aic23_write(codec, TLV320AIC23_SRATE, data);
+#ifdef DEBUG
+ {
+ u32 adc, dac;
+ get_current_sample_rates(codec, mclk, &adc, &dac);
+ printk(KERN_DEBUG "actual samplerate = %u,%u reg=%x\n",
+ adc, dac, data);
+ }
+#endif
+ return 0;
+}
+
+static int tlv320aic23_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u16 iface_reg;
+ int ret;
+ struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec);
+ u32 sample_rate_adc = aic23->requested_adc;
+ u32 sample_rate_dac = aic23->requested_dac;
+ u32 sample_rate = params_rate(params);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ aic23->requested_dac = sample_rate_dac = sample_rate;
+ if (!sample_rate_adc)
+ sample_rate_adc = sample_rate;
+ } else {
+ aic23->requested_adc = sample_rate_adc = sample_rate;
+ if (!sample_rate_dac)
+ sample_rate_dac = sample_rate;
+ }
+ ret = set_sample_rate_control(codec, aic23->mclk, sample_rate_adc,
+ sample_rate_dac);
+ if (ret < 0)
+ return ret;
+
+ iface_reg =
+ tlv320aic23_read_reg_cache(codec,
+ TLV320AIC23_DIGT_FMT) & ~(0x03 << 2);
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface_reg |= (0x01 << 2);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface_reg |= (0x02 << 2);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface_reg |= (0x03 << 2);
+ break;
+ }
+ tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg);
+
+ return 0;
+}
+
+static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ /* set active */
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0001);
+
+ return 0;
+}
+
+static void tlv320aic23_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec);
+
+ /* deactivate */
+ if (!codec->active) {
+ udelay(50);
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ aic23->requested_dac = 0;
+ else
+ aic23->requested_adc = 0;
+}
+
+static int tlv320aic23_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 reg;
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT);
+ if (mute)
+ reg |= TLV320AIC23_DACM_MUTE;
+
+ else
+ reg &= ~TLV320AIC23_DACM_MUTE;
+
+ tlv320aic23_write(codec, TLV320AIC23_DIGT, reg);
+
+ return 0;
+}
+
+static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface_reg;
+
+ iface_reg =
+ tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT_FMT) & (~0x03);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface_reg |= TLV320AIC23_MS_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface_reg |= TLV320AIC23_FOR_I2S;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface_reg |= TLV320AIC23_LRP_ON;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface_reg |= TLV320AIC23_FOR_DSP;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface_reg |= TLV320AIC23_FOR_LJUST;
+ break;
+ default:
+ return -EINVAL;
+
+ }
+
+ tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg);
+
+ return 0;
+}
+
+static int tlv320aic23_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct aic23 *aic23 = snd_soc_dai_get_drvdata(codec_dai);
+ aic23->mclk = freq;
+ return 0;
+}
+
+static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_PWR) & 0xff7f;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* vref/mid, osc on, dac unmute */
+ reg &= ~(TLV320AIC23_DEVICE_PWR_OFF | TLV320AIC23_OSC_OFF | \
+ TLV320AIC23_DAC_OFF);
+ tlv320aic23_write(codec, TLV320AIC23_PWR, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ tlv320aic23_write(codec, TLV320AIC23_PWR, reg | \
+ TLV320AIC23_CLK_OFF);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
+ tlv320aic23_write(codec, TLV320AIC23_PWR, 0xffff);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define AIC23_RATES SNDRV_PCM_RATE_8000_96000
+#define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops tlv320aic23_dai_ops = {
+ .prepare = tlv320aic23_pcm_prepare,
+ .hw_params = tlv320aic23_hw_params,
+ .shutdown = tlv320aic23_shutdown,
+ .digital_mute = tlv320aic23_mute,
+ .set_fmt = tlv320aic23_set_dai_fmt,
+ .set_sysclk = tlv320aic23_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver tlv320aic23_dai = {
+ .name = "tlv320aic23-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC23_RATES,
+ .formats = AIC23_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC23_RATES,
+ .formats = AIC23_FORMATS,},
+ .ops = &tlv320aic23_dai_ops,
+};
+
+static int tlv320aic23_suspend(struct snd_soc_codec *codec,
+ pm_message_t state)
+{
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int tlv320aic23_resume(struct snd_soc_codec *codec)
+{
+ u16 reg;
+
+ /* Sync reg_cache with the hardware */
+ for (reg = 0; reg <= TLV320AIC23_ACTIVE; reg++) {
+ u16 val = tlv320aic23_read_reg_cache(codec, reg);
+ tlv320aic23_write(codec, reg, val);
+ }
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int tlv320aic23_probe(struct snd_soc_codec *codec)
+{
+ struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec);
+ int reg;
+
+ printk(KERN_INFO "AIC23 Audio Codec %s\n", AIC23_VERSION);
+ codec->control_data = aic23->control_data;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ codec->hw_read = NULL;
+
+ /* Reset codec */
+ tlv320aic23_write(codec, TLV320AIC23_RESET, 0);
+
+ /* power on device */
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ tlv320aic23_write(codec, TLV320AIC23_DIGT, TLV320AIC23_DEEMP_44K);
+
+ /* Unmute input */
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_LINVOL);
+ tlv320aic23_write(codec, TLV320AIC23_LINVOL,
+ (reg & (~TLV320AIC23_LIM_MUTED)) |
+ (TLV320AIC23_LRS_ENABLED));
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_RINVOL);
+ tlv320aic23_write(codec, TLV320AIC23_RINVOL,
+ (reg & (~TLV320AIC23_LIM_MUTED)) |
+ TLV320AIC23_LRS_ENABLED);
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG);
+ tlv320aic23_write(codec, TLV320AIC23_ANLG,
+ (reg) & (~TLV320AIC23_BYPASS_ON) &
+ (~TLV320AIC23_MICM_MUTED));
+
+ /* Default output volume */
+ tlv320aic23_write(codec, TLV320AIC23_LCHNVOL,
+ TLV320AIC23_DEFAULT_OUT_VOL &
+ TLV320AIC23_OUT_VOL_MASK);
+ tlv320aic23_write(codec, TLV320AIC23_RCHNVOL,
+ TLV320AIC23_DEFAULT_OUT_VOL &
+ TLV320AIC23_OUT_VOL_MASK);
+
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x1);
+
+ snd_soc_add_controls(codec, tlv320aic23_snd_controls,
+ ARRAY_SIZE(tlv320aic23_snd_controls));
+
+ return 0;
+}
+
+static int tlv320aic23_remove(struct snd_soc_codec *codec)
+{
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_tlv320aic23 = {
+ .reg_cache_size = ARRAY_SIZE(tlv320aic23_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = tlv320aic23_reg,
+ .probe = tlv320aic23_probe,
+ .remove = tlv320aic23_remove,
+ .suspend = tlv320aic23_suspend,
+ .resume = tlv320aic23_resume,
+ .read = tlv320aic23_read_reg_cache,
+ .write = tlv320aic23_write,
+ .set_bias_level = tlv320aic23_set_bias_level,
+ .dapm_widgets = tlv320aic23_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets),
+ .dapm_routes = tlv320aic23_intercon,
+ .num_dapm_routes = ARRAY_SIZE(tlv320aic23_intercon),
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * If the i2c layer weren't so broken, we could pass this kind of data
+ * around
+ */
+static int tlv320aic23_codec_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *i2c_id)
+{
+ struct aic23 *aic23;
+ int ret;
+
+ if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EINVAL;
+
+ aic23 = kzalloc(sizeof(struct aic23), GFP_KERNEL);
+ if (aic23 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, aic23);
+ aic23->control_data = i2c;
+ aic23->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_tlv320aic23, &tlv320aic23_dai, 1);
+ if (ret < 0)
+ kfree(aic23);
+ return ret;
+}
+static int __exit tlv320aic23_i2c_remove(struct i2c_client *i2c)
+{
+ snd_soc_unregister_codec(&i2c->dev);
+ kfree(i2c_get_clientdata(i2c));
+ return 0;
+}
+
+static const struct i2c_device_id tlv320aic23_id[] = {
+ {"tlv320aic23", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, tlv320aic23_id);
+
+static struct i2c_driver tlv320aic23_i2c_driver = {
+ .driver = {
+ .name = "tlv320aic23-codec",
+ },
+ .probe = tlv320aic23_codec_probe,
+ .remove = __exit_p(tlv320aic23_i2c_remove),
+ .id_table = tlv320aic23_id,
+};
+
+#endif
+
+static int __init tlv320aic23_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&tlv320aic23_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register TLV320AIC23 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(tlv320aic23_modinit);
+
+static void __exit tlv320aic23_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&tlv320aic23_i2c_driver);
+#endif
+}
+module_exit(tlv320aic23_exit);
+
+MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver");
+MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tlv320aic23.h b/sound/soc/codecs/tlv320aic23.h
new file mode 100644
index 00000000..e804120b
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic23.h
@@ -0,0 +1,119 @@
+/*
+ * ALSA SoC TLV320AIC23 codec driver
+ *
+ * Author: Arun KS, <arunks@mistralsolutions.com>
+ * Copyright: (C) 2008 Mistral Solutions Pvt Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _TLV320AIC23_H
+#define _TLV320AIC23_H
+
+/* Codec TLV320AIC23 */
+#define TLV320AIC23_LINVOL 0x00
+#define TLV320AIC23_RINVOL 0x01
+#define TLV320AIC23_LCHNVOL 0x02
+#define TLV320AIC23_RCHNVOL 0x03
+#define TLV320AIC23_ANLG 0x04
+#define TLV320AIC23_DIGT 0x05
+#define TLV320AIC23_PWR 0x06
+#define TLV320AIC23_DIGT_FMT 0x07
+#define TLV320AIC23_SRATE 0x08
+#define TLV320AIC23_ACTIVE 0x09
+#define TLV320AIC23_RESET 0x0F
+
+/* Left (right) line input volume control register */
+#define TLV320AIC23_LRS_ENABLED 0x0100
+#define TLV320AIC23_LIM_MUTED 0x0080
+#define TLV320AIC23_LIV_DEFAULT 0x0017
+#define TLV320AIC23_LIV_MAX 0x001f
+#define TLV320AIC23_LIV_MIN 0x0000
+
+/* Left (right) channel headphone volume control register */
+#define TLV320AIC23_LZC_ON 0x0080
+#define TLV320AIC23_LHV_DEFAULT 0x0079
+#define TLV320AIC23_LHV_MAX 0x007f
+#define TLV320AIC23_LHV_MIN 0x0000
+
+/* Analog audio path control register */
+#define TLV320AIC23_STA_REG(x) ((x)<<6)
+#define TLV320AIC23_STE_ENABLED 0x0020
+#define TLV320AIC23_DAC_SELECTED 0x0010
+#define TLV320AIC23_BYPASS_ON 0x0008
+#define TLV320AIC23_INSEL_MIC 0x0004
+#define TLV320AIC23_MICM_MUTED 0x0002
+#define TLV320AIC23_MICB_20DB 0x0001
+
+/* Digital audio path control register */
+#define TLV320AIC23_DACM_MUTE 0x0008
+#define TLV320AIC23_DEEMP_32K 0x0002
+#define TLV320AIC23_DEEMP_44K 0x0004
+#define TLV320AIC23_DEEMP_48K 0x0006
+#define TLV320AIC23_ADCHP_ON 0x0001
+
+/* Power control down register */
+#define TLV320AIC23_DEVICE_PWR_OFF 0x0080
+#define TLV320AIC23_CLK_OFF 0x0040
+#define TLV320AIC23_OSC_OFF 0x0020
+#define TLV320AIC23_OUT_OFF 0x0010
+#define TLV320AIC23_DAC_OFF 0x0008
+#define TLV320AIC23_ADC_OFF 0x0004
+#define TLV320AIC23_MIC_OFF 0x0002
+#define TLV320AIC23_LINE_OFF 0x0001
+
+/* Digital audio interface register */
+#define TLV320AIC23_MS_MASTER 0x0040
+#define TLV320AIC23_LRSWAP_ON 0x0020
+#define TLV320AIC23_LRP_ON 0x0010
+#define TLV320AIC23_IWL_16 0x0000
+#define TLV320AIC23_IWL_20 0x0004
+#define TLV320AIC23_IWL_24 0x0008
+#define TLV320AIC23_IWL_32 0x000C
+#define TLV320AIC23_FOR_I2S 0x0002
+#define TLV320AIC23_FOR_DSP 0x0003
+#define TLV320AIC23_FOR_LJUST 0x0001
+
+/* Sample rate control register */
+#define TLV320AIC23_CLKOUT_HALF 0x0080
+#define TLV320AIC23_CLKIN_HALF 0x0040
+#define TLV320AIC23_BOSR_384fs 0x0002 /* BOSR_272fs in USB mode */
+#define TLV320AIC23_USB_CLK_ON 0x0001
+#define TLV320AIC23_SR_MASK 0xf
+#define TLV320AIC23_CLKOUT_SHIFT 7
+#define TLV320AIC23_CLKIN_SHIFT 6
+#define TLV320AIC23_SR_SHIFT 2
+#define TLV320AIC23_BOSR_SHIFT 1
+
+/* Digital interface register */
+#define TLV320AIC23_ACT_ON 0x0001
+
+/*
+ * AUDIO related MACROS
+ */
+
+#define TLV320AIC23_DEFAULT_OUT_VOL 0x70
+#define TLV320AIC23_DEFAULT_IN_VOLUME 0x10
+
+#define TLV320AIC23_OUT_VOL_MIN TLV320AIC23_LHV_MIN
+#define TLV320AIC23_OUT_VOL_MAX TLV320AIC23_LHV_MAX
+#define TLV320AIC23_OUT_VO_RANGE (TLV320AIC23_OUT_VOL_MAX - \
+ TLV320AIC23_OUT_VOL_MIN)
+#define TLV320AIC23_OUT_VOL_MASK TLV320AIC23_OUT_VOL_MAX
+
+#define TLV320AIC23_IN_VOL_MIN TLV320AIC23_LIV_MIN
+#define TLV320AIC23_IN_VOL_MAX TLV320AIC23_LIV_MAX
+#define TLV320AIC23_IN_VOL_RANGE (TLV320AIC23_IN_VOL_MAX - \
+ TLV320AIC23_IN_VOL_MIN)
+#define TLV320AIC23_IN_VOL_MASK TLV320AIC23_IN_VOL_MAX
+
+#define TLV320AIC23_SIDETONE_MASK 0x1c0
+#define TLV320AIC23_SIDETONE_0 0x100
+#define TLV320AIC23_SIDETONE_6 0x000
+#define TLV320AIC23_SIDETONE_9 0x040
+#define TLV320AIC23_SIDETONE_12 0x080
+#define TLV320AIC23_SIDETONE_18 0x0c0
+
+#endif /* _TLV320AIC23_H */
diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c
new file mode 100644
index 00000000..7859bdcc
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic26.c
@@ -0,0 +1,464 @@
+/*
+ * Texas Instruments TLV320AIC26 low power audio CODEC
+ * ALSA SoC CODEC driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "tlv320aic26.h"
+
+MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver");
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_LICENSE("GPL");
+
+/* AIC26 driver private data */
+struct aic26 {
+ struct spi_device *spi;
+ struct snd_soc_codec codec;
+ int master;
+ int datfm;
+ int mclk;
+
+ /* Keyclick parameters */
+ int keyclick_amplitude;
+ int keyclick_freq;
+ int keyclick_len;
+};
+
+/* ---------------------------------------------------------------------
+ * Register access routines
+ */
+static unsigned int aic26_reg_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+ u16 *cache = codec->reg_cache;
+ u16 cmd, value;
+ u8 buffer[2];
+ int rc;
+
+ if (reg >= AIC26_NUM_REGS) {
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ /* Do SPI transfer; first 16bits are command; remaining is
+ * register contents */
+ cmd = AIC26_READ_COMMAND_WORD(reg);
+ buffer[0] = (cmd >> 8) & 0xff;
+ buffer[1] = cmd & 0xff;
+ rc = spi_write_then_read(aic26->spi, buffer, 2, buffer, 2);
+ if (rc) {
+ dev_err(&aic26->spi->dev, "AIC26 reg read error\n");
+ return -EIO;
+ }
+ value = (buffer[0] << 8) | buffer[1];
+
+ /* Update the cache before returning with the value */
+ cache[reg] = value;
+ return value;
+}
+
+static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg >= AIC26_NUM_REGS) {
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ return cache[reg];
+}
+
+static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+ u16 *cache = codec->reg_cache;
+ u16 cmd;
+ u8 buffer[4];
+ int rc;
+
+ if (reg >= AIC26_NUM_REGS) {
+ WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /* Do SPI transfer; first 16bits are command; remaining is data
+ * to write into register */
+ cmd = AIC26_WRITE_COMMAND_WORD(reg);
+ buffer[0] = (cmd >> 8) & 0xff;
+ buffer[1] = cmd & 0xff;
+ buffer[2] = value >> 8;
+ buffer[3] = value;
+ rc = spi_write(aic26->spi, buffer, 4);
+ if (rc) {
+ dev_err(&aic26->spi->dev, "AIC26 reg read error\n");
+ return -EIO;
+ }
+
+ /* update cache before returning */
+ cache[reg] = value;
+ return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Digital Audio Interface Operations
+ */
+static int aic26_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+ int fsref, divisor, wlen, pval, jval, dval, qval;
+ u16 reg;
+
+ dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n",
+ substream, params);
+ dev_dbg(&aic26->spi->dev, "rate=%i format=%i\n", params_rate(params),
+ params_format(params));
+
+ switch (params_rate(params)) {
+ case 8000: fsref = 48000; divisor = AIC26_DIV_6; break;
+ case 11025: fsref = 44100; divisor = AIC26_DIV_4; break;
+ case 12000: fsref = 48000; divisor = AIC26_DIV_4; break;
+ case 16000: fsref = 48000; divisor = AIC26_DIV_3; break;
+ case 22050: fsref = 44100; divisor = AIC26_DIV_2; break;
+ case 24000: fsref = 48000; divisor = AIC26_DIV_2; break;
+ case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break;
+ case 44100: fsref = 44100; divisor = AIC26_DIV_1; break;
+ case 48000: fsref = 48000; divisor = AIC26_DIV_1; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL;
+ }
+
+ /* select data word length */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8: wlen = AIC26_WLEN_16; break;
+ case SNDRV_PCM_FORMAT_S16_BE: wlen = AIC26_WLEN_16; break;
+ case SNDRV_PCM_FORMAT_S24_BE: wlen = AIC26_WLEN_24; break;
+ case SNDRV_PCM_FORMAT_S32_BE: wlen = AIC26_WLEN_32; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
+ }
+
+ /**
+ * Configure PLL
+ * fsref = (mclk * PLLM) / 2048
+ * where PLLM = J.DDDD (DDDD register ranges from 0 to 9999, decimal)
+ */
+ pval = 1;
+ /* compute J portion of multiplier */
+ jval = fsref / (aic26->mclk / 2048);
+ /* compute fractional DDDD component of multiplier */
+ dval = fsref - (jval * (aic26->mclk / 2048));
+ dval = (10000 * dval) / (aic26->mclk / 2048);
+ dev_dbg(&aic26->spi->dev, "Setting PLLM to %d.%04d\n", jval, dval);
+ qval = 0;
+ reg = 0x8000 | qval << 11 | pval << 8 | jval << 2;
+ aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg);
+ reg = dval << 2;
+ aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg);
+
+ /* Audio Control 3 (master mode, fsref rate) */
+ reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL3);
+ reg &= ~0xf800;
+ if (aic26->master)
+ reg |= 0x0800;
+ if (fsref == 48000)
+ reg |= 0x2000;
+ aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg);
+
+ /* Audio Control 1 (FSref divisor) */
+ reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL1);
+ reg &= ~0x0fff;
+ reg |= wlen | aic26->datfm | (divisor << 3) | divisor;
+ aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL1, reg);
+
+ return 0;
+}
+
+/**
+ * aic26_mute - Mute control to reduce noise when changing audio format
+ */
+static int aic26_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+ u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN);
+
+ dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n",
+ dai, mute);
+
+ if (mute)
+ reg |= 0x8080;
+ else
+ reg &= ~0x8080;
+ aic26_reg_write(codec, AIC26_REG_DAC_GAIN, reg);
+
+ return 0;
+}
+
+static int aic26_set_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+
+ dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i,"
+ " freq=%i, dir=%i)\n",
+ codec_dai, clk_id, freq, dir);
+
+ /* MCLK needs to fall between 2MHz and 50 MHz */
+ if ((freq < 2000000) || (freq > 50000000))
+ return -EINVAL;
+
+ aic26->mclk = freq;
+ return 0;
+}
+
+static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+
+ dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n",
+ codec_dai, fmt);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break;
+ case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break;
+ case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break;
+ case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break;
+ case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Digital Audio Interface Definition
+ */
+#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\
+ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
+
+static struct snd_soc_dai_ops aic26_dai_ops = {
+ .hw_params = aic26_hw_params,
+ .digital_mute = aic26_mute,
+ .set_sysclk = aic26_set_sysclk,
+ .set_fmt = aic26_set_fmt,
+};
+
+static struct snd_soc_dai_driver aic26_dai = {
+ .name = "tlv320aic26-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC26_RATES,
+ .formats = AIC26_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC26_RATES,
+ .formats = AIC26_FORMATS,
+ },
+ .ops = &aic26_dai_ops,
+};
+
+/* ---------------------------------------------------------------------
+ * ALSA controls
+ */
+static const char *aic26_capture_src_text[] = {"Mic", "Aux"};
+static const struct soc_enum aic26_capture_src_enum =
+ SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12, 2, aic26_capture_src_text);
+
+static const struct snd_kcontrol_new aic26_snd_controls[] = {
+ /* Output */
+ SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1),
+ SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1),
+ SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0),
+ SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1),
+ SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0),
+ SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0),
+ SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0),
+ SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0),
+ SOC_ENUM("Capture Source", aic26_capture_src_enum),
+};
+
+/* ---------------------------------------------------------------------
+ * SPI device portion of driver: sysfs files for debugging
+ */
+
+static ssize_t aic26_keyclick_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct aic26 *aic26 = dev_get_drvdata(dev);
+ int val, amp, freq, len;
+
+ val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2);
+ amp = (val >> 12) & 0x7;
+ freq = (125 << ((val >> 8) & 0x7)) >> 1;
+ len = 2 * (1 + ((val >> 4) & 0xf));
+
+ return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len);
+}
+
+/* Any write to the keyclick attribute will trigger the keyclick event */
+static ssize_t aic26_keyclick_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct aic26 *aic26 = dev_get_drvdata(dev);
+ int val;
+
+ val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2);
+ val |= 0x8000;
+ aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL2, val);
+
+ return count;
+}
+
+static DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set);
+
+/* ---------------------------------------------------------------------
+ * SoC CODEC portion of driver: probe and release routines
+ */
+static int aic26_probe(struct snd_soc_codec *codec)
+{
+ int ret, err, i, reg;
+
+ dev_info(codec->dev, "Probing AIC26 SoC CODEC driver\n");
+
+ /* Reset the codec to power on defaults */
+ aic26_reg_write(codec, AIC26_REG_RESET, 0xBB00);
+
+ /* Power up CODEC */
+ aic26_reg_write(codec, AIC26_REG_POWER_CTRL, 0);
+
+ /* Audio Control 3 (master mode, fsref rate) */
+ reg = aic26_reg_read(codec, AIC26_REG_AUDIO_CTRL3);
+ reg &= ~0xf800;
+ reg |= 0x0800; /* set master mode */
+ aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg);
+
+ /* Fill register cache */
+ for (i = 0; i < codec->driver->reg_cache_size; i++)
+ aic26_reg_read(codec, i);
+
+ /* Register the sysfs files for debugging */
+ /* Create SysFS files */
+ ret = device_create_file(codec->dev, &dev_attr_keyclick);
+ if (ret)
+ dev_info(codec->dev, "error creating sysfs files\n");
+
+ /* register controls */
+ dev_dbg(codec->dev, "Registering controls\n");
+ err = snd_soc_add_controls(codec, aic26_snd_controls,
+ ARRAY_SIZE(aic26_snd_controls));
+ WARN_ON(err < 0);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver aic26_soc_codec_dev = {
+ .probe = aic26_probe,
+ .read = aic26_reg_read,
+ .write = aic26_reg_write,
+ .reg_cache_size = AIC26_NUM_REGS,
+ .reg_word_size = sizeof(u16),
+};
+
+/* ---------------------------------------------------------------------
+ * SPI device portion of driver: probe and release routines and SPI
+ * driver registration.
+ */
+static int aic26_spi_probe(struct spi_device *spi)
+{
+ struct aic26 *aic26;
+ int ret;
+
+ dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n");
+
+ /* Allocate driver data */
+ aic26 = kzalloc(sizeof *aic26, GFP_KERNEL);
+ if (!aic26)
+ return -ENOMEM;
+
+ /* Initialize the driver data */
+ aic26->spi = spi;
+ dev_set_drvdata(&spi->dev, aic26);
+ aic26->master = 1;
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &aic26_soc_codec_dev, &aic26_dai, 1);
+ if (ret < 0)
+ kfree(aic26);
+ return ret;
+
+ dev_dbg(&spi->dev, "SPI device initialized\n");
+ return 0;
+}
+
+static int aic26_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver aic26_spi = {
+ .driver = {
+ .name = "tlv320aic26-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = aic26_spi_probe,
+ .remove = aic26_spi_remove,
+};
+
+static int __init aic26_init(void)
+{
+ return spi_register_driver(&aic26_spi);
+}
+module_init(aic26_init);
+
+static void __exit aic26_exit(void)
+{
+ spi_unregister_driver(&aic26_spi);
+}
+module_exit(aic26_exit);
diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h
new file mode 100644
index 00000000..67f19c3b
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic26.h
@@ -0,0 +1,93 @@
+/*
+ * Texas Instruments TLV320AIC26 low power audio CODEC
+ * register definitions
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#ifndef _TLV320AIC16_H_
+#define _TLV320AIC16_H_
+
+/* AIC26 Registers */
+#define AIC26_READ_COMMAND_WORD(addr) ((1 << 15) | (addr << 5))
+#define AIC26_WRITE_COMMAND_WORD(addr) ((0 << 15) | (addr << 5))
+#define AIC26_PAGE_ADDR(page, offset) ((page << 6) | offset)
+#define AIC26_NUM_REGS AIC26_PAGE_ADDR(3, 0)
+
+/* Page 0: Auxiliary data registers */
+#define AIC26_REG_BAT1 AIC26_PAGE_ADDR(0, 0x05)
+#define AIC26_REG_BAT2 AIC26_PAGE_ADDR(0, 0x06)
+#define AIC26_REG_AUX AIC26_PAGE_ADDR(0, 0x07)
+#define AIC26_REG_TEMP1 AIC26_PAGE_ADDR(0, 0x09)
+#define AIC26_REG_TEMP2 AIC26_PAGE_ADDR(0, 0x0A)
+
+/* Page 1: Auxiliary control registers */
+#define AIC26_REG_AUX_ADC AIC26_PAGE_ADDR(1, 0x00)
+#define AIC26_REG_STATUS AIC26_PAGE_ADDR(1, 0x01)
+#define AIC26_REG_REFERENCE AIC26_PAGE_ADDR(1, 0x03)
+#define AIC26_REG_RESET AIC26_PAGE_ADDR(1, 0x04)
+
+/* Page 2: Audio control registers */
+#define AIC26_REG_AUDIO_CTRL1 AIC26_PAGE_ADDR(2, 0x00)
+#define AIC26_REG_ADC_GAIN AIC26_PAGE_ADDR(2, 0x01)
+#define AIC26_REG_DAC_GAIN AIC26_PAGE_ADDR(2, 0x02)
+#define AIC26_REG_SIDETONE AIC26_PAGE_ADDR(2, 0x03)
+#define AIC26_REG_AUDIO_CTRL2 AIC26_PAGE_ADDR(2, 0x04)
+#define AIC26_REG_POWER_CTRL AIC26_PAGE_ADDR(2, 0x05)
+#define AIC26_REG_AUDIO_CTRL3 AIC26_PAGE_ADDR(2, 0x06)
+
+#define AIC26_REG_FILTER_COEFF_L_N0 AIC26_PAGE_ADDR(2, 0x07)
+#define AIC26_REG_FILTER_COEFF_L_N1 AIC26_PAGE_ADDR(2, 0x08)
+#define AIC26_REG_FILTER_COEFF_L_N2 AIC26_PAGE_ADDR(2, 0x09)
+#define AIC26_REG_FILTER_COEFF_L_N3 AIC26_PAGE_ADDR(2, 0x0A)
+#define AIC26_REG_FILTER_COEFF_L_N4 AIC26_PAGE_ADDR(2, 0x0B)
+#define AIC26_REG_FILTER_COEFF_L_N5 AIC26_PAGE_ADDR(2, 0x0C)
+#define AIC26_REG_FILTER_COEFF_L_D1 AIC26_PAGE_ADDR(2, 0x0D)
+#define AIC26_REG_FILTER_COEFF_L_D2 AIC26_PAGE_ADDR(2, 0x0E)
+#define AIC26_REG_FILTER_COEFF_L_D4 AIC26_PAGE_ADDR(2, 0x0F)
+#define AIC26_REG_FILTER_COEFF_L_D5 AIC26_PAGE_ADDR(2, 0x10)
+#define AIC26_REG_FILTER_COEFF_R_N0 AIC26_PAGE_ADDR(2, 0x11)
+#define AIC26_REG_FILTER_COEFF_R_N1 AIC26_PAGE_ADDR(2, 0x12)
+#define AIC26_REG_FILTER_COEFF_R_N2 AIC26_PAGE_ADDR(2, 0x13)
+#define AIC26_REG_FILTER_COEFF_R_N3 AIC26_PAGE_ADDR(2, 0x14)
+#define AIC26_REG_FILTER_COEFF_R_N4 AIC26_PAGE_ADDR(2, 0x15)
+#define AIC26_REG_FILTER_COEFF_R_N5 AIC26_PAGE_ADDR(2, 0x16)
+#define AIC26_REG_FILTER_COEFF_R_D1 AIC26_PAGE_ADDR(2, 0x17)
+#define AIC26_REG_FILTER_COEFF_R_D2 AIC26_PAGE_ADDR(2, 0x18)
+#define AIC26_REG_FILTER_COEFF_R_D4 AIC26_PAGE_ADDR(2, 0x19)
+#define AIC26_REG_FILTER_COEFF_R_D5 AIC26_PAGE_ADDR(2, 0x1A)
+
+#define AIC26_REG_PLL_PROG1 AIC26_PAGE_ADDR(2, 0x1B)
+#define AIC26_REG_PLL_PROG2 AIC26_PAGE_ADDR(2, 0x1C)
+#define AIC26_REG_AUDIO_CTRL4 AIC26_PAGE_ADDR(2, 0x1D)
+#define AIC26_REG_AUDIO_CTRL5 AIC26_PAGE_ADDR(2, 0x1E)
+
+/* fsref dividers; used in register 'Audio Control 1' */
+enum aic26_divisors {
+ AIC26_DIV_1 = 0,
+ AIC26_DIV_1_5 = 1,
+ AIC26_DIV_2 = 2,
+ AIC26_DIV_3 = 3,
+ AIC26_DIV_4 = 4,
+ AIC26_DIV_5 = 5,
+ AIC26_DIV_5_5 = 6,
+ AIC26_DIV_6 = 7,
+};
+
+/* Digital data format */
+enum aic26_datfm {
+ AIC26_DATFM_I2S = 0 << 8,
+ AIC26_DATFM_DSP = 1 << 8,
+ AIC26_DATFM_RIGHTJ = 2 << 8, /* right justified */
+ AIC26_DATFM_LEFTJ = 3 << 8, /* left justified */
+};
+
+/* Sample word length in bits; used in register 'Audio Control 1' */
+enum aic26_wlen {
+ AIC26_WLEN_16 = 0 << 10,
+ AIC26_WLEN_20 = 1 << 10,
+ AIC26_WLEN_24 = 2 << 10,
+ AIC26_WLEN_32 = 3 << 10,
+};
+
+#endif /* _TLV320AIC16_H_ */
diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c
new file mode 100644
index 00000000..e93b9d1a
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic32x4.c
@@ -0,0 +1,794 @@
+/*
+ * linux/sound/soc/codecs/tlv320aic32x4.c
+ *
+ * Copyright 2011 Vista Silicon S.L.
+ *
+ * Author: Javier Martin <javier.martin@vista-silicon.com>
+ *
+ * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27.
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/cdev.h>
+#include <linux/slab.h>
+
+#include <sound/tlv320aic32x4.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "tlv320aic32x4.h"
+
+struct aic32x4_rate_divs {
+ u32 mclk;
+ u32 rate;
+ u8 p_val;
+ u8 pll_j;
+ u16 pll_d;
+ u16 dosr;
+ u8 ndac;
+ u8 mdac;
+ u8 aosr;
+ u8 nadc;
+ u8 madc;
+ u8 blck_N;
+};
+
+struct aic32x4_priv {
+ u32 sysclk;
+ s32 master;
+ u8 page_no;
+ void *control_data;
+ u32 power_cfg;
+ u32 micpga_routing;
+ bool swapdacs;
+};
+
+/* 0dB min, 1dB steps */
+static DECLARE_TLV_DB_SCALE(tlv_step_1, 0, 100, 0);
+/* 0dB min, 0.5dB steps */
+static DECLARE_TLV_DB_SCALE(tlv_step_0_5, 0, 50, 0);
+
+static const struct snd_kcontrol_new aic32x4_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("PCM Playback Volume", AIC32X4_LDACVOL,
+ AIC32X4_RDACVOL, 0, 0x30, 0, tlv_step_0_5),
+ SOC_DOUBLE_R_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN,
+ AIC32X4_HPRGAIN, 0, 0x1D, 0, tlv_step_1),
+ SOC_DOUBLE_R_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN,
+ AIC32X4_LORGAIN, 0, 0x1D, 0, tlv_step_1),
+ SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN,
+ AIC32X4_HPRGAIN, 6, 0x01, 1),
+ SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN,
+ AIC32X4_LORGAIN, 6, 0x01, 1),
+ SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL,
+ AIC32X4_RMICPGAVOL, 7, 0x01, 1),
+
+ SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0),
+ SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0),
+
+ SOC_DOUBLE_R_TLV("ADC Level Volume", AIC32X4_LADCVOL,
+ AIC32X4_RADCVOL, 0, 0x28, 0, tlv_step_0_5),
+ SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL,
+ AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5),
+
+ SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0),
+
+ SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0),
+ SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0),
+ SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1,
+ 4, 0x07, 0),
+ SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1,
+ 0, 0x03, 0),
+ SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2,
+ 6, 0x03, 0),
+ SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2,
+ 1, 0x1F, 0),
+ SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3,
+ 0, 0x7F, 0),
+ SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4,
+ 3, 0x1F, 0),
+ SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5,
+ 3, 0x1F, 0),
+ SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6,
+ 0, 0x1F, 0),
+ SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7,
+ 0, 0x0F, 0),
+};
+
+static const struct aic32x4_rate_divs aic32x4_divs[] = {
+ /* 8k rate */
+ {AIC32X4_FREQ_12000000, 8000, 1, 7, 6800, 768, 5, 3, 128, 5, 18, 24},
+ {AIC32X4_FREQ_24000000, 8000, 2, 7, 6800, 768, 15, 1, 64, 45, 4, 24},
+ {AIC32X4_FREQ_25000000, 8000, 2, 7, 3728, 768, 15, 1, 64, 45, 4, 24},
+ /* 11.025k rate */
+ {AIC32X4_FREQ_12000000, 11025, 1, 7, 5264, 512, 8, 2, 128, 8, 8, 16},
+ {AIC32X4_FREQ_24000000, 11025, 2, 7, 5264, 512, 16, 1, 64, 32, 4, 16},
+ /* 16k rate */
+ {AIC32X4_FREQ_12000000, 16000, 1, 7, 6800, 384, 5, 3, 128, 5, 9, 12},
+ {AIC32X4_FREQ_24000000, 16000, 2, 7, 6800, 384, 15, 1, 64, 18, 5, 12},
+ {AIC32X4_FREQ_25000000, 16000, 2, 7, 3728, 384, 15, 1, 64, 18, 5, 12},
+ /* 22.05k rate */
+ {AIC32X4_FREQ_12000000, 22050, 1, 7, 5264, 256, 4, 4, 128, 4, 8, 8},
+ {AIC32X4_FREQ_24000000, 22050, 2, 7, 5264, 256, 16, 1, 64, 16, 4, 8},
+ {AIC32X4_FREQ_25000000, 22050, 2, 7, 2253, 256, 16, 1, 64, 16, 4, 8},
+ /* 32k rate */
+ {AIC32X4_FREQ_12000000, 32000, 1, 7, 1680, 192, 2, 7, 64, 2, 21, 6},
+ {AIC32X4_FREQ_24000000, 32000, 2, 7, 1680, 192, 7, 2, 64, 7, 6, 6},
+ /* 44.1k rate */
+ {AIC32X4_FREQ_12000000, 44100, 1, 7, 5264, 128, 2, 8, 128, 2, 8, 4},
+ {AIC32X4_FREQ_24000000, 44100, 2, 7, 5264, 128, 8, 2, 64, 8, 4, 4},
+ {AIC32X4_FREQ_25000000, 44100, 2, 7, 2253, 128, 8, 2, 64, 8, 4, 4},
+ /* 48k rate */
+ {AIC32X4_FREQ_12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4},
+ {AIC32X4_FREQ_24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4},
+ {AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4}
+};
+
+static const struct snd_kcontrol_new hpl_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_HPLROUTE, 3, 1, 0),
+ SOC_DAPM_SINGLE("IN1_L Switch", AIC32X4_HPLROUTE, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new hpr_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_HPRROUTE, 3, 1, 0),
+ SOC_DAPM_SINGLE("IN1_R Switch", AIC32X4_HPRROUTE, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new lol_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_LOLROUTE, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new lor_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_LORROUTE, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_input_mixer_controls[] = {
+ SOC_DAPM_SINGLE("IN1_L P Switch", AIC32X4_LMICPGAPIN, 6, 1, 0),
+ SOC_DAPM_SINGLE("IN2_L P Switch", AIC32X4_LMICPGAPIN, 4, 1, 0),
+ SOC_DAPM_SINGLE("IN3_L P Switch", AIC32X4_LMICPGAPIN, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_input_mixer_controls[] = {
+ SOC_DAPM_SINGLE("IN1_R P Switch", AIC32X4_RMICPGAPIN, 6, 1, 0),
+ SOC_DAPM_SINGLE("IN2_R P Switch", AIC32X4_RMICPGAPIN, 4, 1, 0),
+ SOC_DAPM_SINGLE("IN3_R P Switch", AIC32X4_RMICPGAPIN, 2, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", AIC32X4_DACSETUP, 7, 0),
+ SND_SOC_DAPM_MIXER("HPL Output Mixer", SND_SOC_NOPM, 0, 0,
+ &hpl_output_mixer_controls[0],
+ ARRAY_SIZE(hpl_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("HPL Power", AIC32X4_OUTPWRCTL, 5, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("LOL Output Mixer", SND_SOC_NOPM, 0, 0,
+ &lol_output_mixer_controls[0],
+ ARRAY_SIZE(lol_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("LOL Power", AIC32X4_OUTPWRCTL, 3, 0, NULL, 0),
+
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", AIC32X4_DACSETUP, 6, 0),
+ SND_SOC_DAPM_MIXER("HPR Output Mixer", SND_SOC_NOPM, 0, 0,
+ &hpr_output_mixer_controls[0],
+ ARRAY_SIZE(hpr_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("HPR Power", AIC32X4_OUTPWRCTL, 4, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("LOR Output Mixer", SND_SOC_NOPM, 0, 0,
+ &lor_output_mixer_controls[0],
+ ARRAY_SIZE(lor_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("LOR Power", AIC32X4_OUTPWRCTL, 2, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0,
+ &left_input_mixer_controls[0],
+ ARRAY_SIZE(left_input_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0,
+ &right_input_mixer_controls[0],
+ ARRAY_SIZE(right_input_mixer_controls)),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0),
+ SND_SOC_DAPM_MICBIAS("Mic Bias", AIC32X4_MICBIAS, 6, 0),
+
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("LOL"),
+ SND_SOC_DAPM_OUTPUT("LOR"),
+ SND_SOC_DAPM_INPUT("IN1_L"),
+ SND_SOC_DAPM_INPUT("IN1_R"),
+ SND_SOC_DAPM_INPUT("IN2_L"),
+ SND_SOC_DAPM_INPUT("IN2_R"),
+ SND_SOC_DAPM_INPUT("IN3_L"),
+ SND_SOC_DAPM_INPUT("IN3_R"),
+};
+
+static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = {
+ /* Left Output */
+ {"HPL Output Mixer", "L_DAC Switch", "Left DAC"},
+ {"HPL Output Mixer", "IN1_L Switch", "IN1_L"},
+
+ {"HPL Power", NULL, "HPL Output Mixer"},
+ {"HPL", NULL, "HPL Power"},
+
+ {"LOL Output Mixer", "L_DAC Switch", "Left DAC"},
+
+ {"LOL Power", NULL, "LOL Output Mixer"},
+ {"LOL", NULL, "LOL Power"},
+
+ /* Right Output */
+ {"HPR Output Mixer", "R_DAC Switch", "Right DAC"},
+ {"HPR Output Mixer", "IN1_R Switch", "IN1_R"},
+
+ {"HPR Power", NULL, "HPR Output Mixer"},
+ {"HPR", NULL, "HPR Power"},
+
+ {"LOR Output Mixer", "R_DAC Switch", "Right DAC"},
+
+ {"LOR Power", NULL, "LOR Output Mixer"},
+ {"LOR", NULL, "LOR Power"},
+
+ /* Left input */
+ {"Left Input Mixer", "IN1_L P Switch", "IN1_L"},
+ {"Left Input Mixer", "IN2_L P Switch", "IN2_L"},
+ {"Left Input Mixer", "IN3_L P Switch", "IN3_L"},
+
+ {"Left ADC", NULL, "Left Input Mixer"},
+
+ /* Right Input */
+ {"Right Input Mixer", "IN1_R P Switch", "IN1_R"},
+ {"Right Input Mixer", "IN2_R P Switch", "IN2_R"},
+ {"Right Input Mixer", "IN3_R P Switch", "IN3_R"},
+
+ {"Right ADC", NULL, "Right Input Mixer"},
+};
+
+static inline int aic32x4_change_page(struct snd_soc_codec *codec,
+ unsigned int new_page)
+{
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ u8 data[2];
+ int ret;
+
+ data[0] = 0x00;
+ data[1] = new_page & 0xff;
+
+ ret = codec->hw_write(codec->control_data, data, 2);
+ if (ret == 2) {
+ aic32x4->page_no = new_page;
+ return 0;
+ } else {
+ return ret;
+ }
+}
+
+static int aic32x4_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ unsigned int page = reg / 128;
+ unsigned int fixed_reg = reg % 128;
+ u8 data[2];
+ int ret;
+
+ /* A write to AIC32X4_PSEL is really a non-explicit page change */
+ if (reg == AIC32X4_PSEL)
+ return aic32x4_change_page(codec, val);
+
+ if (aic32x4->page_no != page) {
+ ret = aic32x4_change_page(codec, page);
+ if (ret != 0)
+ return ret;
+ }
+
+ data[0] = fixed_reg & 0xff;
+ data[1] = val & 0xff;
+
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+static unsigned int aic32x4_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ unsigned int page = reg / 128;
+ unsigned int fixed_reg = reg % 128;
+ int ret;
+
+ if (aic32x4->page_no != page) {
+ ret = aic32x4_change_page(codec, page);
+ if (ret != 0)
+ return ret;
+ }
+ return i2c_smbus_read_byte_data(codec->control_data, fixed_reg & 0xff);
+}
+
+static inline int aic32x4_get_divs(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(aic32x4_divs); i++) {
+ if ((aic32x4_divs[i].rate == rate)
+ && (aic32x4_divs[i].mclk == mclk)) {
+ return i;
+ }
+ }
+ printk(KERN_ERR "aic32x4: master clock and sample rate is not supported\n");
+ return -EINVAL;
+}
+
+static int aic32x4_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(&codec->dapm, aic32x4_dapm_widgets,
+ ARRAY_SIZE(aic32x4_dapm_widgets));
+
+ snd_soc_dapm_add_routes(&codec->dapm, aic32x4_dapm_routes,
+ ARRAY_SIZE(aic32x4_dapm_routes));
+
+ snd_soc_dapm_new_widgets(&codec->dapm);
+ return 0;
+}
+
+static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case AIC32X4_FREQ_12000000:
+ case AIC32X4_FREQ_24000000:
+ case AIC32X4_FREQ_25000000:
+ aic32x4->sysclk = freq;
+ return 0;
+ }
+ printk(KERN_ERR "aic32x4: invalid frequency to set DAI system clock\n");
+ return -EINVAL;
+}
+
+static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ u8 iface_reg_1;
+ u8 iface_reg_2;
+ u8 iface_reg_3;
+
+ iface_reg_1 = snd_soc_read(codec, AIC32X4_IFACE1);
+ iface_reg_1 = iface_reg_1 & ~(3 << 6 | 3 << 2);
+ iface_reg_2 = snd_soc_read(codec, AIC32X4_IFACE2);
+ iface_reg_2 = 0;
+ iface_reg_3 = snd_soc_read(codec, AIC32X4_IFACE3);
+ iface_reg_3 = iface_reg_3 & ~(1 << 3);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aic32x4->master = 1;
+ iface_reg_1 |= AIC32X4_BCLKMASTER | AIC32X4_WCLKMASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ aic32x4->master = 0;
+ break;
+ default:
+ printk(KERN_ERR "aic32x4: invalid DAI master/slave interface\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface_reg_1 |= (AIC32X4_DSP_MODE << AIC32X4_PLLJ_SHIFT);
+ iface_reg_3 |= (1 << 3); /* invert bit clock */
+ iface_reg_2 = 0x01; /* add offset 1 */
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface_reg_1 |= (AIC32X4_DSP_MODE << AIC32X4_PLLJ_SHIFT);
+ iface_reg_3 |= (1 << 3); /* invert bit clock */
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ iface_reg_1 |=
+ (AIC32X4_RIGHT_JUSTIFIED_MODE << AIC32X4_PLLJ_SHIFT);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface_reg_1 |=
+ (AIC32X4_LEFT_JUSTIFIED_MODE << AIC32X4_PLLJ_SHIFT);
+ break;
+ default:
+ printk(KERN_ERR "aic32x4: invalid DAI interface format\n");
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AIC32X4_IFACE1, iface_reg_1);
+ snd_soc_write(codec, AIC32X4_IFACE2, iface_reg_2);
+ snd_soc_write(codec, AIC32X4_IFACE3, iface_reg_3);
+ return 0;
+}
+
+static int aic32x4_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ u8 data;
+ int i;
+
+ i = aic32x4_get_divs(aic32x4->sysclk, params_rate(params));
+ if (i < 0) {
+ printk(KERN_ERR "aic32x4: sampling rate not supported\n");
+ return i;
+ }
+
+ /* Use PLL as CODEC_CLKIN and DAC_MOD_CLK as BDIV_CLKIN */
+ snd_soc_write(codec, AIC32X4_CLKMUX, AIC32X4_PLLCLKIN);
+ snd_soc_write(codec, AIC32X4_IFACE3, AIC32X4_DACMOD2BCLK);
+
+ /* We will fix R value to 1 and will make P & J=K.D as varialble */
+ data = snd_soc_read(codec, AIC32X4_PLLPR);
+ data &= ~(7 << 4);
+ snd_soc_write(codec, AIC32X4_PLLPR,
+ (data | (aic32x4_divs[i].p_val << 4) | 0x01));
+
+ snd_soc_write(codec, AIC32X4_PLLJ, aic32x4_divs[i].pll_j);
+
+ snd_soc_write(codec, AIC32X4_PLLDMSB, (aic32x4_divs[i].pll_d >> 8));
+ snd_soc_write(codec, AIC32X4_PLLDLSB,
+ (aic32x4_divs[i].pll_d & 0xff));
+
+ /* NDAC divider value */
+ data = snd_soc_read(codec, AIC32X4_NDAC);
+ data &= ~(0x7f);
+ snd_soc_write(codec, AIC32X4_NDAC, data | aic32x4_divs[i].ndac);
+
+ /* MDAC divider value */
+ data = snd_soc_read(codec, AIC32X4_MDAC);
+ data &= ~(0x7f);
+ snd_soc_write(codec, AIC32X4_MDAC, data | aic32x4_divs[i].mdac);
+
+ /* DOSR MSB & LSB values */
+ snd_soc_write(codec, AIC32X4_DOSRMSB, aic32x4_divs[i].dosr >> 8);
+ snd_soc_write(codec, AIC32X4_DOSRLSB,
+ (aic32x4_divs[i].dosr & 0xff));
+
+ /* NADC divider value */
+ data = snd_soc_read(codec, AIC32X4_NADC);
+ data &= ~(0x7f);
+ snd_soc_write(codec, AIC32X4_NADC, data | aic32x4_divs[i].nadc);
+
+ /* MADC divider value */
+ data = snd_soc_read(codec, AIC32X4_MADC);
+ data &= ~(0x7f);
+ snd_soc_write(codec, AIC32X4_MADC, data | aic32x4_divs[i].madc);
+
+ /* AOSR value */
+ snd_soc_write(codec, AIC32X4_AOSR, aic32x4_divs[i].aosr);
+
+ /* BCLK N divider */
+ data = snd_soc_read(codec, AIC32X4_BCLKN);
+ data &= ~(0x7f);
+ snd_soc_write(codec, AIC32X4_BCLKN, data | aic32x4_divs[i].blck_N);
+
+ data = snd_soc_read(codec, AIC32X4_IFACE1);
+ data = data & ~(3 << 4);
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ data |= (AIC32X4_WORD_LEN_20BITS << AIC32X4_DOSRMSB_SHIFT);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ data |= (AIC32X4_WORD_LEN_24BITS << AIC32X4_DOSRMSB_SHIFT);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ data |= (AIC32X4_WORD_LEN_32BITS << AIC32X4_DOSRMSB_SHIFT);
+ break;
+ }
+ snd_soc_write(codec, AIC32X4_IFACE1, data);
+
+ return 0;
+}
+
+static int aic32x4_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 dac_reg;
+
+ dac_reg = snd_soc_read(codec, AIC32X4_DACMUTE) & ~AIC32X4_MUTEON;
+ if (mute)
+ snd_soc_write(codec, AIC32X4_DACMUTE, dac_reg | AIC32X4_MUTEON);
+ else
+ snd_soc_write(codec, AIC32X4_DACMUTE, dac_reg);
+ return 0;
+}
+
+static int aic32x4_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ u8 value;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ if (aic32x4->master) {
+ /* Switch on PLL */
+ value = snd_soc_read(codec, AIC32X4_PLLPR);
+ snd_soc_write(codec, AIC32X4_PLLPR,
+ (value | AIC32X4_PLLEN));
+
+ /* Switch on NDAC Divider */
+ value = snd_soc_read(codec, AIC32X4_NDAC);
+ snd_soc_write(codec, AIC32X4_NDAC,
+ value | AIC32X4_NDACEN);
+
+ /* Switch on MDAC Divider */
+ value = snd_soc_read(codec, AIC32X4_MDAC);
+ snd_soc_write(codec, AIC32X4_MDAC,
+ value | AIC32X4_MDACEN);
+
+ /* Switch on NADC Divider */
+ value = snd_soc_read(codec, AIC32X4_NADC);
+ snd_soc_write(codec, AIC32X4_NADC,
+ value | AIC32X4_MDACEN);
+
+ /* Switch on MADC Divider */
+ value = snd_soc_read(codec, AIC32X4_MADC);
+ snd_soc_write(codec, AIC32X4_MADC,
+ value | AIC32X4_MDACEN);
+
+ /* Switch on BCLK_N Divider */
+ value = snd_soc_read(codec, AIC32X4_BCLKN);
+ snd_soc_write(codec, AIC32X4_BCLKN,
+ value | AIC32X4_BCLKEN);
+ }
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (aic32x4->master) {
+ /* Switch off PLL */
+ value = snd_soc_read(codec, AIC32X4_PLLPR);
+ snd_soc_write(codec, AIC32X4_PLLPR,
+ (value & ~AIC32X4_PLLEN));
+
+ /* Switch off NDAC Divider */
+ value = snd_soc_read(codec, AIC32X4_NDAC);
+ snd_soc_write(codec, AIC32X4_NDAC,
+ value & ~AIC32X4_NDACEN);
+
+ /* Switch off MDAC Divider */
+ value = snd_soc_read(codec, AIC32X4_MDAC);
+ snd_soc_write(codec, AIC32X4_MDAC,
+ value & ~AIC32X4_MDACEN);
+
+ /* Switch off NADC Divider */
+ value = snd_soc_read(codec, AIC32X4_NADC);
+ snd_soc_write(codec, AIC32X4_NADC,
+ value & ~AIC32X4_NDACEN);
+
+ /* Switch off MADC Divider */
+ value = snd_soc_read(codec, AIC32X4_MADC);
+ snd_soc_write(codec, AIC32X4_MADC,
+ value & ~AIC32X4_MDACEN);
+ value = snd_soc_read(codec, AIC32X4_BCLKN);
+
+ /* Switch off BCLK_N Divider */
+ snd_soc_write(codec, AIC32X4_BCLKN,
+ value & ~AIC32X4_BCLKEN);
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define AIC32X4_RATES SNDRV_PCM_RATE_8000_48000
+#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \
+ | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops aic32x4_ops = {
+ .hw_params = aic32x4_hw_params,
+ .digital_mute = aic32x4_mute,
+ .set_fmt = aic32x4_set_dai_fmt,
+ .set_sysclk = aic32x4_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver aic32x4_dai = {
+ .name = "tlv320aic32x4-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AIC32X4_RATES,
+ .formats = AIC32X4_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AIC32X4_RATES,
+ .formats = AIC32X4_FORMATS,},
+ .ops = &aic32x4_ops,
+ .symmetric_rates = 1,
+};
+
+static int aic32x4_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int aic32x4_resume(struct snd_soc_codec *codec)
+{
+ aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+static int aic32x4_probe(struct snd_soc_codec *codec)
+{
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ u32 tmp_reg;
+
+ codec->hw_write = (hw_write_t) i2c_master_send;
+ codec->control_data = aic32x4->control_data;
+
+ snd_soc_write(codec, AIC32X4_RESET, 0x01);
+
+ /* Power platform configuration */
+ if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) {
+ snd_soc_write(codec, AIC32X4_MICBIAS, AIC32X4_MICBIAS_LDOIN |
+ AIC32X4_MICBIAS_2075V);
+ }
+ if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) {
+ snd_soc_write(codec, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE);
+ }
+ if (aic32x4->power_cfg & AIC32X4_PWR_AIC32X4_LDO_ENABLE) {
+ snd_soc_write(codec, AIC32X4_LDOCTL, AIC32X4_LDOCTLEN);
+ }
+ tmp_reg = snd_soc_read(codec, AIC32X4_CMMODE);
+ if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36) {
+ tmp_reg |= AIC32X4_LDOIN_18_36;
+ }
+ if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED) {
+ tmp_reg |= AIC32X4_LDOIN2HP;
+ }
+ snd_soc_write(codec, AIC32X4_CMMODE, tmp_reg);
+
+ /* Do DACs need to be swapped? */
+ if (aic32x4->swapdacs) {
+ snd_soc_write(codec, AIC32X4_DACSETUP, AIC32X4_LDAC2RCHN | AIC32X4_RDAC2LCHN);
+ } else {
+ snd_soc_write(codec, AIC32X4_DACSETUP, AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN);
+ }
+
+ /* Mic PGA routing */
+ if (aic32x4->micpga_routing | AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) {
+ snd_soc_write(codec, AIC32X4_LMICPGANIN, AIC32X4_LMICPGANIN_IN2R_10K);
+ }
+ if (aic32x4->micpga_routing | AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) {
+ snd_soc_write(codec, AIC32X4_RMICPGANIN, AIC32X4_RMICPGANIN_IN1L_10K);
+ }
+
+ aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ snd_soc_add_controls(codec, aic32x4_snd_controls,
+ ARRAY_SIZE(aic32x4_snd_controls));
+ aic32x4_add_widgets(codec);
+
+ return 0;
+}
+
+static int aic32x4_remove(struct snd_soc_codec *codec)
+{
+ aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = {
+ .read = aic32x4_read,
+ .write = aic32x4_write,
+ .probe = aic32x4_probe,
+ .remove = aic32x4_remove,
+ .suspend = aic32x4_suspend,
+ .resume = aic32x4_resume,
+ .set_bias_level = aic32x4_set_bias_level,
+};
+
+static __devinit int aic32x4_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct aic32x4_pdata *pdata = i2c->dev.platform_data;
+ struct aic32x4_priv *aic32x4;
+ int ret;
+
+ aic32x4 = kzalloc(sizeof(struct aic32x4_priv), GFP_KERNEL);
+ if (aic32x4 == NULL)
+ return -ENOMEM;
+
+ aic32x4->control_data = i2c;
+ i2c_set_clientdata(i2c, aic32x4);
+
+ if (pdata) {
+ aic32x4->power_cfg = pdata->power_cfg;
+ aic32x4->swapdacs = pdata->swapdacs;
+ aic32x4->micpga_routing = pdata->micpga_routing;
+ } else {
+ aic32x4->power_cfg = 0;
+ aic32x4->swapdacs = false;
+ aic32x4->micpga_routing = 0;
+ }
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_aic32x4, &aic32x4_dai, 1);
+ if (ret < 0)
+ kfree(aic32x4);
+ return ret;
+}
+
+static __devexit int aic32x4_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id aic32x4_i2c_id[] = {
+ { "tlv320aic32x4", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id);
+
+static struct i2c_driver aic32x4_i2c_driver = {
+ .driver = {
+ .name = "tlv320aic32x4",
+ .owner = THIS_MODULE,
+ },
+ .probe = aic32x4_i2c_probe,
+ .remove = __devexit_p(aic32x4_i2c_remove),
+ .id_table = aic32x4_i2c_id,
+};
+
+static int __init aic32x4_modinit(void)
+{
+ int ret = 0;
+
+ ret = i2c_add_driver(&aic32x4_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register aic32x4 I2C driver: %d\n",
+ ret);
+ }
+ return ret;
+}
+module_init(aic32x4_modinit);
+
+static void __exit aic32x4_exit(void)
+{
+ i2c_del_driver(&aic32x4_i2c_driver);
+}
+module_exit(aic32x4_exit);
+
+MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver");
+MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h
new file mode 100644
index 00000000..aae2b244
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic32x4.h
@@ -0,0 +1,143 @@
+/*
+ * tlv320aic32x4.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+
+#ifndef _TLV320AIC32X4_H
+#define _TLV320AIC32X4_H
+
+/* tlv320aic32x4 register space (in decimal to match datasheet) */
+
+#define AIC32X4_PAGE1 128
+
+#define AIC32X4_PSEL 0
+#define AIC32X4_RESET 1
+#define AIC32X4_CLKMUX 4
+#define AIC32X4_PLLPR 5
+#define AIC32X4_PLLJ 6
+#define AIC32X4_PLLDMSB 7
+#define AIC32X4_PLLDLSB 8
+#define AIC32X4_NDAC 11
+#define AIC32X4_MDAC 12
+#define AIC32X4_DOSRMSB 13
+#define AIC32X4_DOSRLSB 14
+#define AIC32X4_NADC 18
+#define AIC32X4_MADC 19
+#define AIC32X4_AOSR 20
+#define AIC32X4_CLKMUX2 25
+#define AIC32X4_CLKOUTM 26
+#define AIC32X4_IFACE1 27
+#define AIC32X4_IFACE2 28
+#define AIC32X4_IFACE3 29
+#define AIC32X4_BCLKN 30
+#define AIC32X4_IFACE4 31
+#define AIC32X4_IFACE5 32
+#define AIC32X4_IFACE6 33
+#define AIC32X4_DOUTCTL 53
+#define AIC32X4_DINCTL 54
+#define AIC32X4_DACSPB 60
+#define AIC32X4_ADCSPB 61
+#define AIC32X4_DACSETUP 63
+#define AIC32X4_DACMUTE 64
+#define AIC32X4_LDACVOL 65
+#define AIC32X4_RDACVOL 66
+#define AIC32X4_ADCSETUP 81
+#define AIC32X4_ADCFGA 82
+#define AIC32X4_LADCVOL 83
+#define AIC32X4_RADCVOL 84
+#define AIC32X4_LAGC1 86
+#define AIC32X4_LAGC2 87
+#define AIC32X4_LAGC3 88
+#define AIC32X4_LAGC4 89
+#define AIC32X4_LAGC5 90
+#define AIC32X4_LAGC6 91
+#define AIC32X4_LAGC7 92
+#define AIC32X4_RAGC1 94
+#define AIC32X4_RAGC2 95
+#define AIC32X4_RAGC3 96
+#define AIC32X4_RAGC4 97
+#define AIC32X4_RAGC5 98
+#define AIC32X4_RAGC6 99
+#define AIC32X4_RAGC7 100
+#define AIC32X4_PWRCFG (AIC32X4_PAGE1 + 1)
+#define AIC32X4_LDOCTL (AIC32X4_PAGE1 + 2)
+#define AIC32X4_OUTPWRCTL (AIC32X4_PAGE1 + 9)
+#define AIC32X4_CMMODE (AIC32X4_PAGE1 + 10)
+#define AIC32X4_HPLROUTE (AIC32X4_PAGE1 + 12)
+#define AIC32X4_HPRROUTE (AIC32X4_PAGE1 + 13)
+#define AIC32X4_LOLROUTE (AIC32X4_PAGE1 + 14)
+#define AIC32X4_LORROUTE (AIC32X4_PAGE1 + 15)
+#define AIC32X4_HPLGAIN (AIC32X4_PAGE1 + 16)
+#define AIC32X4_HPRGAIN (AIC32X4_PAGE1 + 17)
+#define AIC32X4_LOLGAIN (AIC32X4_PAGE1 + 18)
+#define AIC32X4_LORGAIN (AIC32X4_PAGE1 + 19)
+#define AIC32X4_HEADSTART (AIC32X4_PAGE1 + 20)
+#define AIC32X4_MICBIAS (AIC32X4_PAGE1 + 51)
+#define AIC32X4_LMICPGAPIN (AIC32X4_PAGE1 + 52)
+#define AIC32X4_LMICPGANIN (AIC32X4_PAGE1 + 54)
+#define AIC32X4_RMICPGAPIN (AIC32X4_PAGE1 + 55)
+#define AIC32X4_RMICPGANIN (AIC32X4_PAGE1 + 57)
+#define AIC32X4_FLOATINGINPUT (AIC32X4_PAGE1 + 58)
+#define AIC32X4_LMICPGAVOL (AIC32X4_PAGE1 + 59)
+#define AIC32X4_RMICPGAVOL (AIC32X4_PAGE1 + 60)
+
+#define AIC32X4_FREQ_12000000 12000000
+#define AIC32X4_FREQ_24000000 24000000
+#define AIC32X4_FREQ_25000000 25000000
+
+#define AIC32X4_WORD_LEN_16BITS 0x00
+#define AIC32X4_WORD_LEN_20BITS 0x01
+#define AIC32X4_WORD_LEN_24BITS 0x02
+#define AIC32X4_WORD_LEN_32BITS 0x03
+
+#define AIC32X4_I2S_MODE 0x00
+#define AIC32X4_DSP_MODE 0x01
+#define AIC32X4_RIGHT_JUSTIFIED_MODE 0x02
+#define AIC32X4_LEFT_JUSTIFIED_MODE 0x03
+
+#define AIC32X4_AVDDWEAKDISABLE 0x08
+#define AIC32X4_LDOCTLEN 0x01
+
+#define AIC32X4_LDOIN_18_36 0x01
+#define AIC32X4_LDOIN2HP 0x02
+
+#define AIC32X4_DACSPBLOCK_MASK 0x1f
+#define AIC32X4_ADCSPBLOCK_MASK 0x1f
+
+#define AIC32X4_PLLJ_SHIFT 6
+#define AIC32X4_DOSRMSB_SHIFT 4
+
+#define AIC32X4_PLLCLKIN 0x03
+
+#define AIC32X4_MICBIAS_LDOIN 0x08
+#define AIC32X4_MICBIAS_2075V 0x60
+
+#define AIC32X4_LMICPGANIN_IN2R_10K 0x10
+#define AIC32X4_RMICPGANIN_IN1L_10K 0x10
+
+#define AIC32X4_LMICPGAVOL_NOGAIN 0x80
+#define AIC32X4_RMICPGAVOL_NOGAIN 0x80
+
+#define AIC32X4_BCLKMASTER 0x08
+#define AIC32X4_WCLKMASTER 0x04
+#define AIC32X4_PLLEN (0x01 << 7)
+#define AIC32X4_NDACEN (0x01 << 7)
+#define AIC32X4_MDACEN (0x01 << 7)
+#define AIC32X4_NADCEN (0x01 << 7)
+#define AIC32X4_MADCEN (0x01 << 7)
+#define AIC32X4_BCLKEN (0x01 << 7)
+#define AIC32X4_DACEN (0x03 << 6)
+#define AIC32X4_RDAC2LCHN (0x02 << 2)
+#define AIC32X4_LDAC2RCHN (0x02 << 4)
+#define AIC32X4_LDAC2LCHN (0x01 << 4)
+#define AIC32X4_RDAC2RCHN (0x01 << 2)
+
+#define AIC32X4_SSTEP2WCLK 0x01
+#define AIC32X4_MUTEON 0x0C
+#define AIC32X4_DACMOD2BCLK 0x01
+
+#endif /* _TLV320AIC32X4_H */
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c
new file mode 100644
index 00000000..789453d4
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic3x.c
@@ -0,0 +1,1582 @@
+/*
+ * ALSA SoC TLV320AIC3X codec driver
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * Based on sound/soc/codecs/wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Notes:
+ * The AIC3X is a driver for a low power stereo audio
+ * codecs aic31, aic32, aic33, aic3007.
+ *
+ * It supports full aic33 codec functionality.
+ * The compatibility with aic32, aic31 and aic3007 is as follows:
+ * aic32/aic3007 | aic31
+ * ---------------------------------------
+ * MONO_LOUT -> N/A | MONO_LOUT -> N/A
+ * | IN1L -> LINE1L
+ * | IN1R -> LINE1R
+ * | IN2L -> LINE2L
+ * | IN2R -> LINE2R
+ * | MIC3L/R -> N/A
+ * truncated internal functionality in
+ * accordance with documentation
+ * ---------------------------------------
+ *
+ * Hence the machine layer should disable unsupported inputs/outputs by
+ * snd_soc_dapm_disable_pin(codec, "MONO_LOUT"), etc.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/tlv320aic3x.h>
+
+#include "tlv320aic3x.h"
+
+#define AIC3X_NUM_SUPPLIES 4
+static const char *aic3x_supply_names[AIC3X_NUM_SUPPLIES] = {
+ "IOVDD", /* I/O Voltage */
+ "DVDD", /* Digital Core Voltage */
+ "AVDD", /* Analog DAC Voltage */
+ "DRVDD", /* ADC Analog and Output Driver Voltage */
+};
+
+static LIST_HEAD(reset_list);
+
+struct aic3x_priv;
+
+struct aic3x_disable_nb {
+ struct notifier_block nb;
+ struct aic3x_priv *aic3x;
+};
+
+/* codec private data */
+struct aic3x_priv {
+ struct snd_soc_codec *codec;
+ struct regulator_bulk_data supplies[AIC3X_NUM_SUPPLIES];
+ struct aic3x_disable_nb disable_nb[AIC3X_NUM_SUPPLIES];
+ enum snd_soc_control_type control_type;
+ struct aic3x_setup_data *setup;
+ void *control_data;
+ unsigned int sysclk;
+ struct list_head list;
+ int master;
+ int gpio_reset;
+ int power;
+#define AIC3X_MODEL_3X 0
+#define AIC3X_MODEL_33 1
+#define AIC3X_MODEL_3007 2
+ u16 model;
+};
+
+/*
+ * AIC3X register cache
+ * We can't read the AIC3X register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u8 aic3x_reg[AIC3X_CACHEREGNUM] = {
+ 0x00, 0x00, 0x00, 0x10, /* 0 */
+ 0x04, 0x00, 0x00, 0x00, /* 4 */
+ 0x00, 0x00, 0x00, 0x01, /* 8 */
+ 0x00, 0x00, 0x00, 0x80, /* 12 */
+ 0x80, 0xff, 0xff, 0x78, /* 16 */
+ 0x78, 0x78, 0x78, 0x78, /* 20 */
+ 0x78, 0x00, 0x00, 0xfe, /* 24 */
+ 0x00, 0x00, 0xfe, 0x00, /* 28 */
+ 0x18, 0x18, 0x00, 0x00, /* 32 */
+ 0x00, 0x00, 0x00, 0x00, /* 36 */
+ 0x00, 0x00, 0x00, 0x80, /* 40 */
+ 0x80, 0x00, 0x00, 0x00, /* 44 */
+ 0x00, 0x00, 0x00, 0x04, /* 48 */
+ 0x00, 0x00, 0x00, 0x00, /* 52 */
+ 0x00, 0x00, 0x04, 0x00, /* 56 */
+ 0x00, 0x00, 0x00, 0x00, /* 60 */
+ 0x00, 0x04, 0x00, 0x00, /* 64 */
+ 0x00, 0x00, 0x00, 0x00, /* 68 */
+ 0x04, 0x00, 0x00, 0x00, /* 72 */
+ 0x00, 0x00, 0x00, 0x00, /* 76 */
+ 0x00, 0x00, 0x00, 0x00, /* 80 */
+ 0x00, 0x00, 0x00, 0x00, /* 84 */
+ 0x00, 0x00, 0x00, 0x00, /* 88 */
+ 0x00, 0x00, 0x00, 0x00, /* 92 */
+ 0x00, 0x00, 0x00, 0x00, /* 96 */
+ 0x00, 0x00, 0x02, /* 100 */
+};
+
+/*
+ * read from the aic3x register space. Only use for this function is if
+ * wanting to read volatile bits from those registers that has both read-only
+ * and read/write bits. All other cases should use snd_soc_read.
+ */
+static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg,
+ u8 *value)
+{
+ u8 *cache = codec->reg_cache;
+
+ if (codec->cache_only)
+ return -EINVAL;
+ if (reg >= AIC3X_CACHEREGNUM)
+ return -1;
+
+ *value = codec->hw_read(codec, reg);
+ cache[reg] = *value;
+
+ return 0;
+}
+
+#define SOC_DAPM_SINGLE_AIC3X(xname, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw_aic3x, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, mask, invert) }
+
+/*
+ * All input lines are connected when !0xf and disconnected with 0xf bit field,
+ * so we have to use specific dapm_put call for input mixer
+ */
+static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+ unsigned short val, val_mask;
+ int ret;
+ struct snd_soc_dapm_path *path;
+ int found = 0;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+
+ mask = 0xf;
+ if (val)
+ val = mask;
+
+ if (invert)
+ val = mask - val;
+ val_mask = mask << shift;
+ val = val << shift;
+
+ mutex_lock(&widget->codec->mutex);
+
+ if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) {
+ /* find dapm widget path assoc with kcontrol */
+ list_for_each_entry(path, &widget->dapm->card->paths, list) {
+ if (path->kcontrol != kcontrol)
+ continue;
+
+ /* found, now check type */
+ found = 1;
+ if (val)
+ /* new connection */
+ path->connect = invert ? 0 : 1;
+ else
+ /* old connection must be powered down */
+ path->connect = invert ? 1 : 0;
+ break;
+ }
+
+ if (found)
+ snd_soc_dapm_sync(widget->dapm);
+ }
+
+ ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
+
+ mutex_unlock(&widget->codec->mutex);
+ return ret;
+}
+
+static const char *aic3x_left_dac_mux[] = { "DAC_L1", "DAC_L3", "DAC_L2" };
+static const char *aic3x_right_dac_mux[] = { "DAC_R1", "DAC_R3", "DAC_R2" };
+static const char *aic3x_left_hpcom_mux[] =
+ { "differential of HPLOUT", "constant VCM", "single-ended" };
+static const char *aic3x_right_hpcom_mux[] =
+ { "differential of HPROUT", "constant VCM", "single-ended",
+ "differential of HPLCOM", "external feedback" };
+static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" };
+static const char *aic3x_adc_hpf[] =
+ { "Disabled", "0.0045xFs", "0.0125xFs", "0.025xFs" };
+
+#define LDAC_ENUM 0
+#define RDAC_ENUM 1
+#define LHPCOM_ENUM 2
+#define RHPCOM_ENUM 3
+#define LINE1L_ENUM 4
+#define LINE1R_ENUM 5
+#define LINE2L_ENUM 6
+#define LINE2R_ENUM 7
+#define ADC_HPF_ENUM 8
+
+static const struct soc_enum aic3x_enum[] = {
+ SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux),
+ SOC_ENUM_SINGLE(DAC_LINE_MUX, 4, 3, aic3x_right_dac_mux),
+ SOC_ENUM_SINGLE(HPLCOM_CFG, 4, 3, aic3x_left_hpcom_mux),
+ SOC_ENUM_SINGLE(HPRCOM_CFG, 3, 5, aic3x_right_hpcom_mux),
+ SOC_ENUM_SINGLE(LINE1L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+ SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+ SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+ SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+ SOC_ENUM_DOUBLE(AIC3X_CODEC_DFILT_CTRL, 6, 4, 4, aic3x_adc_hpf),
+};
+
+/*
+ * DAC digital volumes. From -63.5 to 0 dB in 0.5 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(dac_tlv, -6350, 50, 0);
+/* ADC PGA gain volumes. From 0 to 59.5 dB in 0.5 dB steps */
+static DECLARE_TLV_DB_SCALE(adc_tlv, 0, 50, 0);
+/*
+ * Output stage volumes. From -78.3 to 0 dB. Muted below -78.3 dB.
+ * Step size is approximately 0.5 dB over most of the scale but increasing
+ * near the very low levels.
+ * Define dB scale so that it is mostly correct for range about -55 to 0 dB
+ * but having increasing dB difference below that (and where it doesn't count
+ * so much). This setting shows -50 dB (actual is -50.3 dB) for register
+ * value 100 and -58.5 dB (actual is -78.3 dB) for register value 117.
+ */
+static DECLARE_TLV_DB_SCALE(output_stage_tlv, -5900, 50, 1);
+
+static const struct snd_kcontrol_new aic3x_snd_controls[] = {
+ /* Output */
+ SOC_DOUBLE_R_TLV("PCM Playback Volume",
+ LDAC_VOL, RDAC_VOL, 0, 0x7f, 1, dac_tlv),
+
+ /*
+ * Output controls that map to output mixer switches. Note these are
+ * only for swapped L-to-R and R-to-L routes. See below stereo controls
+ * for direct L-to-L and R-to-R routes.
+ */
+ SOC_SINGLE_TLV("Left Line Mixer Line2R Bypass Volume",
+ LINE2R_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Left Line Mixer PGAR Bypass Volume",
+ PGAR_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Left Line Mixer DACR1 Playback Volume",
+ DACR1_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv),
+
+ SOC_SINGLE_TLV("Right Line Mixer Line2L Bypass Volume",
+ LINE2L_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Right Line Mixer PGAL Bypass Volume",
+ PGAL_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Right Line Mixer DACL1 Playback Volume",
+ DACL1_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv),
+
+ SOC_SINGLE_TLV("Left HP Mixer Line2R Bypass Volume",
+ LINE2R_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Left HP Mixer PGAR Bypass Volume",
+ PGAR_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Left HP Mixer DACR1 Playback Volume",
+ DACR1_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv),
+
+ SOC_SINGLE_TLV("Right HP Mixer Line2L Bypass Volume",
+ LINE2L_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Right HP Mixer PGAL Bypass Volume",
+ PGAL_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Right HP Mixer DACL1 Playback Volume",
+ DACL1_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv),
+
+ SOC_SINGLE_TLV("Left HPCOM Mixer Line2R Bypass Volume",
+ LINE2R_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Left HPCOM Mixer PGAR Bypass Volume",
+ PGAR_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Left HPCOM Mixer DACR1 Playback Volume",
+ DACR1_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv),
+
+ SOC_SINGLE_TLV("Right HPCOM Mixer Line2L Bypass Volume",
+ LINE2L_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Right HPCOM Mixer PGAL Bypass Volume",
+ PGAL_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("Right HPCOM Mixer DACL1 Playback Volume",
+ DACL1_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv),
+
+ /* Stereo output controls for direct L-to-L and R-to-R routes */
+ SOC_DOUBLE_R_TLV("Line Line2 Bypass Volume",
+ LINE2L_2_LLOPM_VOL, LINE2R_2_RLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("Line PGA Bypass Volume",
+ PGAL_2_LLOPM_VOL, PGAR_2_RLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("Line DAC Playback Volume",
+ DACL1_2_LLOPM_VOL, DACR1_2_RLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+
+ SOC_DOUBLE_R_TLV("Mono Line2 Bypass Volume",
+ LINE2L_2_MONOLOPM_VOL, LINE2R_2_MONOLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("Mono PGA Bypass Volume",
+ PGAL_2_MONOLOPM_VOL, PGAR_2_MONOLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("Mono DAC Playback Volume",
+ DACL1_2_MONOLOPM_VOL, DACR1_2_MONOLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+
+ SOC_DOUBLE_R_TLV("HP Line2 Bypass Volume",
+ LINE2L_2_HPLOUT_VOL, LINE2R_2_HPROUT_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("HP PGA Bypass Volume",
+ PGAL_2_HPLOUT_VOL, PGAR_2_HPROUT_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("HP DAC Playback Volume",
+ DACL1_2_HPLOUT_VOL, DACR1_2_HPROUT_VOL,
+ 0, 118, 1, output_stage_tlv),
+
+ SOC_DOUBLE_R_TLV("HPCOM Line2 Bypass Volume",
+ LINE2L_2_HPLCOM_VOL, LINE2R_2_HPRCOM_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("HPCOM PGA Bypass Volume",
+ PGAL_2_HPLCOM_VOL, PGAR_2_HPRCOM_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("HPCOM DAC Playback Volume",
+ DACL1_2_HPLCOM_VOL, DACR1_2_HPRCOM_VOL,
+ 0, 118, 1, output_stage_tlv),
+
+ /* Output pin mute controls */
+ SOC_DOUBLE_R("Line Playback Switch", LLOPM_CTRL, RLOPM_CTRL, 3,
+ 0x01, 0),
+ SOC_SINGLE("Mono Playback Switch", MONOLOPM_CTRL, 3, 0x01, 0),
+ SOC_DOUBLE_R("HP Playback Switch", HPLOUT_CTRL, HPROUT_CTRL, 3,
+ 0x01, 0),
+ SOC_DOUBLE_R("HPCOM Playback Switch", HPLCOM_CTRL, HPRCOM_CTRL, 3,
+ 0x01, 0),
+
+ /*
+ * Note: enable Automatic input Gain Controller with care. It can
+ * adjust PGA to max value when ADC is on and will never go back.
+ */
+ SOC_DOUBLE_R("AGC Switch", LAGC_CTRL_A, RAGC_CTRL_A, 7, 0x01, 0),
+
+ /* Input */
+ SOC_DOUBLE_R_TLV("PGA Capture Volume", LADC_VOL, RADC_VOL,
+ 0, 119, 0, adc_tlv),
+ SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1),
+
+ SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]),
+};
+
+/*
+ * Class-D amplifier gain. From 0 to 18 dB in 6 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(classd_amp_tlv, 0, 600, 0);
+
+static const struct snd_kcontrol_new aic3x_classd_amp_gain_ctrl =
+ SOC_DOUBLE_TLV("Class-D Amplifier Gain", CLASSD_CTRL, 6, 4, 3, 0, classd_amp_tlv);
+
+/* Left DAC Mux */
+static const struct snd_kcontrol_new aic3x_left_dac_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LDAC_ENUM]);
+
+/* Right DAC Mux */
+static const struct snd_kcontrol_new aic3x_right_dac_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[RDAC_ENUM]);
+
+/* Left HPCOM Mux */
+static const struct snd_kcontrol_new aic3x_left_hpcom_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LHPCOM_ENUM]);
+
+/* Right HPCOM Mux */
+static const struct snd_kcontrol_new aic3x_right_hpcom_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[RHPCOM_ENUM]);
+
+/* Left Line Mixer */
+static const struct snd_kcontrol_new aic3x_left_line_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_LLOPM_VOL, 7, 1, 0),
+};
+
+/* Right Line Mixer */
+static const struct snd_kcontrol_new aic3x_right_line_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_RLOPM_VOL, 7, 1, 0),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new aic3x_mono_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_MONOLOPM_VOL, 7, 1, 0),
+};
+
+/* Left HP Mixer */
+static const struct snd_kcontrol_new aic3x_left_hp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPLOUT_VOL, 7, 1, 0),
+};
+
+/* Right HP Mixer */
+static const struct snd_kcontrol_new aic3x_right_hp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPROUT_VOL, 7, 1, 0),
+};
+
+/* Left HPCOM Mixer */
+static const struct snd_kcontrol_new aic3x_left_hpcom_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPLCOM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPLCOM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPLCOM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPLCOM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPLCOM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPLCOM_VOL, 7, 1, 0),
+};
+
+/* Right HPCOM Mixer */
+static const struct snd_kcontrol_new aic3x_right_hpcom_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPRCOM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPRCOM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPRCOM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPRCOM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPRCOM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPRCOM_VOL, 7, 1, 0),
+};
+
+/* Left PGA Mixer */
+static const struct snd_kcontrol_new aic3x_left_pga_mixer_controls[] = {
+ SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_LADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_LADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Line2L Switch", LINE2L_2_LADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_LADC_CTRL, 4, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_LADC_CTRL, 0, 1, 1),
+};
+
+/* Right PGA Mixer */
+static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = {
+ SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_RADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_RADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Line2R Switch", LINE2R_2_RADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_RADC_CTRL, 4, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_RADC_CTRL, 0, 1, 1),
+};
+
+/* Left Line1 Mux */
+static const struct snd_kcontrol_new aic3x_left_line1_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_ENUM]);
+
+/* Right Line1 Mux */
+static const struct snd_kcontrol_new aic3x_right_line1_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_ENUM]);
+
+/* Left Line2 Mux */
+static const struct snd_kcontrol_new aic3x_left_line2_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE2L_ENUM]);
+
+/* Right Line2 Mux */
+static const struct snd_kcontrol_new aic3x_right_line2_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE2R_ENUM]);
+
+static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
+ /* Left DAC to Left Outputs */
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", DAC_PWR, 7, 0),
+ SND_SOC_DAPM_MUX("Left DAC Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_dac_mux_controls),
+ SND_SOC_DAPM_MUX("Left HPCOM Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_hpcom_mux_controls),
+ SND_SOC_DAPM_PGA("Left Line Out", LLOPM_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left HP Out", HPLOUT_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left HP Com", HPLCOM_CTRL, 0, 0, NULL, 0),
+
+ /* Right DAC to Right Outputs */
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", DAC_PWR, 6, 0),
+ SND_SOC_DAPM_MUX("Right DAC Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_dac_mux_controls),
+ SND_SOC_DAPM_MUX("Right HPCOM Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_hpcom_mux_controls),
+ SND_SOC_DAPM_PGA("Right Line Out", RLOPM_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right HP Out", HPROUT_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right HP Com", HPRCOM_CTRL, 0, 0, NULL, 0),
+
+ /* Mono Output */
+ SND_SOC_DAPM_PGA("Mono Out", MONOLOPM_CTRL, 0, 0, NULL, 0),
+
+ /* Inputs to Left ADC */
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", LINE1L_2_LADC_CTRL, 2, 0),
+ SND_SOC_DAPM_MIXER("Left PGA Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_pga_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_pga_mixer_controls)),
+ SND_SOC_DAPM_MUX("Left Line1L Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_line1_mux_controls),
+ SND_SOC_DAPM_MUX("Left Line1R Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_line1_mux_controls),
+ SND_SOC_DAPM_MUX("Left Line2L Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_line2_mux_controls),
+
+ /* Inputs to Right ADC */
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture",
+ LINE1R_2_RADC_CTRL, 2, 0),
+ SND_SOC_DAPM_MIXER("Right PGA Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_pga_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_pga_mixer_controls)),
+ SND_SOC_DAPM_MUX("Right Line1L Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_line1_mux_controls),
+ SND_SOC_DAPM_MUX("Right Line1R Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_line1_mux_controls),
+ SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_line2_mux_controls),
+
+ /*
+ * Not a real mic bias widget but similar function. This is for dynamic
+ * control of GPIO1 digital mic modulator clock output function when
+ * using digital mic.
+ */
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "GPIO1 dmic modclk",
+ AIC3X_GPIO1_REG, 4, 0xf,
+ AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK,
+ AIC3X_GPIO1_FUNC_DISABLED),
+
+ /*
+ * Also similar function like mic bias. Selects digital mic with
+ * configurable oversampling rate instead of ADC converter.
+ */
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 128",
+ AIC3X_ASD_INTF_CTRLA, 0, 3, 1, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 64",
+ AIC3X_ASD_INTF_CTRLA, 0, 3, 2, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 32",
+ AIC3X_ASD_INTF_CTRLA, 0, 3, 3, 0),
+
+ /* Mic Bias */
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2V",
+ MICBIAS_CTRL, 6, 3, 1, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2.5V",
+ MICBIAS_CTRL, 6, 3, 2, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias AVDD",
+ MICBIAS_CTRL, 6, 3, 3, 0),
+
+ /* Output mixers */
+ SND_SOC_DAPM_MIXER("Left Line Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_line_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_line_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Line Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_line_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_line_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_mono_mixer_controls[0],
+ ARRAY_SIZE(aic3x_mono_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_hp_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_hp_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_hp_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_hp_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Left HPCOM Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_hpcom_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_hpcom_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right HPCOM Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_hpcom_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_hpcom_mixer_controls)),
+
+ SND_SOC_DAPM_OUTPUT("LLOUT"),
+ SND_SOC_DAPM_OUTPUT("RLOUT"),
+ SND_SOC_DAPM_OUTPUT("MONO_LOUT"),
+ SND_SOC_DAPM_OUTPUT("HPLOUT"),
+ SND_SOC_DAPM_OUTPUT("HPROUT"),
+ SND_SOC_DAPM_OUTPUT("HPLCOM"),
+ SND_SOC_DAPM_OUTPUT("HPRCOM"),
+
+ SND_SOC_DAPM_INPUT("MIC3L"),
+ SND_SOC_DAPM_INPUT("MIC3R"),
+ SND_SOC_DAPM_INPUT("LINE1L"),
+ SND_SOC_DAPM_INPUT("LINE1R"),
+ SND_SOC_DAPM_INPUT("LINE2L"),
+ SND_SOC_DAPM_INPUT("LINE2R"),
+
+ /*
+ * Virtual output pin to detection block inside codec. This can be
+ * used to keep codec bias on if gpio or detection features are needed.
+ * Force pin on or construct a path with an input jack and mic bias
+ * widgets.
+ */
+ SND_SOC_DAPM_OUTPUT("Detection"),
+};
+
+static const struct snd_soc_dapm_widget aic3007_dapm_widgets[] = {
+ /* Class-D outputs */
+ SND_SOC_DAPM_PGA("Left Class-D Out", CLASSD_CTRL, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Class-D Out", CLASSD_CTRL, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("SPOP"),
+ SND_SOC_DAPM_OUTPUT("SPOM"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* Left Input */
+ {"Left Line1L Mux", "single-ended", "LINE1L"},
+ {"Left Line1L Mux", "differential", "LINE1L"},
+
+ {"Left Line2L Mux", "single-ended", "LINE2L"},
+ {"Left Line2L Mux", "differential", "LINE2L"},
+
+ {"Left PGA Mixer", "Line1L Switch", "Left Line1L Mux"},
+ {"Left PGA Mixer", "Line1R Switch", "Left Line1R Mux"},
+ {"Left PGA Mixer", "Line2L Switch", "Left Line2L Mux"},
+ {"Left PGA Mixer", "Mic3L Switch", "MIC3L"},
+ {"Left PGA Mixer", "Mic3R Switch", "MIC3R"},
+
+ {"Left ADC", NULL, "Left PGA Mixer"},
+ {"Left ADC", NULL, "GPIO1 dmic modclk"},
+
+ /* Right Input */
+ {"Right Line1R Mux", "single-ended", "LINE1R"},
+ {"Right Line1R Mux", "differential", "LINE1R"},
+
+ {"Right Line2R Mux", "single-ended", "LINE2R"},
+ {"Right Line2R Mux", "differential", "LINE2R"},
+
+ {"Right PGA Mixer", "Line1L Switch", "Right Line1L Mux"},
+ {"Right PGA Mixer", "Line1R Switch", "Right Line1R Mux"},
+ {"Right PGA Mixer", "Line2R Switch", "Right Line2R Mux"},
+ {"Right PGA Mixer", "Mic3L Switch", "MIC3L"},
+ {"Right PGA Mixer", "Mic3R Switch", "MIC3R"},
+
+ {"Right ADC", NULL, "Right PGA Mixer"},
+ {"Right ADC", NULL, "GPIO1 dmic modclk"},
+
+ /*
+ * Logical path between digital mic enable and GPIO1 modulator clock
+ * output function
+ */
+ {"GPIO1 dmic modclk", NULL, "DMic Rate 128"},
+ {"GPIO1 dmic modclk", NULL, "DMic Rate 64"},
+ {"GPIO1 dmic modclk", NULL, "DMic Rate 32"},
+
+ /* Left DAC Output */
+ {"Left DAC Mux", "DAC_L1", "Left DAC"},
+ {"Left DAC Mux", "DAC_L2", "Left DAC"},
+ {"Left DAC Mux", "DAC_L3", "Left DAC"},
+
+ /* Right DAC Output */
+ {"Right DAC Mux", "DAC_R1", "Right DAC"},
+ {"Right DAC Mux", "DAC_R2", "Right DAC"},
+ {"Right DAC Mux", "DAC_R3", "Right DAC"},
+
+ /* Left Line Output */
+ {"Left Line Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+ {"Left Line Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+ {"Left Line Mixer", "DACL1 Switch", "Left DAC Mux"},
+ {"Left Line Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+ {"Left Line Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+ {"Left Line Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+ {"Left Line Out", NULL, "Left Line Mixer"},
+ {"Left Line Out", NULL, "Left DAC Mux"},
+ {"LLOUT", NULL, "Left Line Out"},
+
+ /* Right Line Output */
+ {"Right Line Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+ {"Right Line Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+ {"Right Line Mixer", "DACL1 Switch", "Left DAC Mux"},
+ {"Right Line Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+ {"Right Line Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+ {"Right Line Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+ {"Right Line Out", NULL, "Right Line Mixer"},
+ {"Right Line Out", NULL, "Right DAC Mux"},
+ {"RLOUT", NULL, "Right Line Out"},
+
+ /* Mono Output */
+ {"Mono Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+ {"Mono Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+ {"Mono Mixer", "DACL1 Switch", "Left DAC Mux"},
+ {"Mono Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+ {"Mono Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+ {"Mono Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+ {"Mono Out", NULL, "Mono Mixer"},
+ {"MONO_LOUT", NULL, "Mono Out"},
+
+ /* Left HP Output */
+ {"Left HP Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+ {"Left HP Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+ {"Left HP Mixer", "DACL1 Switch", "Left DAC Mux"},
+ {"Left HP Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+ {"Left HP Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+ {"Left HP Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+ {"Left HP Out", NULL, "Left HP Mixer"},
+ {"Left HP Out", NULL, "Left DAC Mux"},
+ {"HPLOUT", NULL, "Left HP Out"},
+
+ /* Right HP Output */
+ {"Right HP Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+ {"Right HP Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+ {"Right HP Mixer", "DACL1 Switch", "Left DAC Mux"},
+ {"Right HP Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+ {"Right HP Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+ {"Right HP Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+ {"Right HP Out", NULL, "Right HP Mixer"},
+ {"Right HP Out", NULL, "Right DAC Mux"},
+ {"HPROUT", NULL, "Right HP Out"},
+
+ /* Left HPCOM Output */
+ {"Left HPCOM Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+ {"Left HPCOM Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+ {"Left HPCOM Mixer", "DACL1 Switch", "Left DAC Mux"},
+ {"Left HPCOM Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+ {"Left HPCOM Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+ {"Left HPCOM Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+ {"Left HPCOM Mux", "differential of HPLOUT", "Left HP Mixer"},
+ {"Left HPCOM Mux", "constant VCM", "Left HPCOM Mixer"},
+ {"Left HPCOM Mux", "single-ended", "Left HPCOM Mixer"},
+ {"Left HP Com", NULL, "Left HPCOM Mux"},
+ {"HPLCOM", NULL, "Left HP Com"},
+
+ /* Right HPCOM Output */
+ {"Right HPCOM Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+ {"Right HPCOM Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+ {"Right HPCOM Mixer", "DACL1 Switch", "Left DAC Mux"},
+ {"Right HPCOM Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+ {"Right HPCOM Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+ {"Right HPCOM Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+ {"Right HPCOM Mux", "differential of HPROUT", "Right HP Mixer"},
+ {"Right HPCOM Mux", "constant VCM", "Right HPCOM Mixer"},
+ {"Right HPCOM Mux", "single-ended", "Right HPCOM Mixer"},
+ {"Right HPCOM Mux", "differential of HPLCOM", "Left HPCOM Mixer"},
+ {"Right HPCOM Mux", "external feedback", "Right HPCOM Mixer"},
+ {"Right HP Com", NULL, "Right HPCOM Mux"},
+ {"HPRCOM", NULL, "Right HP Com"},
+};
+
+static const struct snd_soc_dapm_route intercon_3007[] = {
+ /* Class-D outputs */
+ {"Left Class-D Out", NULL, "Left Line Out"},
+ {"Right Class-D Out", NULL, "Left Line Out"},
+ {"SPOP", NULL, "Left Class-D Out"},
+ {"SPOM", NULL, "Right Class-D Out"},
+};
+
+static int aic3x_add_widgets(struct snd_soc_codec *codec)
+{
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, aic3x_dapm_widgets,
+ ARRAY_SIZE(aic3x_dapm_widgets));
+
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ if (aic3x->model == AIC3X_MODEL_3007) {
+ snd_soc_dapm_new_controls(dapm, aic3007_dapm_widgets,
+ ARRAY_SIZE(aic3007_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon_3007,
+ ARRAY_SIZE(intercon_3007));
+ }
+
+ return 0;
+}
+
+static int aic3x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec =rtd->codec;
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0;
+ u8 data, j, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1;
+ u16 d, pll_d = 1;
+ u8 reg;
+ int clk;
+
+ /* select data word length */
+ data = snd_soc_read(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4));
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ data |= (0x01 << 4);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ data |= (0x02 << 4);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ data |= (0x03 << 4);
+ break;
+ }
+ snd_soc_write(codec, AIC3X_ASD_INTF_CTRLB, data);
+
+ /* Fsref can be 44100 or 48000 */
+ fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000;
+
+ /* Try to find a value for Q which allows us to bypass the PLL and
+ * generate CODEC_CLK directly. */
+ for (pll_q = 2; pll_q < 18; pll_q++)
+ if (aic3x->sysclk / (128 * pll_q) == fsref) {
+ bypass_pll = 1;
+ break;
+ }
+
+ if (bypass_pll) {
+ pll_q &= 0xf;
+ snd_soc_write(codec, AIC3X_PLL_PROGA_REG, pll_q << PLLQ_SHIFT);
+ snd_soc_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_CLKDIV);
+ /* disable PLL if it is bypassed */
+ reg = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
+ snd_soc_write(codec, AIC3X_PLL_PROGA_REG, reg & ~PLL_ENABLE);
+
+ } else {
+ snd_soc_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_PLLDIV);
+ /* enable PLL when it is used */
+ reg = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
+ snd_soc_write(codec, AIC3X_PLL_PROGA_REG, reg | PLL_ENABLE);
+ }
+
+ /* Route Left DAC to left channel input and
+ * right DAC to right channel input */
+ data = (LDAC2LCH | RDAC2RCH);
+ data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000;
+ if (params_rate(params) >= 64000)
+ data |= DUAL_RATE_MODE;
+ snd_soc_write(codec, AIC3X_CODEC_DATAPATH_REG, data);
+
+ /* codec sample rate select */
+ data = (fsref * 20) / params_rate(params);
+ if (params_rate(params) < 64000)
+ data /= 2;
+ data /= 5;
+ data -= 2;
+ data |= (data << 4);
+ snd_soc_write(codec, AIC3X_SAMPLE_RATE_SEL_REG, data);
+
+ if (bypass_pll)
+ return 0;
+
+ /* Use PLL, compute appropriate setup for j, d, r and p, the closest
+ * one wins the game. Try with d==0 first, next with d!=0.
+ * Constraints for j are according to the datasheet.
+ * The sysclk is divided by 1000 to prevent integer overflows.
+ */
+
+ codec_clk = (2048 * fsref) / (aic3x->sysclk / 1000);
+
+ for (r = 1; r <= 16; r++)
+ for (p = 1; p <= 8; p++) {
+ for (j = 4; j <= 55; j++) {
+ /* This is actually 1000*((j+(d/10000))*r)/p
+ * The term had to be converted to get
+ * rid of the division by 10000; d = 0 here
+ */
+ int tmp_clk = (1000 * j * r) / p;
+
+ /* Check whether this values get closer than
+ * the best ones we had before
+ */
+ if (abs(codec_clk - tmp_clk) <
+ abs(codec_clk - last_clk)) {
+ pll_j = j; pll_d = 0;
+ pll_r = r; pll_p = p;
+ last_clk = tmp_clk;
+ }
+
+ /* Early exit for exact matches */
+ if (tmp_clk == codec_clk)
+ goto found;
+ }
+ }
+
+ /* try with d != 0 */
+ for (p = 1; p <= 8; p++) {
+ j = codec_clk * p / 1000;
+
+ if (j < 4 || j > 11)
+ continue;
+
+ /* do not use codec_clk here since we'd loose precision */
+ d = ((2048 * p * fsref) - j * aic3x->sysclk)
+ * 100 / (aic3x->sysclk/100);
+
+ clk = (10000 * j + d) / (10 * p);
+
+ /* check whether this values get closer than the best
+ * ones we had before */
+ if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) {
+ pll_j = j; pll_d = d; pll_r = 1; pll_p = p;
+ last_clk = clk;
+ }
+
+ /* Early exit for exact matches */
+ if (clk == codec_clk)
+ goto found;
+ }
+
+ if (last_clk == 0) {
+ printk(KERN_ERR "%s(): unable to setup PLL\n", __func__);
+ return -EINVAL;
+ }
+
+found:
+ data = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
+ snd_soc_write(codec, AIC3X_PLL_PROGA_REG,
+ data | (pll_p << PLLP_SHIFT));
+ snd_soc_write(codec, AIC3X_OVRF_STATUS_AND_PLLR_REG,
+ pll_r << PLLR_SHIFT);
+ snd_soc_write(codec, AIC3X_PLL_PROGB_REG, pll_j << PLLJ_SHIFT);
+ snd_soc_write(codec, AIC3X_PLL_PROGC_REG,
+ (pll_d >> 6) << PLLD_MSB_SHIFT);
+ snd_soc_write(codec, AIC3X_PLL_PROGD_REG,
+ (pll_d & 0x3F) << PLLD_LSB_SHIFT);
+
+ return 0;
+}
+
+static int aic3x_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 ldac_reg = snd_soc_read(codec, LDAC_VOL) & ~MUTE_ON;
+ u8 rdac_reg = snd_soc_read(codec, RDAC_VOL) & ~MUTE_ON;
+
+ if (mute) {
+ snd_soc_write(codec, LDAC_VOL, ldac_reg | MUTE_ON);
+ snd_soc_write(codec, RDAC_VOL, rdac_reg | MUTE_ON);
+ } else {
+ snd_soc_write(codec, LDAC_VOL, ldac_reg);
+ snd_soc_write(codec, RDAC_VOL, rdac_reg);
+ }
+
+ return 0;
+}
+
+static int aic3x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+
+ aic3x->sysclk = freq;
+ return 0;
+}
+
+static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ u8 iface_areg, iface_breg;
+ int delay = 0;
+
+ iface_areg = snd_soc_read(codec, AIC3X_ASD_INTF_CTRLA) & 0x3f;
+ iface_breg = snd_soc_read(codec, AIC3X_ASD_INTF_CTRLB) & 0x3f;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aic3x->master = 1;
+ iface_areg |= BIT_CLK_MASTER | WORD_CLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ aic3x->master = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * match both interface format and signal polarities since they
+ * are fixed
+ */
+ switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
+ SND_SOC_DAIFMT_INV_MASK)) {
+ case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF):
+ break;
+ case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF):
+ delay = 1;
+ case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF):
+ iface_breg |= (0x01 << 6);
+ break;
+ case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF):
+ iface_breg |= (0x02 << 6);
+ break;
+ case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF):
+ iface_breg |= (0x03 << 6);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ snd_soc_write(codec, AIC3X_ASD_INTF_CTRLA, iface_areg);
+ snd_soc_write(codec, AIC3X_ASD_INTF_CTRLB, iface_breg);
+ snd_soc_write(codec, AIC3X_ASD_INTF_CTRLC, delay);
+
+ return 0;
+}
+
+static int aic3x_init_3007(struct snd_soc_codec *codec)
+{
+ u8 tmp1, tmp2, *cache = codec->reg_cache;
+
+ /*
+ * There is no need to cache writes to undocumented page 0xD but
+ * respective page 0 register cache entries must be preserved
+ */
+ tmp1 = cache[0xD];
+ tmp2 = cache[0x8];
+ /* Class-D speaker driver init; datasheet p. 46 */
+ snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x0D);
+ snd_soc_write(codec, 0xD, 0x0D);
+ snd_soc_write(codec, 0x8, 0x5C);
+ snd_soc_write(codec, 0x8, 0x5D);
+ snd_soc_write(codec, 0x8, 0x5C);
+ snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x00);
+ cache[0xD] = tmp1;
+ cache[0x8] = tmp2;
+
+ return 0;
+}
+
+static int aic3x_regulator_event(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct aic3x_disable_nb *disable_nb =
+ container_of(nb, struct aic3x_disable_nb, nb);
+ struct aic3x_priv *aic3x = disable_nb->aic3x;
+
+ if (event & REGULATOR_EVENT_DISABLE) {
+ /*
+ * Put codec to reset and require cache sync as at least one
+ * of the supplies was disabled
+ */
+ if (gpio_is_valid(aic3x->gpio_reset))
+ gpio_set_value(aic3x->gpio_reset, 0);
+ aic3x->codec->cache_sync = 1;
+ }
+
+ return 0;
+}
+
+static int aic3x_set_power(struct snd_soc_codec *codec, int power)
+{
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ int i, ret;
+ u8 *cache = codec->reg_cache;
+
+ if (power) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies),
+ aic3x->supplies);
+ if (ret)
+ goto out;
+ aic3x->power = 1;
+ /*
+ * Reset release and cache sync is necessary only if some
+ * supply was off or if there were cached writes
+ */
+ if (!codec->cache_sync)
+ goto out;
+
+ if (gpio_is_valid(aic3x->gpio_reset)) {
+ udelay(1);
+ gpio_set_value(aic3x->gpio_reset, 1);
+ }
+
+ /* Sync reg_cache with the hardware */
+ codec->cache_only = 0;
+ for (i = AIC3X_SAMPLE_RATE_SEL_REG; i < ARRAY_SIZE(aic3x_reg); i++)
+ snd_soc_write(codec, i, cache[i]);
+ if (aic3x->model == AIC3X_MODEL_3007)
+ aic3x_init_3007(codec);
+ codec->cache_sync = 0;
+ } else {
+ /*
+ * Do soft reset to this codec instance in order to clear
+ * possible VDD leakage currents in case the supply regulators
+ * remain on
+ */
+ snd_soc_write(codec, AIC3X_RESET, SOFT_RESET);
+ codec->cache_sync = 1;
+ aic3x->power = 0;
+ /* HW writes are needless when bias is off */
+ codec->cache_only = 1;
+ ret = regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies),
+ aic3x->supplies);
+ }
+out:
+ return ret;
+}
+
+static int aic3x_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ u8 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY &&
+ aic3x->master) {
+ /* enable pll */
+ reg = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
+ snd_soc_write(codec, AIC3X_PLL_PROGA_REG,
+ reg | PLL_ENABLE);
+ }
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (!aic3x->power)
+ aic3x_set_power(codec, 1);
+ if (codec->dapm.bias_level == SND_SOC_BIAS_PREPARE &&
+ aic3x->master) {
+ /* disable pll */
+ reg = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
+ snd_soc_write(codec, AIC3X_PLL_PROGA_REG,
+ reg & ~PLL_ENABLE);
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ if (aic3x->power)
+ aic3x_set_power(codec, 0);
+ break;
+ }
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state)
+{
+ u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG;
+ u8 bit = gpio ? 3: 0;
+ u8 val = snd_soc_read(codec, reg) & ~(1 << bit);
+ snd_soc_write(codec, reg, val | (!!state << bit));
+}
+EXPORT_SYMBOL_GPL(aic3x_set_gpio);
+
+int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio)
+{
+ u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG;
+ u8 val = 0, bit = gpio ? 2 : 1;
+
+ aic3x_read(codec, reg, &val);
+ return (val >> bit) & 1;
+}
+EXPORT_SYMBOL_GPL(aic3x_get_gpio);
+
+void aic3x_set_headset_detection(struct snd_soc_codec *codec, int detect,
+ int headset_debounce, int button_debounce)
+{
+ u8 val;
+
+ val = ((detect & AIC3X_HEADSET_DETECT_MASK)
+ << AIC3X_HEADSET_DETECT_SHIFT) |
+ ((headset_debounce & AIC3X_HEADSET_DEBOUNCE_MASK)
+ << AIC3X_HEADSET_DEBOUNCE_SHIFT) |
+ ((button_debounce & AIC3X_BUTTON_DEBOUNCE_MASK)
+ << AIC3X_BUTTON_DEBOUNCE_SHIFT);
+
+ if (detect & AIC3X_HEADSET_DETECT_MASK)
+ val |= AIC3X_HEADSET_DETECT_ENABLED;
+
+ snd_soc_write(codec, AIC3X_HEADSET_DETECT_CTRL_A, val);
+}
+EXPORT_SYMBOL_GPL(aic3x_set_headset_detection);
+
+int aic3x_headset_detected(struct snd_soc_codec *codec)
+{
+ u8 val = 0;
+ aic3x_read(codec, AIC3X_HEADSET_DETECT_CTRL_B, &val);
+ return (val >> 4) & 1;
+}
+EXPORT_SYMBOL_GPL(aic3x_headset_detected);
+
+int aic3x_button_pressed(struct snd_soc_codec *codec)
+{
+ u8 val = 0;
+ aic3x_read(codec, AIC3X_HEADSET_DETECT_CTRL_B, &val);
+ return (val >> 5) & 1;
+}
+EXPORT_SYMBOL_GPL(aic3x_button_pressed);
+
+#define AIC3X_RATES SNDRV_PCM_RATE_8000_96000
+#define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops aic3x_dai_ops = {
+ .hw_params = aic3x_hw_params,
+ .digital_mute = aic3x_mute,
+ .set_sysclk = aic3x_set_dai_sysclk,
+ .set_fmt = aic3x_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver aic3x_dai = {
+ .name = "tlv320aic3x-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AIC3X_RATES,
+ .formats = AIC3X_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AIC3X_RATES,
+ .formats = AIC3X_FORMATS,},
+ .ops = &aic3x_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int aic3x_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int aic3x_resume(struct snd_soc_codec *codec)
+{
+ aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+/*
+ * initialise the AIC3X driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int aic3x_init(struct snd_soc_codec *codec)
+{
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ int reg;
+
+ snd_soc_write(codec, AIC3X_PAGE_SELECT, PAGE0_SELECT);
+ snd_soc_write(codec, AIC3X_RESET, SOFT_RESET);
+
+ /* DAC default volume and mute */
+ snd_soc_write(codec, LDAC_VOL, DEFAULT_VOL | MUTE_ON);
+ snd_soc_write(codec, RDAC_VOL, DEFAULT_VOL | MUTE_ON);
+
+ /* DAC to HP default volume and route to Output mixer */
+ snd_soc_write(codec, DACL1_2_HPLOUT_VOL, DEFAULT_VOL | ROUTE_ON);
+ snd_soc_write(codec, DACR1_2_HPROUT_VOL, DEFAULT_VOL | ROUTE_ON);
+ snd_soc_write(codec, DACL1_2_HPLCOM_VOL, DEFAULT_VOL | ROUTE_ON);
+ snd_soc_write(codec, DACR1_2_HPRCOM_VOL, DEFAULT_VOL | ROUTE_ON);
+ /* DAC to Line Out default volume and route to Output mixer */
+ snd_soc_write(codec, DACL1_2_LLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+ snd_soc_write(codec, DACR1_2_RLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+ /* DAC to Mono Line Out default volume and route to Output mixer */
+ snd_soc_write(codec, DACL1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+ snd_soc_write(codec, DACR1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+
+ /* unmute all outputs */
+ reg = snd_soc_read(codec, LLOPM_CTRL);
+ snd_soc_write(codec, LLOPM_CTRL, reg | UNMUTE);
+ reg = snd_soc_read(codec, RLOPM_CTRL);
+ snd_soc_write(codec, RLOPM_CTRL, reg | UNMUTE);
+ reg = snd_soc_read(codec, MONOLOPM_CTRL);
+ snd_soc_write(codec, MONOLOPM_CTRL, reg | UNMUTE);
+ reg = snd_soc_read(codec, HPLOUT_CTRL);
+ snd_soc_write(codec, HPLOUT_CTRL, reg | UNMUTE);
+ reg = snd_soc_read(codec, HPROUT_CTRL);
+ snd_soc_write(codec, HPROUT_CTRL, reg | UNMUTE);
+ reg = snd_soc_read(codec, HPLCOM_CTRL);
+ snd_soc_write(codec, HPLCOM_CTRL, reg | UNMUTE);
+ reg = snd_soc_read(codec, HPRCOM_CTRL);
+ snd_soc_write(codec, HPRCOM_CTRL, reg | UNMUTE);
+
+ /* ADC default volume and unmute */
+ snd_soc_write(codec, LADC_VOL, DEFAULT_GAIN);
+ snd_soc_write(codec, RADC_VOL, DEFAULT_GAIN);
+ /* By default route Line1 to ADC PGA mixer */
+ snd_soc_write(codec, LINE1L_2_LADC_CTRL, 0x0);
+ snd_soc_write(codec, LINE1R_2_RADC_CTRL, 0x0);
+
+ /* PGA to HP Bypass default volume, disconnect from Output Mixer */
+ snd_soc_write(codec, PGAL_2_HPLOUT_VOL, DEFAULT_VOL);
+ snd_soc_write(codec, PGAR_2_HPROUT_VOL, DEFAULT_VOL);
+ snd_soc_write(codec, PGAL_2_HPLCOM_VOL, DEFAULT_VOL);
+ snd_soc_write(codec, PGAR_2_HPRCOM_VOL, DEFAULT_VOL);
+ /* PGA to Line Out default volume, disconnect from Output Mixer */
+ snd_soc_write(codec, PGAL_2_LLOPM_VOL, DEFAULT_VOL);
+ snd_soc_write(codec, PGAR_2_RLOPM_VOL, DEFAULT_VOL);
+ /* PGA to Mono Line Out default volume, disconnect from Output Mixer */
+ snd_soc_write(codec, PGAL_2_MONOLOPM_VOL, DEFAULT_VOL);
+ snd_soc_write(codec, PGAR_2_MONOLOPM_VOL, DEFAULT_VOL);
+
+ /* Line2 to HP Bypass default volume, disconnect from Output Mixer */
+ snd_soc_write(codec, LINE2L_2_HPLOUT_VOL, DEFAULT_VOL);
+ snd_soc_write(codec, LINE2R_2_HPROUT_VOL, DEFAULT_VOL);
+ snd_soc_write(codec, LINE2L_2_HPLCOM_VOL, DEFAULT_VOL);
+ snd_soc_write(codec, LINE2R_2_HPRCOM_VOL, DEFAULT_VOL);
+ /* Line2 Line Out default volume, disconnect from Output Mixer */
+ snd_soc_write(codec, LINE2L_2_LLOPM_VOL, DEFAULT_VOL);
+ snd_soc_write(codec, LINE2R_2_RLOPM_VOL, DEFAULT_VOL);
+ /* Line2 to Mono Out default volume, disconnect from Output Mixer */
+ snd_soc_write(codec, LINE2L_2_MONOLOPM_VOL, DEFAULT_VOL);
+ snd_soc_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL);
+
+ if (aic3x->model == AIC3X_MODEL_3007) {
+ aic3x_init_3007(codec);
+ snd_soc_write(codec, CLASSD_CTRL, 0);
+ }
+
+ return 0;
+}
+
+static bool aic3x_is_shared_reset(struct aic3x_priv *aic3x)
+{
+ struct aic3x_priv *a;
+
+ list_for_each_entry(a, &reset_list, list) {
+ if (gpio_is_valid(aic3x->gpio_reset) &&
+ aic3x->gpio_reset == a->gpio_reset)
+ return true;
+ }
+
+ return false;
+}
+
+static int aic3x_probe(struct snd_soc_codec *codec)
+{
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ int ret, i;
+
+ INIT_LIST_HEAD(&aic3x->list);
+ codec->control_data = aic3x->control_data;
+ aic3x->codec = codec;
+ codec->dapm.idle_bias_off = 1;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, aic3x->control_type);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ if (gpio_is_valid(aic3x->gpio_reset) &&
+ !aic3x_is_shared_reset(aic3x)) {
+ ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset");
+ if (ret != 0)
+ goto err_gpio;
+ gpio_direction_output(aic3x->gpio_reset, 0);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
+ aic3x->supplies[i].supply = aic3x_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(aic3x->supplies),
+ aic3x->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ goto err_get;
+ }
+ for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) {
+ aic3x->disable_nb[i].nb.notifier_call = aic3x_regulator_event;
+ aic3x->disable_nb[i].aic3x = aic3x;
+ ret = regulator_register_notifier(aic3x->supplies[i].consumer,
+ &aic3x->disable_nb[i].nb);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to request regulator notifier: %d\n",
+ ret);
+ goto err_notif;
+ }
+ }
+
+ codec->cache_only = 1;
+ aic3x_init(codec);
+
+ if (aic3x->setup) {
+ /* setup GPIO functions */
+ snd_soc_write(codec, AIC3X_GPIO1_REG,
+ (aic3x->setup->gpio_func[0] & 0xf) << 4);
+ snd_soc_write(codec, AIC3X_GPIO2_REG,
+ (aic3x->setup->gpio_func[1] & 0xf) << 4);
+ }
+
+ snd_soc_add_controls(codec, aic3x_snd_controls,
+ ARRAY_SIZE(aic3x_snd_controls));
+ if (aic3x->model == AIC3X_MODEL_3007)
+ snd_soc_add_controls(codec, &aic3x_classd_amp_gain_ctrl, 1);
+
+ aic3x_add_widgets(codec);
+ list_add(&aic3x->list, &reset_list);
+
+ return 0;
+
+err_notif:
+ while (i--)
+ regulator_unregister_notifier(aic3x->supplies[i].consumer,
+ &aic3x->disable_nb[i].nb);
+ regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
+err_get:
+ if (gpio_is_valid(aic3x->gpio_reset) &&
+ !aic3x_is_shared_reset(aic3x))
+ gpio_free(aic3x->gpio_reset);
+err_gpio:
+ return ret;
+}
+
+static int aic3x_remove(struct snd_soc_codec *codec)
+{
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ list_del(&aic3x->list);
+ if (gpio_is_valid(aic3x->gpio_reset) &&
+ !aic3x_is_shared_reset(aic3x)) {
+ gpio_set_value(aic3x->gpio_reset, 0);
+ gpio_free(aic3x->gpio_reset);
+ }
+ for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
+ regulator_unregister_notifier(aic3x->supplies[i].consumer,
+ &aic3x->disable_nb[i].nb);
+ regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_aic3x = {
+ .set_bias_level = aic3x_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(aic3x_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = aic3x_reg,
+ .probe = aic3x_probe,
+ .remove = aic3x_remove,
+ .suspend = aic3x_suspend,
+ .resume = aic3x_resume,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * AIC3X 2 wire address can be up to 4 devices with device addresses
+ * 0x18, 0x19, 0x1A, 0x1B
+ */
+
+static const struct i2c_device_id aic3x_i2c_id[] = {
+ [AIC3X_MODEL_3X] = { "tlv320aic3x", 0 },
+ [AIC3X_MODEL_33] = { "tlv320aic33", 0 },
+ [AIC3X_MODEL_3007] = { "tlv320aic3007", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id);
+
+/*
+ * If the i2c layer weren't so broken, we could pass this kind of data
+ * around
+ */
+static int aic3x_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct aic3x_pdata *pdata = i2c->dev.platform_data;
+ struct aic3x_priv *aic3x;
+ int ret;
+ const struct i2c_device_id *tbl;
+
+ aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL);
+ if (aic3x == NULL) {
+ dev_err(&i2c->dev, "failed to create private data\n");
+ return -ENOMEM;
+ }
+
+ aic3x->control_data = i2c;
+ aic3x->control_type = SND_SOC_I2C;
+
+ i2c_set_clientdata(i2c, aic3x);
+ if (pdata) {
+ aic3x->gpio_reset = pdata->gpio_reset;
+ aic3x->setup = pdata->setup;
+ } else {
+ aic3x->gpio_reset = -1;
+ }
+
+ for (tbl = aic3x_i2c_id; tbl->name[0]; tbl++) {
+ if (!strcmp(tbl->name, id->name))
+ break;
+ }
+ aic3x->model = tbl - aic3x_i2c_id;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_aic3x, &aic3x_dai, 1);
+ if (ret < 0)
+ kfree(aic3x);
+ return ret;
+}
+
+static int aic3x_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+/* machine i2c codec control layer */
+static struct i2c_driver aic3x_i2c_driver = {
+ .driver = {
+ .name = "tlv320aic3x-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = aic3x_i2c_probe,
+ .remove = aic3x_i2c_remove,
+ .id_table = aic3x_i2c_id,
+};
+#endif
+
+static int __init aic3x_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&aic3x_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register TLV320AIC3x I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(aic3x_modinit);
+
+static void __exit aic3x_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&aic3x_i2c_driver);
+#endif
+}
+module_exit(aic3x_exit);
+
+MODULE_DESCRIPTION("ASoC TLV320AIC3X codec driver");
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h
new file mode 100644
index 00000000..06a19784
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic3x.h
@@ -0,0 +1,261 @@
+/*
+ * ALSA SoC TLV320AIC3X codec driver
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AIC3X_H
+#define _AIC3X_H
+
+/* AIC3X register space */
+#define AIC3X_CACHEREGNUM 103
+
+/* Page select register */
+#define AIC3X_PAGE_SELECT 0
+/* Software reset register */
+#define AIC3X_RESET 1
+/* Codec Sample rate select register */
+#define AIC3X_SAMPLE_RATE_SEL_REG 2
+/* PLL progrramming register A */
+#define AIC3X_PLL_PROGA_REG 3
+/* PLL progrramming register B */
+#define AIC3X_PLL_PROGB_REG 4
+/* PLL progrramming register C */
+#define AIC3X_PLL_PROGC_REG 5
+/* PLL progrramming register D */
+#define AIC3X_PLL_PROGD_REG 6
+/* Codec datapath setup register */
+#define AIC3X_CODEC_DATAPATH_REG 7
+/* Audio serial data interface control register A */
+#define AIC3X_ASD_INTF_CTRLA 8
+/* Audio serial data interface control register B */
+#define AIC3X_ASD_INTF_CTRLB 9
+/* Audio serial data interface control register C */
+#define AIC3X_ASD_INTF_CTRLC 10
+/* Audio overflow status and PLL R value programming register */
+#define AIC3X_OVRF_STATUS_AND_PLLR_REG 11
+/* Audio codec digital filter control register */
+#define AIC3X_CODEC_DFILT_CTRL 12
+/* Headset/button press detection register */
+#define AIC3X_HEADSET_DETECT_CTRL_A 13
+#define AIC3X_HEADSET_DETECT_CTRL_B 14
+/* ADC PGA Gain control registers */
+#define LADC_VOL 15
+#define RADC_VOL 16
+/* MIC3 control registers */
+#define MIC3LR_2_LADC_CTRL 17
+#define MIC3LR_2_RADC_CTRL 18
+/* Line1 Input control registers */
+#define LINE1L_2_LADC_CTRL 19
+#define LINE1R_2_LADC_CTRL 21
+#define LINE1R_2_RADC_CTRL 22
+#define LINE1L_2_RADC_CTRL 24
+/* Line2 Input control registers */
+#define LINE2L_2_LADC_CTRL 20
+#define LINE2R_2_RADC_CTRL 23
+/* MICBIAS Control Register */
+#define MICBIAS_CTRL 25
+
+/* AGC Control Registers A, B, C */
+#define LAGC_CTRL_A 26
+#define LAGC_CTRL_B 27
+#define LAGC_CTRL_C 28
+#define RAGC_CTRL_A 29
+#define RAGC_CTRL_B 30
+#define RAGC_CTRL_C 31
+
+/* DAC Power and Left High Power Output control registers */
+#define DAC_PWR 37
+#define HPLCOM_CFG 37
+/* Right High Power Output control registers */
+#define HPRCOM_CFG 38
+/* DAC Output Switching control registers */
+#define DAC_LINE_MUX 41
+/* High Power Output Driver Pop Reduction registers */
+#define HPOUT_POP_REDUCTION 42
+/* DAC Digital control registers */
+#define LDAC_VOL 43
+#define RDAC_VOL 44
+/* Left High Power Output control registers */
+#define LINE2L_2_HPLOUT_VOL 45
+#define PGAL_2_HPLOUT_VOL 46
+#define DACL1_2_HPLOUT_VOL 47
+#define LINE2R_2_HPLOUT_VOL 48
+#define PGAR_2_HPLOUT_VOL 49
+#define DACR1_2_HPLOUT_VOL 50
+#define HPLOUT_CTRL 51
+/* Left High Power COM control registers */
+#define LINE2L_2_HPLCOM_VOL 52
+#define PGAL_2_HPLCOM_VOL 53
+#define DACL1_2_HPLCOM_VOL 54
+#define LINE2R_2_HPLCOM_VOL 55
+#define PGAR_2_HPLCOM_VOL 56
+#define DACR1_2_HPLCOM_VOL 57
+#define HPLCOM_CTRL 58
+/* Right High Power Output control registers */
+#define LINE2L_2_HPROUT_VOL 59
+#define PGAL_2_HPROUT_VOL 60
+#define DACL1_2_HPROUT_VOL 61
+#define LINE2R_2_HPROUT_VOL 62
+#define PGAR_2_HPROUT_VOL 63
+#define DACR1_2_HPROUT_VOL 64
+#define HPROUT_CTRL 65
+/* Right High Power COM control registers */
+#define LINE2L_2_HPRCOM_VOL 66
+#define PGAL_2_HPRCOM_VOL 67
+#define DACL1_2_HPRCOM_VOL 68
+#define LINE2R_2_HPRCOM_VOL 69
+#define PGAR_2_HPRCOM_VOL 70
+#define DACR1_2_HPRCOM_VOL 71
+#define HPRCOM_CTRL 72
+/* Mono Line Output Plus/Minus control registers */
+#define LINE2L_2_MONOLOPM_VOL 73
+#define PGAL_2_MONOLOPM_VOL 74
+#define DACL1_2_MONOLOPM_VOL 75
+#define LINE2R_2_MONOLOPM_VOL 76
+#define PGAR_2_MONOLOPM_VOL 77
+#define DACR1_2_MONOLOPM_VOL 78
+#define MONOLOPM_CTRL 79
+/* Class-D speaker driver on tlv320aic3007 */
+#define CLASSD_CTRL 73
+/* Left Line Output Plus/Minus control registers */
+#define LINE2L_2_LLOPM_VOL 80
+#define PGAL_2_LLOPM_VOL 81
+#define DACL1_2_LLOPM_VOL 82
+#define LINE2R_2_LLOPM_VOL 83
+#define PGAR_2_LLOPM_VOL 84
+#define DACR1_2_LLOPM_VOL 85
+#define LLOPM_CTRL 86
+/* Right Line Output Plus/Minus control registers */
+#define LINE2L_2_RLOPM_VOL 87
+#define PGAL_2_RLOPM_VOL 88
+#define DACL1_2_RLOPM_VOL 89
+#define LINE2R_2_RLOPM_VOL 90
+#define PGAR_2_RLOPM_VOL 91
+#define DACR1_2_RLOPM_VOL 92
+#define RLOPM_CTRL 93
+/* GPIO/IRQ registers */
+#define AIC3X_STICKY_IRQ_FLAGS_REG 96
+#define AIC3X_RT_IRQ_FLAGS_REG 97
+#define AIC3X_GPIO1_REG 98
+#define AIC3X_GPIO2_REG 99
+#define AIC3X_GPIOA_REG 100
+#define AIC3X_GPIOB_REG 101
+/* Clock generation control register */
+#define AIC3X_CLKGEN_CTRL_REG 102
+
+/* Page select register bits */
+#define PAGE0_SELECT 0
+#define PAGE1_SELECT 1
+
+/* Audio serial data interface control register A bits */
+#define BIT_CLK_MASTER 0x80
+#define WORD_CLK_MASTER 0x40
+
+/* Codec Datapath setup register 7 */
+#define FSREF_44100 (1 << 7)
+#define FSREF_48000 (0 << 7)
+#define DUAL_RATE_MODE ((1 << 5) | (1 << 6))
+#define LDAC2LCH (0x1 << 3)
+#define RDAC2RCH (0x1 << 1)
+
+/* PLL registers bitfields */
+#define PLLP_SHIFT 0
+#define PLLQ_SHIFT 3
+#define PLLR_SHIFT 0
+#define PLLJ_SHIFT 2
+#define PLLD_MSB_SHIFT 0
+#define PLLD_LSB_SHIFT 2
+
+/* Clock generation register bits */
+#define CODEC_CLKIN_PLLDIV 0
+#define CODEC_CLKIN_CLKDIV 1
+#define PLL_CLKIN_SHIFT 4
+#define MCLK_SOURCE 0x0
+#define PLL_CLKDIV_SHIFT 0
+
+/* Software reset register bits */
+#define SOFT_RESET 0x80
+
+/* PLL progrramming register A bits */
+#define PLL_ENABLE 0x80
+
+/* Route bits */
+#define ROUTE_ON 0x80
+
+/* Mute bits */
+#define UNMUTE 0x08
+#define MUTE_ON 0x80
+
+/* Power bits */
+#define LADC_PWR_ON 0x04
+#define RADC_PWR_ON 0x04
+#define LDAC_PWR_ON 0x80
+#define RDAC_PWR_ON 0x40
+#define HPLOUT_PWR_ON 0x01
+#define HPROUT_PWR_ON 0x01
+#define HPLCOM_PWR_ON 0x01
+#define HPRCOM_PWR_ON 0x01
+#define MONOLOPM_PWR_ON 0x01
+#define LLOPM_PWR_ON 0x01
+#define RLOPM_PWR_ON 0x01
+
+#define INVERT_VOL(val) (0x7f - val)
+
+/* Default output volume (inverted) */
+#define DEFAULT_VOL INVERT_VOL(0x50)
+/* Default input volume */
+#define DEFAULT_GAIN 0x20
+
+void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state);
+int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio);
+
+/* headset detection / button API */
+
+/* The AIC3x supports detection of stereo headsets (GND + left + right signal)
+ * and cellular headsets (GND + speaker output + microphone input).
+ * It is recommended to enable MIC bias for this function to work properly.
+ * For more information, please refer to the datasheet. */
+enum {
+ AIC3X_HEADSET_DETECT_OFF = 0,
+ AIC3X_HEADSET_DETECT_STEREO = 1,
+ AIC3X_HEADSET_DETECT_CELLULAR = 2,
+ AIC3X_HEADSET_DETECT_BOTH = 3
+};
+
+enum {
+ AIC3X_HEADSET_DEBOUNCE_16MS = 0,
+ AIC3X_HEADSET_DEBOUNCE_32MS = 1,
+ AIC3X_HEADSET_DEBOUNCE_64MS = 2,
+ AIC3X_HEADSET_DEBOUNCE_128MS = 3,
+ AIC3X_HEADSET_DEBOUNCE_256MS = 4,
+ AIC3X_HEADSET_DEBOUNCE_512MS = 5
+};
+
+enum {
+ AIC3X_BUTTON_DEBOUNCE_0MS = 0,
+ AIC3X_BUTTON_DEBOUNCE_8MS = 1,
+ AIC3X_BUTTON_DEBOUNCE_16MS = 2,
+ AIC3X_BUTTON_DEBOUNCE_32MS = 3
+};
+
+#define AIC3X_HEADSET_DETECT_ENABLED 0x80
+#define AIC3X_HEADSET_DETECT_SHIFT 5
+#define AIC3X_HEADSET_DETECT_MASK 3
+#define AIC3X_HEADSET_DEBOUNCE_SHIFT 2
+#define AIC3X_HEADSET_DEBOUNCE_MASK 7
+#define AIC3X_BUTTON_DEBOUNCE_SHIFT 0
+#define AIC3X_BUTTON_DEBOUNCE_MASK 3
+
+/* see the enums above for valid parameters to this function */
+void aic3x_set_headset_detection(struct snd_soc_codec *codec, int detect,
+ int headset_debounce, int button_debounce);
+int aic3x_headset_detected(struct snd_soc_codec *codec);
+int aic3x_button_pressed(struct snd_soc_codec *codec);
+
+#endif /* _AIC3X_H */
diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c
new file mode 100644
index 00000000..faa5e9fb
--- /dev/null
+++ b/sound/soc/codecs/tlv320dac33.c
@@ -0,0 +1,1662 @@
+/*
+ * ALSA SoC Texas Instruments TLV320DAC33 codec driver
+ *
+ * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * Copyright: (C) 2009 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <sound/tlv320dac33-plat.h>
+#include "tlv320dac33.h"
+
+/*
+ * The internal FIFO is 24576 bytes long
+ * It can be configured to hold 16bit or 24bit samples
+ * In 16bit configuration the FIFO can hold 6144 stereo samples
+ * In 24bit configuration the FIFO can hold 4096 stereo samples
+ */
+#define DAC33_FIFO_SIZE_16BIT 6144
+#define DAC33_FIFO_SIZE_24BIT 4096
+#define DAC33_MODE7_MARGIN 10 /* Safety margin for FIFO in Mode7 */
+
+#define BURST_BASEFREQ_HZ 49152000
+
+#define SAMPLES_TO_US(rate, samples) \
+ (1000000000 / ((rate * 1000) / samples))
+
+#define US_TO_SAMPLES(rate, us) \
+ (rate / (1000000 / (us < 1000000 ? us : 1000000)))
+
+#define UTHR_FROM_PERIOD_SIZE(samples, playrate, burstrate) \
+ ((samples * 5000) / ((burstrate * 5000) / (burstrate - playrate)))
+
+static void dac33_calculate_times(struct snd_pcm_substream *substream);
+static int dac33_prepare_chip(struct snd_pcm_substream *substream);
+
+enum dac33_state {
+ DAC33_IDLE = 0,
+ DAC33_PREFILL,
+ DAC33_PLAYBACK,
+ DAC33_FLUSH,
+};
+
+enum dac33_fifo_modes {
+ DAC33_FIFO_BYPASS = 0,
+ DAC33_FIFO_MODE1,
+ DAC33_FIFO_MODE7,
+ DAC33_FIFO_LAST_MODE,
+};
+
+#define DAC33_NUM_SUPPLIES 3
+static const char *dac33_supply_names[DAC33_NUM_SUPPLIES] = {
+ "AVDD",
+ "DVDD",
+ "IOVDD",
+};
+
+struct tlv320dac33_priv {
+ struct mutex mutex;
+ struct workqueue_struct *dac33_wq;
+ struct work_struct work;
+ struct snd_soc_codec *codec;
+ struct regulator_bulk_data supplies[DAC33_NUM_SUPPLIES];
+ struct snd_pcm_substream *substream;
+ int power_gpio;
+ int chip_power;
+ int irq;
+ unsigned int refclk;
+
+ unsigned int alarm_threshold; /* set to be half of LATENCY_TIME_MS */
+ enum dac33_fifo_modes fifo_mode;/* FIFO mode selection */
+ unsigned int fifo_size; /* Size of the FIFO in samples */
+ unsigned int nsample; /* burst read amount from host */
+ int mode1_latency; /* latency caused by the i2c writes in
+ * us */
+ u8 burst_bclkdiv; /* BCLK divider value in burst mode */
+ unsigned int burst_rate; /* Interface speed in Burst modes */
+
+ int keep_bclk; /* Keep the BCLK continuously running
+ * in FIFO modes */
+ spinlock_t lock;
+ unsigned long long t_stamp1; /* Time stamp for FIFO modes to */
+ unsigned long long t_stamp2; /* calculate the FIFO caused delay */
+
+ unsigned int mode1_us_burst; /* Time to burst read n number of
+ * samples */
+ unsigned int mode7_us_to_lthr; /* Time to reach lthr from uthr */
+
+ unsigned int uthr;
+
+ enum dac33_state state;
+ enum snd_soc_control_type control_type;
+ void *control_data;
+};
+
+static const u8 dac33_reg[DAC33_CACHEREGNUM] = {
+0x00, 0x00, 0x00, 0x00, /* 0x00 - 0x03 */
+0x00, 0x00, 0x00, 0x00, /* 0x04 - 0x07 */
+0x00, 0x00, 0x00, 0x00, /* 0x08 - 0x0b */
+0x00, 0x00, 0x00, 0x00, /* 0x0c - 0x0f */
+0x00, 0x00, 0x00, 0x00, /* 0x10 - 0x13 */
+0x00, 0x00, 0x00, 0x00, /* 0x14 - 0x17 */
+0x00, 0x00, 0x00, 0x00, /* 0x18 - 0x1b */
+0x00, 0x00, 0x00, 0x00, /* 0x1c - 0x1f */
+0x00, 0x00, 0x00, 0x00, /* 0x20 - 0x23 */
+0x00, 0x00, 0x00, 0x00, /* 0x24 - 0x27 */
+0x00, 0x00, 0x00, 0x00, /* 0x28 - 0x2b */
+0x00, 0x00, 0x00, 0x80, /* 0x2c - 0x2f */
+0x80, 0x00, 0x00, 0x00, /* 0x30 - 0x33 */
+0x00, 0x00, 0x00, 0x00, /* 0x34 - 0x37 */
+0x00, 0x00, /* 0x38 - 0x39 */
+/* Registers 0x3a - 0x3f are reserved */
+ 0x00, 0x00, /* 0x3a - 0x3b */
+0x00, 0x00, 0x00, 0x00, /* 0x3c - 0x3f */
+
+0x00, 0x00, 0x00, 0x00, /* 0x40 - 0x43 */
+0x00, 0x80, /* 0x44 - 0x45 */
+/* Registers 0x46 - 0x47 are reserved */
+ 0x80, 0x80, /* 0x46 - 0x47 */
+
+0x80, 0x00, 0x00, /* 0x48 - 0x4a */
+/* Registers 0x4b - 0x7c are reserved */
+ 0x00, /* 0x4b */
+0x00, 0x00, 0x00, 0x00, /* 0x4c - 0x4f */
+0x00, 0x00, 0x00, 0x00, /* 0x50 - 0x53 */
+0x00, 0x00, 0x00, 0x00, /* 0x54 - 0x57 */
+0x00, 0x00, 0x00, 0x00, /* 0x58 - 0x5b */
+0x00, 0x00, 0x00, 0x00, /* 0x5c - 0x5f */
+0x00, 0x00, 0x00, 0x00, /* 0x60 - 0x63 */
+0x00, 0x00, 0x00, 0x00, /* 0x64 - 0x67 */
+0x00, 0x00, 0x00, 0x00, /* 0x68 - 0x6b */
+0x00, 0x00, 0x00, 0x00, /* 0x6c - 0x6f */
+0x00, 0x00, 0x00, 0x00, /* 0x70 - 0x73 */
+0x00, 0x00, 0x00, 0x00, /* 0x74 - 0x77 */
+0x00, 0x00, 0x00, 0x00, /* 0x78 - 0x7b */
+0x00, /* 0x7c */
+
+ 0xda, 0x33, 0x03, /* 0x7d - 0x7f */
+};
+
+/* Register read and write */
+static inline unsigned int dac33_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned reg)
+{
+ u8 *cache = codec->reg_cache;
+ if (reg >= DAC33_CACHEREGNUM)
+ return 0;
+
+ return cache[reg];
+}
+
+static inline void dac33_write_reg_cache(struct snd_soc_codec *codec,
+ u8 reg, u8 value)
+{
+ u8 *cache = codec->reg_cache;
+ if (reg >= DAC33_CACHEREGNUM)
+ return;
+
+ cache[reg] = value;
+}
+
+static int dac33_read(struct snd_soc_codec *codec, unsigned int reg,
+ u8 *value)
+{
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ int val, ret = 0;
+
+ *value = reg & 0xff;
+
+ /* If powered off, return the cached value */
+ if (dac33->chip_power) {
+ val = i2c_smbus_read_byte_data(codec->control_data, value[0]);
+ if (val < 0) {
+ dev_err(codec->dev, "Read failed (%d)\n", val);
+ value[0] = dac33_read_reg_cache(codec, reg);
+ ret = val;
+ } else {
+ value[0] = val;
+ dac33_write_reg_cache(codec, reg, val);
+ }
+ } else {
+ value[0] = dac33_read_reg_cache(codec, reg);
+ }
+
+ return ret;
+}
+
+static int dac33_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ u8 data[2];
+ int ret = 0;
+
+ /*
+ * data is
+ * D15..D8 dac33 register offset
+ * D7...D0 register data
+ */
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ dac33_write_reg_cache(codec, data[0], data[1]);
+ if (dac33->chip_power) {
+ ret = codec->hw_write(codec->control_data, data, 2);
+ if (ret != 2)
+ dev_err(codec->dev, "Write failed (%d)\n", ret);
+ else
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int dac33_write_locked(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ mutex_lock(&dac33->mutex);
+ ret = dac33_write(codec, reg, value);
+ mutex_unlock(&dac33->mutex);
+
+ return ret;
+}
+
+#define DAC33_I2C_ADDR_AUTOINC 0x80
+static int dac33_write16(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ u8 data[3];
+ int ret = 0;
+
+ /*
+ * data is
+ * D23..D16 dac33 register offset
+ * D15..D8 register data MSB
+ * D7...D0 register data LSB
+ */
+ data[0] = reg & 0xff;
+ data[1] = (value >> 8) & 0xff;
+ data[2] = value & 0xff;
+
+ dac33_write_reg_cache(codec, data[0], data[1]);
+ dac33_write_reg_cache(codec, data[0] + 1, data[2]);
+
+ if (dac33->chip_power) {
+ /* We need to set autoincrement mode for 16 bit writes */
+ data[0] |= DAC33_I2C_ADDR_AUTOINC;
+ ret = codec->hw_write(codec->control_data, data, 3);
+ if (ret != 3)
+ dev_err(codec->dev, "Write failed (%d)\n", ret);
+ else
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static void dac33_init_chip(struct snd_soc_codec *codec)
+{
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+ if (unlikely(!dac33->chip_power))
+ return;
+
+ /* A : DAC sample rate Fsref/1.5 */
+ dac33_write(codec, DAC33_DAC_CTRL_A, DAC33_DACRATE(0));
+ /* B : DAC src=normal, not muted */
+ dac33_write(codec, DAC33_DAC_CTRL_B, DAC33_DACSRCR_RIGHT |
+ DAC33_DACSRCL_LEFT);
+ /* C : (defaults) */
+ dac33_write(codec, DAC33_DAC_CTRL_C, 0x00);
+
+ /* 73 : volume soft stepping control,
+ clock source = internal osc (?) */
+ dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN);
+
+ /* Restore only selected registers (gains mostly) */
+ dac33_write(codec, DAC33_LDAC_DIG_VOL_CTRL,
+ dac33_read_reg_cache(codec, DAC33_LDAC_DIG_VOL_CTRL));
+ dac33_write(codec, DAC33_RDAC_DIG_VOL_CTRL,
+ dac33_read_reg_cache(codec, DAC33_RDAC_DIG_VOL_CTRL));
+
+ dac33_write(codec, DAC33_LINEL_TO_LLO_VOL,
+ dac33_read_reg_cache(codec, DAC33_LINEL_TO_LLO_VOL));
+ dac33_write(codec, DAC33_LINER_TO_RLO_VOL,
+ dac33_read_reg_cache(codec, DAC33_LINER_TO_RLO_VOL));
+
+ dac33_write(codec, DAC33_OUT_AMP_CTRL,
+ dac33_read_reg_cache(codec, DAC33_OUT_AMP_CTRL));
+
+ dac33_write(codec, DAC33_LDAC_PWR_CTRL,
+ dac33_read_reg_cache(codec, DAC33_LDAC_PWR_CTRL));
+ dac33_write(codec, DAC33_RDAC_PWR_CTRL,
+ dac33_read_reg_cache(codec, DAC33_RDAC_PWR_CTRL));
+}
+
+static inline int dac33_read_id(struct snd_soc_codec *codec)
+{
+ int i, ret = 0;
+ u8 reg;
+
+ for (i = 0; i < 3; i++) {
+ ret = dac33_read(codec, DAC33_DEVICE_ID_MSB + i, &reg);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static inline void dac33_soft_power(struct snd_soc_codec *codec, int power)
+{
+ u8 reg;
+
+ reg = dac33_read_reg_cache(codec, DAC33_PWR_CTRL);
+ if (power)
+ reg |= DAC33_PDNALLB;
+ else
+ reg &= ~(DAC33_PDNALLB | DAC33_OSCPDNB |
+ DAC33_DACRPDNB | DAC33_DACLPDNB);
+ dac33_write(codec, DAC33_PWR_CTRL, reg);
+}
+
+static inline void dac33_disable_digital(struct snd_soc_codec *codec)
+{
+ u8 reg;
+
+ /* Stop the DAI clock */
+ reg = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B);
+ reg &= ~DAC33_BCLKON;
+ dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_B, reg);
+
+ /* Power down the Oscillator, and DACs */
+ reg = dac33_read_reg_cache(codec, DAC33_PWR_CTRL);
+ reg &= ~(DAC33_OSCPDNB | DAC33_DACRPDNB | DAC33_DACLPDNB);
+ dac33_write(codec, DAC33_PWR_CTRL, reg);
+}
+
+static int dac33_hard_power(struct snd_soc_codec *codec, int power)
+{
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ mutex_lock(&dac33->mutex);
+
+ /* Safety check */
+ if (unlikely(power == dac33->chip_power)) {
+ dev_dbg(codec->dev, "Trying to set the same power state: %s\n",
+ power ? "ON" : "OFF");
+ goto exit;
+ }
+
+ if (power) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies),
+ dac33->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n", ret);
+ goto exit;
+ }
+
+ if (dac33->power_gpio >= 0)
+ gpio_set_value(dac33->power_gpio, 1);
+
+ dac33->chip_power = 1;
+ } else {
+ dac33_soft_power(codec, 0);
+ if (dac33->power_gpio >= 0)
+ gpio_set_value(dac33->power_gpio, 0);
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(dac33->supplies),
+ dac33->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to disable supplies: %d\n", ret);
+ goto exit;
+ }
+
+ dac33->chip_power = 0;
+ }
+
+exit:
+ mutex_unlock(&dac33->mutex);
+ return ret;
+}
+
+static int dac33_playback_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(w->codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (likely(dac33->substream)) {
+ dac33_calculate_times(dac33->substream);
+ dac33_prepare_chip(dac33->substream);
+ }
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ dac33_disable_digital(w->codec);
+ break;
+ }
+ return 0;
+}
+
+static int dac33_get_fifo_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = dac33->fifo_mode;
+
+ return 0;
+}
+
+static int dac33_set_fifo_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ if (dac33->fifo_mode == ucontrol->value.integer.value[0])
+ return 0;
+ /* Do not allow changes while stream is running*/
+ if (codec->active)
+ return -EPERM;
+
+ if (ucontrol->value.integer.value[0] < 0 ||
+ ucontrol->value.integer.value[0] >= DAC33_FIFO_LAST_MODE)
+ ret = -EINVAL;
+ else
+ dac33->fifo_mode = ucontrol->value.integer.value[0];
+
+ return ret;
+}
+
+/* Codec operation modes */
+static const char *dac33_fifo_mode_texts[] = {
+ "Bypass", "Mode 1", "Mode 7"
+};
+
+static const struct soc_enum dac33_fifo_mode_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(dac33_fifo_mode_texts),
+ dac33_fifo_mode_texts);
+
+/* L/R Line Output Gain */
+static const char *lr_lineout_gain_texts[] = {
+ "Line -12dB DAC 0dB", "Line -6dB DAC 6dB",
+ "Line 0dB DAC 12dB", "Line 6dB DAC 18dB",
+};
+
+static const struct soc_enum l_lineout_gain_enum =
+ SOC_ENUM_SINGLE(DAC33_LDAC_PWR_CTRL, 0,
+ ARRAY_SIZE(lr_lineout_gain_texts),
+ lr_lineout_gain_texts);
+
+static const struct soc_enum r_lineout_gain_enum =
+ SOC_ENUM_SINGLE(DAC33_RDAC_PWR_CTRL, 0,
+ ARRAY_SIZE(lr_lineout_gain_texts),
+ lr_lineout_gain_texts);
+
+/*
+ * DACL/R digital volume control:
+ * from 0 dB to -63.5 in 0.5 dB steps
+ * Need to be inverted later on:
+ * 0x00 == 0 dB
+ * 0x7f == -63.5 dB
+ */
+static DECLARE_TLV_DB_SCALE(dac_digivol_tlv, -6350, 50, 0);
+
+static const struct snd_kcontrol_new dac33_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("DAC Digital Playback Volume",
+ DAC33_LDAC_DIG_VOL_CTRL, DAC33_RDAC_DIG_VOL_CTRL,
+ 0, 0x7f, 1, dac_digivol_tlv),
+ SOC_DOUBLE_R("DAC Digital Playback Switch",
+ DAC33_LDAC_DIG_VOL_CTRL, DAC33_RDAC_DIG_VOL_CTRL, 7, 1, 1),
+ SOC_DOUBLE_R("Line to Line Out Volume",
+ DAC33_LINEL_TO_LLO_VOL, DAC33_LINER_TO_RLO_VOL, 0, 127, 1),
+ SOC_ENUM("Left Line Output Gain", l_lineout_gain_enum),
+ SOC_ENUM("Right Line Output Gain", r_lineout_gain_enum),
+};
+
+static const struct snd_kcontrol_new dac33_mode_snd_controls[] = {
+ SOC_ENUM_EXT("FIFO Mode", dac33_fifo_mode_enum,
+ dac33_get_fifo_mode, dac33_set_fifo_mode),
+};
+
+/* Analog bypass */
+static const struct snd_kcontrol_new dac33_dapm_abypassl_control =
+ SOC_DAPM_SINGLE("Switch", DAC33_LINEL_TO_LLO_VOL, 7, 1, 1);
+
+static const struct snd_kcontrol_new dac33_dapm_abypassr_control =
+ SOC_DAPM_SINGLE("Switch", DAC33_LINER_TO_RLO_VOL, 7, 1, 1);
+
+/* LOP L/R invert selection */
+static const char *dac33_lr_lom_texts[] = {"DAC", "LOP"};
+
+static const struct soc_enum dac33_left_lom_enum =
+ SOC_ENUM_SINGLE(DAC33_OUT_AMP_CTRL, 3,
+ ARRAY_SIZE(dac33_lr_lom_texts),
+ dac33_lr_lom_texts);
+
+static const struct snd_kcontrol_new dac33_dapm_left_lom_control =
+SOC_DAPM_ENUM("Route", dac33_left_lom_enum);
+
+static const struct soc_enum dac33_right_lom_enum =
+ SOC_ENUM_SINGLE(DAC33_OUT_AMP_CTRL, 2,
+ ARRAY_SIZE(dac33_lr_lom_texts),
+ dac33_lr_lom_texts);
+
+static const struct snd_kcontrol_new dac33_dapm_right_lom_control =
+SOC_DAPM_ENUM("Route", dac33_right_lom_enum);
+
+static const struct snd_soc_dapm_widget dac33_dapm_widgets[] = {
+ SND_SOC_DAPM_OUTPUT("LEFT_LO"),
+ SND_SOC_DAPM_OUTPUT("RIGHT_LO"),
+
+ SND_SOC_DAPM_INPUT("LINEL"),
+ SND_SOC_DAPM_INPUT("LINER"),
+
+ SND_SOC_DAPM_DAC("DACL", "Left Playback", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DAC("DACR", "Right Playback", SND_SOC_NOPM, 0, 0),
+
+ /* Analog bypass */
+ SND_SOC_DAPM_SWITCH("Analog Left Bypass", SND_SOC_NOPM, 0, 0,
+ &dac33_dapm_abypassl_control),
+ SND_SOC_DAPM_SWITCH("Analog Right Bypass", SND_SOC_NOPM, 0, 0,
+ &dac33_dapm_abypassr_control),
+
+ SND_SOC_DAPM_MUX("Left LOM Inverted From", SND_SOC_NOPM, 0, 0,
+ &dac33_dapm_left_lom_control),
+ SND_SOC_DAPM_MUX("Right LOM Inverted From", SND_SOC_NOPM, 0, 0,
+ &dac33_dapm_right_lom_control),
+ /*
+ * For DAPM path, when only the anlog bypass path is enabled, and the
+ * LOP inverted from the corresponding DAC side.
+ * This is needed, so we can attach the DAC power supply in this case.
+ */
+ SND_SOC_DAPM_PGA("Left Bypass PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Bypass PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Left Amplifier",
+ DAC33_OUT_AMP_PWR_CTRL, 6, 3, 3, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Right Amplifier",
+ DAC33_OUT_AMP_PWR_CTRL, 4, 3, 3, 0),
+
+ SND_SOC_DAPM_SUPPLY("Left DAC Power",
+ DAC33_LDAC_PWR_CTRL, 2, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("Right DAC Power",
+ DAC33_RDAC_PWR_CTRL, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_SUPPLY("Codec Power",
+ DAC33_PWR_CTRL, 4, 0, NULL, 0),
+
+ SND_SOC_DAPM_PRE("Pre Playback", dac33_playback_event),
+ SND_SOC_DAPM_POST("Post Playback", dac33_playback_event),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Analog bypass */
+ {"Analog Left Bypass", "Switch", "LINEL"},
+ {"Analog Right Bypass", "Switch", "LINER"},
+
+ {"Output Left Amplifier", NULL, "DACL"},
+ {"Output Right Amplifier", NULL, "DACR"},
+
+ {"Left Bypass PGA", NULL, "Analog Left Bypass"},
+ {"Right Bypass PGA", NULL, "Analog Right Bypass"},
+
+ {"Left LOM Inverted From", "DAC", "Left Bypass PGA"},
+ {"Right LOM Inverted From", "DAC", "Right Bypass PGA"},
+ {"Left LOM Inverted From", "LOP", "Analog Left Bypass"},
+ {"Right LOM Inverted From", "LOP", "Analog Right Bypass"},
+
+ {"Output Left Amplifier", NULL, "Left LOM Inverted From"},
+ {"Output Right Amplifier", NULL, "Right LOM Inverted From"},
+
+ {"DACL", NULL, "Left DAC Power"},
+ {"DACR", NULL, "Right DAC Power"},
+
+ {"Left Bypass PGA", NULL, "Left DAC Power"},
+ {"Right Bypass PGA", NULL, "Right DAC Power"},
+
+ /* output */
+ {"LEFT_LO", NULL, "Output Left Amplifier"},
+ {"RIGHT_LO", NULL, "Output Right Amplifier"},
+
+ {"LEFT_LO", NULL, "Codec Power"},
+ {"RIGHT_LO", NULL, "Codec Power"},
+};
+
+static int dac33_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, dac33_dapm_widgets,
+ ARRAY_SIZE(dac33_dapm_widgets));
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+static int dac33_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Coming from OFF, switch on the codec */
+ ret = dac33_hard_power(codec, 1);
+ if (ret != 0)
+ return ret;
+
+ dac33_init_chip(codec);
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* Do not power off, when the codec is already off */
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF)
+ return 0;
+ ret = dac33_hard_power(codec, 0);
+ if (ret != 0)
+ return ret;
+ break;
+ }
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33)
+{
+ struct snd_soc_codec *codec = dac33->codec;
+ unsigned int delay;
+ unsigned long flags;
+
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
+ dac33_write16(codec, DAC33_NSAMPLE_MSB,
+ DAC33_THRREG(dac33->nsample));
+
+ /* Take the timestamps */
+ spin_lock_irqsave(&dac33->lock, flags);
+ dac33->t_stamp2 = ktime_to_us(ktime_get());
+ dac33->t_stamp1 = dac33->t_stamp2;
+ spin_unlock_irqrestore(&dac33->lock, flags);
+
+ dac33_write16(codec, DAC33_PREFILL_MSB,
+ DAC33_THRREG(dac33->alarm_threshold));
+ /* Enable Alarm Threshold IRQ with a delay */
+ delay = SAMPLES_TO_US(dac33->burst_rate,
+ dac33->alarm_threshold) + 1000;
+ usleep_range(delay, delay + 500);
+ dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT);
+ break;
+ case DAC33_FIFO_MODE7:
+ /* Take the timestamp */
+ spin_lock_irqsave(&dac33->lock, flags);
+ dac33->t_stamp1 = ktime_to_us(ktime_get());
+ /* Move back the timestamp with drain time */
+ dac33->t_stamp1 -= dac33->mode7_us_to_lthr;
+ spin_unlock_irqrestore(&dac33->lock, flags);
+
+ dac33_write16(codec, DAC33_PREFILL_MSB,
+ DAC33_THRREG(DAC33_MODE7_MARGIN));
+
+ /* Enable Upper Threshold IRQ */
+ dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MUT);
+ break;
+ default:
+ dev_warn(codec->dev, "Unhandled FIFO mode: %d\n",
+ dac33->fifo_mode);
+ break;
+ }
+}
+
+static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33)
+{
+ struct snd_soc_codec *codec = dac33->codec;
+ unsigned long flags;
+
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
+ /* Take the timestamp */
+ spin_lock_irqsave(&dac33->lock, flags);
+ dac33->t_stamp2 = ktime_to_us(ktime_get());
+ spin_unlock_irqrestore(&dac33->lock, flags);
+
+ dac33_write16(codec, DAC33_NSAMPLE_MSB,
+ DAC33_THRREG(dac33->nsample));
+ break;
+ case DAC33_FIFO_MODE7:
+ /* At the moment we are not using interrupts in mode7 */
+ break;
+ default:
+ dev_warn(codec->dev, "Unhandled FIFO mode: %d\n",
+ dac33->fifo_mode);
+ break;
+ }
+}
+
+static void dac33_work(struct work_struct *work)
+{
+ struct snd_soc_codec *codec;
+ struct tlv320dac33_priv *dac33;
+ u8 reg;
+
+ dac33 = container_of(work, struct tlv320dac33_priv, work);
+ codec = dac33->codec;
+
+ mutex_lock(&dac33->mutex);
+ switch (dac33->state) {
+ case DAC33_PREFILL:
+ dac33->state = DAC33_PLAYBACK;
+ dac33_prefill_handler(dac33);
+ break;
+ case DAC33_PLAYBACK:
+ dac33_playback_handler(dac33);
+ break;
+ case DAC33_IDLE:
+ break;
+ case DAC33_FLUSH:
+ dac33->state = DAC33_IDLE;
+ /* Mask all interrupts from dac33 */
+ dac33_write(codec, DAC33_FIFO_IRQ_MASK, 0);
+
+ /* flush fifo */
+ reg = dac33_read_reg_cache(codec, DAC33_FIFO_CTRL_A);
+ reg |= DAC33_FIFOFLUSH;
+ dac33_write(codec, DAC33_FIFO_CTRL_A, reg);
+ break;
+ }
+ mutex_unlock(&dac33->mutex);
+}
+
+static irqreturn_t dac33_interrupt_handler(int irq, void *dev)
+{
+ struct snd_soc_codec *codec = dev;
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ unsigned long flags;
+
+ spin_lock_irqsave(&dac33->lock, flags);
+ dac33->t_stamp1 = ktime_to_us(ktime_get());
+ spin_unlock_irqrestore(&dac33->lock, flags);
+
+ /* Do not schedule the workqueue in Mode7 */
+ if (dac33->fifo_mode != DAC33_FIFO_MODE7)
+ queue_work(dac33->dac33_wq, &dac33->work);
+
+ return IRQ_HANDLED;
+}
+
+static void dac33_oscwait(struct snd_soc_codec *codec)
+{
+ int timeout = 60;
+ u8 reg;
+
+ do {
+ usleep_range(1000, 2000);
+ dac33_read(codec, DAC33_INT_OSC_STATUS, &reg);
+ } while (((reg & 0x03) != DAC33_OSCSTATUS_NORMAL) && timeout--);
+ if ((reg & 0x03) != DAC33_OSCSTATUS_NORMAL)
+ dev_err(codec->dev,
+ "internal oscillator calibration failed\n");
+}
+
+static int dac33_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+ /* Stream started, save the substream pointer */
+ dac33->substream = substream;
+
+ snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24);
+
+ return 0;
+}
+
+static void dac33_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+ dac33->substream = NULL;
+}
+
+#define CALC_BURST_RATE(bclkdiv, bclk_per_sample) \
+ (BURST_BASEFREQ_HZ / bclkdiv / bclk_per_sample)
+static int dac33_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+ /* Check parameters for validity */
+ switch (params_rate(params)) {
+ case 44100:
+ case 48000:
+ break;
+ default:
+ dev_err(codec->dev, "unsupported rate %d\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dac33->fifo_size = DAC33_FIFO_SIZE_16BIT;
+ dac33->burst_rate = CALC_BURST_RATE(dac33->burst_bclkdiv, 32);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ dac33->fifo_size = DAC33_FIFO_SIZE_24BIT;
+ dac33->burst_rate = CALC_BURST_RATE(dac33->burst_bclkdiv, 64);
+ break;
+ default:
+ dev_err(codec->dev, "unsupported format %d\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#define CALC_OSCSET(rate, refclk) ( \
+ ((((rate * 10000) / refclk) * 4096) + 7000) / 10000)
+#define CALC_RATIOSET(rate, refclk) ( \
+ ((((refclk * 100000) / rate) * 16384) + 50000) / 100000)
+
+/*
+ * tlv320dac33 is strict on the sequence of the register writes, if the register
+ * writes happens in different order, than dac33 might end up in unknown state.
+ * Use the known, working sequence of register writes to initialize the dac33.
+ */
+static int dac33_prepare_chip(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ unsigned int oscset, ratioset, pwr_ctrl, reg_tmp;
+ u8 aictrl_a, aictrl_b, fifoctrl_a;
+
+ switch (substream->runtime->rate) {
+ case 44100:
+ case 48000:
+ oscset = CALC_OSCSET(substream->runtime->rate, dac33->refclk);
+ ratioset = CALC_RATIOSET(substream->runtime->rate,
+ dac33->refclk);
+ break;
+ default:
+ dev_err(codec->dev, "unsupported rate %d\n",
+ substream->runtime->rate);
+ return -EINVAL;
+ }
+
+
+ aictrl_a = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A);
+ aictrl_a &= ~(DAC33_NCYCL_MASK | DAC33_WLEN_MASK);
+ /* Read FIFO control A, and clear FIFO flush bit */
+ fifoctrl_a = dac33_read_reg_cache(codec, DAC33_FIFO_CTRL_A);
+ fifoctrl_a &= ~DAC33_FIFOFLUSH;
+
+ fifoctrl_a &= ~DAC33_WIDTH;
+ switch (substream->runtime->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ aictrl_a |= (DAC33_NCYCL_16 | DAC33_WLEN_16);
+ fifoctrl_a |= DAC33_WIDTH;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ aictrl_a |= (DAC33_NCYCL_32 | DAC33_WLEN_24);
+ break;
+ default:
+ dev_err(codec->dev, "unsupported format %d\n",
+ substream->runtime->format);
+ return -EINVAL;
+ }
+
+ mutex_lock(&dac33->mutex);
+
+ if (!dac33->chip_power) {
+ /*
+ * Chip is not powered yet.
+ * Do the init in the dac33_set_bias_level later.
+ */
+ mutex_unlock(&dac33->mutex);
+ return 0;
+ }
+
+ dac33_soft_power(codec, 0);
+ dac33_soft_power(codec, 1);
+
+ reg_tmp = dac33_read_reg_cache(codec, DAC33_INT_OSC_CTRL);
+ dac33_write(codec, DAC33_INT_OSC_CTRL, reg_tmp);
+
+ /* Write registers 0x08 and 0x09 (MSB, LSB) */
+ dac33_write16(codec, DAC33_INT_OSC_FREQ_RAT_A, oscset);
+
+ /* OSC calibration time */
+ dac33_write(codec, DAC33_CALIB_TIME, 96);
+
+ /* adjustment treshold & step */
+ dac33_write(codec, DAC33_INT_OSC_CTRL_B, DAC33_ADJTHRSHLD(2) |
+ DAC33_ADJSTEP(1));
+
+ /* div=4 / gain=1 / div */
+ dac33_write(codec, DAC33_INT_OSC_CTRL_C, DAC33_REFDIV(4));
+
+ pwr_ctrl = dac33_read_reg_cache(codec, DAC33_PWR_CTRL);
+ pwr_ctrl |= DAC33_OSCPDNB | DAC33_DACRPDNB | DAC33_DACLPDNB;
+ dac33_write(codec, DAC33_PWR_CTRL, pwr_ctrl);
+
+ dac33_oscwait(codec);
+
+ if (dac33->fifo_mode) {
+ /* Generic for all FIFO modes */
+ /* 50-51 : ASRC Control registers */
+ dac33_write(codec, DAC33_ASRC_CTRL_A, DAC33_SRCLKDIV(1));
+ dac33_write(codec, DAC33_ASRC_CTRL_B, 1); /* ??? */
+
+ /* Write registers 0x34 and 0x35 (MSB, LSB) */
+ dac33_write16(codec, DAC33_SRC_REF_CLK_RATIO_A, ratioset);
+
+ /* Set interrupts to high active */
+ dac33_write(codec, DAC33_INTP_CTRL_A, DAC33_INTPM_AHIGH);
+ } else {
+ /* FIFO bypass mode */
+ /* 50-51 : ASRC Control registers */
+ dac33_write(codec, DAC33_ASRC_CTRL_A, DAC33_SRCBYP);
+ dac33_write(codec, DAC33_ASRC_CTRL_B, 0); /* ??? */
+ }
+
+ /* Interrupt behaviour configuration */
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
+ dac33_write(codec, DAC33_FIFO_IRQ_MODE_B,
+ DAC33_ATM(DAC33_FIFO_IRQ_MODE_LEVEL));
+ break;
+ case DAC33_FIFO_MODE7:
+ dac33_write(codec, DAC33_FIFO_IRQ_MODE_A,
+ DAC33_UTM(DAC33_FIFO_IRQ_MODE_LEVEL));
+ break;
+ default:
+ /* in FIFO bypass mode, the interrupts are not used */
+ break;
+ }
+
+ aictrl_b = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B);
+
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
+ /*
+ * For mode1:
+ * Disable the FIFO bypass (Enable the use of FIFO)
+ * Select nSample mode
+ * BCLK is only running when data is needed by DAC33
+ */
+ fifoctrl_a &= ~DAC33_FBYPAS;
+ fifoctrl_a &= ~DAC33_FAUTO;
+ if (dac33->keep_bclk)
+ aictrl_b |= DAC33_BCLKON;
+ else
+ aictrl_b &= ~DAC33_BCLKON;
+ break;
+ case DAC33_FIFO_MODE7:
+ /*
+ * For mode1:
+ * Disable the FIFO bypass (Enable the use of FIFO)
+ * Select Threshold mode
+ * BCLK is only running when data is needed by DAC33
+ */
+ fifoctrl_a &= ~DAC33_FBYPAS;
+ fifoctrl_a |= DAC33_FAUTO;
+ if (dac33->keep_bclk)
+ aictrl_b |= DAC33_BCLKON;
+ else
+ aictrl_b &= ~DAC33_BCLKON;
+ break;
+ default:
+ /*
+ * For FIFO bypass mode:
+ * Enable the FIFO bypass (Disable the FIFO use)
+ * Set the BCLK as continuous
+ */
+ fifoctrl_a |= DAC33_FBYPAS;
+ aictrl_b |= DAC33_BCLKON;
+ break;
+ }
+
+ dac33_write(codec, DAC33_FIFO_CTRL_A, fifoctrl_a);
+ dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_A, aictrl_a);
+ dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_B, aictrl_b);
+
+ /*
+ * BCLK divide ratio
+ * 0: 1.5
+ * 1: 1
+ * 2: 2
+ * ...
+ * 254: 254
+ * 255: 255
+ */
+ if (dac33->fifo_mode)
+ dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C,
+ dac33->burst_bclkdiv);
+ else
+ if (substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE)
+ dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C, 32);
+ else
+ dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C, 16);
+
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
+ dac33_write16(codec, DAC33_ATHR_MSB,
+ DAC33_THRREG(dac33->alarm_threshold));
+ break;
+ case DAC33_FIFO_MODE7:
+ /*
+ * Configure the threshold levels, and leave 10 sample space
+ * at the bottom, and also at the top of the FIFO
+ */
+ dac33_write16(codec, DAC33_UTHR_MSB, DAC33_THRREG(dac33->uthr));
+ dac33_write16(codec, DAC33_LTHR_MSB,
+ DAC33_THRREG(DAC33_MODE7_MARGIN));
+ break;
+ default:
+ break;
+ }
+
+ mutex_unlock(&dac33->mutex);
+
+ return 0;
+}
+
+static void dac33_calculate_times(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ unsigned int period_size = substream->runtime->period_size;
+ unsigned int rate = substream->runtime->rate;
+ unsigned int nsample_limit;
+
+ /* In bypass mode we don't need to calculate */
+ if (!dac33->fifo_mode)
+ return;
+
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
+ /* Number of samples under i2c latency */
+ dac33->alarm_threshold = US_TO_SAMPLES(rate,
+ dac33->mode1_latency);
+ nsample_limit = dac33->fifo_size - dac33->alarm_threshold;
+
+ if (period_size <= dac33->alarm_threshold)
+ /*
+ * Configure nSamaple to number of periods,
+ * which covers the latency requironment.
+ */
+ dac33->nsample = period_size *
+ ((dac33->alarm_threshold / period_size) +
+ (dac33->alarm_threshold % period_size ?
+ 1 : 0));
+ else if (period_size > nsample_limit)
+ dac33->nsample = nsample_limit;
+ else
+ dac33->nsample = period_size;
+
+ dac33->mode1_us_burst = SAMPLES_TO_US(dac33->burst_rate,
+ dac33->nsample);
+ dac33->t_stamp1 = 0;
+ dac33->t_stamp2 = 0;
+ break;
+ case DAC33_FIFO_MODE7:
+ dac33->uthr = UTHR_FROM_PERIOD_SIZE(period_size, rate,
+ dac33->burst_rate) + 9;
+ if (dac33->uthr > (dac33->fifo_size - DAC33_MODE7_MARGIN))
+ dac33->uthr = dac33->fifo_size - DAC33_MODE7_MARGIN;
+ if (dac33->uthr < (DAC33_MODE7_MARGIN + 10))
+ dac33->uthr = (DAC33_MODE7_MARGIN + 10);
+
+ dac33->mode7_us_to_lthr =
+ SAMPLES_TO_US(substream->runtime->rate,
+ dac33->uthr - DAC33_MODE7_MARGIN + 1);
+ dac33->t_stamp1 = 0;
+ break;
+ default:
+ break;
+ }
+
+}
+
+static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (dac33->fifo_mode) {
+ dac33->state = DAC33_PREFILL;
+ queue_work(dac33->dac33_wq, &dac33->work);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (dac33->fifo_mode) {
+ dac33->state = DAC33_FLUSH;
+ queue_work(dac33->dac33_wq, &dac33->work);
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_sframes_t dac33_dai_delay(
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ unsigned long long t0, t1, t_now;
+ unsigned int time_delta, uthr;
+ int samples_out, samples_in, samples;
+ snd_pcm_sframes_t delay = 0;
+ unsigned long flags;
+
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_BYPASS:
+ break;
+ case DAC33_FIFO_MODE1:
+ spin_lock_irqsave(&dac33->lock, flags);
+ t0 = dac33->t_stamp1;
+ t1 = dac33->t_stamp2;
+ spin_unlock_irqrestore(&dac33->lock, flags);
+ t_now = ktime_to_us(ktime_get());
+
+ /* We have not started to fill the FIFO yet, delay is 0 */
+ if (!t1)
+ goto out;
+
+ if (t0 > t1) {
+ /*
+ * Phase 1:
+ * After Alarm threshold, and before nSample write
+ */
+ time_delta = t_now - t0;
+ samples_out = time_delta ? US_TO_SAMPLES(
+ substream->runtime->rate,
+ time_delta) : 0;
+
+ if (likely(dac33->alarm_threshold > samples_out))
+ delay = dac33->alarm_threshold - samples_out;
+ else
+ delay = 0;
+ } else if ((t_now - t1) <= dac33->mode1_us_burst) {
+ /*
+ * Phase 2:
+ * After nSample write (during burst operation)
+ */
+ time_delta = t_now - t0;
+ samples_out = time_delta ? US_TO_SAMPLES(
+ substream->runtime->rate,
+ time_delta) : 0;
+
+ time_delta = t_now - t1;
+ samples_in = time_delta ? US_TO_SAMPLES(
+ dac33->burst_rate,
+ time_delta) : 0;
+
+ samples = dac33->alarm_threshold;
+ samples += (samples_in - samples_out);
+
+ if (likely(samples > 0))
+ delay = samples;
+ else
+ delay = 0;
+ } else {
+ /*
+ * Phase 3:
+ * After burst operation, before next alarm threshold
+ */
+ time_delta = t_now - t0;
+ samples_out = time_delta ? US_TO_SAMPLES(
+ substream->runtime->rate,
+ time_delta) : 0;
+
+ samples_in = dac33->nsample;
+ samples = dac33->alarm_threshold;
+ samples += (samples_in - samples_out);
+
+ if (likely(samples > 0))
+ delay = samples > dac33->fifo_size ?
+ dac33->fifo_size : samples;
+ else
+ delay = 0;
+ }
+ break;
+ case DAC33_FIFO_MODE7:
+ spin_lock_irqsave(&dac33->lock, flags);
+ t0 = dac33->t_stamp1;
+ uthr = dac33->uthr;
+ spin_unlock_irqrestore(&dac33->lock, flags);
+ t_now = ktime_to_us(ktime_get());
+
+ /* We have not started to fill the FIFO yet, delay is 0 */
+ if (!t0)
+ goto out;
+
+ if (t_now <= t0) {
+ /*
+ * Either the timestamps are messed or equal. Report
+ * maximum delay
+ */
+ delay = uthr;
+ goto out;
+ }
+
+ time_delta = t_now - t0;
+ if (time_delta <= dac33->mode7_us_to_lthr) {
+ /*
+ * Phase 1:
+ * After burst (draining phase)
+ */
+ samples_out = US_TO_SAMPLES(
+ substream->runtime->rate,
+ time_delta);
+
+ if (likely(uthr > samples_out))
+ delay = uthr - samples_out;
+ else
+ delay = 0;
+ } else {
+ /*
+ * Phase 2:
+ * During burst operation
+ */
+ time_delta = time_delta - dac33->mode7_us_to_lthr;
+
+ samples_out = US_TO_SAMPLES(
+ substream->runtime->rate,
+ time_delta);
+ samples_in = US_TO_SAMPLES(
+ dac33->burst_rate,
+ time_delta);
+ delay = DAC33_MODE7_MARGIN + samples_in - samples_out;
+
+ if (unlikely(delay > uthr))
+ delay = uthr;
+ }
+ break;
+ default:
+ dev_warn(codec->dev, "Unhandled FIFO mode: %d\n",
+ dac33->fifo_mode);
+ break;
+ }
+out:
+ return delay;
+}
+
+static int dac33_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ u8 ioc_reg, asrcb_reg;
+
+ ioc_reg = dac33_read_reg_cache(codec, DAC33_INT_OSC_CTRL);
+ asrcb_reg = dac33_read_reg_cache(codec, DAC33_ASRC_CTRL_B);
+ switch (clk_id) {
+ case TLV320DAC33_MCLK:
+ ioc_reg |= DAC33_REFSEL;
+ asrcb_reg |= DAC33_SRCREFSEL;
+ break;
+ case TLV320DAC33_SLEEPCLK:
+ ioc_reg &= ~DAC33_REFSEL;
+ asrcb_reg &= ~DAC33_SRCREFSEL;
+ break;
+ default:
+ dev_err(codec->dev, "Invalid clock ID (%d)\n", clk_id);
+ break;
+ }
+ dac33->refclk = freq;
+
+ dac33_write_reg_cache(codec, DAC33_INT_OSC_CTRL, ioc_reg);
+ dac33_write_reg_cache(codec, DAC33_ASRC_CTRL_B, asrcb_reg);
+
+ return 0;
+}
+
+static int dac33_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ u8 aictrl_a, aictrl_b;
+
+ aictrl_a = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A);
+ aictrl_b = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B);
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* Codec Master */
+ aictrl_a |= (DAC33_MSBCLK | DAC33_MSWCLK);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* Codec Slave */
+ if (dac33->fifo_mode) {
+ dev_err(codec->dev, "FIFO mode requires master mode\n");
+ return -EINVAL;
+ } else
+ aictrl_a &= ~(DAC33_MSBCLK | DAC33_MSWCLK);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ aictrl_a &= ~DAC33_AFMT_MASK;
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ aictrl_a |= DAC33_AFMT_I2S;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ aictrl_a |= DAC33_AFMT_DSP;
+ aictrl_b &= ~DAC33_DATA_DELAY_MASK;
+ aictrl_b |= DAC33_DATA_DELAY(0);
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ aictrl_a |= DAC33_AFMT_RIGHT_J;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aictrl_a |= DAC33_AFMT_LEFT_J;
+ break;
+ default:
+ dev_err(codec->dev, "Unsupported format (%u)\n",
+ fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+ return -EINVAL;
+ }
+
+ dac33_write_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A, aictrl_a);
+ dac33_write_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B, aictrl_b);
+
+ return 0;
+}
+
+static int dac33_soc_probe(struct snd_soc_codec *codec)
+{
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ codec->control_data = dac33->control_data;
+ codec->hw_write = (hw_write_t) i2c_master_send;
+ codec->dapm.idle_bias_off = 1;
+ dac33->codec = codec;
+
+ /* Read the tlv320dac33 ID registers */
+ ret = dac33_hard_power(codec, 1);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to power up codec: %d\n", ret);
+ goto err_power;
+ }
+ ret = dac33_read_id(codec);
+ dac33_hard_power(codec, 0);
+
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read chip ID: %d\n", ret);
+ ret = -ENODEV;
+ goto err_power;
+ }
+
+ /* Check if the IRQ number is valid and request it */
+ if (dac33->irq >= 0) {
+ ret = request_irq(dac33->irq, dac33_interrupt_handler,
+ IRQF_TRIGGER_RISING | IRQF_DISABLED,
+ codec->name, codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Could not request IRQ%d (%d)\n",
+ dac33->irq, ret);
+ dac33->irq = -1;
+ }
+ if (dac33->irq != -1) {
+ /* Setup work queue */
+ dac33->dac33_wq =
+ create_singlethread_workqueue("tlv320dac33");
+ if (dac33->dac33_wq == NULL) {
+ free_irq(dac33->irq, codec);
+ return -ENOMEM;
+ }
+
+ INIT_WORK(&dac33->work, dac33_work);
+ }
+ }
+
+ snd_soc_add_controls(codec, dac33_snd_controls,
+ ARRAY_SIZE(dac33_snd_controls));
+ /* Only add the FIFO controls, if we have valid IRQ number */
+ if (dac33->irq >= 0)
+ snd_soc_add_controls(codec, dac33_mode_snd_controls,
+ ARRAY_SIZE(dac33_mode_snd_controls));
+
+ dac33_add_widgets(codec);
+
+err_power:
+ return ret;
+}
+
+static int dac33_soc_remove(struct snd_soc_codec *codec)
+{
+ struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+ dac33_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ if (dac33->irq >= 0) {
+ free_irq(dac33->irq, dac33->codec);
+ destroy_workqueue(dac33->dac33_wq);
+ }
+ return 0;
+}
+
+static int dac33_soc_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ dac33_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int dac33_soc_resume(struct snd_soc_codec *codec)
+{
+ dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_tlv320dac33 = {
+ .read = dac33_read_reg_cache,
+ .write = dac33_write_locked,
+ .set_bias_level = dac33_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(dac33_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = dac33_reg,
+ .probe = dac33_soc_probe,
+ .remove = dac33_soc_remove,
+ .suspend = dac33_soc_suspend,
+ .resume = dac33_soc_resume,
+};
+
+#define DAC33_RATES (SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+#define DAC33_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops dac33_dai_ops = {
+ .startup = dac33_startup,
+ .shutdown = dac33_shutdown,
+ .hw_params = dac33_hw_params,
+ .trigger = dac33_pcm_trigger,
+ .delay = dac33_dai_delay,
+ .set_sysclk = dac33_set_dai_sysclk,
+ .set_fmt = dac33_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver dac33_dai = {
+ .name = "tlv320dac33-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = DAC33_RATES,
+ .formats = DAC33_FORMATS,},
+ .ops = &dac33_dai_ops,
+};
+
+static int __devinit dac33_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct tlv320dac33_platform_data *pdata;
+ struct tlv320dac33_priv *dac33;
+ int ret, i;
+
+ if (client->dev.platform_data == NULL) {
+ dev_err(&client->dev, "Platform data not set\n");
+ return -ENODEV;
+ }
+ pdata = client->dev.platform_data;
+
+ dac33 = kzalloc(sizeof(struct tlv320dac33_priv), GFP_KERNEL);
+ if (dac33 == NULL)
+ return -ENOMEM;
+
+ dac33->control_data = client;
+ mutex_init(&dac33->mutex);
+ spin_lock_init(&dac33->lock);
+
+ i2c_set_clientdata(client, dac33);
+
+ dac33->power_gpio = pdata->power_gpio;
+ dac33->burst_bclkdiv = pdata->burst_bclkdiv;
+ dac33->keep_bclk = pdata->keep_bclk;
+ dac33->mode1_latency = pdata->mode1_latency;
+ if (!dac33->mode1_latency)
+ dac33->mode1_latency = 10000; /* 10ms */
+ dac33->irq = client->irq;
+ /* Disable FIFO use by default */
+ dac33->fifo_mode = DAC33_FIFO_BYPASS;
+
+ /* Check if the reset GPIO number is valid and request it */
+ if (dac33->power_gpio >= 0) {
+ ret = gpio_request(dac33->power_gpio, "tlv320dac33 reset");
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Failed to request reset GPIO (%d)\n",
+ dac33->power_gpio);
+ goto err_gpio;
+ }
+ gpio_direction_output(dac33->power_gpio, 0);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(dac33->supplies); i++)
+ dac33->supplies[i].supply = dac33_supply_names[i];
+
+ ret = regulator_bulk_get(&client->dev, ARRAY_SIZE(dac33->supplies),
+ dac33->supplies);
+
+ if (ret != 0) {
+ dev_err(&client->dev, "Failed to request supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ ret = snd_soc_register_codec(&client->dev,
+ &soc_codec_dev_tlv320dac33, &dac33_dai, 1);
+ if (ret < 0)
+ goto err_register;
+
+ return ret;
+err_register:
+ regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies);
+err_get:
+ if (dac33->power_gpio >= 0)
+ gpio_free(dac33->power_gpio);
+err_gpio:
+ kfree(dac33);
+ return ret;
+}
+
+static int __devexit dac33_i2c_remove(struct i2c_client *client)
+{
+ struct tlv320dac33_priv *dac33 = i2c_get_clientdata(client);
+
+ if (unlikely(dac33->chip_power))
+ dac33_hard_power(dac33->codec, 0);
+
+ if (dac33->power_gpio >= 0)
+ gpio_free(dac33->power_gpio);
+
+ regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies);
+
+ snd_soc_unregister_codec(&client->dev);
+ kfree(dac33);
+
+ return 0;
+}
+
+static const struct i2c_device_id tlv320dac33_i2c_id[] = {
+ {
+ .name = "tlv320dac33",
+ .driver_data = 0,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, tlv320dac33_i2c_id);
+
+static struct i2c_driver tlv320dac33_i2c_driver = {
+ .driver = {
+ .name = "tlv320dac33-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = dac33_i2c_probe,
+ .remove = __devexit_p(dac33_i2c_remove),
+ .id_table = tlv320dac33_i2c_id,
+};
+
+static int __init dac33_module_init(void)
+{
+ int r;
+ r = i2c_add_driver(&tlv320dac33_i2c_driver);
+ if (r < 0) {
+ printk(KERN_ERR "DAC33: driver registration failed\n");
+ return r;
+ }
+ return 0;
+}
+module_init(dac33_module_init);
+
+static void __exit dac33_module_exit(void)
+{
+ i2c_del_driver(&tlv320dac33_i2c_driver);
+}
+module_exit(dac33_module_exit);
+
+
+MODULE_DESCRIPTION("ASoC TLV320DAC33 codec driver");
+MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tlv320dac33.h b/sound/soc/codecs/tlv320dac33.h
new file mode 100644
index 00000000..ed696707
--- /dev/null
+++ b/sound/soc/codecs/tlv320dac33.h
@@ -0,0 +1,264 @@
+/*
+ * ALSA SoC Texas Instruments TLV320DAC33 codec driver
+ *
+ * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * Copyright: (C) 2009 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TLV320DAC33_H
+#define __TLV320DAC33_H
+
+#define DAC33_PAGE_SELECT 0x00
+#define DAC33_PWR_CTRL 0x01
+#define DAC33_PLL_CTRL_A 0x02
+#define DAC33_PLL_CTRL_B 0x03
+#define DAC33_PLL_CTRL_C 0x04
+#define DAC33_PLL_CTRL_D 0x05
+#define DAC33_PLL_CTRL_E 0x06
+#define DAC33_INT_OSC_CTRL 0x07
+#define DAC33_INT_OSC_FREQ_RAT_A 0x08
+#define DAC33_INT_OSC_FREQ_RAT_B 0x09
+#define DAC33_INT_OSC_DAC_RATIO_SET 0x0A
+#define DAC33_CALIB_TIME 0x0B
+#define DAC33_INT_OSC_CTRL_B 0x0C
+#define DAC33_INT_OSC_CTRL_C 0x0D
+#define DAC33_INT_OSC_STATUS 0x0E
+#define DAC33_INT_OSC_DAC_RATIO_READ 0x0F
+#define DAC33_INT_OSC_FREQ_RAT_READ_A 0x10
+#define DAC33_INT_OSC_FREQ_RAT_READ_B 0x11
+#define DAC33_SER_AUDIOIF_CTRL_A 0x12
+#define DAC33_SER_AUDIOIF_CTRL_B 0x13
+#define DAC33_SER_AUDIOIF_CTRL_C 0x14
+#define DAC33_FIFO_CTRL_A 0x15
+#define DAC33_UTHR_MSB 0x16
+#define DAC33_UTHR_LSB 0x17
+#define DAC33_ATHR_MSB 0x18
+#define DAC33_ATHR_LSB 0x19
+#define DAC33_LTHR_MSB 0x1A
+#define DAC33_LTHR_LSB 0x1B
+#define DAC33_PREFILL_MSB 0x1C
+#define DAC33_PREFILL_LSB 0x1D
+#define DAC33_NSAMPLE_MSB 0x1E
+#define DAC33_NSAMPLE_LSB 0x1F
+#define DAC33_FIFO_WPTR_MSB 0x20
+#define DAC33_FIFO_WPTR_LSB 0x21
+#define DAC33_FIFO_RPTR_MSB 0x22
+#define DAC33_FIFO_RPTR_LSB 0x23
+#define DAC33_FIFO_DEPTH_MSB 0x24
+#define DAC33_FIFO_DEPTH_LSB 0x25
+#define DAC33_SAMPLES_REMAINING_MSB 0x26
+#define DAC33_SAMPLES_REMAINING_LSB 0x27
+#define DAC33_FIFO_IRQ_FLAG 0x28
+#define DAC33_FIFO_IRQ_MASK 0x29
+#define DAC33_FIFO_IRQ_MODE_A 0x2A
+#define DAC33_FIFO_IRQ_MODE_B 0x2B
+#define DAC33_DAC_CTRL_A 0x2C
+#define DAC33_DAC_CTRL_B 0x2D
+#define DAC33_DAC_CTRL_C 0x2E
+#define DAC33_LDAC_DIG_VOL_CTRL 0x2F
+#define DAC33_RDAC_DIG_VOL_CTRL 0x30
+#define DAC33_DAC_STATUS_FLAGS 0x31
+#define DAC33_ASRC_CTRL_A 0x32
+#define DAC33_ASRC_CTRL_B 0x33
+#define DAC33_SRC_REF_CLK_RATIO_A 0x34
+#define DAC33_SRC_REF_CLK_RATIO_B 0x35
+#define DAC33_SRC_EST_REF_CLK_RATIO_A 0x36
+#define DAC33_SRC_EST_REF_CLK_RATIO_B 0x37
+#define DAC33_INTP_CTRL_A 0x38
+#define DAC33_INTP_CTRL_B 0x39
+/* Registers 0x3A - 0x3F Reserved */
+#define DAC33_LDAC_PWR_CTRL 0x40
+#define DAC33_RDAC_PWR_CTRL 0x41
+#define DAC33_OUT_AMP_CM_CTRL 0x42
+#define DAC33_OUT_AMP_PWR_CTRL 0x43
+#define DAC33_OUT_AMP_CTRL 0x44
+#define DAC33_LINEL_TO_LLO_VOL 0x45
+/* Registers 0x45 - 0x47 Reserved */
+#define DAC33_LINER_TO_RLO_VOL 0x48
+#define DAC33_ANA_VOL_SOFT_STEP_CTRL 0x49
+#define DAC33_OSC_TRIM 0x4A
+/* Registers 0x4B - 0x7C Reserved */
+#define DAC33_DEVICE_ID_MSB 0x7D
+#define DAC33_DEVICE_ID_LSB 0x7E
+#define DAC33_DEVICE_REV_ID 0x7F
+
+#define DAC33_CACHEREGNUM 128
+
+/* Bit definitions */
+
+/* DAC33_PWR_CTRL (0x01) */
+#define DAC33_DACRPDNB (0x01 << 0)
+#define DAC33_DACLPDNB (0x01 << 1)
+#define DAC33_OSCPDNB (0x01 << 2)
+#define DAC33_PLLPDNB (0x01 << 3)
+#define DAC33_PDNALLB (0x01 << 4)
+#define DAC33_SOFT_RESET (0x01 << 7)
+
+/* DAC33_INT_OSC_CTRL (0x07) */
+#define DAC33_REFSEL (0x01 << 1)
+
+/* DAC33_INT_OSC_CTRL_B (0x0C) */
+#define DAC33_ADJSTEP(x) (x << 0)
+#define DAC33_ADJTHRSHLD(x) (x << 4)
+
+/* DAC33_INT_OSC_CTRL_C (0x0D) */
+#define DAC33_REFDIV(x) (x << 4)
+
+/* DAC33_INT_OSC_STATUS (0x0E) */
+#define DAC33_OSCSTATUS_IDLE_CALIB (0x00)
+#define DAC33_OSCSTATUS_NORMAL (0x01)
+#define DAC33_OSCSTATUS_ADJUSTMENT (0x03)
+#define DAC33_OSCSTATUS_NOT_USED (0x02)
+
+/* DAC33_SER_AUDIOIF_CTRL_A (0x12) */
+#define DAC33_MSWCLK (0x01 << 0)
+#define DAC33_MSBCLK (0x01 << 1)
+#define DAC33_AFMT_MASK (0x03 << 2)
+#define DAC33_AFMT_I2S (0x00 << 2)
+#define DAC33_AFMT_DSP (0x01 << 2)
+#define DAC33_AFMT_RIGHT_J (0x02 << 2)
+#define DAC33_AFMT_LEFT_J (0x03 << 2)
+#define DAC33_WLEN_MASK (0x03 << 4)
+#define DAC33_WLEN_16 (0x00 << 4)
+#define DAC33_WLEN_20 (0x01 << 4)
+#define DAC33_WLEN_24 (0x02 << 4)
+#define DAC33_WLEN_32 (0x03 << 4)
+#define DAC33_NCYCL_MASK (0x03 << 6)
+#define DAC33_NCYCL_16 (0x00 << 6)
+#define DAC33_NCYCL_20 (0x01 << 6)
+#define DAC33_NCYCL_24 (0x02 << 6)
+#define DAC33_NCYCL_32 (0x03 << 6)
+
+/* DAC33_SER_AUDIOIF_CTRL_B (0x13) */
+#define DAC33_DATA_DELAY_MASK (0x03 << 2)
+#define DAC33_DATA_DELAY(x) (x << 2)
+#define DAC33_BCLKON (0x01 << 5)
+
+/* DAC33_FIFO_CTRL_A (0x15) */
+#define DAC33_WIDTH (0x01 << 0)
+#define DAC33_FBYPAS (0x01 << 1)
+#define DAC33_FAUTO (0x01 << 2)
+#define DAC33_FIFOFLUSH (0x01 << 3)
+
+/*
+ * UTHR, ATHR, LTHR, PREFILL, NSAMPLE (0x16 - 0x1F)
+ * 13-bit values
+*/
+#define DAC33_THRREG(x) (((x) & 0x1FFF) << 3)
+
+/* DAC33_FIFO_IRQ_MASK (0x29) */
+#define DAC33_MNS (0x01 << 0)
+#define DAC33_MPS (0x01 << 1)
+#define DAC33_MAT (0x01 << 2)
+#define DAC33_MLT (0x01 << 3)
+#define DAC33_MUT (0x01 << 4)
+#define DAC33_MUF (0x01 << 5)
+#define DAC33_MOF (0x01 << 6)
+
+#define DAC33_FIFO_IRQ_MODE_MASK (0x03)
+#define DAC33_FIFO_IRQ_MODE_RISING (0x00)
+#define DAC33_FIFO_IRQ_MODE_FALLING (0x01)
+#define DAC33_FIFO_IRQ_MODE_LEVEL (0x02)
+#define DAC33_FIFO_IRQ_MODE_EDGE (0x03)
+
+/* DAC33_FIFO_IRQ_MODE_A (0x2A) */
+#define DAC33_UTM(x) (x << 0)
+#define DAC33_UFM(x) (x << 2)
+#define DAC33_OFM(x) (x << 4)
+
+/* DAC33_FIFO_IRQ_MODE_B (0x2B) */
+#define DAC33_NSM(x) (x << 0)
+#define DAC33_PSM(x) (x << 2)
+#define DAC33_ATM(x) (x << 4)
+#define DAC33_LTM(x) (x << 6)
+
+/* DAC33_DAC_CTRL_A (0x2C) */
+#define DAC33_DACRATE(x) (x << 0)
+#define DAC33_DACDUAL (0x01 << 4)
+#define DAC33_DACLKSEL_MASK (0x03 << 5)
+#define DAC33_DACLKSEL_INTSOC (0x00 << 5)
+#define DAC33_DACLKSEL_PLL (0x01 << 5)
+#define DAC33_DACLKSEL_MCLK (0x02 << 5)
+#define DAC33_DACLKSEL_BCLK (0x03 << 5)
+
+/* DAC33_DAC_CTRL_B (0x2D) */
+#define DAC33_DACSRCR_MASK (0x03 << 0)
+#define DAC33_DACSRCR_MUTE (0x00 << 0)
+#define DAC33_DACSRCR_RIGHT (0x01 << 0)
+#define DAC33_DACSRCR_LEFT (0x02 << 0)
+#define DAC33_DACSRCR_MONOMIX (0x03 << 0)
+#define DAC33_DACSRCL_MASK (0x03 << 2)
+#define DAC33_DACSRCL_MUTE (0x00 << 2)
+#define DAC33_DACSRCL_LEFT (0x01 << 2)
+#define DAC33_DACSRCL_RIGHT (0x02 << 2)
+#define DAC33_DACSRCL_MONOMIX (0x03 << 2)
+#define DAC33_DVOLSTEP_MASK (0x03 << 4)
+#define DAC33_DVOLSTEP_SS_PERFS (0x00 << 4)
+#define DAC33_DVOLSTEP_SS_PER2FS (0x01 << 4)
+#define DAC33_DVOLSTEP_SS_DISABLED (0x02 << 4)
+#define DAC33_DVOLCTRL_MASK (0x03 << 6)
+#define DAC33_DVOLCTRL_LR_INDEPENDENT1 (0x00 << 6)
+#define DAC33_DVOLCTRL_LR_RIGHT_CONTROL (0x01 << 6)
+#define DAC33_DVOLCTRL_LR_LEFT_CONTROL (0x02 << 6)
+#define DAC33_DVOLCTRL_LR_INDEPENDENT2 (0x03 << 6)
+
+/* DAC33_DAC_CTRL_C (0x2E) */
+#define DAC33_DEEMENR (0x01 << 0)
+#define DAC33_EFFENR (0x01 << 1)
+#define DAC33_DEEMENL (0x01 << 2)
+#define DAC33_EFFENL (0x01 << 3)
+#define DAC33_EN3D (0x01 << 4)
+#define DAC33_RESYNMUTE (0x01 << 5)
+#define DAC33_RESYNEN (0x01 << 6)
+
+/* DAC33_ASRC_CTRL_A (0x32) */
+#define DAC33_SRCBYP (0x01 << 0)
+#define DAC33_SRCLKSEL_MASK (0x03 << 1)
+#define DAC33_SRCLKSEL_INTSOC (0x00 << 1)
+#define DAC33_SRCLKSEL_PLL (0x01 << 1)
+#define DAC33_SRCLKSEL_MCLK (0x02 << 1)
+#define DAC33_SRCLKSEL_BCLK (0x03 << 1)
+#define DAC33_SRCLKDIV(x) (x << 3)
+
+/* DAC33_ASRC_CTRL_B (0x33) */
+#define DAC33_SRCSETUP(x) (x << 0)
+#define DAC33_SRCREFSEL (0x01 << 4)
+#define DAC33_SRCREFDIV(x) (x << 5)
+
+/* DAC33_INTP_CTRL_A (0x38) */
+#define DAC33_INTPSEL (0x01 << 0)
+#define DAC33_INTPM_MASK (0x03 << 1)
+#define DAC33_INTPM_ALOW_OPENDRAIN (0x00 << 1)
+#define DAC33_INTPM_ALOW (0x01 << 1)
+#define DAC33_INTPM_AHIGH (0x02 << 1)
+
+/* DAC33_LDAC_PWR_CTRL (0x40) */
+/* DAC33_RDAC_PWR_CTRL (0x41) */
+#define DAC33_DACLRNUM (0x01 << 2)
+#define DAC33_LROUT_GAIN(x) (x << 0)
+
+/* DAC33_ANA_VOL_SOFT_STEP_CTRL (0x49) */
+#define DAC33_VOLCLKSEL (0x01 << 0)
+#define DAC33_VOLCLKEN (0x01 << 1)
+#define DAC33_VOLBYPASS (0x01 << 2)
+
+#define TLV320DAC33_MCLK 0
+#define TLV320DAC33_SLEEPCLK 1
+
+#endif /* __TLV320DAC33_H */
diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c
new file mode 100644
index 00000000..239e0c46
--- /dev/null
+++ b/sound/soc/codecs/tpa6130a2.c
@@ -0,0 +1,503 @@
+/*
+ * ALSA SoC Texas Instruments TPA6130A2 headset stereo amplifier driver
+ *
+ * Copyright (C) Nokia Corporation
+ *
+ * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/tpa6130a2-plat.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "tpa6130a2.h"
+
+static struct i2c_client *tpa6130a2_client;
+
+/* This struct is used to save the context */
+struct tpa6130a2_data {
+ struct mutex mutex;
+ unsigned char regs[TPA6130A2_CACHEREGNUM];
+ struct regulator *supply;
+ int power_gpio;
+ u8 power_state:1;
+ enum tpa_model id;
+};
+
+static int tpa6130a2_i2c_read(int reg)
+{
+ struct tpa6130a2_data *data;
+ int val;
+
+ BUG_ON(tpa6130a2_client == NULL);
+ data = i2c_get_clientdata(tpa6130a2_client);
+
+ /* If powered off, return the cached value */
+ if (data->power_state) {
+ val = i2c_smbus_read_byte_data(tpa6130a2_client, reg);
+ if (val < 0)
+ dev_err(&tpa6130a2_client->dev, "Read failed\n");
+ else
+ data->regs[reg] = val;
+ } else {
+ val = data->regs[reg];
+ }
+
+ return val;
+}
+
+static int tpa6130a2_i2c_write(int reg, u8 value)
+{
+ struct tpa6130a2_data *data;
+ int val = 0;
+
+ BUG_ON(tpa6130a2_client == NULL);
+ data = i2c_get_clientdata(tpa6130a2_client);
+
+ if (data->power_state) {
+ val = i2c_smbus_write_byte_data(tpa6130a2_client, reg, value);
+ if (val < 0) {
+ dev_err(&tpa6130a2_client->dev, "Write failed\n");
+ return val;
+ }
+ }
+
+ /* Either powered on or off, we save the context */
+ data->regs[reg] = value;
+
+ return val;
+}
+
+static u8 tpa6130a2_read(int reg)
+{
+ struct tpa6130a2_data *data;
+
+ BUG_ON(tpa6130a2_client == NULL);
+ data = i2c_get_clientdata(tpa6130a2_client);
+
+ return data->regs[reg];
+}
+
+static int tpa6130a2_initialize(void)
+{
+ struct tpa6130a2_data *data;
+ int i, ret = 0;
+
+ BUG_ON(tpa6130a2_client == NULL);
+ data = i2c_get_clientdata(tpa6130a2_client);
+
+ for (i = 1; i < TPA6130A2_REG_VERSION; i++) {
+ ret = tpa6130a2_i2c_write(i, data->regs[i]);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static int tpa6130a2_power(u8 power)
+{
+ struct tpa6130a2_data *data;
+ u8 val;
+ int ret = 0;
+
+ BUG_ON(tpa6130a2_client == NULL);
+ data = i2c_get_clientdata(tpa6130a2_client);
+
+ mutex_lock(&data->mutex);
+ if (power == data->power_state)
+ goto exit;
+
+ if (power) {
+ ret = regulator_enable(data->supply);
+ if (ret != 0) {
+ dev_err(&tpa6130a2_client->dev,
+ "Failed to enable supply: %d\n", ret);
+ goto exit;
+ }
+ /* Power on */
+ if (data->power_gpio >= 0)
+ gpio_set_value(data->power_gpio, 1);
+
+ data->power_state = 1;
+ ret = tpa6130a2_initialize();
+ if (ret < 0) {
+ dev_err(&tpa6130a2_client->dev,
+ "Failed to initialize chip\n");
+ if (data->power_gpio >= 0)
+ gpio_set_value(data->power_gpio, 0);
+ regulator_disable(data->supply);
+ data->power_state = 0;
+ goto exit;
+ }
+ } else {
+ /* set SWS */
+ val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
+ val |= TPA6130A2_SWS;
+ tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
+
+ /* Power off */
+ if (data->power_gpio >= 0)
+ gpio_set_value(data->power_gpio, 0);
+
+ ret = regulator_disable(data->supply);
+ if (ret != 0) {
+ dev_err(&tpa6130a2_client->dev,
+ "Failed to disable supply: %d\n", ret);
+ goto exit;
+ }
+
+ data->power_state = 0;
+ }
+
+exit:
+ mutex_unlock(&data->mutex);
+ return ret;
+}
+
+static int tpa6130a2_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct tpa6130a2_data *data;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+
+ BUG_ON(tpa6130a2_client == NULL);
+ data = i2c_get_clientdata(tpa6130a2_client);
+
+ mutex_lock(&data->mutex);
+
+ ucontrol->value.integer.value[0] =
+ (tpa6130a2_read(reg) >> shift) & mask;
+
+ if (invert)
+ ucontrol->value.integer.value[0] =
+ max - ucontrol->value.integer.value[0];
+
+ mutex_unlock(&data->mutex);
+ return 0;
+}
+
+static int tpa6130a2_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct tpa6130a2_data *data;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+ unsigned int val = (ucontrol->value.integer.value[0] & mask);
+ unsigned int val_reg;
+
+ BUG_ON(tpa6130a2_client == NULL);
+ data = i2c_get_clientdata(tpa6130a2_client);
+
+ if (invert)
+ val = max - val;
+
+ mutex_lock(&data->mutex);
+
+ val_reg = tpa6130a2_read(reg);
+ if (((val_reg >> shift) & mask) == val) {
+ mutex_unlock(&data->mutex);
+ return 0;
+ }
+
+ val_reg &= ~(mask << shift);
+ val_reg |= val << shift;
+ tpa6130a2_i2c_write(reg, val_reg);
+
+ mutex_unlock(&data->mutex);
+
+ return 1;
+}
+
+/*
+ * TPA6130 volume. From -59.5 to 4 dB with increasing step size when going
+ * down in gain.
+ */
+static const unsigned int tpa6130_tlv[] = {
+ TLV_DB_RANGE_HEAD(10),
+ 0, 1, TLV_DB_SCALE_ITEM(-5950, 600, 0),
+ 2, 3, TLV_DB_SCALE_ITEM(-5000, 250, 0),
+ 4, 5, TLV_DB_SCALE_ITEM(-4550, 160, 0),
+ 6, 7, TLV_DB_SCALE_ITEM(-4140, 190, 0),
+ 8, 9, TLV_DB_SCALE_ITEM(-3650, 120, 0),
+ 10, 11, TLV_DB_SCALE_ITEM(-3330, 160, 0),
+ 12, 13, TLV_DB_SCALE_ITEM(-3040, 180, 0),
+ 14, 20, TLV_DB_SCALE_ITEM(-2710, 110, 0),
+ 21, 37, TLV_DB_SCALE_ITEM(-1960, 74, 0),
+ 38, 63, TLV_DB_SCALE_ITEM(-720, 45, 0),
+};
+
+static const struct snd_kcontrol_new tpa6130a2_controls[] = {
+ SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume",
+ TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0,
+ tpa6130a2_get_volsw, tpa6130a2_put_volsw,
+ tpa6130_tlv),
+};
+
+static const unsigned int tpa6140_tlv[] = {
+ TLV_DB_RANGE_HEAD(3),
+ 0, 8, TLV_DB_SCALE_ITEM(-5900, 400, 0),
+ 9, 16, TLV_DB_SCALE_ITEM(-2500, 200, 0),
+ 17, 31, TLV_DB_SCALE_ITEM(-1000, 100, 0),
+};
+
+static const struct snd_kcontrol_new tpa6140a2_controls[] = {
+ SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume",
+ TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0,
+ tpa6130a2_get_volsw, tpa6130a2_put_volsw,
+ tpa6140_tlv),
+};
+
+/*
+ * Enable or disable channel (left or right)
+ * The bit number for mute and amplifier are the same per channel:
+ * bit 6: Right channel
+ * bit 7: Left channel
+ * in both registers.
+ */
+static void tpa6130a2_channel_enable(u8 channel, int enable)
+{
+ u8 val;
+
+ if (enable) {
+ /* Enable channel */
+ /* Enable amplifier */
+ val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
+ val |= channel;
+ val &= ~TPA6130A2_SWS;
+ tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
+
+ /* Unmute channel */
+ val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE);
+ val &= ~channel;
+ tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val);
+ } else {
+ /* Disable channel */
+ /* Mute channel */
+ val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE);
+ val |= channel;
+ tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val);
+
+ /* Disable amplifier */
+ val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
+ val &= ~channel;
+ tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
+ }
+}
+
+int tpa6130a2_stereo_enable(struct snd_soc_codec *codec, int enable)
+{
+ int ret = 0;
+ if (enable) {
+ ret = tpa6130a2_power(1);
+ if (ret < 0)
+ return ret;
+ tpa6130a2_channel_enable(TPA6130A2_HP_EN_R | TPA6130A2_HP_EN_L,
+ 1);
+ } else {
+ tpa6130a2_channel_enable(TPA6130A2_HP_EN_R | TPA6130A2_HP_EN_L,
+ 0);
+ ret = tpa6130a2_power(0);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(tpa6130a2_stereo_enable);
+
+int tpa6130a2_add_controls(struct snd_soc_codec *codec)
+{
+ struct tpa6130a2_data *data;
+
+ if (tpa6130a2_client == NULL)
+ return -ENODEV;
+
+ data = i2c_get_clientdata(tpa6130a2_client);
+
+ if (data->id == TPA6140A2)
+ return snd_soc_add_controls(codec, tpa6140a2_controls,
+ ARRAY_SIZE(tpa6140a2_controls));
+ else
+ return snd_soc_add_controls(codec, tpa6130a2_controls,
+ ARRAY_SIZE(tpa6130a2_controls));
+}
+EXPORT_SYMBOL_GPL(tpa6130a2_add_controls);
+
+static int __devinit tpa6130a2_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev;
+ struct tpa6130a2_data *data;
+ struct tpa6130a2_platform_data *pdata;
+ const char *regulator;
+ int ret;
+
+ dev = &client->dev;
+
+ if (client->dev.platform_data == NULL) {
+ dev_err(dev, "Platform data not set\n");
+ dump_stack();
+ return -ENODEV;
+ }
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(dev, "Can not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ tpa6130a2_client = client;
+
+ i2c_set_clientdata(tpa6130a2_client, data);
+
+ pdata = client->dev.platform_data;
+ data->power_gpio = pdata->power_gpio;
+ data->id = pdata->id;
+
+ mutex_init(&data->mutex);
+
+ /* Set default register values */
+ data->regs[TPA6130A2_REG_CONTROL] = TPA6130A2_SWS;
+ data->regs[TPA6130A2_REG_VOL_MUTE] = TPA6130A2_MUTE_R |
+ TPA6130A2_MUTE_L;
+
+ if (data->power_gpio >= 0) {
+ ret = gpio_request(data->power_gpio, "tpa6130a2 enable");
+ if (ret < 0) {
+ dev_err(dev, "Failed to request power GPIO (%d)\n",
+ data->power_gpio);
+ goto err_gpio;
+ }
+ gpio_direction_output(data->power_gpio, 0);
+ }
+
+ switch (data->id) {
+ default:
+ dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n",
+ pdata->id);
+ case TPA6130A2:
+ regulator = "Vdd";
+ break;
+ case TPA6140A2:
+ regulator = "AVdd";
+ break;
+ }
+
+ data->supply = regulator_get(dev, regulator);
+ if (IS_ERR(data->supply)) {
+ ret = PTR_ERR(data->supply);
+ dev_err(dev, "Failed to request supply: %d\n", ret);
+ goto err_regulator;
+ }
+
+ ret = tpa6130a2_power(1);
+ if (ret != 0)
+ goto err_power;
+
+
+ /* Read version */
+ ret = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) &
+ TPA6130A2_VERSION_MASK;
+ if ((ret != 1) && (ret != 2))
+ dev_warn(dev, "UNTESTED version detected (%d)\n", ret);
+
+ /* Disable the chip */
+ ret = tpa6130a2_power(0);
+ if (ret != 0)
+ goto err_power;
+
+ return 0;
+
+err_power:
+ regulator_put(data->supply);
+err_regulator:
+ if (data->power_gpio >= 0)
+ gpio_free(data->power_gpio);
+err_gpio:
+ kfree(data);
+ i2c_set_clientdata(tpa6130a2_client, NULL);
+ tpa6130a2_client = NULL;
+
+ return ret;
+}
+
+static int __devexit tpa6130a2_remove(struct i2c_client *client)
+{
+ struct tpa6130a2_data *data = i2c_get_clientdata(client);
+
+ tpa6130a2_power(0);
+
+ if (data->power_gpio >= 0)
+ gpio_free(data->power_gpio);
+
+ regulator_put(data->supply);
+
+ kfree(data);
+ tpa6130a2_client = NULL;
+
+ return 0;
+}
+
+static const struct i2c_device_id tpa6130a2_id[] = {
+ { "tpa6130a2", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tpa6130a2_id);
+
+static struct i2c_driver tpa6130a2_i2c_driver = {
+ .driver = {
+ .name = "tpa6130a2",
+ .owner = THIS_MODULE,
+ },
+ .probe = tpa6130a2_probe,
+ .remove = __devexit_p(tpa6130a2_remove),
+ .id_table = tpa6130a2_id,
+};
+
+static int __init tpa6130a2_init(void)
+{
+ return i2c_add_driver(&tpa6130a2_i2c_driver);
+}
+
+static void __exit tpa6130a2_exit(void)
+{
+ i2c_del_driver(&tpa6130a2_i2c_driver);
+}
+
+MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
+MODULE_DESCRIPTION("TPA6130A2 Headphone amplifier driver");
+MODULE_LICENSE("GPL");
+
+module_init(tpa6130a2_init);
+module_exit(tpa6130a2_exit);
diff --git a/sound/soc/codecs/tpa6130a2.h b/sound/soc/codecs/tpa6130a2.h
new file mode 100644
index 00000000..41744402
--- /dev/null
+++ b/sound/soc/codecs/tpa6130a2.h
@@ -0,0 +1,62 @@
+/*
+ * ALSA SoC TPA6130A2 amplifier driver
+ *
+ * Copyright (C) Nokia Corporation
+ *
+ * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TPA6130A2_H__
+#define __TPA6130A2_H__
+
+/* Register addresses */
+#define TPA6130A2_REG_CONTROL 0x01
+#define TPA6130A2_REG_VOL_MUTE 0x02
+#define TPA6130A2_REG_OUT_IMPEDANCE 0x03
+#define TPA6130A2_REG_VERSION 0x04
+
+#define TPA6130A2_CACHEREGNUM (TPA6130A2_REG_VERSION + 1)
+
+/* Register bits */
+/* TPA6130A2_REG_CONTROL (0x01) */
+#define TPA6130A2_SWS (0x01 << 0)
+#define TPA6130A2_TERMAL (0x01 << 1)
+#define TPA6130A2_MODE(x) (x << 4)
+#define TPA6130A2_MODE_STEREO (0x00)
+#define TPA6130A2_MODE_DUAL_MONO (0x01)
+#define TPA6130A2_MODE_BRIDGE (0x02)
+#define TPA6130A2_MODE_MASK (0x03)
+#define TPA6130A2_HP_EN_R (0x01 << 6)
+#define TPA6130A2_HP_EN_L (0x01 << 7)
+
+/* TPA6130A2_REG_VOL_MUTE (0x02) */
+#define TPA6130A2_VOLUME(x) ((x & 0x3f) << 0)
+#define TPA6130A2_MUTE_R (0x01 << 6)
+#define TPA6130A2_MUTE_L (0x01 << 7)
+
+/* TPA6130A2_REG_OUT_IMPEDANCE (0x03) */
+#define TPA6130A2_HIZ_R (0x01 << 0)
+#define TPA6130A2_HIZ_L (0x01 << 1)
+
+/* TPA6130A2_REG_VERSION (0x04) */
+#define TPA6130A2_VERSION_MASK (0x0f)
+
+extern int tpa6130a2_add_controls(struct snd_soc_codec *codec);
+extern int tpa6130a2_stereo_enable(struct snd_soc_codec *codec, int enable);
+
+#endif /* __TPA6130A2_H__ */
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c
new file mode 100644
index 00000000..bec788b1
--- /dev/null
+++ b/sound/soc/codecs/twl4030.c
@@ -0,0 +1,2342 @@
+/*
+ * ALSA SoC TWL4030 codec driver
+ *
+ * Author: Steve Sakoman, <steve@sakoman.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+/* Register descriptions are here */
+#include <linux/mfd/twl4030-codec.h>
+
+/* Shadow register used by the audio driver */
+#define TWL4030_REG_SW_SHADOW 0x4A
+#define TWL4030_CACHEREGNUM (TWL4030_REG_SW_SHADOW + 1)
+
+/* TWL4030_REG_SW_SHADOW (0x4A) Fields */
+#define TWL4030_HFL_EN 0x01
+#define TWL4030_HFR_EN 0x02
+
+/*
+ * twl4030 register cache & default register settings
+ */
+static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
+ 0x00, /* this register not used */
+ 0x00, /* REG_CODEC_MODE (0x1) */
+ 0x00, /* REG_OPTION (0x2) */
+ 0x00, /* REG_UNKNOWN (0x3) */
+ 0x00, /* REG_MICBIAS_CTL (0x4) */
+ 0x00, /* REG_ANAMICL (0x5) */
+ 0x00, /* REG_ANAMICR (0x6) */
+ 0x00, /* REG_AVADC_CTL (0x7) */
+ 0x00, /* REG_ADCMICSEL (0x8) */
+ 0x00, /* REG_DIGMIXING (0x9) */
+ 0x0f, /* REG_ATXL1PGA (0xA) */
+ 0x0f, /* REG_ATXR1PGA (0xB) */
+ 0x0f, /* REG_AVTXL2PGA (0xC) */
+ 0x0f, /* REG_AVTXR2PGA (0xD) */
+ 0x00, /* REG_AUDIO_IF (0xE) */
+ 0x00, /* REG_VOICE_IF (0xF) */
+ 0x3f, /* REG_ARXR1PGA (0x10) */
+ 0x3f, /* REG_ARXL1PGA (0x11) */
+ 0x3f, /* REG_ARXR2PGA (0x12) */
+ 0x3f, /* REG_ARXL2PGA (0x13) */
+ 0x25, /* REG_VRXPGA (0x14) */
+ 0x00, /* REG_VSTPGA (0x15) */
+ 0x00, /* REG_VRX2ARXPGA (0x16) */
+ 0x00, /* REG_AVDAC_CTL (0x17) */
+ 0x00, /* REG_ARX2VTXPGA (0x18) */
+ 0x32, /* REG_ARXL1_APGA_CTL (0x19) */
+ 0x32, /* REG_ARXR1_APGA_CTL (0x1A) */
+ 0x32, /* REG_ARXL2_APGA_CTL (0x1B) */
+ 0x32, /* REG_ARXR2_APGA_CTL (0x1C) */
+ 0x00, /* REG_ATX2ARXPGA (0x1D) */
+ 0x00, /* REG_BT_IF (0x1E) */
+ 0x55, /* REG_BTPGA (0x1F) */
+ 0x00, /* REG_BTSTPGA (0x20) */
+ 0x00, /* REG_EAR_CTL (0x21) */
+ 0x00, /* REG_HS_SEL (0x22) */
+ 0x00, /* REG_HS_GAIN_SET (0x23) */
+ 0x00, /* REG_HS_POPN_SET (0x24) */
+ 0x00, /* REG_PREDL_CTL (0x25) */
+ 0x00, /* REG_PREDR_CTL (0x26) */
+ 0x00, /* REG_PRECKL_CTL (0x27) */
+ 0x00, /* REG_PRECKR_CTL (0x28) */
+ 0x00, /* REG_HFL_CTL (0x29) */
+ 0x00, /* REG_HFR_CTL (0x2A) */
+ 0x05, /* REG_ALC_CTL (0x2B) */
+ 0x00, /* REG_ALC_SET1 (0x2C) */
+ 0x00, /* REG_ALC_SET2 (0x2D) */
+ 0x00, /* REG_BOOST_CTL (0x2E) */
+ 0x00, /* REG_SOFTVOL_CTL (0x2F) */
+ 0x13, /* REG_DTMF_FREQSEL (0x30) */
+ 0x00, /* REG_DTMF_TONEXT1H (0x31) */
+ 0x00, /* REG_DTMF_TONEXT1L (0x32) */
+ 0x00, /* REG_DTMF_TONEXT2H (0x33) */
+ 0x00, /* REG_DTMF_TONEXT2L (0x34) */
+ 0x79, /* REG_DTMF_TONOFF (0x35) */
+ 0x11, /* REG_DTMF_WANONOFF (0x36) */
+ 0x00, /* REG_I2S_RX_SCRAMBLE_H (0x37) */
+ 0x00, /* REG_I2S_RX_SCRAMBLE_M (0x38) */
+ 0x00, /* REG_I2S_RX_SCRAMBLE_L (0x39) */
+ 0x06, /* REG_APLL_CTL (0x3A) */
+ 0x00, /* REG_DTMF_CTL (0x3B) */
+ 0x44, /* REG_DTMF_PGA_CTL2 (0x3C) */
+ 0x69, /* REG_DTMF_PGA_CTL1 (0x3D) */
+ 0x00, /* REG_MISC_SET_1 (0x3E) */
+ 0x00, /* REG_PCMBTMUX (0x3F) */
+ 0x00, /* not used (0x40) */
+ 0x00, /* not used (0x41) */
+ 0x00, /* not used (0x42) */
+ 0x00, /* REG_RX_PATH_SEL (0x43) */
+ 0x32, /* REG_VDL_APGA_CTL (0x44) */
+ 0x00, /* REG_VIBRA_CTL (0x45) */
+ 0x00, /* REG_VIBRA_SET (0x46) */
+ 0x00, /* REG_VIBRA_PWM_SET (0x47) */
+ 0x00, /* REG_ANAMIC_GAIN (0x48) */
+ 0x00, /* REG_MISC_SET_2 (0x49) */
+ 0x00, /* REG_SW_SHADOW (0x4A) - Shadow, non HW register */
+};
+
+/* codec private data */
+struct twl4030_priv {
+ struct snd_soc_codec codec;
+
+ unsigned int codec_powered;
+
+ /* reference counts of AIF/APLL users */
+ unsigned int apll_enabled;
+
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+
+ unsigned int configured;
+ unsigned int rate;
+ unsigned int sample_bits;
+ unsigned int channels;
+
+ unsigned int sysclk;
+
+ /* Output (with associated amp) states */
+ u8 hsl_enabled, hsr_enabled;
+ u8 earpiece_enabled;
+ u8 predrivel_enabled, predriver_enabled;
+ u8 carkitl_enabled, carkitr_enabled;
+
+ /* Delay needed after enabling the digimic interface */
+ unsigned int digimic_delay;
+};
+
+/*
+ * read twl4030 register cache
+ */
+static inline unsigned int twl4030_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+
+ if (reg >= TWL4030_CACHEREGNUM)
+ return -EIO;
+
+ return cache[reg];
+}
+
+/*
+ * write twl4030 register cache
+ */
+static inline void twl4030_write_reg_cache(struct snd_soc_codec *codec,
+ u8 reg, u8 value)
+{
+ u8 *cache = codec->reg_cache;
+
+ if (reg >= TWL4030_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the twl4030 register space
+ */
+static int twl4030_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ int write_to_reg = 0;
+
+ twl4030_write_reg_cache(codec, reg, value);
+ if (likely(reg < TWL4030_REG_SW_SHADOW)) {
+ /* Decide if the given register can be written */
+ switch (reg) {
+ case TWL4030_REG_EAR_CTL:
+ if (twl4030->earpiece_enabled)
+ write_to_reg = 1;
+ break;
+ case TWL4030_REG_PREDL_CTL:
+ if (twl4030->predrivel_enabled)
+ write_to_reg = 1;
+ break;
+ case TWL4030_REG_PREDR_CTL:
+ if (twl4030->predriver_enabled)
+ write_to_reg = 1;
+ break;
+ case TWL4030_REG_PRECKL_CTL:
+ if (twl4030->carkitl_enabled)
+ write_to_reg = 1;
+ break;
+ case TWL4030_REG_PRECKR_CTL:
+ if (twl4030->carkitr_enabled)
+ write_to_reg = 1;
+ break;
+ case TWL4030_REG_HS_GAIN_SET:
+ if (twl4030->hsl_enabled || twl4030->hsr_enabled)
+ write_to_reg = 1;
+ break;
+ default:
+ /* All other register can be written */
+ write_to_reg = 1;
+ break;
+ }
+ if (write_to_reg)
+ return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ value, reg);
+ }
+ return 0;
+}
+
+static inline void twl4030_wait_ms(int time)
+{
+ if (time < 60) {
+ time *= 1000;
+ usleep_range(time, time + 500);
+ } else {
+ msleep(time);
+ }
+}
+
+static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable)
+{
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ int mode;
+
+ if (enable == twl4030->codec_powered)
+ return;
+
+ if (enable)
+ mode = twl4030_codec_enable_resource(TWL4030_CODEC_RES_POWER);
+ else
+ mode = twl4030_codec_disable_resource(TWL4030_CODEC_RES_POWER);
+
+ if (mode >= 0) {
+ twl4030_write_reg_cache(codec, TWL4030_REG_CODEC_MODE, mode);
+ twl4030->codec_powered = enable;
+ }
+
+ /* REVISIT: this delay is present in TI sample drivers */
+ /* but there seems to be no TRM requirement for it */
+ udelay(10);
+}
+
+static inline void twl4030_check_defaults(struct snd_soc_codec *codec)
+{
+ int i, difference = 0;
+ u8 val;
+
+ dev_dbg(codec->dev, "Checking TWL audio default configuration\n");
+ for (i = 1; i <= TWL4030_REG_MISC_SET_2; i++) {
+ twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, i);
+ if (val != twl4030_reg[i]) {
+ difference++;
+ dev_dbg(codec->dev,
+ "Reg 0x%02x: chip: 0x%02x driver: 0x%02x\n",
+ i, val, twl4030_reg[i]);
+ }
+ }
+ dev_dbg(codec->dev, "Found %d non-matching registers. %s\n",
+ difference, difference ? "Not OK" : "OK");
+}
+
+static inline void twl4030_reset_registers(struct snd_soc_codec *codec)
+{
+ int i;
+
+ /* set all audio section registers to reasonable defaults */
+ for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++)
+ if (i != TWL4030_REG_APLL_CTL)
+ twl4030_write(codec, i, twl4030_reg[i]);
+
+}
+
+static void twl4030_init_chip(struct snd_soc_codec *codec)
+{
+ struct twl4030_codec_audio_data *pdata = dev_get_platdata(codec->dev);
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ u8 reg, byte;
+ int i = 0;
+
+ /* Check defaults, if instructed before anything else */
+ if (pdata && pdata->check_defaults)
+ twl4030_check_defaults(codec);
+
+ /* Reset registers, if no setup data or if instructed to do so */
+ if (!pdata || (pdata && pdata->reset_registers))
+ twl4030_reset_registers(codec);
+
+ /* Refresh APLL_CTL register from HW */
+ twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte,
+ TWL4030_REG_APLL_CTL);
+ twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, byte);
+
+ /* anti-pop when changing analog gain */
+ reg = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1);
+ twl4030_write(codec, TWL4030_REG_MISC_SET_1,
+ reg | TWL4030_SMOOTH_ANAVOL_EN);
+
+ twl4030_write(codec, TWL4030_REG_OPTION,
+ TWL4030_ATXL1_EN | TWL4030_ATXR1_EN |
+ TWL4030_ARXL2_EN | TWL4030_ARXR2_EN);
+
+ /* REG_ARXR2_APGA_CTL reset according to the TRM: 0dB, DA_EN */
+ twl4030_write(codec, TWL4030_REG_ARXR2_APGA_CTL, 0x32);
+
+ /* Machine dependent setup */
+ if (!pdata)
+ return;
+
+ twl4030->digimic_delay = pdata->digimic_delay;
+
+ reg = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
+ reg &= ~TWL4030_RAMP_DELAY;
+ reg |= (pdata->ramp_delay_value << 2);
+ twl4030_write_reg_cache(codec, TWL4030_REG_HS_POPN_SET, reg);
+
+ /* initiate offset cancellation */
+ twl4030_codec_enable(codec, 1);
+
+ reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL);
+ reg &= ~TWL4030_OFFSET_CNCL_SEL;
+ reg |= pdata->offset_cncl_path;
+ twl4030_write(codec, TWL4030_REG_ANAMICL,
+ reg | TWL4030_CNCL_OFFSET_START);
+
+ /*
+ * Wait for offset cancellation to complete.
+ * Since this takes a while, do not slam the i2c.
+ * Start polling the status after ~20ms.
+ */
+ msleep(20);
+ do {
+ usleep_range(1000, 2000);
+ twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte,
+ TWL4030_REG_ANAMICL);
+ } while ((i++ < 100) &&
+ ((byte & TWL4030_CNCL_OFFSET_START) ==
+ TWL4030_CNCL_OFFSET_START));
+
+ /* Make sure that the reg_cache has the same value as the HW */
+ twl4030_write_reg_cache(codec, TWL4030_REG_ANAMICL, byte);
+
+ twl4030_codec_enable(codec, 0);
+}
+
+static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable)
+{
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ int status = -1;
+
+ if (enable) {
+ twl4030->apll_enabled++;
+ if (twl4030->apll_enabled == 1)
+ status = twl4030_codec_enable_resource(
+ TWL4030_CODEC_RES_APLL);
+ } else {
+ twl4030->apll_enabled--;
+ if (!twl4030->apll_enabled)
+ status = twl4030_codec_disable_resource(
+ TWL4030_CODEC_RES_APLL);
+ }
+
+ if (status >= 0)
+ twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, status);
+}
+
+/* Earpiece */
+static const struct snd_kcontrol_new twl4030_dapm_earpiece_controls[] = {
+ SOC_DAPM_SINGLE("Voice", TWL4030_REG_EAR_CTL, 0, 1, 0),
+ SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_EAR_CTL, 1, 1, 0),
+ SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_EAR_CTL, 2, 1, 0),
+ SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_EAR_CTL, 3, 1, 0),
+};
+
+/* PreDrive Left */
+static const struct snd_kcontrol_new twl4030_dapm_predrivel_controls[] = {
+ SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDL_CTL, 0, 1, 0),
+ SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PREDL_CTL, 1, 1, 0),
+ SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDL_CTL, 2, 1, 0),
+ SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDL_CTL, 3, 1, 0),
+};
+
+/* PreDrive Right */
+static const struct snd_kcontrol_new twl4030_dapm_predriver_controls[] = {
+ SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDR_CTL, 0, 1, 0),
+ SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PREDR_CTL, 1, 1, 0),
+ SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDR_CTL, 2, 1, 0),
+ SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDR_CTL, 3, 1, 0),
+};
+
+/* Headset Left */
+static const struct snd_kcontrol_new twl4030_dapm_hsol_controls[] = {
+ SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 0, 1, 0),
+ SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_HS_SEL, 1, 1, 0),
+ SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_HS_SEL, 2, 1, 0),
+};
+
+/* Headset Right */
+static const struct snd_kcontrol_new twl4030_dapm_hsor_controls[] = {
+ SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 3, 1, 0),
+ SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_HS_SEL, 4, 1, 0),
+ SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_HS_SEL, 5, 1, 0),
+};
+
+/* Carkit Left */
+static const struct snd_kcontrol_new twl4030_dapm_carkitl_controls[] = {
+ SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKL_CTL, 0, 1, 0),
+ SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PRECKL_CTL, 1, 1, 0),
+ SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PRECKL_CTL, 2, 1, 0),
+};
+
+/* Carkit Right */
+static const struct snd_kcontrol_new twl4030_dapm_carkitr_controls[] = {
+ SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKR_CTL, 0, 1, 0),
+ SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PRECKR_CTL, 1, 1, 0),
+ SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PRECKR_CTL, 2, 1, 0),
+};
+
+/* Handsfree Left */
+static const char *twl4030_handsfreel_texts[] =
+ {"Voice", "AudioL1", "AudioL2", "AudioR2"};
+
+static const struct soc_enum twl4030_handsfreel_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_HFL_CTL, 0,
+ ARRAY_SIZE(twl4030_handsfreel_texts),
+ twl4030_handsfreel_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_handsfreel_control =
+SOC_DAPM_ENUM("Route", twl4030_handsfreel_enum);
+
+/* Handsfree Left virtual mute */
+static const struct snd_kcontrol_new twl4030_dapm_handsfreelmute_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_SW_SHADOW, 0, 1, 0);
+
+/* Handsfree Right */
+static const char *twl4030_handsfreer_texts[] =
+ {"Voice", "AudioR1", "AudioR2", "AudioL2"};
+
+static const struct soc_enum twl4030_handsfreer_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_HFR_CTL, 0,
+ ARRAY_SIZE(twl4030_handsfreer_texts),
+ twl4030_handsfreer_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_handsfreer_control =
+SOC_DAPM_ENUM("Route", twl4030_handsfreer_enum);
+
+/* Handsfree Right virtual mute */
+static const struct snd_kcontrol_new twl4030_dapm_handsfreermute_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_SW_SHADOW, 1, 1, 0);
+
+/* Vibra */
+/* Vibra audio path selection */
+static const char *twl4030_vibra_texts[] =
+ {"AudioL1", "AudioR1", "AudioL2", "AudioR2"};
+
+static const struct soc_enum twl4030_vibra_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 2,
+ ARRAY_SIZE(twl4030_vibra_texts),
+ twl4030_vibra_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_vibra_control =
+SOC_DAPM_ENUM("Route", twl4030_vibra_enum);
+
+/* Vibra path selection: local vibrator (PWM) or audio driven */
+static const char *twl4030_vibrapath_texts[] =
+ {"Local vibrator", "Audio"};
+
+static const struct soc_enum twl4030_vibrapath_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 4,
+ ARRAY_SIZE(twl4030_vibrapath_texts),
+ twl4030_vibrapath_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_vibrapath_control =
+SOC_DAPM_ENUM("Route", twl4030_vibrapath_enum);
+
+/* Left analog microphone selection */
+static const struct snd_kcontrol_new twl4030_dapm_analoglmic_controls[] = {
+ SOC_DAPM_SINGLE("Main Mic Capture Switch",
+ TWL4030_REG_ANAMICL, 0, 1, 0),
+ SOC_DAPM_SINGLE("Headset Mic Capture Switch",
+ TWL4030_REG_ANAMICL, 1, 1, 0),
+ SOC_DAPM_SINGLE("AUXL Capture Switch",
+ TWL4030_REG_ANAMICL, 2, 1, 0),
+ SOC_DAPM_SINGLE("Carkit Mic Capture Switch",
+ TWL4030_REG_ANAMICL, 3, 1, 0),
+};
+
+/* Right analog microphone selection */
+static const struct snd_kcontrol_new twl4030_dapm_analogrmic_controls[] = {
+ SOC_DAPM_SINGLE("Sub Mic Capture Switch", TWL4030_REG_ANAMICR, 0, 1, 0),
+ SOC_DAPM_SINGLE("AUXR Capture Switch", TWL4030_REG_ANAMICR, 2, 1, 0),
+};
+
+/* TX1 L/R Analog/Digital microphone selection */
+static const char *twl4030_micpathtx1_texts[] =
+ {"Analog", "Digimic0"};
+
+static const struct soc_enum twl4030_micpathtx1_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_ADCMICSEL, 0,
+ ARRAY_SIZE(twl4030_micpathtx1_texts),
+ twl4030_micpathtx1_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_micpathtx1_control =
+SOC_DAPM_ENUM("Route", twl4030_micpathtx1_enum);
+
+/* TX2 L/R Analog/Digital microphone selection */
+static const char *twl4030_micpathtx2_texts[] =
+ {"Analog", "Digimic1"};
+
+static const struct soc_enum twl4030_micpathtx2_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_ADCMICSEL, 2,
+ ARRAY_SIZE(twl4030_micpathtx2_texts),
+ twl4030_micpathtx2_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_micpathtx2_control =
+SOC_DAPM_ENUM("Route", twl4030_micpathtx2_enum);
+
+/* Analog bypass for AudioR1 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassr1_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR1_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioL1 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassl1_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL1_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioR2 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassr2_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR2_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioL2 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL2_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for Voice */
+static const struct snd_kcontrol_new twl4030_dapm_abypassv_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_VDL_APGA_CTL, 2, 1, 0);
+
+/* Digital bypass gain, mute instead of -30dB */
+static const unsigned int twl4030_dapm_dbypass_tlv[] = {
+ TLV_DB_RANGE_HEAD(3),
+ 0, 1, TLV_DB_SCALE_ITEM(-3000, 600, 1),
+ 2, 3, TLV_DB_SCALE_ITEM(-2400, 0, 0),
+ 4, 7, TLV_DB_SCALE_ITEM(-1800, 600, 0),
+};
+
+/* Digital bypass left (TX1L -> RX2L) */
+static const struct snd_kcontrol_new twl4030_dapm_dbypassl_control =
+ SOC_DAPM_SINGLE_TLV("Volume",
+ TWL4030_REG_ATX2ARXPGA, 3, 7, 0,
+ twl4030_dapm_dbypass_tlv);
+
+/* Digital bypass right (TX1R -> RX2R) */
+static const struct snd_kcontrol_new twl4030_dapm_dbypassr_control =
+ SOC_DAPM_SINGLE_TLV("Volume",
+ TWL4030_REG_ATX2ARXPGA, 0, 7, 0,
+ twl4030_dapm_dbypass_tlv);
+
+/*
+ * Voice Sidetone GAIN volume control:
+ * from -51 to -10 dB in 1 dB steps (mute instead of -51 dB)
+ */
+static DECLARE_TLV_DB_SCALE(twl4030_dapm_dbypassv_tlv, -5100, 100, 1);
+
+/* Digital bypass voice: sidetone (VUL -> VDL)*/
+static const struct snd_kcontrol_new twl4030_dapm_dbypassv_control =
+ SOC_DAPM_SINGLE_TLV("Volume",
+ TWL4030_REG_VSTPGA, 0, 0x29, 0,
+ twl4030_dapm_dbypassv_tlv);
+
+/*
+ * Output PGA builder:
+ * Handle the muting and unmuting of the given output (turning off the
+ * amplifier associated with the output pin)
+ * On mute bypass the reg_cache and write 0 to the register
+ * On unmute: restore the register content from the reg_cache
+ * Outputs handled in this way: Earpiece, PreDrivL/R, CarkitL/R
+ */
+#define TWL4030_OUTPUT_PGA(pin_name, reg, mask) \
+static int pin_name##pga_event(struct snd_soc_dapm_widget *w, \
+ struct snd_kcontrol *kcontrol, int event) \
+{ \
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec); \
+ \
+ switch (event) { \
+ case SND_SOC_DAPM_POST_PMU: \
+ twl4030->pin_name##_enabled = 1; \
+ twl4030_write(w->codec, reg, \
+ twl4030_read_reg_cache(w->codec, reg)); \
+ break; \
+ case SND_SOC_DAPM_POST_PMD: \
+ twl4030->pin_name##_enabled = 0; \
+ twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, \
+ 0, reg); \
+ break; \
+ } \
+ return 0; \
+}
+
+TWL4030_OUTPUT_PGA(earpiece, TWL4030_REG_EAR_CTL, TWL4030_EAR_GAIN);
+TWL4030_OUTPUT_PGA(predrivel, TWL4030_REG_PREDL_CTL, TWL4030_PREDL_GAIN);
+TWL4030_OUTPUT_PGA(predriver, TWL4030_REG_PREDR_CTL, TWL4030_PREDR_GAIN);
+TWL4030_OUTPUT_PGA(carkitl, TWL4030_REG_PRECKL_CTL, TWL4030_PRECKL_GAIN);
+TWL4030_OUTPUT_PGA(carkitr, TWL4030_REG_PRECKR_CTL, TWL4030_PRECKR_GAIN);
+
+static void handsfree_ramp(struct snd_soc_codec *codec, int reg, int ramp)
+{
+ unsigned char hs_ctl;
+
+ hs_ctl = twl4030_read_reg_cache(codec, reg);
+
+ if (ramp) {
+ /* HF ramp-up */
+ hs_ctl |= TWL4030_HF_CTL_REF_EN;
+ twl4030_write(codec, reg, hs_ctl);
+ udelay(10);
+ hs_ctl |= TWL4030_HF_CTL_RAMP_EN;
+ twl4030_write(codec, reg, hs_ctl);
+ udelay(40);
+ hs_ctl |= TWL4030_HF_CTL_LOOP_EN;
+ hs_ctl |= TWL4030_HF_CTL_HB_EN;
+ twl4030_write(codec, reg, hs_ctl);
+ } else {
+ /* HF ramp-down */
+ hs_ctl &= ~TWL4030_HF_CTL_LOOP_EN;
+ hs_ctl &= ~TWL4030_HF_CTL_HB_EN;
+ twl4030_write(codec, reg, hs_ctl);
+ hs_ctl &= ~TWL4030_HF_CTL_RAMP_EN;
+ twl4030_write(codec, reg, hs_ctl);
+ udelay(40);
+ hs_ctl &= ~TWL4030_HF_CTL_REF_EN;
+ twl4030_write(codec, reg, hs_ctl);
+ }
+}
+
+static int handsfreelpga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ handsfree_ramp(w->codec, TWL4030_REG_HFL_CTL, 1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ handsfree_ramp(w->codec, TWL4030_REG_HFL_CTL, 0);
+ break;
+ }
+ return 0;
+}
+
+static int handsfreerpga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ handsfree_ramp(w->codec, TWL4030_REG_HFR_CTL, 1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ handsfree_ramp(w->codec, TWL4030_REG_HFR_CTL, 0);
+ break;
+ }
+ return 0;
+}
+
+static int vibramux_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ twl4030_write(w->codec, TWL4030_REG_VIBRA_SET, 0xff);
+ return 0;
+}
+
+static int apll_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ twl4030_apll_enable(w->codec, 1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ twl4030_apll_enable(w->codec, 0);
+ break;
+ }
+ return 0;
+}
+
+static int aif_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u8 audio_if;
+
+ audio_if = twl4030_read_reg_cache(w->codec, TWL4030_REG_AUDIO_IF);
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Enable AIF */
+ /* enable the PLL before we use it to clock the DAI */
+ twl4030_apll_enable(w->codec, 1);
+
+ twl4030_write(w->codec, TWL4030_REG_AUDIO_IF,
+ audio_if | TWL4030_AIF_EN);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ /* disable the DAI before we stop it's source PLL */
+ twl4030_write(w->codec, TWL4030_REG_AUDIO_IF,
+ audio_if & ~TWL4030_AIF_EN);
+ twl4030_apll_enable(w->codec, 0);
+ break;
+ }
+ return 0;
+}
+
+static void headset_ramp(struct snd_soc_codec *codec, int ramp)
+{
+ struct twl4030_codec_audio_data *pdata = codec->dev->platform_data;
+ unsigned char hs_gain, hs_pop;
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ /* Base values for ramp delay calculation: 2^19 - 2^26 */
+ unsigned int ramp_base[] = {524288, 1048576, 2097152, 4194304,
+ 8388608, 16777216, 33554432, 67108864};
+ unsigned int delay;
+
+ hs_gain = twl4030_read_reg_cache(codec, TWL4030_REG_HS_GAIN_SET);
+ hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
+ delay = (ramp_base[(hs_pop & TWL4030_RAMP_DELAY) >> 2] /
+ twl4030->sysclk) + 1;
+
+ /* Enable external mute control, this dramatically reduces
+ * the pop-noise */
+ if (pdata && pdata->hs_extmute) {
+ if (pdata->set_hs_extmute) {
+ pdata->set_hs_extmute(1);
+ } else {
+ hs_pop |= TWL4030_EXTMUTE;
+ twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+ }
+ }
+
+ if (ramp) {
+ /* Headset ramp-up according to the TRM */
+ hs_pop |= TWL4030_VMID_EN;
+ twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+ /* Actually write to the register */
+ twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ hs_gain,
+ TWL4030_REG_HS_GAIN_SET);
+ hs_pop |= TWL4030_RAMP_EN;
+ twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+ /* Wait ramp delay time + 1, so the VMID can settle */
+ twl4030_wait_ms(delay);
+ } else {
+ /* Headset ramp-down _not_ according to
+ * the TRM, but in a way that it is working */
+ hs_pop &= ~TWL4030_RAMP_EN;
+ twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+ /* Wait ramp delay time + 1, so the VMID can settle */
+ twl4030_wait_ms(delay);
+ /* Bypass the reg_cache to mute the headset */
+ twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ hs_gain & (~0x0f),
+ TWL4030_REG_HS_GAIN_SET);
+
+ hs_pop &= ~TWL4030_VMID_EN;
+ twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+ }
+
+ /* Disable external mute */
+ if (pdata && pdata->hs_extmute) {
+ if (pdata->set_hs_extmute) {
+ pdata->set_hs_extmute(0);
+ } else {
+ hs_pop &= ~TWL4030_EXTMUTE;
+ twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+ }
+ }
+}
+
+static int headsetlpga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ /* Do the ramp-up only once */
+ if (!twl4030->hsr_enabled)
+ headset_ramp(w->codec, 1);
+
+ twl4030->hsl_enabled = 1;
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ /* Do the ramp-down only if both headsetL/R is disabled */
+ if (!twl4030->hsr_enabled)
+ headset_ramp(w->codec, 0);
+
+ twl4030->hsl_enabled = 0;
+ break;
+ }
+ return 0;
+}
+
+static int headsetrpga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ /* Do the ramp-up only once */
+ if (!twl4030->hsl_enabled)
+ headset_ramp(w->codec, 1);
+
+ twl4030->hsr_enabled = 1;
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ /* Do the ramp-down only if both headsetL/R is disabled */
+ if (!twl4030->hsl_enabled)
+ headset_ramp(w->codec, 0);
+
+ twl4030->hsr_enabled = 0;
+ break;
+ }
+ return 0;
+}
+
+static int digimic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec);
+
+ if (twl4030->digimic_delay)
+ twl4030_wait_ms(twl4030->digimic_delay);
+ return 0;
+}
+
+/*
+ * Some of the gain controls in TWL (mostly those which are associated with
+ * the outputs) are implemented in an interesting way:
+ * 0x0 : Power down (mute)
+ * 0x1 : 6dB
+ * 0x2 : 0 dB
+ * 0x3 : -6 dB
+ * Inverting not going to help with these.
+ * Custom volsw and volsw_2r get/put functions to handle these gain bits.
+ */
+#define SOC_DOUBLE_TLV_TWL4030(xname, xreg, shift_left, shift_right, xmax,\
+ xinvert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_get_volsw_twl4030, \
+ .put = snd_soc_put_volsw_twl4030, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = xreg, .shift = shift_left, .rshift = shift_right,\
+ .max = xmax, .invert = xinvert} }
+#define SOC_DOUBLE_R_TLV_TWL4030(xname, reg_left, reg_right, xshift, xmax,\
+ xinvert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_2r, \
+ .get = snd_soc_get_volsw_r2_twl4030,\
+ .put = snd_soc_put_volsw_r2_twl4030, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = reg_left, .rreg = reg_right, .shift = xshift, \
+ .rshift = xshift, .max = xmax, .invert = xinvert} }
+#define SOC_SINGLE_TLV_TWL4030(xname, xreg, xshift, xmax, xinvert, tlv_array) \
+ SOC_DOUBLE_TLV_TWL4030(xname, xreg, xshift, xshift, xmax, \
+ xinvert, tlv_array)
+
+static int snd_soc_get_volsw_twl4030(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+ int max = mc->max;
+ int mask = (1 << fls(max)) - 1;
+
+ ucontrol->value.integer.value[0] =
+ (snd_soc_read(codec, reg) >> shift) & mask;
+ if (ucontrol->value.integer.value[0])
+ ucontrol->value.integer.value[0] =
+ max + 1 - ucontrol->value.integer.value[0];
+
+ if (shift != rshift) {
+ ucontrol->value.integer.value[1] =
+ (snd_soc_read(codec, reg) >> rshift) & mask;
+ if (ucontrol->value.integer.value[1])
+ ucontrol->value.integer.value[1] =
+ max + 1 - ucontrol->value.integer.value[1];
+ }
+
+ return 0;
+}
+
+static int snd_soc_put_volsw_twl4030(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+ int max = mc->max;
+ int mask = (1 << fls(max)) - 1;
+ unsigned short val, val2, val_mask;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+
+ val_mask = mask << shift;
+ if (val)
+ val = max + 1 - val;
+ val = val << shift;
+ if (shift != rshift) {
+ val2 = (ucontrol->value.integer.value[1] & mask);
+ val_mask |= mask << rshift;
+ if (val2)
+ val2 = max + 1 - val2;
+ val |= val2 << rshift;
+ }
+ return snd_soc_update_bits(codec, reg, val_mask, val);
+}
+
+static int snd_soc_get_volsw_r2_twl4030(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ int mask = (1<<fls(max))-1;
+
+ ucontrol->value.integer.value[0] =
+ (snd_soc_read(codec, reg) >> shift) & mask;
+ ucontrol->value.integer.value[1] =
+ (snd_soc_read(codec, reg2) >> shift) & mask;
+
+ if (ucontrol->value.integer.value[0])
+ ucontrol->value.integer.value[0] =
+ max + 1 - ucontrol->value.integer.value[0];
+ if (ucontrol->value.integer.value[1])
+ ucontrol->value.integer.value[1] =
+ max + 1 - ucontrol->value.integer.value[1];
+
+ return 0;
+}
+
+static int snd_soc_put_volsw_r2_twl4030(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ int mask = (1 << fls(max)) - 1;
+ int err;
+ unsigned short val, val2, val_mask;
+
+ val_mask = mask << shift;
+ val = (ucontrol->value.integer.value[0] & mask);
+ val2 = (ucontrol->value.integer.value[1] & mask);
+
+ if (val)
+ val = max + 1 - val;
+ if (val2)
+ val2 = max + 1 - val2;
+
+ val = val << shift;
+ val2 = val2 << shift;
+
+ err = snd_soc_update_bits(codec, reg, val_mask, val);
+ if (err < 0)
+ return err;
+
+ err = snd_soc_update_bits(codec, reg2, val_mask, val2);
+ return err;
+}
+
+/* Codec operation modes */
+static const char *twl4030_op_modes_texts[] = {
+ "Option 2 (voice/audio)", "Option 1 (audio)"
+};
+
+static const struct soc_enum twl4030_op_modes_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_CODEC_MODE, 0,
+ ARRAY_SIZE(twl4030_op_modes_texts),
+ twl4030_op_modes_texts);
+
+static int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned short val;
+ unsigned short mask, bitmask;
+
+ if (twl4030->configured) {
+ printk(KERN_ERR "twl4030 operation mode cannot be "
+ "changed on-the-fly\n");
+ return -EBUSY;
+ }
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+
+ val = ucontrol->value.enumerated.item[0] << e->shift_l;
+ mask = (bitmask - 1) << e->shift_l;
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->max - 1)
+ return -EINVAL;
+ val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+ mask |= (bitmask - 1) << e->shift_r;
+ }
+
+ return snd_soc_update_bits(codec, e->reg, mask, val);
+}
+
+/*
+ * FGAIN volume control:
+ * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB)
+ */
+static DECLARE_TLV_DB_SCALE(digital_fine_tlv, -6300, 100, 1);
+
+/*
+ * CGAIN volume control:
+ * 0 dB to 12 dB in 6 dB steps
+ * value 2 and 3 means 12 dB
+ */
+static DECLARE_TLV_DB_SCALE(digital_coarse_tlv, 0, 600, 0);
+
+/*
+ * Voice Downlink GAIN volume control:
+ * from -37 to 12 dB in 1 dB steps (mute instead of -37 dB)
+ */
+static DECLARE_TLV_DB_SCALE(digital_voice_downlink_tlv, -3700, 100, 1);
+
+/*
+ * Analog playback gain
+ * -24 dB to 12 dB in 2 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(analog_tlv, -2400, 200, 0);
+
+/*
+ * Gain controls tied to outputs
+ * -6 dB to 6 dB in 6 dB steps (mute instead of -12)
+ */
+static DECLARE_TLV_DB_SCALE(output_tvl, -1200, 600, 1);
+
+/*
+ * Gain control for earpiece amplifier
+ * 0 dB to 12 dB in 6 dB steps (mute instead of -6)
+ */
+static DECLARE_TLV_DB_SCALE(output_ear_tvl, -600, 600, 1);
+
+/*
+ * Capture gain after the ADCs
+ * from 0 dB to 31 dB in 1 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0);
+
+/*
+ * Gain control for input amplifiers
+ * 0 dB to 30 dB in 6 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0);
+
+/* AVADC clock priority */
+static const char *twl4030_avadc_clk_priority_texts[] = {
+ "Voice high priority", "HiFi high priority"
+};
+
+static const struct soc_enum twl4030_avadc_clk_priority_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_AVADC_CTL, 2,
+ ARRAY_SIZE(twl4030_avadc_clk_priority_texts),
+ twl4030_avadc_clk_priority_texts);
+
+static const char *twl4030_rampdelay_texts[] = {
+ "27/20/14 ms", "55/40/27 ms", "109/81/55 ms", "218/161/109 ms",
+ "437/323/218 ms", "874/645/437 ms", "1748/1291/874 ms",
+ "3495/2581/1748 ms"
+};
+
+static const struct soc_enum twl4030_rampdelay_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_HS_POPN_SET, 2,
+ ARRAY_SIZE(twl4030_rampdelay_texts),
+ twl4030_rampdelay_texts);
+
+/* Vibra H-bridge direction mode */
+static const char *twl4030_vibradirmode_texts[] = {
+ "Vibra H-bridge direction", "Audio data MSB",
+};
+
+static const struct soc_enum twl4030_vibradirmode_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 5,
+ ARRAY_SIZE(twl4030_vibradirmode_texts),
+ twl4030_vibradirmode_texts);
+
+/* Vibra H-bridge direction */
+static const char *twl4030_vibradir_texts[] = {
+ "Positive polarity", "Negative polarity",
+};
+
+static const struct soc_enum twl4030_vibradir_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 1,
+ ARRAY_SIZE(twl4030_vibradir_texts),
+ twl4030_vibradir_texts);
+
+/* Digimic Left and right swapping */
+static const char *twl4030_digimicswap_texts[] = {
+ "Not swapped", "Swapped",
+};
+
+static const struct soc_enum twl4030_digimicswap_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_MISC_SET_1, 0,
+ ARRAY_SIZE(twl4030_digimicswap_texts),
+ twl4030_digimicswap_texts);
+
+static const struct snd_kcontrol_new twl4030_snd_controls[] = {
+ /* Codec operation mode control */
+ SOC_ENUM_EXT("Codec Operation Mode", twl4030_op_modes_enum,
+ snd_soc_get_enum_double,
+ snd_soc_put_twl4030_opmode_enum_double),
+
+ /* Common playback gain controls */
+ SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume",
+ TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA,
+ 0, 0x3f, 0, digital_fine_tlv),
+ SOC_DOUBLE_R_TLV("DAC2 Digital Fine Playback Volume",
+ TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA,
+ 0, 0x3f, 0, digital_fine_tlv),
+
+ SOC_DOUBLE_R_TLV("DAC1 Digital Coarse Playback Volume",
+ TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA,
+ 6, 0x2, 0, digital_coarse_tlv),
+ SOC_DOUBLE_R_TLV("DAC2 Digital Coarse Playback Volume",
+ TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA,
+ 6, 0x2, 0, digital_coarse_tlv),
+
+ SOC_DOUBLE_R_TLV("DAC1 Analog Playback Volume",
+ TWL4030_REG_ARXL1_APGA_CTL, TWL4030_REG_ARXR1_APGA_CTL,
+ 3, 0x12, 1, analog_tlv),
+ SOC_DOUBLE_R_TLV("DAC2 Analog Playback Volume",
+ TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL,
+ 3, 0x12, 1, analog_tlv),
+ SOC_DOUBLE_R("DAC1 Analog Playback Switch",
+ TWL4030_REG_ARXL1_APGA_CTL, TWL4030_REG_ARXR1_APGA_CTL,
+ 1, 1, 0),
+ SOC_DOUBLE_R("DAC2 Analog Playback Switch",
+ TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL,
+ 1, 1, 0),
+
+ /* Common voice downlink gain controls */
+ SOC_SINGLE_TLV("DAC Voice Digital Downlink Volume",
+ TWL4030_REG_VRXPGA, 0, 0x31, 0, digital_voice_downlink_tlv),
+
+ SOC_SINGLE_TLV("DAC Voice Analog Downlink Volume",
+ TWL4030_REG_VDL_APGA_CTL, 3, 0x12, 1, analog_tlv),
+
+ SOC_SINGLE("DAC Voice Analog Downlink Switch",
+ TWL4030_REG_VDL_APGA_CTL, 1, 1, 0),
+
+ /* Separate output gain controls */
+ SOC_DOUBLE_R_TLV_TWL4030("PreDriv Playback Volume",
+ TWL4030_REG_PREDL_CTL, TWL4030_REG_PREDR_CTL,
+ 4, 3, 0, output_tvl),
+
+ SOC_DOUBLE_TLV_TWL4030("Headset Playback Volume",
+ TWL4030_REG_HS_GAIN_SET, 0, 2, 3, 0, output_tvl),
+
+ SOC_DOUBLE_R_TLV_TWL4030("Carkit Playback Volume",
+ TWL4030_REG_PRECKL_CTL, TWL4030_REG_PRECKR_CTL,
+ 4, 3, 0, output_tvl),
+
+ SOC_SINGLE_TLV_TWL4030("Earpiece Playback Volume",
+ TWL4030_REG_EAR_CTL, 4, 3, 0, output_ear_tvl),
+
+ /* Common capture gain controls */
+ SOC_DOUBLE_R_TLV("TX1 Digital Capture Volume",
+ TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA,
+ 0, 0x1f, 0, digital_capture_tlv),
+ SOC_DOUBLE_R_TLV("TX2 Digital Capture Volume",
+ TWL4030_REG_AVTXL2PGA, TWL4030_REG_AVTXR2PGA,
+ 0, 0x1f, 0, digital_capture_tlv),
+
+ SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN,
+ 0, 3, 5, 0, input_gain_tlv),
+
+ SOC_ENUM("AVADC Clock Priority", twl4030_avadc_clk_priority_enum),
+
+ SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum),
+
+ SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum),
+ SOC_ENUM("Vibra H-bridge direction", twl4030_vibradir_enum),
+
+ SOC_ENUM("Digimic LR Swap", twl4030_digimicswap_enum),
+};
+
+static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
+ /* Left channel inputs */
+ SND_SOC_DAPM_INPUT("MAINMIC"),
+ SND_SOC_DAPM_INPUT("HSMIC"),
+ SND_SOC_DAPM_INPUT("AUXL"),
+ SND_SOC_DAPM_INPUT("CARKITMIC"),
+ /* Right channel inputs */
+ SND_SOC_DAPM_INPUT("SUBMIC"),
+ SND_SOC_DAPM_INPUT("AUXR"),
+ /* Digital microphones (Stereo) */
+ SND_SOC_DAPM_INPUT("DIGIMIC0"),
+ SND_SOC_DAPM_INPUT("DIGIMIC1"),
+
+ /* Outputs */
+ SND_SOC_DAPM_OUTPUT("EARPIECE"),
+ SND_SOC_DAPM_OUTPUT("PREDRIVEL"),
+ SND_SOC_DAPM_OUTPUT("PREDRIVER"),
+ SND_SOC_DAPM_OUTPUT("HSOL"),
+ SND_SOC_DAPM_OUTPUT("HSOR"),
+ SND_SOC_DAPM_OUTPUT("CARKITL"),
+ SND_SOC_DAPM_OUTPUT("CARKITR"),
+ SND_SOC_DAPM_OUTPUT("HFL"),
+ SND_SOC_DAPM_OUTPUT("HFR"),
+ SND_SOC_DAPM_OUTPUT("VIBRA"),
+
+ /* AIF and APLL clocks for running DAIs (including loopback) */
+ SND_SOC_DAPM_OUTPUT("Virtual HiFi OUT"),
+ SND_SOC_DAPM_INPUT("Virtual HiFi IN"),
+ SND_SOC_DAPM_OUTPUT("Virtual Voice OUT"),
+
+ /* DACs */
+ SND_SOC_DAPM_DAC("DAC Right1", "Right Front HiFi Playback",
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DAC("DAC Left1", "Left Front HiFi Playback",
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DAC("DAC Right2", "Right Rear HiFi Playback",
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DAC("DAC Left2", "Left Rear HiFi Playback",
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DAC("DAC Voice", "Voice Playback",
+ SND_SOC_NOPM, 0, 0),
+
+ /* Analog bypasses */
+ SND_SOC_DAPM_SWITCH("Right1 Analog Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_abypassr1_control),
+ SND_SOC_DAPM_SWITCH("Left1 Analog Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_abypassl1_control),
+ SND_SOC_DAPM_SWITCH("Right2 Analog Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_abypassr2_control),
+ SND_SOC_DAPM_SWITCH("Left2 Analog Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_abypassl2_control),
+ SND_SOC_DAPM_SWITCH("Voice Analog Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_abypassv_control),
+
+ /* Master analog loopback switch */
+ SND_SOC_DAPM_SUPPLY("FM Loop Enable", TWL4030_REG_MISC_SET_1, 5, 0,
+ NULL, 0),
+
+ /* Digital bypasses */
+ SND_SOC_DAPM_SWITCH("Left Digital Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_dbypassl_control),
+ SND_SOC_DAPM_SWITCH("Right Digital Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_dbypassr_control),
+ SND_SOC_DAPM_SWITCH("Voice Digital Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_dbypassv_control),
+
+ /* Digital mixers, power control for the physical DACs */
+ SND_SOC_DAPM_MIXER("Digital R1 Playback Mixer",
+ TWL4030_REG_AVDAC_CTL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Digital L1 Playback Mixer",
+ TWL4030_REG_AVDAC_CTL, 1, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Digital R2 Playback Mixer",
+ TWL4030_REG_AVDAC_CTL, 2, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Digital L2 Playback Mixer",
+ TWL4030_REG_AVDAC_CTL, 3, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Digital Voice Playback Mixer",
+ TWL4030_REG_AVDAC_CTL, 4, 0, NULL, 0),
+
+ /* Analog mixers, power control for the physical PGAs */
+ SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer",
+ TWL4030_REG_ARXR1_APGA_CTL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer",
+ TWL4030_REG_ARXL1_APGA_CTL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer",
+ TWL4030_REG_ARXR2_APGA_CTL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer",
+ TWL4030_REG_ARXL2_APGA_CTL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Analog Voice Playback Mixer",
+ TWL4030_REG_VDL_APGA_CTL, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_SUPPLY("APLL Enable", SND_SOC_NOPM, 0, 0, apll_event,
+ SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_SUPPLY("AIF Enable", SND_SOC_NOPM, 0, 0, aif_event,
+ SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD),
+
+ /* Output MIXER controls */
+ /* Earpiece */
+ SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_earpiece_controls[0],
+ ARRAY_SIZE(twl4030_dapm_earpiece_controls)),
+ SND_SOC_DAPM_PGA_E("Earpiece PGA", SND_SOC_NOPM,
+ 0, 0, NULL, 0, earpiecepga_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+ /* PreDrivL/R */
+ SND_SOC_DAPM_MIXER("PredriveL Mixer", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_predrivel_controls[0],
+ ARRAY_SIZE(twl4030_dapm_predrivel_controls)),
+ SND_SOC_DAPM_PGA_E("PredriveL PGA", SND_SOC_NOPM,
+ 0, 0, NULL, 0, predrivelpga_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_MIXER("PredriveR Mixer", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_predriver_controls[0],
+ ARRAY_SIZE(twl4030_dapm_predriver_controls)),
+ SND_SOC_DAPM_PGA_E("PredriveR PGA", SND_SOC_NOPM,
+ 0, 0, NULL, 0, predriverpga_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+ /* HeadsetL/R */
+ SND_SOC_DAPM_MIXER("HeadsetL Mixer", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_hsol_controls[0],
+ ARRAY_SIZE(twl4030_dapm_hsol_controls)),
+ SND_SOC_DAPM_PGA_E("HeadsetL PGA", SND_SOC_NOPM,
+ 0, 0, NULL, 0, headsetlpga_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_MIXER("HeadsetR Mixer", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_hsor_controls[0],
+ ARRAY_SIZE(twl4030_dapm_hsor_controls)),
+ SND_SOC_DAPM_PGA_E("HeadsetR PGA", SND_SOC_NOPM,
+ 0, 0, NULL, 0, headsetrpga_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+ /* CarkitL/R */
+ SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_carkitl_controls[0],
+ ARRAY_SIZE(twl4030_dapm_carkitl_controls)),
+ SND_SOC_DAPM_PGA_E("CarkitL PGA", SND_SOC_NOPM,
+ 0, 0, NULL, 0, carkitlpga_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_MIXER("CarkitR Mixer", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_carkitr_controls[0],
+ ARRAY_SIZE(twl4030_dapm_carkitr_controls)),
+ SND_SOC_DAPM_PGA_E("CarkitR PGA", SND_SOC_NOPM,
+ 0, 0, NULL, 0, carkitrpga_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+
+ /* Output MUX controls */
+ /* HandsfreeL/R */
+ SND_SOC_DAPM_MUX("HandsfreeL Mux", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_handsfreel_control),
+ SND_SOC_DAPM_SWITCH("HandsfreeL", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_handsfreelmute_control),
+ SND_SOC_DAPM_PGA_E("HandsfreeL PGA", SND_SOC_NOPM,
+ 0, 0, NULL, 0, handsfreelpga_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_MUX("HandsfreeR Mux", SND_SOC_NOPM, 5, 0,
+ &twl4030_dapm_handsfreer_control),
+ SND_SOC_DAPM_SWITCH("HandsfreeR", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_handsfreermute_control),
+ SND_SOC_DAPM_PGA_E("HandsfreeR PGA", SND_SOC_NOPM,
+ 0, 0, NULL, 0, handsfreerpga_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+ /* Vibra */
+ SND_SOC_DAPM_MUX_E("Vibra Mux", TWL4030_REG_VIBRA_CTL, 0, 0,
+ &twl4030_dapm_vibra_control, vibramux_event,
+ SND_SOC_DAPM_PRE_PMU),
+ SND_SOC_DAPM_MUX("Vibra Route", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_vibrapath_control),
+
+ /* Introducing four virtual ADC, since TWL4030 have four channel for
+ capture */
+ SND_SOC_DAPM_ADC("ADC Virtual Left1", "Left Front Capture",
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_ADC("ADC Virtual Right1", "Right Front Capture",
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_ADC("ADC Virtual Left2", "Left Rear Capture",
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_ADC("ADC Virtual Right2", "Right Rear Capture",
+ SND_SOC_NOPM, 0, 0),
+
+ /* Analog/Digital mic path selection.
+ TX1 Left/Right: either analog Left/Right or Digimic0
+ TX2 Left/Right: either analog Left/Right or Digimic1 */
+ SND_SOC_DAPM_MUX("TX1 Capture Route", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_micpathtx1_control),
+ SND_SOC_DAPM_MUX("TX2 Capture Route", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_micpathtx2_control),
+
+ /* Analog input mixers for the capture amplifiers */
+ SND_SOC_DAPM_MIXER("Analog Left",
+ TWL4030_REG_ANAMICL, 4, 0,
+ &twl4030_dapm_analoglmic_controls[0],
+ ARRAY_SIZE(twl4030_dapm_analoglmic_controls)),
+ SND_SOC_DAPM_MIXER("Analog Right",
+ TWL4030_REG_ANAMICR, 4, 0,
+ &twl4030_dapm_analogrmic_controls[0],
+ ARRAY_SIZE(twl4030_dapm_analogrmic_controls)),
+
+ SND_SOC_DAPM_PGA("ADC Physical Left",
+ TWL4030_REG_AVADC_CTL, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("ADC Physical Right",
+ TWL4030_REG_AVADC_CTL, 1, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA_E("Digimic0 Enable",
+ TWL4030_REG_ADCMICSEL, 1, 0, NULL, 0,
+ digimic_event, SND_SOC_DAPM_POST_PMU),
+ SND_SOC_DAPM_PGA_E("Digimic1 Enable",
+ TWL4030_REG_ADCMICSEL, 3, 0, NULL, 0,
+ digimic_event, SND_SOC_DAPM_POST_PMU),
+
+ SND_SOC_DAPM_SUPPLY("micbias1 select", TWL4030_REG_MICBIAS_CTL, 5, 0,
+ NULL, 0),
+ SND_SOC_DAPM_SUPPLY("micbias2 select", TWL4030_REG_MICBIAS_CTL, 6, 0,
+ NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias 1", TWL4030_REG_MICBIAS_CTL, 0, 0),
+ SND_SOC_DAPM_MICBIAS("Mic Bias 2", TWL4030_REG_MICBIAS_CTL, 1, 0),
+ SND_SOC_DAPM_MICBIAS("Headset Mic Bias", TWL4030_REG_MICBIAS_CTL, 2, 0),
+
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ {"Digital L1 Playback Mixer", NULL, "DAC Left1"},
+ {"Digital R1 Playback Mixer", NULL, "DAC Right1"},
+ {"Digital L2 Playback Mixer", NULL, "DAC Left2"},
+ {"Digital R2 Playback Mixer", NULL, "DAC Right2"},
+ {"Digital Voice Playback Mixer", NULL, "DAC Voice"},
+
+ /* Supply for the digital part (APLL) */
+ {"Digital Voice Playback Mixer", NULL, "APLL Enable"},
+
+ {"DAC Left1", NULL, "AIF Enable"},
+ {"DAC Right1", NULL, "AIF Enable"},
+ {"DAC Left2", NULL, "AIF Enable"},
+ {"DAC Right1", NULL, "AIF Enable"},
+
+ {"Digital R2 Playback Mixer", NULL, "AIF Enable"},
+ {"Digital L2 Playback Mixer", NULL, "AIF Enable"},
+
+ {"Analog L1 Playback Mixer", NULL, "Digital L1 Playback Mixer"},
+ {"Analog R1 Playback Mixer", NULL, "Digital R1 Playback Mixer"},
+ {"Analog L2 Playback Mixer", NULL, "Digital L2 Playback Mixer"},
+ {"Analog R2 Playback Mixer", NULL, "Digital R2 Playback Mixer"},
+ {"Analog Voice Playback Mixer", NULL, "Digital Voice Playback Mixer"},
+
+ /* Internal playback routings */
+ /* Earpiece */
+ {"Earpiece Mixer", "Voice", "Analog Voice Playback Mixer"},
+ {"Earpiece Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+ {"Earpiece Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+ {"Earpiece Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+ {"Earpiece PGA", NULL, "Earpiece Mixer"},
+ /* PreDrivL */
+ {"PredriveL Mixer", "Voice", "Analog Voice Playback Mixer"},
+ {"PredriveL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+ {"PredriveL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+ {"PredriveL Mixer", "AudioR2", "Analog R2 Playback Mixer"},
+ {"PredriveL PGA", NULL, "PredriveL Mixer"},
+ /* PreDrivR */
+ {"PredriveR Mixer", "Voice", "Analog Voice Playback Mixer"},
+ {"PredriveR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+ {"PredriveR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
+ {"PredriveR Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+ {"PredriveR PGA", NULL, "PredriveR Mixer"},
+ /* HeadsetL */
+ {"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"},
+ {"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+ {"HeadsetL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+ {"HeadsetL PGA", NULL, "HeadsetL Mixer"},
+ /* HeadsetR */
+ {"HeadsetR Mixer", "Voice", "Analog Voice Playback Mixer"},
+ {"HeadsetR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+ {"HeadsetR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
+ {"HeadsetR PGA", NULL, "HeadsetR Mixer"},
+ /* CarkitL */
+ {"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"},
+ {"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+ {"CarkitL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+ {"CarkitL PGA", NULL, "CarkitL Mixer"},
+ /* CarkitR */
+ {"CarkitR Mixer", "Voice", "Analog Voice Playback Mixer"},
+ {"CarkitR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+ {"CarkitR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
+ {"CarkitR PGA", NULL, "CarkitR Mixer"},
+ /* HandsfreeL */
+ {"HandsfreeL Mux", "Voice", "Analog Voice Playback Mixer"},
+ {"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"},
+ {"HandsfreeL Mux", "AudioL2", "Analog L2 Playback Mixer"},
+ {"HandsfreeL Mux", "AudioR2", "Analog R2 Playback Mixer"},
+ {"HandsfreeL", "Switch", "HandsfreeL Mux"},
+ {"HandsfreeL PGA", NULL, "HandsfreeL"},
+ /* HandsfreeR */
+ {"HandsfreeR Mux", "Voice", "Analog Voice Playback Mixer"},
+ {"HandsfreeR Mux", "AudioR1", "Analog R1 Playback Mixer"},
+ {"HandsfreeR Mux", "AudioR2", "Analog R2 Playback Mixer"},
+ {"HandsfreeR Mux", "AudioL2", "Analog L2 Playback Mixer"},
+ {"HandsfreeR", "Switch", "HandsfreeR Mux"},
+ {"HandsfreeR PGA", NULL, "HandsfreeR"},
+ /* Vibra */
+ {"Vibra Mux", "AudioL1", "DAC Left1"},
+ {"Vibra Mux", "AudioR1", "DAC Right1"},
+ {"Vibra Mux", "AudioL2", "DAC Left2"},
+ {"Vibra Mux", "AudioR2", "DAC Right2"},
+
+ /* outputs */
+ /* Must be always connected (for AIF and APLL) */
+ {"Virtual HiFi OUT", NULL, "DAC Left1"},
+ {"Virtual HiFi OUT", NULL, "DAC Right1"},
+ {"Virtual HiFi OUT", NULL, "DAC Left2"},
+ {"Virtual HiFi OUT", NULL, "DAC Right2"},
+ /* Must be always connected (for APLL) */
+ {"Virtual Voice OUT", NULL, "Digital Voice Playback Mixer"},
+ /* Physical outputs */
+ {"EARPIECE", NULL, "Earpiece PGA"},
+ {"PREDRIVEL", NULL, "PredriveL PGA"},
+ {"PREDRIVER", NULL, "PredriveR PGA"},
+ {"HSOL", NULL, "HeadsetL PGA"},
+ {"HSOR", NULL, "HeadsetR PGA"},
+ {"CARKITL", NULL, "CarkitL PGA"},
+ {"CARKITR", NULL, "CarkitR PGA"},
+ {"HFL", NULL, "HandsfreeL PGA"},
+ {"HFR", NULL, "HandsfreeR PGA"},
+ {"Vibra Route", "Audio", "Vibra Mux"},
+ {"VIBRA", NULL, "Vibra Route"},
+
+ /* Capture path */
+ /* Must be always connected (for AIF and APLL) */
+ {"ADC Virtual Left1", NULL, "Virtual HiFi IN"},
+ {"ADC Virtual Right1", NULL, "Virtual HiFi IN"},
+ {"ADC Virtual Left2", NULL, "Virtual HiFi IN"},
+ {"ADC Virtual Right2", NULL, "Virtual HiFi IN"},
+ /* Physical inputs */
+ {"Analog Left", "Main Mic Capture Switch", "MAINMIC"},
+ {"Analog Left", "Headset Mic Capture Switch", "HSMIC"},
+ {"Analog Left", "AUXL Capture Switch", "AUXL"},
+ {"Analog Left", "Carkit Mic Capture Switch", "CARKITMIC"},
+
+ {"Analog Right", "Sub Mic Capture Switch", "SUBMIC"},
+ {"Analog Right", "AUXR Capture Switch", "AUXR"},
+
+ {"ADC Physical Left", NULL, "Analog Left"},
+ {"ADC Physical Right", NULL, "Analog Right"},
+
+ {"Digimic0 Enable", NULL, "DIGIMIC0"},
+ {"Digimic1 Enable", NULL, "DIGIMIC1"},
+
+ {"DIGIMIC0", NULL, "micbias1 select"},
+ {"DIGIMIC1", NULL, "micbias2 select"},
+
+ /* TX1 Left capture path */
+ {"TX1 Capture Route", "Analog", "ADC Physical Left"},
+ {"TX1 Capture Route", "Digimic0", "Digimic0 Enable"},
+ /* TX1 Right capture path */
+ {"TX1 Capture Route", "Analog", "ADC Physical Right"},
+ {"TX1 Capture Route", "Digimic0", "Digimic0 Enable"},
+ /* TX2 Left capture path */
+ {"TX2 Capture Route", "Analog", "ADC Physical Left"},
+ {"TX2 Capture Route", "Digimic1", "Digimic1 Enable"},
+ /* TX2 Right capture path */
+ {"TX2 Capture Route", "Analog", "ADC Physical Right"},
+ {"TX2 Capture Route", "Digimic1", "Digimic1 Enable"},
+
+ {"ADC Virtual Left1", NULL, "TX1 Capture Route"},
+ {"ADC Virtual Right1", NULL, "TX1 Capture Route"},
+ {"ADC Virtual Left2", NULL, "TX2 Capture Route"},
+ {"ADC Virtual Right2", NULL, "TX2 Capture Route"},
+
+ {"ADC Virtual Left1", NULL, "AIF Enable"},
+ {"ADC Virtual Right1", NULL, "AIF Enable"},
+ {"ADC Virtual Left2", NULL, "AIF Enable"},
+ {"ADC Virtual Right2", NULL, "AIF Enable"},
+
+ /* Analog bypass routes */
+ {"Right1 Analog Loopback", "Switch", "Analog Right"},
+ {"Left1 Analog Loopback", "Switch", "Analog Left"},
+ {"Right2 Analog Loopback", "Switch", "Analog Right"},
+ {"Left2 Analog Loopback", "Switch", "Analog Left"},
+ {"Voice Analog Loopback", "Switch", "Analog Left"},
+
+ /* Supply for the Analog loopbacks */
+ {"Right1 Analog Loopback", NULL, "FM Loop Enable"},
+ {"Left1 Analog Loopback", NULL, "FM Loop Enable"},
+ {"Right2 Analog Loopback", NULL, "FM Loop Enable"},
+ {"Left2 Analog Loopback", NULL, "FM Loop Enable"},
+ {"Voice Analog Loopback", NULL, "FM Loop Enable"},
+
+ {"Analog R1 Playback Mixer", NULL, "Right1 Analog Loopback"},
+ {"Analog L1 Playback Mixer", NULL, "Left1 Analog Loopback"},
+ {"Analog R2 Playback Mixer", NULL, "Right2 Analog Loopback"},
+ {"Analog L2 Playback Mixer", NULL, "Left2 Analog Loopback"},
+ {"Analog Voice Playback Mixer", NULL, "Voice Analog Loopback"},
+
+ /* Digital bypass routes */
+ {"Right Digital Loopback", "Volume", "TX1 Capture Route"},
+ {"Left Digital Loopback", "Volume", "TX1 Capture Route"},
+ {"Voice Digital Loopback", "Volume", "TX2 Capture Route"},
+
+ {"Digital R2 Playback Mixer", NULL, "Right Digital Loopback"},
+ {"Digital L2 Playback Mixer", NULL, "Left Digital Loopback"},
+ {"Digital Voice Playback Mixer", NULL, "Voice Digital Loopback"},
+
+};
+
+static int twl4030_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, twl4030_dapm_widgets,
+ ARRAY_SIZE(twl4030_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ return 0;
+}
+
+static int twl4030_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF)
+ twl4030_codec_enable(codec, 1);
+ break;
+ case SND_SOC_BIAS_OFF:
+ twl4030_codec_enable(codec, 0);
+ break;
+ }
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static void twl4030_constraints(struct twl4030_priv *twl4030,
+ struct snd_pcm_substream *mst_substream)
+{
+ struct snd_pcm_substream *slv_substream;
+
+ /* Pick the stream, which need to be constrained */
+ if (mst_substream == twl4030->master_substream)
+ slv_substream = twl4030->slave_substream;
+ else if (mst_substream == twl4030->slave_substream)
+ slv_substream = twl4030->master_substream;
+ else /* This should not happen.. */
+ return;
+
+ /* Set the constraints according to the already configured stream */
+ snd_pcm_hw_constraint_minmax(slv_substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ twl4030->rate,
+ twl4030->rate);
+
+ snd_pcm_hw_constraint_minmax(slv_substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ twl4030->sample_bits,
+ twl4030->sample_bits);
+
+ snd_pcm_hw_constraint_minmax(slv_substream->runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ twl4030->channels,
+ twl4030->channels);
+}
+
+/* In case of 4 channel mode, the RX1 L/R for playback and the TX2 L/R for
+ * capture has to be enabled/disabled. */
+static void twl4030_tdm_enable(struct snd_soc_codec *codec, int direction,
+ int enable)
+{
+ u8 reg, mask;
+
+ reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION);
+
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+ mask = TWL4030_ARXL1_VRX_EN | TWL4030_ARXR1_EN;
+ else
+ mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN;
+
+ if (enable)
+ reg |= mask;
+ else
+ reg &= ~mask;
+
+ twl4030_write(codec, TWL4030_REG_OPTION, reg);
+}
+
+static int twl4030_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+
+ snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24);
+ if (twl4030->master_substream) {
+ twl4030->slave_substream = substream;
+ /* The DAI has one configuration for playback and capture, so
+ * if the DAI has been already configured then constrain this
+ * substream to match it. */
+ if (twl4030->configured)
+ twl4030_constraints(twl4030, twl4030->master_substream);
+ } else {
+ if (!(twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) &
+ TWL4030_OPTION_1)) {
+ /* In option2 4 channel is not supported, set the
+ * constraint for the first stream for channels, the
+ * second stream will 'inherit' this cosntraint */
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ 2, 2);
+ }
+ twl4030->master_substream = substream;
+ }
+
+ return 0;
+}
+
+static void twl4030_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+
+ if (twl4030->master_substream == substream)
+ twl4030->master_substream = twl4030->slave_substream;
+
+ twl4030->slave_substream = NULL;
+
+ /* If all streams are closed, or the remaining stream has not yet
+ * been configured than set the DAI as not configured. */
+ if (!twl4030->master_substream)
+ twl4030->configured = 0;
+ else if (!twl4030->master_substream->runtime->channels)
+ twl4030->configured = 0;
+
+ /* If the closing substream had 4 channel, do the necessary cleanup */
+ if (substream->runtime->channels == 4)
+ twl4030_tdm_enable(codec, substream->stream, 0);
+}
+
+static int twl4030_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ u8 mode, old_mode, format, old_format;
+
+ /* If the substream has 4 channel, do the necessary setup */
+ if (params_channels(params) == 4) {
+ format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+ mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE);
+
+ /* Safety check: are we in the correct operating mode and
+ * the interface is in TDM mode? */
+ if ((mode & TWL4030_OPTION_1) &&
+ ((format & TWL4030_AIF_FORMAT) == TWL4030_AIF_FORMAT_TDM))
+ twl4030_tdm_enable(codec, substream->stream, 1);
+ else
+ return -EINVAL;
+ }
+
+ if (twl4030->configured)
+ /* Ignoring hw_params for already configured DAI */
+ return 0;
+
+ /* bit rate */
+ old_mode = twl4030_read_reg_cache(codec,
+ TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ;
+ mode = old_mode & ~TWL4030_APLL_RATE;
+
+ switch (params_rate(params)) {
+ case 8000:
+ mode |= TWL4030_APLL_RATE_8000;
+ break;
+ case 11025:
+ mode |= TWL4030_APLL_RATE_11025;
+ break;
+ case 12000:
+ mode |= TWL4030_APLL_RATE_12000;
+ break;
+ case 16000:
+ mode |= TWL4030_APLL_RATE_16000;
+ break;
+ case 22050:
+ mode |= TWL4030_APLL_RATE_22050;
+ break;
+ case 24000:
+ mode |= TWL4030_APLL_RATE_24000;
+ break;
+ case 32000:
+ mode |= TWL4030_APLL_RATE_32000;
+ break;
+ case 44100:
+ mode |= TWL4030_APLL_RATE_44100;
+ break;
+ case 48000:
+ mode |= TWL4030_APLL_RATE_48000;
+ break;
+ case 96000:
+ mode |= TWL4030_APLL_RATE_96000;
+ break;
+ default:
+ printk(KERN_ERR "TWL4030 hw params: unknown rate %d\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ /* sample size */
+ old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+ format = old_format;
+ format &= ~TWL4030_DATA_WIDTH;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ format |= TWL4030_DATA_WIDTH_16S_16W;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ format |= TWL4030_DATA_WIDTH_32S_24W;
+ break;
+ default:
+ printk(KERN_ERR "TWL4030 hw params: unknown format %d\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ if (format != old_format || mode != old_mode) {
+ if (twl4030->codec_powered) {
+ /*
+ * If the codec is powered, than we need to toggle the
+ * codec power.
+ */
+ twl4030_codec_enable(codec, 0);
+ twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+ twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+ twl4030_codec_enable(codec, 1);
+ } else {
+ twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+ twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+ }
+ }
+
+ /* Store the important parameters for the DAI configuration and set
+ * the DAI as configured */
+ twl4030->configured = 1;
+ twl4030->rate = params_rate(params);
+ twl4030->sample_bits = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
+ twl4030->channels = params_channels(params);
+
+ /* If both playback and capture streams are open, and one of them
+ * is setting the hw parameters right now (since we are here), set
+ * constraints to the other stream to match the current one. */
+ if (twl4030->slave_substream)
+ twl4030_constraints(twl4030, substream);
+
+ return 0;
+}
+
+static int twl4030_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case 19200000:
+ case 26000000:
+ case 38400000:
+ break;
+ default:
+ dev_err(codec->dev, "Unsupported APLL mclk: %u\n", freq);
+ return -EINVAL;
+ }
+
+ if ((freq / 1000) != twl4030->sysclk) {
+ dev_err(codec->dev,
+ "Mismatch in APLL mclk: %u (configured: %u)\n",
+ freq, twl4030->sysclk * 1000);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ u8 old_format, format;
+
+ /* get format */
+ old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+ format = old_format;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ format &= ~(TWL4030_AIF_SLAVE_EN);
+ format &= ~(TWL4030_CLK256FS_EN);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ format |= TWL4030_AIF_SLAVE_EN;
+ format |= TWL4030_CLK256FS_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ format &= ~TWL4030_AIF_FORMAT;
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ format |= TWL4030_AIF_FORMAT_CODEC;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ format |= TWL4030_AIF_FORMAT_TDM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (format != old_format) {
+ if (twl4030->codec_powered) {
+ /*
+ * If the codec is powered, than we need to toggle the
+ * codec power.
+ */
+ twl4030_codec_enable(codec, 0);
+ twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+ twl4030_codec_enable(codec, 1);
+ } else {
+ twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+ }
+ }
+
+ return 0;
+}
+
+static int twl4030_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+
+ if (tristate)
+ reg |= TWL4030_AIF_TRI_EN;
+ else
+ reg &= ~TWL4030_AIF_TRI_EN;
+
+ return twl4030_write(codec, TWL4030_REG_AUDIO_IF, reg);
+}
+
+/* In case of voice mode, the RX1 L(VRX) for downlink and the TX2 L/R
+ * (VTXL, VTXR) for uplink has to be enabled/disabled. */
+static void twl4030_voice_enable(struct snd_soc_codec *codec, int direction,
+ int enable)
+{
+ u8 reg, mask;
+
+ reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION);
+
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+ mask = TWL4030_ARXL1_VRX_EN;
+ else
+ mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN;
+
+ if (enable)
+ reg |= mask;
+ else
+ reg &= ~mask;
+
+ twl4030_write(codec, TWL4030_REG_OPTION, reg);
+}
+
+static int twl4030_voice_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ u8 mode;
+
+ /* If the system master clock is not 26MHz, the voice PCM interface is
+ * not available.
+ */
+ if (twl4030->sysclk != 26000) {
+ dev_err(codec->dev, "The board is configured for %u Hz, while"
+ "the Voice interface needs 26MHz APLL mclk\n",
+ twl4030->sysclk * 1000);
+ return -EINVAL;
+ }
+
+ /* If the codec mode is not option2, the voice PCM interface is not
+ * available.
+ */
+ mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE)
+ & TWL4030_OPT_MODE;
+
+ if (mode != TWL4030_OPTION_2) {
+ printk(KERN_ERR "TWL4030 voice startup: "
+ "the codec mode is not option2\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void twl4030_voice_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ /* Enable voice digital filters */
+ twl4030_voice_enable(codec, substream->stream, 0);
+}
+
+static int twl4030_voice_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ u8 old_mode, mode;
+
+ /* Enable voice digital filters */
+ twl4030_voice_enable(codec, substream->stream, 1);
+
+ /* bit rate */
+ old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE)
+ & ~(TWL4030_CODECPDZ);
+ mode = old_mode;
+
+ switch (params_rate(params)) {
+ case 8000:
+ mode &= ~(TWL4030_SEL_16K);
+ break;
+ case 16000:
+ mode |= TWL4030_SEL_16K;
+ break;
+ default:
+ printk(KERN_ERR "TWL4030 voice hw params: unknown rate %d\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ if (mode != old_mode) {
+ if (twl4030->codec_powered) {
+ /*
+ * If the codec is powered, than we need to toggle the
+ * codec power.
+ */
+ twl4030_codec_enable(codec, 0);
+ twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+ twl4030_codec_enable(codec, 1);
+ } else {
+ twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+ }
+ }
+
+ return 0;
+}
+
+static int twl4030_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+
+ if (freq != 26000000) {
+ dev_err(codec->dev, "Unsupported APLL mclk: %u, the Voice"
+ "interface needs 26MHz APLL mclk\n", freq);
+ return -EINVAL;
+ }
+ if ((freq / 1000) != twl4030->sysclk) {
+ dev_err(codec->dev,
+ "Mismatch in APLL mclk: %u (configured: %u)\n",
+ freq, twl4030->sysclk * 1000);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ u8 old_format, format;
+
+ /* get format */
+ old_format = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF);
+ format = old_format;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ format &= ~(TWL4030_VIF_SLAVE_EN);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ format |= TWL4030_VIF_SLAVE_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_NF:
+ format &= ~(TWL4030_VIF_FORMAT);
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ format |= TWL4030_VIF_FORMAT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (format != old_format) {
+ if (twl4030->codec_powered) {
+ /*
+ * If the codec is powered, than we need to toggle the
+ * codec power.
+ */
+ twl4030_codec_enable(codec, 0);
+ twl4030_write(codec, TWL4030_REG_VOICE_IF, format);
+ twl4030_codec_enable(codec, 1);
+ } else {
+ twl4030_write(codec, TWL4030_REG_VOICE_IF, format);
+ }
+ }
+
+ return 0;
+}
+
+static int twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF);
+
+ if (tristate)
+ reg |= TWL4030_VIF_TRI_EN;
+ else
+ reg &= ~TWL4030_VIF_TRI_EN;
+
+ return twl4030_write(codec, TWL4030_REG_VOICE_IF, reg);
+}
+
+#define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000)
+#define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops twl4030_dai_hifi_ops = {
+ .startup = twl4030_startup,
+ .shutdown = twl4030_shutdown,
+ .hw_params = twl4030_hw_params,
+ .set_sysclk = twl4030_set_dai_sysclk,
+ .set_fmt = twl4030_set_dai_fmt,
+ .set_tristate = twl4030_set_tristate,
+};
+
+static struct snd_soc_dai_ops twl4030_dai_voice_ops = {
+ .startup = twl4030_voice_startup,
+ .shutdown = twl4030_voice_shutdown,
+ .hw_params = twl4030_voice_hw_params,
+ .set_sysclk = twl4030_voice_set_dai_sysclk,
+ .set_fmt = twl4030_voice_set_dai_fmt,
+ .set_tristate = twl4030_voice_set_tristate,
+};
+
+static struct snd_soc_dai_driver twl4030_dai[] = {
+{
+ .name = "twl4030-hifi",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 2,
+ .channels_max = 4,
+ .rates = TWL4030_RATES | SNDRV_PCM_RATE_96000,
+ .formats = TWL4030_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 4,
+ .rates = TWL4030_RATES,
+ .formats = TWL4030_FORMATS,},
+ .ops = &twl4030_dai_hifi_ops,
+},
+{
+ .name = "twl4030-voice",
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &twl4030_dai_voice_ops,
+},
+};
+
+static int twl4030_soc_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int twl4030_soc_resume(struct snd_soc_codec *codec)
+{
+ twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+static int twl4030_soc_probe(struct snd_soc_codec *codec)
+{
+ struct twl4030_priv *twl4030;
+
+ twl4030 = kzalloc(sizeof(struct twl4030_priv), GFP_KERNEL);
+ if (twl4030 == NULL) {
+ printk("Can not allocate memroy\n");
+ return -ENOMEM;
+ }
+ snd_soc_codec_set_drvdata(codec, twl4030);
+ /* Set the defaults, and power up the codec */
+ twl4030->sysclk = twl4030_codec_get_mclk() / 1000;
+ codec->dapm.idle_bias_off = 1;
+
+ twl4030_init_chip(codec);
+
+ snd_soc_add_controls(codec, twl4030_snd_controls,
+ ARRAY_SIZE(twl4030_snd_controls));
+ twl4030_add_widgets(codec);
+ return 0;
+}
+
+static int twl4030_soc_remove(struct snd_soc_codec *codec)
+{
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+
+ /* Reset registers to their chip default before leaving */
+ twl4030_reset_registers(codec);
+ twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ kfree(twl4030);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_twl4030 = {
+ .probe = twl4030_soc_probe,
+ .remove = twl4030_soc_remove,
+ .suspend = twl4030_soc_suspend,
+ .resume = twl4030_soc_resume,
+ .read = twl4030_read_reg_cache,
+ .write = twl4030_write,
+ .set_bias_level = twl4030_set_bias_level,
+ .reg_cache_size = sizeof(twl4030_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = twl4030_reg,
+};
+
+static int __devinit twl4030_codec_probe(struct platform_device *pdev)
+{
+ struct twl4030_codec_audio_data *pdata = pdev->dev.platform_data;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "platform_data is missing\n");
+ return -EINVAL;
+ }
+
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_twl4030,
+ twl4030_dai, ARRAY_SIZE(twl4030_dai));
+}
+
+static int __devexit twl4030_codec_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+MODULE_ALIAS("platform:twl4030-codec");
+
+static struct platform_driver twl4030_codec_driver = {
+ .probe = twl4030_codec_probe,
+ .remove = __devexit_p(twl4030_codec_remove),
+ .driver = {
+ .name = "twl4030-codec",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init twl4030_modinit(void)
+{
+ return platform_driver_register(&twl4030_codec_driver);
+}
+module_init(twl4030_modinit);
+
+static void __exit twl4030_exit(void)
+{
+ platform_driver_unregister(&twl4030_codec_driver);
+}
+module_exit(twl4030_exit);
+
+MODULE_DESCRIPTION("ASoC TWL4030 codec driver");
+MODULE_AUTHOR("Steve Sakoman");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c
new file mode 100644
index 00000000..4c336636
--- /dev/null
+++ b/sound/soc/codecs/twl6040.c
@@ -0,0 +1,1789 @@
+/*
+ * ALSA SoC TWL6040 codec driver
+ *
+ * Author: Misael Lopez Cruz <x0052729@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/i2c/twl.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "twl6040.h"
+
+#define TWL6040_RATES SNDRV_PCM_RATE_8000_96000
+#define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
+
+#define TWL6040_OUTHS_0dB 0x00
+#define TWL6040_OUTHS_M30dB 0x0F
+#define TWL6040_OUTHF_0dB 0x03
+#define TWL6040_OUTHF_M52dB 0x1D
+
+#define TWL6040_RAMP_NONE 0
+#define TWL6040_RAMP_UP 1
+#define TWL6040_RAMP_DOWN 2
+
+#define TWL6040_HSL_VOL_MASK 0x0F
+#define TWL6040_HSL_VOL_SHIFT 0
+#define TWL6040_HSR_VOL_MASK 0xF0
+#define TWL6040_HSR_VOL_SHIFT 4
+#define TWL6040_HF_VOL_MASK 0x1F
+#define TWL6040_HF_VOL_SHIFT 0
+
+struct twl6040_output {
+ u16 active;
+ u16 left_vol;
+ u16 right_vol;
+ u16 left_step;
+ u16 right_step;
+ unsigned int step_delay;
+ u16 ramp;
+ u16 mute;
+ struct completion ramp_done;
+};
+
+struct twl6040_jack_data {
+ struct snd_soc_jack *jack;
+ int report;
+};
+
+/* codec private data */
+struct twl6040_data {
+ int audpwron;
+ int naudint;
+ int codec_powered;
+ int pll;
+ int non_lp;
+ unsigned int sysclk;
+ struct snd_pcm_hw_constraint_list *sysclk_constraints;
+ struct completion ready;
+ struct twl6040_jack_data hs_jack;
+ struct snd_soc_codec *codec;
+ struct workqueue_struct *workqueue;
+ struct delayed_work delayed_work;
+ struct mutex mutex;
+ struct twl6040_output headset;
+ struct twl6040_output handsfree;
+ struct workqueue_struct *hf_workqueue;
+ struct workqueue_struct *hs_workqueue;
+ struct delayed_work hs_delayed_work;
+ struct delayed_work hf_delayed_work;
+};
+
+/*
+ * twl6040 register cache & default register settings
+ */
+static const u8 twl6040_reg[TWL6040_CACHEREGNUM] = {
+ 0x00, /* not used 0x00 */
+ 0x4B, /* TWL6040_ASICID (ro) 0x01 */
+ 0x00, /* TWL6040_ASICREV (ro) 0x02 */
+ 0x00, /* TWL6040_INTID 0x03 */
+ 0x00, /* TWL6040_INTMR 0x04 */
+ 0x00, /* TWL6040_NCPCTRL 0x05 */
+ 0x00, /* TWL6040_LDOCTL 0x06 */
+ 0x60, /* TWL6040_HPPLLCTL 0x07 */
+ 0x00, /* TWL6040_LPPLLCTL 0x08 */
+ 0x4A, /* TWL6040_LPPLLDIV 0x09 */
+ 0x00, /* TWL6040_AMICBCTL 0x0A */
+ 0x00, /* TWL6040_DMICBCTL 0x0B */
+ 0x18, /* TWL6040_MICLCTL 0x0C - No input selected on Left Mic */
+ 0x18, /* TWL6040_MICRCTL 0x0D - No input selected on Right Mic */
+ 0x00, /* TWL6040_MICGAIN 0x0E */
+ 0x1B, /* TWL6040_LINEGAIN 0x0F */
+ 0x00, /* TWL6040_HSLCTL 0x10 */
+ 0x00, /* TWL6040_HSRCTL 0x11 */
+ 0x00, /* TWL6040_HSGAIN 0x12 */
+ 0x00, /* TWL6040_EARCTL 0x13 */
+ 0x00, /* TWL6040_HFLCTL 0x14 */
+ 0x00, /* TWL6040_HFLGAIN 0x15 */
+ 0x00, /* TWL6040_HFRCTL 0x16 */
+ 0x00, /* TWL6040_HFRGAIN 0x17 */
+ 0x00, /* TWL6040_VIBCTLL 0x18 */
+ 0x00, /* TWL6040_VIBDATL 0x19 */
+ 0x00, /* TWL6040_VIBCTLR 0x1A */
+ 0x00, /* TWL6040_VIBDATR 0x1B */
+ 0x00, /* TWL6040_HKCTL1 0x1C */
+ 0x00, /* TWL6040_HKCTL2 0x1D */
+ 0x00, /* TWL6040_GPOCTL 0x1E */
+ 0x00, /* TWL6040_ALB 0x1F */
+ 0x00, /* TWL6040_DLB 0x20 */
+ 0x00, /* not used 0x21 */
+ 0x00, /* not used 0x22 */
+ 0x00, /* not used 0x23 */
+ 0x00, /* not used 0x24 */
+ 0x00, /* not used 0x25 */
+ 0x00, /* not used 0x26 */
+ 0x00, /* not used 0x27 */
+ 0x00, /* TWL6040_TRIM1 0x28 */
+ 0x00, /* TWL6040_TRIM2 0x29 */
+ 0x00, /* TWL6040_TRIM3 0x2A */
+ 0x00, /* TWL6040_HSOTRIM 0x2B */
+ 0x00, /* TWL6040_HFOTRIM 0x2C */
+ 0x09, /* TWL6040_ACCCTL 0x2D */
+ 0x00, /* TWL6040_STATUS (ro) 0x2E */
+};
+
+/*
+ * twl6040 vio/gnd registers:
+ * registers under vio/gnd supply can be accessed
+ * before the power-up sequence, after NRESPWRON goes high
+ */
+static const int twl6040_vio_reg[TWL6040_VIOREGNUM] = {
+ TWL6040_REG_ASICID,
+ TWL6040_REG_ASICREV,
+ TWL6040_REG_INTID,
+ TWL6040_REG_INTMR,
+ TWL6040_REG_NCPCTL,
+ TWL6040_REG_LDOCTL,
+ TWL6040_REG_AMICBCTL,
+ TWL6040_REG_DMICBCTL,
+ TWL6040_REG_HKCTL1,
+ TWL6040_REG_HKCTL2,
+ TWL6040_REG_GPOCTL,
+ TWL6040_REG_TRIM1,
+ TWL6040_REG_TRIM2,
+ TWL6040_REG_TRIM3,
+ TWL6040_REG_HSOTRIM,
+ TWL6040_REG_HFOTRIM,
+ TWL6040_REG_ACCCTL,
+ TWL6040_REG_STATUS,
+};
+
+/*
+ * twl6040 vdd/vss registers:
+ * registers under vdd/vss supplies can only be accessed
+ * after the power-up sequence
+ */
+static const int twl6040_vdd_reg[TWL6040_VDDREGNUM] = {
+ TWL6040_REG_HPPLLCTL,
+ TWL6040_REG_LPPLLCTL,
+ TWL6040_REG_LPPLLDIV,
+ TWL6040_REG_MICLCTL,
+ TWL6040_REG_MICRCTL,
+ TWL6040_REG_MICGAIN,
+ TWL6040_REG_LINEGAIN,
+ TWL6040_REG_HSLCTL,
+ TWL6040_REG_HSRCTL,
+ TWL6040_REG_HSGAIN,
+ TWL6040_REG_EARCTL,
+ TWL6040_REG_HFLCTL,
+ TWL6040_REG_HFLGAIN,
+ TWL6040_REG_HFRCTL,
+ TWL6040_REG_HFRGAIN,
+ TWL6040_REG_VIBCTLL,
+ TWL6040_REG_VIBDATL,
+ TWL6040_REG_VIBCTLR,
+ TWL6040_REG_VIBDATR,
+ TWL6040_REG_ALB,
+ TWL6040_REG_DLB,
+};
+
+/*
+ * read twl6040 register cache
+ */
+static inline unsigned int twl6040_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+
+ if (reg >= TWL6040_CACHEREGNUM)
+ return -EIO;
+
+ return cache[reg];
+}
+
+/*
+ * write twl6040 register cache
+ */
+static inline void twl6040_write_reg_cache(struct snd_soc_codec *codec,
+ u8 reg, u8 value)
+{
+ u8 *cache = codec->reg_cache;
+
+ if (reg >= TWL6040_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * read from twl6040 hardware register
+ */
+static int twl6040_read_reg_volatile(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 value;
+
+ if (reg >= TWL6040_CACHEREGNUM)
+ return -EIO;
+
+ twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &value, reg);
+ twl6040_write_reg_cache(codec, reg, value);
+
+ return value;
+}
+
+/*
+ * write to the twl6040 register space
+ */
+static int twl6040_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ if (reg >= TWL6040_CACHEREGNUM)
+ return -EIO;
+
+ twl6040_write_reg_cache(codec, reg, value);
+ return twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, value, reg);
+}
+
+static void twl6040_init_vio_regs(struct snd_soc_codec *codec)
+{
+ u8 *cache = codec->reg_cache;
+ int reg, i;
+
+ /* allow registers to be accessed by i2c */
+ twl6040_write(codec, TWL6040_REG_ACCCTL, cache[TWL6040_REG_ACCCTL]);
+
+ for (i = 0; i < TWL6040_VIOREGNUM; i++) {
+ reg = twl6040_vio_reg[i];
+ /* skip read-only registers (ASICID, ASICREV, STATUS) */
+ switch (reg) {
+ case TWL6040_REG_ASICID:
+ case TWL6040_REG_ASICREV:
+ case TWL6040_REG_STATUS:
+ continue;
+ default:
+ break;
+ }
+ twl6040_write(codec, reg, cache[reg]);
+ }
+}
+
+static void twl6040_init_vdd_regs(struct snd_soc_codec *codec)
+{
+ u8 *cache = codec->reg_cache;
+ int reg, i;
+
+ for (i = 0; i < TWL6040_VDDREGNUM; i++) {
+ reg = twl6040_vdd_reg[i];
+ twl6040_write(codec, reg, cache[reg]);
+ }
+}
+
+/*
+ * Ramp HS PGA volume to minimise pops at stream startup and shutdown.
+ */
+static inline int twl6040_hs_ramp_step(struct snd_soc_codec *codec,
+ unsigned int left_step, unsigned int right_step)
+{
+
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct twl6040_output *headset = &priv->headset;
+ int left_complete = 0, right_complete = 0;
+ u8 reg, val;
+
+ /* left channel */
+ left_step = (left_step > 0xF) ? 0xF : left_step;
+ reg = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN);
+ val = (~reg & TWL6040_HSL_VOL_MASK);
+
+ if (headset->ramp == TWL6040_RAMP_UP) {
+ /* ramp step up */
+ if (val < headset->left_vol) {
+ val += left_step;
+ reg &= ~TWL6040_HSL_VOL_MASK;
+ twl6040_write(codec, TWL6040_REG_HSGAIN,
+ (reg | (~val & TWL6040_HSL_VOL_MASK)));
+ } else {
+ left_complete = 1;
+ }
+ } else if (headset->ramp == TWL6040_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0x0) {
+ val -= left_step;
+ reg &= ~TWL6040_HSL_VOL_MASK;
+ twl6040_write(codec, TWL6040_REG_HSGAIN, reg |
+ (~val & TWL6040_HSL_VOL_MASK));
+ } else {
+ left_complete = 1;
+ }
+ }
+
+ /* right channel */
+ right_step = (right_step > 0xF) ? 0xF : right_step;
+ reg = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN);
+ val = (~reg & TWL6040_HSR_VOL_MASK) >> TWL6040_HSR_VOL_SHIFT;
+
+ if (headset->ramp == TWL6040_RAMP_UP) {
+ /* ramp step up */
+ if (val < headset->right_vol) {
+ val += right_step;
+ reg &= ~TWL6040_HSR_VOL_MASK;
+ twl6040_write(codec, TWL6040_REG_HSGAIN,
+ (reg | (~val << TWL6040_HSR_VOL_SHIFT)));
+ } else {
+ right_complete = 1;
+ }
+ } else if (headset->ramp == TWL6040_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0x0) {
+ val -= right_step;
+ reg &= ~TWL6040_HSR_VOL_MASK;
+ twl6040_write(codec, TWL6040_REG_HSGAIN,
+ reg | (~val << TWL6040_HSR_VOL_SHIFT));
+ } else {
+ right_complete = 1;
+ }
+ }
+
+ return left_complete & right_complete;
+}
+
+/*
+ * Ramp HF PGA volume to minimise pops at stream startup and shutdown.
+ */
+static inline int twl6040_hf_ramp_step(struct snd_soc_codec *codec,
+ unsigned int left_step, unsigned int right_step)
+{
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct twl6040_output *handsfree = &priv->handsfree;
+ int left_complete = 0, right_complete = 0;
+ u16 reg, val;
+
+ /* left channel */
+ left_step = (left_step > 0x1D) ? 0x1D : left_step;
+ reg = twl6040_read_reg_cache(codec, TWL6040_REG_HFLGAIN);
+ reg = 0x1D - reg;
+ val = (reg & TWL6040_HF_VOL_MASK);
+ if (handsfree->ramp == TWL6040_RAMP_UP) {
+ /* ramp step up */
+ if (val < handsfree->left_vol) {
+ val += left_step;
+ reg &= ~TWL6040_HF_VOL_MASK;
+ twl6040_write(codec, TWL6040_REG_HFLGAIN,
+ reg | (0x1D - val));
+ } else {
+ left_complete = 1;
+ }
+ } else if (handsfree->ramp == TWL6040_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0) {
+ val -= left_step;
+ reg &= ~TWL6040_HF_VOL_MASK;
+ twl6040_write(codec, TWL6040_REG_HFLGAIN,
+ reg | (0x1D - val));
+ } else {
+ left_complete = 1;
+ }
+ }
+
+ /* right channel */
+ right_step = (right_step > 0x1D) ? 0x1D : right_step;
+ reg = twl6040_read_reg_cache(codec, TWL6040_REG_HFRGAIN);
+ reg = 0x1D - reg;
+ val = (reg & TWL6040_HF_VOL_MASK);
+ if (handsfree->ramp == TWL6040_RAMP_UP) {
+ /* ramp step up */
+ if (val < handsfree->right_vol) {
+ val += right_step;
+ reg &= ~TWL6040_HF_VOL_MASK;
+ twl6040_write(codec, TWL6040_REG_HFRGAIN,
+ reg | (0x1D - val));
+ } else {
+ right_complete = 1;
+ }
+ } else if (handsfree->ramp == TWL6040_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0) {
+ val -= right_step;
+ reg &= ~TWL6040_HF_VOL_MASK;
+ twl6040_write(codec, TWL6040_REG_HFRGAIN,
+ reg | (0x1D - val));
+ }
+ }
+
+ return left_complete & right_complete;
+}
+
+/*
+ * This work ramps both output PGAs at stream start/stop time to
+ * minimise pop associated with DAPM power switching.
+ */
+static void twl6040_pga_hs_work(struct work_struct *work)
+{
+ struct twl6040_data *priv =
+ container_of(work, struct twl6040_data, hs_delayed_work.work);
+ struct snd_soc_codec *codec = priv->codec;
+ struct twl6040_output *headset = &priv->headset;
+ unsigned int delay = headset->step_delay;
+ int i, headset_complete;
+
+ /* do we need to ramp at all ? */
+ if (headset->ramp == TWL6040_RAMP_NONE)
+ return;
+
+ /* HS PGA volumes have 4 bits of resolution to ramp */
+ for (i = 0; i <= 16; i++) {
+ headset_complete = 1;
+ if (headset->ramp != TWL6040_RAMP_NONE)
+ headset_complete = twl6040_hs_ramp_step(codec,
+ headset->left_step,
+ headset->right_step);
+
+ /* ramp finished ? */
+ if (headset_complete)
+ break;
+
+ /*
+ * TODO: tune: delay is longer over 0dB
+ * as increases are larger.
+ */
+ if (i >= 8)
+ schedule_timeout_interruptible(msecs_to_jiffies(delay +
+ (delay >> 1)));
+ else
+ schedule_timeout_interruptible(msecs_to_jiffies(delay));
+ }
+
+ if (headset->ramp == TWL6040_RAMP_DOWN) {
+ headset->active = 0;
+ complete(&headset->ramp_done);
+ } else {
+ headset->active = 1;
+ }
+ headset->ramp = TWL6040_RAMP_NONE;
+}
+
+static void twl6040_pga_hf_work(struct work_struct *work)
+{
+ struct twl6040_data *priv =
+ container_of(work, struct twl6040_data, hf_delayed_work.work);
+ struct snd_soc_codec *codec = priv->codec;
+ struct twl6040_output *handsfree = &priv->handsfree;
+ unsigned int delay = handsfree->step_delay;
+ int i, handsfree_complete;
+
+ /* do we need to ramp at all ? */
+ if (handsfree->ramp == TWL6040_RAMP_NONE)
+ return;
+
+ /* HF PGA volumes have 5 bits of resolution to ramp */
+ for (i = 0; i <= 32; i++) {
+ handsfree_complete = 1;
+ if (handsfree->ramp != TWL6040_RAMP_NONE)
+ handsfree_complete = twl6040_hf_ramp_step(codec,
+ handsfree->left_step,
+ handsfree->right_step);
+
+ /* ramp finished ? */
+ if (handsfree_complete)
+ break;
+
+ /*
+ * TODO: tune: delay is longer over 0dB
+ * as increases are larger.
+ */
+ if (i >= 16)
+ schedule_timeout_interruptible(msecs_to_jiffies(delay +
+ (delay >> 1)));
+ else
+ schedule_timeout_interruptible(msecs_to_jiffies(delay));
+ }
+
+
+ if (handsfree->ramp == TWL6040_RAMP_DOWN) {
+ handsfree->active = 0;
+ complete(&handsfree->ramp_done);
+ } else
+ handsfree->active = 1;
+ handsfree->ramp = TWL6040_RAMP_NONE;
+}
+
+static int pga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct twl6040_output *out;
+ struct delayed_work *work;
+ struct workqueue_struct *queue;
+
+ switch (w->shift) {
+ case 2:
+ case 3:
+ out = &priv->headset;
+ work = &priv->hs_delayed_work;
+ queue = priv->hs_workqueue;
+ out->step_delay = 5; /* 5 ms between volume ramp steps */
+ break;
+ case 4:
+ out = &priv->handsfree;
+ work = &priv->hf_delayed_work;
+ queue = priv->hf_workqueue;
+ out->step_delay = 5; /* 5 ms between volume ramp steps */
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ priv->non_lp++;
+ else
+ priv->non_lp--;
+ break;
+ default:
+ return -1;
+ }
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ if (out->active)
+ break;
+
+ /* don't use volume ramp for power-up */
+ out->left_step = out->left_vol;
+ out->right_step = out->right_vol;
+
+ if (!delayed_work_pending(work)) {
+ out->ramp = TWL6040_RAMP_UP;
+ queue_delayed_work(queue, work,
+ msecs_to_jiffies(1));
+ }
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ if (!out->active)
+ break;
+
+ if (!delayed_work_pending(work)) {
+ /* use volume ramp for power-down */
+ out->left_step = 1;
+ out->right_step = 1;
+ out->ramp = TWL6040_RAMP_DOWN;
+ INIT_COMPLETION(out->ramp_done);
+
+ queue_delayed_work(queue, work,
+ msecs_to_jiffies(1));
+
+ wait_for_completion_timeout(&out->ramp_done,
+ msecs_to_jiffies(2000));
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* twl6040 codec manual power-up sequence */
+static void twl6040_power_up(struct snd_soc_codec *codec)
+{
+ u8 ncpctl, ldoctl, lppllctl, accctl;
+
+ ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL);
+ ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL);
+ lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
+ accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL);
+
+ /* enable reference system */
+ ldoctl |= TWL6040_REFENA;
+ twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+ msleep(10);
+ /* enable internal oscillator */
+ ldoctl |= TWL6040_OSCENA;
+ twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+ udelay(10);
+ /* enable high-side ldo */
+ ldoctl |= TWL6040_HSLDOENA;
+ twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+ udelay(244);
+ /* enable negative charge pump */
+ ncpctl |= TWL6040_NCPENA | TWL6040_NCPOPEN;
+ twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl);
+ udelay(488);
+ /* enable low-side ldo */
+ ldoctl |= TWL6040_LSLDOENA;
+ twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+ udelay(244);
+ /* enable low-power pll */
+ lppllctl |= TWL6040_LPLLENA;
+ twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+ /* reset state machine */
+ accctl |= TWL6040_RESETSPLIT;
+ twl6040_write(codec, TWL6040_REG_ACCCTL, accctl);
+ mdelay(5);
+ accctl &= ~TWL6040_RESETSPLIT;
+ twl6040_write(codec, TWL6040_REG_ACCCTL, accctl);
+ /* disable internal oscillator */
+ ldoctl &= ~TWL6040_OSCENA;
+ twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+}
+
+/* twl6040 codec manual power-down sequence */
+static void twl6040_power_down(struct snd_soc_codec *codec)
+{
+ u8 ncpctl, ldoctl, lppllctl, accctl;
+
+ ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL);
+ ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL);
+ lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
+ accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL);
+
+ /* enable internal oscillator */
+ ldoctl |= TWL6040_OSCENA;
+ twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+ udelay(10);
+ /* disable low-power pll */
+ lppllctl &= ~TWL6040_LPLLENA;
+ twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+ /* disable low-side ldo */
+ ldoctl &= ~TWL6040_LSLDOENA;
+ twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+ udelay(244);
+ /* disable negative charge pump */
+ ncpctl &= ~(TWL6040_NCPENA | TWL6040_NCPOPEN);
+ twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl);
+ udelay(488);
+ /* disable high-side ldo */
+ ldoctl &= ~TWL6040_HSLDOENA;
+ twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+ udelay(244);
+ /* disable internal oscillator */
+ ldoctl &= ~TWL6040_OSCENA;
+ twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+ /* disable reference system */
+ ldoctl &= ~TWL6040_REFENA;
+ twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+ msleep(10);
+}
+
+/* set headset dac and driver power mode */
+static int headset_power_mode(struct snd_soc_codec *codec, int high_perf)
+{
+ int hslctl, hsrctl;
+ int mask = TWL6040_HSDRVMODEL | TWL6040_HSDACMODEL;
+
+ hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL);
+ hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL);
+
+ if (high_perf) {
+ hslctl &= ~mask;
+ hsrctl &= ~mask;
+ } else {
+ hslctl |= mask;
+ hsrctl |= mask;
+ }
+
+ twl6040_write(codec, TWL6040_REG_HSLCTL, hslctl);
+ twl6040_write(codec, TWL6040_REG_HSRCTL, hsrctl);
+
+ return 0;
+}
+
+static int twl6040_hs_dac_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ msleep(1);
+ return 0;
+}
+
+static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ priv->non_lp++;
+ else
+ priv->non_lp--;
+
+ msleep(1);
+
+ return 0;
+}
+
+static void twl6040_hs_jack_report(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack, int report)
+{
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ int status;
+
+ mutex_lock(&priv->mutex);
+
+ /* Sync status */
+ status = twl6040_read_reg_volatile(codec, TWL6040_REG_STATUS);
+ if (status & TWL6040_PLUGCOMP)
+ snd_soc_jack_report(jack, report, report);
+ else
+ snd_soc_jack_report(jack, 0, report);
+
+ mutex_unlock(&priv->mutex);
+}
+
+void twl6040_hs_jack_detect(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack, int report)
+{
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct twl6040_jack_data *hs_jack = &priv->hs_jack;
+
+ hs_jack->jack = jack;
+ hs_jack->report = report;
+
+ twl6040_hs_jack_report(codec, hs_jack->jack, hs_jack->report);
+}
+EXPORT_SYMBOL_GPL(twl6040_hs_jack_detect);
+
+static void twl6040_accessory_work(struct work_struct *work)
+{
+ struct twl6040_data *priv = container_of(work,
+ struct twl6040_data, delayed_work.work);
+ struct snd_soc_codec *codec = priv->codec;
+ struct twl6040_jack_data *hs_jack = &priv->hs_jack;
+
+ twl6040_hs_jack_report(codec, hs_jack->jack, hs_jack->report);
+}
+
+/* audio interrupt handler */
+static irqreturn_t twl6040_naudint_handler(int irq, void *data)
+{
+ struct snd_soc_codec *codec = data;
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ u8 intid;
+
+ twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID);
+
+ if (intid & TWL6040_THINT)
+ dev_alert(codec->dev, "die temp over-limit detection\n");
+
+ if ((intid & TWL6040_PLUGINT) || (intid & TWL6040_UNPLUGINT))
+ queue_delayed_work(priv->workqueue, &priv->delayed_work,
+ msecs_to_jiffies(200));
+
+ if (intid & TWL6040_HOOKINT)
+ dev_info(codec->dev, "hook detection\n");
+
+ if (intid & TWL6040_HFINT)
+ dev_alert(codec->dev, "hf drivers over current detection\n");
+
+ if (intid & TWL6040_VIBINT)
+ dev_alert(codec->dev, "vib drivers over current detection\n");
+
+ if (intid & TWL6040_READYINT)
+ complete(&priv->ready);
+
+ return IRQ_HANDLED;
+}
+
+static int twl6040_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec);
+ struct twl6040_output *out = NULL;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int ret;
+ unsigned int reg = mc->reg;
+
+ /* For HS and HF we shadow the values and only actually write
+ * them out when active in order to ensure the amplifier comes on
+ * as quietly as possible. */
+ switch (reg) {
+ case TWL6040_REG_HSGAIN:
+ out = &twl6040_priv->headset;
+ break;
+ default:
+ break;
+ }
+
+ if (out) {
+ out->left_vol = ucontrol->value.integer.value[0];
+ out->right_vol = ucontrol->value.integer.value[1];
+ if (!out->active)
+ return 1;
+ }
+
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ return 1;
+}
+
+static int twl6040_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec);
+ struct twl6040_output *out = &twl6040_priv->headset;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+
+ switch (reg) {
+ case TWL6040_REG_HSGAIN:
+ out = &twl6040_priv->headset;
+ ucontrol->value.integer.value[0] = out->left_vol;
+ ucontrol->value.integer.value[1] = out->right_vol;
+ return 0;
+
+ default:
+ break;
+ }
+
+ return snd_soc_get_volsw(kcontrol, ucontrol);
+}
+
+static int twl6040_put_volsw_2r_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec);
+ struct twl6040_output *out = NULL;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int ret;
+ unsigned int reg = mc->reg;
+
+ /* For HS and HF we shadow the values and only actually write
+ * them out when active in order to ensure the amplifier comes on
+ * as quietly as possible. */
+ switch (reg) {
+ case TWL6040_REG_HFLGAIN:
+ case TWL6040_REG_HFRGAIN:
+ out = &twl6040_priv->handsfree;
+ break;
+ default:
+ break;
+ }
+
+ if (out) {
+ out->left_vol = ucontrol->value.integer.value[0];
+ out->right_vol = ucontrol->value.integer.value[1];
+ if (!out->active)
+ return 1;
+ }
+
+ ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ return 1;
+}
+
+static int twl6040_get_volsw_2r(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec);
+ struct twl6040_output *out = &twl6040_priv->handsfree;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+
+ /* If these are cached registers use the cache */
+ switch (reg) {
+ case TWL6040_REG_HFLGAIN:
+ case TWL6040_REG_HFRGAIN:
+ out = &twl6040_priv->handsfree;
+ ucontrol->value.integer.value[0] = out->left_vol;
+ ucontrol->value.integer.value[1] = out->right_vol;
+ return 0;
+
+ default:
+ break;
+ }
+
+ return snd_soc_get_volsw_2r(kcontrol, ucontrol);
+}
+
+/* double control with volume update */
+#define SOC_TWL6040_DOUBLE_TLV(xname, xreg, shift_left, shift_right, xmax,\
+ xinvert, tlv_array)\
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, .get = twl6040_get_volsw, \
+ .put = twl6040_put_volsw, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = xreg, .shift = shift_left, .rshift = shift_right,\
+ .max = xmax, .platform_max = xmax, .invert = xinvert} }
+
+/* double control with volume update */
+#define SOC_TWL6040_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax,\
+ xinvert, tlv_array)\
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_2r, \
+ .get = twl6040_get_volsw_2r, .put = twl6040_put_volsw_2r_vu, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = reg_left, .rreg = reg_right, .shift = xshift, \
+ .rshift = xshift, .max = xmax, .invert = xinvert}, }
+
+/*
+ * MICATT volume control:
+ * from -6 to 0 dB in 6 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0);
+
+/*
+ * MICGAIN volume control:
+ * from -6 to 30 dB in 6 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(mic_amp_tlv, -600, 600, 0);
+
+/*
+ * AFMGAIN volume control:
+ * from -18 to 24 dB in 6 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(afm_amp_tlv, -1800, 600, 0);
+
+/*
+ * HSGAIN volume control:
+ * from -30 to 0 dB in 2 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(hs_tlv, -3000, 200, 0);
+
+/*
+ * HFGAIN volume control:
+ * from -52 to 6 dB in 2 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(hf_tlv, -5200, 200, 0);
+
+/*
+ * EPGAIN volume control:
+ * from -24 to 6 dB in 2 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(ep_tlv, -2400, 200, 0);
+
+/* Left analog microphone selection */
+static const char *twl6040_amicl_texts[] =
+ {"Headset Mic", "Main Mic", "Aux/FM Left", "Off"};
+
+/* Right analog microphone selection */
+static const char *twl6040_amicr_texts[] =
+ {"Headset Mic", "Sub Mic", "Aux/FM Right", "Off"};
+
+static const struct soc_enum twl6040_enum[] = {
+ SOC_ENUM_SINGLE(TWL6040_REG_MICLCTL, 3, 4, twl6040_amicl_texts),
+ SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3, 4, twl6040_amicr_texts),
+};
+
+static const char *twl6040_hs_texts[] = {
+ "Off", "HS DAC", "Line-In amp"
+};
+
+static const struct soc_enum twl6040_hs_enum[] = {
+ SOC_ENUM_SINGLE(TWL6040_REG_HSLCTL, 5, ARRAY_SIZE(twl6040_hs_texts),
+ twl6040_hs_texts),
+ SOC_ENUM_SINGLE(TWL6040_REG_HSRCTL, 5, ARRAY_SIZE(twl6040_hs_texts),
+ twl6040_hs_texts),
+};
+
+static const char *twl6040_hf_texts[] = {
+ "Off", "HF DAC", "Line-In amp"
+};
+
+static const struct soc_enum twl6040_hf_enum[] = {
+ SOC_ENUM_SINGLE(TWL6040_REG_HFLCTL, 2, ARRAY_SIZE(twl6040_hf_texts),
+ twl6040_hf_texts),
+ SOC_ENUM_SINGLE(TWL6040_REG_HFRCTL, 2, ARRAY_SIZE(twl6040_hf_texts),
+ twl6040_hf_texts),
+};
+
+static const struct snd_kcontrol_new amicl_control =
+ SOC_DAPM_ENUM("Route", twl6040_enum[0]);
+
+static const struct snd_kcontrol_new amicr_control =
+ SOC_DAPM_ENUM("Route", twl6040_enum[1]);
+
+/* Headset DAC playback switches */
+static const struct snd_kcontrol_new hsl_mux_controls =
+ SOC_DAPM_ENUM("Route", twl6040_hs_enum[0]);
+
+static const struct snd_kcontrol_new hsr_mux_controls =
+ SOC_DAPM_ENUM("Route", twl6040_hs_enum[1]);
+
+/* Handsfree DAC playback switches */
+static const struct snd_kcontrol_new hfl_mux_controls =
+ SOC_DAPM_ENUM("Route", twl6040_hf_enum[0]);
+
+static const struct snd_kcontrol_new hfr_mux_controls =
+ SOC_DAPM_ENUM("Route", twl6040_hf_enum[1]);
+
+static const struct snd_kcontrol_new ep_driver_switch_controls =
+ SOC_DAPM_SINGLE("Switch", TWL6040_REG_EARCTL, 0, 1, 0);
+
+static const struct snd_kcontrol_new twl6040_snd_controls[] = {
+ /* Capture gains */
+ SOC_DOUBLE_TLV("Capture Preamplifier Volume",
+ TWL6040_REG_MICGAIN, 6, 7, 1, 1, mic_preamp_tlv),
+ SOC_DOUBLE_TLV("Capture Volume",
+ TWL6040_REG_MICGAIN, 0, 3, 4, 0, mic_amp_tlv),
+
+ /* AFM gains */
+ SOC_DOUBLE_TLV("Aux FM Volume",
+ TWL6040_REG_LINEGAIN, 0, 3, 7, 0, afm_amp_tlv),
+
+ /* Playback gains */
+ SOC_TWL6040_DOUBLE_TLV("Headset Playback Volume",
+ TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv),
+ SOC_TWL6040_DOUBLE_R_TLV("Handsfree Playback Volume",
+ TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv),
+ SOC_SINGLE_TLV("Earphone Playback Volume",
+ TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv),
+};
+
+static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
+ /* Inputs */
+ SND_SOC_DAPM_INPUT("MAINMIC"),
+ SND_SOC_DAPM_INPUT("HSMIC"),
+ SND_SOC_DAPM_INPUT("SUBMIC"),
+ SND_SOC_DAPM_INPUT("AFML"),
+ SND_SOC_DAPM_INPUT("AFMR"),
+
+ /* Outputs */
+ SND_SOC_DAPM_OUTPUT("HSOL"),
+ SND_SOC_DAPM_OUTPUT("HSOR"),
+ SND_SOC_DAPM_OUTPUT("HFL"),
+ SND_SOC_DAPM_OUTPUT("HFR"),
+ SND_SOC_DAPM_OUTPUT("EP"),
+
+ /* Analog input muxes for the capture amplifiers */
+ SND_SOC_DAPM_MUX("Analog Left Capture Route",
+ SND_SOC_NOPM, 0, 0, &amicl_control),
+ SND_SOC_DAPM_MUX("Analog Right Capture Route",
+ SND_SOC_NOPM, 0, 0, &amicr_control),
+
+ /* Analog capture PGAs */
+ SND_SOC_DAPM_PGA("MicAmpL",
+ TWL6040_REG_MICLCTL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("MicAmpR",
+ TWL6040_REG_MICRCTL, 0, 0, NULL, 0),
+
+ /* Auxiliary FM PGAs */
+ SND_SOC_DAPM_PGA("AFMAmpL",
+ TWL6040_REG_MICLCTL, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("AFMAmpR",
+ TWL6040_REG_MICRCTL, 1, 0, NULL, 0),
+
+ /* ADCs */
+ SND_SOC_DAPM_ADC("ADC Left", "Left Front Capture",
+ TWL6040_REG_MICLCTL, 2, 0),
+ SND_SOC_DAPM_ADC("ADC Right", "Right Front Capture",
+ TWL6040_REG_MICRCTL, 2, 0),
+
+ /* Microphone bias */
+ SND_SOC_DAPM_MICBIAS("Headset Mic Bias",
+ TWL6040_REG_AMICBCTL, 0, 0),
+ SND_SOC_DAPM_MICBIAS("Main Mic Bias",
+ TWL6040_REG_AMICBCTL, 4, 0),
+ SND_SOC_DAPM_MICBIAS("Digital Mic1 Bias",
+ TWL6040_REG_DMICBCTL, 0, 0),
+ SND_SOC_DAPM_MICBIAS("Digital Mic2 Bias",
+ TWL6040_REG_DMICBCTL, 4, 0),
+
+ /* DACs */
+ SND_SOC_DAPM_DAC_E("HSDAC Left", "Headset Playback",
+ TWL6040_REG_HSLCTL, 0, 0,
+ twl6040_hs_dac_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_DAC_E("HSDAC Right", "Headset Playback",
+ TWL6040_REG_HSRCTL, 0, 0,
+ twl6040_hs_dac_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_DAC_E("HFDAC Left", "Handsfree Playback",
+ TWL6040_REG_HFLCTL, 0, 0,
+ twl6040_power_mode_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_DAC_E("HFDAC Right", "Handsfree Playback",
+ TWL6040_REG_HFRCTL, 0, 0,
+ twl6040_power_mode_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_MUX("HF Left Playback",
+ SND_SOC_NOPM, 0, 0, &hfl_mux_controls),
+ SND_SOC_DAPM_MUX("HF Right Playback",
+ SND_SOC_NOPM, 0, 0, &hfr_mux_controls),
+ /* Analog playback Muxes */
+ SND_SOC_DAPM_MUX("HS Left Playback",
+ SND_SOC_NOPM, 0, 0, &hsl_mux_controls),
+ SND_SOC_DAPM_MUX("HS Right Playback",
+ SND_SOC_NOPM, 0, 0, &hsr_mux_controls),
+
+ /* Analog playback drivers */
+ SND_SOC_DAPM_OUT_DRV_E("Handsfree Left Driver",
+ TWL6040_REG_HFLCTL, 4, 0, NULL, 0,
+ pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_OUT_DRV_E("Handsfree Right Driver",
+ TWL6040_REG_HFRCTL, 4, 0, NULL, 0,
+ pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_OUT_DRV_E("Headset Left Driver",
+ TWL6040_REG_HSLCTL, 2, 0, NULL, 0,
+ pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_OUT_DRV_E("Headset Right Driver",
+ TWL6040_REG_HSRCTL, 2, 0, NULL, 0,
+ pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_SWITCH_E("Earphone Driver",
+ SND_SOC_NOPM, 0, 0, &ep_driver_switch_controls,
+ twl6040_power_mode_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* Analog playback PGAs */
+ SND_SOC_DAPM_PGA("HFDAC Left PGA",
+ TWL6040_REG_HFLCTL, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HFDAC Right PGA",
+ TWL6040_REG_HFRCTL, 1, 0, NULL, 0),
+
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* Capture path */
+ {"Analog Left Capture Route", "Headset Mic", "HSMIC"},
+ {"Analog Left Capture Route", "Main Mic", "MAINMIC"},
+ {"Analog Left Capture Route", "Aux/FM Left", "AFML"},
+
+ {"Analog Right Capture Route", "Headset Mic", "HSMIC"},
+ {"Analog Right Capture Route", "Sub Mic", "SUBMIC"},
+ {"Analog Right Capture Route", "Aux/FM Right", "AFMR"},
+
+ {"MicAmpL", NULL, "Analog Left Capture Route"},
+ {"MicAmpR", NULL, "Analog Right Capture Route"},
+
+ {"ADC Left", NULL, "MicAmpL"},
+ {"ADC Right", NULL, "MicAmpR"},
+
+ /* AFM path */
+ {"AFMAmpL", "NULL", "AFML"},
+ {"AFMAmpR", "NULL", "AFMR"},
+
+ {"HS Left Playback", "HS DAC", "HSDAC Left"},
+ {"HS Left Playback", "Line-In amp", "AFMAmpL"},
+
+ {"HS Right Playback", "HS DAC", "HSDAC Right"},
+ {"HS Right Playback", "Line-In amp", "AFMAmpR"},
+
+ {"Headset Left Driver", "NULL", "HS Left Playback"},
+ {"Headset Right Driver", "NULL", "HS Right Playback"},
+
+ {"HSOL", NULL, "Headset Left Driver"},
+ {"HSOR", NULL, "Headset Right Driver"},
+
+ /* Earphone playback path */
+ {"Earphone Driver", "Switch", "HSDAC Left"},
+ {"EP", NULL, "Earphone Driver"},
+
+ {"HF Left Playback", "HF DAC", "HFDAC Left"},
+ {"HF Left Playback", "Line-In amp", "AFMAmpL"},
+
+ {"HF Right Playback", "HF DAC", "HFDAC Right"},
+ {"HF Right Playback", "Line-In amp", "AFMAmpR"},
+
+ {"HFDAC Left PGA", NULL, "HF Left Playback"},
+ {"HFDAC Right PGA", NULL, "HF Right Playback"},
+
+ {"Handsfree Left Driver", "Switch", "HFDAC Left PGA"},
+ {"Handsfree Right Driver", "Switch", "HFDAC Right PGA"},
+
+ {"HFL", NULL, "Handsfree Left Driver"},
+ {"HFR", NULL, "Handsfree Right Driver"},
+};
+
+static int twl6040_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, twl6040_dapm_widgets,
+ ARRAY_SIZE(twl6040_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+ snd_soc_dapm_new_widgets(dapm);
+
+ return 0;
+}
+
+static int twl6040_power_up_completion(struct snd_soc_codec *codec,
+ int naudint)
+{
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ int time_left;
+ u8 intid;
+
+ time_left = wait_for_completion_timeout(&priv->ready,
+ msecs_to_jiffies(144));
+
+ if (!time_left) {
+ twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &intid,
+ TWL6040_REG_INTID);
+ if (!(intid & TWL6040_READYINT)) {
+ dev_err(codec->dev, "timeout waiting for READYINT\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ priv->codec_powered = 1;
+
+ return 0;
+}
+
+static int twl6040_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ int audpwron = priv->audpwron;
+ int naudint = priv->naudint;
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (priv->codec_powered)
+ break;
+
+ if (gpio_is_valid(audpwron)) {
+ /* use AUDPWRON line */
+ gpio_set_value(audpwron, 1);
+
+ /* wait for power-up completion */
+ ret = twl6040_power_up_completion(codec, naudint);
+ if (ret)
+ return ret;
+
+ /* sync registers updated during power-up sequence */
+ twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL);
+ twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL);
+ twl6040_read_reg_volatile(codec, TWL6040_REG_LPPLLCTL);
+ } else {
+ /* use manual power-up sequence */
+ twl6040_power_up(codec);
+ priv->codec_powered = 1;
+ }
+
+ /* initialize vdd/vss registers with reg_cache */
+ twl6040_init_vdd_regs(codec);
+
+ /* Set external boost GPO */
+ twl6040_write(codec, TWL6040_REG_GPOCTL, 0x02);
+
+ /* Set initial minimal gain values */
+ twl6040_write(codec, TWL6040_REG_HSGAIN, 0xFF);
+ twl6040_write(codec, TWL6040_REG_EARCTL, 0x1E);
+ twl6040_write(codec, TWL6040_REG_HFLGAIN, 0x1D);
+ twl6040_write(codec, TWL6040_REG_HFRGAIN, 0x1D);
+ break;
+ case SND_SOC_BIAS_OFF:
+ if (!priv->codec_powered)
+ break;
+
+ if (gpio_is_valid(audpwron)) {
+ /* use AUDPWRON line */
+ gpio_set_value(audpwron, 0);
+
+ /* power-down sequence latency */
+ udelay(500);
+
+ /* sync registers updated during power-down sequence */
+ twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL);
+ twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL);
+ twl6040_write_reg_cache(codec, TWL6040_REG_LPPLLCTL,
+ 0x00);
+ } else {
+ /* use manual power-down sequence */
+ twl6040_power_down(codec);
+ }
+
+ priv->codec_powered = 0;
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+/* set of rates for each pll: low-power and high-performance */
+
+static unsigned int lp_rates[] = {
+ 88200,
+ 96000,
+};
+
+static struct snd_pcm_hw_constraint_list lp_constraints = {
+ .count = ARRAY_SIZE(lp_rates),
+ .list = lp_rates,
+};
+
+static unsigned int hp_rates[] = {
+ 96000,
+};
+
+static struct snd_pcm_hw_constraint_list hp_constraints = {
+ .count = ARRAY_SIZE(hp_rates),
+ .list = hp_rates,
+};
+
+static int twl6040_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ priv->sysclk_constraints);
+
+ return 0;
+}
+
+static int twl6040_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ u8 lppllctl;
+ int rate;
+
+ /* nothing to do for high-perf pll, it supports only 48 kHz */
+ if (priv->pll == TWL6040_HPPLL_ID)
+ return 0;
+
+ lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
+
+ rate = params_rate(params);
+ switch (rate) {
+ case 11250:
+ case 22500:
+ case 44100:
+ case 88200:
+ lppllctl |= TWL6040_LPLLFIN;
+ priv->sysclk = 17640000;
+ break;
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 96000:
+ lppllctl &= ~TWL6040_LPLLFIN;
+ priv->sysclk = 19200000;
+ break;
+ default:
+ dev_err(codec->dev, "unsupported rate %d\n", rate);
+ return -EINVAL;
+ }
+
+ twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+
+ return 0;
+}
+
+static int twl6040_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+ if (!priv->sysclk) {
+ dev_err(codec->dev,
+ "no mclk configured, call set_sysclk() on init\n");
+ return -EINVAL;
+ }
+
+ /*
+ * capture is not supported at 17.64 MHz,
+ * it's reserved for headset low-power playback scenario
+ */
+ if ((priv->sysclk == 17640000) &&
+ substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ dev_err(codec->dev,
+ "capture mode is not supported at %dHz\n",
+ priv->sysclk);
+ return -EINVAL;
+ }
+
+ if ((priv->sysclk == 17640000) && priv->non_lp) {
+ dev_err(codec->dev,
+ "some enabled paths aren't supported at %dHz\n",
+ priv->sysclk);
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ u8 hppllctl, lppllctl;
+
+ hppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_HPPLLCTL);
+ lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
+
+ switch (clk_id) {
+ case TWL6040_SYSCLK_SEL_LPPLL:
+ switch (freq) {
+ case 32768:
+ /* headset dac and driver must be in low-power mode */
+ headset_power_mode(codec, 0);
+
+ /* clk32k input requires low-power pll */
+ lppllctl |= TWL6040_LPLLENA;
+ twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+ mdelay(5);
+ lppllctl &= ~TWL6040_HPLLSEL;
+ twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+ hppllctl &= ~TWL6040_HPLLENA;
+ twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl);
+ break;
+ default:
+ dev_err(codec->dev, "unknown mclk freq %d\n", freq);
+ return -EINVAL;
+ }
+
+ /* lppll divider */
+ switch (priv->sysclk) {
+ case 17640000:
+ lppllctl |= TWL6040_LPLLFIN;
+ break;
+ case 19200000:
+ lppllctl &= ~TWL6040_LPLLFIN;
+ break;
+ default:
+ /* sysclk not yet configured */
+ lppllctl &= ~TWL6040_LPLLFIN;
+ priv->sysclk = 19200000;
+ break;
+ }
+
+ twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+
+ priv->pll = TWL6040_LPPLL_ID;
+ priv->sysclk_constraints = &lp_constraints;
+ break;
+ case TWL6040_SYSCLK_SEL_HPPLL:
+ hppllctl &= ~TWL6040_MCLK_MSK;
+
+ switch (freq) {
+ case 12000000:
+ /* mclk input, pll enabled */
+ hppllctl |= TWL6040_MCLK_12000KHZ |
+ TWL6040_HPLLSQRBP |
+ TWL6040_HPLLENA;
+ break;
+ case 19200000:
+ /* mclk input, pll disabled */
+ hppllctl |= TWL6040_MCLK_19200KHZ |
+ TWL6040_HPLLSQRENA |
+ TWL6040_HPLLBP;
+ break;
+ case 26000000:
+ /* mclk input, pll enabled */
+ hppllctl |= TWL6040_MCLK_26000KHZ |
+ TWL6040_HPLLSQRBP |
+ TWL6040_HPLLENA;
+ break;
+ case 38400000:
+ /* clk slicer, pll disabled */
+ hppllctl |= TWL6040_MCLK_38400KHZ |
+ TWL6040_HPLLSQRENA |
+ TWL6040_HPLLBP;
+ break;
+ default:
+ dev_err(codec->dev, "unknown mclk freq %d\n", freq);
+ return -EINVAL;
+ }
+
+ /* headset dac and driver must be in high-performance mode */
+ headset_power_mode(codec, 1);
+
+ twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl);
+ udelay(500);
+ lppllctl |= TWL6040_HPLLSEL;
+ twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+ lppllctl &= ~TWL6040_LPLLENA;
+ twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+
+ /* high-performance pll can provide only 19.2 MHz */
+ priv->pll = TWL6040_HPPLL_ID;
+ priv->sysclk = 19200000;
+ priv->sysclk_constraints = &hp_constraints;
+ break;
+ default:
+ dev_err(codec->dev, "unknown clk_id %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops twl6040_dai_ops = {
+ .startup = twl6040_startup,
+ .hw_params = twl6040_hw_params,
+ .prepare = twl6040_prepare,
+ .set_sysclk = twl6040_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver twl6040_dai = {
+ .name = "twl6040-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = TWL6040_RATES,
+ .formats = TWL6040_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TWL6040_RATES,
+ .formats = TWL6040_FORMATS,
+ },
+ .ops = &twl6040_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int twl6040_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int twl6040_resume(struct snd_soc_codec *codec)
+{
+ twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ twl6040_set_bias_level(codec, codec->dapm.suspend_bias_level);
+
+ return 0;
+}
+#else
+#define twl6040_suspend NULL
+#define twl6040_resume NULL
+#endif
+
+static int twl6040_probe(struct snd_soc_codec *codec)
+{
+ struct twl4030_codec_data *twl_codec = codec->dev->platform_data;
+ struct twl6040_data *priv;
+ int audpwron, naudint;
+ int ret = 0;
+ u8 icrev, intmr = TWL6040_ALLINT_MSK;
+
+ priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL);
+ if (priv == NULL)
+ return -ENOMEM;
+ snd_soc_codec_set_drvdata(codec, priv);
+
+ priv->codec = codec;
+
+ twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &icrev, TWL6040_REG_ASICREV);
+
+ if (twl_codec && (icrev > 0))
+ audpwron = twl_codec->audpwron_gpio;
+ else
+ audpwron = -EINVAL;
+
+ if (twl_codec)
+ naudint = twl_codec->naudint_irq;
+ else
+ naudint = 0;
+
+ priv->audpwron = audpwron;
+ priv->naudint = naudint;
+ priv->workqueue = create_singlethread_workqueue("twl6040-codec");
+
+ if (!priv->workqueue) {
+ ret = -ENOMEM;
+ goto work_err;
+ }
+
+ INIT_DELAYED_WORK(&priv->delayed_work, twl6040_accessory_work);
+
+ mutex_init(&priv->mutex);
+
+ init_completion(&priv->ready);
+ init_completion(&priv->headset.ramp_done);
+ init_completion(&priv->handsfree.ramp_done);
+
+ if (gpio_is_valid(audpwron)) {
+ ret = gpio_request(audpwron, "audpwron");
+ if (ret)
+ goto gpio1_err;
+
+ ret = gpio_direction_output(audpwron, 0);
+ if (ret)
+ goto gpio2_err;
+
+ priv->codec_powered = 0;
+
+ /* enable only codec ready interrupt */
+ intmr &= ~(TWL6040_READYMSK | TWL6040_PLUGMSK);
+
+ /* reset interrupt status to allow correct power up sequence */
+ twl6040_read_reg_volatile(codec, TWL6040_REG_INTID);
+ }
+ twl6040_write(codec, TWL6040_REG_INTMR, intmr);
+
+ if (naudint) {
+ /* audio interrupt */
+ ret = request_threaded_irq(naudint, NULL,
+ twl6040_naudint_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "twl6040_codec", codec);
+ if (ret)
+ goto gpio2_err;
+ }
+
+ /* init vio registers */
+ twl6040_init_vio_regs(codec);
+
+ priv->hf_workqueue = create_singlethread_workqueue("twl6040-hf");
+ if (priv->hf_workqueue == NULL) {
+ ret = -ENOMEM;
+ goto irq_err;
+ }
+ priv->hs_workqueue = create_singlethread_workqueue("twl6040-hs");
+ if (priv->hs_workqueue == NULL) {
+ ret = -ENOMEM;
+ goto wq_err;
+ }
+
+ INIT_DELAYED_WORK(&priv->hs_delayed_work, twl6040_pga_hs_work);
+ INIT_DELAYED_WORK(&priv->hf_delayed_work, twl6040_pga_hf_work);
+
+ /* power on device */
+ ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ if (ret)
+ goto bias_err;
+
+ snd_soc_add_controls(codec, twl6040_snd_controls,
+ ARRAY_SIZE(twl6040_snd_controls));
+ twl6040_add_widgets(codec);
+
+ return 0;
+
+bias_err:
+ destroy_workqueue(priv->hs_workqueue);
+wq_err:
+ destroy_workqueue(priv->hf_workqueue);
+irq_err:
+ if (naudint)
+ free_irq(naudint, codec);
+gpio2_err:
+ if (gpio_is_valid(audpwron))
+ gpio_free(audpwron);
+gpio1_err:
+ destroy_workqueue(priv->workqueue);
+work_err:
+ kfree(priv);
+ return ret;
+}
+
+static int twl6040_remove(struct snd_soc_codec *codec)
+{
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ int audpwron = priv->audpwron;
+ int naudint = priv->naudint;
+
+ twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ if (gpio_is_valid(audpwron))
+ gpio_free(audpwron);
+
+ if (naudint)
+ free_irq(naudint, codec);
+
+ destroy_workqueue(priv->workqueue);
+ destroy_workqueue(priv->hf_workqueue);
+ destroy_workqueue(priv->hs_workqueue);
+ kfree(priv);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_twl6040 = {
+ .probe = twl6040_probe,
+ .remove = twl6040_remove,
+ .suspend = twl6040_suspend,
+ .resume = twl6040_resume,
+ .read = twl6040_read_reg_cache,
+ .write = twl6040_write,
+ .set_bias_level = twl6040_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(twl6040_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = twl6040_reg,
+};
+
+static int __devinit twl6040_codec_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_twl6040, &twl6040_dai, 1);
+}
+
+static int __devexit twl6040_codec_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver twl6040_codec_driver = {
+ .driver = {
+ .name = "twl6040-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = twl6040_codec_probe,
+ .remove = __devexit_p(twl6040_codec_remove),
+};
+
+static int __init twl6040_codec_init(void)
+{
+ return platform_driver_register(&twl6040_codec_driver);
+}
+module_init(twl6040_codec_init);
+
+static void __exit twl6040_codec_exit(void)
+{
+ platform_driver_unregister(&twl6040_codec_driver);
+}
+module_exit(twl6040_codec_exit);
+
+MODULE_DESCRIPTION("ASoC TWL6040 codec driver");
+MODULE_AUTHOR("Misael Lopez Cruz");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/twl6040.h b/sound/soc/codecs/twl6040.h
new file mode 100644
index 00000000..23aeed09
--- /dev/null
+++ b/sound/soc/codecs/twl6040.h
@@ -0,0 +1,146 @@
+/*
+ * ALSA SoC TWL6040 codec driver
+ *
+ * Author: Misael Lopez Cruz <x0052729@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TWL6040_H__
+#define __TWL6040_H__
+
+#define TWL6040_REG_ASICID 0x01
+#define TWL6040_REG_ASICREV 0x02
+#define TWL6040_REG_INTID 0x03
+#define TWL6040_REG_INTMR 0x04
+#define TWL6040_REG_NCPCTL 0x05
+#define TWL6040_REG_LDOCTL 0x06
+#define TWL6040_REG_HPPLLCTL 0x07
+#define TWL6040_REG_LPPLLCTL 0x08
+#define TWL6040_REG_LPPLLDIV 0x09
+#define TWL6040_REG_AMICBCTL 0x0A
+#define TWL6040_REG_DMICBCTL 0x0B
+#define TWL6040_REG_MICLCTL 0x0C
+#define TWL6040_REG_MICRCTL 0x0D
+#define TWL6040_REG_MICGAIN 0x0E
+#define TWL6040_REG_LINEGAIN 0x0F
+#define TWL6040_REG_HSLCTL 0x10
+#define TWL6040_REG_HSRCTL 0x11
+#define TWL6040_REG_HSGAIN 0x12
+#define TWL6040_REG_EARCTL 0x13
+#define TWL6040_REG_HFLCTL 0x14
+#define TWL6040_REG_HFLGAIN 0x15
+#define TWL6040_REG_HFRCTL 0x16
+#define TWL6040_REG_HFRGAIN 0x17
+#define TWL6040_REG_VIBCTLL 0x18
+#define TWL6040_REG_VIBDATL 0x19
+#define TWL6040_REG_VIBCTLR 0x1A
+#define TWL6040_REG_VIBDATR 0x1B
+#define TWL6040_REG_HKCTL1 0x1C
+#define TWL6040_REG_HKCTL2 0x1D
+#define TWL6040_REG_GPOCTL 0x1E
+#define TWL6040_REG_ALB 0x1F
+#define TWL6040_REG_DLB 0x20
+#define TWL6040_REG_TRIM1 0x28
+#define TWL6040_REG_TRIM2 0x29
+#define TWL6040_REG_TRIM3 0x2A
+#define TWL6040_REG_HSOTRIM 0x2B
+#define TWL6040_REG_HFOTRIM 0x2C
+#define TWL6040_REG_ACCCTL 0x2D
+#define TWL6040_REG_STATUS 0x2E
+
+#define TWL6040_CACHEREGNUM (TWL6040_REG_STATUS + 1)
+
+#define TWL6040_VIOREGNUM 18
+#define TWL6040_VDDREGNUM 21
+
+/* INTID (0x03) fields */
+
+#define TWL6040_THINT 0x01
+#define TWL6040_PLUGINT 0x02
+#define TWL6040_UNPLUGINT 0x04
+#define TWL6040_HOOKINT 0x08
+#define TWL6040_HFINT 0x10
+#define TWL6040_VIBINT 0x20
+#define TWL6040_READYINT 0x40
+
+/* INTMR (0x04) fields */
+
+#define TWL6040_PLUGMSK 0x02
+#define TWL6040_READYMSK 0x40
+#define TWL6040_ALLINT_MSK 0x7B
+
+/* NCPCTL (0x05) fields */
+
+#define TWL6040_NCPENA 0x01
+#define TWL6040_NCPOPEN 0x40
+
+/* LDOCTL (0x06) fields */
+
+#define TWL6040_LSLDOENA 0x01
+#define TWL6040_HSLDOENA 0x04
+#define TWL6040_REFENA 0x40
+#define TWL6040_OSCENA 0x80
+
+/* HPPLLCTL (0x07) fields */
+
+#define TWL6040_HPLLENA 0x01
+#define TWL6040_HPLLRST 0x02
+#define TWL6040_HPLLBP 0x04
+#define TWL6040_HPLLSQRENA 0x08
+#define TWL6040_HPLLSQRBP 0x10
+#define TWL6040_MCLK_12000KHZ (0 << 5)
+#define TWL6040_MCLK_19200KHZ (1 << 5)
+#define TWL6040_MCLK_26000KHZ (2 << 5)
+#define TWL6040_MCLK_38400KHZ (3 << 5)
+#define TWL6040_MCLK_MSK 0x60
+
+/* LPPLLCTL (0x08) fields */
+
+#define TWL6040_LPLLENA 0x01
+#define TWL6040_LPLLRST 0x02
+#define TWL6040_LPLLSEL 0x04
+#define TWL6040_LPLLFIN 0x08
+#define TWL6040_HPLLSEL 0x10
+
+/* HSLCTL (0x10) fields */
+
+#define TWL6040_HSDACMODEL 0x02
+#define TWL6040_HSDRVMODEL 0x08
+
+/* HSRCTL (0x11) fields */
+
+#define TWL6040_HSDACMODER 0x02
+#define TWL6040_HSDRVMODER 0x08
+
+/* ACCCTL (0x2D) fields */
+
+#define TWL6040_RESETSPLIT 0x04
+
+#define TWL6040_SYSCLK_SEL_LPPLL 1
+#define TWL6040_SYSCLK_SEL_HPPLL 2
+
+#define TWL6040_HPPLL_ID 1
+#define TWL6040_LPPLL_ID 2
+
+/* STATUS (0x2E) fields */
+
+#define TWL6040_PLUGCOMP 0x02
+
+void twl6040_hs_jack_detect(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack, int report);
+
+#endif /* End of __TWL6040_H__ */
diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c
new file mode 100644
index 00000000..a7b8f301
--- /dev/null
+++ b/sound/soc/codecs/uda134x.c
@@ -0,0 +1,642 @@
+/*
+ * uda134x.c -- UDA134X ALSA SoC Codec driver
+ *
+ * Modifications by Christian Pellegrin <chripell@evolware.org>
+ *
+ * Copyright 2007 Dension Audio Systems Ltd.
+ * Author: Zoltan Devai
+ *
+ * Based on the WM87xx drivers by Liam Girdwood and Richard Purdie
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include <sound/uda134x.h>
+#include <sound/l3.h>
+
+#include "uda134x.h"
+
+
+#define UDA134X_RATES SNDRV_PCM_RATE_8000_48000
+#define UDA134X_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE)
+
+struct uda134x_priv {
+ int sysclk;
+ int dai_fmt;
+
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+};
+
+/* In-data addresses are hard-coded into the reg-cache values */
+static const char uda134x_reg[UDA134X_REGS_NUM] = {
+ /* Extended address registers */
+ 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Status, data regs */
+ 0x00, 0x83, 0x00, 0x40, 0x80, 0xC0, 0x00,
+};
+
+/*
+ * The codec has no support for reading its registers except for peak level...
+ */
+static inline unsigned int uda134x_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+
+ if (reg >= UDA134X_REGS_NUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * Write the register cache
+ */
+static inline void uda134x_write_reg_cache(struct snd_soc_codec *codec,
+ u8 reg, unsigned int value)
+{
+ u8 *cache = codec->reg_cache;
+
+ if (reg >= UDA134X_REGS_NUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * Write to the uda134x registers
+ *
+ */
+static int uda134x_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ int ret;
+ u8 addr;
+ u8 data = value;
+ struct uda134x_platform_data *pd = codec->control_data;
+
+ pr_debug("%s reg: %02X, value:%02X\n", __func__, reg, value);
+
+ if (reg >= UDA134X_REGS_NUM) {
+ printk(KERN_ERR "%s unknown register: reg: %u",
+ __func__, reg);
+ return -EINVAL;
+ }
+
+ uda134x_write_reg_cache(codec, reg, value);
+
+ switch (reg) {
+ case UDA134X_STATUS0:
+ case UDA134X_STATUS1:
+ addr = UDA134X_STATUS_ADDR;
+ break;
+ case UDA134X_DATA000:
+ case UDA134X_DATA001:
+ case UDA134X_DATA010:
+ case UDA134X_DATA011:
+ addr = UDA134X_DATA0_ADDR;
+ break;
+ case UDA134X_DATA1:
+ addr = UDA134X_DATA1_ADDR;
+ break;
+ default:
+ /* It's an extended address register */
+ addr = (reg | UDA134X_EXTADDR_PREFIX);
+
+ ret = l3_write(&pd->l3,
+ UDA134X_DATA0_ADDR, &addr, 1);
+ if (ret != 1)
+ return -EIO;
+
+ addr = UDA134X_DATA0_ADDR;
+ data = (value | UDA134X_EXTDATA_PREFIX);
+ break;
+ }
+
+ ret = l3_write(&pd->l3,
+ addr, &data, 1);
+ if (ret != 1)
+ return -EIO;
+
+ return 0;
+}
+
+static inline void uda134x_reset(struct snd_soc_codec *codec)
+{
+ u8 reset_reg = uda134x_read_reg_cache(codec, UDA134X_STATUS0);
+ uda134x_write(codec, UDA134X_STATUS0, reset_reg | (1<<6));
+ msleep(1);
+ uda134x_write(codec, UDA134X_STATUS0, reset_reg & ~(1<<6));
+}
+
+static int uda134x_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 mute_reg = uda134x_read_reg_cache(codec, UDA134X_DATA010);
+
+ pr_debug("%s mute: %d\n", __func__, mute);
+
+ if (mute)
+ mute_reg |= (1<<2);
+ else
+ mute_reg &= ~(1<<2);
+
+ uda134x_write(codec, UDA134X_DATA010, mute_reg);
+
+ return 0;
+}
+
+static int uda134x_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec =rtd->codec;
+ struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+ struct snd_pcm_runtime *master_runtime;
+
+ if (uda134x->master_substream) {
+ master_runtime = uda134x->master_substream->runtime;
+
+ pr_debug("%s constraining to %d bits at %d\n", __func__,
+ master_runtime->sample_bits,
+ master_runtime->rate);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ master_runtime->rate,
+ master_runtime->rate);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
+
+ uda134x->slave_substream = substream;
+ } else
+ uda134x->master_substream = substream;
+
+ return 0;
+}
+
+static void uda134x_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+
+ if (uda134x->master_substream == substream)
+ uda134x->master_substream = uda134x->slave_substream;
+
+ uda134x->slave_substream = NULL;
+}
+
+static int uda134x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+ u8 hw_params;
+
+ if (substream == uda134x->slave_substream) {
+ pr_debug("%s ignoring hw_params for slave substream\n",
+ __func__);
+ return 0;
+ }
+
+ hw_params = uda134x_read_reg_cache(codec, UDA134X_STATUS0);
+ hw_params &= STATUS0_SYSCLK_MASK;
+ hw_params &= STATUS0_DAIFMT_MASK;
+
+ pr_debug("%s sysclk: %d, rate:%d\n", __func__,
+ uda134x->sysclk, params_rate(params));
+
+ /* set SYSCLK / fs ratio */
+ switch (uda134x->sysclk / params_rate(params)) {
+ case 512:
+ break;
+ case 384:
+ hw_params |= (1<<4);
+ break;
+ case 256:
+ hw_params |= (1<<5);
+ break;
+ default:
+ printk(KERN_ERR "%s unsupported fs\n", __func__);
+ return -EINVAL;
+ }
+
+ pr_debug("%s dai_fmt: %d, params_format:%d\n", __func__,
+ uda134x->dai_fmt, params_format(params));
+
+ /* set DAI format and word length */
+ switch (uda134x->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ hw_params |= (1<<1);
+ break;
+ case SNDRV_PCM_FORMAT_S18_3LE:
+ hw_params |= (1<<2);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ hw_params |= ((1<<2) | (1<<1));
+ break;
+ default:
+ printk(KERN_ERR "%s unsupported format (right)\n",
+ __func__);
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ hw_params |= (1<<3);
+ break;
+ default:
+ printk(KERN_ERR "%s unsupported format\n", __func__);
+ return -EINVAL;
+ }
+
+ uda134x_write(codec, UDA134X_STATUS0, hw_params);
+
+ return 0;
+}
+
+static int uda134x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+
+ pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__,
+ clk_id, freq, dir);
+
+ /* Anything between 256fs*8Khz and 512fs*48Khz should be acceptable
+ because the codec is slave. Of course limitations of the clock
+ master (the IIS controller) apply.
+ We'll error out on set_hw_params if it's not OK */
+ if ((freq >= (256 * 8000)) && (freq <= (512 * 48000))) {
+ uda134x->sysclk = freq;
+ return 0;
+ }
+
+ printk(KERN_ERR "%s unsupported sysclk\n", __func__);
+ return -EINVAL;
+}
+
+static int uda134x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+
+ pr_debug("%s fmt: %08X\n", __func__, fmt);
+
+ /* codec supports only full slave mode */
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) {
+ printk(KERN_ERR "%s unsupported slave mode\n", __func__);
+ return -EINVAL;
+ }
+
+ /* no support for clock inversion */
+ if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) {
+ printk(KERN_ERR "%s unsupported clock inversion\n", __func__);
+ return -EINVAL;
+ }
+
+ /* We can't setup DAI format here as it depends on the word bit num */
+ /* so let's just store the value for later */
+ uda134x->dai_fmt = fmt;
+
+ return 0;
+}
+
+static int uda134x_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u8 reg;
+ struct uda134x_platform_data *pd = codec->control_data;
+ int i;
+ u8 *cache = codec->reg_cache;
+
+ pr_debug("%s bias level %d\n", __func__, level);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* ADC, DAC on */
+ switch (pd->model) {
+ case UDA134X_UDA1340:
+ case UDA134X_UDA1344:
+ case UDA134X_UDA1345:
+ reg = uda134x_read_reg_cache(codec, UDA134X_DATA011);
+ uda134x_write(codec, UDA134X_DATA011, reg | 0x03);
+ break;
+ case UDA134X_UDA1341:
+ reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1);
+ uda134x_write(codec, UDA134X_STATUS1, reg | 0x03);
+ break;
+ default:
+ printk(KERN_ERR "UDA134X SoC codec: "
+ "unsupported model %d\n", pd->model);
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ /* power on */
+ if (pd->power) {
+ pd->power(1);
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(uda134x_reg); i++)
+ codec->driver->write(codec, i, *cache++);
+ }
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* ADC, DAC power off */
+ switch (pd->model) {
+ case UDA134X_UDA1340:
+ case UDA134X_UDA1344:
+ case UDA134X_UDA1345:
+ reg = uda134x_read_reg_cache(codec, UDA134X_DATA011);
+ uda134x_write(codec, UDA134X_DATA011, reg & ~(0x03));
+ break;
+ case UDA134X_UDA1341:
+ reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1);
+ uda134x_write(codec, UDA134X_STATUS1, reg & ~(0x03));
+ break;
+ default:
+ printk(KERN_ERR "UDA134X SoC codec: "
+ "unsupported model %d\n", pd->model);
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* power off */
+ if (pd->power)
+ pd->power(0);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static const char *uda134x_dsp_setting[] = {"Flat", "Minimum1",
+ "Minimum2", "Maximum"};
+static const char *uda134x_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const char *uda134x_mixmode[] = {"Differential", "Analog1",
+ "Analog2", "Both"};
+
+static const struct soc_enum uda134x_mixer_enum[] = {
+SOC_ENUM_SINGLE(UDA134X_DATA010, 0, 0x04, uda134x_dsp_setting),
+SOC_ENUM_SINGLE(UDA134X_DATA010, 3, 0x04, uda134x_deemph),
+SOC_ENUM_SINGLE(UDA134X_EA010, 0, 0x04, uda134x_mixmode),
+};
+
+static const struct snd_kcontrol_new uda1341_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+SOC_SINGLE("Capture Volume", UDA134X_EA010, 2, 0x07, 0),
+SOC_SINGLE("Analog1 Volume", UDA134X_EA000, 0, 0x1F, 1),
+SOC_SINGLE("Analog2 Volume", UDA134X_EA001, 0, 0x1F, 1),
+
+SOC_SINGLE("Mic Sensitivity", UDA134X_EA010, 2, 7, 0),
+SOC_SINGLE("Mic Volume", UDA134X_EA101, 0, 0x1F, 0),
+
+SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
+SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0),
+
+SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]),
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+SOC_ENUM("Input Mux", uda134x_mixer_enum[2]),
+
+SOC_SINGLE("AGC Switch", UDA134X_EA100, 4, 1, 0),
+SOC_SINGLE("AGC Target Volume", UDA134X_EA110, 0, 0x03, 1),
+SOC_SINGLE("AGC Timing", UDA134X_EA110, 2, 0x07, 0),
+
+SOC_SINGLE("DAC +6dB Switch", UDA134X_STATUS1, 6, 1, 0),
+SOC_SINGLE("ADC +6dB Switch", UDA134X_STATUS1, 5, 1, 0),
+SOC_SINGLE("ADC Polarity Switch", UDA134X_STATUS1, 4, 1, 0),
+SOC_SINGLE("DAC Polarity Switch", UDA134X_STATUS1, 3, 1, 0),
+SOC_SINGLE("Double Speed Playback Switch", UDA134X_STATUS1, 2, 1, 0),
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new uda1340_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+
+SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
+SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0),
+
+SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]),
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new uda1345_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+static struct snd_soc_dai_ops uda134x_dai_ops = {
+ .startup = uda134x_startup,
+ .shutdown = uda134x_shutdown,
+ .hw_params = uda134x_hw_params,
+ .digital_mute = uda134x_mute,
+ .set_sysclk = uda134x_set_dai_sysclk,
+ .set_fmt = uda134x_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver uda134x_dai = {
+ .name = "uda134x-hifi",
+ /* playback capabilities */
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA134X_RATES,
+ .formats = UDA134X_FORMATS,
+ },
+ /* capture capabilities */
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA134X_RATES,
+ .formats = UDA134X_FORMATS,
+ },
+ /* pcm operations */
+ .ops = &uda134x_dai_ops,
+};
+
+static int uda134x_soc_probe(struct snd_soc_codec *codec)
+{
+ struct uda134x_priv *uda134x;
+ struct uda134x_platform_data *pd = codec->card->dev->platform_data;
+
+ int ret;
+
+ printk(KERN_INFO "UDA134X SoC Audio Codec\n");
+
+ if (!pd) {
+ printk(KERN_ERR "UDA134X SoC codec: "
+ "missing L3 bitbang function\n");
+ return -ENODEV;
+ }
+
+ switch (pd->model) {
+ case UDA134X_UDA1340:
+ case UDA134X_UDA1341:
+ case UDA134X_UDA1344:
+ case UDA134X_UDA1345:
+ break;
+ default:
+ printk(KERN_ERR "UDA134X SoC codec: "
+ "unsupported model %d\n",
+ pd->model);
+ return -EINVAL;
+ }
+
+ uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL);
+ if (uda134x == NULL)
+ return -ENOMEM;
+ snd_soc_codec_set_drvdata(codec, uda134x);
+
+ codec->control_data = pd;
+
+ if (pd->power)
+ pd->power(1);
+
+ uda134x_reset(codec);
+
+ if (pd->is_powered_on_standby)
+ uda134x_set_bias_level(codec, SND_SOC_BIAS_ON);
+ else
+ uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ switch (pd->model) {
+ case UDA134X_UDA1340:
+ case UDA134X_UDA1344:
+ ret = snd_soc_add_controls(codec, uda1340_snd_controls,
+ ARRAY_SIZE(uda1340_snd_controls));
+ break;
+ case UDA134X_UDA1341:
+ ret = snd_soc_add_controls(codec, uda1341_snd_controls,
+ ARRAY_SIZE(uda1341_snd_controls));
+ break;
+ case UDA134X_UDA1345:
+ ret = snd_soc_add_controls(codec, uda1345_snd_controls,
+ ARRAY_SIZE(uda1345_snd_controls));
+ break;
+ default:
+ printk(KERN_ERR "%s unknown codec type: %d",
+ __func__, pd->model);
+ kfree(uda134x);
+ return -EINVAL;
+ }
+
+ if (ret < 0) {
+ printk(KERN_ERR "UDA134X: failed to register controls\n");
+ kfree(uda134x);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* power down chip */
+static int uda134x_soc_remove(struct snd_soc_codec *codec)
+{
+ struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+
+ uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ kfree(uda134x);
+ return 0;
+}
+
+#if defined(CONFIG_PM)
+static int uda134x_soc_suspend(struct snd_soc_codec *codec,
+ pm_message_t state)
+{
+ uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int uda134x_soc_resume(struct snd_soc_codec *codec)
+{
+ uda134x_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ uda134x_set_bias_level(codec, SND_SOC_BIAS_ON);
+ return 0;
+}
+#else
+#define uda134x_soc_suspend NULL
+#define uda134x_soc_resume NULL
+#endif /* CONFIG_PM */
+
+static struct snd_soc_codec_driver soc_codec_dev_uda134x = {
+ .probe = uda134x_soc_probe,
+ .remove = uda134x_soc_remove,
+ .suspend = uda134x_soc_suspend,
+ .resume = uda134x_soc_resume,
+ .reg_cache_size = sizeof(uda134x_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = uda134x_reg,
+ .reg_cache_step = 1,
+ .read = uda134x_read_reg_cache,
+ .write = uda134x_write,
+ .set_bias_level = uda134x_set_bias_level,
+};
+
+static int __devinit uda134x_codec_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_uda134x, &uda134x_dai, 1);
+}
+
+static int __devexit uda134x_codec_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver uda134x_codec_driver = {
+ .driver = {
+ .name = "uda134x-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = uda134x_codec_probe,
+ .remove = __devexit_p(uda134x_codec_remove),
+};
+
+static int __init uda134x_codec_init(void)
+{
+ return platform_driver_register(&uda134x_codec_driver);
+}
+module_init(uda134x_codec_init);
+
+static void __exit uda134x_codec_exit(void)
+{
+ platform_driver_unregister(&uda134x_codec_driver);
+}
+module_exit(uda134x_codec_exit);
+
+MODULE_DESCRIPTION("UDA134X ALSA soc codec driver");
+MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/uda134x.h b/sound/soc/codecs/uda134x.h
new file mode 100644
index 00000000..9faae069
--- /dev/null
+++ b/sound/soc/codecs/uda134x.h
@@ -0,0 +1,34 @@
+#ifndef _UDA134X_CODEC_H
+#define _UDA134X_CODEC_H
+
+#define UDA134X_L3ADDR 5
+#define UDA134X_DATA0_ADDR ((UDA134X_L3ADDR << 2) | 0)
+#define UDA134X_DATA1_ADDR ((UDA134X_L3ADDR << 2) | 1)
+#define UDA134X_STATUS_ADDR ((UDA134X_L3ADDR << 2) | 2)
+
+#define UDA134X_EXTADDR_PREFIX 0xC0
+#define UDA134X_EXTDATA_PREFIX 0xE0
+
+/* UDA134X registers */
+#define UDA134X_EA000 0
+#define UDA134X_EA001 1
+#define UDA134X_EA010 2
+#define UDA134X_EA011 3
+#define UDA134X_EA100 4
+#define UDA134X_EA101 5
+#define UDA134X_EA110 6
+#define UDA134X_EA111 7
+#define UDA134X_STATUS0 8
+#define UDA134X_STATUS1 9
+#define UDA134X_DATA000 10
+#define UDA134X_DATA001 11
+#define UDA134X_DATA010 12
+#define UDA134X_DATA011 13
+#define UDA134X_DATA1 14
+
+#define UDA134X_REGS_NUM 15
+
+#define STATUS0_DAIFMT_MASK (~(7<<1))
+#define STATUS0_SYSCLK_MASK (~(3<<4))
+
+#endif
diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c
new file mode 100644
index 00000000..c5ca8cfe
--- /dev/null
+++ b/sound/soc/codecs/uda1380.c
@@ -0,0 +1,886 @@
+/*
+ * uda1380.c - Philips UDA1380 ALSA SoC audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (c) 2007-2009 Philipp Zabel <philipp.zabel@gmail.com>
+ *
+ * Modified by Richard Purdie <richard@openedhand.com> to fit into SoC
+ * codec model.
+ *
+ * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org>
+ * Copyright 2005 Openedhand Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/uda1380.h>
+
+#include "uda1380.h"
+
+/* codec private data */
+struct uda1380_priv {
+ struct snd_soc_codec *codec;
+ unsigned int dac_clk;
+ struct work_struct work;
+ void *control_data;
+};
+
+/*
+ * uda1380 register cache
+ */
+static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = {
+ 0x0502, 0x0000, 0x0000, 0x3f3f,
+ 0x0202, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0xff00, 0x0000, 0x4800,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x8000, 0x0002, 0x0000,
+};
+
+static unsigned long uda1380_cache_dirty;
+
+/*
+ * read uda1380 register cache
+ */
+static inline unsigned int uda1380_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg == UDA1380_RESET)
+ return 0;
+ if (reg >= UDA1380_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write uda1380 register cache
+ */
+static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg >= UDA1380_CACHEREGNUM)
+ return;
+ if ((reg >= 0x10) && (cache[reg] != value))
+ set_bit(reg - 0x10, &uda1380_cache_dirty);
+ cache[reg] = value;
+}
+
+/*
+ * write to the UDA1380 register space
+ */
+static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ /* data is
+ * data[0] is register offset
+ * data[1] is MS byte
+ * data[2] is LS byte
+ */
+ data[0] = reg;
+ data[1] = (value & 0xff00) >> 8;
+ data[2] = value & 0x00ff;
+
+ uda1380_write_reg_cache(codec, reg, value);
+
+ /* the interpolator & decimator regs must only be written when the
+ * codec DAI is active.
+ */
+ if (!codec->active && (reg >= UDA1380_MVOL))
+ return 0;
+ pr_debug("uda1380: hw write %x val %x\n", reg, value);
+ if (codec->hw_write(codec->control_data, data, 3) == 3) {
+ unsigned int val;
+ i2c_master_send(codec->control_data, data, 1);
+ i2c_master_recv(codec->control_data, data, 2);
+ val = (data[0]<<8) | data[1];
+ if (val != value) {
+ pr_debug("uda1380: READ BACK VAL %x\n",
+ (data[0]<<8) | data[1]);
+ return -EIO;
+ }
+ if (reg >= 0x10)
+ clear_bit(reg - 0x10, &uda1380_cache_dirty);
+ return 0;
+ } else
+ return -EIO;
+}
+
+static void uda1380_sync_cache(struct snd_soc_codec *codec)
+{
+ int reg;
+ u8 data[3];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (reg = 0; reg < UDA1380_MVOL; reg++) {
+ data[0] = reg;
+ data[1] = (cache[reg] & 0xff00) >> 8;
+ data[2] = cache[reg] & 0x00ff;
+ if (codec->hw_write(codec->control_data, data, 3) != 3)
+ dev_err(codec->dev, "%s: write to reg 0x%x failed\n",
+ __func__, reg);
+ }
+}
+
+static int uda1380_reset(struct snd_soc_codec *codec)
+{
+ struct uda1380_platform_data *pdata = codec->dev->platform_data;
+
+ if (gpio_is_valid(pdata->gpio_reset)) {
+ gpio_set_value(pdata->gpio_reset, 1);
+ mdelay(1);
+ gpio_set_value(pdata->gpio_reset, 0);
+ } else {
+ u8 data[3];
+
+ data[0] = UDA1380_RESET;
+ data[1] = 0;
+ data[2] = 0;
+
+ if (codec->hw_write(codec->control_data, data, 3) != 3) {
+ dev_err(codec->dev, "%s: failed\n", __func__);
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+static void uda1380_flush_work(struct work_struct *work)
+{
+ struct uda1380_priv *uda1380 = container_of(work, struct uda1380_priv, work);
+ struct snd_soc_codec *uda1380_codec = uda1380->codec;
+ int bit, reg;
+
+ for_each_set_bit(bit, &uda1380_cache_dirty, UDA1380_CACHEREGNUM - 0x10) {
+ reg = 0x10 + bit;
+ pr_debug("uda1380: flush reg %x val %x:\n", reg,
+ uda1380_read_reg_cache(uda1380_codec, reg));
+ uda1380_write(uda1380_codec, reg,
+ uda1380_read_reg_cache(uda1380_codec, reg));
+ clear_bit(bit, &uda1380_cache_dirty);
+ }
+
+}
+
+/* declarations of ALSA reg_elem_REAL controls */
+static const char *uda1380_deemp[] = {
+ "None",
+ "32kHz",
+ "44.1kHz",
+ "48kHz",
+ "96kHz",
+};
+static const char *uda1380_input_sel[] = {
+ "Line",
+ "Mic + Line R",
+ "Line L",
+ "Mic",
+};
+static const char *uda1380_output_sel[] = {
+ "DAC",
+ "Analog Mixer",
+};
+static const char *uda1380_spf_mode[] = {
+ "Flat",
+ "Minimum1",
+ "Minimum2",
+ "Maximum"
+};
+static const char *uda1380_capture_sel[] = {
+ "ADC",
+ "Digital Mixer"
+};
+static const char *uda1380_sel_ns[] = {
+ "3rd-order",
+ "5th-order"
+};
+static const char *uda1380_mix_control[] = {
+ "off",
+ "PCM only",
+ "before sound processing",
+ "after sound processing"
+};
+static const char *uda1380_sdet_setting[] = {
+ "3200",
+ "4800",
+ "9600",
+ "19200"
+};
+static const char *uda1380_os_setting[] = {
+ "single-speed",
+ "double-speed (no mixing)",
+ "quad-speed (no mixing)"
+};
+
+static const struct soc_enum uda1380_deemp_enum[] = {
+ SOC_ENUM_SINGLE(UDA1380_DEEMP, 8, 5, uda1380_deemp),
+ SOC_ENUM_SINGLE(UDA1380_DEEMP, 0, 5, uda1380_deemp),
+};
+static const struct soc_enum uda1380_input_sel_enum =
+ SOC_ENUM_SINGLE(UDA1380_ADC, 2, 4, uda1380_input_sel); /* SEL_MIC, SEL_LNA */
+static const struct soc_enum uda1380_output_sel_enum =
+ SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel); /* R02_EN_AVC */
+static const struct soc_enum uda1380_spf_enum =
+ SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode); /* M */
+static const struct soc_enum uda1380_capture_sel_enum =
+ SOC_ENUM_SINGLE(UDA1380_IFACE, 6, 2, uda1380_capture_sel); /* SEL_SOURCE */
+static const struct soc_enum uda1380_sel_ns_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 14, 2, uda1380_sel_ns); /* SEL_NS */
+static const struct soc_enum uda1380_mix_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 12, 4, uda1380_mix_control); /* MIX, MIX_POS */
+static const struct soc_enum uda1380_sdet_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 4, 4, uda1380_sdet_setting); /* SD_VALUE */
+static const struct soc_enum uda1380_os_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 0, 3, uda1380_os_setting); /* OS */
+
+/*
+ * from -48 dB in 1.5 dB steps (mute instead of -49.5 dB)
+ */
+static DECLARE_TLV_DB_SCALE(amix_tlv, -4950, 150, 1);
+
+/*
+ * from -78 dB in 1 dB steps (3 dB steps, really. LSB are ignored),
+ * from -66 dB in 0.5 dB steps (2 dB steps, really) and
+ * from -52 dB in 0.25 dB steps
+ */
+static const unsigned int mvol_tlv[] = {
+ TLV_DB_RANGE_HEAD(3),
+ 0, 15, TLV_DB_SCALE_ITEM(-8200, 100, 1),
+ 16, 43, TLV_DB_SCALE_ITEM(-6600, 50, 0),
+ 44, 252, TLV_DB_SCALE_ITEM(-5200, 25, 0),
+};
+
+/*
+ * from -72 dB in 1.5 dB steps (6 dB steps really),
+ * from -66 dB in 0.75 dB steps (3 dB steps really),
+ * from -60 dB in 0.5 dB steps (2 dB steps really) and
+ * from -46 dB in 0.25 dB steps
+ */
+static const unsigned int vc_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 7, TLV_DB_SCALE_ITEM(-7800, 150, 1),
+ 8, 15, TLV_DB_SCALE_ITEM(-6600, 75, 0),
+ 16, 43, TLV_DB_SCALE_ITEM(-6000, 50, 0),
+ 44, 228, TLV_DB_SCALE_ITEM(-4600, 25, 0),
+};
+
+/* from 0 to 6 dB in 2 dB steps if SPF mode != flat */
+static DECLARE_TLV_DB_SCALE(tr_tlv, 0, 200, 0);
+
+/* from 0 to 24 dB in 2 dB steps, if SPF mode == maximum, otherwise cuts
+ * off at 18 dB max) */
+static DECLARE_TLV_DB_SCALE(bb_tlv, 0, 200, 0);
+
+/* from -63 to 24 dB in 0.5 dB steps (-128...48) */
+static DECLARE_TLV_DB_SCALE(dec_tlv, -6400, 50, 1);
+
+/* from 0 to 24 dB in 3 dB steps */
+static DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0);
+
+/* from 0 to 30 dB in 2 dB steps */
+static DECLARE_TLV_DB_SCALE(vga_tlv, 0, 200, 0);
+
+static const struct snd_kcontrol_new uda1380_snd_controls[] = {
+ SOC_DOUBLE_TLV("Analog Mixer Volume", UDA1380_AMIX, 0, 8, 44, 1, amix_tlv), /* AVCR, AVCL */
+ SOC_DOUBLE_TLV("Master Playback Volume", UDA1380_MVOL, 0, 8, 252, 1, mvol_tlv), /* MVCL, MVCR */
+ SOC_SINGLE_TLV("ADC Playback Volume", UDA1380_MIXVOL, 8, 228, 1, vc_tlv), /* VC2 */
+ SOC_SINGLE_TLV("PCM Playback Volume", UDA1380_MIXVOL, 0, 228, 1, vc_tlv), /* VC1 */
+ SOC_ENUM("Sound Processing Filter", uda1380_spf_enum), /* M */
+ SOC_DOUBLE_TLV("Tone Control - Treble", UDA1380_MODE, 4, 12, 3, 0, tr_tlv), /* TRL, TRR */
+ SOC_DOUBLE_TLV("Tone Control - Bass", UDA1380_MODE, 0, 8, 15, 0, bb_tlv), /* BBL, BBR */
+/**/ SOC_SINGLE("Master Playback Switch", UDA1380_DEEMP, 14, 1, 1), /* MTM */
+ SOC_SINGLE("ADC Playback Switch", UDA1380_DEEMP, 11, 1, 1), /* MT2 from decimation filter */
+ SOC_ENUM("ADC Playback De-emphasis", uda1380_deemp_enum[0]), /* DE2 */
+ SOC_SINGLE("PCM Playback Switch", UDA1380_DEEMP, 3, 1, 1), /* MT1, from digital data input */
+ SOC_ENUM("PCM Playback De-emphasis", uda1380_deemp_enum[1]), /* DE1 */
+ SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */
+ SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */
+ SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */
+ SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */
+ SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */
+ SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */
+ SOC_DOUBLE_S8_TLV("ADC Capture Volume", UDA1380_DEC, -128, 48, dec_tlv), /* ML_DEC, MR_DEC */
+/**/ SOC_SINGLE("ADC Capture Switch", UDA1380_PGA, 15, 1, 1), /* MT_ADC */
+ SOC_DOUBLE_TLV("Line Capture Volume", UDA1380_PGA, 0, 8, 8, 0, pga_tlv), /* PGA_GAINCTRLL, PGA_GAINCTRLR */
+ SOC_SINGLE("ADC Polarity inverting Switch", UDA1380_ADC, 12, 1, 0), /* ADCPOL_INV */
+ SOC_SINGLE_TLV("Mic Capture Volume", UDA1380_ADC, 8, 15, 0, vga_tlv), /* VGA_CTRL */
+ SOC_SINGLE("DC Filter Bypass Switch", UDA1380_ADC, 1, 1, 0), /* SKIP_DCFIL (before decimator) */
+ SOC_SINGLE("DC Filter Enable Switch", UDA1380_ADC, 0, 1, 0), /* EN_DCFIL (at output of decimator) */
+ SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), /* TODO: enum, see table 62 */
+ SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), /* AGC_LEVEL */
+ /* -5.5, -8, -11.5, -14 dBFS */
+ SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new uda1380_input_mux_control =
+ SOC_DAPM_ENUM("Route", uda1380_input_sel_enum);
+
+/* Output mux */
+static const struct snd_kcontrol_new uda1380_output_mux_control =
+ SOC_DAPM_ENUM("Route", uda1380_output_sel_enum);
+
+/* Capture mux */
+static const struct snd_kcontrol_new uda1380_capture_mux_control =
+ SOC_DAPM_ENUM("Route", uda1380_capture_sel_enum);
+
+
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+ SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0,
+ &uda1380_input_mux_control),
+ SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0,
+ &uda1380_output_mux_control),
+ SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0,
+ &uda1380_capture_mux_control),
+ SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", UDA1380_PM, 2, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", UDA1380_PM, 0, 0),
+ SND_SOC_DAPM_INPUT("VINM"),
+ SND_SOC_DAPM_INPUT("VINL"),
+ SND_SOC_DAPM_INPUT("VINR"),
+ SND_SOC_DAPM_MIXER("Analog Mixer", UDA1380_PM, 6, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("VOUTLHP"),
+ SND_SOC_DAPM_OUTPUT("VOUTRHP"),
+ SND_SOC_DAPM_OUTPUT("VOUTL"),
+ SND_SOC_DAPM_OUTPUT("VOUTR"),
+ SND_SOC_DAPM_DAC("DAC", "Playback", UDA1380_PM, 10, 0),
+ SND_SOC_DAPM_PGA("HeadPhone Driver", UDA1380_PM, 13, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* output mux */
+ {"HeadPhone Driver", NULL, "Output Mux"},
+ {"VOUTR", NULL, "Output Mux"},
+ {"VOUTL", NULL, "Output Mux"},
+
+ {"Analog Mixer", NULL, "VINR"},
+ {"Analog Mixer", NULL, "VINL"},
+ {"Analog Mixer", NULL, "DAC"},
+
+ {"Output Mux", "DAC", "DAC"},
+ {"Output Mux", "Analog Mixer", "Analog Mixer"},
+
+ /* {"DAC", "Digital Mixer", "I2S" } */
+
+ /* headphone driver */
+ {"VOUTLHP", NULL, "HeadPhone Driver"},
+ {"VOUTRHP", NULL, "HeadPhone Driver"},
+
+ /* input mux */
+ {"Left ADC", NULL, "Input Mux"},
+ {"Input Mux", "Mic", "Mic LNA"},
+ {"Input Mux", "Mic + Line R", "Mic LNA"},
+ {"Input Mux", "Line L", "Left PGA"},
+ {"Input Mux", "Line", "Left PGA"},
+
+ /* right input */
+ {"Right ADC", "Mic + Line R", "Right PGA"},
+ {"Right ADC", "Line", "Right PGA"},
+
+ /* inputs */
+ {"Mic LNA", NULL, "VINM"},
+ {"Left PGA", NULL, "VINL"},
+ {"Right PGA", NULL, "VINR"},
+};
+
+static int uda1380_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, uda1380_dapm_widgets,
+ ARRAY_SIZE(uda1380_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+static int uda1380_set_dai_fmt_both(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int iface;
+
+ /* set up DAI based upon fmt */
+ iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
+ iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK);
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= R01_SFORI_I2S | R01_SFORO_I2S;
+ break;
+ case SND_SOC_DAIFMT_LSB:
+ iface |= R01_SFORI_LSB16 | R01_SFORO_LSB16;
+ break;
+ case SND_SOC_DAIFMT_MSB:
+ iface |= R01_SFORI_MSB | R01_SFORO_MSB;
+ }
+
+ /* DATAI is slave only, so in single-link mode, this has to be slave */
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+ return -EINVAL;
+
+ uda1380_write(codec, UDA1380_IFACE, iface);
+
+ return 0;
+}
+
+static int uda1380_set_dai_fmt_playback(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int iface;
+
+ /* set up DAI based upon fmt */
+ iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
+ iface &= ~R01_SFORI_MASK;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= R01_SFORI_I2S;
+ break;
+ case SND_SOC_DAIFMT_LSB:
+ iface |= R01_SFORI_LSB16;
+ break;
+ case SND_SOC_DAIFMT_MSB:
+ iface |= R01_SFORI_MSB;
+ }
+
+ /* DATAI is slave only, so this has to be slave */
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+ return -EINVAL;
+
+ uda1380_write(codec, UDA1380_IFACE, iface);
+
+ return 0;
+}
+
+static int uda1380_set_dai_fmt_capture(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int iface;
+
+ /* set up DAI based upon fmt */
+ iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
+ iface &= ~(R01_SIM | R01_SFORO_MASK);
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= R01_SFORO_I2S;
+ break;
+ case SND_SOC_DAIFMT_LSB:
+ iface |= R01_SFORO_LSB16;
+ break;
+ case SND_SOC_DAIFMT_MSB:
+ iface |= R01_SFORO_MSB;
+ }
+
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM)
+ iface |= R01_SIM;
+
+ uda1380_write(codec, UDA1380_IFACE, iface);
+
+ return 0;
+}
+
+static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct uda1380_priv *uda1380 = snd_soc_codec_get_drvdata(codec);
+ int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ uda1380_write_reg_cache(codec, UDA1380_MIXER,
+ mixer & ~R14_SILENCE);
+ schedule_work(&uda1380->work);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ uda1380_write_reg_cache(codec, UDA1380_MIXER,
+ mixer | R14_SILENCE);
+ schedule_work(&uda1380->work);
+ break;
+ }
+ return 0;
+}
+
+static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+
+ /* set WSPLL power and divider if running from this clock */
+ if (clk & R00_DAC_CLK) {
+ int rate = params_rate(params);
+ u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+ clk &= ~0x3; /* clear SEL_LOOP_DIV */
+ switch (rate) {
+ case 6250 ... 12500:
+ clk |= 0x0;
+ break;
+ case 12501 ... 25000:
+ clk |= 0x1;
+ break;
+ case 25001 ... 50000:
+ clk |= 0x2;
+ break;
+ case 50001 ... 100000:
+ clk |= 0x3;
+ break;
+ }
+ uda1380_write(codec, UDA1380_PM, R02_PON_PLL | pm);
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ clk |= R00_EN_DAC | R00_EN_INT;
+ else
+ clk |= R00_EN_ADC | R00_EN_DEC;
+
+ uda1380_write(codec, UDA1380_CLK, clk);
+ return 0;
+}
+
+static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+
+ /* shut down WSPLL power if running from this clock */
+ if (clk & R00_DAC_CLK) {
+ u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+ uda1380_write(codec, UDA1380_PM, ~R02_PON_PLL & pm);
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ clk &= ~(R00_EN_DAC | R00_EN_INT);
+ else
+ clk &= ~(R00_EN_ADC | R00_EN_DEC);
+
+ uda1380_write(codec, UDA1380_CLK, clk);
+}
+
+static int uda1380_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+ int reg;
+ struct uda1380_platform_data *pdata = codec->dev->platform_data;
+
+ if (codec->dapm.bias_level == level)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ /* ADC, DAC on */
+ uda1380_write(codec, UDA1380_PM, R02_PON_BIAS | pm);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ if (gpio_is_valid(pdata->gpio_power)) {
+ gpio_set_value(pdata->gpio_power, 1);
+ mdelay(1);
+ uda1380_reset(codec);
+ }
+
+ uda1380_sync_cache(codec);
+ }
+ uda1380_write(codec, UDA1380_PM, 0x0);
+ break;
+ case SND_SOC_BIAS_OFF:
+ if (!gpio_is_valid(pdata->gpio_power))
+ break;
+
+ gpio_set_value(pdata->gpio_power, 0);
+
+ /* Mark mixer regs cache dirty to sync them with
+ * codec regs on power on.
+ */
+ for (reg = UDA1380_MVOL; reg < UDA1380_CACHEREGNUM; reg++)
+ set_bit(reg - 0x10, &uda1380_cache_dirty);
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define UDA1380_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops uda1380_dai_ops = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .trigger = uda1380_trigger,
+ .set_fmt = uda1380_set_dai_fmt_both,
+};
+
+static struct snd_soc_dai_ops uda1380_dai_ops_playback = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .trigger = uda1380_trigger,
+ .set_fmt = uda1380_set_dai_fmt_playback,
+};
+
+static struct snd_soc_dai_ops uda1380_dai_ops_capture = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .trigger = uda1380_trigger,
+ .set_fmt = uda1380_set_dai_fmt_capture,
+};
+
+static struct snd_soc_dai_driver uda1380_dai[] = {
+{
+ .name = "uda1380-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &uda1380_dai_ops,
+},
+{ /* playback only - dual interface */
+ .name = "uda1380-hifi-playback",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &uda1380_dai_ops_playback,
+},
+{ /* capture only - dual interface*/
+ .name = "uda1380-hifi-capture",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &uda1380_dai_ops_capture,
+},
+};
+
+static int uda1380_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int uda1380_resume(struct snd_soc_codec *codec)
+{
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+static int uda1380_probe(struct snd_soc_codec *codec)
+{
+ struct uda1380_platform_data *pdata =codec->dev->platform_data;
+ struct uda1380_priv *uda1380 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ uda1380->codec = codec;
+
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ codec->control_data = uda1380->control_data;
+
+ if (!pdata)
+ return -EINVAL;
+
+ if (gpio_is_valid(pdata->gpio_reset)) {
+ ret = gpio_request(pdata->gpio_reset, "uda1380 reset");
+ if (ret)
+ goto err_out;
+ ret = gpio_direction_output(pdata->gpio_reset, 0);
+ if (ret)
+ goto err_gpio_reset_conf;
+ }
+
+ if (gpio_is_valid(pdata->gpio_power)) {
+ ret = gpio_request(pdata->gpio_power, "uda1380 power");
+ if (ret)
+ goto err_gpio;
+ ret = gpio_direction_output(pdata->gpio_power, 0);
+ if (ret)
+ goto err_gpio_power_conf;
+ } else {
+ ret = uda1380_reset(codec);
+ if (ret) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ goto err_reset;
+ }
+ }
+
+ INIT_WORK(&uda1380->work, uda1380_flush_work);
+
+ /* power on device */
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ /* set clock input */
+ switch (pdata->dac_clk) {
+ case UDA1380_DAC_CLK_SYSCLK:
+ uda1380_write_reg_cache(codec, UDA1380_CLK, 0);
+ break;
+ case UDA1380_DAC_CLK_WSPLL:
+ uda1380_write_reg_cache(codec, UDA1380_CLK,
+ R00_DAC_CLK);
+ break;
+ }
+
+ snd_soc_add_controls(codec, uda1380_snd_controls,
+ ARRAY_SIZE(uda1380_snd_controls));
+ uda1380_add_widgets(codec);
+
+ return 0;
+
+err_reset:
+err_gpio_power_conf:
+ if (gpio_is_valid(pdata->gpio_power))
+ gpio_free(pdata->gpio_power);
+
+err_gpio_reset_conf:
+err_gpio:
+ if (gpio_is_valid(pdata->gpio_reset))
+ gpio_free(pdata->gpio_reset);
+err_out:
+ return ret;
+}
+
+/* power down chip */
+static int uda1380_remove(struct snd_soc_codec *codec)
+{
+ struct uda1380_platform_data *pdata =codec->dev->platform_data;
+
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ gpio_free(pdata->gpio_reset);
+ gpio_free(pdata->gpio_power);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_uda1380 = {
+ .probe = uda1380_probe,
+ .remove = uda1380_remove,
+ .suspend = uda1380_suspend,
+ .resume = uda1380_resume,
+ .read = uda1380_read_reg_cache,
+ .write = uda1380_write,
+ .set_bias_level = uda1380_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(uda1380_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = uda1380_reg,
+ .reg_cache_step = 1,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int uda1380_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct uda1380_priv *uda1380;
+ int ret;
+
+ uda1380 = kzalloc(sizeof(struct uda1380_priv), GFP_KERNEL);
+ if (uda1380 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, uda1380);
+ uda1380->control_data = i2c;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_uda1380, uda1380_dai, ARRAY_SIZE(uda1380_dai));
+ if (ret < 0)
+ kfree(uda1380);
+ return ret;
+}
+
+static int __devexit uda1380_i2c_remove(struct i2c_client *i2c)
+{
+ snd_soc_unregister_codec(&i2c->dev);
+ kfree(i2c_get_clientdata(i2c));
+ return 0;
+}
+
+static const struct i2c_device_id uda1380_i2c_id[] = {
+ { "uda1380", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id);
+
+static struct i2c_driver uda1380_i2c_driver = {
+ .driver = {
+ .name = "uda1380-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = uda1380_i2c_probe,
+ .remove = __devexit_p(uda1380_i2c_remove),
+ .id_table = uda1380_i2c_id,
+};
+#endif
+
+static int __init uda1380_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&uda1380_i2c_driver);
+ if (ret != 0)
+ pr_err("Failed to register UDA1380 I2C driver: %d\n", ret);
+#endif
+ return 0;
+}
+module_init(uda1380_modinit);
+
+static void __exit uda1380_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&uda1380_i2c_driver);
+#endif
+}
+module_exit(uda1380_exit);
+
+MODULE_AUTHOR("Giorgio Padrin");
+MODULE_DESCRIPTION("Audio support for codec Philips UDA1380");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h
new file mode 100644
index 00000000..942e3927
--- /dev/null
+++ b/sound/soc/codecs/uda1380.h
@@ -0,0 +1,79 @@
+/*
+ * Audio support for Philips UDA1380
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org>
+ */
+
+#ifndef _UDA1380_H
+#define _UDA1380_H
+
+#define UDA1380_CLK 0x00
+#define UDA1380_IFACE 0x01
+#define UDA1380_PM 0x02
+#define UDA1380_AMIX 0x03
+#define UDA1380_HP 0x04
+#define UDA1380_MVOL 0x10
+#define UDA1380_MIXVOL 0x11
+#define UDA1380_MODE 0x12
+#define UDA1380_DEEMP 0x13
+#define UDA1380_MIXER 0x14
+#define UDA1380_INTSTAT 0x18
+#define UDA1380_DEC 0x20
+#define UDA1380_PGA 0x21
+#define UDA1380_ADC 0x22
+#define UDA1380_AGC 0x23
+#define UDA1380_DECSTAT 0x28
+#define UDA1380_RESET 0x7f
+
+#define UDA1380_CACHEREGNUM 0x24
+
+/* Register flags */
+#define R00_EN_ADC 0x0800
+#define R00_EN_DEC 0x0400
+#define R00_EN_DAC 0x0200
+#define R00_EN_INT 0x0100
+#define R00_DAC_CLK 0x0010
+#define R01_SFORI_I2S 0x0000
+#define R01_SFORI_LSB16 0x0100
+#define R01_SFORI_LSB18 0x0200
+#define R01_SFORI_LSB20 0x0300
+#define R01_SFORI_MSB 0x0500
+#define R01_SFORI_MASK 0x0700
+#define R01_SFORO_I2S 0x0000
+#define R01_SFORO_LSB16 0x0001
+#define R01_SFORO_LSB18 0x0002
+#define R01_SFORO_LSB20 0x0003
+#define R01_SFORO_LSB24 0x0004
+#define R01_SFORO_MSB 0x0005
+#define R01_SFORO_MASK 0x0007
+#define R01_SEL_SOURCE 0x0040
+#define R01_SIM 0x0010
+#define R02_PON_PLL 0x8000
+#define R02_PON_HP 0x2000
+#define R02_PON_DAC 0x0400
+#define R02_PON_BIAS 0x0100
+#define R02_EN_AVC 0x0080
+#define R02_PON_AVC 0x0040
+#define R02_PON_LNA 0x0010
+#define R02_PON_PGAL 0x0008
+#define R02_PON_ADCL 0x0004
+#define R02_PON_PGAR 0x0002
+#define R02_PON_ADCR 0x0001
+#define R13_MTM 0x4000
+#define R14_SILENCE 0x0080
+#define R14_SDET_ON 0x0040
+#define R21_MT_ADC 0x8000
+#define R22_SEL_LNA 0x0008
+#define R22_SEL_MIC 0x0004
+#define R22_SKIP_DCFIL 0x0002
+#define R23_AGC_EN 0x0001
+
+#define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */
+#define UDA1380_DAI_PLAYBACK 1 /* playback DAI */
+#define UDA1380_DAI_CAPTURE 2 /* capture DAI */
+
+#endif /* _UDA1380_H */
diff --git a/sound/soc/codecs/wl1273.c b/sound/soc/codecs/wl1273.c
new file mode 100644
index 00000000..58362018
--- /dev/null
+++ b/sound/soc/codecs/wl1273.c
@@ -0,0 +1,527 @@
+/*
+ * ALSA SoC WL1273 codec driver
+ *
+ * Author: Matti Aaltonen, <matti.j.aaltonen@nokia.com>
+ *
+ * Copyright: (C) 2010, 2011 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/mfd/wl1273-core.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "wl1273.h"
+
+enum wl1273_mode { WL1273_MODE_BT, WL1273_MODE_FM_RX, WL1273_MODE_FM_TX };
+
+/* codec private data */
+struct wl1273_priv {
+ enum wl1273_mode mode;
+ struct wl1273_core *core;
+ unsigned int channels;
+};
+
+static int snd_wl1273_fm_set_i2s_mode(struct wl1273_core *core,
+ int rate, int width)
+{
+ struct device *dev = &core->client->dev;
+ int r = 0;
+ u16 mode;
+
+ dev_dbg(dev, "rate: %d\n", rate);
+ dev_dbg(dev, "width: %d\n", width);
+
+ mutex_lock(&core->lock);
+
+ mode = core->i2s_mode & ~WL1273_IS2_WIDTH & ~WL1273_IS2_RATE;
+
+ switch (rate) {
+ case 48000:
+ mode |= WL1273_IS2_RATE_48K;
+ break;
+ case 44100:
+ mode |= WL1273_IS2_RATE_44_1K;
+ break;
+ case 32000:
+ mode |= WL1273_IS2_RATE_32K;
+ break;
+ case 22050:
+ mode |= WL1273_IS2_RATE_22_05K;
+ break;
+ case 16000:
+ mode |= WL1273_IS2_RATE_16K;
+ break;
+ case 12000:
+ mode |= WL1273_IS2_RATE_12K;
+ break;
+ case 11025:
+ mode |= WL1273_IS2_RATE_11_025;
+ break;
+ case 8000:
+ mode |= WL1273_IS2_RATE_8K;
+ break;
+ default:
+ dev_err(dev, "Sampling rate: %d not supported\n", rate);
+ r = -EINVAL;
+ goto out;
+ }
+
+ switch (width) {
+ case 16:
+ mode |= WL1273_IS2_WIDTH_32;
+ break;
+ case 20:
+ mode |= WL1273_IS2_WIDTH_40;
+ break;
+ case 24:
+ mode |= WL1273_IS2_WIDTH_48;
+ break;
+ case 25:
+ mode |= WL1273_IS2_WIDTH_50;
+ break;
+ case 30:
+ mode |= WL1273_IS2_WIDTH_60;
+ break;
+ case 32:
+ mode |= WL1273_IS2_WIDTH_64;
+ break;
+ case 40:
+ mode |= WL1273_IS2_WIDTH_80;
+ break;
+ case 48:
+ mode |= WL1273_IS2_WIDTH_96;
+ break;
+ case 64:
+ mode |= WL1273_IS2_WIDTH_128;
+ break;
+ default:
+ dev_err(dev, "Data width: %d not supported\n", width);
+ r = -EINVAL;
+ goto out;
+ }
+
+ dev_dbg(dev, "WL1273_I2S_DEF_MODE: 0x%04x\n", WL1273_I2S_DEF_MODE);
+ dev_dbg(dev, "core->i2s_mode: 0x%04x\n", core->i2s_mode);
+ dev_dbg(dev, "mode: 0x%04x\n", mode);
+
+ if (core->i2s_mode != mode) {
+ r = core->write(core, WL1273_I2S_MODE_CONFIG_SET, mode);
+ if (r)
+ goto out;
+
+ core->i2s_mode = mode;
+ r = core->write(core, WL1273_AUDIO_ENABLE,
+ WL1273_AUDIO_ENABLE_I2S);
+ if (r)
+ goto out;
+ }
+out:
+ mutex_unlock(&core->lock);
+
+ return r;
+}
+
+static int snd_wl1273_fm_set_channel_number(struct wl1273_core *core,
+ int channel_number)
+{
+ struct device *dev = &core->client->dev;
+ int r = 0;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ mutex_lock(&core->lock);
+
+ if (core->channel_number == channel_number)
+ goto out;
+
+ if (channel_number == 1 && core->mode == WL1273_MODE_RX)
+ r = core->write(core, WL1273_MOST_MODE_SET, WL1273_RX_MONO);
+ else if (channel_number == 1 && core->mode == WL1273_MODE_TX)
+ r = core->write(core, WL1273_MONO_SET, WL1273_TX_MONO);
+ else if (channel_number == 2 && core->mode == WL1273_MODE_RX)
+ r = core->write(core, WL1273_MOST_MODE_SET, WL1273_RX_STEREO);
+ else if (channel_number == 2 && core->mode == WL1273_MODE_TX)
+ r = core->write(core, WL1273_MONO_SET, WL1273_TX_STEREO);
+ else
+ r = -EINVAL;
+out:
+ mutex_unlock(&core->lock);
+
+ return r;
+}
+
+static int snd_wl1273_get_audio_route(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = wl1273->mode;
+
+ return 0;
+}
+
+/*
+ * TODO: Implement the audio routing in the driver. Now this control
+ * only indicates the setting that has been done elsewhere (in the user
+ * space).
+ */
+static const char * const wl1273_audio_route[] = { "Bt", "FmRx", "FmTx" };
+
+static int snd_wl1273_set_audio_route(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+ if (wl1273->mode == ucontrol->value.integer.value[0])
+ return 0;
+
+ /* Do not allow changes while stream is running */
+ if (codec->active)
+ return -EPERM;
+
+ if (ucontrol->value.integer.value[0] < 0 ||
+ ucontrol->value.integer.value[0] >= ARRAY_SIZE(wl1273_audio_route))
+ return -EINVAL;
+
+ wl1273->mode = ucontrol->value.integer.value[0];
+
+ return 1;
+}
+
+static const struct soc_enum wl1273_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wl1273_audio_route), wl1273_audio_route);
+
+static int snd_wl1273_fm_audio_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+ dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+ ucontrol->value.integer.value[0] = wl1273->core->audio_mode;
+
+ return 0;
+}
+
+static int snd_wl1273_fm_audio_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+ int val, r = 0;
+
+ dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+ val = ucontrol->value.integer.value[0];
+ if (wl1273->core->audio_mode == val)
+ return 0;
+
+ r = wl1273->core->set_audio(wl1273->core, val);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static const char * const wl1273_audio_strings[] = { "Digital", "Analog" };
+
+static const struct soc_enum wl1273_audio_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wl1273_audio_strings),
+ wl1273_audio_strings);
+
+static int snd_wl1273_fm_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+ dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+ ucontrol->value.integer.value[0] = wl1273->core->volume;
+
+ return 0;
+}
+
+static int snd_wl1273_fm_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+ int r;
+
+ dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+ r = wl1273->core->set_volume(wl1273->core,
+ ucontrol->value.integer.value[0]);
+ if (r)
+ return r;
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new wl1273_controls[] = {
+ SOC_ENUM_EXT("Codec Mode", wl1273_enum,
+ snd_wl1273_get_audio_route, snd_wl1273_set_audio_route),
+ SOC_ENUM_EXT("Audio Switch", wl1273_audio_enum,
+ snd_wl1273_fm_audio_get, snd_wl1273_fm_audio_put),
+ SOC_SINGLE_EXT("Volume", 0, 0, WL1273_MAX_VOLUME, 0,
+ snd_wl1273_fm_volume_get, snd_wl1273_fm_volume_put),
+};
+
+static int wl1273_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+ switch (wl1273->mode) {
+ case WL1273_MODE_BT:
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ 8000, 8000);
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 1, 1);
+ break;
+ case WL1273_MODE_FM_RX:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ pr_err("Cannot play in RX mode.\n");
+ return -EINVAL;
+ }
+ break;
+ case WL1273_MODE_FM_TX:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ pr_err("Cannot capture in TX mode.\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+
+ return 0;
+}
+
+static int wl1273_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(rtd->codec);
+ struct wl1273_core *core = wl1273->core;
+ unsigned int rate, width, r;
+
+ if (params_format(params) != SNDRV_PCM_FORMAT_S16_LE) {
+ pr_err("Only SNDRV_PCM_FORMAT_S16_LE supported.\n");
+ return -EINVAL;
+ }
+
+ rate = params_rate(params);
+ width = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
+
+ if (wl1273->mode == WL1273_MODE_BT) {
+ if (rate != 8000) {
+ pr_err("Rate %d not supported.\n", params_rate(params));
+ return -EINVAL;
+ }
+
+ if (params_channels(params) != 1) {
+ pr_err("Only mono supported.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ if (wl1273->mode == WL1273_MODE_FM_TX &&
+ substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ pr_err("Only playback supported with TX.\n");
+ return -EINVAL;
+ }
+
+ if (wl1273->mode == WL1273_MODE_FM_RX &&
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ pr_err("Only capture supported with RX.\n");
+ return -EINVAL;
+ }
+
+ if (wl1273->mode != WL1273_MODE_FM_RX &&
+ wl1273->mode != WL1273_MODE_FM_TX) {
+ pr_err("Unexpected mode: %d.\n", wl1273->mode);
+ return -EINVAL;
+ }
+
+ r = snd_wl1273_fm_set_i2s_mode(core, rate, width);
+ if (r)
+ return r;
+
+ wl1273->channels = params_channels(params);
+ r = snd_wl1273_fm_set_channel_number(core, wl1273->channels);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops wl1273_dai_ops = {
+ .startup = wl1273_startup,
+ .hw_params = wl1273_hw_params,
+};
+
+static struct snd_soc_dai_driver wl1273_dai = {
+ .name = "wl1273-fm",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE},
+ .ops = &wl1273_dai_ops,
+};
+
+/* Audio interface format for the soc_card driver */
+int wl1273_get_format(struct snd_soc_codec *codec, unsigned int *fmt)
+{
+ struct wl1273_priv *wl1273;
+
+ if (codec == NULL || fmt == NULL)
+ return -EINVAL;
+
+ wl1273 = snd_soc_codec_get_drvdata(codec);
+
+ switch (wl1273->mode) {
+ case WL1273_MODE_FM_RX:
+ case WL1273_MODE_FM_TX:
+ *fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ break;
+ case WL1273_MODE_BT:
+ *fmt = SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wl1273_get_format);
+
+static int wl1273_probe(struct snd_soc_codec *codec)
+{
+ struct wl1273_core **core = codec->dev->platform_data;
+ struct wl1273_priv *wl1273;
+ int r;
+
+ dev_dbg(codec->dev, "%s.\n", __func__);
+
+ if (!core) {
+ dev_err(codec->dev, "Platform data is missing.\n");
+ return -EINVAL;
+ }
+
+ wl1273 = kzalloc(sizeof(struct wl1273_priv), GFP_KERNEL);
+ if (wl1273 == NULL) {
+ dev_err(codec->dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ wl1273->mode = WL1273_MODE_BT;
+ wl1273->core = *core;
+
+ snd_soc_codec_set_drvdata(codec, wl1273);
+ mutex_init(&codec->mutex);
+
+ r = snd_soc_add_controls(codec, wl1273_controls,
+ ARRAY_SIZE(wl1273_controls));
+ if (r)
+ kfree(wl1273);
+
+ return r;
+}
+
+static int wl1273_remove(struct snd_soc_codec *codec)
+{
+ struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+ dev_dbg(codec->dev, "%s\n", __func__);
+ kfree(wl1273);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wl1273 = {
+ .probe = wl1273_probe,
+ .remove = wl1273_remove,
+};
+
+static int __devinit wl1273_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wl1273,
+ &wl1273_dai, 1);
+}
+
+static int __devexit wl1273_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+MODULE_ALIAS("platform:wl1273-codec");
+
+static struct platform_driver wl1273_platform_driver = {
+ .driver = {
+ .name = "wl1273-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wl1273_platform_probe,
+ .remove = __devexit_p(wl1273_platform_remove),
+};
+
+static int __init wl1273_init(void)
+{
+ return platform_driver_register(&wl1273_platform_driver);
+}
+module_init(wl1273_init);
+
+static void __exit wl1273_exit(void)
+{
+ platform_driver_unregister(&wl1273_platform_driver);
+}
+module_exit(wl1273_exit);
+
+MODULE_AUTHOR("Matti Aaltonen <matti.j.aaltonen@nokia.com>");
+MODULE_DESCRIPTION("ASoC WL1273 codec driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wl1273.h b/sound/soc/codecs/wl1273.h
new file mode 100644
index 00000000..43ec7e66
--- /dev/null
+++ b/sound/soc/codecs/wl1273.h
@@ -0,0 +1,30 @@
+/*
+ * sound/soc/codec/wl1273.h
+ *
+ * ALSA SoC WL1273 codec driver
+ *
+ * Copyright (C) Nokia Corporation
+ * Author: Matti Aaltonen <matti.j.aaltonen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __WL1273_CODEC_H__
+#define __WL1273_CODEC_H__
+
+int wl1273_get_format(struct snd_soc_codec *codec, unsigned int *fmt);
+
+#endif /* End of __WL1273_CODEC_H__ */
diff --git a/sound/soc/codecs/wm1250-ev1.c b/sound/soc/codecs/wm1250-ev1.c
new file mode 100644
index 00000000..bcc20896
--- /dev/null
+++ b/sound/soc/codecs/wm1250-ev1.c
@@ -0,0 +1,108 @@
+/*
+ * Driver for the 1250-EV1 audio I/O module
+ *
+ * Copyright 2011 Wolfson Microelectronics plc
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+static const struct snd_soc_dapm_widget wm1250_ev1_dapm_widgets[] = {
+SND_SOC_DAPM_ADC("ADC", "wm1250-ev1 Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_DAC("DAC", "wm1250-ev1 Playback", SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_INPUT("WM1250 Input"),
+SND_SOC_DAPM_OUTPUT("WM1250 Output"),
+};
+
+static const struct snd_soc_dapm_route wm1250_ev1_dapm_routes[] = {
+ { "ADC", NULL, "WM1250 Input" },
+ { "WM1250 Output", NULL, "DAC" },
+};
+
+static struct snd_soc_dai_driver wm1250_ev1_dai = {
+ .name = "wm1250-ev1",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm1250_ev1 = {
+ .dapm_widgets = wm1250_ev1_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm1250_ev1_dapm_widgets),
+ .dapm_routes = wm1250_ev1_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(wm1250_ev1_dapm_routes),
+};
+
+static int __devinit wm1250_ev1_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ return snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm1250_ev1,
+ &wm1250_ev1_dai, 1);
+}
+
+static int __devexit wm1250_ev1_remove(struct i2c_client *i2c)
+{
+ snd_soc_unregister_codec(&i2c->dev);
+
+ return 0;
+}
+
+static const struct i2c_device_id wm1250_ev1_i2c_id[] = {
+ { "wm1250-ev1", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm1250_ev1_i2c_id);
+
+static struct i2c_driver wm1250_ev1_i2c_driver = {
+ .driver = {
+ .name = "wm1250-ev1",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm1250_ev1_probe,
+ .remove = __devexit_p(wm1250_ev1_remove),
+ .id_table = wm1250_ev1_i2c_id,
+};
+
+static int __init wm1250_ev1_modinit(void)
+{
+ int ret = 0;
+
+ ret = i2c_add_driver(&wm1250_ev1_i2c_driver);
+ if (ret != 0)
+ pr_err("Failed to register WM1250-EV1 I2C driver: %d\n", ret);
+
+ return ret;
+}
+module_init(wm1250_ev1_modinit);
+
+static void __exit wm1250_ev1_exit(void)
+{
+ i2c_del_driver(&wm1250_ev1_i2c_driver);
+}
+module_exit(wm1250_ev1_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM1250-EV1 audio I/O module driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c
new file mode 100644
index 00000000..a3b9cbb2
--- /dev/null
+++ b/sound/soc/codecs/wm2000.c
@@ -0,0 +1,890 @@
+/*
+ * wm2000.c -- WM2000 ALSA Soc Audio driver
+ *
+ * Copyright 2008-2010 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * The download image for the WM2000 will be requested as
+ * 'wm2000_anc.bin' by default (overridable via platform data) at
+ * runtime and is expected to be in flat binary format. This is
+ * generated by Wolfson configuration tools and includes
+ * system-specific callibration information. If supplied as a
+ * sequence of ASCII-encoded hexidecimal bytes this can be converted
+ * into a flat binary with a command such as this on the command line:
+ *
+ * perl -e 'while (<>) { s/[\r\n]+// ; printf("%c", hex($_)); }'
+ * < file > wm2000_anc.bin
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <sound/wm2000.h>
+
+#include "wm2000.h"
+
+enum wm2000_anc_mode {
+ ANC_ACTIVE = 0,
+ ANC_BYPASS = 1,
+ ANC_STANDBY = 2,
+ ANC_OFF = 3,
+};
+
+struct wm2000_priv {
+ struct i2c_client *i2c;
+
+ enum wm2000_anc_mode anc_mode;
+
+ unsigned int anc_active:1;
+ unsigned int anc_eng_ena:1;
+ unsigned int spk_ena:1;
+
+ unsigned int mclk_div:1;
+ unsigned int speech_clarity:1;
+
+ int anc_download_size;
+ char *anc_download;
+};
+
+static struct i2c_client *wm2000_i2c;
+
+static int wm2000_write(struct i2c_client *i2c, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+ int ret;
+
+ data[0] = (reg >> 8) & 0xff;
+ data[1] = reg & 0xff;
+ data[2] = value & 0xff;
+
+ dev_vdbg(&i2c->dev, "write %x = %x\n", reg, value);
+
+ ret = i2c_master_send(i2c, data, 3);
+ if (ret == 3)
+ return 0;
+ if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+}
+
+static unsigned int wm2000_read(struct i2c_client *i2c, unsigned int r)
+{
+ struct i2c_msg xfer[2];
+ u8 reg[2];
+ u8 data;
+ int ret;
+
+ /* Write register */
+ reg[0] = (r >> 8) & 0xff;
+ reg[1] = r & 0xff;
+ xfer[0].addr = i2c->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = sizeof(reg);
+ xfer[0].buf = &reg[0];
+
+ /* Read data */
+ xfer[1].addr = i2c->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = 1;
+ xfer[1].buf = &data;
+
+ ret = i2c_transfer(i2c->adapter, xfer, 2);
+ if (ret != 2) {
+ dev_err(&i2c->dev, "i2c_transfer() returned %d\n", ret);
+ return 0;
+ }
+
+ dev_vdbg(&i2c->dev, "read %x from %x\n", data, r);
+
+ return data;
+}
+
+static void wm2000_reset(struct wm2000_priv *wm2000)
+{
+ struct i2c_client *i2c = wm2000->i2c;
+
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR);
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR);
+ wm2000_write(i2c, WM2000_REG_ID1, 0);
+
+ wm2000->anc_mode = ANC_OFF;
+}
+
+static int wm2000_poll_bit(struct i2c_client *i2c,
+ unsigned int reg, u8 mask, int timeout)
+{
+ int val;
+
+ val = wm2000_read(i2c, reg);
+
+ while (!(val & mask) && --timeout) {
+ msleep(1);
+ val = wm2000_read(i2c, reg);
+ }
+
+ if (timeout == 0)
+ return 0;
+ else
+ return 1;
+}
+
+static int wm2000_power_up(struct i2c_client *i2c, int analogue)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+ int ret, timeout;
+
+ BUG_ON(wm2000->anc_mode != ANC_OFF);
+
+ dev_dbg(&i2c->dev, "Beginning power up\n");
+
+ if (!wm2000->mclk_div) {
+ dev_dbg(&i2c->dev, "Disabling MCLK divider\n");
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2,
+ WM2000_MCLK_DIV2_ENA_CLR);
+ } else {
+ dev_dbg(&i2c->dev, "Enabling MCLK divider\n");
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2,
+ WM2000_MCLK_DIV2_ENA_SET);
+ }
+
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR);
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_SET);
+
+ /* Wait for ANC engine to become ready */
+ if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT,
+ WM2000_ANC_ENG_IDLE, 1)) {
+ dev_err(&i2c->dev, "ANC engine failed to reset\n");
+ return -ETIMEDOUT;
+ }
+
+ if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+ WM2000_STATUS_BOOT_COMPLETE, 1)) {
+ dev_err(&i2c->dev, "ANC engine failed to initialise\n");
+ return -ETIMEDOUT;
+ }
+
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET);
+
+ /* Open code download of the data since it is the only bulk
+ * write we do. */
+ dev_dbg(&i2c->dev, "Downloading %d bytes\n",
+ wm2000->anc_download_size - 2);
+
+ ret = i2c_master_send(i2c, wm2000->anc_download,
+ wm2000->anc_download_size);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "i2c_transfer() failed: %d\n", ret);
+ return ret;
+ }
+ if (ret != wm2000->anc_download_size) {
+ dev_err(&i2c->dev, "i2c_transfer() failed, %d != %d\n",
+ ret, wm2000->anc_download_size);
+ return -EIO;
+ }
+
+ dev_dbg(&i2c->dev, "Download complete\n");
+
+ if (analogue) {
+ timeout = 248;
+ wm2000_write(i2c, WM2000_REG_ANA_VMID_PU_TIME, timeout / 4);
+
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_ANA_SEQ_INCLUDE |
+ WM2000_MODE_MOUSE_ENABLE |
+ WM2000_MODE_THERMAL_ENABLE);
+ } else {
+ timeout = 10;
+
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_MOUSE_ENABLE |
+ WM2000_MODE_THERMAL_ENABLE);
+ }
+
+ ret = wm2000_read(i2c, WM2000_REG_SPEECH_CLARITY);
+ if (wm2000->speech_clarity)
+ ret &= ~WM2000_SPEECH_CLARITY;
+ else
+ ret |= WM2000_SPEECH_CLARITY;
+ wm2000_write(i2c, WM2000_REG_SPEECH_CLARITY, ret);
+
+ wm2000_write(i2c, WM2000_REG_SYS_START0, 0x33);
+ wm2000_write(i2c, WM2000_REG_SYS_START1, 0x02);
+
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR);
+
+ if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+ WM2000_STATUS_MOUSE_ACTIVE, timeout)) {
+ dev_err(&i2c->dev, "Timed out waiting for device after %dms\n",
+ timeout * 10);
+ return -ETIMEDOUT;
+ }
+
+ dev_dbg(&i2c->dev, "ANC active\n");
+ if (analogue)
+ dev_dbg(&i2c->dev, "Analogue active\n");
+ wm2000->anc_mode = ANC_ACTIVE;
+
+ return 0;
+}
+
+static int wm2000_power_down(struct i2c_client *i2c, int analogue)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+ int timeout;
+
+ if (analogue) {
+ timeout = 248;
+ wm2000_write(i2c, WM2000_REG_ANA_VMID_PD_TIME, timeout / 4);
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_ANA_SEQ_INCLUDE |
+ WM2000_MODE_POWER_DOWN);
+ } else {
+ timeout = 10;
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_POWER_DOWN);
+ }
+
+ if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+ WM2000_STATUS_POWER_DOWN_COMPLETE, timeout)) {
+ dev_err(&i2c->dev, "Timeout waiting for ANC power down\n");
+ return -ETIMEDOUT;
+ }
+
+ if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT,
+ WM2000_ANC_ENG_IDLE, 1)) {
+ dev_err(&i2c->dev, "Timeout waiting for ANC engine idle\n");
+ return -ETIMEDOUT;
+ }
+
+ dev_dbg(&i2c->dev, "powered off\n");
+ wm2000->anc_mode = ANC_OFF;
+
+ return 0;
+}
+
+static int wm2000_enter_bypass(struct i2c_client *i2c, int analogue)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+ BUG_ON(wm2000->anc_mode != ANC_ACTIVE);
+
+ if (analogue) {
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_ANA_SEQ_INCLUDE |
+ WM2000_MODE_THERMAL_ENABLE |
+ WM2000_MODE_BYPASS_ENTRY);
+ } else {
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_THERMAL_ENABLE |
+ WM2000_MODE_BYPASS_ENTRY);
+ }
+
+ if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+ WM2000_STATUS_ANC_DISABLED, 10)) {
+ dev_err(&i2c->dev, "Timeout waiting for ANC disable\n");
+ return -ETIMEDOUT;
+ }
+
+ if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT,
+ WM2000_ANC_ENG_IDLE, 1)) {
+ dev_err(&i2c->dev, "Timeout waiting for ANC engine idle\n");
+ return -ETIMEDOUT;
+ }
+
+ wm2000_write(i2c, WM2000_REG_SYS_CTL1, WM2000_SYS_STBY);
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR);
+
+ wm2000->anc_mode = ANC_BYPASS;
+ dev_dbg(&i2c->dev, "bypass enabled\n");
+
+ return 0;
+}
+
+static int wm2000_exit_bypass(struct i2c_client *i2c, int analogue)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+ BUG_ON(wm2000->anc_mode != ANC_BYPASS);
+
+ wm2000_write(i2c, WM2000_REG_SYS_CTL1, 0);
+
+ if (analogue) {
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_ANA_SEQ_INCLUDE |
+ WM2000_MODE_MOUSE_ENABLE |
+ WM2000_MODE_THERMAL_ENABLE);
+ } else {
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_MOUSE_ENABLE |
+ WM2000_MODE_THERMAL_ENABLE);
+ }
+
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET);
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR);
+
+ if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+ WM2000_STATUS_MOUSE_ACTIVE, 10)) {
+ dev_err(&i2c->dev, "Timed out waiting for MOUSE\n");
+ return -ETIMEDOUT;
+ }
+
+ wm2000->anc_mode = ANC_ACTIVE;
+ dev_dbg(&i2c->dev, "MOUSE active\n");
+
+ return 0;
+}
+
+static int wm2000_enter_standby(struct i2c_client *i2c, int analogue)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+ int timeout;
+
+ BUG_ON(wm2000->anc_mode != ANC_ACTIVE);
+
+ if (analogue) {
+ timeout = 248;
+ wm2000_write(i2c, WM2000_REG_ANA_VMID_PD_TIME, timeout / 4);
+
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_ANA_SEQ_INCLUDE |
+ WM2000_MODE_THERMAL_ENABLE |
+ WM2000_MODE_STANDBY_ENTRY);
+ } else {
+ timeout = 10;
+
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_THERMAL_ENABLE |
+ WM2000_MODE_STANDBY_ENTRY);
+ }
+
+ if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+ WM2000_STATUS_ANC_DISABLED, timeout)) {
+ dev_err(&i2c->dev,
+ "Timed out waiting for ANC disable after 1ms\n");
+ return -ETIMEDOUT;
+ }
+
+ if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, WM2000_ANC_ENG_IDLE,
+ 1)) {
+ dev_err(&i2c->dev,
+ "Timed out waiting for standby after %dms\n",
+ timeout * 10);
+ return -ETIMEDOUT;
+ }
+
+ wm2000_write(i2c, WM2000_REG_SYS_CTL1, WM2000_SYS_STBY);
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR);
+
+ wm2000->anc_mode = ANC_STANDBY;
+ dev_dbg(&i2c->dev, "standby\n");
+ if (analogue)
+ dev_dbg(&i2c->dev, "Analogue disabled\n");
+
+ return 0;
+}
+
+static int wm2000_exit_standby(struct i2c_client *i2c, int analogue)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+ int timeout;
+
+ BUG_ON(wm2000->anc_mode != ANC_STANDBY);
+
+ wm2000_write(i2c, WM2000_REG_SYS_CTL1, 0);
+
+ if (analogue) {
+ timeout = 248;
+ wm2000_write(i2c, WM2000_REG_ANA_VMID_PU_TIME, timeout / 4);
+
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_ANA_SEQ_INCLUDE |
+ WM2000_MODE_THERMAL_ENABLE |
+ WM2000_MODE_MOUSE_ENABLE);
+ } else {
+ timeout = 10;
+
+ wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+ WM2000_MODE_THERMAL_ENABLE |
+ WM2000_MODE_MOUSE_ENABLE);
+ }
+
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET);
+ wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR);
+
+ if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+ WM2000_STATUS_MOUSE_ACTIVE, timeout)) {
+ dev_err(&i2c->dev, "Timed out waiting for MOUSE after %dms\n",
+ timeout * 10);
+ return -ETIMEDOUT;
+ }
+
+ wm2000->anc_mode = ANC_ACTIVE;
+ dev_dbg(&i2c->dev, "MOUSE active\n");
+ if (analogue)
+ dev_dbg(&i2c->dev, "Analogue enabled\n");
+
+ return 0;
+}
+
+typedef int (*wm2000_mode_fn)(struct i2c_client *i2c, int analogue);
+
+static struct {
+ enum wm2000_anc_mode source;
+ enum wm2000_anc_mode dest;
+ int analogue;
+ wm2000_mode_fn step[2];
+} anc_transitions[] = {
+ {
+ .source = ANC_OFF,
+ .dest = ANC_ACTIVE,
+ .analogue = 1,
+ .step = {
+ wm2000_power_up,
+ },
+ },
+ {
+ .source = ANC_OFF,
+ .dest = ANC_STANDBY,
+ .step = {
+ wm2000_power_up,
+ wm2000_enter_standby,
+ },
+ },
+ {
+ .source = ANC_OFF,
+ .dest = ANC_BYPASS,
+ .analogue = 1,
+ .step = {
+ wm2000_power_up,
+ wm2000_enter_bypass,
+ },
+ },
+ {
+ .source = ANC_ACTIVE,
+ .dest = ANC_BYPASS,
+ .analogue = 1,
+ .step = {
+ wm2000_enter_bypass,
+ },
+ },
+ {
+ .source = ANC_ACTIVE,
+ .dest = ANC_STANDBY,
+ .analogue = 1,
+ .step = {
+ wm2000_enter_standby,
+ },
+ },
+ {
+ .source = ANC_ACTIVE,
+ .dest = ANC_OFF,
+ .analogue = 1,
+ .step = {
+ wm2000_power_down,
+ },
+ },
+ {
+ .source = ANC_BYPASS,
+ .dest = ANC_ACTIVE,
+ .analogue = 1,
+ .step = {
+ wm2000_exit_bypass,
+ },
+ },
+ {
+ .source = ANC_BYPASS,
+ .dest = ANC_STANDBY,
+ .analogue = 1,
+ .step = {
+ wm2000_exit_bypass,
+ wm2000_enter_standby,
+ },
+ },
+ {
+ .source = ANC_BYPASS,
+ .dest = ANC_OFF,
+ .step = {
+ wm2000_exit_bypass,
+ wm2000_power_down,
+ },
+ },
+ {
+ .source = ANC_STANDBY,
+ .dest = ANC_ACTIVE,
+ .analogue = 1,
+ .step = {
+ wm2000_exit_standby,
+ },
+ },
+ {
+ .source = ANC_STANDBY,
+ .dest = ANC_BYPASS,
+ .analogue = 1,
+ .step = {
+ wm2000_exit_standby,
+ wm2000_enter_bypass,
+ },
+ },
+ {
+ .source = ANC_STANDBY,
+ .dest = ANC_OFF,
+ .step = {
+ wm2000_exit_standby,
+ wm2000_power_down,
+ },
+ },
+};
+
+static int wm2000_anc_transition(struct wm2000_priv *wm2000,
+ enum wm2000_anc_mode mode)
+{
+ struct i2c_client *i2c = wm2000->i2c;
+ int i, j;
+ int ret;
+
+ if (wm2000->anc_mode == mode)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(anc_transitions); i++)
+ if (anc_transitions[i].source == wm2000->anc_mode &&
+ anc_transitions[i].dest == mode)
+ break;
+ if (i == ARRAY_SIZE(anc_transitions)) {
+ dev_err(&i2c->dev, "No transition for %d->%d\n",
+ wm2000->anc_mode, mode);
+ return -EINVAL;
+ }
+
+ for (j = 0; j < ARRAY_SIZE(anc_transitions[j].step); j++) {
+ if (!anc_transitions[i].step[j])
+ break;
+ ret = anc_transitions[i].step[j](i2c,
+ anc_transitions[i].analogue);
+ if (ret != 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int wm2000_anc_set_mode(struct wm2000_priv *wm2000)
+{
+ struct i2c_client *i2c = wm2000->i2c;
+ enum wm2000_anc_mode mode;
+
+ if (wm2000->anc_eng_ena && wm2000->spk_ena)
+ if (wm2000->anc_active)
+ mode = ANC_ACTIVE;
+ else
+ mode = ANC_BYPASS;
+ else
+ mode = ANC_STANDBY;
+
+ dev_dbg(&i2c->dev, "Set mode %d (enabled %d, mute %d, active %d)\n",
+ mode, wm2000->anc_eng_ena, !wm2000->spk_ena,
+ wm2000->anc_active);
+
+ return wm2000_anc_transition(wm2000, mode);
+}
+
+static int wm2000_anc_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
+
+ ucontrol->value.enumerated.item[0] = wm2000->anc_active;
+
+ return 0;
+}
+
+static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
+ int anc_active = ucontrol->value.enumerated.item[0];
+
+ if (anc_active > 1)
+ return -EINVAL;
+
+ wm2000->anc_active = anc_active;
+
+ return wm2000_anc_set_mode(wm2000);
+}
+
+static int wm2000_speaker_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
+
+ ucontrol->value.enumerated.item[0] = wm2000->spk_ena;
+
+ return 0;
+}
+
+static int wm2000_speaker_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
+ int val = ucontrol->value.enumerated.item[0];
+
+ if (val > 1)
+ return -EINVAL;
+
+ wm2000->spk_ena = val;
+
+ return wm2000_anc_set_mode(wm2000);
+}
+
+static const struct snd_kcontrol_new wm2000_controls[] = {
+ SOC_SINGLE_BOOL_EXT("WM2000 ANC Switch", 0,
+ wm2000_anc_mode_get,
+ wm2000_anc_mode_put),
+ SOC_SINGLE_BOOL_EXT("WM2000 Switch", 0,
+ wm2000_speaker_get,
+ wm2000_speaker_put),
+};
+
+static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ wm2000->anc_eng_ena = 1;
+
+ if (SND_SOC_DAPM_EVENT_OFF(event))
+ wm2000->anc_eng_ena = 0;
+
+ return wm2000_anc_set_mode(wm2000);
+}
+
+static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = {
+/* Externally visible pins */
+SND_SOC_DAPM_OUTPUT("WM2000 SPKN"),
+SND_SOC_DAPM_OUTPUT("WM2000 SPKP"),
+
+SND_SOC_DAPM_INPUT("WM2000 LINN"),
+SND_SOC_DAPM_INPUT("WM2000 LINP"),
+
+SND_SOC_DAPM_PGA_E("ANC Engine", SND_SOC_NOPM, 0, 0, NULL, 0,
+ wm2000_anc_power_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+};
+
+/* Target, Path, Source */
+static const struct snd_soc_dapm_route audio_map[] = {
+ { "WM2000 SPKN", NULL, "ANC Engine" },
+ { "WM2000 SPKP", NULL, "ANC Engine" },
+ { "ANC Engine", NULL, "WM2000 LINN" },
+ { "ANC Engine", NULL, "WM2000 LINP" },
+};
+
+/* Called from the machine driver */
+int wm2000_add_controls(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ if (!wm2000_i2c) {
+ pr_err("WM2000 not yet probed\n");
+ return -ENODEV;
+ }
+
+ ret = snd_soc_dapm_new_controls(dapm, wm2000_dapm_widgets,
+ ARRAY_SIZE(wm2000_dapm_widgets));
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+ if (ret < 0)
+ return ret;
+
+ return snd_soc_add_controls(codec, wm2000_controls,
+ ARRAY_SIZE(wm2000_controls));
+}
+EXPORT_SYMBOL_GPL(wm2000_add_controls);
+
+static int __devinit wm2000_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *i2c_id)
+{
+ struct wm2000_priv *wm2000;
+ struct wm2000_platform_data *pdata;
+ const char *filename;
+ const struct firmware *fw;
+ int reg, ret;
+ u16 id;
+
+ if (wm2000_i2c) {
+ dev_err(&i2c->dev, "Another WM2000 is already registered\n");
+ return -EINVAL;
+ }
+
+ wm2000 = kzalloc(sizeof(struct wm2000_priv), GFP_KERNEL);
+ if (wm2000 == NULL) {
+ dev_err(&i2c->dev, "Unable to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ /* Verify that this is a WM2000 */
+ reg = wm2000_read(i2c, WM2000_REG_ID1);
+ id = reg << 8;
+ reg = wm2000_read(i2c, WM2000_REG_ID2);
+ id |= reg & 0xff;
+
+ if (id != 0x2000) {
+ dev_err(&i2c->dev, "Device is not a WM2000 - ID %x\n", id);
+ ret = -ENODEV;
+ goto err;
+ }
+
+ reg = wm2000_read(i2c, WM2000_REG_REVISON);
+ dev_info(&i2c->dev, "revision %c\n", reg + 'A');
+
+ filename = "wm2000_anc.bin";
+ pdata = dev_get_platdata(&i2c->dev);
+ if (pdata) {
+ wm2000->mclk_div = pdata->mclkdiv2;
+ wm2000->speech_clarity = !pdata->speech_enh_disable;
+
+ if (pdata->download_file)
+ filename = pdata->download_file;
+ }
+
+ ret = request_firmware(&fw, filename, &i2c->dev);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to acquire ANC data: %d\n", ret);
+ goto err;
+ }
+
+ /* Pre-cook the concatenation of the register address onto the image */
+ wm2000->anc_download_size = fw->size + 2;
+ wm2000->anc_download = kmalloc(wm2000->anc_download_size, GFP_KERNEL);
+ if (wm2000->anc_download == NULL) {
+ dev_err(&i2c->dev, "Out of memory\n");
+ ret = -ENOMEM;
+ goto err_fw;
+ }
+
+ wm2000->anc_download[0] = 0x80;
+ wm2000->anc_download[1] = 0x00;
+ memcpy(wm2000->anc_download + 2, fw->data, fw->size);
+
+ release_firmware(fw);
+
+ dev_set_drvdata(&i2c->dev, wm2000);
+ wm2000->anc_eng_ena = 1;
+ wm2000->anc_active = 1;
+ wm2000->spk_ena = 1;
+ wm2000->i2c = i2c;
+
+ wm2000_reset(wm2000);
+
+ /* This will trigger a transition to standby mode by default */
+ wm2000_anc_set_mode(wm2000);
+
+ wm2000_i2c = i2c;
+
+ return 0;
+
+err_fw:
+ release_firmware(fw);
+err:
+ kfree(wm2000);
+ return ret;
+}
+
+static __devexit int wm2000_i2c_remove(struct i2c_client *i2c)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+ wm2000_anc_transition(wm2000, ANC_OFF);
+
+ wm2000_i2c = NULL;
+ kfree(wm2000->anc_download);
+ kfree(wm2000);
+
+ return 0;
+}
+
+static void wm2000_i2c_shutdown(struct i2c_client *i2c)
+{
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+ wm2000_anc_transition(wm2000, ANC_OFF);
+}
+
+#ifdef CONFIG_PM
+static int wm2000_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+ return wm2000_anc_transition(wm2000, ANC_OFF);
+}
+
+static int wm2000_i2c_resume(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+ return wm2000_anc_set_mode(wm2000);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(wm2000_pm, wm2000_i2c_suspend, wm2000_i2c_resume);
+
+static const struct i2c_device_id wm2000_i2c_id[] = {
+ { "wm2000", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm2000_i2c_id);
+
+static struct i2c_driver wm2000_i2c_driver = {
+ .driver = {
+ .name = "wm2000",
+ .owner = THIS_MODULE,
+ .pm = &wm2000_pm,
+ },
+ .probe = wm2000_i2c_probe,
+ .remove = __devexit_p(wm2000_i2c_remove),
+ .shutdown = wm2000_i2c_shutdown,
+ .id_table = wm2000_i2c_id,
+};
+
+static int __init wm2000_init(void)
+{
+ return i2c_add_driver(&wm2000_i2c_driver);
+}
+module_init(wm2000_init);
+
+static void __exit wm2000_exit(void)
+{
+ i2c_del_driver(&wm2000_i2c_driver);
+}
+module_exit(wm2000_exit);
+
+MODULE_DESCRIPTION("ASoC WM2000 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm2000.h b/sound/soc/codecs/wm2000.h
new file mode 100644
index 00000000..0b6f056f
--- /dev/null
+++ b/sound/soc/codecs/wm2000.h
@@ -0,0 +1,76 @@
+/*
+ * wm2000.h -- WM2000 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM2000_H
+#define _WM2000_H
+
+struct wm2000_setup_data {
+ unsigned short i2c_address;
+ int mclk_div; /* Set to a non-zero value if MCLK_DIV_2 required */
+};
+
+extern int wm2000_add_controls(struct snd_soc_codec *codec);
+
+#define WM2000_REG_SYS_START 0x8000
+#define WM2000_REG_SPEECH_CLARITY 0x8fef
+#define WM2000_REG_SYS_WATCHDOG 0x8ff6
+#define WM2000_REG_ANA_VMID_PD_TIME 0x8ff7
+#define WM2000_REG_ANA_VMID_PU_TIME 0x8ff8
+#define WM2000_REG_CAT_FLTR_INDX 0x8ff9
+#define WM2000_REG_CAT_GAIN_0 0x8ffa
+#define WM2000_REG_SYS_STATUS 0x8ffc
+#define WM2000_REG_SYS_MODE_CNTRL 0x8ffd
+#define WM2000_REG_SYS_START0 0x8ffe
+#define WM2000_REG_SYS_START1 0x8fff
+#define WM2000_REG_ID1 0xf000
+#define WM2000_REG_ID2 0xf001
+#define WM2000_REG_REVISON 0xf002
+#define WM2000_REG_SYS_CTL1 0xf003
+#define WM2000_REG_SYS_CTL2 0xf004
+#define WM2000_REG_ANC_STAT 0xf005
+#define WM2000_REG_IF_CTL 0xf006
+
+/* SPEECH_CLARITY */
+#define WM2000_SPEECH_CLARITY 0x01
+
+/* SYS_STATUS */
+#define WM2000_STATUS_MOUSE_ACTIVE 0x40
+#define WM2000_STATUS_CAT_FREQ_COMPLETE 0x20
+#define WM2000_STATUS_CAT_GAIN_COMPLETE 0x10
+#define WM2000_STATUS_THERMAL_SHUTDOWN_COMPLETE 0x08
+#define WM2000_STATUS_ANC_DISABLED 0x04
+#define WM2000_STATUS_POWER_DOWN_COMPLETE 0x02
+#define WM2000_STATUS_BOOT_COMPLETE 0x01
+
+/* SYS_MODE_CNTRL */
+#define WM2000_MODE_ANA_SEQ_INCLUDE 0x80
+#define WM2000_MODE_MOUSE_ENABLE 0x40
+#define WM2000_MODE_CAT_FREQ_ENABLE 0x20
+#define WM2000_MODE_CAT_GAIN_ENABLE 0x10
+#define WM2000_MODE_BYPASS_ENTRY 0x08
+#define WM2000_MODE_STANDBY_ENTRY 0x04
+#define WM2000_MODE_THERMAL_ENABLE 0x02
+#define WM2000_MODE_POWER_DOWN 0x01
+
+/* SYS_CTL1 */
+#define WM2000_SYS_STBY 0x01
+
+/* SYS_CTL2 */
+#define WM2000_MCLK_DIV2_ENA_CLR 0x80
+#define WM2000_MCLK_DIV2_ENA_SET 0x40
+#define WM2000_ANC_ENG_CLR 0x20
+#define WM2000_ANC_ENG_SET 0x10
+#define WM2000_ANC_INT_N_CLR 0x08
+#define WM2000_ANC_INT_N_SET 0x04
+#define WM2000_RAM_CLR 0x02
+#define WM2000_RAM_SET 0x01
+
+/* ANC_STAT */
+#define WM2000_ANC_ENG_IDLE 0x01
+
+#endif
diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c
new file mode 100644
index 00000000..6d6dc9ef
--- /dev/null
+++ b/sound/soc/codecs/wm8350.c
@@ -0,0 +1,1738 @@
+/*
+ * wm8350.c -- WM8350 ALSA SoC audio driver
+ *
+ * Copyright (C) 2007, 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/wm8350/audio.h>
+#include <linux/mfd/wm8350/core.h>
+#include <linux/regulator/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <trace/events/asoc.h>
+
+#include "wm8350.h"
+
+#define WM8350_OUTn_0dB 0x39
+
+#define WM8350_RAMP_NONE 0
+#define WM8350_RAMP_UP 1
+#define WM8350_RAMP_DOWN 2
+
+/* We only include the analogue supplies here; the digital supplies
+ * need to be available well before this driver can be probed.
+ */
+static const char *supply_names[] = {
+ "AVDD",
+ "HPVDD",
+};
+
+struct wm8350_output {
+ u16 active;
+ u16 left_vol;
+ u16 right_vol;
+ u16 ramp;
+ u16 mute;
+};
+
+struct wm8350_jack_data {
+ struct snd_soc_jack *jack;
+ struct delayed_work work;
+ int report;
+ int short_report;
+};
+
+struct wm8350_data {
+ struct snd_soc_codec codec;
+ struct wm8350_output out1;
+ struct wm8350_output out2;
+ struct wm8350_jack_data hpl;
+ struct wm8350_jack_data hpr;
+ struct wm8350_jack_data mic;
+ struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
+ int fll_freq_out;
+ int fll_freq_in;
+};
+
+static unsigned int wm8350_codec_cache_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct wm8350 *wm8350 = codec->control_data;
+ return wm8350->reg_cache[reg];
+}
+
+static unsigned int wm8350_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct wm8350 *wm8350 = codec->control_data;
+ return wm8350_reg_read(wm8350, reg);
+}
+
+static int wm8350_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct wm8350 *wm8350 = codec->control_data;
+ return wm8350_reg_write(wm8350, reg, value);
+}
+
+/*
+ * Ramp OUT1 PGA volume to minimise pops at stream startup and shutdown.
+ */
+static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec)
+{
+ struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec);
+ struct wm8350_output *out1 = &wm8350_data->out1;
+ struct wm8350 *wm8350 = codec->control_data;
+ int left_complete = 0, right_complete = 0;
+ u16 reg, val;
+
+ /* left channel */
+ reg = wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME);
+ val = (reg & WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+
+ if (out1->ramp == WM8350_RAMP_UP) {
+ /* ramp step up */
+ if (val < out1->left_vol) {
+ val++;
+ reg &= ~WM8350_OUT1L_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME,
+ reg | (val << WM8350_OUT1L_VOL_SHIFT));
+ } else
+ left_complete = 1;
+ } else if (out1->ramp == WM8350_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0) {
+ val--;
+ reg &= ~WM8350_OUT1L_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME,
+ reg | (val << WM8350_OUT1L_VOL_SHIFT));
+ } else
+ left_complete = 1;
+ } else
+ return 1;
+
+ /* right channel */
+ reg = wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME);
+ val = (reg & WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+ if (out1->ramp == WM8350_RAMP_UP) {
+ /* ramp step up */
+ if (val < out1->right_vol) {
+ val++;
+ reg &= ~WM8350_OUT1R_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME,
+ reg | (val << WM8350_OUT1R_VOL_SHIFT));
+ } else
+ right_complete = 1;
+ } else if (out1->ramp == WM8350_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0) {
+ val--;
+ reg &= ~WM8350_OUT1R_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME,
+ reg | (val << WM8350_OUT1R_VOL_SHIFT));
+ } else
+ right_complete = 1;
+ }
+
+ /* only hit the update bit if either volume has changed this step */
+ if (!left_complete || !right_complete)
+ wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, WM8350_OUT1_VU);
+
+ return left_complete & right_complete;
+}
+
+/*
+ * Ramp OUT2 PGA volume to minimise pops at stream startup and shutdown.
+ */
+static inline int wm8350_out2_ramp_step(struct snd_soc_codec *codec)
+{
+ struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec);
+ struct wm8350_output *out2 = &wm8350_data->out2;
+ struct wm8350 *wm8350 = codec->control_data;
+ int left_complete = 0, right_complete = 0;
+ u16 reg, val;
+
+ /* left channel */
+ reg = wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME);
+ val = (reg & WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+ if (out2->ramp == WM8350_RAMP_UP) {
+ /* ramp step up */
+ if (val < out2->left_vol) {
+ val++;
+ reg &= ~WM8350_OUT2L_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME,
+ reg | (val << WM8350_OUT1L_VOL_SHIFT));
+ } else
+ left_complete = 1;
+ } else if (out2->ramp == WM8350_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0) {
+ val--;
+ reg &= ~WM8350_OUT2L_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME,
+ reg | (val << WM8350_OUT1L_VOL_SHIFT));
+ } else
+ left_complete = 1;
+ } else
+ return 1;
+
+ /* right channel */
+ reg = wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME);
+ val = (reg & WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+ if (out2->ramp == WM8350_RAMP_UP) {
+ /* ramp step up */
+ if (val < out2->right_vol) {
+ val++;
+ reg &= ~WM8350_OUT2R_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME,
+ reg | (val << WM8350_OUT1R_VOL_SHIFT));
+ } else
+ right_complete = 1;
+ } else if (out2->ramp == WM8350_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0) {
+ val--;
+ reg &= ~WM8350_OUT2R_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME,
+ reg | (val << WM8350_OUT1R_VOL_SHIFT));
+ } else
+ right_complete = 1;
+ }
+
+ /* only hit the update bit if either volume has changed this step */
+ if (!left_complete || !right_complete)
+ wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, WM8350_OUT2_VU);
+
+ return left_complete & right_complete;
+}
+
+/*
+ * This work ramps both output PGAs at stream start/stop time to
+ * minimise pop associated with DAPM power switching.
+ * It's best to enable Zero Cross when ramping occurs to minimise any
+ * zipper noises.
+ */
+static void wm8350_pga_work(struct work_struct *work)
+{
+ struct snd_soc_dapm_context *dapm =
+ container_of(work, struct snd_soc_dapm_context, delayed_work.work);
+ struct snd_soc_codec *codec = dapm->codec;
+ struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec);
+ struct wm8350_output *out1 = &wm8350_data->out1,
+ *out2 = &wm8350_data->out2;
+ int i, out1_complete, out2_complete;
+
+ /* do we need to ramp at all ? */
+ if (out1->ramp == WM8350_RAMP_NONE && out2->ramp == WM8350_RAMP_NONE)
+ return;
+
+ /* PGA volumes have 6 bits of resolution to ramp */
+ for (i = 0; i <= 63; i++) {
+ out1_complete = 1, out2_complete = 1;
+ if (out1->ramp != WM8350_RAMP_NONE)
+ out1_complete = wm8350_out1_ramp_step(codec);
+ if (out2->ramp != WM8350_RAMP_NONE)
+ out2_complete = wm8350_out2_ramp_step(codec);
+
+ /* ramp finished ? */
+ if (out1_complete && out2_complete)
+ break;
+
+ /* we need to delay longer on the up ramp */
+ if (out1->ramp == WM8350_RAMP_UP ||
+ out2->ramp == WM8350_RAMP_UP) {
+ /* delay is longer over 0dB as increases are larger */
+ if (i >= WM8350_OUTn_0dB)
+ schedule_timeout_interruptible(msecs_to_jiffies
+ (2));
+ else
+ schedule_timeout_interruptible(msecs_to_jiffies
+ (1));
+ } else
+ udelay(50); /* doesn't matter if we delay longer */
+ }
+
+ out1->ramp = WM8350_RAMP_NONE;
+ out2->ramp = WM8350_RAMP_NONE;
+}
+
+/*
+ * WM8350 Controls
+ */
+
+static int pga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec);
+ struct wm8350_output *out;
+
+ switch (w->shift) {
+ case 0:
+ case 1:
+ out = &wm8350_data->out1;
+ break;
+ case 2:
+ case 3:
+ out = &wm8350_data->out2;
+ break;
+
+ default:
+ BUG();
+ return -1;
+ }
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ out->ramp = WM8350_RAMP_UP;
+ out->active = 1;
+
+ if (!delayed_work_pending(&codec->dapm.delayed_work))
+ schedule_delayed_work(&codec->dapm.delayed_work,
+ msecs_to_jiffies(1));
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ out->ramp = WM8350_RAMP_DOWN;
+ out->active = 0;
+
+ if (!delayed_work_pending(&codec->dapm.delayed_work))
+ schedule_delayed_work(&codec->dapm.delayed_work,
+ msecs_to_jiffies(1));
+ break;
+ }
+
+ return 0;
+}
+
+static int wm8350_put_volsw_2r_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8350_data *wm8350_priv = snd_soc_codec_get_drvdata(codec);
+ struct wm8350_output *out = NULL;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int ret;
+ unsigned int reg = mc->reg;
+ u16 val;
+
+ /* For OUT1 and OUT2 we shadow the values and only actually write
+ * them out when active in order to ensure the amplifier comes on
+ * as quietly as possible. */
+ switch (reg) {
+ case WM8350_LOUT1_VOLUME:
+ out = &wm8350_priv->out1;
+ break;
+ case WM8350_LOUT2_VOLUME:
+ out = &wm8350_priv->out2;
+ break;
+ default:
+ break;
+ }
+
+ if (out) {
+ out->left_vol = ucontrol->value.integer.value[0];
+ out->right_vol = ucontrol->value.integer.value[1];
+ if (!out->active)
+ return 1;
+ }
+
+ ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* now hit the volume update bits (always bit 8) */
+ val = wm8350_codec_read(codec, reg);
+ wm8350_codec_write(codec, reg, val | WM8350_OUT1_VU);
+ return 1;
+}
+
+static int wm8350_get_volsw_2r(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8350_data *wm8350_priv = snd_soc_codec_get_drvdata(codec);
+ struct wm8350_output *out1 = &wm8350_priv->out1;
+ struct wm8350_output *out2 = &wm8350_priv->out2;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+
+ /* If these are cached registers use the cache */
+ switch (reg) {
+ case WM8350_LOUT1_VOLUME:
+ ucontrol->value.integer.value[0] = out1->left_vol;
+ ucontrol->value.integer.value[1] = out1->right_vol;
+ return 0;
+
+ case WM8350_LOUT2_VOLUME:
+ ucontrol->value.integer.value[0] = out2->left_vol;
+ ucontrol->value.integer.value[1] = out2->right_vol;
+ return 0;
+
+ default:
+ break;
+ }
+
+ return snd_soc_get_volsw_2r(kcontrol, ucontrol);
+}
+
+/* double control with volume update */
+#define SOC_WM8350_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, \
+ xinvert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_2r, \
+ .get = wm8350_get_volsw_2r, .put = wm8350_put_volsw_2r_vu, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = reg_left, .rreg = reg_right, .shift = xshift, \
+ .rshift = xshift, .max = xmax, .invert = xinvert}, }
+
+static const char *wm8350_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" };
+static const char *wm8350_dacmutem[] = { "Normal", "Soft" };
+static const char *wm8350_dacmutes[] = { "Fast", "Slow" };
+static const char *wm8350_adcfilter[] = { "None", "High Pass" };
+static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" };
+static const char *wm8350_lr[] = { "Left", "Right" };
+
+static const struct soc_enum wm8350_enum[] = {
+ SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 4, 4, wm8350_deemp),
+ SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol),
+ SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem),
+ SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes),
+ SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter),
+ SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp),
+ SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol),
+ SOC_ENUM_SINGLE(WM8350_INPUT_MIXER_VOLUME, 15, 2, wm8350_lr),
+};
+
+static DECLARE_TLV_DB_SCALE(pre_amp_tlv, -1200, 3525, 0);
+static DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 600, 0);
+static DECLARE_TLV_DB_SCALE(dac_pcm_tlv, -7163, 36, 1);
+static DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -12700, 50, 1);
+static DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 1);
+
+static const unsigned int capture_sd_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 12, TLV_DB_SCALE_ITEM(-3600, 300, 1),
+ 13, 15, TLV_DB_SCALE_ITEM(0, 0, 0),
+};
+
+static const struct snd_kcontrol_new wm8350_snd_controls[] = {
+ SOC_ENUM("Playback Deemphasis", wm8350_enum[0]),
+ SOC_ENUM("Playback DAC Inversion", wm8350_enum[1]),
+ SOC_WM8350_DOUBLE_R_TLV("Playback PCM Volume",
+ WM8350_DAC_DIGITAL_VOLUME_L,
+ WM8350_DAC_DIGITAL_VOLUME_R,
+ 0, 255, 0, dac_pcm_tlv),
+ SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]),
+ SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]),
+ SOC_ENUM("Capture PCM Filter", wm8350_enum[4]),
+ SOC_ENUM("Capture PCM HP Filter", wm8350_enum[5]),
+ SOC_ENUM("Capture ADC Inversion", wm8350_enum[6]),
+ SOC_WM8350_DOUBLE_R_TLV("Capture PCM Volume",
+ WM8350_ADC_DIGITAL_VOLUME_L,
+ WM8350_ADC_DIGITAL_VOLUME_R,
+ 0, 255, 0, adc_pcm_tlv),
+ SOC_DOUBLE_TLV("Capture Sidetone Volume",
+ WM8350_ADC_DIVIDER,
+ 8, 4, 15, 1, capture_sd_tlv),
+ SOC_WM8350_DOUBLE_R_TLV("Capture Volume",
+ WM8350_LEFT_INPUT_VOLUME,
+ WM8350_RIGHT_INPUT_VOLUME,
+ 2, 63, 0, pre_amp_tlv),
+ SOC_DOUBLE_R("Capture ZC Switch",
+ WM8350_LEFT_INPUT_VOLUME,
+ WM8350_RIGHT_INPUT_VOLUME, 13, 1, 0),
+ SOC_SINGLE_TLV("Left Input Left Sidetone Volume",
+ WM8350_OUTPUT_LEFT_MIXER_VOLUME, 1, 7, 0, out_mix_tlv),
+ SOC_SINGLE_TLV("Left Input Right Sidetone Volume",
+ WM8350_OUTPUT_LEFT_MIXER_VOLUME,
+ 5, 7, 0, out_mix_tlv),
+ SOC_SINGLE_TLV("Left Input Bypass Volume",
+ WM8350_OUTPUT_LEFT_MIXER_VOLUME,
+ 9, 7, 0, out_mix_tlv),
+ SOC_SINGLE_TLV("Right Input Left Sidetone Volume",
+ WM8350_OUTPUT_RIGHT_MIXER_VOLUME,
+ 1, 7, 0, out_mix_tlv),
+ SOC_SINGLE_TLV("Right Input Right Sidetone Volume",
+ WM8350_OUTPUT_RIGHT_MIXER_VOLUME,
+ 5, 7, 0, out_mix_tlv),
+ SOC_SINGLE_TLV("Right Input Bypass Volume",
+ WM8350_OUTPUT_RIGHT_MIXER_VOLUME,
+ 13, 7, 0, out_mix_tlv),
+ SOC_SINGLE("Left Input Mixer +20dB Switch",
+ WM8350_INPUT_MIXER_VOLUME_L, 0, 1, 0),
+ SOC_SINGLE("Right Input Mixer +20dB Switch",
+ WM8350_INPUT_MIXER_VOLUME_R, 0, 1, 0),
+ SOC_SINGLE_TLV("Out4 Capture Volume",
+ WM8350_INPUT_MIXER_VOLUME,
+ 1, 7, 0, out_mix_tlv),
+ SOC_WM8350_DOUBLE_R_TLV("Out1 Playback Volume",
+ WM8350_LOUT1_VOLUME,
+ WM8350_ROUT1_VOLUME,
+ 2, 63, 0, out_pga_tlv),
+ SOC_DOUBLE_R("Out1 Playback ZC Switch",
+ WM8350_LOUT1_VOLUME,
+ WM8350_ROUT1_VOLUME, 13, 1, 0),
+ SOC_WM8350_DOUBLE_R_TLV("Out2 Playback Volume",
+ WM8350_LOUT2_VOLUME,
+ WM8350_ROUT2_VOLUME,
+ 2, 63, 0, out_pga_tlv),
+ SOC_DOUBLE_R("Out2 Playback ZC Switch", WM8350_LOUT2_VOLUME,
+ WM8350_ROUT2_VOLUME, 13, 1, 0),
+ SOC_SINGLE("Out2 Right Invert Switch", WM8350_ROUT2_VOLUME, 10, 1, 0),
+ SOC_SINGLE_TLV("Out2 Beep Volume", WM8350_BEEP_VOLUME,
+ 5, 7, 0, out_mix_tlv),
+
+ SOC_DOUBLE_R("Out1 Playback Switch",
+ WM8350_LOUT1_VOLUME,
+ WM8350_ROUT1_VOLUME,
+ 14, 1, 1),
+ SOC_DOUBLE_R("Out2 Playback Switch",
+ WM8350_LOUT2_VOLUME,
+ WM8350_ROUT2_VOLUME,
+ 14, 1, 1),
+};
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Playback Mixer */
+static const struct snd_kcontrol_new wm8350_left_play_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Playback Switch",
+ WM8350_LEFT_MIXER_CONTROL, 11, 1, 0),
+ SOC_DAPM_SINGLE("Left Bypass Switch",
+ WM8350_LEFT_MIXER_CONTROL, 2, 1, 0),
+ SOC_DAPM_SINGLE("Right Playback Switch",
+ WM8350_LEFT_MIXER_CONTROL, 12, 1, 0),
+ SOC_DAPM_SINGLE("Left Sidetone Switch",
+ WM8350_LEFT_MIXER_CONTROL, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right Sidetone Switch",
+ WM8350_LEFT_MIXER_CONTROL, 1, 1, 0),
+};
+
+/* Right Playback Mixer */
+static const struct snd_kcontrol_new wm8350_right_play_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Playback Switch",
+ WM8350_RIGHT_MIXER_CONTROL, 12, 1, 0),
+ SOC_DAPM_SINGLE("Right Bypass Switch",
+ WM8350_RIGHT_MIXER_CONTROL, 3, 1, 0),
+ SOC_DAPM_SINGLE("Left Playback Switch",
+ WM8350_RIGHT_MIXER_CONTROL, 11, 1, 0),
+ SOC_DAPM_SINGLE("Left Sidetone Switch",
+ WM8350_RIGHT_MIXER_CONTROL, 0, 1, 0),
+ SOC_DAPM_SINGLE("Right Sidetone Switch",
+ WM8350_RIGHT_MIXER_CONTROL, 1, 1, 0),
+};
+
+/* Out4 Mixer */
+static const struct snd_kcontrol_new wm8350_out4_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Right Playback Switch",
+ WM8350_OUT4_MIXER_CONTROL, 12, 1, 0),
+ SOC_DAPM_SINGLE("Left Playback Switch",
+ WM8350_OUT4_MIXER_CONTROL, 11, 1, 0),
+ SOC_DAPM_SINGLE("Right Capture Switch",
+ WM8350_OUT4_MIXER_CONTROL, 9, 1, 0),
+ SOC_DAPM_SINGLE("Out3 Playback Switch",
+ WM8350_OUT4_MIXER_CONTROL, 2, 1, 0),
+ SOC_DAPM_SINGLE("Right Mixer Switch",
+ WM8350_OUT4_MIXER_CONTROL, 1, 1, 0),
+ SOC_DAPM_SINGLE("Left Mixer Switch",
+ WM8350_OUT4_MIXER_CONTROL, 0, 1, 0),
+};
+
+/* Out3 Mixer */
+static const struct snd_kcontrol_new wm8350_out3_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left Playback Switch",
+ WM8350_OUT3_MIXER_CONTROL, 11, 1, 0),
+ SOC_DAPM_SINGLE("Left Capture Switch",
+ WM8350_OUT3_MIXER_CONTROL, 8, 1, 0),
+ SOC_DAPM_SINGLE("Out4 Playback Switch",
+ WM8350_OUT3_MIXER_CONTROL, 3, 1, 0),
+ SOC_DAPM_SINGLE("Left Mixer Switch",
+ WM8350_OUT3_MIXER_CONTROL, 0, 1, 0),
+};
+
+/* Left Input Mixer */
+static const struct snd_kcontrol_new wm8350_left_capt_mixer_controls[] = {
+ SOC_DAPM_SINGLE_TLV("L2 Capture Volume",
+ WM8350_INPUT_MIXER_VOLUME_L, 1, 7, 0, out_mix_tlv),
+ SOC_DAPM_SINGLE_TLV("L3 Capture Volume",
+ WM8350_INPUT_MIXER_VOLUME_L, 9, 7, 0, out_mix_tlv),
+ SOC_DAPM_SINGLE("PGA Capture Switch",
+ WM8350_LEFT_INPUT_VOLUME, 14, 1, 1),
+};
+
+/* Right Input Mixer */
+static const struct snd_kcontrol_new wm8350_right_capt_mixer_controls[] = {
+ SOC_DAPM_SINGLE_TLV("L2 Capture Volume",
+ WM8350_INPUT_MIXER_VOLUME_R, 5, 7, 0, out_mix_tlv),
+ SOC_DAPM_SINGLE_TLV("L3 Capture Volume",
+ WM8350_INPUT_MIXER_VOLUME_R, 13, 7, 0, out_mix_tlv),
+ SOC_DAPM_SINGLE("PGA Capture Switch",
+ WM8350_RIGHT_INPUT_VOLUME, 14, 1, 1),
+};
+
+/* Left Mic Mixer */
+static const struct snd_kcontrol_new wm8350_left_mic_mixer_controls[] = {
+ SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 1, 1, 0),
+ SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 0, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 2, 1, 0),
+};
+
+/* Right Mic Mixer */
+static const struct snd_kcontrol_new wm8350_right_mic_mixer_controls[] = {
+ SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 9, 1, 0),
+ SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 8, 1, 0),
+ SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 10, 1, 0),
+};
+
+/* Beep Switch */
+static const struct snd_kcontrol_new wm8350_beep_switch_controls =
+SOC_DAPM_SINGLE("Switch", WM8350_BEEP_VOLUME, 15, 1, 1);
+
+/* Out4 Capture Mux */
+static const struct snd_kcontrol_new wm8350_out4_capture_controls =
+SOC_DAPM_ENUM("Route", wm8350_enum[7]);
+
+static const struct snd_soc_dapm_widget wm8350_dapm_widgets[] = {
+
+ SND_SOC_DAPM_PGA("IN3R PGA", WM8350_POWER_MGMT_2, 11, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("IN3L PGA", WM8350_POWER_MGMT_2, 10, 0, NULL, 0),
+ SND_SOC_DAPM_PGA_E("Right Out2 PGA", WM8350_POWER_MGMT_3, 3, 0, NULL,
+ 0, pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("Left Out2 PGA", WM8350_POWER_MGMT_3, 2, 0, NULL, 0,
+ pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("Right Out1 PGA", WM8350_POWER_MGMT_3, 1, 0, NULL,
+ 0, pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("Left Out1 PGA", WM8350_POWER_MGMT_3, 0, 0, NULL, 0,
+ pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_MIXER("Right Capture Mixer", WM8350_POWER_MGMT_2,
+ 7, 0, &wm8350_right_capt_mixer_controls[0],
+ ARRAY_SIZE(wm8350_right_capt_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left Capture Mixer", WM8350_POWER_MGMT_2,
+ 6, 0, &wm8350_left_capt_mixer_controls[0],
+ ARRAY_SIZE(wm8350_left_capt_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Out4 Mixer", WM8350_POWER_MGMT_2, 5, 0,
+ &wm8350_out4_mixer_controls[0],
+ ARRAY_SIZE(wm8350_out4_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Out3 Mixer", WM8350_POWER_MGMT_2, 4, 0,
+ &wm8350_out3_mixer_controls[0],
+ ARRAY_SIZE(wm8350_out3_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right Playback Mixer", WM8350_POWER_MGMT_2, 1, 0,
+ &wm8350_right_play_mixer_controls[0],
+ ARRAY_SIZE(wm8350_right_play_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left Playback Mixer", WM8350_POWER_MGMT_2, 0, 0,
+ &wm8350_left_play_mixer_controls[0],
+ ARRAY_SIZE(wm8350_left_play_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left Mic Mixer", WM8350_POWER_MGMT_2, 8, 0,
+ &wm8350_left_mic_mixer_controls[0],
+ ARRAY_SIZE(wm8350_left_mic_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right Mic Mixer", WM8350_POWER_MGMT_2, 9, 0,
+ &wm8350_right_mic_mixer_controls[0],
+ ARRAY_SIZE(wm8350_right_mic_mixer_controls)),
+
+ /* virtual mixer for Beep and Out2R */
+ SND_SOC_DAPM_MIXER("Out2 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_SWITCH("Beep", WM8350_POWER_MGMT_3, 7, 0,
+ &wm8350_beep_switch_controls),
+
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture",
+ WM8350_POWER_MGMT_4, 3, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture",
+ WM8350_POWER_MGMT_4, 2, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback",
+ WM8350_POWER_MGMT_4, 5, 0),
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback",
+ WM8350_POWER_MGMT_4, 4, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8350_POWER_MGMT_1, 4, 0),
+
+ SND_SOC_DAPM_MUX("Out4 Capture Channel", SND_SOC_NOPM, 0, 0,
+ &wm8350_out4_capture_controls),
+
+ SND_SOC_DAPM_OUTPUT("OUT1R"),
+ SND_SOC_DAPM_OUTPUT("OUT1L"),
+ SND_SOC_DAPM_OUTPUT("OUT2R"),
+ SND_SOC_DAPM_OUTPUT("OUT2L"),
+ SND_SOC_DAPM_OUTPUT("OUT3"),
+ SND_SOC_DAPM_OUTPUT("OUT4"),
+
+ SND_SOC_DAPM_INPUT("IN1RN"),
+ SND_SOC_DAPM_INPUT("IN1RP"),
+ SND_SOC_DAPM_INPUT("IN2R"),
+ SND_SOC_DAPM_INPUT("IN1LP"),
+ SND_SOC_DAPM_INPUT("IN1LN"),
+ SND_SOC_DAPM_INPUT("IN2L"),
+ SND_SOC_DAPM_INPUT("IN3R"),
+ SND_SOC_DAPM_INPUT("IN3L"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* left playback mixer */
+ {"Left Playback Mixer", "Playback Switch", "Left DAC"},
+ {"Left Playback Mixer", "Left Bypass Switch", "IN3L PGA"},
+ {"Left Playback Mixer", "Right Playback Switch", "Right DAC"},
+ {"Left Playback Mixer", "Left Sidetone Switch", "Left Mic Mixer"},
+ {"Left Playback Mixer", "Right Sidetone Switch", "Right Mic Mixer"},
+
+ /* right playback mixer */
+ {"Right Playback Mixer", "Playback Switch", "Right DAC"},
+ {"Right Playback Mixer", "Right Bypass Switch", "IN3R PGA"},
+ {"Right Playback Mixer", "Left Playback Switch", "Left DAC"},
+ {"Right Playback Mixer", "Left Sidetone Switch", "Left Mic Mixer"},
+ {"Right Playback Mixer", "Right Sidetone Switch", "Right Mic Mixer"},
+
+ /* out4 playback mixer */
+ {"Out4 Mixer", "Right Playback Switch", "Right DAC"},
+ {"Out4 Mixer", "Left Playback Switch", "Left DAC"},
+ {"Out4 Mixer", "Right Capture Switch", "Right Capture Mixer"},
+ {"Out4 Mixer", "Out3 Playback Switch", "Out3 Mixer"},
+ {"Out4 Mixer", "Right Mixer Switch", "Right Playback Mixer"},
+ {"Out4 Mixer", "Left Mixer Switch", "Left Playback Mixer"},
+ {"OUT4", NULL, "Out4 Mixer"},
+
+ /* out3 playback mixer */
+ {"Out3 Mixer", "Left Playback Switch", "Left DAC"},
+ {"Out3 Mixer", "Left Capture Switch", "Left Capture Mixer"},
+ {"Out3 Mixer", "Left Mixer Switch", "Left Playback Mixer"},
+ {"Out3 Mixer", "Out4 Playback Switch", "Out4 Mixer"},
+ {"OUT3", NULL, "Out3 Mixer"},
+
+ /* out2 */
+ {"Right Out2 PGA", NULL, "Right Playback Mixer"},
+ {"Left Out2 PGA", NULL, "Left Playback Mixer"},
+ {"OUT2L", NULL, "Left Out2 PGA"},
+ {"OUT2R", NULL, "Right Out2 PGA"},
+
+ /* out1 */
+ {"Right Out1 PGA", NULL, "Right Playback Mixer"},
+ {"Left Out1 PGA", NULL, "Left Playback Mixer"},
+ {"OUT1L", NULL, "Left Out1 PGA"},
+ {"OUT1R", NULL, "Right Out1 PGA"},
+
+ /* ADCs */
+ {"Left ADC", NULL, "Left Capture Mixer"},
+ {"Right ADC", NULL, "Right Capture Mixer"},
+
+ /* Left capture mixer */
+ {"Left Capture Mixer", "L2 Capture Volume", "IN2L"},
+ {"Left Capture Mixer", "L3 Capture Volume", "IN3L PGA"},
+ {"Left Capture Mixer", "PGA Capture Switch", "Left Mic Mixer"},
+ {"Left Capture Mixer", NULL, "Out4 Capture Channel"},
+
+ /* Right capture mixer */
+ {"Right Capture Mixer", "L2 Capture Volume", "IN2R"},
+ {"Right Capture Mixer", "L3 Capture Volume", "IN3R PGA"},
+ {"Right Capture Mixer", "PGA Capture Switch", "Right Mic Mixer"},
+ {"Right Capture Mixer", NULL, "Out4 Capture Channel"},
+
+ /* L3 Inputs */
+ {"IN3L PGA", NULL, "IN3L"},
+ {"IN3R PGA", NULL, "IN3R"},
+
+ /* Left Mic mixer */
+ {"Left Mic Mixer", "INN Capture Switch", "IN1LN"},
+ {"Left Mic Mixer", "INP Capture Switch", "IN1LP"},
+ {"Left Mic Mixer", "IN2 Capture Switch", "IN2L"},
+
+ /* Right Mic mixer */
+ {"Right Mic Mixer", "INN Capture Switch", "IN1RN"},
+ {"Right Mic Mixer", "INP Capture Switch", "IN1RP"},
+ {"Right Mic Mixer", "IN2 Capture Switch", "IN2R"},
+
+ /* out 4 capture */
+ {"Out4 Capture Channel", NULL, "Out4 Mixer"},
+
+ /* Beep */
+ {"Beep", NULL, "IN3R PGA"},
+};
+
+static int wm8350_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(dapm,
+ wm8350_dapm_widgets,
+ ARRAY_SIZE(wm8350_dapm_widgets));
+ if (ret != 0) {
+ dev_err(codec->dev, "dapm control register failed\n");
+ return ret;
+ }
+
+ /* set up audio paths */
+ ret = snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+ if (ret != 0) {
+ dev_err(codec->dev, "DAPM route register failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int wm8350_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8350 *wm8350 = codec->control_data;
+ u16 fll_4;
+
+ switch (clk_id) {
+ case WM8350_MCLK_SEL_MCLK:
+ wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_1,
+ WM8350_MCLK_SEL);
+ break;
+ case WM8350_MCLK_SEL_PLL_MCLK:
+ case WM8350_MCLK_SEL_PLL_DAC:
+ case WM8350_MCLK_SEL_PLL_ADC:
+ case WM8350_MCLK_SEL_PLL_32K:
+ wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_1,
+ WM8350_MCLK_SEL);
+ fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) &
+ ~WM8350_FLL_CLK_SRC_MASK;
+ wm8350_codec_write(codec, WM8350_FLL_CONTROL_4, fll_4 | clk_id);
+ break;
+ }
+
+ /* MCLK direction */
+ if (dir == SND_SOC_CLOCK_OUT)
+ wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+ WM8350_MCLK_DIR);
+ else
+ wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+ WM8350_MCLK_DIR);
+
+ return 0;
+}
+
+static int wm8350_set_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 val;
+
+ switch (div_id) {
+ case WM8350_ADC_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_ADC_DIVIDER) &
+ ~WM8350_ADC_CLKDIV_MASK;
+ wm8350_codec_write(codec, WM8350_ADC_DIVIDER, val | div);
+ break;
+ case WM8350_DAC_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_DAC_CLOCK_CONTROL) &
+ ~WM8350_DAC_CLKDIV_MASK;
+ wm8350_codec_write(codec, WM8350_DAC_CLOCK_CONTROL, val | div);
+ break;
+ case WM8350_BCLK_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) &
+ ~WM8350_BCLK_DIV_MASK;
+ wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div);
+ break;
+ case WM8350_OPCLK_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) &
+ ~WM8350_OPCLK_DIV_MASK;
+ wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div);
+ break;
+ case WM8350_SYS_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) &
+ ~WM8350_MCLK_DIV_MASK;
+ wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div);
+ break;
+ case WM8350_DACLR_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_DAC_LR_RATE) &
+ ~WM8350_DACLRC_RATE_MASK;
+ wm8350_codec_write(codec, WM8350_DAC_LR_RATE, val | div);
+ break;
+ case WM8350_ADCLR_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_ADC_LR_RATE) &
+ ~WM8350_ADCLRC_RATE_MASK;
+ wm8350_codec_write(codec, WM8350_ADC_LR_RATE, val | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8350_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) &
+ ~(WM8350_AIF_BCLK_INV | WM8350_AIF_LRCLK_INV | WM8350_AIF_FMT_MASK);
+ u16 master = wm8350_codec_read(codec, WM8350_AI_DAC_CONTROL) &
+ ~WM8350_BCLK_MSTR;
+ u16 dac_lrc = wm8350_codec_read(codec, WM8350_DAC_LR_RATE) &
+ ~WM8350_DACLRC_ENA;
+ u16 adc_lrc = wm8350_codec_read(codec, WM8350_ADC_LR_RATE) &
+ ~WM8350_ADCLRC_ENA;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ master |= WM8350_BCLK_MSTR;
+ dac_lrc |= WM8350_DACLRC_ENA;
+ adc_lrc |= WM8350_ADCLRC_ENA;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x2 << 8;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x1 << 8;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x3 << 8;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x3 << 8 | WM8350_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= WM8350_AIF_LRCLK_INV | WM8350_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= WM8350_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= WM8350_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8350_codec_write(codec, WM8350_AI_FORMATING, iface);
+ wm8350_codec_write(codec, WM8350_AI_DAC_CONTROL, master);
+ wm8350_codec_write(codec, WM8350_DAC_LR_RATE, dac_lrc);
+ wm8350_codec_write(codec, WM8350_ADC_LR_RATE, adc_lrc);
+ return 0;
+}
+
+static int wm8350_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *codec_dai)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int master = wm8350_codec_cache_read(codec, WM8350_AI_DAC_CONTROL) &
+ WM8350_BCLK_MSTR;
+ int enabled = 0;
+
+ /* Check that the DACs or ADCs are enabled since they are
+ * required for LRC in master mode. The DACs or ADCs need a
+ * valid audio path i.e. pin -> ADC or DAC -> pin before
+ * the LRC will be enabled in master mode. */
+ if (!master || cmd != SNDRV_PCM_TRIGGER_START)
+ return 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ enabled = wm8350_codec_cache_read(codec, WM8350_POWER_MGMT_4) &
+ (WM8350_ADCR_ENA | WM8350_ADCL_ENA);
+ } else {
+ enabled = wm8350_codec_cache_read(codec, WM8350_POWER_MGMT_4) &
+ (WM8350_DACR_ENA | WM8350_DACL_ENA);
+ }
+
+ if (!enabled) {
+ dev_err(codec->dev,
+ "%s: invalid audio path - no clocks available\n",
+ __func__);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *codec_dai)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8350 *wm8350 = codec->control_data;
+ u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) &
+ ~WM8350_AIF_WL_MASK;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x1 << 10;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x2 << 10;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x3 << 10;
+ break;
+ }
+
+ wm8350_codec_write(codec, WM8350_AI_FORMATING, iface);
+
+ /* The sloping stopband filter is recommended for use with
+ * lower sample rates to improve performance.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (params_rate(params) < 24000)
+ wm8350_set_bits(wm8350, WM8350_DAC_MUTE_VOLUME,
+ WM8350_DAC_SB_FILT);
+ else
+ wm8350_clear_bits(wm8350, WM8350_DAC_MUTE_VOLUME,
+ WM8350_DAC_SB_FILT);
+ }
+
+ return 0;
+}
+
+static int wm8350_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8350 *wm8350 = codec->control_data;
+
+ if (mute)
+ wm8350_set_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA);
+ else
+ wm8350_clear_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA);
+ return 0;
+}
+
+/* FLL divisors */
+struct _fll_div {
+ int div; /* FLL_OUTDIV */
+ int n;
+ int k;
+ int ratio; /* FLL_FRATIO */
+};
+
+/* The size in bits of the fll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static inline int fll_factors(struct _fll_div *fll_div, unsigned int input,
+ unsigned int output)
+{
+ u64 Kpart;
+ unsigned int t1, t2, K, Nmod;
+
+ if (output >= 2815250 && output <= 3125000)
+ fll_div->div = 0x4;
+ else if (output >= 5625000 && output <= 6250000)
+ fll_div->div = 0x3;
+ else if (output >= 11250000 && output <= 12500000)
+ fll_div->div = 0x2;
+ else if (output >= 22500000 && output <= 25000000)
+ fll_div->div = 0x1;
+ else {
+ printk(KERN_ERR "wm8350: fll freq %d out of range\n", output);
+ return -EINVAL;
+ }
+
+ if (input > 48000)
+ fll_div->ratio = 1;
+ else
+ fll_div->ratio = 8;
+
+ t1 = output * (1 << (fll_div->div + 1));
+ t2 = input * fll_div->ratio;
+
+ fll_div->n = t1 / t2;
+ Nmod = t1 % t2;
+
+ if (Nmod) {
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+ do_div(Kpart, t2);
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+ fll_div->k = K;
+ } else
+ fll_div->k = 0;
+
+ return 0;
+}
+
+static int wm8350_set_fll(struct snd_soc_dai *codec_dai,
+ int pll_id, int source, unsigned int freq_in,
+ unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8350 *wm8350 = codec->control_data;
+ struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct _fll_div fll_div;
+ int ret = 0;
+ u16 fll_1, fll_4;
+
+ if (freq_in == priv->fll_freq_in && freq_out == priv->fll_freq_out)
+ return 0;
+
+ /* power down FLL - we need to do this for reconfiguration */
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4,
+ WM8350_FLL_ENA | WM8350_FLL_OSC_ENA);
+
+ if (freq_out == 0 || freq_in == 0)
+ return ret;
+
+ ret = fll_factors(&fll_div, freq_in, freq_out);
+ if (ret < 0)
+ return ret;
+ dev_dbg(wm8350->dev,
+ "FLL in %u FLL out %u N 0x%x K 0x%x div %d ratio %d",
+ freq_in, freq_out, fll_div.n, fll_div.k, fll_div.div,
+ fll_div.ratio);
+
+ /* set up N.K & dividers */
+ fll_1 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_1) &
+ ~(WM8350_FLL_OUTDIV_MASK | WM8350_FLL_RSP_RATE_MASK | 0xc000);
+ wm8350_codec_write(codec, WM8350_FLL_CONTROL_1,
+ fll_1 | (fll_div.div << 8) | 0x50);
+ wm8350_codec_write(codec, WM8350_FLL_CONTROL_2,
+ (fll_div.ratio << 11) | (fll_div.
+ n & WM8350_FLL_N_MASK));
+ wm8350_codec_write(codec, WM8350_FLL_CONTROL_3, fll_div.k);
+ fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) &
+ ~(WM8350_FLL_FRAC | WM8350_FLL_SLOW_LOCK_REF);
+ wm8350_codec_write(codec, WM8350_FLL_CONTROL_4,
+ fll_4 | (fll_div.k ? WM8350_FLL_FRAC : 0) |
+ (fll_div.ratio == 8 ? WM8350_FLL_SLOW_LOCK_REF : 0));
+
+ /* power FLL on */
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_OSC_ENA);
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_ENA);
+
+ priv->fll_freq_out = freq_out;
+ priv->fll_freq_in = freq_in;
+
+ return 0;
+}
+
+static int wm8350_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8350 *wm8350 = codec->control_data;
+ struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct wm8350_audio_platform_data *platform =
+ wm8350->codec.platform_data;
+ u16 pm1;
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1 | WM8350_VMID_50K |
+ platform->codec_current_on << 14);
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1);
+ pm1 &= ~WM8350_VMID_MASK;
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1 | WM8350_VMID_50K);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies),
+ priv->supplies);
+ if (ret != 0)
+ return ret;
+
+ /* Enable the system clock */
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4,
+ WM8350_SYSCLK_ENA);
+
+ /* mute DAC & outputs */
+ wm8350_set_bits(wm8350, WM8350_DAC_MUTE,
+ WM8350_DAC_MUTE_ENA);
+
+ /* discharge cap memory */
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+ platform->dis_out1 |
+ (platform->dis_out2 << 2) |
+ (platform->dis_out3 << 4) |
+ (platform->dis_out4 << 6));
+
+ /* wait for discharge */
+ schedule_timeout_interruptible(msecs_to_jiffies
+ (platform->
+ cap_discharge_msecs));
+
+ /* enable antipop */
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+ (platform->vmid_s_curve << 8));
+
+ /* ramp up vmid */
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ (platform->
+ codec_current_charge << 14) |
+ WM8350_VMID_5K | WM8350_VMIDEN |
+ WM8350_VBUFEN);
+
+ /* wait for vmid */
+ schedule_timeout_interruptible(msecs_to_jiffies
+ (platform->
+ vmid_charge_msecs));
+
+ /* turn on vmid 300k */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK);
+ pm1 |= WM8350_VMID_300K |
+ (platform->codec_current_standby << 14);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1);
+
+
+ /* enable analogue bias */
+ pm1 |= WM8350_BIASEN;
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1);
+
+ /* disable antipop */
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, 0);
+
+ } else {
+ /* turn on vmid 300k and reduce current */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1 | WM8350_VMID_300K |
+ (platform->
+ codec_current_standby << 14));
+
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+
+ /* mute DAC & enable outputs */
+ wm8350_set_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA);
+
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_3,
+ WM8350_OUT1L_ENA | WM8350_OUT1R_ENA |
+ WM8350_OUT2L_ENA | WM8350_OUT2R_ENA);
+
+ /* enable anti pop S curve */
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+ (platform->vmid_s_curve << 8));
+
+ /* turn off vmid */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~WM8350_VMIDEN;
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1);
+
+ /* wait */
+ schedule_timeout_interruptible(msecs_to_jiffies
+ (platform->
+ vmid_discharge_msecs));
+
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+ (platform->vmid_s_curve << 8) |
+ platform->dis_out1 |
+ (platform->dis_out2 << 2) |
+ (platform->dis_out3 << 4) |
+ (platform->dis_out4 << 6));
+
+ /* turn off VBuf and drain */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~(WM8350_VBUFEN | WM8350_VMID_MASK);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1 | WM8350_OUTPUT_DRAIN_EN);
+
+ /* wait */
+ schedule_timeout_interruptible(msecs_to_jiffies
+ (platform->drain_msecs));
+
+ pm1 &= ~WM8350_BIASEN;
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1);
+
+ /* disable anti-pop */
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, 0);
+
+ wm8350_clear_bits(wm8350, WM8350_LOUT1_VOLUME,
+ WM8350_OUT1L_ENA);
+ wm8350_clear_bits(wm8350, WM8350_ROUT1_VOLUME,
+ WM8350_OUT1R_ENA);
+ wm8350_clear_bits(wm8350, WM8350_LOUT2_VOLUME,
+ WM8350_OUT2L_ENA);
+ wm8350_clear_bits(wm8350, WM8350_ROUT2_VOLUME,
+ WM8350_OUT2R_ENA);
+
+ /* disable clock gen */
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4,
+ WM8350_SYSCLK_ENA);
+
+ regulator_bulk_disable(ARRAY_SIZE(priv->supplies),
+ priv->supplies);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int wm8350_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8350_resume(struct snd_soc_codec *codec)
+{
+ wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static void wm8350_hp_work(struct wm8350_data *priv,
+ struct wm8350_jack_data *jack,
+ u16 mask)
+{
+ struct wm8350 *wm8350 = priv->codec.control_data;
+ u16 reg;
+ int report;
+
+ reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS);
+ if (reg & mask)
+ report = jack->report;
+ else
+ report = 0;
+
+ snd_soc_jack_report(jack->jack, report, jack->report);
+
+}
+
+static void wm8350_hpl_work(struct work_struct *work)
+{
+ struct wm8350_data *priv =
+ container_of(work, struct wm8350_data, hpl.work.work);
+
+ wm8350_hp_work(priv, &priv->hpl, WM8350_JACK_L_LVL);
+}
+
+static void wm8350_hpr_work(struct work_struct *work)
+{
+ struct wm8350_data *priv =
+ container_of(work, struct wm8350_data, hpr.work.work);
+
+ wm8350_hp_work(priv, &priv->hpr, WM8350_JACK_R_LVL);
+}
+
+static irqreturn_t wm8350_hp_jack_handler(int irq, void *data)
+{
+ struct wm8350_data *priv = data;
+ struct wm8350 *wm8350 = priv->codec.control_data;
+ struct wm8350_jack_data *jack = NULL;
+
+ switch (irq - wm8350->irq_base) {
+ case WM8350_IRQ_CODEC_JCK_DET_L:
+#ifndef CONFIG_SND_SOC_WM8350_MODULE
+ trace_snd_soc_jack_irq("WM8350 HPL");
+#endif
+ jack = &priv->hpl;
+ break;
+
+ case WM8350_IRQ_CODEC_JCK_DET_R:
+#ifndef CONFIG_SND_SOC_WM8350_MODULE
+ trace_snd_soc_jack_irq("WM8350 HPR");
+#endif
+ jack = &priv->hpr;
+ break;
+
+ default:
+ BUG();
+ }
+
+ if (device_may_wakeup(wm8350->dev))
+ pm_wakeup_event(wm8350->dev, 250);
+
+ schedule_delayed_work(&jack->work, 200);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * wm8350_hp_jack_detect - Enable headphone jack detection.
+ *
+ * @codec: WM8350 codec
+ * @which: left or right jack detect signal
+ * @jack: jack to report detection events on
+ * @report: value to report
+ *
+ * Enables the headphone jack detection of the WM8350. If no report
+ * is specified then detection is disabled.
+ */
+int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which,
+ struct snd_soc_jack *jack, int report)
+{
+ struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct wm8350 *wm8350 = codec->control_data;
+ int irq;
+ int ena;
+
+ switch (which) {
+ case WM8350_JDL:
+ priv->hpl.jack = jack;
+ priv->hpl.report = report;
+ irq = WM8350_IRQ_CODEC_JCK_DET_L;
+ ena = WM8350_JDL_ENA;
+ break;
+
+ case WM8350_JDR:
+ priv->hpr.jack = jack;
+ priv->hpr.report = report;
+ irq = WM8350_IRQ_CODEC_JCK_DET_R;
+ ena = WM8350_JDR_ENA;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (report) {
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
+ wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena);
+ } else {
+ wm8350_clear_bits(wm8350, WM8350_JACK_DETECT, ena);
+ }
+
+ /* Sync status */
+ wm8350_hp_jack_handler(irq + wm8350->irq_base, priv);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_hp_jack_detect);
+
+static irqreturn_t wm8350_mic_handler(int irq, void *data)
+{
+ struct wm8350_data *priv = data;
+ struct wm8350 *wm8350 = priv->codec.control_data;
+ u16 reg;
+ int report = 0;
+
+#ifndef CONFIG_SND_SOC_WM8350_MODULE
+ trace_snd_soc_jack_irq("WM8350 mic");
+#endif
+
+ reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS);
+ if (reg & WM8350_JACK_MICSCD_LVL)
+ report |= priv->mic.short_report;
+ if (reg & WM8350_JACK_MICSD_LVL)
+ report |= priv->mic.report;
+
+ snd_soc_jack_report(priv->mic.jack, report,
+ priv->mic.report | priv->mic.short_report);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * wm8350_mic_jack_detect - Enable microphone jack detection.
+ *
+ * @codec: WM8350 codec
+ * @jack: jack to report detection events on
+ * @detect_report: value to report when presence detected
+ * @short_report: value to report when microphone short detected
+ *
+ * Enables the microphone jack detection of the WM8350. If both reports
+ * are specified as zero then detection is disabled.
+ */
+int wm8350_mic_jack_detect(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack,
+ int detect_report, int short_report)
+{
+ struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct wm8350 *wm8350 = codec->control_data;
+
+ priv->mic.jack = jack;
+ priv->mic.report = detect_report;
+ priv->mic.short_report = short_report;
+
+ if (detect_report || short_report) {
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_1,
+ WM8350_MIC_DET_ENA);
+ } else {
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_1,
+ WM8350_MIC_DET_ENA);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_mic_jack_detect);
+
+#define WM8350_RATES (SNDRV_PCM_RATE_8000_96000)
+
+#define WM8350_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8350_dai_ops = {
+ .hw_params = wm8350_pcm_hw_params,
+ .digital_mute = wm8350_mute,
+ .trigger = wm8350_pcm_trigger,
+ .set_fmt = wm8350_set_dai_fmt,
+ .set_sysclk = wm8350_set_dai_sysclk,
+ .set_pll = wm8350_set_fll,
+ .set_clkdiv = wm8350_set_clkdiv,
+};
+
+static struct snd_soc_dai_driver wm8350_dai = {
+ .name = "wm8350-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8350_RATES,
+ .formats = WM8350_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8350_RATES,
+ .formats = WM8350_FORMATS,
+ },
+ .ops = &wm8350_dai_ops,
+};
+
+static int wm8350_codec_probe(struct snd_soc_codec *codec)
+{
+ struct wm8350 *wm8350 = dev_get_platdata(codec->dev);
+ struct wm8350_data *priv;
+ struct wm8350_output *out1;
+ struct wm8350_output *out2;
+ int ret, i;
+
+ if (wm8350->codec.platform_data == NULL) {
+ dev_err(codec->dev, "No audio platform data supplied\n");
+ return -EINVAL;
+ }
+
+ priv = kzalloc(sizeof(struct wm8350_data), GFP_KERNEL);
+ if (priv == NULL)
+ return -ENOMEM;
+ snd_soc_codec_set_drvdata(codec, priv);
+
+ for (i = 0; i < ARRAY_SIZE(supply_names); i++)
+ priv->supplies[i].supply = supply_names[i];
+
+ ret = regulator_bulk_get(wm8350->dev, ARRAY_SIZE(priv->supplies),
+ priv->supplies);
+ if (ret != 0)
+ goto err_priv;
+
+ wm8350->codec.codec = codec;
+ codec->control_data = wm8350;
+
+ /* Put the codec into reset if it wasn't already */
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
+
+ INIT_DELAYED_WORK(&codec->dapm.delayed_work, wm8350_pga_work);
+ INIT_DELAYED_WORK(&priv->hpl.work, wm8350_hpl_work);
+ INIT_DELAYED_WORK(&priv->hpr.work, wm8350_hpr_work);
+
+ /* Enable the codec */
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
+
+ /* Enable robust clocking mode in ADC */
+ wm8350_codec_write(codec, WM8350_SECURITY, 0xa7);
+ wm8350_codec_write(codec, 0xde, 0x13);
+ wm8350_codec_write(codec, WM8350_SECURITY, 0);
+
+ /* read OUT1 & OUT2 volumes */
+ out1 = &priv->out1;
+ out2 = &priv->out2;
+ out1->left_vol = (wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME) &
+ WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+ out1->right_vol = (wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME) &
+ WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+ out2->left_vol = (wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME) &
+ WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+ out2->right_vol = (wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME) &
+ WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+ wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, 0);
+ wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, 0);
+ wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, 0);
+ wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, 0);
+
+ /* Latch VU bits & mute */
+ wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME,
+ WM8350_OUT1_VU | WM8350_OUT1L_MUTE);
+ wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME,
+ WM8350_OUT2_VU | WM8350_OUT2L_MUTE);
+ wm8350_set_bits(wm8350, WM8350_ROUT1_VOLUME,
+ WM8350_OUT1_VU | WM8350_OUT1R_MUTE);
+ wm8350_set_bits(wm8350, WM8350_ROUT2_VOLUME,
+ WM8350_OUT2_VU | WM8350_OUT2R_MUTE);
+
+ /* Make sure AIF tristating is disabled by default */
+ wm8350_clear_bits(wm8350, WM8350_AI_FORMATING, WM8350_AIF_TRI);
+
+ /* Make sure we've got a sane companding setup too */
+ wm8350_clear_bits(wm8350, WM8350_ADC_DAC_COMP,
+ WM8350_DAC_COMP | WM8350_LOOPBACK);
+
+ /* Make sure jack detect is disabled to start off with */
+ wm8350_clear_bits(wm8350, WM8350_JACK_DETECT,
+ WM8350_JDL_ENA | WM8350_JDR_ENA);
+
+ wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L,
+ wm8350_hp_jack_handler, 0, "Left jack detect",
+ priv);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R,
+ wm8350_hp_jack_handler, 0, "Right jack detect",
+ priv);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICSCD,
+ wm8350_mic_handler, 0, "Microphone short", priv);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICD,
+ wm8350_mic_handler, 0, "Microphone detect", priv);
+
+
+ snd_soc_add_controls(codec, wm8350_snd_controls,
+ ARRAY_SIZE(wm8350_snd_controls));
+ wm8350_add_widgets(codec);
+
+ wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+
+err_priv:
+ kfree(priv);
+ return ret;
+}
+
+static int wm8350_codec_remove(struct snd_soc_codec *codec)
+{
+ struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct wm8350 *wm8350 = dev_get_platdata(codec->dev);
+
+ wm8350_clear_bits(wm8350, WM8350_JACK_DETECT,
+ WM8350_JDL_ENA | WM8350_JDR_ENA);
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
+
+ wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICD, priv);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICSCD, priv);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, priv);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, priv);
+
+ priv->hpl.jack = NULL;
+ priv->hpr.jack = NULL;
+ priv->mic.jack = NULL;
+
+ cancel_delayed_work_sync(&priv->hpl.work);
+ cancel_delayed_work_sync(&priv->hpr.work);
+
+ /* if there was any work waiting then we run it now and
+ * wait for its completion */
+ flush_delayed_work_sync(&codec->dapm.delayed_work);
+
+ wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
+
+ regulator_bulk_free(ARRAY_SIZE(priv->supplies), priv->supplies);
+ kfree(priv);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8350 = {
+ .probe = wm8350_codec_probe,
+ .remove = wm8350_codec_remove,
+ .suspend = wm8350_suspend,
+ .resume = wm8350_resume,
+ .read = wm8350_codec_read,
+ .write = wm8350_codec_write,
+ .set_bias_level = wm8350_set_bias_level,
+};
+
+static int __devinit wm8350_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8350,
+ &wm8350_dai, 1);
+}
+
+static int __devexit wm8350_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver wm8350_codec_driver = {
+ .driver = {
+ .name = "wm8350-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8350_probe,
+ .remove = __devexit_p(wm8350_remove),
+};
+
+static __init int wm8350_init(void)
+{
+ return platform_driver_register(&wm8350_codec_driver);
+}
+module_init(wm8350_init);
+
+static __exit void wm8350_exit(void)
+{
+ platform_driver_unregister(&wm8350_codec_driver);
+}
+module_exit(wm8350_exit);
+
+MODULE_DESCRIPTION("ASoC WM8350 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm8350-codec");
diff --git a/sound/soc/codecs/wm8350.h b/sound/soc/codecs/wm8350.h
new file mode 100644
index 00000000..74108eb8
--- /dev/null
+++ b/sound/soc/codecs/wm8350.h
@@ -0,0 +1,29 @@
+/*
+ * wm8350.h - WM8903 audio codec interface
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _WM8350_H
+#define _WM8350_H
+
+#include <sound/soc.h>
+#include <linux/mfd/wm8350/audio.h>
+
+enum wm8350_jack {
+ WM8350_JDL = 1,
+ WM8350_JDR = 2,
+};
+
+int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which,
+ struct snd_soc_jack *jack, int report);
+int wm8350_mic_jack_detect(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack,
+ int detect_report, int short_report);
+
+#endif
diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c
new file mode 100644
index 00000000..fbee556c
--- /dev/null
+++ b/sound/soc/codecs/wm8400.c
@@ -0,0 +1,1495 @@
+/*
+ * wm8400.c -- WM8400 ALSA Soc Audio driver
+ *
+ * Copyright 2008, 2009 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mfd/wm8400-audio.h>
+#include <linux/mfd/wm8400-private.h>
+#include <linux/mfd/core.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8400.h"
+
+/* Fake register for internal state */
+#define WM8400_INTDRIVBITS (WM8400_REGISTER_COUNT + 1)
+#define WM8400_INMIXL_PWR 0
+#define WM8400_AINLMUX_PWR 1
+#define WM8400_INMIXR_PWR 2
+#define WM8400_AINRMUX_PWR 3
+
+static struct regulator_bulk_data power[] = {
+ {
+ .supply = "I2S1VDD",
+ },
+ {
+ .supply = "I2S2VDD",
+ },
+ {
+ .supply = "DCVDD",
+ },
+ {
+ .supply = "AVDD",
+ },
+ {
+ .supply = "FLLVDD",
+ },
+ {
+ .supply = "HPVDD",
+ },
+ {
+ .supply = "SPKVDD",
+ },
+};
+
+/* codec private data */
+struct wm8400_priv {
+ struct snd_soc_codec *codec;
+ struct wm8400 *wm8400;
+ u16 fake_register;
+ unsigned int sysclk;
+ unsigned int pcmclk;
+ struct work_struct work;
+ int fll_in, fll_out;
+};
+
+static inline unsigned int wm8400_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+
+ if (reg == WM8400_INTDRIVBITS)
+ return wm8400->fake_register;
+ else
+ return wm8400_reg_read(wm8400->wm8400, reg);
+}
+
+/*
+ * write to the wm8400 register space
+ */
+static int wm8400_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+
+ if (reg == WM8400_INTDRIVBITS) {
+ wm8400->fake_register = value;
+ return 0;
+ } else
+ return wm8400_set_bits(wm8400->wm8400, reg, 0xffff, value);
+}
+
+static void wm8400_codec_reset(struct snd_soc_codec *codec)
+{
+ struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+
+ wm8400_reset_codec_reg_cache(wm8400->wm8400);
+}
+
+static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1650, 3000, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -2100, 0, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -7300, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_omix_tlv, -600, 0, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_dac_tlv, -7163, 0, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_adc_tlv, -7163, 1763, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_sidetone_tlv, -3600, 0, 0);
+
+static int wm8400_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int reg = mc->reg;
+ int ret;
+ u16 val;
+
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* now hit the volume update bits (always bit 8) */
+ val = wm8400_read(codec, reg);
+ return wm8400_write(codec, reg, val | 0x0100);
+}
+
+#define WM8400_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_get_volsw, .put = wm8400_outpga_put_volsw_vu, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+static const char *wm8400_digital_sidetone[] =
+ {"None", "Left ADC", "Right ADC", "Reserved"};
+
+static const struct soc_enum wm8400_left_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8400_DIGITAL_SIDE_TONE,
+ WM8400_ADC_TO_DACL_SHIFT, 2, wm8400_digital_sidetone);
+
+static const struct soc_enum wm8400_right_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8400_DIGITAL_SIDE_TONE,
+ WM8400_ADC_TO_DACR_SHIFT, 2, wm8400_digital_sidetone);
+
+static const char *wm8400_adcmode[] =
+ {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"};
+
+static const struct soc_enum wm8400_right_adcmode_enum =
+SOC_ENUM_SINGLE(WM8400_ADC_CTRL, WM8400_ADC_HPF_CUT_SHIFT, 3, wm8400_adcmode);
+
+static const struct snd_kcontrol_new wm8400_snd_controls[] = {
+/* INMIXL */
+SOC_SINGLE("LIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L12MNBST_SHIFT,
+ 1, 0),
+SOC_SINGLE("LIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L34MNBST_SHIFT,
+ 1, 0),
+/* INMIXR */
+SOC_SINGLE("RIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R12MNBST_SHIFT,
+ 1, 0),
+SOC_SINGLE("RIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R34MNBST_SHIFT,
+ 1, 0),
+
+/* LOMIX */
+SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER3,
+ WM8400_LLI3LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3,
+ WM8400_LR12LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3,
+ WM8400_LL12LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER5,
+ WM8400_LRI3LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER5,
+ WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER5,
+ WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv),
+
+/* ROMIX */
+SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER4,
+ WM8400_RRI3ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4,
+ WM8400_RL12ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4,
+ WM8400_RR12ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER6,
+ WM8400_RLI3ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER6,
+ WM8400_RLBROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER6,
+ WM8400_RRBROVOL_SHIFT, 7, 0, out_mix_tlv),
+
+/* LOUT */
+WM8400_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8400_LEFT_OUTPUT_VOLUME,
+ WM8400_LOUTVOL_SHIFT, WM8400_LOUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOUT ZC", WM8400_LEFT_OUTPUT_VOLUME, WM8400_LOZC_SHIFT, 1, 0),
+
+/* ROUT */
+WM8400_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8400_RIGHT_OUTPUT_VOLUME,
+ WM8400_ROUTVOL_SHIFT, WM8400_ROUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROUT ZC", WM8400_RIGHT_OUTPUT_VOLUME, WM8400_ROZC_SHIFT, 1, 0),
+
+/* LOPGA */
+WM8400_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8400_LEFT_OPGA_VOLUME,
+ WM8400_LOPGAVOL_SHIFT, WM8400_LOPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOPGA ZC Switch", WM8400_LEFT_OPGA_VOLUME,
+ WM8400_LOPGAZC_SHIFT, 1, 0),
+
+/* ROPGA */
+WM8400_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8400_RIGHT_OPGA_VOLUME,
+ WM8400_ROPGAVOL_SHIFT, WM8400_ROPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROPGA ZC Switch", WM8400_RIGHT_OPGA_VOLUME,
+ WM8400_ROPGAZC_SHIFT, 1, 0),
+
+SOC_SINGLE("LON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_LONMUTE_SHIFT, 1, 0),
+SOC_SINGLE("LOP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_LOPMUTE_SHIFT, 1, 0),
+SOC_SINGLE("LOP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_LOATTN_SHIFT, 1, 0),
+SOC_SINGLE("RON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_RONMUTE_SHIFT, 1, 0),
+SOC_SINGLE("ROP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_ROPMUTE_SHIFT, 1, 0),
+SOC_SINGLE("ROP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_ROATTN_SHIFT, 1, 0),
+
+SOC_SINGLE("OUT3 Mute Switch", WM8400_OUT3_4_VOLUME,
+ WM8400_OUT3MUTE_SHIFT, 1, 0),
+SOC_SINGLE("OUT3 Attenuation Switch", WM8400_OUT3_4_VOLUME,
+ WM8400_OUT3ATTN_SHIFT, 1, 0),
+
+SOC_SINGLE("OUT4 Mute Switch", WM8400_OUT3_4_VOLUME,
+ WM8400_OUT4MUTE_SHIFT, 1, 0),
+SOC_SINGLE("OUT4 Attenuation Switch", WM8400_OUT3_4_VOLUME,
+ WM8400_OUT4ATTN_SHIFT, 1, 0),
+
+SOC_SINGLE("Speaker Mode Switch", WM8400_CLASSD1,
+ WM8400_CDMODE_SHIFT, 1, 0),
+
+SOC_SINGLE("Speaker Output Attenuation Volume", WM8400_SPEAKER_VOLUME,
+ WM8400_SPKATTN_SHIFT, WM8400_SPKATTN_MASK, 0),
+SOC_SINGLE("Speaker DC Boost Volume", WM8400_CLASSD3,
+ WM8400_DCGAIN_SHIFT, 6, 0),
+SOC_SINGLE("Speaker AC Boost Volume", WM8400_CLASSD3,
+ WM8400_ACGAIN_SHIFT, 6, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume",
+ WM8400_LEFT_DAC_DIGITAL_VOLUME, WM8400_DACL_VOL_SHIFT,
+ 127, 0, out_dac_tlv),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume",
+ WM8400_RIGHT_DAC_DIGITAL_VOLUME, WM8400_DACR_VOL_SHIFT,
+ 127, 0, out_dac_tlv),
+
+SOC_ENUM("Left Digital Sidetone", wm8400_left_digital_sidetone_enum),
+SOC_ENUM("Right Digital Sidetone", wm8400_right_digital_sidetone_enum),
+
+SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE,
+ WM8400_ADCL_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv),
+SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE,
+ WM8400_ADCR_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv),
+
+SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8400_ADC_CTRL,
+ WM8400_ADC_HPF_ENA_SHIFT, 1, 0),
+
+SOC_ENUM("ADC HPF Mode", wm8400_right_adcmode_enum),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume",
+ WM8400_LEFT_ADC_DIGITAL_VOLUME,
+ WM8400_ADCL_VOL_SHIFT,
+ WM8400_ADCL_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume",
+ WM8400_RIGHT_ADC_DIGITAL_VOLUME,
+ WM8400_ADCR_VOL_SHIFT,
+ WM8400_ADCR_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+WM8400_OUTPGA_SINGLE_R_TLV("LIN12 Volume",
+ WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8400_LIN12VOL_SHIFT,
+ WM8400_LIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("LIN12 ZC Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8400_LI12ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("LIN12 Mute Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8400_LI12MUTE_SHIFT, 1, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("LIN34 Volume",
+ WM8400_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8400_LIN34VOL_SHIFT,
+ WM8400_LIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("LIN34 ZC Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8400_LI34ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("LIN34 Mute Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8400_LI34MUTE_SHIFT, 1, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("RIN12 Volume",
+ WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8400_RIN12VOL_SHIFT,
+ WM8400_RIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("RIN12 ZC Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8400_RI12ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("RIN12 Mute Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8400_RI12MUTE_SHIFT, 1, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("RIN34 Volume",
+ WM8400_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8400_RIN34VOL_SHIFT,
+ WM8400_RIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("RIN34 ZC Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8400_RI34ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("RIN34 Mute Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8400_RI34MUTE_SHIFT, 1, 0),
+
+};
+
+/* add non dapm controls */
+static int wm8400_add_controls(struct snd_soc_codec *codec)
+{
+ return snd_soc_add_controls(codec, wm8400_snd_controls,
+ ARRAY_SIZE(wm8400_snd_controls));
+}
+
+/*
+ * _DAPM_ Controls
+ */
+
+static int inmixer_event (struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u16 reg, fakepower;
+
+ reg = wm8400_read(w->codec, WM8400_POWER_MANAGEMENT_2);
+ fakepower = wm8400_read(w->codec, WM8400_INTDRIVBITS);
+
+ if (fakepower & ((1 << WM8400_INMIXL_PWR) |
+ (1 << WM8400_AINLMUX_PWR))) {
+ reg |= WM8400_AINL_ENA;
+ } else {
+ reg &= ~WM8400_AINL_ENA;
+ }
+
+ if (fakepower & ((1 << WM8400_INMIXR_PWR) |
+ (1 << WM8400_AINRMUX_PWR))) {
+ reg |= WM8400_AINR_ENA;
+ } else {
+ reg &= ~WM8400_AINL_ENA;
+ }
+ wm8400_write(w->codec, WM8400_POWER_MANAGEMENT_2, reg);
+
+ return 0;
+}
+
+static int outmixer_event (struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol * kcontrol, int event)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ u32 reg_shift = mc->shift;
+ int ret = 0;
+ u16 reg;
+
+ switch (reg_shift) {
+ case WM8400_SPEAKER_MIXER | (WM8400_LDSPK << 8) :
+ reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER1);
+ if (reg & WM8400_LDLO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 1 LDLO Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8400_SPEAKER_MIXER | (WM8400_RDSPK << 8):
+ reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER2);
+ if (reg & WM8400_RDRO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 2 RDRO Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8400_OUTPUT_MIXER1 | (WM8400_LDLO << 8):
+ reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER);
+ if (reg & WM8400_LDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer LDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8400_OUTPUT_MIXER2 | (WM8400_RDRO << 8):
+ reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER);
+ if (reg & WM8400_RDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer RDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/* INMIX dB values */
+static const unsigned int in_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0,7, TLV_DB_SCALE_ITEM(-1200, 600, 0),
+};
+
+/* Left In PGA Connections */
+static const struct snd_kcontrol_new wm8400_dapm_lin12_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN1 Switch", WM8400_INPUT_MIXER2, WM8400_LMN1_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LIN2 Switch", WM8400_INPUT_MIXER2, WM8400_LMP2_SHIFT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8400_dapm_lin34_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN3 Switch", WM8400_INPUT_MIXER2, WM8400_LMN3_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LIN4 Switch", WM8400_INPUT_MIXER2, WM8400_LMP4_SHIFT, 1, 0),
+};
+
+/* Right In PGA Connections */
+static const struct snd_kcontrol_new wm8400_dapm_rin12_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN1 Switch", WM8400_INPUT_MIXER2, WM8400_RMN1_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RIN2 Switch", WM8400_INPUT_MIXER2, WM8400_RMP2_SHIFT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8400_dapm_rin34_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN3 Switch", WM8400_INPUT_MIXER2, WM8400_RMN3_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RIN4 Switch", WM8400_INPUT_MIXER2, WM8400_RMP4_SHIFT, 1, 0),
+};
+
+/* INMIXL */
+static const struct snd_kcontrol_new wm8400_dapm_inmixl_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8400_INPUT_MIXER3,
+ WM8400_LDBVOL_SHIFT, WM8400_LDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8400_INPUT_MIXER5, WM8400_LI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("LINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT,
+ 1, 0),
+SOC_DAPM_SINGLE("LINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT,
+ 1, 0),
+};
+
+/* INMIXR */
+static const struct snd_kcontrol_new wm8400_dapm_inmixr_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8400_INPUT_MIXER4,
+ WM8400_RDBVOL_SHIFT, WM8400_RDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8400_INPUT_MIXER6, WM8400_RI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("RINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT,
+ 1, 0),
+SOC_DAPM_SINGLE("RINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT,
+ 1, 0),
+};
+
+/* AINLMUX */
+static const char *wm8400_ainlmux[] =
+ {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"};
+
+static const struct soc_enum wm8400_ainlmux_enum =
+SOC_ENUM_SINGLE( WM8400_INPUT_MIXER1, WM8400_AINLMODE_SHIFT,
+ ARRAY_SIZE(wm8400_ainlmux), wm8400_ainlmux);
+
+static const struct snd_kcontrol_new wm8400_dapm_ainlmux_controls =
+SOC_DAPM_ENUM("Route", wm8400_ainlmux_enum);
+
+/* DIFFINL */
+
+/* AINRMUX */
+static const char *wm8400_ainrmux[] =
+ {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"};
+
+static const struct soc_enum wm8400_ainrmux_enum =
+SOC_ENUM_SINGLE( WM8400_INPUT_MIXER1, WM8400_AINRMODE_SHIFT,
+ ARRAY_SIZE(wm8400_ainrmux), wm8400_ainrmux);
+
+static const struct snd_kcontrol_new wm8400_dapm_ainrmux_controls =
+SOC_DAPM_ENUM("Route", wm8400_ainrmux_enum);
+
+/* RXVOICE */
+static const struct snd_kcontrol_new wm8400_dapm_rxvoice_controls[] = {
+SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8400_INPUT_MIXER5, WM8400_LR4BVOL_SHIFT,
+ WM8400_LR4BVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8400_INPUT_MIXER6, WM8400_RL4BVOL_SHIFT,
+ WM8400_RL4BVOL_MASK, 0, in_mix_tlv),
+};
+
+/* LOMIX */
+static const struct snd_kcontrol_new wm8400_dapm_lomix_controls[] = {
+SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LRBLO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LLBLO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LRI3LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LLI3LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LR12LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LL12LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LDLO_SHIFT, 1, 0),
+};
+
+/* ROMIX */
+static const struct snd_kcontrol_new wm8400_dapm_romix_controls[] = {
+SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RLBRO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RRBRO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RLI3RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RRI3RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RL12RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RR12RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RDRO_SHIFT, 1, 0),
+};
+
+/* LONMIX */
+static const struct snd_kcontrol_new wm8400_dapm_lonmix_controls[] = {
+SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1,
+ WM8400_LLOPGALON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER1,
+ WM8400_LROPGALON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8400_LINE_MIXER1,
+ WM8400_LOPLON_SHIFT, 1, 0),
+};
+
+/* LOPMIX */
+static const struct snd_kcontrol_new wm8400_dapm_lopmix_controls[] = {
+SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER1,
+ WM8400_LR12LOP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER1,
+ WM8400_LL12LOP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1,
+ WM8400_LLOPGALOP_SHIFT, 1, 0),
+};
+
+/* RONMIX */
+static const struct snd_kcontrol_new wm8400_dapm_ronmix_controls[] = {
+SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2,
+ WM8400_RROPGARON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER2,
+ WM8400_RLOPGARON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8400_LINE_MIXER2,
+ WM8400_ROPRON_SHIFT, 1, 0),
+};
+
+/* ROPMIX */
+static const struct snd_kcontrol_new wm8400_dapm_ropmix_controls[] = {
+SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER2,
+ WM8400_RL12ROP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER2,
+ WM8400_RR12ROP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2,
+ WM8400_RROPGAROP_SHIFT, 1, 0),
+};
+
+/* OUT3MIX */
+static const struct snd_kcontrol_new wm8400_dapm_out3mix_controls[] = {
+SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER,
+ WM8400_LI4O3_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8400_OUT3_4_MIXER,
+ WM8400_LPGAO3_SHIFT, 1, 0),
+};
+
+/* OUT4MIX */
+static const struct snd_kcontrol_new wm8400_dapm_out4mix_controls[] = {
+SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8400_OUT3_4_MIXER,
+ WM8400_RPGAO4_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER,
+ WM8400_RI4O4_SHIFT, 1, 0),
+};
+
+/* SPKMIX */
+static const struct snd_kcontrol_new wm8400_dapm_spkmix_controls[] = {
+SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8400_SPEAKER_MIXER,
+ WM8400_LI2SPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8400_SPEAKER_MIXER,
+ WM8400_LB2SPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8400_SPEAKER_MIXER,
+ WM8400_LOPGASPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8400_SPEAKER_MIXER,
+ WM8400_LDSPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8400_SPEAKER_MIXER,
+ WM8400_RDSPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8400_SPEAKER_MIXER,
+ WM8400_ROPGASPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8400_SPEAKER_MIXER,
+ WM8400_RL12ROP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8400_SPEAKER_MIXER,
+ WM8400_RI2SPK_SHIFT, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8400_dapm_widgets[] = {
+/* Input Side */
+/* Input Lines */
+SND_SOC_DAPM_INPUT("LIN1"),
+SND_SOC_DAPM_INPUT("LIN2"),
+SND_SOC_DAPM_INPUT("LIN3"),
+SND_SOC_DAPM_INPUT("LIN4/RXN"),
+SND_SOC_DAPM_INPUT("RIN3"),
+SND_SOC_DAPM_INPUT("RIN4/RXP"),
+SND_SOC_DAPM_INPUT("RIN1"),
+SND_SOC_DAPM_INPUT("RIN2"),
+SND_SOC_DAPM_INPUT("Internal ADC Source"),
+
+/* DACs */
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8400_POWER_MANAGEMENT_2,
+ WM8400_ADCL_ENA_SHIFT, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8400_POWER_MANAGEMENT_2,
+ WM8400_ADCR_ENA_SHIFT, 0),
+
+/* Input PGAs */
+SND_SOC_DAPM_MIXER("LIN12 PGA", WM8400_POWER_MANAGEMENT_2,
+ WM8400_LIN12_ENA_SHIFT,
+ 0, &wm8400_dapm_lin12_pga_controls[0],
+ ARRAY_SIZE(wm8400_dapm_lin12_pga_controls)),
+SND_SOC_DAPM_MIXER("LIN34 PGA", WM8400_POWER_MANAGEMENT_2,
+ WM8400_LIN34_ENA_SHIFT,
+ 0, &wm8400_dapm_lin34_pga_controls[0],
+ ARRAY_SIZE(wm8400_dapm_lin34_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN12 PGA", WM8400_POWER_MANAGEMENT_2,
+ WM8400_RIN12_ENA_SHIFT,
+ 0, &wm8400_dapm_rin12_pga_controls[0],
+ ARRAY_SIZE(wm8400_dapm_rin12_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN34 PGA", WM8400_POWER_MANAGEMENT_2,
+ WM8400_RIN34_ENA_SHIFT,
+ 0, &wm8400_dapm_rin34_pga_controls[0],
+ ARRAY_SIZE(wm8400_dapm_rin34_pga_controls)),
+
+/* INMIXL */
+SND_SOC_DAPM_MIXER_E("INMIXL", WM8400_INTDRIVBITS, WM8400_INMIXL_PWR, 0,
+ &wm8400_dapm_inmixl_controls[0],
+ ARRAY_SIZE(wm8400_dapm_inmixl_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINLMUX */
+SND_SOC_DAPM_MUX_E("AILNMUX", WM8400_INTDRIVBITS, WM8400_AINLMUX_PWR, 0,
+ &wm8400_dapm_ainlmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* INMIXR */
+SND_SOC_DAPM_MIXER_E("INMIXR", WM8400_INTDRIVBITS, WM8400_INMIXR_PWR, 0,
+ &wm8400_dapm_inmixr_controls[0],
+ ARRAY_SIZE(wm8400_dapm_inmixr_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINRMUX */
+SND_SOC_DAPM_MUX_E("AIRNMUX", WM8400_INTDRIVBITS, WM8400_AINRMUX_PWR, 0,
+ &wm8400_dapm_ainrmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* Output Side */
+/* DACs */
+SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8400_POWER_MANAGEMENT_3,
+ WM8400_DACL_ENA_SHIFT, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8400_POWER_MANAGEMENT_3,
+ WM8400_DACR_ENA_SHIFT, 0),
+
+/* LOMIX */
+SND_SOC_DAPM_MIXER_E("LOMIX", WM8400_POWER_MANAGEMENT_3,
+ WM8400_LOMIX_ENA_SHIFT,
+ 0, &wm8400_dapm_lomix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_lomix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LONMIX */
+SND_SOC_DAPM_MIXER("LONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LON_ENA_SHIFT,
+ 0, &wm8400_dapm_lonmix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_lonmix_controls)),
+
+/* LOPMIX */
+SND_SOC_DAPM_MIXER("LOPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LOP_ENA_SHIFT,
+ 0, &wm8400_dapm_lopmix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_lopmix_controls)),
+
+/* OUT3MIX */
+SND_SOC_DAPM_MIXER("OUT3MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT3_ENA_SHIFT,
+ 0, &wm8400_dapm_out3mix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_out3mix_controls)),
+
+/* SPKMIX */
+SND_SOC_DAPM_MIXER_E("SPKMIX", WM8400_POWER_MANAGEMENT_1, WM8400_SPK_ENA_SHIFT,
+ 0, &wm8400_dapm_spkmix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_spkmix_controls), outmixer_event,
+ SND_SOC_DAPM_PRE_REG),
+
+/* OUT4MIX */
+SND_SOC_DAPM_MIXER("OUT4MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT4_ENA_SHIFT,
+ 0, &wm8400_dapm_out4mix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_out4mix_controls)),
+
+/* ROPMIX */
+SND_SOC_DAPM_MIXER("ROPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_ROP_ENA_SHIFT,
+ 0, &wm8400_dapm_ropmix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_ropmix_controls)),
+
+/* RONMIX */
+SND_SOC_DAPM_MIXER("RONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_RON_ENA_SHIFT,
+ 0, &wm8400_dapm_ronmix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_ronmix_controls)),
+
+/* ROMIX */
+SND_SOC_DAPM_MIXER_E("ROMIX", WM8400_POWER_MANAGEMENT_3,
+ WM8400_ROMIX_ENA_SHIFT,
+ 0, &wm8400_dapm_romix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_romix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LOUT PGA */
+SND_SOC_DAPM_PGA("LOUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_LOUT_ENA_SHIFT,
+ 0, NULL, 0),
+
+/* ROUT PGA */
+SND_SOC_DAPM_PGA("ROUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_ROUT_ENA_SHIFT,
+ 0, NULL, 0),
+
+/* LOPGA */
+SND_SOC_DAPM_PGA("LOPGA", WM8400_POWER_MANAGEMENT_3, WM8400_LOPGA_ENA_SHIFT, 0,
+ NULL, 0),
+
+/* ROPGA */
+SND_SOC_DAPM_PGA("ROPGA", WM8400_POWER_MANAGEMENT_3, WM8400_ROPGA_ENA_SHIFT, 0,
+ NULL, 0),
+
+/* MICBIAS */
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8400_POWER_MANAGEMENT_1,
+ WM8400_MIC1BIAS_ENA_SHIFT, 0),
+
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("SPKN"),
+SND_SOC_DAPM_OUTPUT("SPKP"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_OUTPUT("Internal DAC Sink"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Make DACs turn on when playing even if not mixed into any outputs */
+ {"Internal DAC Sink", NULL, "Left DAC"},
+ {"Internal DAC Sink", NULL, "Right DAC"},
+
+ /* Make ADCs turn on when recording
+ * even if not mixed from any inputs */
+ {"Left ADC", NULL, "Internal ADC Source"},
+ {"Right ADC", NULL, "Internal ADC Source"},
+
+ /* Input Side */
+ /* LIN12 PGA */
+ {"LIN12 PGA", "LIN1 Switch", "LIN1"},
+ {"LIN12 PGA", "LIN2 Switch", "LIN2"},
+ /* LIN34 PGA */
+ {"LIN34 PGA", "LIN3 Switch", "LIN3"},
+ {"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"},
+ /* INMIXL */
+ {"INMIXL", "Record Left Volume", "LOMIX"},
+ {"INMIXL", "LIN2 Volume", "LIN2"},
+ {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"},
+ {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"},
+ /* AILNMUX */
+ {"AILNMUX", "INMIXL Mix", "INMIXL"},
+ {"AILNMUX", "DIFFINL Mix", "LIN12 PGA"},
+ {"AILNMUX", "DIFFINL Mix", "LIN34 PGA"},
+ {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"},
+ {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"},
+ /* ADC */
+ {"Left ADC", NULL, "AILNMUX"},
+
+ /* RIN12 PGA */
+ {"RIN12 PGA", "RIN1 Switch", "RIN1"},
+ {"RIN12 PGA", "RIN2 Switch", "RIN2"},
+ /* RIN34 PGA */
+ {"RIN34 PGA", "RIN3 Switch", "RIN3"},
+ {"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"},
+ /* INMIXL */
+ {"INMIXR", "Record Right Volume", "ROMIX"},
+ {"INMIXR", "RIN2 Volume", "RIN2"},
+ {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"},
+ {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"},
+ /* AIRNMUX */
+ {"AIRNMUX", "INMIXR Mix", "INMIXR"},
+ {"AIRNMUX", "DIFFINR Mix", "RIN12 PGA"},
+ {"AIRNMUX", "DIFFINR Mix", "RIN34 PGA"},
+ {"AIRNMUX", "RXVOICE Mix", "LIN4/RXN"},
+ {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"},
+ /* ADC */
+ {"Right ADC", NULL, "AIRNMUX"},
+
+ /* LOMIX */
+ {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"},
+ {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"},
+ {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"LOMIX", "LOMIX Right ADC Bypass Switch", "AIRNMUX"},
+ {"LOMIX", "LOMIX Left ADC Bypass Switch", "AILNMUX"},
+ {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"},
+
+ /* ROMIX */
+ {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"},
+ {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"},
+ {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"ROMIX", "ROMIX Right ADC Bypass Switch", "AIRNMUX"},
+ {"ROMIX", "ROMIX Left ADC Bypass Switch", "AILNMUX"},
+ {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"},
+
+ /* SPKMIX */
+ {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"},
+ {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"},
+ {"SPKMIX", "SPKMIX LADC Bypass Switch", "AILNMUX"},
+ {"SPKMIX", "SPKMIX RADC Bypass Switch", "AIRNMUX"},
+ {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"},
+ {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"},
+ {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"},
+ {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"},
+
+ /* LONMIX */
+ {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"},
+
+ /* LOPMIX */
+ {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"},
+
+ /* OUT3MIX */
+ {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"},
+ {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"},
+
+ /* OUT4MIX */
+ {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"},
+ {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"},
+
+ /* RONMIX */
+ {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"},
+
+ /* ROPMIX */
+ {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"},
+
+ /* Out Mixer PGAs */
+ {"LOPGA", NULL, "LOMIX"},
+ {"ROPGA", NULL, "ROMIX"},
+
+ {"LOUT PGA", NULL, "LOMIX"},
+ {"ROUT PGA", NULL, "ROMIX"},
+
+ /* Output Pins */
+ {"LON", NULL, "LONMIX"},
+ {"LOP", NULL, "LOPMIX"},
+ {"OUT3", NULL, "OUT3MIX"},
+ {"LOUT", NULL, "LOUT PGA"},
+ {"SPKN", NULL, "SPKMIX"},
+ {"ROUT", NULL, "ROUT PGA"},
+ {"OUT4", NULL, "OUT4MIX"},
+ {"ROP", NULL, "ROPMIX"},
+ {"RON", NULL, "RONMIX"},
+};
+
+static int wm8400_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8400_dapm_widgets,
+ ARRAY_SIZE(wm8400_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+/*
+ * Clock after FLL and dividers
+ */
+static int wm8400_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+
+ wm8400->sysclk = freq;
+ return 0;
+}
+
+struct fll_factors {
+ u16 n;
+ u16 k;
+ u16 outdiv;
+ u16 fratio;
+ u16 freq_ref;
+};
+
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors,
+ unsigned int Fref, unsigned int Fout)
+{
+ u64 Kpart;
+ unsigned int K, Nmod, target;
+
+ factors->outdiv = 2;
+ while (Fout * factors->outdiv < 90000000 ||
+ Fout * factors->outdiv > 100000000) {
+ factors->outdiv *= 2;
+ if (factors->outdiv > 32) {
+ dev_err(wm8400->wm8400->dev,
+ "Unsupported FLL output frequency %uHz\n",
+ Fout);
+ return -EINVAL;
+ }
+ }
+ target = Fout * factors->outdiv;
+ factors->outdiv = factors->outdiv >> 2;
+
+ if (Fref < 48000)
+ factors->freq_ref = 1;
+ else
+ factors->freq_ref = 0;
+
+ if (Fref < 1000000)
+ factors->fratio = 9;
+ else
+ factors->fratio = 0;
+
+ /* Ensure we have a fractional part */
+ do {
+ if (Fref < 1000000)
+ factors->fratio--;
+ else
+ factors->fratio++;
+
+ if (factors->fratio < 1 || factors->fratio > 8) {
+ dev_err(wm8400->wm8400->dev,
+ "Unable to calculate FRATIO\n");
+ return -EINVAL;
+ }
+
+ factors->n = target / (Fref * factors->fratio);
+ Nmod = target % (Fref * factors->fratio);
+ } while (Nmod == 0);
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, (Fref * factors->fratio));
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ factors->k = K / 10;
+
+ dev_dbg(wm8400->wm8400->dev,
+ "FLL: Fref=%u Fout=%u N=%x K=%x, FRATIO=%x OUTDIV=%x\n",
+ Fref, Fout,
+ factors->n, factors->k, factors->fratio, factors->outdiv);
+
+ return 0;
+}
+
+static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in,
+ unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+ struct fll_factors factors;
+ int ret;
+ u16 reg;
+
+ if (freq_in == wm8400->fll_in && freq_out == wm8400->fll_out)
+ return 0;
+
+ if (freq_out) {
+ ret = fll_factors(wm8400, &factors, freq_in, freq_out);
+ if (ret != 0)
+ return ret;
+ } else {
+ /* Bodge GCC 4.4.0 uninitialised variable warning - it
+ * doesn't seem capable of working out that we exit if
+ * freq_out is 0 before any of the uses. */
+ memset(&factors, 0, sizeof(factors));
+ }
+
+ wm8400->fll_out = freq_out;
+ wm8400->fll_in = freq_in;
+
+ /* We *must* disable the FLL before any changes */
+ reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_2);
+ reg &= ~WM8400_FLL_ENA;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_2, reg);
+
+ reg = wm8400_read(codec, WM8400_FLL_CONTROL_1);
+ reg &= ~WM8400_FLL_OSC_ENA;
+ wm8400_write(codec, WM8400_FLL_CONTROL_1, reg);
+
+ if (!freq_out)
+ return 0;
+
+ reg &= ~(WM8400_FLL_REF_FREQ | WM8400_FLL_FRATIO_MASK);
+ reg |= WM8400_FLL_FRAC | factors.fratio;
+ reg |= factors.freq_ref << WM8400_FLL_REF_FREQ_SHIFT;
+ wm8400_write(codec, WM8400_FLL_CONTROL_1, reg);
+
+ wm8400_write(codec, WM8400_FLL_CONTROL_2, factors.k);
+ wm8400_write(codec, WM8400_FLL_CONTROL_3, factors.n);
+
+ reg = wm8400_read(codec, WM8400_FLL_CONTROL_4);
+ reg &= WM8400_FLL_OUTDIV_MASK;
+ reg |= factors.outdiv;
+ wm8400_write(codec, WM8400_FLL_CONTROL_4, reg);
+
+ return 0;
+}
+
+/*
+ * Sets ADC and Voice DAC format.
+ */
+static int wm8400_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 audio1, audio3;
+
+ audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1);
+ audio3 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_3);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ audio3 &= ~WM8400_AIF_MSTR1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ audio3 |= WM8400_AIF_MSTR1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ audio1 &= ~WM8400_AIF_FMT_MASK;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ audio1 |= WM8400_AIF_FMT_I2S;
+ audio1 &= ~WM8400_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ audio1 |= WM8400_AIF_FMT_RIGHTJ;
+ audio1 &= ~WM8400_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ audio1 |= WM8400_AIF_FMT_LEFTJ;
+ audio1 &= ~WM8400_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ audio1 |= WM8400_AIF_FMT_DSP;
+ audio1 &= ~WM8400_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ audio1 |= WM8400_AIF_FMT_DSP | WM8400_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1);
+ wm8400_write(codec, WM8400_AUDIO_INTERFACE_3, audio3);
+ return 0;
+}
+
+static int wm8400_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8400_MCLK_DIV:
+ reg = wm8400_read(codec, WM8400_CLOCKING_2) &
+ ~WM8400_MCLK_DIV_MASK;
+ wm8400_write(codec, WM8400_CLOCKING_2, reg | div);
+ break;
+ case WM8400_DACCLK_DIV:
+ reg = wm8400_read(codec, WM8400_CLOCKING_2) &
+ ~WM8400_DAC_CLKDIV_MASK;
+ wm8400_write(codec, WM8400_CLOCKING_2, reg | div);
+ break;
+ case WM8400_ADCCLK_DIV:
+ reg = wm8400_read(codec, WM8400_CLOCKING_2) &
+ ~WM8400_ADC_CLKDIV_MASK;
+ wm8400_write(codec, WM8400_CLOCKING_2, reg | div);
+ break;
+ case WM8400_BCLK_DIV:
+ reg = wm8400_read(codec, WM8400_CLOCKING_1) &
+ ~WM8400_BCLK_DIV_MASK;
+ wm8400_write(codec, WM8400_CLOCKING_1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8400_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u16 audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1);
+
+ audio1 &= ~WM8400_AIF_WL_MASK;
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ audio1 |= WM8400_AIF_WL_20BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ audio1 |= WM8400_AIF_WL_24BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ audio1 |= WM8400_AIF_WL_32BITS;
+ break;
+ }
+
+ wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1);
+ return 0;
+}
+
+static int wm8400_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 val = wm8400_read(codec, WM8400_DAC_CTRL) & ~WM8400_DAC_MUTE;
+
+ if (mute)
+ wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE);
+ else
+ wm8400_write(codec, WM8400_DAC_CTRL, val);
+
+ return 0;
+}
+
+/* TODO: set bias for best performance at standby */
+static int wm8400_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+ u16 val;
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID=2*50k */
+ val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) &
+ ~WM8400_VMID_MODE_MASK;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x2);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(power),
+ &power[0]);
+ if (ret != 0) {
+ dev_err(wm8400->wm8400->dev,
+ "Failed to enable regulators: %d\n",
+ ret);
+ return ret;
+ }
+
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1,
+ WM8400_CODEC_ENA | WM8400_SYSCLK_ENA);
+
+ /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */
+ wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+ WM8400_BUFDCOPEN | WM8400_POBCTRL);
+
+ msleep(50);
+
+ /* Enable VREF & VMID at 2x50k */
+ val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1);
+ val |= 0x2 | WM8400_VREF_ENA;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+ /* Enable BUFIOEN */
+ wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+ WM8400_BUFDCOPEN | WM8400_POBCTRL |
+ WM8400_BUFIOEN);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8400_write(codec, WM8400_ANTIPOP2, WM8400_BUFIOEN);
+ }
+
+ /* VMID=2*300k */
+ val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) &
+ ~WM8400_VMID_MODE_MASK;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x4);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable POBCTRL and SOFT_ST */
+ wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+ WM8400_POBCTRL | WM8400_BUFIOEN);
+
+ /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+ WM8400_BUFDCOPEN | WM8400_POBCTRL |
+ WM8400_BUFIOEN);
+
+ /* mute DAC */
+ val = wm8400_read(codec, WM8400_DAC_CTRL);
+ wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE);
+
+ /* Enable any disabled outputs */
+ val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1);
+ val |= WM8400_SPK_ENA | WM8400_OUT3_ENA |
+ WM8400_OUT4_ENA | WM8400_LOUT_ENA |
+ WM8400_ROUT_ENA;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+ /* Disable VMID */
+ val &= ~WM8400_VMID_MODE_MASK;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+ msleep(300);
+
+ /* Enable all output discharge bits */
+ wm8400_write(codec, WM8400_ANTIPOP1, WM8400_DIS_LLINE |
+ WM8400_DIS_RLINE | WM8400_DIS_OUT3 |
+ WM8400_DIS_OUT4 | WM8400_DIS_LOUT |
+ WM8400_DIS_ROUT);
+
+ /* Disable VREF */
+ val &= ~WM8400_VREF_ENA;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8400_write(codec, WM8400_ANTIPOP2, 0x0);
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(power),
+ &power[0]);
+ if (ret != 0)
+ return ret;
+
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8400_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8400_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8400_dai_ops = {
+ .hw_params = wm8400_hw_params,
+ .digital_mute = wm8400_mute,
+ .set_fmt = wm8400_set_dai_fmt,
+ .set_clkdiv = wm8400_set_dai_clkdiv,
+ .set_sysclk = wm8400_set_dai_sysclk,
+ .set_pll = wm8400_set_dai_pll,
+};
+
+/*
+ * The WM8400 supports 2 different and mutually exclusive DAI
+ * configurations.
+ *
+ * 1. ADC/DAC on Primary Interface
+ * 2. ADC on Primary Interface/DAC on secondary
+ */
+static struct snd_soc_dai_driver wm8400_dai = {
+/* ADC/DAC on primary */
+ .name = "wm8400-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8400_RATES,
+ .formats = WM8400_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8400_RATES,
+ .formats = WM8400_FORMATS,
+ },
+ .ops = &wm8400_dai_ops,
+};
+
+static int wm8400_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8400_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8400_resume(struct snd_soc_codec *codec)
+{
+ wm8400_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static void wm8400_probe_deferred(struct work_struct *work)
+{
+ struct wm8400_priv *priv = container_of(work, struct wm8400_priv,
+ work);
+ struct snd_soc_codec *codec = priv->codec;
+
+ /* charge output caps */
+ wm8400_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+}
+
+static int wm8400_codec_probe(struct snd_soc_codec *codec)
+{
+ struct wm8400 *wm8400 = dev_get_platdata(codec->dev);
+ struct wm8400_priv *priv;
+ int ret;
+ u16 reg;
+
+ priv = kzalloc(sizeof(struct wm8400_priv), GFP_KERNEL);
+ if (priv == NULL)
+ return -ENOMEM;
+
+ snd_soc_codec_set_drvdata(codec, priv);
+ codec->control_data = priv->wm8400 = wm8400;
+ priv->codec = codec;
+
+ ret = regulator_bulk_get(wm8400->dev,
+ ARRAY_SIZE(power), &power[0]);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to get regulators: %d\n", ret);
+ goto err;
+ }
+
+ INIT_WORK(&priv->work, wm8400_probe_deferred);
+
+ wm8400_codec_reset(codec);
+
+ reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1);
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, reg | WM8400_CODEC_ENA);
+
+ /* Latch volume update bits */
+ reg = wm8400_read(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME);
+ wm8400_write(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+ reg & WM8400_IPVU);
+ reg = wm8400_read(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME);
+ wm8400_write(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+ reg & WM8400_IPVU);
+
+ wm8400_write(codec, WM8400_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8));
+ wm8400_write(codec, WM8400_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8));
+
+ if (!schedule_work(&priv->work)) {
+ ret = -EINVAL;
+ goto err_regulator;
+ }
+ wm8400_add_controls(codec);
+ wm8400_add_widgets(codec);
+ return 0;
+
+err_regulator:
+ regulator_bulk_free(ARRAY_SIZE(power), power);
+err:
+ kfree(priv);
+ return ret;
+}
+
+static int wm8400_codec_remove(struct snd_soc_codec *codec)
+{
+ struct wm8400_priv *priv = snd_soc_codec_get_drvdata(codec);
+ u16 reg;
+
+ reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1);
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1,
+ reg & (~WM8400_CODEC_ENA));
+
+ regulator_bulk_free(ARRAY_SIZE(power), power);
+ kfree(priv);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8400 = {
+ .probe = wm8400_codec_probe,
+ .remove = wm8400_codec_remove,
+ .suspend = wm8400_suspend,
+ .resume = wm8400_resume,
+ .read = wm8400_read,
+ .write = wm8400_write,
+ .set_bias_level = wm8400_set_bias_level,
+};
+
+static int __devinit wm8400_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8400,
+ &wm8400_dai, 1);
+}
+
+static int __devexit wm8400_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver wm8400_codec_driver = {
+ .driver = {
+ .name = "wm8400-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8400_probe,
+ .remove = __devexit_p(wm8400_remove),
+};
+
+static __init int wm8400_init(void)
+{
+ return platform_driver_register(&wm8400_codec_driver);
+}
+module_init(wm8400_init);
+
+static __exit void wm8400_exit(void)
+{
+ platform_driver_unregister(&wm8400_codec_driver);
+}
+module_exit(wm8400_exit);
+
+MODULE_DESCRIPTION("ASoC WM8400 driver");
+MODULE_AUTHOR("Mark Brown");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm8400-codec");
diff --git a/sound/soc/codecs/wm8400.h b/sound/soc/codecs/wm8400.h
new file mode 100644
index 00000000..521adb19
--- /dev/null
+++ b/sound/soc/codecs/wm8400.h
@@ -0,0 +1,59 @@
+/*
+ * wm8400.h -- audio driver for WM8400
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _WM8400_CODEC_H
+#define _WM8400_CODEC_H
+
+#define WM8400_MCLK_DIV 0
+#define WM8400_DACCLK_DIV 1
+#define WM8400_ADCCLK_DIV 2
+#define WM8400_BCLK_DIV 3
+
+#define WM8400_MCLK_DIV_1 0x400
+#define WM8400_MCLK_DIV_2 0x800
+
+#define WM8400_DAC_CLKDIV_1 0x00
+#define WM8400_DAC_CLKDIV_1_5 0x04
+#define WM8400_DAC_CLKDIV_2 0x08
+#define WM8400_DAC_CLKDIV_3 0x0c
+#define WM8400_DAC_CLKDIV_4 0x10
+#define WM8400_DAC_CLKDIV_5_5 0x14
+#define WM8400_DAC_CLKDIV_6 0x18
+
+#define WM8400_ADC_CLKDIV_1 0x00
+#define WM8400_ADC_CLKDIV_1_5 0x20
+#define WM8400_ADC_CLKDIV_2 0x40
+#define WM8400_ADC_CLKDIV_3 0x60
+#define WM8400_ADC_CLKDIV_4 0x80
+#define WM8400_ADC_CLKDIV_5_5 0xa0
+#define WM8400_ADC_CLKDIV_6 0xc0
+
+
+#define WM8400_BCLK_DIV_1 (0x0 << 1)
+#define WM8400_BCLK_DIV_1_5 (0x1 << 1)
+#define WM8400_BCLK_DIV_2 (0x2 << 1)
+#define WM8400_BCLK_DIV_3 (0x3 << 1)
+#define WM8400_BCLK_DIV_4 (0x4 << 1)
+#define WM8400_BCLK_DIV_5_5 (0x5 << 1)
+#define WM8400_BCLK_DIV_6 (0x6 << 1)
+#define WM8400_BCLK_DIV_8 (0x7 << 1)
+#define WM8400_BCLK_DIV_11 (0x8 << 1)
+#define WM8400_BCLK_DIV_12 (0x9 << 1)
+#define WM8400_BCLK_DIV_16 (0xA << 1)
+#define WM8400_BCLK_DIV_22 (0xB << 1)
+#define WM8400_BCLK_DIV_24 (0xC << 1)
+#define WM8400_BCLK_DIV_32 (0xD << 1)
+#define WM8400_BCLK_DIV_44 (0xE << 1)
+#define WM8400_BCLK_DIV_48 (0xF << 1)
+
+#endif
diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c
new file mode 100644
index 00000000..db0dced7
--- /dev/null
+++ b/sound/soc/codecs/wm8510.c
@@ -0,0 +1,715 @@
+/*
+ * wm8510.c -- WM8510 ALSA Soc Audio driver
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "wm8510.h"
+
+/*
+ * wm8510 register cache
+ * We can't read the WM8510 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8510_reg[WM8510_CACHEREGNUM] = {
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0050, 0x0000, 0x0140, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x00ff,
+ 0x0000, 0x0000, 0x0100, 0x00ff,
+ 0x0000, 0x0000, 0x012c, 0x002c,
+ 0x002c, 0x002c, 0x002c, 0x0000,
+ 0x0032, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0038, 0x000b, 0x0032, 0x0000,
+ 0x0008, 0x000c, 0x0093, 0x00e9,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0003, 0x0010, 0x0000, 0x0000,
+ 0x0000, 0x0002, 0x0001, 0x0000,
+ 0x0000, 0x0000, 0x0039, 0x0000,
+ 0x0001,
+};
+
+#define WM8510_POWER1_BIASEN 0x08
+#define WM8510_POWER1_BUFIOEN 0x10
+
+#define wm8510_reset(c) snd_soc_write(c, WM8510_RESET, 0)
+
+/* codec private data */
+struct wm8510_priv {
+ enum snd_soc_control_type control_type;
+};
+
+static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" };
+static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8510_alc[] = { "ALC", "Limiter" };
+
+static const struct soc_enum wm8510_enum[] = {
+ SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */
+ SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */
+ SOC_ENUM_SINGLE(WM8510_DAC, 4, 4, wm8510_deemp),
+ SOC_ENUM_SINGLE(WM8510_ALC3, 8, 2, wm8510_alc),
+};
+
+static const struct snd_kcontrol_new wm8510_snd_controls[] = {
+
+SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0),
+
+SOC_ENUM("DAC Companding", wm8510_enum[1]),
+SOC_ENUM("ADC Companding", wm8510_enum[0]),
+
+SOC_ENUM("Playback De-emphasis", wm8510_enum[2]),
+SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0),
+
+SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0),
+
+SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0),
+SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0),
+SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0),
+
+SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0),
+
+SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1, 8, 1, 0),
+SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1, 4, 15, 0),
+SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1, 0, 15, 0),
+
+SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2, 4, 7, 0),
+SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2, 0, 15, 0),
+
+SOC_SINGLE("ALC Enable Switch", WM8510_ALC1, 8, 1, 0),
+SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1, 3, 7, 0),
+SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1, 0, 7, 0),
+
+SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2, 8, 1, 0),
+SOC_SINGLE("ALC Capture Hold", WM8510_ALC2, 4, 7, 0),
+SOC_SINGLE("ALC Capture Target", WM8510_ALC2, 0, 15, 0),
+
+SOC_ENUM("ALC Capture Mode", wm8510_enum[3]),
+SOC_SINGLE("ALC Capture Decay", WM8510_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack", WM8510_ALC3, 0, 15, 0),
+
+SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE, 3, 1, 0),
+SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE, 0, 7, 0),
+
+SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA, 7, 1, 0),
+SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA, 0, 63, 0),
+
+SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL, 7, 1, 0),
+SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL, 6, 1, 1),
+SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL, 0, 63, 0),
+SOC_SINGLE("Speaker Boost", WM8510_OUTPUT, 2, 1, 0),
+
+SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST, 8, 1, 0),
+SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 1),
+};
+
+/* Speaker Output Mixer */
+static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 0),
+};
+
+/* Mono Output Mixer */
+static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8510_boost_controls[] = {
+SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA, 6, 1, 1),
+SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0),
+SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0),
+};
+
+static const struct snd_kcontrol_new wm8510_micpga_controls[] = {
+SOC_DAPM_SINGLE("MICP Switch", WM8510_INPUT, 0, 1, 0),
+SOC_DAPM_SINGLE("MICN Switch", WM8510_INPUT, 1, 1, 0),
+SOC_DAPM_SINGLE("AUX Switch", WM8510_INPUT, 2, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0,
+ &wm8510_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm8510_speaker_mixer_controls)),
+SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0,
+ &wm8510_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8510_mono_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER2, 0, 0),
+SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Mic PGA", WM8510_POWER2, 2, 0,
+ &wm8510_micpga_controls[0],
+ ARRAY_SIZE(wm8510_micpga_controls)),
+SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0,
+ &wm8510_boost_controls[0],
+ ARRAY_SIZE(wm8510_boost_controls)),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0),
+
+SND_SOC_DAPM_INPUT("MICN"),
+SND_SOC_DAPM_INPUT("MICP"),
+SND_SOC_DAPM_INPUT("AUX"),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("SPKOUTP"),
+SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Mono output mixer */
+ {"Mono Mixer", "PCM Playback Switch", "DAC"},
+ {"Mono Mixer", "Aux Playback Switch", "Aux Input"},
+ {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+ /* Speaker output mixer */
+ {"Speaker Mixer", "PCM Playback Switch", "DAC"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
+ {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+ /* Outputs */
+ {"Mono Out", NULL, "Mono Mixer"},
+ {"MONOOUT", NULL, "Mono Out"},
+ {"SpkN Out", NULL, "Speaker Mixer"},
+ {"SpkP Out", NULL, "Speaker Mixer"},
+ {"SPKOUTN", NULL, "SpkN Out"},
+ {"SPKOUTP", NULL, "SpkP Out"},
+
+ /* Microphone PGA */
+ {"Mic PGA", "MICN Switch", "MICN"},
+ {"Mic PGA", "MICP Switch", "MICP"},
+ { "Mic PGA", "AUX Switch", "Aux Input" },
+
+ /* Boost Mixer */
+ {"Boost Mixer", "Mic PGA Switch", "Mic PGA"},
+ {"Boost Mixer", "Mic Volume", "MICP"},
+ {"Boost Mixer", "Aux Volume", "Aux Input"},
+
+ {"ADC", NULL, "Boost Mixer"},
+};
+
+static int wm8510_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8510_dapm_widgets,
+ ARRAY_SIZE(wm8510_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+struct pll_ {
+ unsigned int pre_div:4; /* prescale - 1 */
+ unsigned int n:4;
+ unsigned int k;
+};
+
+static struct pll_ pll_div;
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+
+static void pll_factors(unsigned int target, unsigned int source)
+{
+ unsigned long long Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div.pre_div = 1;
+ Ndiv = target / source;
+ } else
+ pll_div.pre_div = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM8510 N value %u outwith recommended range!d\n",
+ Ndiv);
+
+ pll_div.n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div.k = K;
+}
+
+static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ if (freq_in == 0 || freq_out == 0) {
+ /* Clock CODEC directly from MCLK */
+ reg = snd_soc_read(codec, WM8510_CLOCK);
+ snd_soc_write(codec, WM8510_CLOCK, reg & 0x0ff);
+
+ /* Turn off PLL */
+ reg = snd_soc_read(codec, WM8510_POWER1);
+ snd_soc_write(codec, WM8510_POWER1, reg & 0x1df);
+ return 0;
+ }
+
+ pll_factors(freq_out*4, freq_in);
+
+ snd_soc_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n);
+ snd_soc_write(codec, WM8510_PLLK1, pll_div.k >> 18);
+ snd_soc_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff);
+ snd_soc_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff);
+ reg = snd_soc_read(codec, WM8510_POWER1);
+ snd_soc_write(codec, WM8510_POWER1, reg | 0x020);
+
+ /* Run CODEC from PLL instead of MCLK */
+ reg = snd_soc_read(codec, WM8510_CLOCK);
+ snd_soc_write(codec, WM8510_CLOCK, reg | 0x100);
+
+ return 0;
+}
+
+/*
+ * Configure WM8510 clock dividers.
+ */
+static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8510_OPCLKDIV:
+ reg = snd_soc_read(codec, WM8510_GPIO) & 0x1cf;
+ snd_soc_write(codec, WM8510_GPIO, reg | div);
+ break;
+ case WM8510_MCLKDIV:
+ reg = snd_soc_read(codec, WM8510_CLOCK) & 0x11f;
+ snd_soc_write(codec, WM8510_CLOCK, reg | div);
+ break;
+ case WM8510_ADCCLK:
+ reg = snd_soc_read(codec, WM8510_ADC) & 0x1f7;
+ snd_soc_write(codec, WM8510_ADC, reg | div);
+ break;
+ case WM8510_DACCLK:
+ reg = snd_soc_read(codec, WM8510_DAC) & 0x1f7;
+ snd_soc_write(codec, WM8510_DAC, reg | div);
+ break;
+ case WM8510_BCLKDIV:
+ reg = snd_soc_read(codec, WM8510_CLOCK) & 0x1e3;
+ snd_soc_write(codec, WM8510_CLOCK, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+ u16 clk = snd_soc_read(codec, WM8510_CLOCK) & 0x1fe;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ clk |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0010;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0008;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x00018;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0180;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0100;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0080;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8510_IFACE, iface);
+ snd_soc_write(codec, WM8510_CLOCK, clk);
+ return 0;
+}
+
+static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u16 iface = snd_soc_read(codec, WM8510_IFACE) & 0x19f;
+ u16 adn = snd_soc_read(codec, WM8510_ADD) & 0x1f1;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0020;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0040;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x0060;
+ break;
+ }
+
+ /* filter coefficient */
+ switch (params_rate(params)) {
+ case 8000:
+ adn |= 0x5 << 1;
+ break;
+ case 11025:
+ adn |= 0x4 << 1;
+ break;
+ case 16000:
+ adn |= 0x3 << 1;
+ break;
+ case 22050:
+ adn |= 0x2 << 1;
+ break;
+ case 32000:
+ adn |= 0x1 << 1;
+ break;
+ case 44100:
+ case 48000:
+ break;
+ }
+
+ snd_soc_write(codec, WM8510_IFACE, iface);
+ snd_soc_write(codec, WM8510_ADD, adn);
+ return 0;
+}
+
+static int wm8510_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8510_DAC) & 0xffbf;
+
+ if (mute)
+ snd_soc_write(codec, WM8510_DAC, mute_reg | 0x40);
+ else
+ snd_soc_write(codec, WM8510_DAC, mute_reg);
+ return 0;
+}
+
+/* liam need to make this lower power with dapm */
+static int wm8510_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 power1 = snd_soc_read(codec, WM8510_POWER1) & ~0x3;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ power1 |= 0x1; /* VMID 50k */
+ snd_soc_write(codec, WM8510_POWER1, power1);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ power1 |= WM8510_POWER1_BIASEN | WM8510_POWER1_BUFIOEN;
+
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Initial cap charge at VMID 5k */
+ snd_soc_write(codec, WM8510_POWER1, power1 | 0x3);
+ mdelay(100);
+ }
+
+ power1 |= 0x2; /* VMID 500k */
+ snd_soc_write(codec, WM8510_POWER1, power1);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, WM8510_POWER1, 0);
+ snd_soc_write(codec, WM8510_POWER2, 0);
+ snd_soc_write(codec, WM8510_POWER3, 0);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8510_dai_ops = {
+ .hw_params = wm8510_pcm_hw_params,
+ .digital_mute = wm8510_mute,
+ .set_fmt = wm8510_set_dai_fmt,
+ .set_clkdiv = wm8510_set_dai_clkdiv,
+ .set_pll = wm8510_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver wm8510_dai = {
+ .name = "wm8510-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8510_RATES,
+ .formats = WM8510_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8510_RATES,
+ .formats = WM8510_FORMATS,},
+ .ops = &wm8510_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int wm8510_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8510_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8510_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int wm8510_probe(struct snd_soc_codec *codec)
+{
+ struct wm8510_priv *wm8510 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8510->control_type);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8510: failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ wm8510_reset(codec);
+
+ /* power on device */
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ snd_soc_add_controls(codec, wm8510_snd_controls,
+ ARRAY_SIZE(wm8510_snd_controls));
+ wm8510_add_widgets(codec);
+
+ return ret;
+}
+
+/* power down chip */
+static int wm8510_remove(struct snd_soc_codec *codec)
+{
+ struct wm8510_priv *wm8510 = snd_soc_codec_get_drvdata(codec);
+
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ kfree(wm8510);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8510 = {
+ .probe = wm8510_probe,
+ .remove = wm8510_remove,
+ .suspend = wm8510_suspend,
+ .resume = wm8510_resume,
+ .set_bias_level = wm8510_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8510_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default =wm8510_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8510_spi_probe(struct spi_device *spi)
+{
+ struct wm8510_priv *wm8510;
+ int ret;
+
+ wm8510 = kzalloc(sizeof(struct wm8510_priv), GFP_KERNEL);
+ if (wm8510 == NULL)
+ return -ENOMEM;
+
+ wm8510->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8510);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8510, &wm8510_dai, 1);
+ if (ret < 0)
+ kfree(wm8510);
+ return ret;
+}
+
+static int __devexit wm8510_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ return 0;
+}
+
+static struct spi_driver wm8510_spi_driver = {
+ .driver = {
+ .name = "wm8510",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8510_spi_probe,
+ .remove = __devexit_p(wm8510_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8510_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8510_priv *wm8510;
+ int ret;
+
+ wm8510 = kzalloc(sizeof(struct wm8510_priv), GFP_KERNEL);
+ if (wm8510 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8510);
+ wm8510->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8510, &wm8510_dai, 1);
+ if (ret < 0)
+ kfree(wm8510);
+ return ret;
+}
+
+static __devexit int wm8510_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ return 0;
+}
+
+static const struct i2c_device_id wm8510_i2c_id[] = {
+ { "wm8510", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8510_i2c_id);
+
+static struct i2c_driver wm8510_i2c_driver = {
+ .driver = {
+ .name = "wm8510-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8510_i2c_probe,
+ .remove = __devexit_p(wm8510_i2c_remove),
+ .id_table = wm8510_i2c_id,
+};
+#endif
+
+static int __init wm8510_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8510_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8510 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8510_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8510 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8510_modinit);
+
+static void __exit wm8510_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8510_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8510_spi_driver);
+#endif
+}
+module_exit(wm8510_exit);
+
+MODULE_DESCRIPTION("ASoC WM8510 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8510.h b/sound/soc/codecs/wm8510.h
new file mode 100644
index 00000000..b3e26ed9
--- /dev/null
+++ b/sound/soc/codecs/wm8510.h
@@ -0,0 +1,102 @@
+/*
+ * wm8510.h -- WM8510 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8510_H
+#define _WM8510_H
+
+/* WM8510 register space */
+
+#define WM8510_RESET 0x0
+#define WM8510_POWER1 0x1
+#define WM8510_POWER2 0x2
+#define WM8510_POWER3 0x3
+#define WM8510_IFACE 0x4
+#define WM8510_COMP 0x5
+#define WM8510_CLOCK 0x6
+#define WM8510_ADD 0x7
+#define WM8510_GPIO 0x8
+#define WM8510_DAC 0xa
+#define WM8510_DACVOL 0xb
+#define WM8510_ADC 0xe
+#define WM8510_ADCVOL 0xf
+#define WM8510_EQ1 0x12
+#define WM8510_EQ2 0x13
+#define WM8510_EQ3 0x14
+#define WM8510_EQ4 0x15
+#define WM8510_EQ5 0x16
+#define WM8510_DACLIM1 0x18
+#define WM8510_DACLIM2 0x19
+#define WM8510_NOTCH1 0x1b
+#define WM8510_NOTCH2 0x1c
+#define WM8510_NOTCH3 0x1d
+#define WM8510_NOTCH4 0x1e
+#define WM8510_ALC1 0x20
+#define WM8510_ALC2 0x21
+#define WM8510_ALC3 0x22
+#define WM8510_NGATE 0x23
+#define WM8510_PLLN 0x24
+#define WM8510_PLLK1 0x25
+#define WM8510_PLLK2 0x26
+#define WM8510_PLLK3 0x27
+#define WM8510_ATTEN 0x28
+#define WM8510_INPUT 0x2c
+#define WM8510_INPPGA 0x2d
+#define WM8510_ADCBOOST 0x2f
+#define WM8510_OUTPUT 0x31
+#define WM8510_SPKMIX 0x32
+#define WM8510_SPKVOL 0x36
+#define WM8510_MONOMIX 0x38
+
+#define WM8510_CACHEREGNUM 57
+
+/* Clock divider Id's */
+#define WM8510_OPCLKDIV 0
+#define WM8510_MCLKDIV 1
+#define WM8510_ADCCLK 2
+#define WM8510_DACCLK 3
+#define WM8510_BCLKDIV 4
+
+/* DAC clock dividers */
+#define WM8510_DACCLK_F2 (1 << 3)
+#define WM8510_DACCLK_F4 (0 << 3)
+
+/* ADC clock dividers */
+#define WM8510_ADCCLK_F2 (1 << 3)
+#define WM8510_ADCCLK_F4 (0 << 3)
+
+/* PLL Out dividers */
+#define WM8510_OPCLKDIV_1 (0 << 4)
+#define WM8510_OPCLKDIV_2 (1 << 4)
+#define WM8510_OPCLKDIV_3 (2 << 4)
+#define WM8510_OPCLKDIV_4 (3 << 4)
+
+/* BCLK clock dividers */
+#define WM8510_BCLKDIV_1 (0 << 2)
+#define WM8510_BCLKDIV_2 (1 << 2)
+#define WM8510_BCLKDIV_4 (2 << 2)
+#define WM8510_BCLKDIV_8 (3 << 2)
+#define WM8510_BCLKDIV_16 (4 << 2)
+#define WM8510_BCLKDIV_32 (5 << 2)
+
+/* MCLK clock dividers */
+#define WM8510_MCLKDIV_1 (0 << 5)
+#define WM8510_MCLKDIV_1_5 (1 << 5)
+#define WM8510_MCLKDIV_2 (2 << 5)
+#define WM8510_MCLKDIV_3 (3 << 5)
+#define WM8510_MCLKDIV_4 (4 << 5)
+#define WM8510_MCLKDIV_6 (5 << 5)
+#define WM8510_MCLKDIV_8 (6 << 5)
+#define WM8510_MCLKDIV_12 (7 << 5)
+
+struct wm8510_setup_data {
+ int spi;
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+#endif
diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c
new file mode 100644
index 00000000..4fd4d8dc
--- /dev/null
+++ b/sound/soc/codecs/wm8523.c
@@ -0,0 +1,587 @@
+/*
+ * wm8523.c -- WM8523 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8523.h"
+
+#define WM8523_NUM_SUPPLIES 2
+static const char *wm8523_supply_names[WM8523_NUM_SUPPLIES] = {
+ "AVDD",
+ "LINEVDD",
+};
+
+#define WM8523_NUM_RATES 7
+
+/* codec private data */
+struct wm8523_priv {
+ enum snd_soc_control_type control_type;
+ struct regulator_bulk_data supplies[WM8523_NUM_SUPPLIES];
+ unsigned int sysclk;
+ unsigned int rate_constraint_list[WM8523_NUM_RATES];
+ struct snd_pcm_hw_constraint_list rate_constraint;
+};
+
+static const u16 wm8523_reg[WM8523_REGISTER_COUNT] = {
+ 0x8523, /* R0 - DEVICE_ID */
+ 0x0001, /* R1 - REVISION */
+ 0x0000, /* R2 - PSCTRL1 */
+ 0x1812, /* R3 - AIF_CTRL1 */
+ 0x0000, /* R4 - AIF_CTRL2 */
+ 0x0001, /* R5 - DAC_CTRL3 */
+ 0x0190, /* R6 - DAC_GAINL */
+ 0x0190, /* R7 - DAC_GAINR */
+ 0x0000, /* R8 - ZERO_DETECT */
+};
+
+static int wm8523_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case WM8523_DEVICE_ID:
+ case WM8523_REVISION:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int wm8523_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8523_DEVICE_ID, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -10000, 25, 0);
+
+static const char *wm8523_zd_count_text[] = {
+ "1024",
+ "2048",
+};
+
+static const struct soc_enum wm8523_zc_count =
+ SOC_ENUM_SINGLE(WM8523_ZERO_DETECT, 0, 2, wm8523_zd_count_text);
+
+static const struct snd_kcontrol_new wm8523_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Playback Volume", WM8523_DAC_GAINL, WM8523_DAC_GAINR,
+ 0, 448, 0, dac_tlv),
+SOC_SINGLE("ZC Switch", WM8523_DAC_CTRL3, 4, 1, 0),
+SOC_SINGLE("Playback Deemphasis Switch", WM8523_AIF_CTRL1, 8, 1, 0),
+SOC_DOUBLE("Playback Switch", WM8523_DAC_CTRL3, 2, 3, 1, 1),
+SOC_SINGLE("Volume Ramp Up Switch", WM8523_DAC_CTRL3, 1, 1, 0),
+SOC_SINGLE("Volume Ramp Down Switch", WM8523_DAC_CTRL3, 0, 1, 0),
+SOC_ENUM("Zero Detect Count", wm8523_zc_count),
+};
+
+static const struct snd_soc_dapm_widget wm8523_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_OUTPUT("LINEVOUTL"),
+SND_SOC_DAPM_OUTPUT("LINEVOUTR"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ { "LINEVOUTL", NULL, "DAC" },
+ { "LINEVOUTR", NULL, "DAC" },
+};
+
+static int wm8523_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8523_dapm_widgets,
+ ARRAY_SIZE(wm8523_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ return 0;
+}
+
+static struct {
+ int value;
+ int ratio;
+} lrclk_ratios[WM8523_NUM_RATES] = {
+ { 1, 128 },
+ { 2, 192 },
+ { 3, 256 },
+ { 4, 384 },
+ { 5, 512 },
+ { 6, 768 },
+ { 7, 1152 },
+};
+
+static int wm8523_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+
+ /* The set of sample rates that can be supported depends on the
+ * MCLK supplied to the CODEC - enforce this.
+ */
+ if (!wm8523->sysclk) {
+ dev_err(codec->dev,
+ "No MCLK configured, call set_sysclk() on init\n");
+ return -EINVAL;
+ }
+
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &wm8523->rate_constraint);
+
+ return 0;
+}
+
+static int wm8523_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+ int i;
+ u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1);
+ u16 aifctrl2 = snd_soc_read(codec, WM8523_AIF_CTRL2);
+
+ /* Find a supported LRCLK ratio */
+ for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
+ if (wm8523->sysclk / params_rate(params) ==
+ lrclk_ratios[i].ratio)
+ break;
+ }
+
+ /* Should never happen, should be handled by constraints */
+ if (i == ARRAY_SIZE(lrclk_ratios)) {
+ dev_err(codec->dev, "MCLK/fs ratio %d unsupported\n",
+ wm8523->sysclk / params_rate(params));
+ return -EINVAL;
+ }
+
+ aifctrl2 &= ~WM8523_SR_MASK;
+ aifctrl2 |= lrclk_ratios[i].value;
+
+ aifctrl1 &= ~WM8523_WL_MASK;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ aifctrl1 |= 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ aifctrl1 |= 0x10;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ aifctrl1 |= 0x18;
+ break;
+ }
+
+ snd_soc_write(codec, WM8523_AIF_CTRL1, aifctrl1);
+ snd_soc_write(codec, WM8523_AIF_CTRL2, aifctrl2);
+
+ return 0;
+}
+
+static int wm8523_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+ unsigned int val;
+ int i;
+
+ wm8523->sysclk = freq;
+
+ wm8523->rate_constraint.count = 0;
+ for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
+ val = freq / lrclk_ratios[i].ratio;
+ /* Check that it's a standard rate since core can't
+ * cope with others and having the odd rates confuses
+ * constraint matching.
+ */
+ switch (val) {
+ case 8000:
+ case 11025:
+ case 16000:
+ case 22050:
+ case 32000:
+ case 44100:
+ case 48000:
+ case 64000:
+ case 88200:
+ case 96000:
+ case 176400:
+ case 192000:
+ dev_dbg(codec->dev, "Supported sample rate: %dHz\n",
+ val);
+ wm8523->rate_constraint_list[i] = val;
+ wm8523->rate_constraint.count++;
+ break;
+ default:
+ dev_dbg(codec->dev, "Skipping sample rate: %dHz\n",
+ val);
+ }
+ }
+
+ /* Need at least one supported rate... */
+ if (wm8523->rate_constraint.count == 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+
+static int wm8523_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1);
+
+ aifctrl1 &= ~(WM8523_BCLK_INV_MASK | WM8523_LRCLK_INV_MASK |
+ WM8523_FMT_MASK | WM8523_AIF_MSTR_MASK);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aifctrl1 |= WM8523_AIF_MSTR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ aifctrl1 |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aifctrl1 |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ aifctrl1 |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ aifctrl1 |= 0x0023;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aifctrl1 |= WM8523_BCLK_INV | WM8523_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aifctrl1 |= WM8523_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aifctrl1 |= WM8523_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8523_AIF_CTRL1, aifctrl1);
+
+ return 0;
+}
+
+static int wm8523_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+ u16 *reg_cache = codec->reg_cache;
+ int ret, i;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* Full power on */
+ snd_soc_update_bits(codec, WM8523_PSCTRL1,
+ WM8523_SYS_ENA_MASK, 3);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8523->supplies),
+ wm8523->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Initial power up */
+ snd_soc_update_bits(codec, WM8523_PSCTRL1,
+ WM8523_SYS_ENA_MASK, 1);
+
+ /* Sync back default/cached values */
+ for (i = WM8523_AIF_CTRL1;
+ i < WM8523_MAX_REGISTER; i++)
+ snd_soc_write(codec, i, reg_cache[i]);
+
+
+ msleep(100);
+ }
+
+ /* Power up to mute */
+ snd_soc_update_bits(codec, WM8523_PSCTRL1,
+ WM8523_SYS_ENA_MASK, 2);
+
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* The chip runs through the power down sequence for us. */
+ snd_soc_update_bits(codec, WM8523_PSCTRL1,
+ WM8523_SYS_ENA_MASK, 0);
+ msleep(100);
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies),
+ wm8523->supplies);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8523_RATES SNDRV_PCM_RATE_8000_192000
+
+#define WM8523_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8523_dai_ops = {
+ .startup = wm8523_startup,
+ .hw_params = wm8523_hw_params,
+ .set_sysclk = wm8523_set_dai_sysclk,
+ .set_fmt = wm8523_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8523_dai = {
+ .name = "wm8523-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2, /* Mono modes not yet supported */
+ .channels_max = 2,
+ .rates = WM8523_RATES,
+ .formats = WM8523_FORMATS,
+ },
+ .ops = &wm8523_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int wm8523_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8523_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8523_resume(struct snd_soc_codec *codec)
+{
+ wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+#else
+#define wm8523_suspend NULL
+#define wm8523_resume NULL
+#endif
+
+static int wm8523_probe(struct snd_soc_codec *codec)
+{
+ struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+ int ret, i;
+
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ wm8523->rate_constraint.list = &wm8523->rate_constraint_list[0];
+ wm8523->rate_constraint.count =
+ ARRAY_SIZE(wm8523->rate_constraint_list);
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm8523->control_type);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8523->supplies); i++)
+ wm8523->supplies[i].supply = wm8523_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8523->supplies),
+ wm8523->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8523->supplies),
+ wm8523->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ ret = snd_soc_read(codec, WM8523_DEVICE_ID);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read ID register\n");
+ goto err_enable;
+ }
+ if (ret != wm8523_reg[WM8523_DEVICE_ID]) {
+ dev_err(codec->dev, "Device is not a WM8523, ID is %x\n", ret);
+ ret = -EINVAL;
+ goto err_enable;
+ }
+
+ ret = snd_soc_read(codec, WM8523_REVISION);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read revision register\n");
+ goto err_enable;
+ }
+ dev_info(codec->dev, "revision %c\n",
+ (ret & WM8523_CHIP_REV_MASK) + 'A');
+
+ ret = wm8523_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ goto err_enable;
+ }
+
+ /* Change some default settings - latch VU and enable ZC */
+ snd_soc_update_bits(codec, WM8523_DAC_GAINR,
+ WM8523_DACR_VU, WM8523_DACR_VU);
+ snd_soc_update_bits(codec, WM8523_DAC_CTRL3, WM8523_ZC, WM8523_ZC);
+
+ wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Bias level configuration will have done an extra enable */
+ regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
+
+ snd_soc_add_controls(codec, wm8523_snd_controls,
+ ARRAY_SIZE(wm8523_snd_controls));
+ wm8523_add_widgets(codec);
+
+ return 0;
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
+
+ return ret;
+}
+
+static int wm8523_remove(struct snd_soc_codec *codec)
+{
+ struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+
+ wm8523_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ regulator_bulk_free(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8523 = {
+ .probe = wm8523_probe,
+ .remove = wm8523_remove,
+ .suspend = wm8523_suspend,
+ .resume = wm8523_resume,
+ .set_bias_level = wm8523_set_bias_level,
+ .reg_cache_size = WM8523_REGISTER_COUNT,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8523_reg,
+ .volatile_register = wm8523_volatile_register,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8523_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8523_priv *wm8523;
+ int ret;
+
+ wm8523 = kzalloc(sizeof(struct wm8523_priv), GFP_KERNEL);
+ if (wm8523 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8523);
+ wm8523->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8523, &wm8523_dai, 1);
+ if (ret < 0)
+ kfree(wm8523);
+ return ret;
+
+}
+
+static __devexit int wm8523_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8523_i2c_id[] = {
+ { "wm8523", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8523_i2c_id);
+
+static struct i2c_driver wm8523_i2c_driver = {
+ .driver = {
+ .name = "wm8523-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8523_i2c_probe,
+ .remove = __devexit_p(wm8523_i2c_remove),
+ .id_table = wm8523_i2c_id,
+};
+#endif
+
+static int __init wm8523_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8523_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8523 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return 0;
+}
+module_init(wm8523_modinit);
+
+static void __exit wm8523_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8523_i2c_driver);
+#endif
+}
+module_exit(wm8523_exit);
+
+MODULE_DESCRIPTION("ASoC WM8523 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8523.h b/sound/soc/codecs/wm8523.h
new file mode 100644
index 00000000..4d5b1eb8
--- /dev/null
+++ b/sound/soc/codecs/wm8523.h
@@ -0,0 +1,157 @@
+/*
+ * wm8523.h -- WM8423 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics, plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8523_H
+#define _WM8523_H
+
+/*
+ * Register values.
+ */
+#define WM8523_DEVICE_ID 0x00
+#define WM8523_REVISION 0x01
+#define WM8523_PSCTRL1 0x02
+#define WM8523_AIF_CTRL1 0x03
+#define WM8523_AIF_CTRL2 0x04
+#define WM8523_DAC_CTRL3 0x05
+#define WM8523_DAC_GAINL 0x06
+#define WM8523_DAC_GAINR 0x07
+#define WM8523_ZERO_DETECT 0x08
+
+#define WM8523_REGISTER_COUNT 9
+#define WM8523_MAX_REGISTER 0x08
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - DEVICE_ID
+ */
+#define WM8523_CHIP_ID_MASK 0xFFFF /* CHIP_ID - [15:0] */
+#define WM8523_CHIP_ID_SHIFT 0 /* CHIP_ID - [15:0] */
+#define WM8523_CHIP_ID_WIDTH 16 /* CHIP_ID - [15:0] */
+
+/*
+ * R1 (0x01) - REVISION
+ */
+#define WM8523_CHIP_REV_MASK 0x0007 /* CHIP_REV - [2:0] */
+#define WM8523_CHIP_REV_SHIFT 0 /* CHIP_REV - [2:0] */
+#define WM8523_CHIP_REV_WIDTH 3 /* CHIP_REV - [2:0] */
+
+/*
+ * R2 (0x02) - PSCTRL1
+ */
+#define WM8523_SYS_ENA_MASK 0x0003 /* SYS_ENA - [1:0] */
+#define WM8523_SYS_ENA_SHIFT 0 /* SYS_ENA - [1:0] */
+#define WM8523_SYS_ENA_WIDTH 2 /* SYS_ENA - [1:0] */
+
+/*
+ * R3 (0x03) - AIF_CTRL1
+ */
+#define WM8523_TDM_MODE_MASK 0x1800 /* TDM_MODE - [12:11] */
+#define WM8523_TDM_MODE_SHIFT 11 /* TDM_MODE - [12:11] */
+#define WM8523_TDM_MODE_WIDTH 2 /* TDM_MODE - [12:11] */
+#define WM8523_TDM_SLOT_MASK 0x0600 /* TDM_SLOT - [10:9] */
+#define WM8523_TDM_SLOT_SHIFT 9 /* TDM_SLOT - [10:9] */
+#define WM8523_TDM_SLOT_WIDTH 2 /* TDM_SLOT - [10:9] */
+#define WM8523_DEEMPH 0x0100 /* DEEMPH */
+#define WM8523_DEEMPH_MASK 0x0100 /* DEEMPH */
+#define WM8523_DEEMPH_SHIFT 8 /* DEEMPH */
+#define WM8523_DEEMPH_WIDTH 1 /* DEEMPH */
+#define WM8523_AIF_MSTR 0x0080 /* AIF_MSTR */
+#define WM8523_AIF_MSTR_MASK 0x0080 /* AIF_MSTR */
+#define WM8523_AIF_MSTR_SHIFT 7 /* AIF_MSTR */
+#define WM8523_AIF_MSTR_WIDTH 1 /* AIF_MSTR */
+#define WM8523_LRCLK_INV 0x0040 /* LRCLK_INV */
+#define WM8523_LRCLK_INV_MASK 0x0040 /* LRCLK_INV */
+#define WM8523_LRCLK_INV_SHIFT 6 /* LRCLK_INV */
+#define WM8523_LRCLK_INV_WIDTH 1 /* LRCLK_INV */
+#define WM8523_BCLK_INV 0x0020 /* BCLK_INV */
+#define WM8523_BCLK_INV_MASK 0x0020 /* BCLK_INV */
+#define WM8523_BCLK_INV_SHIFT 5 /* BCLK_INV */
+#define WM8523_BCLK_INV_WIDTH 1 /* BCLK_INV */
+#define WM8523_WL_MASK 0x0018 /* WL - [4:3] */
+#define WM8523_WL_SHIFT 3 /* WL - [4:3] */
+#define WM8523_WL_WIDTH 2 /* WL - [4:3] */
+#define WM8523_FMT_MASK 0x0007 /* FMT - [2:0] */
+#define WM8523_FMT_SHIFT 0 /* FMT - [2:0] */
+#define WM8523_FMT_WIDTH 3 /* FMT - [2:0] */
+
+/*
+ * R4 (0x04) - AIF_CTRL2
+ */
+#define WM8523_DAC_OP_MUX_MASK 0x00C0 /* DAC_OP_MUX - [7:6] */
+#define WM8523_DAC_OP_MUX_SHIFT 6 /* DAC_OP_MUX - [7:6] */
+#define WM8523_DAC_OP_MUX_WIDTH 2 /* DAC_OP_MUX - [7:6] */
+#define WM8523_BCLKDIV_MASK 0x0038 /* BCLKDIV - [5:3] */
+#define WM8523_BCLKDIV_SHIFT 3 /* BCLKDIV - [5:3] */
+#define WM8523_BCLKDIV_WIDTH 3 /* BCLKDIV - [5:3] */
+#define WM8523_SR_MASK 0x0007 /* SR - [2:0] */
+#define WM8523_SR_SHIFT 0 /* SR - [2:0] */
+#define WM8523_SR_WIDTH 3 /* SR - [2:0] */
+
+/*
+ * R5 (0x05) - DAC_CTRL3
+ */
+#define WM8523_ZC 0x0010 /* ZC */
+#define WM8523_ZC_MASK 0x0010 /* ZC */
+#define WM8523_ZC_SHIFT 4 /* ZC */
+#define WM8523_ZC_WIDTH 1 /* ZC */
+#define WM8523_DACR 0x0008 /* DACR */
+#define WM8523_DACR_MASK 0x0008 /* DACR */
+#define WM8523_DACR_SHIFT 3 /* DACR */
+#define WM8523_DACR_WIDTH 1 /* DACR */
+#define WM8523_DACL 0x0004 /* DACL */
+#define WM8523_DACL_MASK 0x0004 /* DACL */
+#define WM8523_DACL_SHIFT 2 /* DACL */
+#define WM8523_DACL_WIDTH 1 /* DACL */
+#define WM8523_VOL_UP_RAMP 0x0002 /* VOL_UP_RAMP */
+#define WM8523_VOL_UP_RAMP_MASK 0x0002 /* VOL_UP_RAMP */
+#define WM8523_VOL_UP_RAMP_SHIFT 1 /* VOL_UP_RAMP */
+#define WM8523_VOL_UP_RAMP_WIDTH 1 /* VOL_UP_RAMP */
+#define WM8523_VOL_DOWN_RAMP 0x0001 /* VOL_DOWN_RAMP */
+#define WM8523_VOL_DOWN_RAMP_MASK 0x0001 /* VOL_DOWN_RAMP */
+#define WM8523_VOL_DOWN_RAMP_SHIFT 0 /* VOL_DOWN_RAMP */
+#define WM8523_VOL_DOWN_RAMP_WIDTH 1 /* VOL_DOWN_RAMP */
+
+/*
+ * R6 (0x06) - DAC_GAINL
+ */
+#define WM8523_DACL_VU 0x0200 /* DACL_VU */
+#define WM8523_DACL_VU_MASK 0x0200 /* DACL_VU */
+#define WM8523_DACL_VU_SHIFT 9 /* DACL_VU */
+#define WM8523_DACL_VU_WIDTH 1 /* DACL_VU */
+#define WM8523_DACL_VOL_MASK 0x01FF /* DACL_VOL - [8:0] */
+#define WM8523_DACL_VOL_SHIFT 0 /* DACL_VOL - [8:0] */
+#define WM8523_DACL_VOL_WIDTH 9 /* DACL_VOL - [8:0] */
+
+/*
+ * R7 (0x07) - DAC_GAINR
+ */
+#define WM8523_DACR_VU 0x0200 /* DACR_VU */
+#define WM8523_DACR_VU_MASK 0x0200 /* DACR_VU */
+#define WM8523_DACR_VU_SHIFT 9 /* DACR_VU */
+#define WM8523_DACR_VU_WIDTH 1 /* DACR_VU */
+#define WM8523_DACR_VOL_MASK 0x01FF /* DACR_VOL - [8:0] */
+#define WM8523_DACR_VOL_SHIFT 0 /* DACR_VOL - [8:0] */
+#define WM8523_DACR_VOL_WIDTH 9 /* DACR_VOL - [8:0] */
+
+/*
+ * R8 (0x08) - ZERO_DETECT
+ */
+#define WM8523_ZD_COUNT_MASK 0x0003 /* ZD_COUNT - [1:0] */
+#define WM8523_ZD_COUNT_SHIFT 0 /* ZD_COUNT - [1:0] */
+#define WM8523_ZD_COUNT_WIDTH 2 /* ZD_COUNT - [1:0] */
+
+#endif
diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c
new file mode 100644
index 00000000..4bbc0a79
--- /dev/null
+++ b/sound/soc/codecs/wm8580.c
@@ -0,0 +1,980 @@
+/*
+ * wm8580.c -- WM8580 ALSA Soc Audio driver
+ *
+ * Copyright 2008, 2009 Wolfson Microelectronics PLC.
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ * Notes:
+ * The WM8580 is a multichannel codec with S/PDIF support, featuring six
+ * DAC channels and two ADC channels.
+ *
+ * Currently only the primary audio interface is supported - S/PDIF and
+ * the secondary audio interfaces are not.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <asm/div64.h>
+
+#include "wm8580.h"
+
+/* WM8580 register space */
+#define WM8580_PLLA1 0x00
+#define WM8580_PLLA2 0x01
+#define WM8580_PLLA3 0x02
+#define WM8580_PLLA4 0x03
+#define WM8580_PLLB1 0x04
+#define WM8580_PLLB2 0x05
+#define WM8580_PLLB3 0x06
+#define WM8580_PLLB4 0x07
+#define WM8580_CLKSEL 0x08
+#define WM8580_PAIF1 0x09
+#define WM8580_PAIF2 0x0A
+#define WM8580_SAIF1 0x0B
+#define WM8580_PAIF3 0x0C
+#define WM8580_PAIF4 0x0D
+#define WM8580_SAIF2 0x0E
+#define WM8580_DAC_CONTROL1 0x0F
+#define WM8580_DAC_CONTROL2 0x10
+#define WM8580_DAC_CONTROL3 0x11
+#define WM8580_DAC_CONTROL4 0x12
+#define WM8580_DAC_CONTROL5 0x13
+#define WM8580_DIGITAL_ATTENUATION_DACL1 0x14
+#define WM8580_DIGITAL_ATTENUATION_DACR1 0x15
+#define WM8580_DIGITAL_ATTENUATION_DACL2 0x16
+#define WM8580_DIGITAL_ATTENUATION_DACR2 0x17
+#define WM8580_DIGITAL_ATTENUATION_DACL3 0x18
+#define WM8580_DIGITAL_ATTENUATION_DACR3 0x19
+#define WM8580_MASTER_DIGITAL_ATTENUATION 0x1C
+#define WM8580_ADC_CONTROL1 0x1D
+#define WM8580_SPDTXCHAN0 0x1E
+#define WM8580_SPDTXCHAN1 0x1F
+#define WM8580_SPDTXCHAN2 0x20
+#define WM8580_SPDTXCHAN3 0x21
+#define WM8580_SPDTXCHAN4 0x22
+#define WM8580_SPDTXCHAN5 0x23
+#define WM8580_SPDMODE 0x24
+#define WM8580_INTMASK 0x25
+#define WM8580_GPO1 0x26
+#define WM8580_GPO2 0x27
+#define WM8580_GPO3 0x28
+#define WM8580_GPO4 0x29
+#define WM8580_GPO5 0x2A
+#define WM8580_INTSTAT 0x2B
+#define WM8580_SPDRXCHAN1 0x2C
+#define WM8580_SPDRXCHAN2 0x2D
+#define WM8580_SPDRXCHAN3 0x2E
+#define WM8580_SPDRXCHAN4 0x2F
+#define WM8580_SPDRXCHAN5 0x30
+#define WM8580_SPDSTAT 0x31
+#define WM8580_PWRDN1 0x32
+#define WM8580_PWRDN2 0x33
+#define WM8580_READBACK 0x34
+#define WM8580_RESET 0x35
+
+#define WM8580_MAX_REGISTER 0x35
+
+#define WM8580_DACOSR 0x40
+
+/* PLLB4 (register 7h) */
+#define WM8580_PLLB4_MCLKOUTSRC_MASK 0x60
+#define WM8580_PLLB4_MCLKOUTSRC_PLLA 0x20
+#define WM8580_PLLB4_MCLKOUTSRC_PLLB 0x40
+#define WM8580_PLLB4_MCLKOUTSRC_OSC 0x60
+
+#define WM8580_PLLB4_CLKOUTSRC_MASK 0x180
+#define WM8580_PLLB4_CLKOUTSRC_PLLACLK 0x080
+#define WM8580_PLLB4_CLKOUTSRC_PLLBCLK 0x100
+#define WM8580_PLLB4_CLKOUTSRC_OSCCLK 0x180
+
+/* CLKSEL (register 8h) */
+#define WM8580_CLKSEL_DAC_CLKSEL_MASK 0x03
+#define WM8580_CLKSEL_DAC_CLKSEL_PLLA 0x01
+#define WM8580_CLKSEL_DAC_CLKSEL_PLLB 0x02
+
+/* AIF control 1 (registers 9h-bh) */
+#define WM8580_AIF_RATE_MASK 0x7
+#define WM8580_AIF_BCLKSEL_MASK 0x18
+
+#define WM8580_AIF_MS 0x20
+
+#define WM8580_AIF_CLKSRC_MASK 0xc0
+#define WM8580_AIF_CLKSRC_PLLA 0x40
+#define WM8580_AIF_CLKSRC_PLLB 0x40
+#define WM8580_AIF_CLKSRC_MCLK 0xc0
+
+/* AIF control 2 (registers ch-eh) */
+#define WM8580_AIF_FMT_MASK 0x03
+#define WM8580_AIF_FMT_RIGHTJ 0x00
+#define WM8580_AIF_FMT_LEFTJ 0x01
+#define WM8580_AIF_FMT_I2S 0x02
+#define WM8580_AIF_FMT_DSP 0x03
+
+#define WM8580_AIF_LENGTH_MASK 0x0c
+#define WM8580_AIF_LENGTH_16 0x00
+#define WM8580_AIF_LENGTH_20 0x04
+#define WM8580_AIF_LENGTH_24 0x08
+#define WM8580_AIF_LENGTH_32 0x0c
+
+#define WM8580_AIF_LRP 0x10
+#define WM8580_AIF_BCP 0x20
+
+/* Powerdown Register 1 (register 32h) */
+#define WM8580_PWRDN1_PWDN 0x001
+#define WM8580_PWRDN1_ALLDACPD 0x040
+
+/* Powerdown Register 2 (register 33h) */
+#define WM8580_PWRDN2_OSSCPD 0x001
+#define WM8580_PWRDN2_PLLAPD 0x002
+#define WM8580_PWRDN2_PLLBPD 0x004
+#define WM8580_PWRDN2_SPDIFPD 0x008
+#define WM8580_PWRDN2_SPDIFTXD 0x010
+#define WM8580_PWRDN2_SPDIFRXD 0x020
+
+#define WM8580_DAC_CONTROL5_MUTEALL 0x10
+
+/*
+ * wm8580 register cache
+ * We can't read the WM8580 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8580_reg[] = {
+ 0x0121, 0x017e, 0x007d, 0x0014, /*R3*/
+ 0x0121, 0x017e, 0x007d, 0x0194, /*R7*/
+ 0x0010, 0x0002, 0x0002, 0x00c2, /*R11*/
+ 0x0182, 0x0082, 0x000a, 0x0024, /*R15*/
+ 0x0009, 0x0000, 0x00ff, 0x0000, /*R19*/
+ 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R23*/
+ 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R27*/
+ 0x01f0, 0x0040, 0x0000, 0x0000, /*R31(0x1F)*/
+ 0x0000, 0x0000, 0x0031, 0x000b, /*R35*/
+ 0x0039, 0x0000, 0x0010, 0x0032, /*R39*/
+ 0x0054, 0x0076, 0x0098, 0x0000, /*R43(0x2B)*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R47*/
+ 0x0000, 0x0000, 0x005e, 0x003e, /*R51(0x33)*/
+ 0x0000, 0x0000 /*R53*/
+};
+
+struct pll_state {
+ unsigned int in;
+ unsigned int out;
+};
+
+#define WM8580_NUM_SUPPLIES 3
+static const char *wm8580_supply_names[WM8580_NUM_SUPPLIES] = {
+ "AVDD",
+ "DVDD",
+ "PVDD",
+};
+
+/* codec private data */
+struct wm8580_priv {
+ enum snd_soc_control_type control_type;
+ struct regulator_bulk_data supplies[WM8580_NUM_SUPPLIES];
+ struct pll_state a;
+ struct pll_state b;
+ int sysclk[2];
+};
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+
+static int wm8580_out_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 *reg_cache = codec->reg_cache;
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ int ret;
+
+ /* Clear the register cache so we write without VU set */
+ reg_cache[reg] = 0;
+ reg_cache[reg2] = 0;
+
+ ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* Now write again with the volume update bit set */
+ snd_soc_update_bits(codec, reg, 0x100, 0x100);
+ snd_soc_update_bits(codec, reg2, 0x100, 0x100);
+
+ return 0;
+}
+
+#define SOC_WM8580_OUT_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, \
+ xinvert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_2r, \
+ .get = snd_soc_get_volsw_2r, .put = wm8580_out_vu, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = reg_left, .rreg = reg_right, .shift = xshift, \
+ .max = xmax, .invert = xinvert} }
+
+static const struct snd_kcontrol_new wm8580_snd_controls[] = {
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC1 Playback Volume",
+ WM8580_DIGITAL_ATTENUATION_DACL1,
+ WM8580_DIGITAL_ATTENUATION_DACR1,
+ 0, 0xff, 0, dac_tlv),
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC2 Playback Volume",
+ WM8580_DIGITAL_ATTENUATION_DACL2,
+ WM8580_DIGITAL_ATTENUATION_DACR2,
+ 0, 0xff, 0, dac_tlv),
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC3 Playback Volume",
+ WM8580_DIGITAL_ATTENUATION_DACL3,
+ WM8580_DIGITAL_ATTENUATION_DACR3,
+ 0, 0xff, 0, dac_tlv),
+
+SOC_SINGLE("DAC1 Deemphasis Switch", WM8580_DAC_CONTROL3, 0, 1, 0),
+SOC_SINGLE("DAC2 Deemphasis Switch", WM8580_DAC_CONTROL3, 1, 1, 0),
+SOC_SINGLE("DAC3 Deemphasis Switch", WM8580_DAC_CONTROL3, 2, 1, 0),
+
+SOC_DOUBLE("DAC1 Invert Switch", WM8580_DAC_CONTROL4, 0, 1, 1, 0),
+SOC_DOUBLE("DAC2 Invert Switch", WM8580_DAC_CONTROL4, 2, 3, 1, 0),
+SOC_DOUBLE("DAC3 Invert Switch", WM8580_DAC_CONTROL4, 4, 5, 1, 0),
+
+SOC_SINGLE("DAC ZC Switch", WM8580_DAC_CONTROL5, 5, 1, 0),
+SOC_SINGLE("DAC1 Switch", WM8580_DAC_CONTROL5, 0, 1, 1),
+SOC_SINGLE("DAC2 Switch", WM8580_DAC_CONTROL5, 1, 1, 1),
+SOC_SINGLE("DAC3 Switch", WM8580_DAC_CONTROL5, 2, 1, 1),
+
+SOC_DOUBLE("Capture Switch", WM8580_ADC_CONTROL1, 0, 1, 1, 1),
+SOC_SINGLE("Capture High-Pass Filter Switch", WM8580_ADC_CONTROL1, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8580_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC1", "Playback", WM8580_PWRDN1, 2, 1),
+SND_SOC_DAPM_DAC("DAC2", "Playback", WM8580_PWRDN1, 3, 1),
+SND_SOC_DAPM_DAC("DAC3", "Playback", WM8580_PWRDN1, 4, 1),
+
+SND_SOC_DAPM_OUTPUT("VOUT1L"),
+SND_SOC_DAPM_OUTPUT("VOUT1R"),
+SND_SOC_DAPM_OUTPUT("VOUT2L"),
+SND_SOC_DAPM_OUTPUT("VOUT2R"),
+SND_SOC_DAPM_OUTPUT("VOUT3L"),
+SND_SOC_DAPM_OUTPUT("VOUT3R"),
+
+SND_SOC_DAPM_ADC("ADC", "Capture", WM8580_PWRDN1, 1, 1),
+
+SND_SOC_DAPM_INPUT("AINL"),
+SND_SOC_DAPM_INPUT("AINR"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ { "VOUT1L", NULL, "DAC1" },
+ { "VOUT1R", NULL, "DAC1" },
+
+ { "VOUT2L", NULL, "DAC2" },
+ { "VOUT2R", NULL, "DAC2" },
+
+ { "VOUT3L", NULL, "DAC3" },
+ { "VOUT3R", NULL, "DAC3" },
+
+ { "ADC", NULL, "AINL" },
+ { "ADC", NULL, "AINR" },
+};
+
+static int wm8580_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8580_dapm_widgets,
+ ARRAY_SIZE(wm8580_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 prescale:1;
+ u32 postscale:1;
+ u32 freqmode:2;
+ u32 n:4;
+ u32 k:24;
+};
+
+/* The size in bits of the pll divide */
+#define FIXED_PLL_SIZE (1 << 22)
+
+/* PLL rate to output rate divisions */
+static struct {
+ unsigned int div;
+ unsigned int freqmode;
+ unsigned int postscale;
+} post_table[] = {
+ { 2, 0, 0 },
+ { 4, 0, 1 },
+ { 4, 1, 0 },
+ { 8, 1, 1 },
+ { 8, 2, 0 },
+ { 16, 2, 1 },
+ { 12, 3, 0 },
+ { 24, 3, 1 }
+};
+
+static int pll_factors(struct _pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+ int i;
+
+ pr_debug("wm8580: PLL %uHz->%uHz\n", source, target);
+
+ /* Scale the output frequency up; the PLL should run in the
+ * region of 90-100MHz.
+ */
+ for (i = 0; i < ARRAY_SIZE(post_table); i++) {
+ if (target * post_table[i].div >= 90000000 &&
+ target * post_table[i].div <= 100000000) {
+ pll_div->freqmode = post_table[i].freqmode;
+ pll_div->postscale = post_table[i].postscale;
+ target *= post_table[i].div;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(post_table)) {
+ printk(KERN_ERR "wm8580: Unable to scale output frequency "
+ "%u\n", target);
+ return -EINVAL;
+ }
+
+ Ndiv = target / source;
+
+ if (Ndiv < 5) {
+ source /= 2;
+ pll_div->prescale = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->prescale = 0;
+
+ if ((Ndiv < 5) || (Ndiv > 13)) {
+ printk(KERN_ERR
+ "WM8580 N=%u outside supported range\n", Ndiv);
+ return -EINVAL;
+ }
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ pll_div->k = K;
+
+ pr_debug("PLL %x.%x prescale %d freqmode %d postscale %d\n",
+ pll_div->n, pll_div->k, pll_div->prescale, pll_div->freqmode,
+ pll_div->postscale);
+
+ return 0;
+}
+
+static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ int offset;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec);
+ struct pll_state *state;
+ struct _pll_div pll_div;
+ unsigned int reg;
+ unsigned int pwr_mask;
+ int ret;
+
+ /* GCC isn't able to work out the ifs below for initialising/using
+ * pll_div so suppress warnings.
+ */
+ memset(&pll_div, 0, sizeof(pll_div));
+
+ switch (pll_id) {
+ case WM8580_PLLA:
+ state = &wm8580->a;
+ offset = 0;
+ pwr_mask = WM8580_PWRDN2_PLLAPD;
+ break;
+ case WM8580_PLLB:
+ state = &wm8580->b;
+ offset = 4;
+ pwr_mask = WM8580_PWRDN2_PLLBPD;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ if (freq_in && freq_out) {
+ ret = pll_factors(&pll_div, freq_out, freq_in);
+ if (ret != 0)
+ return ret;
+ }
+
+ state->in = freq_in;
+ state->out = freq_out;
+
+ /* Always disable the PLL - it is not safe to leave it running
+ * while reprogramming it.
+ */
+ reg = snd_soc_read(codec, WM8580_PWRDN2);
+ snd_soc_write(codec, WM8580_PWRDN2, reg | pwr_mask);
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ snd_soc_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff);
+ snd_soc_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0x1ff);
+ snd_soc_write(codec, WM8580_PLLA3 + offset,
+ (pll_div.k >> 18 & 0xf) | (pll_div.n << 4));
+
+ reg = snd_soc_read(codec, WM8580_PLLA4 + offset);
+ reg &= ~0x1b;
+ reg |= pll_div.prescale | pll_div.postscale << 1 |
+ pll_div.freqmode << 3;
+
+ snd_soc_write(codec, WM8580_PLLA4 + offset, reg);
+
+ /* All done, turn it on */
+ reg = snd_soc_read(codec, WM8580_PWRDN2);
+ snd_soc_write(codec, WM8580_PWRDN2, reg & ~pwr_mask);
+
+ return 0;
+}
+
+static const int wm8580_sysclk_ratios[] = {
+ 128, 192, 256, 384, 512, 768, 1152,
+};
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8580_paif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec);
+ u16 paifa = 0;
+ u16 paifb = 0;
+ int i, ratio, osr;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ paifa |= 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ paifa |= 0x0;
+ paifb |= WM8580_AIF_LENGTH_20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ paifa |= 0x0;
+ paifb |= WM8580_AIF_LENGTH_24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ paifa |= 0x0;
+ paifb |= WM8580_AIF_LENGTH_32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Look up the SYSCLK ratio; accept only exact matches */
+ ratio = wm8580->sysclk[dai->driver->id] / params_rate(params);
+ for (i = 0; i < ARRAY_SIZE(wm8580_sysclk_ratios); i++)
+ if (ratio == wm8580_sysclk_ratios[i])
+ break;
+ if (i == ARRAY_SIZE(wm8580_sysclk_ratios)) {
+ dev_err(codec->dev, "Invalid clock ratio %d/%d\n",
+ wm8580->sysclk[dai->driver->id], params_rate(params));
+ return -EINVAL;
+ }
+ paifa |= i;
+ dev_dbg(codec->dev, "Running at %dfs with %dHz clock\n",
+ wm8580_sysclk_ratios[i], wm8580->sysclk[dai->driver->id]);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ switch (ratio) {
+ case 128:
+ case 192:
+ osr = WM8580_DACOSR;
+ dev_dbg(codec->dev, "Selecting 64x OSR\n");
+ break;
+ default:
+ osr = 0;
+ dev_dbg(codec->dev, "Selecting 128x OSR\n");
+ break;
+ }
+
+ snd_soc_update_bits(codec, WM8580_PAIF3, WM8580_DACOSR, osr);
+ }
+
+ snd_soc_update_bits(codec, WM8580_PAIF1 + dai->driver->id,
+ WM8580_AIF_RATE_MASK | WM8580_AIF_BCLKSEL_MASK,
+ paifa);
+ snd_soc_update_bits(codec, WM8580_PAIF3 + dai->driver->id,
+ WM8580_AIF_LENGTH_MASK, paifb);
+ return 0;
+}
+
+static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int aifa;
+ unsigned int aifb;
+ int can_invert_lrclk;
+
+ aifa = snd_soc_read(codec, WM8580_PAIF1 + codec_dai->driver->id);
+ aifb = snd_soc_read(codec, WM8580_PAIF3 + codec_dai->driver->id);
+
+ aifb &= ~(WM8580_AIF_FMT_MASK | WM8580_AIF_LRP | WM8580_AIF_BCP);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ aifa &= ~WM8580_AIF_MS;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aifa |= WM8580_AIF_MS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ can_invert_lrclk = 1;
+ aifb |= WM8580_AIF_FMT_I2S;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ can_invert_lrclk = 1;
+ aifb |= WM8580_AIF_FMT_RIGHTJ;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ can_invert_lrclk = 1;
+ aifb |= WM8580_AIF_FMT_LEFTJ;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ can_invert_lrclk = 0;
+ aifb |= WM8580_AIF_FMT_DSP;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ can_invert_lrclk = 0;
+ aifb |= WM8580_AIF_FMT_DSP;
+ aifb |= WM8580_AIF_LRP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+
+ case SND_SOC_DAIFMT_IB_IF:
+ if (!can_invert_lrclk)
+ return -EINVAL;
+ aifb |= WM8580_AIF_BCP;
+ aifb |= WM8580_AIF_LRP;
+ break;
+
+ case SND_SOC_DAIFMT_IB_NF:
+ aifb |= WM8580_AIF_BCP;
+ break;
+
+ case SND_SOC_DAIFMT_NB_IF:
+ if (!can_invert_lrclk)
+ return -EINVAL;
+ aifb |= WM8580_AIF_LRP;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8580_PAIF1 + codec_dai->driver->id, aifa);
+ snd_soc_write(codec, WM8580_PAIF3 + codec_dai->driver->id, aifb);
+
+ return 0;
+}
+
+static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ switch (div_id) {
+ case WM8580_MCLK:
+ reg = snd_soc_read(codec, WM8580_PLLB4);
+ reg &= ~WM8580_PLLB4_MCLKOUTSRC_MASK;
+
+ switch (div) {
+ case WM8580_CLKSRC_MCLK:
+ /* Input */
+ break;
+
+ case WM8580_CLKSRC_PLLA:
+ reg |= WM8580_PLLB4_MCLKOUTSRC_PLLA;
+ break;
+ case WM8580_CLKSRC_PLLB:
+ reg |= WM8580_PLLB4_MCLKOUTSRC_PLLB;
+ break;
+
+ case WM8580_CLKSRC_OSC:
+ reg |= WM8580_PLLB4_MCLKOUTSRC_OSC;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ snd_soc_write(codec, WM8580_PLLB4, reg);
+ break;
+
+ case WM8580_CLKOUTSRC:
+ reg = snd_soc_read(codec, WM8580_PLLB4);
+ reg &= ~WM8580_PLLB4_CLKOUTSRC_MASK;
+
+ switch (div) {
+ case WM8580_CLKSRC_NONE:
+ break;
+
+ case WM8580_CLKSRC_PLLA:
+ reg |= WM8580_PLLB4_CLKOUTSRC_PLLACLK;
+ break;
+
+ case WM8580_CLKSRC_PLLB:
+ reg |= WM8580_PLLB4_CLKOUTSRC_PLLBCLK;
+ break;
+
+ case WM8580_CLKSRC_OSC:
+ reg |= WM8580_PLLB4_CLKOUTSRC_OSCCLK;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ snd_soc_write(codec, WM8580_PLLB4, reg);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8580_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec);
+ int sel, sel_mask, sel_shift;
+
+ switch (dai->driver->id) {
+ case WM8580_DAI_PAIFRX:
+ sel_mask = 0x3;
+ sel_shift = 0;
+ break;
+
+ case WM8580_DAI_PAIFTX:
+ sel_mask = 0xc;
+ sel_shift = 2;
+ break;
+
+ default:
+ BUG_ON("Unknown DAI driver ID\n");
+ return -EINVAL;
+ }
+
+ switch (clk_id) {
+ case WM8580_CLKSRC_ADCMCLK:
+ if (dai->driver->id != WM8580_DAI_PAIFTX)
+ return -EINVAL;
+ sel = 0 << sel_shift;
+ break;
+ case WM8580_CLKSRC_PLLA:
+ sel = 1 << sel_shift;
+ break;
+ case WM8580_CLKSRC_PLLB:
+ sel = 2 << sel_shift;
+ break;
+ case WM8580_CLKSRC_MCLK:
+ sel = 3 << sel_shift;
+ break;
+ default:
+ dev_err(codec->dev, "Unknown clock %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ /* We really should validate PLL settings but not yet */
+ wm8580->sysclk[dai->driver->id] = freq;
+
+ return snd_soc_update_bits(codec, WM8580_CLKSEL, sel_mask, sel);
+}
+
+static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ reg = snd_soc_read(codec, WM8580_DAC_CONTROL5);
+
+ if (mute)
+ reg |= WM8580_DAC_CONTROL5_MUTEALL;
+ else
+ reg &= ~WM8580_DAC_CONTROL5_MUTEALL;
+
+ snd_soc_write(codec, WM8580_DAC_CONTROL5, reg);
+
+ return 0;
+}
+
+static int wm8580_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Power up and get individual control of the DACs */
+ reg = snd_soc_read(codec, WM8580_PWRDN1);
+ reg &= ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD);
+ snd_soc_write(codec, WM8580_PWRDN1, reg);
+
+ /* Make VMID high impedance */
+ reg = snd_soc_read(codec, WM8580_ADC_CONTROL1);
+ reg &= ~0x100;
+ snd_soc_write(codec, WM8580_ADC_CONTROL1, reg);
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ reg = snd_soc_read(codec, WM8580_PWRDN1);
+ snd_soc_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8580_dai_ops_playback = {
+ .set_sysclk = wm8580_set_sysclk,
+ .hw_params = wm8580_paif_hw_params,
+ .set_fmt = wm8580_set_paif_dai_fmt,
+ .set_clkdiv = wm8580_set_dai_clkdiv,
+ .set_pll = wm8580_set_dai_pll,
+ .digital_mute = wm8580_digital_mute,
+};
+
+static struct snd_soc_dai_ops wm8580_dai_ops_capture = {
+ .set_sysclk = wm8580_set_sysclk,
+ .hw_params = wm8580_paif_hw_params,
+ .set_fmt = wm8580_set_paif_dai_fmt,
+ .set_clkdiv = wm8580_set_dai_clkdiv,
+ .set_pll = wm8580_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver wm8580_dai[] = {
+ {
+ .name = "wm8580-hifi-playback",
+ .id = WM8580_DAI_PAIFRX,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = WM8580_FORMATS,
+ },
+ .ops = &wm8580_dai_ops_playback,
+ },
+ {
+ .name = "wm8580-hifi-capture",
+ .id = WM8580_DAI_PAIFTX,
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = WM8580_FORMATS,
+ },
+ .ops = &wm8580_dai_ops_capture,
+ },
+};
+
+static int wm8580_probe(struct snd_soc_codec *codec)
+{
+ struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0,i;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8580->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8580->supplies); i++)
+ wm8580->supplies[i].supply = wm8580_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8580->supplies),
+ wm8580->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8580->supplies),
+ wm8580->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_regulator_get;
+ }
+
+ /* Get the codec into a known state */
+ ret = snd_soc_write(codec, WM8580_RESET, 0);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to reset codec: %d\n", ret);
+ goto err_regulator_enable;
+ }
+
+ wm8580_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_add_controls(codec, wm8580_snd_controls,
+ ARRAY_SIZE(wm8580_snd_controls));
+ wm8580_add_widgets(codec);
+
+ return 0;
+
+err_regulator_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
+err_regulator_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
+ return ret;
+}
+
+/* power down chip */
+static int wm8580_remove(struct snd_soc_codec *codec)
+{
+ struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec);
+
+ wm8580_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
+ regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8580 = {
+ .probe = wm8580_probe,
+ .remove = wm8580_remove,
+ .set_bias_level = wm8580_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8580_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8580_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int wm8580_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8580_priv *wm8580;
+ int ret;
+
+ wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL);
+ if (wm8580 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8580);
+ wm8580->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8580, wm8580_dai, ARRAY_SIZE(wm8580_dai));
+ if (ret < 0)
+ kfree(wm8580);
+ return ret;
+}
+
+static int wm8580_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8580_i2c_id[] = {
+ { "wm8580", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8580_i2c_id);
+
+static struct i2c_driver wm8580_i2c_driver = {
+ .driver = {
+ .name = "wm8580-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8580_i2c_probe,
+ .remove = wm8580_i2c_remove,
+ .id_table = wm8580_i2c_id,
+};
+#endif
+
+static int __init wm8580_modinit(void)
+{
+ int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8580_i2c_driver);
+ if (ret != 0) {
+ pr_err("Failed to register WM8580 I2C driver: %d\n", ret);
+ }
+#endif
+
+ return ret;
+}
+module_init(wm8580_modinit);
+
+static void __exit wm8580_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8580_i2c_driver);
+#endif
+}
+module_exit(wm8580_exit);
+
+MODULE_DESCRIPTION("ASoC WM8580 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8580.h b/sound/soc/codecs/wm8580.h
new file mode 100644
index 00000000..1d34656d
--- /dev/null
+++ b/sound/soc/codecs/wm8580.h
@@ -0,0 +1,35 @@
+/*
+ * wm8580.h -- audio driver for WM8580
+ *
+ * Copyright 2008 Samsung Electronics.
+ * Author: Ryu Euiyoul
+ * ryu.real@gmail.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _WM8580_H
+#define _WM8580_H
+
+#define WM8580_PLLA 1
+#define WM8580_PLLB 2
+
+#define WM8580_MCLK 1
+#define WM8580_CLKOUTSRC 2
+
+#define WM8580_CLKSRC_MCLK 1
+#define WM8580_CLKSRC_PLLA 2
+#define WM8580_CLKSRC_PLLB 3
+#define WM8580_CLKSRC_OSC 4
+#define WM8580_CLKSRC_NONE 5
+#define WM8580_CLKSRC_ADCMCLK 6
+
+#define WM8580_DAI_PAIFRX 0
+#define WM8580_DAI_PAIFTX 1
+
+#endif
+
diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c
new file mode 100644
index 00000000..1dae5c4d
--- /dev/null
+++ b/sound/soc/codecs/wm8711.c
@@ -0,0 +1,533 @@
+/*
+ * wm8711.c -- WM8711 ALSA SoC Audio driver
+ *
+ * Copyright 2006 Wolfson Microelectronics
+ *
+ * Author: Mike Arthur <linux@wolfsonmicro.com>
+ *
+ * Based on wm8731.c by Richard Purdie
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+
+#include "wm8711.h"
+
+/* codec private data */
+struct wm8711_priv {
+ enum snd_soc_control_type bus_type;
+ unsigned int sysclk;
+};
+
+/*
+ * wm8711 register cache
+ * We can't read the WM8711 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 wm8711_reg[WM8711_CACHEREGNUM] = {
+ 0x0079, 0x0079, 0x000a, 0x0008,
+ 0x009f, 0x000a, 0x0000, 0x0000
+};
+
+#define wm8711_reset(c) snd_soc_write(c, WM8711_RESET, 0)
+
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+
+static const struct snd_kcontrol_new wm8711_snd_controls[] = {
+
+SOC_DOUBLE_R_TLV("Master Playback Volume", WM8711_LOUT1V, WM8711_ROUT1V,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Master Playback ZC Switch", WM8711_LOUT1V, WM8711_ROUT1V,
+ 7, 1, 0),
+
+};
+
+/* Output Mixer */
+static const struct snd_kcontrol_new wm8711_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8711_APANA, 3, 1, 0),
+SOC_DAPM_SINGLE("HiFi Playback Switch", WM8711_APANA, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8711_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Output Mixer", WM8711_PWR, 4, 1,
+ &wm8711_output_mixer_controls[0],
+ ARRAY_SIZE(wm8711_output_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8711_PWR, 3, 1),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+};
+
+static const struct snd_soc_dapm_route wm8711_intercon[] = {
+ /* output mixer */
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "HiFi Playback Switch", "DAC"},
+
+ /* outputs */
+ {"RHPOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+ {"LHPOUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+};
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:4;
+ u8 bosr:1;
+ u8 usb:1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0, 0x0},
+ {18432000, 48000, 384, 0x0, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x0, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0x6, 0x0, 0x0},
+ {18432000, 32000, 576, 0x6, 0x1, 0x0},
+ {12000000, 32000, 375, 0x6, 0x0, 0x1},
+
+ /* 8k */
+ {12288000, 8000, 1536, 0x3, 0x0, 0x0},
+ {18432000, 8000, 2304, 0x3, 0x1, 0x0},
+ {11289600, 8000, 1408, 0xb, 0x0, 0x0},
+ {16934400, 8000, 2112, 0xb, 0x1, 0x0},
+ {12000000, 8000, 1500, 0x3, 0x0, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0x7, 0x0, 0x0},
+ {18432000, 96000, 192, 0x7, 0x1, 0x0},
+ {12000000, 96000, 125, 0x7, 0x0, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x8, 0x0, 0x0},
+ {16934400, 44100, 384, 0x8, 0x1, 0x0},
+ {12000000, 44100, 272, 0x8, 0x1, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0xf, 0x0, 0x0},
+ {16934400, 88200, 192, 0xf, 0x1, 0x0},
+ {12000000, 88200, 136, 0xf, 0x1, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return 0;
+}
+
+static int wm8711_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8711_priv *wm8711 = snd_soc_codec_get_drvdata(codec);
+ u16 iface = snd_soc_read(codec, WM8711_IFACE) & 0xfff3;
+ int i = get_coeff(wm8711->sysclk, params_rate(params));
+ u16 srate = (coeff_div[i].sr << 2) |
+ (coeff_div[i].bosr << 1) | coeff_div[i].usb;
+
+ snd_soc_write(codec, WM8711_SRATE, srate);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ }
+
+ snd_soc_write(codec, WM8711_IFACE, iface);
+ return 0;
+}
+
+static int wm8711_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+
+ /* set active */
+ snd_soc_write(codec, WM8711_ACTIVE, 0x0001);
+
+ return 0;
+}
+
+static void wm8711_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+
+ /* deactivate */
+ if (!codec->active) {
+ udelay(50);
+ snd_soc_write(codec, WM8711_ACTIVE, 0x0);
+ }
+}
+
+static int wm8711_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8711_APDIGI) & 0xfff7;
+
+ if (mute)
+ snd_soc_write(codec, WM8711_APDIGI, mute_reg | 0x8);
+ else
+ snd_soc_write(codec, WM8711_APDIGI, mute_reg);
+
+ return 0;
+}
+
+static int wm8711_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8711_priv *wm8711 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8711->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int wm8711_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = snd_soc_read(codec, WM8711_IFACE) & 0x000c;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ snd_soc_write(codec, WM8711_IFACE, iface);
+ return 0;
+}
+
+
+static int wm8711_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg = snd_soc_read(codec, WM8711_PWR) & 0xff7f;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ snd_soc_write(codec, WM8711_PWR, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ snd_soc_write(codec, WM8711_PWR, reg | 0x0040);
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, WM8711_ACTIVE, 0x0);
+ snd_soc_write(codec, WM8711_PWR, 0xffff);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8711_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8711_ops = {
+ .prepare = wm8711_pcm_prepare,
+ .hw_params = wm8711_hw_params,
+ .shutdown = wm8711_shutdown,
+ .digital_mute = wm8711_mute,
+ .set_sysclk = wm8711_set_dai_sysclk,
+ .set_fmt = wm8711_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8711_dai = {
+ .name = "wm8711-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8711_RATES,
+ .formats = WM8711_FORMATS,
+ },
+ .ops = &wm8711_ops,
+};
+
+static int wm8711_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ snd_soc_write(codec, WM8711_ACTIVE, 0x0);
+ wm8711_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8711_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8711_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int wm8711_probe(struct snd_soc_codec *codec)
+{
+ struct wm8711_priv *wm8711 = snd_soc_codec_get_drvdata(codec);
+ int ret, reg;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8711->bus_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8711_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Latch the update bits */
+ reg = snd_soc_read(codec, WM8711_LOUT1V);
+ snd_soc_write(codec, WM8711_LOUT1V, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8711_ROUT1V);
+ snd_soc_write(codec, WM8711_ROUT1V, reg | 0x0100);
+
+ snd_soc_add_controls(codec, wm8711_snd_controls,
+ ARRAY_SIZE(wm8711_snd_controls));
+
+ return ret;
+
+}
+
+/* power down chip */
+static int wm8711_remove(struct snd_soc_codec *codec)
+{
+ wm8711_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8711 = {
+ .probe = wm8711_probe,
+ .remove = wm8711_remove,
+ .suspend = wm8711_suspend,
+ .resume = wm8711_resume,
+ .set_bias_level = wm8711_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8711_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8711_reg,
+ .dapm_widgets = wm8711_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm8711_dapm_widgets),
+ .dapm_routes = wm8711_intercon,
+ .num_dapm_routes = ARRAY_SIZE(wm8711_intercon),
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8711_spi_probe(struct spi_device *spi)
+{
+ struct wm8711_priv *wm8711;
+ int ret;
+
+ wm8711 = kzalloc(sizeof(struct wm8711_priv), GFP_KERNEL);
+ if (wm8711 == NULL)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, wm8711);
+ wm8711->bus_type = SND_SOC_SPI;
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8711, &wm8711_dai, 1);
+ if (ret < 0)
+ kfree(wm8711);
+ return ret;
+}
+
+static int __devexit wm8711_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8711_spi_driver = {
+ .driver = {
+ .name = "wm8711-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8711_spi_probe,
+ .remove = __devexit_p(wm8711_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8711_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct wm8711_priv *wm8711;
+ int ret;
+
+ wm8711 = kzalloc(sizeof(struct wm8711_priv), GFP_KERNEL);
+ if (wm8711 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, wm8711);
+ wm8711->bus_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&client->dev,
+ &soc_codec_dev_wm8711, &wm8711_dai, 1);
+ if (ret < 0)
+ kfree(wm8711);
+ return ret;
+}
+
+static __devexit int wm8711_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8711_i2c_id[] = {
+ { "wm8711", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8711_i2c_id);
+
+static struct i2c_driver wm8711_i2c_driver = {
+ .driver = {
+ .name = "wm8711-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8711_i2c_probe,
+ .remove = __devexit_p(wm8711_i2c_remove),
+ .id_table = wm8711_i2c_id,
+};
+#endif
+
+static int __init wm8711_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8711_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8711 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8711_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8711 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return 0;
+}
+module_init(wm8711_modinit);
+
+static void __exit wm8711_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8711_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8711_spi_driver);
+#endif
+}
+module_exit(wm8711_exit);
+
+MODULE_DESCRIPTION("ASoC WM8711 driver");
+MODULE_AUTHOR("Mike Arthur");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8711.h b/sound/soc/codecs/wm8711.h
new file mode 100644
index 00000000..a61db985
--- /dev/null
+++ b/sound/soc/codecs/wm8711.h
@@ -0,0 +1,39 @@
+/*
+ * wm8711.h -- WM8711 Soc Audio driver
+ *
+ * Copyright 2006 Wolfson Microelectronics
+ *
+ * Author: Mike Arthur <linux@wolfsonmicro.com>
+ *
+ * Based on wm8731.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8711_H
+#define _WM8711_H
+
+/* WM8711 register space */
+
+#define WM8711_LOUT1V 0x02
+#define WM8711_ROUT1V 0x03
+#define WM8711_APANA 0x04
+#define WM8711_APDIGI 0x05
+#define WM8711_PWR 0x06
+#define WM8711_IFACE 0x07
+#define WM8711_SRATE 0x08
+#define WM8711_ACTIVE 0x09
+#define WM8711_RESET 0x0f
+
+#define WM8711_CACHEREGNUM 8
+
+#define WM8711_SYSCLK 0
+#define WM8711_DAI 0
+
+struct wm8711_setup_data {
+ unsigned short i2c_address;
+};
+
+#endif
diff --git a/sound/soc/codecs/wm8727.c b/sound/soc/codecs/wm8727.c
new file mode 100644
index 00000000..74880828
--- /dev/null
+++ b/sound/soc/codecs/wm8727.c
@@ -0,0 +1,84 @@
+/*
+ * wm8727.c
+ *
+ * Created on: 15-Oct-2009
+ * Author: neil.jones@imgtec.com
+ *
+ * Copyright (C) 2009 Imagination Technologies Ltd.
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+/*
+ * Note this is a simple chip with no configuration interface, sample rate is
+ * determined automatically by examining the Master clock and Bit clock ratios
+ */
+#define WM8727_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |\
+ SNDRV_PCM_RATE_192000)
+
+
+static struct snd_soc_dai_driver wm8727_dai = {
+ .name = "wm8727-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8727_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8727;
+
+static __devinit int wm8727_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_wm8727, &wm8727_dai, 1);
+}
+
+static int __devexit wm8727_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver wm8727_codec_driver = {
+ .driver = {
+ .name = "wm8727-codec",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = wm8727_probe,
+ .remove = __devexit_p(wm8727_remove),
+};
+
+static int __init wm8727_init(void)
+{
+ return platform_driver_register(&wm8727_codec_driver);
+}
+module_init(wm8727_init);
+
+static void __exit wm8727_exit(void)
+{
+ platform_driver_unregister(&wm8727_codec_driver);
+}
+module_exit(wm8727_exit);
+
+MODULE_DESCRIPTION("ASoC wm8727 driver");
+MODULE_AUTHOR("Neil Jones");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c
new file mode 100644
index 00000000..86d4718d
--- /dev/null
+++ b/sound/soc/codecs/wm8728.c
@@ -0,0 +1,388 @@
+/*
+ * wm8728.c -- WM8728 ALSA SoC Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8728.h"
+
+/*
+ * We can't read the WM8728 register space so we cache them instead.
+ * Note that the defaults here aren't the physical defaults, we latch
+ * the volume update bits, mute the output and enable infinite zero
+ * detect.
+ */
+static const u16 wm8728_reg_defaults[] = {
+ 0x1ff,
+ 0x1ff,
+ 0x001,
+ 0x100,
+};
+
+/* codec private data */
+struct wm8728_priv {
+ enum snd_soc_control_type control_type;
+};
+
+static const DECLARE_TLV_DB_SCALE(wm8728_tlv, -12750, 50, 1);
+
+static const struct snd_kcontrol_new wm8728_snd_controls[] = {
+
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8728_DACLVOL, WM8728_DACRVOL,
+ 0, 255, 0, wm8728_tlv),
+
+SOC_SINGLE("Deemphasis", WM8728_DACCTL, 1, 1, 0),
+};
+
+/*
+ * DAPM controls.
+ */
+static const struct snd_soc_dapm_widget wm8728_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_OUTPUT("VOUTL"),
+SND_SOC_DAPM_OUTPUT("VOUTR"),
+};
+
+static const struct snd_soc_dapm_route wm8728_intercon[] = {
+ {"VOUTL", NULL, "DAC"},
+ {"VOUTR", NULL, "DAC"},
+};
+
+static int wm8728_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8728_DACCTL);
+
+ if (mute)
+ snd_soc_write(codec, WM8728_DACCTL, mute_reg | 1);
+ else
+ snd_soc_write(codec, WM8728_DACCTL, mute_reg & ~1);
+
+ return 0;
+}
+
+static int wm8728_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u16 dac = snd_soc_read(codec, WM8728_DACCTL);
+
+ dac &= ~0x18;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ dac |= 0x10;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ dac |= 0x08;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8728_DACCTL, dac);
+
+ return 0;
+}
+
+static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = snd_soc_read(codec, WM8728_IFCTL);
+
+ /* Currently only I2S is supported by the driver, though the
+ * hardware is more flexible.
+ */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* The hardware only support full slave mode */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ iface &= ~0x22;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x20;
+ iface &= ~0x02;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x02;
+ iface &= ~0x20;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x22;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8728_IFCTL, iface);
+ return 0;
+}
+
+static int wm8728_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+ int i;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Power everything up... */
+ reg = snd_soc_read(codec, WM8728_DACCTL);
+ snd_soc_write(codec, WM8728_DACCTL, reg & ~0x4);
+
+ /* ..then sync in the register cache. */
+ for (i = 0; i < ARRAY_SIZE(wm8728_reg_defaults); i++)
+ snd_soc_write(codec, i,
+ snd_soc_read(codec, i));
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ reg = snd_soc_read(codec, WM8728_DACCTL);
+ snd_soc_write(codec, WM8728_DACCTL, reg | 0x4);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8728_RATES (SNDRV_PCM_RATE_8000_192000)
+
+#define WM8728_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8728_dai_ops = {
+ .hw_params = wm8728_hw_params,
+ .digital_mute = wm8728_mute,
+ .set_fmt = wm8728_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8728_dai = {
+ .name = "wm8728-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8728_RATES,
+ .formats = WM8728_FORMATS,
+ },
+ .ops = &wm8728_dai_ops,
+};
+
+static int wm8728_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8728_resume(struct snd_soc_codec *codec)
+{
+ wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int wm8728_probe(struct snd_soc_codec *codec)
+{
+ struct wm8728_priv *wm8728 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8728->control_type);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8728: failed to configure cache I/O: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* power on device */
+ wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_add_controls(codec, wm8728_snd_controls,
+ ARRAY_SIZE(wm8728_snd_controls));
+
+ return ret;
+}
+
+static int wm8728_remove(struct snd_soc_codec *codec)
+{
+ wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8728 = {
+ .probe = wm8728_probe,
+ .remove = wm8728_remove,
+ .suspend = wm8728_suspend,
+ .resume = wm8728_resume,
+ .set_bias_level = wm8728_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8728_reg_defaults),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8728_reg_defaults,
+ .dapm_widgets = wm8728_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm8728_dapm_widgets),
+ .dapm_routes = wm8728_intercon,
+ .num_dapm_routes = ARRAY_SIZE(wm8728_intercon),
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8728_spi_probe(struct spi_device *spi)
+{
+ struct wm8728_priv *wm8728;
+ int ret;
+
+ wm8728 = kzalloc(sizeof(struct wm8728_priv), GFP_KERNEL);
+ if (wm8728 == NULL)
+ return -ENOMEM;
+
+ wm8728->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8728);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8728, &wm8728_dai, 1);
+ if (ret < 0)
+ kfree(wm8728);
+ return ret;
+}
+
+static int __devexit wm8728_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8728_spi_driver = {
+ .driver = {
+ .name = "wm8728-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8728_spi_probe,
+ .remove = __devexit_p(wm8728_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8728_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8728_priv *wm8728;
+ int ret;
+
+ wm8728 = kzalloc(sizeof(struct wm8728_priv), GFP_KERNEL);
+ if (wm8728 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8728);
+ wm8728->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8728, &wm8728_dai, 1);
+ if (ret < 0)
+ kfree(wm8728);
+ return ret;
+}
+
+static __devexit int wm8728_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8728_i2c_id[] = {
+ { "wm8728", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8728_i2c_id);
+
+static struct i2c_driver wm8728_i2c_driver = {
+ .driver = {
+ .name = "wm8728-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8728_i2c_probe,
+ .remove = __devexit_p(wm8728_i2c_remove),
+ .id_table = wm8728_i2c_id,
+};
+#endif
+
+static int __init wm8728_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8728_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8728 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8728_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8728 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8728_modinit);
+
+static void __exit wm8728_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8728_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8728_spi_driver);
+#endif
+}
+module_exit(wm8728_exit);
+
+MODULE_DESCRIPTION("ASoC WM8728 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8728.h b/sound/soc/codecs/wm8728.h
new file mode 100644
index 00000000..8aea362f
--- /dev/null
+++ b/sound/soc/codecs/wm8728.h
@@ -0,0 +1,21 @@
+/*
+ * wm8728.h -- WM8728 ASoC codec driver
+ *
+ * Copyright 2008 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8728_H
+#define _WM8728_H
+
+#define WM8728_DACLVOL 0x00
+#define WM8728_DACRVOL 0x01
+#define WM8728_DACCTL 0x02
+#define WM8728_IFCTL 0x03
+
+#endif
diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c
new file mode 100644
index 00000000..f5a0ec4a
--- /dev/null
+++ b/sound/soc/codecs/wm8731.c
@@ -0,0 +1,727 @@
+/*
+ * wm8731.c -- WM8731 ALSA SoC Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8731.h"
+
+#define WM8731_NUM_SUPPLIES 4
+static const char *wm8731_supply_names[WM8731_NUM_SUPPLIES] = {
+ "AVDD",
+ "HPVDD",
+ "DCVDD",
+ "DBVDD",
+};
+
+/* codec private data */
+struct wm8731_priv {
+ enum snd_soc_control_type control_type;
+ struct regulator_bulk_data supplies[WM8731_NUM_SUPPLIES];
+ unsigned int sysclk;
+ int sysclk_type;
+ int playback_fs;
+ bool deemph;
+};
+
+
+/*
+ * wm8731 register cache
+ * We can't read the WM8731 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 wm8731_reg[WM8731_CACHEREGNUM] = {
+ 0x0097, 0x0097, 0x0079, 0x0079,
+ 0x000a, 0x0008, 0x009f, 0x000a,
+ 0x0000, 0x0000
+};
+
+#define wm8731_reset(c) snd_soc_write(c, WM8731_RESET, 0)
+
+static const char *wm8731_input_select[] = {"Line In", "Mic"};
+
+static const struct soc_enum wm8731_insel_enum =
+ SOC_ENUM_SINGLE(WM8731_APANA, 2, 2, wm8731_input_select);
+
+static int wm8731_deemph[] = { 0, 32000, 44100, 48000 };
+
+static int wm8731_set_deemph(struct snd_soc_codec *codec)
+{
+ struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+ int val, i, best;
+
+ /* If we're using deemphasis select the nearest available sample
+ * rate.
+ */
+ if (wm8731->deemph) {
+ best = 1;
+ for (i = 2; i < ARRAY_SIZE(wm8731_deemph); i++) {
+ if (abs(wm8731_deemph[i] - wm8731->playback_fs) <
+ abs(wm8731_deemph[best] - wm8731->playback_fs))
+ best = i;
+ }
+
+ val = best << 1;
+ } else {
+ best = 0;
+ val = 0;
+ }
+
+ dev_dbg(codec->dev, "Set deemphasis %d (%dHz)\n",
+ best, wm8731_deemph[best]);
+
+ return snd_soc_update_bits(codec, WM8731_APDIGI, 0x6, val);
+}
+
+static int wm8731_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8731->deemph;
+
+ return 0;
+}
+
+static int wm8731_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+ int deemph = ucontrol->value.enumerated.item[0];
+ int ret = 0;
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ mutex_lock(&codec->mutex);
+ if (wm8731->deemph != deemph) {
+ wm8731->deemph = deemph;
+
+ wm8731_set_deemph(codec);
+
+ ret = 1;
+ }
+ mutex_unlock(&codec->mutex);
+
+ return ret;
+}
+
+static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 2000, 0);
+
+static const struct snd_kcontrol_new wm8731_snd_controls[] = {
+
+SOC_DOUBLE_R_TLV("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Master Playback ZC Switch", WM8731_LOUT1V, WM8731_ROUT1V,
+ 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0,
+ in_tlv),
+SOC_DOUBLE_R("Line Capture Switch", WM8731_LINVOL, WM8731_RINVOL, 7, 1, 1),
+
+SOC_SINGLE_TLV("Mic Boost Volume", WM8731_APANA, 0, 1, 0, mic_tlv),
+SOC_SINGLE("Mic Capture Switch", WM8731_APANA, 1, 1, 1),
+
+SOC_SINGLE_TLV("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1,
+ sidetone_tlv),
+
+SOC_SINGLE("ADC High Pass Filter Switch", WM8731_APDIGI, 0, 1, 1),
+SOC_SINGLE("Store DC Offset Switch", WM8731_APDIGI, 4, 1, 0),
+
+SOC_SINGLE_BOOL_EXT("Playback Deemphasis Switch", 0,
+ wm8731_get_deemph, wm8731_put_deemph),
+};
+
+/* Output Mixer */
+static const struct snd_kcontrol_new wm8731_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8731_APANA, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic Sidetone Switch", WM8731_APANA, 5, 1, 0),
+SOC_DAPM_SINGLE("HiFi Playback Switch", WM8731_APANA, 4, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new wm8731_input_mux_controls =
+SOC_DAPM_ENUM("Input Select", wm8731_insel_enum);
+
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("ACTIVE",WM8731_ACTIVE, 0, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("OSC", WM8731_PWR, 5, 1, NULL, 0),
+SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1,
+ &wm8731_output_mixer_controls[0],
+ ARRAY_SIZE(wm8731_output_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8731_PWR, 3, 1),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8731_PWR, 2, 1),
+SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &wm8731_input_mux_controls),
+SND_SOC_DAPM_PGA("Line Input", WM8731_PWR, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8731_PWR, 1, 1),
+SND_SOC_DAPM_INPUT("MICIN"),
+SND_SOC_DAPM_INPUT("RLINEIN"),
+SND_SOC_DAPM_INPUT("LLINEIN"),
+};
+
+static int wm8731_check_osc(struct snd_soc_dapm_widget *source,
+ struct snd_soc_dapm_widget *sink)
+{
+ struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(source->codec);
+
+ return wm8731->sysclk_type == WM8731_SYSCLK_XTAL;
+}
+
+static const struct snd_soc_dapm_route wm8731_intercon[] = {
+ {"DAC", NULL, "OSC", wm8731_check_osc},
+ {"ADC", NULL, "OSC", wm8731_check_osc},
+ {"DAC", NULL, "ACTIVE"},
+ {"ADC", NULL, "ACTIVE"},
+
+ /* output mixer */
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "HiFi Playback Switch", "DAC"},
+ {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
+
+ /* outputs */
+ {"RHPOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+ {"LHPOUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+
+ /* input mux */
+ {"Input Mux", "Line In", "Line Input"},
+ {"Input Mux", "Mic", "Mic Bias"},
+ {"ADC", NULL, "Input Mux"},
+
+ /* inputs */
+ {"Line Input", NULL, "LLINEIN"},
+ {"Line Input", NULL, "RLINEIN"},
+ {"Mic Bias", NULL, "MICIN"},
+};
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:4;
+ u8 bosr:1;
+ u8 usb:1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0, 0x0},
+ {18432000, 48000, 384, 0x0, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x0, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0x6, 0x0, 0x0},
+ {18432000, 32000, 576, 0x6, 0x1, 0x0},
+ {12000000, 32000, 375, 0x6, 0x0, 0x1},
+
+ /* 8k */
+ {12288000, 8000, 1536, 0x3, 0x0, 0x0},
+ {18432000, 8000, 2304, 0x3, 0x1, 0x0},
+ {11289600, 8000, 1408, 0xb, 0x0, 0x0},
+ {16934400, 8000, 2112, 0xb, 0x1, 0x0},
+ {12000000, 8000, 1500, 0x3, 0x0, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0x7, 0x0, 0x0},
+ {18432000, 96000, 192, 0x7, 0x1, 0x0},
+ {12000000, 96000, 125, 0x7, 0x0, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x8, 0x0, 0x0},
+ {16934400, 44100, 384, 0x8, 0x1, 0x0},
+ {12000000, 44100, 272, 0x8, 0x1, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0xf, 0x0, 0x0},
+ {16934400, 88200, 192, 0xf, 0x1, 0x0},
+ {12000000, 88200, 136, 0xf, 0x1, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return 0;
+}
+
+static int wm8731_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+ u16 iface = snd_soc_read(codec, WM8731_IFACE) & 0xfff3;
+ int i = get_coeff(wm8731->sysclk, params_rate(params));
+ u16 srate = (coeff_div[i].sr << 2) |
+ (coeff_div[i].bosr << 1) | coeff_div[i].usb;
+
+ wm8731->playback_fs = params_rate(params);
+
+ snd_soc_write(codec, WM8731_SRATE, srate);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ }
+
+ wm8731_set_deemph(codec);
+
+ snd_soc_write(codec, WM8731_IFACE, iface);
+ return 0;
+}
+
+static int wm8731_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8731_APDIGI) & 0xfff7;
+
+ if (mute)
+ snd_soc_write(codec, WM8731_APDIGI, mute_reg | 0x8);
+ else
+ snd_soc_write(codec, WM8731_APDIGI, mute_reg);
+ return 0;
+}
+
+static int wm8731_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+
+ switch (clk_id) {
+ case WM8731_SYSCLK_XTAL:
+ case WM8731_SYSCLK_MCLK:
+ wm8731->sysclk_type = clk_id;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8731->sysclk = freq;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_dapm_sync(&codec->dapm);
+
+ return 0;
+}
+
+
+static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ snd_soc_write(codec, WM8731_IFACE, iface);
+ return 0;
+}
+
+static int wm8731_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+ int i, ret;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+ u16 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies),
+ wm8731->supplies);
+ if (ret != 0)
+ return ret;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) {
+ if (cache[i] == wm8731_reg[i])
+ continue;
+
+ data[0] = (i << 1) | ((cache[i] >> 8)
+ & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ }
+
+ /* Clear PWROFF, gate CLKOUT, everything else as-is */
+ reg = snd_soc_read(codec, WM8731_PWR) & 0xff7f;
+ snd_soc_write(codec, WM8731_PWR, reg | 0x0040);
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, WM8731_PWR, 0xffff);
+ regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies),
+ wm8731->supplies);
+ codec->cache_sync = 1;
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8731_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8731_dai_ops = {
+ .hw_params = wm8731_hw_params,
+ .digital_mute = wm8731_mute,
+ .set_sysclk = wm8731_set_dai_sysclk,
+ .set_fmt = wm8731_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8731_dai = {
+ .name = "wm8731-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8731_RATES,
+ .formats = WM8731_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8731_RATES,
+ .formats = WM8731_FORMATS,},
+ .ops = &wm8731_dai_ops,
+ .symmetric_rates = 1,
+};
+
+#ifdef CONFIG_PM
+static int wm8731_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8731_resume(struct snd_soc_codec *codec)
+{
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define wm8731_suspend NULL
+#define wm8731_resume NULL
+#endif
+
+static int wm8731_probe(struct snd_soc_codec *codec)
+{
+ struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0, i;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8731->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8731->supplies); i++)
+ wm8731->supplies[i].supply = wm8731_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8731->supplies),
+ wm8731->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies),
+ wm8731->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_regulator_get;
+ }
+
+ ret = wm8731_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ goto err_regulator_enable;
+ }
+
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Latch the update bits */
+ snd_soc_update_bits(codec, WM8731_LOUT1V, 0x100, 0);
+ snd_soc_update_bits(codec, WM8731_ROUT1V, 0x100, 0);
+ snd_soc_update_bits(codec, WM8731_LINVOL, 0x100, 0);
+ snd_soc_update_bits(codec, WM8731_RINVOL, 0x100, 0);
+
+ /* Disable bypass path by default */
+ snd_soc_update_bits(codec, WM8731_APANA, 0x8, 0);
+
+ snd_soc_add_controls(codec, wm8731_snd_controls,
+ ARRAY_SIZE(wm8731_snd_controls));
+
+ /* Regulators will have been enabled by bias management */
+ regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies);
+
+ return 0;
+
+err_regulator_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies);
+err_regulator_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8731->supplies), wm8731->supplies);
+
+ return ret;
+}
+
+/* power down chip */
+static int wm8731_remove(struct snd_soc_codec *codec)
+{
+ struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies);
+ regulator_bulk_free(ARRAY_SIZE(wm8731->supplies), wm8731->supplies);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8731 = {
+ .probe = wm8731_probe,
+ .remove = wm8731_remove,
+ .suspend = wm8731_suspend,
+ .resume = wm8731_resume,
+ .set_bias_level = wm8731_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8731_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8731_reg,
+ .dapm_widgets = wm8731_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets),
+ .dapm_routes = wm8731_intercon,
+ .num_dapm_routes = ARRAY_SIZE(wm8731_intercon),
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8731_spi_probe(struct spi_device *spi)
+{
+ struct wm8731_priv *wm8731;
+ int ret;
+
+ wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL);
+ if (wm8731 == NULL)
+ return -ENOMEM;
+
+ wm8731->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8731);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8731, &wm8731_dai, 1);
+ if (ret < 0)
+ kfree(wm8731);
+ return ret;
+}
+
+static int __devexit wm8731_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8731_spi_driver = {
+ .driver = {
+ .name = "wm8731",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8731_spi_probe,
+ .remove = __devexit_p(wm8731_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8731_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8731_priv *wm8731;
+ int ret;
+
+ wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL);
+ if (wm8731 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8731);
+ wm8731->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8731, &wm8731_dai, 1);
+ if (ret < 0)
+ kfree(wm8731);
+ return ret;
+}
+
+static __devexit int wm8731_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8731_i2c_id[] = {
+ { "wm8731", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id);
+
+static struct i2c_driver wm8731_i2c_driver = {
+ .driver = {
+ .name = "wm8731",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8731_i2c_probe,
+ .remove = __devexit_p(wm8731_i2c_remove),
+ .id_table = wm8731_i2c_id,
+};
+#endif
+
+static int __init wm8731_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8731_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8731 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8731_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8731 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8731_modinit);
+
+static void __exit wm8731_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8731_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8731_spi_driver);
+#endif
+}
+module_exit(wm8731_exit);
+
+MODULE_DESCRIPTION("ASoC WM8731 driver");
+MODULE_AUTHOR("Richard Purdie");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8731.h b/sound/soc/codecs/wm8731.h
new file mode 100644
index 00000000..e9c0c76a
--- /dev/null
+++ b/sound/soc/codecs/wm8731.h
@@ -0,0 +1,39 @@
+/*
+ * wm8731.h -- WM8731 Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8731_H
+#define _WM8731_H
+
+/* WM8731 register space */
+
+#define WM8731_LINVOL 0x00
+#define WM8731_RINVOL 0x01
+#define WM8731_LOUT1V 0x02
+#define WM8731_ROUT1V 0x03
+#define WM8731_APANA 0x04
+#define WM8731_APDIGI 0x05
+#define WM8731_PWR 0x06
+#define WM8731_IFACE 0x07
+#define WM8731_SRATE 0x08
+#define WM8731_ACTIVE 0x09
+#define WM8731_RESET 0x0f
+
+#define WM8731_CACHEREGNUM 10
+
+#define WM8731_SYSCLK_XTAL 1
+#define WM8731_SYSCLK_MCLK 2
+
+#define WM8731_DAI 0
+
+#endif
diff --git a/sound/soc/codecs/wm8737.c b/sound/soc/codecs/wm8737.c
new file mode 100644
index 00000000..30c67d06
--- /dev/null
+++ b/sound/soc/codecs/wm8737.c
@@ -0,0 +1,754 @@
+/*
+ * wm8737.c -- WM8737 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8737.h"
+
+#define WM8737_NUM_SUPPLIES 4
+static const char *wm8737_supply_names[WM8737_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD",
+ "AVDD",
+ "MVDD",
+};
+
+/* codec private data */
+struct wm8737_priv {
+ enum snd_soc_control_type control_type;
+ struct regulator_bulk_data supplies[WM8737_NUM_SUPPLIES];
+ unsigned int mclk;
+};
+
+static const u16 wm8737_reg[WM8737_REGISTER_COUNT] = {
+ 0x00C3, /* R0 - Left PGA volume */
+ 0x00C3, /* R1 - Right PGA volume */
+ 0x0007, /* R2 - AUDIO path L */
+ 0x0007, /* R3 - AUDIO path R */
+ 0x0000, /* R4 - 3D Enhance */
+ 0x0000, /* R5 - ADC Control */
+ 0x0000, /* R6 - Power Management */
+ 0x000A, /* R7 - Audio Format */
+ 0x0000, /* R8 - Clocking */
+ 0x000F, /* R9 - MIC Preamp Control */
+ 0x0003, /* R10 - Misc Bias Control */
+ 0x0000, /* R11 - Noise Gate */
+ 0x007C, /* R12 - ALC1 */
+ 0x0000, /* R13 - ALC2 */
+ 0x0032, /* R14 - ALC3 */
+};
+
+static int wm8737_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8737_RESET, 0);
+}
+
+static const unsigned int micboost_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 0, TLV_DB_SCALE_ITEM(1300, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(1800, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(2800, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(3300, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(pga_tlv, -9750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(ng_tlv, -7800, 600, 0);
+static const DECLARE_TLV_DB_SCALE(alc_max_tlv, -1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(alc_target_tlv, -1800, 100, 0);
+
+static const char *micbias_enum_text[] = {
+ "25%",
+ "50%",
+ "75%",
+ "100%",
+};
+
+static const struct soc_enum micbias_enum =
+ SOC_ENUM_SINGLE(WM8737_MIC_PREAMP_CONTROL, 0, 4, micbias_enum_text);
+
+static const char *low_cutoff_text[] = {
+ "Low", "High"
+};
+
+static const struct soc_enum low_3d =
+ SOC_ENUM_SINGLE(WM8737_3D_ENHANCE, 6, 2, low_cutoff_text);
+
+static const char *high_cutoff_text[] = {
+ "High", "Low"
+};
+
+static const struct soc_enum high_3d =
+ SOC_ENUM_SINGLE(WM8737_3D_ENHANCE, 5, 2, high_cutoff_text);
+
+static const char *alc_fn_text[] = {
+ "Disabled", "Right", "Left", "Stereo"
+};
+
+static const struct soc_enum alc_fn =
+ SOC_ENUM_SINGLE(WM8737_ALC1, 7, 4, alc_fn_text);
+
+static const char *alc_hold_text[] = {
+ "0", "2.67ms", "5.33ms", "10.66ms", "21.32ms", "42.64ms", "85.28ms",
+ "170.56ms", "341.12ms", "682.24ms", "1.364s", "2.728s", "5.458s",
+ "10.916s", "21.832s", "43.691s"
+};
+
+static const struct soc_enum alc_hold =
+ SOC_ENUM_SINGLE(WM8737_ALC2, 0, 16, alc_hold_text);
+
+static const char *alc_atk_text[] = {
+ "8.4ms", "16.8ms", "33.6ms", "67.2ms", "134.4ms", "268.8ms", "537.6ms",
+ "1.075s", "2.15s", "4.3s", "8.6s"
+};
+
+static const struct soc_enum alc_atk =
+ SOC_ENUM_SINGLE(WM8737_ALC3, 0, 11, alc_atk_text);
+
+static const char *alc_dcy_text[] = {
+ "33.6ms", "67.2ms", "134.4ms", "268.8ms", "537.6ms", "1.075s", "2.15s",
+ "4.3s", "8.6s", "17.2s", "34.41s"
+};
+
+static const struct soc_enum alc_dcy =
+ SOC_ENUM_SINGLE(WM8737_ALC3, 4, 11, alc_dcy_text);
+
+static const struct snd_kcontrol_new wm8737_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Mic Boost Volume", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R,
+ 6, 3, 0, micboost_tlv),
+SOC_DOUBLE_R("Mic Boost Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R,
+ 4, 1, 0),
+SOC_DOUBLE("Mic ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R,
+ 3, 1, 0),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8737_LEFT_PGA_VOLUME,
+ WM8737_RIGHT_PGA_VOLUME, 0, 255, 0, pga_tlv),
+SOC_DOUBLE("Capture ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R,
+ 2, 1, 0),
+
+SOC_DOUBLE("INPUT1 DC Bias Switch", WM8737_MISC_BIAS_CONTROL, 0, 1, 1, 0),
+
+SOC_ENUM("Mic PGA Bias", micbias_enum),
+SOC_SINGLE("ADC Low Power Switch", WM8737_ADC_CONTROL, 2, 1, 0),
+SOC_SINGLE("High Pass Filter Switch", WM8737_ADC_CONTROL, 0, 1, 1),
+SOC_DOUBLE("Polarity Invert Switch", WM8737_ADC_CONTROL, 5, 6, 1, 0),
+
+SOC_SINGLE("3D Switch", WM8737_3D_ENHANCE, 0, 1, 0),
+SOC_SINGLE("3D Depth", WM8737_3D_ENHANCE, 1, 15, 0),
+SOC_ENUM("3D Low Cut-off", low_3d),
+SOC_ENUM("3D High Cut-off", low_3d),
+SOC_SINGLE_TLV("3D ADC Volume", WM8737_3D_ENHANCE, 7, 1, 1, adc_tlv),
+
+SOC_SINGLE("Noise Gate Switch", WM8737_NOISE_GATE, 0, 1, 0),
+SOC_SINGLE_TLV("Noise Gate Threshold Volume", WM8737_NOISE_GATE, 2, 7, 0,
+ ng_tlv),
+
+SOC_ENUM("ALC", alc_fn),
+SOC_SINGLE_TLV("ALC Max Gain Volume", WM8737_ALC1, 4, 7, 0, alc_max_tlv),
+SOC_SINGLE_TLV("ALC Target Volume", WM8737_ALC1, 0, 15, 0, alc_target_tlv),
+SOC_ENUM("ALC Hold Time", alc_hold),
+SOC_SINGLE("ALC ZC Switch", WM8737_ALC2, 4, 1, 0),
+SOC_ENUM("ALC Attack Time", alc_atk),
+SOC_ENUM("ALC Decay Time", alc_dcy),
+};
+
+static const char *linsel_text[] = {
+ "LINPUT1", "LINPUT2", "LINPUT3", "LINPUT1 DC",
+};
+
+static const struct soc_enum linsel_enum =
+ SOC_ENUM_SINGLE(WM8737_AUDIO_PATH_L, 7, 4, linsel_text);
+
+static const struct snd_kcontrol_new linsel_mux =
+ SOC_DAPM_ENUM("LINSEL", linsel_enum);
+
+
+static const char *rinsel_text[] = {
+ "RINPUT1", "RINPUT2", "RINPUT3", "RINPUT1 DC",
+};
+
+static const struct soc_enum rinsel_enum =
+ SOC_ENUM_SINGLE(WM8737_AUDIO_PATH_R, 7, 4, rinsel_text);
+
+static const struct snd_kcontrol_new rinsel_mux =
+ SOC_DAPM_ENUM("RINSEL", rinsel_enum);
+
+static const char *bypass_text[] = {
+ "Direct", "Preamp"
+};
+
+static const struct soc_enum lbypass_enum =
+ SOC_ENUM_SINGLE(WM8737_MIC_PREAMP_CONTROL, 2, 2, bypass_text);
+
+static const struct snd_kcontrol_new lbypass_mux =
+ SOC_DAPM_ENUM("Left Bypass", lbypass_enum);
+
+
+static const struct soc_enum rbypass_enum =
+ SOC_ENUM_SINGLE(WM8737_MIC_PREAMP_CONTROL, 3, 2, bypass_text);
+
+static const struct snd_kcontrol_new rbypass_mux =
+ SOC_DAPM_ENUM("Left Bypass", rbypass_enum);
+
+static const struct snd_soc_dapm_widget wm8737_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("LINPUT1"),
+SND_SOC_DAPM_INPUT("LINPUT2"),
+SND_SOC_DAPM_INPUT("LINPUT3"),
+SND_SOC_DAPM_INPUT("RINPUT1"),
+SND_SOC_DAPM_INPUT("RINPUT2"),
+SND_SOC_DAPM_INPUT("RINPUT3"),
+SND_SOC_DAPM_INPUT("LACIN"),
+SND_SOC_DAPM_INPUT("RACIN"),
+
+SND_SOC_DAPM_MUX("LINSEL", SND_SOC_NOPM, 0, 0, &linsel_mux),
+SND_SOC_DAPM_MUX("RINSEL", SND_SOC_NOPM, 0, 0, &rinsel_mux),
+
+SND_SOC_DAPM_MUX("Left Preamp Mux", SND_SOC_NOPM, 0, 0, &lbypass_mux),
+SND_SOC_DAPM_MUX("Right Preamp Mux", SND_SOC_NOPM, 0, 0, &rbypass_mux),
+
+SND_SOC_DAPM_PGA("PGAL", WM8737_POWER_MANAGEMENT, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("PGAR", WM8737_POWER_MANAGEMENT, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_DAC("ADCL", NULL, WM8737_POWER_MANAGEMENT, 3, 0),
+SND_SOC_DAPM_DAC("ADCR", NULL, WM8737_POWER_MANAGEMENT, 2, 0),
+
+SND_SOC_DAPM_AIF_OUT("AIF", "Capture", 0, WM8737_POWER_MANAGEMENT, 6, 0),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ { "LINSEL", "LINPUT1", "LINPUT1" },
+ { "LINSEL", "LINPUT2", "LINPUT2" },
+ { "LINSEL", "LINPUT3", "LINPUT3" },
+ { "LINSEL", "LINPUT1 DC", "LINPUT1" },
+
+ { "RINSEL", "RINPUT1", "RINPUT1" },
+ { "RINSEL", "RINPUT2", "RINPUT2" },
+ { "RINSEL", "RINPUT3", "RINPUT3" },
+ { "RINSEL", "RINPUT1 DC", "RINPUT1" },
+
+ { "Left Preamp Mux", "Preamp", "LINSEL" },
+ { "Left Preamp Mux", "Direct", "LACIN" },
+
+ { "Right Preamp Mux", "Preamp", "RINSEL" },
+ { "Right Preamp Mux", "Direct", "RACIN" },
+
+ { "PGAL", NULL, "Left Preamp Mux" },
+ { "PGAR", NULL, "Right Preamp Mux" },
+
+ { "ADCL", NULL, "PGAL" },
+ { "ADCR", NULL, "PGAR" },
+
+ { "AIF", NULL, "ADCL" },
+ { "AIF", NULL, "ADCR" },
+};
+
+static int wm8737_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8737_dapm_widgets,
+ ARRAY_SIZE(wm8737_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ return 0;
+}
+
+/* codec mclk clock divider coefficients */
+static const struct {
+ u32 mclk;
+ u32 rate;
+ u8 usb;
+ u8 sr;
+} coeff_div[] = {
+ { 12288000, 8000, 0, 0x4 },
+ { 12288000, 12000, 0, 0x8 },
+ { 12288000, 16000, 0, 0xa },
+ { 12288000, 24000, 0, 0x1c },
+ { 12288000, 32000, 0, 0xc },
+ { 12288000, 48000, 0, 0 },
+ { 12288000, 96000, 0, 0xe },
+
+ { 11289600, 8000, 0, 0x14 },
+ { 11289600, 11025, 0, 0x18 },
+ { 11289600, 22050, 0, 0x1a },
+ { 11289600, 44100, 0, 0x10 },
+ { 11289600, 88200, 0, 0x1e },
+
+ { 18432000, 8000, 0, 0x5 },
+ { 18432000, 12000, 0, 0x9 },
+ { 18432000, 16000, 0, 0xb },
+ { 18432000, 24000, 0, 0x1b },
+ { 18432000, 32000, 0, 0xd },
+ { 18432000, 48000, 0, 0x1 },
+ { 18432000, 96000, 0, 0x1f },
+
+ { 16934400, 8000, 0, 0x15 },
+ { 16934400, 11025, 0, 0x19 },
+ { 16934400, 22050, 0, 0x1b },
+ { 16934400, 44100, 0, 0x11 },
+ { 16934400, 88200, 0, 0x1f },
+
+ { 12000000, 8000, 1, 0x4 },
+ { 12000000, 11025, 1, 0x19 },
+ { 12000000, 12000, 1, 0x8 },
+ { 12000000, 16000, 1, 0xa },
+ { 12000000, 22050, 1, 0x1b },
+ { 12000000, 24000, 1, 0x1c },
+ { 12000000, 32000, 1, 0xc },
+ { 12000000, 44100, 1, 0x11 },
+ { 12000000, 48000, 1, 0x0 },
+ { 12000000, 88200, 1, 0x1f },
+ { 12000000, 96000, 1, 0xe },
+};
+
+static int wm8737_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8737_priv *wm8737 = snd_soc_codec_get_drvdata(codec);
+ int i;
+ u16 clocking = 0;
+ u16 af = 0;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate != params_rate(params))
+ continue;
+
+ if (coeff_div[i].mclk == wm8737->mclk)
+ break;
+
+ if (coeff_div[i].mclk == wm8737->mclk * 2) {
+ clocking |= WM8737_CLKDIV2;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(coeff_div)) {
+ dev_err(codec->dev, "%dHz MCLK can't support %dHz\n",
+ wm8737->mclk, params_rate(params));
+ return -EINVAL;
+ }
+
+ clocking |= coeff_div[i].usb | (coeff_div[i].sr << WM8737_SR_SHIFT);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ af |= 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ af |= 0x10;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ af |= 0x18;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8737_AUDIO_FORMAT, WM8737_WL_MASK, af);
+ snd_soc_update_bits(codec, WM8737_CLOCKING,
+ WM8737_USB_MODE | WM8737_CLKDIV2 | WM8737_SR_MASK,
+ clocking);
+
+ return 0;
+}
+
+static int wm8737_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8737_priv *wm8737 = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (freq == coeff_div[i].mclk ||
+ freq == coeff_div[i].mclk * 2) {
+ wm8737->mclk = freq;
+ return 0;
+ }
+ }
+
+ dev_err(codec->dev, "MCLK rate %dHz not supported\n", freq);
+
+ return -EINVAL;
+}
+
+
+static int wm8737_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 af = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ af |= WM8737_MS;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ af |= 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ af |= 0x1;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ af |= 0x3;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ af |= 0x13;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ af |= WM8737_LRP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8737_AUDIO_FORMAT,
+ WM8737_FORMAT_MASK | WM8737_LRP | WM8737_MS, af);
+
+ return 0;
+}
+
+static int wm8737_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8737_priv *wm8737 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID at 2*75k */
+ snd_soc_update_bits(codec, WM8737_MISC_BIAS_CONTROL,
+ WM8737_VMIDSEL_MASK, 0);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8737->supplies),
+ wm8737->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ snd_soc_cache_sync(codec);
+
+ /* Fast VMID ramp at 2*2.5k */
+ snd_soc_update_bits(codec, WM8737_MISC_BIAS_CONTROL,
+ WM8737_VMIDSEL_MASK, 0x4);
+
+ /* Bring VMID up */
+ snd_soc_update_bits(codec, WM8737_POWER_MANAGEMENT,
+ WM8737_VMID_MASK |
+ WM8737_VREF_MASK,
+ WM8737_VMID_MASK |
+ WM8737_VREF_MASK);
+
+ msleep(500);
+ }
+
+ /* VMID at 2*300k */
+ snd_soc_update_bits(codec, WM8737_MISC_BIAS_CONTROL,
+ WM8737_VMIDSEL_MASK, 2);
+
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, WM8737_POWER_MANAGEMENT,
+ WM8737_VMID_MASK | WM8737_VREF_MASK, 0);
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8737->supplies),
+ wm8737->supplies);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8737_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8737_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8737_dai_ops = {
+ .hw_params = wm8737_hw_params,
+ .set_sysclk = wm8737_set_dai_sysclk,
+ .set_fmt = wm8737_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8737_dai = {
+ .name = "wm8737",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2, /* Mono modes not yet supported */
+ .channels_max = 2,
+ .rates = WM8737_RATES,
+ .formats = WM8737_FORMATS,
+ },
+ .ops = &wm8737_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int wm8737_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8737_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8737_resume(struct snd_soc_codec *codec)
+{
+ wm8737_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+#else
+#define wm8737_suspend NULL
+#define wm8737_resume NULL
+#endif
+
+static int wm8737_probe(struct snd_soc_codec *codec)
+{
+ struct wm8737_priv *wm8737 = snd_soc_codec_get_drvdata(codec);
+ int ret, i;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8737->control_type);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8737->supplies); i++)
+ wm8737->supplies[i].supply = wm8737_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8737->supplies),
+ wm8737->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8737->supplies),
+ wm8737->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ ret = wm8737_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ goto err_enable;
+ }
+
+ snd_soc_update_bits(codec, WM8737_LEFT_PGA_VOLUME, WM8737_LVU,
+ WM8737_LVU);
+ snd_soc_update_bits(codec, WM8737_RIGHT_PGA_VOLUME, WM8737_RVU,
+ WM8737_RVU);
+
+ wm8737_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Bias level configuration will have done an extra enable */
+ regulator_bulk_disable(ARRAY_SIZE(wm8737->supplies), wm8737->supplies);
+
+ snd_soc_add_controls(codec, wm8737_snd_controls,
+ ARRAY_SIZE(wm8737_snd_controls));
+ wm8737_add_widgets(codec);
+
+ return 0;
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8737->supplies), wm8737->supplies);
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8737->supplies), wm8737->supplies);
+
+ return ret;
+}
+
+static int wm8737_remove(struct snd_soc_codec *codec)
+{
+ struct wm8737_priv *wm8737 = snd_soc_codec_get_drvdata(codec);
+
+ wm8737_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ regulator_bulk_free(ARRAY_SIZE(wm8737->supplies), wm8737->supplies);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8737 = {
+ .probe = wm8737_probe,
+ .remove = wm8737_remove,
+ .suspend = wm8737_suspend,
+ .resume = wm8737_resume,
+ .set_bias_level = wm8737_set_bias_level,
+
+ .reg_cache_size = WM8737_REGISTER_COUNT - 1, /* Skip reset */
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8737_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8737_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8737_priv *wm8737;
+ int ret;
+
+ wm8737 = kzalloc(sizeof(struct wm8737_priv), GFP_KERNEL);
+ if (wm8737 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8737);
+ wm8737->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8737, &wm8737_dai, 1);
+ if (ret < 0)
+ kfree(wm8737);
+ return ret;
+
+}
+
+static __devexit int wm8737_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8737_i2c_id[] = {
+ { "wm8737", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8737_i2c_id);
+
+static struct i2c_driver wm8737_i2c_driver = {
+ .driver = {
+ .name = "wm8737",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8737_i2c_probe,
+ .remove = __devexit_p(wm8737_i2c_remove),
+ .id_table = wm8737_i2c_id,
+};
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8737_spi_probe(struct spi_device *spi)
+{
+ struct wm8737_priv *wm8737;
+ int ret;
+
+ wm8737 = kzalloc(sizeof(struct wm8737_priv), GFP_KERNEL);
+ if (wm8737 == NULL)
+ return -ENOMEM;
+
+ wm8737->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8737);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8737, &wm8737_dai, 1);
+ if (ret < 0)
+ kfree(wm8737);
+ return ret;
+}
+
+static int __devexit wm8737_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8737_spi_driver = {
+ .driver = {
+ .name = "wm8737",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8737_spi_probe,
+ .remove = __devexit_p(wm8737_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+static int __init wm8737_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8737_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8737 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8737_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8737 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return 0;
+}
+module_init(wm8737_modinit);
+
+static void __exit wm8737_exit(void)
+{
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8737_spi_driver);
+#endif
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8737_i2c_driver);
+#endif
+}
+module_exit(wm8737_exit);
+
+MODULE_DESCRIPTION("ASoC WM8737 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8737.h b/sound/soc/codecs/wm8737.h
new file mode 100644
index 00000000..23d14c8f
--- /dev/null
+++ b/sound/soc/codecs/wm8737.h
@@ -0,0 +1,322 @@
+#ifndef _WM8737_H
+#define _WM8737_H
+
+/*
+ * wm8737.c -- WM8523 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/*
+ * Register values.
+ */
+#define WM8737_LEFT_PGA_VOLUME 0x00
+#define WM8737_RIGHT_PGA_VOLUME 0x01
+#define WM8737_AUDIO_PATH_L 0x02
+#define WM8737_AUDIO_PATH_R 0x03
+#define WM8737_3D_ENHANCE 0x04
+#define WM8737_ADC_CONTROL 0x05
+#define WM8737_POWER_MANAGEMENT 0x06
+#define WM8737_AUDIO_FORMAT 0x07
+#define WM8737_CLOCKING 0x08
+#define WM8737_MIC_PREAMP_CONTROL 0x09
+#define WM8737_MISC_BIAS_CONTROL 0x0A
+#define WM8737_NOISE_GATE 0x0B
+#define WM8737_ALC1 0x0C
+#define WM8737_ALC2 0x0D
+#define WM8737_ALC3 0x0E
+#define WM8737_RESET 0x0F
+
+#define WM8737_REGISTER_COUNT 16
+#define WM8737_MAX_REGISTER 0x0F
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Left PGA volume
+ */
+#define WM8737_LVU 0x0100 /* LVU */
+#define WM8737_LVU_MASK 0x0100 /* LVU */
+#define WM8737_LVU_SHIFT 8 /* LVU */
+#define WM8737_LVU_WIDTH 1 /* LVU */
+#define WM8737_LINVOL_MASK 0x00FF /* LINVOL - [7:0] */
+#define WM8737_LINVOL_SHIFT 0 /* LINVOL - [7:0] */
+#define WM8737_LINVOL_WIDTH 8 /* LINVOL - [7:0] */
+
+/*
+ * R1 (0x01) - Right PGA volume
+ */
+#define WM8737_RVU 0x0100 /* RVU */
+#define WM8737_RVU_MASK 0x0100 /* RVU */
+#define WM8737_RVU_SHIFT 8 /* RVU */
+#define WM8737_RVU_WIDTH 1 /* RVU */
+#define WM8737_RINVOL_MASK 0x00FF /* RINVOL - [7:0] */
+#define WM8737_RINVOL_SHIFT 0 /* RINVOL - [7:0] */
+#define WM8737_RINVOL_WIDTH 8 /* RINVOL - [7:0] */
+
+/*
+ * R2 (0x02) - AUDIO path L
+ */
+#define WM8737_LINSEL_MASK 0x0180 /* LINSEL - [8:7] */
+#define WM8737_LINSEL_SHIFT 7 /* LINSEL - [8:7] */
+#define WM8737_LINSEL_WIDTH 2 /* LINSEL - [8:7] */
+#define WM8737_LMICBOOST_MASK 0x0060 /* LMICBOOST - [6:5] */
+#define WM8737_LMICBOOST_SHIFT 5 /* LMICBOOST - [6:5] */
+#define WM8737_LMICBOOST_WIDTH 2 /* LMICBOOST - [6:5] */
+#define WM8737_LMBE 0x0010 /* LMBE */
+#define WM8737_LMBE_MASK 0x0010 /* LMBE */
+#define WM8737_LMBE_SHIFT 4 /* LMBE */
+#define WM8737_LMBE_WIDTH 1 /* LMBE */
+#define WM8737_LMZC 0x0008 /* LMZC */
+#define WM8737_LMZC_MASK 0x0008 /* LMZC */
+#define WM8737_LMZC_SHIFT 3 /* LMZC */
+#define WM8737_LMZC_WIDTH 1 /* LMZC */
+#define WM8737_LPZC 0x0004 /* LPZC */
+#define WM8737_LPZC_MASK 0x0004 /* LPZC */
+#define WM8737_LPZC_SHIFT 2 /* LPZC */
+#define WM8737_LPZC_WIDTH 1 /* LPZC */
+#define WM8737_LZCTO_MASK 0x0003 /* LZCTO - [1:0] */
+#define WM8737_LZCTO_SHIFT 0 /* LZCTO - [1:0] */
+#define WM8737_LZCTO_WIDTH 2 /* LZCTO - [1:0] */
+
+/*
+ * R3 (0x03) - AUDIO path R
+ */
+#define WM8737_RINSEL_MASK 0x0180 /* RINSEL - [8:7] */
+#define WM8737_RINSEL_SHIFT 7 /* RINSEL - [8:7] */
+#define WM8737_RINSEL_WIDTH 2 /* RINSEL - [8:7] */
+#define WM8737_RMICBOOST_MASK 0x0060 /* RMICBOOST - [6:5] */
+#define WM8737_RMICBOOST_SHIFT 5 /* RMICBOOST - [6:5] */
+#define WM8737_RMICBOOST_WIDTH 2 /* RMICBOOST - [6:5] */
+#define WM8737_RMBE 0x0010 /* RMBE */
+#define WM8737_RMBE_MASK 0x0010 /* RMBE */
+#define WM8737_RMBE_SHIFT 4 /* RMBE */
+#define WM8737_RMBE_WIDTH 1 /* RMBE */
+#define WM8737_RMZC 0x0008 /* RMZC */
+#define WM8737_RMZC_MASK 0x0008 /* RMZC */
+#define WM8737_RMZC_SHIFT 3 /* RMZC */
+#define WM8737_RMZC_WIDTH 1 /* RMZC */
+#define WM8737_RPZC 0x0004 /* RPZC */
+#define WM8737_RPZC_MASK 0x0004 /* RPZC */
+#define WM8737_RPZC_SHIFT 2 /* RPZC */
+#define WM8737_RPZC_WIDTH 1 /* RPZC */
+#define WM8737_RZCTO_MASK 0x0003 /* RZCTO - [1:0] */
+#define WM8737_RZCTO_SHIFT 0 /* RZCTO - [1:0] */
+#define WM8737_RZCTO_WIDTH 2 /* RZCTO - [1:0] */
+
+/*
+ * R4 (0x04) - 3D Enhance
+ */
+#define WM8737_DIV2 0x0080 /* DIV2 */
+#define WM8737_DIV2_MASK 0x0080 /* DIV2 */
+#define WM8737_DIV2_SHIFT 7 /* DIV2 */
+#define WM8737_DIV2_WIDTH 1 /* DIV2 */
+#define WM8737_3DLC 0x0040 /* 3DLC */
+#define WM8737_3DLC_MASK 0x0040 /* 3DLC */
+#define WM8737_3DLC_SHIFT 6 /* 3DLC */
+#define WM8737_3DLC_WIDTH 1 /* 3DLC */
+#define WM8737_3DUC 0x0020 /* 3DUC */
+#define WM8737_3DUC_MASK 0x0020 /* 3DUC */
+#define WM8737_3DUC_SHIFT 5 /* 3DUC */
+#define WM8737_3DUC_WIDTH 1 /* 3DUC */
+#define WM8737_3DDEPTH_MASK 0x001E /* 3DDEPTH - [4:1] */
+#define WM8737_3DDEPTH_SHIFT 1 /* 3DDEPTH - [4:1] */
+#define WM8737_3DDEPTH_WIDTH 4 /* 3DDEPTH - [4:1] */
+#define WM8737_3DE 0x0001 /* 3DE */
+#define WM8737_3DE_MASK 0x0001 /* 3DE */
+#define WM8737_3DE_SHIFT 0 /* 3DE */
+#define WM8737_3DE_WIDTH 1 /* 3DE */
+
+/*
+ * R5 (0x05) - ADC Control
+ */
+#define WM8737_MONOMIX_MASK 0x0180 /* MONOMIX - [8:7] */
+#define WM8737_MONOMIX_SHIFT 7 /* MONOMIX - [8:7] */
+#define WM8737_MONOMIX_WIDTH 2 /* MONOMIX - [8:7] */
+#define WM8737_POLARITY_MASK 0x0060 /* POLARITY - [6:5] */
+#define WM8737_POLARITY_SHIFT 5 /* POLARITY - [6:5] */
+#define WM8737_POLARITY_WIDTH 2 /* POLARITY - [6:5] */
+#define WM8737_HPOR 0x0010 /* HPOR */
+#define WM8737_HPOR_MASK 0x0010 /* HPOR */
+#define WM8737_HPOR_SHIFT 4 /* HPOR */
+#define WM8737_HPOR_WIDTH 1 /* HPOR */
+#define WM8737_LP 0x0004 /* LP */
+#define WM8737_LP_MASK 0x0004 /* LP */
+#define WM8737_LP_SHIFT 2 /* LP */
+#define WM8737_LP_WIDTH 1 /* LP */
+#define WM8737_MONOUT 0x0002 /* MONOUT */
+#define WM8737_MONOUT_MASK 0x0002 /* MONOUT */
+#define WM8737_MONOUT_SHIFT 1 /* MONOUT */
+#define WM8737_MONOUT_WIDTH 1 /* MONOUT */
+#define WM8737_ADCHPD 0x0001 /* ADCHPD */
+#define WM8737_ADCHPD_MASK 0x0001 /* ADCHPD */
+#define WM8737_ADCHPD_SHIFT 0 /* ADCHPD */
+#define WM8737_ADCHPD_WIDTH 1 /* ADCHPD */
+
+/*
+ * R6 (0x06) - Power Management
+ */
+#define WM8737_VMID 0x0100 /* VMID */
+#define WM8737_VMID_MASK 0x0100 /* VMID */
+#define WM8737_VMID_SHIFT 8 /* VMID */
+#define WM8737_VMID_WIDTH 1 /* VMID */
+#define WM8737_VREF 0x0080 /* VREF */
+#define WM8737_VREF_MASK 0x0080 /* VREF */
+#define WM8737_VREF_SHIFT 7 /* VREF */
+#define WM8737_VREF_WIDTH 1 /* VREF */
+#define WM8737_AI 0x0040 /* AI */
+#define WM8737_AI_MASK 0x0040 /* AI */
+#define WM8737_AI_SHIFT 6 /* AI */
+#define WM8737_AI_WIDTH 1 /* AI */
+#define WM8737_PGL 0x0020 /* PGL */
+#define WM8737_PGL_MASK 0x0020 /* PGL */
+#define WM8737_PGL_SHIFT 5 /* PGL */
+#define WM8737_PGL_WIDTH 1 /* PGL */
+#define WM8737_PGR 0x0010 /* PGR */
+#define WM8737_PGR_MASK 0x0010 /* PGR */
+#define WM8737_PGR_SHIFT 4 /* PGR */
+#define WM8737_PGR_WIDTH 1 /* PGR */
+#define WM8737_ADL 0x0008 /* ADL */
+#define WM8737_ADL_MASK 0x0008 /* ADL */
+#define WM8737_ADL_SHIFT 3 /* ADL */
+#define WM8737_ADL_WIDTH 1 /* ADL */
+#define WM8737_ADR 0x0004 /* ADR */
+#define WM8737_ADR_MASK 0x0004 /* ADR */
+#define WM8737_ADR_SHIFT 2 /* ADR */
+#define WM8737_ADR_WIDTH 1 /* ADR */
+#define WM8737_MICBIAS_MASK 0x0003 /* MICBIAS - [1:0] */
+#define WM8737_MICBIAS_SHIFT 0 /* MICBIAS - [1:0] */
+#define WM8737_MICBIAS_WIDTH 2 /* MICBIAS - [1:0] */
+
+/*
+ * R7 (0x07) - Audio Format
+ */
+#define WM8737_SDODIS 0x0080 /* SDODIS */
+#define WM8737_SDODIS_MASK 0x0080 /* SDODIS */
+#define WM8737_SDODIS_SHIFT 7 /* SDODIS */
+#define WM8737_SDODIS_WIDTH 1 /* SDODIS */
+#define WM8737_MS 0x0040 /* MS */
+#define WM8737_MS_MASK 0x0040 /* MS */
+#define WM8737_MS_SHIFT 6 /* MS */
+#define WM8737_MS_WIDTH 1 /* MS */
+#define WM8737_LRP 0x0010 /* LRP */
+#define WM8737_LRP_MASK 0x0010 /* LRP */
+#define WM8737_LRP_SHIFT 4 /* LRP */
+#define WM8737_LRP_WIDTH 1 /* LRP */
+#define WM8737_WL_MASK 0x000C /* WL - [3:2] */
+#define WM8737_WL_SHIFT 2 /* WL - [3:2] */
+#define WM8737_WL_WIDTH 2 /* WL - [3:2] */
+#define WM8737_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */
+#define WM8737_FORMAT_SHIFT 0 /* FORMAT - [1:0] */
+#define WM8737_FORMAT_WIDTH 2 /* FORMAT - [1:0] */
+
+/*
+ * R8 (0x08) - Clocking
+ */
+#define WM8737_AUTODETECT 0x0080 /* AUTODETECT */
+#define WM8737_AUTODETECT_MASK 0x0080 /* AUTODETECT */
+#define WM8737_AUTODETECT_SHIFT 7 /* AUTODETECT */
+#define WM8737_AUTODETECT_WIDTH 1 /* AUTODETECT */
+#define WM8737_CLKDIV2 0x0040 /* CLKDIV2 */
+#define WM8737_CLKDIV2_MASK 0x0040 /* CLKDIV2 */
+#define WM8737_CLKDIV2_SHIFT 6 /* CLKDIV2 */
+#define WM8737_CLKDIV2_WIDTH 1 /* CLKDIV2 */
+#define WM8737_SR_MASK 0x003E /* SR - [5:1] */
+#define WM8737_SR_SHIFT 1 /* SR - [5:1] */
+#define WM8737_SR_WIDTH 5 /* SR - [5:1] */
+#define WM8737_USB_MODE 0x0001 /* USB MODE */
+#define WM8737_USB_MODE_MASK 0x0001 /* USB MODE */
+#define WM8737_USB_MODE_SHIFT 0 /* USB MODE */
+#define WM8737_USB_MODE_WIDTH 1 /* USB MODE */
+
+/*
+ * R9 (0x09) - MIC Preamp Control
+ */
+#define WM8737_RBYPEN 0x0008 /* RBYPEN */
+#define WM8737_RBYPEN_MASK 0x0008 /* RBYPEN */
+#define WM8737_RBYPEN_SHIFT 3 /* RBYPEN */
+#define WM8737_RBYPEN_WIDTH 1 /* RBYPEN */
+#define WM8737_LBYPEN 0x0004 /* LBYPEN */
+#define WM8737_LBYPEN_MASK 0x0004 /* LBYPEN */
+#define WM8737_LBYPEN_SHIFT 2 /* LBYPEN */
+#define WM8737_LBYPEN_WIDTH 1 /* LBYPEN */
+#define WM8737_MBCTRL_MASK 0x0003 /* MBCTRL - [1:0] */
+#define WM8737_MBCTRL_SHIFT 0 /* MBCTRL - [1:0] */
+#define WM8737_MBCTRL_WIDTH 2 /* MBCTRL - [1:0] */
+
+/*
+ * R10 (0x0A) - Misc Bias Control
+ */
+#define WM8737_VMIDSEL_MASK 0x000C /* VMIDSEL - [3:2] */
+#define WM8737_VMIDSEL_SHIFT 2 /* VMIDSEL - [3:2] */
+#define WM8737_VMIDSEL_WIDTH 2 /* VMIDSEL - [3:2] */
+#define WM8737_LINPUT1_DC_BIAS_ENABLE 0x0002 /* LINPUT1 DC BIAS ENABLE */
+#define WM8737_LINPUT1_DC_BIAS_ENABLE_MASK 0x0002 /* LINPUT1 DC BIAS ENABLE */
+#define WM8737_LINPUT1_DC_BIAS_ENABLE_SHIFT 1 /* LINPUT1 DC BIAS ENABLE */
+#define WM8737_LINPUT1_DC_BIAS_ENABLE_WIDTH 1 /* LINPUT1 DC BIAS ENABLE */
+#define WM8737_RINPUT1_DC_BIAS_ENABLE 0x0001 /* RINPUT1 DC BIAS ENABLE */
+#define WM8737_RINPUT1_DC_BIAS_ENABLE_MASK 0x0001 /* RINPUT1 DC BIAS ENABLE */
+#define WM8737_RINPUT1_DC_BIAS_ENABLE_SHIFT 0 /* RINPUT1 DC BIAS ENABLE */
+#define WM8737_RINPUT1_DC_BIAS_ENABLE_WIDTH 1 /* RINPUT1 DC BIAS ENABLE */
+
+/*
+ * R11 (0x0B) - Noise Gate
+ */
+#define WM8737_NGTH_MASK 0x001C /* NGTH - [4:2] */
+#define WM8737_NGTH_SHIFT 2 /* NGTH - [4:2] */
+#define WM8737_NGTH_WIDTH 3 /* NGTH - [4:2] */
+#define WM8737_NGAT 0x0001 /* NGAT */
+#define WM8737_NGAT_MASK 0x0001 /* NGAT */
+#define WM8737_NGAT_SHIFT 0 /* NGAT */
+#define WM8737_NGAT_WIDTH 1 /* NGAT */
+
+/*
+ * R12 (0x0C) - ALC1
+ */
+#define WM8737_ALCSEL_MASK 0x0180 /* ALCSEL - [8:7] */
+#define WM8737_ALCSEL_SHIFT 7 /* ALCSEL - [8:7] */
+#define WM8737_ALCSEL_WIDTH 2 /* ALCSEL - [8:7] */
+#define WM8737_MAX_GAIN_MASK 0x0070 /* MAX GAIN - [6:4] */
+#define WM8737_MAX_GAIN_SHIFT 4 /* MAX GAIN - [6:4] */
+#define WM8737_MAX_GAIN_WIDTH 3 /* MAX GAIN - [6:4] */
+#define WM8737_ALCL_MASK 0x000F /* ALCL - [3:0] */
+#define WM8737_ALCL_SHIFT 0 /* ALCL - [3:0] */
+#define WM8737_ALCL_WIDTH 4 /* ALCL - [3:0] */
+
+/*
+ * R13 (0x0D) - ALC2
+ */
+#define WM8737_ALCZCE 0x0010 /* ALCZCE */
+#define WM8737_ALCZCE_MASK 0x0010 /* ALCZCE */
+#define WM8737_ALCZCE_SHIFT 4 /* ALCZCE */
+#define WM8737_ALCZCE_WIDTH 1 /* ALCZCE */
+#define WM8737_HLD_MASK 0x000F /* HLD - [3:0] */
+#define WM8737_HLD_SHIFT 0 /* HLD - [3:0] */
+#define WM8737_HLD_WIDTH 4 /* HLD - [3:0] */
+
+/*
+ * R14 (0x0E) - ALC3
+ */
+#define WM8737_DCY_MASK 0x00F0 /* DCY - [7:4] */
+#define WM8737_DCY_SHIFT 4 /* DCY - [7:4] */
+#define WM8737_DCY_WIDTH 4 /* DCY - [7:4] */
+#define WM8737_ATK_MASK 0x000F /* ATK - [3:0] */
+#define WM8737_ATK_SHIFT 0 /* ATK - [3:0] */
+#define WM8737_ATK_WIDTH 4 /* ATK - [3:0] */
+
+/*
+ * R15 (0x0F) - Reset
+ */
+#define WM8737_RESET_MASK 0x01FF /* RESET - [8:0] */
+#define WM8737_RESET_SHIFT 0 /* RESET - [8:0] */
+#define WM8737_RESET_WIDTH 9 /* RESET - [8:0] */
+
+#endif
diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c
new file mode 100644
index 00000000..c173aee3
--- /dev/null
+++ b/sound/soc/codecs/wm8741.c
@@ -0,0 +1,562 @@
+/*
+ * wm8741.c -- WM8741 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Ian Lartey <ian@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8741.h"
+
+#define WM8741_NUM_SUPPLIES 2
+static const char *wm8741_supply_names[WM8741_NUM_SUPPLIES] = {
+ "AVDD",
+ "DVDD",
+};
+
+#define WM8741_NUM_RATES 6
+
+/* codec private data */
+struct wm8741_priv {
+ enum snd_soc_control_type control_type;
+ struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES];
+ unsigned int sysclk;
+ struct snd_pcm_hw_constraint_list *sysclk_constraints;
+};
+
+static const u16 wm8741_reg_defaults[WM8741_REGISTER_COUNT] = {
+ 0x0000, /* R0 - DACLLSB Attenuation */
+ 0x0000, /* R1 - DACLMSB Attenuation */
+ 0x0000, /* R2 - DACRLSB Attenuation */
+ 0x0000, /* R3 - DACRMSB Attenuation */
+ 0x0000, /* R4 - Volume Control */
+ 0x000A, /* R5 - Format Control */
+ 0x0000, /* R6 - Filter Control */
+ 0x0000, /* R7 - Mode Control 1 */
+ 0x0002, /* R8 - Mode Control 2 */
+ 0x0000, /* R9 - Reset */
+ 0x0002, /* R32 - ADDITONAL_CONTROL_1 */
+};
+
+
+static int wm8741_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8741_RESET, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv_fine, -12700, 13, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 400, 0);
+
+static const struct snd_kcontrol_new wm8741_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Fine Playback Volume", WM8741_DACLLSB_ATTENUATION,
+ WM8741_DACRLSB_ATTENUATION, 1, 255, 1, dac_tlv_fine),
+SOC_DOUBLE_R_TLV("Playback Volume", WM8741_DACLMSB_ATTENUATION,
+ WM8741_DACRMSB_ATTENUATION, 0, 511, 1, dac_tlv),
+};
+
+static const struct snd_soc_dapm_widget wm8741_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DACL", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_DAC("DACR", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_OUTPUT("VOUTLP"),
+SND_SOC_DAPM_OUTPUT("VOUTLN"),
+SND_SOC_DAPM_OUTPUT("VOUTRP"),
+SND_SOC_DAPM_OUTPUT("VOUTRN"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ { "VOUTLP", NULL, "DACL" },
+ { "VOUTLN", NULL, "DACL" },
+ { "VOUTRP", NULL, "DACR" },
+ { "VOUTRN", NULL, "DACR" },
+};
+
+static int wm8741_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8741_dapm_widgets,
+ ARRAY_SIZE(wm8741_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ return 0;
+}
+
+static struct {
+ int value;
+ int ratio;
+} lrclk_ratios[WM8741_NUM_RATES] = {
+ { 1, 128 },
+ { 2, 192 },
+ { 3, 256 },
+ { 4, 384 },
+ { 5, 512 },
+ { 6, 768 },
+};
+
+static unsigned int rates_11289[] = {
+ 44100, 88235,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_11289 = {
+ .count = ARRAY_SIZE(rates_11289),
+ .list = rates_11289,
+};
+
+static unsigned int rates_12288[] = {
+ 32000, 48000, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_12288 = {
+ .count = ARRAY_SIZE(rates_12288),
+ .list = rates_12288,
+};
+
+static unsigned int rates_16384[] = {
+ 32000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_16384 = {
+ .count = ARRAY_SIZE(rates_16384),
+ .list = rates_16384,
+};
+
+static unsigned int rates_16934[] = {
+ 44100, 88235,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_16934 = {
+ .count = ARRAY_SIZE(rates_16934),
+ .list = rates_16934,
+};
+
+static unsigned int rates_18432[] = {
+ 48000, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_18432 = {
+ .count = ARRAY_SIZE(rates_18432),
+ .list = rates_18432,
+};
+
+static unsigned int rates_22579[] = {
+ 44100, 88235, 1764000
+};
+
+static struct snd_pcm_hw_constraint_list constraints_22579 = {
+ .count = ARRAY_SIZE(rates_22579),
+ .list = rates_22579,
+};
+
+static unsigned int rates_24576[] = {
+ 32000, 48000, 96000, 192000
+};
+
+static struct snd_pcm_hw_constraint_list constraints_24576 = {
+ .count = ARRAY_SIZE(rates_24576),
+ .list = rates_24576,
+};
+
+static unsigned int rates_36864[] = {
+ 48000, 96000, 19200
+};
+
+static struct snd_pcm_hw_constraint_list constraints_36864 = {
+ .count = ARRAY_SIZE(rates_36864),
+ .list = rates_36864,
+};
+
+
+static int wm8741_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+
+ /* The set of sample rates that can be supported depends on the
+ * MCLK supplied to the CODEC - enforce this.
+ */
+ if (!wm8741->sysclk) {
+ dev_err(codec->dev,
+ "No MCLK configured, call set_sysclk() on init\n");
+ return -EINVAL;
+ }
+
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ wm8741->sysclk_constraints);
+
+ return 0;
+}
+
+static int wm8741_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+ u16 iface = snd_soc_read(codec, WM8741_FORMAT_CONTROL) & 0x1FC;
+ int i;
+
+ /* Find a supported LRCLK ratio */
+ for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
+ if (wm8741->sysclk / params_rate(params) ==
+ lrclk_ratios[i].ratio)
+ break;
+ }
+
+ /* Should never happen, should be handled by constraints */
+ if (i == ARRAY_SIZE(lrclk_ratios)) {
+ dev_err(codec->dev, "MCLK/fs ratio %d unsupported\n",
+ wm8741->sysclk / params_rate(params));
+ return -EINVAL;
+ }
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0001;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0002;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x0003;
+ break;
+ default:
+ dev_dbg(codec->dev, "wm8741_hw_params: Unsupported bit size param = %d",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ dev_dbg(codec->dev, "wm8741_hw_params: bit size param = %d",
+ params_format(params));
+
+ snd_soc_write(codec, WM8741_FORMAT_CONTROL, iface);
+ return 0;
+}
+
+static int wm8741_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+
+ dev_dbg(codec->dev, "wm8741_set_dai_sysclk info: freq=%dHz\n", freq);
+
+ switch (freq) {
+ case 11289600:
+ wm8741->sysclk_constraints = &constraints_11289;
+ wm8741->sysclk = freq;
+ return 0;
+
+ case 12288000:
+ wm8741->sysclk_constraints = &constraints_12288;
+ wm8741->sysclk = freq;
+ return 0;
+
+ case 16384000:
+ wm8741->sysclk_constraints = &constraints_16384;
+ wm8741->sysclk = freq;
+ return 0;
+
+ case 16934400:
+ wm8741->sysclk_constraints = &constraints_16934;
+ wm8741->sysclk = freq;
+ return 0;
+
+ case 18432000:
+ wm8741->sysclk_constraints = &constraints_18432;
+ wm8741->sysclk = freq;
+ return 0;
+
+ case 22579200:
+ case 33868800:
+ wm8741->sysclk_constraints = &constraints_22579;
+ wm8741->sysclk = freq;
+ return 0;
+
+ case 24576000:
+ wm8741->sysclk_constraints = &constraints_24576;
+ wm8741->sysclk = freq;
+ return 0;
+
+ case 36864000:
+ wm8741->sysclk_constraints = &constraints_36864;
+ wm8741->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int wm8741_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = snd_soc_read(codec, WM8741_FORMAT_CONTROL) & 0x1C3;
+
+ /* check master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0008;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0004;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x000C;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x001C;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0010;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0020;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0030;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+
+ dev_dbg(codec->dev, "wm8741_set_dai_fmt: Format=%x, Clock Inv=%x\n",
+ fmt & SND_SOC_DAIFMT_FORMAT_MASK,
+ ((fmt & SND_SOC_DAIFMT_INV_MASK)));
+
+ snd_soc_write(codec, WM8741_FORMAT_CONTROL, iface);
+ return 0;
+}
+
+#define WM8741_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \
+ SNDRV_PCM_RATE_192000)
+
+#define WM8741_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8741_dai_ops = {
+ .startup = wm8741_startup,
+ .hw_params = wm8741_hw_params,
+ .set_sysclk = wm8741_set_dai_sysclk,
+ .set_fmt = wm8741_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8741_dai = {
+ .name = "wm8741",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2, /* Mono modes not yet supported */
+ .channels_max = 2,
+ .rates = WM8741_RATES,
+ .formats = WM8741_FORMATS,
+ },
+ .ops = &wm8741_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int wm8741_resume(struct snd_soc_codec *codec)
+{
+ u16 *cache = codec->reg_cache;
+ int i;
+
+ /* RESTORE REG Cache */
+ for (i = 0; i < WM8741_REGISTER_COUNT; i++) {
+ if (cache[i] == wm8741_reg_defaults[i] || WM8741_RESET == i)
+ continue;
+ snd_soc_write(codec, i, cache[i]);
+ }
+ return 0;
+}
+#else
+#define wm8741_suspend NULL
+#define wm8741_resume NULL
+#endif
+
+static int wm8741_probe(struct snd_soc_codec *codec)
+{
+ struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8741->control_type);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8741_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ /* Change some default settings - latch VU */
+ snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION,
+ WM8741_UPDATELL, WM8741_UPDATELL);
+ snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION,
+ WM8741_UPDATELM, WM8741_UPDATELM);
+ snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION,
+ WM8741_UPDATERL, WM8741_UPDATERL);
+ snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION,
+ WM8741_UPDATERM, WM8741_UPDATERM);
+
+ snd_soc_add_controls(codec, wm8741_snd_controls,
+ ARRAY_SIZE(wm8741_snd_controls));
+ wm8741_add_widgets(codec);
+
+ dev_dbg(codec->dev, "Successful registration\n");
+ return ret;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8741 = {
+ .probe = wm8741_probe,
+ .resume = wm8741_resume,
+ .reg_cache_size = ARRAY_SIZE(wm8741_reg_defaults),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8741_reg_defaults,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int wm8741_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8741_priv *wm8741;
+ int ret, i;
+
+ wm8741 = kzalloc(sizeof(struct wm8741_priv), GFP_KERNEL);
+ if (wm8741 == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(wm8741->supplies); i++)
+ wm8741->supplies[i].supply = wm8741_supply_names[i];
+
+ ret = regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8741->supplies),
+ wm8741->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret);
+ goto err;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8741->supplies),
+ wm8741->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ i2c_set_clientdata(i2c, wm8741);
+ wm8741->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8741, &wm8741_dai, 1);
+ if (ret < 0)
+ goto err_enable;
+ return ret;
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);
+
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);
+err:
+ kfree(wm8741);
+ return ret;
+}
+
+static int wm8741_i2c_remove(struct i2c_client *client)
+{
+ struct wm8741_priv *wm8741 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_codec(&client->dev);
+ regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8741_i2c_id[] = {
+ { "wm8741", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8741_i2c_id);
+
+static struct i2c_driver wm8741_i2c_driver = {
+ .driver = {
+ .name = "wm8741-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8741_i2c_probe,
+ .remove = wm8741_i2c_remove,
+ .id_table = wm8741_i2c_id,
+};
+#endif
+
+static int __init wm8741_modinit(void)
+{
+ int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8741_i2c_driver);
+ if (ret != 0)
+ pr_err("Failed to register WM8741 I2C driver: %d\n", ret);
+#endif
+
+ return ret;
+}
+module_init(wm8741_modinit);
+
+static void __exit wm8741_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8741_i2c_driver);
+#endif
+}
+module_exit(wm8741_exit);
+
+MODULE_DESCRIPTION("ASoC WM8741 driver");
+MODULE_AUTHOR("Ian Lartey <ian@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8741.h b/sound/soc/codecs/wm8741.h
new file mode 100644
index 00000000..56c1b1d4
--- /dev/null
+++ b/sound/soc/codecs/wm8741.h
@@ -0,0 +1,211 @@
+/*
+ * wm8741.h -- WM8423 ASoC driver
+ *
+ * Copyright 2010 Wolfson Microelectronics, plc
+ *
+ * Author: Ian Lartey <ian@opensource.wolfsonmicro.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8741_H
+#define _WM8741_H
+
+/*
+ * Register values.
+ */
+#define WM8741_DACLLSB_ATTENUATION 0x00
+#define WM8741_DACLMSB_ATTENUATION 0x01
+#define WM8741_DACRLSB_ATTENUATION 0x02
+#define WM8741_DACRMSB_ATTENUATION 0x03
+#define WM8741_VOLUME_CONTROL 0x04
+#define WM8741_FORMAT_CONTROL 0x05
+#define WM8741_FILTER_CONTROL 0x06
+#define WM8741_MODE_CONTROL_1 0x07
+#define WM8741_MODE_CONTROL_2 0x08
+#define WM8741_RESET 0x09
+#define WM8741_ADDITIONAL_CONTROL_1 0x20
+
+#define WM8741_REGISTER_COUNT 11
+#define WM8741_MAX_REGISTER 0x20
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - DACLLSB_ATTENUATION
+ */
+#define WM8741_UPDATELL 0x0020 /* UPDATELL */
+#define WM8741_UPDATELL_MASK 0x0020 /* UPDATELL */
+#define WM8741_UPDATELL_SHIFT 5 /* UPDATELL */
+#define WM8741_UPDATELL_WIDTH 1 /* UPDATELL */
+#define WM8741_LAT_4_0_MASK 0x001F /* LAT[4:0] - [4:0] */
+#define WM8741_LAT_4_0_SHIFT 0 /* LAT[4:0] - [4:0] */
+#define WM8741_LAT_4_0_WIDTH 5 /* LAT[4:0] - [4:0] */
+
+/*
+ * R1 (0x01) - DACLMSB_ATTENUATION
+ */
+#define WM8741_UPDATELM 0x0020 /* UPDATELM */
+#define WM8741_UPDATELM_MASK 0x0020 /* UPDATELM */
+#define WM8741_UPDATELM_SHIFT 5 /* UPDATELM */
+#define WM8741_UPDATELM_WIDTH 1 /* UPDATELM */
+#define WM8741_LAT_9_5_0_MASK 0x001F /* LAT[9:5] - [4:0] */
+#define WM8741_LAT_9_5_0_SHIFT 0 /* LAT[9:5] - [4:0] */
+#define WM8741_LAT_9_5_0_WIDTH 5 /* LAT[9:5] - [4:0] */
+
+/*
+ * R2 (0x02) - DACRLSB_ATTENUATION
+ */
+#define WM8741_UPDATERL 0x0020 /* UPDATERL */
+#define WM8741_UPDATERL_MASK 0x0020 /* UPDATERL */
+#define WM8741_UPDATERL_SHIFT 5 /* UPDATERL */
+#define WM8741_UPDATERL_WIDTH 1 /* UPDATERL */
+#define WM8741_RAT_4_0_MASK 0x001F /* RAT[4:0] - [4:0] */
+#define WM8741_RAT_4_0_SHIFT 0 /* RAT[4:0] - [4:0] */
+#define WM8741_RAT_4_0_WIDTH 5 /* RAT[4:0] - [4:0] */
+
+/*
+ * R3 (0x03) - DACRMSB_ATTENUATION
+ */
+#define WM8741_UPDATERM 0x0020 /* UPDATERM */
+#define WM8741_UPDATERM_MASK 0x0020 /* UPDATERM */
+#define WM8741_UPDATERM_SHIFT 5 /* UPDATERM */
+#define WM8741_UPDATERM_WIDTH 1 /* UPDATERM */
+#define WM8741_RAT_9_5_0_MASK 0x001F /* RAT[9:5] - [4:0] */
+#define WM8741_RAT_9_5_0_SHIFT 0 /* RAT[9:5] - [4:0] */
+#define WM8741_RAT_9_5_0_WIDTH 5 /* RAT[9:5] - [4:0] */
+
+/*
+ * R4 (0x04) - VOLUME_CONTROL
+ */
+#define WM8741_AMUTE 0x0080 /* AMUTE */
+#define WM8741_AMUTE_MASK 0x0080 /* AMUTE */
+#define WM8741_AMUTE_SHIFT 7 /* AMUTE */
+#define WM8741_AMUTE_WIDTH 1 /* AMUTE */
+#define WM8741_ZFLAG_MASK 0x0060 /* ZFLAG - [6:5] */
+#define WM8741_ZFLAG_SHIFT 5 /* ZFLAG - [6:5] */
+#define WM8741_ZFLAG_WIDTH 2 /* ZFLAG - [6:5] */
+#define WM8741_IZD 0x0010 /* IZD */
+#define WM8741_IZD_MASK 0x0010 /* IZD */
+#define WM8741_IZD_SHIFT 4 /* IZD */
+#define WM8741_IZD_WIDTH 1 /* IZD */
+#define WM8741_SOFT 0x0008 /* SOFT MUTE */
+#define WM8741_SOFT_MASK 0x0008 /* SOFT MUTE */
+#define WM8741_SOFT_SHIFT 3 /* SOFT MUTE */
+#define WM8741_SOFT_WIDTH 1 /* SOFT MUTE */
+#define WM8741_ATC 0x0004 /* ATC */
+#define WM8741_ATC_MASK 0x0004 /* ATC */
+#define WM8741_ATC_SHIFT 2 /* ATC */
+#define WM8741_ATC_WIDTH 1 /* ATC */
+#define WM8741_ATT2DB 0x0002 /* ATT2DB */
+#define WM8741_ATT2DB_MASK 0x0002 /* ATT2DB */
+#define WM8741_ATT2DB_SHIFT 1 /* ATT2DB */
+#define WM8741_ATT2DB_WIDTH 1 /* ATT2DB */
+#define WM8741_VOL_RAMP 0x0001 /* VOL_RAMP */
+#define WM8741_VOL_RAMP_MASK 0x0001 /* VOL_RAMP */
+#define WM8741_VOL_RAMP_SHIFT 0 /* VOL_RAMP */
+#define WM8741_VOL_RAMP_WIDTH 1 /* VOL_RAMP */
+
+/*
+ * R5 (0x05) - FORMAT_CONTROL
+ */
+#define WM8741_PWDN 0x0080 /* PWDN */
+#define WM8741_PWDN_MASK 0x0080 /* PWDN */
+#define WM8741_PWDN_SHIFT 7 /* PWDN */
+#define WM8741_PWDN_WIDTH 1 /* PWDN */
+#define WM8741_REV 0x0040 /* REV */
+#define WM8741_REV_MASK 0x0040 /* REV */
+#define WM8741_REV_SHIFT 6 /* REV */
+#define WM8741_REV_WIDTH 1 /* REV */
+#define WM8741_BCP 0x0020 /* BCP */
+#define WM8741_BCP_MASK 0x0020 /* BCP */
+#define WM8741_BCP_SHIFT 5 /* BCP */
+#define WM8741_BCP_WIDTH 1 /* BCP */
+#define WM8741_LRP 0x0010 /* LRP */
+#define WM8741_LRP_MASK 0x0010 /* LRP */
+#define WM8741_LRP_SHIFT 4 /* LRP */
+#define WM8741_LRP_WIDTH 1 /* LRP */
+#define WM8741_FMT_MASK 0x000C /* FMT - [3:2] */
+#define WM8741_FMT_SHIFT 2 /* FMT - [3:2] */
+#define WM8741_FMT_WIDTH 2 /* FMT - [3:2] */
+#define WM8741_IWL_MASK 0x0003 /* IWL - [1:0] */
+#define WM8741_IWL_SHIFT 0 /* IWL - [1:0] */
+#define WM8741_IWL_WIDTH 2 /* IWL - [1:0] */
+
+/*
+ * R6 (0x06) - FILTER_CONTROL
+ */
+#define WM8741_ZFLAG_HI 0x0080 /* ZFLAG_HI */
+#define WM8741_ZFLAG_HI_MASK 0x0080 /* ZFLAG_HI */
+#define WM8741_ZFLAG_HI_SHIFT 7 /* ZFLAG_HI */
+#define WM8741_ZFLAG_HI_WIDTH 1 /* ZFLAG_HI */
+#define WM8741_DEEMPH_MASK 0x0060 /* DEEMPH - [6:5] */
+#define WM8741_DEEMPH_SHIFT 5 /* DEEMPH - [6:5] */
+#define WM8741_DEEMPH_WIDTH 2 /* DEEMPH - [6:5] */
+#define WM8741_DSDFILT_MASK 0x0018 /* DSDFILT - [4:3] */
+#define WM8741_DSDFILT_SHIFT 3 /* DSDFILT - [4:3] */
+#define WM8741_DSDFILT_WIDTH 2 /* DSDFILT - [4:3] */
+#define WM8741_FIRSEL_MASK 0x0007 /* FIRSEL - [2:0] */
+#define WM8741_FIRSEL_SHIFT 0 /* FIRSEL - [2:0] */
+#define WM8741_FIRSEL_WIDTH 3 /* FIRSEL - [2:0] */
+
+/*
+ * R7 (0x07) - MODE_CONTROL_1
+ */
+#define WM8741_MODE8X 0x0080 /* MODE8X */
+#define WM8741_MODE8X_MASK 0x0080 /* MODE8X */
+#define WM8741_MODE8X_SHIFT 7 /* MODE8X */
+#define WM8741_MODE8X_WIDTH 1 /* MODE8X */
+#define WM8741_OSR_MASK 0x0060 /* OSR - [6:5] */
+#define WM8741_OSR_SHIFT 5 /* OSR - [6:5] */
+#define WM8741_OSR_WIDTH 2 /* OSR - [6:5] */
+#define WM8741_SR_MASK 0x001C /* SR - [4:2] */
+#define WM8741_SR_SHIFT 2 /* SR - [4:2] */
+#define WM8741_SR_WIDTH 3 /* SR - [4:2] */
+#define WM8741_MODESEL_MASK 0x0003 /* MODESEL - [1:0] */
+#define WM8741_MODESEL_SHIFT 0 /* MODESEL - [1:0] */
+#define WM8741_MODESEL_WIDTH 2 /* MODESEL - [1:0] */
+
+/*
+ * R8 (0x08) - MODE_CONTROL_2
+ */
+#define WM8741_DSD_GAIN 0x0040 /* DSD_GAIN */
+#define WM8741_DSD_GAIN_MASK 0x0040 /* DSD_GAIN */
+#define WM8741_DSD_GAIN_SHIFT 6 /* DSD_GAIN */
+#define WM8741_DSD_GAIN_WIDTH 1 /* DSD_GAIN */
+#define WM8741_SDOUT 0x0020 /* SDOUT */
+#define WM8741_SDOUT_MASK 0x0020 /* SDOUT */
+#define WM8741_SDOUT_SHIFT 5 /* SDOUT */
+#define WM8741_SDOUT_WIDTH 1 /* SDOUT */
+#define WM8741_DOUT 0x0010 /* DOUT */
+#define WM8741_DOUT_MASK 0x0010 /* DOUT */
+#define WM8741_DOUT_SHIFT 4 /* DOUT */
+#define WM8741_DOUT_WIDTH 1 /* DOUT */
+#define WM8741_DIFF_MASK 0x000C /* DIFF - [3:2] */
+#define WM8741_DIFF_SHIFT 2 /* DIFF - [3:2] */
+#define WM8741_DIFF_WIDTH 2 /* DIFF - [3:2] */
+#define WM8741_DITHER_MASK 0x0003 /* DITHER - [1:0] */
+#define WM8741_DITHER_SHIFT 0 /* DITHER - [1:0] */
+#define WM8741_DITHER_WIDTH 2 /* DITHER - [1:0] */
+
+/*
+ * R32 (0x20) - ADDITONAL_CONTROL_1
+ */
+#define WM8741_DSD_LEVEL 0x0002 /* DSD_LEVEL */
+#define WM8741_DSD_LEVEL_MASK 0x0002 /* DSD_LEVEL */
+#define WM8741_DSD_LEVEL_SHIFT 1 /* DSD_LEVEL */
+#define WM8741_DSD_LEVEL_WIDTH 1 /* DSD_LEVEL */
+#define WM8741_DSD_NO_NOTCH 0x0001 /* DSD_NO_NOTCH */
+#define WM8741_DSD_NO_NOTCH_MASK 0x0001 /* DSD_NO_NOTCH */
+#define WM8741_DSD_NO_NOTCH_SHIFT 0 /* DSD_NO_NOTCH */
+#define WM8741_DSD_NO_NOTCH_WIDTH 1 /* DSD_NO_NOTCH */
+
+#define WM8741_SYSCLK 0
+
+#endif
diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c
new file mode 100644
index 00000000..38f38fdd
--- /dev/null
+++ b/sound/soc/codecs/wm8750.c
@@ -0,0 +1,871 @@
+/*
+ * wm8750.c -- WM8750 ALSA SoC audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on WM8753.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "wm8750.h"
+
+/*
+ * wm8750 register cache
+ * We can't read the WM8750 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8750_reg[] = {
+ 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */
+ 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */
+ 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */
+ 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */
+ 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */
+ 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */
+ 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */
+ 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */
+ 0x0079, 0x0079, 0x0079, /* 40 */
+};
+
+/* codec private data */
+struct wm8750_priv {
+ unsigned int sysclk;
+ enum snd_soc_control_type control_type;
+};
+
+#define wm8750_reset(c) snd_soc_write(c, WM8750_RESET, 0)
+
+/*
+ * WM8750 Controls
+ */
+static const char *wm8750_bass[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm8750_bass_filter[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" };
+static const char *wm8750_treble[] = {"8kHz", "4kHz"};
+static const char *wm8750_3d_lc[] = {"200Hz", "500Hz"};
+static const char *wm8750_3d_uc[] = {"2.2kHz", "1.5kHz"};
+static const char *wm8750_3d_func[] = {"Capture", "Playback"};
+static const char *wm8750_alc_func[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8750_ng_type[] = {"Constant PGA Gain",
+ "Mute ADC Output"};
+static const char *wm8750_line_mux[] = {"Line 1", "Line 2", "Line 3", "PGA",
+ "Differential"};
+static const char *wm8750_pga_sel[] = {"Line 1", "Line 2", "Line 3",
+ "Differential"};
+static const char *wm8750_out3[] = {"VREF", "ROUT1 + Vol", "MonoOut",
+ "ROUT1"};
+static const char *wm8750_diff_sel[] = {"Line 1", "Line 2"};
+static const char *wm8750_adcpol[] = {"Normal", "L Invert", "R Invert",
+ "L + R Invert"};
+static const char *wm8750_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const char *wm8750_mono_mux[] = {"Stereo", "Mono (Left)",
+ "Mono (Right)", "Digital Mono"};
+
+static const struct soc_enum wm8750_enum[] = {
+SOC_ENUM_SINGLE(WM8750_BASS, 7, 2, wm8750_bass),
+SOC_ENUM_SINGLE(WM8750_BASS, 6, 2, wm8750_bass_filter),
+SOC_ENUM_SINGLE(WM8750_TREBLE, 6, 2, wm8750_treble),
+SOC_ENUM_SINGLE(WM8750_3D, 5, 2, wm8750_3d_lc),
+SOC_ENUM_SINGLE(WM8750_3D, 6, 2, wm8750_3d_uc),
+SOC_ENUM_SINGLE(WM8750_3D, 7, 2, wm8750_3d_func),
+SOC_ENUM_SINGLE(WM8750_ALC1, 7, 4, wm8750_alc_func),
+SOC_ENUM_SINGLE(WM8750_NGATE, 1, 2, wm8750_ng_type),
+SOC_ENUM_SINGLE(WM8750_LOUTM1, 0, 5, wm8750_line_mux),
+SOC_ENUM_SINGLE(WM8750_ROUTM1, 0, 5, wm8750_line_mux),
+SOC_ENUM_SINGLE(WM8750_LADCIN, 6, 4, wm8750_pga_sel), /* 10 */
+SOC_ENUM_SINGLE(WM8750_RADCIN, 6, 4, wm8750_pga_sel),
+SOC_ENUM_SINGLE(WM8750_ADCTL2, 7, 4, wm8750_out3),
+SOC_ENUM_SINGLE(WM8750_ADCIN, 8, 2, wm8750_diff_sel),
+SOC_ENUM_SINGLE(WM8750_ADCDAC, 5, 4, wm8750_adcpol),
+SOC_ENUM_SINGLE(WM8750_ADCDAC, 1, 4, wm8750_deemph),
+SOC_ENUM_SINGLE(WM8750_ADCIN, 6, 4, wm8750_mono_mux), /* 16 */
+
+};
+
+static const struct snd_kcontrol_new wm8750_snd_controls[] = {
+
+SOC_DOUBLE_R("Capture Volume", WM8750_LINVOL, WM8750_RINVOL, 0, 63, 0),
+SOC_DOUBLE_R("Capture ZC Switch", WM8750_LINVOL, WM8750_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8750_LINVOL, WM8750_RINVOL, 7, 1, 1),
+
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8750_LOUT1V,
+ WM8750_ROUT1V, 7, 1, 0),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8750_LOUT2V,
+ WM8750_ROUT2V, 7, 1, 0),
+
+SOC_ENUM("Playback De-emphasis", wm8750_enum[15]),
+
+SOC_ENUM("Capture Polarity", wm8750_enum[14]),
+SOC_SINGLE("Playback 6dB Attenuate", WM8750_ADCDAC, 7, 1, 0),
+SOC_SINGLE("Capture 6dB Attenuate", WM8750_ADCDAC, 8, 1, 0),
+
+SOC_DOUBLE_R("PCM Volume", WM8750_LDAC, WM8750_RDAC, 0, 255, 0),
+
+SOC_ENUM("Bass Boost", wm8750_enum[0]),
+SOC_ENUM("Bass Filter", wm8750_enum[1]),
+SOC_SINGLE("Bass Volume", WM8750_BASS, 0, 15, 1),
+
+SOC_SINGLE("Treble Volume", WM8750_TREBLE, 0, 15, 1),
+SOC_ENUM("Treble Cut-off", wm8750_enum[2]),
+
+SOC_SINGLE("3D Switch", WM8750_3D, 0, 1, 0),
+SOC_SINGLE("3D Volume", WM8750_3D, 1, 15, 0),
+SOC_ENUM("3D Lower Cut-off", wm8750_enum[3]),
+SOC_ENUM("3D Upper Cut-off", wm8750_enum[4]),
+SOC_ENUM("3D Mode", wm8750_enum[5]),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8750_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8750_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", wm8750_enum[6]),
+SOC_SINGLE("ALC Capture ZC Switch", WM8750_ALC2, 7, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8750_ALC2, 0, 15, 0),
+SOC_SINGLE("ALC Capture Decay Time", WM8750_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack Time", WM8750_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8750_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", wm8750_enum[4]),
+SOC_SINGLE("ALC Capture NG Switch", WM8750_NGATE, 0, 1, 0),
+
+SOC_SINGLE("Left ADC Capture Volume", WM8750_LADC, 0, 255, 0),
+SOC_SINGLE("Right ADC Capture Volume", WM8750_RADC, 0, 255, 0),
+
+SOC_SINGLE("ZC Timeout Switch", WM8750_ADCTL1, 0, 1, 0),
+SOC_SINGLE("Playback Invert Switch", WM8750_ADCTL1, 1, 1, 0),
+
+SOC_SINGLE("Right Speaker Playback Invert Switch", WM8750_ADCTL2, 4, 1, 0),
+
+/* Unimplemented */
+/* ADCDAC Bit 0 - ADCHPD */
+/* ADCDAC Bit 4 - HPOR */
+/* ADCTL1 Bit 2,3 - DATSEL */
+/* ADCTL1 Bit 4,5 - DMONOMIX */
+/* ADCTL1 Bit 6,7 - VSEL */
+/* ADCTL2 Bit 2 - LRCM */
+/* ADCTL2 Bit 3 - TRI */
+/* ADCTL3 Bit 5 - HPFLREN */
+/* ADCTL3 Bit 6 - VROI */
+/* ADCTL3 Bit 7,8 - ADCLRM */
+/* ADCIN Bit 4 - LDCM */
+/* ADCIN Bit 5 - RDCM */
+
+SOC_DOUBLE_R("Mic Boost", WM8750_LADCIN, WM8750_RADCIN, 4, 3, 0),
+
+SOC_DOUBLE_R("Bypass Left Playback Volume", WM8750_LOUTM1,
+ WM8750_LOUTM2, 4, 7, 1),
+SOC_DOUBLE_R("Bypass Right Playback Volume", WM8750_ROUTM1,
+ WM8750_ROUTM2, 4, 7, 1),
+SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8750_MOUTM1,
+ WM8750_MOUTM2, 4, 7, 1),
+
+SOC_SINGLE("Mono Playback ZC Switch", WM8750_MOUTV, 7, 1, 0),
+
+SOC_DOUBLE_R("Headphone Playback Volume", WM8750_LOUT1V, WM8750_ROUT1V,
+ 0, 127, 0),
+SOC_DOUBLE_R("Speaker Playback Volume", WM8750_LOUT2V, WM8750_ROUT2V,
+ 0, 127, 0),
+
+SOC_SINGLE("Mono Playback Volume", WM8750_MOUTV, 0, 127, 0),
+
+};
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8750_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8750_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_LOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8750_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8750_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8750_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_ROUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8750_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_ROUTM2, 7, 1, 0),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm8750_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8750_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_MOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8750_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_MOUTM2, 7, 1, 0),
+};
+
+/* Left Line Mux */
+static const struct snd_kcontrol_new wm8750_left_line_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[8]);
+
+/* Right Line Mux */
+static const struct snd_kcontrol_new wm8750_right_line_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[9]);
+
+/* Left PGA Mux */
+static const struct snd_kcontrol_new wm8750_left_pga_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[10]);
+
+/* Right PGA Mux */
+static const struct snd_kcontrol_new wm8750_right_pga_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[11]);
+
+/* Out 3 Mux */
+static const struct snd_kcontrol_new wm8750_out3_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[12]);
+
+/* Differential Mux */
+static const struct snd_kcontrol_new wm8750_diffmux_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[13]);
+
+/* Mono ADC Mux */
+static const struct snd_kcontrol_new wm8750_monomux_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[16]);
+
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8750_left_mixer_controls[0],
+ ARRAY_SIZE(wm8750_left_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8750_right_mixer_controls[0],
+ ARRAY_SIZE(wm8750_right_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono Mixer", WM8750_PWR2, 2, 0,
+ &wm8750_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8750_mono_mixer_controls)),
+
+ SND_SOC_DAPM_PGA("Right Out 2", WM8750_PWR2, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 2", WM8750_PWR2, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Out 1", WM8750_PWR2, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 1", WM8750_PWR2, 6, 0, NULL, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8750_PWR2, 7, 0),
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8750_PWR2, 8, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8750_PWR1, 1, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8750_PWR1, 2, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8750_PWR1, 3, 0),
+
+ SND_SOC_DAPM_MUX("Left PGA Mux", WM8750_PWR1, 5, 0,
+ &wm8750_left_pga_controls),
+ SND_SOC_DAPM_MUX("Right PGA Mux", WM8750_PWR1, 4, 0,
+ &wm8750_right_pga_controls),
+ SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_left_line_controls),
+ SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_right_line_controls),
+
+ SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8750_out3_controls),
+ SND_SOC_DAPM_PGA("Out 3", WM8750_PWR2, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mono Out 1", WM8750_PWR2, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_diffmux_controls),
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_monomux_controls),
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_monomux_controls),
+
+ SND_SOC_DAPM_OUTPUT("LOUT1"),
+ SND_SOC_DAPM_OUTPUT("ROUT1"),
+ SND_SOC_DAPM_OUTPUT("LOUT2"),
+ SND_SOC_DAPM_OUTPUT("ROUT2"),
+ SND_SOC_DAPM_OUTPUT("MONO1"),
+ SND_SOC_DAPM_OUTPUT("OUT3"),
+ SND_SOC_DAPM_OUTPUT("VREF"),
+
+ SND_SOC_DAPM_INPUT("LINPUT1"),
+ SND_SOC_DAPM_INPUT("LINPUT2"),
+ SND_SOC_DAPM_INPUT("LINPUT3"),
+ SND_SOC_DAPM_INPUT("RINPUT1"),
+ SND_SOC_DAPM_INPUT("RINPUT2"),
+ SND_SOC_DAPM_INPUT("RINPUT3"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* left mixer */
+ {"Left Mixer", "Playback Switch", "Left DAC"},
+ {"Left Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Left Mixer", "Right Playback Switch", "Right DAC"},
+ {"Left Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* right mixer */
+ {"Right Mixer", "Left Playback Switch", "Left DAC"},
+ {"Right Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Right Mixer", "Playback Switch", "Right DAC"},
+ {"Right Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* left out 1 */
+ {"Left Out 1", NULL, "Left Mixer"},
+ {"LOUT1", NULL, "Left Out 1"},
+
+ /* left out 2 */
+ {"Left Out 2", NULL, "Left Mixer"},
+ {"LOUT2", NULL, "Left Out 2"},
+
+ /* right out 1 */
+ {"Right Out 1", NULL, "Right Mixer"},
+ {"ROUT1", NULL, "Right Out 1"},
+
+ /* right out 2 */
+ {"Right Out 2", NULL, "Right Mixer"},
+ {"ROUT2", NULL, "Right Out 2"},
+
+ /* mono mixer */
+ {"Mono Mixer", "Left Playback Switch", "Left DAC"},
+ {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Mono Mixer", "Right Playback Switch", "Right DAC"},
+ {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* mono out */
+ {"Mono Out 1", NULL, "Mono Mixer"},
+ {"MONO1", NULL, "Mono Out 1"},
+
+ /* out 3 */
+ {"Out3 Mux", "VREF", "VREF"},
+ {"Out3 Mux", "ROUT1 + Vol", "ROUT1"},
+ {"Out3 Mux", "ROUT1", "Right Mixer"},
+ {"Out3 Mux", "MonoOut", "MONO1"},
+ {"Out 3", NULL, "Out3 Mux"},
+ {"OUT3", NULL, "Out 3"},
+
+ /* Left Line Mux */
+ {"Left Line Mux", "Line 1", "LINPUT1"},
+ {"Left Line Mux", "Line 2", "LINPUT2"},
+ {"Left Line Mux", "Line 3", "LINPUT3"},
+ {"Left Line Mux", "PGA", "Left PGA Mux"},
+ {"Left Line Mux", "Differential", "Differential Mux"},
+
+ /* Right Line Mux */
+ {"Right Line Mux", "Line 1", "RINPUT1"},
+ {"Right Line Mux", "Line 2", "RINPUT2"},
+ {"Right Line Mux", "Line 3", "RINPUT3"},
+ {"Right Line Mux", "PGA", "Right PGA Mux"},
+ {"Right Line Mux", "Differential", "Differential Mux"},
+
+ /* Left PGA Mux */
+ {"Left PGA Mux", "Line 1", "LINPUT1"},
+ {"Left PGA Mux", "Line 2", "LINPUT2"},
+ {"Left PGA Mux", "Line 3", "LINPUT3"},
+ {"Left PGA Mux", "Differential", "Differential Mux"},
+
+ /* Right PGA Mux */
+ {"Right PGA Mux", "Line 1", "RINPUT1"},
+ {"Right PGA Mux", "Line 2", "RINPUT2"},
+ {"Right PGA Mux", "Line 3", "RINPUT3"},
+ {"Right PGA Mux", "Differential", "Differential Mux"},
+
+ /* Differential Mux */
+ {"Differential Mux", "Line 1", "LINPUT1"},
+ {"Differential Mux", "Line 1", "RINPUT1"},
+ {"Differential Mux", "Line 2", "LINPUT2"},
+ {"Differential Mux", "Line 2", "RINPUT2"},
+
+ /* Left ADC Mux */
+ {"Left ADC Mux", "Stereo", "Left PGA Mux"},
+ {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"},
+ {"Left ADC Mux", "Digital Mono", "Left PGA Mux"},
+
+ /* Right ADC Mux */
+ {"Right ADC Mux", "Stereo", "Right PGA Mux"},
+ {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"},
+ {"Right ADC Mux", "Digital Mono", "Right PGA Mux"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Left ADC Mux"},
+ {"Right ADC", NULL, "Right ADC Mux"},
+};
+
+static int wm8750_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8750_dapm_widgets,
+ ARRAY_SIZE(wm8750_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:5;
+ u8 usb:1;
+};
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 8k */
+ {12288000, 8000, 1536, 0x6, 0x0},
+ {11289600, 8000, 1408, 0x16, 0x0},
+ {18432000, 8000, 2304, 0x7, 0x0},
+ {16934400, 8000, 2112, 0x17, 0x0},
+ {12000000, 8000, 1500, 0x6, 0x1},
+
+ /* 11.025k */
+ {11289600, 11025, 1024, 0x18, 0x0},
+ {16934400, 11025, 1536, 0x19, 0x0},
+ {12000000, 11025, 1088, 0x19, 0x1},
+
+ /* 16k */
+ {12288000, 16000, 768, 0xa, 0x0},
+ {18432000, 16000, 1152, 0xb, 0x0},
+ {12000000, 16000, 750, 0xa, 0x1},
+
+ /* 22.05k */
+ {11289600, 22050, 512, 0x1a, 0x0},
+ {16934400, 22050, 768, 0x1b, 0x0},
+ {12000000, 22050, 544, 0x1b, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0xc, 0x0},
+ {18432000, 32000, 576, 0xd, 0x0},
+ {12000000, 32000, 375, 0xa, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x10, 0x0},
+ {16934400, 44100, 384, 0x11, 0x0},
+ {12000000, 44100, 272, 0x11, 0x1},
+
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0},
+ {18432000, 48000, 384, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0x1e, 0x0},
+ {16934400, 88200, 192, 0x1f, 0x0},
+ {12000000, 88200, 136, 0x1f, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0xe, 0x0},
+ {18432000, 96000, 192, 0xf, 0x0},
+ {12000000, 96000, 125, 0xe, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+
+ printk(KERN_ERR "wm8750: could not get coeff for mclk %d @ rate %d\n",
+ mclk, rate);
+ return -EINVAL;
+}
+
+static int wm8750_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8750->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface = 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8750_IFACE, iface);
+ return 0;
+}
+
+static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec);
+ u16 iface = snd_soc_read(codec, WM8750_IFACE) & 0x1f3;
+ u16 srate = snd_soc_read(codec, WM8750_SRATE) & 0x1c0;
+ int coeff = get_coeff(wm8750->sysclk, params_rate(params));
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+
+ /* set iface & srate */
+ snd_soc_write(codec, WM8750_IFACE, iface);
+ if (coeff >= 0)
+ snd_soc_write(codec, WM8750_SRATE, srate |
+ (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+ return 0;
+}
+
+static int wm8750_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8750_ADCDAC) & 0xfff7;
+
+ if (mute)
+ snd_soc_write(codec, WM8750_ADCDAC, mute_reg | 0x8);
+ else
+ snd_soc_write(codec, WM8750_ADCDAC, mute_reg);
+ return 0;
+}
+
+static int wm8750_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 pwr_reg = snd_soc_read(codec, WM8750_PWR1) & 0xfe3e;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* set vmid to 50k and unmute dac */
+ snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x00c0);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Set VMID to 5k */
+ snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x01c1);
+
+ /* ...and ramp */
+ msleep(1000);
+ }
+
+ /* mute dac and set vmid to 500k, enable VREF */
+ snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x0141);
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, WM8750_PWR1, 0x0001);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8750_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8750_dai_ops = {
+ .hw_params = wm8750_pcm_hw_params,
+ .digital_mute = wm8750_mute,
+ .set_fmt = wm8750_set_dai_fmt,
+ .set_sysclk = wm8750_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8750_dai = {
+ .name = "wm8750-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8750_RATES,
+ .formats = WM8750_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8750_RATES,
+ .formats = WM8750_FORMATS,},
+ .ops = &wm8750_dai_ops,
+};
+
+static int wm8750_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8750_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8750_reg); i++) {
+ if (i == WM8750_RESET)
+ continue;
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int wm8750_probe(struct snd_soc_codec *codec)
+{
+ struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec);
+ int reg, ret;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8750->control_type);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8750: failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8750_reset(codec);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8750: failed to reset: %d\n", ret);
+ return ret;
+ }
+
+ /* charge output caps */
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* set the update bits */
+ reg = snd_soc_read(codec, WM8750_LDAC);
+ snd_soc_write(codec, WM8750_LDAC, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8750_RDAC);
+ snd_soc_write(codec, WM8750_RDAC, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8750_LOUT1V);
+ snd_soc_write(codec, WM8750_LOUT1V, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8750_ROUT1V);
+ snd_soc_write(codec, WM8750_ROUT1V, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8750_LOUT2V);
+ snd_soc_write(codec, WM8750_LOUT2V, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8750_ROUT2V);
+ snd_soc_write(codec, WM8750_ROUT2V, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8750_LINVOL);
+ snd_soc_write(codec, WM8750_LINVOL, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8750_RINVOL);
+ snd_soc_write(codec, WM8750_RINVOL, reg | 0x0100);
+
+ snd_soc_add_controls(codec, wm8750_snd_controls,
+ ARRAY_SIZE(wm8750_snd_controls));
+ wm8750_add_widgets(codec);
+ return ret;
+}
+
+static int wm8750_remove(struct snd_soc_codec *codec)
+{
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8750 = {
+ .probe = wm8750_probe,
+ .remove = wm8750_remove,
+ .suspend = wm8750_suspend,
+ .resume = wm8750_resume,
+ .set_bias_level = wm8750_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8750_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8750_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8750_spi_probe(struct spi_device *spi)
+{
+ struct wm8750_priv *wm8750;
+ int ret;
+
+ wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL);
+ if (wm8750 == NULL)
+ return -ENOMEM;
+
+ wm8750->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8750);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8750, &wm8750_dai, 1);
+ if (ret < 0)
+ kfree(wm8750);
+ return ret;
+}
+
+static int __devexit wm8750_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8750_spi_driver = {
+ .driver = {
+ .name = "wm8750-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8750_spi_probe,
+ .remove = __devexit_p(wm8750_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8750_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8750_priv *wm8750;
+ int ret;
+
+ wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL);
+ if (wm8750 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8750);
+ wm8750->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8750, &wm8750_dai, 1);
+ if (ret < 0)
+ kfree(wm8750);
+ return ret;
+}
+
+static __devexit int wm8750_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8750_i2c_id[] = {
+ { "wm8750", 0 },
+ { "wm8987", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id);
+
+static struct i2c_driver wm8750_i2c_driver = {
+ .driver = {
+ .name = "wm8750-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8750_i2c_probe,
+ .remove = __devexit_p(wm8750_i2c_remove),
+ .id_table = wm8750_i2c_id,
+};
+#endif
+
+static int __init wm8750_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8750_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8750 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8750_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8750 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8750_modinit);
+
+static void __exit wm8750_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8750_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8750_spi_driver);
+#endif
+}
+module_exit(wm8750_exit);
+
+MODULE_DESCRIPTION("ASoC WM8750 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8750.h b/sound/soc/codecs/wm8750.h
new file mode 100644
index 00000000..121427c0
--- /dev/null
+++ b/sound/soc/codecs/wm8750.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on WM8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _WM8750_H
+#define _WM8750_H
+
+/* WM8750 register space */
+
+#define WM8750_LINVOL 0x00
+#define WM8750_RINVOL 0x01
+#define WM8750_LOUT1V 0x02
+#define WM8750_ROUT1V 0x03
+#define WM8750_ADCDAC 0x05
+#define WM8750_IFACE 0x07
+#define WM8750_SRATE 0x08
+#define WM8750_LDAC 0x0a
+#define WM8750_RDAC 0x0b
+#define WM8750_BASS 0x0c
+#define WM8750_TREBLE 0x0d
+#define WM8750_RESET 0x0f
+#define WM8750_3D 0x10
+#define WM8750_ALC1 0x11
+#define WM8750_ALC2 0x12
+#define WM8750_ALC3 0x13
+#define WM8750_NGATE 0x14
+#define WM8750_LADC 0x15
+#define WM8750_RADC 0x16
+#define WM8750_ADCTL1 0x17
+#define WM8750_ADCTL2 0x18
+#define WM8750_PWR1 0x19
+#define WM8750_PWR2 0x1a
+#define WM8750_ADCTL3 0x1b
+#define WM8750_ADCIN 0x1f
+#define WM8750_LADCIN 0x20
+#define WM8750_RADCIN 0x21
+#define WM8750_LOUTM1 0x22
+#define WM8750_LOUTM2 0x23
+#define WM8750_ROUTM1 0x24
+#define WM8750_ROUTM2 0x25
+#define WM8750_MOUTM1 0x26
+#define WM8750_MOUTM2 0x27
+#define WM8750_LOUT2V 0x28
+#define WM8750_ROUT2V 0x29
+#define WM8750_MOUTV 0x2a
+
+#define WM8750_CACHE_REGNUM 0x2a
+
+#define WM8750_SYSCLK 0
+
+#endif
diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c
new file mode 100644
index 00000000..66d18a3e
--- /dev/null
+++ b/sound/soc/codecs/wm8753.c
@@ -0,0 +1,1612 @@
+/*
+ * wm8753.c -- WM8753 ALSA Soc Audio driver
+ *
+ * Copyright 2003 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ * Notes:
+ * The WM8753 is a low power, high quality stereo codec with integrated PCM
+ * codec designed for portable digital telephony applications.
+ *
+ * Dual DAI:-
+ *
+ * This driver support 2 DAI PCM's. This makes the default PCM available for
+ * HiFi audio (e.g. MP3, ogg) playback/capture and the other PCM available for
+ * voice.
+ *
+ * Please note that the voice PCM can be connected directly to a Bluetooth
+ * codec or GSM modem and thus cannot be read or written to, although it is
+ * available to be configured with snd_hw_params(), etc and kcontrols in the
+ * normal alsa manner.
+ *
+ * Fast DAI switching:-
+ *
+ * The driver can now fast switch between the DAI configurations via a
+ * an alsa kcontrol. This allows the PCM to remain open.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <asm/div64.h>
+
+#include "wm8753.h"
+
+static int caps_charge = 2000;
+module_param(caps_charge, int, 0);
+MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)");
+
+static int wm8753_hifi_write_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt);
+static int wm8753_voice_write_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt);
+
+/*
+ * wm8753 register cache
+ * We can't read the WM8753 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8753_reg[] = {
+ 0x0000, 0x0008, 0x0000, 0x000a,
+ 0x000a, 0x0033, 0x0000, 0x0007,
+ 0x00ff, 0x00ff, 0x000f, 0x000f,
+ 0x007b, 0x0000, 0x0032, 0x0000,
+ 0x00c3, 0x00c3, 0x00c0, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0055, 0x0005, 0x0050, 0x0055,
+ 0x0050, 0x0055, 0x0050, 0x0055,
+ 0x0079, 0x0079, 0x0079, 0x0079,
+ 0x0079, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0097, 0x0097, 0x0000,
+ 0x0004, 0x0000, 0x0083, 0x0024,
+ 0x01ba, 0x0000, 0x0083, 0x0024,
+ 0x01ba, 0x0000, 0x0000, 0x0000
+};
+
+/* codec private data */
+struct wm8753_priv {
+ enum snd_soc_control_type control_type;
+ unsigned int sysclk;
+ unsigned int pcmclk;
+
+ unsigned int voice_fmt;
+ unsigned int hifi_fmt;
+
+ int dai_func;
+};
+
+#define wm8753_reset(c) snd_soc_write(c, WM8753_RESET, 0)
+
+/*
+ * WM8753 Controls
+ */
+static const char *wm8753_base[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm8753_base_filter[] =
+ {"130Hz @ 48kHz", "200Hz @ 48kHz", "100Hz @ 16kHz", "400Hz @ 48kHz",
+ "100Hz @ 8kHz", "200Hz @ 8kHz"};
+static const char *wm8753_treble[] = {"8kHz", "4kHz"};
+static const char *wm8753_alc_func[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8753_ng_type[] = {"Constant PGA Gain", "Mute ADC Output"};
+static const char *wm8753_3d_func[] = {"Capture", "Playback"};
+static const char *wm8753_3d_uc[] = {"2.2kHz", "1.5kHz"};
+static const char *wm8753_3d_lc[] = {"200Hz", "500Hz"};
+static const char *wm8753_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz"};
+static const char *wm8753_mono_mix[] = {"Stereo", "Left", "Right", "Mono"};
+static const char *wm8753_dac_phase[] = {"Non Inverted", "Inverted"};
+static const char *wm8753_line_mix[] = {"Line 1 + 2", "Line 1 - 2",
+ "Line 1", "Line 2"};
+static const char *wm8753_mono_mux[] = {"Line Mix", "Rx Mix"};
+static const char *wm8753_right_mux[] = {"Line 2", "Rx Mix"};
+static const char *wm8753_left_mux[] = {"Line 1", "Rx Mix"};
+static const char *wm8753_rxmsel[] = {"RXP - RXN", "RXP + RXN", "RXP", "RXN"};
+static const char *wm8753_sidetone_mux[] = {"Left PGA", "Mic 1", "Mic 2",
+ "Right PGA"};
+static const char *wm8753_mono2_src[] = {"Inverted Mono 1", "Left", "Right",
+ "Left + Right"};
+static const char *wm8753_out3[] = {"VREF", "ROUT2", "Left + Right"};
+static const char *wm8753_out4[] = {"VREF", "Capture ST", "LOUT2"};
+static const char *wm8753_radcsel[] = {"PGA", "Line or RXP-RXN", "Sidetone"};
+static const char *wm8753_ladcsel[] = {"PGA", "Line or RXP-RXN", "Line"};
+static const char *wm8753_mono_adc[] = {"Stereo", "Analogue Mix Left",
+ "Analogue Mix Right", "Digital Mono Mix"};
+static const char *wm8753_adc_hp[] = {"3.4Hz @ 48kHz", "82Hz @ 16k",
+ "82Hz @ 8kHz", "170Hz @ 8kHz"};
+static const char *wm8753_adc_filter[] = {"HiFi", "Voice"};
+static const char *wm8753_mic_sel[] = {"Mic 1", "Mic 2", "Mic 3"};
+static const char *wm8753_dai_mode[] = {"DAI 0", "DAI 1", "DAI 2", "DAI 3"};
+static const char *wm8753_dat_sel[] = {"Stereo", "Left ADC", "Right ADC",
+ "Channel Swap"};
+static const char *wm8753_rout2_phase[] = {"Non Inverted", "Inverted"};
+
+static const struct soc_enum wm8753_enum[] = {
+SOC_ENUM_SINGLE(WM8753_BASS, 7, 2, wm8753_base),
+SOC_ENUM_SINGLE(WM8753_BASS, 4, 6, wm8753_base_filter),
+SOC_ENUM_SINGLE(WM8753_TREBLE, 6, 2, wm8753_treble),
+SOC_ENUM_SINGLE(WM8753_ALC1, 7, 4, wm8753_alc_func),
+SOC_ENUM_SINGLE(WM8753_NGATE, 1, 2, wm8753_ng_type),
+SOC_ENUM_SINGLE(WM8753_3D, 7, 2, wm8753_3d_func),
+SOC_ENUM_SINGLE(WM8753_3D, 6, 2, wm8753_3d_uc),
+SOC_ENUM_SINGLE(WM8753_3D, 5, 2, wm8753_3d_lc),
+SOC_ENUM_SINGLE(WM8753_DAC, 1, 4, wm8753_deemp),
+SOC_ENUM_SINGLE(WM8753_DAC, 4, 4, wm8753_mono_mix),
+SOC_ENUM_SINGLE(WM8753_DAC, 6, 2, wm8753_dac_phase),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 3, 4, wm8753_line_mix),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 2, 2, wm8753_mono_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 1, 2, wm8753_right_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 0, 2, wm8753_left_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL2, 6, 4, wm8753_rxmsel),
+SOC_ENUM_SINGLE(WM8753_INCTL2, 4, 4, wm8753_sidetone_mux),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 7, 4, wm8753_mono2_src),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 0, 3, wm8753_out3),
+SOC_ENUM_SINGLE(WM8753_ADCTL2, 7, 3, wm8753_out4),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 2, 3, wm8753_radcsel),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 0, 3, wm8753_ladcsel),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 4, 4, wm8753_mono_adc),
+SOC_ENUM_SINGLE(WM8753_ADC, 2, 4, wm8753_adc_hp),
+SOC_ENUM_SINGLE(WM8753_ADC, 4, 2, wm8753_adc_filter),
+SOC_ENUM_SINGLE(WM8753_MICBIAS, 6, 3, wm8753_mic_sel),
+SOC_ENUM_SINGLE(WM8753_IOCTL, 2, 4, wm8753_dai_mode),
+SOC_ENUM_SINGLE(WM8753_ADC, 7, 4, wm8753_dat_sel),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 2, 2, wm8753_rout2_phase),
+};
+
+
+static int wm8753_get_dai(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = wm8753->dai_func;
+ return 0;
+}
+
+static int wm8753_set_dai(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+ u16 ioctl;
+
+ if (wm8753->dai_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ if (codec->active)
+ return -EBUSY;
+
+ ioctl = snd_soc_read(codec, WM8753_IOCTL);
+
+ wm8753->dai_func = ucontrol->value.integer.value[0];
+
+ if (((ioctl >> 2) & 0x3) == wm8753->dai_func)
+ return 1;
+
+ ioctl = (ioctl & 0x1f3) | (wm8753->dai_func << 2);
+ snd_soc_write(codec, WM8753_IOCTL, ioctl);
+
+
+ wm8753_hifi_write_dai_fmt(codec, wm8753->hifi_fmt);
+ wm8753_voice_write_dai_fmt(codec, wm8753->voice_fmt);
+
+ return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(mic_preamp_tlv, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+static const unsigned int out_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ /* 0000000 - 0101111 = "Analogue mute" */
+ 0, 48, TLV_DB_SCALE_ITEM(-25500, 0, 0),
+ 48, 127, TLV_DB_SCALE_ITEM(-7300, 100, 0),
+};
+static const DECLARE_TLV_DB_SCALE(mix_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(voice_mix_tlv, -1200, 300, 0);
+static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0);
+
+static const struct snd_kcontrol_new wm8753_snd_controls[] = {
+SOC_DOUBLE_R_TLV("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0, dac_tlv),
+
+SOC_DOUBLE_R_TLV("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0,
+ adc_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0,
+ 127, 0, out_tlv),
+
+SOC_SINGLE_TLV("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0, out_tlv),
+
+SOC_DOUBLE_R_TLV("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7,
+ 1, mix_tlv),
+SOC_DOUBLE_R_TLV("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4,
+ 7, 1, mix_tlv),
+SOC_DOUBLE_R_TLV("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7,
+ 1, voice_mix_tlv),
+
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7,
+ 1, 0),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7,
+ 1, 0),
+
+SOC_SINGLE_TLV("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1, mix_tlv),
+SOC_SINGLE_TLV("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1,
+ mix_tlv),
+SOC_SINGLE_TLV("Mono Voice Playback Volume", WM8753_MOUTM2, 0, 7, 1,
+ voice_mix_tlv),
+SOC_SINGLE("Mono Playback ZC Switch", WM8753_MOUTV, 7, 1, 0),
+
+SOC_ENUM("Bass Boost", wm8753_enum[0]),
+SOC_ENUM("Bass Filter", wm8753_enum[1]),
+SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1),
+
+SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1),
+SOC_ENUM("Treble Cut-off", wm8753_enum[2]),
+
+SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1,
+ rec_mix_tlv),
+SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1,
+ rec_mix_tlv),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0,
+ pga_tlv),
+SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8753_LINVOL, WM8753_RINVOL, 7, 1, 1),
+
+SOC_ENUM("Capture Filter Select", wm8753_enum[23]),
+SOC_ENUM("Capture Filter Cut-off", wm8753_enum[24]),
+SOC_SINGLE("Capture Filter Switch", WM8753_ADC, 0, 1, 1),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8753_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8753_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", wm8753_enum[3]),
+SOC_SINGLE("ALC Capture ZC Switch", WM8753_ALC2, 8, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8753_ALC2, 0, 15, 1),
+SOC_SINGLE("ALC Capture Decay Time", WM8753_ALC3, 4, 15, 1),
+SOC_SINGLE("ALC Capture Attack Time", WM8753_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8753_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", wm8753_enum[4]),
+SOC_SINGLE("ALC Capture NG Switch", WM8753_NGATE, 0, 1, 0),
+
+SOC_ENUM("3D Function", wm8753_enum[5]),
+SOC_ENUM("3D Upper Cut-off", wm8753_enum[6]),
+SOC_ENUM("3D Lower Cut-off", wm8753_enum[7]),
+SOC_SINGLE("3D Volume", WM8753_3D, 1, 15, 0),
+SOC_SINGLE("3D Switch", WM8753_3D, 0, 1, 0),
+
+SOC_SINGLE("Capture 6dB Attenuate", WM8753_ADCTL1, 2, 1, 0),
+SOC_SINGLE("Playback 6dB Attenuate", WM8753_ADCTL1, 1, 1, 0),
+
+SOC_ENUM("De-emphasis", wm8753_enum[8]),
+SOC_ENUM("Playback Mono Mix", wm8753_enum[9]),
+SOC_ENUM("Playback Phase", wm8753_enum[10]),
+
+SOC_SINGLE_TLV("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0, mic_preamp_tlv),
+SOC_SINGLE_TLV("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0, mic_preamp_tlv),
+
+SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai),
+
+SOC_ENUM("ADC Data Select", wm8753_enum[27]),
+SOC_ENUM("ROUT2 Phase", wm8753_enum[28]),
+};
+
+/*
+ * _DAPM_ Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8753_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_LOUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Left Playback Switch", WM8753_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_LOUTM1, 7, 1, 0),
+};
+
+/* Right mixer */
+static const struct snd_kcontrol_new wm8753_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_ROUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8753_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_ROUTM1, 7, 1, 0),
+};
+
+/* Mono mixer */
+static const struct snd_kcontrol_new wm8753_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8753_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8753_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_MOUTM2, 3, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_MOUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_MOUTM1, 7, 1, 0),
+};
+
+/* Mono 2 Mux */
+static const struct snd_kcontrol_new wm8753_mono2_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[17]);
+
+/* Out 3 Mux */
+static const struct snd_kcontrol_new wm8753_out3_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[18]);
+
+/* Out 4 Mux */
+static const struct snd_kcontrol_new wm8753_out4_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[19]);
+
+/* ADC Mono Mix */
+static const struct snd_kcontrol_new wm8753_adc_mono_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[22]);
+
+/* Record mixer */
+static const struct snd_kcontrol_new wm8753_record_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Capture Switch", WM8753_RECMIX2, 3, 1, 0),
+SOC_DAPM_SINGLE("Left Capture Switch", WM8753_RECMIX1, 3, 1, 0),
+SOC_DAPM_SINGLE("Right Capture Switch", WM8753_RECMIX1, 7, 1, 0),
+};
+
+/* Left ADC mux */
+static const struct snd_kcontrol_new wm8753_adc_left_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[21]);
+
+/* Right ADC mux */
+static const struct snd_kcontrol_new wm8753_adc_right_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[20]);
+
+/* MIC mux */
+static const struct snd_kcontrol_new wm8753_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[16]);
+
+/* ALC mixer */
+static const struct snd_kcontrol_new wm8753_alc_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Capture Switch", WM8753_INCTL2, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic2 Capture Switch", WM8753_INCTL2, 2, 1, 0),
+SOC_DAPM_SINGLE("Mic1 Capture Switch", WM8753_INCTL2, 1, 1, 0),
+SOC_DAPM_SINGLE("Rx Capture Switch", WM8753_INCTL2, 0, 1, 0),
+};
+
+/* Left Line mux */
+static const struct snd_kcontrol_new wm8753_line_left_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[14]);
+
+/* Right Line mux */
+static const struct snd_kcontrol_new wm8753_line_right_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[13]);
+
+/* Mono Line mux */
+static const struct snd_kcontrol_new wm8753_line_mono_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[12]);
+
+/* Line mux and mixer */
+static const struct snd_kcontrol_new wm8753_line_mux_mix_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[11]);
+
+/* Rx mux and mixer */
+static const struct snd_kcontrol_new wm8753_rx_mux_mix_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[15]);
+
+/* Mic Selector Mux */
+static const struct snd_kcontrol_new wm8753_mic_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[25]);
+
+static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = {
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8753_PWR1, 5, 0),
+SND_SOC_DAPM_MIXER("Left Mixer", WM8753_PWR4, 0, 0,
+ &wm8753_left_mixer_controls[0], ARRAY_SIZE(wm8753_left_mixer_controls)),
+SND_SOC_DAPM_PGA("Left Out 1", WM8753_PWR3, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Out 2", WM8753_PWR3, 6, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", WM8753_PWR1, 3, 0),
+SND_SOC_DAPM_OUTPUT("LOUT1"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_MIXER("Right Mixer", WM8753_PWR4, 1, 0,
+ &wm8753_right_mixer_controls[0], ARRAY_SIZE(wm8753_right_mixer_controls)),
+SND_SOC_DAPM_PGA("Right Out 1", WM8753_PWR3, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Out 2", WM8753_PWR3, 5, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", WM8753_PWR1, 2, 0),
+SND_SOC_DAPM_OUTPUT("ROUT1"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_MIXER("Mono Mixer", WM8753_PWR4, 2, 0,
+ &wm8753_mono_mixer_controls[0], ARRAY_SIZE(wm8753_mono_mixer_controls)),
+SND_SOC_DAPM_PGA("Mono Out 1", WM8753_PWR3, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out 2", WM8753_PWR3, 1, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", WM8753_PWR1, 4, 0),
+SND_SOC_DAPM_OUTPUT("MONO1"),
+SND_SOC_DAPM_MUX("Mono 2 Mux", SND_SOC_NOPM, 0, 0, &wm8753_mono2_controls),
+SND_SOC_DAPM_OUTPUT("MONO2"),
+SND_SOC_DAPM_MIXER("Out3 Left + Right", -1, 0, 0, NULL, 0),
+SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out3_controls),
+SND_SOC_DAPM_PGA("Out 3", WM8753_PWR3, 4, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_MUX("Out4 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out4_controls),
+SND_SOC_DAPM_PGA("Out 4", WM8753_PWR3, 3, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_MIXER("Playback Mixer", WM8753_PWR4, 3, 0,
+ &wm8753_record_mixer_controls[0],
+ ARRAY_SIZE(wm8753_record_mixer_controls)),
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8753_PWR2, 3, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8753_PWR2, 2, 0),
+SND_SOC_DAPM_MUX("Capture Left Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_mono_controls),
+SND_SOC_DAPM_MUX("Capture Right Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_mono_controls),
+SND_SOC_DAPM_MUX("Capture Left Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_left_controls),
+SND_SOC_DAPM_MUX("Capture Right Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_right_controls),
+SND_SOC_DAPM_MUX("Mic Sidetone Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_mic_mux_controls),
+SND_SOC_DAPM_PGA("Left Capture Volume", WM8753_PWR2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Capture Volume", WM8753_PWR2, 4, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("ALC Mixer", WM8753_PWR2, 6, 0,
+ &wm8753_alc_mixer_controls[0], ARRAY_SIZE(wm8753_alc_mixer_controls)),
+SND_SOC_DAPM_MUX("Line Left Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_line_left_controls),
+SND_SOC_DAPM_MUX("Line Right Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_line_right_controls),
+SND_SOC_DAPM_MUX("Line Mono Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_line_mono_controls),
+SND_SOC_DAPM_MUX("Line Mixer", WM8753_PWR2, 0, 0,
+ &wm8753_line_mux_mix_controls),
+SND_SOC_DAPM_MUX("Rx Mixer", WM8753_PWR2, 1, 0,
+ &wm8753_rx_mux_mix_controls),
+SND_SOC_DAPM_PGA("Mic 1 Volume", WM8753_PWR2, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mic 2 Volume", WM8753_PWR2, 7, 0, NULL, 0),
+SND_SOC_DAPM_MUX("Mic Selection Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_mic_sel_mux_controls),
+SND_SOC_DAPM_INPUT("LINE1"),
+SND_SOC_DAPM_INPUT("LINE2"),
+SND_SOC_DAPM_INPUT("RXP"),
+SND_SOC_DAPM_INPUT("RXN"),
+SND_SOC_DAPM_INPUT("ACIN"),
+SND_SOC_DAPM_OUTPUT("ACOP"),
+SND_SOC_DAPM_INPUT("MIC1N"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2N"),
+SND_SOC_DAPM_INPUT("MIC2"),
+SND_SOC_DAPM_VMID("VREF"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* left mixer */
+ {"Left Mixer", "Left Playback Switch", "Left DAC"},
+ {"Left Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Left Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+ {"Left Mixer", "Bypass Playback Switch", "Line Left Mux"},
+
+ /* right mixer */
+ {"Right Mixer", "Right Playback Switch", "Right DAC"},
+ {"Right Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Right Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+ {"Right Mixer", "Bypass Playback Switch", "Line Right Mux"},
+
+ /* mono mixer */
+ {"Mono Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Mono Mixer", "Left Playback Switch", "Left DAC"},
+ {"Mono Mixer", "Right Playback Switch", "Right DAC"},
+ {"Mono Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+ {"Mono Mixer", "Bypass Playback Switch", "Line Mono Mux"},
+
+ /* left out */
+ {"Left Out 1", NULL, "Left Mixer"},
+ {"Left Out 2", NULL, "Left Mixer"},
+ {"LOUT1", NULL, "Left Out 1"},
+ {"LOUT2", NULL, "Left Out 2"},
+
+ /* right out */
+ {"Right Out 1", NULL, "Right Mixer"},
+ {"Right Out 2", NULL, "Right Mixer"},
+ {"ROUT1", NULL, "Right Out 1"},
+ {"ROUT2", NULL, "Right Out 2"},
+
+ /* mono 1 out */
+ {"Mono Out 1", NULL, "Mono Mixer"},
+ {"MONO1", NULL, "Mono Out 1"},
+
+ /* mono 2 out */
+ {"Mono 2 Mux", "Left + Right", "Out3 Left + Right"},
+ {"Mono 2 Mux", "Inverted Mono 1", "MONO1"},
+ {"Mono 2 Mux", "Left", "Left Mixer"},
+ {"Mono 2 Mux", "Right", "Right Mixer"},
+ {"Mono Out 2", NULL, "Mono 2 Mux"},
+ {"MONO2", NULL, "Mono Out 2"},
+
+ /* out 3 */
+ {"Out3 Left + Right", NULL, "Left Mixer"},
+ {"Out3 Left + Right", NULL, "Right Mixer"},
+ {"Out3 Mux", "VREF", "VREF"},
+ {"Out3 Mux", "Left + Right", "Out3 Left + Right"},
+ {"Out3 Mux", "ROUT2", "ROUT2"},
+ {"Out 3", NULL, "Out3 Mux"},
+ {"OUT3", NULL, "Out 3"},
+
+ /* out 4 */
+ {"Out4 Mux", "VREF", "VREF"},
+ {"Out4 Mux", "Capture ST", "Playback Mixer"},
+ {"Out4 Mux", "LOUT2", "LOUT2"},
+ {"Out 4", NULL, "Out4 Mux"},
+ {"OUT4", NULL, "Out 4"},
+
+ /* record mixer */
+ {"Playback Mixer", "Left Capture Switch", "Left Mixer"},
+ {"Playback Mixer", "Voice Capture Switch", "Mono Mixer"},
+ {"Playback Mixer", "Right Capture Switch", "Right Mixer"},
+
+ /* Mic/SideTone Mux */
+ {"Mic Sidetone Mux", "Left PGA", "Left Capture Volume"},
+ {"Mic Sidetone Mux", "Right PGA", "Right Capture Volume"},
+ {"Mic Sidetone Mux", "Mic 1", "Mic 1 Volume"},
+ {"Mic Sidetone Mux", "Mic 2", "Mic 2 Volume"},
+
+ /* Capture Left Mux */
+ {"Capture Left Mux", "PGA", "Left Capture Volume"},
+ {"Capture Left Mux", "Line or RXP-RXN", "Line Left Mux"},
+ {"Capture Left Mux", "Line", "LINE1"},
+
+ /* Capture Right Mux */
+ {"Capture Right Mux", "PGA", "Right Capture Volume"},
+ {"Capture Right Mux", "Line or RXP-RXN", "Line Right Mux"},
+ {"Capture Right Mux", "Sidetone", "Playback Mixer"},
+
+ /* Mono Capture mixer-mux */
+ {"Capture Right Mixer", "Stereo", "Capture Right Mux"},
+ {"Capture Left Mixer", "Stereo", "Capture Left Mux"},
+ {"Capture Left Mixer", "Analogue Mix Left", "Capture Left Mux"},
+ {"Capture Left Mixer", "Analogue Mix Left", "Capture Right Mux"},
+ {"Capture Right Mixer", "Analogue Mix Right", "Capture Left Mux"},
+ {"Capture Right Mixer", "Analogue Mix Right", "Capture Right Mux"},
+ {"Capture Left Mixer", "Digital Mono Mix", "Capture Left Mux"},
+ {"Capture Left Mixer", "Digital Mono Mix", "Capture Right Mux"},
+ {"Capture Right Mixer", "Digital Mono Mix", "Capture Left Mux"},
+ {"Capture Right Mixer", "Digital Mono Mix", "Capture Right Mux"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Capture Left Mixer"},
+ {"Right ADC", NULL, "Capture Right Mixer"},
+
+ /* Left Capture Volume */
+ {"Left Capture Volume", NULL, "ACIN"},
+
+ /* Right Capture Volume */
+ {"Right Capture Volume", NULL, "Mic 2 Volume"},
+
+ /* ALC Mixer */
+ {"ALC Mixer", "Line Capture Switch", "Line Mixer"},
+ {"ALC Mixer", "Mic2 Capture Switch", "Mic 2 Volume"},
+ {"ALC Mixer", "Mic1 Capture Switch", "Mic 1 Volume"},
+ {"ALC Mixer", "Rx Capture Switch", "Rx Mixer"},
+
+ /* Line Left Mux */
+ {"Line Left Mux", "Line 1", "LINE1"},
+ {"Line Left Mux", "Rx Mix", "Rx Mixer"},
+
+ /* Line Right Mux */
+ {"Line Right Mux", "Line 2", "LINE2"},
+ {"Line Right Mux", "Rx Mix", "Rx Mixer"},
+
+ /* Line Mono Mux */
+ {"Line Mono Mux", "Line Mix", "Line Mixer"},
+ {"Line Mono Mux", "Rx Mix", "Rx Mixer"},
+
+ /* Line Mixer/Mux */
+ {"Line Mixer", "Line 1 + 2", "LINE1"},
+ {"Line Mixer", "Line 1 - 2", "LINE1"},
+ {"Line Mixer", "Line 1 + 2", "LINE2"},
+ {"Line Mixer", "Line 1 - 2", "LINE2"},
+ {"Line Mixer", "Line 1", "LINE1"},
+ {"Line Mixer", "Line 2", "LINE2"},
+
+ /* Rx Mixer/Mux */
+ {"Rx Mixer", "RXP - RXN", "RXP"},
+ {"Rx Mixer", "RXP + RXN", "RXP"},
+ {"Rx Mixer", "RXP - RXN", "RXN"},
+ {"Rx Mixer", "RXP + RXN", "RXN"},
+ {"Rx Mixer", "RXP", "RXP"},
+ {"Rx Mixer", "RXN", "RXN"},
+
+ /* Mic 1 Volume */
+ {"Mic 1 Volume", NULL, "MIC1N"},
+ {"Mic 1 Volume", NULL, "Mic Selection Mux"},
+
+ /* Mic 2 Volume */
+ {"Mic 2 Volume", NULL, "MIC2N"},
+ {"Mic 2 Volume", NULL, "MIC2"},
+
+ /* Mic Selector Mux */
+ {"Mic Selection Mux", "Mic 1", "MIC1"},
+ {"Mic Selection Mux", "Mic 2", "MIC2N"},
+ {"Mic Selection Mux", "Mic 3", "MIC2"},
+
+ /* ACOP */
+ {"ACOP", NULL, "ALC Mixer"},
+};
+
+static int wm8753_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8753_dapm_widgets,
+ ARRAY_SIZE(wm8753_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 div2:1;
+ u32 n:4;
+ u32 k:24;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 22) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->div2 = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->div2 = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "wm8753: unsupported N = %u\n", Ndiv);
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+}
+
+static int wm8753_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ u16 reg, enable;
+ int offset;
+ struct snd_soc_codec *codec = codec_dai->codec;
+
+ if (pll_id < WM8753_PLL1 || pll_id > WM8753_PLL2)
+ return -ENODEV;
+
+ if (pll_id == WM8753_PLL1) {
+ offset = 0;
+ enable = 0x10;
+ reg = snd_soc_read(codec, WM8753_CLOCK) & 0xffef;
+ } else {
+ offset = 4;
+ enable = 0x8;
+ reg = snd_soc_read(codec, WM8753_CLOCK) & 0xfff7;
+ }
+
+ if (!freq_in || !freq_out) {
+ /* disable PLL */
+ snd_soc_write(codec, WM8753_PLL1CTL1 + offset, 0x0026);
+ snd_soc_write(codec, WM8753_CLOCK, reg);
+ return 0;
+ } else {
+ u16 value = 0;
+ struct _pll_div pll_div;
+
+ pll_factors(&pll_div, freq_out * 8, freq_in);
+
+ /* set up N and K PLL divisor ratios */
+ /* bits 8:5 = PLL_N, bits 3:0 = PLL_K[21:18] */
+ value = (pll_div.n << 5) + ((pll_div.k & 0x3c0000) >> 18);
+ snd_soc_write(codec, WM8753_PLL1CTL2 + offset, value);
+
+ /* bits 8:0 = PLL_K[17:9] */
+ value = (pll_div.k & 0x03fe00) >> 9;
+ snd_soc_write(codec, WM8753_PLL1CTL3 + offset, value);
+
+ /* bits 8:0 = PLL_K[8:0] */
+ value = pll_div.k & 0x0001ff;
+ snd_soc_write(codec, WM8753_PLL1CTL4 + offset, value);
+
+ /* set PLL as input and enable */
+ snd_soc_write(codec, WM8753_PLL1CTL1 + offset, 0x0027 |
+ (pll_div.div2 << 3));
+ snd_soc_write(codec, WM8753_CLOCK, reg | enable);
+ }
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u8 sr:5;
+ u8 usb:1;
+};
+
+/* codec hifi mclk (after PLL) clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 8k */
+ {12288000, 8000, 0x6, 0x0},
+ {11289600, 8000, 0x16, 0x0},
+ {18432000, 8000, 0x7, 0x0},
+ {16934400, 8000, 0x17, 0x0},
+ {12000000, 8000, 0x6, 0x1},
+
+ /* 11.025k */
+ {11289600, 11025, 0x18, 0x0},
+ {16934400, 11025, 0x19, 0x0},
+ {12000000, 11025, 0x19, 0x1},
+
+ /* 16k */
+ {12288000, 16000, 0xa, 0x0},
+ {18432000, 16000, 0xb, 0x0},
+ {12000000, 16000, 0xa, 0x1},
+
+ /* 22.05k */
+ {11289600, 22050, 0x1a, 0x0},
+ {16934400, 22050, 0x1b, 0x0},
+ {12000000, 22050, 0x1b, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 0xc, 0x0},
+ {18432000, 32000, 0xd, 0x0},
+ {12000000, 32000, 0xa, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 0x10, 0x0},
+ {16934400, 44100, 0x11, 0x0},
+ {12000000, 44100, 0x11, 0x1},
+
+ /* 48k */
+ {12288000, 48000, 0x0, 0x0},
+ {18432000, 48000, 0x1, 0x0},
+ {12000000, 48000, 0x0, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 0x1e, 0x0},
+ {16934400, 88200, 0x1f, 0x0},
+ {12000000, 88200, 0x1f, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 0xe, 0x0},
+ {18432000, 96000, 0xf, 0x0},
+ {12000000, 96000, 0xe, 0x1},
+};
+
+static int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return -EINVAL;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int wm8753_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ if (clk_id == WM8753_MCLK) {
+ wm8753->sysclk = freq;
+ return 0;
+ } else if (clk_id == WM8753_PCMCLK) {
+ wm8753->pcmclk = freq;
+ return 0;
+ }
+ break;
+ }
+ return -EINVAL;
+}
+
+/*
+ * Set's ADC and Voice DAC format.
+ */
+static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ u16 voice = snd_soc_read(codec, WM8753_PCM) & 0x01ec;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ voice |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ voice |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ voice |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ voice |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8753_PCM, voice);
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+ u16 voice = snd_soc_read(codec, WM8753_PCM) & 0x01f3;
+ u16 srate = snd_soc_read(codec, WM8753_SRATE1) & 0x017f;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ voice |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ voice |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ voice |= 0x000c;
+ break;
+ }
+
+ /* sample rate */
+ if (params_rate(params) * 384 == wm8753->pcmclk)
+ srate |= 0x80;
+ snd_soc_write(codec, WM8753_SRATE1, srate);
+
+ snd_soc_write(codec, WM8753_PCM, voice);
+ return 0;
+}
+
+/*
+ * Set's PCM dai fmt and BCLK.
+ */
+static int wm8753_pcm_set_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ u16 voice, ioctl;
+
+ voice = snd_soc_read(codec, WM8753_PCM) & 0x011f;
+ ioctl = snd_soc_read(codec, WM8753_IOCTL) & 0x015d;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ ioctl |= 0x2;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ voice |= 0x0040;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ voice |= 0x0080;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ voice &= ~0x0010;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ voice |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ voice |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ voice |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8753_PCM, voice);
+ snd_soc_write(codec, WM8753_IOCTL, ioctl);
+ return 0;
+}
+
+static int wm8753_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8753_PCMDIV:
+ reg = snd_soc_read(codec, WM8753_CLOCK) & 0x003f;
+ snd_soc_write(codec, WM8753_CLOCK, reg | div);
+ break;
+ case WM8753_BCLKDIV:
+ reg = snd_soc_read(codec, WM8753_SRATE2) & 0x01c7;
+ snd_soc_write(codec, WM8753_SRATE2, reg | div);
+ break;
+ case WM8753_VXCLKDIV:
+ reg = snd_soc_read(codec, WM8753_SRATE2) & 0x003f;
+ snd_soc_write(codec, WM8753_SRATE2, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Set's HiFi DAC format.
+ */
+static int wm8753_hdac_set_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ u16 hifi = snd_soc_read(codec, WM8753_HIFI) & 0x01e0;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ hifi |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ hifi |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ hifi |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ hifi |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8753_HIFI, hifi);
+ return 0;
+}
+
+/*
+ * Set's I2S DAI format.
+ */
+static int wm8753_i2s_set_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ u16 ioctl, hifi;
+
+ hifi = snd_soc_read(codec, WM8753_HIFI) & 0x011f;
+ ioctl = snd_soc_read(codec, WM8753_IOCTL) & 0x00ae;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ ioctl |= 0x1;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ hifi |= 0x0040;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ hifi |= 0x0080;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ hifi &= ~0x0010;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ hifi |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ hifi |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ hifi |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8753_HIFI, hifi);
+ snd_soc_write(codec, WM8753_IOCTL, ioctl);
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+ u16 srate = snd_soc_read(codec, WM8753_SRATE1) & 0x01c0;
+ u16 hifi = snd_soc_read(codec, WM8753_HIFI) & 0x01f3;
+ int coeff;
+
+ /* is digital filter coefficient valid ? */
+ coeff = get_coeff(wm8753->sysclk, params_rate(params));
+ if (coeff < 0) {
+ printk(KERN_ERR "wm8753 invalid MCLK or rate\n");
+ return coeff;
+ }
+ snd_soc_write(codec, WM8753_SRATE1, srate | (coeff_div[coeff].sr << 1) |
+ coeff_div[coeff].usb);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ hifi |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ hifi |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ hifi |= 0x000c;
+ break;
+ }
+
+ snd_soc_write(codec, WM8753_HIFI, hifi);
+ return 0;
+}
+
+static int wm8753_mode1v_set_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ u16 clock;
+
+ /* set clk source as pcmclk */
+ clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb;
+ snd_soc_write(codec, WM8753_CLOCK, clock);
+
+ return wm8753_vdac_adc_set_dai_fmt(codec, fmt);
+}
+
+static int wm8753_mode1h_set_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ return wm8753_hdac_set_dai_fmt(codec, fmt);
+}
+
+static int wm8753_mode2_set_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ u16 clock;
+
+ /* set clk source as pcmclk */
+ clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb;
+ snd_soc_write(codec, WM8753_CLOCK, clock);
+
+ return wm8753_vdac_adc_set_dai_fmt(codec, fmt);
+}
+
+static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ u16 clock;
+
+ /* set clk source as mclk */
+ clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb;
+ snd_soc_write(codec, WM8753_CLOCK, clock | 0x4);
+
+ if (wm8753_hdac_set_dai_fmt(codec, fmt) < 0)
+ return -EINVAL;
+ return wm8753_vdac_adc_set_dai_fmt(codec, fmt);
+}
+
+static int wm8753_hifi_write_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ switch (wm8753->dai_func) {
+ case 0:
+ ret = wm8753_mode1h_set_dai_fmt(codec, fmt);
+ break;
+ case 1:
+ ret = wm8753_mode2_set_dai_fmt(codec, fmt);
+ break;
+ case 2:
+ case 3:
+ ret = wm8753_mode3_4_set_dai_fmt(codec, fmt);
+ break;
+ default:
+ break;
+ }
+ if (ret)
+ return ret;
+
+ return wm8753_i2s_set_dai_fmt(codec, fmt);
+}
+
+static int wm8753_hifi_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+ wm8753->hifi_fmt = fmt;
+
+ return wm8753_hifi_write_dai_fmt(codec, fmt);
+};
+
+static int wm8753_voice_write_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ if (wm8753->dai_func != 0)
+ return 0;
+
+ ret = wm8753_mode1v_set_dai_fmt(codec, fmt);
+ if (ret)
+ return ret;
+ ret = wm8753_pcm_set_dai_fmt(codec, fmt);
+ if (ret)
+ return ret;
+
+ return 0;
+};
+
+static int wm8753_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+ wm8753->voice_fmt = fmt;
+
+ return wm8753_voice_write_dai_fmt(codec, fmt);
+};
+
+static int wm8753_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8753_DAC) & 0xfff7;
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+ /* the digital mute covers the HiFi and Voice DAC's on the WM8753.
+ * make sure we check if they are not both active when we mute */
+ if (mute && wm8753->dai_func == 1) {
+ if (!codec->active)
+ snd_soc_write(codec, WM8753_DAC, mute_reg | 0x8);
+ } else {
+ if (mute)
+ snd_soc_write(codec, WM8753_DAC, mute_reg | 0x8);
+ else
+ snd_soc_write(codec, WM8753_DAC, mute_reg);
+ }
+
+ return 0;
+}
+
+static int wm8753_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 pwr_reg = snd_soc_read(codec, WM8753_PWR1) & 0xfe3e;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* set vmid to 50k and unmute dac */
+ snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x00c0);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ /* set vmid to 5k for quick power up */
+ snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x01c1);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* mute dac and set vmid to 500k, enable VREF */
+ snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x0141);
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, WM8753_PWR1, 0x0001);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8753_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define WM8753_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+/*
+ * The WM8753 supports up to 4 different and mutually exclusive DAI
+ * configurations. This gives 2 PCM's available for use, hifi and voice.
+ * NOTE: The Voice PCM cannot play or capture audio to the CPU as it's DAI
+ * is connected between the wm8753 and a BT codec or GSM modem.
+ *
+ * 1. Voice over PCM DAI - HIFI DAC over HIFI DAI
+ * 2. Voice over HIFI DAI - HIFI disabled
+ * 3. Voice disabled - HIFI over HIFI
+ * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture
+ */
+static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode = {
+ .hw_params = wm8753_i2s_hw_params,
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_hifi_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode = {
+ .hw_params = wm8753_pcm_hw_params,
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_voice_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8753_dai[] = {
+/* DAI HiFi mode 1 */
+{ .name = "wm8753-hifi",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS
+ },
+ .capture = { /* dummy for fast DAI switching */
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS
+ },
+ .ops = &wm8753_dai_ops_hifi_mode,
+},
+/* DAI Voice mode 1 */
+{ .name = "wm8753-voice",
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,
+ },
+ .ops = &wm8753_dai_ops_voice_mode,
+},
+};
+
+static void wm8753_work(struct work_struct *work)
+{
+ struct snd_soc_dapm_context *dapm =
+ container_of(work, struct snd_soc_dapm_context,
+ delayed_work.work);
+ struct snd_soc_codec *codec = dapm->codec;
+ wm8753_set_bias_level(codec, dapm->bias_level);
+}
+
+static int wm8753_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8753_resume(struct snd_soc_codec *codec)
+{
+ u16 *reg_cache = codec->reg_cache;
+ int i;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 1; i < ARRAY_SIZE(wm8753_reg); i++) {
+ if (i == WM8753_RESET)
+ continue;
+
+ /* No point in writing hardware default values back */
+ if (reg_cache[i] == wm8753_reg[i])
+ continue;
+
+ snd_soc_write(codec, i, reg_cache[i]);
+ }
+
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge wm8753 caps */
+ if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) {
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ codec->dapm.bias_level = SND_SOC_BIAS_ON;
+ schedule_delayed_work(&codec->dapm.delayed_work,
+ msecs_to_jiffies(caps_charge));
+ }
+
+ return 0;
+}
+
+static int wm8753_probe(struct snd_soc_codec *codec)
+{
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ INIT_DELAYED_WORK(&codec->dapm.delayed_work, wm8753_work);
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8753->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8753_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ return ret;
+ }
+
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm8753->dai_func = 0;
+
+ /* charge output caps */
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ schedule_delayed_work(&codec->dapm.delayed_work,
+ msecs_to_jiffies(caps_charge));
+
+ /* set the update bits */
+ snd_soc_update_bits(codec, WM8753_LDAC, 0x0100, 0x0100);
+ snd_soc_update_bits(codec, WM8753_RDAC, 0x0100, 0x0100);
+ snd_soc_update_bits(codec, WM8753_LADC, 0x0100, 0x0100);
+ snd_soc_update_bits(codec, WM8753_RADC, 0x0100, 0x0100);
+ snd_soc_update_bits(codec, WM8753_LOUT1V, 0x0100, 0x0100);
+ snd_soc_update_bits(codec, WM8753_ROUT1V, 0x0100, 0x0100);
+ snd_soc_update_bits(codec, WM8753_LOUT2V, 0x0100, 0x0100);
+ snd_soc_update_bits(codec, WM8753_ROUT2V, 0x0100, 0x0100);
+ snd_soc_update_bits(codec, WM8753_LINVOL, 0x0100, 0x0100);
+ snd_soc_update_bits(codec, WM8753_RINVOL, 0x0100, 0x0100);
+
+ snd_soc_add_controls(codec, wm8753_snd_controls,
+ ARRAY_SIZE(wm8753_snd_controls));
+ wm8753_add_widgets(codec);
+
+ return 0;
+}
+
+/* power down chip */
+static int wm8753_remove(struct snd_soc_codec *codec)
+{
+ flush_delayed_work_sync(&codec->dapm.delayed_work);
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8753 = {
+ .probe = wm8753_probe,
+ .remove = wm8753_remove,
+ .suspend = wm8753_suspend,
+ .resume = wm8753_resume,
+ .set_bias_level = wm8753_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8753_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8753_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8753_spi_probe(struct spi_device *spi)
+{
+ struct wm8753_priv *wm8753;
+ int ret;
+
+ wm8753 = kzalloc(sizeof(struct wm8753_priv), GFP_KERNEL);
+ if (wm8753 == NULL)
+ return -ENOMEM;
+
+ wm8753->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8753);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8753, wm8753_dai, ARRAY_SIZE(wm8753_dai));
+ if (ret < 0)
+ kfree(wm8753);
+ return ret;
+}
+
+static int __devexit wm8753_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8753_spi_driver = {
+ .driver = {
+ .name = "wm8753-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8753_spi_probe,
+ .remove = __devexit_p(wm8753_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8753_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8753_priv *wm8753;
+ int ret;
+
+ wm8753 = kzalloc(sizeof(struct wm8753_priv), GFP_KERNEL);
+ if (wm8753 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8753);
+ wm8753->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8753, wm8753_dai, ARRAY_SIZE(wm8753_dai));
+ if (ret < 0)
+ kfree(wm8753);
+ return ret;
+}
+
+static __devexit int wm8753_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8753_i2c_id[] = {
+ { "wm8753", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8753_i2c_id);
+
+static struct i2c_driver wm8753_i2c_driver = {
+ .driver = {
+ .name = "wm8753-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8753_i2c_probe,
+ .remove = __devexit_p(wm8753_i2c_remove),
+ .id_table = wm8753_i2c_id,
+};
+#endif
+
+static int __init wm8753_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8753_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8753 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8753_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8753 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8753_modinit);
+
+static void __exit wm8753_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8753_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8753_spi_driver);
+#endif
+}
+module_exit(wm8753_exit);
+
+MODULE_DESCRIPTION("ASoC WM8753 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8753.h b/sound/soc/codecs/wm8753.h
new file mode 100644
index 00000000..94edac14
--- /dev/null
+++ b/sound/soc/codecs/wm8753.h
@@ -0,0 +1,118 @@
+/*
+ * wm8753.h -- audio driver for WM8753
+ *
+ * Copyright 2003 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _WM8753_H
+#define _WM8753_H
+
+/* WM8753 register space */
+
+#define WM8753_DAC 0x01
+#define WM8753_ADC 0x02
+#define WM8753_PCM 0x03
+#define WM8753_HIFI 0x04
+#define WM8753_IOCTL 0x05
+#define WM8753_SRATE1 0x06
+#define WM8753_SRATE2 0x07
+#define WM8753_LDAC 0x08
+#define WM8753_RDAC 0x09
+#define WM8753_BASS 0x0a
+#define WM8753_TREBLE 0x0b
+#define WM8753_ALC1 0x0c
+#define WM8753_ALC2 0x0d
+#define WM8753_ALC3 0x0e
+#define WM8753_NGATE 0x0f
+#define WM8753_LADC 0x10
+#define WM8753_RADC 0x11
+#define WM8753_ADCTL1 0x12
+#define WM8753_3D 0x13
+#define WM8753_PWR1 0x14
+#define WM8753_PWR2 0x15
+#define WM8753_PWR3 0x16
+#define WM8753_PWR4 0x17
+#define WM8753_ID 0x18
+#define WM8753_INTPOL 0x19
+#define WM8753_INTEN 0x1a
+#define WM8753_GPIO1 0x1b
+#define WM8753_GPIO2 0x1c
+#define WM8753_RESET 0x1f
+#define WM8753_RECMIX1 0x20
+#define WM8753_RECMIX2 0x21
+#define WM8753_LOUTM1 0x22
+#define WM8753_LOUTM2 0x23
+#define WM8753_ROUTM1 0x24
+#define WM8753_ROUTM2 0x25
+#define WM8753_MOUTM1 0x26
+#define WM8753_MOUTM2 0x27
+#define WM8753_LOUT1V 0x28
+#define WM8753_ROUT1V 0x29
+#define WM8753_LOUT2V 0x2a
+#define WM8753_ROUT2V 0x2b
+#define WM8753_MOUTV 0x2c
+#define WM8753_OUTCTL 0x2d
+#define WM8753_ADCIN 0x2e
+#define WM8753_INCTL1 0x2f
+#define WM8753_INCTL2 0x30
+#define WM8753_LINVOL 0x31
+#define WM8753_RINVOL 0x32
+#define WM8753_MICBIAS 0x33
+#define WM8753_CLOCK 0x34
+#define WM8753_PLL1CTL1 0x35
+#define WM8753_PLL1CTL2 0x36
+#define WM8753_PLL1CTL3 0x37
+#define WM8753_PLL1CTL4 0x38
+#define WM8753_PLL2CTL1 0x39
+#define WM8753_PLL2CTL2 0x3a
+#define WM8753_PLL2CTL3 0x3b
+#define WM8753_PLL2CTL4 0x3c
+#define WM8753_BIASCTL 0x3d
+#define WM8753_ADCTL2 0x3f
+
+#define WM8753_PLL1 0
+#define WM8753_PLL2 1
+
+/* clock inputs */
+#define WM8753_MCLK 0
+#define WM8753_PCMCLK 1
+
+/* clock divider id's */
+#define WM8753_PCMDIV 0
+#define WM8753_BCLKDIV 1
+#define WM8753_VXCLKDIV 2
+
+/* PCM clock dividers */
+#define WM8753_PCM_DIV_1 (0 << 6)
+#define WM8753_PCM_DIV_3 (2 << 6)
+#define WM8753_PCM_DIV_5_5 (3 << 6)
+#define WM8753_PCM_DIV_2 (4 << 6)
+#define WM8753_PCM_DIV_4 (5 << 6)
+#define WM8753_PCM_DIV_6 (6 << 6)
+#define WM8753_PCM_DIV_8 (7 << 6)
+
+/* BCLK clock dividers */
+#define WM8753_BCLK_DIV_1 (0 << 3)
+#define WM8753_BCLK_DIV_2 (1 << 3)
+#define WM8753_BCLK_DIV_4 (2 << 3)
+#define WM8753_BCLK_DIV_8 (3 << 3)
+#define WM8753_BCLK_DIV_16 (4 << 3)
+
+/* VXCLK clock dividers */
+#define WM8753_VXCLK_DIV_1 (0 << 6)
+#define WM8753_VXCLK_DIV_2 (1 << 6)
+#define WM8753_VXCLK_DIV_4 (2 << 6)
+#define WM8753_VXCLK_DIV_8 (3 << 6)
+#define WM8753_VXCLK_DIV_16 (4 << 6)
+
+#define WM8753_DAI_HIFI 0
+#define WM8753_DAI_VOICE 1
+
+#endif
diff --git a/sound/soc/codecs/wm8770.c b/sound/soc/codecs/wm8770.c
new file mode 100644
index 00000000..19b92baa
--- /dev/null
+++ b/sound/soc/codecs/wm8770.c
@@ -0,0 +1,749 @@
+/*
+ * wm8770.c -- WM8770 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8770.h"
+
+#define WM8770_NUM_SUPPLIES 3
+static const char *wm8770_supply_names[WM8770_NUM_SUPPLIES] = {
+ "AVDD1",
+ "AVDD2",
+ "DVDD"
+};
+
+static const u16 wm8770_reg_defs[WM8770_CACHEREGNUM] = {
+ 0x7f, 0x7f, 0x7f, 0x7f,
+ 0x7f, 0x7f, 0x7f, 0x7f,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0, 0x90, 0,
+ 0, 0x22, 0x22, 0x3e,
+ 0xc, 0xc, 0x100, 0x189,
+ 0x189, 0x8770
+};
+
+struct wm8770_priv {
+ enum snd_soc_control_type control_type;
+ struct regulator_bulk_data supplies[WM8770_NUM_SUPPLIES];
+ struct notifier_block disable_nb[WM8770_NUM_SUPPLIES];
+ struct snd_soc_codec *codec;
+ int sysclk;
+};
+
+static int vout12supply_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event);
+static int vout34supply_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event);
+
+/*
+ * We can't use the same notifier block for more than one supply and
+ * there's no way I can see to get from a callback to the caller
+ * except container_of().
+ */
+#define WM8770_REGULATOR_EVENT(n) \
+static int wm8770_regulator_event_##n(struct notifier_block *nb, \
+ unsigned long event, void *data) \
+{ \
+ struct wm8770_priv *wm8770 = container_of(nb, struct wm8770_priv, \
+ disable_nb[n]); \
+ if (event & REGULATOR_EVENT_DISABLE) { \
+ wm8770->codec->cache_sync = 1; \
+ } \
+ return 0; \
+}
+
+WM8770_REGULATOR_EVENT(0)
+WM8770_REGULATOR_EVENT(1)
+WM8770_REGULATOR_EVENT(2)
+
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(dac_dig_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(dac_alg_tlv, -12700, 100, 1);
+
+static const char *dac_phase_text[][2] = {
+ { "DAC1 Normal", "DAC1 Inverted" },
+ { "DAC2 Normal", "DAC2 Inverted" },
+ { "DAC3 Normal", "DAC3 Inverted" },
+ { "DAC4 Normal", "DAC4 Inverted" },
+};
+
+static const struct soc_enum dac_phase[] = {
+ SOC_ENUM_DOUBLE(WM8770_DACPHASE, 0, 1, 2, dac_phase_text[0]),
+ SOC_ENUM_DOUBLE(WM8770_DACPHASE, 2, 3, 2, dac_phase_text[1]),
+ SOC_ENUM_DOUBLE(WM8770_DACPHASE, 4, 5, 2, dac_phase_text[2]),
+ SOC_ENUM_DOUBLE(WM8770_DACPHASE, 6, 7, 2, dac_phase_text[3]),
+};
+
+static const struct snd_kcontrol_new wm8770_snd_controls[] = {
+ /* global DAC playback controls */
+ SOC_SINGLE_TLV("DAC Playback Volume", WM8770_MSDIGVOL, 0, 255, 0,
+ dac_dig_tlv),
+ SOC_SINGLE("DAC Playback Switch", WM8770_DACMUTE, 4, 1, 1),
+ SOC_SINGLE("DAC Playback ZC Switch", WM8770_DACCTRL1, 0, 1, 0),
+
+ /* global VOUT playback controls */
+ SOC_SINGLE_TLV("VOUT Playback Volume", WM8770_MSALGVOL, 0, 127, 0,
+ dac_alg_tlv),
+ SOC_SINGLE("VOUT Playback ZC Switch", WM8770_MSALGVOL, 7, 1, 0),
+
+ /* VOUT1/2/3/4 specific controls */
+ SOC_DOUBLE_R_TLV("VOUT1 Playback Volume", WM8770_VOUT1LVOL,
+ WM8770_VOUT1RVOL, 0, 127, 0, dac_alg_tlv),
+ SOC_DOUBLE_R("VOUT1 Playback ZC Switch", WM8770_VOUT1LVOL,
+ WM8770_VOUT1RVOL, 7, 1, 0),
+ SOC_DOUBLE_R_TLV("VOUT2 Playback Volume", WM8770_VOUT2LVOL,
+ WM8770_VOUT2RVOL, 0, 127, 0, dac_alg_tlv),
+ SOC_DOUBLE_R("VOUT2 Playback ZC Switch", WM8770_VOUT2LVOL,
+ WM8770_VOUT2RVOL, 7, 1, 0),
+ SOC_DOUBLE_R_TLV("VOUT3 Playback Volume", WM8770_VOUT3LVOL,
+ WM8770_VOUT3RVOL, 0, 127, 0, dac_alg_tlv),
+ SOC_DOUBLE_R("VOUT3 Playback ZC Switch", WM8770_VOUT3LVOL,
+ WM8770_VOUT3RVOL, 7, 1, 0),
+ SOC_DOUBLE_R_TLV("VOUT4 Playback Volume", WM8770_VOUT4LVOL,
+ WM8770_VOUT4RVOL, 0, 127, 0, dac_alg_tlv),
+ SOC_DOUBLE_R("VOUT4 Playback ZC Switch", WM8770_VOUT4LVOL,
+ WM8770_VOUT4RVOL, 7, 1, 0),
+
+ /* DAC1/2/3/4 specific controls */
+ SOC_DOUBLE_R_TLV("DAC1 Playback Volume", WM8770_DAC1LVOL,
+ WM8770_DAC1RVOL, 0, 255, 0, dac_dig_tlv),
+ SOC_SINGLE("DAC1 Deemphasis Switch", WM8770_DACCTRL2, 0, 1, 0),
+ SOC_ENUM("DAC1 Phase", dac_phase[0]),
+ SOC_DOUBLE_R_TLV("DAC2 Playback Volume", WM8770_DAC2LVOL,
+ WM8770_DAC2RVOL, 0, 255, 0, dac_dig_tlv),
+ SOC_SINGLE("DAC2 Deemphasis Switch", WM8770_DACCTRL2, 1, 1, 0),
+ SOC_ENUM("DAC2 Phase", dac_phase[1]),
+ SOC_DOUBLE_R_TLV("DAC3 Playback Volume", WM8770_DAC3LVOL,
+ WM8770_DAC3RVOL, 0, 255, 0, dac_dig_tlv),
+ SOC_SINGLE("DAC3 Deemphasis Switch", WM8770_DACCTRL2, 2, 1, 0),
+ SOC_ENUM("DAC3 Phase", dac_phase[2]),
+ SOC_DOUBLE_R_TLV("DAC4 Playback Volume", WM8770_DAC4LVOL,
+ WM8770_DAC4RVOL, 0, 255, 0, dac_dig_tlv),
+ SOC_SINGLE("DAC4 Deemphasis Switch", WM8770_DACCTRL2, 3, 1, 0),
+ SOC_ENUM("DAC4 Phase", dac_phase[3]),
+
+ /* ADC specific controls */
+ SOC_DOUBLE_R_TLV("Capture Volume", WM8770_ADCLCTRL, WM8770_ADCRCTRL,
+ 0, 31, 0, adc_tlv),
+ SOC_DOUBLE_R("Capture Switch", WM8770_ADCLCTRL, WM8770_ADCRCTRL,
+ 5, 1, 1),
+
+ /* other controls */
+ SOC_SINGLE("ADC 128x Oversampling Switch", WM8770_MSTRCTRL, 3, 1, 0),
+ SOC_SINGLE("ADC Highpass Filter Switch", WM8770_IFACECTRL, 8, 1, 1)
+};
+
+static const char *ain_text[] = {
+ "AIN1", "AIN2", "AIN3", "AIN4",
+ "AIN5", "AIN6", "AIN7", "AIN8"
+};
+
+static const struct soc_enum ain_enum =
+ SOC_ENUM_DOUBLE(WM8770_ADCMUX, 0, 4, 8, ain_text);
+
+static const struct snd_kcontrol_new ain_mux =
+ SOC_DAPM_ENUM("Capture Mux", ain_enum);
+
+static const struct snd_kcontrol_new vout1_mix_controls[] = {
+ SOC_DAPM_SINGLE("DAC1 Switch", WM8770_OUTMUX1, 0, 1, 0),
+ SOC_DAPM_SINGLE("AUX1 Switch", WM8770_OUTMUX1, 1, 1, 0),
+ SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX1, 2, 1, 0)
+};
+
+static const struct snd_kcontrol_new vout2_mix_controls[] = {
+ SOC_DAPM_SINGLE("DAC2 Switch", WM8770_OUTMUX1, 3, 1, 0),
+ SOC_DAPM_SINGLE("AUX2 Switch", WM8770_OUTMUX1, 4, 1, 0),
+ SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX1, 5, 1, 0)
+};
+
+static const struct snd_kcontrol_new vout3_mix_controls[] = {
+ SOC_DAPM_SINGLE("DAC3 Switch", WM8770_OUTMUX2, 0, 1, 0),
+ SOC_DAPM_SINGLE("AUX3 Switch", WM8770_OUTMUX2, 1, 1, 0),
+ SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX2, 2, 1, 0)
+};
+
+static const struct snd_kcontrol_new vout4_mix_controls[] = {
+ SOC_DAPM_SINGLE("DAC4 Switch", WM8770_OUTMUX2, 3, 1, 0),
+ SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX2, 4, 1, 0)
+};
+
+static const struct snd_soc_dapm_widget wm8770_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("AUX1"),
+ SND_SOC_DAPM_INPUT("AUX2"),
+ SND_SOC_DAPM_INPUT("AUX3"),
+
+ SND_SOC_DAPM_INPUT("AIN1"),
+ SND_SOC_DAPM_INPUT("AIN2"),
+ SND_SOC_DAPM_INPUT("AIN3"),
+ SND_SOC_DAPM_INPUT("AIN4"),
+ SND_SOC_DAPM_INPUT("AIN5"),
+ SND_SOC_DAPM_INPUT("AIN6"),
+ SND_SOC_DAPM_INPUT("AIN7"),
+ SND_SOC_DAPM_INPUT("AIN8"),
+
+ SND_SOC_DAPM_MUX("Capture Mux", WM8770_ADCMUX, 8, 1, &ain_mux),
+
+ SND_SOC_DAPM_ADC("ADC", "Capture", WM8770_PWDNCTRL, 1, 1),
+
+ SND_SOC_DAPM_DAC("DAC1", "Playback", WM8770_PWDNCTRL, 2, 1),
+ SND_SOC_DAPM_DAC("DAC2", "Playback", WM8770_PWDNCTRL, 3, 1),
+ SND_SOC_DAPM_DAC("DAC3", "Playback", WM8770_PWDNCTRL, 4, 1),
+ SND_SOC_DAPM_DAC("DAC4", "Playback", WM8770_PWDNCTRL, 5, 1),
+
+ SND_SOC_DAPM_SUPPLY("VOUT12 Supply", SND_SOC_NOPM, 0, 0,
+ vout12supply_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("VOUT34 Supply", SND_SOC_NOPM, 0, 0,
+ vout34supply_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_MIXER("VOUT1 Mixer", SND_SOC_NOPM, 0, 0,
+ vout1_mix_controls, ARRAY_SIZE(vout1_mix_controls)),
+ SND_SOC_DAPM_MIXER("VOUT2 Mixer", SND_SOC_NOPM, 0, 0,
+ vout2_mix_controls, ARRAY_SIZE(vout2_mix_controls)),
+ SND_SOC_DAPM_MIXER("VOUT3 Mixer", SND_SOC_NOPM, 0, 0,
+ vout3_mix_controls, ARRAY_SIZE(vout3_mix_controls)),
+ SND_SOC_DAPM_MIXER("VOUT4 Mixer", SND_SOC_NOPM, 0, 0,
+ vout4_mix_controls, ARRAY_SIZE(vout4_mix_controls)),
+
+ SND_SOC_DAPM_OUTPUT("VOUT1"),
+ SND_SOC_DAPM_OUTPUT("VOUT2"),
+ SND_SOC_DAPM_OUTPUT("VOUT3"),
+ SND_SOC_DAPM_OUTPUT("VOUT4")
+};
+
+static const struct snd_soc_dapm_route wm8770_intercon[] = {
+ { "Capture Mux", "AIN1", "AIN1" },
+ { "Capture Mux", "AIN2", "AIN2" },
+ { "Capture Mux", "AIN3", "AIN3" },
+ { "Capture Mux", "AIN4", "AIN4" },
+ { "Capture Mux", "AIN5", "AIN5" },
+ { "Capture Mux", "AIN6", "AIN6" },
+ { "Capture Mux", "AIN7", "AIN7" },
+ { "Capture Mux", "AIN8", "AIN8" },
+
+ { "ADC", NULL, "Capture Mux" },
+
+ { "VOUT1 Mixer", NULL, "VOUT12 Supply" },
+ { "VOUT1 Mixer", "DAC1 Switch", "DAC1" },
+ { "VOUT1 Mixer", "AUX1 Switch", "AUX1" },
+ { "VOUT1 Mixer", "Bypass Switch", "Capture Mux" },
+
+ { "VOUT2 Mixer", NULL, "VOUT12 Supply" },
+ { "VOUT2 Mixer", "DAC2 Switch", "DAC2" },
+ { "VOUT2 Mixer", "AUX2 Switch", "AUX2" },
+ { "VOUT2 Mixer", "Bypass Switch", "Capture Mux" },
+
+ { "VOUT3 Mixer", NULL, "VOUT34 Supply" },
+ { "VOUT3 Mixer", "DAC3 Switch", "DAC3" },
+ { "VOUT3 Mixer", "AUX3 Switch", "AUX3" },
+ { "VOUT3 Mixer", "Bypass Switch", "Capture Mux" },
+
+ { "VOUT4 Mixer", NULL, "VOUT34 Supply" },
+ { "VOUT4 Mixer", "DAC4 Switch", "DAC4" },
+ { "VOUT4 Mixer", "Bypass Switch", "Capture Mux" },
+
+ { "VOUT1", NULL, "VOUT1 Mixer" },
+ { "VOUT2", NULL, "VOUT2 Mixer" },
+ { "VOUT3", NULL, "VOUT3 Mixer" },
+ { "VOUT4", NULL, "VOUT4 Mixer" }
+};
+
+static int vout12supply_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec;
+
+ codec = w->codec;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ snd_soc_update_bits(codec, WM8770_OUTMUX1, 0x180, 0);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_update_bits(codec, WM8770_OUTMUX1, 0x180, 0x180);
+ break;
+ }
+
+ return 0;
+}
+
+static int vout34supply_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec;
+
+ codec = w->codec;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ snd_soc_update_bits(codec, WM8770_OUTMUX2, 0x180, 0);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_update_bits(codec, WM8770_OUTMUX2, 0x180, 0x180);
+ break;
+ }
+
+ return 0;
+}
+
+static int wm8770_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8770_RESET, 0);
+}
+
+static int wm8770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec;
+ int iface, master;
+
+ codec = dai->codec;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ master = 0x100;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ master = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ iface = 0;
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0xc;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x8;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8770_IFACECTRL, 0xf, iface);
+ snd_soc_update_bits(codec, WM8770_MSTRCTRL, 0x100, master);
+
+ return 0;
+}
+
+static const int mclk_ratios[] = {
+ 128,
+ 192,
+ 256,
+ 384,
+ 512,
+ 768
+};
+
+static int wm8770_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec;
+ struct wm8770_priv *wm8770;
+ int i;
+ int iface;
+ int shift;
+ int ratio;
+
+ codec = dai->codec;
+ wm8770 = snd_soc_codec_get_drvdata(codec);
+
+ iface = 0;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x10;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x20;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x30;
+ break;
+ }
+
+ switch (substream->stream) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ i = 0;
+ shift = 4;
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ i = 2;
+ shift = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Only need to set MCLK/LRCLK ratio if we're master */
+ if (snd_soc_read(codec, WM8770_MSTRCTRL) & 0x100) {
+ for (; i < ARRAY_SIZE(mclk_ratios); ++i) {
+ ratio = wm8770->sysclk / params_rate(params);
+ if (ratio == mclk_ratios[i])
+ break;
+ }
+
+ if (i == ARRAY_SIZE(mclk_ratios)) {
+ dev_err(codec->dev,
+ "Unable to configure MCLK ratio %d/%d\n",
+ wm8770->sysclk, params_rate(params));
+ return -EINVAL;
+ }
+
+ dev_dbg(codec->dev, "MCLK is %dfs\n", mclk_ratios[i]);
+
+ snd_soc_update_bits(codec, WM8770_MSTRCTRL, 0x7 << shift,
+ i << shift);
+ }
+
+ snd_soc_update_bits(codec, WM8770_IFACECTRL, 0x30, iface);
+
+ return 0;
+}
+
+static int wm8770_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec;
+
+ codec = dai->codec;
+ return snd_soc_update_bits(codec, WM8770_DACMUTE, 0x10,
+ !!mute << 4);
+}
+
+static int wm8770_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec;
+ struct wm8770_priv *wm8770;
+
+ codec = dai->codec;
+ wm8770 = snd_soc_codec_get_drvdata(codec);
+ wm8770->sysclk = freq;
+ return 0;
+}
+
+static void wm8770_sync_cache(struct snd_soc_codec *codec)
+{
+ int i;
+ u16 *cache;
+
+ if (!codec->cache_sync)
+ return;
+
+ codec->cache_only = 0;
+ cache = codec->reg_cache;
+ for (i = 0; i < codec->driver->reg_cache_size; i++) {
+ if (i == WM8770_RESET || cache[i] == wm8770_reg_defs[i])
+ continue;
+ snd_soc_write(codec, i, cache[i]);
+ }
+ codec->cache_sync = 0;
+}
+
+static int wm8770_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+ struct wm8770_priv *wm8770;
+
+ wm8770 = snd_soc_codec_get_drvdata(codec);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8770->supplies),
+ wm8770->supplies);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+ wm8770_sync_cache(codec);
+ /* global powerup */
+ snd_soc_write(codec, WM8770_PWDNCTRL, 0);
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* global powerdown */
+ snd_soc_write(codec, WM8770_PWDNCTRL, 1);
+ regulator_bulk_disable(ARRAY_SIZE(wm8770->supplies),
+ wm8770->supplies);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8770_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8770_dai_ops = {
+ .digital_mute = wm8770_mute,
+ .hw_params = wm8770_hw_params,
+ .set_fmt = wm8770_set_fmt,
+ .set_sysclk = wm8770_set_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8770_dai = {
+ .name = "wm8770-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = WM8770_FORMATS
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = WM8770_FORMATS
+ },
+ .ops = &wm8770_dai_ops,
+ .symmetric_rates = 1
+};
+
+#ifdef CONFIG_PM
+static int wm8770_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8770_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8770_resume(struct snd_soc_codec *codec)
+{
+ wm8770_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+#else
+#define wm8770_suspend NULL
+#define wm8770_resume NULL
+#endif
+
+static int wm8770_probe(struct snd_soc_codec *codec)
+{
+ struct wm8770_priv *wm8770;
+ int ret;
+ int i;
+
+ wm8770 = snd_soc_codec_get_drvdata(codec);
+ wm8770->codec = codec;
+
+ codec->dapm.idle_bias_off = 1;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8770->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8770->supplies); i++)
+ wm8770->supplies[i].supply = wm8770_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8770->supplies),
+ wm8770->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ wm8770->disable_nb[0].notifier_call = wm8770_regulator_event_0;
+ wm8770->disable_nb[1].notifier_call = wm8770_regulator_event_1;
+ wm8770->disable_nb[2].notifier_call = wm8770_regulator_event_2;
+
+ /* This should really be moved into the regulator core */
+ for (i = 0; i < ARRAY_SIZE(wm8770->supplies); i++) {
+ ret = regulator_register_notifier(wm8770->supplies[i].consumer,
+ &wm8770->disable_nb[i]);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to register regulator notifier: %d\n",
+ ret);
+ }
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8770->supplies),
+ wm8770->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_reg_get;
+ }
+
+ ret = wm8770_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ goto err_reg_enable;
+ }
+
+ wm8770_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* latch the volume update bits */
+ snd_soc_update_bits(codec, WM8770_MSDIGVOL, 0x100, 0x100);
+ snd_soc_update_bits(codec, WM8770_MSALGVOL, 0x100, 0x100);
+ snd_soc_update_bits(codec, WM8770_VOUT1RVOL, 0x100, 0x100);
+ snd_soc_update_bits(codec, WM8770_VOUT2RVOL, 0x100, 0x100);
+ snd_soc_update_bits(codec, WM8770_VOUT3RVOL, 0x100, 0x100);
+ snd_soc_update_bits(codec, WM8770_VOUT4RVOL, 0x100, 0x100);
+ snd_soc_update_bits(codec, WM8770_DAC1RVOL, 0x100, 0x100);
+ snd_soc_update_bits(codec, WM8770_DAC2RVOL, 0x100, 0x100);
+ snd_soc_update_bits(codec, WM8770_DAC3RVOL, 0x100, 0x100);
+ snd_soc_update_bits(codec, WM8770_DAC4RVOL, 0x100, 0x100);
+
+ /* mute all DACs */
+ snd_soc_update_bits(codec, WM8770_DACMUTE, 0x10, 0x10);
+
+ snd_soc_add_controls(codec, wm8770_snd_controls,
+ ARRAY_SIZE(wm8770_snd_controls));
+ snd_soc_dapm_new_controls(&codec->dapm, wm8770_dapm_widgets,
+ ARRAY_SIZE(wm8770_dapm_widgets));
+ snd_soc_dapm_add_routes(&codec->dapm, wm8770_intercon,
+ ARRAY_SIZE(wm8770_intercon));
+ return 0;
+
+err_reg_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8770->supplies), wm8770->supplies);
+err_reg_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8770->supplies), wm8770->supplies);
+ return ret;
+}
+
+static int wm8770_remove(struct snd_soc_codec *codec)
+{
+ struct wm8770_priv *wm8770;
+ int i;
+
+ wm8770 = snd_soc_codec_get_drvdata(codec);
+ wm8770_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ for (i = 0; i < ARRAY_SIZE(wm8770->supplies); ++i)
+ regulator_unregister_notifier(wm8770->supplies[i].consumer,
+ &wm8770->disable_nb[i]);
+ regulator_bulk_free(ARRAY_SIZE(wm8770->supplies), wm8770->supplies);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8770 = {
+ .probe = wm8770_probe,
+ .remove = wm8770_remove,
+ .suspend = wm8770_suspend,
+ .resume = wm8770_resume,
+ .set_bias_level = wm8770_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8770_reg_defs),
+ .reg_word_size = sizeof (u16),
+ .reg_cache_default = wm8770_reg_defs
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8770_spi_probe(struct spi_device *spi)
+{
+ struct wm8770_priv *wm8770;
+ int ret;
+
+ wm8770 = kzalloc(sizeof(struct wm8770_priv), GFP_KERNEL);
+ if (!wm8770)
+ return -ENOMEM;
+
+ wm8770->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8770);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8770, &wm8770_dai, 1);
+ if (ret < 0)
+ kfree(wm8770);
+ return ret;
+}
+
+static int __devexit wm8770_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8770_spi_driver = {
+ .driver = {
+ .name = "wm8770",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8770_spi_probe,
+ .remove = __devexit_p(wm8770_spi_remove)
+};
+#endif
+
+static int __init wm8770_modinit(void)
+{
+ int ret = 0;
+
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8770_spi_driver);
+ if (ret) {
+ printk(KERN_ERR "Failed to register wm8770 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8770_modinit);
+
+static void __exit wm8770_exit(void)
+{
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8770_spi_driver);
+#endif
+}
+module_exit(wm8770_exit);
+
+MODULE_DESCRIPTION("ASoC WM8770 driver");
+MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8770.h b/sound/soc/codecs/wm8770.h
new file mode 100644
index 00000000..5f1b3bda
--- /dev/null
+++ b/sound/soc/codecs/wm8770.h
@@ -0,0 +1,51 @@
+/*
+ * wm8770.h -- WM8770 ASoC driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8770_H
+#define _WM8770_H
+
+/* Registers */
+#define WM8770_VOUT1LVOL 0
+#define WM8770_VOUT1RVOL 0x1
+#define WM8770_VOUT2LVOL 0x2
+#define WM8770_VOUT2RVOL 0x3
+#define WM8770_VOUT3LVOL 0x4
+#define WM8770_VOUT3RVOL 0x5
+#define WM8770_VOUT4LVOL 0x6
+#define WM8770_VOUT4RVOL 0x7
+#define WM8770_MSALGVOL 0x8
+#define WM8770_DAC1LVOL 0x9
+#define WM8770_DAC1RVOL 0xa
+#define WM8770_DAC2LVOL 0xb
+#define WM8770_DAC2RVOL 0xc
+#define WM8770_DAC3LVOL 0xd
+#define WM8770_DAC3RVOL 0xe
+#define WM8770_DAC4LVOL 0xf
+#define WM8770_DAC4RVOL 0x10
+#define WM8770_MSDIGVOL 0x11
+#define WM8770_DACPHASE 0x12
+#define WM8770_DACCTRL1 0x13
+#define WM8770_DACMUTE 0x14
+#define WM8770_DACCTRL2 0x15
+#define WM8770_IFACECTRL 0x16
+#define WM8770_MSTRCTRL 0x17
+#define WM8770_PWDNCTRL 0x18
+#define WM8770_ADCLCTRL 0x19
+#define WM8770_ADCRCTRL 0x1a
+#define WM8770_ADCMUX 0x1b
+#define WM8770_OUTMUX1 0x1c
+#define WM8770_OUTMUX2 0x1d
+#define WM8770_RESET 0x31
+
+#define WM8770_CACHEREGNUM 0x20
+
+#endif
diff --git a/sound/soc/codecs/wm8776.c b/sound/soc/codecs/wm8776.c
new file mode 100644
index 00000000..8e7953b1
--- /dev/null
+++ b/sound/soc/codecs/wm8776.c
@@ -0,0 +1,571 @@
+/*
+ * wm8776.c -- WM8776 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO: Input ALC/limiter support
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8776.h"
+
+/* codec private data */
+struct wm8776_priv {
+ enum snd_soc_control_type control_type;
+ int sysclk[2];
+};
+
+static const u16 wm8776_reg[WM8776_CACHEREGNUM] = {
+ 0x79, 0x79, 0x79, 0xff, 0xff, /* 4 */
+ 0xff, 0x00, 0x90, 0x00, 0x00, /* 9 */
+ 0x22, 0x22, 0x22, 0x08, 0xcf, /* 14 */
+ 0xcf, 0x7b, 0x00, 0x32, 0x00, /* 19 */
+ 0xa6, 0x01, 0x01
+};
+
+static int wm8776_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8776_RESET, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(hp_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -10350, 50, 1);
+
+static const struct snd_kcontrol_new wm8776_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8776_HPLVOL, WM8776_HPRVOL,
+ 0, 127, 0, hp_tlv),
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8776_DACLVOL, WM8776_DACRVOL,
+ 0, 255, 0, dac_tlv),
+SOC_SINGLE("Digital Playback ZC Switch", WM8776_DACCTRL1, 0, 1, 0),
+
+SOC_SINGLE("Deemphasis Switch", WM8776_DACCTRL2, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8776_ADCLVOL, WM8776_ADCRVOL,
+ 0, 255, 0, adc_tlv),
+SOC_DOUBLE("Capture Switch", WM8776_ADCMUX, 7, 6, 1, 1),
+SOC_DOUBLE_R("Capture ZC Switch", WM8776_ADCLVOL, WM8776_ADCRVOL, 8, 1, 0),
+SOC_SINGLE("Capture HPF Switch", WM8776_ADCIFCTRL, 8, 1, 1),
+};
+
+static const struct snd_kcontrol_new inmix_controls[] = {
+SOC_DAPM_SINGLE("AIN1 Switch", WM8776_ADCMUX, 0, 1, 0),
+SOC_DAPM_SINGLE("AIN2 Switch", WM8776_ADCMUX, 1, 1, 0),
+SOC_DAPM_SINGLE("AIN3 Switch", WM8776_ADCMUX, 2, 1, 0),
+SOC_DAPM_SINGLE("AIN4 Switch", WM8776_ADCMUX, 3, 1, 0),
+SOC_DAPM_SINGLE("AIN5 Switch", WM8776_ADCMUX, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new outmix_controls[] = {
+SOC_DAPM_SINGLE("DAC Switch", WM8776_OUTMUX, 0, 1, 0),
+SOC_DAPM_SINGLE("AUX Switch", WM8776_OUTMUX, 1, 1, 0),
+SOC_DAPM_SINGLE("Bypass Switch", WM8776_OUTMUX, 2, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8776_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("AUX"),
+
+SND_SOC_DAPM_INPUT("AIN1"),
+SND_SOC_DAPM_INPUT("AIN2"),
+SND_SOC_DAPM_INPUT("AIN3"),
+SND_SOC_DAPM_INPUT("AIN4"),
+SND_SOC_DAPM_INPUT("AIN5"),
+
+SND_SOC_DAPM_MIXER("Input Mixer", WM8776_PWRDOWN, 6, 1,
+ inmix_controls, ARRAY_SIZE(inmix_controls)),
+
+SND_SOC_DAPM_ADC("ADC", "Capture", WM8776_PWRDOWN, 1, 1),
+SND_SOC_DAPM_DAC("DAC", "Playback", WM8776_PWRDOWN, 2, 1),
+
+SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0,
+ outmix_controls, ARRAY_SIZE(outmix_controls)),
+
+SND_SOC_DAPM_PGA("Headphone PGA", WM8776_PWRDOWN, 3, 1, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("VOUT"),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+};
+
+static const struct snd_soc_dapm_route routes[] = {
+ { "Input Mixer", "AIN1 Switch", "AIN1" },
+ { "Input Mixer", "AIN2 Switch", "AIN2" },
+ { "Input Mixer", "AIN3 Switch", "AIN3" },
+ { "Input Mixer", "AIN4 Switch", "AIN4" },
+ { "Input Mixer", "AIN5 Switch", "AIN5" },
+
+ { "ADC", NULL, "Input Mixer" },
+
+ { "Output Mixer", "DAC Switch", "DAC" },
+ { "Output Mixer", "AUX Switch", "AUX" },
+ { "Output Mixer", "Bypass Switch", "Input Mixer" },
+
+ { "VOUT", NULL, "Output Mixer" },
+
+ { "Headphone PGA", NULL, "Output Mixer" },
+
+ { "HPOUTL", NULL, "Headphone PGA" },
+ { "HPOUTR", NULL, "Headphone PGA" },
+};
+
+static int wm8776_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int reg, iface, master;
+
+ switch (dai->driver->id) {
+ case WM8776_DAI_DAC:
+ reg = WM8776_DACIFCTRL;
+ master = 0x80;
+ break;
+ case WM8776_DAI_ADC:
+ reg = WM8776_ADCIFCTRL;
+ master = 0x100;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ iface = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ master = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x00c;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x008;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x004;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Finally, write out the values */
+ snd_soc_update_bits(codec, reg, 0xf, iface);
+ snd_soc_update_bits(codec, WM8776_MSTRCTRL, 0x180, master);
+
+ return 0;
+}
+
+static int mclk_ratios[] = {
+ 128,
+ 192,
+ 256,
+ 384,
+ 512,
+ 768,
+};
+
+static int wm8776_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec);
+ int iface_reg, iface;
+ int ratio_shift, master;
+ int i;
+
+ iface = 0;
+
+ switch (dai->driver->id) {
+ case WM8776_DAI_DAC:
+ iface_reg = WM8776_DACIFCTRL;
+ master = 0x80;
+ ratio_shift = 4;
+ break;
+ case WM8776_DAI_ADC:
+ iface_reg = WM8776_ADCIFCTRL;
+ master = 0x100;
+ ratio_shift = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+
+ /* Set word length */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x10;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x20;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x30;
+ break;
+ }
+
+ /* Only need to set MCLK/LRCLK ratio if we're master */
+ if (snd_soc_read(codec, WM8776_MSTRCTRL) & master) {
+ for (i = 0; i < ARRAY_SIZE(mclk_ratios); i++) {
+ if (wm8776->sysclk[dai->driver->id] / params_rate(params)
+ == mclk_ratios[i])
+ break;
+ }
+
+ if (i == ARRAY_SIZE(mclk_ratios)) {
+ dev_err(codec->dev,
+ "Unable to configure MCLK ratio %d/%d\n",
+ wm8776->sysclk[dai->driver->id], params_rate(params));
+ return -EINVAL;
+ }
+
+ dev_dbg(codec->dev, "MCLK is %dfs\n", mclk_ratios[i]);
+
+ snd_soc_update_bits(codec, WM8776_MSTRCTRL,
+ 0x7 << ratio_shift, i << ratio_shift);
+ } else {
+ dev_dbg(codec->dev, "DAI in slave mode\n");
+ }
+
+ snd_soc_update_bits(codec, iface_reg, 0x30, iface);
+
+ return 0;
+}
+
+static int wm8776_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+
+ return snd_soc_write(codec, WM8776_DACMUTE, !!mute);
+}
+
+static int wm8776_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec);
+
+ BUG_ON(dai->driver->id >= ARRAY_SIZE(wm8776->sysclk));
+
+ wm8776->sysclk[dai->driver->id] = freq;
+
+ return 0;
+}
+
+static int wm8776_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Disable the global powerdown; DAPM does the rest */
+ snd_soc_update_bits(codec, WM8776_PWRDOWN, 1, 0);
+ }
+
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, WM8776_PWRDOWN, 1, 1);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8776_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
+ SNDRV_PCM_RATE_96000)
+
+
+#define WM8776_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8776_dac_ops = {
+ .digital_mute = wm8776_mute,
+ .hw_params = wm8776_hw_params,
+ .set_fmt = wm8776_set_fmt,
+ .set_sysclk = wm8776_set_sysclk,
+};
+
+static struct snd_soc_dai_ops wm8776_adc_ops = {
+ .hw_params = wm8776_hw_params,
+ .set_fmt = wm8776_set_fmt,
+ .set_sysclk = wm8776_set_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8776_dai[] = {
+ {
+ .name = "wm8776-hifi-playback",
+ .id = WM8776_DAI_DAC,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8776_RATES,
+ .formats = WM8776_FORMATS,
+ },
+ .ops = &wm8776_dac_ops,
+ },
+ {
+ .name = "wm8776-hifi-capture",
+ .id = WM8776_DAI_ADC,
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8776_RATES,
+ .formats = WM8776_FORMATS,
+ },
+ .ops = &wm8776_adc_ops,
+ },
+};
+
+#ifdef CONFIG_PM
+static int wm8776_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8776_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8776_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8776_reg); i++) {
+ if (cache[i] == wm8776_reg[i])
+ continue;
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define wm8776_suspend NULL
+#define wm8776_resume NULL
+#endif
+
+static int wm8776_probe(struct snd_soc_codec *codec)
+{
+ struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret = 0;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8776->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8776_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ return ret;
+ }
+
+ wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Latch the update bits; right channel only since we always
+ * update both. */
+ snd_soc_update_bits(codec, WM8776_HPRVOL, 0x100, 0x100);
+ snd_soc_update_bits(codec, WM8776_DACRVOL, 0x100, 0x100);
+
+ snd_soc_add_controls(codec, wm8776_snd_controls,
+ ARRAY_SIZE(wm8776_snd_controls));
+ snd_soc_dapm_new_controls(dapm, wm8776_dapm_widgets,
+ ARRAY_SIZE(wm8776_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, routes, ARRAY_SIZE(routes));
+
+ return ret;
+}
+
+/* power down chip */
+static int wm8776_remove(struct snd_soc_codec *codec)
+{
+ wm8776_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8776 = {
+ .probe = wm8776_probe,
+ .remove = wm8776_remove,
+ .suspend = wm8776_suspend,
+ .resume = wm8776_resume,
+ .set_bias_level = wm8776_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8776_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8776_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8776_spi_probe(struct spi_device *spi)
+{
+ struct wm8776_priv *wm8776;
+ int ret;
+
+ wm8776 = kzalloc(sizeof(struct wm8776_priv), GFP_KERNEL);
+ if (wm8776 == NULL)
+ return -ENOMEM;
+
+ wm8776->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8776);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8776, wm8776_dai, ARRAY_SIZE(wm8776_dai));
+ if (ret < 0)
+ kfree(wm8776);
+ return ret;
+}
+
+static int __devexit wm8776_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8776_spi_driver = {
+ .driver = {
+ .name = "wm8776-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8776_spi_probe,
+ .remove = __devexit_p(wm8776_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8776_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8776_priv *wm8776;
+ int ret;
+
+ wm8776 = kzalloc(sizeof(struct wm8776_priv), GFP_KERNEL);
+ if (wm8776 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8776);
+ wm8776->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8776, wm8776_dai, ARRAY_SIZE(wm8776_dai));
+ if (ret < 0)
+ kfree(wm8776);
+ return ret;
+}
+
+static __devexit int wm8776_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8776_i2c_id[] = {
+ { "wm8776", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8776_i2c_id);
+
+static struct i2c_driver wm8776_i2c_driver = {
+ .driver = {
+ .name = "wm8776-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8776_i2c_probe,
+ .remove = __devexit_p(wm8776_i2c_remove),
+ .id_table = wm8776_i2c_id,
+};
+#endif
+
+static int __init wm8776_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8776_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8776 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8776_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8776 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8776_modinit);
+
+static void __exit wm8776_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8776_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8776_spi_driver);
+#endif
+}
+module_exit(wm8776_exit);
+
+MODULE_DESCRIPTION("ASoC WM8776 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8776.h b/sound/soc/codecs/wm8776.h
new file mode 100644
index 00000000..4cf1c8e0
--- /dev/null
+++ b/sound/soc/codecs/wm8776.h
@@ -0,0 +1,48 @@
+/*
+ * wm8776.h -- WM8776 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8776_H
+#define _WM8776_H
+
+/* Registers */
+
+#define WM8776_HPLVOL 0x00
+#define WM8776_HPRVOL 0x01
+#define WM8776_HPMASTER 0x02
+#define WM8776_DACLVOL 0x03
+#define WM8776_DACRVOL 0x04
+#define WM8776_DACMASTER 0x05
+#define WM8776_PHASESWAP 0x06
+#define WM8776_DACCTRL1 0x07
+#define WM8776_DACMUTE 0x08
+#define WM8776_DACCTRL2 0x09
+#define WM8776_DACIFCTRL 0x0a
+#define WM8776_ADCIFCTRL 0x0b
+#define WM8776_MSTRCTRL 0x0c
+#define WM8776_PWRDOWN 0x0d
+#define WM8776_ADCLVOL 0x0e
+#define WM8776_ADCRVOL 0x0f
+#define WM8776_ALCCTRL1 0x10
+#define WM8776_ALCCTRL2 0x11
+#define WM8776_ALCCTRL3 0x12
+#define WM8776_NOISEGATE 0x13
+#define WM8776_LIMITER 0x14
+#define WM8776_ADCMUX 0x15
+#define WM8776_OUTMUX 0x16
+#define WM8776_RESET 0x17
+
+#define WM8776_CACHEREGNUM 0x17
+
+#define WM8776_DAI_DAC 0
+#define WM8776_DAI_ADC 1
+
+#endif
diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c
new file mode 100644
index 00000000..9a5e67c5
--- /dev/null
+++ b/sound/soc/codecs/wm8804.c
@@ -0,0 +1,837 @@
+/*
+ * wm8804.c -- WM8804 S/PDIF transceiver driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8804.h"
+
+#define WM8804_NUM_SUPPLIES 2
+static const char *wm8804_supply_names[WM8804_NUM_SUPPLIES] = {
+ "PVDD",
+ "DVDD"
+};
+
+static const u8 wm8804_reg_defs[] = {
+ 0x05, /* R0 - RST/DEVID1 */
+ 0x88, /* R1 - DEVID2 */
+ 0x04, /* R2 - DEVREV */
+ 0x21, /* R3 - PLL1 */
+ 0xFD, /* R4 - PLL2 */
+ 0x36, /* R5 - PLL3 */
+ 0x07, /* R6 - PLL4 */
+ 0x16, /* R7 - PLL5 */
+ 0x18, /* R8 - PLL6 */
+ 0xFF, /* R9 - SPDMODE */
+ 0x00, /* R10 - INTMASK */
+ 0x00, /* R11 - INTSTAT */
+ 0x00, /* R12 - SPDSTAT */
+ 0x00, /* R13 - RXCHAN1 */
+ 0x00, /* R14 - RXCHAN2 */
+ 0x00, /* R15 - RXCHAN3 */
+ 0x00, /* R16 - RXCHAN4 */
+ 0x00, /* R17 - RXCHAN5 */
+ 0x00, /* R18 - SPDTX1 */
+ 0x00, /* R19 - SPDTX2 */
+ 0x00, /* R20 - SPDTX3 */
+ 0x71, /* R21 - SPDTX4 */
+ 0x0B, /* R22 - SPDTX5 */
+ 0x70, /* R23 - GPO0 */
+ 0x57, /* R24 - GPO1 */
+ 0x00, /* R25 */
+ 0x42, /* R26 - GPO2 */
+ 0x06, /* R27 - AIFTX */
+ 0x06, /* R28 - AIFRX */
+ 0x80, /* R29 - SPDRX1 */
+ 0x07, /* R30 - PWRDN */
+};
+
+struct wm8804_priv {
+ enum snd_soc_control_type control_type;
+ struct regulator_bulk_data supplies[WM8804_NUM_SUPPLIES];
+ struct notifier_block disable_nb[WM8804_NUM_SUPPLIES];
+ struct snd_soc_codec *codec;
+};
+
+static int txsrc_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+
+static int txsrc_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+
+/*
+ * We can't use the same notifier block for more than one supply and
+ * there's no way I can see to get from a callback to the caller
+ * except container_of().
+ */
+#define WM8804_REGULATOR_EVENT(n) \
+static int wm8804_regulator_event_##n(struct notifier_block *nb, \
+ unsigned long event, void *data) \
+{ \
+ struct wm8804_priv *wm8804 = container_of(nb, struct wm8804_priv, \
+ disable_nb[n]); \
+ if (event & REGULATOR_EVENT_DISABLE) { \
+ wm8804->codec->cache_sync = 1; \
+ } \
+ return 0; \
+}
+
+WM8804_REGULATOR_EVENT(0)
+WM8804_REGULATOR_EVENT(1)
+
+static const char *txsrc_text[] = { "S/PDIF RX", "AIF" };
+static const SOC_ENUM_SINGLE_EXT_DECL(txsrc, txsrc_text);
+
+static const struct snd_kcontrol_new wm8804_snd_controls[] = {
+ SOC_ENUM_EXT("Input Source", txsrc, txsrc_get, txsrc_put),
+ SOC_SINGLE("TX Playback Switch", WM8804_PWRDN, 2, 1, 1),
+ SOC_SINGLE("AIF Playback Switch", WM8804_PWRDN, 4, 1, 1)
+};
+
+static int txsrc_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec;
+ unsigned int src;
+
+ codec = snd_kcontrol_chip(kcontrol);
+ src = snd_soc_read(codec, WM8804_SPDTX4);
+ if (src & 0x40)
+ ucontrol->value.integer.value[0] = 1;
+ else
+ ucontrol->value.integer.value[0] = 0;
+
+ return 0;
+}
+
+static int txsrc_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec;
+ unsigned int src, txpwr;
+
+ codec = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.integer.value[0] != 0
+ && ucontrol->value.integer.value[0] != 1)
+ return -EINVAL;
+
+ src = snd_soc_read(codec, WM8804_SPDTX4);
+ switch ((src & 0x40) >> 6) {
+ case 0:
+ if (!ucontrol->value.integer.value[0])
+ return 0;
+ break;
+ case 1:
+ if (ucontrol->value.integer.value[1])
+ return 0;
+ break;
+ }
+
+ /* save the current power state of the transmitter */
+ txpwr = snd_soc_read(codec, WM8804_PWRDN) & 0x4;
+ /* power down the transmitter */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x4, 0x4);
+ /* set the tx source */
+ snd_soc_update_bits(codec, WM8804_SPDTX4, 0x40,
+ ucontrol->value.integer.value[0] << 6);
+
+ if (ucontrol->value.integer.value[0]) {
+ /* power down the receiver */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x2, 0x2);
+ /* power up the AIF */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x10, 0);
+ } else {
+ /* don't power down the AIF -- may be used as an output */
+ /* power up the receiver */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x2, 0);
+ }
+
+ /* restore the transmitter's configuration */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x4, txpwr);
+
+ return 0;
+}
+
+static int wm8804_volatile(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case WM8804_RST_DEVID1:
+ case WM8804_DEVID2:
+ case WM8804_DEVREV:
+ case WM8804_INTSTAT:
+ case WM8804_SPDSTAT:
+ case WM8804_RXCHAN1:
+ case WM8804_RXCHAN2:
+ case WM8804_RXCHAN3:
+ case WM8804_RXCHAN4:
+ case WM8804_RXCHAN5:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int wm8804_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8804_RST_DEVID1, 0x0);
+}
+
+static int wm8804_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec;
+ u16 format, master, bcp, lrp;
+
+ codec = dai->codec;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ format = 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ format = 0x0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ format = 0x1;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ format = 0x3;
+ break;
+ default:
+ dev_err(dai->dev, "Unknown dai format\n");
+ return -EINVAL;
+ }
+
+ /* set data format */
+ snd_soc_update_bits(codec, WM8804_AIFTX, 0x3, format);
+ snd_soc_update_bits(codec, WM8804_AIFRX, 0x3, format);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ master = 0;
+ break;
+ default:
+ dev_err(dai->dev, "Unknown master/slave configuration\n");
+ return -EINVAL;
+ }
+
+ /* set master/slave mode */
+ snd_soc_update_bits(codec, WM8804_AIFRX, 0x40, master << 6);
+
+ bcp = lrp = 0;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ bcp = lrp = 1;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ bcp = 1;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ lrp = 1;
+ break;
+ default:
+ dev_err(dai->dev, "Unknown polarity configuration\n");
+ return -EINVAL;
+ }
+
+ /* set frame inversion */
+ snd_soc_update_bits(codec, WM8804_AIFTX, 0x10 | 0x20,
+ (bcp << 4) | (lrp << 5));
+ snd_soc_update_bits(codec, WM8804_AIFRX, 0x10 | 0x20,
+ (bcp << 4) | (lrp << 5));
+ return 0;
+}
+
+static int wm8804_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec;
+ u16 blen;
+
+ codec = dai->codec;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ blen = 0x0;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ blen = 0x1;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ blen = 0x2;
+ break;
+ default:
+ dev_err(dai->dev, "Unsupported word length: %u\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ /* set word length */
+ snd_soc_update_bits(codec, WM8804_AIFTX, 0xc, blen << 2);
+ snd_soc_update_bits(codec, WM8804_AIFRX, 0xc, blen << 2);
+
+ return 0;
+}
+
+struct pll_div {
+ u32 prescale:1;
+ u32 mclkdiv:1;
+ u32 freqmode:2;
+ u32 n:4;
+ u32 k:22;
+};
+
+/* PLL rate to output rate divisions */
+static struct {
+ unsigned int div;
+ unsigned int freqmode;
+ unsigned int mclkdiv;
+} post_table[] = {
+ { 2, 0, 0 },
+ { 4, 0, 1 },
+ { 4, 1, 0 },
+ { 8, 1, 1 },
+ { 8, 2, 0 },
+ { 16, 2, 1 },
+ { 12, 3, 0 },
+ { 24, 3, 1 }
+};
+
+#define FIXED_PLL_SIZE ((1ULL << 22) * 10)
+static int pll_factors(struct pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned long int K, Ndiv, Nmod, tmp;
+ int i;
+
+ /*
+ * Scale the output frequency up; the PLL should run in the
+ * region of 90-100MHz.
+ */
+ for (i = 0; i < ARRAY_SIZE(post_table); i++) {
+ tmp = target * post_table[i].div;
+ if (tmp >= 90000000 && tmp <= 100000000) {
+ pll_div->freqmode = post_table[i].freqmode;
+ pll_div->mclkdiv = post_table[i].mclkdiv;
+ target *= post_table[i].div;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(post_table)) {
+ pr_err("%s: Unable to scale output frequency: %uHz\n",
+ __func__, target);
+ return -EINVAL;
+ }
+
+ pll_div->prescale = 0;
+ Ndiv = target / source;
+ if (Ndiv < 5) {
+ source >>= 1;
+ pll_div->prescale = 1;
+ Ndiv = target / source;
+ }
+
+ if (Ndiv < 5 || Ndiv > 13) {
+ pr_err("%s: WM8804 N value is not within the recommended range: %lu\n",
+ __func__, Ndiv);
+ return -EINVAL;
+ }
+ pll_div->n = Ndiv;
+
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (u64)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xffffffff;
+ if ((K % 10) >= 5)
+ K += 5;
+ K /= 10;
+ pll_div->k = K;
+
+ return 0;
+}
+
+static int wm8804_set_pll(struct snd_soc_dai *dai, int pll_id,
+ int source, unsigned int freq_in,
+ unsigned int freq_out)
+{
+ struct snd_soc_codec *codec;
+
+ codec = dai->codec;
+ if (!freq_in || !freq_out) {
+ /* disable the PLL */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0x1);
+ return 0;
+ } else {
+ int ret;
+ struct pll_div pll_div;
+
+ ret = pll_factors(&pll_div, freq_out, freq_in);
+ if (ret)
+ return ret;
+
+ /* power down the PLL before reprogramming it */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0x1);
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ /* set PLLN and PRESCALE */
+ snd_soc_update_bits(codec, WM8804_PLL4, 0xf | 0x10,
+ pll_div.n | (pll_div.prescale << 4));
+ /* set mclkdiv and freqmode */
+ snd_soc_update_bits(codec, WM8804_PLL5, 0x3 | 0x8,
+ pll_div.freqmode | (pll_div.mclkdiv << 3));
+ /* set PLLK */
+ snd_soc_write(codec, WM8804_PLL1, pll_div.k & 0xff);
+ snd_soc_write(codec, WM8804_PLL2, (pll_div.k >> 8) & 0xff);
+ snd_soc_write(codec, WM8804_PLL3, pll_div.k >> 16);
+
+ /* power up the PLL */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0);
+ }
+
+ return 0;
+}
+
+static int wm8804_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec;
+
+ codec = dai->codec;
+
+ switch (clk_id) {
+ case WM8804_TX_CLKSRC_MCLK:
+ if ((freq >= 10000000 && freq <= 14400000)
+ || (freq >= 16280000 && freq <= 27000000))
+ snd_soc_update_bits(codec, WM8804_PLL6, 0x80, 0x80);
+ else {
+ dev_err(dai->dev, "OSCCLOCK is not within the "
+ "recommended range: %uHz\n", freq);
+ return -EINVAL;
+ }
+ break;
+ case WM8804_TX_CLKSRC_PLL:
+ snd_soc_update_bits(codec, WM8804_PLL6, 0x80, 0);
+ break;
+ case WM8804_CLKOUT_SRC_CLK1:
+ snd_soc_update_bits(codec, WM8804_PLL6, 0x8, 0);
+ break;
+ case WM8804_CLKOUT_SRC_OSCCLK:
+ snd_soc_update_bits(codec, WM8804_PLL6, 0x8, 0x8);
+ break;
+ default:
+ dev_err(dai->dev, "Unknown clock source: %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8804_set_clkdiv(struct snd_soc_dai *dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec;
+
+ codec = dai->codec;
+ switch (div_id) {
+ case WM8804_CLKOUT_DIV:
+ snd_soc_update_bits(codec, WM8804_PLL5, 0x30,
+ (div & 0x3) << 4);
+ break;
+ default:
+ dev_err(dai->dev, "Unknown clock divider: %d\n", div_id);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void wm8804_sync_cache(struct snd_soc_codec *codec)
+{
+ short i;
+ u8 *cache;
+
+ if (!codec->cache_sync)
+ return;
+
+ codec->cache_only = 0;
+ cache = codec->reg_cache;
+ for (i = 0; i < codec->driver->reg_cache_size; i++) {
+ if (i == WM8804_RST_DEVID1 || cache[i] == wm8804_reg_defs[i])
+ continue;
+ snd_soc_write(codec, i, cache[i]);
+ }
+ codec->cache_sync = 0;
+}
+
+static int wm8804_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+ struct wm8804_priv *wm8804;
+
+ wm8804 = snd_soc_codec_get_drvdata(codec);
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ /* power up the OSC and the PLL */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies),
+ wm8804->supplies);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+ wm8804_sync_cache(codec);
+ }
+ /* power down the OSC and the PLL */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0x9);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* power down the OSC and the PLL */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0x9);
+ regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies),
+ wm8804->supplies);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm8804_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8804_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8804_resume(struct snd_soc_codec *codec)
+{
+ wm8804_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+#else
+#define wm8804_suspend NULL
+#define wm8804_resume NULL
+#endif
+
+static int wm8804_remove(struct snd_soc_codec *codec)
+{
+ struct wm8804_priv *wm8804;
+ int i;
+
+ wm8804 = snd_soc_codec_get_drvdata(codec);
+ wm8804_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ for (i = 0; i < ARRAY_SIZE(wm8804->supplies); ++i)
+ regulator_unregister_notifier(wm8804->supplies[i].consumer,
+ &wm8804->disable_nb[i]);
+ regulator_bulk_free(ARRAY_SIZE(wm8804->supplies), wm8804->supplies);
+ return 0;
+}
+
+static int wm8804_probe(struct snd_soc_codec *codec)
+{
+ struct wm8804_priv *wm8804;
+ int i, id1, id2, ret;
+
+ wm8804 = snd_soc_codec_get_drvdata(codec);
+ wm8804->codec = codec;
+
+ codec->dapm.idle_bias_off = 1;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, wm8804->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache i/o: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++)
+ wm8804->supplies[i].supply = wm8804_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8804->supplies),
+ wm8804->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ wm8804->disable_nb[0].notifier_call = wm8804_regulator_event_0;
+ wm8804->disable_nb[1].notifier_call = wm8804_regulator_event_1;
+
+ /* This should really be moved into the regulator core */
+ for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++) {
+ ret = regulator_register_notifier(wm8804->supplies[i].consumer,
+ &wm8804->disable_nb[i]);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to register regulator notifier: %d\n",
+ ret);
+ }
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies),
+ wm8804->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_reg_get;
+ }
+
+ id1 = snd_soc_read(codec, WM8804_RST_DEVID1);
+ if (id1 < 0) {
+ dev_err(codec->dev, "Failed to read device ID: %d\n", id1);
+ ret = id1;
+ goto err_reg_enable;
+ }
+
+ id2 = snd_soc_read(codec, WM8804_DEVID2);
+ if (id2 < 0) {
+ dev_err(codec->dev, "Failed to read device ID: %d\n", id2);
+ ret = id2;
+ goto err_reg_enable;
+ }
+
+ id2 = (id2 << 8) | id1;
+
+ if (id2 != ((wm8804_reg_defs[WM8804_DEVID2] << 8)
+ | wm8804_reg_defs[WM8804_RST_DEVID1])) {
+ dev_err(codec->dev, "Invalid device ID: %#x\n", id2);
+ ret = -EINVAL;
+ goto err_reg_enable;
+ }
+
+ ret = snd_soc_read(codec, WM8804_DEVREV);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read device revision: %d\n",
+ ret);
+ goto err_reg_enable;
+ }
+ dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+ ret = wm8804_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ goto err_reg_enable;
+ }
+
+ wm8804_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_add_controls(codec, wm8804_snd_controls,
+ ARRAY_SIZE(wm8804_snd_controls));
+ return 0;
+
+err_reg_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), wm8804->supplies);
+err_reg_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8804->supplies), wm8804->supplies);
+ return ret;
+}
+
+static struct snd_soc_dai_ops wm8804_dai_ops = {
+ .hw_params = wm8804_hw_params,
+ .set_fmt = wm8804_set_fmt,
+ .set_sysclk = wm8804_set_sysclk,
+ .set_clkdiv = wm8804_set_clkdiv,
+ .set_pll = wm8804_set_pll
+};
+
+#define WM8804_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+#define WM8804_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
+
+static struct snd_soc_dai_driver wm8804_dai = {
+ .name = "wm8804-spdif",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8804_RATES,
+ .formats = WM8804_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8804_RATES,
+ .formats = WM8804_FORMATS,
+ },
+ .ops = &wm8804_dai_ops,
+ .symmetric_rates = 1
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8804 = {
+ .probe = wm8804_probe,
+ .remove = wm8804_remove,
+ .suspend = wm8804_suspend,
+ .resume = wm8804_resume,
+ .set_bias_level = wm8804_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8804_reg_defs),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = wm8804_reg_defs,
+ .volatile_register = wm8804_volatile
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8804_spi_probe(struct spi_device *spi)
+{
+ struct wm8804_priv *wm8804;
+ int ret;
+
+ wm8804 = kzalloc(sizeof *wm8804, GFP_KERNEL);
+ if (!wm8804)
+ return -ENOMEM;
+
+ wm8804->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8804);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8804, &wm8804_dai, 1);
+ if (ret < 0)
+ kfree(wm8804);
+ return ret;
+}
+
+static int __devexit wm8804_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8804_spi_driver = {
+ .driver = {
+ .name = "wm8804",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8804_spi_probe,
+ .remove = __devexit_p(wm8804_spi_remove)
+};
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8804_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8804_priv *wm8804;
+ int ret;
+
+ wm8804 = kzalloc(sizeof *wm8804, GFP_KERNEL);
+ if (!wm8804)
+ return -ENOMEM;
+
+ wm8804->control_type = SND_SOC_I2C;
+ i2c_set_clientdata(i2c, wm8804);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8804, &wm8804_dai, 1);
+ if (ret < 0)
+ kfree(wm8804);
+ return ret;
+}
+
+static __devexit int wm8804_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8804_i2c_id[] = {
+ { "wm8804", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8804_i2c_id);
+
+static struct i2c_driver wm8804_i2c_driver = {
+ .driver = {
+ .name = "wm8804",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8804_i2c_probe,
+ .remove = __devexit_p(wm8804_i2c_remove),
+ .id_table = wm8804_i2c_id
+};
+#endif
+
+static int __init wm8804_modinit(void)
+{
+ int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8804_i2c_driver);
+ if (ret) {
+ printk(KERN_ERR "Failed to register wm8804 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8804_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8804 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8804_modinit);
+
+static void __exit wm8804_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8804_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8804_spi_driver);
+#endif
+}
+module_exit(wm8804_exit);
+
+MODULE_DESCRIPTION("ASoC WM8804 driver");
+MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8804.h b/sound/soc/codecs/wm8804.h
new file mode 100644
index 00000000..8ec14f55
--- /dev/null
+++ b/sound/soc/codecs/wm8804.h
@@ -0,0 +1,61 @@
+/*
+ * wm8804.h -- WM8804 S/PDIF transceiver driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8804_H
+#define _WM8804_H
+
+/*
+ * Register values.
+ */
+#define WM8804_RST_DEVID1 0x00
+#define WM8804_DEVID2 0x01
+#define WM8804_DEVREV 0x02
+#define WM8804_PLL1 0x03
+#define WM8804_PLL2 0x04
+#define WM8804_PLL3 0x05
+#define WM8804_PLL4 0x06
+#define WM8804_PLL5 0x07
+#define WM8804_PLL6 0x08
+#define WM8804_SPDMODE 0x09
+#define WM8804_INTMASK 0x0A
+#define WM8804_INTSTAT 0x0B
+#define WM8804_SPDSTAT 0x0C
+#define WM8804_RXCHAN1 0x0D
+#define WM8804_RXCHAN2 0x0E
+#define WM8804_RXCHAN3 0x0F
+#define WM8804_RXCHAN4 0x10
+#define WM8804_RXCHAN5 0x11
+#define WM8804_SPDTX1 0x12
+#define WM8804_SPDTX2 0x13
+#define WM8804_SPDTX3 0x14
+#define WM8804_SPDTX4 0x15
+#define WM8804_SPDTX5 0x16
+#define WM8804_GPO0 0x17
+#define WM8804_GPO1 0x18
+#define WM8804_GPO2 0x1A
+#define WM8804_AIFTX 0x1B
+#define WM8804_AIFRX 0x1C
+#define WM8804_SPDRX1 0x1D
+#define WM8804_PWRDN 0x1E
+
+#define WM8804_REGISTER_COUNT 30
+#define WM8804_MAX_REGISTER 0x1E
+
+#define WM8804_TX_CLKSRC_MCLK 1
+#define WM8804_TX_CLKSRC_PLL 2
+
+#define WM8804_CLKOUT_SRC_CLK1 3
+#define WM8804_CLKOUT_SRC_OSCCLK 4
+
+#define WM8804_CLKOUT_DIV 1
+
+#endif /* _WM8804_H */
diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c
new file mode 100644
index 00000000..449ea09a
--- /dev/null
+++ b/sound/soc/codecs/wm8900.c
@@ -0,0 +1,1373 @@
+/*
+ * wm8900.c -- WM8900 ALSA Soc Audio driver
+ *
+ * Copyright 2007, 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO:
+ * - Tristating.
+ * - TDM.
+ * - Jack detect.
+ * - FLL source configuration, currently only MCLK is supported.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8900.h"
+
+/* WM8900 register space */
+#define WM8900_REG_RESET 0x0
+#define WM8900_REG_ID 0x0
+#define WM8900_REG_POWER1 0x1
+#define WM8900_REG_POWER2 0x2
+#define WM8900_REG_POWER3 0x3
+#define WM8900_REG_AUDIO1 0x4
+#define WM8900_REG_AUDIO2 0x5
+#define WM8900_REG_CLOCKING1 0x6
+#define WM8900_REG_CLOCKING2 0x7
+#define WM8900_REG_AUDIO3 0x8
+#define WM8900_REG_AUDIO4 0x9
+#define WM8900_REG_DACCTRL 0xa
+#define WM8900_REG_LDAC_DV 0xb
+#define WM8900_REG_RDAC_DV 0xc
+#define WM8900_REG_SIDETONE 0xd
+#define WM8900_REG_ADCCTRL 0xe
+#define WM8900_REG_LADC_DV 0xf
+#define WM8900_REG_RADC_DV 0x10
+#define WM8900_REG_GPIO 0x12
+#define WM8900_REG_INCTL 0x15
+#define WM8900_REG_LINVOL 0x16
+#define WM8900_REG_RINVOL 0x17
+#define WM8900_REG_INBOOSTMIX1 0x18
+#define WM8900_REG_INBOOSTMIX2 0x19
+#define WM8900_REG_ADCPATH 0x1a
+#define WM8900_REG_AUXBOOST 0x1b
+#define WM8900_REG_ADDCTL 0x1e
+#define WM8900_REG_FLLCTL1 0x24
+#define WM8900_REG_FLLCTL2 0x25
+#define WM8900_REG_FLLCTL3 0x26
+#define WM8900_REG_FLLCTL4 0x27
+#define WM8900_REG_FLLCTL5 0x28
+#define WM8900_REG_FLLCTL6 0x29
+#define WM8900_REG_LOUTMIXCTL1 0x2c
+#define WM8900_REG_ROUTMIXCTL1 0x2d
+#define WM8900_REG_BYPASS1 0x2e
+#define WM8900_REG_BYPASS2 0x2f
+#define WM8900_REG_AUXOUT_CTL 0x30
+#define WM8900_REG_LOUT1CTL 0x33
+#define WM8900_REG_ROUT1CTL 0x34
+#define WM8900_REG_LOUT2CTL 0x35
+#define WM8900_REG_ROUT2CTL 0x36
+#define WM8900_REG_HPCTL1 0x3a
+#define WM8900_REG_OUTBIASCTL 0x73
+
+#define WM8900_MAXREG 0x80
+
+#define WM8900_REG_ADDCTL_OUT1_DIS 0x80
+#define WM8900_REG_ADDCTL_OUT2_DIS 0x40
+#define WM8900_REG_ADDCTL_VMID_DIS 0x20
+#define WM8900_REG_ADDCTL_BIAS_SRC 0x10
+#define WM8900_REG_ADDCTL_VMID_SOFTST 0x04
+#define WM8900_REG_ADDCTL_TEMP_SD 0x02
+
+#define WM8900_REG_GPIO_TEMP_ENA 0x2
+
+#define WM8900_REG_POWER1_STARTUP_BIAS_ENA 0x0100
+#define WM8900_REG_POWER1_BIAS_ENA 0x0008
+#define WM8900_REG_POWER1_VMID_BUF_ENA 0x0004
+#define WM8900_REG_POWER1_FLL_ENA 0x0040
+
+#define WM8900_REG_POWER2_SYSCLK_ENA 0x8000
+#define WM8900_REG_POWER2_ADCL_ENA 0x0002
+#define WM8900_REG_POWER2_ADCR_ENA 0x0001
+
+#define WM8900_REG_POWER3_DACL_ENA 0x0002
+#define WM8900_REG_POWER3_DACR_ENA 0x0001
+
+#define WM8900_REG_AUDIO1_AIF_FMT_MASK 0x0018
+#define WM8900_REG_AUDIO1_LRCLK_INV 0x0080
+#define WM8900_REG_AUDIO1_BCLK_INV 0x0100
+
+#define WM8900_REG_CLOCKING1_BCLK_DIR 0x1
+#define WM8900_REG_CLOCKING1_MCLK_SRC 0x100
+#define WM8900_REG_CLOCKING1_BCLK_MASK (~0x01e)
+#define WM8900_REG_CLOCKING1_OPCLK_MASK (~0x7000)
+
+#define WM8900_REG_CLOCKING2_ADC_CLKDIV 0xe0
+#define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c
+
+#define WM8900_REG_DACCTRL_MUTE 0x004
+#define WM8900_REG_DACCTRL_DAC_SB_FILT 0x100
+#define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400
+
+#define WM8900_REG_AUDIO3_ADCLRC_DIR 0x0800
+
+#define WM8900_REG_AUDIO4_DACLRC_DIR 0x0800
+
+#define WM8900_REG_FLLCTL1_OSC_ENA 0x100
+
+#define WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF 0x100
+
+#define WM8900_REG_HPCTL1_HP_IPSTAGE_ENA 0x80
+#define WM8900_REG_HPCTL1_HP_OPSTAGE_ENA 0x40
+#define WM8900_REG_HPCTL1_HP_CLAMP_IP 0x20
+#define WM8900_REG_HPCTL1_HP_CLAMP_OP 0x10
+#define WM8900_REG_HPCTL1_HP_SHORT 0x08
+#define WM8900_REG_HPCTL1_HP_SHORT2 0x04
+
+#define WM8900_LRC_MASK 0xfc00
+
+struct wm8900_priv {
+ enum snd_soc_control_type control_type;
+
+ u32 fll_in; /* FLL input frequency */
+ u32 fll_out; /* FLL output frequency */
+};
+
+/*
+ * wm8900 register cache. We can't read the entire register space and we
+ * have slow control buses so we cache the registers.
+ */
+static const u16 wm8900_reg_defaults[WM8900_MAXREG] = {
+ 0x8900, 0x0000,
+ 0xc000, 0x0000,
+ 0x4050, 0x4000,
+ 0x0008, 0x0000,
+ 0x0040, 0x0040,
+ 0x1004, 0x00c0,
+ 0x00c0, 0x0000,
+ 0x0100, 0x00c0,
+ 0x00c0, 0x0000,
+ 0xb001, 0x0000,
+ 0x0000, 0x0044,
+ 0x004c, 0x004c,
+ 0x0044, 0x0044,
+ 0x0000, 0x0044,
+ 0x0000, 0x0000,
+ 0x0002, 0x0000,
+ 0x0000, 0x0000,
+ 0x0000, 0x0000,
+ 0x0008, 0x0000,
+ 0x0000, 0x0008,
+ 0x0097, 0x0100,
+ 0x0000, 0x0000,
+ 0x0050, 0x0050,
+ 0x0055, 0x0055,
+ 0x0055, 0x0000,
+ 0x0000, 0x0079,
+ 0x0079, 0x0079,
+ 0x0079, 0x0000,
+ /* Remaining registers all zero */
+};
+
+static int wm8900_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case WM8900_REG_ID:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void wm8900_reset(struct snd_soc_codec *codec)
+{
+ snd_soc_write(codec, WM8900_REG_RESET, 0);
+
+ memcpy(codec->reg_cache, wm8900_reg_defaults,
+ sizeof(wm8900_reg_defaults));
+}
+
+static int wm8900_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u16 hpctl1 = snd_soc_read(codec, WM8900_REG_HPCTL1);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Clamp headphone outputs */
+ hpctl1 = WM8900_REG_HPCTL1_HP_CLAMP_IP |
+ WM8900_REG_HPCTL1_HP_CLAMP_OP;
+ snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ break;
+
+ case SND_SOC_DAPM_POST_PMU:
+ /* Enable the input stage */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_IP;
+ hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT |
+ WM8900_REG_HPCTL1_HP_SHORT2 |
+ WM8900_REG_HPCTL1_HP_IPSTAGE_ENA;
+ snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ msleep(400);
+
+ /* Enable the output stage */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_OP;
+ hpctl1 |= WM8900_REG_HPCTL1_HP_OPSTAGE_ENA;
+ snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ /* Remove the shorts */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT2;
+ snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT;
+ snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ /* Short the output */
+ hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT;
+ snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ /* Disable the output stage */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_OPSTAGE_ENA;
+ snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ /* Clamp the outputs and power down input */
+ hpctl1 |= WM8900_REG_HPCTL1_HP_CLAMP_IP |
+ WM8900_REG_HPCTL1_HP_CLAMP_OP;
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_IPSTAGE_ENA;
+ snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ break;
+
+ case SND_SOC_DAPM_POST_PMD:
+ /* Disable everything */
+ snd_soc_write(codec, WM8900_REG_HPCTL1, 0);
+ break;
+
+ default:
+ BUG();
+ }
+
+ return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_boost_tlv, -1200, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1200, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1);
+
+static const DECLARE_TLV_DB_SCALE(adc_svol_tlv, -3600, 300, 0);
+
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1);
+
+static const char *mic_bias_level_txt[] = { "0.9*AVDD", "0.65*AVDD" };
+
+static const struct soc_enum mic_bias_level =
+SOC_ENUM_SINGLE(WM8900_REG_INCTL, 8, 2, mic_bias_level_txt);
+
+static const char *dac_mute_rate_txt[] = { "Fast", "Slow" };
+
+static const struct soc_enum dac_mute_rate =
+SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 7, 2, dac_mute_rate_txt);
+
+static const char *dac_deemphasis_txt[] = {
+ "Disabled", "32kHz", "44.1kHz", "48kHz"
+};
+
+static const struct soc_enum dac_deemphasis =
+SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 4, 4, dac_deemphasis_txt);
+
+static const char *adc_hpf_cut_txt[] = {
+ "Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"
+};
+
+static const struct soc_enum adc_hpf_cut =
+SOC_ENUM_SINGLE(WM8900_REG_ADCCTRL, 5, 4, adc_hpf_cut_txt);
+
+static const char *lr_txt[] = {
+ "Left", "Right"
+};
+
+static const struct soc_enum aifl_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 15, 2, lr_txt);
+
+static const struct soc_enum aifr_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 14, 2, lr_txt);
+
+static const struct soc_enum dacl_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 15, 2, lr_txt);
+
+static const struct soc_enum dacr_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 14, 2, lr_txt);
+
+static const char *sidetone_txt[] = {
+ "Disabled", "Left ADC", "Right ADC"
+};
+
+static const struct soc_enum dacl_sidetone =
+SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 2, 3, sidetone_txt);
+
+static const struct soc_enum dacr_sidetone =
+SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 0, 3, sidetone_txt);
+
+static const struct snd_kcontrol_new wm8900_snd_controls[] = {
+SOC_ENUM("Mic Bias Level", mic_bias_level),
+
+SOC_SINGLE_TLV("Left Input PGA Volume", WM8900_REG_LINVOL, 0, 31, 0,
+ in_pga_tlv),
+SOC_SINGLE("Left Input PGA Switch", WM8900_REG_LINVOL, 6, 1, 1),
+SOC_SINGLE("Left Input PGA ZC Switch", WM8900_REG_LINVOL, 7, 1, 0),
+
+SOC_SINGLE_TLV("Right Input PGA Volume", WM8900_REG_RINVOL, 0, 31, 0,
+ in_pga_tlv),
+SOC_SINGLE("Right Input PGA Switch", WM8900_REG_RINVOL, 6, 1, 1),
+SOC_SINGLE("Right Input PGA ZC Switch", WM8900_REG_RINVOL, 7, 1, 0),
+
+SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1),
+SOC_ENUM("DAC Mute Rate", dac_mute_rate),
+SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0),
+SOC_ENUM("DAC Deemphasis", dac_deemphasis),
+SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL,
+ 12, 1, 0),
+
+SOC_SINGLE("ADC HPF Switch", WM8900_REG_ADCCTRL, 8, 1, 0),
+SOC_ENUM("ADC HPF Cut-Off", adc_hpf_cut),
+SOC_DOUBLE("ADC Invert Switch", WM8900_REG_ADCCTRL, 1, 0, 1, 0),
+SOC_SINGLE_TLV("Left ADC Sidetone Volume", WM8900_REG_SIDETONE, 9, 12, 0,
+ adc_svol_tlv),
+SOC_SINGLE_TLV("Right ADC Sidetone Volume", WM8900_REG_SIDETONE, 5, 12, 0,
+ adc_svol_tlv),
+SOC_ENUM("Left Digital Audio Source", aifl_src),
+SOC_ENUM("Right Digital Audio Source", aifr_src),
+
+SOC_SINGLE_TLV("DAC Input Boost Volume", WM8900_REG_AUDIO2, 10, 4, 0,
+ dac_boost_tlv),
+SOC_ENUM("Left DAC Source", dacl_src),
+SOC_ENUM("Right DAC Source", dacr_src),
+SOC_ENUM("Left DAC Sidetone", dacl_sidetone),
+SOC_ENUM("Right DAC Sidetone", dacr_sidetone),
+SOC_DOUBLE("DAC Invert Switch", WM8900_REG_DACCTRL, 1, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Digital Playback Volume",
+ WM8900_REG_LDAC_DV, WM8900_REG_RDAC_DV,
+ 1, 96, 0, dac_tlv),
+SOC_DOUBLE_R_TLV("Digital Capture Volume",
+ WM8900_REG_LADC_DV, WM8900_REG_RADC_DV, 1, 119, 0, adc_tlv),
+
+SOC_SINGLE_TLV("LINPUT3 Bypass Volume", WM8900_REG_LOUTMIXCTL1, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("RINPUT3 Bypass Volume", WM8900_REG_ROUTMIXCTL1, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("Left AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("Right AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 0, 7, 0,
+ out_mix_tlv),
+
+SOC_SINGLE_TLV("LeftIn to RightOut Mixer Volume", WM8900_REG_BYPASS1, 0, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("LeftIn to LeftOut Mixer Volume", WM8900_REG_BYPASS1, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("RightIn to LeftOut Mixer Volume", WM8900_REG_BYPASS2, 0, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("RightIn to RightOut Mixer Volume", WM8900_REG_BYPASS2, 4, 7, 0,
+ out_mix_tlv),
+
+SOC_SINGLE_TLV("IN2L Boost Volume", WM8900_REG_INBOOSTMIX1, 0, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("IN3L Boost Volume", WM8900_REG_INBOOSTMIX1, 4, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("IN2R Boost Volume", WM8900_REG_INBOOSTMIX2, 0, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("IN3R Boost Volume", WM8900_REG_INBOOSTMIX2, 4, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("Left AUX Boost Volume", WM8900_REG_AUXBOOST, 4, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("Right AUX Boost Volume", WM8900_REG_AUXBOOST, 0, 3, 0,
+ in_boost_tlv),
+
+SOC_DOUBLE_R_TLV("LINEOUT1 Volume", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+ 0, 63, 0, out_pga_tlv),
+SOC_DOUBLE_R("LINEOUT1 Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+ 6, 1, 1),
+SOC_DOUBLE_R("LINEOUT1 ZC Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+ 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("LINEOUT2 Volume",
+ WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL,
+ 0, 63, 0, out_pga_tlv),
+SOC_DOUBLE_R("LINEOUT2 Switch",
+ WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 6, 1, 1),
+SOC_DOUBLE_R("LINEOUT2 ZC Switch",
+ WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 7, 1, 0),
+SOC_SINGLE("LINEOUT2 LP -12dB", WM8900_REG_LOUTMIXCTL1,
+ 0, 1, 1),
+
+};
+
+static const struct snd_kcontrol_new wm8900_dapm_loutput2_control =
+SOC_DAPM_SINGLE("LINEOUT2L Switch", WM8900_REG_POWER3, 6, 1, 0);
+
+static const struct snd_kcontrol_new wm8900_dapm_routput2_control =
+SOC_DAPM_SINGLE("LINEOUT2R Switch", WM8900_REG_POWER3, 5, 1, 0);
+
+static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {
+SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),
+SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),
+SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),
+SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_routmix_controls[] = {
+SOC_DAPM_SINGLE("RINPUT3 Bypass Switch", WM8900_REG_ROUTMIXCTL1, 7, 1, 0),
+SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 3, 1, 0),
+SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 3, 1, 0),
+SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 7, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8900_REG_ROUTMIXCTL1, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_linmix_controls[] = {
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INBOOSTMIX1, 2, 1, 1),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INBOOSTMIX1, 6, 1, 1),
+SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 6, 1, 1),
+SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 6, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_rinmix_controls[] = {
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INBOOSTMIX2, 2, 1, 1),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INBOOSTMIX2, 6, 1, 1),
+SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 2, 1, 1),
+SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_linpga_controls[] = {
+SOC_DAPM_SINGLE("LINPUT1 Switch", WM8900_REG_INCTL, 6, 1, 0),
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INCTL, 5, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INCTL, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_rinpga_controls[] = {
+SOC_DAPM_SINGLE("RINPUT1 Switch", WM8900_REG_INCTL, 2, 1, 0),
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INCTL, 1, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INCTL, 0, 1, 0),
+};
+
+static const char *wm9700_lp_mux[] = { "Disabled", "Enabled" };
+
+static const struct soc_enum wm8900_lineout2_lp_mux =
+SOC_ENUM_SINGLE(WM8900_REG_LOUTMIXCTL1, 1, 2, wm9700_lp_mux);
+
+static const struct snd_kcontrol_new wm8900_lineout2_lp =
+SOC_DAPM_ENUM("Route", wm8900_lineout2_lp_mux);
+
+static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = {
+
+/* Externally visible pins */
+SND_SOC_DAPM_OUTPUT("LINEOUT1L"),
+SND_SOC_DAPM_OUTPUT("LINEOUT1R"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2L"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2R"),
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+
+SND_SOC_DAPM_INPUT("RINPUT1"),
+SND_SOC_DAPM_INPUT("LINPUT1"),
+SND_SOC_DAPM_INPUT("RINPUT2"),
+SND_SOC_DAPM_INPUT("LINPUT2"),
+SND_SOC_DAPM_INPUT("RINPUT3"),
+SND_SOC_DAPM_INPUT("LINPUT3"),
+SND_SOC_DAPM_INPUT("AUX"),
+
+SND_SOC_DAPM_VMID("VMID"),
+
+/* Input */
+SND_SOC_DAPM_MIXER("Left Input PGA", WM8900_REG_POWER2, 3, 0,
+ wm8900_linpga_controls,
+ ARRAY_SIZE(wm8900_linpga_controls)),
+SND_SOC_DAPM_MIXER("Right Input PGA", WM8900_REG_POWER2, 2, 0,
+ wm8900_rinpga_controls,
+ ARRAY_SIZE(wm8900_rinpga_controls)),
+
+SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0,
+ wm8900_linmix_controls,
+ ARRAY_SIZE(wm8900_linmix_controls)),
+SND_SOC_DAPM_MIXER("Right Input Mixer", WM8900_REG_POWER2, 4, 0,
+ wm8900_rinmix_controls,
+ ARRAY_SIZE(wm8900_rinmix_controls)),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8900_REG_POWER1, 4, 0),
+
+SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8900_REG_POWER2, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8900_REG_POWER2, 0, 0),
+
+/* Output */
+SND_SOC_DAPM_DAC("DACL", "Left HiFi Playback", WM8900_REG_POWER3, 1, 0),
+SND_SOC_DAPM_DAC("DACR", "Right HiFi Playback", WM8900_REG_POWER3, 0, 0),
+
+SND_SOC_DAPM_PGA_E("Headphone Amplifier", WM8900_REG_POWER3, 7, 0, NULL, 0,
+ wm8900_hp_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_PGA("LINEOUT1L PGA", WM8900_REG_POWER2, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT1R PGA", WM8900_REG_POWER2, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MUX("LINEOUT2 LP", SND_SOC_NOPM, 0, 0, &wm8900_lineout2_lp),
+SND_SOC_DAPM_PGA("LINEOUT2L PGA", WM8900_REG_POWER3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT2R PGA", WM8900_REG_POWER3, 5, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0,
+ wm8900_loutmix_controls,
+ ARRAY_SIZE(wm8900_loutmix_controls)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8900_REG_POWER3, 2, 0,
+ wm8900_routmix_controls,
+ ARRAY_SIZE(wm8900_routmix_controls)),
+};
+
+/* Target, Path, Source */
+static const struct snd_soc_dapm_route audio_map[] = {
+/* Inputs */
+{"Left Input PGA", "LINPUT1 Switch", "LINPUT1"},
+{"Left Input PGA", "LINPUT2 Switch", "LINPUT2"},
+{"Left Input PGA", "LINPUT3 Switch", "LINPUT3"},
+
+{"Right Input PGA", "RINPUT1 Switch", "RINPUT1"},
+{"Right Input PGA", "RINPUT2 Switch", "RINPUT2"},
+{"Right Input PGA", "RINPUT3 Switch", "RINPUT3"},
+
+{"Left Input Mixer", "LINPUT2 Switch", "LINPUT2"},
+{"Left Input Mixer", "LINPUT3 Switch", "LINPUT3"},
+{"Left Input Mixer", "AUX Switch", "AUX"},
+{"Left Input Mixer", "Input PGA Switch", "Left Input PGA"},
+
+{"Right Input Mixer", "RINPUT2 Switch", "RINPUT2"},
+{"Right Input Mixer", "RINPUT3 Switch", "RINPUT3"},
+{"Right Input Mixer", "AUX Switch", "AUX"},
+{"Right Input Mixer", "Input PGA Switch", "Right Input PGA"},
+
+{"ADCL", NULL, "Left Input Mixer"},
+{"ADCR", NULL, "Right Input Mixer"},
+
+/* Outputs */
+{"LINEOUT1L", NULL, "LINEOUT1L PGA"},
+{"LINEOUT1L PGA", NULL, "Left Output Mixer"},
+{"LINEOUT1R", NULL, "LINEOUT1R PGA"},
+{"LINEOUT1R PGA", NULL, "Right Output Mixer"},
+
+{"LINEOUT2L PGA", NULL, "Left Output Mixer"},
+{"LINEOUT2 LP", "Disabled", "LINEOUT2L PGA"},
+{"LINEOUT2 LP", "Enabled", "Left Output Mixer"},
+{"LINEOUT2L", NULL, "LINEOUT2 LP"},
+
+{"LINEOUT2R PGA", NULL, "Right Output Mixer"},
+{"LINEOUT2 LP", "Disabled", "LINEOUT2R PGA"},
+{"LINEOUT2 LP", "Enabled", "Right Output Mixer"},
+{"LINEOUT2R", NULL, "LINEOUT2 LP"},
+
+{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},
+{"Left Output Mixer", "AUX Bypass Switch", "AUX"},
+{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
+{"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
+{"Left Output Mixer", "DACL Switch", "DACL"},
+
+{"Right Output Mixer", "RINPUT3 Bypass Switch", "RINPUT3"},
+{"Right Output Mixer", "AUX Bypass Switch", "AUX"},
+{"Right Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
+{"Right Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
+{"Right Output Mixer", "DACR Switch", "DACR"},
+
+/* Note that the headphone output stage needs to be connected
+ * externally to LINEOUT2 via DC blocking capacitors. Other
+ * configurations are not supported.
+ *
+ * Note also that left and right headphone paths are treated as a
+ * mono path.
+ */
+{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
+{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
+{"HP_L", NULL, "Headphone Amplifier"},
+{"HP_R", NULL, "Headphone Amplifier"},
+};
+
+static int wm8900_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8900_dapm_widgets,
+ ARRAY_SIZE(wm8900_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+static int wm8900_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u16 reg;
+
+ reg = snd_soc_read(codec, WM8900_REG_AUDIO1) & ~0x60;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ reg |= 0x20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ reg |= 0x40;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ reg |= 0x60;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8900_REG_AUDIO1, reg);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ reg = snd_soc_read(codec, WM8900_REG_DACCTRL);
+
+ if (params_rate(params) <= 24000)
+ reg |= WM8900_REG_DACCTRL_DAC_SB_FILT;
+ else
+ reg &= ~WM8900_REG_DACCTRL_DAC_SB_FILT;
+
+ snd_soc_write(codec, WM8900_REG_DACCTRL, reg);
+ }
+
+ return 0;
+}
+
+/* FLL divisors */
+struct _fll_div {
+ u16 fll_ratio;
+ u16 fllclk_div;
+ u16 fll_slow_lock_ref;
+ u16 n;
+ u16 k;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+ unsigned int Fout)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+ unsigned int div;
+
+ BUG_ON(!Fout);
+
+ /* The FLL must run at 90-100MHz which is then scaled down to
+ * the output value by FLLCLK_DIV. */
+ target = Fout;
+ div = 1;
+ while (target < 90000000) {
+ div *= 2;
+ target *= 2;
+ }
+
+ if (target > 100000000)
+ printk(KERN_WARNING "wm8900: FLL rate %u out of range, Fref=%u"
+ " Fout=%u\n", target, Fref, Fout);
+ if (div > 32) {
+ printk(KERN_ERR "wm8900: Invalid FLL division rate %u, "
+ "Fref=%u, Fout=%u, target=%u\n",
+ div, Fref, Fout, target);
+ return -EINVAL;
+ }
+
+ fll_div->fllclk_div = div >> 2;
+
+ if (Fref < 48000)
+ fll_div->fll_slow_lock_ref = 1;
+ else
+ fll_div->fll_slow_lock_ref = 0;
+
+ Ndiv = target / Fref;
+
+ if (Fref < 1000000)
+ fll_div->fll_ratio = 8;
+ else
+ fll_div->fll_ratio = 1;
+
+ fll_div->n = Ndiv / fll_div->fll_ratio;
+ Nmod = (target / fll_div->fll_ratio) % Fref;
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, Fref);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ fll_div->k = K / 10;
+
+ BUG_ON(target != Fout * (fll_div->fllclk_div << 2));
+ BUG_ON(!K && target != Fref * fll_div->fll_ratio * fll_div->n);
+
+ return 0;
+}
+
+static int wm8900_set_fll(struct snd_soc_codec *codec,
+ int fll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec);
+ struct _fll_div fll_div;
+ unsigned int reg;
+
+ if (wm8900->fll_in == freq_in && wm8900->fll_out == freq_out)
+ return 0;
+
+ /* The digital side should be disabled during any change. */
+ reg = snd_soc_read(codec, WM8900_REG_POWER1);
+ snd_soc_write(codec, WM8900_REG_POWER1,
+ reg & (~WM8900_REG_POWER1_FLL_ENA));
+
+ /* Disable the FLL? */
+ if (!freq_in || !freq_out) {
+ reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
+ snd_soc_write(codec, WM8900_REG_CLOCKING1,
+ reg & (~WM8900_REG_CLOCKING1_MCLK_SRC));
+
+ reg = snd_soc_read(codec, WM8900_REG_FLLCTL1);
+ snd_soc_write(codec, WM8900_REG_FLLCTL1,
+ reg & (~WM8900_REG_FLLCTL1_OSC_ENA));
+
+ wm8900->fll_in = freq_in;
+ wm8900->fll_out = freq_out;
+
+ return 0;
+ }
+
+ if (fll_factors(&fll_div, freq_in, freq_out) != 0)
+ goto reenable;
+
+ wm8900->fll_in = freq_in;
+ wm8900->fll_out = freq_out;
+
+ /* The osclilator *MUST* be enabled before we enable the
+ * digital circuit. */
+ snd_soc_write(codec, WM8900_REG_FLLCTL1,
+ fll_div.fll_ratio | WM8900_REG_FLLCTL1_OSC_ENA);
+
+ snd_soc_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5);
+ snd_soc_write(codec, WM8900_REG_FLLCTL5,
+ (fll_div.fllclk_div << 6) | (fll_div.n & 0x1f));
+
+ if (fll_div.k) {
+ snd_soc_write(codec, WM8900_REG_FLLCTL2,
+ (fll_div.k >> 8) | 0x100);
+ snd_soc_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff);
+ } else
+ snd_soc_write(codec, WM8900_REG_FLLCTL2, 0);
+
+ if (fll_div.fll_slow_lock_ref)
+ snd_soc_write(codec, WM8900_REG_FLLCTL6,
+ WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF);
+ else
+ snd_soc_write(codec, WM8900_REG_FLLCTL6, 0);
+
+ reg = snd_soc_read(codec, WM8900_REG_POWER1);
+ snd_soc_write(codec, WM8900_REG_POWER1,
+ reg | WM8900_REG_POWER1_FLL_ENA);
+
+reenable:
+ reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
+ snd_soc_write(codec, WM8900_REG_CLOCKING1,
+ reg | WM8900_REG_CLOCKING1_MCLK_SRC);
+
+ return 0;
+}
+
+static int wm8900_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ return wm8900_set_fll(codec_dai->codec, pll_id, freq_in, freq_out);
+}
+
+static int wm8900_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ switch (div_id) {
+ case WM8900_BCLK_DIV:
+ reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
+ snd_soc_write(codec, WM8900_REG_CLOCKING1,
+ div | (reg & WM8900_REG_CLOCKING1_BCLK_MASK));
+ break;
+ case WM8900_OPCLK_DIV:
+ reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
+ snd_soc_write(codec, WM8900_REG_CLOCKING1,
+ div | (reg & WM8900_REG_CLOCKING1_OPCLK_MASK));
+ break;
+ case WM8900_DAC_LRCLK:
+ reg = snd_soc_read(codec, WM8900_REG_AUDIO4);
+ snd_soc_write(codec, WM8900_REG_AUDIO4,
+ div | (reg & WM8900_LRC_MASK));
+ break;
+ case WM8900_ADC_LRCLK:
+ reg = snd_soc_read(codec, WM8900_REG_AUDIO3);
+ snd_soc_write(codec, WM8900_REG_AUDIO3,
+ div | (reg & WM8900_LRC_MASK));
+ break;
+ case WM8900_DAC_CLKDIV:
+ reg = snd_soc_read(codec, WM8900_REG_CLOCKING2);
+ snd_soc_write(codec, WM8900_REG_CLOCKING2,
+ div | (reg & WM8900_REG_CLOCKING2_DAC_CLKDIV));
+ break;
+ case WM8900_ADC_CLKDIV:
+ reg = snd_soc_read(codec, WM8900_REG_CLOCKING2);
+ snd_soc_write(codec, WM8900_REG_CLOCKING2,
+ div | (reg & WM8900_REG_CLOCKING2_ADC_CLKDIV));
+ break;
+ case WM8900_LRCLK_MODE:
+ reg = snd_soc_read(codec, WM8900_REG_DACCTRL);
+ snd_soc_write(codec, WM8900_REG_DACCTRL,
+ div | (reg & WM8900_REG_DACCTRL_AIF_LRCLKRATE));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int clocking1, aif1, aif3, aif4;
+
+ clocking1 = snd_soc_read(codec, WM8900_REG_CLOCKING1);
+ aif1 = snd_soc_read(codec, WM8900_REG_AUDIO1);
+ aif3 = snd_soc_read(codec, WM8900_REG_AUDIO3);
+ aif4 = snd_soc_read(codec, WM8900_REG_AUDIO4);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 |= 0x10;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 |= 0x8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8900_REG_CLOCKING1, clocking1);
+ snd_soc_write(codec, WM8900_REG_AUDIO1, aif1);
+ snd_soc_write(codec, WM8900_REG_AUDIO3, aif3);
+ snd_soc_write(codec, WM8900_REG_AUDIO4, aif4);
+
+ return 0;
+}
+
+static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ reg = snd_soc_read(codec, WM8900_REG_DACCTRL);
+
+ if (mute)
+ reg |= WM8900_REG_DACCTRL_MUTE;
+ else
+ reg &= ~WM8900_REG_DACCTRL_MUTE;
+
+ snd_soc_write(codec, WM8900_REG_DACCTRL, reg);
+
+ return 0;
+}
+
+#define WM8900_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define WM8900_PCM_FORMATS \
+ (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
+ SNDRV_PCM_FORMAT_S24_LE)
+
+static struct snd_soc_dai_ops wm8900_dai_ops = {
+ .hw_params = wm8900_hw_params,
+ .set_clkdiv = wm8900_set_dai_clkdiv,
+ .set_pll = wm8900_set_dai_pll,
+ .set_fmt = wm8900_set_dai_fmt,
+ .digital_mute = wm8900_digital_mute,
+};
+
+static struct snd_soc_dai_driver wm8900_dai = {
+ .name = "wm8900-hifi",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8900_RATES,
+ .formats = WM8900_PCM_FORMATS,
+ },
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8900_RATES,
+ .formats = WM8900_PCM_FORMATS,
+ },
+ .ops = &wm8900_dai_ops,
+};
+
+static int wm8900_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* Enable thermal shutdown */
+ reg = snd_soc_read(codec, WM8900_REG_GPIO);
+ snd_soc_write(codec, WM8900_REG_GPIO,
+ reg | WM8900_REG_GPIO_TEMP_ENA);
+ reg = snd_soc_read(codec, WM8900_REG_ADDCTL);
+ snd_soc_write(codec, WM8900_REG_ADDCTL,
+ reg | WM8900_REG_ADDCTL_TEMP_SD);
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ /* Charge capacitors if initial power up */
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* STARTUP_BIAS_ENA on */
+ snd_soc_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+
+ /* Startup bias mode */
+ snd_soc_write(codec, WM8900_REG_ADDCTL,
+ WM8900_REG_ADDCTL_BIAS_SRC |
+ WM8900_REG_ADDCTL_VMID_SOFTST);
+
+ /* VMID 2x50k */
+ snd_soc_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1);
+
+ /* Allow capacitors to charge */
+ schedule_timeout_interruptible(msecs_to_jiffies(400));
+
+ /* Enable bias */
+ snd_soc_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA |
+ WM8900_REG_POWER1_BIAS_ENA | 0x1);
+
+ snd_soc_write(codec, WM8900_REG_ADDCTL, 0);
+
+ snd_soc_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_BIAS_ENA | 0x1);
+ }
+
+ reg = snd_soc_read(codec, WM8900_REG_POWER1);
+ snd_soc_write(codec, WM8900_REG_POWER1,
+ (reg & WM8900_REG_POWER1_FLL_ENA) |
+ WM8900_REG_POWER1_BIAS_ENA | 0x1);
+ snd_soc_write(codec, WM8900_REG_POWER2,
+ WM8900_REG_POWER2_SYSCLK_ENA);
+ snd_soc_write(codec, WM8900_REG_POWER3, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Startup bias enable */
+ reg = snd_soc_read(codec, WM8900_REG_POWER1);
+ snd_soc_write(codec, WM8900_REG_POWER1,
+ reg & WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+ snd_soc_write(codec, WM8900_REG_ADDCTL,
+ WM8900_REG_ADDCTL_BIAS_SRC |
+ WM8900_REG_ADDCTL_VMID_SOFTST);
+
+ /* Discharge caps */
+ snd_soc_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+ schedule_timeout_interruptible(msecs_to_jiffies(500));
+
+ /* Remove clamp */
+ snd_soc_write(codec, WM8900_REG_HPCTL1, 0);
+
+ /* Power down */
+ snd_soc_write(codec, WM8900_REG_ADDCTL, 0);
+ snd_soc_write(codec, WM8900_REG_POWER1, 0);
+ snd_soc_write(codec, WM8900_REG_POWER2, 0);
+ snd_soc_write(codec, WM8900_REG_POWER3, 0);
+
+ /* Need to let things settle before stopping the clock
+ * to ensure that restart works, see "Stopping the
+ * master clock" in the datasheet. */
+ schedule_timeout_interruptible(msecs_to_jiffies(1));
+ snd_soc_write(codec, WM8900_REG_POWER2,
+ WM8900_REG_POWER2_SYSCLK_ENA);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int wm8900_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec);
+ int fll_out = wm8900->fll_out;
+ int fll_in = wm8900->fll_in;
+ int ret;
+
+ /* Stop the FLL in an orderly fashion */
+ ret = wm8900_set_fll(codec, 0, 0, 0);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to stop FLL\n");
+ return ret;
+ }
+
+ wm8900->fll_out = fll_out;
+ wm8900->fll_in = fll_in;
+
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8900_resume(struct snd_soc_codec *codec)
+{
+ struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec);
+ u16 *cache;
+ int i, ret;
+
+ cache = kmemdup(codec->reg_cache, sizeof(wm8900_reg_defaults),
+ GFP_KERNEL);
+
+ wm8900_reset(codec);
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Restart the FLL? */
+ if (wm8900->fll_out) {
+ int fll_out = wm8900->fll_out;
+ int fll_in = wm8900->fll_in;
+
+ wm8900->fll_in = 0;
+ wm8900->fll_out = 0;
+
+ ret = wm8900_set_fll(codec, 0, fll_in, fll_out);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to restart FLL\n");
+ return ret;
+ }
+ }
+
+ if (cache) {
+ for (i = 0; i < WM8900_MAXREG; i++)
+ snd_soc_write(codec, i, cache[i]);
+ kfree(cache);
+ } else
+ dev_err(codec->dev, "Unable to allocate register cache\n");
+
+ return 0;
+}
+
+static int wm8900_probe(struct snd_soc_codec *codec)
+{
+ struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0, reg;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm8900->control_type);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ reg = snd_soc_read(codec, WM8900_REG_ID);
+ if (reg != 0x8900) {
+ dev_err(codec->dev, "Device is not a WM8900 - ID %x\n", reg);
+ return -ENODEV;
+ }
+
+ wm8900_reset(codec);
+
+ /* Turn the chip on */
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Latch the volume update bits */
+ snd_soc_write(codec, WM8900_REG_LINVOL,
+ snd_soc_read(codec, WM8900_REG_LINVOL) | 0x100);
+ snd_soc_write(codec, WM8900_REG_RINVOL,
+ snd_soc_read(codec, WM8900_REG_RINVOL) | 0x100);
+ snd_soc_write(codec, WM8900_REG_LOUT1CTL,
+ snd_soc_read(codec, WM8900_REG_LOUT1CTL) | 0x100);
+ snd_soc_write(codec, WM8900_REG_ROUT1CTL,
+ snd_soc_read(codec, WM8900_REG_ROUT1CTL) | 0x100);
+ snd_soc_write(codec, WM8900_REG_LOUT2CTL,
+ snd_soc_read(codec, WM8900_REG_LOUT2CTL) | 0x100);
+ snd_soc_write(codec, WM8900_REG_ROUT2CTL,
+ snd_soc_read(codec, WM8900_REG_ROUT2CTL) | 0x100);
+ snd_soc_write(codec, WM8900_REG_LDAC_DV,
+ snd_soc_read(codec, WM8900_REG_LDAC_DV) | 0x100);
+ snd_soc_write(codec, WM8900_REG_RDAC_DV,
+ snd_soc_read(codec, WM8900_REG_RDAC_DV) | 0x100);
+ snd_soc_write(codec, WM8900_REG_LADC_DV,
+ snd_soc_read(codec, WM8900_REG_LADC_DV) | 0x100);
+ snd_soc_write(codec, WM8900_REG_RADC_DV,
+ snd_soc_read(codec, WM8900_REG_RADC_DV) | 0x100);
+
+ /* Set the DAC and mixer output bias */
+ snd_soc_write(codec, WM8900_REG_OUTBIASCTL, 0x81);
+
+ snd_soc_add_controls(codec, wm8900_snd_controls,
+ ARRAY_SIZE(wm8900_snd_controls));
+ wm8900_add_widgets(codec);
+
+ return 0;
+}
+
+/* power down chip */
+static int wm8900_remove(struct snd_soc_codec *codec)
+{
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8900 = {
+ .probe = wm8900_probe,
+ .remove = wm8900_remove,
+ .suspend = wm8900_suspend,
+ .resume = wm8900_resume,
+ .set_bias_level = wm8900_set_bias_level,
+ .volatile_register = wm8900_volatile_register,
+ .reg_cache_size = ARRAY_SIZE(wm8900_reg_defaults),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8900_reg_defaults,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8900_spi_probe(struct spi_device *spi)
+{
+ struct wm8900_priv *wm8900;
+ int ret;
+
+ wm8900 = kzalloc(sizeof(struct wm8900_priv), GFP_KERNEL);
+ if (wm8900 == NULL)
+ return -ENOMEM;
+
+ wm8900->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8900);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8900, &wm8900_dai, 1);
+ if (ret < 0)
+ kfree(wm8900);
+ return ret;
+}
+
+static int __devexit wm8900_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8900_spi_driver = {
+ .driver = {
+ .name = "wm8900-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8900_spi_probe,
+ .remove = __devexit_p(wm8900_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8900_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8900_priv *wm8900;
+ int ret;
+
+ wm8900 = kzalloc(sizeof(struct wm8900_priv), GFP_KERNEL);
+ if (wm8900 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8900);
+ wm8900->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8900, &wm8900_dai, 1);
+ if (ret < 0)
+ kfree(wm8900);
+ return ret;
+}
+
+static __devexit int wm8900_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8900_i2c_id[] = {
+ { "wm8900", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8900_i2c_id);
+
+static struct i2c_driver wm8900_i2c_driver = {
+ .driver = {
+ .name = "wm8900-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8900_i2c_probe,
+ .remove = __devexit_p(wm8900_i2c_remove),
+ .id_table = wm8900_i2c_id,
+};
+#endif
+
+static int __init wm8900_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8900_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8900 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8900_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8900 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8900_modinit);
+
+static void __exit wm8900_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8900_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8900_spi_driver);
+#endif
+}
+module_exit(wm8900_exit);
+
+MODULE_DESCRIPTION("ASoC WM8900 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8900.h b/sound/soc/codecs/wm8900.h
new file mode 100644
index 00000000..583f257e
--- /dev/null
+++ b/sound/soc/codecs/wm8900.h
@@ -0,0 +1,55 @@
+/*
+ * wm8900.h -- WM890 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8900_H
+#define _WM8900_H
+
+#define WM8900_FLL 1
+
+#define WM8900_BCLK_DIV 1
+#define WM8900_ADC_CLKDIV 2
+#define WM8900_DAC_CLKDIV 3
+#define WM8900_ADC_LRCLK 4
+#define WM8900_DAC_LRCLK 5
+#define WM8900_OPCLK_DIV 6
+#define WM8900_LRCLK_MODE 7
+
+#define WM8900_BCLK_DIV_1 0x00
+#define WM8900_BCLK_DIV_1_5 0x02
+#define WM8900_BCLK_DIV_2 0x04
+#define WM8900_BCLK_DIV_3 0x06
+#define WM8900_BCLK_DIV_4 0x08
+#define WM8900_BCLK_DIV_5_5 0x0a
+#define WM8900_BCLK_DIV_6 0x0c
+#define WM8900_BCLK_DIV_8 0x0e
+#define WM8900_BCLK_DIV_11 0x10
+#define WM8900_BCLK_DIV_12 0x12
+#define WM8900_BCLK_DIV_16 0x14
+#define WM8900_BCLK_DIV_22 0x16
+#define WM8900_BCLK_DIV_24 0x18
+#define WM8900_BCLK_DIV_32 0x1a
+#define WM8900_BCLK_DIV_44 0x1c
+#define WM8900_BCLK_DIV_48 0x1e
+
+#define WM8900_ADC_CLKDIV_1 0x00
+#define WM8900_ADC_CLKDIV_1_5 0x20
+#define WM8900_ADC_CLKDIV_2 0x40
+#define WM8900_ADC_CLKDIV_3 0x60
+#define WM8900_ADC_CLKDIV_4 0x80
+#define WM8900_ADC_CLKDIV_5_5 0xa0
+#define WM8900_ADC_CLKDIV_6 0xc0
+
+#define WM8900_DAC_CLKDIV_1 0x00
+#define WM8900_DAC_CLKDIV_1_5 0x04
+#define WM8900_DAC_CLKDIV_2 0x08
+#define WM8900_DAC_CLKDIV_3 0x0c
+#define WM8900_DAC_CLKDIV_4 0x10
+#define WM8900_DAC_CLKDIV_5_5 0x14
+#define WM8900_DAC_CLKDIV_6 0x18
+
+#endif
diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c
new file mode 100644
index 00000000..43e3d760
--- /dev/null
+++ b/sound/soc/codecs/wm8903.c
@@ -0,0 +1,2140 @@
+/*
+ * wm8903.c -- WM8903 ALSA SoC Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics
+ * Copyright 2011 NVIDIA, Inc.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO:
+ * - TDM mode configuration.
+ * - Digital microphone support.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/wm8903.h>
+#include <trace/events/asoc.h>
+
+#include "wm8903.h"
+
+/* Register defaults at reset */
+static u16 wm8903_reg_defaults[] = {
+ 0x8903, /* R0 - SW Reset and ID */
+ 0x0000, /* R1 - Revision Number */
+ 0x0000, /* R2 */
+ 0x0000, /* R3 */
+ 0x0018, /* R4 - Bias Control 0 */
+ 0x0000, /* R5 - VMID Control 0 */
+ 0x0000, /* R6 - Mic Bias Control 0 */
+ 0x0000, /* R7 */
+ 0x0001, /* R8 - Analogue DAC 0 */
+ 0x0000, /* R9 */
+ 0x0001, /* R10 - Analogue ADC 0 */
+ 0x0000, /* R11 */
+ 0x0000, /* R12 - Power Management 0 */
+ 0x0000, /* R13 - Power Management 1 */
+ 0x0000, /* R14 - Power Management 2 */
+ 0x0000, /* R15 - Power Management 3 */
+ 0x0000, /* R16 - Power Management 4 */
+ 0x0000, /* R17 - Power Management 5 */
+ 0x0000, /* R18 - Power Management 6 */
+ 0x0000, /* R19 */
+ 0x0400, /* R20 - Clock Rates 0 */
+ 0x0D07, /* R21 - Clock Rates 1 */
+ 0x0000, /* R22 - Clock Rates 2 */
+ 0x0000, /* R23 */
+ 0x0050, /* R24 - Audio Interface 0 */
+ 0x0242, /* R25 - Audio Interface 1 */
+ 0x0008, /* R26 - Audio Interface 2 */
+ 0x0022, /* R27 - Audio Interface 3 */
+ 0x0000, /* R28 */
+ 0x0000, /* R29 */
+ 0x00C0, /* R30 - DAC Digital Volume Left */
+ 0x00C0, /* R31 - DAC Digital Volume Right */
+ 0x0000, /* R32 - DAC Digital 0 */
+ 0x0000, /* R33 - DAC Digital 1 */
+ 0x0000, /* R34 */
+ 0x0000, /* R35 */
+ 0x00C0, /* R36 - ADC Digital Volume Left */
+ 0x00C0, /* R37 - ADC Digital Volume Right */
+ 0x0000, /* R38 - ADC Digital 0 */
+ 0x0073, /* R39 - Digital Microphone 0 */
+ 0x09BF, /* R40 - DRC 0 */
+ 0x3241, /* R41 - DRC 1 */
+ 0x0020, /* R42 - DRC 2 */
+ 0x0000, /* R43 - DRC 3 */
+ 0x0085, /* R44 - Analogue Left Input 0 */
+ 0x0085, /* R45 - Analogue Right Input 0 */
+ 0x0044, /* R46 - Analogue Left Input 1 */
+ 0x0044, /* R47 - Analogue Right Input 1 */
+ 0x0000, /* R48 */
+ 0x0000, /* R49 */
+ 0x0008, /* R50 - Analogue Left Mix 0 */
+ 0x0004, /* R51 - Analogue Right Mix 0 */
+ 0x0000, /* R52 - Analogue Spk Mix Left 0 */
+ 0x0000, /* R53 - Analogue Spk Mix Left 1 */
+ 0x0000, /* R54 - Analogue Spk Mix Right 0 */
+ 0x0000, /* R55 - Analogue Spk Mix Right 1 */
+ 0x0000, /* R56 */
+ 0x002D, /* R57 - Analogue OUT1 Left */
+ 0x002D, /* R58 - Analogue OUT1 Right */
+ 0x0039, /* R59 - Analogue OUT2 Left */
+ 0x0039, /* R60 - Analogue OUT2 Right */
+ 0x0100, /* R61 */
+ 0x0139, /* R62 - Analogue OUT3 Left */
+ 0x0139, /* R63 - Analogue OUT3 Right */
+ 0x0000, /* R64 */
+ 0x0000, /* R65 - Analogue SPK Output Control 0 */
+ 0x0000, /* R66 */
+ 0x0010, /* R67 - DC Servo 0 */
+ 0x0100, /* R68 */
+ 0x00A4, /* R69 - DC Servo 2 */
+ 0x0807, /* R70 */
+ 0x0000, /* R71 */
+ 0x0000, /* R72 */
+ 0x0000, /* R73 */
+ 0x0000, /* R74 */
+ 0x0000, /* R75 */
+ 0x0000, /* R76 */
+ 0x0000, /* R77 */
+ 0x0000, /* R78 */
+ 0x000E, /* R79 */
+ 0x0000, /* R80 */
+ 0x0000, /* R81 */
+ 0x0000, /* R82 */
+ 0x0000, /* R83 */
+ 0x0000, /* R84 */
+ 0x0000, /* R85 */
+ 0x0000, /* R86 */
+ 0x0006, /* R87 */
+ 0x0000, /* R88 */
+ 0x0000, /* R89 */
+ 0x0000, /* R90 - Analogue HP 0 */
+ 0x0060, /* R91 */
+ 0x0000, /* R92 */
+ 0x0000, /* R93 */
+ 0x0000, /* R94 - Analogue Lineout 0 */
+ 0x0060, /* R95 */
+ 0x0000, /* R96 */
+ 0x0000, /* R97 */
+ 0x0000, /* R98 - Charge Pump 0 */
+ 0x1F25, /* R99 */
+ 0x2B19, /* R100 */
+ 0x01C0, /* R101 */
+ 0x01EF, /* R102 */
+ 0x2B00, /* R103 */
+ 0x0000, /* R104 - Class W 0 */
+ 0x01C0, /* R105 */
+ 0x1C10, /* R106 */
+ 0x0000, /* R107 */
+ 0x0000, /* R108 - Write Sequencer 0 */
+ 0x0000, /* R109 - Write Sequencer 1 */
+ 0x0000, /* R110 - Write Sequencer 2 */
+ 0x0000, /* R111 - Write Sequencer 3 */
+ 0x0000, /* R112 - Write Sequencer 4 */
+ 0x0000, /* R113 */
+ 0x0000, /* R114 - Control Interface */
+ 0x0000, /* R115 */
+ 0x00A8, /* R116 - GPIO Control 1 */
+ 0x00A8, /* R117 - GPIO Control 2 */
+ 0x00A8, /* R118 - GPIO Control 3 */
+ 0x0220, /* R119 - GPIO Control 4 */
+ 0x01A0, /* R120 - GPIO Control 5 */
+ 0x0000, /* R121 - Interrupt Status 1 */
+ 0xFFFF, /* R122 - Interrupt Status 1 Mask */
+ 0x0000, /* R123 - Interrupt Polarity 1 */
+ 0x0000, /* R124 */
+ 0x0003, /* R125 */
+ 0x0000, /* R126 - Interrupt Control */
+ 0x0000, /* R127 */
+ 0x0005, /* R128 */
+ 0x0000, /* R129 - Control Interface Test 1 */
+ 0x0000, /* R130 */
+ 0x0000, /* R131 */
+ 0x0000, /* R132 */
+ 0x0000, /* R133 */
+ 0x0000, /* R134 */
+ 0x03FF, /* R135 */
+ 0x0007, /* R136 */
+ 0x0040, /* R137 */
+ 0x0000, /* R138 */
+ 0x0000, /* R139 */
+ 0x0000, /* R140 */
+ 0x0000, /* R141 */
+ 0x0000, /* R142 */
+ 0x0000, /* R143 */
+ 0x0000, /* R144 */
+ 0x0000, /* R145 */
+ 0x0000, /* R146 */
+ 0x0000, /* R147 */
+ 0x4000, /* R148 */
+ 0x6810, /* R149 - Charge Pump Test 1 */
+ 0x0004, /* R150 */
+ 0x0000, /* R151 */
+ 0x0000, /* R152 */
+ 0x0000, /* R153 */
+ 0x0000, /* R154 */
+ 0x0000, /* R155 */
+ 0x0000, /* R156 */
+ 0x0000, /* R157 */
+ 0x0000, /* R158 */
+ 0x0000, /* R159 */
+ 0x0000, /* R160 */
+ 0x0000, /* R161 */
+ 0x0000, /* R162 */
+ 0x0000, /* R163 */
+ 0x0028, /* R164 - Clock Rate Test 4 */
+ 0x0004, /* R165 */
+ 0x0000, /* R166 */
+ 0x0060, /* R167 */
+ 0x0000, /* R168 */
+ 0x0000, /* R169 */
+ 0x0000, /* R170 */
+ 0x0000, /* R171 */
+ 0x0000, /* R172 - Analogue Output Bias 0 */
+};
+
+struct wm8903_priv {
+ struct snd_soc_codec *codec;
+
+ int sysclk;
+ int irq;
+
+ int fs;
+ int deemph;
+
+ int dcs_pending;
+ int dcs_cache[4];
+
+ /* Reference count */
+ int class_w_users;
+
+ struct snd_soc_jack *mic_jack;
+ int mic_det;
+ int mic_short;
+ int mic_last_report;
+ int mic_delay;
+
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gpio_chip;
+#endif
+};
+
+static int wm8903_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case WM8903_SW_RESET_AND_ID:
+ case WM8903_REVISION_NUMBER:
+ case WM8903_INTERRUPT_STATUS_1:
+ case WM8903_WRITE_SEQUENCER_4:
+ case WM8903_DC_SERVO_READBACK_1:
+ case WM8903_DC_SERVO_READBACK_2:
+ case WM8903_DC_SERVO_READBACK_3:
+ case WM8903_DC_SERVO_READBACK_4:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static void wm8903_reset(struct snd_soc_codec *codec)
+{
+ snd_soc_write(codec, WM8903_SW_RESET_AND_ID, 0);
+ memcpy(codec->reg_cache, wm8903_reg_defaults,
+ sizeof(wm8903_reg_defaults));
+}
+
+static int wm8903_cp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ WARN_ON(event != SND_SOC_DAPM_POST_PMU);
+ mdelay(4);
+
+ return 0;
+}
+
+static int wm8903_dcs_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ wm8903->dcs_pending |= 1 << w->shift;
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_update_bits(codec, WM8903_DC_SERVO_0,
+ 1 << w->shift, 0);
+ break;
+ }
+
+ return 0;
+}
+
+#define WM8903_DCS_MODE_WRITE_STOP 0
+#define WM8903_DCS_MODE_START_STOP 2
+
+static void wm8903_seq_notifier(struct snd_soc_dapm_context *dapm,
+ enum snd_soc_dapm_type event, int subseq)
+{
+ struct snd_soc_codec *codec = container_of(dapm,
+ struct snd_soc_codec, dapm);
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int dcs_mode = WM8903_DCS_MODE_WRITE_STOP;
+ int i, val;
+
+ /* Complete any pending DC servo starts */
+ if (wm8903->dcs_pending) {
+ dev_dbg(codec->dev, "Starting DC servo for %x\n",
+ wm8903->dcs_pending);
+
+ /* If we've no cached values then we need to do startup */
+ for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) {
+ if (!(wm8903->dcs_pending & (1 << i)))
+ continue;
+
+ if (wm8903->dcs_cache[i]) {
+ dev_dbg(codec->dev,
+ "Restore DC servo %d value %x\n",
+ 3 - i, wm8903->dcs_cache[i]);
+
+ snd_soc_write(codec, WM8903_DC_SERVO_4 + i,
+ wm8903->dcs_cache[i] & 0xff);
+ } else {
+ dev_dbg(codec->dev,
+ "Calibrate DC servo %d\n", 3 - i);
+ dcs_mode = WM8903_DCS_MODE_START_STOP;
+ }
+ }
+
+ /* Don't trust the cache for analogue */
+ if (wm8903->class_w_users)
+ dcs_mode = WM8903_DCS_MODE_START_STOP;
+
+ snd_soc_update_bits(codec, WM8903_DC_SERVO_2,
+ WM8903_DCS_MODE_MASK, dcs_mode);
+
+ snd_soc_update_bits(codec, WM8903_DC_SERVO_0,
+ WM8903_DCS_ENA_MASK, wm8903->dcs_pending);
+
+ switch (dcs_mode) {
+ case WM8903_DCS_MODE_WRITE_STOP:
+ break;
+
+ case WM8903_DCS_MODE_START_STOP:
+ msleep(270);
+
+ /* Cache the measured offsets for digital */
+ if (wm8903->class_w_users)
+ break;
+
+ for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) {
+ if (!(wm8903->dcs_pending & (1 << i)))
+ continue;
+
+ val = snd_soc_read(codec,
+ WM8903_DC_SERVO_READBACK_1 + i);
+ dev_dbg(codec->dev, "DC servo %d: %x\n",
+ 3 - i, val);
+ wm8903->dcs_cache[i] = val;
+ }
+ break;
+
+ default:
+ pr_warn("DCS mode %d delay not set\n", dcs_mode);
+ break;
+ }
+
+ wm8903->dcs_pending = 0;
+ }
+}
+
+/*
+ * When used with DAC outputs only the WM8903 charge pump supports
+ * operation in class W mode, providing very low power consumption
+ * when used with digital sources. Enable and disable this mode
+ * automatically depending on the mixer configuration.
+ *
+ * All the relevant controls are simple switches.
+ */
+static int wm8903_class_w_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ u16 reg;
+ int ret;
+
+ reg = snd_soc_read(codec, WM8903_CLASS_W_0);
+
+ /* Turn it off if we're about to enable bypass */
+ if (ucontrol->value.integer.value[0]) {
+ if (wm8903->class_w_users == 0) {
+ dev_dbg(codec->dev, "Disabling Class W\n");
+ snd_soc_write(codec, WM8903_CLASS_W_0, reg &
+ ~(WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V));
+ }
+ wm8903->class_w_users++;
+ }
+
+ /* Implement the change */
+ ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+ /* If we've just disabled the last bypass path turn Class W on */
+ if (!ucontrol->value.integer.value[0]) {
+ if (wm8903->class_w_users == 1) {
+ dev_dbg(codec->dev, "Enabling Class W\n");
+ snd_soc_write(codec, WM8903_CLASS_W_0, reg |
+ WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V);
+ }
+ wm8903->class_w_users--;
+ }
+
+ dev_dbg(codec->dev, "Bypass use count now %d\n",
+ wm8903->class_w_users);
+
+ return ret;
+}
+
+#define SOC_DAPM_SINGLE_W(xname, reg, shift, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_dapm_get_volsw, .put = wm8903_class_w_put, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+static int wm8903_deemph[] = { 0, 32000, 44100, 48000 };
+
+static int wm8903_set_deemph(struct snd_soc_codec *codec)
+{
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int val, i, best;
+
+ /* If we're using deemphasis select the nearest available sample
+ * rate.
+ */
+ if (wm8903->deemph) {
+ best = 1;
+ for (i = 2; i < ARRAY_SIZE(wm8903_deemph); i++) {
+ if (abs(wm8903_deemph[i] - wm8903->fs) <
+ abs(wm8903_deemph[best] - wm8903->fs))
+ best = i;
+ }
+
+ val = best << WM8903_DEEMPH_SHIFT;
+ } else {
+ best = 0;
+ val = 0;
+ }
+
+ dev_dbg(codec->dev, "Set deemphasis %d (%dHz)\n",
+ best, wm8903_deemph[best]);
+
+ return snd_soc_update_bits(codec, WM8903_DAC_DIGITAL_1,
+ WM8903_DEEMPH_MASK, val);
+}
+
+static int wm8903_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8903->deemph;
+
+ return 0;
+}
+
+static int wm8903_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int deemph = ucontrol->value.enumerated.item[0];
+ int ret = 0;
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ mutex_lock(&codec->mutex);
+ if (wm8903->deemph != deemph) {
+ wm8903->deemph = deemph;
+
+ wm8903_set_deemph(codec);
+
+ ret = 1;
+ }
+ mutex_unlock(&codec->mutex);
+
+ return ret;
+}
+
+/* ALSA can only do steps of .01dB */
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+
+static const DECLARE_TLV_DB_SCALE(digital_sidetone_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_amp, -2250, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_min, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_max, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_startup, -300, 50, 0);
+
+static const char *hpf_mode_text[] = {
+ "Hi-fi", "Voice 1", "Voice 2", "Voice 3"
+};
+
+static const struct soc_enum hpf_mode =
+ SOC_ENUM_SINGLE(WM8903_ADC_DIGITAL_0, 5, 4, hpf_mode_text);
+
+static const char *osr_text[] = {
+ "Low power", "High performance"
+};
+
+static const struct soc_enum adc_osr =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_ADC_0, 0, 2, osr_text);
+
+static const struct soc_enum dac_osr =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 0, 2, osr_text);
+
+static const char *drc_slope_text[] = {
+ "1", "1/2", "1/4", "1/8", "1/16", "0"
+};
+
+static const struct soc_enum drc_slope_r0 =
+ SOC_ENUM_SINGLE(WM8903_DRC_2, 3, 6, drc_slope_text);
+
+static const struct soc_enum drc_slope_r1 =
+ SOC_ENUM_SINGLE(WM8903_DRC_2, 0, 6, drc_slope_text);
+
+static const char *drc_attack_text[] = {
+ "instantaneous",
+ "363us", "762us", "1.45ms", "2.9ms", "5.8ms", "11.6ms", "23.2ms",
+ "46.4ms", "92.8ms", "185.6ms"
+};
+
+static const struct soc_enum drc_attack =
+ SOC_ENUM_SINGLE(WM8903_DRC_1, 12, 11, drc_attack_text);
+
+static const char *drc_decay_text[] = {
+ "186ms", "372ms", "743ms", "1.49s", "2.97s", "5.94s", "11.89s",
+ "23.87s", "47.56s"
+};
+
+static const struct soc_enum drc_decay =
+ SOC_ENUM_SINGLE(WM8903_DRC_1, 8, 9, drc_decay_text);
+
+static const char *drc_ff_delay_text[] = {
+ "5 samples", "9 samples"
+};
+
+static const struct soc_enum drc_ff_delay =
+ SOC_ENUM_SINGLE(WM8903_DRC_0, 5, 2, drc_ff_delay_text);
+
+static const char *drc_qr_decay_text[] = {
+ "0.725ms", "1.45ms", "5.8ms"
+};
+
+static const struct soc_enum drc_qr_decay =
+ SOC_ENUM_SINGLE(WM8903_DRC_1, 4, 3, drc_qr_decay_text);
+
+static const char *drc_smoothing_text[] = {
+ "Low", "Medium", "High"
+};
+
+static const struct soc_enum drc_smoothing =
+ SOC_ENUM_SINGLE(WM8903_DRC_0, 11, 3, drc_smoothing_text);
+
+static const char *soft_mute_text[] = {
+ "Fast (fs/2)", "Slow (fs/32)"
+};
+
+static const struct soc_enum soft_mute =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 10, 2, soft_mute_text);
+
+static const char *mute_mode_text[] = {
+ "Hard", "Soft"
+};
+
+static const struct soc_enum mute_mode =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 9, 2, mute_mode_text);
+
+static const char *companding_text[] = {
+ "ulaw", "alaw"
+};
+
+static const struct soc_enum dac_companding =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 0, 2, companding_text);
+
+static const struct soc_enum adc_companding =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 2, 2, companding_text);
+
+static const char *input_mode_text[] = {
+ "Single-Ended", "Differential Line", "Differential Mic"
+};
+
+static const struct soc_enum linput_mode_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 0, 3, input_mode_text);
+
+static const struct soc_enum rinput_mode_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 0, 3, input_mode_text);
+
+static const char *linput_mux_text[] = {
+ "IN1L", "IN2L", "IN3L"
+};
+
+static const struct soc_enum linput_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 2, 3, linput_mux_text);
+
+static const struct soc_enum linput_inv_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 4, 3, linput_mux_text);
+
+static const char *rinput_mux_text[] = {
+ "IN1R", "IN2R", "IN3R"
+};
+
+static const struct soc_enum rinput_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 2, 3, rinput_mux_text);
+
+static const struct soc_enum rinput_inv_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text);
+
+
+static const char *sidetone_text[] = {
+ "None", "Left", "Right"
+};
+
+static const struct soc_enum lsidetone_enum =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 2, 3, sidetone_text);
+
+static const struct soc_enum rsidetone_enum =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text);
+
+static const char *adcinput_text[] = {
+ "ADC", "DMIC"
+};
+
+static const struct soc_enum adcinput_enum =
+ SOC_ENUM_SINGLE(WM8903_CLOCK_RATE_TEST_4, 9, 2, adcinput_text);
+
+static const char *aif_text[] = {
+ "Left", "Right"
+};
+
+static const struct soc_enum lcapture_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 7, 2, aif_text);
+
+static const struct soc_enum rcapture_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 6, 2, aif_text);
+
+static const struct soc_enum lplay_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 5, 2, aif_text);
+
+static const struct soc_enum rplay_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 4, 2, aif_text);
+
+static const struct snd_kcontrol_new wm8903_snd_controls[] = {
+
+/* Input PGAs - No TLV since the scale depends on PGA mode */
+SOC_SINGLE("Left Input PGA Switch", WM8903_ANALOGUE_LEFT_INPUT_0,
+ 7, 1, 1),
+SOC_SINGLE("Left Input PGA Volume", WM8903_ANALOGUE_LEFT_INPUT_0,
+ 0, 31, 0),
+SOC_SINGLE("Left Input PGA Common Mode Switch", WM8903_ANALOGUE_LEFT_INPUT_1,
+ 6, 1, 0),
+
+SOC_SINGLE("Right Input PGA Switch", WM8903_ANALOGUE_RIGHT_INPUT_0,
+ 7, 1, 1),
+SOC_SINGLE("Right Input PGA Volume", WM8903_ANALOGUE_RIGHT_INPUT_0,
+ 0, 31, 0),
+SOC_SINGLE("Right Input PGA Common Mode Switch", WM8903_ANALOGUE_RIGHT_INPUT_1,
+ 6, 1, 0),
+
+/* ADCs */
+SOC_ENUM("ADC OSR", adc_osr),
+SOC_SINGLE("HPF Switch", WM8903_ADC_DIGITAL_0, 4, 1, 0),
+SOC_ENUM("HPF Mode", hpf_mode),
+SOC_SINGLE("DRC Switch", WM8903_DRC_0, 15, 1, 0),
+SOC_ENUM("DRC Compressor Slope R0", drc_slope_r0),
+SOC_ENUM("DRC Compressor Slope R1", drc_slope_r1),
+SOC_SINGLE_TLV("DRC Compressor Threshold Volume", WM8903_DRC_3, 5, 124, 1,
+ drc_tlv_thresh),
+SOC_SINGLE_TLV("DRC Volume", WM8903_DRC_3, 0, 30, 1, drc_tlv_amp),
+SOC_SINGLE_TLV("DRC Minimum Gain Volume", WM8903_DRC_1, 2, 3, 1, drc_tlv_min),
+SOC_SINGLE_TLV("DRC Maximum Gain Volume", WM8903_DRC_1, 0, 3, 0, drc_tlv_max),
+SOC_ENUM("DRC Attack Rate", drc_attack),
+SOC_ENUM("DRC Decay Rate", drc_decay),
+SOC_ENUM("DRC FF Delay", drc_ff_delay),
+SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0),
+SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0),
+SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max),
+SOC_ENUM("DRC QR Decay Rate", drc_qr_decay),
+SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0),
+SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0),
+SOC_ENUM("DRC Smoothing Threshold", drc_smoothing),
+SOC_SINGLE_TLV("DRC Startup Volume", WM8903_DRC_0, 6, 18, 0, drc_tlv_startup),
+
+SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT,
+ WM8903_ADC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv),
+SOC_ENUM("ADC Companding Mode", adc_companding),
+SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0),
+
+SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8903_DAC_DIGITAL_0, 4, 8,
+ 12, 0, digital_sidetone_tlv),
+
+/* DAC */
+SOC_ENUM("DAC OSR", dac_osr),
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT,
+ WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv),
+SOC_ENUM("DAC Soft Mute Rate", soft_mute),
+SOC_ENUM("DAC Mute Mode", mute_mode),
+SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0),
+SOC_ENUM("DAC Companding Mode", dac_companding),
+SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0),
+SOC_SINGLE_BOOL_EXT("Playback Deemphasis Switch", 0,
+ wm8903_get_deemph, wm8903_put_deemph),
+
+/* Headphones */
+SOC_DOUBLE_R("Headphone Switch",
+ WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+ 8, 1, 1),
+SOC_DOUBLE_R("Headphone ZC Switch",
+ WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+ 6, 1, 0),
+SOC_DOUBLE_R_TLV("Headphone Volume",
+ WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+ 0, 63, 0, out_tlv),
+
+/* Line out */
+SOC_DOUBLE_R("Line Out Switch",
+ WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+ 8, 1, 1),
+SOC_DOUBLE_R("Line Out ZC Switch",
+ WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+ 6, 1, 0),
+SOC_DOUBLE_R_TLV("Line Out Volume",
+ WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+ 0, 63, 0, out_tlv),
+
+/* Speaker */
+SOC_DOUBLE_R("Speaker Switch",
+ WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 8, 1, 1),
+SOC_DOUBLE_R("Speaker ZC Switch",
+ WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 6, 1, 0),
+SOC_DOUBLE_R_TLV("Speaker Volume",
+ WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT,
+ 0, 63, 0, out_tlv),
+};
+
+static const struct snd_kcontrol_new linput_mode_mux =
+ SOC_DAPM_ENUM("Left Input Mode Mux", linput_mode_enum);
+
+static const struct snd_kcontrol_new rinput_mode_mux =
+ SOC_DAPM_ENUM("Right Input Mode Mux", rinput_mode_enum);
+
+static const struct snd_kcontrol_new linput_mux =
+ SOC_DAPM_ENUM("Left Input Mux", linput_enum);
+
+static const struct snd_kcontrol_new linput_inv_mux =
+ SOC_DAPM_ENUM("Left Inverting Input Mux", linput_inv_enum);
+
+static const struct snd_kcontrol_new rinput_mux =
+ SOC_DAPM_ENUM("Right Input Mux", rinput_enum);
+
+static const struct snd_kcontrol_new rinput_inv_mux =
+ SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum);
+
+static const struct snd_kcontrol_new lsidetone_mux =
+ SOC_DAPM_ENUM("DACL Sidetone Mux", lsidetone_enum);
+
+static const struct snd_kcontrol_new rsidetone_mux =
+ SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum);
+
+static const struct snd_kcontrol_new adcinput_mux =
+ SOC_DAPM_ENUM("ADC Input", adcinput_enum);
+
+static const struct snd_kcontrol_new lcapture_mux =
+ SOC_DAPM_ENUM("Left Capture Mux", lcapture_enum);
+
+static const struct snd_kcontrol_new rcapture_mux =
+ SOC_DAPM_ENUM("Right Capture Mux", rcapture_enum);
+
+static const struct snd_kcontrol_new lplay_mux =
+ SOC_DAPM_ENUM("Left Playback Mux", lplay_enum);
+
+static const struct snd_kcontrol_new rplay_mux =
+ SOC_DAPM_ENUM("Right Playback Mux", rplay_enum);
+
+static const struct snd_kcontrol_new left_output_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0),
+SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0),
+SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_output_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 2, 1, 0),
+SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0),
+SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 2, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 1, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 2, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0,
+ 0, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1L"),
+SND_SOC_DAPM_INPUT("IN1R"),
+SND_SOC_DAPM_INPUT("IN2L"),
+SND_SOC_DAPM_INPUT("IN2R"),
+SND_SOC_DAPM_INPUT("IN3L"),
+SND_SOC_DAPM_INPUT("IN3R"),
+SND_SOC_DAPM_INPUT("DMICDAT"),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LINEOUTL"),
+SND_SOC_DAPM_OUTPUT("LINEOUTR"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8903_MIC_BIAS_CONTROL_0, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Input Mux", SND_SOC_NOPM, 0, 0, &linput_mux),
+SND_SOC_DAPM_MUX("Left Input Inverting Mux", SND_SOC_NOPM, 0, 0,
+ &linput_inv_mux),
+SND_SOC_DAPM_MUX("Left Input Mode Mux", SND_SOC_NOPM, 0, 0, &linput_mode_mux),
+
+SND_SOC_DAPM_MUX("Right Input Mux", SND_SOC_NOPM, 0, 0, &rinput_mux),
+SND_SOC_DAPM_MUX("Right Input Inverting Mux", SND_SOC_NOPM, 0, 0,
+ &rinput_inv_mux),
+SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux),
+
+SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_MUX("Left ADC Input", SND_SOC_NOPM, 0, 0, &adcinput_mux),
+SND_SOC_DAPM_MUX("Right ADC Input", SND_SOC_NOPM, 0, 0, &adcinput_mux),
+
+SND_SOC_DAPM_ADC("ADCL", NULL, WM8903_POWER_MANAGEMENT_6, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, WM8903_POWER_MANAGEMENT_6, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lcapture_mux),
+SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rcapture_mux),
+
+SND_SOC_DAPM_AIF_OUT("AIFTXL", "Left HiFi Capture", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIFTXR", "Right HiFi Capture", 0, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux),
+SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux),
+
+SND_SOC_DAPM_AIF_IN("AIFRXL", "Left Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIFRXR", "Right Playback", 0, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Playback Mux", SND_SOC_NOPM, 0, 0, &lplay_mux),
+SND_SOC_DAPM_MUX("Right Playback Mux", SND_SOC_NOPM, 0, 0, &rplay_mux),
+
+SND_SOC_DAPM_DAC("DACL", NULL, WM8903_POWER_MANAGEMENT_6, 3, 0),
+SND_SOC_DAPM_DAC("DACR", NULL, WM8903_POWER_MANAGEMENT_6, 2, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0,
+ left_output_mixer, ARRAY_SIZE(left_output_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8903_POWER_MANAGEMENT_1, 0, 0,
+ right_output_mixer, ARRAY_SIZE(right_output_mixer)),
+
+SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0,
+ left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
+SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0,
+ right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
+
+SND_SOC_DAPM_PGA_S("Left Headphone Output PGA", 0, WM8903_POWER_MANAGEMENT_2,
+ 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("Right Headphone Output PGA", 0, WM8903_POWER_MANAGEMENT_2,
+ 0, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_S("Left Line Output PGA", 0, WM8903_POWER_MANAGEMENT_3, 1, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("Right Line Output PGA", 0, WM8903_POWER_MANAGEMENT_3, 0, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_PGA_S("HPL_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_ENA_DLY", 2, WM8903_ANALOGUE_HP_0, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_ENA", 1, WM8903_ANALOGUE_HP_0, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPR_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 3, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPR_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPR_ENA_DLY", 2, WM8903_ANALOGUE_HP_0, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPR_ENA", 1, WM8903_ANALOGUE_HP_0, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_S("LINEOUTL_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 7, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 6, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_DLY", 2, WM8903_ANALOGUE_LINEOUT_0, 5, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTL_ENA", 1, WM8903_ANALOGUE_LINEOUT_0, 4, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTR_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 3, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 2, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_DLY", 2, WM8903_ANALOGUE_LINEOUT_0, 1, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTR_ENA", 1, WM8903_ANALOGUE_LINEOUT_0, 0, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("DCS Master", WM8903_DC_SERVO_0, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_DCS", 3, SND_SOC_NOPM, 3, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("HPR_DCS", 3, SND_SOC_NOPM, 2, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("LINEOUTL_DCS", 3, SND_SOC_NOPM, 1, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("LINEOUTR_DCS", 3, SND_SOC_NOPM, 0, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0,
+ wm8903_cp_event, SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8903_CLOCK_RATES_2, 2, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route wm8903_intercon[] = {
+
+ { "CLK_DSP", NULL, "CLK_SYS" },
+ { "Mic Bias", NULL, "CLK_SYS" },
+ { "HPL_DCS", NULL, "CLK_SYS" },
+ { "HPR_DCS", NULL, "CLK_SYS" },
+ { "LINEOUTL_DCS", NULL, "CLK_SYS" },
+ { "LINEOUTR_DCS", NULL, "CLK_SYS" },
+
+ { "Left Input Mux", "IN1L", "IN1L" },
+ { "Left Input Mux", "IN2L", "IN2L" },
+ { "Left Input Mux", "IN3L", "IN3L" },
+
+ { "Left Input Inverting Mux", "IN1L", "IN1L" },
+ { "Left Input Inverting Mux", "IN2L", "IN2L" },
+ { "Left Input Inverting Mux", "IN3L", "IN3L" },
+
+ { "Right Input Mux", "IN1R", "IN1R" },
+ { "Right Input Mux", "IN2R", "IN2R" },
+ { "Right Input Mux", "IN3R", "IN3R" },
+
+ { "Right Input Inverting Mux", "IN1R", "IN1R" },
+ { "Right Input Inverting Mux", "IN2R", "IN2R" },
+ { "Right Input Inverting Mux", "IN3R", "IN3R" },
+
+ { "Left Input Mode Mux", "Single-Ended", "Left Input Inverting Mux" },
+ { "Left Input Mode Mux", "Differential Line",
+ "Left Input Mux" },
+ { "Left Input Mode Mux", "Differential Line",
+ "Left Input Inverting Mux" },
+ { "Left Input Mode Mux", "Differential Mic",
+ "Left Input Mux" },
+ { "Left Input Mode Mux", "Differential Mic",
+ "Left Input Inverting Mux" },
+
+ { "Right Input Mode Mux", "Single-Ended",
+ "Right Input Inverting Mux" },
+ { "Right Input Mode Mux", "Differential Line",
+ "Right Input Mux" },
+ { "Right Input Mode Mux", "Differential Line",
+ "Right Input Inverting Mux" },
+ { "Right Input Mode Mux", "Differential Mic",
+ "Right Input Mux" },
+ { "Right Input Mode Mux", "Differential Mic",
+ "Right Input Inverting Mux" },
+
+ { "Left Input PGA", NULL, "Left Input Mode Mux" },
+ { "Right Input PGA", NULL, "Right Input Mode Mux" },
+
+ { "Left ADC Input", "ADC", "Left Input PGA" },
+ { "Left ADC Input", "DMIC", "DMICDAT" },
+ { "Right ADC Input", "ADC", "Right Input PGA" },
+ { "Right ADC Input", "DMIC", "DMICDAT" },
+
+ { "Left Capture Mux", "Left", "ADCL" },
+ { "Left Capture Mux", "Right", "ADCR" },
+
+ { "Right Capture Mux", "Left", "ADCL" },
+ { "Right Capture Mux", "Right", "ADCR" },
+
+ { "AIFTXL", NULL, "Left Capture Mux" },
+ { "AIFTXR", NULL, "Right Capture Mux" },
+
+ { "ADCL", NULL, "Left ADC Input" },
+ { "ADCL", NULL, "CLK_DSP" },
+ { "ADCR", NULL, "Right ADC Input" },
+ { "ADCR", NULL, "CLK_DSP" },
+
+ { "Left Playback Mux", "Left", "AIFRXL" },
+ { "Left Playback Mux", "Right", "AIFRXR" },
+
+ { "Right Playback Mux", "Left", "AIFRXL" },
+ { "Right Playback Mux", "Right", "AIFRXR" },
+
+ { "DACL Sidetone", "Left", "ADCL" },
+ { "DACL Sidetone", "Right", "ADCR" },
+ { "DACR Sidetone", "Left", "ADCL" },
+ { "DACR Sidetone", "Right", "ADCR" },
+
+ { "DACL", NULL, "Left Playback Mux" },
+ { "DACL", NULL, "DACL Sidetone" },
+ { "DACL", NULL, "CLK_DSP" },
+
+ { "DACR", NULL, "Right Playback Mux" },
+ { "DACR", NULL, "DACR Sidetone" },
+ { "DACR", NULL, "CLK_DSP" },
+
+ { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Left Output Mixer", "DACL Switch", "DACL" },
+ { "Left Output Mixer", "DACR Switch", "DACR" },
+
+ { "Right Output Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Right Output Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Right Output Mixer", "DACL Switch", "DACL" },
+ { "Right Output Mixer", "DACR Switch", "DACR" },
+
+ { "Left Speaker Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Left Speaker Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Left Speaker Mixer", "DACL Switch", "DACL" },
+ { "Left Speaker Mixer", "DACR Switch", "DACR" },
+
+ { "Right Speaker Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Right Speaker Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Right Speaker Mixer", "DACL Switch", "DACL" },
+ { "Right Speaker Mixer", "DACR Switch", "DACR" },
+
+ { "Left Line Output PGA", NULL, "Left Output Mixer" },
+ { "Right Line Output PGA", NULL, "Right Output Mixer" },
+
+ { "Left Headphone Output PGA", NULL, "Left Output Mixer" },
+ { "Right Headphone Output PGA", NULL, "Right Output Mixer" },
+
+ { "Left Speaker PGA", NULL, "Left Speaker Mixer" },
+ { "Right Speaker PGA", NULL, "Right Speaker Mixer" },
+
+ { "HPL_ENA", NULL, "Left Headphone Output PGA" },
+ { "HPR_ENA", NULL, "Right Headphone Output PGA" },
+ { "HPL_ENA_DLY", NULL, "HPL_ENA" },
+ { "HPR_ENA_DLY", NULL, "HPR_ENA" },
+ { "LINEOUTL_ENA", NULL, "Left Line Output PGA" },
+ { "LINEOUTR_ENA", NULL, "Right Line Output PGA" },
+ { "LINEOUTL_ENA_DLY", NULL, "LINEOUTL_ENA" },
+ { "LINEOUTR_ENA_DLY", NULL, "LINEOUTR_ENA" },
+
+ { "HPL_DCS", NULL, "DCS Master" },
+ { "HPR_DCS", NULL, "DCS Master" },
+ { "LINEOUTL_DCS", NULL, "DCS Master" },
+ { "LINEOUTR_DCS", NULL, "DCS Master" },
+
+ { "HPL_DCS", NULL, "HPL_ENA_DLY" },
+ { "HPR_DCS", NULL, "HPR_ENA_DLY" },
+ { "LINEOUTL_DCS", NULL, "LINEOUTL_ENA_DLY" },
+ { "LINEOUTR_DCS", NULL, "LINEOUTR_ENA_DLY" },
+
+ { "HPL_ENA_OUTP", NULL, "HPL_DCS" },
+ { "HPR_ENA_OUTP", NULL, "HPR_DCS" },
+ { "LINEOUTL_ENA_OUTP", NULL, "LINEOUTL_DCS" },
+ { "LINEOUTR_ENA_OUTP", NULL, "LINEOUTR_DCS" },
+
+ { "HPL_RMV_SHORT", NULL, "HPL_ENA_OUTP" },
+ { "HPR_RMV_SHORT", NULL, "HPR_ENA_OUTP" },
+ { "LINEOUTL_RMV_SHORT", NULL, "LINEOUTL_ENA_OUTP" },
+ { "LINEOUTR_RMV_SHORT", NULL, "LINEOUTR_ENA_OUTP" },
+
+ { "HPOUTL", NULL, "HPL_RMV_SHORT" },
+ { "HPOUTR", NULL, "HPR_RMV_SHORT" },
+ { "LINEOUTL", NULL, "LINEOUTL_RMV_SHORT" },
+ { "LINEOUTR", NULL, "LINEOUTR_RMV_SHORT" },
+
+ { "LOP", NULL, "Left Speaker PGA" },
+ { "LON", NULL, "Left Speaker PGA" },
+
+ { "ROP", NULL, "Right Speaker PGA" },
+ { "RON", NULL, "Right Speaker PGA" },
+
+ { "Left Headphone Output PGA", NULL, "Charge Pump" },
+ { "Right Headphone Output PGA", NULL, "Charge Pump" },
+ { "Left Line Output PGA", NULL, "Charge Pump" },
+ { "Right Line Output PGA", NULL, "Charge Pump" },
+};
+
+static int wm8903_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_RES_MASK,
+ WM8903_VMID_RES_50K);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0,
+ WM8903_POBCTRL | WM8903_ISEL_MASK |
+ WM8903_STARTUP_BIAS_ENA |
+ WM8903_BIAS_ENA,
+ WM8903_POBCTRL |
+ (2 << WM8903_ISEL_SHIFT) |
+ WM8903_STARTUP_BIAS_ENA);
+
+ snd_soc_update_bits(codec,
+ WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0,
+ WM8903_SPK_DISCHARGE,
+ WM8903_SPK_DISCHARGE);
+
+ msleep(33);
+
+ snd_soc_update_bits(codec, WM8903_POWER_MANAGEMENT_5,
+ WM8903_SPKL_ENA | WM8903_SPKR_ENA,
+ WM8903_SPKL_ENA | WM8903_SPKR_ENA);
+
+ snd_soc_update_bits(codec,
+ WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0,
+ WM8903_SPK_DISCHARGE, 0);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_TIE_ENA |
+ WM8903_BUFIO_ENA |
+ WM8903_VMID_IO_ENA |
+ WM8903_VMID_SOFT_MASK |
+ WM8903_VMID_RES_MASK |
+ WM8903_VMID_BUF_ENA,
+ WM8903_VMID_TIE_ENA |
+ WM8903_BUFIO_ENA |
+ WM8903_VMID_IO_ENA |
+ (2 << WM8903_VMID_SOFT_SHIFT) |
+ WM8903_VMID_RES_250K |
+ WM8903_VMID_BUF_ENA);
+
+ msleep(129);
+
+ snd_soc_update_bits(codec, WM8903_POWER_MANAGEMENT_5,
+ WM8903_SPKL_ENA | WM8903_SPKR_ENA,
+ 0);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_SOFT_MASK, 0);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_RES_MASK,
+ WM8903_VMID_RES_50K);
+
+ snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0,
+ WM8903_BIAS_ENA | WM8903_POBCTRL,
+ WM8903_BIAS_ENA);
+
+ /* By default no bypass paths are enabled so
+ * enable Class W support.
+ */
+ dev_dbg(codec->dev, "Enabling Class W\n");
+ snd_soc_update_bits(codec, WM8903_CLASS_W_0,
+ WM8903_CP_DYN_FREQ |
+ WM8903_CP_DYN_V,
+ WM8903_CP_DYN_FREQ |
+ WM8903_CP_DYN_V);
+ }
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_RES_MASK,
+ WM8903_VMID_RES_250K);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0,
+ WM8903_BIAS_ENA, 0);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_SOFT_MASK,
+ 2 << WM8903_VMID_SOFT_SHIFT);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_BUF_ENA, 0);
+
+ msleep(290);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_TIE_ENA | WM8903_BUFIO_ENA |
+ WM8903_VMID_IO_ENA | WM8903_VMID_RES_MASK |
+ WM8903_VMID_SOFT_MASK |
+ WM8903_VMID_BUF_ENA, 0);
+
+ snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0,
+ WM8903_STARTUP_BIAS_ENA, 0);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static int wm8903_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+
+ wm8903->sysclk = freq;
+
+ return 0;
+}
+
+static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 aif1 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_1);
+
+ aif1 &= ~(WM8903_LRCLK_DIR | WM8903_BCLK_DIR | WM8903_AIF_FMT_MASK |
+ WM8903_AIF_LRCLK_INV | WM8903_AIF_BCLK_INV);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ aif1 |= WM8903_LRCLK_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif1 |= WM8903_LRCLK_DIR | WM8903_BCLK_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ aif1 |= WM8903_BCLK_DIR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ aif1 |= 0x3;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ aif1 |= 0x3 | WM8903_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif1 |= 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ aif1 |= 0x1;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8903_AIF_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif1 |= WM8903_AIF_BCLK_INV | WM8903_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8903_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif1 |= WM8903_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
+
+ return 0;
+}
+
+static int wm8903_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ reg = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
+
+ if (mute)
+ reg |= WM8903_DAC_MUTE;
+ else
+ reg &= ~WM8903_DAC_MUTE;
+
+ snd_soc_write(codec, WM8903_DAC_DIGITAL_1, reg);
+
+ return 0;
+}
+
+/* Lookup table for CLK_SYS/fs ratio. 256fs or more is recommended
+ * for optimal performance so we list the lower rates first and match
+ * on the last match we find. */
+static struct {
+ int div;
+ int rate;
+ int mode;
+ int mclk_div;
+} clk_sys_ratios[] = {
+ { 64, 0x0, 0x0, 1 },
+ { 68, 0x0, 0x1, 1 },
+ { 125, 0x0, 0x2, 1 },
+ { 128, 0x1, 0x0, 1 },
+ { 136, 0x1, 0x1, 1 },
+ { 192, 0x2, 0x0, 1 },
+ { 204, 0x2, 0x1, 1 },
+
+ { 64, 0x0, 0x0, 2 },
+ { 68, 0x0, 0x1, 2 },
+ { 125, 0x0, 0x2, 2 },
+ { 128, 0x1, 0x0, 2 },
+ { 136, 0x1, 0x1, 2 },
+ { 192, 0x2, 0x0, 2 },
+ { 204, 0x2, 0x1, 2 },
+
+ { 250, 0x2, 0x2, 1 },
+ { 256, 0x3, 0x0, 1 },
+ { 272, 0x3, 0x1, 1 },
+ { 384, 0x4, 0x0, 1 },
+ { 408, 0x4, 0x1, 1 },
+ { 375, 0x4, 0x2, 1 },
+ { 512, 0x5, 0x0, 1 },
+ { 544, 0x5, 0x1, 1 },
+ { 500, 0x5, 0x2, 1 },
+ { 768, 0x6, 0x0, 1 },
+ { 816, 0x6, 0x1, 1 },
+ { 750, 0x6, 0x2, 1 },
+ { 1024, 0x7, 0x0, 1 },
+ { 1088, 0x7, 0x1, 1 },
+ { 1000, 0x7, 0x2, 1 },
+ { 1408, 0x8, 0x0, 1 },
+ { 1496, 0x8, 0x1, 1 },
+ { 1536, 0x9, 0x0, 1 },
+ { 1632, 0x9, 0x1, 1 },
+ { 1500, 0x9, 0x2, 1 },
+
+ { 250, 0x2, 0x2, 2 },
+ { 256, 0x3, 0x0, 2 },
+ { 272, 0x3, 0x1, 2 },
+ { 384, 0x4, 0x0, 2 },
+ { 408, 0x4, 0x1, 2 },
+ { 375, 0x4, 0x2, 2 },
+ { 512, 0x5, 0x0, 2 },
+ { 544, 0x5, 0x1, 2 },
+ { 500, 0x5, 0x2, 2 },
+ { 768, 0x6, 0x0, 2 },
+ { 816, 0x6, 0x1, 2 },
+ { 750, 0x6, 0x2, 2 },
+ { 1024, 0x7, 0x0, 2 },
+ { 1088, 0x7, 0x1, 2 },
+ { 1000, 0x7, 0x2, 2 },
+ { 1408, 0x8, 0x0, 2 },
+ { 1496, 0x8, 0x1, 2 },
+ { 1536, 0x9, 0x0, 2 },
+ { 1632, 0x9, 0x1, 2 },
+ { 1500, 0x9, 0x2, 2 },
+};
+
+/* CLK_SYS/BCLK ratios - multiplied by 10 due to .5s */
+static struct {
+ int ratio;
+ int div;
+} bclk_divs[] = {
+ { 10, 0 },
+ { 20, 2 },
+ { 30, 3 },
+ { 40, 4 },
+ { 50, 5 },
+ { 60, 7 },
+ { 80, 8 },
+ { 100, 9 },
+ { 120, 11 },
+ { 160, 12 },
+ { 200, 13 },
+ { 220, 14 },
+ { 240, 15 },
+ { 300, 17 },
+ { 320, 18 },
+ { 440, 19 },
+ { 480, 20 },
+};
+
+/* Sample rates for DSP */
+static struct {
+ int rate;
+ int value;
+} sample_rates[] = {
+ { 8000, 0 },
+ { 11025, 1 },
+ { 12000, 2 },
+ { 16000, 3 },
+ { 22050, 4 },
+ { 24000, 5 },
+ { 32000, 6 },
+ { 44100, 7 },
+ { 48000, 8 },
+ { 88200, 9 },
+ { 96000, 10 },
+ { 0, 0 },
+};
+
+static int wm8903_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec =rtd->codec;
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int fs = params_rate(params);
+ int bclk;
+ int bclk_div;
+ int i;
+ int dsp_config;
+ int clk_config;
+ int best_val;
+ int cur_val;
+ int clk_sys;
+
+ u16 aif1 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_1);
+ u16 aif2 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_2);
+ u16 aif3 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_3);
+ u16 clock0 = snd_soc_read(codec, WM8903_CLOCK_RATES_0);
+ u16 clock1 = snd_soc_read(codec, WM8903_CLOCK_RATES_1);
+ u16 dac_digital1 = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
+
+ /* Enable sloping stopband filter for low sample rates */
+ if (fs <= 24000)
+ dac_digital1 |= WM8903_DAC_SB_FILT;
+ else
+ dac_digital1 &= ~WM8903_DAC_SB_FILT;
+
+ /* Configure sample rate logic for DSP - choose nearest rate */
+ dsp_config = 0;
+ best_val = abs(sample_rates[dsp_config].rate - fs);
+ for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+ cur_val = abs(sample_rates[i].rate - fs);
+ if (cur_val <= best_val) {
+ dsp_config = i;
+ best_val = cur_val;
+ }
+ }
+
+ dev_dbg(codec->dev, "DSP fs = %dHz\n", sample_rates[dsp_config].rate);
+ clock1 &= ~WM8903_SAMPLE_RATE_MASK;
+ clock1 |= sample_rates[dsp_config].value;
+
+ aif1 &= ~WM8903_AIF_WL_MASK;
+ bclk = 2 * fs;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bclk *= 16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ bclk *= 20;
+ aif1 |= 0x4;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ bclk *= 24;
+ aif1 |= 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bclk *= 32;
+ aif1 |= 0xc;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(codec->dev, "MCLK = %dHz, target sample rate = %dHz\n",
+ wm8903->sysclk, fs);
+
+ /* We may not have an MCLK which allows us to generate exactly
+ * the clock we want, particularly with USB derived inputs, so
+ * approximate.
+ */
+ clk_config = 0;
+ best_val = abs((wm8903->sysclk /
+ (clk_sys_ratios[0].mclk_div *
+ clk_sys_ratios[0].div)) - fs);
+ for (i = 1; i < ARRAY_SIZE(clk_sys_ratios); i++) {
+ cur_val = abs((wm8903->sysclk /
+ (clk_sys_ratios[i].mclk_div *
+ clk_sys_ratios[i].div)) - fs);
+
+ if (cur_val <= best_val) {
+ clk_config = i;
+ best_val = cur_val;
+ }
+ }
+
+ if (clk_sys_ratios[clk_config].mclk_div == 2) {
+ clock0 |= WM8903_MCLKDIV2;
+ clk_sys = wm8903->sysclk / 2;
+ } else {
+ clock0 &= ~WM8903_MCLKDIV2;
+ clk_sys = wm8903->sysclk;
+ }
+
+ clock1 &= ~(WM8903_CLK_SYS_RATE_MASK |
+ WM8903_CLK_SYS_MODE_MASK);
+ clock1 |= clk_sys_ratios[clk_config].rate << WM8903_CLK_SYS_RATE_SHIFT;
+ clock1 |= clk_sys_ratios[clk_config].mode << WM8903_CLK_SYS_MODE_SHIFT;
+
+ dev_dbg(codec->dev, "CLK_SYS_RATE=%x, CLK_SYS_MODE=%x div=%d\n",
+ clk_sys_ratios[clk_config].rate,
+ clk_sys_ratios[clk_config].mode,
+ clk_sys_ratios[clk_config].div);
+
+ dev_dbg(codec->dev, "Actual CLK_SYS = %dHz\n", clk_sys);
+
+ /* We may not get quite the right frequency if using
+ * approximate clocks so look for the closest match that is
+ * higher than the target (we need to ensure that there enough
+ * BCLKs to clock out the samples).
+ */
+ bclk_div = 0;
+ best_val = ((clk_sys * 10) / bclk_divs[0].ratio) - bclk;
+ i = 1;
+ while (i < ARRAY_SIZE(bclk_divs)) {
+ cur_val = ((clk_sys * 10) / bclk_divs[i].ratio) - bclk;
+ if (cur_val < 0) /* BCLK table is sorted */
+ break;
+ bclk_div = i;
+ best_val = cur_val;
+ i++;
+ }
+
+ aif2 &= ~WM8903_BCLK_DIV_MASK;
+ aif3 &= ~WM8903_LRCLK_RATE_MASK;
+
+ dev_dbg(codec->dev, "BCLK ratio %d for %dHz - actual BCLK = %dHz\n",
+ bclk_divs[bclk_div].ratio / 10, bclk,
+ (clk_sys * 10) / bclk_divs[bclk_div].ratio);
+
+ aif2 |= bclk_divs[bclk_div].div;
+ aif3 |= bclk / fs;
+
+ wm8903->fs = params_rate(params);
+ wm8903_set_deemph(codec);
+
+ snd_soc_write(codec, WM8903_CLOCK_RATES_0, clock0);
+ snd_soc_write(codec, WM8903_CLOCK_RATES_1, clock1);
+ snd_soc_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
+ snd_soc_write(codec, WM8903_AUDIO_INTERFACE_2, aif2);
+ snd_soc_write(codec, WM8903_AUDIO_INTERFACE_3, aif3);
+ snd_soc_write(codec, WM8903_DAC_DIGITAL_1, dac_digital1);
+
+ return 0;
+}
+
+/**
+ * wm8903_mic_detect - Enable microphone detection via the WM8903 IRQ
+ *
+ * @codec: WM8903 codec
+ * @jack: jack to report detection events on
+ * @det: value to report for presence detection
+ * @shrt: value to report for short detection
+ *
+ * Enable microphone detection via IRQ on the WM8903. If GPIOs are
+ * being used to bring out signals to the processor then only platform
+ * data configuration is needed for WM8903 and processor GPIOs should
+ * be configured using snd_soc_jack_add_gpios() instead.
+ *
+ * The current threasholds for detection should be configured using
+ * micdet_cfg in the platform data. Using this function will force on
+ * the microphone bias for the device.
+ */
+int wm8903_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+ int det, int shrt)
+{
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int irq_mask = WM8903_MICDET_EINT | WM8903_MICSHRT_EINT;
+
+ dev_dbg(codec->dev, "Enabling microphone detection: %x %x\n",
+ det, shrt);
+
+ /* Store the configuration */
+ wm8903->mic_jack = jack;
+ wm8903->mic_det = det;
+ wm8903->mic_short = shrt;
+
+ /* Enable interrupts we've got a report configured for */
+ if (det)
+ irq_mask &= ~WM8903_MICDET_EINT;
+ if (shrt)
+ irq_mask &= ~WM8903_MICSHRT_EINT;
+
+ snd_soc_update_bits(codec, WM8903_INTERRUPT_STATUS_1_MASK,
+ WM8903_MICDET_EINT | WM8903_MICSHRT_EINT,
+ irq_mask);
+
+ if (det || shrt) {
+ /* Enable mic detection, this may not have been set through
+ * platform data (eg, if the defaults are OK). */
+ snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0,
+ WM8903_WSEQ_ENA, WM8903_WSEQ_ENA);
+ snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0,
+ WM8903_MICDET_ENA, WM8903_MICDET_ENA);
+ } else {
+ snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0,
+ WM8903_MICDET_ENA, 0);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8903_mic_detect);
+
+static irqreturn_t wm8903_irq(int irq, void *data)
+{
+ struct snd_soc_codec *codec = data;
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int mic_report;
+ int int_pol;
+ int int_val = 0;
+ int mask = ~snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1_MASK);
+
+ int_val = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1) & mask;
+
+ if (int_val & WM8903_WSEQ_BUSY_EINT) {
+ dev_warn(codec->dev, "Write sequencer done\n");
+ }
+
+ /*
+ * The rest is microphone jack detection. We need to manually
+ * invert the polarity of the interrupt after each event - to
+ * simplify the code keep track of the last state we reported
+ * and just invert the relevant bits in both the report and
+ * the polarity register.
+ */
+ mic_report = wm8903->mic_last_report;
+ int_pol = snd_soc_read(codec, WM8903_INTERRUPT_POLARITY_1);
+
+#ifndef CONFIG_SND_SOC_WM8903_MODULE
+ if (int_val & (WM8903_MICSHRT_EINT | WM8903_MICDET_EINT))
+ trace_snd_soc_jack_irq(dev_name(codec->dev));
+#endif
+
+ if (int_val & WM8903_MICSHRT_EINT) {
+ dev_dbg(codec->dev, "Microphone short (pol=%x)\n", int_pol);
+
+ mic_report ^= wm8903->mic_short;
+ int_pol ^= WM8903_MICSHRT_INV;
+ }
+
+ if (int_val & WM8903_MICDET_EINT) {
+ dev_dbg(codec->dev, "Microphone detect (pol=%x)\n", int_pol);
+
+ mic_report ^= wm8903->mic_det;
+ int_pol ^= WM8903_MICDET_INV;
+
+ msleep(wm8903->mic_delay);
+ }
+
+ snd_soc_update_bits(codec, WM8903_INTERRUPT_POLARITY_1,
+ WM8903_MICSHRT_INV | WM8903_MICDET_INV, int_pol);
+
+ snd_soc_jack_report(wm8903->mic_jack, mic_report,
+ wm8903->mic_short | wm8903->mic_det);
+
+ wm8903->mic_last_report = mic_report;
+
+ return IRQ_HANDLED;
+}
+
+#define WM8903_PLAYBACK_RATES (SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000)
+
+#define WM8903_CAPTURE_RATES (SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM8903_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8903_dai_ops = {
+ .hw_params = wm8903_hw_params,
+ .digital_mute = wm8903_digital_mute,
+ .set_fmt = wm8903_set_dai_fmt,
+ .set_sysclk = wm8903_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8903_dai = {
+ .name = "wm8903-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8903_PLAYBACK_RATES,
+ .formats = WM8903_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8903_CAPTURE_RATES,
+ .formats = WM8903_FORMATS,
+ },
+ .ops = &wm8903_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int wm8903_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8903_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ u16 *reg_cache = codec->reg_cache;
+ u16 *tmp_cache = kmemdup(reg_cache, sizeof(wm8903_reg_defaults),
+ GFP_KERNEL);
+
+ /* Bring the codec back up to standby first to minimise pop/clicks */
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Sync back everything else */
+ if (tmp_cache) {
+ for (i = 2; i < ARRAY_SIZE(wm8903_reg_defaults); i++)
+ if (tmp_cache[i] != reg_cache[i])
+ snd_soc_write(codec, i, tmp_cache[i]);
+ kfree(tmp_cache);
+ } else {
+ dev_err(codec->dev, "Failed to allocate temporary cache\n");
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_GPIOLIB
+static inline struct wm8903_priv *gpio_to_wm8903(struct gpio_chip *chip)
+{
+ return container_of(chip, struct wm8903_priv, gpio_chip);
+}
+
+static int wm8903_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ if (offset >= WM8903_NUM_GPIO)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int wm8903_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+ unsigned int mask, val;
+
+ mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK;
+ val = (WM8903_GPn_FN_GPIO_INPUT << WM8903_GP1_FN_SHIFT) |
+ WM8903_GP1_DIR;
+
+ return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ mask, val);
+}
+
+static int wm8903_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+ int reg;
+
+ reg = snd_soc_read(codec, WM8903_GPIO_CONTROL_1 + offset);
+
+ return (reg & WM8903_GP1_LVL_MASK) >> WM8903_GP1_LVL_SHIFT;
+}
+
+static int wm8903_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+ unsigned int mask, val;
+
+ mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK | WM8903_GP1_LVL_MASK;
+ val = (WM8903_GPn_FN_GPIO_OUTPUT << WM8903_GP1_FN_SHIFT) |
+ (value << WM8903_GP2_LVL_SHIFT);
+
+ return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ mask, val);
+}
+
+static void wm8903_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+
+ snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ WM8903_GP1_LVL_MASK,
+ !!value << WM8903_GP1_LVL_SHIFT);
+}
+
+static struct gpio_chip wm8903_template_chip = {
+ .label = "wm8903",
+ .owner = THIS_MODULE,
+ .request = wm8903_gpio_request,
+ .direction_input = wm8903_gpio_direction_in,
+ .get = wm8903_gpio_get,
+ .direction_output = wm8903_gpio_direction_out,
+ .set = wm8903_gpio_set,
+ .can_sleep = 1,
+};
+
+static void wm8903_init_gpio(struct snd_soc_codec *codec)
+{
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev);
+ int ret;
+
+ wm8903->gpio_chip = wm8903_template_chip;
+ wm8903->gpio_chip.ngpio = WM8903_NUM_GPIO;
+ wm8903->gpio_chip.dev = codec->dev;
+
+ if (pdata && pdata->gpio_base)
+ wm8903->gpio_chip.base = pdata->gpio_base;
+ else
+ wm8903->gpio_chip.base = -1;
+
+ ret = gpiochip_add(&wm8903->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add GPIOs: %d\n", ret);
+}
+
+static void wm8903_free_gpio(struct snd_soc_codec *codec)
+{
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = gpiochip_remove(&wm8903->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to remove GPIOs: %d\n", ret);
+}
+#else
+static void wm8903_init_gpio(struct snd_soc_codec *codec)
+{
+}
+
+static void wm8903_free_gpio(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+static int wm8903_probe(struct snd_soc_codec *codec)
+{
+ struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev);
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int ret, i;
+ int trigger, irq_pol;
+ u16 val;
+
+ wm8903->codec = codec;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ val = snd_soc_read(codec, WM8903_SW_RESET_AND_ID);
+ if (val != wm8903_reg_defaults[WM8903_SW_RESET_AND_ID]) {
+ dev_err(codec->dev,
+ "Device with ID register %x is not a WM8903\n", val);
+ return -ENODEV;
+ }
+
+ val = snd_soc_read(codec, WM8903_REVISION_NUMBER);
+ dev_info(codec->dev, "WM8903 revision %c\n",
+ (val & WM8903_CHIP_REV_MASK) + 'A');
+
+ wm8903_reset(codec);
+
+ /* Set up GPIOs and microphone detection */
+ if (pdata) {
+ bool mic_gpio = false;
+
+ for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) {
+ if (pdata->gpio_cfg[i] == WM8903_GPIO_NO_CONFIG)
+ continue;
+
+ snd_soc_write(codec, WM8903_GPIO_CONTROL_1 + i,
+ pdata->gpio_cfg[i] & 0xffff);
+
+ val = (pdata->gpio_cfg[i] & WM8903_GP1_FN_MASK)
+ >> WM8903_GP1_FN_SHIFT;
+
+ switch (val) {
+ case WM8903_GPn_FN_MICBIAS_CURRENT_DETECT:
+ case WM8903_GPn_FN_MICBIAS_SHORT_DETECT:
+ mic_gpio = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ snd_soc_write(codec, WM8903_MIC_BIAS_CONTROL_0,
+ pdata->micdet_cfg);
+
+ /* Microphone detection needs the WSEQ clock */
+ if (pdata->micdet_cfg)
+ snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0,
+ WM8903_WSEQ_ENA, WM8903_WSEQ_ENA);
+
+ /* If microphone detection is enabled by pdata but
+ * detected via IRQ then interrupts can be lost before
+ * the machine driver has set up microphone detection
+ * IRQs as the IRQs are clear on read. The detection
+ * will be enabled when the machine driver configures.
+ */
+ WARN_ON(!mic_gpio && (pdata->micdet_cfg & WM8903_MICDET_ENA));
+
+ wm8903->mic_delay = pdata->micdet_delay;
+ }
+
+ if (wm8903->irq) {
+ if (pdata && pdata->irq_active_low) {
+ trigger = IRQF_TRIGGER_LOW;
+ irq_pol = WM8903_IRQ_POL;
+ } else {
+ trigger = IRQF_TRIGGER_HIGH;
+ irq_pol = 0;
+ }
+
+ snd_soc_update_bits(codec, WM8903_INTERRUPT_CONTROL,
+ WM8903_IRQ_POL, irq_pol);
+
+ ret = request_threaded_irq(wm8903->irq, NULL, wm8903_irq,
+ trigger | IRQF_ONESHOT,
+ "wm8903", codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request IRQ: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Enable write sequencer interrupts */
+ snd_soc_update_bits(codec, WM8903_INTERRUPT_STATUS_1_MASK,
+ WM8903_IM_WSEQ_BUSY_EINT, 0);
+ }
+
+ /* power on device */
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Latch volume update bits */
+ val = snd_soc_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT);
+ val |= WM8903_ADCVU;
+ snd_soc_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val);
+ snd_soc_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val);
+
+ val = snd_soc_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT);
+ val |= WM8903_DACVU;
+ snd_soc_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val);
+ snd_soc_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val);
+
+ val = snd_soc_read(codec, WM8903_ANALOGUE_OUT1_LEFT);
+ val |= WM8903_HPOUTVU;
+ snd_soc_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val);
+ snd_soc_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val);
+
+ val = snd_soc_read(codec, WM8903_ANALOGUE_OUT2_LEFT);
+ val |= WM8903_LINEOUTVU;
+ snd_soc_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val);
+ snd_soc_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val);
+
+ val = snd_soc_read(codec, WM8903_ANALOGUE_OUT3_LEFT);
+ val |= WM8903_SPKVU;
+ snd_soc_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val);
+ snd_soc_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val);
+
+ /* Enable DAC soft mute by default */
+ snd_soc_update_bits(codec, WM8903_DAC_DIGITAL_1,
+ WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE,
+ WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE);
+
+ snd_soc_add_controls(codec, wm8903_snd_controls,
+ ARRAY_SIZE(wm8903_snd_controls));
+
+ wm8903_init_gpio(codec);
+
+ return ret;
+}
+
+/* power down chip */
+static int wm8903_remove(struct snd_soc_codec *codec)
+{
+ wm8903_free_gpio(codec);
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8903 = {
+ .probe = wm8903_probe,
+ .remove = wm8903_remove,
+ .suspend = wm8903_suspend,
+ .resume = wm8903_resume,
+ .set_bias_level = wm8903_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8903_reg_defaults),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8903_reg_defaults,
+ .volatile_register = wm8903_volatile_register,
+ .seq_notifier = wm8903_seq_notifier,
+ .dapm_widgets = wm8903_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm8903_dapm_widgets),
+ .dapm_routes = wm8903_intercon,
+ .num_dapm_routes = ARRAY_SIZE(wm8903_intercon),
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8903_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8903_priv *wm8903;
+ int ret;
+
+ wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL);
+ if (wm8903 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8903);
+ wm8903->irq = i2c->irq;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8903, &wm8903_dai, 1);
+ if (ret < 0)
+ kfree(wm8903);
+ return ret;
+}
+
+static __devexit int wm8903_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8903_i2c_id[] = {
+ { "wm8903", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id);
+
+static struct i2c_driver wm8903_i2c_driver = {
+ .driver = {
+ .name = "wm8903",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8903_i2c_probe,
+ .remove = __devexit_p(wm8903_i2c_remove),
+ .id_table = wm8903_i2c_id,
+};
+#endif
+
+static int __init wm8903_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8903_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8903 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8903_modinit);
+
+static void __exit wm8903_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8903_i2c_driver);
+#endif
+}
+module_exit(wm8903_exit);
+
+MODULE_DESCRIPTION("ASoC WM8903 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.cm>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h
new file mode 100644
index 00000000..db949311
--- /dev/null
+++ b/sound/soc/codecs/wm8903.h
@@ -0,0 +1,1225 @@
+/*
+ * wm8903.h - WM8903 audio codec interface
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _WM8903_H
+#define _WM8903_H
+
+#include <linux/i2c.h>
+
+extern int wm8903_mic_detect(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack,
+ int det, int shrt);
+
+
+/*
+ * Register values.
+ */
+#define WM8903_SW_RESET_AND_ID 0x00
+#define WM8903_REVISION_NUMBER 0x01
+#define WM8903_BIAS_CONTROL_0 0x04
+#define WM8903_VMID_CONTROL_0 0x05
+#define WM8903_MIC_BIAS_CONTROL_0 0x06
+#define WM8903_ANALOGUE_DAC_0 0x08
+#define WM8903_ANALOGUE_ADC_0 0x0A
+#define WM8903_POWER_MANAGEMENT_0 0x0C
+#define WM8903_POWER_MANAGEMENT_1 0x0D
+#define WM8903_POWER_MANAGEMENT_2 0x0E
+#define WM8903_POWER_MANAGEMENT_3 0x0F
+#define WM8903_POWER_MANAGEMENT_4 0x10
+#define WM8903_POWER_MANAGEMENT_5 0x11
+#define WM8903_POWER_MANAGEMENT_6 0x12
+#define WM8903_CLOCK_RATES_0 0x14
+#define WM8903_CLOCK_RATES_1 0x15
+#define WM8903_CLOCK_RATES_2 0x16
+#define WM8903_AUDIO_INTERFACE_0 0x18
+#define WM8903_AUDIO_INTERFACE_1 0x19
+#define WM8903_AUDIO_INTERFACE_2 0x1A
+#define WM8903_AUDIO_INTERFACE_3 0x1B
+#define WM8903_DAC_DIGITAL_VOLUME_LEFT 0x1E
+#define WM8903_DAC_DIGITAL_VOLUME_RIGHT 0x1F
+#define WM8903_DAC_DIGITAL_0 0x20
+#define WM8903_DAC_DIGITAL_1 0x21
+#define WM8903_ADC_DIGITAL_VOLUME_LEFT 0x24
+#define WM8903_ADC_DIGITAL_VOLUME_RIGHT 0x25
+#define WM8903_ADC_DIGITAL_0 0x26
+#define WM8903_DIGITAL_MICROPHONE_0 0x27
+#define WM8903_DRC_0 0x28
+#define WM8903_DRC_1 0x29
+#define WM8903_DRC_2 0x2A
+#define WM8903_DRC_3 0x2B
+#define WM8903_ANALOGUE_LEFT_INPUT_0 0x2C
+#define WM8903_ANALOGUE_RIGHT_INPUT_0 0x2D
+#define WM8903_ANALOGUE_LEFT_INPUT_1 0x2E
+#define WM8903_ANALOGUE_RIGHT_INPUT_1 0x2F
+#define WM8903_ANALOGUE_LEFT_MIX_0 0x32
+#define WM8903_ANALOGUE_RIGHT_MIX_0 0x33
+#define WM8903_ANALOGUE_SPK_MIX_LEFT_0 0x34
+#define WM8903_ANALOGUE_SPK_MIX_LEFT_1 0x35
+#define WM8903_ANALOGUE_SPK_MIX_RIGHT_0 0x36
+#define WM8903_ANALOGUE_SPK_MIX_RIGHT_1 0x37
+#define WM8903_ANALOGUE_OUT1_LEFT 0x39
+#define WM8903_ANALOGUE_OUT1_RIGHT 0x3A
+#define WM8903_ANALOGUE_OUT2_LEFT 0x3B
+#define WM8903_ANALOGUE_OUT2_RIGHT 0x3C
+#define WM8903_ANALOGUE_OUT3_LEFT 0x3E
+#define WM8903_ANALOGUE_OUT3_RIGHT 0x3F
+#define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0 0x41
+#define WM8903_DC_SERVO_0 0x43
+#define WM8903_DC_SERVO_2 0x45
+#define WM8903_DC_SERVO_4 0x47
+#define WM8903_DC_SERVO_5 0x48
+#define WM8903_DC_SERVO_6 0x49
+#define WM8903_DC_SERVO_7 0x4A
+#define WM8903_DC_SERVO_READBACK_1 0x51
+#define WM8903_DC_SERVO_READBACK_2 0x52
+#define WM8903_DC_SERVO_READBACK_3 0x53
+#define WM8903_DC_SERVO_READBACK_4 0x54
+#define WM8903_ANALOGUE_HP_0 0x5A
+#define WM8903_ANALOGUE_LINEOUT_0 0x5E
+#define WM8903_CHARGE_PUMP_0 0x62
+#define WM8903_CLASS_W_0 0x68
+#define WM8903_WRITE_SEQUENCER_0 0x6C
+#define WM8903_WRITE_SEQUENCER_1 0x6D
+#define WM8903_WRITE_SEQUENCER_2 0x6E
+#define WM8903_WRITE_SEQUENCER_3 0x6F
+#define WM8903_WRITE_SEQUENCER_4 0x70
+#define WM8903_CONTROL_INTERFACE 0x72
+#define WM8903_GPIO_CONTROL_1 0x74
+#define WM8903_GPIO_CONTROL_2 0x75
+#define WM8903_GPIO_CONTROL_3 0x76
+#define WM8903_GPIO_CONTROL_4 0x77
+#define WM8903_GPIO_CONTROL_5 0x78
+#define WM8903_INTERRUPT_STATUS_1 0x79
+#define WM8903_INTERRUPT_STATUS_1_MASK 0x7A
+#define WM8903_INTERRUPT_POLARITY_1 0x7B
+#define WM8903_INTERRUPT_CONTROL 0x7E
+#define WM8903_CLOCK_RATE_TEST_4 0xA4
+#define WM8903_ANALOGUE_OUTPUT_BIAS_0 0xAC
+
+#define WM8903_REGISTER_COUNT 75
+#define WM8903_MAX_REGISTER 0xAC
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - SW Reset and ID
+ */
+#define WM8903_SW_RESET_DEV_ID1_MASK 0xFFFF /* SW_RESET_DEV_ID1 - [15:0] */
+#define WM8903_SW_RESET_DEV_ID1_SHIFT 0 /* SW_RESET_DEV_ID1 - [15:0] */
+#define WM8903_SW_RESET_DEV_ID1_WIDTH 16 /* SW_RESET_DEV_ID1 - [15:0] */
+
+/*
+ * R1 (0x01) - Revision Number
+ */
+#define WM8903_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */
+#define WM8903_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */
+#define WM8903_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */
+
+/*
+ * R4 (0x04) - Bias Control 0
+ */
+#define WM8903_POBCTRL 0x0010 /* POBCTRL */
+#define WM8903_POBCTRL_MASK 0x0010 /* POBCTRL */
+#define WM8903_POBCTRL_SHIFT 4 /* POBCTRL */
+#define WM8903_POBCTRL_WIDTH 1 /* POBCTRL */
+#define WM8903_ISEL_MASK 0x000C /* ISEL - [3:2] */
+#define WM8903_ISEL_SHIFT 2 /* ISEL - [3:2] */
+#define WM8903_ISEL_WIDTH 2 /* ISEL - [3:2] */
+#define WM8903_STARTUP_BIAS_ENA 0x0002 /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_MASK 0x0002 /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_SHIFT 1 /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */
+#define WM8903_BIAS_ENA 0x0001 /* BIAS_ENA */
+#define WM8903_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */
+#define WM8903_BIAS_ENA_SHIFT 0 /* BIAS_ENA */
+#define WM8903_BIAS_ENA_WIDTH 1 /* BIAS_ENA */
+
+/*
+ * R5 (0x05) - VMID Control 0
+ */
+#define WM8903_VMID_TIE_ENA 0x0080 /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_MASK 0x0080 /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_SHIFT 7 /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_WIDTH 1 /* VMID_TIE_ENA */
+#define WM8903_BUFIO_ENA 0x0040 /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_MASK 0x0040 /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_SHIFT 6 /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_WIDTH 1 /* BUFIO_ENA */
+#define WM8903_VMID_IO_ENA 0x0020 /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_MASK 0x0020 /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_SHIFT 5 /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_WIDTH 1 /* VMID_IO_ENA */
+#define WM8903_VMID_SOFT_MASK 0x0018 /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_SOFT_SHIFT 3 /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_SOFT_WIDTH 2 /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */
+#define WM8903_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */
+#define WM8903_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */
+#define WM8903_VMID_BUF_ENA 0x0001 /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_MASK 0x0001 /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_SHIFT 0 /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */
+
+#define WM8903_VMID_RES_50K 2
+#define WM8903_VMID_RES_250K 3
+#define WM8903_VMID_RES_5K 6
+
+/*
+ * R8 (0x08) - Analogue DAC 0
+ */
+#define WM8903_DACBIAS_SEL_MASK 0x0018 /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACBIAS_SEL_SHIFT 3 /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACBIAS_SEL_WIDTH 2 /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACVMID_BIAS_SEL_MASK 0x0006 /* DACVMID_BIAS_SEL - [2:1] */
+#define WM8903_DACVMID_BIAS_SEL_SHIFT 1 /* DACVMID_BIAS_SEL - [2:1] */
+#define WM8903_DACVMID_BIAS_SEL_WIDTH 2 /* DACVMID_BIAS_SEL - [2:1] */
+
+/*
+ * R10 (0x0A) - Analogue ADC 0
+ */
+#define WM8903_ADC_OSR128 0x0001 /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_MASK 0x0001 /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_SHIFT 0 /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */
+
+/*
+ * R12 (0x0C) - Power Management 0
+ */
+#define WM8903_INL_ENA 0x0002 /* INL_ENA */
+#define WM8903_INL_ENA_MASK 0x0002 /* INL_ENA */
+#define WM8903_INL_ENA_SHIFT 1 /* INL_ENA */
+#define WM8903_INL_ENA_WIDTH 1 /* INL_ENA */
+#define WM8903_INR_ENA 0x0001 /* INR_ENA */
+#define WM8903_INR_ENA_MASK 0x0001 /* INR_ENA */
+#define WM8903_INR_ENA_SHIFT 0 /* INR_ENA */
+#define WM8903_INR_ENA_WIDTH 1 /* INR_ENA */
+
+/*
+ * R13 (0x0D) - Power Management 1
+ */
+#define WM8903_MIXOUTL_ENA 0x0002 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_MASK 0x0002 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_SHIFT 1 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTR_ENA 0x0001 /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_MASK 0x0001 /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_SHIFT 0 /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */
+
+/*
+ * R14 (0x0E) - Power Management 2
+ */
+#define WM8903_HPL_PGA_ENA 0x0002 /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_MASK 0x0002 /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_SHIFT 1 /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_WIDTH 1 /* HPL_PGA_ENA */
+#define WM8903_HPR_PGA_ENA 0x0001 /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_MASK 0x0001 /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_SHIFT 0 /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_WIDTH 1 /* HPR_PGA_ENA */
+
+/*
+ * R15 (0x0F) - Power Management 3
+ */
+#define WM8903_LINEOUTL_PGA_ENA 0x0002 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_MASK 0x0002 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_SHIFT 1 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_WIDTH 1 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA 0x0001 /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_MASK 0x0001 /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_SHIFT 0 /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_WIDTH 1 /* LINEOUTR_PGA_ENA */
+
+/*
+ * R16 (0x10) - Power Management 4
+ */
+#define WM8903_MIXSPKL_ENA 0x0002 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_MASK 0x0002 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_SHIFT 1 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_WIDTH 1 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKR_ENA 0x0001 /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_MASK 0x0001 /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_SHIFT 0 /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_WIDTH 1 /* MIXSPKR_ENA */
+
+/*
+ * R17 (0x11) - Power Management 5
+ */
+#define WM8903_SPKL_ENA 0x0002 /* SPKL_ENA */
+#define WM8903_SPKL_ENA_MASK 0x0002 /* SPKL_ENA */
+#define WM8903_SPKL_ENA_SHIFT 1 /* SPKL_ENA */
+#define WM8903_SPKL_ENA_WIDTH 1 /* SPKL_ENA */
+#define WM8903_SPKR_ENA 0x0001 /* SPKR_ENA */
+#define WM8903_SPKR_ENA_MASK 0x0001 /* SPKR_ENA */
+#define WM8903_SPKR_ENA_SHIFT 0 /* SPKR_ENA */
+#define WM8903_SPKR_ENA_WIDTH 1 /* SPKR_ENA */
+
+/*
+ * R18 (0x12) - Power Management 6
+ */
+#define WM8903_DACL_ENA 0x0008 /* DACL_ENA */
+#define WM8903_DACL_ENA_MASK 0x0008 /* DACL_ENA */
+#define WM8903_DACL_ENA_SHIFT 3 /* DACL_ENA */
+#define WM8903_DACL_ENA_WIDTH 1 /* DACL_ENA */
+#define WM8903_DACR_ENA 0x0004 /* DACR_ENA */
+#define WM8903_DACR_ENA_MASK 0x0004 /* DACR_ENA */
+#define WM8903_DACR_ENA_SHIFT 2 /* DACR_ENA */
+#define WM8903_DACR_ENA_WIDTH 1 /* DACR_ENA */
+#define WM8903_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8903_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */
+#define WM8903_ADCL_ENA_SHIFT 1 /* ADCL_ENA */
+#define WM8903_ADCL_ENA_WIDTH 1 /* ADCL_ENA */
+#define WM8903_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8903_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */
+#define WM8903_ADCR_ENA_SHIFT 0 /* ADCR_ENA */
+#define WM8903_ADCR_ENA_WIDTH 1 /* ADCR_ENA */
+
+/*
+ * R20 (0x14) - Clock Rates 0
+ */
+#define WM8903_MCLKDIV2 0x0001 /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_MASK 0x0001 /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_SHIFT 0 /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */
+
+/*
+ * R21 (0x15) - Clock Rates 1
+ */
+#define WM8903_CLK_SYS_RATE_MASK 0x3C00 /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_RATE_SHIFT 10 /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_MODE_MASK 0x0300 /* CLK_SYS_MODE - [9:8] */
+#define WM8903_CLK_SYS_MODE_SHIFT 8 /* CLK_SYS_MODE - [9:8] */
+#define WM8903_CLK_SYS_MODE_WIDTH 2 /* CLK_SYS_MODE - [9:8] */
+#define WM8903_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */
+#define WM8903_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */
+#define WM8903_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */
+
+/*
+ * R22 (0x16) - Clock Rates 2
+ */
+#define WM8903_CLK_SYS_ENA 0x0004 /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_MASK 0x0004 /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_SHIFT 2 /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */
+#define WM8903_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */
+#define WM8903_TO_ENA 0x0001 /* TO_ENA */
+#define WM8903_TO_ENA_MASK 0x0001 /* TO_ENA */
+#define WM8903_TO_ENA_SHIFT 0 /* TO_ENA */
+#define WM8903_TO_ENA_WIDTH 1 /* TO_ENA */
+
+/*
+ * R24 (0x18) - Audio Interface 0
+ */
+#define WM8903_DACL_DATINV 0x1000 /* DACL_DATINV */
+#define WM8903_DACL_DATINV_MASK 0x1000 /* DACL_DATINV */
+#define WM8903_DACL_DATINV_SHIFT 12 /* DACL_DATINV */
+#define WM8903_DACL_DATINV_WIDTH 1 /* DACL_DATINV */
+#define WM8903_DACR_DATINV 0x0800 /* DACR_DATINV */
+#define WM8903_DACR_DATINV_MASK 0x0800 /* DACR_DATINV */
+#define WM8903_DACR_DATINV_SHIFT 11 /* DACR_DATINV */
+#define WM8903_DACR_DATINV_WIDTH 1 /* DACR_DATINV */
+#define WM8903_DAC_BOOST_MASK 0x0600 /* DAC_BOOST - [10:9] */
+#define WM8903_DAC_BOOST_SHIFT 9 /* DAC_BOOST - [10:9] */
+#define WM8903_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [10:9] */
+#define WM8903_LOOPBACK 0x0100 /* LOOPBACK */
+#define WM8903_LOOPBACK_MASK 0x0100 /* LOOPBACK */
+#define WM8903_LOOPBACK_SHIFT 8 /* LOOPBACK */
+#define WM8903_LOOPBACK_WIDTH 1 /* LOOPBACK */
+#define WM8903_AIFADCL_SRC 0x0080 /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_MASK 0x0080 /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_SHIFT 7 /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */
+#define WM8903_AIFADCR_SRC 0x0040 /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_MASK 0x0040 /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_SHIFT 6 /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */
+#define WM8903_AIFDACL_SRC 0x0020 /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_MASK 0x0020 /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_SHIFT 5 /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */
+#define WM8903_AIFDACR_SRC 0x0010 /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_MASK 0x0010 /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_SHIFT 4 /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */
+#define WM8903_ADC_COMP 0x0008 /* ADC_COMP */
+#define WM8903_ADC_COMP_MASK 0x0008 /* ADC_COMP */
+#define WM8903_ADC_COMP_SHIFT 3 /* ADC_COMP */
+#define WM8903_ADC_COMP_WIDTH 1 /* ADC_COMP */
+#define WM8903_ADC_COMPMODE 0x0004 /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_MASK 0x0004 /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_SHIFT 2 /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */
+#define WM8903_DAC_COMP 0x0002 /* DAC_COMP */
+#define WM8903_DAC_COMP_MASK 0x0002 /* DAC_COMP */
+#define WM8903_DAC_COMP_SHIFT 1 /* DAC_COMP */
+#define WM8903_DAC_COMP_WIDTH 1 /* DAC_COMP */
+#define WM8903_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */
+
+/*
+ * R25 (0x19) - Audio Interface 1
+ */
+#define WM8903_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFADC_TDM 0x0800 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_MASK 0x0800 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_SHIFT 11 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_CHAN 0x0400 /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_MASK 0x0400 /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_SHIFT 10 /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */
+#define WM8903_LRCLK_DIR 0x0200 /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_MASK 0x0200 /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_SHIFT 9 /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */
+#define WM8903_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */
+#define WM8903_BCLK_DIR 0x0040 /* BCLK_DIR */
+#define WM8903_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */
+#define WM8903_BCLK_DIR_SHIFT 6 /* BCLK_DIR */
+#define WM8903_BCLK_DIR_WIDTH 1 /* BCLK_DIR */
+#define WM8903_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */
+#define WM8903_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */
+#define WM8903_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */
+#define WM8903_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */
+#define WM8903_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */
+#define WM8903_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */
+#define WM8903_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */
+
+/*
+ * R26 (0x1A) - Audio Interface 2
+ */
+#define WM8903_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */
+#define WM8903_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */
+#define WM8903_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */
+
+/*
+ * R27 (0x1B) - Audio Interface 3
+ */
+#define WM8903_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */
+#define WM8903_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */
+#define WM8903_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */
+
+/*
+ * R30 (0x1E) - DAC Digital Volume Left
+ */
+#define WM8903_DACVU 0x0100 /* DACVU */
+#define WM8903_DACVU_MASK 0x0100 /* DACVU */
+#define WM8903_DACVU_SHIFT 8 /* DACVU */
+#define WM8903_DACVU_WIDTH 1 /* DACVU */
+#define WM8903_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8903_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */
+#define WM8903_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */
+
+/*
+ * R31 (0x1F) - DAC Digital Volume Right
+ */
+#define WM8903_DACVU 0x0100 /* DACVU */
+#define WM8903_DACVU_MASK 0x0100 /* DACVU */
+#define WM8903_DACVU_SHIFT 8 /* DACVU */
+#define WM8903_DACVU_WIDTH 1 /* DACVU */
+#define WM8903_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8903_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */
+#define WM8903_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */
+
+/*
+ * R32 (0x20) - DAC Digital 0
+ */
+#define WM8903_ADCL_DAC_SVOL_MASK 0x0F00 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCL_DAC_SVOL_SHIFT 8 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */
+#define WM8903_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */
+#define WM8903_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */
+
+/*
+ * R33 (0x21) - DAC Digital 1
+ */
+#define WM8903_DAC_MONO 0x1000 /* DAC_MONO */
+#define WM8903_DAC_MONO_MASK 0x1000 /* DAC_MONO */
+#define WM8903_DAC_MONO_SHIFT 12 /* DAC_MONO */
+#define WM8903_DAC_MONO_WIDTH 1 /* DAC_MONO */
+#define WM8903_DAC_SB_FILT 0x0800 /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_MASK 0x0800 /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_SHIFT 11 /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */
+#define WM8903_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTE 0x0008 /* DAC_MUTE */
+#define WM8903_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */
+#define WM8903_DAC_MUTE_SHIFT 3 /* DAC_MUTE */
+#define WM8903_DAC_MUTE_WIDTH 1 /* DAC_MUTE */
+#define WM8903_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
+#define WM8903_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
+#define WM8903_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
+
+/*
+ * R36 (0x24) - ADC Digital Volume Left
+ */
+#define WM8903_ADCVU 0x0100 /* ADCVU */
+#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */
+#define WM8903_ADCVU_SHIFT 8 /* ADCVU */
+#define WM8903_ADCVU_WIDTH 1 /* ADCVU */
+#define WM8903_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8903_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */
+#define WM8903_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */
+
+/*
+ * R37 (0x25) - ADC Digital Volume Right
+ */
+#define WM8903_ADCVU 0x0100 /* ADCVU */
+#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */
+#define WM8903_ADCVU_SHIFT 8 /* ADCVU */
+#define WM8903_ADCVU_WIDTH 1 /* ADCVU */
+#define WM8903_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8903_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */
+#define WM8903_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */
+
+/*
+ * R38 (0x26) - ADC Digital 0
+ */
+#define WM8903_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_ENA 0x0010 /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_MASK 0x0010 /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_SHIFT 4 /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_WIDTH 1 /* ADC_HPF_ENA */
+#define WM8903_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */
+#define WM8903_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */
+
+/*
+ * R39 (0x27) - Digital Microphone 0
+ */
+#define WM8903_DIGMIC_MODE_SEL 0x0100 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_MASK 0x0100 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_SHIFT 8 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_WIDTH 1 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_CLK_SEL_L_MASK 0x00C0 /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_L_SHIFT 6 /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_L_WIDTH 2 /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_R_MASK 0x0030 /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_R_SHIFT 4 /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_R_WIDTH 2 /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_RT_MASK 0x000C /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_RT_SHIFT 2 /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_RT_WIDTH 2 /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_MASK 0x0003 /* DIGMIC_CLK_SEL - [1:0] */
+#define WM8903_DIGMIC_CLK_SEL_SHIFT 0 /* DIGMIC_CLK_SEL - [1:0] */
+#define WM8903_DIGMIC_CLK_SEL_WIDTH 2 /* DIGMIC_CLK_SEL - [1:0] */
+
+/*
+ * R40 (0x28) - DRC 0
+ */
+#define WM8903_DRC_ENA 0x8000 /* DRC_ENA */
+#define WM8903_DRC_ENA_MASK 0x8000 /* DRC_ENA */
+#define WM8903_DRC_ENA_SHIFT 15 /* DRC_ENA */
+#define WM8903_DRC_ENA_WIDTH 1 /* DRC_ENA */
+#define WM8903_DRC_THRESH_HYST_MASK 0x1800 /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_THRESH_HYST_SHIFT 11 /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_THRESH_HYST_WIDTH 2 /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_FF_DELAY 0x0020 /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_MASK 0x0020 /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_SHIFT 5 /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */
+#define WM8903_DRC_SMOOTH_ENA 0x0008 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_MASK 0x0008 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_SHIFT 3 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_WIDTH 1 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_QR_ENA 0x0004 /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_MASK 0x0004 /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_SHIFT 2 /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_WIDTH 1 /* DRC_QR_ENA */
+#define WM8903_DRC_ANTICLIP_ENA 0x0002 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_MASK 0x0002 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_SHIFT 1 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_WIDTH 1 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_HYST_ENA 0x0001 /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_MASK 0x0001 /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_SHIFT 0 /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_WIDTH 1 /* DRC_HYST_ENA */
+
+/*
+ * R41 (0x29) - DRC 1
+ */
+#define WM8903_DRC_ATTACK_RATE_MASK 0xF000 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_ATTACK_RATE_SHIFT 12 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_ATTACK_RATE_WIDTH 4 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_DECAY_RATE_MASK 0x0F00 /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_DECAY_RATE_SHIFT 8 /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_DECAY_RATE_WIDTH 4 /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_THRESH_QR_MASK 0x00C0 /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_THRESH_QR_SHIFT 6 /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_THRESH_QR_WIDTH 2 /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_RATE_QR_MASK 0x0030 /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_RATE_QR_SHIFT 4 /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_RATE_QR_WIDTH 2 /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */
+#define WM8903_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */
+#define WM8903_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R42 (0x2A) - DRC 2
+ */
+#define WM8903_DRC_R0_SLOPE_COMP_MASK 0x0038 /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R0_SLOPE_COMP_SHIFT 3 /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R0_SLOPE_COMP_WIDTH 3 /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R1_SLOPE_COMP_MASK 0x0007 /* DRC_R1_SLOPE_COMP - [2:0] */
+#define WM8903_DRC_R1_SLOPE_COMP_SHIFT 0 /* DRC_R1_SLOPE_COMP - [2:0] */
+#define WM8903_DRC_R1_SLOPE_COMP_WIDTH 3 /* DRC_R1_SLOPE_COMP - [2:0] */
+
+/*
+ * R43 (0x2B) - DRC 3
+ */
+#define WM8903_DRC_THRESH_COMP_MASK 0x07E0 /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_THRESH_COMP_SHIFT 5 /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_THRESH_COMP_WIDTH 6 /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_AMP_COMP_MASK 0x001F /* DRC_AMP_COMP - [4:0] */
+#define WM8903_DRC_AMP_COMP_SHIFT 0 /* DRC_AMP_COMP - [4:0] */
+#define WM8903_DRC_AMP_COMP_WIDTH 5 /* DRC_AMP_COMP - [4:0] */
+
+/*
+ * R44 (0x2C) - Analogue Left Input 0
+ */
+#define WM8903_LINMUTE 0x0080 /* LINMUTE */
+#define WM8903_LINMUTE_MASK 0x0080 /* LINMUTE */
+#define WM8903_LINMUTE_SHIFT 7 /* LINMUTE */
+#define WM8903_LINMUTE_WIDTH 1 /* LINMUTE */
+#define WM8903_LIN_VOL_MASK 0x001F /* LIN_VOL - [4:0] */
+#define WM8903_LIN_VOL_SHIFT 0 /* LIN_VOL - [4:0] */
+#define WM8903_LIN_VOL_WIDTH 5 /* LIN_VOL - [4:0] */
+
+/*
+ * R45 (0x2D) - Analogue Right Input 0
+ */
+#define WM8903_RINMUTE 0x0080 /* RINMUTE */
+#define WM8903_RINMUTE_MASK 0x0080 /* RINMUTE */
+#define WM8903_RINMUTE_SHIFT 7 /* RINMUTE */
+#define WM8903_RINMUTE_WIDTH 1 /* RINMUTE */
+#define WM8903_RIN_VOL_MASK 0x001F /* RIN_VOL - [4:0] */
+#define WM8903_RIN_VOL_SHIFT 0 /* RIN_VOL - [4:0] */
+#define WM8903_RIN_VOL_WIDTH 5 /* RIN_VOL - [4:0] */
+
+/*
+ * R46 (0x2E) - Analogue Left Input 1
+ */
+#define WM8903_INL_CM_ENA 0x0040 /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_MASK 0x0040 /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_SHIFT 6 /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_WIDTH 1 /* INL_CM_ENA */
+#define WM8903_L_IP_SEL_N_MASK 0x0030 /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_N_SHIFT 4 /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_N_WIDTH 2 /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_P_MASK 0x000C /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_IP_SEL_P_SHIFT 2 /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_IP_SEL_P_WIDTH 2 /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_MODE_MASK 0x0003 /* L_MODE - [1:0] */
+#define WM8903_L_MODE_SHIFT 0 /* L_MODE - [1:0] */
+#define WM8903_L_MODE_WIDTH 2 /* L_MODE - [1:0] */
+
+/*
+ * R47 (0x2F) - Analogue Right Input 1
+ */
+#define WM8903_INR_CM_ENA 0x0040 /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_MASK 0x0040 /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_SHIFT 6 /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_WIDTH 1 /* INR_CM_ENA */
+#define WM8903_R_IP_SEL_N_MASK 0x0030 /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_N_SHIFT 4 /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_N_WIDTH 2 /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_P_MASK 0x000C /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_IP_SEL_P_SHIFT 2 /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_IP_SEL_P_WIDTH 2 /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_MODE_MASK 0x0003 /* R_MODE - [1:0] */
+#define WM8903_R_MODE_SHIFT 0 /* R_MODE - [1:0] */
+#define WM8903_R_MODE_WIDTH 2 /* R_MODE - [1:0] */
+
+/*
+ * R50 (0x32) - Analogue Left Mix 0
+ */
+#define WM8903_DACL_TO_MIXOUTL 0x0008 /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_MASK 0x0008 /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_SHIFT 3 /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_WIDTH 1 /* DACL_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL 0x0004 /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_MASK 0x0004 /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_SHIFT 2 /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_WIDTH 1 /* DACR_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL 0x0002 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_MASK 0x0002 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_SHIFT 1 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_WIDTH 1 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL 0x0001 /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_MASK 0x0001 /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_SHIFT 0 /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_WIDTH 1 /* BYPASSR_TO_MIXOUTL */
+
+/*
+ * R51 (0x33) - Analogue Right Mix 0
+ */
+#define WM8903_DACL_TO_MIXOUTR 0x0008 /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_MASK 0x0008 /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_SHIFT 3 /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_WIDTH 1 /* DACL_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR 0x0004 /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_MASK 0x0004 /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_SHIFT 2 /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_WIDTH 1 /* DACR_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR 0x0002 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_MASK 0x0002 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_SHIFT 1 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_WIDTH 1 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR 0x0001 /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_MASK 0x0001 /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_SHIFT 0 /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_WIDTH 1 /* BYPASSR_TO_MIXOUTR */
+
+/*
+ * R52 (0x34) - Analogue Spk Mix Left 0
+ */
+#define WM8903_DACL_TO_MIXSPKL 0x0008 /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_MASK 0x0008 /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_SHIFT 3 /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_WIDTH 1 /* DACL_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL 0x0004 /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_MASK 0x0004 /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_SHIFT 2 /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_WIDTH 1 /* DACR_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL 0x0002 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_MASK 0x0002 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_SHIFT 1 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_WIDTH 1 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL 0x0001 /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_MASK 0x0001 /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_SHIFT 0 /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_WIDTH 1 /* BYPASSR_TO_MIXSPKL */
+
+/*
+ * R53 (0x35) - Analogue Spk Mix Left 1
+ */
+#define WM8903_DACL_MIXSPKL_VOL 0x0008 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_MASK 0x0008 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_SHIFT 3 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_WIDTH 1 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL 0x0004 /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_MASK 0x0004 /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_SHIFT 2 /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_WIDTH 1 /* DACR_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL 0x0002 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_MASK 0x0002 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_SHIFT 1 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_WIDTH 1 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL 0x0001 /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_MASK 0x0001 /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_SHIFT 0 /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_WIDTH 1 /* BYPASSR_MIXSPKL_VOL */
+
+/*
+ * R54 (0x36) - Analogue Spk Mix Right 0
+ */
+#define WM8903_DACL_TO_MIXSPKR 0x0008 /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_MASK 0x0008 /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_SHIFT 3 /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_WIDTH 1 /* DACL_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR 0x0004 /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_MASK 0x0004 /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_SHIFT 2 /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_WIDTH 1 /* DACR_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR 0x0002 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_MASK 0x0002 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_SHIFT 1 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_WIDTH 1 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR 0x0001 /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_MASK 0x0001 /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_SHIFT 0 /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_WIDTH 1 /* BYPASSR_TO_MIXSPKR */
+
+/*
+ * R55 (0x37) - Analogue Spk Mix Right 1
+ */
+#define WM8903_DACL_MIXSPKR_VOL 0x0008 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_MASK 0x0008 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_SHIFT 3 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_WIDTH 1 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL 0x0004 /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_MASK 0x0004 /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_SHIFT 2 /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_WIDTH 1 /* DACR_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL 0x0002 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_MASK 0x0002 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_SHIFT 1 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_WIDTH 1 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL 0x0001 /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_MASK 0x0001 /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_SHIFT 0 /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_WIDTH 1 /* BYPASSR_MIXSPKR_VOL */
+
+/*
+ * R57 (0x39) - Analogue OUT1 Left
+ */
+#define WM8903_HPL_MUTE 0x0100 /* HPL_MUTE */
+#define WM8903_HPL_MUTE_MASK 0x0100 /* HPL_MUTE */
+#define WM8903_HPL_MUTE_SHIFT 8 /* HPL_MUTE */
+#define WM8903_HPL_MUTE_WIDTH 1 /* HPL_MUTE */
+#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */
+#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */
+#define WM8903_HPOUTLZC 0x0040 /* HPOUTLZC */
+#define WM8903_HPOUTLZC_MASK 0x0040 /* HPOUTLZC */
+#define WM8903_HPOUTLZC_SHIFT 6 /* HPOUTLZC */
+#define WM8903_HPOUTLZC_WIDTH 1 /* HPOUTLZC */
+#define WM8903_HPOUTL_VOL_MASK 0x003F /* HPOUTL_VOL - [5:0] */
+#define WM8903_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [5:0] */
+#define WM8903_HPOUTL_VOL_WIDTH 6 /* HPOUTL_VOL - [5:0] */
+
+/*
+ * R58 (0x3A) - Analogue OUT1 Right
+ */
+#define WM8903_HPR_MUTE 0x0100 /* HPR_MUTE */
+#define WM8903_HPR_MUTE_MASK 0x0100 /* HPR_MUTE */
+#define WM8903_HPR_MUTE_SHIFT 8 /* HPR_MUTE */
+#define WM8903_HPR_MUTE_WIDTH 1 /* HPR_MUTE */
+#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */
+#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */
+#define WM8903_HPOUTRZC 0x0040 /* HPOUTRZC */
+#define WM8903_HPOUTRZC_MASK 0x0040 /* HPOUTRZC */
+#define WM8903_HPOUTRZC_SHIFT 6 /* HPOUTRZC */
+#define WM8903_HPOUTRZC_WIDTH 1 /* HPOUTRZC */
+#define WM8903_HPOUTR_VOL_MASK 0x003F /* HPOUTR_VOL - [5:0] */
+#define WM8903_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [5:0] */
+#define WM8903_HPOUTR_VOL_WIDTH 6 /* HPOUTR_VOL - [5:0] */
+
+/*
+ * R59 (0x3B) - Analogue OUT2 Left
+ */
+#define WM8903_LINEOUTL_MUTE 0x0100 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_MASK 0x0100 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_SHIFT 8 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_WIDTH 1 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */
+#define WM8903_LINEOUTLZC 0x0040 /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_MASK 0x0040 /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_SHIFT 6 /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_WIDTH 1 /* LINEOUTLZC */
+#define WM8903_LINEOUTL_VOL_MASK 0x003F /* LINEOUTL_VOL - [5:0] */
+#define WM8903_LINEOUTL_VOL_SHIFT 0 /* LINEOUTL_VOL - [5:0] */
+#define WM8903_LINEOUTL_VOL_WIDTH 6 /* LINEOUTL_VOL - [5:0] */
+
+/*
+ * R60 (0x3C) - Analogue OUT2 Right
+ */
+#define WM8903_LINEOUTR_MUTE 0x0100 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_MASK 0x0100 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_SHIFT 8 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_WIDTH 1 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */
+#define WM8903_LINEOUTRZC 0x0040 /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_MASK 0x0040 /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_SHIFT 6 /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_WIDTH 1 /* LINEOUTRZC */
+#define WM8903_LINEOUTR_VOL_MASK 0x003F /* LINEOUTR_VOL - [5:0] */
+#define WM8903_LINEOUTR_VOL_SHIFT 0 /* LINEOUTR_VOL - [5:0] */
+#define WM8903_LINEOUTR_VOL_WIDTH 6 /* LINEOUTR_VOL - [5:0] */
+
+/*
+ * R62 (0x3E) - Analogue OUT3 Left
+ */
+#define WM8903_SPKL_MUTE 0x0100 /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_MASK 0x0100 /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_SHIFT 8 /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_WIDTH 1 /* SPKL_MUTE */
+#define WM8903_SPKVU 0x0080 /* SPKVU */
+#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */
+#define WM8903_SPKVU_SHIFT 7 /* SPKVU */
+#define WM8903_SPKVU_WIDTH 1 /* SPKVU */
+#define WM8903_SPKLZC 0x0040 /* SPKLZC */
+#define WM8903_SPKLZC_MASK 0x0040 /* SPKLZC */
+#define WM8903_SPKLZC_SHIFT 6 /* SPKLZC */
+#define WM8903_SPKLZC_WIDTH 1 /* SPKLZC */
+#define WM8903_SPKL_VOL_MASK 0x003F /* SPKL_VOL - [5:0] */
+#define WM8903_SPKL_VOL_SHIFT 0 /* SPKL_VOL - [5:0] */
+#define WM8903_SPKL_VOL_WIDTH 6 /* SPKL_VOL - [5:0] */
+
+/*
+ * R63 (0x3F) - Analogue OUT3 Right
+ */
+#define WM8903_SPKR_MUTE 0x0100 /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_MASK 0x0100 /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_SHIFT 8 /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_WIDTH 1 /* SPKR_MUTE */
+#define WM8903_SPKVU 0x0080 /* SPKVU */
+#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */
+#define WM8903_SPKVU_SHIFT 7 /* SPKVU */
+#define WM8903_SPKVU_WIDTH 1 /* SPKVU */
+#define WM8903_SPKRZC 0x0040 /* SPKRZC */
+#define WM8903_SPKRZC_MASK 0x0040 /* SPKRZC */
+#define WM8903_SPKRZC_SHIFT 6 /* SPKRZC */
+#define WM8903_SPKRZC_WIDTH 1 /* SPKRZC */
+#define WM8903_SPKR_VOL_MASK 0x003F /* SPKR_VOL - [5:0] */
+#define WM8903_SPKR_VOL_SHIFT 0 /* SPKR_VOL - [5:0] */
+#define WM8903_SPKR_VOL_WIDTH 6 /* SPKR_VOL - [5:0] */
+
+/*
+ * R65 (0x41) - Analogue SPK Output Control 0
+ */
+#define WM8903_SPK_DISCHARGE 0x0002 /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_MASK 0x0002 /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_SHIFT 1 /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_WIDTH 1 /* SPK_DISCHARGE */
+#define WM8903_VROI 0x0001 /* VROI */
+#define WM8903_VROI_MASK 0x0001 /* VROI */
+#define WM8903_VROI_SHIFT 0 /* VROI */
+#define WM8903_VROI_WIDTH 1 /* VROI */
+
+/*
+ * R67 (0x43) - DC Servo 0
+ */
+#define WM8903_DCS_MASTER_ENA 0x0010 /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_MASK 0x0010 /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_SHIFT 4 /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_WIDTH 1 /* DCS_MASTER_ENA */
+#define WM8903_DCS_ENA_MASK 0x000F /* DCS_ENA - [3:0] */
+#define WM8903_DCS_ENA_SHIFT 0 /* DCS_ENA - [3:0] */
+#define WM8903_DCS_ENA_WIDTH 4 /* DCS_ENA - [3:0] */
+
+/*
+ * R69 (0x45) - DC Servo 2
+ */
+#define WM8903_DCS_MODE_MASK 0x0003 /* DCS_MODE - [1:0] */
+#define WM8903_DCS_MODE_SHIFT 0 /* DCS_MODE - [1:0] */
+#define WM8903_DCS_MODE_WIDTH 2 /* DCS_MODE - [1:0] */
+
+/*
+ * R90 (0x5A) - Analogue HP 0
+ */
+#define WM8903_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */
+#define WM8903_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA 0x0010 /* HPL_ENA */
+#define WM8903_HPL_ENA_MASK 0x0010 /* HPL_ENA */
+#define WM8903_HPL_ENA_SHIFT 4 /* HPL_ENA */
+#define WM8903_HPL_ENA_WIDTH 1 /* HPL_ENA */
+#define WM8903_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */
+#define WM8903_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA 0x0001 /* HPR_ENA */
+#define WM8903_HPR_ENA_MASK 0x0001 /* HPR_ENA */
+#define WM8903_HPR_ENA_SHIFT 0 /* HPR_ENA */
+#define WM8903_HPR_ENA_WIDTH 1 /* HPR_ENA */
+
+/*
+ * R94 (0x5E) - Analogue Lineout 0
+ */
+#define WM8903_LINEOUTL_RMV_SHORT 0x0080 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_MASK 0x0080 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_SHIFT 7 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_WIDTH 1 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_ENA_OUTP 0x0040 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_MASK 0x0040 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_SHIFT 6 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_WIDTH 1 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_DLY 0x0020 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_MASK 0x0020 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_SHIFT 5 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_WIDTH 1 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA 0x0010 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_MASK 0x0010 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_SHIFT 4 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_WIDTH 1 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTR_RMV_SHORT 0x0008 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_MASK 0x0008 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_SHIFT 3 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_WIDTH 1 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_ENA_OUTP 0x0004 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_MASK 0x0004 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_SHIFT 2 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_WIDTH 1 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_DLY 0x0002 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_MASK 0x0002 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_SHIFT 1 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_WIDTH 1 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA 0x0001 /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_MASK 0x0001 /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_SHIFT 0 /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_WIDTH 1 /* LINEOUTR_ENA */
+
+/*
+ * R98 (0x62) - Charge Pump 0
+ */
+#define WM8903_CP_ENA 0x0001 /* CP_ENA */
+#define WM8903_CP_ENA_MASK 0x0001 /* CP_ENA */
+#define WM8903_CP_ENA_SHIFT 0 /* CP_ENA */
+#define WM8903_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R104 (0x68) - Class W 0
+ */
+#define WM8903_CP_DYN_FREQ 0x0002 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_MASK 0x0002 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_SHIFT 1 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_WIDTH 1 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_V 0x0001 /* CP_DYN_V */
+#define WM8903_CP_DYN_V_MASK 0x0001 /* CP_DYN_V */
+#define WM8903_CP_DYN_V_SHIFT 0 /* CP_DYN_V */
+#define WM8903_CP_DYN_V_WIDTH 1 /* CP_DYN_V */
+
+/*
+ * R108 (0x6C) - Write Sequencer 0
+ */
+#define WM8903_WSEQ_ENA 0x0100 /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM8903_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8903_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8903_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R109 (0x6D) - Write Sequencer 1
+ */
+#define WM8903_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */
+#define WM8903_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */
+#define WM8903_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R110 (0x6E) - Write Sequencer 2
+ */
+#define WM8903_WSEQ_EOS 0x4000 /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */
+#define WM8903_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */
+#define WM8903_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */
+#define WM8903_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */
+
+/*
+ * R111 (0x6F) - Write Sequencer 3
+ */
+#define WM8903_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM8903_WSEQ_START 0x0100 /* WSEQ_START */
+#define WM8903_WSEQ_START_MASK 0x0100 /* WSEQ_START */
+#define WM8903_WSEQ_START_SHIFT 8 /* WSEQ_START */
+#define WM8903_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM8903_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */
+#define WM8903_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */
+#define WM8903_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R112 (0x70) - Write Sequencer 4
+ */
+#define WM8903_WSEQ_CURRENT_INDEX_MASK 0x03F0 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+
+/*
+ * R114 (0x72) - Control Interface
+ */
+#define WM8903_MASK_WRITE_ENA 0x0001 /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_MASK 0x0001 /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */
+
+/*
+ * R121 (0x79) - Interrupt Status 1
+ */
+#define WM8903_MICSHRT_EINT 0x8000 /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_MASK 0x8000 /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_SHIFT 15 /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_WIDTH 1 /* MICSHRT_EINT */
+#define WM8903_MICDET_EINT 0x4000 /* MICDET_EINT */
+#define WM8903_MICDET_EINT_MASK 0x4000 /* MICDET_EINT */
+#define WM8903_MICDET_EINT_SHIFT 14 /* MICDET_EINT */
+#define WM8903_MICDET_EINT_WIDTH 1 /* MICDET_EINT */
+#define WM8903_WSEQ_BUSY_EINT 0x2000 /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_MASK 0x2000 /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_SHIFT 13 /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */
+#define WM8903_GP5_EINT 0x0010 /* GP5_EINT */
+#define WM8903_GP5_EINT_MASK 0x0010 /* GP5_EINT */
+#define WM8903_GP5_EINT_SHIFT 4 /* GP5_EINT */
+#define WM8903_GP5_EINT_WIDTH 1 /* GP5_EINT */
+#define WM8903_GP4_EINT 0x0008 /* GP4_EINT */
+#define WM8903_GP4_EINT_MASK 0x0008 /* GP4_EINT */
+#define WM8903_GP4_EINT_SHIFT 3 /* GP4_EINT */
+#define WM8903_GP4_EINT_WIDTH 1 /* GP4_EINT */
+#define WM8903_GP3_EINT 0x0004 /* GP3_EINT */
+#define WM8903_GP3_EINT_MASK 0x0004 /* GP3_EINT */
+#define WM8903_GP3_EINT_SHIFT 2 /* GP3_EINT */
+#define WM8903_GP3_EINT_WIDTH 1 /* GP3_EINT */
+#define WM8903_GP2_EINT 0x0002 /* GP2_EINT */
+#define WM8903_GP2_EINT_MASK 0x0002 /* GP2_EINT */
+#define WM8903_GP2_EINT_SHIFT 1 /* GP2_EINT */
+#define WM8903_GP2_EINT_WIDTH 1 /* GP2_EINT */
+#define WM8903_GP1_EINT 0x0001 /* GP1_EINT */
+#define WM8903_GP1_EINT_MASK 0x0001 /* GP1_EINT */
+#define WM8903_GP1_EINT_SHIFT 0 /* GP1_EINT */
+#define WM8903_GP1_EINT_WIDTH 1 /* GP1_EINT */
+
+/*
+ * R122 (0x7A) - Interrupt Status 1 Mask
+ */
+#define WM8903_IM_MICSHRT_EINT 0x8000 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_MASK 0x8000 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_SHIFT 15 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_WIDTH 1 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICDET_EINT 0x4000 /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_MASK 0x4000 /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_SHIFT 14 /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_WIDTH 1 /* IM_MICDET_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT 0x2000 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_MASK 0x2000 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_SHIFT 13 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */
+#define WM8903_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */
+#define WM8903_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */
+#define WM8903_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */
+#define WM8903_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */
+
+/*
+ * R123 (0x7B) - Interrupt Polarity 1
+ */
+#define WM8903_MICSHRT_INV 0x8000 /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_MASK 0x8000 /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_SHIFT 15 /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_WIDTH 1 /* MICSHRT_INV */
+#define WM8903_MICDET_INV 0x4000 /* MICDET_INV */
+#define WM8903_MICDET_INV_MASK 0x4000 /* MICDET_INV */
+#define WM8903_MICDET_INV_SHIFT 14 /* MICDET_INV */
+#define WM8903_MICDET_INV_WIDTH 1 /* MICDET_INV */
+
+/*
+ * R126 (0x7E) - Interrupt Control
+ */
+#define WM8903_IRQ_POL 0x0001 /* IRQ_POL */
+#define WM8903_IRQ_POL_MASK 0x0001 /* IRQ_POL */
+#define WM8903_IRQ_POL_SHIFT 0 /* IRQ_POL */
+#define WM8903_IRQ_POL_WIDTH 1 /* IRQ_POL */
+
+/*
+ * R164 (0xA4) - Clock Rate Test 4
+ */
+#define WM8903_ADC_DIG_MIC 0x0200 /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_MASK 0x0200 /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_SHIFT 9 /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_WIDTH 1 /* ADC_DIG_MIC */
+
+/*
+ * R172 (0xAC) - Analogue Output Bias 0
+ */
+#define WM8903_PGA_BIAS_MASK 0x0070 /* PGA_BIAS - [6:4] */
+#define WM8903_PGA_BIAS_SHIFT 4 /* PGA_BIAS - [6:4] */
+#define WM8903_PGA_BIAS_WIDTH 3 /* PGA_BIAS - [6:4] */
+
+#endif
diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c
new file mode 100644
index 00000000..0fce199a
--- /dev/null
+++ b/sound/soc/codecs/wm8904.c
@@ -0,0 +1,2602 @@
+/*
+ * wm8904.c -- WM8904 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/wm8904.h>
+
+#include "wm8904.h"
+
+enum wm8904_type {
+ WM8904,
+ WM8912,
+};
+
+#define WM8904_NUM_DCS_CHANNELS 4
+
+#define WM8904_NUM_SUPPLIES 5
+static const char *wm8904_supply_names[WM8904_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD",
+ "AVDD",
+ "CPVDD",
+ "MICVDD",
+};
+
+/* codec private data */
+struct wm8904_priv {
+
+ enum wm8904_type devtype;
+ void *control_data;
+
+ struct regulator_bulk_data supplies[WM8904_NUM_SUPPLIES];
+
+ struct wm8904_pdata *pdata;
+
+ int deemph;
+
+ /* Platform provided DRC configuration */
+ const char **drc_texts;
+ int drc_cfg;
+ struct soc_enum drc_enum;
+
+ /* Platform provided ReTune mobile configuration */
+ int num_retune_mobile_texts;
+ const char **retune_mobile_texts;
+ int retune_mobile_cfg;
+ struct soc_enum retune_mobile_enum;
+
+ /* FLL setup */
+ int fll_src;
+ int fll_fref;
+ int fll_fout;
+
+ /* Clocking configuration */
+ unsigned int mclk_rate;
+ int sysclk_src;
+ unsigned int sysclk_rate;
+
+ int tdm_width;
+ int tdm_slots;
+ int bclk;
+ int fs;
+
+ /* DC servo configuration - cached offset values */
+ int dcs_state[WM8904_NUM_DCS_CHANNELS];
+};
+
+static const u16 wm8904_reg[WM8904_MAX_REGISTER + 1] = {
+ 0x8904, /* R0 - SW Reset and ID */
+ 0x0000, /* R1 - Revision */
+ 0x0000, /* R2 */
+ 0x0000, /* R3 */
+ 0x0018, /* R4 - Bias Control 0 */
+ 0x0000, /* R5 - VMID Control 0 */
+ 0x0000, /* R6 - Mic Bias Control 0 */
+ 0x0000, /* R7 - Mic Bias Control 1 */
+ 0x0001, /* R8 - Analogue DAC 0 */
+ 0x9696, /* R9 - mic Filter Control */
+ 0x0001, /* R10 - Analogue ADC 0 */
+ 0x0000, /* R11 */
+ 0x0000, /* R12 - Power Management 0 */
+ 0x0000, /* R13 */
+ 0x0000, /* R14 - Power Management 2 */
+ 0x0000, /* R15 - Power Management 3 */
+ 0x0000, /* R16 */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 - Power Management 6 */
+ 0x0000, /* R19 */
+ 0x945E, /* R20 - Clock Rates 0 */
+ 0x0C05, /* R21 - Clock Rates 1 */
+ 0x0006, /* R22 - Clock Rates 2 */
+ 0x0000, /* R23 */
+ 0x0050, /* R24 - Audio Interface 0 */
+ 0x000A, /* R25 - Audio Interface 1 */
+ 0x00E4, /* R26 - Audio Interface 2 */
+ 0x0040, /* R27 - Audio Interface 3 */
+ 0x0000, /* R28 */
+ 0x0000, /* R29 */
+ 0x00C0, /* R30 - DAC Digital Volume Left */
+ 0x00C0, /* R31 - DAC Digital Volume Right */
+ 0x0000, /* R32 - DAC Digital 0 */
+ 0x0008, /* R33 - DAC Digital 1 */
+ 0x0000, /* R34 */
+ 0x0000, /* R35 */
+ 0x00C0, /* R36 - ADC Digital Volume Left */
+ 0x00C0, /* R37 - ADC Digital Volume Right */
+ 0x0010, /* R38 - ADC Digital 0 */
+ 0x0000, /* R39 - Digital Microphone 0 */
+ 0x01AF, /* R40 - DRC 0 */
+ 0x3248, /* R41 - DRC 1 */
+ 0x0000, /* R42 - DRC 2 */
+ 0x0000, /* R43 - DRC 3 */
+ 0x0085, /* R44 - Analogue Left Input 0 */
+ 0x0085, /* R45 - Analogue Right Input 0 */
+ 0x0044, /* R46 - Analogue Left Input 1 */
+ 0x0044, /* R47 - Analogue Right Input 1 */
+ 0x0000, /* R48 */
+ 0x0000, /* R49 */
+ 0x0000, /* R50 */
+ 0x0000, /* R51 */
+ 0x0000, /* R52 */
+ 0x0000, /* R53 */
+ 0x0000, /* R54 */
+ 0x0000, /* R55 */
+ 0x0000, /* R56 */
+ 0x002D, /* R57 - Analogue OUT1 Left */
+ 0x002D, /* R58 - Analogue OUT1 Right */
+ 0x0039, /* R59 - Analogue OUT2 Left */
+ 0x0039, /* R60 - Analogue OUT2 Right */
+ 0x0000, /* R61 - Analogue OUT12 ZC */
+ 0x0000, /* R62 */
+ 0x0000, /* R63 */
+ 0x0000, /* R64 */
+ 0x0000, /* R65 */
+ 0x0000, /* R66 */
+ 0x0000, /* R67 - DC Servo 0 */
+ 0x0000, /* R68 - DC Servo 1 */
+ 0xAAAA, /* R69 - DC Servo 2 */
+ 0x0000, /* R70 */
+ 0xAAAA, /* R71 - DC Servo 4 */
+ 0xAAAA, /* R72 - DC Servo 5 */
+ 0x0000, /* R73 - DC Servo 6 */
+ 0x0000, /* R74 - DC Servo 7 */
+ 0x0000, /* R75 - DC Servo 8 */
+ 0x0000, /* R76 - DC Servo 9 */
+ 0x0000, /* R77 - DC Servo Readback 0 */
+ 0x0000, /* R78 */
+ 0x0000, /* R79 */
+ 0x0000, /* R80 */
+ 0x0000, /* R81 */
+ 0x0000, /* R82 */
+ 0x0000, /* R83 */
+ 0x0000, /* R84 */
+ 0x0000, /* R85 */
+ 0x0000, /* R86 */
+ 0x0000, /* R87 */
+ 0x0000, /* R88 */
+ 0x0000, /* R89 */
+ 0x0000, /* R90 - Analogue HP 0 */
+ 0x0000, /* R91 */
+ 0x0000, /* R92 */
+ 0x0000, /* R93 */
+ 0x0000, /* R94 - Analogue Lineout 0 */
+ 0x0000, /* R95 */
+ 0x0000, /* R96 */
+ 0x0000, /* R97 */
+ 0x0000, /* R98 - Charge Pump 0 */
+ 0x0000, /* R99 */
+ 0x0000, /* R100 */
+ 0x0000, /* R101 */
+ 0x0000, /* R102 */
+ 0x0000, /* R103 */
+ 0x0004, /* R104 - Class W 0 */
+ 0x0000, /* R105 */
+ 0x0000, /* R106 */
+ 0x0000, /* R107 */
+ 0x0000, /* R108 - Write Sequencer 0 */
+ 0x0000, /* R109 - Write Sequencer 1 */
+ 0x0000, /* R110 - Write Sequencer 2 */
+ 0x0000, /* R111 - Write Sequencer 3 */
+ 0x0000, /* R112 - Write Sequencer 4 */
+ 0x0000, /* R113 */
+ 0x0000, /* R114 */
+ 0x0000, /* R115 */
+ 0x0000, /* R116 - FLL Control 1 */
+ 0x0007, /* R117 - FLL Control 2 */
+ 0x0000, /* R118 - FLL Control 3 */
+ 0x2EE0, /* R119 - FLL Control 4 */
+ 0x0004, /* R120 - FLL Control 5 */
+ 0x0014, /* R121 - GPIO Control 1 */
+ 0x0010, /* R122 - GPIO Control 2 */
+ 0x0010, /* R123 - GPIO Control 3 */
+ 0x0000, /* R124 - GPIO Control 4 */
+ 0x0000, /* R125 */
+ 0x0000, /* R126 - Digital Pulls */
+ 0x0000, /* R127 - Interrupt Status */
+ 0xFFFF, /* R128 - Interrupt Status Mask */
+ 0x0000, /* R129 - Interrupt Polarity */
+ 0x0000, /* R130 - Interrupt Debounce */
+ 0x0000, /* R131 */
+ 0x0000, /* R132 */
+ 0x0000, /* R133 */
+ 0x0000, /* R134 - EQ1 */
+ 0x000C, /* R135 - EQ2 */
+ 0x000C, /* R136 - EQ3 */
+ 0x000C, /* R137 - EQ4 */
+ 0x000C, /* R138 - EQ5 */
+ 0x000C, /* R139 - EQ6 */
+ 0x0FCA, /* R140 - EQ7 */
+ 0x0400, /* R141 - EQ8 */
+ 0x00D8, /* R142 - EQ9 */
+ 0x1EB5, /* R143 - EQ10 */
+ 0xF145, /* R144 - EQ11 */
+ 0x0B75, /* R145 - EQ12 */
+ 0x01C5, /* R146 - EQ13 */
+ 0x1C58, /* R147 - EQ14 */
+ 0xF373, /* R148 - EQ15 */
+ 0x0A54, /* R149 - EQ16 */
+ 0x0558, /* R150 - EQ17 */
+ 0x168E, /* R151 - EQ18 */
+ 0xF829, /* R152 - EQ19 */
+ 0x07AD, /* R153 - EQ20 */
+ 0x1103, /* R154 - EQ21 */
+ 0x0564, /* R155 - EQ22 */
+ 0x0559, /* R156 - EQ23 */
+ 0x4000, /* R157 - EQ24 */
+ 0x0000, /* R158 */
+ 0x0000, /* R159 */
+ 0x0000, /* R160 */
+ 0x0000, /* R161 - Control Interface Test 1 */
+ 0x0000, /* R162 */
+ 0x0000, /* R163 */
+ 0x0000, /* R164 */
+ 0x0000, /* R165 */
+ 0x0000, /* R166 */
+ 0x0000, /* R167 */
+ 0x0000, /* R168 */
+ 0x0000, /* R169 */
+ 0x0000, /* R170 */
+ 0x0000, /* R171 */
+ 0x0000, /* R172 */
+ 0x0000, /* R173 */
+ 0x0000, /* R174 */
+ 0x0000, /* R175 */
+ 0x0000, /* R176 */
+ 0x0000, /* R177 */
+ 0x0000, /* R178 */
+ 0x0000, /* R179 */
+ 0x0000, /* R180 */
+ 0x0000, /* R181 */
+ 0x0000, /* R182 */
+ 0x0000, /* R183 */
+ 0x0000, /* R184 */
+ 0x0000, /* R185 */
+ 0x0000, /* R186 */
+ 0x0000, /* R187 */
+ 0x0000, /* R188 */
+ 0x0000, /* R189 */
+ 0x0000, /* R190 */
+ 0x0000, /* R191 */
+ 0x0000, /* R192 */
+ 0x0000, /* R193 */
+ 0x0000, /* R194 */
+ 0x0000, /* R195 */
+ 0x0000, /* R196 */
+ 0x0000, /* R197 */
+ 0x0000, /* R198 */
+ 0x0000, /* R199 */
+ 0x0000, /* R200 */
+ 0x0000, /* R201 */
+ 0x0000, /* R202 */
+ 0x0000, /* R203 */
+ 0x0000, /* R204 - Analogue Output Bias 0 */
+ 0x0000, /* R205 */
+ 0x0000, /* R206 */
+ 0x0000, /* R207 */
+ 0x0000, /* R208 */
+ 0x0000, /* R209 */
+ 0x0000, /* R210 */
+ 0x0000, /* R211 */
+ 0x0000, /* R212 */
+ 0x0000, /* R213 */
+ 0x0000, /* R214 */
+ 0x0000, /* R215 */
+ 0x0000, /* R216 */
+ 0x0000, /* R217 */
+ 0x0000, /* R218 */
+ 0x0000, /* R219 */
+ 0x0000, /* R220 */
+ 0x0000, /* R221 */
+ 0x0000, /* R222 */
+ 0x0000, /* R223 */
+ 0x0000, /* R224 */
+ 0x0000, /* R225 */
+ 0x0000, /* R226 */
+ 0x0000, /* R227 */
+ 0x0000, /* R228 */
+ 0x0000, /* R229 */
+ 0x0000, /* R230 */
+ 0x0000, /* R231 */
+ 0x0000, /* R232 */
+ 0x0000, /* R233 */
+ 0x0000, /* R234 */
+ 0x0000, /* R235 */
+ 0x0000, /* R236 */
+ 0x0000, /* R237 */
+ 0x0000, /* R238 */
+ 0x0000, /* R239 */
+ 0x0000, /* R240 */
+ 0x0000, /* R241 */
+ 0x0000, /* R242 */
+ 0x0000, /* R243 */
+ 0x0000, /* R244 */
+ 0x0000, /* R245 */
+ 0x0000, /* R246 */
+ 0x0000, /* R247 - FLL NCO Test 0 */
+ 0x0019, /* R248 - FLL NCO Test 1 */
+};
+
+static struct {
+ int readable;
+ int writable;
+ int vol;
+} wm8904_access[] = {
+ { 0xFFFF, 0xFFFF, 1 }, /* R0 - SW Reset and ID */
+ { 0x0000, 0x0000, 0 }, /* R1 - Revision */
+ { 0x0000, 0x0000, 0 }, /* R2 */
+ { 0x0000, 0x0000, 0 }, /* R3 */
+ { 0x001F, 0x001F, 0 }, /* R4 - Bias Control 0 */
+ { 0x0047, 0x0047, 0 }, /* R5 - VMID Control 0 */
+ { 0x007F, 0x007F, 0 }, /* R6 - Mic Bias Control 0 */
+ { 0xC007, 0xC007, 0 }, /* R7 - Mic Bias Control 1 */
+ { 0x001E, 0x001E, 0 }, /* R8 - Analogue DAC 0 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R9 - mic Filter Control */
+ { 0x0001, 0x0001, 0 }, /* R10 - Analogue ADC 0 */
+ { 0x0000, 0x0000, 0 }, /* R11 */
+ { 0x0003, 0x0003, 0 }, /* R12 - Power Management 0 */
+ { 0x0000, 0x0000, 0 }, /* R13 */
+ { 0x0003, 0x0003, 0 }, /* R14 - Power Management 2 */
+ { 0x0003, 0x0003, 0 }, /* R15 - Power Management 3 */
+ { 0x0000, 0x0000, 0 }, /* R16 */
+ { 0x0000, 0x0000, 0 }, /* R17 */
+ { 0x000F, 0x000F, 0 }, /* R18 - Power Management 6 */
+ { 0x0000, 0x0000, 0 }, /* R19 */
+ { 0x7001, 0x7001, 0 }, /* R20 - Clock Rates 0 */
+ { 0x3C07, 0x3C07, 0 }, /* R21 - Clock Rates 1 */
+ { 0xD00F, 0xD00F, 0 }, /* R22 - Clock Rates 2 */
+ { 0x0000, 0x0000, 0 }, /* R23 */
+ { 0x1FFF, 0x1FFF, 0 }, /* R24 - Audio Interface 0 */
+ { 0x3DDF, 0x3DDF, 0 }, /* R25 - Audio Interface 1 */
+ { 0x0F1F, 0x0F1F, 0 }, /* R26 - Audio Interface 2 */
+ { 0x0FFF, 0x0FFF, 0 }, /* R27 - Audio Interface 3 */
+ { 0x0000, 0x0000, 0 }, /* R28 */
+ { 0x0000, 0x0000, 0 }, /* R29 */
+ { 0x00FF, 0x01FF, 0 }, /* R30 - DAC Digital Volume Left */
+ { 0x00FF, 0x01FF, 0 }, /* R31 - DAC Digital Volume Right */
+ { 0x0FFF, 0x0FFF, 0 }, /* R32 - DAC Digital 0 */
+ { 0x1E4E, 0x1E4E, 0 }, /* R33 - DAC Digital 1 */
+ { 0x0000, 0x0000, 0 }, /* R34 */
+ { 0x0000, 0x0000, 0 }, /* R35 */
+ { 0x00FF, 0x01FF, 0 }, /* R36 - ADC Digital Volume Left */
+ { 0x00FF, 0x01FF, 0 }, /* R37 - ADC Digital Volume Right */
+ { 0x0073, 0x0073, 0 }, /* R38 - ADC Digital 0 */
+ { 0x1800, 0x1800, 0 }, /* R39 - Digital Microphone 0 */
+ { 0xDFEF, 0xDFEF, 0 }, /* R40 - DRC 0 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R41 - DRC 1 */
+ { 0x003F, 0x003F, 0 }, /* R42 - DRC 2 */
+ { 0x07FF, 0x07FF, 0 }, /* R43 - DRC 3 */
+ { 0x009F, 0x009F, 0 }, /* R44 - Analogue Left Input 0 */
+ { 0x009F, 0x009F, 0 }, /* R45 - Analogue Right Input 0 */
+ { 0x007F, 0x007F, 0 }, /* R46 - Analogue Left Input 1 */
+ { 0x007F, 0x007F, 0 }, /* R47 - Analogue Right Input 1 */
+ { 0x0000, 0x0000, 0 }, /* R48 */
+ { 0x0000, 0x0000, 0 }, /* R49 */
+ { 0x0000, 0x0000, 0 }, /* R50 */
+ { 0x0000, 0x0000, 0 }, /* R51 */
+ { 0x0000, 0x0000, 0 }, /* R52 */
+ { 0x0000, 0x0000, 0 }, /* R53 */
+ { 0x0000, 0x0000, 0 }, /* R54 */
+ { 0x0000, 0x0000, 0 }, /* R55 */
+ { 0x0000, 0x0000, 0 }, /* R56 */
+ { 0x017F, 0x01FF, 0 }, /* R57 - Analogue OUT1 Left */
+ { 0x017F, 0x01FF, 0 }, /* R58 - Analogue OUT1 Right */
+ { 0x017F, 0x01FF, 0 }, /* R59 - Analogue OUT2 Left */
+ { 0x017F, 0x01FF, 0 }, /* R60 - Analogue OUT2 Right */
+ { 0x000F, 0x000F, 0 }, /* R61 - Analogue OUT12 ZC */
+ { 0x0000, 0x0000, 0 }, /* R62 */
+ { 0x0000, 0x0000, 0 }, /* R63 */
+ { 0x0000, 0x0000, 0 }, /* R64 */
+ { 0x0000, 0x0000, 0 }, /* R65 */
+ { 0x0000, 0x0000, 0 }, /* R66 */
+ { 0x000F, 0x000F, 0 }, /* R67 - DC Servo 0 */
+ { 0xFFFF, 0xFFFF, 1 }, /* R68 - DC Servo 1 */
+ { 0x0F0F, 0x0F0F, 0 }, /* R69 - DC Servo 2 */
+ { 0x0000, 0x0000, 0 }, /* R70 */
+ { 0x007F, 0x007F, 0 }, /* R71 - DC Servo 4 */
+ { 0x007F, 0x007F, 0 }, /* R72 - DC Servo 5 */
+ { 0x00FF, 0x00FF, 1 }, /* R73 - DC Servo 6 */
+ { 0x00FF, 0x00FF, 1 }, /* R74 - DC Servo 7 */
+ { 0x00FF, 0x00FF, 1 }, /* R75 - DC Servo 8 */
+ { 0x00FF, 0x00FF, 1 }, /* R76 - DC Servo 9 */
+ { 0x0FFF, 0x0000, 1 }, /* R77 - DC Servo Readback 0 */
+ { 0x0000, 0x0000, 0 }, /* R78 */
+ { 0x0000, 0x0000, 0 }, /* R79 */
+ { 0x0000, 0x0000, 0 }, /* R80 */
+ { 0x0000, 0x0000, 0 }, /* R81 */
+ { 0x0000, 0x0000, 0 }, /* R82 */
+ { 0x0000, 0x0000, 0 }, /* R83 */
+ { 0x0000, 0x0000, 0 }, /* R84 */
+ { 0x0000, 0x0000, 0 }, /* R85 */
+ { 0x0000, 0x0000, 0 }, /* R86 */
+ { 0x0000, 0x0000, 0 }, /* R87 */
+ { 0x0000, 0x0000, 0 }, /* R88 */
+ { 0x0000, 0x0000, 0 }, /* R89 */
+ { 0x00FF, 0x00FF, 0 }, /* R90 - Analogue HP 0 */
+ { 0x0000, 0x0000, 0 }, /* R91 */
+ { 0x0000, 0x0000, 0 }, /* R92 */
+ { 0x0000, 0x0000, 0 }, /* R93 */
+ { 0x00FF, 0x00FF, 0 }, /* R94 - Analogue Lineout 0 */
+ { 0x0000, 0x0000, 0 }, /* R95 */
+ { 0x0000, 0x0000, 0 }, /* R96 */
+ { 0x0000, 0x0000, 0 }, /* R97 */
+ { 0x0001, 0x0001, 0 }, /* R98 - Charge Pump 0 */
+ { 0x0000, 0x0000, 0 }, /* R99 */
+ { 0x0000, 0x0000, 0 }, /* R100 */
+ { 0x0000, 0x0000, 0 }, /* R101 */
+ { 0x0000, 0x0000, 0 }, /* R102 */
+ { 0x0000, 0x0000, 0 }, /* R103 */
+ { 0x0001, 0x0001, 0 }, /* R104 - Class W 0 */
+ { 0x0000, 0x0000, 0 }, /* R105 */
+ { 0x0000, 0x0000, 0 }, /* R106 */
+ { 0x0000, 0x0000, 0 }, /* R107 */
+ { 0x011F, 0x011F, 0 }, /* R108 - Write Sequencer 0 */
+ { 0x7FFF, 0x7FFF, 0 }, /* R109 - Write Sequencer 1 */
+ { 0x4FFF, 0x4FFF, 0 }, /* R110 - Write Sequencer 2 */
+ { 0x003F, 0x033F, 0 }, /* R111 - Write Sequencer 3 */
+ { 0x03F1, 0x0000, 0 }, /* R112 - Write Sequencer 4 */
+ { 0x0000, 0x0000, 0 }, /* R113 */
+ { 0x0000, 0x0000, 0 }, /* R114 */
+ { 0x0000, 0x0000, 0 }, /* R115 */
+ { 0x0007, 0x0007, 0 }, /* R116 - FLL Control 1 */
+ { 0x3F77, 0x3F77, 0 }, /* R117 - FLL Control 2 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R118 - FLL Control 3 */
+ { 0x7FEF, 0x7FEF, 0 }, /* R119 - FLL Control 4 */
+ { 0x001B, 0x001B, 0 }, /* R120 - FLL Control 5 */
+ { 0x003F, 0x003F, 0 }, /* R121 - GPIO Control 1 */
+ { 0x003F, 0x003F, 0 }, /* R122 - GPIO Control 2 */
+ { 0x003F, 0x003F, 0 }, /* R123 - GPIO Control 3 */
+ { 0x038F, 0x038F, 0 }, /* R124 - GPIO Control 4 */
+ { 0x0000, 0x0000, 0 }, /* R125 */
+ { 0x00FF, 0x00FF, 0 }, /* R126 - Digital Pulls */
+ { 0x07FF, 0x03FF, 1 }, /* R127 - Interrupt Status */
+ { 0x03FF, 0x03FF, 0 }, /* R128 - Interrupt Status Mask */
+ { 0x03FF, 0x03FF, 0 }, /* R129 - Interrupt Polarity */
+ { 0x03FF, 0x03FF, 0 }, /* R130 - Interrupt Debounce */
+ { 0x0000, 0x0000, 0 }, /* R131 */
+ { 0x0000, 0x0000, 0 }, /* R132 */
+ { 0x0000, 0x0000, 0 }, /* R133 */
+ { 0x0001, 0x0001, 0 }, /* R134 - EQ1 */
+ { 0x001F, 0x001F, 0 }, /* R135 - EQ2 */
+ { 0x001F, 0x001F, 0 }, /* R136 - EQ3 */
+ { 0x001F, 0x001F, 0 }, /* R137 - EQ4 */
+ { 0x001F, 0x001F, 0 }, /* R138 - EQ5 */
+ { 0x001F, 0x001F, 0 }, /* R139 - EQ6 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R140 - EQ7 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R141 - EQ8 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R142 - EQ9 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R143 - EQ10 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R144 - EQ11 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R145 - EQ12 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R146 - EQ13 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R147 - EQ14 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R148 - EQ15 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R149 - EQ16 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R150 - EQ17 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R151wm8523_dai - EQ18 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R152 - EQ19 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R153 - EQ20 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R154 - EQ21 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R155 - EQ22 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R156 - EQ23 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R157 - EQ24 */
+ { 0x0000, 0x0000, 0 }, /* R158 */
+ { 0x0000, 0x0000, 0 }, /* R159 */
+ { 0x0000, 0x0000, 0 }, /* R160 */
+ { 0x0002, 0x0002, 0 }, /* R161 - Control Interface Test 1 */
+ { 0x0000, 0x0000, 0 }, /* R162 */
+ { 0x0000, 0x0000, 0 }, /* R163 */
+ { 0x0000, 0x0000, 0 }, /* R164 */
+ { 0x0000, 0x0000, 0 }, /* R165 */
+ { 0x0000, 0x0000, 0 }, /* R166 */
+ { 0x0000, 0x0000, 0 }, /* R167 */
+ { 0x0000, 0x0000, 0 }, /* R168 */
+ { 0x0000, 0x0000, 0 }, /* R169 */
+ { 0x0000, 0x0000, 0 }, /* R170 */
+ { 0x0000, 0x0000, 0 }, /* R171 */
+ { 0x0000, 0x0000, 0 }, /* R172 */
+ { 0x0000, 0x0000, 0 }, /* R173 */
+ { 0x0000, 0x0000, 0 }, /* R174 */
+ { 0x0000, 0x0000, 0 }, /* R175 */
+ { 0x0000, 0x0000, 0 }, /* R176 */
+ { 0x0000, 0x0000, 0 }, /* R177 */
+ { 0x0000, 0x0000, 0 }, /* R178 */
+ { 0x0000, 0x0000, 0 }, /* R179 */
+ { 0x0000, 0x0000, 0 }, /* R180 */
+ { 0x0000, 0x0000, 0 }, /* R181 */
+ { 0x0000, 0x0000, 0 }, /* R182 */
+ { 0x0000, 0x0000, 0 }, /* R183 */
+ { 0x0000, 0x0000, 0 }, /* R184 */
+ { 0x0000, 0x0000, 0 }, /* R185 */
+ { 0x0000, 0x0000, 0 }, /* R186 */
+ { 0x0000, 0x0000, 0 }, /* R187 */
+ { 0x0000, 0x0000, 0 }, /* R188 */
+ { 0x0000, 0x0000, 0 }, /* R189 */
+ { 0x0000, 0x0000, 0 }, /* R190 */
+ { 0x0000, 0x0000, 0 }, /* R191 */
+ { 0x0000, 0x0000, 0 }, /* R192 */
+ { 0x0000, 0x0000, 0 }, /* R193 */
+ { 0x0000, 0x0000, 0 }, /* R194 */
+ { 0x0000, 0x0000, 0 }, /* R195 */
+ { 0x0000, 0x0000, 0 }, /* R196 */
+ { 0x0000, 0x0000, 0 }, /* R197 */
+ { 0x0000, 0x0000, 0 }, /* R198 */
+ { 0x0000, 0x0000, 0 }, /* R199 */
+ { 0x0000, 0x0000, 0 }, /* R200 */
+ { 0x0000, 0x0000, 0 }, /* R201 */
+ { 0x0000, 0x0000, 0 }, /* R202 */
+ { 0x0000, 0x0000, 0 }, /* R203 */
+ { 0x0070, 0x0070, 0 }, /* R204 - Analogue Output Bias 0 */
+ { 0x0000, 0x0000, 0 }, /* R205 */
+ { 0x0000, 0x0000, 0 }, /* R206 */
+ { 0x0000, 0x0000, 0 }, /* R207 */
+ { 0x0000, 0x0000, 0 }, /* R208 */
+ { 0x0000, 0x0000, 0 }, /* R209 */
+ { 0x0000, 0x0000, 0 }, /* R210 */
+ { 0x0000, 0x0000, 0 }, /* R211 */
+ { 0x0000, 0x0000, 0 }, /* R212 */
+ { 0x0000, 0x0000, 0 }, /* R213 */
+ { 0x0000, 0x0000, 0 }, /* R214 */
+ { 0x0000, 0x0000, 0 }, /* R215 */
+ { 0x0000, 0x0000, 0 }, /* R216 */
+ { 0x0000, 0x0000, 0 }, /* R217 */
+ { 0x0000, 0x0000, 0 }, /* R218 */
+ { 0x0000, 0x0000, 0 }, /* R219 */
+ { 0x0000, 0x0000, 0 }, /* R220 */
+ { 0x0000, 0x0000, 0 }, /* R221 */
+ { 0x0000, 0x0000, 0 }, /* R222 */
+ { 0x0000, 0x0000, 0 }, /* R223 */
+ { 0x0000, 0x0000, 0 }, /* R224 */
+ { 0x0000, 0x0000, 0 }, /* R225 */
+ { 0x0000, 0x0000, 0 }, /* R226 */
+ { 0x0000, 0x0000, 0 }, /* R227 */
+ { 0x0000, 0x0000, 0 }, /* R228 */
+ { 0x0000, 0x0000, 0 }, /* R229 */
+ { 0x0000, 0x0000, 0 }, /* R230 */
+ { 0x0000, 0x0000, 0 }, /* R231 */
+ { 0x0000, 0x0000, 0 }, /* R232 */
+ { 0x0000, 0x0000, 0 }, /* R233 */
+ { 0x0000, 0x0000, 0 }, /* R234 */
+ { 0x0000, 0x0000, 0 }, /* R235 */
+ { 0x0000, 0x0000, 0 }, /* R236 */
+ { 0x0000, 0x0000, 0 }, /* R237 */
+ { 0x0000, 0x0000, 0 }, /* R238 */
+ { 0x0000, 0x0000, 0 }, /* R239 */
+ { 0x0000, 0x0000, 0 }, /* R240 */
+ { 0x0000, 0x0000, 0 }, /* R241 */
+ { 0x0000, 0x0000, 0 }, /* R242 */
+ { 0x0000, 0x0000, 0 }, /* R243 */
+ { 0x0000, 0x0000, 0 }, /* R244 */
+ { 0x0000, 0x0000, 0 }, /* R245 */
+ { 0x0000, 0x0000, 0 }, /* R246 */
+ { 0x0001, 0x0001, 0 }, /* R247 - FLL NCO Test 0 */
+ { 0x003F, 0x003F, 0 }, /* R248 - FLL NCO Test 1 */
+};
+
+static int wm8904_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ return wm8904_access[reg].vol;
+}
+
+static int wm8904_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8904_SW_RESET_AND_ID, 0);
+}
+
+static int wm8904_configure_clocking(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ unsigned int clock0, clock2, rate;
+
+ /* Gate the clock while we're updating to avoid misclocking */
+ clock2 = snd_soc_read(codec, WM8904_CLOCK_RATES_2);
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+ WM8904_SYSCLK_SRC, 0);
+
+ /* This should be done on init() for bypass paths */
+ switch (wm8904->sysclk_src) {
+ case WM8904_CLK_MCLK:
+ dev_dbg(codec->dev, "Using %dHz MCLK\n", wm8904->mclk_rate);
+
+ clock2 &= ~WM8904_SYSCLK_SRC;
+ rate = wm8904->mclk_rate;
+
+ /* Ensure the FLL is stopped */
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+ break;
+
+ case WM8904_CLK_FLL:
+ dev_dbg(codec->dev, "Using %dHz FLL clock\n",
+ wm8904->fll_fout);
+
+ clock2 |= WM8904_SYSCLK_SRC;
+ rate = wm8904->fll_fout;
+ break;
+
+ default:
+ dev_err(codec->dev, "System clock not configured\n");
+ return -EINVAL;
+ }
+
+ /* SYSCLK shouldn't be over 13.5MHz */
+ if (rate > 13500000) {
+ clock0 = WM8904_MCLK_DIV;
+ wm8904->sysclk_rate = rate / 2;
+ } else {
+ clock0 = 0;
+ wm8904->sysclk_rate = rate;
+ }
+
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_0, WM8904_MCLK_DIV,
+ clock0);
+
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+ WM8904_CLK_SYS_ENA | WM8904_SYSCLK_SRC, clock2);
+
+ dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm8904->sysclk_rate);
+
+ return 0;
+}
+
+static void wm8904_set_drc(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ int save, i;
+
+ /* Save any enables; the configuration should clear them. */
+ save = snd_soc_read(codec, WM8904_DRC_0);
+
+ for (i = 0; i < WM8904_DRC_REGS; i++)
+ snd_soc_update_bits(codec, WM8904_DRC_0 + i, 0xffff,
+ pdata->drc_cfgs[wm8904->drc_cfg].regs[i]);
+
+ /* Reenable the DRC */
+ snd_soc_update_bits(codec, WM8904_DRC_0,
+ WM8904_DRC_ENA | WM8904_DRC_DAC_PATH, save);
+}
+
+static int wm8904_put_drc_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ int value = ucontrol->value.integer.value[0];
+
+ if (value >= pdata->num_drc_cfgs)
+ return -EINVAL;
+
+ wm8904->drc_cfg = value;
+
+ wm8904_set_drc(codec);
+
+ return 0;
+}
+
+static int wm8904_get_drc_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8904->drc_cfg;
+
+ return 0;
+}
+
+static void wm8904_set_retune_mobile(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ int best, best_val, save, i, cfg;
+
+ if (!pdata || !wm8904->num_retune_mobile_texts)
+ return;
+
+ /* Find the version of the currently selected configuration
+ * with the nearest sample rate. */
+ cfg = wm8904->retune_mobile_cfg;
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+ if (strcmp(pdata->retune_mobile_cfgs[i].name,
+ wm8904->retune_mobile_texts[cfg]) == 0 &&
+ abs(pdata->retune_mobile_cfgs[i].rate
+ - wm8904->fs) < best_val) {
+ best = i;
+ best_val = abs(pdata->retune_mobile_cfgs[i].rate
+ - wm8904->fs);
+ }
+ }
+
+ dev_dbg(codec->dev, "ReTune Mobile %s/%dHz for %dHz sample rate\n",
+ pdata->retune_mobile_cfgs[best].name,
+ pdata->retune_mobile_cfgs[best].rate,
+ wm8904->fs);
+
+ /* The EQ will be disabled while reconfiguring it, remember the
+ * current configuration.
+ */
+ save = snd_soc_read(codec, WM8904_EQ1);
+
+ for (i = 0; i < WM8904_EQ_REGS; i++)
+ snd_soc_update_bits(codec, WM8904_EQ1 + i, 0xffff,
+ pdata->retune_mobile_cfgs[best].regs[i]);
+
+ snd_soc_update_bits(codec, WM8904_EQ1, WM8904_EQ_ENA, save);
+}
+
+static int wm8904_put_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ int value = ucontrol->value.integer.value[0];
+
+ if (value >= pdata->num_retune_mobile_cfgs)
+ return -EINVAL;
+
+ wm8904->retune_mobile_cfg = value;
+
+ wm8904_set_retune_mobile(codec);
+
+ return 0;
+}
+
+static int wm8904_get_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8904->retune_mobile_cfg;
+
+ return 0;
+}
+
+static int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8904_set_deemph(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ int val, i, best;
+
+ /* If we're using deemphasis select the nearest available sample
+ * rate.
+ */
+ if (wm8904->deemph) {
+ best = 1;
+ for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+ if (abs(deemph_settings[i] - wm8904->fs) <
+ abs(deemph_settings[best] - wm8904->fs))
+ best = i;
+ }
+
+ val = best << WM8904_DEEMPH_SHIFT;
+ } else {
+ val = 0;
+ }
+
+ dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+ return snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_1,
+ WM8904_DEEMPH_MASK, val);
+}
+
+static int wm8904_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8904->deemph;
+ return 0;
+}
+
+static int wm8904_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ int deemph = ucontrol->value.enumerated.item[0];
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ wm8904->deemph = deemph;
+
+ return wm8904_set_deemph(codec);
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+
+static const char *input_mode_text[] = {
+ "Single-Ended", "Differential Line", "Differential Mic"
+};
+
+static const struct soc_enum lin_mode =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_LEFT_INPUT_1, 0, 3, input_mode_text);
+
+static const struct soc_enum rin_mode =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_RIGHT_INPUT_1, 0, 3, input_mode_text);
+
+static const char *hpf_mode_text[] = {
+ "Hi-fi", "Voice 1", "Voice 2", "Voice 3"
+};
+
+static const struct soc_enum hpf_mode =
+ SOC_ENUM_SINGLE(WM8904_ADC_DIGITAL_0, 5, 4, hpf_mode_text);
+
+static const struct snd_kcontrol_new wm8904_adc_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8904_ADC_DIGITAL_VOLUME_LEFT,
+ WM8904_ADC_DIGITAL_VOLUME_RIGHT, 1, 119, 0, digital_tlv),
+
+SOC_ENUM("Left Caputure Mode", lin_mode),
+SOC_ENUM("Right Capture Mode", rin_mode),
+
+/* No TLV since it depends on mode */
+SOC_DOUBLE_R("Capture Volume", WM8904_ANALOGUE_LEFT_INPUT_0,
+ WM8904_ANALOGUE_RIGHT_INPUT_0, 0, 31, 0),
+SOC_DOUBLE_R("Capture Switch", WM8904_ANALOGUE_LEFT_INPUT_0,
+ WM8904_ANALOGUE_RIGHT_INPUT_0, 7, 1, 1),
+
+SOC_SINGLE("High Pass Filter Switch", WM8904_ADC_DIGITAL_0, 4, 1, 0),
+SOC_ENUM("High Pass Filter Mode", hpf_mode),
+
+SOC_SINGLE("ADC 128x OSR Switch", WM8904_ANALOGUE_ADC_0, 0, 1, 0),
+};
+
+static const char *drc_path_text[] = {
+ "ADC", "DAC"
+};
+
+static const struct soc_enum drc_path =
+ SOC_ENUM_SINGLE(WM8904_DRC_0, 14, 2, drc_path_text);
+
+static const struct snd_kcontrol_new wm8904_dac_snd_controls[] = {
+SOC_SINGLE_TLV("Digital Playback Boost Volume",
+ WM8904_AUDIO_INTERFACE_0, 9, 3, 0, dac_boost_tlv),
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8904_DAC_DIGITAL_VOLUME_LEFT,
+ WM8904_DAC_DIGITAL_VOLUME_RIGHT, 1, 96, 0, digital_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Volume", WM8904_ANALOGUE_OUT1_LEFT,
+ WM8904_ANALOGUE_OUT1_RIGHT, 0, 63, 0, out_tlv),
+SOC_DOUBLE_R("Headphone Switch", WM8904_ANALOGUE_OUT1_LEFT,
+ WM8904_ANALOGUE_OUT1_RIGHT, 8, 1, 1),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8904_ANALOGUE_OUT1_LEFT,
+ WM8904_ANALOGUE_OUT1_RIGHT, 6, 1, 0),
+
+SOC_DOUBLE_R_TLV("Line Output Volume", WM8904_ANALOGUE_OUT2_LEFT,
+ WM8904_ANALOGUE_OUT2_RIGHT, 0, 63, 0, out_tlv),
+SOC_DOUBLE_R("Line Output Switch", WM8904_ANALOGUE_OUT2_LEFT,
+ WM8904_ANALOGUE_OUT2_RIGHT, 8, 1, 1),
+SOC_DOUBLE_R("Line Output ZC Switch", WM8904_ANALOGUE_OUT2_LEFT,
+ WM8904_ANALOGUE_OUT2_RIGHT, 6, 1, 0),
+
+SOC_SINGLE("EQ Switch", WM8904_EQ1, 0, 1, 0),
+SOC_SINGLE("DRC Switch", WM8904_DRC_0, 15, 1, 0),
+SOC_ENUM("DRC Path", drc_path),
+SOC_SINGLE("DAC OSRx2 Switch", WM8904_DAC_DIGITAL_1, 6, 1, 0),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+ wm8904_get_deemph, wm8904_put_deemph),
+};
+
+static const struct snd_kcontrol_new wm8904_snd_controls[] = {
+SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8904_DAC_DIGITAL_0, 4, 8, 15, 0,
+ sidetone_tlv),
+};
+
+static const struct snd_kcontrol_new wm8904_eq_controls[] = {
+SOC_SINGLE_TLV("EQ1 Volume", WM8904_EQ2, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ2 Volume", WM8904_EQ3, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ3 Volume", WM8904_EQ4, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ4 Volume", WM8904_EQ5, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ5 Volume", WM8904_EQ6, 0, 24, 0, eq_tlv),
+};
+
+static int cp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ BUG_ON(event != SND_SOC_DAPM_POST_PMU);
+
+ /* Maximum startup time */
+ udelay(500);
+
+ return 0;
+}
+
+static int sysclk_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* If we're using the FLL then we only start it when
+ * required; we assume that the configuration has been
+ * done previously and all we need to do is kick it
+ * off.
+ */
+ switch (wm8904->sysclk_src) {
+ case WM8904_CLK_FLL:
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA,
+ WM8904_FLL_OSC_ENA);
+
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_ENA,
+ WM8904_FLL_ENA);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static int out_pga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ int reg, val;
+ int dcs_mask;
+ int dcs_l, dcs_r;
+ int dcs_l_reg, dcs_r_reg;
+ int timeout;
+ int pwr_reg;
+
+ /* This code is shared between HP and LINEOUT; we do all our
+ * power management in stereo pairs to avoid latency issues so
+ * we reuse shift to identify which rather than strcmp() the
+ * name. */
+ reg = w->shift;
+
+ switch (reg) {
+ case WM8904_ANALOGUE_HP_0:
+ pwr_reg = WM8904_POWER_MANAGEMENT_2;
+ dcs_mask = WM8904_DCS_ENA_CHAN_0 | WM8904_DCS_ENA_CHAN_1;
+ dcs_r_reg = WM8904_DC_SERVO_8;
+ dcs_l_reg = WM8904_DC_SERVO_9;
+ dcs_l = 0;
+ dcs_r = 1;
+ break;
+ case WM8904_ANALOGUE_LINEOUT_0:
+ pwr_reg = WM8904_POWER_MANAGEMENT_3;
+ dcs_mask = WM8904_DCS_ENA_CHAN_2 | WM8904_DCS_ENA_CHAN_3;
+ dcs_r_reg = WM8904_DC_SERVO_6;
+ dcs_l_reg = WM8904_DC_SERVO_7;
+ dcs_l = 2;
+ dcs_r = 3;
+ break;
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Power on the PGAs */
+ snd_soc_update_bits(codec, pwr_reg,
+ WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA,
+ WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA);
+
+ /* Power on the amplifier */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_ENA | WM8904_HPR_ENA,
+ WM8904_HPL_ENA | WM8904_HPR_ENA);
+
+
+ /* Enable the first stage */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY,
+ WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY);
+
+ /* Power up the DC servo */
+ snd_soc_update_bits(codec, WM8904_DC_SERVO_0,
+ dcs_mask, dcs_mask);
+
+ /* Either calibrate the DC servo or restore cached state
+ * if we have that.
+ */
+ if (wm8904->dcs_state[dcs_l] || wm8904->dcs_state[dcs_r]) {
+ dev_dbg(codec->dev, "Restoring DC servo state\n");
+
+ snd_soc_write(codec, dcs_l_reg,
+ wm8904->dcs_state[dcs_l]);
+ snd_soc_write(codec, dcs_r_reg,
+ wm8904->dcs_state[dcs_r]);
+
+ snd_soc_write(codec, WM8904_DC_SERVO_1, dcs_mask);
+
+ timeout = 20;
+ } else {
+ dev_dbg(codec->dev, "Calibrating DC servo\n");
+
+ snd_soc_write(codec, WM8904_DC_SERVO_1,
+ dcs_mask << WM8904_DCS_TRIG_STARTUP_0_SHIFT);
+
+ timeout = 500;
+ }
+
+ /* Wait for DC servo to complete */
+ dcs_mask <<= WM8904_DCS_CAL_COMPLETE_SHIFT;
+ do {
+ val = snd_soc_read(codec, WM8904_DC_SERVO_READBACK_0);
+ if ((val & dcs_mask) == dcs_mask)
+ break;
+
+ msleep(1);
+ } while (--timeout);
+
+ if ((val & dcs_mask) != dcs_mask)
+ dev_warn(codec->dev, "DC servo timed out\n");
+ else
+ dev_dbg(codec->dev, "DC servo ready\n");
+
+ /* Enable the output stage */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP,
+ WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP);
+ break;
+
+ case SND_SOC_DAPM_POST_PMU:
+ /* Unshort the output itself */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_RMV_SHORT |
+ WM8904_HPR_RMV_SHORT,
+ WM8904_HPL_RMV_SHORT |
+ WM8904_HPR_RMV_SHORT);
+
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ /* Short the output */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_RMV_SHORT |
+ WM8904_HPR_RMV_SHORT, 0);
+ break;
+
+ case SND_SOC_DAPM_POST_PMD:
+ /* Cache the DC servo configuration; this will be
+ * invalidated if we change the configuration. */
+ wm8904->dcs_state[dcs_l] = snd_soc_read(codec, dcs_l_reg);
+ wm8904->dcs_state[dcs_r] = snd_soc_read(codec, dcs_r_reg);
+
+ snd_soc_update_bits(codec, WM8904_DC_SERVO_0,
+ dcs_mask, 0);
+
+ /* Disable the amplifier input and output stages */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_ENA | WM8904_HPR_ENA |
+ WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY |
+ WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP,
+ 0);
+
+ /* PGAs too */
+ snd_soc_update_bits(codec, pwr_reg,
+ WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA,
+ 0);
+ break;
+ }
+
+ return 0;
+}
+
+static const char *lin_text[] = {
+ "IN1L", "IN2L", "IN3L"
+};
+
+static const struct soc_enum lin_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_LEFT_INPUT_1, 2, 3, lin_text);
+
+static const struct snd_kcontrol_new lin_mux =
+ SOC_DAPM_ENUM("Left Capture Mux", lin_enum);
+
+static const struct soc_enum lin_inv_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_LEFT_INPUT_1, 4, 3, lin_text);
+
+static const struct snd_kcontrol_new lin_inv_mux =
+ SOC_DAPM_ENUM("Left Capture Inveting Mux", lin_inv_enum);
+
+static const char *rin_text[] = {
+ "IN1R", "IN2R", "IN3R"
+};
+
+static const struct soc_enum rin_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_RIGHT_INPUT_1, 2, 3, rin_text);
+
+static const struct snd_kcontrol_new rin_mux =
+ SOC_DAPM_ENUM("Right Capture Mux", rin_enum);
+
+static const struct soc_enum rin_inv_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_RIGHT_INPUT_1, 4, 3, rin_text);
+
+static const struct snd_kcontrol_new rin_inv_mux =
+ SOC_DAPM_ENUM("Right Capture Inveting Mux", rin_inv_enum);
+
+static const char *aif_text[] = {
+ "Left", "Right"
+};
+
+static const struct soc_enum aifoutl_enum =
+ SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 7, 2, aif_text);
+
+static const struct snd_kcontrol_new aifoutl_mux =
+ SOC_DAPM_ENUM("AIFOUTL Mux", aifoutl_enum);
+
+static const struct soc_enum aifoutr_enum =
+ SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 6, 2, aif_text);
+
+static const struct snd_kcontrol_new aifoutr_mux =
+ SOC_DAPM_ENUM("AIFOUTR Mux", aifoutr_enum);
+
+static const struct soc_enum aifinl_enum =
+ SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 5, 2, aif_text);
+
+static const struct snd_kcontrol_new aifinl_mux =
+ SOC_DAPM_ENUM("AIFINL Mux", aifinl_enum);
+
+static const struct soc_enum aifinr_enum =
+ SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 4, 2, aif_text);
+
+static const struct snd_kcontrol_new aifinr_mux =
+ SOC_DAPM_ENUM("AIFINR Mux", aifinr_enum);
+
+static const struct snd_soc_dapm_widget wm8904_core_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("SYSCLK", WM8904_CLOCK_RATES_2, 2, 0, sysclk_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8904_CLOCK_RATES_2, 1, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("TOCLK", WM8904_CLOCK_RATES_2, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8904_adc_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1L"),
+SND_SOC_DAPM_INPUT("IN1R"),
+SND_SOC_DAPM_INPUT("IN2L"),
+SND_SOC_DAPM_INPUT("IN2R"),
+SND_SOC_DAPM_INPUT("IN3L"),
+SND_SOC_DAPM_INPUT("IN3R"),
+
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8904_MIC_BIAS_CONTROL_0, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lin_mux),
+SND_SOC_DAPM_MUX("Left Capture Inverting Mux", SND_SOC_NOPM, 0, 0,
+ &lin_inv_mux),
+SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rin_mux),
+SND_SOC_DAPM_MUX("Right Capture Inverting Mux", SND_SOC_NOPM, 0, 0,
+ &rin_inv_mux),
+
+SND_SOC_DAPM_PGA("Left Capture PGA", WM8904_POWER_MANAGEMENT_0, 1, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA("Right Capture PGA", WM8904_POWER_MANAGEMENT_0, 0, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_ADC("ADCL", NULL, WM8904_POWER_MANAGEMENT_6, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, WM8904_POWER_MANAGEMENT_6, 0, 0),
+
+SND_SOC_DAPM_MUX("AIFOUTL Mux", SND_SOC_NOPM, 0, 0, &aifoutl_mux),
+SND_SOC_DAPM_MUX("AIFOUTR Mux", SND_SOC_NOPM, 0, 0, &aifoutr_mux),
+
+SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8904_dac_dapm_widgets[] = {
+SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &aifinl_mux),
+SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &aifinr_mux),
+
+SND_SOC_DAPM_DAC("DACL", NULL, WM8904_POWER_MANAGEMENT_6, 3, 0),
+SND_SOC_DAPM_DAC("DACR", NULL, WM8904_POWER_MANAGEMENT_6, 2, 0),
+
+SND_SOC_DAPM_SUPPLY("Charge pump", WM8904_CHARGE_PUMP_0, 0, 0, cp_event,
+ SND_SOC_DAPM_POST_PMU),
+
+SND_SOC_DAPM_PGA("HPL PGA", SND_SOC_NOPM, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("HPR PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("LINEL PGA", SND_SOC_NOPM, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINER PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_E("Headphone Output", SND_SOC_NOPM, WM8904_ANALOGUE_HP_0,
+ 0, NULL, 0, out_pga_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_PGA_E("Line Output", SND_SOC_NOPM, WM8904_ANALOGUE_LINEOUT_0,
+ 0, NULL, 0, out_pga_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LINEOUTL"),
+SND_SOC_DAPM_OUTPUT("LINEOUTR"),
+};
+
+static const char *out_mux_text[] = {
+ "DAC", "Bypass"
+};
+
+static const struct soc_enum hpl_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 3, 2, out_mux_text);
+
+static const struct snd_kcontrol_new hpl_mux =
+ SOC_DAPM_ENUM("HPL Mux", hpl_enum);
+
+static const struct soc_enum hpr_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 2, 2, out_mux_text);
+
+static const struct snd_kcontrol_new hpr_mux =
+ SOC_DAPM_ENUM("HPR Mux", hpr_enum);
+
+static const struct soc_enum linel_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 1, 2, out_mux_text);
+
+static const struct snd_kcontrol_new linel_mux =
+ SOC_DAPM_ENUM("LINEL Mux", linel_enum);
+
+static const struct soc_enum liner_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 0, 2, out_mux_text);
+
+static const struct snd_kcontrol_new liner_mux =
+ SOC_DAPM_ENUM("LINEL Mux", liner_enum);
+
+static const char *sidetone_text[] = {
+ "None", "Left", "Right"
+};
+
+static const struct soc_enum dacl_sidetone_enum =
+ SOC_ENUM_SINGLE(WM8904_DAC_DIGITAL_0, 2, 3, sidetone_text);
+
+static const struct snd_kcontrol_new dacl_sidetone_mux =
+ SOC_DAPM_ENUM("Left Sidetone Mux", dacl_sidetone_enum);
+
+static const struct soc_enum dacr_sidetone_enum =
+ SOC_ENUM_SINGLE(WM8904_DAC_DIGITAL_0, 0, 3, sidetone_text);
+
+static const struct snd_kcontrol_new dacr_sidetone_mux =
+ SOC_DAPM_ENUM("Right Sidetone Mux", dacr_sidetone_enum);
+
+static const struct snd_soc_dapm_widget wm8904_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("Class G", WM8904_CLASS_W_0, 0, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Bypass", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Bypass", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &dacl_sidetone_mux),
+SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &dacr_sidetone_mux),
+
+SND_SOC_DAPM_MUX("HPL Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
+SND_SOC_DAPM_MUX("HPR Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
+SND_SOC_DAPM_MUX("LINEL Mux", SND_SOC_NOPM, 0, 0, &linel_mux),
+SND_SOC_DAPM_MUX("LINER Mux", SND_SOC_NOPM, 0, 0, &liner_mux),
+};
+
+static const struct snd_soc_dapm_route core_intercon[] = {
+ { "CLK_DSP", NULL, "SYSCLK" },
+ { "TOCLK", NULL, "SYSCLK" },
+};
+
+static const struct snd_soc_dapm_route adc_intercon[] = {
+ { "Left Capture Mux", "IN1L", "IN1L" },
+ { "Left Capture Mux", "IN2L", "IN2L" },
+ { "Left Capture Mux", "IN3L", "IN3L" },
+
+ { "Left Capture Inverting Mux", "IN1L", "IN1L" },
+ { "Left Capture Inverting Mux", "IN2L", "IN2L" },
+ { "Left Capture Inverting Mux", "IN3L", "IN3L" },
+
+ { "Right Capture Mux", "IN1R", "IN1R" },
+ { "Right Capture Mux", "IN2R", "IN2R" },
+ { "Right Capture Mux", "IN3R", "IN3R" },
+
+ { "Right Capture Inverting Mux", "IN1R", "IN1R" },
+ { "Right Capture Inverting Mux", "IN2R", "IN2R" },
+ { "Right Capture Inverting Mux", "IN3R", "IN3R" },
+
+ { "Left Capture PGA", NULL, "Left Capture Mux" },
+ { "Left Capture PGA", NULL, "Left Capture Inverting Mux" },
+
+ { "Right Capture PGA", NULL, "Right Capture Mux" },
+ { "Right Capture PGA", NULL, "Right Capture Inverting Mux" },
+
+ { "AIFOUTL", "Left", "ADCL" },
+ { "AIFOUTL", "Right", "ADCR" },
+ { "AIFOUTR", "Left", "ADCL" },
+ { "AIFOUTR", "Right", "ADCR" },
+
+ { "ADCL", NULL, "CLK_DSP" },
+ { "ADCL", NULL, "Left Capture PGA" },
+
+ { "ADCR", NULL, "CLK_DSP" },
+ { "ADCR", NULL, "Right Capture PGA" },
+};
+
+static const struct snd_soc_dapm_route dac_intercon[] = {
+ { "DACL", "Right", "AIFINR" },
+ { "DACL", "Left", "AIFINL" },
+ { "DACL", NULL, "CLK_DSP" },
+
+ { "DACR", "Right", "AIFINR" },
+ { "DACR", "Left", "AIFINL" },
+ { "DACR", NULL, "CLK_DSP" },
+
+ { "Charge pump", NULL, "SYSCLK" },
+
+ { "Headphone Output", NULL, "HPL PGA" },
+ { "Headphone Output", NULL, "HPR PGA" },
+ { "Headphone Output", NULL, "Charge pump" },
+ { "Headphone Output", NULL, "TOCLK" },
+
+ { "Line Output", NULL, "LINEL PGA" },
+ { "Line Output", NULL, "LINER PGA" },
+ { "Line Output", NULL, "Charge pump" },
+ { "Line Output", NULL, "TOCLK" },
+
+ { "HPOUTL", NULL, "Headphone Output" },
+ { "HPOUTR", NULL, "Headphone Output" },
+
+ { "LINEOUTL", NULL, "Line Output" },
+ { "LINEOUTR", NULL, "Line Output" },
+};
+
+static const struct snd_soc_dapm_route wm8904_intercon[] = {
+ { "Left Sidetone", "Left", "ADCL" },
+ { "Left Sidetone", "Right", "ADCR" },
+ { "DACL", NULL, "Left Sidetone" },
+
+ { "Right Sidetone", "Left", "ADCL" },
+ { "Right Sidetone", "Right", "ADCR" },
+ { "DACR", NULL, "Right Sidetone" },
+
+ { "Left Bypass", NULL, "Class G" },
+ { "Left Bypass", NULL, "Left Capture PGA" },
+
+ { "Right Bypass", NULL, "Class G" },
+ { "Right Bypass", NULL, "Right Capture PGA" },
+
+ { "HPL Mux", "DAC", "DACL" },
+ { "HPL Mux", "Bypass", "Left Bypass" },
+
+ { "HPR Mux", "DAC", "DACR" },
+ { "HPR Mux", "Bypass", "Right Bypass" },
+
+ { "LINEL Mux", "DAC", "DACL" },
+ { "LINEL Mux", "Bypass", "Left Bypass" },
+
+ { "LINER Mux", "DAC", "DACR" },
+ { "LINER Mux", "Bypass", "Right Bypass" },
+
+ { "HPL PGA", NULL, "HPL Mux" },
+ { "HPR PGA", NULL, "HPR Mux" },
+
+ { "LINEL PGA", NULL, "LINEL Mux" },
+ { "LINER PGA", NULL, "LINER Mux" },
+};
+
+static const struct snd_soc_dapm_route wm8912_intercon[] = {
+ { "HPL PGA", NULL, "DACL" },
+ { "HPR PGA", NULL, "DACR" },
+
+ { "LINEL PGA", NULL, "DACL" },
+ { "LINER PGA", NULL, "DACR" },
+};
+
+static int wm8904_add_widgets(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8904_core_dapm_widgets,
+ ARRAY_SIZE(wm8904_core_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, core_intercon,
+ ARRAY_SIZE(core_intercon));
+
+ switch (wm8904->devtype) {
+ case WM8904:
+ snd_soc_add_controls(codec, wm8904_adc_snd_controls,
+ ARRAY_SIZE(wm8904_adc_snd_controls));
+ snd_soc_add_controls(codec, wm8904_dac_snd_controls,
+ ARRAY_SIZE(wm8904_dac_snd_controls));
+ snd_soc_add_controls(codec, wm8904_snd_controls,
+ ARRAY_SIZE(wm8904_snd_controls));
+
+ snd_soc_dapm_new_controls(dapm, wm8904_adc_dapm_widgets,
+ ARRAY_SIZE(wm8904_adc_dapm_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8904_dac_dapm_widgets,
+ ARRAY_SIZE(wm8904_dac_dapm_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8904_dapm_widgets,
+ ARRAY_SIZE(wm8904_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, core_intercon,
+ ARRAY_SIZE(core_intercon));
+ snd_soc_dapm_add_routes(dapm, adc_intercon,
+ ARRAY_SIZE(adc_intercon));
+ snd_soc_dapm_add_routes(dapm, dac_intercon,
+ ARRAY_SIZE(dac_intercon));
+ snd_soc_dapm_add_routes(dapm, wm8904_intercon,
+ ARRAY_SIZE(wm8904_intercon));
+ break;
+
+ case WM8912:
+ snd_soc_add_controls(codec, wm8904_dac_snd_controls,
+ ARRAY_SIZE(wm8904_dac_snd_controls));
+
+ snd_soc_dapm_new_controls(dapm, wm8904_dac_dapm_widgets,
+ ARRAY_SIZE(wm8904_dac_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, dac_intercon,
+ ARRAY_SIZE(dac_intercon));
+ snd_soc_dapm_add_routes(dapm, wm8912_intercon,
+ ARRAY_SIZE(wm8912_intercon));
+ break;
+ }
+
+ snd_soc_dapm_new_widgets(dapm);
+ return 0;
+}
+
+static struct {
+ int ratio;
+ unsigned int clk_sys_rate;
+} clk_sys_rates[] = {
+ { 64, 0 },
+ { 128, 1 },
+ { 192, 2 },
+ { 256, 3 },
+ { 384, 4 },
+ { 512, 5 },
+ { 786, 6 },
+ { 1024, 7 },
+ { 1408, 8 },
+ { 1536, 9 },
+};
+
+static struct {
+ int rate;
+ int sample_rate;
+} sample_rates[] = {
+ { 8000, 0 },
+ { 11025, 1 },
+ { 12000, 1 },
+ { 16000, 2 },
+ { 22050, 3 },
+ { 24000, 3 },
+ { 32000, 4 },
+ { 44100, 5 },
+ { 48000, 5 },
+};
+
+static struct {
+ int div; /* *10 due to .5s */
+ int bclk_div;
+} bclk_divs[] = {
+ { 10, 0 },
+ { 15, 1 },
+ { 20, 2 },
+ { 30, 3 },
+ { 40, 4 },
+ { 50, 5 },
+ { 55, 6 },
+ { 60, 7 },
+ { 80, 8 },
+ { 100, 9 },
+ { 110, 10 },
+ { 120, 11 },
+ { 160, 12 },
+ { 200, 13 },
+ { 220, 14 },
+ { 240, 16 },
+ { 200, 17 },
+ { 320, 18 },
+ { 440, 19 },
+ { 480, 20 },
+};
+
+
+static int wm8904_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ int ret, i, best, best_val, cur_val;
+ unsigned int aif1 = 0;
+ unsigned int aif2 = 0;
+ unsigned int aif3 = 0;
+ unsigned int clock1 = 0;
+ unsigned int dac_digital1 = 0;
+
+ /* What BCLK do we need? */
+ wm8904->fs = params_rate(params);
+ if (wm8904->tdm_slots) {
+ dev_dbg(codec->dev, "Configuring for %d %d bit TDM slots\n",
+ wm8904->tdm_slots, wm8904->tdm_width);
+ wm8904->bclk = snd_soc_calc_bclk(wm8904->fs,
+ wm8904->tdm_width, 2,
+ wm8904->tdm_slots);
+ } else {
+ wm8904->bclk = snd_soc_params_to_bclk(params);
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ aif1 |= 0x40;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ aif1 |= 0x80;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ aif1 |= 0xc0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+
+ dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm8904->bclk);
+
+ ret = wm8904_configure_clocking(codec);
+ if (ret != 0)
+ return ret;
+
+ /* Select nearest CLK_SYS_RATE */
+ best = 0;
+ best_val = abs((wm8904->sysclk_rate / clk_sys_rates[0].ratio)
+ - wm8904->fs);
+ for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+ cur_val = abs((wm8904->sysclk_rate /
+ clk_sys_rates[i].ratio) - wm8904->fs);
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ dev_dbg(codec->dev, "Selected CLK_SYS_RATIO of %d\n",
+ clk_sys_rates[best].ratio);
+ clock1 |= (clk_sys_rates[best].clk_sys_rate
+ << WM8904_CLK_SYS_RATE_SHIFT);
+
+ /* SAMPLE_RATE */
+ best = 0;
+ best_val = abs(wm8904->fs - sample_rates[0].rate);
+ for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+ /* Closest match */
+ cur_val = abs(wm8904->fs - sample_rates[i].rate);
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n",
+ sample_rates[best].rate);
+ clock1 |= (sample_rates[best].sample_rate
+ << WM8904_SAMPLE_RATE_SHIFT);
+
+ /* Enable sloping stopband filter for low sample rates */
+ if (wm8904->fs <= 24000)
+ dac_digital1 |= WM8904_DAC_SB_FILT;
+
+ /* BCLK_DIV */
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+ cur_val = ((wm8904->sysclk_rate * 10) / bclk_divs[i].div)
+ - wm8904->bclk;
+ if (cur_val < 0) /* Table is sorted */
+ break;
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ wm8904->bclk = (wm8904->sysclk_rate * 10) / bclk_divs[best].div;
+ dev_dbg(codec->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n",
+ bclk_divs[best].div, wm8904->bclk);
+ aif2 |= bclk_divs[best].bclk_div;
+
+ /* LRCLK is a simple fraction of BCLK */
+ dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm8904->bclk / wm8904->fs);
+ aif3 |= wm8904->bclk / wm8904->fs;
+
+ /* Apply the settings */
+ snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_1,
+ WM8904_DAC_SB_FILT, dac_digital1);
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_1,
+ WM8904_AIF_WL_MASK, aif1);
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_2,
+ WM8904_BCLK_DIV_MASK, aif2);
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_3,
+ WM8904_LRCLK_RATE_MASK, aif3);
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_1,
+ WM8904_SAMPLE_RATE_MASK |
+ WM8904_CLK_SYS_RATE_MASK, clock1);
+
+ /* Update filters for the new settings */
+ wm8904_set_retune_mobile(codec);
+ wm8904_set_deemph(codec);
+
+ return 0;
+}
+
+
+static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8904_priv *priv = snd_soc_codec_get_drvdata(codec);
+
+ switch (clk_id) {
+ case WM8904_CLK_MCLK:
+ priv->sysclk_src = clk_id;
+ priv->mclk_rate = freq;
+ break;
+
+ case WM8904_CLK_FLL:
+ priv->sysclk_src = clk_id;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+ wm8904_configure_clocking(codec);
+
+ return 0;
+}
+
+static int wm8904_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ unsigned int aif1 = 0;
+ unsigned int aif3 = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ aif3 |= WM8904_LRCLK_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ aif1 |= WM8904_BCLK_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif1 |= WM8904_BCLK_DIR;
+ aif3 |= WM8904_LRCLK_DIR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_B:
+ aif1 |= WM8904_AIF_LRCLK_INV;
+ case SND_SOC_DAIFMT_DSP_A:
+ aif1 |= 0x3;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif1 |= 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif1 |= 0x1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8904_AIF_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif1 |= WM8904_AIF_BCLK_INV | WM8904_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8904_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif1 |= WM8904_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_1,
+ WM8904_AIF_BCLK_INV | WM8904_AIF_LRCLK_INV |
+ WM8904_AIF_FMT_MASK | WM8904_BCLK_DIR, aif1);
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_3,
+ WM8904_LRCLK_DIR, aif3);
+
+ return 0;
+}
+
+
+static int wm8904_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ int aif1 = 0;
+
+ /* Don't need to validate anything if we're turning off TDM */
+ if (slots == 0)
+ goto out;
+
+ /* Note that we allow configurations we can't handle ourselves -
+ * for example, we can generate clocks for slots 2 and up even if
+ * we can't use those slots ourselves.
+ */
+ aif1 |= WM8904_AIFADC_TDM | WM8904_AIFDAC_TDM;
+
+ switch (rx_mask) {
+ case 3:
+ break;
+ case 0xc:
+ aif1 |= WM8904_AIFADC_TDM_CHAN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+
+ switch (tx_mask) {
+ case 3:
+ break;
+ case 0xc:
+ aif1 |= WM8904_AIFDAC_TDM_CHAN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+out:
+ wm8904->tdm_width = slot_width;
+ wm8904->tdm_slots = slots / 2;
+
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_1,
+ WM8904_AIFADC_TDM | WM8904_AIFADC_TDM_CHAN |
+ WM8904_AIFDAC_TDM | WM8904_AIFDAC_TDM_CHAN, aif1);
+
+ return 0;
+}
+
+struct _fll_div {
+ u16 fll_fratio;
+ u16 fll_outdiv;
+ u16 fll_clk_ref_div;
+ u16 n;
+ u16 k;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static struct {
+ unsigned int min;
+ unsigned int max;
+ u16 fll_fratio;
+ int ratio;
+} fll_fratios[] = {
+ { 0, 64000, 4, 16 },
+ { 64000, 128000, 3, 8 },
+ { 128000, 256000, 2, 4 },
+ { 256000, 1000000, 1, 2 },
+ { 1000000, 13500000, 0, 1 },
+};
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+ unsigned int Fout)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+ unsigned int div;
+ int i;
+
+ /* Fref must be <=13.5MHz */
+ div = 1;
+ fll_div->fll_clk_ref_div = 0;
+ while ((Fref / div) > 13500000) {
+ div *= 2;
+ fll_div->fll_clk_ref_div++;
+
+ if (div > 8) {
+ pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+ Fref);
+ return -EINVAL;
+ }
+ }
+
+ pr_debug("Fref=%u Fout=%u\n", Fref, Fout);
+
+ /* Apply the division for our remaining calculations */
+ Fref /= div;
+
+ /* Fvco should be 90-100MHz; don't check the upper bound */
+ div = 4;
+ while (Fout * div < 90000000) {
+ div++;
+ if (div > 64) {
+ pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+ Fout);
+ return -EINVAL;
+ }
+ }
+ target = Fout * div;
+ fll_div->fll_outdiv = div - 1;
+
+ pr_debug("Fvco=%dHz\n", target);
+
+ /* Find an appropriate FLL_FRATIO and factor it out of the target */
+ for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+ if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+ fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+ target /= fll_fratios[i].ratio;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(fll_fratios)) {
+ pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+ return -EINVAL;
+ }
+
+ /* Now, calculate N.K */
+ Ndiv = target / Fref;
+
+ fll_div->n = Ndiv;
+ Nmod = target % Fref;
+ pr_debug("Nmod=%d\n", Nmod);
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, Fref);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ fll_div->k = K / 10;
+
+ pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n",
+ fll_div->n, fll_div->k,
+ fll_div->fll_fratio, fll_div->fll_outdiv,
+ fll_div->fll_clk_ref_div);
+
+ return 0;
+}
+
+static int wm8904_set_fll(struct snd_soc_dai *dai, int fll_id, int source,
+ unsigned int Fref, unsigned int Fout)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ struct _fll_div fll_div;
+ int ret, val;
+ int clock2, fll1;
+
+ /* Any change? */
+ if (source == wm8904->fll_src && Fref == wm8904->fll_fref &&
+ Fout == wm8904->fll_fout)
+ return 0;
+
+ clock2 = snd_soc_read(codec, WM8904_CLOCK_RATES_2);
+
+ if (Fout == 0) {
+ dev_dbg(codec->dev, "FLL disabled\n");
+
+ wm8904->fll_fref = 0;
+ wm8904->fll_fout = 0;
+
+ /* Gate SYSCLK to avoid glitches */
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+ WM8904_CLK_SYS_ENA, 0);
+
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+
+ goto out;
+ }
+
+ /* Validate the FLL ID */
+ switch (source) {
+ case WM8904_FLL_MCLK:
+ case WM8904_FLL_LRCLK:
+ case WM8904_FLL_BCLK:
+ ret = fll_factors(&fll_div, Fref, Fout);
+ if (ret != 0)
+ return ret;
+ break;
+
+ case WM8904_FLL_FREE_RUNNING:
+ dev_dbg(codec->dev, "Using free running FLL\n");
+ /* Force 12MHz and output/4 for now */
+ Fout = 12000000;
+ Fref = 12000000;
+
+ memset(&fll_div, 0, sizeof(fll_div));
+ fll_div.fll_outdiv = 3;
+ break;
+
+ default:
+ dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id);
+ return -EINVAL;
+ }
+
+ /* Save current state then disable the FLL and SYSCLK to avoid
+ * misclocking */
+ fll1 = snd_soc_read(codec, WM8904_FLL_CONTROL_1);
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+ WM8904_CLK_SYS_ENA, 0);
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+
+ /* Unlock forced oscilator control to switch it on/off */
+ snd_soc_update_bits(codec, WM8904_CONTROL_INTERFACE_TEST_1,
+ WM8904_USER_KEY, WM8904_USER_KEY);
+
+ if (fll_id == WM8904_FLL_FREE_RUNNING) {
+ val = WM8904_FLL_FRC_NCO;
+ } else {
+ val = 0;
+ }
+
+ snd_soc_update_bits(codec, WM8904_FLL_NCO_TEST_1, WM8904_FLL_FRC_NCO,
+ val);
+ snd_soc_update_bits(codec, WM8904_CONTROL_INTERFACE_TEST_1,
+ WM8904_USER_KEY, 0);
+
+ switch (fll_id) {
+ case WM8904_FLL_MCLK:
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+ WM8904_FLL_CLK_REF_SRC_MASK, 0);
+ break;
+
+ case WM8904_FLL_LRCLK:
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+ WM8904_FLL_CLK_REF_SRC_MASK, 1);
+ break;
+
+ case WM8904_FLL_BCLK:
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+ WM8904_FLL_CLK_REF_SRC_MASK, 2);
+ break;
+ }
+
+ if (fll_div.k)
+ val = WM8904_FLL_FRACN_ENA;
+ else
+ val = 0;
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_FRACN_ENA, val);
+
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_2,
+ WM8904_FLL_OUTDIV_MASK | WM8904_FLL_FRATIO_MASK,
+ (fll_div.fll_outdiv << WM8904_FLL_OUTDIV_SHIFT) |
+ (fll_div.fll_fratio << WM8904_FLL_FRATIO_SHIFT));
+
+ snd_soc_write(codec, WM8904_FLL_CONTROL_3, fll_div.k);
+
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_4, WM8904_FLL_N_MASK,
+ fll_div.n << WM8904_FLL_N_SHIFT);
+
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+ WM8904_FLL_CLK_REF_DIV_MASK,
+ fll_div.fll_clk_ref_div
+ << WM8904_FLL_CLK_REF_DIV_SHIFT);
+
+ dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
+
+ wm8904->fll_fref = Fref;
+ wm8904->fll_fout = Fout;
+ wm8904->fll_src = source;
+
+ /* Enable the FLL if it was previously active */
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA, fll1);
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_ENA, fll1);
+
+out:
+ /* Reenable SYSCLK if it was previously active */
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+ WM8904_CLK_SYS_ENA, clock2);
+
+ return 0;
+}
+
+static int wm8904_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int val;
+
+ if (mute)
+ val = WM8904_DAC_MUTE;
+ else
+ val = 0;
+
+ snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_1, WM8904_DAC_MUTE, val);
+
+ return 0;
+}
+
+static void wm8904_sync_cache(struct snd_soc_codec *codec)
+{
+ u16 *reg_cache = codec->reg_cache;
+ int i;
+
+ if (!codec->cache_sync)
+ return;
+
+ codec->cache_only = 0;
+
+ /* Sync back cached values if they're different from the
+ * hardware default.
+ */
+ for (i = 1; i < codec->driver->reg_cache_size; i++) {
+ if (!wm8904_access[i].writable)
+ continue;
+
+ if (reg_cache[i] == wm8904_reg[i])
+ continue;
+
+ snd_soc_write(codec, i, reg_cache[i]);
+ }
+
+ codec->cache_sync = 0;
+}
+
+static int wm8904_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID resistance 2*50k */
+ snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+ WM8904_VMID_RES_MASK,
+ 0x1 << WM8904_VMID_RES_SHIFT);
+
+ /* Normal bias current */
+ snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+ WM8904_ISEL_MASK, 2 << WM8904_ISEL_SHIFT);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8904->supplies),
+ wm8904->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ wm8904_sync_cache(codec);
+
+ /* Enable bias */
+ snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+ WM8904_BIAS_ENA, WM8904_BIAS_ENA);
+
+ /* Enable VMID, VMID buffering, 2*5k resistance */
+ snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+ WM8904_VMID_ENA |
+ WM8904_VMID_RES_MASK,
+ WM8904_VMID_ENA |
+ 0x3 << WM8904_VMID_RES_SHIFT);
+
+ /* Let VMID ramp */
+ msleep(1);
+ }
+
+ /* Maintain VMID with 2*250k */
+ snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+ WM8904_VMID_RES_MASK,
+ 0x2 << WM8904_VMID_RES_SHIFT);
+
+ /* Bias current *0.5 */
+ snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+ WM8904_ISEL_MASK, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Turn off VMID */
+ snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+ WM8904_VMID_RES_MASK | WM8904_VMID_ENA, 0);
+
+ /* Stop bias generation */
+ snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+ WM8904_BIAS_ENA, 0);
+
+#ifdef CONFIG_REGULATOR
+ /* Post 2.6.34 we will be able to get a callback when
+ * the regulators are disabled which we can use but
+ * for now just assume that the power will be cut if
+ * the regulator API is in use.
+ */
+ codec->cache_sync = 1;
+#endif
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies),
+ wm8904->supplies);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8904_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8904_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8904_dai_ops = {
+ .set_sysclk = wm8904_set_sysclk,
+ .set_fmt = wm8904_set_fmt,
+ .set_tdm_slot = wm8904_set_tdm_slot,
+ .set_pll = wm8904_set_fll,
+ .hw_params = wm8904_hw_params,
+ .digital_mute = wm8904_digital_mute,
+};
+
+static struct snd_soc_dai_driver wm8904_dai = {
+ .name = "wm8904-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8904_RATES,
+ .formats = WM8904_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8904_RATES,
+ .formats = WM8904_FORMATS,
+ },
+ .ops = &wm8904_dai_ops,
+ .symmetric_rates = 1,
+};
+
+#ifdef CONFIG_PM
+static int wm8904_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8904_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8904_resume(struct snd_soc_codec *codec)
+{
+ wm8904_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define wm8904_suspend NULL
+#define wm8904_resume NULL
+#endif
+
+static void wm8904_handle_retune_mobile_pdata(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ struct snd_kcontrol_new control =
+ SOC_ENUM_EXT("EQ Mode",
+ wm8904->retune_mobile_enum,
+ wm8904_get_retune_mobile_enum,
+ wm8904_put_retune_mobile_enum);
+ int ret, i, j;
+ const char **t;
+
+ /* We need an array of texts for the enum API but the number
+ * of texts is likely to be less than the number of
+ * configurations due to the sample rate dependency of the
+ * configurations. */
+ wm8904->num_retune_mobile_texts = 0;
+ wm8904->retune_mobile_texts = NULL;
+ for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+ for (j = 0; j < wm8904->num_retune_mobile_texts; j++) {
+ if (strcmp(pdata->retune_mobile_cfgs[i].name,
+ wm8904->retune_mobile_texts[j]) == 0)
+ break;
+ }
+
+ if (j != wm8904->num_retune_mobile_texts)
+ continue;
+
+ /* Expand the array... */
+ t = krealloc(wm8904->retune_mobile_texts,
+ sizeof(char *) *
+ (wm8904->num_retune_mobile_texts + 1),
+ GFP_KERNEL);
+ if (t == NULL)
+ continue;
+
+ /* ...store the new entry... */
+ t[wm8904->num_retune_mobile_texts] =
+ pdata->retune_mobile_cfgs[i].name;
+
+ /* ...and remember the new version. */
+ wm8904->num_retune_mobile_texts++;
+ wm8904->retune_mobile_texts = t;
+ }
+
+ dev_dbg(codec->dev, "Allocated %d unique ReTune Mobile names\n",
+ wm8904->num_retune_mobile_texts);
+
+ wm8904->retune_mobile_enum.max = wm8904->num_retune_mobile_texts;
+ wm8904->retune_mobile_enum.texts = wm8904->retune_mobile_texts;
+
+ ret = snd_soc_add_controls(codec, &control, 1);
+ if (ret != 0)
+ dev_err(codec->dev,
+ "Failed to add ReTune Mobile control: %d\n", ret);
+}
+
+static void wm8904_handle_pdata(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ int ret, i;
+
+ if (!pdata) {
+ snd_soc_add_controls(codec, wm8904_eq_controls,
+ ARRAY_SIZE(wm8904_eq_controls));
+ return;
+ }
+
+ dev_dbg(codec->dev, "%d DRC configurations\n", pdata->num_drc_cfgs);
+
+ if (pdata->num_drc_cfgs) {
+ struct snd_kcontrol_new control =
+ SOC_ENUM_EXT("DRC Mode", wm8904->drc_enum,
+ wm8904_get_drc_enum, wm8904_put_drc_enum);
+
+ /* We need an array of texts for the enum API */
+ wm8904->drc_texts = kmalloc(sizeof(char *)
+ * pdata->num_drc_cfgs, GFP_KERNEL);
+ if (!wm8904->drc_texts) {
+ dev_err(codec->dev,
+ "Failed to allocate %d DRC config texts\n",
+ pdata->num_drc_cfgs);
+ return;
+ }
+
+ for (i = 0; i < pdata->num_drc_cfgs; i++)
+ wm8904->drc_texts[i] = pdata->drc_cfgs[i].name;
+
+ wm8904->drc_enum.max = pdata->num_drc_cfgs;
+ wm8904->drc_enum.texts = wm8904->drc_texts;
+
+ ret = snd_soc_add_controls(codec, &control, 1);
+ if (ret != 0)
+ dev_err(codec->dev,
+ "Failed to add DRC mode control: %d\n", ret);
+
+ wm8904_set_drc(codec);
+ }
+
+ dev_dbg(codec->dev, "%d ReTune Mobile configurations\n",
+ pdata->num_retune_mobile_cfgs);
+
+ if (pdata->num_retune_mobile_cfgs)
+ wm8904_handle_retune_mobile_pdata(codec);
+ else
+ snd_soc_add_controls(codec, wm8904_eq_controls,
+ ARRAY_SIZE(wm8904_eq_controls));
+}
+
+
+static int wm8904_probe(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ u16 *reg_cache = codec->reg_cache;
+ int ret, i;
+
+ codec->cache_sync = 1;
+ codec->dapm.idle_bias_off = 1;
+
+ switch (wm8904->devtype) {
+ case WM8904:
+ break;
+ case WM8912:
+ memset(&wm8904_dai.capture, 0, sizeof(wm8904_dai.capture));
+ break;
+ default:
+ dev_err(codec->dev, "Unknown device type %d\n",
+ wm8904->devtype);
+ return -EINVAL;
+ }
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8904->supplies); i++)
+ wm8904->supplies[i].supply = wm8904_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8904->supplies),
+ wm8904->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8904->supplies),
+ wm8904->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ ret = snd_soc_read(codec, WM8904_SW_RESET_AND_ID);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read ID register\n");
+ goto err_enable;
+ }
+ if (ret != wm8904_reg[WM8904_SW_RESET_AND_ID]) {
+ dev_err(codec->dev, "Device is not a WM8904, ID is %x\n", ret);
+ ret = -EINVAL;
+ goto err_enable;
+ }
+
+ ret = snd_soc_read(codec, WM8904_REVISION);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read device revision: %d\n",
+ ret);
+ goto err_enable;
+ }
+ dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+ ret = wm8904_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ goto err_enable;
+ }
+
+ /* Change some default settings - latch VU and enable ZC */
+ snd_soc_update_bits(codec, WM8904_ADC_DIGITAL_VOLUME_LEFT,
+ WM8904_ADC_VU, WM8904_ADC_VU);
+ snd_soc_update_bits(codec, WM8904_ADC_DIGITAL_VOLUME_RIGHT,
+ WM8904_ADC_VU, WM8904_ADC_VU);
+ snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_VOLUME_LEFT,
+ WM8904_DAC_VU, WM8904_DAC_VU);
+ snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_VOLUME_RIGHT,
+ WM8904_DAC_VU, WM8904_DAC_VU);
+ snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT1_LEFT,
+ WM8904_HPOUT_VU | WM8904_HPOUTLZC,
+ WM8904_HPOUT_VU | WM8904_HPOUTLZC);
+ snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT1_RIGHT,
+ WM8904_HPOUT_VU | WM8904_HPOUTRZC,
+ WM8904_HPOUT_VU | WM8904_HPOUTRZC);
+ snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT2_LEFT,
+ WM8904_LINEOUT_VU | WM8904_LINEOUTLZC,
+ WM8904_LINEOUT_VU | WM8904_LINEOUTLZC);
+ snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT2_RIGHT,
+ WM8904_LINEOUT_VU | WM8904_LINEOUTRZC,
+ WM8904_LINEOUT_VU | WM8904_LINEOUTRZC);
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_0,
+ WM8904_SR_MODE, 0);
+
+ /* Apply configuration from the platform data. */
+ if (wm8904->pdata) {
+ for (i = 0; i < WM8904_GPIO_REGS; i++) {
+ if (!pdata->gpio_cfg[i])
+ continue;
+
+ reg_cache[WM8904_GPIO_CONTROL_1 + i]
+ = pdata->gpio_cfg[i] & 0xffff;
+ }
+
+ /* Zero is the default value for these anyway */
+ for (i = 0; i < WM8904_MIC_REGS; i++)
+ reg_cache[WM8904_MIC_BIAS_CONTROL_0 + i]
+ = pdata->mic_cfg[i];
+ }
+
+ /* Set Class W by default - this will be managed by the Class
+ * G widget at runtime where bypass paths are available.
+ */
+ snd_soc_update_bits(codec, WM8904_CLASS_W_0,
+ WM8904_CP_DYN_PWR, WM8904_CP_DYN_PWR);
+
+ /* Use normal bias source */
+ snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+ WM8904_POBCTRL, 0);
+
+ wm8904_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Bias level configuration will have done an extra enable */
+ regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+
+ wm8904_handle_pdata(codec);
+
+ wm8904_add_widgets(codec);
+
+ return 0;
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+ return ret;
+}
+
+static int wm8904_remove(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+ wm8904_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ regulator_bulk_free(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+ kfree(wm8904->retune_mobile_texts);
+ kfree(wm8904->drc_texts);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8904 = {
+ .probe = wm8904_probe,
+ .remove = wm8904_remove,
+ .suspend = wm8904_suspend,
+ .resume = wm8904_resume,
+ .set_bias_level = wm8904_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8904_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8904_reg,
+ .volatile_register = wm8904_volatile_register,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8904_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8904_priv *wm8904;
+ int ret;
+
+ wm8904 = kzalloc(sizeof(struct wm8904_priv), GFP_KERNEL);
+ if (wm8904 == NULL)
+ return -ENOMEM;
+
+ wm8904->devtype = id->driver_data;
+ i2c_set_clientdata(i2c, wm8904);
+ wm8904->control_data = i2c;
+ wm8904->pdata = i2c->dev.platform_data;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8904, &wm8904_dai, 1);
+ if (ret < 0)
+ kfree(wm8904);
+ return ret;
+}
+
+static __devexit int wm8904_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8904_i2c_id[] = {
+ { "wm8904", WM8904 },
+ { "wm8912", WM8912 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8904_i2c_id);
+
+static struct i2c_driver wm8904_i2c_driver = {
+ .driver = {
+ .name = "wm8904-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8904_i2c_probe,
+ .remove = __devexit_p(wm8904_i2c_remove),
+ .id_table = wm8904_i2c_id,
+};
+#endif
+
+static int __init wm8904_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8904_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8904 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8904_modinit);
+
+static void __exit wm8904_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8904_i2c_driver);
+#endif
+}
+module_exit(wm8904_exit);
+
+MODULE_DESCRIPTION("ASoC WM8904 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8904.h b/sound/soc/codecs/wm8904.h
new file mode 100644
index 00000000..9e8c8418
--- /dev/null
+++ b/sound/soc/codecs/wm8904.h
@@ -0,0 +1,1581 @@
+/*
+ * wm8904.h -- WM8904 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics, plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8904_H
+#define _WM8904_H
+
+#define WM8904_CLK_MCLK 1
+#define WM8904_CLK_FLL 2
+
+#define WM8904_FLL_MCLK 1
+#define WM8904_FLL_BCLK 2
+#define WM8904_FLL_LRCLK 3
+#define WM8904_FLL_FREE_RUNNING 4
+
+/*
+ * Register values.
+ */
+#define WM8904_SW_RESET_AND_ID 0x00
+#define WM8904_REVISION 0x01
+#define WM8904_BIAS_CONTROL_0 0x04
+#define WM8904_VMID_CONTROL_0 0x05
+#define WM8904_MIC_BIAS_CONTROL_0 0x06
+#define WM8904_MIC_BIAS_CONTROL_1 0x07
+#define WM8904_ANALOGUE_DAC_0 0x08
+#define WM8904_MIC_FILTER_CONTROL 0x09
+#define WM8904_ANALOGUE_ADC_0 0x0A
+#define WM8904_POWER_MANAGEMENT_0 0x0C
+#define WM8904_POWER_MANAGEMENT_2 0x0E
+#define WM8904_POWER_MANAGEMENT_3 0x0F
+#define WM8904_POWER_MANAGEMENT_6 0x12
+#define WM8904_CLOCK_RATES_0 0x14
+#define WM8904_CLOCK_RATES_1 0x15
+#define WM8904_CLOCK_RATES_2 0x16
+#define WM8904_AUDIO_INTERFACE_0 0x18
+#define WM8904_AUDIO_INTERFACE_1 0x19
+#define WM8904_AUDIO_INTERFACE_2 0x1A
+#define WM8904_AUDIO_INTERFACE_3 0x1B
+#define WM8904_DAC_DIGITAL_VOLUME_LEFT 0x1E
+#define WM8904_DAC_DIGITAL_VOLUME_RIGHT 0x1F
+#define WM8904_DAC_DIGITAL_0 0x20
+#define WM8904_DAC_DIGITAL_1 0x21
+#define WM8904_ADC_DIGITAL_VOLUME_LEFT 0x24
+#define WM8904_ADC_DIGITAL_VOLUME_RIGHT 0x25
+#define WM8904_ADC_DIGITAL_0 0x26
+#define WM8904_DIGITAL_MICROPHONE_0 0x27
+#define WM8904_DRC_0 0x28
+#define WM8904_DRC_1 0x29
+#define WM8904_DRC_2 0x2A
+#define WM8904_DRC_3 0x2B
+#define WM8904_ANALOGUE_LEFT_INPUT_0 0x2C
+#define WM8904_ANALOGUE_RIGHT_INPUT_0 0x2D
+#define WM8904_ANALOGUE_LEFT_INPUT_1 0x2E
+#define WM8904_ANALOGUE_RIGHT_INPUT_1 0x2F
+#define WM8904_ANALOGUE_OUT1_LEFT 0x39
+#define WM8904_ANALOGUE_OUT1_RIGHT 0x3A
+#define WM8904_ANALOGUE_OUT2_LEFT 0x3B
+#define WM8904_ANALOGUE_OUT2_RIGHT 0x3C
+#define WM8904_ANALOGUE_OUT12_ZC 0x3D
+#define WM8904_DC_SERVO_0 0x43
+#define WM8904_DC_SERVO_1 0x44
+#define WM8904_DC_SERVO_2 0x45
+#define WM8904_DC_SERVO_4 0x47
+#define WM8904_DC_SERVO_5 0x48
+#define WM8904_DC_SERVO_6 0x49
+#define WM8904_DC_SERVO_7 0x4A
+#define WM8904_DC_SERVO_8 0x4B
+#define WM8904_DC_SERVO_9 0x4C
+#define WM8904_DC_SERVO_READBACK_0 0x4D
+#define WM8904_ANALOGUE_HP_0 0x5A
+#define WM8904_ANALOGUE_LINEOUT_0 0x5E
+#define WM8904_CHARGE_PUMP_0 0x62
+#define WM8904_CLASS_W_0 0x68
+#define WM8904_WRITE_SEQUENCER_0 0x6C
+#define WM8904_WRITE_SEQUENCER_1 0x6D
+#define WM8904_WRITE_SEQUENCER_2 0x6E
+#define WM8904_WRITE_SEQUENCER_3 0x6F
+#define WM8904_WRITE_SEQUENCER_4 0x70
+#define WM8904_FLL_CONTROL_1 0x74
+#define WM8904_FLL_CONTROL_2 0x75
+#define WM8904_FLL_CONTROL_3 0x76
+#define WM8904_FLL_CONTROL_4 0x77
+#define WM8904_FLL_CONTROL_5 0x78
+#define WM8904_GPIO_CONTROL_1 0x79
+#define WM8904_GPIO_CONTROL_2 0x7A
+#define WM8904_GPIO_CONTROL_3 0x7B
+#define WM8904_GPIO_CONTROL_4 0x7C
+#define WM8904_DIGITAL_PULLS 0x7E
+#define WM8904_INTERRUPT_STATUS 0x7F
+#define WM8904_INTERRUPT_STATUS_MASK 0x80
+#define WM8904_INTERRUPT_POLARITY 0x81
+#define WM8904_INTERRUPT_DEBOUNCE 0x82
+#define WM8904_EQ1 0x86
+#define WM8904_EQ2 0x87
+#define WM8904_EQ3 0x88
+#define WM8904_EQ4 0x89
+#define WM8904_EQ5 0x8A
+#define WM8904_EQ6 0x8B
+#define WM8904_EQ7 0x8C
+#define WM8904_EQ8 0x8D
+#define WM8904_EQ9 0x8E
+#define WM8904_EQ10 0x8F
+#define WM8904_EQ11 0x90
+#define WM8904_EQ12 0x91
+#define WM8904_EQ13 0x92
+#define WM8904_EQ14 0x93
+#define WM8904_EQ15 0x94
+#define WM8904_EQ16 0x95
+#define WM8904_EQ17 0x96
+#define WM8904_EQ18 0x97
+#define WM8904_EQ19 0x98
+#define WM8904_EQ20 0x99
+#define WM8904_EQ21 0x9A
+#define WM8904_EQ22 0x9B
+#define WM8904_EQ23 0x9C
+#define WM8904_EQ24 0x9D
+#define WM8904_CONTROL_INTERFACE_TEST_1 0xA1
+#define WM8904_ANALOGUE_OUTPUT_BIAS_0 0xCC
+#define WM8904_FLL_NCO_TEST_0 0xF7
+#define WM8904_FLL_NCO_TEST_1 0xF8
+
+#define WM8904_REGISTER_COUNT 101
+#define WM8904_MAX_REGISTER 0xF8
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - SW Reset and ID
+ */
+#define WM8904_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */
+#define WM8904_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */
+#define WM8904_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */
+
+/*
+ * R1 (0x01) - Revision
+ */
+#define WM8904_REVISION_MASK 0x000F /* REVISION - [3:0] */
+#define WM8904_REVISION_SHIFT 0 /* REVISION - [3:0] */
+#define WM8904_REVISION_WIDTH 16 /* REVISION - [3:0] */
+
+/*
+ * R4 (0x04) - Bias Control 0
+ */
+#define WM8904_POBCTRL 0x0010 /* POBCTRL */
+#define WM8904_POBCTRL_MASK 0x0010 /* POBCTRL */
+#define WM8904_POBCTRL_SHIFT 4 /* POBCTRL */
+#define WM8904_POBCTRL_WIDTH 1 /* POBCTRL */
+#define WM8904_ISEL_MASK 0x000C /* ISEL - [3:2] */
+#define WM8904_ISEL_SHIFT 2 /* ISEL - [3:2] */
+#define WM8904_ISEL_WIDTH 2 /* ISEL - [3:2] */
+#define WM8904_STARTUP_BIAS_ENA 0x0002 /* STARTUP_BIAS_ENA */
+#define WM8904_STARTUP_BIAS_ENA_MASK 0x0002 /* STARTUP_BIAS_ENA */
+#define WM8904_STARTUP_BIAS_ENA_SHIFT 1 /* STARTUP_BIAS_ENA */
+#define WM8904_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */
+#define WM8904_BIAS_ENA 0x0001 /* BIAS_ENA */
+#define WM8904_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */
+#define WM8904_BIAS_ENA_SHIFT 0 /* BIAS_ENA */
+#define WM8904_BIAS_ENA_WIDTH 1 /* BIAS_ENA */
+
+/*
+ * R5 (0x05) - VMID Control 0
+ */
+#define WM8904_VMID_BUF_ENA 0x0040 /* VMID_BUF_ENA */
+#define WM8904_VMID_BUF_ENA_MASK 0x0040 /* VMID_BUF_ENA */
+#define WM8904_VMID_BUF_ENA_SHIFT 6 /* VMID_BUF_ENA */
+#define WM8904_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */
+#define WM8904_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */
+#define WM8904_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */
+#define WM8904_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */
+#define WM8904_VMID_ENA 0x0001 /* VMID_ENA */
+#define WM8904_VMID_ENA_MASK 0x0001 /* VMID_ENA */
+#define WM8904_VMID_ENA_SHIFT 0 /* VMID_ENA */
+#define WM8904_VMID_ENA_WIDTH 1 /* VMID_ENA */
+
+/*
+ * R8 (0x08) - Analogue DAC 0
+ */
+#define WM8904_DAC_BIAS_SEL_MASK 0x0018 /* DAC_BIAS_SEL - [4:3] */
+#define WM8904_DAC_BIAS_SEL_SHIFT 3 /* DAC_BIAS_SEL - [4:3] */
+#define WM8904_DAC_BIAS_SEL_WIDTH 2 /* DAC_BIAS_SEL - [4:3] */
+#define WM8904_DAC_VMID_BIAS_SEL_MASK 0x0006 /* DAC_VMID_BIAS_SEL - [2:1] */
+#define WM8904_DAC_VMID_BIAS_SEL_SHIFT 1 /* DAC_VMID_BIAS_SEL - [2:1] */
+#define WM8904_DAC_VMID_BIAS_SEL_WIDTH 2 /* DAC_VMID_BIAS_SEL - [2:1] */
+
+/*
+ * R9 (0x09) - mic Filter Control
+ */
+#define WM8904_MIC_DET_SET_THRESHOLD_MASK 0xF000 /* MIC_DET_SET_THRESHOLD - [15:12] */
+#define WM8904_MIC_DET_SET_THRESHOLD_SHIFT 12 /* MIC_DET_SET_THRESHOLD - [15:12] */
+#define WM8904_MIC_DET_SET_THRESHOLD_WIDTH 4 /* MIC_DET_SET_THRESHOLD - [15:12] */
+#define WM8904_MIC_DET_RESET_THRESHOLD_MASK 0x0F00 /* MIC_DET_RESET_THRESHOLD - [11:8] */
+#define WM8904_MIC_DET_RESET_THRESHOLD_SHIFT 8 /* MIC_DET_RESET_THRESHOLD - [11:8] */
+#define WM8904_MIC_DET_RESET_THRESHOLD_WIDTH 4 /* MIC_DET_RESET_THRESHOLD - [11:8] */
+#define WM8904_MIC_SHORT_SET_THRESHOLD_MASK 0x00F0 /* MIC_SHORT_SET_THRESHOLD - [7:4] */
+#define WM8904_MIC_SHORT_SET_THRESHOLD_SHIFT 4 /* MIC_SHORT_SET_THRESHOLD - [7:4] */
+#define WM8904_MIC_SHORT_SET_THRESHOLD_WIDTH 4 /* MIC_SHORT_SET_THRESHOLD - [7:4] */
+#define WM8904_MIC_SHORT_RESET_THRESHOLD_MASK 0x000F /* MIC_SHORT_RESET_THRESHOLD - [3:0] */
+#define WM8904_MIC_SHORT_RESET_THRESHOLD_SHIFT 0 /* MIC_SHORT_RESET_THRESHOLD - [3:0] */
+#define WM8904_MIC_SHORT_RESET_THRESHOLD_WIDTH 4 /* MIC_SHORT_RESET_THRESHOLD - [3:0] */
+
+/*
+ * R10 (0x0A) - Analogue ADC 0
+ */
+#define WM8904_ADC_OSR128 0x0001 /* ADC_OSR128 */
+#define WM8904_ADC_OSR128_MASK 0x0001 /* ADC_OSR128 */
+#define WM8904_ADC_OSR128_SHIFT 0 /* ADC_OSR128 */
+#define WM8904_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */
+
+/*
+ * R12 (0x0C) - Power Management 0
+ */
+#define WM8904_INL_ENA 0x0002 /* INL_ENA */
+#define WM8904_INL_ENA_MASK 0x0002 /* INL_ENA */
+#define WM8904_INL_ENA_SHIFT 1 /* INL_ENA */
+#define WM8904_INL_ENA_WIDTH 1 /* INL_ENA */
+#define WM8904_INR_ENA 0x0001 /* INR_ENA */
+#define WM8904_INR_ENA_MASK 0x0001 /* INR_ENA */
+#define WM8904_INR_ENA_SHIFT 0 /* INR_ENA */
+#define WM8904_INR_ENA_WIDTH 1 /* INR_ENA */
+
+/*
+ * R14 (0x0E) - Power Management 2
+ */
+#define WM8904_HPL_PGA_ENA 0x0002 /* HPL_PGA_ENA */
+#define WM8904_HPL_PGA_ENA_MASK 0x0002 /* HPL_PGA_ENA */
+#define WM8904_HPL_PGA_ENA_SHIFT 1 /* HPL_PGA_ENA */
+#define WM8904_HPL_PGA_ENA_WIDTH 1 /* HPL_PGA_ENA */
+#define WM8904_HPR_PGA_ENA 0x0001 /* HPR_PGA_ENA */
+#define WM8904_HPR_PGA_ENA_MASK 0x0001 /* HPR_PGA_ENA */
+#define WM8904_HPR_PGA_ENA_SHIFT 0 /* HPR_PGA_ENA */
+#define WM8904_HPR_PGA_ENA_WIDTH 1 /* HPR_PGA_ENA */
+
+/*
+ * R15 (0x0F) - Power Management 3
+ */
+#define WM8904_LINEOUTL_PGA_ENA 0x0002 /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTL_PGA_ENA_MASK 0x0002 /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTL_PGA_ENA_SHIFT 1 /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTL_PGA_ENA_WIDTH 1 /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA 0x0001 /* LINEOUTR_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA_MASK 0x0001 /* LINEOUTR_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA_SHIFT 0 /* LINEOUTR_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA_WIDTH 1 /* LINEOUTR_PGA_ENA */
+
+/*
+ * R18 (0x12) - Power Management 6
+ */
+#define WM8904_DACL_ENA 0x0008 /* DACL_ENA */
+#define WM8904_DACL_ENA_MASK 0x0008 /* DACL_ENA */
+#define WM8904_DACL_ENA_SHIFT 3 /* DACL_ENA */
+#define WM8904_DACL_ENA_WIDTH 1 /* DACL_ENA */
+#define WM8904_DACR_ENA 0x0004 /* DACR_ENA */
+#define WM8904_DACR_ENA_MASK 0x0004 /* DACR_ENA */
+#define WM8904_DACR_ENA_SHIFT 2 /* DACR_ENA */
+#define WM8904_DACR_ENA_WIDTH 1 /* DACR_ENA */
+#define WM8904_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8904_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */
+#define WM8904_ADCL_ENA_SHIFT 1 /* ADCL_ENA */
+#define WM8904_ADCL_ENA_WIDTH 1 /* ADCL_ENA */
+#define WM8904_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8904_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */
+#define WM8904_ADCR_ENA_SHIFT 0 /* ADCR_ENA */
+#define WM8904_ADCR_ENA_WIDTH 1 /* ADCR_ENA */
+
+/*
+ * R20 (0x14) - Clock Rates 0
+ */
+#define WM8904_TOCLK_RATE_DIV16 0x4000 /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_DIV16_MASK 0x4000 /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_DIV16_SHIFT 14 /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_DIV16_WIDTH 1 /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_X4 0x2000 /* TOCLK_RATE_X4 */
+#define WM8904_TOCLK_RATE_X4_MASK 0x2000 /* TOCLK_RATE_X4 */
+#define WM8904_TOCLK_RATE_X4_SHIFT 13 /* TOCLK_RATE_X4 */
+#define WM8904_TOCLK_RATE_X4_WIDTH 1 /* TOCLK_RATE_X4 */
+#define WM8904_SR_MODE 0x1000 /* SR_MODE */
+#define WM8904_SR_MODE_MASK 0x1000 /* SR_MODE */
+#define WM8904_SR_MODE_SHIFT 12 /* SR_MODE */
+#define WM8904_SR_MODE_WIDTH 1 /* SR_MODE */
+#define WM8904_MCLK_DIV 0x0001 /* MCLK_DIV */
+#define WM8904_MCLK_DIV_MASK 0x0001 /* MCLK_DIV */
+#define WM8904_MCLK_DIV_SHIFT 0 /* MCLK_DIV */
+#define WM8904_MCLK_DIV_WIDTH 1 /* MCLK_DIV */
+
+/*
+ * R21 (0x15) - Clock Rates 1
+ */
+#define WM8904_CLK_SYS_RATE_MASK 0x3C00 /* CLK_SYS_RATE - [13:10] */
+#define WM8904_CLK_SYS_RATE_SHIFT 10 /* CLK_SYS_RATE - [13:10] */
+#define WM8904_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [13:10] */
+#define WM8904_SAMPLE_RATE_MASK 0x0007 /* SAMPLE_RATE - [2:0] */
+#define WM8904_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [2:0] */
+#define WM8904_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [2:0] */
+
+/*
+ * R22 (0x16) - Clock Rates 2
+ */
+#define WM8904_MCLK_INV 0x8000 /* MCLK_INV */
+#define WM8904_MCLK_INV_MASK 0x8000 /* MCLK_INV */
+#define WM8904_MCLK_INV_SHIFT 15 /* MCLK_INV */
+#define WM8904_MCLK_INV_WIDTH 1 /* MCLK_INV */
+#define WM8904_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */
+#define WM8904_SYSCLK_SRC_MASK 0x4000 /* SYSCLK_SRC */
+#define WM8904_SYSCLK_SRC_SHIFT 14 /* SYSCLK_SRC */
+#define WM8904_SYSCLK_SRC_WIDTH 1 /* SYSCLK_SRC */
+#define WM8904_TOCLK_RATE 0x1000 /* TOCLK_RATE */
+#define WM8904_TOCLK_RATE_MASK 0x1000 /* TOCLK_RATE */
+#define WM8904_TOCLK_RATE_SHIFT 12 /* TOCLK_RATE */
+#define WM8904_TOCLK_RATE_WIDTH 1 /* TOCLK_RATE */
+#define WM8904_OPCLK_ENA 0x0008 /* OPCLK_ENA */
+#define WM8904_OPCLK_ENA_MASK 0x0008 /* OPCLK_ENA */
+#define WM8904_OPCLK_ENA_SHIFT 3 /* OPCLK_ENA */
+#define WM8904_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */
+#define WM8904_CLK_SYS_ENA 0x0004 /* CLK_SYS_ENA */
+#define WM8904_CLK_SYS_ENA_MASK 0x0004 /* CLK_SYS_ENA */
+#define WM8904_CLK_SYS_ENA_SHIFT 2 /* CLK_SYS_ENA */
+#define WM8904_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */
+#define WM8904_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */
+#define WM8904_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */
+#define WM8904_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */
+#define WM8904_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */
+#define WM8904_TOCLK_ENA 0x0001 /* TOCLK_ENA */
+#define WM8904_TOCLK_ENA_MASK 0x0001 /* TOCLK_ENA */
+#define WM8904_TOCLK_ENA_SHIFT 0 /* TOCLK_ENA */
+#define WM8904_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */
+
+/*
+ * R24 (0x18) - Audio Interface 0
+ */
+#define WM8904_DACL_DATINV 0x1000 /* DACL_DATINV */
+#define WM8904_DACL_DATINV_MASK 0x1000 /* DACL_DATINV */
+#define WM8904_DACL_DATINV_SHIFT 12 /* DACL_DATINV */
+#define WM8904_DACL_DATINV_WIDTH 1 /* DACL_DATINV */
+#define WM8904_DACR_DATINV 0x0800 /* DACR_DATINV */
+#define WM8904_DACR_DATINV_MASK 0x0800 /* DACR_DATINV */
+#define WM8904_DACR_DATINV_SHIFT 11 /* DACR_DATINV */
+#define WM8904_DACR_DATINV_WIDTH 1 /* DACR_DATINV */
+#define WM8904_DAC_BOOST_MASK 0x0600 /* DAC_BOOST - [10:9] */
+#define WM8904_DAC_BOOST_SHIFT 9 /* DAC_BOOST - [10:9] */
+#define WM8904_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [10:9] */
+#define WM8904_LOOPBACK 0x0100 /* LOOPBACK */
+#define WM8904_LOOPBACK_MASK 0x0100 /* LOOPBACK */
+#define WM8904_LOOPBACK_SHIFT 8 /* LOOPBACK */
+#define WM8904_LOOPBACK_WIDTH 1 /* LOOPBACK */
+#define WM8904_AIFADCL_SRC 0x0080 /* AIFADCL_SRC */
+#define WM8904_AIFADCL_SRC_MASK 0x0080 /* AIFADCL_SRC */
+#define WM8904_AIFADCL_SRC_SHIFT 7 /* AIFADCL_SRC */
+#define WM8904_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */
+#define WM8904_AIFADCR_SRC 0x0040 /* AIFADCR_SRC */
+#define WM8904_AIFADCR_SRC_MASK 0x0040 /* AIFADCR_SRC */
+#define WM8904_AIFADCR_SRC_SHIFT 6 /* AIFADCR_SRC */
+#define WM8904_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */
+#define WM8904_AIFDACL_SRC 0x0020 /* AIFDACL_SRC */
+#define WM8904_AIFDACL_SRC_MASK 0x0020 /* AIFDACL_SRC */
+#define WM8904_AIFDACL_SRC_SHIFT 5 /* AIFDACL_SRC */
+#define WM8904_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */
+#define WM8904_AIFDACR_SRC 0x0010 /* AIFDACR_SRC */
+#define WM8904_AIFDACR_SRC_MASK 0x0010 /* AIFDACR_SRC */
+#define WM8904_AIFDACR_SRC_SHIFT 4 /* AIFDACR_SRC */
+#define WM8904_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */
+#define WM8904_ADC_COMP 0x0008 /* ADC_COMP */
+#define WM8904_ADC_COMP_MASK 0x0008 /* ADC_COMP */
+#define WM8904_ADC_COMP_SHIFT 3 /* ADC_COMP */
+#define WM8904_ADC_COMP_WIDTH 1 /* ADC_COMP */
+#define WM8904_ADC_COMPMODE 0x0004 /* ADC_COMPMODE */
+#define WM8904_ADC_COMPMODE_MASK 0x0004 /* ADC_COMPMODE */
+#define WM8904_ADC_COMPMODE_SHIFT 2 /* ADC_COMPMODE */
+#define WM8904_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */
+#define WM8904_DAC_COMP 0x0002 /* DAC_COMP */
+#define WM8904_DAC_COMP_MASK 0x0002 /* DAC_COMP */
+#define WM8904_DAC_COMP_SHIFT 1 /* DAC_COMP */
+#define WM8904_DAC_COMP_WIDTH 1 /* DAC_COMP */
+#define WM8904_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */
+#define WM8904_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */
+#define WM8904_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */
+#define WM8904_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */
+
+/*
+ * R25 (0x19) - Audio Interface 1
+ */
+#define WM8904_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFADC_TDM 0x0800 /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_MASK 0x0800 /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_SHIFT 11 /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_CHAN 0x0400 /* AIFADC_TDM_CHAN */
+#define WM8904_AIFADC_TDM_CHAN_MASK 0x0400 /* AIFADC_TDM_CHAN */
+#define WM8904_AIFADC_TDM_CHAN_SHIFT 10 /* AIFADC_TDM_CHAN */
+#define WM8904_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */
+#define WM8904_AIF_TRIS 0x0100 /* AIF_TRIS */
+#define WM8904_AIF_TRIS_MASK 0x0100 /* AIF_TRIS */
+#define WM8904_AIF_TRIS_SHIFT 8 /* AIF_TRIS */
+#define WM8904_AIF_TRIS_WIDTH 1 /* AIF_TRIS */
+#define WM8904_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */
+#define WM8904_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */
+#define WM8904_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */
+#define WM8904_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */
+#define WM8904_BCLK_DIR 0x0040 /* BCLK_DIR */
+#define WM8904_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */
+#define WM8904_BCLK_DIR_SHIFT 6 /* BCLK_DIR */
+#define WM8904_BCLK_DIR_WIDTH 1 /* BCLK_DIR */
+#define WM8904_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */
+#define WM8904_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */
+#define WM8904_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */
+#define WM8904_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */
+#define WM8904_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */
+#define WM8904_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */
+#define WM8904_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */
+#define WM8904_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */
+#define WM8904_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */
+#define WM8904_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */
+
+/*
+ * R26 (0x1A) - Audio Interface 2
+ */
+#define WM8904_OPCLK_DIV_MASK 0x0F00 /* OPCLK_DIV - [11:8] */
+#define WM8904_OPCLK_DIV_SHIFT 8 /* OPCLK_DIV - [11:8] */
+#define WM8904_OPCLK_DIV_WIDTH 4 /* OPCLK_DIV - [11:8] */
+#define WM8904_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */
+#define WM8904_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */
+#define WM8904_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */
+
+/*
+ * R27 (0x1B) - Audio Interface 3
+ */
+#define WM8904_LRCLK_DIR 0x0800 /* LRCLK_DIR */
+#define WM8904_LRCLK_DIR_MASK 0x0800 /* LRCLK_DIR */
+#define WM8904_LRCLK_DIR_SHIFT 11 /* LRCLK_DIR */
+#define WM8904_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */
+#define WM8904_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */
+#define WM8904_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */
+#define WM8904_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */
+
+/*
+ * R30 (0x1E) - DAC Digital Volume Left
+ */
+#define WM8904_DAC_VU 0x0100 /* DAC_VU */
+#define WM8904_DAC_VU_MASK 0x0100 /* DAC_VU */
+#define WM8904_DAC_VU_SHIFT 8 /* DAC_VU */
+#define WM8904_DAC_VU_WIDTH 1 /* DAC_VU */
+#define WM8904_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8904_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */
+#define WM8904_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */
+
+/*
+ * R31 (0x1F) - DAC Digital Volume Right
+ */
+#define WM8904_DAC_VU 0x0100 /* DAC_VU */
+#define WM8904_DAC_VU_MASK 0x0100 /* DAC_VU */
+#define WM8904_DAC_VU_SHIFT 8 /* DAC_VU */
+#define WM8904_DAC_VU_WIDTH 1 /* DAC_VU */
+#define WM8904_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8904_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */
+#define WM8904_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */
+
+/*
+ * R32 (0x20) - DAC Digital 0
+ */
+#define WM8904_ADCL_DAC_SVOL_MASK 0x0F00 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8904_ADCL_DAC_SVOL_SHIFT 8 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8904_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8904_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8904_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8904_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8904_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */
+#define WM8904_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */
+#define WM8904_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */
+#define WM8904_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */
+#define WM8904_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */
+#define WM8904_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */
+
+/*
+ * R33 (0x21) - DAC Digital 1
+ */
+#define WM8904_DAC_MONO 0x1000 /* DAC_MONO */
+#define WM8904_DAC_MONO_MASK 0x1000 /* DAC_MONO */
+#define WM8904_DAC_MONO_SHIFT 12 /* DAC_MONO */
+#define WM8904_DAC_MONO_WIDTH 1 /* DAC_MONO */
+#define WM8904_DAC_SB_FILT 0x0800 /* DAC_SB_FILT */
+#define WM8904_DAC_SB_FILT_MASK 0x0800 /* DAC_SB_FILT */
+#define WM8904_DAC_SB_FILT_SHIFT 11 /* DAC_SB_FILT */
+#define WM8904_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */
+#define WM8904_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */
+#define WM8904_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */
+#define WM8904_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */
+#define WM8904_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
+#define WM8904_DAC_UNMUTE_RAMP 0x0200 /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_UNMUTE_RAMP_MASK 0x0200 /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_UNMUTE_RAMP_SHIFT 9 /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_UNMUTE_RAMP_WIDTH 1 /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_OSR128 0x0040 /* DAC_OSR128 */
+#define WM8904_DAC_OSR128_MASK 0x0040 /* DAC_OSR128 */
+#define WM8904_DAC_OSR128_SHIFT 6 /* DAC_OSR128 */
+#define WM8904_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */
+#define WM8904_DAC_MUTE 0x0008 /* DAC_MUTE */
+#define WM8904_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */
+#define WM8904_DAC_MUTE_SHIFT 3 /* DAC_MUTE */
+#define WM8904_DAC_MUTE_WIDTH 1 /* DAC_MUTE */
+#define WM8904_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
+#define WM8904_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
+#define WM8904_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
+
+/*
+ * R36 (0x24) - ADC Digital Volume Left
+ */
+#define WM8904_ADC_VU 0x0100 /* ADC_VU */
+#define WM8904_ADC_VU_MASK 0x0100 /* ADC_VU */
+#define WM8904_ADC_VU_SHIFT 8 /* ADC_VU */
+#define WM8904_ADC_VU_WIDTH 1 /* ADC_VU */
+#define WM8904_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8904_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */
+#define WM8904_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */
+
+/*
+ * R37 (0x25) - ADC Digital Volume Right
+ */
+#define WM8904_ADC_VU 0x0100 /* ADC_VU */
+#define WM8904_ADC_VU_MASK 0x0100 /* ADC_VU */
+#define WM8904_ADC_VU_SHIFT 8 /* ADC_VU */
+#define WM8904_ADC_VU_WIDTH 1 /* ADC_VU */
+#define WM8904_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8904_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */
+#define WM8904_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */
+
+/*
+ * R38 (0x26) - ADC Digital 0
+ */
+#define WM8904_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */
+#define WM8904_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */
+#define WM8904_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */
+#define WM8904_ADC_HPF 0x0010 /* ADC_HPF */
+#define WM8904_ADC_HPF_MASK 0x0010 /* ADC_HPF */
+#define WM8904_ADC_HPF_SHIFT 4 /* ADC_HPF */
+#define WM8904_ADC_HPF_WIDTH 1 /* ADC_HPF */
+#define WM8904_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8904_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */
+#define WM8904_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */
+#define WM8904_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */
+#define WM8904_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8904_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */
+#define WM8904_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */
+#define WM8904_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */
+
+/*
+ * R39 (0x27) - Digital Microphone 0
+ */
+#define WM8904_DMIC_ENA 0x1000 /* DMIC_ENA */
+#define WM8904_DMIC_ENA_MASK 0x1000 /* DMIC_ENA */
+#define WM8904_DMIC_ENA_SHIFT 12 /* DMIC_ENA */
+#define WM8904_DMIC_ENA_WIDTH 1 /* DMIC_ENA */
+#define WM8904_DMIC_SRC 0x0800 /* DMIC_SRC */
+#define WM8904_DMIC_SRC_MASK 0x0800 /* DMIC_SRC */
+#define WM8904_DMIC_SRC_SHIFT 11 /* DMIC_SRC */
+#define WM8904_DMIC_SRC_WIDTH 1 /* DMIC_SRC */
+
+/*
+ * R40 (0x28) - DRC 0
+ */
+#define WM8904_DRC_ENA 0x8000 /* DRC_ENA */
+#define WM8904_DRC_ENA_MASK 0x8000 /* DRC_ENA */
+#define WM8904_DRC_ENA_SHIFT 15 /* DRC_ENA */
+#define WM8904_DRC_ENA_WIDTH 1 /* DRC_ENA */
+#define WM8904_DRC_DAC_PATH 0x4000 /* DRC_DAC_PATH */
+#define WM8904_DRC_DAC_PATH_MASK 0x4000 /* DRC_DAC_PATH */
+#define WM8904_DRC_DAC_PATH_SHIFT 14 /* DRC_DAC_PATH */
+#define WM8904_DRC_DAC_PATH_WIDTH 1 /* DRC_DAC_PATH */
+#define WM8904_DRC_GS_HYST_LVL_MASK 0x1800 /* DRC_GS_HYST_LVL - [12:11] */
+#define WM8904_DRC_GS_HYST_LVL_SHIFT 11 /* DRC_GS_HYST_LVL - [12:11] */
+#define WM8904_DRC_GS_HYST_LVL_WIDTH 2 /* DRC_GS_HYST_LVL - [12:11] */
+#define WM8904_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8904_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8904_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8904_DRC_FF_DELAY 0x0020 /* DRC_FF_DELAY */
+#define WM8904_DRC_FF_DELAY_MASK 0x0020 /* DRC_FF_DELAY */
+#define WM8904_DRC_FF_DELAY_SHIFT 5 /* DRC_FF_DELAY */
+#define WM8904_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */
+#define WM8904_DRC_GS_ENA 0x0008 /* DRC_GS_ENA */
+#define WM8904_DRC_GS_ENA_MASK 0x0008 /* DRC_GS_ENA */
+#define WM8904_DRC_GS_ENA_SHIFT 3 /* DRC_GS_ENA */
+#define WM8904_DRC_GS_ENA_WIDTH 1 /* DRC_GS_ENA */
+#define WM8904_DRC_QR 0x0004 /* DRC_QR */
+#define WM8904_DRC_QR_MASK 0x0004 /* DRC_QR */
+#define WM8904_DRC_QR_SHIFT 2 /* DRC_QR */
+#define WM8904_DRC_QR_WIDTH 1 /* DRC_QR */
+#define WM8904_DRC_ANTICLIP 0x0002 /* DRC_ANTICLIP */
+#define WM8904_DRC_ANTICLIP_MASK 0x0002 /* DRC_ANTICLIP */
+#define WM8904_DRC_ANTICLIP_SHIFT 1 /* DRC_ANTICLIP */
+#define WM8904_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */
+#define WM8904_DRC_GS_HYST 0x0001 /* DRC_GS_HYST */
+#define WM8904_DRC_GS_HYST_MASK 0x0001 /* DRC_GS_HYST */
+#define WM8904_DRC_GS_HYST_SHIFT 0 /* DRC_GS_HYST */
+#define WM8904_DRC_GS_HYST_WIDTH 1 /* DRC_GS_HYST */
+
+/*
+ * R41 (0x29) - DRC 1
+ */
+#define WM8904_DRC_ATK_MASK 0xF000 /* DRC_ATK - [15:12] */
+#define WM8904_DRC_ATK_SHIFT 12 /* DRC_ATK - [15:12] */
+#define WM8904_DRC_ATK_WIDTH 4 /* DRC_ATK - [15:12] */
+#define WM8904_DRC_DCY_MASK 0x0F00 /* DRC_DCY - [11:8] */
+#define WM8904_DRC_DCY_SHIFT 8 /* DRC_DCY - [11:8] */
+#define WM8904_DRC_DCY_WIDTH 4 /* DRC_DCY - [11:8] */
+#define WM8904_DRC_QR_THR_MASK 0x00C0 /* DRC_QR_THR - [7:6] */
+#define WM8904_DRC_QR_THR_SHIFT 6 /* DRC_QR_THR - [7:6] */
+#define WM8904_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [7:6] */
+#define WM8904_DRC_QR_DCY_MASK 0x0030 /* DRC_QR_DCY - [5:4] */
+#define WM8904_DRC_QR_DCY_SHIFT 4 /* DRC_QR_DCY - [5:4] */
+#define WM8904_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [5:4] */
+#define WM8904_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */
+#define WM8904_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */
+#define WM8904_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */
+#define WM8904_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */
+#define WM8904_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */
+#define WM8904_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R42 (0x2A) - DRC 2
+ */
+#define WM8904_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */
+#define WM8904_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */
+#define WM8904_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */
+#define WM8904_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */
+#define WM8904_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */
+#define WM8904_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */
+
+/*
+ * R43 (0x2B) - DRC 3
+ */
+#define WM8904_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */
+#define WM8904_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */
+#define WM8904_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */
+#define WM8904_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */
+#define WM8904_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */
+#define WM8904_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */
+
+/*
+ * R44 (0x2C) - Analogue Left Input 0
+ */
+#define WM8904_LINMUTE 0x0080 /* LINMUTE */
+#define WM8904_LINMUTE_MASK 0x0080 /* LINMUTE */
+#define WM8904_LINMUTE_SHIFT 7 /* LINMUTE */
+#define WM8904_LINMUTE_WIDTH 1 /* LINMUTE */
+#define WM8904_LIN_VOL_MASK 0x001F /* LIN_VOL - [4:0] */
+#define WM8904_LIN_VOL_SHIFT 0 /* LIN_VOL - [4:0] */
+#define WM8904_LIN_VOL_WIDTH 5 /* LIN_VOL - [4:0] */
+
+/*
+ * R45 (0x2D) - Analogue Right Input 0
+ */
+#define WM8904_RINMUTE 0x0080 /* RINMUTE */
+#define WM8904_RINMUTE_MASK 0x0080 /* RINMUTE */
+#define WM8904_RINMUTE_SHIFT 7 /* RINMUTE */
+#define WM8904_RINMUTE_WIDTH 1 /* RINMUTE */
+#define WM8904_RIN_VOL_MASK 0x001F /* RIN_VOL - [4:0] */
+#define WM8904_RIN_VOL_SHIFT 0 /* RIN_VOL - [4:0] */
+#define WM8904_RIN_VOL_WIDTH 5 /* RIN_VOL - [4:0] */
+
+/*
+ * R46 (0x2E) - Analogue Left Input 1
+ */
+#define WM8904_INL_CM_ENA 0x0040 /* INL_CM_ENA */
+#define WM8904_INL_CM_ENA_MASK 0x0040 /* INL_CM_ENA */
+#define WM8904_INL_CM_ENA_SHIFT 6 /* INL_CM_ENA */
+#define WM8904_INL_CM_ENA_WIDTH 1 /* INL_CM_ENA */
+#define WM8904_L_IP_SEL_N_MASK 0x0030 /* L_IP_SEL_N - [5:4] */
+#define WM8904_L_IP_SEL_N_SHIFT 4 /* L_IP_SEL_N - [5:4] */
+#define WM8904_L_IP_SEL_N_WIDTH 2 /* L_IP_SEL_N - [5:4] */
+#define WM8904_L_IP_SEL_P_MASK 0x000C /* L_IP_SEL_P - [3:2] */
+#define WM8904_L_IP_SEL_P_SHIFT 2 /* L_IP_SEL_P - [3:2] */
+#define WM8904_L_IP_SEL_P_WIDTH 2 /* L_IP_SEL_P - [3:2] */
+#define WM8904_L_MODE_MASK 0x0003 /* L_MODE - [1:0] */
+#define WM8904_L_MODE_SHIFT 0 /* L_MODE - [1:0] */
+#define WM8904_L_MODE_WIDTH 2 /* L_MODE - [1:0] */
+
+/*
+ * R47 (0x2F) - Analogue Right Input 1
+ */
+#define WM8904_INR_CM_ENA 0x0040 /* INR_CM_ENA */
+#define WM8904_INR_CM_ENA_MASK 0x0040 /* INR_CM_ENA */
+#define WM8904_INR_CM_ENA_SHIFT 6 /* INR_CM_ENA */
+#define WM8904_INR_CM_ENA_WIDTH 1 /* INR_CM_ENA */
+#define WM8904_R_IP_SEL_N_MASK 0x0030 /* R_IP_SEL_N - [5:4] */
+#define WM8904_R_IP_SEL_N_SHIFT 4 /* R_IP_SEL_N - [5:4] */
+#define WM8904_R_IP_SEL_N_WIDTH 2 /* R_IP_SEL_N - [5:4] */
+#define WM8904_R_IP_SEL_P_MASK 0x000C /* R_IP_SEL_P - [3:2] */
+#define WM8904_R_IP_SEL_P_SHIFT 2 /* R_IP_SEL_P - [3:2] */
+#define WM8904_R_IP_SEL_P_WIDTH 2 /* R_IP_SEL_P - [3:2] */
+#define WM8904_R_MODE_MASK 0x0003 /* R_MODE - [1:0] */
+#define WM8904_R_MODE_SHIFT 0 /* R_MODE - [1:0] */
+#define WM8904_R_MODE_WIDTH 2 /* R_MODE - [1:0] */
+
+/*
+ * R57 (0x39) - Analogue OUT1 Left
+ */
+#define WM8904_HPOUTL_MUTE 0x0100 /* HPOUTL_MUTE */
+#define WM8904_HPOUTL_MUTE_MASK 0x0100 /* HPOUTL_MUTE */
+#define WM8904_HPOUTL_MUTE_SHIFT 8 /* HPOUTL_MUTE */
+#define WM8904_HPOUTL_MUTE_WIDTH 1 /* HPOUTL_MUTE */
+#define WM8904_HPOUT_VU 0x0080 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_MASK 0x0080 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_SHIFT 7 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_WIDTH 1 /* HPOUT_VU */
+#define WM8904_HPOUTLZC 0x0040 /* HPOUTLZC */
+#define WM8904_HPOUTLZC_MASK 0x0040 /* HPOUTLZC */
+#define WM8904_HPOUTLZC_SHIFT 6 /* HPOUTLZC */
+#define WM8904_HPOUTLZC_WIDTH 1 /* HPOUTLZC */
+#define WM8904_HPOUTL_VOL_MASK 0x003F /* HPOUTL_VOL - [5:0] */
+#define WM8904_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [5:0] */
+#define WM8904_HPOUTL_VOL_WIDTH 6 /* HPOUTL_VOL - [5:0] */
+
+/*
+ * R58 (0x3A) - Analogue OUT1 Right
+ */
+#define WM8904_HPOUTR_MUTE 0x0100 /* HPOUTR_MUTE */
+#define WM8904_HPOUTR_MUTE_MASK 0x0100 /* HPOUTR_MUTE */
+#define WM8904_HPOUTR_MUTE_SHIFT 8 /* HPOUTR_MUTE */
+#define WM8904_HPOUTR_MUTE_WIDTH 1 /* HPOUTR_MUTE */
+#define WM8904_HPOUT_VU 0x0080 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_MASK 0x0080 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_SHIFT 7 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_WIDTH 1 /* HPOUT_VU */
+#define WM8904_HPOUTRZC 0x0040 /* HPOUTRZC */
+#define WM8904_HPOUTRZC_MASK 0x0040 /* HPOUTRZC */
+#define WM8904_HPOUTRZC_SHIFT 6 /* HPOUTRZC */
+#define WM8904_HPOUTRZC_WIDTH 1 /* HPOUTRZC */
+#define WM8904_HPOUTR_VOL_MASK 0x003F /* HPOUTR_VOL - [5:0] */
+#define WM8904_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [5:0] */
+#define WM8904_HPOUTR_VOL_WIDTH 6 /* HPOUTR_VOL - [5:0] */
+
+/*
+ * R59 (0x3B) - Analogue OUT2 Left
+ */
+#define WM8904_LINEOUTL_MUTE 0x0100 /* LINEOUTL_MUTE */
+#define WM8904_LINEOUTL_MUTE_MASK 0x0100 /* LINEOUTL_MUTE */
+#define WM8904_LINEOUTL_MUTE_SHIFT 8 /* LINEOUTL_MUTE */
+#define WM8904_LINEOUTL_MUTE_WIDTH 1 /* LINEOUTL_MUTE */
+#define WM8904_LINEOUT_VU 0x0080 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_MASK 0x0080 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_SHIFT 7 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_WIDTH 1 /* LINEOUT_VU */
+#define WM8904_LINEOUTLZC 0x0040 /* LINEOUTLZC */
+#define WM8904_LINEOUTLZC_MASK 0x0040 /* LINEOUTLZC */
+#define WM8904_LINEOUTLZC_SHIFT 6 /* LINEOUTLZC */
+#define WM8904_LINEOUTLZC_WIDTH 1 /* LINEOUTLZC */
+#define WM8904_LINEOUTL_VOL_MASK 0x003F /* LINEOUTL_VOL - [5:0] */
+#define WM8904_LINEOUTL_VOL_SHIFT 0 /* LINEOUTL_VOL - [5:0] */
+#define WM8904_LINEOUTL_VOL_WIDTH 6 /* LINEOUTL_VOL - [5:0] */
+
+/*
+ * R60 (0x3C) - Analogue OUT2 Right
+ */
+#define WM8904_LINEOUTR_MUTE 0x0100 /* LINEOUTR_MUTE */
+#define WM8904_LINEOUTR_MUTE_MASK 0x0100 /* LINEOUTR_MUTE */
+#define WM8904_LINEOUTR_MUTE_SHIFT 8 /* LINEOUTR_MUTE */
+#define WM8904_LINEOUTR_MUTE_WIDTH 1 /* LINEOUTR_MUTE */
+#define WM8904_LINEOUT_VU 0x0080 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_MASK 0x0080 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_SHIFT 7 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_WIDTH 1 /* LINEOUT_VU */
+#define WM8904_LINEOUTRZC 0x0040 /* LINEOUTRZC */
+#define WM8904_LINEOUTRZC_MASK 0x0040 /* LINEOUTRZC */
+#define WM8904_LINEOUTRZC_SHIFT 6 /* LINEOUTRZC */
+#define WM8904_LINEOUTRZC_WIDTH 1 /* LINEOUTRZC */
+#define WM8904_LINEOUTR_VOL_MASK 0x003F /* LINEOUTR_VOL - [5:0] */
+#define WM8904_LINEOUTR_VOL_SHIFT 0 /* LINEOUTR_VOL - [5:0] */
+#define WM8904_LINEOUTR_VOL_WIDTH 6 /* LINEOUTR_VOL - [5:0] */
+
+/*
+ * R61 (0x3D) - Analogue OUT12 ZC
+ */
+#define WM8904_HPL_BYP_ENA 0x0008 /* HPL_BYP_ENA */
+#define WM8904_HPL_BYP_ENA_MASK 0x0008 /* HPL_BYP_ENA */
+#define WM8904_HPL_BYP_ENA_SHIFT 3 /* HPL_BYP_ENA */
+#define WM8904_HPL_BYP_ENA_WIDTH 1 /* HPL_BYP_ENA */
+#define WM8904_HPR_BYP_ENA 0x0004 /* HPR_BYP_ENA */
+#define WM8904_HPR_BYP_ENA_MASK 0x0004 /* HPR_BYP_ENA */
+#define WM8904_HPR_BYP_ENA_SHIFT 2 /* HPR_BYP_ENA */
+#define WM8904_HPR_BYP_ENA_WIDTH 1 /* HPR_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA 0x0002 /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA_MASK 0x0002 /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA_SHIFT 1 /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA_WIDTH 1 /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA 0x0001 /* LINEOUTR_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA_MASK 0x0001 /* LINEOUTR_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA_SHIFT 0 /* LINEOUTR_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA_WIDTH 1 /* LINEOUTR_BYP_ENA */
+
+/*
+ * R67 (0x43) - DC Servo 0
+ */
+#define WM8904_DCS_ENA_CHAN_3 0x0008 /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_3_MASK 0x0008 /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_3_SHIFT 3 /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_3_WIDTH 1 /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_2 0x0004 /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_2_MASK 0x0004 /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_2_SHIFT 2 /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_2_WIDTH 1 /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM8904_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM8904_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */
+#define WM8904_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */
+
+/*
+ * R68 (0x44) - DC Servo 1
+ */
+#define WM8904_DCS_TRIG_SINGLE_3 0x8000 /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_3_MASK 0x8000 /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_3_SHIFT 15 /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_3_WIDTH 1 /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_2 0x4000 /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_2_MASK 0x4000 /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_2_SHIFT 14 /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_2_WIDTH 1 /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SERIES_3 0x0800 /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_3_MASK 0x0800 /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_3_SHIFT 11 /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_3_WIDTH 1 /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_2 0x0400 /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_2_MASK 0x0400 /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_2_SHIFT 10 /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_2_WIDTH 1 /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_STARTUP_3 0x0080 /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_3_MASK 0x0080 /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_3_SHIFT 7 /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_3_WIDTH 1 /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_2 0x0040 /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_2_MASK 0x0040 /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_2_SHIFT 6 /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_2_WIDTH 1 /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_DAC_WR_3 0x0008 /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_3_MASK 0x0008 /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_3_SHIFT 3 /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_3_WIDTH 1 /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_2 0x0004 /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_2_MASK 0x0004 /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_2_SHIFT 2 /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_2_WIDTH 1 /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_1 0x0002 /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_1_MASK 0x0002 /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_1_SHIFT 1 /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_0 0x0001 /* DCS_TRIG_DAC_WR_0 */
+#define WM8904_DCS_TRIG_DAC_WR_0_MASK 0x0001 /* DCS_TRIG_DAC_WR_0 */
+#define WM8904_DCS_TRIG_DAC_WR_0_SHIFT 0 /* DCS_TRIG_DAC_WR_0 */
+#define WM8904_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */
+
+/*
+ * R69 (0x45) - DC Servo 2
+ */
+#define WM8904_DCS_TIMER_PERIOD_23_MASK 0x0F00 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8904_DCS_TIMER_PERIOD_23_SHIFT 8 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8904_DCS_TIMER_PERIOD_23_WIDTH 4 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8904_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8904_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8904_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */
+
+/*
+ * R71 (0x47) - DC Servo 4
+ */
+#define WM8904_DCS_SERIES_NO_23_MASK 0x007F /* DCS_SERIES_NO_23 - [6:0] */
+#define WM8904_DCS_SERIES_NO_23_SHIFT 0 /* DCS_SERIES_NO_23 - [6:0] */
+#define WM8904_DCS_SERIES_NO_23_WIDTH 7 /* DCS_SERIES_NO_23 - [6:0] */
+
+/*
+ * R72 (0x48) - DC Servo 5
+ */
+#define WM8904_DCS_SERIES_NO_01_MASK 0x007F /* DCS_SERIES_NO_01 - [6:0] */
+#define WM8904_DCS_SERIES_NO_01_SHIFT 0 /* DCS_SERIES_NO_01 - [6:0] */
+#define WM8904_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [6:0] */
+
+/*
+ * R73 (0x49) - DC Servo 6
+ */
+#define WM8904_DCS_DAC_WR_VAL_3_MASK 0x00FF /* DCS_DAC_WR_VAL_3 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_3_SHIFT 0 /* DCS_DAC_WR_VAL_3 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_3_WIDTH 8 /* DCS_DAC_WR_VAL_3 - [7:0] */
+
+/*
+ * R74 (0x4A) - DC Servo 7
+ */
+#define WM8904_DCS_DAC_WR_VAL_2_MASK 0x00FF /* DCS_DAC_WR_VAL_2 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_2_SHIFT 0 /* DCS_DAC_WR_VAL_2 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_2_WIDTH 8 /* DCS_DAC_WR_VAL_2 - [7:0] */
+
+/*
+ * R75 (0x4B) - DC Servo 8
+ */
+#define WM8904_DCS_DAC_WR_VAL_1_MASK 0x00FF /* DCS_DAC_WR_VAL_1 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_1_SHIFT 0 /* DCS_DAC_WR_VAL_1 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [7:0] */
+
+/*
+ * R76 (0x4C) - DC Servo 9
+ */
+#define WM8904_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */
+
+/*
+ * R77 (0x4D) - DC Servo Readback 0
+ */
+#define WM8904_DCS_CAL_COMPLETE_MASK 0x0F00 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8904_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8904_DCS_CAL_COMPLETE_WIDTH 4 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8904_DCS_DAC_WR_COMPLETE_MASK 0x00F0 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8904_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8904_DCS_DAC_WR_COMPLETE_WIDTH 4 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8904_DCS_STARTUP_COMPLETE_MASK 0x000F /* DCS_STARTUP_COMPLETE - [3:0] */
+#define WM8904_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [3:0] */
+#define WM8904_DCS_STARTUP_COMPLETE_WIDTH 4 /* DCS_STARTUP_COMPLETE - [3:0] */
+
+/*
+ * R90 (0x5A) - Analogue HP 0
+ */
+#define WM8904_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */
+#define WM8904_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */
+#define WM8904_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */
+#define WM8904_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */
+#define WM8904_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA 0x0010 /* HPL_ENA */
+#define WM8904_HPL_ENA_MASK 0x0010 /* HPL_ENA */
+#define WM8904_HPL_ENA_SHIFT 4 /* HPL_ENA */
+#define WM8904_HPL_ENA_WIDTH 1 /* HPL_ENA */
+#define WM8904_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */
+#define WM8904_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */
+#define WM8904_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */
+#define WM8904_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */
+#define WM8904_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA 0x0001 /* HPR_ENA */
+#define WM8904_HPR_ENA_MASK 0x0001 /* HPR_ENA */
+#define WM8904_HPR_ENA_SHIFT 0 /* HPR_ENA */
+#define WM8904_HPR_ENA_WIDTH 1 /* HPR_ENA */
+
+/*
+ * R94 (0x5E) - Analogue Lineout 0
+ */
+#define WM8904_LINEOUTL_RMV_SHORT 0x0080 /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_RMV_SHORT_MASK 0x0080 /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_RMV_SHORT_SHIFT 7 /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_RMV_SHORT_WIDTH 1 /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_ENA_OUTP 0x0040 /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_OUTP_MASK 0x0040 /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_OUTP_SHIFT 6 /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_OUTP_WIDTH 1 /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_DLY 0x0020 /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA_DLY_MASK 0x0020 /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA_DLY_SHIFT 5 /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA_DLY_WIDTH 1 /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA 0x0010 /* LINEOUTL_ENA */
+#define WM8904_LINEOUTL_ENA_MASK 0x0010 /* LINEOUTL_ENA */
+#define WM8904_LINEOUTL_ENA_SHIFT 4 /* LINEOUTL_ENA */
+#define WM8904_LINEOUTL_ENA_WIDTH 1 /* LINEOUTL_ENA */
+#define WM8904_LINEOUTR_RMV_SHORT 0x0008 /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_RMV_SHORT_MASK 0x0008 /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_RMV_SHORT_SHIFT 3 /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_RMV_SHORT_WIDTH 1 /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_ENA_OUTP 0x0004 /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_OUTP_MASK 0x0004 /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_OUTP_SHIFT 2 /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_OUTP_WIDTH 1 /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_DLY 0x0002 /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA_DLY_MASK 0x0002 /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA_DLY_SHIFT 1 /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA_DLY_WIDTH 1 /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA 0x0001 /* LINEOUTR_ENA */
+#define WM8904_LINEOUTR_ENA_MASK 0x0001 /* LINEOUTR_ENA */
+#define WM8904_LINEOUTR_ENA_SHIFT 0 /* LINEOUTR_ENA */
+#define WM8904_LINEOUTR_ENA_WIDTH 1 /* LINEOUTR_ENA */
+
+/*
+ * R98 (0x62) - Charge Pump 0
+ */
+#define WM8904_CP_ENA 0x0001 /* CP_ENA */
+#define WM8904_CP_ENA_MASK 0x0001 /* CP_ENA */
+#define WM8904_CP_ENA_SHIFT 0 /* CP_ENA */
+#define WM8904_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R104 (0x68) - Class W 0
+ */
+#define WM8904_CP_DYN_PWR 0x0001 /* CP_DYN_PWR */
+#define WM8904_CP_DYN_PWR_MASK 0x0001 /* CP_DYN_PWR */
+#define WM8904_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR */
+#define WM8904_CP_DYN_PWR_WIDTH 1 /* CP_DYN_PWR */
+
+/*
+ * R108 (0x6C) - Write Sequencer 0
+ */
+#define WM8904_WSEQ_ENA 0x0100 /* WSEQ_ENA */
+#define WM8904_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */
+#define WM8904_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */
+#define WM8904_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM8904_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8904_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8904_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R109 (0x6D) - Write Sequencer 1
+ */
+#define WM8904_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8904_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8904_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8904_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */
+#define WM8904_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */
+#define WM8904_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */
+#define WM8904_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */
+#define WM8904_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */
+#define WM8904_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R110 (0x6E) - Write Sequencer 2
+ */
+#define WM8904_WSEQ_EOS 0x4000 /* WSEQ_EOS */
+#define WM8904_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */
+#define WM8904_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */
+#define WM8904_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */
+#define WM8904_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */
+#define WM8904_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */
+#define WM8904_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */
+#define WM8904_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */
+#define WM8904_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */
+#define WM8904_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */
+
+/*
+ * R111 (0x6F) - Write Sequencer 3
+ */
+#define WM8904_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
+#define WM8904_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
+#define WM8904_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
+#define WM8904_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM8904_WSEQ_START 0x0100 /* WSEQ_START */
+#define WM8904_WSEQ_START_MASK 0x0100 /* WSEQ_START */
+#define WM8904_WSEQ_START_SHIFT 8 /* WSEQ_START */
+#define WM8904_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM8904_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */
+#define WM8904_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */
+#define WM8904_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R112 (0x70) - Write Sequencer 4
+ */
+#define WM8904_WSEQ_CURRENT_INDEX_MASK 0x03F0 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8904_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8904_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8904_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
+#define WM8904_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
+#define WM8904_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
+#define WM8904_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+
+/*
+ * R116 (0x74) - FLL Control 1
+ */
+#define WM8904_FLL_FRACN_ENA 0x0004 /* FLL_FRACN_ENA */
+#define WM8904_FLL_FRACN_ENA_MASK 0x0004 /* FLL_FRACN_ENA */
+#define WM8904_FLL_FRACN_ENA_SHIFT 2 /* FLL_FRACN_ENA */
+#define WM8904_FLL_FRACN_ENA_WIDTH 1 /* FLL_FRACN_ENA */
+#define WM8904_FLL_OSC_ENA 0x0002 /* FLL_OSC_ENA */
+#define WM8904_FLL_OSC_ENA_MASK 0x0002 /* FLL_OSC_ENA */
+#define WM8904_FLL_OSC_ENA_SHIFT 1 /* FLL_OSC_ENA */
+#define WM8904_FLL_OSC_ENA_WIDTH 1 /* FLL_OSC_ENA */
+#define WM8904_FLL_ENA 0x0001 /* FLL_ENA */
+#define WM8904_FLL_ENA_MASK 0x0001 /* FLL_ENA */
+#define WM8904_FLL_ENA_SHIFT 0 /* FLL_ENA */
+#define WM8904_FLL_ENA_WIDTH 1 /* FLL_ENA */
+
+/*
+ * R117 (0x75) - FLL Control 2
+ */
+#define WM8904_FLL_OUTDIV_MASK 0x3F00 /* FLL_OUTDIV - [13:8] */
+#define WM8904_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [13:8] */
+#define WM8904_FLL_OUTDIV_WIDTH 6 /* FLL_OUTDIV - [13:8] */
+#define WM8904_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */
+#define WM8904_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */
+#define WM8904_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */
+#define WM8904_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */
+#define WM8904_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */
+#define WM8904_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */
+
+/*
+ * R118 (0x76) - FLL Control 3
+ */
+#define WM8904_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */
+#define WM8904_FLL_K_SHIFT 0 /* FLL_K - [15:0] */
+#define WM8904_FLL_K_WIDTH 16 /* FLL_K - [15:0] */
+
+/*
+ * R119 (0x77) - FLL Control 4
+ */
+#define WM8904_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */
+#define WM8904_FLL_N_SHIFT 5 /* FLL_N - [14:5] */
+#define WM8904_FLL_N_WIDTH 10 /* FLL_N - [14:5] */
+#define WM8904_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */
+#define WM8904_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */
+#define WM8904_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */
+
+/*
+ * R120 (0x78) - FLL Control 5
+ */
+#define WM8904_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8904_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8904_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8904_FLL_CLK_REF_SRC_MASK 0x0003 /* FLL_CLK_REF_SRC - [1:0] */
+#define WM8904_FLL_CLK_REF_SRC_SHIFT 0 /* FLL_CLK_REF_SRC - [1:0] */
+#define WM8904_FLL_CLK_REF_SRC_WIDTH 2 /* FLL_CLK_REF_SRC - [1:0] */
+
+/*
+ * R126 (0x7E) - Digital Pulls
+ */
+#define WM8904_MCLK_PU 0x0080 /* MCLK_PU */
+#define WM8904_MCLK_PU_MASK 0x0080 /* MCLK_PU */
+#define WM8904_MCLK_PU_SHIFT 7 /* MCLK_PU */
+#define WM8904_MCLK_PU_WIDTH 1 /* MCLK_PU */
+#define WM8904_MCLK_PD 0x0040 /* MCLK_PD */
+#define WM8904_MCLK_PD_MASK 0x0040 /* MCLK_PD */
+#define WM8904_MCLK_PD_SHIFT 6 /* MCLK_PD */
+#define WM8904_MCLK_PD_WIDTH 1 /* MCLK_PD */
+#define WM8904_DACDAT_PU 0x0020 /* DACDAT_PU */
+#define WM8904_DACDAT_PU_MASK 0x0020 /* DACDAT_PU */
+#define WM8904_DACDAT_PU_SHIFT 5 /* DACDAT_PU */
+#define WM8904_DACDAT_PU_WIDTH 1 /* DACDAT_PU */
+#define WM8904_DACDAT_PD 0x0010 /* DACDAT_PD */
+#define WM8904_DACDAT_PD_MASK 0x0010 /* DACDAT_PD */
+#define WM8904_DACDAT_PD_SHIFT 4 /* DACDAT_PD */
+#define WM8904_DACDAT_PD_WIDTH 1 /* DACDAT_PD */
+#define WM8904_LRCLK_PU 0x0008 /* LRCLK_PU */
+#define WM8904_LRCLK_PU_MASK 0x0008 /* LRCLK_PU */
+#define WM8904_LRCLK_PU_SHIFT 3 /* LRCLK_PU */
+#define WM8904_LRCLK_PU_WIDTH 1 /* LRCLK_PU */
+#define WM8904_LRCLK_PD 0x0004 /* LRCLK_PD */
+#define WM8904_LRCLK_PD_MASK 0x0004 /* LRCLK_PD */
+#define WM8904_LRCLK_PD_SHIFT 2 /* LRCLK_PD */
+#define WM8904_LRCLK_PD_WIDTH 1 /* LRCLK_PD */
+#define WM8904_BCLK_PU 0x0002 /* BCLK_PU */
+#define WM8904_BCLK_PU_MASK 0x0002 /* BCLK_PU */
+#define WM8904_BCLK_PU_SHIFT 1 /* BCLK_PU */
+#define WM8904_BCLK_PU_WIDTH 1 /* BCLK_PU */
+#define WM8904_BCLK_PD 0x0001 /* BCLK_PD */
+#define WM8904_BCLK_PD_MASK 0x0001 /* BCLK_PD */
+#define WM8904_BCLK_PD_SHIFT 0 /* BCLK_PD */
+#define WM8904_BCLK_PD_WIDTH 1 /* BCLK_PD */
+
+/*
+ * R127 (0x7F) - Interrupt Status
+ */
+#define WM8904_IRQ 0x0400 /* IRQ */
+#define WM8904_IRQ_MASK 0x0400 /* IRQ */
+#define WM8904_IRQ_SHIFT 10 /* IRQ */
+#define WM8904_IRQ_WIDTH 1 /* IRQ */
+#define WM8904_GPIO_BCLK_EINT 0x0200 /* GPIO_BCLK_EINT */
+#define WM8904_GPIO_BCLK_EINT_MASK 0x0200 /* GPIO_BCLK_EINT */
+#define WM8904_GPIO_BCLK_EINT_SHIFT 9 /* GPIO_BCLK_EINT */
+#define WM8904_GPIO_BCLK_EINT_WIDTH 1 /* GPIO_BCLK_EINT */
+#define WM8904_WSEQ_EINT 0x0100 /* WSEQ_EINT */
+#define WM8904_WSEQ_EINT_MASK 0x0100 /* WSEQ_EINT */
+#define WM8904_WSEQ_EINT_SHIFT 8 /* WSEQ_EINT */
+#define WM8904_WSEQ_EINT_WIDTH 1 /* WSEQ_EINT */
+#define WM8904_GPIO3_EINT 0x0080 /* GPIO3_EINT */
+#define WM8904_GPIO3_EINT_MASK 0x0080 /* GPIO3_EINT */
+#define WM8904_GPIO3_EINT_SHIFT 7 /* GPIO3_EINT */
+#define WM8904_GPIO3_EINT_WIDTH 1 /* GPIO3_EINT */
+#define WM8904_GPIO2_EINT 0x0040 /* GPIO2_EINT */
+#define WM8904_GPIO2_EINT_MASK 0x0040 /* GPIO2_EINT */
+#define WM8904_GPIO2_EINT_SHIFT 6 /* GPIO2_EINT */
+#define WM8904_GPIO2_EINT_WIDTH 1 /* GPIO2_EINT */
+#define WM8904_GPIO1_EINT 0x0020 /* GPIO1_EINT */
+#define WM8904_GPIO1_EINT_MASK 0x0020 /* GPIO1_EINT */
+#define WM8904_GPIO1_EINT_SHIFT 5 /* GPIO1_EINT */
+#define WM8904_GPIO1_EINT_WIDTH 1 /* GPIO1_EINT */
+#define WM8904_GPI8_EINT 0x0010 /* GPI8_EINT */
+#define WM8904_GPI8_EINT_MASK 0x0010 /* GPI8_EINT */
+#define WM8904_GPI8_EINT_SHIFT 4 /* GPI8_EINT */
+#define WM8904_GPI8_EINT_WIDTH 1 /* GPI8_EINT */
+#define WM8904_GPI7_EINT 0x0008 /* GPI7_EINT */
+#define WM8904_GPI7_EINT_MASK 0x0008 /* GPI7_EINT */
+#define WM8904_GPI7_EINT_SHIFT 3 /* GPI7_EINT */
+#define WM8904_GPI7_EINT_WIDTH 1 /* GPI7_EINT */
+#define WM8904_FLL_LOCK_EINT 0x0004 /* FLL_LOCK_EINT */
+#define WM8904_FLL_LOCK_EINT_MASK 0x0004 /* FLL_LOCK_EINT */
+#define WM8904_FLL_LOCK_EINT_SHIFT 2 /* FLL_LOCK_EINT */
+#define WM8904_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */
+#define WM8904_MIC_SHRT_EINT 0x0002 /* MIC_SHRT_EINT */
+#define WM8904_MIC_SHRT_EINT_MASK 0x0002 /* MIC_SHRT_EINT */
+#define WM8904_MIC_SHRT_EINT_SHIFT 1 /* MIC_SHRT_EINT */
+#define WM8904_MIC_SHRT_EINT_WIDTH 1 /* MIC_SHRT_EINT */
+#define WM8904_MIC_DET_EINT 0x0001 /* MIC_DET_EINT */
+#define WM8904_MIC_DET_EINT_MASK 0x0001 /* MIC_DET_EINT */
+#define WM8904_MIC_DET_EINT_SHIFT 0 /* MIC_DET_EINT */
+#define WM8904_MIC_DET_EINT_WIDTH 1 /* MIC_DET_EINT */
+
+/*
+ * R128 (0x80) - Interrupt Status Mask
+ */
+#define WM8904_IM_GPIO_BCLK_EINT 0x0200 /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_GPIO_BCLK_EINT_MASK 0x0200 /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_GPIO_BCLK_EINT_SHIFT 9 /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_GPIO_BCLK_EINT_WIDTH 1 /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_WSEQ_EINT 0x0100 /* IM_WSEQ_EINT */
+#define WM8904_IM_WSEQ_EINT_MASK 0x0100 /* IM_WSEQ_EINT */
+#define WM8904_IM_WSEQ_EINT_SHIFT 8 /* IM_WSEQ_EINT */
+#define WM8904_IM_WSEQ_EINT_WIDTH 1 /* IM_WSEQ_EINT */
+#define WM8904_IM_GPIO3_EINT 0x0080 /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO3_EINT_MASK 0x0080 /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO3_EINT_SHIFT 7 /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO3_EINT_WIDTH 1 /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO2_EINT 0x0040 /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO2_EINT_MASK 0x0040 /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO2_EINT_SHIFT 6 /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO2_EINT_WIDTH 1 /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO1_EINT 0x0020 /* IM_GPIO1_EINT */
+#define WM8904_IM_GPIO1_EINT_MASK 0x0020 /* IM_GPIO1_EINT */
+#define WM8904_IM_GPIO1_EINT_SHIFT 5 /* IM_GPIO1_EINT */
+#define WM8904_IM_GPIO1_EINT_WIDTH 1 /* IM_GPIO1_EINT */
+#define WM8904_IM_GPI8_EINT 0x0010 /* IM_GPI8_EINT */
+#define WM8904_IM_GPI8_EINT_MASK 0x0010 /* IM_GPI8_EINT */
+#define WM8904_IM_GPI8_EINT_SHIFT 4 /* IM_GPI8_EINT */
+#define WM8904_IM_GPI8_EINT_WIDTH 1 /* IM_GPI8_EINT */
+#define WM8904_IM_GPI7_EINT 0x0008 /* IM_GPI7_EINT */
+#define WM8904_IM_GPI7_EINT_MASK 0x0008 /* IM_GPI7_EINT */
+#define WM8904_IM_GPI7_EINT_SHIFT 3 /* IM_GPI7_EINT */
+#define WM8904_IM_GPI7_EINT_WIDTH 1 /* IM_GPI7_EINT */
+#define WM8904_IM_FLL_LOCK_EINT 0x0004 /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_FLL_LOCK_EINT_MASK 0x0004 /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_FLL_LOCK_EINT_SHIFT 2 /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_MIC_SHRT_EINT 0x0002 /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_SHRT_EINT_MASK 0x0002 /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_SHRT_EINT_SHIFT 1 /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_SHRT_EINT_WIDTH 1 /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_DET_EINT 0x0001 /* IM_MIC_DET_EINT */
+#define WM8904_IM_MIC_DET_EINT_MASK 0x0001 /* IM_MIC_DET_EINT */
+#define WM8904_IM_MIC_DET_EINT_SHIFT 0 /* IM_MIC_DET_EINT */
+#define WM8904_IM_MIC_DET_EINT_WIDTH 1 /* IM_MIC_DET_EINT */
+
+/*
+ * R129 (0x81) - Interrupt Polarity
+ */
+#define WM8904_GPIO_BCLK_EINT_POL 0x0200 /* GPIO_BCLK_EINT_POL */
+#define WM8904_GPIO_BCLK_EINT_POL_MASK 0x0200 /* GPIO_BCLK_EINT_POL */
+#define WM8904_GPIO_BCLK_EINT_POL_SHIFT 9 /* GPIO_BCLK_EINT_POL */
+#define WM8904_GPIO_BCLK_EINT_POL_WIDTH 1 /* GPIO_BCLK_EINT_POL */
+#define WM8904_WSEQ_EINT_POL 0x0100 /* WSEQ_EINT_POL */
+#define WM8904_WSEQ_EINT_POL_MASK 0x0100 /* WSEQ_EINT_POL */
+#define WM8904_WSEQ_EINT_POL_SHIFT 8 /* WSEQ_EINT_POL */
+#define WM8904_WSEQ_EINT_POL_WIDTH 1 /* WSEQ_EINT_POL */
+#define WM8904_GPIO3_EINT_POL 0x0080 /* GPIO3_EINT_POL */
+#define WM8904_GPIO3_EINT_POL_MASK 0x0080 /* GPIO3_EINT_POL */
+#define WM8904_GPIO3_EINT_POL_SHIFT 7 /* GPIO3_EINT_POL */
+#define WM8904_GPIO3_EINT_POL_WIDTH 1 /* GPIO3_EINT_POL */
+#define WM8904_GPIO2_EINT_POL 0x0040 /* GPIO2_EINT_POL */
+#define WM8904_GPIO2_EINT_POL_MASK 0x0040 /* GPIO2_EINT_POL */
+#define WM8904_GPIO2_EINT_POL_SHIFT 6 /* GPIO2_EINT_POL */
+#define WM8904_GPIO2_EINT_POL_WIDTH 1 /* GPIO2_EINT_POL */
+#define WM8904_GPIO1_EINT_POL 0x0020 /* GPIO1_EINT_POL */
+#define WM8904_GPIO1_EINT_POL_MASK 0x0020 /* GPIO1_EINT_POL */
+#define WM8904_GPIO1_EINT_POL_SHIFT 5 /* GPIO1_EINT_POL */
+#define WM8904_GPIO1_EINT_POL_WIDTH 1 /* GPIO1_EINT_POL */
+#define WM8904_GPI8_EINT_POL 0x0010 /* GPI8_EINT_POL */
+#define WM8904_GPI8_EINT_POL_MASK 0x0010 /* GPI8_EINT_POL */
+#define WM8904_GPI8_EINT_POL_SHIFT 4 /* GPI8_EINT_POL */
+#define WM8904_GPI8_EINT_POL_WIDTH 1 /* GPI8_EINT_POL */
+#define WM8904_GPI7_EINT_POL 0x0008 /* GPI7_EINT_POL */
+#define WM8904_GPI7_EINT_POL_MASK 0x0008 /* GPI7_EINT_POL */
+#define WM8904_GPI7_EINT_POL_SHIFT 3 /* GPI7_EINT_POL */
+#define WM8904_GPI7_EINT_POL_WIDTH 1 /* GPI7_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL 0x0004 /* FLL_LOCK_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL_MASK 0x0004 /* FLL_LOCK_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL_SHIFT 2 /* FLL_LOCK_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL_WIDTH 1 /* FLL_LOCK_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL 0x0002 /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL_MASK 0x0002 /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL_SHIFT 1 /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL_WIDTH 1 /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL 0x0001 /* MIC_DET_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL_MASK 0x0001 /* MIC_DET_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL_SHIFT 0 /* MIC_DET_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL_WIDTH 1 /* MIC_DET_EINT_POL */
+
+/*
+ * R130 (0x82) - Interrupt Debounce
+ */
+#define WM8904_GPIO_BCLK_EINT_DB 0x0200 /* GPIO_BCLK_EINT_DB */
+#define WM8904_GPIO_BCLK_EINT_DB_MASK 0x0200 /* GPIO_BCLK_EINT_DB */
+#define WM8904_GPIO_BCLK_EINT_DB_SHIFT 9 /* GPIO_BCLK_EINT_DB */
+#define WM8904_GPIO_BCLK_EINT_DB_WIDTH 1 /* GPIO_BCLK_EINT_DB */
+#define WM8904_WSEQ_EINT_DB 0x0100 /* WSEQ_EINT_DB */
+#define WM8904_WSEQ_EINT_DB_MASK 0x0100 /* WSEQ_EINT_DB */
+#define WM8904_WSEQ_EINT_DB_SHIFT 8 /* WSEQ_EINT_DB */
+#define WM8904_WSEQ_EINT_DB_WIDTH 1 /* WSEQ_EINT_DB */
+#define WM8904_GPIO3_EINT_DB 0x0080 /* GPIO3_EINT_DB */
+#define WM8904_GPIO3_EINT_DB_MASK 0x0080 /* GPIO3_EINT_DB */
+#define WM8904_GPIO3_EINT_DB_SHIFT 7 /* GPIO3_EINT_DB */
+#define WM8904_GPIO3_EINT_DB_WIDTH 1 /* GPIO3_EINT_DB */
+#define WM8904_GPIO2_EINT_DB 0x0040 /* GPIO2_EINT_DB */
+#define WM8904_GPIO2_EINT_DB_MASK 0x0040 /* GPIO2_EINT_DB */
+#define WM8904_GPIO2_EINT_DB_SHIFT 6 /* GPIO2_EINT_DB */
+#define WM8904_GPIO2_EINT_DB_WIDTH 1 /* GPIO2_EINT_DB */
+#define WM8904_GPIO1_EINT_DB 0x0020 /* GPIO1_EINT_DB */
+#define WM8904_GPIO1_EINT_DB_MASK 0x0020 /* GPIO1_EINT_DB */
+#define WM8904_GPIO1_EINT_DB_SHIFT 5 /* GPIO1_EINT_DB */
+#define WM8904_GPIO1_EINT_DB_WIDTH 1 /* GPIO1_EINT_DB */
+#define WM8904_GPI8_EINT_DB 0x0010 /* GPI8_EINT_DB */
+#define WM8904_GPI8_EINT_DB_MASK 0x0010 /* GPI8_EINT_DB */
+#define WM8904_GPI8_EINT_DB_SHIFT 4 /* GPI8_EINT_DB */
+#define WM8904_GPI8_EINT_DB_WIDTH 1 /* GPI8_EINT_DB */
+#define WM8904_GPI7_EINT_DB 0x0008 /* GPI7_EINT_DB */
+#define WM8904_GPI7_EINT_DB_MASK 0x0008 /* GPI7_EINT_DB */
+#define WM8904_GPI7_EINT_DB_SHIFT 3 /* GPI7_EINT_DB */
+#define WM8904_GPI7_EINT_DB_WIDTH 1 /* GPI7_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB 0x0004 /* FLL_LOCK_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB_MASK 0x0004 /* FLL_LOCK_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB_SHIFT 2 /* FLL_LOCK_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB_WIDTH 1 /* FLL_LOCK_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB 0x0002 /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB_MASK 0x0002 /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB_SHIFT 1 /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB_WIDTH 1 /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB 0x0001 /* MIC_DET_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB_MASK 0x0001 /* MIC_DET_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB_SHIFT 0 /* MIC_DET_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB_WIDTH 1 /* MIC_DET_EINT_DB */
+
+/*
+ * R134 (0x86) - EQ1
+ */
+#define WM8904_EQ_ENA 0x0001 /* EQ_ENA */
+#define WM8904_EQ_ENA_MASK 0x0001 /* EQ_ENA */
+#define WM8904_EQ_ENA_SHIFT 0 /* EQ_ENA */
+#define WM8904_EQ_ENA_WIDTH 1 /* EQ_ENA */
+
+/*
+ * R135 (0x87) - EQ2
+ */
+#define WM8904_EQ_B1_GAIN_MASK 0x001F /* EQ_B1_GAIN - [4:0] */
+#define WM8904_EQ_B1_GAIN_SHIFT 0 /* EQ_B1_GAIN - [4:0] */
+#define WM8904_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [4:0] */
+
+/*
+ * R136 (0x88) - EQ3
+ */
+#define WM8904_EQ_B2_GAIN_MASK 0x001F /* EQ_B2_GAIN - [4:0] */
+#define WM8904_EQ_B2_GAIN_SHIFT 0 /* EQ_B2_GAIN - [4:0] */
+#define WM8904_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [4:0] */
+
+/*
+ * R137 (0x89) - EQ4
+ */
+#define WM8904_EQ_B3_GAIN_MASK 0x001F /* EQ_B3_GAIN - [4:0] */
+#define WM8904_EQ_B3_GAIN_SHIFT 0 /* EQ_B3_GAIN - [4:0] */
+#define WM8904_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [4:0] */
+
+/*
+ * R138 (0x8A) - EQ5
+ */
+#define WM8904_EQ_B4_GAIN_MASK 0x001F /* EQ_B4_GAIN - [4:0] */
+#define WM8904_EQ_B4_GAIN_SHIFT 0 /* EQ_B4_GAIN - [4:0] */
+#define WM8904_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [4:0] */
+
+/*
+ * R139 (0x8B) - EQ6
+ */
+#define WM8904_EQ_B5_GAIN_MASK 0x001F /* EQ_B5_GAIN - [4:0] */
+#define WM8904_EQ_B5_GAIN_SHIFT 0 /* EQ_B5_GAIN - [4:0] */
+#define WM8904_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [4:0] */
+
+/*
+ * R140 (0x8C) - EQ7
+ */
+#define WM8904_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */
+#define WM8904_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */
+#define WM8904_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */
+
+/*
+ * R141 (0x8D) - EQ8
+ */
+#define WM8904_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */
+#define WM8904_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */
+#define WM8904_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */
+
+/*
+ * R142 (0x8E) - EQ9
+ */
+#define WM8904_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */
+#define WM8904_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */
+#define WM8904_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */
+
+/*
+ * R143 (0x8F) - EQ10
+ */
+#define WM8904_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */
+#define WM8904_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */
+#define WM8904_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */
+
+/*
+ * R144 (0x90) - EQ11
+ */
+#define WM8904_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */
+#define WM8904_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */
+#define WM8904_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */
+
+/*
+ * R145 (0x91) - EQ12
+ */
+#define WM8904_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */
+#define WM8904_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */
+#define WM8904_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */
+
+/*
+ * R146 (0x92) - EQ13
+ */
+#define WM8904_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */
+#define WM8904_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */
+#define WM8904_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */
+
+/*
+ * R147 (0x93) - EQ14
+ */
+#define WM8904_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */
+#define WM8904_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */
+#define WM8904_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */
+
+/*
+ * R148 (0x94) - EQ15
+ */
+#define WM8904_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */
+#define WM8904_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */
+#define WM8904_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */
+
+/*
+ * R149 (0x95) - EQ16
+ */
+#define WM8904_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */
+#define WM8904_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */
+#define WM8904_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */
+
+/*
+ * R150 (0x96) - EQ17
+ */
+#define WM8904_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */
+#define WM8904_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */
+#define WM8904_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */
+
+/*
+ * R151 (0x97) - EQ18
+ */
+#define WM8904_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */
+#define WM8904_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */
+#define WM8904_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */
+
+/*
+ * R152 (0x98) - EQ19
+ */
+#define WM8904_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */
+#define WM8904_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */
+#define WM8904_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */
+
+/*
+ * R153 (0x99) - EQ20
+ */
+#define WM8904_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */
+#define WM8904_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */
+#define WM8904_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */
+
+/*
+ * R154 (0x9A) - EQ21
+ */
+#define WM8904_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */
+#define WM8904_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */
+#define WM8904_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */
+
+/*
+ * R155 (0x9B) - EQ22
+ */
+#define WM8904_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */
+#define WM8904_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */
+#define WM8904_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */
+
+/*
+ * R156 (0x9C) - EQ23
+ */
+#define WM8904_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */
+#define WM8904_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */
+#define WM8904_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */
+
+/*
+ * R157 (0x9D) - EQ24
+ */
+#define WM8904_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */
+#define WM8904_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */
+#define WM8904_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */
+
+/*
+ * R161 (0xA1) - Control Interface Test 1
+ */
+#define WM8904_USER_KEY 0x0002 /* USER_KEY */
+#define WM8904_USER_KEY_MASK 0x0002 /* USER_KEY */
+#define WM8904_USER_KEY_SHIFT 1 /* USER_KEY */
+#define WM8904_USER_KEY_WIDTH 1 /* USER_KEY */
+
+/*
+ * R204 (0xCC) - Analogue Output Bias 0
+ */
+#define WM8904_PGA_BIAS_MASK 0x0070 /* PGA_BIAS - [6:4] */
+#define WM8904_PGA_BIAS_SHIFT 4 /* PGA_BIAS - [6:4] */
+#define WM8904_PGA_BIAS_WIDTH 3 /* PGA_BIAS - [6:4] */
+
+/*
+ * R247 (0xF7) - FLL NCO Test 0
+ */
+#define WM8904_FLL_FRC_NCO 0x0001 /* FLL_FRC_NCO */
+#define WM8904_FLL_FRC_NCO_MASK 0x0001 /* FLL_FRC_NCO */
+#define WM8904_FLL_FRC_NCO_SHIFT 0 /* FLL_FRC_NCO */
+#define WM8904_FLL_FRC_NCO_WIDTH 1 /* FLL_FRC_NCO */
+
+/*
+ * R248 (0xF8) - FLL NCO Test 1
+ */
+#define WM8904_FLL_FRC_NCO_VAL_MASK 0x003F /* FLL_FRC_NCO_VAL - [5:0] */
+#define WM8904_FLL_FRC_NCO_VAL_SHIFT 0 /* FLL_FRC_NCO_VAL - [5:0] */
+#define WM8904_FLL_FRC_NCO_VAL_WIDTH 6 /* FLL_FRC_NCO_VAL - [5:0] */
+
+#endif
diff --git a/sound/soc/codecs/wm8915.c b/sound/soc/codecs/wm8915.c
new file mode 100644
index 00000000..e2ab4fac
--- /dev/null
+++ b/sound/soc/codecs/wm8915.c
@@ -0,0 +1,2931 @@
+/*
+ * wm8915.c - WM8915 audio codec interface
+ *
+ * Copyright 2011 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/gcd.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <trace/events/asoc.h>
+
+#include <sound/wm8915.h>
+#include "wm8915.h"
+
+#define WM8915_AIFS 2
+
+#define HPOUT1L 1
+#define HPOUT1R 2
+#define HPOUT2L 4
+#define HPOUT2R 8
+
+#define WM8915_NUM_SUPPLIES 6
+static const char *wm8915_supply_names[WM8915_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD",
+ "AVDD1",
+ "AVDD2",
+ "CPVDD",
+ "MICVDD",
+};
+
+struct wm8915_priv {
+ struct snd_soc_codec *codec;
+
+ int ldo1ena;
+
+ int sysclk;
+
+ int fll_src;
+ int fll_fref;
+ int fll_fout;
+
+ struct completion fll_lock;
+
+ u16 dcs_pending;
+ struct completion dcs_done;
+
+ u16 hpout_ena;
+ u16 hpout_pending;
+
+ struct regulator_bulk_data supplies[WM8915_NUM_SUPPLIES];
+ struct notifier_block disable_nb[WM8915_NUM_SUPPLIES];
+
+ struct wm8915_pdata pdata;
+
+ int rx_rate[WM8915_AIFS];
+
+ /* Platform dependant ReTune mobile configuration */
+ int num_retune_mobile_texts;
+ const char **retune_mobile_texts;
+ int retune_mobile_cfg[2];
+ struct soc_enum retune_mobile_enum;
+
+ struct snd_soc_jack *jack;
+ bool detecting;
+ bool jack_mic;
+ wm8915_polarity_fn polarity_cb;
+
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gpio_chip;
+#endif
+};
+
+/* We can't use the same notifier block for more than one supply and
+ * there's no way I can see to get from a callback to the caller
+ * except container_of().
+ */
+#define WM8915_REGULATOR_EVENT(n) \
+static int wm8915_regulator_event_##n(struct notifier_block *nb, \
+ unsigned long event, void *data) \
+{ \
+ struct wm8915_priv *wm8915 = container_of(nb, struct wm8915_priv, \
+ disable_nb[n]); \
+ if (event & REGULATOR_EVENT_DISABLE) { \
+ wm8915->codec->cache_sync = 1; \
+ } \
+ return 0; \
+}
+
+WM8915_REGULATOR_EVENT(0)
+WM8915_REGULATOR_EVENT(1)
+WM8915_REGULATOR_EVENT(2)
+WM8915_REGULATOR_EVENT(3)
+WM8915_REGULATOR_EVENT(4)
+WM8915_REGULATOR_EVENT(5)
+
+static const u16 wm8915_reg[WM8915_MAX_REGISTER] = {
+ [WM8915_SOFTWARE_RESET] = 0x8915,
+ [WM8915_POWER_MANAGEMENT_7] = 0x10,
+ [WM8915_DAC1_HPOUT1_VOLUME] = 0x88,
+ [WM8915_DAC2_HPOUT2_VOLUME] = 0x88,
+ [WM8915_DAC1_LEFT_VOLUME] = 0x2c0,
+ [WM8915_DAC1_RIGHT_VOLUME] = 0x2c0,
+ [WM8915_DAC2_LEFT_VOLUME] = 0x2c0,
+ [WM8915_DAC2_RIGHT_VOLUME] = 0x2c0,
+ [WM8915_OUTPUT1_LEFT_VOLUME] = 0x80,
+ [WM8915_OUTPUT1_RIGHT_VOLUME] = 0x80,
+ [WM8915_OUTPUT2_LEFT_VOLUME] = 0x80,
+ [WM8915_OUTPUT2_RIGHT_VOLUME] = 0x80,
+ [WM8915_MICBIAS_1] = 0x39,
+ [WM8915_MICBIAS_2] = 0x39,
+ [WM8915_LDO_1] = 0x3,
+ [WM8915_LDO_2] = 0x13,
+ [WM8915_ACCESSORY_DETECT_MODE_1] = 0x4,
+ [WM8915_HEADPHONE_DETECT_1] = 0x20,
+ [WM8915_MIC_DETECT_1] = 0x7600,
+ [WM8915_MIC_DETECT_2] = 0xbf,
+ [WM8915_CHARGE_PUMP_1] = 0x1f25,
+ [WM8915_CHARGE_PUMP_2] = 0xab19,
+ [WM8915_DC_SERVO_5] = 0x2a2a,
+ [WM8915_CONTROL_INTERFACE_1] = 0x8004,
+ [WM8915_CLOCKING_1] = 0x10,
+ [WM8915_AIF_RATE] = 0x83,
+ [WM8915_FLL_CONTROL_4] = 0x5dc0,
+ [WM8915_FLL_CONTROL_5] = 0xc84,
+ [WM8915_FLL_EFS_2] = 0x2,
+ [WM8915_AIF1_TX_LRCLK_1] = 0x80,
+ [WM8915_AIF1_TX_LRCLK_2] = 0x8,
+ [WM8915_AIF1_RX_LRCLK_1] = 0x80,
+ [WM8915_AIF1TX_DATA_CONFIGURATION_1] = 0x1818,
+ [WM8915_AIF1RX_DATA_CONFIGURATION] = 0x1818,
+ [WM8915_AIF1TX_TEST] = 0x7,
+ [WM8915_AIF2_TX_LRCLK_1] = 0x80,
+ [WM8915_AIF2_TX_LRCLK_2] = 0x8,
+ [WM8915_AIF2_RX_LRCLK_1] = 0x80,
+ [WM8915_AIF2TX_DATA_CONFIGURATION_1] = 0x1818,
+ [WM8915_AIF2RX_DATA_CONFIGURATION] = 0x1818,
+ [WM8915_AIF2TX_TEST] = 0x1,
+ [WM8915_DSP1_TX_LEFT_VOLUME] = 0xc0,
+ [WM8915_DSP1_TX_RIGHT_VOLUME] = 0xc0,
+ [WM8915_DSP1_RX_LEFT_VOLUME] = 0xc0,
+ [WM8915_DSP1_RX_RIGHT_VOLUME] = 0xc0,
+ [WM8915_DSP1_TX_FILTERS] = 0x2000,
+ [WM8915_DSP1_RX_FILTERS_1] = 0x200,
+ [WM8915_DSP1_RX_FILTERS_2] = 0x10,
+ [WM8915_DSP1_DRC_1] = 0x98,
+ [WM8915_DSP1_DRC_2] = 0x845,
+ [WM8915_DSP1_RX_EQ_GAINS_1] = 0x6318,
+ [WM8915_DSP1_RX_EQ_GAINS_2] = 0x6300,
+ [WM8915_DSP1_RX_EQ_BAND_1_A] = 0xfca,
+ [WM8915_DSP1_RX_EQ_BAND_1_B] = 0x400,
+ [WM8915_DSP1_RX_EQ_BAND_1_PG] = 0xd8,
+ [WM8915_DSP1_RX_EQ_BAND_2_A] = 0x1eb5,
+ [WM8915_DSP1_RX_EQ_BAND_2_B] = 0xf145,
+ [WM8915_DSP1_RX_EQ_BAND_2_C] = 0xb75,
+ [WM8915_DSP1_RX_EQ_BAND_2_PG] = 0x1c5,
+ [WM8915_DSP1_RX_EQ_BAND_3_A] = 0x1c58,
+ [WM8915_DSP1_RX_EQ_BAND_3_B] = 0xf373,
+ [WM8915_DSP1_RX_EQ_BAND_3_C] = 0xa54,
+ [WM8915_DSP1_RX_EQ_BAND_3_PG] = 0x558,
+ [WM8915_DSP1_RX_EQ_BAND_4_A] = 0x168e,
+ [WM8915_DSP1_RX_EQ_BAND_4_B] = 0xf829,
+ [WM8915_DSP1_RX_EQ_BAND_4_C] = 0x7ad,
+ [WM8915_DSP1_RX_EQ_BAND_4_PG] = 0x1103,
+ [WM8915_DSP1_RX_EQ_BAND_5_A] = 0x564,
+ [WM8915_DSP1_RX_EQ_BAND_5_B] = 0x559,
+ [WM8915_DSP1_RX_EQ_BAND_5_PG] = 0x4000,
+ [WM8915_DSP2_TX_LEFT_VOLUME] = 0xc0,
+ [WM8915_DSP2_TX_RIGHT_VOLUME] = 0xc0,
+ [WM8915_DSP2_RX_LEFT_VOLUME] = 0xc0,
+ [WM8915_DSP2_RX_RIGHT_VOLUME] = 0xc0,
+ [WM8915_DSP2_TX_FILTERS] = 0x2000,
+ [WM8915_DSP2_RX_FILTERS_1] = 0x200,
+ [WM8915_DSP2_RX_FILTERS_2] = 0x10,
+ [WM8915_DSP2_DRC_1] = 0x98,
+ [WM8915_DSP2_DRC_2] = 0x845,
+ [WM8915_DSP2_RX_EQ_GAINS_1] = 0x6318,
+ [WM8915_DSP2_RX_EQ_GAINS_2] = 0x6300,
+ [WM8915_DSP2_RX_EQ_BAND_1_A] = 0xfca,
+ [WM8915_DSP2_RX_EQ_BAND_1_B] = 0x400,
+ [WM8915_DSP2_RX_EQ_BAND_1_PG] = 0xd8,
+ [WM8915_DSP2_RX_EQ_BAND_2_A] = 0x1eb5,
+ [WM8915_DSP2_RX_EQ_BAND_2_B] = 0xf145,
+ [WM8915_DSP2_RX_EQ_BAND_2_C] = 0xb75,
+ [WM8915_DSP2_RX_EQ_BAND_2_PG] = 0x1c5,
+ [WM8915_DSP2_RX_EQ_BAND_3_A] = 0x1c58,
+ [WM8915_DSP2_RX_EQ_BAND_3_B] = 0xf373,
+ [WM8915_DSP2_RX_EQ_BAND_3_C] = 0xa54,
+ [WM8915_DSP2_RX_EQ_BAND_3_PG] = 0x558,
+ [WM8915_DSP2_RX_EQ_BAND_4_A] = 0x168e,
+ [WM8915_DSP2_RX_EQ_BAND_4_B] = 0xf829,
+ [WM8915_DSP2_RX_EQ_BAND_4_C] = 0x7ad,
+ [WM8915_DSP2_RX_EQ_BAND_4_PG] = 0x1103,
+ [WM8915_DSP2_RX_EQ_BAND_5_A] = 0x564,
+ [WM8915_DSP2_RX_EQ_BAND_5_B] = 0x559,
+ [WM8915_DSP2_RX_EQ_BAND_5_PG] = 0x4000,
+ [WM8915_OVERSAMPLING] = 0xd,
+ [WM8915_SIDETONE] = 0x1040,
+ [WM8915_GPIO_1] = 0xa101,
+ [WM8915_GPIO_2] = 0xa101,
+ [WM8915_GPIO_3] = 0xa101,
+ [WM8915_GPIO_4] = 0xa101,
+ [WM8915_GPIO_5] = 0xa101,
+ [WM8915_PULL_CONTROL_2] = 0x140,
+ [WM8915_INTERRUPT_STATUS_1_MASK] = 0x1f,
+ [WM8915_INTERRUPT_STATUS_2_MASK] = 0x1ecf,
+ [WM8915_RIGHT_PDM_SPEAKER] = 0x1,
+ [WM8915_PDM_SPEAKER_MUTE_SEQUENCE] = 0x69,
+ [WM8915_PDM_SPEAKER_VOLUME] = 0x66,
+ [WM8915_WRITE_SEQUENCER_0] = 0x1,
+ [WM8915_WRITE_SEQUENCER_1] = 0x1,
+ [WM8915_WRITE_SEQUENCER_3] = 0x6,
+ [WM8915_WRITE_SEQUENCER_4] = 0x40,
+ [WM8915_WRITE_SEQUENCER_5] = 0x1,
+ [WM8915_WRITE_SEQUENCER_6] = 0xf,
+ [WM8915_WRITE_SEQUENCER_7] = 0x6,
+ [WM8915_WRITE_SEQUENCER_8] = 0x1,
+ [WM8915_WRITE_SEQUENCER_9] = 0x3,
+ [WM8915_WRITE_SEQUENCER_10] = 0x104,
+ [WM8915_WRITE_SEQUENCER_12] = 0x60,
+ [WM8915_WRITE_SEQUENCER_13] = 0x11,
+ [WM8915_WRITE_SEQUENCER_14] = 0x401,
+ [WM8915_WRITE_SEQUENCER_16] = 0x50,
+ [WM8915_WRITE_SEQUENCER_17] = 0x3,
+ [WM8915_WRITE_SEQUENCER_18] = 0x100,
+ [WM8915_WRITE_SEQUENCER_20] = 0x51,
+ [WM8915_WRITE_SEQUENCER_21] = 0x3,
+ [WM8915_WRITE_SEQUENCER_22] = 0x104,
+ [WM8915_WRITE_SEQUENCER_23] = 0xa,
+ [WM8915_WRITE_SEQUENCER_24] = 0x60,
+ [WM8915_WRITE_SEQUENCER_25] = 0x3b,
+ [WM8915_WRITE_SEQUENCER_26] = 0x502,
+ [WM8915_WRITE_SEQUENCER_27] = 0x100,
+ [WM8915_WRITE_SEQUENCER_28] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_32] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_36] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_40] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_44] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_48] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_52] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_56] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_60] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_64] = 0x1,
+ [WM8915_WRITE_SEQUENCER_65] = 0x1,
+ [WM8915_WRITE_SEQUENCER_67] = 0x6,
+ [WM8915_WRITE_SEQUENCER_68] = 0x40,
+ [WM8915_WRITE_SEQUENCER_69] = 0x1,
+ [WM8915_WRITE_SEQUENCER_70] = 0xf,
+ [WM8915_WRITE_SEQUENCER_71] = 0x6,
+ [WM8915_WRITE_SEQUENCER_72] = 0x1,
+ [WM8915_WRITE_SEQUENCER_73] = 0x3,
+ [WM8915_WRITE_SEQUENCER_74] = 0x104,
+ [WM8915_WRITE_SEQUENCER_76] = 0x60,
+ [WM8915_WRITE_SEQUENCER_77] = 0x11,
+ [WM8915_WRITE_SEQUENCER_78] = 0x401,
+ [WM8915_WRITE_SEQUENCER_80] = 0x50,
+ [WM8915_WRITE_SEQUENCER_81] = 0x3,
+ [WM8915_WRITE_SEQUENCER_82] = 0x100,
+ [WM8915_WRITE_SEQUENCER_84] = 0x60,
+ [WM8915_WRITE_SEQUENCER_85] = 0x3b,
+ [WM8915_WRITE_SEQUENCER_86] = 0x502,
+ [WM8915_WRITE_SEQUENCER_87] = 0x100,
+ [WM8915_WRITE_SEQUENCER_88] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_92] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_96] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_100] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_104] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_108] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_112] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_116] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_120] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_124] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_128] = 0x1,
+ [WM8915_WRITE_SEQUENCER_129] = 0x1,
+ [WM8915_WRITE_SEQUENCER_131] = 0x6,
+ [WM8915_WRITE_SEQUENCER_132] = 0x40,
+ [WM8915_WRITE_SEQUENCER_133] = 0x1,
+ [WM8915_WRITE_SEQUENCER_134] = 0xf,
+ [WM8915_WRITE_SEQUENCER_135] = 0x6,
+ [WM8915_WRITE_SEQUENCER_136] = 0x1,
+ [WM8915_WRITE_SEQUENCER_137] = 0x3,
+ [WM8915_WRITE_SEQUENCER_138] = 0x106,
+ [WM8915_WRITE_SEQUENCER_140] = 0x61,
+ [WM8915_WRITE_SEQUENCER_141] = 0x11,
+ [WM8915_WRITE_SEQUENCER_142] = 0x401,
+ [WM8915_WRITE_SEQUENCER_144] = 0x50,
+ [WM8915_WRITE_SEQUENCER_145] = 0x3,
+ [WM8915_WRITE_SEQUENCER_146] = 0x102,
+ [WM8915_WRITE_SEQUENCER_148] = 0x51,
+ [WM8915_WRITE_SEQUENCER_149] = 0x3,
+ [WM8915_WRITE_SEQUENCER_150] = 0x106,
+ [WM8915_WRITE_SEQUENCER_151] = 0xa,
+ [WM8915_WRITE_SEQUENCER_152] = 0x61,
+ [WM8915_WRITE_SEQUENCER_153] = 0x3b,
+ [WM8915_WRITE_SEQUENCER_154] = 0x502,
+ [WM8915_WRITE_SEQUENCER_155] = 0x100,
+ [WM8915_WRITE_SEQUENCER_156] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_160] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_164] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_168] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_172] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_176] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_180] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_184] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_188] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_192] = 0x1,
+ [WM8915_WRITE_SEQUENCER_193] = 0x1,
+ [WM8915_WRITE_SEQUENCER_195] = 0x6,
+ [WM8915_WRITE_SEQUENCER_196] = 0x40,
+ [WM8915_WRITE_SEQUENCER_197] = 0x1,
+ [WM8915_WRITE_SEQUENCER_198] = 0xf,
+ [WM8915_WRITE_SEQUENCER_199] = 0x6,
+ [WM8915_WRITE_SEQUENCER_200] = 0x1,
+ [WM8915_WRITE_SEQUENCER_201] = 0x3,
+ [WM8915_WRITE_SEQUENCER_202] = 0x106,
+ [WM8915_WRITE_SEQUENCER_204] = 0x61,
+ [WM8915_WRITE_SEQUENCER_205] = 0x11,
+ [WM8915_WRITE_SEQUENCER_206] = 0x401,
+ [WM8915_WRITE_SEQUENCER_208] = 0x50,
+ [WM8915_WRITE_SEQUENCER_209] = 0x3,
+ [WM8915_WRITE_SEQUENCER_210] = 0x102,
+ [WM8915_WRITE_SEQUENCER_212] = 0x61,
+ [WM8915_WRITE_SEQUENCER_213] = 0x3b,
+ [WM8915_WRITE_SEQUENCER_214] = 0x502,
+ [WM8915_WRITE_SEQUENCER_215] = 0x100,
+ [WM8915_WRITE_SEQUENCER_216] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_220] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_224] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_228] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_232] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_236] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_240] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_244] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_248] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_252] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_256] = 0x60,
+ [WM8915_WRITE_SEQUENCER_258] = 0x601,
+ [WM8915_WRITE_SEQUENCER_260] = 0x50,
+ [WM8915_WRITE_SEQUENCER_262] = 0x100,
+ [WM8915_WRITE_SEQUENCER_264] = 0x1,
+ [WM8915_WRITE_SEQUENCER_266] = 0x104,
+ [WM8915_WRITE_SEQUENCER_267] = 0x100,
+ [WM8915_WRITE_SEQUENCER_268] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_272] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_276] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_280] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_284] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_288] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_292] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_296] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_300] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_304] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_308] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_312] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_316] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_320] = 0x61,
+ [WM8915_WRITE_SEQUENCER_322] = 0x601,
+ [WM8915_WRITE_SEQUENCER_324] = 0x50,
+ [WM8915_WRITE_SEQUENCER_326] = 0x102,
+ [WM8915_WRITE_SEQUENCER_328] = 0x1,
+ [WM8915_WRITE_SEQUENCER_330] = 0x106,
+ [WM8915_WRITE_SEQUENCER_331] = 0x100,
+ [WM8915_WRITE_SEQUENCER_332] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_336] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_340] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_344] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_348] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_352] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_356] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_360] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_364] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_368] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_372] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_376] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_380] = 0x2fff,
+ [WM8915_WRITE_SEQUENCER_384] = 0x60,
+ [WM8915_WRITE_SEQUENCER_386] = 0x601,
+ [WM8915_WRITE_SEQUENCER_388] = 0x61,
+ [WM8915_WRITE_SEQUENCER_390] = 0x601,
+ [WM8915_WRITE_SEQUENCER_392] = 0x50,
+ [WM8915_WRITE_SEQUENCER_394] = 0x300,
+ [WM8915_WRITE_SEQUENCER_396] = 0x1,
+ [WM8915_WRITE_SEQUENCER_398] = 0x304,
+ [WM8915_WRITE_SEQUENCER_400] = 0x40,
+ [WM8915_WRITE_SEQUENCER_402] = 0xf,
+ [WM8915_WRITE_SEQUENCER_404] = 0x1,
+ [WM8915_WRITE_SEQUENCER_407] = 0x100,
+};
+
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, 0, 100, 0);
+static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 150, 0);
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(out_digital_tlv, -1200, 150, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -900, 75, 0);
+static const DECLARE_TLV_DB_SCALE(spk_tlv, -900, 150, 0);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+
+static const char *sidetone_hpf_text[] = {
+ "2.9kHz", "1.5kHz", "735Hz", "403Hz", "196Hz", "98Hz", "49Hz"
+};
+
+static const struct soc_enum sidetone_hpf =
+ SOC_ENUM_SINGLE(WM8915_SIDETONE, 7, 6, sidetone_hpf_text);
+
+static const char *hpf_mode_text[] = {
+ "HiFi", "Custom", "Voice"
+};
+
+static const struct soc_enum dsp1tx_hpf_mode =
+ SOC_ENUM_SINGLE(WM8915_DSP1_TX_FILTERS, 3, 3, hpf_mode_text);
+
+static const struct soc_enum dsp2tx_hpf_mode =
+ SOC_ENUM_SINGLE(WM8915_DSP2_TX_FILTERS, 3, 3, hpf_mode_text);
+
+static const char *hpf_cutoff_text[] = {
+ "50Hz", "75Hz", "100Hz", "150Hz", "200Hz", "300Hz", "400Hz"
+};
+
+static const struct soc_enum dsp1tx_hpf_cutoff =
+ SOC_ENUM_SINGLE(WM8915_DSP1_TX_FILTERS, 0, 7, hpf_cutoff_text);
+
+static const struct soc_enum dsp2tx_hpf_cutoff =
+ SOC_ENUM_SINGLE(WM8915_DSP2_TX_FILTERS, 0, 7, hpf_cutoff_text);
+
+static void wm8915_set_retune_mobile(struct snd_soc_codec *codec, int block)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ struct wm8915_pdata *pdata = &wm8915->pdata;
+ int base, best, best_val, save, i, cfg, iface;
+
+ if (!wm8915->num_retune_mobile_texts)
+ return;
+
+ switch (block) {
+ case 0:
+ base = WM8915_DSP1_RX_EQ_GAINS_1;
+ if (snd_soc_read(codec, WM8915_POWER_MANAGEMENT_8) &
+ WM8915_DSP1RX_SRC)
+ iface = 1;
+ else
+ iface = 0;
+ break;
+ case 1:
+ base = WM8915_DSP1_RX_EQ_GAINS_2;
+ if (snd_soc_read(codec, WM8915_POWER_MANAGEMENT_8) &
+ WM8915_DSP2RX_SRC)
+ iface = 1;
+ else
+ iface = 0;
+ break;
+ default:
+ return;
+ }
+
+ /* Find the version of the currently selected configuration
+ * with the nearest sample rate. */
+ cfg = wm8915->retune_mobile_cfg[block];
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+ if (strcmp(pdata->retune_mobile_cfgs[i].name,
+ wm8915->retune_mobile_texts[cfg]) == 0 &&
+ abs(pdata->retune_mobile_cfgs[i].rate
+ - wm8915->rx_rate[iface]) < best_val) {
+ best = i;
+ best_val = abs(pdata->retune_mobile_cfgs[i].rate
+ - wm8915->rx_rate[iface]);
+ }
+ }
+
+ dev_dbg(codec->dev, "ReTune Mobile %d %s/%dHz for %dHz sample rate\n",
+ block,
+ pdata->retune_mobile_cfgs[best].name,
+ pdata->retune_mobile_cfgs[best].rate,
+ wm8915->rx_rate[iface]);
+
+ /* The EQ will be disabled while reconfiguring it, remember the
+ * current configuration.
+ */
+ save = snd_soc_read(codec, base);
+ save &= WM8915_DSP1RX_EQ_ENA;
+
+ for (i = 0; i < ARRAY_SIZE(pdata->retune_mobile_cfgs[best].regs); i++)
+ snd_soc_update_bits(codec, base + i, 0xffff,
+ pdata->retune_mobile_cfgs[best].regs[i]);
+
+ snd_soc_update_bits(codec, base, WM8915_DSP1RX_EQ_ENA, save);
+}
+
+/* Icky as hell but saves code duplication */
+static int wm8915_get_retune_mobile_block(const char *name)
+{
+ if (strcmp(name, "DSP1 EQ Mode") == 0)
+ return 0;
+ if (strcmp(name, "DSP2 EQ Mode") == 0)
+ return 1;
+ return -EINVAL;
+}
+
+static int wm8915_put_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ struct wm8915_pdata *pdata = &wm8915->pdata;
+ int block = wm8915_get_retune_mobile_block(kcontrol->id.name);
+ int value = ucontrol->value.integer.value[0];
+
+ if (block < 0)
+ return block;
+
+ if (value >= pdata->num_retune_mobile_cfgs)
+ return -EINVAL;
+
+ wm8915->retune_mobile_cfg[block] = value;
+
+ wm8915_set_retune_mobile(codec, block);
+
+ return 0;
+}
+
+static int wm8915_get_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ int block = wm8915_get_retune_mobile_block(kcontrol->id.name);
+
+ ucontrol->value.enumerated.item[0] = wm8915->retune_mobile_cfg[block];
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new wm8915_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Capture Volume", WM8915_LEFT_LINE_INPUT_VOLUME,
+ WM8915_RIGHT_LINE_INPUT_VOLUME, 0, 31, 0, inpga_tlv),
+SOC_DOUBLE_R("Capture ZC Switch", WM8915_LEFT_LINE_INPUT_VOLUME,
+ WM8915_RIGHT_LINE_INPUT_VOLUME, 5, 1, 0),
+
+SOC_DOUBLE_TLV("DAC1 Sidetone Volume", WM8915_DAC1_MIXER_VOLUMES,
+ 0, 5, 24, 0, sidetone_tlv),
+SOC_DOUBLE_TLV("DAC2 Sidetone Volume", WM8915_DAC2_MIXER_VOLUMES,
+ 0, 5, 24, 0, sidetone_tlv),
+SOC_SINGLE("Sidetone LPF Switch", WM8915_SIDETONE, 12, 1, 0),
+SOC_ENUM("Sidetone HPF Cut-off", sidetone_hpf),
+SOC_SINGLE("Sidetone HPF Switch", WM8915_SIDETONE, 6, 1, 0),
+
+SOC_DOUBLE_R_TLV("DSP1 Capture Volume", WM8915_DSP1_TX_LEFT_VOLUME,
+ WM8915_DSP1_TX_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("DSP2 Capture Volume", WM8915_DSP2_TX_LEFT_VOLUME,
+ WM8915_DSP2_TX_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+
+SOC_SINGLE("DSP1 Capture Notch Filter Switch", WM8915_DSP1_TX_FILTERS,
+ 13, 1, 0),
+SOC_DOUBLE("DSP1 Capture HPF Switch", WM8915_DSP1_TX_FILTERS, 12, 11, 1, 0),
+SOC_ENUM("DSP1 Capture HPF Mode", dsp1tx_hpf_mode),
+SOC_ENUM("DSP1 Capture HPF Cutoff", dsp1tx_hpf_cutoff),
+
+SOC_SINGLE("DSP2 Capture Notch Filter Switch", WM8915_DSP2_TX_FILTERS,
+ 13, 1, 0),
+SOC_DOUBLE("DSP2 Capture HPF Switch", WM8915_DSP2_TX_FILTERS, 12, 11, 1, 0),
+SOC_ENUM("DSP2 Capture HPF Mode", dsp2tx_hpf_mode),
+SOC_ENUM("DSP2 Capture HPF Cutoff", dsp2tx_hpf_cutoff),
+
+SOC_DOUBLE_R_TLV("DSP1 Playback Volume", WM8915_DSP1_RX_LEFT_VOLUME,
+ WM8915_DSP1_RX_RIGHT_VOLUME, 1, 112, 0, digital_tlv),
+SOC_SINGLE("DSP1 Playback Switch", WM8915_DSP1_RX_FILTERS_1, 9, 1, 1),
+
+SOC_DOUBLE_R_TLV("DSP2 Playback Volume", WM8915_DSP2_RX_LEFT_VOLUME,
+ WM8915_DSP2_RX_RIGHT_VOLUME, 1, 112, 0, digital_tlv),
+SOC_SINGLE("DSP2 Playback Switch", WM8915_DSP2_RX_FILTERS_1, 9, 1, 1),
+
+SOC_DOUBLE_R_TLV("DAC1 Volume", WM8915_DAC1_LEFT_VOLUME,
+ WM8915_DAC1_RIGHT_VOLUME, 1, 112, 0, digital_tlv),
+SOC_DOUBLE_R("DAC1 Switch", WM8915_DAC1_LEFT_VOLUME,
+ WM8915_DAC1_RIGHT_VOLUME, 9, 1, 1),
+
+SOC_DOUBLE_R_TLV("DAC2 Volume", WM8915_DAC2_LEFT_VOLUME,
+ WM8915_DAC2_RIGHT_VOLUME, 1, 112, 0, digital_tlv),
+SOC_DOUBLE_R("DAC2 Switch", WM8915_DAC2_LEFT_VOLUME,
+ WM8915_DAC2_RIGHT_VOLUME, 9, 1, 1),
+
+SOC_SINGLE("Speaker High Performance Switch", WM8915_OVERSAMPLING, 3, 1, 0),
+SOC_SINGLE("DMIC High Performance Switch", WM8915_OVERSAMPLING, 2, 1, 0),
+SOC_SINGLE("ADC High Performance Switch", WM8915_OVERSAMPLING, 1, 1, 0),
+SOC_SINGLE("DAC High Performance Switch", WM8915_OVERSAMPLING, 0, 1, 0),
+
+SOC_SINGLE("DAC Soft Mute Switch", WM8915_DAC_SOFTMUTE, 1, 1, 0),
+SOC_SINGLE("DAC Slow Soft Mute Switch", WM8915_DAC_SOFTMUTE, 0, 1, 0),
+
+SOC_DOUBLE_TLV("Digital Output 1 Volume", WM8915_DAC1_HPOUT1_VOLUME, 0, 4,
+ 8, 0, out_digital_tlv),
+SOC_DOUBLE_TLV("Digital Output 2 Volume", WM8915_DAC2_HPOUT2_VOLUME, 0, 4,
+ 8, 0, out_digital_tlv),
+
+SOC_DOUBLE_R_TLV("Output 1 Volume", WM8915_OUTPUT1_LEFT_VOLUME,
+ WM8915_OUTPUT1_RIGHT_VOLUME, 0, 12, 0, out_tlv),
+SOC_DOUBLE_R("Output 1 ZC Switch", WM8915_OUTPUT1_LEFT_VOLUME,
+ WM8915_OUTPUT1_RIGHT_VOLUME, 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Output 2 Volume", WM8915_OUTPUT2_LEFT_VOLUME,
+ WM8915_OUTPUT2_RIGHT_VOLUME, 0, 12, 0, out_tlv),
+SOC_DOUBLE_R("Output 2 ZC Switch", WM8915_OUTPUT2_LEFT_VOLUME,
+ WM8915_OUTPUT2_RIGHT_VOLUME, 7, 1, 0),
+
+SOC_DOUBLE_TLV("Speaker Volume", WM8915_PDM_SPEAKER_VOLUME, 0, 4, 8, 0,
+ spk_tlv),
+SOC_DOUBLE_R("Speaker Switch", WM8915_LEFT_PDM_SPEAKER,
+ WM8915_RIGHT_PDM_SPEAKER, 3, 1, 1),
+SOC_DOUBLE_R("Speaker ZC Switch", WM8915_LEFT_PDM_SPEAKER,
+ WM8915_RIGHT_PDM_SPEAKER, 2, 1, 0),
+
+SOC_SINGLE("DSP1 EQ Switch", WM8915_DSP1_RX_EQ_GAINS_1, 0, 1, 0),
+SOC_SINGLE("DSP2 EQ Switch", WM8915_DSP2_RX_EQ_GAINS_1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8915_eq_controls[] = {
+SOC_SINGLE_TLV("DSP1 EQ B1 Volume", WM8915_DSP1_RX_EQ_GAINS_1, 11, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("DSP1 EQ B2 Volume", WM8915_DSP1_RX_EQ_GAINS_1, 6, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("DSP1 EQ B3 Volume", WM8915_DSP1_RX_EQ_GAINS_1, 1, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("DSP1 EQ B4 Volume", WM8915_DSP1_RX_EQ_GAINS_2, 11, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("DSP1 EQ B5 Volume", WM8915_DSP1_RX_EQ_GAINS_2, 6, 31, 0,
+ eq_tlv),
+
+SOC_SINGLE_TLV("DSP2 EQ B1 Volume", WM8915_DSP2_RX_EQ_GAINS_1, 11, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("DSP2 EQ B2 Volume", WM8915_DSP2_RX_EQ_GAINS_1, 6, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("DSP2 EQ B3 Volume", WM8915_DSP2_RX_EQ_GAINS_1, 1, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("DSP2 EQ B4 Volume", WM8915_DSP2_RX_EQ_GAINS_2, 11, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("DSP2 EQ B5 Volume", WM8915_DSP2_RX_EQ_GAINS_2, 6, 31, 0,
+ eq_tlv),
+};
+
+static int cp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ msleep(5);
+ break;
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rmv_short_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(w->codec);
+
+ /* Record which outputs we enabled */
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMD:
+ wm8915->hpout_pending &= ~w->shift;
+ break;
+ case SND_SOC_DAPM_PRE_PMU:
+ wm8915->hpout_pending |= w->shift;
+ break;
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void wait_for_dc_servo(struct snd_soc_codec *codec, u16 mask)
+{
+ struct i2c_client *i2c = to_i2c_client(codec->dev);
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ int i, ret;
+ unsigned long timeout = 200;
+
+ snd_soc_write(codec, WM8915_DC_SERVO_2, mask);
+
+ /* Use the interrupt if possible */
+ do {
+ if (i2c->irq) {
+ timeout = wait_for_completion_timeout(&wm8915->dcs_done,
+ msecs_to_jiffies(200));
+ if (timeout == 0)
+ dev_err(codec->dev, "DC servo timed out\n");
+
+ } else {
+ msleep(1);
+ if (--i) {
+ timeout = 0;
+ break;
+ }
+ }
+
+ ret = snd_soc_read(codec, WM8915_DC_SERVO_2);
+ dev_dbg(codec->dev, "DC servo state: %x\n", ret);
+ } while (ret & mask);
+
+ if (timeout == 0)
+ dev_err(codec->dev, "DC servo timed out for %x\n", mask);
+ else
+ dev_dbg(codec->dev, "DC servo complete for %x\n", mask);
+}
+
+static void wm8915_seq_notifier(struct snd_soc_dapm_context *dapm,
+ enum snd_soc_dapm_type event, int subseq)
+{
+ struct snd_soc_codec *codec = container_of(dapm,
+ struct snd_soc_codec, dapm);
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ u16 val, mask;
+
+ /* Complete any pending DC servo starts */
+ if (wm8915->dcs_pending) {
+ dev_dbg(codec->dev, "Starting DC servo for %x\n",
+ wm8915->dcs_pending);
+
+ /* Trigger a startup sequence */
+ wait_for_dc_servo(codec, wm8915->dcs_pending
+ << WM8915_DCS_TRIG_STARTUP_0_SHIFT);
+
+ wm8915->dcs_pending = 0;
+ }
+
+ if (wm8915->hpout_pending != wm8915->hpout_ena) {
+ dev_dbg(codec->dev, "Applying RMV_SHORTs %x->%x\n",
+ wm8915->hpout_ena, wm8915->hpout_pending);
+
+ val = 0;
+ mask = 0;
+ if (wm8915->hpout_pending & HPOUT1L) {
+ val |= WM8915_HPOUT1L_RMV_SHORT;
+ mask |= WM8915_HPOUT1L_RMV_SHORT;
+ } else {
+ mask |= WM8915_HPOUT1L_RMV_SHORT |
+ WM8915_HPOUT1L_OUTP |
+ WM8915_HPOUT1L_DLY;
+ }
+
+ if (wm8915->hpout_pending & HPOUT1R) {
+ val |= WM8915_HPOUT1R_RMV_SHORT;
+ mask |= WM8915_HPOUT1R_RMV_SHORT;
+ } else {
+ mask |= WM8915_HPOUT1R_RMV_SHORT |
+ WM8915_HPOUT1R_OUTP |
+ WM8915_HPOUT1R_DLY;
+ }
+
+ snd_soc_update_bits(codec, WM8915_ANALOGUE_HP_1, mask, val);
+
+ val = 0;
+ mask = 0;
+ if (wm8915->hpout_pending & HPOUT2L) {
+ val |= WM8915_HPOUT2L_RMV_SHORT;
+ mask |= WM8915_HPOUT2L_RMV_SHORT;
+ } else {
+ mask |= WM8915_HPOUT2L_RMV_SHORT |
+ WM8915_HPOUT2L_OUTP |
+ WM8915_HPOUT2L_DLY;
+ }
+
+ if (wm8915->hpout_pending & HPOUT2R) {
+ val |= WM8915_HPOUT2R_RMV_SHORT;
+ mask |= WM8915_HPOUT2R_RMV_SHORT;
+ } else {
+ mask |= WM8915_HPOUT2R_RMV_SHORT |
+ WM8915_HPOUT2R_OUTP |
+ WM8915_HPOUT2R_DLY;
+ }
+
+ snd_soc_update_bits(codec, WM8915_ANALOGUE_HP_2, mask, val);
+
+ wm8915->hpout_ena = wm8915->hpout_pending;
+ }
+}
+
+static int dcs_start(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(w->codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ wm8915->dcs_pending |= 1 << w->shift;
+ break;
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const char *sidetone_text[] = {
+ "IN1", "IN2",
+};
+
+static const struct soc_enum left_sidetone_enum =
+ SOC_ENUM_SINGLE(WM8915_SIDETONE, 0, 2, sidetone_text);
+
+static const struct snd_kcontrol_new left_sidetone =
+ SOC_DAPM_ENUM("Left Sidetone", left_sidetone_enum);
+
+static const struct soc_enum right_sidetone_enum =
+ SOC_ENUM_SINGLE(WM8915_SIDETONE, 1, 2, sidetone_text);
+
+static const struct snd_kcontrol_new right_sidetone =
+ SOC_DAPM_ENUM("Right Sidetone", right_sidetone_enum);
+
+static const char *spk_text[] = {
+ "DAC1L", "DAC1R", "DAC2L", "DAC2R"
+};
+
+static const struct soc_enum spkl_enum =
+ SOC_ENUM_SINGLE(WM8915_LEFT_PDM_SPEAKER, 0, 4, spk_text);
+
+static const struct snd_kcontrol_new spkl_mux =
+ SOC_DAPM_ENUM("SPKL", spkl_enum);
+
+static const struct soc_enum spkr_enum =
+ SOC_ENUM_SINGLE(WM8915_RIGHT_PDM_SPEAKER, 0, 4, spk_text);
+
+static const struct snd_kcontrol_new spkr_mux =
+ SOC_DAPM_ENUM("SPKR", spkr_enum);
+
+static const char *dsp1rx_text[] = {
+ "AIF1", "AIF2"
+};
+
+static const struct soc_enum dsp1rx_enum =
+ SOC_ENUM_SINGLE(WM8915_POWER_MANAGEMENT_8, 0, 2, dsp1rx_text);
+
+static const struct snd_kcontrol_new dsp1rx =
+ SOC_DAPM_ENUM("DSP1RX", dsp1rx_enum);
+
+static const char *dsp2rx_text[] = {
+ "AIF2", "AIF1"
+};
+
+static const struct soc_enum dsp2rx_enum =
+ SOC_ENUM_SINGLE(WM8915_POWER_MANAGEMENT_8, 4, 2, dsp2rx_text);
+
+static const struct snd_kcontrol_new dsp2rx =
+ SOC_DAPM_ENUM("DSP2RX", dsp2rx_enum);
+
+static const char *aif2tx_text[] = {
+ "DSP2", "DSP1", "AIF1"
+};
+
+static const struct soc_enum aif2tx_enum =
+ SOC_ENUM_SINGLE(WM8915_POWER_MANAGEMENT_8, 6, 3, aif2tx_text);
+
+static const struct snd_kcontrol_new aif2tx =
+ SOC_DAPM_ENUM("AIF2TX", aif2tx_enum);
+
+static const char *inmux_text[] = {
+ "ADC", "DMIC1", "DMIC2"
+};
+
+static const struct soc_enum in1_enum =
+ SOC_ENUM_SINGLE(WM8915_POWER_MANAGEMENT_7, 0, 3, inmux_text);
+
+static const struct snd_kcontrol_new in1_mux =
+ SOC_DAPM_ENUM("IN1 Mux", in1_enum);
+
+static const struct soc_enum in2_enum =
+ SOC_ENUM_SINGLE(WM8915_POWER_MANAGEMENT_7, 4, 3, inmux_text);
+
+static const struct snd_kcontrol_new in2_mux =
+ SOC_DAPM_ENUM("IN2 Mux", in2_enum);
+
+static const struct snd_kcontrol_new dac2r_mix[] = {
+SOC_DAPM_SINGLE("Right Sidetone Switch", WM8915_DAC2_RIGHT_MIXER_ROUTING,
+ 5, 1, 0),
+SOC_DAPM_SINGLE("Left Sidetone Switch", WM8915_DAC2_RIGHT_MIXER_ROUTING,
+ 4, 1, 0),
+SOC_DAPM_SINGLE("DSP2 Switch", WM8915_DAC2_RIGHT_MIXER_ROUTING, 1, 1, 0),
+SOC_DAPM_SINGLE("DSP1 Switch", WM8915_DAC2_RIGHT_MIXER_ROUTING, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dac2l_mix[] = {
+SOC_DAPM_SINGLE("Right Sidetone Switch", WM8915_DAC2_LEFT_MIXER_ROUTING,
+ 5, 1, 0),
+SOC_DAPM_SINGLE("Left Sidetone Switch", WM8915_DAC2_LEFT_MIXER_ROUTING,
+ 4, 1, 0),
+SOC_DAPM_SINGLE("DSP2 Switch", WM8915_DAC2_LEFT_MIXER_ROUTING, 1, 1, 0),
+SOC_DAPM_SINGLE("DSP1 Switch", WM8915_DAC2_LEFT_MIXER_ROUTING, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dac1r_mix[] = {
+SOC_DAPM_SINGLE("Right Sidetone Switch", WM8915_DAC1_RIGHT_MIXER_ROUTING,
+ 5, 1, 0),
+SOC_DAPM_SINGLE("Left Sidetone Switch", WM8915_DAC1_RIGHT_MIXER_ROUTING,
+ 4, 1, 0),
+SOC_DAPM_SINGLE("DSP2 Switch", WM8915_DAC1_RIGHT_MIXER_ROUTING, 1, 1, 0),
+SOC_DAPM_SINGLE("DSP1 Switch", WM8915_DAC1_RIGHT_MIXER_ROUTING, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dac1l_mix[] = {
+SOC_DAPM_SINGLE("Right Sidetone Switch", WM8915_DAC1_LEFT_MIXER_ROUTING,
+ 5, 1, 0),
+SOC_DAPM_SINGLE("Left Sidetone Switch", WM8915_DAC1_LEFT_MIXER_ROUTING,
+ 4, 1, 0),
+SOC_DAPM_SINGLE("DSP2 Switch", WM8915_DAC1_LEFT_MIXER_ROUTING, 1, 1, 0),
+SOC_DAPM_SINGLE("DSP1 Switch", WM8915_DAC1_LEFT_MIXER_ROUTING, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dsp1txl[] = {
+SOC_DAPM_SINGLE("IN1 Switch", WM8915_DSP1_TX_LEFT_MIXER_ROUTING,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8915_DSP1_TX_LEFT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dsp1txr[] = {
+SOC_DAPM_SINGLE("IN1 Switch", WM8915_DSP1_TX_RIGHT_MIXER_ROUTING,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8915_DSP1_TX_RIGHT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dsp2txl[] = {
+SOC_DAPM_SINGLE("IN1 Switch", WM8915_DSP2_TX_LEFT_MIXER_ROUTING,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8915_DSP2_TX_LEFT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dsp2txr[] = {
+SOC_DAPM_SINGLE("IN1 Switch", WM8915_DSP2_TX_RIGHT_MIXER_ROUTING,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8915_DSP2_TX_RIGHT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+
+static const struct snd_soc_dapm_widget wm8915_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1LN"),
+SND_SOC_DAPM_INPUT("IN1LP"),
+SND_SOC_DAPM_INPUT("IN1RN"),
+SND_SOC_DAPM_INPUT("IN1RP"),
+
+SND_SOC_DAPM_INPUT("IN2LN"),
+SND_SOC_DAPM_INPUT("IN2LP"),
+SND_SOC_DAPM_INPUT("IN2RN"),
+SND_SOC_DAPM_INPUT("IN2RP"),
+
+SND_SOC_DAPM_INPUT("DMIC1DAT"),
+SND_SOC_DAPM_INPUT("DMIC2DAT"),
+
+SND_SOC_DAPM_SUPPLY_S("SYSCLK", 1, WM8915_AIF_CLOCKING_1, 0, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY_S("SYSDSPCLK", 2, WM8915_CLOCKING_1, 1, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY_S("AIFCLK", 2, WM8915_CLOCKING_1, 2, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY_S("Charge Pump", 2, WM8915_CHARGE_PUMP_1, 15, 0, cp_event,
+ SND_SOC_DAPM_POST_PMU),
+
+SND_SOC_DAPM_SUPPLY("LDO2", WM8915_POWER_MANAGEMENT_2, 1, 0, NULL, 0),
+SND_SOC_DAPM_MICBIAS("MICB2", WM8915_POWER_MANAGEMENT_1, 9, 0),
+SND_SOC_DAPM_MICBIAS("MICB1", WM8915_POWER_MANAGEMENT_1, 8, 0),
+
+SND_SOC_DAPM_PGA("IN1L PGA", WM8915_POWER_MANAGEMENT_2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("IN1R PGA", WM8915_POWER_MANAGEMENT_2, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_MUX("IN1L Mux", SND_SOC_NOPM, 0, 0, &in1_mux),
+SND_SOC_DAPM_MUX("IN1R Mux", SND_SOC_NOPM, 0, 0, &in1_mux),
+SND_SOC_DAPM_MUX("IN2L Mux", SND_SOC_NOPM, 0, 0, &in2_mux),
+SND_SOC_DAPM_MUX("IN2R Mux", SND_SOC_NOPM, 0, 0, &in2_mux),
+
+SND_SOC_DAPM_PGA("IN1L", WM8915_POWER_MANAGEMENT_7, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("IN1R", WM8915_POWER_MANAGEMENT_7, 3, 0, NULL, 0),
+SND_SOC_DAPM_PGA("IN2L", WM8915_POWER_MANAGEMENT_7, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("IN2R", WM8915_POWER_MANAGEMENT_7, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("DMIC2", WM8915_POWER_MANAGEMENT_7, 9, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("DMIC1", WM8915_POWER_MANAGEMENT_7, 8, 0, NULL, 0),
+
+SND_SOC_DAPM_ADC("DMIC2L", NULL, WM8915_POWER_MANAGEMENT_3, 5, 0),
+SND_SOC_DAPM_ADC("DMIC2R", NULL, WM8915_POWER_MANAGEMENT_3, 4, 0),
+SND_SOC_DAPM_ADC("DMIC1L", NULL, WM8915_POWER_MANAGEMENT_3, 3, 0),
+SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8915_POWER_MANAGEMENT_3, 2, 0),
+
+SND_SOC_DAPM_ADC("ADCL", NULL, WM8915_POWER_MANAGEMENT_3, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, WM8915_POWER_MANAGEMENT_3, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &left_sidetone),
+SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &right_sidetone),
+
+SND_SOC_DAPM_AIF_IN("DSP2RXL", NULL, 0, WM8915_POWER_MANAGEMENT_3, 11, 0),
+SND_SOC_DAPM_AIF_IN("DSP2RXR", NULL, 1, WM8915_POWER_MANAGEMENT_3, 10, 0),
+SND_SOC_DAPM_AIF_IN("DSP1RXL", NULL, 0, WM8915_POWER_MANAGEMENT_3, 9, 0),
+SND_SOC_DAPM_AIF_IN("DSP1RXR", NULL, 1, WM8915_POWER_MANAGEMENT_3, 8, 0),
+
+SND_SOC_DAPM_MIXER("DSP2TXL", WM8915_POWER_MANAGEMENT_5, 11, 0,
+ dsp2txl, ARRAY_SIZE(dsp2txl)),
+SND_SOC_DAPM_MIXER("DSP2TXR", WM8915_POWER_MANAGEMENT_5, 10, 0,
+ dsp2txr, ARRAY_SIZE(dsp2txr)),
+SND_SOC_DAPM_MIXER("DSP1TXL", WM8915_POWER_MANAGEMENT_5, 9, 0,
+ dsp1txl, ARRAY_SIZE(dsp1txl)),
+SND_SOC_DAPM_MIXER("DSP1TXR", WM8915_POWER_MANAGEMENT_5, 8, 0,
+ dsp1txr, ARRAY_SIZE(dsp1txr)),
+
+SND_SOC_DAPM_MIXER("DAC2L Mixer", SND_SOC_NOPM, 0, 0,
+ dac2l_mix, ARRAY_SIZE(dac2l_mix)),
+SND_SOC_DAPM_MIXER("DAC2R Mixer", SND_SOC_NOPM, 0, 0,
+ dac2r_mix, ARRAY_SIZE(dac2r_mix)),
+SND_SOC_DAPM_MIXER("DAC1L Mixer", SND_SOC_NOPM, 0, 0,
+ dac1l_mix, ARRAY_SIZE(dac1l_mix)),
+SND_SOC_DAPM_MIXER("DAC1R Mixer", SND_SOC_NOPM, 0, 0,
+ dac1r_mix, ARRAY_SIZE(dac1r_mix)),
+
+SND_SOC_DAPM_DAC("DAC2L", NULL, WM8915_POWER_MANAGEMENT_5, 3, 0),
+SND_SOC_DAPM_DAC("DAC2R", NULL, WM8915_POWER_MANAGEMENT_5, 2, 0),
+SND_SOC_DAPM_DAC("DAC1L", NULL, WM8915_POWER_MANAGEMENT_5, 1, 0),
+SND_SOC_DAPM_DAC("DAC1R", NULL, WM8915_POWER_MANAGEMENT_5, 0, 0),
+
+SND_SOC_DAPM_AIF_IN("AIF2RX1", "AIF2 Playback", 1,
+ WM8915_POWER_MANAGEMENT_4, 9, 0),
+SND_SOC_DAPM_AIF_IN("AIF2RX0", "AIF2 Playback", 2,
+ WM8915_POWER_MANAGEMENT_4, 8, 0),
+
+SND_SOC_DAPM_AIF_IN("AIF2TX1", "AIF2 Capture", 1,
+ WM8915_POWER_MANAGEMENT_6, 9, 0),
+SND_SOC_DAPM_AIF_IN("AIF2TX0", "AIF2 Capture", 2,
+ WM8915_POWER_MANAGEMENT_6, 8, 0),
+
+SND_SOC_DAPM_AIF_IN("AIF1RX5", "AIF1 Playback", 5,
+ WM8915_POWER_MANAGEMENT_4, 5, 0),
+SND_SOC_DAPM_AIF_IN("AIF1RX4", "AIF1 Playback", 4,
+ WM8915_POWER_MANAGEMENT_4, 4, 0),
+SND_SOC_DAPM_AIF_IN("AIF1RX3", "AIF1 Playback", 3,
+ WM8915_POWER_MANAGEMENT_4, 3, 0),
+SND_SOC_DAPM_AIF_IN("AIF1RX2", "AIF1 Playback", 2,
+ WM8915_POWER_MANAGEMENT_4, 2, 0),
+SND_SOC_DAPM_AIF_IN("AIF1RX1", "AIF1 Playback", 1,
+ WM8915_POWER_MANAGEMENT_4, 1, 0),
+SND_SOC_DAPM_AIF_IN("AIF1RX0", "AIF1 Playback", 0,
+ WM8915_POWER_MANAGEMENT_4, 0, 0),
+
+SND_SOC_DAPM_AIF_OUT("AIF1TX5", "AIF1 Capture", 5,
+ WM8915_POWER_MANAGEMENT_6, 5, 0),
+SND_SOC_DAPM_AIF_OUT("AIF1TX4", "AIF1 Capture", 4,
+ WM8915_POWER_MANAGEMENT_6, 4, 0),
+SND_SOC_DAPM_AIF_OUT("AIF1TX3", "AIF1 Capture", 3,
+ WM8915_POWER_MANAGEMENT_6, 3, 0),
+SND_SOC_DAPM_AIF_OUT("AIF1TX2", "AIF1 Capture", 2,
+ WM8915_POWER_MANAGEMENT_6, 2, 0),
+SND_SOC_DAPM_AIF_OUT("AIF1TX1", "AIF1 Capture", 1,
+ WM8915_POWER_MANAGEMENT_6, 1, 0),
+SND_SOC_DAPM_AIF_OUT("AIF1TX0", "AIF1 Capture", 0,
+ WM8915_POWER_MANAGEMENT_6, 0, 0),
+
+/* We route as stereo pairs so define some dummy widgets to squash
+ * things down for now. RXA = 0,1, RXB = 2,3 and so on */
+SND_SOC_DAPM_PGA("AIF1RXA", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_PGA("AIF1RXB", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_PGA("AIF1RXC", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_PGA("AIF2RX", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_PGA("DSP2TX", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_MUX("DSP1RX", SND_SOC_NOPM, 0, 0, &dsp1rx),
+SND_SOC_DAPM_MUX("DSP2RX", SND_SOC_NOPM, 0, 0, &dsp2rx),
+SND_SOC_DAPM_MUX("AIF2TX", SND_SOC_NOPM, 0, 0, &aif2tx),
+
+SND_SOC_DAPM_MUX("SPKL", SND_SOC_NOPM, 0, 0, &spkl_mux),
+SND_SOC_DAPM_MUX("SPKR", SND_SOC_NOPM, 0, 0, &spkr_mux),
+SND_SOC_DAPM_PGA("SPKL PGA", WM8915_LEFT_PDM_SPEAKER, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SPKR PGA", WM8915_RIGHT_PDM_SPEAKER, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_S("HPOUT2L PGA", 0, WM8915_POWER_MANAGEMENT_1, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT2L_DLY", 1, WM8915_ANALOGUE_HP_2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT2L_DCS", 2, WM8915_DC_SERVO_1, 2, 0, dcs_start,
+ SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_PGA_S("HPOUT2L_OUTP", 3, WM8915_ANALOGUE_HP_2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT2L_RMV_SHORT", 3, SND_SOC_NOPM, HPOUT2L, 0,
+ rmv_short_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_PGA_S("HPOUT2R PGA", 0, WM8915_POWER_MANAGEMENT_1, 6, 0,NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT2R_DLY", 1, WM8915_ANALOGUE_HP_2, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT2R_DCS", 2, WM8915_DC_SERVO_1, 3, 0, dcs_start,
+ SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_PGA_S("HPOUT2R_OUTP", 3, WM8915_ANALOGUE_HP_2, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT2R_RMV_SHORT", 3, SND_SOC_NOPM, HPOUT2R, 0,
+ rmv_short_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_PGA_S("HPOUT1L PGA", 0, WM8915_POWER_MANAGEMENT_1, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT1L_DLY", 1, WM8915_ANALOGUE_HP_1, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT1L_DCS", 2, WM8915_DC_SERVO_1, 0, 0, dcs_start,
+ SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_PGA_S("HPOUT1L_OUTP", 3, WM8915_ANALOGUE_HP_1, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT1L_RMV_SHORT", 3, SND_SOC_NOPM, HPOUT1L, 0,
+ rmv_short_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_PGA_S("HPOUT1R PGA", 0, WM8915_POWER_MANAGEMENT_1, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT1R_DLY", 1, WM8915_ANALOGUE_HP_1, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT1R_DCS", 2, WM8915_DC_SERVO_1, 1, 0, dcs_start,
+ SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_PGA_S("HPOUT1R_OUTP", 3, WM8915_ANALOGUE_HP_1, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPOUT1R_RMV_SHORT", 3, SND_SOC_NOPM, HPOUT1R, 0,
+ rmv_short_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_OUTPUT("HPOUT1L"),
+SND_SOC_DAPM_OUTPUT("HPOUT1R"),
+SND_SOC_DAPM_OUTPUT("HPOUT2L"),
+SND_SOC_DAPM_OUTPUT("HPOUT2R"),
+SND_SOC_DAPM_OUTPUT("SPKDAT"),
+};
+
+static const struct snd_soc_dapm_route wm8915_dapm_routes[] = {
+ { "AIFCLK", NULL, "SYSCLK" },
+ { "SYSDSPCLK", NULL, "SYSCLK" },
+ { "Charge Pump", NULL, "SYSCLK" },
+
+ { "MICB1", NULL, "LDO2" },
+ { "MICB2", NULL, "LDO2" },
+
+ { "IN1L PGA", NULL, "IN2LN" },
+ { "IN1L PGA", NULL, "IN2LP" },
+ { "IN1L PGA", NULL, "IN1LN" },
+ { "IN1L PGA", NULL, "IN1LP" },
+
+ { "IN1R PGA", NULL, "IN2RN" },
+ { "IN1R PGA", NULL, "IN2RP" },
+ { "IN1R PGA", NULL, "IN1RN" },
+ { "IN1R PGA", NULL, "IN1RP" },
+
+ { "ADCL", NULL, "IN1L PGA" },
+
+ { "ADCR", NULL, "IN1R PGA" },
+
+ { "DMIC1L", NULL, "DMIC1DAT" },
+ { "DMIC1R", NULL, "DMIC1DAT" },
+ { "DMIC2L", NULL, "DMIC2DAT" },
+ { "DMIC2R", NULL, "DMIC2DAT" },
+
+ { "DMIC2L", NULL, "DMIC2" },
+ { "DMIC2R", NULL, "DMIC2" },
+ { "DMIC1L", NULL, "DMIC1" },
+ { "DMIC1R", NULL, "DMIC1" },
+
+ { "IN1L Mux", "ADC", "ADCL" },
+ { "IN1L Mux", "DMIC1", "DMIC1L" },
+ { "IN1L Mux", "DMIC2", "DMIC2L" },
+
+ { "IN1R Mux", "ADC", "ADCR" },
+ { "IN1R Mux", "DMIC1", "DMIC1R" },
+ { "IN1R Mux", "DMIC2", "DMIC2R" },
+
+ { "IN2L Mux", "ADC", "ADCL" },
+ { "IN2L Mux", "DMIC1", "DMIC1L" },
+ { "IN2L Mux", "DMIC2", "DMIC2L" },
+
+ { "IN2R Mux", "ADC", "ADCR" },
+ { "IN2R Mux", "DMIC1", "DMIC1R" },
+ { "IN2R Mux", "DMIC2", "DMIC2R" },
+
+ { "Left Sidetone", "IN1", "IN1L Mux" },
+ { "Left Sidetone", "IN2", "IN2L Mux" },
+
+ { "Right Sidetone", "IN1", "IN1R Mux" },
+ { "Right Sidetone", "IN2", "IN2R Mux" },
+
+ { "DSP1TXL", "IN1 Switch", "IN1L Mux" },
+ { "DSP1TXR", "IN1 Switch", "IN1R Mux" },
+
+ { "DSP2TXL", "IN1 Switch", "IN2L Mux" },
+ { "DSP2TXR", "IN1 Switch", "IN2R Mux" },
+
+ { "AIF1TX0", NULL, "DSP1TXL" },
+ { "AIF1TX1", NULL, "DSP1TXR" },
+ { "AIF1TX2", NULL, "DSP2TXL" },
+ { "AIF1TX3", NULL, "DSP2TXR" },
+ { "AIF1TX4", NULL, "AIF2RX0" },
+ { "AIF1TX5", NULL, "AIF2RX1" },
+
+ { "AIF1RX0", NULL, "AIFCLK" },
+ { "AIF1RX1", NULL, "AIFCLK" },
+ { "AIF1RX2", NULL, "AIFCLK" },
+ { "AIF1RX3", NULL, "AIFCLK" },
+ { "AIF1RX4", NULL, "AIFCLK" },
+ { "AIF1RX5", NULL, "AIFCLK" },
+
+ { "AIF2RX0", NULL, "AIFCLK" },
+ { "AIF2RX1", NULL, "AIFCLK" },
+
+ { "DSP1RXL", NULL, "SYSDSPCLK" },
+ { "DSP1RXR", NULL, "SYSDSPCLK" },
+ { "DSP2RXL", NULL, "SYSDSPCLK" },
+ { "DSP2RXR", NULL, "SYSDSPCLK" },
+ { "DSP1TXL", NULL, "SYSDSPCLK" },
+ { "DSP1TXR", NULL, "SYSDSPCLK" },
+ { "DSP2TXL", NULL, "SYSDSPCLK" },
+ { "DSP2TXR", NULL, "SYSDSPCLK" },
+
+ { "AIF1RXA", NULL, "AIF1RX0" },
+ { "AIF1RXA", NULL, "AIF1RX1" },
+ { "AIF1RXB", NULL, "AIF1RX2" },
+ { "AIF1RXB", NULL, "AIF1RX3" },
+ { "AIF1RXC", NULL, "AIF1RX4" },
+ { "AIF1RXC", NULL, "AIF1RX5" },
+
+ { "AIF2RX", NULL, "AIF2RX0" },
+ { "AIF2RX", NULL, "AIF2RX1" },
+
+ { "AIF2TX", "DSP2", "DSP2TX" },
+ { "AIF2TX", "DSP1", "DSP1RX" },
+ { "AIF2TX", "AIF1", "AIF1RXC" },
+
+ { "DSP1RXL", NULL, "DSP1RX" },
+ { "DSP1RXR", NULL, "DSP1RX" },
+ { "DSP2RXL", NULL, "DSP2RX" },
+ { "DSP2RXR", NULL, "DSP2RX" },
+
+ { "DSP2TX", NULL, "DSP2TXL" },
+ { "DSP2TX", NULL, "DSP2TXR" },
+
+ { "DSP1RX", "AIF1", "AIF1RXA" },
+ { "DSP1RX", "AIF2", "AIF2RX" },
+
+ { "DSP2RX", "AIF1", "AIF1RXB" },
+ { "DSP2RX", "AIF2", "AIF2RX" },
+
+ { "DAC2L Mixer", "DSP2 Switch", "DSP2RXL" },
+ { "DAC2L Mixer", "DSP1 Switch", "DSP1RXL" },
+ { "DAC2L Mixer", "Right Sidetone Switch", "Right Sidetone" },
+ { "DAC2L Mixer", "Left Sidetone Switch", "Left Sidetone" },
+
+ { "DAC2R Mixer", "DSP2 Switch", "DSP2RXR" },
+ { "DAC2R Mixer", "DSP1 Switch", "DSP1RXR" },
+ { "DAC2R Mixer", "Right Sidetone Switch", "Right Sidetone" },
+ { "DAC2R Mixer", "Left Sidetone Switch", "Left Sidetone" },
+
+ { "DAC1L Mixer", "DSP2 Switch", "DSP2RXL" },
+ { "DAC1L Mixer", "DSP1 Switch", "DSP1RXL" },
+ { "DAC1L Mixer", "Right Sidetone Switch", "Right Sidetone" },
+ { "DAC1L Mixer", "Left Sidetone Switch", "Left Sidetone" },
+
+ { "DAC1R Mixer", "DSP2 Switch", "DSP2RXR" },
+ { "DAC1R Mixer", "DSP1 Switch", "DSP1RXR" },
+ { "DAC1R Mixer", "Right Sidetone Switch", "Right Sidetone" },
+ { "DAC1R Mixer", "Left Sidetone Switch", "Left Sidetone" },
+
+ { "DAC1L", NULL, "DAC1L Mixer" },
+ { "DAC1R", NULL, "DAC1R Mixer" },
+ { "DAC2L", NULL, "DAC2L Mixer" },
+ { "DAC2R", NULL, "DAC2R Mixer" },
+
+ { "HPOUT2L PGA", NULL, "Charge Pump" },
+ { "HPOUT2L PGA", NULL, "DAC2L" },
+ { "HPOUT2L_DLY", NULL, "HPOUT2L PGA" },
+ { "HPOUT2L_DCS", NULL, "HPOUT2L_DLY" },
+ { "HPOUT2L_OUTP", NULL, "HPOUT2L_DCS" },
+ { "HPOUT2L_RMV_SHORT", NULL, "HPOUT2L_OUTP" },
+
+ { "HPOUT2R PGA", NULL, "Charge Pump" },
+ { "HPOUT2R PGA", NULL, "DAC2R" },
+ { "HPOUT2R_DLY", NULL, "HPOUT2R PGA" },
+ { "HPOUT2R_DCS", NULL, "HPOUT2R_DLY" },
+ { "HPOUT2R_OUTP", NULL, "HPOUT2R_DCS" },
+ { "HPOUT2R_RMV_SHORT", NULL, "HPOUT2R_OUTP" },
+
+ { "HPOUT1L PGA", NULL, "Charge Pump" },
+ { "HPOUT1L PGA", NULL, "DAC1L" },
+ { "HPOUT1L_DLY", NULL, "HPOUT1L PGA" },
+ { "HPOUT1L_DCS", NULL, "HPOUT1L_DLY" },
+ { "HPOUT1L_OUTP", NULL, "HPOUT1L_DCS" },
+ { "HPOUT1L_RMV_SHORT", NULL, "HPOUT1L_OUTP" },
+
+ { "HPOUT1R PGA", NULL, "Charge Pump" },
+ { "HPOUT1R PGA", NULL, "DAC1R" },
+ { "HPOUT1R_DLY", NULL, "HPOUT1R PGA" },
+ { "HPOUT1R_DCS", NULL, "HPOUT1R_DLY" },
+ { "HPOUT1R_OUTP", NULL, "HPOUT1R_DCS" },
+ { "HPOUT1R_RMV_SHORT", NULL, "HPOUT1R_OUTP" },
+
+ { "HPOUT2L", NULL, "HPOUT2L_RMV_SHORT" },
+ { "HPOUT2R", NULL, "HPOUT2R_RMV_SHORT" },
+ { "HPOUT1L", NULL, "HPOUT1L_RMV_SHORT" },
+ { "HPOUT1R", NULL, "HPOUT1R_RMV_SHORT" },
+
+ { "SPKL", "DAC1L", "DAC1L" },
+ { "SPKL", "DAC1R", "DAC1R" },
+ { "SPKL", "DAC2L", "DAC2L" },
+ { "SPKL", "DAC2R", "DAC2R" },
+
+ { "SPKR", "DAC1L", "DAC1L" },
+ { "SPKR", "DAC1R", "DAC1R" },
+ { "SPKR", "DAC2L", "DAC2L" },
+ { "SPKR", "DAC2R", "DAC2R" },
+
+ { "SPKL PGA", NULL, "SPKL" },
+ { "SPKR PGA", NULL, "SPKR" },
+
+ { "SPKDAT", NULL, "SPKL PGA" },
+ { "SPKDAT", NULL, "SPKR PGA" },
+};
+
+static int wm8915_readable_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ /* Due to the sparseness of the register map the compiler
+ * output from an explicit switch statement ends up being much
+ * more efficient than a table.
+ */
+ switch (reg) {
+ case WM8915_SOFTWARE_RESET:
+ case WM8915_POWER_MANAGEMENT_1:
+ case WM8915_POWER_MANAGEMENT_2:
+ case WM8915_POWER_MANAGEMENT_3:
+ case WM8915_POWER_MANAGEMENT_4:
+ case WM8915_POWER_MANAGEMENT_5:
+ case WM8915_POWER_MANAGEMENT_6:
+ case WM8915_POWER_MANAGEMENT_7:
+ case WM8915_POWER_MANAGEMENT_8:
+ case WM8915_LEFT_LINE_INPUT_VOLUME:
+ case WM8915_RIGHT_LINE_INPUT_VOLUME:
+ case WM8915_LINE_INPUT_CONTROL:
+ case WM8915_DAC1_HPOUT1_VOLUME:
+ case WM8915_DAC2_HPOUT2_VOLUME:
+ case WM8915_DAC1_LEFT_VOLUME:
+ case WM8915_DAC1_RIGHT_VOLUME:
+ case WM8915_DAC2_LEFT_VOLUME:
+ case WM8915_DAC2_RIGHT_VOLUME:
+ case WM8915_OUTPUT1_LEFT_VOLUME:
+ case WM8915_OUTPUT1_RIGHT_VOLUME:
+ case WM8915_OUTPUT2_LEFT_VOLUME:
+ case WM8915_OUTPUT2_RIGHT_VOLUME:
+ case WM8915_MICBIAS_1:
+ case WM8915_MICBIAS_2:
+ case WM8915_LDO_1:
+ case WM8915_LDO_2:
+ case WM8915_ACCESSORY_DETECT_MODE_1:
+ case WM8915_ACCESSORY_DETECT_MODE_2:
+ case WM8915_HEADPHONE_DETECT_1:
+ case WM8915_HEADPHONE_DETECT_2:
+ case WM8915_MIC_DETECT_1:
+ case WM8915_MIC_DETECT_2:
+ case WM8915_MIC_DETECT_3:
+ case WM8915_CHARGE_PUMP_1:
+ case WM8915_CHARGE_PUMP_2:
+ case WM8915_DC_SERVO_1:
+ case WM8915_DC_SERVO_2:
+ case WM8915_DC_SERVO_3:
+ case WM8915_DC_SERVO_5:
+ case WM8915_DC_SERVO_6:
+ case WM8915_DC_SERVO_7:
+ case WM8915_DC_SERVO_READBACK_0:
+ case WM8915_ANALOGUE_HP_1:
+ case WM8915_ANALOGUE_HP_2:
+ case WM8915_CHIP_REVISION:
+ case WM8915_CONTROL_INTERFACE_1:
+ case WM8915_WRITE_SEQUENCER_CTRL_1:
+ case WM8915_WRITE_SEQUENCER_CTRL_2:
+ case WM8915_AIF_CLOCKING_1:
+ case WM8915_AIF_CLOCKING_2:
+ case WM8915_CLOCKING_1:
+ case WM8915_CLOCKING_2:
+ case WM8915_AIF_RATE:
+ case WM8915_FLL_CONTROL_1:
+ case WM8915_FLL_CONTROL_2:
+ case WM8915_FLL_CONTROL_3:
+ case WM8915_FLL_CONTROL_4:
+ case WM8915_FLL_CONTROL_5:
+ case WM8915_FLL_CONTROL_6:
+ case WM8915_FLL_EFS_1:
+ case WM8915_FLL_EFS_2:
+ case WM8915_AIF1_CONTROL:
+ case WM8915_AIF1_BCLK:
+ case WM8915_AIF1_TX_LRCLK_1:
+ case WM8915_AIF1_TX_LRCLK_2:
+ case WM8915_AIF1_RX_LRCLK_1:
+ case WM8915_AIF1_RX_LRCLK_2:
+ case WM8915_AIF1TX_DATA_CONFIGURATION_1:
+ case WM8915_AIF1TX_DATA_CONFIGURATION_2:
+ case WM8915_AIF1RX_DATA_CONFIGURATION:
+ case WM8915_AIF1TX_CHANNEL_0_CONFIGURATION:
+ case WM8915_AIF1TX_CHANNEL_1_CONFIGURATION:
+ case WM8915_AIF1TX_CHANNEL_2_CONFIGURATION:
+ case WM8915_AIF1TX_CHANNEL_3_CONFIGURATION:
+ case WM8915_AIF1TX_CHANNEL_4_CONFIGURATION:
+ case WM8915_AIF1TX_CHANNEL_5_CONFIGURATION:
+ case WM8915_AIF1RX_CHANNEL_0_CONFIGURATION:
+ case WM8915_AIF1RX_CHANNEL_1_CONFIGURATION:
+ case WM8915_AIF1RX_CHANNEL_2_CONFIGURATION:
+ case WM8915_AIF1RX_CHANNEL_3_CONFIGURATION:
+ case WM8915_AIF1RX_CHANNEL_4_CONFIGURATION:
+ case WM8915_AIF1RX_CHANNEL_5_CONFIGURATION:
+ case WM8915_AIF1RX_MONO_CONFIGURATION:
+ case WM8915_AIF1TX_TEST:
+ case WM8915_AIF2_CONTROL:
+ case WM8915_AIF2_BCLK:
+ case WM8915_AIF2_TX_LRCLK_1:
+ case WM8915_AIF2_TX_LRCLK_2:
+ case WM8915_AIF2_RX_LRCLK_1:
+ case WM8915_AIF2_RX_LRCLK_2:
+ case WM8915_AIF2TX_DATA_CONFIGURATION_1:
+ case WM8915_AIF2TX_DATA_CONFIGURATION_2:
+ case WM8915_AIF2RX_DATA_CONFIGURATION:
+ case WM8915_AIF2TX_CHANNEL_0_CONFIGURATION:
+ case WM8915_AIF2TX_CHANNEL_1_CONFIGURATION:
+ case WM8915_AIF2RX_CHANNEL_0_CONFIGURATION:
+ case WM8915_AIF2RX_CHANNEL_1_CONFIGURATION:
+ case WM8915_AIF2RX_MONO_CONFIGURATION:
+ case WM8915_AIF2TX_TEST:
+ case WM8915_DSP1_TX_LEFT_VOLUME:
+ case WM8915_DSP1_TX_RIGHT_VOLUME:
+ case WM8915_DSP1_RX_LEFT_VOLUME:
+ case WM8915_DSP1_RX_RIGHT_VOLUME:
+ case WM8915_DSP1_TX_FILTERS:
+ case WM8915_DSP1_RX_FILTERS_1:
+ case WM8915_DSP1_RX_FILTERS_2:
+ case WM8915_DSP1_DRC_1:
+ case WM8915_DSP1_DRC_2:
+ case WM8915_DSP1_DRC_3:
+ case WM8915_DSP1_DRC_4:
+ case WM8915_DSP1_DRC_5:
+ case WM8915_DSP1_RX_EQ_GAINS_1:
+ case WM8915_DSP1_RX_EQ_GAINS_2:
+ case WM8915_DSP1_RX_EQ_BAND_1_A:
+ case WM8915_DSP1_RX_EQ_BAND_1_B:
+ case WM8915_DSP1_RX_EQ_BAND_1_PG:
+ case WM8915_DSP1_RX_EQ_BAND_2_A:
+ case WM8915_DSP1_RX_EQ_BAND_2_B:
+ case WM8915_DSP1_RX_EQ_BAND_2_C:
+ case WM8915_DSP1_RX_EQ_BAND_2_PG:
+ case WM8915_DSP1_RX_EQ_BAND_3_A:
+ case WM8915_DSP1_RX_EQ_BAND_3_B:
+ case WM8915_DSP1_RX_EQ_BAND_3_C:
+ case WM8915_DSP1_RX_EQ_BAND_3_PG:
+ case WM8915_DSP1_RX_EQ_BAND_4_A:
+ case WM8915_DSP1_RX_EQ_BAND_4_B:
+ case WM8915_DSP1_RX_EQ_BAND_4_C:
+ case WM8915_DSP1_RX_EQ_BAND_4_PG:
+ case WM8915_DSP1_RX_EQ_BAND_5_A:
+ case WM8915_DSP1_RX_EQ_BAND_5_B:
+ case WM8915_DSP1_RX_EQ_BAND_5_PG:
+ case WM8915_DSP2_TX_LEFT_VOLUME:
+ case WM8915_DSP2_TX_RIGHT_VOLUME:
+ case WM8915_DSP2_RX_LEFT_VOLUME:
+ case WM8915_DSP2_RX_RIGHT_VOLUME:
+ case WM8915_DSP2_TX_FILTERS:
+ case WM8915_DSP2_RX_FILTERS_1:
+ case WM8915_DSP2_RX_FILTERS_2:
+ case WM8915_DSP2_DRC_1:
+ case WM8915_DSP2_DRC_2:
+ case WM8915_DSP2_DRC_3:
+ case WM8915_DSP2_DRC_4:
+ case WM8915_DSP2_DRC_5:
+ case WM8915_DSP2_RX_EQ_GAINS_1:
+ case WM8915_DSP2_RX_EQ_GAINS_2:
+ case WM8915_DSP2_RX_EQ_BAND_1_A:
+ case WM8915_DSP2_RX_EQ_BAND_1_B:
+ case WM8915_DSP2_RX_EQ_BAND_1_PG:
+ case WM8915_DSP2_RX_EQ_BAND_2_A:
+ case WM8915_DSP2_RX_EQ_BAND_2_B:
+ case WM8915_DSP2_RX_EQ_BAND_2_C:
+ case WM8915_DSP2_RX_EQ_BAND_2_PG:
+ case WM8915_DSP2_RX_EQ_BAND_3_A:
+ case WM8915_DSP2_RX_EQ_BAND_3_B:
+ case WM8915_DSP2_RX_EQ_BAND_3_C:
+ case WM8915_DSP2_RX_EQ_BAND_3_PG:
+ case WM8915_DSP2_RX_EQ_BAND_4_A:
+ case WM8915_DSP2_RX_EQ_BAND_4_B:
+ case WM8915_DSP2_RX_EQ_BAND_4_C:
+ case WM8915_DSP2_RX_EQ_BAND_4_PG:
+ case WM8915_DSP2_RX_EQ_BAND_5_A:
+ case WM8915_DSP2_RX_EQ_BAND_5_B:
+ case WM8915_DSP2_RX_EQ_BAND_5_PG:
+ case WM8915_DAC1_MIXER_VOLUMES:
+ case WM8915_DAC1_LEFT_MIXER_ROUTING:
+ case WM8915_DAC1_RIGHT_MIXER_ROUTING:
+ case WM8915_DAC2_MIXER_VOLUMES:
+ case WM8915_DAC2_LEFT_MIXER_ROUTING:
+ case WM8915_DAC2_RIGHT_MIXER_ROUTING:
+ case WM8915_DSP1_TX_LEFT_MIXER_ROUTING:
+ case WM8915_DSP1_TX_RIGHT_MIXER_ROUTING:
+ case WM8915_DSP2_TX_LEFT_MIXER_ROUTING:
+ case WM8915_DSP2_TX_RIGHT_MIXER_ROUTING:
+ case WM8915_DSP_TX_MIXER_SELECT:
+ case WM8915_DAC_SOFTMUTE:
+ case WM8915_OVERSAMPLING:
+ case WM8915_SIDETONE:
+ case WM8915_GPIO_1:
+ case WM8915_GPIO_2:
+ case WM8915_GPIO_3:
+ case WM8915_GPIO_4:
+ case WM8915_GPIO_5:
+ case WM8915_PULL_CONTROL_1:
+ case WM8915_PULL_CONTROL_2:
+ case WM8915_INTERRUPT_STATUS_1:
+ case WM8915_INTERRUPT_STATUS_2:
+ case WM8915_INTERRUPT_RAW_STATUS_2:
+ case WM8915_INTERRUPT_STATUS_1_MASK:
+ case WM8915_INTERRUPT_STATUS_2_MASK:
+ case WM8915_INTERRUPT_CONTROL:
+ case WM8915_LEFT_PDM_SPEAKER:
+ case WM8915_RIGHT_PDM_SPEAKER:
+ case WM8915_PDM_SPEAKER_MUTE_SEQUENCE:
+ case WM8915_PDM_SPEAKER_VOLUME:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int wm8915_volatile_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ case WM8915_SOFTWARE_RESET:
+ case WM8915_CHIP_REVISION:
+ case WM8915_LDO_1:
+ case WM8915_LDO_2:
+ case WM8915_INTERRUPT_STATUS_1:
+ case WM8915_INTERRUPT_STATUS_2:
+ case WM8915_INTERRUPT_RAW_STATUS_2:
+ case WM8915_DC_SERVO_READBACK_0:
+ case WM8915_DC_SERVO_2:
+ case WM8915_DC_SERVO_6:
+ case WM8915_DC_SERVO_7:
+ case WM8915_FLL_CONTROL_6:
+ case WM8915_MIC_DETECT_3:
+ case WM8915_HEADPHONE_DETECT_1:
+ case WM8915_HEADPHONE_DETECT_2:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int wm8915_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8915_SOFTWARE_RESET, 0x8915);
+}
+
+static int wm8915_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) {
+ snd_soc_update_bits(codec, WM8915_POWER_MANAGEMENT_1,
+ WM8915_BG_ENA, WM8915_BG_ENA);
+ msleep(2);
+ }
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8915->supplies),
+ wm8915->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (wm8915->pdata.ldo_ena >= 0) {
+ gpio_set_value_cansleep(wm8915->pdata.ldo_ena,
+ 1);
+ msleep(5);
+ }
+
+ codec->cache_only = false;
+ snd_soc_cache_sync(codec);
+ }
+
+ snd_soc_update_bits(codec, WM8915_POWER_MANAGEMENT_1,
+ WM8915_BG_ENA, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ codec->cache_only = true;
+ if (wm8915->pdata.ldo_ena >= 0)
+ gpio_set_value_cansleep(wm8915->pdata.ldo_ena, 0);
+ regulator_bulk_disable(ARRAY_SIZE(wm8915->supplies),
+ wm8915->supplies);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static int wm8915_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int aifctrl = 0;
+ int bclk = 0;
+ int lrclk_tx = 0;
+ int lrclk_rx = 0;
+ int aifctrl_reg, bclk_reg, lrclk_tx_reg, lrclk_rx_reg;
+
+ switch (dai->id) {
+ case 0:
+ aifctrl_reg = WM8915_AIF1_CONTROL;
+ bclk_reg = WM8915_AIF1_BCLK;
+ lrclk_tx_reg = WM8915_AIF1_TX_LRCLK_2;
+ lrclk_rx_reg = WM8915_AIF1_RX_LRCLK_2;
+ break;
+ case 1:
+ aifctrl_reg = WM8915_AIF2_CONTROL;
+ bclk_reg = WM8915_AIF2_BCLK;
+ lrclk_tx_reg = WM8915_AIF2_TX_LRCLK_2;
+ lrclk_rx_reg = WM8915_AIF2_RX_LRCLK_2;
+ break;
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ bclk |= WM8915_AIF1_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ lrclk_tx |= WM8915_AIF1TX_LRCLK_INV;
+ lrclk_rx |= WM8915_AIF1RX_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ bclk |= WM8915_AIF1_BCLK_INV;
+ lrclk_tx |= WM8915_AIF1TX_LRCLK_INV;
+ lrclk_rx |= WM8915_AIF1RX_LRCLK_INV;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ lrclk_tx |= WM8915_AIF1TX_LRCLK_MSTR;
+ lrclk_rx |= WM8915_AIF1RX_LRCLK_MSTR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ bclk |= WM8915_AIF1_BCLK_MSTR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ bclk |= WM8915_AIF1_BCLK_MSTR;
+ lrclk_tx |= WM8915_AIF1TX_LRCLK_MSTR;
+ lrclk_rx |= WM8915_AIF1RX_LRCLK_MSTR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ aifctrl |= 1;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aifctrl |= 2;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aifctrl |= 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, aifctrl_reg, WM8915_AIF1_FMT_MASK, aifctrl);
+ snd_soc_update_bits(codec, bclk_reg,
+ WM8915_AIF1_BCLK_INV | WM8915_AIF1_BCLK_MSTR,
+ bclk);
+ snd_soc_update_bits(codec, lrclk_tx_reg,
+ WM8915_AIF1TX_LRCLK_INV |
+ WM8915_AIF1TX_LRCLK_MSTR,
+ lrclk_tx);
+ snd_soc_update_bits(codec, lrclk_rx_reg,
+ WM8915_AIF1RX_LRCLK_INV |
+ WM8915_AIF1RX_LRCLK_MSTR,
+ lrclk_rx);
+
+ return 0;
+}
+
+static const int bclk_divs[] = {
+ 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96
+};
+
+static const int dsp_divs[] = {
+ 48000, 32000, 16000, 8000
+};
+
+static int wm8915_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ int bits, i, bclk_rate, best, cur_val;
+ int aifdata = 0;
+ int bclk = 0;
+ int lrclk = 0;
+ int dsp = 0;
+ int aifdata_reg, bclk_reg, lrclk_reg, dsp_shift;
+
+ if (!wm8915->sysclk) {
+ dev_err(codec->dev, "SYSCLK not configured\n");
+ return -EINVAL;
+ }
+
+ switch (dai->id) {
+ case 0:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
+ (snd_soc_read(codec, WM8915_GPIO_1)) & WM8915_GP1_FN_MASK) {
+ aifdata_reg = WM8915_AIF1RX_DATA_CONFIGURATION;
+ lrclk_reg = WM8915_AIF1_RX_LRCLK_1;
+ } else {
+ aifdata_reg = WM8915_AIF1TX_DATA_CONFIGURATION_1;
+ lrclk_reg = WM8915_AIF1_TX_LRCLK_1;
+ }
+ bclk_reg = WM8915_AIF1_BCLK;
+ dsp_shift = 0;
+ break;
+ case 1:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
+ (snd_soc_read(codec, WM8915_GPIO_2)) & WM8915_GP2_FN_MASK) {
+ aifdata_reg = WM8915_AIF2RX_DATA_CONFIGURATION;
+ lrclk_reg = WM8915_AIF2_RX_LRCLK_1;
+ } else {
+ aifdata_reg = WM8915_AIF2TX_DATA_CONFIGURATION_1;
+ lrclk_reg = WM8915_AIF2_TX_LRCLK_1;
+ }
+ bclk_reg = WM8915_AIF2_BCLK;
+ dsp_shift = WM8915_DSP2_DIV_SHIFT;
+ break;
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ bclk_rate = snd_soc_params_to_bclk(params);
+ if (bclk_rate < 0) {
+ dev_err(codec->dev, "Unsupported BCLK rate: %d\n", bclk_rate);
+ return bclk_rate;
+ }
+
+ /* Needs looking at for TDM */
+ bits = snd_pcm_format_width(params_format(params));
+ if (bits < 0)
+ return bits;
+ aifdata |= (bits << WM8915_AIF1TX_WL_SHIFT) | bits;
+
+ for (i = 0; i < ARRAY_SIZE(dsp_divs); i++) {
+ if (dsp_divs[i] == params_rate(params))
+ break;
+ }
+ if (i == ARRAY_SIZE(dsp_divs)) {
+ dev_err(codec->dev, "Unsupported sample rate %dHz\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+ dsp |= i << dsp_shift;
+
+ /* Pick a divisor for BCLK as close as we can get to ideal */
+ best = 0;
+ for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+ cur_val = (wm8915->sysclk / bclk_divs[i]) - bclk_rate;
+ if (cur_val < 0) /* BCLK table is sorted */
+ break;
+ best = i;
+ }
+ bclk_rate = wm8915->sysclk / bclk_divs[best];
+ dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n",
+ bclk_divs[best], bclk_rate);
+ bclk |= best;
+
+ lrclk = bclk_rate / params_rate(params);
+ dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n",
+ lrclk, bclk_rate / lrclk);
+
+ snd_soc_update_bits(codec, aifdata_reg,
+ WM8915_AIF1TX_WL_MASK |
+ WM8915_AIF1TX_SLOT_LEN_MASK,
+ aifdata);
+ snd_soc_update_bits(codec, bclk_reg, WM8915_AIF1_BCLK_DIV_MASK, bclk);
+ snd_soc_update_bits(codec, lrclk_reg, WM8915_AIF1RX_RATE_MASK,
+ lrclk);
+ snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_2,
+ WM8915_DSP1_DIV_SHIFT << dsp_shift, dsp);
+
+ wm8915->rx_rate[dai->id] = params_rate(params);
+
+ return 0;
+}
+
+static int wm8915_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ int lfclk = 0;
+ int ratediv = 0;
+ int src;
+ int old;
+
+ /* Disable SYSCLK while we reconfigure */
+ old = snd_soc_read(codec, WM8915_AIF_CLOCKING_1) & WM8915_SYSCLK_ENA;
+ snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1,
+ WM8915_SYSCLK_ENA, 0);
+
+ switch (clk_id) {
+ case WM8915_SYSCLK_MCLK1:
+ wm8915->sysclk = freq;
+ src = 0;
+ break;
+ case WM8915_SYSCLK_MCLK2:
+ wm8915->sysclk = freq;
+ src = 1;
+ break;
+ case WM8915_SYSCLK_FLL:
+ wm8915->sysclk = freq;
+ src = 2;
+ break;
+ default:
+ dev_err(codec->dev, "Unsupported clock source %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ switch (wm8915->sysclk) {
+ case 6144000:
+ snd_soc_update_bits(codec, WM8915_AIF_RATE,
+ WM8915_SYSCLK_RATE, 0);
+ break;
+ case 24576000:
+ ratediv = WM8915_SYSCLK_DIV;
+ case 12288000:
+ snd_soc_update_bits(codec, WM8915_AIF_RATE,
+ WM8915_SYSCLK_RATE, WM8915_SYSCLK_RATE);
+ break;
+ case 32000:
+ case 32768:
+ lfclk = WM8915_LFCLK_ENA;
+ break;
+ default:
+ dev_warn(codec->dev, "Unsupported clock rate %dHz\n",
+ wm8915->sysclk);
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1,
+ WM8915_SYSCLK_SRC_MASK | WM8915_SYSCLK_DIV_MASK,
+ src << WM8915_SYSCLK_SRC_SHIFT | ratediv);
+ snd_soc_update_bits(codec, WM8915_CLOCKING_1, WM8915_LFCLK_ENA, lfclk);
+ snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1,
+ WM8915_SYSCLK_ENA, old);
+
+ return 0;
+}
+
+struct _fll_div {
+ u16 fll_fratio;
+ u16 fll_outdiv;
+ u16 fll_refclk_div;
+ u16 fll_loop_gain;
+ u16 fll_ref_freq;
+ u16 n;
+ u16 theta;
+ u16 lambda;
+};
+
+static struct {
+ unsigned int min;
+ unsigned int max;
+ u16 fll_fratio;
+ int ratio;
+} fll_fratios[] = {
+ { 0, 64000, 4, 16 },
+ { 64000, 128000, 3, 8 },
+ { 128000, 256000, 2, 4 },
+ { 256000, 1000000, 1, 2 },
+ { 1000000, 13500000, 0, 1 },
+};
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+ unsigned int Fout)
+{
+ unsigned int target;
+ unsigned int div;
+ unsigned int fratio, gcd_fll;
+ int i;
+
+ /* Fref must be <=13.5MHz */
+ div = 1;
+ fll_div->fll_refclk_div = 0;
+ while ((Fref / div) > 13500000) {
+ div *= 2;
+ fll_div->fll_refclk_div++;
+
+ if (div > 8) {
+ pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+ Fref);
+ return -EINVAL;
+ }
+ }
+
+ pr_debug("FLL Fref=%u Fout=%u\n", Fref, Fout);
+
+ /* Apply the division for our remaining calculations */
+ Fref /= div;
+
+ if (Fref >= 3000000)
+ fll_div->fll_loop_gain = 5;
+ else
+ fll_div->fll_loop_gain = 0;
+
+ if (Fref >= 48000)
+ fll_div->fll_ref_freq = 0;
+ else
+ fll_div->fll_ref_freq = 1;
+
+ /* Fvco should be 90-100MHz; don't check the upper bound */
+ div = 2;
+ while (Fout * div < 90000000) {
+ div++;
+ if (div > 64) {
+ pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+ Fout);
+ return -EINVAL;
+ }
+ }
+ target = Fout * div;
+ fll_div->fll_outdiv = div - 1;
+
+ pr_debug("FLL Fvco=%dHz\n", target);
+
+ /* Find an appropraite FLL_FRATIO and factor it out of the target */
+ for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+ if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+ fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+ fratio = fll_fratios[i].ratio;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(fll_fratios)) {
+ pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+ return -EINVAL;
+ }
+
+ fll_div->n = target / (fratio * Fref);
+
+ if (target % Fref == 0) {
+ fll_div->theta = 0;
+ fll_div->lambda = 0;
+ } else {
+ gcd_fll = gcd(target, fratio * Fref);
+
+ fll_div->theta = (target - (fll_div->n * fratio * Fref))
+ / gcd_fll;
+ fll_div->lambda = (fratio * Fref) / gcd_fll;
+ }
+
+ pr_debug("FLL N=%x THETA=%x LAMBDA=%x\n",
+ fll_div->n, fll_div->theta, fll_div->lambda);
+ pr_debug("FLL_FRATIO=%x FLL_OUTDIV=%x FLL_REFCLK_DIV=%x\n",
+ fll_div->fll_fratio, fll_div->fll_outdiv,
+ fll_div->fll_refclk_div);
+
+ return 0;
+}
+
+static int wm8915_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
+ unsigned int Fref, unsigned int Fout)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ struct _fll_div fll_div;
+ unsigned long timeout;
+ int ret, reg;
+
+ /* Any change? */
+ if (source == wm8915->fll_src && Fref == wm8915->fll_fref &&
+ Fout == wm8915->fll_fout)
+ return 0;
+
+ if (Fout == 0) {
+ dev_dbg(codec->dev, "FLL disabled\n");
+
+ wm8915->fll_fref = 0;
+ wm8915->fll_fout = 0;
+
+ snd_soc_update_bits(codec, WM8915_FLL_CONTROL_1,
+ WM8915_FLL_ENA, 0);
+
+ return 0;
+ }
+
+ ret = fll_factors(&fll_div, Fref, Fout);
+ if (ret != 0)
+ return ret;
+
+ switch (source) {
+ case WM8915_FLL_MCLK1:
+ reg = 0;
+ break;
+ case WM8915_FLL_MCLK2:
+ reg = 1;
+ break;
+ case WM8915_FLL_DACLRCLK1:
+ reg = 2;
+ break;
+ case WM8915_FLL_BCLK1:
+ reg = 3;
+ break;
+ default:
+ dev_err(codec->dev, "Unknown FLL source %d\n", ret);
+ return -EINVAL;
+ }
+
+ reg |= fll_div.fll_refclk_div << WM8915_FLL_REFCLK_DIV_SHIFT;
+ reg |= fll_div.fll_ref_freq << WM8915_FLL_REF_FREQ_SHIFT;
+
+ snd_soc_update_bits(codec, WM8915_FLL_CONTROL_5,
+ WM8915_FLL_REFCLK_DIV_MASK | WM8915_FLL_REF_FREQ |
+ WM8915_FLL_REFCLK_SRC_MASK, reg);
+
+ reg = 0;
+ if (fll_div.theta || fll_div.lambda)
+ reg |= WM8915_FLL_EFS_ENA | (3 << WM8915_FLL_LFSR_SEL_SHIFT);
+ else
+ reg |= 1 << WM8915_FLL_LFSR_SEL_SHIFT;
+ snd_soc_write(codec, WM8915_FLL_EFS_2, reg);
+
+ snd_soc_update_bits(codec, WM8915_FLL_CONTROL_2,
+ WM8915_FLL_OUTDIV_MASK |
+ WM8915_FLL_FRATIO_MASK,
+ (fll_div.fll_outdiv << WM8915_FLL_OUTDIV_SHIFT) |
+ (fll_div.fll_fratio));
+
+ snd_soc_write(codec, WM8915_FLL_CONTROL_3, fll_div.theta);
+
+ snd_soc_update_bits(codec, WM8915_FLL_CONTROL_4,
+ WM8915_FLL_N_MASK | WM8915_FLL_LOOP_GAIN_MASK,
+ (fll_div.n << WM8915_FLL_N_SHIFT) |
+ fll_div.fll_loop_gain);
+
+ snd_soc_write(codec, WM8915_FLL_EFS_1, fll_div.lambda);
+
+ snd_soc_update_bits(codec, WM8915_FLL_CONTROL_1,
+ WM8915_FLL_ENA, WM8915_FLL_ENA);
+
+ /* The FLL supports live reconfiguration - kick that in case we were
+ * already enabled.
+ */
+ snd_soc_write(codec, WM8915_FLL_CONTROL_6, WM8915_FLL_SWITCH_CLK);
+
+ /* Wait for the FLL to lock, using the interrupt if possible */
+ if (Fref > 1000000)
+ timeout = usecs_to_jiffies(300);
+ else
+ timeout = msecs_to_jiffies(2);
+
+ wait_for_completion_timeout(&wm8915->fll_lock, timeout);
+
+ dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
+
+ wm8915->fll_fref = Fref;
+ wm8915->fll_fout = Fout;
+ wm8915->fll_src = source;
+
+ return 0;
+}
+
+#ifdef CONFIG_GPIOLIB
+static inline struct wm8915_priv *gpio_to_wm8915(struct gpio_chip *chip)
+{
+ return container_of(chip, struct wm8915_priv, gpio_chip);
+}
+
+static void wm8915_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct wm8915_priv *wm8915 = gpio_to_wm8915(chip);
+ struct snd_soc_codec *codec = wm8915->codec;
+
+ snd_soc_update_bits(codec, WM8915_GPIO_1 + offset,
+ WM8915_GP1_LVL, !!value << WM8915_GP1_LVL_SHIFT);
+}
+
+static int wm8915_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct wm8915_priv *wm8915 = gpio_to_wm8915(chip);
+ struct snd_soc_codec *codec = wm8915->codec;
+ int val;
+
+ val = (1 << WM8915_GP1_FN_SHIFT) | (!!value << WM8915_GP1_LVL_SHIFT);
+
+ return snd_soc_update_bits(codec, WM8915_GPIO_1 + offset,
+ WM8915_GP1_FN_MASK | WM8915_GP1_DIR |
+ WM8915_GP1_LVL, val);
+}
+
+static int wm8915_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8915_priv *wm8915 = gpio_to_wm8915(chip);
+ struct snd_soc_codec *codec = wm8915->codec;
+ int ret;
+
+ ret = snd_soc_read(codec, WM8915_GPIO_1 + offset);
+ if (ret < 0)
+ return ret;
+
+ return (ret & WM8915_GP1_LVL) != 0;
+}
+
+static int wm8915_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8915_priv *wm8915 = gpio_to_wm8915(chip);
+ struct snd_soc_codec *codec = wm8915->codec;
+
+ return snd_soc_update_bits(codec, WM8915_GPIO_1 + offset,
+ WM8915_GP1_FN_MASK | WM8915_GP1_DIR,
+ (1 << WM8915_GP1_FN_SHIFT) |
+ (1 << WM8915_GP1_DIR_SHIFT));
+}
+
+static struct gpio_chip wm8915_template_chip = {
+ .label = "wm8915",
+ .owner = THIS_MODULE,
+ .direction_output = wm8915_gpio_direction_out,
+ .set = wm8915_gpio_set,
+ .direction_input = wm8915_gpio_direction_in,
+ .get = wm8915_gpio_get,
+ .can_sleep = 1,
+};
+
+static void wm8915_init_gpio(struct snd_soc_codec *codec)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ wm8915->gpio_chip = wm8915_template_chip;
+ wm8915->gpio_chip.ngpio = 5;
+ wm8915->gpio_chip.dev = codec->dev;
+
+ if (wm8915->pdata.gpio_base)
+ wm8915->gpio_chip.base = wm8915->pdata.gpio_base;
+ else
+ wm8915->gpio_chip.base = -1;
+
+ ret = gpiochip_add(&wm8915->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add GPIOs: %d\n", ret);
+}
+
+static void wm8915_free_gpio(struct snd_soc_codec *codec)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = gpiochip_remove(&wm8915->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to remove GPIOs: %d\n", ret);
+}
+#else
+static void wm8915_init_gpio(struct snd_soc_codec *codec)
+{
+}
+
+static void wm8915_free_gpio(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+/**
+ * wm8915_detect - Enable default WM8915 jack detection
+ *
+ * The WM8915 has advanced accessory detection support for headsets.
+ * This function provides a default implementation which integrates
+ * the majority of this functionality with minimal user configuration.
+ *
+ * This will detect headset, headphone and short circuit button and
+ * will also detect inverted microphone ground connections and update
+ * the polarity of the connections.
+ */
+int wm8915_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+ wm8915_polarity_fn polarity_cb)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+
+ wm8915->jack = jack;
+ wm8915->detecting = true;
+ wm8915->polarity_cb = polarity_cb;
+
+ if (wm8915->polarity_cb)
+ wm8915->polarity_cb(codec, 0);
+
+ /* Clear discarge to avoid noise during detection */
+ snd_soc_update_bits(codec, WM8915_MICBIAS_1,
+ WM8915_MICB1_DISCH, 0);
+ snd_soc_update_bits(codec, WM8915_MICBIAS_2,
+ WM8915_MICB2_DISCH, 0);
+
+ /* LDO2 powers the microphones, SYSCLK clocks detection */
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "LDO2");
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "SYSCLK");
+
+ /* We start off just enabling microphone detection - even a
+ * plain headphone will trigger detection.
+ */
+ snd_soc_update_bits(codec, WM8915_MIC_DETECT_1,
+ WM8915_MICD_ENA, WM8915_MICD_ENA);
+
+ /* Slowest detection rate, gives debounce for initial detection */
+ snd_soc_update_bits(codec, WM8915_MIC_DETECT_1,
+ WM8915_MICD_RATE_MASK,
+ WM8915_MICD_RATE_MASK);
+
+ /* Enable interrupts and we're off */
+ snd_soc_update_bits(codec, WM8915_INTERRUPT_STATUS_2_MASK,
+ WM8915_IM_MICD_EINT, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8915_detect);
+
+static void wm8915_micd(struct snd_soc_codec *codec)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ int val, reg;
+
+ val = snd_soc_read(codec, WM8915_MIC_DETECT_3);
+
+ dev_dbg(codec->dev, "Microphone event: %x\n", val);
+
+ if (!(val & WM8915_MICD_VALID)) {
+ dev_warn(codec->dev, "Microphone detection state invalid\n");
+ return;
+ }
+
+ /* No accessory, reset everything and report removal */
+ if (!(val & WM8915_MICD_STS)) {
+ dev_dbg(codec->dev, "Jack removal detected\n");
+ wm8915->jack_mic = false;
+ wm8915->detecting = true;
+ snd_soc_jack_report(wm8915->jack, 0,
+ SND_JACK_HEADSET | SND_JACK_BTN_0);
+ snd_soc_update_bits(codec, WM8915_MIC_DETECT_1,
+ WM8915_MICD_RATE_MASK,
+ WM8915_MICD_RATE_MASK);
+ return;
+ }
+
+ /* If the measurement is very high we've got a microphone but
+ * do a little debounce to account for mechanical issues.
+ */
+ if (val & 0x400) {
+ dev_dbg(codec->dev, "Microphone detected\n");
+ snd_soc_jack_report(wm8915->jack, SND_JACK_HEADSET,
+ SND_JACK_HEADSET | SND_JACK_BTN_0);
+ wm8915->jack_mic = true;
+ wm8915->detecting = false;
+ }
+
+ /* If we detected a lower impedence during initial startup
+ * then we probably have the wrong polarity, flip it. Don't
+ * do this for the lowest impedences to speed up detection of
+ * plain headphones.
+ */
+ if (wm8915->detecting && (val & 0x3f0)) {
+ reg = snd_soc_read(codec, WM8915_ACCESSORY_DETECT_MODE_2);
+ reg ^= WM8915_HPOUT1FB_SRC | WM8915_MICD_SRC |
+ WM8915_MICD_BIAS_SRC;
+ snd_soc_update_bits(codec, WM8915_ACCESSORY_DETECT_MODE_2,
+ WM8915_HPOUT1FB_SRC | WM8915_MICD_SRC |
+ WM8915_MICD_BIAS_SRC, reg);
+
+ if (wm8915->polarity_cb)
+ wm8915->polarity_cb(codec,
+ (reg & WM8915_MICD_SRC) != 0);
+
+ dev_dbg(codec->dev, "Set microphone polarity to %d\n",
+ (reg & WM8915_MICD_SRC) != 0);
+
+ return;
+ }
+
+ /* Don't distinguish between buttons, just report any low
+ * impedence as BTN_0.
+ */
+ if (val & 0x3fc) {
+ if (wm8915->jack_mic) {
+ dev_dbg(codec->dev, "Mic button detected\n");
+ snd_soc_jack_report(wm8915->jack,
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ SND_JACK_HEADSET | SND_JACK_BTN_0);
+ } else {
+ dev_dbg(codec->dev, "Headphone detected\n");
+ snd_soc_jack_report(wm8915->jack,
+ SND_JACK_HEADPHONE,
+ SND_JACK_HEADSET |
+ SND_JACK_BTN_0);
+ wm8915->detecting = false;
+ }
+ }
+
+ /* Increase poll rate to give better responsiveness for buttons */
+ if (!wm8915->detecting)
+ snd_soc_update_bits(codec, WM8915_MIC_DETECT_1,
+ WM8915_MICD_RATE_MASK,
+ 5 << WM8915_MICD_RATE_SHIFT);
+}
+
+static irqreturn_t wm8915_irq(int irq, void *data)
+{
+ struct snd_soc_codec *codec = data;
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ int irq_val;
+
+ irq_val = snd_soc_read(codec, WM8915_INTERRUPT_STATUS_2);
+ if (irq_val < 0) {
+ dev_err(codec->dev, "Failed to read IRQ status: %d\n",
+ irq_val);
+ return IRQ_NONE;
+ }
+ irq_val &= ~snd_soc_read(codec, WM8915_INTERRUPT_STATUS_2_MASK);
+
+ if (irq_val & (WM8915_DCS_DONE_01_EINT | WM8915_DCS_DONE_23_EINT)) {
+ dev_dbg(codec->dev, "DC servo IRQ\n");
+ complete(&wm8915->dcs_done);
+ }
+
+ if (irq_val & WM8915_FIFOS_ERR_EINT)
+ dev_err(codec->dev, "Digital core FIFO error\n");
+
+ if (irq_val & WM8915_FLL_LOCK_EINT) {
+ dev_dbg(codec->dev, "FLL locked\n");
+ complete(&wm8915->fll_lock);
+ }
+
+ if (irq_val & WM8915_MICD_EINT)
+ wm8915_micd(codec);
+
+ if (irq_val) {
+ snd_soc_write(codec, WM8915_INTERRUPT_STATUS_2, irq_val);
+
+ return IRQ_HANDLED;
+ } else {
+ return IRQ_NONE;
+ }
+}
+
+static void wm8915_retune_mobile_pdata(struct snd_soc_codec *codec)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ struct wm8915_pdata *pdata = &wm8915->pdata;
+
+ struct snd_kcontrol_new controls[] = {
+ SOC_ENUM_EXT("DSP1 EQ Mode",
+ wm8915->retune_mobile_enum,
+ wm8915_get_retune_mobile_enum,
+ wm8915_put_retune_mobile_enum),
+ SOC_ENUM_EXT("DSP2 EQ Mode",
+ wm8915->retune_mobile_enum,
+ wm8915_get_retune_mobile_enum,
+ wm8915_put_retune_mobile_enum),
+ };
+ int ret, i, j;
+ const char **t;
+
+ /* We need an array of texts for the enum API but the number
+ * of texts is likely to be less than the number of
+ * configurations due to the sample rate dependency of the
+ * configurations. */
+ wm8915->num_retune_mobile_texts = 0;
+ wm8915->retune_mobile_texts = NULL;
+ for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+ for (j = 0; j < wm8915->num_retune_mobile_texts; j++) {
+ if (strcmp(pdata->retune_mobile_cfgs[i].name,
+ wm8915->retune_mobile_texts[j]) == 0)
+ break;
+ }
+
+ if (j != wm8915->num_retune_mobile_texts)
+ continue;
+
+ /* Expand the array... */
+ t = krealloc(wm8915->retune_mobile_texts,
+ sizeof(char *) *
+ (wm8915->num_retune_mobile_texts + 1),
+ GFP_KERNEL);
+ if (t == NULL)
+ continue;
+
+ /* ...store the new entry... */
+ t[wm8915->num_retune_mobile_texts] =
+ pdata->retune_mobile_cfgs[i].name;
+
+ /* ...and remember the new version. */
+ wm8915->num_retune_mobile_texts++;
+ wm8915->retune_mobile_texts = t;
+ }
+
+ dev_dbg(codec->dev, "Allocated %d unique ReTune Mobile names\n",
+ wm8915->num_retune_mobile_texts);
+
+ wm8915->retune_mobile_enum.max = wm8915->num_retune_mobile_texts;
+ wm8915->retune_mobile_enum.texts = wm8915->retune_mobile_texts;
+
+ ret = snd_soc_add_controls(codec, controls, ARRAY_SIZE(controls));
+ if (ret != 0)
+ dev_err(codec->dev,
+ "Failed to add ReTune Mobile controls: %d\n", ret);
+}
+
+static int wm8915_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ struct i2c_client *i2c = to_i2c_client(codec->dev);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int i, irq_flags;
+
+ wm8915->codec = codec;
+
+ init_completion(&wm8915->dcs_done);
+ init_completion(&wm8915->fll_lock);
+
+ dapm->idle_bias_off = true;
+ dapm->bias_level = SND_SOC_BIAS_OFF;
+
+ ret = snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ goto err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8915->supplies); i++)
+ wm8915->supplies[i].supply = wm8915_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8915->supplies),
+ wm8915->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ goto err;
+ }
+
+ wm8915->disable_nb[0].notifier_call = wm8915_regulator_event_0;
+ wm8915->disable_nb[1].notifier_call = wm8915_regulator_event_1;
+ wm8915->disable_nb[2].notifier_call = wm8915_regulator_event_2;
+ wm8915->disable_nb[3].notifier_call = wm8915_regulator_event_3;
+ wm8915->disable_nb[4].notifier_call = wm8915_regulator_event_4;
+ wm8915->disable_nb[5].notifier_call = wm8915_regulator_event_5;
+
+ /* This should really be moved into the regulator core */
+ for (i = 0; i < ARRAY_SIZE(wm8915->supplies); i++) {
+ ret = regulator_register_notifier(wm8915->supplies[i].consumer,
+ &wm8915->disable_nb[i]);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to register regulator notifier: %d\n",
+ ret);
+ }
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8915->supplies),
+ wm8915->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ if (wm8915->pdata.ldo_ena >= 0) {
+ gpio_set_value_cansleep(wm8915->pdata.ldo_ena, 1);
+ msleep(5);
+ }
+
+ ret = snd_soc_read(codec, WM8915_SOFTWARE_RESET);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read ID register: %d\n", ret);
+ goto err_enable;
+ }
+ if (ret != 0x8915) {
+ dev_err(codec->dev, "Device is not a WM8915, ID %x\n", ret);
+ ret = -EINVAL;
+ goto err_enable;
+ }
+
+ ret = snd_soc_read(codec, WM8915_CHIP_REVISION);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read device revision: %d\n",
+ ret);
+ goto err_enable;
+ }
+
+ dev_info(codec->dev, "revision %c\n",
+ (ret & WM8915_CHIP_REV_MASK) + 'A');
+
+ if (wm8915->pdata.ldo_ena >= 0) {
+ gpio_set_value_cansleep(wm8915->pdata.ldo_ena, 0);
+ } else {
+ ret = wm8915_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ goto err_enable;
+ }
+ }
+
+ codec->cache_only = true;
+
+ /* Apply platform data settings */
+ snd_soc_update_bits(codec, WM8915_LINE_INPUT_CONTROL,
+ WM8915_INL_MODE_MASK | WM8915_INR_MODE_MASK,
+ wm8915->pdata.inl_mode << WM8915_INL_MODE_SHIFT |
+ wm8915->pdata.inr_mode);
+
+ for (i = 0; i < ARRAY_SIZE(wm8915->pdata.gpio_default); i++) {
+ if (!wm8915->pdata.gpio_default[i])
+ continue;
+
+ snd_soc_write(codec, WM8915_GPIO_1 + i,
+ wm8915->pdata.gpio_default[i] & 0xffff);
+ }
+
+ if (wm8915->pdata.spkmute_seq)
+ snd_soc_update_bits(codec, WM8915_PDM_SPEAKER_MUTE_SEQUENCE,
+ WM8915_SPK_MUTE_ENDIAN |
+ WM8915_SPK_MUTE_SEQ1_MASK,
+ wm8915->pdata.spkmute_seq);
+
+ snd_soc_update_bits(codec, WM8915_ACCESSORY_DETECT_MODE_2,
+ WM8915_MICD_BIAS_SRC | WM8915_HPOUT1FB_SRC |
+ WM8915_MICD_SRC, wm8915->pdata.micdet_def);
+
+ /* Latch volume update bits */
+ snd_soc_update_bits(codec, WM8915_LEFT_LINE_INPUT_VOLUME,
+ WM8915_IN1_VU, WM8915_IN1_VU);
+ snd_soc_update_bits(codec, WM8915_RIGHT_LINE_INPUT_VOLUME,
+ WM8915_IN1_VU, WM8915_IN1_VU);
+
+ snd_soc_update_bits(codec, WM8915_DAC1_LEFT_VOLUME,
+ WM8915_DAC1_VU, WM8915_DAC1_VU);
+ snd_soc_update_bits(codec, WM8915_DAC1_RIGHT_VOLUME,
+ WM8915_DAC1_VU, WM8915_DAC1_VU);
+ snd_soc_update_bits(codec, WM8915_DAC2_LEFT_VOLUME,
+ WM8915_DAC2_VU, WM8915_DAC2_VU);
+ snd_soc_update_bits(codec, WM8915_DAC2_RIGHT_VOLUME,
+ WM8915_DAC2_VU, WM8915_DAC2_VU);
+
+ snd_soc_update_bits(codec, WM8915_OUTPUT1_LEFT_VOLUME,
+ WM8915_DAC1_VU, WM8915_DAC1_VU);
+ snd_soc_update_bits(codec, WM8915_OUTPUT1_RIGHT_VOLUME,
+ WM8915_DAC1_VU, WM8915_DAC1_VU);
+ snd_soc_update_bits(codec, WM8915_OUTPUT2_LEFT_VOLUME,
+ WM8915_DAC2_VU, WM8915_DAC2_VU);
+ snd_soc_update_bits(codec, WM8915_OUTPUT2_RIGHT_VOLUME,
+ WM8915_DAC2_VU, WM8915_DAC2_VU);
+
+ snd_soc_update_bits(codec, WM8915_DSP1_TX_LEFT_VOLUME,
+ WM8915_DSP1TX_VU, WM8915_DSP1TX_VU);
+ snd_soc_update_bits(codec, WM8915_DSP1_TX_RIGHT_VOLUME,
+ WM8915_DSP1TX_VU, WM8915_DSP1TX_VU);
+ snd_soc_update_bits(codec, WM8915_DSP2_TX_LEFT_VOLUME,
+ WM8915_DSP2TX_VU, WM8915_DSP2TX_VU);
+ snd_soc_update_bits(codec, WM8915_DSP2_TX_RIGHT_VOLUME,
+ WM8915_DSP2TX_VU, WM8915_DSP2TX_VU);
+
+ snd_soc_update_bits(codec, WM8915_DSP1_RX_LEFT_VOLUME,
+ WM8915_DSP1RX_VU, WM8915_DSP1RX_VU);
+ snd_soc_update_bits(codec, WM8915_DSP1_RX_RIGHT_VOLUME,
+ WM8915_DSP1RX_VU, WM8915_DSP1RX_VU);
+ snd_soc_update_bits(codec, WM8915_DSP2_RX_LEFT_VOLUME,
+ WM8915_DSP2RX_VU, WM8915_DSP2RX_VU);
+ snd_soc_update_bits(codec, WM8915_DSP2_RX_RIGHT_VOLUME,
+ WM8915_DSP2RX_VU, WM8915_DSP2RX_VU);
+
+ /* No support currently for the underclocked TDM modes and
+ * pick a default TDM layout with each channel pair working with
+ * slots 0 and 1. */
+ snd_soc_update_bits(codec, WM8915_AIF1RX_CHANNEL_0_CONFIGURATION,
+ WM8915_AIF1RX_CHAN0_SLOTS_MASK |
+ WM8915_AIF1RX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF1RX_CHAN0_SLOTS_SHIFT | 0);
+ snd_soc_update_bits(codec, WM8915_AIF1RX_CHANNEL_1_CONFIGURATION,
+ WM8915_AIF1RX_CHAN1_SLOTS_MASK |
+ WM8915_AIF1RX_CHAN1_START_SLOT_MASK,
+ 1 << WM8915_AIF1RX_CHAN1_SLOTS_SHIFT | 1);
+ snd_soc_update_bits(codec, WM8915_AIF1RX_CHANNEL_2_CONFIGURATION,
+ WM8915_AIF1RX_CHAN2_SLOTS_MASK |
+ WM8915_AIF1RX_CHAN2_START_SLOT_MASK,
+ 1 << WM8915_AIF1RX_CHAN2_SLOTS_SHIFT | 0);
+ snd_soc_update_bits(codec, WM8915_AIF1RX_CHANNEL_3_CONFIGURATION,
+ WM8915_AIF1RX_CHAN3_SLOTS_MASK |
+ WM8915_AIF1RX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF1RX_CHAN3_SLOTS_SHIFT | 1);
+ snd_soc_update_bits(codec, WM8915_AIF1RX_CHANNEL_4_CONFIGURATION,
+ WM8915_AIF1RX_CHAN4_SLOTS_MASK |
+ WM8915_AIF1RX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF1RX_CHAN4_SLOTS_SHIFT | 0);
+ snd_soc_update_bits(codec, WM8915_AIF1RX_CHANNEL_5_CONFIGURATION,
+ WM8915_AIF1RX_CHAN5_SLOTS_MASK |
+ WM8915_AIF1RX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF1RX_CHAN5_SLOTS_SHIFT | 1);
+
+ snd_soc_update_bits(codec, WM8915_AIF2RX_CHANNEL_0_CONFIGURATION,
+ WM8915_AIF2RX_CHAN0_SLOTS_MASK |
+ WM8915_AIF2RX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF2RX_CHAN0_SLOTS_SHIFT | 0);
+ snd_soc_update_bits(codec, WM8915_AIF2RX_CHANNEL_1_CONFIGURATION,
+ WM8915_AIF2RX_CHAN1_SLOTS_MASK |
+ WM8915_AIF2RX_CHAN1_START_SLOT_MASK,
+ 1 << WM8915_AIF2RX_CHAN1_SLOTS_SHIFT | 1);
+
+ snd_soc_update_bits(codec, WM8915_AIF1TX_CHANNEL_0_CONFIGURATION,
+ WM8915_AIF1TX_CHAN0_SLOTS_MASK |
+ WM8915_AIF1TX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF1TX_CHAN0_SLOTS_SHIFT | 0);
+ snd_soc_update_bits(codec, WM8915_AIF1TX_CHANNEL_1_CONFIGURATION,
+ WM8915_AIF1TX_CHAN1_SLOTS_MASK |
+ WM8915_AIF1TX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF1TX_CHAN1_SLOTS_SHIFT | 1);
+ snd_soc_update_bits(codec, WM8915_AIF1TX_CHANNEL_2_CONFIGURATION,
+ WM8915_AIF1TX_CHAN2_SLOTS_MASK |
+ WM8915_AIF1TX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF1TX_CHAN2_SLOTS_SHIFT | 0);
+ snd_soc_update_bits(codec, WM8915_AIF1TX_CHANNEL_3_CONFIGURATION,
+ WM8915_AIF1TX_CHAN3_SLOTS_MASK |
+ WM8915_AIF1TX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF1TX_CHAN3_SLOTS_SHIFT | 1);
+ snd_soc_update_bits(codec, WM8915_AIF1TX_CHANNEL_4_CONFIGURATION,
+ WM8915_AIF1TX_CHAN4_SLOTS_MASK |
+ WM8915_AIF1TX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF1TX_CHAN4_SLOTS_SHIFT | 0);
+ snd_soc_update_bits(codec, WM8915_AIF1TX_CHANNEL_5_CONFIGURATION,
+ WM8915_AIF1TX_CHAN5_SLOTS_MASK |
+ WM8915_AIF1TX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF1TX_CHAN5_SLOTS_SHIFT | 1);
+
+ snd_soc_update_bits(codec, WM8915_AIF2TX_CHANNEL_0_CONFIGURATION,
+ WM8915_AIF2TX_CHAN0_SLOTS_MASK |
+ WM8915_AIF2TX_CHAN0_START_SLOT_MASK,
+ 1 << WM8915_AIF2TX_CHAN0_SLOTS_SHIFT | 0);
+ snd_soc_update_bits(codec, WM8915_AIF1TX_CHANNEL_1_CONFIGURATION,
+ WM8915_AIF2TX_CHAN1_SLOTS_MASK |
+ WM8915_AIF2TX_CHAN1_START_SLOT_MASK,
+ 1 << WM8915_AIF1TX_CHAN1_SLOTS_SHIFT | 1);
+
+ if (wm8915->pdata.num_retune_mobile_cfgs)
+ wm8915_retune_mobile_pdata(codec);
+ else
+ snd_soc_add_controls(codec, wm8915_eq_controls,
+ ARRAY_SIZE(wm8915_eq_controls));
+
+ /* If the TX LRCLK pins are not in LRCLK mode configure the
+ * AIFs to source their clocks from the RX LRCLKs.
+ */
+ if ((snd_soc_read(codec, WM8915_GPIO_1)))
+ snd_soc_update_bits(codec, WM8915_AIF1_TX_LRCLK_2,
+ WM8915_AIF1TX_LRCLK_MODE,
+ WM8915_AIF1TX_LRCLK_MODE);
+
+ if ((snd_soc_read(codec, WM8915_GPIO_2)))
+ snd_soc_update_bits(codec, WM8915_AIF2_TX_LRCLK_2,
+ WM8915_AIF2TX_LRCLK_MODE,
+ WM8915_AIF2TX_LRCLK_MODE);
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8915->supplies), wm8915->supplies);
+
+ wm8915_init_gpio(codec);
+
+ if (i2c->irq) {
+ if (wm8915->pdata.irq_flags)
+ irq_flags = wm8915->pdata.irq_flags;
+ else
+ irq_flags = IRQF_TRIGGER_LOW;
+
+ irq_flags |= IRQF_ONESHOT;
+
+ ret = request_threaded_irq(i2c->irq, NULL, wm8915_irq,
+ irq_flags, "wm8915", codec);
+ if (ret == 0) {
+ /* Unmask the interrupt */
+ snd_soc_update_bits(codec, WM8915_INTERRUPT_CONTROL,
+ WM8915_IM_IRQ, 0);
+
+ /* Enable error reporting and DC servo status */
+ snd_soc_update_bits(codec,
+ WM8915_INTERRUPT_STATUS_2_MASK,
+ WM8915_IM_DCS_DONE_23_EINT |
+ WM8915_IM_DCS_DONE_01_EINT |
+ WM8915_IM_FLL_LOCK_EINT |
+ WM8915_IM_FIFOS_ERR_EINT,
+ 0);
+ } else {
+ dev_err(codec->dev, "Failed to request IRQ: %d\n",
+ ret);
+ }
+ }
+
+ return 0;
+
+err_enable:
+ if (wm8915->pdata.ldo_ena >= 0)
+ gpio_set_value_cansleep(wm8915->pdata.ldo_ena, 0);
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8915->supplies), wm8915->supplies);
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8915->supplies), wm8915->supplies);
+err:
+ return ret;
+}
+
+static int wm8915_remove(struct snd_soc_codec *codec)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ struct i2c_client *i2c = to_i2c_client(codec->dev);
+ int i;
+
+ snd_soc_update_bits(codec, WM8915_INTERRUPT_CONTROL,
+ WM8915_IM_IRQ, WM8915_IM_IRQ);
+
+ if (i2c->irq)
+ free_irq(i2c->irq, codec);
+
+ wm8915_free_gpio(codec);
+
+ for (i = 0; i < ARRAY_SIZE(wm8915->supplies); i++)
+ regulator_unregister_notifier(wm8915->supplies[i].consumer,
+ &wm8915->disable_nb[i]);
+ regulator_bulk_free(ARRAY_SIZE(wm8915->supplies), wm8915->supplies);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8915 = {
+ .probe = wm8915_probe,
+ .remove = wm8915_remove,
+ .set_bias_level = wm8915_set_bias_level,
+ .seq_notifier = wm8915_seq_notifier,
+ .reg_cache_size = WM8915_MAX_REGISTER + 1,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8915_reg,
+ .volatile_register = wm8915_volatile_register,
+ .readable_register = wm8915_readable_register,
+ .compress_type = SND_SOC_RBTREE_COMPRESSION,
+ .controls = wm8915_snd_controls,
+ .num_controls = ARRAY_SIZE(wm8915_snd_controls),
+ .dapm_widgets = wm8915_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm8915_dapm_widgets),
+ .dapm_routes = wm8915_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(wm8915_dapm_routes),
+ .set_pll = wm8915_set_fll,
+};
+
+#define WM8915_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000)
+#define WM8915_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8915_dai_ops = {
+ .set_fmt = wm8915_set_fmt,
+ .hw_params = wm8915_hw_params,
+ .set_sysclk = wm8915_set_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8915_dai[] = {
+ {
+ .name = "wm8915-aif1",
+ .playback = {
+ .stream_name = "AIF1 Playback",
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = WM8915_RATES,
+ .formats = WM8915_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AIF1 Capture",
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = WM8915_RATES,
+ .formats = WM8915_FORMATS,
+ },
+ .ops = &wm8915_dai_ops,
+ },
+ {
+ .name = "wm8915-aif2",
+ .playback = {
+ .stream_name = "AIF2 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8915_RATES,
+ .formats = WM8915_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AIF2 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8915_RATES,
+ .formats = WM8915_FORMATS,
+ },
+ .ops = &wm8915_dai_ops,
+ },
+};
+
+static __devinit int wm8915_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8915_priv *wm8915;
+ int ret;
+
+ wm8915 = kzalloc(sizeof(struct wm8915_priv), GFP_KERNEL);
+ if (wm8915 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8915);
+
+ if (dev_get_platdata(&i2c->dev))
+ memcpy(&wm8915->pdata, dev_get_platdata(&i2c->dev),
+ sizeof(wm8915->pdata));
+
+ if (wm8915->pdata.ldo_ena > 0) {
+ ret = gpio_request_one(wm8915->pdata.ldo_ena,
+ GPIOF_OUT_INIT_LOW, "WM8915 ENA");
+ if (ret < 0) {
+ dev_err(&i2c->dev, "Failed to request GPIO %d: %d\n",
+ wm8915->pdata.ldo_ena, ret);
+ goto err;
+ }
+ }
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8915, wm8915_dai,
+ ARRAY_SIZE(wm8915_dai));
+ if (ret < 0)
+ goto err_gpio;
+
+ return ret;
+
+err_gpio:
+ if (wm8915->pdata.ldo_ena > 0)
+ gpio_free(wm8915->pdata.ldo_ena);
+err:
+ kfree(wm8915);
+
+ return ret;
+}
+
+static __devexit int wm8915_i2c_remove(struct i2c_client *client)
+{
+ struct wm8915_priv *wm8915 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_codec(&client->dev);
+ if (wm8915->pdata.ldo_ena > 0)
+ gpio_free(wm8915->pdata.ldo_ena);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8915_i2c_id[] = {
+ { "wm8915", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8915_i2c_id);
+
+static struct i2c_driver wm8915_i2c_driver = {
+ .driver = {
+ .name = "wm8915",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8915_i2c_probe,
+ .remove = __devexit_p(wm8915_i2c_remove),
+ .id_table = wm8915_i2c_id,
+};
+
+static int __init wm8915_modinit(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&wm8915_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8915 I2C driver: %d\n",
+ ret);
+ }
+
+ return ret;
+}
+module_init(wm8915_modinit);
+
+static void __exit wm8915_exit(void)
+{
+ i2c_del_driver(&wm8915_i2c_driver);
+}
+module_exit(wm8915_exit);
+
+MODULE_DESCRIPTION("ASoC WM8915 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8915.h b/sound/soc/codecs/wm8915.h
new file mode 100644
index 00000000..200ffd7b
--- /dev/null
+++ b/sound/soc/codecs/wm8915.h
@@ -0,0 +1,3717 @@
+/*
+ * wm8915.h - WM8915 audio codec interface
+ *
+ * Copyright 2011 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _WM8915_H
+#define _WM8915_H
+
+#define WM8915_SYSCLK_MCLK1 1
+#define WM8915_SYSCLK_MCLK2 2
+#define WM8915_SYSCLK_FLL 3
+
+#define WM8915_FLL_MCLK1 1
+#define WM8915_FLL_MCLK2 2
+#define WM8915_FLL_DACLRCLK1 3
+#define WM8915_FLL_BCLK1 4
+
+typedef void (*wm8915_polarity_fn)(struct snd_soc_codec *codec, int polarity);
+
+int wm8915_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+ wm8915_polarity_fn polarity_cb);
+
+/*
+ * Register values.
+ */
+#define WM8915_SOFTWARE_RESET 0x00
+#define WM8915_POWER_MANAGEMENT_1 0x01
+#define WM8915_POWER_MANAGEMENT_2 0x02
+#define WM8915_POWER_MANAGEMENT_3 0x03
+#define WM8915_POWER_MANAGEMENT_4 0x04
+#define WM8915_POWER_MANAGEMENT_5 0x05
+#define WM8915_POWER_MANAGEMENT_6 0x06
+#define WM8915_POWER_MANAGEMENT_7 0x07
+#define WM8915_POWER_MANAGEMENT_8 0x08
+#define WM8915_LEFT_LINE_INPUT_VOLUME 0x10
+#define WM8915_RIGHT_LINE_INPUT_VOLUME 0x11
+#define WM8915_LINE_INPUT_CONTROL 0x12
+#define WM8915_DAC1_HPOUT1_VOLUME 0x15
+#define WM8915_DAC2_HPOUT2_VOLUME 0x16
+#define WM8915_DAC1_LEFT_VOLUME 0x18
+#define WM8915_DAC1_RIGHT_VOLUME 0x19
+#define WM8915_DAC2_LEFT_VOLUME 0x1A
+#define WM8915_DAC2_RIGHT_VOLUME 0x1B
+#define WM8915_OUTPUT1_LEFT_VOLUME 0x1C
+#define WM8915_OUTPUT1_RIGHT_VOLUME 0x1D
+#define WM8915_OUTPUT2_LEFT_VOLUME 0x1E
+#define WM8915_OUTPUT2_RIGHT_VOLUME 0x1F
+#define WM8915_MICBIAS_1 0x20
+#define WM8915_MICBIAS_2 0x21
+#define WM8915_LDO_1 0x28
+#define WM8915_LDO_2 0x29
+#define WM8915_ACCESSORY_DETECT_MODE_1 0x30
+#define WM8915_ACCESSORY_DETECT_MODE_2 0x31
+#define WM8915_HEADPHONE_DETECT_1 0x34
+#define WM8915_HEADPHONE_DETECT_2 0x35
+#define WM8915_MIC_DETECT_1 0x38
+#define WM8915_MIC_DETECT_2 0x39
+#define WM8915_MIC_DETECT_3 0x3A
+#define WM8915_CHARGE_PUMP_1 0x40
+#define WM8915_CHARGE_PUMP_2 0x41
+#define WM8915_DC_SERVO_1 0x50
+#define WM8915_DC_SERVO_2 0x51
+#define WM8915_DC_SERVO_3 0x52
+#define WM8915_DC_SERVO_5 0x54
+#define WM8915_DC_SERVO_6 0x55
+#define WM8915_DC_SERVO_7 0x56
+#define WM8915_DC_SERVO_READBACK_0 0x57
+#define WM8915_ANALOGUE_HP_1 0x60
+#define WM8915_ANALOGUE_HP_2 0x61
+#define WM8915_CHIP_REVISION 0x100
+#define WM8915_CONTROL_INTERFACE_1 0x101
+#define WM8915_WRITE_SEQUENCER_CTRL_1 0x110
+#define WM8915_WRITE_SEQUENCER_CTRL_2 0x111
+#define WM8915_AIF_CLOCKING_1 0x200
+#define WM8915_AIF_CLOCKING_2 0x201
+#define WM8915_CLOCKING_1 0x208
+#define WM8915_CLOCKING_2 0x209
+#define WM8915_AIF_RATE 0x210
+#define WM8915_FLL_CONTROL_1 0x220
+#define WM8915_FLL_CONTROL_2 0x221
+#define WM8915_FLL_CONTROL_3 0x222
+#define WM8915_FLL_CONTROL_4 0x223
+#define WM8915_FLL_CONTROL_5 0x224
+#define WM8915_FLL_CONTROL_6 0x225
+#define WM8915_FLL_EFS_1 0x226
+#define WM8915_FLL_EFS_2 0x227
+#define WM8915_AIF1_CONTROL 0x300
+#define WM8915_AIF1_BCLK 0x301
+#define WM8915_AIF1_TX_LRCLK_1 0x302
+#define WM8915_AIF1_TX_LRCLK_2 0x303
+#define WM8915_AIF1_RX_LRCLK_1 0x304
+#define WM8915_AIF1_RX_LRCLK_2 0x305
+#define WM8915_AIF1TX_DATA_CONFIGURATION_1 0x306
+#define WM8915_AIF1TX_DATA_CONFIGURATION_2 0x307
+#define WM8915_AIF1RX_DATA_CONFIGURATION 0x308
+#define WM8915_AIF1TX_CHANNEL_0_CONFIGURATION 0x309
+#define WM8915_AIF1TX_CHANNEL_1_CONFIGURATION 0x30A
+#define WM8915_AIF1TX_CHANNEL_2_CONFIGURATION 0x30B
+#define WM8915_AIF1TX_CHANNEL_3_CONFIGURATION 0x30C
+#define WM8915_AIF1TX_CHANNEL_4_CONFIGURATION 0x30D
+#define WM8915_AIF1TX_CHANNEL_5_CONFIGURATION 0x30E
+#define WM8915_AIF1RX_CHANNEL_0_CONFIGURATION 0x30F
+#define WM8915_AIF1RX_CHANNEL_1_CONFIGURATION 0x310
+#define WM8915_AIF1RX_CHANNEL_2_CONFIGURATION 0x311
+#define WM8915_AIF1RX_CHANNEL_3_CONFIGURATION 0x312
+#define WM8915_AIF1RX_CHANNEL_4_CONFIGURATION 0x313
+#define WM8915_AIF1RX_CHANNEL_5_CONFIGURATION 0x314
+#define WM8915_AIF1RX_MONO_CONFIGURATION 0x315
+#define WM8915_AIF1TX_TEST 0x31A
+#define WM8915_AIF2_CONTROL 0x320
+#define WM8915_AIF2_BCLK 0x321
+#define WM8915_AIF2_TX_LRCLK_1 0x322
+#define WM8915_AIF2_TX_LRCLK_2 0x323
+#define WM8915_AIF2_RX_LRCLK_1 0x324
+#define WM8915_AIF2_RX_LRCLK_2 0x325
+#define WM8915_AIF2TX_DATA_CONFIGURATION_1 0x326
+#define WM8915_AIF2TX_DATA_CONFIGURATION_2 0x327
+#define WM8915_AIF2RX_DATA_CONFIGURATION 0x328
+#define WM8915_AIF2TX_CHANNEL_0_CONFIGURATION 0x329
+#define WM8915_AIF2TX_CHANNEL_1_CONFIGURATION 0x32A
+#define WM8915_AIF2RX_CHANNEL_0_CONFIGURATION 0x32B
+#define WM8915_AIF2RX_CHANNEL_1_CONFIGURATION 0x32C
+#define WM8915_AIF2RX_MONO_CONFIGURATION 0x32D
+#define WM8915_AIF2TX_TEST 0x32F
+#define WM8915_DSP1_TX_LEFT_VOLUME 0x400
+#define WM8915_DSP1_TX_RIGHT_VOLUME 0x401
+#define WM8915_DSP1_RX_LEFT_VOLUME 0x402
+#define WM8915_DSP1_RX_RIGHT_VOLUME 0x403
+#define WM8915_DSP1_TX_FILTERS 0x410
+#define WM8915_DSP1_RX_FILTERS_1 0x420
+#define WM8915_DSP1_RX_FILTERS_2 0x421
+#define WM8915_DSP1_DRC_1 0x440
+#define WM8915_DSP1_DRC_2 0x441
+#define WM8915_DSP1_DRC_3 0x442
+#define WM8915_DSP1_DRC_4 0x443
+#define WM8915_DSP1_DRC_5 0x444
+#define WM8915_DSP1_RX_EQ_GAINS_1 0x480
+#define WM8915_DSP1_RX_EQ_GAINS_2 0x481
+#define WM8915_DSP1_RX_EQ_BAND_1_A 0x482
+#define WM8915_DSP1_RX_EQ_BAND_1_B 0x483
+#define WM8915_DSP1_RX_EQ_BAND_1_PG 0x484
+#define WM8915_DSP1_RX_EQ_BAND_2_A 0x485
+#define WM8915_DSP1_RX_EQ_BAND_2_B 0x486
+#define WM8915_DSP1_RX_EQ_BAND_2_C 0x487
+#define WM8915_DSP1_RX_EQ_BAND_2_PG 0x488
+#define WM8915_DSP1_RX_EQ_BAND_3_A 0x489
+#define WM8915_DSP1_RX_EQ_BAND_3_B 0x48A
+#define WM8915_DSP1_RX_EQ_BAND_3_C 0x48B
+#define WM8915_DSP1_RX_EQ_BAND_3_PG 0x48C
+#define WM8915_DSP1_RX_EQ_BAND_4_A 0x48D
+#define WM8915_DSP1_RX_EQ_BAND_4_B 0x48E
+#define WM8915_DSP1_RX_EQ_BAND_4_C 0x48F
+#define WM8915_DSP1_RX_EQ_BAND_4_PG 0x490
+#define WM8915_DSP1_RX_EQ_BAND_5_A 0x491
+#define WM8915_DSP1_RX_EQ_BAND_5_B 0x492
+#define WM8915_DSP1_RX_EQ_BAND_5_PG 0x493
+#define WM8915_DSP2_TX_LEFT_VOLUME 0x500
+#define WM8915_DSP2_TX_RIGHT_VOLUME 0x501
+#define WM8915_DSP2_RX_LEFT_VOLUME 0x502
+#define WM8915_DSP2_RX_RIGHT_VOLUME 0x503
+#define WM8915_DSP2_TX_FILTERS 0x510
+#define WM8915_DSP2_RX_FILTERS_1 0x520
+#define WM8915_DSP2_RX_FILTERS_2 0x521
+#define WM8915_DSP2_DRC_1 0x540
+#define WM8915_DSP2_DRC_2 0x541
+#define WM8915_DSP2_DRC_3 0x542
+#define WM8915_DSP2_DRC_4 0x543
+#define WM8915_DSP2_DRC_5 0x544
+#define WM8915_DSP2_RX_EQ_GAINS_1 0x580
+#define WM8915_DSP2_RX_EQ_GAINS_2 0x581
+#define WM8915_DSP2_RX_EQ_BAND_1_A 0x582
+#define WM8915_DSP2_RX_EQ_BAND_1_B 0x583
+#define WM8915_DSP2_RX_EQ_BAND_1_PG 0x584
+#define WM8915_DSP2_RX_EQ_BAND_2_A 0x585
+#define WM8915_DSP2_RX_EQ_BAND_2_B 0x586
+#define WM8915_DSP2_RX_EQ_BAND_2_C 0x587
+#define WM8915_DSP2_RX_EQ_BAND_2_PG 0x588
+#define WM8915_DSP2_RX_EQ_BAND_3_A 0x589
+#define WM8915_DSP2_RX_EQ_BAND_3_B 0x58A
+#define WM8915_DSP2_RX_EQ_BAND_3_C 0x58B
+#define WM8915_DSP2_RX_EQ_BAND_3_PG 0x58C
+#define WM8915_DSP2_RX_EQ_BAND_4_A 0x58D
+#define WM8915_DSP2_RX_EQ_BAND_4_B 0x58E
+#define WM8915_DSP2_RX_EQ_BAND_4_C 0x58F
+#define WM8915_DSP2_RX_EQ_BAND_4_PG 0x590
+#define WM8915_DSP2_RX_EQ_BAND_5_A 0x591
+#define WM8915_DSP2_RX_EQ_BAND_5_B 0x592
+#define WM8915_DSP2_RX_EQ_BAND_5_PG 0x593
+#define WM8915_DAC1_MIXER_VOLUMES 0x600
+#define WM8915_DAC1_LEFT_MIXER_ROUTING 0x601
+#define WM8915_DAC1_RIGHT_MIXER_ROUTING 0x602
+#define WM8915_DAC2_MIXER_VOLUMES 0x603
+#define WM8915_DAC2_LEFT_MIXER_ROUTING 0x604
+#define WM8915_DAC2_RIGHT_MIXER_ROUTING 0x605
+#define WM8915_DSP1_TX_LEFT_MIXER_ROUTING 0x606
+#define WM8915_DSP1_TX_RIGHT_MIXER_ROUTING 0x607
+#define WM8915_DSP2_TX_LEFT_MIXER_ROUTING 0x608
+#define WM8915_DSP2_TX_RIGHT_MIXER_ROUTING 0x609
+#define WM8915_DSP_TX_MIXER_SELECT 0x60A
+#define WM8915_DAC_SOFTMUTE 0x610
+#define WM8915_OVERSAMPLING 0x620
+#define WM8915_SIDETONE 0x621
+#define WM8915_GPIO_1 0x700
+#define WM8915_GPIO_2 0x701
+#define WM8915_GPIO_3 0x702
+#define WM8915_GPIO_4 0x703
+#define WM8915_GPIO_5 0x704
+#define WM8915_PULL_CONTROL_1 0x720
+#define WM8915_PULL_CONTROL_2 0x721
+#define WM8915_INTERRUPT_STATUS_1 0x730
+#define WM8915_INTERRUPT_STATUS_2 0x731
+#define WM8915_INTERRUPT_RAW_STATUS_2 0x732
+#define WM8915_INTERRUPT_STATUS_1_MASK 0x738
+#define WM8915_INTERRUPT_STATUS_2_MASK 0x739
+#define WM8915_INTERRUPT_CONTROL 0x740
+#define WM8915_LEFT_PDM_SPEAKER 0x800
+#define WM8915_RIGHT_PDM_SPEAKER 0x801
+#define WM8915_PDM_SPEAKER_MUTE_SEQUENCE 0x802
+#define WM8915_PDM_SPEAKER_VOLUME 0x803
+#define WM8915_WRITE_SEQUENCER_0 0x3000
+#define WM8915_WRITE_SEQUENCER_1 0x3001
+#define WM8915_WRITE_SEQUENCER_2 0x3002
+#define WM8915_WRITE_SEQUENCER_3 0x3003
+#define WM8915_WRITE_SEQUENCER_4 0x3004
+#define WM8915_WRITE_SEQUENCER_5 0x3005
+#define WM8915_WRITE_SEQUENCER_6 0x3006
+#define WM8915_WRITE_SEQUENCER_7 0x3007
+#define WM8915_WRITE_SEQUENCER_8 0x3008
+#define WM8915_WRITE_SEQUENCER_9 0x3009
+#define WM8915_WRITE_SEQUENCER_10 0x300A
+#define WM8915_WRITE_SEQUENCER_11 0x300B
+#define WM8915_WRITE_SEQUENCER_12 0x300C
+#define WM8915_WRITE_SEQUENCER_13 0x300D
+#define WM8915_WRITE_SEQUENCER_14 0x300E
+#define WM8915_WRITE_SEQUENCER_15 0x300F
+#define WM8915_WRITE_SEQUENCER_16 0x3010
+#define WM8915_WRITE_SEQUENCER_17 0x3011
+#define WM8915_WRITE_SEQUENCER_18 0x3012
+#define WM8915_WRITE_SEQUENCER_19 0x3013
+#define WM8915_WRITE_SEQUENCER_20 0x3014
+#define WM8915_WRITE_SEQUENCER_21 0x3015
+#define WM8915_WRITE_SEQUENCER_22 0x3016
+#define WM8915_WRITE_SEQUENCER_23 0x3017
+#define WM8915_WRITE_SEQUENCER_24 0x3018
+#define WM8915_WRITE_SEQUENCER_25 0x3019
+#define WM8915_WRITE_SEQUENCER_26 0x301A
+#define WM8915_WRITE_SEQUENCER_27 0x301B
+#define WM8915_WRITE_SEQUENCER_28 0x301C
+#define WM8915_WRITE_SEQUENCER_29 0x301D
+#define WM8915_WRITE_SEQUENCER_30 0x301E
+#define WM8915_WRITE_SEQUENCER_31 0x301F
+#define WM8915_WRITE_SEQUENCER_32 0x3020
+#define WM8915_WRITE_SEQUENCER_33 0x3021
+#define WM8915_WRITE_SEQUENCER_34 0x3022
+#define WM8915_WRITE_SEQUENCER_35 0x3023
+#define WM8915_WRITE_SEQUENCER_36 0x3024
+#define WM8915_WRITE_SEQUENCER_37 0x3025
+#define WM8915_WRITE_SEQUENCER_38 0x3026
+#define WM8915_WRITE_SEQUENCER_39 0x3027
+#define WM8915_WRITE_SEQUENCER_40 0x3028
+#define WM8915_WRITE_SEQUENCER_41 0x3029
+#define WM8915_WRITE_SEQUENCER_42 0x302A
+#define WM8915_WRITE_SEQUENCER_43 0x302B
+#define WM8915_WRITE_SEQUENCER_44 0x302C
+#define WM8915_WRITE_SEQUENCER_45 0x302D
+#define WM8915_WRITE_SEQUENCER_46 0x302E
+#define WM8915_WRITE_SEQUENCER_47 0x302F
+#define WM8915_WRITE_SEQUENCER_48 0x3030
+#define WM8915_WRITE_SEQUENCER_49 0x3031
+#define WM8915_WRITE_SEQUENCER_50 0x3032
+#define WM8915_WRITE_SEQUENCER_51 0x3033
+#define WM8915_WRITE_SEQUENCER_52 0x3034
+#define WM8915_WRITE_SEQUENCER_53 0x3035
+#define WM8915_WRITE_SEQUENCER_54 0x3036
+#define WM8915_WRITE_SEQUENCER_55 0x3037
+#define WM8915_WRITE_SEQUENCER_56 0x3038
+#define WM8915_WRITE_SEQUENCER_57 0x3039
+#define WM8915_WRITE_SEQUENCER_58 0x303A
+#define WM8915_WRITE_SEQUENCER_59 0x303B
+#define WM8915_WRITE_SEQUENCER_60 0x303C
+#define WM8915_WRITE_SEQUENCER_61 0x303D
+#define WM8915_WRITE_SEQUENCER_62 0x303E
+#define WM8915_WRITE_SEQUENCER_63 0x303F
+#define WM8915_WRITE_SEQUENCER_64 0x3040
+#define WM8915_WRITE_SEQUENCER_65 0x3041
+#define WM8915_WRITE_SEQUENCER_66 0x3042
+#define WM8915_WRITE_SEQUENCER_67 0x3043
+#define WM8915_WRITE_SEQUENCER_68 0x3044
+#define WM8915_WRITE_SEQUENCER_69 0x3045
+#define WM8915_WRITE_SEQUENCER_70 0x3046
+#define WM8915_WRITE_SEQUENCER_71 0x3047
+#define WM8915_WRITE_SEQUENCER_72 0x3048
+#define WM8915_WRITE_SEQUENCER_73 0x3049
+#define WM8915_WRITE_SEQUENCER_74 0x304A
+#define WM8915_WRITE_SEQUENCER_75 0x304B
+#define WM8915_WRITE_SEQUENCER_76 0x304C
+#define WM8915_WRITE_SEQUENCER_77 0x304D
+#define WM8915_WRITE_SEQUENCER_78 0x304E
+#define WM8915_WRITE_SEQUENCER_79 0x304F
+#define WM8915_WRITE_SEQUENCER_80 0x3050
+#define WM8915_WRITE_SEQUENCER_81 0x3051
+#define WM8915_WRITE_SEQUENCER_82 0x3052
+#define WM8915_WRITE_SEQUENCER_83 0x3053
+#define WM8915_WRITE_SEQUENCER_84 0x3054
+#define WM8915_WRITE_SEQUENCER_85 0x3055
+#define WM8915_WRITE_SEQUENCER_86 0x3056
+#define WM8915_WRITE_SEQUENCER_87 0x3057
+#define WM8915_WRITE_SEQUENCER_88 0x3058
+#define WM8915_WRITE_SEQUENCER_89 0x3059
+#define WM8915_WRITE_SEQUENCER_90 0x305A
+#define WM8915_WRITE_SEQUENCER_91 0x305B
+#define WM8915_WRITE_SEQUENCER_92 0x305C
+#define WM8915_WRITE_SEQUENCER_93 0x305D
+#define WM8915_WRITE_SEQUENCER_94 0x305E
+#define WM8915_WRITE_SEQUENCER_95 0x305F
+#define WM8915_WRITE_SEQUENCER_96 0x3060
+#define WM8915_WRITE_SEQUENCER_97 0x3061
+#define WM8915_WRITE_SEQUENCER_98 0x3062
+#define WM8915_WRITE_SEQUENCER_99 0x3063
+#define WM8915_WRITE_SEQUENCER_100 0x3064
+#define WM8915_WRITE_SEQUENCER_101 0x3065
+#define WM8915_WRITE_SEQUENCER_102 0x3066
+#define WM8915_WRITE_SEQUENCER_103 0x3067
+#define WM8915_WRITE_SEQUENCER_104 0x3068
+#define WM8915_WRITE_SEQUENCER_105 0x3069
+#define WM8915_WRITE_SEQUENCER_106 0x306A
+#define WM8915_WRITE_SEQUENCER_107 0x306B
+#define WM8915_WRITE_SEQUENCER_108 0x306C
+#define WM8915_WRITE_SEQUENCER_109 0x306D
+#define WM8915_WRITE_SEQUENCER_110 0x306E
+#define WM8915_WRITE_SEQUENCER_111 0x306F
+#define WM8915_WRITE_SEQUENCER_112 0x3070
+#define WM8915_WRITE_SEQUENCER_113 0x3071
+#define WM8915_WRITE_SEQUENCER_114 0x3072
+#define WM8915_WRITE_SEQUENCER_115 0x3073
+#define WM8915_WRITE_SEQUENCER_116 0x3074
+#define WM8915_WRITE_SEQUENCER_117 0x3075
+#define WM8915_WRITE_SEQUENCER_118 0x3076
+#define WM8915_WRITE_SEQUENCER_119 0x3077
+#define WM8915_WRITE_SEQUENCER_120 0x3078
+#define WM8915_WRITE_SEQUENCER_121 0x3079
+#define WM8915_WRITE_SEQUENCER_122 0x307A
+#define WM8915_WRITE_SEQUENCER_123 0x307B
+#define WM8915_WRITE_SEQUENCER_124 0x307C
+#define WM8915_WRITE_SEQUENCER_125 0x307D
+#define WM8915_WRITE_SEQUENCER_126 0x307E
+#define WM8915_WRITE_SEQUENCER_127 0x307F
+#define WM8915_WRITE_SEQUENCER_128 0x3080
+#define WM8915_WRITE_SEQUENCER_129 0x3081
+#define WM8915_WRITE_SEQUENCER_130 0x3082
+#define WM8915_WRITE_SEQUENCER_131 0x3083
+#define WM8915_WRITE_SEQUENCER_132 0x3084
+#define WM8915_WRITE_SEQUENCER_133 0x3085
+#define WM8915_WRITE_SEQUENCER_134 0x3086
+#define WM8915_WRITE_SEQUENCER_135 0x3087
+#define WM8915_WRITE_SEQUENCER_136 0x3088
+#define WM8915_WRITE_SEQUENCER_137 0x3089
+#define WM8915_WRITE_SEQUENCER_138 0x308A
+#define WM8915_WRITE_SEQUENCER_139 0x308B
+#define WM8915_WRITE_SEQUENCER_140 0x308C
+#define WM8915_WRITE_SEQUENCER_141 0x308D
+#define WM8915_WRITE_SEQUENCER_142 0x308E
+#define WM8915_WRITE_SEQUENCER_143 0x308F
+#define WM8915_WRITE_SEQUENCER_144 0x3090
+#define WM8915_WRITE_SEQUENCER_145 0x3091
+#define WM8915_WRITE_SEQUENCER_146 0x3092
+#define WM8915_WRITE_SEQUENCER_147 0x3093
+#define WM8915_WRITE_SEQUENCER_148 0x3094
+#define WM8915_WRITE_SEQUENCER_149 0x3095
+#define WM8915_WRITE_SEQUENCER_150 0x3096
+#define WM8915_WRITE_SEQUENCER_151 0x3097
+#define WM8915_WRITE_SEQUENCER_152 0x3098
+#define WM8915_WRITE_SEQUENCER_153 0x3099
+#define WM8915_WRITE_SEQUENCER_154 0x309A
+#define WM8915_WRITE_SEQUENCER_155 0x309B
+#define WM8915_WRITE_SEQUENCER_156 0x309C
+#define WM8915_WRITE_SEQUENCER_157 0x309D
+#define WM8915_WRITE_SEQUENCER_158 0x309E
+#define WM8915_WRITE_SEQUENCER_159 0x309F
+#define WM8915_WRITE_SEQUENCER_160 0x30A0
+#define WM8915_WRITE_SEQUENCER_161 0x30A1
+#define WM8915_WRITE_SEQUENCER_162 0x30A2
+#define WM8915_WRITE_SEQUENCER_163 0x30A3
+#define WM8915_WRITE_SEQUENCER_164 0x30A4
+#define WM8915_WRITE_SEQUENCER_165 0x30A5
+#define WM8915_WRITE_SEQUENCER_166 0x30A6
+#define WM8915_WRITE_SEQUENCER_167 0x30A7
+#define WM8915_WRITE_SEQUENCER_168 0x30A8
+#define WM8915_WRITE_SEQUENCER_169 0x30A9
+#define WM8915_WRITE_SEQUENCER_170 0x30AA
+#define WM8915_WRITE_SEQUENCER_171 0x30AB
+#define WM8915_WRITE_SEQUENCER_172 0x30AC
+#define WM8915_WRITE_SEQUENCER_173 0x30AD
+#define WM8915_WRITE_SEQUENCER_174 0x30AE
+#define WM8915_WRITE_SEQUENCER_175 0x30AF
+#define WM8915_WRITE_SEQUENCER_176 0x30B0
+#define WM8915_WRITE_SEQUENCER_177 0x30B1
+#define WM8915_WRITE_SEQUENCER_178 0x30B2
+#define WM8915_WRITE_SEQUENCER_179 0x30B3
+#define WM8915_WRITE_SEQUENCER_180 0x30B4
+#define WM8915_WRITE_SEQUENCER_181 0x30B5
+#define WM8915_WRITE_SEQUENCER_182 0x30B6
+#define WM8915_WRITE_SEQUENCER_183 0x30B7
+#define WM8915_WRITE_SEQUENCER_184 0x30B8
+#define WM8915_WRITE_SEQUENCER_185 0x30B9
+#define WM8915_WRITE_SEQUENCER_186 0x30BA
+#define WM8915_WRITE_SEQUENCER_187 0x30BB
+#define WM8915_WRITE_SEQUENCER_188 0x30BC
+#define WM8915_WRITE_SEQUENCER_189 0x30BD
+#define WM8915_WRITE_SEQUENCER_190 0x30BE
+#define WM8915_WRITE_SEQUENCER_191 0x30BF
+#define WM8915_WRITE_SEQUENCER_192 0x30C0
+#define WM8915_WRITE_SEQUENCER_193 0x30C1
+#define WM8915_WRITE_SEQUENCER_194 0x30C2
+#define WM8915_WRITE_SEQUENCER_195 0x30C3
+#define WM8915_WRITE_SEQUENCER_196 0x30C4
+#define WM8915_WRITE_SEQUENCER_197 0x30C5
+#define WM8915_WRITE_SEQUENCER_198 0x30C6
+#define WM8915_WRITE_SEQUENCER_199 0x30C7
+#define WM8915_WRITE_SEQUENCER_200 0x30C8
+#define WM8915_WRITE_SEQUENCER_201 0x30C9
+#define WM8915_WRITE_SEQUENCER_202 0x30CA
+#define WM8915_WRITE_SEQUENCER_203 0x30CB
+#define WM8915_WRITE_SEQUENCER_204 0x30CC
+#define WM8915_WRITE_SEQUENCER_205 0x30CD
+#define WM8915_WRITE_SEQUENCER_206 0x30CE
+#define WM8915_WRITE_SEQUENCER_207 0x30CF
+#define WM8915_WRITE_SEQUENCER_208 0x30D0
+#define WM8915_WRITE_SEQUENCER_209 0x30D1
+#define WM8915_WRITE_SEQUENCER_210 0x30D2
+#define WM8915_WRITE_SEQUENCER_211 0x30D3
+#define WM8915_WRITE_SEQUENCER_212 0x30D4
+#define WM8915_WRITE_SEQUENCER_213 0x30D5
+#define WM8915_WRITE_SEQUENCER_214 0x30D6
+#define WM8915_WRITE_SEQUENCER_215 0x30D7
+#define WM8915_WRITE_SEQUENCER_216 0x30D8
+#define WM8915_WRITE_SEQUENCER_217 0x30D9
+#define WM8915_WRITE_SEQUENCER_218 0x30DA
+#define WM8915_WRITE_SEQUENCER_219 0x30DB
+#define WM8915_WRITE_SEQUENCER_220 0x30DC
+#define WM8915_WRITE_SEQUENCER_221 0x30DD
+#define WM8915_WRITE_SEQUENCER_222 0x30DE
+#define WM8915_WRITE_SEQUENCER_223 0x30DF
+#define WM8915_WRITE_SEQUENCER_224 0x30E0
+#define WM8915_WRITE_SEQUENCER_225 0x30E1
+#define WM8915_WRITE_SEQUENCER_226 0x30E2
+#define WM8915_WRITE_SEQUENCER_227 0x30E3
+#define WM8915_WRITE_SEQUENCER_228 0x30E4
+#define WM8915_WRITE_SEQUENCER_229 0x30E5
+#define WM8915_WRITE_SEQUENCER_230 0x30E6
+#define WM8915_WRITE_SEQUENCER_231 0x30E7
+#define WM8915_WRITE_SEQUENCER_232 0x30E8
+#define WM8915_WRITE_SEQUENCER_233 0x30E9
+#define WM8915_WRITE_SEQUENCER_234 0x30EA
+#define WM8915_WRITE_SEQUENCER_235 0x30EB
+#define WM8915_WRITE_SEQUENCER_236 0x30EC
+#define WM8915_WRITE_SEQUENCER_237 0x30ED
+#define WM8915_WRITE_SEQUENCER_238 0x30EE
+#define WM8915_WRITE_SEQUENCER_239 0x30EF
+#define WM8915_WRITE_SEQUENCER_240 0x30F0
+#define WM8915_WRITE_SEQUENCER_241 0x30F1
+#define WM8915_WRITE_SEQUENCER_242 0x30F2
+#define WM8915_WRITE_SEQUENCER_243 0x30F3
+#define WM8915_WRITE_SEQUENCER_244 0x30F4
+#define WM8915_WRITE_SEQUENCER_245 0x30F5
+#define WM8915_WRITE_SEQUENCER_246 0x30F6
+#define WM8915_WRITE_SEQUENCER_247 0x30F7
+#define WM8915_WRITE_SEQUENCER_248 0x30F8
+#define WM8915_WRITE_SEQUENCER_249 0x30F9
+#define WM8915_WRITE_SEQUENCER_250 0x30FA
+#define WM8915_WRITE_SEQUENCER_251 0x30FB
+#define WM8915_WRITE_SEQUENCER_252 0x30FC
+#define WM8915_WRITE_SEQUENCER_253 0x30FD
+#define WM8915_WRITE_SEQUENCER_254 0x30FE
+#define WM8915_WRITE_SEQUENCER_255 0x30FF
+#define WM8915_WRITE_SEQUENCER_256 0x3100
+#define WM8915_WRITE_SEQUENCER_257 0x3101
+#define WM8915_WRITE_SEQUENCER_258 0x3102
+#define WM8915_WRITE_SEQUENCER_259 0x3103
+#define WM8915_WRITE_SEQUENCER_260 0x3104
+#define WM8915_WRITE_SEQUENCER_261 0x3105
+#define WM8915_WRITE_SEQUENCER_262 0x3106
+#define WM8915_WRITE_SEQUENCER_263 0x3107
+#define WM8915_WRITE_SEQUENCER_264 0x3108
+#define WM8915_WRITE_SEQUENCER_265 0x3109
+#define WM8915_WRITE_SEQUENCER_266 0x310A
+#define WM8915_WRITE_SEQUENCER_267 0x310B
+#define WM8915_WRITE_SEQUENCER_268 0x310C
+#define WM8915_WRITE_SEQUENCER_269 0x310D
+#define WM8915_WRITE_SEQUENCER_270 0x310E
+#define WM8915_WRITE_SEQUENCER_271 0x310F
+#define WM8915_WRITE_SEQUENCER_272 0x3110
+#define WM8915_WRITE_SEQUENCER_273 0x3111
+#define WM8915_WRITE_SEQUENCER_274 0x3112
+#define WM8915_WRITE_SEQUENCER_275 0x3113
+#define WM8915_WRITE_SEQUENCER_276 0x3114
+#define WM8915_WRITE_SEQUENCER_277 0x3115
+#define WM8915_WRITE_SEQUENCER_278 0x3116
+#define WM8915_WRITE_SEQUENCER_279 0x3117
+#define WM8915_WRITE_SEQUENCER_280 0x3118
+#define WM8915_WRITE_SEQUENCER_281 0x3119
+#define WM8915_WRITE_SEQUENCER_282 0x311A
+#define WM8915_WRITE_SEQUENCER_283 0x311B
+#define WM8915_WRITE_SEQUENCER_284 0x311C
+#define WM8915_WRITE_SEQUENCER_285 0x311D
+#define WM8915_WRITE_SEQUENCER_286 0x311E
+#define WM8915_WRITE_SEQUENCER_287 0x311F
+#define WM8915_WRITE_SEQUENCER_288 0x3120
+#define WM8915_WRITE_SEQUENCER_289 0x3121
+#define WM8915_WRITE_SEQUENCER_290 0x3122
+#define WM8915_WRITE_SEQUENCER_291 0x3123
+#define WM8915_WRITE_SEQUENCER_292 0x3124
+#define WM8915_WRITE_SEQUENCER_293 0x3125
+#define WM8915_WRITE_SEQUENCER_294 0x3126
+#define WM8915_WRITE_SEQUENCER_295 0x3127
+#define WM8915_WRITE_SEQUENCER_296 0x3128
+#define WM8915_WRITE_SEQUENCER_297 0x3129
+#define WM8915_WRITE_SEQUENCER_298 0x312A
+#define WM8915_WRITE_SEQUENCER_299 0x312B
+#define WM8915_WRITE_SEQUENCER_300 0x312C
+#define WM8915_WRITE_SEQUENCER_301 0x312D
+#define WM8915_WRITE_SEQUENCER_302 0x312E
+#define WM8915_WRITE_SEQUENCER_303 0x312F
+#define WM8915_WRITE_SEQUENCER_304 0x3130
+#define WM8915_WRITE_SEQUENCER_305 0x3131
+#define WM8915_WRITE_SEQUENCER_306 0x3132
+#define WM8915_WRITE_SEQUENCER_307 0x3133
+#define WM8915_WRITE_SEQUENCER_308 0x3134
+#define WM8915_WRITE_SEQUENCER_309 0x3135
+#define WM8915_WRITE_SEQUENCER_310 0x3136
+#define WM8915_WRITE_SEQUENCER_311 0x3137
+#define WM8915_WRITE_SEQUENCER_312 0x3138
+#define WM8915_WRITE_SEQUENCER_313 0x3139
+#define WM8915_WRITE_SEQUENCER_314 0x313A
+#define WM8915_WRITE_SEQUENCER_315 0x313B
+#define WM8915_WRITE_SEQUENCER_316 0x313C
+#define WM8915_WRITE_SEQUENCER_317 0x313D
+#define WM8915_WRITE_SEQUENCER_318 0x313E
+#define WM8915_WRITE_SEQUENCER_319 0x313F
+#define WM8915_WRITE_SEQUENCER_320 0x3140
+#define WM8915_WRITE_SEQUENCER_321 0x3141
+#define WM8915_WRITE_SEQUENCER_322 0x3142
+#define WM8915_WRITE_SEQUENCER_323 0x3143
+#define WM8915_WRITE_SEQUENCER_324 0x3144
+#define WM8915_WRITE_SEQUENCER_325 0x3145
+#define WM8915_WRITE_SEQUENCER_326 0x3146
+#define WM8915_WRITE_SEQUENCER_327 0x3147
+#define WM8915_WRITE_SEQUENCER_328 0x3148
+#define WM8915_WRITE_SEQUENCER_329 0x3149
+#define WM8915_WRITE_SEQUENCER_330 0x314A
+#define WM8915_WRITE_SEQUENCER_331 0x314B
+#define WM8915_WRITE_SEQUENCER_332 0x314C
+#define WM8915_WRITE_SEQUENCER_333 0x314D
+#define WM8915_WRITE_SEQUENCER_334 0x314E
+#define WM8915_WRITE_SEQUENCER_335 0x314F
+#define WM8915_WRITE_SEQUENCER_336 0x3150
+#define WM8915_WRITE_SEQUENCER_337 0x3151
+#define WM8915_WRITE_SEQUENCER_338 0x3152
+#define WM8915_WRITE_SEQUENCER_339 0x3153
+#define WM8915_WRITE_SEQUENCER_340 0x3154
+#define WM8915_WRITE_SEQUENCER_341 0x3155
+#define WM8915_WRITE_SEQUENCER_342 0x3156
+#define WM8915_WRITE_SEQUENCER_343 0x3157
+#define WM8915_WRITE_SEQUENCER_344 0x3158
+#define WM8915_WRITE_SEQUENCER_345 0x3159
+#define WM8915_WRITE_SEQUENCER_346 0x315A
+#define WM8915_WRITE_SEQUENCER_347 0x315B
+#define WM8915_WRITE_SEQUENCER_348 0x315C
+#define WM8915_WRITE_SEQUENCER_349 0x315D
+#define WM8915_WRITE_SEQUENCER_350 0x315E
+#define WM8915_WRITE_SEQUENCER_351 0x315F
+#define WM8915_WRITE_SEQUENCER_352 0x3160
+#define WM8915_WRITE_SEQUENCER_353 0x3161
+#define WM8915_WRITE_SEQUENCER_354 0x3162
+#define WM8915_WRITE_SEQUENCER_355 0x3163
+#define WM8915_WRITE_SEQUENCER_356 0x3164
+#define WM8915_WRITE_SEQUENCER_357 0x3165
+#define WM8915_WRITE_SEQUENCER_358 0x3166
+#define WM8915_WRITE_SEQUENCER_359 0x3167
+#define WM8915_WRITE_SEQUENCER_360 0x3168
+#define WM8915_WRITE_SEQUENCER_361 0x3169
+#define WM8915_WRITE_SEQUENCER_362 0x316A
+#define WM8915_WRITE_SEQUENCER_363 0x316B
+#define WM8915_WRITE_SEQUENCER_364 0x316C
+#define WM8915_WRITE_SEQUENCER_365 0x316D
+#define WM8915_WRITE_SEQUENCER_366 0x316E
+#define WM8915_WRITE_SEQUENCER_367 0x316F
+#define WM8915_WRITE_SEQUENCER_368 0x3170
+#define WM8915_WRITE_SEQUENCER_369 0x3171
+#define WM8915_WRITE_SEQUENCER_370 0x3172
+#define WM8915_WRITE_SEQUENCER_371 0x3173
+#define WM8915_WRITE_SEQUENCER_372 0x3174
+#define WM8915_WRITE_SEQUENCER_373 0x3175
+#define WM8915_WRITE_SEQUENCER_374 0x3176
+#define WM8915_WRITE_SEQUENCER_375 0x3177
+#define WM8915_WRITE_SEQUENCER_376 0x3178
+#define WM8915_WRITE_SEQUENCER_377 0x3179
+#define WM8915_WRITE_SEQUENCER_378 0x317A
+#define WM8915_WRITE_SEQUENCER_379 0x317B
+#define WM8915_WRITE_SEQUENCER_380 0x317C
+#define WM8915_WRITE_SEQUENCER_381 0x317D
+#define WM8915_WRITE_SEQUENCER_382 0x317E
+#define WM8915_WRITE_SEQUENCER_383 0x317F
+#define WM8915_WRITE_SEQUENCER_384 0x3180
+#define WM8915_WRITE_SEQUENCER_385 0x3181
+#define WM8915_WRITE_SEQUENCER_386 0x3182
+#define WM8915_WRITE_SEQUENCER_387 0x3183
+#define WM8915_WRITE_SEQUENCER_388 0x3184
+#define WM8915_WRITE_SEQUENCER_389 0x3185
+#define WM8915_WRITE_SEQUENCER_390 0x3186
+#define WM8915_WRITE_SEQUENCER_391 0x3187
+#define WM8915_WRITE_SEQUENCER_392 0x3188
+#define WM8915_WRITE_SEQUENCER_393 0x3189
+#define WM8915_WRITE_SEQUENCER_394 0x318A
+#define WM8915_WRITE_SEQUENCER_395 0x318B
+#define WM8915_WRITE_SEQUENCER_396 0x318C
+#define WM8915_WRITE_SEQUENCER_397 0x318D
+#define WM8915_WRITE_SEQUENCER_398 0x318E
+#define WM8915_WRITE_SEQUENCER_399 0x318F
+#define WM8915_WRITE_SEQUENCER_400 0x3190
+#define WM8915_WRITE_SEQUENCER_401 0x3191
+#define WM8915_WRITE_SEQUENCER_402 0x3192
+#define WM8915_WRITE_SEQUENCER_403 0x3193
+#define WM8915_WRITE_SEQUENCER_404 0x3194
+#define WM8915_WRITE_SEQUENCER_405 0x3195
+#define WM8915_WRITE_SEQUENCER_406 0x3196
+#define WM8915_WRITE_SEQUENCER_407 0x3197
+#define WM8915_WRITE_SEQUENCER_408 0x3198
+#define WM8915_WRITE_SEQUENCER_409 0x3199
+#define WM8915_WRITE_SEQUENCER_410 0x319A
+#define WM8915_WRITE_SEQUENCER_411 0x319B
+#define WM8915_WRITE_SEQUENCER_412 0x319C
+#define WM8915_WRITE_SEQUENCER_413 0x319D
+#define WM8915_WRITE_SEQUENCER_414 0x319E
+#define WM8915_WRITE_SEQUENCER_415 0x319F
+#define WM8915_WRITE_SEQUENCER_416 0x31A0
+#define WM8915_WRITE_SEQUENCER_417 0x31A1
+#define WM8915_WRITE_SEQUENCER_418 0x31A2
+#define WM8915_WRITE_SEQUENCER_419 0x31A3
+#define WM8915_WRITE_SEQUENCER_420 0x31A4
+#define WM8915_WRITE_SEQUENCER_421 0x31A5
+#define WM8915_WRITE_SEQUENCER_422 0x31A6
+#define WM8915_WRITE_SEQUENCER_423 0x31A7
+#define WM8915_WRITE_SEQUENCER_424 0x31A8
+#define WM8915_WRITE_SEQUENCER_425 0x31A9
+#define WM8915_WRITE_SEQUENCER_426 0x31AA
+#define WM8915_WRITE_SEQUENCER_427 0x31AB
+#define WM8915_WRITE_SEQUENCER_428 0x31AC
+#define WM8915_WRITE_SEQUENCER_429 0x31AD
+#define WM8915_WRITE_SEQUENCER_430 0x31AE
+#define WM8915_WRITE_SEQUENCER_431 0x31AF
+#define WM8915_WRITE_SEQUENCER_432 0x31B0
+#define WM8915_WRITE_SEQUENCER_433 0x31B1
+#define WM8915_WRITE_SEQUENCER_434 0x31B2
+#define WM8915_WRITE_SEQUENCER_435 0x31B3
+#define WM8915_WRITE_SEQUENCER_436 0x31B4
+#define WM8915_WRITE_SEQUENCER_437 0x31B5
+#define WM8915_WRITE_SEQUENCER_438 0x31B6
+#define WM8915_WRITE_SEQUENCER_439 0x31B7
+#define WM8915_WRITE_SEQUENCER_440 0x31B8
+#define WM8915_WRITE_SEQUENCER_441 0x31B9
+#define WM8915_WRITE_SEQUENCER_442 0x31BA
+#define WM8915_WRITE_SEQUENCER_443 0x31BB
+#define WM8915_WRITE_SEQUENCER_444 0x31BC
+#define WM8915_WRITE_SEQUENCER_445 0x31BD
+#define WM8915_WRITE_SEQUENCER_446 0x31BE
+#define WM8915_WRITE_SEQUENCER_447 0x31BF
+#define WM8915_WRITE_SEQUENCER_448 0x31C0
+#define WM8915_WRITE_SEQUENCER_449 0x31C1
+#define WM8915_WRITE_SEQUENCER_450 0x31C2
+#define WM8915_WRITE_SEQUENCER_451 0x31C3
+#define WM8915_WRITE_SEQUENCER_452 0x31C4
+#define WM8915_WRITE_SEQUENCER_453 0x31C5
+#define WM8915_WRITE_SEQUENCER_454 0x31C6
+#define WM8915_WRITE_SEQUENCER_455 0x31C7
+#define WM8915_WRITE_SEQUENCER_456 0x31C8
+#define WM8915_WRITE_SEQUENCER_457 0x31C9
+#define WM8915_WRITE_SEQUENCER_458 0x31CA
+#define WM8915_WRITE_SEQUENCER_459 0x31CB
+#define WM8915_WRITE_SEQUENCER_460 0x31CC
+#define WM8915_WRITE_SEQUENCER_461 0x31CD
+#define WM8915_WRITE_SEQUENCER_462 0x31CE
+#define WM8915_WRITE_SEQUENCER_463 0x31CF
+#define WM8915_WRITE_SEQUENCER_464 0x31D0
+#define WM8915_WRITE_SEQUENCER_465 0x31D1
+#define WM8915_WRITE_SEQUENCER_466 0x31D2
+#define WM8915_WRITE_SEQUENCER_467 0x31D3
+#define WM8915_WRITE_SEQUENCER_468 0x31D4
+#define WM8915_WRITE_SEQUENCER_469 0x31D5
+#define WM8915_WRITE_SEQUENCER_470 0x31D6
+#define WM8915_WRITE_SEQUENCER_471 0x31D7
+#define WM8915_WRITE_SEQUENCER_472 0x31D8
+#define WM8915_WRITE_SEQUENCER_473 0x31D9
+#define WM8915_WRITE_SEQUENCER_474 0x31DA
+#define WM8915_WRITE_SEQUENCER_475 0x31DB
+#define WM8915_WRITE_SEQUENCER_476 0x31DC
+#define WM8915_WRITE_SEQUENCER_477 0x31DD
+#define WM8915_WRITE_SEQUENCER_478 0x31DE
+#define WM8915_WRITE_SEQUENCER_479 0x31DF
+#define WM8915_WRITE_SEQUENCER_480 0x31E0
+#define WM8915_WRITE_SEQUENCER_481 0x31E1
+#define WM8915_WRITE_SEQUENCER_482 0x31E2
+#define WM8915_WRITE_SEQUENCER_483 0x31E3
+#define WM8915_WRITE_SEQUENCER_484 0x31E4
+#define WM8915_WRITE_SEQUENCER_485 0x31E5
+#define WM8915_WRITE_SEQUENCER_486 0x31E6
+#define WM8915_WRITE_SEQUENCER_487 0x31E7
+#define WM8915_WRITE_SEQUENCER_488 0x31E8
+#define WM8915_WRITE_SEQUENCER_489 0x31E9
+#define WM8915_WRITE_SEQUENCER_490 0x31EA
+#define WM8915_WRITE_SEQUENCER_491 0x31EB
+#define WM8915_WRITE_SEQUENCER_492 0x31EC
+#define WM8915_WRITE_SEQUENCER_493 0x31ED
+#define WM8915_WRITE_SEQUENCER_494 0x31EE
+#define WM8915_WRITE_SEQUENCER_495 0x31EF
+#define WM8915_WRITE_SEQUENCER_496 0x31F0
+#define WM8915_WRITE_SEQUENCER_497 0x31F1
+#define WM8915_WRITE_SEQUENCER_498 0x31F2
+#define WM8915_WRITE_SEQUENCER_499 0x31F3
+#define WM8915_WRITE_SEQUENCER_500 0x31F4
+#define WM8915_WRITE_SEQUENCER_501 0x31F5
+#define WM8915_WRITE_SEQUENCER_502 0x31F6
+#define WM8915_WRITE_SEQUENCER_503 0x31F7
+#define WM8915_WRITE_SEQUENCER_504 0x31F8
+#define WM8915_WRITE_SEQUENCER_505 0x31F9
+#define WM8915_WRITE_SEQUENCER_506 0x31FA
+#define WM8915_WRITE_SEQUENCER_507 0x31FB
+#define WM8915_WRITE_SEQUENCER_508 0x31FC
+#define WM8915_WRITE_SEQUENCER_509 0x31FD
+#define WM8915_WRITE_SEQUENCER_510 0x31FE
+#define WM8915_WRITE_SEQUENCER_511 0x31FF
+
+#define WM8915_REGISTER_COUNT 706
+#define WM8915_MAX_REGISTER 0x31FF
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Software Reset
+ */
+#define WM8915_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */
+#define WM8915_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */
+#define WM8915_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM8915_MICB2_ENA 0x0200 /* MICB2_ENA */
+#define WM8915_MICB2_ENA_MASK 0x0200 /* MICB2_ENA */
+#define WM8915_MICB2_ENA_SHIFT 9 /* MICB2_ENA */
+#define WM8915_MICB2_ENA_WIDTH 1 /* MICB2_ENA */
+#define WM8915_MICB1_ENA 0x0100 /* MICB1_ENA */
+#define WM8915_MICB1_ENA_MASK 0x0100 /* MICB1_ENA */
+#define WM8915_MICB1_ENA_SHIFT 8 /* MICB1_ENA */
+#define WM8915_MICB1_ENA_WIDTH 1 /* MICB1_ENA */
+#define WM8915_HPOUT2L_ENA 0x0080 /* HPOUT2L_ENA */
+#define WM8915_HPOUT2L_ENA_MASK 0x0080 /* HPOUT2L_ENA */
+#define WM8915_HPOUT2L_ENA_SHIFT 7 /* HPOUT2L_ENA */
+#define WM8915_HPOUT2L_ENA_WIDTH 1 /* HPOUT2L_ENA */
+#define WM8915_HPOUT2R_ENA 0x0040 /* HPOUT2R_ENA */
+#define WM8915_HPOUT2R_ENA_MASK 0x0040 /* HPOUT2R_ENA */
+#define WM8915_HPOUT2R_ENA_SHIFT 6 /* HPOUT2R_ENA */
+#define WM8915_HPOUT2R_ENA_WIDTH 1 /* HPOUT2R_ENA */
+#define WM8915_HPOUT1L_ENA 0x0020 /* HPOUT1L_ENA */
+#define WM8915_HPOUT1L_ENA_MASK 0x0020 /* HPOUT1L_ENA */
+#define WM8915_HPOUT1L_ENA_SHIFT 5 /* HPOUT1L_ENA */
+#define WM8915_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */
+#define WM8915_HPOUT1R_ENA 0x0010 /* HPOUT1R_ENA */
+#define WM8915_HPOUT1R_ENA_MASK 0x0010 /* HPOUT1R_ENA */
+#define WM8915_HPOUT1R_ENA_SHIFT 4 /* HPOUT1R_ENA */
+#define WM8915_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */
+#define WM8915_BG_ENA 0x0001 /* BG_ENA */
+#define WM8915_BG_ENA_MASK 0x0001 /* BG_ENA */
+#define WM8915_BG_ENA_SHIFT 0 /* BG_ENA */
+#define WM8915_BG_ENA_WIDTH 1 /* BG_ENA */
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM8915_OPCLK_ENA 0x0800 /* OPCLK_ENA */
+#define WM8915_OPCLK_ENA_MASK 0x0800 /* OPCLK_ENA */
+#define WM8915_OPCLK_ENA_SHIFT 11 /* OPCLK_ENA */
+#define WM8915_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */
+#define WM8915_INL_ENA 0x0020 /* INL_ENA */
+#define WM8915_INL_ENA_MASK 0x0020 /* INL_ENA */
+#define WM8915_INL_ENA_SHIFT 5 /* INL_ENA */
+#define WM8915_INL_ENA_WIDTH 1 /* INL_ENA */
+#define WM8915_INR_ENA 0x0010 /* INR_ENA */
+#define WM8915_INR_ENA_MASK 0x0010 /* INR_ENA */
+#define WM8915_INR_ENA_SHIFT 4 /* INR_ENA */
+#define WM8915_INR_ENA_WIDTH 1 /* INR_ENA */
+#define WM8915_LDO2_ENA 0x0002 /* LDO2_ENA */
+#define WM8915_LDO2_ENA_MASK 0x0002 /* LDO2_ENA */
+#define WM8915_LDO2_ENA_SHIFT 1 /* LDO2_ENA */
+#define WM8915_LDO2_ENA_WIDTH 1 /* LDO2_ENA */
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM8915_DSP2RXL_ENA 0x0800 /* DSP2RXL_ENA */
+#define WM8915_DSP2RXL_ENA_MASK 0x0800 /* DSP2RXL_ENA */
+#define WM8915_DSP2RXL_ENA_SHIFT 11 /* DSP2RXL_ENA */
+#define WM8915_DSP2RXL_ENA_WIDTH 1 /* DSP2RXL_ENA */
+#define WM8915_DSP2RXR_ENA 0x0400 /* DSP2RXR_ENA */
+#define WM8915_DSP2RXR_ENA_MASK 0x0400 /* DSP2RXR_ENA */
+#define WM8915_DSP2RXR_ENA_SHIFT 10 /* DSP2RXR_ENA */
+#define WM8915_DSP2RXR_ENA_WIDTH 1 /* DSP2RXR_ENA */
+#define WM8915_DSP1RXL_ENA 0x0200 /* DSP1RXL_ENA */
+#define WM8915_DSP1RXL_ENA_MASK 0x0200 /* DSP1RXL_ENA */
+#define WM8915_DSP1RXL_ENA_SHIFT 9 /* DSP1RXL_ENA */
+#define WM8915_DSP1RXL_ENA_WIDTH 1 /* DSP1RXL_ENA */
+#define WM8915_DSP1RXR_ENA 0x0100 /* DSP1RXR_ENA */
+#define WM8915_DSP1RXR_ENA_MASK 0x0100 /* DSP1RXR_ENA */
+#define WM8915_DSP1RXR_ENA_SHIFT 8 /* DSP1RXR_ENA */
+#define WM8915_DSP1RXR_ENA_WIDTH 1 /* DSP1RXR_ENA */
+#define WM8915_DMIC2L_ENA 0x0020 /* DMIC2L_ENA */
+#define WM8915_DMIC2L_ENA_MASK 0x0020 /* DMIC2L_ENA */
+#define WM8915_DMIC2L_ENA_SHIFT 5 /* DMIC2L_ENA */
+#define WM8915_DMIC2L_ENA_WIDTH 1 /* DMIC2L_ENA */
+#define WM8915_DMIC2R_ENA 0x0010 /* DMIC2R_ENA */
+#define WM8915_DMIC2R_ENA_MASK 0x0010 /* DMIC2R_ENA */
+#define WM8915_DMIC2R_ENA_SHIFT 4 /* DMIC2R_ENA */
+#define WM8915_DMIC2R_ENA_WIDTH 1 /* DMIC2R_ENA */
+#define WM8915_DMIC1L_ENA 0x0008 /* DMIC1L_ENA */
+#define WM8915_DMIC1L_ENA_MASK 0x0008 /* DMIC1L_ENA */
+#define WM8915_DMIC1L_ENA_SHIFT 3 /* DMIC1L_ENA */
+#define WM8915_DMIC1L_ENA_WIDTH 1 /* DMIC1L_ENA */
+#define WM8915_DMIC1R_ENA 0x0004 /* DMIC1R_ENA */
+#define WM8915_DMIC1R_ENA_MASK 0x0004 /* DMIC1R_ENA */
+#define WM8915_DMIC1R_ENA_SHIFT 2 /* DMIC1R_ENA */
+#define WM8915_DMIC1R_ENA_WIDTH 1 /* DMIC1R_ENA */
+#define WM8915_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8915_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */
+#define WM8915_ADCL_ENA_SHIFT 1 /* ADCL_ENA */
+#define WM8915_ADCL_ENA_WIDTH 1 /* ADCL_ENA */
+#define WM8915_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8915_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */
+#define WM8915_ADCR_ENA_SHIFT 0 /* ADCR_ENA */
+#define WM8915_ADCR_ENA_WIDTH 1 /* ADCR_ENA */
+
+/*
+ * R4 (0x04) - Power Management (4)
+ */
+#define WM8915_AIF2RX_CHAN1_ENA 0x0200 /* AIF2RX_CHAN1_ENA */
+#define WM8915_AIF2RX_CHAN1_ENA_MASK 0x0200 /* AIF2RX_CHAN1_ENA */
+#define WM8915_AIF2RX_CHAN1_ENA_SHIFT 9 /* AIF2RX_CHAN1_ENA */
+#define WM8915_AIF2RX_CHAN1_ENA_WIDTH 1 /* AIF2RX_CHAN1_ENA */
+#define WM8915_AIF2RX_CHAN0_ENA 0x0100 /* AIF2RX_CHAN0_ENA */
+#define WM8915_AIF2RX_CHAN0_ENA_MASK 0x0100 /* AIF2RX_CHAN0_ENA */
+#define WM8915_AIF2RX_CHAN0_ENA_SHIFT 8 /* AIF2RX_CHAN0_ENA */
+#define WM8915_AIF2RX_CHAN0_ENA_WIDTH 1 /* AIF2RX_CHAN0_ENA */
+#define WM8915_AIF1RX_CHAN5_ENA 0x0020 /* AIF1RX_CHAN5_ENA */
+#define WM8915_AIF1RX_CHAN5_ENA_MASK 0x0020 /* AIF1RX_CHAN5_ENA */
+#define WM8915_AIF1RX_CHAN5_ENA_SHIFT 5 /* AIF1RX_CHAN5_ENA */
+#define WM8915_AIF1RX_CHAN5_ENA_WIDTH 1 /* AIF1RX_CHAN5_ENA */
+#define WM8915_AIF1RX_CHAN4_ENA 0x0010 /* AIF1RX_CHAN4_ENA */
+#define WM8915_AIF1RX_CHAN4_ENA_MASK 0x0010 /* AIF1RX_CHAN4_ENA */
+#define WM8915_AIF1RX_CHAN4_ENA_SHIFT 4 /* AIF1RX_CHAN4_ENA */
+#define WM8915_AIF1RX_CHAN4_ENA_WIDTH 1 /* AIF1RX_CHAN4_ENA */
+#define WM8915_AIF1RX_CHAN3_ENA 0x0008 /* AIF1RX_CHAN3_ENA */
+#define WM8915_AIF1RX_CHAN3_ENA_MASK 0x0008 /* AIF1RX_CHAN3_ENA */
+#define WM8915_AIF1RX_CHAN3_ENA_SHIFT 3 /* AIF1RX_CHAN3_ENA */
+#define WM8915_AIF1RX_CHAN3_ENA_WIDTH 1 /* AIF1RX_CHAN3_ENA */
+#define WM8915_AIF1RX_CHAN2_ENA 0x0004 /* AIF1RX_CHAN2_ENA */
+#define WM8915_AIF1RX_CHAN2_ENA_MASK 0x0004 /* AIF1RX_CHAN2_ENA */
+#define WM8915_AIF1RX_CHAN2_ENA_SHIFT 2 /* AIF1RX_CHAN2_ENA */
+#define WM8915_AIF1RX_CHAN2_ENA_WIDTH 1 /* AIF1RX_CHAN2_ENA */
+#define WM8915_AIF1RX_CHAN1_ENA 0x0002 /* AIF1RX_CHAN1_ENA */
+#define WM8915_AIF1RX_CHAN1_ENA_MASK 0x0002 /* AIF1RX_CHAN1_ENA */
+#define WM8915_AIF1RX_CHAN1_ENA_SHIFT 1 /* AIF1RX_CHAN1_ENA */
+#define WM8915_AIF1RX_CHAN1_ENA_WIDTH 1 /* AIF1RX_CHAN1_ENA */
+#define WM8915_AIF1RX_CHAN0_ENA 0x0001 /* AIF1RX_CHAN0_ENA */
+#define WM8915_AIF1RX_CHAN0_ENA_MASK 0x0001 /* AIF1RX_CHAN0_ENA */
+#define WM8915_AIF1RX_CHAN0_ENA_SHIFT 0 /* AIF1RX_CHAN0_ENA */
+#define WM8915_AIF1RX_CHAN0_ENA_WIDTH 1 /* AIF1RX_CHAN0_ENA */
+
+/*
+ * R5 (0x05) - Power Management (5)
+ */
+#define WM8915_DSP2TXL_ENA 0x0800 /* DSP2TXL_ENA */
+#define WM8915_DSP2TXL_ENA_MASK 0x0800 /* DSP2TXL_ENA */
+#define WM8915_DSP2TXL_ENA_SHIFT 11 /* DSP2TXL_ENA */
+#define WM8915_DSP2TXL_ENA_WIDTH 1 /* DSP2TXL_ENA */
+#define WM8915_DSP2TXR_ENA 0x0400 /* DSP2TXR_ENA */
+#define WM8915_DSP2TXR_ENA_MASK 0x0400 /* DSP2TXR_ENA */
+#define WM8915_DSP2TXR_ENA_SHIFT 10 /* DSP2TXR_ENA */
+#define WM8915_DSP2TXR_ENA_WIDTH 1 /* DSP2TXR_ENA */
+#define WM8915_DSP1TXL_ENA 0x0200 /* DSP1TXL_ENA */
+#define WM8915_DSP1TXL_ENA_MASK 0x0200 /* DSP1TXL_ENA */
+#define WM8915_DSP1TXL_ENA_SHIFT 9 /* DSP1TXL_ENA */
+#define WM8915_DSP1TXL_ENA_WIDTH 1 /* DSP1TXL_ENA */
+#define WM8915_DSP1TXR_ENA 0x0100 /* DSP1TXR_ENA */
+#define WM8915_DSP1TXR_ENA_MASK 0x0100 /* DSP1TXR_ENA */
+#define WM8915_DSP1TXR_ENA_SHIFT 8 /* DSP1TXR_ENA */
+#define WM8915_DSP1TXR_ENA_WIDTH 1 /* DSP1TXR_ENA */
+#define WM8915_DAC2L_ENA 0x0008 /* DAC2L_ENA */
+#define WM8915_DAC2L_ENA_MASK 0x0008 /* DAC2L_ENA */
+#define WM8915_DAC2L_ENA_SHIFT 3 /* DAC2L_ENA */
+#define WM8915_DAC2L_ENA_WIDTH 1 /* DAC2L_ENA */
+#define WM8915_DAC2R_ENA 0x0004 /* DAC2R_ENA */
+#define WM8915_DAC2R_ENA_MASK 0x0004 /* DAC2R_ENA */
+#define WM8915_DAC2R_ENA_SHIFT 2 /* DAC2R_ENA */
+#define WM8915_DAC2R_ENA_WIDTH 1 /* DAC2R_ENA */
+#define WM8915_DAC1L_ENA 0x0002 /* DAC1L_ENA */
+#define WM8915_DAC1L_ENA_MASK 0x0002 /* DAC1L_ENA */
+#define WM8915_DAC1L_ENA_SHIFT 1 /* DAC1L_ENA */
+#define WM8915_DAC1L_ENA_WIDTH 1 /* DAC1L_ENA */
+#define WM8915_DAC1R_ENA 0x0001 /* DAC1R_ENA */
+#define WM8915_DAC1R_ENA_MASK 0x0001 /* DAC1R_ENA */
+#define WM8915_DAC1R_ENA_SHIFT 0 /* DAC1R_ENA */
+#define WM8915_DAC1R_ENA_WIDTH 1 /* DAC1R_ENA */
+
+/*
+ * R6 (0x06) - Power Management (6)
+ */
+#define WM8915_AIF2TX_CHAN1_ENA 0x0200 /* AIF2TX_CHAN1_ENA */
+#define WM8915_AIF2TX_CHAN1_ENA_MASK 0x0200 /* AIF2TX_CHAN1_ENA */
+#define WM8915_AIF2TX_CHAN1_ENA_SHIFT 9 /* AIF2TX_CHAN1_ENA */
+#define WM8915_AIF2TX_CHAN1_ENA_WIDTH 1 /* AIF2TX_CHAN1_ENA */
+#define WM8915_AIF2TX_CHAN0_ENA 0x0100 /* AIF2TX_CHAN0_ENA */
+#define WM8915_AIF2TX_CHAN0_ENA_MASK 0x0100 /* AIF2TX_CHAN0_ENA */
+#define WM8915_AIF2TX_CHAN0_ENA_SHIFT 8 /* AIF2TX_CHAN0_ENA */
+#define WM8915_AIF2TX_CHAN0_ENA_WIDTH 1 /* AIF2TX_CHAN0_ENA */
+#define WM8915_AIF1TX_CHAN5_ENA 0x0020 /* AIF1TX_CHAN5_ENA */
+#define WM8915_AIF1TX_CHAN5_ENA_MASK 0x0020 /* AIF1TX_CHAN5_ENA */
+#define WM8915_AIF1TX_CHAN5_ENA_SHIFT 5 /* AIF1TX_CHAN5_ENA */
+#define WM8915_AIF1TX_CHAN5_ENA_WIDTH 1 /* AIF1TX_CHAN5_ENA */
+#define WM8915_AIF1TX_CHAN4_ENA 0x0010 /* AIF1TX_CHAN4_ENA */
+#define WM8915_AIF1TX_CHAN4_ENA_MASK 0x0010 /* AIF1TX_CHAN4_ENA */
+#define WM8915_AIF1TX_CHAN4_ENA_SHIFT 4 /* AIF1TX_CHAN4_ENA */
+#define WM8915_AIF1TX_CHAN4_ENA_WIDTH 1 /* AIF1TX_CHAN4_ENA */
+#define WM8915_AIF1TX_CHAN3_ENA 0x0008 /* AIF1TX_CHAN3_ENA */
+#define WM8915_AIF1TX_CHAN3_ENA_MASK 0x0008 /* AIF1TX_CHAN3_ENA */
+#define WM8915_AIF1TX_CHAN3_ENA_SHIFT 3 /* AIF1TX_CHAN3_ENA */
+#define WM8915_AIF1TX_CHAN3_ENA_WIDTH 1 /* AIF1TX_CHAN3_ENA */
+#define WM8915_AIF1TX_CHAN2_ENA 0x0004 /* AIF1TX_CHAN2_ENA */
+#define WM8915_AIF1TX_CHAN2_ENA_MASK 0x0004 /* AIF1TX_CHAN2_ENA */
+#define WM8915_AIF1TX_CHAN2_ENA_SHIFT 2 /* AIF1TX_CHAN2_ENA */
+#define WM8915_AIF1TX_CHAN2_ENA_WIDTH 1 /* AIF1TX_CHAN2_ENA */
+#define WM8915_AIF1TX_CHAN1_ENA 0x0002 /* AIF1TX_CHAN1_ENA */
+#define WM8915_AIF1TX_CHAN1_ENA_MASK 0x0002 /* AIF1TX_CHAN1_ENA */
+#define WM8915_AIF1TX_CHAN1_ENA_SHIFT 1 /* AIF1TX_CHAN1_ENA */
+#define WM8915_AIF1TX_CHAN1_ENA_WIDTH 1 /* AIF1TX_CHAN1_ENA */
+#define WM8915_AIF1TX_CHAN0_ENA 0x0001 /* AIF1TX_CHAN0_ENA */
+#define WM8915_AIF1TX_CHAN0_ENA_MASK 0x0001 /* AIF1TX_CHAN0_ENA */
+#define WM8915_AIF1TX_CHAN0_ENA_SHIFT 0 /* AIF1TX_CHAN0_ENA */
+#define WM8915_AIF1TX_CHAN0_ENA_WIDTH 1 /* AIF1TX_CHAN0_ENA */
+
+/*
+ * R7 (0x07) - Power Management (7)
+ */
+#define WM8915_DMIC2_FN 0x0200 /* DMIC2_FN */
+#define WM8915_DMIC2_FN_MASK 0x0200 /* DMIC2_FN */
+#define WM8915_DMIC2_FN_SHIFT 9 /* DMIC2_FN */
+#define WM8915_DMIC2_FN_WIDTH 1 /* DMIC2_FN */
+#define WM8915_DMIC1_FN 0x0100 /* DMIC1_FN */
+#define WM8915_DMIC1_FN_MASK 0x0100 /* DMIC1_FN */
+#define WM8915_DMIC1_FN_SHIFT 8 /* DMIC1_FN */
+#define WM8915_DMIC1_FN_WIDTH 1 /* DMIC1_FN */
+#define WM8915_ADC_DMIC_DSP2R_ENA 0x0080 /* ADC_DMIC_DSP2R_ENA */
+#define WM8915_ADC_DMIC_DSP2R_ENA_MASK 0x0080 /* ADC_DMIC_DSP2R_ENA */
+#define WM8915_ADC_DMIC_DSP2R_ENA_SHIFT 7 /* ADC_DMIC_DSP2R_ENA */
+#define WM8915_ADC_DMIC_DSP2R_ENA_WIDTH 1 /* ADC_DMIC_DSP2R_ENA */
+#define WM8915_ADC_DMIC_DSP2L_ENA 0x0040 /* ADC_DMIC_DSP2L_ENA */
+#define WM8915_ADC_DMIC_DSP2L_ENA_MASK 0x0040 /* ADC_DMIC_DSP2L_ENA */
+#define WM8915_ADC_DMIC_DSP2L_ENA_SHIFT 6 /* ADC_DMIC_DSP2L_ENA */
+#define WM8915_ADC_DMIC_DSP2L_ENA_WIDTH 1 /* ADC_DMIC_DSP2L_ENA */
+#define WM8915_ADC_DMIC_SRC2_MASK 0x0030 /* ADC_DMIC_SRC2 - [5:4] */
+#define WM8915_ADC_DMIC_SRC2_SHIFT 4 /* ADC_DMIC_SRC2 - [5:4] */
+#define WM8915_ADC_DMIC_SRC2_WIDTH 2 /* ADC_DMIC_SRC2 - [5:4] */
+#define WM8915_ADC_DMIC_DSP1R_ENA 0x0008 /* ADC_DMIC_DSP1R_ENA */
+#define WM8915_ADC_DMIC_DSP1R_ENA_MASK 0x0008 /* ADC_DMIC_DSP1R_ENA */
+#define WM8915_ADC_DMIC_DSP1R_ENA_SHIFT 3 /* ADC_DMIC_DSP1R_ENA */
+#define WM8915_ADC_DMIC_DSP1R_ENA_WIDTH 1 /* ADC_DMIC_DSP1R_ENA */
+#define WM8915_ADC_DMIC_DSP1L_ENA 0x0004 /* ADC_DMIC_DSP1L_ENA */
+#define WM8915_ADC_DMIC_DSP1L_ENA_MASK 0x0004 /* ADC_DMIC_DSP1L_ENA */
+#define WM8915_ADC_DMIC_DSP1L_ENA_SHIFT 2 /* ADC_DMIC_DSP1L_ENA */
+#define WM8915_ADC_DMIC_DSP1L_ENA_WIDTH 1 /* ADC_DMIC_DSP1L_ENA */
+#define WM8915_ADC_DMIC_SRC1_MASK 0x0003 /* ADC_DMIC_SRC1 - [1:0] */
+#define WM8915_ADC_DMIC_SRC1_SHIFT 0 /* ADC_DMIC_SRC1 - [1:0] */
+#define WM8915_ADC_DMIC_SRC1_WIDTH 2 /* ADC_DMIC_SRC1 - [1:0] */
+
+/*
+ * R8 (0x08) - Power Management (8)
+ */
+#define WM8915_AIF2TX_SRC_MASK 0x00C0 /* AIF2TX_SRC - [7:6] */
+#define WM8915_AIF2TX_SRC_SHIFT 6 /* AIF2TX_SRC - [7:6] */
+#define WM8915_AIF2TX_SRC_WIDTH 2 /* AIF2TX_SRC - [7:6] */
+#define WM8915_DSP2RX_SRC 0x0010 /* DSP2RX_SRC */
+#define WM8915_DSP2RX_SRC_MASK 0x0010 /* DSP2RX_SRC */
+#define WM8915_DSP2RX_SRC_SHIFT 4 /* DSP2RX_SRC */
+#define WM8915_DSP2RX_SRC_WIDTH 1 /* DSP2RX_SRC */
+#define WM8915_DSP1RX_SRC 0x0001 /* DSP1RX_SRC */
+#define WM8915_DSP1RX_SRC_MASK 0x0001 /* DSP1RX_SRC */
+#define WM8915_DSP1RX_SRC_SHIFT 0 /* DSP1RX_SRC */
+#define WM8915_DSP1RX_SRC_WIDTH 1 /* DSP1RX_SRC */
+
+/*
+ * R16 (0x10) - Left Line Input Volume
+ */
+#define WM8915_IN1_VU 0x0080 /* IN1_VU */
+#define WM8915_IN1_VU_MASK 0x0080 /* IN1_VU */
+#define WM8915_IN1_VU_SHIFT 7 /* IN1_VU */
+#define WM8915_IN1_VU_WIDTH 1 /* IN1_VU */
+#define WM8915_IN1L_ZC 0x0020 /* IN1L_ZC */
+#define WM8915_IN1L_ZC_MASK 0x0020 /* IN1L_ZC */
+#define WM8915_IN1L_ZC_SHIFT 5 /* IN1L_ZC */
+#define WM8915_IN1L_ZC_WIDTH 1 /* IN1L_ZC */
+#define WM8915_IN1L_VOL_MASK 0x001F /* IN1L_VOL - [4:0] */
+#define WM8915_IN1L_VOL_SHIFT 0 /* IN1L_VOL - [4:0] */
+#define WM8915_IN1L_VOL_WIDTH 5 /* IN1L_VOL - [4:0] */
+
+/*
+ * R17 (0x11) - Right Line Input Volume
+ */
+#define WM8915_IN1_VU 0x0080 /* IN1_VU */
+#define WM8915_IN1_VU_MASK 0x0080 /* IN1_VU */
+#define WM8915_IN1_VU_SHIFT 7 /* IN1_VU */
+#define WM8915_IN1_VU_WIDTH 1 /* IN1_VU */
+#define WM8915_IN1R_ZC 0x0020 /* IN1R_ZC */
+#define WM8915_IN1R_ZC_MASK 0x0020 /* IN1R_ZC */
+#define WM8915_IN1R_ZC_SHIFT 5 /* IN1R_ZC */
+#define WM8915_IN1R_ZC_WIDTH 1 /* IN1R_ZC */
+#define WM8915_IN1R_VOL_MASK 0x001F /* IN1R_VOL - [4:0] */
+#define WM8915_IN1R_VOL_SHIFT 0 /* IN1R_VOL - [4:0] */
+#define WM8915_IN1R_VOL_WIDTH 5 /* IN1R_VOL - [4:0] */
+
+/*
+ * R18 (0x12) - Line Input Control
+ */
+#define WM8915_INL_MODE_MASK 0x000C /* INL_MODE - [3:2] */
+#define WM8915_INL_MODE_SHIFT 2 /* INL_MODE - [3:2] */
+#define WM8915_INL_MODE_WIDTH 2 /* INL_MODE - [3:2] */
+#define WM8915_INR_MODE_MASK 0x0003 /* INR_MODE - [1:0] */
+#define WM8915_INR_MODE_SHIFT 0 /* INR_MODE - [1:0] */
+#define WM8915_INR_MODE_WIDTH 2 /* INR_MODE - [1:0] */
+
+/*
+ * R21 (0x15) - DAC1 HPOUT1 Volume
+ */
+#define WM8915_DAC1R_HPOUT1R_VOL_MASK 0x00F0 /* DAC1R_HPOUT1R_VOL - [7:4] */
+#define WM8915_DAC1R_HPOUT1R_VOL_SHIFT 4 /* DAC1R_HPOUT1R_VOL - [7:4] */
+#define WM8915_DAC1R_HPOUT1R_VOL_WIDTH 4 /* DAC1R_HPOUT1R_VOL - [7:4] */
+#define WM8915_DAC1L_HPOUT1L_VOL_MASK 0x000F /* DAC1L_HPOUT1L_VOL - [3:0] */
+#define WM8915_DAC1L_HPOUT1L_VOL_SHIFT 0 /* DAC1L_HPOUT1L_VOL - [3:0] */
+#define WM8915_DAC1L_HPOUT1L_VOL_WIDTH 4 /* DAC1L_HPOUT1L_VOL - [3:0] */
+
+/*
+ * R22 (0x16) - DAC2 HPOUT2 Volume
+ */
+#define WM8915_DAC2R_HPOUT2R_VOL_MASK 0x00F0 /* DAC2R_HPOUT2R_VOL - [7:4] */
+#define WM8915_DAC2R_HPOUT2R_VOL_SHIFT 4 /* DAC2R_HPOUT2R_VOL - [7:4] */
+#define WM8915_DAC2R_HPOUT2R_VOL_WIDTH 4 /* DAC2R_HPOUT2R_VOL - [7:4] */
+#define WM8915_DAC2L_HPOUT2L_VOL_MASK 0x000F /* DAC2L_HPOUT2L_VOL - [3:0] */
+#define WM8915_DAC2L_HPOUT2L_VOL_SHIFT 0 /* DAC2L_HPOUT2L_VOL - [3:0] */
+#define WM8915_DAC2L_HPOUT2L_VOL_WIDTH 4 /* DAC2L_HPOUT2L_VOL - [3:0] */
+
+/*
+ * R24 (0x18) - DAC1 Left Volume
+ */
+#define WM8915_DAC1L_MUTE 0x0200 /* DAC1L_MUTE */
+#define WM8915_DAC1L_MUTE_MASK 0x0200 /* DAC1L_MUTE */
+#define WM8915_DAC1L_MUTE_SHIFT 9 /* DAC1L_MUTE */
+#define WM8915_DAC1L_MUTE_WIDTH 1 /* DAC1L_MUTE */
+#define WM8915_DAC1_VU 0x0100 /* DAC1_VU */
+#define WM8915_DAC1_VU_MASK 0x0100 /* DAC1_VU */
+#define WM8915_DAC1_VU_SHIFT 8 /* DAC1_VU */
+#define WM8915_DAC1_VU_WIDTH 1 /* DAC1_VU */
+#define WM8915_DAC1L_VOL_MASK 0x00FF /* DAC1L_VOL - [7:0] */
+#define WM8915_DAC1L_VOL_SHIFT 0 /* DAC1L_VOL - [7:0] */
+#define WM8915_DAC1L_VOL_WIDTH 8 /* DAC1L_VOL - [7:0] */
+
+/*
+ * R25 (0x19) - DAC1 Right Volume
+ */
+#define WM8915_DAC1R_MUTE 0x0200 /* DAC1R_MUTE */
+#define WM8915_DAC1R_MUTE_MASK 0x0200 /* DAC1R_MUTE */
+#define WM8915_DAC1R_MUTE_SHIFT 9 /* DAC1R_MUTE */
+#define WM8915_DAC1R_MUTE_WIDTH 1 /* DAC1R_MUTE */
+#define WM8915_DAC1_VU 0x0100 /* DAC1_VU */
+#define WM8915_DAC1_VU_MASK 0x0100 /* DAC1_VU */
+#define WM8915_DAC1_VU_SHIFT 8 /* DAC1_VU */
+#define WM8915_DAC1_VU_WIDTH 1 /* DAC1_VU */
+#define WM8915_DAC1R_VOL_MASK 0x00FF /* DAC1R_VOL - [7:0] */
+#define WM8915_DAC1R_VOL_SHIFT 0 /* DAC1R_VOL - [7:0] */
+#define WM8915_DAC1R_VOL_WIDTH 8 /* DAC1R_VOL - [7:0] */
+
+/*
+ * R26 (0x1A) - DAC2 Left Volume
+ */
+#define WM8915_DAC2L_MUTE 0x0200 /* DAC2L_MUTE */
+#define WM8915_DAC2L_MUTE_MASK 0x0200 /* DAC2L_MUTE */
+#define WM8915_DAC2L_MUTE_SHIFT 9 /* DAC2L_MUTE */
+#define WM8915_DAC2L_MUTE_WIDTH 1 /* DAC2L_MUTE */
+#define WM8915_DAC2_VU 0x0100 /* DAC2_VU */
+#define WM8915_DAC2_VU_MASK 0x0100 /* DAC2_VU */
+#define WM8915_DAC2_VU_SHIFT 8 /* DAC2_VU */
+#define WM8915_DAC2_VU_WIDTH 1 /* DAC2_VU */
+#define WM8915_DAC2L_VOL_MASK 0x00FF /* DAC2L_VOL - [7:0] */
+#define WM8915_DAC2L_VOL_SHIFT 0 /* DAC2L_VOL - [7:0] */
+#define WM8915_DAC2L_VOL_WIDTH 8 /* DAC2L_VOL - [7:0] */
+
+/*
+ * R27 (0x1B) - DAC2 Right Volume
+ */
+#define WM8915_DAC2R_MUTE 0x0200 /* DAC2R_MUTE */
+#define WM8915_DAC2R_MUTE_MASK 0x0200 /* DAC2R_MUTE */
+#define WM8915_DAC2R_MUTE_SHIFT 9 /* DAC2R_MUTE */
+#define WM8915_DAC2R_MUTE_WIDTH 1 /* DAC2R_MUTE */
+#define WM8915_DAC2_VU 0x0100 /* DAC2_VU */
+#define WM8915_DAC2_VU_MASK 0x0100 /* DAC2_VU */
+#define WM8915_DAC2_VU_SHIFT 8 /* DAC2_VU */
+#define WM8915_DAC2_VU_WIDTH 1 /* DAC2_VU */
+#define WM8915_DAC2R_VOL_MASK 0x00FF /* DAC2R_VOL - [7:0] */
+#define WM8915_DAC2R_VOL_SHIFT 0 /* DAC2R_VOL - [7:0] */
+#define WM8915_DAC2R_VOL_WIDTH 8 /* DAC2R_VOL - [7:0] */
+
+/*
+ * R28 (0x1C) - Output1 Left Volume
+ */
+#define WM8915_DAC1_VU 0x0100 /* DAC1_VU */
+#define WM8915_DAC1_VU_MASK 0x0100 /* DAC1_VU */
+#define WM8915_DAC1_VU_SHIFT 8 /* DAC1_VU */
+#define WM8915_DAC1_VU_WIDTH 1 /* DAC1_VU */
+#define WM8915_HPOUT1L_ZC 0x0080 /* HPOUT1L_ZC */
+#define WM8915_HPOUT1L_ZC_MASK 0x0080 /* HPOUT1L_ZC */
+#define WM8915_HPOUT1L_ZC_SHIFT 7 /* HPOUT1L_ZC */
+#define WM8915_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */
+#define WM8915_HPOUT1L_VOL_MASK 0x000F /* HPOUT1L_VOL - [3:0] */
+#define WM8915_HPOUT1L_VOL_SHIFT 0 /* HPOUT1L_VOL - [3:0] */
+#define WM8915_HPOUT1L_VOL_WIDTH 4 /* HPOUT1L_VOL - [3:0] */
+
+/*
+ * R29 (0x1D) - Output1 Right Volume
+ */
+#define WM8915_DAC1_VU 0x0100 /* DAC1_VU */
+#define WM8915_DAC1_VU_MASK 0x0100 /* DAC1_VU */
+#define WM8915_DAC1_VU_SHIFT 8 /* DAC1_VU */
+#define WM8915_DAC1_VU_WIDTH 1 /* DAC1_VU */
+#define WM8915_HPOUT1R_ZC 0x0080 /* HPOUT1R_ZC */
+#define WM8915_HPOUT1R_ZC_MASK 0x0080 /* HPOUT1R_ZC */
+#define WM8915_HPOUT1R_ZC_SHIFT 7 /* HPOUT1R_ZC */
+#define WM8915_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */
+#define WM8915_HPOUT1R_VOL_MASK 0x000F /* HPOUT1R_VOL - [3:0] */
+#define WM8915_HPOUT1R_VOL_SHIFT 0 /* HPOUT1R_VOL - [3:0] */
+#define WM8915_HPOUT1R_VOL_WIDTH 4 /* HPOUT1R_VOL - [3:0] */
+
+/*
+ * R30 (0x1E) - Output2 Left Volume
+ */
+#define WM8915_DAC2_VU 0x0100 /* DAC2_VU */
+#define WM8915_DAC2_VU_MASK 0x0100 /* DAC2_VU */
+#define WM8915_DAC2_VU_SHIFT 8 /* DAC2_VU */
+#define WM8915_DAC2_VU_WIDTH 1 /* DAC2_VU */
+#define WM8915_HPOUT2L_ZC 0x0080 /* HPOUT2L_ZC */
+#define WM8915_HPOUT2L_ZC_MASK 0x0080 /* HPOUT2L_ZC */
+#define WM8915_HPOUT2L_ZC_SHIFT 7 /* HPOUT2L_ZC */
+#define WM8915_HPOUT2L_ZC_WIDTH 1 /* HPOUT2L_ZC */
+#define WM8915_HPOUT2L_VOL_MASK 0x000F /* HPOUT2L_VOL - [3:0] */
+#define WM8915_HPOUT2L_VOL_SHIFT 0 /* HPOUT2L_VOL - [3:0] */
+#define WM8915_HPOUT2L_VOL_WIDTH 4 /* HPOUT2L_VOL - [3:0] */
+
+/*
+ * R31 (0x1F) - Output2 Right Volume
+ */
+#define WM8915_DAC2_VU 0x0100 /* DAC2_VU */
+#define WM8915_DAC2_VU_MASK 0x0100 /* DAC2_VU */
+#define WM8915_DAC2_VU_SHIFT 8 /* DAC2_VU */
+#define WM8915_DAC2_VU_WIDTH 1 /* DAC2_VU */
+#define WM8915_HPOUT2R_ZC 0x0080 /* HPOUT2R_ZC */
+#define WM8915_HPOUT2R_ZC_MASK 0x0080 /* HPOUT2R_ZC */
+#define WM8915_HPOUT2R_ZC_SHIFT 7 /* HPOUT2R_ZC */
+#define WM8915_HPOUT2R_ZC_WIDTH 1 /* HPOUT2R_ZC */
+#define WM8915_HPOUT2R_VOL_MASK 0x000F /* HPOUT2R_VOL - [3:0] */
+#define WM8915_HPOUT2R_VOL_SHIFT 0 /* HPOUT2R_VOL - [3:0] */
+#define WM8915_HPOUT2R_VOL_WIDTH 4 /* HPOUT2R_VOL - [3:0] */
+
+/*
+ * R32 (0x20) - MICBIAS (1)
+ */
+#define WM8915_MICB1_RATE 0x0020 /* MICB1_RATE */
+#define WM8915_MICB1_RATE_MASK 0x0020 /* MICB1_RATE */
+#define WM8915_MICB1_RATE_SHIFT 5 /* MICB1_RATE */
+#define WM8915_MICB1_RATE_WIDTH 1 /* MICB1_RATE */
+#define WM8915_MICB1_MODE 0x0010 /* MICB1_MODE */
+#define WM8915_MICB1_MODE_MASK 0x0010 /* MICB1_MODE */
+#define WM8915_MICB1_MODE_SHIFT 4 /* MICB1_MODE */
+#define WM8915_MICB1_MODE_WIDTH 1 /* MICB1_MODE */
+#define WM8915_MICB1_LVL_MASK 0x000E /* MICB1_LVL - [3:1] */
+#define WM8915_MICB1_LVL_SHIFT 1 /* MICB1_LVL - [3:1] */
+#define WM8915_MICB1_LVL_WIDTH 3 /* MICB1_LVL - [3:1] */
+#define WM8915_MICB1_DISCH 0x0001 /* MICB1_DISCH */
+#define WM8915_MICB1_DISCH_MASK 0x0001 /* MICB1_DISCH */
+#define WM8915_MICB1_DISCH_SHIFT 0 /* MICB1_DISCH */
+#define WM8915_MICB1_DISCH_WIDTH 1 /* MICB1_DISCH */
+
+/*
+ * R33 (0x21) - MICBIAS (2)
+ */
+#define WM8915_MICB2_RATE 0x0020 /* MICB2_RATE */
+#define WM8915_MICB2_RATE_MASK 0x0020 /* MICB2_RATE */
+#define WM8915_MICB2_RATE_SHIFT 5 /* MICB2_RATE */
+#define WM8915_MICB2_RATE_WIDTH 1 /* MICB2_RATE */
+#define WM8915_MICB2_MODE 0x0010 /* MICB2_MODE */
+#define WM8915_MICB2_MODE_MASK 0x0010 /* MICB2_MODE */
+#define WM8915_MICB2_MODE_SHIFT 4 /* MICB2_MODE */
+#define WM8915_MICB2_MODE_WIDTH 1 /* MICB2_MODE */
+#define WM8915_MICB2_LVL_MASK 0x000E /* MICB2_LVL - [3:1] */
+#define WM8915_MICB2_LVL_SHIFT 1 /* MICB2_LVL - [3:1] */
+#define WM8915_MICB2_LVL_WIDTH 3 /* MICB2_LVL - [3:1] */
+#define WM8915_MICB2_DISCH 0x0001 /* MICB2_DISCH */
+#define WM8915_MICB2_DISCH_MASK 0x0001 /* MICB2_DISCH */
+#define WM8915_MICB2_DISCH_SHIFT 0 /* MICB2_DISCH */
+#define WM8915_MICB2_DISCH_WIDTH 1 /* MICB2_DISCH */
+
+/*
+ * R40 (0x28) - LDO 1
+ */
+#define WM8915_LDO1_MODE 0x0020 /* LDO1_MODE */
+#define WM8915_LDO1_MODE_MASK 0x0020 /* LDO1_MODE */
+#define WM8915_LDO1_MODE_SHIFT 5 /* LDO1_MODE */
+#define WM8915_LDO1_MODE_WIDTH 1 /* LDO1_MODE */
+#define WM8915_LDO1_VSEL_MASK 0x0006 /* LDO1_VSEL - [2:1] */
+#define WM8915_LDO1_VSEL_SHIFT 1 /* LDO1_VSEL - [2:1] */
+#define WM8915_LDO1_VSEL_WIDTH 2 /* LDO1_VSEL - [2:1] */
+#define WM8915_LDO1_DISCH 0x0001 /* LDO1_DISCH */
+#define WM8915_LDO1_DISCH_MASK 0x0001 /* LDO1_DISCH */
+#define WM8915_LDO1_DISCH_SHIFT 0 /* LDO1_DISCH */
+#define WM8915_LDO1_DISCH_WIDTH 1 /* LDO1_DISCH */
+
+/*
+ * R41 (0x29) - LDO 2
+ */
+#define WM8915_LDO2_MODE 0x0020 /* LDO2_MODE */
+#define WM8915_LDO2_MODE_MASK 0x0020 /* LDO2_MODE */
+#define WM8915_LDO2_MODE_SHIFT 5 /* LDO2_MODE */
+#define WM8915_LDO2_MODE_WIDTH 1 /* LDO2_MODE */
+#define WM8915_LDO2_VSEL_MASK 0x001E /* LDO2_VSEL - [4:1] */
+#define WM8915_LDO2_VSEL_SHIFT 1 /* LDO2_VSEL - [4:1] */
+#define WM8915_LDO2_VSEL_WIDTH 4 /* LDO2_VSEL - [4:1] */
+#define WM8915_LDO2_DISCH 0x0001 /* LDO2_DISCH */
+#define WM8915_LDO2_DISCH_MASK 0x0001 /* LDO2_DISCH */
+#define WM8915_LDO2_DISCH_SHIFT 0 /* LDO2_DISCH */
+#define WM8915_LDO2_DISCH_WIDTH 1 /* LDO2_DISCH */
+
+/*
+ * R48 (0x30) - Accessory Detect Mode 1
+ */
+#define WM8915_JD_MODE_MASK 0x0003 /* JD_MODE - [1:0] */
+#define WM8915_JD_MODE_SHIFT 0 /* JD_MODE - [1:0] */
+#define WM8915_JD_MODE_WIDTH 2 /* JD_MODE - [1:0] */
+
+/*
+ * R49 (0x31) - Accessory Detect Mode 2
+ */
+#define WM8915_HPOUT1FB_SRC 0x0004 /* HPOUT1FB_SRC */
+#define WM8915_HPOUT1FB_SRC_MASK 0x0004 /* HPOUT1FB_SRC */
+#define WM8915_HPOUT1FB_SRC_SHIFT 2 /* HPOUT1FB_SRC */
+#define WM8915_HPOUT1FB_SRC_WIDTH 1 /* HPOUT1FB_SRC */
+#define WM8915_MICD_SRC 0x0002 /* MICD_SRC */
+#define WM8915_MICD_SRC_MASK 0x0002 /* MICD_SRC */
+#define WM8915_MICD_SRC_SHIFT 1 /* MICD_SRC */
+#define WM8915_MICD_SRC_WIDTH 1 /* MICD_SRC */
+#define WM8915_MICD_BIAS_SRC 0x0001 /* MICD_BIAS_SRC */
+#define WM8915_MICD_BIAS_SRC_MASK 0x0001 /* MICD_BIAS_SRC */
+#define WM8915_MICD_BIAS_SRC_SHIFT 0 /* MICD_BIAS_SRC */
+#define WM8915_MICD_BIAS_SRC_WIDTH 1 /* MICD_BIAS_SRC */
+
+/*
+ * R52 (0x34) - Headphone Detect 1
+ */
+#define WM8915_HP_HOLDTIME_MASK 0x00E0 /* HP_HOLDTIME - [7:5] */
+#define WM8915_HP_HOLDTIME_SHIFT 5 /* HP_HOLDTIME - [7:5] */
+#define WM8915_HP_HOLDTIME_WIDTH 3 /* HP_HOLDTIME - [7:5] */
+#define WM8915_HP_CLK_DIV_MASK 0x0018 /* HP_CLK_DIV - [4:3] */
+#define WM8915_HP_CLK_DIV_SHIFT 3 /* HP_CLK_DIV - [4:3] */
+#define WM8915_HP_CLK_DIV_WIDTH 2 /* HP_CLK_DIV - [4:3] */
+#define WM8915_HP_STEP_SIZE 0x0002 /* HP_STEP_SIZE */
+#define WM8915_HP_STEP_SIZE_MASK 0x0002 /* HP_STEP_SIZE */
+#define WM8915_HP_STEP_SIZE_SHIFT 1 /* HP_STEP_SIZE */
+#define WM8915_HP_STEP_SIZE_WIDTH 1 /* HP_STEP_SIZE */
+#define WM8915_HP_POLL 0x0001 /* HP_POLL */
+#define WM8915_HP_POLL_MASK 0x0001 /* HP_POLL */
+#define WM8915_HP_POLL_SHIFT 0 /* HP_POLL */
+#define WM8915_HP_POLL_WIDTH 1 /* HP_POLL */
+
+/*
+ * R53 (0x35) - Headphone Detect 2
+ */
+#define WM8915_HP_DONE 0x0080 /* HP_DONE */
+#define WM8915_HP_DONE_MASK 0x0080 /* HP_DONE */
+#define WM8915_HP_DONE_SHIFT 7 /* HP_DONE */
+#define WM8915_HP_DONE_WIDTH 1 /* HP_DONE */
+#define WM8915_HP_LVL_MASK 0x007F /* HP_LVL - [6:0] */
+#define WM8915_HP_LVL_SHIFT 0 /* HP_LVL - [6:0] */
+#define WM8915_HP_LVL_WIDTH 7 /* HP_LVL - [6:0] */
+
+/*
+ * R56 (0x38) - Mic Detect 1
+ */
+#define WM8915_MICD_BIAS_STARTTIME_MASK 0xF000 /* MICD_BIAS_STARTTIME - [15:12] */
+#define WM8915_MICD_BIAS_STARTTIME_SHIFT 12 /* MICD_BIAS_STARTTIME - [15:12] */
+#define WM8915_MICD_BIAS_STARTTIME_WIDTH 4 /* MICD_BIAS_STARTTIME - [15:12] */
+#define WM8915_MICD_RATE_MASK 0x0F00 /* MICD_RATE - [11:8] */
+#define WM8915_MICD_RATE_SHIFT 8 /* MICD_RATE - [11:8] */
+#define WM8915_MICD_RATE_WIDTH 4 /* MICD_RATE - [11:8] */
+#define WM8915_MICD_DBTIME 0x0002 /* MICD_DBTIME */
+#define WM8915_MICD_DBTIME_MASK 0x0002 /* MICD_DBTIME */
+#define WM8915_MICD_DBTIME_SHIFT 1 /* MICD_DBTIME */
+#define WM8915_MICD_DBTIME_WIDTH 1 /* MICD_DBTIME */
+#define WM8915_MICD_ENA 0x0001 /* MICD_ENA */
+#define WM8915_MICD_ENA_MASK 0x0001 /* MICD_ENA */
+#define WM8915_MICD_ENA_SHIFT 0 /* MICD_ENA */
+#define WM8915_MICD_ENA_WIDTH 1 /* MICD_ENA */
+
+/*
+ * R57 (0x39) - Mic Detect 2
+ */
+#define WM8915_MICD_LVL_SEL_MASK 0x00FF /* MICD_LVL_SEL - [7:0] */
+#define WM8915_MICD_LVL_SEL_SHIFT 0 /* MICD_LVL_SEL - [7:0] */
+#define WM8915_MICD_LVL_SEL_WIDTH 8 /* MICD_LVL_SEL - [7:0] */
+
+/*
+ * R58 (0x3A) - Mic Detect 3
+ */
+#define WM8915_MICD_LVL_MASK 0x07FC /* MICD_LVL - [10:2] */
+#define WM8915_MICD_LVL_SHIFT 2 /* MICD_LVL - [10:2] */
+#define WM8915_MICD_LVL_WIDTH 9 /* MICD_LVL - [10:2] */
+#define WM8915_MICD_VALID 0x0002 /* MICD_VALID */
+#define WM8915_MICD_VALID_MASK 0x0002 /* MICD_VALID */
+#define WM8915_MICD_VALID_SHIFT 1 /* MICD_VALID */
+#define WM8915_MICD_VALID_WIDTH 1 /* MICD_VALID */
+#define WM8915_MICD_STS 0x0001 /* MICD_STS */
+#define WM8915_MICD_STS_MASK 0x0001 /* MICD_STS */
+#define WM8915_MICD_STS_SHIFT 0 /* MICD_STS */
+#define WM8915_MICD_STS_WIDTH 1 /* MICD_STS */
+
+/*
+ * R64 (0x40) - Charge Pump (1)
+ */
+#define WM8915_CP_ENA 0x8000 /* CP_ENA */
+#define WM8915_CP_ENA_MASK 0x8000 /* CP_ENA */
+#define WM8915_CP_ENA_SHIFT 15 /* CP_ENA */
+#define WM8915_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R65 (0x41) - Charge Pump (2)
+ */
+#define WM8915_CP_DISCH 0x8000 /* CP_DISCH */
+#define WM8915_CP_DISCH_MASK 0x8000 /* CP_DISCH */
+#define WM8915_CP_DISCH_SHIFT 15 /* CP_DISCH */
+#define WM8915_CP_DISCH_WIDTH 1 /* CP_DISCH */
+
+/*
+ * R80 (0x50) - DC Servo (1)
+ */
+#define WM8915_DCS_ENA_CHAN_3 0x0008 /* DCS_ENA_CHAN_3 */
+#define WM8915_DCS_ENA_CHAN_3_MASK 0x0008 /* DCS_ENA_CHAN_3 */
+#define WM8915_DCS_ENA_CHAN_3_SHIFT 3 /* DCS_ENA_CHAN_3 */
+#define WM8915_DCS_ENA_CHAN_3_WIDTH 1 /* DCS_ENA_CHAN_3 */
+#define WM8915_DCS_ENA_CHAN_2 0x0004 /* DCS_ENA_CHAN_2 */
+#define WM8915_DCS_ENA_CHAN_2_MASK 0x0004 /* DCS_ENA_CHAN_2 */
+#define WM8915_DCS_ENA_CHAN_2_SHIFT 2 /* DCS_ENA_CHAN_2 */
+#define WM8915_DCS_ENA_CHAN_2_WIDTH 1 /* DCS_ENA_CHAN_2 */
+#define WM8915_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM8915_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM8915_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */
+#define WM8915_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */
+#define WM8915_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM8915_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM8915_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */
+#define WM8915_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */
+
+/*
+ * R81 (0x51) - DC Servo (2)
+ */
+#define WM8915_DCS_TRIG_SINGLE_3 0x8000 /* DCS_TRIG_SINGLE_3 */
+#define WM8915_DCS_TRIG_SINGLE_3_MASK 0x8000 /* DCS_TRIG_SINGLE_3 */
+#define WM8915_DCS_TRIG_SINGLE_3_SHIFT 15 /* DCS_TRIG_SINGLE_3 */
+#define WM8915_DCS_TRIG_SINGLE_3_WIDTH 1 /* DCS_TRIG_SINGLE_3 */
+#define WM8915_DCS_TRIG_SINGLE_2 0x4000 /* DCS_TRIG_SINGLE_2 */
+#define WM8915_DCS_TRIG_SINGLE_2_MASK 0x4000 /* DCS_TRIG_SINGLE_2 */
+#define WM8915_DCS_TRIG_SINGLE_2_SHIFT 14 /* DCS_TRIG_SINGLE_2 */
+#define WM8915_DCS_TRIG_SINGLE_2_WIDTH 1 /* DCS_TRIG_SINGLE_2 */
+#define WM8915_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM8915_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM8915_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */
+#define WM8915_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */
+#define WM8915_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM8915_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM8915_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */
+#define WM8915_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */
+#define WM8915_DCS_TRIG_SERIES_3 0x0800 /* DCS_TRIG_SERIES_3 */
+#define WM8915_DCS_TRIG_SERIES_3_MASK 0x0800 /* DCS_TRIG_SERIES_3 */
+#define WM8915_DCS_TRIG_SERIES_3_SHIFT 11 /* DCS_TRIG_SERIES_3 */
+#define WM8915_DCS_TRIG_SERIES_3_WIDTH 1 /* DCS_TRIG_SERIES_3 */
+#define WM8915_DCS_TRIG_SERIES_2 0x0400 /* DCS_TRIG_SERIES_2 */
+#define WM8915_DCS_TRIG_SERIES_2_MASK 0x0400 /* DCS_TRIG_SERIES_2 */
+#define WM8915_DCS_TRIG_SERIES_2_SHIFT 10 /* DCS_TRIG_SERIES_2 */
+#define WM8915_DCS_TRIG_SERIES_2_WIDTH 1 /* DCS_TRIG_SERIES_2 */
+#define WM8915_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM8915_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM8915_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */
+#define WM8915_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */
+#define WM8915_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM8915_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM8915_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */
+#define WM8915_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */
+#define WM8915_DCS_TRIG_STARTUP_3 0x0080 /* DCS_TRIG_STARTUP_3 */
+#define WM8915_DCS_TRIG_STARTUP_3_MASK 0x0080 /* DCS_TRIG_STARTUP_3 */
+#define WM8915_DCS_TRIG_STARTUP_3_SHIFT 7 /* DCS_TRIG_STARTUP_3 */
+#define WM8915_DCS_TRIG_STARTUP_3_WIDTH 1 /* DCS_TRIG_STARTUP_3 */
+#define WM8915_DCS_TRIG_STARTUP_2 0x0040 /* DCS_TRIG_STARTUP_2 */
+#define WM8915_DCS_TRIG_STARTUP_2_MASK 0x0040 /* DCS_TRIG_STARTUP_2 */
+#define WM8915_DCS_TRIG_STARTUP_2_SHIFT 6 /* DCS_TRIG_STARTUP_2 */
+#define WM8915_DCS_TRIG_STARTUP_2_WIDTH 1 /* DCS_TRIG_STARTUP_2 */
+#define WM8915_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM8915_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM8915_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */
+#define WM8915_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */
+#define WM8915_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM8915_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM8915_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */
+#define WM8915_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */
+#define WM8915_DCS_TRIG_DAC_WR_3 0x0008 /* DCS_TRIG_DAC_WR_3 */
+#define WM8915_DCS_TRIG_DAC_WR_3_MASK 0x0008 /* DCS_TRIG_DAC_WR_3 */
+#define WM8915_DCS_TRIG_DAC_WR_3_SHIFT 3 /* DCS_TRIG_DAC_WR_3 */
+#define WM8915_DCS_TRIG_DAC_WR_3_WIDTH 1 /* DCS_TRIG_DAC_WR_3 */
+#define WM8915_DCS_TRIG_DAC_WR_2 0x0004 /* DCS_TRIG_DAC_WR_2 */
+#define WM8915_DCS_TRIG_DAC_WR_2_MASK 0x0004 /* DCS_TRIG_DAC_WR_2 */
+#define WM8915_DCS_TRIG_DAC_WR_2_SHIFT 2 /* DCS_TRIG_DAC_WR_2 */
+#define WM8915_DCS_TRIG_DAC_WR_2_WIDTH 1 /* DCS_TRIG_DAC_WR_2 */
+#define WM8915_DCS_TRIG_DAC_WR_1 0x0002 /* DCS_TRIG_DAC_WR_1 */
+#define WM8915_DCS_TRIG_DAC_WR_1_MASK 0x0002 /* DCS_TRIG_DAC_WR_1 */
+#define WM8915_DCS_TRIG_DAC_WR_1_SHIFT 1 /* DCS_TRIG_DAC_WR_1 */
+#define WM8915_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */
+#define WM8915_DCS_TRIG_DAC_WR_0 0x0001 /* DCS_TRIG_DAC_WR_0 */
+#define WM8915_DCS_TRIG_DAC_WR_0_MASK 0x0001 /* DCS_TRIG_DAC_WR_0 */
+#define WM8915_DCS_TRIG_DAC_WR_0_SHIFT 0 /* DCS_TRIG_DAC_WR_0 */
+#define WM8915_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */
+
+/*
+ * R82 (0x52) - DC Servo (3)
+ */
+#define WM8915_DCS_TIMER_PERIOD_23_MASK 0x0F00 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8915_DCS_TIMER_PERIOD_23_SHIFT 8 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8915_DCS_TIMER_PERIOD_23_WIDTH 4 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8915_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8915_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8915_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */
+
+/*
+ * R84 (0x54) - DC Servo (5)
+ */
+#define WM8915_DCS_SERIES_NO_23_MASK 0x7F00 /* DCS_SERIES_NO_23 - [14:8] */
+#define WM8915_DCS_SERIES_NO_23_SHIFT 8 /* DCS_SERIES_NO_23 - [14:8] */
+#define WM8915_DCS_SERIES_NO_23_WIDTH 7 /* DCS_SERIES_NO_23 - [14:8] */
+#define WM8915_DCS_SERIES_NO_01_MASK 0x007F /* DCS_SERIES_NO_01 - [6:0] */
+#define WM8915_DCS_SERIES_NO_01_SHIFT 0 /* DCS_SERIES_NO_01 - [6:0] */
+#define WM8915_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [6:0] */
+
+/*
+ * R85 (0x55) - DC Servo (6)
+ */
+#define WM8915_DCS_DAC_WR_VAL_3_MASK 0xFF00 /* DCS_DAC_WR_VAL_3 - [15:8] */
+#define WM8915_DCS_DAC_WR_VAL_3_SHIFT 8 /* DCS_DAC_WR_VAL_3 - [15:8] */
+#define WM8915_DCS_DAC_WR_VAL_3_WIDTH 8 /* DCS_DAC_WR_VAL_3 - [15:8] */
+#define WM8915_DCS_DAC_WR_VAL_2_MASK 0x00FF /* DCS_DAC_WR_VAL_2 - [7:0] */
+#define WM8915_DCS_DAC_WR_VAL_2_SHIFT 0 /* DCS_DAC_WR_VAL_2 - [7:0] */
+#define WM8915_DCS_DAC_WR_VAL_2_WIDTH 8 /* DCS_DAC_WR_VAL_2 - [7:0] */
+
+/*
+ * R86 (0x56) - DC Servo (7)
+ */
+#define WM8915_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8915_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8915_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8915_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8915_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8915_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */
+
+/*
+ * R87 (0x57) - DC Servo Readback 0
+ */
+#define WM8915_DCS_CAL_COMPLETE_MASK 0x0F00 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8915_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8915_DCS_CAL_COMPLETE_WIDTH 4 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8915_DCS_DAC_WR_COMPLETE_MASK 0x00F0 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8915_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8915_DCS_DAC_WR_COMPLETE_WIDTH 4 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8915_DCS_STARTUP_COMPLETE_MASK 0x000F /* DCS_STARTUP_COMPLETE - [3:0] */
+#define WM8915_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [3:0] */
+#define WM8915_DCS_STARTUP_COMPLETE_WIDTH 4 /* DCS_STARTUP_COMPLETE - [3:0] */
+
+/*
+ * R96 (0x60) - Analogue HP (1)
+ */
+#define WM8915_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */
+#define WM8915_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */
+#define WM8915_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */
+#define WM8915_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */
+#define WM8915_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */
+#define WM8915_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */
+#define WM8915_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */
+#define WM8915_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */
+#define WM8915_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */
+#define WM8915_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */
+#define WM8915_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */
+#define WM8915_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */
+#define WM8915_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */
+#define WM8915_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */
+#define WM8915_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */
+#define WM8915_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */
+#define WM8915_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */
+#define WM8915_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */
+#define WM8915_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */
+#define WM8915_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */
+#define WM8915_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */
+#define WM8915_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */
+#define WM8915_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */
+#define WM8915_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */
+
+/*
+ * R97 (0x61) - Analogue HP (2)
+ */
+#define WM8915_HPOUT2L_RMV_SHORT 0x0080 /* HPOUT2L_RMV_SHORT */
+#define WM8915_HPOUT2L_RMV_SHORT_MASK 0x0080 /* HPOUT2L_RMV_SHORT */
+#define WM8915_HPOUT2L_RMV_SHORT_SHIFT 7 /* HPOUT2L_RMV_SHORT */
+#define WM8915_HPOUT2L_RMV_SHORT_WIDTH 1 /* HPOUT2L_RMV_SHORT */
+#define WM8915_HPOUT2L_OUTP 0x0040 /* HPOUT2L_OUTP */
+#define WM8915_HPOUT2L_OUTP_MASK 0x0040 /* HPOUT2L_OUTP */
+#define WM8915_HPOUT2L_OUTP_SHIFT 6 /* HPOUT2L_OUTP */
+#define WM8915_HPOUT2L_OUTP_WIDTH 1 /* HPOUT2L_OUTP */
+#define WM8915_HPOUT2L_DLY 0x0020 /* HPOUT2L_DLY */
+#define WM8915_HPOUT2L_DLY_MASK 0x0020 /* HPOUT2L_DLY */
+#define WM8915_HPOUT2L_DLY_SHIFT 5 /* HPOUT2L_DLY */
+#define WM8915_HPOUT2L_DLY_WIDTH 1 /* HPOUT2L_DLY */
+#define WM8915_HPOUT2R_RMV_SHORT 0x0008 /* HPOUT2R_RMV_SHORT */
+#define WM8915_HPOUT2R_RMV_SHORT_MASK 0x0008 /* HPOUT2R_RMV_SHORT */
+#define WM8915_HPOUT2R_RMV_SHORT_SHIFT 3 /* HPOUT2R_RMV_SHORT */
+#define WM8915_HPOUT2R_RMV_SHORT_WIDTH 1 /* HPOUT2R_RMV_SHORT */
+#define WM8915_HPOUT2R_OUTP 0x0004 /* HPOUT2R_OUTP */
+#define WM8915_HPOUT2R_OUTP_MASK 0x0004 /* HPOUT2R_OUTP */
+#define WM8915_HPOUT2R_OUTP_SHIFT 2 /* HPOUT2R_OUTP */
+#define WM8915_HPOUT2R_OUTP_WIDTH 1 /* HPOUT2R_OUTP */
+#define WM8915_HPOUT2R_DLY 0x0002 /* HPOUT2R_DLY */
+#define WM8915_HPOUT2R_DLY_MASK 0x0002 /* HPOUT2R_DLY */
+#define WM8915_HPOUT2R_DLY_SHIFT 1 /* HPOUT2R_DLY */
+#define WM8915_HPOUT2R_DLY_WIDTH 1 /* HPOUT2R_DLY */
+
+/*
+ * R256 (0x100) - Chip Revision
+ */
+#define WM8915_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */
+#define WM8915_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */
+#define WM8915_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */
+
+/*
+ * R257 (0x101) - Control Interface (1)
+ */
+#define WM8915_AUTO_INC 0x0004 /* AUTO_INC */
+#define WM8915_AUTO_INC_MASK 0x0004 /* AUTO_INC */
+#define WM8915_AUTO_INC_SHIFT 2 /* AUTO_INC */
+#define WM8915_AUTO_INC_WIDTH 1 /* AUTO_INC */
+
+/*
+ * R272 (0x110) - Write Sequencer Ctrl (1)
+ */
+#define WM8915_WSEQ_ENA 0x8000 /* WSEQ_ENA */
+#define WM8915_WSEQ_ENA_MASK 0x8000 /* WSEQ_ENA */
+#define WM8915_WSEQ_ENA_SHIFT 15 /* WSEQ_ENA */
+#define WM8915_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM8915_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
+#define WM8915_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
+#define WM8915_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
+#define WM8915_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM8915_WSEQ_START 0x0100 /* WSEQ_START */
+#define WM8915_WSEQ_START_MASK 0x0100 /* WSEQ_START */
+#define WM8915_WSEQ_START_SHIFT 8 /* WSEQ_START */
+#define WM8915_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM8915_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */
+#define WM8915_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */
+#define WM8915_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */
+
+/*
+ * R273 (0x111) - Write Sequencer Ctrl (2)
+ */
+#define WM8915_WSEQ_BUSY 0x0100 /* WSEQ_BUSY */
+#define WM8915_WSEQ_BUSY_MASK 0x0100 /* WSEQ_BUSY */
+#define WM8915_WSEQ_BUSY_SHIFT 8 /* WSEQ_BUSY */
+#define WM8915_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+#define WM8915_WSEQ_CURRENT_INDEX_MASK 0x007F /* WSEQ_CURRENT_INDEX - [6:0] */
+#define WM8915_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [6:0] */
+#define WM8915_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [6:0] */
+
+/*
+ * R512 (0x200) - AIF Clocking (1)
+ */
+#define WM8915_SYSCLK_SRC_MASK 0x0018 /* SYSCLK_SRC - [4:3] */
+#define WM8915_SYSCLK_SRC_SHIFT 3 /* SYSCLK_SRC - [4:3] */
+#define WM8915_SYSCLK_SRC_WIDTH 2 /* SYSCLK_SRC - [4:3] */
+#define WM8915_SYSCLK_INV 0x0004 /* SYSCLK_INV */
+#define WM8915_SYSCLK_INV_MASK 0x0004 /* SYSCLK_INV */
+#define WM8915_SYSCLK_INV_SHIFT 2 /* SYSCLK_INV */
+#define WM8915_SYSCLK_INV_WIDTH 1 /* SYSCLK_INV */
+#define WM8915_SYSCLK_DIV 0x0002 /* SYSCLK_DIV */
+#define WM8915_SYSCLK_DIV_MASK 0x0002 /* SYSCLK_DIV */
+#define WM8915_SYSCLK_DIV_SHIFT 1 /* SYSCLK_DIV */
+#define WM8915_SYSCLK_DIV_WIDTH 1 /* SYSCLK_DIV */
+#define WM8915_SYSCLK_ENA 0x0001 /* SYSCLK_ENA */
+#define WM8915_SYSCLK_ENA_MASK 0x0001 /* SYSCLK_ENA */
+#define WM8915_SYSCLK_ENA_SHIFT 0 /* SYSCLK_ENA */
+#define WM8915_SYSCLK_ENA_WIDTH 1 /* SYSCLK_ENA */
+
+/*
+ * R513 (0x201) - AIF Clocking (2)
+ */
+#define WM8915_DSP2_DIV_MASK 0x0018 /* DSP2_DIV - [4:3] */
+#define WM8915_DSP2_DIV_SHIFT 3 /* DSP2_DIV - [4:3] */
+#define WM8915_DSP2_DIV_WIDTH 2 /* DSP2_DIV - [4:3] */
+#define WM8915_DSP1_DIV_MASK 0x0003 /* DSP1_DIV - [1:0] */
+#define WM8915_DSP1_DIV_SHIFT 0 /* DSP1_DIV - [1:0] */
+#define WM8915_DSP1_DIV_WIDTH 2 /* DSP1_DIV - [1:0] */
+
+/*
+ * R520 (0x208) - Clocking (1)
+ */
+#define WM8915_LFCLK_ENA 0x0020 /* LFCLK_ENA */
+#define WM8915_LFCLK_ENA_MASK 0x0020 /* LFCLK_ENA */
+#define WM8915_LFCLK_ENA_SHIFT 5 /* LFCLK_ENA */
+#define WM8915_LFCLK_ENA_WIDTH 1 /* LFCLK_ENA */
+#define WM8915_TOCLK_ENA 0x0010 /* TOCLK_ENA */
+#define WM8915_TOCLK_ENA_MASK 0x0010 /* TOCLK_ENA */
+#define WM8915_TOCLK_ENA_SHIFT 4 /* TOCLK_ENA */
+#define WM8915_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */
+#define WM8915_AIFCLK_ENA 0x0004 /* AIFCLK_ENA */
+#define WM8915_AIFCLK_ENA_MASK 0x0004 /* AIFCLK_ENA */
+#define WM8915_AIFCLK_ENA_SHIFT 2 /* AIFCLK_ENA */
+#define WM8915_AIFCLK_ENA_WIDTH 1 /* AIFCLK_ENA */
+#define WM8915_SYSDSPCLK_ENA 0x0002 /* SYSDSPCLK_ENA */
+#define WM8915_SYSDSPCLK_ENA_MASK 0x0002 /* SYSDSPCLK_ENA */
+#define WM8915_SYSDSPCLK_ENA_SHIFT 1 /* SYSDSPCLK_ENA */
+#define WM8915_SYSDSPCLK_ENA_WIDTH 1 /* SYSDSPCLK_ENA */
+
+/*
+ * R521 (0x209) - Clocking (2)
+ */
+#define WM8915_TOCLK_DIV_MASK 0x0700 /* TOCLK_DIV - [10:8] */
+#define WM8915_TOCLK_DIV_SHIFT 8 /* TOCLK_DIV - [10:8] */
+#define WM8915_TOCLK_DIV_WIDTH 3 /* TOCLK_DIV - [10:8] */
+#define WM8915_DBCLK_DIV_MASK 0x00F0 /* DBCLK_DIV - [7:4] */
+#define WM8915_DBCLK_DIV_SHIFT 4 /* DBCLK_DIV - [7:4] */
+#define WM8915_DBCLK_DIV_WIDTH 4 /* DBCLK_DIV - [7:4] */
+#define WM8915_OPCLK_DIV_MASK 0x0007 /* OPCLK_DIV - [2:0] */
+#define WM8915_OPCLK_DIV_SHIFT 0 /* OPCLK_DIV - [2:0] */
+#define WM8915_OPCLK_DIV_WIDTH 3 /* OPCLK_DIV - [2:0] */
+
+/*
+ * R528 (0x210) - AIF Rate
+ */
+#define WM8915_SYSCLK_RATE 0x0001 /* SYSCLK_RATE */
+#define WM8915_SYSCLK_RATE_MASK 0x0001 /* SYSCLK_RATE */
+#define WM8915_SYSCLK_RATE_SHIFT 0 /* SYSCLK_RATE */
+#define WM8915_SYSCLK_RATE_WIDTH 1 /* SYSCLK_RATE */
+
+/*
+ * R544 (0x220) - FLL Control (1)
+ */
+#define WM8915_FLL_OSC_ENA 0x0002 /* FLL_OSC_ENA */
+#define WM8915_FLL_OSC_ENA_MASK 0x0002 /* FLL_OSC_ENA */
+#define WM8915_FLL_OSC_ENA_SHIFT 1 /* FLL_OSC_ENA */
+#define WM8915_FLL_OSC_ENA_WIDTH 1 /* FLL_OSC_ENA */
+#define WM8915_FLL_ENA 0x0001 /* FLL_ENA */
+#define WM8915_FLL_ENA_MASK 0x0001 /* FLL_ENA */
+#define WM8915_FLL_ENA_SHIFT 0 /* FLL_ENA */
+#define WM8915_FLL_ENA_WIDTH 1 /* FLL_ENA */
+
+/*
+ * R545 (0x221) - FLL Control (2)
+ */
+#define WM8915_FLL_OUTDIV_MASK 0x3F00 /* FLL_OUTDIV - [13:8] */
+#define WM8915_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [13:8] */
+#define WM8915_FLL_OUTDIV_WIDTH 6 /* FLL_OUTDIV - [13:8] */
+#define WM8915_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */
+#define WM8915_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */
+#define WM8915_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */
+
+/*
+ * R546 (0x222) - FLL Control (3)
+ */
+#define WM8915_FLL_THETA_MASK 0xFFFF /* FLL_THETA - [15:0] */
+#define WM8915_FLL_THETA_SHIFT 0 /* FLL_THETA - [15:0] */
+#define WM8915_FLL_THETA_WIDTH 16 /* FLL_THETA - [15:0] */
+
+/*
+ * R547 (0x223) - FLL Control (4)
+ */
+#define WM8915_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */
+#define WM8915_FLL_N_SHIFT 5 /* FLL_N - [14:5] */
+#define WM8915_FLL_N_WIDTH 10 /* FLL_N - [14:5] */
+#define WM8915_FLL_LOOP_GAIN_MASK 0x000F /* FLL_LOOP_GAIN - [3:0] */
+#define WM8915_FLL_LOOP_GAIN_SHIFT 0 /* FLL_LOOP_GAIN - [3:0] */
+#define WM8915_FLL_LOOP_GAIN_WIDTH 4 /* FLL_LOOP_GAIN - [3:0] */
+
+/*
+ * R548 (0x224) - FLL Control (5)
+ */
+#define WM8915_FLL_FRC_NCO_VAL_MASK 0x1F80 /* FLL_FRC_NCO_VAL - [12:7] */
+#define WM8915_FLL_FRC_NCO_VAL_SHIFT 7 /* FLL_FRC_NCO_VAL - [12:7] */
+#define WM8915_FLL_FRC_NCO_VAL_WIDTH 6 /* FLL_FRC_NCO_VAL - [12:7] */
+#define WM8915_FLL_FRC_NCO 0x0040 /* FLL_FRC_NCO */
+#define WM8915_FLL_FRC_NCO_MASK 0x0040 /* FLL_FRC_NCO */
+#define WM8915_FLL_FRC_NCO_SHIFT 6 /* FLL_FRC_NCO */
+#define WM8915_FLL_FRC_NCO_WIDTH 1 /* FLL_FRC_NCO */
+#define WM8915_FLL_REFCLK_DIV_MASK 0x0018 /* FLL_REFCLK_DIV - [4:3] */
+#define WM8915_FLL_REFCLK_DIV_SHIFT 3 /* FLL_REFCLK_DIV - [4:3] */
+#define WM8915_FLL_REFCLK_DIV_WIDTH 2 /* FLL_REFCLK_DIV - [4:3] */
+#define WM8915_FLL_REF_FREQ 0x0004 /* FLL_REF_FREQ */
+#define WM8915_FLL_REF_FREQ_MASK 0x0004 /* FLL_REF_FREQ */
+#define WM8915_FLL_REF_FREQ_SHIFT 2 /* FLL_REF_FREQ */
+#define WM8915_FLL_REF_FREQ_WIDTH 1 /* FLL_REF_FREQ */
+#define WM8915_FLL_REFCLK_SRC_MASK 0x0003 /* FLL_REFCLK_SRC - [1:0] */
+#define WM8915_FLL_REFCLK_SRC_SHIFT 0 /* FLL_REFCLK_SRC - [1:0] */
+#define WM8915_FLL_REFCLK_SRC_WIDTH 2 /* FLL_REFCLK_SRC - [1:0] */
+
+/*
+ * R549 (0x225) - FLL Control (6)
+ */
+#define WM8915_FLL_REFCLK_SRC_STS_MASK 0x000C /* FLL_REFCLK_SRC_STS - [3:2] */
+#define WM8915_FLL_REFCLK_SRC_STS_SHIFT 2 /* FLL_REFCLK_SRC_STS - [3:2] */
+#define WM8915_FLL_REFCLK_SRC_STS_WIDTH 2 /* FLL_REFCLK_SRC_STS - [3:2] */
+#define WM8915_FLL_SWITCH_CLK 0x0001 /* FLL_SWITCH_CLK */
+#define WM8915_FLL_SWITCH_CLK_MASK 0x0001 /* FLL_SWITCH_CLK */
+#define WM8915_FLL_SWITCH_CLK_SHIFT 0 /* FLL_SWITCH_CLK */
+#define WM8915_FLL_SWITCH_CLK_WIDTH 1 /* FLL_SWITCH_CLK */
+
+/*
+ * R550 (0x226) - FLL EFS 1
+ */
+#define WM8915_FLL_LAMBDA_MASK 0xFFFF /* FLL_LAMBDA - [15:0] */
+#define WM8915_FLL_LAMBDA_SHIFT 0 /* FLL_LAMBDA - [15:0] */
+#define WM8915_FLL_LAMBDA_WIDTH 16 /* FLL_LAMBDA - [15:0] */
+
+/*
+ * R551 (0x227) - FLL EFS 2
+ */
+#define WM8915_FLL_LFSR_SEL_MASK 0x0006 /* FLL_LFSR_SEL - [2:1] */
+#define WM8915_FLL_LFSR_SEL_SHIFT 1 /* FLL_LFSR_SEL - [2:1] */
+#define WM8915_FLL_LFSR_SEL_WIDTH 2 /* FLL_LFSR_SEL - [2:1] */
+#define WM8915_FLL_EFS_ENA 0x0001 /* FLL_EFS_ENA */
+#define WM8915_FLL_EFS_ENA_MASK 0x0001 /* FLL_EFS_ENA */
+#define WM8915_FLL_EFS_ENA_SHIFT 0 /* FLL_EFS_ENA */
+#define WM8915_FLL_EFS_ENA_WIDTH 1 /* FLL_EFS_ENA */
+
+/*
+ * R768 (0x300) - AIF1 Control
+ */
+#define WM8915_AIF1_TRI 0x0004 /* AIF1_TRI */
+#define WM8915_AIF1_TRI_MASK 0x0004 /* AIF1_TRI */
+#define WM8915_AIF1_TRI_SHIFT 2 /* AIF1_TRI */
+#define WM8915_AIF1_TRI_WIDTH 1 /* AIF1_TRI */
+#define WM8915_AIF1_FMT_MASK 0x0003 /* AIF1_FMT - [1:0] */
+#define WM8915_AIF1_FMT_SHIFT 0 /* AIF1_FMT - [1:0] */
+#define WM8915_AIF1_FMT_WIDTH 2 /* AIF1_FMT - [1:0] */
+
+/*
+ * R769 (0x301) - AIF1 BCLK
+ */
+#define WM8915_AIF1_BCLK_INV 0x0400 /* AIF1_BCLK_INV */
+#define WM8915_AIF1_BCLK_INV_MASK 0x0400 /* AIF1_BCLK_INV */
+#define WM8915_AIF1_BCLK_INV_SHIFT 10 /* AIF1_BCLK_INV */
+#define WM8915_AIF1_BCLK_INV_WIDTH 1 /* AIF1_BCLK_INV */
+#define WM8915_AIF1_BCLK_FRC 0x0200 /* AIF1_BCLK_FRC */
+#define WM8915_AIF1_BCLK_FRC_MASK 0x0200 /* AIF1_BCLK_FRC */
+#define WM8915_AIF1_BCLK_FRC_SHIFT 9 /* AIF1_BCLK_FRC */
+#define WM8915_AIF1_BCLK_FRC_WIDTH 1 /* AIF1_BCLK_FRC */
+#define WM8915_AIF1_BCLK_MSTR 0x0100 /* AIF1_BCLK_MSTR */
+#define WM8915_AIF1_BCLK_MSTR_MASK 0x0100 /* AIF1_BCLK_MSTR */
+#define WM8915_AIF1_BCLK_MSTR_SHIFT 8 /* AIF1_BCLK_MSTR */
+#define WM8915_AIF1_BCLK_MSTR_WIDTH 1 /* AIF1_BCLK_MSTR */
+#define WM8915_AIF1_BCLK_DIV_MASK 0x000F /* AIF1_BCLK_DIV - [3:0] */
+#define WM8915_AIF1_BCLK_DIV_SHIFT 0 /* AIF1_BCLK_DIV - [3:0] */
+#define WM8915_AIF1_BCLK_DIV_WIDTH 4 /* AIF1_BCLK_DIV - [3:0] */
+
+/*
+ * R770 (0x302) - AIF1 TX LRCLK(1)
+ */
+#define WM8915_AIF1TX_RATE_MASK 0x07FF /* AIF1TX_RATE - [10:0] */
+#define WM8915_AIF1TX_RATE_SHIFT 0 /* AIF1TX_RATE - [10:0] */
+#define WM8915_AIF1TX_RATE_WIDTH 11 /* AIF1TX_RATE - [10:0] */
+
+/*
+ * R771 (0x303) - AIF1 TX LRCLK(2)
+ */
+#define WM8915_AIF1TX_LRCLK_MODE 0x0008 /* AIF1TX_LRCLK_MODE */
+#define WM8915_AIF1TX_LRCLK_MODE_MASK 0x0008 /* AIF1TX_LRCLK_MODE */
+#define WM8915_AIF1TX_LRCLK_MODE_SHIFT 3 /* AIF1TX_LRCLK_MODE */
+#define WM8915_AIF1TX_LRCLK_MODE_WIDTH 1 /* AIF1TX_LRCLK_MODE */
+#define WM8915_AIF1TX_LRCLK_INV 0x0004 /* AIF1TX_LRCLK_INV */
+#define WM8915_AIF1TX_LRCLK_INV_MASK 0x0004 /* AIF1TX_LRCLK_INV */
+#define WM8915_AIF1TX_LRCLK_INV_SHIFT 2 /* AIF1TX_LRCLK_INV */
+#define WM8915_AIF1TX_LRCLK_INV_WIDTH 1 /* AIF1TX_LRCLK_INV */
+#define WM8915_AIF1TX_LRCLK_FRC 0x0002 /* AIF1TX_LRCLK_FRC */
+#define WM8915_AIF1TX_LRCLK_FRC_MASK 0x0002 /* AIF1TX_LRCLK_FRC */
+#define WM8915_AIF1TX_LRCLK_FRC_SHIFT 1 /* AIF1TX_LRCLK_FRC */
+#define WM8915_AIF1TX_LRCLK_FRC_WIDTH 1 /* AIF1TX_LRCLK_FRC */
+#define WM8915_AIF1TX_LRCLK_MSTR 0x0001 /* AIF1TX_LRCLK_MSTR */
+#define WM8915_AIF1TX_LRCLK_MSTR_MASK 0x0001 /* AIF1TX_LRCLK_MSTR */
+#define WM8915_AIF1TX_LRCLK_MSTR_SHIFT 0 /* AIF1TX_LRCLK_MSTR */
+#define WM8915_AIF1TX_LRCLK_MSTR_WIDTH 1 /* AIF1TX_LRCLK_MSTR */
+
+/*
+ * R772 (0x304) - AIF1 RX LRCLK(1)
+ */
+#define WM8915_AIF1RX_RATE_MASK 0x07FF /* AIF1RX_RATE - [10:0] */
+#define WM8915_AIF1RX_RATE_SHIFT 0 /* AIF1RX_RATE - [10:0] */
+#define WM8915_AIF1RX_RATE_WIDTH 11 /* AIF1RX_RATE - [10:0] */
+
+/*
+ * R773 (0x305) - AIF1 RX LRCLK(2)
+ */
+#define WM8915_AIF1RX_LRCLK_INV 0x0004 /* AIF1RX_LRCLK_INV */
+#define WM8915_AIF1RX_LRCLK_INV_MASK 0x0004 /* AIF1RX_LRCLK_INV */
+#define WM8915_AIF1RX_LRCLK_INV_SHIFT 2 /* AIF1RX_LRCLK_INV */
+#define WM8915_AIF1RX_LRCLK_INV_WIDTH 1 /* AIF1RX_LRCLK_INV */
+#define WM8915_AIF1RX_LRCLK_FRC 0x0002 /* AIF1RX_LRCLK_FRC */
+#define WM8915_AIF1RX_LRCLK_FRC_MASK 0x0002 /* AIF1RX_LRCLK_FRC */
+#define WM8915_AIF1RX_LRCLK_FRC_SHIFT 1 /* AIF1RX_LRCLK_FRC */
+#define WM8915_AIF1RX_LRCLK_FRC_WIDTH 1 /* AIF1RX_LRCLK_FRC */
+#define WM8915_AIF1RX_LRCLK_MSTR 0x0001 /* AIF1RX_LRCLK_MSTR */
+#define WM8915_AIF1RX_LRCLK_MSTR_MASK 0x0001 /* AIF1RX_LRCLK_MSTR */
+#define WM8915_AIF1RX_LRCLK_MSTR_SHIFT 0 /* AIF1RX_LRCLK_MSTR */
+#define WM8915_AIF1RX_LRCLK_MSTR_WIDTH 1 /* AIF1RX_LRCLK_MSTR */
+
+/*
+ * R774 (0x306) - AIF1TX Data Configuration (1)
+ */
+#define WM8915_AIF1TX_WL_MASK 0xFF00 /* AIF1TX_WL - [15:8] */
+#define WM8915_AIF1TX_WL_SHIFT 8 /* AIF1TX_WL - [15:8] */
+#define WM8915_AIF1TX_WL_WIDTH 8 /* AIF1TX_WL - [15:8] */
+#define WM8915_AIF1TX_SLOT_LEN_MASK 0x00FF /* AIF1TX_SLOT_LEN - [7:0] */
+#define WM8915_AIF1TX_SLOT_LEN_SHIFT 0 /* AIF1TX_SLOT_LEN - [7:0] */
+#define WM8915_AIF1TX_SLOT_LEN_WIDTH 8 /* AIF1TX_SLOT_LEN - [7:0] */
+
+/*
+ * R775 (0x307) - AIF1TX Data Configuration (2)
+ */
+#define WM8915_AIF1TX_DAT_TRI 0x0001 /* AIF1TX_DAT_TRI */
+#define WM8915_AIF1TX_DAT_TRI_MASK 0x0001 /* AIF1TX_DAT_TRI */
+#define WM8915_AIF1TX_DAT_TRI_SHIFT 0 /* AIF1TX_DAT_TRI */
+#define WM8915_AIF1TX_DAT_TRI_WIDTH 1 /* AIF1TX_DAT_TRI */
+
+/*
+ * R776 (0x308) - AIF1RX Data Configuration
+ */
+#define WM8915_AIF1RX_WL_MASK 0xFF00 /* AIF1RX_WL - [15:8] */
+#define WM8915_AIF1RX_WL_SHIFT 8 /* AIF1RX_WL - [15:8] */
+#define WM8915_AIF1RX_WL_WIDTH 8 /* AIF1RX_WL - [15:8] */
+#define WM8915_AIF1RX_SLOT_LEN_MASK 0x00FF /* AIF1RX_SLOT_LEN - [7:0] */
+#define WM8915_AIF1RX_SLOT_LEN_SHIFT 0 /* AIF1RX_SLOT_LEN - [7:0] */
+#define WM8915_AIF1RX_SLOT_LEN_WIDTH 8 /* AIF1RX_SLOT_LEN - [7:0] */
+
+/*
+ * R777 (0x309) - AIF1TX Channel 0 Configuration
+ */
+#define WM8915_AIF1TX_CHAN0_DAT_INV 0x8000 /* AIF1TX_CHAN0_DAT_INV */
+#define WM8915_AIF1TX_CHAN0_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN0_DAT_INV */
+#define WM8915_AIF1TX_CHAN0_DAT_INV_SHIFT 15 /* AIF1TX_CHAN0_DAT_INV */
+#define WM8915_AIF1TX_CHAN0_DAT_INV_WIDTH 1 /* AIF1TX_CHAN0_DAT_INV */
+#define WM8915_AIF1TX_CHAN0_SPACING_MASK 0x7E00 /* AIF1TX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN0_SPACING_SHIFT 9 /* AIF1TX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN0_SPACING_WIDTH 6 /* AIF1TX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN0_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN0_SLOTS_SHIFT 6 /* AIF1TX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN0_SLOTS_WIDTH 3 /* AIF1TX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN0_START_SLOT_MASK 0x003F /* AIF1TX_CHAN0_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN0_START_SLOT_SHIFT 0 /* AIF1TX_CHAN0_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN0_START_SLOT_WIDTH 6 /* AIF1TX_CHAN0_START_SLOT - [5:0] */
+
+/*
+ * R778 (0x30A) - AIF1TX Channel 1 Configuration
+ */
+#define WM8915_AIF1TX_CHAN1_DAT_INV 0x8000 /* AIF1TX_CHAN1_DAT_INV */
+#define WM8915_AIF1TX_CHAN1_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN1_DAT_INV */
+#define WM8915_AIF1TX_CHAN1_DAT_INV_SHIFT 15 /* AIF1TX_CHAN1_DAT_INV */
+#define WM8915_AIF1TX_CHAN1_DAT_INV_WIDTH 1 /* AIF1TX_CHAN1_DAT_INV */
+#define WM8915_AIF1TX_CHAN1_SPACING_MASK 0x7E00 /* AIF1TX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN1_SPACING_SHIFT 9 /* AIF1TX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN1_SPACING_WIDTH 6 /* AIF1TX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN1_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN1_SLOTS_SHIFT 6 /* AIF1TX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN1_SLOTS_WIDTH 3 /* AIF1TX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN1_START_SLOT_MASK 0x003F /* AIF1TX_CHAN1_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN1_START_SLOT_SHIFT 0 /* AIF1TX_CHAN1_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN1_START_SLOT_WIDTH 6 /* AIF1TX_CHAN1_START_SLOT - [5:0] */
+
+/*
+ * R779 (0x30B) - AIF1TX Channel 2 Configuration
+ */
+#define WM8915_AIF1TX_CHAN2_DAT_INV 0x8000 /* AIF1TX_CHAN2_DAT_INV */
+#define WM8915_AIF1TX_CHAN2_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN2_DAT_INV */
+#define WM8915_AIF1TX_CHAN2_DAT_INV_SHIFT 15 /* AIF1TX_CHAN2_DAT_INV */
+#define WM8915_AIF1TX_CHAN2_DAT_INV_WIDTH 1 /* AIF1TX_CHAN2_DAT_INV */
+#define WM8915_AIF1TX_CHAN2_SPACING_MASK 0x7E00 /* AIF1TX_CHAN2_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN2_SPACING_SHIFT 9 /* AIF1TX_CHAN2_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN2_SPACING_WIDTH 6 /* AIF1TX_CHAN2_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN2_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN2_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN2_SLOTS_SHIFT 6 /* AIF1TX_CHAN2_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN2_SLOTS_WIDTH 3 /* AIF1TX_CHAN2_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN2_START_SLOT_MASK 0x003F /* AIF1TX_CHAN2_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN2_START_SLOT_SHIFT 0 /* AIF1TX_CHAN2_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN2_START_SLOT_WIDTH 6 /* AIF1TX_CHAN2_START_SLOT - [5:0] */
+
+/*
+ * R780 (0x30C) - AIF1TX Channel 3 Configuration
+ */
+#define WM8915_AIF1TX_CHAN3_DAT_INV 0x8000 /* AIF1TX_CHAN3_DAT_INV */
+#define WM8915_AIF1TX_CHAN3_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN3_DAT_INV */
+#define WM8915_AIF1TX_CHAN3_DAT_INV_SHIFT 15 /* AIF1TX_CHAN3_DAT_INV */
+#define WM8915_AIF1TX_CHAN3_DAT_INV_WIDTH 1 /* AIF1TX_CHAN3_DAT_INV */
+#define WM8915_AIF1TX_CHAN3_SPACING_MASK 0x7E00 /* AIF1TX_CHAN3_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN3_SPACING_SHIFT 9 /* AIF1TX_CHAN3_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN3_SPACING_WIDTH 6 /* AIF1TX_CHAN3_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN3_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN3_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN3_SLOTS_SHIFT 6 /* AIF1TX_CHAN3_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN3_SLOTS_WIDTH 3 /* AIF1TX_CHAN3_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN3_START_SLOT_MASK 0x003F /* AIF1TX_CHAN3_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN3_START_SLOT_SHIFT 0 /* AIF1TX_CHAN3_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN3_START_SLOT_WIDTH 6 /* AIF1TX_CHAN3_START_SLOT - [5:0] */
+
+/*
+ * R781 (0x30D) - AIF1TX Channel 4 Configuration
+ */
+#define WM8915_AIF1TX_CHAN4_DAT_INV 0x8000 /* AIF1TX_CHAN4_DAT_INV */
+#define WM8915_AIF1TX_CHAN4_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN4_DAT_INV */
+#define WM8915_AIF1TX_CHAN4_DAT_INV_SHIFT 15 /* AIF1TX_CHAN4_DAT_INV */
+#define WM8915_AIF1TX_CHAN4_DAT_INV_WIDTH 1 /* AIF1TX_CHAN4_DAT_INV */
+#define WM8915_AIF1TX_CHAN4_SPACING_MASK 0x7E00 /* AIF1TX_CHAN4_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN4_SPACING_SHIFT 9 /* AIF1TX_CHAN4_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN4_SPACING_WIDTH 6 /* AIF1TX_CHAN4_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN4_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN4_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN4_SLOTS_SHIFT 6 /* AIF1TX_CHAN4_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN4_SLOTS_WIDTH 3 /* AIF1TX_CHAN4_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN4_START_SLOT_MASK 0x003F /* AIF1TX_CHAN4_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN4_START_SLOT_SHIFT 0 /* AIF1TX_CHAN4_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN4_START_SLOT_WIDTH 6 /* AIF1TX_CHAN4_START_SLOT - [5:0] */
+
+/*
+ * R782 (0x30E) - AIF1TX Channel 5 Configuration
+ */
+#define WM8915_AIF1TX_CHAN5_DAT_INV 0x8000 /* AIF1TX_CHAN5_DAT_INV */
+#define WM8915_AIF1TX_CHAN5_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN5_DAT_INV */
+#define WM8915_AIF1TX_CHAN5_DAT_INV_SHIFT 15 /* AIF1TX_CHAN5_DAT_INV */
+#define WM8915_AIF1TX_CHAN5_DAT_INV_WIDTH 1 /* AIF1TX_CHAN5_DAT_INV */
+#define WM8915_AIF1TX_CHAN5_SPACING_MASK 0x7E00 /* AIF1TX_CHAN5_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN5_SPACING_SHIFT 9 /* AIF1TX_CHAN5_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN5_SPACING_WIDTH 6 /* AIF1TX_CHAN5_SPACING - [14:9] */
+#define WM8915_AIF1TX_CHAN5_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN5_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN5_SLOTS_SHIFT 6 /* AIF1TX_CHAN5_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN5_SLOTS_WIDTH 3 /* AIF1TX_CHAN5_SLOTS - [8:6] */
+#define WM8915_AIF1TX_CHAN5_START_SLOT_MASK 0x003F /* AIF1TX_CHAN5_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN5_START_SLOT_SHIFT 0 /* AIF1TX_CHAN5_START_SLOT - [5:0] */
+#define WM8915_AIF1TX_CHAN5_START_SLOT_WIDTH 6 /* AIF1TX_CHAN5_START_SLOT - [5:0] */
+
+/*
+ * R783 (0x30F) - AIF1RX Channel 0 Configuration
+ */
+#define WM8915_AIF1RX_CHAN0_DAT_INV 0x8000 /* AIF1RX_CHAN0_DAT_INV */
+#define WM8915_AIF1RX_CHAN0_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN0_DAT_INV */
+#define WM8915_AIF1RX_CHAN0_DAT_INV_SHIFT 15 /* AIF1RX_CHAN0_DAT_INV */
+#define WM8915_AIF1RX_CHAN0_DAT_INV_WIDTH 1 /* AIF1RX_CHAN0_DAT_INV */
+#define WM8915_AIF1RX_CHAN0_SPACING_MASK 0x7E00 /* AIF1RX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN0_SPACING_SHIFT 9 /* AIF1RX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN0_SPACING_WIDTH 6 /* AIF1RX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN0_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN0_SLOTS_SHIFT 6 /* AIF1RX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN0_SLOTS_WIDTH 3 /* AIF1RX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN0_START_SLOT_MASK 0x003F /* AIF1RX_CHAN0_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN0_START_SLOT_SHIFT 0 /* AIF1RX_CHAN0_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN0_START_SLOT_WIDTH 6 /* AIF1RX_CHAN0_START_SLOT - [5:0] */
+
+/*
+ * R784 (0x310) - AIF1RX Channel 1 Configuration
+ */
+#define WM8915_AIF1RX_CHAN1_DAT_INV 0x8000 /* AIF1RX_CHAN1_DAT_INV */
+#define WM8915_AIF1RX_CHAN1_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN1_DAT_INV */
+#define WM8915_AIF1RX_CHAN1_DAT_INV_SHIFT 15 /* AIF1RX_CHAN1_DAT_INV */
+#define WM8915_AIF1RX_CHAN1_DAT_INV_WIDTH 1 /* AIF1RX_CHAN1_DAT_INV */
+#define WM8915_AIF1RX_CHAN1_SPACING_MASK 0x7E00 /* AIF1RX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN1_SPACING_SHIFT 9 /* AIF1RX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN1_SPACING_WIDTH 6 /* AIF1RX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN1_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN1_SLOTS_SHIFT 6 /* AIF1RX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN1_SLOTS_WIDTH 3 /* AIF1RX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN1_START_SLOT_MASK 0x003F /* AIF1RX_CHAN1_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN1_START_SLOT_SHIFT 0 /* AIF1RX_CHAN1_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN1_START_SLOT_WIDTH 6 /* AIF1RX_CHAN1_START_SLOT - [5:0] */
+
+/*
+ * R785 (0x311) - AIF1RX Channel 2 Configuration
+ */
+#define WM8915_AIF1RX_CHAN2_DAT_INV 0x8000 /* AIF1RX_CHAN2_DAT_INV */
+#define WM8915_AIF1RX_CHAN2_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN2_DAT_INV */
+#define WM8915_AIF1RX_CHAN2_DAT_INV_SHIFT 15 /* AIF1RX_CHAN2_DAT_INV */
+#define WM8915_AIF1RX_CHAN2_DAT_INV_WIDTH 1 /* AIF1RX_CHAN2_DAT_INV */
+#define WM8915_AIF1RX_CHAN2_SPACING_MASK 0x7E00 /* AIF1RX_CHAN2_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN2_SPACING_SHIFT 9 /* AIF1RX_CHAN2_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN2_SPACING_WIDTH 6 /* AIF1RX_CHAN2_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN2_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN2_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN2_SLOTS_SHIFT 6 /* AIF1RX_CHAN2_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN2_SLOTS_WIDTH 3 /* AIF1RX_CHAN2_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN2_START_SLOT_MASK 0x003F /* AIF1RX_CHAN2_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN2_START_SLOT_SHIFT 0 /* AIF1RX_CHAN2_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN2_START_SLOT_WIDTH 6 /* AIF1RX_CHAN2_START_SLOT - [5:0] */
+
+/*
+ * R786 (0x312) - AIF1RX Channel 3 Configuration
+ */
+#define WM8915_AIF1RX_CHAN3_DAT_INV 0x8000 /* AIF1RX_CHAN3_DAT_INV */
+#define WM8915_AIF1RX_CHAN3_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN3_DAT_INV */
+#define WM8915_AIF1RX_CHAN3_DAT_INV_SHIFT 15 /* AIF1RX_CHAN3_DAT_INV */
+#define WM8915_AIF1RX_CHAN3_DAT_INV_WIDTH 1 /* AIF1RX_CHAN3_DAT_INV */
+#define WM8915_AIF1RX_CHAN3_SPACING_MASK 0x7E00 /* AIF1RX_CHAN3_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN3_SPACING_SHIFT 9 /* AIF1RX_CHAN3_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN3_SPACING_WIDTH 6 /* AIF1RX_CHAN3_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN3_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN3_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN3_SLOTS_SHIFT 6 /* AIF1RX_CHAN3_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN3_SLOTS_WIDTH 3 /* AIF1RX_CHAN3_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN3_START_SLOT_MASK 0x003F /* AIF1RX_CHAN3_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN3_START_SLOT_SHIFT 0 /* AIF1RX_CHAN3_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN3_START_SLOT_WIDTH 6 /* AIF1RX_CHAN3_START_SLOT - [5:0] */
+
+/*
+ * R787 (0x313) - AIF1RX Channel 4 Configuration
+ */
+#define WM8915_AIF1RX_CHAN4_DAT_INV 0x8000 /* AIF1RX_CHAN4_DAT_INV */
+#define WM8915_AIF1RX_CHAN4_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN4_DAT_INV */
+#define WM8915_AIF1RX_CHAN4_DAT_INV_SHIFT 15 /* AIF1RX_CHAN4_DAT_INV */
+#define WM8915_AIF1RX_CHAN4_DAT_INV_WIDTH 1 /* AIF1RX_CHAN4_DAT_INV */
+#define WM8915_AIF1RX_CHAN4_SPACING_MASK 0x7E00 /* AIF1RX_CHAN4_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN4_SPACING_SHIFT 9 /* AIF1RX_CHAN4_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN4_SPACING_WIDTH 6 /* AIF1RX_CHAN4_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN4_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN4_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN4_SLOTS_SHIFT 6 /* AIF1RX_CHAN4_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN4_SLOTS_WIDTH 3 /* AIF1RX_CHAN4_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN4_START_SLOT_MASK 0x003F /* AIF1RX_CHAN4_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN4_START_SLOT_SHIFT 0 /* AIF1RX_CHAN4_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN4_START_SLOT_WIDTH 6 /* AIF1RX_CHAN4_START_SLOT - [5:0] */
+
+/*
+ * R788 (0x314) - AIF1RX Channel 5 Configuration
+ */
+#define WM8915_AIF1RX_CHAN5_DAT_INV 0x8000 /* AIF1RX_CHAN5_DAT_INV */
+#define WM8915_AIF1RX_CHAN5_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN5_DAT_INV */
+#define WM8915_AIF1RX_CHAN5_DAT_INV_SHIFT 15 /* AIF1RX_CHAN5_DAT_INV */
+#define WM8915_AIF1RX_CHAN5_DAT_INV_WIDTH 1 /* AIF1RX_CHAN5_DAT_INV */
+#define WM8915_AIF1RX_CHAN5_SPACING_MASK 0x7E00 /* AIF1RX_CHAN5_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN5_SPACING_SHIFT 9 /* AIF1RX_CHAN5_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN5_SPACING_WIDTH 6 /* AIF1RX_CHAN5_SPACING - [14:9] */
+#define WM8915_AIF1RX_CHAN5_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN5_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN5_SLOTS_SHIFT 6 /* AIF1RX_CHAN5_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN5_SLOTS_WIDTH 3 /* AIF1RX_CHAN5_SLOTS - [8:6] */
+#define WM8915_AIF1RX_CHAN5_START_SLOT_MASK 0x003F /* AIF1RX_CHAN5_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN5_START_SLOT_SHIFT 0 /* AIF1RX_CHAN5_START_SLOT - [5:0] */
+#define WM8915_AIF1RX_CHAN5_START_SLOT_WIDTH 6 /* AIF1RX_CHAN5_START_SLOT - [5:0] */
+
+/*
+ * R789 (0x315) - AIF1RX Mono Configuration
+ */
+#define WM8915_AIF1RX_CHAN4_MONO_MODE 0x0004 /* AIF1RX_CHAN4_MONO_MODE */
+#define WM8915_AIF1RX_CHAN4_MONO_MODE_MASK 0x0004 /* AIF1RX_CHAN4_MONO_MODE */
+#define WM8915_AIF1RX_CHAN4_MONO_MODE_SHIFT 2 /* AIF1RX_CHAN4_MONO_MODE */
+#define WM8915_AIF1RX_CHAN4_MONO_MODE_WIDTH 1 /* AIF1RX_CHAN4_MONO_MODE */
+#define WM8915_AIF1RX_CHAN2_MONO_MODE 0x0002 /* AIF1RX_CHAN2_MONO_MODE */
+#define WM8915_AIF1RX_CHAN2_MONO_MODE_MASK 0x0002 /* AIF1RX_CHAN2_MONO_MODE */
+#define WM8915_AIF1RX_CHAN2_MONO_MODE_SHIFT 1 /* AIF1RX_CHAN2_MONO_MODE */
+#define WM8915_AIF1RX_CHAN2_MONO_MODE_WIDTH 1 /* AIF1RX_CHAN2_MONO_MODE */
+#define WM8915_AIF1RX_CHAN0_MONO_MODE 0x0001 /* AIF1RX_CHAN0_MONO_MODE */
+#define WM8915_AIF1RX_CHAN0_MONO_MODE_MASK 0x0001 /* AIF1RX_CHAN0_MONO_MODE */
+#define WM8915_AIF1RX_CHAN0_MONO_MODE_SHIFT 0 /* AIF1RX_CHAN0_MONO_MODE */
+#define WM8915_AIF1RX_CHAN0_MONO_MODE_WIDTH 1 /* AIF1RX_CHAN0_MONO_MODE */
+
+/*
+ * R794 (0x31A) - AIF1TX Test
+ */
+#define WM8915_AIF1TX45_DITHER_ENA 0x0004 /* AIF1TX45_DITHER_ENA */
+#define WM8915_AIF1TX45_DITHER_ENA_MASK 0x0004 /* AIF1TX45_DITHER_ENA */
+#define WM8915_AIF1TX45_DITHER_ENA_SHIFT 2 /* AIF1TX45_DITHER_ENA */
+#define WM8915_AIF1TX45_DITHER_ENA_WIDTH 1 /* AIF1TX45_DITHER_ENA */
+#define WM8915_AIF1TX23_DITHER_ENA 0x0002 /* AIF1TX23_DITHER_ENA */
+#define WM8915_AIF1TX23_DITHER_ENA_MASK 0x0002 /* AIF1TX23_DITHER_ENA */
+#define WM8915_AIF1TX23_DITHER_ENA_SHIFT 1 /* AIF1TX23_DITHER_ENA */
+#define WM8915_AIF1TX23_DITHER_ENA_WIDTH 1 /* AIF1TX23_DITHER_ENA */
+#define WM8915_AIF1TX01_DITHER_ENA 0x0001 /* AIF1TX01_DITHER_ENA */
+#define WM8915_AIF1TX01_DITHER_ENA_MASK 0x0001 /* AIF1TX01_DITHER_ENA */
+#define WM8915_AIF1TX01_DITHER_ENA_SHIFT 0 /* AIF1TX01_DITHER_ENA */
+#define WM8915_AIF1TX01_DITHER_ENA_WIDTH 1 /* AIF1TX01_DITHER_ENA */
+
+/*
+ * R800 (0x320) - AIF2 Control
+ */
+#define WM8915_AIF2_TRI 0x0004 /* AIF2_TRI */
+#define WM8915_AIF2_TRI_MASK 0x0004 /* AIF2_TRI */
+#define WM8915_AIF2_TRI_SHIFT 2 /* AIF2_TRI */
+#define WM8915_AIF2_TRI_WIDTH 1 /* AIF2_TRI */
+#define WM8915_AIF2_FMT_MASK 0x0003 /* AIF2_FMT - [1:0] */
+#define WM8915_AIF2_FMT_SHIFT 0 /* AIF2_FMT - [1:0] */
+#define WM8915_AIF2_FMT_WIDTH 2 /* AIF2_FMT - [1:0] */
+
+/*
+ * R801 (0x321) - AIF2 BCLK
+ */
+#define WM8915_AIF2_BCLK_INV 0x0400 /* AIF2_BCLK_INV */
+#define WM8915_AIF2_BCLK_INV_MASK 0x0400 /* AIF2_BCLK_INV */
+#define WM8915_AIF2_BCLK_INV_SHIFT 10 /* AIF2_BCLK_INV */
+#define WM8915_AIF2_BCLK_INV_WIDTH 1 /* AIF2_BCLK_INV */
+#define WM8915_AIF2_BCLK_FRC 0x0200 /* AIF2_BCLK_FRC */
+#define WM8915_AIF2_BCLK_FRC_MASK 0x0200 /* AIF2_BCLK_FRC */
+#define WM8915_AIF2_BCLK_FRC_SHIFT 9 /* AIF2_BCLK_FRC */
+#define WM8915_AIF2_BCLK_FRC_WIDTH 1 /* AIF2_BCLK_FRC */
+#define WM8915_AIF2_BCLK_MSTR 0x0100 /* AIF2_BCLK_MSTR */
+#define WM8915_AIF2_BCLK_MSTR_MASK 0x0100 /* AIF2_BCLK_MSTR */
+#define WM8915_AIF2_BCLK_MSTR_SHIFT 8 /* AIF2_BCLK_MSTR */
+#define WM8915_AIF2_BCLK_MSTR_WIDTH 1 /* AIF2_BCLK_MSTR */
+#define WM8915_AIF2_BCLK_DIV_MASK 0x000F /* AIF2_BCLK_DIV - [3:0] */
+#define WM8915_AIF2_BCLK_DIV_SHIFT 0 /* AIF2_BCLK_DIV - [3:0] */
+#define WM8915_AIF2_BCLK_DIV_WIDTH 4 /* AIF2_BCLK_DIV - [3:0] */
+
+/*
+ * R802 (0x322) - AIF2 TX LRCLK(1)
+ */
+#define WM8915_AIF2TX_RATE_MASK 0x07FF /* AIF2TX_RATE - [10:0] */
+#define WM8915_AIF2TX_RATE_SHIFT 0 /* AIF2TX_RATE - [10:0] */
+#define WM8915_AIF2TX_RATE_WIDTH 11 /* AIF2TX_RATE - [10:0] */
+
+/*
+ * R803 (0x323) - AIF2 TX LRCLK(2)
+ */
+#define WM8915_AIF2TX_LRCLK_MODE 0x0008 /* AIF2TX_LRCLK_MODE */
+#define WM8915_AIF2TX_LRCLK_MODE_MASK 0x0008 /* AIF2TX_LRCLK_MODE */
+#define WM8915_AIF2TX_LRCLK_MODE_SHIFT 3 /* AIF2TX_LRCLK_MODE */
+#define WM8915_AIF2TX_LRCLK_MODE_WIDTH 1 /* AIF2TX_LRCLK_MODE */
+#define WM8915_AIF2TX_LRCLK_INV 0x0004 /* AIF2TX_LRCLK_INV */
+#define WM8915_AIF2TX_LRCLK_INV_MASK 0x0004 /* AIF2TX_LRCLK_INV */
+#define WM8915_AIF2TX_LRCLK_INV_SHIFT 2 /* AIF2TX_LRCLK_INV */
+#define WM8915_AIF2TX_LRCLK_INV_WIDTH 1 /* AIF2TX_LRCLK_INV */
+#define WM8915_AIF2TX_LRCLK_FRC 0x0002 /* AIF2TX_LRCLK_FRC */
+#define WM8915_AIF2TX_LRCLK_FRC_MASK 0x0002 /* AIF2TX_LRCLK_FRC */
+#define WM8915_AIF2TX_LRCLK_FRC_SHIFT 1 /* AIF2TX_LRCLK_FRC */
+#define WM8915_AIF2TX_LRCLK_FRC_WIDTH 1 /* AIF2TX_LRCLK_FRC */
+#define WM8915_AIF2TX_LRCLK_MSTR 0x0001 /* AIF2TX_LRCLK_MSTR */
+#define WM8915_AIF2TX_LRCLK_MSTR_MASK 0x0001 /* AIF2TX_LRCLK_MSTR */
+#define WM8915_AIF2TX_LRCLK_MSTR_SHIFT 0 /* AIF2TX_LRCLK_MSTR */
+#define WM8915_AIF2TX_LRCLK_MSTR_WIDTH 1 /* AIF2TX_LRCLK_MSTR */
+
+/*
+ * R804 (0x324) - AIF2 RX LRCLK(1)
+ */
+#define WM8915_AIF2RX_RATE_MASK 0x07FF /* AIF2RX_RATE - [10:0] */
+#define WM8915_AIF2RX_RATE_SHIFT 0 /* AIF2RX_RATE - [10:0] */
+#define WM8915_AIF2RX_RATE_WIDTH 11 /* AIF2RX_RATE - [10:0] */
+
+/*
+ * R805 (0x325) - AIF2 RX LRCLK(2)
+ */
+#define WM8915_AIF2RX_LRCLK_INV 0x0004 /* AIF2RX_LRCLK_INV */
+#define WM8915_AIF2RX_LRCLK_INV_MASK 0x0004 /* AIF2RX_LRCLK_INV */
+#define WM8915_AIF2RX_LRCLK_INV_SHIFT 2 /* AIF2RX_LRCLK_INV */
+#define WM8915_AIF2RX_LRCLK_INV_WIDTH 1 /* AIF2RX_LRCLK_INV */
+#define WM8915_AIF2RX_LRCLK_FRC 0x0002 /* AIF2RX_LRCLK_FRC */
+#define WM8915_AIF2RX_LRCLK_FRC_MASK 0x0002 /* AIF2RX_LRCLK_FRC */
+#define WM8915_AIF2RX_LRCLK_FRC_SHIFT 1 /* AIF2RX_LRCLK_FRC */
+#define WM8915_AIF2RX_LRCLK_FRC_WIDTH 1 /* AIF2RX_LRCLK_FRC */
+#define WM8915_AIF2RX_LRCLK_MSTR 0x0001 /* AIF2RX_LRCLK_MSTR */
+#define WM8915_AIF2RX_LRCLK_MSTR_MASK 0x0001 /* AIF2RX_LRCLK_MSTR */
+#define WM8915_AIF2RX_LRCLK_MSTR_SHIFT 0 /* AIF2RX_LRCLK_MSTR */
+#define WM8915_AIF2RX_LRCLK_MSTR_WIDTH 1 /* AIF2RX_LRCLK_MSTR */
+
+/*
+ * R806 (0x326) - AIF2TX Data Configuration (1)
+ */
+#define WM8915_AIF2TX_WL_MASK 0xFF00 /* AIF2TX_WL - [15:8] */
+#define WM8915_AIF2TX_WL_SHIFT 8 /* AIF2TX_WL - [15:8] */
+#define WM8915_AIF2TX_WL_WIDTH 8 /* AIF2TX_WL - [15:8] */
+#define WM8915_AIF2TX_SLOT_LEN_MASK 0x00FF /* AIF2TX_SLOT_LEN - [7:0] */
+#define WM8915_AIF2TX_SLOT_LEN_SHIFT 0 /* AIF2TX_SLOT_LEN - [7:0] */
+#define WM8915_AIF2TX_SLOT_LEN_WIDTH 8 /* AIF2TX_SLOT_LEN - [7:0] */
+
+/*
+ * R807 (0x327) - AIF2TX Data Configuration (2)
+ */
+#define WM8915_AIF2TX_DAT_TRI 0x0001 /* AIF2TX_DAT_TRI */
+#define WM8915_AIF2TX_DAT_TRI_MASK 0x0001 /* AIF2TX_DAT_TRI */
+#define WM8915_AIF2TX_DAT_TRI_SHIFT 0 /* AIF2TX_DAT_TRI */
+#define WM8915_AIF2TX_DAT_TRI_WIDTH 1 /* AIF2TX_DAT_TRI */
+
+/*
+ * R808 (0x328) - AIF2RX Data Configuration
+ */
+#define WM8915_AIF2RX_WL_MASK 0xFF00 /* AIF2RX_WL - [15:8] */
+#define WM8915_AIF2RX_WL_SHIFT 8 /* AIF2RX_WL - [15:8] */
+#define WM8915_AIF2RX_WL_WIDTH 8 /* AIF2RX_WL - [15:8] */
+#define WM8915_AIF2RX_SLOT_LEN_MASK 0x00FF /* AIF2RX_SLOT_LEN - [7:0] */
+#define WM8915_AIF2RX_SLOT_LEN_SHIFT 0 /* AIF2RX_SLOT_LEN - [7:0] */
+#define WM8915_AIF2RX_SLOT_LEN_WIDTH 8 /* AIF2RX_SLOT_LEN - [7:0] */
+
+/*
+ * R809 (0x329) - AIF2TX Channel 0 Configuration
+ */
+#define WM8915_AIF2TX_CHAN0_DAT_INV 0x8000 /* AIF2TX_CHAN0_DAT_INV */
+#define WM8915_AIF2TX_CHAN0_DAT_INV_MASK 0x8000 /* AIF2TX_CHAN0_DAT_INV */
+#define WM8915_AIF2TX_CHAN0_DAT_INV_SHIFT 15 /* AIF2TX_CHAN0_DAT_INV */
+#define WM8915_AIF2TX_CHAN0_DAT_INV_WIDTH 1 /* AIF2TX_CHAN0_DAT_INV */
+#define WM8915_AIF2TX_CHAN0_SPACING_MASK 0x7E00 /* AIF2TX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF2TX_CHAN0_SPACING_SHIFT 9 /* AIF2TX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF2TX_CHAN0_SPACING_WIDTH 6 /* AIF2TX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF2TX_CHAN0_SLOTS_MASK 0x01C0 /* AIF2TX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF2TX_CHAN0_SLOTS_SHIFT 6 /* AIF2TX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF2TX_CHAN0_SLOTS_WIDTH 3 /* AIF2TX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF2TX_CHAN0_START_SLOT_MASK 0x003F /* AIF2TX_CHAN0_START_SLOT - [5:0] */
+#define WM8915_AIF2TX_CHAN0_START_SLOT_SHIFT 0 /* AIF2TX_CHAN0_START_SLOT - [5:0] */
+#define WM8915_AIF2TX_CHAN0_START_SLOT_WIDTH 6 /* AIF2TX_CHAN0_START_SLOT - [5:0] */
+
+/*
+ * R810 (0x32A) - AIF2TX Channel 1 Configuration
+ */
+#define WM8915_AIF2TX_CHAN1_DAT_INV 0x8000 /* AIF2TX_CHAN1_DAT_INV */
+#define WM8915_AIF2TX_CHAN1_DAT_INV_MASK 0x8000 /* AIF2TX_CHAN1_DAT_INV */
+#define WM8915_AIF2TX_CHAN1_DAT_INV_SHIFT 15 /* AIF2TX_CHAN1_DAT_INV */
+#define WM8915_AIF2TX_CHAN1_DAT_INV_WIDTH 1 /* AIF2TX_CHAN1_DAT_INV */
+#define WM8915_AIF2TX_CHAN1_SPACING_MASK 0x7E00 /* AIF2TX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF2TX_CHAN1_SPACING_SHIFT 9 /* AIF2TX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF2TX_CHAN1_SPACING_WIDTH 6 /* AIF2TX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF2TX_CHAN1_SLOTS_MASK 0x01C0 /* AIF2TX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF2TX_CHAN1_SLOTS_SHIFT 6 /* AIF2TX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF2TX_CHAN1_SLOTS_WIDTH 3 /* AIF2TX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF2TX_CHAN1_START_SLOT_MASK 0x003F /* AIF2TX_CHAN1_START_SLOT - [5:0] */
+#define WM8915_AIF2TX_CHAN1_START_SLOT_SHIFT 0 /* AIF2TX_CHAN1_START_SLOT - [5:0] */
+#define WM8915_AIF2TX_CHAN1_START_SLOT_WIDTH 6 /* AIF2TX_CHAN1_START_SLOT - [5:0] */
+
+/*
+ * R811 (0x32B) - AIF2RX Channel 0 Configuration
+ */
+#define WM8915_AIF2RX_CHAN0_DAT_INV 0x8000 /* AIF2RX_CHAN0_DAT_INV */
+#define WM8915_AIF2RX_CHAN0_DAT_INV_MASK 0x8000 /* AIF2RX_CHAN0_DAT_INV */
+#define WM8915_AIF2RX_CHAN0_DAT_INV_SHIFT 15 /* AIF2RX_CHAN0_DAT_INV */
+#define WM8915_AIF2RX_CHAN0_DAT_INV_WIDTH 1 /* AIF2RX_CHAN0_DAT_INV */
+#define WM8915_AIF2RX_CHAN0_SPACING_MASK 0x7E00 /* AIF2RX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF2RX_CHAN0_SPACING_SHIFT 9 /* AIF2RX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF2RX_CHAN0_SPACING_WIDTH 6 /* AIF2RX_CHAN0_SPACING - [14:9] */
+#define WM8915_AIF2RX_CHAN0_SLOTS_MASK 0x01C0 /* AIF2RX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF2RX_CHAN0_SLOTS_SHIFT 6 /* AIF2RX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF2RX_CHAN0_SLOTS_WIDTH 3 /* AIF2RX_CHAN0_SLOTS - [8:6] */
+#define WM8915_AIF2RX_CHAN0_START_SLOT_MASK 0x003F /* AIF2RX_CHAN0_START_SLOT - [5:0] */
+#define WM8915_AIF2RX_CHAN0_START_SLOT_SHIFT 0 /* AIF2RX_CHAN0_START_SLOT - [5:0] */
+#define WM8915_AIF2RX_CHAN0_START_SLOT_WIDTH 6 /* AIF2RX_CHAN0_START_SLOT - [5:0] */
+
+/*
+ * R812 (0x32C) - AIF2RX Channel 1 Configuration
+ */
+#define WM8915_AIF2RX_CHAN1_DAT_INV 0x8000 /* AIF2RX_CHAN1_DAT_INV */
+#define WM8915_AIF2RX_CHAN1_DAT_INV_MASK 0x8000 /* AIF2RX_CHAN1_DAT_INV */
+#define WM8915_AIF2RX_CHAN1_DAT_INV_SHIFT 15 /* AIF2RX_CHAN1_DAT_INV */
+#define WM8915_AIF2RX_CHAN1_DAT_INV_WIDTH 1 /* AIF2RX_CHAN1_DAT_INV */
+#define WM8915_AIF2RX_CHAN1_SPACING_MASK 0x7E00 /* AIF2RX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF2RX_CHAN1_SPACING_SHIFT 9 /* AIF2RX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF2RX_CHAN1_SPACING_WIDTH 6 /* AIF2RX_CHAN1_SPACING - [14:9] */
+#define WM8915_AIF2RX_CHAN1_SLOTS_MASK 0x01C0 /* AIF2RX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF2RX_CHAN1_SLOTS_SHIFT 6 /* AIF2RX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF2RX_CHAN1_SLOTS_WIDTH 3 /* AIF2RX_CHAN1_SLOTS - [8:6] */
+#define WM8915_AIF2RX_CHAN1_START_SLOT_MASK 0x003F /* AIF2RX_CHAN1_START_SLOT - [5:0] */
+#define WM8915_AIF2RX_CHAN1_START_SLOT_SHIFT 0 /* AIF2RX_CHAN1_START_SLOT - [5:0] */
+#define WM8915_AIF2RX_CHAN1_START_SLOT_WIDTH 6 /* AIF2RX_CHAN1_START_SLOT - [5:0] */
+
+/*
+ * R813 (0x32D) - AIF2RX Mono Configuration
+ */
+#define WM8915_AIF2RX_CHAN0_MONO_MODE 0x0001 /* AIF2RX_CHAN0_MONO_MODE */
+#define WM8915_AIF2RX_CHAN0_MONO_MODE_MASK 0x0001 /* AIF2RX_CHAN0_MONO_MODE */
+#define WM8915_AIF2RX_CHAN0_MONO_MODE_SHIFT 0 /* AIF2RX_CHAN0_MONO_MODE */
+#define WM8915_AIF2RX_CHAN0_MONO_MODE_WIDTH 1 /* AIF2RX_CHAN0_MONO_MODE */
+
+/*
+ * R815 (0x32F) - AIF2TX Test
+ */
+#define WM8915_AIF2TX_DITHER_ENA 0x0001 /* AIF2TX_DITHER_ENA */
+#define WM8915_AIF2TX_DITHER_ENA_MASK 0x0001 /* AIF2TX_DITHER_ENA */
+#define WM8915_AIF2TX_DITHER_ENA_SHIFT 0 /* AIF2TX_DITHER_ENA */
+#define WM8915_AIF2TX_DITHER_ENA_WIDTH 1 /* AIF2TX_DITHER_ENA */
+
+/*
+ * R1024 (0x400) - DSP1 TX Left Volume
+ */
+#define WM8915_DSP1TX_VU 0x0100 /* DSP1TX_VU */
+#define WM8915_DSP1TX_VU_MASK 0x0100 /* DSP1TX_VU */
+#define WM8915_DSP1TX_VU_SHIFT 8 /* DSP1TX_VU */
+#define WM8915_DSP1TX_VU_WIDTH 1 /* DSP1TX_VU */
+#define WM8915_DSP1TXL_VOL_MASK 0x00FF /* DSP1TXL_VOL - [7:0] */
+#define WM8915_DSP1TXL_VOL_SHIFT 0 /* DSP1TXL_VOL - [7:0] */
+#define WM8915_DSP1TXL_VOL_WIDTH 8 /* DSP1TXL_VOL - [7:0] */
+
+/*
+ * R1025 (0x401) - DSP1 TX Right Volume
+ */
+#define WM8915_DSP1TX_VU 0x0100 /* DSP1TX_VU */
+#define WM8915_DSP1TX_VU_MASK 0x0100 /* DSP1TX_VU */
+#define WM8915_DSP1TX_VU_SHIFT 8 /* DSP1TX_VU */
+#define WM8915_DSP1TX_VU_WIDTH 1 /* DSP1TX_VU */
+#define WM8915_DSP1TXR_VOL_MASK 0x00FF /* DSP1TXR_VOL - [7:0] */
+#define WM8915_DSP1TXR_VOL_SHIFT 0 /* DSP1TXR_VOL - [7:0] */
+#define WM8915_DSP1TXR_VOL_WIDTH 8 /* DSP1TXR_VOL - [7:0] */
+
+/*
+ * R1026 (0x402) - DSP1 RX Left Volume
+ */
+#define WM8915_DSP1RX_VU 0x0100 /* DSP1RX_VU */
+#define WM8915_DSP1RX_VU_MASK 0x0100 /* DSP1RX_VU */
+#define WM8915_DSP1RX_VU_SHIFT 8 /* DSP1RX_VU */
+#define WM8915_DSP1RX_VU_WIDTH 1 /* DSP1RX_VU */
+#define WM8915_DSP1RXL_VOL_MASK 0x00FF /* DSP1RXL_VOL - [7:0] */
+#define WM8915_DSP1RXL_VOL_SHIFT 0 /* DSP1RXL_VOL - [7:0] */
+#define WM8915_DSP1RXL_VOL_WIDTH 8 /* DSP1RXL_VOL - [7:0] */
+
+/*
+ * R1027 (0x403) - DSP1 RX Right Volume
+ */
+#define WM8915_DSP1RX_VU 0x0100 /* DSP1RX_VU */
+#define WM8915_DSP1RX_VU_MASK 0x0100 /* DSP1RX_VU */
+#define WM8915_DSP1RX_VU_SHIFT 8 /* DSP1RX_VU */
+#define WM8915_DSP1RX_VU_WIDTH 1 /* DSP1RX_VU */
+#define WM8915_DSP1RXR_VOL_MASK 0x00FF /* DSP1RXR_VOL - [7:0] */
+#define WM8915_DSP1RXR_VOL_SHIFT 0 /* DSP1RXR_VOL - [7:0] */
+#define WM8915_DSP1RXR_VOL_WIDTH 8 /* DSP1RXR_VOL - [7:0] */
+
+/*
+ * R1040 (0x410) - DSP1 TX Filters
+ */
+#define WM8915_DSP1TX_NF 0x2000 /* DSP1TX_NF */
+#define WM8915_DSP1TX_NF_MASK 0x2000 /* DSP1TX_NF */
+#define WM8915_DSP1TX_NF_SHIFT 13 /* DSP1TX_NF */
+#define WM8915_DSP1TX_NF_WIDTH 1 /* DSP1TX_NF */
+#define WM8915_DSP1TXL_HPF 0x1000 /* DSP1TXL_HPF */
+#define WM8915_DSP1TXL_HPF_MASK 0x1000 /* DSP1TXL_HPF */
+#define WM8915_DSP1TXL_HPF_SHIFT 12 /* DSP1TXL_HPF */
+#define WM8915_DSP1TXL_HPF_WIDTH 1 /* DSP1TXL_HPF */
+#define WM8915_DSP1TXR_HPF 0x0800 /* DSP1TXR_HPF */
+#define WM8915_DSP1TXR_HPF_MASK 0x0800 /* DSP1TXR_HPF */
+#define WM8915_DSP1TXR_HPF_SHIFT 11 /* DSP1TXR_HPF */
+#define WM8915_DSP1TXR_HPF_WIDTH 1 /* DSP1TXR_HPF */
+#define WM8915_DSP1TX_HPF_MODE_MASK 0x0018 /* DSP1TX_HPF_MODE - [4:3] */
+#define WM8915_DSP1TX_HPF_MODE_SHIFT 3 /* DSP1TX_HPF_MODE - [4:3] */
+#define WM8915_DSP1TX_HPF_MODE_WIDTH 2 /* DSP1TX_HPF_MODE - [4:3] */
+#define WM8915_DSP1TX_HPF_CUT_MASK 0x0007 /* DSP1TX_HPF_CUT - [2:0] */
+#define WM8915_DSP1TX_HPF_CUT_SHIFT 0 /* DSP1TX_HPF_CUT - [2:0] */
+#define WM8915_DSP1TX_HPF_CUT_WIDTH 3 /* DSP1TX_HPF_CUT - [2:0] */
+
+/*
+ * R1056 (0x420) - DSP1 RX Filters (1)
+ */
+#define WM8915_DSP1RX_MUTE 0x0200 /* DSP1RX_MUTE */
+#define WM8915_DSP1RX_MUTE_MASK 0x0200 /* DSP1RX_MUTE */
+#define WM8915_DSP1RX_MUTE_SHIFT 9 /* DSP1RX_MUTE */
+#define WM8915_DSP1RX_MUTE_WIDTH 1 /* DSP1RX_MUTE */
+#define WM8915_DSP1RX_MONO 0x0080 /* DSP1RX_MONO */
+#define WM8915_DSP1RX_MONO_MASK 0x0080 /* DSP1RX_MONO */
+#define WM8915_DSP1RX_MONO_SHIFT 7 /* DSP1RX_MONO */
+#define WM8915_DSP1RX_MONO_WIDTH 1 /* DSP1RX_MONO */
+#define WM8915_DSP1RX_MUTERATE 0x0020 /* DSP1RX_MUTERATE */
+#define WM8915_DSP1RX_MUTERATE_MASK 0x0020 /* DSP1RX_MUTERATE */
+#define WM8915_DSP1RX_MUTERATE_SHIFT 5 /* DSP1RX_MUTERATE */
+#define WM8915_DSP1RX_MUTERATE_WIDTH 1 /* DSP1RX_MUTERATE */
+#define WM8915_DSP1RX_UNMUTE_RAMP 0x0010 /* DSP1RX_UNMUTE_RAMP */
+#define WM8915_DSP1RX_UNMUTE_RAMP_MASK 0x0010 /* DSP1RX_UNMUTE_RAMP */
+#define WM8915_DSP1RX_UNMUTE_RAMP_SHIFT 4 /* DSP1RX_UNMUTE_RAMP */
+#define WM8915_DSP1RX_UNMUTE_RAMP_WIDTH 1 /* DSP1RX_UNMUTE_RAMP */
+
+/*
+ * R1057 (0x421) - DSP1 RX Filters (2)
+ */
+#define WM8915_DSP1RX_3D_GAIN_MASK 0x3E00 /* DSP1RX_3D_GAIN - [13:9] */
+#define WM8915_DSP1RX_3D_GAIN_SHIFT 9 /* DSP1RX_3D_GAIN - [13:9] */
+#define WM8915_DSP1RX_3D_GAIN_WIDTH 5 /* DSP1RX_3D_GAIN - [13:9] */
+#define WM8915_DSP1RX_3D_ENA 0x0100 /* DSP1RX_3D_ENA */
+#define WM8915_DSP1RX_3D_ENA_MASK 0x0100 /* DSP1RX_3D_ENA */
+#define WM8915_DSP1RX_3D_ENA_SHIFT 8 /* DSP1RX_3D_ENA */
+#define WM8915_DSP1RX_3D_ENA_WIDTH 1 /* DSP1RX_3D_ENA */
+
+/*
+ * R1088 (0x440) - DSP1 DRC (1)
+ */
+#define WM8915_DSP1DRC_SIG_DET_RMS_MASK 0xF800 /* DSP1DRC_SIG_DET_RMS - [15:11] */
+#define WM8915_DSP1DRC_SIG_DET_RMS_SHIFT 11 /* DSP1DRC_SIG_DET_RMS - [15:11] */
+#define WM8915_DSP1DRC_SIG_DET_RMS_WIDTH 5 /* DSP1DRC_SIG_DET_RMS - [15:11] */
+#define WM8915_DSP1DRC_SIG_DET_PK_MASK 0x0600 /* DSP1DRC_SIG_DET_PK - [10:9] */
+#define WM8915_DSP1DRC_SIG_DET_PK_SHIFT 9 /* DSP1DRC_SIG_DET_PK - [10:9] */
+#define WM8915_DSP1DRC_SIG_DET_PK_WIDTH 2 /* DSP1DRC_SIG_DET_PK - [10:9] */
+#define WM8915_DSP1DRC_NG_ENA 0x0100 /* DSP1DRC_NG_ENA */
+#define WM8915_DSP1DRC_NG_ENA_MASK 0x0100 /* DSP1DRC_NG_ENA */
+#define WM8915_DSP1DRC_NG_ENA_SHIFT 8 /* DSP1DRC_NG_ENA */
+#define WM8915_DSP1DRC_NG_ENA_WIDTH 1 /* DSP1DRC_NG_ENA */
+#define WM8915_DSP1DRC_SIG_DET_MODE 0x0080 /* DSP1DRC_SIG_DET_MODE */
+#define WM8915_DSP1DRC_SIG_DET_MODE_MASK 0x0080 /* DSP1DRC_SIG_DET_MODE */
+#define WM8915_DSP1DRC_SIG_DET_MODE_SHIFT 7 /* DSP1DRC_SIG_DET_MODE */
+#define WM8915_DSP1DRC_SIG_DET_MODE_WIDTH 1 /* DSP1DRC_SIG_DET_MODE */
+#define WM8915_DSP1DRC_SIG_DET 0x0040 /* DSP1DRC_SIG_DET */
+#define WM8915_DSP1DRC_SIG_DET_MASK 0x0040 /* DSP1DRC_SIG_DET */
+#define WM8915_DSP1DRC_SIG_DET_SHIFT 6 /* DSP1DRC_SIG_DET */
+#define WM8915_DSP1DRC_SIG_DET_WIDTH 1 /* DSP1DRC_SIG_DET */
+#define WM8915_DSP1DRC_KNEE2_OP_ENA 0x0020 /* DSP1DRC_KNEE2_OP_ENA */
+#define WM8915_DSP1DRC_KNEE2_OP_ENA_MASK 0x0020 /* DSP1DRC_KNEE2_OP_ENA */
+#define WM8915_DSP1DRC_KNEE2_OP_ENA_SHIFT 5 /* DSP1DRC_KNEE2_OP_ENA */
+#define WM8915_DSP1DRC_KNEE2_OP_ENA_WIDTH 1 /* DSP1DRC_KNEE2_OP_ENA */
+#define WM8915_DSP1DRC_QR 0x0010 /* DSP1DRC_QR */
+#define WM8915_DSP1DRC_QR_MASK 0x0010 /* DSP1DRC_QR */
+#define WM8915_DSP1DRC_QR_SHIFT 4 /* DSP1DRC_QR */
+#define WM8915_DSP1DRC_QR_WIDTH 1 /* DSP1DRC_QR */
+#define WM8915_DSP1DRC_ANTICLIP 0x0008 /* DSP1DRC_ANTICLIP */
+#define WM8915_DSP1DRC_ANTICLIP_MASK 0x0008 /* DSP1DRC_ANTICLIP */
+#define WM8915_DSP1DRC_ANTICLIP_SHIFT 3 /* DSP1DRC_ANTICLIP */
+#define WM8915_DSP1DRC_ANTICLIP_WIDTH 1 /* DSP1DRC_ANTICLIP */
+#define WM8915_DSP1RX_DRC_ENA 0x0004 /* DSP1RX_DRC_ENA */
+#define WM8915_DSP1RX_DRC_ENA_MASK 0x0004 /* DSP1RX_DRC_ENA */
+#define WM8915_DSP1RX_DRC_ENA_SHIFT 2 /* DSP1RX_DRC_ENA */
+#define WM8915_DSP1RX_DRC_ENA_WIDTH 1 /* DSP1RX_DRC_ENA */
+#define WM8915_DSP1TXL_DRC_ENA 0x0002 /* DSP1TXL_DRC_ENA */
+#define WM8915_DSP1TXL_DRC_ENA_MASK 0x0002 /* DSP1TXL_DRC_ENA */
+#define WM8915_DSP1TXL_DRC_ENA_SHIFT 1 /* DSP1TXL_DRC_ENA */
+#define WM8915_DSP1TXL_DRC_ENA_WIDTH 1 /* DSP1TXL_DRC_ENA */
+#define WM8915_DSP1TXR_DRC_ENA 0x0001 /* DSP1TXR_DRC_ENA */
+#define WM8915_DSP1TXR_DRC_ENA_MASK 0x0001 /* DSP1TXR_DRC_ENA */
+#define WM8915_DSP1TXR_DRC_ENA_SHIFT 0 /* DSP1TXR_DRC_ENA */
+#define WM8915_DSP1TXR_DRC_ENA_WIDTH 1 /* DSP1TXR_DRC_ENA */
+
+/*
+ * R1089 (0x441) - DSP1 DRC (2)
+ */
+#define WM8915_DSP1DRC_ATK_MASK 0x1E00 /* DSP1DRC_ATK - [12:9] */
+#define WM8915_DSP1DRC_ATK_SHIFT 9 /* DSP1DRC_ATK - [12:9] */
+#define WM8915_DSP1DRC_ATK_WIDTH 4 /* DSP1DRC_ATK - [12:9] */
+#define WM8915_DSP1DRC_DCY_MASK 0x01E0 /* DSP1DRC_DCY - [8:5] */
+#define WM8915_DSP1DRC_DCY_SHIFT 5 /* DSP1DRC_DCY - [8:5] */
+#define WM8915_DSP1DRC_DCY_WIDTH 4 /* DSP1DRC_DCY - [8:5] */
+#define WM8915_DSP1DRC_MINGAIN_MASK 0x001C /* DSP1DRC_MINGAIN - [4:2] */
+#define WM8915_DSP1DRC_MINGAIN_SHIFT 2 /* DSP1DRC_MINGAIN - [4:2] */
+#define WM8915_DSP1DRC_MINGAIN_WIDTH 3 /* DSP1DRC_MINGAIN - [4:2] */
+#define WM8915_DSP1DRC_MAXGAIN_MASK 0x0003 /* DSP1DRC_MAXGAIN - [1:0] */
+#define WM8915_DSP1DRC_MAXGAIN_SHIFT 0 /* DSP1DRC_MAXGAIN - [1:0] */
+#define WM8915_DSP1DRC_MAXGAIN_WIDTH 2 /* DSP1DRC_MAXGAIN - [1:0] */
+
+/*
+ * R1090 (0x442) - DSP1 DRC (3)
+ */
+#define WM8915_DSP1DRC_NG_MINGAIN_MASK 0xF000 /* DSP1DRC_NG_MINGAIN - [15:12] */
+#define WM8915_DSP1DRC_NG_MINGAIN_SHIFT 12 /* DSP1DRC_NG_MINGAIN - [15:12] */
+#define WM8915_DSP1DRC_NG_MINGAIN_WIDTH 4 /* DSP1DRC_NG_MINGAIN - [15:12] */
+#define WM8915_DSP1DRC_NG_EXP_MASK 0x0C00 /* DSP1DRC_NG_EXP - [11:10] */
+#define WM8915_DSP1DRC_NG_EXP_SHIFT 10 /* DSP1DRC_NG_EXP - [11:10] */
+#define WM8915_DSP1DRC_NG_EXP_WIDTH 2 /* DSP1DRC_NG_EXP - [11:10] */
+#define WM8915_DSP1DRC_QR_THR_MASK 0x0300 /* DSP1DRC_QR_THR - [9:8] */
+#define WM8915_DSP1DRC_QR_THR_SHIFT 8 /* DSP1DRC_QR_THR - [9:8] */
+#define WM8915_DSP1DRC_QR_THR_WIDTH 2 /* DSP1DRC_QR_THR - [9:8] */
+#define WM8915_DSP1DRC_QR_DCY_MASK 0x00C0 /* DSP1DRC_QR_DCY - [7:6] */
+#define WM8915_DSP1DRC_QR_DCY_SHIFT 6 /* DSP1DRC_QR_DCY - [7:6] */
+#define WM8915_DSP1DRC_QR_DCY_WIDTH 2 /* DSP1DRC_QR_DCY - [7:6] */
+#define WM8915_DSP1DRC_HI_COMP_MASK 0x0038 /* DSP1DRC_HI_COMP - [5:3] */
+#define WM8915_DSP1DRC_HI_COMP_SHIFT 3 /* DSP1DRC_HI_COMP - [5:3] */
+#define WM8915_DSP1DRC_HI_COMP_WIDTH 3 /* DSP1DRC_HI_COMP - [5:3] */
+#define WM8915_DSP1DRC_LO_COMP_MASK 0x0007 /* DSP1DRC_LO_COMP - [2:0] */
+#define WM8915_DSP1DRC_LO_COMP_SHIFT 0 /* DSP1DRC_LO_COMP - [2:0] */
+#define WM8915_DSP1DRC_LO_COMP_WIDTH 3 /* DSP1DRC_LO_COMP - [2:0] */
+
+/*
+ * R1091 (0x443) - DSP1 DRC (4)
+ */
+#define WM8915_DSP1DRC_KNEE_IP_MASK 0x07E0 /* DSP1DRC_KNEE_IP - [10:5] */
+#define WM8915_DSP1DRC_KNEE_IP_SHIFT 5 /* DSP1DRC_KNEE_IP - [10:5] */
+#define WM8915_DSP1DRC_KNEE_IP_WIDTH 6 /* DSP1DRC_KNEE_IP - [10:5] */
+#define WM8915_DSP1DRC_KNEE_OP_MASK 0x001F /* DSP1DRC_KNEE_OP - [4:0] */
+#define WM8915_DSP1DRC_KNEE_OP_SHIFT 0 /* DSP1DRC_KNEE_OP - [4:0] */
+#define WM8915_DSP1DRC_KNEE_OP_WIDTH 5 /* DSP1DRC_KNEE_OP - [4:0] */
+
+/*
+ * R1092 (0x444) - DSP1 DRC (5)
+ */
+#define WM8915_DSP1DRC_KNEE2_IP_MASK 0x03E0 /* DSP1DRC_KNEE2_IP - [9:5] */
+#define WM8915_DSP1DRC_KNEE2_IP_SHIFT 5 /* DSP1DRC_KNEE2_IP - [9:5] */
+#define WM8915_DSP1DRC_KNEE2_IP_WIDTH 5 /* DSP1DRC_KNEE2_IP - [9:5] */
+#define WM8915_DSP1DRC_KNEE2_OP_MASK 0x001F /* DSP1DRC_KNEE2_OP - [4:0] */
+#define WM8915_DSP1DRC_KNEE2_OP_SHIFT 0 /* DSP1DRC_KNEE2_OP - [4:0] */
+#define WM8915_DSP1DRC_KNEE2_OP_WIDTH 5 /* DSP1DRC_KNEE2_OP - [4:0] */
+
+/*
+ * R1152 (0x480) - DSP1 RX EQ Gains (1)
+ */
+#define WM8915_DSP1RX_EQ_B1_GAIN_MASK 0xF800 /* DSP1RX_EQ_B1_GAIN - [15:11] */
+#define WM8915_DSP1RX_EQ_B1_GAIN_SHIFT 11 /* DSP1RX_EQ_B1_GAIN - [15:11] */
+#define WM8915_DSP1RX_EQ_B1_GAIN_WIDTH 5 /* DSP1RX_EQ_B1_GAIN - [15:11] */
+#define WM8915_DSP1RX_EQ_B2_GAIN_MASK 0x07C0 /* DSP1RX_EQ_B2_GAIN - [10:6] */
+#define WM8915_DSP1RX_EQ_B2_GAIN_SHIFT 6 /* DSP1RX_EQ_B2_GAIN - [10:6] */
+#define WM8915_DSP1RX_EQ_B2_GAIN_WIDTH 5 /* DSP1RX_EQ_B2_GAIN - [10:6] */
+#define WM8915_DSP1RX_EQ_B3_GAIN_MASK 0x003E /* DSP1RX_EQ_B3_GAIN - [5:1] */
+#define WM8915_DSP1RX_EQ_B3_GAIN_SHIFT 1 /* DSP1RX_EQ_B3_GAIN - [5:1] */
+#define WM8915_DSP1RX_EQ_B3_GAIN_WIDTH 5 /* DSP1RX_EQ_B3_GAIN - [5:1] */
+#define WM8915_DSP1RX_EQ_ENA 0x0001 /* DSP1RX_EQ_ENA */
+#define WM8915_DSP1RX_EQ_ENA_MASK 0x0001 /* DSP1RX_EQ_ENA */
+#define WM8915_DSP1RX_EQ_ENA_SHIFT 0 /* DSP1RX_EQ_ENA */
+#define WM8915_DSP1RX_EQ_ENA_WIDTH 1 /* DSP1RX_EQ_ENA */
+
+/*
+ * R1153 (0x481) - DSP1 RX EQ Gains (2)
+ */
+#define WM8915_DSP1RX_EQ_B4_GAIN_MASK 0xF800 /* DSP1RX_EQ_B4_GAIN - [15:11] */
+#define WM8915_DSP1RX_EQ_B4_GAIN_SHIFT 11 /* DSP1RX_EQ_B4_GAIN - [15:11] */
+#define WM8915_DSP1RX_EQ_B4_GAIN_WIDTH 5 /* DSP1RX_EQ_B4_GAIN - [15:11] */
+#define WM8915_DSP1RX_EQ_B5_GAIN_MASK 0x07C0 /* DSP1RX_EQ_B5_GAIN - [10:6] */
+#define WM8915_DSP1RX_EQ_B5_GAIN_SHIFT 6 /* DSP1RX_EQ_B5_GAIN - [10:6] */
+#define WM8915_DSP1RX_EQ_B5_GAIN_WIDTH 5 /* DSP1RX_EQ_B5_GAIN - [10:6] */
+
+/*
+ * R1154 (0x482) - DSP1 RX EQ Band 1 A
+ */
+#define WM8915_DSP1RX_EQ_B1_A_MASK 0xFFFF /* DSP1RX_EQ_B1_A - [15:0] */
+#define WM8915_DSP1RX_EQ_B1_A_SHIFT 0 /* DSP1RX_EQ_B1_A - [15:0] */
+#define WM8915_DSP1RX_EQ_B1_A_WIDTH 16 /* DSP1RX_EQ_B1_A - [15:0] */
+
+/*
+ * R1155 (0x483) - DSP1 RX EQ Band 1 B
+ */
+#define WM8915_DSP1RX_EQ_B1_B_MASK 0xFFFF /* DSP1RX_EQ_B1_B - [15:0] */
+#define WM8915_DSP1RX_EQ_B1_B_SHIFT 0 /* DSP1RX_EQ_B1_B - [15:0] */
+#define WM8915_DSP1RX_EQ_B1_B_WIDTH 16 /* DSP1RX_EQ_B1_B - [15:0] */
+
+/*
+ * R1156 (0x484) - DSP1 RX EQ Band 1 PG
+ */
+#define WM8915_DSP1RX_EQ_B1_PG_MASK 0xFFFF /* DSP1RX_EQ_B1_PG - [15:0] */
+#define WM8915_DSP1RX_EQ_B1_PG_SHIFT 0 /* DSP1RX_EQ_B1_PG - [15:0] */
+#define WM8915_DSP1RX_EQ_B1_PG_WIDTH 16 /* DSP1RX_EQ_B1_PG - [15:0] */
+
+/*
+ * R1157 (0x485) - DSP1 RX EQ Band 2 A
+ */
+#define WM8915_DSP1RX_EQ_B2_A_MASK 0xFFFF /* DSP1RX_EQ_B2_A - [15:0] */
+#define WM8915_DSP1RX_EQ_B2_A_SHIFT 0 /* DSP1RX_EQ_B2_A - [15:0] */
+#define WM8915_DSP1RX_EQ_B2_A_WIDTH 16 /* DSP1RX_EQ_B2_A - [15:0] */
+
+/*
+ * R1158 (0x486) - DSP1 RX EQ Band 2 B
+ */
+#define WM8915_DSP1RX_EQ_B2_B_MASK 0xFFFF /* DSP1RX_EQ_B2_B - [15:0] */
+#define WM8915_DSP1RX_EQ_B2_B_SHIFT 0 /* DSP1RX_EQ_B2_B - [15:0] */
+#define WM8915_DSP1RX_EQ_B2_B_WIDTH 16 /* DSP1RX_EQ_B2_B - [15:0] */
+
+/*
+ * R1159 (0x487) - DSP1 RX EQ Band 2 C
+ */
+#define WM8915_DSP1RX_EQ_B2_C_MASK 0xFFFF /* DSP1RX_EQ_B2_C - [15:0] */
+#define WM8915_DSP1RX_EQ_B2_C_SHIFT 0 /* DSP1RX_EQ_B2_C - [15:0] */
+#define WM8915_DSP1RX_EQ_B2_C_WIDTH 16 /* DSP1RX_EQ_B2_C - [15:0] */
+
+/*
+ * R1160 (0x488) - DSP1 RX EQ Band 2 PG
+ */
+#define WM8915_DSP1RX_EQ_B2_PG_MASK 0xFFFF /* DSP1RX_EQ_B2_PG - [15:0] */
+#define WM8915_DSP1RX_EQ_B2_PG_SHIFT 0 /* DSP1RX_EQ_B2_PG - [15:0] */
+#define WM8915_DSP1RX_EQ_B2_PG_WIDTH 16 /* DSP1RX_EQ_B2_PG - [15:0] */
+
+/*
+ * R1161 (0x489) - DSP1 RX EQ Band 3 A
+ */
+#define WM8915_DSP1RX_EQ_B3_A_MASK 0xFFFF /* DSP1RX_EQ_B3_A - [15:0] */
+#define WM8915_DSP1RX_EQ_B3_A_SHIFT 0 /* DSP1RX_EQ_B3_A - [15:0] */
+#define WM8915_DSP1RX_EQ_B3_A_WIDTH 16 /* DSP1RX_EQ_B3_A - [15:0] */
+
+/*
+ * R1162 (0x48A) - DSP1 RX EQ Band 3 B
+ */
+#define WM8915_DSP1RX_EQ_B3_B_MASK 0xFFFF /* DSP1RX_EQ_B3_B - [15:0] */
+#define WM8915_DSP1RX_EQ_B3_B_SHIFT 0 /* DSP1RX_EQ_B3_B - [15:0] */
+#define WM8915_DSP1RX_EQ_B3_B_WIDTH 16 /* DSP1RX_EQ_B3_B - [15:0] */
+
+/*
+ * R1163 (0x48B) - DSP1 RX EQ Band 3 C
+ */
+#define WM8915_DSP1RX_EQ_B3_C_MASK 0xFFFF /* DSP1RX_EQ_B3_C - [15:0] */
+#define WM8915_DSP1RX_EQ_B3_C_SHIFT 0 /* DSP1RX_EQ_B3_C - [15:0] */
+#define WM8915_DSP1RX_EQ_B3_C_WIDTH 16 /* DSP1RX_EQ_B3_C - [15:0] */
+
+/*
+ * R1164 (0x48C) - DSP1 RX EQ Band 3 PG
+ */
+#define WM8915_DSP1RX_EQ_B3_PG_MASK 0xFFFF /* DSP1RX_EQ_B3_PG - [15:0] */
+#define WM8915_DSP1RX_EQ_B3_PG_SHIFT 0 /* DSP1RX_EQ_B3_PG - [15:0] */
+#define WM8915_DSP1RX_EQ_B3_PG_WIDTH 16 /* DSP1RX_EQ_B3_PG - [15:0] */
+
+/*
+ * R1165 (0x48D) - DSP1 RX EQ Band 4 A
+ */
+#define WM8915_DSP1RX_EQ_B4_A_MASK 0xFFFF /* DSP1RX_EQ_B4_A - [15:0] */
+#define WM8915_DSP1RX_EQ_B4_A_SHIFT 0 /* DSP1RX_EQ_B4_A - [15:0] */
+#define WM8915_DSP1RX_EQ_B4_A_WIDTH 16 /* DSP1RX_EQ_B4_A - [15:0] */
+
+/*
+ * R1166 (0x48E) - DSP1 RX EQ Band 4 B
+ */
+#define WM8915_DSP1RX_EQ_B4_B_MASK 0xFFFF /* DSP1RX_EQ_B4_B - [15:0] */
+#define WM8915_DSP1RX_EQ_B4_B_SHIFT 0 /* DSP1RX_EQ_B4_B - [15:0] */
+#define WM8915_DSP1RX_EQ_B4_B_WIDTH 16 /* DSP1RX_EQ_B4_B - [15:0] */
+
+/*
+ * R1167 (0x48F) - DSP1 RX EQ Band 4 C
+ */
+#define WM8915_DSP1RX_EQ_B4_C_MASK 0xFFFF /* DSP1RX_EQ_B4_C - [15:0] */
+#define WM8915_DSP1RX_EQ_B4_C_SHIFT 0 /* DSP1RX_EQ_B4_C - [15:0] */
+#define WM8915_DSP1RX_EQ_B4_C_WIDTH 16 /* DSP1RX_EQ_B4_C - [15:0] */
+
+/*
+ * R1168 (0x490) - DSP1 RX EQ Band 4 PG
+ */
+#define WM8915_DSP1RX_EQ_B4_PG_MASK 0xFFFF /* DSP1RX_EQ_B4_PG - [15:0] */
+#define WM8915_DSP1RX_EQ_B4_PG_SHIFT 0 /* DSP1RX_EQ_B4_PG - [15:0] */
+#define WM8915_DSP1RX_EQ_B4_PG_WIDTH 16 /* DSP1RX_EQ_B4_PG - [15:0] */
+
+/*
+ * R1169 (0x491) - DSP1 RX EQ Band 5 A
+ */
+#define WM8915_DSP1RX_EQ_B5_A_MASK 0xFFFF /* DSP1RX_EQ_B5_A - [15:0] */
+#define WM8915_DSP1RX_EQ_B5_A_SHIFT 0 /* DSP1RX_EQ_B5_A - [15:0] */
+#define WM8915_DSP1RX_EQ_B5_A_WIDTH 16 /* DSP1RX_EQ_B5_A - [15:0] */
+
+/*
+ * R1170 (0x492) - DSP1 RX EQ Band 5 B
+ */
+#define WM8915_DSP1RX_EQ_B5_B_MASK 0xFFFF /* DSP1RX_EQ_B5_B - [15:0] */
+#define WM8915_DSP1RX_EQ_B5_B_SHIFT 0 /* DSP1RX_EQ_B5_B - [15:0] */
+#define WM8915_DSP1RX_EQ_B5_B_WIDTH 16 /* DSP1RX_EQ_B5_B - [15:0] */
+
+/*
+ * R1171 (0x493) - DSP1 RX EQ Band 5 PG
+ */
+#define WM8915_DSP1RX_EQ_B5_PG_MASK 0xFFFF /* DSP1RX_EQ_B5_PG - [15:0] */
+#define WM8915_DSP1RX_EQ_B5_PG_SHIFT 0 /* DSP1RX_EQ_B5_PG - [15:0] */
+#define WM8915_DSP1RX_EQ_B5_PG_WIDTH 16 /* DSP1RX_EQ_B5_PG - [15:0] */
+
+/*
+ * R1280 (0x500) - DSP2 TX Left Volume
+ */
+#define WM8915_DSP2TX_VU 0x0100 /* DSP2TX_VU */
+#define WM8915_DSP2TX_VU_MASK 0x0100 /* DSP2TX_VU */
+#define WM8915_DSP2TX_VU_SHIFT 8 /* DSP2TX_VU */
+#define WM8915_DSP2TX_VU_WIDTH 1 /* DSP2TX_VU */
+#define WM8915_DSP2TXL_VOL_MASK 0x00FF /* DSP2TXL_VOL - [7:0] */
+#define WM8915_DSP2TXL_VOL_SHIFT 0 /* DSP2TXL_VOL - [7:0] */
+#define WM8915_DSP2TXL_VOL_WIDTH 8 /* DSP2TXL_VOL - [7:0] */
+
+/*
+ * R1281 (0x501) - DSP2 TX Right Volume
+ */
+#define WM8915_DSP2TX_VU 0x0100 /* DSP2TX_VU */
+#define WM8915_DSP2TX_VU_MASK 0x0100 /* DSP2TX_VU */
+#define WM8915_DSP2TX_VU_SHIFT 8 /* DSP2TX_VU */
+#define WM8915_DSP2TX_VU_WIDTH 1 /* DSP2TX_VU */
+#define WM8915_DSP2TXR_VOL_MASK 0x00FF /* DSP2TXR_VOL - [7:0] */
+#define WM8915_DSP2TXR_VOL_SHIFT 0 /* DSP2TXR_VOL - [7:0] */
+#define WM8915_DSP2TXR_VOL_WIDTH 8 /* DSP2TXR_VOL - [7:0] */
+
+/*
+ * R1282 (0x502) - DSP2 RX Left Volume
+ */
+#define WM8915_DSP2RX_VU 0x0100 /* DSP2RX_VU */
+#define WM8915_DSP2RX_VU_MASK 0x0100 /* DSP2RX_VU */
+#define WM8915_DSP2RX_VU_SHIFT 8 /* DSP2RX_VU */
+#define WM8915_DSP2RX_VU_WIDTH 1 /* DSP2RX_VU */
+#define WM8915_DSP2RXL_VOL_MASK 0x00FF /* DSP2RXL_VOL - [7:0] */
+#define WM8915_DSP2RXL_VOL_SHIFT 0 /* DSP2RXL_VOL - [7:0] */
+#define WM8915_DSP2RXL_VOL_WIDTH 8 /* DSP2RXL_VOL - [7:0] */
+
+/*
+ * R1283 (0x503) - DSP2 RX Right Volume
+ */
+#define WM8915_DSP2RX_VU 0x0100 /* DSP2RX_VU */
+#define WM8915_DSP2RX_VU_MASK 0x0100 /* DSP2RX_VU */
+#define WM8915_DSP2RX_VU_SHIFT 8 /* DSP2RX_VU */
+#define WM8915_DSP2RX_VU_WIDTH 1 /* DSP2RX_VU */
+#define WM8915_DSP2RXR_VOL_MASK 0x00FF /* DSP2RXR_VOL - [7:0] */
+#define WM8915_DSP2RXR_VOL_SHIFT 0 /* DSP2RXR_VOL - [7:0] */
+#define WM8915_DSP2RXR_VOL_WIDTH 8 /* DSP2RXR_VOL - [7:0] */
+
+/*
+ * R1296 (0x510) - DSP2 TX Filters
+ */
+#define WM8915_DSP2TX_NF 0x2000 /* DSP2TX_NF */
+#define WM8915_DSP2TX_NF_MASK 0x2000 /* DSP2TX_NF */
+#define WM8915_DSP2TX_NF_SHIFT 13 /* DSP2TX_NF */
+#define WM8915_DSP2TX_NF_WIDTH 1 /* DSP2TX_NF */
+#define WM8915_DSP2TXL_HPF 0x1000 /* DSP2TXL_HPF */
+#define WM8915_DSP2TXL_HPF_MASK 0x1000 /* DSP2TXL_HPF */
+#define WM8915_DSP2TXL_HPF_SHIFT 12 /* DSP2TXL_HPF */
+#define WM8915_DSP2TXL_HPF_WIDTH 1 /* DSP2TXL_HPF */
+#define WM8915_DSP2TXR_HPF 0x0800 /* DSP2TXR_HPF */
+#define WM8915_DSP2TXR_HPF_MASK 0x0800 /* DSP2TXR_HPF */
+#define WM8915_DSP2TXR_HPF_SHIFT 11 /* DSP2TXR_HPF */
+#define WM8915_DSP2TXR_HPF_WIDTH 1 /* DSP2TXR_HPF */
+#define WM8915_DSP2TX_HPF_MODE_MASK 0x0018 /* DSP2TX_HPF_MODE - [4:3] */
+#define WM8915_DSP2TX_HPF_MODE_SHIFT 3 /* DSP2TX_HPF_MODE - [4:3] */
+#define WM8915_DSP2TX_HPF_MODE_WIDTH 2 /* DSP2TX_HPF_MODE - [4:3] */
+#define WM8915_DSP2TX_HPF_CUT_MASK 0x0007 /* DSP2TX_HPF_CUT - [2:0] */
+#define WM8915_DSP2TX_HPF_CUT_SHIFT 0 /* DSP2TX_HPF_CUT - [2:0] */
+#define WM8915_DSP2TX_HPF_CUT_WIDTH 3 /* DSP2TX_HPF_CUT - [2:0] */
+
+/*
+ * R1312 (0x520) - DSP2 RX Filters (1)
+ */
+#define WM8915_DSP2RX_MUTE 0x0200 /* DSP2RX_MUTE */
+#define WM8915_DSP2RX_MUTE_MASK 0x0200 /* DSP2RX_MUTE */
+#define WM8915_DSP2RX_MUTE_SHIFT 9 /* DSP2RX_MUTE */
+#define WM8915_DSP2RX_MUTE_WIDTH 1 /* DSP2RX_MUTE */
+#define WM8915_DSP2RX_MONO 0x0080 /* DSP2RX_MONO */
+#define WM8915_DSP2RX_MONO_MASK 0x0080 /* DSP2RX_MONO */
+#define WM8915_DSP2RX_MONO_SHIFT 7 /* DSP2RX_MONO */
+#define WM8915_DSP2RX_MONO_WIDTH 1 /* DSP2RX_MONO */
+#define WM8915_DSP2RX_MUTERATE 0x0020 /* DSP2RX_MUTERATE */
+#define WM8915_DSP2RX_MUTERATE_MASK 0x0020 /* DSP2RX_MUTERATE */
+#define WM8915_DSP2RX_MUTERATE_SHIFT 5 /* DSP2RX_MUTERATE */
+#define WM8915_DSP2RX_MUTERATE_WIDTH 1 /* DSP2RX_MUTERATE */
+#define WM8915_DSP2RX_UNMUTE_RAMP 0x0010 /* DSP2RX_UNMUTE_RAMP */
+#define WM8915_DSP2RX_UNMUTE_RAMP_MASK 0x0010 /* DSP2RX_UNMUTE_RAMP */
+#define WM8915_DSP2RX_UNMUTE_RAMP_SHIFT 4 /* DSP2RX_UNMUTE_RAMP */
+#define WM8915_DSP2RX_UNMUTE_RAMP_WIDTH 1 /* DSP2RX_UNMUTE_RAMP */
+
+/*
+ * R1313 (0x521) - DSP2 RX Filters (2)
+ */
+#define WM8915_DSP2RX_3D_GAIN_MASK 0x3E00 /* DSP2RX_3D_GAIN - [13:9] */
+#define WM8915_DSP2RX_3D_GAIN_SHIFT 9 /* DSP2RX_3D_GAIN - [13:9] */
+#define WM8915_DSP2RX_3D_GAIN_WIDTH 5 /* DSP2RX_3D_GAIN - [13:9] */
+#define WM8915_DSP2RX_3D_ENA 0x0100 /* DSP2RX_3D_ENA */
+#define WM8915_DSP2RX_3D_ENA_MASK 0x0100 /* DSP2RX_3D_ENA */
+#define WM8915_DSP2RX_3D_ENA_SHIFT 8 /* DSP2RX_3D_ENA */
+#define WM8915_DSP2RX_3D_ENA_WIDTH 1 /* DSP2RX_3D_ENA */
+
+/*
+ * R1344 (0x540) - DSP2 DRC (1)
+ */
+#define WM8915_DSP2DRC_SIG_DET_RMS_MASK 0xF800 /* DSP2DRC_SIG_DET_RMS - [15:11] */
+#define WM8915_DSP2DRC_SIG_DET_RMS_SHIFT 11 /* DSP2DRC_SIG_DET_RMS - [15:11] */
+#define WM8915_DSP2DRC_SIG_DET_RMS_WIDTH 5 /* DSP2DRC_SIG_DET_RMS - [15:11] */
+#define WM8915_DSP2DRC_SIG_DET_PK_MASK 0x0600 /* DSP2DRC_SIG_DET_PK - [10:9] */
+#define WM8915_DSP2DRC_SIG_DET_PK_SHIFT 9 /* DSP2DRC_SIG_DET_PK - [10:9] */
+#define WM8915_DSP2DRC_SIG_DET_PK_WIDTH 2 /* DSP2DRC_SIG_DET_PK - [10:9] */
+#define WM8915_DSP2DRC_NG_ENA 0x0100 /* DSP2DRC_NG_ENA */
+#define WM8915_DSP2DRC_NG_ENA_MASK 0x0100 /* DSP2DRC_NG_ENA */
+#define WM8915_DSP2DRC_NG_ENA_SHIFT 8 /* DSP2DRC_NG_ENA */
+#define WM8915_DSP2DRC_NG_ENA_WIDTH 1 /* DSP2DRC_NG_ENA */
+#define WM8915_DSP2DRC_SIG_DET_MODE 0x0080 /* DSP2DRC_SIG_DET_MODE */
+#define WM8915_DSP2DRC_SIG_DET_MODE_MASK 0x0080 /* DSP2DRC_SIG_DET_MODE */
+#define WM8915_DSP2DRC_SIG_DET_MODE_SHIFT 7 /* DSP2DRC_SIG_DET_MODE */
+#define WM8915_DSP2DRC_SIG_DET_MODE_WIDTH 1 /* DSP2DRC_SIG_DET_MODE */
+#define WM8915_DSP2DRC_SIG_DET 0x0040 /* DSP2DRC_SIG_DET */
+#define WM8915_DSP2DRC_SIG_DET_MASK 0x0040 /* DSP2DRC_SIG_DET */
+#define WM8915_DSP2DRC_SIG_DET_SHIFT 6 /* DSP2DRC_SIG_DET */
+#define WM8915_DSP2DRC_SIG_DET_WIDTH 1 /* DSP2DRC_SIG_DET */
+#define WM8915_DSP2DRC_KNEE2_OP_ENA 0x0020 /* DSP2DRC_KNEE2_OP_ENA */
+#define WM8915_DSP2DRC_KNEE2_OP_ENA_MASK 0x0020 /* DSP2DRC_KNEE2_OP_ENA */
+#define WM8915_DSP2DRC_KNEE2_OP_ENA_SHIFT 5 /* DSP2DRC_KNEE2_OP_ENA */
+#define WM8915_DSP2DRC_KNEE2_OP_ENA_WIDTH 1 /* DSP2DRC_KNEE2_OP_ENA */
+#define WM8915_DSP2DRC_QR 0x0010 /* DSP2DRC_QR */
+#define WM8915_DSP2DRC_QR_MASK 0x0010 /* DSP2DRC_QR */
+#define WM8915_DSP2DRC_QR_SHIFT 4 /* DSP2DRC_QR */
+#define WM8915_DSP2DRC_QR_WIDTH 1 /* DSP2DRC_QR */
+#define WM8915_DSP2DRC_ANTICLIP 0x0008 /* DSP2DRC_ANTICLIP */
+#define WM8915_DSP2DRC_ANTICLIP_MASK 0x0008 /* DSP2DRC_ANTICLIP */
+#define WM8915_DSP2DRC_ANTICLIP_SHIFT 3 /* DSP2DRC_ANTICLIP */
+#define WM8915_DSP2DRC_ANTICLIP_WIDTH 1 /* DSP2DRC_ANTICLIP */
+#define WM8915_DSP2RX_DRC_ENA 0x0004 /* DSP2RX_DRC_ENA */
+#define WM8915_DSP2RX_DRC_ENA_MASK 0x0004 /* DSP2RX_DRC_ENA */
+#define WM8915_DSP2RX_DRC_ENA_SHIFT 2 /* DSP2RX_DRC_ENA */
+#define WM8915_DSP2RX_DRC_ENA_WIDTH 1 /* DSP2RX_DRC_ENA */
+#define WM8915_DSP2TXL_DRC_ENA 0x0002 /* DSP2TXL_DRC_ENA */
+#define WM8915_DSP2TXL_DRC_ENA_MASK 0x0002 /* DSP2TXL_DRC_ENA */
+#define WM8915_DSP2TXL_DRC_ENA_SHIFT 1 /* DSP2TXL_DRC_ENA */
+#define WM8915_DSP2TXL_DRC_ENA_WIDTH 1 /* DSP2TXL_DRC_ENA */
+#define WM8915_DSP2TXR_DRC_ENA 0x0001 /* DSP2TXR_DRC_ENA */
+#define WM8915_DSP2TXR_DRC_ENA_MASK 0x0001 /* DSP2TXR_DRC_ENA */
+#define WM8915_DSP2TXR_DRC_ENA_SHIFT 0 /* DSP2TXR_DRC_ENA */
+#define WM8915_DSP2TXR_DRC_ENA_WIDTH 1 /* DSP2TXR_DRC_ENA */
+
+/*
+ * R1345 (0x541) - DSP2 DRC (2)
+ */
+#define WM8915_DSP2DRC_ATK_MASK 0x1E00 /* DSP2DRC_ATK - [12:9] */
+#define WM8915_DSP2DRC_ATK_SHIFT 9 /* DSP2DRC_ATK - [12:9] */
+#define WM8915_DSP2DRC_ATK_WIDTH 4 /* DSP2DRC_ATK - [12:9] */
+#define WM8915_DSP2DRC_DCY_MASK 0x01E0 /* DSP2DRC_DCY - [8:5] */
+#define WM8915_DSP2DRC_DCY_SHIFT 5 /* DSP2DRC_DCY - [8:5] */
+#define WM8915_DSP2DRC_DCY_WIDTH 4 /* DSP2DRC_DCY - [8:5] */
+#define WM8915_DSP2DRC_MINGAIN_MASK 0x001C /* DSP2DRC_MINGAIN - [4:2] */
+#define WM8915_DSP2DRC_MINGAIN_SHIFT 2 /* DSP2DRC_MINGAIN - [4:2] */
+#define WM8915_DSP2DRC_MINGAIN_WIDTH 3 /* DSP2DRC_MINGAIN - [4:2] */
+#define WM8915_DSP2DRC_MAXGAIN_MASK 0x0003 /* DSP2DRC_MAXGAIN - [1:0] */
+#define WM8915_DSP2DRC_MAXGAIN_SHIFT 0 /* DSP2DRC_MAXGAIN - [1:0] */
+#define WM8915_DSP2DRC_MAXGAIN_WIDTH 2 /* DSP2DRC_MAXGAIN - [1:0] */
+
+/*
+ * R1346 (0x542) - DSP2 DRC (3)
+ */
+#define WM8915_DSP2DRC_NG_MINGAIN_MASK 0xF000 /* DSP2DRC_NG_MINGAIN - [15:12] */
+#define WM8915_DSP2DRC_NG_MINGAIN_SHIFT 12 /* DSP2DRC_NG_MINGAIN - [15:12] */
+#define WM8915_DSP2DRC_NG_MINGAIN_WIDTH 4 /* DSP2DRC_NG_MINGAIN - [15:12] */
+#define WM8915_DSP2DRC_NG_EXP_MASK 0x0C00 /* DSP2DRC_NG_EXP - [11:10] */
+#define WM8915_DSP2DRC_NG_EXP_SHIFT 10 /* DSP2DRC_NG_EXP - [11:10] */
+#define WM8915_DSP2DRC_NG_EXP_WIDTH 2 /* DSP2DRC_NG_EXP - [11:10] */
+#define WM8915_DSP2DRC_QR_THR_MASK 0x0300 /* DSP2DRC_QR_THR - [9:8] */
+#define WM8915_DSP2DRC_QR_THR_SHIFT 8 /* DSP2DRC_QR_THR - [9:8] */
+#define WM8915_DSP2DRC_QR_THR_WIDTH 2 /* DSP2DRC_QR_THR - [9:8] */
+#define WM8915_DSP2DRC_QR_DCY_MASK 0x00C0 /* DSP2DRC_QR_DCY - [7:6] */
+#define WM8915_DSP2DRC_QR_DCY_SHIFT 6 /* DSP2DRC_QR_DCY - [7:6] */
+#define WM8915_DSP2DRC_QR_DCY_WIDTH 2 /* DSP2DRC_QR_DCY - [7:6] */
+#define WM8915_DSP2DRC_HI_COMP_MASK 0x0038 /* DSP2DRC_HI_COMP - [5:3] */
+#define WM8915_DSP2DRC_HI_COMP_SHIFT 3 /* DSP2DRC_HI_COMP - [5:3] */
+#define WM8915_DSP2DRC_HI_COMP_WIDTH 3 /* DSP2DRC_HI_COMP - [5:3] */
+#define WM8915_DSP2DRC_LO_COMP_MASK 0x0007 /* DSP2DRC_LO_COMP - [2:0] */
+#define WM8915_DSP2DRC_LO_COMP_SHIFT 0 /* DSP2DRC_LO_COMP - [2:0] */
+#define WM8915_DSP2DRC_LO_COMP_WIDTH 3 /* DSP2DRC_LO_COMP - [2:0] */
+
+/*
+ * R1347 (0x543) - DSP2 DRC (4)
+ */
+#define WM8915_DSP2DRC_KNEE_IP_MASK 0x07E0 /* DSP2DRC_KNEE_IP - [10:5] */
+#define WM8915_DSP2DRC_KNEE_IP_SHIFT 5 /* DSP2DRC_KNEE_IP - [10:5] */
+#define WM8915_DSP2DRC_KNEE_IP_WIDTH 6 /* DSP2DRC_KNEE_IP - [10:5] */
+#define WM8915_DSP2DRC_KNEE_OP_MASK 0x001F /* DSP2DRC_KNEE_OP - [4:0] */
+#define WM8915_DSP2DRC_KNEE_OP_SHIFT 0 /* DSP2DRC_KNEE_OP - [4:0] */
+#define WM8915_DSP2DRC_KNEE_OP_WIDTH 5 /* DSP2DRC_KNEE_OP - [4:0] */
+
+/*
+ * R1348 (0x544) - DSP2 DRC (5)
+ */
+#define WM8915_DSP2DRC_KNEE2_IP_MASK 0x03E0 /* DSP2DRC_KNEE2_IP - [9:5] */
+#define WM8915_DSP2DRC_KNEE2_IP_SHIFT 5 /* DSP2DRC_KNEE2_IP - [9:5] */
+#define WM8915_DSP2DRC_KNEE2_IP_WIDTH 5 /* DSP2DRC_KNEE2_IP - [9:5] */
+#define WM8915_DSP2DRC_KNEE2_OP_MASK 0x001F /* DSP2DRC_KNEE2_OP - [4:0] */
+#define WM8915_DSP2DRC_KNEE2_OP_SHIFT 0 /* DSP2DRC_KNEE2_OP - [4:0] */
+#define WM8915_DSP2DRC_KNEE2_OP_WIDTH 5 /* DSP2DRC_KNEE2_OP - [4:0] */
+
+/*
+ * R1408 (0x580) - DSP2 RX EQ Gains (1)
+ */
+#define WM8915_DSP2RX_EQ_B1_GAIN_MASK 0xF800 /* DSP2RX_EQ_B1_GAIN - [15:11] */
+#define WM8915_DSP2RX_EQ_B1_GAIN_SHIFT 11 /* DSP2RX_EQ_B1_GAIN - [15:11] */
+#define WM8915_DSP2RX_EQ_B1_GAIN_WIDTH 5 /* DSP2RX_EQ_B1_GAIN - [15:11] */
+#define WM8915_DSP2RX_EQ_B2_GAIN_MASK 0x07C0 /* DSP2RX_EQ_B2_GAIN - [10:6] */
+#define WM8915_DSP2RX_EQ_B2_GAIN_SHIFT 6 /* DSP2RX_EQ_B2_GAIN - [10:6] */
+#define WM8915_DSP2RX_EQ_B2_GAIN_WIDTH 5 /* DSP2RX_EQ_B2_GAIN - [10:6] */
+#define WM8915_DSP2RX_EQ_B3_GAIN_MASK 0x003E /* DSP2RX_EQ_B3_GAIN - [5:1] */
+#define WM8915_DSP2RX_EQ_B3_GAIN_SHIFT 1 /* DSP2RX_EQ_B3_GAIN - [5:1] */
+#define WM8915_DSP2RX_EQ_B3_GAIN_WIDTH 5 /* DSP2RX_EQ_B3_GAIN - [5:1] */
+#define WM8915_DSP2RX_EQ_ENA 0x0001 /* DSP2RX_EQ_ENA */
+#define WM8915_DSP2RX_EQ_ENA_MASK 0x0001 /* DSP2RX_EQ_ENA */
+#define WM8915_DSP2RX_EQ_ENA_SHIFT 0 /* DSP2RX_EQ_ENA */
+#define WM8915_DSP2RX_EQ_ENA_WIDTH 1 /* DSP2RX_EQ_ENA */
+
+/*
+ * R1409 (0x581) - DSP2 RX EQ Gains (2)
+ */
+#define WM8915_DSP2RX_EQ_B4_GAIN_MASK 0xF800 /* DSP2RX_EQ_B4_GAIN - [15:11] */
+#define WM8915_DSP2RX_EQ_B4_GAIN_SHIFT 11 /* DSP2RX_EQ_B4_GAIN - [15:11] */
+#define WM8915_DSP2RX_EQ_B4_GAIN_WIDTH 5 /* DSP2RX_EQ_B4_GAIN - [15:11] */
+#define WM8915_DSP2RX_EQ_B5_GAIN_MASK 0x07C0 /* DSP2RX_EQ_B5_GAIN - [10:6] */
+#define WM8915_DSP2RX_EQ_B5_GAIN_SHIFT 6 /* DSP2RX_EQ_B5_GAIN - [10:6] */
+#define WM8915_DSP2RX_EQ_B5_GAIN_WIDTH 5 /* DSP2RX_EQ_B5_GAIN - [10:6] */
+
+/*
+ * R1410 (0x582) - DSP2 RX EQ Band 1 A
+ */
+#define WM8915_DSP2RX_EQ_B1_A_MASK 0xFFFF /* DSP2RX_EQ_B1_A - [15:0] */
+#define WM8915_DSP2RX_EQ_B1_A_SHIFT 0 /* DSP2RX_EQ_B1_A - [15:0] */
+#define WM8915_DSP2RX_EQ_B1_A_WIDTH 16 /* DSP2RX_EQ_B1_A - [15:0] */
+
+/*
+ * R1411 (0x583) - DSP2 RX EQ Band 1 B
+ */
+#define WM8915_DSP2RX_EQ_B1_B_MASK 0xFFFF /* DSP2RX_EQ_B1_B - [15:0] */
+#define WM8915_DSP2RX_EQ_B1_B_SHIFT 0 /* DSP2RX_EQ_B1_B - [15:0] */
+#define WM8915_DSP2RX_EQ_B1_B_WIDTH 16 /* DSP2RX_EQ_B1_B - [15:0] */
+
+/*
+ * R1412 (0x584) - DSP2 RX EQ Band 1 PG
+ */
+#define WM8915_DSP2RX_EQ_B1_PG_MASK 0xFFFF /* DSP2RX_EQ_B1_PG - [15:0] */
+#define WM8915_DSP2RX_EQ_B1_PG_SHIFT 0 /* DSP2RX_EQ_B1_PG - [15:0] */
+#define WM8915_DSP2RX_EQ_B1_PG_WIDTH 16 /* DSP2RX_EQ_B1_PG - [15:0] */
+
+/*
+ * R1413 (0x585) - DSP2 RX EQ Band 2 A
+ */
+#define WM8915_DSP2RX_EQ_B2_A_MASK 0xFFFF /* DSP2RX_EQ_B2_A - [15:0] */
+#define WM8915_DSP2RX_EQ_B2_A_SHIFT 0 /* DSP2RX_EQ_B2_A - [15:0] */
+#define WM8915_DSP2RX_EQ_B2_A_WIDTH 16 /* DSP2RX_EQ_B2_A - [15:0] */
+
+/*
+ * R1414 (0x586) - DSP2 RX EQ Band 2 B
+ */
+#define WM8915_DSP2RX_EQ_B2_B_MASK 0xFFFF /* DSP2RX_EQ_B2_B - [15:0] */
+#define WM8915_DSP2RX_EQ_B2_B_SHIFT 0 /* DSP2RX_EQ_B2_B - [15:0] */
+#define WM8915_DSP2RX_EQ_B2_B_WIDTH 16 /* DSP2RX_EQ_B2_B - [15:0] */
+
+/*
+ * R1415 (0x587) - DSP2 RX EQ Band 2 C
+ */
+#define WM8915_DSP2RX_EQ_B2_C_MASK 0xFFFF /* DSP2RX_EQ_B2_C - [15:0] */
+#define WM8915_DSP2RX_EQ_B2_C_SHIFT 0 /* DSP2RX_EQ_B2_C - [15:0] */
+#define WM8915_DSP2RX_EQ_B2_C_WIDTH 16 /* DSP2RX_EQ_B2_C - [15:0] */
+
+/*
+ * R1416 (0x588) - DSP2 RX EQ Band 2 PG
+ */
+#define WM8915_DSP2RX_EQ_B2_PG_MASK 0xFFFF /* DSP2RX_EQ_B2_PG - [15:0] */
+#define WM8915_DSP2RX_EQ_B2_PG_SHIFT 0 /* DSP2RX_EQ_B2_PG - [15:0] */
+#define WM8915_DSP2RX_EQ_B2_PG_WIDTH 16 /* DSP2RX_EQ_B2_PG - [15:0] */
+
+/*
+ * R1417 (0x589) - DSP2 RX EQ Band 3 A
+ */
+#define WM8915_DSP2RX_EQ_B3_A_MASK 0xFFFF /* DSP2RX_EQ_B3_A - [15:0] */
+#define WM8915_DSP2RX_EQ_B3_A_SHIFT 0 /* DSP2RX_EQ_B3_A - [15:0] */
+#define WM8915_DSP2RX_EQ_B3_A_WIDTH 16 /* DSP2RX_EQ_B3_A - [15:0] */
+
+/*
+ * R1418 (0x58A) - DSP2 RX EQ Band 3 B
+ */
+#define WM8915_DSP2RX_EQ_B3_B_MASK 0xFFFF /* DSP2RX_EQ_B3_B - [15:0] */
+#define WM8915_DSP2RX_EQ_B3_B_SHIFT 0 /* DSP2RX_EQ_B3_B - [15:0] */
+#define WM8915_DSP2RX_EQ_B3_B_WIDTH 16 /* DSP2RX_EQ_B3_B - [15:0] */
+
+/*
+ * R1419 (0x58B) - DSP2 RX EQ Band 3 C
+ */
+#define WM8915_DSP2RX_EQ_B3_C_MASK 0xFFFF /* DSP2RX_EQ_B3_C - [15:0] */
+#define WM8915_DSP2RX_EQ_B3_C_SHIFT 0 /* DSP2RX_EQ_B3_C - [15:0] */
+#define WM8915_DSP2RX_EQ_B3_C_WIDTH 16 /* DSP2RX_EQ_B3_C - [15:0] */
+
+/*
+ * R1420 (0x58C) - DSP2 RX EQ Band 3 PG
+ */
+#define WM8915_DSP2RX_EQ_B3_PG_MASK 0xFFFF /* DSP2RX_EQ_B3_PG - [15:0] */
+#define WM8915_DSP2RX_EQ_B3_PG_SHIFT 0 /* DSP2RX_EQ_B3_PG - [15:0] */
+#define WM8915_DSP2RX_EQ_B3_PG_WIDTH 16 /* DSP2RX_EQ_B3_PG - [15:0] */
+
+/*
+ * R1421 (0x58D) - DSP2 RX EQ Band 4 A
+ */
+#define WM8915_DSP2RX_EQ_B4_A_MASK 0xFFFF /* DSP2RX_EQ_B4_A - [15:0] */
+#define WM8915_DSP2RX_EQ_B4_A_SHIFT 0 /* DSP2RX_EQ_B4_A - [15:0] */
+#define WM8915_DSP2RX_EQ_B4_A_WIDTH 16 /* DSP2RX_EQ_B4_A - [15:0] */
+
+/*
+ * R1422 (0x58E) - DSP2 RX EQ Band 4 B
+ */
+#define WM8915_DSP2RX_EQ_B4_B_MASK 0xFFFF /* DSP2RX_EQ_B4_B - [15:0] */
+#define WM8915_DSP2RX_EQ_B4_B_SHIFT 0 /* DSP2RX_EQ_B4_B - [15:0] */
+#define WM8915_DSP2RX_EQ_B4_B_WIDTH 16 /* DSP2RX_EQ_B4_B - [15:0] */
+
+/*
+ * R1423 (0x58F) - DSP2 RX EQ Band 4 C
+ */
+#define WM8915_DSP2RX_EQ_B4_C_MASK 0xFFFF /* DSP2RX_EQ_B4_C - [15:0] */
+#define WM8915_DSP2RX_EQ_B4_C_SHIFT 0 /* DSP2RX_EQ_B4_C - [15:0] */
+#define WM8915_DSP2RX_EQ_B4_C_WIDTH 16 /* DSP2RX_EQ_B4_C - [15:0] */
+
+/*
+ * R1424 (0x590) - DSP2 RX EQ Band 4 PG
+ */
+#define WM8915_DSP2RX_EQ_B4_PG_MASK 0xFFFF /* DSP2RX_EQ_B4_PG - [15:0] */
+#define WM8915_DSP2RX_EQ_B4_PG_SHIFT 0 /* DSP2RX_EQ_B4_PG - [15:0] */
+#define WM8915_DSP2RX_EQ_B4_PG_WIDTH 16 /* DSP2RX_EQ_B4_PG - [15:0] */
+
+/*
+ * R1425 (0x591) - DSP2 RX EQ Band 5 A
+ */
+#define WM8915_DSP2RX_EQ_B5_A_MASK 0xFFFF /* DSP2RX_EQ_B5_A - [15:0] */
+#define WM8915_DSP2RX_EQ_B5_A_SHIFT 0 /* DSP2RX_EQ_B5_A - [15:0] */
+#define WM8915_DSP2RX_EQ_B5_A_WIDTH 16 /* DSP2RX_EQ_B5_A - [15:0] */
+
+/*
+ * R1426 (0x592) - DSP2 RX EQ Band 5 B
+ */
+#define WM8915_DSP2RX_EQ_B5_B_MASK 0xFFFF /* DSP2RX_EQ_B5_B - [15:0] */
+#define WM8915_DSP2RX_EQ_B5_B_SHIFT 0 /* DSP2RX_EQ_B5_B - [15:0] */
+#define WM8915_DSP2RX_EQ_B5_B_WIDTH 16 /* DSP2RX_EQ_B5_B - [15:0] */
+
+/*
+ * R1427 (0x593) - DSP2 RX EQ Band 5 PG
+ */
+#define WM8915_DSP2RX_EQ_B5_PG_MASK 0xFFFF /* DSP2RX_EQ_B5_PG - [15:0] */
+#define WM8915_DSP2RX_EQ_B5_PG_SHIFT 0 /* DSP2RX_EQ_B5_PG - [15:0] */
+#define WM8915_DSP2RX_EQ_B5_PG_WIDTH 16 /* DSP2RX_EQ_B5_PG - [15:0] */
+
+/*
+ * R1536 (0x600) - DAC1 Mixer Volumes
+ */
+#define WM8915_ADCR_DAC1_VOL_MASK 0x03E0 /* ADCR_DAC1_VOL - [9:5] */
+#define WM8915_ADCR_DAC1_VOL_SHIFT 5 /* ADCR_DAC1_VOL - [9:5] */
+#define WM8915_ADCR_DAC1_VOL_WIDTH 5 /* ADCR_DAC1_VOL - [9:5] */
+#define WM8915_ADCL_DAC1_VOL_MASK 0x001F /* ADCL_DAC1_VOL - [4:0] */
+#define WM8915_ADCL_DAC1_VOL_SHIFT 0 /* ADCL_DAC1_VOL - [4:0] */
+#define WM8915_ADCL_DAC1_VOL_WIDTH 5 /* ADCL_DAC1_VOL - [4:0] */
+
+/*
+ * R1537 (0x601) - DAC1 Left Mixer Routing
+ */
+#define WM8915_ADCR_TO_DAC1L 0x0020 /* ADCR_TO_DAC1L */
+#define WM8915_ADCR_TO_DAC1L_MASK 0x0020 /* ADCR_TO_DAC1L */
+#define WM8915_ADCR_TO_DAC1L_SHIFT 5 /* ADCR_TO_DAC1L */
+#define WM8915_ADCR_TO_DAC1L_WIDTH 1 /* ADCR_TO_DAC1L */
+#define WM8915_ADCL_TO_DAC1L 0x0010 /* ADCL_TO_DAC1L */
+#define WM8915_ADCL_TO_DAC1L_MASK 0x0010 /* ADCL_TO_DAC1L */
+#define WM8915_ADCL_TO_DAC1L_SHIFT 4 /* ADCL_TO_DAC1L */
+#define WM8915_ADCL_TO_DAC1L_WIDTH 1 /* ADCL_TO_DAC1L */
+#define WM8915_DSP2RXL_TO_DAC1L 0x0002 /* DSP2RXL_TO_DAC1L */
+#define WM8915_DSP2RXL_TO_DAC1L_MASK 0x0002 /* DSP2RXL_TO_DAC1L */
+#define WM8915_DSP2RXL_TO_DAC1L_SHIFT 1 /* DSP2RXL_TO_DAC1L */
+#define WM8915_DSP2RXL_TO_DAC1L_WIDTH 1 /* DSP2RXL_TO_DAC1L */
+#define WM8915_DSP1RXL_TO_DAC1L 0x0001 /* DSP1RXL_TO_DAC1L */
+#define WM8915_DSP1RXL_TO_DAC1L_MASK 0x0001 /* DSP1RXL_TO_DAC1L */
+#define WM8915_DSP1RXL_TO_DAC1L_SHIFT 0 /* DSP1RXL_TO_DAC1L */
+#define WM8915_DSP1RXL_TO_DAC1L_WIDTH 1 /* DSP1RXL_TO_DAC1L */
+
+/*
+ * R1538 (0x602) - DAC1 Right Mixer Routing
+ */
+#define WM8915_ADCR_TO_DAC1R 0x0020 /* ADCR_TO_DAC1R */
+#define WM8915_ADCR_TO_DAC1R_MASK 0x0020 /* ADCR_TO_DAC1R */
+#define WM8915_ADCR_TO_DAC1R_SHIFT 5 /* ADCR_TO_DAC1R */
+#define WM8915_ADCR_TO_DAC1R_WIDTH 1 /* ADCR_TO_DAC1R */
+#define WM8915_ADCL_TO_DAC1R 0x0010 /* ADCL_TO_DAC1R */
+#define WM8915_ADCL_TO_DAC1R_MASK 0x0010 /* ADCL_TO_DAC1R */
+#define WM8915_ADCL_TO_DAC1R_SHIFT 4 /* ADCL_TO_DAC1R */
+#define WM8915_ADCL_TO_DAC1R_WIDTH 1 /* ADCL_TO_DAC1R */
+#define WM8915_DSP2RXR_TO_DAC1R 0x0002 /* DSP2RXR_TO_DAC1R */
+#define WM8915_DSP2RXR_TO_DAC1R_MASK 0x0002 /* DSP2RXR_TO_DAC1R */
+#define WM8915_DSP2RXR_TO_DAC1R_SHIFT 1 /* DSP2RXR_TO_DAC1R */
+#define WM8915_DSP2RXR_TO_DAC1R_WIDTH 1 /* DSP2RXR_TO_DAC1R */
+#define WM8915_DSP1RXR_TO_DAC1R 0x0001 /* DSP1RXR_TO_DAC1R */
+#define WM8915_DSP1RXR_TO_DAC1R_MASK 0x0001 /* DSP1RXR_TO_DAC1R */
+#define WM8915_DSP1RXR_TO_DAC1R_SHIFT 0 /* DSP1RXR_TO_DAC1R */
+#define WM8915_DSP1RXR_TO_DAC1R_WIDTH 1 /* DSP1RXR_TO_DAC1R */
+
+/*
+ * R1539 (0x603) - DAC2 Mixer Volumes
+ */
+#define WM8915_ADCR_DAC2_VOL_MASK 0x03E0 /* ADCR_DAC2_VOL - [9:5] */
+#define WM8915_ADCR_DAC2_VOL_SHIFT 5 /* ADCR_DAC2_VOL - [9:5] */
+#define WM8915_ADCR_DAC2_VOL_WIDTH 5 /* ADCR_DAC2_VOL - [9:5] */
+#define WM8915_ADCL_DAC2_VOL_MASK 0x001F /* ADCL_DAC2_VOL - [4:0] */
+#define WM8915_ADCL_DAC2_VOL_SHIFT 0 /* ADCL_DAC2_VOL - [4:0] */
+#define WM8915_ADCL_DAC2_VOL_WIDTH 5 /* ADCL_DAC2_VOL - [4:0] */
+
+/*
+ * R1540 (0x604) - DAC2 Left Mixer Routing
+ */
+#define WM8915_ADCR_TO_DAC2L 0x0020 /* ADCR_TO_DAC2L */
+#define WM8915_ADCR_TO_DAC2L_MASK 0x0020 /* ADCR_TO_DAC2L */
+#define WM8915_ADCR_TO_DAC2L_SHIFT 5 /* ADCR_TO_DAC2L */
+#define WM8915_ADCR_TO_DAC2L_WIDTH 1 /* ADCR_TO_DAC2L */
+#define WM8915_ADCL_TO_DAC2L 0x0010 /* ADCL_TO_DAC2L */
+#define WM8915_ADCL_TO_DAC2L_MASK 0x0010 /* ADCL_TO_DAC2L */
+#define WM8915_ADCL_TO_DAC2L_SHIFT 4 /* ADCL_TO_DAC2L */
+#define WM8915_ADCL_TO_DAC2L_WIDTH 1 /* ADCL_TO_DAC2L */
+#define WM8915_DSP2RXL_TO_DAC2L 0x0002 /* DSP2RXL_TO_DAC2L */
+#define WM8915_DSP2RXL_TO_DAC2L_MASK 0x0002 /* DSP2RXL_TO_DAC2L */
+#define WM8915_DSP2RXL_TO_DAC2L_SHIFT 1 /* DSP2RXL_TO_DAC2L */
+#define WM8915_DSP2RXL_TO_DAC2L_WIDTH 1 /* DSP2RXL_TO_DAC2L */
+#define WM8915_DSP1RXL_TO_DAC2L 0x0001 /* DSP1RXL_TO_DAC2L */
+#define WM8915_DSP1RXL_TO_DAC2L_MASK 0x0001 /* DSP1RXL_TO_DAC2L */
+#define WM8915_DSP1RXL_TO_DAC2L_SHIFT 0 /* DSP1RXL_TO_DAC2L */
+#define WM8915_DSP1RXL_TO_DAC2L_WIDTH 1 /* DSP1RXL_TO_DAC2L */
+
+/*
+ * R1541 (0x605) - DAC2 Right Mixer Routing
+ */
+#define WM8915_ADCR_TO_DAC2R 0x0020 /* ADCR_TO_DAC2R */
+#define WM8915_ADCR_TO_DAC2R_MASK 0x0020 /* ADCR_TO_DAC2R */
+#define WM8915_ADCR_TO_DAC2R_SHIFT 5 /* ADCR_TO_DAC2R */
+#define WM8915_ADCR_TO_DAC2R_WIDTH 1 /* ADCR_TO_DAC2R */
+#define WM8915_ADCL_TO_DAC2R 0x0010 /* ADCL_TO_DAC2R */
+#define WM8915_ADCL_TO_DAC2R_MASK 0x0010 /* ADCL_TO_DAC2R */
+#define WM8915_ADCL_TO_DAC2R_SHIFT 4 /* ADCL_TO_DAC2R */
+#define WM8915_ADCL_TO_DAC2R_WIDTH 1 /* ADCL_TO_DAC2R */
+#define WM8915_DSP2RXR_TO_DAC2R 0x0002 /* DSP2RXR_TO_DAC2R */
+#define WM8915_DSP2RXR_TO_DAC2R_MASK 0x0002 /* DSP2RXR_TO_DAC2R */
+#define WM8915_DSP2RXR_TO_DAC2R_SHIFT 1 /* DSP2RXR_TO_DAC2R */
+#define WM8915_DSP2RXR_TO_DAC2R_WIDTH 1 /* DSP2RXR_TO_DAC2R */
+#define WM8915_DSP1RXR_TO_DAC2R 0x0001 /* DSP1RXR_TO_DAC2R */
+#define WM8915_DSP1RXR_TO_DAC2R_MASK 0x0001 /* DSP1RXR_TO_DAC2R */
+#define WM8915_DSP1RXR_TO_DAC2R_SHIFT 0 /* DSP1RXR_TO_DAC2R */
+#define WM8915_DSP1RXR_TO_DAC2R_WIDTH 1 /* DSP1RXR_TO_DAC2R */
+
+/*
+ * R1542 (0x606) - DSP1 TX Left Mixer Routing
+ */
+#define WM8915_ADC1L_TO_DSP1TXL 0x0002 /* ADC1L_TO_DSP1TXL */
+#define WM8915_ADC1L_TO_DSP1TXL_MASK 0x0002 /* ADC1L_TO_DSP1TXL */
+#define WM8915_ADC1L_TO_DSP1TXL_SHIFT 1 /* ADC1L_TO_DSP1TXL */
+#define WM8915_ADC1L_TO_DSP1TXL_WIDTH 1 /* ADC1L_TO_DSP1TXL */
+#define WM8915_DACL_TO_DSP1TXL 0x0001 /* DACL_TO_DSP1TXL */
+#define WM8915_DACL_TO_DSP1TXL_MASK 0x0001 /* DACL_TO_DSP1TXL */
+#define WM8915_DACL_TO_DSP1TXL_SHIFT 0 /* DACL_TO_DSP1TXL */
+#define WM8915_DACL_TO_DSP1TXL_WIDTH 1 /* DACL_TO_DSP1TXL */
+
+/*
+ * R1543 (0x607) - DSP1 TX Right Mixer Routing
+ */
+#define WM8915_ADC1R_TO_DSP1TXR 0x0002 /* ADC1R_TO_DSP1TXR */
+#define WM8915_ADC1R_TO_DSP1TXR_MASK 0x0002 /* ADC1R_TO_DSP1TXR */
+#define WM8915_ADC1R_TO_DSP1TXR_SHIFT 1 /* ADC1R_TO_DSP1TXR */
+#define WM8915_ADC1R_TO_DSP1TXR_WIDTH 1 /* ADC1R_TO_DSP1TXR */
+#define WM8915_DACR_TO_DSP1TXR 0x0001 /* DACR_TO_DSP1TXR */
+#define WM8915_DACR_TO_DSP1TXR_MASK 0x0001 /* DACR_TO_DSP1TXR */
+#define WM8915_DACR_TO_DSP1TXR_SHIFT 0 /* DACR_TO_DSP1TXR */
+#define WM8915_DACR_TO_DSP1TXR_WIDTH 1 /* DACR_TO_DSP1TXR */
+
+/*
+ * R1544 (0x608) - DSP2 TX Left Mixer Routing
+ */
+#define WM8915_ADC2L_TO_DSP2TXL 0x0002 /* ADC2L_TO_DSP2TXL */
+#define WM8915_ADC2L_TO_DSP2TXL_MASK 0x0002 /* ADC2L_TO_DSP2TXL */
+#define WM8915_ADC2L_TO_DSP2TXL_SHIFT 1 /* ADC2L_TO_DSP2TXL */
+#define WM8915_ADC2L_TO_DSP2TXL_WIDTH 1 /* ADC2L_TO_DSP2TXL */
+#define WM8915_DACL_TO_DSP2TXL 0x0001 /* DACL_TO_DSP2TXL */
+#define WM8915_DACL_TO_DSP2TXL_MASK 0x0001 /* DACL_TO_DSP2TXL */
+#define WM8915_DACL_TO_DSP2TXL_SHIFT 0 /* DACL_TO_DSP2TXL */
+#define WM8915_DACL_TO_DSP2TXL_WIDTH 1 /* DACL_TO_DSP2TXL */
+
+/*
+ * R1545 (0x609) - DSP2 TX Right Mixer Routing
+ */
+#define WM8915_ADC2R_TO_DSP2TXR 0x0002 /* ADC2R_TO_DSP2TXR */
+#define WM8915_ADC2R_TO_DSP2TXR_MASK 0x0002 /* ADC2R_TO_DSP2TXR */
+#define WM8915_ADC2R_TO_DSP2TXR_SHIFT 1 /* ADC2R_TO_DSP2TXR */
+#define WM8915_ADC2R_TO_DSP2TXR_WIDTH 1 /* ADC2R_TO_DSP2TXR */
+#define WM8915_DACR_TO_DSP2TXR 0x0001 /* DACR_TO_DSP2TXR */
+#define WM8915_DACR_TO_DSP2TXR_MASK 0x0001 /* DACR_TO_DSP2TXR */
+#define WM8915_DACR_TO_DSP2TXR_SHIFT 0 /* DACR_TO_DSP2TXR */
+#define WM8915_DACR_TO_DSP2TXR_WIDTH 1 /* DACR_TO_DSP2TXR */
+
+/*
+ * R1546 (0x60A) - DSP TX Mixer Select
+ */
+#define WM8915_DAC_TO_DSPTX_SRC 0x0001 /* DAC_TO_DSPTX_SRC */
+#define WM8915_DAC_TO_DSPTX_SRC_MASK 0x0001 /* DAC_TO_DSPTX_SRC */
+#define WM8915_DAC_TO_DSPTX_SRC_SHIFT 0 /* DAC_TO_DSPTX_SRC */
+#define WM8915_DAC_TO_DSPTX_SRC_WIDTH 1 /* DAC_TO_DSPTX_SRC */
+
+/*
+ * R1552 (0x610) - DAC Softmute
+ */
+#define WM8915_DAC_SOFTMUTEMODE 0x0002 /* DAC_SOFTMUTEMODE */
+#define WM8915_DAC_SOFTMUTEMODE_MASK 0x0002 /* DAC_SOFTMUTEMODE */
+#define WM8915_DAC_SOFTMUTEMODE_SHIFT 1 /* DAC_SOFTMUTEMODE */
+#define WM8915_DAC_SOFTMUTEMODE_WIDTH 1 /* DAC_SOFTMUTEMODE */
+#define WM8915_DAC_MUTERATE 0x0001 /* DAC_MUTERATE */
+#define WM8915_DAC_MUTERATE_MASK 0x0001 /* DAC_MUTERATE */
+#define WM8915_DAC_MUTERATE_SHIFT 0 /* DAC_MUTERATE */
+#define WM8915_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
+
+/*
+ * R1568 (0x620) - Oversampling
+ */
+#define WM8915_SPK_OSR128 0x0008 /* SPK_OSR128 */
+#define WM8915_SPK_OSR128_MASK 0x0008 /* SPK_OSR128 */
+#define WM8915_SPK_OSR128_SHIFT 3 /* SPK_OSR128 */
+#define WM8915_SPK_OSR128_WIDTH 1 /* SPK_OSR128 */
+#define WM8915_DMIC_OSR64 0x0004 /* DMIC_OSR64 */
+#define WM8915_DMIC_OSR64_MASK 0x0004 /* DMIC_OSR64 */
+#define WM8915_DMIC_OSR64_SHIFT 2 /* DMIC_OSR64 */
+#define WM8915_DMIC_OSR64_WIDTH 1 /* DMIC_OSR64 */
+#define WM8915_ADC_OSR128 0x0002 /* ADC_OSR128 */
+#define WM8915_ADC_OSR128_MASK 0x0002 /* ADC_OSR128 */
+#define WM8915_ADC_OSR128_SHIFT 1 /* ADC_OSR128 */
+#define WM8915_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */
+#define WM8915_DAC_OSR128 0x0001 /* DAC_OSR128 */
+#define WM8915_DAC_OSR128_MASK 0x0001 /* DAC_OSR128 */
+#define WM8915_DAC_OSR128_SHIFT 0 /* DAC_OSR128 */
+#define WM8915_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */
+
+/*
+ * R1569 (0x621) - Sidetone
+ */
+#define WM8915_ST_LPF 0x1000 /* ST_LPF */
+#define WM8915_ST_LPF_MASK 0x1000 /* ST_LPF */
+#define WM8915_ST_LPF_SHIFT 12 /* ST_LPF */
+#define WM8915_ST_LPF_WIDTH 1 /* ST_LPF */
+#define WM8915_ST_HPF_CUT_MASK 0x0380 /* ST_HPF_CUT - [9:7] */
+#define WM8915_ST_HPF_CUT_SHIFT 7 /* ST_HPF_CUT - [9:7] */
+#define WM8915_ST_HPF_CUT_WIDTH 3 /* ST_HPF_CUT - [9:7] */
+#define WM8915_ST_HPF 0x0040 /* ST_HPF */
+#define WM8915_ST_HPF_MASK 0x0040 /* ST_HPF */
+#define WM8915_ST_HPF_SHIFT 6 /* ST_HPF */
+#define WM8915_ST_HPF_WIDTH 1 /* ST_HPF */
+#define WM8915_STR_SEL 0x0002 /* STR_SEL */
+#define WM8915_STR_SEL_MASK 0x0002 /* STR_SEL */
+#define WM8915_STR_SEL_SHIFT 1 /* STR_SEL */
+#define WM8915_STR_SEL_WIDTH 1 /* STR_SEL */
+#define WM8915_STL_SEL 0x0001 /* STL_SEL */
+#define WM8915_STL_SEL_MASK 0x0001 /* STL_SEL */
+#define WM8915_STL_SEL_SHIFT 0 /* STL_SEL */
+#define WM8915_STL_SEL_WIDTH 1 /* STL_SEL */
+
+/*
+ * R1792 (0x700) - GPIO 1
+ */
+#define WM8915_GP1_DIR 0x8000 /* GP1_DIR */
+#define WM8915_GP1_DIR_MASK 0x8000 /* GP1_DIR */
+#define WM8915_GP1_DIR_SHIFT 15 /* GP1_DIR */
+#define WM8915_GP1_DIR_WIDTH 1 /* GP1_DIR */
+#define WM8915_GP1_PU 0x4000 /* GP1_PU */
+#define WM8915_GP1_PU_MASK 0x4000 /* GP1_PU */
+#define WM8915_GP1_PU_SHIFT 14 /* GP1_PU */
+#define WM8915_GP1_PU_WIDTH 1 /* GP1_PU */
+#define WM8915_GP1_PD 0x2000 /* GP1_PD */
+#define WM8915_GP1_PD_MASK 0x2000 /* GP1_PD */
+#define WM8915_GP1_PD_SHIFT 13 /* GP1_PD */
+#define WM8915_GP1_PD_WIDTH 1 /* GP1_PD */
+#define WM8915_GP1_POL 0x0400 /* GP1_POL */
+#define WM8915_GP1_POL_MASK 0x0400 /* GP1_POL */
+#define WM8915_GP1_POL_SHIFT 10 /* GP1_POL */
+#define WM8915_GP1_POL_WIDTH 1 /* GP1_POL */
+#define WM8915_GP1_OP_CFG 0x0200 /* GP1_OP_CFG */
+#define WM8915_GP1_OP_CFG_MASK 0x0200 /* GP1_OP_CFG */
+#define WM8915_GP1_OP_CFG_SHIFT 9 /* GP1_OP_CFG */
+#define WM8915_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */
+#define WM8915_GP1_DB 0x0100 /* GP1_DB */
+#define WM8915_GP1_DB_MASK 0x0100 /* GP1_DB */
+#define WM8915_GP1_DB_SHIFT 8 /* GP1_DB */
+#define WM8915_GP1_DB_WIDTH 1 /* GP1_DB */
+#define WM8915_GP1_LVL 0x0040 /* GP1_LVL */
+#define WM8915_GP1_LVL_MASK 0x0040 /* GP1_LVL */
+#define WM8915_GP1_LVL_SHIFT 6 /* GP1_LVL */
+#define WM8915_GP1_LVL_WIDTH 1 /* GP1_LVL */
+#define WM8915_GP1_FN_MASK 0x000F /* GP1_FN - [3:0] */
+#define WM8915_GP1_FN_SHIFT 0 /* GP1_FN - [3:0] */
+#define WM8915_GP1_FN_WIDTH 4 /* GP1_FN - [3:0] */
+
+/*
+ * R1793 (0x701) - GPIO 2
+ */
+#define WM8915_GP2_DIR 0x8000 /* GP2_DIR */
+#define WM8915_GP2_DIR_MASK 0x8000 /* GP2_DIR */
+#define WM8915_GP2_DIR_SHIFT 15 /* GP2_DIR */
+#define WM8915_GP2_DIR_WIDTH 1 /* GP2_DIR */
+#define WM8915_GP2_PU 0x4000 /* GP2_PU */
+#define WM8915_GP2_PU_MASK 0x4000 /* GP2_PU */
+#define WM8915_GP2_PU_SHIFT 14 /* GP2_PU */
+#define WM8915_GP2_PU_WIDTH 1 /* GP2_PU */
+#define WM8915_GP2_PD 0x2000 /* GP2_PD */
+#define WM8915_GP2_PD_MASK 0x2000 /* GP2_PD */
+#define WM8915_GP2_PD_SHIFT 13 /* GP2_PD */
+#define WM8915_GP2_PD_WIDTH 1 /* GP2_PD */
+#define WM8915_GP2_POL 0x0400 /* GP2_POL */
+#define WM8915_GP2_POL_MASK 0x0400 /* GP2_POL */
+#define WM8915_GP2_POL_SHIFT 10 /* GP2_POL */
+#define WM8915_GP2_POL_WIDTH 1 /* GP2_POL */
+#define WM8915_GP2_OP_CFG 0x0200 /* GP2_OP_CFG */
+#define WM8915_GP2_OP_CFG_MASK 0x0200 /* GP2_OP_CFG */
+#define WM8915_GP2_OP_CFG_SHIFT 9 /* GP2_OP_CFG */
+#define WM8915_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */
+#define WM8915_GP2_DB 0x0100 /* GP2_DB */
+#define WM8915_GP2_DB_MASK 0x0100 /* GP2_DB */
+#define WM8915_GP2_DB_SHIFT 8 /* GP2_DB */
+#define WM8915_GP2_DB_WIDTH 1 /* GP2_DB */
+#define WM8915_GP2_LVL 0x0040 /* GP2_LVL */
+#define WM8915_GP2_LVL_MASK 0x0040 /* GP2_LVL */
+#define WM8915_GP2_LVL_SHIFT 6 /* GP2_LVL */
+#define WM8915_GP2_LVL_WIDTH 1 /* GP2_LVL */
+#define WM8915_GP2_FN_MASK 0x000F /* GP2_FN - [3:0] */
+#define WM8915_GP2_FN_SHIFT 0 /* GP2_FN - [3:0] */
+#define WM8915_GP2_FN_WIDTH 4 /* GP2_FN - [3:0] */
+
+/*
+ * R1794 (0x702) - GPIO 3
+ */
+#define WM8915_GP3_DIR 0x8000 /* GP3_DIR */
+#define WM8915_GP3_DIR_MASK 0x8000 /* GP3_DIR */
+#define WM8915_GP3_DIR_SHIFT 15 /* GP3_DIR */
+#define WM8915_GP3_DIR_WIDTH 1 /* GP3_DIR */
+#define WM8915_GP3_PU 0x4000 /* GP3_PU */
+#define WM8915_GP3_PU_MASK 0x4000 /* GP3_PU */
+#define WM8915_GP3_PU_SHIFT 14 /* GP3_PU */
+#define WM8915_GP3_PU_WIDTH 1 /* GP3_PU */
+#define WM8915_GP3_PD 0x2000 /* GP3_PD */
+#define WM8915_GP3_PD_MASK 0x2000 /* GP3_PD */
+#define WM8915_GP3_PD_SHIFT 13 /* GP3_PD */
+#define WM8915_GP3_PD_WIDTH 1 /* GP3_PD */
+#define WM8915_GP3_POL 0x0400 /* GP3_POL */
+#define WM8915_GP3_POL_MASK 0x0400 /* GP3_POL */
+#define WM8915_GP3_POL_SHIFT 10 /* GP3_POL */
+#define WM8915_GP3_POL_WIDTH 1 /* GP3_POL */
+#define WM8915_GP3_OP_CFG 0x0200 /* GP3_OP_CFG */
+#define WM8915_GP3_OP_CFG_MASK 0x0200 /* GP3_OP_CFG */
+#define WM8915_GP3_OP_CFG_SHIFT 9 /* GP3_OP_CFG */
+#define WM8915_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */
+#define WM8915_GP3_DB 0x0100 /* GP3_DB */
+#define WM8915_GP3_DB_MASK 0x0100 /* GP3_DB */
+#define WM8915_GP3_DB_SHIFT 8 /* GP3_DB */
+#define WM8915_GP3_DB_WIDTH 1 /* GP3_DB */
+#define WM8915_GP3_LVL 0x0040 /* GP3_LVL */
+#define WM8915_GP3_LVL_MASK 0x0040 /* GP3_LVL */
+#define WM8915_GP3_LVL_SHIFT 6 /* GP3_LVL */
+#define WM8915_GP3_LVL_WIDTH 1 /* GP3_LVL */
+#define WM8915_GP3_FN_MASK 0x000F /* GP3_FN - [3:0] */
+#define WM8915_GP3_FN_SHIFT 0 /* GP3_FN - [3:0] */
+#define WM8915_GP3_FN_WIDTH 4 /* GP3_FN - [3:0] */
+
+/*
+ * R1795 (0x703) - GPIO 4
+ */
+#define WM8915_GP4_DIR 0x8000 /* GP4_DIR */
+#define WM8915_GP4_DIR_MASK 0x8000 /* GP4_DIR */
+#define WM8915_GP4_DIR_SHIFT 15 /* GP4_DIR */
+#define WM8915_GP4_DIR_WIDTH 1 /* GP4_DIR */
+#define WM8915_GP4_PU 0x4000 /* GP4_PU */
+#define WM8915_GP4_PU_MASK 0x4000 /* GP4_PU */
+#define WM8915_GP4_PU_SHIFT 14 /* GP4_PU */
+#define WM8915_GP4_PU_WIDTH 1 /* GP4_PU */
+#define WM8915_GP4_PD 0x2000 /* GP4_PD */
+#define WM8915_GP4_PD_MASK 0x2000 /* GP4_PD */
+#define WM8915_GP4_PD_SHIFT 13 /* GP4_PD */
+#define WM8915_GP4_PD_WIDTH 1 /* GP4_PD */
+#define WM8915_GP4_POL 0x0400 /* GP4_POL */
+#define WM8915_GP4_POL_MASK 0x0400 /* GP4_POL */
+#define WM8915_GP4_POL_SHIFT 10 /* GP4_POL */
+#define WM8915_GP4_POL_WIDTH 1 /* GP4_POL */
+#define WM8915_GP4_OP_CFG 0x0200 /* GP4_OP_CFG */
+#define WM8915_GP4_OP_CFG_MASK 0x0200 /* GP4_OP_CFG */
+#define WM8915_GP4_OP_CFG_SHIFT 9 /* GP4_OP_CFG */
+#define WM8915_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */
+#define WM8915_GP4_DB 0x0100 /* GP4_DB */
+#define WM8915_GP4_DB_MASK 0x0100 /* GP4_DB */
+#define WM8915_GP4_DB_SHIFT 8 /* GP4_DB */
+#define WM8915_GP4_DB_WIDTH 1 /* GP4_DB */
+#define WM8915_GP4_LVL 0x0040 /* GP4_LVL */
+#define WM8915_GP4_LVL_MASK 0x0040 /* GP4_LVL */
+#define WM8915_GP4_LVL_SHIFT 6 /* GP4_LVL */
+#define WM8915_GP4_LVL_WIDTH 1 /* GP4_LVL */
+#define WM8915_GP4_FN_MASK 0x000F /* GP4_FN - [3:0] */
+#define WM8915_GP4_FN_SHIFT 0 /* GP4_FN - [3:0] */
+#define WM8915_GP4_FN_WIDTH 4 /* GP4_FN - [3:0] */
+
+/*
+ * R1796 (0x704) - GPIO 5
+ */
+#define WM8915_GP5_DIR 0x8000 /* GP5_DIR */
+#define WM8915_GP5_DIR_MASK 0x8000 /* GP5_DIR */
+#define WM8915_GP5_DIR_SHIFT 15 /* GP5_DIR */
+#define WM8915_GP5_DIR_WIDTH 1 /* GP5_DIR */
+#define WM8915_GP5_PU 0x4000 /* GP5_PU */
+#define WM8915_GP5_PU_MASK 0x4000 /* GP5_PU */
+#define WM8915_GP5_PU_SHIFT 14 /* GP5_PU */
+#define WM8915_GP5_PU_WIDTH 1 /* GP5_PU */
+#define WM8915_GP5_PD 0x2000 /* GP5_PD */
+#define WM8915_GP5_PD_MASK 0x2000 /* GP5_PD */
+#define WM8915_GP5_PD_SHIFT 13 /* GP5_PD */
+#define WM8915_GP5_PD_WIDTH 1 /* GP5_PD */
+#define WM8915_GP5_POL 0x0400 /* GP5_POL */
+#define WM8915_GP5_POL_MASK 0x0400 /* GP5_POL */
+#define WM8915_GP5_POL_SHIFT 10 /* GP5_POL */
+#define WM8915_GP5_POL_WIDTH 1 /* GP5_POL */
+#define WM8915_GP5_OP_CFG 0x0200 /* GP5_OP_CFG */
+#define WM8915_GP5_OP_CFG_MASK 0x0200 /* GP5_OP_CFG */
+#define WM8915_GP5_OP_CFG_SHIFT 9 /* GP5_OP_CFG */
+#define WM8915_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */
+#define WM8915_GP5_DB 0x0100 /* GP5_DB */
+#define WM8915_GP5_DB_MASK 0x0100 /* GP5_DB */
+#define WM8915_GP5_DB_SHIFT 8 /* GP5_DB */
+#define WM8915_GP5_DB_WIDTH 1 /* GP5_DB */
+#define WM8915_GP5_LVL 0x0040 /* GP5_LVL */
+#define WM8915_GP5_LVL_MASK 0x0040 /* GP5_LVL */
+#define WM8915_GP5_LVL_SHIFT 6 /* GP5_LVL */
+#define WM8915_GP5_LVL_WIDTH 1 /* GP5_LVL */
+#define WM8915_GP5_FN_MASK 0x000F /* GP5_FN - [3:0] */
+#define WM8915_GP5_FN_SHIFT 0 /* GP5_FN - [3:0] */
+#define WM8915_GP5_FN_WIDTH 4 /* GP5_FN - [3:0] */
+
+/*
+ * R1824 (0x720) - Pull Control (1)
+ */
+#define WM8915_DMICDAT2_PD 0x1000 /* DMICDAT2_PD */
+#define WM8915_DMICDAT2_PD_MASK 0x1000 /* DMICDAT2_PD */
+#define WM8915_DMICDAT2_PD_SHIFT 12 /* DMICDAT2_PD */
+#define WM8915_DMICDAT2_PD_WIDTH 1 /* DMICDAT2_PD */
+#define WM8915_DMICDAT1_PD 0x0400 /* DMICDAT1_PD */
+#define WM8915_DMICDAT1_PD_MASK 0x0400 /* DMICDAT1_PD */
+#define WM8915_DMICDAT1_PD_SHIFT 10 /* DMICDAT1_PD */
+#define WM8915_DMICDAT1_PD_WIDTH 1 /* DMICDAT1_PD */
+#define WM8915_MCLK2_PU 0x0200 /* MCLK2_PU */
+#define WM8915_MCLK2_PU_MASK 0x0200 /* MCLK2_PU */
+#define WM8915_MCLK2_PU_SHIFT 9 /* MCLK2_PU */
+#define WM8915_MCLK2_PU_WIDTH 1 /* MCLK2_PU */
+#define WM8915_MCLK2_PD 0x0100 /* MCLK2_PD */
+#define WM8915_MCLK2_PD_MASK 0x0100 /* MCLK2_PD */
+#define WM8915_MCLK2_PD_SHIFT 8 /* MCLK2_PD */
+#define WM8915_MCLK2_PD_WIDTH 1 /* MCLK2_PD */
+#define WM8915_MCLK1_PU 0x0080 /* MCLK1_PU */
+#define WM8915_MCLK1_PU_MASK 0x0080 /* MCLK1_PU */
+#define WM8915_MCLK1_PU_SHIFT 7 /* MCLK1_PU */
+#define WM8915_MCLK1_PU_WIDTH 1 /* MCLK1_PU */
+#define WM8915_MCLK1_PD 0x0040 /* MCLK1_PD */
+#define WM8915_MCLK1_PD_MASK 0x0040 /* MCLK1_PD */
+#define WM8915_MCLK1_PD_SHIFT 6 /* MCLK1_PD */
+#define WM8915_MCLK1_PD_WIDTH 1 /* MCLK1_PD */
+#define WM8915_DACDAT1_PU 0x0020 /* DACDAT1_PU */
+#define WM8915_DACDAT1_PU_MASK 0x0020 /* DACDAT1_PU */
+#define WM8915_DACDAT1_PU_SHIFT 5 /* DACDAT1_PU */
+#define WM8915_DACDAT1_PU_WIDTH 1 /* DACDAT1_PU */
+#define WM8915_DACDAT1_PD 0x0010 /* DACDAT1_PD */
+#define WM8915_DACDAT1_PD_MASK 0x0010 /* DACDAT1_PD */
+#define WM8915_DACDAT1_PD_SHIFT 4 /* DACDAT1_PD */
+#define WM8915_DACDAT1_PD_WIDTH 1 /* DACDAT1_PD */
+#define WM8915_DACLRCLK1_PU 0x0008 /* DACLRCLK1_PU */
+#define WM8915_DACLRCLK1_PU_MASK 0x0008 /* DACLRCLK1_PU */
+#define WM8915_DACLRCLK1_PU_SHIFT 3 /* DACLRCLK1_PU */
+#define WM8915_DACLRCLK1_PU_WIDTH 1 /* DACLRCLK1_PU */
+#define WM8915_DACLRCLK1_PD 0x0004 /* DACLRCLK1_PD */
+#define WM8915_DACLRCLK1_PD_MASK 0x0004 /* DACLRCLK1_PD */
+#define WM8915_DACLRCLK1_PD_SHIFT 2 /* DACLRCLK1_PD */
+#define WM8915_DACLRCLK1_PD_WIDTH 1 /* DACLRCLK1_PD */
+#define WM8915_BCLK1_PU 0x0002 /* BCLK1_PU */
+#define WM8915_BCLK1_PU_MASK 0x0002 /* BCLK1_PU */
+#define WM8915_BCLK1_PU_SHIFT 1 /* BCLK1_PU */
+#define WM8915_BCLK1_PU_WIDTH 1 /* BCLK1_PU */
+#define WM8915_BCLK1_PD 0x0001 /* BCLK1_PD */
+#define WM8915_BCLK1_PD_MASK 0x0001 /* BCLK1_PD */
+#define WM8915_BCLK1_PD_SHIFT 0 /* BCLK1_PD */
+#define WM8915_BCLK1_PD_WIDTH 1 /* BCLK1_PD */
+
+/*
+ * R1825 (0x721) - Pull Control (2)
+ */
+#define WM8915_LDO1ENA_PD 0x0100 /* LDO1ENA_PD */
+#define WM8915_LDO1ENA_PD_MASK 0x0100 /* LDO1ENA_PD */
+#define WM8915_LDO1ENA_PD_SHIFT 8 /* LDO1ENA_PD */
+#define WM8915_LDO1ENA_PD_WIDTH 1 /* LDO1ENA_PD */
+#define WM8915_ADDR_PD 0x0040 /* ADDR_PD */
+#define WM8915_ADDR_PD_MASK 0x0040 /* ADDR_PD */
+#define WM8915_ADDR_PD_SHIFT 6 /* ADDR_PD */
+#define WM8915_ADDR_PD_WIDTH 1 /* ADDR_PD */
+#define WM8915_DACDAT2_PU 0x0020 /* DACDAT2_PU */
+#define WM8915_DACDAT2_PU_MASK 0x0020 /* DACDAT2_PU */
+#define WM8915_DACDAT2_PU_SHIFT 5 /* DACDAT2_PU */
+#define WM8915_DACDAT2_PU_WIDTH 1 /* DACDAT2_PU */
+#define WM8915_DACDAT2_PD 0x0010 /* DACDAT2_PD */
+#define WM8915_DACDAT2_PD_MASK 0x0010 /* DACDAT2_PD */
+#define WM8915_DACDAT2_PD_SHIFT 4 /* DACDAT2_PD */
+#define WM8915_DACDAT2_PD_WIDTH 1 /* DACDAT2_PD */
+#define WM8915_DACLRCLK2_PU 0x0008 /* DACLRCLK2_PU */
+#define WM8915_DACLRCLK2_PU_MASK 0x0008 /* DACLRCLK2_PU */
+#define WM8915_DACLRCLK2_PU_SHIFT 3 /* DACLRCLK2_PU */
+#define WM8915_DACLRCLK2_PU_WIDTH 1 /* DACLRCLK2_PU */
+#define WM8915_DACLRCLK2_PD 0x0004 /* DACLRCLK2_PD */
+#define WM8915_DACLRCLK2_PD_MASK 0x0004 /* DACLRCLK2_PD */
+#define WM8915_DACLRCLK2_PD_SHIFT 2 /* DACLRCLK2_PD */
+#define WM8915_DACLRCLK2_PD_WIDTH 1 /* DACLRCLK2_PD */
+#define WM8915_BCLK2_PU 0x0002 /* BCLK2_PU */
+#define WM8915_BCLK2_PU_MASK 0x0002 /* BCLK2_PU */
+#define WM8915_BCLK2_PU_SHIFT 1 /* BCLK2_PU */
+#define WM8915_BCLK2_PU_WIDTH 1 /* BCLK2_PU */
+#define WM8915_BCLK2_PD 0x0001 /* BCLK2_PD */
+#define WM8915_BCLK2_PD_MASK 0x0001 /* BCLK2_PD */
+#define WM8915_BCLK2_PD_SHIFT 0 /* BCLK2_PD */
+#define WM8915_BCLK2_PD_WIDTH 1 /* BCLK2_PD */
+
+/*
+ * R1840 (0x730) - Interrupt Status 1
+ */
+#define WM8915_GP5_EINT 0x0010 /* GP5_EINT */
+#define WM8915_GP5_EINT_MASK 0x0010 /* GP5_EINT */
+#define WM8915_GP5_EINT_SHIFT 4 /* GP5_EINT */
+#define WM8915_GP5_EINT_WIDTH 1 /* GP5_EINT */
+#define WM8915_GP4_EINT 0x0008 /* GP4_EINT */
+#define WM8915_GP4_EINT_MASK 0x0008 /* GP4_EINT */
+#define WM8915_GP4_EINT_SHIFT 3 /* GP4_EINT */
+#define WM8915_GP4_EINT_WIDTH 1 /* GP4_EINT */
+#define WM8915_GP3_EINT 0x0004 /* GP3_EINT */
+#define WM8915_GP3_EINT_MASK 0x0004 /* GP3_EINT */
+#define WM8915_GP3_EINT_SHIFT 2 /* GP3_EINT */
+#define WM8915_GP3_EINT_WIDTH 1 /* GP3_EINT */
+#define WM8915_GP2_EINT 0x0002 /* GP2_EINT */
+#define WM8915_GP2_EINT_MASK 0x0002 /* GP2_EINT */
+#define WM8915_GP2_EINT_SHIFT 1 /* GP2_EINT */
+#define WM8915_GP2_EINT_WIDTH 1 /* GP2_EINT */
+#define WM8915_GP1_EINT 0x0001 /* GP1_EINT */
+#define WM8915_GP1_EINT_MASK 0x0001 /* GP1_EINT */
+#define WM8915_GP1_EINT_SHIFT 0 /* GP1_EINT */
+#define WM8915_GP1_EINT_WIDTH 1 /* GP1_EINT */
+
+/*
+ * R1841 (0x731) - Interrupt Status 2
+ */
+#define WM8915_DCS_DONE_23_EINT 0x1000 /* DCS_DONE_23_EINT */
+#define WM8915_DCS_DONE_23_EINT_MASK 0x1000 /* DCS_DONE_23_EINT */
+#define WM8915_DCS_DONE_23_EINT_SHIFT 12 /* DCS_DONE_23_EINT */
+#define WM8915_DCS_DONE_23_EINT_WIDTH 1 /* DCS_DONE_23_EINT */
+#define WM8915_DCS_DONE_01_EINT 0x0800 /* DCS_DONE_01_EINT */
+#define WM8915_DCS_DONE_01_EINT_MASK 0x0800 /* DCS_DONE_01_EINT */
+#define WM8915_DCS_DONE_01_EINT_SHIFT 11 /* DCS_DONE_01_EINT */
+#define WM8915_DCS_DONE_01_EINT_WIDTH 1 /* DCS_DONE_01_EINT */
+#define WM8915_WSEQ_DONE_EINT 0x0400 /* WSEQ_DONE_EINT */
+#define WM8915_WSEQ_DONE_EINT_MASK 0x0400 /* WSEQ_DONE_EINT */
+#define WM8915_WSEQ_DONE_EINT_SHIFT 10 /* WSEQ_DONE_EINT */
+#define WM8915_WSEQ_DONE_EINT_WIDTH 1 /* WSEQ_DONE_EINT */
+#define WM8915_FIFOS_ERR_EINT 0x0200 /* FIFOS_ERR_EINT */
+#define WM8915_FIFOS_ERR_EINT_MASK 0x0200 /* FIFOS_ERR_EINT */
+#define WM8915_FIFOS_ERR_EINT_SHIFT 9 /* FIFOS_ERR_EINT */
+#define WM8915_FIFOS_ERR_EINT_WIDTH 1 /* FIFOS_ERR_EINT */
+#define WM8915_DSP2DRC_SIG_DET_EINT 0x0080 /* DSP2DRC_SIG_DET_EINT */
+#define WM8915_DSP2DRC_SIG_DET_EINT_MASK 0x0080 /* DSP2DRC_SIG_DET_EINT */
+#define WM8915_DSP2DRC_SIG_DET_EINT_SHIFT 7 /* DSP2DRC_SIG_DET_EINT */
+#define WM8915_DSP2DRC_SIG_DET_EINT_WIDTH 1 /* DSP2DRC_SIG_DET_EINT */
+#define WM8915_DSP1DRC_SIG_DET_EINT 0x0040 /* DSP1DRC_SIG_DET_EINT */
+#define WM8915_DSP1DRC_SIG_DET_EINT_MASK 0x0040 /* DSP1DRC_SIG_DET_EINT */
+#define WM8915_DSP1DRC_SIG_DET_EINT_SHIFT 6 /* DSP1DRC_SIG_DET_EINT */
+#define WM8915_DSP1DRC_SIG_DET_EINT_WIDTH 1 /* DSP1DRC_SIG_DET_EINT */
+#define WM8915_FLL_SW_CLK_DONE_EINT 0x0008 /* FLL_SW_CLK_DONE_EINT */
+#define WM8915_FLL_SW_CLK_DONE_EINT_MASK 0x0008 /* FLL_SW_CLK_DONE_EINT */
+#define WM8915_FLL_SW_CLK_DONE_EINT_SHIFT 3 /* FLL_SW_CLK_DONE_EINT */
+#define WM8915_FLL_SW_CLK_DONE_EINT_WIDTH 1 /* FLL_SW_CLK_DONE_EINT */
+#define WM8915_FLL_LOCK_EINT 0x0004 /* FLL_LOCK_EINT */
+#define WM8915_FLL_LOCK_EINT_MASK 0x0004 /* FLL_LOCK_EINT */
+#define WM8915_FLL_LOCK_EINT_SHIFT 2 /* FLL_LOCK_EINT */
+#define WM8915_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */
+#define WM8915_HP_DONE_EINT 0x0002 /* HP_DONE_EINT */
+#define WM8915_HP_DONE_EINT_MASK 0x0002 /* HP_DONE_EINT */
+#define WM8915_HP_DONE_EINT_SHIFT 1 /* HP_DONE_EINT */
+#define WM8915_HP_DONE_EINT_WIDTH 1 /* HP_DONE_EINT */
+#define WM8915_MICD_EINT 0x0001 /* MICD_EINT */
+#define WM8915_MICD_EINT_MASK 0x0001 /* MICD_EINT */
+#define WM8915_MICD_EINT_SHIFT 0 /* MICD_EINT */
+#define WM8915_MICD_EINT_WIDTH 1 /* MICD_EINT */
+
+/*
+ * R1842 (0x732) - Interrupt Raw Status 2
+ */
+#define WM8915_DCS_DONE_23_STS 0x1000 /* DCS_DONE_23_STS */
+#define WM8915_DCS_DONE_23_STS_MASK 0x1000 /* DCS_DONE_23_STS */
+#define WM8915_DCS_DONE_23_STS_SHIFT 12 /* DCS_DONE_23_STS */
+#define WM8915_DCS_DONE_23_STS_WIDTH 1 /* DCS_DONE_23_STS */
+#define WM8915_DCS_DONE_01_STS 0x0800 /* DCS_DONE_01_STS */
+#define WM8915_DCS_DONE_01_STS_MASK 0x0800 /* DCS_DONE_01_STS */
+#define WM8915_DCS_DONE_01_STS_SHIFT 11 /* DCS_DONE_01_STS */
+#define WM8915_DCS_DONE_01_STS_WIDTH 1 /* DCS_DONE_01_STS */
+#define WM8915_WSEQ_DONE_STS 0x0400 /* WSEQ_DONE_STS */
+#define WM8915_WSEQ_DONE_STS_MASK 0x0400 /* WSEQ_DONE_STS */
+#define WM8915_WSEQ_DONE_STS_SHIFT 10 /* WSEQ_DONE_STS */
+#define WM8915_WSEQ_DONE_STS_WIDTH 1 /* WSEQ_DONE_STS */
+#define WM8915_FIFOS_ERR_STS 0x0200 /* FIFOS_ERR_STS */
+#define WM8915_FIFOS_ERR_STS_MASK 0x0200 /* FIFOS_ERR_STS */
+#define WM8915_FIFOS_ERR_STS_SHIFT 9 /* FIFOS_ERR_STS */
+#define WM8915_FIFOS_ERR_STS_WIDTH 1 /* FIFOS_ERR_STS */
+#define WM8915_DSP2DRC_SIG_DET_STS 0x0080 /* DSP2DRC_SIG_DET_STS */
+#define WM8915_DSP2DRC_SIG_DET_STS_MASK 0x0080 /* DSP2DRC_SIG_DET_STS */
+#define WM8915_DSP2DRC_SIG_DET_STS_SHIFT 7 /* DSP2DRC_SIG_DET_STS */
+#define WM8915_DSP2DRC_SIG_DET_STS_WIDTH 1 /* DSP2DRC_SIG_DET_STS */
+#define WM8915_DSP1DRC_SIG_DET_STS 0x0040 /* DSP1DRC_SIG_DET_STS */
+#define WM8915_DSP1DRC_SIG_DET_STS_MASK 0x0040 /* DSP1DRC_SIG_DET_STS */
+#define WM8915_DSP1DRC_SIG_DET_STS_SHIFT 6 /* DSP1DRC_SIG_DET_STS */
+#define WM8915_DSP1DRC_SIG_DET_STS_WIDTH 1 /* DSP1DRC_SIG_DET_STS */
+#define WM8915_FLL_LOCK_STS 0x0004 /* FLL_LOCK_STS */
+#define WM8915_FLL_LOCK_STS_MASK 0x0004 /* FLL_LOCK_STS */
+#define WM8915_FLL_LOCK_STS_SHIFT 2 /* FLL_LOCK_STS */
+#define WM8915_FLL_LOCK_STS_WIDTH 1 /* FLL_LOCK_STS */
+
+/*
+ * R1848 (0x738) - Interrupt Status 1 Mask
+ */
+#define WM8915_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */
+#define WM8915_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */
+#define WM8915_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */
+#define WM8915_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */
+#define WM8915_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */
+#define WM8915_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */
+#define WM8915_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */
+#define WM8915_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */
+#define WM8915_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */
+#define WM8915_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */
+#define WM8915_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */
+#define WM8915_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */
+#define WM8915_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */
+#define WM8915_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */
+#define WM8915_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */
+#define WM8915_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */
+#define WM8915_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */
+#define WM8915_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */
+#define WM8915_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */
+#define WM8915_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */
+
+/*
+ * R1849 (0x739) - Interrupt Status 2 Mask
+ */
+#define WM8915_IM_DCS_DONE_23_EINT 0x1000 /* IM_DCS_DONE_23_EINT */
+#define WM8915_IM_DCS_DONE_23_EINT_MASK 0x1000 /* IM_DCS_DONE_23_EINT */
+#define WM8915_IM_DCS_DONE_23_EINT_SHIFT 12 /* IM_DCS_DONE_23_EINT */
+#define WM8915_IM_DCS_DONE_23_EINT_WIDTH 1 /* IM_DCS_DONE_23_EINT */
+#define WM8915_IM_DCS_DONE_01_EINT 0x0800 /* IM_DCS_DONE_01_EINT */
+#define WM8915_IM_DCS_DONE_01_EINT_MASK 0x0800 /* IM_DCS_DONE_01_EINT */
+#define WM8915_IM_DCS_DONE_01_EINT_SHIFT 11 /* IM_DCS_DONE_01_EINT */
+#define WM8915_IM_DCS_DONE_01_EINT_WIDTH 1 /* IM_DCS_DONE_01_EINT */
+#define WM8915_IM_WSEQ_DONE_EINT 0x0400 /* IM_WSEQ_DONE_EINT */
+#define WM8915_IM_WSEQ_DONE_EINT_MASK 0x0400 /* IM_WSEQ_DONE_EINT */
+#define WM8915_IM_WSEQ_DONE_EINT_SHIFT 10 /* IM_WSEQ_DONE_EINT */
+#define WM8915_IM_WSEQ_DONE_EINT_WIDTH 1 /* IM_WSEQ_DONE_EINT */
+#define WM8915_IM_FIFOS_ERR_EINT 0x0200 /* IM_FIFOS_ERR_EINT */
+#define WM8915_IM_FIFOS_ERR_EINT_MASK 0x0200 /* IM_FIFOS_ERR_EINT */
+#define WM8915_IM_FIFOS_ERR_EINT_SHIFT 9 /* IM_FIFOS_ERR_EINT */
+#define WM8915_IM_FIFOS_ERR_EINT_WIDTH 1 /* IM_FIFOS_ERR_EINT */
+#define WM8915_IM_DSP2DRC_SIG_DET_EINT 0x0080 /* IM_DSP2DRC_SIG_DET_EINT */
+#define WM8915_IM_DSP2DRC_SIG_DET_EINT_MASK 0x0080 /* IM_DSP2DRC_SIG_DET_EINT */
+#define WM8915_IM_DSP2DRC_SIG_DET_EINT_SHIFT 7 /* IM_DSP2DRC_SIG_DET_EINT */
+#define WM8915_IM_DSP2DRC_SIG_DET_EINT_WIDTH 1 /* IM_DSP2DRC_SIG_DET_EINT */
+#define WM8915_IM_DSP1DRC_SIG_DET_EINT 0x0040 /* IM_DSP1DRC_SIG_DET_EINT */
+#define WM8915_IM_DSP1DRC_SIG_DET_EINT_MASK 0x0040 /* IM_DSP1DRC_SIG_DET_EINT */
+#define WM8915_IM_DSP1DRC_SIG_DET_EINT_SHIFT 6 /* IM_DSP1DRC_SIG_DET_EINT */
+#define WM8915_IM_DSP1DRC_SIG_DET_EINT_WIDTH 1 /* IM_DSP1DRC_SIG_DET_EINT */
+#define WM8915_IM_FLL_SW_CLK_DONE_EINT 0x0008 /* IM_FLL_SW_CLK_DONE_EINT */
+#define WM8915_IM_FLL_SW_CLK_DONE_EINT_MASK 0x0008 /* IM_FLL_SW_CLK_DONE_EINT */
+#define WM8915_IM_FLL_SW_CLK_DONE_EINT_SHIFT 3 /* IM_FLL_SW_CLK_DONE_EINT */
+#define WM8915_IM_FLL_SW_CLK_DONE_EINT_WIDTH 1 /* IM_FLL_SW_CLK_DONE_EINT */
+#define WM8915_IM_FLL_LOCK_EINT 0x0004 /* IM_FLL_LOCK_EINT */
+#define WM8915_IM_FLL_LOCK_EINT_MASK 0x0004 /* IM_FLL_LOCK_EINT */
+#define WM8915_IM_FLL_LOCK_EINT_SHIFT 2 /* IM_FLL_LOCK_EINT */
+#define WM8915_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */
+#define WM8915_IM_HP_DONE_EINT 0x0002 /* IM_HP_DONE_EINT */
+#define WM8915_IM_HP_DONE_EINT_MASK 0x0002 /* IM_HP_DONE_EINT */
+#define WM8915_IM_HP_DONE_EINT_SHIFT 1 /* IM_HP_DONE_EINT */
+#define WM8915_IM_HP_DONE_EINT_WIDTH 1 /* IM_HP_DONE_EINT */
+#define WM8915_IM_MICD_EINT 0x0001 /* IM_MICD_EINT */
+#define WM8915_IM_MICD_EINT_MASK 0x0001 /* IM_MICD_EINT */
+#define WM8915_IM_MICD_EINT_SHIFT 0 /* IM_MICD_EINT */
+#define WM8915_IM_MICD_EINT_WIDTH 1 /* IM_MICD_EINT */
+
+/*
+ * R1856 (0x740) - Interrupt Control
+ */
+#define WM8915_IM_IRQ 0x0001 /* IM_IRQ */
+#define WM8915_IM_IRQ_MASK 0x0001 /* IM_IRQ */
+#define WM8915_IM_IRQ_SHIFT 0 /* IM_IRQ */
+#define WM8915_IM_IRQ_WIDTH 1 /* IM_IRQ */
+
+/*
+ * R2048 (0x800) - Left PDM Speaker
+ */
+#define WM8915_SPKL_ENA 0x0010 /* SPKL_ENA */
+#define WM8915_SPKL_ENA_MASK 0x0010 /* SPKL_ENA */
+#define WM8915_SPKL_ENA_SHIFT 4 /* SPKL_ENA */
+#define WM8915_SPKL_ENA_WIDTH 1 /* SPKL_ENA */
+#define WM8915_SPKL_MUTE 0x0008 /* SPKL_MUTE */
+#define WM8915_SPKL_MUTE_MASK 0x0008 /* SPKL_MUTE */
+#define WM8915_SPKL_MUTE_SHIFT 3 /* SPKL_MUTE */
+#define WM8915_SPKL_MUTE_WIDTH 1 /* SPKL_MUTE */
+#define WM8915_SPKL_MUTE_ZC 0x0004 /* SPKL_MUTE_ZC */
+#define WM8915_SPKL_MUTE_ZC_MASK 0x0004 /* SPKL_MUTE_ZC */
+#define WM8915_SPKL_MUTE_ZC_SHIFT 2 /* SPKL_MUTE_ZC */
+#define WM8915_SPKL_MUTE_ZC_WIDTH 1 /* SPKL_MUTE_ZC */
+#define WM8915_SPKL_SRC_MASK 0x0003 /* SPKL_SRC - [1:0] */
+#define WM8915_SPKL_SRC_SHIFT 0 /* SPKL_SRC - [1:0] */
+#define WM8915_SPKL_SRC_WIDTH 2 /* SPKL_SRC - [1:0] */
+
+/*
+ * R2049 (0x801) - Right PDM Speaker
+ */
+#define WM8915_SPKR_ENA 0x0010 /* SPKR_ENA */
+#define WM8915_SPKR_ENA_MASK 0x0010 /* SPKR_ENA */
+#define WM8915_SPKR_ENA_SHIFT 4 /* SPKR_ENA */
+#define WM8915_SPKR_ENA_WIDTH 1 /* SPKR_ENA */
+#define WM8915_SPKR_MUTE 0x0008 /* SPKR_MUTE */
+#define WM8915_SPKR_MUTE_MASK 0x0008 /* SPKR_MUTE */
+#define WM8915_SPKR_MUTE_SHIFT 3 /* SPKR_MUTE */
+#define WM8915_SPKR_MUTE_WIDTH 1 /* SPKR_MUTE */
+#define WM8915_SPKR_MUTE_ZC 0x0004 /* SPKR_MUTE_ZC */
+#define WM8915_SPKR_MUTE_ZC_MASK 0x0004 /* SPKR_MUTE_ZC */
+#define WM8915_SPKR_MUTE_ZC_SHIFT 2 /* SPKR_MUTE_ZC */
+#define WM8915_SPKR_MUTE_ZC_WIDTH 1 /* SPKR_MUTE_ZC */
+#define WM8915_SPKR_SRC_MASK 0x0003 /* SPKR_SRC - [1:0] */
+#define WM8915_SPKR_SRC_SHIFT 0 /* SPKR_SRC - [1:0] */
+#define WM8915_SPKR_SRC_WIDTH 2 /* SPKR_SRC - [1:0] */
+
+/*
+ * R2050 (0x802) - PDM Speaker Mute Sequence
+ */
+#define WM8915_SPK_MUTE_ENDIAN 0x0100 /* SPK_MUTE_ENDIAN */
+#define WM8915_SPK_MUTE_ENDIAN_MASK 0x0100 /* SPK_MUTE_ENDIAN */
+#define WM8915_SPK_MUTE_ENDIAN_SHIFT 8 /* SPK_MUTE_ENDIAN */
+#define WM8915_SPK_MUTE_ENDIAN_WIDTH 1 /* SPK_MUTE_ENDIAN */
+#define WM8915_SPK_MUTE_SEQ1_MASK 0x00FF /* SPK_MUTE_SEQ1 - [7:0] */
+#define WM8915_SPK_MUTE_SEQ1_SHIFT 0 /* SPK_MUTE_SEQ1 - [7:0] */
+#define WM8915_SPK_MUTE_SEQ1_WIDTH 8 /* SPK_MUTE_SEQ1 - [7:0] */
+
+/*
+ * R2051 (0x803) - PDM Speaker Volume
+ */
+#define WM8915_SPKR_VOL_MASK 0x00F0 /* SPKR_VOL - [7:4] */
+#define WM8915_SPKR_VOL_SHIFT 4 /* SPKR_VOL - [7:4] */
+#define WM8915_SPKR_VOL_WIDTH 4 /* SPKR_VOL - [7:4] */
+#define WM8915_SPKL_VOL_MASK 0x000F /* SPKL_VOL - [3:0] */
+#define WM8915_SPKL_VOL_SHIFT 0 /* SPKL_VOL - [3:0] */
+#define WM8915_SPKL_VOL_WIDTH 4 /* SPKL_VOL - [3:0] */
+
+#endif
diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c
new file mode 100644
index 00000000..d4ecb3f2
--- /dev/null
+++ b/sound/soc/codecs/wm8940.c
@@ -0,0 +1,828 @@
+/*
+ * wm8940.c -- WM8940 ALSA Soc Audio driver
+ *
+ * Author: Jonathan Cameron <jic23@cam.ac.uk>
+ *
+ * Based on wm8510.c
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Not currently handled:
+ * Notch filter control
+ * AUXMode (inverting vs mixer)
+ * No means to obtain current gain if alc enabled.
+ * No use made of gpio
+ * Fast VMID discharge for power down
+ * Soft Start
+ * DLR and ALR Swaps not enabled
+ * Digital Sidetone not supported
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8940.h"
+
+struct wm8940_priv {
+ unsigned int sysclk;
+ enum snd_soc_control_type control_type;
+ void *control_data;
+};
+
+static u16 wm8940_reg_defaults[] = {
+ 0x8940, /* Soft Reset */
+ 0x0000, /* Power 1 */
+ 0x0000, /* Power 2 */
+ 0x0000, /* Power 3 */
+ 0x0010, /* Interface Control */
+ 0x0000, /* Companding Control */
+ 0x0140, /* Clock Control */
+ 0x0000, /* Additional Controls */
+ 0x0000, /* GPIO Control */
+ 0x0002, /* Auto Increment Control */
+ 0x0000, /* DAC Control */
+ 0x00FF, /* DAC Volume */
+ 0,
+ 0,
+ 0x0100, /* ADC Control */
+ 0x00FF, /* ADC Volume */
+ 0x0000, /* Notch Filter 1 Control 1 */
+ 0x0000, /* Notch Filter 1 Control 2 */
+ 0x0000, /* Notch Filter 2 Control 1 */
+ 0x0000, /* Notch Filter 2 Control 2 */
+ 0x0000, /* Notch Filter 3 Control 1 */
+ 0x0000, /* Notch Filter 3 Control 2 */
+ 0x0000, /* Notch Filter 4 Control 1 */
+ 0x0000, /* Notch Filter 4 Control 2 */
+ 0x0032, /* DAC Limit Control 1 */
+ 0x0000, /* DAC Limit Control 2 */
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0x0038, /* ALC Control 1 */
+ 0x000B, /* ALC Control 2 */
+ 0x0032, /* ALC Control 3 */
+ 0x0000, /* Noise Gate */
+ 0x0041, /* PLLN */
+ 0x000C, /* PLLK1 */
+ 0x0093, /* PLLK2 */
+ 0x00E9, /* PLLK3 */
+ 0,
+ 0,
+ 0x0030, /* ALC Control 4 */
+ 0,
+ 0x0002, /* Input Control */
+ 0x0050, /* PGA Gain */
+ 0,
+ 0x0002, /* ADC Boost Control */
+ 0,
+ 0x0002, /* Output Control */
+ 0x0000, /* Speaker Mixer Control */
+ 0,
+ 0,
+ 0,
+ 0x0079, /* Speaker Volume */
+ 0,
+ 0x0000, /* Mono Mixer Control */
+};
+
+static const char *wm8940_companding[] = { "Off", "NC", "u-law", "A-law" };
+static const struct soc_enum wm8940_adc_companding_enum
+= SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 1, 4, wm8940_companding);
+static const struct soc_enum wm8940_dac_companding_enum
+= SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 3, 4, wm8940_companding);
+
+static const char *wm8940_alc_mode_text[] = {"ALC", "Limiter"};
+static const struct soc_enum wm8940_alc_mode_enum
+= SOC_ENUM_SINGLE(WM8940_ALC3, 8, 2, wm8940_alc_mode_text);
+
+static const char *wm8940_mic_bias_level_text[] = {"0.9", "0.65"};
+static const struct soc_enum wm8940_mic_bias_level_enum
+= SOC_ENUM_SINGLE(WM8940_INPUTCTL, 8, 2, wm8940_mic_bias_level_text);
+
+static const char *wm8940_filter_mode_text[] = {"Audio", "Application"};
+static const struct soc_enum wm8940_filter_mode_enum
+= SOC_ENUM_SINGLE(WM8940_ADC, 7, 2, wm8940_filter_mode_text);
+
+static DECLARE_TLV_DB_SCALE(wm8940_spk_vol_tlv, -5700, 100, 1);
+static DECLARE_TLV_DB_SCALE(wm8940_att_tlv, -1000, 1000, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_pga_vol_tlv, -1200, 75, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_alc_min_tlv, -1200, 600, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_alc_max_tlv, 675, 600, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_alc_tar_tlv, -2250, 50, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_lim_boost_tlv, 0, 100, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_lim_thresh_tlv, -600, 100, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_adc_tlv, -12750, 50, 1);
+static DECLARE_TLV_DB_SCALE(wm8940_capture_boost_vol_tlv, 0, 2000, 0);
+
+static const struct snd_kcontrol_new wm8940_snd_controls[] = {
+ SOC_SINGLE("Digital Loopback Switch", WM8940_COMPANDINGCTL,
+ 6, 1, 0),
+ SOC_ENUM("DAC Companding", wm8940_dac_companding_enum),
+ SOC_ENUM("ADC Companding", wm8940_adc_companding_enum),
+
+ SOC_ENUM("ALC Mode", wm8940_alc_mode_enum),
+ SOC_SINGLE("ALC Switch", WM8940_ALC1, 8, 1, 0),
+ SOC_SINGLE_TLV("ALC Capture Max Gain", WM8940_ALC1,
+ 3, 7, 1, wm8940_alc_max_tlv),
+ SOC_SINGLE_TLV("ALC Capture Min Gain", WM8940_ALC1,
+ 0, 7, 0, wm8940_alc_min_tlv),
+ SOC_SINGLE_TLV("ALC Capture Target", WM8940_ALC2,
+ 0, 14, 0, wm8940_alc_tar_tlv),
+ SOC_SINGLE("ALC Capture Hold", WM8940_ALC2, 4, 10, 0),
+ SOC_SINGLE("ALC Capture Decay", WM8940_ALC3, 4, 10, 0),
+ SOC_SINGLE("ALC Capture Attach", WM8940_ALC3, 0, 10, 0),
+ SOC_SINGLE("ALC ZC Switch", WM8940_ALC4, 1, 1, 0),
+ SOC_SINGLE("ALC Capture Noise Gate Switch", WM8940_NOISEGATE,
+ 3, 1, 0),
+ SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8940_NOISEGATE,
+ 0, 7, 0),
+
+ SOC_SINGLE("DAC Playback Limiter Switch", WM8940_DACLIM1, 8, 1, 0),
+ SOC_SINGLE("DAC Playback Limiter Attack", WM8940_DACLIM1, 0, 9, 0),
+ SOC_SINGLE("DAC Playback Limiter Decay", WM8940_DACLIM1, 4, 11, 0),
+ SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8940_DACLIM2,
+ 4, 9, 1, wm8940_lim_thresh_tlv),
+ SOC_SINGLE_TLV("DAC Playback Limiter Boost", WM8940_DACLIM2,
+ 0, 12, 0, wm8940_lim_boost_tlv),
+
+ SOC_SINGLE("Capture PGA ZC Switch", WM8940_PGAGAIN, 7, 1, 0),
+ SOC_SINGLE_TLV("Capture PGA Volume", WM8940_PGAGAIN,
+ 0, 63, 0, wm8940_pga_vol_tlv),
+ SOC_SINGLE_TLV("Digital Playback Volume", WM8940_DACVOL,
+ 0, 255, 0, wm8940_adc_tlv),
+ SOC_SINGLE_TLV("Digital Capture Volume", WM8940_ADCVOL,
+ 0, 255, 0, wm8940_adc_tlv),
+ SOC_ENUM("Mic Bias Level", wm8940_mic_bias_level_enum),
+ SOC_SINGLE_TLV("Capture Boost Volue", WM8940_ADCBOOST,
+ 8, 1, 0, wm8940_capture_boost_vol_tlv),
+ SOC_SINGLE_TLV("Speaker Playback Volume", WM8940_SPKVOL,
+ 0, 63, 0, wm8940_spk_vol_tlv),
+ SOC_SINGLE("Speaker Playback Switch", WM8940_SPKVOL, 6, 1, 1),
+
+ SOC_SINGLE_TLV("Speaker Mixer Line Bypass Volume", WM8940_SPKVOL,
+ 8, 1, 1, wm8940_att_tlv),
+ SOC_SINGLE("Speaker Playback ZC Switch", WM8940_SPKVOL, 7, 1, 0),
+
+ SOC_SINGLE("Mono Out Switch", WM8940_MONOMIX, 6, 1, 1),
+ SOC_SINGLE_TLV("Mono Mixer Line Bypass Volume", WM8940_MONOMIX,
+ 7, 1, 1, wm8940_att_tlv),
+
+ SOC_SINGLE("High Pass Filter Switch", WM8940_ADC, 8, 1, 0),
+ SOC_ENUM("High Pass Filter Mode", wm8940_filter_mode_enum),
+ SOC_SINGLE("High Pass Filter Cut Off", WM8940_ADC, 4, 7, 0),
+ SOC_SINGLE("ADC Inversion Switch", WM8940_ADC, 0, 1, 0),
+ SOC_SINGLE("DAC Inversion Switch", WM8940_DAC, 0, 1, 0),
+ SOC_SINGLE("DAC Auto Mute Switch", WM8940_DAC, 2, 1, 0),
+ SOC_SINGLE("ZC Timeout Clock Switch", WM8940_ADDCNTRL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8940_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_SPKMIX, 1, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_SPKMIX, 5, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_SPKMIX, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8940_mono_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_MONOMIX, 1, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_MONOMIX, 2, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_MONOMIX, 0, 1, 0),
+};
+
+static DECLARE_TLV_DB_SCALE(wm8940_boost_vol_tlv, -1500, 300, 1);
+static const struct snd_kcontrol_new wm8940_input_boost_controls[] = {
+ SOC_DAPM_SINGLE("Mic PGA Switch", WM8940_PGAGAIN, 6, 1, 1),
+ SOC_DAPM_SINGLE_TLV("Aux Volume", WM8940_ADCBOOST,
+ 0, 7, 0, wm8940_boost_vol_tlv),
+ SOC_DAPM_SINGLE_TLV("Mic Volume", WM8940_ADCBOOST,
+ 4, 7, 0, wm8940_boost_vol_tlv),
+};
+
+static const struct snd_kcontrol_new wm8940_micpga_controls[] = {
+ SOC_DAPM_SINGLE("AUX Switch", WM8940_INPUTCTL, 2, 1, 0),
+ SOC_DAPM_SINGLE("MICP Switch", WM8940_INPUTCTL, 0, 1, 0),
+ SOC_DAPM_SINGLE("MICN Switch", WM8940_INPUTCTL, 1, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8940_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Speaker Mixer", WM8940_POWER3, 2, 0,
+ &wm8940_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm8940_speaker_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono Mixer", WM8940_POWER3, 3, 0,
+ &wm8940_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8940_mono_mixer_controls)),
+ SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8940_POWER3, 0, 0),
+
+ SND_SOC_DAPM_PGA("SpkN Out", WM8940_POWER3, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("SpkP Out", WM8940_POWER3, 6, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mono Out", WM8940_POWER3, 7, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("MONOOUT"),
+ SND_SOC_DAPM_OUTPUT("SPKOUTP"),
+ SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+
+ SND_SOC_DAPM_PGA("Aux Input", WM8940_POWER1, 6, 0, NULL, 0),
+ SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8940_POWER2, 0, 0),
+ SND_SOC_DAPM_MIXER("Mic PGA", WM8940_POWER2, 2, 0,
+ &wm8940_micpga_controls[0],
+ ARRAY_SIZE(wm8940_micpga_controls)),
+ SND_SOC_DAPM_MIXER("Boost Mixer", WM8940_POWER2, 4, 0,
+ &wm8940_input_boost_controls[0],
+ ARRAY_SIZE(wm8940_input_boost_controls)),
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8940_POWER1, 4, 0),
+
+ SND_SOC_DAPM_INPUT("MICN"),
+ SND_SOC_DAPM_INPUT("MICP"),
+ SND_SOC_DAPM_INPUT("AUX"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Mono output mixer */
+ {"Mono Mixer", "PCM Playback Switch", "DAC"},
+ {"Mono Mixer", "Aux Playback Switch", "Aux Input"},
+ {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+ /* Speaker output mixer */
+ {"Speaker Mixer", "PCM Playback Switch", "DAC"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
+ {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+ /* Outputs */
+ {"Mono Out", NULL, "Mono Mixer"},
+ {"MONOOUT", NULL, "Mono Out"},
+ {"SpkN Out", NULL, "Speaker Mixer"},
+ {"SpkP Out", NULL, "Speaker Mixer"},
+ {"SPKOUTN", NULL, "SpkN Out"},
+ {"SPKOUTP", NULL, "SpkP Out"},
+
+ /* Microphone PGA */
+ {"Mic PGA", "MICN Switch", "MICN"},
+ {"Mic PGA", "MICP Switch", "MICP"},
+ {"Mic PGA", "AUX Switch", "AUX"},
+
+ /* Boost Mixer */
+ {"Boost Mixer", "Mic PGA Switch", "Mic PGA"},
+ {"Boost Mixer", "Mic Volume", "MICP"},
+ {"Boost Mixer", "Aux Volume", "Aux Input"},
+
+ {"ADC", NULL, "Boost Mixer"},
+};
+
+static int wm8940_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, wm8940_dapm_widgets,
+ ARRAY_SIZE(wm8940_dapm_widgets));
+ if (ret)
+ goto error_ret;
+ ret = snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+ if (ret)
+ goto error_ret;
+
+error_ret:
+ return ret;
+}
+
+#define wm8940_reset(c) snd_soc_write(c, WM8940_SOFTRESET, 0);
+
+static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = snd_soc_read(codec, WM8940_IFACE) & 0xFE67;
+ u16 clk = snd_soc_read(codec, WM8940_CLOCK) & 0x1fe;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ clk |= 1;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_write(codec, WM8940_CLOCK, clk);
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= (2 << 3);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= (1 << 3);
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= (3 << 3);
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= (3 << 3) | (1 << 7);
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= (1 << 7);
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= (1 << 8);
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= (1 << 8) | (1 << 7);
+ break;
+ }
+
+ snd_soc_write(codec, WM8940_IFACE, iface);
+
+ return 0;
+}
+
+static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u16 iface = snd_soc_read(codec, WM8940_IFACE) & 0xFD9F;
+ u16 addcntrl = snd_soc_read(codec, WM8940_ADDCNTRL) & 0xFFF1;
+ u16 companding = snd_soc_read(codec,
+ WM8940_COMPANDINGCTL) & 0xFFDF;
+ int ret;
+
+ /* LoutR control */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE
+ && params_channels(params) == 2)
+ iface |= (1 << 9);
+
+ switch (params_rate(params)) {
+ case 8000:
+ addcntrl |= (0x5 << 1);
+ break;
+ case 11025:
+ addcntrl |= (0x4 << 1);
+ break;
+ case 16000:
+ addcntrl |= (0x3 << 1);
+ break;
+ case 22050:
+ addcntrl |= (0x2 << 1);
+ break;
+ case 32000:
+ addcntrl |= (0x1 << 1);
+ break;
+ case 44100:
+ case 48000:
+ break;
+ }
+ ret = snd_soc_write(codec, WM8940_ADDCNTRL, addcntrl);
+ if (ret)
+ goto error_ret;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ companding = companding | (1 << 5);
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= (1 << 5);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= (2 << 5);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= (3 << 5);
+ break;
+ }
+ ret = snd_soc_write(codec, WM8940_COMPANDINGCTL, companding);
+ if (ret)
+ goto error_ret;
+ ret = snd_soc_write(codec, WM8940_IFACE, iface);
+
+error_ret:
+ return ret;
+}
+
+static int wm8940_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8940_DAC) & 0xffbf;
+
+ if (mute)
+ mute_reg |= 0x40;
+
+ return snd_soc_write(codec, WM8940_DAC, mute_reg);
+}
+
+static int wm8940_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 val;
+ u16 pwr_reg = snd_soc_read(codec, WM8940_POWER1) & 0x1F0;
+ int ret = 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* ensure bufioen and biasen */
+ pwr_reg |= (1 << 2) | (1 << 3);
+ /* Enable thermal shutdown */
+ val = snd_soc_read(codec, WM8940_OUTPUTCTL);
+ ret = snd_soc_write(codec, WM8940_OUTPUTCTL, val | 0x2);
+ if (ret)
+ break;
+ /* set vmid to 75k */
+ ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x1);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ /* ensure bufioen and biasen */
+ pwr_reg |= (1 << 2) | (1 << 3);
+ ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x1);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* ensure bufioen and biasen */
+ pwr_reg |= (1 << 2) | (1 << 3);
+ /* set vmid to 300k for standby */
+ ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x2);
+ break;
+ case SND_SOC_BIAS_OFF:
+ ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return ret;
+}
+
+struct pll_ {
+ unsigned int pre_scale:2;
+ unsigned int n:4;
+ unsigned int k;
+};
+
+static struct pll_ pll_div;
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+static void pll_factors(unsigned int target, unsigned int source)
+{
+ unsigned long long Kpart;
+ unsigned int K, Ndiv, Nmod;
+ /* The left shift ist to avoid accuracy loss when right shifting */
+ Ndiv = target / source;
+
+ if (Ndiv > 12) {
+ source <<= 1;
+ /* Multiply by 2 */
+ pll_div.pre_scale = 0;
+ Ndiv = target / source;
+ } else if (Ndiv < 3) {
+ source >>= 2;
+ /* Divide by 4 */
+ pll_div.pre_scale = 3;
+ Ndiv = target / source;
+ } else if (Ndiv < 6) {
+ source >>= 1;
+ /* divide by 2 */
+ pll_div.pre_scale = 2;
+ Ndiv = target / source;
+ } else
+ pll_div.pre_scale = 1;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM8940 N value %d outwith recommended range!d\n",
+ Ndiv);
+
+ pll_div.n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div.k = K;
+}
+
+/* Untested at the moment */
+static int wm8940_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ /* Turn off PLL */
+ reg = snd_soc_read(codec, WM8940_POWER1);
+ snd_soc_write(codec, WM8940_POWER1, reg & 0x1df);
+
+ if (freq_in == 0 || freq_out == 0) {
+ /* Clock CODEC directly from MCLK */
+ reg = snd_soc_read(codec, WM8940_CLOCK);
+ snd_soc_write(codec, WM8940_CLOCK, reg & 0x0ff);
+ /* Pll power down */
+ snd_soc_write(codec, WM8940_PLLN, (1 << 7));
+ return 0;
+ }
+
+ /* Pll is followed by a frequency divide by 4 */
+ pll_factors(freq_out*4, freq_in);
+ if (pll_div.k)
+ snd_soc_write(codec, WM8940_PLLN,
+ (pll_div.pre_scale << 4) | pll_div.n | (1 << 6));
+ else /* No factional component */
+ snd_soc_write(codec, WM8940_PLLN,
+ (pll_div.pre_scale << 4) | pll_div.n);
+ snd_soc_write(codec, WM8940_PLLK1, pll_div.k >> 18);
+ snd_soc_write(codec, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff);
+ snd_soc_write(codec, WM8940_PLLK3, pll_div.k & 0x1ff);
+ /* Enable the PLL */
+ reg = snd_soc_read(codec, WM8940_POWER1);
+ snd_soc_write(codec, WM8940_POWER1, reg | 0x020);
+
+ /* Run CODEC from PLL instead of MCLK */
+ reg = snd_soc_read(codec, WM8940_CLOCK);
+ snd_soc_write(codec, WM8940_CLOCK, reg | 0x100);
+
+ return 0;
+}
+
+static int wm8940_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8940_priv *wm8940 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8940->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int wm8940_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+ int ret = 0;
+
+ switch (div_id) {
+ case WM8940_BCLKDIV:
+ reg = snd_soc_read(codec, WM8940_CLOCK) & 0xFFEF3;
+ ret = snd_soc_write(codec, WM8940_CLOCK, reg | (div << 2));
+ break;
+ case WM8940_MCLKDIV:
+ reg = snd_soc_read(codec, WM8940_CLOCK) & 0xFF1F;
+ ret = snd_soc_write(codec, WM8940_CLOCK, reg | (div << 5));
+ break;
+ case WM8940_OPCLKDIV:
+ reg = snd_soc_read(codec, WM8940_ADDCNTRL) & 0xFFCF;
+ ret = snd_soc_write(codec, WM8940_ADDCNTRL, reg | (div << 4));
+ break;
+ }
+ return ret;
+}
+
+#define WM8940_RATES SNDRV_PCM_RATE_8000_48000
+
+#define WM8940_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8940_dai_ops = {
+ .hw_params = wm8940_i2s_hw_params,
+ .set_sysclk = wm8940_set_dai_sysclk,
+ .digital_mute = wm8940_mute,
+ .set_fmt = wm8940_set_dai_fmt,
+ .set_clkdiv = wm8940_set_dai_clkdiv,
+ .set_pll = wm8940_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver wm8940_dai = {
+ .name = "wm8940-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8940_RATES,
+ .formats = WM8940_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8940_RATES,
+ .formats = WM8940_FORMATS,
+ },
+ .ops = &wm8940_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int wm8940_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ return wm8940_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int wm8940_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ int ret;
+ u8 data[3];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware
+ * Could use auto incremented writes to speed this up
+ */
+ for (i = 0; i < ARRAY_SIZE(wm8940_reg_defaults); i++) {
+ data[0] = i;
+ data[1] = (cache[i] & 0xFF00) >> 8;
+ data[2] = cache[i] & 0x00FF;
+ ret = codec->hw_write(codec->control_data, data, 3);
+ if (ret < 0)
+ goto error_ret;
+ else if (ret != 3) {
+ ret = -EIO;
+ goto error_ret;
+ }
+ }
+ ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ if (ret)
+ goto error_ret;
+
+error_ret:
+ return ret;
+}
+
+static int wm8940_probe(struct snd_soc_codec *codec)
+{
+ struct wm8940_priv *wm8940 = snd_soc_codec_get_drvdata(codec);
+ struct wm8940_setup_data *pdata = codec->dev->platform_data;
+ int ret;
+ u16 reg;
+
+ codec->control_data = wm8940->control_data;
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm8940->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8940_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ ret = snd_soc_write(codec, WM8940_POWER1, 0x180);
+ if (ret < 0)
+ return ret;
+
+ if (!pdata)
+ dev_warn(codec->dev, "No platform data supplied\n");
+ else {
+ reg = snd_soc_read(codec, WM8940_OUTPUTCTL);
+ ret = snd_soc_write(codec, WM8940_OUTPUTCTL, reg | pdata->vroi);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = snd_soc_add_controls(codec, wm8940_snd_controls,
+ ARRAY_SIZE(wm8940_snd_controls));
+ if (ret)
+ return ret;
+ ret = wm8940_add_widgets(codec);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int wm8940_remove(struct snd_soc_codec *codec)
+{
+ wm8940_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8940 = {
+ .probe = wm8940_probe,
+ .remove = wm8940_remove,
+ .suspend = wm8940_suspend,
+ .resume = wm8940_resume,
+ .set_bias_level = wm8940_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8940_reg_defaults),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8940_reg_defaults,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8940_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8940_priv *wm8940;
+ int ret;
+
+ wm8940 = kzalloc(sizeof(struct wm8940_priv), GFP_KERNEL);
+ if (wm8940 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8940);
+ wm8940->control_data = i2c;
+ wm8940->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8940, &wm8940_dai, 1);
+ if (ret < 0)
+ kfree(wm8940);
+ return ret;
+}
+
+static __devexit int wm8940_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8940_i2c_id[] = {
+ { "wm8940", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8940_i2c_id);
+
+static struct i2c_driver wm8940_i2c_driver = {
+ .driver = {
+ .name = "wm8940-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8940_i2c_probe,
+ .remove = __devexit_p(wm8940_i2c_remove),
+ .id_table = wm8940_i2c_id,
+};
+#endif
+
+static int __init wm8940_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8940_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8940 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8940_modinit);
+
+static void __exit wm8940_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8940_i2c_driver);
+#endif
+}
+module_exit(wm8940_exit);
+
+MODULE_DESCRIPTION("ASoC WM8940 driver");
+MODULE_AUTHOR("Jonathan Cameron");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8940.h b/sound/soc/codecs/wm8940.h
new file mode 100644
index 00000000..907fe192
--- /dev/null
+++ b/sound/soc/codecs/wm8940.h
@@ -0,0 +1,102 @@
+/*
+ * wm8940.h -- WM8940 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8940_H
+#define _WM8940_H
+
+struct wm8940_setup_data {
+ /* Vref to analogue output resistance */
+#define WM8940_VROI_1K 0
+#define WM8940_VROI_30K 1
+ unsigned int vroi:1;
+};
+
+/* WM8940 register space */
+#define WM8940_SOFTRESET 0x00
+#define WM8940_POWER1 0x01
+#define WM8940_POWER2 0x02
+#define WM8940_POWER3 0x03
+#define WM8940_IFACE 0x04
+#define WM8940_COMPANDINGCTL 0x05
+#define WM8940_CLOCK 0x06
+#define WM8940_ADDCNTRL 0x07
+#define WM8940_GPIO 0x08
+#define WM8940_CTLINT 0x09
+#define WM8940_DAC 0x0A
+#define WM8940_DACVOL 0x0B
+
+#define WM8940_ADC 0x0E
+#define WM8940_ADCVOL 0x0F
+#define WM8940_NOTCH1 0x10
+#define WM8940_NOTCH2 0x11
+#define WM8940_NOTCH3 0x12
+#define WM8940_NOTCH4 0x13
+#define WM8940_NOTCH5 0x14
+#define WM8940_NOTCH6 0x15
+#define WM8940_NOTCH7 0x16
+#define WM8940_NOTCH8 0x17
+#define WM8940_DACLIM1 0x18
+#define WM8940_DACLIM2 0x19
+
+#define WM8940_ALC1 0x20
+#define WM8940_ALC2 0x21
+#define WM8940_ALC3 0x22
+#define WM8940_NOISEGATE 0x23
+#define WM8940_PLLN 0x24
+#define WM8940_PLLK1 0x25
+#define WM8940_PLLK2 0x26
+#define WM8940_PLLK3 0x27
+
+#define WM8940_ALC4 0x2A
+
+#define WM8940_INPUTCTL 0x2C
+#define WM8940_PGAGAIN 0x2D
+
+#define WM8940_ADCBOOST 0x2F
+
+#define WM8940_OUTPUTCTL 0x31
+#define WM8940_SPKMIX 0x32
+
+#define WM8940_SPKVOL 0x36
+
+#define WM8940_MONOMIX 0x38
+
+#define WM8940_CACHEREGNUM 0x57
+
+
+/* Clock divider Id's */
+#define WM8940_BCLKDIV 0
+#define WM8940_MCLKDIV 1
+#define WM8940_OPCLKDIV 2
+
+/* MCLK clock dividers */
+#define WM8940_MCLKDIV_1 0
+#define WM8940_MCLKDIV_1_5 1
+#define WM8940_MCLKDIV_2 2
+#define WM8940_MCLKDIV_3 3
+#define WM8940_MCLKDIV_4 4
+#define WM8940_MCLKDIV_6 5
+#define WM8940_MCLKDIV_8 6
+#define WM8940_MCLKDIV_12 7
+
+/* BCLK clock dividers */
+#define WM8940_BCLKDIV_1 0
+#define WM8940_BCLKDIV_2 1
+#define WM8940_BCLKDIV_4 2
+#define WM8940_BCLKDIV_8 3
+#define WM8940_BCLKDIV_16 4
+#define WM8940_BCLKDIV_32 5
+
+/* PLL Out Dividers */
+#define WM8940_OPCLKDIV_1 0
+#define WM8940_OPCLKDIV_2 1
+#define WM8940_OPCLKDIV_3 2
+#define WM8940_OPCLKDIV_4 3
+
+#endif /* _WM8940_H */
+
diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c
new file mode 100644
index 00000000..3c719877
--- /dev/null
+++ b/sound/soc/codecs/wm8955.c
@@ -0,0 +1,1074 @@
+/*
+ * wm8955.c -- WM8955 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/wm8955.h>
+
+#include "wm8955.h"
+
+#define WM8955_NUM_SUPPLIES 4
+static const char *wm8955_supply_names[WM8955_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD",
+ "HPVDD",
+ "AVDD",
+};
+
+/* codec private data */
+struct wm8955_priv {
+ enum snd_soc_control_type control_type;
+
+ unsigned int mclk_rate;
+
+ int deemph;
+ int fs;
+
+ struct regulator_bulk_data supplies[WM8955_NUM_SUPPLIES];
+};
+
+static const u16 wm8955_reg[WM8955_MAX_REGISTER + 1] = {
+ 0x0000, /* R0 */
+ 0x0000, /* R1 */
+ 0x0079, /* R2 - LOUT1 volume */
+ 0x0079, /* R3 - ROUT1 volume */
+ 0x0000, /* R4 */
+ 0x0008, /* R5 - DAC Control */
+ 0x0000, /* R6 */
+ 0x000A, /* R7 - Audio Interface */
+ 0x0000, /* R8 - Sample Rate */
+ 0x0000, /* R9 */
+ 0x00FF, /* R10 - Left DAC volume */
+ 0x00FF, /* R11 - Right DAC volume */
+ 0x000F, /* R12 - Bass control */
+ 0x000F, /* R13 - Treble control */
+ 0x0000, /* R14 */
+ 0x0000, /* R15 - Reset */
+ 0x0000, /* R16 */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 */
+ 0x0000, /* R19 */
+ 0x0000, /* R20 */
+ 0x0000, /* R21 */
+ 0x0000, /* R22 */
+ 0x00C1, /* R23 - Additional control (1) */
+ 0x0000, /* R24 - Additional control (2) */
+ 0x0000, /* R25 - Power Management (1) */
+ 0x0000, /* R26 - Power Management (2) */
+ 0x0000, /* R27 - Additional Control (3) */
+ 0x0000, /* R28 */
+ 0x0000, /* R29 */
+ 0x0000, /* R30 */
+ 0x0000, /* R31 */
+ 0x0000, /* R32 */
+ 0x0000, /* R33 */
+ 0x0050, /* R34 - Left out Mix (1) */
+ 0x0050, /* R35 - Left out Mix (2) */
+ 0x0050, /* R36 - Right out Mix (1) */
+ 0x0050, /* R37 - Right Out Mix (2) */
+ 0x0050, /* R38 - Mono out Mix (1) */
+ 0x0050, /* R39 - Mono out Mix (2) */
+ 0x0079, /* R40 - LOUT2 volume */
+ 0x0079, /* R41 - ROUT2 volume */
+ 0x0079, /* R42 - MONOOUT volume */
+ 0x0000, /* R43 - Clocking / PLL */
+ 0x0103, /* R44 - PLL Control 1 */
+ 0x0024, /* R45 - PLL Control 2 */
+ 0x01BA, /* R46 - PLL Control 3 */
+ 0x0000, /* R47 */
+ 0x0000, /* R48 */
+ 0x0000, /* R49 */
+ 0x0000, /* R50 */
+ 0x0000, /* R51 */
+ 0x0000, /* R52 */
+ 0x0000, /* R53 */
+ 0x0000, /* R54 */
+ 0x0000, /* R55 */
+ 0x0000, /* R56 */
+ 0x0000, /* R57 */
+ 0x0000, /* R58 */
+ 0x0000, /* R59 - PLL Control 4 */
+};
+
+static int wm8955_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8955_RESET, 0);
+}
+
+struct pll_factors {
+ int n;
+ int k;
+ int outdiv;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 22) * 10)
+
+static int wm8995_pll_factors(struct device *dev,
+ int Fref, int Fout, struct pll_factors *pll)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+
+ dev_dbg(dev, "Fref=%u Fout=%u\n", Fref, Fout);
+
+ /* The oscilator should run at should be 90-100MHz, and
+ * there's a divide by 4 plus an optional divide by 2 in the
+ * output path to generate the system clock. The clock table
+ * is sortd so we should always generate a suitable target. */
+ target = Fout * 4;
+ if (target < 90000000) {
+ pll->outdiv = 1;
+ target *= 2;
+ } else {
+ pll->outdiv = 0;
+ }
+
+ WARN_ON(target < 90000000 || target > 100000000);
+
+ dev_dbg(dev, "Fvco=%dHz\n", target);
+
+ /* Now, calculate N.K */
+ Ndiv = target / Fref;
+
+ pll->n = Ndiv;
+ Nmod = target % Fref;
+ dev_dbg(dev, "Nmod=%d\n", Nmod);
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, Fref);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ pll->k = K / 10;
+
+ dev_dbg(dev, "N=%x K=%x OUTDIV=%x\n", pll->n, pll->k, pll->outdiv);
+
+ return 0;
+}
+
+/* Lookup table specifying SRATE (table 25 in datasheet); some of the
+ * output frequencies have been rounded to the standard frequencies
+ * they are intended to match where the error is slight. */
+static struct {
+ int mclk;
+ int fs;
+ int usb;
+ int sr;
+} clock_cfgs[] = {
+ { 18432000, 8000, 0, 3, },
+ { 18432000, 12000, 0, 9, },
+ { 18432000, 16000, 0, 11, },
+ { 18432000, 24000, 0, 29, },
+ { 18432000, 32000, 0, 13, },
+ { 18432000, 48000, 0, 1, },
+ { 18432000, 96000, 0, 15, },
+
+ { 16934400, 8018, 0, 19, },
+ { 16934400, 11025, 0, 25, },
+ { 16934400, 22050, 0, 27, },
+ { 16934400, 44100, 0, 17, },
+ { 16934400, 88200, 0, 31, },
+
+ { 12000000, 8000, 1, 2, },
+ { 12000000, 11025, 1, 25, },
+ { 12000000, 12000, 1, 8, },
+ { 12000000, 16000, 1, 10, },
+ { 12000000, 22050, 1, 27, },
+ { 12000000, 24000, 1, 28, },
+ { 12000000, 32000, 1, 12, },
+ { 12000000, 44100, 1, 17, },
+ { 12000000, 48000, 1, 0, },
+ { 12000000, 88200, 1, 31, },
+ { 12000000, 96000, 1, 14, },
+
+ { 12288000, 8000, 0, 2, },
+ { 12288000, 12000, 0, 8, },
+ { 12288000, 16000, 0, 10, },
+ { 12288000, 24000, 0, 28, },
+ { 12288000, 32000, 0, 12, },
+ { 12288000, 48000, 0, 0, },
+ { 12288000, 96000, 0, 14, },
+
+ { 12289600, 8018, 0, 18, },
+ { 12289600, 11025, 0, 24, },
+ { 12289600, 22050, 0, 26, },
+ { 11289600, 44100, 0, 16, },
+ { 11289600, 88200, 0, 31, },
+};
+
+static int wm8955_configure_clocking(struct snd_soc_codec *codec)
+{
+ struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+ int i, ret, val;
+ int clocking = 0;
+ int srate = 0;
+ int sr = -1;
+ struct pll_factors pll;
+
+ /* If we're not running a sample rate currently just pick one */
+ if (wm8955->fs == 0)
+ wm8955->fs = 8000;
+
+ /* Can we generate an exact output? */
+ for (i = 0; i < ARRAY_SIZE(clock_cfgs); i++) {
+ if (wm8955->fs != clock_cfgs[i].fs)
+ continue;
+ sr = i;
+
+ if (wm8955->mclk_rate == clock_cfgs[i].mclk)
+ break;
+ }
+
+ /* We should never get here with an unsupported sample rate */
+ if (sr == -1) {
+ dev_err(codec->dev, "Sample rate %dHz unsupported\n",
+ wm8955->fs);
+ WARN_ON(sr == -1);
+ return -EINVAL;
+ }
+
+ if (i == ARRAY_SIZE(clock_cfgs)) {
+ /* If we can't generate the right clock from MCLK then
+ * we should configure the PLL to supply us with an
+ * appropriate clock.
+ */
+ clocking |= WM8955_MCLKSEL;
+
+ /* Use the last divider configuration we saw for the
+ * sample rate. */
+ ret = wm8995_pll_factors(codec->dev, wm8955->mclk_rate,
+ clock_cfgs[sr].mclk, &pll);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Unable to generate %dHz from %dHz MCLK\n",
+ wm8955->fs, wm8955->mclk_rate);
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8955_PLL_CONTROL_1,
+ WM8955_N_MASK | WM8955_K_21_18_MASK,
+ (pll.n << WM8955_N_SHIFT) |
+ pll.k >> 18);
+ snd_soc_update_bits(codec, WM8955_PLL_CONTROL_2,
+ WM8955_K_17_9_MASK,
+ (pll.k >> 9) & WM8955_K_17_9_MASK);
+ snd_soc_update_bits(codec, WM8955_PLL_CONTROL_2,
+ WM8955_K_8_0_MASK,
+ pll.k & WM8955_K_8_0_MASK);
+ if (pll.k)
+ snd_soc_update_bits(codec, WM8955_PLL_CONTROL_4,
+ WM8955_KEN, WM8955_KEN);
+ else
+ snd_soc_update_bits(codec, WM8955_PLL_CONTROL_4,
+ WM8955_KEN, 0);
+
+ if (pll.outdiv)
+ val = WM8955_PLL_RB | WM8955_PLLOUTDIV2;
+ else
+ val = WM8955_PLL_RB;
+
+ /* Now start the PLL running */
+ snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+ WM8955_PLL_RB | WM8955_PLLOUTDIV2, val);
+ snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+ WM8955_PLLEN, WM8955_PLLEN);
+ }
+
+ srate = clock_cfgs[sr].usb | (clock_cfgs[sr].sr << WM8955_SR_SHIFT);
+
+ snd_soc_update_bits(codec, WM8955_SAMPLE_RATE,
+ WM8955_USB | WM8955_SR_MASK, srate);
+ snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+ WM8955_MCLKSEL, clocking);
+
+ return 0;
+}
+
+static int wm8955_sysclk(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ int ret = 0;
+
+ /* Always disable the clocks - if we're doing reconfiguration this
+ * avoids misclocking.
+ */
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_DIGENB, 0);
+ snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+ WM8955_PLL_RB | WM8955_PLLEN, 0);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMD:
+ break;
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = wm8955_configure_clocking(codec);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8955_set_deemph(struct snd_soc_codec *codec)
+{
+ struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+ int val, i, best;
+
+ /* If we're using deemphasis select the nearest available sample
+ * rate.
+ */
+ if (wm8955->deemph) {
+ best = 1;
+ for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+ if (abs(deemph_settings[i] - wm8955->fs) <
+ abs(deemph_settings[best] - wm8955->fs))
+ best = i;
+ }
+
+ val = best << WM8955_DEEMPH_SHIFT;
+ } else {
+ val = 0;
+ }
+
+ dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+ return snd_soc_update_bits(codec, WM8955_DAC_CONTROL,
+ WM8955_DEEMPH_MASK, val);
+}
+
+static int wm8955_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8955->deemph;
+ return 0;
+}
+
+static int wm8955_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+ int deemph = ucontrol->value.enumerated.item[0];
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ wm8955->deemph = deemph;
+
+ return wm8955_set_deemph(codec);
+}
+
+static const char *bass_mode_text[] = {
+ "Linear", "Adaptive",
+};
+
+static const struct soc_enum bass_mode =
+ SOC_ENUM_SINGLE(WM8955_BASS_CONTROL, 7, 2, bass_mode_text);
+
+static const char *bass_cutoff_text[] = {
+ "Low", "High"
+};
+
+static const struct soc_enum bass_cutoff =
+ SOC_ENUM_SINGLE(WM8955_BASS_CONTROL, 6, 2, bass_cutoff_text);
+
+static const char *treble_cutoff_text[] = {
+ "High", "Low"
+};
+
+static const struct soc_enum treble_cutoff =
+ SOC_ENUM_SINGLE(WM8955_TREBLE_CONTROL, 6, 2, treble_cutoff_text);
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(atten_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(mono_tlv, -2100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(treble_tlv, -1200, 150, 1);
+
+static const struct snd_kcontrol_new wm8955_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8955_LEFT_DAC_VOLUME,
+ WM8955_RIGHT_DAC_VOLUME, 0, 255, 0, digital_tlv),
+SOC_SINGLE_TLV("Playback Attenuation Volume", WM8955_DAC_CONTROL, 7, 1, 1,
+ atten_tlv),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+ wm8955_get_deemph, wm8955_put_deemph),
+
+SOC_ENUM("Bass Mode", bass_mode),
+SOC_ENUM("Bass Cutoff", bass_cutoff),
+SOC_SINGLE("Bass Volume", WM8955_BASS_CONTROL, 0, 15, 1),
+
+SOC_ENUM("Treble Cutoff", treble_cutoff),
+SOC_SINGLE_TLV("Treble Volume", WM8955_TREBLE_CONTROL, 0, 14, 1, treble_tlv),
+
+SOC_SINGLE_TLV("Left Bypass Volume", WM8955_LEFT_OUT_MIX_1, 4, 7, 1,
+ bypass_tlv),
+SOC_SINGLE_TLV("Left Mono Volume", WM8955_LEFT_OUT_MIX_2, 4, 7, 1,
+ bypass_tlv),
+
+SOC_SINGLE_TLV("Right Mono Volume", WM8955_RIGHT_OUT_MIX_1, 4, 7, 1,
+ bypass_tlv),
+SOC_SINGLE_TLV("Right Bypass Volume", WM8955_RIGHT_OUT_MIX_2, 4, 7, 1,
+ bypass_tlv),
+
+/* Not a stereo pair so they line up with the DAPM switches */
+SOC_SINGLE_TLV("Mono Left Bypass Volume", WM8955_MONO_OUT_MIX_1, 4, 7, 1,
+ mono_tlv),
+SOC_SINGLE_TLV("Mono Right Bypass Volume", WM8955_MONO_OUT_MIX_2, 4, 7, 1,
+ mono_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Volume", WM8955_LOUT1_VOLUME,
+ WM8955_ROUT1_VOLUME, 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8955_LOUT1_VOLUME,
+ WM8955_ROUT1_VOLUME, 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Speaker Volume", WM8955_LOUT2_VOLUME,
+ WM8955_ROUT2_VOLUME, 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Speaker ZC Switch", WM8955_LOUT2_VOLUME,
+ WM8955_ROUT2_VOLUME, 7, 1, 0),
+
+SOC_SINGLE_TLV("Mono Volume", WM8955_MONOOUT_VOLUME, 0, 127, 0, out_tlv),
+SOC_SINGLE("Mono ZC Switch", WM8955_MONOOUT_VOLUME, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new lmixer[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8955_LEFT_OUT_MIX_1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Switch", WM8955_LEFT_OUT_MIX_1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8955_LEFT_OUT_MIX_2, 8, 1, 0),
+SOC_DAPM_SINGLE("Mono Switch", WM8955_LEFT_OUT_MIX_2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new rmixer[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8955_RIGHT_OUT_MIX_1, 8, 1, 0),
+SOC_DAPM_SINGLE("Mono Switch", WM8955_RIGHT_OUT_MIX_1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8955_RIGHT_OUT_MIX_2, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Switch", WM8955_RIGHT_OUT_MIX_2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new mmixer[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8955_MONO_OUT_MIX_1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8955_MONO_OUT_MIX_1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8955_MONO_OUT_MIX_2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8955_MONO_OUT_MIX_2, 7, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8955_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("MONOIN-"),
+SND_SOC_DAPM_INPUT("MONOIN+"),
+SND_SOC_DAPM_INPUT("LINEINR"),
+SND_SOC_DAPM_INPUT("LINEINL"),
+
+SND_SOC_DAPM_PGA("Mono Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("SYSCLK", WM8955_POWER_MANAGEMENT_1, 0, 1, wm8955_sysclk,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("TSDEN", WM8955_ADDITIONAL_CONTROL_1, 8, 0, NULL, 0),
+
+SND_SOC_DAPM_DAC("DACL", "Playback", WM8955_POWER_MANAGEMENT_2, 8, 0),
+SND_SOC_DAPM_DAC("DACR", "Playback", WM8955_POWER_MANAGEMENT_2, 7, 0),
+
+SND_SOC_DAPM_PGA("LOUT1 PGA", WM8955_POWER_MANAGEMENT_2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT1 PGA", WM8955_POWER_MANAGEMENT_2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LOUT2 PGA", WM8955_POWER_MANAGEMENT_2, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT2 PGA", WM8955_POWER_MANAGEMENT_2, 3, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MOUT PGA", WM8955_POWER_MANAGEMENT_2, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("OUT3 PGA", WM8955_POWER_MANAGEMENT_2, 1, 0, NULL, 0),
+
+/* The names are chosen to make the control names nice */
+SND_SOC_DAPM_MIXER("Left", SND_SOC_NOPM, 0, 0,
+ lmixer, ARRAY_SIZE(lmixer)),
+SND_SOC_DAPM_MIXER("Right", SND_SOC_NOPM, 0, 0,
+ rmixer, ARRAY_SIZE(rmixer)),
+SND_SOC_DAPM_MIXER("Mono", SND_SOC_NOPM, 0, 0,
+ mmixer, ARRAY_SIZE(mmixer)),
+
+SND_SOC_DAPM_OUTPUT("LOUT1"),
+SND_SOC_DAPM_OUTPUT("ROUT1"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+};
+
+static const struct snd_soc_dapm_route wm8955_intercon[] = {
+ { "DACL", NULL, "SYSCLK" },
+ { "DACR", NULL, "SYSCLK" },
+
+ { "Mono Input", NULL, "MONOIN-" },
+ { "Mono Input", NULL, "MONOIN+" },
+
+ { "Left", "Playback Switch", "DACL" },
+ { "Left", "Right Playback Switch", "DACR" },
+ { "Left", "Bypass Switch", "LINEINL" },
+ { "Left", "Mono Switch", "Mono Input" },
+
+ { "Right", "Playback Switch", "DACR" },
+ { "Right", "Left Playback Switch", "DACL" },
+ { "Right", "Bypass Switch", "LINEINR" },
+ { "Right", "Mono Switch", "Mono Input" },
+
+ { "Mono", "Left Playback Switch", "DACL" },
+ { "Mono", "Right Playback Switch", "DACR" },
+ { "Mono", "Left Bypass Switch", "LINEINL" },
+ { "Mono", "Right Bypass Switch", "LINEINR" },
+
+ { "LOUT1 PGA", NULL, "Left" },
+ { "LOUT1", NULL, "TSDEN" },
+ { "LOUT1", NULL, "LOUT1 PGA" },
+
+ { "ROUT1 PGA", NULL, "Right" },
+ { "ROUT1", NULL, "TSDEN" },
+ { "ROUT1", NULL, "ROUT1 PGA" },
+
+ { "LOUT2 PGA", NULL, "Left" },
+ { "LOUT2", NULL, "TSDEN" },
+ { "LOUT2", NULL, "LOUT2 PGA" },
+
+ { "ROUT2 PGA", NULL, "Right" },
+ { "ROUT2", NULL, "TSDEN" },
+ { "ROUT2", NULL, "ROUT2 PGA" },
+
+ { "MOUT PGA", NULL, "Mono" },
+ { "MONOOUT", NULL, "MOUT PGA" },
+
+ /* OUT3 not currently implemented */
+ { "OUT3", NULL, "OUT3 PGA" },
+};
+
+static int wm8955_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_add_controls(codec, wm8955_snd_controls,
+ ARRAY_SIZE(wm8955_snd_controls));
+
+ snd_soc_dapm_new_controls(dapm, wm8955_dapm_widgets,
+ ARRAY_SIZE(wm8955_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, wm8955_intercon,
+ ARRAY_SIZE(wm8955_intercon));
+
+ return 0;
+}
+
+static int wm8955_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+ int wl;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ wl = 0;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ wl = 0x4;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ wl = 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ wl = 0xc;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(codec, WM8955_AUDIO_INTERFACE,
+ WM8955_WL_MASK, wl);
+
+ wm8955->fs = params_rate(params);
+ wm8955_set_deemph(codec);
+
+ /* If the chip is clocked then disable the clocks and force a
+ * reconfiguration, otherwise DAPM will power up the
+ * clocks for us later. */
+ ret = snd_soc_read(codec, WM8955_POWER_MANAGEMENT_1);
+ if (ret < 0)
+ return ret;
+ if (ret & WM8955_DIGENB) {
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_DIGENB, 0);
+ snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+ WM8955_PLL_RB | WM8955_PLLEN, 0);
+
+ wm8955_configure_clocking(codec);
+ }
+
+ return 0;
+}
+
+
+static int wm8955_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8955_priv *priv = snd_soc_codec_get_drvdata(codec);
+ int div;
+
+ switch (clk_id) {
+ case WM8955_CLK_MCLK:
+ if (freq > 15000000) {
+ priv->mclk_rate = freq /= 2;
+ div = WM8955_MCLKDIV2;
+ } else {
+ priv->mclk_rate = freq;
+ div = 0;
+ }
+
+ snd_soc_update_bits(codec, WM8955_SAMPLE_RATE,
+ WM8955_MCLKDIV2, div);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+ return 0;
+}
+
+static int wm8955_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 aif = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif |= WM8955_MS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_B:
+ aif |= WM8955_LRP;
+ case SND_SOC_DAIFMT_DSP_A:
+ aif |= 0x3;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif |= 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif |= 0x1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif |= WM8955_BCLKINV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif |= WM8955_BCLKINV | WM8955_LRP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif |= WM8955_BCLKINV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif |= WM8955_LRP;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8955_AUDIO_INTERFACE,
+ WM8955_MS | WM8955_FORMAT_MASK | WM8955_BCLKINV |
+ WM8955_LRP, aif);
+
+ return 0;
+}
+
+
+static int wm8955_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int val;
+
+ if (mute)
+ val = WM8955_DACMU;
+ else
+ val = 0;
+
+ snd_soc_update_bits(codec, WM8955_DAC_CONTROL, WM8955_DACMU, val);
+
+ return 0;
+}
+
+static int wm8955_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+ u16 *reg_cache = codec->reg_cache;
+ int ret, i;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID resistance 2*50k */
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_VMIDSEL_MASK,
+ 0x1 << WM8955_VMIDSEL_SHIFT);
+
+ /* Default bias current */
+ snd_soc_update_bits(codec, WM8955_ADDITIONAL_CONTROL_1,
+ WM8955_VSEL_MASK,
+ 0x2 << WM8955_VSEL_SHIFT);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies),
+ wm8955->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Sync back cached values if they're
+ * different from the hardware default.
+ */
+ for (i = 0; i < codec->driver->reg_cache_size; i++) {
+ if (i == WM8955_RESET)
+ continue;
+
+ if (reg_cache[i] == wm8955_reg[i])
+ continue;
+
+ snd_soc_write(codec, i, reg_cache[i]);
+ }
+
+ /* Enable VREF and VMID */
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_VREF |
+ WM8955_VMIDSEL_MASK,
+ WM8955_VREF |
+ 0x3 << WM8955_VREF_SHIFT);
+
+ /* Let VMID ramp */
+ msleep(500);
+
+ /* High resistance VROI to maintain outputs */
+ snd_soc_update_bits(codec,
+ WM8955_ADDITIONAL_CONTROL_3,
+ WM8955_VROI, WM8955_VROI);
+ }
+
+ /* Maintain VMID with 2*250k */
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_VMIDSEL_MASK,
+ 0x2 << WM8955_VMIDSEL_SHIFT);
+
+ /* Minimum bias current */
+ snd_soc_update_bits(codec, WM8955_ADDITIONAL_CONTROL_1,
+ WM8955_VSEL_MASK, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Low resistance VROI to help discharge */
+ snd_soc_update_bits(codec,
+ WM8955_ADDITIONAL_CONTROL_3,
+ WM8955_VROI, 0);
+
+ /* Turn off VMID and VREF */
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_VREF |
+ WM8955_VMIDSEL_MASK, 0);
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies),
+ wm8955->supplies);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8955_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8955_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8955_dai_ops = {
+ .set_sysclk = wm8955_set_sysclk,
+ .set_fmt = wm8955_set_fmt,
+ .hw_params = wm8955_hw_params,
+ .digital_mute = wm8955_digital_mute,
+};
+
+static struct snd_soc_dai_driver wm8955_dai = {
+ .name = "wm8955-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8955_RATES,
+ .formats = WM8955_FORMATS,
+ },
+ .ops = &wm8955_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int wm8955_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8955_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8955_resume(struct snd_soc_codec *codec)
+{
+ wm8955_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define wm8955_suspend NULL
+#define wm8955_resume NULL
+#endif
+
+static int wm8955_probe(struct snd_soc_codec *codec)
+{
+ struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+ struct wm8955_pdata *pdata = dev_get_platdata(codec->dev);
+ u16 *reg_cache = codec->reg_cache;
+ int ret, i;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8955->control_type);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++)
+ wm8955->supplies[i].supply = wm8955_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8955->supplies),
+ wm8955->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies),
+ wm8955->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ ret = wm8955_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ goto err_enable;
+ }
+
+ /* Change some default settings - latch VU and enable ZC */
+ snd_soc_update_bits(codec, WM8955_LEFT_DAC_VOLUME,
+ WM8955_LDVU, WM8955_LDVU);
+ snd_soc_update_bits(codec, WM8955_RIGHT_DAC_VOLUME,
+ WM8955_RDVU, WM8955_RDVU);
+ snd_soc_update_bits(codec, WM8955_LOUT1_VOLUME,
+ WM8955_LO1VU | WM8955_LO1ZC,
+ WM8955_LO1VU | WM8955_LO1ZC);
+ snd_soc_update_bits(codec, WM8955_ROUT1_VOLUME,
+ WM8955_RO1VU | WM8955_RO1ZC,
+ WM8955_RO1VU | WM8955_RO1ZC);
+ snd_soc_update_bits(codec, WM8955_LOUT2_VOLUME,
+ WM8955_LO2VU | WM8955_LO2ZC,
+ WM8955_LO2VU | WM8955_LO2ZC);
+ snd_soc_update_bits(codec, WM8955_ROUT2_VOLUME,
+ WM8955_RO2VU | WM8955_RO2ZC,
+ WM8955_RO2VU | WM8955_RO2ZC);
+ snd_soc_update_bits(codec, WM8955_MONOOUT_VOLUME,
+ WM8955_MOZC, WM8955_MOZC);
+
+ /* Also enable adaptive bass boost by default */
+ snd_soc_update_bits(codec, WM8955_BASS_CONTROL, WM8955_BB, WM8955_BB);
+
+ /* Set platform data values */
+ if (pdata) {
+ if (pdata->out2_speaker)
+ reg_cache[WM8955_ADDITIONAL_CONTROL_2]
+ |= WM8955_ROUT2INV;
+
+ if (pdata->monoin_diff)
+ reg_cache[WM8955_MONO_OUT_MIX_1]
+ |= WM8955_DMEN;
+ }
+
+ wm8955_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Bias level configuration will have done an extra enable */
+ regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+
+ wm8955_add_widgets(codec);
+ return 0;
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+ return ret;
+}
+
+static int wm8955_remove(struct snd_soc_codec *codec)
+{
+ struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+
+ wm8955_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ regulator_bulk_free(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8955 = {
+ .probe = wm8955_probe,
+ .remove = wm8955_remove,
+ .suspend = wm8955_suspend,
+ .resume = wm8955_resume,
+ .set_bias_level = wm8955_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8955_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8955_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8955_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8955_priv *wm8955;
+ int ret;
+
+ wm8955 = kzalloc(sizeof(struct wm8955_priv), GFP_KERNEL);
+ if (wm8955 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8955);
+ wm8955->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8955, &wm8955_dai, 1);
+ if (ret < 0)
+ kfree(wm8955);
+ return ret;
+}
+
+static __devexit int wm8955_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8955_i2c_id[] = {
+ { "wm8955", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8955_i2c_id);
+
+static struct i2c_driver wm8955_i2c_driver = {
+ .driver = {
+ .name = "wm8955-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8955_i2c_probe,
+ .remove = __devexit_p(wm8955_i2c_remove),
+ .id_table = wm8955_i2c_id,
+};
+#endif
+
+static int __init wm8955_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8955_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8955 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8955_modinit);
+
+static void __exit wm8955_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8955_i2c_driver);
+#endif
+}
+module_exit(wm8955_exit);
+
+MODULE_DESCRIPTION("ASoC WM8955 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8955.h b/sound/soc/codecs/wm8955.h
new file mode 100644
index 00000000..d13fd5c5
--- /dev/null
+++ b/sound/soc/codecs/wm8955.h
@@ -0,0 +1,486 @@
+/*
+ * wm8955.h -- WM8904 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics, plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8955_H
+#define _WM8955_H
+
+#define WM8955_CLK_MCLK 1
+
+/*
+ * Register values.
+ */
+#define WM8955_LOUT1_VOLUME 0x02
+#define WM8955_ROUT1_VOLUME 0x03
+#define WM8955_DAC_CONTROL 0x05
+#define WM8955_AUDIO_INTERFACE 0x07
+#define WM8955_SAMPLE_RATE 0x08
+#define WM8955_LEFT_DAC_VOLUME 0x0A
+#define WM8955_RIGHT_DAC_VOLUME 0x0B
+#define WM8955_BASS_CONTROL 0x0C
+#define WM8955_TREBLE_CONTROL 0x0D
+#define WM8955_RESET 0x0F
+#define WM8955_ADDITIONAL_CONTROL_1 0x17
+#define WM8955_ADDITIONAL_CONTROL_2 0x18
+#define WM8955_POWER_MANAGEMENT_1 0x19
+#define WM8955_POWER_MANAGEMENT_2 0x1A
+#define WM8955_ADDITIONAL_CONTROL_3 0x1B
+#define WM8955_LEFT_OUT_MIX_1 0x22
+#define WM8955_LEFT_OUT_MIX_2 0x23
+#define WM8955_RIGHT_OUT_MIX_1 0x24
+#define WM8955_RIGHT_OUT_MIX_2 0x25
+#define WM8955_MONO_OUT_MIX_1 0x26
+#define WM8955_MONO_OUT_MIX_2 0x27
+#define WM8955_LOUT2_VOLUME 0x28
+#define WM8955_ROUT2_VOLUME 0x29
+#define WM8955_MONOOUT_VOLUME 0x2A
+#define WM8955_CLOCKING_PLL 0x2B
+#define WM8955_PLL_CONTROL_1 0x2C
+#define WM8955_PLL_CONTROL_2 0x2D
+#define WM8955_PLL_CONTROL_3 0x2E
+#define WM8955_PLL_CONTROL_4 0x3B
+
+#define WM8955_REGISTER_COUNT 29
+#define WM8955_MAX_REGISTER 0x3B
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R2 (0x02) - LOUT1 volume
+ */
+#define WM8955_LO1VU 0x0100 /* LO1VU */
+#define WM8955_LO1VU_MASK 0x0100 /* LO1VU */
+#define WM8955_LO1VU_SHIFT 8 /* LO1VU */
+#define WM8955_LO1VU_WIDTH 1 /* LO1VU */
+#define WM8955_LO1ZC 0x0080 /* LO1ZC */
+#define WM8955_LO1ZC_MASK 0x0080 /* LO1ZC */
+#define WM8955_LO1ZC_SHIFT 7 /* LO1ZC */
+#define WM8955_LO1ZC_WIDTH 1 /* LO1ZC */
+#define WM8955_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */
+#define WM8955_LOUTVOL_SHIFT 0 /* LOUTVOL - [6:0] */
+#define WM8955_LOUTVOL_WIDTH 7 /* LOUTVOL - [6:0] */
+
+/*
+ * R3 (0x03) - ROUT1 volume
+ */
+#define WM8955_RO1VU 0x0100 /* RO1VU */
+#define WM8955_RO1VU_MASK 0x0100 /* RO1VU */
+#define WM8955_RO1VU_SHIFT 8 /* RO1VU */
+#define WM8955_RO1VU_WIDTH 1 /* RO1VU */
+#define WM8955_RO1ZC 0x0080 /* RO1ZC */
+#define WM8955_RO1ZC_MASK 0x0080 /* RO1ZC */
+#define WM8955_RO1ZC_SHIFT 7 /* RO1ZC */
+#define WM8955_RO1ZC_WIDTH 1 /* RO1ZC */
+#define WM8955_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */
+#define WM8955_ROUTVOL_SHIFT 0 /* ROUTVOL - [6:0] */
+#define WM8955_ROUTVOL_WIDTH 7 /* ROUTVOL - [6:0] */
+
+/*
+ * R5 (0x05) - DAC Control
+ */
+#define WM8955_DAT 0x0080 /* DAT */
+#define WM8955_DAT_MASK 0x0080 /* DAT */
+#define WM8955_DAT_SHIFT 7 /* DAT */
+#define WM8955_DAT_WIDTH 1 /* DAT */
+#define WM8955_DACMU 0x0008 /* DACMU */
+#define WM8955_DACMU_MASK 0x0008 /* DACMU */
+#define WM8955_DACMU_SHIFT 3 /* DACMU */
+#define WM8955_DACMU_WIDTH 1 /* DACMU */
+#define WM8955_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
+#define WM8955_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
+#define WM8955_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
+
+/*
+ * R7 (0x07) - Audio Interface
+ */
+#define WM8955_BCLKINV 0x0080 /* BCLKINV */
+#define WM8955_BCLKINV_MASK 0x0080 /* BCLKINV */
+#define WM8955_BCLKINV_SHIFT 7 /* BCLKINV */
+#define WM8955_BCLKINV_WIDTH 1 /* BCLKINV */
+#define WM8955_MS 0x0040 /* MS */
+#define WM8955_MS_MASK 0x0040 /* MS */
+#define WM8955_MS_SHIFT 6 /* MS */
+#define WM8955_MS_WIDTH 1 /* MS */
+#define WM8955_LRSWAP 0x0020 /* LRSWAP */
+#define WM8955_LRSWAP_MASK 0x0020 /* LRSWAP */
+#define WM8955_LRSWAP_SHIFT 5 /* LRSWAP */
+#define WM8955_LRSWAP_WIDTH 1 /* LRSWAP */
+#define WM8955_LRP 0x0010 /* LRP */
+#define WM8955_LRP_MASK 0x0010 /* LRP */
+#define WM8955_LRP_SHIFT 4 /* LRP */
+#define WM8955_LRP_WIDTH 1 /* LRP */
+#define WM8955_WL_MASK 0x000C /* WL - [3:2] */
+#define WM8955_WL_SHIFT 2 /* WL - [3:2] */
+#define WM8955_WL_WIDTH 2 /* WL - [3:2] */
+#define WM8955_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */
+#define WM8955_FORMAT_SHIFT 0 /* FORMAT - [1:0] */
+#define WM8955_FORMAT_WIDTH 2 /* FORMAT - [1:0] */
+
+/*
+ * R8 (0x08) - Sample Rate
+ */
+#define WM8955_BCLKDIV2 0x0080 /* BCLKDIV2 */
+#define WM8955_BCLKDIV2_MASK 0x0080 /* BCLKDIV2 */
+#define WM8955_BCLKDIV2_SHIFT 7 /* BCLKDIV2 */
+#define WM8955_BCLKDIV2_WIDTH 1 /* BCLKDIV2 */
+#define WM8955_MCLKDIV2 0x0040 /* MCLKDIV2 */
+#define WM8955_MCLKDIV2_MASK 0x0040 /* MCLKDIV2 */
+#define WM8955_MCLKDIV2_SHIFT 6 /* MCLKDIV2 */
+#define WM8955_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */
+#define WM8955_SR_MASK 0x003E /* SR - [5:1] */
+#define WM8955_SR_SHIFT 1 /* SR - [5:1] */
+#define WM8955_SR_WIDTH 5 /* SR - [5:1] */
+#define WM8955_USB 0x0001 /* USB */
+#define WM8955_USB_MASK 0x0001 /* USB */
+#define WM8955_USB_SHIFT 0 /* USB */
+#define WM8955_USB_WIDTH 1 /* USB */
+
+/*
+ * R10 (0x0A) - Left DAC volume
+ */
+#define WM8955_LDVU 0x0100 /* LDVU */
+#define WM8955_LDVU_MASK 0x0100 /* LDVU */
+#define WM8955_LDVU_SHIFT 8 /* LDVU */
+#define WM8955_LDVU_WIDTH 1 /* LDVU */
+#define WM8955_LDACVOL_MASK 0x00FF /* LDACVOL - [7:0] */
+#define WM8955_LDACVOL_SHIFT 0 /* LDACVOL - [7:0] */
+#define WM8955_LDACVOL_WIDTH 8 /* LDACVOL - [7:0] */
+
+/*
+ * R11 (0x0B) - Right DAC volume
+ */
+#define WM8955_RDVU 0x0100 /* RDVU */
+#define WM8955_RDVU_MASK 0x0100 /* RDVU */
+#define WM8955_RDVU_SHIFT 8 /* RDVU */
+#define WM8955_RDVU_WIDTH 1 /* RDVU */
+#define WM8955_RDACVOL_MASK 0x00FF /* RDACVOL - [7:0] */
+#define WM8955_RDACVOL_SHIFT 0 /* RDACVOL - [7:0] */
+#define WM8955_RDACVOL_WIDTH 8 /* RDACVOL - [7:0] */
+
+/*
+ * R12 (0x0C) - Bass control
+ */
+#define WM8955_BB 0x0080 /* BB */
+#define WM8955_BB_MASK 0x0080 /* BB */
+#define WM8955_BB_SHIFT 7 /* BB */
+#define WM8955_BB_WIDTH 1 /* BB */
+#define WM8955_BC 0x0040 /* BC */
+#define WM8955_BC_MASK 0x0040 /* BC */
+#define WM8955_BC_SHIFT 6 /* BC */
+#define WM8955_BC_WIDTH 1 /* BC */
+#define WM8955_BASS_MASK 0x000F /* BASS - [3:0] */
+#define WM8955_BASS_SHIFT 0 /* BASS - [3:0] */
+#define WM8955_BASS_WIDTH 4 /* BASS - [3:0] */
+
+/*
+ * R13 (0x0D) - Treble control
+ */
+#define WM8955_TC 0x0040 /* TC */
+#define WM8955_TC_MASK 0x0040 /* TC */
+#define WM8955_TC_SHIFT 6 /* TC */
+#define WM8955_TC_WIDTH 1 /* TC */
+#define WM8955_TRBL_MASK 0x000F /* TRBL - [3:0] */
+#define WM8955_TRBL_SHIFT 0 /* TRBL - [3:0] */
+#define WM8955_TRBL_WIDTH 4 /* TRBL - [3:0] */
+
+/*
+ * R15 (0x0F) - Reset
+ */
+#define WM8955_RESET_MASK 0x01FF /* RESET - [8:0] */
+#define WM8955_RESET_SHIFT 0 /* RESET - [8:0] */
+#define WM8955_RESET_WIDTH 9 /* RESET - [8:0] */
+
+/*
+ * R23 (0x17) - Additional control (1)
+ */
+#define WM8955_TSDEN 0x0100 /* TSDEN */
+#define WM8955_TSDEN_MASK 0x0100 /* TSDEN */
+#define WM8955_TSDEN_SHIFT 8 /* TSDEN */
+#define WM8955_TSDEN_WIDTH 1 /* TSDEN */
+#define WM8955_VSEL_MASK 0x00C0 /* VSEL - [7:6] */
+#define WM8955_VSEL_SHIFT 6 /* VSEL - [7:6] */
+#define WM8955_VSEL_WIDTH 2 /* VSEL - [7:6] */
+#define WM8955_DMONOMIX_MASK 0x0030 /* DMONOMIX - [5:4] */
+#define WM8955_DMONOMIX_SHIFT 4 /* DMONOMIX - [5:4] */
+#define WM8955_DMONOMIX_WIDTH 2 /* DMONOMIX - [5:4] */
+#define WM8955_DACINV 0x0002 /* DACINV */
+#define WM8955_DACINV_MASK 0x0002 /* DACINV */
+#define WM8955_DACINV_SHIFT 1 /* DACINV */
+#define WM8955_DACINV_WIDTH 1 /* DACINV */
+#define WM8955_TOEN 0x0001 /* TOEN */
+#define WM8955_TOEN_MASK 0x0001 /* TOEN */
+#define WM8955_TOEN_SHIFT 0 /* TOEN */
+#define WM8955_TOEN_WIDTH 1 /* TOEN */
+
+/*
+ * R24 (0x18) - Additional control (2)
+ */
+#define WM8955_OUT3SW_MASK 0x0180 /* OUT3SW - [8:7] */
+#define WM8955_OUT3SW_SHIFT 7 /* OUT3SW - [8:7] */
+#define WM8955_OUT3SW_WIDTH 2 /* OUT3SW - [8:7] */
+#define WM8955_ROUT2INV 0x0010 /* ROUT2INV */
+#define WM8955_ROUT2INV_MASK 0x0010 /* ROUT2INV */
+#define WM8955_ROUT2INV_SHIFT 4 /* ROUT2INV */
+#define WM8955_ROUT2INV_WIDTH 1 /* ROUT2INV */
+#define WM8955_DACOSR 0x0001 /* DACOSR */
+#define WM8955_DACOSR_MASK 0x0001 /* DACOSR */
+#define WM8955_DACOSR_SHIFT 0 /* DACOSR */
+#define WM8955_DACOSR_WIDTH 1 /* DACOSR */
+
+/*
+ * R25 (0x19) - Power Management (1)
+ */
+#define WM8955_VMIDSEL_MASK 0x0180 /* VMIDSEL - [8:7] */
+#define WM8955_VMIDSEL_SHIFT 7 /* VMIDSEL - [8:7] */
+#define WM8955_VMIDSEL_WIDTH 2 /* VMIDSEL - [8:7] */
+#define WM8955_VREF 0x0040 /* VREF */
+#define WM8955_VREF_MASK 0x0040 /* VREF */
+#define WM8955_VREF_SHIFT 6 /* VREF */
+#define WM8955_VREF_WIDTH 1 /* VREF */
+#define WM8955_DIGENB 0x0001 /* DIGENB */
+#define WM8955_DIGENB_MASK 0x0001 /* DIGENB */
+#define WM8955_DIGENB_SHIFT 0 /* DIGENB */
+#define WM8955_DIGENB_WIDTH 1 /* DIGENB */
+
+/*
+ * R26 (0x1A) - Power Management (2)
+ */
+#define WM8955_DACL 0x0100 /* DACL */
+#define WM8955_DACL_MASK 0x0100 /* DACL */
+#define WM8955_DACL_SHIFT 8 /* DACL */
+#define WM8955_DACL_WIDTH 1 /* DACL */
+#define WM8955_DACR 0x0080 /* DACR */
+#define WM8955_DACR_MASK 0x0080 /* DACR */
+#define WM8955_DACR_SHIFT 7 /* DACR */
+#define WM8955_DACR_WIDTH 1 /* DACR */
+#define WM8955_LOUT1 0x0040 /* LOUT1 */
+#define WM8955_LOUT1_MASK 0x0040 /* LOUT1 */
+#define WM8955_LOUT1_SHIFT 6 /* LOUT1 */
+#define WM8955_LOUT1_WIDTH 1 /* LOUT1 */
+#define WM8955_ROUT1 0x0020 /* ROUT1 */
+#define WM8955_ROUT1_MASK 0x0020 /* ROUT1 */
+#define WM8955_ROUT1_SHIFT 5 /* ROUT1 */
+#define WM8955_ROUT1_WIDTH 1 /* ROUT1 */
+#define WM8955_LOUT2 0x0010 /* LOUT2 */
+#define WM8955_LOUT2_MASK 0x0010 /* LOUT2 */
+#define WM8955_LOUT2_SHIFT 4 /* LOUT2 */
+#define WM8955_LOUT2_WIDTH 1 /* LOUT2 */
+#define WM8955_ROUT2 0x0008 /* ROUT2 */
+#define WM8955_ROUT2_MASK 0x0008 /* ROUT2 */
+#define WM8955_ROUT2_SHIFT 3 /* ROUT2 */
+#define WM8955_ROUT2_WIDTH 1 /* ROUT2 */
+#define WM8955_MONO 0x0004 /* MONO */
+#define WM8955_MONO_MASK 0x0004 /* MONO */
+#define WM8955_MONO_SHIFT 2 /* MONO */
+#define WM8955_MONO_WIDTH 1 /* MONO */
+#define WM8955_OUT3 0x0002 /* OUT3 */
+#define WM8955_OUT3_MASK 0x0002 /* OUT3 */
+#define WM8955_OUT3_SHIFT 1 /* OUT3 */
+#define WM8955_OUT3_WIDTH 1 /* OUT3 */
+
+/*
+ * R27 (0x1B) - Additional Control (3)
+ */
+#define WM8955_VROI 0x0040 /* VROI */
+#define WM8955_VROI_MASK 0x0040 /* VROI */
+#define WM8955_VROI_SHIFT 6 /* VROI */
+#define WM8955_VROI_WIDTH 1 /* VROI */
+
+/*
+ * R34 (0x22) - Left out Mix (1)
+ */
+#define WM8955_LD2LO 0x0100 /* LD2LO */
+#define WM8955_LD2LO_MASK 0x0100 /* LD2LO */
+#define WM8955_LD2LO_SHIFT 8 /* LD2LO */
+#define WM8955_LD2LO_WIDTH 1 /* LD2LO */
+#define WM8955_LI2LO 0x0080 /* LI2LO */
+#define WM8955_LI2LO_MASK 0x0080 /* LI2LO */
+#define WM8955_LI2LO_SHIFT 7 /* LI2LO */
+#define WM8955_LI2LO_WIDTH 1 /* LI2LO */
+#define WM8955_LI2LOVOL_MASK 0x0070 /* LI2LOVOL - [6:4] */
+#define WM8955_LI2LOVOL_SHIFT 4 /* LI2LOVOL - [6:4] */
+#define WM8955_LI2LOVOL_WIDTH 3 /* LI2LOVOL - [6:4] */
+
+/*
+ * R35 (0x23) - Left out Mix (2)
+ */
+#define WM8955_RD2LO 0x0100 /* RD2LO */
+#define WM8955_RD2LO_MASK 0x0100 /* RD2LO */
+#define WM8955_RD2LO_SHIFT 8 /* RD2LO */
+#define WM8955_RD2LO_WIDTH 1 /* RD2LO */
+#define WM8955_RI2LO 0x0080 /* RI2LO */
+#define WM8955_RI2LO_MASK 0x0080 /* RI2LO */
+#define WM8955_RI2LO_SHIFT 7 /* RI2LO */
+#define WM8955_RI2LO_WIDTH 1 /* RI2LO */
+#define WM8955_RI2LOVOL_MASK 0x0070 /* RI2LOVOL - [6:4] */
+#define WM8955_RI2LOVOL_SHIFT 4 /* RI2LOVOL - [6:4] */
+#define WM8955_RI2LOVOL_WIDTH 3 /* RI2LOVOL - [6:4] */
+
+/*
+ * R36 (0x24) - Right out Mix (1)
+ */
+#define WM8955_LD2RO 0x0100 /* LD2RO */
+#define WM8955_LD2RO_MASK 0x0100 /* LD2RO */
+#define WM8955_LD2RO_SHIFT 8 /* LD2RO */
+#define WM8955_LD2RO_WIDTH 1 /* LD2RO */
+#define WM8955_LI2RO 0x0080 /* LI2RO */
+#define WM8955_LI2RO_MASK 0x0080 /* LI2RO */
+#define WM8955_LI2RO_SHIFT 7 /* LI2RO */
+#define WM8955_LI2RO_WIDTH 1 /* LI2RO */
+#define WM8955_LI2ROVOL_MASK 0x0070 /* LI2ROVOL - [6:4] */
+#define WM8955_LI2ROVOL_SHIFT 4 /* LI2ROVOL - [6:4] */
+#define WM8955_LI2ROVOL_WIDTH 3 /* LI2ROVOL - [6:4] */
+
+/*
+ * R37 (0x25) - Right Out Mix (2)
+ */
+#define WM8955_RD2RO 0x0100 /* RD2RO */
+#define WM8955_RD2RO_MASK 0x0100 /* RD2RO */
+#define WM8955_RD2RO_SHIFT 8 /* RD2RO */
+#define WM8955_RD2RO_WIDTH 1 /* RD2RO */
+#define WM8955_RI2RO 0x0080 /* RI2RO */
+#define WM8955_RI2RO_MASK 0x0080 /* RI2RO */
+#define WM8955_RI2RO_SHIFT 7 /* RI2RO */
+#define WM8955_RI2RO_WIDTH 1 /* RI2RO */
+#define WM8955_RI2ROVOL_MASK 0x0070 /* RI2ROVOL - [6:4] */
+#define WM8955_RI2ROVOL_SHIFT 4 /* RI2ROVOL - [6:4] */
+#define WM8955_RI2ROVOL_WIDTH 3 /* RI2ROVOL - [6:4] */
+
+/*
+ * R38 (0x26) - Mono out Mix (1)
+ */
+#define WM8955_LD2MO 0x0100 /* LD2MO */
+#define WM8955_LD2MO_MASK 0x0100 /* LD2MO */
+#define WM8955_LD2MO_SHIFT 8 /* LD2MO */
+#define WM8955_LD2MO_WIDTH 1 /* LD2MO */
+#define WM8955_LI2MO 0x0080 /* LI2MO */
+#define WM8955_LI2MO_MASK 0x0080 /* LI2MO */
+#define WM8955_LI2MO_SHIFT 7 /* LI2MO */
+#define WM8955_LI2MO_WIDTH 1 /* LI2MO */
+#define WM8955_LI2MOVOL_MASK 0x0070 /* LI2MOVOL - [6:4] */
+#define WM8955_LI2MOVOL_SHIFT 4 /* LI2MOVOL - [6:4] */
+#define WM8955_LI2MOVOL_WIDTH 3 /* LI2MOVOL - [6:4] */
+#define WM8955_DMEN 0x0001 /* DMEN */
+#define WM8955_DMEN_MASK 0x0001 /* DMEN */
+#define WM8955_DMEN_SHIFT 0 /* DMEN */
+#define WM8955_DMEN_WIDTH 1 /* DMEN */
+
+/*
+ * R39 (0x27) - Mono out Mix (2)
+ */
+#define WM8955_RD2MO 0x0100 /* RD2MO */
+#define WM8955_RD2MO_MASK 0x0100 /* RD2MO */
+#define WM8955_RD2MO_SHIFT 8 /* RD2MO */
+#define WM8955_RD2MO_WIDTH 1 /* RD2MO */
+#define WM8955_RI2MO 0x0080 /* RI2MO */
+#define WM8955_RI2MO_MASK 0x0080 /* RI2MO */
+#define WM8955_RI2MO_SHIFT 7 /* RI2MO */
+#define WM8955_RI2MO_WIDTH 1 /* RI2MO */
+#define WM8955_RI2MOVOL_MASK 0x0070 /* RI2MOVOL - [6:4] */
+#define WM8955_RI2MOVOL_SHIFT 4 /* RI2MOVOL - [6:4] */
+#define WM8955_RI2MOVOL_WIDTH 3 /* RI2MOVOL - [6:4] */
+
+/*
+ * R40 (0x28) - LOUT2 volume
+ */
+#define WM8955_LO2VU 0x0100 /* LO2VU */
+#define WM8955_LO2VU_MASK 0x0100 /* LO2VU */
+#define WM8955_LO2VU_SHIFT 8 /* LO2VU */
+#define WM8955_LO2VU_WIDTH 1 /* LO2VU */
+#define WM8955_LO2ZC 0x0080 /* LO2ZC */
+#define WM8955_LO2ZC_MASK 0x0080 /* LO2ZC */
+#define WM8955_LO2ZC_SHIFT 7 /* LO2ZC */
+#define WM8955_LO2ZC_WIDTH 1 /* LO2ZC */
+#define WM8955_LOUT2VOL_MASK 0x007F /* LOUT2VOL - [6:0] */
+#define WM8955_LOUT2VOL_SHIFT 0 /* LOUT2VOL - [6:0] */
+#define WM8955_LOUT2VOL_WIDTH 7 /* LOUT2VOL - [6:0] */
+
+/*
+ * R41 (0x29) - ROUT2 volume
+ */
+#define WM8955_RO2VU 0x0100 /* RO2VU */
+#define WM8955_RO2VU_MASK 0x0100 /* RO2VU */
+#define WM8955_RO2VU_SHIFT 8 /* RO2VU */
+#define WM8955_RO2VU_WIDTH 1 /* RO2VU */
+#define WM8955_RO2ZC 0x0080 /* RO2ZC */
+#define WM8955_RO2ZC_MASK 0x0080 /* RO2ZC */
+#define WM8955_RO2ZC_SHIFT 7 /* RO2ZC */
+#define WM8955_RO2ZC_WIDTH 1 /* RO2ZC */
+#define WM8955_ROUT2VOL_MASK 0x007F /* ROUT2VOL - [6:0] */
+#define WM8955_ROUT2VOL_SHIFT 0 /* ROUT2VOL - [6:0] */
+#define WM8955_ROUT2VOL_WIDTH 7 /* ROUT2VOL - [6:0] */
+
+/*
+ * R42 (0x2A) - MONOOUT volume
+ */
+#define WM8955_MOZC 0x0080 /* MOZC */
+#define WM8955_MOZC_MASK 0x0080 /* MOZC */
+#define WM8955_MOZC_SHIFT 7 /* MOZC */
+#define WM8955_MOZC_WIDTH 1 /* MOZC */
+#define WM8955_MOUTVOL_MASK 0x007F /* MOUTVOL - [6:0] */
+#define WM8955_MOUTVOL_SHIFT 0 /* MOUTVOL - [6:0] */
+#define WM8955_MOUTVOL_WIDTH 7 /* MOUTVOL - [6:0] */
+
+/*
+ * R43 (0x2B) - Clocking / PLL
+ */
+#define WM8955_MCLKSEL 0x0100 /* MCLKSEL */
+#define WM8955_MCLKSEL_MASK 0x0100 /* MCLKSEL */
+#define WM8955_MCLKSEL_SHIFT 8 /* MCLKSEL */
+#define WM8955_MCLKSEL_WIDTH 1 /* MCLKSEL */
+#define WM8955_PLLOUTDIV2 0x0020 /* PLLOUTDIV2 */
+#define WM8955_PLLOUTDIV2_MASK 0x0020 /* PLLOUTDIV2 */
+#define WM8955_PLLOUTDIV2_SHIFT 5 /* PLLOUTDIV2 */
+#define WM8955_PLLOUTDIV2_WIDTH 1 /* PLLOUTDIV2 */
+#define WM8955_PLL_RB 0x0010 /* PLL_RB */
+#define WM8955_PLL_RB_MASK 0x0010 /* PLL_RB */
+#define WM8955_PLL_RB_SHIFT 4 /* PLL_RB */
+#define WM8955_PLL_RB_WIDTH 1 /* PLL_RB */
+#define WM8955_PLLEN 0x0008 /* PLLEN */
+#define WM8955_PLLEN_MASK 0x0008 /* PLLEN */
+#define WM8955_PLLEN_SHIFT 3 /* PLLEN */
+#define WM8955_PLLEN_WIDTH 1 /* PLLEN */
+
+/*
+ * R44 (0x2C) - PLL Control 1
+ */
+#define WM8955_N_MASK 0x01E0 /* N - [8:5] */
+#define WM8955_N_SHIFT 5 /* N - [8:5] */
+#define WM8955_N_WIDTH 4 /* N - [8:5] */
+#define WM8955_K_21_18_MASK 0x000F /* K(21:18) - [3:0] */
+#define WM8955_K_21_18_SHIFT 0 /* K(21:18) - [3:0] */
+#define WM8955_K_21_18_WIDTH 4 /* K(21:18) - [3:0] */
+
+/*
+ * R45 (0x2D) - PLL Control 2
+ */
+#define WM8955_K_17_9_MASK 0x01FF /* K(17:9) - [8:0] */
+#define WM8955_K_17_9_SHIFT 0 /* K(17:9) - [8:0] */
+#define WM8955_K_17_9_WIDTH 9 /* K(17:9) - [8:0] */
+
+/*
+ * R46 (0x2E) - PLL Control 3
+ */
+#define WM8955_K_8_0_MASK 0x01FF /* K(8:0) - [8:0] */
+#define WM8955_K_8_0_SHIFT 0 /* K(8:0) - [8:0] */
+#define WM8955_K_8_0_WIDTH 9 /* K(8:0) - [8:0] */
+
+/*
+ * R59 (0x3B) - PLL Control 4
+ */
+#define WM8955_KEN 0x0080 /* KEN */
+#define WM8955_KEN_MASK 0x0080 /* KEN */
+#define WM8955_KEN_SHIFT 7 /* KEN */
+#define WM8955_KEN_WIDTH 1 /* KEN */
+
+#endif
diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c
new file mode 100644
index 00000000..0293763d
--- /dev/null
+++ b/sound/soc/codecs/wm8958-dsp2.c
@@ -0,0 +1,1051 @@
+/*
+ * wm8958-dsp2.c -- WM8958 DSP2 support
+ *
+ * Copyright 2011 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <trace/events/asoc.h>
+
+#include <linux/mfd/wm8994/core.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <linux/mfd/wm8994/pdata.h>
+#include <linux/mfd/wm8994/gpio.h>
+
+#include "wm8994.h"
+
+#define WM_FW_BLOCK_INFO 0xff
+#define WM_FW_BLOCK_PM 0x00
+#define WM_FW_BLOCK_X 0x01
+#define WM_FW_BLOCK_Y 0x02
+#define WM_FW_BLOCK_Z 0x03
+#define WM_FW_BLOCK_I 0x06
+#define WM_FW_BLOCK_A 0x08
+#define WM_FW_BLOCK_C 0x0c
+
+static int wm8958_dsp2_fw(struct snd_soc_codec *codec, const char *name,
+ const struct firmware *fw, bool check)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ u64 data64;
+ u32 data32;
+ const u8 *data;
+ char *str;
+ size_t block_len, len;
+ int ret = 0;
+
+ /* Suppress unneeded downloads */
+ if (wm8994->cur_fw == fw)
+ return 0;
+
+ if (fw->size < 32) {
+ dev_err(codec->dev, "%s: firmware too short\n", name);
+ goto err;
+ }
+
+ if (memcmp(fw->data, "WMFW", 4) != 0) {
+ dev_err(codec->dev, "%s: firmware has bad file magic %08x\n",
+ name, data32);
+ goto err;
+ }
+
+ memcpy(&data32, fw->data + 4, sizeof(data32));
+ len = be32_to_cpu(data32);
+
+ memcpy(&data32, fw->data + 8, sizeof(data32));
+ data32 = be32_to_cpu(data32);
+ if ((data32 >> 24) & 0xff) {
+ dev_err(codec->dev, "%s: unsupported firmware version %d\n",
+ name, (data32 >> 24) & 0xff);
+ goto err;
+ }
+ if ((data32 & 0xffff) != 8958) {
+ dev_err(codec->dev, "%s: unsupported target device %d\n",
+ name, data32 & 0xffff);
+ goto err;
+ }
+ if (((data32 >> 16) & 0xff) != 0xc) {
+ dev_err(codec->dev, "%s: unsupported target core %d\n",
+ name, (data32 >> 16) & 0xff);
+ goto err;
+ }
+
+ if (check) {
+ memcpy(&data64, fw->data + 24, sizeof(u64));
+ dev_info(codec->dev, "%s timestamp %llx\n",
+ name, be64_to_cpu(data64));
+ } else {
+ snd_soc_write(codec, 0x102, 0x2);
+ snd_soc_write(codec, 0x900, 0x2);
+ }
+
+ data = fw->data + len;
+ len = fw->size - len;
+ while (len) {
+ if (len < 12) {
+ dev_err(codec->dev, "%s short data block of %zd\n",
+ name, len);
+ goto err;
+ }
+
+ memcpy(&data32, data + 4, sizeof(data32));
+ block_len = be32_to_cpu(data32);
+ if (block_len + 8 > len) {
+ dev_err(codec->dev, "%zd byte block longer than file\n",
+ block_len);
+ goto err;
+ }
+ if (block_len == 0) {
+ dev_err(codec->dev, "Zero length block\n");
+ goto err;
+ }
+
+ memcpy(&data32, data, sizeof(data32));
+ data32 = be32_to_cpu(data32);
+
+ switch ((data32 >> 24) & 0xff) {
+ case WM_FW_BLOCK_INFO:
+ /* Informational text */
+ if (!check)
+ break;
+
+ str = kzalloc(block_len + 1, GFP_KERNEL);
+ if (str) {
+ memcpy(str, data + 8, block_len);
+ dev_info(codec->dev, "%s: %s\n", name, str);
+ kfree(str);
+ } else {
+ dev_err(codec->dev, "Out of memory\n");
+ }
+ break;
+ case WM_FW_BLOCK_PM:
+ case WM_FW_BLOCK_X:
+ case WM_FW_BLOCK_Y:
+ case WM_FW_BLOCK_Z:
+ case WM_FW_BLOCK_I:
+ case WM_FW_BLOCK_A:
+ case WM_FW_BLOCK_C:
+ dev_dbg(codec->dev, "%s: %zd bytes of %x@%x\n", name,
+ block_len, (data32 >> 24) & 0xff,
+ data32 & 0xffffff);
+
+ if (check)
+ break;
+
+ data32 &= 0xffffff;
+
+ wm8994_bulk_write(codec->control_data,
+ data32 & 0xffffff,
+ block_len / 2,
+ (void *)(data + 8));
+
+ break;
+ default:
+ dev_warn(codec->dev, "%s: unknown block type %d\n",
+ name, (data32 >> 24) & 0xff);
+ break;
+ }
+
+ /* Round up to the next 32 bit word */
+ block_len += block_len % 4;
+
+ data += block_len + 8;
+ len -= block_len + 8;
+ }
+
+ if (!check) {
+ dev_dbg(codec->dev, "%s: download done\n", name);
+ wm8994->cur_fw = fw;
+ } else {
+ dev_info(codec->dev, "%s: got firmware\n", name);
+ }
+
+ goto ok;
+
+err:
+ ret = -EINVAL;
+ok:
+ if (!check) {
+ snd_soc_write(codec, 0x900, 0x0);
+ snd_soc_write(codec, 0x102, 0x0);
+ }
+
+ return ret;
+}
+
+static void wm8958_dsp_start_mbc(struct snd_soc_codec *codec, int path)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int i;
+
+ /* If the DSP is already running then noop */
+ if (snd_soc_read(codec, WM8958_DSP2_PROGRAM) & WM8958_DSP2_ENA)
+ return;
+
+ /* If we have MBC firmware download it */
+ if (wm8994->mbc)
+ wm8958_dsp2_fw(codec, "MBC", wm8994->mbc, false);
+
+ snd_soc_update_bits(codec, WM8958_DSP2_PROGRAM,
+ WM8958_DSP2_ENA, WM8958_DSP2_ENA);
+
+ /* If we've got user supplied MBC settings use them */
+ if (pdata && pdata->num_mbc_cfgs) {
+ struct wm8958_mbc_cfg *cfg
+ = &pdata->mbc_cfgs[wm8994->mbc_cfg];
+
+ for (i = 0; i < ARRAY_SIZE(cfg->coeff_regs); i++)
+ snd_soc_write(codec, i + WM8958_MBC_BAND_1_K_1,
+ cfg->coeff_regs[i]);
+
+ for (i = 0; i < ARRAY_SIZE(cfg->cutoff_regs); i++)
+ snd_soc_write(codec,
+ i + WM8958_MBC_BAND_2_LOWER_CUTOFF_C1_1,
+ cfg->cutoff_regs[i]);
+ }
+
+ /* Run the DSP */
+ snd_soc_write(codec, WM8958_DSP2_EXECCONTROL,
+ WM8958_DSP2_RUNR);
+
+ /* And we're off! */
+ snd_soc_update_bits(codec, WM8958_DSP2_CONFIG,
+ WM8958_MBC_ENA |
+ WM8958_MBC_SEL_MASK,
+ path << WM8958_MBC_SEL_SHIFT |
+ WM8958_MBC_ENA);
+}
+
+static void wm8958_dsp_start_vss(struct snd_soc_codec *codec, int path)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int i, ena;
+
+ if (wm8994->mbc_vss)
+ wm8958_dsp2_fw(codec, "MBC+VSS", wm8994->mbc_vss, false);
+
+ snd_soc_update_bits(codec, WM8958_DSP2_PROGRAM,
+ WM8958_DSP2_ENA, WM8958_DSP2_ENA);
+
+ /* If we've got user supplied settings use them */
+ if (pdata && pdata->num_mbc_cfgs) {
+ struct wm8958_mbc_cfg *cfg
+ = &pdata->mbc_cfgs[wm8994->mbc_cfg];
+
+ for (i = 0; i < ARRAY_SIZE(cfg->combined_regs); i++)
+ snd_soc_write(codec, i + 0x2800,
+ cfg->combined_regs[i]);
+ }
+
+ if (pdata && pdata->num_vss_cfgs) {
+ struct wm8958_vss_cfg *cfg
+ = &pdata->vss_cfgs[wm8994->vss_cfg];
+
+ for (i = 0; i < ARRAY_SIZE(cfg->regs); i++)
+ snd_soc_write(codec, i + 0x2600, cfg->regs[i]);
+ }
+
+ if (pdata && pdata->num_vss_hpf_cfgs) {
+ struct wm8958_vss_hpf_cfg *cfg
+ = &pdata->vss_hpf_cfgs[wm8994->vss_hpf_cfg];
+
+ for (i = 0; i < ARRAY_SIZE(cfg->regs); i++)
+ snd_soc_write(codec, i + 0x2400, cfg->regs[i]);
+ }
+
+ /* Run the DSP */
+ snd_soc_write(codec, WM8958_DSP2_EXECCONTROL,
+ WM8958_DSP2_RUNR);
+
+ /* Enable the algorithms we've selected */
+ ena = 0;
+ if (wm8994->mbc_ena[path])
+ ena |= 0x8;
+ if (wm8994->hpf2_ena[path])
+ ena |= 0x4;
+ if (wm8994->hpf1_ena[path])
+ ena |= 0x2;
+ if (wm8994->vss_ena[path])
+ ena |= 0x1;
+
+ snd_soc_write(codec, 0x2201, ena);
+
+ /* Switch the DSP into the data path */
+ snd_soc_update_bits(codec, WM8958_DSP2_CONFIG,
+ WM8958_MBC_SEL_MASK | WM8958_MBC_ENA,
+ path << WM8958_MBC_SEL_SHIFT | WM8958_MBC_ENA);
+}
+
+static void wm8958_dsp_start_enh_eq(struct snd_soc_codec *codec, int path)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int i;
+
+ wm8958_dsp2_fw(codec, "ENH_EQ", wm8994->enh_eq, false);
+
+ snd_soc_update_bits(codec, WM8958_DSP2_PROGRAM,
+ WM8958_DSP2_ENA, WM8958_DSP2_ENA);
+
+ /* If we've got user supplied settings use them */
+ if (pdata && pdata->num_enh_eq_cfgs) {
+ struct wm8958_enh_eq_cfg *cfg
+ = &pdata->enh_eq_cfgs[wm8994->enh_eq_cfg];
+
+ for (i = 0; i < ARRAY_SIZE(cfg->regs); i++)
+ snd_soc_write(codec, i + 0x2200,
+ cfg->regs[i]);
+ }
+
+ /* Run the DSP */
+ snd_soc_write(codec, WM8958_DSP2_EXECCONTROL,
+ WM8958_DSP2_RUNR);
+
+ /* Switch the DSP into the data path */
+ snd_soc_update_bits(codec, WM8958_DSP2_CONFIG,
+ WM8958_MBC_SEL_MASK | WM8958_MBC_ENA,
+ path << WM8958_MBC_SEL_SHIFT | WM8958_MBC_ENA);
+}
+
+static void wm8958_dsp_apply(struct snd_soc_codec *codec, int path, int start)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int pwr_reg = snd_soc_read(codec, WM8994_POWER_MANAGEMENT_5);
+ int ena, reg, aif;
+
+ switch (path) {
+ case 0:
+ pwr_reg &= (WM8994_AIF1DAC1L_ENA | WM8994_AIF1DAC1R_ENA);
+ aif = 0;
+ break;
+ case 1:
+ pwr_reg &= (WM8994_AIF1DAC2L_ENA | WM8994_AIF1DAC2R_ENA);
+ aif = 0;
+ break;
+ case 2:
+ pwr_reg &= (WM8994_AIF2DACL_ENA | WM8994_AIF2DACR_ENA);
+ aif = 1;
+ break;
+ default:
+ BUG();
+ return;
+ }
+
+ /* Do we have both an active AIF and an active algorithm? */
+ ena = wm8994->mbc_ena[path] || wm8994->vss_ena[path] ||
+ wm8994->hpf1_ena[path] || wm8994->hpf2_ena[path] ||
+ wm8994->enh_eq_ena[path];
+ if (!pwr_reg)
+ ena = 0;
+
+ reg = snd_soc_read(codec, WM8958_DSP2_PROGRAM);
+
+ dev_dbg(codec->dev, "DSP path %d %d startup: %d, power: %x, DSP: %x\n",
+ path, wm8994->dsp_active, start, pwr_reg, reg);
+
+ if (start && ena) {
+ /* If the DSP is already running then noop */
+ if (reg & WM8958_DSP2_ENA)
+ return;
+
+ /* If either AIFnCLK is not yet enabled postpone */
+ if (!(snd_soc_read(codec, WM8994_AIF1_CLOCKING_1)
+ & WM8994_AIF1CLK_ENA_MASK) &&
+ !(snd_soc_read(codec, WM8994_AIF2_CLOCKING_1)
+ & WM8994_AIF2CLK_ENA_MASK))
+ return;
+
+ /* Switch the clock over to the appropriate AIF */
+ snd_soc_update_bits(codec, WM8994_CLOCKING_1,
+ WM8958_DSP2CLK_SRC | WM8958_DSP2CLK_ENA,
+ aif << WM8958_DSP2CLK_SRC_SHIFT |
+ WM8958_DSP2CLK_ENA);
+
+ if (wm8994->enh_eq_ena[path])
+ wm8958_dsp_start_enh_eq(codec, path);
+ else if (wm8994->vss_ena[path] || wm8994->hpf1_ena[path] ||
+ wm8994->hpf2_ena[path])
+ wm8958_dsp_start_vss(codec, path);
+ else if (wm8994->mbc_ena[path])
+ wm8958_dsp_start_mbc(codec, path);
+
+ wm8994->dsp_active = path;
+
+ dev_dbg(codec->dev, "DSP running in path %d\n", path);
+ }
+
+ if (!start && wm8994->dsp_active == path) {
+ /* If the DSP is already stopped then noop */
+ if (!(reg & WM8958_DSP2_ENA))
+ return;
+
+ snd_soc_update_bits(codec, WM8958_DSP2_CONFIG,
+ WM8958_MBC_ENA, 0);
+ snd_soc_write(codec, WM8958_DSP2_EXECCONTROL,
+ WM8958_DSP2_STOP);
+ snd_soc_update_bits(codec, WM8958_DSP2_PROGRAM,
+ WM8958_DSP2_ENA, 0);
+ snd_soc_update_bits(codec, WM8994_CLOCKING_1,
+ WM8958_DSP2CLK_ENA, 0);
+
+ wm8994->dsp_active = -1;
+
+ dev_dbg(codec->dev, "DSP stopped\n");
+ }
+}
+
+int wm8958_aif_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ int i;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ case SND_SOC_DAPM_PRE_PMU:
+ for (i = 0; i < 3; i++)
+ wm8958_dsp_apply(codec, i, 1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ case SND_SOC_DAPM_PRE_PMD:
+ for (i = 0; i < 3; i++)
+ wm8958_dsp_apply(codec, i, 0);
+ break;
+ }
+
+ return 0;
+}
+
+/* Check if DSP2 is in use on another AIF */
+static int wm8958_dsp2_busy(struct wm8994_priv *wm8994, int aif)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8994->mbc_ena); i++) {
+ if (i == aif)
+ continue;
+ if (wm8994->mbc_ena[i] || wm8994->vss_ena[i] ||
+ wm8994->hpf1_ena[i] || wm8994->hpf2_ena[i])
+ return 1;
+ }
+
+ return 0;
+}
+
+static int wm8958_put_mbc_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int value = ucontrol->value.integer.value[0];
+ int reg;
+
+ /* Don't allow on the fly reconfiguration */
+ reg = snd_soc_read(codec, WM8994_CLOCKING_1);
+ if (reg < 0 || reg & WM8958_DSP2CLK_ENA)
+ return -EBUSY;
+
+ if (value >= pdata->num_mbc_cfgs)
+ return -EINVAL;
+
+ wm8994->mbc_cfg = value;
+
+ return 0;
+}
+
+static int wm8958_get_mbc_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8994->mbc_cfg;
+
+ return 0;
+}
+
+static int wm8958_mbc_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int wm8958_mbc_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int mbc = kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = wm8994->mbc_ena[mbc];
+
+ return 0;
+}
+
+static int wm8958_mbc_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int mbc = kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (wm8994->mbc_ena[mbc] == ucontrol->value.integer.value[0])
+ return 0;
+
+ if (ucontrol->value.integer.value[0] > 1)
+ return -EINVAL;
+
+ if (wm8958_dsp2_busy(wm8994, mbc)) {
+ dev_dbg(codec->dev, "DSP2 active on %d already\n", mbc);
+ return -EBUSY;
+ }
+
+ if (wm8994->enh_eq_ena[mbc])
+ return -EBUSY;
+
+ wm8994->mbc_ena[mbc] = ucontrol->value.integer.value[0];
+
+ wm8958_dsp_apply(codec, mbc, wm8994->mbc_ena[mbc]);
+
+ return 0;
+}
+
+#define WM8958_MBC_SWITCH(xname, xval) {\
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .info = wm8958_mbc_info, \
+ .get = wm8958_mbc_get, .put = wm8958_mbc_put, \
+ .private_value = xval }
+
+static int wm8958_put_vss_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int value = ucontrol->value.integer.value[0];
+ int reg;
+
+ /* Don't allow on the fly reconfiguration */
+ reg = snd_soc_read(codec, WM8994_CLOCKING_1);
+ if (reg < 0 || reg & WM8958_DSP2CLK_ENA)
+ return -EBUSY;
+
+ if (value >= pdata->num_vss_cfgs)
+ return -EINVAL;
+
+ wm8994->vss_cfg = value;
+
+ return 0;
+}
+
+static int wm8958_get_vss_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8994->vss_cfg;
+
+ return 0;
+}
+
+static int wm8958_put_vss_hpf_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int value = ucontrol->value.integer.value[0];
+ int reg;
+
+ /* Don't allow on the fly reconfiguration */
+ reg = snd_soc_read(codec, WM8994_CLOCKING_1);
+ if (reg < 0 || reg & WM8958_DSP2CLK_ENA)
+ return -EBUSY;
+
+ if (value >= pdata->num_vss_hpf_cfgs)
+ return -EINVAL;
+
+ wm8994->vss_hpf_cfg = value;
+
+ return 0;
+}
+
+static int wm8958_get_vss_hpf_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8994->vss_hpf_cfg;
+
+ return 0;
+}
+
+static int wm8958_vss_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int wm8958_vss_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int vss = kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = wm8994->vss_ena[vss];
+
+ return 0;
+}
+
+static int wm8958_vss_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int vss = kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (wm8994->vss_ena[vss] == ucontrol->value.integer.value[0])
+ return 0;
+
+ if (ucontrol->value.integer.value[0] > 1)
+ return -EINVAL;
+
+ if (!wm8994->mbc_vss)
+ return -ENODEV;
+
+ if (wm8958_dsp2_busy(wm8994, vss)) {
+ dev_dbg(codec->dev, "DSP2 active on %d already\n", vss);
+ return -EBUSY;
+ }
+
+ if (wm8994->enh_eq_ena[vss])
+ return -EBUSY;
+
+ wm8994->vss_ena[vss] = ucontrol->value.integer.value[0];
+
+ wm8958_dsp_apply(codec, vss, wm8994->vss_ena[vss]);
+
+ return 0;
+}
+
+
+#define WM8958_VSS_SWITCH(xname, xval) {\
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .info = wm8958_vss_info, \
+ .get = wm8958_vss_get, .put = wm8958_vss_put, \
+ .private_value = xval }
+
+static int wm8958_hpf_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int wm8958_hpf_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int hpf = kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (hpf < 3)
+ ucontrol->value.integer.value[0] = wm8994->hpf1_ena[hpf % 3];
+ else
+ ucontrol->value.integer.value[0] = wm8994->hpf2_ena[hpf % 3];
+
+ return 0;
+}
+
+static int wm8958_hpf_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int hpf = kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (hpf < 3) {
+ if (wm8994->hpf1_ena[hpf % 3] ==
+ ucontrol->value.integer.value[0])
+ return 0;
+ } else {
+ if (wm8994->hpf2_ena[hpf % 3] ==
+ ucontrol->value.integer.value[0])
+ return 0;
+ }
+
+ if (ucontrol->value.integer.value[0] > 1)
+ return -EINVAL;
+
+ if (!wm8994->mbc_vss)
+ return -ENODEV;
+
+ if (wm8958_dsp2_busy(wm8994, hpf % 3)) {
+ dev_dbg(codec->dev, "DSP2 active on %d already\n", hpf);
+ return -EBUSY;
+ }
+
+ if (wm8994->enh_eq_ena[hpf % 3])
+ return -EBUSY;
+
+ if (hpf < 3)
+ wm8994->hpf1_ena[hpf % 3] = ucontrol->value.integer.value[0];
+ else
+ wm8994->hpf2_ena[hpf % 3] = ucontrol->value.integer.value[0];
+
+ wm8958_dsp_apply(codec, hpf % 3, ucontrol->value.integer.value[0]);
+
+ return 0;
+}
+
+#define WM8958_HPF_SWITCH(xname, xval) {\
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .info = wm8958_hpf_info, \
+ .get = wm8958_hpf_get, .put = wm8958_hpf_put, \
+ .private_value = xval }
+
+static int wm8958_put_enh_eq_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int value = ucontrol->value.integer.value[0];
+ int reg;
+
+ /* Don't allow on the fly reconfiguration */
+ reg = snd_soc_read(codec, WM8994_CLOCKING_1);
+ if (reg < 0 || reg & WM8958_DSP2CLK_ENA)
+ return -EBUSY;
+
+ if (value >= pdata->num_enh_eq_cfgs)
+ return -EINVAL;
+
+ wm8994->enh_eq_cfg = value;
+
+ return 0;
+}
+
+static int wm8958_get_enh_eq_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8994->enh_eq_cfg;
+
+ return 0;
+}
+
+static int wm8958_enh_eq_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int wm8958_enh_eq_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int eq = kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = wm8994->enh_eq_ena[eq];
+
+ return 0;
+}
+
+static int wm8958_enh_eq_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int eq = kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (wm8994->enh_eq_ena[eq] == ucontrol->value.integer.value[0])
+ return 0;
+
+ if (ucontrol->value.integer.value[0] > 1)
+ return -EINVAL;
+
+ if (!wm8994->enh_eq)
+ return -ENODEV;
+
+ if (wm8958_dsp2_busy(wm8994, eq)) {
+ dev_dbg(codec->dev, "DSP2 active on %d already\n", eq);
+ return -EBUSY;
+ }
+
+ if (wm8994->mbc_ena[eq] || wm8994->vss_ena[eq] ||
+ wm8994->hpf1_ena[eq] || wm8994->hpf2_ena[eq])
+ return -EBUSY;
+
+ wm8994->enh_eq_ena[eq] = ucontrol->value.integer.value[0];
+
+ wm8958_dsp_apply(codec, eq, ucontrol->value.integer.value[0]);
+
+ return 0;
+}
+
+#define WM8958_ENH_EQ_SWITCH(xname, xval) {\
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .info = wm8958_enh_eq_info, \
+ .get = wm8958_enh_eq_get, .put = wm8958_enh_eq_put, \
+ .private_value = xval }
+
+static const struct snd_kcontrol_new wm8958_mbc_snd_controls[] = {
+WM8958_MBC_SWITCH("AIF1DAC1 MBC Switch", 0),
+WM8958_MBC_SWITCH("AIF1DAC2 MBC Switch", 1),
+WM8958_MBC_SWITCH("AIF2DAC MBC Switch", 2),
+};
+
+static const struct snd_kcontrol_new wm8958_vss_snd_controls[] = {
+WM8958_VSS_SWITCH("AIF1DAC1 VSS Switch", 0),
+WM8958_VSS_SWITCH("AIF1DAC2 VSS Switch", 1),
+WM8958_VSS_SWITCH("AIF2DAC VSS Switch", 2),
+WM8958_HPF_SWITCH("AIF1DAC1 HPF1 Switch", 0),
+WM8958_HPF_SWITCH("AIF1DAC2 HPF1 Switch", 1),
+WM8958_HPF_SWITCH("AIF2DAC HPF1 Switch", 2),
+WM8958_HPF_SWITCH("AIF1DAC1 HPF2 Switch", 3),
+WM8958_HPF_SWITCH("AIF1DAC2 HPF2 Switch", 4),
+WM8958_HPF_SWITCH("AIF2DAC HPF2 Switch", 5),
+};
+
+static const struct snd_kcontrol_new wm8958_enh_eq_snd_controls[] = {
+WM8958_ENH_EQ_SWITCH("AIF1DAC1 Enhanced EQ Switch", 0),
+WM8958_ENH_EQ_SWITCH("AIF1DAC2 Enhanced EQ Switch", 1),
+WM8958_ENH_EQ_SWITCH("AIF2DAC Enhanced EQ Switch", 2),
+};
+
+static void wm8958_enh_eq_loaded(const struct firmware *fw, void *context)
+{
+ struct snd_soc_codec *codec = context;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (fw && (wm8958_dsp2_fw(codec, "ENH_EQ", fw, true) == 0)) {
+ mutex_lock(&codec->mutex);
+ wm8994->enh_eq = fw;
+ mutex_unlock(&codec->mutex);
+ }
+}
+
+static void wm8958_mbc_vss_loaded(const struct firmware *fw, void *context)
+{
+ struct snd_soc_codec *codec = context;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (fw && (wm8958_dsp2_fw(codec, "MBC+VSS", fw, true) == 0)) {
+ mutex_lock(&codec->mutex);
+ wm8994->mbc_vss = fw;
+ mutex_unlock(&codec->mutex);
+ }
+
+ /* We can't have more than one request outstanding at once so
+ * we daisy chain.
+ */
+ request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+ "wm8958_enh_eq.wfw", codec->dev, GFP_KERNEL,
+ codec, wm8958_enh_eq_loaded);
+}
+
+static void wm8958_mbc_loaded(const struct firmware *fw, void *context)
+{
+ struct snd_soc_codec *codec = context;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ if (wm8958_dsp2_fw(codec, "MBC", fw, true) != 0)
+ return;
+
+ mutex_lock(&codec->mutex);
+ wm8994->mbc = fw;
+ mutex_unlock(&codec->mutex);
+
+ /* We can't have more than one request outstanding at once so
+ * we daisy chain.
+ */
+ request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+ "wm8958_mbc_vss.wfw", codec->dev, GFP_KERNEL,
+ codec, wm8958_mbc_vss_loaded);
+}
+
+void wm8958_dsp2_init(struct snd_soc_codec *codec)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int ret, i;
+
+ wm8994->dsp_active = -1;
+
+ snd_soc_add_controls(codec, wm8958_mbc_snd_controls,
+ ARRAY_SIZE(wm8958_mbc_snd_controls));
+ snd_soc_add_controls(codec, wm8958_vss_snd_controls,
+ ARRAY_SIZE(wm8958_vss_snd_controls));
+ snd_soc_add_controls(codec, wm8958_enh_eq_snd_controls,
+ ARRAY_SIZE(wm8958_enh_eq_snd_controls));
+
+
+ /* We don't *require* firmware and don't want to delay boot */
+ request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+ "wm8958_mbc.wfw", codec->dev, GFP_KERNEL,
+ codec, wm8958_mbc_loaded);
+
+ if (!pdata)
+ return;
+
+ if (pdata->num_mbc_cfgs) {
+ struct snd_kcontrol_new control[] = {
+ SOC_ENUM_EXT("MBC Mode", wm8994->mbc_enum,
+ wm8958_get_mbc_enum, wm8958_put_mbc_enum),
+ };
+
+ /* We need an array of texts for the enum API */
+ wm8994->mbc_texts = kmalloc(sizeof(char *)
+ * pdata->num_mbc_cfgs, GFP_KERNEL);
+ if (!wm8994->mbc_texts) {
+ dev_err(wm8994->codec->dev,
+ "Failed to allocate %d MBC config texts\n",
+ pdata->num_mbc_cfgs);
+ return;
+ }
+
+ for (i = 0; i < pdata->num_mbc_cfgs; i++)
+ wm8994->mbc_texts[i] = pdata->mbc_cfgs[i].name;
+
+ wm8994->mbc_enum.max = pdata->num_mbc_cfgs;
+ wm8994->mbc_enum.texts = wm8994->mbc_texts;
+
+ ret = snd_soc_add_controls(wm8994->codec, control, 1);
+ if (ret != 0)
+ dev_err(wm8994->codec->dev,
+ "Failed to add MBC mode controls: %d\n", ret);
+ }
+
+ if (pdata->num_vss_cfgs) {
+ struct snd_kcontrol_new control[] = {
+ SOC_ENUM_EXT("VSS Mode", wm8994->vss_enum,
+ wm8958_get_vss_enum, wm8958_put_vss_enum),
+ };
+
+ /* We need an array of texts for the enum API */
+ wm8994->vss_texts = kmalloc(sizeof(char *)
+ * pdata->num_vss_cfgs, GFP_KERNEL);
+ if (!wm8994->vss_texts) {
+ dev_err(wm8994->codec->dev,
+ "Failed to allocate %d VSS config texts\n",
+ pdata->num_vss_cfgs);
+ return;
+ }
+
+ for (i = 0; i < pdata->num_vss_cfgs; i++)
+ wm8994->vss_texts[i] = pdata->vss_cfgs[i].name;
+
+ wm8994->vss_enum.max = pdata->num_vss_cfgs;
+ wm8994->vss_enum.texts = wm8994->vss_texts;
+
+ ret = snd_soc_add_controls(wm8994->codec, control, 1);
+ if (ret != 0)
+ dev_err(wm8994->codec->dev,
+ "Failed to add VSS mode controls: %d\n", ret);
+ }
+
+ if (pdata->num_vss_hpf_cfgs) {
+ struct snd_kcontrol_new control[] = {
+ SOC_ENUM_EXT("VSS HPF Mode", wm8994->vss_hpf_enum,
+ wm8958_get_vss_hpf_enum,
+ wm8958_put_vss_hpf_enum),
+ };
+
+ /* We need an array of texts for the enum API */
+ wm8994->vss_hpf_texts = kmalloc(sizeof(char *)
+ * pdata->num_vss_hpf_cfgs, GFP_KERNEL);
+ if (!wm8994->vss_hpf_texts) {
+ dev_err(wm8994->codec->dev,
+ "Failed to allocate %d VSS HPF config texts\n",
+ pdata->num_vss_hpf_cfgs);
+ return;
+ }
+
+ for (i = 0; i < pdata->num_vss_hpf_cfgs; i++)
+ wm8994->vss_hpf_texts[i] = pdata->vss_hpf_cfgs[i].name;
+
+ wm8994->vss_hpf_enum.max = pdata->num_vss_hpf_cfgs;
+ wm8994->vss_hpf_enum.texts = wm8994->vss_hpf_texts;
+
+ ret = snd_soc_add_controls(wm8994->codec, control, 1);
+ if (ret != 0)
+ dev_err(wm8994->codec->dev,
+ "Failed to add VSS HPFmode controls: %d\n",
+ ret);
+ }
+
+ if (pdata->num_enh_eq_cfgs) {
+ struct snd_kcontrol_new control[] = {
+ SOC_ENUM_EXT("Enhanced EQ Mode", wm8994->enh_eq_enum,
+ wm8958_get_enh_eq_enum,
+ wm8958_put_enh_eq_enum),
+ };
+
+ /* We need an array of texts for the enum API */
+ wm8994->enh_eq_texts = kmalloc(sizeof(char *)
+ * pdata->num_enh_eq_cfgs, GFP_KERNEL);
+ if (!wm8994->enh_eq_texts) {
+ dev_err(wm8994->codec->dev,
+ "Failed to allocate %d enhanced EQ config texts\n",
+ pdata->num_enh_eq_cfgs);
+ return;
+ }
+
+ for (i = 0; i < pdata->num_enh_eq_cfgs; i++)
+ wm8994->enh_eq_texts[i] = pdata->enh_eq_cfgs[i].name;
+
+ wm8994->enh_eq_enum.max = pdata->num_enh_eq_cfgs;
+ wm8994->enh_eq_enum.texts = wm8994->enh_eq_texts;
+
+ ret = snd_soc_add_controls(wm8994->codec, control, 1);
+ if (ret != 0)
+ dev_err(wm8994->codec->dev,
+ "Failed to add enhanced EQ controls: %d\n",
+ ret);
+ }
+}
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c
new file mode 100644
index 00000000..4393394b
--- /dev/null
+++ b/sound/soc/codecs/wm8960.c
@@ -0,0 +1,1075 @@
+/*
+ * wm8960.c -- WM8960 ALSA SoC Audio driver
+ *
+ * Author: Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/wm8960.h>
+
+#include "wm8960.h"
+
+#define AUDIO_NAME "wm8960"
+
+/* R25 - Power 1 */
+#define WM8960_VMID_MASK 0x180
+#define WM8960_VREF 0x40
+
+/* R26 - Power 2 */
+#define WM8960_PWR2_LOUT1 0x40
+#define WM8960_PWR2_ROUT1 0x20
+#define WM8960_PWR2_OUT3 0x02
+
+/* R28 - Anti-pop 1 */
+#define WM8960_POBCTRL 0x80
+#define WM8960_BUFDCOPEN 0x10
+#define WM8960_BUFIOEN 0x08
+#define WM8960_SOFT_ST 0x04
+#define WM8960_HPSTBY 0x01
+
+/* R29 - Anti-pop 2 */
+#define WM8960_DISOP 0x40
+#define WM8960_DRES_MASK 0x30
+
+/*
+ * wm8960 register cache
+ * We can't read the WM8960 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8960_reg[WM8960_CACHEREGNUM] = {
+ 0x0097, 0x0097, 0x0000, 0x0000,
+ 0x0000, 0x0008, 0x0000, 0x000a,
+ 0x01c0, 0x0000, 0x00ff, 0x00ff,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x007b, 0x0100, 0x0032,
+ 0x0000, 0x00c3, 0x00c3, 0x01c0,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0100, 0x0100, 0x0050, 0x0050,
+ 0x0050, 0x0050, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0040, 0x0000,
+ 0x0000, 0x0050, 0x0050, 0x0000,
+ 0x0002, 0x0037, 0x004d, 0x0080,
+ 0x0008, 0x0031, 0x0026, 0x00e9,
+};
+
+struct wm8960_priv {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+ int (*set_bias_level)(struct snd_soc_codec *,
+ enum snd_soc_bias_level level);
+ struct snd_soc_dapm_widget *lout1;
+ struct snd_soc_dapm_widget *rout1;
+ struct snd_soc_dapm_widget *out3;
+ bool deemph;
+ int playback_fs;
+};
+
+#define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0)
+
+/* enumerated controls */
+static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted",
+ "Right Inverted", "Stereo Inversion"};
+static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"};
+static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"};
+static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8960_alcmode[] = {"ALC", "Limiter"};
+
+static const struct soc_enum wm8960_enum[] = {
+ SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity),
+ SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity),
+ SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff),
+ SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff),
+ SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc),
+ SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode),
+};
+
+static const int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8960_set_deemph(struct snd_soc_codec *codec)
+{
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ int val, i, best;
+
+ /* If we're using deemphasis select the nearest available sample
+ * rate.
+ */
+ if (wm8960->deemph) {
+ best = 1;
+ for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+ if (abs(deemph_settings[i] - wm8960->playback_fs) <
+ abs(deemph_settings[best] - wm8960->playback_fs))
+ best = i;
+ }
+
+ val = best << 1;
+ } else {
+ val = 0;
+ }
+
+ dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+ return snd_soc_update_bits(codec, WM8960_DACCTL1,
+ 0x6, val);
+}
+
+static int wm8960_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = wm8960->deemph;
+ return 0;
+}
+
+static int wm8960_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ int deemph = ucontrol->value.enumerated.item[0];
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ wm8960->deemph = deemph;
+
+ return wm8960_set_deemph(codec);
+}
+
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+
+static const struct snd_kcontrol_new wm8960_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL,
+ 0, 63, 0, adc_tlv),
+SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL,
+ 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL,
+ 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC,
+ 0, 255, 0, dac_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1,
+ 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2,
+ 7, 1, 0),
+SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
+SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),
+
+SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),
+SOC_ENUM("ADC Polarity", wm8960_enum[0]),
+SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),
+
+SOC_ENUM("DAC Polarity", wm8960_enum[2]),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+ wm8960_get_deemph, wm8960_put_deemph),
+
+SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]),
+SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]),
+SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0),
+SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0),
+
+SOC_ENUM("ALC Function", wm8960_enum[4]),
+SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0),
+SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1),
+SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0),
+SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0),
+SOC_ENUM("ALC Mode", wm8960_enum[5]),
+SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),
+
+SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0),
+SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0),
+
+SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH,
+ 0, 127, 0),
+
+SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume",
+ WM8960_BYPASS1, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume",
+ WM8960_LOUTMIX, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume",
+ WM8960_BYPASS2, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume",
+ WM8960_ROUTMIX, 4, 7, 1, bypass_tlv),
+};
+
+static const struct snd_kcontrol_new wm8960_lin_boost[] = {
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
+SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_lin[] = {
+SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_rin_boost[] = {
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0),
+SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_rin[] = {
+SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
+SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_routput_mixer[] = {
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0),
+SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_mono_out[] = {
+SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("LINPUT1"),
+SND_SOC_DAPM_INPUT("RINPUT1"),
+SND_SOC_DAPM_INPUT("LINPUT2"),
+SND_SOC_DAPM_INPUT("RINPUT2"),
+SND_SOC_DAPM_INPUT("LINPUT3"),
+SND_SOC_DAPM_INPUT("RINPUT3"),
+
+SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0),
+
+SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0,
+ wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),
+SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0,
+ wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)),
+
+SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,
+ wm8960_lin, ARRAY_SIZE(wm8960_lin)),
+SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0,
+ wm8960_rin, ARRAY_SIZE(wm8960_rin)),
+
+SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0),
+
+SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,
+ &wm8960_loutput_mixer[0],
+ ARRAY_SIZE(wm8960_loutput_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0,
+ &wm8960_routput_mixer[0],
+ ARRAY_SIZE(wm8960_routput_mixer)),
+
+SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("SPK_LP"),
+SND_SOC_DAPM_OUTPUT("SPK_LN"),
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+SND_SOC_DAPM_OUTPUT("SPK_RP"),
+SND_SOC_DAPM_OUTPUT("SPK_RN"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+};
+
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = {
+SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
+ &wm8960_mono_out[0],
+ ARRAY_SIZE(wm8960_mono_out)),
+};
+
+/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = {
+SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+ { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
+ { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
+ { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" },
+
+ { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", },
+ { "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */
+ { "Left Input Mixer", NULL, "LINPUT2" },
+ { "Left Input Mixer", NULL, "LINPUT3" },
+
+ { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" },
+ { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" },
+ { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" },
+
+ { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", },
+ { "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */
+ { "Right Input Mixer", NULL, "RINPUT2" },
+ { "Right Input Mixer", NULL, "LINPUT3" },
+
+ { "Left ADC", NULL, "Left Input Mixer" },
+ { "Right ADC", NULL, "Right Input Mixer" },
+
+ { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" },
+ { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} ,
+ { "Left Output Mixer", "PCM Playback Switch", "Left DAC" },
+
+ { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" },
+ { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } ,
+ { "Right Output Mixer", "PCM Playback Switch", "Right DAC" },
+
+ { "LOUT1 PGA", NULL, "Left Output Mixer" },
+ { "ROUT1 PGA", NULL, "Right Output Mixer" },
+
+ { "HP_L", NULL, "LOUT1 PGA" },
+ { "HP_R", NULL, "ROUT1 PGA" },
+
+ { "Left Speaker PGA", NULL, "Left Output Mixer" },
+ { "Right Speaker PGA", NULL, "Right Output Mixer" },
+
+ { "Left Speaker Output", NULL, "Left Speaker PGA" },
+ { "Right Speaker Output", NULL, "Right Speaker PGA" },
+
+ { "SPK_LN", NULL, "Left Speaker Output" },
+ { "SPK_LP", NULL, "Left Speaker Output" },
+ { "SPK_RN", NULL, "Right Speaker Output" },
+ { "SPK_RP", NULL, "Right Speaker Output" },
+};
+
+static const struct snd_soc_dapm_route audio_paths_out3[] = {
+ { "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
+ { "Mono Output Mixer", "Right Switch", "Right Output Mixer" },
+
+ { "OUT3", NULL, "Mono Output Mixer", }
+};
+
+static const struct snd_soc_dapm_route audio_paths_capless[] = {
+ { "HP_L", NULL, "OUT3 VMID" },
+ { "HP_R", NULL, "OUT3 VMID" },
+
+ { "OUT3 VMID", NULL, "Left Output Mixer" },
+ { "OUT3 VMID", NULL, "Right Output Mixer" },
+};
+
+static int wm8960_add_widgets(struct snd_soc_codec *codec)
+{
+ struct wm8960_data *pdata = codec->dev->platform_data;
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ struct snd_soc_dapm_widget *w;
+
+ snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets,
+ ARRAY_SIZE(wm8960_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
+
+ /* In capless mode OUT3 is used to provide VMID for the
+ * headphone outputs, otherwise it is used as a mono mixer.
+ */
+ if (pdata && pdata->capless) {
+ snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless,
+ ARRAY_SIZE(wm8960_dapm_widgets_capless));
+
+ snd_soc_dapm_add_routes(dapm, audio_paths_capless,
+ ARRAY_SIZE(audio_paths_capless));
+ } else {
+ snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3,
+ ARRAY_SIZE(wm8960_dapm_widgets_out3));
+
+ snd_soc_dapm_add_routes(dapm, audio_paths_out3,
+ ARRAY_SIZE(audio_paths_out3));
+ }
+
+ /* We need to power up the headphone output stage out of
+ * sequence for capless mode. To save scanning the widget
+ * list each time to find the desired power state do so now
+ * and save the result.
+ */
+ list_for_each_entry(w, &codec->card->widgets, list) {
+ if (w->dapm != &codec->dapm)
+ continue;
+ if (strcmp(w->name, "LOUT1 PGA") == 0)
+ wm8960->lout1 = w;
+ if (strcmp(w->name, "ROUT1 PGA") == 0)
+ wm8960->rout1 = w;
+ if (strcmp(w->name, "OUT3 VMID") == 0)
+ wm8960->out3 = w;
+ }
+
+ return 0;
+}
+
+static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ snd_soc_write(codec, WM8960_IFACE1, iface);
+ return 0;
+}
+
+static struct {
+ int rate;
+ unsigned int val;
+} alc_rates[] = {
+ { 48000, 0 },
+ { 44100, 0 },
+ { 32000, 1 },
+ { 22050, 2 },
+ { 24000, 2 },
+ { 16000, 3 },
+ { 11250, 4 },
+ { 12000, 4 },
+ { 8000, 5 },
+};
+
+static int wm8960_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3;
+ int i;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ }
+
+ /* Update filters for the new rate */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ wm8960->playback_fs = params_rate(params);
+ wm8960_set_deemph(codec);
+ } else {
+ for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
+ if (alc_rates[i].rate == params_rate(params))
+ snd_soc_update_bits(codec,
+ WM8960_ADDCTL3, 0x7,
+ alc_rates[i].val);
+ }
+
+ /* set iface */
+ snd_soc_write(codec, WM8960_IFACE1, iface);
+ return 0;
+}
+
+static int wm8960_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7;
+
+ if (mute)
+ snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8);
+ else
+ snd_soc_write(codec, WM8960_DACCTL1, mute_reg);
+ return 0;
+}
+
+static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* Set VMID to 2x50k */
+ reg = snd_soc_read(codec, WM8960_POWER1);
+ reg &= ~0x180;
+ reg |= 0x80;
+ snd_soc_write(codec, WM8960_POWER1, reg);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Enable anti-pop features */
+ snd_soc_write(codec, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+ /* Enable & ramp VMID at 2x50k */
+ reg = snd_soc_read(codec, WM8960_POWER1);
+ reg |= 0x80;
+ snd_soc_write(codec, WM8960_POWER1, reg);
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF);
+
+ /* Disable anti-pop features */
+ snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN);
+ }
+
+ /* Set VMID to 2x250k */
+ reg = snd_soc_read(codec, WM8960_POWER1);
+ reg &= ~0x180;
+ reg |= 0x100;
+ snd_soc_write(codec, WM8960_POWER1, reg);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable anti-pop features */
+ snd_soc_write(codec, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+ /* Disable VMID and VREF, let them discharge */
+ snd_soc_write(codec, WM8960_POWER1, 0);
+ msleep(600);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ int reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ switch (codec->dapm.bias_level) {
+ case SND_SOC_BIAS_STANDBY:
+ /* Enable anti pop mode */
+ snd_soc_update_bits(codec, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN);
+
+ /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */
+ reg = 0;
+ if (wm8960->lout1 && wm8960->lout1->power)
+ reg |= WM8960_PWR2_LOUT1;
+ if (wm8960->rout1 && wm8960->rout1->power)
+ reg |= WM8960_PWR2_ROUT1;
+ if (wm8960->out3 && wm8960->out3->power)
+ reg |= WM8960_PWR2_OUT3;
+ snd_soc_update_bits(codec, WM8960_POWER2,
+ WM8960_PWR2_LOUT1 |
+ WM8960_PWR2_ROUT1 |
+ WM8960_PWR2_OUT3, reg);
+
+ /* Enable VMID at 2*50k */
+ snd_soc_update_bits(codec, WM8960_POWER1,
+ WM8960_VMID_MASK, 0x80);
+
+ /* Ramp */
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_update_bits(codec, WM8960_POWER1,
+ WM8960_VREF, WM8960_VREF);
+
+ msleep(100);
+ break;
+
+ case SND_SOC_BIAS_ON:
+ /* Enable anti-pop mode */
+ snd_soc_update_bits(codec, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN);
+
+ /* Disable VMID and VREF */
+ snd_soc_update_bits(codec, WM8960_POWER1,
+ WM8960_VREF | WM8960_VMID_MASK, 0);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ switch (codec->dapm.bias_level) {
+ case SND_SOC_BIAS_PREPARE:
+ /* Disable HP discharge */
+ snd_soc_update_bits(codec, WM8960_APOP2,
+ WM8960_DISOP | WM8960_DRES_MASK,
+ 0);
+
+ /* Disable anti-pop features */
+ snd_soc_update_bits(codec, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 pre_div:1;
+ u32 n:4;
+ u32 k:24;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+
+static int pll_factors(unsigned int source, unsigned int target,
+ struct _pll_div *pll_div)
+{
+ unsigned long long Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target);
+
+ /* Scale up target to PLL operating frequency */
+ target *= 4;
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->pre_div = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->pre_div = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12)) {
+ pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv);
+ return -EINVAL;
+ }
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+
+ pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n",
+ pll_div->n, pll_div->k, pll_div->pre_div);
+
+ return 0;
+}
+
+static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+ static struct _pll_div pll_div;
+ int ret;
+
+ if (freq_in && freq_out) {
+ ret = pll_factors(freq_in, freq_out, &pll_div);
+ if (ret != 0)
+ return ret;
+ }
+
+ /* Disable the PLL: even if we are changing the frequency the
+ * PLL needs to be disabled while we do so. */
+ snd_soc_write(codec, WM8960_CLOCK1,
+ snd_soc_read(codec, WM8960_CLOCK1) & ~1);
+ snd_soc_write(codec, WM8960_POWER2,
+ snd_soc_read(codec, WM8960_POWER2) & ~1);
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f;
+ reg |= pll_div.pre_div << 4;
+ reg |= pll_div.n;
+
+ if (pll_div.k) {
+ reg |= 0x20;
+
+ snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f);
+ snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff);
+ snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff);
+ }
+ snd_soc_write(codec, WM8960_PLL1, reg);
+
+ /* Turn it on */
+ snd_soc_write(codec, WM8960_POWER2,
+ snd_soc_read(codec, WM8960_POWER2) | 1);
+ msleep(250);
+ snd_soc_write(codec, WM8960_CLOCK1,
+ snd_soc_read(codec, WM8960_CLOCK1) | 1);
+
+ return 0;
+}
+
+static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8960_SYSCLKDIV:
+ reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9;
+ snd_soc_write(codec, WM8960_CLOCK1, reg | div);
+ break;
+ case WM8960_DACDIV:
+ reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7;
+ snd_soc_write(codec, WM8960_CLOCK1, reg | div);
+ break;
+ case WM8960_OPCLKDIV:
+ reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f;
+ snd_soc_write(codec, WM8960_PLL1, reg | div);
+ break;
+ case WM8960_DCLKDIV:
+ reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f;
+ snd_soc_write(codec, WM8960_CLOCK2, reg | div);
+ break;
+ case WM8960_TOCLKSEL:
+ reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd;
+ snd_soc_write(codec, WM8960_ADDCTL1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8960_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+ return wm8960->set_bias_level(codec, level);
+}
+
+#define WM8960_RATES SNDRV_PCM_RATE_8000_48000
+
+#define WM8960_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8960_dai_ops = {
+ .hw_params = wm8960_hw_params,
+ .digital_mute = wm8960_mute,
+ .set_fmt = wm8960_set_dai_fmt,
+ .set_clkdiv = wm8960_set_dai_clkdiv,
+ .set_pll = wm8960_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver wm8960_dai = {
+ .name = "wm8960-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8960_RATES,
+ .formats = WM8960_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8960_RATES,
+ .formats = WM8960_FORMATS,},
+ .ops = &wm8960_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int wm8960_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+ wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8960_resume(struct snd_soc_codec *codec)
+{
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+static int wm8960_probe(struct snd_soc_codec *codec)
+{
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ struct wm8960_data *pdata = dev_get_platdata(codec->dev);
+ int ret;
+ u16 reg;
+
+ wm8960->set_bias_level = wm8960_set_bias_level_out3;
+ codec->control_data = wm8960->control_data;
+
+ if (!pdata) {
+ dev_warn(codec->dev, "No platform data supplied\n");
+ } else {
+ if (pdata->dres > WM8960_DRES_MAX) {
+ dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres);
+ pdata->dres = 0;
+ }
+
+ if (pdata->capless)
+ wm8960->set_bias_level = wm8960_set_bias_level_capless;
+ }
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8960->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8960_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Latch the update bits */
+ reg = snd_soc_read(codec, WM8960_LINVOL);
+ snd_soc_write(codec, WM8960_LINVOL, reg | 0x100);
+ reg = snd_soc_read(codec, WM8960_RINVOL);
+ snd_soc_write(codec, WM8960_RINVOL, reg | 0x100);
+ reg = snd_soc_read(codec, WM8960_LADC);
+ snd_soc_write(codec, WM8960_LADC, reg | 0x100);
+ reg = snd_soc_read(codec, WM8960_RADC);
+ snd_soc_write(codec, WM8960_RADC, reg | 0x100);
+ reg = snd_soc_read(codec, WM8960_LDAC);
+ snd_soc_write(codec, WM8960_LDAC, reg | 0x100);
+ reg = snd_soc_read(codec, WM8960_RDAC);
+ snd_soc_write(codec, WM8960_RDAC, reg | 0x100);
+ reg = snd_soc_read(codec, WM8960_LOUT1);
+ snd_soc_write(codec, WM8960_LOUT1, reg | 0x100);
+ reg = snd_soc_read(codec, WM8960_ROUT1);
+ snd_soc_write(codec, WM8960_ROUT1, reg | 0x100);
+ reg = snd_soc_read(codec, WM8960_LOUT2);
+ snd_soc_write(codec, WM8960_LOUT2, reg | 0x100);
+ reg = snd_soc_read(codec, WM8960_ROUT2);
+ snd_soc_write(codec, WM8960_ROUT2, reg | 0x100);
+
+ snd_soc_add_controls(codec, wm8960_snd_controls,
+ ARRAY_SIZE(wm8960_snd_controls));
+ wm8960_add_widgets(codec);
+
+ return 0;
+}
+
+/* power down chip */
+static int wm8960_remove(struct snd_soc_codec *codec)
+{
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+ wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8960 = {
+ .probe = wm8960_probe,
+ .remove = wm8960_remove,
+ .suspend = wm8960_suspend,
+ .resume = wm8960_resume,
+ .set_bias_level = wm8960_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8960_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8960_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8960_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8960_priv *wm8960;
+ int ret;
+
+ wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL);
+ if (wm8960 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8960);
+ wm8960->control_type = SND_SOC_I2C;
+ wm8960->control_data = i2c;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8960, &wm8960_dai, 1);
+ if (ret < 0)
+ kfree(wm8960);
+ return ret;
+}
+
+static __devexit int wm8960_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8960_i2c_id[] = {
+ { "wm8960", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id);
+
+static struct i2c_driver wm8960_i2c_driver = {
+ .driver = {
+ .name = "wm8960-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8960_i2c_probe,
+ .remove = __devexit_p(wm8960_i2c_remove),
+ .id_table = wm8960_i2c_id,
+};
+#endif
+
+static int __init wm8960_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8960_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8960_modinit);
+
+static void __exit wm8960_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8960_i2c_driver);
+#endif
+}
+module_exit(wm8960_exit);
+
+MODULE_DESCRIPTION("ASoC WM8960 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8960.h b/sound/soc/codecs/wm8960.h
new file mode 100644
index 00000000..2d8163d7
--- /dev/null
+++ b/sound/soc/codecs/wm8960.h
@@ -0,0 +1,113 @@
+/*
+ * wm8960.h -- WM8960 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8960_H
+#define _WM8960_H
+
+/* WM8960 register space */
+
+
+#define WM8960_CACHEREGNUM 56
+
+#define WM8960_LINVOL 0x0
+#define WM8960_RINVOL 0x1
+#define WM8960_LOUT1 0x2
+#define WM8960_ROUT1 0x3
+#define WM8960_CLOCK1 0x4
+#define WM8960_DACCTL1 0x5
+#define WM8960_DACCTL2 0x6
+#define WM8960_IFACE1 0x7
+#define WM8960_CLOCK2 0x8
+#define WM8960_IFACE2 0x9
+#define WM8960_LDAC 0xa
+#define WM8960_RDAC 0xb
+
+#define WM8960_RESET 0xf
+#define WM8960_3D 0x10
+#define WM8960_ALC1 0x11
+#define WM8960_ALC2 0x12
+#define WM8960_ALC3 0x13
+#define WM8960_NOISEG 0x14
+#define WM8960_LADC 0x15
+#define WM8960_RADC 0x16
+#define WM8960_ADDCTL1 0x17
+#define WM8960_ADDCTL2 0x18
+#define WM8960_POWER1 0x19
+#define WM8960_POWER2 0x1a
+#define WM8960_ADDCTL3 0x1b
+#define WM8960_APOP1 0x1c
+#define WM8960_APOP2 0x1d
+
+#define WM8960_LINPATH 0x20
+#define WM8960_RINPATH 0x21
+#define WM8960_LOUTMIX 0x22
+
+#define WM8960_ROUTMIX 0x25
+#define WM8960_MONOMIX1 0x26
+#define WM8960_MONOMIX2 0x27
+#define WM8960_LOUT2 0x28
+#define WM8960_ROUT2 0x29
+#define WM8960_MONO 0x2a
+#define WM8960_INBMIX1 0x2b
+#define WM8960_INBMIX2 0x2c
+#define WM8960_BYPASS1 0x2d
+#define WM8960_BYPASS2 0x2e
+#define WM8960_POWER3 0x2f
+#define WM8960_ADDCTL4 0x30
+#define WM8960_CLASSD1 0x31
+
+#define WM8960_CLASSD3 0x33
+#define WM8960_PLL1 0x34
+#define WM8960_PLL2 0x35
+#define WM8960_PLL3 0x36
+#define WM8960_PLL4 0x37
+
+
+/*
+ * WM8960 Clock dividers
+ */
+#define WM8960_SYSCLKDIV 0
+#define WM8960_DACDIV 1
+#define WM8960_OPCLKDIV 2
+#define WM8960_DCLKDIV 3
+#define WM8960_TOCLKSEL 4
+
+#define WM8960_SYSCLK_DIV_1 (0 << 1)
+#define WM8960_SYSCLK_DIV_2 (2 << 1)
+
+#define WM8960_SYSCLK_MCLK (0 << 0)
+#define WM8960_SYSCLK_PLL (1 << 0)
+
+#define WM8960_DAC_DIV_1 (0 << 3)
+#define WM8960_DAC_DIV_1_5 (1 << 3)
+#define WM8960_DAC_DIV_2 (2 << 3)
+#define WM8960_DAC_DIV_3 (3 << 3)
+#define WM8960_DAC_DIV_4 (4 << 3)
+#define WM8960_DAC_DIV_5_5 (5 << 3)
+#define WM8960_DAC_DIV_6 (6 << 3)
+
+#define WM8960_DCLK_DIV_1_5 (0 << 6)
+#define WM8960_DCLK_DIV_2 (1 << 6)
+#define WM8960_DCLK_DIV_3 (2 << 6)
+#define WM8960_DCLK_DIV_4 (3 << 6)
+#define WM8960_DCLK_DIV_6 (4 << 6)
+#define WM8960_DCLK_DIV_8 (5 << 6)
+#define WM8960_DCLK_DIV_12 (6 << 6)
+#define WM8960_DCLK_DIV_16 (7 << 6)
+
+#define WM8960_TOCLK_F19 (0 << 1)
+#define WM8960_TOCLK_F21 (1 << 1)
+
+#define WM8960_OPCLK_DIV_1 (0 << 0)
+#define WM8960_OPCLK_DIV_2 (1 << 0)
+#define WM8960_OPCLK_DIV_3 (2 << 0)
+#define WM8960_OPCLK_DIV_4 (3 << 0)
+#define WM8960_OPCLK_DIV_5_5 (4 << 0)
+#define WM8960_OPCLK_DIV_6 (5 << 0)
+
+#endif
diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c
new file mode 100644
index 00000000..cdee8103
--- /dev/null
+++ b/sound/soc/codecs/wm8961.c
@@ -0,0 +1,1151 @@
+/*
+ * wm8961.c -- WM8961 ALSA SoC Audio driver
+ *
+ * Author: Mark Brown
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Currently unimplemented features:
+ * - ALC
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8961.h"
+
+#define WM8961_MAX_REGISTER 0xFC
+
+static u16 wm8961_reg_defaults[] = {
+ 0x009F, /* R0 - Left Input volume */
+ 0x009F, /* R1 - Right Input volume */
+ 0x0000, /* R2 - LOUT1 volume */
+ 0x0000, /* R3 - ROUT1 volume */
+ 0x0020, /* R4 - Clocking1 */
+ 0x0008, /* R5 - ADC & DAC Control 1 */
+ 0x0000, /* R6 - ADC & DAC Control 2 */
+ 0x000A, /* R7 - Audio Interface 0 */
+ 0x01F4, /* R8 - Clocking2 */
+ 0x0000, /* R9 - Audio Interface 1 */
+ 0x00FF, /* R10 - Left DAC volume */
+ 0x00FF, /* R11 - Right DAC volume */
+ 0x0000, /* R12 */
+ 0x0000, /* R13 */
+ 0x0040, /* R14 - Audio Interface 2 */
+ 0x0000, /* R15 - Software Reset */
+ 0x0000, /* R16 */
+ 0x007B, /* R17 - ALC1 */
+ 0x0000, /* R18 - ALC2 */
+ 0x0032, /* R19 - ALC3 */
+ 0x0000, /* R20 - Noise Gate */
+ 0x00C0, /* R21 - Left ADC volume */
+ 0x00C0, /* R22 - Right ADC volume */
+ 0x0120, /* R23 - Additional control(1) */
+ 0x0000, /* R24 - Additional control(2) */
+ 0x0000, /* R25 - Pwr Mgmt (1) */
+ 0x0000, /* R26 - Pwr Mgmt (2) */
+ 0x0000, /* R27 - Additional Control (3) */
+ 0x0000, /* R28 - Anti-pop */
+ 0x0000, /* R29 */
+ 0x005F, /* R30 - Clocking 3 */
+ 0x0000, /* R31 */
+ 0x0000, /* R32 - ADCL signal path */
+ 0x0000, /* R33 - ADCR signal path */
+ 0x0000, /* R34 */
+ 0x0000, /* R35 */
+ 0x0000, /* R36 */
+ 0x0000, /* R37 */
+ 0x0000, /* R38 */
+ 0x0000, /* R39 */
+ 0x0000, /* R40 - LOUT2 volume */
+ 0x0000, /* R41 - ROUT2 volume */
+ 0x0000, /* R42 */
+ 0x0000, /* R43 */
+ 0x0000, /* R44 */
+ 0x0000, /* R45 */
+ 0x0000, /* R46 */
+ 0x0000, /* R47 - Pwr Mgmt (3) */
+ 0x0023, /* R48 - Additional Control (4) */
+ 0x0000, /* R49 - Class D Control 1 */
+ 0x0000, /* R50 */
+ 0x0003, /* R51 - Class D Control 2 */
+ 0x0000, /* R52 */
+ 0x0000, /* R53 */
+ 0x0000, /* R54 */
+ 0x0000, /* R55 */
+ 0x0106, /* R56 - Clocking 4 */
+ 0x0000, /* R57 - DSP Sidetone 0 */
+ 0x0000, /* R58 - DSP Sidetone 1 */
+ 0x0000, /* R59 */
+ 0x0000, /* R60 - DC Servo 0 */
+ 0x0000, /* R61 - DC Servo 1 */
+ 0x0000, /* R62 */
+ 0x015E, /* R63 - DC Servo 3 */
+ 0x0010, /* R64 */
+ 0x0010, /* R65 - DC Servo 5 */
+ 0x0000, /* R66 */
+ 0x0001, /* R67 */
+ 0x0003, /* R68 - Analogue PGA Bias */
+ 0x0000, /* R69 - Analogue HP 0 */
+ 0x0060, /* R70 */
+ 0x01FB, /* R71 - Analogue HP 2 */
+ 0x0000, /* R72 - Charge Pump 1 */
+ 0x0065, /* R73 */
+ 0x005F, /* R74 */
+ 0x0059, /* R75 */
+ 0x006B, /* R76 */
+ 0x0038, /* R77 */
+ 0x000C, /* R78 */
+ 0x000A, /* R79 */
+ 0x006B, /* R80 */
+ 0x0000, /* R81 */
+ 0x0000, /* R82 - Charge Pump B */
+ 0x0087, /* R83 */
+ 0x0000, /* R84 */
+ 0x005C, /* R85 */
+ 0x0000, /* R86 */
+ 0x0000, /* R87 - Write Sequencer 1 */
+ 0x0000, /* R88 - Write Sequencer 2 */
+ 0x0000, /* R89 - Write Sequencer 3 */
+ 0x0000, /* R90 - Write Sequencer 4 */
+ 0x0000, /* R91 - Write Sequencer 5 */
+ 0x0000, /* R92 - Write Sequencer 6 */
+ 0x0000, /* R93 - Write Sequencer 7 */
+ 0x0000, /* R94 */
+ 0x0000, /* R95 */
+ 0x0000, /* R96 */
+ 0x0000, /* R97 */
+ 0x0000, /* R98 */
+ 0x0000, /* R99 */
+ 0x0000, /* R100 */
+ 0x0000, /* R101 */
+ 0x0000, /* R102 */
+ 0x0000, /* R103 */
+ 0x0000, /* R104 */
+ 0x0000, /* R105 */
+ 0x0000, /* R106 */
+ 0x0000, /* R107 */
+ 0x0000, /* R108 */
+ 0x0000, /* R109 */
+ 0x0000, /* R110 */
+ 0x0000, /* R111 */
+ 0x0000, /* R112 */
+ 0x0000, /* R113 */
+ 0x0000, /* R114 */
+ 0x0000, /* R115 */
+ 0x0000, /* R116 */
+ 0x0000, /* R117 */
+ 0x0000, /* R118 */
+ 0x0000, /* R119 */
+ 0x0000, /* R120 */
+ 0x0000, /* R121 */
+ 0x0000, /* R122 */
+ 0x0000, /* R123 */
+ 0x0000, /* R124 */
+ 0x0000, /* R125 */
+ 0x0000, /* R126 */
+ 0x0000, /* R127 */
+ 0x0000, /* R128 */
+ 0x0000, /* R129 */
+ 0x0000, /* R130 */
+ 0x0000, /* R131 */
+ 0x0000, /* R132 */
+ 0x0000, /* R133 */
+ 0x0000, /* R134 */
+ 0x0000, /* R135 */
+ 0x0000, /* R136 */
+ 0x0000, /* R137 */
+ 0x0000, /* R138 */
+ 0x0000, /* R139 */
+ 0x0000, /* R140 */
+ 0x0000, /* R141 */
+ 0x0000, /* R142 */
+ 0x0000, /* R143 */
+ 0x0000, /* R144 */
+ 0x0000, /* R145 */
+ 0x0000, /* R146 */
+ 0x0000, /* R147 */
+ 0x0000, /* R148 */
+ 0x0000, /* R149 */
+ 0x0000, /* R150 */
+ 0x0000, /* R151 */
+ 0x0000, /* R152 */
+ 0x0000, /* R153 */
+ 0x0000, /* R154 */
+ 0x0000, /* R155 */
+ 0x0000, /* R156 */
+ 0x0000, /* R157 */
+ 0x0000, /* R158 */
+ 0x0000, /* R159 */
+ 0x0000, /* R160 */
+ 0x0000, /* R161 */
+ 0x0000, /* R162 */
+ 0x0000, /* R163 */
+ 0x0000, /* R164 */
+ 0x0000, /* R165 */
+ 0x0000, /* R166 */
+ 0x0000, /* R167 */
+ 0x0000, /* R168 */
+ 0x0000, /* R169 */
+ 0x0000, /* R170 */
+ 0x0000, /* R171 */
+ 0x0000, /* R172 */
+ 0x0000, /* R173 */
+ 0x0000, /* R174 */
+ 0x0000, /* R175 */
+ 0x0000, /* R176 */
+ 0x0000, /* R177 */
+ 0x0000, /* R178 */
+ 0x0000, /* R179 */
+ 0x0000, /* R180 */
+ 0x0000, /* R181 */
+ 0x0000, /* R182 */
+ 0x0000, /* R183 */
+ 0x0000, /* R184 */
+ 0x0000, /* R185 */
+ 0x0000, /* R186 */
+ 0x0000, /* R187 */
+ 0x0000, /* R188 */
+ 0x0000, /* R189 */
+ 0x0000, /* R190 */
+ 0x0000, /* R191 */
+ 0x0000, /* R192 */
+ 0x0000, /* R193 */
+ 0x0000, /* R194 */
+ 0x0000, /* R195 */
+ 0x0030, /* R196 */
+ 0x0006, /* R197 */
+ 0x0000, /* R198 */
+ 0x0060, /* R199 */
+ 0x0000, /* R200 */
+ 0x003F, /* R201 */
+ 0x0000, /* R202 */
+ 0x0000, /* R203 */
+ 0x0000, /* R204 */
+ 0x0001, /* R205 */
+ 0x0000, /* R206 */
+ 0x0181, /* R207 */
+ 0x0005, /* R208 */
+ 0x0008, /* R209 */
+ 0x0008, /* R210 */
+ 0x0000, /* R211 */
+ 0x013B, /* R212 */
+ 0x0000, /* R213 */
+ 0x0000, /* R214 */
+ 0x0000, /* R215 */
+ 0x0000, /* R216 */
+ 0x0070, /* R217 */
+ 0x0000, /* R218 */
+ 0x0000, /* R219 */
+ 0x0000, /* R220 */
+ 0x0000, /* R221 */
+ 0x0000, /* R222 */
+ 0x0003, /* R223 */
+ 0x0000, /* R224 */
+ 0x0000, /* R225 */
+ 0x0001, /* R226 */
+ 0x0008, /* R227 */
+ 0x0000, /* R228 */
+ 0x0000, /* R229 */
+ 0x0000, /* R230 */
+ 0x0000, /* R231 */
+ 0x0004, /* R232 */
+ 0x0000, /* R233 */
+ 0x0000, /* R234 */
+ 0x0000, /* R235 */
+ 0x0000, /* R236 */
+ 0x0000, /* R237 */
+ 0x0080, /* R238 */
+ 0x0000, /* R239 */
+ 0x0000, /* R240 */
+ 0x0000, /* R241 */
+ 0x0000, /* R242 */
+ 0x0000, /* R243 */
+ 0x0000, /* R244 */
+ 0x0052, /* R245 */
+ 0x0110, /* R246 */
+ 0x0040, /* R247 */
+ 0x0000, /* R248 */
+ 0x0030, /* R249 */
+ 0x0000, /* R250 */
+ 0x0000, /* R251 */
+ 0x0001, /* R252 - General test 1 */
+};
+
+struct wm8961_priv {
+ enum snd_soc_control_type control_type;
+ int sysclk;
+};
+
+static int wm8961_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case WM8961_SOFTWARE_RESET:
+ case WM8961_WRITE_SEQUENCER_7:
+ case WM8961_DC_SERVO_1:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static int wm8961_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8961_SOFTWARE_RESET, 0);
+}
+
+/*
+ * The headphone output supports special anti-pop sequences giving
+ * silent power up and power down.
+ */
+static int wm8961_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u16 hp_reg = snd_soc_read(codec, WM8961_ANALOGUE_HP_0);
+ u16 cp_reg = snd_soc_read(codec, WM8961_CHARGE_PUMP_1);
+ u16 pwr_reg = snd_soc_read(codec, WM8961_PWR_MGMT_2);
+ u16 dcs_reg = snd_soc_read(codec, WM8961_DC_SERVO_1);
+ int timeout = 500;
+
+ if (event & SND_SOC_DAPM_POST_PMU) {
+ /* Make sure the output is shorted */
+ hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT);
+ snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+ /* Enable the charge pump */
+ cp_reg |= WM8961_CP_ENA;
+ snd_soc_write(codec, WM8961_CHARGE_PUMP_1, cp_reg);
+ mdelay(5);
+
+ /* Enable the PGA */
+ pwr_reg |= WM8961_LOUT1_PGA | WM8961_ROUT1_PGA;
+ snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg);
+
+ /* Enable the amplifier */
+ hp_reg |= WM8961_HPR_ENA | WM8961_HPL_ENA;
+ snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+ /* Second stage enable */
+ hp_reg |= WM8961_HPR_ENA_DLY | WM8961_HPL_ENA_DLY;
+ snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+ /* Enable the DC servo & trigger startup */
+ dcs_reg |=
+ WM8961_DCS_ENA_CHAN_HPR | WM8961_DCS_TRIG_STARTUP_HPR |
+ WM8961_DCS_ENA_CHAN_HPL | WM8961_DCS_TRIG_STARTUP_HPL;
+ dev_dbg(codec->dev, "Enabling DC servo\n");
+
+ snd_soc_write(codec, WM8961_DC_SERVO_1, dcs_reg);
+ do {
+ msleep(1);
+ dcs_reg = snd_soc_read(codec, WM8961_DC_SERVO_1);
+ } while (--timeout &&
+ dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR |
+ WM8961_DCS_TRIG_STARTUP_HPL));
+ if (dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR |
+ WM8961_DCS_TRIG_STARTUP_HPL))
+ dev_err(codec->dev, "DC servo timed out\n");
+ else
+ dev_dbg(codec->dev, "DC servo startup complete\n");
+
+ /* Enable the output stage */
+ hp_reg |= WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP;
+ snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+ /* Remove the short on the output stage */
+ hp_reg |= WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT;
+ snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+ }
+
+ if (event & SND_SOC_DAPM_PRE_PMD) {
+ /* Short the output */
+ hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT);
+ snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+ /* Disable the output stage */
+ hp_reg &= ~(WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP);
+ snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+ /* Disable DC offset cancellation */
+ dcs_reg &= ~(WM8961_DCS_ENA_CHAN_HPR |
+ WM8961_DCS_ENA_CHAN_HPL);
+ snd_soc_write(codec, WM8961_DC_SERVO_1, dcs_reg);
+
+ /* Finish up */
+ hp_reg &= ~(WM8961_HPR_ENA_DLY | WM8961_HPR_ENA |
+ WM8961_HPL_ENA_DLY | WM8961_HPL_ENA);
+ snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+ /* Disable the PGA */
+ pwr_reg &= ~(WM8961_LOUT1_PGA | WM8961_ROUT1_PGA);
+ snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg);
+
+ /* Disable the charge pump */
+ dev_dbg(codec->dev, "Disabling charge pump\n");
+ snd_soc_write(codec, WM8961_CHARGE_PUMP_1,
+ cp_reg & ~WM8961_CP_ENA);
+ }
+
+ return 0;
+}
+
+static int wm8961_spk_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u16 pwr_reg = snd_soc_read(codec, WM8961_PWR_MGMT_2);
+ u16 spk_reg = snd_soc_read(codec, WM8961_CLASS_D_CONTROL_1);
+
+ if (event & SND_SOC_DAPM_POST_PMU) {
+ /* Enable the PGA */
+ pwr_reg |= WM8961_SPKL_PGA | WM8961_SPKR_PGA;
+ snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg);
+
+ /* Enable the amplifier */
+ spk_reg |= WM8961_SPKL_ENA | WM8961_SPKR_ENA;
+ snd_soc_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg);
+ }
+
+ if (event & SND_SOC_DAPM_PRE_PMD) {
+ /* Enable the amplifier */
+ spk_reg &= ~(WM8961_SPKL_ENA | WM8961_SPKR_ENA);
+ snd_soc_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg);
+
+ /* Enable the PGA */
+ pwr_reg &= ~(WM8961_SPKL_PGA | WM8961_SPKR_PGA);
+ snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg);
+ }
+
+ return 0;
+}
+
+static const char *adc_hpf_text[] = {
+ "Hi-fi", "Voice 1", "Voice 2", "Voice 3",
+};
+
+static const struct soc_enum adc_hpf =
+ SOC_ENUM_SINGLE(WM8961_ADC_DAC_CONTROL_2, 7, 4, adc_hpf_text);
+
+static const char *dac_deemph_text[] = {
+ "None", "32kHz", "44.1kHz", "48kHz",
+};
+
+static const struct soc_enum dac_deemph =
+ SOC_ENUM_SINGLE(WM8961_ADC_DAC_CONTROL_1, 1, 4, dac_deemph_text);
+
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(hp_sec_tlv, -700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0);
+static unsigned int boost_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(13, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(20, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(29, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(pga_tlv, -2325, 75, 0);
+
+static const struct snd_kcontrol_new wm8961_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Headphone Volume", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_TLV("Headphone Secondary Volume", WM8961_ANALOGUE_HP_2,
+ 6, 3, 7, 0, hp_sec_tlv),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME,
+ 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Speaker Volume", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Speaker ZC Switch", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME,
+ 7, 1, 0),
+SOC_SINGLE("Speaker AC Gain", WM8961_CLASS_D_CONTROL_2, 0, 7, 0),
+
+SOC_SINGLE("DAC x128 OSR Switch", WM8961_ADC_DAC_CONTROL_2, 0, 1, 0),
+SOC_ENUM("DAC Deemphasis", dac_deemph),
+SOC_SINGLE("DAC Soft Mute Switch", WM8961_ADC_DAC_CONTROL_2, 3, 1, 0),
+
+SOC_DOUBLE_R_TLV("Sidetone Volume", WM8961_DSP_SIDETONE_0,
+ WM8961_DSP_SIDETONE_1, 4, 12, 0, sidetone_tlv),
+
+SOC_SINGLE("ADC High Pass Filter Switch", WM8961_ADC_DAC_CONTROL_1, 0, 1, 0),
+SOC_ENUM("ADC High Pass Filter Mode", adc_hpf),
+
+SOC_DOUBLE_R_TLV("Capture Volume",
+ WM8961_LEFT_ADC_VOLUME, WM8961_RIGHT_ADC_VOLUME,
+ 1, 119, 0, adc_tlv),
+SOC_DOUBLE_R_TLV("Capture Boost Volume",
+ WM8961_ADCL_SIGNAL_PATH, WM8961_ADCR_SIGNAL_PATH,
+ 4, 3, 0, boost_tlv),
+SOC_DOUBLE_R_TLV("Capture PGA Volume",
+ WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME,
+ 0, 62, 0, pga_tlv),
+SOC_DOUBLE_R("Capture PGA ZC Switch",
+ WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME,
+ 6, 1, 1),
+SOC_DOUBLE_R("Capture PGA Switch",
+ WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME,
+ 7, 1, 1),
+};
+
+static const char *sidetone_text[] = {
+ "None", "Left", "Right"
+};
+
+static const struct soc_enum dacl_sidetone =
+ SOC_ENUM_SINGLE(WM8961_DSP_SIDETONE_0, 2, 3, sidetone_text);
+
+static const struct soc_enum dacr_sidetone =
+ SOC_ENUM_SINGLE(WM8961_DSP_SIDETONE_1, 2, 3, sidetone_text);
+
+static const struct snd_kcontrol_new dacl_mux =
+ SOC_DAPM_ENUM("DACL Sidetone", dacl_sidetone);
+
+static const struct snd_kcontrol_new dacr_mux =
+ SOC_DAPM_ENUM("DACR Sidetone", dacr_sidetone);
+
+static const struct snd_soc_dapm_widget wm8961_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("LINPUT"),
+SND_SOC_DAPM_INPUT("RINPUT"),
+
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8961_CLOCKING2, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Left Input", WM8961_PWR_MGMT_1, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Input", WM8961_PWR_MGMT_1, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", WM8961_PWR_MGMT_1, 3, 0),
+SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", WM8961_PWR_MGMT_1, 2, 0),
+
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8961_PWR_MGMT_1, 1, 0),
+
+SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &dacl_mux),
+SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &dacr_mux),
+
+SND_SOC_DAPM_DAC("DACL", "HiFi Playback", WM8961_PWR_MGMT_2, 8, 0),
+SND_SOC_DAPM_DAC("DACR", "HiFi Playback", WM8961_PWR_MGMT_2, 7, 0),
+
+/* Handle as a mono path for DCS */
+SND_SOC_DAPM_PGA_E("Headphone Output", SND_SOC_NOPM,
+ 4, 0, NULL, 0, wm8961_hp_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_E("Speaker Output", SND_SOC_NOPM,
+ 4, 0, NULL, 0, wm8961_spk_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+SND_SOC_DAPM_OUTPUT("SPK_LN"),
+SND_SOC_DAPM_OUTPUT("SPK_LP"),
+SND_SOC_DAPM_OUTPUT("SPK_RN"),
+SND_SOC_DAPM_OUTPUT("SPK_RP"),
+};
+
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+ { "DACL", NULL, "CLK_DSP" },
+ { "DACL", NULL, "DACL Sidetone" },
+ { "DACR", NULL, "CLK_DSP" },
+ { "DACR", NULL, "DACR Sidetone" },
+
+ { "DACL Sidetone", "Left", "ADCL" },
+ { "DACL Sidetone", "Right", "ADCR" },
+
+ { "DACR Sidetone", "Left", "ADCL" },
+ { "DACR Sidetone", "Right", "ADCR" },
+
+ { "HP_L", NULL, "Headphone Output" },
+ { "HP_R", NULL, "Headphone Output" },
+ { "Headphone Output", NULL, "DACL" },
+ { "Headphone Output", NULL, "DACR" },
+
+ { "SPK_LN", NULL, "Speaker Output" },
+ { "SPK_LP", NULL, "Speaker Output" },
+ { "SPK_RN", NULL, "Speaker Output" },
+ { "SPK_RP", NULL, "Speaker Output" },
+
+ { "Speaker Output", NULL, "DACL" },
+ { "Speaker Output", NULL, "DACR" },
+
+ { "ADCL", NULL, "Left Input" },
+ { "ADCL", NULL, "CLK_DSP" },
+ { "ADCR", NULL, "Right Input" },
+ { "ADCR", NULL, "CLK_DSP" },
+
+ { "Left Input", NULL, "LINPUT" },
+ { "Right Input", NULL, "RINPUT" },
+
+};
+
+/* Values for CLK_SYS_RATE */
+static struct {
+ int ratio;
+ u16 val;
+} wm8961_clk_sys_ratio[] = {
+ { 64, 0 },
+ { 128, 1 },
+ { 192, 2 },
+ { 256, 3 },
+ { 384, 4 },
+ { 512, 5 },
+ { 768, 6 },
+ { 1024, 7 },
+ { 1408, 8 },
+ { 1536, 9 },
+};
+
+/* Values for SAMPLE_RATE */
+static struct {
+ int rate;
+ u16 val;
+} wm8961_srate[] = {
+ { 48000, 0 },
+ { 44100, 0 },
+ { 32000, 1 },
+ { 22050, 2 },
+ { 24000, 2 },
+ { 16000, 3 },
+ { 11250, 4 },
+ { 12000, 4 },
+ { 8000, 5 },
+};
+
+static int wm8961_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8961_priv *wm8961 = snd_soc_codec_get_drvdata(codec);
+ int i, best, target, fs;
+ u16 reg;
+
+ fs = params_rate(params);
+
+ if (!wm8961->sysclk) {
+ dev_err(codec->dev, "MCLK has not been specified\n");
+ return -EINVAL;
+ }
+
+ /* Find the closest sample rate for the filters */
+ best = 0;
+ for (i = 0; i < ARRAY_SIZE(wm8961_srate); i++) {
+ if (abs(wm8961_srate[i].rate - fs) <
+ abs(wm8961_srate[best].rate - fs))
+ best = i;
+ }
+ reg = snd_soc_read(codec, WM8961_ADDITIONAL_CONTROL_3);
+ reg &= ~WM8961_SAMPLE_RATE_MASK;
+ reg |= wm8961_srate[best].val;
+ snd_soc_write(codec, WM8961_ADDITIONAL_CONTROL_3, reg);
+ dev_dbg(codec->dev, "Selected SRATE %dHz for %dHz\n",
+ wm8961_srate[best].rate, fs);
+
+ /* Select a CLK_SYS/fs ratio equal to or higher than required */
+ target = wm8961->sysclk / fs;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && target < 64) {
+ dev_err(codec->dev,
+ "SYSCLK must be at least 64*fs for DAC\n");
+ return -EINVAL;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && target < 256) {
+ dev_err(codec->dev,
+ "SYSCLK must be at least 256*fs for ADC\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8961_clk_sys_ratio); i++) {
+ if (wm8961_clk_sys_ratio[i].ratio >= target)
+ break;
+ }
+ if (i == ARRAY_SIZE(wm8961_clk_sys_ratio)) {
+ dev_err(codec->dev, "Unable to generate CLK_SYS_RATE\n");
+ return -EINVAL;
+ }
+ dev_dbg(codec->dev, "Selected CLK_SYS_RATE of %d for %d/%d=%d\n",
+ wm8961_clk_sys_ratio[i].ratio, wm8961->sysclk, fs,
+ wm8961->sysclk / fs);
+
+ reg = snd_soc_read(codec, WM8961_CLOCKING_4);
+ reg &= ~WM8961_CLK_SYS_RATE_MASK;
+ reg |= wm8961_clk_sys_ratio[i].val << WM8961_CLK_SYS_RATE_SHIFT;
+ snd_soc_write(codec, WM8961_CLOCKING_4, reg);
+
+ reg = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_0);
+ reg &= ~WM8961_WL_MASK;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ reg |= 1 << WM8961_WL_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ reg |= 2 << WM8961_WL_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ reg |= 3 << WM8961_WL_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_write(codec, WM8961_AUDIO_INTERFACE_0, reg);
+
+ /* Sloping stop-band filter is recommended for <= 24kHz */
+ reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_2);
+ if (fs <= 24000)
+ reg |= WM8961_DACSLOPE;
+ else
+ reg &= ~WM8961_DACSLOPE;
+ snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_2, reg);
+
+ return 0;
+}
+
+static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq,
+ int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8961_priv *wm8961 = snd_soc_codec_get_drvdata(codec);
+ u16 reg = snd_soc_read(codec, WM8961_CLOCKING1);
+
+ if (freq > 33000000) {
+ dev_err(codec->dev, "MCLK must be <33MHz\n");
+ return -EINVAL;
+ }
+
+ if (freq > 16500000) {
+ dev_dbg(codec->dev, "Using MCLK/2 for %dHz MCLK\n", freq);
+ reg |= WM8961_MCLKDIV;
+ freq /= 2;
+ } else {
+ dev_dbg(codec->dev, "Using MCLK/1 for %dHz MCLK\n", freq);
+ reg &= ~WM8961_MCLKDIV;
+ }
+
+ snd_soc_write(codec, WM8961_CLOCKING1, reg);
+
+ wm8961->sysclk = freq;
+
+ return 0;
+}
+
+static int wm8961_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 aif = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_0);
+
+ aif &= ~(WM8961_BCLKINV | WM8961_LRP |
+ WM8961_MS | WM8961_FORMAT_MASK);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif |= WM8961_MS;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif |= 1;
+ break;
+
+ case SND_SOC_DAIFMT_I2S:
+ aif |= 2;
+ break;
+
+ case SND_SOC_DAIFMT_DSP_B:
+ aif |= WM8961_LRP;
+ case SND_SOC_DAIFMT_DSP_A:
+ aif |= 3;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ case SND_SOC_DAIFMT_IB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif |= WM8961_LRP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif |= WM8961_BCLKINV;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif |= WM8961_BCLKINV | WM8961_LRP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return snd_soc_write(codec, WM8961_AUDIO_INTERFACE_0, aif);
+}
+
+static int wm8961_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 reg = snd_soc_read(codec, WM8961_ADDITIONAL_CONTROL_2);
+
+ if (tristate)
+ reg |= WM8961_TRIS;
+ else
+ reg &= ~WM8961_TRIS;
+
+ return snd_soc_write(codec, WM8961_ADDITIONAL_CONTROL_2, reg);
+}
+
+static int wm8961_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_1);
+
+ if (mute)
+ reg |= WM8961_DACMU;
+ else
+ reg &= ~WM8961_DACMU;
+
+ msleep(17);
+
+ return snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_1, reg);
+}
+
+static int wm8961_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8961_BCLK:
+ reg = snd_soc_read(codec, WM8961_CLOCKING2);
+ reg &= ~WM8961_BCLKDIV_MASK;
+ reg |= div;
+ snd_soc_write(codec, WM8961_CLOCKING2, reg);
+ break;
+
+ case WM8961_LRCLK:
+ reg = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_2);
+ reg &= ~WM8961_LRCLK_RATE_MASK;
+ reg |= div;
+ snd_soc_write(codec, WM8961_AUDIO_INTERFACE_2, reg);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8961_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+
+ /* This is all slightly unusual since we have no bypass paths
+ * and the output amplifier structure means we can just slam
+ * the biases straight up rather than having to ramp them
+ * slowly.
+ */
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) {
+ /* Enable bias generation */
+ reg = snd_soc_read(codec, WM8961_ANTI_POP);
+ reg |= WM8961_BUFIOEN | WM8961_BUFDCOPEN;
+ snd_soc_write(codec, WM8961_ANTI_POP, reg);
+
+ /* VMID=2*50k, VREF */
+ reg = snd_soc_read(codec, WM8961_PWR_MGMT_1);
+ reg &= ~WM8961_VMIDSEL_MASK;
+ reg |= (1 << WM8961_VMIDSEL_SHIFT) | WM8961_VREF;
+ snd_soc_write(codec, WM8961_PWR_MGMT_1, reg);
+ }
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_PREPARE) {
+ /* VREF off */
+ reg = snd_soc_read(codec, WM8961_PWR_MGMT_1);
+ reg &= ~WM8961_VREF;
+ snd_soc_write(codec, WM8961_PWR_MGMT_1, reg);
+
+ /* Bias generation off */
+ reg = snd_soc_read(codec, WM8961_ANTI_POP);
+ reg &= ~(WM8961_BUFIOEN | WM8961_BUFDCOPEN);
+ snd_soc_write(codec, WM8961_ANTI_POP, reg);
+
+ /* VMID off */
+ reg = snd_soc_read(codec, WM8961_PWR_MGMT_1);
+ reg &= ~WM8961_VMIDSEL_MASK;
+ snd_soc_write(codec, WM8961_PWR_MGMT_1, reg);
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+
+#define WM8961_RATES SNDRV_PCM_RATE_8000_48000
+
+#define WM8961_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8961_dai_ops = {
+ .hw_params = wm8961_hw_params,
+ .set_sysclk = wm8961_set_sysclk,
+ .set_fmt = wm8961_set_fmt,
+ .digital_mute = wm8961_digital_mute,
+ .set_tristate = wm8961_set_tristate,
+ .set_clkdiv = wm8961_set_clkdiv,
+};
+
+static struct snd_soc_dai_driver wm8961_dai = {
+ .name = "wm8961-hifi",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8961_RATES,
+ .formats = WM8961_FORMATS,},
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8961_RATES,
+ .formats = WM8961_FORMATS,},
+ .ops = &wm8961_dai_ops,
+};
+
+static int wm8961_probe(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret = 0;
+ u16 reg;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ reg = snd_soc_read(codec, WM8961_SOFTWARE_RESET);
+ if (reg != 0x1801) {
+ dev_err(codec->dev, "Device is not a WM8961: ID=0x%x\n", reg);
+ return -EINVAL;
+ }
+
+ /* This isn't volatile - readback doesn't correspond to write */
+ reg = codec->hw_read(codec, WM8961_RIGHT_INPUT_VOLUME);
+ dev_info(codec->dev, "WM8961 family %d revision %c\n",
+ (reg & WM8961_DEVICE_ID_MASK) >> WM8961_DEVICE_ID_SHIFT,
+ ((reg & WM8961_CHIP_REV_MASK) >> WM8961_CHIP_REV_SHIFT)
+ + 'A');
+
+ ret = wm8961_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ /* Enable class W */
+ reg = snd_soc_read(codec, WM8961_CHARGE_PUMP_B);
+ reg |= WM8961_CP_DYN_PWR_MASK;
+ snd_soc_write(codec, WM8961_CHARGE_PUMP_B, reg);
+
+ /* Latch volume update bits (right channel only, we always
+ * write both out) and default ZC on. */
+ reg = snd_soc_read(codec, WM8961_ROUT1_VOLUME);
+ snd_soc_write(codec, WM8961_ROUT1_VOLUME,
+ reg | WM8961_LO1ZC | WM8961_OUT1VU);
+ snd_soc_write(codec, WM8961_LOUT1_VOLUME, reg | WM8961_LO1ZC);
+ reg = snd_soc_read(codec, WM8961_ROUT2_VOLUME);
+ snd_soc_write(codec, WM8961_ROUT2_VOLUME,
+ reg | WM8961_SPKRZC | WM8961_SPKVU);
+ snd_soc_write(codec, WM8961_LOUT2_VOLUME, reg | WM8961_SPKLZC);
+
+ reg = snd_soc_read(codec, WM8961_RIGHT_ADC_VOLUME);
+ snd_soc_write(codec, WM8961_RIGHT_ADC_VOLUME, reg | WM8961_ADCVU);
+ reg = snd_soc_read(codec, WM8961_RIGHT_INPUT_VOLUME);
+ snd_soc_write(codec, WM8961_RIGHT_INPUT_VOLUME, reg | WM8961_IPVU);
+
+ /* Use soft mute by default */
+ reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_2);
+ reg |= WM8961_DACSMM;
+ snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_2, reg);
+
+ /* Use automatic clocking mode by default; for now this is all
+ * we support.
+ */
+ reg = snd_soc_read(codec, WM8961_CLOCKING_3);
+ reg &= ~WM8961_MANUAL_MODE;
+ snd_soc_write(codec, WM8961_CLOCKING_3, reg);
+
+ wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_add_controls(codec, wm8961_snd_controls,
+ ARRAY_SIZE(wm8961_snd_controls));
+ snd_soc_dapm_new_controls(dapm, wm8961_dapm_widgets,
+ ARRAY_SIZE(wm8961_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
+
+ return 0;
+}
+
+static int wm8961_remove(struct snd_soc_codec *codec)
+{
+ wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm8961_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8961_resume(struct snd_soc_codec *codec)
+{
+ u16 *reg_cache = codec->reg_cache;
+ int i;
+
+ for (i = 0; i < codec->driver->reg_cache_size; i++) {
+ if (reg_cache[i] == wm8961_reg_defaults[i])
+ continue;
+
+ if (i == WM8961_SOFTWARE_RESET)
+ continue;
+
+ snd_soc_write(codec, i, reg_cache[i]);
+ }
+
+ wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define wm8961_suspend NULL
+#define wm8961_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8961 = {
+ .probe = wm8961_probe,
+ .remove = wm8961_remove,
+ .suspend = wm8961_suspend,
+ .resume = wm8961_resume,
+ .set_bias_level = wm8961_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8961_reg_defaults),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8961_reg_defaults,
+ .volatile_register = wm8961_volatile_register,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8961_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8961_priv *wm8961;
+ int ret;
+
+ wm8961 = kzalloc(sizeof(struct wm8961_priv), GFP_KERNEL);
+ if (wm8961 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8961);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8961, &wm8961_dai, 1);
+ if (ret < 0)
+ kfree(wm8961);
+ return ret;
+}
+
+static __devexit int wm8961_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8961_i2c_id[] = {
+ { "wm8961", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8961_i2c_id);
+
+static struct i2c_driver wm8961_i2c_driver = {
+ .driver = {
+ .name = "wm8961-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8961_i2c_probe,
+ .remove = __devexit_p(wm8961_i2c_remove),
+ .id_table = wm8961_i2c_id,
+};
+#endif
+
+static int __init wm8961_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8961_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8961 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8961_modinit);
+
+static void __exit wm8961_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8961_i2c_driver);
+#endif
+}
+module_exit(wm8961_exit);
+
+MODULE_DESCRIPTION("ASoC WM8961 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8961.h b/sound/soc/codecs/wm8961.h
new file mode 100644
index 00000000..1d736e57
--- /dev/null
+++ b/sound/soc/codecs/wm8961.h
@@ -0,0 +1,863 @@
+/*
+ * wm8961.h -- WM8961 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8961_H
+#define _WM8961_H
+
+#include <sound/soc.h>
+
+#define WM8961_BCLK 1
+#define WM8961_LRCLK 2
+
+#define WM8961_BCLK_DIV_1 0
+#define WM8961_BCLK_DIV_1_5 1
+#define WM8961_BCLK_DIV_2 2
+#define WM8961_BCLK_DIV_3 3
+#define WM8961_BCLK_DIV_4 4
+#define WM8961_BCLK_DIV_5_5 5
+#define WM8961_BCLK_DIV_6 6
+#define WM8961_BCLK_DIV_8 7
+#define WM8961_BCLK_DIV_11 8
+#define WM8961_BCLK_DIV_12 9
+#define WM8961_BCLK_DIV_16 10
+#define WM8961_BCLK_DIV_24 11
+#define WM8961_BCLK_DIV_32 13
+
+
+/*
+ * Register values.
+ */
+#define WM8961_LEFT_INPUT_VOLUME 0x00
+#define WM8961_RIGHT_INPUT_VOLUME 0x01
+#define WM8961_LOUT1_VOLUME 0x02
+#define WM8961_ROUT1_VOLUME 0x03
+#define WM8961_CLOCKING1 0x04
+#define WM8961_ADC_DAC_CONTROL_1 0x05
+#define WM8961_ADC_DAC_CONTROL_2 0x06
+#define WM8961_AUDIO_INTERFACE_0 0x07
+#define WM8961_CLOCKING2 0x08
+#define WM8961_AUDIO_INTERFACE_1 0x09
+#define WM8961_LEFT_DAC_VOLUME 0x0A
+#define WM8961_RIGHT_DAC_VOLUME 0x0B
+#define WM8961_AUDIO_INTERFACE_2 0x0E
+#define WM8961_SOFTWARE_RESET 0x0F
+#define WM8961_ALC1 0x11
+#define WM8961_ALC2 0x12
+#define WM8961_ALC3 0x13
+#define WM8961_NOISE_GATE 0x14
+#define WM8961_LEFT_ADC_VOLUME 0x15
+#define WM8961_RIGHT_ADC_VOLUME 0x16
+#define WM8961_ADDITIONAL_CONTROL_1 0x17
+#define WM8961_ADDITIONAL_CONTROL_2 0x18
+#define WM8961_PWR_MGMT_1 0x19
+#define WM8961_PWR_MGMT_2 0x1A
+#define WM8961_ADDITIONAL_CONTROL_3 0x1B
+#define WM8961_ANTI_POP 0x1C
+#define WM8961_CLOCKING_3 0x1E
+#define WM8961_ADCL_SIGNAL_PATH 0x20
+#define WM8961_ADCR_SIGNAL_PATH 0x21
+#define WM8961_LOUT2_VOLUME 0x28
+#define WM8961_ROUT2_VOLUME 0x29
+#define WM8961_PWR_MGMT_3 0x2F
+#define WM8961_ADDITIONAL_CONTROL_4 0x30
+#define WM8961_CLASS_D_CONTROL_1 0x31
+#define WM8961_CLASS_D_CONTROL_2 0x33
+#define WM8961_CLOCKING_4 0x38
+#define WM8961_DSP_SIDETONE_0 0x39
+#define WM8961_DSP_SIDETONE_1 0x3A
+#define WM8961_DC_SERVO_0 0x3C
+#define WM8961_DC_SERVO_1 0x3D
+#define WM8961_DC_SERVO_3 0x3F
+#define WM8961_DC_SERVO_5 0x41
+#define WM8961_ANALOGUE_PGA_BIAS 0x44
+#define WM8961_ANALOGUE_HP_0 0x45
+#define WM8961_ANALOGUE_HP_2 0x47
+#define WM8961_CHARGE_PUMP_1 0x48
+#define WM8961_CHARGE_PUMP_B 0x52
+#define WM8961_WRITE_SEQUENCER_1 0x57
+#define WM8961_WRITE_SEQUENCER_2 0x58
+#define WM8961_WRITE_SEQUENCER_3 0x59
+#define WM8961_WRITE_SEQUENCER_4 0x5A
+#define WM8961_WRITE_SEQUENCER_5 0x5B
+#define WM8961_WRITE_SEQUENCER_6 0x5C
+#define WM8961_WRITE_SEQUENCER_7 0x5D
+#define WM8961_GENERAL_TEST_1 0xFC
+
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Left Input volume
+ */
+#define WM8961_IPVU 0x0100 /* IPVU */
+#define WM8961_IPVU_MASK 0x0100 /* IPVU */
+#define WM8961_IPVU_SHIFT 8 /* IPVU */
+#define WM8961_IPVU_WIDTH 1 /* IPVU */
+#define WM8961_LINMUTE 0x0080 /* LINMUTE */
+#define WM8961_LINMUTE_MASK 0x0080 /* LINMUTE */
+#define WM8961_LINMUTE_SHIFT 7 /* LINMUTE */
+#define WM8961_LINMUTE_WIDTH 1 /* LINMUTE */
+#define WM8961_LIZC 0x0040 /* LIZC */
+#define WM8961_LIZC_MASK 0x0040 /* LIZC */
+#define WM8961_LIZC_SHIFT 6 /* LIZC */
+#define WM8961_LIZC_WIDTH 1 /* LIZC */
+#define WM8961_LINVOL_MASK 0x003F /* LINVOL - [5:0] */
+#define WM8961_LINVOL_SHIFT 0 /* LINVOL - [5:0] */
+#define WM8961_LINVOL_WIDTH 6 /* LINVOL - [5:0] */
+
+/*
+ * R1 (0x01) - Right Input volume
+ */
+#define WM8961_DEVICE_ID_MASK 0xF000 /* DEVICE_ID - [15:12] */
+#define WM8961_DEVICE_ID_SHIFT 12 /* DEVICE_ID - [15:12] */
+#define WM8961_DEVICE_ID_WIDTH 4 /* DEVICE_ID - [15:12] */
+#define WM8961_CHIP_REV_MASK 0x0E00 /* CHIP_REV - [11:9] */
+#define WM8961_CHIP_REV_SHIFT 9 /* CHIP_REV - [11:9] */
+#define WM8961_CHIP_REV_WIDTH 3 /* CHIP_REV - [11:9] */
+#define WM8961_IPVU 0x0100 /* IPVU */
+#define WM8961_IPVU_MASK 0x0100 /* IPVU */
+#define WM8961_IPVU_SHIFT 8 /* IPVU */
+#define WM8961_IPVU_WIDTH 1 /* IPVU */
+#define WM8961_RINMUTE 0x0080 /* RINMUTE */
+#define WM8961_RINMUTE_MASK 0x0080 /* RINMUTE */
+#define WM8961_RINMUTE_SHIFT 7 /* RINMUTE */
+#define WM8961_RINMUTE_WIDTH 1 /* RINMUTE */
+#define WM8961_RIZC 0x0040 /* RIZC */
+#define WM8961_RIZC_MASK 0x0040 /* RIZC */
+#define WM8961_RIZC_SHIFT 6 /* RIZC */
+#define WM8961_RIZC_WIDTH 1 /* RIZC */
+#define WM8961_RINVOL_MASK 0x003F /* RINVOL - [5:0] */
+#define WM8961_RINVOL_SHIFT 0 /* RINVOL - [5:0] */
+#define WM8961_RINVOL_WIDTH 6 /* RINVOL - [5:0] */
+
+/*
+ * R2 (0x02) - LOUT1 volume
+ */
+#define WM8961_OUT1VU 0x0100 /* OUT1VU */
+#define WM8961_OUT1VU_MASK 0x0100 /* OUT1VU */
+#define WM8961_OUT1VU_SHIFT 8 /* OUT1VU */
+#define WM8961_OUT1VU_WIDTH 1 /* OUT1VU */
+#define WM8961_LO1ZC 0x0080 /* LO1ZC */
+#define WM8961_LO1ZC_MASK 0x0080 /* LO1ZC */
+#define WM8961_LO1ZC_SHIFT 7 /* LO1ZC */
+#define WM8961_LO1ZC_WIDTH 1 /* LO1ZC */
+#define WM8961_LOUT1VOL_MASK 0x007F /* LOUT1VOL - [6:0] */
+#define WM8961_LOUT1VOL_SHIFT 0 /* LOUT1VOL - [6:0] */
+#define WM8961_LOUT1VOL_WIDTH 7 /* LOUT1VOL - [6:0] */
+
+/*
+ * R3 (0x03) - ROUT1 volume
+ */
+#define WM8961_OUT1VU 0x0100 /* OUT1VU */
+#define WM8961_OUT1VU_MASK 0x0100 /* OUT1VU */
+#define WM8961_OUT1VU_SHIFT 8 /* OUT1VU */
+#define WM8961_OUT1VU_WIDTH 1 /* OUT1VU */
+#define WM8961_RO1ZC 0x0080 /* RO1ZC */
+#define WM8961_RO1ZC_MASK 0x0080 /* RO1ZC */
+#define WM8961_RO1ZC_SHIFT 7 /* RO1ZC */
+#define WM8961_RO1ZC_WIDTH 1 /* RO1ZC */
+#define WM8961_ROUT1VOL_MASK 0x007F /* ROUT1VOL - [6:0] */
+#define WM8961_ROUT1VOL_SHIFT 0 /* ROUT1VOL - [6:0] */
+#define WM8961_ROUT1VOL_WIDTH 7 /* ROUT1VOL - [6:0] */
+
+/*
+ * R4 (0x04) - Clocking1
+ */
+#define WM8961_ADCDIV_MASK 0x01C0 /* ADCDIV - [8:6] */
+#define WM8961_ADCDIV_SHIFT 6 /* ADCDIV - [8:6] */
+#define WM8961_ADCDIV_WIDTH 3 /* ADCDIV - [8:6] */
+#define WM8961_DACDIV_MASK 0x0038 /* DACDIV - [5:3] */
+#define WM8961_DACDIV_SHIFT 3 /* DACDIV - [5:3] */
+#define WM8961_DACDIV_WIDTH 3 /* DACDIV - [5:3] */
+#define WM8961_MCLKDIV 0x0004 /* MCLKDIV */
+#define WM8961_MCLKDIV_MASK 0x0004 /* MCLKDIV */
+#define WM8961_MCLKDIV_SHIFT 2 /* MCLKDIV */
+#define WM8961_MCLKDIV_WIDTH 1 /* MCLKDIV */
+
+/*
+ * R5 (0x05) - ADC & DAC Control 1
+ */
+#define WM8961_ADCPOL_MASK 0x0060 /* ADCPOL - [6:5] */
+#define WM8961_ADCPOL_SHIFT 5 /* ADCPOL - [6:5] */
+#define WM8961_ADCPOL_WIDTH 2 /* ADCPOL - [6:5] */
+#define WM8961_DACMU 0x0008 /* DACMU */
+#define WM8961_DACMU_MASK 0x0008 /* DACMU */
+#define WM8961_DACMU_SHIFT 3 /* DACMU */
+#define WM8961_DACMU_WIDTH 1 /* DACMU */
+#define WM8961_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
+#define WM8961_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
+#define WM8961_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
+#define WM8961_ADCHPD 0x0001 /* ADCHPD */
+#define WM8961_ADCHPD_MASK 0x0001 /* ADCHPD */
+#define WM8961_ADCHPD_SHIFT 0 /* ADCHPD */
+#define WM8961_ADCHPD_WIDTH 1 /* ADCHPD */
+
+/*
+ * R6 (0x06) - ADC & DAC Control 2
+ */
+#define WM8961_ADC_HPF_CUT_MASK 0x0180 /* ADC_HPF_CUT - [8:7] */
+#define WM8961_ADC_HPF_CUT_SHIFT 7 /* ADC_HPF_CUT - [8:7] */
+#define WM8961_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [8:7] */
+#define WM8961_DACPOL_MASK 0x0060 /* DACPOL - [6:5] */
+#define WM8961_DACPOL_SHIFT 5 /* DACPOL - [6:5] */
+#define WM8961_DACPOL_WIDTH 2 /* DACPOL - [6:5] */
+#define WM8961_DACSMM 0x0008 /* DACSMM */
+#define WM8961_DACSMM_MASK 0x0008 /* DACSMM */
+#define WM8961_DACSMM_SHIFT 3 /* DACSMM */
+#define WM8961_DACSMM_WIDTH 1 /* DACSMM */
+#define WM8961_DACMR 0x0004 /* DACMR */
+#define WM8961_DACMR_MASK 0x0004 /* DACMR */
+#define WM8961_DACMR_SHIFT 2 /* DACMR */
+#define WM8961_DACMR_WIDTH 1 /* DACMR */
+#define WM8961_DACSLOPE 0x0002 /* DACSLOPE */
+#define WM8961_DACSLOPE_MASK 0x0002 /* DACSLOPE */
+#define WM8961_DACSLOPE_SHIFT 1 /* DACSLOPE */
+#define WM8961_DACSLOPE_WIDTH 1 /* DACSLOPE */
+#define WM8961_DAC_OSR128 0x0001 /* DAC_OSR128 */
+#define WM8961_DAC_OSR128_MASK 0x0001 /* DAC_OSR128 */
+#define WM8961_DAC_OSR128_SHIFT 0 /* DAC_OSR128 */
+#define WM8961_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */
+
+/*
+ * R7 (0x07) - Audio Interface 0
+ */
+#define WM8961_ALRSWAP 0x0100 /* ALRSWAP */
+#define WM8961_ALRSWAP_MASK 0x0100 /* ALRSWAP */
+#define WM8961_ALRSWAP_SHIFT 8 /* ALRSWAP */
+#define WM8961_ALRSWAP_WIDTH 1 /* ALRSWAP */
+#define WM8961_BCLKINV 0x0080 /* BCLKINV */
+#define WM8961_BCLKINV_MASK 0x0080 /* BCLKINV */
+#define WM8961_BCLKINV_SHIFT 7 /* BCLKINV */
+#define WM8961_BCLKINV_WIDTH 1 /* BCLKINV */
+#define WM8961_MS 0x0040 /* MS */
+#define WM8961_MS_MASK 0x0040 /* MS */
+#define WM8961_MS_SHIFT 6 /* MS */
+#define WM8961_MS_WIDTH 1 /* MS */
+#define WM8961_DLRSWAP 0x0020 /* DLRSWAP */
+#define WM8961_DLRSWAP_MASK 0x0020 /* DLRSWAP */
+#define WM8961_DLRSWAP_SHIFT 5 /* DLRSWAP */
+#define WM8961_DLRSWAP_WIDTH 1 /* DLRSWAP */
+#define WM8961_LRP 0x0010 /* LRP */
+#define WM8961_LRP_MASK 0x0010 /* LRP */
+#define WM8961_LRP_SHIFT 4 /* LRP */
+#define WM8961_LRP_WIDTH 1 /* LRP */
+#define WM8961_WL_MASK 0x000C /* WL - [3:2] */
+#define WM8961_WL_SHIFT 2 /* WL - [3:2] */
+#define WM8961_WL_WIDTH 2 /* WL - [3:2] */
+#define WM8961_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */
+#define WM8961_FORMAT_SHIFT 0 /* FORMAT - [1:0] */
+#define WM8961_FORMAT_WIDTH 2 /* FORMAT - [1:0] */
+
+/*
+ * R8 (0x08) - Clocking2
+ */
+#define WM8961_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */
+#define WM8961_DCLKDIV_SHIFT 6 /* DCLKDIV - [8:6] */
+#define WM8961_DCLKDIV_WIDTH 3 /* DCLKDIV - [8:6] */
+#define WM8961_CLK_SYS_ENA 0x0020 /* CLK_SYS_ENA */
+#define WM8961_CLK_SYS_ENA_MASK 0x0020 /* CLK_SYS_ENA */
+#define WM8961_CLK_SYS_ENA_SHIFT 5 /* CLK_SYS_ENA */
+#define WM8961_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */
+#define WM8961_CLK_DSP_ENA 0x0010 /* CLK_DSP_ENA */
+#define WM8961_CLK_DSP_ENA_MASK 0x0010 /* CLK_DSP_ENA */
+#define WM8961_CLK_DSP_ENA_SHIFT 4 /* CLK_DSP_ENA */
+#define WM8961_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */
+#define WM8961_BCLKDIV_MASK 0x000F /* BCLKDIV - [3:0] */
+#define WM8961_BCLKDIV_SHIFT 0 /* BCLKDIV - [3:0] */
+#define WM8961_BCLKDIV_WIDTH 4 /* BCLKDIV - [3:0] */
+
+/*
+ * R9 (0x09) - Audio Interface 1
+ */
+#define WM8961_DACCOMP_MASK 0x0018 /* DACCOMP - [4:3] */
+#define WM8961_DACCOMP_SHIFT 3 /* DACCOMP - [4:3] */
+#define WM8961_DACCOMP_WIDTH 2 /* DACCOMP - [4:3] */
+#define WM8961_ADCCOMP_MASK 0x0006 /* ADCCOMP - [2:1] */
+#define WM8961_ADCCOMP_SHIFT 1 /* ADCCOMP - [2:1] */
+#define WM8961_ADCCOMP_WIDTH 2 /* ADCCOMP - [2:1] */
+#define WM8961_LOOPBACK 0x0001 /* LOOPBACK */
+#define WM8961_LOOPBACK_MASK 0x0001 /* LOOPBACK */
+#define WM8961_LOOPBACK_SHIFT 0 /* LOOPBACK */
+#define WM8961_LOOPBACK_WIDTH 1 /* LOOPBACK */
+
+/*
+ * R10 (0x0A) - Left DAC volume
+ */
+#define WM8961_DACVU 0x0100 /* DACVU */
+#define WM8961_DACVU_MASK 0x0100 /* DACVU */
+#define WM8961_DACVU_SHIFT 8 /* DACVU */
+#define WM8961_DACVU_WIDTH 1 /* DACVU */
+#define WM8961_LDACVOL_MASK 0x00FF /* LDACVOL - [7:0] */
+#define WM8961_LDACVOL_SHIFT 0 /* LDACVOL - [7:0] */
+#define WM8961_LDACVOL_WIDTH 8 /* LDACVOL - [7:0] */
+
+/*
+ * R11 (0x0B) - Right DAC volume
+ */
+#define WM8961_DACVU 0x0100 /* DACVU */
+#define WM8961_DACVU_MASK 0x0100 /* DACVU */
+#define WM8961_DACVU_SHIFT 8 /* DACVU */
+#define WM8961_DACVU_WIDTH 1 /* DACVU */
+#define WM8961_RDACVOL_MASK 0x00FF /* RDACVOL - [7:0] */
+#define WM8961_RDACVOL_SHIFT 0 /* RDACVOL - [7:0] */
+#define WM8961_RDACVOL_WIDTH 8 /* RDACVOL - [7:0] */
+
+/*
+ * R14 (0x0E) - Audio Interface 2
+ */
+#define WM8961_LRCLK_RATE_MASK 0x01FF /* LRCLK_RATE - [8:0] */
+#define WM8961_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [8:0] */
+#define WM8961_LRCLK_RATE_WIDTH 9 /* LRCLK_RATE - [8:0] */
+
+/*
+ * R15 (0x0F) - Software Reset
+ */
+#define WM8961_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */
+#define WM8961_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */
+#define WM8961_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */
+
+/*
+ * R17 (0x11) - ALC1
+ */
+#define WM8961_ALCSEL_MASK 0x0180 /* ALCSEL - [8:7] */
+#define WM8961_ALCSEL_SHIFT 7 /* ALCSEL - [8:7] */
+#define WM8961_ALCSEL_WIDTH 2 /* ALCSEL - [8:7] */
+#define WM8961_MAXGAIN_MASK 0x0070 /* MAXGAIN - [6:4] */
+#define WM8961_MAXGAIN_SHIFT 4 /* MAXGAIN - [6:4] */
+#define WM8961_MAXGAIN_WIDTH 3 /* MAXGAIN - [6:4] */
+#define WM8961_ALCL_MASK 0x000F /* ALCL - [3:0] */
+#define WM8961_ALCL_SHIFT 0 /* ALCL - [3:0] */
+#define WM8961_ALCL_WIDTH 4 /* ALCL - [3:0] */
+
+/*
+ * R18 (0x12) - ALC2
+ */
+#define WM8961_ALCZC 0x0080 /* ALCZC */
+#define WM8961_ALCZC_MASK 0x0080 /* ALCZC */
+#define WM8961_ALCZC_SHIFT 7 /* ALCZC */
+#define WM8961_ALCZC_WIDTH 1 /* ALCZC */
+#define WM8961_MINGAIN_MASK 0x0070 /* MINGAIN - [6:4] */
+#define WM8961_MINGAIN_SHIFT 4 /* MINGAIN - [6:4] */
+#define WM8961_MINGAIN_WIDTH 3 /* MINGAIN - [6:4] */
+#define WM8961_HLD_MASK 0x000F /* HLD - [3:0] */
+#define WM8961_HLD_SHIFT 0 /* HLD - [3:0] */
+#define WM8961_HLD_WIDTH 4 /* HLD - [3:0] */
+
+/*
+ * R19 (0x13) - ALC3
+ */
+#define WM8961_ALCMODE 0x0100 /* ALCMODE */
+#define WM8961_ALCMODE_MASK 0x0100 /* ALCMODE */
+#define WM8961_ALCMODE_SHIFT 8 /* ALCMODE */
+#define WM8961_ALCMODE_WIDTH 1 /* ALCMODE */
+#define WM8961_DCY_MASK 0x00F0 /* DCY - [7:4] */
+#define WM8961_DCY_SHIFT 4 /* DCY - [7:4] */
+#define WM8961_DCY_WIDTH 4 /* DCY - [7:4] */
+#define WM8961_ATK_MASK 0x000F /* ATK - [3:0] */
+#define WM8961_ATK_SHIFT 0 /* ATK - [3:0] */
+#define WM8961_ATK_WIDTH 4 /* ATK - [3:0] */
+
+/*
+ * R20 (0x14) - Noise Gate
+ */
+#define WM8961_NGTH_MASK 0x00F8 /* NGTH - [7:3] */
+#define WM8961_NGTH_SHIFT 3 /* NGTH - [7:3] */
+#define WM8961_NGTH_WIDTH 5 /* NGTH - [7:3] */
+#define WM8961_NGG 0x0002 /* NGG */
+#define WM8961_NGG_MASK 0x0002 /* NGG */
+#define WM8961_NGG_SHIFT 1 /* NGG */
+#define WM8961_NGG_WIDTH 1 /* NGG */
+#define WM8961_NGAT 0x0001 /* NGAT */
+#define WM8961_NGAT_MASK 0x0001 /* NGAT */
+#define WM8961_NGAT_SHIFT 0 /* NGAT */
+#define WM8961_NGAT_WIDTH 1 /* NGAT */
+
+/*
+ * R21 (0x15) - Left ADC volume
+ */
+#define WM8961_ADCVU 0x0100 /* ADCVU */
+#define WM8961_ADCVU_MASK 0x0100 /* ADCVU */
+#define WM8961_ADCVU_SHIFT 8 /* ADCVU */
+#define WM8961_ADCVU_WIDTH 1 /* ADCVU */
+#define WM8961_LADCVOL_MASK 0x00FF /* LADCVOL - [7:0] */
+#define WM8961_LADCVOL_SHIFT 0 /* LADCVOL - [7:0] */
+#define WM8961_LADCVOL_WIDTH 8 /* LADCVOL - [7:0] */
+
+/*
+ * R22 (0x16) - Right ADC volume
+ */
+#define WM8961_ADCVU 0x0100 /* ADCVU */
+#define WM8961_ADCVU_MASK 0x0100 /* ADCVU */
+#define WM8961_ADCVU_SHIFT 8 /* ADCVU */
+#define WM8961_ADCVU_WIDTH 1 /* ADCVU */
+#define WM8961_RADCVOL_MASK 0x00FF /* RADCVOL - [7:0] */
+#define WM8961_RADCVOL_SHIFT 0 /* RADCVOL - [7:0] */
+#define WM8961_RADCVOL_WIDTH 8 /* RADCVOL - [7:0] */
+
+/*
+ * R23 (0x17) - Additional control(1)
+ */
+#define WM8961_TSDEN 0x0100 /* TSDEN */
+#define WM8961_TSDEN_MASK 0x0100 /* TSDEN */
+#define WM8961_TSDEN_SHIFT 8 /* TSDEN */
+#define WM8961_TSDEN_WIDTH 1 /* TSDEN */
+#define WM8961_DMONOMIX 0x0010 /* DMONOMIX */
+#define WM8961_DMONOMIX_MASK 0x0010 /* DMONOMIX */
+#define WM8961_DMONOMIX_SHIFT 4 /* DMONOMIX */
+#define WM8961_DMONOMIX_WIDTH 1 /* DMONOMIX */
+#define WM8961_TOEN 0x0001 /* TOEN */
+#define WM8961_TOEN_MASK 0x0001 /* TOEN */
+#define WM8961_TOEN_SHIFT 0 /* TOEN */
+#define WM8961_TOEN_WIDTH 1 /* TOEN */
+
+/*
+ * R24 (0x18) - Additional control(2)
+ */
+#define WM8961_TRIS 0x0008 /* TRIS */
+#define WM8961_TRIS_MASK 0x0008 /* TRIS */
+#define WM8961_TRIS_SHIFT 3 /* TRIS */
+#define WM8961_TRIS_WIDTH 1 /* TRIS */
+
+/*
+ * R25 (0x19) - Pwr Mgmt (1)
+ */
+#define WM8961_VMIDSEL_MASK 0x0180 /* VMIDSEL - [8:7] */
+#define WM8961_VMIDSEL_SHIFT 7 /* VMIDSEL - [8:7] */
+#define WM8961_VMIDSEL_WIDTH 2 /* VMIDSEL - [8:7] */
+#define WM8961_VREF 0x0040 /* VREF */
+#define WM8961_VREF_MASK 0x0040 /* VREF */
+#define WM8961_VREF_SHIFT 6 /* VREF */
+#define WM8961_VREF_WIDTH 1 /* VREF */
+#define WM8961_AINL 0x0020 /* AINL */
+#define WM8961_AINL_MASK 0x0020 /* AINL */
+#define WM8961_AINL_SHIFT 5 /* AINL */
+#define WM8961_AINL_WIDTH 1 /* AINL */
+#define WM8961_AINR 0x0010 /* AINR */
+#define WM8961_AINR_MASK 0x0010 /* AINR */
+#define WM8961_AINR_SHIFT 4 /* AINR */
+#define WM8961_AINR_WIDTH 1 /* AINR */
+#define WM8961_ADCL 0x0008 /* ADCL */
+#define WM8961_ADCL_MASK 0x0008 /* ADCL */
+#define WM8961_ADCL_SHIFT 3 /* ADCL */
+#define WM8961_ADCL_WIDTH 1 /* ADCL */
+#define WM8961_ADCR 0x0004 /* ADCR */
+#define WM8961_ADCR_MASK 0x0004 /* ADCR */
+#define WM8961_ADCR_SHIFT 2 /* ADCR */
+#define WM8961_ADCR_WIDTH 1 /* ADCR */
+#define WM8961_MICB 0x0002 /* MICB */
+#define WM8961_MICB_MASK 0x0002 /* MICB */
+#define WM8961_MICB_SHIFT 1 /* MICB */
+#define WM8961_MICB_WIDTH 1 /* MICB */
+
+/*
+ * R26 (0x1A) - Pwr Mgmt (2)
+ */
+#define WM8961_DACL 0x0100 /* DACL */
+#define WM8961_DACL_MASK 0x0100 /* DACL */
+#define WM8961_DACL_SHIFT 8 /* DACL */
+#define WM8961_DACL_WIDTH 1 /* DACL */
+#define WM8961_DACR 0x0080 /* DACR */
+#define WM8961_DACR_MASK 0x0080 /* DACR */
+#define WM8961_DACR_SHIFT 7 /* DACR */
+#define WM8961_DACR_WIDTH 1 /* DACR */
+#define WM8961_LOUT1_PGA 0x0040 /* LOUT1_PGA */
+#define WM8961_LOUT1_PGA_MASK 0x0040 /* LOUT1_PGA */
+#define WM8961_LOUT1_PGA_SHIFT 6 /* LOUT1_PGA */
+#define WM8961_LOUT1_PGA_WIDTH 1 /* LOUT1_PGA */
+#define WM8961_ROUT1_PGA 0x0020 /* ROUT1_PGA */
+#define WM8961_ROUT1_PGA_MASK 0x0020 /* ROUT1_PGA */
+#define WM8961_ROUT1_PGA_SHIFT 5 /* ROUT1_PGA */
+#define WM8961_ROUT1_PGA_WIDTH 1 /* ROUT1_PGA */
+#define WM8961_SPKL_PGA 0x0010 /* SPKL_PGA */
+#define WM8961_SPKL_PGA_MASK 0x0010 /* SPKL_PGA */
+#define WM8961_SPKL_PGA_SHIFT 4 /* SPKL_PGA */
+#define WM8961_SPKL_PGA_WIDTH 1 /* SPKL_PGA */
+#define WM8961_SPKR_PGA 0x0008 /* SPKR_PGA */
+#define WM8961_SPKR_PGA_MASK 0x0008 /* SPKR_PGA */
+#define WM8961_SPKR_PGA_SHIFT 3 /* SPKR_PGA */
+#define WM8961_SPKR_PGA_WIDTH 1 /* SPKR_PGA */
+
+/*
+ * R27 (0x1B) - Additional Control (3)
+ */
+#define WM8961_SAMPLE_RATE_MASK 0x0007 /* SAMPLE_RATE - [2:0] */
+#define WM8961_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [2:0] */
+#define WM8961_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [2:0] */
+
+/*
+ * R28 (0x1C) - Anti-pop
+ */
+#define WM8961_BUFDCOPEN 0x0010 /* BUFDCOPEN */
+#define WM8961_BUFDCOPEN_MASK 0x0010 /* BUFDCOPEN */
+#define WM8961_BUFDCOPEN_SHIFT 4 /* BUFDCOPEN */
+#define WM8961_BUFDCOPEN_WIDTH 1 /* BUFDCOPEN */
+#define WM8961_BUFIOEN 0x0008 /* BUFIOEN */
+#define WM8961_BUFIOEN_MASK 0x0008 /* BUFIOEN */
+#define WM8961_BUFIOEN_SHIFT 3 /* BUFIOEN */
+#define WM8961_BUFIOEN_WIDTH 1 /* BUFIOEN */
+#define WM8961_SOFT_ST 0x0004 /* SOFT_ST */
+#define WM8961_SOFT_ST_MASK 0x0004 /* SOFT_ST */
+#define WM8961_SOFT_ST_SHIFT 2 /* SOFT_ST */
+#define WM8961_SOFT_ST_WIDTH 1 /* SOFT_ST */
+
+/*
+ * R30 (0x1E) - Clocking 3
+ */
+#define WM8961_CLK_TO_DIV_MASK 0x0180 /* CLK_TO_DIV - [8:7] */
+#define WM8961_CLK_TO_DIV_SHIFT 7 /* CLK_TO_DIV - [8:7] */
+#define WM8961_CLK_TO_DIV_WIDTH 2 /* CLK_TO_DIV - [8:7] */
+#define WM8961_CLK_256K_DIV_MASK 0x007E /* CLK_256K_DIV - [6:1] */
+#define WM8961_CLK_256K_DIV_SHIFT 1 /* CLK_256K_DIV - [6:1] */
+#define WM8961_CLK_256K_DIV_WIDTH 6 /* CLK_256K_DIV - [6:1] */
+#define WM8961_MANUAL_MODE 0x0001 /* MANUAL_MODE */
+#define WM8961_MANUAL_MODE_MASK 0x0001 /* MANUAL_MODE */
+#define WM8961_MANUAL_MODE_SHIFT 0 /* MANUAL_MODE */
+#define WM8961_MANUAL_MODE_WIDTH 1 /* MANUAL_MODE */
+
+/*
+ * R32 (0x20) - ADCL signal path
+ */
+#define WM8961_LMICBOOST_MASK 0x0030 /* LMICBOOST - [5:4] */
+#define WM8961_LMICBOOST_SHIFT 4 /* LMICBOOST - [5:4] */
+#define WM8961_LMICBOOST_WIDTH 2 /* LMICBOOST - [5:4] */
+
+/*
+ * R33 (0x21) - ADCR signal path
+ */
+#define WM8961_RMICBOOST_MASK 0x0030 /* RMICBOOST - [5:4] */
+#define WM8961_RMICBOOST_SHIFT 4 /* RMICBOOST - [5:4] */
+#define WM8961_RMICBOOST_WIDTH 2 /* RMICBOOST - [5:4] */
+
+/*
+ * R40 (0x28) - LOUT2 volume
+ */
+#define WM8961_SPKVU 0x0100 /* SPKVU */
+#define WM8961_SPKVU_MASK 0x0100 /* SPKVU */
+#define WM8961_SPKVU_SHIFT 8 /* SPKVU */
+#define WM8961_SPKVU_WIDTH 1 /* SPKVU */
+#define WM8961_SPKLZC 0x0080 /* SPKLZC */
+#define WM8961_SPKLZC_MASK 0x0080 /* SPKLZC */
+#define WM8961_SPKLZC_SHIFT 7 /* SPKLZC */
+#define WM8961_SPKLZC_WIDTH 1 /* SPKLZC */
+#define WM8961_SPKLVOL_MASK 0x007F /* SPKLVOL - [6:0] */
+#define WM8961_SPKLVOL_SHIFT 0 /* SPKLVOL - [6:0] */
+#define WM8961_SPKLVOL_WIDTH 7 /* SPKLVOL - [6:0] */
+
+/*
+ * R41 (0x29) - ROUT2 volume
+ */
+#define WM8961_SPKVU 0x0100 /* SPKVU */
+#define WM8961_SPKVU_MASK 0x0100 /* SPKVU */
+#define WM8961_SPKVU_SHIFT 8 /* SPKVU */
+#define WM8961_SPKVU_WIDTH 1 /* SPKVU */
+#define WM8961_SPKRZC 0x0080 /* SPKRZC */
+#define WM8961_SPKRZC_MASK 0x0080 /* SPKRZC */
+#define WM8961_SPKRZC_SHIFT 7 /* SPKRZC */
+#define WM8961_SPKRZC_WIDTH 1 /* SPKRZC */
+#define WM8961_SPKRVOL_MASK 0x007F /* SPKRVOL - [6:0] */
+#define WM8961_SPKRVOL_SHIFT 0 /* SPKRVOL - [6:0] */
+#define WM8961_SPKRVOL_WIDTH 7 /* SPKRVOL - [6:0] */
+
+/*
+ * R47 (0x2F) - Pwr Mgmt (3)
+ */
+#define WM8961_TEMP_SHUT 0x0002 /* TEMP_SHUT */
+#define WM8961_TEMP_SHUT_MASK 0x0002 /* TEMP_SHUT */
+#define WM8961_TEMP_SHUT_SHIFT 1 /* TEMP_SHUT */
+#define WM8961_TEMP_SHUT_WIDTH 1 /* TEMP_SHUT */
+#define WM8961_TEMP_WARN 0x0001 /* TEMP_WARN */
+#define WM8961_TEMP_WARN_MASK 0x0001 /* TEMP_WARN */
+#define WM8961_TEMP_WARN_SHIFT 0 /* TEMP_WARN */
+#define WM8961_TEMP_WARN_WIDTH 1 /* TEMP_WARN */
+
+/*
+ * R48 (0x30) - Additional Control (4)
+ */
+#define WM8961_TSENSEN 0x0002 /* TSENSEN */
+#define WM8961_TSENSEN_MASK 0x0002 /* TSENSEN */
+#define WM8961_TSENSEN_SHIFT 1 /* TSENSEN */
+#define WM8961_TSENSEN_WIDTH 1 /* TSENSEN */
+#define WM8961_MBSEL 0x0001 /* MBSEL */
+#define WM8961_MBSEL_MASK 0x0001 /* MBSEL */
+#define WM8961_MBSEL_SHIFT 0 /* MBSEL */
+#define WM8961_MBSEL_WIDTH 1 /* MBSEL */
+
+/*
+ * R49 (0x31) - Class D Control 1
+ */
+#define WM8961_SPKR_ENA 0x0080 /* SPKR_ENA */
+#define WM8961_SPKR_ENA_MASK 0x0080 /* SPKR_ENA */
+#define WM8961_SPKR_ENA_SHIFT 7 /* SPKR_ENA */
+#define WM8961_SPKR_ENA_WIDTH 1 /* SPKR_ENA */
+#define WM8961_SPKL_ENA 0x0040 /* SPKL_ENA */
+#define WM8961_SPKL_ENA_MASK 0x0040 /* SPKL_ENA */
+#define WM8961_SPKL_ENA_SHIFT 6 /* SPKL_ENA */
+#define WM8961_SPKL_ENA_WIDTH 1 /* SPKL_ENA */
+
+/*
+ * R51 (0x33) - Class D Control 2
+ */
+#define WM8961_CLASSD_ACGAIN_MASK 0x0007 /* CLASSD_ACGAIN - [2:0] */
+#define WM8961_CLASSD_ACGAIN_SHIFT 0 /* CLASSD_ACGAIN - [2:0] */
+#define WM8961_CLASSD_ACGAIN_WIDTH 3 /* CLASSD_ACGAIN - [2:0] */
+
+/*
+ * R56 (0x38) - Clocking 4
+ */
+#define WM8961_CLK_DCS_DIV_MASK 0x01E0 /* CLK_DCS_DIV - [8:5] */
+#define WM8961_CLK_DCS_DIV_SHIFT 5 /* CLK_DCS_DIV - [8:5] */
+#define WM8961_CLK_DCS_DIV_WIDTH 4 /* CLK_DCS_DIV - [8:5] */
+#define WM8961_CLK_SYS_RATE_MASK 0x001E /* CLK_SYS_RATE - [4:1] */
+#define WM8961_CLK_SYS_RATE_SHIFT 1 /* CLK_SYS_RATE - [4:1] */
+#define WM8961_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [4:1] */
+
+/*
+ * R57 (0x39) - DSP Sidetone 0
+ */
+#define WM8961_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8961_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8961_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8961_ADC_TO_DACR_MASK 0x000C /* ADC_TO_DACR - [3:2] */
+#define WM8961_ADC_TO_DACR_SHIFT 2 /* ADC_TO_DACR - [3:2] */
+#define WM8961_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [3:2] */
+
+/*
+ * R58 (0x3A) - DSP Sidetone 1
+ */
+#define WM8961_ADCL_DAC_SVOL_MASK 0x00F0 /* ADCL_DAC_SVOL - [7:4] */
+#define WM8961_ADCL_DAC_SVOL_SHIFT 4 /* ADCL_DAC_SVOL - [7:4] */
+#define WM8961_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [7:4] */
+#define WM8961_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */
+#define WM8961_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */
+#define WM8961_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */
+
+/*
+ * R60 (0x3C) - DC Servo 0
+ */
+#define WM8961_DCS_ENA_CHAN_INL 0x0080 /* DCS_ENA_CHAN_INL */
+#define WM8961_DCS_ENA_CHAN_INL_MASK 0x0080 /* DCS_ENA_CHAN_INL */
+#define WM8961_DCS_ENA_CHAN_INL_SHIFT 7 /* DCS_ENA_CHAN_INL */
+#define WM8961_DCS_ENA_CHAN_INL_WIDTH 1 /* DCS_ENA_CHAN_INL */
+#define WM8961_DCS_TRIG_STARTUP_INL 0x0040 /* DCS_TRIG_STARTUP_INL */
+#define WM8961_DCS_TRIG_STARTUP_INL_MASK 0x0040 /* DCS_TRIG_STARTUP_INL */
+#define WM8961_DCS_TRIG_STARTUP_INL_SHIFT 6 /* DCS_TRIG_STARTUP_INL */
+#define WM8961_DCS_TRIG_STARTUP_INL_WIDTH 1 /* DCS_TRIG_STARTUP_INL */
+#define WM8961_DCS_TRIG_SERIES_INL 0x0010 /* DCS_TRIG_SERIES_INL */
+#define WM8961_DCS_TRIG_SERIES_INL_MASK 0x0010 /* DCS_TRIG_SERIES_INL */
+#define WM8961_DCS_TRIG_SERIES_INL_SHIFT 4 /* DCS_TRIG_SERIES_INL */
+#define WM8961_DCS_TRIG_SERIES_INL_WIDTH 1 /* DCS_TRIG_SERIES_INL */
+#define WM8961_DCS_ENA_CHAN_INR 0x0008 /* DCS_ENA_CHAN_INR */
+#define WM8961_DCS_ENA_CHAN_INR_MASK 0x0008 /* DCS_ENA_CHAN_INR */
+#define WM8961_DCS_ENA_CHAN_INR_SHIFT 3 /* DCS_ENA_CHAN_INR */
+#define WM8961_DCS_ENA_CHAN_INR_WIDTH 1 /* DCS_ENA_CHAN_INR */
+#define WM8961_DCS_TRIG_STARTUP_INR 0x0004 /* DCS_TRIG_STARTUP_INR */
+#define WM8961_DCS_TRIG_STARTUP_INR_MASK 0x0004 /* DCS_TRIG_STARTUP_INR */
+#define WM8961_DCS_TRIG_STARTUP_INR_SHIFT 2 /* DCS_TRIG_STARTUP_INR */
+#define WM8961_DCS_TRIG_STARTUP_INR_WIDTH 1 /* DCS_TRIG_STARTUP_INR */
+#define WM8961_DCS_TRIG_SERIES_INR 0x0001 /* DCS_TRIG_SERIES_INR */
+#define WM8961_DCS_TRIG_SERIES_INR_MASK 0x0001 /* DCS_TRIG_SERIES_INR */
+#define WM8961_DCS_TRIG_SERIES_INR_SHIFT 0 /* DCS_TRIG_SERIES_INR */
+#define WM8961_DCS_TRIG_SERIES_INR_WIDTH 1 /* DCS_TRIG_SERIES_INR */
+
+/*
+ * R61 (0x3D) - DC Servo 1
+ */
+#define WM8961_DCS_ENA_CHAN_HPL 0x0080 /* DCS_ENA_CHAN_HPL */
+#define WM8961_DCS_ENA_CHAN_HPL_MASK 0x0080 /* DCS_ENA_CHAN_HPL */
+#define WM8961_DCS_ENA_CHAN_HPL_SHIFT 7 /* DCS_ENA_CHAN_HPL */
+#define WM8961_DCS_ENA_CHAN_HPL_WIDTH 1 /* DCS_ENA_CHAN_HPL */
+#define WM8961_DCS_TRIG_STARTUP_HPL 0x0040 /* DCS_TRIG_STARTUP_HPL */
+#define WM8961_DCS_TRIG_STARTUP_HPL_MASK 0x0040 /* DCS_TRIG_STARTUP_HPL */
+#define WM8961_DCS_TRIG_STARTUP_HPL_SHIFT 6 /* DCS_TRIG_STARTUP_HPL */
+#define WM8961_DCS_TRIG_STARTUP_HPL_WIDTH 1 /* DCS_TRIG_STARTUP_HPL */
+#define WM8961_DCS_TRIG_SERIES_HPL 0x0010 /* DCS_TRIG_SERIES_HPL */
+#define WM8961_DCS_TRIG_SERIES_HPL_MASK 0x0010 /* DCS_TRIG_SERIES_HPL */
+#define WM8961_DCS_TRIG_SERIES_HPL_SHIFT 4 /* DCS_TRIG_SERIES_HPL */
+#define WM8961_DCS_TRIG_SERIES_HPL_WIDTH 1 /* DCS_TRIG_SERIES_HPL */
+#define WM8961_DCS_ENA_CHAN_HPR 0x0008 /* DCS_ENA_CHAN_HPR */
+#define WM8961_DCS_ENA_CHAN_HPR_MASK 0x0008 /* DCS_ENA_CHAN_HPR */
+#define WM8961_DCS_ENA_CHAN_HPR_SHIFT 3 /* DCS_ENA_CHAN_HPR */
+#define WM8961_DCS_ENA_CHAN_HPR_WIDTH 1 /* DCS_ENA_CHAN_HPR */
+#define WM8961_DCS_TRIG_STARTUP_HPR 0x0004 /* DCS_TRIG_STARTUP_HPR */
+#define WM8961_DCS_TRIG_STARTUP_HPR_MASK 0x0004 /* DCS_TRIG_STARTUP_HPR */
+#define WM8961_DCS_TRIG_STARTUP_HPR_SHIFT 2 /* DCS_TRIG_STARTUP_HPR */
+#define WM8961_DCS_TRIG_STARTUP_HPR_WIDTH 1 /* DCS_TRIG_STARTUP_HPR */
+#define WM8961_DCS_TRIG_SERIES_HPR 0x0001 /* DCS_TRIG_SERIES_HPR */
+#define WM8961_DCS_TRIG_SERIES_HPR_MASK 0x0001 /* DCS_TRIG_SERIES_HPR */
+#define WM8961_DCS_TRIG_SERIES_HPR_SHIFT 0 /* DCS_TRIG_SERIES_HPR */
+#define WM8961_DCS_TRIG_SERIES_HPR_WIDTH 1 /* DCS_TRIG_SERIES_HPR */
+
+/*
+ * R63 (0x3F) - DC Servo 3
+ */
+#define WM8961_DCS_FILT_BW_SERIES_MASK 0x0030 /* DCS_FILT_BW_SERIES - [5:4] */
+#define WM8961_DCS_FILT_BW_SERIES_SHIFT 4 /* DCS_FILT_BW_SERIES - [5:4] */
+#define WM8961_DCS_FILT_BW_SERIES_WIDTH 2 /* DCS_FILT_BW_SERIES - [5:4] */
+
+/*
+ * R65 (0x41) - DC Servo 5
+ */
+#define WM8961_DCS_SERIES_NO_HP_MASK 0x007F /* DCS_SERIES_NO_HP - [6:0] */
+#define WM8961_DCS_SERIES_NO_HP_SHIFT 0 /* DCS_SERIES_NO_HP - [6:0] */
+#define WM8961_DCS_SERIES_NO_HP_WIDTH 7 /* DCS_SERIES_NO_HP - [6:0] */
+
+/*
+ * R68 (0x44) - Analogue PGA Bias
+ */
+#define WM8961_HP_PGAS_BIAS_MASK 0x0007 /* HP_PGAS_BIAS - [2:0] */
+#define WM8961_HP_PGAS_BIAS_SHIFT 0 /* HP_PGAS_BIAS - [2:0] */
+#define WM8961_HP_PGAS_BIAS_WIDTH 3 /* HP_PGAS_BIAS - [2:0] */
+
+/*
+ * R69 (0x45) - Analogue HP 0
+ */
+#define WM8961_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */
+#define WM8961_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */
+#define WM8961_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */
+#define WM8961_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */
+#define WM8961_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */
+#define WM8961_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */
+#define WM8961_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */
+#define WM8961_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */
+#define WM8961_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */
+#define WM8961_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */
+#define WM8961_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */
+#define WM8961_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */
+#define WM8961_HPL_ENA 0x0010 /* HPL_ENA */
+#define WM8961_HPL_ENA_MASK 0x0010 /* HPL_ENA */
+#define WM8961_HPL_ENA_SHIFT 4 /* HPL_ENA */
+#define WM8961_HPL_ENA_WIDTH 1 /* HPL_ENA */
+#define WM8961_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */
+#define WM8961_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */
+#define WM8961_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */
+#define WM8961_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */
+#define WM8961_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */
+#define WM8961_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */
+#define WM8961_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */
+#define WM8961_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */
+#define WM8961_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */
+#define WM8961_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */
+#define WM8961_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */
+#define WM8961_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */
+#define WM8961_HPR_ENA 0x0001 /* HPR_ENA */
+#define WM8961_HPR_ENA_MASK 0x0001 /* HPR_ENA */
+#define WM8961_HPR_ENA_SHIFT 0 /* HPR_ENA */
+#define WM8961_HPR_ENA_WIDTH 1 /* HPR_ENA */
+
+/*
+ * R71 (0x47) - Analogue HP 2
+ */
+#define WM8961_HPL_VOL_MASK 0x01C0 /* HPL_VOL - [8:6] */
+#define WM8961_HPL_VOL_SHIFT 6 /* HPL_VOL - [8:6] */
+#define WM8961_HPL_VOL_WIDTH 3 /* HPL_VOL - [8:6] */
+#define WM8961_HPR_VOL_MASK 0x0038 /* HPR_VOL - [5:3] */
+#define WM8961_HPR_VOL_SHIFT 3 /* HPR_VOL - [5:3] */
+#define WM8961_HPR_VOL_WIDTH 3 /* HPR_VOL - [5:3] */
+#define WM8961_HP_BIAS_BOOST_MASK 0x0007 /* HP_BIAS_BOOST - [2:0] */
+#define WM8961_HP_BIAS_BOOST_SHIFT 0 /* HP_BIAS_BOOST - [2:0] */
+#define WM8961_HP_BIAS_BOOST_WIDTH 3 /* HP_BIAS_BOOST - [2:0] */
+
+/*
+ * R72 (0x48) - Charge Pump 1
+ */
+#define WM8961_CP_ENA 0x0001 /* CP_ENA */
+#define WM8961_CP_ENA_MASK 0x0001 /* CP_ENA */
+#define WM8961_CP_ENA_SHIFT 0 /* CP_ENA */
+#define WM8961_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R82 (0x52) - Charge Pump B
+ */
+#define WM8961_CP_DYN_PWR_MASK 0x0003 /* CP_DYN_PWR - [1:0] */
+#define WM8961_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR - [1:0] */
+#define WM8961_CP_DYN_PWR_WIDTH 2 /* CP_DYN_PWR - [1:0] */
+
+/*
+ * R87 (0x57) - Write Sequencer 1
+ */
+#define WM8961_WSEQ_ENA 0x0020 /* WSEQ_ENA */
+#define WM8961_WSEQ_ENA_MASK 0x0020 /* WSEQ_ENA */
+#define WM8961_WSEQ_ENA_SHIFT 5 /* WSEQ_ENA */
+#define WM8961_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM8961_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8961_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8961_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R88 (0x58) - Write Sequencer 2
+ */
+#define WM8961_WSEQ_EOS 0x0100 /* WSEQ_EOS */
+#define WM8961_WSEQ_EOS_MASK 0x0100 /* WSEQ_EOS */
+#define WM8961_WSEQ_EOS_SHIFT 8 /* WSEQ_EOS */
+#define WM8961_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */
+#define WM8961_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */
+#define WM8961_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */
+#define WM8961_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R89 (0x59) - Write Sequencer 3
+ */
+#define WM8961_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */
+#define WM8961_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */
+#define WM8961_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */
+
+/*
+ * R90 (0x5A) - Write Sequencer 4
+ */
+#define WM8961_WSEQ_ABORT 0x0100 /* WSEQ_ABORT */
+#define WM8961_WSEQ_ABORT_MASK 0x0100 /* WSEQ_ABORT */
+#define WM8961_WSEQ_ABORT_SHIFT 8 /* WSEQ_ABORT */
+#define WM8961_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM8961_WSEQ_START 0x0080 /* WSEQ_START */
+#define WM8961_WSEQ_START_MASK 0x0080 /* WSEQ_START */
+#define WM8961_WSEQ_START_SHIFT 7 /* WSEQ_START */
+#define WM8961_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM8961_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */
+#define WM8961_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */
+#define WM8961_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R91 (0x5B) - Write Sequencer 5
+ */
+#define WM8961_WSEQ_DATA_WIDTH_MASK 0x0070 /* WSEQ_DATA_WIDTH - [6:4] */
+#define WM8961_WSEQ_DATA_WIDTH_SHIFT 4 /* WSEQ_DATA_WIDTH - [6:4] */
+#define WM8961_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [6:4] */
+#define WM8961_WSEQ_DATA_START_MASK 0x000F /* WSEQ_DATA_START - [3:0] */
+#define WM8961_WSEQ_DATA_START_SHIFT 0 /* WSEQ_DATA_START - [3:0] */
+#define WM8961_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [3:0] */
+
+/*
+ * R92 (0x5C) - Write Sequencer 6
+ */
+#define WM8961_WSEQ_DELAY_MASK 0x000F /* WSEQ_DELAY - [3:0] */
+#define WM8961_WSEQ_DELAY_SHIFT 0 /* WSEQ_DELAY - [3:0] */
+#define WM8961_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [3:0] */
+
+/*
+ * R93 (0x5D) - Write Sequencer 7
+ */
+#define WM8961_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
+#define WM8961_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
+#define WM8961_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
+#define WM8961_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+
+/*
+ * R252 (0xFC) - General test 1
+ */
+#define WM8961_ARA_ENA 0x0002 /* ARA_ENA */
+#define WM8961_ARA_ENA_MASK 0x0002 /* ARA_ENA */
+#define WM8961_ARA_ENA_SHIFT 1 /* ARA_ENA */
+#define WM8961_ARA_ENA_WIDTH 1 /* ARA_ENA */
+#define WM8961_AUTO_INC 0x0001 /* AUTO_INC */
+#define WM8961_AUTO_INC_MASK 0x0001 /* AUTO_INC */
+#define WM8961_AUTO_INC_SHIFT 0 /* AUTO_INC */
+#define WM8961_AUTO_INC_WIDTH 1 /* AUTO_INC */
+
+#endif
diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c
new file mode 100644
index 00000000..b54ddb75
--- /dev/null
+++ b/sound/soc/codecs/wm8962.c
@@ -0,0 +1,4316 @@
+/*
+ * wm8962.c -- WM8962 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/gcd.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/wm8962.h>
+#include <trace/events/asoc.h>
+
+#include "wm8962.h"
+
+#define WM8962_NUM_SUPPLIES 8
+static const char *wm8962_supply_names[WM8962_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD",
+ "AVDD",
+ "CPVDD",
+ "MICVDD",
+ "PLLVDD",
+ "SPKVDD1",
+ "SPKVDD2",
+};
+
+/* codec private data */
+struct wm8962_priv {
+ struct snd_soc_codec *codec;
+
+ int sysclk;
+ int sysclk_rate;
+
+ int bclk; /* Desired BCLK */
+ int lrclk;
+
+ struct completion fll_lock;
+ int fll_src;
+ int fll_fref;
+ int fll_fout;
+
+ u16 dsp2_ena;
+
+ struct delayed_work mic_work;
+ struct snd_soc_jack *jack;
+
+ struct regulator_bulk_data supplies[WM8962_NUM_SUPPLIES];
+ struct notifier_block disable_nb[WM8962_NUM_SUPPLIES];
+
+#if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE)
+ struct input_dev *beep;
+ struct work_struct beep_work;
+ int beep_rate;
+#endif
+
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gpio_chip;
+#endif
+
+ int irq;
+};
+
+/* We can't use the same notifier block for more than one supply and
+ * there's no way I can see to get from a callback to the caller
+ * except container_of().
+ */
+#define WM8962_REGULATOR_EVENT(n) \
+static int wm8962_regulator_event_##n(struct notifier_block *nb, \
+ unsigned long event, void *data) \
+{ \
+ struct wm8962_priv *wm8962 = container_of(nb, struct wm8962_priv, \
+ disable_nb[n]); \
+ if (event & REGULATOR_EVENT_DISABLE) { \
+ wm8962->codec->cache_sync = 1; \
+ } \
+ return 0; \
+}
+
+WM8962_REGULATOR_EVENT(0)
+WM8962_REGULATOR_EVENT(1)
+WM8962_REGULATOR_EVENT(2)
+WM8962_REGULATOR_EVENT(3)
+WM8962_REGULATOR_EVENT(4)
+WM8962_REGULATOR_EVENT(5)
+WM8962_REGULATOR_EVENT(6)
+WM8962_REGULATOR_EVENT(7)
+
+static const u16 wm8962_reg[WM8962_MAX_REGISTER + 1] = {
+ [0] = 0x009F, /* R0 - Left Input volume */
+ [1] = 0x049F, /* R1 - Right Input volume */
+ [2] = 0x0000, /* R2 - HPOUTL volume */
+ [3] = 0x0000, /* R3 - HPOUTR volume */
+ [4] = 0x0020, /* R4 - Clocking1 */
+ [5] = 0x0018, /* R5 - ADC & DAC Control 1 */
+ [6] = 0x2008, /* R6 - ADC & DAC Control 2 */
+ [7] = 0x000A, /* R7 - Audio Interface 0 */
+ [8] = 0x01E4, /* R8 - Clocking2 */
+ [9] = 0x0300, /* R9 - Audio Interface 1 */
+ [10] = 0x00C0, /* R10 - Left DAC volume */
+ [11] = 0x00C0, /* R11 - Right DAC volume */
+
+ [14] = 0x0040, /* R14 - Audio Interface 2 */
+ [15] = 0x6243, /* R15 - Software Reset */
+
+ [17] = 0x007B, /* R17 - ALC1 */
+ [18] = 0x0000, /* R18 - ALC2 */
+ [19] = 0x1C32, /* R19 - ALC3 */
+ [20] = 0x3200, /* R20 - Noise Gate */
+ [21] = 0x00C0, /* R21 - Left ADC volume */
+ [22] = 0x00C0, /* R22 - Right ADC volume */
+ [23] = 0x0160, /* R23 - Additional control(1) */
+ [24] = 0x0000, /* R24 - Additional control(2) */
+ [25] = 0x0000, /* R25 - Pwr Mgmt (1) */
+ [26] = 0x0000, /* R26 - Pwr Mgmt (2) */
+ [27] = 0x0010, /* R27 - Additional Control (3) */
+ [28] = 0x0000, /* R28 - Anti-pop */
+
+ [30] = 0x005E, /* R30 - Clocking 3 */
+ [31] = 0x0000, /* R31 - Input mixer control (1) */
+ [32] = 0x0145, /* R32 - Left input mixer volume */
+ [33] = 0x0145, /* R33 - Right input mixer volume */
+ [34] = 0x0009, /* R34 - Input mixer control (2) */
+ [35] = 0x0003, /* R35 - Input bias control */
+ [37] = 0x0008, /* R37 - Left input PGA control */
+ [38] = 0x0008, /* R38 - Right input PGA control */
+
+ [40] = 0x0000, /* R40 - SPKOUTL volume */
+ [41] = 0x0000, /* R41 - SPKOUTR volume */
+
+ [47] = 0x0000, /* R47 - Thermal Shutdown Status */
+ [48] = 0x8027, /* R48 - Additional Control (4) */
+ [49] = 0x0010, /* R49 - Class D Control 1 */
+
+ [51] = 0x0003, /* R51 - Class D Control 2 */
+
+ [56] = 0x0506, /* R56 - Clocking 4 */
+ [57] = 0x0000, /* R57 - DAC DSP Mixing (1) */
+ [58] = 0x0000, /* R58 - DAC DSP Mixing (2) */
+
+ [60] = 0x0300, /* R60 - DC Servo 0 */
+ [61] = 0x0300, /* R61 - DC Servo 1 */
+
+ [64] = 0x0810, /* R64 - DC Servo 4 */
+
+ [66] = 0x0000, /* R66 - DC Servo 6 */
+
+ [68] = 0x001B, /* R68 - Analogue PGA Bias */
+ [69] = 0x0000, /* R69 - Analogue HP 0 */
+
+ [71] = 0x01FB, /* R71 - Analogue HP 2 */
+ [72] = 0x0000, /* R72 - Charge Pump 1 */
+
+ [82] = 0x0004, /* R82 - Charge Pump B */
+
+ [87] = 0x0000, /* R87 - Write Sequencer Control 1 */
+
+ [90] = 0x0000, /* R90 - Write Sequencer Control 2 */
+
+ [93] = 0x0000, /* R93 - Write Sequencer Control 3 */
+ [94] = 0x0000, /* R94 - Control Interface */
+
+ [99] = 0x0000, /* R99 - Mixer Enables */
+ [100] = 0x0000, /* R100 - Headphone Mixer (1) */
+ [101] = 0x0000, /* R101 - Headphone Mixer (2) */
+ [102] = 0x013F, /* R102 - Headphone Mixer (3) */
+ [103] = 0x013F, /* R103 - Headphone Mixer (4) */
+
+ [105] = 0x0000, /* R105 - Speaker Mixer (1) */
+ [106] = 0x0000, /* R106 - Speaker Mixer (2) */
+ [107] = 0x013F, /* R107 - Speaker Mixer (3) */
+ [108] = 0x013F, /* R108 - Speaker Mixer (4) */
+ [109] = 0x0003, /* R109 - Speaker Mixer (5) */
+ [110] = 0x0002, /* R110 - Beep Generator (1) */
+
+ [115] = 0x0006, /* R115 - Oscillator Trim (3) */
+ [116] = 0x0026, /* R116 - Oscillator Trim (4) */
+
+ [119] = 0x0000, /* R119 - Oscillator Trim (7) */
+
+ [124] = 0x0011, /* R124 - Analogue Clocking1 */
+ [125] = 0x004B, /* R125 - Analogue Clocking2 */
+ [126] = 0x000D, /* R126 - Analogue Clocking3 */
+ [127] = 0x0000, /* R127 - PLL Software Reset */
+
+ [129] = 0x0000, /* R129 - PLL2 */
+
+ [131] = 0x0000, /* R131 - PLL 4 */
+
+ [136] = 0x0067, /* R136 - PLL 9 */
+ [137] = 0x001C, /* R137 - PLL 10 */
+ [138] = 0x0071, /* R138 - PLL 11 */
+ [139] = 0x00C7, /* R139 - PLL 12 */
+ [140] = 0x0067, /* R140 - PLL 13 */
+ [141] = 0x0048, /* R141 - PLL 14 */
+ [142] = 0x0022, /* R142 - PLL 15 */
+ [143] = 0x0097, /* R143 - PLL 16 */
+
+ [155] = 0x000C, /* R155 - FLL Control (1) */
+ [156] = 0x0039, /* R156 - FLL Control (2) */
+ [157] = 0x0180, /* R157 - FLL Control (3) */
+
+ [159] = 0x0032, /* R159 - FLL Control (5) */
+ [160] = 0x0018, /* R160 - FLL Control (6) */
+ [161] = 0x007D, /* R161 - FLL Control (7) */
+ [162] = 0x0008, /* R162 - FLL Control (8) */
+
+ [252] = 0x0005, /* R252 - General test 1 */
+
+ [256] = 0x0000, /* R256 - DF1 */
+ [257] = 0x0000, /* R257 - DF2 */
+ [258] = 0x0000, /* R258 - DF3 */
+ [259] = 0x0000, /* R259 - DF4 */
+ [260] = 0x0000, /* R260 - DF5 */
+ [261] = 0x0000, /* R261 - DF6 */
+ [262] = 0x0000, /* R262 - DF7 */
+
+ [264] = 0x0000, /* R264 - LHPF1 */
+ [265] = 0x0000, /* R265 - LHPF2 */
+
+ [268] = 0x0000, /* R268 - THREED1 */
+ [269] = 0x0000, /* R269 - THREED2 */
+ [270] = 0x0000, /* R270 - THREED3 */
+ [271] = 0x0000, /* R271 - THREED4 */
+
+ [276] = 0x000C, /* R276 - DRC 1 */
+ [277] = 0x0925, /* R277 - DRC 2 */
+ [278] = 0x0000, /* R278 - DRC 3 */
+ [279] = 0x0000, /* R279 - DRC 4 */
+ [280] = 0x0000, /* R280 - DRC 5 */
+
+ [285] = 0x0000, /* R285 - Tloopback */
+
+ [335] = 0x0004, /* R335 - EQ1 */
+ [336] = 0x6318, /* R336 - EQ2 */
+ [337] = 0x6300, /* R337 - EQ3 */
+ [338] = 0x0FCA, /* R338 - EQ4 */
+ [339] = 0x0400, /* R339 - EQ5 */
+ [340] = 0x00D8, /* R340 - EQ6 */
+ [341] = 0x1EB5, /* R341 - EQ7 */
+ [342] = 0xF145, /* R342 - EQ8 */
+ [343] = 0x0B75, /* R343 - EQ9 */
+ [344] = 0x01C5, /* R344 - EQ10 */
+ [345] = 0x1C58, /* R345 - EQ11 */
+ [346] = 0xF373, /* R346 - EQ12 */
+ [347] = 0x0A54, /* R347 - EQ13 */
+ [348] = 0x0558, /* R348 - EQ14 */
+ [349] = 0x168E, /* R349 - EQ15 */
+ [350] = 0xF829, /* R350 - EQ16 */
+ [351] = 0x07AD, /* R351 - EQ17 */
+ [352] = 0x1103, /* R352 - EQ18 */
+ [353] = 0x0564, /* R353 - EQ19 */
+ [354] = 0x0559, /* R354 - EQ20 */
+ [355] = 0x4000, /* R355 - EQ21 */
+ [356] = 0x6318, /* R356 - EQ22 */
+ [357] = 0x6300, /* R357 - EQ23 */
+ [358] = 0x0FCA, /* R358 - EQ24 */
+ [359] = 0x0400, /* R359 - EQ25 */
+ [360] = 0x00D8, /* R360 - EQ26 */
+ [361] = 0x1EB5, /* R361 - EQ27 */
+ [362] = 0xF145, /* R362 - EQ28 */
+ [363] = 0x0B75, /* R363 - EQ29 */
+ [364] = 0x01C5, /* R364 - EQ30 */
+ [365] = 0x1C58, /* R365 - EQ31 */
+ [366] = 0xF373, /* R366 - EQ32 */
+ [367] = 0x0A54, /* R367 - EQ33 */
+ [368] = 0x0558, /* R368 - EQ34 */
+ [369] = 0x168E, /* R369 - EQ35 */
+ [370] = 0xF829, /* R370 - EQ36 */
+ [371] = 0x07AD, /* R371 - EQ37 */
+ [372] = 0x1103, /* R372 - EQ38 */
+ [373] = 0x0564, /* R373 - EQ39 */
+ [374] = 0x0559, /* R374 - EQ40 */
+ [375] = 0x4000, /* R375 - EQ41 */
+
+ [513] = 0x0000, /* R513 - GPIO 2 */
+ [514] = 0x0000, /* R514 - GPIO 3 */
+
+ [516] = 0x8100, /* R516 - GPIO 5 */
+ [517] = 0x8100, /* R517 - GPIO 6 */
+
+ [560] = 0x0000, /* R560 - Interrupt Status 1 */
+ [561] = 0x0000, /* R561 - Interrupt Status 2 */
+
+ [568] = 0x0030, /* R568 - Interrupt Status 1 Mask */
+ [569] = 0xFFED, /* R569 - Interrupt Status 2 Mask */
+
+ [576] = 0x0000, /* R576 - Interrupt Control */
+
+ [584] = 0x002D, /* R584 - IRQ Debounce */
+
+ [586] = 0x0000, /* R586 - MICINT Source Pol */
+
+ [768] = 0x1C00, /* R768 - DSP2 Power Management */
+
+ [1037] = 0x0000, /* R1037 - DSP2_ExecControl */
+
+ [8192] = 0x0000, /* R8192 - DSP2 Instruction RAM 0 */
+
+ [9216] = 0x0030, /* R9216 - DSP2 Address RAM 2 */
+ [9217] = 0x0000, /* R9217 - DSP2 Address RAM 1 */
+ [9218] = 0x0000, /* R9218 - DSP2 Address RAM 0 */
+
+ [12288] = 0x0000, /* R12288 - DSP2 Data1 RAM 1 */
+ [12289] = 0x0000, /* R12289 - DSP2 Data1 RAM 0 */
+
+ [13312] = 0x0000, /* R13312 - DSP2 Data2 RAM 1 */
+ [13313] = 0x0000, /* R13313 - DSP2 Data2 RAM 0 */
+
+ [14336] = 0x0000, /* R14336 - DSP2 Data3 RAM 1 */
+ [14337] = 0x0000, /* R14337 - DSP2 Data3 RAM 0 */
+
+ [15360] = 0x000A, /* R15360 - DSP2 Coeff RAM 0 */
+
+ [16384] = 0x0000, /* R16384 - RETUNEADC_SHARED_COEFF_1 */
+ [16385] = 0x0000, /* R16385 - RETUNEADC_SHARED_COEFF_0 */
+ [16386] = 0x0000, /* R16386 - RETUNEDAC_SHARED_COEFF_1 */
+ [16387] = 0x0000, /* R16387 - RETUNEDAC_SHARED_COEFF_0 */
+ [16388] = 0x0000, /* R16388 - SOUNDSTAGE_ENABLES_1 */
+ [16389] = 0x0000, /* R16389 - SOUNDSTAGE_ENABLES_0 */
+
+ [16896] = 0x0002, /* R16896 - HDBASS_AI_1 */
+ [16897] = 0xBD12, /* R16897 - HDBASS_AI_0 */
+ [16898] = 0x007C, /* R16898 - HDBASS_AR_1 */
+ [16899] = 0x586C, /* R16899 - HDBASS_AR_0 */
+ [16900] = 0x0053, /* R16900 - HDBASS_B_1 */
+ [16901] = 0x8121, /* R16901 - HDBASS_B_0 */
+ [16902] = 0x003F, /* R16902 - HDBASS_K_1 */
+ [16903] = 0x8BD8, /* R16903 - HDBASS_K_0 */
+ [16904] = 0x0032, /* R16904 - HDBASS_N1_1 */
+ [16905] = 0xF52D, /* R16905 - HDBASS_N1_0 */
+ [16906] = 0x0065, /* R16906 - HDBASS_N2_1 */
+ [16907] = 0xAC8C, /* R16907 - HDBASS_N2_0 */
+ [16908] = 0x006B, /* R16908 - HDBASS_N3_1 */
+ [16909] = 0xE087, /* R16909 - HDBASS_N3_0 */
+ [16910] = 0x0072, /* R16910 - HDBASS_N4_1 */
+ [16911] = 0x1483, /* R16911 - HDBASS_N4_0 */
+ [16912] = 0x0072, /* R16912 - HDBASS_N5_1 */
+ [16913] = 0x1483, /* R16913 - HDBASS_N5_0 */
+ [16914] = 0x0043, /* R16914 - HDBASS_X1_1 */
+ [16915] = 0x3525, /* R16915 - HDBASS_X1_0 */
+ [16916] = 0x0006, /* R16916 - HDBASS_X2_1 */
+ [16917] = 0x6A4A, /* R16917 - HDBASS_X2_0 */
+ [16918] = 0x0043, /* R16918 - HDBASS_X3_1 */
+ [16919] = 0x6079, /* R16919 - HDBASS_X3_0 */
+ [16920] = 0x0008, /* R16920 - HDBASS_ATK_1 */
+ [16921] = 0x0000, /* R16921 - HDBASS_ATK_0 */
+ [16922] = 0x0001, /* R16922 - HDBASS_DCY_1 */
+ [16923] = 0x0000, /* R16923 - HDBASS_DCY_0 */
+ [16924] = 0x0059, /* R16924 - HDBASS_PG_1 */
+ [16925] = 0x999A, /* R16925 - HDBASS_PG_0 */
+
+ [17048] = 0x0083, /* R17408 - HPF_C_1 */
+ [17049] = 0x98AD, /* R17409 - HPF_C_0 */
+
+ [17920] = 0x007F, /* R17920 - ADCL_RETUNE_C1_1 */
+ [17921] = 0xFFFF, /* R17921 - ADCL_RETUNE_C1_0 */
+ [17922] = 0x0000, /* R17922 - ADCL_RETUNE_C2_1 */
+ [17923] = 0x0000, /* R17923 - ADCL_RETUNE_C2_0 */
+ [17924] = 0x0000, /* R17924 - ADCL_RETUNE_C3_1 */
+ [17925] = 0x0000, /* R17925 - ADCL_RETUNE_C3_0 */
+ [17926] = 0x0000, /* R17926 - ADCL_RETUNE_C4_1 */
+ [17927] = 0x0000, /* R17927 - ADCL_RETUNE_C4_0 */
+ [17928] = 0x0000, /* R17928 - ADCL_RETUNE_C5_1 */
+ [17929] = 0x0000, /* R17929 - ADCL_RETUNE_C5_0 */
+ [17930] = 0x0000, /* R17930 - ADCL_RETUNE_C6_1 */
+ [17931] = 0x0000, /* R17931 - ADCL_RETUNE_C6_0 */
+ [17932] = 0x0000, /* R17932 - ADCL_RETUNE_C7_1 */
+ [17933] = 0x0000, /* R17933 - ADCL_RETUNE_C7_0 */
+ [17934] = 0x0000, /* R17934 - ADCL_RETUNE_C8_1 */
+ [17935] = 0x0000, /* R17935 - ADCL_RETUNE_C8_0 */
+ [17936] = 0x0000, /* R17936 - ADCL_RETUNE_C9_1 */
+ [17937] = 0x0000, /* R17937 - ADCL_RETUNE_C9_0 */
+ [17938] = 0x0000, /* R17938 - ADCL_RETUNE_C10_1 */
+ [17939] = 0x0000, /* R17939 - ADCL_RETUNE_C10_0 */
+ [17940] = 0x0000, /* R17940 - ADCL_RETUNE_C11_1 */
+ [17941] = 0x0000, /* R17941 - ADCL_RETUNE_C11_0 */
+ [17942] = 0x0000, /* R17942 - ADCL_RETUNE_C12_1 */
+ [17943] = 0x0000, /* R17943 - ADCL_RETUNE_C12_0 */
+ [17944] = 0x0000, /* R17944 - ADCL_RETUNE_C13_1 */
+ [17945] = 0x0000, /* R17945 - ADCL_RETUNE_C13_0 */
+ [17946] = 0x0000, /* R17946 - ADCL_RETUNE_C14_1 */
+ [17947] = 0x0000, /* R17947 - ADCL_RETUNE_C14_0 */
+ [17948] = 0x0000, /* R17948 - ADCL_RETUNE_C15_1 */
+ [17949] = 0x0000, /* R17949 - ADCL_RETUNE_C15_0 */
+ [17950] = 0x0000, /* R17950 - ADCL_RETUNE_C16_1 */
+ [17951] = 0x0000, /* R17951 - ADCL_RETUNE_C16_0 */
+ [17952] = 0x0000, /* R17952 - ADCL_RETUNE_C17_1 */
+ [17953] = 0x0000, /* R17953 - ADCL_RETUNE_C17_0 */
+ [17954] = 0x0000, /* R17954 - ADCL_RETUNE_C18_1 */
+ [17955] = 0x0000, /* R17955 - ADCL_RETUNE_C18_0 */
+ [17956] = 0x0000, /* R17956 - ADCL_RETUNE_C19_1 */
+ [17957] = 0x0000, /* R17957 - ADCL_RETUNE_C19_0 */
+ [17958] = 0x0000, /* R17958 - ADCL_RETUNE_C20_1 */
+ [17959] = 0x0000, /* R17959 - ADCL_RETUNE_C20_0 */
+ [17960] = 0x0000, /* R17960 - ADCL_RETUNE_C21_1 */
+ [17961] = 0x0000, /* R17961 - ADCL_RETUNE_C21_0 */
+ [17962] = 0x0000, /* R17962 - ADCL_RETUNE_C22_1 */
+ [17963] = 0x0000, /* R17963 - ADCL_RETUNE_C22_0 */
+ [17964] = 0x0000, /* R17964 - ADCL_RETUNE_C23_1 */
+ [17965] = 0x0000, /* R17965 - ADCL_RETUNE_C23_0 */
+ [17966] = 0x0000, /* R17966 - ADCL_RETUNE_C24_1 */
+ [17967] = 0x0000, /* R17967 - ADCL_RETUNE_C24_0 */
+ [17968] = 0x0000, /* R17968 - ADCL_RETUNE_C25_1 */
+ [17969] = 0x0000, /* R17969 - ADCL_RETUNE_C25_0 */
+ [17970] = 0x0000, /* R17970 - ADCL_RETUNE_C26_1 */
+ [17971] = 0x0000, /* R17971 - ADCL_RETUNE_C26_0 */
+ [17972] = 0x0000, /* R17972 - ADCL_RETUNE_C27_1 */
+ [17973] = 0x0000, /* R17973 - ADCL_RETUNE_C27_0 */
+ [17974] = 0x0000, /* R17974 - ADCL_RETUNE_C28_1 */
+ [17975] = 0x0000, /* R17975 - ADCL_RETUNE_C28_0 */
+ [17976] = 0x0000, /* R17976 - ADCL_RETUNE_C29_1 */
+ [17977] = 0x0000, /* R17977 - ADCL_RETUNE_C29_0 */
+ [17978] = 0x0000, /* R17978 - ADCL_RETUNE_C30_1 */
+ [17979] = 0x0000, /* R17979 - ADCL_RETUNE_C30_0 */
+ [17980] = 0x0000, /* R17980 - ADCL_RETUNE_C31_1 */
+ [17981] = 0x0000, /* R17981 - ADCL_RETUNE_C31_0 */
+ [17982] = 0x0000, /* R17982 - ADCL_RETUNE_C32_1 */
+ [17983] = 0x0000, /* R17983 - ADCL_RETUNE_C32_0 */
+
+ [18432] = 0x0020, /* R18432 - RETUNEADC_PG2_1 */
+ [18433] = 0x0000, /* R18433 - RETUNEADC_PG2_0 */
+ [18434] = 0x0040, /* R18434 - RETUNEADC_PG_1 */
+ [18435] = 0x0000, /* R18435 - RETUNEADC_PG_0 */
+
+ [18944] = 0x007F, /* R18944 - ADCR_RETUNE_C1_1 */
+ [18945] = 0xFFFF, /* R18945 - ADCR_RETUNE_C1_0 */
+ [18946] = 0x0000, /* R18946 - ADCR_RETUNE_C2_1 */
+ [18947] = 0x0000, /* R18947 - ADCR_RETUNE_C2_0 */
+ [18948] = 0x0000, /* R18948 - ADCR_RETUNE_C3_1 */
+ [18949] = 0x0000, /* R18949 - ADCR_RETUNE_C3_0 */
+ [18950] = 0x0000, /* R18950 - ADCR_RETUNE_C4_1 */
+ [18951] = 0x0000, /* R18951 - ADCR_RETUNE_C4_0 */
+ [18952] = 0x0000, /* R18952 - ADCR_RETUNE_C5_1 */
+ [18953] = 0x0000, /* R18953 - ADCR_RETUNE_C5_0 */
+ [18954] = 0x0000, /* R18954 - ADCR_RETUNE_C6_1 */
+ [18955] = 0x0000, /* R18955 - ADCR_RETUNE_C6_0 */
+ [18956] = 0x0000, /* R18956 - ADCR_RETUNE_C7_1 */
+ [18957] = 0x0000, /* R18957 - ADCR_RETUNE_C7_0 */
+ [18958] = 0x0000, /* R18958 - ADCR_RETUNE_C8_1 */
+ [18959] = 0x0000, /* R18959 - ADCR_RETUNE_C8_0 */
+ [18960] = 0x0000, /* R18960 - ADCR_RETUNE_C9_1 */
+ [18961] = 0x0000, /* R18961 - ADCR_RETUNE_C9_0 */
+ [18962] = 0x0000, /* R18962 - ADCR_RETUNE_C10_1 */
+ [18963] = 0x0000, /* R18963 - ADCR_RETUNE_C10_0 */
+ [18964] = 0x0000, /* R18964 - ADCR_RETUNE_C11_1 */
+ [18965] = 0x0000, /* R18965 - ADCR_RETUNE_C11_0 */
+ [18966] = 0x0000, /* R18966 - ADCR_RETUNE_C12_1 */
+ [18967] = 0x0000, /* R18967 - ADCR_RETUNE_C12_0 */
+ [18968] = 0x0000, /* R18968 - ADCR_RETUNE_C13_1 */
+ [18969] = 0x0000, /* R18969 - ADCR_RETUNE_C13_0 */
+ [18970] = 0x0000, /* R18970 - ADCR_RETUNE_C14_1 */
+ [18971] = 0x0000, /* R18971 - ADCR_RETUNE_C14_0 */
+ [18972] = 0x0000, /* R18972 - ADCR_RETUNE_C15_1 */
+ [18973] = 0x0000, /* R18973 - ADCR_RETUNE_C15_0 */
+ [18974] = 0x0000, /* R18974 - ADCR_RETUNE_C16_1 */
+ [18975] = 0x0000, /* R18975 - ADCR_RETUNE_C16_0 */
+ [18976] = 0x0000, /* R18976 - ADCR_RETUNE_C17_1 */
+ [18977] = 0x0000, /* R18977 - ADCR_RETUNE_C17_0 */
+ [18978] = 0x0000, /* R18978 - ADCR_RETUNE_C18_1 */
+ [18979] = 0x0000, /* R18979 - ADCR_RETUNE_C18_0 */
+ [18980] = 0x0000, /* R18980 - ADCR_RETUNE_C19_1 */
+ [18981] = 0x0000, /* R18981 - ADCR_RETUNE_C19_0 */
+ [18982] = 0x0000, /* R18982 - ADCR_RETUNE_C20_1 */
+ [18983] = 0x0000, /* R18983 - ADCR_RETUNE_C20_0 */
+ [18984] = 0x0000, /* R18984 - ADCR_RETUNE_C21_1 */
+ [18985] = 0x0000, /* R18985 - ADCR_RETUNE_C21_0 */
+ [18986] = 0x0000, /* R18986 - ADCR_RETUNE_C22_1 */
+ [18987] = 0x0000, /* R18987 - ADCR_RETUNE_C22_0 */
+ [18988] = 0x0000, /* R18988 - ADCR_RETUNE_C23_1 */
+ [18989] = 0x0000, /* R18989 - ADCR_RETUNE_C23_0 */
+ [18990] = 0x0000, /* R18990 - ADCR_RETUNE_C24_1 */
+ [18991] = 0x0000, /* R18991 - ADCR_RETUNE_C24_0 */
+ [18992] = 0x0000, /* R18992 - ADCR_RETUNE_C25_1 */
+ [18993] = 0x0000, /* R18993 - ADCR_RETUNE_C25_0 */
+ [18994] = 0x0000, /* R18994 - ADCR_RETUNE_C26_1 */
+ [18995] = 0x0000, /* R18995 - ADCR_RETUNE_C26_0 */
+ [18996] = 0x0000, /* R18996 - ADCR_RETUNE_C27_1 */
+ [18997] = 0x0000, /* R18997 - ADCR_RETUNE_C27_0 */
+ [18998] = 0x0000, /* R18998 - ADCR_RETUNE_C28_1 */
+ [18999] = 0x0000, /* R18999 - ADCR_RETUNE_C28_0 */
+ [19000] = 0x0000, /* R19000 - ADCR_RETUNE_C29_1 */
+ [19001] = 0x0000, /* R19001 - ADCR_RETUNE_C29_0 */
+ [19002] = 0x0000, /* R19002 - ADCR_RETUNE_C30_1 */
+ [19003] = 0x0000, /* R19003 - ADCR_RETUNE_C30_0 */
+ [19004] = 0x0000, /* R19004 - ADCR_RETUNE_C31_1 */
+ [19005] = 0x0000, /* R19005 - ADCR_RETUNE_C31_0 */
+ [19006] = 0x0000, /* R19006 - ADCR_RETUNE_C32_1 */
+ [19007] = 0x0000, /* R19007 - ADCR_RETUNE_C32_0 */
+
+ [19456] = 0x007F, /* R19456 - DACL_RETUNE_C1_1 */
+ [19457] = 0xFFFF, /* R19457 - DACL_RETUNE_C1_0 */
+ [19458] = 0x0000, /* R19458 - DACL_RETUNE_C2_1 */
+ [19459] = 0x0000, /* R19459 - DACL_RETUNE_C2_0 */
+ [19460] = 0x0000, /* R19460 - DACL_RETUNE_C3_1 */
+ [19461] = 0x0000, /* R19461 - DACL_RETUNE_C3_0 */
+ [19462] = 0x0000, /* R19462 - DACL_RETUNE_C4_1 */
+ [19463] = 0x0000, /* R19463 - DACL_RETUNE_C4_0 */
+ [19464] = 0x0000, /* R19464 - DACL_RETUNE_C5_1 */
+ [19465] = 0x0000, /* R19465 - DACL_RETUNE_C5_0 */
+ [19466] = 0x0000, /* R19466 - DACL_RETUNE_C6_1 */
+ [19467] = 0x0000, /* R19467 - DACL_RETUNE_C6_0 */
+ [19468] = 0x0000, /* R19468 - DACL_RETUNE_C7_1 */
+ [19469] = 0x0000, /* R19469 - DACL_RETUNE_C7_0 */
+ [19470] = 0x0000, /* R19470 - DACL_RETUNE_C8_1 */
+ [19471] = 0x0000, /* R19471 - DACL_RETUNE_C8_0 */
+ [19472] = 0x0000, /* R19472 - DACL_RETUNE_C9_1 */
+ [19473] = 0x0000, /* R19473 - DACL_RETUNE_C9_0 */
+ [19474] = 0x0000, /* R19474 - DACL_RETUNE_C10_1 */
+ [19475] = 0x0000, /* R19475 - DACL_RETUNE_C10_0 */
+ [19476] = 0x0000, /* R19476 - DACL_RETUNE_C11_1 */
+ [19477] = 0x0000, /* R19477 - DACL_RETUNE_C11_0 */
+ [19478] = 0x0000, /* R19478 - DACL_RETUNE_C12_1 */
+ [19479] = 0x0000, /* R19479 - DACL_RETUNE_C12_0 */
+ [19480] = 0x0000, /* R19480 - DACL_RETUNE_C13_1 */
+ [19481] = 0x0000, /* R19481 - DACL_RETUNE_C13_0 */
+ [19482] = 0x0000, /* R19482 - DACL_RETUNE_C14_1 */
+ [19483] = 0x0000, /* R19483 - DACL_RETUNE_C14_0 */
+ [19484] = 0x0000, /* R19484 - DACL_RETUNE_C15_1 */
+ [19485] = 0x0000, /* R19485 - DACL_RETUNE_C15_0 */
+ [19486] = 0x0000, /* R19486 - DACL_RETUNE_C16_1 */
+ [19487] = 0x0000, /* R19487 - DACL_RETUNE_C16_0 */
+ [19488] = 0x0000, /* R19488 - DACL_RETUNE_C17_1 */
+ [19489] = 0x0000, /* R19489 - DACL_RETUNE_C17_0 */
+ [19490] = 0x0000, /* R19490 - DACL_RETUNE_C18_1 */
+ [19491] = 0x0000, /* R19491 - DACL_RETUNE_C18_0 */
+ [19492] = 0x0000, /* R19492 - DACL_RETUNE_C19_1 */
+ [19493] = 0x0000, /* R19493 - DACL_RETUNE_C19_0 */
+ [19494] = 0x0000, /* R19494 - DACL_RETUNE_C20_1 */
+ [19495] = 0x0000, /* R19495 - DACL_RETUNE_C20_0 */
+ [19496] = 0x0000, /* R19496 - DACL_RETUNE_C21_1 */
+ [19497] = 0x0000, /* R19497 - DACL_RETUNE_C21_0 */
+ [19498] = 0x0000, /* R19498 - DACL_RETUNE_C22_1 */
+ [19499] = 0x0000, /* R19499 - DACL_RETUNE_C22_0 */
+ [19500] = 0x0000, /* R19500 - DACL_RETUNE_C23_1 */
+ [19501] = 0x0000, /* R19501 - DACL_RETUNE_C23_0 */
+ [19502] = 0x0000, /* R19502 - DACL_RETUNE_C24_1 */
+ [19503] = 0x0000, /* R19503 - DACL_RETUNE_C24_0 */
+ [19504] = 0x0000, /* R19504 - DACL_RETUNE_C25_1 */
+ [19505] = 0x0000, /* R19505 - DACL_RETUNE_C25_0 */
+ [19506] = 0x0000, /* R19506 - DACL_RETUNE_C26_1 */
+ [19507] = 0x0000, /* R19507 - DACL_RETUNE_C26_0 */
+ [19508] = 0x0000, /* R19508 - DACL_RETUNE_C27_1 */
+ [19509] = 0x0000, /* R19509 - DACL_RETUNE_C27_0 */
+ [19510] = 0x0000, /* R19510 - DACL_RETUNE_C28_1 */
+ [19511] = 0x0000, /* R19511 - DACL_RETUNE_C28_0 */
+ [19512] = 0x0000, /* R19512 - DACL_RETUNE_C29_1 */
+ [19513] = 0x0000, /* R19513 - DACL_RETUNE_C29_0 */
+ [19514] = 0x0000, /* R19514 - DACL_RETUNE_C30_1 */
+ [19515] = 0x0000, /* R19515 - DACL_RETUNE_C30_0 */
+ [19516] = 0x0000, /* R19516 - DACL_RETUNE_C31_1 */
+ [19517] = 0x0000, /* R19517 - DACL_RETUNE_C31_0 */
+ [19518] = 0x0000, /* R19518 - DACL_RETUNE_C32_1 */
+ [19519] = 0x0000, /* R19519 - DACL_RETUNE_C32_0 */
+
+ [19968] = 0x0020, /* R19968 - RETUNEDAC_PG2_1 */
+ [19969] = 0x0000, /* R19969 - RETUNEDAC_PG2_0 */
+ [19970] = 0x0040, /* R19970 - RETUNEDAC_PG_1 */
+ [19971] = 0x0000, /* R19971 - RETUNEDAC_PG_0 */
+
+ [20480] = 0x007F, /* R20480 - DACR_RETUNE_C1_1 */
+ [20481] = 0xFFFF, /* R20481 - DACR_RETUNE_C1_0 */
+ [20482] = 0x0000, /* R20482 - DACR_RETUNE_C2_1 */
+ [20483] = 0x0000, /* R20483 - DACR_RETUNE_C2_0 */
+ [20484] = 0x0000, /* R20484 - DACR_RETUNE_C3_1 */
+ [20485] = 0x0000, /* R20485 - DACR_RETUNE_C3_0 */
+ [20486] = 0x0000, /* R20486 - DACR_RETUNE_C4_1 */
+ [20487] = 0x0000, /* R20487 - DACR_RETUNE_C4_0 */
+ [20488] = 0x0000, /* R20488 - DACR_RETUNE_C5_1 */
+ [20489] = 0x0000, /* R20489 - DACR_RETUNE_C5_0 */
+ [20490] = 0x0000, /* R20490 - DACR_RETUNE_C6_1 */
+ [20491] = 0x0000, /* R20491 - DACR_RETUNE_C6_0 */
+ [20492] = 0x0000, /* R20492 - DACR_RETUNE_C7_1 */
+ [20493] = 0x0000, /* R20493 - DACR_RETUNE_C7_0 */
+ [20494] = 0x0000, /* R20494 - DACR_RETUNE_C8_1 */
+ [20495] = 0x0000, /* R20495 - DACR_RETUNE_C8_0 */
+ [20496] = 0x0000, /* R20496 - DACR_RETUNE_C9_1 */
+ [20497] = 0x0000, /* R20497 - DACR_RETUNE_C9_0 */
+ [20498] = 0x0000, /* R20498 - DACR_RETUNE_C10_1 */
+ [20499] = 0x0000, /* R20499 - DACR_RETUNE_C10_0 */
+ [20500] = 0x0000, /* R20500 - DACR_RETUNE_C11_1 */
+ [20501] = 0x0000, /* R20501 - DACR_RETUNE_C11_0 */
+ [20502] = 0x0000, /* R20502 - DACR_RETUNE_C12_1 */
+ [20503] = 0x0000, /* R20503 - DACR_RETUNE_C12_0 */
+ [20504] = 0x0000, /* R20504 - DACR_RETUNE_C13_1 */
+ [20505] = 0x0000, /* R20505 - DACR_RETUNE_C13_0 */
+ [20506] = 0x0000, /* R20506 - DACR_RETUNE_C14_1 */
+ [20507] = 0x0000, /* R20507 - DACR_RETUNE_C14_0 */
+ [20508] = 0x0000, /* R20508 - DACR_RETUNE_C15_1 */
+ [20509] = 0x0000, /* R20509 - DACR_RETUNE_C15_0 */
+ [20510] = 0x0000, /* R20510 - DACR_RETUNE_C16_1 */
+ [20511] = 0x0000, /* R20511 - DACR_RETUNE_C16_0 */
+ [20512] = 0x0000, /* R20512 - DACR_RETUNE_C17_1 */
+ [20513] = 0x0000, /* R20513 - DACR_RETUNE_C17_0 */
+ [20514] = 0x0000, /* R20514 - DACR_RETUNE_C18_1 */
+ [20515] = 0x0000, /* R20515 - DACR_RETUNE_C18_0 */
+ [20516] = 0x0000, /* R20516 - DACR_RETUNE_C19_1 */
+ [20517] = 0x0000, /* R20517 - DACR_RETUNE_C19_0 */
+ [20518] = 0x0000, /* R20518 - DACR_RETUNE_C20_1 */
+ [20519] = 0x0000, /* R20519 - DACR_RETUNE_C20_0 */
+ [20520] = 0x0000, /* R20520 - DACR_RETUNE_C21_1 */
+ [20521] = 0x0000, /* R20521 - DACR_RETUNE_C21_0 */
+ [20522] = 0x0000, /* R20522 - DACR_RETUNE_C22_1 */
+ [20523] = 0x0000, /* R20523 - DACR_RETUNE_C22_0 */
+ [20524] = 0x0000, /* R20524 - DACR_RETUNE_C23_1 */
+ [20525] = 0x0000, /* R20525 - DACR_RETUNE_C23_0 */
+ [20526] = 0x0000, /* R20526 - DACR_RETUNE_C24_1 */
+ [20527] = 0x0000, /* R20527 - DACR_RETUNE_C24_0 */
+ [20528] = 0x0000, /* R20528 - DACR_RETUNE_C25_1 */
+ [20529] = 0x0000, /* R20529 - DACR_RETUNE_C25_0 */
+ [20530] = 0x0000, /* R20530 - DACR_RETUNE_C26_1 */
+ [20531] = 0x0000, /* R20531 - DACR_RETUNE_C26_0 */
+ [20532] = 0x0000, /* R20532 - DACR_RETUNE_C27_1 */
+ [20533] = 0x0000, /* R20533 - DACR_RETUNE_C27_0 */
+ [20534] = 0x0000, /* R20534 - DACR_RETUNE_C28_1 */
+ [20535] = 0x0000, /* R20535 - DACR_RETUNE_C28_0 */
+ [20536] = 0x0000, /* R20536 - DACR_RETUNE_C29_1 */
+ [20537] = 0x0000, /* R20537 - DACR_RETUNE_C29_0 */
+ [20538] = 0x0000, /* R20538 - DACR_RETUNE_C30_1 */
+ [20539] = 0x0000, /* R20539 - DACR_RETUNE_C30_0 */
+ [20540] = 0x0000, /* R20540 - DACR_RETUNE_C31_1 */
+ [20541] = 0x0000, /* R20541 - DACR_RETUNE_C31_0 */
+ [20542] = 0x0000, /* R20542 - DACR_RETUNE_C32_1 */
+ [20543] = 0x0000, /* R20543 - DACR_RETUNE_C32_0 */
+
+ [20992] = 0x008C, /* R20992 - VSS_XHD2_1 */
+ [20993] = 0x0200, /* R20993 - VSS_XHD2_0 */
+ [20994] = 0x0035, /* R20994 - VSS_XHD3_1 */
+ [20995] = 0x0700, /* R20995 - VSS_XHD3_0 */
+ [20996] = 0x003A, /* R20996 - VSS_XHN1_1 */
+ [20997] = 0x4100, /* R20997 - VSS_XHN1_0 */
+ [20998] = 0x008B, /* R20998 - VSS_XHN2_1 */
+ [20999] = 0x7D00, /* R20999 - VSS_XHN2_0 */
+ [21000] = 0x003A, /* R21000 - VSS_XHN3_1 */
+ [21001] = 0x4100, /* R21001 - VSS_XHN3_0 */
+ [21002] = 0x008C, /* R21002 - VSS_XLA_1 */
+ [21003] = 0xFEE8, /* R21003 - VSS_XLA_0 */
+ [21004] = 0x0078, /* R21004 - VSS_XLB_1 */
+ [21005] = 0x0000, /* R21005 - VSS_XLB_0 */
+ [21006] = 0x003F, /* R21006 - VSS_XLG_1 */
+ [21007] = 0xB260, /* R21007 - VSS_XLG_0 */
+ [21008] = 0x002D, /* R21008 - VSS_PG2_1 */
+ [21009] = 0x1818, /* R21009 - VSS_PG2_0 */
+ [21010] = 0x0020, /* R21010 - VSS_PG_1 */
+ [21011] = 0x0000, /* R21011 - VSS_PG_0 */
+ [21012] = 0x00F1, /* R21012 - VSS_XTD1_1 */
+ [21013] = 0x8340, /* R21013 - VSS_XTD1_0 */
+ [21014] = 0x00FB, /* R21014 - VSS_XTD2_1 */
+ [21015] = 0x8300, /* R21015 - VSS_XTD2_0 */
+ [21016] = 0x00EE, /* R21016 - VSS_XTD3_1 */
+ [21017] = 0xAEC0, /* R21017 - VSS_XTD3_0 */
+ [21018] = 0x00FB, /* R21018 - VSS_XTD4_1 */
+ [21019] = 0xAC40, /* R21019 - VSS_XTD4_0 */
+ [21020] = 0x00F1, /* R21020 - VSS_XTD5_1 */
+ [21021] = 0x7F80, /* R21021 - VSS_XTD5_0 */
+ [21022] = 0x00F4, /* R21022 - VSS_XTD6_1 */
+ [21023] = 0x3B40, /* R21023 - VSS_XTD6_0 */
+ [21024] = 0x00F5, /* R21024 - VSS_XTD7_1 */
+ [21025] = 0xFB00, /* R21025 - VSS_XTD7_0 */
+ [21026] = 0x00EA, /* R21026 - VSS_XTD8_1 */
+ [21027] = 0x10C0, /* R21027 - VSS_XTD8_0 */
+ [21028] = 0x00FC, /* R21028 - VSS_XTD9_1 */
+ [21029] = 0xC580, /* R21029 - VSS_XTD9_0 */
+ [21030] = 0x00E2, /* R21030 - VSS_XTD10_1 */
+ [21031] = 0x75C0, /* R21031 - VSS_XTD10_0 */
+ [21032] = 0x0004, /* R21032 - VSS_XTD11_1 */
+ [21033] = 0xB480, /* R21033 - VSS_XTD11_0 */
+ [21034] = 0x00D4, /* R21034 - VSS_XTD12_1 */
+ [21035] = 0xF980, /* R21035 - VSS_XTD12_0 */
+ [21036] = 0x0004, /* R21036 - VSS_XTD13_1 */
+ [21037] = 0x9140, /* R21037 - VSS_XTD13_0 */
+ [21038] = 0x00D8, /* R21038 - VSS_XTD14_1 */
+ [21039] = 0xA480, /* R21039 - VSS_XTD14_0 */
+ [21040] = 0x0002, /* R21040 - VSS_XTD15_1 */
+ [21041] = 0x3DC0, /* R21041 - VSS_XTD15_0 */
+ [21042] = 0x00CF, /* R21042 - VSS_XTD16_1 */
+ [21043] = 0x7A80, /* R21043 - VSS_XTD16_0 */
+ [21044] = 0x00DC, /* R21044 - VSS_XTD17_1 */
+ [21045] = 0x0600, /* R21045 - VSS_XTD17_0 */
+ [21046] = 0x00F2, /* R21046 - VSS_XTD18_1 */
+ [21047] = 0xDAC0, /* R21047 - VSS_XTD18_0 */
+ [21048] = 0x00BA, /* R21048 - VSS_XTD19_1 */
+ [21049] = 0xF340, /* R21049 - VSS_XTD19_0 */
+ [21050] = 0x000A, /* R21050 - VSS_XTD20_1 */
+ [21051] = 0x7940, /* R21051 - VSS_XTD20_0 */
+ [21052] = 0x001C, /* R21052 - VSS_XTD21_1 */
+ [21053] = 0x0680, /* R21053 - VSS_XTD21_0 */
+ [21054] = 0x00FD, /* R21054 - VSS_XTD22_1 */
+ [21055] = 0x2D00, /* R21055 - VSS_XTD22_0 */
+ [21056] = 0x001C, /* R21056 - VSS_XTD23_1 */
+ [21057] = 0xE840, /* R21057 - VSS_XTD23_0 */
+ [21058] = 0x000D, /* R21058 - VSS_XTD24_1 */
+ [21059] = 0xDC40, /* R21059 - VSS_XTD24_0 */
+ [21060] = 0x00FC, /* R21060 - VSS_XTD25_1 */
+ [21061] = 0x9D00, /* R21061 - VSS_XTD25_0 */
+ [21062] = 0x0009, /* R21062 - VSS_XTD26_1 */
+ [21063] = 0x5580, /* R21063 - VSS_XTD26_0 */
+ [21064] = 0x00FE, /* R21064 - VSS_XTD27_1 */
+ [21065] = 0x7E80, /* R21065 - VSS_XTD27_0 */
+ [21066] = 0x000E, /* R21066 - VSS_XTD28_1 */
+ [21067] = 0xAB40, /* R21067 - VSS_XTD28_0 */
+ [21068] = 0x00F9, /* R21068 - VSS_XTD29_1 */
+ [21069] = 0x9880, /* R21069 - VSS_XTD29_0 */
+ [21070] = 0x0009, /* R21070 - VSS_XTD30_1 */
+ [21071] = 0x87C0, /* R21071 - VSS_XTD30_0 */
+ [21072] = 0x00FD, /* R21072 - VSS_XTD31_1 */
+ [21073] = 0x2C40, /* R21073 - VSS_XTD31_0 */
+ [21074] = 0x0009, /* R21074 - VSS_XTD32_1 */
+ [21075] = 0x4800, /* R21075 - VSS_XTD32_0 */
+ [21076] = 0x0003, /* R21076 - VSS_XTS1_1 */
+ [21077] = 0x5F40, /* R21077 - VSS_XTS1_0 */
+ [21078] = 0x0000, /* R21078 - VSS_XTS2_1 */
+ [21079] = 0x8700, /* R21079 - VSS_XTS2_0 */
+ [21080] = 0x00FA, /* R21080 - VSS_XTS3_1 */
+ [21081] = 0xE4C0, /* R21081 - VSS_XTS3_0 */
+ [21082] = 0x0000, /* R21082 - VSS_XTS4_1 */
+ [21083] = 0x0B40, /* R21083 - VSS_XTS4_0 */
+ [21084] = 0x0004, /* R21084 - VSS_XTS5_1 */
+ [21085] = 0xE180, /* R21085 - VSS_XTS5_0 */
+ [21086] = 0x0001, /* R21086 - VSS_XTS6_1 */
+ [21087] = 0x1F40, /* R21087 - VSS_XTS6_0 */
+ [21088] = 0x00F8, /* R21088 - VSS_XTS7_1 */
+ [21089] = 0xB000, /* R21089 - VSS_XTS7_0 */
+ [21090] = 0x00FB, /* R21090 - VSS_XTS8_1 */
+ [21091] = 0xCBC0, /* R21091 - VSS_XTS8_0 */
+ [21092] = 0x0004, /* R21092 - VSS_XTS9_1 */
+ [21093] = 0xF380, /* R21093 - VSS_XTS9_0 */
+ [21094] = 0x0007, /* R21094 - VSS_XTS10_1 */
+ [21095] = 0xDF40, /* R21095 - VSS_XTS10_0 */
+ [21096] = 0x00FF, /* R21096 - VSS_XTS11_1 */
+ [21097] = 0x0700, /* R21097 - VSS_XTS11_0 */
+ [21098] = 0x00EF, /* R21098 - VSS_XTS12_1 */
+ [21099] = 0xD700, /* R21099 - VSS_XTS12_0 */
+ [21100] = 0x00FB, /* R21100 - VSS_XTS13_1 */
+ [21101] = 0xAF40, /* R21101 - VSS_XTS13_0 */
+ [21102] = 0x0010, /* R21102 - VSS_XTS14_1 */
+ [21103] = 0x8A80, /* R21103 - VSS_XTS14_0 */
+ [21104] = 0x0011, /* R21104 - VSS_XTS15_1 */
+ [21105] = 0x07C0, /* R21105 - VSS_XTS15_0 */
+ [21106] = 0x00E0, /* R21106 - VSS_XTS16_1 */
+ [21107] = 0x0800, /* R21107 - VSS_XTS16_0 */
+ [21108] = 0x00D2, /* R21108 - VSS_XTS17_1 */
+ [21109] = 0x7600, /* R21109 - VSS_XTS17_0 */
+ [21110] = 0x0020, /* R21110 - VSS_XTS18_1 */
+ [21111] = 0xCF40, /* R21111 - VSS_XTS18_0 */
+ [21112] = 0x0030, /* R21112 - VSS_XTS19_1 */
+ [21113] = 0x2340, /* R21113 - VSS_XTS19_0 */
+ [21114] = 0x00FD, /* R21114 - VSS_XTS20_1 */
+ [21115] = 0x69C0, /* R21115 - VSS_XTS20_0 */
+ [21116] = 0x0028, /* R21116 - VSS_XTS21_1 */
+ [21117] = 0x3500, /* R21117 - VSS_XTS21_0 */
+ [21118] = 0x0006, /* R21118 - VSS_XTS22_1 */
+ [21119] = 0x3300, /* R21119 - VSS_XTS22_0 */
+ [21120] = 0x00D9, /* R21120 - VSS_XTS23_1 */
+ [21121] = 0xF6C0, /* R21121 - VSS_XTS23_0 */
+ [21122] = 0x00F3, /* R21122 - VSS_XTS24_1 */
+ [21123] = 0x3340, /* R21123 - VSS_XTS24_0 */
+ [21124] = 0x000F, /* R21124 - VSS_XTS25_1 */
+ [21125] = 0x4200, /* R21125 - VSS_XTS25_0 */
+ [21126] = 0x0004, /* R21126 - VSS_XTS26_1 */
+ [21127] = 0x0C80, /* R21127 - VSS_XTS26_0 */
+ [21128] = 0x00FB, /* R21128 - VSS_XTS27_1 */
+ [21129] = 0x3F80, /* R21129 - VSS_XTS27_0 */
+ [21130] = 0x00F7, /* R21130 - VSS_XTS28_1 */
+ [21131] = 0x57C0, /* R21131 - VSS_XTS28_0 */
+ [21132] = 0x0003, /* R21132 - VSS_XTS29_1 */
+ [21133] = 0x5400, /* R21133 - VSS_XTS29_0 */
+ [21134] = 0x0000, /* R21134 - VSS_XTS30_1 */
+ [21135] = 0xC6C0, /* R21135 - VSS_XTS30_0 */
+ [21136] = 0x0003, /* R21136 - VSS_XTS31_1 */
+ [21137] = 0x12C0, /* R21137 - VSS_XTS31_0 */
+ [21138] = 0x00FD, /* R21138 - VSS_XTS32_1 */
+ [21139] = 0x8580, /* R21139 - VSS_XTS32_0 */
+};
+
+static const struct wm8962_reg_access {
+ u16 read;
+ u16 write;
+ u16 vol;
+} wm8962_reg_access[WM8962_MAX_REGISTER + 1] = {
+ [0] = { 0x00FF, 0x01FF, 0x0000 }, /* R0 - Left Input volume */
+ [1] = { 0xFEFF, 0x01FF, 0xFFFF }, /* R1 - Right Input volume */
+ [2] = { 0x00FF, 0x01FF, 0x0000 }, /* R2 - HPOUTL volume */
+ [3] = { 0x00FF, 0x01FF, 0x0000 }, /* R3 - HPOUTR volume */
+ [4] = { 0x07FE, 0x07FE, 0xFFFF }, /* R4 - Clocking1 */
+ [5] = { 0x007F, 0x007F, 0x0000 }, /* R5 - ADC & DAC Control 1 */
+ [6] = { 0x37ED, 0x37ED, 0x0000 }, /* R6 - ADC & DAC Control 2 */
+ [7] = { 0x1FFF, 0x1FFF, 0x0000 }, /* R7 - Audio Interface 0 */
+ [8] = { 0x0FEF, 0x0FEF, 0xFFFF }, /* R8 - Clocking2 */
+ [9] = { 0x0B9F, 0x039F, 0x0000 }, /* R9 - Audio Interface 1 */
+ [10] = { 0x00FF, 0x01FF, 0x0000 }, /* R10 - Left DAC volume */
+ [11] = { 0x00FF, 0x01FF, 0x0000 }, /* R11 - Right DAC volume */
+ [14] = { 0x07FF, 0x07FF, 0x0000 }, /* R14 - Audio Interface 2 */
+ [15] = { 0xFFFF, 0xFFFF, 0xFFFF }, /* R15 - Software Reset */
+ [17] = { 0x07FF, 0x07FF, 0x0000 }, /* R17 - ALC1 */
+ [18] = { 0xF8FF, 0x00FF, 0xFFFF }, /* R18 - ALC2 */
+ [19] = { 0x1DFF, 0x1DFF, 0x0000 }, /* R19 - ALC3 */
+ [20] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20 - Noise Gate */
+ [21] = { 0x00FF, 0x01FF, 0x0000 }, /* R21 - Left ADC volume */
+ [22] = { 0x00FF, 0x01FF, 0x0000 }, /* R22 - Right ADC volume */
+ [23] = { 0x0161, 0x0161, 0x0000 }, /* R23 - Additional control(1) */
+ [24] = { 0x0008, 0x0008, 0x0000 }, /* R24 - Additional control(2) */
+ [25] = { 0x07FE, 0x07FE, 0x0000 }, /* R25 - Pwr Mgmt (1) */
+ [26] = { 0x01FB, 0x01FB, 0x0000 }, /* R26 - Pwr Mgmt (2) */
+ [27] = { 0x0017, 0x0017, 0x0000 }, /* R27 - Additional Control (3) */
+ [28] = { 0x001C, 0x001C, 0x0000 }, /* R28 - Anti-pop */
+
+ [30] = { 0xFFFE, 0xFFFE, 0x0000 }, /* R30 - Clocking 3 */
+ [31] = { 0x000F, 0x000F, 0x0000 }, /* R31 - Input mixer control (1) */
+ [32] = { 0x01FF, 0x01FF, 0x0000 }, /* R32 - Left input mixer volume */
+ [33] = { 0x01FF, 0x01FF, 0x0000 }, /* R33 - Right input mixer volume */
+ [34] = { 0x003F, 0x003F, 0x0000 }, /* R34 - Input mixer control (2) */
+ [35] = { 0x003F, 0x003F, 0x0000 }, /* R35 - Input bias control */
+ [37] = { 0x001F, 0x001F, 0x0000 }, /* R37 - Left input PGA control */
+ [38] = { 0x001F, 0x001F, 0x0000 }, /* R38 - Right input PGA control */
+ [40] = { 0x00FF, 0x01FF, 0x0000 }, /* R40 - SPKOUTL volume */
+ [41] = { 0x00FF, 0x01FF, 0x0000 }, /* R41 - SPKOUTR volume */
+
+ [47] = { 0x000F, 0x0000, 0xFFFF }, /* R47 - Thermal Shutdown Status*/
+ [48] = { 0x7EC7, 0x7E07, 0xFFFF }, /* R48 - Additional Control (4) */
+ [49] = { 0x00D3, 0x00D7, 0xFFFF }, /* R49 - Class D Control 1 */
+ [51] = { 0x0047, 0x0047, 0x0000 }, /* R51 - Class D Control 2 */
+ [56] = { 0x001E, 0x001E, 0x0000 }, /* R56 - Clocking 4 */
+ [57] = { 0x02FC, 0x02FC, 0x0000 }, /* R57 - DAC DSP Mixing (1) */
+ [58] = { 0x00FC, 0x00FC, 0x0000 }, /* R58 - DAC DSP Mixing (2) */
+ [60] = { 0x00CC, 0x00CC, 0x0000 }, /* R60 - DC Servo 0 */
+ [61] = { 0x00DD, 0x00DD, 0x0000 }, /* R61 - DC Servo 1 */
+ [64] = { 0x3F80, 0x3F80, 0x0000 }, /* R64 - DC Servo 4 */
+ [66] = { 0x0780, 0x0000, 0xFFFF }, /* R66 - DC Servo 6 */
+ [68] = { 0x0007, 0x0007, 0x0000 }, /* R68 - Analogue PGA Bias */
+ [69] = { 0x00FF, 0x00FF, 0x0000 }, /* R69 - Analogue HP 0 */
+ [71] = { 0x01FF, 0x01FF, 0x0000 }, /* R71 - Analogue HP 2 */
+ [72] = { 0x0001, 0x0001, 0x0000 }, /* R72 - Charge Pump 1 */
+ [82] = { 0x0001, 0x0001, 0x0000 }, /* R82 - Charge Pump B */
+ [87] = { 0x00A0, 0x00A0, 0x0000 }, /* R87 - Write Sequencer Control 1 */
+ [90] = { 0x007F, 0x01FF, 0x0000 }, /* R90 - Write Sequencer Control 2 */
+ [93] = { 0x03F9, 0x0000, 0x0000 }, /* R93 - Write Sequencer Control 3 */
+ [94] = { 0x0070, 0x0070, 0x0000 }, /* R94 - Control Interface */
+ [99] = { 0x000F, 0x000F, 0x0000 }, /* R99 - Mixer Enables */
+ [100] = { 0x00BF, 0x00BF, 0x0000 }, /* R100 - Headphone Mixer (1) */
+ [101] = { 0x00BF, 0x00BF, 0x0000 }, /* R101 - Headphone Mixer (2) */
+ [102] = { 0x01FF, 0x01FF, 0x0000 }, /* R102 - Headphone Mixer (3) */
+ [103] = { 0x01FF, 0x01FF, 0x0000 }, /* R103 - Headphone Mixer (4) */
+ [105] = { 0x00BF, 0x00BF, 0x0000 }, /* R105 - Speaker Mixer (1) */
+ [106] = { 0x00BF, 0x00BF, 0x0000 }, /* R106 - Speaker Mixer (2) */
+ [107] = { 0x01FF, 0x01FF, 0x0000 }, /* R107 - Speaker Mixer (3) */
+ [108] = { 0x01FF, 0x01FF, 0x0000 }, /* R108 - Speaker Mixer (4) */
+ [109] = { 0x00F0, 0x00F0, 0x0000 }, /* R109 - Speaker Mixer (5) */
+ [110] = { 0x00F7, 0x00F7, 0x0000 }, /* R110 - Beep Generator (1) */
+ [115] = { 0x001F, 0x001F, 0x0000 }, /* R115 - Oscillator Trim (3) */
+ [116] = { 0x001F, 0x001F, 0x0000 }, /* R116 - Oscillator Trim (4) */
+ [119] = { 0x00FF, 0x00FF, 0x0000 }, /* R119 - Oscillator Trim (7) */
+ [124] = { 0x0079, 0x0079, 0x0000 }, /* R124 - Analogue Clocking1 */
+ [125] = { 0x00DF, 0x00DF, 0x0000 }, /* R125 - Analogue Clocking2 */
+ [126] = { 0x000D, 0x000D, 0x0000 }, /* R126 - Analogue Clocking3 */
+ [127] = { 0x0000, 0xFFFF, 0x0000 }, /* R127 - PLL Software Reset */
+ [129] = { 0x00B0, 0x00B0, 0x0000 }, /* R129 - PLL2 */
+ [131] = { 0x0003, 0x0003, 0x0000 }, /* R131 - PLL 4 */
+ [136] = { 0x005F, 0x005F, 0x0000 }, /* R136 - PLL 9 */
+ [137] = { 0x00FF, 0x00FF, 0x0000 }, /* R137 - PLL 10 */
+ [138] = { 0x00FF, 0x00FF, 0x0000 }, /* R138 - PLL 11 */
+ [139] = { 0x00FF, 0x00FF, 0x0000 }, /* R139 - PLL 12 */
+ [140] = { 0x005F, 0x005F, 0x0000 }, /* R140 - PLL 13 */
+ [141] = { 0x00FF, 0x00FF, 0x0000 }, /* R141 - PLL 14 */
+ [142] = { 0x00FF, 0x00FF, 0x0000 }, /* R142 - PLL 15 */
+ [143] = { 0x00FF, 0x00FF, 0x0000 }, /* R143 - PLL 16 */
+ [155] = { 0x0067, 0x0067, 0x0000 }, /* R155 - FLL Control (1) */
+ [156] = { 0x01FB, 0x01FB, 0x0000 }, /* R156 - FLL Control (2) */
+ [157] = { 0x0007, 0x0007, 0x0000 }, /* R157 - FLL Control (3) */
+ [159] = { 0x007F, 0x007F, 0x0000 }, /* R159 - FLL Control (5) */
+ [160] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R160 - FLL Control (6) */
+ [161] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R161 - FLL Control (7) */
+ [162] = { 0x03FF, 0x03FF, 0x0000 }, /* R162 - FLL Control (8) */
+ [252] = { 0x0005, 0x0005, 0x0000 }, /* R252 - General test 1 */
+ [256] = { 0x000F, 0x000F, 0x0000 }, /* R256 - DF1 */
+ [257] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R257 - DF2 */
+ [258] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R258 - DF3 */
+ [259] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R259 - DF4 */
+ [260] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R260 - DF5 */
+ [261] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R261 - DF6 */
+ [262] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R262 - DF7 */
+ [264] = { 0x0003, 0x0003, 0x0000 }, /* R264 - LHPF1 */
+ [265] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R265 - LHPF2 */
+ [268] = { 0x0077, 0x0077, 0x0000 }, /* R268 - THREED1 */
+ [269] = { 0xFFFC, 0xFFFC, 0x0000 }, /* R269 - THREED2 */
+ [270] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R270 - THREED3 */
+ [271] = { 0xFFFC, 0xFFFC, 0x0000 }, /* R271 - THREED4 */
+ [276] = { 0x7FFF, 0x7FFF, 0x0000 }, /* R276 - DRC 1 */
+ [277] = { 0x1FFF, 0x1FFF, 0x0000 }, /* R277 - DRC 2 */
+ [278] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R278 - DRC 3 */
+ [279] = { 0x07FF, 0x07FF, 0x0000 }, /* R279 - DRC 4 */
+ [280] = { 0x03FF, 0x03FF, 0x0000 }, /* R280 - DRC 5 */
+ [285] = { 0x0003, 0x0003, 0x0000 }, /* R285 - Tloopback */
+ [335] = { 0x0007, 0x0007, 0x0000 }, /* R335 - EQ1 */
+ [336] = { 0xFFFE, 0xFFFE, 0x0000 }, /* R336 - EQ2 */
+ [337] = { 0xFFC0, 0xFFC0, 0x0000 }, /* R337 - EQ3 */
+ [338] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R338 - EQ4 */
+ [339] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R339 - EQ5 */
+ [340] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R340 - EQ6 */
+ [341] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R341 - EQ7 */
+ [342] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R342 - EQ8 */
+ [343] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R343 - EQ9 */
+ [344] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R344 - EQ10 */
+ [345] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R345 - EQ11 */
+ [346] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R346 - EQ12 */
+ [347] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R347 - EQ13 */
+ [348] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R348 - EQ14 */
+ [349] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R349 - EQ15 */
+ [350] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R350 - EQ16 */
+ [351] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R351 - EQ17 */
+ [352] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R352 - EQ18 */
+ [353] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R353 - EQ19 */
+ [354] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R354 - EQ20 */
+ [355] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R355 - EQ21 */
+ [356] = { 0xFFFE, 0xFFFE, 0x0000 }, /* R356 - EQ22 */
+ [357] = { 0xFFC0, 0xFFC0, 0x0000 }, /* R357 - EQ23 */
+ [358] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R358 - EQ24 */
+ [359] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R359 - EQ25 */
+ [360] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R360 - EQ26 */
+ [361] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R361 - EQ27 */
+ [362] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R362 - EQ28 */
+ [363] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R363 - EQ29 */
+ [364] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R364 - EQ30 */
+ [365] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R365 - EQ31 */
+ [366] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R366 - EQ32 */
+ [367] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R367 - EQ33 */
+ [368] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R368 - EQ34 */
+ [369] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R369 - EQ35 */
+ [370] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R370 - EQ36 */
+ [371] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R371 - EQ37 */
+ [372] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R372 - EQ38 */
+ [373] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R373 - EQ39 */
+ [374] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R374 - EQ40 */
+ [375] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R375 - EQ41 */
+ [513] = { 0x045F, 0x045F, 0x0000 }, /* R513 - GPIO 2 */
+ [514] = { 0x045F, 0x045F, 0x0000 }, /* R514 - GPIO 3 */
+ [516] = { 0xE75F, 0xE75F, 0x0000 }, /* R516 - GPIO 5 */
+ [517] = { 0xE75F, 0xE75F, 0x0000 }, /* R517 - GPIO 6 */
+ [560] = { 0x0030, 0x0030, 0xFFFF }, /* R560 - Interrupt Status 1 */
+ [561] = { 0xFFED, 0xFFED, 0xFFFF }, /* R561 - Interrupt Status 2 */
+ [568] = { 0x0030, 0x0030, 0x0000 }, /* R568 - Interrupt Status 1 Mask */
+ [569] = { 0xFFED, 0xFFED, 0x0000 }, /* R569 - Interrupt Status 2 Mask */
+ [576] = { 0x0001, 0x0001, 0x0000 }, /* R576 - Interrupt Control */
+ [584] = { 0x002D, 0x002D, 0x0000 }, /* R584 - IRQ Debounce */
+ [586] = { 0xC000, 0xC000, 0x0000 }, /* R586 - MICINT Source Pol */
+ [768] = { 0x0001, 0x0001, 0x0000 }, /* R768 - DSP2 Power Management */
+ [1037] = { 0x0000, 0x003F, 0xFFFF }, /* R1037 - DSP2_ExecControl */
+ [4096] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4096 - Write Sequencer 0 */
+ [4097] = { 0x00FF, 0x00FF, 0x0000 }, /* R4097 - Write Sequencer 1 */
+ [4098] = { 0x070F, 0x070F, 0x0000 }, /* R4098 - Write Sequencer 2 */
+ [4099] = { 0x010F, 0x010F, 0x0000 }, /* R4099 - Write Sequencer 3 */
+ [4100] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4100 - Write Sequencer 4 */
+ [4101] = { 0x00FF, 0x00FF, 0x0000 }, /* R4101 - Write Sequencer 5 */
+ [4102] = { 0x070F, 0x070F, 0x0000 }, /* R4102 - Write Sequencer 6 */
+ [4103] = { 0x010F, 0x010F, 0x0000 }, /* R4103 - Write Sequencer 7 */
+ [4104] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4104 - Write Sequencer 8 */
+ [4105] = { 0x00FF, 0x00FF, 0x0000 }, /* R4105 - Write Sequencer 9 */
+ [4106] = { 0x070F, 0x070F, 0x0000 }, /* R4106 - Write Sequencer 10 */
+ [4107] = { 0x010F, 0x010F, 0x0000 }, /* R4107 - Write Sequencer 11 */
+ [4108] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4108 - Write Sequencer 12 */
+ [4109] = { 0x00FF, 0x00FF, 0x0000 }, /* R4109 - Write Sequencer 13 */
+ [4110] = { 0x070F, 0x070F, 0x0000 }, /* R4110 - Write Sequencer 14 */
+ [4111] = { 0x010F, 0x010F, 0x0000 }, /* R4111 - Write Sequencer 15 */
+ [4112] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4112 - Write Sequencer 16 */
+ [4113] = { 0x00FF, 0x00FF, 0x0000 }, /* R4113 - Write Sequencer 17 */
+ [4114] = { 0x070F, 0x070F, 0x0000 }, /* R4114 - Write Sequencer 18 */
+ [4115] = { 0x010F, 0x010F, 0x0000 }, /* R4115 - Write Sequencer 19 */
+ [4116] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4116 - Write Sequencer 20 */
+ [4117] = { 0x00FF, 0x00FF, 0x0000 }, /* R4117 - Write Sequencer 21 */
+ [4118] = { 0x070F, 0x070F, 0x0000 }, /* R4118 - Write Sequencer 22 */
+ [4119] = { 0x010F, 0x010F, 0x0000 }, /* R4119 - Write Sequencer 23 */
+ [4120] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4120 - Write Sequencer 24 */
+ [4121] = { 0x00FF, 0x00FF, 0x0000 }, /* R4121 - Write Sequencer 25 */
+ [4122] = { 0x070F, 0x070F, 0x0000 }, /* R4122 - Write Sequencer 26 */
+ [4123] = { 0x010F, 0x010F, 0x0000 }, /* R4123 - Write Sequencer 27 */
+ [4124] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4124 - Write Sequencer 28 */
+ [4125] = { 0x00FF, 0x00FF, 0x0000 }, /* R4125 - Write Sequencer 29 */
+ [4126] = { 0x070F, 0x070F, 0x0000 }, /* R4126 - Write Sequencer 30 */
+ [4127] = { 0x010F, 0x010F, 0x0000 }, /* R4127 - Write Sequencer 31 */
+ [4128] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4128 - Write Sequencer 32 */
+ [4129] = { 0x00FF, 0x00FF, 0x0000 }, /* R4129 - Write Sequencer 33 */
+ [4130] = { 0x070F, 0x070F, 0x0000 }, /* R4130 - Write Sequencer 34 */
+ [4131] = { 0x010F, 0x010F, 0x0000 }, /* R4131 - Write Sequencer 35 */
+ [4132] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4132 - Write Sequencer 36 */
+ [4133] = { 0x00FF, 0x00FF, 0x0000 }, /* R4133 - Write Sequencer 37 */
+ [4134] = { 0x070F, 0x070F, 0x0000 }, /* R4134 - Write Sequencer 38 */
+ [4135] = { 0x010F, 0x010F, 0x0000 }, /* R4135 - Write Sequencer 39 */
+ [4136] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4136 - Write Sequencer 40 */
+ [4137] = { 0x00FF, 0x00FF, 0x0000 }, /* R4137 - Write Sequencer 41 */
+ [4138] = { 0x070F, 0x070F, 0x0000 }, /* R4138 - Write Sequencer 42 */
+ [4139] = { 0x010F, 0x010F, 0x0000 }, /* R4139 - Write Sequencer 43 */
+ [4140] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4140 - Write Sequencer 44 */
+ [4141] = { 0x00FF, 0x00FF, 0x0000 }, /* R4141 - Write Sequencer 45 */
+ [4142] = { 0x070F, 0x070F, 0x0000 }, /* R4142 - Write Sequencer 46 */
+ [4143] = { 0x010F, 0x010F, 0x0000 }, /* R4143 - Write Sequencer 47 */
+ [4144] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4144 - Write Sequencer 48 */
+ [4145] = { 0x00FF, 0x00FF, 0x0000 }, /* R4145 - Write Sequencer 49 */
+ [4146] = { 0x070F, 0x070F, 0x0000 }, /* R4146 - Write Sequencer 50 */
+ [4147] = { 0x010F, 0x010F, 0x0000 }, /* R4147 - Write Sequencer 51 */
+ [4148] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4148 - Write Sequencer 52 */
+ [4149] = { 0x00FF, 0x00FF, 0x0000 }, /* R4149 - Write Sequencer 53 */
+ [4150] = { 0x070F, 0x070F, 0x0000 }, /* R4150 - Write Sequencer 54 */
+ [4151] = { 0x010F, 0x010F, 0x0000 }, /* R4151 - Write Sequencer 55 */
+ [4152] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4152 - Write Sequencer 56 */
+ [4153] = { 0x00FF, 0x00FF, 0x0000 }, /* R4153 - Write Sequencer 57 */
+ [4154] = { 0x070F, 0x070F, 0x0000 }, /* R4154 - Write Sequencer 58 */
+ [4155] = { 0x010F, 0x010F, 0x0000 }, /* R4155 - Write Sequencer 59 */
+ [4156] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4156 - Write Sequencer 60 */
+ [4157] = { 0x00FF, 0x00FF, 0x0000 }, /* R4157 - Write Sequencer 61 */
+ [4158] = { 0x070F, 0x070F, 0x0000 }, /* R4158 - Write Sequencer 62 */
+ [4159] = { 0x010F, 0x010F, 0x0000 }, /* R4159 - Write Sequencer 63 */
+ [4160] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4160 - Write Sequencer 64 */
+ [4161] = { 0x00FF, 0x00FF, 0x0000 }, /* R4161 - Write Sequencer 65 */
+ [4162] = { 0x070F, 0x070F, 0x0000 }, /* R4162 - Write Sequencer 66 */
+ [4163] = { 0x010F, 0x010F, 0x0000 }, /* R4163 - Write Sequencer 67 */
+ [4164] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4164 - Write Sequencer 68 */
+ [4165] = { 0x00FF, 0x00FF, 0x0000 }, /* R4165 - Write Sequencer 69 */
+ [4166] = { 0x070F, 0x070F, 0x0000 }, /* R4166 - Write Sequencer 70 */
+ [4167] = { 0x010F, 0x010F, 0x0000 }, /* R4167 - Write Sequencer 71 */
+ [4168] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4168 - Write Sequencer 72 */
+ [4169] = { 0x00FF, 0x00FF, 0x0000 }, /* R4169 - Write Sequencer 73 */
+ [4170] = { 0x070F, 0x070F, 0x0000 }, /* R4170 - Write Sequencer 74 */
+ [4171] = { 0x010F, 0x010F, 0x0000 }, /* R4171 - Write Sequencer 75 */
+ [4172] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4172 - Write Sequencer 76 */
+ [4173] = { 0x00FF, 0x00FF, 0x0000 }, /* R4173 - Write Sequencer 77 */
+ [4174] = { 0x070F, 0x070F, 0x0000 }, /* R4174 - Write Sequencer 78 */
+ [4175] = { 0x010F, 0x010F, 0x0000 }, /* R4175 - Write Sequencer 79 */
+ [4176] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4176 - Write Sequencer 80 */
+ [4177] = { 0x00FF, 0x00FF, 0x0000 }, /* R4177 - Write Sequencer 81 */
+ [4178] = { 0x070F, 0x070F, 0x0000 }, /* R4178 - Write Sequencer 82 */
+ [4179] = { 0x010F, 0x010F, 0x0000 }, /* R4179 - Write Sequencer 83 */
+ [4180] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4180 - Write Sequencer 84 */
+ [4181] = { 0x00FF, 0x00FF, 0x0000 }, /* R4181 - Write Sequencer 85 */
+ [4182] = { 0x070F, 0x070F, 0x0000 }, /* R4182 - Write Sequencer 86 */
+ [4183] = { 0x010F, 0x010F, 0x0000 }, /* R4183 - Write Sequencer 87 */
+ [4184] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4184 - Write Sequencer 88 */
+ [4185] = { 0x00FF, 0x00FF, 0x0000 }, /* R4185 - Write Sequencer 89 */
+ [4186] = { 0x070F, 0x070F, 0x0000 }, /* R4186 - Write Sequencer 90 */
+ [4187] = { 0x010F, 0x010F, 0x0000 }, /* R4187 - Write Sequencer 91 */
+ [4188] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4188 - Write Sequencer 92 */
+ [4189] = { 0x00FF, 0x00FF, 0x0000 }, /* R4189 - Write Sequencer 93 */
+ [4190] = { 0x070F, 0x070F, 0x0000 }, /* R4190 - Write Sequencer 94 */
+ [4191] = { 0x010F, 0x010F, 0x0000 }, /* R4191 - Write Sequencer 95 */
+ [4192] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4192 - Write Sequencer 96 */
+ [4193] = { 0x00FF, 0x00FF, 0x0000 }, /* R4193 - Write Sequencer 97 */
+ [4194] = { 0x070F, 0x070F, 0x0000 }, /* R4194 - Write Sequencer 98 */
+ [4195] = { 0x010F, 0x010F, 0x0000 }, /* R4195 - Write Sequencer 99 */
+ [4196] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4196 - Write Sequencer 100 */
+ [4197] = { 0x00FF, 0x00FF, 0x0000 }, /* R4197 - Write Sequencer 101 */
+ [4198] = { 0x070F, 0x070F, 0x0000 }, /* R4198 - Write Sequencer 102 */
+ [4199] = { 0x010F, 0x010F, 0x0000 }, /* R4199 - Write Sequencer 103 */
+ [4200] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4200 - Write Sequencer 104 */
+ [4201] = { 0x00FF, 0x00FF, 0x0000 }, /* R4201 - Write Sequencer 105 */
+ [4202] = { 0x070F, 0x070F, 0x0000 }, /* R4202 - Write Sequencer 106 */
+ [4203] = { 0x010F, 0x010F, 0x0000 }, /* R4203 - Write Sequencer 107 */
+ [4204] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4204 - Write Sequencer 108 */
+ [4205] = { 0x00FF, 0x00FF, 0x0000 }, /* R4205 - Write Sequencer 109 */
+ [4206] = { 0x070F, 0x070F, 0x0000 }, /* R4206 - Write Sequencer 110 */
+ [4207] = { 0x010F, 0x010F, 0x0000 }, /* R4207 - Write Sequencer 111 */
+ [4208] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4208 - Write Sequencer 112 */
+ [4209] = { 0x00FF, 0x00FF, 0x0000 }, /* R4209 - Write Sequencer 113 */
+ [4210] = { 0x070F, 0x070F, 0x0000 }, /* R4210 - Write Sequencer 114 */
+ [4211] = { 0x010F, 0x010F, 0x0000 }, /* R4211 - Write Sequencer 115 */
+ [4212] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4212 - Write Sequencer 116 */
+ [4213] = { 0x00FF, 0x00FF, 0x0000 }, /* R4213 - Write Sequencer 117 */
+ [4214] = { 0x070F, 0x070F, 0x0000 }, /* R4214 - Write Sequencer 118 */
+ [4215] = { 0x010F, 0x010F, 0x0000 }, /* R4215 - Write Sequencer 119 */
+ [4216] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4216 - Write Sequencer 120 */
+ [4217] = { 0x00FF, 0x00FF, 0x0000 }, /* R4217 - Write Sequencer 121 */
+ [4218] = { 0x070F, 0x070F, 0x0000 }, /* R4218 - Write Sequencer 122 */
+ [4219] = { 0x010F, 0x010F, 0x0000 }, /* R4219 - Write Sequencer 123 */
+ [4220] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4220 - Write Sequencer 124 */
+ [4221] = { 0x00FF, 0x00FF, 0x0000 }, /* R4221 - Write Sequencer 125 */
+ [4222] = { 0x070F, 0x070F, 0x0000 }, /* R4222 - Write Sequencer 126 */
+ [4223] = { 0x010F, 0x010F, 0x0000 }, /* R4223 - Write Sequencer 127 */
+ [4224] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4224 - Write Sequencer 128 */
+ [4225] = { 0x00FF, 0x00FF, 0x0000 }, /* R4225 - Write Sequencer 129 */
+ [4226] = { 0x070F, 0x070F, 0x0000 }, /* R4226 - Write Sequencer 130 */
+ [4227] = { 0x010F, 0x010F, 0x0000 }, /* R4227 - Write Sequencer 131 */
+ [4228] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4228 - Write Sequencer 132 */
+ [4229] = { 0x00FF, 0x00FF, 0x0000 }, /* R4229 - Write Sequencer 133 */
+ [4230] = { 0x070F, 0x070F, 0x0000 }, /* R4230 - Write Sequencer 134 */
+ [4231] = { 0x010F, 0x010F, 0x0000 }, /* R4231 - Write Sequencer 135 */
+ [4232] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4232 - Write Sequencer 136 */
+ [4233] = { 0x00FF, 0x00FF, 0x0000 }, /* R4233 - Write Sequencer 137 */
+ [4234] = { 0x070F, 0x070F, 0x0000 }, /* R4234 - Write Sequencer 138 */
+ [4235] = { 0x010F, 0x010F, 0x0000 }, /* R4235 - Write Sequencer 139 */
+ [4236] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4236 - Write Sequencer 140 */
+ [4237] = { 0x00FF, 0x00FF, 0x0000 }, /* R4237 - Write Sequencer 141 */
+ [4238] = { 0x070F, 0x070F, 0x0000 }, /* R4238 - Write Sequencer 142 */
+ [4239] = { 0x010F, 0x010F, 0x0000 }, /* R4239 - Write Sequencer 143 */
+ [4240] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4240 - Write Sequencer 144 */
+ [4241] = { 0x00FF, 0x00FF, 0x0000 }, /* R4241 - Write Sequencer 145 */
+ [4242] = { 0x070F, 0x070F, 0x0000 }, /* R4242 - Write Sequencer 146 */
+ [4243] = { 0x010F, 0x010F, 0x0000 }, /* R4243 - Write Sequencer 147 */
+ [4244] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4244 - Write Sequencer 148 */
+ [4245] = { 0x00FF, 0x00FF, 0x0000 }, /* R4245 - Write Sequencer 149 */
+ [4246] = { 0x070F, 0x070F, 0x0000 }, /* R4246 - Write Sequencer 150 */
+ [4247] = { 0x010F, 0x010F, 0x0000 }, /* R4247 - Write Sequencer 151 */
+ [4248] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4248 - Write Sequencer 152 */
+ [4249] = { 0x00FF, 0x00FF, 0x0000 }, /* R4249 - Write Sequencer 153 */
+ [4250] = { 0x070F, 0x070F, 0x0000 }, /* R4250 - Write Sequencer 154 */
+ [4251] = { 0x010F, 0x010F, 0x0000 }, /* R4251 - Write Sequencer 155 */
+ [4252] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4252 - Write Sequencer 156 */
+ [4253] = { 0x00FF, 0x00FF, 0x0000 }, /* R4253 - Write Sequencer 157 */
+ [4254] = { 0x070F, 0x070F, 0x0000 }, /* R4254 - Write Sequencer 158 */
+ [4255] = { 0x010F, 0x010F, 0x0000 }, /* R4255 - Write Sequencer 159 */
+ [4256] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4256 - Write Sequencer 160 */
+ [4257] = { 0x00FF, 0x00FF, 0x0000 }, /* R4257 - Write Sequencer 161 */
+ [4258] = { 0x070F, 0x070F, 0x0000 }, /* R4258 - Write Sequencer 162 */
+ [4259] = { 0x010F, 0x010F, 0x0000 }, /* R4259 - Write Sequencer 163 */
+ [4260] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4260 - Write Sequencer 164 */
+ [4261] = { 0x00FF, 0x00FF, 0x0000 }, /* R4261 - Write Sequencer 165 */
+ [4262] = { 0x070F, 0x070F, 0x0000 }, /* R4262 - Write Sequencer 166 */
+ [4263] = { 0x010F, 0x010F, 0x0000 }, /* R4263 - Write Sequencer 167 */
+ [4264] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4264 - Write Sequencer 168 */
+ [4265] = { 0x00FF, 0x00FF, 0x0000 }, /* R4265 - Write Sequencer 169 */
+ [4266] = { 0x070F, 0x070F, 0x0000 }, /* R4266 - Write Sequencer 170 */
+ [4267] = { 0x010F, 0x010F, 0x0000 }, /* R4267 - Write Sequencer 171 */
+ [4268] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4268 - Write Sequencer 172 */
+ [4269] = { 0x00FF, 0x00FF, 0x0000 }, /* R4269 - Write Sequencer 173 */
+ [4270] = { 0x070F, 0x070F, 0x0000 }, /* R4270 - Write Sequencer 174 */
+ [4271] = { 0x010F, 0x010F, 0x0000 }, /* R4271 - Write Sequencer 175 */
+ [4272] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4272 - Write Sequencer 176 */
+ [4273] = { 0x00FF, 0x00FF, 0x0000 }, /* R4273 - Write Sequencer 177 */
+ [4274] = { 0x070F, 0x070F, 0x0000 }, /* R4274 - Write Sequencer 178 */
+ [4275] = { 0x010F, 0x010F, 0x0000 }, /* R4275 - Write Sequencer 179 */
+ [4276] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4276 - Write Sequencer 180 */
+ [4277] = { 0x00FF, 0x00FF, 0x0000 }, /* R4277 - Write Sequencer 181 */
+ [4278] = { 0x070F, 0x070F, 0x0000 }, /* R4278 - Write Sequencer 182 */
+ [4279] = { 0x010F, 0x010F, 0x0000 }, /* R4279 - Write Sequencer 183 */
+ [4280] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4280 - Write Sequencer 184 */
+ [4281] = { 0x00FF, 0x00FF, 0x0000 }, /* R4281 - Write Sequencer 185 */
+ [4282] = { 0x070F, 0x070F, 0x0000 }, /* R4282 - Write Sequencer 186 */
+ [4283] = { 0x010F, 0x010F, 0x0000 }, /* R4283 - Write Sequencer 187 */
+ [4284] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4284 - Write Sequencer 188 */
+ [4285] = { 0x00FF, 0x00FF, 0x0000 }, /* R4285 - Write Sequencer 189 */
+ [4286] = { 0x070F, 0x070F, 0x0000 }, /* R4286 - Write Sequencer 190 */
+ [4287] = { 0x010F, 0x010F, 0x0000 }, /* R4287 - Write Sequencer 191 */
+ [4288] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4288 - Write Sequencer 192 */
+ [4289] = { 0x00FF, 0x00FF, 0x0000 }, /* R4289 - Write Sequencer 193 */
+ [4290] = { 0x070F, 0x070F, 0x0000 }, /* R4290 - Write Sequencer 194 */
+ [4291] = { 0x010F, 0x010F, 0x0000 }, /* R4291 - Write Sequencer 195 */
+ [4292] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4292 - Write Sequencer 196 */
+ [4293] = { 0x00FF, 0x00FF, 0x0000 }, /* R4293 - Write Sequencer 197 */
+ [4294] = { 0x070F, 0x070F, 0x0000 }, /* R4294 - Write Sequencer 198 */
+ [4295] = { 0x010F, 0x010F, 0x0000 }, /* R4295 - Write Sequencer 199 */
+ [4296] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4296 - Write Sequencer 200 */
+ [4297] = { 0x00FF, 0x00FF, 0x0000 }, /* R4297 - Write Sequencer 201 */
+ [4298] = { 0x070F, 0x070F, 0x0000 }, /* R4298 - Write Sequencer 202 */
+ [4299] = { 0x010F, 0x010F, 0x0000 }, /* R4299 - Write Sequencer 203 */
+ [4300] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4300 - Write Sequencer 204 */
+ [4301] = { 0x00FF, 0x00FF, 0x0000 }, /* R4301 - Write Sequencer 205 */
+ [4302] = { 0x070F, 0x070F, 0x0000 }, /* R4302 - Write Sequencer 206 */
+ [4303] = { 0x010F, 0x010F, 0x0000 }, /* R4303 - Write Sequencer 207 */
+ [4304] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4304 - Write Sequencer 208 */
+ [4305] = { 0x00FF, 0x00FF, 0x0000 }, /* R4305 - Write Sequencer 209 */
+ [4306] = { 0x070F, 0x070F, 0x0000 }, /* R4306 - Write Sequencer 210 */
+ [4307] = { 0x010F, 0x010F, 0x0000 }, /* R4307 - Write Sequencer 211 */
+ [4308] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4308 - Write Sequencer 212 */
+ [4309] = { 0x00FF, 0x00FF, 0x0000 }, /* R4309 - Write Sequencer 213 */
+ [4310] = { 0x070F, 0x070F, 0x0000 }, /* R4310 - Write Sequencer 214 */
+ [4311] = { 0x010F, 0x010F, 0x0000 }, /* R4311 - Write Sequencer 215 */
+ [4312] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4312 - Write Sequencer 216 */
+ [4313] = { 0x00FF, 0x00FF, 0x0000 }, /* R4313 - Write Sequencer 217 */
+ [4314] = { 0x070F, 0x070F, 0x0000 }, /* R4314 - Write Sequencer 218 */
+ [4315] = { 0x010F, 0x010F, 0x0000 }, /* R4315 - Write Sequencer 219 */
+ [4316] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4316 - Write Sequencer 220 */
+ [4317] = { 0x00FF, 0x00FF, 0x0000 }, /* R4317 - Write Sequencer 221 */
+ [4318] = { 0x070F, 0x070F, 0x0000 }, /* R4318 - Write Sequencer 222 */
+ [4319] = { 0x010F, 0x010F, 0x0000 }, /* R4319 - Write Sequencer 223 */
+ [4320] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4320 - Write Sequencer 224 */
+ [4321] = { 0x00FF, 0x00FF, 0x0000 }, /* R4321 - Write Sequencer 225 */
+ [4322] = { 0x070F, 0x070F, 0x0000 }, /* R4322 - Write Sequencer 226 */
+ [4323] = { 0x010F, 0x010F, 0x0000 }, /* R4323 - Write Sequencer 227 */
+ [4324] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4324 - Write Sequencer 228 */
+ [4325] = { 0x00FF, 0x00FF, 0x0000 }, /* R4325 - Write Sequencer 229 */
+ [4326] = { 0x070F, 0x070F, 0x0000 }, /* R4326 - Write Sequencer 230 */
+ [4327] = { 0x010F, 0x010F, 0x0000 }, /* R4327 - Write Sequencer 231 */
+ [4328] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4328 - Write Sequencer 232 */
+ [4329] = { 0x00FF, 0x00FF, 0x0000 }, /* R4329 - Write Sequencer 233 */
+ [4330] = { 0x070F, 0x070F, 0x0000 }, /* R4330 - Write Sequencer 234 */
+ [4331] = { 0x010F, 0x010F, 0x0000 }, /* R4331 - Write Sequencer 235 */
+ [4332] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4332 - Write Sequencer 236 */
+ [4333] = { 0x00FF, 0x00FF, 0x0000 }, /* R4333 - Write Sequencer 237 */
+ [4334] = { 0x070F, 0x070F, 0x0000 }, /* R4334 - Write Sequencer 238 */
+ [4335] = { 0x010F, 0x010F, 0x0000 }, /* R4335 - Write Sequencer 239 */
+ [4336] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4336 - Write Sequencer 240 */
+ [4337] = { 0x00FF, 0x00FF, 0x0000 }, /* R4337 - Write Sequencer 241 */
+ [4338] = { 0x070F, 0x070F, 0x0000 }, /* R4338 - Write Sequencer 242 */
+ [4339] = { 0x010F, 0x010F, 0x0000 }, /* R4339 - Write Sequencer 243 */
+ [4340] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4340 - Write Sequencer 244 */
+ [4341] = { 0x00FF, 0x00FF, 0x0000 }, /* R4341 - Write Sequencer 245 */
+ [4342] = { 0x070F, 0x070F, 0x0000 }, /* R4342 - Write Sequencer 246 */
+ [4343] = { 0x010F, 0x010F, 0x0000 }, /* R4343 - Write Sequencer 247 */
+ [4344] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4344 - Write Sequencer 248 */
+ [4345] = { 0x00FF, 0x00FF, 0x0000 }, /* R4345 - Write Sequencer 249 */
+ [4346] = { 0x070F, 0x070F, 0x0000 }, /* R4346 - Write Sequencer 250 */
+ [4347] = { 0x010F, 0x010F, 0x0000 }, /* R4347 - Write Sequencer 251 */
+ [4348] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4348 - Write Sequencer 252 */
+ [4349] = { 0x00FF, 0x00FF, 0x0000 }, /* R4349 - Write Sequencer 253 */
+ [4350] = { 0x070F, 0x070F, 0x0000 }, /* R4350 - Write Sequencer 254 */
+ [4351] = { 0x010F, 0x010F, 0x0000 }, /* R4351 - Write Sequencer 255 */
+ [4352] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4352 - Write Sequencer 256 */
+ [4353] = { 0x00FF, 0x00FF, 0x0000 }, /* R4353 - Write Sequencer 257 */
+ [4354] = { 0x070F, 0x070F, 0x0000 }, /* R4354 - Write Sequencer 258 */
+ [4355] = { 0x010F, 0x010F, 0x0000 }, /* R4355 - Write Sequencer 259 */
+ [4356] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4356 - Write Sequencer 260 */
+ [4357] = { 0x00FF, 0x00FF, 0x0000 }, /* R4357 - Write Sequencer 261 */
+ [4358] = { 0x070F, 0x070F, 0x0000 }, /* R4358 - Write Sequencer 262 */
+ [4359] = { 0x010F, 0x010F, 0x0000 }, /* R4359 - Write Sequencer 263 */
+ [4360] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4360 - Write Sequencer 264 */
+ [4361] = { 0x00FF, 0x00FF, 0x0000 }, /* R4361 - Write Sequencer 265 */
+ [4362] = { 0x070F, 0x070F, 0x0000 }, /* R4362 - Write Sequencer 266 */
+ [4363] = { 0x010F, 0x010F, 0x0000 }, /* R4363 - Write Sequencer 267 */
+ [4364] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4364 - Write Sequencer 268 */
+ [4365] = { 0x00FF, 0x00FF, 0x0000 }, /* R4365 - Write Sequencer 269 */
+ [4366] = { 0x070F, 0x070F, 0x0000 }, /* R4366 - Write Sequencer 270 */
+ [4367] = { 0x010F, 0x010F, 0x0000 }, /* R4367 - Write Sequencer 271 */
+ [4368] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4368 - Write Sequencer 272 */
+ [4369] = { 0x00FF, 0x00FF, 0x0000 }, /* R4369 - Write Sequencer 273 */
+ [4370] = { 0x070F, 0x070F, 0x0000 }, /* R4370 - Write Sequencer 274 */
+ [4371] = { 0x010F, 0x010F, 0x0000 }, /* R4371 - Write Sequencer 275 */
+ [4372] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4372 - Write Sequencer 276 */
+ [4373] = { 0x00FF, 0x00FF, 0x0000 }, /* R4373 - Write Sequencer 277 */
+ [4374] = { 0x070F, 0x070F, 0x0000 }, /* R4374 - Write Sequencer 278 */
+ [4375] = { 0x010F, 0x010F, 0x0000 }, /* R4375 - Write Sequencer 279 */
+ [4376] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4376 - Write Sequencer 280 */
+ [4377] = { 0x00FF, 0x00FF, 0x0000 }, /* R4377 - Write Sequencer 281 */
+ [4378] = { 0x070F, 0x070F, 0x0000 }, /* R4378 - Write Sequencer 282 */
+ [4379] = { 0x010F, 0x010F, 0x0000 }, /* R4379 - Write Sequencer 283 */
+ [4380] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4380 - Write Sequencer 284 */
+ [4381] = { 0x00FF, 0x00FF, 0x0000 }, /* R4381 - Write Sequencer 285 */
+ [4382] = { 0x070F, 0x070F, 0x0000 }, /* R4382 - Write Sequencer 286 */
+ [4383] = { 0x010F, 0x010F, 0x0000 }, /* R4383 - Write Sequencer 287 */
+ [4384] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4384 - Write Sequencer 288 */
+ [4385] = { 0x00FF, 0x00FF, 0x0000 }, /* R4385 - Write Sequencer 289 */
+ [4386] = { 0x070F, 0x070F, 0x0000 }, /* R4386 - Write Sequencer 290 */
+ [4387] = { 0x010F, 0x010F, 0x0000 }, /* R4387 - Write Sequencer 291 */
+ [4388] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4388 - Write Sequencer 292 */
+ [4389] = { 0x00FF, 0x00FF, 0x0000 }, /* R4389 - Write Sequencer 293 */
+ [4390] = { 0x070F, 0x070F, 0x0000 }, /* R4390 - Write Sequencer 294 */
+ [4391] = { 0x010F, 0x010F, 0x0000 }, /* R4391 - Write Sequencer 295 */
+ [4392] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4392 - Write Sequencer 296 */
+ [4393] = { 0x00FF, 0x00FF, 0x0000 }, /* R4393 - Write Sequencer 297 */
+ [4394] = { 0x070F, 0x070F, 0x0000 }, /* R4394 - Write Sequencer 298 */
+ [4395] = { 0x010F, 0x010F, 0x0000 }, /* R4395 - Write Sequencer 299 */
+ [4396] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4396 - Write Sequencer 300 */
+ [4397] = { 0x00FF, 0x00FF, 0x0000 }, /* R4397 - Write Sequencer 301 */
+ [4398] = { 0x070F, 0x070F, 0x0000 }, /* R4398 - Write Sequencer 302 */
+ [4399] = { 0x010F, 0x010F, 0x0000 }, /* R4399 - Write Sequencer 303 */
+ [4400] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4400 - Write Sequencer 304 */
+ [4401] = { 0x00FF, 0x00FF, 0x0000 }, /* R4401 - Write Sequencer 305 */
+ [4402] = { 0x070F, 0x070F, 0x0000 }, /* R4402 - Write Sequencer 306 */
+ [4403] = { 0x010F, 0x010F, 0x0000 }, /* R4403 - Write Sequencer 307 */
+ [4404] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4404 - Write Sequencer 308 */
+ [4405] = { 0x00FF, 0x00FF, 0x0000 }, /* R4405 - Write Sequencer 309 */
+ [4406] = { 0x070F, 0x070F, 0x0000 }, /* R4406 - Write Sequencer 310 */
+ [4407] = { 0x010F, 0x010F, 0x0000 }, /* R4407 - Write Sequencer 311 */
+ [4408] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4408 - Write Sequencer 312 */
+ [4409] = { 0x00FF, 0x00FF, 0x0000 }, /* R4409 - Write Sequencer 313 */
+ [4410] = { 0x070F, 0x070F, 0x0000 }, /* R4410 - Write Sequencer 314 */
+ [4411] = { 0x010F, 0x010F, 0x0000 }, /* R4411 - Write Sequencer 315 */
+ [4412] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4412 - Write Sequencer 316 */
+ [4413] = { 0x00FF, 0x00FF, 0x0000 }, /* R4413 - Write Sequencer 317 */
+ [4414] = { 0x070F, 0x070F, 0x0000 }, /* R4414 - Write Sequencer 318 */
+ [4415] = { 0x010F, 0x010F, 0x0000 }, /* R4415 - Write Sequencer 319 */
+ [4416] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4416 - Write Sequencer 320 */
+ [4417] = { 0x00FF, 0x00FF, 0x0000 }, /* R4417 - Write Sequencer 321 */
+ [4418] = { 0x070F, 0x070F, 0x0000 }, /* R4418 - Write Sequencer 322 */
+ [4419] = { 0x010F, 0x010F, 0x0000 }, /* R4419 - Write Sequencer 323 */
+ [4420] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4420 - Write Sequencer 324 */
+ [4421] = { 0x00FF, 0x00FF, 0x0000 }, /* R4421 - Write Sequencer 325 */
+ [4422] = { 0x070F, 0x070F, 0x0000 }, /* R4422 - Write Sequencer 326 */
+ [4423] = { 0x010F, 0x010F, 0x0000 }, /* R4423 - Write Sequencer 327 */
+ [4424] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4424 - Write Sequencer 328 */
+ [4425] = { 0x00FF, 0x00FF, 0x0000 }, /* R4425 - Write Sequencer 329 */
+ [4426] = { 0x070F, 0x070F, 0x0000 }, /* R4426 - Write Sequencer 330 */
+ [4427] = { 0x010F, 0x010F, 0x0000 }, /* R4427 - Write Sequencer 331 */
+ [4428] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4428 - Write Sequencer 332 */
+ [4429] = { 0x00FF, 0x00FF, 0x0000 }, /* R4429 - Write Sequencer 333 */
+ [4430] = { 0x070F, 0x070F, 0x0000 }, /* R4430 - Write Sequencer 334 */
+ [4431] = { 0x010F, 0x010F, 0x0000 }, /* R4431 - Write Sequencer 335 */
+ [4432] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4432 - Write Sequencer 336 */
+ [4433] = { 0x00FF, 0x00FF, 0x0000 }, /* R4433 - Write Sequencer 337 */
+ [4434] = { 0x070F, 0x070F, 0x0000 }, /* R4434 - Write Sequencer 338 */
+ [4435] = { 0x010F, 0x010F, 0x0000 }, /* R4435 - Write Sequencer 339 */
+ [4436] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4436 - Write Sequencer 340 */
+ [4437] = { 0x00FF, 0x00FF, 0x0000 }, /* R4437 - Write Sequencer 341 */
+ [4438] = { 0x070F, 0x070F, 0x0000 }, /* R4438 - Write Sequencer 342 */
+ [4439] = { 0x010F, 0x010F, 0x0000 }, /* R4439 - Write Sequencer 343 */
+ [4440] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4440 - Write Sequencer 344 */
+ [4441] = { 0x00FF, 0x00FF, 0x0000 }, /* R4441 - Write Sequencer 345 */
+ [4442] = { 0x070F, 0x070F, 0x0000 }, /* R4442 - Write Sequencer 346 */
+ [4443] = { 0x010F, 0x010F, 0x0000 }, /* R4443 - Write Sequencer 347 */
+ [4444] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4444 - Write Sequencer 348 */
+ [4445] = { 0x00FF, 0x00FF, 0x0000 }, /* R4445 - Write Sequencer 349 */
+ [4446] = { 0x070F, 0x070F, 0x0000 }, /* R4446 - Write Sequencer 350 */
+ [4447] = { 0x010F, 0x010F, 0x0000 }, /* R4447 - Write Sequencer 351 */
+ [4448] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4448 - Write Sequencer 352 */
+ [4449] = { 0x00FF, 0x00FF, 0x0000 }, /* R4449 - Write Sequencer 353 */
+ [4450] = { 0x070F, 0x070F, 0x0000 }, /* R4450 - Write Sequencer 354 */
+ [4451] = { 0x010F, 0x010F, 0x0000 }, /* R4451 - Write Sequencer 355 */
+ [4452] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4452 - Write Sequencer 356 */
+ [4453] = { 0x00FF, 0x00FF, 0x0000 }, /* R4453 - Write Sequencer 357 */
+ [4454] = { 0x070F, 0x070F, 0x0000 }, /* R4454 - Write Sequencer 358 */
+ [4455] = { 0x010F, 0x010F, 0x0000 }, /* R4455 - Write Sequencer 359 */
+ [4456] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4456 - Write Sequencer 360 */
+ [4457] = { 0x00FF, 0x00FF, 0x0000 }, /* R4457 - Write Sequencer 361 */
+ [4458] = { 0x070F, 0x070F, 0x0000 }, /* R4458 - Write Sequencer 362 */
+ [4459] = { 0x010F, 0x010F, 0x0000 }, /* R4459 - Write Sequencer 363 */
+ [4460] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4460 - Write Sequencer 364 */
+ [4461] = { 0x00FF, 0x00FF, 0x0000 }, /* R4461 - Write Sequencer 365 */
+ [4462] = { 0x070F, 0x070F, 0x0000 }, /* R4462 - Write Sequencer 366 */
+ [4463] = { 0x010F, 0x010F, 0x0000 }, /* R4463 - Write Sequencer 367 */
+ [4464] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4464 - Write Sequencer 368 */
+ [4465] = { 0x00FF, 0x00FF, 0x0000 }, /* R4465 - Write Sequencer 369 */
+ [4466] = { 0x070F, 0x070F, 0x0000 }, /* R4466 - Write Sequencer 370 */
+ [4467] = { 0x010F, 0x010F, 0x0000 }, /* R4467 - Write Sequencer 371 */
+ [4468] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4468 - Write Sequencer 372 */
+ [4469] = { 0x00FF, 0x00FF, 0x0000 }, /* R4469 - Write Sequencer 373 */
+ [4470] = { 0x070F, 0x070F, 0x0000 }, /* R4470 - Write Sequencer 374 */
+ [4471] = { 0x010F, 0x010F, 0x0000 }, /* R4471 - Write Sequencer 375 */
+ [4472] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4472 - Write Sequencer 376 */
+ [4473] = { 0x00FF, 0x00FF, 0x0000 }, /* R4473 - Write Sequencer 377 */
+ [4474] = { 0x070F, 0x070F, 0x0000 }, /* R4474 - Write Sequencer 378 */
+ [4475] = { 0x010F, 0x010F, 0x0000 }, /* R4475 - Write Sequencer 379 */
+ [4476] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4476 - Write Sequencer 380 */
+ [4477] = { 0x00FF, 0x00FF, 0x0000 }, /* R4477 - Write Sequencer 381 */
+ [4478] = { 0x070F, 0x070F, 0x0000 }, /* R4478 - Write Sequencer 382 */
+ [4479] = { 0x010F, 0x010F, 0x0000 }, /* R4479 - Write Sequencer 383 */
+ [4480] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4480 - Write Sequencer 384 */
+ [4481] = { 0x00FF, 0x00FF, 0x0000 }, /* R4481 - Write Sequencer 385 */
+ [4482] = { 0x070F, 0x070F, 0x0000 }, /* R4482 - Write Sequencer 386 */
+ [4483] = { 0x010F, 0x010F, 0x0000 }, /* R4483 - Write Sequencer 387 */
+ [4484] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4484 - Write Sequencer 388 */
+ [4485] = { 0x00FF, 0x00FF, 0x0000 }, /* R4485 - Write Sequencer 389 */
+ [4486] = { 0x070F, 0x070F, 0x0000 }, /* R4486 - Write Sequencer 390 */
+ [4487] = { 0x010F, 0x010F, 0x0000 }, /* R4487 - Write Sequencer 391 */
+ [4488] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4488 - Write Sequencer 392 */
+ [4489] = { 0x00FF, 0x00FF, 0x0000 }, /* R4489 - Write Sequencer 393 */
+ [4490] = { 0x070F, 0x070F, 0x0000 }, /* R4490 - Write Sequencer 394 */
+ [4491] = { 0x010F, 0x010F, 0x0000 }, /* R4491 - Write Sequencer 395 */
+ [4492] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4492 - Write Sequencer 396 */
+ [4493] = { 0x00FF, 0x00FF, 0x0000 }, /* R4493 - Write Sequencer 397 */
+ [4494] = { 0x070F, 0x070F, 0x0000 }, /* R4494 - Write Sequencer 398 */
+ [4495] = { 0x010F, 0x010F, 0x0000 }, /* R4495 - Write Sequencer 399 */
+ [4496] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4496 - Write Sequencer 400 */
+ [4497] = { 0x00FF, 0x00FF, 0x0000 }, /* R4497 - Write Sequencer 401 */
+ [4498] = { 0x070F, 0x070F, 0x0000 }, /* R4498 - Write Sequencer 402 */
+ [4499] = { 0x010F, 0x010F, 0x0000 }, /* R4499 - Write Sequencer 403 */
+ [4500] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4500 - Write Sequencer 404 */
+ [4501] = { 0x00FF, 0x00FF, 0x0000 }, /* R4501 - Write Sequencer 405 */
+ [4502] = { 0x070F, 0x070F, 0x0000 }, /* R4502 - Write Sequencer 406 */
+ [4503] = { 0x010F, 0x010F, 0x0000 }, /* R4503 - Write Sequencer 407 */
+ [4504] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4504 - Write Sequencer 408 */
+ [4505] = { 0x00FF, 0x00FF, 0x0000 }, /* R4505 - Write Sequencer 409 */
+ [4506] = { 0x070F, 0x070F, 0x0000 }, /* R4506 - Write Sequencer 410 */
+ [4507] = { 0x010F, 0x010F, 0x0000 }, /* R4507 - Write Sequencer 411 */
+ [4508] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4508 - Write Sequencer 412 */
+ [4509] = { 0x00FF, 0x00FF, 0x0000 }, /* R4509 - Write Sequencer 413 */
+ [4510] = { 0x070F, 0x070F, 0x0000 }, /* R4510 - Write Sequencer 414 */
+ [4511] = { 0x010F, 0x010F, 0x0000 }, /* R4511 - Write Sequencer 415 */
+ [4512] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4512 - Write Sequencer 416 */
+ [4513] = { 0x00FF, 0x00FF, 0x0000 }, /* R4513 - Write Sequencer 417 */
+ [4514] = { 0x070F, 0x070F, 0x0000 }, /* R4514 - Write Sequencer 418 */
+ [4515] = { 0x010F, 0x010F, 0x0000 }, /* R4515 - Write Sequencer 419 */
+ [4516] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4516 - Write Sequencer 420 */
+ [4517] = { 0x00FF, 0x00FF, 0x0000 }, /* R4517 - Write Sequencer 421 */
+ [4518] = { 0x070F, 0x070F, 0x0000 }, /* R4518 - Write Sequencer 422 */
+ [4519] = { 0x010F, 0x010F, 0x0000 }, /* R4519 - Write Sequencer 423 */
+ [4520] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4520 - Write Sequencer 424 */
+ [4521] = { 0x00FF, 0x00FF, 0x0000 }, /* R4521 - Write Sequencer 425 */
+ [4522] = { 0x070F, 0x070F, 0x0000 }, /* R4522 - Write Sequencer 426 */
+ [4523] = { 0x010F, 0x010F, 0x0000 }, /* R4523 - Write Sequencer 427 */
+ [4524] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4524 - Write Sequencer 428 */
+ [4525] = { 0x00FF, 0x00FF, 0x0000 }, /* R4525 - Write Sequencer 429 */
+ [4526] = { 0x070F, 0x070F, 0x0000 }, /* R4526 - Write Sequencer 430 */
+ [4527] = { 0x010F, 0x010F, 0x0000 }, /* R4527 - Write Sequencer 431 */
+ [4528] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4528 - Write Sequencer 432 */
+ [4529] = { 0x00FF, 0x00FF, 0x0000 }, /* R4529 - Write Sequencer 433 */
+ [4530] = { 0x070F, 0x070F, 0x0000 }, /* R4530 - Write Sequencer 434 */
+ [4531] = { 0x010F, 0x010F, 0x0000 }, /* R4531 - Write Sequencer 435 */
+ [4532] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4532 - Write Sequencer 436 */
+ [4533] = { 0x00FF, 0x00FF, 0x0000 }, /* R4533 - Write Sequencer 437 */
+ [4534] = { 0x070F, 0x070F, 0x0000 }, /* R4534 - Write Sequencer 438 */
+ [4535] = { 0x010F, 0x010F, 0x0000 }, /* R4535 - Write Sequencer 439 */
+ [4536] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4536 - Write Sequencer 440 */
+ [4537] = { 0x00FF, 0x00FF, 0x0000 }, /* R4537 - Write Sequencer 441 */
+ [4538] = { 0x070F, 0x070F, 0x0000 }, /* R4538 - Write Sequencer 442 */
+ [4539] = { 0x010F, 0x010F, 0x0000 }, /* R4539 - Write Sequencer 443 */
+ [4540] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4540 - Write Sequencer 444 */
+ [4541] = { 0x00FF, 0x00FF, 0x0000 }, /* R4541 - Write Sequencer 445 */
+ [4542] = { 0x070F, 0x070F, 0x0000 }, /* R4542 - Write Sequencer 446 */
+ [4543] = { 0x010F, 0x010F, 0x0000 }, /* R4543 - Write Sequencer 447 */
+ [4544] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4544 - Write Sequencer 448 */
+ [4545] = { 0x00FF, 0x00FF, 0x0000 }, /* R4545 - Write Sequencer 449 */
+ [4546] = { 0x070F, 0x070F, 0x0000 }, /* R4546 - Write Sequencer 450 */
+ [4547] = { 0x010F, 0x010F, 0x0000 }, /* R4547 - Write Sequencer 451 */
+ [4548] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4548 - Write Sequencer 452 */
+ [4549] = { 0x00FF, 0x00FF, 0x0000 }, /* R4549 - Write Sequencer 453 */
+ [4550] = { 0x070F, 0x070F, 0x0000 }, /* R4550 - Write Sequencer 454 */
+ [4551] = { 0x010F, 0x010F, 0x0000 }, /* R4551 - Write Sequencer 455 */
+ [4552] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4552 - Write Sequencer 456 */
+ [4553] = { 0x00FF, 0x00FF, 0x0000 }, /* R4553 - Write Sequencer 457 */
+ [4554] = { 0x070F, 0x070F, 0x0000 }, /* R4554 - Write Sequencer 458 */
+ [4555] = { 0x010F, 0x010F, 0x0000 }, /* R4555 - Write Sequencer 459 */
+ [4556] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4556 - Write Sequencer 460 */
+ [4557] = { 0x00FF, 0x00FF, 0x0000 }, /* R4557 - Write Sequencer 461 */
+ [4558] = { 0x070F, 0x070F, 0x0000 }, /* R4558 - Write Sequencer 462 */
+ [4559] = { 0x010F, 0x010F, 0x0000 }, /* R4559 - Write Sequencer 463 */
+ [4560] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4560 - Write Sequencer 464 */
+ [4561] = { 0x00FF, 0x00FF, 0x0000 }, /* R4561 - Write Sequencer 465 */
+ [4562] = { 0x070F, 0x070F, 0x0000 }, /* R4562 - Write Sequencer 466 */
+ [4563] = { 0x010F, 0x010F, 0x0000 }, /* R4563 - Write Sequencer 467 */
+ [4564] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4564 - Write Sequencer 468 */
+ [4565] = { 0x00FF, 0x00FF, 0x0000 }, /* R4565 - Write Sequencer 469 */
+ [4566] = { 0x070F, 0x070F, 0x0000 }, /* R4566 - Write Sequencer 470 */
+ [4567] = { 0x010F, 0x010F, 0x0000 }, /* R4567 - Write Sequencer 471 */
+ [4568] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4568 - Write Sequencer 472 */
+ [4569] = { 0x00FF, 0x00FF, 0x0000 }, /* R4569 - Write Sequencer 473 */
+ [4570] = { 0x070F, 0x070F, 0x0000 }, /* R4570 - Write Sequencer 474 */
+ [4571] = { 0x010F, 0x010F, 0x0000 }, /* R4571 - Write Sequencer 475 */
+ [4572] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4572 - Write Sequencer 476 */
+ [4573] = { 0x00FF, 0x00FF, 0x0000 }, /* R4573 - Write Sequencer 477 */
+ [4574] = { 0x070F, 0x070F, 0x0000 }, /* R4574 - Write Sequencer 478 */
+ [4575] = { 0x010F, 0x010F, 0x0000 }, /* R4575 - Write Sequencer 479 */
+ [4576] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4576 - Write Sequencer 480 */
+ [4577] = { 0x00FF, 0x00FF, 0x0000 }, /* R4577 - Write Sequencer 481 */
+ [4578] = { 0x070F, 0x070F, 0x0000 }, /* R4578 - Write Sequencer 482 */
+ [4579] = { 0x010F, 0x010F, 0x0000 }, /* R4579 - Write Sequencer 483 */
+ [4580] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4580 - Write Sequencer 484 */
+ [4581] = { 0x00FF, 0x00FF, 0x0000 }, /* R4581 - Write Sequencer 485 */
+ [4582] = { 0x070F, 0x070F, 0x0000 }, /* R4582 - Write Sequencer 486 */
+ [4583] = { 0x010F, 0x010F, 0x0000 }, /* R4583 - Write Sequencer 487 */
+ [4584] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4584 - Write Sequencer 488 */
+ [4585] = { 0x00FF, 0x00FF, 0x0000 }, /* R4585 - Write Sequencer 489 */
+ [4586] = { 0x070F, 0x070F, 0x0000 }, /* R4586 - Write Sequencer 490 */
+ [4587] = { 0x010F, 0x010F, 0x0000 }, /* R4587 - Write Sequencer 491 */
+ [4588] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4588 - Write Sequencer 492 */
+ [4589] = { 0x00FF, 0x00FF, 0x0000 }, /* R4589 - Write Sequencer 493 */
+ [4590] = { 0x070F, 0x070F, 0x0000 }, /* R4590 - Write Sequencer 494 */
+ [4591] = { 0x010F, 0x010F, 0x0000 }, /* R4591 - Write Sequencer 495 */
+ [4592] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4592 - Write Sequencer 496 */
+ [4593] = { 0x00FF, 0x00FF, 0x0000 }, /* R4593 - Write Sequencer 497 */
+ [4594] = { 0x070F, 0x070F, 0x0000 }, /* R4594 - Write Sequencer 498 */
+ [4595] = { 0x010F, 0x010F, 0x0000 }, /* R4595 - Write Sequencer 499 */
+ [4596] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4596 - Write Sequencer 500 */
+ [4597] = { 0x00FF, 0x00FF, 0x0000 }, /* R4597 - Write Sequencer 501 */
+ [4598] = { 0x070F, 0x070F, 0x0000 }, /* R4598 - Write Sequencer 502 */
+ [4599] = { 0x010F, 0x010F, 0x0000 }, /* R4599 - Write Sequencer 503 */
+ [4600] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4600 - Write Sequencer 504 */
+ [4601] = { 0x00FF, 0x00FF, 0x0000 }, /* R4601 - Write Sequencer 505 */
+ [4602] = { 0x070F, 0x070F, 0x0000 }, /* R4602 - Write Sequencer 506 */
+ [4603] = { 0x010F, 0x010F, 0x0000 }, /* R4603 - Write Sequencer 507 */
+ [4604] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4604 - Write Sequencer 508 */
+ [4605] = { 0x00FF, 0x00FF, 0x0000 }, /* R4605 - Write Sequencer 509 */
+ [4606] = { 0x070F, 0x070F, 0x0000 }, /* R4606 - Write Sequencer 510 */
+ [4607] = { 0x010F, 0x010F, 0x0000 }, /* R4607 - Write Sequencer 511 */
+ [8192] = { 0x03FF, 0x03FF, 0x0000 }, /* R8192 - DSP2 Instruction RAM 0 */
+ [9216] = { 0x003F, 0x003F, 0x0000 }, /* R9216 - DSP2 Address RAM 2 */
+ [9217] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R9217 - DSP2 Address RAM 1 */
+ [9218] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R9218 - DSP2 Address RAM 0 */
+ [12288] = { 0x00FF, 0x00FF, 0x0000 }, /* R12288 - DSP2 Data1 RAM 1 */
+ [12289] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R12289 - DSP2 Data1 RAM 0 */
+ [13312] = { 0x00FF, 0x00FF, 0x0000 }, /* R13312 - DSP2 Data2 RAM 1 */
+ [13313] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R13313 - DSP2 Data2 RAM 0 */
+ [14336] = { 0x00FF, 0x00FF, 0x0000 }, /* R14336 - DSP2 Data3 RAM 1 */
+ [14337] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R14337 - DSP2 Data3 RAM 0 */
+ [15360] = { 0x07FF, 0x07FF, 0x0000 }, /* R15360 - DSP2 Coeff RAM 0 */
+ [16384] = { 0x00FF, 0x00FF, 0x0000 }, /* R16384 - RETUNEADC_SHARED_COEFF_1 */
+ [16385] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16385 - RETUNEADC_SHARED_COEFF_0 */
+ [16386] = { 0x00FF, 0x00FF, 0x0000 }, /* R16386 - RETUNEDAC_SHARED_COEFF_1 */
+ [16387] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16387 - RETUNEDAC_SHARED_COEFF_0 */
+ [16388] = { 0x00FF, 0x00FF, 0x0000 }, /* R16388 - SOUNDSTAGE_ENABLES_1 */
+ [16389] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16389 - SOUNDSTAGE_ENABLES_0 */
+ [16896] = { 0x00FF, 0x00FF, 0x0000 }, /* R16896 - HDBASS_AI_1 */
+ [16897] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16897 - HDBASS_AI_0 */
+ [16898] = { 0x00FF, 0x00FF, 0x0000 }, /* R16898 - HDBASS_AR_1 */
+ [16899] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16899 - HDBASS_AR_0 */
+ [16900] = { 0x00FF, 0x00FF, 0x0000 }, /* R16900 - HDBASS_B_1 */
+ [16901] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16901 - HDBASS_B_0 */
+ [16902] = { 0x00FF, 0x00FF, 0x0000 }, /* R16902 - HDBASS_K_1 */
+ [16903] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16903 - HDBASS_K_0 */
+ [16904] = { 0x00FF, 0x00FF, 0x0000 }, /* R16904 - HDBASS_N1_1 */
+ [16905] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16905 - HDBASS_N1_0 */
+ [16906] = { 0x00FF, 0x00FF, 0x0000 }, /* R16906 - HDBASS_N2_1 */
+ [16907] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16907 - HDBASS_N2_0 */
+ [16908] = { 0x00FF, 0x00FF, 0x0000 }, /* R16908 - HDBASS_N3_1 */
+ [16909] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16909 - HDBASS_N3_0 */
+ [16910] = { 0x00FF, 0x00FF, 0x0000 }, /* R16910 - HDBASS_N4_1 */
+ [16911] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16911 - HDBASS_N4_0 */
+ [16912] = { 0x00FF, 0x00FF, 0x0000 }, /* R16912 - HDBASS_N5_1 */
+ [16913] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16913 - HDBASS_N5_0 */
+ [16914] = { 0x00FF, 0x00FF, 0x0000 }, /* R16914 - HDBASS_X1_1 */
+ [16915] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16915 - HDBASS_X1_0 */
+ [16916] = { 0x00FF, 0x00FF, 0x0000 }, /* R16916 - HDBASS_X2_1 */
+ [16917] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16917 - HDBASS_X2_0 */
+ [16918] = { 0x00FF, 0x00FF, 0x0000 }, /* R16918 - HDBASS_X3_1 */
+ [16919] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16919 - HDBASS_X3_0 */
+ [16920] = { 0x00FF, 0x00FF, 0x0000 }, /* R16920 - HDBASS_ATK_1 */
+ [16921] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16921 - HDBASS_ATK_0 */
+ [16922] = { 0x00FF, 0x00FF, 0x0000 }, /* R16922 - HDBASS_DCY_1 */
+ [16923] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16923 - HDBASS_DCY_0 */
+ [16924] = { 0x00FF, 0x00FF, 0x0000 }, /* R16924 - HDBASS_PG_1 */
+ [16925] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16925 - HDBASS_PG_0 */
+ [17408] = { 0x00FF, 0x00FF, 0x0000 }, /* R17408 - HPF_C_1 */
+ [17409] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17409 - HPF_C_0 */
+ [17920] = { 0x00FF, 0x00FF, 0x0000 }, /* R17920 - ADCL_RETUNE_C1_1 */
+ [17921] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17921 - ADCL_RETUNE_C1_0 */
+ [17922] = { 0x00FF, 0x00FF, 0x0000 }, /* R17922 - ADCL_RETUNE_C2_1 */
+ [17923] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17923 - ADCL_RETUNE_C2_0 */
+ [17924] = { 0x00FF, 0x00FF, 0x0000 }, /* R17924 - ADCL_RETUNE_C3_1 */
+ [17925] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17925 - ADCL_RETUNE_C3_0 */
+ [17926] = { 0x00FF, 0x00FF, 0x0000 }, /* R17926 - ADCL_RETUNE_C4_1 */
+ [17927] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17927 - ADCL_RETUNE_C4_0 */
+ [17928] = { 0x00FF, 0x00FF, 0x0000 }, /* R17928 - ADCL_RETUNE_C5_1 */
+ [17929] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17929 - ADCL_RETUNE_C5_0 */
+ [17930] = { 0x00FF, 0x00FF, 0x0000 }, /* R17930 - ADCL_RETUNE_C6_1 */
+ [17931] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17931 - ADCL_RETUNE_C6_0 */
+ [17932] = { 0x00FF, 0x00FF, 0x0000 }, /* R17932 - ADCL_RETUNE_C7_1 */
+ [17933] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17933 - ADCL_RETUNE_C7_0 */
+ [17934] = { 0x00FF, 0x00FF, 0x0000 }, /* R17934 - ADCL_RETUNE_C8_1 */
+ [17935] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17935 - ADCL_RETUNE_C8_0 */
+ [17936] = { 0x00FF, 0x00FF, 0x0000 }, /* R17936 - ADCL_RETUNE_C9_1 */
+ [17937] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17937 - ADCL_RETUNE_C9_0 */
+ [17938] = { 0x00FF, 0x00FF, 0x0000 }, /* R17938 - ADCL_RETUNE_C10_1 */
+ [17939] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17939 - ADCL_RETUNE_C10_0 */
+ [17940] = { 0x00FF, 0x00FF, 0x0000 }, /* R17940 - ADCL_RETUNE_C11_1 */
+ [17941] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17941 - ADCL_RETUNE_C11_0 */
+ [17942] = { 0x00FF, 0x00FF, 0x0000 }, /* R17942 - ADCL_RETUNE_C12_1 */
+ [17943] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17943 - ADCL_RETUNE_C12_0 */
+ [17944] = { 0x00FF, 0x00FF, 0x0000 }, /* R17944 - ADCL_RETUNE_C13_1 */
+ [17945] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17945 - ADCL_RETUNE_C13_0 */
+ [17946] = { 0x00FF, 0x00FF, 0x0000 }, /* R17946 - ADCL_RETUNE_C14_1 */
+ [17947] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17947 - ADCL_RETUNE_C14_0 */
+ [17948] = { 0x00FF, 0x00FF, 0x0000 }, /* R17948 - ADCL_RETUNE_C15_1 */
+ [17949] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17949 - ADCL_RETUNE_C15_0 */
+ [17950] = { 0x00FF, 0x00FF, 0x0000 }, /* R17950 - ADCL_RETUNE_C16_1 */
+ [17951] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17951 - ADCL_RETUNE_C16_0 */
+ [17952] = { 0x00FF, 0x00FF, 0x0000 }, /* R17952 - ADCL_RETUNE_C17_1 */
+ [17953] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17953 - ADCL_RETUNE_C17_0 */
+ [17954] = { 0x00FF, 0x00FF, 0x0000 }, /* R17954 - ADCL_RETUNE_C18_1 */
+ [17955] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17955 - ADCL_RETUNE_C18_0 */
+ [17956] = { 0x00FF, 0x00FF, 0x0000 }, /* R17956 - ADCL_RETUNE_C19_1 */
+ [17957] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17957 - ADCL_RETUNE_C19_0 */
+ [17958] = { 0x00FF, 0x00FF, 0x0000 }, /* R17958 - ADCL_RETUNE_C20_1 */
+ [17959] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17959 - ADCL_RETUNE_C20_0 */
+ [17960] = { 0x00FF, 0x00FF, 0x0000 }, /* R17960 - ADCL_RETUNE_C21_1 */
+ [17961] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17961 - ADCL_RETUNE_C21_0 */
+ [17962] = { 0x00FF, 0x00FF, 0x0000 }, /* R17962 - ADCL_RETUNE_C22_1 */
+ [17963] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17963 - ADCL_RETUNE_C22_0 */
+ [17964] = { 0x00FF, 0x00FF, 0x0000 }, /* R17964 - ADCL_RETUNE_C23_1 */
+ [17965] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17965 - ADCL_RETUNE_C23_0 */
+ [17966] = { 0x00FF, 0x00FF, 0x0000 }, /* R17966 - ADCL_RETUNE_C24_1 */
+ [17967] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17967 - ADCL_RETUNE_C24_0 */
+ [17968] = { 0x00FF, 0x00FF, 0x0000 }, /* R17968 - ADCL_RETUNE_C25_1 */
+ [17969] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17969 - ADCL_RETUNE_C25_0 */
+ [17970] = { 0x00FF, 0x00FF, 0x0000 }, /* R17970 - ADCL_RETUNE_C26_1 */
+ [17971] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17971 - ADCL_RETUNE_C26_0 */
+ [17972] = { 0x00FF, 0x00FF, 0x0000 }, /* R17972 - ADCL_RETUNE_C27_1 */
+ [17973] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17973 - ADCL_RETUNE_C27_0 */
+ [17974] = { 0x00FF, 0x00FF, 0x0000 }, /* R17974 - ADCL_RETUNE_C28_1 */
+ [17975] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17975 - ADCL_RETUNE_C28_0 */
+ [17976] = { 0x00FF, 0x00FF, 0x0000 }, /* R17976 - ADCL_RETUNE_C29_1 */
+ [17977] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17977 - ADCL_RETUNE_C29_0 */
+ [17978] = { 0x00FF, 0x00FF, 0x0000 }, /* R17978 - ADCL_RETUNE_C30_1 */
+ [17979] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17979 - ADCL_RETUNE_C30_0 */
+ [17980] = { 0x00FF, 0x00FF, 0x0000 }, /* R17980 - ADCL_RETUNE_C31_1 */
+ [17981] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17981 - ADCL_RETUNE_C31_0 */
+ [17982] = { 0x00FF, 0x00FF, 0x0000 }, /* R17982 - ADCL_RETUNE_C32_1 */
+ [17983] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17983 - ADCL_RETUNE_C32_0 */
+ [18432] = { 0x00FF, 0x00FF, 0x0000 }, /* R18432 - RETUNEADC_PG2_1 */
+ [18433] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18433 - RETUNEADC_PG2_0 */
+ [18434] = { 0x00FF, 0x00FF, 0x0000 }, /* R18434 - RETUNEADC_PG_1 */
+ [18435] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18435 - RETUNEADC_PG_0 */
+ [18944] = { 0x00FF, 0x00FF, 0x0000 }, /* R18944 - ADCR_RETUNE_C1_1 */
+ [18945] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18945 - ADCR_RETUNE_C1_0 */
+ [18946] = { 0x00FF, 0x00FF, 0x0000 }, /* R18946 - ADCR_RETUNE_C2_1 */
+ [18947] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18947 - ADCR_RETUNE_C2_0 */
+ [18948] = { 0x00FF, 0x00FF, 0x0000 }, /* R18948 - ADCR_RETUNE_C3_1 */
+ [18949] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18949 - ADCR_RETUNE_C3_0 */
+ [18950] = { 0x00FF, 0x00FF, 0x0000 }, /* R18950 - ADCR_RETUNE_C4_1 */
+ [18951] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18951 - ADCR_RETUNE_C4_0 */
+ [18952] = { 0x00FF, 0x00FF, 0x0000 }, /* R18952 - ADCR_RETUNE_C5_1 */
+ [18953] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18953 - ADCR_RETUNE_C5_0 */
+ [18954] = { 0x00FF, 0x00FF, 0x0000 }, /* R18954 - ADCR_RETUNE_C6_1 */
+ [18955] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18955 - ADCR_RETUNE_C6_0 */
+ [18956] = { 0x00FF, 0x00FF, 0x0000 }, /* R18956 - ADCR_RETUNE_C7_1 */
+ [18957] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18957 - ADCR_RETUNE_C7_0 */
+ [18958] = { 0x00FF, 0x00FF, 0x0000 }, /* R18958 - ADCR_RETUNE_C8_1 */
+ [18959] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18959 - ADCR_RETUNE_C8_0 */
+ [18960] = { 0x00FF, 0x00FF, 0x0000 }, /* R18960 - ADCR_RETUNE_C9_1 */
+ [18961] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18961 - ADCR_RETUNE_C9_0 */
+ [18962] = { 0x00FF, 0x00FF, 0x0000 }, /* R18962 - ADCR_RETUNE_C10_1 */
+ [18963] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18963 - ADCR_RETUNE_C10_0 */
+ [18964] = { 0x00FF, 0x00FF, 0x0000 }, /* R18964 - ADCR_RETUNE_C11_1 */
+ [18965] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18965 - ADCR_RETUNE_C11_0 */
+ [18966] = { 0x00FF, 0x00FF, 0x0000 }, /* R18966 - ADCR_RETUNE_C12_1 */
+ [18967] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18967 - ADCR_RETUNE_C12_0 */
+ [18968] = { 0x00FF, 0x00FF, 0x0000 }, /* R18968 - ADCR_RETUNE_C13_1 */
+ [18969] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18969 - ADCR_RETUNE_C13_0 */
+ [18970] = { 0x00FF, 0x00FF, 0x0000 }, /* R18970 - ADCR_RETUNE_C14_1 */
+ [18971] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18971 - ADCR_RETUNE_C14_0 */
+ [18972] = { 0x00FF, 0x00FF, 0x0000 }, /* R18972 - ADCR_RETUNE_C15_1 */
+ [18973] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18973 - ADCR_RETUNE_C15_0 */
+ [18974] = { 0x00FF, 0x00FF, 0x0000 }, /* R18974 - ADCR_RETUNE_C16_1 */
+ [18975] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18975 - ADCR_RETUNE_C16_0 */
+ [18976] = { 0x00FF, 0x00FF, 0x0000 }, /* R18976 - ADCR_RETUNE_C17_1 */
+ [18977] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18977 - ADCR_RETUNE_C17_0 */
+ [18978] = { 0x00FF, 0x00FF, 0x0000 }, /* R18978 - ADCR_RETUNE_C18_1 */
+ [18979] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18979 - ADCR_RETUNE_C18_0 */
+ [18980] = { 0x00FF, 0x00FF, 0x0000 }, /* R18980 - ADCR_RETUNE_C19_1 */
+ [18981] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18981 - ADCR_RETUNE_C19_0 */
+ [18982] = { 0x00FF, 0x00FF, 0x0000 }, /* R18982 - ADCR_RETUNE_C20_1 */
+ [18983] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18983 - ADCR_RETUNE_C20_0 */
+ [18984] = { 0x00FF, 0x00FF, 0x0000 }, /* R18984 - ADCR_RETUNE_C21_1 */
+ [18985] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18985 - ADCR_RETUNE_C21_0 */
+ [18986] = { 0x00FF, 0x00FF, 0x0000 }, /* R18986 - ADCR_RETUNE_C22_1 */
+ [18987] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18987 - ADCR_RETUNE_C22_0 */
+ [18988] = { 0x00FF, 0x00FF, 0x0000 }, /* R18988 - ADCR_RETUNE_C23_1 */
+ [18989] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18989 - ADCR_RETUNE_C23_0 */
+ [18990] = { 0x00FF, 0x00FF, 0x0000 }, /* R18990 - ADCR_RETUNE_C24_1 */
+ [18991] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18991 - ADCR_RETUNE_C24_0 */
+ [18992] = { 0x00FF, 0x00FF, 0x0000 }, /* R18992 - ADCR_RETUNE_C25_1 */
+ [18993] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18993 - ADCR_RETUNE_C25_0 */
+ [18994] = { 0x00FF, 0x00FF, 0x0000 }, /* R18994 - ADCR_RETUNE_C26_1 */
+ [18995] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18995 - ADCR_RETUNE_C26_0 */
+ [18996] = { 0x00FF, 0x00FF, 0x0000 }, /* R18996 - ADCR_RETUNE_C27_1 */
+ [18997] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18997 - ADCR_RETUNE_C27_0 */
+ [18998] = { 0x00FF, 0x00FF, 0x0000 }, /* R18998 - ADCR_RETUNE_C28_1 */
+ [18999] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18999 - ADCR_RETUNE_C28_0 */
+ [19000] = { 0x00FF, 0x00FF, 0x0000 }, /* R19000 - ADCR_RETUNE_C29_1 */
+ [19001] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19001 - ADCR_RETUNE_C29_0 */
+ [19002] = { 0x00FF, 0x00FF, 0x0000 }, /* R19002 - ADCR_RETUNE_C30_1 */
+ [19003] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19003 - ADCR_RETUNE_C30_0 */
+ [19004] = { 0x00FF, 0x00FF, 0x0000 }, /* R19004 - ADCR_RETUNE_C31_1 */
+ [19005] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19005 - ADCR_RETUNE_C31_0 */
+ [19006] = { 0x00FF, 0x00FF, 0x0000 }, /* R19006 - ADCR_RETUNE_C32_1 */
+ [19007] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19007 - ADCR_RETUNE_C32_0 */
+ [19456] = { 0x00FF, 0x00FF, 0x0000 }, /* R19456 - DACL_RETUNE_C1_1 */
+ [19457] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19457 - DACL_RETUNE_C1_0 */
+ [19458] = { 0x00FF, 0x00FF, 0x0000 }, /* R19458 - DACL_RETUNE_C2_1 */
+ [19459] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19459 - DACL_RETUNE_C2_0 */
+ [19460] = { 0x00FF, 0x00FF, 0x0000 }, /* R19460 - DACL_RETUNE_C3_1 */
+ [19461] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19461 - DACL_RETUNE_C3_0 */
+ [19462] = { 0x00FF, 0x00FF, 0x0000 }, /* R19462 - DACL_RETUNE_C4_1 */
+ [19463] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19463 - DACL_RETUNE_C4_0 */
+ [19464] = { 0x00FF, 0x00FF, 0x0000 }, /* R19464 - DACL_RETUNE_C5_1 */
+ [19465] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19465 - DACL_RETUNE_C5_0 */
+ [19466] = { 0x00FF, 0x00FF, 0x0000 }, /* R19466 - DACL_RETUNE_C6_1 */
+ [19467] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19467 - DACL_RETUNE_C6_0 */
+ [19468] = { 0x00FF, 0x00FF, 0x0000 }, /* R19468 - DACL_RETUNE_C7_1 */
+ [19469] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19469 - DACL_RETUNE_C7_0 */
+ [19470] = { 0x00FF, 0x00FF, 0x0000 }, /* R19470 - DACL_RETUNE_C8_1 */
+ [19471] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19471 - DACL_RETUNE_C8_0 */
+ [19472] = { 0x00FF, 0x00FF, 0x0000 }, /* R19472 - DACL_RETUNE_C9_1 */
+ [19473] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19473 - DACL_RETUNE_C9_0 */
+ [19474] = { 0x00FF, 0x00FF, 0x0000 }, /* R19474 - DACL_RETUNE_C10_1 */
+ [19475] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19475 - DACL_RETUNE_C10_0 */
+ [19476] = { 0x00FF, 0x00FF, 0x0000 }, /* R19476 - DACL_RETUNE_C11_1 */
+ [19477] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19477 - DACL_RETUNE_C11_0 */
+ [19478] = { 0x00FF, 0x00FF, 0x0000 }, /* R19478 - DACL_RETUNE_C12_1 */
+ [19479] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19479 - DACL_RETUNE_C12_0 */
+ [19480] = { 0x00FF, 0x00FF, 0x0000 }, /* R19480 - DACL_RETUNE_C13_1 */
+ [19481] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19481 - DACL_RETUNE_C13_0 */
+ [19482] = { 0x00FF, 0x00FF, 0x0000 }, /* R19482 - DACL_RETUNE_C14_1 */
+ [19483] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19483 - DACL_RETUNE_C14_0 */
+ [19484] = { 0x00FF, 0x00FF, 0x0000 }, /* R19484 - DACL_RETUNE_C15_1 */
+ [19485] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19485 - DACL_RETUNE_C15_0 */
+ [19486] = { 0x00FF, 0x00FF, 0x0000 }, /* R19486 - DACL_RETUNE_C16_1 */
+ [19487] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19487 - DACL_RETUNE_C16_0 */
+ [19488] = { 0x00FF, 0x00FF, 0x0000 }, /* R19488 - DACL_RETUNE_C17_1 */
+ [19489] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19489 - DACL_RETUNE_C17_0 */
+ [19490] = { 0x00FF, 0x00FF, 0x0000 }, /* R19490 - DACL_RETUNE_C18_1 */
+ [19491] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19491 - DACL_RETUNE_C18_0 */
+ [19492] = { 0x00FF, 0x00FF, 0x0000 }, /* R19492 - DACL_RETUNE_C19_1 */
+ [19493] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19493 - DACL_RETUNE_C19_0 */
+ [19494] = { 0x00FF, 0x00FF, 0x0000 }, /* R19494 - DACL_RETUNE_C20_1 */
+ [19495] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19495 - DACL_RETUNE_C20_0 */
+ [19496] = { 0x00FF, 0x00FF, 0x0000 }, /* R19496 - DACL_RETUNE_C21_1 */
+ [19497] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19497 - DACL_RETUNE_C21_0 */
+ [19498] = { 0x00FF, 0x00FF, 0x0000 }, /* R19498 - DACL_RETUNE_C22_1 */
+ [19499] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19499 - DACL_RETUNE_C22_0 */
+ [19500] = { 0x00FF, 0x00FF, 0x0000 }, /* R19500 - DACL_RETUNE_C23_1 */
+ [19501] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19501 - DACL_RETUNE_C23_0 */
+ [19502] = { 0x00FF, 0x00FF, 0x0000 }, /* R19502 - DACL_RETUNE_C24_1 */
+ [19503] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19503 - DACL_RETUNE_C24_0 */
+ [19504] = { 0x00FF, 0x00FF, 0x0000 }, /* R19504 - DACL_RETUNE_C25_1 */
+ [19505] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19505 - DACL_RETUNE_C25_0 */
+ [19506] = { 0x00FF, 0x00FF, 0x0000 }, /* R19506 - DACL_RETUNE_C26_1 */
+ [19507] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19507 - DACL_RETUNE_C26_0 */
+ [19508] = { 0x00FF, 0x00FF, 0x0000 }, /* R19508 - DACL_RETUNE_C27_1 */
+ [19509] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19509 - DACL_RETUNE_C27_0 */
+ [19510] = { 0x00FF, 0x00FF, 0x0000 }, /* R19510 - DACL_RETUNE_C28_1 */
+ [19511] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19511 - DACL_RETUNE_C28_0 */
+ [19512] = { 0x00FF, 0x00FF, 0x0000 }, /* R19512 - DACL_RETUNE_C29_1 */
+ [19513] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19513 - DACL_RETUNE_C29_0 */
+ [19514] = { 0x00FF, 0x00FF, 0x0000 }, /* R19514 - DACL_RETUNE_C30_1 */
+ [19515] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19515 - DACL_RETUNE_C30_0 */
+ [19516] = { 0x00FF, 0x00FF, 0x0000 }, /* R19516 - DACL_RETUNE_C31_1 */
+ [19517] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19517 - DACL_RETUNE_C31_0 */
+ [19518] = { 0x00FF, 0x00FF, 0x0000 }, /* R19518 - DACL_RETUNE_C32_1 */
+ [19519] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19519 - DACL_RETUNE_C32_0 */
+ [19968] = { 0x00FF, 0x00FF, 0x0000 }, /* R19968 - RETUNEDAC_PG2_1 */
+ [19969] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19969 - RETUNEDAC_PG2_0 */
+ [19970] = { 0x00FF, 0x00FF, 0x0000 }, /* R19970 - RETUNEDAC_PG_1 */
+ [19971] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19971 - RETUNEDAC_PG_0 */
+ [20480] = { 0x00FF, 0x00FF, 0x0000 }, /* R20480 - DACR_RETUNE_C1_1 */
+ [20481] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20481 - DACR_RETUNE_C1_0 */
+ [20482] = { 0x00FF, 0x00FF, 0x0000 }, /* R20482 - DACR_RETUNE_C2_1 */
+ [20483] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20483 - DACR_RETUNE_C2_0 */
+ [20484] = { 0x00FF, 0x00FF, 0x0000 }, /* R20484 - DACR_RETUNE_C3_1 */
+ [20485] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20485 - DACR_RETUNE_C3_0 */
+ [20486] = { 0x00FF, 0x00FF, 0x0000 }, /* R20486 - DACR_RETUNE_C4_1 */
+ [20487] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20487 - DACR_RETUNE_C4_0 */
+ [20488] = { 0x00FF, 0x00FF, 0x0000 }, /* R20488 - DACR_RETUNE_C5_1 */
+ [20489] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20489 - DACR_RETUNE_C5_0 */
+ [20490] = { 0x00FF, 0x00FF, 0x0000 }, /* R20490 - DACR_RETUNE_C6_1 */
+ [20491] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20491 - DACR_RETUNE_C6_0 */
+ [20492] = { 0x00FF, 0x00FF, 0x0000 }, /* R20492 - DACR_RETUNE_C7_1 */
+ [20493] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20493 - DACR_RETUNE_C7_0 */
+ [20494] = { 0x00FF, 0x00FF, 0x0000 }, /* R20494 - DACR_RETUNE_C8_1 */
+ [20495] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20495 - DACR_RETUNE_C8_0 */
+ [20496] = { 0x00FF, 0x00FF, 0x0000 }, /* R20496 - DACR_RETUNE_C9_1 */
+ [20497] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20497 - DACR_RETUNE_C9_0 */
+ [20498] = { 0x00FF, 0x00FF, 0x0000 }, /* R20498 - DACR_RETUNE_C10_1 */
+ [20499] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20499 - DACR_RETUNE_C10_0 */
+ [20500] = { 0x00FF, 0x00FF, 0x0000 }, /* R20500 - DACR_RETUNE_C11_1 */
+ [20501] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20501 - DACR_RETUNE_C11_0 */
+ [20502] = { 0x00FF, 0x00FF, 0x0000 }, /* R20502 - DACR_RETUNE_C12_1 */
+ [20503] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20503 - DACR_RETUNE_C12_0 */
+ [20504] = { 0x00FF, 0x00FF, 0x0000 }, /* R20504 - DACR_RETUNE_C13_1 */
+ [20505] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20505 - DACR_RETUNE_C13_0 */
+ [20506] = { 0x00FF, 0x00FF, 0x0000 }, /* R20506 - DACR_RETUNE_C14_1 */
+ [20507] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20507 - DACR_RETUNE_C14_0 */
+ [20508] = { 0x00FF, 0x00FF, 0x0000 }, /* R20508 - DACR_RETUNE_C15_1 */
+ [20509] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20509 - DACR_RETUNE_C15_0 */
+ [20510] = { 0x00FF, 0x00FF, 0x0000 }, /* R20510 - DACR_RETUNE_C16_1 */
+ [20511] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20511 - DACR_RETUNE_C16_0 */
+ [20512] = { 0x00FF, 0x00FF, 0x0000 }, /* R20512 - DACR_RETUNE_C17_1 */
+ [20513] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20513 - DACR_RETUNE_C17_0 */
+ [20514] = { 0x00FF, 0x00FF, 0x0000 }, /* R20514 - DACR_RETUNE_C18_1 */
+ [20515] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20515 - DACR_RETUNE_C18_0 */
+ [20516] = { 0x00FF, 0x00FF, 0x0000 }, /* R20516 - DACR_RETUNE_C19_1 */
+ [20517] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20517 - DACR_RETUNE_C19_0 */
+ [20518] = { 0x00FF, 0x00FF, 0x0000 }, /* R20518 - DACR_RETUNE_C20_1 */
+ [20519] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20519 - DACR_RETUNE_C20_0 */
+ [20520] = { 0x00FF, 0x00FF, 0x0000 }, /* R20520 - DACR_RETUNE_C21_1 */
+ [20521] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20521 - DACR_RETUNE_C21_0 */
+ [20522] = { 0x00FF, 0x00FF, 0x0000 }, /* R20522 - DACR_RETUNE_C22_1 */
+ [20523] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20523 - DACR_RETUNE_C22_0 */
+ [20524] = { 0x00FF, 0x00FF, 0x0000 }, /* R20524 - DACR_RETUNE_C23_1 */
+ [20525] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20525 - DACR_RETUNE_C23_0 */
+ [20526] = { 0x00FF, 0x00FF, 0x0000 }, /* R20526 - DACR_RETUNE_C24_1 */
+ [20527] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20527 - DACR_RETUNE_C24_0 */
+ [20528] = { 0x00FF, 0x00FF, 0x0000 }, /* R20528 - DACR_RETUNE_C25_1 */
+ [20529] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20529 - DACR_RETUNE_C25_0 */
+ [20530] = { 0x00FF, 0x00FF, 0x0000 }, /* R20530 - DACR_RETUNE_C26_1 */
+ [20531] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20531 - DACR_RETUNE_C26_0 */
+ [20532] = { 0x00FF, 0x00FF, 0x0000 }, /* R20532 - DACR_RETUNE_C27_1 */
+ [20533] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20533 - DACR_RETUNE_C27_0 */
+ [20534] = { 0x00FF, 0x00FF, 0x0000 }, /* R20534 - DACR_RETUNE_C28_1 */
+ [20535] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20535 - DACR_RETUNE_C28_0 */
+ [20536] = { 0x00FF, 0x00FF, 0x0000 }, /* R20536 - DACR_RETUNE_C29_1 */
+ [20537] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20537 - DACR_RETUNE_C29_0 */
+ [20538] = { 0x00FF, 0x00FF, 0x0000 }, /* R20538 - DACR_RETUNE_C30_1 */
+ [20539] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20539 - DACR_RETUNE_C30_0 */
+ [20540] = { 0x00FF, 0x00FF, 0x0000 }, /* R20540 - DACR_RETUNE_C31_1 */
+ [20541] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20541 - DACR_RETUNE_C31_0 */
+ [20542] = { 0x00FF, 0x00FF, 0x0000 }, /* R20542 - DACR_RETUNE_C32_1 */
+ [20543] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20543 - DACR_RETUNE_C32_0 */
+ [20992] = { 0x00FF, 0x00FF, 0x0000 }, /* R20992 - VSS_XHD2_1 */
+ [20993] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20993 - VSS_XHD2_0 */
+ [20994] = { 0x00FF, 0x00FF, 0x0000 }, /* R20994 - VSS_XHD3_1 */
+ [20995] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20995 - VSS_XHD3_0 */
+ [20996] = { 0x00FF, 0x00FF, 0x0000 }, /* R20996 - VSS_XHN1_1 */
+ [20997] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20997 - VSS_XHN1_0 */
+ [20998] = { 0x00FF, 0x00FF, 0x0000 }, /* R20998 - VSS_XHN2_1 */
+ [20999] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20999 - VSS_XHN2_0 */
+ [21000] = { 0x00FF, 0x00FF, 0x0000 }, /* R21000 - VSS_XHN3_1 */
+ [21001] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21001 - VSS_XHN3_0 */
+ [21002] = { 0x00FF, 0x00FF, 0x0000 }, /* R21002 - VSS_XLA_1 */
+ [21003] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21003 - VSS_XLA_0 */
+ [21004] = { 0x00FF, 0x00FF, 0x0000 }, /* R21004 - VSS_XLB_1 */
+ [21005] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21005 - VSS_XLB_0 */
+ [21006] = { 0x00FF, 0x00FF, 0x0000 }, /* R21006 - VSS_XLG_1 */
+ [21007] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21007 - VSS_XLG_0 */
+ [21008] = { 0x00FF, 0x00FF, 0x0000 }, /* R21008 - VSS_PG2_1 */
+ [21009] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21009 - VSS_PG2_0 */
+ [21010] = { 0x00FF, 0x00FF, 0x0000 }, /* R21010 - VSS_PG_1 */
+ [21011] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21011 - VSS_PG_0 */
+ [21012] = { 0x00FF, 0x00FF, 0x0000 }, /* R21012 - VSS_XTD1_1 */
+ [21013] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21013 - VSS_XTD1_0 */
+ [21014] = { 0x00FF, 0x00FF, 0x0000 }, /* R21014 - VSS_XTD2_1 */
+ [21015] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21015 - VSS_XTD2_0 */
+ [21016] = { 0x00FF, 0x00FF, 0x0000 }, /* R21016 - VSS_XTD3_1 */
+ [21017] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21017 - VSS_XTD3_0 */
+ [21018] = { 0x00FF, 0x00FF, 0x0000 }, /* R21018 - VSS_XTD4_1 */
+ [21019] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21019 - VSS_XTD4_0 */
+ [21020] = { 0x00FF, 0x00FF, 0x0000 }, /* R21020 - VSS_XTD5_1 */
+ [21021] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21021 - VSS_XTD5_0 */
+ [21022] = { 0x00FF, 0x00FF, 0x0000 }, /* R21022 - VSS_XTD6_1 */
+ [21023] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21023 - VSS_XTD6_0 */
+ [21024] = { 0x00FF, 0x00FF, 0x0000 }, /* R21024 - VSS_XTD7_1 */
+ [21025] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21025 - VSS_XTD7_0 */
+ [21026] = { 0x00FF, 0x00FF, 0x0000 }, /* R21026 - VSS_XTD8_1 */
+ [21027] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21027 - VSS_XTD8_0 */
+ [21028] = { 0x00FF, 0x00FF, 0x0000 }, /* R21028 - VSS_XTD9_1 */
+ [21029] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21029 - VSS_XTD9_0 */
+ [21030] = { 0x00FF, 0x00FF, 0x0000 }, /* R21030 - VSS_XTD10_1 */
+ [21031] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21031 - VSS_XTD10_0 */
+ [21032] = { 0x00FF, 0x00FF, 0x0000 }, /* R21032 - VSS_XTD11_1 */
+ [21033] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21033 - VSS_XTD11_0 */
+ [21034] = { 0x00FF, 0x00FF, 0x0000 }, /* R21034 - VSS_XTD12_1 */
+ [21035] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21035 - VSS_XTD12_0 */
+ [21036] = { 0x00FF, 0x00FF, 0x0000 }, /* R21036 - VSS_XTD13_1 */
+ [21037] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21037 - VSS_XTD13_0 */
+ [21038] = { 0x00FF, 0x00FF, 0x0000 }, /* R21038 - VSS_XTD14_1 */
+ [21039] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21039 - VSS_XTD14_0 */
+ [21040] = { 0x00FF, 0x00FF, 0x0000 }, /* R21040 - VSS_XTD15_1 */
+ [21041] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21041 - VSS_XTD15_0 */
+ [21042] = { 0x00FF, 0x00FF, 0x0000 }, /* R21042 - VSS_XTD16_1 */
+ [21043] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21043 - VSS_XTD16_0 */
+ [21044] = { 0x00FF, 0x00FF, 0x0000 }, /* R21044 - VSS_XTD17_1 */
+ [21045] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21045 - VSS_XTD17_0 */
+ [21046] = { 0x00FF, 0x00FF, 0x0000 }, /* R21046 - VSS_XTD18_1 */
+ [21047] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21047 - VSS_XTD18_0 */
+ [21048] = { 0x00FF, 0x00FF, 0x0000 }, /* R21048 - VSS_XTD19_1 */
+ [21049] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21049 - VSS_XTD19_0 */
+ [21050] = { 0x00FF, 0x00FF, 0x0000 }, /* R21050 - VSS_XTD20_1 */
+ [21051] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21051 - VSS_XTD20_0 */
+ [21052] = { 0x00FF, 0x00FF, 0x0000 }, /* R21052 - VSS_XTD21_1 */
+ [21053] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21053 - VSS_XTD21_0 */
+ [21054] = { 0x00FF, 0x00FF, 0x0000 }, /* R21054 - VSS_XTD22_1 */
+ [21055] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21055 - VSS_XTD22_0 */
+ [21056] = { 0x00FF, 0x00FF, 0x0000 }, /* R21056 - VSS_XTD23_1 */
+ [21057] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21057 - VSS_XTD23_0 */
+ [21058] = { 0x00FF, 0x00FF, 0x0000 }, /* R21058 - VSS_XTD24_1 */
+ [21059] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21059 - VSS_XTD24_0 */
+ [21060] = { 0x00FF, 0x00FF, 0x0000 }, /* R21060 - VSS_XTD25_1 */
+ [21061] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21061 - VSS_XTD25_0 */
+ [21062] = { 0x00FF, 0x00FF, 0x0000 }, /* R21062 - VSS_XTD26_1 */
+ [21063] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21063 - VSS_XTD26_0 */
+ [21064] = { 0x00FF, 0x00FF, 0x0000 }, /* R21064 - VSS_XTD27_1 */
+ [21065] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21065 - VSS_XTD27_0 */
+ [21066] = { 0x00FF, 0x00FF, 0x0000 }, /* R21066 - VSS_XTD28_1 */
+ [21067] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21067 - VSS_XTD28_0 */
+ [21068] = { 0x00FF, 0x00FF, 0x0000 }, /* R21068 - VSS_XTD29_1 */
+ [21069] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21069 - VSS_XTD29_0 */
+ [21070] = { 0x00FF, 0x00FF, 0x0000 }, /* R21070 - VSS_XTD30_1 */
+ [21071] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21071 - VSS_XTD30_0 */
+ [21072] = { 0x00FF, 0x00FF, 0x0000 }, /* R21072 - VSS_XTD31_1 */
+ [21073] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21073 - VSS_XTD31_0 */
+ [21074] = { 0x00FF, 0x00FF, 0x0000 }, /* R21074 - VSS_XTD32_1 */
+ [21075] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21075 - VSS_XTD32_0 */
+ [21076] = { 0x00FF, 0x00FF, 0x0000 }, /* R21076 - VSS_XTS1_1 */
+ [21077] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21077 - VSS_XTS1_0 */
+ [21078] = { 0x00FF, 0x00FF, 0x0000 }, /* R21078 - VSS_XTS2_1 */
+ [21079] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21079 - VSS_XTS2_0 */
+ [21080] = { 0x00FF, 0x00FF, 0x0000 }, /* R21080 - VSS_XTS3_1 */
+ [21081] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21081 - VSS_XTS3_0 */
+ [21082] = { 0x00FF, 0x00FF, 0x0000 }, /* R21082 - VSS_XTS4_1 */
+ [21083] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21083 - VSS_XTS4_0 */
+ [21084] = { 0x00FF, 0x00FF, 0x0000 }, /* R21084 - VSS_XTS5_1 */
+ [21085] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21085 - VSS_XTS5_0 */
+ [21086] = { 0x00FF, 0x00FF, 0x0000 }, /* R21086 - VSS_XTS6_1 */
+ [21087] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21087 - VSS_XTS6_0 */
+ [21088] = { 0x00FF, 0x00FF, 0x0000 }, /* R21088 - VSS_XTS7_1 */
+ [21089] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21089 - VSS_XTS7_0 */
+ [21090] = { 0x00FF, 0x00FF, 0x0000 }, /* R21090 - VSS_XTS8_1 */
+ [21091] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21091 - VSS_XTS8_0 */
+ [21092] = { 0x00FF, 0x00FF, 0x0000 }, /* R21092 - VSS_XTS9_1 */
+ [21093] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21093 - VSS_XTS9_0 */
+ [21094] = { 0x00FF, 0x00FF, 0x0000 }, /* R21094 - VSS_XTS10_1 */
+ [21095] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21095 - VSS_XTS10_0 */
+ [21096] = { 0x00FF, 0x00FF, 0x0000 }, /* R21096 - VSS_XTS11_1 */
+ [21097] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21097 - VSS_XTS11_0 */
+ [21098] = { 0x00FF, 0x00FF, 0x0000 }, /* R21098 - VSS_XTS12_1 */
+ [21099] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21099 - VSS_XTS12_0 */
+ [21100] = { 0x00FF, 0x00FF, 0x0000 }, /* R21100 - VSS_XTS13_1 */
+ [21101] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21101 - VSS_XTS13_0 */
+ [21102] = { 0x00FF, 0x00FF, 0x0000 }, /* R21102 - VSS_XTS14_1 */
+ [21103] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21103 - VSS_XTS14_0 */
+ [21104] = { 0x00FF, 0x00FF, 0x0000 }, /* R21104 - VSS_XTS15_1 */
+ [21105] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21105 - VSS_XTS15_0 */
+ [21106] = { 0x00FF, 0x00FF, 0x0000 }, /* R21106 - VSS_XTS16_1 */
+ [21107] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21107 - VSS_XTS16_0 */
+ [21108] = { 0x00FF, 0x00FF, 0x0000 }, /* R21108 - VSS_XTS17_1 */
+ [21109] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21109 - VSS_XTS17_0 */
+ [21110] = { 0x00FF, 0x00FF, 0x0000 }, /* R21110 - VSS_XTS18_1 */
+ [21111] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21111 - VSS_XTS18_0 */
+ [21112] = { 0x00FF, 0x00FF, 0x0000 }, /* R21112 - VSS_XTS19_1 */
+ [21113] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21113 - VSS_XTS19_0 */
+ [21114] = { 0x00FF, 0x00FF, 0x0000 }, /* R21114 - VSS_XTS20_1 */
+ [21115] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21115 - VSS_XTS20_0 */
+ [21116] = { 0x00FF, 0x00FF, 0x0000 }, /* R21116 - VSS_XTS21_1 */
+ [21117] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21117 - VSS_XTS21_0 */
+ [21118] = { 0x00FF, 0x00FF, 0x0000 }, /* R21118 - VSS_XTS22_1 */
+ [21119] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21119 - VSS_XTS22_0 */
+ [21120] = { 0x00FF, 0x00FF, 0x0000 }, /* R21120 - VSS_XTS23_1 */
+ [21121] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21121 - VSS_XTS23_0 */
+ [21122] = { 0x00FF, 0x00FF, 0x0000 }, /* R21122 - VSS_XTS24_1 */
+ [21123] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21123 - VSS_XTS24_0 */
+ [21124] = { 0x00FF, 0x00FF, 0x0000 }, /* R21124 - VSS_XTS25_1 */
+ [21125] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21125 - VSS_XTS25_0 */
+ [21126] = { 0x00FF, 0x00FF, 0x0000 }, /* R21126 - VSS_XTS26_1 */
+ [21127] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21127 - VSS_XTS26_0 */
+ [21128] = { 0x00FF, 0x00FF, 0x0000 }, /* R21128 - VSS_XTS27_1 */
+ [21129] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21129 - VSS_XTS27_0 */
+ [21130] = { 0x00FF, 0x00FF, 0x0000 }, /* R21130 - VSS_XTS28_1 */
+ [21131] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21131 - VSS_XTS28_0 */
+ [21132] = { 0x00FF, 0x00FF, 0x0000 }, /* R21132 - VSS_XTS29_1 */
+ [21133] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21133 - VSS_XTS29_0 */
+ [21134] = { 0x00FF, 0x00FF, 0x0000 }, /* R21134 - VSS_XTS30_1 */
+ [21135] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21135 - VSS_XTS30_0 */
+ [21136] = { 0x00FF, 0x00FF, 0x0000 }, /* R21136 - VSS_XTS31_1 */
+ [21137] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21137 - VSS_XTS31_0 */
+ [21138] = { 0x00FF, 0x00FF, 0x0000 }, /* R21138 - VSS_XTS32_1 */
+ [21139] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21139 - VSS_XTS32_0 */
+};
+
+static int wm8962_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ if (wm8962_reg_access[reg].vol)
+ return 1;
+ else
+ return 0;
+}
+
+static int wm8962_readable_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ if (wm8962_reg_access[reg].read)
+ return 1;
+ else
+ return 0;
+}
+
+static int wm8962_reset(struct snd_soc_codec *codec)
+{
+ int ret;
+
+ ret = snd_soc_write(codec, WM8962_SOFTWARE_RESET, 0x6243);
+ if (ret != 0)
+ return ret;
+
+ return snd_soc_write(codec, WM8962_PLL_SOFTWARE_RESET, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -2325, 75, 0);
+static const DECLARE_TLV_DB_SCALE(mixin_tlv, -1500, 300, 0);
+static const unsigned int mixinpga_tlv[] = {
+ TLV_DB_RANGE_HEAD(5),
+ 0, 1, TLV_DB_SCALE_ITEM(0, 600, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(1300, 1300, 0),
+ 3, 4, TLV_DB_SCALE_ITEM(1800, 200, 0),
+ 5, 5, TLV_DB_SCALE_ITEM(2400, 0, 0),
+ 6, 7, TLV_DB_SCALE_ITEM(2700, 300, 0),
+};
+static const DECLARE_TLV_DB_SCALE(beep_tlv, -9600, 600, 1);
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(st_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(inmix_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(hp_tlv, -700, 100, 0);
+static const unsigned int classd_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0),
+ 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+
+static int wm8962_dsp2_write_config(struct snd_soc_codec *codec)
+{
+ return 0;
+}
+
+static int wm8962_dsp2_set_enable(struct snd_soc_codec *codec, u16 val)
+{
+ u16 adcl = snd_soc_read(codec, WM8962_LEFT_ADC_VOLUME);
+ u16 adcr = snd_soc_read(codec, WM8962_RIGHT_ADC_VOLUME);
+ u16 dac = snd_soc_read(codec, WM8962_ADC_DAC_CONTROL_1);
+
+ /* Mute the ADCs and DACs */
+ snd_soc_write(codec, WM8962_LEFT_ADC_VOLUME, 0);
+ snd_soc_write(codec, WM8962_RIGHT_ADC_VOLUME, WM8962_ADC_VU);
+ snd_soc_update_bits(codec, WM8962_ADC_DAC_CONTROL_1,
+ WM8962_DAC_MUTE, WM8962_DAC_MUTE);
+
+ snd_soc_write(codec, WM8962_SOUNDSTAGE_ENABLES_0, val);
+
+ /* Restore the ADCs and DACs */
+ snd_soc_write(codec, WM8962_LEFT_ADC_VOLUME, adcl);
+ snd_soc_write(codec, WM8962_RIGHT_ADC_VOLUME, adcr);
+ snd_soc_update_bits(codec, WM8962_ADC_DAC_CONTROL_1,
+ WM8962_DAC_MUTE, dac);
+
+ return 0;
+}
+
+static int wm8962_dsp2_start(struct snd_soc_codec *codec)
+{
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+
+ wm8962_dsp2_write_config(codec);
+
+ snd_soc_write(codec, WM8962_DSP2_EXECCONTROL, WM8962_DSP2_RUNR);
+
+ wm8962_dsp2_set_enable(codec, wm8962->dsp2_ena);
+
+ return 0;
+}
+
+static int wm8962_dsp2_stop(struct snd_soc_codec *codec)
+{
+ wm8962_dsp2_set_enable(codec, 0);
+
+ snd_soc_write(codec, WM8962_DSP2_EXECCONTROL, WM8962_DSP2_STOP);
+
+ return 0;
+}
+
+#define WM8962_DSP2_ENABLE(xname, xshift) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = wm8962_dsp2_ena_info, \
+ .get = wm8962_dsp2_ena_get, .put = wm8962_dsp2_ena_put, \
+ .private_value = xshift }
+
+static int wm8962_dsp2_ena_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+
+ return 0;
+}
+
+static int wm8962_dsp2_ena_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int shift = kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = !!(wm8962->dsp2_ena & 1 << shift);
+
+ return 0;
+}
+
+static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int shift = kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ int old = wm8962->dsp2_ena;
+ int ret = 0;
+ int dsp2_running = snd_soc_read(codec, WM8962_DSP2_POWER_MANAGEMENT) &
+ WM8962_DSP2_ENA;
+
+ mutex_lock(&codec->mutex);
+
+ if (ucontrol->value.integer.value[0])
+ wm8962->dsp2_ena |= 1 << shift;
+ else
+ wm8962->dsp2_ena &= ~(1 << shift);
+
+ if (wm8962->dsp2_ena == old)
+ goto out;
+
+ ret = 1;
+
+ if (dsp2_running) {
+ if (wm8962->dsp2_ena)
+ wm8962_dsp2_set_enable(codec, wm8962->dsp2_ena);
+ else
+ wm8962_dsp2_stop(codec);
+ }
+
+out:
+ mutex_unlock(&codec->mutex);
+
+ return ret;
+}
+
+/* The VU bits for the headphones are in a different register to the mute
+ * bits and only take effect on the PGA if it is actually powered.
+ */
+static int wm8962_put_hp_sw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 *reg_cache = codec->reg_cache;
+ int ret;
+
+ /* Apply the update (if any) */
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ if (ret == 0)
+ return 0;
+
+ /* If the left PGA is enabled hit that VU bit... */
+ if (snd_soc_read(codec, WM8962_PWR_MGMT_2) & WM8962_HPOUTL_PGA_ENA)
+ return snd_soc_write(codec, WM8962_HPOUTL_VOLUME,
+ reg_cache[WM8962_HPOUTL_VOLUME]);
+
+ /* ...otherwise the right. The VU is stereo. */
+ if (snd_soc_read(codec, WM8962_PWR_MGMT_2) & WM8962_HPOUTR_PGA_ENA)
+ return snd_soc_write(codec, WM8962_HPOUTR_VOLUME,
+ reg_cache[WM8962_HPOUTR_VOLUME]);
+
+ return 0;
+}
+
+/* The VU bits for the speakers are in a different register to the mute
+ * bits and only take effect on the PGA if it is actually powered.
+ */
+static int wm8962_put_spk_sw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int ret;
+
+ /* Apply the update (if any) */
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ if (ret == 0)
+ return 0;
+
+ /* If the left PGA is enabled hit that VU bit... */
+ ret = snd_soc_read(codec, WM8962_PWR_MGMT_2);
+ if (ret & WM8962_SPKOUTL_PGA_ENA) {
+ snd_soc_write(codec, WM8962_SPKOUTL_VOLUME,
+ snd_soc_read(codec, WM8962_SPKOUTL_VOLUME));
+ return 1;
+ }
+
+ /* ...otherwise the right. The VU is stereo. */
+ if (ret & WM8962_SPKOUTR_PGA_ENA)
+ snd_soc_write(codec, WM8962_SPKOUTR_VOLUME,
+ snd_soc_read(codec, WM8962_SPKOUTR_VOLUME));
+
+ return 1;
+}
+
+static const char *cap_hpf_mode_text[] = {
+ "Hi-fi", "Application"
+};
+
+static const struct soc_enum cap_hpf_mode =
+ SOC_ENUM_SINGLE(WM8962_ADC_DAC_CONTROL_2, 10, 2, cap_hpf_mode_text);
+
+
+static const char *cap_lhpf_mode_text[] = {
+ "LPF", "HPF"
+};
+
+static const struct soc_enum cap_lhpf_mode =
+ SOC_ENUM_SINGLE(WM8962_LHPF1, 1, 2, cap_lhpf_mode_text);
+
+static const struct snd_kcontrol_new wm8962_snd_controls[] = {
+SOC_DOUBLE("Input Mixer Switch", WM8962_INPUT_MIXER_CONTROL_1, 3, 2, 1, 1),
+
+SOC_SINGLE_TLV("MIXINL IN2L Volume", WM8962_LEFT_INPUT_MIXER_VOLUME, 6, 7, 0,
+ mixin_tlv),
+SOC_SINGLE_TLV("MIXINL PGA Volume", WM8962_LEFT_INPUT_MIXER_VOLUME, 3, 7, 0,
+ mixinpga_tlv),
+SOC_SINGLE_TLV("MIXINL IN3L Volume", WM8962_LEFT_INPUT_MIXER_VOLUME, 0, 7, 0,
+ mixin_tlv),
+
+SOC_SINGLE_TLV("MIXINR IN2R Volume", WM8962_RIGHT_INPUT_MIXER_VOLUME, 6, 7, 0,
+ mixin_tlv),
+SOC_SINGLE_TLV("MIXINR PGA Volume", WM8962_RIGHT_INPUT_MIXER_VOLUME, 3, 7, 0,
+ mixinpga_tlv),
+SOC_SINGLE_TLV("MIXINR IN3R Volume", WM8962_RIGHT_INPUT_MIXER_VOLUME, 0, 7, 0,
+ mixin_tlv),
+
+SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8962_LEFT_ADC_VOLUME,
+ WM8962_RIGHT_ADC_VOLUME, 1, 127, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("Capture Volume", WM8962_LEFT_INPUT_VOLUME,
+ WM8962_RIGHT_INPUT_VOLUME, 0, 63, 0, inpga_tlv),
+SOC_DOUBLE_R("Capture Switch", WM8962_LEFT_INPUT_VOLUME,
+ WM8962_RIGHT_INPUT_VOLUME, 7, 1, 1),
+SOC_DOUBLE_R("Capture ZC Switch", WM8962_LEFT_INPUT_VOLUME,
+ WM8962_RIGHT_INPUT_VOLUME, 6, 1, 1),
+SOC_SINGLE("Capture HPF Switch", WM8962_ADC_DAC_CONTROL_1, 0, 1, 1),
+SOC_ENUM("Capture HPF Mode", cap_hpf_mode),
+SOC_SINGLE("Capture HPF Cutoff", WM8962_ADC_DAC_CONTROL_2, 7, 7, 0),
+SOC_SINGLE("Capture LHPF Switch", WM8962_LHPF1, 0, 1, 0),
+SOC_ENUM("Capture LHPF Mode", cap_lhpf_mode),
+
+SOC_DOUBLE_R_TLV("Sidetone Volume", WM8962_DAC_DSP_MIXING_1,
+ WM8962_DAC_DSP_MIXING_2, 4, 12, 0, st_tlv),
+
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8962_LEFT_DAC_VOLUME,
+ WM8962_RIGHT_DAC_VOLUME, 1, 127, 0, digital_tlv),
+SOC_SINGLE("DAC High Performance Switch", WM8962_ADC_DAC_CONTROL_2, 0, 1, 0),
+
+SOC_SINGLE("ADC High Performance Switch", WM8962_ADDITIONAL_CONTROL_1,
+ 5, 1, 0),
+
+SOC_SINGLE_TLV("Beep Volume", WM8962_BEEP_GENERATOR_1, 4, 15, 0, beep_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Volume", WM8962_HPOUTL_VOLUME,
+ WM8962_HPOUTR_VOLUME, 0, 127, 0, out_tlv),
+SOC_DOUBLE_EXT("Headphone Switch", WM8962_PWR_MGMT_2, 1, 0, 1, 1,
+ snd_soc_get_volsw, wm8962_put_hp_sw),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8962_HPOUTL_VOLUME, WM8962_HPOUTR_VOLUME,
+ 7, 1, 0),
+SOC_DOUBLE_TLV("Headphone Aux Volume", WM8962_ANALOGUE_HP_2, 3, 6, 7, 0,
+ hp_tlv),
+
+SOC_DOUBLE_R("Headphone Mixer Switch", WM8962_HEADPHONE_MIXER_3,
+ WM8962_HEADPHONE_MIXER_4, 8, 1, 1),
+
+SOC_SINGLE_TLV("HPMIXL IN4L Volume", WM8962_HEADPHONE_MIXER_3,
+ 3, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("HPMIXL IN4R Volume", WM8962_HEADPHONE_MIXER_3,
+ 0, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("HPMIXL MIXINL Volume", WM8962_HEADPHONE_MIXER_3,
+ 7, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("HPMIXL MIXINR Volume", WM8962_HEADPHONE_MIXER_3,
+ 6, 1, 1, inmix_tlv),
+
+SOC_SINGLE_TLV("HPMIXR IN4L Volume", WM8962_HEADPHONE_MIXER_4,
+ 3, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("HPMIXR IN4R Volume", WM8962_HEADPHONE_MIXER_4,
+ 0, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("HPMIXR MIXINL Volume", WM8962_HEADPHONE_MIXER_4,
+ 7, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("HPMIXR MIXINR Volume", WM8962_HEADPHONE_MIXER_4,
+ 6, 1, 1, inmix_tlv),
+
+SOC_SINGLE_TLV("Speaker Boost Volume", WM8962_CLASS_D_CONTROL_2, 0, 7, 0,
+ classd_tlv),
+
+SOC_SINGLE("EQ Switch", WM8962_EQ1, WM8962_EQ_ENA_SHIFT, 1, 0),
+SOC_DOUBLE_R_TLV("EQ1 Volume", WM8962_EQ2, WM8962_EQ22,
+ WM8962_EQL_B1_GAIN_SHIFT, 31, 0, eq_tlv),
+SOC_DOUBLE_R_TLV("EQ2 Volume", WM8962_EQ2, WM8962_EQ22,
+ WM8962_EQL_B2_GAIN_SHIFT, 31, 0, eq_tlv),
+SOC_DOUBLE_R_TLV("EQ3 Volume", WM8962_EQ2, WM8962_EQ22,
+ WM8962_EQL_B3_GAIN_SHIFT, 31, 0, eq_tlv),
+SOC_DOUBLE_R_TLV("EQ4 Volume", WM8962_EQ3, WM8962_EQ23,
+ WM8962_EQL_B4_GAIN_SHIFT, 31, 0, eq_tlv),
+SOC_DOUBLE_R_TLV("EQ5 Volume", WM8962_EQ3, WM8962_EQ23,
+ WM8962_EQL_B5_GAIN_SHIFT, 31, 0, eq_tlv),
+
+WM8962_DSP2_ENABLE("VSS Switch", WM8962_VSS_ENA_SHIFT),
+WM8962_DSP2_ENABLE("HPF1 Switch", WM8962_HPF1_ENA_SHIFT),
+WM8962_DSP2_ENABLE("HPF2 Switch", WM8962_HPF2_ENA_SHIFT),
+WM8962_DSP2_ENABLE("HD Bass Switch", WM8962_HDBASS_ENA_SHIFT),
+};
+
+static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = {
+SOC_SINGLE_TLV("Speaker Volume", WM8962_SPKOUTL_VOLUME, 0, 127, 0, out_tlv),
+SOC_SINGLE_EXT("Speaker Switch", WM8962_CLASS_D_CONTROL_1, 1, 1, 1,
+ snd_soc_get_volsw, wm8962_put_spk_sw),
+SOC_SINGLE("Speaker ZC Switch", WM8962_SPKOUTL_VOLUME, 7, 1, 0),
+
+SOC_SINGLE("Speaker Mixer Switch", WM8962_SPEAKER_MIXER_3, 8, 1, 1),
+SOC_SINGLE_TLV("Speaker Mixer IN4L Volume", WM8962_SPEAKER_MIXER_3,
+ 3, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("Speaker Mixer IN4R Volume", WM8962_SPEAKER_MIXER_3,
+ 0, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("Speaker Mixer MIXINL Volume", WM8962_SPEAKER_MIXER_3,
+ 7, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("Speaker Mixer MIXINR Volume", WM8962_SPEAKER_MIXER_3,
+ 6, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("Speaker Mixer DACL Volume", WM8962_SPEAKER_MIXER_5,
+ 7, 1, 0, inmix_tlv),
+SOC_SINGLE_TLV("Speaker Mixer DACR Volume", WM8962_SPEAKER_MIXER_5,
+ 6, 1, 0, inmix_tlv),
+};
+
+static const struct snd_kcontrol_new wm8962_spk_stereo_controls[] = {
+SOC_DOUBLE_R_TLV("Speaker Volume", WM8962_SPKOUTL_VOLUME,
+ WM8962_SPKOUTR_VOLUME, 0, 127, 0, out_tlv),
+SOC_DOUBLE_EXT("Speaker Switch", WM8962_CLASS_D_CONTROL_1, 1, 0, 1, 1,
+ snd_soc_get_volsw, wm8962_put_spk_sw),
+SOC_DOUBLE_R("Speaker ZC Switch", WM8962_SPKOUTL_VOLUME, WM8962_SPKOUTR_VOLUME,
+ 7, 1, 0),
+
+SOC_DOUBLE_R("Speaker Mixer Switch", WM8962_SPEAKER_MIXER_3,
+ WM8962_SPEAKER_MIXER_4, 8, 1, 1),
+
+SOC_SINGLE_TLV("SPKOUTL Mixer IN4L Volume", WM8962_SPEAKER_MIXER_3,
+ 3, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("SPKOUTL Mixer IN4R Volume", WM8962_SPEAKER_MIXER_3,
+ 0, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("SPKOUTL Mixer MIXINL Volume", WM8962_SPEAKER_MIXER_3,
+ 7, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTL Mixer MIXINR Volume", WM8962_SPEAKER_MIXER_3,
+ 6, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTL Mixer DACL Volume", WM8962_SPEAKER_MIXER_5,
+ 7, 1, 0, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTL Mixer DACR Volume", WM8962_SPEAKER_MIXER_5,
+ 6, 1, 0, inmix_tlv),
+
+SOC_SINGLE_TLV("SPKOUTR Mixer IN4L Volume", WM8962_SPEAKER_MIXER_4,
+ 3, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("SPKOUTR Mixer IN4R Volume", WM8962_SPEAKER_MIXER_4,
+ 0, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("SPKOUTR Mixer MIXINL Volume", WM8962_SPEAKER_MIXER_4,
+ 7, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTR Mixer MIXINR Volume", WM8962_SPEAKER_MIXER_4,
+ 6, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTR Mixer DACL Volume", WM8962_SPEAKER_MIXER_5,
+ 5, 1, 0, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTR Mixer DACR Volume", WM8962_SPEAKER_MIXER_5,
+ 4, 1, 0, inmix_tlv),
+};
+
+static int sysclk_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ unsigned long timeout;
+ int src;
+ int fll;
+
+ src = snd_soc_read(codec, WM8962_CLOCKING2) & WM8962_SYSCLK_SRC_MASK;
+
+ switch (src) {
+ case 0: /* MCLK */
+ fll = 0;
+ break;
+ case 0x200: /* FLL */
+ fll = 1;
+ break;
+ default:
+ dev_err(codec->dev, "Unknown SYSCLK source %x\n", src);
+ return -EINVAL;
+ }
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (fll) {
+ try_wait_for_completion(&wm8962->fll_lock);
+
+ snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
+ WM8962_FLL_ENA, WM8962_FLL_ENA);
+
+ timeout = msecs_to_jiffies(5);
+ timeout = wait_for_completion_timeout(&wm8962->fll_lock,
+ timeout);
+
+ if (wm8962->irq && timeout == 0)
+ dev_err(codec->dev,
+ "Timed out starting FLL\n");
+ }
+ break;
+
+ case SND_SOC_DAPM_POST_PMD:
+ /* After Power-down, close FLL if FLL-enabled */
+ snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
+ WM8962_FLL_ENA, 0);
+ break;
+
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ msleep(5);
+ break;
+
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ int timeout;
+ int reg;
+ int expected = (WM8962_DCS_STARTUP_DONE_HP1L |
+ WM8962_DCS_STARTUP_DONE_HP1R);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+ WM8962_HP1L_ENA | WM8962_HP1R_ENA,
+ WM8962_HP1L_ENA | WM8962_HP1R_ENA);
+ udelay(20);
+
+ snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+ WM8962_HP1L_ENA_DLY | WM8962_HP1R_ENA_DLY,
+ WM8962_HP1L_ENA_DLY | WM8962_HP1R_ENA_DLY);
+
+ /* Start the DC servo */
+ snd_soc_update_bits(codec, WM8962_DC_SERVO_1,
+ WM8962_HP1L_DCS_ENA | WM8962_HP1R_DCS_ENA |
+ WM8962_HP1L_DCS_STARTUP |
+ WM8962_HP1R_DCS_STARTUP,
+ WM8962_HP1L_DCS_ENA | WM8962_HP1R_DCS_ENA |
+ WM8962_HP1L_DCS_STARTUP |
+ WM8962_HP1R_DCS_STARTUP);
+
+ /* Wait for it to complete, should be well under 100ms */
+ timeout = 0;
+ do {
+ msleep(1);
+ reg = snd_soc_read(codec, WM8962_DC_SERVO_6);
+ if (reg < 0) {
+ dev_err(codec->dev,
+ "Failed to read DCS status: %d\n",
+ reg);
+ continue;
+ }
+ dev_dbg(codec->dev, "DCS status: %x\n", reg);
+ } while (++timeout < 200 && (reg & expected) != expected);
+
+ if ((reg & expected) != expected)
+ dev_err(codec->dev, "DC servo timed out\n");
+ else
+ dev_dbg(codec->dev, "DC servo complete after %dms\n",
+ timeout);
+
+ snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+ WM8962_HP1L_ENA_OUTP |
+ WM8962_HP1R_ENA_OUTP,
+ WM8962_HP1L_ENA_OUTP |
+ WM8962_HP1R_ENA_OUTP);
+ udelay(20);
+
+ snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+ WM8962_HP1L_RMV_SHORT |
+ WM8962_HP1R_RMV_SHORT,
+ WM8962_HP1L_RMV_SHORT |
+ WM8962_HP1R_RMV_SHORT);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+ WM8962_HP1L_RMV_SHORT |
+ WM8962_HP1R_RMV_SHORT, 0);
+
+ udelay(20);
+
+ snd_soc_update_bits(codec, WM8962_DC_SERVO_1,
+ WM8962_HP1L_DCS_ENA | WM8962_HP1R_DCS_ENA |
+ WM8962_HP1L_DCS_STARTUP |
+ WM8962_HP1R_DCS_STARTUP,
+ 0);
+
+ snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+ WM8962_HP1L_ENA | WM8962_HP1R_ENA |
+ WM8962_HP1L_ENA_DLY | WM8962_HP1R_ENA_DLY |
+ WM8962_HP1L_ENA_OUTP |
+ WM8962_HP1R_ENA_OUTP, 0);
+
+ break;
+
+ default:
+ BUG();
+ return -EINVAL;
+
+ }
+
+ return 0;
+}
+
+/* VU bits for the output PGAs only take effect while the PGA is powered */
+static int out_pga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ int reg;
+
+ switch (w->shift) {
+ case WM8962_HPOUTR_PGA_ENA_SHIFT:
+ reg = WM8962_HPOUTR_VOLUME;
+ break;
+ case WM8962_HPOUTL_PGA_ENA_SHIFT:
+ reg = WM8962_HPOUTL_VOLUME;
+ break;
+ case WM8962_SPKOUTR_PGA_ENA_SHIFT:
+ reg = WM8962_SPKOUTR_VOLUME;
+ break;
+ case WM8962_SPKOUTL_PGA_ENA_SHIFT:
+ reg = WM8962_SPKOUTL_VOLUME;
+ break;
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ return snd_soc_write(codec, reg, snd_soc_read(codec, reg));
+ default:
+ BUG();
+ return -EINVAL;
+ }
+}
+
+static int dsp2_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ if (wm8962->dsp2_ena)
+ wm8962_dsp2_start(codec);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ if (wm8962->dsp2_ena)
+ wm8962_dsp2_stop(codec);
+ break;
+
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const char *st_text[] = { "None", "Right", "Left" };
+
+static const struct soc_enum str_enum =
+ SOC_ENUM_SINGLE(WM8962_DAC_DSP_MIXING_1, 2, 3, st_text);
+
+static const struct snd_kcontrol_new str_mux =
+ SOC_DAPM_ENUM("Right Sidetone", str_enum);
+
+static const struct soc_enum stl_enum =
+ SOC_ENUM_SINGLE(WM8962_DAC_DSP_MIXING_2, 2, 3, st_text);
+
+static const struct snd_kcontrol_new stl_mux =
+ SOC_DAPM_ENUM("Left Sidetone", stl_enum);
+
+static const char *outmux_text[] = { "DAC", "Mixer" };
+
+static const struct soc_enum spkoutr_enum =
+ SOC_ENUM_SINGLE(WM8962_SPEAKER_MIXER_2, 7, 2, outmux_text);
+
+static const struct snd_kcontrol_new spkoutr_mux =
+ SOC_DAPM_ENUM("SPKOUTR Mux", spkoutr_enum);
+
+static const struct soc_enum spkoutl_enum =
+ SOC_ENUM_SINGLE(WM8962_SPEAKER_MIXER_1, 7, 2, outmux_text);
+
+static const struct snd_kcontrol_new spkoutl_mux =
+ SOC_DAPM_ENUM("SPKOUTL Mux", spkoutl_enum);
+
+static const struct soc_enum hpoutr_enum =
+ SOC_ENUM_SINGLE(WM8962_HEADPHONE_MIXER_2, 7, 2, outmux_text);
+
+static const struct snd_kcontrol_new hpoutr_mux =
+ SOC_DAPM_ENUM("HPOUTR Mux", hpoutr_enum);
+
+static const struct soc_enum hpoutl_enum =
+ SOC_ENUM_SINGLE(WM8962_HEADPHONE_MIXER_1, 7, 2, outmux_text);
+
+static const struct snd_kcontrol_new hpoutl_mux =
+ SOC_DAPM_ENUM("HPOUTL Mux", hpoutl_enum);
+
+static const struct snd_kcontrol_new inpgal[] = {
+SOC_DAPM_SINGLE("IN1L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 3, 1, 0),
+SOC_DAPM_SINGLE("IN2L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 2, 1, 0),
+SOC_DAPM_SINGLE("IN3L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new inpgar[] = {
+SOC_DAPM_SINGLE("IN1R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 3, 1, 0),
+SOC_DAPM_SINGLE("IN2R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 2, 1, 0),
+SOC_DAPM_SINGLE("IN3R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixinl[] = {
+SOC_DAPM_SINGLE("IN2L Switch", WM8962_INPUT_MIXER_CONTROL_2, 5, 1, 0),
+SOC_DAPM_SINGLE("IN3L Switch", WM8962_INPUT_MIXER_CONTROL_2, 4, 1, 0),
+SOC_DAPM_SINGLE("PGA Switch", WM8962_INPUT_MIXER_CONTROL_2, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixinr[] = {
+SOC_DAPM_SINGLE("IN2R Switch", WM8962_INPUT_MIXER_CONTROL_2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN3R Switch", WM8962_INPUT_MIXER_CONTROL_2, 1, 1, 0),
+SOC_DAPM_SINGLE("PGA Switch", WM8962_INPUT_MIXER_CONTROL_2, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new hpmixl[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8962_HEADPHONE_MIXER_1, 5, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8962_HEADPHONE_MIXER_1, 4, 1, 0),
+SOC_DAPM_SINGLE("MIXINL Switch", WM8962_HEADPHONE_MIXER_1, 3, 1, 0),
+SOC_DAPM_SINGLE("MIXINR Switch", WM8962_HEADPHONE_MIXER_1, 2, 1, 0),
+SOC_DAPM_SINGLE("IN4L Switch", WM8962_HEADPHONE_MIXER_1, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4R Switch", WM8962_HEADPHONE_MIXER_1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new hpmixr[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8962_HEADPHONE_MIXER_2, 5, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8962_HEADPHONE_MIXER_2, 4, 1, 0),
+SOC_DAPM_SINGLE("MIXINL Switch", WM8962_HEADPHONE_MIXER_2, 3, 1, 0),
+SOC_DAPM_SINGLE("MIXINR Switch", WM8962_HEADPHONE_MIXER_2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN4L Switch", WM8962_HEADPHONE_MIXER_2, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4R Switch", WM8962_HEADPHONE_MIXER_2, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new spkmixl[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8962_SPEAKER_MIXER_1, 5, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8962_SPEAKER_MIXER_1, 4, 1, 0),
+SOC_DAPM_SINGLE("MIXINL Switch", WM8962_SPEAKER_MIXER_1, 3, 1, 0),
+SOC_DAPM_SINGLE("MIXINR Switch", WM8962_SPEAKER_MIXER_1, 2, 1, 0),
+SOC_DAPM_SINGLE("IN4L Switch", WM8962_SPEAKER_MIXER_1, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4R Switch", WM8962_SPEAKER_MIXER_1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new spkmixr[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8962_SPEAKER_MIXER_2, 5, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8962_SPEAKER_MIXER_2, 4, 1, 0),
+SOC_DAPM_SINGLE("MIXINL Switch", WM8962_SPEAKER_MIXER_2, 3, 1, 0),
+SOC_DAPM_SINGLE("MIXINR Switch", WM8962_SPEAKER_MIXER_2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN4L Switch", WM8962_SPEAKER_MIXER_2, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4R Switch", WM8962_SPEAKER_MIXER_2, 0, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8962_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1L"),
+SND_SOC_DAPM_INPUT("IN1R"),
+SND_SOC_DAPM_INPUT("IN2L"),
+SND_SOC_DAPM_INPUT("IN2R"),
+SND_SOC_DAPM_INPUT("IN3L"),
+SND_SOC_DAPM_INPUT("IN3R"),
+SND_SOC_DAPM_INPUT("IN4L"),
+SND_SOC_DAPM_INPUT("IN4R"),
+SND_SOC_DAPM_INPUT("Beep"),
+SND_SOC_DAPM_INPUT("DMICDAT"),
+
+SND_SOC_DAPM_SUPPLY("MICBIAS", WM8962_PWR_MGMT_1, 1, 0, NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("Class G", WM8962_CHARGE_PUMP_B, 0, 1, NULL, 0),
+SND_SOC_DAPM_SUPPLY("SYSCLK", WM8962_CLOCKING2, 5, 0, sysclk_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("Charge Pump", WM8962_CHARGE_PUMP_1, 0, 0, cp_event,
+ SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_SUPPLY("TOCLK", WM8962_ADDITIONAL_CONTROL_1, 0, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("TEMP_HP", WM8962_ADDITIONAL_CONTROL_4, 2, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("TEMP_SPK", WM8962_ADDITIONAL_CONTROL_4, 1, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY_S("DSP2", 1, WM8962_DSP2_POWER_MANAGEMENT,
+ WM8962_DSP2_ENA_SHIFT, 0, dsp2_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_MIXER("INPGAL", WM8962_LEFT_INPUT_PGA_CONTROL, 4, 0,
+ inpgal, ARRAY_SIZE(inpgal)),
+SND_SOC_DAPM_MIXER("INPGAR", WM8962_RIGHT_INPUT_PGA_CONTROL, 4, 0,
+ inpgar, ARRAY_SIZE(inpgar)),
+SND_SOC_DAPM_MIXER("MIXINL", WM8962_PWR_MGMT_1, 5, 0,
+ mixinl, ARRAY_SIZE(mixinl)),
+SND_SOC_DAPM_MIXER("MIXINR", WM8962_PWR_MGMT_1, 4, 0,
+ mixinr, ARRAY_SIZE(mixinr)),
+
+SND_SOC_DAPM_AIF_IN("DMIC_ENA", NULL, 0, WM8962_PWR_MGMT_1, 10, 0),
+
+SND_SOC_DAPM_ADC("ADCL", "Capture", WM8962_PWR_MGMT_1, 3, 0),
+SND_SOC_DAPM_ADC("ADCR", "Capture", WM8962_PWR_MGMT_1, 2, 0),
+
+SND_SOC_DAPM_MUX("STL", SND_SOC_NOPM, 0, 0, &stl_mux),
+SND_SOC_DAPM_MUX("STR", SND_SOC_NOPM, 0, 0, &str_mux),
+
+SND_SOC_DAPM_DAC("DACL", "Playback", WM8962_PWR_MGMT_2, 8, 0),
+SND_SOC_DAPM_DAC("DACR", "Playback", WM8962_PWR_MGMT_2, 7, 0),
+
+SND_SOC_DAPM_PGA("Left Bypass", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Bypass", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("HPMIXL", WM8962_MIXER_ENABLES, 3, 0,
+ hpmixl, ARRAY_SIZE(hpmixl)),
+SND_SOC_DAPM_MIXER("HPMIXR", WM8962_MIXER_ENABLES, 2, 0,
+ hpmixr, ARRAY_SIZE(hpmixr)),
+
+SND_SOC_DAPM_MUX_E("HPOUTL PGA", WM8962_PWR_MGMT_2, 6, 0, &hpoutl_mux,
+ out_pga_event, SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_MUX_E("HPOUTR PGA", WM8962_PWR_MGMT_2, 5, 0, &hpoutr_mux,
+ out_pga_event, SND_SOC_DAPM_POST_PMU),
+
+SND_SOC_DAPM_PGA_E("HPOUT", SND_SOC_NOPM, 0, 0, NULL, 0, hp_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+};
+
+static const struct snd_soc_dapm_widget wm8962_dapm_spk_mono_widgets[] = {
+SND_SOC_DAPM_MIXER("Speaker Mixer", WM8962_MIXER_ENABLES, 1, 0,
+ spkmixl, ARRAY_SIZE(spkmixl)),
+SND_SOC_DAPM_MUX_E("Speaker PGA", WM8962_PWR_MGMT_2, 4, 0, &spkoutl_mux,
+ out_pga_event, SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_PGA("Speaker Output", WM8962_CLASS_D_CONTROL_1, 7, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("SPKOUT"),
+};
+
+static const struct snd_soc_dapm_widget wm8962_dapm_spk_stereo_widgets[] = {
+SND_SOC_DAPM_MIXER("SPKOUTL Mixer", WM8962_MIXER_ENABLES, 1, 0,
+ spkmixl, ARRAY_SIZE(spkmixl)),
+SND_SOC_DAPM_MIXER("SPKOUTR Mixer", WM8962_MIXER_ENABLES, 0, 0,
+ spkmixr, ARRAY_SIZE(spkmixr)),
+
+SND_SOC_DAPM_MUX_E("SPKOUTL PGA", WM8962_PWR_MGMT_2, 4, 0, &spkoutl_mux,
+ out_pga_event, SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_MUX_E("SPKOUTR PGA", WM8962_PWR_MGMT_2, 3, 0, &spkoutr_mux,
+ out_pga_event, SND_SOC_DAPM_POST_PMU),
+
+SND_SOC_DAPM_PGA("SPKOUTR Output", WM8962_CLASS_D_CONTROL_1, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SPKOUTL Output", WM8962_CLASS_D_CONTROL_1, 6, 0, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("SPKOUTL"),
+SND_SOC_DAPM_OUTPUT("SPKOUTR"),
+};
+
+static const struct snd_soc_dapm_route wm8962_intercon[] = {
+ { "INPGAL", "IN1L Switch", "IN1L" },
+ { "INPGAL", "IN2L Switch", "IN2L" },
+ { "INPGAL", "IN3L Switch", "IN3L" },
+ { "INPGAL", "IN4L Switch", "IN4L" },
+
+ { "INPGAR", "IN1R Switch", "IN1R" },
+ { "INPGAR", "IN2R Switch", "IN2R" },
+ { "INPGAR", "IN3R Switch", "IN3R" },
+ { "INPGAR", "IN4R Switch", "IN4R" },
+
+ { "MIXINL", "IN2L Switch", "IN2L" },
+ { "MIXINL", "IN3L Switch", "IN3L" },
+ { "MIXINL", "PGA Switch", "INPGAL" },
+
+ { "MIXINR", "IN2R Switch", "IN2R" },
+ { "MIXINR", "IN3R Switch", "IN3R" },
+ { "MIXINR", "PGA Switch", "INPGAR" },
+
+ { "MICBIAS", NULL, "SYSCLK" },
+
+ { "DMIC_ENA", NULL, "DMICDAT" },
+
+ { "ADCL", NULL, "SYSCLK" },
+ { "ADCL", NULL, "TOCLK" },
+ { "ADCL", NULL, "MIXINL" },
+ { "ADCL", NULL, "DMIC_ENA" },
+ { "ADCL", NULL, "DSP2" },
+
+ { "ADCR", NULL, "SYSCLK" },
+ { "ADCR", NULL, "TOCLK" },
+ { "ADCR", NULL, "MIXINR" },
+ { "ADCR", NULL, "DMIC_ENA" },
+ { "ADCR", NULL, "DSP2" },
+
+ { "STL", "Left", "ADCL" },
+ { "STL", "Right", "ADCR" },
+
+ { "STR", "Left", "ADCL" },
+ { "STR", "Right", "ADCR" },
+
+ { "DACL", NULL, "SYSCLK" },
+ { "DACL", NULL, "TOCLK" },
+ { "DACL", NULL, "Beep" },
+ { "DACL", NULL, "STL" },
+ { "DACL", NULL, "DSP2" },
+
+ { "DACR", NULL, "SYSCLK" },
+ { "DACR", NULL, "TOCLK" },
+ { "DACR", NULL, "Beep" },
+ { "DACR", NULL, "STR" },
+ { "DACR", NULL, "DSP2" },
+
+ { "HPMIXL", "IN4L Switch", "IN4L" },
+ { "HPMIXL", "IN4R Switch", "IN4R" },
+ { "HPMIXL", "DACL Switch", "DACL" },
+ { "HPMIXL", "DACR Switch", "DACR" },
+ { "HPMIXL", "MIXINL Switch", "MIXINL" },
+ { "HPMIXL", "MIXINR Switch", "MIXINR" },
+
+ { "HPMIXR", "IN4L Switch", "IN4L" },
+ { "HPMIXR", "IN4R Switch", "IN4R" },
+ { "HPMIXR", "DACL Switch", "DACL" },
+ { "HPMIXR", "DACR Switch", "DACR" },
+ { "HPMIXR", "MIXINL Switch", "MIXINL" },
+ { "HPMIXR", "MIXINR Switch", "MIXINR" },
+
+ { "Left Bypass", NULL, "HPMIXL" },
+ { "Left Bypass", NULL, "Class G" },
+
+ { "Right Bypass", NULL, "HPMIXR" },
+ { "Right Bypass", NULL, "Class G" },
+
+ { "HPOUTL PGA", "Mixer", "Left Bypass" },
+ { "HPOUTL PGA", "DAC", "DACL" },
+
+ { "HPOUTR PGA", "Mixer", "Right Bypass" },
+ { "HPOUTR PGA", "DAC", "DACR" },
+
+ { "HPOUT", NULL, "HPOUTL PGA" },
+ { "HPOUT", NULL, "HPOUTR PGA" },
+ { "HPOUT", NULL, "Charge Pump" },
+ { "HPOUT", NULL, "SYSCLK" },
+ { "HPOUT", NULL, "TOCLK" },
+
+ { "HPOUTL", NULL, "HPOUT" },
+ { "HPOUTR", NULL, "HPOUT" },
+
+ { "HPOUTL", NULL, "TEMP_HP" },
+ { "HPOUTR", NULL, "TEMP_HP" },
+};
+
+static const struct snd_soc_dapm_route wm8962_spk_mono_intercon[] = {
+ { "Speaker Mixer", "IN4L Switch", "IN4L" },
+ { "Speaker Mixer", "IN4R Switch", "IN4R" },
+ { "Speaker Mixer", "DACL Switch", "DACL" },
+ { "Speaker Mixer", "DACR Switch", "DACR" },
+ { "Speaker Mixer", "MIXINL Switch", "MIXINL" },
+ { "Speaker Mixer", "MIXINR Switch", "MIXINR" },
+
+ { "Speaker PGA", "Mixer", "Speaker Mixer" },
+ { "Speaker PGA", "DAC", "DACL" },
+
+ { "Speaker Output", NULL, "Speaker PGA" },
+ { "Speaker Output", NULL, "SYSCLK" },
+ { "Speaker Output", NULL, "TOCLK" },
+ { "Speaker Output", NULL, "TEMP_SPK" },
+
+ { "SPKOUT", NULL, "Speaker Output" },
+};
+
+static const struct snd_soc_dapm_route wm8962_spk_stereo_intercon[] = {
+ { "SPKOUTL Mixer", "IN4L Switch", "IN4L" },
+ { "SPKOUTL Mixer", "IN4R Switch", "IN4R" },
+ { "SPKOUTL Mixer", "DACL Switch", "DACL" },
+ { "SPKOUTL Mixer", "DACR Switch", "DACR" },
+ { "SPKOUTL Mixer", "MIXINL Switch", "MIXINL" },
+ { "SPKOUTL Mixer", "MIXINR Switch", "MIXINR" },
+
+ { "SPKOUTR Mixer", "IN4L Switch", "IN4L" },
+ { "SPKOUTR Mixer", "IN4R Switch", "IN4R" },
+ { "SPKOUTR Mixer", "DACL Switch", "DACL" },
+ { "SPKOUTR Mixer", "DACR Switch", "DACR" },
+ { "SPKOUTR Mixer", "MIXINL Switch", "MIXINL" },
+ { "SPKOUTR Mixer", "MIXINR Switch", "MIXINR" },
+
+ { "SPKOUTL PGA", "Mixer", "SPKOUTL Mixer" },
+ { "SPKOUTL PGA", "DAC", "DACL" },
+
+ { "SPKOUTR PGA", "Mixer", "SPKOUTR Mixer" },
+ { "SPKOUTR PGA", "DAC", "DACR" },
+
+ { "SPKOUTL Output", NULL, "SPKOUTL PGA" },
+ { "SPKOUTL Output", NULL, "SYSCLK" },
+ { "SPKOUTL Output", NULL, "TOCLK" },
+ { "SPKOUTL Output", NULL, "TEMP_SPK" },
+
+ { "SPKOUTR Output", NULL, "SPKOUTR PGA" },
+ { "SPKOUTR Output", NULL, "SYSCLK" },
+ { "SPKOUTR Output", NULL, "TOCLK" },
+ { "SPKOUTR Output", NULL, "TEMP_SPK" },
+
+ { "SPKOUTL", NULL, "SPKOUTL Output" },
+ { "SPKOUTR", NULL, "SPKOUTR Output" },
+};
+
+static int wm8962_add_widgets(struct snd_soc_codec *codec)
+{
+ struct wm8962_pdata *pdata = dev_get_platdata(codec->dev);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_add_controls(codec, wm8962_snd_controls,
+ ARRAY_SIZE(wm8962_snd_controls));
+ if (pdata && pdata->spk_mono)
+ snd_soc_add_controls(codec, wm8962_spk_mono_controls,
+ ARRAY_SIZE(wm8962_spk_mono_controls));
+ else
+ snd_soc_add_controls(codec, wm8962_spk_stereo_controls,
+ ARRAY_SIZE(wm8962_spk_stereo_controls));
+
+
+ snd_soc_dapm_new_controls(dapm, wm8962_dapm_widgets,
+ ARRAY_SIZE(wm8962_dapm_widgets));
+ if (pdata && pdata->spk_mono)
+ snd_soc_dapm_new_controls(dapm, wm8962_dapm_spk_mono_widgets,
+ ARRAY_SIZE(wm8962_dapm_spk_mono_widgets));
+ else
+ snd_soc_dapm_new_controls(dapm, wm8962_dapm_spk_stereo_widgets,
+ ARRAY_SIZE(wm8962_dapm_spk_stereo_widgets));
+
+ snd_soc_dapm_add_routes(dapm, wm8962_intercon,
+ ARRAY_SIZE(wm8962_intercon));
+ if (pdata && pdata->spk_mono)
+ snd_soc_dapm_add_routes(dapm, wm8962_spk_mono_intercon,
+ ARRAY_SIZE(wm8962_spk_mono_intercon));
+ else
+ snd_soc_dapm_add_routes(dapm, wm8962_spk_stereo_intercon,
+ ARRAY_SIZE(wm8962_spk_stereo_intercon));
+
+
+ snd_soc_dapm_disable_pin(dapm, "Beep");
+
+ return 0;
+}
+
+static void wm8962_sync_cache(struct snd_soc_codec *codec)
+{
+ u16 *reg_cache = codec->reg_cache;
+ int i;
+
+ if (!codec->cache_sync)
+ return;
+
+ dev_dbg(codec->dev, "Syncing cache\n");
+
+ codec->cache_only = 0;
+
+ /* Sync back cached values if they're different from the
+ * hardware default.
+ */
+ for (i = 1; i < codec->driver->reg_cache_size; i++) {
+ if (i == WM8962_SOFTWARE_RESET)
+ continue;
+ if (reg_cache[i] == wm8962_reg[i])
+ continue;
+
+ snd_soc_write(codec, i, reg_cache[i]);
+ }
+
+ codec->cache_sync = 0;
+}
+
+/* -1 for reserved values */
+static const int bclk_divs[] = {
+ 1, -1, 2, 3, 4, -1, 6, 8, -1, 12, 16, 24, -1, 32, 32, 32
+};
+
+static const int sysclk_rates[] = {
+ 64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536,
+};
+
+static void wm8962_configure_bclk(struct snd_soc_codec *codec)
+{
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ int dspclk, i;
+ int clocking2 = 0;
+ int clocking4 = 0;
+ int aif2 = 0;
+
+ if (!wm8962->sysclk_rate) {
+ dev_dbg(codec->dev, "No SYSCLK configured\n");
+ return;
+ }
+
+ if (!wm8962->bclk || !wm8962->lrclk) {
+ dev_dbg(codec->dev, "No audio clocks configured\n");
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) {
+ if (sysclk_rates[i] == wm8962->sysclk_rate / wm8962->lrclk) {
+ clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(sysclk_rates)) {
+ dev_err(codec->dev, "Unsupported sysclk ratio %d\n",
+ wm8962->sysclk_rate / wm8962->lrclk);
+ return;
+ }
+
+ snd_soc_update_bits(codec, WM8962_CLOCKING_4,
+ WM8962_SYSCLK_RATE_MASK, clocking4);
+
+ dspclk = snd_soc_read(codec, WM8962_CLOCKING1);
+ if (dspclk < 0) {
+ dev_err(codec->dev, "Failed to read DSPCLK: %d\n", dspclk);
+ return;
+ }
+
+ dspclk = (dspclk & WM8962_DSPCLK_DIV_MASK) >> WM8962_DSPCLK_DIV_SHIFT;
+ switch (dspclk) {
+ case 0:
+ dspclk = wm8962->sysclk_rate;
+ break;
+ case 1:
+ dspclk = wm8962->sysclk_rate / 2;
+ break;
+ case 2:
+ dspclk = wm8962->sysclk_rate / 4;
+ break;
+ default:
+ dev_warn(codec->dev, "Unknown DSPCLK divisor read back\n");
+ dspclk = wm8962->sysclk;
+ }
+
+ dev_dbg(codec->dev, "DSPCLK is %dHz, BCLK %d\n", dspclk, wm8962->bclk);
+
+ /* We're expecting an exact match */
+ for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+ if (bclk_divs[i] < 0)
+ continue;
+
+ if (dspclk / bclk_divs[i] == wm8962->bclk) {
+ dev_dbg(codec->dev, "Selected BCLK_DIV %d for %dHz\n",
+ bclk_divs[i], wm8962->bclk);
+ clocking2 |= i;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(bclk_divs)) {
+ dev_err(codec->dev, "Unsupported BCLK ratio %d\n",
+ dspclk / wm8962->bclk);
+ return;
+ }
+
+ aif2 |= wm8962->bclk / wm8962->lrclk;
+ dev_dbg(codec->dev, "Selected LRCLK divisor %d for %dHz\n",
+ wm8962->bclk / wm8962->lrclk, wm8962->lrclk);
+
+ snd_soc_update_bits(codec, WM8962_CLOCKING2,
+ WM8962_BCLK_DIV_MASK, clocking2);
+ snd_soc_update_bits(codec, WM8962_AUDIO_INTERFACE_2,
+ WM8962_AIF_RATE_MASK, aif2);
+}
+
+static int wm8962_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ if (level == codec->dapm.bias_level)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID 2*50k */
+ snd_soc_update_bits(codec, WM8962_PWR_MGMT_1,
+ WM8962_VMID_SEL_MASK, 0x80);
+
+ wm8962_configure_bclk(codec);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8962->supplies),
+ wm8962->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ wm8962_sync_cache(codec);
+
+ snd_soc_update_bits(codec, WM8962_ANTI_POP,
+ WM8962_STARTUP_BIAS_ENA |
+ WM8962_VMID_BUF_ENA,
+ WM8962_STARTUP_BIAS_ENA |
+ WM8962_VMID_BUF_ENA);
+
+ /* Bias enable at 2*50k for ramp */
+ snd_soc_update_bits(codec, WM8962_PWR_MGMT_1,
+ WM8962_VMID_SEL_MASK |
+ WM8962_BIAS_ENA,
+ WM8962_BIAS_ENA | 0x180);
+
+ msleep(5);
+ }
+
+ /* VMID 2*250k */
+ snd_soc_update_bits(codec, WM8962_PWR_MGMT_1,
+ WM8962_VMID_SEL_MASK, 0x100);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, WM8962_PWR_MGMT_1,
+ WM8962_VMID_SEL_MASK | WM8962_BIAS_ENA, 0);
+
+ snd_soc_update_bits(codec, WM8962_ANTI_POP,
+ WM8962_STARTUP_BIAS_ENA |
+ WM8962_VMID_BUF_ENA, 0);
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies),
+ wm8962->supplies);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static const struct {
+ int rate;
+ int reg;
+} sr_vals[] = {
+ { 48000, 0 },
+ { 44100, 0 },
+ { 32000, 1 },
+ { 22050, 2 },
+ { 24000, 2 },
+ { 16000, 3 },
+ { 11025, 4 },
+ { 12000, 4 },
+ { 8000, 5 },
+ { 88200, 6 },
+ { 96000, 6 },
+};
+
+static int wm8962_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ int i;
+ int aif0 = 0;
+ int adctl3 = 0;
+
+ wm8962->bclk = snd_soc_params_to_bclk(params);
+ if (params_channels(params) == 1)
+ wm8962->bclk *= 2;
+
+ wm8962->lrclk = params_rate(params);
+
+ for (i = 0; i < ARRAY_SIZE(sr_vals); i++) {
+ if (sr_vals[i].rate == wm8962->lrclk) {
+ adctl3 |= sr_vals[i].reg;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(sr_vals)) {
+ dev_err(codec->dev, "Unsupported rate %dHz\n", wm8962->lrclk);
+ return -EINVAL;
+ }
+
+ if (wm8962->lrclk % 8000 == 0)
+ adctl3 |= WM8962_SAMPLE_RATE_INT_MODE;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ aif0 |= 0x4;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ aif0 |= 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ aif0 |= 0xc;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8962_AUDIO_INTERFACE_0,
+ WM8962_WL_MASK, aif0);
+ snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_3,
+ WM8962_SAMPLE_RATE_INT_MODE |
+ WM8962_SAMPLE_RATE_MASK, adctl3);
+
+ wm8962_configure_bclk(codec);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ if (snd_soc_dapm_get_pin_status(&codec->dapm, "DMIC"))
+ snd_soc_update_bits(codec, WM8962_THREED1, WM8962_ADC_MONOMIX_MASK, 0);
+ else
+ snd_soc_update_bits(codec, WM8962_THREED1,
+ WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX);
+ }
+
+ return 0;
+}
+
+static int wm8962_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ int src;
+
+ switch (clk_id) {
+ case WM8962_SYSCLK_MCLK:
+ wm8962->sysclk = WM8962_SYSCLK_MCLK;
+ src = 0;
+ break;
+ case WM8962_SYSCLK_FLL:
+ wm8962->sysclk = WM8962_SYSCLK_FLL;
+ src = 1 << WM8962_SYSCLK_SRC_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8962_CLOCKING2, WM8962_SYSCLK_SRC_MASK,
+ src);
+
+ wm8962->sysclk_rate = freq;
+
+ return 0;
+}
+
+static int wm8962_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int aif0 = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_B:
+ aif0 |= WM8962_LRCLK_INV | 3;
+ case SND_SOC_DAIFMT_DSP_A:
+ aif0 |= 3;
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ case SND_SOC_DAIFMT_IB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif0 |= 1;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif0 |= 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif0 |= WM8962_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif0 |= WM8962_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif0 |= WM8962_BCLK_INV | WM8962_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif0 |= WM8962_MSTR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8962_AUDIO_INTERFACE_0,
+ WM8962_FMT_MASK | WM8962_BCLK_INV | WM8962_MSTR |
+ WM8962_LRCLK_INV, aif0);
+
+ return 0;
+}
+
+struct _fll_div {
+ u16 fll_fratio;
+ u16 fll_outdiv;
+ u16 fll_refclk_div;
+ u16 n;
+ u16 theta;
+ u16 lambda;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static struct {
+ unsigned int min;
+ unsigned int max;
+ u16 fll_fratio;
+ int ratio;
+} fll_fratios[] = {
+ { 0, 64000, 4, 16 },
+ { 64000, 128000, 3, 8 },
+ { 128000, 256000, 2, 4 },
+ { 256000, 1000000, 1, 2 },
+ { 1000000, 13500000, 0, 1 },
+};
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+ unsigned int Fout)
+{
+ unsigned int target;
+ unsigned int div;
+ unsigned int fratio, gcd_fll;
+ int i;
+
+ /* Fref must be <=13.5MHz */
+ div = 1;
+ fll_div->fll_refclk_div = 0;
+ while ((Fref / div) > 13500000) {
+ div *= 2;
+ fll_div->fll_refclk_div++;
+
+ if (div > 4) {
+ pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+ Fref);
+ return -EINVAL;
+ }
+ }
+
+ pr_debug("FLL Fref=%u Fout=%u\n", Fref, Fout);
+
+ /* Apply the division for our remaining calculations */
+ Fref /= div;
+
+ /* Fvco should be 90-100MHz; don't check the upper bound */
+ div = 2;
+ while (Fout * div < 90000000) {
+ div++;
+ if (div > 64) {
+ pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+ Fout);
+ return -EINVAL;
+ }
+ }
+ target = Fout * div;
+ fll_div->fll_outdiv = div - 1;
+
+ pr_debug("FLL Fvco=%dHz\n", target);
+
+ /* Find an appropriate FLL_FRATIO and factor it out of the target */
+ for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+ if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+ fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+ fratio = fll_fratios[i].ratio;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(fll_fratios)) {
+ pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+ return -EINVAL;
+ }
+
+ fll_div->n = target / (fratio * Fref);
+
+ if (target % Fref == 0) {
+ fll_div->theta = 0;
+ fll_div->lambda = 0;
+ } else {
+ gcd_fll = gcd(target, fratio * Fref);
+
+ fll_div->theta = (target - (fll_div->n * fratio * Fref))
+ / gcd_fll;
+ fll_div->lambda = (fratio * Fref) / gcd_fll;
+ }
+
+ pr_debug("FLL N=%x THETA=%x LAMBDA=%x\n",
+ fll_div->n, fll_div->theta, fll_div->lambda);
+ pr_debug("FLL_FRATIO=%x FLL_OUTDIV=%x FLL_REFCLK_DIV=%x\n",
+ fll_div->fll_fratio, fll_div->fll_outdiv,
+ fll_div->fll_refclk_div);
+
+ return 0;
+}
+
+static int wm8962_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
+ unsigned int Fref, unsigned int Fout)
+{
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ struct _fll_div fll_div = {0};
+ unsigned long timeout;
+ int ret;
+ int fll1 = snd_soc_read(codec, WM8962_FLL_CONTROL_1) & WM8962_FLL_ENA;
+
+ /* Any change? */
+ if (source == wm8962->fll_src && Fref == wm8962->fll_fref &&
+ Fout == wm8962->fll_fout)
+ return 0;
+
+ if (Fout == 0) {
+ dev_dbg(codec->dev, "FLL disabled\n");
+
+ wm8962->fll_fref = 0;
+ wm8962->fll_fout = 0;
+
+ snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
+ WM8962_FLL_ENA, 0);
+
+ return 0;
+ }
+
+ ret = fll_factors(&fll_div, Fref, Fout);
+ if (ret != 0)
+ return ret;
+
+ switch (fll_id) {
+ case WM8962_FLL_MCLK:
+ case WM8962_FLL_BCLK:
+ case WM8962_FLL_OSC:
+ fll1 |= (fll_id - 1) << WM8962_FLL_REFCLK_SRC_SHIFT;
+ break;
+ case WM8962_FLL_INT:
+ snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
+ WM8962_FLL_OSC_ENA, WM8962_FLL_OSC_ENA);
+ snd_soc_update_bits(codec, WM8962_FLL_CONTROL_5,
+ WM8962_FLL_FRC_NCO, WM8962_FLL_FRC_NCO);
+ break;
+ default:
+ dev_err(codec->dev, "Unknown FLL source %d\n", ret);
+ return -EINVAL;
+ }
+
+ if (fll_div.theta || fll_div.lambda)
+ fll1 |= WM8962_FLL_FRAC;
+
+ /* Stop the FLL while we reconfigure */
+ snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1, WM8962_FLL_ENA, 0);
+
+ snd_soc_update_bits(codec, WM8962_FLL_CONTROL_2,
+ WM8962_FLL_OUTDIV_MASK |
+ WM8962_FLL_REFCLK_DIV_MASK,
+ (fll_div.fll_outdiv << WM8962_FLL_OUTDIV_SHIFT) |
+ (fll_div.fll_refclk_div));
+
+ snd_soc_update_bits(codec, WM8962_FLL_CONTROL_3,
+ WM8962_FLL_FRATIO_MASK, fll_div.fll_fratio);
+
+ snd_soc_write(codec, WM8962_FLL_CONTROL_6, fll_div.theta);
+ snd_soc_write(codec, WM8962_FLL_CONTROL_7, fll_div.lambda);
+ snd_soc_write(codec, WM8962_FLL_CONTROL_8, fll_div.n);
+
+ try_wait_for_completion(&wm8962->fll_lock);
+
+ snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
+ WM8962_FLL_FRAC | WM8962_FLL_REFCLK_SRC_MASK |
+ WM8962_FLL_ENA, fll1);
+
+ dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
+
+ ret = 0;
+
+ if (fll1 & WM8962_FLL_ENA) {
+ /* This should be a massive overestimate but go even
+ * higher if we'll error out
+ */
+ if (wm8962->irq)
+ timeout = msecs_to_jiffies(5);
+ else
+ timeout = msecs_to_jiffies(1);
+
+ timeout = wait_for_completion_timeout(&wm8962->fll_lock,
+ timeout);
+
+ if (timeout == 0 && wm8962->irq) {
+ dev_err(codec->dev, "FLL lock timed out");
+ ret = -ETIMEDOUT;
+ }
+ }
+
+ wm8962->fll_fref = Fref;
+ wm8962->fll_fout = Fout;
+ wm8962->fll_src = source;
+
+ return ret;
+}
+
+static int wm8962_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int val;
+
+ if (mute)
+ val = WM8962_DAC_MUTE;
+ else
+ val = 0;
+
+ return snd_soc_update_bits(codec, WM8962_ADC_DAC_CONTROL_1,
+ WM8962_DAC_MUTE, val);
+}
+
+#define WM8962_RATES (SNDRV_PCM_RATE_8000_48000 |\
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define WM8962_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8962_dai_ops = {
+ .hw_params = wm8962_hw_params,
+ .set_sysclk = wm8962_set_dai_sysclk,
+ .set_fmt = wm8962_set_dai_fmt,
+ .digital_mute = wm8962_mute,
+};
+
+static struct snd_soc_dai_driver wm8962_dai = {
+ .name = "wm8962",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8962_RATES,
+ .formats = WM8962_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8962_RATES,
+ .formats = WM8962_FORMATS,
+ },
+ .ops = &wm8962_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static void wm8962_mic_work(struct work_struct *work)
+{
+ struct wm8962_priv *wm8962 = container_of(work,
+ struct wm8962_priv,
+ mic_work.work);
+ struct snd_soc_codec *codec = wm8962->codec;
+ int status = 0;
+ int irq_pol = 0;
+ int reg;
+
+ reg = snd_soc_read(codec, WM8962_ADDITIONAL_CONTROL_4);
+
+ if (reg & WM8962_MICDET_STS) {
+ status |= SND_JACK_MICROPHONE;
+ irq_pol |= WM8962_MICD_IRQ_POL;
+ }
+
+ if (reg & WM8962_MICSHORT_STS) {
+ status |= SND_JACK_BTN_0;
+ irq_pol |= WM8962_MICSCD_IRQ_POL;
+ }
+
+ snd_soc_jack_report(wm8962->jack, status,
+ SND_JACK_MICROPHONE | SND_JACK_BTN_0);
+
+ snd_soc_update_bits(codec, WM8962_MICINT_SOURCE_POL,
+ WM8962_MICSCD_IRQ_POL |
+ WM8962_MICD_IRQ_POL, irq_pol);
+}
+
+static irqreturn_t wm8962_irq(int irq, void *data)
+{
+ struct snd_soc_codec *codec = data;
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ int mask;
+ int active;
+ int reg;
+
+ mask = snd_soc_read(codec, WM8962_INTERRUPT_STATUS_2_MASK);
+
+ active = snd_soc_read(codec, WM8962_INTERRUPT_STATUS_2);
+ active &= ~mask;
+
+ if (!active)
+ return IRQ_NONE;
+
+ /* Acknowledge the interrupts */
+ snd_soc_write(codec, WM8962_INTERRUPT_STATUS_2, active);
+
+ if (active & WM8962_FLL_LOCK_EINT) {
+ dev_dbg(codec->dev, "FLL locked\n");
+ complete(&wm8962->fll_lock);
+ }
+
+ if (active & WM8962_FIFOS_ERR_EINT)
+ dev_err(codec->dev, "FIFO error\n");
+
+ if (active & WM8962_TEMP_SHUT_EINT) {
+ dev_crit(codec->dev, "Thermal shutdown\n");
+
+ reg = snd_soc_read(codec, WM8962_THERMAL_SHUTDOWN_STATUS);
+
+ if (reg & WM8962_TEMP_ERR_HP)
+ dev_crit(codec->dev, "Headphone thermal error\n");
+ if (reg & WM8962_TEMP_WARN_HP)
+ dev_crit(codec->dev, "Headphone thermal warning\n");
+ if (reg & WM8962_TEMP_ERR_SPK)
+ dev_crit(codec->dev, "Speaker thermal error\n");
+ if (reg & WM8962_TEMP_WARN_SPK)
+ dev_crit(codec->dev, "Speaker thermal warning\n");
+ }
+
+ if (active & (WM8962_MICSCD_EINT | WM8962_MICD_EINT)) {
+ dev_dbg(codec->dev, "Microphone event detected\n");
+
+#ifndef CONFIG_SND_SOC_WM8962_MODULE
+ trace_snd_soc_jack_irq(dev_name(codec->dev));
+#endif
+
+ pm_wakeup_event(codec->dev, 300);
+
+ schedule_delayed_work(&wm8962->mic_work,
+ msecs_to_jiffies(250));
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * wm8962_mic_detect - Enable microphone detection via the WM8962 IRQ
+ *
+ * @codec: WM8962 codec
+ * @jack: jack to report detection events on
+ *
+ * Enable microphone detection via IRQ on the WM8962. If GPIOs are
+ * being used to bring out signals to the processor then only platform
+ * data configuration is needed for WM8962 and processor GPIOs should
+ * be configured using snd_soc_jack_add_gpios() instead.
+ *
+ * If no jack is supplied detection will be disabled.
+ */
+int wm8962_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack)
+{
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ int irq_mask, enable;
+
+ wm8962->jack = jack;
+ if (jack) {
+ irq_mask = 0;
+ enable = WM8962_MICDET_ENA;
+ } else {
+ irq_mask = WM8962_MICD_EINT | WM8962_MICSCD_EINT;
+ enable = 0;
+ }
+
+ snd_soc_update_bits(codec, WM8962_INTERRUPT_STATUS_2_MASK,
+ WM8962_MICD_EINT | WM8962_MICSCD_EINT, irq_mask);
+ snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_4,
+ WM8962_MICDET_ENA, enable);
+
+ /* Send an initial empty report */
+ snd_soc_jack_report(wm8962->jack, 0,
+ SND_JACK_MICROPHONE | SND_JACK_BTN_0);
+
+ if (jack) {
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "SYSCLK");
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "MICBIAS");
+ } else {
+ snd_soc_dapm_disable_pin(&codec->dapm, "SYSCLK");
+ snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS");
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8962_mic_detect);
+
+#if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE)
+static int beep_rates[] = {
+ 500, 1000, 2000, 4000,
+};
+
+static void wm8962_beep_work(struct work_struct *work)
+{
+ struct wm8962_priv *wm8962 =
+ container_of(work, struct wm8962_priv, beep_work);
+ struct snd_soc_codec *codec = wm8962->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int i;
+ int reg = 0;
+ int best = 0;
+
+ if (wm8962->beep_rate) {
+ for (i = 0; i < ARRAY_SIZE(beep_rates); i++) {
+ if (abs(wm8962->beep_rate - beep_rates[i]) <
+ abs(wm8962->beep_rate - beep_rates[best]))
+ best = i;
+ }
+
+ dev_dbg(codec->dev, "Set beep rate %dHz for requested %dHz\n",
+ beep_rates[best], wm8962->beep_rate);
+
+ reg = WM8962_BEEP_ENA | (best << WM8962_BEEP_RATE_SHIFT);
+
+ snd_soc_dapm_enable_pin(dapm, "Beep");
+ } else {
+ dev_dbg(codec->dev, "Disabling beep\n");
+ snd_soc_dapm_disable_pin(dapm, "Beep");
+ }
+
+ snd_soc_update_bits(codec, WM8962_BEEP_GENERATOR_1,
+ WM8962_BEEP_ENA | WM8962_BEEP_RATE_MASK, reg);
+
+ snd_soc_dapm_sync(dapm);
+}
+
+/* For usability define a way of injecting beep events for the device -
+ * many systems will not have a keyboard.
+ */
+static int wm8962_beep_event(struct input_dev *dev, unsigned int type,
+ unsigned int code, int hz)
+{
+ struct snd_soc_codec *codec = input_get_drvdata(dev);
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+
+ dev_dbg(codec->dev, "Beep event %x %x\n", code, hz);
+
+ switch (code) {
+ case SND_BELL:
+ if (hz)
+ hz = 1000;
+ case SND_TONE:
+ break;
+ default:
+ return -1;
+ }
+
+ /* Kick the beep from a workqueue */
+ wm8962->beep_rate = hz;
+ schedule_work(&wm8962->beep_work);
+ return 0;
+}
+
+static ssize_t wm8962_beep_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct wm8962_priv *wm8962 = dev_get_drvdata(dev);
+ long int time;
+ int ret;
+
+ ret = strict_strtol(buf, 10, &time);
+ if (ret != 0)
+ return ret;
+
+ input_event(wm8962->beep, EV_SND, SND_TONE, time);
+
+ return count;
+}
+
+static DEVICE_ATTR(beep, 0200, NULL, wm8962_beep_set);
+
+static void wm8962_init_beep(struct snd_soc_codec *codec)
+{
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ wm8962->beep = input_allocate_device();
+ if (!wm8962->beep) {
+ dev_err(codec->dev, "Failed to allocate beep device\n");
+ return;
+ }
+
+ INIT_WORK(&wm8962->beep_work, wm8962_beep_work);
+ wm8962->beep_rate = 0;
+
+ wm8962->beep->name = "WM8962 Beep Generator";
+ wm8962->beep->phys = dev_name(codec->dev);
+ wm8962->beep->id.bustype = BUS_I2C;
+
+ wm8962->beep->evbit[0] = BIT_MASK(EV_SND);
+ wm8962->beep->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+ wm8962->beep->event = wm8962_beep_event;
+ wm8962->beep->dev.parent = codec->dev;
+ input_set_drvdata(wm8962->beep, codec);
+
+ ret = input_register_device(wm8962->beep);
+ if (ret != 0) {
+ input_free_device(wm8962->beep);
+ wm8962->beep = NULL;
+ dev_err(codec->dev, "Failed to register beep device\n");
+ }
+
+ ret = device_create_file(codec->dev, &dev_attr_beep);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to create keyclick file: %d\n",
+ ret);
+ }
+}
+
+static void wm8962_free_beep(struct snd_soc_codec *codec)
+{
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+
+ device_remove_file(codec->dev, &dev_attr_beep);
+ input_unregister_device(wm8962->beep);
+ cancel_work_sync(&wm8962->beep_work);
+ wm8962->beep = NULL;
+
+ snd_soc_update_bits(codec, WM8962_BEEP_GENERATOR_1, WM8962_BEEP_ENA,0);
+}
+#else
+static void wm8962_init_beep(struct snd_soc_codec *codec)
+{
+}
+
+static void wm8962_free_beep(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+static void wm8962_set_gpio_mode(struct snd_soc_codec *codec, int gpio)
+{
+ int mask = 0;
+ int val = 0;
+
+ /* Some of the GPIOs are behind MFP configuration and need to
+ * be put into GPIO mode. */
+ switch (gpio) {
+ case 2:
+ mask = WM8962_CLKOUT2_SEL_MASK;
+ val = 1 << WM8962_CLKOUT2_SEL_SHIFT;
+ break;
+ case 3:
+ mask = WM8962_CLKOUT3_SEL_MASK;
+ val = 1 << WM8962_CLKOUT3_SEL_SHIFT;
+ break;
+ default:
+ break;
+ }
+
+ if (mask)
+ snd_soc_update_bits(codec, WM8962_ANALOGUE_CLOCKING1,
+ mask, val);
+}
+
+#ifdef CONFIG_GPIOLIB
+static inline struct wm8962_priv *gpio_to_wm8962(struct gpio_chip *chip)
+{
+ return container_of(chip, struct wm8962_priv, gpio_chip);
+}
+
+static int wm8962_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8962_priv *wm8962 = gpio_to_wm8962(chip);
+ struct snd_soc_codec *codec = wm8962->codec;
+
+ /* The WM8962 GPIOs aren't linearly numbered. For simplicity
+ * we export linear numbers and error out if the unsupported
+ * ones are requsted.
+ */
+ switch (offset + 1) {
+ case 2:
+ case 3:
+ case 5:
+ case 6:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8962_set_gpio_mode(codec, offset + 1);
+
+ return 0;
+}
+
+static void wm8962_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct wm8962_priv *wm8962 = gpio_to_wm8962(chip);
+ struct snd_soc_codec *codec = wm8962->codec;
+
+ snd_soc_update_bits(codec, WM8962_GPIO_BASE + offset,
+ WM8962_GP2_LVL, !!value << WM8962_GP2_LVL_SHIFT);
+}
+
+static int wm8962_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct wm8962_priv *wm8962 = gpio_to_wm8962(chip);
+ struct snd_soc_codec *codec = wm8962->codec;
+ int val;
+
+ /* Force function 1 (logic output) */
+ val = (1 << WM8962_GP2_FN_SHIFT) | (value << WM8962_GP2_LVL_SHIFT);
+
+ return snd_soc_update_bits(codec, WM8962_GPIO_BASE + offset,
+ WM8962_GP2_FN_MASK | WM8962_GP2_LVL, val);
+}
+
+static struct gpio_chip wm8962_template_chip = {
+ .label = "wm8962",
+ .owner = THIS_MODULE,
+ .request = wm8962_gpio_request,
+ .direction_output = wm8962_gpio_direction_out,
+ .set = wm8962_gpio_set,
+ .can_sleep = 1,
+};
+
+static void wm8962_init_gpio(struct snd_soc_codec *codec)
+{
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ struct wm8962_pdata *pdata = dev_get_platdata(codec->dev);
+ int ret;
+
+ wm8962->gpio_chip = wm8962_template_chip;
+ wm8962->gpio_chip.ngpio = WM8962_MAX_GPIO;
+ wm8962->gpio_chip.dev = codec->dev;
+
+ if (pdata && pdata->gpio_base)
+ wm8962->gpio_chip.base = pdata->gpio_base;
+ else
+ wm8962->gpio_chip.base = -1;
+
+ ret = gpiochip_add(&wm8962->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add GPIOs: %d\n", ret);
+}
+
+static void wm8962_free_gpio(struct snd_soc_codec *codec)
+{
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = gpiochip_remove(&wm8962->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to remove GPIOs: %d\n", ret);
+}
+#else
+static void wm8962_init_gpio(struct snd_soc_codec *codec)
+{
+}
+
+static void wm8962_free_gpio(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+static int wm8962_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ struct wm8962_pdata *pdata = dev_get_platdata(codec->dev);
+ u16 *reg_cache = codec->reg_cache;
+ int i, trigger, irq_pol;
+ bool dmicclk, dmicdat;
+
+ wm8962->codec = codec;
+ INIT_DELAYED_WORK(&wm8962->mic_work, wm8962_mic_work);
+ init_completion(&wm8962->fll_lock);
+
+ codec->cache_sync = 1;
+ codec->dapm.idle_bias_off = 1;
+
+ ret = snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ goto err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++)
+ wm8962->supplies[i].supply = wm8962_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8962->supplies),
+ wm8962->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ goto err;
+ }
+
+ wm8962->disable_nb[0].notifier_call = wm8962_regulator_event_0;
+ wm8962->disable_nb[1].notifier_call = wm8962_regulator_event_1;
+ wm8962->disable_nb[2].notifier_call = wm8962_regulator_event_2;
+ wm8962->disable_nb[3].notifier_call = wm8962_regulator_event_3;
+ wm8962->disable_nb[4].notifier_call = wm8962_regulator_event_4;
+ wm8962->disable_nb[5].notifier_call = wm8962_regulator_event_5;
+ wm8962->disable_nb[6].notifier_call = wm8962_regulator_event_6;
+ wm8962->disable_nb[7].notifier_call = wm8962_regulator_event_7;
+
+ /* This should really be moved into the regulator core */
+ for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++) {
+ ret = regulator_register_notifier(wm8962->supplies[i].consumer,
+ &wm8962->disable_nb[i]);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to register regulator notifier: %d\n",
+ ret);
+ }
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8962->supplies),
+ wm8962->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+ msleep(100);
+ ret = snd_soc_read(codec, WM8962_SOFTWARE_RESET);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read ID register\n");
+ goto err_enable;
+ }
+ if (ret != wm8962_reg[WM8962_SOFTWARE_RESET]) {
+ dev_err(codec->dev, "Device is not a WM8962, ID %x != %x\n",
+ ret, wm8962_reg[WM8962_SOFTWARE_RESET]);
+ ret = -EINVAL;
+ goto err_enable;
+ }
+
+ ret = snd_soc_read(codec, WM8962_RIGHT_INPUT_VOLUME);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read device revision: %d\n",
+ ret);
+ goto err_enable;
+ }
+
+ dev_info(codec->dev, "customer id %x revision %c\n",
+ (ret & WM8962_CUST_ID_MASK) >> WM8962_CUST_ID_SHIFT,
+ ((ret & WM8962_CHIP_REV_MASK) >> WM8962_CHIP_REV_SHIFT)
+ + 'A');
+
+ ret = wm8962_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ goto err_enable;
+ }
+
+ /* SYSCLK defaults to on; make sure it is off so we can safely
+ * write to registers if the device is declocked.
+ */
+ snd_soc_update_bits(codec, WM8962_CLOCKING2, WM8962_SYSCLK_ENA, 0);
+
+ /* Ensure we have soft control over all registers */
+ snd_soc_update_bits(codec, WM8962_CLOCKING2,
+ WM8962_CLKREG_OVD, WM8962_CLKREG_OVD);
+
+ /* Ensure that the oscillator and PLLs are disabled */
+ snd_soc_update_bits(codec, WM8962_PLL2,
+ WM8962_OSC_ENA | WM8962_PLL2_ENA | WM8962_PLL3_ENA,
+ 0);
+
+/* regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), wm8962->supplies);*/
+
+ if (pdata) {
+ /* Apply static configuration for GPIOs */
+ for (i = 0; i < ARRAY_SIZE(pdata->gpio_init); i++)
+ if (pdata->gpio_init[i]) {
+ wm8962_set_gpio_mode(codec, i + 1);
+ snd_soc_write(codec, 0x200 + i,
+ pdata->gpio_init[i] & 0xffff);
+ }
+
+ /* Put the speakers into mono mode? */
+ if (pdata->spk_mono)
+ reg_cache[WM8962_CLASS_D_CONTROL_2]
+ |= WM8962_SPK_MONO;
+
+ /* Micbias setup, detection enable and detection
+ * threasholds. */
+ if (pdata->mic_cfg)
+ snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_4,
+ WM8962_MICDET_ENA |
+ WM8962_MICDET_THR_MASK |
+ WM8962_MICSHORT_THR_MASK |
+ WM8962_MICBIAS_LVL,
+ pdata->mic_cfg);
+ }
+
+ /* set the default volume for playback and record*/
+ snd_soc_update_bits(codec, WM8962_HPOUTL_VOLUME,
+ WM8962_HPOUTL_VOL_MASK, 0x5d);
+ snd_soc_update_bits(codec, WM8962_HPOUTR_VOLUME,
+ WM8962_HPOUTR_VOL_MASK, 0x5d);
+ snd_soc_update_bits(codec, WM8962_SPKOUTL_VOLUME,
+ WM8962_SPKOUTL_VOL_MASK, 0x72);
+ snd_soc_update_bits(codec, WM8962_SPKOUTR_VOLUME,
+ WM8962_SPKOUTR_VOL_MASK, 0x72);
+
+ snd_soc_update_bits(codec, WM8962_LEFT_INPUT_VOLUME,
+ WM8962_INL_VOL_MASK, 0x3f);
+ snd_soc_update_bits(codec, WM8962_RIGHT_INPUT_VOLUME,
+ WM8962_INR_VOL_MASK, 0x3f);
+ snd_soc_update_bits(codec, WM8962_LEFT_ADC_VOLUME,
+ WM8962_ADCL_VOL_MASK, 0xd8);
+ snd_soc_update_bits(codec, WM8962_RIGHT_ADC_VOLUME,
+ WM8962_ADCR_VOL_MASK, 0xd8);
+ snd_soc_update_bits(codec, WM8962_RIGHT_INPUT_MIXER_VOLUME,
+ WM8962_IN3R_MIXINR_VOL_MASK, 0x7);
+
+
+ /* Latch volume update bits */
+ snd_soc_update_bits(codec, WM8962_LEFT_INPUT_VOLUME,
+ WM8962_IN_VU, WM8962_IN_VU);
+ snd_soc_update_bits(codec, WM8962_RIGHT_INPUT_VOLUME,
+ WM8962_IN_VU, WM8962_IN_VU);
+ snd_soc_update_bits(codec, WM8962_LEFT_ADC_VOLUME,
+ WM8962_ADC_VU, WM8962_ADC_VU);
+ snd_soc_update_bits(codec, WM8962_RIGHT_ADC_VOLUME,
+ WM8962_ADC_VU, WM8962_ADC_VU);
+ snd_soc_update_bits(codec, WM8962_LEFT_DAC_VOLUME,
+ WM8962_DAC_VU, WM8962_DAC_VU);
+ snd_soc_update_bits(codec, WM8962_RIGHT_DAC_VOLUME,
+ WM8962_DAC_VU, WM8962_DAC_VU);
+ snd_soc_update_bits(codec, WM8962_SPKOUTL_VOLUME,
+ WM8962_SPKOUT_VU, WM8962_SPKOUT_VU);
+ snd_soc_update_bits(codec, WM8962_SPKOUTR_VOLUME,
+ WM8962_SPKOUT_VU, WM8962_SPKOUT_VU);
+ snd_soc_update_bits(codec, WM8962_HPOUTL_VOLUME,
+ WM8962_HPOUT_VU, WM8962_HPOUT_VU);
+ snd_soc_update_bits(codec, WM8962_HPOUTR_VOLUME,
+ WM8962_HPOUT_VU, WM8962_HPOUT_VU);
+
+ /* Stereo control for EQ */
+ snd_soc_update_bits(codec, WM8962_EQ1, WM8962_EQ_SHARED_COEFF, 0);
+
+ wm8962_add_widgets(codec);
+
+ /* Save boards having to disable DMIC when not in use */
+ dmicclk = false;
+ dmicdat = false;
+ for (i = 0; i < WM8962_MAX_GPIO; i++) {
+ switch (snd_soc_read(codec, WM8962_GPIO_BASE + i)
+ & WM8962_GP2_FN_MASK) {
+ case WM8962_GPIO_FN_DMICCLK:
+ dmicclk = true;
+ break;
+ case WM8962_GPIO_FN_DMICDAT:
+ dmicdat = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (!dmicclk || !dmicdat) {
+ dev_dbg(codec->dev, "DMIC not in use, disabling\n");
+ snd_soc_dapm_nc_pin(&codec->dapm, "DMICDAT");
+ }
+ if (dmicclk != dmicdat)
+ dev_warn(codec->dev, "DMIC GPIOs partially configured\n");
+
+ wm8962_init_beep(codec);
+ wm8962_init_gpio(codec);
+
+ if (wm8962->irq) {
+ if (pdata && pdata->irq_active_low) {
+ trigger = IRQF_TRIGGER_LOW;
+ irq_pol = WM8962_IRQ_POL;
+ } else {
+ trigger = IRQF_TRIGGER_HIGH;
+ irq_pol = 0;
+ }
+
+ snd_soc_update_bits(codec, WM8962_INTERRUPT_CONTROL,
+ WM8962_IRQ_POL, irq_pol);
+
+ ret = request_threaded_irq(wm8962->irq, NULL, wm8962_irq,
+ trigger | IRQF_ONESHOT,
+ "wm8962", codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request IRQ %d: %d\n",
+ wm8962->irq, ret);
+ wm8962->irq = 0;
+ /* Non-fatal */
+ } else {
+ /* Enable some IRQs by default */
+ snd_soc_update_bits(codec,
+ WM8962_INTERRUPT_STATUS_2_MASK,
+ WM8962_FLL_LOCK_EINT |
+ WM8962_TEMP_SHUT_EINT |
+ WM8962_FIFOS_ERR_EINT, 0);
+ }
+ }
+
+ return 0;
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), wm8962->supplies);
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8962->supplies), wm8962->supplies);
+err:
+ return ret;
+}
+
+static int wm8962_remove(struct snd_soc_codec *codec)
+{
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ if (wm8962->irq)
+ free_irq(wm8962->irq, codec);
+
+ cancel_delayed_work_sync(&wm8962->mic_work);
+
+ wm8962_free_gpio(codec);
+ wm8962_free_beep(codec);
+ for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++)
+ regulator_unregister_notifier(wm8962->supplies[i].consumer,
+ &wm8962->disable_nb[i]);
+ regulator_bulk_free(ARRAY_SIZE(wm8962->supplies), wm8962->supplies);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8962 = {
+ .probe = wm8962_probe,
+ .remove = wm8962_remove,
+ .set_bias_level = wm8962_set_bias_level,
+ .reg_cache_size = WM8962_MAX_REGISTER + 1,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8962_reg,
+ .volatile_register = wm8962_volatile_register,
+ .readable_register = wm8962_readable_register,
+ .set_pll = wm8962_set_fll,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8962_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8962_priv *wm8962;
+ int ret;
+
+ wm8962 = kzalloc(sizeof(struct wm8962_priv), GFP_KERNEL);
+ if (wm8962 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8962);
+
+ wm8962->irq = i2c->irq;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8962, &wm8962_dai, 1);
+ if (ret < 0)
+ kfree(wm8962);
+
+ return ret;
+}
+
+static __devexit int wm8962_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8962_i2c_id[] = {
+ { "wm8962", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8962_i2c_id);
+
+static struct i2c_driver wm8962_i2c_driver = {
+ .driver = {
+ .name = "wm8962",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8962_i2c_probe,
+ .remove = __devexit_p(wm8962_i2c_remove),
+ .id_table = wm8962_i2c_id,
+};
+#endif
+
+static int __init wm8962_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8962_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8962 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return 0;
+}
+module_init(wm8962_modinit);
+
+static void __exit wm8962_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8962_i2c_driver);
+#endif
+}
+module_exit(wm8962_exit);
+
+MODULE_DESCRIPTION("ASoC WM8962 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8962.h b/sound/soc/codecs/wm8962.h
new file mode 100644
index 00000000..a1a5d529
--- /dev/null
+++ b/sound/soc/codecs/wm8962.h
@@ -0,0 +1,3780 @@
+/*
+ * wm8962.h -- WM8962 ASoC driver
+ *
+ * Copyright 2010 Wolfson Microelectronics, plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8962_H
+#define _WM8962_H
+
+#include <asm/types.h>
+#include <sound/soc.h>
+
+#define WM8962_SYSCLK_MCLK 1
+#define WM8962_SYSCLK_FLL 2
+#define WM8962_SYSCLK_PLL3 3
+
+#define WM8962_FLL 1
+
+#define WM8962_FLL_MCLK 1
+#define WM8962_FLL_BCLK 2
+#define WM8962_FLL_OSC 3
+#define WM8962_FLL_INT 4
+
+/*
+ * Register values.
+ */
+#define WM8962_LEFT_INPUT_VOLUME 0x00
+#define WM8962_RIGHT_INPUT_VOLUME 0x01
+#define WM8962_HPOUTL_VOLUME 0x02
+#define WM8962_HPOUTR_VOLUME 0x03
+#define WM8962_CLOCKING1 0x04
+#define WM8962_ADC_DAC_CONTROL_1 0x05
+#define WM8962_ADC_DAC_CONTROL_2 0x06
+#define WM8962_AUDIO_INTERFACE_0 0x07
+#define WM8962_CLOCKING2 0x08
+#define WM8962_AUDIO_INTERFACE_1 0x09
+#define WM8962_LEFT_DAC_VOLUME 0x0A
+#define WM8962_RIGHT_DAC_VOLUME 0x0B
+#define WM8962_AUDIO_INTERFACE_2 0x0E
+#define WM8962_SOFTWARE_RESET 0x0F
+#define WM8962_ALC1 0x11
+#define WM8962_ALC2 0x12
+#define WM8962_ALC3 0x13
+#define WM8962_NOISE_GATE 0x14
+#define WM8962_LEFT_ADC_VOLUME 0x15
+#define WM8962_RIGHT_ADC_VOLUME 0x16
+#define WM8962_ADDITIONAL_CONTROL_1 0x17
+#define WM8962_ADDITIONAL_CONTROL_2 0x18
+#define WM8962_PWR_MGMT_1 0x19
+#define WM8962_PWR_MGMT_2 0x1A
+#define WM8962_ADDITIONAL_CONTROL_3 0x1B
+#define WM8962_ANTI_POP 0x1C
+#define WM8962_CLOCKING_3 0x1E
+#define WM8962_INPUT_MIXER_CONTROL_1 0x1F
+#define WM8962_LEFT_INPUT_MIXER_VOLUME 0x20
+#define WM8962_RIGHT_INPUT_MIXER_VOLUME 0x21
+#define WM8962_INPUT_MIXER_CONTROL_2 0x22
+#define WM8962_INPUT_BIAS_CONTROL 0x23
+#define WM8962_LEFT_INPUT_PGA_CONTROL 0x25
+#define WM8962_RIGHT_INPUT_PGA_CONTROL 0x26
+#define WM8962_SPKOUTL_VOLUME 0x28
+#define WM8962_SPKOUTR_VOLUME 0x29
+#define WM8962_THERMAL_SHUTDOWN_STATUS 0x2F
+#define WM8962_ADDITIONAL_CONTROL_4 0x30
+#define WM8962_CLASS_D_CONTROL_1 0x31
+#define WM8962_CLASS_D_CONTROL_2 0x33
+#define WM8962_CLOCKING_4 0x38
+#define WM8962_DAC_DSP_MIXING_1 0x39
+#define WM8962_DAC_DSP_MIXING_2 0x3A
+#define WM8962_DC_SERVO_0 0x3C
+#define WM8962_DC_SERVO_1 0x3D
+#define WM8962_DC_SERVO_4 0x40
+#define WM8962_DC_SERVO_6 0x42
+#define WM8962_ANALOGUE_PGA_BIAS 0x44
+#define WM8962_ANALOGUE_HP_0 0x45
+#define WM8962_ANALOGUE_HP_2 0x47
+#define WM8962_CHARGE_PUMP_1 0x48
+#define WM8962_CHARGE_PUMP_B 0x52
+#define WM8962_WRITE_SEQUENCER_CONTROL_1 0x57
+#define WM8962_WRITE_SEQUENCER_CONTROL_2 0x5A
+#define WM8962_WRITE_SEQUENCER_CONTROL_3 0x5D
+#define WM8962_CONTROL_INTERFACE 0x5E
+#define WM8962_MIXER_ENABLES 0x63
+#define WM8962_HEADPHONE_MIXER_1 0x64
+#define WM8962_HEADPHONE_MIXER_2 0x65
+#define WM8962_HEADPHONE_MIXER_3 0x66
+#define WM8962_HEADPHONE_MIXER_4 0x67
+#define WM8962_SPEAKER_MIXER_1 0x69
+#define WM8962_SPEAKER_MIXER_2 0x6A
+#define WM8962_SPEAKER_MIXER_3 0x6B
+#define WM8962_SPEAKER_MIXER_4 0x6C
+#define WM8962_SPEAKER_MIXER_5 0x6D
+#define WM8962_BEEP_GENERATOR_1 0x6E
+#define WM8962_OSCILLATOR_TRIM_3 0x73
+#define WM8962_OSCILLATOR_TRIM_4 0x74
+#define WM8962_OSCILLATOR_TRIM_7 0x77
+#define WM8962_ANALOGUE_CLOCKING1 0x7C
+#define WM8962_ANALOGUE_CLOCKING2 0x7D
+#define WM8962_ANALOGUE_CLOCKING3 0x7E
+#define WM8962_PLL_SOFTWARE_RESET 0x7F
+#define WM8962_PLL2 0x81
+#define WM8962_PLL_4 0x83
+#define WM8962_PLL_9 0x88
+#define WM8962_PLL_10 0x89
+#define WM8962_PLL_11 0x8A
+#define WM8962_PLL_12 0x8B
+#define WM8962_PLL_13 0x8C
+#define WM8962_PLL_14 0x8D
+#define WM8962_PLL_15 0x8E
+#define WM8962_PLL_16 0x8F
+#define WM8962_FLL_CONTROL_1 0x9B
+#define WM8962_FLL_CONTROL_2 0x9C
+#define WM8962_FLL_CONTROL_3 0x9D
+#define WM8962_FLL_CONTROL_5 0x9F
+#define WM8962_FLL_CONTROL_6 0xA0
+#define WM8962_FLL_CONTROL_7 0xA1
+#define WM8962_FLL_CONTROL_8 0xA2
+#define WM8962_GENERAL_TEST_1 0xFC
+#define WM8962_DF1 0x100
+#define WM8962_DF2 0x101
+#define WM8962_DF3 0x102
+#define WM8962_DF4 0x103
+#define WM8962_DF5 0x104
+#define WM8962_DF6 0x105
+#define WM8962_DF7 0x106
+#define WM8962_LHPF1 0x108
+#define WM8962_LHPF2 0x109
+#define WM8962_THREED1 0x10C
+#define WM8962_THREED2 0x10D
+#define WM8962_THREED3 0x10E
+#define WM8962_THREED4 0x10F
+#define WM8962_DRC_1 0x114
+#define WM8962_DRC_2 0x115
+#define WM8962_DRC_3 0x116
+#define WM8962_DRC_4 0x117
+#define WM8962_DRC_5 0x118
+#define WM8962_TLOOPBACK 0x11D
+#define WM8962_EQ1 0x14F
+#define WM8962_EQ2 0x150
+#define WM8962_EQ3 0x151
+#define WM8962_EQ4 0x152
+#define WM8962_EQ5 0x153
+#define WM8962_EQ6 0x154
+#define WM8962_EQ7 0x155
+#define WM8962_EQ8 0x156
+#define WM8962_EQ9 0x157
+#define WM8962_EQ10 0x158
+#define WM8962_EQ11 0x159
+#define WM8962_EQ12 0x15A
+#define WM8962_EQ13 0x15B
+#define WM8962_EQ14 0x15C
+#define WM8962_EQ15 0x15D
+#define WM8962_EQ16 0x15E
+#define WM8962_EQ17 0x15F
+#define WM8962_EQ18 0x160
+#define WM8962_EQ19 0x161
+#define WM8962_EQ20 0x162
+#define WM8962_EQ21 0x163
+#define WM8962_EQ22 0x164
+#define WM8962_EQ23 0x165
+#define WM8962_EQ24 0x166
+#define WM8962_EQ25 0x167
+#define WM8962_EQ26 0x168
+#define WM8962_EQ27 0x169
+#define WM8962_EQ28 0x16A
+#define WM8962_EQ29 0x16B
+#define WM8962_EQ30 0x16C
+#define WM8962_EQ31 0x16D
+#define WM8962_EQ32 0x16E
+#define WM8962_EQ33 0x16F
+#define WM8962_EQ34 0x170
+#define WM8962_EQ35 0x171
+#define WM8962_EQ36 0x172
+#define WM8962_EQ37 0x173
+#define WM8962_EQ38 0x174
+#define WM8962_EQ39 0x175
+#define WM8962_EQ40 0x176
+#define WM8962_EQ41 0x177
+#define WM8962_GPIO_BASE 0x200
+#define WM8962_GPIO_2 0x201
+#define WM8962_GPIO_3 0x202
+#define WM8962_GPIO_5 0x204
+#define WM8962_GPIO_6 0x205
+#define WM8962_INTERRUPT_STATUS_1 0x230
+#define WM8962_INTERRUPT_STATUS_2 0x231
+#define WM8962_INTERRUPT_STATUS_1_MASK 0x238
+#define WM8962_INTERRUPT_STATUS_2_MASK 0x239
+#define WM8962_INTERRUPT_CONTROL 0x240
+#define WM8962_IRQ_DEBOUNCE 0x248
+#define WM8962_MICINT_SOURCE_POL 0x24A
+#define WM8962_DSP2_POWER_MANAGEMENT 0x300
+#define WM8962_DSP2_EXECCONTROL 0x40D
+#define WM8962_WRITE_SEQUENCER_0 0x1000
+#define WM8962_WRITE_SEQUENCER_1 0x1001
+#define WM8962_WRITE_SEQUENCER_2 0x1002
+#define WM8962_WRITE_SEQUENCER_3 0x1003
+#define WM8962_WRITE_SEQUENCER_4 0x1004
+#define WM8962_WRITE_SEQUENCER_5 0x1005
+#define WM8962_WRITE_SEQUENCER_6 0x1006
+#define WM8962_WRITE_SEQUENCER_7 0x1007
+#define WM8962_WRITE_SEQUENCER_8 0x1008
+#define WM8962_WRITE_SEQUENCER_9 0x1009
+#define WM8962_WRITE_SEQUENCER_10 0x100A
+#define WM8962_WRITE_SEQUENCER_11 0x100B
+#define WM8962_WRITE_SEQUENCER_12 0x100C
+#define WM8962_WRITE_SEQUENCER_13 0x100D
+#define WM8962_WRITE_SEQUENCER_14 0x100E
+#define WM8962_WRITE_SEQUENCER_15 0x100F
+#define WM8962_WRITE_SEQUENCER_16 0x1010
+#define WM8962_WRITE_SEQUENCER_17 0x1011
+#define WM8962_WRITE_SEQUENCER_18 0x1012
+#define WM8962_WRITE_SEQUENCER_19 0x1013
+#define WM8962_WRITE_SEQUENCER_20 0x1014
+#define WM8962_WRITE_SEQUENCER_21 0x1015
+#define WM8962_WRITE_SEQUENCER_22 0x1016
+#define WM8962_WRITE_SEQUENCER_23 0x1017
+#define WM8962_WRITE_SEQUENCER_24 0x1018
+#define WM8962_WRITE_SEQUENCER_25 0x1019
+#define WM8962_WRITE_SEQUENCER_26 0x101A
+#define WM8962_WRITE_SEQUENCER_27 0x101B
+#define WM8962_WRITE_SEQUENCER_28 0x101C
+#define WM8962_WRITE_SEQUENCER_29 0x101D
+#define WM8962_WRITE_SEQUENCER_30 0x101E
+#define WM8962_WRITE_SEQUENCER_31 0x101F
+#define WM8962_WRITE_SEQUENCER_32 0x1020
+#define WM8962_WRITE_SEQUENCER_33 0x1021
+#define WM8962_WRITE_SEQUENCER_34 0x1022
+#define WM8962_WRITE_SEQUENCER_35 0x1023
+#define WM8962_WRITE_SEQUENCER_36 0x1024
+#define WM8962_WRITE_SEQUENCER_37 0x1025
+#define WM8962_WRITE_SEQUENCER_38 0x1026
+#define WM8962_WRITE_SEQUENCER_39 0x1027
+#define WM8962_WRITE_SEQUENCER_40 0x1028
+#define WM8962_WRITE_SEQUENCER_41 0x1029
+#define WM8962_WRITE_SEQUENCER_42 0x102A
+#define WM8962_WRITE_SEQUENCER_43 0x102B
+#define WM8962_WRITE_SEQUENCER_44 0x102C
+#define WM8962_WRITE_SEQUENCER_45 0x102D
+#define WM8962_WRITE_SEQUENCER_46 0x102E
+#define WM8962_WRITE_SEQUENCER_47 0x102F
+#define WM8962_WRITE_SEQUENCER_48 0x1030
+#define WM8962_WRITE_SEQUENCER_49 0x1031
+#define WM8962_WRITE_SEQUENCER_50 0x1032
+#define WM8962_WRITE_SEQUENCER_51 0x1033
+#define WM8962_WRITE_SEQUENCER_52 0x1034
+#define WM8962_WRITE_SEQUENCER_53 0x1035
+#define WM8962_WRITE_SEQUENCER_54 0x1036
+#define WM8962_WRITE_SEQUENCER_55 0x1037
+#define WM8962_WRITE_SEQUENCER_56 0x1038
+#define WM8962_WRITE_SEQUENCER_57 0x1039
+#define WM8962_WRITE_SEQUENCER_58 0x103A
+#define WM8962_WRITE_SEQUENCER_59 0x103B
+#define WM8962_WRITE_SEQUENCER_60 0x103C
+#define WM8962_WRITE_SEQUENCER_61 0x103D
+#define WM8962_WRITE_SEQUENCER_62 0x103E
+#define WM8962_WRITE_SEQUENCER_63 0x103F
+#define WM8962_WRITE_SEQUENCER_64 0x1040
+#define WM8962_WRITE_SEQUENCER_65 0x1041
+#define WM8962_WRITE_SEQUENCER_66 0x1042
+#define WM8962_WRITE_SEQUENCER_67 0x1043
+#define WM8962_WRITE_SEQUENCER_68 0x1044
+#define WM8962_WRITE_SEQUENCER_69 0x1045
+#define WM8962_WRITE_SEQUENCER_70 0x1046
+#define WM8962_WRITE_SEQUENCER_71 0x1047
+#define WM8962_WRITE_SEQUENCER_72 0x1048
+#define WM8962_WRITE_SEQUENCER_73 0x1049
+#define WM8962_WRITE_SEQUENCER_74 0x104A
+#define WM8962_WRITE_SEQUENCER_75 0x104B
+#define WM8962_WRITE_SEQUENCER_76 0x104C
+#define WM8962_WRITE_SEQUENCER_77 0x104D
+#define WM8962_WRITE_SEQUENCER_78 0x104E
+#define WM8962_WRITE_SEQUENCER_79 0x104F
+#define WM8962_WRITE_SEQUENCER_80 0x1050
+#define WM8962_WRITE_SEQUENCER_81 0x1051
+#define WM8962_WRITE_SEQUENCER_82 0x1052
+#define WM8962_WRITE_SEQUENCER_83 0x1053
+#define WM8962_WRITE_SEQUENCER_84 0x1054
+#define WM8962_WRITE_SEQUENCER_85 0x1055
+#define WM8962_WRITE_SEQUENCER_86 0x1056
+#define WM8962_WRITE_SEQUENCER_87 0x1057
+#define WM8962_WRITE_SEQUENCER_88 0x1058
+#define WM8962_WRITE_SEQUENCER_89 0x1059
+#define WM8962_WRITE_SEQUENCER_90 0x105A
+#define WM8962_WRITE_SEQUENCER_91 0x105B
+#define WM8962_WRITE_SEQUENCER_92 0x105C
+#define WM8962_WRITE_SEQUENCER_93 0x105D
+#define WM8962_WRITE_SEQUENCER_94 0x105E
+#define WM8962_WRITE_SEQUENCER_95 0x105F
+#define WM8962_WRITE_SEQUENCER_96 0x1060
+#define WM8962_WRITE_SEQUENCER_97 0x1061
+#define WM8962_WRITE_SEQUENCER_98 0x1062
+#define WM8962_WRITE_SEQUENCER_99 0x1063
+#define WM8962_WRITE_SEQUENCER_100 0x1064
+#define WM8962_WRITE_SEQUENCER_101 0x1065
+#define WM8962_WRITE_SEQUENCER_102 0x1066
+#define WM8962_WRITE_SEQUENCER_103 0x1067
+#define WM8962_WRITE_SEQUENCER_104 0x1068
+#define WM8962_WRITE_SEQUENCER_105 0x1069
+#define WM8962_WRITE_SEQUENCER_106 0x106A
+#define WM8962_WRITE_SEQUENCER_107 0x106B
+#define WM8962_WRITE_SEQUENCER_108 0x106C
+#define WM8962_WRITE_SEQUENCER_109 0x106D
+#define WM8962_WRITE_SEQUENCER_110 0x106E
+#define WM8962_WRITE_SEQUENCER_111 0x106F
+#define WM8962_WRITE_SEQUENCER_112 0x1070
+#define WM8962_WRITE_SEQUENCER_113 0x1071
+#define WM8962_WRITE_SEQUENCER_114 0x1072
+#define WM8962_WRITE_SEQUENCER_115 0x1073
+#define WM8962_WRITE_SEQUENCER_116 0x1074
+#define WM8962_WRITE_SEQUENCER_117 0x1075
+#define WM8962_WRITE_SEQUENCER_118 0x1076
+#define WM8962_WRITE_SEQUENCER_119 0x1077
+#define WM8962_WRITE_SEQUENCER_120 0x1078
+#define WM8962_WRITE_SEQUENCER_121 0x1079
+#define WM8962_WRITE_SEQUENCER_122 0x107A
+#define WM8962_WRITE_SEQUENCER_123 0x107B
+#define WM8962_WRITE_SEQUENCER_124 0x107C
+#define WM8962_WRITE_SEQUENCER_125 0x107D
+#define WM8962_WRITE_SEQUENCER_126 0x107E
+#define WM8962_WRITE_SEQUENCER_127 0x107F
+#define WM8962_WRITE_SEQUENCER_128 0x1080
+#define WM8962_WRITE_SEQUENCER_129 0x1081
+#define WM8962_WRITE_SEQUENCER_130 0x1082
+#define WM8962_WRITE_SEQUENCER_131 0x1083
+#define WM8962_WRITE_SEQUENCER_132 0x1084
+#define WM8962_WRITE_SEQUENCER_133 0x1085
+#define WM8962_WRITE_SEQUENCER_134 0x1086
+#define WM8962_WRITE_SEQUENCER_135 0x1087
+#define WM8962_WRITE_SEQUENCER_136 0x1088
+#define WM8962_WRITE_SEQUENCER_137 0x1089
+#define WM8962_WRITE_SEQUENCER_138 0x108A
+#define WM8962_WRITE_SEQUENCER_139 0x108B
+#define WM8962_WRITE_SEQUENCER_140 0x108C
+#define WM8962_WRITE_SEQUENCER_141 0x108D
+#define WM8962_WRITE_SEQUENCER_142 0x108E
+#define WM8962_WRITE_SEQUENCER_143 0x108F
+#define WM8962_WRITE_SEQUENCER_144 0x1090
+#define WM8962_WRITE_SEQUENCER_145 0x1091
+#define WM8962_WRITE_SEQUENCER_146 0x1092
+#define WM8962_WRITE_SEQUENCER_147 0x1093
+#define WM8962_WRITE_SEQUENCER_148 0x1094
+#define WM8962_WRITE_SEQUENCER_149 0x1095
+#define WM8962_WRITE_SEQUENCER_150 0x1096
+#define WM8962_WRITE_SEQUENCER_151 0x1097
+#define WM8962_WRITE_SEQUENCER_152 0x1098
+#define WM8962_WRITE_SEQUENCER_153 0x1099
+#define WM8962_WRITE_SEQUENCER_154 0x109A
+#define WM8962_WRITE_SEQUENCER_155 0x109B
+#define WM8962_WRITE_SEQUENCER_156 0x109C
+#define WM8962_WRITE_SEQUENCER_157 0x109D
+#define WM8962_WRITE_SEQUENCER_158 0x109E
+#define WM8962_WRITE_SEQUENCER_159 0x109F
+#define WM8962_WRITE_SEQUENCER_160 0x10A0
+#define WM8962_WRITE_SEQUENCER_161 0x10A1
+#define WM8962_WRITE_SEQUENCER_162 0x10A2
+#define WM8962_WRITE_SEQUENCER_163 0x10A3
+#define WM8962_WRITE_SEQUENCER_164 0x10A4
+#define WM8962_WRITE_SEQUENCER_165 0x10A5
+#define WM8962_WRITE_SEQUENCER_166 0x10A6
+#define WM8962_WRITE_SEQUENCER_167 0x10A7
+#define WM8962_WRITE_SEQUENCER_168 0x10A8
+#define WM8962_WRITE_SEQUENCER_169 0x10A9
+#define WM8962_WRITE_SEQUENCER_170 0x10AA
+#define WM8962_WRITE_SEQUENCER_171 0x10AB
+#define WM8962_WRITE_SEQUENCER_172 0x10AC
+#define WM8962_WRITE_SEQUENCER_173 0x10AD
+#define WM8962_WRITE_SEQUENCER_174 0x10AE
+#define WM8962_WRITE_SEQUENCER_175 0x10AF
+#define WM8962_WRITE_SEQUENCER_176 0x10B0
+#define WM8962_WRITE_SEQUENCER_177 0x10B1
+#define WM8962_WRITE_SEQUENCER_178 0x10B2
+#define WM8962_WRITE_SEQUENCER_179 0x10B3
+#define WM8962_WRITE_SEQUENCER_180 0x10B4
+#define WM8962_WRITE_SEQUENCER_181 0x10B5
+#define WM8962_WRITE_SEQUENCER_182 0x10B6
+#define WM8962_WRITE_SEQUENCER_183 0x10B7
+#define WM8962_WRITE_SEQUENCER_184 0x10B8
+#define WM8962_WRITE_SEQUENCER_185 0x10B9
+#define WM8962_WRITE_SEQUENCER_186 0x10BA
+#define WM8962_WRITE_SEQUENCER_187 0x10BB
+#define WM8962_WRITE_SEQUENCER_188 0x10BC
+#define WM8962_WRITE_SEQUENCER_189 0x10BD
+#define WM8962_WRITE_SEQUENCER_190 0x10BE
+#define WM8962_WRITE_SEQUENCER_191 0x10BF
+#define WM8962_WRITE_SEQUENCER_192 0x10C0
+#define WM8962_WRITE_SEQUENCER_193 0x10C1
+#define WM8962_WRITE_SEQUENCER_194 0x10C2
+#define WM8962_WRITE_SEQUENCER_195 0x10C3
+#define WM8962_WRITE_SEQUENCER_196 0x10C4
+#define WM8962_WRITE_SEQUENCER_197 0x10C5
+#define WM8962_WRITE_SEQUENCER_198 0x10C6
+#define WM8962_WRITE_SEQUENCER_199 0x10C7
+#define WM8962_WRITE_SEQUENCER_200 0x10C8
+#define WM8962_WRITE_SEQUENCER_201 0x10C9
+#define WM8962_WRITE_SEQUENCER_202 0x10CA
+#define WM8962_WRITE_SEQUENCER_203 0x10CB
+#define WM8962_WRITE_SEQUENCER_204 0x10CC
+#define WM8962_WRITE_SEQUENCER_205 0x10CD
+#define WM8962_WRITE_SEQUENCER_206 0x10CE
+#define WM8962_WRITE_SEQUENCER_207 0x10CF
+#define WM8962_WRITE_SEQUENCER_208 0x10D0
+#define WM8962_WRITE_SEQUENCER_209 0x10D1
+#define WM8962_WRITE_SEQUENCER_210 0x10D2
+#define WM8962_WRITE_SEQUENCER_211 0x10D3
+#define WM8962_WRITE_SEQUENCER_212 0x10D4
+#define WM8962_WRITE_SEQUENCER_213 0x10D5
+#define WM8962_WRITE_SEQUENCER_214 0x10D6
+#define WM8962_WRITE_SEQUENCER_215 0x10D7
+#define WM8962_WRITE_SEQUENCER_216 0x10D8
+#define WM8962_WRITE_SEQUENCER_217 0x10D9
+#define WM8962_WRITE_SEQUENCER_218 0x10DA
+#define WM8962_WRITE_SEQUENCER_219 0x10DB
+#define WM8962_WRITE_SEQUENCER_220 0x10DC
+#define WM8962_WRITE_SEQUENCER_221 0x10DD
+#define WM8962_WRITE_SEQUENCER_222 0x10DE
+#define WM8962_WRITE_SEQUENCER_223 0x10DF
+#define WM8962_WRITE_SEQUENCER_224 0x10E0
+#define WM8962_WRITE_SEQUENCER_225 0x10E1
+#define WM8962_WRITE_SEQUENCER_226 0x10E2
+#define WM8962_WRITE_SEQUENCER_227 0x10E3
+#define WM8962_WRITE_SEQUENCER_228 0x10E4
+#define WM8962_WRITE_SEQUENCER_229 0x10E5
+#define WM8962_WRITE_SEQUENCER_230 0x10E6
+#define WM8962_WRITE_SEQUENCER_231 0x10E7
+#define WM8962_WRITE_SEQUENCER_232 0x10E8
+#define WM8962_WRITE_SEQUENCER_233 0x10E9
+#define WM8962_WRITE_SEQUENCER_234 0x10EA
+#define WM8962_WRITE_SEQUENCER_235 0x10EB
+#define WM8962_WRITE_SEQUENCER_236 0x10EC
+#define WM8962_WRITE_SEQUENCER_237 0x10ED
+#define WM8962_WRITE_SEQUENCER_238 0x10EE
+#define WM8962_WRITE_SEQUENCER_239 0x10EF
+#define WM8962_WRITE_SEQUENCER_240 0x10F0
+#define WM8962_WRITE_SEQUENCER_241 0x10F1
+#define WM8962_WRITE_SEQUENCER_242 0x10F2
+#define WM8962_WRITE_SEQUENCER_243 0x10F3
+#define WM8962_WRITE_SEQUENCER_244 0x10F4
+#define WM8962_WRITE_SEQUENCER_245 0x10F5
+#define WM8962_WRITE_SEQUENCER_246 0x10F6
+#define WM8962_WRITE_SEQUENCER_247 0x10F7
+#define WM8962_WRITE_SEQUENCER_248 0x10F8
+#define WM8962_WRITE_SEQUENCER_249 0x10F9
+#define WM8962_WRITE_SEQUENCER_250 0x10FA
+#define WM8962_WRITE_SEQUENCER_251 0x10FB
+#define WM8962_WRITE_SEQUENCER_252 0x10FC
+#define WM8962_WRITE_SEQUENCER_253 0x10FD
+#define WM8962_WRITE_SEQUENCER_254 0x10FE
+#define WM8962_WRITE_SEQUENCER_255 0x10FF
+#define WM8962_WRITE_SEQUENCER_256 0x1100
+#define WM8962_WRITE_SEQUENCER_257 0x1101
+#define WM8962_WRITE_SEQUENCER_258 0x1102
+#define WM8962_WRITE_SEQUENCER_259 0x1103
+#define WM8962_WRITE_SEQUENCER_260 0x1104
+#define WM8962_WRITE_SEQUENCER_261 0x1105
+#define WM8962_WRITE_SEQUENCER_262 0x1106
+#define WM8962_WRITE_SEQUENCER_263 0x1107
+#define WM8962_WRITE_SEQUENCER_264 0x1108
+#define WM8962_WRITE_SEQUENCER_265 0x1109
+#define WM8962_WRITE_SEQUENCER_266 0x110A
+#define WM8962_WRITE_SEQUENCER_267 0x110B
+#define WM8962_WRITE_SEQUENCER_268 0x110C
+#define WM8962_WRITE_SEQUENCER_269 0x110D
+#define WM8962_WRITE_SEQUENCER_270 0x110E
+#define WM8962_WRITE_SEQUENCER_271 0x110F
+#define WM8962_WRITE_SEQUENCER_272 0x1110
+#define WM8962_WRITE_SEQUENCER_273 0x1111
+#define WM8962_WRITE_SEQUENCER_274 0x1112
+#define WM8962_WRITE_SEQUENCER_275 0x1113
+#define WM8962_WRITE_SEQUENCER_276 0x1114
+#define WM8962_WRITE_SEQUENCER_277 0x1115
+#define WM8962_WRITE_SEQUENCER_278 0x1116
+#define WM8962_WRITE_SEQUENCER_279 0x1117
+#define WM8962_WRITE_SEQUENCER_280 0x1118
+#define WM8962_WRITE_SEQUENCER_281 0x1119
+#define WM8962_WRITE_SEQUENCER_282 0x111A
+#define WM8962_WRITE_SEQUENCER_283 0x111B
+#define WM8962_WRITE_SEQUENCER_284 0x111C
+#define WM8962_WRITE_SEQUENCER_285 0x111D
+#define WM8962_WRITE_SEQUENCER_286 0x111E
+#define WM8962_WRITE_SEQUENCER_287 0x111F
+#define WM8962_WRITE_SEQUENCER_288 0x1120
+#define WM8962_WRITE_SEQUENCER_289 0x1121
+#define WM8962_WRITE_SEQUENCER_290 0x1122
+#define WM8962_WRITE_SEQUENCER_291 0x1123
+#define WM8962_WRITE_SEQUENCER_292 0x1124
+#define WM8962_WRITE_SEQUENCER_293 0x1125
+#define WM8962_WRITE_SEQUENCER_294 0x1126
+#define WM8962_WRITE_SEQUENCER_295 0x1127
+#define WM8962_WRITE_SEQUENCER_296 0x1128
+#define WM8962_WRITE_SEQUENCER_297 0x1129
+#define WM8962_WRITE_SEQUENCER_298 0x112A
+#define WM8962_WRITE_SEQUENCER_299 0x112B
+#define WM8962_WRITE_SEQUENCER_300 0x112C
+#define WM8962_WRITE_SEQUENCER_301 0x112D
+#define WM8962_WRITE_SEQUENCER_302 0x112E
+#define WM8962_WRITE_SEQUENCER_303 0x112F
+#define WM8962_WRITE_SEQUENCER_304 0x1130
+#define WM8962_WRITE_SEQUENCER_305 0x1131
+#define WM8962_WRITE_SEQUENCER_306 0x1132
+#define WM8962_WRITE_SEQUENCER_307 0x1133
+#define WM8962_WRITE_SEQUENCER_308 0x1134
+#define WM8962_WRITE_SEQUENCER_309 0x1135
+#define WM8962_WRITE_SEQUENCER_310 0x1136
+#define WM8962_WRITE_SEQUENCER_311 0x1137
+#define WM8962_WRITE_SEQUENCER_312 0x1138
+#define WM8962_WRITE_SEQUENCER_313 0x1139
+#define WM8962_WRITE_SEQUENCER_314 0x113A
+#define WM8962_WRITE_SEQUENCER_315 0x113B
+#define WM8962_WRITE_SEQUENCER_316 0x113C
+#define WM8962_WRITE_SEQUENCER_317 0x113D
+#define WM8962_WRITE_SEQUENCER_318 0x113E
+#define WM8962_WRITE_SEQUENCER_319 0x113F
+#define WM8962_WRITE_SEQUENCER_320 0x1140
+#define WM8962_WRITE_SEQUENCER_321 0x1141
+#define WM8962_WRITE_SEQUENCER_322 0x1142
+#define WM8962_WRITE_SEQUENCER_323 0x1143
+#define WM8962_WRITE_SEQUENCER_324 0x1144
+#define WM8962_WRITE_SEQUENCER_325 0x1145
+#define WM8962_WRITE_SEQUENCER_326 0x1146
+#define WM8962_WRITE_SEQUENCER_327 0x1147
+#define WM8962_WRITE_SEQUENCER_328 0x1148
+#define WM8962_WRITE_SEQUENCER_329 0x1149
+#define WM8962_WRITE_SEQUENCER_330 0x114A
+#define WM8962_WRITE_SEQUENCER_331 0x114B
+#define WM8962_WRITE_SEQUENCER_332 0x114C
+#define WM8962_WRITE_SEQUENCER_333 0x114D
+#define WM8962_WRITE_SEQUENCER_334 0x114E
+#define WM8962_WRITE_SEQUENCER_335 0x114F
+#define WM8962_WRITE_SEQUENCER_336 0x1150
+#define WM8962_WRITE_SEQUENCER_337 0x1151
+#define WM8962_WRITE_SEQUENCER_338 0x1152
+#define WM8962_WRITE_SEQUENCER_339 0x1153
+#define WM8962_WRITE_SEQUENCER_340 0x1154
+#define WM8962_WRITE_SEQUENCER_341 0x1155
+#define WM8962_WRITE_SEQUENCER_342 0x1156
+#define WM8962_WRITE_SEQUENCER_343 0x1157
+#define WM8962_WRITE_SEQUENCER_344 0x1158
+#define WM8962_WRITE_SEQUENCER_345 0x1159
+#define WM8962_WRITE_SEQUENCER_346 0x115A
+#define WM8962_WRITE_SEQUENCER_347 0x115B
+#define WM8962_WRITE_SEQUENCER_348 0x115C
+#define WM8962_WRITE_SEQUENCER_349 0x115D
+#define WM8962_WRITE_SEQUENCER_350 0x115E
+#define WM8962_WRITE_SEQUENCER_351 0x115F
+#define WM8962_WRITE_SEQUENCER_352 0x1160
+#define WM8962_WRITE_SEQUENCER_353 0x1161
+#define WM8962_WRITE_SEQUENCER_354 0x1162
+#define WM8962_WRITE_SEQUENCER_355 0x1163
+#define WM8962_WRITE_SEQUENCER_356 0x1164
+#define WM8962_WRITE_SEQUENCER_357 0x1165
+#define WM8962_WRITE_SEQUENCER_358 0x1166
+#define WM8962_WRITE_SEQUENCER_359 0x1167
+#define WM8962_WRITE_SEQUENCER_360 0x1168
+#define WM8962_WRITE_SEQUENCER_361 0x1169
+#define WM8962_WRITE_SEQUENCER_362 0x116A
+#define WM8962_WRITE_SEQUENCER_363 0x116B
+#define WM8962_WRITE_SEQUENCER_364 0x116C
+#define WM8962_WRITE_SEQUENCER_365 0x116D
+#define WM8962_WRITE_SEQUENCER_366 0x116E
+#define WM8962_WRITE_SEQUENCER_367 0x116F
+#define WM8962_WRITE_SEQUENCER_368 0x1170
+#define WM8962_WRITE_SEQUENCER_369 0x1171
+#define WM8962_WRITE_SEQUENCER_370 0x1172
+#define WM8962_WRITE_SEQUENCER_371 0x1173
+#define WM8962_WRITE_SEQUENCER_372 0x1174
+#define WM8962_WRITE_SEQUENCER_373 0x1175
+#define WM8962_WRITE_SEQUENCER_374 0x1176
+#define WM8962_WRITE_SEQUENCER_375 0x1177
+#define WM8962_WRITE_SEQUENCER_376 0x1178
+#define WM8962_WRITE_SEQUENCER_377 0x1179
+#define WM8962_WRITE_SEQUENCER_378 0x117A
+#define WM8962_WRITE_SEQUENCER_379 0x117B
+#define WM8962_WRITE_SEQUENCER_380 0x117C
+#define WM8962_WRITE_SEQUENCER_381 0x117D
+#define WM8962_WRITE_SEQUENCER_382 0x117E
+#define WM8962_WRITE_SEQUENCER_383 0x117F
+#define WM8962_WRITE_SEQUENCER_384 0x1180
+#define WM8962_WRITE_SEQUENCER_385 0x1181
+#define WM8962_WRITE_SEQUENCER_386 0x1182
+#define WM8962_WRITE_SEQUENCER_387 0x1183
+#define WM8962_WRITE_SEQUENCER_388 0x1184
+#define WM8962_WRITE_SEQUENCER_389 0x1185
+#define WM8962_WRITE_SEQUENCER_390 0x1186
+#define WM8962_WRITE_SEQUENCER_391 0x1187
+#define WM8962_WRITE_SEQUENCER_392 0x1188
+#define WM8962_WRITE_SEQUENCER_393 0x1189
+#define WM8962_WRITE_SEQUENCER_394 0x118A
+#define WM8962_WRITE_SEQUENCER_395 0x118B
+#define WM8962_WRITE_SEQUENCER_396 0x118C
+#define WM8962_WRITE_SEQUENCER_397 0x118D
+#define WM8962_WRITE_SEQUENCER_398 0x118E
+#define WM8962_WRITE_SEQUENCER_399 0x118F
+#define WM8962_WRITE_SEQUENCER_400 0x1190
+#define WM8962_WRITE_SEQUENCER_401 0x1191
+#define WM8962_WRITE_SEQUENCER_402 0x1192
+#define WM8962_WRITE_SEQUENCER_403 0x1193
+#define WM8962_WRITE_SEQUENCER_404 0x1194
+#define WM8962_WRITE_SEQUENCER_405 0x1195
+#define WM8962_WRITE_SEQUENCER_406 0x1196
+#define WM8962_WRITE_SEQUENCER_407 0x1197
+#define WM8962_WRITE_SEQUENCER_408 0x1198
+#define WM8962_WRITE_SEQUENCER_409 0x1199
+#define WM8962_WRITE_SEQUENCER_410 0x119A
+#define WM8962_WRITE_SEQUENCER_411 0x119B
+#define WM8962_WRITE_SEQUENCER_412 0x119C
+#define WM8962_WRITE_SEQUENCER_413 0x119D
+#define WM8962_WRITE_SEQUENCER_414 0x119E
+#define WM8962_WRITE_SEQUENCER_415 0x119F
+#define WM8962_WRITE_SEQUENCER_416 0x11A0
+#define WM8962_WRITE_SEQUENCER_417 0x11A1
+#define WM8962_WRITE_SEQUENCER_418 0x11A2
+#define WM8962_WRITE_SEQUENCER_419 0x11A3
+#define WM8962_WRITE_SEQUENCER_420 0x11A4
+#define WM8962_WRITE_SEQUENCER_421 0x11A5
+#define WM8962_WRITE_SEQUENCER_422 0x11A6
+#define WM8962_WRITE_SEQUENCER_423 0x11A7
+#define WM8962_WRITE_SEQUENCER_424 0x11A8
+#define WM8962_WRITE_SEQUENCER_425 0x11A9
+#define WM8962_WRITE_SEQUENCER_426 0x11AA
+#define WM8962_WRITE_SEQUENCER_427 0x11AB
+#define WM8962_WRITE_SEQUENCER_428 0x11AC
+#define WM8962_WRITE_SEQUENCER_429 0x11AD
+#define WM8962_WRITE_SEQUENCER_430 0x11AE
+#define WM8962_WRITE_SEQUENCER_431 0x11AF
+#define WM8962_WRITE_SEQUENCER_432 0x11B0
+#define WM8962_WRITE_SEQUENCER_433 0x11B1
+#define WM8962_WRITE_SEQUENCER_434 0x11B2
+#define WM8962_WRITE_SEQUENCER_435 0x11B3
+#define WM8962_WRITE_SEQUENCER_436 0x11B4
+#define WM8962_WRITE_SEQUENCER_437 0x11B5
+#define WM8962_WRITE_SEQUENCER_438 0x11B6
+#define WM8962_WRITE_SEQUENCER_439 0x11B7
+#define WM8962_WRITE_SEQUENCER_440 0x11B8
+#define WM8962_WRITE_SEQUENCER_441 0x11B9
+#define WM8962_WRITE_SEQUENCER_442 0x11BA
+#define WM8962_WRITE_SEQUENCER_443 0x11BB
+#define WM8962_WRITE_SEQUENCER_444 0x11BC
+#define WM8962_WRITE_SEQUENCER_445 0x11BD
+#define WM8962_WRITE_SEQUENCER_446 0x11BE
+#define WM8962_WRITE_SEQUENCER_447 0x11BF
+#define WM8962_WRITE_SEQUENCER_448 0x11C0
+#define WM8962_WRITE_SEQUENCER_449 0x11C1
+#define WM8962_WRITE_SEQUENCER_450 0x11C2
+#define WM8962_WRITE_SEQUENCER_451 0x11C3
+#define WM8962_WRITE_SEQUENCER_452 0x11C4
+#define WM8962_WRITE_SEQUENCER_453 0x11C5
+#define WM8962_WRITE_SEQUENCER_454 0x11C6
+#define WM8962_WRITE_SEQUENCER_455 0x11C7
+#define WM8962_WRITE_SEQUENCER_456 0x11C8
+#define WM8962_WRITE_SEQUENCER_457 0x11C9
+#define WM8962_WRITE_SEQUENCER_458 0x11CA
+#define WM8962_WRITE_SEQUENCER_459 0x11CB
+#define WM8962_WRITE_SEQUENCER_460 0x11CC
+#define WM8962_WRITE_SEQUENCER_461 0x11CD
+#define WM8962_WRITE_SEQUENCER_462 0x11CE
+#define WM8962_WRITE_SEQUENCER_463 0x11CF
+#define WM8962_WRITE_SEQUENCER_464 0x11D0
+#define WM8962_WRITE_SEQUENCER_465 0x11D1
+#define WM8962_WRITE_SEQUENCER_466 0x11D2
+#define WM8962_WRITE_SEQUENCER_467 0x11D3
+#define WM8962_WRITE_SEQUENCER_468 0x11D4
+#define WM8962_WRITE_SEQUENCER_469 0x11D5
+#define WM8962_WRITE_SEQUENCER_470 0x11D6
+#define WM8962_WRITE_SEQUENCER_471 0x11D7
+#define WM8962_WRITE_SEQUENCER_472 0x11D8
+#define WM8962_WRITE_SEQUENCER_473 0x11D9
+#define WM8962_WRITE_SEQUENCER_474 0x11DA
+#define WM8962_WRITE_SEQUENCER_475 0x11DB
+#define WM8962_WRITE_SEQUENCER_476 0x11DC
+#define WM8962_WRITE_SEQUENCER_477 0x11DD
+#define WM8962_WRITE_SEQUENCER_478 0x11DE
+#define WM8962_WRITE_SEQUENCER_479 0x11DF
+#define WM8962_WRITE_SEQUENCER_480 0x11E0
+#define WM8962_WRITE_SEQUENCER_481 0x11E1
+#define WM8962_WRITE_SEQUENCER_482 0x11E2
+#define WM8962_WRITE_SEQUENCER_483 0x11E3
+#define WM8962_WRITE_SEQUENCER_484 0x11E4
+#define WM8962_WRITE_SEQUENCER_485 0x11E5
+#define WM8962_WRITE_SEQUENCER_486 0x11E6
+#define WM8962_WRITE_SEQUENCER_487 0x11E7
+#define WM8962_WRITE_SEQUENCER_488 0x11E8
+#define WM8962_WRITE_SEQUENCER_489 0x11E9
+#define WM8962_WRITE_SEQUENCER_490 0x11EA
+#define WM8962_WRITE_SEQUENCER_491 0x11EB
+#define WM8962_WRITE_SEQUENCER_492 0x11EC
+#define WM8962_WRITE_SEQUENCER_493 0x11ED
+#define WM8962_WRITE_SEQUENCER_494 0x11EE
+#define WM8962_WRITE_SEQUENCER_495 0x11EF
+#define WM8962_WRITE_SEQUENCER_496 0x11F0
+#define WM8962_WRITE_SEQUENCER_497 0x11F1
+#define WM8962_WRITE_SEQUENCER_498 0x11F2
+#define WM8962_WRITE_SEQUENCER_499 0x11F3
+#define WM8962_WRITE_SEQUENCER_500 0x11F4
+#define WM8962_WRITE_SEQUENCER_501 0x11F5
+#define WM8962_WRITE_SEQUENCER_502 0x11F6
+#define WM8962_WRITE_SEQUENCER_503 0x11F7
+#define WM8962_WRITE_SEQUENCER_504 0x11F8
+#define WM8962_WRITE_SEQUENCER_505 0x11F9
+#define WM8962_WRITE_SEQUENCER_506 0x11FA
+#define WM8962_WRITE_SEQUENCER_507 0x11FB
+#define WM8962_WRITE_SEQUENCER_508 0x11FC
+#define WM8962_WRITE_SEQUENCER_509 0x11FD
+#define WM8962_WRITE_SEQUENCER_510 0x11FE
+#define WM8962_WRITE_SEQUENCER_511 0x11FF
+#define WM8962_DSP2_INSTRUCTION_RAM_0 0x2000
+#define WM8962_DSP2_ADDRESS_RAM_2 0x2400
+#define WM8962_DSP2_ADDRESS_RAM_1 0x2401
+#define WM8962_DSP2_ADDRESS_RAM_0 0x2402
+#define WM8962_DSP2_DATA1_RAM_1 0x3000
+#define WM8962_DSP2_DATA1_RAM_0 0x3001
+#define WM8962_DSP2_DATA2_RAM_1 0x3400
+#define WM8962_DSP2_DATA2_RAM_0 0x3401
+#define WM8962_DSP2_DATA3_RAM_1 0x3800
+#define WM8962_DSP2_DATA3_RAM_0 0x3801
+#define WM8962_DSP2_COEFF_RAM_0 0x3C00
+#define WM8962_RETUNEADC_SHARED_COEFF_1 0x4000
+#define WM8962_RETUNEADC_SHARED_COEFF_0 0x4001
+#define WM8962_RETUNEDAC_SHARED_COEFF_1 0x4002
+#define WM8962_RETUNEDAC_SHARED_COEFF_0 0x4003
+#define WM8962_SOUNDSTAGE_ENABLES_1 0x4004
+#define WM8962_SOUNDSTAGE_ENABLES_0 0x4005
+#define WM8962_HDBASS_AI_1 0x4200
+#define WM8962_HDBASS_AI_0 0x4201
+#define WM8962_HDBASS_AR_1 0x4202
+#define WM8962_HDBASS_AR_0 0x4203
+#define WM8962_HDBASS_B_1 0x4204
+#define WM8962_HDBASS_B_0 0x4205
+#define WM8962_HDBASS_K_1 0x4206
+#define WM8962_HDBASS_K_0 0x4207
+#define WM8962_HDBASS_N1_1 0x4208
+#define WM8962_HDBASS_N1_0 0x4209
+#define WM8962_HDBASS_N2_1 0x420A
+#define WM8962_HDBASS_N2_0 0x420B
+#define WM8962_HDBASS_N3_1 0x420C
+#define WM8962_HDBASS_N3_0 0x420D
+#define WM8962_HDBASS_N4_1 0x420E
+#define WM8962_HDBASS_N4_0 0x420F
+#define WM8962_HDBASS_N5_1 0x4210
+#define WM8962_HDBASS_N5_0 0x4211
+#define WM8962_HDBASS_X1_1 0x4212
+#define WM8962_HDBASS_X1_0 0x4213
+#define WM8962_HDBASS_X2_1 0x4214
+#define WM8962_HDBASS_X2_0 0x4215
+#define WM8962_HDBASS_X3_1 0x4216
+#define WM8962_HDBASS_X3_0 0x4217
+#define WM8962_HDBASS_ATK_1 0x4218
+#define WM8962_HDBASS_ATK_0 0x4219
+#define WM8962_HDBASS_DCY_1 0x421A
+#define WM8962_HDBASS_DCY_0 0x421B
+#define WM8962_HDBASS_PG_1 0x421C
+#define WM8962_HDBASS_PG_0 0x421D
+#define WM8962_HPF_C_1 0x4400
+#define WM8962_HPF_C_0 0x4401
+#define WM8962_ADCL_RETUNE_C1_1 0x4600
+#define WM8962_ADCL_RETUNE_C1_0 0x4601
+#define WM8962_ADCL_RETUNE_C2_1 0x4602
+#define WM8962_ADCL_RETUNE_C2_0 0x4603
+#define WM8962_ADCL_RETUNE_C3_1 0x4604
+#define WM8962_ADCL_RETUNE_C3_0 0x4605
+#define WM8962_ADCL_RETUNE_C4_1 0x4606
+#define WM8962_ADCL_RETUNE_C4_0 0x4607
+#define WM8962_ADCL_RETUNE_C5_1 0x4608
+#define WM8962_ADCL_RETUNE_C5_0 0x4609
+#define WM8962_ADCL_RETUNE_C6_1 0x460A
+#define WM8962_ADCL_RETUNE_C6_0 0x460B
+#define WM8962_ADCL_RETUNE_C7_1 0x460C
+#define WM8962_ADCL_RETUNE_C7_0 0x460D
+#define WM8962_ADCL_RETUNE_C8_1 0x460E
+#define WM8962_ADCL_RETUNE_C8_0 0x460F
+#define WM8962_ADCL_RETUNE_C9_1 0x4610
+#define WM8962_ADCL_RETUNE_C9_0 0x4611
+#define WM8962_ADCL_RETUNE_C10_1 0x4612
+#define WM8962_ADCL_RETUNE_C10_0 0x4613
+#define WM8962_ADCL_RETUNE_C11_1 0x4614
+#define WM8962_ADCL_RETUNE_C11_0 0x4615
+#define WM8962_ADCL_RETUNE_C12_1 0x4616
+#define WM8962_ADCL_RETUNE_C12_0 0x4617
+#define WM8962_ADCL_RETUNE_C13_1 0x4618
+#define WM8962_ADCL_RETUNE_C13_0 0x4619
+#define WM8962_ADCL_RETUNE_C14_1 0x461A
+#define WM8962_ADCL_RETUNE_C14_0 0x461B
+#define WM8962_ADCL_RETUNE_C15_1 0x461C
+#define WM8962_ADCL_RETUNE_C15_0 0x461D
+#define WM8962_ADCL_RETUNE_C16_1 0x461E
+#define WM8962_ADCL_RETUNE_C16_0 0x461F
+#define WM8962_ADCL_RETUNE_C17_1 0x4620
+#define WM8962_ADCL_RETUNE_C17_0 0x4621
+#define WM8962_ADCL_RETUNE_C18_1 0x4622
+#define WM8962_ADCL_RETUNE_C18_0 0x4623
+#define WM8962_ADCL_RETUNE_C19_1 0x4624
+#define WM8962_ADCL_RETUNE_C19_0 0x4625
+#define WM8962_ADCL_RETUNE_C20_1 0x4626
+#define WM8962_ADCL_RETUNE_C20_0 0x4627
+#define WM8962_ADCL_RETUNE_C21_1 0x4628
+#define WM8962_ADCL_RETUNE_C21_0 0x4629
+#define WM8962_ADCL_RETUNE_C22_1 0x462A
+#define WM8962_ADCL_RETUNE_C22_0 0x462B
+#define WM8962_ADCL_RETUNE_C23_1 0x462C
+#define WM8962_ADCL_RETUNE_C23_0 0x462D
+#define WM8962_ADCL_RETUNE_C24_1 0x462E
+#define WM8962_ADCL_RETUNE_C24_0 0x462F
+#define WM8962_ADCL_RETUNE_C25_1 0x4630
+#define WM8962_ADCL_RETUNE_C25_0 0x4631
+#define WM8962_ADCL_RETUNE_C26_1 0x4632
+#define WM8962_ADCL_RETUNE_C26_0 0x4633
+#define WM8962_ADCL_RETUNE_C27_1 0x4634
+#define WM8962_ADCL_RETUNE_C27_0 0x4635
+#define WM8962_ADCL_RETUNE_C28_1 0x4636
+#define WM8962_ADCL_RETUNE_C28_0 0x4637
+#define WM8962_ADCL_RETUNE_C29_1 0x4638
+#define WM8962_ADCL_RETUNE_C29_0 0x4639
+#define WM8962_ADCL_RETUNE_C30_1 0x463A
+#define WM8962_ADCL_RETUNE_C30_0 0x463B
+#define WM8962_ADCL_RETUNE_C31_1 0x463C
+#define WM8962_ADCL_RETUNE_C31_0 0x463D
+#define WM8962_ADCL_RETUNE_C32_1 0x463E
+#define WM8962_ADCL_RETUNE_C32_0 0x463F
+#define WM8962_RETUNEADC_PG2_1 0x4800
+#define WM8962_RETUNEADC_PG2_0 0x4801
+#define WM8962_RETUNEADC_PG_1 0x4802
+#define WM8962_RETUNEADC_PG_0 0x4803
+#define WM8962_ADCR_RETUNE_C1_1 0x4A00
+#define WM8962_ADCR_RETUNE_C1_0 0x4A01
+#define WM8962_ADCR_RETUNE_C2_1 0x4A02
+#define WM8962_ADCR_RETUNE_C2_0 0x4A03
+#define WM8962_ADCR_RETUNE_C3_1 0x4A04
+#define WM8962_ADCR_RETUNE_C3_0 0x4A05
+#define WM8962_ADCR_RETUNE_C4_1 0x4A06
+#define WM8962_ADCR_RETUNE_C4_0 0x4A07
+#define WM8962_ADCR_RETUNE_C5_1 0x4A08
+#define WM8962_ADCR_RETUNE_C5_0 0x4A09
+#define WM8962_ADCR_RETUNE_C6_1 0x4A0A
+#define WM8962_ADCR_RETUNE_C6_0 0x4A0B
+#define WM8962_ADCR_RETUNE_C7_1 0x4A0C
+#define WM8962_ADCR_RETUNE_C7_0 0x4A0D
+#define WM8962_ADCR_RETUNE_C8_1 0x4A0E
+#define WM8962_ADCR_RETUNE_C8_0 0x4A0F
+#define WM8962_ADCR_RETUNE_C9_1 0x4A10
+#define WM8962_ADCR_RETUNE_C9_0 0x4A11
+#define WM8962_ADCR_RETUNE_C10_1 0x4A12
+#define WM8962_ADCR_RETUNE_C10_0 0x4A13
+#define WM8962_ADCR_RETUNE_C11_1 0x4A14
+#define WM8962_ADCR_RETUNE_C11_0 0x4A15
+#define WM8962_ADCR_RETUNE_C12_1 0x4A16
+#define WM8962_ADCR_RETUNE_C12_0 0x4A17
+#define WM8962_ADCR_RETUNE_C13_1 0x4A18
+#define WM8962_ADCR_RETUNE_C13_0 0x4A19
+#define WM8962_ADCR_RETUNE_C14_1 0x4A1A
+#define WM8962_ADCR_RETUNE_C14_0 0x4A1B
+#define WM8962_ADCR_RETUNE_C15_1 0x4A1C
+#define WM8962_ADCR_RETUNE_C15_0 0x4A1D
+#define WM8962_ADCR_RETUNE_C16_1 0x4A1E
+#define WM8962_ADCR_RETUNE_C16_0 0x4A1F
+#define WM8962_ADCR_RETUNE_C17_1 0x4A20
+#define WM8962_ADCR_RETUNE_C17_0 0x4A21
+#define WM8962_ADCR_RETUNE_C18_1 0x4A22
+#define WM8962_ADCR_RETUNE_C18_0 0x4A23
+#define WM8962_ADCR_RETUNE_C19_1 0x4A24
+#define WM8962_ADCR_RETUNE_C19_0 0x4A25
+#define WM8962_ADCR_RETUNE_C20_1 0x4A26
+#define WM8962_ADCR_RETUNE_C20_0 0x4A27
+#define WM8962_ADCR_RETUNE_C21_1 0x4A28
+#define WM8962_ADCR_RETUNE_C21_0 0x4A29
+#define WM8962_ADCR_RETUNE_C22_1 0x4A2A
+#define WM8962_ADCR_RETUNE_C22_0 0x4A2B
+#define WM8962_ADCR_RETUNE_C23_1 0x4A2C
+#define WM8962_ADCR_RETUNE_C23_0 0x4A2D
+#define WM8962_ADCR_RETUNE_C24_1 0x4A2E
+#define WM8962_ADCR_RETUNE_C24_0 0x4A2F
+#define WM8962_ADCR_RETUNE_C25_1 0x4A30
+#define WM8962_ADCR_RETUNE_C25_0 0x4A31
+#define WM8962_ADCR_RETUNE_C26_1 0x4A32
+#define WM8962_ADCR_RETUNE_C26_0 0x4A33
+#define WM8962_ADCR_RETUNE_C27_1 0x4A34
+#define WM8962_ADCR_RETUNE_C27_0 0x4A35
+#define WM8962_ADCR_RETUNE_C28_1 0x4A36
+#define WM8962_ADCR_RETUNE_C28_0 0x4A37
+#define WM8962_ADCR_RETUNE_C29_1 0x4A38
+#define WM8962_ADCR_RETUNE_C29_0 0x4A39
+#define WM8962_ADCR_RETUNE_C30_1 0x4A3A
+#define WM8962_ADCR_RETUNE_C30_0 0x4A3B
+#define WM8962_ADCR_RETUNE_C31_1 0x4A3C
+#define WM8962_ADCR_RETUNE_C31_0 0x4A3D
+#define WM8962_ADCR_RETUNE_C32_1 0x4A3E
+#define WM8962_ADCR_RETUNE_C32_0 0x4A3F
+#define WM8962_DACL_RETUNE_C1_1 0x4C00
+#define WM8962_DACL_RETUNE_C1_0 0x4C01
+#define WM8962_DACL_RETUNE_C2_1 0x4C02
+#define WM8962_DACL_RETUNE_C2_0 0x4C03
+#define WM8962_DACL_RETUNE_C3_1 0x4C04
+#define WM8962_DACL_RETUNE_C3_0 0x4C05
+#define WM8962_DACL_RETUNE_C4_1 0x4C06
+#define WM8962_DACL_RETUNE_C4_0 0x4C07
+#define WM8962_DACL_RETUNE_C5_1 0x4C08
+#define WM8962_DACL_RETUNE_C5_0 0x4C09
+#define WM8962_DACL_RETUNE_C6_1 0x4C0A
+#define WM8962_DACL_RETUNE_C6_0 0x4C0B
+#define WM8962_DACL_RETUNE_C7_1 0x4C0C
+#define WM8962_DACL_RETUNE_C7_0 0x4C0D
+#define WM8962_DACL_RETUNE_C8_1 0x4C0E
+#define WM8962_DACL_RETUNE_C8_0 0x4C0F
+#define WM8962_DACL_RETUNE_C9_1 0x4C10
+#define WM8962_DACL_RETUNE_C9_0 0x4C11
+#define WM8962_DACL_RETUNE_C10_1 0x4C12
+#define WM8962_DACL_RETUNE_C10_0 0x4C13
+#define WM8962_DACL_RETUNE_C11_1 0x4C14
+#define WM8962_DACL_RETUNE_C11_0 0x4C15
+#define WM8962_DACL_RETUNE_C12_1 0x4C16
+#define WM8962_DACL_RETUNE_C12_0 0x4C17
+#define WM8962_DACL_RETUNE_C13_1 0x4C18
+#define WM8962_DACL_RETUNE_C13_0 0x4C19
+#define WM8962_DACL_RETUNE_C14_1 0x4C1A
+#define WM8962_DACL_RETUNE_C14_0 0x4C1B
+#define WM8962_DACL_RETUNE_C15_1 0x4C1C
+#define WM8962_DACL_RETUNE_C15_0 0x4C1D
+#define WM8962_DACL_RETUNE_C16_1 0x4C1E
+#define WM8962_DACL_RETUNE_C16_0 0x4C1F
+#define WM8962_DACL_RETUNE_C17_1 0x4C20
+#define WM8962_DACL_RETUNE_C17_0 0x4C21
+#define WM8962_DACL_RETUNE_C18_1 0x4C22
+#define WM8962_DACL_RETUNE_C18_0 0x4C23
+#define WM8962_DACL_RETUNE_C19_1 0x4C24
+#define WM8962_DACL_RETUNE_C19_0 0x4C25
+#define WM8962_DACL_RETUNE_C20_1 0x4C26
+#define WM8962_DACL_RETUNE_C20_0 0x4C27
+#define WM8962_DACL_RETUNE_C21_1 0x4C28
+#define WM8962_DACL_RETUNE_C21_0 0x4C29
+#define WM8962_DACL_RETUNE_C22_1 0x4C2A
+#define WM8962_DACL_RETUNE_C22_0 0x4C2B
+#define WM8962_DACL_RETUNE_C23_1 0x4C2C
+#define WM8962_DACL_RETUNE_C23_0 0x4C2D
+#define WM8962_DACL_RETUNE_C24_1 0x4C2E
+#define WM8962_DACL_RETUNE_C24_0 0x4C2F
+#define WM8962_DACL_RETUNE_C25_1 0x4C30
+#define WM8962_DACL_RETUNE_C25_0 0x4C31
+#define WM8962_DACL_RETUNE_C26_1 0x4C32
+#define WM8962_DACL_RETUNE_C26_0 0x4C33
+#define WM8962_DACL_RETUNE_C27_1 0x4C34
+#define WM8962_DACL_RETUNE_C27_0 0x4C35
+#define WM8962_DACL_RETUNE_C28_1 0x4C36
+#define WM8962_DACL_RETUNE_C28_0 0x4C37
+#define WM8962_DACL_RETUNE_C29_1 0x4C38
+#define WM8962_DACL_RETUNE_C29_0 0x4C39
+#define WM8962_DACL_RETUNE_C30_1 0x4C3A
+#define WM8962_DACL_RETUNE_C30_0 0x4C3B
+#define WM8962_DACL_RETUNE_C31_1 0x4C3C
+#define WM8962_DACL_RETUNE_C31_0 0x4C3D
+#define WM8962_DACL_RETUNE_C32_1 0x4C3E
+#define WM8962_DACL_RETUNE_C32_0 0x4C3F
+#define WM8962_RETUNEDAC_PG2_1 0x4E00
+#define WM8962_RETUNEDAC_PG2_0 0x4E01
+#define WM8962_RETUNEDAC_PG_1 0x4E02
+#define WM8962_RETUNEDAC_PG_0 0x4E03
+#define WM8962_DACR_RETUNE_C1_1 0x5000
+#define WM8962_DACR_RETUNE_C1_0 0x5001
+#define WM8962_DACR_RETUNE_C2_1 0x5002
+#define WM8962_DACR_RETUNE_C2_0 0x5003
+#define WM8962_DACR_RETUNE_C3_1 0x5004
+#define WM8962_DACR_RETUNE_C3_0 0x5005
+#define WM8962_DACR_RETUNE_C4_1 0x5006
+#define WM8962_DACR_RETUNE_C4_0 0x5007
+#define WM8962_DACR_RETUNE_C5_1 0x5008
+#define WM8962_DACR_RETUNE_C5_0 0x5009
+#define WM8962_DACR_RETUNE_C6_1 0x500A
+#define WM8962_DACR_RETUNE_C6_0 0x500B
+#define WM8962_DACR_RETUNE_C7_1 0x500C
+#define WM8962_DACR_RETUNE_C7_0 0x500D
+#define WM8962_DACR_RETUNE_C8_1 0x500E
+#define WM8962_DACR_RETUNE_C8_0 0x500F
+#define WM8962_DACR_RETUNE_C9_1 0x5010
+#define WM8962_DACR_RETUNE_C9_0 0x5011
+#define WM8962_DACR_RETUNE_C10_1 0x5012
+#define WM8962_DACR_RETUNE_C10_0 0x5013
+#define WM8962_DACR_RETUNE_C11_1 0x5014
+#define WM8962_DACR_RETUNE_C11_0 0x5015
+#define WM8962_DACR_RETUNE_C12_1 0x5016
+#define WM8962_DACR_RETUNE_C12_0 0x5017
+#define WM8962_DACR_RETUNE_C13_1 0x5018
+#define WM8962_DACR_RETUNE_C13_0 0x5019
+#define WM8962_DACR_RETUNE_C14_1 0x501A
+#define WM8962_DACR_RETUNE_C14_0 0x501B
+#define WM8962_DACR_RETUNE_C15_1 0x501C
+#define WM8962_DACR_RETUNE_C15_0 0x501D
+#define WM8962_DACR_RETUNE_C16_1 0x501E
+#define WM8962_DACR_RETUNE_C16_0 0x501F
+#define WM8962_DACR_RETUNE_C17_1 0x5020
+#define WM8962_DACR_RETUNE_C17_0 0x5021
+#define WM8962_DACR_RETUNE_C18_1 0x5022
+#define WM8962_DACR_RETUNE_C18_0 0x5023
+#define WM8962_DACR_RETUNE_C19_1 0x5024
+#define WM8962_DACR_RETUNE_C19_0 0x5025
+#define WM8962_DACR_RETUNE_C20_1 0x5026
+#define WM8962_DACR_RETUNE_C20_0 0x5027
+#define WM8962_DACR_RETUNE_C21_1 0x5028
+#define WM8962_DACR_RETUNE_C21_0 0x5029
+#define WM8962_DACR_RETUNE_C22_1 0x502A
+#define WM8962_DACR_RETUNE_C22_0 0x502B
+#define WM8962_DACR_RETUNE_C23_1 0x502C
+#define WM8962_DACR_RETUNE_C23_0 0x502D
+#define WM8962_DACR_RETUNE_C24_1 0x502E
+#define WM8962_DACR_RETUNE_C24_0 0x502F
+#define WM8962_DACR_RETUNE_C25_1 0x5030
+#define WM8962_DACR_RETUNE_C25_0 0x5031
+#define WM8962_DACR_RETUNE_C26_1 0x5032
+#define WM8962_DACR_RETUNE_C26_0 0x5033
+#define WM8962_DACR_RETUNE_C27_1 0x5034
+#define WM8962_DACR_RETUNE_C27_0 0x5035
+#define WM8962_DACR_RETUNE_C28_1 0x5036
+#define WM8962_DACR_RETUNE_C28_0 0x5037
+#define WM8962_DACR_RETUNE_C29_1 0x5038
+#define WM8962_DACR_RETUNE_C29_0 0x5039
+#define WM8962_DACR_RETUNE_C30_1 0x503A
+#define WM8962_DACR_RETUNE_C30_0 0x503B
+#define WM8962_DACR_RETUNE_C31_1 0x503C
+#define WM8962_DACR_RETUNE_C31_0 0x503D
+#define WM8962_DACR_RETUNE_C32_1 0x503E
+#define WM8962_DACR_RETUNE_C32_0 0x503F
+#define WM8962_VSS_XHD2_1 0x5200
+#define WM8962_VSS_XHD2_0 0x5201
+#define WM8962_VSS_XHD3_1 0x5202
+#define WM8962_VSS_XHD3_0 0x5203
+#define WM8962_VSS_XHN1_1 0x5204
+#define WM8962_VSS_XHN1_0 0x5205
+#define WM8962_VSS_XHN2_1 0x5206
+#define WM8962_VSS_XHN2_0 0x5207
+#define WM8962_VSS_XHN3_1 0x5208
+#define WM8962_VSS_XHN3_0 0x5209
+#define WM8962_VSS_XLA_1 0x520A
+#define WM8962_VSS_XLA_0 0x520B
+#define WM8962_VSS_XLB_1 0x520C
+#define WM8962_VSS_XLB_0 0x520D
+#define WM8962_VSS_XLG_1 0x520E
+#define WM8962_VSS_XLG_0 0x520F
+#define WM8962_VSS_PG2_1 0x5210
+#define WM8962_VSS_PG2_0 0x5211
+#define WM8962_VSS_PG_1 0x5212
+#define WM8962_VSS_PG_0 0x5213
+#define WM8962_VSS_XTD1_1 0x5214
+#define WM8962_VSS_XTD1_0 0x5215
+#define WM8962_VSS_XTD2_1 0x5216
+#define WM8962_VSS_XTD2_0 0x5217
+#define WM8962_VSS_XTD3_1 0x5218
+#define WM8962_VSS_XTD3_0 0x5219
+#define WM8962_VSS_XTD4_1 0x521A
+#define WM8962_VSS_XTD4_0 0x521B
+#define WM8962_VSS_XTD5_1 0x521C
+#define WM8962_VSS_XTD5_0 0x521D
+#define WM8962_VSS_XTD6_1 0x521E
+#define WM8962_VSS_XTD6_0 0x521F
+#define WM8962_VSS_XTD7_1 0x5220
+#define WM8962_VSS_XTD7_0 0x5221
+#define WM8962_VSS_XTD8_1 0x5222
+#define WM8962_VSS_XTD8_0 0x5223
+#define WM8962_VSS_XTD9_1 0x5224
+#define WM8962_VSS_XTD9_0 0x5225
+#define WM8962_VSS_XTD10_1 0x5226
+#define WM8962_VSS_XTD10_0 0x5227
+#define WM8962_VSS_XTD11_1 0x5228
+#define WM8962_VSS_XTD11_0 0x5229
+#define WM8962_VSS_XTD12_1 0x522A
+#define WM8962_VSS_XTD12_0 0x522B
+#define WM8962_VSS_XTD13_1 0x522C
+#define WM8962_VSS_XTD13_0 0x522D
+#define WM8962_VSS_XTD14_1 0x522E
+#define WM8962_VSS_XTD14_0 0x522F
+#define WM8962_VSS_XTD15_1 0x5230
+#define WM8962_VSS_XTD15_0 0x5231
+#define WM8962_VSS_XTD16_1 0x5232
+#define WM8962_VSS_XTD16_0 0x5233
+#define WM8962_VSS_XTD17_1 0x5234
+#define WM8962_VSS_XTD17_0 0x5235
+#define WM8962_VSS_XTD18_1 0x5236
+#define WM8962_VSS_XTD18_0 0x5237
+#define WM8962_VSS_XTD19_1 0x5238
+#define WM8962_VSS_XTD19_0 0x5239
+#define WM8962_VSS_XTD20_1 0x523A
+#define WM8962_VSS_XTD20_0 0x523B
+#define WM8962_VSS_XTD21_1 0x523C
+#define WM8962_VSS_XTD21_0 0x523D
+#define WM8962_VSS_XTD22_1 0x523E
+#define WM8962_VSS_XTD22_0 0x523F
+#define WM8962_VSS_XTD23_1 0x5240
+#define WM8962_VSS_XTD23_0 0x5241
+#define WM8962_VSS_XTD24_1 0x5242
+#define WM8962_VSS_XTD24_0 0x5243
+#define WM8962_VSS_XTD25_1 0x5244
+#define WM8962_VSS_XTD25_0 0x5245
+#define WM8962_VSS_XTD26_1 0x5246
+#define WM8962_VSS_XTD26_0 0x5247
+#define WM8962_VSS_XTD27_1 0x5248
+#define WM8962_VSS_XTD27_0 0x5249
+#define WM8962_VSS_XTD28_1 0x524A
+#define WM8962_VSS_XTD28_0 0x524B
+#define WM8962_VSS_XTD29_1 0x524C
+#define WM8962_VSS_XTD29_0 0x524D
+#define WM8962_VSS_XTD30_1 0x524E
+#define WM8962_VSS_XTD30_0 0x524F
+#define WM8962_VSS_XTD31_1 0x5250
+#define WM8962_VSS_XTD31_0 0x5251
+#define WM8962_VSS_XTD32_1 0x5252
+#define WM8962_VSS_XTD32_0 0x5253
+#define WM8962_VSS_XTS1_1 0x5254
+#define WM8962_VSS_XTS1_0 0x5255
+#define WM8962_VSS_XTS2_1 0x5256
+#define WM8962_VSS_XTS2_0 0x5257
+#define WM8962_VSS_XTS3_1 0x5258
+#define WM8962_VSS_XTS3_0 0x5259
+#define WM8962_VSS_XTS4_1 0x525A
+#define WM8962_VSS_XTS4_0 0x525B
+#define WM8962_VSS_XTS5_1 0x525C
+#define WM8962_VSS_XTS5_0 0x525D
+#define WM8962_VSS_XTS6_1 0x525E
+#define WM8962_VSS_XTS6_0 0x525F
+#define WM8962_VSS_XTS7_1 0x5260
+#define WM8962_VSS_XTS7_0 0x5261
+#define WM8962_VSS_XTS8_1 0x5262
+#define WM8962_VSS_XTS8_0 0x5263
+#define WM8962_VSS_XTS9_1 0x5264
+#define WM8962_VSS_XTS9_0 0x5265
+#define WM8962_VSS_XTS10_1 0x5266
+#define WM8962_VSS_XTS10_0 0x5267
+#define WM8962_VSS_XTS11_1 0x5268
+#define WM8962_VSS_XTS11_0 0x5269
+#define WM8962_VSS_XTS12_1 0x526A
+#define WM8962_VSS_XTS12_0 0x526B
+#define WM8962_VSS_XTS13_1 0x526C
+#define WM8962_VSS_XTS13_0 0x526D
+#define WM8962_VSS_XTS14_1 0x526E
+#define WM8962_VSS_XTS14_0 0x526F
+#define WM8962_VSS_XTS15_1 0x5270
+#define WM8962_VSS_XTS15_0 0x5271
+#define WM8962_VSS_XTS16_1 0x5272
+#define WM8962_VSS_XTS16_0 0x5273
+#define WM8962_VSS_XTS17_1 0x5274
+#define WM8962_VSS_XTS17_0 0x5275
+#define WM8962_VSS_XTS18_1 0x5276
+#define WM8962_VSS_XTS18_0 0x5277
+#define WM8962_VSS_XTS19_1 0x5278
+#define WM8962_VSS_XTS19_0 0x5279
+#define WM8962_VSS_XTS20_1 0x527A
+#define WM8962_VSS_XTS20_0 0x527B
+#define WM8962_VSS_XTS21_1 0x527C
+#define WM8962_VSS_XTS21_0 0x527D
+#define WM8962_VSS_XTS22_1 0x527E
+#define WM8962_VSS_XTS22_0 0x527F
+#define WM8962_VSS_XTS23_1 0x5280
+#define WM8962_VSS_XTS23_0 0x5281
+#define WM8962_VSS_XTS24_1 0x5282
+#define WM8962_VSS_XTS24_0 0x5283
+#define WM8962_VSS_XTS25_1 0x5284
+#define WM8962_VSS_XTS25_0 0x5285
+#define WM8962_VSS_XTS26_1 0x5286
+#define WM8962_VSS_XTS26_0 0x5287
+#define WM8962_VSS_XTS27_1 0x5288
+#define WM8962_VSS_XTS27_0 0x5289
+#define WM8962_VSS_XTS28_1 0x528A
+#define WM8962_VSS_XTS28_0 0x528B
+#define WM8962_VSS_XTS29_1 0x528C
+#define WM8962_VSS_XTS29_0 0x528D
+#define WM8962_VSS_XTS30_1 0x528E
+#define WM8962_VSS_XTS30_0 0x528F
+#define WM8962_VSS_XTS31_1 0x5290
+#define WM8962_VSS_XTS31_0 0x5291
+#define WM8962_VSS_XTS32_1 0x5292
+#define WM8962_VSS_XTS32_0 0x5293
+
+#define WM8962_REGISTER_COUNT 1138
+#define WM8962_MAX_REGISTER 0x5293
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Left Input volume
+ */
+#define WM8962_IN_VU 0x0100 /* IN_VU */
+#define WM8962_IN_VU_MASK 0x0100 /* IN_VU */
+#define WM8962_IN_VU_SHIFT 8 /* IN_VU */
+#define WM8962_IN_VU_WIDTH 1 /* IN_VU */
+#define WM8962_INPGAL_MUTE 0x0080 /* INPGAL_MUTE */
+#define WM8962_INPGAL_MUTE_MASK 0x0080 /* INPGAL_MUTE */
+#define WM8962_INPGAL_MUTE_SHIFT 7 /* INPGAL_MUTE */
+#define WM8962_INPGAL_MUTE_WIDTH 1 /* INPGAL_MUTE */
+#define WM8962_INL_ZC 0x0040 /* INL_ZC */
+#define WM8962_INL_ZC_MASK 0x0040 /* INL_ZC */
+#define WM8962_INL_ZC_SHIFT 6 /* INL_ZC */
+#define WM8962_INL_ZC_WIDTH 1 /* INL_ZC */
+#define WM8962_INL_VOL_MASK 0x003F /* INL_VOL - [5:0] */
+#define WM8962_INL_VOL_SHIFT 0 /* INL_VOL - [5:0] */
+#define WM8962_INL_VOL_WIDTH 6 /* INL_VOL - [5:0] */
+
+/*
+ * R1 (0x01) - Right Input volume
+ */
+#define WM8962_CUST_ID_MASK 0xF000 /* CUST_ID - [15:12] */
+#define WM8962_CUST_ID_SHIFT 12 /* CUST_ID - [15:12] */
+#define WM8962_CUST_ID_WIDTH 4 /* CUST_ID - [15:12] */
+#define WM8962_CHIP_REV_MASK 0x0E00 /* CHIP_REV - [11:9] */
+#define WM8962_CHIP_REV_SHIFT 9 /* CHIP_REV - [11:9] */
+#define WM8962_CHIP_REV_WIDTH 3 /* CHIP_REV - [11:9] */
+#define WM8962_IN_VU 0x0100 /* IN_VU */
+#define WM8962_IN_VU_MASK 0x0100 /* IN_VU */
+#define WM8962_IN_VU_SHIFT 8 /* IN_VU */
+#define WM8962_IN_VU_WIDTH 1 /* IN_VU */
+#define WM8962_INPGAR_MUTE 0x0080 /* INPGAR_MUTE */
+#define WM8962_INPGAR_MUTE_MASK 0x0080 /* INPGAR_MUTE */
+#define WM8962_INPGAR_MUTE_SHIFT 7 /* INPGAR_MUTE */
+#define WM8962_INPGAR_MUTE_WIDTH 1 /* INPGAR_MUTE */
+#define WM8962_INR_ZC 0x0040 /* INR_ZC */
+#define WM8962_INR_ZC_MASK 0x0040 /* INR_ZC */
+#define WM8962_INR_ZC_SHIFT 6 /* INR_ZC */
+#define WM8962_INR_ZC_WIDTH 1 /* INR_ZC */
+#define WM8962_INR_VOL_MASK 0x003F /* INR_VOL - [5:0] */
+#define WM8962_INR_VOL_SHIFT 0 /* INR_VOL - [5:0] */
+#define WM8962_INR_VOL_WIDTH 6 /* INR_VOL - [5:0] */
+
+/*
+ * R2 (0x02) - HPOUTL volume
+ */
+#define WM8962_HPOUT_VU 0x0100 /* HPOUT_VU */
+#define WM8962_HPOUT_VU_MASK 0x0100 /* HPOUT_VU */
+#define WM8962_HPOUT_VU_SHIFT 8 /* HPOUT_VU */
+#define WM8962_HPOUT_VU_WIDTH 1 /* HPOUT_VU */
+#define WM8962_HPOUTL_ZC 0x0080 /* HPOUTL_ZC */
+#define WM8962_HPOUTL_ZC_MASK 0x0080 /* HPOUTL_ZC */
+#define WM8962_HPOUTL_ZC_SHIFT 7 /* HPOUTL_ZC */
+#define WM8962_HPOUTL_ZC_WIDTH 1 /* HPOUTL_ZC */
+#define WM8962_HPOUTL_VOL_MASK 0x007F /* HPOUTL_VOL - [6:0] */
+#define WM8962_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [6:0] */
+#define WM8962_HPOUTL_VOL_WIDTH 7 /* HPOUTL_VOL - [6:0] */
+
+/*
+ * R3 (0x03) - HPOUTR volume
+ */
+#define WM8962_HPOUT_VU 0x0100 /* HPOUT_VU */
+#define WM8962_HPOUT_VU_MASK 0x0100 /* HPOUT_VU */
+#define WM8962_HPOUT_VU_SHIFT 8 /* HPOUT_VU */
+#define WM8962_HPOUT_VU_WIDTH 1 /* HPOUT_VU */
+#define WM8962_HPOUTR_ZC 0x0080 /* HPOUTR_ZC */
+#define WM8962_HPOUTR_ZC_MASK 0x0080 /* HPOUTR_ZC */
+#define WM8962_HPOUTR_ZC_SHIFT 7 /* HPOUTR_ZC */
+#define WM8962_HPOUTR_ZC_WIDTH 1 /* HPOUTR_ZC */
+#define WM8962_HPOUTR_VOL_MASK 0x007F /* HPOUTR_VOL - [6:0] */
+#define WM8962_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [6:0] */
+#define WM8962_HPOUTR_VOL_WIDTH 7 /* HPOUTR_VOL - [6:0] */
+
+/*
+ * R4 (0x04) - Clocking1
+ */
+#define WM8962_DSPCLK_DIV_MASK 0x0600 /* DSPCLK_DIV - [10:9] */
+#define WM8962_DSPCLK_DIV_SHIFT 9 /* DSPCLK_DIV - [10:9] */
+#define WM8962_DSPCLK_DIV_WIDTH 2 /* DSPCLK_DIV - [10:9] */
+#define WM8962_ADCSYS_CLK_DIV_MASK 0x01C0 /* ADCSYS_CLK_DIV - [8:6] */
+#define WM8962_ADCSYS_CLK_DIV_SHIFT 6 /* ADCSYS_CLK_DIV - [8:6] */
+#define WM8962_ADCSYS_CLK_DIV_WIDTH 3 /* ADCSYS_CLK_DIV - [8:6] */
+#define WM8962_DACSYS_CLK_DIV_MASK 0x0038 /* DACSYS_CLK_DIV - [5:3] */
+#define WM8962_DACSYS_CLK_DIV_SHIFT 3 /* DACSYS_CLK_DIV - [5:3] */
+#define WM8962_DACSYS_CLK_DIV_WIDTH 3 /* DACSYS_CLK_DIV - [5:3] */
+#define WM8962_MCLKDIV_MASK 0x0006 /* MCLKDIV - [2:1] */
+#define WM8962_MCLKDIV_SHIFT 1 /* MCLKDIV - [2:1] */
+#define WM8962_MCLKDIV_WIDTH 2 /* MCLKDIV - [2:1] */
+
+/*
+ * R5 (0x05) - ADC & DAC Control 1
+ */
+#define WM8962_ADCR_DAT_INV 0x0040 /* ADCR_DAT_INV */
+#define WM8962_ADCR_DAT_INV_MASK 0x0040 /* ADCR_DAT_INV */
+#define WM8962_ADCR_DAT_INV_SHIFT 6 /* ADCR_DAT_INV */
+#define WM8962_ADCR_DAT_INV_WIDTH 1 /* ADCR_DAT_INV */
+#define WM8962_ADCL_DAT_INV 0x0020 /* ADCL_DAT_INV */
+#define WM8962_ADCL_DAT_INV_MASK 0x0020 /* ADCL_DAT_INV */
+#define WM8962_ADCL_DAT_INV_SHIFT 5 /* ADCL_DAT_INV */
+#define WM8962_ADCL_DAT_INV_WIDTH 1 /* ADCL_DAT_INV */
+#define WM8962_DAC_MUTE_RAMP 0x0010 /* DAC_MUTE_RAMP */
+#define WM8962_DAC_MUTE_RAMP_MASK 0x0010 /* DAC_MUTE_RAMP */
+#define WM8962_DAC_MUTE_RAMP_SHIFT 4 /* DAC_MUTE_RAMP */
+#define WM8962_DAC_MUTE_RAMP_WIDTH 1 /* DAC_MUTE_RAMP */
+#define WM8962_DAC_MUTE 0x0008 /* DAC_MUTE */
+#define WM8962_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */
+#define WM8962_DAC_MUTE_SHIFT 3 /* DAC_MUTE */
+#define WM8962_DAC_MUTE_WIDTH 1 /* DAC_MUTE */
+#define WM8962_DAC_DEEMP_MASK 0x0006 /* DAC_DEEMP - [2:1] */
+#define WM8962_DAC_DEEMP_SHIFT 1 /* DAC_DEEMP - [2:1] */
+#define WM8962_DAC_DEEMP_WIDTH 2 /* DAC_DEEMP - [2:1] */
+#define WM8962_ADC_HPF_DIS 0x0001 /* ADC_HPF_DIS */
+#define WM8962_ADC_HPF_DIS_MASK 0x0001 /* ADC_HPF_DIS */
+#define WM8962_ADC_HPF_DIS_SHIFT 0 /* ADC_HPF_DIS */
+#define WM8962_ADC_HPF_DIS_WIDTH 1 /* ADC_HPF_DIS */
+
+/*
+ * R6 (0x06) - ADC & DAC Control 2
+ */
+#define WM8962_ADC_HPF_SR_MASK 0x3000 /* ADC_HPF_SR - [13:12] */
+#define WM8962_ADC_HPF_SR_SHIFT 12 /* ADC_HPF_SR - [13:12] */
+#define WM8962_ADC_HPF_SR_WIDTH 2 /* ADC_HPF_SR - [13:12] */
+#define WM8962_ADC_HPF_MODE 0x0400 /* ADC_HPF_MODE */
+#define WM8962_ADC_HPF_MODE_MASK 0x0400 /* ADC_HPF_MODE */
+#define WM8962_ADC_HPF_MODE_SHIFT 10 /* ADC_HPF_MODE */
+#define WM8962_ADC_HPF_MODE_WIDTH 1 /* ADC_HPF_MODE */
+#define WM8962_ADC_HPF_CUT_MASK 0x0380 /* ADC_HPF_CUT - [9:7] */
+#define WM8962_ADC_HPF_CUT_SHIFT 7 /* ADC_HPF_CUT - [9:7] */
+#define WM8962_ADC_HPF_CUT_WIDTH 3 /* ADC_HPF_CUT - [9:7] */
+#define WM8962_DACR_DAT_INV 0x0040 /* DACR_DAT_INV */
+#define WM8962_DACR_DAT_INV_MASK 0x0040 /* DACR_DAT_INV */
+#define WM8962_DACR_DAT_INV_SHIFT 6 /* DACR_DAT_INV */
+#define WM8962_DACR_DAT_INV_WIDTH 1 /* DACR_DAT_INV */
+#define WM8962_DACL_DAT_INV 0x0020 /* DACL_DAT_INV */
+#define WM8962_DACL_DAT_INV_MASK 0x0020 /* DACL_DAT_INV */
+#define WM8962_DACL_DAT_INV_SHIFT 5 /* DACL_DAT_INV */
+#define WM8962_DACL_DAT_INV_WIDTH 1 /* DACL_DAT_INV */
+#define WM8962_DAC_UNMUTE_RAMP 0x0008 /* DAC_UNMUTE_RAMP */
+#define WM8962_DAC_UNMUTE_RAMP_MASK 0x0008 /* DAC_UNMUTE_RAMP */
+#define WM8962_DAC_UNMUTE_RAMP_SHIFT 3 /* DAC_UNMUTE_RAMP */
+#define WM8962_DAC_UNMUTE_RAMP_WIDTH 1 /* DAC_UNMUTE_RAMP */
+#define WM8962_DAC_MUTERATE 0x0004 /* DAC_MUTERATE */
+#define WM8962_DAC_MUTERATE_MASK 0x0004 /* DAC_MUTERATE */
+#define WM8962_DAC_MUTERATE_SHIFT 2 /* DAC_MUTERATE */
+#define WM8962_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
+#define WM8962_DAC_HP 0x0001 /* DAC_HP */
+#define WM8962_DAC_HP_MASK 0x0001 /* DAC_HP */
+#define WM8962_DAC_HP_SHIFT 0 /* DAC_HP */
+#define WM8962_DAC_HP_WIDTH 1 /* DAC_HP */
+
+/*
+ * R7 (0x07) - Audio Interface 0
+ */
+#define WM8962_AIFDAC_TDM_MODE 0x1000 /* AIFDAC_TDM_MODE */
+#define WM8962_AIFDAC_TDM_MODE_MASK 0x1000 /* AIFDAC_TDM_MODE */
+#define WM8962_AIFDAC_TDM_MODE_SHIFT 12 /* AIFDAC_TDM_MODE */
+#define WM8962_AIFDAC_TDM_MODE_WIDTH 1 /* AIFDAC_TDM_MODE */
+#define WM8962_AIFDAC_TDM_SLOT 0x0800 /* AIFDAC_TDM_SLOT */
+#define WM8962_AIFDAC_TDM_SLOT_MASK 0x0800 /* AIFDAC_TDM_SLOT */
+#define WM8962_AIFDAC_TDM_SLOT_SHIFT 11 /* AIFDAC_TDM_SLOT */
+#define WM8962_AIFDAC_TDM_SLOT_WIDTH 1 /* AIFDAC_TDM_SLOT */
+#define WM8962_AIFADC_TDM_MODE 0x0400 /* AIFADC_TDM_MODE */
+#define WM8962_AIFADC_TDM_MODE_MASK 0x0400 /* AIFADC_TDM_MODE */
+#define WM8962_AIFADC_TDM_MODE_SHIFT 10 /* AIFADC_TDM_MODE */
+#define WM8962_AIFADC_TDM_MODE_WIDTH 1 /* AIFADC_TDM_MODE */
+#define WM8962_AIFADC_TDM_SLOT 0x0200 /* AIFADC_TDM_SLOT */
+#define WM8962_AIFADC_TDM_SLOT_MASK 0x0200 /* AIFADC_TDM_SLOT */
+#define WM8962_AIFADC_TDM_SLOT_SHIFT 9 /* AIFADC_TDM_SLOT */
+#define WM8962_AIFADC_TDM_SLOT_WIDTH 1 /* AIFADC_TDM_SLOT */
+#define WM8962_ADC_LRSWAP 0x0100 /* ADC_LRSWAP */
+#define WM8962_ADC_LRSWAP_MASK 0x0100 /* ADC_LRSWAP */
+#define WM8962_ADC_LRSWAP_SHIFT 8 /* ADC_LRSWAP */
+#define WM8962_ADC_LRSWAP_WIDTH 1 /* ADC_LRSWAP */
+#define WM8962_BCLK_INV 0x0080 /* BCLK_INV */
+#define WM8962_BCLK_INV_MASK 0x0080 /* BCLK_INV */
+#define WM8962_BCLK_INV_SHIFT 7 /* BCLK_INV */
+#define WM8962_BCLK_INV_WIDTH 1 /* BCLK_INV */
+#define WM8962_MSTR 0x0040 /* MSTR */
+#define WM8962_MSTR_MASK 0x0040 /* MSTR */
+#define WM8962_MSTR_SHIFT 6 /* MSTR */
+#define WM8962_MSTR_WIDTH 1 /* MSTR */
+#define WM8962_DAC_LRSWAP 0x0020 /* DAC_LRSWAP */
+#define WM8962_DAC_LRSWAP_MASK 0x0020 /* DAC_LRSWAP */
+#define WM8962_DAC_LRSWAP_SHIFT 5 /* DAC_LRSWAP */
+#define WM8962_DAC_LRSWAP_WIDTH 1 /* DAC_LRSWAP */
+#define WM8962_LRCLK_INV 0x0010 /* LRCLK_INV */
+#define WM8962_LRCLK_INV_MASK 0x0010 /* LRCLK_INV */
+#define WM8962_LRCLK_INV_SHIFT 4 /* LRCLK_INV */
+#define WM8962_LRCLK_INV_WIDTH 1 /* LRCLK_INV */
+#define WM8962_WL_MASK 0x000C /* WL - [3:2] */
+#define WM8962_WL_SHIFT 2 /* WL - [3:2] */
+#define WM8962_WL_WIDTH 2 /* WL - [3:2] */
+#define WM8962_FMT_MASK 0x0003 /* FMT - [1:0] */
+#define WM8962_FMT_SHIFT 0 /* FMT - [1:0] */
+#define WM8962_FMT_WIDTH 2 /* FMT - [1:0] */
+
+/*
+ * R8 (0x08) - Clocking2
+ */
+#define WM8962_CLKREG_OVD 0x0800 /* CLKREG_OVD */
+#define WM8962_CLKREG_OVD_MASK 0x0800 /* CLKREG_OVD */
+#define WM8962_CLKREG_OVD_SHIFT 11 /* CLKREG_OVD */
+#define WM8962_CLKREG_OVD_WIDTH 1 /* CLKREG_OVD */
+#define WM8962_SYSCLK_SRC_MASK 0x0600 /* SYSCLK_SRC - [10:9] */
+#define WM8962_SYSCLK_SRC_SHIFT 9 /* SYSCLK_SRC - [10:9] */
+#define WM8962_SYSCLK_SRC_WIDTH 2 /* SYSCLK_SRC - [10:9] */
+#define WM8962_CLASSD_CLK_DIV_MASK 0x01C0 /* CLASSD_CLK_DIV - [8:6] */
+#define WM8962_CLASSD_CLK_DIV_SHIFT 6 /* CLASSD_CLK_DIV - [8:6] */
+#define WM8962_CLASSD_CLK_DIV_WIDTH 3 /* CLASSD_CLK_DIV - [8:6] */
+#define WM8962_SYSCLK_ENA 0x0020 /* SYSCLK_ENA */
+#define WM8962_SYSCLK_ENA_MASK 0x0020 /* SYSCLK_ENA */
+#define WM8962_SYSCLK_ENA_SHIFT 5 /* SYSCLK_ENA */
+#define WM8962_SYSCLK_ENA_WIDTH 1 /* SYSCLK_ENA */
+#define WM8962_BCLK_DIV_MASK 0x000F /* BCLK_DIV - [3:0] */
+#define WM8962_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [3:0] */
+#define WM8962_BCLK_DIV_WIDTH 4 /* BCLK_DIV - [3:0] */
+
+/*
+ * R9 (0x09) - Audio Interface 1
+ */
+#define WM8962_AUTOMUTE_STS 0x0800 /* AUTOMUTE_STS */
+#define WM8962_AUTOMUTE_STS_MASK 0x0800 /* AUTOMUTE_STS */
+#define WM8962_AUTOMUTE_STS_SHIFT 11 /* AUTOMUTE_STS */
+#define WM8962_AUTOMUTE_STS_WIDTH 1 /* AUTOMUTE_STS */
+#define WM8962_DAC_AUTOMUTE_SAMPLES_MASK 0x0300 /* DAC_AUTOMUTE_SAMPLES - [9:8] */
+#define WM8962_DAC_AUTOMUTE_SAMPLES_SHIFT 8 /* DAC_AUTOMUTE_SAMPLES - [9:8] */
+#define WM8962_DAC_AUTOMUTE_SAMPLES_WIDTH 2 /* DAC_AUTOMUTE_SAMPLES - [9:8] */
+#define WM8962_DAC_AUTOMUTE 0x0080 /* DAC_AUTOMUTE */
+#define WM8962_DAC_AUTOMUTE_MASK 0x0080 /* DAC_AUTOMUTE */
+#define WM8962_DAC_AUTOMUTE_SHIFT 7 /* DAC_AUTOMUTE */
+#define WM8962_DAC_AUTOMUTE_WIDTH 1 /* DAC_AUTOMUTE */
+#define WM8962_DAC_COMP 0x0010 /* DAC_COMP */
+#define WM8962_DAC_COMP_MASK 0x0010 /* DAC_COMP */
+#define WM8962_DAC_COMP_SHIFT 4 /* DAC_COMP */
+#define WM8962_DAC_COMP_WIDTH 1 /* DAC_COMP */
+#define WM8962_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */
+#define WM8962_DAC_COMPMODE_MASK 0x0008 /* DAC_COMPMODE */
+#define WM8962_DAC_COMPMODE_SHIFT 3 /* DAC_COMPMODE */
+#define WM8962_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */
+#define WM8962_ADC_COMP 0x0004 /* ADC_COMP */
+#define WM8962_ADC_COMP_MASK 0x0004 /* ADC_COMP */
+#define WM8962_ADC_COMP_SHIFT 2 /* ADC_COMP */
+#define WM8962_ADC_COMP_WIDTH 1 /* ADC_COMP */
+#define WM8962_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */
+#define WM8962_ADC_COMPMODE_MASK 0x0002 /* ADC_COMPMODE */
+#define WM8962_ADC_COMPMODE_SHIFT 1 /* ADC_COMPMODE */
+#define WM8962_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */
+#define WM8962_LOOPBACK 0x0001 /* LOOPBACK */
+#define WM8962_LOOPBACK_MASK 0x0001 /* LOOPBACK */
+#define WM8962_LOOPBACK_SHIFT 0 /* LOOPBACK */
+#define WM8962_LOOPBACK_WIDTH 1 /* LOOPBACK */
+
+/*
+ * R10 (0x0A) - Left DAC volume
+ */
+#define WM8962_DAC_VU 0x0100 /* DAC_VU */
+#define WM8962_DAC_VU_MASK 0x0100 /* DAC_VU */
+#define WM8962_DAC_VU_SHIFT 8 /* DAC_VU */
+#define WM8962_DAC_VU_WIDTH 1 /* DAC_VU */
+#define WM8962_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8962_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */
+#define WM8962_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */
+
+/*
+ * R11 (0x0B) - Right DAC volume
+ */
+#define WM8962_DAC_VU 0x0100 /* DAC_VU */
+#define WM8962_DAC_VU_MASK 0x0100 /* DAC_VU */
+#define WM8962_DAC_VU_SHIFT 8 /* DAC_VU */
+#define WM8962_DAC_VU_WIDTH 1 /* DAC_VU */
+#define WM8962_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8962_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */
+#define WM8962_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */
+
+/*
+ * R14 (0x0E) - Audio Interface 2
+ */
+#define WM8962_AIF_RATE_MASK 0x07FF /* AIF_RATE - [10:0] */
+#define WM8962_AIF_RATE_SHIFT 0 /* AIF_RATE - [10:0] */
+#define WM8962_AIF_RATE_WIDTH 11 /* AIF_RATE - [10:0] */
+
+/*
+ * R15 (0x0F) - Software Reset
+ */
+#define WM8962_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */
+#define WM8962_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */
+#define WM8962_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */
+
+/*
+ * R17 (0x11) - ALC1
+ */
+#define WM8962_ALC_INACTIVE_ENA 0x0400 /* ALC_INACTIVE_ENA */
+#define WM8962_ALC_INACTIVE_ENA_MASK 0x0400 /* ALC_INACTIVE_ENA */
+#define WM8962_ALC_INACTIVE_ENA_SHIFT 10 /* ALC_INACTIVE_ENA */
+#define WM8962_ALC_INACTIVE_ENA_WIDTH 1 /* ALC_INACTIVE_ENA */
+#define WM8962_ALC_LVL_MODE 0x0200 /* ALC_LVL_MODE */
+#define WM8962_ALC_LVL_MODE_MASK 0x0200 /* ALC_LVL_MODE */
+#define WM8962_ALC_LVL_MODE_SHIFT 9 /* ALC_LVL_MODE */
+#define WM8962_ALC_LVL_MODE_WIDTH 1 /* ALC_LVL_MODE */
+#define WM8962_ALCL_ENA 0x0100 /* ALCL_ENA */
+#define WM8962_ALCL_ENA_MASK 0x0100 /* ALCL_ENA */
+#define WM8962_ALCL_ENA_SHIFT 8 /* ALCL_ENA */
+#define WM8962_ALCL_ENA_WIDTH 1 /* ALCL_ENA */
+#define WM8962_ALCR_ENA 0x0080 /* ALCR_ENA */
+#define WM8962_ALCR_ENA_MASK 0x0080 /* ALCR_ENA */
+#define WM8962_ALCR_ENA_SHIFT 7 /* ALCR_ENA */
+#define WM8962_ALCR_ENA_WIDTH 1 /* ALCR_ENA */
+#define WM8962_ALC_MAXGAIN_MASK 0x0070 /* ALC_MAXGAIN - [6:4] */
+#define WM8962_ALC_MAXGAIN_SHIFT 4 /* ALC_MAXGAIN - [6:4] */
+#define WM8962_ALC_MAXGAIN_WIDTH 3 /* ALC_MAXGAIN - [6:4] */
+#define WM8962_ALC_LVL_MASK 0x000F /* ALC_LVL - [3:0] */
+#define WM8962_ALC_LVL_SHIFT 0 /* ALC_LVL - [3:0] */
+#define WM8962_ALC_LVL_WIDTH 4 /* ALC_LVL - [3:0] */
+
+/*
+ * R18 (0x12) - ALC2
+ */
+#define WM8962_ALC_LOCK_STS 0x8000 /* ALC_LOCK_STS */
+#define WM8962_ALC_LOCK_STS_MASK 0x8000 /* ALC_LOCK_STS */
+#define WM8962_ALC_LOCK_STS_SHIFT 15 /* ALC_LOCK_STS */
+#define WM8962_ALC_LOCK_STS_WIDTH 1 /* ALC_LOCK_STS */
+#define WM8962_ALC_THRESH_STS 0x4000 /* ALC_THRESH_STS */
+#define WM8962_ALC_THRESH_STS_MASK 0x4000 /* ALC_THRESH_STS */
+#define WM8962_ALC_THRESH_STS_SHIFT 14 /* ALC_THRESH_STS */
+#define WM8962_ALC_THRESH_STS_WIDTH 1 /* ALC_THRESH_STS */
+#define WM8962_ALC_SAT_STS 0x2000 /* ALC_SAT_STS */
+#define WM8962_ALC_SAT_STS_MASK 0x2000 /* ALC_SAT_STS */
+#define WM8962_ALC_SAT_STS_SHIFT 13 /* ALC_SAT_STS */
+#define WM8962_ALC_SAT_STS_WIDTH 1 /* ALC_SAT_STS */
+#define WM8962_ALC_PKOVR_STS 0x1000 /* ALC_PKOVR_STS */
+#define WM8962_ALC_PKOVR_STS_MASK 0x1000 /* ALC_PKOVR_STS */
+#define WM8962_ALC_PKOVR_STS_SHIFT 12 /* ALC_PKOVR_STS */
+#define WM8962_ALC_PKOVR_STS_WIDTH 1 /* ALC_PKOVR_STS */
+#define WM8962_ALC_NGATE_STS 0x0800 /* ALC_NGATE_STS */
+#define WM8962_ALC_NGATE_STS_MASK 0x0800 /* ALC_NGATE_STS */
+#define WM8962_ALC_NGATE_STS_SHIFT 11 /* ALC_NGATE_STS */
+#define WM8962_ALC_NGATE_STS_WIDTH 1 /* ALC_NGATE_STS */
+#define WM8962_ALC_ZC 0x0080 /* ALC_ZC */
+#define WM8962_ALC_ZC_MASK 0x0080 /* ALC_ZC */
+#define WM8962_ALC_ZC_SHIFT 7 /* ALC_ZC */
+#define WM8962_ALC_ZC_WIDTH 1 /* ALC_ZC */
+#define WM8962_ALC_MINGAIN_MASK 0x0070 /* ALC_MINGAIN - [6:4] */
+#define WM8962_ALC_MINGAIN_SHIFT 4 /* ALC_MINGAIN - [6:4] */
+#define WM8962_ALC_MINGAIN_WIDTH 3 /* ALC_MINGAIN - [6:4] */
+#define WM8962_ALC_HLD_MASK 0x000F /* ALC_HLD - [3:0] */
+#define WM8962_ALC_HLD_SHIFT 0 /* ALC_HLD - [3:0] */
+#define WM8962_ALC_HLD_WIDTH 4 /* ALC_HLD - [3:0] */
+
+/*
+ * R19 (0x13) - ALC3
+ */
+#define WM8962_ALC_NGATE_GAIN_MASK 0x1C00 /* ALC_NGATE_GAIN - [12:10] */
+#define WM8962_ALC_NGATE_GAIN_SHIFT 10 /* ALC_NGATE_GAIN - [12:10] */
+#define WM8962_ALC_NGATE_GAIN_WIDTH 3 /* ALC_NGATE_GAIN - [12:10] */
+#define WM8962_ALC_MODE 0x0100 /* ALC_MODE */
+#define WM8962_ALC_MODE_MASK 0x0100 /* ALC_MODE */
+#define WM8962_ALC_MODE_SHIFT 8 /* ALC_MODE */
+#define WM8962_ALC_MODE_WIDTH 1 /* ALC_MODE */
+#define WM8962_ALC_DCY_MASK 0x00F0 /* ALC_DCY - [7:4] */
+#define WM8962_ALC_DCY_SHIFT 4 /* ALC_DCY - [7:4] */
+#define WM8962_ALC_DCY_WIDTH 4 /* ALC_DCY - [7:4] */
+#define WM8962_ALC_ATK_MASK 0x000F /* ALC_ATK - [3:0] */
+#define WM8962_ALC_ATK_SHIFT 0 /* ALC_ATK - [3:0] */
+#define WM8962_ALC_ATK_WIDTH 4 /* ALC_ATK - [3:0] */
+
+/*
+ * R20 (0x14) - Noise Gate
+ */
+#define WM8962_ALC_NGATE_DCY_MASK 0xF000 /* ALC_NGATE_DCY - [15:12] */
+#define WM8962_ALC_NGATE_DCY_SHIFT 12 /* ALC_NGATE_DCY - [15:12] */
+#define WM8962_ALC_NGATE_DCY_WIDTH 4 /* ALC_NGATE_DCY - [15:12] */
+#define WM8962_ALC_NGATE_ATK_MASK 0x0F00 /* ALC_NGATE_ATK - [11:8] */
+#define WM8962_ALC_NGATE_ATK_SHIFT 8 /* ALC_NGATE_ATK - [11:8] */
+#define WM8962_ALC_NGATE_ATK_WIDTH 4 /* ALC_NGATE_ATK - [11:8] */
+#define WM8962_ALC_NGATE_THR_MASK 0x00F8 /* ALC_NGATE_THR - [7:3] */
+#define WM8962_ALC_NGATE_THR_SHIFT 3 /* ALC_NGATE_THR - [7:3] */
+#define WM8962_ALC_NGATE_THR_WIDTH 5 /* ALC_NGATE_THR - [7:3] */
+#define WM8962_ALC_NGATE_MODE_MASK 0x0006 /* ALC_NGATE_MODE - [2:1] */
+#define WM8962_ALC_NGATE_MODE_SHIFT 1 /* ALC_NGATE_MODE - [2:1] */
+#define WM8962_ALC_NGATE_MODE_WIDTH 2 /* ALC_NGATE_MODE - [2:1] */
+#define WM8962_ALC_NGATE_ENA 0x0001 /* ALC_NGATE_ENA */
+#define WM8962_ALC_NGATE_ENA_MASK 0x0001 /* ALC_NGATE_ENA */
+#define WM8962_ALC_NGATE_ENA_SHIFT 0 /* ALC_NGATE_ENA */
+#define WM8962_ALC_NGATE_ENA_WIDTH 1 /* ALC_NGATE_ENA */
+
+/*
+ * R21 (0x15) - Left ADC volume
+ */
+#define WM8962_ADC_VU 0x0100 /* ADC_VU */
+#define WM8962_ADC_VU_MASK 0x0100 /* ADC_VU */
+#define WM8962_ADC_VU_SHIFT 8 /* ADC_VU */
+#define WM8962_ADC_VU_WIDTH 1 /* ADC_VU */
+#define WM8962_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8962_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */
+#define WM8962_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */
+
+/*
+ * R22 (0x16) - Right ADC volume
+ */
+#define WM8962_ADC_VU 0x0100 /* ADC_VU */
+#define WM8962_ADC_VU_MASK 0x0100 /* ADC_VU */
+#define WM8962_ADC_VU_SHIFT 8 /* ADC_VU */
+#define WM8962_ADC_VU_WIDTH 1 /* ADC_VU */
+#define WM8962_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8962_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */
+#define WM8962_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */
+
+/*
+ * R23 (0x17) - Additional control(1)
+ */
+#define WM8962_THERR_ACT 0x0100 /* THERR_ACT */
+#define WM8962_THERR_ACT_MASK 0x0100 /* THERR_ACT */
+#define WM8962_THERR_ACT_SHIFT 8 /* THERR_ACT */
+#define WM8962_THERR_ACT_WIDTH 1 /* THERR_ACT */
+#define WM8962_ADC_BIAS 0x0040 /* ADC_BIAS */
+#define WM8962_ADC_BIAS_MASK 0x0040 /* ADC_BIAS */
+#define WM8962_ADC_BIAS_SHIFT 6 /* ADC_BIAS */
+#define WM8962_ADC_BIAS_WIDTH 1 /* ADC_BIAS */
+#define WM8962_ADC_HP 0x0020 /* ADC_HP */
+#define WM8962_ADC_HP_MASK 0x0020 /* ADC_HP */
+#define WM8962_ADC_HP_SHIFT 5 /* ADC_HP */
+#define WM8962_ADC_HP_WIDTH 1 /* ADC_HP */
+#define WM8962_TOCLK_ENA 0x0001 /* TOCLK_ENA */
+#define WM8962_TOCLK_ENA_MASK 0x0001 /* TOCLK_ENA */
+#define WM8962_TOCLK_ENA_SHIFT 0 /* TOCLK_ENA */
+#define WM8962_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */
+
+/*
+ * R24 (0x18) - Additional control(2)
+ */
+#define WM8962_AIF_TRI 0x0008 /* AIF_TRI */
+#define WM8962_AIF_TRI_MASK 0x0008 /* AIF_TRI */
+#define WM8962_AIF_TRI_SHIFT 3 /* AIF_TRI */
+#define WM8962_AIF_TRI_WIDTH 1 /* AIF_TRI */
+
+/*
+ * R25 (0x19) - Pwr Mgmt (1)
+ */
+#define WM8962_DMIC_ENA 0x0400 /* DMIC_ENA */
+#define WM8962_DMIC_ENA_MASK 0x0400 /* DMIC_ENA */
+#define WM8962_DMIC_ENA_SHIFT 10 /* DMIC_ENA */
+#define WM8962_DMIC_ENA_WIDTH 1 /* DMIC_ENA */
+#define WM8962_OPCLK_ENA 0x0200 /* OPCLK_ENA */
+#define WM8962_OPCLK_ENA_MASK 0x0200 /* OPCLK_ENA */
+#define WM8962_OPCLK_ENA_SHIFT 9 /* OPCLK_ENA */
+#define WM8962_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */
+#define WM8962_VMID_SEL_MASK 0x0180 /* VMID_SEL - [8:7] */
+#define WM8962_VMID_SEL_SHIFT 7 /* VMID_SEL - [8:7] */
+#define WM8962_VMID_SEL_WIDTH 2 /* VMID_SEL - [8:7] */
+#define WM8962_BIAS_ENA 0x0040 /* BIAS_ENA */
+#define WM8962_BIAS_ENA_MASK 0x0040 /* BIAS_ENA */
+#define WM8962_BIAS_ENA_SHIFT 6 /* BIAS_ENA */
+#define WM8962_BIAS_ENA_WIDTH 1 /* BIAS_ENA */
+#define WM8962_INL_ENA 0x0020 /* INL_ENA */
+#define WM8962_INL_ENA_MASK 0x0020 /* INL_ENA */
+#define WM8962_INL_ENA_SHIFT 5 /* INL_ENA */
+#define WM8962_INL_ENA_WIDTH 1 /* INL_ENA */
+#define WM8962_INR_ENA 0x0010 /* INR_ENA */
+#define WM8962_INR_ENA_MASK 0x0010 /* INR_ENA */
+#define WM8962_INR_ENA_SHIFT 4 /* INR_ENA */
+#define WM8962_INR_ENA_WIDTH 1 /* INR_ENA */
+#define WM8962_ADCL_ENA 0x0008 /* ADCL_ENA */
+#define WM8962_ADCL_ENA_MASK 0x0008 /* ADCL_ENA */
+#define WM8962_ADCL_ENA_SHIFT 3 /* ADCL_ENA */
+#define WM8962_ADCL_ENA_WIDTH 1 /* ADCL_ENA */
+#define WM8962_ADCR_ENA 0x0004 /* ADCR_ENA */
+#define WM8962_ADCR_ENA_MASK 0x0004 /* ADCR_ENA */
+#define WM8962_ADCR_ENA_SHIFT 2 /* ADCR_ENA */
+#define WM8962_ADCR_ENA_WIDTH 1 /* ADCR_ENA */
+#define WM8962_MICBIAS_ENA 0x0002 /* MICBIAS_ENA */
+#define WM8962_MICBIAS_ENA_MASK 0x0002 /* MICBIAS_ENA */
+#define WM8962_MICBIAS_ENA_SHIFT 1 /* MICBIAS_ENA */
+#define WM8962_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */
+
+/*
+ * R26 (0x1A) - Pwr Mgmt (2)
+ */
+#define WM8962_DACL_ENA 0x0100 /* DACL_ENA */
+#define WM8962_DACL_ENA_MASK 0x0100 /* DACL_ENA */
+#define WM8962_DACL_ENA_SHIFT 8 /* DACL_ENA */
+#define WM8962_DACL_ENA_WIDTH 1 /* DACL_ENA */
+#define WM8962_DACR_ENA 0x0080 /* DACR_ENA */
+#define WM8962_DACR_ENA_MASK 0x0080 /* DACR_ENA */
+#define WM8962_DACR_ENA_SHIFT 7 /* DACR_ENA */
+#define WM8962_DACR_ENA_WIDTH 1 /* DACR_ENA */
+#define WM8962_HPOUTL_PGA_ENA 0x0040 /* HPOUTL_PGA_ENA */
+#define WM8962_HPOUTL_PGA_ENA_MASK 0x0040 /* HPOUTL_PGA_ENA */
+#define WM8962_HPOUTL_PGA_ENA_SHIFT 6 /* HPOUTL_PGA_ENA */
+#define WM8962_HPOUTL_PGA_ENA_WIDTH 1 /* HPOUTL_PGA_ENA */
+#define WM8962_HPOUTR_PGA_ENA 0x0020 /* HPOUTR_PGA_ENA */
+#define WM8962_HPOUTR_PGA_ENA_MASK 0x0020 /* HPOUTR_PGA_ENA */
+#define WM8962_HPOUTR_PGA_ENA_SHIFT 5 /* HPOUTR_PGA_ENA */
+#define WM8962_HPOUTR_PGA_ENA_WIDTH 1 /* HPOUTR_PGA_ENA */
+#define WM8962_SPKOUTL_PGA_ENA 0x0010 /* SPKOUTL_PGA_ENA */
+#define WM8962_SPKOUTL_PGA_ENA_MASK 0x0010 /* SPKOUTL_PGA_ENA */
+#define WM8962_SPKOUTL_PGA_ENA_SHIFT 4 /* SPKOUTL_PGA_ENA */
+#define WM8962_SPKOUTL_PGA_ENA_WIDTH 1 /* SPKOUTL_PGA_ENA */
+#define WM8962_SPKOUTR_PGA_ENA 0x0008 /* SPKOUTR_PGA_ENA */
+#define WM8962_SPKOUTR_PGA_ENA_MASK 0x0008 /* SPKOUTR_PGA_ENA */
+#define WM8962_SPKOUTR_PGA_ENA_SHIFT 3 /* SPKOUTR_PGA_ENA */
+#define WM8962_SPKOUTR_PGA_ENA_WIDTH 1 /* SPKOUTR_PGA_ENA */
+#define WM8962_HPOUTL_PGA_MUTE 0x0002 /* HPOUTL_PGA_MUTE */
+#define WM8962_HPOUTL_PGA_MUTE_MASK 0x0002 /* HPOUTL_PGA_MUTE */
+#define WM8962_HPOUTL_PGA_MUTE_SHIFT 1 /* HPOUTL_PGA_MUTE */
+#define WM8962_HPOUTL_PGA_MUTE_WIDTH 1 /* HPOUTL_PGA_MUTE */
+#define WM8962_HPOUTR_PGA_MUTE 0x0001 /* HPOUTR_PGA_MUTE */
+#define WM8962_HPOUTR_PGA_MUTE_MASK 0x0001 /* HPOUTR_PGA_MUTE */
+#define WM8962_HPOUTR_PGA_MUTE_SHIFT 0 /* HPOUTR_PGA_MUTE */
+#define WM8962_HPOUTR_PGA_MUTE_WIDTH 1 /* HPOUTR_PGA_MUTE */
+
+/*
+ * R27 (0x1B) - Additional Control (3)
+ */
+#define WM8962_SAMPLE_RATE_INT_MODE 0x0010 /* SAMPLE_RATE_INT_MODE */
+#define WM8962_SAMPLE_RATE_INT_MODE_MASK 0x0010 /* SAMPLE_RATE_INT_MODE */
+#define WM8962_SAMPLE_RATE_INT_MODE_SHIFT 4 /* SAMPLE_RATE_INT_MODE */
+#define WM8962_SAMPLE_RATE_INT_MODE_WIDTH 1 /* SAMPLE_RATE_INT_MODE */
+#define WM8962_SAMPLE_RATE_MASK 0x0007 /* SAMPLE_RATE - [2:0] */
+#define WM8962_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [2:0] */
+#define WM8962_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [2:0] */
+
+/*
+ * R28 (0x1C) - Anti-pop
+ */
+#define WM8962_STARTUP_BIAS_ENA 0x0010 /* STARTUP_BIAS_ENA */
+#define WM8962_STARTUP_BIAS_ENA_MASK 0x0010 /* STARTUP_BIAS_ENA */
+#define WM8962_STARTUP_BIAS_ENA_SHIFT 4 /* STARTUP_BIAS_ENA */
+#define WM8962_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */
+#define WM8962_VMID_BUF_ENA 0x0008 /* VMID_BUF_ENA */
+#define WM8962_VMID_BUF_ENA_MASK 0x0008 /* VMID_BUF_ENA */
+#define WM8962_VMID_BUF_ENA_SHIFT 3 /* VMID_BUF_ENA */
+#define WM8962_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */
+#define WM8962_VMID_RAMP 0x0004 /* VMID_RAMP */
+#define WM8962_VMID_RAMP_MASK 0x0004 /* VMID_RAMP */
+#define WM8962_VMID_RAMP_SHIFT 2 /* VMID_RAMP */
+#define WM8962_VMID_RAMP_WIDTH 1 /* VMID_RAMP */
+
+/*
+ * R30 (0x1E) - Clocking 3
+ */
+#define WM8962_DBCLK_DIV_MASK 0xE000 /* DBCLK_DIV - [15:13] */
+#define WM8962_DBCLK_DIV_SHIFT 13 /* DBCLK_DIV - [15:13] */
+#define WM8962_DBCLK_DIV_WIDTH 3 /* DBCLK_DIV - [15:13] */
+#define WM8962_OPCLK_DIV_MASK 0x1C00 /* OPCLK_DIV - [12:10] */
+#define WM8962_OPCLK_DIV_SHIFT 10 /* OPCLK_DIV - [12:10] */
+#define WM8962_OPCLK_DIV_WIDTH 3 /* OPCLK_DIV - [12:10] */
+#define WM8962_TOCLK_DIV_MASK 0x0380 /* TOCLK_DIV - [9:7] */
+#define WM8962_TOCLK_DIV_SHIFT 7 /* TOCLK_DIV - [9:7] */
+#define WM8962_TOCLK_DIV_WIDTH 3 /* TOCLK_DIV - [9:7] */
+#define WM8962_F256KCLK_DIV_MASK 0x007E /* F256KCLK_DIV - [6:1] */
+#define WM8962_F256KCLK_DIV_SHIFT 1 /* F256KCLK_DIV - [6:1] */
+#define WM8962_F256KCLK_DIV_WIDTH 6 /* F256KCLK_DIV - [6:1] */
+
+/*
+ * R31 (0x1F) - Input mixer control (1)
+ */
+#define WM8962_MIXINL_MUTE 0x0008 /* MIXINL_MUTE */
+#define WM8962_MIXINL_MUTE_MASK 0x0008 /* MIXINL_MUTE */
+#define WM8962_MIXINL_MUTE_SHIFT 3 /* MIXINL_MUTE */
+#define WM8962_MIXINL_MUTE_WIDTH 1 /* MIXINL_MUTE */
+#define WM8962_MIXINR_MUTE 0x0004 /* MIXINR_MUTE */
+#define WM8962_MIXINR_MUTE_MASK 0x0004 /* MIXINR_MUTE */
+#define WM8962_MIXINR_MUTE_SHIFT 2 /* MIXINR_MUTE */
+#define WM8962_MIXINR_MUTE_WIDTH 1 /* MIXINR_MUTE */
+#define WM8962_MIXINL_ENA 0x0002 /* MIXINL_ENA */
+#define WM8962_MIXINL_ENA_MASK 0x0002 /* MIXINL_ENA */
+#define WM8962_MIXINL_ENA_SHIFT 1 /* MIXINL_ENA */
+#define WM8962_MIXINL_ENA_WIDTH 1 /* MIXINL_ENA */
+#define WM8962_MIXINR_ENA 0x0001 /* MIXINR_ENA */
+#define WM8962_MIXINR_ENA_MASK 0x0001 /* MIXINR_ENA */
+#define WM8962_MIXINR_ENA_SHIFT 0 /* MIXINR_ENA */
+#define WM8962_MIXINR_ENA_WIDTH 1 /* MIXINR_ENA */
+
+/*
+ * R32 (0x20) - Left input mixer volume
+ */
+#define WM8962_IN2L_MIXINL_VOL_MASK 0x01C0 /* IN2L_MIXINL_VOL - [8:6] */
+#define WM8962_IN2L_MIXINL_VOL_SHIFT 6 /* IN2L_MIXINL_VOL - [8:6] */
+#define WM8962_IN2L_MIXINL_VOL_WIDTH 3 /* IN2L_MIXINL_VOL - [8:6] */
+#define WM8962_INPGAL_MIXINL_VOL_MASK 0x0038 /* INPGAL_MIXINL_VOL - [5:3] */
+#define WM8962_INPGAL_MIXINL_VOL_SHIFT 3 /* INPGAL_MIXINL_VOL - [5:3] */
+#define WM8962_INPGAL_MIXINL_VOL_WIDTH 3 /* INPGAL_MIXINL_VOL - [5:3] */
+#define WM8962_IN3L_MIXINL_VOL_MASK 0x0007 /* IN3L_MIXINL_VOL - [2:0] */
+#define WM8962_IN3L_MIXINL_VOL_SHIFT 0 /* IN3L_MIXINL_VOL - [2:0] */
+#define WM8962_IN3L_MIXINL_VOL_WIDTH 3 /* IN3L_MIXINL_VOL - [2:0] */
+
+/*
+ * R33 (0x21) - Right input mixer volume
+ */
+#define WM8962_IN2R_MIXINR_VOL_MASK 0x01C0 /* IN2R_MIXINR_VOL - [8:6] */
+#define WM8962_IN2R_MIXINR_VOL_SHIFT 6 /* IN2R_MIXINR_VOL - [8:6] */
+#define WM8962_IN2R_MIXINR_VOL_WIDTH 3 /* IN2R_MIXINR_VOL - [8:6] */
+#define WM8962_INPGAR_MIXINR_VOL_MASK 0x0038 /* INPGAR_MIXINR_VOL - [5:3] */
+#define WM8962_INPGAR_MIXINR_VOL_SHIFT 3 /* INPGAR_MIXINR_VOL - [5:3] */
+#define WM8962_INPGAR_MIXINR_VOL_WIDTH 3 /* INPGAR_MIXINR_VOL - [5:3] */
+#define WM8962_IN3R_MIXINR_VOL_MASK 0x0007 /* IN3R_MIXINR_VOL - [2:0] */
+#define WM8962_IN3R_MIXINR_VOL_SHIFT 0 /* IN3R_MIXINR_VOL - [2:0] */
+#define WM8962_IN3R_MIXINR_VOL_WIDTH 3 /* IN3R_MIXINR_VOL - [2:0] */
+
+/*
+ * R34 (0x22) - Input mixer control (2)
+ */
+#define WM8962_IN2L_TO_MIXINL 0x0020 /* IN2L_TO_MIXINL */
+#define WM8962_IN2L_TO_MIXINL_MASK 0x0020 /* IN2L_TO_MIXINL */
+#define WM8962_IN2L_TO_MIXINL_SHIFT 5 /* IN2L_TO_MIXINL */
+#define WM8962_IN2L_TO_MIXINL_WIDTH 1 /* IN2L_TO_MIXINL */
+#define WM8962_IN3L_TO_MIXINL 0x0010 /* IN3L_TO_MIXINL */
+#define WM8962_IN3L_TO_MIXINL_MASK 0x0010 /* IN3L_TO_MIXINL */
+#define WM8962_IN3L_TO_MIXINL_SHIFT 4 /* IN3L_TO_MIXINL */
+#define WM8962_IN3L_TO_MIXINL_WIDTH 1 /* IN3L_TO_MIXINL */
+#define WM8962_INPGAL_TO_MIXINL 0x0008 /* INPGAL_TO_MIXINL */
+#define WM8962_INPGAL_TO_MIXINL_MASK 0x0008 /* INPGAL_TO_MIXINL */
+#define WM8962_INPGAL_TO_MIXINL_SHIFT 3 /* INPGAL_TO_MIXINL */
+#define WM8962_INPGAL_TO_MIXINL_WIDTH 1 /* INPGAL_TO_MIXINL */
+#define WM8962_IN2R_TO_MIXINR 0x0004 /* IN2R_TO_MIXINR */
+#define WM8962_IN2R_TO_MIXINR_MASK 0x0004 /* IN2R_TO_MIXINR */
+#define WM8962_IN2R_TO_MIXINR_SHIFT 2 /* IN2R_TO_MIXINR */
+#define WM8962_IN2R_TO_MIXINR_WIDTH 1 /* IN2R_TO_MIXINR */
+#define WM8962_IN3R_TO_MIXINR 0x0002 /* IN3R_TO_MIXINR */
+#define WM8962_IN3R_TO_MIXINR_MASK 0x0002 /* IN3R_TO_MIXINR */
+#define WM8962_IN3R_TO_MIXINR_SHIFT 1 /* IN3R_TO_MIXINR */
+#define WM8962_IN3R_TO_MIXINR_WIDTH 1 /* IN3R_TO_MIXINR */
+#define WM8962_INPGAR_TO_MIXINR 0x0001 /* INPGAR_TO_MIXINR */
+#define WM8962_INPGAR_TO_MIXINR_MASK 0x0001 /* INPGAR_TO_MIXINR */
+#define WM8962_INPGAR_TO_MIXINR_SHIFT 0 /* INPGAR_TO_MIXINR */
+#define WM8962_INPGAR_TO_MIXINR_WIDTH 1 /* INPGAR_TO_MIXINR */
+
+/*
+ * R35 (0x23) - Input bias control
+ */
+#define WM8962_MIXIN_BIAS_MASK 0x0038 /* MIXIN_BIAS - [5:3] */
+#define WM8962_MIXIN_BIAS_SHIFT 3 /* MIXIN_BIAS - [5:3] */
+#define WM8962_MIXIN_BIAS_WIDTH 3 /* MIXIN_BIAS - [5:3] */
+#define WM8962_INPGA_BIAS_MASK 0x0007 /* INPGA_BIAS - [2:0] */
+#define WM8962_INPGA_BIAS_SHIFT 0 /* INPGA_BIAS - [2:0] */
+#define WM8962_INPGA_BIAS_WIDTH 3 /* INPGA_BIAS - [2:0] */
+
+/*
+ * R37 (0x25) - Left input PGA control
+ */
+#define WM8962_INPGAL_ENA 0x0010 /* INPGAL_ENA */
+#define WM8962_INPGAL_ENA_MASK 0x0010 /* INPGAL_ENA */
+#define WM8962_INPGAL_ENA_SHIFT 4 /* INPGAL_ENA */
+#define WM8962_INPGAL_ENA_WIDTH 1 /* INPGAL_ENA */
+#define WM8962_IN1L_TO_INPGAL 0x0008 /* IN1L_TO_INPGAL */
+#define WM8962_IN1L_TO_INPGAL_MASK 0x0008 /* IN1L_TO_INPGAL */
+#define WM8962_IN1L_TO_INPGAL_SHIFT 3 /* IN1L_TO_INPGAL */
+#define WM8962_IN1L_TO_INPGAL_WIDTH 1 /* IN1L_TO_INPGAL */
+#define WM8962_IN2L_TO_INPGAL 0x0004 /* IN2L_TO_INPGAL */
+#define WM8962_IN2L_TO_INPGAL_MASK 0x0004 /* IN2L_TO_INPGAL */
+#define WM8962_IN2L_TO_INPGAL_SHIFT 2 /* IN2L_TO_INPGAL */
+#define WM8962_IN2L_TO_INPGAL_WIDTH 1 /* IN2L_TO_INPGAL */
+#define WM8962_IN3L_TO_INPGAL 0x0002 /* IN3L_TO_INPGAL */
+#define WM8962_IN3L_TO_INPGAL_MASK 0x0002 /* IN3L_TO_INPGAL */
+#define WM8962_IN3L_TO_INPGAL_SHIFT 1 /* IN3L_TO_INPGAL */
+#define WM8962_IN3L_TO_INPGAL_WIDTH 1 /* IN3L_TO_INPGAL */
+#define WM8962_IN4L_TO_INPGAL 0x0001 /* IN4L_TO_INPGAL */
+#define WM8962_IN4L_TO_INPGAL_MASK 0x0001 /* IN4L_TO_INPGAL */
+#define WM8962_IN4L_TO_INPGAL_SHIFT 0 /* IN4L_TO_INPGAL */
+#define WM8962_IN4L_TO_INPGAL_WIDTH 1 /* IN4L_TO_INPGAL */
+
+/*
+ * R38 (0x26) - Right input PGA control
+ */
+#define WM8962_INPGAR_ENA 0x0010 /* INPGAR_ENA */
+#define WM8962_INPGAR_ENA_MASK 0x0010 /* INPGAR_ENA */
+#define WM8962_INPGAR_ENA_SHIFT 4 /* INPGAR_ENA */
+#define WM8962_INPGAR_ENA_WIDTH 1 /* INPGAR_ENA */
+#define WM8962_IN1R_TO_INPGAR 0x0008 /* IN1R_TO_INPGAR */
+#define WM8962_IN1R_TO_INPGAR_MASK 0x0008 /* IN1R_TO_INPGAR */
+#define WM8962_IN1R_TO_INPGAR_SHIFT 3 /* IN1R_TO_INPGAR */
+#define WM8962_IN1R_TO_INPGAR_WIDTH 1 /* IN1R_TO_INPGAR */
+#define WM8962_IN2R_TO_INPGAR 0x0004 /* IN2R_TO_INPGAR */
+#define WM8962_IN2R_TO_INPGAR_MASK 0x0004 /* IN2R_TO_INPGAR */
+#define WM8962_IN2R_TO_INPGAR_SHIFT 2 /* IN2R_TO_INPGAR */
+#define WM8962_IN2R_TO_INPGAR_WIDTH 1 /* IN2R_TO_INPGAR */
+#define WM8962_IN3R_TO_INPGAR 0x0002 /* IN3R_TO_INPGAR */
+#define WM8962_IN3R_TO_INPGAR_MASK 0x0002 /* IN3R_TO_INPGAR */
+#define WM8962_IN3R_TO_INPGAR_SHIFT 1 /* IN3R_TO_INPGAR */
+#define WM8962_IN3R_TO_INPGAR_WIDTH 1 /* IN3R_TO_INPGAR */
+#define WM8962_IN4R_TO_INPGAR 0x0001 /* IN4R_TO_INPGAR */
+#define WM8962_IN4R_TO_INPGAR_MASK 0x0001 /* IN4R_TO_INPGAR */
+#define WM8962_IN4R_TO_INPGAR_SHIFT 0 /* IN4R_TO_INPGAR */
+#define WM8962_IN4R_TO_INPGAR_WIDTH 1 /* IN4R_TO_INPGAR */
+
+/*
+ * R40 (0x28) - SPKOUTL volume
+ */
+#define WM8962_SPKOUT_VU 0x0100 /* SPKOUT_VU */
+#define WM8962_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */
+#define WM8962_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */
+#define WM8962_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */
+#define WM8962_SPKOUTL_ZC 0x0080 /* SPKOUTL_ZC */
+#define WM8962_SPKOUTL_ZC_MASK 0x0080 /* SPKOUTL_ZC */
+#define WM8962_SPKOUTL_ZC_SHIFT 7 /* SPKOUTL_ZC */
+#define WM8962_SPKOUTL_ZC_WIDTH 1 /* SPKOUTL_ZC */
+#define WM8962_SPKOUTL_VOL_MASK 0x007F /* SPKOUTL_VOL - [6:0] */
+#define WM8962_SPKOUTL_VOL_SHIFT 0 /* SPKOUTL_VOL - [6:0] */
+#define WM8962_SPKOUTL_VOL_WIDTH 7 /* SPKOUTL_VOL - [6:0] */
+
+/*
+ * R41 (0x29) - SPKOUTR volume
+ */
+#define WM8962_SPKOUTR_ZC 0x0080 /* SPKOUTR_ZC */
+#define WM8962_SPKOUTR_ZC_MASK 0x0080 /* SPKOUTR_ZC */
+#define WM8962_SPKOUTR_ZC_SHIFT 7 /* SPKOUTR_ZC */
+#define WM8962_SPKOUTR_ZC_WIDTH 1 /* SPKOUTR_ZC */
+#define WM8962_SPKOUTR_VOL_MASK 0x007F /* SPKOUTR_VOL - [6:0] */
+#define WM8962_SPKOUTR_VOL_SHIFT 0 /* SPKOUTR_VOL - [6:0] */
+#define WM8962_SPKOUTR_VOL_WIDTH 7 /* SPKOUTR_VOL - [6:0] */
+
+/*
+ * R47 (0x2F) - Thermal Shutdown Status
+ */
+#define WM8962_TEMP_ERR_HP 0x0008 /* TEMP_ERR_HP */
+#define WM8962_TEMP_ERR_HP_MASK 0x0008 /* TEMP_ERR_HP */
+#define WM8962_TEMP_ERR_HP_SHIFT 3 /* TEMP_ERR_HP */
+#define WM8962_TEMP_ERR_HP_WIDTH 1 /* TEMP_ERR_HP */
+#define WM8962_TEMP_WARN_HP 0x0004 /* TEMP_WARN_HP */
+#define WM8962_TEMP_WARN_HP_MASK 0x0004 /* TEMP_WARN_HP */
+#define WM8962_TEMP_WARN_HP_SHIFT 2 /* TEMP_WARN_HP */
+#define WM8962_TEMP_WARN_HP_WIDTH 1 /* TEMP_WARN_HP */
+#define WM8962_TEMP_ERR_SPK 0x0002 /* TEMP_ERR_SPK */
+#define WM8962_TEMP_ERR_SPK_MASK 0x0002 /* TEMP_ERR_SPK */
+#define WM8962_TEMP_ERR_SPK_SHIFT 1 /* TEMP_ERR_SPK */
+#define WM8962_TEMP_ERR_SPK_WIDTH 1 /* TEMP_ERR_SPK */
+#define WM8962_TEMP_WARN_SPK 0x0001 /* TEMP_WARN_SPK */
+#define WM8962_TEMP_WARN_SPK_MASK 0x0001 /* TEMP_WARN_SPK */
+#define WM8962_TEMP_WARN_SPK_SHIFT 0 /* TEMP_WARN_SPK */
+#define WM8962_TEMP_WARN_SPK_WIDTH 1 /* TEMP_WARN_SPK */
+
+/*
+ * R48 (0x30) - Additional Control (4)
+ */
+#define WM8962_MICDET_THR_MASK 0x7000 /* MICDET_THR - [14:12] */
+#define WM8962_MICDET_THR_SHIFT 12 /* MICDET_THR - [14:12] */
+#define WM8962_MICDET_THR_WIDTH 3 /* MICDET_THR - [14:12] */
+#define WM8962_MICSHORT_THR_MASK 0x0C00 /* MICSHORT_THR - [11:10] */
+#define WM8962_MICSHORT_THR_SHIFT 10 /* MICSHORT_THR - [11:10] */
+#define WM8962_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [11:10] */
+#define WM8962_MICDET_ENA 0x0200 /* MICDET_ENA */
+#define WM8962_MICDET_ENA_MASK 0x0200 /* MICDET_ENA */
+#define WM8962_MICDET_ENA_SHIFT 9 /* MICDET_ENA */
+#define WM8962_MICDET_ENA_WIDTH 1 /* MICDET_ENA */
+#define WM8962_MICDET_STS 0x0080 /* MICDET_STS */
+#define WM8962_MICDET_STS_MASK 0x0080 /* MICDET_STS */
+#define WM8962_MICDET_STS_SHIFT 7 /* MICDET_STS */
+#define WM8962_MICDET_STS_WIDTH 1 /* MICDET_STS */
+#define WM8962_MICSHORT_STS 0x0040 /* MICSHORT_STS */
+#define WM8962_MICSHORT_STS_MASK 0x0040 /* MICSHORT_STS */
+#define WM8962_MICSHORT_STS_SHIFT 6 /* MICSHORT_STS */
+#define WM8962_MICSHORT_STS_WIDTH 1 /* MICSHORT_STS */
+#define WM8962_TEMP_ENA_HP 0x0004 /* TEMP_ENA_HP */
+#define WM8962_TEMP_ENA_HP_MASK 0x0004 /* TEMP_ENA_HP */
+#define WM8962_TEMP_ENA_HP_SHIFT 2 /* TEMP_ENA_HP */
+#define WM8962_TEMP_ENA_HP_WIDTH 1 /* TEMP_ENA_HP */
+#define WM8962_TEMP_ENA_SPK 0x0002 /* TEMP_ENA_SPK */
+#define WM8962_TEMP_ENA_SPK_MASK 0x0002 /* TEMP_ENA_SPK */
+#define WM8962_TEMP_ENA_SPK_SHIFT 1 /* TEMP_ENA_SPK */
+#define WM8962_TEMP_ENA_SPK_WIDTH 1 /* TEMP_ENA_SPK */
+#define WM8962_MICBIAS_LVL 0x0001 /* MICBIAS_LVL */
+#define WM8962_MICBIAS_LVL_MASK 0x0001 /* MICBIAS_LVL */
+#define WM8962_MICBIAS_LVL_SHIFT 0 /* MICBIAS_LVL */
+#define WM8962_MICBIAS_LVL_WIDTH 1 /* MICBIAS_LVL */
+
+/*
+ * R49 (0x31) - Class D Control 1
+ */
+#define WM8962_SPKOUTR_ENA 0x0080 /* SPKOUTR_ENA */
+#define WM8962_SPKOUTR_ENA_MASK 0x0080 /* SPKOUTR_ENA */
+#define WM8962_SPKOUTR_ENA_SHIFT 7 /* SPKOUTR_ENA */
+#define WM8962_SPKOUTR_ENA_WIDTH 1 /* SPKOUTR_ENA */
+#define WM8962_SPKOUTL_ENA 0x0040 /* SPKOUTL_ENA */
+#define WM8962_SPKOUTL_ENA_MASK 0x0040 /* SPKOUTL_ENA */
+#define WM8962_SPKOUTL_ENA_SHIFT 6 /* SPKOUTL_ENA */
+#define WM8962_SPKOUTL_ENA_WIDTH 1 /* SPKOUTL_ENA */
+#define WM8962_SPKOUTL_PGA_MUTE 0x0002 /* SPKOUTL_PGA_MUTE */
+#define WM8962_SPKOUTL_PGA_MUTE_MASK 0x0002 /* SPKOUTL_PGA_MUTE */
+#define WM8962_SPKOUTL_PGA_MUTE_SHIFT 1 /* SPKOUTL_PGA_MUTE */
+#define WM8962_SPKOUTL_PGA_MUTE_WIDTH 1 /* SPKOUTL_PGA_MUTE */
+#define WM8962_SPKOUTR_PGA_MUTE 0x0001 /* SPKOUTR_PGA_MUTE */
+#define WM8962_SPKOUTR_PGA_MUTE_MASK 0x0001 /* SPKOUTR_PGA_MUTE */
+#define WM8962_SPKOUTR_PGA_MUTE_SHIFT 0 /* SPKOUTR_PGA_MUTE */
+#define WM8962_SPKOUTR_PGA_MUTE_WIDTH 1 /* SPKOUTR_PGA_MUTE */
+
+/*
+ * R51 (0x33) - Class D Control 2
+ */
+#define WM8962_SPK_MONO 0x0040 /* SPK_MONO */
+#define WM8962_SPK_MONO_MASK 0x0040 /* SPK_MONO */
+#define WM8962_SPK_MONO_SHIFT 6 /* SPK_MONO */
+#define WM8962_SPK_MONO_WIDTH 1 /* SPK_MONO */
+#define WM8962_CLASSD_VOL_MASK 0x0007 /* CLASSD_VOL - [2:0] */
+#define WM8962_CLASSD_VOL_SHIFT 0 /* CLASSD_VOL - [2:0] */
+#define WM8962_CLASSD_VOL_WIDTH 3 /* CLASSD_VOL - [2:0] */
+
+/*
+ * R56 (0x38) - Clocking 4
+ */
+#define WM8962_SYSCLK_RATE_MASK 0x001E /* SYSCLK_RATE - [4:1] */
+#define WM8962_SYSCLK_RATE_SHIFT 1 /* SYSCLK_RATE - [4:1] */
+#define WM8962_SYSCLK_RATE_WIDTH 4 /* SYSCLK_RATE - [4:1] */
+
+/*
+ * R57 (0x39) - DAC DSP Mixing (1)
+ */
+#define WM8962_DAC_MONOMIX 0x0200 /* DAC_MONOMIX */
+#define WM8962_DAC_MONOMIX_MASK 0x0200 /* DAC_MONOMIX */
+#define WM8962_DAC_MONOMIX_SHIFT 9 /* DAC_MONOMIX */
+#define WM8962_DAC_MONOMIX_WIDTH 1 /* DAC_MONOMIX */
+#define WM8962_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8962_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8962_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8962_ADC_TO_DACR_MASK 0x000C /* ADC_TO_DACR - [3:2] */
+#define WM8962_ADC_TO_DACR_SHIFT 2 /* ADC_TO_DACR - [3:2] */
+#define WM8962_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [3:2] */
+
+/*
+ * R58 (0x3A) - DAC DSP Mixing (2)
+ */
+#define WM8962_ADCL_DAC_SVOL_MASK 0x00F0 /* ADCL_DAC_SVOL - [7:4] */
+#define WM8962_ADCL_DAC_SVOL_SHIFT 4 /* ADCL_DAC_SVOL - [7:4] */
+#define WM8962_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [7:4] */
+#define WM8962_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */
+#define WM8962_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */
+#define WM8962_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */
+
+/*
+ * R60 (0x3C) - DC Servo 0
+ */
+#define WM8962_INL_DCS_ENA 0x0080 /* INL_DCS_ENA */
+#define WM8962_INL_DCS_ENA_MASK 0x0080 /* INL_DCS_ENA */
+#define WM8962_INL_DCS_ENA_SHIFT 7 /* INL_DCS_ENA */
+#define WM8962_INL_DCS_ENA_WIDTH 1 /* INL_DCS_ENA */
+#define WM8962_INL_DCS_STARTUP 0x0040 /* INL_DCS_STARTUP */
+#define WM8962_INL_DCS_STARTUP_MASK 0x0040 /* INL_DCS_STARTUP */
+#define WM8962_INL_DCS_STARTUP_SHIFT 6 /* INL_DCS_STARTUP */
+#define WM8962_INL_DCS_STARTUP_WIDTH 1 /* INL_DCS_STARTUP */
+#define WM8962_INR_DCS_ENA 0x0008 /* INR_DCS_ENA */
+#define WM8962_INR_DCS_ENA_MASK 0x0008 /* INR_DCS_ENA */
+#define WM8962_INR_DCS_ENA_SHIFT 3 /* INR_DCS_ENA */
+#define WM8962_INR_DCS_ENA_WIDTH 1 /* INR_DCS_ENA */
+#define WM8962_INR_DCS_STARTUP 0x0004 /* INR_DCS_STARTUP */
+#define WM8962_INR_DCS_STARTUP_MASK 0x0004 /* INR_DCS_STARTUP */
+#define WM8962_INR_DCS_STARTUP_SHIFT 2 /* INR_DCS_STARTUP */
+#define WM8962_INR_DCS_STARTUP_WIDTH 1 /* INR_DCS_STARTUP */
+
+/*
+ * R61 (0x3D) - DC Servo 1
+ */
+#define WM8962_HP1L_DCS_ENA 0x0080 /* HP1L_DCS_ENA */
+#define WM8962_HP1L_DCS_ENA_MASK 0x0080 /* HP1L_DCS_ENA */
+#define WM8962_HP1L_DCS_ENA_SHIFT 7 /* HP1L_DCS_ENA */
+#define WM8962_HP1L_DCS_ENA_WIDTH 1 /* HP1L_DCS_ENA */
+#define WM8962_HP1L_DCS_STARTUP 0x0040 /* HP1L_DCS_STARTUP */
+#define WM8962_HP1L_DCS_STARTUP_MASK 0x0040 /* HP1L_DCS_STARTUP */
+#define WM8962_HP1L_DCS_STARTUP_SHIFT 6 /* HP1L_DCS_STARTUP */
+#define WM8962_HP1L_DCS_STARTUP_WIDTH 1 /* HP1L_DCS_STARTUP */
+#define WM8962_HP1L_DCS_SYNC 0x0010 /* HP1L_DCS_SYNC */
+#define WM8962_HP1L_DCS_SYNC_MASK 0x0010 /* HP1L_DCS_SYNC */
+#define WM8962_HP1L_DCS_SYNC_SHIFT 4 /* HP1L_DCS_SYNC */
+#define WM8962_HP1L_DCS_SYNC_WIDTH 1 /* HP1L_DCS_SYNC */
+#define WM8962_HP1R_DCS_ENA 0x0008 /* HP1R_DCS_ENA */
+#define WM8962_HP1R_DCS_ENA_MASK 0x0008 /* HP1R_DCS_ENA */
+#define WM8962_HP1R_DCS_ENA_SHIFT 3 /* HP1R_DCS_ENA */
+#define WM8962_HP1R_DCS_ENA_WIDTH 1 /* HP1R_DCS_ENA */
+#define WM8962_HP1R_DCS_STARTUP 0x0004 /* HP1R_DCS_STARTUP */
+#define WM8962_HP1R_DCS_STARTUP_MASK 0x0004 /* HP1R_DCS_STARTUP */
+#define WM8962_HP1R_DCS_STARTUP_SHIFT 2 /* HP1R_DCS_STARTUP */
+#define WM8962_HP1R_DCS_STARTUP_WIDTH 1 /* HP1R_DCS_STARTUP */
+#define WM8962_HP1R_DCS_SYNC 0x0001 /* HP1R_DCS_SYNC */
+#define WM8962_HP1R_DCS_SYNC_MASK 0x0001 /* HP1R_DCS_SYNC */
+#define WM8962_HP1R_DCS_SYNC_SHIFT 0 /* HP1R_DCS_SYNC */
+#define WM8962_HP1R_DCS_SYNC_WIDTH 1 /* HP1R_DCS_SYNC */
+
+/*
+ * R64 (0x40) - DC Servo 4
+ */
+#define WM8962_HP1_DCS_SYNC_STEPS_MASK 0x3F80 /* HP1_DCS_SYNC_STEPS - [13:7] */
+#define WM8962_HP1_DCS_SYNC_STEPS_SHIFT 7 /* HP1_DCS_SYNC_STEPS - [13:7] */
+#define WM8962_HP1_DCS_SYNC_STEPS_WIDTH 7 /* HP1_DCS_SYNC_STEPS - [13:7] */
+
+/*
+ * R66 (0x42) - DC Servo 6
+ */
+#define WM8962_DCS_STARTUP_DONE_INL 0x0400 /* DCS_STARTUP_DONE_INL */
+#define WM8962_DCS_STARTUP_DONE_INL_MASK 0x0400 /* DCS_STARTUP_DONE_INL */
+#define WM8962_DCS_STARTUP_DONE_INL_SHIFT 10 /* DCS_STARTUP_DONE_INL */
+#define WM8962_DCS_STARTUP_DONE_INL_WIDTH 1 /* DCS_STARTUP_DONE_INL */
+#define WM8962_DCS_STARTUP_DONE_INR 0x0200 /* DCS_STARTUP_DONE_INR */
+#define WM8962_DCS_STARTUP_DONE_INR_MASK 0x0200 /* DCS_STARTUP_DONE_INR */
+#define WM8962_DCS_STARTUP_DONE_INR_SHIFT 9 /* DCS_STARTUP_DONE_INR */
+#define WM8962_DCS_STARTUP_DONE_INR_WIDTH 1 /* DCS_STARTUP_DONE_INR */
+#define WM8962_DCS_STARTUP_DONE_HP1L 0x0100 /* DCS_STARTUP_DONE_HP1L */
+#define WM8962_DCS_STARTUP_DONE_HP1L_MASK 0x0100 /* DCS_STARTUP_DONE_HP1L */
+#define WM8962_DCS_STARTUP_DONE_HP1L_SHIFT 8 /* DCS_STARTUP_DONE_HP1L */
+#define WM8962_DCS_STARTUP_DONE_HP1L_WIDTH 1 /* DCS_STARTUP_DONE_HP1L */
+#define WM8962_DCS_STARTUP_DONE_HP1R 0x0080 /* DCS_STARTUP_DONE_HP1R */
+#define WM8962_DCS_STARTUP_DONE_HP1R_MASK 0x0080 /* DCS_STARTUP_DONE_HP1R */
+#define WM8962_DCS_STARTUP_DONE_HP1R_SHIFT 7 /* DCS_STARTUP_DONE_HP1R */
+#define WM8962_DCS_STARTUP_DONE_HP1R_WIDTH 1 /* DCS_STARTUP_DONE_HP1R */
+
+/*
+ * R68 (0x44) - Analogue PGA Bias
+ */
+#define WM8962_HP_PGAS_BIAS_MASK 0x0007 /* HP_PGAS_BIAS - [2:0] */
+#define WM8962_HP_PGAS_BIAS_SHIFT 0 /* HP_PGAS_BIAS - [2:0] */
+#define WM8962_HP_PGAS_BIAS_WIDTH 3 /* HP_PGAS_BIAS - [2:0] */
+
+/*
+ * R69 (0x45) - Analogue HP 0
+ */
+#define WM8962_HP1L_RMV_SHORT 0x0080 /* HP1L_RMV_SHORT */
+#define WM8962_HP1L_RMV_SHORT_MASK 0x0080 /* HP1L_RMV_SHORT */
+#define WM8962_HP1L_RMV_SHORT_SHIFT 7 /* HP1L_RMV_SHORT */
+#define WM8962_HP1L_RMV_SHORT_WIDTH 1 /* HP1L_RMV_SHORT */
+#define WM8962_HP1L_ENA_OUTP 0x0040 /* HP1L_ENA_OUTP */
+#define WM8962_HP1L_ENA_OUTP_MASK 0x0040 /* HP1L_ENA_OUTP */
+#define WM8962_HP1L_ENA_OUTP_SHIFT 6 /* HP1L_ENA_OUTP */
+#define WM8962_HP1L_ENA_OUTP_WIDTH 1 /* HP1L_ENA_OUTP */
+#define WM8962_HP1L_ENA_DLY 0x0020 /* HP1L_ENA_DLY */
+#define WM8962_HP1L_ENA_DLY_MASK 0x0020 /* HP1L_ENA_DLY */
+#define WM8962_HP1L_ENA_DLY_SHIFT 5 /* HP1L_ENA_DLY */
+#define WM8962_HP1L_ENA_DLY_WIDTH 1 /* HP1L_ENA_DLY */
+#define WM8962_HP1L_ENA 0x0010 /* HP1L_ENA */
+#define WM8962_HP1L_ENA_MASK 0x0010 /* HP1L_ENA */
+#define WM8962_HP1L_ENA_SHIFT 4 /* HP1L_ENA */
+#define WM8962_HP1L_ENA_WIDTH 1 /* HP1L_ENA */
+#define WM8962_HP1R_RMV_SHORT 0x0008 /* HP1R_RMV_SHORT */
+#define WM8962_HP1R_RMV_SHORT_MASK 0x0008 /* HP1R_RMV_SHORT */
+#define WM8962_HP1R_RMV_SHORT_SHIFT 3 /* HP1R_RMV_SHORT */
+#define WM8962_HP1R_RMV_SHORT_WIDTH 1 /* HP1R_RMV_SHORT */
+#define WM8962_HP1R_ENA_OUTP 0x0004 /* HP1R_ENA_OUTP */
+#define WM8962_HP1R_ENA_OUTP_MASK 0x0004 /* HP1R_ENA_OUTP */
+#define WM8962_HP1R_ENA_OUTP_SHIFT 2 /* HP1R_ENA_OUTP */
+#define WM8962_HP1R_ENA_OUTP_WIDTH 1 /* HP1R_ENA_OUTP */
+#define WM8962_HP1R_ENA_DLY 0x0002 /* HP1R_ENA_DLY */
+#define WM8962_HP1R_ENA_DLY_MASK 0x0002 /* HP1R_ENA_DLY */
+#define WM8962_HP1R_ENA_DLY_SHIFT 1 /* HP1R_ENA_DLY */
+#define WM8962_HP1R_ENA_DLY_WIDTH 1 /* HP1R_ENA_DLY */
+#define WM8962_HP1R_ENA 0x0001 /* HP1R_ENA */
+#define WM8962_HP1R_ENA_MASK 0x0001 /* HP1R_ENA */
+#define WM8962_HP1R_ENA_SHIFT 0 /* HP1R_ENA */
+#define WM8962_HP1R_ENA_WIDTH 1 /* HP1R_ENA */
+
+/*
+ * R71 (0x47) - Analogue HP 2
+ */
+#define WM8962_HP1L_VOL_MASK 0x01C0 /* HP1L_VOL - [8:6] */
+#define WM8962_HP1L_VOL_SHIFT 6 /* HP1L_VOL - [8:6] */
+#define WM8962_HP1L_VOL_WIDTH 3 /* HP1L_VOL - [8:6] */
+#define WM8962_HP1R_VOL_MASK 0x0038 /* HP1R_VOL - [5:3] */
+#define WM8962_HP1R_VOL_SHIFT 3 /* HP1R_VOL - [5:3] */
+#define WM8962_HP1R_VOL_WIDTH 3 /* HP1R_VOL - [5:3] */
+#define WM8962_HP_BIAS_BOOST_MASK 0x0007 /* HP_BIAS_BOOST - [2:0] */
+#define WM8962_HP_BIAS_BOOST_SHIFT 0 /* HP_BIAS_BOOST - [2:0] */
+#define WM8962_HP_BIAS_BOOST_WIDTH 3 /* HP_BIAS_BOOST - [2:0] */
+
+/*
+ * R72 (0x48) - Charge Pump 1
+ */
+#define WM8962_CP_ENA 0x0001 /* CP_ENA */
+#define WM8962_CP_ENA_MASK 0x0001 /* CP_ENA */
+#define WM8962_CP_ENA_SHIFT 0 /* CP_ENA */
+#define WM8962_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R82 (0x52) - Charge Pump B
+ */
+#define WM8962_CP_DYN_PWR 0x0001 /* CP_DYN_PWR */
+#define WM8962_CP_DYN_PWR_MASK 0x0001 /* CP_DYN_PWR */
+#define WM8962_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR */
+#define WM8962_CP_DYN_PWR_WIDTH 1 /* CP_DYN_PWR */
+
+/*
+ * R87 (0x57) - Write Sequencer Control 1
+ */
+#define WM8962_WSEQ_AUTOSEQ_ENA 0x0080 /* WSEQ_AUTOSEQ_ENA */
+#define WM8962_WSEQ_AUTOSEQ_ENA_MASK 0x0080 /* WSEQ_AUTOSEQ_ENA */
+#define WM8962_WSEQ_AUTOSEQ_ENA_SHIFT 7 /* WSEQ_AUTOSEQ_ENA */
+#define WM8962_WSEQ_AUTOSEQ_ENA_WIDTH 1 /* WSEQ_AUTOSEQ_ENA */
+#define WM8962_WSEQ_ENA 0x0020 /* WSEQ_ENA */
+#define WM8962_WSEQ_ENA_MASK 0x0020 /* WSEQ_ENA */
+#define WM8962_WSEQ_ENA_SHIFT 5 /* WSEQ_ENA */
+#define WM8962_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+
+/*
+ * R90 (0x5A) - Write Sequencer Control 2
+ */
+#define WM8962_WSEQ_ABORT 0x0100 /* WSEQ_ABORT */
+#define WM8962_WSEQ_ABORT_MASK 0x0100 /* WSEQ_ABORT */
+#define WM8962_WSEQ_ABORT_SHIFT 8 /* WSEQ_ABORT */
+#define WM8962_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM8962_WSEQ_START 0x0080 /* WSEQ_START */
+#define WM8962_WSEQ_START_MASK 0x0080 /* WSEQ_START */
+#define WM8962_WSEQ_START_SHIFT 7 /* WSEQ_START */
+#define WM8962_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM8962_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */
+#define WM8962_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */
+#define WM8962_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */
+
+/*
+ * R93 (0x5D) - Write Sequencer Control 3
+ */
+#define WM8962_WSEQ_CURRENT_INDEX_MASK 0x03F8 /* WSEQ_CURRENT_INDEX - [9:3] */
+#define WM8962_WSEQ_CURRENT_INDEX_SHIFT 3 /* WSEQ_CURRENT_INDEX - [9:3] */
+#define WM8962_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [9:3] */
+#define WM8962_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
+#define WM8962_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
+#define WM8962_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
+#define WM8962_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+
+/*
+ * R94 (0x5E) - Control Interface
+ */
+#define WM8962_SPI_CONTRD 0x0040 /* SPI_CONTRD */
+#define WM8962_SPI_CONTRD_MASK 0x0040 /* SPI_CONTRD */
+#define WM8962_SPI_CONTRD_SHIFT 6 /* SPI_CONTRD */
+#define WM8962_SPI_CONTRD_WIDTH 1 /* SPI_CONTRD */
+#define WM8962_SPI_4WIRE 0x0020 /* SPI_4WIRE */
+#define WM8962_SPI_4WIRE_MASK 0x0020 /* SPI_4WIRE */
+#define WM8962_SPI_4WIRE_SHIFT 5 /* SPI_4WIRE */
+#define WM8962_SPI_4WIRE_WIDTH 1 /* SPI_4WIRE */
+#define WM8962_SPI_CFG 0x0010 /* SPI_CFG */
+#define WM8962_SPI_CFG_MASK 0x0010 /* SPI_CFG */
+#define WM8962_SPI_CFG_SHIFT 4 /* SPI_CFG */
+#define WM8962_SPI_CFG_WIDTH 1 /* SPI_CFG */
+
+/*
+ * R99 (0x63) - Mixer Enables
+ */
+#define WM8962_HPMIXL_ENA 0x0008 /* HPMIXL_ENA */
+#define WM8962_HPMIXL_ENA_MASK 0x0008 /* HPMIXL_ENA */
+#define WM8962_HPMIXL_ENA_SHIFT 3 /* HPMIXL_ENA */
+#define WM8962_HPMIXL_ENA_WIDTH 1 /* HPMIXL_ENA */
+#define WM8962_HPMIXR_ENA 0x0004 /* HPMIXR_ENA */
+#define WM8962_HPMIXR_ENA_MASK 0x0004 /* HPMIXR_ENA */
+#define WM8962_HPMIXR_ENA_SHIFT 2 /* HPMIXR_ENA */
+#define WM8962_HPMIXR_ENA_WIDTH 1 /* HPMIXR_ENA */
+#define WM8962_SPKMIXL_ENA 0x0002 /* SPKMIXL_ENA */
+#define WM8962_SPKMIXL_ENA_MASK 0x0002 /* SPKMIXL_ENA */
+#define WM8962_SPKMIXL_ENA_SHIFT 1 /* SPKMIXL_ENA */
+#define WM8962_SPKMIXL_ENA_WIDTH 1 /* SPKMIXL_ENA */
+#define WM8962_SPKMIXR_ENA 0x0001 /* SPKMIXR_ENA */
+#define WM8962_SPKMIXR_ENA_MASK 0x0001 /* SPKMIXR_ENA */
+#define WM8962_SPKMIXR_ENA_SHIFT 0 /* SPKMIXR_ENA */
+#define WM8962_SPKMIXR_ENA_WIDTH 1 /* SPKMIXR_ENA */
+
+/*
+ * R100 (0x64) - Headphone Mixer (1)
+ */
+#define WM8962_HPMIXL_TO_HPOUTL_PGA 0x0080 /* HPMIXL_TO_HPOUTL_PGA */
+#define WM8962_HPMIXL_TO_HPOUTL_PGA_MASK 0x0080 /* HPMIXL_TO_HPOUTL_PGA */
+#define WM8962_HPMIXL_TO_HPOUTL_PGA_SHIFT 7 /* HPMIXL_TO_HPOUTL_PGA */
+#define WM8962_HPMIXL_TO_HPOUTL_PGA_WIDTH 1 /* HPMIXL_TO_HPOUTL_PGA */
+#define WM8962_DACL_TO_HPMIXL 0x0020 /* DACL_TO_HPMIXL */
+#define WM8962_DACL_TO_HPMIXL_MASK 0x0020 /* DACL_TO_HPMIXL */
+#define WM8962_DACL_TO_HPMIXL_SHIFT 5 /* DACL_TO_HPMIXL */
+#define WM8962_DACL_TO_HPMIXL_WIDTH 1 /* DACL_TO_HPMIXL */
+#define WM8962_DACR_TO_HPMIXL 0x0010 /* DACR_TO_HPMIXL */
+#define WM8962_DACR_TO_HPMIXL_MASK 0x0010 /* DACR_TO_HPMIXL */
+#define WM8962_DACR_TO_HPMIXL_SHIFT 4 /* DACR_TO_HPMIXL */
+#define WM8962_DACR_TO_HPMIXL_WIDTH 1 /* DACR_TO_HPMIXL */
+#define WM8962_MIXINL_TO_HPMIXL 0x0008 /* MIXINL_TO_HPMIXL */
+#define WM8962_MIXINL_TO_HPMIXL_MASK 0x0008 /* MIXINL_TO_HPMIXL */
+#define WM8962_MIXINL_TO_HPMIXL_SHIFT 3 /* MIXINL_TO_HPMIXL */
+#define WM8962_MIXINL_TO_HPMIXL_WIDTH 1 /* MIXINL_TO_HPMIXL */
+#define WM8962_MIXINR_TO_HPMIXL 0x0004 /* MIXINR_TO_HPMIXL */
+#define WM8962_MIXINR_TO_HPMIXL_MASK 0x0004 /* MIXINR_TO_HPMIXL */
+#define WM8962_MIXINR_TO_HPMIXL_SHIFT 2 /* MIXINR_TO_HPMIXL */
+#define WM8962_MIXINR_TO_HPMIXL_WIDTH 1 /* MIXINR_TO_HPMIXL */
+#define WM8962_IN4L_TO_HPMIXL 0x0002 /* IN4L_TO_HPMIXL */
+#define WM8962_IN4L_TO_HPMIXL_MASK 0x0002 /* IN4L_TO_HPMIXL */
+#define WM8962_IN4L_TO_HPMIXL_SHIFT 1 /* IN4L_TO_HPMIXL */
+#define WM8962_IN4L_TO_HPMIXL_WIDTH 1 /* IN4L_TO_HPMIXL */
+#define WM8962_IN4R_TO_HPMIXL 0x0001 /* IN4R_TO_HPMIXL */
+#define WM8962_IN4R_TO_HPMIXL_MASK 0x0001 /* IN4R_TO_HPMIXL */
+#define WM8962_IN4R_TO_HPMIXL_SHIFT 0 /* IN4R_TO_HPMIXL */
+#define WM8962_IN4R_TO_HPMIXL_WIDTH 1 /* IN4R_TO_HPMIXL */
+
+/*
+ * R101 (0x65) - Headphone Mixer (2)
+ */
+#define WM8962_HPMIXR_TO_HPOUTR_PGA 0x0080 /* HPMIXR_TO_HPOUTR_PGA */
+#define WM8962_HPMIXR_TO_HPOUTR_PGA_MASK 0x0080 /* HPMIXR_TO_HPOUTR_PGA */
+#define WM8962_HPMIXR_TO_HPOUTR_PGA_SHIFT 7 /* HPMIXR_TO_HPOUTR_PGA */
+#define WM8962_HPMIXR_TO_HPOUTR_PGA_WIDTH 1 /* HPMIXR_TO_HPOUTR_PGA */
+#define WM8962_DACL_TO_HPMIXR 0x0020 /* DACL_TO_HPMIXR */
+#define WM8962_DACL_TO_HPMIXR_MASK 0x0020 /* DACL_TO_HPMIXR */
+#define WM8962_DACL_TO_HPMIXR_SHIFT 5 /* DACL_TO_HPMIXR */
+#define WM8962_DACL_TO_HPMIXR_WIDTH 1 /* DACL_TO_HPMIXR */
+#define WM8962_DACR_TO_HPMIXR 0x0010 /* DACR_TO_HPMIXR */
+#define WM8962_DACR_TO_HPMIXR_MASK 0x0010 /* DACR_TO_HPMIXR */
+#define WM8962_DACR_TO_HPMIXR_SHIFT 4 /* DACR_TO_HPMIXR */
+#define WM8962_DACR_TO_HPMIXR_WIDTH 1 /* DACR_TO_HPMIXR */
+#define WM8962_MIXINL_TO_HPMIXR 0x0008 /* MIXINL_TO_HPMIXR */
+#define WM8962_MIXINL_TO_HPMIXR_MASK 0x0008 /* MIXINL_TO_HPMIXR */
+#define WM8962_MIXINL_TO_HPMIXR_SHIFT 3 /* MIXINL_TO_HPMIXR */
+#define WM8962_MIXINL_TO_HPMIXR_WIDTH 1 /* MIXINL_TO_HPMIXR */
+#define WM8962_MIXINR_TO_HPMIXR 0x0004 /* MIXINR_TO_HPMIXR */
+#define WM8962_MIXINR_TO_HPMIXR_MASK 0x0004 /* MIXINR_TO_HPMIXR */
+#define WM8962_MIXINR_TO_HPMIXR_SHIFT 2 /* MIXINR_TO_HPMIXR */
+#define WM8962_MIXINR_TO_HPMIXR_WIDTH 1 /* MIXINR_TO_HPMIXR */
+#define WM8962_IN4L_TO_HPMIXR 0x0002 /* IN4L_TO_HPMIXR */
+#define WM8962_IN4L_TO_HPMIXR_MASK 0x0002 /* IN4L_TO_HPMIXR */
+#define WM8962_IN4L_TO_HPMIXR_SHIFT 1 /* IN4L_TO_HPMIXR */
+#define WM8962_IN4L_TO_HPMIXR_WIDTH 1 /* IN4L_TO_HPMIXR */
+#define WM8962_IN4R_TO_HPMIXR 0x0001 /* IN4R_TO_HPMIXR */
+#define WM8962_IN4R_TO_HPMIXR_MASK 0x0001 /* IN4R_TO_HPMIXR */
+#define WM8962_IN4R_TO_HPMIXR_SHIFT 0 /* IN4R_TO_HPMIXR */
+#define WM8962_IN4R_TO_HPMIXR_WIDTH 1 /* IN4R_TO_HPMIXR */
+
+/*
+ * R102 (0x66) - Headphone Mixer (3)
+ */
+#define WM8962_HPMIXL_MUTE 0x0100 /* HPMIXL_MUTE */
+#define WM8962_HPMIXL_MUTE_MASK 0x0100 /* HPMIXL_MUTE */
+#define WM8962_HPMIXL_MUTE_SHIFT 8 /* HPMIXL_MUTE */
+#define WM8962_HPMIXL_MUTE_WIDTH 1 /* HPMIXL_MUTE */
+#define WM8962_MIXINL_HPMIXL_VOL 0x0080 /* MIXINL_HPMIXL_VOL */
+#define WM8962_MIXINL_HPMIXL_VOL_MASK 0x0080 /* MIXINL_HPMIXL_VOL */
+#define WM8962_MIXINL_HPMIXL_VOL_SHIFT 7 /* MIXINL_HPMIXL_VOL */
+#define WM8962_MIXINL_HPMIXL_VOL_WIDTH 1 /* MIXINL_HPMIXL_VOL */
+#define WM8962_MIXINR_HPMIXL_VOL 0x0040 /* MIXINR_HPMIXL_VOL */
+#define WM8962_MIXINR_HPMIXL_VOL_MASK 0x0040 /* MIXINR_HPMIXL_VOL */
+#define WM8962_MIXINR_HPMIXL_VOL_SHIFT 6 /* MIXINR_HPMIXL_VOL */
+#define WM8962_MIXINR_HPMIXL_VOL_WIDTH 1 /* MIXINR_HPMIXL_VOL */
+#define WM8962_IN4L_HPMIXL_VOL_MASK 0x0038 /* IN4L_HPMIXL_VOL - [5:3] */
+#define WM8962_IN4L_HPMIXL_VOL_SHIFT 3 /* IN4L_HPMIXL_VOL - [5:3] */
+#define WM8962_IN4L_HPMIXL_VOL_WIDTH 3 /* IN4L_HPMIXL_VOL - [5:3] */
+#define WM8962_IN4R_HPMIXL_VOL_MASK 0x0007 /* IN4R_HPMIXL_VOL - [2:0] */
+#define WM8962_IN4R_HPMIXL_VOL_SHIFT 0 /* IN4R_HPMIXL_VOL - [2:0] */
+#define WM8962_IN4R_HPMIXL_VOL_WIDTH 3 /* IN4R_HPMIXL_VOL - [2:0] */
+
+/*
+ * R103 (0x67) - Headphone Mixer (4)
+ */
+#define WM8962_HPMIXR_MUTE 0x0100 /* HPMIXR_MUTE */
+#define WM8962_HPMIXR_MUTE_MASK 0x0100 /* HPMIXR_MUTE */
+#define WM8962_HPMIXR_MUTE_SHIFT 8 /* HPMIXR_MUTE */
+#define WM8962_HPMIXR_MUTE_WIDTH 1 /* HPMIXR_MUTE */
+#define WM8962_MIXINL_HPMIXR_VOL 0x0080 /* MIXINL_HPMIXR_VOL */
+#define WM8962_MIXINL_HPMIXR_VOL_MASK 0x0080 /* MIXINL_HPMIXR_VOL */
+#define WM8962_MIXINL_HPMIXR_VOL_SHIFT 7 /* MIXINL_HPMIXR_VOL */
+#define WM8962_MIXINL_HPMIXR_VOL_WIDTH 1 /* MIXINL_HPMIXR_VOL */
+#define WM8962_MIXINR_HPMIXR_VOL 0x0040 /* MIXINR_HPMIXR_VOL */
+#define WM8962_MIXINR_HPMIXR_VOL_MASK 0x0040 /* MIXINR_HPMIXR_VOL */
+#define WM8962_MIXINR_HPMIXR_VOL_SHIFT 6 /* MIXINR_HPMIXR_VOL */
+#define WM8962_MIXINR_HPMIXR_VOL_WIDTH 1 /* MIXINR_HPMIXR_VOL */
+#define WM8962_IN4L_HPMIXR_VOL_MASK 0x0038 /* IN4L_HPMIXR_VOL - [5:3] */
+#define WM8962_IN4L_HPMIXR_VOL_SHIFT 3 /* IN4L_HPMIXR_VOL - [5:3] */
+#define WM8962_IN4L_HPMIXR_VOL_WIDTH 3 /* IN4L_HPMIXR_VOL - [5:3] */
+#define WM8962_IN4R_HPMIXR_VOL_MASK 0x0007 /* IN4R_HPMIXR_VOL - [2:0] */
+#define WM8962_IN4R_HPMIXR_VOL_SHIFT 0 /* IN4R_HPMIXR_VOL - [2:0] */
+#define WM8962_IN4R_HPMIXR_VOL_WIDTH 3 /* IN4R_HPMIXR_VOL - [2:0] */
+
+/*
+ * R105 (0x69) - Speaker Mixer (1)
+ */
+#define WM8962_SPKMIXL_TO_SPKOUTL_PGA 0x0080 /* SPKMIXL_TO_SPKOUTL_PGA */
+#define WM8962_SPKMIXL_TO_SPKOUTL_PGA_MASK 0x0080 /* SPKMIXL_TO_SPKOUTL_PGA */
+#define WM8962_SPKMIXL_TO_SPKOUTL_PGA_SHIFT 7 /* SPKMIXL_TO_SPKOUTL_PGA */
+#define WM8962_SPKMIXL_TO_SPKOUTL_PGA_WIDTH 1 /* SPKMIXL_TO_SPKOUTL_PGA */
+#define WM8962_DACL_TO_SPKMIXL 0x0020 /* DACL_TO_SPKMIXL */
+#define WM8962_DACL_TO_SPKMIXL_MASK 0x0020 /* DACL_TO_SPKMIXL */
+#define WM8962_DACL_TO_SPKMIXL_SHIFT 5 /* DACL_TO_SPKMIXL */
+#define WM8962_DACL_TO_SPKMIXL_WIDTH 1 /* DACL_TO_SPKMIXL */
+#define WM8962_DACR_TO_SPKMIXL 0x0010 /* DACR_TO_SPKMIXL */
+#define WM8962_DACR_TO_SPKMIXL_MASK 0x0010 /* DACR_TO_SPKMIXL */
+#define WM8962_DACR_TO_SPKMIXL_SHIFT 4 /* DACR_TO_SPKMIXL */
+#define WM8962_DACR_TO_SPKMIXL_WIDTH 1 /* DACR_TO_SPKMIXL */
+#define WM8962_MIXINL_TO_SPKMIXL 0x0008 /* MIXINL_TO_SPKMIXL */
+#define WM8962_MIXINL_TO_SPKMIXL_MASK 0x0008 /* MIXINL_TO_SPKMIXL */
+#define WM8962_MIXINL_TO_SPKMIXL_SHIFT 3 /* MIXINL_TO_SPKMIXL */
+#define WM8962_MIXINL_TO_SPKMIXL_WIDTH 1 /* MIXINL_TO_SPKMIXL */
+#define WM8962_MIXINR_TO_SPKMIXL 0x0004 /* MIXINR_TO_SPKMIXL */
+#define WM8962_MIXINR_TO_SPKMIXL_MASK 0x0004 /* MIXINR_TO_SPKMIXL */
+#define WM8962_MIXINR_TO_SPKMIXL_SHIFT 2 /* MIXINR_TO_SPKMIXL */
+#define WM8962_MIXINR_TO_SPKMIXL_WIDTH 1 /* MIXINR_TO_SPKMIXL */
+#define WM8962_IN4L_TO_SPKMIXL 0x0002 /* IN4L_TO_SPKMIXL */
+#define WM8962_IN4L_TO_SPKMIXL_MASK 0x0002 /* IN4L_TO_SPKMIXL */
+#define WM8962_IN4L_TO_SPKMIXL_SHIFT 1 /* IN4L_TO_SPKMIXL */
+#define WM8962_IN4L_TO_SPKMIXL_WIDTH 1 /* IN4L_TO_SPKMIXL */
+#define WM8962_IN4R_TO_SPKMIXL 0x0001 /* IN4R_TO_SPKMIXL */
+#define WM8962_IN4R_TO_SPKMIXL_MASK 0x0001 /* IN4R_TO_SPKMIXL */
+#define WM8962_IN4R_TO_SPKMIXL_SHIFT 0 /* IN4R_TO_SPKMIXL */
+#define WM8962_IN4R_TO_SPKMIXL_WIDTH 1 /* IN4R_TO_SPKMIXL */
+
+/*
+ * R106 (0x6A) - Speaker Mixer (2)
+ */
+#define WM8962_SPKMIXR_TO_SPKOUTR_PGA 0x0080 /* SPKMIXR_TO_SPKOUTR_PGA */
+#define WM8962_SPKMIXR_TO_SPKOUTR_PGA_MASK 0x0080 /* SPKMIXR_TO_SPKOUTR_PGA */
+#define WM8962_SPKMIXR_TO_SPKOUTR_PGA_SHIFT 7 /* SPKMIXR_TO_SPKOUTR_PGA */
+#define WM8962_SPKMIXR_TO_SPKOUTR_PGA_WIDTH 1 /* SPKMIXR_TO_SPKOUTR_PGA */
+#define WM8962_DACL_TO_SPKMIXR 0x0020 /* DACL_TO_SPKMIXR */
+#define WM8962_DACL_TO_SPKMIXR_MASK 0x0020 /* DACL_TO_SPKMIXR */
+#define WM8962_DACL_TO_SPKMIXR_SHIFT 5 /* DACL_TO_SPKMIXR */
+#define WM8962_DACL_TO_SPKMIXR_WIDTH 1 /* DACL_TO_SPKMIXR */
+#define WM8962_DACR_TO_SPKMIXR 0x0010 /* DACR_TO_SPKMIXR */
+#define WM8962_DACR_TO_SPKMIXR_MASK 0x0010 /* DACR_TO_SPKMIXR */
+#define WM8962_DACR_TO_SPKMIXR_SHIFT 4 /* DACR_TO_SPKMIXR */
+#define WM8962_DACR_TO_SPKMIXR_WIDTH 1 /* DACR_TO_SPKMIXR */
+#define WM8962_MIXINL_TO_SPKMIXR 0x0008 /* MIXINL_TO_SPKMIXR */
+#define WM8962_MIXINL_TO_SPKMIXR_MASK 0x0008 /* MIXINL_TO_SPKMIXR */
+#define WM8962_MIXINL_TO_SPKMIXR_SHIFT 3 /* MIXINL_TO_SPKMIXR */
+#define WM8962_MIXINL_TO_SPKMIXR_WIDTH 1 /* MIXINL_TO_SPKMIXR */
+#define WM8962_MIXINR_TO_SPKMIXR 0x0004 /* MIXINR_TO_SPKMIXR */
+#define WM8962_MIXINR_TO_SPKMIXR_MASK 0x0004 /* MIXINR_TO_SPKMIXR */
+#define WM8962_MIXINR_TO_SPKMIXR_SHIFT 2 /* MIXINR_TO_SPKMIXR */
+#define WM8962_MIXINR_TO_SPKMIXR_WIDTH 1 /* MIXINR_TO_SPKMIXR */
+#define WM8962_IN4L_TO_SPKMIXR 0x0002 /* IN4L_TO_SPKMIXR */
+#define WM8962_IN4L_TO_SPKMIXR_MASK 0x0002 /* IN4L_TO_SPKMIXR */
+#define WM8962_IN4L_TO_SPKMIXR_SHIFT 1 /* IN4L_TO_SPKMIXR */
+#define WM8962_IN4L_TO_SPKMIXR_WIDTH 1 /* IN4L_TO_SPKMIXR */
+#define WM8962_IN4R_TO_SPKMIXR 0x0001 /* IN4R_TO_SPKMIXR */
+#define WM8962_IN4R_TO_SPKMIXR_MASK 0x0001 /* IN4R_TO_SPKMIXR */
+#define WM8962_IN4R_TO_SPKMIXR_SHIFT 0 /* IN4R_TO_SPKMIXR */
+#define WM8962_IN4R_TO_SPKMIXR_WIDTH 1 /* IN4R_TO_SPKMIXR */
+
+/*
+ * R107 (0x6B) - Speaker Mixer (3)
+ */
+#define WM8962_SPKMIXL_MUTE 0x0100 /* SPKMIXL_MUTE */
+#define WM8962_SPKMIXL_MUTE_MASK 0x0100 /* SPKMIXL_MUTE */
+#define WM8962_SPKMIXL_MUTE_SHIFT 8 /* SPKMIXL_MUTE */
+#define WM8962_SPKMIXL_MUTE_WIDTH 1 /* SPKMIXL_MUTE */
+#define WM8962_MIXINL_SPKMIXL_VOL 0x0080 /* MIXINL_SPKMIXL_VOL */
+#define WM8962_MIXINL_SPKMIXL_VOL_MASK 0x0080 /* MIXINL_SPKMIXL_VOL */
+#define WM8962_MIXINL_SPKMIXL_VOL_SHIFT 7 /* MIXINL_SPKMIXL_VOL */
+#define WM8962_MIXINL_SPKMIXL_VOL_WIDTH 1 /* MIXINL_SPKMIXL_VOL */
+#define WM8962_MIXINR_SPKMIXL_VOL 0x0040 /* MIXINR_SPKMIXL_VOL */
+#define WM8962_MIXINR_SPKMIXL_VOL_MASK 0x0040 /* MIXINR_SPKMIXL_VOL */
+#define WM8962_MIXINR_SPKMIXL_VOL_SHIFT 6 /* MIXINR_SPKMIXL_VOL */
+#define WM8962_MIXINR_SPKMIXL_VOL_WIDTH 1 /* MIXINR_SPKMIXL_VOL */
+#define WM8962_IN4L_SPKMIXL_VOL_MASK 0x0038 /* IN4L_SPKMIXL_VOL - [5:3] */
+#define WM8962_IN4L_SPKMIXL_VOL_SHIFT 3 /* IN4L_SPKMIXL_VOL - [5:3] */
+#define WM8962_IN4L_SPKMIXL_VOL_WIDTH 3 /* IN4L_SPKMIXL_VOL - [5:3] */
+#define WM8962_IN4R_SPKMIXL_VOL_MASK 0x0007 /* IN4R_SPKMIXL_VOL - [2:0] */
+#define WM8962_IN4R_SPKMIXL_VOL_SHIFT 0 /* IN4R_SPKMIXL_VOL - [2:0] */
+#define WM8962_IN4R_SPKMIXL_VOL_WIDTH 3 /* IN4R_SPKMIXL_VOL - [2:0] */
+
+/*
+ * R108 (0x6C) - Speaker Mixer (4)
+ */
+#define WM8962_SPKMIXR_MUTE 0x0100 /* SPKMIXR_MUTE */
+#define WM8962_SPKMIXR_MUTE_MASK 0x0100 /* SPKMIXR_MUTE */
+#define WM8962_SPKMIXR_MUTE_SHIFT 8 /* SPKMIXR_MUTE */
+#define WM8962_SPKMIXR_MUTE_WIDTH 1 /* SPKMIXR_MUTE */
+#define WM8962_MIXINL_SPKMIXR_VOL 0x0080 /* MIXINL_SPKMIXR_VOL */
+#define WM8962_MIXINL_SPKMIXR_VOL_MASK 0x0080 /* MIXINL_SPKMIXR_VOL */
+#define WM8962_MIXINL_SPKMIXR_VOL_SHIFT 7 /* MIXINL_SPKMIXR_VOL */
+#define WM8962_MIXINL_SPKMIXR_VOL_WIDTH 1 /* MIXINL_SPKMIXR_VOL */
+#define WM8962_MIXINR_SPKMIXR_VOL 0x0040 /* MIXINR_SPKMIXR_VOL */
+#define WM8962_MIXINR_SPKMIXR_VOL_MASK 0x0040 /* MIXINR_SPKMIXR_VOL */
+#define WM8962_MIXINR_SPKMIXR_VOL_SHIFT 6 /* MIXINR_SPKMIXR_VOL */
+#define WM8962_MIXINR_SPKMIXR_VOL_WIDTH 1 /* MIXINR_SPKMIXR_VOL */
+#define WM8962_IN4L_SPKMIXR_VOL_MASK 0x0038 /* IN4L_SPKMIXR_VOL - [5:3] */
+#define WM8962_IN4L_SPKMIXR_VOL_SHIFT 3 /* IN4L_SPKMIXR_VOL - [5:3] */
+#define WM8962_IN4L_SPKMIXR_VOL_WIDTH 3 /* IN4L_SPKMIXR_VOL - [5:3] */
+#define WM8962_IN4R_SPKMIXR_VOL_MASK 0x0007 /* IN4R_SPKMIXR_VOL - [2:0] */
+#define WM8962_IN4R_SPKMIXR_VOL_SHIFT 0 /* IN4R_SPKMIXR_VOL - [2:0] */
+#define WM8962_IN4R_SPKMIXR_VOL_WIDTH 3 /* IN4R_SPKMIXR_VOL - [2:0] */
+
+/*
+ * R109 (0x6D) - Speaker Mixer (5)
+ */
+#define WM8962_DACL_SPKMIXL_VOL 0x0080 /* DACL_SPKMIXL_VOL */
+#define WM8962_DACL_SPKMIXL_VOL_MASK 0x0080 /* DACL_SPKMIXL_VOL */
+#define WM8962_DACL_SPKMIXL_VOL_SHIFT 7 /* DACL_SPKMIXL_VOL */
+#define WM8962_DACL_SPKMIXL_VOL_WIDTH 1 /* DACL_SPKMIXL_VOL */
+#define WM8962_DACR_SPKMIXL_VOL 0x0040 /* DACR_SPKMIXL_VOL */
+#define WM8962_DACR_SPKMIXL_VOL_MASK 0x0040 /* DACR_SPKMIXL_VOL */
+#define WM8962_DACR_SPKMIXL_VOL_SHIFT 6 /* DACR_SPKMIXL_VOL */
+#define WM8962_DACR_SPKMIXL_VOL_WIDTH 1 /* DACR_SPKMIXL_VOL */
+#define WM8962_DACL_SPKMIXR_VOL 0x0020 /* DACL_SPKMIXR_VOL */
+#define WM8962_DACL_SPKMIXR_VOL_MASK 0x0020 /* DACL_SPKMIXR_VOL */
+#define WM8962_DACL_SPKMIXR_VOL_SHIFT 5 /* DACL_SPKMIXR_VOL */
+#define WM8962_DACL_SPKMIXR_VOL_WIDTH 1 /* DACL_SPKMIXR_VOL */
+#define WM8962_DACR_SPKMIXR_VOL 0x0010 /* DACR_SPKMIXR_VOL */
+#define WM8962_DACR_SPKMIXR_VOL_MASK 0x0010 /* DACR_SPKMIXR_VOL */
+#define WM8962_DACR_SPKMIXR_VOL_SHIFT 4 /* DACR_SPKMIXR_VOL */
+#define WM8962_DACR_SPKMIXR_VOL_WIDTH 1 /* DACR_SPKMIXR_VOL */
+
+/*
+ * R110 (0x6E) - Beep Generator (1)
+ */
+#define WM8962_BEEP_GAIN_MASK 0x00F0 /* BEEP_GAIN - [7:4] */
+#define WM8962_BEEP_GAIN_SHIFT 4 /* BEEP_GAIN - [7:4] */
+#define WM8962_BEEP_GAIN_WIDTH 4 /* BEEP_GAIN - [7:4] */
+#define WM8962_BEEP_RATE_MASK 0x0006 /* BEEP_RATE - [2:1] */
+#define WM8962_BEEP_RATE_SHIFT 1 /* BEEP_RATE - [2:1] */
+#define WM8962_BEEP_RATE_WIDTH 2 /* BEEP_RATE - [2:1] */
+#define WM8962_BEEP_ENA 0x0001 /* BEEP_ENA */
+#define WM8962_BEEP_ENA_MASK 0x0001 /* BEEP_ENA */
+#define WM8962_BEEP_ENA_SHIFT 0 /* BEEP_ENA */
+#define WM8962_BEEP_ENA_WIDTH 1 /* BEEP_ENA */
+
+/*
+ * R115 (0x73) - Oscillator Trim (3)
+ */
+#define WM8962_OSC_TRIM_XTI_MASK 0x001F /* OSC_TRIM_XTI - [4:0] */
+#define WM8962_OSC_TRIM_XTI_SHIFT 0 /* OSC_TRIM_XTI - [4:0] */
+#define WM8962_OSC_TRIM_XTI_WIDTH 5 /* OSC_TRIM_XTI - [4:0] */
+
+/*
+ * R116 (0x74) - Oscillator Trim (4)
+ */
+#define WM8962_OSC_TRIM_XTO_MASK 0x001F /* OSC_TRIM_XTO - [4:0] */
+#define WM8962_OSC_TRIM_XTO_SHIFT 0 /* OSC_TRIM_XTO - [4:0] */
+#define WM8962_OSC_TRIM_XTO_WIDTH 5 /* OSC_TRIM_XTO - [4:0] */
+
+/*
+ * R119 (0x77) - Oscillator Trim (7)
+ */
+#define WM8962_XTO_CAP_SEL_MASK 0x00F0 /* XTO_CAP_SEL - [7:4] */
+#define WM8962_XTO_CAP_SEL_SHIFT 4 /* XTO_CAP_SEL - [7:4] */
+#define WM8962_XTO_CAP_SEL_WIDTH 4 /* XTO_CAP_SEL - [7:4] */
+#define WM8962_XTI_CAP_SEL_MASK 0x000F /* XTI_CAP_SEL - [3:0] */
+#define WM8962_XTI_CAP_SEL_SHIFT 0 /* XTI_CAP_SEL - [3:0] */
+#define WM8962_XTI_CAP_SEL_WIDTH 4 /* XTI_CAP_SEL - [3:0] */
+
+/*
+ * R124 (0x7C) - Analogue Clocking1
+ */
+#define WM8962_CLKOUT2_SEL_MASK 0x0060 /* CLKOUT2_SEL - [6:5] */
+#define WM8962_CLKOUT2_SEL_SHIFT 5 /* CLKOUT2_SEL - [6:5] */
+#define WM8962_CLKOUT2_SEL_WIDTH 2 /* CLKOUT2_SEL - [6:5] */
+#define WM8962_CLKOUT3_SEL_MASK 0x0018 /* CLKOUT3_SEL - [4:3] */
+#define WM8962_CLKOUT3_SEL_SHIFT 3 /* CLKOUT3_SEL - [4:3] */
+#define WM8962_CLKOUT3_SEL_WIDTH 2 /* CLKOUT3_SEL - [4:3] */
+#define WM8962_CLKOUT5_SEL 0x0001 /* CLKOUT5_SEL */
+#define WM8962_CLKOUT5_SEL_MASK 0x0001 /* CLKOUT5_SEL */
+#define WM8962_CLKOUT5_SEL_SHIFT 0 /* CLKOUT5_SEL */
+#define WM8962_CLKOUT5_SEL_WIDTH 1 /* CLKOUT5_SEL */
+
+/*
+ * R125 (0x7D) - Analogue Clocking2
+ */
+#define WM8962_PLL2_OUTDIV 0x0080 /* PLL2_OUTDIV */
+#define WM8962_PLL2_OUTDIV_MASK 0x0080 /* PLL2_OUTDIV */
+#define WM8962_PLL2_OUTDIV_SHIFT 7 /* PLL2_OUTDIV */
+#define WM8962_PLL2_OUTDIV_WIDTH 1 /* PLL2_OUTDIV */
+#define WM8962_PLL3_OUTDIV 0x0040 /* PLL3_OUTDIV */
+#define WM8962_PLL3_OUTDIV_MASK 0x0040 /* PLL3_OUTDIV */
+#define WM8962_PLL3_OUTDIV_SHIFT 6 /* PLL3_OUTDIV */
+#define WM8962_PLL3_OUTDIV_WIDTH 1 /* PLL3_OUTDIV */
+#define WM8962_PLL_SYSCLK_DIV_MASK 0x0018 /* PLL_SYSCLK_DIV - [4:3] */
+#define WM8962_PLL_SYSCLK_DIV_SHIFT 3 /* PLL_SYSCLK_DIV - [4:3] */
+#define WM8962_PLL_SYSCLK_DIV_WIDTH 2 /* PLL_SYSCLK_DIV - [4:3] */
+#define WM8962_CLKOUT3_DIV 0x0004 /* CLKOUT3_DIV */
+#define WM8962_CLKOUT3_DIV_MASK 0x0004 /* CLKOUT3_DIV */
+#define WM8962_CLKOUT3_DIV_SHIFT 2 /* CLKOUT3_DIV */
+#define WM8962_CLKOUT3_DIV_WIDTH 1 /* CLKOUT3_DIV */
+#define WM8962_CLKOUT2_DIV 0x0002 /* CLKOUT2_DIV */
+#define WM8962_CLKOUT2_DIV_MASK 0x0002 /* CLKOUT2_DIV */
+#define WM8962_CLKOUT2_DIV_SHIFT 1 /* CLKOUT2_DIV */
+#define WM8962_CLKOUT2_DIV_WIDTH 1 /* CLKOUT2_DIV */
+#define WM8962_CLKOUT5_DIV 0x0001 /* CLKOUT5_DIV */
+#define WM8962_CLKOUT5_DIV_MASK 0x0001 /* CLKOUT5_DIV */
+#define WM8962_CLKOUT5_DIV_SHIFT 0 /* CLKOUT5_DIV */
+#define WM8962_CLKOUT5_DIV_WIDTH 1 /* CLKOUT5_DIV */
+
+/*
+ * R126 (0x7E) - Analogue Clocking3
+ */
+#define WM8962_CLKOUT2_OE 0x0008 /* CLKOUT2_OE */
+#define WM8962_CLKOUT2_OE_MASK 0x0008 /* CLKOUT2_OE */
+#define WM8962_CLKOUT2_OE_SHIFT 3 /* CLKOUT2_OE */
+#define WM8962_CLKOUT2_OE_WIDTH 1 /* CLKOUT2_OE */
+#define WM8962_CLKOUT3_OE 0x0004 /* CLKOUT3_OE */
+#define WM8962_CLKOUT3_OE_MASK 0x0004 /* CLKOUT3_OE */
+#define WM8962_CLKOUT3_OE_SHIFT 2 /* CLKOUT3_OE */
+#define WM8962_CLKOUT3_OE_WIDTH 1 /* CLKOUT3_OE */
+#define WM8962_CLKOUT5_OE 0x0001 /* CLKOUT5_OE */
+#define WM8962_CLKOUT5_OE_MASK 0x0001 /* CLKOUT5_OE */
+#define WM8962_CLKOUT5_OE_SHIFT 0 /* CLKOUT5_OE */
+#define WM8962_CLKOUT5_OE_WIDTH 1 /* CLKOUT5_OE */
+
+/*
+ * R127 (0x7F) - PLL Software Reset
+ */
+#define WM8962_SW_RESET_PLL_MASK 0xFFFF /* SW_RESET_PLL - [15:0] */
+#define WM8962_SW_RESET_PLL_SHIFT 0 /* SW_RESET_PLL - [15:0] */
+#define WM8962_SW_RESET_PLL_WIDTH 16 /* SW_RESET_PLL - [15:0] */
+
+/*
+ * R129 (0x81) - PLL2
+ */
+#define WM8962_OSC_ENA 0x0080 /* OSC_ENA */
+#define WM8962_OSC_ENA_MASK 0x0080 /* OSC_ENA */
+#define WM8962_OSC_ENA_SHIFT 7 /* OSC_ENA */
+#define WM8962_OSC_ENA_WIDTH 1 /* OSC_ENA */
+#define WM8962_PLL2_ENA 0x0020 /* PLL2_ENA */
+#define WM8962_PLL2_ENA_MASK 0x0020 /* PLL2_ENA */
+#define WM8962_PLL2_ENA_SHIFT 5 /* PLL2_ENA */
+#define WM8962_PLL2_ENA_WIDTH 1 /* PLL2_ENA */
+#define WM8962_PLL3_ENA 0x0010 /* PLL3_ENA */
+#define WM8962_PLL3_ENA_MASK 0x0010 /* PLL3_ENA */
+#define WM8962_PLL3_ENA_SHIFT 4 /* PLL3_ENA */
+#define WM8962_PLL3_ENA_WIDTH 1 /* PLL3_ENA */
+
+/*
+ * R131 (0x83) - PLL 4
+ */
+#define WM8962_PLL_CLK_SRC 0x0002 /* PLL_CLK_SRC */
+#define WM8962_PLL_CLK_SRC_MASK 0x0002 /* PLL_CLK_SRC */
+#define WM8962_PLL_CLK_SRC_SHIFT 1 /* PLL_CLK_SRC */
+#define WM8962_PLL_CLK_SRC_WIDTH 1 /* PLL_CLK_SRC */
+#define WM8962_FLL_TO_PLL3 0x0001 /* FLL_TO_PLL3 */
+#define WM8962_FLL_TO_PLL3_MASK 0x0001 /* FLL_TO_PLL3 */
+#define WM8962_FLL_TO_PLL3_SHIFT 0 /* FLL_TO_PLL3 */
+#define WM8962_FLL_TO_PLL3_WIDTH 1 /* FLL_TO_PLL3 */
+
+/*
+ * R136 (0x88) - PLL 9
+ */
+#define WM8962_PLL2_FRAC 0x0040 /* PLL2_FRAC */
+#define WM8962_PLL2_FRAC_MASK 0x0040 /* PLL2_FRAC */
+#define WM8962_PLL2_FRAC_SHIFT 6 /* PLL2_FRAC */
+#define WM8962_PLL2_FRAC_WIDTH 1 /* PLL2_FRAC */
+#define WM8962_PLL2_N_MASK 0x001F /* PLL2_N - [4:0] */
+#define WM8962_PLL2_N_SHIFT 0 /* PLL2_N - [4:0] */
+#define WM8962_PLL2_N_WIDTH 5 /* PLL2_N - [4:0] */
+
+/*
+ * R137 (0x89) - PLL 10
+ */
+#define WM8962_PLL2_K_MASK 0x00FF /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_SHIFT 0 /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_WIDTH 8 /* PLL2_K - [7:0] */
+
+/*
+ * R138 (0x8A) - PLL 11
+ */
+#define WM8962_PLL2_K_MASK 0x00FF /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_SHIFT 0 /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_WIDTH 8 /* PLL2_K - [7:0] */
+
+/*
+ * R139 (0x8B) - PLL 12
+ */
+#define WM8962_PLL2_K_MASK 0x00FF /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_SHIFT 0 /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_WIDTH 8 /* PLL2_K - [7:0] */
+
+/*
+ * R140 (0x8C) - PLL 13
+ */
+#define WM8962_PLL3_FRAC 0x0040 /* PLL3_FRAC */
+#define WM8962_PLL3_FRAC_MASK 0x0040 /* PLL3_FRAC */
+#define WM8962_PLL3_FRAC_SHIFT 6 /* PLL3_FRAC */
+#define WM8962_PLL3_FRAC_WIDTH 1 /* PLL3_FRAC */
+#define WM8962_PLL3_N_MASK 0x001F /* PLL3_N - [4:0] */
+#define WM8962_PLL3_N_SHIFT 0 /* PLL3_N - [4:0] */
+#define WM8962_PLL3_N_WIDTH 5 /* PLL3_N - [4:0] */
+
+/*
+ * R141 (0x8D) - PLL 14
+ */
+#define WM8962_PLL3_K_MASK 0x00FF /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_SHIFT 0 /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_WIDTH 8 /* PLL3_K - [7:0] */
+
+/*
+ * R142 (0x8E) - PLL 15
+ */
+#define WM8962_PLL3_K_MASK 0x00FF /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_SHIFT 0 /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_WIDTH 8 /* PLL3_K - [7:0] */
+
+/*
+ * R143 (0x8F) - PLL 16
+ */
+#define WM8962_PLL3_K_MASK 0x00FF /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_SHIFT 0 /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_WIDTH 8 /* PLL3_K - [7:0] */
+
+/*
+ * R155 (0x9B) - FLL Control (1)
+ */
+#define WM8962_FLL_REFCLK_SRC_MASK 0x0060 /* FLL_REFCLK_SRC - [6:5] */
+#define WM8962_FLL_REFCLK_SRC_SHIFT 5 /* FLL_REFCLK_SRC - [6:5] */
+#define WM8962_FLL_REFCLK_SRC_WIDTH 2 /* FLL_REFCLK_SRC - [6:5] */
+#define WM8962_FLL_FRAC 0x0004 /* FLL_FRAC */
+#define WM8962_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */
+#define WM8962_FLL_FRAC_SHIFT 2 /* FLL_FRAC */
+#define WM8962_FLL_FRAC_WIDTH 1 /* FLL_FRAC */
+#define WM8962_FLL_OSC_ENA 0x0002 /* FLL_OSC_ENA */
+#define WM8962_FLL_OSC_ENA_MASK 0x0002 /* FLL_OSC_ENA */
+#define WM8962_FLL_OSC_ENA_SHIFT 1 /* FLL_OSC_ENA */
+#define WM8962_FLL_OSC_ENA_WIDTH 1 /* FLL_OSC_ENA */
+#define WM8962_FLL_ENA 0x0001 /* FLL_ENA */
+#define WM8962_FLL_ENA_MASK 0x0001 /* FLL_ENA */
+#define WM8962_FLL_ENA_SHIFT 0 /* FLL_ENA */
+#define WM8962_FLL_ENA_WIDTH 1 /* FLL_ENA */
+
+/*
+ * R156 (0x9C) - FLL Control (2)
+ */
+#define WM8962_FLL_OUTDIV_MASK 0x01F8 /* FLL_OUTDIV - [8:3] */
+#define WM8962_FLL_OUTDIV_SHIFT 3 /* FLL_OUTDIV - [8:3] */
+#define WM8962_FLL_OUTDIV_WIDTH 6 /* FLL_OUTDIV - [8:3] */
+#define WM8962_FLL_REFCLK_DIV_MASK 0x0003 /* FLL_REFCLK_DIV - [1:0] */
+#define WM8962_FLL_REFCLK_DIV_SHIFT 0 /* FLL_REFCLK_DIV - [1:0] */
+#define WM8962_FLL_REFCLK_DIV_WIDTH 2 /* FLL_REFCLK_DIV - [1:0] */
+
+/*
+ * R157 (0x9D) - FLL Control (3)
+ */
+#define WM8962_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */
+#define WM8962_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */
+#define WM8962_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */
+
+/*
+ * R159 (0x9F) - FLL Control (5)
+ */
+#define WM8962_FLL_FRC_NCO_VAL_MASK 0x007E /* FLL_FRC_NCO_VAL - [6:1] */
+#define WM8962_FLL_FRC_NCO_VAL_SHIFT 1 /* FLL_FRC_NCO_VAL - [6:1] */
+#define WM8962_FLL_FRC_NCO_VAL_WIDTH 6 /* FLL_FRC_NCO_VAL - [6:1] */
+#define WM8962_FLL_FRC_NCO 0x0001 /* FLL_FRC_NCO */
+#define WM8962_FLL_FRC_NCO_MASK 0x0001 /* FLL_FRC_NCO */
+#define WM8962_FLL_FRC_NCO_SHIFT 0 /* FLL_FRC_NCO */
+#define WM8962_FLL_FRC_NCO_WIDTH 1 /* FLL_FRC_NCO */
+
+/*
+ * R160 (0xA0) - FLL Control (6)
+ */
+#define WM8962_FLL_THETA_MASK 0xFFFF /* FLL_THETA - [15:0] */
+#define WM8962_FLL_THETA_SHIFT 0 /* FLL_THETA - [15:0] */
+#define WM8962_FLL_THETA_WIDTH 16 /* FLL_THETA - [15:0] */
+
+/*
+ * R161 (0xA1) - FLL Control (7)
+ */
+#define WM8962_FLL_LAMBDA_MASK 0xFFFF /* FLL_LAMBDA - [15:0] */
+#define WM8962_FLL_LAMBDA_SHIFT 0 /* FLL_LAMBDA - [15:0] */
+#define WM8962_FLL_LAMBDA_WIDTH 16 /* FLL_LAMBDA - [15:0] */
+
+/*
+ * R162 (0xA2) - FLL Control (8)
+ */
+#define WM8962_FLL_N_MASK 0x03FF /* FLL_N - [9:0] */
+#define WM8962_FLL_N_SHIFT 0 /* FLL_N - [9:0] */
+#define WM8962_FLL_N_WIDTH 10 /* FLL_N - [9:0] */
+
+/*
+ * R252 (0xFC) - General test 1
+ */
+#define WM8962_REG_SYNC 0x0004 /* REG_SYNC */
+#define WM8962_REG_SYNC_MASK 0x0004 /* REG_SYNC */
+#define WM8962_REG_SYNC_SHIFT 2 /* REG_SYNC */
+#define WM8962_REG_SYNC_WIDTH 1 /* REG_SYNC */
+#define WM8962_AUTO_INC 0x0001 /* AUTO_INC */
+#define WM8962_AUTO_INC_MASK 0x0001 /* AUTO_INC */
+#define WM8962_AUTO_INC_SHIFT 0 /* AUTO_INC */
+#define WM8962_AUTO_INC_WIDTH 1 /* AUTO_INC */
+
+/*
+ * R256 (0x100) - DF1
+ */
+#define WM8962_DRC_DF1_ENA 0x0008 /* DRC_DF1_ENA */
+#define WM8962_DRC_DF1_ENA_MASK 0x0008 /* DRC_DF1_ENA */
+#define WM8962_DRC_DF1_ENA_SHIFT 3 /* DRC_DF1_ENA */
+#define WM8962_DRC_DF1_ENA_WIDTH 1 /* DRC_DF1_ENA */
+#define WM8962_DF1_SHARED_COEFF 0x0004 /* DF1_SHARED_COEFF */
+#define WM8962_DF1_SHARED_COEFF_MASK 0x0004 /* DF1_SHARED_COEFF */
+#define WM8962_DF1_SHARED_COEFF_SHIFT 2 /* DF1_SHARED_COEFF */
+#define WM8962_DF1_SHARED_COEFF_WIDTH 1 /* DF1_SHARED_COEFF */
+#define WM8962_DF1_SHARED_COEFF_SEL 0x0002 /* DF1_SHARED_COEFF_SEL */
+#define WM8962_DF1_SHARED_COEFF_SEL_MASK 0x0002 /* DF1_SHARED_COEFF_SEL */
+#define WM8962_DF1_SHARED_COEFF_SEL_SHIFT 1 /* DF1_SHARED_COEFF_SEL */
+#define WM8962_DF1_SHARED_COEFF_SEL_WIDTH 1 /* DF1_SHARED_COEFF_SEL */
+#define WM8962_DF1_ENA 0x0001 /* DF1_ENA */
+#define WM8962_DF1_ENA_MASK 0x0001 /* DF1_ENA */
+#define WM8962_DF1_ENA_SHIFT 0 /* DF1_ENA */
+#define WM8962_DF1_ENA_WIDTH 1 /* DF1_ENA */
+
+/*
+ * R257 (0x101) - DF2
+ */
+#define WM8962_DF1_COEFF_L0_MASK 0xFFFF /* DF1_COEFF_L0 - [15:0] */
+#define WM8962_DF1_COEFF_L0_SHIFT 0 /* DF1_COEFF_L0 - [15:0] */
+#define WM8962_DF1_COEFF_L0_WIDTH 16 /* DF1_COEFF_L0 - [15:0] */
+
+/*
+ * R258 (0x102) - DF3
+ */
+#define WM8962_DF1_COEFF_L1_MASK 0xFFFF /* DF1_COEFF_L1 - [15:0] */
+#define WM8962_DF1_COEFF_L1_SHIFT 0 /* DF1_COEFF_L1 - [15:0] */
+#define WM8962_DF1_COEFF_L1_WIDTH 16 /* DF1_COEFF_L1 - [15:0] */
+
+/*
+ * R259 (0x103) - DF4
+ */
+#define WM8962_DF1_COEFF_L2_MASK 0xFFFF /* DF1_COEFF_L2 - [15:0] */
+#define WM8962_DF1_COEFF_L2_SHIFT 0 /* DF1_COEFF_L2 - [15:0] */
+#define WM8962_DF1_COEFF_L2_WIDTH 16 /* DF1_COEFF_L2 - [15:0] */
+
+/*
+ * R260 (0x104) - DF5
+ */
+#define WM8962_DF1_COEFF_R0_MASK 0xFFFF /* DF1_COEFF_R0 - [15:0] */
+#define WM8962_DF1_COEFF_R0_SHIFT 0 /* DF1_COEFF_R0 - [15:0] */
+#define WM8962_DF1_COEFF_R0_WIDTH 16 /* DF1_COEFF_R0 - [15:0] */
+
+/*
+ * R261 (0x105) - DF6
+ */
+#define WM8962_DF1_COEFF_R1_MASK 0xFFFF /* DF1_COEFF_R1 - [15:0] */
+#define WM8962_DF1_COEFF_R1_SHIFT 0 /* DF1_COEFF_R1 - [15:0] */
+#define WM8962_DF1_COEFF_R1_WIDTH 16 /* DF1_COEFF_R1 - [15:0] */
+
+/*
+ * R262 (0x106) - DF7
+ */
+#define WM8962_DF1_COEFF_R2_MASK 0xFFFF /* DF1_COEFF_R2 - [15:0] */
+#define WM8962_DF1_COEFF_R2_SHIFT 0 /* DF1_COEFF_R2 - [15:0] */
+#define WM8962_DF1_COEFF_R2_WIDTH 16 /* DF1_COEFF_R2 - [15:0] */
+
+/*
+ * R264 (0x108) - LHPF1
+ */
+#define WM8962_LHPF_MODE 0x0002 /* LHPF_MODE */
+#define WM8962_LHPF_MODE_MASK 0x0002 /* LHPF_MODE */
+#define WM8962_LHPF_MODE_SHIFT 1 /* LHPF_MODE */
+#define WM8962_LHPF_MODE_WIDTH 1 /* LHPF_MODE */
+#define WM8962_LHPF_ENA 0x0001 /* LHPF_ENA */
+#define WM8962_LHPF_ENA_MASK 0x0001 /* LHPF_ENA */
+#define WM8962_LHPF_ENA_SHIFT 0 /* LHPF_ENA */
+#define WM8962_LHPF_ENA_WIDTH 1 /* LHPF_ENA */
+
+/*
+ * R265 (0x109) - LHPF2
+ */
+#define WM8962_LHPF_COEFF_MASK 0xFFFF /* LHPF_COEFF - [15:0] */
+#define WM8962_LHPF_COEFF_SHIFT 0 /* LHPF_COEFF - [15:0] */
+#define WM8962_LHPF_COEFF_WIDTH 16 /* LHPF_COEFF - [15:0] */
+
+/*
+ * R268 (0x10C) - THREED1
+ */
+#define WM8962_ADC_MONOMIX 0x0040 /* ADC_MONOMIX */
+#define WM8962_ADC_MONOMIX_MASK 0x0040 /* ADC_MONOMIX */
+#define WM8962_ADC_MONOMIX_SHIFT 6 /* ADC_MONOMIX */
+#define WM8962_ADC_MONOMIX_WIDTH 1 /* ADC_MONOMIX */
+#define WM8962_THREED_SIGN_L 0x0020 /* THREED_SIGN_L */
+#define WM8962_THREED_SIGN_L_MASK 0x0020 /* THREED_SIGN_L */
+#define WM8962_THREED_SIGN_L_SHIFT 5 /* THREED_SIGN_L */
+#define WM8962_THREED_SIGN_L_WIDTH 1 /* THREED_SIGN_L */
+#define WM8962_THREED_SIGN_R 0x0010 /* THREED_SIGN_R */
+#define WM8962_THREED_SIGN_R_MASK 0x0010 /* THREED_SIGN_R */
+#define WM8962_THREED_SIGN_R_SHIFT 4 /* THREED_SIGN_R */
+#define WM8962_THREED_SIGN_R_WIDTH 1 /* THREED_SIGN_R */
+#define WM8962_THREED_LHPF_MODE 0x0004 /* THREED_LHPF_MODE */
+#define WM8962_THREED_LHPF_MODE_MASK 0x0004 /* THREED_LHPF_MODE */
+#define WM8962_THREED_LHPF_MODE_SHIFT 2 /* THREED_LHPF_MODE */
+#define WM8962_THREED_LHPF_MODE_WIDTH 1 /* THREED_LHPF_MODE */
+#define WM8962_THREED_LHPF_ENA 0x0002 /* THREED_LHPF_ENA */
+#define WM8962_THREED_LHPF_ENA_MASK 0x0002 /* THREED_LHPF_ENA */
+#define WM8962_THREED_LHPF_ENA_SHIFT 1 /* THREED_LHPF_ENA */
+#define WM8962_THREED_LHPF_ENA_WIDTH 1 /* THREED_LHPF_ENA */
+#define WM8962_THREED_ENA 0x0001 /* THREED_ENA */
+#define WM8962_THREED_ENA_MASK 0x0001 /* THREED_ENA */
+#define WM8962_THREED_ENA_SHIFT 0 /* THREED_ENA */
+#define WM8962_THREED_ENA_WIDTH 1 /* THREED_ENA */
+
+/*
+ * R269 (0x10D) - THREED2
+ */
+#define WM8962_THREED_FGAINL_MASK 0xF800 /* THREED_FGAINL - [15:11] */
+#define WM8962_THREED_FGAINL_SHIFT 11 /* THREED_FGAINL - [15:11] */
+#define WM8962_THREED_FGAINL_WIDTH 5 /* THREED_FGAINL - [15:11] */
+#define WM8962_THREED_CGAINL_MASK 0x07C0 /* THREED_CGAINL - [10:6] */
+#define WM8962_THREED_CGAINL_SHIFT 6 /* THREED_CGAINL - [10:6] */
+#define WM8962_THREED_CGAINL_WIDTH 5 /* THREED_CGAINL - [10:6] */
+#define WM8962_THREED_DELAYL_MASK 0x003C /* THREED_DELAYL - [5:2] */
+#define WM8962_THREED_DELAYL_SHIFT 2 /* THREED_DELAYL - [5:2] */
+#define WM8962_THREED_DELAYL_WIDTH 4 /* THREED_DELAYL - [5:2] */
+
+/*
+ * R270 (0x10E) - THREED3
+ */
+#define WM8962_THREED_LHPF_COEFF_MASK 0xFFFF /* THREED_LHPF_COEFF - [15:0] */
+#define WM8962_THREED_LHPF_COEFF_SHIFT 0 /* THREED_LHPF_COEFF - [15:0] */
+#define WM8962_THREED_LHPF_COEFF_WIDTH 16 /* THREED_LHPF_COEFF - [15:0] */
+
+/*
+ * R271 (0x10F) - THREED4
+ */
+#define WM8962_THREED_FGAINR_MASK 0xF800 /* THREED_FGAINR - [15:11] */
+#define WM8962_THREED_FGAINR_SHIFT 11 /* THREED_FGAINR - [15:11] */
+#define WM8962_THREED_FGAINR_WIDTH 5 /* THREED_FGAINR - [15:11] */
+#define WM8962_THREED_CGAINR_MASK 0x07C0 /* THREED_CGAINR - [10:6] */
+#define WM8962_THREED_CGAINR_SHIFT 6 /* THREED_CGAINR - [10:6] */
+#define WM8962_THREED_CGAINR_WIDTH 5 /* THREED_CGAINR - [10:6] */
+#define WM8962_THREED_DELAYR_MASK 0x003C /* THREED_DELAYR - [5:2] */
+#define WM8962_THREED_DELAYR_SHIFT 2 /* THREED_DELAYR - [5:2] */
+#define WM8962_THREED_DELAYR_WIDTH 4 /* THREED_DELAYR - [5:2] */
+
+/*
+ * R276 (0x114) - DRC 1
+ */
+#define WM8962_DRC_SIG_DET_RMS_MASK 0x7C00 /* DRC_SIG_DET_RMS - [14:10] */
+#define WM8962_DRC_SIG_DET_RMS_SHIFT 10 /* DRC_SIG_DET_RMS - [14:10] */
+#define WM8962_DRC_SIG_DET_RMS_WIDTH 5 /* DRC_SIG_DET_RMS - [14:10] */
+#define WM8962_DRC_SIG_DET_PK_MASK 0x0300 /* DRC_SIG_DET_PK - [9:8] */
+#define WM8962_DRC_SIG_DET_PK_SHIFT 8 /* DRC_SIG_DET_PK - [9:8] */
+#define WM8962_DRC_SIG_DET_PK_WIDTH 2 /* DRC_SIG_DET_PK - [9:8] */
+#define WM8962_DRC_NG_ENA 0x0080 /* DRC_NG_ENA */
+#define WM8962_DRC_NG_ENA_MASK 0x0080 /* DRC_NG_ENA */
+#define WM8962_DRC_NG_ENA_SHIFT 7 /* DRC_NG_ENA */
+#define WM8962_DRC_NG_ENA_WIDTH 1 /* DRC_NG_ENA */
+#define WM8962_DRC_SIG_DET_MODE 0x0040 /* DRC_SIG_DET_MODE */
+#define WM8962_DRC_SIG_DET_MODE_MASK 0x0040 /* DRC_SIG_DET_MODE */
+#define WM8962_DRC_SIG_DET_MODE_SHIFT 6 /* DRC_SIG_DET_MODE */
+#define WM8962_DRC_SIG_DET_MODE_WIDTH 1 /* DRC_SIG_DET_MODE */
+#define WM8962_DRC_SIG_DET 0x0020 /* DRC_SIG_DET */
+#define WM8962_DRC_SIG_DET_MASK 0x0020 /* DRC_SIG_DET */
+#define WM8962_DRC_SIG_DET_SHIFT 5 /* DRC_SIG_DET */
+#define WM8962_DRC_SIG_DET_WIDTH 1 /* DRC_SIG_DET */
+#define WM8962_DRC_KNEE2_OP_ENA 0x0010 /* DRC_KNEE2_OP_ENA */
+#define WM8962_DRC_KNEE2_OP_ENA_MASK 0x0010 /* DRC_KNEE2_OP_ENA */
+#define WM8962_DRC_KNEE2_OP_ENA_SHIFT 4 /* DRC_KNEE2_OP_ENA */
+#define WM8962_DRC_KNEE2_OP_ENA_WIDTH 1 /* DRC_KNEE2_OP_ENA */
+#define WM8962_DRC_QR 0x0008 /* DRC_QR */
+#define WM8962_DRC_QR_MASK 0x0008 /* DRC_QR */
+#define WM8962_DRC_QR_SHIFT 3 /* DRC_QR */
+#define WM8962_DRC_QR_WIDTH 1 /* DRC_QR */
+#define WM8962_DRC_ANTICLIP 0x0004 /* DRC_ANTICLIP */
+#define WM8962_DRC_ANTICLIP_MASK 0x0004 /* DRC_ANTICLIP */
+#define WM8962_DRC_ANTICLIP_SHIFT 2 /* DRC_ANTICLIP */
+#define WM8962_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */
+#define WM8962_DRC_MODE 0x0002 /* DRC_MODE */
+#define WM8962_DRC_MODE_MASK 0x0002 /* DRC_MODE */
+#define WM8962_DRC_MODE_SHIFT 1 /* DRC_MODE */
+#define WM8962_DRC_MODE_WIDTH 1 /* DRC_MODE */
+#define WM8962_DRC_ENA 0x0001 /* DRC_ENA */
+#define WM8962_DRC_ENA_MASK 0x0001 /* DRC_ENA */
+#define WM8962_DRC_ENA_SHIFT 0 /* DRC_ENA */
+#define WM8962_DRC_ENA_WIDTH 1 /* DRC_ENA */
+
+/*
+ * R277 (0x115) - DRC 2
+ */
+#define WM8962_DRC_ATK_MASK 0x1E00 /* DRC_ATK - [12:9] */
+#define WM8962_DRC_ATK_SHIFT 9 /* DRC_ATK - [12:9] */
+#define WM8962_DRC_ATK_WIDTH 4 /* DRC_ATK - [12:9] */
+#define WM8962_DRC_DCY_MASK 0x01E0 /* DRC_DCY - [8:5] */
+#define WM8962_DRC_DCY_SHIFT 5 /* DRC_DCY - [8:5] */
+#define WM8962_DRC_DCY_WIDTH 4 /* DRC_DCY - [8:5] */
+#define WM8962_DRC_MINGAIN_MASK 0x001C /* DRC_MINGAIN - [4:2] */
+#define WM8962_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [4:2] */
+#define WM8962_DRC_MINGAIN_WIDTH 3 /* DRC_MINGAIN - [4:2] */
+#define WM8962_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */
+#define WM8962_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */
+#define WM8962_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R278 (0x116) - DRC 3
+ */
+#define WM8962_DRC_NG_MINGAIN_MASK 0xF000 /* DRC_NG_MINGAIN - [15:12] */
+#define WM8962_DRC_NG_MINGAIN_SHIFT 12 /* DRC_NG_MINGAIN - [15:12] */
+#define WM8962_DRC_NG_MINGAIN_WIDTH 4 /* DRC_NG_MINGAIN - [15:12] */
+#define WM8962_DRC_QR_THR_MASK 0x0C00 /* DRC_QR_THR - [11:10] */
+#define WM8962_DRC_QR_THR_SHIFT 10 /* DRC_QR_THR - [11:10] */
+#define WM8962_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [11:10] */
+#define WM8962_DRC_QR_DCY_MASK 0x0300 /* DRC_QR_DCY - [9:8] */
+#define WM8962_DRC_QR_DCY_SHIFT 8 /* DRC_QR_DCY - [9:8] */
+#define WM8962_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [9:8] */
+#define WM8962_DRC_NG_EXP_MASK 0x00C0 /* DRC_NG_EXP - [7:6] */
+#define WM8962_DRC_NG_EXP_SHIFT 6 /* DRC_NG_EXP - [7:6] */
+#define WM8962_DRC_NG_EXP_WIDTH 2 /* DRC_NG_EXP - [7:6] */
+#define WM8962_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */
+#define WM8962_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */
+#define WM8962_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */
+#define WM8962_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */
+#define WM8962_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */
+#define WM8962_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */
+
+/*
+ * R279 (0x117) - DRC 4
+ */
+#define WM8962_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */
+#define WM8962_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */
+#define WM8962_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */
+#define WM8962_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */
+#define WM8962_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */
+#define WM8962_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */
+
+/*
+ * R280 (0x118) - DRC 5
+ */
+#define WM8962_DRC_KNEE2_IP_MASK 0x03E0 /* DRC_KNEE2_IP - [9:5] */
+#define WM8962_DRC_KNEE2_IP_SHIFT 5 /* DRC_KNEE2_IP - [9:5] */
+#define WM8962_DRC_KNEE2_IP_WIDTH 5 /* DRC_KNEE2_IP - [9:5] */
+#define WM8962_DRC_KNEE2_OP_MASK 0x001F /* DRC_KNEE2_OP - [4:0] */
+#define WM8962_DRC_KNEE2_OP_SHIFT 0 /* DRC_KNEE2_OP - [4:0] */
+#define WM8962_DRC_KNEE2_OP_WIDTH 5 /* DRC_KNEE2_OP - [4:0] */
+
+/*
+ * R285 (0x11D) - Tloopback
+ */
+#define WM8962_TLB_ENA 0x0002 /* TLB_ENA */
+#define WM8962_TLB_ENA_MASK 0x0002 /* TLB_ENA */
+#define WM8962_TLB_ENA_SHIFT 1 /* TLB_ENA */
+#define WM8962_TLB_ENA_WIDTH 1 /* TLB_ENA */
+#define WM8962_TLB_MODE 0x0001 /* TLB_MODE */
+#define WM8962_TLB_MODE_MASK 0x0001 /* TLB_MODE */
+#define WM8962_TLB_MODE_SHIFT 0 /* TLB_MODE */
+#define WM8962_TLB_MODE_WIDTH 1 /* TLB_MODE */
+
+/*
+ * R335 (0x14F) - EQ1
+ */
+#define WM8962_EQ_SHARED_COEFF 0x0004 /* EQ_SHARED_COEFF */
+#define WM8962_EQ_SHARED_COEFF_MASK 0x0004 /* EQ_SHARED_COEFF */
+#define WM8962_EQ_SHARED_COEFF_SHIFT 2 /* EQ_SHARED_COEFF */
+#define WM8962_EQ_SHARED_COEFF_WIDTH 1 /* EQ_SHARED_COEFF */
+#define WM8962_EQ_SHARED_COEFF_SEL 0x0002 /* EQ_SHARED_COEFF_SEL */
+#define WM8962_EQ_SHARED_COEFF_SEL_MASK 0x0002 /* EQ_SHARED_COEFF_SEL */
+#define WM8962_EQ_SHARED_COEFF_SEL_SHIFT 1 /* EQ_SHARED_COEFF_SEL */
+#define WM8962_EQ_SHARED_COEFF_SEL_WIDTH 1 /* EQ_SHARED_COEFF_SEL */
+#define WM8962_EQ_ENA 0x0001 /* EQ_ENA */
+#define WM8962_EQ_ENA_MASK 0x0001 /* EQ_ENA */
+#define WM8962_EQ_ENA_SHIFT 0 /* EQ_ENA */
+#define WM8962_EQ_ENA_WIDTH 1 /* EQ_ENA */
+
+/*
+ * R336 (0x150) - EQ2
+ */
+#define WM8962_EQL_B1_GAIN_MASK 0xF800 /* EQL_B1_GAIN - [15:11] */
+#define WM8962_EQL_B1_GAIN_SHIFT 11 /* EQL_B1_GAIN - [15:11] */
+#define WM8962_EQL_B1_GAIN_WIDTH 5 /* EQL_B1_GAIN - [15:11] */
+#define WM8962_EQL_B2_GAIN_MASK 0x07C0 /* EQL_B2_GAIN - [10:6] */
+#define WM8962_EQL_B2_GAIN_SHIFT 6 /* EQL_B2_GAIN - [10:6] */
+#define WM8962_EQL_B2_GAIN_WIDTH 5 /* EQL_B2_GAIN - [10:6] */
+#define WM8962_EQL_B3_GAIN_MASK 0x003E /* EQL_B3_GAIN - [5:1] */
+#define WM8962_EQL_B3_GAIN_SHIFT 1 /* EQL_B3_GAIN - [5:1] */
+#define WM8962_EQL_B3_GAIN_WIDTH 5 /* EQL_B3_GAIN - [5:1] */
+
+/*
+ * R337 (0x151) - EQ3
+ */
+#define WM8962_EQL_B4_GAIN_MASK 0xF800 /* EQL_B4_GAIN - [15:11] */
+#define WM8962_EQL_B4_GAIN_SHIFT 11 /* EQL_B4_GAIN - [15:11] */
+#define WM8962_EQL_B4_GAIN_WIDTH 5 /* EQL_B4_GAIN - [15:11] */
+#define WM8962_EQL_B5_GAIN_MASK 0x07C0 /* EQL_B5_GAIN - [10:6] */
+#define WM8962_EQL_B5_GAIN_SHIFT 6 /* EQL_B5_GAIN - [10:6] */
+#define WM8962_EQL_B5_GAIN_WIDTH 5 /* EQL_B5_GAIN - [10:6] */
+
+/*
+ * R338 (0x152) - EQ4
+ */
+#define WM8962_EQL_B1_A_MASK 0xFFFF /* EQL_B1_A - [15:0] */
+#define WM8962_EQL_B1_A_SHIFT 0 /* EQL_B1_A - [15:0] */
+#define WM8962_EQL_B1_A_WIDTH 16 /* EQL_B1_A - [15:0] */
+
+/*
+ * R339 (0x153) - EQ5
+ */
+#define WM8962_EQL_B1_B_MASK 0xFFFF /* EQL_B1_B - [15:0] */
+#define WM8962_EQL_B1_B_SHIFT 0 /* EQL_B1_B - [15:0] */
+#define WM8962_EQL_B1_B_WIDTH 16 /* EQL_B1_B - [15:0] */
+
+/*
+ * R340 (0x154) - EQ6
+ */
+#define WM8962_EQL_B1_PG_MASK 0xFFFF /* EQL_B1_PG - [15:0] */
+#define WM8962_EQL_B1_PG_SHIFT 0 /* EQL_B1_PG - [15:0] */
+#define WM8962_EQL_B1_PG_WIDTH 16 /* EQL_B1_PG - [15:0] */
+
+/*
+ * R341 (0x155) - EQ7
+ */
+#define WM8962_EQL_B2_A_MASK 0xFFFF /* EQL_B2_A - [15:0] */
+#define WM8962_EQL_B2_A_SHIFT 0 /* EQL_B2_A - [15:0] */
+#define WM8962_EQL_B2_A_WIDTH 16 /* EQL_B2_A - [15:0] */
+
+/*
+ * R342 (0x156) - EQ8
+ */
+#define WM8962_EQL_B2_B_MASK 0xFFFF /* EQL_B2_B - [15:0] */
+#define WM8962_EQL_B2_B_SHIFT 0 /* EQL_B2_B - [15:0] */
+#define WM8962_EQL_B2_B_WIDTH 16 /* EQL_B2_B - [15:0] */
+
+/*
+ * R343 (0x157) - EQ9
+ */
+#define WM8962_EQL_B2_C_MASK 0xFFFF /* EQL_B2_C - [15:0] */
+#define WM8962_EQL_B2_C_SHIFT 0 /* EQL_B2_C - [15:0] */
+#define WM8962_EQL_B2_C_WIDTH 16 /* EQL_B2_C - [15:0] */
+
+/*
+ * R344 (0x158) - EQ10
+ */
+#define WM8962_EQL_B2_PG_MASK 0xFFFF /* EQL_B2_PG - [15:0] */
+#define WM8962_EQL_B2_PG_SHIFT 0 /* EQL_B2_PG - [15:0] */
+#define WM8962_EQL_B2_PG_WIDTH 16 /* EQL_B2_PG - [15:0] */
+
+/*
+ * R345 (0x159) - EQ11
+ */
+#define WM8962_EQL_B3_A_MASK 0xFFFF /* EQL_B3_A - [15:0] */
+#define WM8962_EQL_B3_A_SHIFT 0 /* EQL_B3_A - [15:0] */
+#define WM8962_EQL_B3_A_WIDTH 16 /* EQL_B3_A - [15:0] */
+
+/*
+ * R346 (0x15A) - EQ12
+ */
+#define WM8962_EQL_B3_B_MASK 0xFFFF /* EQL_B3_B - [15:0] */
+#define WM8962_EQL_B3_B_SHIFT 0 /* EQL_B3_B - [15:0] */
+#define WM8962_EQL_B3_B_WIDTH 16 /* EQL_B3_B - [15:0] */
+
+/*
+ * R347 (0x15B) - EQ13
+ */
+#define WM8962_EQL_B3_C_MASK 0xFFFF /* EQL_B3_C - [15:0] */
+#define WM8962_EQL_B3_C_SHIFT 0 /* EQL_B3_C - [15:0] */
+#define WM8962_EQL_B3_C_WIDTH 16 /* EQL_B3_C - [15:0] */
+
+/*
+ * R348 (0x15C) - EQ14
+ */
+#define WM8962_EQL_B3_PG_MASK 0xFFFF /* EQL_B3_PG - [15:0] */
+#define WM8962_EQL_B3_PG_SHIFT 0 /* EQL_B3_PG - [15:0] */
+#define WM8962_EQL_B3_PG_WIDTH 16 /* EQL_B3_PG - [15:0] */
+
+/*
+ * R349 (0x15D) - EQ15
+ */
+#define WM8962_EQL_B4_A_MASK 0xFFFF /* EQL_B4_A - [15:0] */
+#define WM8962_EQL_B4_A_SHIFT 0 /* EQL_B4_A - [15:0] */
+#define WM8962_EQL_B4_A_WIDTH 16 /* EQL_B4_A - [15:0] */
+
+/*
+ * R350 (0x15E) - EQ16
+ */
+#define WM8962_EQL_B4_B_MASK 0xFFFF /* EQL_B4_B - [15:0] */
+#define WM8962_EQL_B4_B_SHIFT 0 /* EQL_B4_B - [15:0] */
+#define WM8962_EQL_B4_B_WIDTH 16 /* EQL_B4_B - [15:0] */
+
+/*
+ * R351 (0x15F) - EQ17
+ */
+#define WM8962_EQL_B4_C_MASK 0xFFFF /* EQL_B4_C - [15:0] */
+#define WM8962_EQL_B4_C_SHIFT 0 /* EQL_B4_C - [15:0] */
+#define WM8962_EQL_B4_C_WIDTH 16 /* EQL_B4_C - [15:0] */
+
+/*
+ * R352 (0x160) - EQ18
+ */
+#define WM8962_EQL_B4_PG_MASK 0xFFFF /* EQL_B4_PG - [15:0] */
+#define WM8962_EQL_B4_PG_SHIFT 0 /* EQL_B4_PG - [15:0] */
+#define WM8962_EQL_B4_PG_WIDTH 16 /* EQL_B4_PG - [15:0] */
+
+/*
+ * R353 (0x161) - EQ19
+ */
+#define WM8962_EQL_B5_A_MASK 0xFFFF /* EQL_B5_A - [15:0] */
+#define WM8962_EQL_B5_A_SHIFT 0 /* EQL_B5_A - [15:0] */
+#define WM8962_EQL_B5_A_WIDTH 16 /* EQL_B5_A - [15:0] */
+
+/*
+ * R354 (0x162) - EQ20
+ */
+#define WM8962_EQL_B5_B_MASK 0xFFFF /* EQL_B5_B - [15:0] */
+#define WM8962_EQL_B5_B_SHIFT 0 /* EQL_B5_B - [15:0] */
+#define WM8962_EQL_B5_B_WIDTH 16 /* EQL_B5_B - [15:0] */
+
+/*
+ * R355 (0x163) - EQ21
+ */
+#define WM8962_EQL_B5_PG_MASK 0xFFFF /* EQL_B5_PG - [15:0] */
+#define WM8962_EQL_B5_PG_SHIFT 0 /* EQL_B5_PG - [15:0] */
+#define WM8962_EQL_B5_PG_WIDTH 16 /* EQL_B5_PG - [15:0] */
+
+/*
+ * R356 (0x164) - EQ22
+ */
+#define WM8962_EQR_B1_GAIN_MASK 0xF800 /* EQR_B1_GAIN - [15:11] */
+#define WM8962_EQR_B1_GAIN_SHIFT 11 /* EQR_B1_GAIN - [15:11] */
+#define WM8962_EQR_B1_GAIN_WIDTH 5 /* EQR_B1_GAIN - [15:11] */
+#define WM8962_EQR_B2_GAIN_MASK 0x07C0 /* EQR_B2_GAIN - [10:6] */
+#define WM8962_EQR_B2_GAIN_SHIFT 6 /* EQR_B2_GAIN - [10:6] */
+#define WM8962_EQR_B2_GAIN_WIDTH 5 /* EQR_B2_GAIN - [10:6] */
+#define WM8962_EQR_B3_GAIN_MASK 0x003E /* EQR_B3_GAIN - [5:1] */
+#define WM8962_EQR_B3_GAIN_SHIFT 1 /* EQR_B3_GAIN - [5:1] */
+#define WM8962_EQR_B3_GAIN_WIDTH 5 /* EQR_B3_GAIN - [5:1] */
+
+/*
+ * R357 (0x165) - EQ23
+ */
+#define WM8962_EQR_B4_GAIN_MASK 0xF800 /* EQR_B4_GAIN - [15:11] */
+#define WM8962_EQR_B4_GAIN_SHIFT 11 /* EQR_B4_GAIN - [15:11] */
+#define WM8962_EQR_B4_GAIN_WIDTH 5 /* EQR_B4_GAIN - [15:11] */
+#define WM8962_EQR_B5_GAIN_MASK 0x07C0 /* EQR_B5_GAIN - [10:6] */
+#define WM8962_EQR_B5_GAIN_SHIFT 6 /* EQR_B5_GAIN - [10:6] */
+#define WM8962_EQR_B5_GAIN_WIDTH 5 /* EQR_B5_GAIN - [10:6] */
+
+/*
+ * R358 (0x166) - EQ24
+ */
+#define WM8962_EQR_B1_A_MASK 0xFFFF /* EQR_B1_A - [15:0] */
+#define WM8962_EQR_B1_A_SHIFT 0 /* EQR_B1_A - [15:0] */
+#define WM8962_EQR_B1_A_WIDTH 16 /* EQR_B1_A - [15:0] */
+
+/*
+ * R359 (0x167) - EQ25
+ */
+#define WM8962_EQR_B1_B_MASK 0xFFFF /* EQR_B1_B - [15:0] */
+#define WM8962_EQR_B1_B_SHIFT 0 /* EQR_B1_B - [15:0] */
+#define WM8962_EQR_B1_B_WIDTH 16 /* EQR_B1_B - [15:0] */
+
+/*
+ * R360 (0x168) - EQ26
+ */
+#define WM8962_EQR_B1_PG_MASK 0xFFFF /* EQR_B1_PG - [15:0] */
+#define WM8962_EQR_B1_PG_SHIFT 0 /* EQR_B1_PG - [15:0] */
+#define WM8962_EQR_B1_PG_WIDTH 16 /* EQR_B1_PG - [15:0] */
+
+/*
+ * R361 (0x169) - EQ27
+ */
+#define WM8962_EQR_B2_A_MASK 0xFFFF /* EQR_B2_A - [15:0] */
+#define WM8962_EQR_B2_A_SHIFT 0 /* EQR_B2_A - [15:0] */
+#define WM8962_EQR_B2_A_WIDTH 16 /* EQR_B2_A - [15:0] */
+
+/*
+ * R362 (0x16A) - EQ28
+ */
+#define WM8962_EQR_B2_B_MASK 0xFFFF /* EQR_B2_B - [15:0] */
+#define WM8962_EQR_B2_B_SHIFT 0 /* EQR_B2_B - [15:0] */
+#define WM8962_EQR_B2_B_WIDTH 16 /* EQR_B2_B - [15:0] */
+
+/*
+ * R363 (0x16B) - EQ29
+ */
+#define WM8962_EQR_B2_C_MASK 0xFFFF /* EQR_B2_C - [15:0] */
+#define WM8962_EQR_B2_C_SHIFT 0 /* EQR_B2_C - [15:0] */
+#define WM8962_EQR_B2_C_WIDTH 16 /* EQR_B2_C - [15:0] */
+
+/*
+ * R364 (0x16C) - EQ30
+ */
+#define WM8962_EQR_B2_PG_MASK 0xFFFF /* EQR_B2_PG - [15:0] */
+#define WM8962_EQR_B2_PG_SHIFT 0 /* EQR_B2_PG - [15:0] */
+#define WM8962_EQR_B2_PG_WIDTH 16 /* EQR_B2_PG - [15:0] */
+
+/*
+ * R365 (0x16D) - EQ31
+ */
+#define WM8962_EQR_B3_A_MASK 0xFFFF /* EQR_B3_A - [15:0] */
+#define WM8962_EQR_B3_A_SHIFT 0 /* EQR_B3_A - [15:0] */
+#define WM8962_EQR_B3_A_WIDTH 16 /* EQR_B3_A - [15:0] */
+
+/*
+ * R366 (0x16E) - EQ32
+ */
+#define WM8962_EQR_B3_B_MASK 0xFFFF /* EQR_B3_B - [15:0] */
+#define WM8962_EQR_B3_B_SHIFT 0 /* EQR_B3_B - [15:0] */
+#define WM8962_EQR_B3_B_WIDTH 16 /* EQR_B3_B - [15:0] */
+
+/*
+ * R367 (0x16F) - EQ33
+ */
+#define WM8962_EQR_B3_C_MASK 0xFFFF /* EQR_B3_C - [15:0] */
+#define WM8962_EQR_B3_C_SHIFT 0 /* EQR_B3_C - [15:0] */
+#define WM8962_EQR_B3_C_WIDTH 16 /* EQR_B3_C - [15:0] */
+
+/*
+ * R368 (0x170) - EQ34
+ */
+#define WM8962_EQR_B3_PG_MASK 0xFFFF /* EQR_B3_PG - [15:0] */
+#define WM8962_EQR_B3_PG_SHIFT 0 /* EQR_B3_PG - [15:0] */
+#define WM8962_EQR_B3_PG_WIDTH 16 /* EQR_B3_PG - [15:0] */
+
+/*
+ * R369 (0x171) - EQ35
+ */
+#define WM8962_EQR_B4_A_MASK 0xFFFF /* EQR_B4_A - [15:0] */
+#define WM8962_EQR_B4_A_SHIFT 0 /* EQR_B4_A - [15:0] */
+#define WM8962_EQR_B4_A_WIDTH 16 /* EQR_B4_A - [15:0] */
+
+/*
+ * R370 (0x172) - EQ36
+ */
+#define WM8962_EQR_B4_B_MASK 0xFFFF /* EQR_B4_B - [15:0] */
+#define WM8962_EQR_B4_B_SHIFT 0 /* EQR_B4_B - [15:0] */
+#define WM8962_EQR_B4_B_WIDTH 16 /* EQR_B4_B - [15:0] */
+
+/*
+ * R371 (0x173) - EQ37
+ */
+#define WM8962_EQR_B4_C_MASK 0xFFFF /* EQR_B4_C - [15:0] */
+#define WM8962_EQR_B4_C_SHIFT 0 /* EQR_B4_C - [15:0] */
+#define WM8962_EQR_B4_C_WIDTH 16 /* EQR_B4_C - [15:0] */
+
+/*
+ * R372 (0x174) - EQ38
+ */
+#define WM8962_EQR_B4_PG_MASK 0xFFFF /* EQR_B4_PG - [15:0] */
+#define WM8962_EQR_B4_PG_SHIFT 0 /* EQR_B4_PG - [15:0] */
+#define WM8962_EQR_B4_PG_WIDTH 16 /* EQR_B4_PG - [15:0] */
+
+/*
+ * R373 (0x175) - EQ39
+ */
+#define WM8962_EQR_B5_A_MASK 0xFFFF /* EQR_B5_A - [15:0] */
+#define WM8962_EQR_B5_A_SHIFT 0 /* EQR_B5_A - [15:0] */
+#define WM8962_EQR_B5_A_WIDTH 16 /* EQR_B5_A - [15:0] */
+
+/*
+ * R374 (0x176) - EQ40
+ */
+#define WM8962_EQR_B5_B_MASK 0xFFFF /* EQR_B5_B - [15:0] */
+#define WM8962_EQR_B5_B_SHIFT 0 /* EQR_B5_B - [15:0] */
+#define WM8962_EQR_B5_B_WIDTH 16 /* EQR_B5_B - [15:0] */
+
+/*
+ * R375 (0x177) - EQ41
+ */
+#define WM8962_EQR_B5_PG_MASK 0xFFFF /* EQR_B5_PG - [15:0] */
+#define WM8962_EQR_B5_PG_SHIFT 0 /* EQR_B5_PG - [15:0] */
+#define WM8962_EQR_B5_PG_WIDTH 16 /* EQR_B5_PG - [15:0] */
+
+/*
+ * R513 (0x201) - GPIO 2
+ */
+#define WM8962_GP2_POL 0x0400 /* GP2_POL */
+#define WM8962_GP2_POL_MASK 0x0400 /* GP2_POL */
+#define WM8962_GP2_POL_SHIFT 10 /* GP2_POL */
+#define WM8962_GP2_POL_WIDTH 1 /* GP2_POL */
+#define WM8962_GP2_LVL 0x0040 /* GP2_LVL */
+#define WM8962_GP2_LVL_MASK 0x0040 /* GP2_LVL */
+#define WM8962_GP2_LVL_SHIFT 6 /* GP2_LVL */
+#define WM8962_GP2_LVL_WIDTH 1 /* GP2_LVL */
+#define WM8962_GP2_FN_MASK 0x001F /* GP2_FN - [4:0] */
+#define WM8962_GP2_FN_SHIFT 0 /* GP2_FN - [4:0] */
+#define WM8962_GP2_FN_WIDTH 5 /* GP2_FN - [4:0] */
+
+/*
+ * R514 (0x202) - GPIO 3
+ */
+#define WM8962_GP3_POL 0x0400 /* GP3_POL */
+#define WM8962_GP3_POL_MASK 0x0400 /* GP3_POL */
+#define WM8962_GP3_POL_SHIFT 10 /* GP3_POL */
+#define WM8962_GP3_POL_WIDTH 1 /* GP3_POL */
+#define WM8962_GP3_LVL 0x0040 /* GP3_LVL */
+#define WM8962_GP3_LVL_MASK 0x0040 /* GP3_LVL */
+#define WM8962_GP3_LVL_SHIFT 6 /* GP3_LVL */
+#define WM8962_GP3_LVL_WIDTH 1 /* GP3_LVL */
+#define WM8962_GP3_FN_MASK 0x001F /* GP3_FN - [4:0] */
+#define WM8962_GP3_FN_SHIFT 0 /* GP3_FN - [4:0] */
+#define WM8962_GP3_FN_WIDTH 5 /* GP3_FN - [4:0] */
+
+/*
+ * R516 (0x204) - GPIO 5
+ */
+#define WM8962_GP5_DIR 0x8000 /* GP5_DIR */
+#define WM8962_GP5_DIR_MASK 0x8000 /* GP5_DIR */
+#define WM8962_GP5_DIR_SHIFT 15 /* GP5_DIR */
+#define WM8962_GP5_DIR_WIDTH 1 /* GP5_DIR */
+#define WM8962_GP5_PU 0x4000 /* GP5_PU */
+#define WM8962_GP5_PU_MASK 0x4000 /* GP5_PU */
+#define WM8962_GP5_PU_SHIFT 14 /* GP5_PU */
+#define WM8962_GP5_PU_WIDTH 1 /* GP5_PU */
+#define WM8962_GP5_PD 0x2000 /* GP5_PD */
+#define WM8962_GP5_PD_MASK 0x2000 /* GP5_PD */
+#define WM8962_GP5_PD_SHIFT 13 /* GP5_PD */
+#define WM8962_GP5_PD_WIDTH 1 /* GP5_PD */
+#define WM8962_GP5_POL 0x0400 /* GP5_POL */
+#define WM8962_GP5_POL_MASK 0x0400 /* GP5_POL */
+#define WM8962_GP5_POL_SHIFT 10 /* GP5_POL */
+#define WM8962_GP5_POL_WIDTH 1 /* GP5_POL */
+#define WM8962_GP5_OP_CFG 0x0200 /* GP5_OP_CFG */
+#define WM8962_GP5_OP_CFG_MASK 0x0200 /* GP5_OP_CFG */
+#define WM8962_GP5_OP_CFG_SHIFT 9 /* GP5_OP_CFG */
+#define WM8962_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */
+#define WM8962_GP5_DB 0x0100 /* GP5_DB */
+#define WM8962_GP5_DB_MASK 0x0100 /* GP5_DB */
+#define WM8962_GP5_DB_SHIFT 8 /* GP5_DB */
+#define WM8962_GP5_DB_WIDTH 1 /* GP5_DB */
+#define WM8962_GP5_LVL 0x0040 /* GP5_LVL */
+#define WM8962_GP5_LVL_MASK 0x0040 /* GP5_LVL */
+#define WM8962_GP5_LVL_SHIFT 6 /* GP5_LVL */
+#define WM8962_GP5_LVL_WIDTH 1 /* GP5_LVL */
+#define WM8962_GP5_FN_MASK 0x001F /* GP5_FN - [4:0] */
+#define WM8962_GP5_FN_SHIFT 0 /* GP5_FN - [4:0] */
+#define WM8962_GP5_FN_WIDTH 5 /* GP5_FN - [4:0] */
+
+/*
+ * R517 (0x205) - GPIO 6
+ */
+#define WM8962_GP6_DIR 0x8000 /* GP6_DIR */
+#define WM8962_GP6_DIR_MASK 0x8000 /* GP6_DIR */
+#define WM8962_GP6_DIR_SHIFT 15 /* GP6_DIR */
+#define WM8962_GP6_DIR_WIDTH 1 /* GP6_DIR */
+#define WM8962_GP6_PU 0x4000 /* GP6_PU */
+#define WM8962_GP6_PU_MASK 0x4000 /* GP6_PU */
+#define WM8962_GP6_PU_SHIFT 14 /* GP6_PU */
+#define WM8962_GP6_PU_WIDTH 1 /* GP6_PU */
+#define WM8962_GP6_PD 0x2000 /* GP6_PD */
+#define WM8962_GP6_PD_MASK 0x2000 /* GP6_PD */
+#define WM8962_GP6_PD_SHIFT 13 /* GP6_PD */
+#define WM8962_GP6_PD_WIDTH 1 /* GP6_PD */
+#define WM8962_GP6_POL 0x0400 /* GP6_POL */
+#define WM8962_GP6_POL_MASK 0x0400 /* GP6_POL */
+#define WM8962_GP6_POL_SHIFT 10 /* GP6_POL */
+#define WM8962_GP6_POL_WIDTH 1 /* GP6_POL */
+#define WM8962_GP6_OP_CFG 0x0200 /* GP6_OP_CFG */
+#define WM8962_GP6_OP_CFG_MASK 0x0200 /* GP6_OP_CFG */
+#define WM8962_GP6_OP_CFG_SHIFT 9 /* GP6_OP_CFG */
+#define WM8962_GP6_OP_CFG_WIDTH 1 /* GP6_OP_CFG */
+#define WM8962_GP6_DB 0x0100 /* GP6_DB */
+#define WM8962_GP6_DB_MASK 0x0100 /* GP6_DB */
+#define WM8962_GP6_DB_SHIFT 8 /* GP6_DB */
+#define WM8962_GP6_DB_WIDTH 1 /* GP6_DB */
+#define WM8962_GP6_LVL 0x0040 /* GP6_LVL */
+#define WM8962_GP6_LVL_MASK 0x0040 /* GP6_LVL */
+#define WM8962_GP6_LVL_SHIFT 6 /* GP6_LVL */
+#define WM8962_GP6_LVL_WIDTH 1 /* GP6_LVL */
+#define WM8962_GP6_FN_MASK 0x001F /* GP6_FN - [4:0] */
+#define WM8962_GP6_FN_SHIFT 0 /* GP6_FN - [4:0] */
+#define WM8962_GP6_FN_WIDTH 5 /* GP6_FN - [4:0] */
+
+/*
+ * R560 (0x230) - Interrupt Status 1
+ */
+#define WM8962_GP6_EINT 0x0020 /* GP6_EINT */
+#define WM8962_GP6_EINT_MASK 0x0020 /* GP6_EINT */
+#define WM8962_GP6_EINT_SHIFT 5 /* GP6_EINT */
+#define WM8962_GP6_EINT_WIDTH 1 /* GP6_EINT */
+#define WM8962_GP5_EINT 0x0010 /* GP5_EINT */
+#define WM8962_GP5_EINT_MASK 0x0010 /* GP5_EINT */
+#define WM8962_GP5_EINT_SHIFT 4 /* GP5_EINT */
+#define WM8962_GP5_EINT_WIDTH 1 /* GP5_EINT */
+
+/*
+ * R561 (0x231) - Interrupt Status 2
+ */
+#define WM8962_MICSCD_EINT 0x8000 /* MICSCD_EINT */
+#define WM8962_MICSCD_EINT_MASK 0x8000 /* MICSCD_EINT */
+#define WM8962_MICSCD_EINT_SHIFT 15 /* MICSCD_EINT */
+#define WM8962_MICSCD_EINT_WIDTH 1 /* MICSCD_EINT */
+#define WM8962_MICD_EINT 0x4000 /* MICD_EINT */
+#define WM8962_MICD_EINT_MASK 0x4000 /* MICD_EINT */
+#define WM8962_MICD_EINT_SHIFT 14 /* MICD_EINT */
+#define WM8962_MICD_EINT_WIDTH 1 /* MICD_EINT */
+#define WM8962_FIFOS_ERR_EINT 0x2000 /* FIFOS_ERR_EINT */
+#define WM8962_FIFOS_ERR_EINT_MASK 0x2000 /* FIFOS_ERR_EINT */
+#define WM8962_FIFOS_ERR_EINT_SHIFT 13 /* FIFOS_ERR_EINT */
+#define WM8962_FIFOS_ERR_EINT_WIDTH 1 /* FIFOS_ERR_EINT */
+#define WM8962_ALC_LOCK_EINT 0x1000 /* ALC_LOCK_EINT */
+#define WM8962_ALC_LOCK_EINT_MASK 0x1000 /* ALC_LOCK_EINT */
+#define WM8962_ALC_LOCK_EINT_SHIFT 12 /* ALC_LOCK_EINT */
+#define WM8962_ALC_LOCK_EINT_WIDTH 1 /* ALC_LOCK_EINT */
+#define WM8962_ALC_THRESH_EINT 0x0800 /* ALC_THRESH_EINT */
+#define WM8962_ALC_THRESH_EINT_MASK 0x0800 /* ALC_THRESH_EINT */
+#define WM8962_ALC_THRESH_EINT_SHIFT 11 /* ALC_THRESH_EINT */
+#define WM8962_ALC_THRESH_EINT_WIDTH 1 /* ALC_THRESH_EINT */
+#define WM8962_ALC_SAT_EINT 0x0400 /* ALC_SAT_EINT */
+#define WM8962_ALC_SAT_EINT_MASK 0x0400 /* ALC_SAT_EINT */
+#define WM8962_ALC_SAT_EINT_SHIFT 10 /* ALC_SAT_EINT */
+#define WM8962_ALC_SAT_EINT_WIDTH 1 /* ALC_SAT_EINT */
+#define WM8962_ALC_PKOVR_EINT 0x0200 /* ALC_PKOVR_EINT */
+#define WM8962_ALC_PKOVR_EINT_MASK 0x0200 /* ALC_PKOVR_EINT */
+#define WM8962_ALC_PKOVR_EINT_SHIFT 9 /* ALC_PKOVR_EINT */
+#define WM8962_ALC_PKOVR_EINT_WIDTH 1 /* ALC_PKOVR_EINT */
+#define WM8962_ALC_NGATE_EINT 0x0100 /* ALC_NGATE_EINT */
+#define WM8962_ALC_NGATE_EINT_MASK 0x0100 /* ALC_NGATE_EINT */
+#define WM8962_ALC_NGATE_EINT_SHIFT 8 /* ALC_NGATE_EINT */
+#define WM8962_ALC_NGATE_EINT_WIDTH 1 /* ALC_NGATE_EINT */
+#define WM8962_WSEQ_DONE_EINT 0x0080 /* WSEQ_DONE_EINT */
+#define WM8962_WSEQ_DONE_EINT_MASK 0x0080 /* WSEQ_DONE_EINT */
+#define WM8962_WSEQ_DONE_EINT_SHIFT 7 /* WSEQ_DONE_EINT */
+#define WM8962_WSEQ_DONE_EINT_WIDTH 1 /* WSEQ_DONE_EINT */
+#define WM8962_DRC_ACTDET_EINT 0x0040 /* DRC_ACTDET_EINT */
+#define WM8962_DRC_ACTDET_EINT_MASK 0x0040 /* DRC_ACTDET_EINT */
+#define WM8962_DRC_ACTDET_EINT_SHIFT 6 /* DRC_ACTDET_EINT */
+#define WM8962_DRC_ACTDET_EINT_WIDTH 1 /* DRC_ACTDET_EINT */
+#define WM8962_FLL_LOCK_EINT 0x0020 /* FLL_LOCK_EINT */
+#define WM8962_FLL_LOCK_EINT_MASK 0x0020 /* FLL_LOCK_EINT */
+#define WM8962_FLL_LOCK_EINT_SHIFT 5 /* FLL_LOCK_EINT */
+#define WM8962_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */
+#define WM8962_PLL3_LOCK_EINT 0x0008 /* PLL3_LOCK_EINT */
+#define WM8962_PLL3_LOCK_EINT_MASK 0x0008 /* PLL3_LOCK_EINT */
+#define WM8962_PLL3_LOCK_EINT_SHIFT 3 /* PLL3_LOCK_EINT */
+#define WM8962_PLL3_LOCK_EINT_WIDTH 1 /* PLL3_LOCK_EINT */
+#define WM8962_PLL2_LOCK_EINT 0x0004 /* PLL2_LOCK_EINT */
+#define WM8962_PLL2_LOCK_EINT_MASK 0x0004 /* PLL2_LOCK_EINT */
+#define WM8962_PLL2_LOCK_EINT_SHIFT 2 /* PLL2_LOCK_EINT */
+#define WM8962_PLL2_LOCK_EINT_WIDTH 1 /* PLL2_LOCK_EINT */
+#define WM8962_TEMP_SHUT_EINT 0x0001 /* TEMP_SHUT_EINT */
+#define WM8962_TEMP_SHUT_EINT_MASK 0x0001 /* TEMP_SHUT_EINT */
+#define WM8962_TEMP_SHUT_EINT_SHIFT 0 /* TEMP_SHUT_EINT */
+#define WM8962_TEMP_SHUT_EINT_WIDTH 1 /* TEMP_SHUT_EINT */
+
+/*
+ * R568 (0x238) - Interrupt Status 1 Mask
+ */
+#define WM8962_IM_GP6_EINT 0x0020 /* IM_GP6_EINT */
+#define WM8962_IM_GP6_EINT_MASK 0x0020 /* IM_GP6_EINT */
+#define WM8962_IM_GP6_EINT_SHIFT 5 /* IM_GP6_EINT */
+#define WM8962_IM_GP6_EINT_WIDTH 1 /* IM_GP6_EINT */
+#define WM8962_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */
+#define WM8962_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */
+#define WM8962_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */
+#define WM8962_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */
+
+/*
+ * R569 (0x239) - Interrupt Status 2 Mask
+ */
+#define WM8962_IM_MICSCD_EINT 0x8000 /* IM_MICSCD_EINT */
+#define WM8962_IM_MICSCD_EINT_MASK 0x8000 /* IM_MICSCD_EINT */
+#define WM8962_IM_MICSCD_EINT_SHIFT 15 /* IM_MICSCD_EINT */
+#define WM8962_IM_MICSCD_EINT_WIDTH 1 /* IM_MICSCD_EINT */
+#define WM8962_IM_MICD_EINT 0x4000 /* IM_MICD_EINT */
+#define WM8962_IM_MICD_EINT_MASK 0x4000 /* IM_MICD_EINT */
+#define WM8962_IM_MICD_EINT_SHIFT 14 /* IM_MICD_EINT */
+#define WM8962_IM_MICD_EINT_WIDTH 1 /* IM_MICD_EINT */
+#define WM8962_IM_FIFOS_ERR_EINT 0x2000 /* IM_FIFOS_ERR_EINT */
+#define WM8962_IM_FIFOS_ERR_EINT_MASK 0x2000 /* IM_FIFOS_ERR_EINT */
+#define WM8962_IM_FIFOS_ERR_EINT_SHIFT 13 /* IM_FIFOS_ERR_EINT */
+#define WM8962_IM_FIFOS_ERR_EINT_WIDTH 1 /* IM_FIFOS_ERR_EINT */
+#define WM8962_IM_ALC_LOCK_EINT 0x1000 /* IM_ALC_LOCK_EINT */
+#define WM8962_IM_ALC_LOCK_EINT_MASK 0x1000 /* IM_ALC_LOCK_EINT */
+#define WM8962_IM_ALC_LOCK_EINT_SHIFT 12 /* IM_ALC_LOCK_EINT */
+#define WM8962_IM_ALC_LOCK_EINT_WIDTH 1 /* IM_ALC_LOCK_EINT */
+#define WM8962_IM_ALC_THRESH_EINT 0x0800 /* IM_ALC_THRESH_EINT */
+#define WM8962_IM_ALC_THRESH_EINT_MASK 0x0800 /* IM_ALC_THRESH_EINT */
+#define WM8962_IM_ALC_THRESH_EINT_SHIFT 11 /* IM_ALC_THRESH_EINT */
+#define WM8962_IM_ALC_THRESH_EINT_WIDTH 1 /* IM_ALC_THRESH_EINT */
+#define WM8962_IM_ALC_SAT_EINT 0x0400 /* IM_ALC_SAT_EINT */
+#define WM8962_IM_ALC_SAT_EINT_MASK 0x0400 /* IM_ALC_SAT_EINT */
+#define WM8962_IM_ALC_SAT_EINT_SHIFT 10 /* IM_ALC_SAT_EINT */
+#define WM8962_IM_ALC_SAT_EINT_WIDTH 1 /* IM_ALC_SAT_EINT */
+#define WM8962_IM_ALC_PKOVR_EINT 0x0200 /* IM_ALC_PKOVR_EINT */
+#define WM8962_IM_ALC_PKOVR_EINT_MASK 0x0200 /* IM_ALC_PKOVR_EINT */
+#define WM8962_IM_ALC_PKOVR_EINT_SHIFT 9 /* IM_ALC_PKOVR_EINT */
+#define WM8962_IM_ALC_PKOVR_EINT_WIDTH 1 /* IM_ALC_PKOVR_EINT */
+#define WM8962_IM_ALC_NGATE_EINT 0x0100 /* IM_ALC_NGATE_EINT */
+#define WM8962_IM_ALC_NGATE_EINT_MASK 0x0100 /* IM_ALC_NGATE_EINT */
+#define WM8962_IM_ALC_NGATE_EINT_SHIFT 8 /* IM_ALC_NGATE_EINT */
+#define WM8962_IM_ALC_NGATE_EINT_WIDTH 1 /* IM_ALC_NGATE_EINT */
+#define WM8962_IM_WSEQ_DONE_EINT 0x0080 /* IM_WSEQ_DONE_EINT */
+#define WM8962_IM_WSEQ_DONE_EINT_MASK 0x0080 /* IM_WSEQ_DONE_EINT */
+#define WM8962_IM_WSEQ_DONE_EINT_SHIFT 7 /* IM_WSEQ_DONE_EINT */
+#define WM8962_IM_WSEQ_DONE_EINT_WIDTH 1 /* IM_WSEQ_DONE_EINT */
+#define WM8962_IM_DRC_ACTDET_EINT 0x0040 /* IM_DRC_ACTDET_EINT */
+#define WM8962_IM_DRC_ACTDET_EINT_MASK 0x0040 /* IM_DRC_ACTDET_EINT */
+#define WM8962_IM_DRC_ACTDET_EINT_SHIFT 6 /* IM_DRC_ACTDET_EINT */
+#define WM8962_IM_DRC_ACTDET_EINT_WIDTH 1 /* IM_DRC_ACTDET_EINT */
+#define WM8962_IM_FLL_LOCK_EINT 0x0020 /* IM_FLL_LOCK_EINT */
+#define WM8962_IM_FLL_LOCK_EINT_MASK 0x0020 /* IM_FLL_LOCK_EINT */
+#define WM8962_IM_FLL_LOCK_EINT_SHIFT 5 /* IM_FLL_LOCK_EINT */
+#define WM8962_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */
+#define WM8962_IM_PLL3_LOCK_EINT 0x0008 /* IM_PLL3_LOCK_EINT */
+#define WM8962_IM_PLL3_LOCK_EINT_MASK 0x0008 /* IM_PLL3_LOCK_EINT */
+#define WM8962_IM_PLL3_LOCK_EINT_SHIFT 3 /* IM_PLL3_LOCK_EINT */
+#define WM8962_IM_PLL3_LOCK_EINT_WIDTH 1 /* IM_PLL3_LOCK_EINT */
+#define WM8962_IM_PLL2_LOCK_EINT 0x0004 /* IM_PLL2_LOCK_EINT */
+#define WM8962_IM_PLL2_LOCK_EINT_MASK 0x0004 /* IM_PLL2_LOCK_EINT */
+#define WM8962_IM_PLL2_LOCK_EINT_SHIFT 2 /* IM_PLL2_LOCK_EINT */
+#define WM8962_IM_PLL2_LOCK_EINT_WIDTH 1 /* IM_PLL2_LOCK_EINT */
+#define WM8962_IM_TEMP_SHUT_EINT 0x0001 /* IM_TEMP_SHUT_EINT */
+#define WM8962_IM_TEMP_SHUT_EINT_MASK 0x0001 /* IM_TEMP_SHUT_EINT */
+#define WM8962_IM_TEMP_SHUT_EINT_SHIFT 0 /* IM_TEMP_SHUT_EINT */
+#define WM8962_IM_TEMP_SHUT_EINT_WIDTH 1 /* IM_TEMP_SHUT_EINT */
+
+/*
+ * R576 (0x240) - Interrupt Control
+ */
+#define WM8962_IRQ_POL 0x0001 /* IRQ_POL */
+#define WM8962_IRQ_POL_MASK 0x0001 /* IRQ_POL */
+#define WM8962_IRQ_POL_SHIFT 0 /* IRQ_POL */
+#define WM8962_IRQ_POL_WIDTH 1 /* IRQ_POL */
+
+/*
+ * R584 (0x248) - IRQ Debounce
+ */
+#define WM8962_FLL_LOCK_DB 0x0020 /* FLL_LOCK_DB */
+#define WM8962_FLL_LOCK_DB_MASK 0x0020 /* FLL_LOCK_DB */
+#define WM8962_FLL_LOCK_DB_SHIFT 5 /* FLL_LOCK_DB */
+#define WM8962_FLL_LOCK_DB_WIDTH 1 /* FLL_LOCK_DB */
+#define WM8962_PLL3_LOCK_DB 0x0008 /* PLL3_LOCK_DB */
+#define WM8962_PLL3_LOCK_DB_MASK 0x0008 /* PLL3_LOCK_DB */
+#define WM8962_PLL3_LOCK_DB_SHIFT 3 /* PLL3_LOCK_DB */
+#define WM8962_PLL3_LOCK_DB_WIDTH 1 /* PLL3_LOCK_DB */
+#define WM8962_PLL2_LOCK_DB 0x0004 /* PLL2_LOCK_DB */
+#define WM8962_PLL2_LOCK_DB_MASK 0x0004 /* PLL2_LOCK_DB */
+#define WM8962_PLL2_LOCK_DB_SHIFT 2 /* PLL2_LOCK_DB */
+#define WM8962_PLL2_LOCK_DB_WIDTH 1 /* PLL2_LOCK_DB */
+#define WM8962_TEMP_SHUT_DB 0x0001 /* TEMP_SHUT_DB */
+#define WM8962_TEMP_SHUT_DB_MASK 0x0001 /* TEMP_SHUT_DB */
+#define WM8962_TEMP_SHUT_DB_SHIFT 0 /* TEMP_SHUT_DB */
+#define WM8962_TEMP_SHUT_DB_WIDTH 1 /* TEMP_SHUT_DB */
+
+/*
+ * R586 (0x24A) - MICINT Source Pol
+ */
+#define WM8962_MICSCD_IRQ_POL 0x8000 /* MICSCD_IRQ_POL */
+#define WM8962_MICSCD_IRQ_POL_MASK 0x8000 /* MICSCD_IRQ_POL */
+#define WM8962_MICSCD_IRQ_POL_SHIFT 15 /* MICSCD_IRQ_POL */
+#define WM8962_MICSCD_IRQ_POL_WIDTH 1 /* MICSCD_IRQ_POL */
+#define WM8962_MICD_IRQ_POL 0x4000 /* MICD_IRQ_POL */
+#define WM8962_MICD_IRQ_POL_MASK 0x4000 /* MICD_IRQ_POL */
+#define WM8962_MICD_IRQ_POL_SHIFT 14 /* MICD_IRQ_POL */
+#define WM8962_MICD_IRQ_POL_WIDTH 1 /* MICD_IRQ_POL */
+
+/*
+ * R768 (0x300) - DSP2 Power Management
+ */
+#define WM8962_DSP2_ENA 0x0001 /* DSP2_ENA */
+#define WM8962_DSP2_ENA_MASK 0x0001 /* DSP2_ENA */
+#define WM8962_DSP2_ENA_SHIFT 0 /* DSP2_ENA */
+#define WM8962_DSP2_ENA_WIDTH 1 /* DSP2_ENA */
+
+/*
+ * R1037 (0x40D) - DSP2_ExecControl
+ */
+#define WM8962_DSP2_STOPC 0x0020 /* DSP2_STOPC */
+#define WM8962_DSP2_STOPC_MASK 0x0020 /* DSP2_STOPC */
+#define WM8962_DSP2_STOPC_SHIFT 5 /* DSP2_STOPC */
+#define WM8962_DSP2_STOPC_WIDTH 1 /* DSP2_STOPC */
+#define WM8962_DSP2_STOPS 0x0010 /* DSP2_STOPS */
+#define WM8962_DSP2_STOPS_MASK 0x0010 /* DSP2_STOPS */
+#define WM8962_DSP2_STOPS_SHIFT 4 /* DSP2_STOPS */
+#define WM8962_DSP2_STOPS_WIDTH 1 /* DSP2_STOPS */
+#define WM8962_DSP2_STOPI 0x0008 /* DSP2_STOPI */
+#define WM8962_DSP2_STOPI_MASK 0x0008 /* DSP2_STOPI */
+#define WM8962_DSP2_STOPI_SHIFT 3 /* DSP2_STOPI */
+#define WM8962_DSP2_STOPI_WIDTH 1 /* DSP2_STOPI */
+#define WM8962_DSP2_STOP 0x0004 /* DSP2_STOP */
+#define WM8962_DSP2_STOP_MASK 0x0004 /* DSP2_STOP */
+#define WM8962_DSP2_STOP_SHIFT 2 /* DSP2_STOP */
+#define WM8962_DSP2_STOP_WIDTH 1 /* DSP2_STOP */
+#define WM8962_DSP2_RUNR 0x0002 /* DSP2_RUNR */
+#define WM8962_DSP2_RUNR_MASK 0x0002 /* DSP2_RUNR */
+#define WM8962_DSP2_RUNR_SHIFT 1 /* DSP2_RUNR */
+#define WM8962_DSP2_RUNR_WIDTH 1 /* DSP2_RUNR */
+#define WM8962_DSP2_RUN 0x0001 /* DSP2_RUN */
+#define WM8962_DSP2_RUN_MASK 0x0001 /* DSP2_RUN */
+#define WM8962_DSP2_RUN_SHIFT 0 /* DSP2_RUN */
+#define WM8962_DSP2_RUN_WIDTH 1 /* DSP2_RUN */
+
+/*
+ * R8192 (0x2000) - DSP2 Instruction RAM 0
+ */
+#define WM8962_DSP2_INSTR_RAM_1024_10_9_0_MASK 0x03FF /* DSP2_INSTR_RAM_1024_10_9_0 - [9:0] */
+#define WM8962_DSP2_INSTR_RAM_1024_10_9_0_SHIFT 0 /* DSP2_INSTR_RAM_1024_10_9_0 - [9:0] */
+#define WM8962_DSP2_INSTR_RAM_1024_10_9_0_WIDTH 10 /* DSP2_INSTR_RAM_1024_10_9_0 - [9:0] */
+
+/*
+ * R9216 (0x2400) - DSP2 Address RAM 2
+ */
+#define WM8962_DSP2_ADDR_RAM_1024_38_37_32_MASK 0x003F /* DSP2_ADDR_RAM_1024_38_37_32 - [5:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_37_32_SHIFT 0 /* DSP2_ADDR_RAM_1024_38_37_32 - [5:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_37_32_WIDTH 6 /* DSP2_ADDR_RAM_1024_38_37_32 - [5:0] */
+
+/*
+ * R9217 (0x2401) - DSP2 Address RAM 1
+ */
+#define WM8962_DSP2_ADDR_RAM_1024_38_31_16_MASK 0xFFFF /* DSP2_ADDR_RAM_1024_38_31_16 - [15:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_31_16_SHIFT 0 /* DSP2_ADDR_RAM_1024_38_31_16 - [15:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_31_16_WIDTH 16 /* DSP2_ADDR_RAM_1024_38_31_16 - [15:0] */
+
+/*
+ * R9218 (0x2402) - DSP2 Address RAM 0
+ */
+#define WM8962_DSP2_ADDR_RAM_1024_38_15_0_MASK 0xFFFF /* DSP2_ADDR_RAM_1024_38_15_0 - [15:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_15_0_SHIFT 0 /* DSP2_ADDR_RAM_1024_38_15_0 - [15:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_15_0_WIDTH 16 /* DSP2_ADDR_RAM_1024_38_15_0 - [15:0] */
+
+/*
+ * R12288 (0x3000) - DSP2 Data1 RAM 1
+ */
+#define WM8962_DSP2_DATA1_RAM_384_24_23_16_MASK 0x00FF /* DSP2_DATA1_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA1_RAM_384_24_23_16_SHIFT 0 /* DSP2_DATA1_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA1_RAM_384_24_23_16_WIDTH 8 /* DSP2_DATA1_RAM_384_24_23_16 - [7:0] */
+
+/*
+ * R12289 (0x3001) - DSP2 Data1 RAM 0
+ */
+#define WM8962_DSP2_DATA1_RAM_384_24_15_0_MASK 0xFFFF /* DSP2_DATA1_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA1_RAM_384_24_15_0_SHIFT 0 /* DSP2_DATA1_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA1_RAM_384_24_15_0_WIDTH 16 /* DSP2_DATA1_RAM_384_24_15_0 - [15:0] */
+
+/*
+ * R13312 (0x3400) - DSP2 Data2 RAM 1
+ */
+#define WM8962_DSP2_DATA2_RAM_384_24_23_16_MASK 0x00FF /* DSP2_DATA2_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA2_RAM_384_24_23_16_SHIFT 0 /* DSP2_DATA2_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA2_RAM_384_24_23_16_WIDTH 8 /* DSP2_DATA2_RAM_384_24_23_16 - [7:0] */
+
+/*
+ * R13313 (0x3401) - DSP2 Data2 RAM 0
+ */
+#define WM8962_DSP2_DATA2_RAM_384_24_15_0_MASK 0xFFFF /* DSP2_DATA2_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA2_RAM_384_24_15_0_SHIFT 0 /* DSP2_DATA2_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA2_RAM_384_24_15_0_WIDTH 16 /* DSP2_DATA2_RAM_384_24_15_0 - [15:0] */
+
+/*
+ * R14336 (0x3800) - DSP2 Data3 RAM 1
+ */
+#define WM8962_DSP2_DATA3_RAM_384_24_23_16_MASK 0x00FF /* DSP2_DATA3_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA3_RAM_384_24_23_16_SHIFT 0 /* DSP2_DATA3_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA3_RAM_384_24_23_16_WIDTH 8 /* DSP2_DATA3_RAM_384_24_23_16 - [7:0] */
+
+/*
+ * R14337 (0x3801) - DSP2 Data3 RAM 0
+ */
+#define WM8962_DSP2_DATA3_RAM_384_24_15_0_MASK 0xFFFF /* DSP2_DATA3_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA3_RAM_384_24_15_0_SHIFT 0 /* DSP2_DATA3_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA3_RAM_384_24_15_0_WIDTH 16 /* DSP2_DATA3_RAM_384_24_15_0 - [15:0] */
+
+/*
+ * R15360 (0x3C00) - DSP2 Coeff RAM 0
+ */
+#define WM8962_DSP2_CMAP_RAM_384_11_10_0_MASK 0x07FF /* DSP2_CMAP_RAM_384_11_10_0 - [10:0] */
+#define WM8962_DSP2_CMAP_RAM_384_11_10_0_SHIFT 0 /* DSP2_CMAP_RAM_384_11_10_0 - [10:0] */
+#define WM8962_DSP2_CMAP_RAM_384_11_10_0_WIDTH 11 /* DSP2_CMAP_RAM_384_11_10_0 - [10:0] */
+
+/*
+ * R16384 (0x4000) - RETUNEADC_SHARED_COEFF_1
+ */
+#define WM8962_ADC_RETUNE_SCV 0x0080 /* ADC_RETUNE_SCV */
+#define WM8962_ADC_RETUNE_SCV_MASK 0x0080 /* ADC_RETUNE_SCV */
+#define WM8962_ADC_RETUNE_SCV_SHIFT 7 /* ADC_RETUNE_SCV */
+#define WM8962_ADC_RETUNE_SCV_WIDTH 1 /* ADC_RETUNE_SCV */
+#define WM8962_RETUNEADC_SHARED_COEFF_22_16_MASK 0x007F /* RETUNEADC_SHARED_COEFF_22_16 - [6:0] */
+#define WM8962_RETUNEADC_SHARED_COEFF_22_16_SHIFT 0 /* RETUNEADC_SHARED_COEFF_22_16 - [6:0] */
+#define WM8962_RETUNEADC_SHARED_COEFF_22_16_WIDTH 7 /* RETUNEADC_SHARED_COEFF_22_16 - [6:0] */
+
+/*
+ * R16385 (0x4001) - RETUNEADC_SHARED_COEFF_0
+ */
+#define WM8962_RETUNEADC_SHARED_COEFF_15_00_MASK 0xFFFF /* RETUNEADC_SHARED_COEFF_15_00 - [15:0] */
+#define WM8962_RETUNEADC_SHARED_COEFF_15_00_SHIFT 0 /* RETUNEADC_SHARED_COEFF_15_00 - [15:0] */
+#define WM8962_RETUNEADC_SHARED_COEFF_15_00_WIDTH 16 /* RETUNEADC_SHARED_COEFF_15_00 - [15:0] */
+
+/*
+ * R16386 (0x4002) - RETUNEDAC_SHARED_COEFF_1
+ */
+#define WM8962_DAC_RETUNE_SCV 0x0080 /* DAC_RETUNE_SCV */
+#define WM8962_DAC_RETUNE_SCV_MASK 0x0080 /* DAC_RETUNE_SCV */
+#define WM8962_DAC_RETUNE_SCV_SHIFT 7 /* DAC_RETUNE_SCV */
+#define WM8962_DAC_RETUNE_SCV_WIDTH 1 /* DAC_RETUNE_SCV */
+#define WM8962_RETUNEDAC_SHARED_COEFF_23_16_MASK 0x007F /* RETUNEDAC_SHARED_COEFF_23_16 - [6:0] */
+#define WM8962_RETUNEDAC_SHARED_COEFF_23_16_SHIFT 0 /* RETUNEDAC_SHARED_COEFF_23_16 - [6:0] */
+#define WM8962_RETUNEDAC_SHARED_COEFF_23_16_WIDTH 7 /* RETUNEDAC_SHARED_COEFF_23_16 - [6:0] */
+
+/*
+ * R16387 (0x4003) - RETUNEDAC_SHARED_COEFF_0
+ */
+#define WM8962_RETUNEDAC_SHARED_COEFF_15_00_MASK 0xFFFF /* RETUNEDAC_SHARED_COEFF_15_00 - [15:0] */
+#define WM8962_RETUNEDAC_SHARED_COEFF_15_00_SHIFT 0 /* RETUNEDAC_SHARED_COEFF_15_00 - [15:0] */
+#define WM8962_RETUNEDAC_SHARED_COEFF_15_00_WIDTH 16 /* RETUNEDAC_SHARED_COEFF_15_00 - [15:0] */
+
+/*
+ * R16388 (0x4004) - SOUNDSTAGE_ENABLES_1
+ */
+#define WM8962_SOUNDSTAGE_ENABLES_23_16_MASK 0x00FF /* SOUNDSTAGE_ENABLES_23_16 - [7:0] */
+#define WM8962_SOUNDSTAGE_ENABLES_23_16_SHIFT 0 /* SOUNDSTAGE_ENABLES_23_16 - [7:0] */
+#define WM8962_SOUNDSTAGE_ENABLES_23_16_WIDTH 8 /* SOUNDSTAGE_ENABLES_23_16 - [7:0] */
+
+/*
+ * R16389 (0x4005) - SOUNDSTAGE_ENABLES_0
+ */
+#define WM8962_SOUNDSTAGE_ENABLES_15_06_MASK 0xFFC0 /* SOUNDSTAGE_ENABLES_15_06 - [15:6] */
+#define WM8962_SOUNDSTAGE_ENABLES_15_06_SHIFT 6 /* SOUNDSTAGE_ENABLES_15_06 - [15:6] */
+#define WM8962_SOUNDSTAGE_ENABLES_15_06_WIDTH 10 /* SOUNDSTAGE_ENABLES_15_06 - [15:6] */
+#define WM8962_RTN_ADC_ENA 0x0020 /* RTN_ADC_ENA */
+#define WM8962_RTN_ADC_ENA_MASK 0x0020 /* RTN_ADC_ENA */
+#define WM8962_RTN_ADC_ENA_SHIFT 5 /* RTN_ADC_ENA */
+#define WM8962_RTN_ADC_ENA_WIDTH 1 /* RTN_ADC_ENA */
+#define WM8962_RTN_DAC_ENA 0x0010 /* RTN_DAC_ENA */
+#define WM8962_RTN_DAC_ENA_MASK 0x0010 /* RTN_DAC_ENA */
+#define WM8962_RTN_DAC_ENA_SHIFT 4 /* RTN_DAC_ENA */
+#define WM8962_RTN_DAC_ENA_WIDTH 1 /* RTN_DAC_ENA */
+#define WM8962_HDBASS_ENA 0x0008 /* HDBASS_ENA */
+#define WM8962_HDBASS_ENA_MASK 0x0008 /* HDBASS_ENA */
+#define WM8962_HDBASS_ENA_SHIFT 3 /* HDBASS_ENA */
+#define WM8962_HDBASS_ENA_WIDTH 1 /* HDBASS_ENA */
+#define WM8962_HPF2_ENA 0x0004 /* HPF2_ENA */
+#define WM8962_HPF2_ENA_MASK 0x0004 /* HPF2_ENA */
+#define WM8962_HPF2_ENA_SHIFT 2 /* HPF2_ENA */
+#define WM8962_HPF2_ENA_WIDTH 1 /* HPF2_ENA */
+#define WM8962_HPF1_ENA 0x0002 /* HPF1_ENA */
+#define WM8962_HPF1_ENA_MASK 0x0002 /* HPF1_ENA */
+#define WM8962_HPF1_ENA_SHIFT 1 /* HPF1_ENA */
+#define WM8962_HPF1_ENA_WIDTH 1 /* HPF1_ENA */
+#define WM8962_VSS_ENA 0x0001 /* VSS_ENA */
+#define WM8962_VSS_ENA_MASK 0x0001 /* VSS_ENA */
+#define WM8962_VSS_ENA_SHIFT 0 /* VSS_ENA */
+#define WM8962_VSS_ENA_WIDTH 1 /* VSS_ENA */
+
+int wm8962_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack);
+
+#endif
diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c
new file mode 100644
index 00000000..572bb806
--- /dev/null
+++ b/sound/soc/codecs/wm8971.c
@@ -0,0 +1,781 @@
+/*
+ * wm8971.c -- WM8971 ALSA SoC Audio driver
+ *
+ * Copyright 2005 Lab126, Inc.
+ *
+ * Author: Kenneth Kiraly <kiraly@lab126.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "wm8971.h"
+
+#define WM8971_REG_COUNT 43
+
+static struct workqueue_struct *wm8971_workq = NULL;
+
+/* codec private data */
+struct wm8971_priv {
+ enum snd_soc_control_type control_type;
+ unsigned int sysclk;
+};
+
+/*
+ * wm8971 register cache
+ * We can't read the WM8971 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8971_reg[] = {
+ 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */
+ 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */
+ 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */
+ 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */
+ 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */
+ 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */
+ 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */
+ 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */
+ 0x0079, 0x0079, 0x0079, /* 40 */
+};
+
+#define wm8971_reset(c) snd_soc_write(c, WM8971_RESET, 0)
+
+/* WM8971 Controls */
+static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" };
+static const char *wm8971_bass_filter[] = { "130Hz @ 48kHz",
+ "200Hz @ 48kHz" };
+static const char *wm8971_treble[] = { "8kHz", "4kHz" };
+static const char *wm8971_alc_func[] = { "Off", "Right", "Left", "Stereo" };
+static const char *wm8971_ng_type[] = { "Constant PGA Gain",
+ "Mute ADC Output" };
+static const char *wm8971_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8971_mono_mux[] = {"Stereo", "Mono (Left)",
+ "Mono (Right)", "Digital Mono"};
+static const char *wm8971_dac_phase[] = { "Non Inverted", "Inverted" };
+static const char *wm8971_lline_mux[] = {"Line", "NC", "NC", "PGA",
+ "Differential"};
+static const char *wm8971_rline_mux[] = {"Line", "Mic", "NC", "PGA",
+ "Differential"};
+static const char *wm8971_lpga_sel[] = {"Line", "NC", "NC", "Differential"};
+static const char *wm8971_rpga_sel[] = {"Line", "Mic", "NC", "Differential"};
+static const char *wm8971_adcpol[] = {"Normal", "L Invert", "R Invert",
+ "L + R Invert"};
+
+static const struct soc_enum wm8971_enum[] = {
+ SOC_ENUM_SINGLE(WM8971_BASS, 7, 2, wm8971_bass), /* 0 */
+ SOC_ENUM_SINGLE(WM8971_BASS, 6, 2, wm8971_bass_filter),
+ SOC_ENUM_SINGLE(WM8971_TREBLE, 6, 2, wm8971_treble),
+ SOC_ENUM_SINGLE(WM8971_ALC1, 7, 4, wm8971_alc_func),
+ SOC_ENUM_SINGLE(WM8971_NGATE, 1, 2, wm8971_ng_type), /* 4 */
+ SOC_ENUM_SINGLE(WM8971_ADCDAC, 1, 4, wm8971_deemp),
+ SOC_ENUM_SINGLE(WM8971_ADCTL1, 4, 4, wm8971_mono_mux),
+ SOC_ENUM_SINGLE(WM8971_ADCTL1, 1, 2, wm8971_dac_phase),
+ SOC_ENUM_SINGLE(WM8971_LOUTM1, 0, 5, wm8971_lline_mux), /* 8 */
+ SOC_ENUM_SINGLE(WM8971_ROUTM1, 0, 5, wm8971_rline_mux),
+ SOC_ENUM_SINGLE(WM8971_LADCIN, 6, 4, wm8971_lpga_sel),
+ SOC_ENUM_SINGLE(WM8971_RADCIN, 6, 4, wm8971_rpga_sel),
+ SOC_ENUM_SINGLE(WM8971_ADCDAC, 5, 4, wm8971_adcpol), /* 12 */
+ SOC_ENUM_SINGLE(WM8971_ADCIN, 6, 4, wm8971_mono_mux),
+};
+
+static const struct snd_kcontrol_new wm8971_snd_controls[] = {
+ SOC_DOUBLE_R("Capture Volume", WM8971_LINVOL, WM8971_RINVOL, 0, 63, 0),
+ SOC_DOUBLE_R("Capture ZC Switch", WM8971_LINVOL, WM8971_RINVOL,
+ 6, 1, 0),
+ SOC_DOUBLE_R("Capture Switch", WM8971_LINVOL, WM8971_RINVOL, 7, 1, 1),
+
+ SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8971_LOUT1V,
+ WM8971_ROUT1V, 7, 1, 0),
+ SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8971_LOUT2V,
+ WM8971_ROUT2V, 7, 1, 0),
+ SOC_SINGLE("Mono Playback ZC Switch", WM8971_MOUTV, 7, 1, 0),
+
+ SOC_DOUBLE_R("PCM Volume", WM8971_LDAC, WM8971_RDAC, 0, 255, 0),
+
+ SOC_DOUBLE_R("Bypass Left Playback Volume", WM8971_LOUTM1,
+ WM8971_LOUTM2, 4, 7, 1),
+ SOC_DOUBLE_R("Bypass Right Playback Volume", WM8971_ROUTM1,
+ WM8971_ROUTM2, 4, 7, 1),
+ SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8971_MOUTM1,
+ WM8971_MOUTM2, 4, 7, 1),
+
+ SOC_DOUBLE_R("Headphone Playback Volume", WM8971_LOUT1V,
+ WM8971_ROUT1V, 0, 127, 0),
+ SOC_DOUBLE_R("Speaker Playback Volume", WM8971_LOUT2V,
+ WM8971_ROUT2V, 0, 127, 0),
+
+ SOC_ENUM("Bass Boost", wm8971_enum[0]),
+ SOC_ENUM("Bass Filter", wm8971_enum[1]),
+ SOC_SINGLE("Bass Volume", WM8971_BASS, 0, 7, 1),
+
+ SOC_SINGLE("Treble Volume", WM8971_TREBLE, 0, 7, 0),
+ SOC_ENUM("Treble Cut-off", wm8971_enum[2]),
+
+ SOC_SINGLE("Capture Filter Switch", WM8971_ADCDAC, 0, 1, 1),
+
+ SOC_SINGLE("ALC Target Volume", WM8971_ALC1, 0, 7, 0),
+ SOC_SINGLE("ALC Max Volume", WM8971_ALC1, 4, 7, 0),
+
+ SOC_SINGLE("ALC Capture Target Volume", WM8971_ALC1, 0, 7, 0),
+ SOC_SINGLE("ALC Capture Max Volume", WM8971_ALC1, 4, 7, 0),
+ SOC_ENUM("ALC Capture Function", wm8971_enum[3]),
+ SOC_SINGLE("ALC Capture ZC Switch", WM8971_ALC2, 7, 1, 0),
+ SOC_SINGLE("ALC Capture Hold Time", WM8971_ALC2, 0, 15, 0),
+ SOC_SINGLE("ALC Capture Decay Time", WM8971_ALC3, 4, 15, 0),
+ SOC_SINGLE("ALC Capture Attack Time", WM8971_ALC3, 0, 15, 0),
+ SOC_SINGLE("ALC Capture NG Threshold", WM8971_NGATE, 3, 31, 0),
+ SOC_ENUM("ALC Capture NG Type", wm8971_enum[4]),
+ SOC_SINGLE("ALC Capture NG Switch", WM8971_NGATE, 0, 1, 0),
+
+ SOC_SINGLE("Capture 6dB Attenuate", WM8971_ADCDAC, 8, 1, 0),
+ SOC_SINGLE("Playback 6dB Attenuate", WM8971_ADCDAC, 7, 1, 0),
+
+ SOC_ENUM("Playback De-emphasis", wm8971_enum[5]),
+ SOC_ENUM("Playback Function", wm8971_enum[6]),
+ SOC_ENUM("Playback Phase", wm8971_enum[7]),
+
+ SOC_DOUBLE_R("Mic Boost", WM8971_LADCIN, WM8971_RADCIN, 4, 3, 0),
+};
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8971_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8971_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_LOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8971_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8971_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8971_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_ROUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8971_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_ROUTM2, 7, 1, 0),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm8971_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8971_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_MOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8971_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_MOUTM2, 7, 1, 0),
+};
+
+/* Left Line Mux */
+static const struct snd_kcontrol_new wm8971_left_line_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[8]);
+
+/* Right Line Mux */
+static const struct snd_kcontrol_new wm8971_right_line_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[9]);
+
+/* Left PGA Mux */
+static const struct snd_kcontrol_new wm8971_left_pga_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[10]);
+
+/* Right PGA Mux */
+static const struct snd_kcontrol_new wm8971_right_pga_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[11]);
+
+/* Mono ADC Mux */
+static const struct snd_kcontrol_new wm8971_monomux_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[13]);
+
+static const struct snd_soc_dapm_widget wm8971_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8971_left_mixer_controls[0],
+ ARRAY_SIZE(wm8971_left_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8971_right_mixer_controls[0],
+ ARRAY_SIZE(wm8971_right_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono Mixer", WM8971_PWR2, 2, 0,
+ &wm8971_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8971_mono_mixer_controls)),
+
+ SND_SOC_DAPM_PGA("Right Out 2", WM8971_PWR2, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 2", WM8971_PWR2, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Out 1", WM8971_PWR2, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 1", WM8971_PWR2, 6, 0, NULL, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8971_PWR2, 7, 0),
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8971_PWR2, 8, 0),
+ SND_SOC_DAPM_PGA("Mono Out 1", WM8971_PWR2, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8971_PWR1, 1, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8971_PWR1, 2, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8971_PWR1, 3, 0),
+
+ SND_SOC_DAPM_MUX("Left PGA Mux", WM8971_PWR1, 5, 0,
+ &wm8971_left_pga_controls),
+ SND_SOC_DAPM_MUX("Right PGA Mux", WM8971_PWR1, 4, 0,
+ &wm8971_right_pga_controls),
+ SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_left_line_controls),
+ SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_right_line_controls),
+
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_monomux_controls),
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_monomux_controls),
+
+ SND_SOC_DAPM_OUTPUT("LOUT1"),
+ SND_SOC_DAPM_OUTPUT("ROUT1"),
+ SND_SOC_DAPM_OUTPUT("LOUT2"),
+ SND_SOC_DAPM_OUTPUT("ROUT2"),
+ SND_SOC_DAPM_OUTPUT("MONO"),
+
+ SND_SOC_DAPM_INPUT("LINPUT1"),
+ SND_SOC_DAPM_INPUT("RINPUT1"),
+ SND_SOC_DAPM_INPUT("MIC"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* left mixer */
+ {"Left Mixer", "Playback Switch", "Left DAC"},
+ {"Left Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Left Mixer", "Right Playback Switch", "Right DAC"},
+ {"Left Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* right mixer */
+ {"Right Mixer", "Left Playback Switch", "Left DAC"},
+ {"Right Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Right Mixer", "Playback Switch", "Right DAC"},
+ {"Right Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* left out 1 */
+ {"Left Out 1", NULL, "Left Mixer"},
+ {"LOUT1", NULL, "Left Out 1"},
+
+ /* left out 2 */
+ {"Left Out 2", NULL, "Left Mixer"},
+ {"LOUT2", NULL, "Left Out 2"},
+
+ /* right out 1 */
+ {"Right Out 1", NULL, "Right Mixer"},
+ {"ROUT1", NULL, "Right Out 1"},
+
+ /* right out 2 */
+ {"Right Out 2", NULL, "Right Mixer"},
+ {"ROUT2", NULL, "Right Out 2"},
+
+ /* mono mixer */
+ {"Mono Mixer", "Left Playback Switch", "Left DAC"},
+ {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Mono Mixer", "Right Playback Switch", "Right DAC"},
+ {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* mono out */
+ {"Mono Out", NULL, "Mono Mixer"},
+ {"MONO1", NULL, "Mono Out"},
+
+ /* Left Line Mux */
+ {"Left Line Mux", "Line", "LINPUT1"},
+ {"Left Line Mux", "PGA", "Left PGA Mux"},
+ {"Left Line Mux", "Differential", "Differential Mux"},
+
+ /* Right Line Mux */
+ {"Right Line Mux", "Line", "RINPUT1"},
+ {"Right Line Mux", "Mic", "MIC"},
+ {"Right Line Mux", "PGA", "Right PGA Mux"},
+ {"Right Line Mux", "Differential", "Differential Mux"},
+
+ /* Left PGA Mux */
+ {"Left PGA Mux", "Line", "LINPUT1"},
+ {"Left PGA Mux", "Differential", "Differential Mux"},
+
+ /* Right PGA Mux */
+ {"Right PGA Mux", "Line", "RINPUT1"},
+ {"Right PGA Mux", "Differential", "Differential Mux"},
+
+ /* Differential Mux */
+ {"Differential Mux", "Line", "LINPUT1"},
+ {"Differential Mux", "Line", "RINPUT1"},
+
+ /* Left ADC Mux */
+ {"Left ADC Mux", "Stereo", "Left PGA Mux"},
+ {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"},
+ {"Left ADC Mux", "Digital Mono", "Left PGA Mux"},
+
+ /* Right ADC Mux */
+ {"Right ADC Mux", "Stereo", "Right PGA Mux"},
+ {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"},
+ {"Right ADC Mux", "Digital Mono", "Right PGA Mux"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Left ADC Mux"},
+ {"Right ADC", NULL, "Right ADC Mux"},
+};
+
+static int wm8971_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8971_dapm_widgets,
+ ARRAY_SIZE(wm8971_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:5;
+ u8 usb:1;
+};
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 8k */
+ {12288000, 8000, 1536, 0x6, 0x0},
+ {11289600, 8000, 1408, 0x16, 0x0},
+ {18432000, 8000, 2304, 0x7, 0x0},
+ {16934400, 8000, 2112, 0x17, 0x0},
+ {12000000, 8000, 1500, 0x6, 0x1},
+
+ /* 11.025k */
+ {11289600, 11025, 1024, 0x18, 0x0},
+ {16934400, 11025, 1536, 0x19, 0x0},
+ {12000000, 11025, 1088, 0x19, 0x1},
+
+ /* 16k */
+ {12288000, 16000, 768, 0xa, 0x0},
+ {18432000, 16000, 1152, 0xb, 0x0},
+ {12000000, 16000, 750, 0xa, 0x1},
+
+ /* 22.05k */
+ {11289600, 22050, 512, 0x1a, 0x0},
+ {16934400, 22050, 768, 0x1b, 0x0},
+ {12000000, 22050, 544, 0x1b, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0xc, 0x0},
+ {18432000, 32000, 576, 0xd, 0x0},
+ {12000000, 32000, 375, 0xa, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x10, 0x0},
+ {16934400, 44100, 384, 0x11, 0x0},
+ {12000000, 44100, 272, 0x11, 0x1},
+
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0},
+ {18432000, 48000, 384, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0x1e, 0x0},
+ {16934400, 88200, 192, 0x1f, 0x0},
+ {12000000, 88200, 136, 0x1f, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0xe, 0x0},
+ {18432000, 96000, 192, 0xf, 0x0},
+ {12000000, 96000, 125, 0xe, 0x1},
+};
+
+static int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return -EINVAL;
+}
+
+static int wm8971_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8971->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface = 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8971_IFACE, iface);
+ return 0;
+}
+
+static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec);
+ u16 iface = snd_soc_read(codec, WM8971_IFACE) & 0x1f3;
+ u16 srate = snd_soc_read(codec, WM8971_SRATE) & 0x1c0;
+ int coeff = get_coeff(wm8971->sysclk, params_rate(params));
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+
+ /* set iface & srate */
+ snd_soc_write(codec, WM8971_IFACE, iface);
+ if (coeff >= 0)
+ snd_soc_write(codec, WM8971_SRATE, srate |
+ (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+ return 0;
+}
+
+static int wm8971_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8971_ADCDAC) & 0xfff7;
+
+ if (mute)
+ snd_soc_write(codec, WM8971_ADCDAC, mute_reg | 0x8);
+ else
+ snd_soc_write(codec, WM8971_ADCDAC, mute_reg);
+ return 0;
+}
+
+static int wm8971_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 pwr_reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* set vmid to 50k and unmute dac */
+ snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x00c1);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* mute dac and set vmid to 500k, enable VREF */
+ snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x0140);
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, WM8971_PWR1, 0x0001);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8971_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8971_dai_ops = {
+ .hw_params = wm8971_pcm_hw_params,
+ .digital_mute = wm8971_mute,
+ .set_fmt = wm8971_set_dai_fmt,
+ .set_sysclk = wm8971_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8971_dai = {
+ .name = "wm8971-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8971_RATES,
+ .formats = WM8971_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8971_RATES,
+ .formats = WM8971_FORMATS,},
+ .ops = &wm8971_dai_ops,
+};
+
+static void wm8971_work(struct work_struct *work)
+{
+ struct snd_soc_dapm_context *dapm =
+ container_of(work, struct snd_soc_dapm_context,
+ delayed_work.work);
+ struct snd_soc_codec *codec = dapm->codec;
+ wm8971_set_bias_level(codec, codec->dapm.bias_level);
+}
+
+static int wm8971_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8971_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+ u16 reg;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8971_reg); i++) {
+ if (i + 1 == WM8971_RESET)
+ continue;
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge wm8971 caps */
+ if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) {
+ reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
+ snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0);
+ codec->dapm.bias_level = SND_SOC_BIAS_ON;
+ queue_delayed_work(wm8971_workq, &codec->dapm.delayed_work,
+ msecs_to_jiffies(1000));
+ }
+
+ return 0;
+}
+
+static int wm8971_probe(struct snd_soc_codec *codec)
+{
+ struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+ u16 reg;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8971->control_type);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8971: failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ INIT_DELAYED_WORK(&codec->dapm.delayed_work, wm8971_work);
+ wm8971_workq = create_workqueue("wm8971");
+ if (wm8971_workq == NULL)
+ return -ENOMEM;
+
+ wm8971_reset(codec);
+
+ /* charge output caps - set vmid to 5k for quick power up */
+ reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
+ snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0);
+ codec->dapm.bias_level = SND_SOC_BIAS_STANDBY;
+ queue_delayed_work(wm8971_workq, &codec->dapm.delayed_work,
+ msecs_to_jiffies(1000));
+
+ /* set the update bits */
+ reg = snd_soc_read(codec, WM8971_LDAC);
+ snd_soc_write(codec, WM8971_LDAC, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8971_RDAC);
+ snd_soc_write(codec, WM8971_RDAC, reg | 0x0100);
+
+ reg = snd_soc_read(codec, WM8971_LOUT1V);
+ snd_soc_write(codec, WM8971_LOUT1V, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8971_ROUT1V);
+ snd_soc_write(codec, WM8971_ROUT1V, reg | 0x0100);
+
+ reg = snd_soc_read(codec, WM8971_LOUT2V);
+ snd_soc_write(codec, WM8971_LOUT2V, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8971_ROUT2V);
+ snd_soc_write(codec, WM8971_ROUT2V, reg | 0x0100);
+
+ reg = snd_soc_read(codec, WM8971_LINVOL);
+ snd_soc_write(codec, WM8971_LINVOL, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8971_RINVOL);
+ snd_soc_write(codec, WM8971_RINVOL, reg | 0x0100);
+
+ snd_soc_add_controls(codec, wm8971_snd_controls,
+ ARRAY_SIZE(wm8971_snd_controls));
+ wm8971_add_widgets(codec);
+
+ return ret;
+}
+
+
+/* power down chip */
+static int wm8971_remove(struct snd_soc_codec *codec)
+{
+ wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ if (wm8971_workq)
+ destroy_workqueue(wm8971_workq);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8971 = {
+ .probe = wm8971_probe,
+ .remove = wm8971_remove,
+ .suspend = wm8971_suspend,
+ .resume = wm8971_resume,
+ .set_bias_level = wm8971_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8971_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8971_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8971_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8971_priv *wm8971;
+ int ret;
+
+ wm8971 = kzalloc(sizeof(struct wm8971_priv), GFP_KERNEL);
+ if (wm8971 == NULL)
+ return -ENOMEM;
+
+ wm8971->control_type = SND_SOC_I2C;
+ i2c_set_clientdata(i2c, wm8971);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8971, &wm8971_dai, 1);
+ if (ret < 0)
+ kfree(wm8971);
+ return ret;
+}
+
+static __devexit int wm8971_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8971_i2c_id[] = {
+ { "wm8971", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8971_i2c_id);
+
+static struct i2c_driver wm8971_i2c_driver = {
+ .driver = {
+ .name = "wm8971-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8971_i2c_probe,
+ .remove = __devexit_p(wm8971_i2c_remove),
+ .id_table = wm8971_i2c_id,
+};
+#endif
+
+static int __init wm8971_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8971_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8971 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8971_modinit);
+
+static void __exit wm8971_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8971_i2c_driver);
+#endif
+}
+module_exit(wm8971_exit);
+
+MODULE_DESCRIPTION("ASoC WM8971 driver");
+MODULE_AUTHOR("Lab126");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8971.h b/sound/soc/codecs/wm8971.h
new file mode 100644
index 00000000..f31c38fd
--- /dev/null
+++ b/sound/soc/codecs/wm8971.h
@@ -0,0 +1,56 @@
+/*
+ * wm8971.h -- audio driver for WM8971
+ *
+ * Copyright 2005 Lab126, Inc.
+ *
+ * Author: Kenneth Kiraly <kiraly@lab126.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _WM8971_H
+#define _WM8971_H
+
+#define WM8971_LINVOL 0x00
+#define WM8971_RINVOL 0x01
+#define WM8971_LOUT1V 0x02
+#define WM8971_ROUT1V 0x03
+#define WM8971_ADCDAC 0x05
+#define WM8971_IFACE 0x07
+#define WM8971_SRATE 0x08
+#define WM8971_LDAC 0x0a
+#define WM8971_RDAC 0x0b
+#define WM8971_BASS 0x0c
+#define WM8971_TREBLE 0x0d
+#define WM8971_RESET 0x0f
+#define WM8971_ALC1 0x11
+#define WM8971_ALC2 0x12
+#define WM8971_ALC3 0x13
+#define WM8971_NGATE 0x14
+#define WM8971_LADC 0x15
+#define WM8971_RADC 0x16
+#define WM8971_ADCTL1 0x17
+#define WM8971_ADCTL2 0x18
+#define WM8971_PWR1 0x19
+#define WM8971_PWR2 0x1a
+#define WM8971_ADCTL3 0x1b
+#define WM8971_ADCIN 0x1f
+#define WM8971_LADCIN 0x20
+#define WM8971_RADCIN 0x21
+#define WM8971_LOUTM1 0x22
+#define WM8971_LOUTM2 0x23
+#define WM8971_ROUTM1 0x24
+#define WM8971_ROUTM2 0x25
+#define WM8971_MOUTM1 0x26
+#define WM8971_MOUTM2 0x27
+#define WM8971_LOUT2V 0x28
+#define WM8971_ROUT2V 0x29
+#define WM8971_MOUTV 0x2A
+
+#define WM8971_SYSCLK 0
+
+#endif
diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c
new file mode 100644
index 00000000..ca646a82
--- /dev/null
+++ b/sound/soc/codecs/wm8974.c
@@ -0,0 +1,717 @@
+/*
+ * wm8974.c -- WM8974 ALSA Soc Audio driver
+ *
+ * Copyright 2006-2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood <linux@wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8974.h"
+
+static const u16 wm8974_reg[WM8974_CACHEREGNUM] = {
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0050, 0x0000, 0x0140, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x00ff,
+ 0x0000, 0x0000, 0x0100, 0x00ff,
+ 0x0000, 0x0000, 0x012c, 0x002c,
+ 0x002c, 0x002c, 0x002c, 0x0000,
+ 0x0032, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0038, 0x000b, 0x0032, 0x0000,
+ 0x0008, 0x000c, 0x0093, 0x00e9,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0003, 0x0010, 0x0000, 0x0000,
+ 0x0000, 0x0002, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0039, 0x0000,
+ 0x0000,
+};
+
+#define WM8974_POWER1_BIASEN 0x08
+#define WM8974_POWER1_BUFIOEN 0x04
+
+struct wm8974_priv {
+ enum snd_soc_control_type control_type;
+};
+
+#define wm8974_reset(c) snd_soc_write(c, WM8974_RESET, 0)
+
+static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" };
+static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8974_eqmode[] = {"Capture", "Playback" };
+static const char *wm8974_bw[] = {"Narrow", "Wide" };
+static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" };
+static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" };
+static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" };
+static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" };
+static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" };
+static const char *wm8974_alc[] = {"ALC", "Limiter" };
+
+static const struct soc_enum wm8974_enum[] = {
+ SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */
+ SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */
+ SOC_ENUM_SINGLE(WM8974_DAC, 4, 4, wm8974_deemp),
+ SOC_ENUM_SINGLE(WM8974_EQ1, 8, 2, wm8974_eqmode),
+
+ SOC_ENUM_SINGLE(WM8974_EQ1, 5, 4, wm8974_eq1),
+ SOC_ENUM_SINGLE(WM8974_EQ2, 8, 2, wm8974_bw),
+ SOC_ENUM_SINGLE(WM8974_EQ2, 5, 4, wm8974_eq2),
+ SOC_ENUM_SINGLE(WM8974_EQ3, 8, 2, wm8974_bw),
+
+ SOC_ENUM_SINGLE(WM8974_EQ3, 5, 4, wm8974_eq3),
+ SOC_ENUM_SINGLE(WM8974_EQ4, 8, 2, wm8974_bw),
+ SOC_ENUM_SINGLE(WM8974_EQ4, 5, 4, wm8974_eq4),
+ SOC_ENUM_SINGLE(WM8974_EQ5, 8, 2, wm8974_bw),
+
+ SOC_ENUM_SINGLE(WM8974_EQ5, 5, 4, wm8974_eq5),
+ SOC_ENUM_SINGLE(WM8974_ALC3, 8, 2, wm8974_alc),
+};
+
+static const char *wm8974_auxmode_text[] = { "Buffer", "Mixer" };
+
+static const struct soc_enum wm8974_auxmode =
+ SOC_ENUM_SINGLE(WM8974_INPUT, 3, 2, wm8974_auxmode_text);
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
+static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
+
+static const struct snd_kcontrol_new wm8974_snd_controls[] = {
+
+SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0),
+
+SOC_ENUM("DAC Companding", wm8974_enum[1]),
+SOC_ENUM("ADC Companding", wm8974_enum[0]),
+
+SOC_ENUM("Playback De-emphasis", wm8974_enum[2]),
+SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0),
+
+SOC_SINGLE_TLV("PCM Volume", WM8974_DACVOL, 0, 255, 0, digital_tlv),
+
+SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0),
+SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0),
+SOC_SINGLE("ADC Inversion Switch", WM8974_ADC, 0, 1, 0),
+
+SOC_SINGLE_TLV("Capture Volume", WM8974_ADCVOL, 0, 255, 0, digital_tlv),
+
+SOC_ENUM("Equaliser Function", wm8974_enum[3]),
+SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]),
+SOC_SINGLE_TLV("EQ1 Volume", WM8974_EQ1, 0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ2 Bandwith", wm8974_enum[5]),
+SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]),
+SOC_SINGLE_TLV("EQ2 Volume", WM8974_EQ2, 0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ3 Bandwith", wm8974_enum[7]),
+SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]),
+SOC_SINGLE_TLV("EQ3 Volume", WM8974_EQ3, 0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ4 Bandwith", wm8974_enum[9]),
+SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]),
+SOC_SINGLE_TLV("EQ4 Volume", WM8974_EQ4, 0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ5 Bandwith", wm8974_enum[11]),
+SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]),
+SOC_SINGLE_TLV("EQ5 Volume", WM8974_EQ5, 0, 24, 1, eq_tlv),
+
+SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1, 8, 1, 0),
+SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1, 4, 15, 0),
+SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1, 0, 15, 0),
+
+SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2, 4, 7, 0),
+SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2, 0, 15, 0),
+
+SOC_SINGLE("ALC Enable Switch", WM8974_ALC1, 8, 1, 0),
+SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1, 3, 7, 0),
+SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1, 0, 7, 0),
+
+SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2, 8, 1, 0),
+SOC_SINGLE("ALC Capture Hold", WM8974_ALC2, 4, 7, 0),
+SOC_SINGLE("ALC Capture Target", WM8974_ALC2, 0, 15, 0),
+
+SOC_ENUM("ALC Capture Mode", wm8974_enum[13]),
+SOC_SINGLE("ALC Capture Decay", WM8974_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack", WM8974_ALC3, 0, 15, 0),
+
+SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE, 3, 1, 0),
+SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE, 0, 7, 0),
+
+SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA, 7, 1, 0),
+SOC_SINGLE_TLV("Capture PGA Volume", WM8974_INPPGA, 0, 63, 0, inpga_tlv),
+
+SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL, 7, 1, 0),
+SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL, 6, 1, 1),
+SOC_SINGLE_TLV("Speaker Playback Volume", WM8974_SPKVOL, 0, 63, 0, spk_tlv),
+
+SOC_ENUM("Aux Mode", wm8974_auxmode),
+
+SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST, 8, 1, 0),
+SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 1),
+
+/* DAC / ADC oversampling */
+SOC_SINGLE("DAC 128x Oversampling Switch", WM8974_DAC, 8, 1, 0),
+SOC_SINGLE("ADC 128x Oversampling Switch", WM8974_ADC, 8, 1, 0),
+};
+
+/* Speaker Output Mixer */
+static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 0),
+};
+
+/* Mono Output Mixer */
+static const struct snd_kcontrol_new wm8974_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_MONOMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_MONOMIX, 2, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 0),
+};
+
+/* Boost mixer */
+static const struct snd_kcontrol_new wm8974_boost_mixer[] = {
+SOC_DAPM_SINGLE("Aux Switch", WM8974_INPPGA, 6, 1, 0),
+};
+
+/* Input PGA */
+static const struct snd_kcontrol_new wm8974_inpga[] = {
+SOC_DAPM_SINGLE("Aux Switch", WM8974_INPUT, 2, 1, 0),
+SOC_DAPM_SINGLE("MicN Switch", WM8974_INPUT, 1, 1, 0),
+SOC_DAPM_SINGLE("MicP Switch", WM8974_INPUT, 0, 1, 0),
+};
+
+/* AUX Input boost vol */
+static const struct snd_kcontrol_new wm8974_aux_boost_controls =
+SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0);
+
+/* Mic Input boost vol */
+static const struct snd_kcontrol_new wm8974_mic_boost_controls =
+SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0);
+
+static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0,
+ &wm8974_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm8974_speaker_mixer_controls)),
+SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0,
+ &wm8974_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8974_mono_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER2, 0, 0),
+SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Input PGA", WM8974_POWER2, 2, 0, wm8974_inpga,
+ ARRAY_SIZE(wm8974_inpga)),
+SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0,
+ wm8974_boost_mixer, ARRAY_SIZE(wm8974_boost_mixer)),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8974_POWER1, 4, 0),
+
+SND_SOC_DAPM_INPUT("MICN"),
+SND_SOC_DAPM_INPUT("MICP"),
+SND_SOC_DAPM_INPUT("AUX"),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("SPKOUTP"),
+SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Mono output mixer */
+ {"Mono Mixer", "PCM Playback Switch", "DAC"},
+ {"Mono Mixer", "Aux Playback Switch", "Aux Input"},
+ {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+ /* Speaker output mixer */
+ {"Speaker Mixer", "PCM Playback Switch", "DAC"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
+ {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+ /* Outputs */
+ {"Mono Out", NULL, "Mono Mixer"},
+ {"MONOOUT", NULL, "Mono Out"},
+ {"SpkN Out", NULL, "Speaker Mixer"},
+ {"SpkP Out", NULL, "Speaker Mixer"},
+ {"SPKOUTN", NULL, "SpkN Out"},
+ {"SPKOUTP", NULL, "SpkP Out"},
+
+ /* Boost Mixer */
+ {"ADC", NULL, "Boost Mixer"},
+ {"Boost Mixer", "Aux Switch", "Aux Input"},
+ {"Boost Mixer", NULL, "Input PGA"},
+ {"Boost Mixer", NULL, "MICP"},
+
+ /* Input PGA */
+ {"Input PGA", "Aux Switch", "Aux Input"},
+ {"Input PGA", "MicN Switch", "MICN"},
+ {"Input PGA", "MicP Switch", "MICP"},
+
+ /* Inputs */
+ {"Aux Input", NULL, "AUX"},
+};
+
+static int wm8974_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8974_dapm_widgets,
+ ARRAY_SIZE(wm8974_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+struct pll_ {
+ unsigned int pre_div:1;
+ unsigned int n:4;
+ unsigned int k;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+
+static void pll_factors(struct pll_ *pll_div,
+ unsigned int target, unsigned int source)
+{
+ unsigned long long Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ /* There is a fixed divide by 4 in the output path */
+ target *= 4;
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source /= 2;
+ pll_div->pre_div = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->pre_div = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM8974 N value %u outwith recommended range!\n",
+ Ndiv);
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+}
+
+static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct pll_ pll_div;
+ u16 reg;
+
+ if (freq_in == 0 || freq_out == 0) {
+ /* Clock CODEC directly from MCLK */
+ reg = snd_soc_read(codec, WM8974_CLOCK);
+ snd_soc_write(codec, WM8974_CLOCK, reg & 0x0ff);
+
+ /* Turn off PLL */
+ reg = snd_soc_read(codec, WM8974_POWER1);
+ snd_soc_write(codec, WM8974_POWER1, reg & 0x1df);
+ return 0;
+ }
+
+ pll_factors(&pll_div, freq_out, freq_in);
+
+ snd_soc_write(codec, WM8974_PLLN, (pll_div.pre_div << 4) | pll_div.n);
+ snd_soc_write(codec, WM8974_PLLK1, pll_div.k >> 18);
+ snd_soc_write(codec, WM8974_PLLK2, (pll_div.k >> 9) & 0x1ff);
+ snd_soc_write(codec, WM8974_PLLK3, pll_div.k & 0x1ff);
+ reg = snd_soc_read(codec, WM8974_POWER1);
+ snd_soc_write(codec, WM8974_POWER1, reg | 0x020);
+
+ /* Run CODEC from PLL instead of MCLK */
+ reg = snd_soc_read(codec, WM8974_CLOCK);
+ snd_soc_write(codec, WM8974_CLOCK, reg | 0x100);
+
+ return 0;
+}
+
+/*
+ * Configure WM8974 clock dividers.
+ */
+static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8974_OPCLKDIV:
+ reg = snd_soc_read(codec, WM8974_GPIO) & 0x1cf;
+ snd_soc_write(codec, WM8974_GPIO, reg | div);
+ break;
+ case WM8974_MCLKDIV:
+ reg = snd_soc_read(codec, WM8974_CLOCK) & 0x11f;
+ snd_soc_write(codec, WM8974_CLOCK, reg | div);
+ break;
+ case WM8974_BCLKDIV:
+ reg = snd_soc_read(codec, WM8974_CLOCK) & 0x1e3;
+ snd_soc_write(codec, WM8974_CLOCK, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+ u16 clk = snd_soc_read(codec, WM8974_CLOCK) & 0x1fe;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ clk |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0010;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0008;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x00018;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0180;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0100;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0080;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8974_IFACE, iface);
+ snd_soc_write(codec, WM8974_CLOCK, clk);
+ return 0;
+}
+
+static int wm8974_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 iface = snd_soc_read(codec, WM8974_IFACE) & 0x19f;
+ u16 adn = snd_soc_read(codec, WM8974_ADD) & 0x1f1;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0020;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0040;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x0060;
+ break;
+ }
+
+ /* filter coefficient */
+ switch (params_rate(params)) {
+ case 8000:
+ adn |= 0x5 << 1;
+ break;
+ case 11025:
+ adn |= 0x4 << 1;
+ break;
+ case 16000:
+ adn |= 0x3 << 1;
+ break;
+ case 22050:
+ adn |= 0x2 << 1;
+ break;
+ case 32000:
+ adn |= 0x1 << 1;
+ break;
+ case 44100:
+ case 48000:
+ break;
+ }
+
+ snd_soc_write(codec, WM8974_IFACE, iface);
+ snd_soc_write(codec, WM8974_ADD, adn);
+ return 0;
+}
+
+static int wm8974_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8974_DAC) & 0xffbf;
+
+ if (mute)
+ snd_soc_write(codec, WM8974_DAC, mute_reg | 0x40);
+ else
+ snd_soc_write(codec, WM8974_DAC, mute_reg);
+ return 0;
+}
+
+/* liam need to make this lower power with dapm */
+static int wm8974_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 power1 = snd_soc_read(codec, WM8974_POWER1) & ~0x3;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ power1 |= 0x1; /* VMID 50k */
+ snd_soc_write(codec, WM8974_POWER1, power1);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ power1 |= WM8974_POWER1_BIASEN | WM8974_POWER1_BUFIOEN;
+
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Initial cap charge at VMID 5k */
+ snd_soc_write(codec, WM8974_POWER1, power1 | 0x3);
+ mdelay(100);
+ }
+
+ power1 |= 0x2; /* VMID 500k */
+ snd_soc_write(codec, WM8974_POWER1, power1);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, WM8974_POWER1, 0);
+ snd_soc_write(codec, WM8974_POWER2, 0);
+ snd_soc_write(codec, WM8974_POWER3, 0);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8974_RATES (SNDRV_PCM_RATE_8000_48000)
+
+#define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8974_ops = {
+ .hw_params = wm8974_pcm_hw_params,
+ .digital_mute = wm8974_mute,
+ .set_fmt = wm8974_set_dai_fmt,
+ .set_clkdiv = wm8974_set_dai_clkdiv,
+ .set_pll = wm8974_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver wm8974_dai = {
+ .name = "wm8974-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2, /* Only 1 channel of data */
+ .rates = WM8974_RATES,
+ .formats = WM8974_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2, /* Only 1 channel of data */
+ .rates = WM8974_RATES,
+ .formats = WM8974_FORMATS,},
+ .ops = &wm8974_ops,
+ .symmetric_rates = 1,
+};
+
+static int wm8974_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8974_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8974_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int wm8974_probe(struct snd_soc_codec *codec)
+{
+ int ret = 0;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_I2C);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8974_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ snd_soc_add_controls(codec, wm8974_snd_controls,
+ ARRAY_SIZE(wm8974_snd_controls));
+ wm8974_add_widgets(codec);
+
+ return ret;
+}
+
+/* power down chip */
+static int wm8974_remove(struct snd_soc_codec *codec)
+{
+ wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8974 = {
+ .probe = wm8974_probe,
+ .remove = wm8974_remove,
+ .suspend = wm8974_suspend,
+ .resume = wm8974_resume,
+ .set_bias_level = wm8974_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8974_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8974_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8974_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8974_priv *wm8974;
+ int ret;
+
+ wm8974 = kzalloc(sizeof(struct wm8974_priv), GFP_KERNEL);
+ if (wm8974 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8974);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8974, &wm8974_dai, 1);
+ if (ret < 0)
+ kfree(wm8974);
+ return ret;
+}
+
+static __devexit int wm8974_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8974_i2c_id[] = {
+ { "wm8974", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8974_i2c_id);
+
+static struct i2c_driver wm8974_i2c_driver = {
+ .driver = {
+ .name = "wm8974-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8974_i2c_probe,
+ .remove = __devexit_p(wm8974_i2c_remove),
+ .id_table = wm8974_i2c_id,
+};
+#endif
+
+static int __init wm8974_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8974_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8974 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8974_modinit);
+
+static void __exit wm8974_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8974_i2c_driver);
+#endif
+}
+module_exit(wm8974_exit);
+
+MODULE_DESCRIPTION("ASoC WM8974 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8974.h b/sound/soc/codecs/wm8974.h
new file mode 100644
index 00000000..3c94e7bb
--- /dev/null
+++ b/sound/soc/codecs/wm8974.h
@@ -0,0 +1,86 @@
+/*
+ * wm8974.h -- WM8974 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8974_H
+#define _WM8974_H
+
+/* WM8974 register space */
+
+#define WM8974_RESET 0x0
+#define WM8974_POWER1 0x1
+#define WM8974_POWER2 0x2
+#define WM8974_POWER3 0x3
+#define WM8974_IFACE 0x4
+#define WM8974_COMP 0x5
+#define WM8974_CLOCK 0x6
+#define WM8974_ADD 0x7
+#define WM8974_GPIO 0x8
+#define WM8974_DAC 0xa
+#define WM8974_DACVOL 0xb
+#define WM8974_ADC 0xe
+#define WM8974_ADCVOL 0xf
+#define WM8974_EQ1 0x12
+#define WM8974_EQ2 0x13
+#define WM8974_EQ3 0x14
+#define WM8974_EQ4 0x15
+#define WM8974_EQ5 0x16
+#define WM8974_DACLIM1 0x18
+#define WM8974_DACLIM2 0x19
+#define WM8974_NOTCH1 0x1b
+#define WM8974_NOTCH2 0x1c
+#define WM8974_NOTCH3 0x1d
+#define WM8974_NOTCH4 0x1e
+#define WM8974_ALC1 0x20
+#define WM8974_ALC2 0x21
+#define WM8974_ALC3 0x22
+#define WM8974_NGATE 0x23
+#define WM8974_PLLN 0x24
+#define WM8974_PLLK1 0x25
+#define WM8974_PLLK2 0x26
+#define WM8974_PLLK3 0x27
+#define WM8974_ATTEN 0x28
+#define WM8974_INPUT 0x2c
+#define WM8974_INPPGA 0x2d
+#define WM8974_ADCBOOST 0x2f
+#define WM8974_OUTPUT 0x31
+#define WM8974_SPKMIX 0x32
+#define WM8974_SPKVOL 0x36
+#define WM8974_MONOMIX 0x38
+
+#define WM8974_CACHEREGNUM 57
+
+/* Clock divider Id's */
+#define WM8974_OPCLKDIV 0
+#define WM8974_MCLKDIV 1
+#define WM8974_BCLKDIV 2
+
+/* PLL Out dividers */
+#define WM8974_OPCLKDIV_1 (0 << 4)
+#define WM8974_OPCLKDIV_2 (1 << 4)
+#define WM8974_OPCLKDIV_3 (2 << 4)
+#define WM8974_OPCLKDIV_4 (3 << 4)
+
+/* BCLK clock dividers */
+#define WM8974_BCLKDIV_1 (0 << 2)
+#define WM8974_BCLKDIV_2 (1 << 2)
+#define WM8974_BCLKDIV_4 (2 << 2)
+#define WM8974_BCLKDIV_8 (3 << 2)
+#define WM8974_BCLKDIV_16 (4 << 2)
+#define WM8974_BCLKDIV_32 (5 << 2)
+
+/* MCLK clock dividers */
+#define WM8974_MCLKDIV_1 (0 << 5)
+#define WM8974_MCLKDIV_1_5 (1 << 5)
+#define WM8974_MCLKDIV_2 (2 << 5)
+#define WM8974_MCLKDIV_3 (3 << 5)
+#define WM8974_MCLKDIV_4 (4 << 5)
+#define WM8974_MCLKDIV_6 (5 << 5)
+#define WM8974_MCLKDIV_8 (6 << 5)
+#define WM8974_MCLKDIV_12 (7 << 5)
+
+#endif
diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c
new file mode 100644
index 00000000..85e3e630
--- /dev/null
+++ b/sound/soc/codecs/wm8978.c
@@ -0,0 +1,1076 @@
+/*
+ * wm8978.c -- WM8978 ALSA SoC Audio Codec driver
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2007 Carlos Munoz <carlos@kenati.com>
+ * Copyright 2006-2009 Wolfson Microelectronics PLC.
+ * Based on wm8974 and wm8990 by Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <asm/div64.h>
+
+#include "wm8978.h"
+
+/* wm8978 register cache. Note that register 0 is not included in the cache. */
+static const u16 wm8978_reg[WM8978_CACHEREGNUM] = {
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x00...0x03 */
+ 0x0050, 0x0000, 0x0140, 0x0000, /* 0x04...0x07 */
+ 0x0000, 0x0000, 0x0000, 0x00ff, /* 0x08...0x0b */
+ 0x00ff, 0x0000, 0x0100, 0x00ff, /* 0x0c...0x0f */
+ 0x00ff, 0x0000, 0x012c, 0x002c, /* 0x10...0x13 */
+ 0x002c, 0x002c, 0x002c, 0x0000, /* 0x14...0x17 */
+ 0x0032, 0x0000, 0x0000, 0x0000, /* 0x18...0x1b */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x1c...0x1f */
+ 0x0038, 0x000b, 0x0032, 0x0000, /* 0x20...0x23 */
+ 0x0008, 0x000c, 0x0093, 0x00e9, /* 0x24...0x27 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x28...0x2b */
+ 0x0033, 0x0010, 0x0010, 0x0100, /* 0x2c...0x2f */
+ 0x0100, 0x0002, 0x0001, 0x0001, /* 0x30...0x33 */
+ 0x0039, 0x0039, 0x0039, 0x0039, /* 0x34...0x37 */
+ 0x0001, 0x0001, /* 0x38...0x3b */
+};
+
+/* codec private data */
+struct wm8978_priv {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+ unsigned int f_pllout;
+ unsigned int f_mclk;
+ unsigned int f_256fs;
+ unsigned int f_opclk;
+ int mclk_idx;
+ enum wm8978_sysclk_src sysclk;
+};
+
+static const char *wm8978_companding[] = {"Off", "NC", "u-law", "A-law"};
+static const char *wm8978_eqmode[] = {"Capture", "Playback"};
+static const char *wm8978_bw[] = {"Narrow", "Wide"};
+static const char *wm8978_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz"};
+static const char *wm8978_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz"};
+static const char *wm8978_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz"};
+static const char *wm8978_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz"};
+static const char *wm8978_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz"};
+static const char *wm8978_alc3[] = {"ALC", "Limiter"};
+static const char *wm8978_alc1[] = {"Off", "Right", "Left", "Both"};
+
+static const SOC_ENUM_SINGLE_DECL(adc_compand, WM8978_COMPANDING_CONTROL, 1,
+ wm8978_companding);
+static const SOC_ENUM_SINGLE_DECL(dac_compand, WM8978_COMPANDING_CONTROL, 3,
+ wm8978_companding);
+static const SOC_ENUM_SINGLE_DECL(eqmode, WM8978_EQ1, 8, wm8978_eqmode);
+static const SOC_ENUM_SINGLE_DECL(eq1, WM8978_EQ1, 5, wm8978_eq1);
+static const SOC_ENUM_SINGLE_DECL(eq2bw, WM8978_EQ2, 8, wm8978_bw);
+static const SOC_ENUM_SINGLE_DECL(eq2, WM8978_EQ2, 5, wm8978_eq2);
+static const SOC_ENUM_SINGLE_DECL(eq3bw, WM8978_EQ3, 8, wm8978_bw);
+static const SOC_ENUM_SINGLE_DECL(eq3, WM8978_EQ3, 5, wm8978_eq3);
+static const SOC_ENUM_SINGLE_DECL(eq4bw, WM8978_EQ4, 8, wm8978_bw);
+static const SOC_ENUM_SINGLE_DECL(eq4, WM8978_EQ4, 5, wm8978_eq4);
+static const SOC_ENUM_SINGLE_DECL(eq5, WM8978_EQ5, 5, wm8978_eq5);
+static const SOC_ENUM_SINGLE_DECL(alc3, WM8978_ALC_CONTROL_3, 8, wm8978_alc3);
+static const SOC_ENUM_SINGLE_DECL(alc1, WM8978_ALC_CONTROL_1, 7, wm8978_alc1);
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
+static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(boost_tlv, -1500, 300, 1);
+static const DECLARE_TLV_DB_SCALE(limiter_tlv, 0, 100, 0);
+
+static const struct snd_kcontrol_new wm8978_snd_controls[] = {
+
+ SOC_SINGLE("Digital Loopback Switch",
+ WM8978_COMPANDING_CONTROL, 0, 1, 0),
+
+ SOC_ENUM("ADC Companding", adc_compand),
+ SOC_ENUM("DAC Companding", dac_compand),
+
+ SOC_DOUBLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 1, 0),
+
+ SOC_DOUBLE_R_TLV("PCM Volume",
+ WM8978_LEFT_DAC_DIGITAL_VOLUME, WM8978_RIGHT_DAC_DIGITAL_VOLUME,
+ 0, 255, 0, digital_tlv),
+
+ SOC_SINGLE("High Pass Filter Switch", WM8978_ADC_CONTROL, 8, 1, 0),
+ SOC_SINGLE("High Pass Cut Off", WM8978_ADC_CONTROL, 4, 7, 0),
+ SOC_DOUBLE("ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 1, 0),
+
+ SOC_DOUBLE_R_TLV("ADC Volume",
+ WM8978_LEFT_ADC_DIGITAL_VOLUME, WM8978_RIGHT_ADC_DIGITAL_VOLUME,
+ 0, 255, 0, digital_tlv),
+
+ SOC_ENUM("Equaliser Function", eqmode),
+ SOC_ENUM("EQ1 Cut Off", eq1),
+ SOC_SINGLE_TLV("EQ1 Volume", WM8978_EQ1, 0, 24, 1, eq_tlv),
+
+ SOC_ENUM("Equaliser EQ2 Bandwith", eq2bw),
+ SOC_ENUM("EQ2 Cut Off", eq2),
+ SOC_SINGLE_TLV("EQ2 Volume", WM8978_EQ2, 0, 24, 1, eq_tlv),
+
+ SOC_ENUM("Equaliser EQ3 Bandwith", eq3bw),
+ SOC_ENUM("EQ3 Cut Off", eq3),
+ SOC_SINGLE_TLV("EQ3 Volume", WM8978_EQ3, 0, 24, 1, eq_tlv),
+
+ SOC_ENUM("Equaliser EQ4 Bandwith", eq4bw),
+ SOC_ENUM("EQ4 Cut Off", eq4),
+ SOC_SINGLE_TLV("EQ4 Volume", WM8978_EQ4, 0, 24, 1, eq_tlv),
+
+ SOC_ENUM("EQ5 Cut Off", eq5),
+ SOC_SINGLE_TLV("EQ5 Volume", WM8978_EQ5, 0, 24, 1, eq_tlv),
+
+ SOC_SINGLE("DAC Playback Limiter Switch",
+ WM8978_DAC_LIMITER_1, 8, 1, 0),
+ SOC_SINGLE("DAC Playback Limiter Decay",
+ WM8978_DAC_LIMITER_1, 4, 15, 0),
+ SOC_SINGLE("DAC Playback Limiter Attack",
+ WM8978_DAC_LIMITER_1, 0, 15, 0),
+
+ SOC_SINGLE("DAC Playback Limiter Threshold",
+ WM8978_DAC_LIMITER_2, 4, 7, 0),
+ SOC_SINGLE_TLV("DAC Playback Limiter Volume",
+ WM8978_DAC_LIMITER_2, 0, 12, 0, limiter_tlv),
+
+ SOC_ENUM("ALC Enable Switch", alc1),
+ SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 7, 0),
+ SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0),
+
+ SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 10, 0),
+ SOC_SINGLE("ALC Capture Target", WM8978_ALC_CONTROL_2, 0, 15, 0),
+
+ SOC_ENUM("ALC Capture Mode", alc3),
+ SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 10, 0),
+ SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 10, 0),
+
+ SOC_SINGLE("ALC Capture Noise Gate Switch", WM8978_NOISE_GATE, 3, 1, 0),
+ SOC_SINGLE("ALC Capture Noise Gate Threshold",
+ WM8978_NOISE_GATE, 0, 7, 0),
+
+ SOC_DOUBLE_R("Capture PGA ZC Switch",
+ WM8978_LEFT_INP_PGA_CONTROL, WM8978_RIGHT_INP_PGA_CONTROL,
+ 7, 1, 0),
+
+ /* OUT1 - Headphones */
+ SOC_DOUBLE_R("Headphone Playback ZC Switch",
+ WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, 7, 1, 0),
+
+ SOC_DOUBLE_R_TLV("Headphone Playback Volume",
+ WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL,
+ 0, 63, 0, spk_tlv),
+
+ /* OUT2 - Speakers */
+ SOC_DOUBLE_R("Speaker Playback ZC Switch",
+ WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 7, 1, 0),
+
+ SOC_DOUBLE_R_TLV("Speaker Playback Volume",
+ WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL,
+ 0, 63, 0, spk_tlv),
+
+ /* OUT3/4 - Line Output */
+ SOC_DOUBLE_R("Line Playback Switch",
+ WM8978_OUT3_MIXER_CONTROL, WM8978_OUT4_MIXER_CONTROL, 6, 1, 1),
+
+ /* Mixer #3: Boost (Input) mixer */
+ SOC_DOUBLE_R("PGA Boost (+20dB)",
+ WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL,
+ 8, 1, 0),
+ SOC_DOUBLE_R_TLV("L2/R2 Boost Volume",
+ WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL,
+ 4, 7, 0, boost_tlv),
+ SOC_DOUBLE_R_TLV("Aux Boost Volume",
+ WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL,
+ 0, 7, 0, boost_tlv),
+
+ /* Input PGA volume */
+ SOC_DOUBLE_R_TLV("Input PGA Volume",
+ WM8978_LEFT_INP_PGA_CONTROL, WM8978_RIGHT_INP_PGA_CONTROL,
+ 0, 63, 0, inpga_tlv),
+
+ /* Headphone */
+ SOC_DOUBLE_R("Headphone Switch",
+ WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
+
+ /* Speaker */
+ SOC_DOUBLE_R("Speaker Switch",
+ WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 6, 1, 1),
+
+ /* DAC / ADC oversampling */
+ SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL,
+ 5, 1, 0),
+ SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL,
+ 5, 1, 0),
+};
+
+/* Mixer #1: Output (OUT1, OUT2) Mixer: mix AUX, Input mixer output and DAC */
+static const struct snd_kcontrol_new wm8978_left_out_mixer[] = {
+ SOC_DAPM_SINGLE("Line Bypass Switch", WM8978_LEFT_MIXER_CONTROL, 1, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", WM8978_LEFT_MIXER_CONTROL, 5, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", WM8978_LEFT_MIXER_CONTROL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8978_right_out_mixer[] = {
+ SOC_DAPM_SINGLE("Line Bypass Switch", WM8978_RIGHT_MIXER_CONTROL, 1, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", WM8978_RIGHT_MIXER_CONTROL, 5, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", WM8978_RIGHT_MIXER_CONTROL, 0, 1, 0),
+};
+
+/* OUT3/OUT4 Mixer not implemented */
+
+/* Mixer #2: Input PGA Mute */
+static const struct snd_kcontrol_new wm8978_left_input_mixer[] = {
+ SOC_DAPM_SINGLE("L2 Switch", WM8978_INPUT_CONTROL, 2, 1, 0),
+ SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 1, 1, 0),
+ SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 0, 1, 0),
+};
+static const struct snd_kcontrol_new wm8978_right_input_mixer[] = {
+ SOC_DAPM_SINGLE("R2 Switch", WM8978_INPUT_CONTROL, 6, 1, 0),
+ SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 5, 1, 0),
+ SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8978_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback",
+ WM8978_POWER_MANAGEMENT_3, 0, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback",
+ WM8978_POWER_MANAGEMENT_3, 1, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture",
+ WM8978_POWER_MANAGEMENT_2, 0, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture",
+ WM8978_POWER_MANAGEMENT_2, 1, 0),
+
+ /* Mixer #1: OUT1,2 */
+ SOC_MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_3,
+ 2, 0, wm8978_left_out_mixer),
+ SOC_MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_3,
+ 3, 0, wm8978_right_out_mixer),
+
+ SOC_MIXER_ARRAY("Left Input Mixer", WM8978_POWER_MANAGEMENT_2,
+ 2, 0, wm8978_left_input_mixer),
+ SOC_MIXER_ARRAY("Right Input Mixer", WM8978_POWER_MANAGEMENT_2,
+ 3, 0, wm8978_right_input_mixer),
+
+ SND_SOC_DAPM_PGA("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2,
+ 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2,
+ 5, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("Left Capture PGA", WM8978_LEFT_INP_PGA_CONTROL,
+ 6, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Capture PGA", WM8978_RIGHT_INP_PGA_CONTROL,
+ 6, 1, NULL, 0),
+
+ SND_SOC_DAPM_PGA("Left Headphone Out", WM8978_POWER_MANAGEMENT_2,
+ 7, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Headphone Out", WM8978_POWER_MANAGEMENT_2,
+ 8, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("Left Speaker Out", WM8978_POWER_MANAGEMENT_3,
+ 6, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Speaker Out", WM8978_POWER_MANAGEMENT_3,
+ 5, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("OUT4 VMID", WM8978_POWER_MANAGEMENT_3,
+ 8, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8978_POWER_MANAGEMENT_1, 4, 0),
+
+ SND_SOC_DAPM_INPUT("LMICN"),
+ SND_SOC_DAPM_INPUT("LMICP"),
+ SND_SOC_DAPM_INPUT("RMICN"),
+ SND_SOC_DAPM_INPUT("RMICP"),
+ SND_SOC_DAPM_INPUT("LAUX"),
+ SND_SOC_DAPM_INPUT("RAUX"),
+ SND_SOC_DAPM_INPUT("L2"),
+ SND_SOC_DAPM_INPUT("R2"),
+ SND_SOC_DAPM_OUTPUT("LHP"),
+ SND_SOC_DAPM_OUTPUT("RHP"),
+ SND_SOC_DAPM_OUTPUT("LSPK"),
+ SND_SOC_DAPM_OUTPUT("RSPK"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Output mixer */
+ {"Right Output Mixer", "PCM Playback Switch", "Right DAC"},
+ {"Right Output Mixer", "Aux Playback Switch", "RAUX"},
+ {"Right Output Mixer", "Line Bypass Switch", "Right Boost Mixer"},
+
+ {"Left Output Mixer", "PCM Playback Switch", "Left DAC"},
+ {"Left Output Mixer", "Aux Playback Switch", "LAUX"},
+ {"Left Output Mixer", "Line Bypass Switch", "Left Boost Mixer"},
+
+ /* Outputs */
+ {"Right Headphone Out", NULL, "Right Output Mixer"},
+ {"RHP", NULL, "Right Headphone Out"},
+
+ {"Left Headphone Out", NULL, "Left Output Mixer"},
+ {"LHP", NULL, "Left Headphone Out"},
+
+ {"Right Speaker Out", NULL, "Right Output Mixer"},
+ {"RSPK", NULL, "Right Speaker Out"},
+
+ {"Left Speaker Out", NULL, "Left Output Mixer"},
+ {"LSPK", NULL, "Left Speaker Out"},
+
+ /* Boost Mixer */
+ {"Right ADC", NULL, "Right Boost Mixer"},
+
+ {"Right Boost Mixer", NULL, "RAUX"},
+ {"Right Boost Mixer", NULL, "Right Capture PGA"},
+ {"Right Boost Mixer", NULL, "R2"},
+
+ {"Left ADC", NULL, "Left Boost Mixer"},
+
+ {"Left Boost Mixer", NULL, "LAUX"},
+ {"Left Boost Mixer", NULL, "Left Capture PGA"},
+ {"Left Boost Mixer", NULL, "L2"},
+
+ /* Input PGA */
+ {"Right Capture PGA", NULL, "Right Input Mixer"},
+ {"Left Capture PGA", NULL, "Left Input Mixer"},
+
+ {"Right Input Mixer", "R2 Switch", "R2"},
+ {"Right Input Mixer", "MicN Switch", "RMICN"},
+ {"Right Input Mixer", "MicP Switch", "RMICP"},
+
+ {"Left Input Mixer", "L2 Switch", "L2"},
+ {"Left Input Mixer", "MicN Switch", "LMICN"},
+ {"Left Input Mixer", "MicP Switch", "LMICP"},
+};
+
+static int wm8978_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8978_dapm_widgets,
+ ARRAY_SIZE(wm8978_dapm_widgets));
+ /* set up the WM8978 audio map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+/* PLL divisors */
+struct wm8978_pll_div {
+ u32 k;
+ u8 n;
+ u8 div2;
+};
+
+#define FIXED_PLL_SIZE (1 << 24)
+
+static void pll_factors(struct snd_soc_codec *codec,
+ struct wm8978_pll_div *pll_div, unsigned int target, unsigned int source)
+{
+ u64 k_part;
+ unsigned int k, n_div, n_mod;
+
+ n_div = target / source;
+ if (n_div < 6) {
+ source >>= 1;
+ pll_div->div2 = 1;
+ n_div = target / source;
+ } else {
+ pll_div->div2 = 0;
+ }
+
+ if (n_div < 6 || n_div > 12)
+ dev_warn(codec->dev,
+ "WM8978 N value exceeds recommended range! N = %u\n",
+ n_div);
+
+ pll_div->n = n_div;
+ n_mod = target - source * n_div;
+ k_part = FIXED_PLL_SIZE * (long long)n_mod + source / 2;
+
+ do_div(k_part, source);
+
+ k = k_part & 0xFFFFFFFF;
+
+ pll_div->k = k;
+}
+
+/* MCLK dividers */
+static const int mclk_numerator[] = {1, 3, 2, 3, 4, 6, 8, 12};
+static const int mclk_denominator[] = {1, 2, 1, 1, 1, 1, 1, 1};
+
+/*
+ * find index >= idx, such that, for a given f_out,
+ * 3 * f_mclk / 4 <= f_PLLOUT < 13 * f_mclk / 4
+ * f_out can be f_256fs or f_opclk, currently only used for f_256fs. Can be
+ * generalised for f_opclk with suitable coefficient arrays, but currently
+ * the OPCLK divisor is calculated directly, not iteratively.
+ */
+static int wm8978_enum_mclk(unsigned int f_out, unsigned int f_mclk,
+ unsigned int *f_pllout)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) {
+ unsigned int f_pllout_x4 = 4 * f_out * mclk_numerator[i] /
+ mclk_denominator[i];
+ if (3 * f_mclk <= f_pllout_x4 && f_pllout_x4 < 13 * f_mclk) {
+ *f_pllout = f_pllout_x4 / 4;
+ return i;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * Calculate internal frequencies and dividers, according to Figure 40
+ * "PLL and Clock Select Circuit" in WM8978 datasheet Rev. 2.6
+ */
+static int wm8978_configure_pll(struct snd_soc_codec *codec)
+{
+ struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+ struct wm8978_pll_div pll_div;
+ unsigned int f_opclk = wm8978->f_opclk, f_mclk = wm8978->f_mclk,
+ f_256fs = wm8978->f_256fs;
+ unsigned int f2;
+
+ if (!f_mclk)
+ return -EINVAL;
+
+ if (f_opclk) {
+ unsigned int opclk_div;
+ /* Cannot set up MCLK divider now, do later */
+ wm8978->mclk_idx = -1;
+
+ /*
+ * The user needs OPCLK. Choose OPCLKDIV to put
+ * 6 <= R = f2 / f1 < 13, 1 <= OPCLKDIV <= 4.
+ * f_opclk = f_mclk * prescale * R / 4 / OPCLKDIV, where
+ * prescale = 1, or prescale = 2. Prescale is calculated inside
+ * pll_factors(). We have to select f_PLLOUT, such that
+ * f_mclk * 3 / 4 <= f_PLLOUT < f_mclk * 13 / 4. Must be
+ * f_mclk * 3 / 16 <= f_opclk < f_mclk * 13 / 4.
+ */
+ if (16 * f_opclk < 3 * f_mclk || 4 * f_opclk >= 13 * f_mclk)
+ return -EINVAL;
+
+ if (4 * f_opclk < 3 * f_mclk)
+ /* Have to use OPCLKDIV */
+ opclk_div = (3 * f_mclk / 4 + f_opclk - 1) / f_opclk;
+ else
+ opclk_div = 1;
+
+ dev_dbg(codec->dev, "%s: OPCLKDIV=%d\n", __func__, opclk_div);
+
+ snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 0x30,
+ (opclk_div - 1) << 4);
+
+ wm8978->f_pllout = f_opclk * opclk_div;
+ } else if (f_256fs) {
+ /*
+ * Not using OPCLK, but PLL is used for the codec, choose R:
+ * 6 <= R = f2 / f1 < 13, to put 1 <= MCLKDIV <= 12.
+ * f_256fs = f_mclk * prescale * R / 4 / MCLKDIV, where
+ * prescale = 1, or prescale = 2. Prescale is calculated inside
+ * pll_factors(). We have to select f_PLLOUT, such that
+ * f_mclk * 3 / 4 <= f_PLLOUT < f_mclk * 13 / 4. Must be
+ * f_mclk * 3 / 48 <= f_256fs < f_mclk * 13 / 4. This means MCLK
+ * must be 3.781MHz <= f_MCLK <= 32.768MHz
+ */
+ int idx = wm8978_enum_mclk(f_256fs, f_mclk, &wm8978->f_pllout);
+ if (idx < 0)
+ return idx;
+
+ wm8978->mclk_idx = idx;
+
+ /* GPIO1 into default mode as input - before configuring PLL */
+ snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 0);
+ } else {
+ return -EINVAL;
+ }
+
+ f2 = wm8978->f_pllout * 4;
+
+ dev_dbg(codec->dev, "%s: f_MCLK=%uHz, f_PLLOUT=%uHz\n", __func__,
+ wm8978->f_mclk, wm8978->f_pllout);
+
+ pll_factors(codec, &pll_div, f2, wm8978->f_mclk);
+
+ dev_dbg(codec->dev, "%s: calculated PLL N=0x%x, K=0x%x, div2=%d\n",
+ __func__, pll_div.n, pll_div.k, pll_div.div2);
+
+ /* Turn PLL off for configuration... */
+ snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0);
+
+ snd_soc_write(codec, WM8978_PLL_N, (pll_div.div2 << 4) | pll_div.n);
+ snd_soc_write(codec, WM8978_PLL_K1, pll_div.k >> 18);
+ snd_soc_write(codec, WM8978_PLL_K2, (pll_div.k >> 9) & 0x1ff);
+ snd_soc_write(codec, WM8978_PLL_K3, pll_div.k & 0x1ff);
+
+ /* ...and on again */
+ snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0x20);
+
+ if (f_opclk)
+ /* Output PLL (OPCLK) to GPIO1 */
+ snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 4);
+
+ return 0;
+}
+
+/*
+ * Configure WM8978 clock dividers.
+ */
+static int wm8978_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ switch (div_id) {
+ case WM8978_OPCLKRATE:
+ wm8978->f_opclk = div;
+
+ if (wm8978->f_mclk)
+ /*
+ * We know the MCLK frequency, the user has requested
+ * OPCLK, configure the PLL based on that and start it
+ * and OPCLK immediately. We will configure PLL to match
+ * user-requested OPCLK frquency as good as possible.
+ * In fact, it is likely, that matching the sampling
+ * rate, when it becomes known, is more important, and
+ * we will not be reconfiguring PLL then, because we
+ * must not interrupt OPCLK. But it should be fine,
+ * because typically the user will request OPCLK to run
+ * at 256fs or 512fs, and for these cases we will also
+ * find an exact MCLK divider configuration - it will
+ * be equal to or double the OPCLK divisor.
+ */
+ ret = wm8978_configure_pll(codec);
+ break;
+ case WM8978_BCLKDIV:
+ if (div & ~0x1c)
+ return -EINVAL;
+ snd_soc_update_bits(codec, WM8978_CLOCKING, 0x1c, div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(codec->dev, "%s: ID %d, value %u\n", __func__, div_id, div);
+
+ return ret;
+}
+
+/*
+ * @freq: when .set_pll() us not used, freq is codec MCLK input frequency
+ */
+static int wm8978_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ dev_dbg(codec->dev, "%s: ID %d, freq %u\n", __func__, clk_id, freq);
+
+ if (freq) {
+ wm8978->f_mclk = freq;
+
+ /* Even if MCLK is used for system clock, might have to drive OPCLK */
+ if (wm8978->f_opclk)
+ ret = wm8978_configure_pll(codec);
+
+ /* Our sysclk is fixed to 256 * fs, will configure in .hw_params() */
+
+ if (!ret)
+ wm8978->sysclk = clk_id;
+ }
+
+ if (wm8978->sysclk == WM8978_PLL && (!freq || clk_id == WM8978_MCLK)) {
+ /* Clock CODEC directly from MCLK */
+ snd_soc_update_bits(codec, WM8978_CLOCKING, 0x100, 0);
+
+ /* GPIO1 into default mode as input - before configuring PLL */
+ snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 0);
+
+ /* Turn off PLL */
+ snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0);
+ wm8978->sysclk = WM8978_MCLK;
+ wm8978->f_pllout = 0;
+ wm8978->f_opclk = 0;
+ }
+
+ return ret;
+}
+
+/*
+ * Set ADC and Voice DAC format.
+ */
+static int wm8978_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ /*
+ * BCLK polarity mask = 0x100, LRC clock polarity mask = 0x80,
+ * Data Format mask = 0x18: all will be calculated anew
+ */
+ u16 iface = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x198;
+ u16 clk = snd_soc_read(codec, WM8978_CLOCKING);
+
+ dev_dbg(codec->dev, "%s\n", __func__);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ clk |= 1;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ clk &= ~1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x10;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x8;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x18;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x180;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x100;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x80;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface);
+ snd_soc_write(codec, WM8978_CLOCKING, clk);
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8978_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+ /* Word length mask = 0x60 */
+ u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60;
+ /* Sampling rate mask = 0xe (for filters) */
+ u16 add_ctl = snd_soc_read(codec, WM8978_ADDITIONAL_CONTROL) & ~0xe;
+ u16 clking = snd_soc_read(codec, WM8978_CLOCKING);
+ enum wm8978_sysclk_src current_clk_id = clking & 0x100 ?
+ WM8978_PLL : WM8978_MCLK;
+ unsigned int f_sel, diff, diff_best = INT_MAX;
+ int i, best = 0;
+
+ if (!wm8978->f_mclk)
+ return -EINVAL;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface_ctl |= 0x20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface_ctl |= 0x40;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface_ctl |= 0x60;
+ break;
+ }
+
+ /* filter coefficient */
+ switch (params_rate(params)) {
+ case 8000:
+ add_ctl |= 0x5 << 1;
+ break;
+ case 11025:
+ add_ctl |= 0x4 << 1;
+ break;
+ case 16000:
+ add_ctl |= 0x3 << 1;
+ break;
+ case 22050:
+ add_ctl |= 0x2 << 1;
+ break;
+ case 32000:
+ add_ctl |= 0x1 << 1;
+ break;
+ case 44100:
+ case 48000:
+ break;
+ }
+
+ /* Sampling rate is known now, can configure the MCLK divider */
+ wm8978->f_256fs = params_rate(params) * 256;
+
+ if (wm8978->sysclk == WM8978_MCLK) {
+ wm8978->mclk_idx = -1;
+ f_sel = wm8978->f_mclk;
+ } else {
+ if (!wm8978->f_pllout) {
+ /* We only enter here, if OPCLK is not used */
+ int ret = wm8978_configure_pll(codec);
+ if (ret < 0)
+ return ret;
+ }
+ f_sel = wm8978->f_pllout;
+ }
+
+ if (wm8978->mclk_idx < 0) {
+ /* Either MCLK is used directly, or OPCLK is used */
+ if (f_sel < wm8978->f_256fs || f_sel > 12 * wm8978->f_256fs)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) {
+ diff = abs(wm8978->f_256fs * 3 -
+ f_sel * 3 * mclk_denominator[i] / mclk_numerator[i]);
+
+ if (diff < diff_best) {
+ diff_best = diff;
+ best = i;
+ }
+
+ if (!diff)
+ break;
+ }
+ } else {
+ /* OPCLK not used, codec driven by PLL */
+ best = wm8978->mclk_idx;
+ diff = 0;
+ }
+
+ if (diff)
+ dev_warn(codec->dev, "Imprecise sampling rate: %uHz%s\n",
+ f_sel * mclk_denominator[best] / mclk_numerator[best] / 256,
+ wm8978->sysclk == WM8978_MCLK ?
+ ", consider using PLL" : "");
+
+ dev_dbg(codec->dev, "%s: fmt %d, rate %u, MCLK divisor #%d\n", __func__,
+ params_format(params), params_rate(params), best);
+
+ /* MCLK divisor mask = 0xe0 */
+ snd_soc_update_bits(codec, WM8978_CLOCKING, 0xe0, best << 5);
+
+ snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
+ snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
+
+ if (wm8978->sysclk != current_clk_id) {
+ if (wm8978->sysclk == WM8978_PLL)
+ /* Run CODEC from PLL instead of MCLK */
+ snd_soc_update_bits(codec, WM8978_CLOCKING,
+ 0x100, 0x100);
+ else
+ /* Clock CODEC directly from MCLK */
+ snd_soc_update_bits(codec, WM8978_CLOCKING, 0x100, 0);
+ }
+
+ return 0;
+}
+
+static int wm8978_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+
+ dev_dbg(codec->dev, "%s: %d\n", __func__, mute);
+
+ if (mute)
+ snd_soc_update_bits(codec, WM8978_DAC_CONTROL, 0x40, 0x40);
+ else
+ snd_soc_update_bits(codec, WM8978_DAC_CONTROL, 0x40, 0);
+
+ return 0;
+}
+
+static int wm8978_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 power1 = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1) & ~3;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ power1 |= 1; /* VMID 75k */
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, power1);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* bit 3: enable bias, bit 2: enable I/O tie off buffer */
+ power1 |= 0xc;
+
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Initial cap charge at VMID 5k */
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
+ power1 | 0x3);
+ mdelay(100);
+ }
+
+ power1 |= 0x2; /* VMID 500k */
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, power1);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* Preserve PLL - OPCLK may be used by someone */
+ snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, ~0x20, 0);
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0);
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0);
+ break;
+ }
+
+ dev_dbg(codec->dev, "%s: %d, %x\n", __func__, level, power1);
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8978_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8978_dai_ops = {
+ .hw_params = wm8978_hw_params,
+ .digital_mute = wm8978_mute,
+ .set_fmt = wm8978_set_dai_fmt,
+ .set_clkdiv = wm8978_set_dai_clkdiv,
+ .set_sysclk = wm8978_set_dai_sysclk,
+};
+
+/* Also supports 12kHz */
+static struct snd_soc_dai_driver wm8978_dai = {
+ .name = "wm8978-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = WM8978_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = WM8978_FORMATS,
+ },
+ .ops = &wm8978_dai_ops,
+};
+
+static int wm8978_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8978_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ /* Also switch PLL off */
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 0);
+
+ return 0;
+}
+
+static int wm8978_resume(struct snd_soc_codec *codec)
+{
+ struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+ int i;
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8978_reg); i++) {
+ if (i == WM8978_RESET)
+ continue;
+ if (cache[i] != wm8978_reg[i])
+ snd_soc_write(codec, i, cache[i]);
+ }
+
+ wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ if (wm8978->f_pllout)
+ /* Switch PLL on */
+ snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0x20);
+
+ return 0;
+}
+
+/*
+ * These registers contain an "update" bit - bit 8. This means, for example,
+ * that one can write new DAC digital volume for both channels, but only when
+ * the update bit is set, will also the volume be updated - simultaneously for
+ * both channels.
+ */
+static const int update_reg[] = {
+ WM8978_LEFT_DAC_DIGITAL_VOLUME,
+ WM8978_RIGHT_DAC_DIGITAL_VOLUME,
+ WM8978_LEFT_ADC_DIGITAL_VOLUME,
+ WM8978_RIGHT_ADC_DIGITAL_VOLUME,
+ WM8978_LEFT_INP_PGA_CONTROL,
+ WM8978_RIGHT_INP_PGA_CONTROL,
+ WM8978_LOUT1_HP_CONTROL,
+ WM8978_ROUT1_HP_CONTROL,
+ WM8978_LOUT2_SPK_CONTROL,
+ WM8978_ROUT2_SPK_CONTROL,
+};
+
+static int wm8978_probe(struct snd_soc_codec *codec)
+{
+ struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0, i;
+
+ /*
+ * Set default system clock to PLL, it is more precise, this is also the
+ * default hardware setting
+ */
+ wm8978->sysclk = WM8978_PLL;
+ codec->control_data = wm8978->control_data;
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_I2C);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Set the update bit in all registers, that have one. This way all
+ * writes to those registers will also cause the update bit to be
+ * written.
+ */
+ for (i = 0; i < ARRAY_SIZE(update_reg); i++)
+ snd_soc_update_bits(codec, update_reg[i], 0x100, 0x100);
+
+ /* Reset the codec */
+ ret = snd_soc_write(codec, WM8978_RESET, 0);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_add_controls(codec, wm8978_snd_controls,
+ ARRAY_SIZE(wm8978_snd_controls));
+ wm8978_add_widgets(codec);
+
+ return 0;
+}
+
+/* power down chip */
+static int wm8978_remove(struct snd_soc_codec *codec)
+{
+ wm8978_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8978 = {
+ .probe = wm8978_probe,
+ .remove = wm8978_remove,
+ .suspend = wm8978_suspend,
+ .resume = wm8978_resume,
+ .set_bias_level = wm8978_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8978_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8978_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8978_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8978_priv *wm8978;
+ int ret;
+
+ wm8978 = kzalloc(sizeof(struct wm8978_priv), GFP_KERNEL);
+ if (wm8978 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8978);
+ wm8978->control_data = i2c;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8978, &wm8978_dai, 1);
+ if (ret < 0)
+ kfree(wm8978);
+ return ret;
+}
+
+static __devexit int wm8978_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8978_i2c_id[] = {
+ { "wm8978", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8978_i2c_id);
+
+static struct i2c_driver wm8978_i2c_driver = {
+ .driver = {
+ .name = "wm8978",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8978_i2c_probe,
+ .remove = __devexit_p(wm8978_i2c_remove),
+ .id_table = wm8978_i2c_id,
+};
+#endif
+
+static int __init wm8978_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8978_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8978 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8978_modinit);
+
+static void __exit wm8978_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8978_i2c_driver);
+#endif
+}
+module_exit(wm8978_exit);
+
+MODULE_DESCRIPTION("ASoC WM8978 codec driver");
+MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8978.h b/sound/soc/codecs/wm8978.h
new file mode 100644
index 00000000..c75525b7
--- /dev/null
+++ b/sound/soc/codecs/wm8978.h
@@ -0,0 +1,83 @@
+/*
+ * wm8978.h -- codec driver for WM8978
+ *
+ * Copyright 2009 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __WM8978_H__
+#define __WM8978_H__
+
+/*
+ * Register values.
+ */
+#define WM8978_RESET 0x00
+#define WM8978_POWER_MANAGEMENT_1 0x01
+#define WM8978_POWER_MANAGEMENT_2 0x02
+#define WM8978_POWER_MANAGEMENT_3 0x03
+#define WM8978_AUDIO_INTERFACE 0x04
+#define WM8978_COMPANDING_CONTROL 0x05
+#define WM8978_CLOCKING 0x06
+#define WM8978_ADDITIONAL_CONTROL 0x07
+#define WM8978_GPIO_CONTROL 0x08
+#define WM8978_JACK_DETECT_CONTROL_1 0x09
+#define WM8978_DAC_CONTROL 0x0A
+#define WM8978_LEFT_DAC_DIGITAL_VOLUME 0x0B
+#define WM8978_RIGHT_DAC_DIGITAL_VOLUME 0x0C
+#define WM8978_JACK_DETECT_CONTROL_2 0x0D
+#define WM8978_ADC_CONTROL 0x0E
+#define WM8978_LEFT_ADC_DIGITAL_VOLUME 0x0F
+#define WM8978_RIGHT_ADC_DIGITAL_VOLUME 0x10
+#define WM8978_EQ1 0x12
+#define WM8978_EQ2 0x13
+#define WM8978_EQ3 0x14
+#define WM8978_EQ4 0x15
+#define WM8978_EQ5 0x16
+#define WM8978_DAC_LIMITER_1 0x18
+#define WM8978_DAC_LIMITER_2 0x19
+#define WM8978_NOTCH_FILTER_1 0x1b
+#define WM8978_NOTCH_FILTER_2 0x1c
+#define WM8978_NOTCH_FILTER_3 0x1d
+#define WM8978_NOTCH_FILTER_4 0x1e
+#define WM8978_ALC_CONTROL_1 0x20
+#define WM8978_ALC_CONTROL_2 0x21
+#define WM8978_ALC_CONTROL_3 0x22
+#define WM8978_NOISE_GATE 0x23
+#define WM8978_PLL_N 0x24
+#define WM8978_PLL_K1 0x25
+#define WM8978_PLL_K2 0x26
+#define WM8978_PLL_K3 0x27
+#define WM8978_3D_CONTROL 0x29
+#define WM8978_BEEP_CONTROL 0x2b
+#define WM8978_INPUT_CONTROL 0x2c
+#define WM8978_LEFT_INP_PGA_CONTROL 0x2d
+#define WM8978_RIGHT_INP_PGA_CONTROL 0x2e
+#define WM8978_LEFT_ADC_BOOST_CONTROL 0x2f
+#define WM8978_RIGHT_ADC_BOOST_CONTROL 0x30
+#define WM8978_OUTPUT_CONTROL 0x31
+#define WM8978_LEFT_MIXER_CONTROL 0x32
+#define WM8978_RIGHT_MIXER_CONTROL 0x33
+#define WM8978_LOUT1_HP_CONTROL 0x34
+#define WM8978_ROUT1_HP_CONTROL 0x35
+#define WM8978_LOUT2_SPK_CONTROL 0x36
+#define WM8978_ROUT2_SPK_CONTROL 0x37
+#define WM8978_OUT3_MIXER_CONTROL 0x38
+#define WM8978_OUT4_MIXER_CONTROL 0x39
+
+#define WM8978_CACHEREGNUM 58
+
+/* Clock divider Id's */
+enum wm8978_clk_id {
+ WM8978_OPCLKRATE,
+ WM8978_BCLKDIV,
+};
+
+enum wm8978_sysclk_src {
+ WM8978_PLL,
+ WM8978_MCLK
+};
+
+#endif /* __WM8978_H__ */
diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c
new file mode 100644
index 00000000..bae510ac
--- /dev/null
+++ b/sound/soc/codecs/wm8985.c
@@ -0,0 +1,1192 @@
+/*
+ * wm8985.c -- WM8985 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO:
+ * o Add OUT3/OUT4 mixer controls.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8985.h"
+
+#define WM8985_NUM_SUPPLIES 4
+static const char *wm8985_supply_names[WM8985_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD",
+ "AVDD1",
+ "AVDD2"
+};
+
+static const u16 wm8985_reg_defs[] = {
+ 0x0000, /* R0 - Software Reset */
+ 0x0000, /* R1 - Power management 1 */
+ 0x0000, /* R2 - Power management 2 */
+ 0x0000, /* R3 - Power management 3 */
+ 0x0050, /* R4 - Audio Interface */
+ 0x0000, /* R5 - Companding control */
+ 0x0140, /* R6 - Clock Gen control */
+ 0x0000, /* R7 - Additional control */
+ 0x0000, /* R8 - GPIO Control */
+ 0x0000, /* R9 - Jack Detect Control 1 */
+ 0x0000, /* R10 - DAC Control */
+ 0x00FF, /* R11 - Left DAC digital Vol */
+ 0x00FF, /* R12 - Right DAC digital vol */
+ 0x0000, /* R13 - Jack Detect Control 2 */
+ 0x0100, /* R14 - ADC Control */
+ 0x00FF, /* R15 - Left ADC Digital Vol */
+ 0x00FF, /* R16 - Right ADC Digital Vol */
+ 0x0000, /* R17 */
+ 0x012C, /* R18 - EQ1 - low shelf */
+ 0x002C, /* R19 - EQ2 - peak 1 */
+ 0x002C, /* R20 - EQ3 - peak 2 */
+ 0x002C, /* R21 - EQ4 - peak 3 */
+ 0x002C, /* R22 - EQ5 - high shelf */
+ 0x0000, /* R23 */
+ 0x0032, /* R24 - DAC Limiter 1 */
+ 0x0000, /* R25 - DAC Limiter 2 */
+ 0x0000, /* R26 */
+ 0x0000, /* R27 - Notch Filter 1 */
+ 0x0000, /* R28 - Notch Filter 2 */
+ 0x0000, /* R29 - Notch Filter 3 */
+ 0x0000, /* R30 - Notch Filter 4 */
+ 0x0000, /* R31 */
+ 0x0038, /* R32 - ALC control 1 */
+ 0x000B, /* R33 - ALC control 2 */
+ 0x0032, /* R34 - ALC control 3 */
+ 0x0000, /* R35 - Noise Gate */
+ 0x0008, /* R36 - PLL N */
+ 0x000C, /* R37 - PLL K 1 */
+ 0x0093, /* R38 - PLL K 2 */
+ 0x00E9, /* R39 - PLL K 3 */
+ 0x0000, /* R40 */
+ 0x0000, /* R41 - 3D control */
+ 0x0000, /* R42 - OUT4 to ADC */
+ 0x0000, /* R43 - Beep control */
+ 0x0033, /* R44 - Input ctrl */
+ 0x0010, /* R45 - Left INP PGA gain ctrl */
+ 0x0010, /* R46 - Right INP PGA gain ctrl */
+ 0x0100, /* R47 - Left ADC BOOST ctrl */
+ 0x0100, /* R48 - Right ADC BOOST ctrl */
+ 0x0002, /* R49 - Output ctrl */
+ 0x0001, /* R50 - Left mixer ctrl */
+ 0x0001, /* R51 - Right mixer ctrl */
+ 0x0039, /* R52 - LOUT1 (HP) volume ctrl */
+ 0x0039, /* R53 - ROUT1 (HP) volume ctrl */
+ 0x0039, /* R54 - LOUT2 (SPK) volume ctrl */
+ 0x0039, /* R55 - ROUT2 (SPK) volume ctrl */
+ 0x0001, /* R56 - OUT3 mixer ctrl */
+ 0x0001, /* R57 - OUT4 (MONO) mix ctrl */
+ 0x0001, /* R58 */
+ 0x0000, /* R59 */
+ 0x0004, /* R60 - OUTPUT ctrl */
+ 0x0000, /* R61 - BIAS CTRL */
+ 0x0180, /* R62 */
+ 0x0000 /* R63 */
+};
+
+/*
+ * latch bit 8 of these registers to ensure instant
+ * volume updates
+ */
+static const int volume_update_regs[] = {
+ WM8985_LEFT_DAC_DIGITAL_VOL,
+ WM8985_RIGHT_DAC_DIGITAL_VOL,
+ WM8985_LEFT_ADC_DIGITAL_VOL,
+ WM8985_RIGHT_ADC_DIGITAL_VOL,
+ WM8985_LOUT2_SPK_VOLUME_CTRL,
+ WM8985_ROUT2_SPK_VOLUME_CTRL,
+ WM8985_LOUT1_HP_VOLUME_CTRL,
+ WM8985_ROUT1_HP_VOLUME_CTRL,
+ WM8985_LEFT_INP_PGA_GAIN_CTRL,
+ WM8985_RIGHT_INP_PGA_GAIN_CTRL
+};
+
+struct wm8985_priv {
+ enum snd_soc_control_type control_type;
+ struct regulator_bulk_data supplies[WM8985_NUM_SUPPLIES];
+ unsigned int sysclk;
+ unsigned int bclk;
+};
+
+static const struct {
+ int div;
+ int ratio;
+} fs_ratios[] = {
+ { 10, 128 },
+ { 15, 192 },
+ { 20, 256 },
+ { 30, 384 },
+ { 40, 512 },
+ { 60, 768 },
+ { 80, 1024 },
+ { 120, 1536 }
+};
+
+static const int srates[] = { 48000, 32000, 24000, 16000, 12000, 8000 };
+
+static const int bclk_divs[] = {
+ 1, 2, 4, 8, 16, 32
+};
+
+static int eqmode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+static int eqmode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -12700, 50, 1);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(lim_thresh_tlv, -600, 100, 0);
+static const DECLARE_TLV_DB_SCALE(lim_boost_tlv, 0, 100, 0);
+static const DECLARE_TLV_DB_SCALE(alc_min_tlv, -1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(alc_max_tlv, -675, 600, 0);
+static const DECLARE_TLV_DB_SCALE(alc_tar_tlv, -2250, 150, 0);
+static const DECLARE_TLV_DB_SCALE(pga_vol_tlv, -1200, 75, 0);
+static const DECLARE_TLV_DB_SCALE(boost_tlv, -1200, 300, 1);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(aux_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(pga_boost_tlv, 0, 2000, 0);
+
+static const char *alc_sel_text[] = { "Off", "Right", "Left", "Stereo" };
+static const SOC_ENUM_SINGLE_DECL(alc_sel, WM8985_ALC_CONTROL_1, 7,
+ alc_sel_text);
+
+static const char *alc_mode_text[] = { "ALC", "Limiter" };
+static const SOC_ENUM_SINGLE_DECL(alc_mode, WM8985_ALC_CONTROL_3, 8,
+ alc_mode_text);
+
+static const char *filter_mode_text[] = { "Audio", "Application" };
+static const SOC_ENUM_SINGLE_DECL(filter_mode, WM8985_ADC_CONTROL, 7,
+ filter_mode_text);
+
+static const char *eq_bw_text[] = { "Narrow", "Wide" };
+static const char *eqmode_text[] = { "Capture", "Playback" };
+static const SOC_ENUM_SINGLE_EXT_DECL(eqmode, eqmode_text);
+
+static const char *eq1_cutoff_text[] = {
+ "80Hz", "105Hz", "135Hz", "175Hz"
+};
+static const SOC_ENUM_SINGLE_DECL(eq1_cutoff, WM8985_EQ1_LOW_SHELF, 5,
+ eq1_cutoff_text);
+static const char *eq2_cutoff_text[] = {
+ "230Hz", "300Hz", "385Hz", "500Hz"
+};
+static const SOC_ENUM_SINGLE_DECL(eq2_bw, WM8985_EQ2_PEAK_1, 8, eq_bw_text);
+static const SOC_ENUM_SINGLE_DECL(eq2_cutoff, WM8985_EQ2_PEAK_1, 5,
+ eq2_cutoff_text);
+static const char *eq3_cutoff_text[] = {
+ "650Hz", "850Hz", "1.1kHz", "1.4kHz"
+};
+static const SOC_ENUM_SINGLE_DECL(eq3_bw, WM8985_EQ3_PEAK_2, 8, eq_bw_text);
+static const SOC_ENUM_SINGLE_DECL(eq3_cutoff, WM8985_EQ3_PEAK_2, 5,
+ eq3_cutoff_text);
+static const char *eq4_cutoff_text[] = {
+ "1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz"
+};
+static const SOC_ENUM_SINGLE_DECL(eq4_bw, WM8985_EQ4_PEAK_3, 8, eq_bw_text);
+static const SOC_ENUM_SINGLE_DECL(eq4_cutoff, WM8985_EQ4_PEAK_3, 5,
+ eq4_cutoff_text);
+static const char *eq5_cutoff_text[] = {
+ "5.3kHz", "6.9kHz", "9kHz", "11.7kHz"
+};
+static const SOC_ENUM_SINGLE_DECL(eq5_cutoff, WM8985_EQ5_HIGH_SHELF, 5,
+ eq5_cutoff_text);
+
+static const char *speaker_mode_text[] = { "Class A/B", "Class D" };
+static const SOC_ENUM_SINGLE_DECL(speaker_mode, 0x17, 8, speaker_mode_text);
+
+static const char *depth_3d_text[] = {
+ "Off",
+ "6.67%",
+ "13.3%",
+ "20%",
+ "26.7%",
+ "33.3%",
+ "40%",
+ "46.6%",
+ "53.3%",
+ "60%",
+ "66.7%",
+ "73.3%",
+ "80%",
+ "86.7%",
+ "93.3%",
+ "100%"
+};
+static const SOC_ENUM_SINGLE_DECL(depth_3d, WM8985_3D_CONTROL, 0,
+ depth_3d_text);
+
+static const struct snd_kcontrol_new wm8985_snd_controls[] = {
+ SOC_SINGLE("Digital Loopback Switch", WM8985_COMPANDING_CONTROL,
+ 0, 1, 0),
+
+ SOC_ENUM("ALC Capture Function", alc_sel),
+ SOC_SINGLE_TLV("ALC Capture Max Volume", WM8985_ALC_CONTROL_1,
+ 3, 7, 0, alc_max_tlv),
+ SOC_SINGLE_TLV("ALC Capture Min Volume", WM8985_ALC_CONTROL_1,
+ 0, 7, 0, alc_min_tlv),
+ SOC_SINGLE_TLV("ALC Capture Target Volume", WM8985_ALC_CONTROL_2,
+ 0, 15, 0, alc_tar_tlv),
+ SOC_SINGLE("ALC Capture Attack", WM8985_ALC_CONTROL_3, 0, 10, 0),
+ SOC_SINGLE("ALC Capture Hold", WM8985_ALC_CONTROL_2, 4, 10, 0),
+ SOC_SINGLE("ALC Capture Decay", WM8985_ALC_CONTROL_3, 4, 10, 0),
+ SOC_ENUM("ALC Mode", alc_mode),
+ SOC_SINGLE("ALC Capture NG Switch", WM8985_NOISE_GATE,
+ 3, 1, 0),
+ SOC_SINGLE("ALC Capture NG Threshold", WM8985_NOISE_GATE,
+ 0, 7, 1),
+
+ SOC_DOUBLE_R_TLV("Capture Volume", WM8985_LEFT_ADC_DIGITAL_VOL,
+ WM8985_RIGHT_ADC_DIGITAL_VOL, 0, 255, 0, adc_tlv),
+ SOC_DOUBLE_R("Capture PGA ZC Switch", WM8985_LEFT_INP_PGA_GAIN_CTRL,
+ WM8985_RIGHT_INP_PGA_GAIN_CTRL, 7, 1, 0),
+ SOC_DOUBLE_R_TLV("Capture PGA Volume", WM8985_LEFT_INP_PGA_GAIN_CTRL,
+ WM8985_RIGHT_INP_PGA_GAIN_CTRL, 0, 63, 0, pga_vol_tlv),
+
+ SOC_DOUBLE_R_TLV("Capture PGA Boost Volume",
+ WM8985_LEFT_ADC_BOOST_CTRL, WM8985_RIGHT_ADC_BOOST_CTRL,
+ 8, 1, 0, pga_boost_tlv),
+
+ SOC_DOUBLE("ADC Inversion Switch", WM8985_ADC_CONTROL, 0, 1, 1, 0),
+ SOC_SINGLE("ADC 128x Oversampling Switch", WM8985_ADC_CONTROL, 8, 1, 0),
+
+ SOC_DOUBLE_R_TLV("Playback Volume", WM8985_LEFT_DAC_DIGITAL_VOL,
+ WM8985_RIGHT_DAC_DIGITAL_VOL, 0, 255, 0, dac_tlv),
+
+ SOC_SINGLE("DAC Playback Limiter Switch", WM8985_DAC_LIMITER_1, 8, 1, 0),
+ SOC_SINGLE("DAC Playback Limiter Decay", WM8985_DAC_LIMITER_1, 4, 10, 0),
+ SOC_SINGLE("DAC Playback Limiter Attack", WM8985_DAC_LIMITER_1, 0, 11, 0),
+ SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8985_DAC_LIMITER_2,
+ 4, 7, 1, lim_thresh_tlv),
+ SOC_SINGLE_TLV("DAC Playback Limiter Boost Volume", WM8985_DAC_LIMITER_2,
+ 0, 12, 0, lim_boost_tlv),
+ SOC_DOUBLE("DAC Inversion Switch", WM8985_DAC_CONTROL, 0, 1, 1, 0),
+ SOC_SINGLE("DAC Auto Mute Switch", WM8985_DAC_CONTROL, 2, 1, 0),
+ SOC_SINGLE("DAC 128x Oversampling Switch", WM8985_DAC_CONTROL, 3, 1, 0),
+
+ SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8985_LOUT1_HP_VOLUME_CTRL,
+ WM8985_ROUT1_HP_VOLUME_CTRL, 0, 63, 0, out_tlv),
+ SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8985_LOUT1_HP_VOLUME_CTRL,
+ WM8985_ROUT1_HP_VOLUME_CTRL, 7, 1, 0),
+ SOC_DOUBLE_R("Headphone Switch", WM8985_LOUT1_HP_VOLUME_CTRL,
+ WM8985_ROUT1_HP_VOLUME_CTRL, 6, 1, 1),
+
+ SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8985_LOUT2_SPK_VOLUME_CTRL,
+ WM8985_ROUT2_SPK_VOLUME_CTRL, 0, 63, 0, out_tlv),
+ SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8985_LOUT2_SPK_VOLUME_CTRL,
+ WM8985_ROUT2_SPK_VOLUME_CTRL, 7, 1, 0),
+ SOC_DOUBLE_R("Speaker Switch", WM8985_LOUT2_SPK_VOLUME_CTRL,
+ WM8985_ROUT2_SPK_VOLUME_CTRL, 6, 1, 1),
+
+ SOC_SINGLE("High Pass Filter Switch", WM8985_ADC_CONTROL, 8, 1, 0),
+ SOC_ENUM("High Pass Filter Mode", filter_mode),
+ SOC_SINGLE("High Pass Filter Cutoff", WM8985_ADC_CONTROL, 4, 7, 0),
+
+ SOC_DOUBLE_R_TLV("Aux Bypass Volume",
+ WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 6, 7, 0,
+ aux_tlv),
+
+ SOC_DOUBLE_R_TLV("Input PGA Bypass Volume",
+ WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 2, 7, 0,
+ bypass_tlv),
+
+ SOC_ENUM_EXT("Equalizer Function", eqmode, eqmode_get, eqmode_put),
+ SOC_ENUM("EQ1 Cutoff", eq1_cutoff),
+ SOC_SINGLE_TLV("EQ1 Volume", WM8985_EQ1_LOW_SHELF, 0, 24, 1, eq_tlv),
+ SOC_ENUM("EQ2 Bandwith", eq2_bw),
+ SOC_ENUM("EQ2 Cutoff", eq2_cutoff),
+ SOC_SINGLE_TLV("EQ2 Volume", WM8985_EQ2_PEAK_1, 0, 24, 1, eq_tlv),
+ SOC_ENUM("EQ3 Bandwith", eq3_bw),
+ SOC_ENUM("EQ3 Cutoff", eq3_cutoff),
+ SOC_SINGLE_TLV("EQ3 Volume", WM8985_EQ3_PEAK_2, 0, 24, 1, eq_tlv),
+ SOC_ENUM("EQ4 Bandwith", eq4_bw),
+ SOC_ENUM("EQ4 Cutoff", eq4_cutoff),
+ SOC_SINGLE_TLV("EQ4 Volume", WM8985_EQ4_PEAK_3, 0, 24, 1, eq_tlv),
+ SOC_ENUM("EQ5 Cutoff", eq5_cutoff),
+ SOC_SINGLE_TLV("EQ5 Volume", WM8985_EQ5_HIGH_SHELF, 0, 24, 1, eq_tlv),
+
+ SOC_ENUM("3D Depth", depth_3d),
+
+ SOC_ENUM("Speaker Mode", speaker_mode)
+};
+
+static const struct snd_kcontrol_new left_out_mixer[] = {
+ SOC_DAPM_SINGLE("Line Switch", WM8985_LEFT_MIXER_CTRL, 1, 1, 0),
+ SOC_DAPM_SINGLE("Aux Switch", WM8985_LEFT_MIXER_CTRL, 5, 1, 0),
+ SOC_DAPM_SINGLE("PCM Switch", WM8985_LEFT_MIXER_CTRL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_out_mixer[] = {
+ SOC_DAPM_SINGLE("Line Switch", WM8985_RIGHT_MIXER_CTRL, 1, 1, 0),
+ SOC_DAPM_SINGLE("Aux Switch", WM8985_RIGHT_MIXER_CTRL, 5, 1, 0),
+ SOC_DAPM_SINGLE("PCM Switch", WM8985_RIGHT_MIXER_CTRL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_input_mixer[] = {
+ SOC_DAPM_SINGLE("L2 Switch", WM8985_INPUT_CTRL, 2, 1, 0),
+ SOC_DAPM_SINGLE("MicN Switch", WM8985_INPUT_CTRL, 1, 1, 0),
+ SOC_DAPM_SINGLE("MicP Switch", WM8985_INPUT_CTRL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_input_mixer[] = {
+ SOC_DAPM_SINGLE("R2 Switch", WM8985_INPUT_CTRL, 6, 1, 0),
+ SOC_DAPM_SINGLE("MicN Switch", WM8985_INPUT_CTRL, 5, 1, 0),
+ SOC_DAPM_SINGLE("MicP Switch", WM8985_INPUT_CTRL, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_boost_mixer[] = {
+ SOC_DAPM_SINGLE_TLV("L2 Volume", WM8985_LEFT_ADC_BOOST_CTRL,
+ 4, 7, 0, boost_tlv),
+ SOC_DAPM_SINGLE_TLV("AUXL Volume", WM8985_LEFT_ADC_BOOST_CTRL,
+ 0, 7, 0, boost_tlv)
+};
+
+static const struct snd_kcontrol_new right_boost_mixer[] = {
+ SOC_DAPM_SINGLE_TLV("R2 Volume", WM8985_RIGHT_ADC_BOOST_CTRL,
+ 4, 7, 0, boost_tlv),
+ SOC_DAPM_SINGLE_TLV("AUXR Volume", WM8985_RIGHT_ADC_BOOST_CTRL,
+ 0, 7, 0, boost_tlv)
+};
+
+static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8985_POWER_MANAGEMENT_3,
+ 0, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8985_POWER_MANAGEMENT_3,
+ 1, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8985_POWER_MANAGEMENT_2,
+ 0, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8985_POWER_MANAGEMENT_2,
+ 1, 0),
+
+ SND_SOC_DAPM_MIXER("Left Output Mixer", WM8985_POWER_MANAGEMENT_3,
+ 2, 0, left_out_mixer, ARRAY_SIZE(left_out_mixer)),
+ SND_SOC_DAPM_MIXER("Right Output Mixer", WM8985_POWER_MANAGEMENT_3,
+ 3, 0, right_out_mixer, ARRAY_SIZE(right_out_mixer)),
+
+ SND_SOC_DAPM_MIXER("Left Input Mixer", WM8985_POWER_MANAGEMENT_2,
+ 2, 0, left_input_mixer, ARRAY_SIZE(left_input_mixer)),
+ SND_SOC_DAPM_MIXER("Right Input Mixer", WM8985_POWER_MANAGEMENT_2,
+ 3, 0, right_input_mixer, ARRAY_SIZE(right_input_mixer)),
+
+ SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8985_POWER_MANAGEMENT_2,
+ 4, 0, left_boost_mixer, ARRAY_SIZE(left_boost_mixer)),
+ SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8985_POWER_MANAGEMENT_2,
+ 5, 0, right_boost_mixer, ARRAY_SIZE(right_boost_mixer)),
+
+ SND_SOC_DAPM_PGA("Left Capture PGA", WM8985_LEFT_INP_PGA_GAIN_CTRL,
+ 6, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Capture PGA", WM8985_RIGHT_INP_PGA_GAIN_CTRL,
+ 6, 1, NULL, 0),
+
+ SND_SOC_DAPM_PGA("Left Headphone Out", WM8985_POWER_MANAGEMENT_2,
+ 7, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Headphone Out", WM8985_POWER_MANAGEMENT_2,
+ 8, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("Left Speaker Out", WM8985_POWER_MANAGEMENT_3,
+ 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Speaker Out", WM8985_POWER_MANAGEMENT_3,
+ 6, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8985_POWER_MANAGEMENT_1, 4, 0),
+
+ SND_SOC_DAPM_INPUT("LIN"),
+ SND_SOC_DAPM_INPUT("LIP"),
+ SND_SOC_DAPM_INPUT("RIN"),
+ SND_SOC_DAPM_INPUT("RIP"),
+ SND_SOC_DAPM_INPUT("AUXL"),
+ SND_SOC_DAPM_INPUT("AUXR"),
+ SND_SOC_DAPM_INPUT("L2"),
+ SND_SOC_DAPM_INPUT("R2"),
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("SPKL"),
+ SND_SOC_DAPM_OUTPUT("SPKR")
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ { "Right Output Mixer", "PCM Switch", "Right DAC" },
+ { "Right Output Mixer", "Aux Switch", "AUXR" },
+ { "Right Output Mixer", "Line Switch", "Right Boost Mixer" },
+
+ { "Left Output Mixer", "PCM Switch", "Left DAC" },
+ { "Left Output Mixer", "Aux Switch", "AUXL" },
+ { "Left Output Mixer", "Line Switch", "Left Boost Mixer" },
+
+ { "Right Headphone Out", NULL, "Right Output Mixer" },
+ { "HPR", NULL, "Right Headphone Out" },
+
+ { "Left Headphone Out", NULL, "Left Output Mixer" },
+ { "HPL", NULL, "Left Headphone Out" },
+
+ { "Right Speaker Out", NULL, "Right Output Mixer" },
+ { "SPKR", NULL, "Right Speaker Out" },
+
+ { "Left Speaker Out", NULL, "Left Output Mixer" },
+ { "SPKL", NULL, "Left Speaker Out" },
+
+ { "Right ADC", NULL, "Right Boost Mixer" },
+
+ { "Right Boost Mixer", "AUXR Volume", "AUXR" },
+ { "Right Boost Mixer", NULL, "Right Capture PGA" },
+ { "Right Boost Mixer", "R2 Volume", "R2" },
+
+ { "Left ADC", NULL, "Left Boost Mixer" },
+
+ { "Left Boost Mixer", "AUXL Volume", "AUXL" },
+ { "Left Boost Mixer", NULL, "Left Capture PGA" },
+ { "Left Boost Mixer", "L2 Volume", "L2" },
+
+ { "Right Capture PGA", NULL, "Right Input Mixer" },
+ { "Left Capture PGA", NULL, "Left Input Mixer" },
+
+ { "Right Input Mixer", "R2 Switch", "R2" },
+ { "Right Input Mixer", "MicN Switch", "RIN" },
+ { "Right Input Mixer", "MicP Switch", "RIP" },
+
+ { "Left Input Mixer", "L2 Switch", "L2" },
+ { "Left Input Mixer", "MicN Switch", "LIN" },
+ { "Left Input Mixer", "MicP Switch", "LIP" },
+};
+
+static int eqmode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg;
+
+ reg = snd_soc_read(codec, WM8985_EQ1_LOW_SHELF);
+ if (reg & WM8985_EQ3DMODE)
+ ucontrol->value.integer.value[0] = 1;
+ else
+ ucontrol->value.integer.value[0] = 0;
+
+ return 0;
+}
+
+static int eqmode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int regpwr2, regpwr3;
+ unsigned int reg_eq;
+
+ if (ucontrol->value.integer.value[0] != 0
+ && ucontrol->value.integer.value[0] != 1)
+ return -EINVAL;
+
+ reg_eq = snd_soc_read(codec, WM8985_EQ1_LOW_SHELF);
+ switch ((reg_eq & WM8985_EQ3DMODE) >> WM8985_EQ3DMODE_SHIFT) {
+ case 0:
+ if (!ucontrol->value.integer.value[0])
+ return 0;
+ break;
+ case 1:
+ if (ucontrol->value.integer.value[0])
+ return 0;
+ break;
+ }
+
+ regpwr2 = snd_soc_read(codec, WM8985_POWER_MANAGEMENT_2);
+ regpwr3 = snd_soc_read(codec, WM8985_POWER_MANAGEMENT_3);
+ /* disable the DACs and ADCs */
+ snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_2,
+ WM8985_ADCENR_MASK | WM8985_ADCENL_MASK, 0);
+ snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_3,
+ WM8985_DACENR_MASK | WM8985_DACENL_MASK, 0);
+ snd_soc_update_bits(codec, WM8985_ADDITIONAL_CONTROL,
+ WM8985_M128ENB_MASK, WM8985_M128ENB);
+ /* set the desired eqmode */
+ snd_soc_update_bits(codec, WM8985_EQ1_LOW_SHELF,
+ WM8985_EQ3DMODE_MASK,
+ ucontrol->value.integer.value[0]
+ << WM8985_EQ3DMODE_SHIFT);
+ /* restore DAC/ADC configuration */
+ snd_soc_write(codec, WM8985_POWER_MANAGEMENT_2, regpwr2);
+ snd_soc_write(codec, WM8985_POWER_MANAGEMENT_3, regpwr3);
+ return 0;
+}
+
+static int wm8985_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8985_dapm_widgets,
+ ARRAY_SIZE(wm8985_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map,
+ ARRAY_SIZE(audio_map));
+ return 0;
+}
+
+static int wm8985_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8985_SOFTWARE_RESET, 0x0);
+}
+
+static int wm8985_dac_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+
+ return snd_soc_update_bits(codec, WM8985_DAC_CONTROL,
+ WM8985_SOFTMUTE_MASK,
+ !!mute << WM8985_SOFTMUTE_SHIFT);
+}
+
+static int wm8985_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec;
+ u16 format, master, bcp, lrp;
+
+ codec = dai->codec;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ format = 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ format = 0x0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ format = 0x1;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ format = 0x3;
+ break;
+ default:
+ dev_err(dai->dev, "Unknown dai format\n");
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8985_AUDIO_INTERFACE,
+ WM8985_FMT_MASK, format << WM8985_FMT_SHIFT);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ master = 0;
+ break;
+ default:
+ dev_err(dai->dev, "Unknown master/slave configuration\n");
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+ WM8985_MS_MASK, master << WM8985_MS_SHIFT);
+
+ /* frame inversion is not valid for dsp modes */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ case SND_SOC_DAIFMT_NB_IF:
+ return -EINVAL;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ bcp = lrp = 0;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ bcp = lrp = 1;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ bcp = 1;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ lrp = 1;
+ break;
+ default:
+ dev_err(dai->dev, "Unknown polarity configuration\n");
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8985_AUDIO_INTERFACE,
+ WM8985_LRP_MASK, lrp << WM8985_LRP_SHIFT);
+ snd_soc_update_bits(codec, WM8985_AUDIO_INTERFACE,
+ WM8985_BCP_MASK, bcp << WM8985_BCP_SHIFT);
+ return 0;
+}
+
+static int wm8985_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ int i;
+ struct snd_soc_codec *codec;
+ struct wm8985_priv *wm8985;
+ u16 blen, srate_idx;
+ unsigned int tmp;
+ int srate_best;
+
+ codec = dai->codec;
+ wm8985 = snd_soc_codec_get_drvdata(codec);
+
+ wm8985->bclk = snd_soc_params_to_bclk(params);
+ if ((int)wm8985->bclk < 0)
+ return wm8985->bclk;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ blen = 0x0;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ blen = 0x1;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ blen = 0x2;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ blen = 0x3;
+ break;
+ default:
+ dev_err(dai->dev, "Unsupported word length %u\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8985_AUDIO_INTERFACE,
+ WM8985_WL_MASK, blen << WM8985_WL_SHIFT);
+
+ /*
+ * match to the nearest possible sample rate and rely
+ * on the array index to configure the SR register
+ */
+ srate_idx = 0;
+ srate_best = abs(srates[0] - params_rate(params));
+ for (i = 1; i < ARRAY_SIZE(srates); ++i) {
+ if (abs(srates[i] - params_rate(params)) >= srate_best)
+ continue;
+ srate_idx = i;
+ srate_best = abs(srates[i] - params_rate(params));
+ }
+
+ dev_dbg(dai->dev, "Selected SRATE = %d\n", srates[srate_idx]);
+ snd_soc_update_bits(codec, WM8985_ADDITIONAL_CONTROL,
+ WM8985_SR_MASK, srate_idx << WM8985_SR_SHIFT);
+
+ dev_dbg(dai->dev, "Target BCLK = %uHz\n", wm8985->bclk);
+ dev_dbg(dai->dev, "SYSCLK = %uHz\n", wm8985->sysclk);
+
+ for (i = 0; i < ARRAY_SIZE(fs_ratios); ++i) {
+ if (wm8985->sysclk / params_rate(params)
+ == fs_ratios[i].ratio)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(fs_ratios)) {
+ dev_err(dai->dev, "Unable to configure MCLK ratio %u/%u\n",
+ wm8985->sysclk, params_rate(params));
+ return -EINVAL;
+ }
+
+ dev_dbg(dai->dev, "MCLK ratio = %dfs\n", fs_ratios[i].ratio);
+ snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+ WM8985_MCLKDIV_MASK, i << WM8985_MCLKDIV_SHIFT);
+
+ /* select the appropriate bclk divider */
+ tmp = (wm8985->sysclk / fs_ratios[i].div) * 10;
+ for (i = 0; i < ARRAY_SIZE(bclk_divs); ++i) {
+ if (wm8985->bclk == tmp / bclk_divs[i])
+ break;
+ }
+
+ if (i == ARRAY_SIZE(bclk_divs)) {
+ dev_err(dai->dev, "No matching BCLK divider found\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dai->dev, "BCLK div = %d\n", i);
+ snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+ WM8985_BCLKDIV_MASK, i << WM8985_BCLKDIV_SHIFT);
+ return 0;
+}
+
+struct pll_div {
+ u32 div2:1;
+ u32 n:4;
+ u32 k:24;
+};
+
+#define FIXED_PLL_SIZE ((1ULL << 24) * 10)
+static int pll_factors(struct pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned long int K, Ndiv, Nmod;
+
+ pll_div->div2 = 0;
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->div2 = 1;
+ Ndiv = target / source;
+ }
+
+ if (Ndiv < 6 || Ndiv > 12) {
+ printk(KERN_ERR "%s: WM8985 N value is not within"
+ " the recommended range: %lu\n", __func__, Ndiv);
+ return -EINVAL;
+ }
+ pll_div->n = Ndiv;
+
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (u64)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xffffffff;
+ if ((K % 10) >= 5)
+ K += 5;
+ K /= 10;
+ pll_div->k = K;
+
+ return 0;
+}
+
+static int wm8985_set_pll(struct snd_soc_dai *dai, int pll_id,
+ int source, unsigned int freq_in,
+ unsigned int freq_out)
+{
+ int ret;
+ struct snd_soc_codec *codec;
+ struct pll_div pll_div;
+
+ codec = dai->codec;
+ if (freq_in && freq_out) {
+ ret = pll_factors(&pll_div, freq_out * 4 * 2, freq_in);
+ if (ret)
+ return ret;
+ }
+
+ /* disable the PLL before reprogramming it */
+ snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+ WM8985_PLLEN_MASK, 0);
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ /* set PLLN and PRESCALE */
+ snd_soc_write(codec, WM8985_PLL_N,
+ (pll_div.div2 << WM8985_PLL_PRESCALE_SHIFT)
+ | pll_div.n);
+ /* set PLLK */
+ snd_soc_write(codec, WM8985_PLL_K_3, pll_div.k & 0x1ff);
+ snd_soc_write(codec, WM8985_PLL_K_2, (pll_div.k >> 9) & 0x1ff);
+ snd_soc_write(codec, WM8985_PLL_K_1, (pll_div.k >> 18));
+ /* set the source of the clock to be the PLL */
+ snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+ WM8985_CLKSEL_MASK, WM8985_CLKSEL);
+ /* enable the PLL */
+ snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+ WM8985_PLLEN_MASK, WM8985_PLLEN);
+ return 0;
+}
+
+static int wm8985_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec;
+ struct wm8985_priv *wm8985;
+
+ codec = dai->codec;
+ wm8985 = snd_soc_codec_get_drvdata(codec);
+
+ switch (clk_id) {
+ case WM8985_CLKSRC_MCLK:
+ snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+ WM8985_CLKSEL_MASK, 0);
+ snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+ WM8985_PLLEN_MASK, 0);
+ break;
+ case WM8985_CLKSRC_PLL:
+ snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+ WM8985_CLKSEL_MASK, WM8985_CLKSEL);
+ break;
+ default:
+ dev_err(dai->dev, "Unknown clock source %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ wm8985->sysclk = freq;
+ return 0;
+}
+
+static void wm8985_sync_cache(struct snd_soc_codec *codec)
+{
+ short i;
+ u16 *cache;
+
+ if (!codec->cache_sync)
+ return;
+ codec->cache_only = 0;
+ /* restore cache */
+ cache = codec->reg_cache;
+ for (i = 0; i < codec->driver->reg_cache_size; i++) {
+ if (i == WM8985_SOFTWARE_RESET
+ || cache[i] == wm8985_reg_defs[i])
+ continue;
+ snd_soc_write(codec, i, cache[i]);
+ }
+ codec->cache_sync = 0;
+}
+
+static int wm8985_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+ struct wm8985_priv *wm8985;
+
+ wm8985 = snd_soc_codec_get_drvdata(codec);
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID at 75k */
+ snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+ WM8985_VMIDSEL_MASK,
+ 1 << WM8985_VMIDSEL_SHIFT);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies),
+ wm8985->supplies);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ wm8985_sync_cache(codec);
+
+ /* enable anti-pop features */
+ snd_soc_update_bits(codec, WM8985_OUT4_TO_ADC,
+ WM8985_POBCTRL_MASK,
+ WM8985_POBCTRL);
+ /* enable thermal shutdown */
+ snd_soc_update_bits(codec, WM8985_OUTPUT_CTRL0,
+ WM8985_TSDEN_MASK, WM8985_TSDEN);
+ snd_soc_update_bits(codec, WM8985_OUTPUT_CTRL0,
+ WM8985_TSOPCTRL_MASK,
+ WM8985_TSOPCTRL);
+ /* enable BIASEN */
+ snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+ WM8985_BIASEN_MASK, WM8985_BIASEN);
+ /* VMID at 75k */
+ snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+ WM8985_VMIDSEL_MASK,
+ 1 << WM8985_VMIDSEL_SHIFT);
+ msleep(500);
+ /* disable anti-pop features */
+ snd_soc_update_bits(codec, WM8985_OUT4_TO_ADC,
+ WM8985_POBCTRL_MASK, 0);
+ }
+ /* VMID at 300k */
+ snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+ WM8985_VMIDSEL_MASK,
+ 2 << WM8985_VMIDSEL_SHIFT);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* disable thermal shutdown */
+ snd_soc_update_bits(codec, WM8985_OUTPUT_CTRL0,
+ WM8985_TSOPCTRL_MASK, 0);
+ snd_soc_update_bits(codec, WM8985_OUTPUT_CTRL0,
+ WM8985_TSDEN_MASK, 0);
+ /* disable VMIDSEL and BIASEN */
+ snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+ WM8985_VMIDSEL_MASK | WM8985_BIASEN_MASK,
+ 0);
+ snd_soc_write(codec, WM8985_POWER_MANAGEMENT_1, 0);
+ snd_soc_write(codec, WM8985_POWER_MANAGEMENT_2, 0);
+ snd_soc_write(codec, WM8985_POWER_MANAGEMENT_3, 0);
+
+ codec->cache_sync = 1;
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8985->supplies),
+ wm8985->supplies);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm8985_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8985_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8985_resume(struct snd_soc_codec *codec)
+{
+ wm8985_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+#else
+#define wm8985_suspend NULL
+#define wm8985_resume NULL
+#endif
+
+static int wm8985_remove(struct snd_soc_codec *codec)
+{
+ struct wm8985_priv *wm8985;
+
+ wm8985 = snd_soc_codec_get_drvdata(codec);
+ wm8985_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ regulator_bulk_free(ARRAY_SIZE(wm8985->supplies), wm8985->supplies);
+ return 0;
+}
+
+static int wm8985_probe(struct snd_soc_codec *codec)
+{
+ size_t i;
+ struct wm8985_priv *wm8985;
+ int ret;
+ u16 *cache;
+
+ wm8985 = snd_soc_codec_get_drvdata(codec);
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8985->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache i/o: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8985->supplies); i++)
+ wm8985->supplies[i].supply = wm8985_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8985->supplies),
+ wm8985->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies),
+ wm8985->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_reg_get;
+ }
+
+ ret = wm8985_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ goto err_reg_enable;
+ }
+
+ cache = codec->reg_cache;
+ /* latch volume update bits */
+ for (i = 0; i < ARRAY_SIZE(volume_update_regs); ++i)
+ cache[volume_update_regs[i]] |= 0x100;
+ /* enable BIASCUT */
+ cache[WM8985_BIAS_CTRL] |= WM8985_BIASCUT;
+ codec->cache_sync = 1;
+
+ snd_soc_add_controls(codec, wm8985_snd_controls,
+ ARRAY_SIZE(wm8985_snd_controls));
+ wm8985_add_widgets(codec);
+
+ wm8985_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+
+err_reg_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8985->supplies), wm8985->supplies);
+err_reg_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8985->supplies), wm8985->supplies);
+ return ret;
+}
+
+static struct snd_soc_dai_ops wm8985_dai_ops = {
+ .digital_mute = wm8985_dac_mute,
+ .hw_params = wm8985_hw_params,
+ .set_fmt = wm8985_set_fmt,
+ .set_sysclk = wm8985_set_sysclk,
+ .set_pll = wm8985_set_pll
+};
+
+#define WM8985_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver wm8985_dai = {
+ .name = "wm8985-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = WM8985_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = WM8985_FORMATS,
+ },
+ .ops = &wm8985_dai_ops,
+ .symmetric_rates = 1
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8985 = {
+ .probe = wm8985_probe,
+ .remove = wm8985_remove,
+ .suspend = wm8985_suspend,
+ .resume = wm8985_resume,
+ .set_bias_level = wm8985_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8985_reg_defs),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8985_reg_defs
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8985_spi_probe(struct spi_device *spi)
+{
+ struct wm8985_priv *wm8985;
+ int ret;
+
+ wm8985 = kzalloc(sizeof *wm8985, GFP_KERNEL);
+ if (!wm8985)
+ return -ENOMEM;
+
+ wm8985->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8985);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8985, &wm8985_dai, 1);
+ if (ret < 0)
+ kfree(wm8985);
+ return ret;
+}
+
+static int __devexit wm8985_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8985_spi_driver = {
+ .driver = {
+ .name = "wm8985",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8985_spi_probe,
+ .remove = __devexit_p(wm8985_spi_remove)
+};
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8985_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8985_priv *wm8985;
+ int ret;
+
+ wm8985 = kzalloc(sizeof *wm8985, GFP_KERNEL);
+ if (!wm8985)
+ return -ENOMEM;
+
+ wm8985->control_type = SND_SOC_I2C;
+ i2c_set_clientdata(i2c, wm8985);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8985, &wm8985_dai, 1);
+ if (ret < 0)
+ kfree(wm8985);
+ return ret;
+}
+
+static __devexit int wm8985_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8985_i2c_id[] = {
+ { "wm8985", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8985_i2c_id);
+
+static struct i2c_driver wm8985_i2c_driver = {
+ .driver = {
+ .name = "wm8985",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8985_i2c_probe,
+ .remove = __devexit_p(wm8985_i2c_remove),
+ .id_table = wm8985_i2c_id
+};
+#endif
+
+static int __init wm8985_modinit(void)
+{
+ int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8985_i2c_driver);
+ if (ret) {
+ printk(KERN_ERR "Failed to register wm8985 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8985_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8985 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8985_modinit);
+
+static void __exit wm8985_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8985_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8985_spi_driver);
+#endif
+}
+module_exit(wm8985_exit);
+
+MODULE_DESCRIPTION("ASoC WM8985 driver");
+MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8985.h b/sound/soc/codecs/wm8985.h
new file mode 100644
index 00000000..2e71ff50
--- /dev/null
+++ b/sound/soc/codecs/wm8985.h
@@ -0,0 +1,1045 @@
+/*
+ * wm8985.h -- WM8985 ASoC driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8985_H
+#define _WM8985_H
+
+#define WM8985_SOFTWARE_RESET 0x00
+#define WM8985_POWER_MANAGEMENT_1 0x01
+#define WM8985_POWER_MANAGEMENT_2 0x02
+#define WM8985_POWER_MANAGEMENT_3 0x03
+#define WM8985_AUDIO_INTERFACE 0x04
+#define WM8985_COMPANDING_CONTROL 0x05
+#define WM8985_CLOCK_GEN_CONTROL 0x06
+#define WM8985_ADDITIONAL_CONTROL 0x07
+#define WM8985_GPIO_CONTROL 0x08
+#define WM8985_JACK_DETECT_CONTROL_1 0x09
+#define WM8985_DAC_CONTROL 0x0A
+#define WM8985_LEFT_DAC_DIGITAL_VOL 0x0B
+#define WM8985_RIGHT_DAC_DIGITAL_VOL 0x0C
+#define WM8985_JACK_DETECT_CONTROL_2 0x0D
+#define WM8985_ADC_CONTROL 0x0E
+#define WM8985_LEFT_ADC_DIGITAL_VOL 0x0F
+#define WM8985_RIGHT_ADC_DIGITAL_VOL 0x10
+#define WM8985_EQ1_LOW_SHELF 0x12
+#define WM8985_EQ2_PEAK_1 0x13
+#define WM8985_EQ3_PEAK_2 0x14
+#define WM8985_EQ4_PEAK_3 0x15
+#define WM8985_EQ5_HIGH_SHELF 0x16
+#define WM8985_DAC_LIMITER_1 0x18
+#define WM8985_DAC_LIMITER_2 0x19
+#define WM8985_NOTCH_FILTER_1 0x1B
+#define WM8985_NOTCH_FILTER_2 0x1C
+#define WM8985_NOTCH_FILTER_3 0x1D
+#define WM8985_NOTCH_FILTER_4 0x1E
+#define WM8985_ALC_CONTROL_1 0x20
+#define WM8985_ALC_CONTROL_2 0x21
+#define WM8985_ALC_CONTROL_3 0x22
+#define WM8985_NOISE_GATE 0x23
+#define WM8985_PLL_N 0x24
+#define WM8985_PLL_K_1 0x25
+#define WM8985_PLL_K_2 0x26
+#define WM8985_PLL_K_3 0x27
+#define WM8985_3D_CONTROL 0x29
+#define WM8985_OUT4_TO_ADC 0x2A
+#define WM8985_BEEP_CONTROL 0x2B
+#define WM8985_INPUT_CTRL 0x2C
+#define WM8985_LEFT_INP_PGA_GAIN_CTRL 0x2D
+#define WM8985_RIGHT_INP_PGA_GAIN_CTRL 0x2E
+#define WM8985_LEFT_ADC_BOOST_CTRL 0x2F
+#define WM8985_RIGHT_ADC_BOOST_CTRL 0x30
+#define WM8985_OUTPUT_CTRL0 0x31
+#define WM8985_LEFT_MIXER_CTRL 0x32
+#define WM8985_RIGHT_MIXER_CTRL 0x33
+#define WM8985_LOUT1_HP_VOLUME_CTRL 0x34
+#define WM8985_ROUT1_HP_VOLUME_CTRL 0x35
+#define WM8985_LOUT2_SPK_VOLUME_CTRL 0x36
+#define WM8985_ROUT2_SPK_VOLUME_CTRL 0x37
+#define WM8985_OUT3_MIXER_CTRL 0x38
+#define WM8985_OUT4_MONO_MIX_CTRL 0x39
+#define WM8985_OUTPUT_CTRL1 0x3C
+#define WM8985_BIAS_CTRL 0x3D
+
+#define WM8985_REGISTER_COUNT 59
+#define WM8985_MAX_REGISTER 0x3F
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Software Reset
+ */
+#define WM8985_SOFTWARE_RESET_MASK 0x01FF /* SOFTWARE_RESET - [8:0] */
+#define WM8985_SOFTWARE_RESET_SHIFT 0 /* SOFTWARE_RESET - [8:0] */
+#define WM8985_SOFTWARE_RESET_WIDTH 9 /* SOFTWARE_RESET - [8:0] */
+
+/*
+ * R1 (0x01) - Power management 1
+ */
+#define WM8985_OUT4MIXEN 0x0080 /* OUT4MIXEN */
+#define WM8985_OUT4MIXEN_MASK 0x0080 /* OUT4MIXEN */
+#define WM8985_OUT4MIXEN_SHIFT 7 /* OUT4MIXEN */
+#define WM8985_OUT4MIXEN_WIDTH 1 /* OUT4MIXEN */
+#define WM8985_OUT3MIXEN 0x0040 /* OUT3MIXEN */
+#define WM8985_OUT3MIXEN_MASK 0x0040 /* OUT3MIXEN */
+#define WM8985_OUT3MIXEN_SHIFT 6 /* OUT3MIXEN */
+#define WM8985_OUT3MIXEN_WIDTH 1 /* OUT3MIXEN */
+#define WM8985_PLLEN 0x0020 /* PLLEN */
+#define WM8985_PLLEN_MASK 0x0020 /* PLLEN */
+#define WM8985_PLLEN_SHIFT 5 /* PLLEN */
+#define WM8985_PLLEN_WIDTH 1 /* PLLEN */
+#define WM8985_MICBEN 0x0010 /* MICBEN */
+#define WM8985_MICBEN_MASK 0x0010 /* MICBEN */
+#define WM8985_MICBEN_SHIFT 4 /* MICBEN */
+#define WM8985_MICBEN_WIDTH 1 /* MICBEN */
+#define WM8985_BIASEN 0x0008 /* BIASEN */
+#define WM8985_BIASEN_MASK 0x0008 /* BIASEN */
+#define WM8985_BIASEN_SHIFT 3 /* BIASEN */
+#define WM8985_BIASEN_WIDTH 1 /* BIASEN */
+#define WM8985_BUFIOEN 0x0004 /* BUFIOEN */
+#define WM8985_BUFIOEN_MASK 0x0004 /* BUFIOEN */
+#define WM8985_BUFIOEN_SHIFT 2 /* BUFIOEN */
+#define WM8985_BUFIOEN_WIDTH 1 /* BUFIOEN */
+#define WM8985_VMIDSEL 0x0003 /* VMIDSEL */
+#define WM8985_VMIDSEL_MASK 0x0003 /* VMIDSEL - [1:0] */
+#define WM8985_VMIDSEL_SHIFT 0 /* VMIDSEL - [1:0] */
+#define WM8985_VMIDSEL_WIDTH 2 /* VMIDSEL - [1:0] */
+
+/*
+ * R2 (0x02) - Power management 2
+ */
+#define WM8985_ROUT1EN 0x0100 /* ROUT1EN */
+#define WM8985_ROUT1EN_MASK 0x0100 /* ROUT1EN */
+#define WM8985_ROUT1EN_SHIFT 8 /* ROUT1EN */
+#define WM8985_ROUT1EN_WIDTH 1 /* ROUT1EN */
+#define WM8985_LOUT1EN 0x0080 /* LOUT1EN */
+#define WM8985_LOUT1EN_MASK 0x0080 /* LOUT1EN */
+#define WM8985_LOUT1EN_SHIFT 7 /* LOUT1EN */
+#define WM8985_LOUT1EN_WIDTH 1 /* LOUT1EN */
+#define WM8985_SLEEP 0x0040 /* SLEEP */
+#define WM8985_SLEEP_MASK 0x0040 /* SLEEP */
+#define WM8985_SLEEP_SHIFT 6 /* SLEEP */
+#define WM8985_SLEEP_WIDTH 1 /* SLEEP */
+#define WM8985_BOOSTENR 0x0020 /* BOOSTENR */
+#define WM8985_BOOSTENR_MASK 0x0020 /* BOOSTENR */
+#define WM8985_BOOSTENR_SHIFT 5 /* BOOSTENR */
+#define WM8985_BOOSTENR_WIDTH 1 /* BOOSTENR */
+#define WM8985_BOOSTENL 0x0010 /* BOOSTENL */
+#define WM8985_BOOSTENL_MASK 0x0010 /* BOOSTENL */
+#define WM8985_BOOSTENL_SHIFT 4 /* BOOSTENL */
+#define WM8985_BOOSTENL_WIDTH 1 /* BOOSTENL */
+#define WM8985_INPGAENR 0x0008 /* INPGAENR */
+#define WM8985_INPGAENR_MASK 0x0008 /* INPGAENR */
+#define WM8985_INPGAENR_SHIFT 3 /* INPGAENR */
+#define WM8985_INPGAENR_WIDTH 1 /* INPGAENR */
+#define WM8985_INPPGAENL 0x0004 /* INPPGAENL */
+#define WM8985_INPPGAENL_MASK 0x0004 /* INPPGAENL */
+#define WM8985_INPPGAENL_SHIFT 2 /* INPPGAENL */
+#define WM8985_INPPGAENL_WIDTH 1 /* INPPGAENL */
+#define WM8985_ADCENR 0x0002 /* ADCENR */
+#define WM8985_ADCENR_MASK 0x0002 /* ADCENR */
+#define WM8985_ADCENR_SHIFT 1 /* ADCENR */
+#define WM8985_ADCENR_WIDTH 1 /* ADCENR */
+#define WM8985_ADCENL 0x0001 /* ADCENL */
+#define WM8985_ADCENL_MASK 0x0001 /* ADCENL */
+#define WM8985_ADCENL_SHIFT 0 /* ADCENL */
+#define WM8985_ADCENL_WIDTH 1 /* ADCENL */
+
+/*
+ * R3 (0x03) - Power management 3
+ */
+#define WM8985_OUT4EN 0x0100 /* OUT4EN */
+#define WM8985_OUT4EN_MASK 0x0100 /* OUT4EN */
+#define WM8985_OUT4EN_SHIFT 8 /* OUT4EN */
+#define WM8985_OUT4EN_WIDTH 1 /* OUT4EN */
+#define WM8985_OUT3EN 0x0080 /* OUT3EN */
+#define WM8985_OUT3EN_MASK 0x0080 /* OUT3EN */
+#define WM8985_OUT3EN_SHIFT 7 /* OUT3EN */
+#define WM8985_OUT3EN_WIDTH 1 /* OUT3EN */
+#define WM8985_ROUT2EN 0x0040 /* ROUT2EN */
+#define WM8985_ROUT2EN_MASK 0x0040 /* ROUT2EN */
+#define WM8985_ROUT2EN_SHIFT 6 /* ROUT2EN */
+#define WM8985_ROUT2EN_WIDTH 1 /* ROUT2EN */
+#define WM8985_LOUT2EN 0x0020 /* LOUT2EN */
+#define WM8985_LOUT2EN_MASK 0x0020 /* LOUT2EN */
+#define WM8985_LOUT2EN_SHIFT 5 /* LOUT2EN */
+#define WM8985_LOUT2EN_WIDTH 1 /* LOUT2EN */
+#define WM8985_RMIXEN 0x0008 /* RMIXEN */
+#define WM8985_RMIXEN_MASK 0x0008 /* RMIXEN */
+#define WM8985_RMIXEN_SHIFT 3 /* RMIXEN */
+#define WM8985_RMIXEN_WIDTH 1 /* RMIXEN */
+#define WM8985_LMIXEN 0x0004 /* LMIXEN */
+#define WM8985_LMIXEN_MASK 0x0004 /* LMIXEN */
+#define WM8985_LMIXEN_SHIFT 2 /* LMIXEN */
+#define WM8985_LMIXEN_WIDTH 1 /* LMIXEN */
+#define WM8985_DACENR 0x0002 /* DACENR */
+#define WM8985_DACENR_MASK 0x0002 /* DACENR */
+#define WM8985_DACENR_SHIFT 1 /* DACENR */
+#define WM8985_DACENR_WIDTH 1 /* DACENR */
+#define WM8985_DACENL 0x0001 /* DACENL */
+#define WM8985_DACENL_MASK 0x0001 /* DACENL */
+#define WM8985_DACENL_SHIFT 0 /* DACENL */
+#define WM8985_DACENL_WIDTH 1 /* DACENL */
+
+/*
+ * R4 (0x04) - Audio Interface
+ */
+#define WM8985_BCP 0x0100 /* BCP */
+#define WM8985_BCP_MASK 0x0100 /* BCP */
+#define WM8985_BCP_SHIFT 8 /* BCP */
+#define WM8985_BCP_WIDTH 1 /* BCP */
+#define WM8985_LRP 0x0080 /* LRP */
+#define WM8985_LRP_MASK 0x0080 /* LRP */
+#define WM8985_LRP_SHIFT 7 /* LRP */
+#define WM8985_LRP_WIDTH 1 /* LRP */
+#define WM8985_WL_MASK 0x0060 /* WL - [6:5] */
+#define WM8985_WL_SHIFT 5 /* WL - [6:5] */
+#define WM8985_WL_WIDTH 2 /* WL - [6:5] */
+#define WM8985_FMT_MASK 0x0018 /* FMT - [4:3] */
+#define WM8985_FMT_SHIFT 3 /* FMT - [4:3] */
+#define WM8985_FMT_WIDTH 2 /* FMT - [4:3] */
+#define WM8985_DLRSWAP 0x0004 /* DLRSWAP */
+#define WM8985_DLRSWAP_MASK 0x0004 /* DLRSWAP */
+#define WM8985_DLRSWAP_SHIFT 2 /* DLRSWAP */
+#define WM8985_DLRSWAP_WIDTH 1 /* DLRSWAP */
+#define WM8985_ALRSWAP 0x0002 /* ALRSWAP */
+#define WM8985_ALRSWAP_MASK 0x0002 /* ALRSWAP */
+#define WM8985_ALRSWAP_SHIFT 1 /* ALRSWAP */
+#define WM8985_ALRSWAP_WIDTH 1 /* ALRSWAP */
+#define WM8985_MONO 0x0001 /* MONO */
+#define WM8985_MONO_MASK 0x0001 /* MONO */
+#define WM8985_MONO_SHIFT 0 /* MONO */
+#define WM8985_MONO_WIDTH 1 /* MONO */
+
+/*
+ * R5 (0x05) - Companding control
+ */
+#define WM8985_WL8 0x0020 /* WL8 */
+#define WM8985_WL8_MASK 0x0020 /* WL8 */
+#define WM8985_WL8_SHIFT 5 /* WL8 */
+#define WM8985_WL8_WIDTH 1 /* WL8 */
+#define WM8985_DAC_COMP_MASK 0x0018 /* DAC_COMP - [4:3] */
+#define WM8985_DAC_COMP_SHIFT 3 /* DAC_COMP - [4:3] */
+#define WM8985_DAC_COMP_WIDTH 2 /* DAC_COMP - [4:3] */
+#define WM8985_ADC_COMP_MASK 0x0006 /* ADC_COMP - [2:1] */
+#define WM8985_ADC_COMP_SHIFT 1 /* ADC_COMP - [2:1] */
+#define WM8985_ADC_COMP_WIDTH 2 /* ADC_COMP - [2:1] */
+#define WM8985_LOOPBACK 0x0001 /* LOOPBACK */
+#define WM8985_LOOPBACK_MASK 0x0001 /* LOOPBACK */
+#define WM8985_LOOPBACK_SHIFT 0 /* LOOPBACK */
+#define WM8985_LOOPBACK_WIDTH 1 /* LOOPBACK */
+
+/*
+ * R6 (0x06) - Clock Gen control
+ */
+#define WM8985_CLKSEL 0x0100 /* CLKSEL */
+#define WM8985_CLKSEL_MASK 0x0100 /* CLKSEL */
+#define WM8985_CLKSEL_SHIFT 8 /* CLKSEL */
+#define WM8985_CLKSEL_WIDTH 1 /* CLKSEL */
+#define WM8985_MCLKDIV_MASK 0x00E0 /* MCLKDIV - [7:5] */
+#define WM8985_MCLKDIV_SHIFT 5 /* MCLKDIV - [7:5] */
+#define WM8985_MCLKDIV_WIDTH 3 /* MCLKDIV - [7:5] */
+#define WM8985_BCLKDIV_MASK 0x001C /* BCLKDIV - [4:2] */
+#define WM8985_BCLKDIV_SHIFT 2 /* BCLKDIV - [4:2] */
+#define WM8985_BCLKDIV_WIDTH 3 /* BCLKDIV - [4:2] */
+#define WM8985_MS 0x0001 /* MS */
+#define WM8985_MS_MASK 0x0001 /* MS */
+#define WM8985_MS_SHIFT 0 /* MS */
+#define WM8985_MS_WIDTH 1 /* MS */
+
+/*
+ * R7 (0x07) - Additional control
+ */
+#define WM8985_M128ENB 0x0100 /* M128ENB */
+#define WM8985_M128ENB_MASK 0x0100 /* M128ENB */
+#define WM8985_M128ENB_SHIFT 8 /* M128ENB */
+#define WM8985_M128ENB_WIDTH 1 /* M128ENB */
+#define WM8985_DCLKDIV_MASK 0x00F0 /* DCLKDIV - [7:4] */
+#define WM8985_DCLKDIV_SHIFT 4 /* DCLKDIV - [7:4] */
+#define WM8985_DCLKDIV_WIDTH 4 /* DCLKDIV - [7:4] */
+#define WM8985_SR_MASK 0x000E /* SR - [3:1] */
+#define WM8985_SR_SHIFT 1 /* SR - [3:1] */
+#define WM8985_SR_WIDTH 3 /* SR - [3:1] */
+#define WM8985_SLOWCLKEN 0x0001 /* SLOWCLKEN */
+#define WM8985_SLOWCLKEN_MASK 0x0001 /* SLOWCLKEN */
+#define WM8985_SLOWCLKEN_SHIFT 0 /* SLOWCLKEN */
+#define WM8985_SLOWCLKEN_WIDTH 1 /* SLOWCLKEN */
+
+/*
+ * R8 (0x08) - GPIO Control
+ */
+#define WM8985_GPIO1GP 0x0100 /* GPIO1GP */
+#define WM8985_GPIO1GP_MASK 0x0100 /* GPIO1GP */
+#define WM8985_GPIO1GP_SHIFT 8 /* GPIO1GP */
+#define WM8985_GPIO1GP_WIDTH 1 /* GPIO1GP */
+#define WM8985_GPIO1GPU 0x0080 /* GPIO1GPU */
+#define WM8985_GPIO1GPU_MASK 0x0080 /* GPIO1GPU */
+#define WM8985_GPIO1GPU_SHIFT 7 /* GPIO1GPU */
+#define WM8985_GPIO1GPU_WIDTH 1 /* GPIO1GPU */
+#define WM8985_GPIO1GPD 0x0040 /* GPIO1GPD */
+#define WM8985_GPIO1GPD_MASK 0x0040 /* GPIO1GPD */
+#define WM8985_GPIO1GPD_SHIFT 6 /* GPIO1GPD */
+#define WM8985_GPIO1GPD_WIDTH 1 /* GPIO1GPD */
+#define WM8985_GPIO1POL 0x0008 /* GPIO1POL */
+#define WM8985_GPIO1POL_MASK 0x0008 /* GPIO1POL */
+#define WM8985_GPIO1POL_SHIFT 3 /* GPIO1POL */
+#define WM8985_GPIO1POL_WIDTH 1 /* GPIO1POL */
+#define WM8985_GPIO1SEL_MASK 0x0007 /* GPIO1SEL - [2:0] */
+#define WM8985_GPIO1SEL_SHIFT 0 /* GPIO1SEL - [2:0] */
+#define WM8985_GPIO1SEL_WIDTH 3 /* GPIO1SEL - [2:0] */
+
+/*
+ * R9 (0x09) - Jack Detect Control 1
+ */
+#define WM8985_JD_EN 0x0040 /* JD_EN */
+#define WM8985_JD_EN_MASK 0x0040 /* JD_EN */
+#define WM8985_JD_EN_SHIFT 6 /* JD_EN */
+#define WM8985_JD_EN_WIDTH 1 /* JD_EN */
+#define WM8985_JD_SEL_MASK 0x0030 /* JD_SEL - [5:4] */
+#define WM8985_JD_SEL_SHIFT 4 /* JD_SEL - [5:4] */
+#define WM8985_JD_SEL_WIDTH 2 /* JD_SEL - [5:4] */
+
+/*
+ * R10 (0x0A) - DAC Control
+ */
+#define WM8985_SOFTMUTE 0x0040 /* SOFTMUTE */
+#define WM8985_SOFTMUTE_MASK 0x0040 /* SOFTMUTE */
+#define WM8985_SOFTMUTE_SHIFT 6 /* SOFTMUTE */
+#define WM8985_SOFTMUTE_WIDTH 1 /* SOFTMUTE */
+#define WM8985_DACOSR128 0x0008 /* DACOSR128 */
+#define WM8985_DACOSR128_MASK 0x0008 /* DACOSR128 */
+#define WM8985_DACOSR128_SHIFT 3 /* DACOSR128 */
+#define WM8985_DACOSR128_WIDTH 1 /* DACOSR128 */
+#define WM8985_AMUTE 0x0004 /* AMUTE */
+#define WM8985_AMUTE_MASK 0x0004 /* AMUTE */
+#define WM8985_AMUTE_SHIFT 2 /* AMUTE */
+#define WM8985_AMUTE_WIDTH 1 /* AMUTE */
+#define WM8985_DACPOLR 0x0002 /* DACPOLR */
+#define WM8985_DACPOLR_MASK 0x0002 /* DACPOLR */
+#define WM8985_DACPOLR_SHIFT 1 /* DACPOLR */
+#define WM8985_DACPOLR_WIDTH 1 /* DACPOLR */
+#define WM8985_DACPOLL 0x0001 /* DACPOLL */
+#define WM8985_DACPOLL_MASK 0x0001 /* DACPOLL */
+#define WM8985_DACPOLL_SHIFT 0 /* DACPOLL */
+#define WM8985_DACPOLL_WIDTH 1 /* DACPOLL */
+
+/*
+ * R11 (0x0B) - Left DAC digital Vol
+ */
+#define WM8985_DACVU 0x0100 /* DACVU */
+#define WM8985_DACVU_MASK 0x0100 /* DACVU */
+#define WM8985_DACVU_SHIFT 8 /* DACVU */
+#define WM8985_DACVU_WIDTH 1 /* DACVU */
+#define WM8985_DACVOLL_MASK 0x00FF /* DACVOLL - [7:0] */
+#define WM8985_DACVOLL_SHIFT 0 /* DACVOLL - [7:0] */
+#define WM8985_DACVOLL_WIDTH 8 /* DACVOLL - [7:0] */
+
+/*
+ * R12 (0x0C) - Right DAC digital vol
+ */
+#define WM8985_DACVU 0x0100 /* DACVU */
+#define WM8985_DACVU_MASK 0x0100 /* DACVU */
+#define WM8985_DACVU_SHIFT 8 /* DACVU */
+#define WM8985_DACVU_WIDTH 1 /* DACVU */
+#define WM8985_DACVOLR_MASK 0x00FF /* DACVOLR - [7:0] */
+#define WM8985_DACVOLR_SHIFT 0 /* DACVOLR - [7:0] */
+#define WM8985_DACVOLR_WIDTH 8 /* DACVOLR - [7:0] */
+
+/*
+ * R13 (0x0D) - Jack Detect Control 2
+ */
+#define WM8985_JD_EN1_MASK 0x00F0 /* JD_EN1 - [7:4] */
+#define WM8985_JD_EN1_SHIFT 4 /* JD_EN1 - [7:4] */
+#define WM8985_JD_EN1_WIDTH 4 /* JD_EN1 - [7:4] */
+#define WM8985_JD_EN0_MASK 0x000F /* JD_EN0 - [3:0] */
+#define WM8985_JD_EN0_SHIFT 0 /* JD_EN0 - [3:0] */
+#define WM8985_JD_EN0_WIDTH 4 /* JD_EN0 - [3:0] */
+
+/*
+ * R14 (0x0E) - ADC Control
+ */
+#define WM8985_HPFEN 0x0100 /* HPFEN */
+#define WM8985_HPFEN_MASK 0x0100 /* HPFEN */
+#define WM8985_HPFEN_SHIFT 8 /* HPFEN */
+#define WM8985_HPFEN_WIDTH 1 /* HPFEN */
+#define WM8985_HPFAPP 0x0080 /* HPFAPP */
+#define WM8985_HPFAPP_MASK 0x0080 /* HPFAPP */
+#define WM8985_HPFAPP_SHIFT 7 /* HPFAPP */
+#define WM8985_HPFAPP_WIDTH 1 /* HPFAPP */
+#define WM8985_HPFCUT_MASK 0x0070 /* HPFCUT - [6:4] */
+#define WM8985_HPFCUT_SHIFT 4 /* HPFCUT - [6:4] */
+#define WM8985_HPFCUT_WIDTH 3 /* HPFCUT - [6:4] */
+#define WM8985_ADCOSR128 0x0008 /* ADCOSR128 */
+#define WM8985_ADCOSR128_MASK 0x0008 /* ADCOSR128 */
+#define WM8985_ADCOSR128_SHIFT 3 /* ADCOSR128 */
+#define WM8985_ADCOSR128_WIDTH 1 /* ADCOSR128 */
+#define WM8985_ADCRPOL 0x0002 /* ADCRPOL */
+#define WM8985_ADCRPOL_MASK 0x0002 /* ADCRPOL */
+#define WM8985_ADCRPOL_SHIFT 1 /* ADCRPOL */
+#define WM8985_ADCRPOL_WIDTH 1 /* ADCRPOL */
+#define WM8985_ADCLPOL 0x0001 /* ADCLPOL */
+#define WM8985_ADCLPOL_MASK 0x0001 /* ADCLPOL */
+#define WM8985_ADCLPOL_SHIFT 0 /* ADCLPOL */
+#define WM8985_ADCLPOL_WIDTH 1 /* ADCLPOL */
+
+/*
+ * R15 (0x0F) - Left ADC Digital Vol
+ */
+#define WM8985_ADCVU 0x0100 /* ADCVU */
+#define WM8985_ADCVU_MASK 0x0100 /* ADCVU */
+#define WM8985_ADCVU_SHIFT 8 /* ADCVU */
+#define WM8985_ADCVU_WIDTH 1 /* ADCVU */
+#define WM8985_ADCVOLL_MASK 0x00FF /* ADCVOLL - [7:0] */
+#define WM8985_ADCVOLL_SHIFT 0 /* ADCVOLL - [7:0] */
+#define WM8985_ADCVOLL_WIDTH 8 /* ADCVOLL - [7:0] */
+
+/*
+ * R16 (0x10) - Right ADC Digital Vol
+ */
+#define WM8985_ADCVU 0x0100 /* ADCVU */
+#define WM8985_ADCVU_MASK 0x0100 /* ADCVU */
+#define WM8985_ADCVU_SHIFT 8 /* ADCVU */
+#define WM8985_ADCVU_WIDTH 1 /* ADCVU */
+#define WM8985_ADCVOLR_MASK 0x00FF /* ADCVOLR - [7:0] */
+#define WM8985_ADCVOLR_SHIFT 0 /* ADCVOLR - [7:0] */
+#define WM8985_ADCVOLR_WIDTH 8 /* ADCVOLR - [7:0] */
+
+/*
+ * R18 (0x12) - EQ1 - low shelf
+ */
+#define WM8985_EQ3DMODE 0x0100 /* EQ3DMODE */
+#define WM8985_EQ3DMODE_MASK 0x0100 /* EQ3DMODE */
+#define WM8985_EQ3DMODE_SHIFT 8 /* EQ3DMODE */
+#define WM8985_EQ3DMODE_WIDTH 1 /* EQ3DMODE */
+#define WM8985_EQ1C_MASK 0x0060 /* EQ1C - [6:5] */
+#define WM8985_EQ1C_SHIFT 5 /* EQ1C - [6:5] */
+#define WM8985_EQ1C_WIDTH 2 /* EQ1C - [6:5] */
+#define WM8985_EQ1G_MASK 0x001F /* EQ1G - [4:0] */
+#define WM8985_EQ1G_SHIFT 0 /* EQ1G - [4:0] */
+#define WM8985_EQ1G_WIDTH 5 /* EQ1G - [4:0] */
+
+/*
+ * R19 (0x13) - EQ2 - peak 1
+ */
+#define WM8985_EQ2BW 0x0100 /* EQ2BW */
+#define WM8985_EQ2BW_MASK 0x0100 /* EQ2BW */
+#define WM8985_EQ2BW_SHIFT 8 /* EQ2BW */
+#define WM8985_EQ2BW_WIDTH 1 /* EQ2BW */
+#define WM8985_EQ2C_MASK 0x0060 /* EQ2C - [6:5] */
+#define WM8985_EQ2C_SHIFT 5 /* EQ2C - [6:5] */
+#define WM8985_EQ2C_WIDTH 2 /* EQ2C - [6:5] */
+#define WM8985_EQ2G_MASK 0x001F /* EQ2G - [4:0] */
+#define WM8985_EQ2G_SHIFT 0 /* EQ2G - [4:0] */
+#define WM8985_EQ2G_WIDTH 5 /* EQ2G - [4:0] */
+
+/*
+ * R20 (0x14) - EQ3 - peak 2
+ */
+#define WM8985_EQ3BW 0x0100 /* EQ3BW */
+#define WM8985_EQ3BW_MASK 0x0100 /* EQ3BW */
+#define WM8985_EQ3BW_SHIFT 8 /* EQ3BW */
+#define WM8985_EQ3BW_WIDTH 1 /* EQ3BW */
+#define WM8985_EQ3C_MASK 0x0060 /* EQ3C - [6:5] */
+#define WM8985_EQ3C_SHIFT 5 /* EQ3C - [6:5] */
+#define WM8985_EQ3C_WIDTH 2 /* EQ3C - [6:5] */
+#define WM8985_EQ3G_MASK 0x001F /* EQ3G - [4:0] */
+#define WM8985_EQ3G_SHIFT 0 /* EQ3G - [4:0] */
+#define WM8985_EQ3G_WIDTH 5 /* EQ3G - [4:0] */
+
+/*
+ * R21 (0x15) - EQ4 - peak 3
+ */
+#define WM8985_EQ4BW 0x0100 /* EQ4BW */
+#define WM8985_EQ4BW_MASK 0x0100 /* EQ4BW */
+#define WM8985_EQ4BW_SHIFT 8 /* EQ4BW */
+#define WM8985_EQ4BW_WIDTH 1 /* EQ4BW */
+#define WM8985_EQ4C_MASK 0x0060 /* EQ4C - [6:5] */
+#define WM8985_EQ4C_SHIFT 5 /* EQ4C - [6:5] */
+#define WM8985_EQ4C_WIDTH 2 /* EQ4C - [6:5] */
+#define WM8985_EQ4G_MASK 0x001F /* EQ4G - [4:0] */
+#define WM8985_EQ4G_SHIFT 0 /* EQ4G - [4:0] */
+#define WM8985_EQ4G_WIDTH 5 /* EQ4G - [4:0] */
+
+/*
+ * R22 (0x16) - EQ5 - high shelf
+ */
+#define WM8985_EQ5C_MASK 0x0060 /* EQ5C - [6:5] */
+#define WM8985_EQ5C_SHIFT 5 /* EQ5C - [6:5] */
+#define WM8985_EQ5C_WIDTH 2 /* EQ5C - [6:5] */
+#define WM8985_EQ5G_MASK 0x001F /* EQ5G - [4:0] */
+#define WM8985_EQ5G_SHIFT 0 /* EQ5G - [4:0] */
+#define WM8985_EQ5G_WIDTH 5 /* EQ5G - [4:0] */
+
+/*
+ * R24 (0x18) - DAC Limiter 1
+ */
+#define WM8985_LIMEN 0x0100 /* LIMEN */
+#define WM8985_LIMEN_MASK 0x0100 /* LIMEN */
+#define WM8985_LIMEN_SHIFT 8 /* LIMEN */
+#define WM8985_LIMEN_WIDTH 1 /* LIMEN */
+#define WM8985_LIMDCY_MASK 0x00F0 /* LIMDCY - [7:4] */
+#define WM8985_LIMDCY_SHIFT 4 /* LIMDCY - [7:4] */
+#define WM8985_LIMDCY_WIDTH 4 /* LIMDCY - [7:4] */
+#define WM8985_LIMATK_MASK 0x000F /* LIMATK - [3:0] */
+#define WM8985_LIMATK_SHIFT 0 /* LIMATK - [3:0] */
+#define WM8985_LIMATK_WIDTH 4 /* LIMATK - [3:0] */
+
+/*
+ * R25 (0x19) - DAC Limiter 2
+ */
+#define WM8985_LIMLVL_MASK 0x0070 /* LIMLVL - [6:4] */
+#define WM8985_LIMLVL_SHIFT 4 /* LIMLVL - [6:4] */
+#define WM8985_LIMLVL_WIDTH 3 /* LIMLVL - [6:4] */
+#define WM8985_LIMBOOST_MASK 0x000F /* LIMBOOST - [3:0] */
+#define WM8985_LIMBOOST_SHIFT 0 /* LIMBOOST - [3:0] */
+#define WM8985_LIMBOOST_WIDTH 4 /* LIMBOOST - [3:0] */
+
+/*
+ * R27 (0x1B) - Notch Filter 1
+ */
+#define WM8985_NFU 0x0100 /* NFU */
+#define WM8985_NFU_MASK 0x0100 /* NFU */
+#define WM8985_NFU_SHIFT 8 /* NFU */
+#define WM8985_NFU_WIDTH 1 /* NFU */
+#define WM8985_NFEN 0x0080 /* NFEN */
+#define WM8985_NFEN_MASK 0x0080 /* NFEN */
+#define WM8985_NFEN_SHIFT 7 /* NFEN */
+#define WM8985_NFEN_WIDTH 1 /* NFEN */
+#define WM8985_NFA0_13_7_MASK 0x007F /* NFA0(13:7) - [6:0] */
+#define WM8985_NFA0_13_7_SHIFT 0 /* NFA0(13:7) - [6:0] */
+#define WM8985_NFA0_13_7_WIDTH 7 /* NFA0(13:7) - [6:0] */
+
+/*
+ * R28 (0x1C) - Notch Filter 2
+ */
+#define WM8985_NFU 0x0100 /* NFU */
+#define WM8985_NFU_MASK 0x0100 /* NFU */
+#define WM8985_NFU_SHIFT 8 /* NFU */
+#define WM8985_NFU_WIDTH 1 /* NFU */
+#define WM8985_NFA0_6_0_MASK 0x007F /* NFA0(6:0) - [6:0] */
+#define WM8985_NFA0_6_0_SHIFT 0 /* NFA0(6:0) - [6:0] */
+#define WM8985_NFA0_6_0_WIDTH 7 /* NFA0(6:0) - [6:0] */
+
+/*
+ * R29 (0x1D) - Notch Filter 3
+ */
+#define WM8985_NFU 0x0100 /* NFU */
+#define WM8985_NFU_MASK 0x0100 /* NFU */
+#define WM8985_NFU_SHIFT 8 /* NFU */
+#define WM8985_NFU_WIDTH 1 /* NFU */
+#define WM8985_NFA1_13_7_MASK 0x007F /* NFA1(13:7) - [6:0] */
+#define WM8985_NFA1_13_7_SHIFT 0 /* NFA1(13:7) - [6:0] */
+#define WM8985_NFA1_13_7_WIDTH 7 /* NFA1(13:7) - [6:0] */
+
+/*
+ * R30 (0x1E) - Notch Filter 4
+ */
+#define WM8985_NFU 0x0100 /* NFU */
+#define WM8985_NFU_MASK 0x0100 /* NFU */
+#define WM8985_NFU_SHIFT 8 /* NFU */
+#define WM8985_NFU_WIDTH 1 /* NFU */
+#define WM8985_NFA1_6_0_MASK 0x007F /* NFA1(6:0) - [6:0] */
+#define WM8985_NFA1_6_0_SHIFT 0 /* NFA1(6:0) - [6:0] */
+#define WM8985_NFA1_6_0_WIDTH 7 /* NFA1(6:0) - [6:0] */
+
+/*
+ * R32 (0x20) - ALC control 1
+ */
+#define WM8985_ALCSEL_MASK 0x0180 /* ALCSEL - [8:7] */
+#define WM8985_ALCSEL_SHIFT 7 /* ALCSEL - [8:7] */
+#define WM8985_ALCSEL_WIDTH 2 /* ALCSEL - [8:7] */
+#define WM8985_ALCMAX_MASK 0x0038 /* ALCMAX - [5:3] */
+#define WM8985_ALCMAX_SHIFT 3 /* ALCMAX - [5:3] */
+#define WM8985_ALCMAX_WIDTH 3 /* ALCMAX - [5:3] */
+#define WM8985_ALCMIN_MASK 0x0007 /* ALCMIN - [2:0] */
+#define WM8985_ALCMIN_SHIFT 0 /* ALCMIN - [2:0] */
+#define WM8985_ALCMIN_WIDTH 3 /* ALCMIN - [2:0] */
+
+/*
+ * R33 (0x21) - ALC control 2
+ */
+#define WM8985_ALCHLD_MASK 0x00F0 /* ALCHLD - [7:4] */
+#define WM8985_ALCHLD_SHIFT 4 /* ALCHLD - [7:4] */
+#define WM8985_ALCHLD_WIDTH 4 /* ALCHLD - [7:4] */
+#define WM8985_ALCLVL_MASK 0x000F /* ALCLVL - [3:0] */
+#define WM8985_ALCLVL_SHIFT 0 /* ALCLVL - [3:0] */
+#define WM8985_ALCLVL_WIDTH 4 /* ALCLVL - [3:0] */
+
+/*
+ * R34 (0x22) - ALC control 3
+ */
+#define WM8985_ALCMODE 0x0100 /* ALCMODE */
+#define WM8985_ALCMODE_MASK 0x0100 /* ALCMODE */
+#define WM8985_ALCMODE_SHIFT 8 /* ALCMODE */
+#define WM8985_ALCMODE_WIDTH 1 /* ALCMODE */
+#define WM8985_ALCDCY_MASK 0x00F0 /* ALCDCY - [7:4] */
+#define WM8985_ALCDCY_SHIFT 4 /* ALCDCY - [7:4] */
+#define WM8985_ALCDCY_WIDTH 4 /* ALCDCY - [7:4] */
+#define WM8985_ALCATK_MASK 0x000F /* ALCATK - [3:0] */
+#define WM8985_ALCATK_SHIFT 0 /* ALCATK - [3:0] */
+#define WM8985_ALCATK_WIDTH 4 /* ALCATK - [3:0] */
+
+/*
+ * R35 (0x23) - Noise Gate
+ */
+#define WM8985_NGEN 0x0008 /* NGEN */
+#define WM8985_NGEN_MASK 0x0008 /* NGEN */
+#define WM8985_NGEN_SHIFT 3 /* NGEN */
+#define WM8985_NGEN_WIDTH 1 /* NGEN */
+#define WM8985_NGTH_MASK 0x0007 /* NGTH - [2:0] */
+#define WM8985_NGTH_SHIFT 0 /* NGTH - [2:0] */
+#define WM8985_NGTH_WIDTH 3 /* NGTH - [2:0] */
+
+/*
+ * R36 (0x24) - PLL N
+ */
+#define WM8985_PLL_PRESCALE 0x0010 /* PLL_PRESCALE */
+#define WM8985_PLL_PRESCALE_MASK 0x0010 /* PLL_PRESCALE */
+#define WM8985_PLL_PRESCALE_SHIFT 4 /* PLL_PRESCALE */
+#define WM8985_PLL_PRESCALE_WIDTH 1 /* PLL_PRESCALE */
+#define WM8985_PLLN_MASK 0x000F /* PLLN - [3:0] */
+#define WM8985_PLLN_SHIFT 0 /* PLLN - [3:0] */
+#define WM8985_PLLN_WIDTH 4 /* PLLN - [3:0] */
+
+/*
+ * R37 (0x25) - PLL K 1
+ */
+#define WM8985_PLLK_23_18_MASK 0x003F /* PLLK(23:18) - [5:0] */
+#define WM8985_PLLK_23_18_SHIFT 0 /* PLLK(23:18) - [5:0] */
+#define WM8985_PLLK_23_18_WIDTH 6 /* PLLK(23:18) - [5:0] */
+
+/*
+ * R38 (0x26) - PLL K 2
+ */
+#define WM8985_PLLK_17_9_MASK 0x01FF /* PLLK(17:9) - [8:0] */
+#define WM8985_PLLK_17_9_SHIFT 0 /* PLLK(17:9) - [8:0] */
+#define WM8985_PLLK_17_9_WIDTH 9 /* PLLK(17:9) - [8:0] */
+
+/*
+ * R39 (0x27) - PLL K 3
+ */
+#define WM8985_PLLK_8_0_MASK 0x01FF /* PLLK(8:0) - [8:0] */
+#define WM8985_PLLK_8_0_SHIFT 0 /* PLLK(8:0) - [8:0] */
+#define WM8985_PLLK_8_0_WIDTH 9 /* PLLK(8:0) - [8:0] */
+
+/*
+ * R41 (0x29) - 3D control
+ */
+#define WM8985_DEPTH3D_MASK 0x000F /* DEPTH3D - [3:0] */
+#define WM8985_DEPTH3D_SHIFT 0 /* DEPTH3D - [3:0] */
+#define WM8985_DEPTH3D_WIDTH 4 /* DEPTH3D - [3:0] */
+
+/*
+ * R42 (0x2A) - OUT4 to ADC
+ */
+#define WM8985_OUT4_2ADCVOL_MASK 0x01C0 /* OUT4_2ADCVOL - [8:6] */
+#define WM8985_OUT4_2ADCVOL_SHIFT 6 /* OUT4_2ADCVOL - [8:6] */
+#define WM8985_OUT4_2ADCVOL_WIDTH 3 /* OUT4_2ADCVOL - [8:6] */
+#define WM8985_OUT4_2LNR 0x0020 /* OUT4_2LNR */
+#define WM8985_OUT4_2LNR_MASK 0x0020 /* OUT4_2LNR */
+#define WM8985_OUT4_2LNR_SHIFT 5 /* OUT4_2LNR */
+#define WM8985_OUT4_2LNR_WIDTH 1 /* OUT4_2LNR */
+#define WM8985_POBCTRL 0x0004 /* POBCTRL */
+#define WM8985_POBCTRL_MASK 0x0004 /* POBCTRL */
+#define WM8985_POBCTRL_SHIFT 2 /* POBCTRL */
+#define WM8985_POBCTRL_WIDTH 1 /* POBCTRL */
+#define WM8985_DELEN 0x0002 /* DELEN */
+#define WM8985_DELEN_MASK 0x0002 /* DELEN */
+#define WM8985_DELEN_SHIFT 1 /* DELEN */
+#define WM8985_DELEN_WIDTH 1 /* DELEN */
+#define WM8985_OUT1DEL 0x0001 /* OUT1DEL */
+#define WM8985_OUT1DEL_MASK 0x0001 /* OUT1DEL */
+#define WM8985_OUT1DEL_SHIFT 0 /* OUT1DEL */
+#define WM8985_OUT1DEL_WIDTH 1 /* OUT1DEL */
+
+/*
+ * R43 (0x2B) - Beep control
+ */
+#define WM8985_BYPL2RMIX 0x0100 /* BYPL2RMIX */
+#define WM8985_BYPL2RMIX_MASK 0x0100 /* BYPL2RMIX */
+#define WM8985_BYPL2RMIX_SHIFT 8 /* BYPL2RMIX */
+#define WM8985_BYPL2RMIX_WIDTH 1 /* BYPL2RMIX */
+#define WM8985_BYPR2LMIX 0x0080 /* BYPR2LMIX */
+#define WM8985_BYPR2LMIX_MASK 0x0080 /* BYPR2LMIX */
+#define WM8985_BYPR2LMIX_SHIFT 7 /* BYPR2LMIX */
+#define WM8985_BYPR2LMIX_WIDTH 1 /* BYPR2LMIX */
+#define WM8985_MUTERPGA2INV 0x0020 /* MUTERPGA2INV */
+#define WM8985_MUTERPGA2INV_MASK 0x0020 /* MUTERPGA2INV */
+#define WM8985_MUTERPGA2INV_SHIFT 5 /* MUTERPGA2INV */
+#define WM8985_MUTERPGA2INV_WIDTH 1 /* MUTERPGA2INV */
+#define WM8985_INVROUT2 0x0010 /* INVROUT2 */
+#define WM8985_INVROUT2_MASK 0x0010 /* INVROUT2 */
+#define WM8985_INVROUT2_SHIFT 4 /* INVROUT2 */
+#define WM8985_INVROUT2_WIDTH 1 /* INVROUT2 */
+#define WM8985_BEEPVOL_MASK 0x000E /* BEEPVOL - [3:1] */
+#define WM8985_BEEPVOL_SHIFT 1 /* BEEPVOL - [3:1] */
+#define WM8985_BEEPVOL_WIDTH 3 /* BEEPVOL - [3:1] */
+#define WM8985_BEEPEN 0x0001 /* BEEPEN */
+#define WM8985_BEEPEN_MASK 0x0001 /* BEEPEN */
+#define WM8985_BEEPEN_SHIFT 0 /* BEEPEN */
+#define WM8985_BEEPEN_WIDTH 1 /* BEEPEN */
+
+/*
+ * R44 (0x2C) - Input ctrl
+ */
+#define WM8985_MBVSEL 0x0100 /* MBVSEL */
+#define WM8985_MBVSEL_MASK 0x0100 /* MBVSEL */
+#define WM8985_MBVSEL_SHIFT 8 /* MBVSEL */
+#define WM8985_MBVSEL_WIDTH 1 /* MBVSEL */
+#define WM8985_R2_2INPPGA 0x0040 /* R2_2INPPGA */
+#define WM8985_R2_2INPPGA_MASK 0x0040 /* R2_2INPPGA */
+#define WM8985_R2_2INPPGA_SHIFT 6 /* R2_2INPPGA */
+#define WM8985_R2_2INPPGA_WIDTH 1 /* R2_2INPPGA */
+#define WM8985_RIN2INPPGA 0x0020 /* RIN2INPPGA */
+#define WM8985_RIN2INPPGA_MASK 0x0020 /* RIN2INPPGA */
+#define WM8985_RIN2INPPGA_SHIFT 5 /* RIN2INPPGA */
+#define WM8985_RIN2INPPGA_WIDTH 1 /* RIN2INPPGA */
+#define WM8985_RIP2INPPGA 0x0010 /* RIP2INPPGA */
+#define WM8985_RIP2INPPGA_MASK 0x0010 /* RIP2INPPGA */
+#define WM8985_RIP2INPPGA_SHIFT 4 /* RIP2INPPGA */
+#define WM8985_RIP2INPPGA_WIDTH 1 /* RIP2INPPGA */
+#define WM8985_L2_2INPPGA 0x0004 /* L2_2INPPGA */
+#define WM8985_L2_2INPPGA_MASK 0x0004 /* L2_2INPPGA */
+#define WM8985_L2_2INPPGA_SHIFT 2 /* L2_2INPPGA */
+#define WM8985_L2_2INPPGA_WIDTH 1 /* L2_2INPPGA */
+#define WM8985_LIN2INPPGA 0x0002 /* LIN2INPPGA */
+#define WM8985_LIN2INPPGA_MASK 0x0002 /* LIN2INPPGA */
+#define WM8985_LIN2INPPGA_SHIFT 1 /* LIN2INPPGA */
+#define WM8985_LIN2INPPGA_WIDTH 1 /* LIN2INPPGA */
+#define WM8985_LIP2INPPGA 0x0001 /* LIP2INPPGA */
+#define WM8985_LIP2INPPGA_MASK 0x0001 /* LIP2INPPGA */
+#define WM8985_LIP2INPPGA_SHIFT 0 /* LIP2INPPGA */
+#define WM8985_LIP2INPPGA_WIDTH 1 /* LIP2INPPGA */
+
+/*
+ * R45 (0x2D) - Left INP PGA gain ctrl
+ */
+#define WM8985_INPGAVU 0x0100 /* INPGAVU */
+#define WM8985_INPGAVU_MASK 0x0100 /* INPGAVU */
+#define WM8985_INPGAVU_SHIFT 8 /* INPGAVU */
+#define WM8985_INPGAVU_WIDTH 1 /* INPGAVU */
+#define WM8985_INPPGAZCL 0x0080 /* INPPGAZCL */
+#define WM8985_INPPGAZCL_MASK 0x0080 /* INPPGAZCL */
+#define WM8985_INPPGAZCL_SHIFT 7 /* INPPGAZCL */
+#define WM8985_INPPGAZCL_WIDTH 1 /* INPPGAZCL */
+#define WM8985_INPPGAMUTEL 0x0040 /* INPPGAMUTEL */
+#define WM8985_INPPGAMUTEL_MASK 0x0040 /* INPPGAMUTEL */
+#define WM8985_INPPGAMUTEL_SHIFT 6 /* INPPGAMUTEL */
+#define WM8985_INPPGAMUTEL_WIDTH 1 /* INPPGAMUTEL */
+#define WM8985_INPPGAVOLL_MASK 0x003F /* INPPGAVOLL - [5:0] */
+#define WM8985_INPPGAVOLL_SHIFT 0 /* INPPGAVOLL - [5:0] */
+#define WM8985_INPPGAVOLL_WIDTH 6 /* INPPGAVOLL - [5:0] */
+
+/*
+ * R46 (0x2E) - Right INP PGA gain ctrl
+ */
+#define WM8985_INPGAVU 0x0100 /* INPGAVU */
+#define WM8985_INPGAVU_MASK 0x0100 /* INPGAVU */
+#define WM8985_INPGAVU_SHIFT 8 /* INPGAVU */
+#define WM8985_INPGAVU_WIDTH 1 /* INPGAVU */
+#define WM8985_INPPGAZCR 0x0080 /* INPPGAZCR */
+#define WM8985_INPPGAZCR_MASK 0x0080 /* INPPGAZCR */
+#define WM8985_INPPGAZCR_SHIFT 7 /* INPPGAZCR */
+#define WM8985_INPPGAZCR_WIDTH 1 /* INPPGAZCR */
+#define WM8985_INPPGAMUTER 0x0040 /* INPPGAMUTER */
+#define WM8985_INPPGAMUTER_MASK 0x0040 /* INPPGAMUTER */
+#define WM8985_INPPGAMUTER_SHIFT 6 /* INPPGAMUTER */
+#define WM8985_INPPGAMUTER_WIDTH 1 /* INPPGAMUTER */
+#define WM8985_INPPGAVOLR_MASK 0x003F /* INPPGAVOLR - [5:0] */
+#define WM8985_INPPGAVOLR_SHIFT 0 /* INPPGAVOLR - [5:0] */
+#define WM8985_INPPGAVOLR_WIDTH 6 /* INPPGAVOLR - [5:0] */
+
+/*
+ * R47 (0x2F) - Left ADC BOOST ctrl
+ */
+#define WM8985_PGABOOSTL 0x0100 /* PGABOOSTL */
+#define WM8985_PGABOOSTL_MASK 0x0100 /* PGABOOSTL */
+#define WM8985_PGABOOSTL_SHIFT 8 /* PGABOOSTL */
+#define WM8985_PGABOOSTL_WIDTH 1 /* PGABOOSTL */
+#define WM8985_L2_2BOOSTVOL_MASK 0x0070 /* L2_2BOOSTVOL - [6:4] */
+#define WM8985_L2_2BOOSTVOL_SHIFT 4 /* L2_2BOOSTVOL - [6:4] */
+#define WM8985_L2_2BOOSTVOL_WIDTH 3 /* L2_2BOOSTVOL - [6:4] */
+#define WM8985_AUXL2BOOSTVOL_MASK 0x0007 /* AUXL2BOOSTVOL - [2:0] */
+#define WM8985_AUXL2BOOSTVOL_SHIFT 0 /* AUXL2BOOSTVOL - [2:0] */
+#define WM8985_AUXL2BOOSTVOL_WIDTH 3 /* AUXL2BOOSTVOL - [2:0] */
+
+/*
+ * R48 (0x30) - Right ADC BOOST ctrl
+ */
+#define WM8985_PGABOOSTR 0x0100 /* PGABOOSTR */
+#define WM8985_PGABOOSTR_MASK 0x0100 /* PGABOOSTR */
+#define WM8985_PGABOOSTR_SHIFT 8 /* PGABOOSTR */
+#define WM8985_PGABOOSTR_WIDTH 1 /* PGABOOSTR */
+#define WM8985_R2_2BOOSTVOL_MASK 0x0070 /* R2_2BOOSTVOL - [6:4] */
+#define WM8985_R2_2BOOSTVOL_SHIFT 4 /* R2_2BOOSTVOL - [6:4] */
+#define WM8985_R2_2BOOSTVOL_WIDTH 3 /* R2_2BOOSTVOL - [6:4] */
+#define WM8985_AUXR2BOOSTVOL_MASK 0x0007 /* AUXR2BOOSTVOL - [2:0] */
+#define WM8985_AUXR2BOOSTVOL_SHIFT 0 /* AUXR2BOOSTVOL - [2:0] */
+#define WM8985_AUXR2BOOSTVOL_WIDTH 3 /* AUXR2BOOSTVOL - [2:0] */
+
+/*
+ * R49 (0x31) - Output ctrl
+ */
+#define WM8985_DACL2RMIX 0x0040 /* DACL2RMIX */
+#define WM8985_DACL2RMIX_MASK 0x0040 /* DACL2RMIX */
+#define WM8985_DACL2RMIX_SHIFT 6 /* DACL2RMIX */
+#define WM8985_DACL2RMIX_WIDTH 1 /* DACL2RMIX */
+#define WM8985_DACR2LMIX 0x0020 /* DACR2LMIX */
+#define WM8985_DACR2LMIX_MASK 0x0020 /* DACR2LMIX */
+#define WM8985_DACR2LMIX_SHIFT 5 /* DACR2LMIX */
+#define WM8985_DACR2LMIX_WIDTH 1 /* DACR2LMIX */
+#define WM8985_OUT4BOOST 0x0010 /* OUT4BOOST */
+#define WM8985_OUT4BOOST_MASK 0x0010 /* OUT4BOOST */
+#define WM8985_OUT4BOOST_SHIFT 4 /* OUT4BOOST */
+#define WM8985_OUT4BOOST_WIDTH 1 /* OUT4BOOST */
+#define WM8985_OUT3BOOST 0x0008 /* OUT3BOOST */
+#define WM8985_OUT3BOOST_MASK 0x0008 /* OUT3BOOST */
+#define WM8985_OUT3BOOST_SHIFT 3 /* OUT3BOOST */
+#define WM8985_OUT3BOOST_WIDTH 1 /* OUT3BOOST */
+#define WM8985_TSOPCTRL 0x0004 /* TSOPCTRL */
+#define WM8985_TSOPCTRL_MASK 0x0004 /* TSOPCTRL */
+#define WM8985_TSOPCTRL_SHIFT 2 /* TSOPCTRL */
+#define WM8985_TSOPCTRL_WIDTH 1 /* TSOPCTRL */
+#define WM8985_TSDEN 0x0002 /* TSDEN */
+#define WM8985_TSDEN_MASK 0x0002 /* TSDEN */
+#define WM8985_TSDEN_SHIFT 1 /* TSDEN */
+#define WM8985_TSDEN_WIDTH 1 /* TSDEN */
+#define WM8985_VROI 0x0001 /* VROI */
+#define WM8985_VROI_MASK 0x0001 /* VROI */
+#define WM8985_VROI_SHIFT 0 /* VROI */
+#define WM8985_VROI_WIDTH 1 /* VROI */
+
+/*
+ * R50 (0x32) - Left mixer ctrl
+ */
+#define WM8985_AUXLMIXVOL_MASK 0x01C0 /* AUXLMIXVOL - [8:6] */
+#define WM8985_AUXLMIXVOL_SHIFT 6 /* AUXLMIXVOL - [8:6] */
+#define WM8985_AUXLMIXVOL_WIDTH 3 /* AUXLMIXVOL - [8:6] */
+#define WM8985_AUXL2LMIX 0x0020 /* AUXL2LMIX */
+#define WM8985_AUXL2LMIX_MASK 0x0020 /* AUXL2LMIX */
+#define WM8985_AUXL2LMIX_SHIFT 5 /* AUXL2LMIX */
+#define WM8985_AUXL2LMIX_WIDTH 1 /* AUXL2LMIX */
+#define WM8985_BYPLMIXVOL_MASK 0x001C /* BYPLMIXVOL - [4:2] */
+#define WM8985_BYPLMIXVOL_SHIFT 2 /* BYPLMIXVOL - [4:2] */
+#define WM8985_BYPLMIXVOL_WIDTH 3 /* BYPLMIXVOL - [4:2] */
+#define WM8985_BYPL2LMIX 0x0002 /* BYPL2LMIX */
+#define WM8985_BYPL2LMIX_MASK 0x0002 /* BYPL2LMIX */
+#define WM8985_BYPL2LMIX_SHIFT 1 /* BYPL2LMIX */
+#define WM8985_BYPL2LMIX_WIDTH 1 /* BYPL2LMIX */
+#define WM8985_DACL2LMIX 0x0001 /* DACL2LMIX */
+#define WM8985_DACL2LMIX_MASK 0x0001 /* DACL2LMIX */
+#define WM8985_DACL2LMIX_SHIFT 0 /* DACL2LMIX */
+#define WM8985_DACL2LMIX_WIDTH 1 /* DACL2LMIX */
+
+/*
+ * R51 (0x33) - Right mixer ctrl
+ */
+#define WM8985_AUXRMIXVOL_MASK 0x01C0 /* AUXRMIXVOL - [8:6] */
+#define WM8985_AUXRMIXVOL_SHIFT 6 /* AUXRMIXVOL - [8:6] */
+#define WM8985_AUXRMIXVOL_WIDTH 3 /* AUXRMIXVOL - [8:6] */
+#define WM8985_AUXR2RMIX 0x0020 /* AUXR2RMIX */
+#define WM8985_AUXR2RMIX_MASK 0x0020 /* AUXR2RMIX */
+#define WM8985_AUXR2RMIX_SHIFT 5 /* AUXR2RMIX */
+#define WM8985_AUXR2RMIX_WIDTH 1 /* AUXR2RMIX */
+#define WM8985_BYPRMIXVOL_MASK 0x001C /* BYPRMIXVOL - [4:2] */
+#define WM8985_BYPRMIXVOL_SHIFT 2 /* BYPRMIXVOL - [4:2] */
+#define WM8985_BYPRMIXVOL_WIDTH 3 /* BYPRMIXVOL - [4:2] */
+#define WM8985_BYPR2RMIX 0x0002 /* BYPR2RMIX */
+#define WM8985_BYPR2RMIX_MASK 0x0002 /* BYPR2RMIX */
+#define WM8985_BYPR2RMIX_SHIFT 1 /* BYPR2RMIX */
+#define WM8985_BYPR2RMIX_WIDTH 1 /* BYPR2RMIX */
+#define WM8985_DACR2RMIX 0x0001 /* DACR2RMIX */
+#define WM8985_DACR2RMIX_MASK 0x0001 /* DACR2RMIX */
+#define WM8985_DACR2RMIX_SHIFT 0 /* DACR2RMIX */
+#define WM8985_DACR2RMIX_WIDTH 1 /* DACR2RMIX */
+
+/*
+ * R52 (0x34) - LOUT1 (HP) volume ctrl
+ */
+#define WM8985_OUT1VU 0x0100 /* OUT1VU */
+#define WM8985_OUT1VU_MASK 0x0100 /* OUT1VU */
+#define WM8985_OUT1VU_SHIFT 8 /* OUT1VU */
+#define WM8985_OUT1VU_WIDTH 1 /* OUT1VU */
+#define WM8985_LOUT1ZC 0x0080 /* LOUT1ZC */
+#define WM8985_LOUT1ZC_MASK 0x0080 /* LOUT1ZC */
+#define WM8985_LOUT1ZC_SHIFT 7 /* LOUT1ZC */
+#define WM8985_LOUT1ZC_WIDTH 1 /* LOUT1ZC */
+#define WM8985_LOUT1MUTE 0x0040 /* LOUT1MUTE */
+#define WM8985_LOUT1MUTE_MASK 0x0040 /* LOUT1MUTE */
+#define WM8985_LOUT1MUTE_SHIFT 6 /* LOUT1MUTE */
+#define WM8985_LOUT1MUTE_WIDTH 1 /* LOUT1MUTE */
+#define WM8985_LOUT1VOL_MASK 0x003F /* LOUT1VOL - [5:0] */
+#define WM8985_LOUT1VOL_SHIFT 0 /* LOUT1VOL - [5:0] */
+#define WM8985_LOUT1VOL_WIDTH 6 /* LOUT1VOL - [5:0] */
+
+/*
+ * R53 (0x35) - ROUT1 (HP) volume ctrl
+ */
+#define WM8985_OUT1VU 0x0100 /* OUT1VU */
+#define WM8985_OUT1VU_MASK 0x0100 /* OUT1VU */
+#define WM8985_OUT1VU_SHIFT 8 /* OUT1VU */
+#define WM8985_OUT1VU_WIDTH 1 /* OUT1VU */
+#define WM8985_ROUT1ZC 0x0080 /* ROUT1ZC */
+#define WM8985_ROUT1ZC_MASK 0x0080 /* ROUT1ZC */
+#define WM8985_ROUT1ZC_SHIFT 7 /* ROUT1ZC */
+#define WM8985_ROUT1ZC_WIDTH 1 /* ROUT1ZC */
+#define WM8985_ROUT1MUTE 0x0040 /* ROUT1MUTE */
+#define WM8985_ROUT1MUTE_MASK 0x0040 /* ROUT1MUTE */
+#define WM8985_ROUT1MUTE_SHIFT 6 /* ROUT1MUTE */
+#define WM8985_ROUT1MUTE_WIDTH 1 /* ROUT1MUTE */
+#define WM8985_ROUT1VOL_MASK 0x003F /* ROUT1VOL - [5:0] */
+#define WM8985_ROUT1VOL_SHIFT 0 /* ROUT1VOL - [5:0] */
+#define WM8985_ROUT1VOL_WIDTH 6 /* ROUT1VOL - [5:0] */
+
+/*
+ * R54 (0x36) - LOUT2 (SPK) volume ctrl
+ */
+#define WM8985_OUT2VU 0x0100 /* OUT2VU */
+#define WM8985_OUT2VU_MASK 0x0100 /* OUT2VU */
+#define WM8985_OUT2VU_SHIFT 8 /* OUT2VU */
+#define WM8985_OUT2VU_WIDTH 1 /* OUT2VU */
+#define WM8985_LOUT2ZC 0x0080 /* LOUT2ZC */
+#define WM8985_LOUT2ZC_MASK 0x0080 /* LOUT2ZC */
+#define WM8985_LOUT2ZC_SHIFT 7 /* LOUT2ZC */
+#define WM8985_LOUT2ZC_WIDTH 1 /* LOUT2ZC */
+#define WM8985_LOUT2MUTE 0x0040 /* LOUT2MUTE */
+#define WM8985_LOUT2MUTE_MASK 0x0040 /* LOUT2MUTE */
+#define WM8985_LOUT2MUTE_SHIFT 6 /* LOUT2MUTE */
+#define WM8985_LOUT2MUTE_WIDTH 1 /* LOUT2MUTE */
+#define WM8985_LOUT2VOL_MASK 0x003F /* LOUT2VOL - [5:0] */
+#define WM8985_LOUT2VOL_SHIFT 0 /* LOUT2VOL - [5:0] */
+#define WM8985_LOUT2VOL_WIDTH 6 /* LOUT2VOL - [5:0] */
+
+/*
+ * R55 (0x37) - ROUT2 (SPK) volume ctrl
+ */
+#define WM8985_OUT2VU 0x0100 /* OUT2VU */
+#define WM8985_OUT2VU_MASK 0x0100 /* OUT2VU */
+#define WM8985_OUT2VU_SHIFT 8 /* OUT2VU */
+#define WM8985_OUT2VU_WIDTH 1 /* OUT2VU */
+#define WM8985_ROUT2ZC 0x0080 /* ROUT2ZC */
+#define WM8985_ROUT2ZC_MASK 0x0080 /* ROUT2ZC */
+#define WM8985_ROUT2ZC_SHIFT 7 /* ROUT2ZC */
+#define WM8985_ROUT2ZC_WIDTH 1 /* ROUT2ZC */
+#define WM8985_ROUT2MUTE 0x0040 /* ROUT2MUTE */
+#define WM8985_ROUT2MUTE_MASK 0x0040 /* ROUT2MUTE */
+#define WM8985_ROUT2MUTE_SHIFT 6 /* ROUT2MUTE */
+#define WM8985_ROUT2MUTE_WIDTH 1 /* ROUT2MUTE */
+#define WM8985_ROUT2VOL_MASK 0x003F /* ROUT2VOL - [5:0] */
+#define WM8985_ROUT2VOL_SHIFT 0 /* ROUT2VOL - [5:0] */
+#define WM8985_ROUT2VOL_WIDTH 6 /* ROUT2VOL - [5:0] */
+
+/*
+ * R56 (0x38) - OUT3 mixer ctrl
+ */
+#define WM8985_OUT3MUTE 0x0040 /* OUT3MUTE */
+#define WM8985_OUT3MUTE_MASK 0x0040 /* OUT3MUTE */
+#define WM8985_OUT3MUTE_SHIFT 6 /* OUT3MUTE */
+#define WM8985_OUT3MUTE_WIDTH 1 /* OUT3MUTE */
+#define WM8985_OUT4_2OUT3 0x0008 /* OUT4_2OUT3 */
+#define WM8985_OUT4_2OUT3_MASK 0x0008 /* OUT4_2OUT3 */
+#define WM8985_OUT4_2OUT3_SHIFT 3 /* OUT4_2OUT3 */
+#define WM8985_OUT4_2OUT3_WIDTH 1 /* OUT4_2OUT3 */
+#define WM8985_BYPL2OUT3 0x0004 /* BYPL2OUT3 */
+#define WM8985_BYPL2OUT3_MASK 0x0004 /* BYPL2OUT3 */
+#define WM8985_BYPL2OUT3_SHIFT 2 /* BYPL2OUT3 */
+#define WM8985_BYPL2OUT3_WIDTH 1 /* BYPL2OUT3 */
+#define WM8985_LMIX2OUT3 0x0002 /* LMIX2OUT3 */
+#define WM8985_LMIX2OUT3_MASK 0x0002 /* LMIX2OUT3 */
+#define WM8985_LMIX2OUT3_SHIFT 1 /* LMIX2OUT3 */
+#define WM8985_LMIX2OUT3_WIDTH 1 /* LMIX2OUT3 */
+#define WM8985_LDAC2OUT3 0x0001 /* LDAC2OUT3 */
+#define WM8985_LDAC2OUT3_MASK 0x0001 /* LDAC2OUT3 */
+#define WM8985_LDAC2OUT3_SHIFT 0 /* LDAC2OUT3 */
+#define WM8985_LDAC2OUT3_WIDTH 1 /* LDAC2OUT3 */
+
+/*
+ * R57 (0x39) - OUT4 (MONO) mix ctrl
+ */
+#define WM8985_OUT3_2OUT4 0x0080 /* OUT3_2OUT4 */
+#define WM8985_OUT3_2OUT4_MASK 0x0080 /* OUT3_2OUT4 */
+#define WM8985_OUT3_2OUT4_SHIFT 7 /* OUT3_2OUT4 */
+#define WM8985_OUT3_2OUT4_WIDTH 1 /* OUT3_2OUT4 */
+#define WM8985_OUT4MUTE 0x0040 /* OUT4MUTE */
+#define WM8985_OUT4MUTE_MASK 0x0040 /* OUT4MUTE */
+#define WM8985_OUT4MUTE_SHIFT 6 /* OUT4MUTE */
+#define WM8985_OUT4MUTE_WIDTH 1 /* OUT4MUTE */
+#define WM8985_OUT4ATTN 0x0020 /* OUT4ATTN */
+#define WM8985_OUT4ATTN_MASK 0x0020 /* OUT4ATTN */
+#define WM8985_OUT4ATTN_SHIFT 5 /* OUT4ATTN */
+#define WM8985_OUT4ATTN_WIDTH 1 /* OUT4ATTN */
+#define WM8985_LMIX2OUT4 0x0010 /* LMIX2OUT4 */
+#define WM8985_LMIX2OUT4_MASK 0x0010 /* LMIX2OUT4 */
+#define WM8985_LMIX2OUT4_SHIFT 4 /* LMIX2OUT4 */
+#define WM8985_LMIX2OUT4_WIDTH 1 /* LMIX2OUT4 */
+#define WM8985_LDAC2OUT4 0x0008 /* LDAC2OUT4 */
+#define WM8985_LDAC2OUT4_MASK 0x0008 /* LDAC2OUT4 */
+#define WM8985_LDAC2OUT4_SHIFT 3 /* LDAC2OUT4 */
+#define WM8985_LDAC2OUT4_WIDTH 1 /* LDAC2OUT4 */
+#define WM8985_BYPR2OUT4 0x0004 /* BYPR2OUT4 */
+#define WM8985_BYPR2OUT4_MASK 0x0004 /* BYPR2OUT4 */
+#define WM8985_BYPR2OUT4_SHIFT 2 /* BYPR2OUT4 */
+#define WM8985_BYPR2OUT4_WIDTH 1 /* BYPR2OUT4 */
+#define WM8985_RMIX2OUT4 0x0002 /* RMIX2OUT4 */
+#define WM8985_RMIX2OUT4_MASK 0x0002 /* RMIX2OUT4 */
+#define WM8985_RMIX2OUT4_SHIFT 1 /* RMIX2OUT4 */
+#define WM8985_RMIX2OUT4_WIDTH 1 /* RMIX2OUT4 */
+#define WM8985_RDAC2OUT4 0x0001 /* RDAC2OUT4 */
+#define WM8985_RDAC2OUT4_MASK 0x0001 /* RDAC2OUT4 */
+#define WM8985_RDAC2OUT4_SHIFT 0 /* RDAC2OUT4 */
+#define WM8985_RDAC2OUT4_WIDTH 1 /* RDAC2OUT4 */
+
+/*
+ * R60 (0x3C) - OUTPUT ctrl
+ */
+#define WM8985_VIDBUFFTST_MASK 0x01E0 /* VIDBUFFTST - [8:5] */
+#define WM8985_VIDBUFFTST_SHIFT 5 /* VIDBUFFTST - [8:5] */
+#define WM8985_VIDBUFFTST_WIDTH 4 /* VIDBUFFTST - [8:5] */
+#define WM8985_HPTOG 0x0008 /* HPTOG */
+#define WM8985_HPTOG_MASK 0x0008 /* HPTOG */
+#define WM8985_HPTOG_SHIFT 3 /* HPTOG */
+#define WM8985_HPTOG_WIDTH 1 /* HPTOG */
+
+/*
+ * R61 (0x3D) - BIAS CTRL
+ */
+#define WM8985_BIASCUT 0x0100 /* BIASCUT */
+#define WM8985_BIASCUT_MASK 0x0100 /* BIASCUT */
+#define WM8985_BIASCUT_SHIFT 8 /* BIASCUT */
+#define WM8985_BIASCUT_WIDTH 1 /* BIASCUT */
+#define WM8985_HALFIPBIAS 0x0080 /* HALFIPBIAS */
+#define WM8985_HALFIPBIAS_MASK 0x0080 /* HALFIPBIAS */
+#define WM8985_HALFIPBIAS_SHIFT 7 /* HALFIPBIAS */
+#define WM8985_HALFIPBIAS_WIDTH 1 /* HALFIPBIAS */
+#define WM8985_VBBIASTST_MASK 0x0060 /* VBBIASTST - [6:5] */
+#define WM8985_VBBIASTST_SHIFT 5 /* VBBIASTST - [6:5] */
+#define WM8985_VBBIASTST_WIDTH 2 /* VBBIASTST - [6:5] */
+#define WM8985_BUFBIAS_MASK 0x0018 /* BUFBIAS - [4:3] */
+#define WM8985_BUFBIAS_SHIFT 3 /* BUFBIAS - [4:3] */
+#define WM8985_BUFBIAS_WIDTH 2 /* BUFBIAS - [4:3] */
+#define WM8985_ADCBIAS_MASK 0x0006 /* ADCBIAS - [2:1] */
+#define WM8985_ADCBIAS_SHIFT 1 /* ADCBIAS - [2:1] */
+#define WM8985_ADCBIAS_WIDTH 2 /* ADCBIAS - [2:1] */
+#define WM8985_HALFOPBIAS 0x0001 /* HALFOPBIAS */
+#define WM8985_HALFOPBIAS_MASK 0x0001 /* HALFOPBIAS */
+#define WM8985_HALFOPBIAS_SHIFT 0 /* HALFOPBIAS */
+#define WM8985_HALFOPBIAS_WIDTH 1 /* HALFOPBIAS */
+
+enum clk_src {
+ WM8985_CLKSRC_MCLK,
+ WM8985_CLKSRC_PLL
+};
+
+#define WM8985_PLL 0
+
+#endif
diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c
new file mode 100644
index 00000000..d7170f13
--- /dev/null
+++ b/sound/soc/codecs/wm8988.c
@@ -0,0 +1,933 @@
+/*
+ * wm8988.c -- WM8988 ALSA SoC audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "wm8988.h"
+
+/*
+ * wm8988 register cache
+ * We can't read the WM8988 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8988_reg[] = {
+ 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */
+ 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */
+ 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */
+ 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */
+ 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */
+ 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */
+ 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */
+ 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */
+ 0x0079, 0x0079, 0x0079, /* 40 */
+};
+
+/* codec private data */
+struct wm8988_priv {
+ unsigned int sysclk;
+ enum snd_soc_control_type control_type;
+ struct snd_pcm_hw_constraint_list *sysclk_constraints;
+};
+
+
+#define wm8988_reset(c) snd_soc_write(c, WM8988_RESET, 0)
+
+/*
+ * WM8988 Controls
+ */
+
+static const char *bass_boost_txt[] = {"Linear Control", "Adaptive Boost"};
+static const struct soc_enum bass_boost =
+ SOC_ENUM_SINGLE(WM8988_BASS, 7, 2, bass_boost_txt);
+
+static const char *bass_filter_txt[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" };
+static const struct soc_enum bass_filter =
+ SOC_ENUM_SINGLE(WM8988_BASS, 6, 2, bass_filter_txt);
+
+static const char *treble_txt[] = {"8kHz", "4kHz"};
+static const struct soc_enum treble =
+ SOC_ENUM_SINGLE(WM8988_TREBLE, 6, 2, treble_txt);
+
+static const char *stereo_3d_lc_txt[] = {"200Hz", "500Hz"};
+static const struct soc_enum stereo_3d_lc =
+ SOC_ENUM_SINGLE(WM8988_3D, 5, 2, stereo_3d_lc_txt);
+
+static const char *stereo_3d_uc_txt[] = {"2.2kHz", "1.5kHz"};
+static const struct soc_enum stereo_3d_uc =
+ SOC_ENUM_SINGLE(WM8988_3D, 6, 2, stereo_3d_uc_txt);
+
+static const char *stereo_3d_func_txt[] = {"Capture", "Playback"};
+static const struct soc_enum stereo_3d_func =
+ SOC_ENUM_SINGLE(WM8988_3D, 7, 2, stereo_3d_func_txt);
+
+static const char *alc_func_txt[] = {"Off", "Right", "Left", "Stereo"};
+static const struct soc_enum alc_func =
+ SOC_ENUM_SINGLE(WM8988_ALC1, 7, 4, alc_func_txt);
+
+static const char *ng_type_txt[] = {"Constant PGA Gain",
+ "Mute ADC Output"};
+static const struct soc_enum ng_type =
+ SOC_ENUM_SINGLE(WM8988_NGATE, 1, 2, ng_type_txt);
+
+static const char *deemph_txt[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const struct soc_enum deemph =
+ SOC_ENUM_SINGLE(WM8988_ADCDAC, 1, 4, deemph_txt);
+
+static const char *adcpol_txt[] = {"Normal", "L Invert", "R Invert",
+ "L + R Invert"};
+static const struct soc_enum adcpol =
+ SOC_ENUM_SINGLE(WM8988_ADCDAC, 5, 4, adcpol_txt);
+
+static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
+
+static const struct snd_kcontrol_new wm8988_snd_controls[] = {
+
+SOC_ENUM("Bass Boost", bass_boost),
+SOC_ENUM("Bass Filter", bass_filter),
+SOC_SINGLE("Bass Volume", WM8988_BASS, 0, 15, 1),
+
+SOC_SINGLE("Treble Volume", WM8988_TREBLE, 0, 15, 0),
+SOC_ENUM("Treble Cut-off", treble),
+
+SOC_SINGLE("3D Switch", WM8988_3D, 0, 1, 0),
+SOC_SINGLE("3D Volume", WM8988_3D, 1, 15, 0),
+SOC_ENUM("3D Lower Cut-off", stereo_3d_lc),
+SOC_ENUM("3D Upper Cut-off", stereo_3d_uc),
+SOC_ENUM("3D Mode", stereo_3d_func),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8988_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8988_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", alc_func),
+SOC_SINGLE("ALC Capture ZC Switch", WM8988_ALC2, 7, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8988_ALC2, 0, 15, 0),
+SOC_SINGLE("ALC Capture Decay Time", WM8988_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack Time", WM8988_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8988_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", ng_type),
+SOC_SINGLE("ALC Capture NG Switch", WM8988_NGATE, 0, 1, 0),
+
+SOC_SINGLE("ZC Timeout Switch", WM8988_ADCTL1, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Capture Digital Volume", WM8988_LADC, WM8988_RADC,
+ 0, 255, 0, adc_tlv),
+SOC_DOUBLE_R_TLV("Capture Volume", WM8988_LINVOL, WM8988_RINVOL,
+ 0, 63, 0, pga_tlv),
+SOC_DOUBLE_R("Capture ZC Switch", WM8988_LINVOL, WM8988_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8988_LINVOL, WM8988_RINVOL, 7, 1, 1),
+
+SOC_ENUM("Playback De-emphasis", deemph),
+
+SOC_ENUM("Capture Polarity", adcpol),
+SOC_SINGLE("Playback 6dB Attenuate", WM8988_ADCDAC, 7, 1, 0),
+SOC_SINGLE("Capture 6dB Attenuate", WM8988_ADCDAC, 8, 1, 0),
+
+SOC_DOUBLE_R_TLV("PCM Volume", WM8988_LDAC, WM8988_RDAC, 0, 255, 0, dac_tlv),
+
+SOC_SINGLE_TLV("Left Mixer Left Bypass Volume", WM8988_LOUTM1, 4, 7, 1,
+ bypass_tlv),
+SOC_SINGLE_TLV("Left Mixer Right Bypass Volume", WM8988_LOUTM2, 4, 7, 1,
+ bypass_tlv),
+SOC_SINGLE_TLV("Right Mixer Left Bypass Volume", WM8988_ROUTM1, 4, 7, 1,
+ bypass_tlv),
+SOC_SINGLE_TLV("Right Mixer Right Bypass Volume", WM8988_ROUTM2, 4, 7, 1,
+ bypass_tlv),
+
+SOC_DOUBLE_R("Output 1 Playback ZC Switch", WM8988_LOUT1V,
+ WM8988_ROUT1V, 7, 1, 0),
+SOC_DOUBLE_R_TLV("Output 1 Playback Volume", WM8988_LOUT1V, WM8988_ROUT1V,
+ 0, 127, 0, out_tlv),
+
+SOC_DOUBLE_R("Output 2 Playback ZC Switch", WM8988_LOUT2V,
+ WM8988_ROUT2V, 7, 1, 0),
+SOC_DOUBLE_R_TLV("Output 2 Playback Volume", WM8988_LOUT2V, WM8988_ROUT2V,
+ 0, 127, 0, out_tlv),
+
+};
+
+/*
+ * DAPM Controls
+ */
+
+static int wm8988_lrc_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u16 adctl2 = snd_soc_read(codec, WM8988_ADCTL2);
+
+ /* Use the DAC to gate LRC if active, otherwise use ADC */
+ if (snd_soc_read(codec, WM8988_PWR2) & 0x180)
+ adctl2 &= ~0x4;
+ else
+ adctl2 |= 0x4;
+
+ return snd_soc_write(codec, WM8988_ADCTL2, adctl2);
+}
+
+static const char *wm8988_line_texts[] = {
+ "Line 1", "Line 2", "PGA", "Differential"};
+
+static const unsigned int wm8988_line_values[] = {
+ 0, 1, 3, 4};
+
+static const struct soc_enum wm8988_lline_enum =
+ SOC_VALUE_ENUM_SINGLE(WM8988_LOUTM1, 0, 7,
+ ARRAY_SIZE(wm8988_line_texts),
+ wm8988_line_texts,
+ wm8988_line_values);
+static const struct snd_kcontrol_new wm8988_left_line_controls =
+ SOC_DAPM_VALUE_ENUM("Route", wm8988_lline_enum);
+
+static const struct soc_enum wm8988_rline_enum =
+ SOC_VALUE_ENUM_SINGLE(WM8988_ROUTM1, 0, 7,
+ ARRAY_SIZE(wm8988_line_texts),
+ wm8988_line_texts,
+ wm8988_line_values);
+static const struct snd_kcontrol_new wm8988_right_line_controls =
+ SOC_DAPM_VALUE_ENUM("Route", wm8988_lline_enum);
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8988_left_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Playback Switch", WM8988_LOUTM1, 8, 1, 0),
+ SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_LOUTM1, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right Playback Switch", WM8988_LOUTM2, 8, 1, 0),
+ SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8988_right_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left Playback Switch", WM8988_ROUTM1, 8, 1, 0),
+ SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_ROUTM1, 7, 1, 0),
+ SOC_DAPM_SINGLE("Playback Switch", WM8988_ROUTM2, 8, 1, 0),
+ SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_ROUTM2, 7, 1, 0),
+};
+
+static const char *wm8988_pga_sel[] = {"Line 1", "Line 2", "Differential"};
+static const unsigned int wm8988_pga_val[] = { 0, 1, 3 };
+
+/* Left PGA Mux */
+static const struct soc_enum wm8988_lpga_enum =
+ SOC_VALUE_ENUM_SINGLE(WM8988_LADCIN, 6, 3,
+ ARRAY_SIZE(wm8988_pga_sel),
+ wm8988_pga_sel,
+ wm8988_pga_val);
+static const struct snd_kcontrol_new wm8988_left_pga_controls =
+ SOC_DAPM_VALUE_ENUM("Route", wm8988_lpga_enum);
+
+/* Right PGA Mux */
+static const struct soc_enum wm8988_rpga_enum =
+ SOC_VALUE_ENUM_SINGLE(WM8988_RADCIN, 6, 3,
+ ARRAY_SIZE(wm8988_pga_sel),
+ wm8988_pga_sel,
+ wm8988_pga_val);
+static const struct snd_kcontrol_new wm8988_right_pga_controls =
+ SOC_DAPM_VALUE_ENUM("Route", wm8988_rpga_enum);
+
+/* Differential Mux */
+static const char *wm8988_diff_sel[] = {"Line 1", "Line 2"};
+static const struct soc_enum diffmux =
+ SOC_ENUM_SINGLE(WM8988_ADCIN, 8, 2, wm8988_diff_sel);
+static const struct snd_kcontrol_new wm8988_diffmux_controls =
+ SOC_DAPM_ENUM("Route", diffmux);
+
+/* Mono ADC Mux */
+static const char *wm8988_mono_mux[] = {"Stereo", "Mono (Left)",
+ "Mono (Right)", "Digital Mono"};
+static const struct soc_enum monomux =
+ SOC_ENUM_SINGLE(WM8988_ADCIN, 6, 4, wm8988_mono_mux);
+static const struct snd_kcontrol_new wm8988_monomux_controls =
+ SOC_DAPM_ENUM("Route", monomux);
+
+static const struct snd_soc_dapm_widget wm8988_dapm_widgets[] = {
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8988_PWR1, 1, 0),
+
+ SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0,
+ &wm8988_diffmux_controls),
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8988_monomux_controls),
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8988_monomux_controls),
+
+ SND_SOC_DAPM_MUX("Left PGA Mux", WM8988_PWR1, 5, 0,
+ &wm8988_left_pga_controls),
+ SND_SOC_DAPM_MUX("Right PGA Mux", WM8988_PWR1, 4, 0,
+ &wm8988_right_pga_controls),
+
+ SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8988_left_line_controls),
+ SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8988_right_line_controls),
+
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8988_PWR1, 2, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8988_PWR1, 3, 0),
+
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8988_PWR2, 7, 0),
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8988_PWR2, 8, 0),
+
+ SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8988_left_mixer_controls[0],
+ ARRAY_SIZE(wm8988_left_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8988_right_mixer_controls[0],
+ ARRAY_SIZE(wm8988_right_mixer_controls)),
+
+ SND_SOC_DAPM_PGA("Right Out 2", WM8988_PWR2, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 2", WM8988_PWR2, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Out 1", WM8988_PWR2, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 1", WM8988_PWR2, 6, 0, NULL, 0),
+
+ SND_SOC_DAPM_POST("LRC control", wm8988_lrc_control),
+
+ SND_SOC_DAPM_OUTPUT("LOUT1"),
+ SND_SOC_DAPM_OUTPUT("ROUT1"),
+ SND_SOC_DAPM_OUTPUT("LOUT2"),
+ SND_SOC_DAPM_OUTPUT("ROUT2"),
+ SND_SOC_DAPM_OUTPUT("VREF"),
+
+ SND_SOC_DAPM_INPUT("LINPUT1"),
+ SND_SOC_DAPM_INPUT("LINPUT2"),
+ SND_SOC_DAPM_INPUT("RINPUT1"),
+ SND_SOC_DAPM_INPUT("RINPUT2"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ { "Left Line Mux", "Line 1", "LINPUT1" },
+ { "Left Line Mux", "Line 2", "LINPUT2" },
+ { "Left Line Mux", "PGA", "Left PGA Mux" },
+ { "Left Line Mux", "Differential", "Differential Mux" },
+
+ { "Right Line Mux", "Line 1", "RINPUT1" },
+ { "Right Line Mux", "Line 2", "RINPUT2" },
+ { "Right Line Mux", "PGA", "Right PGA Mux" },
+ { "Right Line Mux", "Differential", "Differential Mux" },
+
+ { "Left PGA Mux", "Line 1", "LINPUT1" },
+ { "Left PGA Mux", "Line 2", "LINPUT2" },
+ { "Left PGA Mux", "Differential", "Differential Mux" },
+
+ { "Right PGA Mux", "Line 1", "RINPUT1" },
+ { "Right PGA Mux", "Line 2", "RINPUT2" },
+ { "Right PGA Mux", "Differential", "Differential Mux" },
+
+ { "Differential Mux", "Line 1", "LINPUT1" },
+ { "Differential Mux", "Line 1", "RINPUT1" },
+ { "Differential Mux", "Line 2", "LINPUT2" },
+ { "Differential Mux", "Line 2", "RINPUT2" },
+
+ { "Left ADC Mux", "Stereo", "Left PGA Mux" },
+ { "Left ADC Mux", "Mono (Left)", "Left PGA Mux" },
+ { "Left ADC Mux", "Digital Mono", "Left PGA Mux" },
+
+ { "Right ADC Mux", "Stereo", "Right PGA Mux" },
+ { "Right ADC Mux", "Mono (Right)", "Right PGA Mux" },
+ { "Right ADC Mux", "Digital Mono", "Right PGA Mux" },
+
+ { "Left ADC", NULL, "Left ADC Mux" },
+ { "Right ADC", NULL, "Right ADC Mux" },
+
+ { "Left Line Mux", "Line 1", "LINPUT1" },
+ { "Left Line Mux", "Line 2", "LINPUT2" },
+ { "Left Line Mux", "PGA", "Left PGA Mux" },
+ { "Left Line Mux", "Differential", "Differential Mux" },
+
+ { "Right Line Mux", "Line 1", "RINPUT1" },
+ { "Right Line Mux", "Line 2", "RINPUT2" },
+ { "Right Line Mux", "PGA", "Right PGA Mux" },
+ { "Right Line Mux", "Differential", "Differential Mux" },
+
+ { "Left Mixer", "Playback Switch", "Left DAC" },
+ { "Left Mixer", "Left Bypass Switch", "Left Line Mux" },
+ { "Left Mixer", "Right Playback Switch", "Right DAC" },
+ { "Left Mixer", "Right Bypass Switch", "Right Line Mux" },
+
+ { "Right Mixer", "Left Playback Switch", "Left DAC" },
+ { "Right Mixer", "Left Bypass Switch", "Left Line Mux" },
+ { "Right Mixer", "Playback Switch", "Right DAC" },
+ { "Right Mixer", "Right Bypass Switch", "Right Line Mux" },
+
+ { "Left Out 1", NULL, "Left Mixer" },
+ { "LOUT1", NULL, "Left Out 1" },
+ { "Right Out 1", NULL, "Right Mixer" },
+ { "ROUT1", NULL, "Right Out 1" },
+
+ { "Left Out 2", NULL, "Left Mixer" },
+ { "LOUT2", NULL, "Left Out 2" },
+ { "Right Out 2", NULL, "Right Mixer" },
+ { "ROUT2", NULL, "Right Out 2" },
+};
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:5;
+ u8 usb:1;
+};
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 8k */
+ {12288000, 8000, 1536, 0x6, 0x0},
+ {11289600, 8000, 1408, 0x16, 0x0},
+ {18432000, 8000, 2304, 0x7, 0x0},
+ {16934400, 8000, 2112, 0x17, 0x0},
+ {12000000, 8000, 1500, 0x6, 0x1},
+
+ /* 11.025k */
+ {11289600, 11025, 1024, 0x18, 0x0},
+ {16934400, 11025, 1536, 0x19, 0x0},
+ {12000000, 11025, 1088, 0x19, 0x1},
+
+ /* 16k */
+ {12288000, 16000, 768, 0xa, 0x0},
+ {18432000, 16000, 1152, 0xb, 0x0},
+ {12000000, 16000, 750, 0xa, 0x1},
+
+ /* 22.05k */
+ {11289600, 22050, 512, 0x1a, 0x0},
+ {16934400, 22050, 768, 0x1b, 0x0},
+ {12000000, 22050, 544, 0x1b, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0xc, 0x0},
+ {18432000, 32000, 576, 0xd, 0x0},
+ {12000000, 32000, 375, 0xa, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x10, 0x0},
+ {16934400, 44100, 384, 0x11, 0x0},
+ {12000000, 44100, 272, 0x11, 0x1},
+
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0},
+ {18432000, 48000, 384, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0x1e, 0x0},
+ {16934400, 88200, 192, 0x1f, 0x0},
+ {12000000, 88200, 136, 0x1f, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0xe, 0x0},
+ {18432000, 96000, 192, 0xf, 0x0},
+ {12000000, 96000, 125, 0xe, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+/* The set of rates we can generate from the above for each SYSCLK */
+
+static unsigned int rates_12288[] = {
+ 8000, 12000, 16000, 24000, 24000, 32000, 48000, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_12288 = {
+ .count = ARRAY_SIZE(rates_12288),
+ .list = rates_12288,
+};
+
+static unsigned int rates_112896[] = {
+ 8000, 11025, 22050, 44100,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_112896 = {
+ .count = ARRAY_SIZE(rates_112896),
+ .list = rates_112896,
+};
+
+static unsigned int rates_12[] = {
+ 8000, 11025, 12000, 16000, 22050, 2400, 32000, 41100, 48000,
+ 48000, 88235, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_12 = {
+ .count = ARRAY_SIZE(rates_12),
+ .list = rates_12,
+};
+
+/*
+ * Note that this should be called from init rather than from hw_params.
+ */
+static int wm8988_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case 11289600:
+ case 18432000:
+ case 22579200:
+ case 36864000:
+ wm8988->sysclk_constraints = &constraints_112896;
+ wm8988->sysclk = freq;
+ return 0;
+
+ case 12288000:
+ case 16934400:
+ case 24576000:
+ case 33868800:
+ wm8988->sysclk_constraints = &constraints_12288;
+ wm8988->sysclk = freq;
+ return 0;
+
+ case 12000000:
+ case 24000000:
+ wm8988->sysclk_constraints = &constraints_12;
+ wm8988->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int wm8988_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface = 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8988_IFACE, iface);
+ return 0;
+}
+
+static int wm8988_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec);
+
+ /* The set of sample rates that can be supported depends on the
+ * MCLK supplied to the CODEC - enforce this.
+ */
+ if (!wm8988->sysclk) {
+ dev_err(codec->dev,
+ "No MCLK configured, call set_sysclk() on init\n");
+ return -EINVAL;
+ }
+
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ wm8988->sysclk_constraints);
+
+ return 0;
+}
+
+static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec);
+ u16 iface = snd_soc_read(codec, WM8988_IFACE) & 0x1f3;
+ u16 srate = snd_soc_read(codec, WM8988_SRATE) & 0x180;
+ int coeff;
+
+ coeff = get_coeff(wm8988->sysclk, params_rate(params));
+ if (coeff < 0) {
+ coeff = get_coeff(wm8988->sysclk / 2, params_rate(params));
+ srate |= 0x40;
+ }
+ if (coeff < 0) {
+ dev_err(codec->dev,
+ "Unable to configure sample rate %dHz with %dHz MCLK\n",
+ params_rate(params), wm8988->sysclk);
+ return coeff;
+ }
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+
+ /* set iface & srate */
+ snd_soc_write(codec, WM8988_IFACE, iface);
+ if (coeff >= 0)
+ snd_soc_write(codec, WM8988_SRATE, srate |
+ (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+ return 0;
+}
+
+static int wm8988_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, WM8988_ADCDAC) & 0xfff7;
+
+ if (mute)
+ snd_soc_write(codec, WM8988_ADCDAC, mute_reg | 0x8);
+ else
+ snd_soc_write(codec, WM8988_ADCDAC, mute_reg);
+ return 0;
+}
+
+static int wm8988_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 pwr_reg = snd_soc_read(codec, WM8988_PWR1) & ~0x1c1;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VREF, VMID=2x50k, digital enabled */
+ snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x00c0);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* VREF, VMID=2x5k */
+ snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x1c1);
+
+ /* Charge caps */
+ msleep(100);
+ }
+
+ /* VREF, VMID=2*500k, digital stopped */
+ snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x0141);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, WM8988_PWR1, 0x0000);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8988_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8988_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8988_ops = {
+ .startup = wm8988_pcm_startup,
+ .hw_params = wm8988_pcm_hw_params,
+ .set_fmt = wm8988_set_dai_fmt,
+ .set_sysclk = wm8988_set_dai_sysclk,
+ .digital_mute = wm8988_mute,
+};
+
+static struct snd_soc_dai_driver wm8988_dai = {
+ .name = "wm8988-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8988_RATES,
+ .formats = WM8988_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8988_RATES,
+ .formats = WM8988_FORMATS,
+ },
+ .ops = &wm8988_ops,
+ .symmetric_rates = 1,
+};
+
+static int wm8988_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8988_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8988_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < WM8988_NUM_REG; i++) {
+ if (i == WM8988_RESET)
+ continue;
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8988_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+
+static int wm8988_probe(struct snd_soc_codec *codec)
+{
+ struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret = 0;
+ u16 reg;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8988->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8988_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ /* set the update bits (we always update left then right) */
+ reg = snd_soc_read(codec, WM8988_RADC);
+ snd_soc_write(codec, WM8988_RADC, reg | 0x100);
+ reg = snd_soc_read(codec, WM8988_RDAC);
+ snd_soc_write(codec, WM8988_RDAC, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8988_ROUT1V);
+ snd_soc_write(codec, WM8988_ROUT1V, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8988_ROUT2V);
+ snd_soc_write(codec, WM8988_ROUT2V, reg | 0x0100);
+ reg = snd_soc_read(codec, WM8988_RINVOL);
+ snd_soc_write(codec, WM8988_RINVOL, reg | 0x0100);
+
+ wm8988_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_add_controls(codec, wm8988_snd_controls,
+ ARRAY_SIZE(wm8988_snd_controls));
+ snd_soc_dapm_new_controls(dapm, wm8988_dapm_widgets,
+ ARRAY_SIZE(wm8988_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+static int wm8988_remove(struct snd_soc_codec *codec)
+{
+ wm8988_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8988 = {
+ .probe = wm8988_probe,
+ .remove = wm8988_remove,
+ .suspend = wm8988_suspend,
+ .resume = wm8988_resume,
+ .set_bias_level = wm8988_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8988_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8988_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8988_spi_probe(struct spi_device *spi)
+{
+ struct wm8988_priv *wm8988;
+ int ret;
+
+ wm8988 = kzalloc(sizeof(struct wm8988_priv), GFP_KERNEL);
+ if (wm8988 == NULL)
+ return -ENOMEM;
+
+ wm8988->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8988);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8988, &wm8988_dai, 1);
+ if (ret < 0)
+ kfree(wm8988);
+ return ret;
+}
+
+static int __devexit wm8988_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8988_spi_driver = {
+ .driver = {
+ .name = "wm8988-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8988_spi_probe,
+ .remove = __devexit_p(wm8988_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8988_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8988_priv *wm8988;
+ int ret;
+
+ wm8988 = kzalloc(sizeof(struct wm8988_priv), GFP_KERNEL);
+ if (wm8988 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8988);
+ wm8988->control_type = SND_SOC_I2C;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8988, &wm8988_dai, 1);
+ if (ret < 0)
+ kfree(wm8988);
+ return ret;
+}
+
+static __devexit int wm8988_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8988_i2c_id[] = {
+ { "wm8988", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8988_i2c_id);
+
+static struct i2c_driver wm8988_i2c_driver = {
+ .driver = {
+ .name = "wm8988-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8988_i2c_probe,
+ .remove = __devexit_p(wm8988_i2c_remove),
+ .id_table = wm8988_i2c_id,
+};
+#endif
+
+static int __init wm8988_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8988_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8988 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8988_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8988 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8988_modinit);
+
+static void __exit wm8988_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8988_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8988_spi_driver);
+#endif
+}
+module_exit(wm8988_exit);
+
+
+MODULE_DESCRIPTION("ASoC WM8988 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8988.h b/sound/soc/codecs/wm8988.h
new file mode 100644
index 00000000..5c04024e
--- /dev/null
+++ b/sound/soc/codecs/wm8988.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on WM8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _WM8988_H
+#define _WM8988_H
+
+/* WM8988 register space */
+
+#define WM8988_LINVOL 0x00
+#define WM8988_RINVOL 0x01
+#define WM8988_LOUT1V 0x02
+#define WM8988_ROUT1V 0x03
+#define WM8988_ADCDAC 0x05
+#define WM8988_IFACE 0x07
+#define WM8988_SRATE 0x08
+#define WM8988_LDAC 0x0a
+#define WM8988_RDAC 0x0b
+#define WM8988_BASS 0x0c
+#define WM8988_TREBLE 0x0d
+#define WM8988_RESET 0x0f
+#define WM8988_3D 0x10
+#define WM8988_ALC1 0x11
+#define WM8988_ALC2 0x12
+#define WM8988_ALC3 0x13
+#define WM8988_NGATE 0x14
+#define WM8988_LADC 0x15
+#define WM8988_RADC 0x16
+#define WM8988_ADCTL1 0x17
+#define WM8988_ADCTL2 0x18
+#define WM8988_PWR1 0x19
+#define WM8988_PWR2 0x1a
+#define WM8988_ADCTL3 0x1b
+#define WM8988_ADCIN 0x1f
+#define WM8988_LADCIN 0x20
+#define WM8988_RADCIN 0x21
+#define WM8988_LOUTM1 0x22
+#define WM8988_LOUTM2 0x23
+#define WM8988_ROUTM1 0x24
+#define WM8988_ROUTM2 0x25
+#define WM8988_LOUT2V 0x28
+#define WM8988_ROUT2V 0x29
+#define WM8988_LPPB 0x43
+#define WM8988_NUM_REG 0x44
+
+#define WM8988_SYSCLK 0
+
+#endif
diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c
new file mode 100644
index 00000000..100aeee5
--- /dev/null
+++ b/sound/soc/codecs/wm8990.c
@@ -0,0 +1,1465 @@
+/*
+ * wm8990.c -- WM8990 ALSA Soc Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <asm/div64.h>
+
+#include "wm8990.h"
+
+/* codec private data */
+struct wm8990_priv {
+ enum snd_soc_control_type control_type;
+ unsigned int sysclk;
+ unsigned int pcmclk;
+};
+
+/*
+ * wm8990 register cache. Note that register 0 is not included in the
+ * cache.
+ */
+static const u16 wm8990_reg[] = {
+ 0x8990, /* R0 - Reset */
+ 0x0000, /* R1 - Power Management (1) */
+ 0x6000, /* R2 - Power Management (2) */
+ 0x0000, /* R3 - Power Management (3) */
+ 0x4050, /* R4 - Audio Interface (1) */
+ 0x4000, /* R5 - Audio Interface (2) */
+ 0x01C8, /* R6 - Clocking (1) */
+ 0x0000, /* R7 - Clocking (2) */
+ 0x0040, /* R8 - Audio Interface (3) */
+ 0x0040, /* R9 - Audio Interface (4) */
+ 0x0004, /* R10 - DAC CTRL */
+ 0x00C0, /* R11 - Left DAC Digital Volume */
+ 0x00C0, /* R12 - Right DAC Digital Volume */
+ 0x0000, /* R13 - Digital Side Tone */
+ 0x0100, /* R14 - ADC CTRL */
+ 0x00C0, /* R15 - Left ADC Digital Volume */
+ 0x00C0, /* R16 - Right ADC Digital Volume */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 - GPIO CTRL 1 */
+ 0x1000, /* R19 - GPIO1 & GPIO2 */
+ 0x1010, /* R20 - GPIO3 & GPIO4 */
+ 0x1010, /* R21 - GPIO5 & GPIO6 */
+ 0x8000, /* R22 - GPIOCTRL 2 */
+ 0x0800, /* R23 - GPIO_POL */
+ 0x008B, /* R24 - Left Line Input 1&2 Volume */
+ 0x008B, /* R25 - Left Line Input 3&4 Volume */
+ 0x008B, /* R26 - Right Line Input 1&2 Volume */
+ 0x008B, /* R27 - Right Line Input 3&4 Volume */
+ 0x0000, /* R28 - Left Output Volume */
+ 0x0000, /* R29 - Right Output Volume */
+ 0x0066, /* R30 - Line Outputs Volume */
+ 0x0022, /* R31 - Out3/4 Volume */
+ 0x0079, /* R32 - Left OPGA Volume */
+ 0x0079, /* R33 - Right OPGA Volume */
+ 0x0003, /* R34 - Speaker Volume */
+ 0x0003, /* R35 - ClassD1 */
+ 0x0000, /* R36 */
+ 0x0100, /* R37 - ClassD3 */
+ 0x0079, /* R38 - ClassD4 */
+ 0x0000, /* R39 - Input Mixer1 */
+ 0x0000, /* R40 - Input Mixer2 */
+ 0x0000, /* R41 - Input Mixer3 */
+ 0x0000, /* R42 - Input Mixer4 */
+ 0x0000, /* R43 - Input Mixer5 */
+ 0x0000, /* R44 - Input Mixer6 */
+ 0x0000, /* R45 - Output Mixer1 */
+ 0x0000, /* R46 - Output Mixer2 */
+ 0x0000, /* R47 - Output Mixer3 */
+ 0x0000, /* R48 - Output Mixer4 */
+ 0x0000, /* R49 - Output Mixer5 */
+ 0x0000, /* R50 - Output Mixer6 */
+ 0x0180, /* R51 - Out3/4 Mixer */
+ 0x0000, /* R52 - Line Mixer1 */
+ 0x0000, /* R53 - Line Mixer2 */
+ 0x0000, /* R54 - Speaker Mixer */
+ 0x0000, /* R55 - Additional Control */
+ 0x0000, /* R56 - AntiPOP1 */
+ 0x0000, /* R57 - AntiPOP2 */
+ 0x0000, /* R58 - MICBIAS */
+ 0x0000, /* R59 */
+ 0x0008, /* R60 - PLL1 */
+ 0x0031, /* R61 - PLL2 */
+ 0x0026, /* R62 - PLL3 */
+ 0x0000, /* R63 - Driver internal */
+};
+
+#define wm8990_reset(c) snd_soc_write(c, WM8990_RESET, 0)
+
+static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1650, 3000, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_mix_tlv, 0, -2100, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -7300, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_omix_tlv, -600, 0, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_dac_tlv, -7163, 0, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_adc_tlv, -7163, 1763, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_sidetone_tlv, -3600, 0, 0);
+
+static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int reg = mc->reg;
+ int ret;
+ u16 val;
+
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* now hit the volume update bits (always bit 8) */
+ val = snd_soc_read(codec, reg);
+ return snd_soc_write(codec, reg, val | 0x0100);
+}
+
+#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\
+ tlv_array) {\
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+static const char *wm8990_digital_sidetone[] =
+ {"None", "Left ADC", "Right ADC", "Reserved"};
+
+static const struct soc_enum wm8990_left_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADC_TO_DACL_SHIFT,
+ WM8990_ADC_TO_DACL_MASK,
+ wm8990_digital_sidetone);
+
+static const struct soc_enum wm8990_right_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADC_TO_DACR_SHIFT,
+ WM8990_ADC_TO_DACR_MASK,
+ wm8990_digital_sidetone);
+
+static const char *wm8990_adcmode[] =
+ {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"};
+
+static const struct soc_enum wm8990_right_adcmode_enum =
+SOC_ENUM_SINGLE(WM8990_ADC_CTRL,
+ WM8990_ADC_HPF_CUT_SHIFT,
+ WM8990_ADC_HPF_CUT_MASK,
+ wm8990_adcmode);
+
+static const struct snd_kcontrol_new wm8990_snd_controls[] = {
+/* INMIXL */
+SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0),
+SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0),
+/* INMIXR */
+SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0),
+SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0),
+
+/* LOMIX */
+SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3,
+ WM8990_LLI3LOVOL_SHIFT, WM8990_LLI3LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3,
+ WM8990_LR12LOVOL_SHIFT, WM8990_LR12LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3,
+ WM8990_LL12LOVOL_SHIFT, WM8990_LL12LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5,
+ WM8990_LRI3LOVOL_SHIFT, WM8990_LRI3LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5,
+ WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5,
+ WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv),
+
+/* ROMIX */
+SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4,
+ WM8990_RRI3ROVOL_SHIFT, WM8990_RRI3ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4,
+ WM8990_RL12ROVOL_SHIFT, WM8990_RL12ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4,
+ WM8990_RR12ROVOL_SHIFT, WM8990_RR12ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER6,
+ WM8990_RLI3ROVOL_SHIFT, WM8990_RLI3ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER6,
+ WM8990_RLBROVOL_SHIFT, WM8990_RLBROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER6,
+ WM8990_RRBROVOL_SHIFT, WM8990_RRBROVOL_MASK, 1, out_mix_tlv),
+
+/* LOUT */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8990_LEFT_OUTPUT_VOLUME,
+ WM8990_LOUTVOL_SHIFT, WM8990_LOUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOUT ZC", WM8990_LEFT_OUTPUT_VOLUME, WM8990_LOZC_BIT, 1, 0),
+
+/* ROUT */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8990_RIGHT_OUTPUT_VOLUME,
+ WM8990_ROUTVOL_SHIFT, WM8990_ROUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROUT ZC", WM8990_RIGHT_OUTPUT_VOLUME, WM8990_ROZC_BIT, 1, 0),
+
+/* LOPGA */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8990_LEFT_OPGA_VOLUME,
+ WM8990_LOPGAVOL_SHIFT, WM8990_LOPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOPGA ZC Switch", WM8990_LEFT_OPGA_VOLUME,
+ WM8990_LOPGAZC_BIT, 1, 0),
+
+/* ROPGA */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8990_RIGHT_OPGA_VOLUME,
+ WM8990_ROPGAVOL_SHIFT, WM8990_ROPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROPGA ZC Switch", WM8990_RIGHT_OPGA_VOLUME,
+ WM8990_ROPGAZC_BIT, 1, 0),
+
+SOC_SINGLE("LON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_LONMUTE_BIT, 1, 0),
+SOC_SINGLE("LOP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_LOPMUTE_BIT, 1, 0),
+SOC_SINGLE("LOP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_LOATTN_BIT, 1, 0),
+SOC_SINGLE("RON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_RONMUTE_BIT, 1, 0),
+SOC_SINGLE("ROP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_ROPMUTE_BIT, 1, 0),
+SOC_SINGLE("ROP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_ROATTN_BIT, 1, 0),
+
+SOC_SINGLE("OUT3 Mute Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT3MUTE_BIT, 1, 0),
+SOC_SINGLE("OUT3 Attenuation Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT3ATTN_BIT, 1, 0),
+
+SOC_SINGLE("OUT4 Mute Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT4MUTE_BIT, 1, 0),
+SOC_SINGLE("OUT4 Attenuation Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT4ATTN_BIT, 1, 0),
+
+SOC_SINGLE("Speaker Mode Switch", WM8990_CLASSD1,
+ WM8990_CDMODE_BIT, 1, 0),
+
+SOC_SINGLE("Speaker Output Attenuation Volume", WM8990_SPEAKER_VOLUME,
+ WM8990_SPKATTN_SHIFT, WM8990_SPKATTN_MASK, 0),
+SOC_SINGLE("Speaker DC Boost Volume", WM8990_CLASSD3,
+ WM8990_DCGAIN_SHIFT, WM8990_DCGAIN_MASK, 0),
+SOC_SINGLE("Speaker AC Boost Volume", WM8990_CLASSD3,
+ WM8990_ACGAIN_SHIFT, WM8990_ACGAIN_MASK, 0),
+SOC_SINGLE_TLV("Speaker Volume", WM8990_CLASSD4,
+ WM8990_SPKVOL_SHIFT, WM8990_SPKVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("Speaker ZC Switch", WM8990_CLASSD4,
+ WM8990_SPKZC_SHIFT, WM8990_SPKZC_MASK, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume",
+ WM8990_LEFT_DAC_DIGITAL_VOLUME,
+ WM8990_DACL_VOL_SHIFT,
+ WM8990_DACL_VOL_MASK,
+ 0,
+ out_dac_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume",
+ WM8990_RIGHT_DAC_DIGITAL_VOLUME,
+ WM8990_DACR_VOL_SHIFT,
+ WM8990_DACR_VOL_MASK,
+ 0,
+ out_dac_tlv),
+
+SOC_ENUM("Left Digital Sidetone", wm8990_left_digital_sidetone_enum),
+SOC_ENUM("Right Digital Sidetone", wm8990_right_digital_sidetone_enum),
+
+SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADCL_DAC_SVOL_SHIFT, WM8990_ADCL_DAC_SVOL_MASK, 0,
+ out_sidetone_tlv),
+SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADCR_DAC_SVOL_SHIFT, WM8990_ADCR_DAC_SVOL_MASK, 0,
+ out_sidetone_tlv),
+
+SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8990_ADC_CTRL,
+ WM8990_ADC_HPF_ENA_BIT, 1, 0),
+
+SOC_ENUM("ADC HPF Mode", wm8990_right_adcmode_enum),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume",
+ WM8990_LEFT_ADC_DIGITAL_VOLUME,
+ WM8990_ADCL_VOL_SHIFT,
+ WM8990_ADCL_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume",
+ WM8990_RIGHT_ADC_DIGITAL_VOLUME,
+ WM8990_ADCR_VOL_SHIFT,
+ WM8990_ADCR_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume",
+ WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8990_LIN12VOL_SHIFT,
+ WM8990_LIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("LIN12 ZC Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8990_LI12ZC_BIT, 1, 0),
+
+SOC_SINGLE("LIN12 Mute Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8990_LI12MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume",
+ WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8990_LIN34VOL_SHIFT,
+ WM8990_LIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("LIN34 ZC Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8990_LI34ZC_BIT, 1, 0),
+
+SOC_SINGLE("LIN34 Mute Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8990_LI34MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume",
+ WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8990_RIN12VOL_SHIFT,
+ WM8990_RIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("RIN12 ZC Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8990_RI12ZC_BIT, 1, 0),
+
+SOC_SINGLE("RIN12 Mute Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8990_RI12MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume",
+ WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8990_RIN34VOL_SHIFT,
+ WM8990_RIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("RIN34 ZC Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8990_RI34ZC_BIT, 1, 0),
+
+SOC_SINGLE("RIN34 Mute Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8990_RI34MUTE_BIT, 1, 0),
+
+};
+
+/*
+ * _DAPM_ Controls
+ */
+
+static int inmixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u16 reg, fakepower;
+
+ reg = snd_soc_read(w->codec, WM8990_POWER_MANAGEMENT_2);
+ fakepower = snd_soc_read(w->codec, WM8990_INTDRIVBITS);
+
+ if (fakepower & ((1 << WM8990_INMIXL_PWR_BIT) |
+ (1 << WM8990_AINLMUX_PWR_BIT))) {
+ reg |= WM8990_AINL_ENA;
+ } else {
+ reg &= ~WM8990_AINL_ENA;
+ }
+
+ if (fakepower & ((1 << WM8990_INMIXR_PWR_BIT) |
+ (1 << WM8990_AINRMUX_PWR_BIT))) {
+ reg |= WM8990_AINR_ENA;
+ } else {
+ reg &= ~WM8990_AINL_ENA;
+ }
+ snd_soc_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg);
+
+ return 0;
+}
+
+static int outmixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u32 reg_shift = kcontrol->private_value & 0xfff;
+ int ret = 0;
+ u16 reg;
+
+ switch (reg_shift) {
+ case WM8990_SPEAKER_MIXER | (WM8990_LDSPK_BIT << 8) :
+ reg = snd_soc_read(w->codec, WM8990_OUTPUT_MIXER1);
+ if (reg & WM8990_LDLO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 1 LDLO Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8990_SPEAKER_MIXER | (WM8990_RDSPK_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8990_OUTPUT_MIXER2);
+ if (reg & WM8990_RDRO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 2 RDRO Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8990_OUTPUT_MIXER1 | (WM8990_LDLO_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8990_SPEAKER_MIXER);
+ if (reg & WM8990_LDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer LDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8990_OUTPUT_MIXER2 | (WM8990_RDRO_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8990_SPEAKER_MIXER);
+ if (reg & WM8990_RDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer RDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/* INMIX dB values */
+static const unsigned int in_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_SCALE_ITEM(-1200, 600, 0),
+};
+
+/* Left In PGA Connections */
+static const struct snd_kcontrol_new wm8990_dapm_lin12_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN1 Switch", WM8990_INPUT_MIXER2, WM8990_LMN1_BIT, 1, 0),
+SOC_DAPM_SINGLE("LIN2 Switch", WM8990_INPUT_MIXER2, WM8990_LMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8990_dapm_lin34_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN3 Switch", WM8990_INPUT_MIXER2, WM8990_LMN3_BIT, 1, 0),
+SOC_DAPM_SINGLE("LIN4 Switch", WM8990_INPUT_MIXER2, WM8990_LMP4_BIT, 1, 0),
+};
+
+/* Right In PGA Connections */
+static const struct snd_kcontrol_new wm8990_dapm_rin12_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN1 Switch", WM8990_INPUT_MIXER2, WM8990_RMN1_BIT, 1, 0),
+SOC_DAPM_SINGLE("RIN2 Switch", WM8990_INPUT_MIXER2, WM8990_RMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8990_dapm_rin34_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN3 Switch", WM8990_INPUT_MIXER2, WM8990_RMN3_BIT, 1, 0),
+SOC_DAPM_SINGLE("RIN4 Switch", WM8990_INPUT_MIXER2, WM8990_RMP4_BIT, 1, 0),
+};
+
+/* INMIXL */
+static const struct snd_kcontrol_new wm8990_dapm_inmixl_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8990_INPUT_MIXER3,
+ WM8990_LDBVOL_SHIFT, WM8990_LDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8990_INPUT_MIXER5, WM8990_LI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("LINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT,
+ 1, 0),
+SOC_DAPM_SINGLE("LINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT,
+ 1, 0),
+};
+
+/* INMIXR */
+static const struct snd_kcontrol_new wm8990_dapm_inmixr_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4,
+ WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT,
+ 1, 0),
+SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT,
+ 1, 0),
+};
+
+/* AINLMUX */
+static const char *wm8990_ainlmux[] =
+ {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"};
+
+static const struct soc_enum wm8990_ainlmux_enum =
+SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINLMODE_SHIFT,
+ ARRAY_SIZE(wm8990_ainlmux), wm8990_ainlmux);
+
+static const struct snd_kcontrol_new wm8990_dapm_ainlmux_controls =
+SOC_DAPM_ENUM("Route", wm8990_ainlmux_enum);
+
+/* DIFFINL */
+
+/* AINRMUX */
+static const char *wm8990_ainrmux[] =
+ {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"};
+
+static const struct soc_enum wm8990_ainrmux_enum =
+SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINRMODE_SHIFT,
+ ARRAY_SIZE(wm8990_ainrmux), wm8990_ainrmux);
+
+static const struct snd_kcontrol_new wm8990_dapm_ainrmux_controls =
+SOC_DAPM_ENUM("Route", wm8990_ainrmux_enum);
+
+/* RXVOICE */
+static const struct snd_kcontrol_new wm8990_dapm_rxvoice_controls[] = {
+SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8990_INPUT_MIXER5, WM8990_LR4BVOL_SHIFT,
+ WM8990_LR4BVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8990_INPUT_MIXER6, WM8990_RL4BVOL_SHIFT,
+ WM8990_RL4BVOL_MASK, 0, in_mix_tlv),
+};
+
+/* LOMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lomix_controls[] = {
+SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LRBLO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LLBLO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LRI3LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LLI3LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LR12LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LL12LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LDLO_BIT, 1, 0),
+};
+
+/* ROMIX */
+static const struct snd_kcontrol_new wm8990_dapm_romix_controls[] = {
+SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RLBRO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RRBRO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RLI3RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RRI3RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RL12RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RR12RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RDRO_BIT, 1, 0),
+};
+
+/* LONMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lonmix_controls[] = {
+SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1,
+ WM8990_LLOPGALON_BIT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER1,
+ WM8990_LROPGALON_BIT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8990_LINE_MIXER1,
+ WM8990_LOPLON_BIT, 1, 0),
+};
+
+/* LOPMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lopmix_controls[] = {
+SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER1,
+ WM8990_LR12LOP_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER1,
+ WM8990_LL12LOP_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1,
+ WM8990_LLOPGALOP_BIT, 1, 0),
+};
+
+/* RONMIX */
+static const struct snd_kcontrol_new wm8990_dapm_ronmix_controls[] = {
+SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2,
+ WM8990_RROPGARON_BIT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER2,
+ WM8990_RLOPGARON_BIT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8990_LINE_MIXER2,
+ WM8990_ROPRON_BIT, 1, 0),
+};
+
+/* ROPMIX */
+static const struct snd_kcontrol_new wm8990_dapm_ropmix_controls[] = {
+SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER2,
+ WM8990_RL12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER2,
+ WM8990_RR12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2,
+ WM8990_RROPGAROP_BIT, 1, 0),
+};
+
+/* OUT3MIX */
+static const struct snd_kcontrol_new wm8990_dapm_out3mix_controls[] = {
+SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER,
+ WM8990_LI4O3_BIT, 1, 0),
+SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8990_OUT3_4_MIXER,
+ WM8990_LPGAO3_BIT, 1, 0),
+};
+
+/* OUT4MIX */
+static const struct snd_kcontrol_new wm8990_dapm_out4mix_controls[] = {
+SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8990_OUT3_4_MIXER,
+ WM8990_RPGAO4_BIT, 1, 0),
+SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER,
+ WM8990_RI4O4_BIT, 1, 0),
+};
+
+/* SPKMIX */
+static const struct snd_kcontrol_new wm8990_dapm_spkmix_controls[] = {
+SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LI2SPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LB2SPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LOPGASPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LDSPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8990_SPEAKER_MIXER,
+ WM8990_RDSPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8990_SPEAKER_MIXER,
+ WM8990_ROPGASPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_RL12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_RI2SPK_BIT, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8990_dapm_widgets[] = {
+/* Input Side */
+/* Input Lines */
+SND_SOC_DAPM_INPUT("LIN1"),
+SND_SOC_DAPM_INPUT("LIN2"),
+SND_SOC_DAPM_INPUT("LIN3"),
+SND_SOC_DAPM_INPUT("LIN4/RXN"),
+SND_SOC_DAPM_INPUT("RIN3"),
+SND_SOC_DAPM_INPUT("RIN4/RXP"),
+SND_SOC_DAPM_INPUT("RIN1"),
+SND_SOC_DAPM_INPUT("RIN2"),
+SND_SOC_DAPM_INPUT("Internal ADC Source"),
+
+/* DACs */
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8990_POWER_MANAGEMENT_2,
+ WM8990_ADCL_ENA_BIT, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8990_POWER_MANAGEMENT_2,
+ WM8990_ADCR_ENA_BIT, 0),
+
+/* Input PGAs */
+SND_SOC_DAPM_MIXER("LIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN12_ENA_BIT,
+ 0, &wm8990_dapm_lin12_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lin12_pga_controls)),
+SND_SOC_DAPM_MIXER("LIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN34_ENA_BIT,
+ 0, &wm8990_dapm_lin34_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lin34_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN12_ENA_BIT,
+ 0, &wm8990_dapm_rin12_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_rin12_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN34_ENA_BIT,
+ 0, &wm8990_dapm_rin34_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_rin34_pga_controls)),
+
+/* INMIXL */
+SND_SOC_DAPM_MIXER_E("INMIXL", WM8990_INTDRIVBITS, WM8990_INMIXL_PWR_BIT, 0,
+ &wm8990_dapm_inmixl_controls[0],
+ ARRAY_SIZE(wm8990_dapm_inmixl_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINLMUX */
+SND_SOC_DAPM_MUX_E("AINLMUX", WM8990_INTDRIVBITS, WM8990_AINLMUX_PWR_BIT, 0,
+ &wm8990_dapm_ainlmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* INMIXR */
+SND_SOC_DAPM_MIXER_E("INMIXR", WM8990_INTDRIVBITS, WM8990_INMIXR_PWR_BIT, 0,
+ &wm8990_dapm_inmixr_controls[0],
+ ARRAY_SIZE(wm8990_dapm_inmixr_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINRMUX */
+SND_SOC_DAPM_MUX_E("AINRMUX", WM8990_INTDRIVBITS, WM8990_AINRMUX_PWR_BIT, 0,
+ &wm8990_dapm_ainrmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* Output Side */
+/* DACs */
+SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8990_POWER_MANAGEMENT_3,
+ WM8990_DACL_ENA_BIT, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8990_POWER_MANAGEMENT_3,
+ WM8990_DACR_ENA_BIT, 0),
+
+/* LOMIX */
+SND_SOC_DAPM_MIXER_E("LOMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOMIX_ENA_BIT,
+ 0, &wm8990_dapm_lomix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lomix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LONMIX */
+SND_SOC_DAPM_MIXER("LONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LON_ENA_BIT, 0,
+ &wm8990_dapm_lonmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lonmix_controls)),
+
+/* LOPMIX */
+SND_SOC_DAPM_MIXER("LOPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOP_ENA_BIT, 0,
+ &wm8990_dapm_lopmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lopmix_controls)),
+
+/* OUT3MIX */
+SND_SOC_DAPM_MIXER("OUT3MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT3_ENA_BIT, 0,
+ &wm8990_dapm_out3mix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_out3mix_controls)),
+
+/* SPKMIX */
+SND_SOC_DAPM_MIXER_E("SPKMIX", WM8990_POWER_MANAGEMENT_1, WM8990_SPK_ENA_BIT, 0,
+ &wm8990_dapm_spkmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_spkmix_controls), outmixer_event,
+ SND_SOC_DAPM_PRE_REG),
+
+/* OUT4MIX */
+SND_SOC_DAPM_MIXER("OUT4MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT4_ENA_BIT, 0,
+ &wm8990_dapm_out4mix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_out4mix_controls)),
+
+/* ROPMIX */
+SND_SOC_DAPM_MIXER("ROPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROP_ENA_BIT, 0,
+ &wm8990_dapm_ropmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_ropmix_controls)),
+
+/* RONMIX */
+SND_SOC_DAPM_MIXER("RONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_RON_ENA_BIT, 0,
+ &wm8990_dapm_ronmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_ronmix_controls)),
+
+/* ROMIX */
+SND_SOC_DAPM_MIXER_E("ROMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROMIX_ENA_BIT,
+ 0, &wm8990_dapm_romix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_romix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LOUT PGA */
+SND_SOC_DAPM_PGA("LOUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_LOUT_ENA_BIT, 0,
+ NULL, 0),
+
+/* ROUT PGA */
+SND_SOC_DAPM_PGA("ROUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_ROUT_ENA_BIT, 0,
+ NULL, 0),
+
+/* LOPGA */
+SND_SOC_DAPM_PGA("LOPGA", WM8990_POWER_MANAGEMENT_3, WM8990_LOPGA_ENA_BIT, 0,
+ NULL, 0),
+
+/* ROPGA */
+SND_SOC_DAPM_PGA("ROPGA", WM8990_POWER_MANAGEMENT_3, WM8990_ROPGA_ENA_BIT, 0,
+ NULL, 0),
+
+/* MICBIAS */
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8990_POWER_MANAGEMENT_1,
+ WM8990_MICBIAS_ENA_BIT, 0),
+
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("SPKN"),
+SND_SOC_DAPM_OUTPUT("SPKP"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_OUTPUT("Internal DAC Sink"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Make DACs turn on when playing even if not mixed into any outputs */
+ {"Internal DAC Sink", NULL, "Left DAC"},
+ {"Internal DAC Sink", NULL, "Right DAC"},
+
+ /* Make ADCs turn on when recording even if not mixed from any inputs */
+ {"Left ADC", NULL, "Internal ADC Source"},
+ {"Right ADC", NULL, "Internal ADC Source"},
+
+ /* Input Side */
+ /* LIN12 PGA */
+ {"LIN12 PGA", "LIN1 Switch", "LIN1"},
+ {"LIN12 PGA", "LIN2 Switch", "LIN2"},
+ /* LIN34 PGA */
+ {"LIN34 PGA", "LIN3 Switch", "LIN3"},
+ {"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"},
+ /* INMIXL */
+ {"INMIXL", "Record Left Volume", "LOMIX"},
+ {"INMIXL", "LIN2 Volume", "LIN2"},
+ {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"},
+ {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"},
+ /* AINLMUX */
+ {"AINLMUX", "INMIXL Mix", "INMIXL"},
+ {"AINLMUX", "DIFFINL Mix", "LIN12 PGA"},
+ {"AINLMUX", "DIFFINL Mix", "LIN34 PGA"},
+ {"AINLMUX", "RXVOICE Mix", "LIN4/RXN"},
+ {"AINLMUX", "RXVOICE Mix", "RIN4/RXP"},
+ /* ADC */
+ {"Left ADC", NULL, "AINLMUX"},
+
+ /* RIN12 PGA */
+ {"RIN12 PGA", "RIN1 Switch", "RIN1"},
+ {"RIN12 PGA", "RIN2 Switch", "RIN2"},
+ /* RIN34 PGA */
+ {"RIN34 PGA", "RIN3 Switch", "RIN3"},
+ {"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"},
+ /* INMIXL */
+ {"INMIXR", "Record Right Volume", "ROMIX"},
+ {"INMIXR", "RIN2 Volume", "RIN2"},
+ {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"},
+ {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"},
+ /* AINRMUX */
+ {"AINRMUX", "INMIXR Mix", "INMIXR"},
+ {"AINRMUX", "DIFFINR Mix", "RIN12 PGA"},
+ {"AINRMUX", "DIFFINR Mix", "RIN34 PGA"},
+ {"AINRMUX", "RXVOICE Mix", "LIN4/RXN"},
+ {"AINRMUX", "RXVOICE Mix", "RIN4/RXP"},
+ /* ADC */
+ {"Right ADC", NULL, "AINRMUX"},
+
+ /* LOMIX */
+ {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"},
+ {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"},
+ {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"},
+ {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"},
+ {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"},
+
+ /* ROMIX */
+ {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"},
+ {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"},
+ {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"},
+ {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"},
+ {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"},
+
+ /* SPKMIX */
+ {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"},
+ {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"},
+ {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"},
+ {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"},
+ {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"},
+ {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"},
+ {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"},
+ {"SPKMIX", "SPKMIX Left DAC Switch", "Left DAC"},
+
+ /* LONMIX */
+ {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"},
+
+ /* LOPMIX */
+ {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"},
+
+ /* OUT3MIX */
+ {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"},
+ {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"},
+
+ /* OUT4MIX */
+ {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"},
+ {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"},
+
+ /* RONMIX */
+ {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"},
+
+ /* ROPMIX */
+ {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"},
+
+ /* Out Mixer PGAs */
+ {"LOPGA", NULL, "LOMIX"},
+ {"ROPGA", NULL, "ROMIX"},
+
+ {"LOUT PGA", NULL, "LOMIX"},
+ {"ROUT PGA", NULL, "ROMIX"},
+
+ /* Output Pins */
+ {"LON", NULL, "LONMIX"},
+ {"LOP", NULL, "LOPMIX"},
+ {"OUT3", NULL, "OUT3MIX"},
+ {"LOUT", NULL, "LOUT PGA"},
+ {"SPKN", NULL, "SPKMIX"},
+ {"ROUT", NULL, "ROUT PGA"},
+ {"OUT4", NULL, "OUT4MIX"},
+ {"ROP", NULL, "ROPMIX"},
+ {"RON", NULL, "RONMIX"},
+};
+
+static int wm8990_add_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm8990_dapm_widgets,
+ ARRAY_SIZE(wm8990_dapm_widgets));
+ /* set up the WM8990 audio map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 div2;
+ u32 n;
+ u32 k;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 16) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->div2 = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->div2 = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM8990 N value outwith recommended range! N = %u\n", Ndiv);
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+}
+
+static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ u16 reg;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct _pll_div pll_div;
+
+ if (freq_in && freq_out) {
+ pll_factors(&pll_div, freq_out * 4, freq_in);
+
+ /* Turn on PLL */
+ reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2);
+ reg |= WM8990_PLL_ENA;
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
+
+ /* sysclk comes from PLL */
+ reg = snd_soc_read(codec, WM8990_CLOCKING_2);
+ snd_soc_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC);
+
+ /* set up N , fractional mode and pre-divisor if necessary */
+ snd_soc_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM |
+ (pll_div.div2?WM8990_PRESCALE:0));
+ snd_soc_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8));
+ snd_soc_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF));
+ } else {
+ /* Turn on PLL */
+ reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2);
+ reg &= ~WM8990_PLL_ENA;
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
+ }
+ return 0;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int wm8990_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8990_priv *wm8990 = snd_soc_codec_get_drvdata(codec);
+
+ wm8990->sysclk = freq;
+ return 0;
+}
+
+/*
+ * Set's ADC and Voice DAC format.
+ */
+static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 audio1, audio3;
+
+ audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1);
+ audio3 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_3);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ audio3 &= ~WM8990_AIF_MSTR1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ audio3 |= WM8990_AIF_MSTR1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ audio1 &= ~WM8990_AIF_FMT_MASK;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ audio1 |= WM8990_AIF_TMF_I2S;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ audio1 |= WM8990_AIF_TMF_RIGHTJ;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ audio1 |= WM8990_AIF_TMF_LEFTJ;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ audio1 |= WM8990_AIF_TMF_DSP;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ audio1 |= WM8990_AIF_TMF_DSP | WM8990_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
+ snd_soc_write(codec, WM8990_AUDIO_INTERFACE_3, audio3);
+ return 0;
+}
+
+static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8990_MCLK_DIV:
+ reg = snd_soc_read(codec, WM8990_CLOCKING_2) &
+ ~WM8990_MCLK_DIV_MASK;
+ snd_soc_write(codec, WM8990_CLOCKING_2, reg | div);
+ break;
+ case WM8990_DACCLK_DIV:
+ reg = snd_soc_read(codec, WM8990_CLOCKING_2) &
+ ~WM8990_DAC_CLKDIV_MASK;
+ snd_soc_write(codec, WM8990_CLOCKING_2, reg | div);
+ break;
+ case WM8990_ADCCLK_DIV:
+ reg = snd_soc_read(codec, WM8990_CLOCKING_2) &
+ ~WM8990_ADC_CLKDIV_MASK;
+ snd_soc_write(codec, WM8990_CLOCKING_2, reg | div);
+ break;
+ case WM8990_BCLK_DIV:
+ reg = snd_soc_read(codec, WM8990_CLOCKING_1) &
+ ~WM8990_BCLK_DIV_MASK;
+ snd_soc_write(codec, WM8990_CLOCKING_1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8990_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u16 audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1);
+
+ audio1 &= ~WM8990_AIF_WL_MASK;
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ audio1 |= WM8990_AIF_WL_20BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ audio1 |= WM8990_AIF_WL_24BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ audio1 |= WM8990_AIF_WL_32BITS;
+ break;
+ }
+
+ snd_soc_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
+ return 0;
+}
+
+static int wm8990_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 val;
+
+ val = snd_soc_read(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE;
+
+ if (mute)
+ snd_soc_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
+ else
+ snd_soc_write(codec, WM8990_DAC_CTRL, val);
+
+ return 0;
+}
+
+static int wm8990_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 val;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID=2*50k */
+ val = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_1) &
+ ~WM8990_VMID_MODE_MASK;
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x2);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Enable all output discharge bits */
+ snd_soc_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
+ WM8990_DIS_RLINE | WM8990_DIS_OUT3 |
+ WM8990_DIS_OUT4 | WM8990_DIS_LOUT |
+ WM8990_DIS_ROUT);
+
+ /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */
+ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL |
+ WM8990_VMIDTOG);
+
+ /* Delay to allow output caps to discharge */
+ msleep(300);
+
+ /* Disable VMIDTOG */
+ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL);
+
+ /* disable all output discharge bits */
+ snd_soc_write(codec, WM8990_ANTIPOP1, 0);
+
+ /* Enable outputs */
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00);
+
+ msleep(50);
+
+ /* Enable VMID at 2x50k */
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02);
+
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
+
+ msleep(600);
+
+ /* Enable BUFIOEN */
+ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL |
+ WM8990_BUFIOEN);
+
+ /* Disable outputs */
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN);
+
+ /* Enable workaround for ADC clocking issue. */
+ snd_soc_write(codec, WM8990_EXT_ACCESS_ENA, 0x2);
+ snd_soc_write(codec, WM8990_EXT_CTL1, 0xa003);
+ snd_soc_write(codec, WM8990_EXT_ACCESS_ENA, 0);
+ }
+
+ /* VMID=2*250k */
+ val = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_1) &
+ ~WM8990_VMID_MODE_MASK;
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x4);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable POBCTRL and SOFT_ST */
+ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_POBCTRL | WM8990_BUFIOEN);
+
+ /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */
+ snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL |
+ WM8990_BUFIOEN);
+
+ /* mute DAC */
+ val = snd_soc_read(codec, WM8990_DAC_CTRL);
+ snd_soc_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
+
+ /* Enable any disabled outputs */
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
+
+ /* Disable VMID */
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01);
+
+ msleep(300);
+
+ /* Enable all output discharge bits */
+ snd_soc_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
+ WM8990_DIS_RLINE | WM8990_DIS_OUT3 |
+ WM8990_DIS_OUT4 | WM8990_DIS_LOUT |
+ WM8990_DIS_ROUT);
+
+ /* Disable VREF */
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ snd_soc_write(codec, WM8990_ANTIPOP2, 0x0);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define WM8990_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM8990_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+/*
+ * The WM8990 supports 2 different and mutually exclusive DAI
+ * configurations.
+ *
+ * 1. ADC/DAC on Primary Interface
+ * 2. ADC on Primary Interface/DAC on secondary
+ */
+static struct snd_soc_dai_ops wm8990_dai_ops = {
+ .hw_params = wm8990_hw_params,
+ .digital_mute = wm8990_mute,
+ .set_fmt = wm8990_set_dai_fmt,
+ .set_clkdiv = wm8990_set_dai_clkdiv,
+ .set_pll = wm8990_set_dai_pll,
+ .set_sysclk = wm8990_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8990_dai = {
+/* ADC/DAC on primary */
+ .name = "wm8990-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8990_RATES,
+ .formats = WM8990_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8990_RATES,
+ .formats = WM8990_FORMATS,},
+ .ops = &wm8990_dai_ops,
+};
+
+static int wm8990_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8990_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8990_reg); i++) {
+ if (i + 1 == WM8990_RESET)
+ continue;
+ data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+/*
+ * initialise the WM8990 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8990_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+ u16 reg;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8990: failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ wm8990_reset(codec);
+
+ /* charge output caps */
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ reg = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_4);
+ snd_soc_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1);
+
+ reg = snd_soc_read(codec, WM8990_GPIO1_GPIO2) &
+ ~WM8990_GPIO1_SEL_MASK;
+ snd_soc_write(codec, WM8990_GPIO1_GPIO2, reg | 1);
+
+ reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2);
+ snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA);
+
+ snd_soc_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8));
+ snd_soc_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8));
+
+ snd_soc_add_controls(codec, wm8990_snd_controls,
+ ARRAY_SIZE(wm8990_snd_controls));
+ wm8990_add_widgets(codec);
+
+ return 0;
+}
+
+/* power down chip */
+static int wm8990_remove(struct snd_soc_codec *codec)
+{
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8990 = {
+ .probe = wm8990_probe,
+ .remove = wm8990_remove,
+ .suspend = wm8990_suspend,
+ .resume = wm8990_resume,
+ .set_bias_level = wm8990_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8990_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8990_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8990_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8990_priv *wm8990;
+ int ret;
+
+ wm8990 = kzalloc(sizeof(struct wm8990_priv), GFP_KERNEL);
+ if (wm8990 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8990);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8990, &wm8990_dai, 1);
+ if (ret < 0)
+ kfree(wm8990);
+ return ret;
+}
+
+static __devexit int wm8990_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8990_i2c_id[] = {
+ { "wm8990", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8990_i2c_id);
+
+static struct i2c_driver wm8990_i2c_driver = {
+ .driver = {
+ .name = "wm8990-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8990_i2c_probe,
+ .remove = __devexit_p(wm8990_i2c_remove),
+ .id_table = wm8990_i2c_id,
+};
+#endif
+
+static int __init wm8990_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8990_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8990 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8990_modinit);
+
+static void __exit wm8990_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8990_i2c_driver);
+#endif
+}
+module_exit(wm8990_exit);
+
+MODULE_DESCRIPTION("ASoC WM8990 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8990.h b/sound/soc/codecs/wm8990.h
new file mode 100644
index 00000000..77c98a4b
--- /dev/null
+++ b/sound/soc/codecs/wm8990.h
@@ -0,0 +1,835 @@
+/*
+ * wm8990.h -- audio driver for WM8990
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef __WM8990REGISTERDEFS_H__
+#define __WM8990REGISTERDEFS_H__
+
+/*
+ * Register values.
+ */
+#define WM8990_RESET 0x00
+#define WM8990_POWER_MANAGEMENT_1 0x01
+#define WM8990_POWER_MANAGEMENT_2 0x02
+#define WM8990_POWER_MANAGEMENT_3 0x03
+#define WM8990_AUDIO_INTERFACE_1 0x04
+#define WM8990_AUDIO_INTERFACE_2 0x05
+#define WM8990_CLOCKING_1 0x06
+#define WM8990_CLOCKING_2 0x07
+#define WM8990_AUDIO_INTERFACE_3 0x08
+#define WM8990_AUDIO_INTERFACE_4 0x09
+#define WM8990_DAC_CTRL 0x0A
+#define WM8990_LEFT_DAC_DIGITAL_VOLUME 0x0B
+#define WM8990_RIGHT_DAC_DIGITAL_VOLUME 0x0C
+#define WM8990_DIGITAL_SIDE_TONE 0x0D
+#define WM8990_ADC_CTRL 0x0E
+#define WM8990_LEFT_ADC_DIGITAL_VOLUME 0x0F
+#define WM8990_RIGHT_ADC_DIGITAL_VOLUME 0x10
+#define WM8990_GPIO_CTRL_1 0x12
+#define WM8990_GPIO1_GPIO2 0x13
+#define WM8990_GPIO3_GPIO4 0x14
+#define WM8990_GPIO5_GPIO6 0x15
+#define WM8990_GPIOCTRL_2 0x16
+#define WM8990_GPIO_POL 0x17
+#define WM8990_LEFT_LINE_INPUT_1_2_VOLUME 0x18
+#define WM8990_LEFT_LINE_INPUT_3_4_VOLUME 0x19
+#define WM8990_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A
+#define WM8990_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B
+#define WM8990_LEFT_OUTPUT_VOLUME 0x1C
+#define WM8990_RIGHT_OUTPUT_VOLUME 0x1D
+#define WM8990_LINE_OUTPUTS_VOLUME 0x1E
+#define WM8990_OUT3_4_VOLUME 0x1F
+#define WM8990_LEFT_OPGA_VOLUME 0x20
+#define WM8990_RIGHT_OPGA_VOLUME 0x21
+#define WM8990_SPEAKER_VOLUME 0x22
+#define WM8990_CLASSD1 0x23
+#define WM8990_CLASSD3 0x25
+#define WM8990_CLASSD4 0x26
+#define WM8990_INPUT_MIXER1 0x27
+#define WM8990_INPUT_MIXER2 0x28
+#define WM8990_INPUT_MIXER3 0x29
+#define WM8990_INPUT_MIXER4 0x2A
+#define WM8990_INPUT_MIXER5 0x2B
+#define WM8990_INPUT_MIXER6 0x2C
+#define WM8990_OUTPUT_MIXER1 0x2D
+#define WM8990_OUTPUT_MIXER2 0x2E
+#define WM8990_OUTPUT_MIXER3 0x2F
+#define WM8990_OUTPUT_MIXER4 0x30
+#define WM8990_OUTPUT_MIXER5 0x31
+#define WM8990_OUTPUT_MIXER6 0x32
+#define WM8990_OUT3_4_MIXER 0x33
+#define WM8990_LINE_MIXER1 0x34
+#define WM8990_LINE_MIXER2 0x35
+#define WM8990_SPEAKER_MIXER 0x36
+#define WM8990_ADDITIONAL_CONTROL 0x37
+#define WM8990_ANTIPOP1 0x38
+#define WM8990_ANTIPOP2 0x39
+#define WM8990_MICBIAS 0x3A
+#define WM8990_PLL1 0x3C
+#define WM8990_PLL2 0x3D
+#define WM8990_PLL3 0x3E
+#define WM8990_INTDRIVBITS 0x3F
+
+#define WM8990_EXT_ACCESS_ENA 0x75
+#define WM8990_EXT_CTL1 0x7a
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Reset
+ */
+#define WM8990_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM8990_SPK_ENA 0x1000 /* SPK_ENA */
+#define WM8990_SPK_ENA_BIT 12
+#define WM8990_OUT3_ENA 0x0800 /* OUT3_ENA */
+#define WM8990_OUT3_ENA_BIT 11
+#define WM8990_OUT4_ENA 0x0400 /* OUT4_ENA */
+#define WM8990_OUT4_ENA_BIT 10
+#define WM8990_LOUT_ENA 0x0200 /* LOUT_ENA */
+#define WM8990_LOUT_ENA_BIT 9
+#define WM8990_ROUT_ENA 0x0100 /* ROUT_ENA */
+#define WM8990_ROUT_ENA_BIT 8
+#define WM8990_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */
+#define WM8990_MICBIAS_ENA_BIT 4
+#define WM8990_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */
+#define WM8990_VREF_ENA 0x0001 /* VREF_ENA */
+#define WM8990_VREF_ENA_BIT 0
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM8990_PLL_ENA 0x8000 /* PLL_ENA */
+#define WM8990_PLL_ENA_BIT 15
+#define WM8990_TSHUT_ENA 0x4000 /* TSHUT_ENA */
+#define WM8990_TSHUT_ENA_BIT 14
+#define WM8990_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */
+#define WM8990_TSHUT_OPDIS_BIT 13
+#define WM8990_OPCLK_ENA 0x0800 /* OPCLK_ENA */
+#define WM8990_OPCLK_ENA_BIT 11
+#define WM8990_AINL_ENA 0x0200 /* AINL_ENA */
+#define WM8990_AINL_ENA_BIT 9
+#define WM8990_AINR_ENA 0x0100 /* AINR_ENA */
+#define WM8990_AINR_ENA_BIT 8
+#define WM8990_LIN34_ENA 0x0080 /* LIN34_ENA */
+#define WM8990_LIN34_ENA_BIT 7
+#define WM8990_LIN12_ENA 0x0040 /* LIN12_ENA */
+#define WM8990_LIN12_ENA_BIT 6
+#define WM8990_RIN34_ENA 0x0020 /* RIN34_ENA */
+#define WM8990_RIN34_ENA_BIT 5
+#define WM8990_RIN12_ENA 0x0010 /* RIN12_ENA */
+#define WM8990_RIN12_ENA_BIT 4
+#define WM8990_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8990_ADCL_ENA_BIT 1
+#define WM8990_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8990_ADCR_ENA_BIT 0
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM8990_LON_ENA 0x2000 /* LON_ENA */
+#define WM8990_LON_ENA_BIT 13
+#define WM8990_LOP_ENA 0x1000 /* LOP_ENA */
+#define WM8990_LOP_ENA_BIT 12
+#define WM8990_RON_ENA 0x0800 /* RON_ENA */
+#define WM8990_RON_ENA_BIT 11
+#define WM8990_ROP_ENA 0x0400 /* ROP_ENA */
+#define WM8990_ROP_ENA_BIT 10
+#define WM8990_LOPGA_ENA 0x0080 /* LOPGA_ENA */
+#define WM8990_LOPGA_ENA_BIT 7
+#define WM8990_ROPGA_ENA 0x0040 /* ROPGA_ENA */
+#define WM8990_ROPGA_ENA_BIT 6
+#define WM8990_LOMIX_ENA 0x0020 /* LOMIX_ENA */
+#define WM8990_LOMIX_ENA_BIT 5
+#define WM8990_ROMIX_ENA 0x0010 /* ROMIX_ENA */
+#define WM8990_ROMIX_ENA_BIT 4
+#define WM8990_DACL_ENA 0x0002 /* DACL_ENA */
+#define WM8990_DACL_ENA_BIT 1
+#define WM8990_DACR_ENA 0x0001 /* DACR_ENA */
+#define WM8990_DACR_ENA_BIT 0
+
+/*
+ * R4 (0x04) - Audio Interface (1)
+ */
+#define WM8990_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */
+#define WM8990_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */
+#define WM8990_AIFADC_TDM 0x2000 /* AIFADC_TDM */
+#define WM8990_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */
+#define WM8990_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */
+#define WM8990_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */
+#define WM8990_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */
+#define WM8990_AIF_WL_16BITS (0 << 5)
+#define WM8990_AIF_WL_20BITS (1 << 5)
+#define WM8990_AIF_WL_24BITS (2 << 5)
+#define WM8990_AIF_WL_32BITS (3 << 5)
+#define WM8990_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */
+#define WM8990_AIF_TMF_RIGHTJ (0 << 3)
+#define WM8990_AIF_TMF_LEFTJ (1 << 3)
+#define WM8990_AIF_TMF_I2S (2 << 3)
+#define WM8990_AIF_TMF_DSP (3 << 3)
+
+/*
+ * R5 (0x05) - Audio Interface (2)
+ */
+#define WM8990_DACL_SRC 0x8000 /* DACL_SRC */
+#define WM8990_DACR_SRC 0x4000 /* DACR_SRC */
+#define WM8990_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8990_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8990_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST */
+#define WM8990_DAC_COMP 0x0010 /* DAC_COMP */
+#define WM8990_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */
+#define WM8990_ADC_COMP 0x0004 /* ADC_COMP */
+#define WM8990_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */
+#define WM8990_LOOPBACK 0x0001 /* LOOPBACK */
+
+/*
+ * R6 (0x06) - Clocking (1)
+ */
+#define WM8990_TOCLK_RATE 0x8000 /* TOCLK_RATE */
+#define WM8990_TOCLK_ENA 0x4000 /* TOCLK_ENA */
+#define WM8990_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */
+#define WM8990_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */
+#define WM8990_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */
+#define WM8990_BCLK_DIV_1 (0x0 << 1)
+#define WM8990_BCLK_DIV_1_5 (0x1 << 1)
+#define WM8990_BCLK_DIV_2 (0x2 << 1)
+#define WM8990_BCLK_DIV_3 (0x3 << 1)
+#define WM8990_BCLK_DIV_4 (0x4 << 1)
+#define WM8990_BCLK_DIV_5_5 (0x5 << 1)
+#define WM8990_BCLK_DIV_6 (0x6 << 1)
+#define WM8990_BCLK_DIV_8 (0x7 << 1)
+#define WM8990_BCLK_DIV_11 (0x8 << 1)
+#define WM8990_BCLK_DIV_12 (0x9 << 1)
+#define WM8990_BCLK_DIV_16 (0xA << 1)
+#define WM8990_BCLK_DIV_22 (0xB << 1)
+#define WM8990_BCLK_DIV_24 (0xC << 1)
+#define WM8990_BCLK_DIV_32 (0xD << 1)
+#define WM8990_BCLK_DIV_44 (0xE << 1)
+#define WM8990_BCLK_DIV_48 (0xF << 1)
+
+/*
+ * R7 (0x07) - Clocking (2)
+ */
+#define WM8990_MCLK_SRC 0x8000 /* MCLK_SRC */
+#define WM8990_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */
+#define WM8990_CLK_FORCE 0x2000 /* CLK_FORCE */
+#define WM8990_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */
+#define WM8990_MCLK_DIV_1 (0 << 11)
+#define WM8990_MCLK_DIV_2 (2 << 11)
+#define WM8990_MCLK_INV 0x0400 /* MCLK_INV */
+#define WM8990_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV */
+#define WM8990_ADC_CLKDIV_1 (0 << 5)
+#define WM8990_ADC_CLKDIV_1_5 (1 << 5)
+#define WM8990_ADC_CLKDIV_2 (2 << 5)
+#define WM8990_ADC_CLKDIV_3 (3 << 5)
+#define WM8990_ADC_CLKDIV_4 (4 << 5)
+#define WM8990_ADC_CLKDIV_5_5 (5 << 5)
+#define WM8990_ADC_CLKDIV_6 (6 << 5)
+#define WM8990_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */
+#define WM8990_DAC_CLKDIV_1 (0 << 2)
+#define WM8990_DAC_CLKDIV_1_5 (1 << 2)
+#define WM8990_DAC_CLKDIV_2 (2 << 2)
+#define WM8990_DAC_CLKDIV_3 (3 << 2)
+#define WM8990_DAC_CLKDIV_4 (4 << 2)
+#define WM8990_DAC_CLKDIV_5_5 (5 << 2)
+#define WM8990_DAC_CLKDIV_6 (6 << 2)
+
+/*
+ * R8 (0x08) - Audio Interface (3)
+ */
+#define WM8990_AIF_MSTR1 0x8000 /* AIF_MSTR1 */
+#define WM8990_AIF_MSTR2 0x4000 /* AIF_MSTR2 */
+#define WM8990_AIF_SEL 0x2000 /* AIF_SEL */
+#define WM8990_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */
+#define WM8990_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE */
+
+/*
+ * R9 (0x09) - Audio Interface (4)
+ */
+#define WM8990_ALRCGPIO1 0x8000 /* ALRCGPIO1 */
+#define WM8990_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */
+#define WM8990_AIF_TRIS 0x2000 /* AIF_TRIS */
+#define WM8990_DACLRC_DIR 0x0800 /* DACLRC_DIR */
+#define WM8990_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE */
+
+/*
+ * R10 (0x0A) - DAC CTRL
+ */
+#define WM8990_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */
+#define WM8990_DAC_MONO 0x0200 /* DAC_MONO */
+#define WM8990_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */
+#define WM8990_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */
+#define WM8990_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */
+#define WM8990_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */
+#define WM8990_DAC_MUTE 0x0004 /* DAC_MUTE */
+#define WM8990_DACL_DATINV 0x0002 /* DACL_DATINV */
+#define WM8990_DACR_DATINV 0x0001 /* DACR_DATINV */
+
+/*
+ * R11 (0x0B) - Left DAC Digital Volume
+ */
+#define WM8990_DAC_VU 0x0100 /* DAC_VU */
+#define WM8990_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8990_DACL_VOL_SHIFT 0
+/*
+ * R12 (0x0C) - Right DAC Digital Volume
+ */
+#define WM8990_DAC_VU 0x0100 /* DAC_VU */
+#define WM8990_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8990_DACR_VOL_SHIFT 0
+/*
+ * R13 (0x0D) - Digital Side Tone
+ */
+#define WM8990_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL */
+#define WM8990_ADCL_DAC_SVOL_SHIFT 9
+#define WM8990_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL */
+#define WM8990_ADCR_DAC_SVOL_SHIFT 5
+#define WM8990_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */
+#define WM8990_ADC_TO_DACL_SHIFT 2
+#define WM8990_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */
+#define WM8990_ADC_TO_DACR_SHIFT 0
+
+/*
+ * R14 (0x0E) - ADC CTRL
+ */
+#define WM8990_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */
+#define WM8990_ADC_HPF_ENA_BIT 8
+#define WM8990_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */
+#define WM8990_ADC_HPF_CUT_SHIFT 5
+#define WM8990_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8990_ADCL_DATINV_BIT 1
+#define WM8990_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8990_ADCR_DATINV_BIT 0
+
+/*
+ * R15 (0x0F) - Left ADC Digital Volume
+ */
+#define WM8990_ADC_VU 0x0100 /* ADC_VU */
+#define WM8990_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8990_ADCL_VOL_SHIFT 0
+
+/*
+ * R16 (0x10) - Right ADC Digital Volume
+ */
+#define WM8990_ADC_VU 0x0100 /* ADC_VU */
+#define WM8990_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8990_ADCR_VOL_SHIFT 0
+
+/*
+ * R18 (0x12) - GPIO CTRL 1
+ */
+#define WM8990_IRQ 0x1000 /* IRQ */
+#define WM8990_TEMPOK 0x0800 /* TEMPOK */
+#define WM8990_MICSHRT 0x0400 /* MICSHRT */
+#define WM8990_MICDET 0x0200 /* MICDET */
+#define WM8990_PLL_LCK 0x0100 /* PLL_LCK */
+#define WM8990_GPI8_STATUS 0x0080 /* GPI8_STATUS */
+#define WM8990_GPI7_STATUS 0x0040 /* GPI7_STATUS */
+#define WM8990_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */
+#define WM8990_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */
+#define WM8990_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */
+#define WM8990_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */
+#define WM8990_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */
+#define WM8990_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */
+
+/*
+ * R19 (0x13) - GPIO1 & GPIO2
+ */
+#define WM8990_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */
+#define WM8990_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */
+#define WM8990_GPIO2_PU 0x2000 /* GPIO2_PU */
+#define WM8990_GPIO2_PD 0x1000 /* GPIO2_PD */
+#define WM8990_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */
+#define WM8990_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */
+#define WM8990_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */
+#define WM8990_GPIO1_PU 0x0020 /* GPIO1_PU */
+#define WM8990_GPIO1_PD 0x0010 /* GPIO1_PD */
+#define WM8990_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */
+
+/*
+ * R20 (0x14) - GPIO3 & GPIO4
+ */
+#define WM8990_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */
+#define WM8990_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */
+#define WM8990_GPIO4_PU 0x2000 /* GPIO4_PU */
+#define WM8990_GPIO4_PD 0x1000 /* GPIO4_PD */
+#define WM8990_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */
+#define WM8990_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */
+#define WM8990_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */
+#define WM8990_GPIO3_PU 0x0020 /* GPIO3_PU */
+#define WM8990_GPIO3_PD 0x0010 /* GPIO3_PD */
+#define WM8990_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */
+
+/*
+ * R21 (0x15) - GPIO5 & GPIO6
+ */
+#define WM8990_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */
+#define WM8990_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */
+#define WM8990_GPIO6_PU 0x2000 /* GPIO6_PU */
+#define WM8990_GPIO6_PD 0x1000 /* GPIO6_PD */
+#define WM8990_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */
+#define WM8990_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */
+#define WM8990_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */
+#define WM8990_GPIO5_PU 0x0020 /* GPIO5_PU */
+#define WM8990_GPIO5_PD 0x0010 /* GPIO5_PD */
+#define WM8990_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */
+
+/*
+ * R22 (0x16) - GPIOCTRL 2
+ */
+#define WM8990_RD_3W_ENA 0x8000 /* RD_3W_ENA */
+#define WM8990_MODE_3W4W 0x4000 /* MODE_3W4W */
+#define WM8990_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */
+#define WM8990_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */
+#define WM8990_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */
+#define WM8990_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */
+#define WM8990_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */
+#define WM8990_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */
+#define WM8990_GPI8_ENA 0x0010 /* GPI8_ENA */
+#define WM8990_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */
+#define WM8990_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */
+#define WM8990_GPI7_ENA 0x0001 /* GPI7_ENA */
+
+/*
+ * R23 (0x17) - GPIO_POL
+ */
+#define WM8990_IRQ_INV 0x1000 /* IRQ_INV */
+#define WM8990_TEMPOK_POL 0x0800 /* TEMPOK_POL */
+#define WM8990_MICSHRT_POL 0x0400 /* MICSHRT_POL */
+#define WM8990_MICDET_POL 0x0200 /* MICDET_POL */
+#define WM8990_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */
+#define WM8990_GPI8_POL 0x0080 /* GPI8_POL */
+#define WM8990_GPI7_POL 0x0040 /* GPI7_POL */
+#define WM8990_GPIO6_POL 0x0020 /* GPIO6_POL */
+#define WM8990_GPIO5_POL 0x0010 /* GPIO5_POL */
+#define WM8990_GPIO4_POL 0x0008 /* GPIO4_POL */
+#define WM8990_GPIO3_POL 0x0004 /* GPIO3_POL */
+#define WM8990_GPIO2_POL 0x0002 /* GPIO2_POL */
+#define WM8990_GPIO1_POL 0x0001 /* GPIO1_POL */
+
+/*
+ * R24 (0x18) - Left Line Input 1&2 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_LI12MUTE 0x0080 /* LI12MUTE */
+#define WM8990_LI12MUTE_BIT 7
+#define WM8990_LI12ZC 0x0040 /* LI12ZC */
+#define WM8990_LI12ZC_BIT 6
+#define WM8990_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */
+#define WM8990_LIN12VOL_SHIFT 0
+/*
+ * R25 (0x19) - Left Line Input 3&4 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_LI34MUTE 0x0080 /* LI34MUTE */
+#define WM8990_LI34MUTE_BIT 7
+#define WM8990_LI34ZC 0x0040 /* LI34ZC */
+#define WM8990_LI34ZC_BIT 6
+#define WM8990_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */
+#define WM8990_LIN34VOL_SHIFT 0
+
+/*
+ * R26 (0x1A) - Right Line Input 1&2 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_RI12MUTE 0x0080 /* RI12MUTE */
+#define WM8990_RI12MUTE_BIT 7
+#define WM8990_RI12ZC 0x0040 /* RI12ZC */
+#define WM8990_RI12ZC_BIT 6
+#define WM8990_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */
+#define WM8990_RIN12VOL_SHIFT 0
+
+/*
+ * R27 (0x1B) - Right Line Input 3&4 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_RI34MUTE 0x0080 /* RI34MUTE */
+#define WM8990_RI34MUTE_BIT 7
+#define WM8990_RI34ZC 0x0040 /* RI34ZC */
+#define WM8990_RI34ZC_BIT 6
+#define WM8990_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */
+#define WM8990_RIN34VOL_SHIFT 0
+
+/*
+ * R28 (0x1C) - Left Output Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_LOZC 0x0080 /* LOZC */
+#define WM8990_LOZC_BIT 7
+#define WM8990_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */
+#define WM8990_LOUTVOL_SHIFT 0
+/*
+ * R29 (0x1D) - Right Output Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_ROZC 0x0080 /* ROZC */
+#define WM8990_ROZC_BIT 7
+#define WM8990_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */
+#define WM8990_ROUTVOL_SHIFT 0
+/*
+ * R30 (0x1E) - Line Outputs Volume
+ */
+#define WM8990_LONMUTE 0x0040 /* LONMUTE */
+#define WM8990_LONMUTE_BIT 6
+#define WM8990_LOPMUTE 0x0020 /* LOPMUTE */
+#define WM8990_LOPMUTE_BIT 5
+#define WM8990_LOATTN 0x0010 /* LOATTN */
+#define WM8990_LOATTN_BIT 4
+#define WM8990_RONMUTE 0x0004 /* RONMUTE */
+#define WM8990_RONMUTE_BIT 2
+#define WM8990_ROPMUTE 0x0002 /* ROPMUTE */
+#define WM8990_ROPMUTE_BIT 1
+#define WM8990_ROATTN 0x0001 /* ROATTN */
+#define WM8990_ROATTN_BIT 0
+
+/*
+ * R31 (0x1F) - Out3/4 Volume
+ */
+#define WM8990_OUT3MUTE 0x0020 /* OUT3MUTE */
+#define WM8990_OUT3MUTE_BIT 5
+#define WM8990_OUT3ATTN 0x0010 /* OUT3ATTN */
+#define WM8990_OUT3ATTN_BIT 4
+#define WM8990_OUT4MUTE 0x0002 /* OUT4MUTE */
+#define WM8990_OUT4MUTE_BIT 1
+#define WM8990_OUT4ATTN 0x0001 /* OUT4ATTN */
+#define WM8990_OUT4ATTN_BIT 0
+
+/*
+ * R32 (0x20) - Left OPGA Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_LOPGAZC 0x0080 /* LOPGAZC */
+#define WM8990_LOPGAZC_BIT 7
+#define WM8990_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */
+#define WM8990_LOPGAVOL_SHIFT 0
+
+/*
+ * R33 (0x21) - Right OPGA Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_ROPGAZC 0x0080 /* ROPGAZC */
+#define WM8990_ROPGAZC_BIT 7
+#define WM8990_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */
+#define WM8990_ROPGAVOL_SHIFT 0
+/*
+ * R34 (0x22) - Speaker Volume
+ */
+#define WM8990_SPKATTN_MASK 0x0003 /* SPKATTN - [1:0] */
+#define WM8990_SPKATTN_SHIFT 0
+
+/*
+ * R35 (0x23) - ClassD1
+ */
+#define WM8990_CDMODE 0x0100 /* CDMODE */
+#define WM8990_CDMODE_BIT 8
+
+/*
+ * R37 (0x25) - ClassD3
+ */
+#define WM8990_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */
+#define WM8990_DCGAIN_SHIFT 3
+#define WM8990_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */
+#define WM8990_ACGAIN_SHIFT 0
+
+/*
+ * R38 (0x26) - ClassD4
+ */
+#define WM8990_SPKZC_MASK 0x0001 /* SPKZC */
+#define WM8990_SPKZC_SHIFT 7 /* SPKZC */
+#define WM8990_SPKVOL_MASK 0x007F /* SPKVOL - [6:0] */
+#define WM8990_SPKVOL_SHIFT 0 /* SPKVOL - [6:0] */
+
+/*
+ * R39 (0x27) - Input Mixer1
+ */
+#define WM8990_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */
+#define WM8990_AINLMODE_SHIFT 2
+#define WM8990_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */
+#define WM8990_AINRMODE_SHIFT 0
+
+/*
+ * R40 (0x28) - Input Mixer2
+ */
+#define WM8990_LMP4 0x0080 /* LMP4 */
+#define WM8990_LMP4_BIT 7 /* LMP4 */
+#define WM8990_LMN3 0x0040 /* LMN3 */
+#define WM8990_LMN3_BIT 6 /* LMN3 */
+#define WM8990_LMP2 0x0020 /* LMP2 */
+#define WM8990_LMP2_BIT 5 /* LMP2 */
+#define WM8990_LMN1 0x0010 /* LMN1 */
+#define WM8990_LMN1_BIT 4 /* LMN1 */
+#define WM8990_RMP4 0x0008 /* RMP4 */
+#define WM8990_RMP4_BIT 3 /* RMP4 */
+#define WM8990_RMN3 0x0004 /* RMN3 */
+#define WM8990_RMN3_BIT 2 /* RMN3 */
+#define WM8990_RMP2 0x0002 /* RMP2 */
+#define WM8990_RMP2_BIT 1 /* RMP2 */
+#define WM8990_RMN1 0x0001 /* RMN1 */
+#define WM8990_RMN1_BIT 0 /* RMN1 */
+
+/*
+ * R41 (0x29) - Input Mixer3
+ */
+#define WM8990_L34MNB 0x0100 /* L34MNB */
+#define WM8990_L34MNB_BIT 8
+#define WM8990_L34MNBST 0x0080 /* L34MNBST */
+#define WM8990_L34MNBST_BIT 7
+#define WM8990_L12MNB 0x0020 /* L12MNB */
+#define WM8990_L12MNB_BIT 5
+#define WM8990_L12MNBST 0x0010 /* L12MNBST */
+#define WM8990_L12MNBST_BIT 4
+#define WM8990_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */
+#define WM8990_LDBVOL_SHIFT 0
+
+/*
+ * R42 (0x2A) - Input Mixer4
+ */
+#define WM8990_R34MNB 0x0100 /* R34MNB */
+#define WM8990_R34MNB_BIT 8
+#define WM8990_R34MNBST 0x0080 /* R34MNBST */
+#define WM8990_R34MNBST_BIT 7
+#define WM8990_R12MNB 0x0020 /* R12MNB */
+#define WM8990_R12MNB_BIT 5
+#define WM8990_R12MNBST 0x0010 /* R12MNBST */
+#define WM8990_R12MNBST_BIT 4
+#define WM8990_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */
+#define WM8990_RDBVOL_SHIFT 0
+
+/*
+ * R43 (0x2B) - Input Mixer5
+ */
+#define WM8990_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */
+#define WM8990_LI2BVOL_SHIFT 6
+#define WM8990_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */
+#define WM8990_LR4BVOL_SHIFT 3
+#define WM8990_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */
+#define WM8990_LL4BVOL_SHIFT 0
+
+/*
+ * R44 (0x2C) - Input Mixer6
+ */
+#define WM8990_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */
+#define WM8990_RI2BVOL_SHIFT 6
+#define WM8990_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */
+#define WM8990_RL4BVOL_SHIFT 3
+#define WM8990_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */
+#define WM8990_RR4BVOL_SHIFT 0
+
+/*
+ * R45 (0x2D) - Output Mixer1
+ */
+#define WM8990_LRBLO 0x0080 /* LRBLO */
+#define WM8990_LRBLO_BIT 7
+#define WM8990_LLBLO 0x0040 /* LLBLO */
+#define WM8990_LLBLO_BIT 6
+#define WM8990_LRI3LO 0x0020 /* LRI3LO */
+#define WM8990_LRI3LO_BIT 5
+#define WM8990_LLI3LO 0x0010 /* LLI3LO */
+#define WM8990_LLI3LO_BIT 4
+#define WM8990_LR12LO 0x0008 /* LR12LO */
+#define WM8990_LR12LO_BIT 3
+#define WM8990_LL12LO 0x0004 /* LL12LO */
+#define WM8990_LL12LO_BIT 2
+#define WM8990_LDLO 0x0001 /* LDLO */
+#define WM8990_LDLO_BIT 0
+
+/*
+ * R46 (0x2E) - Output Mixer2
+ */
+#define WM8990_RLBRO 0x0080 /* RLBRO */
+#define WM8990_RLBRO_BIT 7
+#define WM8990_RRBRO 0x0040 /* RRBRO */
+#define WM8990_RRBRO_BIT 6
+#define WM8990_RLI3RO 0x0020 /* RLI3RO */
+#define WM8990_RLI3RO_BIT 5
+#define WM8990_RRI3RO 0x0010 /* RRI3RO */
+#define WM8990_RRI3RO_BIT 4
+#define WM8990_RL12RO 0x0008 /* RL12RO */
+#define WM8990_RL12RO_BIT 3
+#define WM8990_RR12RO 0x0004 /* RR12RO */
+#define WM8990_RR12RO_BIT 2
+#define WM8990_RDRO 0x0001 /* RDRO */
+#define WM8990_RDRO_BIT 0
+
+/*
+ * R47 (0x2F) - Output Mixer3
+ */
+#define WM8990_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */
+#define WM8990_LLI3LOVOL_SHIFT 6
+#define WM8990_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */
+#define WM8990_LR12LOVOL_SHIFT 3
+#define WM8990_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */
+#define WM8990_LL12LOVOL_SHIFT 0
+
+/*
+ * R48 (0x30) - Output Mixer4
+ */
+#define WM8990_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */
+#define WM8990_RRI3ROVOL_SHIFT 6
+#define WM8990_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */
+#define WM8990_RL12ROVOL_SHIFT 3
+#define WM8990_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */
+#define WM8990_RR12ROVOL_SHIFT 0
+
+/*
+ * R49 (0x31) - Output Mixer5
+ */
+#define WM8990_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */
+#define WM8990_LRI3LOVOL_SHIFT 6
+#define WM8990_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */
+#define WM8990_LRBLOVOL_SHIFT 3
+#define WM8990_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */
+#define WM8990_LLBLOVOL_SHIFT 0
+
+/*
+ * R50 (0x32) - Output Mixer6
+ */
+#define WM8990_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */
+#define WM8990_RLI3ROVOL_SHIFT 6
+#define WM8990_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */
+#define WM8990_RLBROVOL_SHIFT 3
+#define WM8990_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */
+#define WM8990_RRBROVOL_SHIFT 0
+
+/*
+ * R51 (0x33) - Out3/4 Mixer
+ */
+#define WM8990_VSEL_MASK 0x0180 /* VSEL - [8:7] */
+#define WM8990_LI4O3 0x0020 /* LI4O3 */
+#define WM8990_LI4O3_BIT 5
+#define WM8990_LPGAO3 0x0010 /* LPGAO3 */
+#define WM8990_LPGAO3_BIT 4
+#define WM8990_RI4O4 0x0002 /* RI4O4 */
+#define WM8990_RI4O4_BIT 1
+#define WM8990_RPGAO4 0x0001 /* RPGAO4 */
+#define WM8990_RPGAO4_BIT 0
+/*
+ * R52 (0x34) - Line Mixer1
+ */
+#define WM8990_LLOPGALON 0x0040 /* LLOPGALON */
+#define WM8990_LLOPGALON_BIT 6
+#define WM8990_LROPGALON 0x0020 /* LROPGALON */
+#define WM8990_LROPGALON_BIT 5
+#define WM8990_LOPLON 0x0010 /* LOPLON */
+#define WM8990_LOPLON_BIT 4
+#define WM8990_LR12LOP 0x0004 /* LR12LOP */
+#define WM8990_LR12LOP_BIT 2
+#define WM8990_LL12LOP 0x0002 /* LL12LOP */
+#define WM8990_LL12LOP_BIT 1
+#define WM8990_LLOPGALOP 0x0001 /* LLOPGALOP */
+#define WM8990_LLOPGALOP_BIT 0
+/*
+ * R53 (0x35) - Line Mixer2
+ */
+#define WM8990_RROPGARON 0x0040 /* RROPGARON */
+#define WM8990_RROPGARON_BIT 6
+#define WM8990_RLOPGARON 0x0020 /* RLOPGARON */
+#define WM8990_RLOPGARON_BIT 5
+#define WM8990_ROPRON 0x0010 /* ROPRON */
+#define WM8990_ROPRON_BIT 4
+#define WM8990_RL12ROP 0x0004 /* RL12ROP */
+#define WM8990_RL12ROP_BIT 2
+#define WM8990_RR12ROP 0x0002 /* RR12ROP */
+#define WM8990_RR12ROP_BIT 1
+#define WM8990_RROPGAROP 0x0001 /* RROPGAROP */
+#define WM8990_RROPGAROP_BIT 0
+
+/*
+ * R54 (0x36) - Speaker Mixer
+ */
+#define WM8990_LB2SPK 0x0080 /* LB2SPK */
+#define WM8990_LB2SPK_BIT 7
+#define WM8990_RB2SPK 0x0040 /* RB2SPK */
+#define WM8990_RB2SPK_BIT 6
+#define WM8990_LI2SPK 0x0020 /* LI2SPK */
+#define WM8990_LI2SPK_BIT 5
+#define WM8990_RI2SPK 0x0010 /* RI2SPK */
+#define WM8990_RI2SPK_BIT 4
+#define WM8990_LOPGASPK 0x0008 /* LOPGASPK */
+#define WM8990_LOPGASPK_BIT 3
+#define WM8990_ROPGASPK 0x0004 /* ROPGASPK */
+#define WM8990_ROPGASPK_BIT 2
+#define WM8990_LDSPK 0x0002 /* LDSPK */
+#define WM8990_LDSPK_BIT 1
+#define WM8990_RDSPK 0x0001 /* RDSPK */
+#define WM8990_RDSPK_BIT 0
+
+/*
+ * R55 (0x37) - Additional Control
+ */
+#define WM8990_VROI 0x0001 /* VROI */
+
+/*
+ * R56 (0x38) - AntiPOP1
+ */
+#define WM8990_DIS_LLINE 0x0020 /* DIS_LLINE */
+#define WM8990_DIS_RLINE 0x0010 /* DIS_RLINE */
+#define WM8990_DIS_OUT3 0x0008 /* DIS_OUT3 */
+#define WM8990_DIS_OUT4 0x0004 /* DIS_OUT4 */
+#define WM8990_DIS_LOUT 0x0002 /* DIS_LOUT */
+#define WM8990_DIS_ROUT 0x0001 /* DIS_ROUT */
+
+/*
+ * R57 (0x39) - AntiPOP2
+ */
+#define WM8990_SOFTST 0x0040 /* SOFTST */
+#define WM8990_BUFIOEN 0x0008 /* BUFIOEN */
+#define WM8990_BUFDCOPEN 0x0004 /* BUFDCOPEN */
+#define WM8990_POBCTRL 0x0002 /* POBCTRL */
+#define WM8990_VMIDTOG 0x0001 /* VMIDTOG */
+
+/*
+ * R58 (0x3A) - MICBIAS
+ */
+#define WM8990_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */
+#define WM8990_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */
+#define WM8990_MCD 0x0004 /* MCD */
+#define WM8990_MBSEL 0x0001 /* MBSEL */
+
+/*
+ * R60 (0x3C) - PLL1
+ */
+#define WM8990_SDM 0x0080 /* SDM */
+#define WM8990_PRESCALE 0x0040 /* PRESCALE */
+#define WM8990_PLLN_MASK 0x000F /* PLLN - [3:0] */
+
+/*
+ * R61 (0x3D) - PLL2
+ */
+#define WM8990_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */
+
+/*
+ * R62 (0x3E) - PLL3
+ */
+#define WM8990_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */
+
+/*
+ * R63 (0x3F) - Internal Driver Bits
+ */
+#define WM8990_INMIXL_PWR_BIT 0
+#define WM8990_AINLMUX_PWR_BIT 1
+#define WM8990_INMIXR_PWR_BIT 2
+#define WM8990_AINRMUX_PWR_BIT 3
+
+#define WM8990_MCLK_DIV 0
+#define WM8990_DACCLK_DIV 1
+#define WM8990_ADCCLK_DIV 2
+#define WM8990_BCLK_DIV 3
+
+#endif /* __WM8990REGISTERDEFS_H__ */
+/*------------------------------ END OF FILE ---------------------------------*/
diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c
new file mode 100644
index 00000000..6af23d06
--- /dev/null
+++ b/sound/soc/codecs/wm8991.c
@@ -0,0 +1,1426 @@
+/*
+ * wm8991.c -- WM8991 ALSA Soc Audio driver
+ *
+ * Copyright 2007-2010 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * linux@wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <asm/div64.h>
+
+#include "wm8991.h"
+
+struct wm8991_priv {
+ enum snd_soc_control_type control_type;
+ unsigned int pcmclk;
+};
+
+static const u16 wm8991_reg_defs[] = {
+ 0x8991, /* R0 - Reset */
+ 0x0000, /* R1 - Power Management (1) */
+ 0x6000, /* R2 - Power Management (2) */
+ 0x0000, /* R3 - Power Management (3) */
+ 0x4050, /* R4 - Audio Interface (1) */
+ 0x4000, /* R5 - Audio Interface (2) */
+ 0x01C8, /* R6 - Clocking (1) */
+ 0x0000, /* R7 - Clocking (2) */
+ 0x0040, /* R8 - Audio Interface (3) */
+ 0x0040, /* R9 - Audio Interface (4) */
+ 0x0004, /* R10 - DAC CTRL */
+ 0x00C0, /* R11 - Left DAC Digital Volume */
+ 0x00C0, /* R12 - Right DAC Digital Volume */
+ 0x0000, /* R13 - Digital Side Tone */
+ 0x0100, /* R14 - ADC CTRL */
+ 0x00C0, /* R15 - Left ADC Digital Volume */
+ 0x00C0, /* R16 - Right ADC Digital Volume */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 - GPIO CTRL 1 */
+ 0x1000, /* R19 - GPIO1 & GPIO2 */
+ 0x1010, /* R20 - GPIO3 & GPIO4 */
+ 0x1010, /* R21 - GPIO5 & GPIO6 */
+ 0x8000, /* R22 - GPIOCTRL 2 */
+ 0x0800, /* R23 - GPIO_POL */
+ 0x008B, /* R24 - Left Line Input 1&2 Volume */
+ 0x008B, /* R25 - Left Line Input 3&4 Volume */
+ 0x008B, /* R26 - Right Line Input 1&2 Volume */
+ 0x008B, /* R27 - Right Line Input 3&4 Volume */
+ 0x0000, /* R28 - Left Output Volume */
+ 0x0000, /* R29 - Right Output Volume */
+ 0x0066, /* R30 - Line Outputs Volume */
+ 0x0022, /* R31 - Out3/4 Volume */
+ 0x0079, /* R32 - Left OPGA Volume */
+ 0x0079, /* R33 - Right OPGA Volume */
+ 0x0003, /* R34 - Speaker Volume */
+ 0x0003, /* R35 - ClassD1 */
+ 0x0000, /* R36 */
+ 0x0100, /* R37 - ClassD3 */
+ 0x0000, /* R38 */
+ 0x0000, /* R39 - Input Mixer1 */
+ 0x0000, /* R40 - Input Mixer2 */
+ 0x0000, /* R41 - Input Mixer3 */
+ 0x0000, /* R42 - Input Mixer4 */
+ 0x0000, /* R43 - Input Mixer5 */
+ 0x0000, /* R44 - Input Mixer6 */
+ 0x0000, /* R45 - Output Mixer1 */
+ 0x0000, /* R46 - Output Mixer2 */
+ 0x0000, /* R47 - Output Mixer3 */
+ 0x0000, /* R48 - Output Mixer4 */
+ 0x0000, /* R49 - Output Mixer5 */
+ 0x0000, /* R50 - Output Mixer6 */
+ 0x0180, /* R51 - Out3/4 Mixer */
+ 0x0000, /* R52 - Line Mixer1 */
+ 0x0000, /* R53 - Line Mixer2 */
+ 0x0000, /* R54 - Speaker Mixer */
+ 0x0000, /* R55 - Additional Control */
+ 0x0000, /* R56 - AntiPOP1 */
+ 0x0000, /* R57 - AntiPOP2 */
+ 0x0000, /* R58 - MICBIAS */
+ 0x0000, /* R59 */
+ 0x0008, /* R60 - PLL1 */
+ 0x0031, /* R61 - PLL2 */
+ 0x0026, /* R62 - PLL3 */
+};
+
+#define wm8991_reset(c) snd_soc_write(c, WM8991_RESET, 0)
+
+static const unsigned int rec_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_LINEAR_ITEM(-1500, 600),
+};
+
+static const unsigned int in_pga_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 0x1F, TLV_DB_LINEAR_ITEM(-1650, 3000),
+};
+
+static const unsigned int out_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_LINEAR_ITEM(0, -2100),
+};
+
+static const unsigned int out_pga_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 127, TLV_DB_LINEAR_ITEM(-7300, 600),
+};
+
+static const unsigned int out_omix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_LINEAR_ITEM(-600, 0),
+};
+
+static const unsigned int out_dac_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 255, TLV_DB_LINEAR_ITEM(-7163, 0),
+};
+
+static const unsigned int in_adc_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 255, TLV_DB_LINEAR_ITEM(-7163, 1763),
+};
+
+static const unsigned int out_sidetone_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 31, TLV_DB_LINEAR_ITEM(-3600, 0),
+};
+
+static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int ret;
+ u16 val;
+
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* now hit the volume update bits (always bit 8) */
+ val = snd_soc_read(codec, reg);
+ return snd_soc_write(codec, reg, val | 0x0100);
+}
+
+static const char *wm8991_digital_sidetone[] =
+{"None", "Left ADC", "Right ADC", "Reserved"};
+
+static const struct soc_enum wm8991_left_digital_sidetone_enum =
+ SOC_ENUM_SINGLE(WM8991_DIGITAL_SIDE_TONE,
+ WM8991_ADC_TO_DACL_SHIFT,
+ WM8991_ADC_TO_DACL_MASK,
+ wm8991_digital_sidetone);
+
+static const struct soc_enum wm8991_right_digital_sidetone_enum =
+ SOC_ENUM_SINGLE(WM8991_DIGITAL_SIDE_TONE,
+ WM8991_ADC_TO_DACR_SHIFT,
+ WM8991_ADC_TO_DACR_MASK,
+ wm8991_digital_sidetone);
+
+static const char *wm8991_adcmode[] =
+{"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"};
+
+static const struct soc_enum wm8991_right_adcmode_enum =
+ SOC_ENUM_SINGLE(WM8991_ADC_CTRL,
+ WM8991_ADC_HPF_CUT_SHIFT,
+ WM8991_ADC_HPF_CUT_MASK,
+ wm8991_adcmode);
+
+static const struct snd_kcontrol_new wm8991_snd_controls[] = {
+ /* INMIXL */
+ SOC_SINGLE("LIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L12MNBST_BIT, 1, 0),
+ SOC_SINGLE("LIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L34MNBST_BIT, 1, 0),
+ /* INMIXR */
+ SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R12MNBST_BIT, 1, 0),
+ SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R34MNBST_BIT, 1, 0),
+
+ /* LOMIX */
+ SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER3,
+ WM8991_LLI3LOVOL_SHIFT, WM8991_LLI3LOVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER3,
+ WM8991_LR12LOVOL_SHIFT, WM8991_LR12LOVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER3,
+ WM8991_LL12LOVOL_SHIFT, WM8991_LL12LOVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER5,
+ WM8991_LRI3LOVOL_SHIFT, WM8991_LRI3LOVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER5,
+ WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER5,
+ WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv),
+
+ /* ROMIX */
+ SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER4,
+ WM8991_RRI3ROVOL_SHIFT, WM8991_RRI3ROVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER4,
+ WM8991_RL12ROVOL_SHIFT, WM8991_RL12ROVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER4,
+ WM8991_RR12ROVOL_SHIFT, WM8991_RR12ROVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER6,
+ WM8991_RLI3ROVOL_SHIFT, WM8991_RLI3ROVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER6,
+ WM8991_RLBROVOL_SHIFT, WM8991_RLBROVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER6,
+ WM8991_RRBROVOL_SHIFT, WM8991_RRBROVOL_MASK, 1, out_mix_tlv),
+
+ /* LOUT */
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8991_LEFT_OUTPUT_VOLUME,
+ WM8991_LOUTVOL_SHIFT, WM8991_LOUTVOL_MASK, 0, out_pga_tlv),
+ SOC_SINGLE("LOUT ZC", WM8991_LEFT_OUTPUT_VOLUME, WM8991_LOZC_BIT, 1, 0),
+
+ /* ROUT */
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8991_RIGHT_OUTPUT_VOLUME,
+ WM8991_ROUTVOL_SHIFT, WM8991_ROUTVOL_MASK, 0, out_pga_tlv),
+ SOC_SINGLE("ROUT ZC", WM8991_RIGHT_OUTPUT_VOLUME, WM8991_ROZC_BIT, 1, 0),
+
+ /* LOPGA */
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8991_LEFT_OPGA_VOLUME,
+ WM8991_LOPGAVOL_SHIFT, WM8991_LOPGAVOL_MASK, 0, out_pga_tlv),
+ SOC_SINGLE("LOPGA ZC Switch", WM8991_LEFT_OPGA_VOLUME,
+ WM8991_LOPGAZC_BIT, 1, 0),
+
+ /* ROPGA */
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8991_RIGHT_OPGA_VOLUME,
+ WM8991_ROPGAVOL_SHIFT, WM8991_ROPGAVOL_MASK, 0, out_pga_tlv),
+ SOC_SINGLE("ROPGA ZC Switch", WM8991_RIGHT_OPGA_VOLUME,
+ WM8991_ROPGAZC_BIT, 1, 0),
+
+ SOC_SINGLE("LON Mute Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_LONMUTE_BIT, 1, 0),
+ SOC_SINGLE("LOP Mute Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_LOPMUTE_BIT, 1, 0),
+ SOC_SINGLE("LOP Attenuation Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_LOATTN_BIT, 1, 0),
+ SOC_SINGLE("RON Mute Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_RONMUTE_BIT, 1, 0),
+ SOC_SINGLE("ROP Mute Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_ROPMUTE_BIT, 1, 0),
+ SOC_SINGLE("ROP Attenuation Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_ROATTN_BIT, 1, 0),
+
+ SOC_SINGLE("OUT3 Mute Switch", WM8991_OUT3_4_VOLUME,
+ WM8991_OUT3MUTE_BIT, 1, 0),
+ SOC_SINGLE("OUT3 Attenuation Switch", WM8991_OUT3_4_VOLUME,
+ WM8991_OUT3ATTN_BIT, 1, 0),
+
+ SOC_SINGLE("OUT4 Mute Switch", WM8991_OUT3_4_VOLUME,
+ WM8991_OUT4MUTE_BIT, 1, 0),
+ SOC_SINGLE("OUT4 Attenuation Switch", WM8991_OUT3_4_VOLUME,
+ WM8991_OUT4ATTN_BIT, 1, 0),
+
+ SOC_SINGLE("Speaker Mode Switch", WM8991_CLASSD1,
+ WM8991_CDMODE_BIT, 1, 0),
+
+ SOC_SINGLE("Speaker Output Attenuation Volume", WM8991_SPEAKER_VOLUME,
+ WM8991_SPKVOL_SHIFT, WM8991_SPKVOL_MASK, 0),
+ SOC_SINGLE("Speaker DC Boost Volume", WM8991_CLASSD3,
+ WM8991_DCGAIN_SHIFT, WM8991_DCGAIN_MASK, 0),
+ SOC_SINGLE("Speaker AC Boost Volume", WM8991_CLASSD3,
+ WM8991_ACGAIN_SHIFT, WM8991_ACGAIN_MASK, 0),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume",
+ WM8991_LEFT_DAC_DIGITAL_VOLUME,
+ WM8991_DACL_VOL_SHIFT,
+ WM8991_DACL_VOL_MASK,
+ 0,
+ out_dac_tlv),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume",
+ WM8991_RIGHT_DAC_DIGITAL_VOLUME,
+ WM8991_DACR_VOL_SHIFT,
+ WM8991_DACR_VOL_MASK,
+ 0,
+ out_dac_tlv),
+
+ SOC_ENUM("Left Digital Sidetone", wm8991_left_digital_sidetone_enum),
+ SOC_ENUM("Right Digital Sidetone", wm8991_right_digital_sidetone_enum),
+
+ SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8991_DIGITAL_SIDE_TONE,
+ WM8991_ADCL_DAC_SVOL_SHIFT, WM8991_ADCL_DAC_SVOL_MASK, 0,
+ out_sidetone_tlv),
+ SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8991_DIGITAL_SIDE_TONE,
+ WM8991_ADCR_DAC_SVOL_SHIFT, WM8991_ADCR_DAC_SVOL_MASK, 0,
+ out_sidetone_tlv),
+
+ SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8991_ADC_CTRL,
+ WM8991_ADC_HPF_ENA_BIT, 1, 0),
+
+ SOC_ENUM("ADC HPF Mode", wm8991_right_adcmode_enum),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume",
+ WM8991_LEFT_ADC_DIGITAL_VOLUME,
+ WM8991_ADCL_VOL_SHIFT,
+ WM8991_ADCL_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume",
+ WM8991_RIGHT_ADC_DIGITAL_VOLUME,
+ WM8991_ADCR_VOL_SHIFT,
+ WM8991_ADCR_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume",
+ WM8991_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8991_LIN12VOL_SHIFT,
+ WM8991_LIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+ SOC_SINGLE("LIN12 ZC Switch", WM8991_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8991_LI12ZC_BIT, 1, 0),
+
+ SOC_SINGLE("LIN12 Mute Switch", WM8991_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8991_LI12MUTE_BIT, 1, 0),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume",
+ WM8991_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8991_LIN34VOL_SHIFT,
+ WM8991_LIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+ SOC_SINGLE("LIN34 ZC Switch", WM8991_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8991_LI34ZC_BIT, 1, 0),
+
+ SOC_SINGLE("LIN34 Mute Switch", WM8991_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8991_LI34MUTE_BIT, 1, 0),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume",
+ WM8991_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8991_RIN12VOL_SHIFT,
+ WM8991_RIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+ SOC_SINGLE("RIN12 ZC Switch", WM8991_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8991_RI12ZC_BIT, 1, 0),
+
+ SOC_SINGLE("RIN12 Mute Switch", WM8991_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8991_RI12MUTE_BIT, 1, 0),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume",
+ WM8991_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8991_RIN34VOL_SHIFT,
+ WM8991_RIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+ SOC_SINGLE("RIN34 ZC Switch", WM8991_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8991_RI34ZC_BIT, 1, 0),
+
+ SOC_SINGLE("RIN34 Mute Switch", WM8991_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8991_RI34MUTE_BIT, 1, 0),
+};
+
+/*
+ * _DAPM_ Controls
+ */
+static int inmixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u16 reg, fakepower;
+
+ reg = snd_soc_read(w->codec, WM8991_POWER_MANAGEMENT_2);
+ fakepower = snd_soc_read(w->codec, WM8991_INTDRIVBITS);
+
+ if (fakepower & ((1 << WM8991_INMIXL_PWR_BIT) |
+ (1 << WM8991_AINLMUX_PWR_BIT)))
+ reg |= WM8991_AINL_ENA;
+ else
+ reg &= ~WM8991_AINL_ENA;
+
+ if (fakepower & ((1 << WM8991_INMIXR_PWR_BIT) |
+ (1 << WM8991_AINRMUX_PWR_BIT)))
+ reg |= WM8991_AINR_ENA;
+ else
+ reg &= ~WM8991_AINL_ENA;
+
+ snd_soc_write(w->codec, WM8991_POWER_MANAGEMENT_2, reg);
+ return 0;
+}
+
+static int outmixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u32 reg_shift = kcontrol->private_value & 0xfff;
+ int ret = 0;
+ u16 reg;
+
+ switch (reg_shift) {
+ case WM8991_SPEAKER_MIXER | (WM8991_LDSPK_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8991_OUTPUT_MIXER1);
+ if (reg & WM8991_LDLO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 1 LDLO Set\n");
+ ret = -1;
+ }
+ break;
+
+ case WM8991_SPEAKER_MIXER | (WM8991_RDSPK_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8991_OUTPUT_MIXER2);
+ if (reg & WM8991_RDRO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 2 RDRO Set\n");
+ ret = -1;
+ }
+ break;
+
+ case WM8991_OUTPUT_MIXER1 | (WM8991_LDLO_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8991_SPEAKER_MIXER);
+ if (reg & WM8991_LDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer LDSPK Set\n");
+ ret = -1;
+ }
+ break;
+
+ case WM8991_OUTPUT_MIXER2 | (WM8991_RDRO_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8991_SPEAKER_MIXER);
+ if (reg & WM8991_RDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer RDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/* INMIX dB values */
+static const unsigned int in_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_LINEAR_ITEM(-1200, 600),
+};
+
+/* Left In PGA Connections */
+static const struct snd_kcontrol_new wm8991_dapm_lin12_pga_controls[] = {
+ SOC_DAPM_SINGLE("LIN1 Switch", WM8991_INPUT_MIXER2, WM8991_LMN1_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LIN2 Switch", WM8991_INPUT_MIXER2, WM8991_LMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8991_dapm_lin34_pga_controls[] = {
+ SOC_DAPM_SINGLE("LIN3 Switch", WM8991_INPUT_MIXER2, WM8991_LMN3_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LIN4 Switch", WM8991_INPUT_MIXER2, WM8991_LMP4_BIT, 1, 0),
+};
+
+/* Right In PGA Connections */
+static const struct snd_kcontrol_new wm8991_dapm_rin12_pga_controls[] = {
+ SOC_DAPM_SINGLE("RIN1 Switch", WM8991_INPUT_MIXER2, WM8991_RMN1_BIT, 1, 0),
+ SOC_DAPM_SINGLE("RIN2 Switch", WM8991_INPUT_MIXER2, WM8991_RMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8991_dapm_rin34_pga_controls[] = {
+ SOC_DAPM_SINGLE("RIN3 Switch", WM8991_INPUT_MIXER2, WM8991_RMN3_BIT, 1, 0),
+ SOC_DAPM_SINGLE("RIN4 Switch", WM8991_INPUT_MIXER2, WM8991_RMP4_BIT, 1, 0),
+};
+
+/* INMIXL */
+static const struct snd_kcontrol_new wm8991_dapm_inmixl_controls[] = {
+ SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8991_INPUT_MIXER3,
+ WM8991_LDBVOL_SHIFT, WM8991_LDBVOL_MASK, 0, in_mix_tlv),
+ SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8991_INPUT_MIXER5, WM8991_LI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+ SOC_DAPM_SINGLE("LINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT,
+ 1, 0),
+ SOC_DAPM_SINGLE("LINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT,
+ 1, 0),
+};
+
+/* INMIXR */
+static const struct snd_kcontrol_new wm8991_dapm_inmixr_controls[] = {
+ SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8991_INPUT_MIXER4,
+ WM8991_RDBVOL_SHIFT, WM8991_RDBVOL_MASK, 0, in_mix_tlv),
+ SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8991_INPUT_MIXER6, WM8991_RI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+ SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT,
+ 1, 0),
+ SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT,
+ 1, 0),
+};
+
+/* AINLMUX */
+static const char *wm8991_ainlmux[] =
+{"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"};
+
+static const struct soc_enum wm8991_ainlmux_enum =
+ SOC_ENUM_SINGLE(WM8991_INPUT_MIXER1, WM8991_AINLMODE_SHIFT,
+ ARRAY_SIZE(wm8991_ainlmux), wm8991_ainlmux);
+
+static const struct snd_kcontrol_new wm8991_dapm_ainlmux_controls =
+ SOC_DAPM_ENUM("Route", wm8991_ainlmux_enum);
+
+/* DIFFINL */
+
+/* AINRMUX */
+static const char *wm8991_ainrmux[] =
+{"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"};
+
+static const struct soc_enum wm8991_ainrmux_enum =
+ SOC_ENUM_SINGLE(WM8991_INPUT_MIXER1, WM8991_AINRMODE_SHIFT,
+ ARRAY_SIZE(wm8991_ainrmux), wm8991_ainrmux);
+
+static const struct snd_kcontrol_new wm8991_dapm_ainrmux_controls =
+ SOC_DAPM_ENUM("Route", wm8991_ainrmux_enum);
+
+/* RXVOICE */
+static const struct snd_kcontrol_new wm8991_dapm_rxvoice_controls[] = {
+ SOC_DAPM_SINGLE_TLV("LIN4RXN", WM8991_INPUT_MIXER5, WM8991_LR4BVOL_SHIFT,
+ WM8991_LR4BVOL_MASK, 0, in_mix_tlv),
+ SOC_DAPM_SINGLE_TLV("RIN4RXP", WM8991_INPUT_MIXER6, WM8991_RL4BVOL_SHIFT,
+ WM8991_RL4BVOL_MASK, 0, in_mix_tlv),
+};
+
+/* LOMIX */
+static const struct snd_kcontrol_new wm8991_dapm_lomix_controls[] = {
+ SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LRBLO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LLBLO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LRI3LO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LLI3LO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LR12LO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LL12LO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LDLO_BIT, 1, 0),
+};
+
+/* ROMIX */
+static const struct snd_kcontrol_new wm8991_dapm_romix_controls[] = {
+ SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RLBRO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RRBRO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RLI3RO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RRI3RO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RL12RO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RR12RO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RDRO_BIT, 1, 0),
+};
+
+/* LONMIX */
+static const struct snd_kcontrol_new wm8991_dapm_lonmix_controls[] = {
+ SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8991_LINE_MIXER1,
+ WM8991_LLOPGALON_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8991_LINE_MIXER1,
+ WM8991_LROPGALON_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8991_LINE_MIXER1,
+ WM8991_LOPLON_BIT, 1, 0),
+};
+
+/* LOPMIX */
+static const struct snd_kcontrol_new wm8991_dapm_lopmix_controls[] = {
+ SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8991_LINE_MIXER1,
+ WM8991_LR12LOP_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8991_LINE_MIXER1,
+ WM8991_LL12LOP_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8991_LINE_MIXER1,
+ WM8991_LLOPGALOP_BIT, 1, 0),
+};
+
+/* RONMIX */
+static const struct snd_kcontrol_new wm8991_dapm_ronmix_controls[] = {
+ SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8991_LINE_MIXER2,
+ WM8991_RROPGARON_BIT, 1, 0),
+ SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8991_LINE_MIXER2,
+ WM8991_RLOPGARON_BIT, 1, 0),
+ SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8991_LINE_MIXER2,
+ WM8991_ROPRON_BIT, 1, 0),
+};
+
+/* ROPMIX */
+static const struct snd_kcontrol_new wm8991_dapm_ropmix_controls[] = {
+ SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8991_LINE_MIXER2,
+ WM8991_RL12ROP_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8991_LINE_MIXER2,
+ WM8991_RR12ROP_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8991_LINE_MIXER2,
+ WM8991_RROPGAROP_BIT, 1, 0),
+};
+
+/* OUT3MIX */
+static const struct snd_kcontrol_new wm8991_dapm_out3mix_controls[] = {
+ SOC_DAPM_SINGLE("OUT3MIX LIN4RXN Bypass Switch", WM8991_OUT3_4_MIXER,
+ WM8991_LI4O3_BIT, 1, 0),
+ SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8991_OUT3_4_MIXER,
+ WM8991_LPGAO3_BIT, 1, 0),
+};
+
+/* OUT4MIX */
+static const struct snd_kcontrol_new wm8991_dapm_out4mix_controls[] = {
+ SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8991_OUT3_4_MIXER,
+ WM8991_RPGAO4_BIT, 1, 0),
+ SOC_DAPM_SINGLE("OUT4MIX RIN4RXP Bypass Switch", WM8991_OUT3_4_MIXER,
+ WM8991_RI4O4_BIT, 1, 0),
+};
+
+/* SPKMIX */
+static const struct snd_kcontrol_new wm8991_dapm_spkmix_controls[] = {
+ SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8991_SPEAKER_MIXER,
+ WM8991_LI2SPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8991_SPEAKER_MIXER,
+ WM8991_LB2SPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8991_SPEAKER_MIXER,
+ WM8991_LOPGASPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8991_SPEAKER_MIXER,
+ WM8991_LDSPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8991_SPEAKER_MIXER,
+ WM8991_RDSPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8991_SPEAKER_MIXER,
+ WM8991_ROPGASPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8991_SPEAKER_MIXER,
+ WM8991_RL12ROP_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8991_SPEAKER_MIXER,
+ WM8991_RI2SPK_BIT, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8991_dapm_widgets[] = {
+ /* Input Side */
+ /* Input Lines */
+ SND_SOC_DAPM_INPUT("LIN1"),
+ SND_SOC_DAPM_INPUT("LIN2"),
+ SND_SOC_DAPM_INPUT("LIN3"),
+ SND_SOC_DAPM_INPUT("LIN4RXN"),
+ SND_SOC_DAPM_INPUT("RIN3"),
+ SND_SOC_DAPM_INPUT("RIN4RXP"),
+ SND_SOC_DAPM_INPUT("RIN1"),
+ SND_SOC_DAPM_INPUT("RIN2"),
+ SND_SOC_DAPM_INPUT("Internal ADC Source"),
+
+ /* DACs */
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8991_POWER_MANAGEMENT_2,
+ WM8991_ADCL_ENA_BIT, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8991_POWER_MANAGEMENT_2,
+ WM8991_ADCR_ENA_BIT, 0),
+
+ /* Input PGAs */
+ SND_SOC_DAPM_MIXER("LIN12 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_LIN12_ENA_BIT,
+ 0, &wm8991_dapm_lin12_pga_controls[0],
+ ARRAY_SIZE(wm8991_dapm_lin12_pga_controls)),
+ SND_SOC_DAPM_MIXER("LIN34 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_LIN34_ENA_BIT,
+ 0, &wm8991_dapm_lin34_pga_controls[0],
+ ARRAY_SIZE(wm8991_dapm_lin34_pga_controls)),
+ SND_SOC_DAPM_MIXER("RIN12 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_RIN12_ENA_BIT,
+ 0, &wm8991_dapm_rin12_pga_controls[0],
+ ARRAY_SIZE(wm8991_dapm_rin12_pga_controls)),
+ SND_SOC_DAPM_MIXER("RIN34 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_RIN34_ENA_BIT,
+ 0, &wm8991_dapm_rin34_pga_controls[0],
+ ARRAY_SIZE(wm8991_dapm_rin34_pga_controls)),
+
+ /* INMIXL */
+ SND_SOC_DAPM_MIXER_E("INMIXL", WM8991_INTDRIVBITS, WM8991_INMIXL_PWR_BIT, 0,
+ &wm8991_dapm_inmixl_controls[0],
+ ARRAY_SIZE(wm8991_dapm_inmixl_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* AINLMUX */
+ SND_SOC_DAPM_MUX_E("AINLMUX", WM8991_INTDRIVBITS, WM8991_AINLMUX_PWR_BIT, 0,
+ &wm8991_dapm_ainlmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* INMIXR */
+ SND_SOC_DAPM_MIXER_E("INMIXR", WM8991_INTDRIVBITS, WM8991_INMIXR_PWR_BIT, 0,
+ &wm8991_dapm_inmixr_controls[0],
+ ARRAY_SIZE(wm8991_dapm_inmixr_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* AINRMUX */
+ SND_SOC_DAPM_MUX_E("AINRMUX", WM8991_INTDRIVBITS, WM8991_AINRMUX_PWR_BIT, 0,
+ &wm8991_dapm_ainrmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* Output Side */
+ /* DACs */
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8991_POWER_MANAGEMENT_3,
+ WM8991_DACL_ENA_BIT, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8991_POWER_MANAGEMENT_3,
+ WM8991_DACR_ENA_BIT, 0),
+
+ /* LOMIX */
+ SND_SOC_DAPM_MIXER_E("LOMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LOMIX_ENA_BIT,
+ 0, &wm8991_dapm_lomix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_lomix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+ /* LONMIX */
+ SND_SOC_DAPM_MIXER("LONMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LON_ENA_BIT, 0,
+ &wm8991_dapm_lonmix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_lonmix_controls)),
+
+ /* LOPMIX */
+ SND_SOC_DAPM_MIXER("LOPMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LOP_ENA_BIT, 0,
+ &wm8991_dapm_lopmix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_lopmix_controls)),
+
+ /* OUT3MIX */
+ SND_SOC_DAPM_MIXER("OUT3MIX", WM8991_POWER_MANAGEMENT_1, WM8991_OUT3_ENA_BIT, 0,
+ &wm8991_dapm_out3mix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_out3mix_controls)),
+
+ /* SPKMIX */
+ SND_SOC_DAPM_MIXER_E("SPKMIX", WM8991_POWER_MANAGEMENT_1, WM8991_SPK_ENA_BIT, 0,
+ &wm8991_dapm_spkmix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_spkmix_controls), outmixer_event,
+ SND_SOC_DAPM_PRE_REG),
+
+ /* OUT4MIX */
+ SND_SOC_DAPM_MIXER("OUT4MIX", WM8991_POWER_MANAGEMENT_1, WM8991_OUT4_ENA_BIT, 0,
+ &wm8991_dapm_out4mix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_out4mix_controls)),
+
+ /* ROPMIX */
+ SND_SOC_DAPM_MIXER("ROPMIX", WM8991_POWER_MANAGEMENT_3, WM8991_ROP_ENA_BIT, 0,
+ &wm8991_dapm_ropmix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_ropmix_controls)),
+
+ /* RONMIX */
+ SND_SOC_DAPM_MIXER("RONMIX", WM8991_POWER_MANAGEMENT_3, WM8991_RON_ENA_BIT, 0,
+ &wm8991_dapm_ronmix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_ronmix_controls)),
+
+ /* ROMIX */
+ SND_SOC_DAPM_MIXER_E("ROMIX", WM8991_POWER_MANAGEMENT_3, WM8991_ROMIX_ENA_BIT,
+ 0, &wm8991_dapm_romix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_romix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+ /* LOUT PGA */
+ SND_SOC_DAPM_PGA("LOUT PGA", WM8991_POWER_MANAGEMENT_1, WM8991_LOUT_ENA_BIT, 0,
+ NULL, 0),
+
+ /* ROUT PGA */
+ SND_SOC_DAPM_PGA("ROUT PGA", WM8991_POWER_MANAGEMENT_1, WM8991_ROUT_ENA_BIT, 0,
+ NULL, 0),
+
+ /* LOPGA */
+ SND_SOC_DAPM_PGA("LOPGA", WM8991_POWER_MANAGEMENT_3, WM8991_LOPGA_ENA_BIT, 0,
+ NULL, 0),
+
+ /* ROPGA */
+ SND_SOC_DAPM_PGA("ROPGA", WM8991_POWER_MANAGEMENT_3, WM8991_ROPGA_ENA_BIT, 0,
+ NULL, 0),
+
+ /* MICBIAS */
+ SND_SOC_DAPM_MICBIAS("MICBIAS", WM8991_POWER_MANAGEMENT_1,
+ WM8991_MICBIAS_ENA_BIT, 0),
+
+ SND_SOC_DAPM_OUTPUT("LON"),
+ SND_SOC_DAPM_OUTPUT("LOP"),
+ SND_SOC_DAPM_OUTPUT("OUT3"),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("SPKN"),
+ SND_SOC_DAPM_OUTPUT("SPKP"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+ SND_SOC_DAPM_OUTPUT("OUT4"),
+ SND_SOC_DAPM_OUTPUT("ROP"),
+ SND_SOC_DAPM_OUTPUT("RON"),
+ SND_SOC_DAPM_OUTPUT("OUT"),
+
+ SND_SOC_DAPM_OUTPUT("Internal DAC Sink"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Make DACs turn on when playing even if not mixed into any outputs */
+ {"Internal DAC Sink", NULL, "Left DAC"},
+ {"Internal DAC Sink", NULL, "Right DAC"},
+
+ /* Make ADCs turn on when recording even if not mixed from any inputs */
+ {"Left ADC", NULL, "Internal ADC Source"},
+ {"Right ADC", NULL, "Internal ADC Source"},
+
+ /* Input Side */
+ /* LIN12 PGA */
+ {"LIN12 PGA", "LIN1 Switch", "LIN1"},
+ {"LIN12 PGA", "LIN2 Switch", "LIN2"},
+ /* LIN34 PGA */
+ {"LIN34 PGA", "LIN3 Switch", "LIN3"},
+ {"LIN34 PGA", "LIN4 Switch", "LIN4RXN"},
+ /* INMIXL */
+ {"INMIXL", "Record Left Volume", "LOMIX"},
+ {"INMIXL", "LIN2 Volume", "LIN2"},
+ {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"},
+ {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"},
+ /* AINLMUX */
+ {"AINLMUX", "INMIXL Mix", "INMIXL"},
+ {"AINLMUX", "DIFFINL Mix", "LIN12 PGA"},
+ {"AINLMUX", "DIFFINL Mix", "LIN34 PGA"},
+ {"AINLMUX", "RXVOICE Mix", "LIN4RXN"},
+ {"AINLMUX", "RXVOICE Mix", "RIN4RXP"},
+ /* ADC */
+ {"Left ADC", NULL, "AINLMUX"},
+
+ /* RIN12 PGA */
+ {"RIN12 PGA", "RIN1 Switch", "RIN1"},
+ {"RIN12 PGA", "RIN2 Switch", "RIN2"},
+ /* RIN34 PGA */
+ {"RIN34 PGA", "RIN3 Switch", "RIN3"},
+ {"RIN34 PGA", "RIN4 Switch", "RIN4RXP"},
+ /* INMIXL */
+ {"INMIXR", "Record Right Volume", "ROMIX"},
+ {"INMIXR", "RIN2 Volume", "RIN2"},
+ {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"},
+ {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"},
+ /* AINRMUX */
+ {"AINRMUX", "INMIXR Mix", "INMIXR"},
+ {"AINRMUX", "DIFFINR Mix", "RIN12 PGA"},
+ {"AINRMUX", "DIFFINR Mix", "RIN34 PGA"},
+ {"AINRMUX", "RXVOICE Mix", "LIN4RXN"},
+ {"AINRMUX", "RXVOICE Mix", "RIN4RXP"},
+ /* ADC */
+ {"Right ADC", NULL, "AINRMUX"},
+
+ /* LOMIX */
+ {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"},
+ {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"},
+ {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"},
+ {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"},
+ {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"},
+
+ /* ROMIX */
+ {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"},
+ {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"},
+ {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"},
+ {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"},
+ {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"},
+
+ /* SPKMIX */
+ {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"},
+ {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"},
+ {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"},
+ {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"},
+ {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"},
+ {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"},
+ {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"},
+ {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"},
+
+ /* LONMIX */
+ {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"},
+
+ /* LOPMIX */
+ {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"},
+
+ /* OUT3MIX */
+ {"OUT3MIX", "OUT3MIX LIN4RXN Bypass Switch", "LIN4RXN"},
+ {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"},
+
+ /* OUT4MIX */
+ {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"},
+ {"OUT4MIX", "OUT4MIX RIN4RXP Bypass Switch", "RIN4RXP"},
+
+ /* RONMIX */
+ {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"},
+
+ /* ROPMIX */
+ {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"},
+
+ /* Out Mixer PGAs */
+ {"LOPGA", NULL, "LOMIX"},
+ {"ROPGA", NULL, "ROMIX"},
+
+ {"LOUT PGA", NULL, "LOMIX"},
+ {"ROUT PGA", NULL, "ROMIX"},
+
+ /* Output Pins */
+ {"LON", NULL, "LONMIX"},
+ {"LOP", NULL, "LOPMIX"},
+ {"OUT", NULL, "OUT3MIX"},
+ {"LOUT", NULL, "LOUT PGA"},
+ {"SPKN", NULL, "SPKMIX"},
+ {"ROUT", NULL, "ROUT PGA"},
+ {"OUT4", NULL, "OUT4MIX"},
+ {"ROP", NULL, "ROPMIX"},
+ {"RON", NULL, "RONMIX"},
+};
+
+/* PLL divisors */
+struct _pll_div {
+ u32 div2;
+ u32 n;
+ u32 k;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 16) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->div2 = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->div2 = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM8991 N value outwith recommended range! N = %d\n", Ndiv);
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+}
+
+static int wm8991_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, int src, unsigned int freq_in, unsigned int freq_out)
+{
+ u16 reg;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct _pll_div pll_div;
+
+ if (freq_in && freq_out) {
+ pll_factors(&pll_div, freq_out * 4, freq_in);
+
+ /* Turn on PLL */
+ reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2);
+ reg |= WM8991_PLL_ENA;
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg);
+
+ /* sysclk comes from PLL */
+ reg = snd_soc_read(codec, WM8991_CLOCKING_2);
+ snd_soc_write(codec, WM8991_CLOCKING_2, reg | WM8991_SYSCLK_SRC);
+
+ /* set up N , fractional mode and pre-divisor if necessary */
+ snd_soc_write(codec, WM8991_PLL1, pll_div.n | WM8991_SDM |
+ (pll_div.div2 ? WM8991_PRESCALE : 0));
+ snd_soc_write(codec, WM8991_PLL2, (u8)(pll_div.k>>8));
+ snd_soc_write(codec, WM8991_PLL3, (u8)(pll_div.k & 0xFF));
+ } else {
+ /* Turn on PLL */
+ reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2);
+ reg &= ~WM8991_PLL_ENA;
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg);
+ }
+ return 0;
+}
+
+/*
+ * Set's ADC and Voice DAC format.
+ */
+static int wm8991_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 audio1, audio3;
+
+ audio1 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_1);
+ audio3 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_3);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ audio3 &= ~WM8991_AIF_MSTR1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ audio3 |= WM8991_AIF_MSTR1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ audio1 &= ~WM8991_AIF_FMT_MASK;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ audio1 |= WM8991_AIF_TMF_I2S;
+ audio1 &= ~WM8991_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ audio1 |= WM8991_AIF_TMF_RIGHTJ;
+ audio1 &= ~WM8991_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ audio1 |= WM8991_AIF_TMF_LEFTJ;
+ audio1 &= ~WM8991_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ audio1 |= WM8991_AIF_TMF_DSP;
+ audio1 &= ~WM8991_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ audio1 |= WM8991_AIF_TMF_DSP | WM8991_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8991_AUDIO_INTERFACE_1, audio1);
+ snd_soc_write(codec, WM8991_AUDIO_INTERFACE_3, audio3);
+ return 0;
+}
+
+static int wm8991_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8991_MCLK_DIV:
+ reg = snd_soc_read(codec, WM8991_CLOCKING_2) &
+ ~WM8991_MCLK_DIV_MASK;
+ snd_soc_write(codec, WM8991_CLOCKING_2, reg | div);
+ break;
+ case WM8991_DACCLK_DIV:
+ reg = snd_soc_read(codec, WM8991_CLOCKING_2) &
+ ~WM8991_DAC_CLKDIV_MASK;
+ snd_soc_write(codec, WM8991_CLOCKING_2, reg | div);
+ break;
+ case WM8991_ADCCLK_DIV:
+ reg = snd_soc_read(codec, WM8991_CLOCKING_2) &
+ ~WM8991_ADC_CLKDIV_MASK;
+ snd_soc_write(codec, WM8991_CLOCKING_2, reg | div);
+ break;
+ case WM8991_BCLK_DIV:
+ reg = snd_soc_read(codec, WM8991_CLOCKING_1) &
+ ~WM8991_BCLK_DIV_MASK;
+ snd_soc_write(codec, WM8991_CLOCKING_1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8991_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 audio1 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_1);
+
+ audio1 &= ~WM8991_AIF_WL_MASK;
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ audio1 |= WM8991_AIF_WL_20BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ audio1 |= WM8991_AIF_WL_24BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ audio1 |= WM8991_AIF_WL_32BITS;
+ break;
+ }
+
+ snd_soc_write(codec, WM8991_AUDIO_INTERFACE_1, audio1);
+ return 0;
+}
+
+static int wm8991_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 val;
+
+ val = snd_soc_read(codec, WM8991_DAC_CTRL) & ~WM8991_DAC_MUTE;
+ if (mute)
+ snd_soc_write(codec, WM8991_DAC_CTRL, val | WM8991_DAC_MUTE);
+ else
+ snd_soc_write(codec, WM8991_DAC_CTRL, val);
+ return 0;
+}
+
+static int wm8991_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 val;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID=2*50k */
+ val = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1) &
+ ~WM8991_VMID_MODE_MASK;
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, val | 0x2);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ snd_soc_cache_sync(codec);
+ /* Enable all output discharge bits */
+ snd_soc_write(codec, WM8991_ANTIPOP1, WM8991_DIS_LLINE |
+ WM8991_DIS_RLINE | WM8991_DIS_OUT3 |
+ WM8991_DIS_OUT4 | WM8991_DIS_LOUT |
+ WM8991_DIS_ROUT);
+
+ /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST |
+ WM8991_BUFDCOPEN | WM8991_POBCTRL |
+ WM8991_VMIDTOG);
+
+ /* Delay to allow output caps to discharge */
+ msleep(300);
+
+ /* Disable VMIDTOG */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST |
+ WM8991_BUFDCOPEN | WM8991_POBCTRL);
+
+ /* disable all output discharge bits */
+ snd_soc_write(codec, WM8991_ANTIPOP1, 0);
+
+ /* Enable outputs */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1b00);
+
+ msleep(50);
+
+ /* Enable VMID at 2x50k */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f02);
+
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f03);
+
+ msleep(600);
+
+ /* Enable BUFIOEN */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST |
+ WM8991_BUFDCOPEN | WM8991_POBCTRL |
+ WM8991_BUFIOEN);
+
+ /* Disable outputs */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x3);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_BUFIOEN);
+ }
+
+ /* VMID=2*250k */
+ val = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1) &
+ ~WM8991_VMID_MODE_MASK;
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, val | 0x4);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable POBCTRL and SOFT_ST */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST |
+ WM8991_POBCTRL | WM8991_BUFIOEN);
+
+ /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST |
+ WM8991_BUFDCOPEN | WM8991_POBCTRL |
+ WM8991_BUFIOEN);
+
+ /* mute DAC */
+ val = snd_soc_read(codec, WM8991_DAC_CTRL);
+ snd_soc_write(codec, WM8991_DAC_CTRL, val | WM8991_DAC_MUTE);
+
+ /* Enable any disabled outputs */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f03);
+
+ /* Disable VMID */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f01);
+
+ msleep(300);
+
+ /* Enable all output discharge bits */
+ snd_soc_write(codec, WM8991_ANTIPOP1, WM8991_DIS_LLINE |
+ WM8991_DIS_RLINE | WM8991_DIS_OUT3 |
+ WM8991_DIS_OUT4 | WM8991_DIS_LOUT |
+ WM8991_DIS_ROUT);
+
+ /* Disable VREF */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x0);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ snd_soc_write(codec, WM8991_ANTIPOP2, 0x0);
+ codec->cache_sync = 1;
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int wm8991_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8991_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8991_resume(struct snd_soc_codec *codec)
+{
+ wm8991_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+/* power down chip */
+static int wm8991_remove(struct snd_soc_codec *codec)
+{
+ wm8991_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8991_probe(struct snd_soc_codec *codec)
+{
+ struct wm8991_priv *wm8991;
+ int ret;
+ unsigned int reg;
+
+ wm8991 = snd_soc_codec_get_drvdata(codec);
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm8991->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache i/o: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8991_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ wm8991_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ reg = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_4);
+ snd_soc_write(codec, WM8991_AUDIO_INTERFACE_4, reg | WM8991_ALRCGPIO1);
+
+ reg = snd_soc_read(codec, WM8991_GPIO1_GPIO2) &
+ ~WM8991_GPIO1_SEL_MASK;
+ snd_soc_write(codec, WM8991_GPIO1_GPIO2, reg | 1);
+
+ reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1);
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, reg | WM8991_VREF_ENA|
+ WM8991_VMID_MODE_MASK);
+
+ reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2);
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg | WM8991_OPCLK_ENA);
+
+ snd_soc_write(codec, WM8991_DAC_CTRL, 0);
+ snd_soc_write(codec, WM8991_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8));
+ snd_soc_write(codec, WM8991_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8));
+
+ snd_soc_add_controls(codec, wm8991_snd_controls,
+ ARRAY_SIZE(wm8991_snd_controls));
+
+ snd_soc_dapm_new_controls(&codec->dapm, wm8991_dapm_widgets,
+ ARRAY_SIZE(wm8991_dapm_widgets));
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map,
+ ARRAY_SIZE(audio_map));
+ return 0;
+}
+
+#define WM8991_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8991_ops = {
+ .hw_params = wm8991_hw_params,
+ .digital_mute = wm8991_mute,
+ .set_fmt = wm8991_set_dai_fmt,
+ .set_clkdiv = wm8991_set_dai_clkdiv,
+ .set_pll = wm8991_set_dai_pll
+};
+
+/*
+ * The WM8991 supports 2 different and mutually exclusive DAI
+ * configurations.
+ *
+ * 1. ADC/DAC on Primary Interface
+ * 2. ADC on Primary Interface/DAC on secondary
+ */
+static struct snd_soc_dai_driver wm8991_dai = {
+ /* ADC/DAC on primary */
+ .name = "wm8991",
+ .id = 1,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = WM8991_FORMATS
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = WM8991_FORMATS
+ },
+ .ops = &wm8991_ops
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8991 = {
+ .probe = wm8991_probe,
+ .remove = wm8991_remove,
+ .suspend = wm8991_suspend,
+ .resume = wm8991_resume,
+ .set_bias_level = wm8991_set_bias_level,
+ .reg_cache_size = WM8991_MAX_REGISTER + 1,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8991_reg_defs
+};
+
+static __devinit int wm8991_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8991_priv *wm8991;
+ int ret;
+
+ wm8991 = kzalloc(sizeof *wm8991, GFP_KERNEL);
+ if (!wm8991)
+ return -ENOMEM;
+
+ wm8991->control_type = SND_SOC_I2C;
+ i2c_set_clientdata(i2c, wm8991);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8991, &wm8991_dai, 1);
+ if (ret < 0)
+ kfree(wm8991);
+ return ret;
+}
+
+static __devexit int wm8991_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8991_i2c_id[] = {
+ { "wm8991", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8991_i2c_id);
+
+static struct i2c_driver wm8991_i2c_driver = {
+ .driver = {
+ .name = "wm8991",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8991_i2c_probe,
+ .remove = __devexit_p(wm8991_i2c_remove),
+ .id_table = wm8991_i2c_id,
+};
+
+static int __init wm8991_modinit(void)
+{
+ int ret;
+ ret = i2c_add_driver(&wm8991_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8991 I2C driver: %d\n",
+ ret);
+ }
+ return 0;
+}
+module_init(wm8991_modinit);
+
+static void __exit wm8991_exit(void)
+{
+ i2c_del_driver(&wm8991_i2c_driver);
+}
+module_exit(wm8991_exit);
+
+MODULE_DESCRIPTION("ASoC WM8991 driver");
+MODULE_AUTHOR("Graeme Gregory");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8991.h b/sound/soc/codecs/wm8991.h
new file mode 100644
index 00000000..8a942efd
--- /dev/null
+++ b/sound/soc/codecs/wm8991.h
@@ -0,0 +1,833 @@
+/*
+ * wm8991.h -- audio driver for WM8991
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _WM8991_H
+#define _WM8991_H
+
+/*
+ * Register values.
+ */
+#define WM8991_RESET 0x00
+#define WM8991_POWER_MANAGEMENT_1 0x01
+#define WM8991_POWER_MANAGEMENT_2 0x02
+#define WM8991_POWER_MANAGEMENT_3 0x03
+#define WM8991_AUDIO_INTERFACE_1 0x04
+#define WM8991_AUDIO_INTERFACE_2 0x05
+#define WM8991_CLOCKING_1 0x06
+#define WM8991_CLOCKING_2 0x07
+#define WM8991_AUDIO_INTERFACE_3 0x08
+#define WM8991_AUDIO_INTERFACE_4 0x09
+#define WM8991_DAC_CTRL 0x0A
+#define WM8991_LEFT_DAC_DIGITAL_VOLUME 0x0B
+#define WM8991_RIGHT_DAC_DIGITAL_VOLUME 0x0C
+#define WM8991_DIGITAL_SIDE_TONE 0x0D
+#define WM8991_ADC_CTRL 0x0E
+#define WM8991_LEFT_ADC_DIGITAL_VOLUME 0x0F
+#define WM8991_RIGHT_ADC_DIGITAL_VOLUME 0x10
+#define WM8991_GPIO_CTRL_1 0x12
+#define WM8991_GPIO1_GPIO2 0x13
+#define WM8991_GPIO3_GPIO4 0x14
+#define WM8991_GPIO5_GPIO6 0x15
+#define WM8991_GPIOCTRL_2 0x16
+#define WM8991_GPIO_POL 0x17
+#define WM8991_LEFT_LINE_INPUT_1_2_VOLUME 0x18
+#define WM8991_LEFT_LINE_INPUT_3_4_VOLUME 0x19
+#define WM8991_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A
+#define WM8991_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B
+#define WM8991_LEFT_OUTPUT_VOLUME 0x1C
+#define WM8991_RIGHT_OUTPUT_VOLUME 0x1D
+#define WM8991_LINE_OUTPUTS_VOLUME 0x1E
+#define WM8991_OUT3_4_VOLUME 0x1F
+#define WM8991_LEFT_OPGA_VOLUME 0x20
+#define WM8991_RIGHT_OPGA_VOLUME 0x21
+#define WM8991_SPEAKER_VOLUME 0x22
+#define WM8991_CLASSD1 0x23
+#define WM8991_CLASSD3 0x25
+#define WM8991_INPUT_MIXER1 0x27
+#define WM8991_INPUT_MIXER2 0x28
+#define WM8991_INPUT_MIXER3 0x29
+#define WM8991_INPUT_MIXER4 0x2A
+#define WM8991_INPUT_MIXER5 0x2B
+#define WM8991_INPUT_MIXER6 0x2C
+#define WM8991_OUTPUT_MIXER1 0x2D
+#define WM8991_OUTPUT_MIXER2 0x2E
+#define WM8991_OUTPUT_MIXER3 0x2F
+#define WM8991_OUTPUT_MIXER4 0x30
+#define WM8991_OUTPUT_MIXER5 0x31
+#define WM8991_OUTPUT_MIXER6 0x32
+#define WM8991_OUT3_4_MIXER 0x33
+#define WM8991_LINE_MIXER1 0x34
+#define WM8991_LINE_MIXER2 0x35
+#define WM8991_SPEAKER_MIXER 0x36
+#define WM8991_ADDITIONAL_CONTROL 0x37
+#define WM8991_ANTIPOP1 0x38
+#define WM8991_ANTIPOP2 0x39
+#define WM8991_MICBIAS 0x3A
+#define WM8991_PLL1 0x3C
+#define WM8991_PLL2 0x3D
+#define WM8991_PLL3 0x3E
+#define WM8991_INTDRIVBITS 0x3F
+
+#define WM8991_REGISTER_COUNT 60
+#define WM8991_MAX_REGISTER 0x3F
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Reset
+ */
+#define WM8991_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID - [15:0] */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM8991_SPK_ENA 0x1000 /* SPK_ENA */
+#define WM8991_SPK_ENA_BIT 12
+#define WM8991_OUT3_ENA 0x0800 /* OUT3_ENA */
+#define WM8991_OUT3_ENA_BIT 11
+#define WM8991_OUT4_ENA 0x0400 /* OUT4_ENA */
+#define WM8991_OUT4_ENA_BIT 10
+#define WM8991_LOUT_ENA 0x0200 /* LOUT_ENA */
+#define WM8991_LOUT_ENA_BIT 9
+#define WM8991_ROUT_ENA 0x0100 /* ROUT_ENA */
+#define WM8991_ROUT_ENA_BIT 8
+#define WM8991_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */
+#define WM8991_MICBIAS_ENA_BIT 4
+#define WM8991_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */
+#define WM8991_VREF_ENA 0x0001 /* VREF_ENA */
+#define WM8991_VREF_ENA_BIT 0
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM8991_PLL_ENA 0x8000 /* PLL_ENA */
+#define WM8991_PLL_ENA_BIT 15
+#define WM8991_TSHUT_ENA 0x4000 /* TSHUT_ENA */
+#define WM8991_TSHUT_ENA_BIT 14
+#define WM8991_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */
+#define WM8991_TSHUT_OPDIS_BIT 13
+#define WM8991_OPCLK_ENA 0x0800 /* OPCLK_ENA */
+#define WM8991_OPCLK_ENA_BIT 11
+#define WM8991_AINL_ENA 0x0200 /* AINL_ENA */
+#define WM8991_AINL_ENA_BIT 9
+#define WM8991_AINR_ENA 0x0100 /* AINR_ENA */
+#define WM8991_AINR_ENA_BIT 8
+#define WM8991_LIN34_ENA 0x0080 /* LIN34_ENA */
+#define WM8991_LIN34_ENA_BIT 7
+#define WM8991_LIN12_ENA 0x0040 /* LIN12_ENA */
+#define WM8991_LIN12_ENA_BIT 6
+#define WM8991_RIN34_ENA 0x0020 /* RIN34_ENA */
+#define WM8991_RIN34_ENA_BIT 5
+#define WM8991_RIN12_ENA 0x0010 /* RIN12_ENA */
+#define WM8991_RIN12_ENA_BIT 4
+#define WM8991_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8991_ADCL_ENA_BIT 1
+#define WM8991_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8991_ADCR_ENA_BIT 0
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM8991_LON_ENA 0x2000 /* LON_ENA */
+#define WM8991_LON_ENA_BIT 13
+#define WM8991_LOP_ENA 0x1000 /* LOP_ENA */
+#define WM8991_LOP_ENA_BIT 12
+#define WM8991_RON_ENA 0x0800 /* RON_ENA */
+#define WM8991_RON_ENA_BIT 11
+#define WM8991_ROP_ENA 0x0400 /* ROP_ENA */
+#define WM8991_ROP_ENA_BIT 10
+#define WM8991_LOPGA_ENA 0x0080 /* LOPGA_ENA */
+#define WM8991_LOPGA_ENA_BIT 7
+#define WM8991_ROPGA_ENA 0x0040 /* ROPGA_ENA */
+#define WM8991_ROPGA_ENA_BIT 6
+#define WM8991_LOMIX_ENA 0x0020 /* LOMIX_ENA */
+#define WM8991_LOMIX_ENA_BIT 5
+#define WM8991_ROMIX_ENA 0x0010 /* ROMIX_ENA */
+#define WM8991_ROMIX_ENA_BIT 4
+#define WM8991_DACL_ENA 0x0002 /* DACL_ENA */
+#define WM8991_DACL_ENA_BIT 1
+#define WM8991_DACR_ENA 0x0001 /* DACR_ENA */
+#define WM8991_DACR_ENA_BIT 0
+
+/*
+ * R4 (0x04) - Audio Interface (1)
+ */
+#define WM8991_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */
+#define WM8991_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */
+#define WM8991_AIFADC_TDM 0x2000 /* AIFADC_TDM */
+#define WM8991_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */
+#define WM8991_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */
+#define WM8991_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */
+#define WM8991_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */
+#define WM8991_AIF_WL_16BITS (0 << 5)
+#define WM8991_AIF_WL_20BITS (1 << 5)
+#define WM8991_AIF_WL_24BITS (2 << 5)
+#define WM8991_AIF_WL_32BITS (3 << 5)
+#define WM8991_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */
+#define WM8991_AIF_TMF_RIGHTJ (0 << 3)
+#define WM8991_AIF_TMF_LEFTJ (1 << 3)
+#define WM8991_AIF_TMF_I2S (2 << 3)
+#define WM8991_AIF_TMF_DSP (3 << 3)
+
+/*
+ * R5 (0x05) - Audio Interface (2)
+ */
+#define WM8991_DACL_SRC 0x8000 /* DACL_SRC */
+#define WM8991_DACR_SRC 0x4000 /* DACR_SRC */
+#define WM8991_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8991_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8991_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST - [11:10] */
+#define WM8991_DAC_COMP 0x0010 /* DAC_COMP */
+#define WM8991_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */
+#define WM8991_ADC_COMP 0x0004 /* ADC_COMP */
+#define WM8991_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */
+#define WM8991_LOOPBACK 0x0001 /* LOOPBACK */
+
+/*
+ * R6 (0x06) - Clocking (1)
+ */
+#define WM8991_TOCLK_RATE 0x8000 /* TOCLK_RATE */
+#define WM8991_TOCLK_ENA 0x4000 /* TOCLK_ENA */
+#define WM8991_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */
+#define WM8991_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */
+#define WM8991_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */
+#define WM8991_BCLK_DIV_1 (0x0 << 1)
+#define WM8991_BCLK_DIV_1_5 (0x1 << 1)
+#define WM8991_BCLK_DIV_2 (0x2 << 1)
+#define WM8991_BCLK_DIV_3 (0x3 << 1)
+#define WM8991_BCLK_DIV_4 (0x4 << 1)
+#define WM8991_BCLK_DIV_5_5 (0x5 << 1)
+#define WM8991_BCLK_DIV_6 (0x6 << 1)
+#define WM8991_BCLK_DIV_8 (0x7 << 1)
+#define WM8991_BCLK_DIV_11 (0x8 << 1)
+#define WM8991_BCLK_DIV_12 (0x9 << 1)
+#define WM8991_BCLK_DIV_16 (0xA << 1)
+#define WM8991_BCLK_DIV_22 (0xB << 1)
+#define WM8991_BCLK_DIV_24 (0xC << 1)
+#define WM8991_BCLK_DIV_32 (0xD << 1)
+#define WM8991_BCLK_DIV_44 (0xE << 1)
+#define WM8991_BCLK_DIV_48 (0xF << 1)
+
+/*
+ * R7 (0x07) - Clocking (2)
+ */
+#define WM8991_MCLK_SRC 0x8000 /* MCLK_SRC */
+#define WM8991_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */
+#define WM8991_CLK_FORCE 0x2000 /* CLK_FORCE */
+#define WM8991_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */
+#define WM8991_MCLK_DIV_1 (0 << 11)
+#define WM8991_MCLK_DIV_2 ( 2 << 11)
+#define WM8991_MCLK_INV 0x0400 /* MCLK_INV */
+#define WM8991_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV - [7:5] */
+#define WM8991_ADC_CLKDIV_1 (0 << 5)
+#define WM8991_ADC_CLKDIV_1_5 (1 << 5)
+#define WM8991_ADC_CLKDIV_2 (2 << 5)
+#define WM8991_ADC_CLKDIV_3 (3 << 5)
+#define WM8991_ADC_CLKDIV_4 (4 << 5)
+#define WM8991_ADC_CLKDIV_5_5 (5 << 5)
+#define WM8991_ADC_CLKDIV_6 (6 << 5)
+#define WM8991_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */
+#define WM8991_DAC_CLKDIV_1 (0 << 2)
+#define WM8991_DAC_CLKDIV_1_5 (1 << 2)
+#define WM8991_DAC_CLKDIV_2 (2 << 2)
+#define WM8991_DAC_CLKDIV_3 (3 << 2)
+#define WM8991_DAC_CLKDIV_4 (4 << 2)
+#define WM8991_DAC_CLKDIV_5_5 (5 << 2)
+#define WM8991_DAC_CLKDIV_6 (6 << 2)
+
+/*
+ * R8 (0x08) - Audio Interface (3)
+ */
+#define WM8991_AIF_MSTR1 0x8000 /* AIF_MSTR1 */
+#define WM8991_AIF_MSTR2 0x4000 /* AIF_MSTR2 */
+#define WM8991_AIF_SEL 0x2000 /* AIF_SEL */
+#define WM8991_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */
+#define WM8991_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE - [10:0] */
+
+/*
+ * R9 (0x09) - Audio Interface (4)
+ */
+#define WM8991_ALRCGPIO1 0x8000 /* ALRCGPIO1 */
+#define WM8991_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */
+#define WM8991_AIF_TRIS 0x2000 /* AIF_TRIS */
+#define WM8991_DACLRC_DIR 0x0800 /* DACLRC_DIR */
+#define WM8991_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE - [10:0] */
+
+/*
+ * R10 (0x0A) - DAC CTRL
+ */
+#define WM8991_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */
+#define WM8991_DAC_MONO 0x0200 /* DAC_MONO */
+#define WM8991_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */
+#define WM8991_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */
+#define WM8991_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */
+#define WM8991_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */
+#define WM8991_DAC_MUTE 0x0004 /* DAC_MUTE */
+#define WM8991_DACL_DATINV 0x0002 /* DACL_DATINV */
+#define WM8991_DACR_DATINV 0x0001 /* DACR_DATINV */
+
+/*
+ * R11 (0x0B) - Left DAC Digital Volume
+ */
+#define WM8991_DAC_VU 0x0100 /* DAC_VU */
+#define WM8991_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8991_DACL_VOL_SHIFT 0
+/*
+ * R12 (0x0C) - Right DAC Digital Volume
+ */
+#define WM8991_DAC_VU 0x0100 /* DAC_VU */
+#define WM8991_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8991_DACR_VOL_SHIFT 0
+/*
+ * R13 (0x0D) - Digital Side Tone
+ */
+#define WM8991_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL - [12:9] */
+#define WM8991_ADCL_DAC_SVOL_SHIFT 9
+#define WM8991_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL - [8:5] */
+#define WM8991_ADCR_DAC_SVOL_SHIFT 5
+#define WM8991_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */
+#define WM8991_ADC_TO_DACL_SHIFT 2
+#define WM8991_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */
+#define WM8991_ADC_TO_DACR_SHIFT 0
+
+/*
+ * R14 (0x0E) - ADC CTRL
+ */
+#define WM8991_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */
+#define WM8991_ADC_HPF_ENA_BIT 8
+#define WM8991_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */
+#define WM8991_ADC_HPF_CUT_SHIFT 5
+#define WM8991_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8991_ADCL_DATINV_BIT 1
+#define WM8991_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8991_ADCR_DATINV_BIT 0
+
+/*
+ * R15 (0x0F) - Left ADC Digital Volume
+ */
+#define WM8991_ADC_VU 0x0100 /* ADC_VU */
+#define WM8991_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8991_ADCL_VOL_SHIFT 0
+
+/*
+ * R16 (0x10) - Right ADC Digital Volume
+ */
+#define WM8991_ADC_VU 0x0100 /* ADC_VU */
+#define WM8991_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8991_ADCR_VOL_SHIFT 0
+
+/*
+ * R18 (0x12) - GPIO CTRL 1
+ */
+#define WM8991_IRQ 0x1000 /* IRQ */
+#define WM8991_TEMPOK 0x0800 /* TEMPOK */
+#define WM8991_MICSHRT 0x0400 /* MICSHRT */
+#define WM8991_MICDET 0x0200 /* MICDET */
+#define WM8991_PLL_LCK 0x0100 /* PLL_LCK */
+#define WM8991_GPI8_STATUS 0x0080 /* GPI8_STATUS */
+#define WM8991_GPI7_STATUS 0x0040 /* GPI7_STATUS */
+#define WM8991_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */
+#define WM8991_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */
+#define WM8991_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */
+#define WM8991_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */
+#define WM8991_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */
+#define WM8991_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */
+
+/*
+ * R19 (0x13) - GPIO1 & GPIO2
+ */
+#define WM8991_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */
+#define WM8991_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */
+#define WM8991_GPIO2_PU 0x2000 /* GPIO2_PU */
+#define WM8991_GPIO2_PD 0x1000 /* GPIO2_PD */
+#define WM8991_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */
+#define WM8991_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */
+#define WM8991_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */
+#define WM8991_GPIO1_PU 0x0020 /* GPIO1_PU */
+#define WM8991_GPIO1_PD 0x0010 /* GPIO1_PD */
+#define WM8991_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */
+
+/*
+ * R20 (0x14) - GPIO3 & GPIO4
+ */
+#define WM8991_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */
+#define WM8991_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */
+#define WM8991_GPIO4_PU 0x2000 /* GPIO4_PU */
+#define WM8991_GPIO4_PD 0x1000 /* GPIO4_PD */
+#define WM8991_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */
+#define WM8991_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */
+#define WM8991_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */
+#define WM8991_GPIO3_PU 0x0020 /* GPIO3_PU */
+#define WM8991_GPIO3_PD 0x0010 /* GPIO3_PD */
+#define WM8991_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */
+
+/*
+ * R21 (0x15) - GPIO5 & GPIO6
+ */
+#define WM8991_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */
+#define WM8991_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */
+#define WM8991_GPIO6_PU 0x2000 /* GPIO6_PU */
+#define WM8991_GPIO6_PD 0x1000 /* GPIO6_PD */
+#define WM8991_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */
+#define WM8991_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */
+#define WM8991_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */
+#define WM8991_GPIO5_PU 0x0020 /* GPIO5_PU */
+#define WM8991_GPIO5_PD 0x0010 /* GPIO5_PD */
+#define WM8991_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */
+
+/*
+ * R22 (0x16) - GPIOCTRL 2
+ */
+#define WM8991_RD_3W_ENA 0x8000 /* RD_3W_ENA */
+#define WM8991_MODE_3W4W 0x4000 /* MODE_3W4W */
+#define WM8991_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */
+#define WM8991_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */
+#define WM8991_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */
+#define WM8991_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */
+#define WM8991_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */
+#define WM8991_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */
+#define WM8991_GPI8_ENA 0x0010 /* GPI8_ENA */
+#define WM8991_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */
+#define WM8991_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */
+#define WM8991_GPI7_ENA 0x0001 /* GPI7_ENA */
+
+/*
+ * R23 (0x17) - GPIO_POL
+ */
+#define WM8991_IRQ_INV 0x1000 /* IRQ_INV */
+#define WM8991_TEMPOK_POL 0x0800 /* TEMPOK_POL */
+#define WM8991_MICSHRT_POL 0x0400 /* MICSHRT_POL */
+#define WM8991_MICDET_POL 0x0200 /* MICDET_POL */
+#define WM8991_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */
+#define WM8991_GPI8_POL 0x0080 /* GPI8_POL */
+#define WM8991_GPI7_POL 0x0040 /* GPI7_POL */
+#define WM8991_GPIO6_POL 0x0020 /* GPIO6_POL */
+#define WM8991_GPIO5_POL 0x0010 /* GPIO5_POL */
+#define WM8991_GPIO4_POL 0x0008 /* GPIO4_POL */
+#define WM8991_GPIO3_POL 0x0004 /* GPIO3_POL */
+#define WM8991_GPIO2_POL 0x0002 /* GPIO2_POL */
+#define WM8991_GPIO1_POL 0x0001 /* GPIO1_POL */
+
+/*
+ * R24 (0x18) - Left Line Input 1&2 Volume
+ */
+#define WM8991_IPVU 0x0100 /* IPVU */
+#define WM8991_LI12MUTE 0x0080 /* LI12MUTE */
+#define WM8991_LI12MUTE_BIT 7
+#define WM8991_LI12ZC 0x0040 /* LI12ZC */
+#define WM8991_LI12ZC_BIT 6
+#define WM8991_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */
+#define WM8991_LIN12VOL_SHIFT 0
+/*
+ * R25 (0x19) - Left Line Input 3&4 Volume
+ */
+#define WM8991_IPVU 0x0100 /* IPVU */
+#define WM8991_LI34MUTE 0x0080 /* LI34MUTE */
+#define WM8991_LI34MUTE_BIT 7
+#define WM8991_LI34ZC 0x0040 /* LI34ZC */
+#define WM8991_LI34ZC_BIT 6
+#define WM8991_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */
+#define WM8991_LIN34VOL_SHIFT 0
+
+/*
+ * R26 (0x1A) - Right Line Input 1&2 Volume
+ */
+#define WM8991_IPVU 0x0100 /* IPVU */
+#define WM8991_RI12MUTE 0x0080 /* RI12MUTE */
+#define WM8991_RI12MUTE_BIT 7
+#define WM8991_RI12ZC 0x0040 /* RI12ZC */
+#define WM8991_RI12ZC_BIT 6
+#define WM8991_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */
+#define WM8991_RIN12VOL_SHIFT 0
+
+/*
+ * R27 (0x1B) - Right Line Input 3&4 Volume
+ */
+#define WM8991_IPVU 0x0100 /* IPVU */
+#define WM8991_RI34MUTE 0x0080 /* RI34MUTE */
+#define WM8991_RI34MUTE_BIT 7
+#define WM8991_RI34ZC 0x0040 /* RI34ZC */
+#define WM8991_RI34ZC_BIT 6
+#define WM8991_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */
+#define WM8991_RIN34VOL_SHIFT 0
+
+/*
+ * R28 (0x1C) - Left Output Volume
+ */
+#define WM8991_OPVU 0x0100 /* OPVU */
+#define WM8991_LOZC 0x0080 /* LOZC */
+#define WM8991_LOZC_BIT 7
+#define WM8991_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */
+#define WM8991_LOUTVOL_SHIFT 0
+/*
+ * R29 (0x1D) - Right Output Volume
+ */
+#define WM8991_OPVU 0x0100 /* OPVU */
+#define WM8991_ROZC 0x0080 /* ROZC */
+#define WM8991_ROZC_BIT 7
+#define WM8991_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */
+#define WM8991_ROUTVOL_SHIFT 0
+/*
+ * R30 (0x1E) - Line Outputs Volume
+ */
+#define WM8991_LONMUTE 0x0040 /* LONMUTE */
+#define WM8991_LONMUTE_BIT 6
+#define WM8991_LOPMUTE 0x0020 /* LOPMUTE */
+#define WM8991_LOPMUTE_BIT 5
+#define WM8991_LOATTN 0x0010 /* LOATTN */
+#define WM8991_LOATTN_BIT 4
+#define WM8991_RONMUTE 0x0004 /* RONMUTE */
+#define WM8991_RONMUTE_BIT 2
+#define WM8991_ROPMUTE 0x0002 /* ROPMUTE */
+#define WM8991_ROPMUTE_BIT 1
+#define WM8991_ROATTN 0x0001 /* ROATTN */
+#define WM8991_ROATTN_BIT 0
+
+/*
+ * R31 (0x1F) - Out3/4 Volume
+ */
+#define WM8991_OUT3MUTE 0x0020 /* OUT3MUTE */
+#define WM8991_OUT3MUTE_BIT 5
+#define WM8991_OUT3ATTN 0x0010 /* OUT3ATTN */
+#define WM8991_OUT3ATTN_BIT 4
+#define WM8991_OUT4MUTE 0x0002 /* OUT4MUTE */
+#define WM8991_OUT4MUTE_BIT 1
+#define WM8991_OUT4ATTN 0x0001 /* OUT4ATTN */
+#define WM8991_OUT4ATTN_BIT 0
+
+/*
+ * R32 (0x20) - Left OPGA Volume
+ */
+#define WM8991_OPVU 0x0100 /* OPVU */
+#define WM8991_LOPGAZC 0x0080 /* LOPGAZC */
+#define WM8991_LOPGAZC_BIT 7
+#define WM8991_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */
+#define WM8991_LOPGAVOL_SHIFT 0
+
+/*
+ * R33 (0x21) - Right OPGA Volume
+ */
+#define WM8991_OPVU 0x0100 /* OPVU */
+#define WM8991_ROPGAZC 0x0080 /* ROPGAZC */
+#define WM8991_ROPGAZC_BIT 7
+#define WM8991_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */
+#define WM8991_ROPGAVOL_SHIFT 0
+/*
+ * R34 (0x22) - Speaker Volume
+ */
+#define WM8991_SPKVOL_MASK 0x0003 /* SPKVOL - [1:0] */
+#define WM8991_SPKVOL_SHIFT 0
+
+/*
+ * R35 (0x23) - ClassD1
+ */
+#define WM8991_CDMODE 0x0100 /* CDMODE */
+#define WM8991_CDMODE_BIT 8
+
+/*
+ * R37 (0x25) - ClassD3
+ */
+#define WM8991_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */
+#define WM8991_DCGAIN_SHIFT 3
+#define WM8991_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */
+#define WM8991_ACGAIN_SHIFT 0
+/*
+ * R39 (0x27) - Input Mixer1
+ */
+#define WM8991_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */
+#define WM8991_AINLMODE_SHIFT 2
+#define WM8991_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */
+#define WM8991_AINRMODE_SHIFT 0
+
+/*
+ * R40 (0x28) - Input Mixer2
+ */
+#define WM8991_LMP4 0x0080 /* LMP4 */
+#define WM8991_LMP4_BIT 7 /* LMP4 */
+#define WM8991_LMN3 0x0040 /* LMN3 */
+#define WM8991_LMN3_BIT 6 /* LMN3 */
+#define WM8991_LMP2 0x0020 /* LMP2 */
+#define WM8991_LMP2_BIT 5 /* LMP2 */
+#define WM8991_LMN1 0x0010 /* LMN1 */
+#define WM8991_LMN1_BIT 4 /* LMN1 */
+#define WM8991_RMP4 0x0008 /* RMP4 */
+#define WM8991_RMP4_BIT 3 /* RMP4 */
+#define WM8991_RMN3 0x0004 /* RMN3 */
+#define WM8991_RMN3_BIT 2 /* RMN3 */
+#define WM8991_RMP2 0x0002 /* RMP2 */
+#define WM8991_RMP2_BIT 1 /* RMP2 */
+#define WM8991_RMN1 0x0001 /* RMN1 */
+#define WM8991_RMN1_BIT 0 /* RMN1 */
+
+/*
+ * R41 (0x29) - Input Mixer3
+ */
+#define WM8991_L34MNB 0x0100 /* L34MNB */
+#define WM8991_L34MNB_BIT 8
+#define WM8991_L34MNBST 0x0080 /* L34MNBST */
+#define WM8991_L34MNBST_BIT 7
+#define WM8991_L12MNB 0x0020 /* L12MNB */
+#define WM8991_L12MNB_BIT 5
+#define WM8991_L12MNBST 0x0010 /* L12MNBST */
+#define WM8991_L12MNBST_BIT 4
+#define WM8991_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */
+#define WM8991_LDBVOL_SHIFT 0
+
+/*
+ * R42 (0x2A) - Input Mixer4
+ */
+#define WM8991_R34MNB 0x0100 /* R34MNB */
+#define WM8991_R34MNB_BIT 8
+#define WM8991_R34MNBST 0x0080 /* R34MNBST */
+#define WM8991_R34MNBST_BIT 7
+#define WM8991_R12MNB 0x0020 /* R12MNB */
+#define WM8991_R12MNB_BIT 5
+#define WM8991_R12MNBST 0x0010 /* R12MNBST */
+#define WM8991_R12MNBST_BIT 4
+#define WM8991_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */
+#define WM8991_RDBVOL_SHIFT 0
+
+/*
+ * R43 (0x2B) - Input Mixer5
+ */
+#define WM8991_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */
+#define WM8991_LI2BVOL_SHIFT 6
+#define WM8991_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */
+#define WM8991_LR4BVOL_SHIFT 3
+#define WM8991_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */
+#define WM8991_LL4BVOL_SHIFT 0
+
+/*
+ * R44 (0x2C) - Input Mixer6
+ */
+#define WM8991_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */
+#define WM8991_RI2BVOL_SHIFT 6
+#define WM8991_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */
+#define WM8991_RL4BVOL_SHIFT 3
+#define WM8991_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */
+#define WM8991_RR4BVOL_SHIFT 0
+
+/*
+ * R45 (0x2D) - Output Mixer1
+ */
+#define WM8991_LRBLO 0x0080 /* LRBLO */
+#define WM8991_LRBLO_BIT 7
+#define WM8991_LLBLO 0x0040 /* LLBLO */
+#define WM8991_LLBLO_BIT 6
+#define WM8991_LRI3LO 0x0020 /* LRI3LO */
+#define WM8991_LRI3LO_BIT 5
+#define WM8991_LLI3LO 0x0010 /* LLI3LO */
+#define WM8991_LLI3LO_BIT 4
+#define WM8991_LR12LO 0x0008 /* LR12LO */
+#define WM8991_LR12LO_BIT 3
+#define WM8991_LL12LO 0x0004 /* LL12LO */
+#define WM8991_LL12LO_BIT 2
+#define WM8991_LDLO 0x0001 /* LDLO */
+#define WM8991_LDLO_BIT 0
+
+/*
+ * R46 (0x2E) - Output Mixer2
+ */
+#define WM8991_RLBRO 0x0080 /* RLBRO */
+#define WM8991_RLBRO_BIT 7
+#define WM8991_RRBRO 0x0040 /* RRBRO */
+#define WM8991_RRBRO_BIT 6
+#define WM8991_RLI3RO 0x0020 /* RLI3RO */
+#define WM8991_RLI3RO_BIT 5
+#define WM8991_RRI3RO 0x0010 /* RRI3RO */
+#define WM8991_RRI3RO_BIT 4
+#define WM8991_RL12RO 0x0008 /* RL12RO */
+#define WM8991_RL12RO_BIT 3
+#define WM8991_RR12RO 0x0004 /* RR12RO */
+#define WM8991_RR12RO_BIT 2
+#define WM8991_RDRO 0x0001 /* RDRO */
+#define WM8991_RDRO_BIT 0
+
+/*
+ * R47 (0x2F) - Output Mixer3
+ */
+#define WM8991_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */
+#define WM8991_LLI3LOVOL_SHIFT 6
+#define WM8991_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */
+#define WM8991_LR12LOVOL_SHIFT 3
+#define WM8991_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */
+#define WM8991_LL12LOVOL_SHIFT 0
+
+/*
+ * R48 (0x30) - Output Mixer4
+ */
+#define WM8991_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */
+#define WM8991_RRI3ROVOL_SHIFT 6
+#define WM8991_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */
+#define WM8991_RL12ROVOL_SHIFT 3
+#define WM8991_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */
+#define WM8991_RR12ROVOL_SHIFT 0
+
+/*
+ * R49 (0x31) - Output Mixer5
+ */
+#define WM8991_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */
+#define WM8991_LRI3LOVOL_SHIFT 6
+#define WM8991_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */
+#define WM8991_LRBLOVOL_SHIFT 3
+#define WM8991_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */
+#define WM8991_LLBLOVOL_SHIFT 0
+
+/*
+ * R50 (0x32) - Output Mixer6
+ */
+#define WM8991_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */
+#define WM8991_RLI3ROVOL_SHIFT 6
+#define WM8991_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */
+#define WM8991_RLBROVOL_SHIFT 3
+#define WM8991_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */
+#define WM8991_RRBROVOL_SHIFT 0
+
+/*
+ * R51 (0x33) - Out3/4 Mixer
+ */
+#define WM8991_VSEL_MASK 0x0180 /* VSEL - [8:7] */
+#define WM8991_LI4O3 0x0020 /* LI4O3 */
+#define WM8991_LI4O3_BIT 5
+#define WM8991_LPGAO3 0x0010 /* LPGAO3 */
+#define WM8991_LPGAO3_BIT 4
+#define WM8991_RI4O4 0x0002 /* RI4O4 */
+#define WM8991_RI4O4_BIT 1
+#define WM8991_RPGAO4 0x0001 /* RPGAO4 */
+#define WM8991_RPGAO4_BIT 0
+/*
+ * R52 (0x34) - Line Mixer1
+ */
+#define WM8991_LLOPGALON 0x0040 /* LLOPGALON */
+#define WM8991_LLOPGALON_BIT 6
+#define WM8991_LROPGALON 0x0020 /* LROPGALON */
+#define WM8991_LROPGALON_BIT 5
+#define WM8991_LOPLON 0x0010 /* LOPLON */
+#define WM8991_LOPLON_BIT 4
+#define WM8991_LR12LOP 0x0004 /* LR12LOP */
+#define WM8991_LR12LOP_BIT 2
+#define WM8991_LL12LOP 0x0002 /* LL12LOP */
+#define WM8991_LL12LOP_BIT 1
+#define WM8991_LLOPGALOP 0x0001 /* LLOPGALOP */
+#define WM8991_LLOPGALOP_BIT 0
+/*
+ * R53 (0x35) - Line Mixer2
+ */
+#define WM8991_RROPGARON 0x0040 /* RROPGARON */
+#define WM8991_RROPGARON_BIT 6
+#define WM8991_RLOPGARON 0x0020 /* RLOPGARON */
+#define WM8991_RLOPGARON_BIT 5
+#define WM8991_ROPRON 0x0010 /* ROPRON */
+#define WM8991_ROPRON_BIT 4
+#define WM8991_RL12ROP 0x0004 /* RL12ROP */
+#define WM8991_RL12ROP_BIT 2
+#define WM8991_RR12ROP 0x0002 /* RR12ROP */
+#define WM8991_RR12ROP_BIT 1
+#define WM8991_RROPGAROP 0x0001 /* RROPGAROP */
+#define WM8991_RROPGAROP_BIT 0
+
+/*
+ * R54 (0x36) - Speaker Mixer
+ */
+#define WM8991_LB2SPK 0x0080 /* LB2SPK */
+#define WM8991_LB2SPK_BIT 7
+#define WM8991_RB2SPK 0x0040 /* RB2SPK */
+#define WM8991_RB2SPK_BIT 6
+#define WM8991_LI2SPK 0x0020 /* LI2SPK */
+#define WM8991_LI2SPK_BIT 5
+#define WM8991_RI2SPK 0x0010 /* RI2SPK */
+#define WM8991_RI2SPK_BIT 4
+#define WM8991_LOPGASPK 0x0008 /* LOPGASPK */
+#define WM8991_LOPGASPK_BIT 3
+#define WM8991_ROPGASPK 0x0004 /* ROPGASPK */
+#define WM8991_ROPGASPK_BIT 2
+#define WM8991_LDSPK 0x0002 /* LDSPK */
+#define WM8991_LDSPK_BIT 1
+#define WM8991_RDSPK 0x0001 /* RDSPK */
+#define WM8991_RDSPK_BIT 0
+
+/*
+ * R55 (0x37) - Additional Control
+ */
+#define WM8991_VROI 0x0001 /* VROI */
+
+/*
+ * R56 (0x38) - AntiPOP1
+ */
+#define WM8991_DIS_LLINE 0x0020 /* DIS_LLINE */
+#define WM8991_DIS_RLINE 0x0010 /* DIS_RLINE */
+#define WM8991_DIS_OUT3 0x0008 /* DIS_OUT3 */
+#define WM8991_DIS_OUT4 0x0004 /* DIS_OUT4 */
+#define WM8991_DIS_LOUT 0x0002 /* DIS_LOUT */
+#define WM8991_DIS_ROUT 0x0001 /* DIS_ROUT */
+
+/*
+ * R57 (0x39) - AntiPOP2
+ */
+#define WM8991_SOFTST 0x0040 /* SOFTST */
+#define WM8991_BUFIOEN 0x0008 /* BUFIOEN */
+#define WM8991_BUFDCOPEN 0x0004 /* BUFDCOPEN */
+#define WM8991_POBCTRL 0x0002 /* POBCTRL */
+#define WM8991_VMIDTOG 0x0001 /* VMIDTOG */
+
+/*
+ * R58 (0x3A) - MICBIAS
+ */
+#define WM8991_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */
+#define WM8991_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */
+#define WM8991_MCD 0x0004 /* MCD */
+#define WM8991_MBSEL 0x0001 /* MBSEL */
+
+/*
+ * R60 (0x3C) - PLL1
+ */
+#define WM8991_SDM 0x0080 /* SDM */
+#define WM8991_PRESCALE 0x0040 /* PRESCALE */
+#define WM8991_PLLN_MASK 0x000F /* PLLN - [3:0] */
+
+/*
+ * R61 (0x3D) - PLL2
+ */
+#define WM8991_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */
+
+/*
+ * R62 (0x3E) - PLL3
+ */
+#define WM8991_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */
+
+/*
+ * R63 (0x3F) - Internal Driver Bits
+ */
+#define WM8991_INMIXL_PWR_BIT 0
+#define WM8991_AINLMUX_PWR_BIT 1
+#define WM8991_INMIXR_PWR_BIT 2
+#define WM8991_AINRMUX_PWR_BIT 3
+
+#define WM8991_MCLK_DIV 0
+#define WM8991_DACCLK_DIV 1
+#define WM8991_ADCCLK_DIV 2
+#define WM8991_BCLK_DIV 3
+
+#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\
+ tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+#endif /* _WM8991_H */
diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c
new file mode 100644
index 00000000..9e5ff789
--- /dev/null
+++ b/sound/soc/codecs/wm8993.c
@@ -0,0 +1,1672 @@
+/*
+ * wm8993.c -- WM8993 ALSA SoC audio driver
+ *
+ * Copyright 2009, 2010 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/wm8993.h>
+
+#include "wm8993.h"
+#include "wm_hubs.h"
+
+#define WM8993_NUM_SUPPLIES 6
+static const char *wm8993_supply_names[WM8993_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD",
+ "AVDD1",
+ "AVDD2",
+ "CPVDD",
+ "SPKVDD",
+};
+
+static u16 wm8993_reg_defaults[WM8993_REGISTER_COUNT] = {
+ 0x8993, /* R0 - Software Reset */
+ 0x0000, /* R1 - Power Management (1) */
+ 0x6000, /* R2 - Power Management (2) */
+ 0x0000, /* R3 - Power Management (3) */
+ 0x4050, /* R4 - Audio Interface (1) */
+ 0x4000, /* R5 - Audio Interface (2) */
+ 0x01C8, /* R6 - Clocking 1 */
+ 0x0000, /* R7 - Clocking 2 */
+ 0x0000, /* R8 - Audio Interface (3) */
+ 0x0040, /* R9 - Audio Interface (4) */
+ 0x0004, /* R10 - DAC CTRL */
+ 0x00C0, /* R11 - Left DAC Digital Volume */
+ 0x00C0, /* R12 - Right DAC Digital Volume */
+ 0x0000, /* R13 - Digital Side Tone */
+ 0x0300, /* R14 - ADC CTRL */
+ 0x00C0, /* R15 - Left ADC Digital Volume */
+ 0x00C0, /* R16 - Right ADC Digital Volume */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 - GPIO CTRL 1 */
+ 0x0010, /* R19 - GPIO1 */
+ 0x0000, /* R20 - IRQ_DEBOUNCE */
+ 0x0000, /* R21 */
+ 0x8000, /* R22 - GPIOCTRL 2 */
+ 0x0800, /* R23 - GPIO_POL */
+ 0x008B, /* R24 - Left Line Input 1&2 Volume */
+ 0x008B, /* R25 - Left Line Input 3&4 Volume */
+ 0x008B, /* R26 - Right Line Input 1&2 Volume */
+ 0x008B, /* R27 - Right Line Input 3&4 Volume */
+ 0x006D, /* R28 - Left Output Volume */
+ 0x006D, /* R29 - Right Output Volume */
+ 0x0066, /* R30 - Line Outputs Volume */
+ 0x0020, /* R31 - HPOUT2 Volume */
+ 0x0079, /* R32 - Left OPGA Volume */
+ 0x0079, /* R33 - Right OPGA Volume */
+ 0x0003, /* R34 - SPKMIXL Attenuation */
+ 0x0003, /* R35 - SPKMIXR Attenuation */
+ 0x0011, /* R36 - SPKOUT Mixers */
+ 0x0100, /* R37 - SPKOUT Boost */
+ 0x0079, /* R38 - Speaker Volume Left */
+ 0x0079, /* R39 - Speaker Volume Right */
+ 0x0000, /* R40 - Input Mixer2 */
+ 0x0000, /* R41 - Input Mixer3 */
+ 0x0000, /* R42 - Input Mixer4 */
+ 0x0000, /* R43 - Input Mixer5 */
+ 0x0000, /* R44 - Input Mixer6 */
+ 0x0000, /* R45 - Output Mixer1 */
+ 0x0000, /* R46 - Output Mixer2 */
+ 0x0000, /* R47 - Output Mixer3 */
+ 0x0000, /* R48 - Output Mixer4 */
+ 0x0000, /* R49 - Output Mixer5 */
+ 0x0000, /* R50 - Output Mixer6 */
+ 0x0000, /* R51 - HPOUT2 Mixer */
+ 0x0000, /* R52 - Line Mixer1 */
+ 0x0000, /* R53 - Line Mixer2 */
+ 0x0000, /* R54 - Speaker Mixer */
+ 0x0000, /* R55 - Additional Control */
+ 0x0000, /* R56 - AntiPOP1 */
+ 0x0000, /* R57 - AntiPOP2 */
+ 0x0000, /* R58 - MICBIAS */
+ 0x0000, /* R59 */
+ 0x0000, /* R60 - FLL Control 1 */
+ 0x0000, /* R61 - FLL Control 2 */
+ 0x0000, /* R62 - FLL Control 3 */
+ 0x2EE0, /* R63 - FLL Control 4 */
+ 0x0002, /* R64 - FLL Control 5 */
+ 0x2287, /* R65 - Clocking 3 */
+ 0x025F, /* R66 - Clocking 4 */
+ 0x0000, /* R67 - MW Slave Control */
+ 0x0000, /* R68 */
+ 0x0002, /* R69 - Bus Control 1 */
+ 0x0000, /* R70 - Write Sequencer 0 */
+ 0x0000, /* R71 - Write Sequencer 1 */
+ 0x0000, /* R72 - Write Sequencer 2 */
+ 0x0000, /* R73 - Write Sequencer 3 */
+ 0x0000, /* R74 - Write Sequencer 4 */
+ 0x0000, /* R75 - Write Sequencer 5 */
+ 0x1F25, /* R76 - Charge Pump 1 */
+ 0x0000, /* R77 */
+ 0x0000, /* R78 */
+ 0x0000, /* R79 */
+ 0x0000, /* R80 */
+ 0x0000, /* R81 - Class W 0 */
+ 0x0000, /* R82 */
+ 0x0000, /* R83 */
+ 0x0000, /* R84 - DC Servo 0 */
+ 0x054A, /* R85 - DC Servo 1 */
+ 0x0000, /* R86 */
+ 0x0000, /* R87 - DC Servo 3 */
+ 0x0000, /* R88 - DC Servo Readback 0 */
+ 0x0000, /* R89 - DC Servo Readback 1 */
+ 0x0000, /* R90 - DC Servo Readback 2 */
+ 0x0000, /* R91 */
+ 0x0000, /* R92 */
+ 0x0000, /* R93 */
+ 0x0000, /* R94 */
+ 0x0000, /* R95 */
+ 0x0100, /* R96 - Analogue HP 0 */
+ 0x0000, /* R97 */
+ 0x0000, /* R98 - EQ1 */
+ 0x000C, /* R99 - EQ2 */
+ 0x000C, /* R100 - EQ3 */
+ 0x000C, /* R101 - EQ4 */
+ 0x000C, /* R102 - EQ5 */
+ 0x000C, /* R103 - EQ6 */
+ 0x0FCA, /* R104 - EQ7 */
+ 0x0400, /* R105 - EQ8 */
+ 0x00D8, /* R106 - EQ9 */
+ 0x1EB5, /* R107 - EQ10 */
+ 0xF145, /* R108 - EQ11 */
+ 0x0B75, /* R109 - EQ12 */
+ 0x01C5, /* R110 - EQ13 */
+ 0x1C58, /* R111 - EQ14 */
+ 0xF373, /* R112 - EQ15 */
+ 0x0A54, /* R113 - EQ16 */
+ 0x0558, /* R114 - EQ17 */
+ 0x168E, /* R115 - EQ18 */
+ 0xF829, /* R116 - EQ19 */
+ 0x07AD, /* R117 - EQ20 */
+ 0x1103, /* R118 - EQ21 */
+ 0x0564, /* R119 - EQ22 */
+ 0x0559, /* R120 - EQ23 */
+ 0x4000, /* R121 - EQ24 */
+ 0x0000, /* R122 - Digital Pulls */
+ 0x0F08, /* R123 - DRC Control 1 */
+ 0x0000, /* R124 - DRC Control 2 */
+ 0x0080, /* R125 - DRC Control 3 */
+ 0x0000, /* R126 - DRC Control 4 */
+};
+
+static struct {
+ int ratio;
+ int clk_sys_rate;
+} clk_sys_rates[] = {
+ { 64, 0 },
+ { 128, 1 },
+ { 192, 2 },
+ { 256, 3 },
+ { 384, 4 },
+ { 512, 5 },
+ { 768, 6 },
+ { 1024, 7 },
+ { 1408, 8 },
+ { 1536, 9 },
+};
+
+static struct {
+ int rate;
+ int sample_rate;
+} sample_rates[] = {
+ { 8000, 0 },
+ { 11025, 1 },
+ { 12000, 1 },
+ { 16000, 2 },
+ { 22050, 3 },
+ { 24000, 3 },
+ { 32000, 4 },
+ { 44100, 5 },
+ { 48000, 5 },
+};
+
+static struct {
+ int div; /* *10 due to .5s */
+ int bclk_div;
+} bclk_divs[] = {
+ { 10, 0 },
+ { 15, 1 },
+ { 20, 2 },
+ { 30, 3 },
+ { 40, 4 },
+ { 55, 5 },
+ { 60, 6 },
+ { 80, 7 },
+ { 110, 8 },
+ { 120, 9 },
+ { 160, 10 },
+ { 220, 11 },
+ { 240, 12 },
+ { 320, 13 },
+ { 440, 14 },
+ { 480, 15 },
+};
+
+struct wm8993_priv {
+ struct wm_hubs_data hubs_data;
+ struct regulator_bulk_data supplies[WM8993_NUM_SUPPLIES];
+ struct wm8993_platform_data pdata;
+ enum snd_soc_control_type control_type;
+ int master;
+ int sysclk_source;
+ int tdm_slots;
+ int tdm_width;
+ unsigned int mclk_rate;
+ unsigned int sysclk_rate;
+ unsigned int fs;
+ unsigned int bclk;
+ int class_w_users;
+ unsigned int fll_fref;
+ unsigned int fll_fout;
+ int fll_src;
+};
+
+static int wm8993_volatile(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case WM8993_SOFTWARE_RESET:
+ case WM8993_DC_SERVO_0:
+ case WM8993_DC_SERVO_READBACK_0:
+ case WM8993_DC_SERVO_READBACK_1:
+ case WM8993_DC_SERVO_READBACK_2:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+struct _fll_div {
+ u16 fll_fratio;
+ u16 fll_outdiv;
+ u16 fll_clk_ref_div;
+ u16 n;
+ u16 k;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static struct {
+ unsigned int min;
+ unsigned int max;
+ u16 fll_fratio;
+ int ratio;
+} fll_fratios[] = {
+ { 0, 64000, 4, 16 },
+ { 64000, 128000, 3, 8 },
+ { 128000, 256000, 2, 4 },
+ { 256000, 1000000, 1, 2 },
+ { 1000000, 13500000, 0, 1 },
+};
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+ unsigned int Fout)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+ unsigned int div;
+ int i;
+
+ /* Fref must be <=13.5MHz */
+ div = 1;
+ fll_div->fll_clk_ref_div = 0;
+ while ((Fref / div) > 13500000) {
+ div *= 2;
+ fll_div->fll_clk_ref_div++;
+
+ if (div > 8) {
+ pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+ Fref);
+ return -EINVAL;
+ }
+ }
+
+ pr_debug("Fref=%u Fout=%u\n", Fref, Fout);
+
+ /* Apply the division for our remaining calculations */
+ Fref /= div;
+
+ /* Fvco should be 90-100MHz; don't check the upper bound */
+ div = 0;
+ target = Fout * 2;
+ while (target < 90000000) {
+ div++;
+ target *= 2;
+ if (div > 7) {
+ pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+ Fout);
+ return -EINVAL;
+ }
+ }
+ fll_div->fll_outdiv = div;
+
+ pr_debug("Fvco=%dHz\n", target);
+
+ /* Find an appropriate FLL_FRATIO and factor it out of the target */
+ for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+ if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+ fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+ target /= fll_fratios[i].ratio;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(fll_fratios)) {
+ pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+ return -EINVAL;
+ }
+
+ /* Now, calculate N.K */
+ Ndiv = target / Fref;
+
+ fll_div->n = Ndiv;
+ Nmod = target % Fref;
+ pr_debug("Nmod=%d\n", Nmod);
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, Fref);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ fll_div->k = K / 10;
+
+ pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n",
+ fll_div->n, fll_div->k,
+ fll_div->fll_fratio, fll_div->fll_outdiv,
+ fll_div->fll_clk_ref_div);
+
+ return 0;
+}
+
+static int _wm8993_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
+ unsigned int Fref, unsigned int Fout)
+{
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+ u16 reg1, reg4, reg5;
+ struct _fll_div fll_div;
+ int ret;
+
+ /* Any change? */
+ if (Fref == wm8993->fll_fref && Fout == wm8993->fll_fout)
+ return 0;
+
+ /* Disable the FLL */
+ if (Fout == 0) {
+ dev_dbg(codec->dev, "FLL disabled\n");
+ wm8993->fll_fref = 0;
+ wm8993->fll_fout = 0;
+
+ reg1 = snd_soc_read(codec, WM8993_FLL_CONTROL_1);
+ reg1 &= ~WM8993_FLL_ENA;
+ snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1);
+
+ return 0;
+ }
+
+ ret = fll_factors(&fll_div, Fref, Fout);
+ if (ret != 0)
+ return ret;
+
+ reg5 = snd_soc_read(codec, WM8993_FLL_CONTROL_5);
+ reg5 &= ~WM8993_FLL_CLK_SRC_MASK;
+
+ switch (fll_id) {
+ case WM8993_FLL_MCLK:
+ break;
+
+ case WM8993_FLL_LRCLK:
+ reg5 |= 1;
+ break;
+
+ case WM8993_FLL_BCLK:
+ reg5 |= 2;
+ break;
+
+ default:
+ dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id);
+ return -EINVAL;
+ }
+
+ /* Any FLL configuration change requires that the FLL be
+ * disabled first. */
+ reg1 = snd_soc_read(codec, WM8993_FLL_CONTROL_1);
+ reg1 &= ~WM8993_FLL_ENA;
+ snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1);
+
+ /* Apply the configuration */
+ if (fll_div.k)
+ reg1 |= WM8993_FLL_FRAC_MASK;
+ else
+ reg1 &= ~WM8993_FLL_FRAC_MASK;
+ snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1);
+
+ snd_soc_write(codec, WM8993_FLL_CONTROL_2,
+ (fll_div.fll_outdiv << WM8993_FLL_OUTDIV_SHIFT) |
+ (fll_div.fll_fratio << WM8993_FLL_FRATIO_SHIFT));
+ snd_soc_write(codec, WM8993_FLL_CONTROL_3, fll_div.k);
+
+ reg4 = snd_soc_read(codec, WM8993_FLL_CONTROL_4);
+ reg4 &= ~WM8993_FLL_N_MASK;
+ reg4 |= fll_div.n << WM8993_FLL_N_SHIFT;
+ snd_soc_write(codec, WM8993_FLL_CONTROL_4, reg4);
+
+ reg5 &= ~WM8993_FLL_CLK_REF_DIV_MASK;
+ reg5 |= fll_div.fll_clk_ref_div << WM8993_FLL_CLK_REF_DIV_SHIFT;
+ snd_soc_write(codec, WM8993_FLL_CONTROL_5, reg5);
+
+ /* Enable the FLL */
+ snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1 | WM8993_FLL_ENA);
+
+ dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout);
+
+ wm8993->fll_fref = Fref;
+ wm8993->fll_fout = Fout;
+ wm8993->fll_src = source;
+
+ return 0;
+}
+
+static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source,
+ unsigned int Fref, unsigned int Fout)
+{
+ return _wm8993_set_fll(dai->codec, fll_id, source, Fref, Fout);
+}
+
+static int configure_clock(struct snd_soc_codec *codec)
+{
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+ unsigned int reg;
+
+ /* This should be done on init() for bypass paths */
+ switch (wm8993->sysclk_source) {
+ case WM8993_SYSCLK_MCLK:
+ dev_dbg(codec->dev, "Using %dHz MCLK\n", wm8993->mclk_rate);
+
+ reg = snd_soc_read(codec, WM8993_CLOCKING_2);
+ reg &= ~(WM8993_MCLK_DIV | WM8993_SYSCLK_SRC);
+ if (wm8993->mclk_rate > 13500000) {
+ reg |= WM8993_MCLK_DIV;
+ wm8993->sysclk_rate = wm8993->mclk_rate / 2;
+ } else {
+ reg &= ~WM8993_MCLK_DIV;
+ wm8993->sysclk_rate = wm8993->mclk_rate;
+ }
+ snd_soc_write(codec, WM8993_CLOCKING_2, reg);
+ break;
+
+ case WM8993_SYSCLK_FLL:
+ dev_dbg(codec->dev, "Using %dHz FLL clock\n",
+ wm8993->fll_fout);
+
+ reg = snd_soc_read(codec, WM8993_CLOCKING_2);
+ reg |= WM8993_SYSCLK_SRC;
+ if (wm8993->fll_fout > 13500000) {
+ reg |= WM8993_MCLK_DIV;
+ wm8993->sysclk_rate = wm8993->fll_fout / 2;
+ } else {
+ reg &= ~WM8993_MCLK_DIV;
+ wm8993->sysclk_rate = wm8993->fll_fout;
+ }
+ snd_soc_write(codec, WM8993_CLOCKING_2, reg);
+ break;
+
+ default:
+ dev_err(codec->dev, "System clock not configured\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm8993->sysclk_rate);
+
+ return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(drc_comp_threash, -4500, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_comp_amp, -2250, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_min_tlv, -1800, 600, 0);
+static const unsigned int drc_max_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 2, TLV_DB_SCALE_ITEM(1200, 600, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(drc_qr_tlv, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -1800, 300, 0);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0);
+
+static const char *dac_deemph_text[] = {
+ "None",
+ "32kHz",
+ "44.1kHz",
+ "48kHz",
+};
+
+static const struct soc_enum dac_deemph =
+ SOC_ENUM_SINGLE(WM8993_DAC_CTRL, 4, 4, dac_deemph_text);
+
+static const char *adc_hpf_text[] = {
+ "Hi-Fi",
+ "Voice 1",
+ "Voice 2",
+ "Voice 3",
+};
+
+static const struct soc_enum adc_hpf =
+ SOC_ENUM_SINGLE(WM8993_ADC_CTRL, 5, 4, adc_hpf_text);
+
+static const char *drc_path_text[] = {
+ "ADC",
+ "DAC"
+};
+
+static const struct soc_enum drc_path =
+ SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 14, 2, drc_path_text);
+
+static const char *drc_r0_text[] = {
+ "1",
+ "1/2",
+ "1/4",
+ "1/8",
+ "1/16",
+ "0",
+};
+
+static const struct soc_enum drc_r0 =
+ SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_3, 8, 6, drc_r0_text);
+
+static const char *drc_r1_text[] = {
+ "1",
+ "1/2",
+ "1/4",
+ "1/8",
+ "0",
+};
+
+static const struct soc_enum drc_r1 =
+ SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_4, 13, 5, drc_r1_text);
+
+static const char *drc_attack_text[] = {
+ "Reserved",
+ "181us",
+ "363us",
+ "726us",
+ "1.45ms",
+ "2.9ms",
+ "5.8ms",
+ "11.6ms",
+ "23.2ms",
+ "46.4ms",
+ "92.8ms",
+ "185.6ms",
+};
+
+static const struct soc_enum drc_attack =
+ SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_2, 12, 12, drc_attack_text);
+
+static const char *drc_decay_text[] = {
+ "186ms",
+ "372ms",
+ "743ms",
+ "1.49s",
+ "2.97ms",
+ "5.94ms",
+ "11.89ms",
+ "23.78ms",
+ "47.56ms",
+};
+
+static const struct soc_enum drc_decay =
+ SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_2, 8, 9, drc_decay_text);
+
+static const char *drc_ff_text[] = {
+ "5 samples",
+ "9 samples",
+};
+
+static const struct soc_enum drc_ff =
+ SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_3, 7, 2, drc_ff_text);
+
+static const char *drc_qr_rate_text[] = {
+ "0.725ms",
+ "1.45ms",
+ "5.8ms",
+};
+
+static const struct soc_enum drc_qr_rate =
+ SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_3, 0, 3, drc_qr_rate_text);
+
+static const char *drc_smooth_text[] = {
+ "Low",
+ "Medium",
+ "High",
+};
+
+static const struct soc_enum drc_smooth =
+ SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 4, 3, drc_smooth_text);
+
+static const struct snd_kcontrol_new wm8993_snd_controls[] = {
+SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8993_DIGITAL_SIDE_TONE,
+ 5, 9, 12, 0, sidetone_tlv),
+
+SOC_SINGLE("DRC Switch", WM8993_DRC_CONTROL_1, 15, 1, 0),
+SOC_ENUM("DRC Path", drc_path),
+SOC_SINGLE_TLV("DRC Compressor Threshold Volume", WM8993_DRC_CONTROL_2,
+ 2, 60, 1, drc_comp_threash),
+SOC_SINGLE_TLV("DRC Compressor Amplitude Volume", WM8993_DRC_CONTROL_3,
+ 11, 30, 1, drc_comp_amp),
+SOC_ENUM("DRC R0", drc_r0),
+SOC_ENUM("DRC R1", drc_r1),
+SOC_SINGLE_TLV("DRC Minimum Volume", WM8993_DRC_CONTROL_1, 2, 3, 1,
+ drc_min_tlv),
+SOC_SINGLE_TLV("DRC Maximum Volume", WM8993_DRC_CONTROL_1, 0, 3, 0,
+ drc_max_tlv),
+SOC_ENUM("DRC Attack Rate", drc_attack),
+SOC_ENUM("DRC Decay Rate", drc_decay),
+SOC_ENUM("DRC FF Delay", drc_ff),
+SOC_SINGLE("DRC Anti-clip Switch", WM8993_DRC_CONTROL_1, 9, 1, 0),
+SOC_SINGLE("DRC Quick Release Switch", WM8993_DRC_CONTROL_1, 10, 1, 0),
+SOC_SINGLE_TLV("DRC Quick Release Volume", WM8993_DRC_CONTROL_3, 2, 3, 0,
+ drc_qr_tlv),
+SOC_ENUM("DRC Quick Release Rate", drc_qr_rate),
+SOC_SINGLE("DRC Smoothing Switch", WM8993_DRC_CONTROL_1, 11, 1, 0),
+SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8993_DRC_CONTROL_1, 8, 1, 0),
+SOC_ENUM("DRC Smoothing Hysteresis Threshold", drc_smooth),
+SOC_SINGLE_TLV("DRC Startup Volume", WM8993_DRC_CONTROL_4, 8, 18, 0,
+ drc_startup_tlv),
+
+SOC_SINGLE("EQ Switch", WM8993_EQ1, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8993_LEFT_ADC_DIGITAL_VOLUME,
+ WM8993_RIGHT_ADC_DIGITAL_VOLUME, 1, 96, 0, digital_tlv),
+SOC_SINGLE("ADC High Pass Filter Switch", WM8993_ADC_CTRL, 8, 1, 0),
+SOC_ENUM("ADC High Pass Filter Mode", adc_hpf),
+
+SOC_DOUBLE_R_TLV("Playback Volume", WM8993_LEFT_DAC_DIGITAL_VOLUME,
+ WM8993_RIGHT_DAC_DIGITAL_VOLUME, 1, 96, 0, digital_tlv),
+SOC_SINGLE_TLV("Playback Boost Volume", WM8993_AUDIO_INTERFACE_2, 10, 3, 0,
+ dac_boost_tlv),
+SOC_ENUM("DAC Deemphasis", dac_deemph),
+
+SOC_SINGLE_TLV("SPKL DAC Volume", WM8993_SPKMIXL_ATTENUATION,
+ 2, 1, 1, wm_hubs_spkmix_tlv),
+
+SOC_SINGLE_TLV("SPKR DAC Volume", WM8993_SPKMIXR_ATTENUATION,
+ 2, 1, 1, wm_hubs_spkmix_tlv),
+};
+
+static const struct snd_kcontrol_new wm8993_eq_controls[] = {
+SOC_SINGLE_TLV("EQ1 Volume", WM8993_EQ2, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ2 Volume", WM8993_EQ3, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ3 Volume", WM8993_EQ4, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ4 Volume", WM8993_EQ5, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ5 Volume", WM8993_EQ6, 0, 24, 0, eq_tlv),
+};
+
+static int clk_sys_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ return configure_clock(codec);
+
+ case SND_SOC_DAPM_POST_PMD:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * When used with DAC outputs only the WM8993 charge pump supports
+ * operation in class W mode, providing very low power consumption
+ * when used with digital sources. Enable and disable this mode
+ * automatically depending on the mixer configuration.
+ *
+ * Currently the only supported paths are the direct DAC->headphone
+ * paths (which provide minimum power consumption anyway).
+ */
+static int class_w_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ /* Turn it off if we're using the main output mixer */
+ if (ucontrol->value.integer.value[0] == 0) {
+ if (wm8993->class_w_users == 0) {
+ dev_dbg(codec->dev, "Disabling Class W\n");
+ snd_soc_update_bits(codec, WM8993_CLASS_W_0,
+ WM8993_CP_DYN_FREQ |
+ WM8993_CP_DYN_V,
+ 0);
+ }
+ wm8993->class_w_users++;
+ wm8993->hubs_data.class_w = true;
+ }
+
+ /* Implement the change */
+ ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
+
+ /* Enable it if we're using the direct DAC path */
+ if (ucontrol->value.integer.value[0] == 1) {
+ if (wm8993->class_w_users == 1) {
+ dev_dbg(codec->dev, "Enabling Class W\n");
+ snd_soc_update_bits(codec, WM8993_CLASS_W_0,
+ WM8993_CP_DYN_FREQ |
+ WM8993_CP_DYN_V,
+ WM8993_CP_DYN_FREQ |
+ WM8993_CP_DYN_V);
+ }
+ wm8993->class_w_users--;
+ wm8993->hubs_data.class_w = false;
+ }
+
+ dev_dbg(codec->dev, "Indirect DAC use count now %d\n",
+ wm8993->class_w_users);
+
+ return ret;
+}
+
+#define SOC_DAPM_ENUM_W(xname, xenum) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_enum_double, \
+ .get = snd_soc_dapm_get_enum_double, \
+ .put = class_w_put, \
+ .private_value = (unsigned long)&xenum }
+
+static const char *hp_mux_text[] = {
+ "Mixer",
+ "DAC",
+};
+
+static const struct soc_enum hpl_enum =
+ SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text);
+
+static const struct snd_kcontrol_new hpl_mux =
+ SOC_DAPM_ENUM_W("Left Headphone Mux", hpl_enum);
+
+static const struct soc_enum hpr_enum =
+ SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text);
+
+static const struct snd_kcontrol_new hpr_mux =
+ SOC_DAPM_ENUM_W("Right Headphone Mux", hpr_enum);
+
+static const struct snd_kcontrol_new left_speaker_mixer[] = {
+SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0),
+SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_speaker_mixer[] = {
+SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
+SOC_DAPM_SINGLE("IN1RP Switch", WM8993_SPEAKER_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 0, 1, 0),
+};
+
+static const char *aif_text[] = {
+ "Left", "Right"
+};
+
+static const struct soc_enum aifoutl_enum =
+ SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_1, 15, 2, aif_text);
+
+static const struct snd_kcontrol_new aifoutl_mux =
+ SOC_DAPM_ENUM("AIFOUTL Mux", aifoutl_enum);
+
+static const struct soc_enum aifoutr_enum =
+ SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_1, 14, 2, aif_text);
+
+static const struct snd_kcontrol_new aifoutr_mux =
+ SOC_DAPM_ENUM("AIFOUTR Mux", aifoutr_enum);
+
+static const struct soc_enum aifinl_enum =
+ SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 15, 2, aif_text);
+
+static const struct snd_kcontrol_new aifinl_mux =
+ SOC_DAPM_ENUM("AIFINL Mux", aifinl_enum);
+
+static const struct soc_enum aifinr_enum =
+ SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 14, 2, aif_text);
+
+static const struct snd_kcontrol_new aifinr_mux =
+ SOC_DAPM_ENUM("AIFINR Mux", aifinr_enum);
+
+static const char *sidetone_text[] = {
+ "None", "Left", "Right"
+};
+
+static const struct soc_enum sidetonel_enum =
+ SOC_ENUM_SINGLE(WM8993_DIGITAL_SIDE_TONE, 2, 3, sidetone_text);
+
+static const struct snd_kcontrol_new sidetonel_mux =
+ SOC_DAPM_ENUM("Left Sidetone", sidetonel_enum);
+
+static const struct soc_enum sidetoner_enum =
+ SOC_ENUM_SINGLE(WM8993_DIGITAL_SIDE_TONE, 0, 3, sidetone_text);
+
+static const struct snd_kcontrol_new sidetoner_mux =
+ SOC_DAPM_ENUM("Right Sidetone", sidetoner_enum);
+
+static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8993_BUS_CONTROL_1, 1, 0, clk_sys_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("TOCLK", WM8993_CLOCKING_1, 14, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8993_CLOCKING_3, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_ADC("ADCL", NULL, WM8993_POWER_MANAGEMENT_2, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, WM8993_POWER_MANAGEMENT_2, 0, 0),
+
+SND_SOC_DAPM_MUX("AIFOUTL Mux", SND_SOC_NOPM, 0, 0, &aifoutl_mux),
+SND_SOC_DAPM_MUX("AIFOUTR Mux", SND_SOC_NOPM, 0, 0, &aifoutr_mux),
+
+SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &aifinl_mux),
+SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &aifinr_mux),
+
+SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &sidetonel_mux),
+SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &sidetoner_mux),
+
+SND_SOC_DAPM_DAC("DACL", NULL, WM8993_POWER_MANAGEMENT_3, 1, 0),
+SND_SOC_DAPM_DAC("DACR", NULL, WM8993_POWER_MANAGEMENT_3, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
+SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
+
+SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0,
+ left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
+SND_SOC_DAPM_MIXER("SPKR", WM8993_POWER_MANAGEMENT_3, 9, 0,
+ right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
+
+};
+
+static const struct snd_soc_dapm_route routes[] = {
+ { "ADCL", NULL, "CLK_SYS" },
+ { "ADCL", NULL, "CLK_DSP" },
+ { "ADCR", NULL, "CLK_SYS" },
+ { "ADCR", NULL, "CLK_DSP" },
+
+ { "AIFOUTL Mux", "Left", "ADCL" },
+ { "AIFOUTL Mux", "Right", "ADCR" },
+ { "AIFOUTR Mux", "Left", "ADCL" },
+ { "AIFOUTR Mux", "Right", "ADCR" },
+
+ { "AIFOUTL", NULL, "AIFOUTL Mux" },
+ { "AIFOUTR", NULL, "AIFOUTR Mux" },
+
+ { "DACL Mux", "Left", "AIFINL" },
+ { "DACL Mux", "Right", "AIFINR" },
+ { "DACR Mux", "Left", "AIFINL" },
+ { "DACR Mux", "Right", "AIFINR" },
+
+ { "DACL Sidetone", "Left", "ADCL" },
+ { "DACL Sidetone", "Right", "ADCR" },
+ { "DACR Sidetone", "Left", "ADCL" },
+ { "DACR Sidetone", "Right", "ADCR" },
+
+ { "DACL", NULL, "CLK_SYS" },
+ { "DACL", NULL, "CLK_DSP" },
+ { "DACL", NULL, "DACL Mux" },
+ { "DACL", NULL, "DACL Sidetone" },
+ { "DACR", NULL, "CLK_SYS" },
+ { "DACR", NULL, "CLK_DSP" },
+ { "DACR", NULL, "DACR Mux" },
+ { "DACR", NULL, "DACR Sidetone" },
+
+ { "Left Output Mixer", "DAC Switch", "DACL" },
+
+ { "Right Output Mixer", "DAC Switch", "DACR" },
+
+ { "Left Output PGA", NULL, "CLK_SYS" },
+
+ { "Right Output PGA", NULL, "CLK_SYS" },
+
+ { "SPKL", "DAC Switch", "DACL" },
+ { "SPKL", NULL, "CLK_SYS" },
+
+ { "SPKR", "DAC Switch", "DACR" },
+ { "SPKR", NULL, "CLK_SYS" },
+
+ { "Left Headphone Mux", "DAC", "DACL" },
+ { "Right Headphone Mux", "DAC", "DACR" },
+};
+
+static void wm8993_cache_restore(struct snd_soc_codec *codec)
+{
+ u16 *cache = codec->reg_cache;
+ int i;
+
+ if (!codec->cache_sync)
+ return;
+
+ /* Reenable hardware writes */
+ codec->cache_only = 0;
+
+ /* Restore the register settings */
+ for (i = 1; i < WM8993_MAX_REGISTER; i++) {
+ if (cache[i] == wm8993_reg_defaults[i])
+ continue;
+ snd_soc_write(codec, i, cache[i]);
+ }
+
+ /* We're in sync again */
+ codec->cache_sync = 0;
+}
+
+static int wm8993_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID=2*40k */
+ snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+ WM8993_VMID_SEL_MASK, 0x2);
+ snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_2,
+ WM8993_TSHUT_ENA, WM8993_TSHUT_ENA);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8993->supplies),
+ wm8993->supplies);
+ if (ret != 0)
+ return ret;
+
+ wm8993_cache_restore(codec);
+
+ /* Tune DC servo configuration */
+ snd_soc_write(codec, 0x44, 3);
+ snd_soc_write(codec, 0x56, 3);
+ snd_soc_write(codec, 0x44, 0);
+
+ /* Bring up VMID with fast soft start */
+ snd_soc_update_bits(codec, WM8993_ANTIPOP2,
+ WM8993_STARTUP_BIAS_ENA |
+ WM8993_VMID_BUF_ENA |
+ WM8993_VMID_RAMP_MASK |
+ WM8993_BIAS_SRC,
+ WM8993_STARTUP_BIAS_ENA |
+ WM8993_VMID_BUF_ENA |
+ WM8993_VMID_RAMP_MASK |
+ WM8993_BIAS_SRC);
+
+ /* If either line output is single ended we
+ * need the VMID buffer */
+ if (!wm8993->pdata.lineout1_diff ||
+ !wm8993->pdata.lineout2_diff)
+ snd_soc_update_bits(codec, WM8993_ANTIPOP1,
+ WM8993_LINEOUT_VMID_BUF_ENA,
+ WM8993_LINEOUT_VMID_BUF_ENA);
+
+ /* VMID=2*40k */
+ snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+ WM8993_VMID_SEL_MASK |
+ WM8993_BIAS_ENA,
+ WM8993_BIAS_ENA | 0x2);
+ msleep(32);
+
+ /* Switch to normal bias */
+ snd_soc_update_bits(codec, WM8993_ANTIPOP2,
+ WM8993_BIAS_SRC |
+ WM8993_STARTUP_BIAS_ENA, 0);
+ }
+
+ /* VMID=2*240k */
+ snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+ WM8993_VMID_SEL_MASK, 0x4);
+
+ snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_2,
+ WM8993_TSHUT_ENA, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, WM8993_ANTIPOP1,
+ WM8993_LINEOUT_VMID_BUF_ENA, 0);
+
+ snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+ WM8993_VMID_SEL_MASK | WM8993_BIAS_ENA,
+ 0);
+
+ snd_soc_update_bits(codec, WM8993_ANTIPOP2,
+ WM8993_STARTUP_BIAS_ENA |
+ WM8993_VMID_BUF_ENA |
+ WM8993_VMID_RAMP_MASK |
+ WM8993_BIAS_SRC, 0);
+
+#ifdef CONFIG_REGULATOR
+ /* Post 2.6.34 we will be able to get a callback when
+ * the regulators are disabled which we can use but
+ * for now just assume that the power will be cut if
+ * the regulator API is in use.
+ */
+ codec->cache_sync = 1;
+#endif
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies),
+ wm8993->supplies);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static int wm8993_set_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+
+ switch (clk_id) {
+ case WM8993_SYSCLK_MCLK:
+ wm8993->mclk_rate = freq;
+ case WM8993_SYSCLK_FLL:
+ wm8993->sysclk_source = clk_id;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8993_set_dai_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+ unsigned int aif1 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_1);
+ unsigned int aif4 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_4);
+
+ aif1 &= ~(WM8993_BCLK_DIR | WM8993_AIF_BCLK_INV |
+ WM8993_AIF_LRCLK_INV | WM8993_AIF_FMT_MASK);
+ aif4 &= ~WM8993_LRCLK_DIR;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ wm8993->master = 0;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ aif4 |= WM8993_LRCLK_DIR;
+ wm8993->master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ aif1 |= WM8993_BCLK_DIR;
+ wm8993->master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif1 |= WM8993_BCLK_DIR;
+ aif4 |= WM8993_LRCLK_DIR;
+ wm8993->master = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_B:
+ aif1 |= WM8993_AIF_LRCLK_INV;
+ case SND_SOC_DAIFMT_DSP_A:
+ aif1 |= 0x18;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif1 |= 0x10;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif1 |= 0x8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8993_AIF_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif1 |= WM8993_AIF_BCLK_INV | WM8993_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8993_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif1 |= WM8993_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8993_AUDIO_INTERFACE_1, aif1);
+ snd_soc_write(codec, WM8993_AUDIO_INTERFACE_4, aif4);
+
+ return 0;
+}
+
+static int wm8993_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+ int ret, i, best, best_val, cur_val;
+ unsigned int clocking1, clocking3, aif1, aif4;
+
+ clocking1 = snd_soc_read(codec, WM8993_CLOCKING_1);
+ clocking1 &= ~WM8993_BCLK_DIV_MASK;
+
+ clocking3 = snd_soc_read(codec, WM8993_CLOCKING_3);
+ clocking3 &= ~(WM8993_CLK_SYS_RATE_MASK | WM8993_SAMPLE_RATE_MASK);
+
+ aif1 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_1);
+ aif1 &= ~WM8993_AIF_WL_MASK;
+
+ aif4 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_4);
+ aif4 &= ~WM8993_LRCLK_RATE_MASK;
+
+ /* What BCLK do we need? */
+ wm8993->fs = params_rate(params);
+ wm8993->bclk = 2 * wm8993->fs;
+ if (wm8993->tdm_slots) {
+ dev_dbg(codec->dev, "Configuring for %d %d bit TDM slots\n",
+ wm8993->tdm_slots, wm8993->tdm_width);
+ wm8993->bclk *= wm8993->tdm_width * wm8993->tdm_slots;
+ } else {
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ wm8993->bclk *= 16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ wm8993->bclk *= 20;
+ aif1 |= 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ wm8993->bclk *= 24;
+ aif1 |= 0x10;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ wm8993->bclk *= 32;
+ aif1 |= 0x18;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm8993->bclk);
+
+ ret = configure_clock(codec);
+ if (ret != 0)
+ return ret;
+
+ /* Select nearest CLK_SYS_RATE */
+ best = 0;
+ best_val = abs((wm8993->sysclk_rate / clk_sys_rates[0].ratio)
+ - wm8993->fs);
+ for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+ cur_val = abs((wm8993->sysclk_rate /
+ clk_sys_rates[i].ratio) - wm8993->fs);
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ dev_dbg(codec->dev, "Selected CLK_SYS_RATIO of %d\n",
+ clk_sys_rates[best].ratio);
+ clocking3 |= (clk_sys_rates[best].clk_sys_rate
+ << WM8993_CLK_SYS_RATE_SHIFT);
+
+ /* SAMPLE_RATE */
+ best = 0;
+ best_val = abs(wm8993->fs - sample_rates[0].rate);
+ for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+ /* Closest match */
+ cur_val = abs(wm8993->fs - sample_rates[i].rate);
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n",
+ sample_rates[best].rate);
+ clocking3 |= (sample_rates[best].sample_rate
+ << WM8993_SAMPLE_RATE_SHIFT);
+
+ /* BCLK_DIV */
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+ cur_val = ((wm8993->sysclk_rate * 10) / bclk_divs[i].div)
+ - wm8993->bclk;
+ if (cur_val < 0) /* Table is sorted */
+ break;
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ wm8993->bclk = (wm8993->sysclk_rate * 10) / bclk_divs[best].div;
+ dev_dbg(codec->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n",
+ bclk_divs[best].div, wm8993->bclk);
+ clocking1 |= bclk_divs[best].bclk_div << WM8993_BCLK_DIV_SHIFT;
+
+ /* LRCLK is a simple fraction of BCLK */
+ dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm8993->bclk / wm8993->fs);
+ aif4 |= wm8993->bclk / wm8993->fs;
+
+ snd_soc_write(codec, WM8993_CLOCKING_1, clocking1);
+ snd_soc_write(codec, WM8993_CLOCKING_3, clocking3);
+ snd_soc_write(codec, WM8993_AUDIO_INTERFACE_1, aif1);
+ snd_soc_write(codec, WM8993_AUDIO_INTERFACE_4, aif4);
+
+ /* ReTune Mobile? */
+ if (wm8993->pdata.num_retune_configs) {
+ u16 eq1 = snd_soc_read(codec, WM8993_EQ1);
+ struct wm8993_retune_mobile_setting *s;
+
+ best = 0;
+ best_val = abs(wm8993->pdata.retune_configs[0].rate
+ - wm8993->fs);
+ for (i = 0; i < wm8993->pdata.num_retune_configs; i++) {
+ cur_val = abs(wm8993->pdata.retune_configs[i].rate
+ - wm8993->fs);
+ if (cur_val < best_val) {
+ best_val = cur_val;
+ best = i;
+ }
+ }
+ s = &wm8993->pdata.retune_configs[best];
+
+ dev_dbg(codec->dev, "ReTune Mobile %s tuned for %dHz\n",
+ s->name, s->rate);
+
+ /* Disable EQ while we reconfigure */
+ snd_soc_update_bits(codec, WM8993_EQ1, WM8993_EQ_ENA, 0);
+
+ for (i = 1; i < ARRAY_SIZE(s->config); i++)
+ snd_soc_write(codec, WM8993_EQ1 + i, s->config[i]);
+
+ snd_soc_update_bits(codec, WM8993_EQ1, WM8993_EQ_ENA, eq1);
+ }
+
+ return 0;
+}
+
+static int wm8993_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ reg = snd_soc_read(codec, WM8993_DAC_CTRL);
+
+ if (mute)
+ reg |= WM8993_DAC_MUTE;
+ else
+ reg &= ~WM8993_DAC_MUTE;
+
+ snd_soc_write(codec, WM8993_DAC_CTRL, reg);
+
+ return 0;
+}
+
+static int wm8993_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+ int aif1 = 0;
+ int aif2 = 0;
+
+ /* Don't need to validate anything if we're turning off TDM */
+ if (slots == 0) {
+ wm8993->tdm_slots = 0;
+ goto out;
+ }
+
+ /* Note that we allow configurations we can't handle ourselves -
+ * for example, we can generate clocks for slots 2 and up even if
+ * we can't use those slots ourselves.
+ */
+ aif1 |= WM8993_AIFADC_TDM;
+ aif2 |= WM8993_AIFDAC_TDM;
+
+ switch (rx_mask) {
+ case 3:
+ break;
+ case 0xc:
+ aif1 |= WM8993_AIFADC_TDM_CHAN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+
+ switch (tx_mask) {
+ case 3:
+ break;
+ case 0xc:
+ aif2 |= WM8993_AIFDAC_TDM_CHAN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+out:
+ wm8993->tdm_width = slot_width;
+ wm8993->tdm_slots = slots / 2;
+
+ snd_soc_update_bits(codec, WM8993_AUDIO_INTERFACE_1,
+ WM8993_AIFADC_TDM | WM8993_AIFADC_TDM_CHAN, aif1);
+ snd_soc_update_bits(codec, WM8993_AUDIO_INTERFACE_2,
+ WM8993_AIFDAC_TDM | WM8993_AIFDAC_TDM_CHAN, aif2);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops wm8993_ops = {
+ .set_sysclk = wm8993_set_sysclk,
+ .set_fmt = wm8993_set_dai_fmt,
+ .hw_params = wm8993_hw_params,
+ .digital_mute = wm8993_digital_mute,
+ .set_pll = wm8993_set_fll,
+ .set_tdm_slot = wm8993_set_tdm_slot,
+};
+
+#define WM8993_RATES SNDRV_PCM_RATE_8000_48000
+
+#define WM8993_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver wm8993_dai = {
+ .name = "wm8993-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8993_RATES,
+ .formats = WM8993_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8993_RATES,
+ .formats = WM8993_FORMATS,
+ },
+ .ops = &wm8993_ops,
+ .symmetric_rates = 1,
+};
+
+static int wm8993_probe(struct snd_soc_codec *codec)
+{
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret, i, val;
+
+ wm8993->hubs_data.hp_startup_mode = 1;
+ wm8993->hubs_data.dcs_codes = -2;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8993->supplies); i++)
+ wm8993->supplies[i].supply = wm8993_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8993->supplies),
+ wm8993->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8993->supplies),
+ wm8993->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ val = snd_soc_read(codec, WM8993_SOFTWARE_RESET);
+ if (val != wm8993_reg_defaults[WM8993_SOFTWARE_RESET]) {
+ dev_err(codec->dev, "Invalid ID register value %x\n", val);
+ ret = -EINVAL;
+ goto err_enable;
+ }
+
+ ret = snd_soc_write(codec, WM8993_SOFTWARE_RESET, 0xffff);
+ if (ret != 0)
+ goto err_enable;
+
+ codec->cache_only = 1;
+
+ /* By default we're using the output mixers */
+ wm8993->class_w_users = 2;
+
+ /* Latch volume update bits and default ZC on */
+ snd_soc_update_bits(codec, WM8993_RIGHT_DAC_DIGITAL_VOLUME,
+ WM8993_DAC_VU, WM8993_DAC_VU);
+ snd_soc_update_bits(codec, WM8993_RIGHT_ADC_DIGITAL_VOLUME,
+ WM8993_ADC_VU, WM8993_ADC_VU);
+
+ /* Manualy manage the HPOUT sequencing for independent stereo
+ * control. */
+ snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
+ WM8993_HPOUT1_AUTO_PU, 0);
+
+ /* Use automatic clock configuration */
+ snd_soc_update_bits(codec, WM8993_CLOCKING_4, WM8993_SR_MODE, 0);
+
+ wm_hubs_handle_analogue_pdata(codec, wm8993->pdata.lineout1_diff,
+ wm8993->pdata.lineout2_diff,
+ wm8993->pdata.lineout1fb,
+ wm8993->pdata.lineout2fb,
+ wm8993->pdata.jd_scthr,
+ wm8993->pdata.jd_thr,
+ wm8993->pdata.micbias1_lvl,
+ wm8993->pdata.micbias2_lvl);
+
+ ret = wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ if (ret != 0)
+ goto err_enable;
+
+ snd_soc_add_controls(codec, wm8993_snd_controls,
+ ARRAY_SIZE(wm8993_snd_controls));
+ if (wm8993->pdata.num_retune_configs != 0) {
+ dev_dbg(codec->dev, "Using ReTune Mobile\n");
+ } else {
+ dev_dbg(codec->dev, "No ReTune Mobile, using normal EQ\n");
+ snd_soc_add_controls(codec, wm8993_eq_controls,
+ ARRAY_SIZE(wm8993_eq_controls));
+ }
+
+ snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,
+ ARRAY_SIZE(wm8993_dapm_widgets));
+ wm_hubs_add_analogue_controls(codec);
+
+ snd_soc_dapm_add_routes(dapm, routes, ARRAY_SIZE(routes));
+ wm_hubs_add_analogue_routes(codec, wm8993->pdata.lineout1_diff,
+ wm8993->pdata.lineout2_diff);
+
+ return 0;
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
+ return ret;
+}
+
+static int wm8993_remove(struct snd_soc_codec *codec)
+{
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+
+ wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ regulator_bulk_free(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm8993_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+ int fll_fout = wm8993->fll_fout;
+ int fll_fref = wm8993->fll_fref;
+ int ret;
+
+ /* Stop the FLL in an orderly fashion */
+ ret = _wm8993_set_fll(codec, 0, 0, 0, 0);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to stop FLL\n");
+ return ret;
+ }
+
+ wm8993->fll_fout = fll_fout;
+ wm8993->fll_fref = fll_fref;
+
+ wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8993_resume(struct snd_soc_codec *codec)
+{
+ struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Restart the FLL? */
+ if (wm8993->fll_fout) {
+ int fll_fout = wm8993->fll_fout;
+ int fll_fref = wm8993->fll_fref;
+
+ wm8993->fll_fref = 0;
+ wm8993->fll_fout = 0;
+
+ ret = _wm8993_set_fll(codec, 0, wm8993->fll_src,
+ fll_fref, fll_fout);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to restart FLL\n");
+ }
+
+ return 0;
+}
+#else
+#define wm8993_suspend NULL
+#define wm8993_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8993 = {
+ .probe = wm8993_probe,
+ .remove = wm8993_remove,
+ .suspend = wm8993_suspend,
+ .resume = wm8993_resume,
+ .set_bias_level = wm8993_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8993_reg_defaults),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8993_reg_defaults,
+ .volatile_register = wm8993_volatile,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8993_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8993_priv *wm8993;
+ int ret;
+
+ wm8993 = kzalloc(sizeof(struct wm8993_priv), GFP_KERNEL);
+ if (wm8993 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm8993);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8993, &wm8993_dai, 1);
+ if (ret < 0)
+ kfree(wm8993);
+ return ret;
+}
+
+static __devexit int wm8993_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8993_i2c_id[] = {
+ { "wm8993", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8993_i2c_id);
+
+static struct i2c_driver wm8993_i2c_driver = {
+ .driver = {
+ .name = "wm8993-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8993_i2c_probe,
+ .remove = __devexit_p(wm8993_i2c_remove),
+ .id_table = wm8993_i2c_id,
+};
+#endif
+
+static int __init wm8993_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8993_i2c_driver);
+ if (ret != 0) {
+ pr_err("WM8993: Unable to register I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8993_modinit);
+
+static void __exit wm8993_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8993_i2c_driver);
+#endif
+}
+module_exit(wm8993_exit);
+
+
+MODULE_DESCRIPTION("ASoC WM8993 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8993.h b/sound/soc/codecs/wm8993.h
new file mode 100644
index 00000000..2184617b
--- /dev/null
+++ b/sound/soc/codecs/wm8993.h
@@ -0,0 +1,2129 @@
+#ifndef WM8993_H
+#define WM8993_H
+
+#define WM8993_SYSCLK_MCLK 1
+#define WM8993_SYSCLK_FLL 2
+
+#define WM8993_FLL_MCLK 1
+#define WM8993_FLL_BCLK 2
+#define WM8993_FLL_LRCLK 3
+
+/*
+ * Register values.
+ */
+#define WM8993_SOFTWARE_RESET 0x00
+#define WM8993_POWER_MANAGEMENT_1 0x01
+#define WM8993_POWER_MANAGEMENT_2 0x02
+#define WM8993_POWER_MANAGEMENT_3 0x03
+#define WM8993_AUDIO_INTERFACE_1 0x04
+#define WM8993_AUDIO_INTERFACE_2 0x05
+#define WM8993_CLOCKING_1 0x06
+#define WM8993_CLOCKING_2 0x07
+#define WM8993_AUDIO_INTERFACE_3 0x08
+#define WM8993_AUDIO_INTERFACE_4 0x09
+#define WM8993_DAC_CTRL 0x0A
+#define WM8993_LEFT_DAC_DIGITAL_VOLUME 0x0B
+#define WM8993_RIGHT_DAC_DIGITAL_VOLUME 0x0C
+#define WM8993_DIGITAL_SIDE_TONE 0x0D
+#define WM8993_ADC_CTRL 0x0E
+#define WM8993_LEFT_ADC_DIGITAL_VOLUME 0x0F
+#define WM8993_RIGHT_ADC_DIGITAL_VOLUME 0x10
+#define WM8993_GPIO_CTRL_1 0x12
+#define WM8993_GPIO1 0x13
+#define WM8993_IRQ_DEBOUNCE 0x14
+#define WM8993_GPIOCTRL_2 0x16
+#define WM8993_GPIO_POL 0x17
+#define WM8993_LEFT_LINE_INPUT_1_2_VOLUME 0x18
+#define WM8993_LEFT_LINE_INPUT_3_4_VOLUME 0x19
+#define WM8993_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A
+#define WM8993_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B
+#define WM8993_LEFT_OUTPUT_VOLUME 0x1C
+#define WM8993_RIGHT_OUTPUT_VOLUME 0x1D
+#define WM8993_LINE_OUTPUTS_VOLUME 0x1E
+#define WM8993_HPOUT2_VOLUME 0x1F
+#define WM8993_LEFT_OPGA_VOLUME 0x20
+#define WM8993_RIGHT_OPGA_VOLUME 0x21
+#define WM8993_SPKMIXL_ATTENUATION 0x22
+#define WM8993_SPKMIXR_ATTENUATION 0x23
+#define WM8993_SPKOUT_MIXERS 0x24
+#define WM8993_SPKOUT_BOOST 0x25
+#define WM8993_SPEAKER_VOLUME_LEFT 0x26
+#define WM8993_SPEAKER_VOLUME_RIGHT 0x27
+#define WM8993_INPUT_MIXER2 0x28
+#define WM8993_INPUT_MIXER3 0x29
+#define WM8993_INPUT_MIXER4 0x2A
+#define WM8993_INPUT_MIXER5 0x2B
+#define WM8993_INPUT_MIXER6 0x2C
+#define WM8993_OUTPUT_MIXER1 0x2D
+#define WM8993_OUTPUT_MIXER2 0x2E
+#define WM8993_OUTPUT_MIXER3 0x2F
+#define WM8993_OUTPUT_MIXER4 0x30
+#define WM8993_OUTPUT_MIXER5 0x31
+#define WM8993_OUTPUT_MIXER6 0x32
+#define WM8993_HPOUT2_MIXER 0x33
+#define WM8993_LINE_MIXER1 0x34
+#define WM8993_LINE_MIXER2 0x35
+#define WM8993_SPEAKER_MIXER 0x36
+#define WM8993_ADDITIONAL_CONTROL 0x37
+#define WM8993_ANTIPOP1 0x38
+#define WM8993_ANTIPOP2 0x39
+#define WM8993_MICBIAS 0x3A
+#define WM8993_FLL_CONTROL_1 0x3C
+#define WM8993_FLL_CONTROL_2 0x3D
+#define WM8993_FLL_CONTROL_3 0x3E
+#define WM8993_FLL_CONTROL_4 0x3F
+#define WM8993_FLL_CONTROL_5 0x40
+#define WM8993_CLOCKING_3 0x41
+#define WM8993_CLOCKING_4 0x42
+#define WM8993_MW_SLAVE_CONTROL 0x43
+#define WM8993_BUS_CONTROL_1 0x45
+#define WM8993_WRITE_SEQUENCER_0 0x46
+#define WM8993_WRITE_SEQUENCER_1 0x47
+#define WM8993_WRITE_SEQUENCER_2 0x48
+#define WM8993_WRITE_SEQUENCER_3 0x49
+#define WM8993_WRITE_SEQUENCER_4 0x4A
+#define WM8993_WRITE_SEQUENCER_5 0x4B
+#define WM8993_CHARGE_PUMP_1 0x4C
+#define WM8993_CLASS_W_0 0x51
+#define WM8993_DC_SERVO_0 0x54
+#define WM8993_DC_SERVO_1 0x55
+#define WM8993_DC_SERVO_3 0x57
+#define WM8993_DC_SERVO_READBACK_0 0x58
+#define WM8993_DC_SERVO_READBACK_1 0x59
+#define WM8993_DC_SERVO_READBACK_2 0x5A
+#define WM8993_ANALOGUE_HP_0 0x60
+#define WM8993_EQ1 0x62
+#define WM8993_EQ2 0x63
+#define WM8993_EQ3 0x64
+#define WM8993_EQ4 0x65
+#define WM8993_EQ5 0x66
+#define WM8993_EQ6 0x67
+#define WM8993_EQ7 0x68
+#define WM8993_EQ8 0x69
+#define WM8993_EQ9 0x6A
+#define WM8993_EQ10 0x6B
+#define WM8993_EQ11 0x6C
+#define WM8993_EQ12 0x6D
+#define WM8993_EQ13 0x6E
+#define WM8993_EQ14 0x6F
+#define WM8993_EQ15 0x70
+#define WM8993_EQ16 0x71
+#define WM8993_EQ17 0x72
+#define WM8993_EQ18 0x73
+#define WM8993_EQ19 0x74
+#define WM8993_EQ20 0x75
+#define WM8993_EQ21 0x76
+#define WM8993_EQ22 0x77
+#define WM8993_EQ23 0x78
+#define WM8993_EQ24 0x79
+#define WM8993_DIGITAL_PULLS 0x7A
+#define WM8993_DRC_CONTROL_1 0x7B
+#define WM8993_DRC_CONTROL_2 0x7C
+#define WM8993_DRC_CONTROL_3 0x7D
+#define WM8993_DRC_CONTROL_4 0x7E
+
+#define WM8993_REGISTER_COUNT 0x7F
+#define WM8993_MAX_REGISTER 0x7E
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Software Reset
+ */
+#define WM8993_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */
+#define WM8993_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */
+#define WM8993_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM8993_SPKOUTR_ENA 0x2000 /* SPKOUTR_ENA */
+#define WM8993_SPKOUTR_ENA_MASK 0x2000 /* SPKOUTR_ENA */
+#define WM8993_SPKOUTR_ENA_SHIFT 13 /* SPKOUTR_ENA */
+#define WM8993_SPKOUTR_ENA_WIDTH 1 /* SPKOUTR_ENA */
+#define WM8993_SPKOUTL_ENA 0x1000 /* SPKOUTL_ENA */
+#define WM8993_SPKOUTL_ENA_MASK 0x1000 /* SPKOUTL_ENA */
+#define WM8993_SPKOUTL_ENA_SHIFT 12 /* SPKOUTL_ENA */
+#define WM8993_SPKOUTL_ENA_WIDTH 1 /* SPKOUTL_ENA */
+#define WM8993_HPOUT2_ENA 0x0800 /* HPOUT2_ENA */
+#define WM8993_HPOUT2_ENA_MASK 0x0800 /* HPOUT2_ENA */
+#define WM8993_HPOUT2_ENA_SHIFT 11 /* HPOUT2_ENA */
+#define WM8993_HPOUT2_ENA_WIDTH 1 /* HPOUT2_ENA */
+#define WM8993_HPOUT1L_ENA 0x0200 /* HPOUT1L_ENA */
+#define WM8993_HPOUT1L_ENA_MASK 0x0200 /* HPOUT1L_ENA */
+#define WM8993_HPOUT1L_ENA_SHIFT 9 /* HPOUT1L_ENA */
+#define WM8993_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */
+#define WM8993_HPOUT1R_ENA 0x0100 /* HPOUT1R_ENA */
+#define WM8993_HPOUT1R_ENA_MASK 0x0100 /* HPOUT1R_ENA */
+#define WM8993_HPOUT1R_ENA_SHIFT 8 /* HPOUT1R_ENA */
+#define WM8993_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */
+#define WM8993_MICB2_ENA 0x0020 /* MICB2_ENA */
+#define WM8993_MICB2_ENA_MASK 0x0020 /* MICB2_ENA */
+#define WM8993_MICB2_ENA_SHIFT 5 /* MICB2_ENA */
+#define WM8993_MICB2_ENA_WIDTH 1 /* MICB2_ENA */
+#define WM8993_MICB1_ENA 0x0010 /* MICB1_ENA */
+#define WM8993_MICB1_ENA_MASK 0x0010 /* MICB1_ENA */
+#define WM8993_MICB1_ENA_SHIFT 4 /* MICB1_ENA */
+#define WM8993_MICB1_ENA_WIDTH 1 /* MICB1_ENA */
+#define WM8993_VMID_SEL_MASK 0x0006 /* VMID_SEL - [2:1] */
+#define WM8993_VMID_SEL_SHIFT 1 /* VMID_SEL - [2:1] */
+#define WM8993_VMID_SEL_WIDTH 2 /* VMID_SEL - [2:1] */
+#define WM8993_BIAS_ENA 0x0001 /* BIAS_ENA */
+#define WM8993_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */
+#define WM8993_BIAS_ENA_SHIFT 0 /* BIAS_ENA */
+#define WM8993_BIAS_ENA_WIDTH 1 /* BIAS_ENA */
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM8993_TSHUT_ENA 0x4000 /* TSHUT_ENA */
+#define WM8993_TSHUT_ENA_MASK 0x4000 /* TSHUT_ENA */
+#define WM8993_TSHUT_ENA_SHIFT 14 /* TSHUT_ENA */
+#define WM8993_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */
+#define WM8993_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */
+#define WM8993_TSHUT_OPDIS_MASK 0x2000 /* TSHUT_OPDIS */
+#define WM8993_TSHUT_OPDIS_SHIFT 13 /* TSHUT_OPDIS */
+#define WM8993_TSHUT_OPDIS_WIDTH 1 /* TSHUT_OPDIS */
+#define WM8993_OPCLK_ENA 0x0800 /* OPCLK_ENA */
+#define WM8993_OPCLK_ENA_MASK 0x0800 /* OPCLK_ENA */
+#define WM8993_OPCLK_ENA_SHIFT 11 /* OPCLK_ENA */
+#define WM8993_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */
+#define WM8993_MIXINL_ENA 0x0200 /* MIXINL_ENA */
+#define WM8993_MIXINL_ENA_MASK 0x0200 /* MIXINL_ENA */
+#define WM8993_MIXINL_ENA_SHIFT 9 /* MIXINL_ENA */
+#define WM8993_MIXINL_ENA_WIDTH 1 /* MIXINL_ENA */
+#define WM8993_MIXINR_ENA 0x0100 /* MIXINR_ENA */
+#define WM8993_MIXINR_ENA_MASK 0x0100 /* MIXINR_ENA */
+#define WM8993_MIXINR_ENA_SHIFT 8 /* MIXINR_ENA */
+#define WM8993_MIXINR_ENA_WIDTH 1 /* MIXINR_ENA */
+#define WM8993_IN2L_ENA 0x0080 /* IN2L_ENA */
+#define WM8993_IN2L_ENA_MASK 0x0080 /* IN2L_ENA */
+#define WM8993_IN2L_ENA_SHIFT 7 /* IN2L_ENA */
+#define WM8993_IN2L_ENA_WIDTH 1 /* IN2L_ENA */
+#define WM8993_IN1L_ENA 0x0040 /* IN1L_ENA */
+#define WM8993_IN1L_ENA_MASK 0x0040 /* IN1L_ENA */
+#define WM8993_IN1L_ENA_SHIFT 6 /* IN1L_ENA */
+#define WM8993_IN1L_ENA_WIDTH 1 /* IN1L_ENA */
+#define WM8993_IN2R_ENA 0x0020 /* IN2R_ENA */
+#define WM8993_IN2R_ENA_MASK 0x0020 /* IN2R_ENA */
+#define WM8993_IN2R_ENA_SHIFT 5 /* IN2R_ENA */
+#define WM8993_IN2R_ENA_WIDTH 1 /* IN2R_ENA */
+#define WM8993_IN1R_ENA 0x0010 /* IN1R_ENA */
+#define WM8993_IN1R_ENA_MASK 0x0010 /* IN1R_ENA */
+#define WM8993_IN1R_ENA_SHIFT 4 /* IN1R_ENA */
+#define WM8993_IN1R_ENA_WIDTH 1 /* IN1R_ENA */
+#define WM8993_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8993_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */
+#define WM8993_ADCL_ENA_SHIFT 1 /* ADCL_ENA */
+#define WM8993_ADCL_ENA_WIDTH 1 /* ADCL_ENA */
+#define WM8993_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8993_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */
+#define WM8993_ADCR_ENA_SHIFT 0 /* ADCR_ENA */
+#define WM8993_ADCR_ENA_WIDTH 1 /* ADCR_ENA */
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM8993_LINEOUT1N_ENA 0x2000 /* LINEOUT1N_ENA */
+#define WM8993_LINEOUT1N_ENA_MASK 0x2000 /* LINEOUT1N_ENA */
+#define WM8993_LINEOUT1N_ENA_SHIFT 13 /* LINEOUT1N_ENA */
+#define WM8993_LINEOUT1N_ENA_WIDTH 1 /* LINEOUT1N_ENA */
+#define WM8993_LINEOUT1P_ENA 0x1000 /* LINEOUT1P_ENA */
+#define WM8993_LINEOUT1P_ENA_MASK 0x1000 /* LINEOUT1P_ENA */
+#define WM8993_LINEOUT1P_ENA_SHIFT 12 /* LINEOUT1P_ENA */
+#define WM8993_LINEOUT1P_ENA_WIDTH 1 /* LINEOUT1P_ENA */
+#define WM8993_LINEOUT2N_ENA 0x0800 /* LINEOUT2N_ENA */
+#define WM8993_LINEOUT2N_ENA_MASK 0x0800 /* LINEOUT2N_ENA */
+#define WM8993_LINEOUT2N_ENA_SHIFT 11 /* LINEOUT2N_ENA */
+#define WM8993_LINEOUT2N_ENA_WIDTH 1 /* LINEOUT2N_ENA */
+#define WM8993_LINEOUT2P_ENA 0x0400 /* LINEOUT2P_ENA */
+#define WM8993_LINEOUT2P_ENA_MASK 0x0400 /* LINEOUT2P_ENA */
+#define WM8993_LINEOUT2P_ENA_SHIFT 10 /* LINEOUT2P_ENA */
+#define WM8993_LINEOUT2P_ENA_WIDTH 1 /* LINEOUT2P_ENA */
+#define WM8993_SPKRVOL_ENA 0x0200 /* SPKRVOL_ENA */
+#define WM8993_SPKRVOL_ENA_MASK 0x0200 /* SPKRVOL_ENA */
+#define WM8993_SPKRVOL_ENA_SHIFT 9 /* SPKRVOL_ENA */
+#define WM8993_SPKRVOL_ENA_WIDTH 1 /* SPKRVOL_ENA */
+#define WM8993_SPKLVOL_ENA 0x0100 /* SPKLVOL_ENA */
+#define WM8993_SPKLVOL_ENA_MASK 0x0100 /* SPKLVOL_ENA */
+#define WM8993_SPKLVOL_ENA_SHIFT 8 /* SPKLVOL_ENA */
+#define WM8993_SPKLVOL_ENA_WIDTH 1 /* SPKLVOL_ENA */
+#define WM8993_MIXOUTLVOL_ENA 0x0080 /* MIXOUTLVOL_ENA */
+#define WM8993_MIXOUTLVOL_ENA_MASK 0x0080 /* MIXOUTLVOL_ENA */
+#define WM8993_MIXOUTLVOL_ENA_SHIFT 7 /* MIXOUTLVOL_ENA */
+#define WM8993_MIXOUTLVOL_ENA_WIDTH 1 /* MIXOUTLVOL_ENA */
+#define WM8993_MIXOUTRVOL_ENA 0x0040 /* MIXOUTRVOL_ENA */
+#define WM8993_MIXOUTRVOL_ENA_MASK 0x0040 /* MIXOUTRVOL_ENA */
+#define WM8993_MIXOUTRVOL_ENA_SHIFT 6 /* MIXOUTRVOL_ENA */
+#define WM8993_MIXOUTRVOL_ENA_WIDTH 1 /* MIXOUTRVOL_ENA */
+#define WM8993_MIXOUTL_ENA 0x0020 /* MIXOUTL_ENA */
+#define WM8993_MIXOUTL_ENA_MASK 0x0020 /* MIXOUTL_ENA */
+#define WM8993_MIXOUTL_ENA_SHIFT 5 /* MIXOUTL_ENA */
+#define WM8993_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */
+#define WM8993_MIXOUTR_ENA 0x0010 /* MIXOUTR_ENA */
+#define WM8993_MIXOUTR_ENA_MASK 0x0010 /* MIXOUTR_ENA */
+#define WM8993_MIXOUTR_ENA_SHIFT 4 /* MIXOUTR_ENA */
+#define WM8993_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */
+#define WM8993_DACL_ENA 0x0002 /* DACL_ENA */
+#define WM8993_DACL_ENA_MASK 0x0002 /* DACL_ENA */
+#define WM8993_DACL_ENA_SHIFT 1 /* DACL_ENA */
+#define WM8993_DACL_ENA_WIDTH 1 /* DACL_ENA */
+#define WM8993_DACR_ENA 0x0001 /* DACR_ENA */
+#define WM8993_DACR_ENA_MASK 0x0001 /* DACR_ENA */
+#define WM8993_DACR_ENA_SHIFT 0 /* DACR_ENA */
+#define WM8993_DACR_ENA_WIDTH 1 /* DACR_ENA */
+
+/*
+ * R4 (0x04) - Audio Interface (1)
+ */
+#define WM8993_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */
+#define WM8993_AIFADCL_SRC_MASK 0x8000 /* AIFADCL_SRC */
+#define WM8993_AIFADCL_SRC_SHIFT 15 /* AIFADCL_SRC */
+#define WM8993_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */
+#define WM8993_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */
+#define WM8993_AIFADCR_SRC_MASK 0x4000 /* AIFADCR_SRC */
+#define WM8993_AIFADCR_SRC_SHIFT 14 /* AIFADCR_SRC */
+#define WM8993_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */
+#define WM8993_AIFADC_TDM 0x2000 /* AIFADC_TDM */
+#define WM8993_AIFADC_TDM_MASK 0x2000 /* AIFADC_TDM */
+#define WM8993_AIFADC_TDM_SHIFT 13 /* AIFADC_TDM */
+#define WM8993_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */
+#define WM8993_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */
+#define WM8993_AIFADC_TDM_CHAN_MASK 0x1000 /* AIFADC_TDM_CHAN */
+#define WM8993_AIFADC_TDM_CHAN_SHIFT 12 /* AIFADC_TDM_CHAN */
+#define WM8993_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */
+#define WM8993_BCLK_DIR 0x0200 /* BCLK_DIR */
+#define WM8993_BCLK_DIR_MASK 0x0200 /* BCLK_DIR */
+#define WM8993_BCLK_DIR_SHIFT 9 /* BCLK_DIR */
+#define WM8993_BCLK_DIR_WIDTH 1 /* BCLK_DIR */
+#define WM8993_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */
+#define WM8993_AIF_BCLK_INV_MASK 0x0100 /* AIF_BCLK_INV */
+#define WM8993_AIF_BCLK_INV_SHIFT 8 /* AIF_BCLK_INV */
+#define WM8993_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */
+#define WM8993_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */
+#define WM8993_AIF_LRCLK_INV_MASK 0x0080 /* AIF_LRCLK_INV */
+#define WM8993_AIF_LRCLK_INV_SHIFT 7 /* AIF_LRCLK_INV */
+#define WM8993_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */
+#define WM8993_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */
+#define WM8993_AIF_WL_SHIFT 5 /* AIF_WL - [6:5] */
+#define WM8993_AIF_WL_WIDTH 2 /* AIF_WL - [6:5] */
+#define WM8993_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */
+#define WM8993_AIF_FMT_SHIFT 3 /* AIF_FMT - [4:3] */
+#define WM8993_AIF_FMT_WIDTH 2 /* AIF_FMT - [4:3] */
+
+/*
+ * R5 (0x05) - Audio Interface (2)
+ */
+#define WM8993_AIFDACL_SRC 0x8000 /* AIFDACL_SRC */
+#define WM8993_AIFDACL_SRC_MASK 0x8000 /* AIFDACL_SRC */
+#define WM8993_AIFDACL_SRC_SHIFT 15 /* AIFDACL_SRC */
+#define WM8993_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */
+#define WM8993_AIFDACR_SRC 0x4000 /* AIFDACR_SRC */
+#define WM8993_AIFDACR_SRC_MASK 0x4000 /* AIFDACR_SRC */
+#define WM8993_AIFDACR_SRC_SHIFT 14 /* AIFDACR_SRC */
+#define WM8993_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */
+#define WM8993_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8993_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */
+#define WM8993_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */
+#define WM8993_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */
+#define WM8993_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8993_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8993_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */
+#define WM8993_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */
+#define WM8993_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST - [11:10] */
+#define WM8993_DAC_BOOST_SHIFT 10 /* DAC_BOOST - [11:10] */
+#define WM8993_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [11:10] */
+#define WM8993_DAC_COMP 0x0010 /* DAC_COMP */
+#define WM8993_DAC_COMP_MASK 0x0010 /* DAC_COMP */
+#define WM8993_DAC_COMP_SHIFT 4 /* DAC_COMP */
+#define WM8993_DAC_COMP_WIDTH 1 /* DAC_COMP */
+#define WM8993_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */
+#define WM8993_DAC_COMPMODE_MASK 0x0008 /* DAC_COMPMODE */
+#define WM8993_DAC_COMPMODE_SHIFT 3 /* DAC_COMPMODE */
+#define WM8993_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */
+#define WM8993_ADC_COMP 0x0004 /* ADC_COMP */
+#define WM8993_ADC_COMP_MASK 0x0004 /* ADC_COMP */
+#define WM8993_ADC_COMP_SHIFT 2 /* ADC_COMP */
+#define WM8993_ADC_COMP_WIDTH 1 /* ADC_COMP */
+#define WM8993_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */
+#define WM8993_ADC_COMPMODE_MASK 0x0002 /* ADC_COMPMODE */
+#define WM8993_ADC_COMPMODE_SHIFT 1 /* ADC_COMPMODE */
+#define WM8993_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */
+#define WM8993_LOOPBACK 0x0001 /* LOOPBACK */
+#define WM8993_LOOPBACK_MASK 0x0001 /* LOOPBACK */
+#define WM8993_LOOPBACK_SHIFT 0 /* LOOPBACK */
+#define WM8993_LOOPBACK_WIDTH 1 /* LOOPBACK */
+
+/*
+ * R6 (0x06) - Clocking 1
+ */
+#define WM8993_TOCLK_RATE 0x8000 /* TOCLK_RATE */
+#define WM8993_TOCLK_RATE_MASK 0x8000 /* TOCLK_RATE */
+#define WM8993_TOCLK_RATE_SHIFT 15 /* TOCLK_RATE */
+#define WM8993_TOCLK_RATE_WIDTH 1 /* TOCLK_RATE */
+#define WM8993_TOCLK_ENA 0x4000 /* TOCLK_ENA */
+#define WM8993_TOCLK_ENA_MASK 0x4000 /* TOCLK_ENA */
+#define WM8993_TOCLK_ENA_SHIFT 14 /* TOCLK_ENA */
+#define WM8993_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */
+#define WM8993_OPCLK_DIV_MASK 0x1E00 /* OPCLK_DIV - [12:9] */
+#define WM8993_OPCLK_DIV_SHIFT 9 /* OPCLK_DIV - [12:9] */
+#define WM8993_OPCLK_DIV_WIDTH 4 /* OPCLK_DIV - [12:9] */
+#define WM8993_DCLK_DIV_MASK 0x01C0 /* DCLK_DIV - [8:6] */
+#define WM8993_DCLK_DIV_SHIFT 6 /* DCLK_DIV - [8:6] */
+#define WM8993_DCLK_DIV_WIDTH 3 /* DCLK_DIV - [8:6] */
+#define WM8993_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */
+#define WM8993_BCLK_DIV_SHIFT 1 /* BCLK_DIV - [4:1] */
+#define WM8993_BCLK_DIV_WIDTH 4 /* BCLK_DIV - [4:1] */
+
+/*
+ * R7 (0x07) - Clocking 2
+ */
+#define WM8993_MCLK_SRC 0x8000 /* MCLK_SRC */
+#define WM8993_MCLK_SRC_MASK 0x8000 /* MCLK_SRC */
+#define WM8993_MCLK_SRC_SHIFT 15 /* MCLK_SRC */
+#define WM8993_MCLK_SRC_WIDTH 1 /* MCLK_SRC */
+#define WM8993_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */
+#define WM8993_SYSCLK_SRC_MASK 0x4000 /* SYSCLK_SRC */
+#define WM8993_SYSCLK_SRC_SHIFT 14 /* SYSCLK_SRC */
+#define WM8993_SYSCLK_SRC_WIDTH 1 /* SYSCLK_SRC */
+#define WM8993_MCLK_DIV 0x1000 /* MCLK_DIV */
+#define WM8993_MCLK_DIV_MASK 0x1000 /* MCLK_DIV */
+#define WM8993_MCLK_DIV_SHIFT 12 /* MCLK_DIV */
+#define WM8993_MCLK_DIV_WIDTH 1 /* MCLK_DIV */
+#define WM8993_MCLK_INV 0x0400 /* MCLK_INV */
+#define WM8993_MCLK_INV_MASK 0x0400 /* MCLK_INV */
+#define WM8993_MCLK_INV_SHIFT 10 /* MCLK_INV */
+#define WM8993_MCLK_INV_WIDTH 1 /* MCLK_INV */
+#define WM8993_ADC_DIV_MASK 0x00E0 /* ADC_DIV - [7:5] */
+#define WM8993_ADC_DIV_SHIFT 5 /* ADC_DIV - [7:5] */
+#define WM8993_ADC_DIV_WIDTH 3 /* ADC_DIV - [7:5] */
+#define WM8993_DAC_DIV_MASK 0x001C /* DAC_DIV - [4:2] */
+#define WM8993_DAC_DIV_SHIFT 2 /* DAC_DIV - [4:2] */
+#define WM8993_DAC_DIV_WIDTH 3 /* DAC_DIV - [4:2] */
+
+/*
+ * R8 (0x08) - Audio Interface (3)
+ */
+#define WM8993_AIF_MSTR1 0x8000 /* AIF_MSTR1 */
+#define WM8993_AIF_MSTR1_MASK 0x8000 /* AIF_MSTR1 */
+#define WM8993_AIF_MSTR1_SHIFT 15 /* AIF_MSTR1 */
+#define WM8993_AIF_MSTR1_WIDTH 1 /* AIF_MSTR1 */
+
+/*
+ * R9 (0x09) - Audio Interface (4)
+ */
+#define WM8993_AIF_TRIS 0x2000 /* AIF_TRIS */
+#define WM8993_AIF_TRIS_MASK 0x2000 /* AIF_TRIS */
+#define WM8993_AIF_TRIS_SHIFT 13 /* AIF_TRIS */
+#define WM8993_AIF_TRIS_WIDTH 1 /* AIF_TRIS */
+#define WM8993_LRCLK_DIR 0x0800 /* LRCLK_DIR */
+#define WM8993_LRCLK_DIR_MASK 0x0800 /* LRCLK_DIR */
+#define WM8993_LRCLK_DIR_SHIFT 11 /* LRCLK_DIR */
+#define WM8993_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */
+#define WM8993_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */
+#define WM8993_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */
+#define WM8993_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */
+
+/*
+ * R10 (0x0A) - DAC CTRL
+ */
+#define WM8993_DAC_OSR128 0x2000 /* DAC_OSR128 */
+#define WM8993_DAC_OSR128_MASK 0x2000 /* DAC_OSR128 */
+#define WM8993_DAC_OSR128_SHIFT 13 /* DAC_OSR128 */
+#define WM8993_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */
+#define WM8993_DAC_MONO 0x0200 /* DAC_MONO */
+#define WM8993_DAC_MONO_MASK 0x0200 /* DAC_MONO */
+#define WM8993_DAC_MONO_SHIFT 9 /* DAC_MONO */
+#define WM8993_DAC_MONO_WIDTH 1 /* DAC_MONO */
+#define WM8993_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */
+#define WM8993_DAC_SB_FILT_MASK 0x0100 /* DAC_SB_FILT */
+#define WM8993_DAC_SB_FILT_SHIFT 8 /* DAC_SB_FILT */
+#define WM8993_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */
+#define WM8993_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */
+#define WM8993_DAC_MUTERATE_MASK 0x0080 /* DAC_MUTERATE */
+#define WM8993_DAC_MUTERATE_SHIFT 7 /* DAC_MUTERATE */
+#define WM8993_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
+#define WM8993_DAC_UNMUTE_RAMP 0x0040 /* DAC_UNMUTE_RAMP */
+#define WM8993_DAC_UNMUTE_RAMP_MASK 0x0040 /* DAC_UNMUTE_RAMP */
+#define WM8993_DAC_UNMUTE_RAMP_SHIFT 6 /* DAC_UNMUTE_RAMP */
+#define WM8993_DAC_UNMUTE_RAMP_WIDTH 1 /* DAC_UNMUTE_RAMP */
+#define WM8993_DEEMPH_MASK 0x0030 /* DEEMPH - [5:4] */
+#define WM8993_DEEMPH_SHIFT 4 /* DEEMPH - [5:4] */
+#define WM8993_DEEMPH_WIDTH 2 /* DEEMPH - [5:4] */
+#define WM8993_DAC_MUTE 0x0004 /* DAC_MUTE */
+#define WM8993_DAC_MUTE_MASK 0x0004 /* DAC_MUTE */
+#define WM8993_DAC_MUTE_SHIFT 2 /* DAC_MUTE */
+#define WM8993_DAC_MUTE_WIDTH 1 /* DAC_MUTE */
+#define WM8993_DACL_DATINV 0x0002 /* DACL_DATINV */
+#define WM8993_DACL_DATINV_MASK 0x0002 /* DACL_DATINV */
+#define WM8993_DACL_DATINV_SHIFT 1 /* DACL_DATINV */
+#define WM8993_DACL_DATINV_WIDTH 1 /* DACL_DATINV */
+#define WM8993_DACR_DATINV 0x0001 /* DACR_DATINV */
+#define WM8993_DACR_DATINV_MASK 0x0001 /* DACR_DATINV */
+#define WM8993_DACR_DATINV_SHIFT 0 /* DACR_DATINV */
+#define WM8993_DACR_DATINV_WIDTH 1 /* DACR_DATINV */
+
+/*
+ * R11 (0x0B) - Left DAC Digital Volume
+ */
+#define WM8993_DAC_VU 0x0100 /* DAC_VU */
+#define WM8993_DAC_VU_MASK 0x0100 /* DAC_VU */
+#define WM8993_DAC_VU_SHIFT 8 /* DAC_VU */
+#define WM8993_DAC_VU_WIDTH 1 /* DAC_VU */
+#define WM8993_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8993_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */
+#define WM8993_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */
+
+/*
+ * R12 (0x0C) - Right DAC Digital Volume
+ */
+#define WM8993_DAC_VU 0x0100 /* DAC_VU */
+#define WM8993_DAC_VU_MASK 0x0100 /* DAC_VU */
+#define WM8993_DAC_VU_SHIFT 8 /* DAC_VU */
+#define WM8993_DAC_VU_WIDTH 1 /* DAC_VU */
+#define WM8993_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8993_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */
+#define WM8993_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */
+
+/*
+ * R13 (0x0D) - Digital Side Tone
+ */
+#define WM8993_ADCL_DAC_SVOL_MASK 0x1E00 /* ADCL_DAC_SVOL - [12:9] */
+#define WM8993_ADCL_DAC_SVOL_SHIFT 9 /* ADCL_DAC_SVOL - [12:9] */
+#define WM8993_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [12:9] */
+#define WM8993_ADCR_DAC_SVOL_MASK 0x01E0 /* ADCR_DAC_SVOL - [8:5] */
+#define WM8993_ADCR_DAC_SVOL_SHIFT 5 /* ADCR_DAC_SVOL - [8:5] */
+#define WM8993_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [8:5] */
+#define WM8993_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */
+#define WM8993_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */
+#define WM8993_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */
+#define WM8993_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */
+#define WM8993_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */
+#define WM8993_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */
+
+/*
+ * R14 (0x0E) - ADC CTRL
+ */
+#define WM8993_ADC_OSR128 0x0200 /* ADC_OSR128 */
+#define WM8993_ADC_OSR128_MASK 0x0200 /* ADC_OSR128 */
+#define WM8993_ADC_OSR128_SHIFT 9 /* ADC_OSR128 */
+#define WM8993_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */
+#define WM8993_ADC_HPF 0x0100 /* ADC_HPF */
+#define WM8993_ADC_HPF_MASK 0x0100 /* ADC_HPF */
+#define WM8993_ADC_HPF_SHIFT 8 /* ADC_HPF */
+#define WM8993_ADC_HPF_WIDTH 1 /* ADC_HPF */
+#define WM8993_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */
+#define WM8993_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */
+#define WM8993_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */
+#define WM8993_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8993_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */
+#define WM8993_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */
+#define WM8993_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */
+#define WM8993_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8993_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */
+#define WM8993_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */
+#define WM8993_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */
+
+/*
+ * R15 (0x0F) - Left ADC Digital Volume
+ */
+#define WM8993_ADC_VU 0x0100 /* ADC_VU */
+#define WM8993_ADC_VU_MASK 0x0100 /* ADC_VU */
+#define WM8993_ADC_VU_SHIFT 8 /* ADC_VU */
+#define WM8993_ADC_VU_WIDTH 1 /* ADC_VU */
+#define WM8993_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8993_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */
+#define WM8993_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */
+
+/*
+ * R16 (0x10) - Right ADC Digital Volume
+ */
+#define WM8993_ADC_VU 0x0100 /* ADC_VU */
+#define WM8993_ADC_VU_MASK 0x0100 /* ADC_VU */
+#define WM8993_ADC_VU_SHIFT 8 /* ADC_VU */
+#define WM8993_ADC_VU_WIDTH 1 /* ADC_VU */
+#define WM8993_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8993_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */
+#define WM8993_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */
+
+/*
+ * R18 (0x12) - GPIO CTRL 1
+ */
+#define WM8993_JD2_SC_EINT 0x8000 /* JD2_SC_EINT */
+#define WM8993_JD2_SC_EINT_MASK 0x8000 /* JD2_SC_EINT */
+#define WM8993_JD2_SC_EINT_SHIFT 15 /* JD2_SC_EINT */
+#define WM8993_JD2_SC_EINT_WIDTH 1 /* JD2_SC_EINT */
+#define WM8993_JD2_EINT 0x4000 /* JD2_EINT */
+#define WM8993_JD2_EINT_MASK 0x4000 /* JD2_EINT */
+#define WM8993_JD2_EINT_SHIFT 14 /* JD2_EINT */
+#define WM8993_JD2_EINT_WIDTH 1 /* JD2_EINT */
+#define WM8993_WSEQ_EINT 0x2000 /* WSEQ_EINT */
+#define WM8993_WSEQ_EINT_MASK 0x2000 /* WSEQ_EINT */
+#define WM8993_WSEQ_EINT_SHIFT 13 /* WSEQ_EINT */
+#define WM8993_WSEQ_EINT_WIDTH 1 /* WSEQ_EINT */
+#define WM8993_IRQ 0x1000 /* IRQ */
+#define WM8993_IRQ_MASK 0x1000 /* IRQ */
+#define WM8993_IRQ_SHIFT 12 /* IRQ */
+#define WM8993_IRQ_WIDTH 1 /* IRQ */
+#define WM8993_TEMPOK_EINT 0x0800 /* TEMPOK_EINT */
+#define WM8993_TEMPOK_EINT_MASK 0x0800 /* TEMPOK_EINT */
+#define WM8993_TEMPOK_EINT_SHIFT 11 /* TEMPOK_EINT */
+#define WM8993_TEMPOK_EINT_WIDTH 1 /* TEMPOK_EINT */
+#define WM8993_JD1_SC_EINT 0x0400 /* JD1_SC_EINT */
+#define WM8993_JD1_SC_EINT_MASK 0x0400 /* JD1_SC_EINT */
+#define WM8993_JD1_SC_EINT_SHIFT 10 /* JD1_SC_EINT */
+#define WM8993_JD1_SC_EINT_WIDTH 1 /* JD1_SC_EINT */
+#define WM8993_JD1_EINT 0x0200 /* JD1_EINT */
+#define WM8993_JD1_EINT_MASK 0x0200 /* JD1_EINT */
+#define WM8993_JD1_EINT_SHIFT 9 /* JD1_EINT */
+#define WM8993_JD1_EINT_WIDTH 1 /* JD1_EINT */
+#define WM8993_FLL_LOCK_EINT 0x0100 /* FLL_LOCK_EINT */
+#define WM8993_FLL_LOCK_EINT_MASK 0x0100 /* FLL_LOCK_EINT */
+#define WM8993_FLL_LOCK_EINT_SHIFT 8 /* FLL_LOCK_EINT */
+#define WM8993_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */
+#define WM8993_GPI8_EINT 0x0080 /* GPI8_EINT */
+#define WM8993_GPI8_EINT_MASK 0x0080 /* GPI8_EINT */
+#define WM8993_GPI8_EINT_SHIFT 7 /* GPI8_EINT */
+#define WM8993_GPI8_EINT_WIDTH 1 /* GPI8_EINT */
+#define WM8993_GPI7_EINT 0x0040 /* GPI7_EINT */
+#define WM8993_GPI7_EINT_MASK 0x0040 /* GPI7_EINT */
+#define WM8993_GPI7_EINT_SHIFT 6 /* GPI7_EINT */
+#define WM8993_GPI7_EINT_WIDTH 1 /* GPI7_EINT */
+#define WM8993_GPIO1_EINT 0x0001 /* GPIO1_EINT */
+#define WM8993_GPIO1_EINT_MASK 0x0001 /* GPIO1_EINT */
+#define WM8993_GPIO1_EINT_SHIFT 0 /* GPIO1_EINT */
+#define WM8993_GPIO1_EINT_WIDTH 1 /* GPIO1_EINT */
+
+/*
+ * R19 (0x13) - GPIO1
+ */
+#define WM8993_GPIO1_PU 0x0020 /* GPIO1_PU */
+#define WM8993_GPIO1_PU_MASK 0x0020 /* GPIO1_PU */
+#define WM8993_GPIO1_PU_SHIFT 5 /* GPIO1_PU */
+#define WM8993_GPIO1_PU_WIDTH 1 /* GPIO1_PU */
+#define WM8993_GPIO1_PD 0x0010 /* GPIO1_PD */
+#define WM8993_GPIO1_PD_MASK 0x0010 /* GPIO1_PD */
+#define WM8993_GPIO1_PD_SHIFT 4 /* GPIO1_PD */
+#define WM8993_GPIO1_PD_WIDTH 1 /* GPIO1_PD */
+#define WM8993_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */
+#define WM8993_GPIO1_SEL_SHIFT 0 /* GPIO1_SEL - [3:0] */
+#define WM8993_GPIO1_SEL_WIDTH 4 /* GPIO1_SEL - [3:0] */
+
+/*
+ * R20 (0x14) - IRQ_DEBOUNCE
+ */
+#define WM8993_JD2_SC_DB 0x8000 /* JD2_SC_DB */
+#define WM8993_JD2_SC_DB_MASK 0x8000 /* JD2_SC_DB */
+#define WM8993_JD2_SC_DB_SHIFT 15 /* JD2_SC_DB */
+#define WM8993_JD2_SC_DB_WIDTH 1 /* JD2_SC_DB */
+#define WM8993_JD2_DB 0x4000 /* JD2_DB */
+#define WM8993_JD2_DB_MASK 0x4000 /* JD2_DB */
+#define WM8993_JD2_DB_SHIFT 14 /* JD2_DB */
+#define WM8993_JD2_DB_WIDTH 1 /* JD2_DB */
+#define WM8993_WSEQ_DB 0x2000 /* WSEQ_DB */
+#define WM8993_WSEQ_DB_MASK 0x2000 /* WSEQ_DB */
+#define WM8993_WSEQ_DB_SHIFT 13 /* WSEQ_DB */
+#define WM8993_WSEQ_DB_WIDTH 1 /* WSEQ_DB */
+#define WM8993_TEMPOK_DB 0x0800 /* TEMPOK_DB */
+#define WM8993_TEMPOK_DB_MASK 0x0800 /* TEMPOK_DB */
+#define WM8993_TEMPOK_DB_SHIFT 11 /* TEMPOK_DB */
+#define WM8993_TEMPOK_DB_WIDTH 1 /* TEMPOK_DB */
+#define WM8993_JD1_SC_DB 0x0400 /* JD1_SC_DB */
+#define WM8993_JD1_SC_DB_MASK 0x0400 /* JD1_SC_DB */
+#define WM8993_JD1_SC_DB_SHIFT 10 /* JD1_SC_DB */
+#define WM8993_JD1_SC_DB_WIDTH 1 /* JD1_SC_DB */
+#define WM8993_JD1_DB 0x0200 /* JD1_DB */
+#define WM8993_JD1_DB_MASK 0x0200 /* JD1_DB */
+#define WM8993_JD1_DB_SHIFT 9 /* JD1_DB */
+#define WM8993_JD1_DB_WIDTH 1 /* JD1_DB */
+#define WM8993_FLL_LOCK_DB 0x0100 /* FLL_LOCK_DB */
+#define WM8993_FLL_LOCK_DB_MASK 0x0100 /* FLL_LOCK_DB */
+#define WM8993_FLL_LOCK_DB_SHIFT 8 /* FLL_LOCK_DB */
+#define WM8993_FLL_LOCK_DB_WIDTH 1 /* FLL_LOCK_DB */
+#define WM8993_GPI8_DB 0x0080 /* GPI8_DB */
+#define WM8993_GPI8_DB_MASK 0x0080 /* GPI8_DB */
+#define WM8993_GPI8_DB_SHIFT 7 /* GPI8_DB */
+#define WM8993_GPI8_DB_WIDTH 1 /* GPI8_DB */
+#define WM8993_GPI7_DB 0x0008 /* GPI7_DB */
+#define WM8993_GPI7_DB_MASK 0x0008 /* GPI7_DB */
+#define WM8993_GPI7_DB_SHIFT 3 /* GPI7_DB */
+#define WM8993_GPI7_DB_WIDTH 1 /* GPI7_DB */
+#define WM8993_GPIO1_DB 0x0001 /* GPIO1_DB */
+#define WM8993_GPIO1_DB_MASK 0x0001 /* GPIO1_DB */
+#define WM8993_GPIO1_DB_SHIFT 0 /* GPIO1_DB */
+#define WM8993_GPIO1_DB_WIDTH 1 /* GPIO1_DB */
+
+/*
+ * R22 (0x16) - GPIOCTRL 2
+ */
+#define WM8993_IM_JD2_EINT 0x2000 /* IM_JD2_EINT */
+#define WM8993_IM_JD2_EINT_MASK 0x2000 /* IM_JD2_EINT */
+#define WM8993_IM_JD2_EINT_SHIFT 13 /* IM_JD2_EINT */
+#define WM8993_IM_JD2_EINT_WIDTH 1 /* IM_JD2_EINT */
+#define WM8993_IM_JD2_SC_EINT 0x1000 /* IM_JD2_SC_EINT */
+#define WM8993_IM_JD2_SC_EINT_MASK 0x1000 /* IM_JD2_SC_EINT */
+#define WM8993_IM_JD2_SC_EINT_SHIFT 12 /* IM_JD2_SC_EINT */
+#define WM8993_IM_JD2_SC_EINT_WIDTH 1 /* IM_JD2_SC_EINT */
+#define WM8993_IM_TEMPOK_EINT 0x0800 /* IM_TEMPOK_EINT */
+#define WM8993_IM_TEMPOK_EINT_MASK 0x0800 /* IM_TEMPOK_EINT */
+#define WM8993_IM_TEMPOK_EINT_SHIFT 11 /* IM_TEMPOK_EINT */
+#define WM8993_IM_TEMPOK_EINT_WIDTH 1 /* IM_TEMPOK_EINT */
+#define WM8993_IM_JD1_SC_EINT 0x0400 /* IM_JD1_SC_EINT */
+#define WM8993_IM_JD1_SC_EINT_MASK 0x0400 /* IM_JD1_SC_EINT */
+#define WM8993_IM_JD1_SC_EINT_SHIFT 10 /* IM_JD1_SC_EINT */
+#define WM8993_IM_JD1_SC_EINT_WIDTH 1 /* IM_JD1_SC_EINT */
+#define WM8993_IM_JD1_EINT 0x0200 /* IM_JD1_EINT */
+#define WM8993_IM_JD1_EINT_MASK 0x0200 /* IM_JD1_EINT */
+#define WM8993_IM_JD1_EINT_SHIFT 9 /* IM_JD1_EINT */
+#define WM8993_IM_JD1_EINT_WIDTH 1 /* IM_JD1_EINT */
+#define WM8993_IM_FLL_LOCK_EINT 0x0100 /* IM_FLL_LOCK_EINT */
+#define WM8993_IM_FLL_LOCK_EINT_MASK 0x0100 /* IM_FLL_LOCK_EINT */
+#define WM8993_IM_FLL_LOCK_EINT_SHIFT 8 /* IM_FLL_LOCK_EINT */
+#define WM8993_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */
+#define WM8993_IM_GPI8_EINT 0x0040 /* IM_GPI8_EINT */
+#define WM8993_IM_GPI8_EINT_MASK 0x0040 /* IM_GPI8_EINT */
+#define WM8993_IM_GPI8_EINT_SHIFT 6 /* IM_GPI8_EINT */
+#define WM8993_IM_GPI8_EINT_WIDTH 1 /* IM_GPI8_EINT */
+#define WM8993_IM_GPIO1_EINT 0x0020 /* IM_GPIO1_EINT */
+#define WM8993_IM_GPIO1_EINT_MASK 0x0020 /* IM_GPIO1_EINT */
+#define WM8993_IM_GPIO1_EINT_SHIFT 5 /* IM_GPIO1_EINT */
+#define WM8993_IM_GPIO1_EINT_WIDTH 1 /* IM_GPIO1_EINT */
+#define WM8993_GPI8_ENA 0x0010 /* GPI8_ENA */
+#define WM8993_GPI8_ENA_MASK 0x0010 /* GPI8_ENA */
+#define WM8993_GPI8_ENA_SHIFT 4 /* GPI8_ENA */
+#define WM8993_GPI8_ENA_WIDTH 1 /* GPI8_ENA */
+#define WM8993_IM_GPI7_EINT 0x0004 /* IM_GPI7_EINT */
+#define WM8993_IM_GPI7_EINT_MASK 0x0004 /* IM_GPI7_EINT */
+#define WM8993_IM_GPI7_EINT_SHIFT 2 /* IM_GPI7_EINT */
+#define WM8993_IM_GPI7_EINT_WIDTH 1 /* IM_GPI7_EINT */
+#define WM8993_IM_WSEQ_EINT 0x0002 /* IM_WSEQ_EINT */
+#define WM8993_IM_WSEQ_EINT_MASK 0x0002 /* IM_WSEQ_EINT */
+#define WM8993_IM_WSEQ_EINT_SHIFT 1 /* IM_WSEQ_EINT */
+#define WM8993_IM_WSEQ_EINT_WIDTH 1 /* IM_WSEQ_EINT */
+#define WM8993_GPI7_ENA 0x0001 /* GPI7_ENA */
+#define WM8993_GPI7_ENA_MASK 0x0001 /* GPI7_ENA */
+#define WM8993_GPI7_ENA_SHIFT 0 /* GPI7_ENA */
+#define WM8993_GPI7_ENA_WIDTH 1 /* GPI7_ENA */
+
+/*
+ * R23 (0x17) - GPIO_POL
+ */
+#define WM8993_JD2_SC_POL 0x8000 /* JD2_SC_POL */
+#define WM8993_JD2_SC_POL_MASK 0x8000 /* JD2_SC_POL */
+#define WM8993_JD2_SC_POL_SHIFT 15 /* JD2_SC_POL */
+#define WM8993_JD2_SC_POL_WIDTH 1 /* JD2_SC_POL */
+#define WM8993_JD2_POL 0x4000 /* JD2_POL */
+#define WM8993_JD2_POL_MASK 0x4000 /* JD2_POL */
+#define WM8993_JD2_POL_SHIFT 14 /* JD2_POL */
+#define WM8993_JD2_POL_WIDTH 1 /* JD2_POL */
+#define WM8993_WSEQ_POL 0x2000 /* WSEQ_POL */
+#define WM8993_WSEQ_POL_MASK 0x2000 /* WSEQ_POL */
+#define WM8993_WSEQ_POL_SHIFT 13 /* WSEQ_POL */
+#define WM8993_WSEQ_POL_WIDTH 1 /* WSEQ_POL */
+#define WM8993_IRQ_POL 0x1000 /* IRQ_POL */
+#define WM8993_IRQ_POL_MASK 0x1000 /* IRQ_POL */
+#define WM8993_IRQ_POL_SHIFT 12 /* IRQ_POL */
+#define WM8993_IRQ_POL_WIDTH 1 /* IRQ_POL */
+#define WM8993_TEMPOK_POL 0x0800 /* TEMPOK_POL */
+#define WM8993_TEMPOK_POL_MASK 0x0800 /* TEMPOK_POL */
+#define WM8993_TEMPOK_POL_SHIFT 11 /* TEMPOK_POL */
+#define WM8993_TEMPOK_POL_WIDTH 1 /* TEMPOK_POL */
+#define WM8993_JD1_SC_POL 0x0400 /* JD1_SC_POL */
+#define WM8993_JD1_SC_POL_MASK 0x0400 /* JD1_SC_POL */
+#define WM8993_JD1_SC_POL_SHIFT 10 /* JD1_SC_POL */
+#define WM8993_JD1_SC_POL_WIDTH 1 /* JD1_SC_POL */
+#define WM8993_JD1_POL 0x0200 /* JD1_POL */
+#define WM8993_JD1_POL_MASK 0x0200 /* JD1_POL */
+#define WM8993_JD1_POL_SHIFT 9 /* JD1_POL */
+#define WM8993_JD1_POL_WIDTH 1 /* JD1_POL */
+#define WM8993_FLL_LOCK_POL 0x0100 /* FLL_LOCK_POL */
+#define WM8993_FLL_LOCK_POL_MASK 0x0100 /* FLL_LOCK_POL */
+#define WM8993_FLL_LOCK_POL_SHIFT 8 /* FLL_LOCK_POL */
+#define WM8993_FLL_LOCK_POL_WIDTH 1 /* FLL_LOCK_POL */
+#define WM8993_GPI8_POL 0x0080 /* GPI8_POL */
+#define WM8993_GPI8_POL_MASK 0x0080 /* GPI8_POL */
+#define WM8993_GPI8_POL_SHIFT 7 /* GPI8_POL */
+#define WM8993_GPI8_POL_WIDTH 1 /* GPI8_POL */
+#define WM8993_GPI7_POL 0x0040 /* GPI7_POL */
+#define WM8993_GPI7_POL_MASK 0x0040 /* GPI7_POL */
+#define WM8993_GPI7_POL_SHIFT 6 /* GPI7_POL */
+#define WM8993_GPI7_POL_WIDTH 1 /* GPI7_POL */
+#define WM8993_GPIO1_POL 0x0001 /* GPIO1_POL */
+#define WM8993_GPIO1_POL_MASK 0x0001 /* GPIO1_POL */
+#define WM8993_GPIO1_POL_SHIFT 0 /* GPIO1_POL */
+#define WM8993_GPIO1_POL_WIDTH 1 /* GPIO1_POL */
+
+/*
+ * R24 (0x18) - Left Line Input 1&2 Volume
+ */
+#define WM8993_IN1_VU 0x0100 /* IN1_VU */
+#define WM8993_IN1_VU_MASK 0x0100 /* IN1_VU */
+#define WM8993_IN1_VU_SHIFT 8 /* IN1_VU */
+#define WM8993_IN1_VU_WIDTH 1 /* IN1_VU */
+#define WM8993_IN1L_MUTE 0x0080 /* IN1L_MUTE */
+#define WM8993_IN1L_MUTE_MASK 0x0080 /* IN1L_MUTE */
+#define WM8993_IN1L_MUTE_SHIFT 7 /* IN1L_MUTE */
+#define WM8993_IN1L_MUTE_WIDTH 1 /* IN1L_MUTE */
+#define WM8993_IN1L_ZC 0x0040 /* IN1L_ZC */
+#define WM8993_IN1L_ZC_MASK 0x0040 /* IN1L_ZC */
+#define WM8993_IN1L_ZC_SHIFT 6 /* IN1L_ZC */
+#define WM8993_IN1L_ZC_WIDTH 1 /* IN1L_ZC */
+#define WM8993_IN1L_VOL_MASK 0x001F /* IN1L_VOL - [4:0] */
+#define WM8993_IN1L_VOL_SHIFT 0 /* IN1L_VOL - [4:0] */
+#define WM8993_IN1L_VOL_WIDTH 5 /* IN1L_VOL - [4:0] */
+
+/*
+ * R25 (0x19) - Left Line Input 3&4 Volume
+ */
+#define WM8993_IN2_VU 0x0100 /* IN2_VU */
+#define WM8993_IN2_VU_MASK 0x0100 /* IN2_VU */
+#define WM8993_IN2_VU_SHIFT 8 /* IN2_VU */
+#define WM8993_IN2_VU_WIDTH 1 /* IN2_VU */
+#define WM8993_IN2L_MUTE 0x0080 /* IN2L_MUTE */
+#define WM8993_IN2L_MUTE_MASK 0x0080 /* IN2L_MUTE */
+#define WM8993_IN2L_MUTE_SHIFT 7 /* IN2L_MUTE */
+#define WM8993_IN2L_MUTE_WIDTH 1 /* IN2L_MUTE */
+#define WM8993_IN2L_ZC 0x0040 /* IN2L_ZC */
+#define WM8993_IN2L_ZC_MASK 0x0040 /* IN2L_ZC */
+#define WM8993_IN2L_ZC_SHIFT 6 /* IN2L_ZC */
+#define WM8993_IN2L_ZC_WIDTH 1 /* IN2L_ZC */
+#define WM8993_IN2L_VOL_MASK 0x001F /* IN2L_VOL - [4:0] */
+#define WM8993_IN2L_VOL_SHIFT 0 /* IN2L_VOL - [4:0] */
+#define WM8993_IN2L_VOL_WIDTH 5 /* IN2L_VOL - [4:0] */
+
+/*
+ * R26 (0x1A) - Right Line Input 1&2 Volume
+ */
+#define WM8993_IN1_VU 0x0100 /* IN1_VU */
+#define WM8993_IN1_VU_MASK 0x0100 /* IN1_VU */
+#define WM8993_IN1_VU_SHIFT 8 /* IN1_VU */
+#define WM8993_IN1_VU_WIDTH 1 /* IN1_VU */
+#define WM8993_IN1R_MUTE 0x0080 /* IN1R_MUTE */
+#define WM8993_IN1R_MUTE_MASK 0x0080 /* IN1R_MUTE */
+#define WM8993_IN1R_MUTE_SHIFT 7 /* IN1R_MUTE */
+#define WM8993_IN1R_MUTE_WIDTH 1 /* IN1R_MUTE */
+#define WM8993_IN1R_ZC 0x0040 /* IN1R_ZC */
+#define WM8993_IN1R_ZC_MASK 0x0040 /* IN1R_ZC */
+#define WM8993_IN1R_ZC_SHIFT 6 /* IN1R_ZC */
+#define WM8993_IN1R_ZC_WIDTH 1 /* IN1R_ZC */
+#define WM8993_IN1R_VOL_MASK 0x001F /* IN1R_VOL - [4:0] */
+#define WM8993_IN1R_VOL_SHIFT 0 /* IN1R_VOL - [4:0] */
+#define WM8993_IN1R_VOL_WIDTH 5 /* IN1R_VOL - [4:0] */
+
+/*
+ * R27 (0x1B) - Right Line Input 3&4 Volume
+ */
+#define WM8993_IN2_VU 0x0100 /* IN2_VU */
+#define WM8993_IN2_VU_MASK 0x0100 /* IN2_VU */
+#define WM8993_IN2_VU_SHIFT 8 /* IN2_VU */
+#define WM8993_IN2_VU_WIDTH 1 /* IN2_VU */
+#define WM8993_IN2R_MUTE 0x0080 /* IN2R_MUTE */
+#define WM8993_IN2R_MUTE_MASK 0x0080 /* IN2R_MUTE */
+#define WM8993_IN2R_MUTE_SHIFT 7 /* IN2R_MUTE */
+#define WM8993_IN2R_MUTE_WIDTH 1 /* IN2R_MUTE */
+#define WM8993_IN2R_ZC 0x0040 /* IN2R_ZC */
+#define WM8993_IN2R_ZC_MASK 0x0040 /* IN2R_ZC */
+#define WM8993_IN2R_ZC_SHIFT 6 /* IN2R_ZC */
+#define WM8993_IN2R_ZC_WIDTH 1 /* IN2R_ZC */
+#define WM8993_IN2R_VOL_MASK 0x001F /* IN2R_VOL - [4:0] */
+#define WM8993_IN2R_VOL_SHIFT 0 /* IN2R_VOL - [4:0] */
+#define WM8993_IN2R_VOL_WIDTH 5 /* IN2R_VOL - [4:0] */
+
+/*
+ * R28 (0x1C) - Left Output Volume
+ */
+#define WM8993_HPOUT1_VU 0x0100 /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */
+#define WM8993_HPOUT1L_ZC 0x0080 /* HPOUT1L_ZC */
+#define WM8993_HPOUT1L_ZC_MASK 0x0080 /* HPOUT1L_ZC */
+#define WM8993_HPOUT1L_ZC_SHIFT 7 /* HPOUT1L_ZC */
+#define WM8993_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */
+#define WM8993_HPOUT1L_MUTE_N 0x0040 /* HPOUT1L_MUTE_N */
+#define WM8993_HPOUT1L_MUTE_N_MASK 0x0040 /* HPOUT1L_MUTE_N */
+#define WM8993_HPOUT1L_MUTE_N_SHIFT 6 /* HPOUT1L_MUTE_N */
+#define WM8993_HPOUT1L_MUTE_N_WIDTH 1 /* HPOUT1L_MUTE_N */
+#define WM8993_HPOUT1L_VOL_MASK 0x003F /* HPOUT1L_VOL - [5:0] */
+#define WM8993_HPOUT1L_VOL_SHIFT 0 /* HPOUT1L_VOL - [5:0] */
+#define WM8993_HPOUT1L_VOL_WIDTH 6 /* HPOUT1L_VOL - [5:0] */
+
+/*
+ * R29 (0x1D) - Right Output Volume
+ */
+#define WM8993_HPOUT1_VU 0x0100 /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */
+#define WM8993_HPOUT1R_ZC 0x0080 /* HPOUT1R_ZC */
+#define WM8993_HPOUT1R_ZC_MASK 0x0080 /* HPOUT1R_ZC */
+#define WM8993_HPOUT1R_ZC_SHIFT 7 /* HPOUT1R_ZC */
+#define WM8993_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */
+#define WM8993_HPOUT1R_MUTE_N 0x0040 /* HPOUT1R_MUTE_N */
+#define WM8993_HPOUT1R_MUTE_N_MASK 0x0040 /* HPOUT1R_MUTE_N */
+#define WM8993_HPOUT1R_MUTE_N_SHIFT 6 /* HPOUT1R_MUTE_N */
+#define WM8993_HPOUT1R_MUTE_N_WIDTH 1 /* HPOUT1R_MUTE_N */
+#define WM8993_HPOUT1R_VOL_MASK 0x003F /* HPOUT1R_VOL - [5:0] */
+#define WM8993_HPOUT1R_VOL_SHIFT 0 /* HPOUT1R_VOL - [5:0] */
+#define WM8993_HPOUT1R_VOL_WIDTH 6 /* HPOUT1R_VOL - [5:0] */
+
+/*
+ * R30 (0x1E) - Line Outputs Volume
+ */
+#define WM8993_LINEOUT1N_MUTE 0x0040 /* LINEOUT1N_MUTE */
+#define WM8993_LINEOUT1N_MUTE_MASK 0x0040 /* LINEOUT1N_MUTE */
+#define WM8993_LINEOUT1N_MUTE_SHIFT 6 /* LINEOUT1N_MUTE */
+#define WM8993_LINEOUT1N_MUTE_WIDTH 1 /* LINEOUT1N_MUTE */
+#define WM8993_LINEOUT1P_MUTE 0x0020 /* LINEOUT1P_MUTE */
+#define WM8993_LINEOUT1P_MUTE_MASK 0x0020 /* LINEOUT1P_MUTE */
+#define WM8993_LINEOUT1P_MUTE_SHIFT 5 /* LINEOUT1P_MUTE */
+#define WM8993_LINEOUT1P_MUTE_WIDTH 1 /* LINEOUT1P_MUTE */
+#define WM8993_LINEOUT1_VOL 0x0010 /* LINEOUT1_VOL */
+#define WM8993_LINEOUT1_VOL_MASK 0x0010 /* LINEOUT1_VOL */
+#define WM8993_LINEOUT1_VOL_SHIFT 4 /* LINEOUT1_VOL */
+#define WM8993_LINEOUT1_VOL_WIDTH 1 /* LINEOUT1_VOL */
+#define WM8993_LINEOUT2N_MUTE 0x0004 /* LINEOUT2N_MUTE */
+#define WM8993_LINEOUT2N_MUTE_MASK 0x0004 /* LINEOUT2N_MUTE */
+#define WM8993_LINEOUT2N_MUTE_SHIFT 2 /* LINEOUT2N_MUTE */
+#define WM8993_LINEOUT2N_MUTE_WIDTH 1 /* LINEOUT2N_MUTE */
+#define WM8993_LINEOUT2P_MUTE 0x0002 /* LINEOUT2P_MUTE */
+#define WM8993_LINEOUT2P_MUTE_MASK 0x0002 /* LINEOUT2P_MUTE */
+#define WM8993_LINEOUT2P_MUTE_SHIFT 1 /* LINEOUT2P_MUTE */
+#define WM8993_LINEOUT2P_MUTE_WIDTH 1 /* LINEOUT2P_MUTE */
+#define WM8993_LINEOUT2_VOL 0x0001 /* LINEOUT2_VOL */
+#define WM8993_LINEOUT2_VOL_MASK 0x0001 /* LINEOUT2_VOL */
+#define WM8993_LINEOUT2_VOL_SHIFT 0 /* LINEOUT2_VOL */
+#define WM8993_LINEOUT2_VOL_WIDTH 1 /* LINEOUT2_VOL */
+
+/*
+ * R31 (0x1F) - HPOUT2 Volume
+ */
+#define WM8993_HPOUT2_MUTE 0x0020 /* HPOUT2_MUTE */
+#define WM8993_HPOUT2_MUTE_MASK 0x0020 /* HPOUT2_MUTE */
+#define WM8993_HPOUT2_MUTE_SHIFT 5 /* HPOUT2_MUTE */
+#define WM8993_HPOUT2_MUTE_WIDTH 1 /* HPOUT2_MUTE */
+#define WM8993_HPOUT2_VOL 0x0010 /* HPOUT2_VOL */
+#define WM8993_HPOUT2_VOL_MASK 0x0010 /* HPOUT2_VOL */
+#define WM8993_HPOUT2_VOL_SHIFT 4 /* HPOUT2_VOL */
+#define WM8993_HPOUT2_VOL_WIDTH 1 /* HPOUT2_VOL */
+
+/*
+ * R32 (0x20) - Left OPGA Volume
+ */
+#define WM8993_MIXOUT_VU 0x0100 /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_MASK 0x0100 /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_SHIFT 8 /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_WIDTH 1 /* MIXOUT_VU */
+#define WM8993_MIXOUTL_ZC 0x0080 /* MIXOUTL_ZC */
+#define WM8993_MIXOUTL_ZC_MASK 0x0080 /* MIXOUTL_ZC */
+#define WM8993_MIXOUTL_ZC_SHIFT 7 /* MIXOUTL_ZC */
+#define WM8993_MIXOUTL_ZC_WIDTH 1 /* MIXOUTL_ZC */
+#define WM8993_MIXOUTL_MUTE_N 0x0040 /* MIXOUTL_MUTE_N */
+#define WM8993_MIXOUTL_MUTE_N_MASK 0x0040 /* MIXOUTL_MUTE_N */
+#define WM8993_MIXOUTL_MUTE_N_SHIFT 6 /* MIXOUTL_MUTE_N */
+#define WM8993_MIXOUTL_MUTE_N_WIDTH 1 /* MIXOUTL_MUTE_N */
+#define WM8993_MIXOUTL_VOL_MASK 0x003F /* MIXOUTL_VOL - [5:0] */
+#define WM8993_MIXOUTL_VOL_SHIFT 0 /* MIXOUTL_VOL - [5:0] */
+#define WM8993_MIXOUTL_VOL_WIDTH 6 /* MIXOUTL_VOL - [5:0] */
+
+/*
+ * R33 (0x21) - Right OPGA Volume
+ */
+#define WM8993_MIXOUT_VU 0x0100 /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_MASK 0x0100 /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_SHIFT 8 /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_WIDTH 1 /* MIXOUT_VU */
+#define WM8993_MIXOUTR_ZC 0x0080 /* MIXOUTR_ZC */
+#define WM8993_MIXOUTR_ZC_MASK 0x0080 /* MIXOUTR_ZC */
+#define WM8993_MIXOUTR_ZC_SHIFT 7 /* MIXOUTR_ZC */
+#define WM8993_MIXOUTR_ZC_WIDTH 1 /* MIXOUTR_ZC */
+#define WM8993_MIXOUTR_MUTE_N 0x0040 /* MIXOUTR_MUTE_N */
+#define WM8993_MIXOUTR_MUTE_N_MASK 0x0040 /* MIXOUTR_MUTE_N */
+#define WM8993_MIXOUTR_MUTE_N_SHIFT 6 /* MIXOUTR_MUTE_N */
+#define WM8993_MIXOUTR_MUTE_N_WIDTH 1 /* MIXOUTR_MUTE_N */
+#define WM8993_MIXOUTR_VOL_MASK 0x003F /* MIXOUTR_VOL - [5:0] */
+#define WM8993_MIXOUTR_VOL_SHIFT 0 /* MIXOUTR_VOL - [5:0] */
+#define WM8993_MIXOUTR_VOL_WIDTH 6 /* MIXOUTR_VOL - [5:0] */
+
+/*
+ * R34 (0x22) - SPKMIXL Attenuation
+ */
+#define WM8993_MIXINL_SPKMIXL_VOL 0x0020 /* MIXINL_SPKMIXL_VOL */
+#define WM8993_MIXINL_SPKMIXL_VOL_MASK 0x0020 /* MIXINL_SPKMIXL_VOL */
+#define WM8993_MIXINL_SPKMIXL_VOL_SHIFT 5 /* MIXINL_SPKMIXL_VOL */
+#define WM8993_MIXINL_SPKMIXL_VOL_WIDTH 1 /* MIXINL_SPKMIXL_VOL */
+#define WM8993_IN1LP_SPKMIXL_VOL 0x0010 /* IN1LP_SPKMIXL_VOL */
+#define WM8993_IN1LP_SPKMIXL_VOL_MASK 0x0010 /* IN1LP_SPKMIXL_VOL */
+#define WM8993_IN1LP_SPKMIXL_VOL_SHIFT 4 /* IN1LP_SPKMIXL_VOL */
+#define WM8993_IN1LP_SPKMIXL_VOL_WIDTH 1 /* IN1LP_SPKMIXL_VOL */
+#define WM8993_MIXOUTL_SPKMIXL_VOL 0x0008 /* MIXOUTL_SPKMIXL_VOL */
+#define WM8993_MIXOUTL_SPKMIXL_VOL_MASK 0x0008 /* MIXOUTL_SPKMIXL_VOL */
+#define WM8993_MIXOUTL_SPKMIXL_VOL_SHIFT 3 /* MIXOUTL_SPKMIXL_VOL */
+#define WM8993_MIXOUTL_SPKMIXL_VOL_WIDTH 1 /* MIXOUTL_SPKMIXL_VOL */
+#define WM8993_DACL_SPKMIXL_VOL 0x0004 /* DACL_SPKMIXL_VOL */
+#define WM8993_DACL_SPKMIXL_VOL_MASK 0x0004 /* DACL_SPKMIXL_VOL */
+#define WM8993_DACL_SPKMIXL_VOL_SHIFT 2 /* DACL_SPKMIXL_VOL */
+#define WM8993_DACL_SPKMIXL_VOL_WIDTH 1 /* DACL_SPKMIXL_VOL */
+#define WM8993_SPKMIXL_VOL_MASK 0x0003 /* SPKMIXL_VOL - [1:0] */
+#define WM8993_SPKMIXL_VOL_SHIFT 0 /* SPKMIXL_VOL - [1:0] */
+#define WM8993_SPKMIXL_VOL_WIDTH 2 /* SPKMIXL_VOL - [1:0] */
+
+/*
+ * R35 (0x23) - SPKMIXR Attenuation
+ */
+#define WM8993_SPKOUT_CLASSAB_MODE 0x0100 /* SPKOUT_CLASSAB_MODE */
+#define WM8993_SPKOUT_CLASSAB_MODE_MASK 0x0100 /* SPKOUT_CLASSAB_MODE */
+#define WM8993_SPKOUT_CLASSAB_MODE_SHIFT 8 /* SPKOUT_CLASSAB_MODE */
+#define WM8993_SPKOUT_CLASSAB_MODE_WIDTH 1 /* SPKOUT_CLASSAB_MODE */
+#define WM8993_MIXINR_SPKMIXR_VOL 0x0020 /* MIXINR_SPKMIXR_VOL */
+#define WM8993_MIXINR_SPKMIXR_VOL_MASK 0x0020 /* MIXINR_SPKMIXR_VOL */
+#define WM8993_MIXINR_SPKMIXR_VOL_SHIFT 5 /* MIXINR_SPKMIXR_VOL */
+#define WM8993_MIXINR_SPKMIXR_VOL_WIDTH 1 /* MIXINR_SPKMIXR_VOL */
+#define WM8993_IN1RP_SPKMIXR_VOL 0x0010 /* IN1RP_SPKMIXR_VOL */
+#define WM8993_IN1RP_SPKMIXR_VOL_MASK 0x0010 /* IN1RP_SPKMIXR_VOL */
+#define WM8993_IN1RP_SPKMIXR_VOL_SHIFT 4 /* IN1RP_SPKMIXR_VOL */
+#define WM8993_IN1RP_SPKMIXR_VOL_WIDTH 1 /* IN1RP_SPKMIXR_VOL */
+#define WM8993_MIXOUTR_SPKMIXR_VOL 0x0008 /* MIXOUTR_SPKMIXR_VOL */
+#define WM8993_MIXOUTR_SPKMIXR_VOL_MASK 0x0008 /* MIXOUTR_SPKMIXR_VOL */
+#define WM8993_MIXOUTR_SPKMIXR_VOL_SHIFT 3 /* MIXOUTR_SPKMIXR_VOL */
+#define WM8993_MIXOUTR_SPKMIXR_VOL_WIDTH 1 /* MIXOUTR_SPKMIXR_VOL */
+#define WM8993_DACR_SPKMIXR_VOL 0x0004 /* DACR_SPKMIXR_VOL */
+#define WM8993_DACR_SPKMIXR_VOL_MASK 0x0004 /* DACR_SPKMIXR_VOL */
+#define WM8993_DACR_SPKMIXR_VOL_SHIFT 2 /* DACR_SPKMIXR_VOL */
+#define WM8993_DACR_SPKMIXR_VOL_WIDTH 1 /* DACR_SPKMIXR_VOL */
+#define WM8993_SPKMIXR_VOL_MASK 0x0003 /* SPKMIXR_VOL - [1:0] */
+#define WM8993_SPKMIXR_VOL_SHIFT 0 /* SPKMIXR_VOL - [1:0] */
+#define WM8993_SPKMIXR_VOL_WIDTH 2 /* SPKMIXR_VOL - [1:0] */
+
+/*
+ * R36 (0x24) - SPKOUT Mixers
+ */
+#define WM8993_VRX_TO_SPKOUTL 0x0020 /* VRX_TO_SPKOUTL */
+#define WM8993_VRX_TO_SPKOUTL_MASK 0x0020 /* VRX_TO_SPKOUTL */
+#define WM8993_VRX_TO_SPKOUTL_SHIFT 5 /* VRX_TO_SPKOUTL */
+#define WM8993_VRX_TO_SPKOUTL_WIDTH 1 /* VRX_TO_SPKOUTL */
+#define WM8993_SPKMIXL_TO_SPKOUTL 0x0010 /* SPKMIXL_TO_SPKOUTL */
+#define WM8993_SPKMIXL_TO_SPKOUTL_MASK 0x0010 /* SPKMIXL_TO_SPKOUTL */
+#define WM8993_SPKMIXL_TO_SPKOUTL_SHIFT 4 /* SPKMIXL_TO_SPKOUTL */
+#define WM8993_SPKMIXL_TO_SPKOUTL_WIDTH 1 /* SPKMIXL_TO_SPKOUTL */
+#define WM8993_SPKMIXR_TO_SPKOUTL 0x0008 /* SPKMIXR_TO_SPKOUTL */
+#define WM8993_SPKMIXR_TO_SPKOUTL_MASK 0x0008 /* SPKMIXR_TO_SPKOUTL */
+#define WM8993_SPKMIXR_TO_SPKOUTL_SHIFT 3 /* SPKMIXR_TO_SPKOUTL */
+#define WM8993_SPKMIXR_TO_SPKOUTL_WIDTH 1 /* SPKMIXR_TO_SPKOUTL */
+#define WM8993_VRX_TO_SPKOUTR 0x0004 /* VRX_TO_SPKOUTR */
+#define WM8993_VRX_TO_SPKOUTR_MASK 0x0004 /* VRX_TO_SPKOUTR */
+#define WM8993_VRX_TO_SPKOUTR_SHIFT 2 /* VRX_TO_SPKOUTR */
+#define WM8993_VRX_TO_SPKOUTR_WIDTH 1 /* VRX_TO_SPKOUTR */
+#define WM8993_SPKMIXL_TO_SPKOUTR 0x0002 /* SPKMIXL_TO_SPKOUTR */
+#define WM8993_SPKMIXL_TO_SPKOUTR_MASK 0x0002 /* SPKMIXL_TO_SPKOUTR */
+#define WM8993_SPKMIXL_TO_SPKOUTR_SHIFT 1 /* SPKMIXL_TO_SPKOUTR */
+#define WM8993_SPKMIXL_TO_SPKOUTR_WIDTH 1 /* SPKMIXL_TO_SPKOUTR */
+#define WM8993_SPKMIXR_TO_SPKOUTR 0x0001 /* SPKMIXR_TO_SPKOUTR */
+#define WM8993_SPKMIXR_TO_SPKOUTR_MASK 0x0001 /* SPKMIXR_TO_SPKOUTR */
+#define WM8993_SPKMIXR_TO_SPKOUTR_SHIFT 0 /* SPKMIXR_TO_SPKOUTR */
+#define WM8993_SPKMIXR_TO_SPKOUTR_WIDTH 1 /* SPKMIXR_TO_SPKOUTR */
+
+/*
+ * R37 (0x25) - SPKOUT Boost
+ */
+#define WM8993_SPKOUTL_BOOST_MASK 0x0038 /* SPKOUTL_BOOST - [5:3] */
+#define WM8993_SPKOUTL_BOOST_SHIFT 3 /* SPKOUTL_BOOST - [5:3] */
+#define WM8993_SPKOUTL_BOOST_WIDTH 3 /* SPKOUTL_BOOST - [5:3] */
+#define WM8993_SPKOUTR_BOOST_MASK 0x0007 /* SPKOUTR_BOOST - [2:0] */
+#define WM8993_SPKOUTR_BOOST_SHIFT 0 /* SPKOUTR_BOOST - [2:0] */
+#define WM8993_SPKOUTR_BOOST_WIDTH 3 /* SPKOUTR_BOOST - [2:0] */
+
+/*
+ * R38 (0x26) - Speaker Volume Left
+ */
+#define WM8993_SPKOUT_VU 0x0100 /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */
+#define WM8993_SPKOUTL_ZC 0x0080 /* SPKOUTL_ZC */
+#define WM8993_SPKOUTL_ZC_MASK 0x0080 /* SPKOUTL_ZC */
+#define WM8993_SPKOUTL_ZC_SHIFT 7 /* SPKOUTL_ZC */
+#define WM8993_SPKOUTL_ZC_WIDTH 1 /* SPKOUTL_ZC */
+#define WM8993_SPKOUTL_MUTE_N 0x0040 /* SPKOUTL_MUTE_N */
+#define WM8993_SPKOUTL_MUTE_N_MASK 0x0040 /* SPKOUTL_MUTE_N */
+#define WM8993_SPKOUTL_MUTE_N_SHIFT 6 /* SPKOUTL_MUTE_N */
+#define WM8993_SPKOUTL_MUTE_N_WIDTH 1 /* SPKOUTL_MUTE_N */
+#define WM8993_SPKOUTL_VOL_MASK 0x003F /* SPKOUTL_VOL - [5:0] */
+#define WM8993_SPKOUTL_VOL_SHIFT 0 /* SPKOUTL_VOL - [5:0] */
+#define WM8993_SPKOUTL_VOL_WIDTH 6 /* SPKOUTL_VOL - [5:0] */
+
+/*
+ * R39 (0x27) - Speaker Volume Right
+ */
+#define WM8993_SPKOUT_VU 0x0100 /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */
+#define WM8993_SPKOUTR_ZC 0x0080 /* SPKOUTR_ZC */
+#define WM8993_SPKOUTR_ZC_MASK 0x0080 /* SPKOUTR_ZC */
+#define WM8993_SPKOUTR_ZC_SHIFT 7 /* SPKOUTR_ZC */
+#define WM8993_SPKOUTR_ZC_WIDTH 1 /* SPKOUTR_ZC */
+#define WM8993_SPKOUTR_MUTE_N 0x0040 /* SPKOUTR_MUTE_N */
+#define WM8993_SPKOUTR_MUTE_N_MASK 0x0040 /* SPKOUTR_MUTE_N */
+#define WM8993_SPKOUTR_MUTE_N_SHIFT 6 /* SPKOUTR_MUTE_N */
+#define WM8993_SPKOUTR_MUTE_N_WIDTH 1 /* SPKOUTR_MUTE_N */
+#define WM8993_SPKOUTR_VOL_MASK 0x003F /* SPKOUTR_VOL - [5:0] */
+#define WM8993_SPKOUTR_VOL_SHIFT 0 /* SPKOUTR_VOL - [5:0] */
+#define WM8993_SPKOUTR_VOL_WIDTH 6 /* SPKOUTR_VOL - [5:0] */
+
+/*
+ * R40 (0x28) - Input Mixer2
+ */
+#define WM8993_IN2LP_TO_IN2L 0x0080 /* IN2LP_TO_IN2L */
+#define WM8993_IN2LP_TO_IN2L_MASK 0x0080 /* IN2LP_TO_IN2L */
+#define WM8993_IN2LP_TO_IN2L_SHIFT 7 /* IN2LP_TO_IN2L */
+#define WM8993_IN2LP_TO_IN2L_WIDTH 1 /* IN2LP_TO_IN2L */
+#define WM8993_IN2LN_TO_IN2L 0x0040 /* IN2LN_TO_IN2L */
+#define WM8993_IN2LN_TO_IN2L_MASK 0x0040 /* IN2LN_TO_IN2L */
+#define WM8993_IN2LN_TO_IN2L_SHIFT 6 /* IN2LN_TO_IN2L */
+#define WM8993_IN2LN_TO_IN2L_WIDTH 1 /* IN2LN_TO_IN2L */
+#define WM8993_IN1LP_TO_IN1L 0x0020 /* IN1LP_TO_IN1L */
+#define WM8993_IN1LP_TO_IN1L_MASK 0x0020 /* IN1LP_TO_IN1L */
+#define WM8993_IN1LP_TO_IN1L_SHIFT 5 /* IN1LP_TO_IN1L */
+#define WM8993_IN1LP_TO_IN1L_WIDTH 1 /* IN1LP_TO_IN1L */
+#define WM8993_IN1LN_TO_IN1L 0x0010 /* IN1LN_TO_IN1L */
+#define WM8993_IN1LN_TO_IN1L_MASK 0x0010 /* IN1LN_TO_IN1L */
+#define WM8993_IN1LN_TO_IN1L_SHIFT 4 /* IN1LN_TO_IN1L */
+#define WM8993_IN1LN_TO_IN1L_WIDTH 1 /* IN1LN_TO_IN1L */
+#define WM8993_IN2RP_TO_IN2R 0x0008 /* IN2RP_TO_IN2R */
+#define WM8993_IN2RP_TO_IN2R_MASK 0x0008 /* IN2RP_TO_IN2R */
+#define WM8993_IN2RP_TO_IN2R_SHIFT 3 /* IN2RP_TO_IN2R */
+#define WM8993_IN2RP_TO_IN2R_WIDTH 1 /* IN2RP_TO_IN2R */
+#define WM8993_IN2RN_TO_IN2R 0x0004 /* IN2RN_TO_IN2R */
+#define WM8993_IN2RN_TO_IN2R_MASK 0x0004 /* IN2RN_TO_IN2R */
+#define WM8993_IN2RN_TO_IN2R_SHIFT 2 /* IN2RN_TO_IN2R */
+#define WM8993_IN2RN_TO_IN2R_WIDTH 1 /* IN2RN_TO_IN2R */
+#define WM8993_IN1RP_TO_IN1R 0x0002 /* IN1RP_TO_IN1R */
+#define WM8993_IN1RP_TO_IN1R_MASK 0x0002 /* IN1RP_TO_IN1R */
+#define WM8993_IN1RP_TO_IN1R_SHIFT 1 /* IN1RP_TO_IN1R */
+#define WM8993_IN1RP_TO_IN1R_WIDTH 1 /* IN1RP_TO_IN1R */
+#define WM8993_IN1RN_TO_IN1R 0x0001 /* IN1RN_TO_IN1R */
+#define WM8993_IN1RN_TO_IN1R_MASK 0x0001 /* IN1RN_TO_IN1R */
+#define WM8993_IN1RN_TO_IN1R_SHIFT 0 /* IN1RN_TO_IN1R */
+#define WM8993_IN1RN_TO_IN1R_WIDTH 1 /* IN1RN_TO_IN1R */
+
+/*
+ * R41 (0x29) - Input Mixer3
+ */
+#define WM8993_IN2L_TO_MIXINL 0x0100 /* IN2L_TO_MIXINL */
+#define WM8993_IN2L_TO_MIXINL_MASK 0x0100 /* IN2L_TO_MIXINL */
+#define WM8993_IN2L_TO_MIXINL_SHIFT 8 /* IN2L_TO_MIXINL */
+#define WM8993_IN2L_TO_MIXINL_WIDTH 1 /* IN2L_TO_MIXINL */
+#define WM8993_IN2L_MIXINL_VOL 0x0080 /* IN2L_MIXINL_VOL */
+#define WM8993_IN2L_MIXINL_VOL_MASK 0x0080 /* IN2L_MIXINL_VOL */
+#define WM8993_IN2L_MIXINL_VOL_SHIFT 7 /* IN2L_MIXINL_VOL */
+#define WM8993_IN2L_MIXINL_VOL_WIDTH 1 /* IN2L_MIXINL_VOL */
+#define WM8993_IN1L_TO_MIXINL 0x0020 /* IN1L_TO_MIXINL */
+#define WM8993_IN1L_TO_MIXINL_MASK 0x0020 /* IN1L_TO_MIXINL */
+#define WM8993_IN1L_TO_MIXINL_SHIFT 5 /* IN1L_TO_MIXINL */
+#define WM8993_IN1L_TO_MIXINL_WIDTH 1 /* IN1L_TO_MIXINL */
+#define WM8993_IN1L_MIXINL_VOL 0x0010 /* IN1L_MIXINL_VOL */
+#define WM8993_IN1L_MIXINL_VOL_MASK 0x0010 /* IN1L_MIXINL_VOL */
+#define WM8993_IN1L_MIXINL_VOL_SHIFT 4 /* IN1L_MIXINL_VOL */
+#define WM8993_IN1L_MIXINL_VOL_WIDTH 1 /* IN1L_MIXINL_VOL */
+#define WM8993_MIXOUTL_MIXINL_VOL_MASK 0x0007 /* MIXOUTL_MIXINL_VOL - [2:0] */
+#define WM8993_MIXOUTL_MIXINL_VOL_SHIFT 0 /* MIXOUTL_MIXINL_VOL - [2:0] */
+#define WM8993_MIXOUTL_MIXINL_VOL_WIDTH 3 /* MIXOUTL_MIXINL_VOL - [2:0] */
+
+/*
+ * R42 (0x2A) - Input Mixer4
+ */
+#define WM8993_IN2R_TO_MIXINR 0x0100 /* IN2R_TO_MIXINR */
+#define WM8993_IN2R_TO_MIXINR_MASK 0x0100 /* IN2R_TO_MIXINR */
+#define WM8993_IN2R_TO_MIXINR_SHIFT 8 /* IN2R_TO_MIXINR */
+#define WM8993_IN2R_TO_MIXINR_WIDTH 1 /* IN2R_TO_MIXINR */
+#define WM8993_IN2R_MIXINR_VOL 0x0080 /* IN2R_MIXINR_VOL */
+#define WM8993_IN2R_MIXINR_VOL_MASK 0x0080 /* IN2R_MIXINR_VOL */
+#define WM8993_IN2R_MIXINR_VOL_SHIFT 7 /* IN2R_MIXINR_VOL */
+#define WM8993_IN2R_MIXINR_VOL_WIDTH 1 /* IN2R_MIXINR_VOL */
+#define WM8993_IN1R_TO_MIXINR 0x0020 /* IN1R_TO_MIXINR */
+#define WM8993_IN1R_TO_MIXINR_MASK 0x0020 /* IN1R_TO_MIXINR */
+#define WM8993_IN1R_TO_MIXINR_SHIFT 5 /* IN1R_TO_MIXINR */
+#define WM8993_IN1R_TO_MIXINR_WIDTH 1 /* IN1R_TO_MIXINR */
+#define WM8993_IN1R_MIXINR_VOL 0x0010 /* IN1R_MIXINR_VOL */
+#define WM8993_IN1R_MIXINR_VOL_MASK 0x0010 /* IN1R_MIXINR_VOL */
+#define WM8993_IN1R_MIXINR_VOL_SHIFT 4 /* IN1R_MIXINR_VOL */
+#define WM8993_IN1R_MIXINR_VOL_WIDTH 1 /* IN1R_MIXINR_VOL */
+#define WM8993_MIXOUTR_MIXINR_VOL_MASK 0x0007 /* MIXOUTR_MIXINR_VOL - [2:0] */
+#define WM8993_MIXOUTR_MIXINR_VOL_SHIFT 0 /* MIXOUTR_MIXINR_VOL - [2:0] */
+#define WM8993_MIXOUTR_MIXINR_VOL_WIDTH 3 /* MIXOUTR_MIXINR_VOL - [2:0] */
+
+/*
+ * R43 (0x2B) - Input Mixer5
+ */
+#define WM8993_IN1LP_MIXINL_VOL_MASK 0x01C0 /* IN1LP_MIXINL_VOL - [8:6] */
+#define WM8993_IN1LP_MIXINL_VOL_SHIFT 6 /* IN1LP_MIXINL_VOL - [8:6] */
+#define WM8993_IN1LP_MIXINL_VOL_WIDTH 3 /* IN1LP_MIXINL_VOL - [8:6] */
+#define WM8993_VRX_MIXINL_VOL_MASK 0x0007 /* VRX_MIXINL_VOL - [2:0] */
+#define WM8993_VRX_MIXINL_VOL_SHIFT 0 /* VRX_MIXINL_VOL - [2:0] */
+#define WM8993_VRX_MIXINL_VOL_WIDTH 3 /* VRX_MIXINL_VOL - [2:0] */
+
+/*
+ * R44 (0x2C) - Input Mixer6
+ */
+#define WM8993_IN1RP_MIXINR_VOL_MASK 0x01C0 /* IN1RP_MIXINR_VOL - [8:6] */
+#define WM8993_IN1RP_MIXINR_VOL_SHIFT 6 /* IN1RP_MIXINR_VOL - [8:6] */
+#define WM8993_IN1RP_MIXINR_VOL_WIDTH 3 /* IN1RP_MIXINR_VOL - [8:6] */
+#define WM8993_VRX_MIXINR_VOL_MASK 0x0007 /* VRX_MIXINR_VOL - [2:0] */
+#define WM8993_VRX_MIXINR_VOL_SHIFT 0 /* VRX_MIXINR_VOL - [2:0] */
+#define WM8993_VRX_MIXINR_VOL_WIDTH 3 /* VRX_MIXINR_VOL - [2:0] */
+
+/*
+ * R45 (0x2D) - Output Mixer1
+ */
+#define WM8993_DACL_TO_HPOUT1L 0x0100 /* DACL_TO_HPOUT1L */
+#define WM8993_DACL_TO_HPOUT1L_MASK 0x0100 /* DACL_TO_HPOUT1L */
+#define WM8993_DACL_TO_HPOUT1L_SHIFT 8 /* DACL_TO_HPOUT1L */
+#define WM8993_DACL_TO_HPOUT1L_WIDTH 1 /* DACL_TO_HPOUT1L */
+#define WM8993_MIXINR_TO_MIXOUTL 0x0080 /* MIXINR_TO_MIXOUTL */
+#define WM8993_MIXINR_TO_MIXOUTL_MASK 0x0080 /* MIXINR_TO_MIXOUTL */
+#define WM8993_MIXINR_TO_MIXOUTL_SHIFT 7 /* MIXINR_TO_MIXOUTL */
+#define WM8993_MIXINR_TO_MIXOUTL_WIDTH 1 /* MIXINR_TO_MIXOUTL */
+#define WM8993_MIXINL_TO_MIXOUTL 0x0040 /* MIXINL_TO_MIXOUTL */
+#define WM8993_MIXINL_TO_MIXOUTL_MASK 0x0040 /* MIXINL_TO_MIXOUTL */
+#define WM8993_MIXINL_TO_MIXOUTL_SHIFT 6 /* MIXINL_TO_MIXOUTL */
+#define WM8993_MIXINL_TO_MIXOUTL_WIDTH 1 /* MIXINL_TO_MIXOUTL */
+#define WM8993_IN2RN_TO_MIXOUTL 0x0020 /* IN2RN_TO_MIXOUTL */
+#define WM8993_IN2RN_TO_MIXOUTL_MASK 0x0020 /* IN2RN_TO_MIXOUTL */
+#define WM8993_IN2RN_TO_MIXOUTL_SHIFT 5 /* IN2RN_TO_MIXOUTL */
+#define WM8993_IN2RN_TO_MIXOUTL_WIDTH 1 /* IN2RN_TO_MIXOUTL */
+#define WM8993_IN2LN_TO_MIXOUTL 0x0010 /* IN2LN_TO_MIXOUTL */
+#define WM8993_IN2LN_TO_MIXOUTL_MASK 0x0010 /* IN2LN_TO_MIXOUTL */
+#define WM8993_IN2LN_TO_MIXOUTL_SHIFT 4 /* IN2LN_TO_MIXOUTL */
+#define WM8993_IN2LN_TO_MIXOUTL_WIDTH 1 /* IN2LN_TO_MIXOUTL */
+#define WM8993_IN1R_TO_MIXOUTL 0x0008 /* IN1R_TO_MIXOUTL */
+#define WM8993_IN1R_TO_MIXOUTL_MASK 0x0008 /* IN1R_TO_MIXOUTL */
+#define WM8993_IN1R_TO_MIXOUTL_SHIFT 3 /* IN1R_TO_MIXOUTL */
+#define WM8993_IN1R_TO_MIXOUTL_WIDTH 1 /* IN1R_TO_MIXOUTL */
+#define WM8993_IN1L_TO_MIXOUTL 0x0004 /* IN1L_TO_MIXOUTL */
+#define WM8993_IN1L_TO_MIXOUTL_MASK 0x0004 /* IN1L_TO_MIXOUTL */
+#define WM8993_IN1L_TO_MIXOUTL_SHIFT 2 /* IN1L_TO_MIXOUTL */
+#define WM8993_IN1L_TO_MIXOUTL_WIDTH 1 /* IN1L_TO_MIXOUTL */
+#define WM8993_IN2LP_TO_MIXOUTL 0x0002 /* IN2LP_TO_MIXOUTL */
+#define WM8993_IN2LP_TO_MIXOUTL_MASK 0x0002 /* IN2LP_TO_MIXOUTL */
+#define WM8993_IN2LP_TO_MIXOUTL_SHIFT 1 /* IN2LP_TO_MIXOUTL */
+#define WM8993_IN2LP_TO_MIXOUTL_WIDTH 1 /* IN2LP_TO_MIXOUTL */
+#define WM8993_DACL_TO_MIXOUTL 0x0001 /* DACL_TO_MIXOUTL */
+#define WM8993_DACL_TO_MIXOUTL_MASK 0x0001 /* DACL_TO_MIXOUTL */
+#define WM8993_DACL_TO_MIXOUTL_SHIFT 0 /* DACL_TO_MIXOUTL */
+#define WM8993_DACL_TO_MIXOUTL_WIDTH 1 /* DACL_TO_MIXOUTL */
+
+/*
+ * R46 (0x2E) - Output Mixer2
+ */
+#define WM8993_DACR_TO_HPOUT1R 0x0100 /* DACR_TO_HPOUT1R */
+#define WM8993_DACR_TO_HPOUT1R_MASK 0x0100 /* DACR_TO_HPOUT1R */
+#define WM8993_DACR_TO_HPOUT1R_SHIFT 8 /* DACR_TO_HPOUT1R */
+#define WM8993_DACR_TO_HPOUT1R_WIDTH 1 /* DACR_TO_HPOUT1R */
+#define WM8993_MIXINL_TO_MIXOUTR 0x0080 /* MIXINL_TO_MIXOUTR */
+#define WM8993_MIXINL_TO_MIXOUTR_MASK 0x0080 /* MIXINL_TO_MIXOUTR */
+#define WM8993_MIXINL_TO_MIXOUTR_SHIFT 7 /* MIXINL_TO_MIXOUTR */
+#define WM8993_MIXINL_TO_MIXOUTR_WIDTH 1 /* MIXINL_TO_MIXOUTR */
+#define WM8993_MIXINR_TO_MIXOUTR 0x0040 /* MIXINR_TO_MIXOUTR */
+#define WM8993_MIXINR_TO_MIXOUTR_MASK 0x0040 /* MIXINR_TO_MIXOUTR */
+#define WM8993_MIXINR_TO_MIXOUTR_SHIFT 6 /* MIXINR_TO_MIXOUTR */
+#define WM8993_MIXINR_TO_MIXOUTR_WIDTH 1 /* MIXINR_TO_MIXOUTR */
+#define WM8993_IN2LN_TO_MIXOUTR 0x0020 /* IN2LN_TO_MIXOUTR */
+#define WM8993_IN2LN_TO_MIXOUTR_MASK 0x0020 /* IN2LN_TO_MIXOUTR */
+#define WM8993_IN2LN_TO_MIXOUTR_SHIFT 5 /* IN2LN_TO_MIXOUTR */
+#define WM8993_IN2LN_TO_MIXOUTR_WIDTH 1 /* IN2LN_TO_MIXOUTR */
+#define WM8993_IN2RN_TO_MIXOUTR 0x0010 /* IN2RN_TO_MIXOUTR */
+#define WM8993_IN2RN_TO_MIXOUTR_MASK 0x0010 /* IN2RN_TO_MIXOUTR */
+#define WM8993_IN2RN_TO_MIXOUTR_SHIFT 4 /* IN2RN_TO_MIXOUTR */
+#define WM8993_IN2RN_TO_MIXOUTR_WIDTH 1 /* IN2RN_TO_MIXOUTR */
+#define WM8993_IN1L_TO_MIXOUTR 0x0008 /* IN1L_TO_MIXOUTR */
+#define WM8993_IN1L_TO_MIXOUTR_MASK 0x0008 /* IN1L_TO_MIXOUTR */
+#define WM8993_IN1L_TO_MIXOUTR_SHIFT 3 /* IN1L_TO_MIXOUTR */
+#define WM8993_IN1L_TO_MIXOUTR_WIDTH 1 /* IN1L_TO_MIXOUTR */
+#define WM8993_IN1R_TO_MIXOUTR 0x0004 /* IN1R_TO_MIXOUTR */
+#define WM8993_IN1R_TO_MIXOUTR_MASK 0x0004 /* IN1R_TO_MIXOUTR */
+#define WM8993_IN1R_TO_MIXOUTR_SHIFT 2 /* IN1R_TO_MIXOUTR */
+#define WM8993_IN1R_TO_MIXOUTR_WIDTH 1 /* IN1R_TO_MIXOUTR */
+#define WM8993_IN2RP_TO_MIXOUTR 0x0002 /* IN2RP_TO_MIXOUTR */
+#define WM8993_IN2RP_TO_MIXOUTR_MASK 0x0002 /* IN2RP_TO_MIXOUTR */
+#define WM8993_IN2RP_TO_MIXOUTR_SHIFT 1 /* IN2RP_TO_MIXOUTR */
+#define WM8993_IN2RP_TO_MIXOUTR_WIDTH 1 /* IN2RP_TO_MIXOUTR */
+#define WM8993_DACR_TO_MIXOUTR 0x0001 /* DACR_TO_MIXOUTR */
+#define WM8993_DACR_TO_MIXOUTR_MASK 0x0001 /* DACR_TO_MIXOUTR */
+#define WM8993_DACR_TO_MIXOUTR_SHIFT 0 /* DACR_TO_MIXOUTR */
+#define WM8993_DACR_TO_MIXOUTR_WIDTH 1 /* DACR_TO_MIXOUTR */
+
+/*
+ * R47 (0x2F) - Output Mixer3
+ */
+#define WM8993_IN2LP_MIXOUTL_VOL_MASK 0x0E00 /* IN2LP_MIXOUTL_VOL - [11:9] */
+#define WM8993_IN2LP_MIXOUTL_VOL_SHIFT 9 /* IN2LP_MIXOUTL_VOL - [11:9] */
+#define WM8993_IN2LP_MIXOUTL_VOL_WIDTH 3 /* IN2LP_MIXOUTL_VOL - [11:9] */
+#define WM8993_IN2LN_MIXOUTL_VOL_MASK 0x01C0 /* IN2LN_MIXOUTL_VOL - [8:6] */
+#define WM8993_IN2LN_MIXOUTL_VOL_SHIFT 6 /* IN2LN_MIXOUTL_VOL - [8:6] */
+#define WM8993_IN2LN_MIXOUTL_VOL_WIDTH 3 /* IN2LN_MIXOUTL_VOL - [8:6] */
+#define WM8993_IN1R_MIXOUTL_VOL_MASK 0x0038 /* IN1R_MIXOUTL_VOL - [5:3] */
+#define WM8993_IN1R_MIXOUTL_VOL_SHIFT 3 /* IN1R_MIXOUTL_VOL - [5:3] */
+#define WM8993_IN1R_MIXOUTL_VOL_WIDTH 3 /* IN1R_MIXOUTL_VOL - [5:3] */
+#define WM8993_IN1L_MIXOUTL_VOL_MASK 0x0007 /* IN1L_MIXOUTL_VOL - [2:0] */
+#define WM8993_IN1L_MIXOUTL_VOL_SHIFT 0 /* IN1L_MIXOUTL_VOL - [2:0] */
+#define WM8993_IN1L_MIXOUTL_VOL_WIDTH 3 /* IN1L_MIXOUTL_VOL - [2:0] */
+
+/*
+ * R48 (0x30) - Output Mixer4
+ */
+#define WM8993_IN2RP_MIXOUTR_VOL_MASK 0x0E00 /* IN2RP_MIXOUTR_VOL - [11:9] */
+#define WM8993_IN2RP_MIXOUTR_VOL_SHIFT 9 /* IN2RP_MIXOUTR_VOL - [11:9] */
+#define WM8993_IN2RP_MIXOUTR_VOL_WIDTH 3 /* IN2RP_MIXOUTR_VOL - [11:9] */
+#define WM8993_IN2RN_MIXOUTR_VOL_MASK 0x01C0 /* IN2RN_MIXOUTR_VOL - [8:6] */
+#define WM8993_IN2RN_MIXOUTR_VOL_SHIFT 6 /* IN2RN_MIXOUTR_VOL - [8:6] */
+#define WM8993_IN2RN_MIXOUTR_VOL_WIDTH 3 /* IN2RN_MIXOUTR_VOL - [8:6] */
+#define WM8993_IN1L_MIXOUTR_VOL_MASK 0x0038 /* IN1L_MIXOUTR_VOL - [5:3] */
+#define WM8993_IN1L_MIXOUTR_VOL_SHIFT 3 /* IN1L_MIXOUTR_VOL - [5:3] */
+#define WM8993_IN1L_MIXOUTR_VOL_WIDTH 3 /* IN1L_MIXOUTR_VOL - [5:3] */
+#define WM8993_IN1R_MIXOUTR_VOL_MASK 0x0007 /* IN1R_MIXOUTR_VOL - [2:0] */
+#define WM8993_IN1R_MIXOUTR_VOL_SHIFT 0 /* IN1R_MIXOUTR_VOL - [2:0] */
+#define WM8993_IN1R_MIXOUTR_VOL_WIDTH 3 /* IN1R_MIXOUTR_VOL - [2:0] */
+
+/*
+ * R49 (0x31) - Output Mixer5
+ */
+#define WM8993_DACL_MIXOUTL_VOL_MASK 0x0E00 /* DACL_MIXOUTL_VOL - [11:9] */
+#define WM8993_DACL_MIXOUTL_VOL_SHIFT 9 /* DACL_MIXOUTL_VOL - [11:9] */
+#define WM8993_DACL_MIXOUTL_VOL_WIDTH 3 /* DACL_MIXOUTL_VOL - [11:9] */
+#define WM8993_IN2RN_MIXOUTL_VOL_MASK 0x01C0 /* IN2RN_MIXOUTL_VOL - [8:6] */
+#define WM8993_IN2RN_MIXOUTL_VOL_SHIFT 6 /* IN2RN_MIXOUTL_VOL - [8:6] */
+#define WM8993_IN2RN_MIXOUTL_VOL_WIDTH 3 /* IN2RN_MIXOUTL_VOL - [8:6] */
+#define WM8993_MIXINR_MIXOUTL_VOL_MASK 0x0038 /* MIXINR_MIXOUTL_VOL - [5:3] */
+#define WM8993_MIXINR_MIXOUTL_VOL_SHIFT 3 /* MIXINR_MIXOUTL_VOL - [5:3] */
+#define WM8993_MIXINR_MIXOUTL_VOL_WIDTH 3 /* MIXINR_MIXOUTL_VOL - [5:3] */
+#define WM8993_MIXINL_MIXOUTL_VOL_MASK 0x0007 /* MIXINL_MIXOUTL_VOL - [2:0] */
+#define WM8993_MIXINL_MIXOUTL_VOL_SHIFT 0 /* MIXINL_MIXOUTL_VOL - [2:0] */
+#define WM8993_MIXINL_MIXOUTL_VOL_WIDTH 3 /* MIXINL_MIXOUTL_VOL - [2:0] */
+
+/*
+ * R50 (0x32) - Output Mixer6
+ */
+#define WM8993_DACR_MIXOUTR_VOL_MASK 0x0E00 /* DACR_MIXOUTR_VOL - [11:9] */
+#define WM8993_DACR_MIXOUTR_VOL_SHIFT 9 /* DACR_MIXOUTR_VOL - [11:9] */
+#define WM8993_DACR_MIXOUTR_VOL_WIDTH 3 /* DACR_MIXOUTR_VOL - [11:9] */
+#define WM8993_IN2LN_MIXOUTR_VOL_MASK 0x01C0 /* IN2LN_MIXOUTR_VOL - [8:6] */
+#define WM8993_IN2LN_MIXOUTR_VOL_SHIFT 6 /* IN2LN_MIXOUTR_VOL - [8:6] */
+#define WM8993_IN2LN_MIXOUTR_VOL_WIDTH 3 /* IN2LN_MIXOUTR_VOL - [8:6] */
+#define WM8993_MIXINL_MIXOUTR_VOL_MASK 0x0038 /* MIXINL_MIXOUTR_VOL - [5:3] */
+#define WM8993_MIXINL_MIXOUTR_VOL_SHIFT 3 /* MIXINL_MIXOUTR_VOL - [5:3] */
+#define WM8993_MIXINL_MIXOUTR_VOL_WIDTH 3 /* MIXINL_MIXOUTR_VOL - [5:3] */
+#define WM8993_MIXINR_MIXOUTR_VOL_MASK 0x0007 /* MIXINR_MIXOUTR_VOL - [2:0] */
+#define WM8993_MIXINR_MIXOUTR_VOL_SHIFT 0 /* MIXINR_MIXOUTR_VOL - [2:0] */
+#define WM8993_MIXINR_MIXOUTR_VOL_WIDTH 3 /* MIXINR_MIXOUTR_VOL - [2:0] */
+
+/*
+ * R51 (0x33) - HPOUT2 Mixer
+ */
+#define WM8993_VRX_TO_HPOUT2 0x0020 /* VRX_TO_HPOUT2 */
+#define WM8993_VRX_TO_HPOUT2_MASK 0x0020 /* VRX_TO_HPOUT2 */
+#define WM8993_VRX_TO_HPOUT2_SHIFT 5 /* VRX_TO_HPOUT2 */
+#define WM8993_VRX_TO_HPOUT2_WIDTH 1 /* VRX_TO_HPOUT2 */
+#define WM8993_MIXOUTLVOL_TO_HPOUT2 0x0010 /* MIXOUTLVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTLVOL_TO_HPOUT2_MASK 0x0010 /* MIXOUTLVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTLVOL_TO_HPOUT2_SHIFT 4 /* MIXOUTLVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTLVOL_TO_HPOUT2_WIDTH 1 /* MIXOUTLVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTRVOL_TO_HPOUT2 0x0008 /* MIXOUTRVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTRVOL_TO_HPOUT2_MASK 0x0008 /* MIXOUTRVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTRVOL_TO_HPOUT2_SHIFT 3 /* MIXOUTRVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTRVOL_TO_HPOUT2_WIDTH 1 /* MIXOUTRVOL_TO_HPOUT2 */
+
+/*
+ * R52 (0x34) - Line Mixer1
+ */
+#define WM8993_MIXOUTL_TO_LINEOUT1N 0x0040 /* MIXOUTL_TO_LINEOUT1N */
+#define WM8993_MIXOUTL_TO_LINEOUT1N_MASK 0x0040 /* MIXOUTL_TO_LINEOUT1N */
+#define WM8993_MIXOUTL_TO_LINEOUT1N_SHIFT 6 /* MIXOUTL_TO_LINEOUT1N */
+#define WM8993_MIXOUTL_TO_LINEOUT1N_WIDTH 1 /* MIXOUTL_TO_LINEOUT1N */
+#define WM8993_MIXOUTR_TO_LINEOUT1N 0x0020 /* MIXOUTR_TO_LINEOUT1N */
+#define WM8993_MIXOUTR_TO_LINEOUT1N_MASK 0x0020 /* MIXOUTR_TO_LINEOUT1N */
+#define WM8993_MIXOUTR_TO_LINEOUT1N_SHIFT 5 /* MIXOUTR_TO_LINEOUT1N */
+#define WM8993_MIXOUTR_TO_LINEOUT1N_WIDTH 1 /* MIXOUTR_TO_LINEOUT1N */
+#define WM8993_LINEOUT1_MODE 0x0010 /* LINEOUT1_MODE */
+#define WM8993_LINEOUT1_MODE_MASK 0x0010 /* LINEOUT1_MODE */
+#define WM8993_LINEOUT1_MODE_SHIFT 4 /* LINEOUT1_MODE */
+#define WM8993_LINEOUT1_MODE_WIDTH 1 /* LINEOUT1_MODE */
+#define WM8993_IN1R_TO_LINEOUT1P 0x0004 /* IN1R_TO_LINEOUT1P */
+#define WM8993_IN1R_TO_LINEOUT1P_MASK 0x0004 /* IN1R_TO_LINEOUT1P */
+#define WM8993_IN1R_TO_LINEOUT1P_SHIFT 2 /* IN1R_TO_LINEOUT1P */
+#define WM8993_IN1R_TO_LINEOUT1P_WIDTH 1 /* IN1R_TO_LINEOUT1P */
+#define WM8993_IN1L_TO_LINEOUT1P 0x0002 /* IN1L_TO_LINEOUT1P */
+#define WM8993_IN1L_TO_LINEOUT1P_MASK 0x0002 /* IN1L_TO_LINEOUT1P */
+#define WM8993_IN1L_TO_LINEOUT1P_SHIFT 1 /* IN1L_TO_LINEOUT1P */
+#define WM8993_IN1L_TO_LINEOUT1P_WIDTH 1 /* IN1L_TO_LINEOUT1P */
+#define WM8993_MIXOUTL_TO_LINEOUT1P 0x0001 /* MIXOUTL_TO_LINEOUT1P */
+#define WM8993_MIXOUTL_TO_LINEOUT1P_MASK 0x0001 /* MIXOUTL_TO_LINEOUT1P */
+#define WM8993_MIXOUTL_TO_LINEOUT1P_SHIFT 0 /* MIXOUTL_TO_LINEOUT1P */
+#define WM8993_MIXOUTL_TO_LINEOUT1P_WIDTH 1 /* MIXOUTL_TO_LINEOUT1P */
+
+/*
+ * R53 (0x35) - Line Mixer2
+ */
+#define WM8993_MIXOUTR_TO_LINEOUT2N 0x0040 /* MIXOUTR_TO_LINEOUT2N */
+#define WM8993_MIXOUTR_TO_LINEOUT2N_MASK 0x0040 /* MIXOUTR_TO_LINEOUT2N */
+#define WM8993_MIXOUTR_TO_LINEOUT2N_SHIFT 6 /* MIXOUTR_TO_LINEOUT2N */
+#define WM8993_MIXOUTR_TO_LINEOUT2N_WIDTH 1 /* MIXOUTR_TO_LINEOUT2N */
+#define WM8993_MIXOUTL_TO_LINEOUT2N 0x0020 /* MIXOUTL_TO_LINEOUT2N */
+#define WM8993_MIXOUTL_TO_LINEOUT2N_MASK 0x0020 /* MIXOUTL_TO_LINEOUT2N */
+#define WM8993_MIXOUTL_TO_LINEOUT2N_SHIFT 5 /* MIXOUTL_TO_LINEOUT2N */
+#define WM8993_MIXOUTL_TO_LINEOUT2N_WIDTH 1 /* MIXOUTL_TO_LINEOUT2N */
+#define WM8993_LINEOUT2_MODE 0x0010 /* LINEOUT2_MODE */
+#define WM8993_LINEOUT2_MODE_MASK 0x0010 /* LINEOUT2_MODE */
+#define WM8993_LINEOUT2_MODE_SHIFT 4 /* LINEOUT2_MODE */
+#define WM8993_LINEOUT2_MODE_WIDTH 1 /* LINEOUT2_MODE */
+#define WM8993_IN1L_TO_LINEOUT2P 0x0004 /* IN1L_TO_LINEOUT2P */
+#define WM8993_IN1L_TO_LINEOUT2P_MASK 0x0004 /* IN1L_TO_LINEOUT2P */
+#define WM8993_IN1L_TO_LINEOUT2P_SHIFT 2 /* IN1L_TO_LINEOUT2P */
+#define WM8993_IN1L_TO_LINEOUT2P_WIDTH 1 /* IN1L_TO_LINEOUT2P */
+#define WM8993_IN1R_TO_LINEOUT2P 0x0002 /* IN1R_TO_LINEOUT2P */
+#define WM8993_IN1R_TO_LINEOUT2P_MASK 0x0002 /* IN1R_TO_LINEOUT2P */
+#define WM8993_IN1R_TO_LINEOUT2P_SHIFT 1 /* IN1R_TO_LINEOUT2P */
+#define WM8993_IN1R_TO_LINEOUT2P_WIDTH 1 /* IN1R_TO_LINEOUT2P */
+#define WM8993_MIXOUTR_TO_LINEOUT2P 0x0001 /* MIXOUTR_TO_LINEOUT2P */
+#define WM8993_MIXOUTR_TO_LINEOUT2P_MASK 0x0001 /* MIXOUTR_TO_LINEOUT2P */
+#define WM8993_MIXOUTR_TO_LINEOUT2P_SHIFT 0 /* MIXOUTR_TO_LINEOUT2P */
+#define WM8993_MIXOUTR_TO_LINEOUT2P_WIDTH 1 /* MIXOUTR_TO_LINEOUT2P */
+
+/*
+ * R54 (0x36) - Speaker Mixer
+ */
+#define WM8993_SPKAB_REF_SEL 0x0100 /* SPKAB_REF_SEL */
+#define WM8993_SPKAB_REF_SEL_MASK 0x0100 /* SPKAB_REF_SEL */
+#define WM8993_SPKAB_REF_SEL_SHIFT 8 /* SPKAB_REF_SEL */
+#define WM8993_SPKAB_REF_SEL_WIDTH 1 /* SPKAB_REF_SEL */
+#define WM8993_MIXINL_TO_SPKMIXL 0x0080 /* MIXINL_TO_SPKMIXL */
+#define WM8993_MIXINL_TO_SPKMIXL_MASK 0x0080 /* MIXINL_TO_SPKMIXL */
+#define WM8993_MIXINL_TO_SPKMIXL_SHIFT 7 /* MIXINL_TO_SPKMIXL */
+#define WM8993_MIXINL_TO_SPKMIXL_WIDTH 1 /* MIXINL_TO_SPKMIXL */
+#define WM8993_MIXINR_TO_SPKMIXR 0x0040 /* MIXINR_TO_SPKMIXR */
+#define WM8993_MIXINR_TO_SPKMIXR_MASK 0x0040 /* MIXINR_TO_SPKMIXR */
+#define WM8993_MIXINR_TO_SPKMIXR_SHIFT 6 /* MIXINR_TO_SPKMIXR */
+#define WM8993_MIXINR_TO_SPKMIXR_WIDTH 1 /* MIXINR_TO_SPKMIXR */
+#define WM8993_IN1LP_TO_SPKMIXL 0x0020 /* IN1LP_TO_SPKMIXL */
+#define WM8993_IN1LP_TO_SPKMIXL_MASK 0x0020 /* IN1LP_TO_SPKMIXL */
+#define WM8993_IN1LP_TO_SPKMIXL_SHIFT 5 /* IN1LP_TO_SPKMIXL */
+#define WM8993_IN1LP_TO_SPKMIXL_WIDTH 1 /* IN1LP_TO_SPKMIXL */
+#define WM8993_IN1RP_TO_SPKMIXR 0x0010 /* IN1RP_TO_SPKMIXR */
+#define WM8993_IN1RP_TO_SPKMIXR_MASK 0x0010 /* IN1RP_TO_SPKMIXR */
+#define WM8993_IN1RP_TO_SPKMIXR_SHIFT 4 /* IN1RP_TO_SPKMIXR */
+#define WM8993_IN1RP_TO_SPKMIXR_WIDTH 1 /* IN1RP_TO_SPKMIXR */
+#define WM8993_MIXOUTL_TO_SPKMIXL 0x0008 /* MIXOUTL_TO_SPKMIXL */
+#define WM8993_MIXOUTL_TO_SPKMIXL_MASK 0x0008 /* MIXOUTL_TO_SPKMIXL */
+#define WM8993_MIXOUTL_TO_SPKMIXL_SHIFT 3 /* MIXOUTL_TO_SPKMIXL */
+#define WM8993_MIXOUTL_TO_SPKMIXL_WIDTH 1 /* MIXOUTL_TO_SPKMIXL */
+#define WM8993_MIXOUTR_TO_SPKMIXR 0x0004 /* MIXOUTR_TO_SPKMIXR */
+#define WM8993_MIXOUTR_TO_SPKMIXR_MASK 0x0004 /* MIXOUTR_TO_SPKMIXR */
+#define WM8993_MIXOUTR_TO_SPKMIXR_SHIFT 2 /* MIXOUTR_TO_SPKMIXR */
+#define WM8993_MIXOUTR_TO_SPKMIXR_WIDTH 1 /* MIXOUTR_TO_SPKMIXR */
+#define WM8993_DACL_TO_SPKMIXL 0x0002 /* DACL_TO_SPKMIXL */
+#define WM8993_DACL_TO_SPKMIXL_MASK 0x0002 /* DACL_TO_SPKMIXL */
+#define WM8993_DACL_TO_SPKMIXL_SHIFT 1 /* DACL_TO_SPKMIXL */
+#define WM8993_DACL_TO_SPKMIXL_WIDTH 1 /* DACL_TO_SPKMIXL */
+#define WM8993_DACR_TO_SPKMIXR 0x0001 /* DACR_TO_SPKMIXR */
+#define WM8993_DACR_TO_SPKMIXR_MASK 0x0001 /* DACR_TO_SPKMIXR */
+#define WM8993_DACR_TO_SPKMIXR_SHIFT 0 /* DACR_TO_SPKMIXR */
+#define WM8993_DACR_TO_SPKMIXR_WIDTH 1 /* DACR_TO_SPKMIXR */
+
+/*
+ * R55 (0x37) - Additional Control
+ */
+#define WM8993_LINEOUT1_FB 0x0080 /* LINEOUT1_FB */
+#define WM8993_LINEOUT1_FB_MASK 0x0080 /* LINEOUT1_FB */
+#define WM8993_LINEOUT1_FB_SHIFT 7 /* LINEOUT1_FB */
+#define WM8993_LINEOUT1_FB_WIDTH 1 /* LINEOUT1_FB */
+#define WM8993_LINEOUT2_FB 0x0040 /* LINEOUT2_FB */
+#define WM8993_LINEOUT2_FB_MASK 0x0040 /* LINEOUT2_FB */
+#define WM8993_LINEOUT2_FB_SHIFT 6 /* LINEOUT2_FB */
+#define WM8993_LINEOUT2_FB_WIDTH 1 /* LINEOUT2_FB */
+#define WM8993_VROI 0x0001 /* VROI */
+#define WM8993_VROI_MASK 0x0001 /* VROI */
+#define WM8993_VROI_SHIFT 0 /* VROI */
+#define WM8993_VROI_WIDTH 1 /* VROI */
+
+/*
+ * R56 (0x38) - AntiPOP1
+ */
+#define WM8993_LINEOUT_VMID_BUF_ENA 0x0080 /* LINEOUT_VMID_BUF_ENA */
+#define WM8993_LINEOUT_VMID_BUF_ENA_MASK 0x0080 /* LINEOUT_VMID_BUF_ENA */
+#define WM8993_LINEOUT_VMID_BUF_ENA_SHIFT 7 /* LINEOUT_VMID_BUF_ENA */
+#define WM8993_LINEOUT_VMID_BUF_ENA_WIDTH 1 /* LINEOUT_VMID_BUF_ENA */
+#define WM8993_HPOUT2_IN_ENA 0x0040 /* HPOUT2_IN_ENA */
+#define WM8993_HPOUT2_IN_ENA_MASK 0x0040 /* HPOUT2_IN_ENA */
+#define WM8993_HPOUT2_IN_ENA_SHIFT 6 /* HPOUT2_IN_ENA */
+#define WM8993_HPOUT2_IN_ENA_WIDTH 1 /* HPOUT2_IN_ENA */
+#define WM8993_LINEOUT1_DISCH 0x0020 /* LINEOUT1_DISCH */
+#define WM8993_LINEOUT1_DISCH_MASK 0x0020 /* LINEOUT1_DISCH */
+#define WM8993_LINEOUT1_DISCH_SHIFT 5 /* LINEOUT1_DISCH */
+#define WM8993_LINEOUT1_DISCH_WIDTH 1 /* LINEOUT1_DISCH */
+#define WM8993_LINEOUT2_DISCH 0x0010 /* LINEOUT2_DISCH */
+#define WM8993_LINEOUT2_DISCH_MASK 0x0010 /* LINEOUT2_DISCH */
+#define WM8993_LINEOUT2_DISCH_SHIFT 4 /* LINEOUT2_DISCH */
+#define WM8993_LINEOUT2_DISCH_WIDTH 1 /* LINEOUT2_DISCH */
+
+/*
+ * R57 (0x39) - AntiPOP2
+ */
+#define WM8993_VMID_RAMP_MASK 0x0060 /* VMID_RAMP - [6:5] */
+#define WM8993_VMID_RAMP_SHIFT 5 /* VMID_RAMP - [6:5] */
+#define WM8993_VMID_RAMP_WIDTH 2 /* VMID_RAMP - [6:5] */
+#define WM8993_VMID_BUF_ENA 0x0008 /* VMID_BUF_ENA */
+#define WM8993_VMID_BUF_ENA_MASK 0x0008 /* VMID_BUF_ENA */
+#define WM8993_VMID_BUF_ENA_SHIFT 3 /* VMID_BUF_ENA */
+#define WM8993_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */
+#define WM8993_STARTUP_BIAS_ENA 0x0004 /* STARTUP_BIAS_ENA */
+#define WM8993_STARTUP_BIAS_ENA_MASK 0x0004 /* STARTUP_BIAS_ENA */
+#define WM8993_STARTUP_BIAS_ENA_SHIFT 2 /* STARTUP_BIAS_ENA */
+#define WM8993_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */
+#define WM8993_BIAS_SRC 0x0002 /* BIAS_SRC */
+#define WM8993_BIAS_SRC_MASK 0x0002 /* BIAS_SRC */
+#define WM8993_BIAS_SRC_SHIFT 1 /* BIAS_SRC */
+#define WM8993_BIAS_SRC_WIDTH 1 /* BIAS_SRC */
+#define WM8993_VMID_DISCH 0x0001 /* VMID_DISCH */
+#define WM8993_VMID_DISCH_MASK 0x0001 /* VMID_DISCH */
+#define WM8993_VMID_DISCH_SHIFT 0 /* VMID_DISCH */
+#define WM8993_VMID_DISCH_WIDTH 1 /* VMID_DISCH */
+
+/*
+ * R58 (0x3A) - MICBIAS
+ */
+#define WM8993_JD_SCTHR_MASK 0x00C0 /* JD_SCTHR - [7:6] */
+#define WM8993_JD_SCTHR_SHIFT 6 /* JD_SCTHR - [7:6] */
+#define WM8993_JD_SCTHR_WIDTH 2 /* JD_SCTHR - [7:6] */
+#define WM8993_JD_THR_MASK 0x0030 /* JD_THR - [5:4] */
+#define WM8993_JD_THR_SHIFT 4 /* JD_THR - [5:4] */
+#define WM8993_JD_THR_WIDTH 2 /* JD_THR - [5:4] */
+#define WM8993_JD_ENA 0x0004 /* JD_ENA */
+#define WM8993_JD_ENA_MASK 0x0004 /* JD_ENA */
+#define WM8993_JD_ENA_SHIFT 2 /* JD_ENA */
+#define WM8993_JD_ENA_WIDTH 1 /* JD_ENA */
+#define WM8993_MICB2_LVL 0x0002 /* MICB2_LVL */
+#define WM8993_MICB2_LVL_MASK 0x0002 /* MICB2_LVL */
+#define WM8993_MICB2_LVL_SHIFT 1 /* MICB2_LVL */
+#define WM8993_MICB2_LVL_WIDTH 1 /* MICB2_LVL */
+#define WM8993_MICB1_LVL 0x0001 /* MICB1_LVL */
+#define WM8993_MICB1_LVL_MASK 0x0001 /* MICB1_LVL */
+#define WM8993_MICB1_LVL_SHIFT 0 /* MICB1_LVL */
+#define WM8993_MICB1_LVL_WIDTH 1 /* MICB1_LVL */
+
+/*
+ * R60 (0x3C) - FLL Control 1
+ */
+#define WM8993_FLL_FRAC 0x0004 /* FLL_FRAC */
+#define WM8993_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */
+#define WM8993_FLL_FRAC_SHIFT 2 /* FLL_FRAC */
+#define WM8993_FLL_FRAC_WIDTH 1 /* FLL_FRAC */
+#define WM8993_FLL_OSC_ENA 0x0002 /* FLL_OSC_ENA */
+#define WM8993_FLL_OSC_ENA_MASK 0x0002 /* FLL_OSC_ENA */
+#define WM8993_FLL_OSC_ENA_SHIFT 1 /* FLL_OSC_ENA */
+#define WM8993_FLL_OSC_ENA_WIDTH 1 /* FLL_OSC_ENA */
+#define WM8993_FLL_ENA 0x0001 /* FLL_ENA */
+#define WM8993_FLL_ENA_MASK 0x0001 /* FLL_ENA */
+#define WM8993_FLL_ENA_SHIFT 0 /* FLL_ENA */
+#define WM8993_FLL_ENA_WIDTH 1 /* FLL_ENA */
+
+/*
+ * R61 (0x3D) - FLL Control 2
+ */
+#define WM8993_FLL_OUTDIV_MASK 0x0700 /* FLL_OUTDIV - [10:8] */
+#define WM8993_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [10:8] */
+#define WM8993_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [10:8] */
+#define WM8993_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */
+#define WM8993_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */
+#define WM8993_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */
+#define WM8993_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */
+#define WM8993_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */
+#define WM8993_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */
+
+/*
+ * R62 (0x3E) - FLL Control 3
+ */
+#define WM8993_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */
+#define WM8993_FLL_K_SHIFT 0 /* FLL_K - [15:0] */
+#define WM8993_FLL_K_WIDTH 16 /* FLL_K - [15:0] */
+
+/*
+ * R63 (0x3F) - FLL Control 4
+ */
+#define WM8993_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */
+#define WM8993_FLL_N_SHIFT 5 /* FLL_N - [14:5] */
+#define WM8993_FLL_N_WIDTH 10 /* FLL_N - [14:5] */
+#define WM8993_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */
+#define WM8993_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */
+#define WM8993_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */
+
+/*
+ * R64 (0x40) - FLL Control 5
+ */
+#define WM8993_FLL_FRC_NCO_VAL_MASK 0x1F80 /* FLL_FRC_NCO_VAL - [12:7] */
+#define WM8993_FLL_FRC_NCO_VAL_SHIFT 7 /* FLL_FRC_NCO_VAL - [12:7] */
+#define WM8993_FLL_FRC_NCO_VAL_WIDTH 6 /* FLL_FRC_NCO_VAL - [12:7] */
+#define WM8993_FLL_FRC_NCO 0x0040 /* FLL_FRC_NCO */
+#define WM8993_FLL_FRC_NCO_MASK 0x0040 /* FLL_FRC_NCO */
+#define WM8993_FLL_FRC_NCO_SHIFT 6 /* FLL_FRC_NCO */
+#define WM8993_FLL_FRC_NCO_WIDTH 1 /* FLL_FRC_NCO */
+#define WM8993_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8993_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8993_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8993_FLL_CLK_SRC_MASK 0x0003 /* FLL_CLK_SRC - [1:0] */
+#define WM8993_FLL_CLK_SRC_SHIFT 0 /* FLL_CLK_SRC - [1:0] */
+#define WM8993_FLL_CLK_SRC_WIDTH 2 /* FLL_CLK_SRC - [1:0] */
+
+/*
+ * R65 (0x41) - Clocking 3
+ */
+#define WM8993_CLK_DCS_DIV_MASK 0x3C00 /* CLK_DCS_DIV - [13:10] */
+#define WM8993_CLK_DCS_DIV_SHIFT 10 /* CLK_DCS_DIV - [13:10] */
+#define WM8993_CLK_DCS_DIV_WIDTH 4 /* CLK_DCS_DIV - [13:10] */
+#define WM8993_SAMPLE_RATE_MASK 0x0380 /* SAMPLE_RATE - [9:7] */
+#define WM8993_SAMPLE_RATE_SHIFT 7 /* SAMPLE_RATE - [9:7] */
+#define WM8993_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [9:7] */
+#define WM8993_CLK_SYS_RATE_MASK 0x001E /* CLK_SYS_RATE - [4:1] */
+#define WM8993_CLK_SYS_RATE_SHIFT 1 /* CLK_SYS_RATE - [4:1] */
+#define WM8993_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [4:1] */
+#define WM8993_CLK_DSP_ENA 0x0001 /* CLK_DSP_ENA */
+#define WM8993_CLK_DSP_ENA_MASK 0x0001 /* CLK_DSP_ENA */
+#define WM8993_CLK_DSP_ENA_SHIFT 0 /* CLK_DSP_ENA */
+#define WM8993_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */
+
+/*
+ * R66 (0x42) - Clocking 4
+ */
+#define WM8993_DAC_DIV4 0x0200 /* DAC_DIV4 */
+#define WM8993_DAC_DIV4_MASK 0x0200 /* DAC_DIV4 */
+#define WM8993_DAC_DIV4_SHIFT 9 /* DAC_DIV4 */
+#define WM8993_DAC_DIV4_WIDTH 1 /* DAC_DIV4 */
+#define WM8993_CLK_256K_DIV_MASK 0x007E /* CLK_256K_DIV - [6:1] */
+#define WM8993_CLK_256K_DIV_SHIFT 1 /* CLK_256K_DIV - [6:1] */
+#define WM8993_CLK_256K_DIV_WIDTH 6 /* CLK_256K_DIV - [6:1] */
+#define WM8993_SR_MODE 0x0001 /* SR_MODE */
+#define WM8993_SR_MODE_MASK 0x0001 /* SR_MODE */
+#define WM8993_SR_MODE_SHIFT 0 /* SR_MODE */
+#define WM8993_SR_MODE_WIDTH 1 /* SR_MODE */
+
+/*
+ * R67 (0x43) - MW Slave Control
+ */
+#define WM8993_MASK_WRITE_ENA 0x0001 /* MASK_WRITE_ENA */
+#define WM8993_MASK_WRITE_ENA_MASK 0x0001 /* MASK_WRITE_ENA */
+#define WM8993_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */
+#define WM8993_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */
+
+/*
+ * R69 (0x45) - Bus Control 1
+ */
+#define WM8993_CLK_SYS_ENA 0x0002 /* CLK_SYS_ENA */
+#define WM8993_CLK_SYS_ENA_MASK 0x0002 /* CLK_SYS_ENA */
+#define WM8993_CLK_SYS_ENA_SHIFT 1 /* CLK_SYS_ENA */
+#define WM8993_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */
+
+/*
+ * R70 (0x46) - Write Sequencer 0
+ */
+#define WM8993_WSEQ_ENA 0x0100 /* WSEQ_ENA */
+#define WM8993_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */
+#define WM8993_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */
+#define WM8993_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM8993_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8993_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8993_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R71 (0x47) - Write Sequencer 1
+ */
+#define WM8993_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8993_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8993_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8993_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */
+#define WM8993_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */
+#define WM8993_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */
+#define WM8993_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */
+#define WM8993_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */
+#define WM8993_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R72 (0x48) - Write Sequencer 2
+ */
+#define WM8993_WSEQ_EOS 0x4000 /* WSEQ_EOS */
+#define WM8993_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */
+#define WM8993_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */
+#define WM8993_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */
+#define WM8993_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */
+#define WM8993_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */
+#define WM8993_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */
+#define WM8993_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */
+#define WM8993_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */
+#define WM8993_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */
+
+/*
+ * R73 (0x49) - Write Sequencer 3
+ */
+#define WM8993_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
+#define WM8993_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
+#define WM8993_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
+#define WM8993_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM8993_WSEQ_START 0x0100 /* WSEQ_START */
+#define WM8993_WSEQ_START_MASK 0x0100 /* WSEQ_START */
+#define WM8993_WSEQ_START_SHIFT 8 /* WSEQ_START */
+#define WM8993_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM8993_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */
+#define WM8993_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */
+#define WM8993_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R74 (0x4A) - Write Sequencer 4
+ */
+#define WM8993_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
+#define WM8993_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
+#define WM8993_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
+#define WM8993_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+
+/*
+ * R75 (0x4B) - Write Sequencer 5
+ */
+#define WM8993_WSEQ_CURRENT_INDEX_MASK 0x003F /* WSEQ_CURRENT_INDEX - [5:0] */
+#define WM8993_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [5:0] */
+#define WM8993_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [5:0] */
+
+/*
+ * R76 (0x4C) - Charge Pump 1
+ */
+#define WM8993_CP_ENA 0x8000 /* CP_ENA */
+#define WM8993_CP_ENA_MASK 0x8000 /* CP_ENA */
+#define WM8993_CP_ENA_SHIFT 15 /* CP_ENA */
+#define WM8993_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R81 (0x51) - Class W 0
+ */
+#define WM8993_CP_DYN_FREQ 0x0002 /* CP_DYN_FREQ */
+#define WM8993_CP_DYN_FREQ_MASK 0x0002 /* CP_DYN_FREQ */
+#define WM8993_CP_DYN_FREQ_SHIFT 1 /* CP_DYN_FREQ */
+#define WM8993_CP_DYN_FREQ_WIDTH 1 /* CP_DYN_FREQ */
+#define WM8993_CP_DYN_V 0x0001 /* CP_DYN_V */
+#define WM8993_CP_DYN_V_MASK 0x0001 /* CP_DYN_V */
+#define WM8993_CP_DYN_V_SHIFT 0 /* CP_DYN_V */
+#define WM8993_CP_DYN_V_WIDTH 1 /* CP_DYN_V */
+
+/*
+ * R84 (0x54) - DC Servo 0
+ */
+#define WM8993_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM8993_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM8993_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */
+#define WM8993_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */
+#define WM8993_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM8993_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM8993_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */
+#define WM8993_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */
+#define WM8993_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM8993_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM8993_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */
+#define WM8993_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */
+#define WM8993_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM8993_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM8993_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */
+#define WM8993_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */
+#define WM8993_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM8993_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM8993_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */
+#define WM8993_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */
+#define WM8993_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM8993_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM8993_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */
+#define WM8993_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */
+#define WM8993_DCS_TRIG_DAC_WR_1 0x0008 /* DCS_TRIG_DAC_WR_1 */
+#define WM8993_DCS_TRIG_DAC_WR_1_MASK 0x0008 /* DCS_TRIG_DAC_WR_1 */
+#define WM8993_DCS_TRIG_DAC_WR_1_SHIFT 3 /* DCS_TRIG_DAC_WR_1 */
+#define WM8993_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */
+#define WM8993_DCS_TRIG_DAC_WR_0 0x0004 /* DCS_TRIG_DAC_WR_0 */
+#define WM8993_DCS_TRIG_DAC_WR_0_MASK 0x0004 /* DCS_TRIG_DAC_WR_0 */
+#define WM8993_DCS_TRIG_DAC_WR_0_SHIFT 2 /* DCS_TRIG_DAC_WR_0 */
+#define WM8993_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */
+#define WM8993_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM8993_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM8993_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */
+#define WM8993_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */
+#define WM8993_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM8993_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM8993_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */
+#define WM8993_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */
+
+/*
+ * R85 (0x55) - DC Servo 1
+ */
+#define WM8993_DCS_SERIES_NO_01_MASK 0x0FE0 /* DCS_SERIES_NO_01 - [11:5] */
+#define WM8993_DCS_SERIES_NO_01_SHIFT 5 /* DCS_SERIES_NO_01 - [11:5] */
+#define WM8993_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [11:5] */
+#define WM8993_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8993_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8993_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */
+
+/*
+ * R87 (0x57) - DC Servo 3
+ */
+#define WM8993_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8993_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8993_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8993_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8993_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8993_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */
+
+/*
+ * R88 (0x58) - DC Servo Readback 0
+ */
+#define WM8993_DCS_DATAPATH_BUSY 0x4000 /* DCS_DATAPATH_BUSY */
+#define WM8993_DCS_DATAPATH_BUSY_MASK 0x4000 /* DCS_DATAPATH_BUSY */
+#define WM8993_DCS_DATAPATH_BUSY_SHIFT 14 /* DCS_DATAPATH_BUSY */
+#define WM8993_DCS_DATAPATH_BUSY_WIDTH 1 /* DCS_DATAPATH_BUSY */
+#define WM8993_DCS_CHANNEL_MASK 0x3000 /* DCS_CHANNEL - [13:12] */
+#define WM8993_DCS_CHANNEL_SHIFT 12 /* DCS_CHANNEL - [13:12] */
+#define WM8993_DCS_CHANNEL_WIDTH 2 /* DCS_CHANNEL - [13:12] */
+#define WM8993_DCS_CAL_COMPLETE_MASK 0x0300 /* DCS_CAL_COMPLETE - [9:8] */
+#define WM8993_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [9:8] */
+#define WM8993_DCS_CAL_COMPLETE_WIDTH 2 /* DCS_CAL_COMPLETE - [9:8] */
+#define WM8993_DCS_DAC_WR_COMPLETE_MASK 0x0030 /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM8993_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM8993_DCS_DAC_WR_COMPLETE_WIDTH 2 /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM8993_DCS_STARTUP_COMPLETE_MASK 0x0003 /* DCS_STARTUP_COMPLETE - [1:0] */
+#define WM8993_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [1:0] */
+#define WM8993_DCS_STARTUP_COMPLETE_WIDTH 2 /* DCS_STARTUP_COMPLETE - [1:0] */
+
+/*
+ * R89 (0x59) - DC Servo Readback 1
+ */
+#define WM8993_DCS_INTEG_CHAN_1_MASK 0x00FF /* DCS_INTEG_CHAN_1 - [7:0] */
+#define WM8993_DCS_INTEG_CHAN_1_SHIFT 0 /* DCS_INTEG_CHAN_1 - [7:0] */
+#define WM8993_DCS_INTEG_CHAN_1_WIDTH 8 /* DCS_INTEG_CHAN_1 - [7:0] */
+
+/*
+ * R90 (0x5A) - DC Servo Readback 2
+ */
+#define WM8993_DCS_INTEG_CHAN_0_MASK 0x00FF /* DCS_INTEG_CHAN_0 - [7:0] */
+#define WM8993_DCS_INTEG_CHAN_0_SHIFT 0 /* DCS_INTEG_CHAN_0 - [7:0] */
+#define WM8993_DCS_INTEG_CHAN_0_WIDTH 8 /* DCS_INTEG_CHAN_0 - [7:0] */
+
+/*
+ * R96 (0x60) - Analogue HP 0
+ */
+#define WM8993_HPOUT1_AUTO_PU 0x0100 /* HPOUT1_AUTO_PU */
+#define WM8993_HPOUT1_AUTO_PU_MASK 0x0100 /* HPOUT1_AUTO_PU */
+#define WM8993_HPOUT1_AUTO_PU_SHIFT 8 /* HPOUT1_AUTO_PU */
+#define WM8993_HPOUT1_AUTO_PU_WIDTH 1 /* HPOUT1_AUTO_PU */
+#define WM8993_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */
+#define WM8993_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */
+#define WM8993_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */
+#define WM8993_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */
+#define WM8993_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */
+#define WM8993_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */
+#define WM8993_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */
+#define WM8993_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */
+#define WM8993_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */
+#define WM8993_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */
+#define WM8993_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */
+#define WM8993_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */
+#define WM8993_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */
+#define WM8993_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */
+#define WM8993_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */
+#define WM8993_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */
+#define WM8993_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */
+#define WM8993_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */
+#define WM8993_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */
+#define WM8993_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */
+#define WM8993_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */
+#define WM8993_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */
+#define WM8993_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */
+#define WM8993_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */
+
+/*
+ * R98 (0x62) - EQ1
+ */
+#define WM8993_EQ_ENA 0x0001 /* EQ_ENA */
+#define WM8993_EQ_ENA_MASK 0x0001 /* EQ_ENA */
+#define WM8993_EQ_ENA_SHIFT 0 /* EQ_ENA */
+#define WM8993_EQ_ENA_WIDTH 1 /* EQ_ENA */
+
+/*
+ * R99 (0x63) - EQ2
+ */
+#define WM8993_EQ_B1_GAIN_MASK 0x001F /* EQ_B1_GAIN - [4:0] */
+#define WM8993_EQ_B1_GAIN_SHIFT 0 /* EQ_B1_GAIN - [4:0] */
+#define WM8993_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [4:0] */
+
+/*
+ * R100 (0x64) - EQ3
+ */
+#define WM8993_EQ_B2_GAIN_MASK 0x001F /* EQ_B2_GAIN - [4:0] */
+#define WM8993_EQ_B2_GAIN_SHIFT 0 /* EQ_B2_GAIN - [4:0] */
+#define WM8993_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [4:0] */
+
+/*
+ * R101 (0x65) - EQ4
+ */
+#define WM8993_EQ_B3_GAIN_MASK 0x001F /* EQ_B3_GAIN - [4:0] */
+#define WM8993_EQ_B3_GAIN_SHIFT 0 /* EQ_B3_GAIN - [4:0] */
+#define WM8993_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [4:0] */
+
+/*
+ * R102 (0x66) - EQ5
+ */
+#define WM8993_EQ_B4_GAIN_MASK 0x001F /* EQ_B4_GAIN - [4:0] */
+#define WM8993_EQ_B4_GAIN_SHIFT 0 /* EQ_B4_GAIN - [4:0] */
+#define WM8993_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [4:0] */
+
+/*
+ * R103 (0x67) - EQ6
+ */
+#define WM8993_EQ_B5_GAIN_MASK 0x001F /* EQ_B5_GAIN - [4:0] */
+#define WM8993_EQ_B5_GAIN_SHIFT 0 /* EQ_B5_GAIN - [4:0] */
+#define WM8993_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [4:0] */
+
+/*
+ * R104 (0x68) - EQ7
+ */
+#define WM8993_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */
+#define WM8993_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */
+#define WM8993_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */
+
+/*
+ * R105 (0x69) - EQ8
+ */
+#define WM8993_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */
+#define WM8993_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */
+#define WM8993_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */
+
+/*
+ * R106 (0x6A) - EQ9
+ */
+#define WM8993_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */
+#define WM8993_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */
+#define WM8993_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */
+
+/*
+ * R107 (0x6B) - EQ10
+ */
+#define WM8993_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */
+#define WM8993_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */
+#define WM8993_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */
+
+/*
+ * R108 (0x6C) - EQ11
+ */
+#define WM8993_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */
+#define WM8993_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */
+#define WM8993_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */
+
+/*
+ * R109 (0x6D) - EQ12
+ */
+#define WM8993_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */
+#define WM8993_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */
+#define WM8993_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */
+
+/*
+ * R110 (0x6E) - EQ13
+ */
+#define WM8993_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */
+#define WM8993_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */
+#define WM8993_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */
+
+/*
+ * R111 (0x6F) - EQ14
+ */
+#define WM8993_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */
+#define WM8993_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */
+#define WM8993_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */
+
+/*
+ * R112 (0x70) - EQ15
+ */
+#define WM8993_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */
+#define WM8993_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */
+#define WM8993_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */
+
+/*
+ * R113 (0x71) - EQ16
+ */
+#define WM8993_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */
+#define WM8993_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */
+#define WM8993_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */
+
+/*
+ * R114 (0x72) - EQ17
+ */
+#define WM8993_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */
+#define WM8993_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */
+#define WM8993_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */
+
+/*
+ * R115 (0x73) - EQ18
+ */
+#define WM8993_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */
+#define WM8993_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */
+#define WM8993_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */
+
+/*
+ * R116 (0x74) - EQ19
+ */
+#define WM8993_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */
+#define WM8993_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */
+#define WM8993_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */
+
+/*
+ * R117 (0x75) - EQ20
+ */
+#define WM8993_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */
+#define WM8993_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */
+#define WM8993_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */
+
+/*
+ * R118 (0x76) - EQ21
+ */
+#define WM8993_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */
+#define WM8993_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */
+#define WM8993_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */
+
+/*
+ * R119 (0x77) - EQ22
+ */
+#define WM8993_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */
+#define WM8993_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */
+#define WM8993_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */
+
+/*
+ * R120 (0x78) - EQ23
+ */
+#define WM8993_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */
+#define WM8993_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */
+#define WM8993_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */
+
+/*
+ * R121 (0x79) - EQ24
+ */
+#define WM8993_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */
+#define WM8993_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */
+#define WM8993_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */
+
+/*
+ * R122 (0x7A) - Digital Pulls
+ */
+#define WM8993_MCLK_PU 0x0080 /* MCLK_PU */
+#define WM8993_MCLK_PU_MASK 0x0080 /* MCLK_PU */
+#define WM8993_MCLK_PU_SHIFT 7 /* MCLK_PU */
+#define WM8993_MCLK_PU_WIDTH 1 /* MCLK_PU */
+#define WM8993_MCLK_PD 0x0040 /* MCLK_PD */
+#define WM8993_MCLK_PD_MASK 0x0040 /* MCLK_PD */
+#define WM8993_MCLK_PD_SHIFT 6 /* MCLK_PD */
+#define WM8993_MCLK_PD_WIDTH 1 /* MCLK_PD */
+#define WM8993_DACDAT_PU 0x0020 /* DACDAT_PU */
+#define WM8993_DACDAT_PU_MASK 0x0020 /* DACDAT_PU */
+#define WM8993_DACDAT_PU_SHIFT 5 /* DACDAT_PU */
+#define WM8993_DACDAT_PU_WIDTH 1 /* DACDAT_PU */
+#define WM8993_DACDAT_PD 0x0010 /* DACDAT_PD */
+#define WM8993_DACDAT_PD_MASK 0x0010 /* DACDAT_PD */
+#define WM8993_DACDAT_PD_SHIFT 4 /* DACDAT_PD */
+#define WM8993_DACDAT_PD_WIDTH 1 /* DACDAT_PD */
+#define WM8993_LRCLK_PU 0x0008 /* LRCLK_PU */
+#define WM8993_LRCLK_PU_MASK 0x0008 /* LRCLK_PU */
+#define WM8993_LRCLK_PU_SHIFT 3 /* LRCLK_PU */
+#define WM8993_LRCLK_PU_WIDTH 1 /* LRCLK_PU */
+#define WM8993_LRCLK_PD 0x0004 /* LRCLK_PD */
+#define WM8993_LRCLK_PD_MASK 0x0004 /* LRCLK_PD */
+#define WM8993_LRCLK_PD_SHIFT 2 /* LRCLK_PD */
+#define WM8993_LRCLK_PD_WIDTH 1 /* LRCLK_PD */
+#define WM8993_BCLK_PU 0x0002 /* BCLK_PU */
+#define WM8993_BCLK_PU_MASK 0x0002 /* BCLK_PU */
+#define WM8993_BCLK_PU_SHIFT 1 /* BCLK_PU */
+#define WM8993_BCLK_PU_WIDTH 1 /* BCLK_PU */
+#define WM8993_BCLK_PD 0x0001 /* BCLK_PD */
+#define WM8993_BCLK_PD_MASK 0x0001 /* BCLK_PD */
+#define WM8993_BCLK_PD_SHIFT 0 /* BCLK_PD */
+#define WM8993_BCLK_PD_WIDTH 1 /* BCLK_PD */
+
+/*
+ * R123 (0x7B) - DRC Control 1
+ */
+#define WM8993_DRC_ENA 0x8000 /* DRC_ENA */
+#define WM8993_DRC_ENA_MASK 0x8000 /* DRC_ENA */
+#define WM8993_DRC_ENA_SHIFT 15 /* DRC_ENA */
+#define WM8993_DRC_ENA_WIDTH 1 /* DRC_ENA */
+#define WM8993_DRC_DAC_PATH 0x4000 /* DRC_DAC_PATH */
+#define WM8993_DRC_DAC_PATH_MASK 0x4000 /* DRC_DAC_PATH */
+#define WM8993_DRC_DAC_PATH_SHIFT 14 /* DRC_DAC_PATH */
+#define WM8993_DRC_DAC_PATH_WIDTH 1 /* DRC_DAC_PATH */
+#define WM8993_DRC_SMOOTH_ENA 0x0800 /* DRC_SMOOTH_ENA */
+#define WM8993_DRC_SMOOTH_ENA_MASK 0x0800 /* DRC_SMOOTH_ENA */
+#define WM8993_DRC_SMOOTH_ENA_SHIFT 11 /* DRC_SMOOTH_ENA */
+#define WM8993_DRC_SMOOTH_ENA_WIDTH 1 /* DRC_SMOOTH_ENA */
+#define WM8993_DRC_QR_ENA 0x0400 /* DRC_QR_ENA */
+#define WM8993_DRC_QR_ENA_MASK 0x0400 /* DRC_QR_ENA */
+#define WM8993_DRC_QR_ENA_SHIFT 10 /* DRC_QR_ENA */
+#define WM8993_DRC_QR_ENA_WIDTH 1 /* DRC_QR_ENA */
+#define WM8993_DRC_ANTICLIP_ENA 0x0200 /* DRC_ANTICLIP_ENA */
+#define WM8993_DRC_ANTICLIP_ENA_MASK 0x0200 /* DRC_ANTICLIP_ENA */
+#define WM8993_DRC_ANTICLIP_ENA_SHIFT 9 /* DRC_ANTICLIP_ENA */
+#define WM8993_DRC_ANTICLIP_ENA_WIDTH 1 /* DRC_ANTICLIP_ENA */
+#define WM8993_DRC_HYST_ENA 0x0100 /* DRC_HYST_ENA */
+#define WM8993_DRC_HYST_ENA_MASK 0x0100 /* DRC_HYST_ENA */
+#define WM8993_DRC_HYST_ENA_SHIFT 8 /* DRC_HYST_ENA */
+#define WM8993_DRC_HYST_ENA_WIDTH 1 /* DRC_HYST_ENA */
+#define WM8993_DRC_THRESH_HYST_MASK 0x0030 /* DRC_THRESH_HYST - [5:4] */
+#define WM8993_DRC_THRESH_HYST_SHIFT 4 /* DRC_THRESH_HYST - [5:4] */
+#define WM8993_DRC_THRESH_HYST_WIDTH 2 /* DRC_THRESH_HYST - [5:4] */
+#define WM8993_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */
+#define WM8993_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */
+#define WM8993_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */
+#define WM8993_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */
+#define WM8993_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */
+#define WM8993_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R124 (0x7C) - DRC Control 2
+ */
+#define WM8993_DRC_ATTACK_RATE_MASK 0xF000 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8993_DRC_ATTACK_RATE_SHIFT 12 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8993_DRC_ATTACK_RATE_WIDTH 4 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8993_DRC_DECAY_RATE_MASK 0x0F00 /* DRC_DECAY_RATE - [11:8] */
+#define WM8993_DRC_DECAY_RATE_SHIFT 8 /* DRC_DECAY_RATE - [11:8] */
+#define WM8993_DRC_DECAY_RATE_WIDTH 4 /* DRC_DECAY_RATE - [11:8] */
+#define WM8993_DRC_THRESH_COMP_MASK 0x00FC /* DRC_THRESH_COMP - [7:2] */
+#define WM8993_DRC_THRESH_COMP_SHIFT 2 /* DRC_THRESH_COMP - [7:2] */
+#define WM8993_DRC_THRESH_COMP_WIDTH 6 /* DRC_THRESH_COMP - [7:2] */
+
+/*
+ * R125 (0x7D) - DRC Control 3
+ */
+#define WM8993_DRC_AMP_COMP_MASK 0xF800 /* DRC_AMP_COMP - [15:11] */
+#define WM8993_DRC_AMP_COMP_SHIFT 11 /* DRC_AMP_COMP - [15:11] */
+#define WM8993_DRC_AMP_COMP_WIDTH 5 /* DRC_AMP_COMP - [15:11] */
+#define WM8993_DRC_R0_SLOPE_COMP_MASK 0x0700 /* DRC_R0_SLOPE_COMP - [10:8] */
+#define WM8993_DRC_R0_SLOPE_COMP_SHIFT 8 /* DRC_R0_SLOPE_COMP - [10:8] */
+#define WM8993_DRC_R0_SLOPE_COMP_WIDTH 3 /* DRC_R0_SLOPE_COMP - [10:8] */
+#define WM8993_DRC_FF_DELAY 0x0080 /* DRC_FF_DELAY */
+#define WM8993_DRC_FF_DELAY_MASK 0x0080 /* DRC_FF_DELAY */
+#define WM8993_DRC_FF_DELAY_SHIFT 7 /* DRC_FF_DELAY */
+#define WM8993_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */
+#define WM8993_DRC_THRESH_QR_MASK 0x000C /* DRC_THRESH_QR - [3:2] */
+#define WM8993_DRC_THRESH_QR_SHIFT 2 /* DRC_THRESH_QR - [3:2] */
+#define WM8993_DRC_THRESH_QR_WIDTH 2 /* DRC_THRESH_QR - [3:2] */
+#define WM8993_DRC_RATE_QR_MASK 0x0003 /* DRC_RATE_QR - [1:0] */
+#define WM8993_DRC_RATE_QR_SHIFT 0 /* DRC_RATE_QR - [1:0] */
+#define WM8993_DRC_RATE_QR_WIDTH 2 /* DRC_RATE_QR - [1:0] */
+
+/*
+ * R126 (0x7E) - DRC Control 4
+ */
+#define WM8993_DRC_R1_SLOPE_COMP_MASK 0xE000 /* DRC_R1_SLOPE_COMP - [15:13] */
+#define WM8993_DRC_R1_SLOPE_COMP_SHIFT 13 /* DRC_R1_SLOPE_COMP - [15:13] */
+#define WM8993_DRC_R1_SLOPE_COMP_WIDTH 3 /* DRC_R1_SLOPE_COMP - [15:13] */
+#define WM8993_DRC_STARTUP_GAIN_MASK 0x1F00 /* DRC_STARTUP_GAIN - [12:8] */
+#define WM8993_DRC_STARTUP_GAIN_SHIFT 8 /* DRC_STARTUP_GAIN - [12:8] */
+#define WM8993_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [12:8] */
+
+#endif
diff --git a/sound/soc/codecs/wm8994-tables.c b/sound/soc/codecs/wm8994-tables.c
new file mode 100644
index 00000000..a87adbd0
--- /dev/null
+++ b/sound/soc/codecs/wm8994-tables.c
@@ -0,0 +1,3147 @@
+#include "wm8994.h"
+
+const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = {
+ { 0xFFFF, 0xFFFF }, /* R0 - Software Reset */
+ { 0x3B37, 0x3B37 }, /* R1 - Power Management (1) */
+ { 0x6BF0, 0x6BF0 }, /* R2 - Power Management (2) */
+ { 0x3FF0, 0x3FF0 }, /* R3 - Power Management (3) */
+ { 0x3F3F, 0x3F3F }, /* R4 - Power Management (4) */
+ { 0x3F0F, 0x3F0F }, /* R5 - Power Management (5) */
+ { 0x003F, 0x003F }, /* R6 - Power Management (6) */
+ { 0x0000, 0x0000 }, /* R7 */
+ { 0x0000, 0x0000 }, /* R8 */
+ { 0x0000, 0x0000 }, /* R9 */
+ { 0x0000, 0x0000 }, /* R10 */
+ { 0x0000, 0x0000 }, /* R11 */
+ { 0x0000, 0x0000 }, /* R12 */
+ { 0x0000, 0x0000 }, /* R13 */
+ { 0x0000, 0x0000 }, /* R14 */
+ { 0x0000, 0x0000 }, /* R15 */
+ { 0x0000, 0x0000 }, /* R16 */
+ { 0x0000, 0x0000 }, /* R17 */
+ { 0x0000, 0x0000 }, /* R18 */
+ { 0x0000, 0x0000 }, /* R19 */
+ { 0x0000, 0x0000 }, /* R20 */
+ { 0x01C0, 0x01C0 }, /* R21 - Input Mixer (1) */
+ { 0x0000, 0x0000 }, /* R22 */
+ { 0x0000, 0x0000 }, /* R23 */
+ { 0x00DF, 0x01DF }, /* R24 - Left Line Input 1&2 Volume */
+ { 0x00DF, 0x01DF }, /* R25 - Left Line Input 3&4 Volume */
+ { 0x00DF, 0x01DF }, /* R26 - Right Line Input 1&2 Volume */
+ { 0x00DF, 0x01DF }, /* R27 - Right Line Input 3&4 Volume */
+ { 0x00FF, 0x01FF }, /* R28 - Left Output Volume */
+ { 0x00FF, 0x01FF }, /* R29 - Right Output Volume */
+ { 0x0077, 0x0077 }, /* R30 - Line Outputs Volume */
+ { 0x0030, 0x0030 }, /* R31 - HPOUT2 Volume */
+ { 0x00FF, 0x01FF }, /* R32 - Left OPGA Volume */
+ { 0x00FF, 0x01FF }, /* R33 - Right OPGA Volume */
+ { 0x007F, 0x007F }, /* R34 - SPKMIXL Attenuation */
+ { 0x017F, 0x017F }, /* R35 - SPKMIXR Attenuation */
+ { 0x003F, 0x003F }, /* R36 - SPKOUT Mixers */
+ { 0x003F, 0x003F }, /* R37 - ClassD */
+ { 0x00FF, 0x01FF }, /* R38 - Speaker Volume Left */
+ { 0x00FF, 0x01FF }, /* R39 - Speaker Volume Right */
+ { 0x00FF, 0x00FF }, /* R40 - Input Mixer (2) */
+ { 0x01B7, 0x01B7 }, /* R41 - Input Mixer (3) */
+ { 0x01B7, 0x01B7 }, /* R42 - Input Mixer (4) */
+ { 0x01C7, 0x01C7 }, /* R43 - Input Mixer (5) */
+ { 0x01C7, 0x01C7 }, /* R44 - Input Mixer (6) */
+ { 0x01FF, 0x01FF }, /* R45 - Output Mixer (1) */
+ { 0x01FF, 0x01FF }, /* R46 - Output Mixer (2) */
+ { 0x0FFF, 0x0FFF }, /* R47 - Output Mixer (3) */
+ { 0x0FFF, 0x0FFF }, /* R48 - Output Mixer (4) */
+ { 0x0FFF, 0x0FFF }, /* R49 - Output Mixer (5) */
+ { 0x0FFF, 0x0FFF }, /* R50 - Output Mixer (6) */
+ { 0x0038, 0x0038 }, /* R51 - HPOUT2 Mixer */
+ { 0x0077, 0x0077 }, /* R52 - Line Mixer (1) */
+ { 0x0077, 0x0077 }, /* R53 - Line Mixer (2) */
+ { 0x03FF, 0x03FF }, /* R54 - Speaker Mixer */
+ { 0x00C1, 0x00C1 }, /* R55 - Additional Control */
+ { 0x00F0, 0x00F0 }, /* R56 - AntiPOP (1) */
+ { 0x01EF, 0x01EF }, /* R57 - AntiPOP (2) */
+ { 0x00FF, 0x00FF }, /* R58 - MICBIAS */
+ { 0x000F, 0x000F }, /* R59 - LDO 1 */
+ { 0x0007, 0x0007 }, /* R60 - LDO 2 */
+ { 0xFFFF, 0xFFFF }, /* R61 */
+ { 0xFFFF, 0xFFFF }, /* R62 */
+ { 0x0000, 0x0000 }, /* R63 */
+ { 0x0000, 0x0000 }, /* R64 */
+ { 0x0000, 0x0000 }, /* R65 */
+ { 0x0000, 0x0000 }, /* R66 */
+ { 0x0000, 0x0000 }, /* R67 */
+ { 0x0000, 0x0000 }, /* R68 */
+ { 0x0000, 0x0000 }, /* R69 */
+ { 0x0000, 0x0000 }, /* R70 */
+ { 0x0000, 0x0000 }, /* R71 */
+ { 0x0000, 0x0000 }, /* R72 */
+ { 0x0000, 0x0000 }, /* R73 */
+ { 0x0000, 0x0000 }, /* R74 */
+ { 0x0000, 0x0000 }, /* R75 */
+ { 0x8000, 0x8000 }, /* R76 - Charge Pump (1) */
+ { 0x0000, 0x0000 }, /* R77 */
+ { 0x0000, 0x0000 }, /* R78 */
+ { 0x0000, 0x0000 }, /* R79 */
+ { 0x0000, 0x0000 }, /* R80 */
+ { 0x0301, 0x0301 }, /* R81 - Class W (1) */
+ { 0x0000, 0x0000 }, /* R82 */
+ { 0x0000, 0x0000 }, /* R83 */
+ { 0x333F, 0x333F }, /* R84 - DC Servo (1) */
+ { 0x0FEF, 0x0FEF }, /* R85 - DC Servo (2) */
+ { 0x0000, 0x0000 }, /* R86 */
+ { 0xFFFF, 0xFFFF }, /* R87 - DC Servo (4) */
+ { 0x0333, 0x0000 }, /* R88 - DC Servo Readback */
+ { 0x0000, 0x0000 }, /* R89 */
+ { 0x0000, 0x0000 }, /* R90 */
+ { 0x0000, 0x0000 }, /* R91 */
+ { 0x0000, 0x0000 }, /* R92 */
+ { 0x0000, 0x0000 }, /* R93 */
+ { 0x0000, 0x0000 }, /* R94 */
+ { 0x0000, 0x0000 }, /* R95 */
+ { 0x00EE, 0x00EE }, /* R96 - Analogue HP (1) */
+ { 0x0000, 0x0000 }, /* R97 */
+ { 0x0000, 0x0000 }, /* R98 */
+ { 0x0000, 0x0000 }, /* R99 */
+ { 0x0000, 0x0000 }, /* R100 */
+ { 0x0000, 0x0000 }, /* R101 */
+ { 0x0000, 0x0000 }, /* R102 */
+ { 0x0000, 0x0000 }, /* R103 */
+ { 0x0000, 0x0000 }, /* R104 */
+ { 0x0000, 0x0000 }, /* R105 */
+ { 0x0000, 0x0000 }, /* R106 */
+ { 0x0000, 0x0000 }, /* R107 */
+ { 0x0000, 0x0000 }, /* R108 */
+ { 0x0000, 0x0000 }, /* R109 */
+ { 0x0000, 0x0000 }, /* R110 */
+ { 0x0000, 0x0000 }, /* R111 */
+ { 0x0000, 0x0000 }, /* R112 */
+ { 0x0000, 0x0000 }, /* R113 */
+ { 0x0000, 0x0000 }, /* R114 */
+ { 0x0000, 0x0000 }, /* R115 */
+ { 0x0000, 0x0000 }, /* R116 */
+ { 0x0000, 0x0000 }, /* R117 */
+ { 0x0000, 0x0000 }, /* R118 */
+ { 0x0000, 0x0000 }, /* R119 */
+ { 0x0000, 0x0000 }, /* R120 */
+ { 0x0000, 0x0000 }, /* R121 */
+ { 0x0000, 0x0000 }, /* R122 */
+ { 0x0000, 0x0000 }, /* R123 */
+ { 0x0000, 0x0000 }, /* R124 */
+ { 0x0000, 0x0000 }, /* R125 */
+ { 0x0000, 0x0000 }, /* R126 */
+ { 0x0000, 0x0000 }, /* R127 */
+ { 0x0000, 0x0000 }, /* R128 */
+ { 0x0000, 0x0000 }, /* R129 */
+ { 0x0000, 0x0000 }, /* R130 */
+ { 0x0000, 0x0000 }, /* R131 */
+ { 0x0000, 0x0000 }, /* R132 */
+ { 0x0000, 0x0000 }, /* R133 */
+ { 0x0000, 0x0000 }, /* R134 */
+ { 0x0000, 0x0000 }, /* R135 */
+ { 0x0000, 0x0000 }, /* R136 */
+ { 0x0000, 0x0000 }, /* R137 */
+ { 0x0000, 0x0000 }, /* R138 */
+ { 0x0000, 0x0000 }, /* R139 */
+ { 0x0000, 0x0000 }, /* R140 */
+ { 0x0000, 0x0000 }, /* R141 */
+ { 0x0000, 0x0000 }, /* R142 */
+ { 0x0000, 0x0000 }, /* R143 */
+ { 0x0000, 0x0000 }, /* R144 */
+ { 0x0000, 0x0000 }, /* R145 */
+ { 0x0000, 0x0000 }, /* R146 */
+ { 0x0000, 0x0000 }, /* R147 */
+ { 0x0000, 0x0000 }, /* R148 */
+ { 0x0000, 0x0000 }, /* R149 */
+ { 0x0000, 0x0000 }, /* R150 */
+ { 0x0000, 0x0000 }, /* R151 */
+ { 0x0000, 0x0000 }, /* R152 */
+ { 0x0000, 0x0000 }, /* R153 */
+ { 0x0000, 0x0000 }, /* R154 */
+ { 0x0000, 0x0000 }, /* R155 */
+ { 0x0000, 0x0000 }, /* R156 */
+ { 0x0000, 0x0000 }, /* R157 */
+ { 0x0000, 0x0000 }, /* R158 */
+ { 0x0000, 0x0000 }, /* R159 */
+ { 0x0000, 0x0000 }, /* R160 */
+ { 0x0000, 0x0000 }, /* R161 */
+ { 0x0000, 0x0000 }, /* R162 */
+ { 0x0000, 0x0000 }, /* R163 */
+ { 0x0000, 0x0000 }, /* R164 */
+ { 0x0000, 0x0000 }, /* R165 */
+ { 0x0000, 0x0000 }, /* R166 */
+ { 0x0000, 0x0000 }, /* R167 */
+ { 0x0000, 0x0000 }, /* R168 */
+ { 0x0000, 0x0000 }, /* R169 */
+ { 0x0000, 0x0000 }, /* R170 */
+ { 0x0000, 0x0000 }, /* R171 */
+ { 0x0000, 0x0000 }, /* R172 */
+ { 0x0000, 0x0000 }, /* R173 */
+ { 0x0000, 0x0000 }, /* R174 */
+ { 0x0000, 0x0000 }, /* R175 */
+ { 0x0000, 0x0000 }, /* R176 */
+ { 0x0000, 0x0000 }, /* R177 */
+ { 0x0000, 0x0000 }, /* R178 */
+ { 0x0000, 0x0000 }, /* R179 */
+ { 0x0000, 0x0000 }, /* R180 */
+ { 0x0000, 0x0000 }, /* R181 */
+ { 0x0000, 0x0000 }, /* R182 */
+ { 0x0000, 0x0000 }, /* R183 */
+ { 0x0000, 0x0000 }, /* R184 */
+ { 0x0000, 0x0000 }, /* R185 */
+ { 0x0000, 0x0000 }, /* R186 */
+ { 0x0000, 0x0000 }, /* R187 */
+ { 0x0000, 0x0000 }, /* R188 */
+ { 0x0000, 0x0000 }, /* R189 */
+ { 0x0000, 0x0000 }, /* R190 */
+ { 0x0000, 0x0000 }, /* R191 */
+ { 0x0000, 0x0000 }, /* R192 */
+ { 0x0000, 0x0000 }, /* R193 */
+ { 0x0000, 0x0000 }, /* R194 */
+ { 0x0000, 0x0000 }, /* R195 */
+ { 0x0000, 0x0000 }, /* R196 */
+ { 0x0000, 0x0000 }, /* R197 */
+ { 0x0000, 0x0000 }, /* R198 */
+ { 0x0000, 0x0000 }, /* R199 */
+ { 0x0000, 0x0000 }, /* R200 */
+ { 0x0000, 0x0000 }, /* R201 */
+ { 0x0000, 0x0000 }, /* R202 */
+ { 0x0000, 0x0000 }, /* R203 */
+ { 0x0000, 0x0000 }, /* R204 */
+ { 0x0000, 0x0000 }, /* R205 */
+ { 0x0000, 0x0000 }, /* R206 */
+ { 0x0000, 0x0000 }, /* R207 */
+ { 0xFFFF, 0xFFFF }, /* R208 */
+ { 0xFFFF, 0xFFFF }, /* R209 */
+ { 0xFFFF, 0xFFFF }, /* R210 */
+ { 0x0000, 0x0000 }, /* R211 */
+ { 0x0000, 0x0000 }, /* R212 */
+ { 0x0000, 0x0000 }, /* R213 */
+ { 0x0000, 0x0000 }, /* R214 */
+ { 0x0000, 0x0000 }, /* R215 */
+ { 0x0000, 0x0000 }, /* R216 */
+ { 0x0000, 0x0000 }, /* R217 */
+ { 0x0000, 0x0000 }, /* R218 */
+ { 0x0000, 0x0000 }, /* R219 */
+ { 0x0000, 0x0000 }, /* R220 */
+ { 0x0000, 0x0000 }, /* R221 */
+ { 0x0000, 0x0000 }, /* R222 */
+ { 0x0000, 0x0000 }, /* R223 */
+ { 0x0000, 0x0000 }, /* R224 */
+ { 0x0000, 0x0000 }, /* R225 */
+ { 0x0000, 0x0000 }, /* R226 */
+ { 0x0000, 0x0000 }, /* R227 */
+ { 0x0000, 0x0000 }, /* R228 */
+ { 0x0000, 0x0000 }, /* R229 */
+ { 0x0000, 0x0000 }, /* R230 */
+ { 0x0000, 0x0000 }, /* R231 */
+ { 0x0000, 0x0000 }, /* R232 */
+ { 0x0000, 0x0000 }, /* R233 */
+ { 0x0000, 0x0000 }, /* R234 */
+ { 0x0000, 0x0000 }, /* R235 */
+ { 0x0000, 0x0000 }, /* R236 */
+ { 0x0000, 0x0000 }, /* R237 */
+ { 0x0000, 0x0000 }, /* R238 */
+ { 0x0000, 0x0000 }, /* R239 */
+ { 0x0000, 0x0000 }, /* R240 */
+ { 0x0000, 0x0000 }, /* R241 */
+ { 0x0000, 0x0000 }, /* R242 */
+ { 0x0000, 0x0000 }, /* R243 */
+ { 0x0000, 0x0000 }, /* R244 */
+ { 0x0000, 0x0000 }, /* R245 */
+ { 0x0000, 0x0000 }, /* R246 */
+ { 0x0000, 0x0000 }, /* R247 */
+ { 0x0000, 0x0000 }, /* R248 */
+ { 0x0000, 0x0000 }, /* R249 */
+ { 0x0000, 0x0000 }, /* R250 */
+ { 0x0000, 0x0000 }, /* R251 */
+ { 0x0000, 0x0000 }, /* R252 */
+ { 0x0000, 0x0000 }, /* R253 */
+ { 0x0000, 0x0000 }, /* R254 */
+ { 0x0000, 0x0000 }, /* R255 */
+ { 0x000F, 0x0000 }, /* R256 - Chip Revision */
+ { 0x0074, 0x0074 }, /* R257 - Control Interface */
+ { 0x0000, 0x0000 }, /* R258 */
+ { 0x0000, 0x0000 }, /* R259 */
+ { 0x0000, 0x0000 }, /* R260 */
+ { 0x0000, 0x0000 }, /* R261 */
+ { 0x0000, 0x0000 }, /* R262 */
+ { 0x0000, 0x0000 }, /* R263 */
+ { 0x0000, 0x0000 }, /* R264 */
+ { 0x0000, 0x0000 }, /* R265 */
+ { 0x0000, 0x0000 }, /* R266 */
+ { 0x0000, 0x0000 }, /* R267 */
+ { 0x0000, 0x0000 }, /* R268 */
+ { 0x0000, 0x0000 }, /* R269 */
+ { 0x0000, 0x0000 }, /* R270 */
+ { 0x0000, 0x0000 }, /* R271 */
+ { 0x807F, 0x837F }, /* R272 - Write Sequencer Ctrl (1) */
+ { 0x017F, 0x0000 }, /* R273 - Write Sequencer Ctrl (2) */
+ { 0x0000, 0x0000 }, /* R274 */
+ { 0x0000, 0x0000 }, /* R275 */
+ { 0x0000, 0x0000 }, /* R276 */
+ { 0x0000, 0x0000 }, /* R277 */
+ { 0x0000, 0x0000 }, /* R278 */
+ { 0x0000, 0x0000 }, /* R279 */
+ { 0x0000, 0x0000 }, /* R280 */
+ { 0x0000, 0x0000 }, /* R281 */
+ { 0x0000, 0x0000 }, /* R282 */
+ { 0x0000, 0x0000 }, /* R283 */
+ { 0x0000, 0x0000 }, /* R284 */
+ { 0x0000, 0x0000 }, /* R285 */
+ { 0x0000, 0x0000 }, /* R286 */
+ { 0x0000, 0x0000 }, /* R287 */
+ { 0x0000, 0x0000 }, /* R288 */
+ { 0x0000, 0x0000 }, /* R289 */
+ { 0x0000, 0x0000 }, /* R290 */
+ { 0x0000, 0x0000 }, /* R291 */
+ { 0x0000, 0x0000 }, /* R292 */
+ { 0x0000, 0x0000 }, /* R293 */
+ { 0x0000, 0x0000 }, /* R294 */
+ { 0x0000, 0x0000 }, /* R295 */
+ { 0x0000, 0x0000 }, /* R296 */
+ { 0x0000, 0x0000 }, /* R297 */
+ { 0x0000, 0x0000 }, /* R298 */
+ { 0x0000, 0x0000 }, /* R299 */
+ { 0x0000, 0x0000 }, /* R300 */
+ { 0x0000, 0x0000 }, /* R301 */
+ { 0x0000, 0x0000 }, /* R302 */
+ { 0x0000, 0x0000 }, /* R303 */
+ { 0x0000, 0x0000 }, /* R304 */
+ { 0x0000, 0x0000 }, /* R305 */
+ { 0x0000, 0x0000 }, /* R306 */
+ { 0x0000, 0x0000 }, /* R307 */
+ { 0x0000, 0x0000 }, /* R308 */
+ { 0x0000, 0x0000 }, /* R309 */
+ { 0x0000, 0x0000 }, /* R310 */
+ { 0x0000, 0x0000 }, /* R311 */
+ { 0x0000, 0x0000 }, /* R312 */
+ { 0x0000, 0x0000 }, /* R313 */
+ { 0x0000, 0x0000 }, /* R314 */
+ { 0x0000, 0x0000 }, /* R315 */
+ { 0x0000, 0x0000 }, /* R316 */
+ { 0x0000, 0x0000 }, /* R317 */
+ { 0x0000, 0x0000 }, /* R318 */
+ { 0x0000, 0x0000 }, /* R319 */
+ { 0x0000, 0x0000 }, /* R320 */
+ { 0x0000, 0x0000 }, /* R321 */
+ { 0x0000, 0x0000 }, /* R322 */
+ { 0x0000, 0x0000 }, /* R323 */
+ { 0x0000, 0x0000 }, /* R324 */
+ { 0x0000, 0x0000 }, /* R325 */
+ { 0x0000, 0x0000 }, /* R326 */
+ { 0x0000, 0x0000 }, /* R327 */
+ { 0x0000, 0x0000 }, /* R328 */
+ { 0x0000, 0x0000 }, /* R329 */
+ { 0x0000, 0x0000 }, /* R330 */
+ { 0x0000, 0x0000 }, /* R331 */
+ { 0x0000, 0x0000 }, /* R332 */
+ { 0x0000, 0x0000 }, /* R333 */
+ { 0x0000, 0x0000 }, /* R334 */
+ { 0x0000, 0x0000 }, /* R335 */
+ { 0x0000, 0x0000 }, /* R336 */
+ { 0x0000, 0x0000 }, /* R337 */
+ { 0x0000, 0x0000 }, /* R338 */
+ { 0x0000, 0x0000 }, /* R339 */
+ { 0x0000, 0x0000 }, /* R340 */
+ { 0x0000, 0x0000 }, /* R341 */
+ { 0x0000, 0x0000 }, /* R342 */
+ { 0x0000, 0x0000 }, /* R343 */
+ { 0x0000, 0x0000 }, /* R344 */
+ { 0x0000, 0x0000 }, /* R345 */
+ { 0x0000, 0x0000 }, /* R346 */
+ { 0x0000, 0x0000 }, /* R347 */
+ { 0x0000, 0x0000 }, /* R348 */
+ { 0x0000, 0x0000 }, /* R349 */
+ { 0x0000, 0x0000 }, /* R350 */
+ { 0x0000, 0x0000 }, /* R351 */
+ { 0x0000, 0x0000 }, /* R352 */
+ { 0x0000, 0x0000 }, /* R353 */
+ { 0x0000, 0x0000 }, /* R354 */
+ { 0x0000, 0x0000 }, /* R355 */
+ { 0x0000, 0x0000 }, /* R356 */
+ { 0x0000, 0x0000 }, /* R357 */
+ { 0x0000, 0x0000 }, /* R358 */
+ { 0x0000, 0x0000 }, /* R359 */
+ { 0x0000, 0x0000 }, /* R360 */
+ { 0x0000, 0x0000 }, /* R361 */
+ { 0x0000, 0x0000 }, /* R362 */
+ { 0x0000, 0x0000 }, /* R363 */
+ { 0x0000, 0x0000 }, /* R364 */
+ { 0x0000, 0x0000 }, /* R365 */
+ { 0x0000, 0x0000 }, /* R366 */
+ { 0x0000, 0x0000 }, /* R367 */
+ { 0x0000, 0x0000 }, /* R368 */
+ { 0x0000, 0x0000 }, /* R369 */
+ { 0x0000, 0x0000 }, /* R370 */
+ { 0x0000, 0x0000 }, /* R371 */
+ { 0x0000, 0x0000 }, /* R372 */
+ { 0x0000, 0x0000 }, /* R373 */
+ { 0x0000, 0x0000 }, /* R374 */
+ { 0x0000, 0x0000 }, /* R375 */
+ { 0x0000, 0x0000 }, /* R376 */
+ { 0x0000, 0x0000 }, /* R377 */
+ { 0x0000, 0x0000 }, /* R378 */
+ { 0x0000, 0x0000 }, /* R379 */
+ { 0x0000, 0x0000 }, /* R380 */
+ { 0x0000, 0x0000 }, /* R381 */
+ { 0x0000, 0x0000 }, /* R382 */
+ { 0x0000, 0x0000 }, /* R383 */
+ { 0x0000, 0x0000 }, /* R384 */
+ { 0x0000, 0x0000 }, /* R385 */
+ { 0x0000, 0x0000 }, /* R386 */
+ { 0x0000, 0x0000 }, /* R387 */
+ { 0x0000, 0x0000 }, /* R388 */
+ { 0x0000, 0x0000 }, /* R389 */
+ { 0x0000, 0x0000 }, /* R390 */
+ { 0x0000, 0x0000 }, /* R391 */
+ { 0x0000, 0x0000 }, /* R392 */
+ { 0x0000, 0x0000 }, /* R393 */
+ { 0x0000, 0x0000 }, /* R394 */
+ { 0x0000, 0x0000 }, /* R395 */
+ { 0x0000, 0x0000 }, /* R396 */
+ { 0x0000, 0x0000 }, /* R397 */
+ { 0x0000, 0x0000 }, /* R398 */
+ { 0x0000, 0x0000 }, /* R399 */
+ { 0x0000, 0x0000 }, /* R400 */
+ { 0x0000, 0x0000 }, /* R401 */
+ { 0x0000, 0x0000 }, /* R402 */
+ { 0x0000, 0x0000 }, /* R403 */
+ { 0x0000, 0x0000 }, /* R404 */
+ { 0x0000, 0x0000 }, /* R405 */
+ { 0x0000, 0x0000 }, /* R406 */
+ { 0x0000, 0x0000 }, /* R407 */
+ { 0x0000, 0x0000 }, /* R408 */
+ { 0x0000, 0x0000 }, /* R409 */
+ { 0x0000, 0x0000 }, /* R410 */
+ { 0x0000, 0x0000 }, /* R411 */
+ { 0x0000, 0x0000 }, /* R412 */
+ { 0x0000, 0x0000 }, /* R413 */
+ { 0x0000, 0x0000 }, /* R414 */
+ { 0x0000, 0x0000 }, /* R415 */
+ { 0x0000, 0x0000 }, /* R416 */
+ { 0x0000, 0x0000 }, /* R417 */
+ { 0x0000, 0x0000 }, /* R418 */
+ { 0x0000, 0x0000 }, /* R419 */
+ { 0x0000, 0x0000 }, /* R420 */
+ { 0x0000, 0x0000 }, /* R421 */
+ { 0x0000, 0x0000 }, /* R422 */
+ { 0x0000, 0x0000 }, /* R423 */
+ { 0x0000, 0x0000 }, /* R424 */
+ { 0x0000, 0x0000 }, /* R425 */
+ { 0x0000, 0x0000 }, /* R426 */
+ { 0x0000, 0x0000 }, /* R427 */
+ { 0x0000, 0x0000 }, /* R428 */
+ { 0x0000, 0x0000 }, /* R429 */
+ { 0x0000, 0x0000 }, /* R430 */
+ { 0x0000, 0x0000 }, /* R431 */
+ { 0x0000, 0x0000 }, /* R432 */
+ { 0x0000, 0x0000 }, /* R433 */
+ { 0x0000, 0x0000 }, /* R434 */
+ { 0x0000, 0x0000 }, /* R435 */
+ { 0x0000, 0x0000 }, /* R436 */
+ { 0x0000, 0x0000 }, /* R437 */
+ { 0x0000, 0x0000 }, /* R438 */
+ { 0x0000, 0x0000 }, /* R439 */
+ { 0x0000, 0x0000 }, /* R440 */
+ { 0x0000, 0x0000 }, /* R441 */
+ { 0x0000, 0x0000 }, /* R442 */
+ { 0x0000, 0x0000 }, /* R443 */
+ { 0x0000, 0x0000 }, /* R444 */
+ { 0x0000, 0x0000 }, /* R445 */
+ { 0x0000, 0x0000 }, /* R446 */
+ { 0x0000, 0x0000 }, /* R447 */
+ { 0x0000, 0x0000 }, /* R448 */
+ { 0x0000, 0x0000 }, /* R449 */
+ { 0x0000, 0x0000 }, /* R450 */
+ { 0x0000, 0x0000 }, /* R451 */
+ { 0x0000, 0x0000 }, /* R452 */
+ { 0x0000, 0x0000 }, /* R453 */
+ { 0x0000, 0x0000 }, /* R454 */
+ { 0x0000, 0x0000 }, /* R455 */
+ { 0x0000, 0x0000 }, /* R456 */
+ { 0x0000, 0x0000 }, /* R457 */
+ { 0x0000, 0x0000 }, /* R458 */
+ { 0x0000, 0x0000 }, /* R459 */
+ { 0x0000, 0x0000 }, /* R460 */
+ { 0x0000, 0x0000 }, /* R461 */
+ { 0x0000, 0x0000 }, /* R462 */
+ { 0x0000, 0x0000 }, /* R463 */
+ { 0x0000, 0x0000 }, /* R464 */
+ { 0x0000, 0x0000 }, /* R465 */
+ { 0x0000, 0x0000 }, /* R466 */
+ { 0x0000, 0x0000 }, /* R467 */
+ { 0x0000, 0x0000 }, /* R468 */
+ { 0x0000, 0x0000 }, /* R469 */
+ { 0x0000, 0x0000 }, /* R470 */
+ { 0x0000, 0x0000 }, /* R471 */
+ { 0x0000, 0x0000 }, /* R472 */
+ { 0x0000, 0x0000 }, /* R473 */
+ { 0x0000, 0x0000 }, /* R474 */
+ { 0x0000, 0x0000 }, /* R475 */
+ { 0x0000, 0x0000 }, /* R476 */
+ { 0x0000, 0x0000 }, /* R477 */
+ { 0x0000, 0x0000 }, /* R478 */
+ { 0x0000, 0x0000 }, /* R479 */
+ { 0x0000, 0x0000 }, /* R480 */
+ { 0x0000, 0x0000 }, /* R481 */
+ { 0x0000, 0x0000 }, /* R482 */
+ { 0x0000, 0x0000 }, /* R483 */
+ { 0x0000, 0x0000 }, /* R484 */
+ { 0x0000, 0x0000 }, /* R485 */
+ { 0x0000, 0x0000 }, /* R486 */
+ { 0x0000, 0x0000 }, /* R487 */
+ { 0x0000, 0x0000 }, /* R488 */
+ { 0x0000, 0x0000 }, /* R489 */
+ { 0x0000, 0x0000 }, /* R490 */
+ { 0x0000, 0x0000 }, /* R491 */
+ { 0x0000, 0x0000 }, /* R492 */
+ { 0x0000, 0x0000 }, /* R493 */
+ { 0x0000, 0x0000 }, /* R494 */
+ { 0x0000, 0x0000 }, /* R495 */
+ { 0x0000, 0x0000 }, /* R496 */
+ { 0x0000, 0x0000 }, /* R497 */
+ { 0x0000, 0x0000 }, /* R498 */
+ { 0x0000, 0x0000 }, /* R499 */
+ { 0x0000, 0x0000 }, /* R500 */
+ { 0x0000, 0x0000 }, /* R501 */
+ { 0x0000, 0x0000 }, /* R502 */
+ { 0x0000, 0x0000 }, /* R503 */
+ { 0x0000, 0x0000 }, /* R504 */
+ { 0x0000, 0x0000 }, /* R505 */
+ { 0x0000, 0x0000 }, /* R506 */
+ { 0x0000, 0x0000 }, /* R507 */
+ { 0x0000, 0x0000 }, /* R508 */
+ { 0x0000, 0x0000 }, /* R509 */
+ { 0x0000, 0x0000 }, /* R510 */
+ { 0x0000, 0x0000 }, /* R511 */
+ { 0x001F, 0x001F }, /* R512 - AIF1 Clocking (1) */
+ { 0x003F, 0x003F }, /* R513 - AIF1 Clocking (2) */
+ { 0x0000, 0x0000 }, /* R514 */
+ { 0x0000, 0x0000 }, /* R515 */
+ { 0x001F, 0x001F }, /* R516 - AIF2 Clocking (1) */
+ { 0x003F, 0x003F }, /* R517 - AIF2 Clocking (2) */
+ { 0x0000, 0x0000 }, /* R518 */
+ { 0x0000, 0x0000 }, /* R519 */
+ { 0x001F, 0x001F }, /* R520 - Clocking (1) */
+ { 0x0777, 0x0777 }, /* R521 - Clocking (2) */
+ { 0x0000, 0x0000 }, /* R522 */
+ { 0x0000, 0x0000 }, /* R523 */
+ { 0x0000, 0x0000 }, /* R524 */
+ { 0x0000, 0x0000 }, /* R525 */
+ { 0x0000, 0x0000 }, /* R526 */
+ { 0x0000, 0x0000 }, /* R527 */
+ { 0x00FF, 0x00FF }, /* R528 - AIF1 Rate */
+ { 0x00FF, 0x00FF }, /* R529 - AIF2 Rate */
+ { 0x000F, 0x0000 }, /* R530 - Rate Status */
+ { 0x0000, 0x0000 }, /* R531 */
+ { 0x0000, 0x0000 }, /* R532 */
+ { 0x0000, 0x0000 }, /* R533 */
+ { 0x0000, 0x0000 }, /* R534 */
+ { 0x0000, 0x0000 }, /* R535 */
+ { 0x0000, 0x0000 }, /* R536 */
+ { 0x0000, 0x0000 }, /* R537 */
+ { 0x0000, 0x0000 }, /* R538 */
+ { 0x0000, 0x0000 }, /* R539 */
+ { 0x0000, 0x0000 }, /* R540 */
+ { 0x0000, 0x0000 }, /* R541 */
+ { 0x0000, 0x0000 }, /* R542 */
+ { 0x0000, 0x0000 }, /* R543 */
+ { 0x0007, 0x0007 }, /* R544 - FLL1 Control (1) */
+ { 0x3F77, 0x3F77 }, /* R545 - FLL1 Control (2) */
+ { 0xFFFF, 0xFFFF }, /* R546 - FLL1 Control (3) */
+ { 0x7FEF, 0x7FEF }, /* R547 - FLL1 Control (4) */
+ { 0x1FDB, 0x1FDB }, /* R548 - FLL1 Control (5) */
+ { 0x0000, 0x0000 }, /* R549 */
+ { 0x0000, 0x0000 }, /* R550 */
+ { 0x0000, 0x0000 }, /* R551 */
+ { 0x0000, 0x0000 }, /* R552 */
+ { 0x0000, 0x0000 }, /* R553 */
+ { 0x0000, 0x0000 }, /* R554 */
+ { 0x0000, 0x0000 }, /* R555 */
+ { 0x0000, 0x0000 }, /* R556 */
+ { 0x0000, 0x0000 }, /* R557 */
+ { 0x0000, 0x0000 }, /* R558 */
+ { 0x0000, 0x0000 }, /* R559 */
+ { 0x0000, 0x0000 }, /* R560 */
+ { 0x0000, 0x0000 }, /* R561 */
+ { 0x0000, 0x0000 }, /* R562 */
+ { 0x0000, 0x0000 }, /* R563 */
+ { 0x0000, 0x0000 }, /* R564 */
+ { 0x0000, 0x0000 }, /* R565 */
+ { 0x0000, 0x0000 }, /* R566 */
+ { 0x0000, 0x0000 }, /* R567 */
+ { 0x0000, 0x0000 }, /* R568 */
+ { 0x0000, 0x0000 }, /* R569 */
+ { 0x0000, 0x0000 }, /* R570 */
+ { 0x0000, 0x0000 }, /* R571 */
+ { 0x0000, 0x0000 }, /* R572 */
+ { 0x0000, 0x0000 }, /* R573 */
+ { 0x0000, 0x0000 }, /* R574 */
+ { 0x0000, 0x0000 }, /* R575 */
+ { 0x0007, 0x0007 }, /* R576 - FLL2 Control (1) */
+ { 0x3F77, 0x3F77 }, /* R577 - FLL2 Control (2) */
+ { 0xFFFF, 0xFFFF }, /* R578 - FLL2 Control (3) */
+ { 0x7FEF, 0x7FEF }, /* R579 - FLL2 Control (4) */
+ { 0x1FDB, 0x1FDB }, /* R580 - FLL2 Control (5) */
+ { 0x0000, 0x0000 }, /* R581 */
+ { 0x0000, 0x0000 }, /* R582 */
+ { 0x0000, 0x0000 }, /* R583 */
+ { 0x0000, 0x0000 }, /* R584 */
+ { 0x0000, 0x0000 }, /* R585 */
+ { 0x0000, 0x0000 }, /* R586 */
+ { 0x0000, 0x0000 }, /* R587 */
+ { 0x0000, 0x0000 }, /* R588 */
+ { 0x0000, 0x0000 }, /* R589 */
+ { 0x0000, 0x0000 }, /* R590 */
+ { 0x0000, 0x0000 }, /* R591 */
+ { 0x0000, 0x0000 }, /* R592 */
+ { 0x0000, 0x0000 }, /* R593 */
+ { 0x0000, 0x0000 }, /* R594 */
+ { 0x0000, 0x0000 }, /* R595 */
+ { 0x0000, 0x0000 }, /* R596 */
+ { 0x0000, 0x0000 }, /* R597 */
+ { 0x0000, 0x0000 }, /* R598 */
+ { 0x0000, 0x0000 }, /* R599 */
+ { 0x0000, 0x0000 }, /* R600 */
+ { 0x0000, 0x0000 }, /* R601 */
+ { 0x0000, 0x0000 }, /* R602 */
+ { 0x0000, 0x0000 }, /* R603 */
+ { 0x0000, 0x0000 }, /* R604 */
+ { 0x0000, 0x0000 }, /* R605 */
+ { 0x0000, 0x0000 }, /* R606 */
+ { 0x0000, 0x0000 }, /* R607 */
+ { 0x0000, 0x0000 }, /* R608 */
+ { 0x0000, 0x0000 }, /* R609 */
+ { 0x0000, 0x0000 }, /* R610 */
+ { 0x0000, 0x0000 }, /* R611 */
+ { 0x0000, 0x0000 }, /* R612 */
+ { 0x0000, 0x0000 }, /* R613 */
+ { 0x0000, 0x0000 }, /* R614 */
+ { 0x0000, 0x0000 }, /* R615 */
+ { 0x0000, 0x0000 }, /* R616 */
+ { 0x0000, 0x0000 }, /* R617 */
+ { 0x0000, 0x0000 }, /* R618 */
+ { 0x0000, 0x0000 }, /* R619 */
+ { 0x0000, 0x0000 }, /* R620 */
+ { 0x0000, 0x0000 }, /* R621 */
+ { 0x0000, 0x0000 }, /* R622 */
+ { 0x0000, 0x0000 }, /* R623 */
+ { 0x0000, 0x0000 }, /* R624 */
+ { 0x0000, 0x0000 }, /* R625 */
+ { 0x0000, 0x0000 }, /* R626 */
+ { 0x0000, 0x0000 }, /* R627 */
+ { 0x0000, 0x0000 }, /* R628 */
+ { 0x0000, 0x0000 }, /* R629 */
+ { 0x0000, 0x0000 }, /* R630 */
+ { 0x0000, 0x0000 }, /* R631 */
+ { 0x0000, 0x0000 }, /* R632 */
+ { 0x0000, 0x0000 }, /* R633 */
+ { 0x0000, 0x0000 }, /* R634 */
+ { 0x0000, 0x0000 }, /* R635 */
+ { 0x0000, 0x0000 }, /* R636 */
+ { 0x0000, 0x0000 }, /* R637 */
+ { 0x0000, 0x0000 }, /* R638 */
+ { 0x0000, 0x0000 }, /* R639 */
+ { 0x0000, 0x0000 }, /* R640 */
+ { 0x0000, 0x0000 }, /* R641 */
+ { 0x0000, 0x0000 }, /* R642 */
+ { 0x0000, 0x0000 }, /* R643 */
+ { 0x0000, 0x0000 }, /* R644 */
+ { 0x0000, 0x0000 }, /* R645 */
+ { 0x0000, 0x0000 }, /* R646 */
+ { 0x0000, 0x0000 }, /* R647 */
+ { 0x0000, 0x0000 }, /* R648 */
+ { 0x0000, 0x0000 }, /* R649 */
+ { 0x0000, 0x0000 }, /* R650 */
+ { 0x0000, 0x0000 }, /* R651 */
+ { 0x0000, 0x0000 }, /* R652 */
+ { 0x0000, 0x0000 }, /* R653 */
+ { 0x0000, 0x0000 }, /* R654 */
+ { 0x0000, 0x0000 }, /* R655 */
+ { 0x0000, 0x0000 }, /* R656 */
+ { 0x0000, 0x0000 }, /* R657 */
+ { 0x0000, 0x0000 }, /* R658 */
+ { 0x0000, 0x0000 }, /* R659 */
+ { 0x0000, 0x0000 }, /* R660 */
+ { 0x0000, 0x0000 }, /* R661 */
+ { 0x0000, 0x0000 }, /* R662 */
+ { 0x0000, 0x0000 }, /* R663 */
+ { 0x0000, 0x0000 }, /* R664 */
+ { 0x0000, 0x0000 }, /* R665 */
+ { 0x0000, 0x0000 }, /* R666 */
+ { 0x0000, 0x0000 }, /* R667 */
+ { 0x0000, 0x0000 }, /* R668 */
+ { 0x0000, 0x0000 }, /* R669 */
+ { 0x0000, 0x0000 }, /* R670 */
+ { 0x0000, 0x0000 }, /* R671 */
+ { 0x0000, 0x0000 }, /* R672 */
+ { 0x0000, 0x0000 }, /* R673 */
+ { 0x0000, 0x0000 }, /* R674 */
+ { 0x0000, 0x0000 }, /* R675 */
+ { 0x0000, 0x0000 }, /* R676 */
+ { 0x0000, 0x0000 }, /* R677 */
+ { 0x0000, 0x0000 }, /* R678 */
+ { 0x0000, 0x0000 }, /* R679 */
+ { 0x0000, 0x0000 }, /* R680 */
+ { 0x0000, 0x0000 }, /* R681 */
+ { 0x0000, 0x0000 }, /* R682 */
+ { 0x0000, 0x0000 }, /* R683 */
+ { 0x0000, 0x0000 }, /* R684 */
+ { 0x0000, 0x0000 }, /* R685 */
+ { 0x0000, 0x0000 }, /* R686 */
+ { 0x0000, 0x0000 }, /* R687 */
+ { 0x0000, 0x0000 }, /* R688 */
+ { 0x0000, 0x0000 }, /* R689 */
+ { 0x0000, 0x0000 }, /* R690 */
+ { 0x0000, 0x0000 }, /* R691 */
+ { 0x0000, 0x0000 }, /* R692 */
+ { 0x0000, 0x0000 }, /* R693 */
+ { 0x0000, 0x0000 }, /* R694 */
+ { 0x0000, 0x0000 }, /* R695 */
+ { 0x0000, 0x0000 }, /* R696 */
+ { 0x0000, 0x0000 }, /* R697 */
+ { 0x0000, 0x0000 }, /* R698 */
+ { 0x0000, 0x0000 }, /* R699 */
+ { 0x0000, 0x0000 }, /* R700 */
+ { 0x0000, 0x0000 }, /* R701 */
+ { 0x0000, 0x0000 }, /* R702 */
+ { 0x0000, 0x0000 }, /* R703 */
+ { 0x0000, 0x0000 }, /* R704 */
+ { 0x0000, 0x0000 }, /* R705 */
+ { 0x0000, 0x0000 }, /* R706 */
+ { 0x0000, 0x0000 }, /* R707 */
+ { 0x0000, 0x0000 }, /* R708 */
+ { 0x0000, 0x0000 }, /* R709 */
+ { 0x0000, 0x0000 }, /* R710 */
+ { 0x0000, 0x0000 }, /* R711 */
+ { 0x0000, 0x0000 }, /* R712 */
+ { 0x0000, 0x0000 }, /* R713 */
+ { 0x0000, 0x0000 }, /* R714 */
+ { 0x0000, 0x0000 }, /* R715 */
+ { 0x0000, 0x0000 }, /* R716 */
+ { 0x0000, 0x0000 }, /* R717 */
+ { 0x0000, 0x0000 }, /* R718 */
+ { 0x0000, 0x0000 }, /* R719 */
+ { 0x0000, 0x0000 }, /* R720 */
+ { 0x0000, 0x0000 }, /* R721 */
+ { 0x0000, 0x0000 }, /* R722 */
+ { 0x0000, 0x0000 }, /* R723 */
+ { 0x0000, 0x0000 }, /* R724 */
+ { 0x0000, 0x0000 }, /* R725 */
+ { 0x0000, 0x0000 }, /* R726 */
+ { 0x0000, 0x0000 }, /* R727 */
+ { 0x0000, 0x0000 }, /* R728 */
+ { 0x0000, 0x0000 }, /* R729 */
+ { 0x0000, 0x0000 }, /* R730 */
+ { 0x0000, 0x0000 }, /* R731 */
+ { 0x0000, 0x0000 }, /* R732 */
+ { 0x0000, 0x0000 }, /* R733 */
+ { 0x0000, 0x0000 }, /* R734 */
+ { 0x0000, 0x0000 }, /* R735 */
+ { 0x0000, 0x0000 }, /* R736 */
+ { 0x0000, 0x0000 }, /* R737 */
+ { 0x0000, 0x0000 }, /* R738 */
+ { 0x0000, 0x0000 }, /* R739 */
+ { 0x0000, 0x0000 }, /* R740 */
+ { 0x0000, 0x0000 }, /* R741 */
+ { 0x0000, 0x0000 }, /* R742 */
+ { 0x0000, 0x0000 }, /* R743 */
+ { 0x0000, 0x0000 }, /* R744 */
+ { 0x0000, 0x0000 }, /* R745 */
+ { 0x0000, 0x0000 }, /* R746 */
+ { 0x0000, 0x0000 }, /* R747 */
+ { 0x0000, 0x0000 }, /* R748 */
+ { 0x0000, 0x0000 }, /* R749 */
+ { 0x0000, 0x0000 }, /* R750 */
+ { 0x0000, 0x0000 }, /* R751 */
+ { 0x0000, 0x0000 }, /* R752 */
+ { 0x0000, 0x0000 }, /* R753 */
+ { 0x0000, 0x0000 }, /* R754 */
+ { 0x0000, 0x0000 }, /* R755 */
+ { 0x0000, 0x0000 }, /* R756 */
+ { 0x0000, 0x0000 }, /* R757 */
+ { 0x0000, 0x0000 }, /* R758 */
+ { 0x0000, 0x0000 }, /* R759 */
+ { 0x0000, 0x0000 }, /* R760 */
+ { 0x0000, 0x0000 }, /* R761 */
+ { 0x0000, 0x0000 }, /* R762 */
+ { 0x0000, 0x0000 }, /* R763 */
+ { 0x0000, 0x0000 }, /* R764 */
+ { 0x0000, 0x0000 }, /* R765 */
+ { 0x0000, 0x0000 }, /* R766 */
+ { 0x0000, 0x0000 }, /* R767 */
+ { 0xE1F8, 0xE1F8 }, /* R768 - AIF1 Control (1) */
+ { 0xCD1F, 0xCD1F }, /* R769 - AIF1 Control (2) */
+ { 0xF000, 0xF000 }, /* R770 - AIF1 Master/Slave */
+ { 0x01F0, 0x01F0 }, /* R771 - AIF1 BCLK */
+ { 0x0FFF, 0x0FFF }, /* R772 - AIF1ADC LRCLK */
+ { 0x0FFF, 0x0FFF }, /* R773 - AIF1DAC LRCLK */
+ { 0x0003, 0x0003 }, /* R774 - AIF1DAC Data */
+ { 0x0003, 0x0003 }, /* R775 - AIF1ADC Data */
+ { 0x0000, 0x0000 }, /* R776 */
+ { 0x0000, 0x0000 }, /* R777 */
+ { 0x0000, 0x0000 }, /* R778 */
+ { 0x0000, 0x0000 }, /* R779 */
+ { 0x0000, 0x0000 }, /* R780 */
+ { 0x0000, 0x0000 }, /* R781 */
+ { 0x0000, 0x0000 }, /* R782 */
+ { 0x0000, 0x0000 }, /* R783 */
+ { 0xF1F8, 0xF1F8 }, /* R784 - AIF2 Control (1) */
+ { 0xFD1F, 0xFD1F }, /* R785 - AIF2 Control (2) */
+ { 0xF000, 0xF000 }, /* R786 - AIF2 Master/Slave */
+ { 0x01F0, 0x01F0 }, /* R787 - AIF2 BCLK */
+ { 0x0FFF, 0x0FFF }, /* R788 - AIF2ADC LRCLK */
+ { 0x0FFF, 0x0FFF }, /* R789 - AIF2DAC LRCLK */
+ { 0x0003, 0x0003 }, /* R790 - AIF2DAC Data */
+ { 0x0003, 0x0003 }, /* R791 - AIF2ADC Data */
+ { 0x0000, 0x0000 }, /* R792 */
+ { 0x0000, 0x0000 }, /* R793 */
+ { 0x0000, 0x0000 }, /* R794 */
+ { 0x0000, 0x0000 }, /* R795 */
+ { 0x0000, 0x0000 }, /* R796 */
+ { 0x0000, 0x0000 }, /* R797 */
+ { 0x0000, 0x0000 }, /* R798 */
+ { 0x0000, 0x0000 }, /* R799 */
+ { 0x0000, 0x0000 }, /* R800 */
+ { 0x0000, 0x0000 }, /* R801 */
+ { 0x0000, 0x0000 }, /* R802 */
+ { 0x0000, 0x0000 }, /* R803 */
+ { 0x0000, 0x0000 }, /* R804 */
+ { 0x0000, 0x0000 }, /* R805 */
+ { 0x0000, 0x0000 }, /* R806 */
+ { 0x0000, 0x0000 }, /* R807 */
+ { 0x0000, 0x0000 }, /* R808 */
+ { 0x0000, 0x0000 }, /* R809 */
+ { 0x0000, 0x0000 }, /* R810 */
+ { 0x0000, 0x0000 }, /* R811 */
+ { 0x0000, 0x0000 }, /* R812 */
+ { 0x0000, 0x0000 }, /* R813 */
+ { 0x0000, 0x0000 }, /* R814 */
+ { 0x0000, 0x0000 }, /* R815 */
+ { 0x0000, 0x0000 }, /* R816 */
+ { 0x0000, 0x0000 }, /* R817 */
+ { 0x0000, 0x0000 }, /* R818 */
+ { 0x0000, 0x0000 }, /* R819 */
+ { 0x0000, 0x0000 }, /* R820 */
+ { 0x0000, 0x0000 }, /* R821 */
+ { 0x0000, 0x0000 }, /* R822 */
+ { 0x0000, 0x0000 }, /* R823 */
+ { 0x0000, 0x0000 }, /* R824 */
+ { 0x0000, 0x0000 }, /* R825 */
+ { 0x0000, 0x0000 }, /* R826 */
+ { 0x0000, 0x0000 }, /* R827 */
+ { 0x0000, 0x0000 }, /* R828 */
+ { 0x0000, 0x0000 }, /* R829 */
+ { 0x0000, 0x0000 }, /* R830 */
+ { 0x0000, 0x0000 }, /* R831 */
+ { 0x0000, 0x0000 }, /* R832 */
+ { 0x0000, 0x0000 }, /* R833 */
+ { 0x0000, 0x0000 }, /* R834 */
+ { 0x0000, 0x0000 }, /* R835 */
+ { 0x0000, 0x0000 }, /* R836 */
+ { 0x0000, 0x0000 }, /* R837 */
+ { 0x0000, 0x0000 }, /* R838 */
+ { 0x0000, 0x0000 }, /* R839 */
+ { 0x0000, 0x0000 }, /* R840 */
+ { 0x0000, 0x0000 }, /* R841 */
+ { 0x0000, 0x0000 }, /* R842 */
+ { 0x0000, 0x0000 }, /* R843 */
+ { 0x0000, 0x0000 }, /* R844 */
+ { 0x0000, 0x0000 }, /* R845 */
+ { 0x0000, 0x0000 }, /* R846 */
+ { 0x0000, 0x0000 }, /* R847 */
+ { 0x0000, 0x0000 }, /* R848 */
+ { 0x0000, 0x0000 }, /* R849 */
+ { 0x0000, 0x0000 }, /* R850 */
+ { 0x0000, 0x0000 }, /* R851 */
+ { 0x0000, 0x0000 }, /* R852 */
+ { 0x0000, 0x0000 }, /* R853 */
+ { 0x0000, 0x0000 }, /* R854 */
+ { 0x0000, 0x0000 }, /* R855 */
+ { 0x0000, 0x0000 }, /* R856 */
+ { 0x0000, 0x0000 }, /* R857 */
+ { 0x0000, 0x0000 }, /* R858 */
+ { 0x0000, 0x0000 }, /* R859 */
+ { 0x0000, 0x0000 }, /* R860 */
+ { 0x0000, 0x0000 }, /* R861 */
+ { 0x0000, 0x0000 }, /* R862 */
+ { 0x0000, 0x0000 }, /* R863 */
+ { 0x0000, 0x0000 }, /* R864 */
+ { 0x0000, 0x0000 }, /* R865 */
+ { 0x0000, 0x0000 }, /* R866 */
+ { 0x0000, 0x0000 }, /* R867 */
+ { 0x0000, 0x0000 }, /* R868 */
+ { 0x0000, 0x0000 }, /* R869 */
+ { 0x0000, 0x0000 }, /* R870 */
+ { 0x0000, 0x0000 }, /* R871 */
+ { 0x0000, 0x0000 }, /* R872 */
+ { 0x0000, 0x0000 }, /* R873 */
+ { 0x0000, 0x0000 }, /* R874 */
+ { 0x0000, 0x0000 }, /* R875 */
+ { 0x0000, 0x0000 }, /* R876 */
+ { 0x0000, 0x0000 }, /* R877 */
+ { 0x0000, 0x0000 }, /* R878 */
+ { 0x0000, 0x0000 }, /* R879 */
+ { 0x0000, 0x0000 }, /* R880 */
+ { 0x0000, 0x0000 }, /* R881 */
+ { 0x0000, 0x0000 }, /* R882 */
+ { 0x0000, 0x0000 }, /* R883 */
+ { 0x0000, 0x0000 }, /* R884 */
+ { 0x0000, 0x0000 }, /* R885 */
+ { 0x0000, 0x0000 }, /* R886 */
+ { 0x0000, 0x0000 }, /* R887 */
+ { 0x0000, 0x0000 }, /* R888 */
+ { 0x0000, 0x0000 }, /* R889 */
+ { 0x0000, 0x0000 }, /* R890 */
+ { 0x0000, 0x0000 }, /* R891 */
+ { 0x0000, 0x0000 }, /* R892 */
+ { 0x0000, 0x0000 }, /* R893 */
+ { 0x0000, 0x0000 }, /* R894 */
+ { 0x0000, 0x0000 }, /* R895 */
+ { 0x0000, 0x0000 }, /* R896 */
+ { 0x0000, 0x0000 }, /* R897 */
+ { 0x0000, 0x0000 }, /* R898 */
+ { 0x0000, 0x0000 }, /* R899 */
+ { 0x0000, 0x0000 }, /* R900 */
+ { 0x0000, 0x0000 }, /* R901 */
+ { 0x0000, 0x0000 }, /* R902 */
+ { 0x0000, 0x0000 }, /* R903 */
+ { 0x0000, 0x0000 }, /* R904 */
+ { 0x0000, 0x0000 }, /* R905 */
+ { 0x0000, 0x0000 }, /* R906 */
+ { 0x0000, 0x0000 }, /* R907 */
+ { 0x0000, 0x0000 }, /* R908 */
+ { 0x0000, 0x0000 }, /* R909 */
+ { 0x0000, 0x0000 }, /* R910 */
+ { 0x0000, 0x0000 }, /* R911 */
+ { 0x0000, 0x0000 }, /* R912 */
+ { 0x0000, 0x0000 }, /* R913 */
+ { 0x0000, 0x0000 }, /* R914 */
+ { 0x0000, 0x0000 }, /* R915 */
+ { 0x0000, 0x0000 }, /* R916 */
+ { 0x0000, 0x0000 }, /* R917 */
+ { 0x0000, 0x0000 }, /* R918 */
+ { 0x0000, 0x0000 }, /* R919 */
+ { 0x0000, 0x0000 }, /* R920 */
+ { 0x0000, 0x0000 }, /* R921 */
+ { 0x0000, 0x0000 }, /* R922 */
+ { 0x0000, 0x0000 }, /* R923 */
+ { 0x0000, 0x0000 }, /* R924 */
+ { 0x0000, 0x0000 }, /* R925 */
+ { 0x0000, 0x0000 }, /* R926 */
+ { 0x0000, 0x0000 }, /* R927 */
+ { 0x0000, 0x0000 }, /* R928 */
+ { 0x0000, 0x0000 }, /* R929 */
+ { 0x0000, 0x0000 }, /* R930 */
+ { 0x0000, 0x0000 }, /* R931 */
+ { 0x0000, 0x0000 }, /* R932 */
+ { 0x0000, 0x0000 }, /* R933 */
+ { 0x0000, 0x0000 }, /* R934 */
+ { 0x0000, 0x0000 }, /* R935 */
+ { 0x0000, 0x0000 }, /* R936 */
+ { 0x0000, 0x0000 }, /* R937 */
+ { 0x0000, 0x0000 }, /* R938 */
+ { 0x0000, 0x0000 }, /* R939 */
+ { 0x0000, 0x0000 }, /* R940 */
+ { 0x0000, 0x0000 }, /* R941 */
+ { 0x0000, 0x0000 }, /* R942 */
+ { 0x0000, 0x0000 }, /* R943 */
+ { 0x0000, 0x0000 }, /* R944 */
+ { 0x0000, 0x0000 }, /* R945 */
+ { 0x0000, 0x0000 }, /* R946 */
+ { 0x0000, 0x0000 }, /* R947 */
+ { 0x0000, 0x0000 }, /* R948 */
+ { 0x0000, 0x0000 }, /* R949 */
+ { 0x0000, 0x0000 }, /* R950 */
+ { 0x0000, 0x0000 }, /* R951 */
+ { 0x0000, 0x0000 }, /* R952 */
+ { 0x0000, 0x0000 }, /* R953 */
+ { 0x0000, 0x0000 }, /* R954 */
+ { 0x0000, 0x0000 }, /* R955 */
+ { 0x0000, 0x0000 }, /* R956 */
+ { 0x0000, 0x0000 }, /* R957 */
+ { 0x0000, 0x0000 }, /* R958 */
+ { 0x0000, 0x0000 }, /* R959 */
+ { 0x0000, 0x0000 }, /* R960 */
+ { 0x0000, 0x0000 }, /* R961 */
+ { 0x0000, 0x0000 }, /* R962 */
+ { 0x0000, 0x0000 }, /* R963 */
+ { 0x0000, 0x0000 }, /* R964 */
+ { 0x0000, 0x0000 }, /* R965 */
+ { 0x0000, 0x0000 }, /* R966 */
+ { 0x0000, 0x0000 }, /* R967 */
+ { 0x0000, 0x0000 }, /* R968 */
+ { 0x0000, 0x0000 }, /* R969 */
+ { 0x0000, 0x0000 }, /* R970 */
+ { 0x0000, 0x0000 }, /* R971 */
+ { 0x0000, 0x0000 }, /* R972 */
+ { 0x0000, 0x0000 }, /* R973 */
+ { 0x0000, 0x0000 }, /* R974 */
+ { 0x0000, 0x0000 }, /* R975 */
+ { 0x0000, 0x0000 }, /* R976 */
+ { 0x0000, 0x0000 }, /* R977 */
+ { 0x0000, 0x0000 }, /* R978 */
+ { 0x0000, 0x0000 }, /* R979 */
+ { 0x0000, 0x0000 }, /* R980 */
+ { 0x0000, 0x0000 }, /* R981 */
+ { 0x0000, 0x0000 }, /* R982 */
+ { 0x0000, 0x0000 }, /* R983 */
+ { 0x0000, 0x0000 }, /* R984 */
+ { 0x0000, 0x0000 }, /* R985 */
+ { 0x0000, 0x0000 }, /* R986 */
+ { 0x0000, 0x0000 }, /* R987 */
+ { 0x0000, 0x0000 }, /* R988 */
+ { 0x0000, 0x0000 }, /* R989 */
+ { 0x0000, 0x0000 }, /* R990 */
+ { 0x0000, 0x0000 }, /* R991 */
+ { 0x0000, 0x0000 }, /* R992 */
+ { 0x0000, 0x0000 }, /* R993 */
+ { 0x0000, 0x0000 }, /* R994 */
+ { 0x0000, 0x0000 }, /* R995 */
+ { 0x0000, 0x0000 }, /* R996 */
+ { 0x0000, 0x0000 }, /* R997 */
+ { 0x0000, 0x0000 }, /* R998 */
+ { 0x0000, 0x0000 }, /* R999 */
+ { 0x0000, 0x0000 }, /* R1000 */
+ { 0x0000, 0x0000 }, /* R1001 */
+ { 0x0000, 0x0000 }, /* R1002 */
+ { 0x0000, 0x0000 }, /* R1003 */
+ { 0x0000, 0x0000 }, /* R1004 */
+ { 0x0000, 0x0000 }, /* R1005 */
+ { 0x0000, 0x0000 }, /* R1006 */
+ { 0x0000, 0x0000 }, /* R1007 */
+ { 0x0000, 0x0000 }, /* R1008 */
+ { 0x0000, 0x0000 }, /* R1009 */
+ { 0x0000, 0x0000 }, /* R1010 */
+ { 0x0000, 0x0000 }, /* R1011 */
+ { 0x0000, 0x0000 }, /* R1012 */
+ { 0x0000, 0x0000 }, /* R1013 */
+ { 0x0000, 0x0000 }, /* R1014 */
+ { 0x0000, 0x0000 }, /* R1015 */
+ { 0x0000, 0x0000 }, /* R1016 */
+ { 0x0000, 0x0000 }, /* R1017 */
+ { 0x0000, 0x0000 }, /* R1018 */
+ { 0x0000, 0x0000 }, /* R1019 */
+ { 0x0000, 0x0000 }, /* R1020 */
+ { 0x0000, 0x0000 }, /* R1021 */
+ { 0x0000, 0x0000 }, /* R1022 */
+ { 0x0000, 0x0000 }, /* R1023 */
+ { 0x00FF, 0x01FF }, /* R1024 - AIF1 ADC1 Left Volume */
+ { 0x00FF, 0x01FF }, /* R1025 - AIF1 ADC1 Right Volume */
+ { 0x00FF, 0x01FF }, /* R1026 - AIF1 DAC1 Left Volume */
+ { 0x00FF, 0x01FF }, /* R1027 - AIF1 DAC1 Right Volume */
+ { 0x00FF, 0x01FF }, /* R1028 - AIF1 ADC2 Left Volume */
+ { 0x00FF, 0x01FF }, /* R1029 - AIF1 ADC2 Right Volume */
+ { 0x00FF, 0x01FF }, /* R1030 - AIF1 DAC2 Left Volume */
+ { 0x00FF, 0x01FF }, /* R1031 - AIF1 DAC2 Right Volume */
+ { 0x0000, 0x0000 }, /* R1032 */
+ { 0x0000, 0x0000 }, /* R1033 */
+ { 0x0000, 0x0000 }, /* R1034 */
+ { 0x0000, 0x0000 }, /* R1035 */
+ { 0x0000, 0x0000 }, /* R1036 */
+ { 0x0000, 0x0000 }, /* R1037 */
+ { 0x0000, 0x0000 }, /* R1038 */
+ { 0x0000, 0x0000 }, /* R1039 */
+ { 0xF800, 0xF800 }, /* R1040 - AIF1 ADC1 Filters */
+ { 0x7800, 0x7800 }, /* R1041 - AIF1 ADC2 Filters */
+ { 0x0000, 0x0000 }, /* R1042 */
+ { 0x0000, 0x0000 }, /* R1043 */
+ { 0x0000, 0x0000 }, /* R1044 */
+ { 0x0000, 0x0000 }, /* R1045 */
+ { 0x0000, 0x0000 }, /* R1046 */
+ { 0x0000, 0x0000 }, /* R1047 */
+ { 0x0000, 0x0000 }, /* R1048 */
+ { 0x0000, 0x0000 }, /* R1049 */
+ { 0x0000, 0x0000 }, /* R1050 */
+ { 0x0000, 0x0000 }, /* R1051 */
+ { 0x0000, 0x0000 }, /* R1052 */
+ { 0x0000, 0x0000 }, /* R1053 */
+ { 0x0000, 0x0000 }, /* R1054 */
+ { 0x0000, 0x0000 }, /* R1055 */
+ { 0x02B6, 0x02B6 }, /* R1056 - AIF1 DAC1 Filters (1) */
+ { 0x3F00, 0x3F00 }, /* R1057 - AIF1 DAC1 Filters (2) */
+ { 0x02B6, 0x02B6 }, /* R1058 - AIF1 DAC2 Filters (1) */
+ { 0x3F00, 0x3F00 }, /* R1059 - AIF1 DAC2 Filters (2) */
+ { 0x0000, 0x0000 }, /* R1060 */
+ { 0x0000, 0x0000 }, /* R1061 */
+ { 0x0000, 0x0000 }, /* R1062 */
+ { 0x0000, 0x0000 }, /* R1063 */
+ { 0x0000, 0x0000 }, /* R1064 */
+ { 0x0000, 0x0000 }, /* R1065 */
+ { 0x0000, 0x0000 }, /* R1066 */
+ { 0x0000, 0x0000 }, /* R1067 */
+ { 0x0000, 0x0000 }, /* R1068 */
+ { 0x0000, 0x0000 }, /* R1069 */
+ { 0x0000, 0x0000 }, /* R1070 */
+ { 0x0000, 0x0000 }, /* R1071 */
+ { 0x0000, 0x0000 }, /* R1072 */
+ { 0x0000, 0x0000 }, /* R1073 */
+ { 0x0000, 0x0000 }, /* R1074 */
+ { 0x0000, 0x0000 }, /* R1075 */
+ { 0x0000, 0x0000 }, /* R1076 */
+ { 0x0000, 0x0000 }, /* R1077 */
+ { 0x0000, 0x0000 }, /* R1078 */
+ { 0x0000, 0x0000 }, /* R1079 */
+ { 0x0000, 0x0000 }, /* R1080 */
+ { 0x0000, 0x0000 }, /* R1081 */
+ { 0x0000, 0x0000 }, /* R1082 */
+ { 0x0000, 0x0000 }, /* R1083 */
+ { 0x0000, 0x0000 }, /* R1084 */
+ { 0x0000, 0x0000 }, /* R1085 */
+ { 0x0000, 0x0000 }, /* R1086 */
+ { 0x0000, 0x0000 }, /* R1087 */
+ { 0xFFFF, 0xFFFF }, /* R1088 - AIF1 DRC1 (1) */
+ { 0x1FFF, 0x1FFF }, /* R1089 - AIF1 DRC1 (2) */
+ { 0xFFFF, 0xFFFF }, /* R1090 - AIF1 DRC1 (3) */
+ { 0x07FF, 0x07FF }, /* R1091 - AIF1 DRC1 (4) */
+ { 0x03FF, 0x03FF }, /* R1092 - AIF1 DRC1 (5) */
+ { 0x0000, 0x0000 }, /* R1093 */
+ { 0x0000, 0x0000 }, /* R1094 */
+ { 0x0000, 0x0000 }, /* R1095 */
+ { 0x0000, 0x0000 }, /* R1096 */
+ { 0x0000, 0x0000 }, /* R1097 */
+ { 0x0000, 0x0000 }, /* R1098 */
+ { 0x0000, 0x0000 }, /* R1099 */
+ { 0x0000, 0x0000 }, /* R1100 */
+ { 0x0000, 0x0000 }, /* R1101 */
+ { 0x0000, 0x0000 }, /* R1102 */
+ { 0x0000, 0x0000 }, /* R1103 */
+ { 0xFFFF, 0xFFFF }, /* R1104 - AIF1 DRC2 (1) */
+ { 0x1FFF, 0x1FFF }, /* R1105 - AIF1 DRC2 (2) */
+ { 0xFFFF, 0xFFFF }, /* R1106 - AIF1 DRC2 (3) */
+ { 0x07FF, 0x07FF }, /* R1107 - AIF1 DRC2 (4) */
+ { 0x03FF, 0x03FF }, /* R1108 - AIF1 DRC2 (5) */
+ { 0x0000, 0x0000 }, /* R1109 */
+ { 0x0000, 0x0000 }, /* R1110 */
+ { 0x0000, 0x0000 }, /* R1111 */
+ { 0x0000, 0x0000 }, /* R1112 */
+ { 0x0000, 0x0000 }, /* R1113 */
+ { 0x0000, 0x0000 }, /* R1114 */
+ { 0x0000, 0x0000 }, /* R1115 */
+ { 0x0000, 0x0000 }, /* R1116 */
+ { 0x0000, 0x0000 }, /* R1117 */
+ { 0x0000, 0x0000 }, /* R1118 */
+ { 0x0000, 0x0000 }, /* R1119 */
+ { 0x0000, 0x0000 }, /* R1120 */
+ { 0x0000, 0x0000 }, /* R1121 */
+ { 0x0000, 0x0000 }, /* R1122 */
+ { 0x0000, 0x0000 }, /* R1123 */
+ { 0x0000, 0x0000 }, /* R1124 */
+ { 0x0000, 0x0000 }, /* R1125 */
+ { 0x0000, 0x0000 }, /* R1126 */
+ { 0x0000, 0x0000 }, /* R1127 */
+ { 0x0000, 0x0000 }, /* R1128 */
+ { 0x0000, 0x0000 }, /* R1129 */
+ { 0x0000, 0x0000 }, /* R1130 */
+ { 0x0000, 0x0000 }, /* R1131 */
+ { 0x0000, 0x0000 }, /* R1132 */
+ { 0x0000, 0x0000 }, /* R1133 */
+ { 0x0000, 0x0000 }, /* R1134 */
+ { 0x0000, 0x0000 }, /* R1135 */
+ { 0x0000, 0x0000 }, /* R1136 */
+ { 0x0000, 0x0000 }, /* R1137 */
+ { 0x0000, 0x0000 }, /* R1138 */
+ { 0x0000, 0x0000 }, /* R1139 */
+ { 0x0000, 0x0000 }, /* R1140 */
+ { 0x0000, 0x0000 }, /* R1141 */
+ { 0x0000, 0x0000 }, /* R1142 */
+ { 0x0000, 0x0000 }, /* R1143 */
+ { 0x0000, 0x0000 }, /* R1144 */
+ { 0x0000, 0x0000 }, /* R1145 */
+ { 0x0000, 0x0000 }, /* R1146 */
+ { 0x0000, 0x0000 }, /* R1147 */
+ { 0x0000, 0x0000 }, /* R1148 */
+ { 0x0000, 0x0000 }, /* R1149 */
+ { 0x0000, 0x0000 }, /* R1150 */
+ { 0x0000, 0x0000 }, /* R1151 */
+ { 0xFFFF, 0xFFFF }, /* R1152 - AIF1 DAC1 EQ Gains (1) */
+ { 0xFFC0, 0xFFC0 }, /* R1153 - AIF1 DAC1 EQ Gains (2) */
+ { 0xFFFF, 0xFFFF }, /* R1154 - AIF1 DAC1 EQ Band 1 A */
+ { 0xFFFF, 0xFFFF }, /* R1155 - AIF1 DAC1 EQ Band 1 B */
+ { 0xFFFF, 0xFFFF }, /* R1156 - AIF1 DAC1 EQ Band 1 PG */
+ { 0xFFFF, 0xFFFF }, /* R1157 - AIF1 DAC1 EQ Band 2 A */
+ { 0xFFFF, 0xFFFF }, /* R1158 - AIF1 DAC1 EQ Band 2 B */
+ { 0xFFFF, 0xFFFF }, /* R1159 - AIF1 DAC1 EQ Band 2 C */
+ { 0xFFFF, 0xFFFF }, /* R1160 - AIF1 DAC1 EQ Band 2 PG */
+ { 0xFFFF, 0xFFFF }, /* R1161 - AIF1 DAC1 EQ Band 3 A */
+ { 0xFFFF, 0xFFFF }, /* R1162 - AIF1 DAC1 EQ Band 3 B */
+ { 0xFFFF, 0xFFFF }, /* R1163 - AIF1 DAC1 EQ Band 3 C */
+ { 0xFFFF, 0xFFFF }, /* R1164 - AIF1 DAC1 EQ Band 3 PG */
+ { 0xFFFF, 0xFFFF }, /* R1165 - AIF1 DAC1 EQ Band 4 A */
+ { 0xFFFF, 0xFFFF }, /* R1166 - AIF1 DAC1 EQ Band 4 B */
+ { 0xFFFF, 0xFFFF }, /* R1167 - AIF1 DAC1 EQ Band 4 C */
+ { 0xFFFF, 0xFFFF }, /* R1168 - AIF1 DAC1 EQ Band 4 PG */
+ { 0xFFFF, 0xFFFF }, /* R1169 - AIF1 DAC1 EQ Band 5 A */
+ { 0xFFFF, 0xFFFF }, /* R1170 - AIF1 DAC1 EQ Band 5 B */
+ { 0xFFFF, 0xFFFF }, /* R1171 - AIF1 DAC1 EQ Band 5 PG */
+ { 0x0000, 0x0000 }, /* R1172 */
+ { 0x0000, 0x0000 }, /* R1173 */
+ { 0x0000, 0x0000 }, /* R1174 */
+ { 0x0000, 0x0000 }, /* R1175 */
+ { 0x0000, 0x0000 }, /* R1176 */
+ { 0x0000, 0x0000 }, /* R1177 */
+ { 0x0000, 0x0000 }, /* R1178 */
+ { 0x0000, 0x0000 }, /* R1179 */
+ { 0x0000, 0x0000 }, /* R1180 */
+ { 0x0000, 0x0000 }, /* R1181 */
+ { 0x0000, 0x0000 }, /* R1182 */
+ { 0x0000, 0x0000 }, /* R1183 */
+ { 0xFFFF, 0xFFFF }, /* R1184 - AIF1 DAC2 EQ Gains (1) */
+ { 0xFFC0, 0xFFC0 }, /* R1185 - AIF1 DAC2 EQ Gains (2) */
+ { 0xFFFF, 0xFFFF }, /* R1186 - AIF1 DAC2 EQ Band 1 A */
+ { 0xFFFF, 0xFFFF }, /* R1187 - AIF1 DAC2 EQ Band 1 B */
+ { 0xFFFF, 0xFFFF }, /* R1188 - AIF1 DAC2 EQ Band 1 PG */
+ { 0xFFFF, 0xFFFF }, /* R1189 - AIF1 DAC2 EQ Band 2 A */
+ { 0xFFFF, 0xFFFF }, /* R1190 - AIF1 DAC2 EQ Band 2 B */
+ { 0xFFFF, 0xFFFF }, /* R1191 - AIF1 DAC2 EQ Band 2 C */
+ { 0xFFFF, 0xFFFF }, /* R1192 - AIF1 DAC2 EQ Band 2 PG */
+ { 0xFFFF, 0xFFFF }, /* R1193 - AIF1 DAC2 EQ Band 3 A */
+ { 0xFFFF, 0xFFFF }, /* R1194 - AIF1 DAC2 EQ Band 3 B */
+ { 0xFFFF, 0xFFFF }, /* R1195 - AIF1 DAC2 EQ Band 3 C */
+ { 0xFFFF, 0xFFFF }, /* R1196 - AIF1 DAC2 EQ Band 3 PG */
+ { 0xFFFF, 0xFFFF }, /* R1197 - AIF1 DAC2 EQ Band 4 A */
+ { 0xFFFF, 0xFFFF }, /* R1198 - AIF1 DAC2 EQ Band 4 B */
+ { 0xFFFF, 0xFFFF }, /* R1199 - AIF1 DAC2 EQ Band 4 C */
+ { 0xFFFF, 0xFFFF }, /* R1200 - AIF1 DAC2 EQ Band 4 PG */
+ { 0xFFFF, 0xFFFF }, /* R1201 - AIF1 DAC2 EQ Band 5 A */
+ { 0xFFFF, 0xFFFF }, /* R1202 - AIF1 DAC2 EQ Band 5 B */
+ { 0xFFFF, 0xFFFF }, /* R1203 - AIF1 DAC2 EQ Band 5 PG */
+ { 0x0000, 0x0000 }, /* R1204 */
+ { 0x0000, 0x0000 }, /* R1205 */
+ { 0x0000, 0x0000 }, /* R1206 */
+ { 0x0000, 0x0000 }, /* R1207 */
+ { 0x0000, 0x0000 }, /* R1208 */
+ { 0x0000, 0x0000 }, /* R1209 */
+ { 0x0000, 0x0000 }, /* R1210 */
+ { 0x0000, 0x0000 }, /* R1211 */
+ { 0x0000, 0x0000 }, /* R1212 */
+ { 0x0000, 0x0000 }, /* R1213 */
+ { 0x0000, 0x0000 }, /* R1214 */
+ { 0x0000, 0x0000 }, /* R1215 */
+ { 0x0000, 0x0000 }, /* R1216 */
+ { 0x0000, 0x0000 }, /* R1217 */
+ { 0x0000, 0x0000 }, /* R1218 */
+ { 0x0000, 0x0000 }, /* R1219 */
+ { 0x0000, 0x0000 }, /* R1220 */
+ { 0x0000, 0x0000 }, /* R1221 */
+ { 0x0000, 0x0000 }, /* R1222 */
+ { 0x0000, 0x0000 }, /* R1223 */
+ { 0x0000, 0x0000 }, /* R1224 */
+ { 0x0000, 0x0000 }, /* R1225 */
+ { 0x0000, 0x0000 }, /* R1226 */
+ { 0x0000, 0x0000 }, /* R1227 */
+ { 0x0000, 0x0000 }, /* R1228 */
+ { 0x0000, 0x0000 }, /* R1229 */
+ { 0x0000, 0x0000 }, /* R1230 */
+ { 0x0000, 0x0000 }, /* R1231 */
+ { 0x0000, 0x0000 }, /* R1232 */
+ { 0x0000, 0x0000 }, /* R1233 */
+ { 0x0000, 0x0000 }, /* R1234 */
+ { 0x0000, 0x0000 }, /* R1235 */
+ { 0x0000, 0x0000 }, /* R1236 */
+ { 0x0000, 0x0000 }, /* R1237 */
+ { 0x0000, 0x0000 }, /* R1238 */
+ { 0x0000, 0x0000 }, /* R1239 */
+ { 0x0000, 0x0000 }, /* R1240 */
+ { 0x0000, 0x0000 }, /* R1241 */
+ { 0x0000, 0x0000 }, /* R1242 */
+ { 0x0000, 0x0000 }, /* R1243 */
+ { 0x0000, 0x0000 }, /* R1244 */
+ { 0x0000, 0x0000 }, /* R1245 */
+ { 0x0000, 0x0000 }, /* R1246 */
+ { 0x0000, 0x0000 }, /* R1247 */
+ { 0x0000, 0x0000 }, /* R1248 */
+ { 0x0000, 0x0000 }, /* R1249 */
+ { 0x0000, 0x0000 }, /* R1250 */
+ { 0x0000, 0x0000 }, /* R1251 */
+ { 0x0000, 0x0000 }, /* R1252 */
+ { 0x0000, 0x0000 }, /* R1253 */
+ { 0x0000, 0x0000 }, /* R1254 */
+ { 0x0000, 0x0000 }, /* R1255 */
+ { 0x0000, 0x0000 }, /* R1256 */
+ { 0x0000, 0x0000 }, /* R1257 */
+ { 0x0000, 0x0000 }, /* R1258 */
+ { 0x0000, 0x0000 }, /* R1259 */
+ { 0x0000, 0x0000 }, /* R1260 */
+ { 0x0000, 0x0000 }, /* R1261 */
+ { 0x0000, 0x0000 }, /* R1262 */
+ { 0x0000, 0x0000 }, /* R1263 */
+ { 0x0000, 0x0000 }, /* R1264 */
+ { 0x0000, 0x0000 }, /* R1265 */
+ { 0x0000, 0x0000 }, /* R1266 */
+ { 0x0000, 0x0000 }, /* R1267 */
+ { 0x0000, 0x0000 }, /* R1268 */
+ { 0x0000, 0x0000 }, /* R1269 */
+ { 0x0000, 0x0000 }, /* R1270 */
+ { 0x0000, 0x0000 }, /* R1271 */
+ { 0x0000, 0x0000 }, /* R1272 */
+ { 0x0000, 0x0000 }, /* R1273 */
+ { 0x0000, 0x0000 }, /* R1274 */
+ { 0x0000, 0x0000 }, /* R1275 */
+ { 0x0000, 0x0000 }, /* R1276 */
+ { 0x0000, 0x0000 }, /* R1277 */
+ { 0x0000, 0x0000 }, /* R1278 */
+ { 0x0000, 0x0000 }, /* R1279 */
+ { 0x00FF, 0x01FF }, /* R1280 - AIF2 ADC Left Volume */
+ { 0x00FF, 0x01FF }, /* R1281 - AIF2 ADC Right Volume */
+ { 0x00FF, 0x01FF }, /* R1282 - AIF2 DAC Left Volume */
+ { 0x00FF, 0x01FF }, /* R1283 - AIF2 DAC Right Volume */
+ { 0x0000, 0x0000 }, /* R1284 */
+ { 0x0000, 0x0000 }, /* R1285 */
+ { 0x0000, 0x0000 }, /* R1286 */
+ { 0x0000, 0x0000 }, /* R1287 */
+ { 0x0000, 0x0000 }, /* R1288 */
+ { 0x0000, 0x0000 }, /* R1289 */
+ { 0x0000, 0x0000 }, /* R1290 */
+ { 0x0000, 0x0000 }, /* R1291 */
+ { 0x0000, 0x0000 }, /* R1292 */
+ { 0x0000, 0x0000 }, /* R1293 */
+ { 0x0000, 0x0000 }, /* R1294 */
+ { 0x0000, 0x0000 }, /* R1295 */
+ { 0xF800, 0xF800 }, /* R1296 - AIF2 ADC Filters */
+ { 0x0000, 0x0000 }, /* R1297 */
+ { 0x0000, 0x0000 }, /* R1298 */
+ { 0x0000, 0x0000 }, /* R1299 */
+ { 0x0000, 0x0000 }, /* R1300 */
+ { 0x0000, 0x0000 }, /* R1301 */
+ { 0x0000, 0x0000 }, /* R1302 */
+ { 0x0000, 0x0000 }, /* R1303 */
+ { 0x0000, 0x0000 }, /* R1304 */
+ { 0x0000, 0x0000 }, /* R1305 */
+ { 0x0000, 0x0000 }, /* R1306 */
+ { 0x0000, 0x0000 }, /* R1307 */
+ { 0x0000, 0x0000 }, /* R1308 */
+ { 0x0000, 0x0000 }, /* R1309 */
+ { 0x0000, 0x0000 }, /* R1310 */
+ { 0x0000, 0x0000 }, /* R1311 */
+ { 0x02B6, 0x02B6 }, /* R1312 - AIF2 DAC Filters (1) */
+ { 0x3F00, 0x3F00 }, /* R1313 - AIF2 DAC Filters (2) */
+ { 0x0000, 0x0000 }, /* R1314 */
+ { 0x0000, 0x0000 }, /* R1315 */
+ { 0x0000, 0x0000 }, /* R1316 */
+ { 0x0000, 0x0000 }, /* R1317 */
+ { 0x0000, 0x0000 }, /* R1318 */
+ { 0x0000, 0x0000 }, /* R1319 */
+ { 0x0000, 0x0000 }, /* R1320 */
+ { 0x0000, 0x0000 }, /* R1321 */
+ { 0x0000, 0x0000 }, /* R1322 */
+ { 0x0000, 0x0000 }, /* R1323 */
+ { 0x0000, 0x0000 }, /* R1324 */
+ { 0x0000, 0x0000 }, /* R1325 */
+ { 0x0000, 0x0000 }, /* R1326 */
+ { 0x0000, 0x0000 }, /* R1327 */
+ { 0x0000, 0x0000 }, /* R1328 */
+ { 0x0000, 0x0000 }, /* R1329 */
+ { 0x0000, 0x0000 }, /* R1330 */
+ { 0x0000, 0x0000 }, /* R1331 */
+ { 0x0000, 0x0000 }, /* R1332 */
+ { 0x0000, 0x0000 }, /* R1333 */
+ { 0x0000, 0x0000 }, /* R1334 */
+ { 0x0000, 0x0000 }, /* R1335 */
+ { 0x0000, 0x0000 }, /* R1336 */
+ { 0x0000, 0x0000 }, /* R1337 */
+ { 0x0000, 0x0000 }, /* R1338 */
+ { 0x0000, 0x0000 }, /* R1339 */
+ { 0x0000, 0x0000 }, /* R1340 */
+ { 0x0000, 0x0000 }, /* R1341 */
+ { 0x0000, 0x0000 }, /* R1342 */
+ { 0x0000, 0x0000 }, /* R1343 */
+ { 0xFFFF, 0xFFFF }, /* R1344 - AIF2 DRC (1) */
+ { 0x1FFF, 0x1FFF }, /* R1345 - AIF2 DRC (2) */
+ { 0xFFFF, 0xFFFF }, /* R1346 - AIF2 DRC (3) */
+ { 0x07FF, 0x07FF }, /* R1347 - AIF2 DRC (4) */
+ { 0x03FF, 0x03FF }, /* R1348 - AIF2 DRC (5) */
+ { 0x0000, 0x0000 }, /* R1349 */
+ { 0x0000, 0x0000 }, /* R1350 */
+ { 0x0000, 0x0000 }, /* R1351 */
+ { 0x0000, 0x0000 }, /* R1352 */
+ { 0x0000, 0x0000 }, /* R1353 */
+ { 0x0000, 0x0000 }, /* R1354 */
+ { 0x0000, 0x0000 }, /* R1355 */
+ { 0x0000, 0x0000 }, /* R1356 */
+ { 0x0000, 0x0000 }, /* R1357 */
+ { 0x0000, 0x0000 }, /* R1358 */
+ { 0x0000, 0x0000 }, /* R1359 */
+ { 0x0000, 0x0000 }, /* R1360 */
+ { 0x0000, 0x0000 }, /* R1361 */
+ { 0x0000, 0x0000 }, /* R1362 */
+ { 0x0000, 0x0000 }, /* R1363 */
+ { 0x0000, 0x0000 }, /* R1364 */
+ { 0x0000, 0x0000 }, /* R1365 */
+ { 0x0000, 0x0000 }, /* R1366 */
+ { 0x0000, 0x0000 }, /* R1367 */
+ { 0x0000, 0x0000 }, /* R1368 */
+ { 0x0000, 0x0000 }, /* R1369 */
+ { 0x0000, 0x0000 }, /* R1370 */
+ { 0x0000, 0x0000 }, /* R1371 */
+ { 0x0000, 0x0000 }, /* R1372 */
+ { 0x0000, 0x0000 }, /* R1373 */
+ { 0x0000, 0x0000 }, /* R1374 */
+ { 0x0000, 0x0000 }, /* R1375 */
+ { 0x0000, 0x0000 }, /* R1376 */
+ { 0x0000, 0x0000 }, /* R1377 */
+ { 0x0000, 0x0000 }, /* R1378 */
+ { 0x0000, 0x0000 }, /* R1379 */
+ { 0x0000, 0x0000 }, /* R1380 */
+ { 0x0000, 0x0000 }, /* R1381 */
+ { 0x0000, 0x0000 }, /* R1382 */
+ { 0x0000, 0x0000 }, /* R1383 */
+ { 0x0000, 0x0000 }, /* R1384 */
+ { 0x0000, 0x0000 }, /* R1385 */
+ { 0x0000, 0x0000 }, /* R1386 */
+ { 0x0000, 0x0000 }, /* R1387 */
+ { 0x0000, 0x0000 }, /* R1388 */
+ { 0x0000, 0x0000 }, /* R1389 */
+ { 0x0000, 0x0000 }, /* R1390 */
+ { 0x0000, 0x0000 }, /* R1391 */
+ { 0x0000, 0x0000 }, /* R1392 */
+ { 0x0000, 0x0000 }, /* R1393 */
+ { 0x0000, 0x0000 }, /* R1394 */
+ { 0x0000, 0x0000 }, /* R1395 */
+ { 0x0000, 0x0000 }, /* R1396 */
+ { 0x0000, 0x0000 }, /* R1397 */
+ { 0x0000, 0x0000 }, /* R1398 */
+ { 0x0000, 0x0000 }, /* R1399 */
+ { 0x0000, 0x0000 }, /* R1400 */
+ { 0x0000, 0x0000 }, /* R1401 */
+ { 0x0000, 0x0000 }, /* R1402 */
+ { 0x0000, 0x0000 }, /* R1403 */
+ { 0x0000, 0x0000 }, /* R1404 */
+ { 0x0000, 0x0000 }, /* R1405 */
+ { 0x0000, 0x0000 }, /* R1406 */
+ { 0x0000, 0x0000 }, /* R1407 */
+ { 0xFFFF, 0xFFFF }, /* R1408 - AIF2 EQ Gains (1) */
+ { 0xFFC0, 0xFFC0 }, /* R1409 - AIF2 EQ Gains (2) */
+ { 0xFFFF, 0xFFFF }, /* R1410 - AIF2 EQ Band 1 A */
+ { 0xFFFF, 0xFFFF }, /* R1411 - AIF2 EQ Band 1 B */
+ { 0xFFFF, 0xFFFF }, /* R1412 - AIF2 EQ Band 1 PG */
+ { 0xFFFF, 0xFFFF }, /* R1413 - AIF2 EQ Band 2 A */
+ { 0xFFFF, 0xFFFF }, /* R1414 - AIF2 EQ Band 2 B */
+ { 0xFFFF, 0xFFFF }, /* R1415 - AIF2 EQ Band 2 C */
+ { 0xFFFF, 0xFFFF }, /* R1416 - AIF2 EQ Band 2 PG */
+ { 0xFFFF, 0xFFFF }, /* R1417 - AIF2 EQ Band 3 A */
+ { 0xFFFF, 0xFFFF }, /* R1418 - AIF2 EQ Band 3 B */
+ { 0xFFFF, 0xFFFF }, /* R1419 - AIF2 EQ Band 3 C */
+ { 0xFFFF, 0xFFFF }, /* R1420 - AIF2 EQ Band 3 PG */
+ { 0xFFFF, 0xFFFF }, /* R1421 - AIF2 EQ Band 4 A */
+ { 0xFFFF, 0xFFFF }, /* R1422 - AIF2 EQ Band 4 B */
+ { 0xFFFF, 0xFFFF }, /* R1423 - AIF2 EQ Band 4 C */
+ { 0xFFFF, 0xFFFF }, /* R1424 - AIF2 EQ Band 4 PG */
+ { 0xFFFF, 0xFFFF }, /* R1425 - AIF2 EQ Band 5 A */
+ { 0xFFFF, 0xFFFF }, /* R1426 - AIF2 EQ Band 5 B */
+ { 0xFFFF, 0xFFFF }, /* R1427 - AIF2 EQ Band 5 PG */
+ { 0x0000, 0x0000 }, /* R1428 */
+ { 0x0000, 0x0000 }, /* R1429 */
+ { 0x0000, 0x0000 }, /* R1430 */
+ { 0x0000, 0x0000 }, /* R1431 */
+ { 0x0000, 0x0000 }, /* R1432 */
+ { 0x0000, 0x0000 }, /* R1433 */
+ { 0x0000, 0x0000 }, /* R1434 */
+ { 0x0000, 0x0000 }, /* R1435 */
+ { 0x0000, 0x0000 }, /* R1436 */
+ { 0x0000, 0x0000 }, /* R1437 */
+ { 0x0000, 0x0000 }, /* R1438 */
+ { 0x0000, 0x0000 }, /* R1439 */
+ { 0x0000, 0x0000 }, /* R1440 */
+ { 0x0000, 0x0000 }, /* R1441 */
+ { 0x0000, 0x0000 }, /* R1442 */
+ { 0x0000, 0x0000 }, /* R1443 */
+ { 0x0000, 0x0000 }, /* R1444 */
+ { 0x0000, 0x0000 }, /* R1445 */
+ { 0x0000, 0x0000 }, /* R1446 */
+ { 0x0000, 0x0000 }, /* R1447 */
+ { 0x0000, 0x0000 }, /* R1448 */
+ { 0x0000, 0x0000 }, /* R1449 */
+ { 0x0000, 0x0000 }, /* R1450 */
+ { 0x0000, 0x0000 }, /* R1451 */
+ { 0x0000, 0x0000 }, /* R1452 */
+ { 0x0000, 0x0000 }, /* R1453 */
+ { 0x0000, 0x0000 }, /* R1454 */
+ { 0x0000, 0x0000 }, /* R1455 */
+ { 0x0000, 0x0000 }, /* R1456 */
+ { 0x0000, 0x0000 }, /* R1457 */
+ { 0x0000, 0x0000 }, /* R1458 */
+ { 0x0000, 0x0000 }, /* R1459 */
+ { 0x0000, 0x0000 }, /* R1460 */
+ { 0x0000, 0x0000 }, /* R1461 */
+ { 0x0000, 0x0000 }, /* R1462 */
+ { 0x0000, 0x0000 }, /* R1463 */
+ { 0x0000, 0x0000 }, /* R1464 */
+ { 0x0000, 0x0000 }, /* R1465 */
+ { 0x0000, 0x0000 }, /* R1466 */
+ { 0x0000, 0x0000 }, /* R1467 */
+ { 0x0000, 0x0000 }, /* R1468 */
+ { 0x0000, 0x0000 }, /* R1469 */
+ { 0x0000, 0x0000 }, /* R1470 */
+ { 0x0000, 0x0000 }, /* R1471 */
+ { 0x0000, 0x0000 }, /* R1472 */
+ { 0x0000, 0x0000 }, /* R1473 */
+ { 0x0000, 0x0000 }, /* R1474 */
+ { 0x0000, 0x0000 }, /* R1475 */
+ { 0x0000, 0x0000 }, /* R1476 */
+ { 0x0000, 0x0000 }, /* R1477 */
+ { 0x0000, 0x0000 }, /* R1478 */
+ { 0x0000, 0x0000 }, /* R1479 */
+ { 0x0000, 0x0000 }, /* R1480 */
+ { 0x0000, 0x0000 }, /* R1481 */
+ { 0x0000, 0x0000 }, /* R1482 */
+ { 0x0000, 0x0000 }, /* R1483 */
+ { 0x0000, 0x0000 }, /* R1484 */
+ { 0x0000, 0x0000 }, /* R1485 */
+ { 0x0000, 0x0000 }, /* R1486 */
+ { 0x0000, 0x0000 }, /* R1487 */
+ { 0x0000, 0x0000 }, /* R1488 */
+ { 0x0000, 0x0000 }, /* R1489 */
+ { 0x0000, 0x0000 }, /* R1490 */
+ { 0x0000, 0x0000 }, /* R1491 */
+ { 0x0000, 0x0000 }, /* R1492 */
+ { 0x0000, 0x0000 }, /* R1493 */
+ { 0x0000, 0x0000 }, /* R1494 */
+ { 0x0000, 0x0000 }, /* R1495 */
+ { 0x0000, 0x0000 }, /* R1496 */
+ { 0x0000, 0x0000 }, /* R1497 */
+ { 0x0000, 0x0000 }, /* R1498 */
+ { 0x0000, 0x0000 }, /* R1499 */
+ { 0x0000, 0x0000 }, /* R1500 */
+ { 0x0000, 0x0000 }, /* R1501 */
+ { 0x0000, 0x0000 }, /* R1502 */
+ { 0x0000, 0x0000 }, /* R1503 */
+ { 0x0000, 0x0000 }, /* R1504 */
+ { 0x0000, 0x0000 }, /* R1505 */
+ { 0x0000, 0x0000 }, /* R1506 */
+ { 0x0000, 0x0000 }, /* R1507 */
+ { 0x0000, 0x0000 }, /* R1508 */
+ { 0x0000, 0x0000 }, /* R1509 */
+ { 0x0000, 0x0000 }, /* R1510 */
+ { 0x0000, 0x0000 }, /* R1511 */
+ { 0x0000, 0x0000 }, /* R1512 */
+ { 0x0000, 0x0000 }, /* R1513 */
+ { 0x0000, 0x0000 }, /* R1514 */
+ { 0x0000, 0x0000 }, /* R1515 */
+ { 0x0000, 0x0000 }, /* R1516 */
+ { 0x0000, 0x0000 }, /* R1517 */
+ { 0x0000, 0x0000 }, /* R1518 */
+ { 0x0000, 0x0000 }, /* R1519 */
+ { 0x0000, 0x0000 }, /* R1520 */
+ { 0x0000, 0x0000 }, /* R1521 */
+ { 0x0000, 0x0000 }, /* R1522 */
+ { 0x0000, 0x0000 }, /* R1523 */
+ { 0x0000, 0x0000 }, /* R1524 */
+ { 0x0000, 0x0000 }, /* R1525 */
+ { 0x0000, 0x0000 }, /* R1526 */
+ { 0x0000, 0x0000 }, /* R1527 */
+ { 0x0000, 0x0000 }, /* R1528 */
+ { 0x0000, 0x0000 }, /* R1529 */
+ { 0x0000, 0x0000 }, /* R1530 */
+ { 0x0000, 0x0000 }, /* R1531 */
+ { 0x0000, 0x0000 }, /* R1532 */
+ { 0x0000, 0x0000 }, /* R1533 */
+ { 0x0000, 0x0000 }, /* R1534 */
+ { 0x0000, 0x0000 }, /* R1535 */
+ { 0x01EF, 0x01EF }, /* R1536 - DAC1 Mixer Volumes */
+ { 0x0037, 0x0037 }, /* R1537 - DAC1 Left Mixer Routing */
+ { 0x0037, 0x0037 }, /* R1538 - DAC1 Right Mixer Routing */
+ { 0x01EF, 0x01EF }, /* R1539 - DAC2 Mixer Volumes */
+ { 0x0037, 0x0037 }, /* R1540 - DAC2 Left Mixer Routing */
+ { 0x0037, 0x0037 }, /* R1541 - DAC2 Right Mixer Routing */
+ { 0x0003, 0x0003 }, /* R1542 - AIF1 ADC1 Left Mixer Routing */
+ { 0x0003, 0x0003 }, /* R1543 - AIF1 ADC1 Right Mixer Routing */
+ { 0x0003, 0x0003 }, /* R1544 - AIF1 ADC2 Left Mixer Routing */
+ { 0x0003, 0x0003 }, /* R1545 - AIF1 ADC2 Right mixer Routing */
+ { 0x0000, 0x0000 }, /* R1546 */
+ { 0x0000, 0x0000 }, /* R1547 */
+ { 0x0000, 0x0000 }, /* R1548 */
+ { 0x0000, 0x0000 }, /* R1549 */
+ { 0x0000, 0x0000 }, /* R1550 */
+ { 0x0000, 0x0000 }, /* R1551 */
+ { 0x02FF, 0x03FF }, /* R1552 - DAC1 Left Volume */
+ { 0x02FF, 0x03FF }, /* R1553 - DAC1 Right Volume */
+ { 0x02FF, 0x03FF }, /* R1554 - DAC2 Left Volume */
+ { 0x02FF, 0x03FF }, /* R1555 - DAC2 Right Volume */
+ { 0x0003, 0x0003 }, /* R1556 - DAC Softmute */
+ { 0x0000, 0x0000 }, /* R1557 */
+ { 0x0000, 0x0000 }, /* R1558 */
+ { 0x0000, 0x0000 }, /* R1559 */
+ { 0x0000, 0x0000 }, /* R1560 */
+ { 0x0000, 0x0000 }, /* R1561 */
+ { 0x0000, 0x0000 }, /* R1562 */
+ { 0x0000, 0x0000 }, /* R1563 */
+ { 0x0000, 0x0000 }, /* R1564 */
+ { 0x0000, 0x0000 }, /* R1565 */
+ { 0x0000, 0x0000 }, /* R1566 */
+ { 0x0000, 0x0000 }, /* R1567 */
+ { 0x0003, 0x0003 }, /* R1568 - Oversampling */
+ { 0x03C3, 0x03C3 }, /* R1569 - Sidetone */
+};
+
+const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE] = {
+ 0x8994, /* R0 - Software Reset */
+ 0x0000, /* R1 - Power Management (1) */
+ 0x6000, /* R2 - Power Management (2) */
+ 0x0000, /* R3 - Power Management (3) */
+ 0x0000, /* R4 - Power Management (4) */
+ 0x0000, /* R5 - Power Management (5) */
+ 0x0000, /* R6 - Power Management (6) */
+ 0x0000, /* R7 */
+ 0x0000, /* R8 */
+ 0x0000, /* R9 */
+ 0x0000, /* R10 */
+ 0x0000, /* R11 */
+ 0x0000, /* R12 */
+ 0x0000, /* R13 */
+ 0x0000, /* R14 */
+ 0x0000, /* R15 */
+ 0x0000, /* R16 */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 */
+ 0x0000, /* R19 */
+ 0x0000, /* R20 */
+ 0x0000, /* R21 - Input Mixer (1) */
+ 0x0000, /* R22 */
+ 0x0000, /* R23 */
+ 0x008B, /* R24 - Left Line Input 1&2 Volume */
+ 0x008B, /* R25 - Left Line Input 3&4 Volume */
+ 0x008B, /* R26 - Right Line Input 1&2 Volume */
+ 0x008B, /* R27 - Right Line Input 3&4 Volume */
+ 0x006D, /* R28 - Left Output Volume */
+ 0x006D, /* R29 - Right Output Volume */
+ 0x0066, /* R30 - Line Outputs Volume */
+ 0x0020, /* R31 - HPOUT2 Volume */
+ 0x0079, /* R32 - Left OPGA Volume */
+ 0x0079, /* R33 - Right OPGA Volume */
+ 0x0003, /* R34 - SPKMIXL Attenuation */
+ 0x0003, /* R35 - SPKMIXR Attenuation */
+ 0x0011, /* R36 - SPKOUT Mixers */
+ 0x0140, /* R37 - ClassD */
+ 0x0079, /* R38 - Speaker Volume Left */
+ 0x0079, /* R39 - Speaker Volume Right */
+ 0x0000, /* R40 - Input Mixer (2) */
+ 0x0000, /* R41 - Input Mixer (3) */
+ 0x0000, /* R42 - Input Mixer (4) */
+ 0x0000, /* R43 - Input Mixer (5) */
+ 0x0000, /* R44 - Input Mixer (6) */
+ 0x0000, /* R45 - Output Mixer (1) */
+ 0x0000, /* R46 - Output Mixer (2) */
+ 0x0000, /* R47 - Output Mixer (3) */
+ 0x0000, /* R48 - Output Mixer (4) */
+ 0x0000, /* R49 - Output Mixer (5) */
+ 0x0000, /* R50 - Output Mixer (6) */
+ 0x0000, /* R51 - HPOUT2 Mixer */
+ 0x0000, /* R52 - Line Mixer (1) */
+ 0x0000, /* R53 - Line Mixer (2) */
+ 0x0000, /* R54 - Speaker Mixer */
+ 0x0000, /* R55 - Additional Control */
+ 0x0000, /* R56 - AntiPOP (1) */
+ 0x0000, /* R57 - AntiPOP (2) */
+ 0x0000, /* R58 - MICBIAS */
+ 0x000D, /* R59 - LDO 1 */
+ 0x0003, /* R60 - LDO 2 */
+ 0x0000, /* R61 */
+ 0x0000, /* R62 */
+ 0x0000, /* R63 */
+ 0x0000, /* R64 */
+ 0x0000, /* R65 */
+ 0x0000, /* R66 */
+ 0x0000, /* R67 */
+ 0x0000, /* R68 */
+ 0x0000, /* R69 */
+ 0x0000, /* R70 */
+ 0x0000, /* R71 */
+ 0x0000, /* R72 */
+ 0x0000, /* R73 */
+ 0x0000, /* R74 */
+ 0x0000, /* R75 */
+ 0x1F25, /* R76 - Charge Pump (1) */
+ 0x0000, /* R77 */
+ 0x0000, /* R78 */
+ 0x0000, /* R79 */
+ 0x0000, /* R80 */
+ 0x0004, /* R81 - Class W (1) */
+ 0x0000, /* R82 */
+ 0x0000, /* R83 */
+ 0x0000, /* R84 - DC Servo (1) */
+ 0x054A, /* R85 - DC Servo (2) */
+ 0x0000, /* R86 */
+ 0x0000, /* R87 - DC Servo (4) */
+ 0x0000, /* R88 - DC Servo Readback */
+ 0x0000, /* R89 */
+ 0x0000, /* R90 */
+ 0x0000, /* R91 */
+ 0x0000, /* R92 */
+ 0x0000, /* R93 */
+ 0x0000, /* R94 */
+ 0x0000, /* R95 */
+ 0x0000, /* R96 - Analogue HP (1) */
+ 0x0000, /* R97 */
+ 0x0000, /* R98 */
+ 0x0000, /* R99 */
+ 0x0000, /* R100 */
+ 0x0000, /* R101 */
+ 0x0000, /* R102 */
+ 0x0000, /* R103 */
+ 0x0000, /* R104 */
+ 0x0000, /* R105 */
+ 0x0000, /* R106 */
+ 0x0000, /* R107 */
+ 0x0000, /* R108 */
+ 0x0000, /* R109 */
+ 0x0000, /* R110 */
+ 0x0000, /* R111 */
+ 0x0000, /* R112 */
+ 0x0000, /* R113 */
+ 0x0000, /* R114 */
+ 0x0000, /* R115 */
+ 0x0000, /* R116 */
+ 0x0000, /* R117 */
+ 0x0000, /* R118 */
+ 0x0000, /* R119 */
+ 0x0000, /* R120 */
+ 0x0000, /* R121 */
+ 0x0000, /* R122 */
+ 0x0000, /* R123 */
+ 0x0000, /* R124 */
+ 0x0000, /* R125 */
+ 0x0000, /* R126 */
+ 0x0000, /* R127 */
+ 0x0000, /* R128 */
+ 0x0000, /* R129 */
+ 0x0000, /* R130 */
+ 0x0000, /* R131 */
+ 0x0000, /* R132 */
+ 0x0000, /* R133 */
+ 0x0000, /* R134 */
+ 0x0000, /* R135 */
+ 0x0000, /* R136 */
+ 0x0000, /* R137 */
+ 0x0000, /* R138 */
+ 0x0000, /* R139 */
+ 0x0000, /* R140 */
+ 0x0000, /* R141 */
+ 0x0000, /* R142 */
+ 0x0000, /* R143 */
+ 0x0000, /* R144 */
+ 0x0000, /* R145 */
+ 0x0000, /* R146 */
+ 0x0000, /* R147 */
+ 0x0000, /* R148 */
+ 0x0000, /* R149 */
+ 0x0000, /* R150 */
+ 0x0000, /* R151 */
+ 0x0000, /* R152 */
+ 0x0000, /* R153 */
+ 0x0000, /* R154 */
+ 0x0000, /* R155 */
+ 0x0000, /* R156 */
+ 0x0000, /* R157 */
+ 0x0000, /* R158 */
+ 0x0000, /* R159 */
+ 0x0000, /* R160 */
+ 0x0000, /* R161 */
+ 0x0000, /* R162 */
+ 0x0000, /* R163 */
+ 0x0000, /* R164 */
+ 0x0000, /* R165 */
+ 0x0000, /* R166 */
+ 0x0000, /* R167 */
+ 0x0000, /* R168 */
+ 0x0000, /* R169 */
+ 0x0000, /* R170 */
+ 0x0000, /* R171 */
+ 0x0000, /* R172 */
+ 0x0000, /* R173 */
+ 0x0000, /* R174 */
+ 0x0000, /* R175 */
+ 0x0000, /* R176 */
+ 0x0000, /* R177 */
+ 0x0000, /* R178 */
+ 0x0000, /* R179 */
+ 0x0000, /* R180 */
+ 0x0000, /* R181 */
+ 0x0000, /* R182 */
+ 0x0000, /* R183 */
+ 0x0000, /* R184 */
+ 0x0000, /* R185 */
+ 0x0000, /* R186 */
+ 0x0000, /* R187 */
+ 0x0000, /* R188 */
+ 0x0000, /* R189 */
+ 0x0000, /* R190 */
+ 0x0000, /* R191 */
+ 0x0000, /* R192 */
+ 0x0000, /* R193 */
+ 0x0000, /* R194 */
+ 0x0000, /* R195 */
+ 0x0000, /* R196 */
+ 0x0000, /* R197 */
+ 0x0000, /* R198 */
+ 0x0000, /* R199 */
+ 0x0000, /* R200 */
+ 0x0000, /* R201 */
+ 0x0000, /* R202 */
+ 0x0000, /* R203 */
+ 0x0000, /* R204 */
+ 0x0000, /* R205 */
+ 0x0000, /* R206 */
+ 0x0000, /* R207 */
+ 0x0000, /* R208 */
+ 0x0000, /* R209 */
+ 0x0000, /* R210 */
+ 0x0000, /* R211 */
+ 0x0000, /* R212 */
+ 0x0000, /* R213 */
+ 0x0000, /* R214 */
+ 0x0000, /* R215 */
+ 0x0000, /* R216 */
+ 0x0000, /* R217 */
+ 0x0000, /* R218 */
+ 0x0000, /* R219 */
+ 0x0000, /* R220 */
+ 0x0000, /* R221 */
+ 0x0000, /* R222 */
+ 0x0000, /* R223 */
+ 0x0000, /* R224 */
+ 0x0000, /* R225 */
+ 0x0000, /* R226 */
+ 0x0000, /* R227 */
+ 0x0000, /* R228 */
+ 0x0000, /* R229 */
+ 0x0000, /* R230 */
+ 0x0000, /* R231 */
+ 0x0000, /* R232 */
+ 0x0000, /* R233 */
+ 0x0000, /* R234 */
+ 0x0000, /* R235 */
+ 0x0000, /* R236 */
+ 0x0000, /* R237 */
+ 0x0000, /* R238 */
+ 0x0000, /* R239 */
+ 0x0000, /* R240 */
+ 0x0000, /* R241 */
+ 0x0000, /* R242 */
+ 0x0000, /* R243 */
+ 0x0000, /* R244 */
+ 0x0000, /* R245 */
+ 0x0000, /* R246 */
+ 0x0000, /* R247 */
+ 0x0000, /* R248 */
+ 0x0000, /* R249 */
+ 0x0000, /* R250 */
+ 0x0000, /* R251 */
+ 0x0000, /* R252 */
+ 0x0000, /* R253 */
+ 0x0000, /* R254 */
+ 0x0000, /* R255 */
+ 0x0003, /* R256 - Chip Revision */
+ 0x8004, /* R257 - Control Interface */
+ 0x0000, /* R258 */
+ 0x0000, /* R259 */
+ 0x0000, /* R260 */
+ 0x0000, /* R261 */
+ 0x0000, /* R262 */
+ 0x0000, /* R263 */
+ 0x0000, /* R264 */
+ 0x0000, /* R265 */
+ 0x0000, /* R266 */
+ 0x0000, /* R267 */
+ 0x0000, /* R268 */
+ 0x0000, /* R269 */
+ 0x0000, /* R270 */
+ 0x0000, /* R271 */
+ 0x0000, /* R272 - Write Sequencer Ctrl (1) */
+ 0x0000, /* R273 - Write Sequencer Ctrl (2) */
+ 0x0000, /* R274 */
+ 0x0000, /* R275 */
+ 0x0000, /* R276 */
+ 0x0000, /* R277 */
+ 0x0000, /* R278 */
+ 0x0000, /* R279 */
+ 0x0000, /* R280 */
+ 0x0000, /* R281 */
+ 0x0000, /* R282 */
+ 0x0000, /* R283 */
+ 0x0000, /* R284 */
+ 0x0000, /* R285 */
+ 0x0000, /* R286 */
+ 0x0000, /* R287 */
+ 0x0000, /* R288 */
+ 0x0000, /* R289 */
+ 0x0000, /* R290 */
+ 0x0000, /* R291 */
+ 0x0000, /* R292 */
+ 0x0000, /* R293 */
+ 0x0000, /* R294 */
+ 0x0000, /* R295 */
+ 0x0000, /* R296 */
+ 0x0000, /* R297 */
+ 0x0000, /* R298 */
+ 0x0000, /* R299 */
+ 0x0000, /* R300 */
+ 0x0000, /* R301 */
+ 0x0000, /* R302 */
+ 0x0000, /* R303 */
+ 0x0000, /* R304 */
+ 0x0000, /* R305 */
+ 0x0000, /* R306 */
+ 0x0000, /* R307 */
+ 0x0000, /* R308 */
+ 0x0000, /* R309 */
+ 0x0000, /* R310 */
+ 0x0000, /* R311 */
+ 0x0000, /* R312 */
+ 0x0000, /* R313 */
+ 0x0000, /* R314 */
+ 0x0000, /* R315 */
+ 0x0000, /* R316 */
+ 0x0000, /* R317 */
+ 0x0000, /* R318 */
+ 0x0000, /* R319 */
+ 0x0000, /* R320 */
+ 0x0000, /* R321 */
+ 0x0000, /* R322 */
+ 0x0000, /* R323 */
+ 0x0000, /* R324 */
+ 0x0000, /* R325 */
+ 0x0000, /* R326 */
+ 0x0000, /* R327 */
+ 0x0000, /* R328 */
+ 0x0000, /* R329 */
+ 0x0000, /* R330 */
+ 0x0000, /* R331 */
+ 0x0000, /* R332 */
+ 0x0000, /* R333 */
+ 0x0000, /* R334 */
+ 0x0000, /* R335 */
+ 0x0000, /* R336 */
+ 0x0000, /* R337 */
+ 0x0000, /* R338 */
+ 0x0000, /* R339 */
+ 0x0000, /* R340 */
+ 0x0000, /* R341 */
+ 0x0000, /* R342 */
+ 0x0000, /* R343 */
+ 0x0000, /* R344 */
+ 0x0000, /* R345 */
+ 0x0000, /* R346 */
+ 0x0000, /* R347 */
+ 0x0000, /* R348 */
+ 0x0000, /* R349 */
+ 0x0000, /* R350 */
+ 0x0000, /* R351 */
+ 0x0000, /* R352 */
+ 0x0000, /* R353 */
+ 0x0000, /* R354 */
+ 0x0000, /* R355 */
+ 0x0000, /* R356 */
+ 0x0000, /* R357 */
+ 0x0000, /* R358 */
+ 0x0000, /* R359 */
+ 0x0000, /* R360 */
+ 0x0000, /* R361 */
+ 0x0000, /* R362 */
+ 0x0000, /* R363 */
+ 0x0000, /* R364 */
+ 0x0000, /* R365 */
+ 0x0000, /* R366 */
+ 0x0000, /* R367 */
+ 0x0000, /* R368 */
+ 0x0000, /* R369 */
+ 0x0000, /* R370 */
+ 0x0000, /* R371 */
+ 0x0000, /* R372 */
+ 0x0000, /* R373 */
+ 0x0000, /* R374 */
+ 0x0000, /* R375 */
+ 0x0000, /* R376 */
+ 0x0000, /* R377 */
+ 0x0000, /* R378 */
+ 0x0000, /* R379 */
+ 0x0000, /* R380 */
+ 0x0000, /* R381 */
+ 0x0000, /* R382 */
+ 0x0000, /* R383 */
+ 0x0000, /* R384 */
+ 0x0000, /* R385 */
+ 0x0000, /* R386 */
+ 0x0000, /* R387 */
+ 0x0000, /* R388 */
+ 0x0000, /* R389 */
+ 0x0000, /* R390 */
+ 0x0000, /* R391 */
+ 0x0000, /* R392 */
+ 0x0000, /* R393 */
+ 0x0000, /* R394 */
+ 0x0000, /* R395 */
+ 0x0000, /* R396 */
+ 0x0000, /* R397 */
+ 0x0000, /* R398 */
+ 0x0000, /* R399 */
+ 0x0000, /* R400 */
+ 0x0000, /* R401 */
+ 0x0000, /* R402 */
+ 0x0000, /* R403 */
+ 0x0000, /* R404 */
+ 0x0000, /* R405 */
+ 0x0000, /* R406 */
+ 0x0000, /* R407 */
+ 0x0000, /* R408 */
+ 0x0000, /* R409 */
+ 0x0000, /* R410 */
+ 0x0000, /* R411 */
+ 0x0000, /* R412 */
+ 0x0000, /* R413 */
+ 0x0000, /* R414 */
+ 0x0000, /* R415 */
+ 0x0000, /* R416 */
+ 0x0000, /* R417 */
+ 0x0000, /* R418 */
+ 0x0000, /* R419 */
+ 0x0000, /* R420 */
+ 0x0000, /* R421 */
+ 0x0000, /* R422 */
+ 0x0000, /* R423 */
+ 0x0000, /* R424 */
+ 0x0000, /* R425 */
+ 0x0000, /* R426 */
+ 0x0000, /* R427 */
+ 0x0000, /* R428 */
+ 0x0000, /* R429 */
+ 0x0000, /* R430 */
+ 0x0000, /* R431 */
+ 0x0000, /* R432 */
+ 0x0000, /* R433 */
+ 0x0000, /* R434 */
+ 0x0000, /* R435 */
+ 0x0000, /* R436 */
+ 0x0000, /* R437 */
+ 0x0000, /* R438 */
+ 0x0000, /* R439 */
+ 0x0000, /* R440 */
+ 0x0000, /* R441 */
+ 0x0000, /* R442 */
+ 0x0000, /* R443 */
+ 0x0000, /* R444 */
+ 0x0000, /* R445 */
+ 0x0000, /* R446 */
+ 0x0000, /* R447 */
+ 0x0000, /* R448 */
+ 0x0000, /* R449 */
+ 0x0000, /* R450 */
+ 0x0000, /* R451 */
+ 0x0000, /* R452 */
+ 0x0000, /* R453 */
+ 0x0000, /* R454 */
+ 0x0000, /* R455 */
+ 0x0000, /* R456 */
+ 0x0000, /* R457 */
+ 0x0000, /* R458 */
+ 0x0000, /* R459 */
+ 0x0000, /* R460 */
+ 0x0000, /* R461 */
+ 0x0000, /* R462 */
+ 0x0000, /* R463 */
+ 0x0000, /* R464 */
+ 0x0000, /* R465 */
+ 0x0000, /* R466 */
+ 0x0000, /* R467 */
+ 0x0000, /* R468 */
+ 0x0000, /* R469 */
+ 0x0000, /* R470 */
+ 0x0000, /* R471 */
+ 0x0000, /* R472 */
+ 0x0000, /* R473 */
+ 0x0000, /* R474 */
+ 0x0000, /* R475 */
+ 0x0000, /* R476 */
+ 0x0000, /* R477 */
+ 0x0000, /* R478 */
+ 0x0000, /* R479 */
+ 0x0000, /* R480 */
+ 0x0000, /* R481 */
+ 0x0000, /* R482 */
+ 0x0000, /* R483 */
+ 0x0000, /* R484 */
+ 0x0000, /* R485 */
+ 0x0000, /* R486 */
+ 0x0000, /* R487 */
+ 0x0000, /* R488 */
+ 0x0000, /* R489 */
+ 0x0000, /* R490 */
+ 0x0000, /* R491 */
+ 0x0000, /* R492 */
+ 0x0000, /* R493 */
+ 0x0000, /* R494 */
+ 0x0000, /* R495 */
+ 0x0000, /* R496 */
+ 0x0000, /* R497 */
+ 0x0000, /* R498 */
+ 0x0000, /* R499 */
+ 0x0000, /* R500 */
+ 0x0000, /* R501 */
+ 0x0000, /* R502 */
+ 0x0000, /* R503 */
+ 0x0000, /* R504 */
+ 0x0000, /* R505 */
+ 0x0000, /* R506 */
+ 0x0000, /* R507 */
+ 0x0000, /* R508 */
+ 0x0000, /* R509 */
+ 0x0000, /* R510 */
+ 0x0000, /* R511 */
+ 0x0000, /* R512 - AIF1 Clocking (1) */
+ 0x0000, /* R513 - AIF1 Clocking (2) */
+ 0x0000, /* R514 */
+ 0x0000, /* R515 */
+ 0x0000, /* R516 - AIF2 Clocking (1) */
+ 0x0000, /* R517 - AIF2 Clocking (2) */
+ 0x0000, /* R518 */
+ 0x0000, /* R519 */
+ 0x0000, /* R520 - Clocking (1) */
+ 0x0000, /* R521 - Clocking (2) */
+ 0x0000, /* R522 */
+ 0x0000, /* R523 */
+ 0x0000, /* R524 */
+ 0x0000, /* R525 */
+ 0x0000, /* R526 */
+ 0x0000, /* R527 */
+ 0x0083, /* R528 - AIF1 Rate */
+ 0x0083, /* R529 - AIF2 Rate */
+ 0x0000, /* R530 - Rate Status */
+ 0x0000, /* R531 */
+ 0x0000, /* R532 */
+ 0x0000, /* R533 */
+ 0x0000, /* R534 */
+ 0x0000, /* R535 */
+ 0x0000, /* R536 */
+ 0x0000, /* R537 */
+ 0x0000, /* R538 */
+ 0x0000, /* R539 */
+ 0x0000, /* R540 */
+ 0x0000, /* R541 */
+ 0x0000, /* R542 */
+ 0x0000, /* R543 */
+ 0x0000, /* R544 - FLL1 Control (1) */
+ 0x0000, /* R545 - FLL1 Control (2) */
+ 0x0000, /* R546 - FLL1 Control (3) */
+ 0x0000, /* R547 - FLL1 Control (4) */
+ 0x0C80, /* R548 - FLL1 Control (5) */
+ 0x0000, /* R549 */
+ 0x0000, /* R550 */
+ 0x0000, /* R551 */
+ 0x0000, /* R552 */
+ 0x0000, /* R553 */
+ 0x0000, /* R554 */
+ 0x0000, /* R555 */
+ 0x0000, /* R556 */
+ 0x0000, /* R557 */
+ 0x0000, /* R558 */
+ 0x0000, /* R559 */
+ 0x0000, /* R560 */
+ 0x0000, /* R561 */
+ 0x0000, /* R562 */
+ 0x0000, /* R563 */
+ 0x0000, /* R564 */
+ 0x0000, /* R565 */
+ 0x0000, /* R566 */
+ 0x0000, /* R567 */
+ 0x0000, /* R568 */
+ 0x0000, /* R569 */
+ 0x0000, /* R570 */
+ 0x0000, /* R571 */
+ 0x0000, /* R572 */
+ 0x0000, /* R573 */
+ 0x0000, /* R574 */
+ 0x0000, /* R575 */
+ 0x0000, /* R576 - FLL2 Control (1) */
+ 0x0000, /* R577 - FLL2 Control (2) */
+ 0x0000, /* R578 - FLL2 Control (3) */
+ 0x0000, /* R579 - FLL2 Control (4) */
+ 0x0C80, /* R580 - FLL2 Control (5) */
+ 0x0000, /* R581 */
+ 0x0000, /* R582 */
+ 0x0000, /* R583 */
+ 0x0000, /* R584 */
+ 0x0000, /* R585 */
+ 0x0000, /* R586 */
+ 0x0000, /* R587 */
+ 0x0000, /* R588 */
+ 0x0000, /* R589 */
+ 0x0000, /* R590 */
+ 0x0000, /* R591 */
+ 0x0000, /* R592 */
+ 0x0000, /* R593 */
+ 0x0000, /* R594 */
+ 0x0000, /* R595 */
+ 0x0000, /* R596 */
+ 0x0000, /* R597 */
+ 0x0000, /* R598 */
+ 0x0000, /* R599 */
+ 0x0000, /* R600 */
+ 0x0000, /* R601 */
+ 0x0000, /* R602 */
+ 0x0000, /* R603 */
+ 0x0000, /* R604 */
+ 0x0000, /* R605 */
+ 0x0000, /* R606 */
+ 0x0000, /* R607 */
+ 0x0000, /* R608 */
+ 0x0000, /* R609 */
+ 0x0000, /* R610 */
+ 0x0000, /* R611 */
+ 0x0000, /* R612 */
+ 0x0000, /* R613 */
+ 0x0000, /* R614 */
+ 0x0000, /* R615 */
+ 0x0000, /* R616 */
+ 0x0000, /* R617 */
+ 0x0000, /* R618 */
+ 0x0000, /* R619 */
+ 0x0000, /* R620 */
+ 0x0000, /* R621 */
+ 0x0000, /* R622 */
+ 0x0000, /* R623 */
+ 0x0000, /* R624 */
+ 0x0000, /* R625 */
+ 0x0000, /* R626 */
+ 0x0000, /* R627 */
+ 0x0000, /* R628 */
+ 0x0000, /* R629 */
+ 0x0000, /* R630 */
+ 0x0000, /* R631 */
+ 0x0000, /* R632 */
+ 0x0000, /* R633 */
+ 0x0000, /* R634 */
+ 0x0000, /* R635 */
+ 0x0000, /* R636 */
+ 0x0000, /* R637 */
+ 0x0000, /* R638 */
+ 0x0000, /* R639 */
+ 0x0000, /* R640 */
+ 0x0000, /* R641 */
+ 0x0000, /* R642 */
+ 0x0000, /* R643 */
+ 0x0000, /* R644 */
+ 0x0000, /* R645 */
+ 0x0000, /* R646 */
+ 0x0000, /* R647 */
+ 0x0000, /* R648 */
+ 0x0000, /* R649 */
+ 0x0000, /* R650 */
+ 0x0000, /* R651 */
+ 0x0000, /* R652 */
+ 0x0000, /* R653 */
+ 0x0000, /* R654 */
+ 0x0000, /* R655 */
+ 0x0000, /* R656 */
+ 0x0000, /* R657 */
+ 0x0000, /* R658 */
+ 0x0000, /* R659 */
+ 0x0000, /* R660 */
+ 0x0000, /* R661 */
+ 0x0000, /* R662 */
+ 0x0000, /* R663 */
+ 0x0000, /* R664 */
+ 0x0000, /* R665 */
+ 0x0000, /* R666 */
+ 0x0000, /* R667 */
+ 0x0000, /* R668 */
+ 0x0000, /* R669 */
+ 0x0000, /* R670 */
+ 0x0000, /* R671 */
+ 0x0000, /* R672 */
+ 0x0000, /* R673 */
+ 0x0000, /* R674 */
+ 0x0000, /* R675 */
+ 0x0000, /* R676 */
+ 0x0000, /* R677 */
+ 0x0000, /* R678 */
+ 0x0000, /* R679 */
+ 0x0000, /* R680 */
+ 0x0000, /* R681 */
+ 0x0000, /* R682 */
+ 0x0000, /* R683 */
+ 0x0000, /* R684 */
+ 0x0000, /* R685 */
+ 0x0000, /* R686 */
+ 0x0000, /* R687 */
+ 0x0000, /* R688 */
+ 0x0000, /* R689 */
+ 0x0000, /* R690 */
+ 0x0000, /* R691 */
+ 0x0000, /* R692 */
+ 0x0000, /* R693 */
+ 0x0000, /* R694 */
+ 0x0000, /* R695 */
+ 0x0000, /* R696 */
+ 0x0000, /* R697 */
+ 0x0000, /* R698 */
+ 0x0000, /* R699 */
+ 0x0000, /* R700 */
+ 0x0000, /* R701 */
+ 0x0000, /* R702 */
+ 0x0000, /* R703 */
+ 0x0000, /* R704 */
+ 0x0000, /* R705 */
+ 0x0000, /* R706 */
+ 0x0000, /* R707 */
+ 0x0000, /* R708 */
+ 0x0000, /* R709 */
+ 0x0000, /* R710 */
+ 0x0000, /* R711 */
+ 0x0000, /* R712 */
+ 0x0000, /* R713 */
+ 0x0000, /* R714 */
+ 0x0000, /* R715 */
+ 0x0000, /* R716 */
+ 0x0000, /* R717 */
+ 0x0000, /* R718 */
+ 0x0000, /* R719 */
+ 0x0000, /* R720 */
+ 0x0000, /* R721 */
+ 0x0000, /* R722 */
+ 0x0000, /* R723 */
+ 0x0000, /* R724 */
+ 0x0000, /* R725 */
+ 0x0000, /* R726 */
+ 0x0000, /* R727 */
+ 0x0000, /* R728 */
+ 0x0000, /* R729 */
+ 0x0000, /* R730 */
+ 0x0000, /* R731 */
+ 0x0000, /* R732 */
+ 0x0000, /* R733 */
+ 0x0000, /* R734 */
+ 0x0000, /* R735 */
+ 0x0000, /* R736 */
+ 0x0000, /* R737 */
+ 0x0000, /* R738 */
+ 0x0000, /* R739 */
+ 0x0000, /* R740 */
+ 0x0000, /* R741 */
+ 0x0000, /* R742 */
+ 0x0000, /* R743 */
+ 0x0000, /* R744 */
+ 0x0000, /* R745 */
+ 0x0000, /* R746 */
+ 0x0000, /* R747 */
+ 0x0000, /* R748 */
+ 0x0000, /* R749 */
+ 0x0000, /* R750 */
+ 0x0000, /* R751 */
+ 0x0000, /* R752 */
+ 0x0000, /* R753 */
+ 0x0000, /* R754 */
+ 0x0000, /* R755 */
+ 0x0000, /* R756 */
+ 0x0000, /* R757 */
+ 0x0000, /* R758 */
+ 0x0000, /* R759 */
+ 0x0000, /* R760 */
+ 0x0000, /* R761 */
+ 0x0000, /* R762 */
+ 0x0000, /* R763 */
+ 0x0000, /* R764 */
+ 0x0000, /* R765 */
+ 0x0000, /* R766 */
+ 0x0000, /* R767 */
+ 0x4050, /* R768 - AIF1 Control (1) */
+ 0x4000, /* R769 - AIF1 Control (2) */
+ 0x0000, /* R770 - AIF1 Master/Slave */
+ 0x0040, /* R771 - AIF1 BCLK */
+ 0x0040, /* R772 - AIF1ADC LRCLK */
+ 0x0040, /* R773 - AIF1DAC LRCLK */
+ 0x0004, /* R774 - AIF1DAC Data */
+ 0x0100, /* R775 - AIF1ADC Data */
+ 0x0000, /* R776 */
+ 0x0000, /* R777 */
+ 0x0000, /* R778 */
+ 0x0000, /* R779 */
+ 0x0000, /* R780 */
+ 0x0000, /* R781 */
+ 0x0000, /* R782 */
+ 0x0000, /* R783 */
+ 0x4050, /* R784 - AIF2 Control (1) */
+ 0x4000, /* R785 - AIF2 Control (2) */
+ 0x0000, /* R786 - AIF2 Master/Slave */
+ 0x0040, /* R787 - AIF2 BCLK */
+ 0x0040, /* R788 - AIF2ADC LRCLK */
+ 0x0040, /* R789 - AIF2DAC LRCLK */
+ 0x0000, /* R790 - AIF2DAC Data */
+ 0x0000, /* R791 - AIF2ADC Data */
+ 0x0000, /* R792 */
+ 0x0000, /* R793 */
+ 0x0000, /* R794 */
+ 0x0000, /* R795 */
+ 0x0000, /* R796 */
+ 0x0000, /* R797 */
+ 0x0000, /* R798 */
+ 0x0000, /* R799 */
+ 0x0000, /* R800 */
+ 0x0000, /* R801 */
+ 0x0000, /* R802 */
+ 0x0000, /* R803 */
+ 0x0000, /* R804 */
+ 0x0000, /* R805 */
+ 0x0000, /* R806 */
+ 0x0000, /* R807 */
+ 0x0000, /* R808 */
+ 0x0000, /* R809 */
+ 0x0000, /* R810 */
+ 0x0000, /* R811 */
+ 0x0000, /* R812 */
+ 0x0000, /* R813 */
+ 0x0000, /* R814 */
+ 0x0000, /* R815 */
+ 0x0000, /* R816 */
+ 0x0000, /* R817 */
+ 0x0000, /* R818 */
+ 0x0000, /* R819 */
+ 0x0000, /* R820 */
+ 0x0000, /* R821 */
+ 0x0000, /* R822 */
+ 0x0000, /* R823 */
+ 0x0000, /* R824 */
+ 0x0000, /* R825 */
+ 0x0000, /* R826 */
+ 0x0000, /* R827 */
+ 0x0000, /* R828 */
+ 0x0000, /* R829 */
+ 0x0000, /* R830 */
+ 0x0000, /* R831 */
+ 0x0000, /* R832 */
+ 0x0000, /* R833 */
+ 0x0000, /* R834 */
+ 0x0000, /* R835 */
+ 0x0000, /* R836 */
+ 0x0000, /* R837 */
+ 0x0000, /* R838 */
+ 0x0000, /* R839 */
+ 0x0000, /* R840 */
+ 0x0000, /* R841 */
+ 0x0000, /* R842 */
+ 0x0000, /* R843 */
+ 0x0000, /* R844 */
+ 0x0000, /* R845 */
+ 0x0000, /* R846 */
+ 0x0000, /* R847 */
+ 0x0000, /* R848 */
+ 0x0000, /* R849 */
+ 0x0000, /* R850 */
+ 0x0000, /* R851 */
+ 0x0000, /* R852 */
+ 0x0000, /* R853 */
+ 0x0000, /* R854 */
+ 0x0000, /* R855 */
+ 0x0000, /* R856 */
+ 0x0000, /* R857 */
+ 0x0000, /* R858 */
+ 0x0000, /* R859 */
+ 0x0000, /* R860 */
+ 0x0000, /* R861 */
+ 0x0000, /* R862 */
+ 0x0000, /* R863 */
+ 0x0000, /* R864 */
+ 0x0000, /* R865 */
+ 0x0000, /* R866 */
+ 0x0000, /* R867 */
+ 0x0000, /* R868 */
+ 0x0000, /* R869 */
+ 0x0000, /* R870 */
+ 0x0000, /* R871 */
+ 0x0000, /* R872 */
+ 0x0000, /* R873 */
+ 0x0000, /* R874 */
+ 0x0000, /* R875 */
+ 0x0000, /* R876 */
+ 0x0000, /* R877 */
+ 0x0000, /* R878 */
+ 0x0000, /* R879 */
+ 0x0000, /* R880 */
+ 0x0000, /* R881 */
+ 0x0000, /* R882 */
+ 0x0000, /* R883 */
+ 0x0000, /* R884 */
+ 0x0000, /* R885 */
+ 0x0000, /* R886 */
+ 0x0000, /* R887 */
+ 0x0000, /* R888 */
+ 0x0000, /* R889 */
+ 0x0000, /* R890 */
+ 0x0000, /* R891 */
+ 0x0000, /* R892 */
+ 0x0000, /* R893 */
+ 0x0000, /* R894 */
+ 0x0000, /* R895 */
+ 0x0000, /* R896 */
+ 0x0000, /* R897 */
+ 0x0000, /* R898 */
+ 0x0000, /* R899 */
+ 0x0000, /* R900 */
+ 0x0000, /* R901 */
+ 0x0000, /* R902 */
+ 0x0000, /* R903 */
+ 0x0000, /* R904 */
+ 0x0000, /* R905 */
+ 0x0000, /* R906 */
+ 0x0000, /* R907 */
+ 0x0000, /* R908 */
+ 0x0000, /* R909 */
+ 0x0000, /* R910 */
+ 0x0000, /* R911 */
+ 0x0000, /* R912 */
+ 0x0000, /* R913 */
+ 0x0000, /* R914 */
+ 0x0000, /* R915 */
+ 0x0000, /* R916 */
+ 0x0000, /* R917 */
+ 0x0000, /* R918 */
+ 0x0000, /* R919 */
+ 0x0000, /* R920 */
+ 0x0000, /* R921 */
+ 0x0000, /* R922 */
+ 0x0000, /* R923 */
+ 0x0000, /* R924 */
+ 0x0000, /* R925 */
+ 0x0000, /* R926 */
+ 0x0000, /* R927 */
+ 0x0000, /* R928 */
+ 0x0000, /* R929 */
+ 0x0000, /* R930 */
+ 0x0000, /* R931 */
+ 0x0000, /* R932 */
+ 0x0000, /* R933 */
+ 0x0000, /* R934 */
+ 0x0000, /* R935 */
+ 0x0000, /* R936 */
+ 0x0000, /* R937 */
+ 0x0000, /* R938 */
+ 0x0000, /* R939 */
+ 0x0000, /* R940 */
+ 0x0000, /* R941 */
+ 0x0000, /* R942 */
+ 0x0000, /* R943 */
+ 0x0000, /* R944 */
+ 0x0000, /* R945 */
+ 0x0000, /* R946 */
+ 0x0000, /* R947 */
+ 0x0000, /* R948 */
+ 0x0000, /* R949 */
+ 0x0000, /* R950 */
+ 0x0000, /* R951 */
+ 0x0000, /* R952 */
+ 0x0000, /* R953 */
+ 0x0000, /* R954 */
+ 0x0000, /* R955 */
+ 0x0000, /* R956 */
+ 0x0000, /* R957 */
+ 0x0000, /* R958 */
+ 0x0000, /* R959 */
+ 0x0000, /* R960 */
+ 0x0000, /* R961 */
+ 0x0000, /* R962 */
+ 0x0000, /* R963 */
+ 0x0000, /* R964 */
+ 0x0000, /* R965 */
+ 0x0000, /* R966 */
+ 0x0000, /* R967 */
+ 0x0000, /* R968 */
+ 0x0000, /* R969 */
+ 0x0000, /* R970 */
+ 0x0000, /* R971 */
+ 0x0000, /* R972 */
+ 0x0000, /* R973 */
+ 0x0000, /* R974 */
+ 0x0000, /* R975 */
+ 0x0000, /* R976 */
+ 0x0000, /* R977 */
+ 0x0000, /* R978 */
+ 0x0000, /* R979 */
+ 0x0000, /* R980 */
+ 0x0000, /* R981 */
+ 0x0000, /* R982 */
+ 0x0000, /* R983 */
+ 0x0000, /* R984 */
+ 0x0000, /* R985 */
+ 0x0000, /* R986 */
+ 0x0000, /* R987 */
+ 0x0000, /* R988 */
+ 0x0000, /* R989 */
+ 0x0000, /* R990 */
+ 0x0000, /* R991 */
+ 0x0000, /* R992 */
+ 0x0000, /* R993 */
+ 0x0000, /* R994 */
+ 0x0000, /* R995 */
+ 0x0000, /* R996 */
+ 0x0000, /* R997 */
+ 0x0000, /* R998 */
+ 0x0000, /* R999 */
+ 0x0000, /* R1000 */
+ 0x0000, /* R1001 */
+ 0x0000, /* R1002 */
+ 0x0000, /* R1003 */
+ 0x0000, /* R1004 */
+ 0x0000, /* R1005 */
+ 0x0000, /* R1006 */
+ 0x0000, /* R1007 */
+ 0x0000, /* R1008 */
+ 0x0000, /* R1009 */
+ 0x0000, /* R1010 */
+ 0x0000, /* R1011 */
+ 0x0000, /* R1012 */
+ 0x0000, /* R1013 */
+ 0x0000, /* R1014 */
+ 0x0000, /* R1015 */
+ 0x0000, /* R1016 */
+ 0x0000, /* R1017 */
+ 0x0000, /* R1018 */
+ 0x0000, /* R1019 */
+ 0x0000, /* R1020 */
+ 0x0000, /* R1021 */
+ 0x0000, /* R1022 */
+ 0x0000, /* R1023 */
+ 0x00C0, /* R1024 - AIF1 ADC1 Left Volume */
+ 0x00C0, /* R1025 - AIF1 ADC1 Right Volume */
+ 0x00C0, /* R1026 - AIF1 DAC1 Left Volume */
+ 0x00C0, /* R1027 - AIF1 DAC1 Right Volume */
+ 0x00C0, /* R1028 - AIF1 ADC2 Left Volume */
+ 0x00C0, /* R1029 - AIF1 ADC2 Right Volume */
+ 0x00C0, /* R1030 - AIF1 DAC2 Left Volume */
+ 0x00C0, /* R1031 - AIF1 DAC2 Right Volume */
+ 0x0000, /* R1032 */
+ 0x0000, /* R1033 */
+ 0x0000, /* R1034 */
+ 0x0000, /* R1035 */
+ 0x0000, /* R1036 */
+ 0x0000, /* R1037 */
+ 0x0000, /* R1038 */
+ 0x0000, /* R1039 */
+ 0x0000, /* R1040 - AIF1 ADC1 Filters */
+ 0x0000, /* R1041 - AIF1 ADC2 Filters */
+ 0x0000, /* R1042 */
+ 0x0000, /* R1043 */
+ 0x0000, /* R1044 */
+ 0x0000, /* R1045 */
+ 0x0000, /* R1046 */
+ 0x0000, /* R1047 */
+ 0x0000, /* R1048 */
+ 0x0000, /* R1049 */
+ 0x0000, /* R1050 */
+ 0x0000, /* R1051 */
+ 0x0000, /* R1052 */
+ 0x0000, /* R1053 */
+ 0x0000, /* R1054 */
+ 0x0000, /* R1055 */
+ 0x0200, /* R1056 - AIF1 DAC1 Filters (1) */
+ 0x0010, /* R1057 - AIF1 DAC1 Filters (2) */
+ 0x0200, /* R1058 - AIF1 DAC2 Filters (1) */
+ 0x0010, /* R1059 - AIF1 DAC2 Filters (2) */
+ 0x0000, /* R1060 */
+ 0x0000, /* R1061 */
+ 0x0000, /* R1062 */
+ 0x0000, /* R1063 */
+ 0x0000, /* R1064 */
+ 0x0000, /* R1065 */
+ 0x0000, /* R1066 */
+ 0x0000, /* R1067 */
+ 0x0000, /* R1068 */
+ 0x0000, /* R1069 */
+ 0x0000, /* R1070 */
+ 0x0000, /* R1071 */
+ 0x0000, /* R1072 */
+ 0x0000, /* R1073 */
+ 0x0000, /* R1074 */
+ 0x0000, /* R1075 */
+ 0x0000, /* R1076 */
+ 0x0000, /* R1077 */
+ 0x0000, /* R1078 */
+ 0x0000, /* R1079 */
+ 0x0000, /* R1080 */
+ 0x0000, /* R1081 */
+ 0x0000, /* R1082 */
+ 0x0000, /* R1083 */
+ 0x0000, /* R1084 */
+ 0x0000, /* R1085 */
+ 0x0000, /* R1086 */
+ 0x0000, /* R1087 */
+ 0x0098, /* R1088 - AIF1 DRC1 (1) */
+ 0x0845, /* R1089 - AIF1 DRC1 (2) */
+ 0x0000, /* R1090 - AIF1 DRC1 (3) */
+ 0x0000, /* R1091 - AIF1 DRC1 (4) */
+ 0x0000, /* R1092 - AIF1 DRC1 (5) */
+ 0x0000, /* R1093 */
+ 0x0000, /* R1094 */
+ 0x0000, /* R1095 */
+ 0x0000, /* R1096 */
+ 0x0000, /* R1097 */
+ 0x0000, /* R1098 */
+ 0x0000, /* R1099 */
+ 0x0000, /* R1100 */
+ 0x0000, /* R1101 */
+ 0x0000, /* R1102 */
+ 0x0000, /* R1103 */
+ 0x0098, /* R1104 - AIF1 DRC2 (1) */
+ 0x0845, /* R1105 - AIF1 DRC2 (2) */
+ 0x0000, /* R1106 - AIF1 DRC2 (3) */
+ 0x0000, /* R1107 - AIF1 DRC2 (4) */
+ 0x0000, /* R1108 - AIF1 DRC2 (5) */
+ 0x0000, /* R1109 */
+ 0x0000, /* R1110 */
+ 0x0000, /* R1111 */
+ 0x0000, /* R1112 */
+ 0x0000, /* R1113 */
+ 0x0000, /* R1114 */
+ 0x0000, /* R1115 */
+ 0x0000, /* R1116 */
+ 0x0000, /* R1117 */
+ 0x0000, /* R1118 */
+ 0x0000, /* R1119 */
+ 0x0000, /* R1120 */
+ 0x0000, /* R1121 */
+ 0x0000, /* R1122 */
+ 0x0000, /* R1123 */
+ 0x0000, /* R1124 */
+ 0x0000, /* R1125 */
+ 0x0000, /* R1126 */
+ 0x0000, /* R1127 */
+ 0x0000, /* R1128 */
+ 0x0000, /* R1129 */
+ 0x0000, /* R1130 */
+ 0x0000, /* R1131 */
+ 0x0000, /* R1132 */
+ 0x0000, /* R1133 */
+ 0x0000, /* R1134 */
+ 0x0000, /* R1135 */
+ 0x0000, /* R1136 */
+ 0x0000, /* R1137 */
+ 0x0000, /* R1138 */
+ 0x0000, /* R1139 */
+ 0x0000, /* R1140 */
+ 0x0000, /* R1141 */
+ 0x0000, /* R1142 */
+ 0x0000, /* R1143 */
+ 0x0000, /* R1144 */
+ 0x0000, /* R1145 */
+ 0x0000, /* R1146 */
+ 0x0000, /* R1147 */
+ 0x0000, /* R1148 */
+ 0x0000, /* R1149 */
+ 0x0000, /* R1150 */
+ 0x0000, /* R1151 */
+ 0x6318, /* R1152 - AIF1 DAC1 EQ Gains (1) */
+ 0x6300, /* R1153 - AIF1 DAC1 EQ Gains (2) */
+ 0x0FCA, /* R1154 - AIF1 DAC1 EQ Band 1 A */
+ 0x0400, /* R1155 - AIF1 DAC1 EQ Band 1 B */
+ 0x00D8, /* R1156 - AIF1 DAC1 EQ Band 1 PG */
+ 0x1EB5, /* R1157 - AIF1 DAC1 EQ Band 2 A */
+ 0xF145, /* R1158 - AIF1 DAC1 EQ Band 2 B */
+ 0x0B75, /* R1159 - AIF1 DAC1 EQ Band 2 C */
+ 0x01C5, /* R1160 - AIF1 DAC1 EQ Band 2 PG */
+ 0x1C58, /* R1161 - AIF1 DAC1 EQ Band 3 A */
+ 0xF373, /* R1162 - AIF1 DAC1 EQ Band 3 B */
+ 0x0A54, /* R1163 - AIF1 DAC1 EQ Band 3 C */
+ 0x0558, /* R1164 - AIF1 DAC1 EQ Band 3 PG */
+ 0x168E, /* R1165 - AIF1 DAC1 EQ Band 4 A */
+ 0xF829, /* R1166 - AIF1 DAC1 EQ Band 4 B */
+ 0x07AD, /* R1167 - AIF1 DAC1 EQ Band 4 C */
+ 0x1103, /* R1168 - AIF1 DAC1 EQ Band 4 PG */
+ 0x0564, /* R1169 - AIF1 DAC1 EQ Band 5 A */
+ 0x0559, /* R1170 - AIF1 DAC1 EQ Band 5 B */
+ 0x4000, /* R1171 - AIF1 DAC1 EQ Band 5 PG */
+ 0x0000, /* R1172 */
+ 0x0000, /* R1173 */
+ 0x0000, /* R1174 */
+ 0x0000, /* R1175 */
+ 0x0000, /* R1176 */
+ 0x0000, /* R1177 */
+ 0x0000, /* R1178 */
+ 0x0000, /* R1179 */
+ 0x0000, /* R1180 */
+ 0x0000, /* R1181 */
+ 0x0000, /* R1182 */
+ 0x0000, /* R1183 */
+ 0x6318, /* R1184 - AIF1 DAC2 EQ Gains (1) */
+ 0x6300, /* R1185 - AIF1 DAC2 EQ Gains (2) */
+ 0x0FCA, /* R1186 - AIF1 DAC2 EQ Band 1 A */
+ 0x0400, /* R1187 - AIF1 DAC2 EQ Band 1 B */
+ 0x00D8, /* R1188 - AIF1 DAC2 EQ Band 1 PG */
+ 0x1EB5, /* R1189 - AIF1 DAC2 EQ Band 2 A */
+ 0xF145, /* R1190 - AIF1 DAC2 EQ Band 2 B */
+ 0x0B75, /* R1191 - AIF1 DAC2 EQ Band 2 C */
+ 0x01C5, /* R1192 - AIF1 DAC2 EQ Band 2 PG */
+ 0x1C58, /* R1193 - AIF1 DAC2 EQ Band 3 A */
+ 0xF373, /* R1194 - AIF1 DAC2 EQ Band 3 B */
+ 0x0A54, /* R1195 - AIF1 DAC2 EQ Band 3 C */
+ 0x0558, /* R1196 - AIF1 DAC2 EQ Band 3 PG */
+ 0x168E, /* R1197 - AIF1 DAC2 EQ Band 4 A */
+ 0xF829, /* R1198 - AIF1 DAC2 EQ Band 4 B */
+ 0x07AD, /* R1199 - AIF1 DAC2 EQ Band 4 C */
+ 0x1103, /* R1200 - AIF1 DAC2 EQ Band 4 PG */
+ 0x0564, /* R1201 - AIF1 DAC2 EQ Band 5 A */
+ 0x0559, /* R1202 - AIF1 DAC2 EQ Band 5 B */
+ 0x4000, /* R1203 - AIF1 DAC2 EQ Band 5 PG */
+ 0x0000, /* R1204 */
+ 0x0000, /* R1205 */
+ 0x0000, /* R1206 */
+ 0x0000, /* R1207 */
+ 0x0000, /* R1208 */
+ 0x0000, /* R1209 */
+ 0x0000, /* R1210 */
+ 0x0000, /* R1211 */
+ 0x0000, /* R1212 */
+ 0x0000, /* R1213 */
+ 0x0000, /* R1214 */
+ 0x0000, /* R1215 */
+ 0x0000, /* R1216 */
+ 0x0000, /* R1217 */
+ 0x0000, /* R1218 */
+ 0x0000, /* R1219 */
+ 0x0000, /* R1220 */
+ 0x0000, /* R1221 */
+ 0x0000, /* R1222 */
+ 0x0000, /* R1223 */
+ 0x0000, /* R1224 */
+ 0x0000, /* R1225 */
+ 0x0000, /* R1226 */
+ 0x0000, /* R1227 */
+ 0x0000, /* R1228 */
+ 0x0000, /* R1229 */
+ 0x0000, /* R1230 */
+ 0x0000, /* R1231 */
+ 0x0000, /* R1232 */
+ 0x0000, /* R1233 */
+ 0x0000, /* R1234 */
+ 0x0000, /* R1235 */
+ 0x0000, /* R1236 */
+ 0x0000, /* R1237 */
+ 0x0000, /* R1238 */
+ 0x0000, /* R1239 */
+ 0x0000, /* R1240 */
+ 0x0000, /* R1241 */
+ 0x0000, /* R1242 */
+ 0x0000, /* R1243 */
+ 0x0000, /* R1244 */
+ 0x0000, /* R1245 */
+ 0x0000, /* R1246 */
+ 0x0000, /* R1247 */
+ 0x0000, /* R1248 */
+ 0x0000, /* R1249 */
+ 0x0000, /* R1250 */
+ 0x0000, /* R1251 */
+ 0x0000, /* R1252 */
+ 0x0000, /* R1253 */
+ 0x0000, /* R1254 */
+ 0x0000, /* R1255 */
+ 0x0000, /* R1256 */
+ 0x0000, /* R1257 */
+ 0x0000, /* R1258 */
+ 0x0000, /* R1259 */
+ 0x0000, /* R1260 */
+ 0x0000, /* R1261 */
+ 0x0000, /* R1262 */
+ 0x0000, /* R1263 */
+ 0x0000, /* R1264 */
+ 0x0000, /* R1265 */
+ 0x0000, /* R1266 */
+ 0x0000, /* R1267 */
+ 0x0000, /* R1268 */
+ 0x0000, /* R1269 */
+ 0x0000, /* R1270 */
+ 0x0000, /* R1271 */
+ 0x0000, /* R1272 */
+ 0x0000, /* R1273 */
+ 0x0000, /* R1274 */
+ 0x0000, /* R1275 */
+ 0x0000, /* R1276 */
+ 0x0000, /* R1277 */
+ 0x0000, /* R1278 */
+ 0x0000, /* R1279 */
+ 0x00C0, /* R1280 - AIF2 ADC Left Volume */
+ 0x00C0, /* R1281 - AIF2 ADC Right Volume */
+ 0x00C0, /* R1282 - AIF2 DAC Left Volume */
+ 0x00C0, /* R1283 - AIF2 DAC Right Volume */
+ 0x0000, /* R1284 */
+ 0x0000, /* R1285 */
+ 0x0000, /* R1286 */
+ 0x0000, /* R1287 */
+ 0x0000, /* R1288 */
+ 0x0000, /* R1289 */
+ 0x0000, /* R1290 */
+ 0x0000, /* R1291 */
+ 0x0000, /* R1292 */
+ 0x0000, /* R1293 */
+ 0x0000, /* R1294 */
+ 0x0000, /* R1295 */
+ 0x0000, /* R1296 - AIF2 ADC Filters */
+ 0x0000, /* R1297 */
+ 0x0000, /* R1298 */
+ 0x0000, /* R1299 */
+ 0x0000, /* R1300 */
+ 0x0000, /* R1301 */
+ 0x0000, /* R1302 */
+ 0x0000, /* R1303 */
+ 0x0000, /* R1304 */
+ 0x0000, /* R1305 */
+ 0x0000, /* R1306 */
+ 0x0000, /* R1307 */
+ 0x0000, /* R1308 */
+ 0x0000, /* R1309 */
+ 0x0000, /* R1310 */
+ 0x0000, /* R1311 */
+ 0x0200, /* R1312 - AIF2 DAC Filters (1) */
+ 0x0010, /* R1313 - AIF2 DAC Filters (2) */
+ 0x0000, /* R1314 */
+ 0x0000, /* R1315 */
+ 0x0000, /* R1316 */
+ 0x0000, /* R1317 */
+ 0x0000, /* R1318 */
+ 0x0000, /* R1319 */
+ 0x0000, /* R1320 */
+ 0x0000, /* R1321 */
+ 0x0000, /* R1322 */
+ 0x0000, /* R1323 */
+ 0x0000, /* R1324 */
+ 0x0000, /* R1325 */
+ 0x0000, /* R1326 */
+ 0x0000, /* R1327 */
+ 0x0000, /* R1328 */
+ 0x0000, /* R1329 */
+ 0x0000, /* R1330 */
+ 0x0000, /* R1331 */
+ 0x0000, /* R1332 */
+ 0x0000, /* R1333 */
+ 0x0000, /* R1334 */
+ 0x0000, /* R1335 */
+ 0x0000, /* R1336 */
+ 0x0000, /* R1337 */
+ 0x0000, /* R1338 */
+ 0x0000, /* R1339 */
+ 0x0000, /* R1340 */
+ 0x0000, /* R1341 */
+ 0x0000, /* R1342 */
+ 0x0000, /* R1343 */
+ 0x0098, /* R1344 - AIF2 DRC (1) */
+ 0x0845, /* R1345 - AIF2 DRC (2) */
+ 0x0000, /* R1346 - AIF2 DRC (3) */
+ 0x0000, /* R1347 - AIF2 DRC (4) */
+ 0x0000, /* R1348 - AIF2 DRC (5) */
+ 0x0000, /* R1349 */
+ 0x0000, /* R1350 */
+ 0x0000, /* R1351 */
+ 0x0000, /* R1352 */
+ 0x0000, /* R1353 */
+ 0x0000, /* R1354 */
+ 0x0000, /* R1355 */
+ 0x0000, /* R1356 */
+ 0x0000, /* R1357 */
+ 0x0000, /* R1358 */
+ 0x0000, /* R1359 */
+ 0x0000, /* R1360 */
+ 0x0000, /* R1361 */
+ 0x0000, /* R1362 */
+ 0x0000, /* R1363 */
+ 0x0000, /* R1364 */
+ 0x0000, /* R1365 */
+ 0x0000, /* R1366 */
+ 0x0000, /* R1367 */
+ 0x0000, /* R1368 */
+ 0x0000, /* R1369 */
+ 0x0000, /* R1370 */
+ 0x0000, /* R1371 */
+ 0x0000, /* R1372 */
+ 0x0000, /* R1373 */
+ 0x0000, /* R1374 */
+ 0x0000, /* R1375 */
+ 0x0000, /* R1376 */
+ 0x0000, /* R1377 */
+ 0x0000, /* R1378 */
+ 0x0000, /* R1379 */
+ 0x0000, /* R1380 */
+ 0x0000, /* R1381 */
+ 0x0000, /* R1382 */
+ 0x0000, /* R1383 */
+ 0x0000, /* R1384 */
+ 0x0000, /* R1385 */
+ 0x0000, /* R1386 */
+ 0x0000, /* R1387 */
+ 0x0000, /* R1388 */
+ 0x0000, /* R1389 */
+ 0x0000, /* R1390 */
+ 0x0000, /* R1391 */
+ 0x0000, /* R1392 */
+ 0x0000, /* R1393 */
+ 0x0000, /* R1394 */
+ 0x0000, /* R1395 */
+ 0x0000, /* R1396 */
+ 0x0000, /* R1397 */
+ 0x0000, /* R1398 */
+ 0x0000, /* R1399 */
+ 0x0000, /* R1400 */
+ 0x0000, /* R1401 */
+ 0x0000, /* R1402 */
+ 0x0000, /* R1403 */
+ 0x0000, /* R1404 */
+ 0x0000, /* R1405 */
+ 0x0000, /* R1406 */
+ 0x0000, /* R1407 */
+ 0x6318, /* R1408 - AIF2 EQ Gains (1) */
+ 0x6300, /* R1409 - AIF2 EQ Gains (2) */
+ 0x0FCA, /* R1410 - AIF2 EQ Band 1 A */
+ 0x0400, /* R1411 - AIF2 EQ Band 1 B */
+ 0x00D8, /* R1412 - AIF2 EQ Band 1 PG */
+ 0x1EB5, /* R1413 - AIF2 EQ Band 2 A */
+ 0xF145, /* R1414 - AIF2 EQ Band 2 B */
+ 0x0B75, /* R1415 - AIF2 EQ Band 2 C */
+ 0x01C5, /* R1416 - AIF2 EQ Band 2 PG */
+ 0x1C58, /* R1417 - AIF2 EQ Band 3 A */
+ 0xF373, /* R1418 - AIF2 EQ Band 3 B */
+ 0x0A54, /* R1419 - AIF2 EQ Band 3 C */
+ 0x0558, /* R1420 - AIF2 EQ Band 3 PG */
+ 0x168E, /* R1421 - AIF2 EQ Band 4 A */
+ 0xF829, /* R1422 - AIF2 EQ Band 4 B */
+ 0x07AD, /* R1423 - AIF2 EQ Band 4 C */
+ 0x1103, /* R1424 - AIF2 EQ Band 4 PG */
+ 0x0564, /* R1425 - AIF2 EQ Band 5 A */
+ 0x0559, /* R1426 - AIF2 EQ Band 5 B */
+ 0x4000, /* R1427 - AIF2 EQ Band 5 PG */
+ 0x0000, /* R1428 */
+ 0x0000, /* R1429 */
+ 0x0000, /* R1430 */
+ 0x0000, /* R1431 */
+ 0x0000, /* R1432 */
+ 0x0000, /* R1433 */
+ 0x0000, /* R1434 */
+ 0x0000, /* R1435 */
+ 0x0000, /* R1436 */
+ 0x0000, /* R1437 */
+ 0x0000, /* R1438 */
+ 0x0000, /* R1439 */
+ 0x0000, /* R1440 */
+ 0x0000, /* R1441 */
+ 0x0000, /* R1442 */
+ 0x0000, /* R1443 */
+ 0x0000, /* R1444 */
+ 0x0000, /* R1445 */
+ 0x0000, /* R1446 */
+ 0x0000, /* R1447 */
+ 0x0000, /* R1448 */
+ 0x0000, /* R1449 */
+ 0x0000, /* R1450 */
+ 0x0000, /* R1451 */
+ 0x0000, /* R1452 */
+ 0x0000, /* R1453 */
+ 0x0000, /* R1454 */
+ 0x0000, /* R1455 */
+ 0x0000, /* R1456 */
+ 0x0000, /* R1457 */
+ 0x0000, /* R1458 */
+ 0x0000, /* R1459 */
+ 0x0000, /* R1460 */
+ 0x0000, /* R1461 */
+ 0x0000, /* R1462 */
+ 0x0000, /* R1463 */
+ 0x0000, /* R1464 */
+ 0x0000, /* R1465 */
+ 0x0000, /* R1466 */
+ 0x0000, /* R1467 */
+ 0x0000, /* R1468 */
+ 0x0000, /* R1469 */
+ 0x0000, /* R1470 */
+ 0x0000, /* R1471 */
+ 0x0000, /* R1472 */
+ 0x0000, /* R1473 */
+ 0x0000, /* R1474 */
+ 0x0000, /* R1475 */
+ 0x0000, /* R1476 */
+ 0x0000, /* R1477 */
+ 0x0000, /* R1478 */
+ 0x0000, /* R1479 */
+ 0x0000, /* R1480 */
+ 0x0000, /* R1481 */
+ 0x0000, /* R1482 */
+ 0x0000, /* R1483 */
+ 0x0000, /* R1484 */
+ 0x0000, /* R1485 */
+ 0x0000, /* R1486 */
+ 0x0000, /* R1487 */
+ 0x0000, /* R1488 */
+ 0x0000, /* R1489 */
+ 0x0000, /* R1490 */
+ 0x0000, /* R1491 */
+ 0x0000, /* R1492 */
+ 0x0000, /* R1493 */
+ 0x0000, /* R1494 */
+ 0x0000, /* R1495 */
+ 0x0000, /* R1496 */
+ 0x0000, /* R1497 */
+ 0x0000, /* R1498 */
+ 0x0000, /* R1499 */
+ 0x0000, /* R1500 */
+ 0x0000, /* R1501 */
+ 0x0000, /* R1502 */
+ 0x0000, /* R1503 */
+ 0x0000, /* R1504 */
+ 0x0000, /* R1505 */
+ 0x0000, /* R1506 */
+ 0x0000, /* R1507 */
+ 0x0000, /* R1508 */
+ 0x0000, /* R1509 */
+ 0x0000, /* R1510 */
+ 0x0000, /* R1511 */
+ 0x0000, /* R1512 */
+ 0x0000, /* R1513 */
+ 0x0000, /* R1514 */
+ 0x0000, /* R1515 */
+ 0x0000, /* R1516 */
+ 0x0000, /* R1517 */
+ 0x0000, /* R1518 */
+ 0x0000, /* R1519 */
+ 0x0000, /* R1520 */
+ 0x0000, /* R1521 */
+ 0x0000, /* R1522 */
+ 0x0000, /* R1523 */
+ 0x0000, /* R1524 */
+ 0x0000, /* R1525 */
+ 0x0000, /* R1526 */
+ 0x0000, /* R1527 */
+ 0x0000, /* R1528 */
+ 0x0000, /* R1529 */
+ 0x0000, /* R1530 */
+ 0x0000, /* R1531 */
+ 0x0000, /* R1532 */
+ 0x0000, /* R1533 */
+ 0x0000, /* R1534 */
+ 0x0000, /* R1535 */
+ 0x0000, /* R1536 - DAC1 Mixer Volumes */
+ 0x0000, /* R1537 - DAC1 Left Mixer Routing */
+ 0x0000, /* R1538 - DAC1 Right Mixer Routing */
+ 0x0000, /* R1539 - DAC2 Mixer Volumes */
+ 0x0000, /* R1540 - DAC2 Left Mixer Routing */
+ 0x0000, /* R1541 - DAC2 Right Mixer Routing */
+ 0x0000, /* R1542 - AIF1 ADC1 Left Mixer Routing */
+ 0x0000, /* R1543 - AIF1 ADC1 Right Mixer Routing */
+ 0x0000, /* R1544 - AIF1 ADC2 Left Mixer Routing */
+ 0x0000, /* R1545 - AIF1 ADC2 Right mixer Routing */
+ 0x0000, /* R1546 */
+ 0x0000, /* R1547 */
+ 0x0000, /* R1548 */
+ 0x0000, /* R1549 */
+ 0x0000, /* R1550 */
+ 0x0000, /* R1551 */
+ 0x02C0, /* R1552 - DAC1 Left Volume */
+ 0x02C0, /* R1553 - DAC1 Right Volume */
+ 0x02C0, /* R1554 - DAC2 Left Volume */
+ 0x02C0, /* R1555 - DAC2 Right Volume */
+ 0x0000, /* R1556 - DAC Softmute */
+ 0x0000, /* R1557 */
+ 0x0000, /* R1558 */
+ 0x0000, /* R1559 */
+ 0x0000, /* R1560 */
+ 0x0000, /* R1561 */
+ 0x0000, /* R1562 */
+ 0x0000, /* R1563 */
+ 0x0000, /* R1564 */
+ 0x0000, /* R1565 */
+ 0x0000, /* R1566 */
+ 0x0000, /* R1567 */
+ 0x0002, /* R1568 - Oversampling */
+ 0x0000, /* R1569 - Sidetone */
+};
diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c
new file mode 100644
index 00000000..54099fdb
--- /dev/null
+++ b/sound/soc/codecs/wm8994.c
@@ -0,0 +1,3259 @@
+/*
+ * wm8994.c -- WM8994 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <trace/events/asoc.h>
+
+#include <linux/mfd/wm8994/core.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <linux/mfd/wm8994/pdata.h>
+#include <linux/mfd/wm8994/gpio.h>
+
+#include "wm8994.h"
+#include "wm_hubs.h"
+
+#define WM8994_NUM_DRC 3
+#define WM8994_NUM_EQ 3
+
+static int wm8994_drc_base[] = {
+ WM8994_AIF1_DRC1_1,
+ WM8994_AIF1_DRC2_1,
+ WM8994_AIF2_DRC_1,
+};
+
+static int wm8994_retune_mobile_base[] = {
+ WM8994_AIF1_DAC1_EQ_GAINS_1,
+ WM8994_AIF1_DAC2_EQ_GAINS_1,
+ WM8994_AIF2_EQ_GAINS_1,
+};
+
+static int wm8994_readable(struct snd_soc_codec *codec, unsigned int reg)
+{
+typedef struct wm8994_priv *wm8994;
+ struct wm8994 *control = codec->control_data;
+
+ switch (reg) {
+ case WM8994_GPIO_1:
+ case WM8994_GPIO_2:
+ case WM8994_GPIO_3:
+ case WM8994_GPIO_4:
+ case WM8994_GPIO_5:
+ case WM8994_GPIO_6:
+ case WM8994_GPIO_7:
+ case WM8994_GPIO_8:
+ case WM8994_GPIO_9:
+ case WM8994_GPIO_10:
+ case WM8994_GPIO_11:
+ case WM8994_INTERRUPT_STATUS_1:
+ case WM8994_INTERRUPT_STATUS_2:
+ case WM8994_INTERRUPT_RAW_STATUS_2:
+ return 1;
+
+ case WM8958_DSP2_PROGRAM:
+ case WM8958_DSP2_CONFIG:
+ case WM8958_DSP2_EXECCONTROL:
+ if (control->type == WM8958)
+ return 1;
+ else
+ return 0;
+
+ default:
+ break;
+ }
+
+ if (reg >= WM8994_CACHE_SIZE)
+ return 0;
+ return wm8994_access_masks[reg].readable != 0;
+}
+
+static int wm8994_volatile(struct snd_soc_codec *codec, unsigned int reg)
+{
+ if (reg >= WM8994_CACHE_SIZE)
+ return 1;
+
+ switch (reg) {
+ case WM8994_SOFTWARE_RESET:
+ case WM8994_CHIP_REVISION:
+ case WM8994_DC_SERVO_1:
+ case WM8994_DC_SERVO_READBACK:
+ case WM8994_RATE_STATUS:
+ case WM8994_LDO_1:
+ case WM8994_LDO_2:
+ case WM8958_DSP2_EXECCONTROL:
+ case WM8958_MIC_DETECT_3:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ int ret;
+
+ BUG_ON(reg > WM8994_MAX_REGISTER);
+
+ if (!wm8994_volatile(codec, reg)) {
+ ret = snd_soc_cache_write(codec, reg, value);
+ if (ret != 0)
+ dev_err(codec->dev, "Cache write to %x failed: %d\n",
+ reg, ret);
+ }
+
+ return wm8994_reg_write(codec->control_data, reg, value);
+}
+
+static unsigned int wm8994_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int val;
+ int ret;
+
+ BUG_ON(reg > WM8994_MAX_REGISTER);
+
+ if (!wm8994_volatile(codec, reg) && wm8994_readable(codec, reg) &&
+ reg < codec->driver->reg_cache_size) {
+ ret = snd_soc_cache_read(codec, reg, &val);
+ if (ret >= 0)
+ return val;
+ else
+ dev_err(codec->dev, "Cache read from %x failed: %d\n",
+ reg, ret);
+ }
+
+ return wm8994_reg_read(codec->control_data, reg);
+}
+
+static int configure_aif_clock(struct snd_soc_codec *codec, int aif)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int rate;
+ int reg1 = 0;
+ int offset;
+
+ if (aif)
+ offset = 4;
+ else
+ offset = 0;
+
+ switch (wm8994->sysclk[aif]) {
+ case WM8994_SYSCLK_MCLK1:
+ rate = wm8994->mclk[0];
+ break;
+
+ case WM8994_SYSCLK_MCLK2:
+ reg1 |= 0x8;
+ rate = wm8994->mclk[1];
+ break;
+
+ case WM8994_SYSCLK_FLL1:
+ reg1 |= 0x10;
+ rate = wm8994->fll[0].out;
+ break;
+
+ case WM8994_SYSCLK_FLL2:
+ reg1 |= 0x18;
+ rate = wm8994->fll[1].out;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (rate >= 13500000) {
+ rate /= 2;
+ reg1 |= WM8994_AIF1CLK_DIV;
+
+ dev_dbg(codec->dev, "Dividing AIF%d clock to %dHz\n",
+ aif + 1, rate);
+ }
+
+ if (rate && rate < 3000000)
+ dev_warn(codec->dev, "AIF%dCLK is %dHz, should be >=3MHz for optimal performance\n",
+ aif + 1, rate);
+
+ wm8994->aifclk[aif] = rate;
+
+ snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1 + offset,
+ WM8994_AIF1CLK_SRC_MASK | WM8994_AIF1CLK_DIV,
+ reg1);
+
+ return 0;
+}
+
+static int configure_clock(struct snd_soc_codec *codec)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int old, new;
+
+ /* Bring up the AIF clocks first */
+ configure_aif_clock(codec, 0);
+ configure_aif_clock(codec, 1);
+
+ /* Then switch CLK_SYS over to the higher of them; a change
+ * can only happen as a result of a clocking change which can
+ * only be made outside of DAPM so we can safely redo the
+ * clocking.
+ */
+
+ /* If they're equal it doesn't matter which is used */
+ if (wm8994->aifclk[0] == wm8994->aifclk[1])
+ return 0;
+
+ if (wm8994->aifclk[0] < wm8994->aifclk[1])
+ new = WM8994_SYSCLK_SRC;
+ else
+ new = 0;
+
+ old = snd_soc_read(codec, WM8994_CLOCKING_1) & WM8994_SYSCLK_SRC;
+
+ /* If there's no change then we're done. */
+ if (old == new)
+ return 0;
+
+ snd_soc_update_bits(codec, WM8994_CLOCKING_1, WM8994_SYSCLK_SRC, new);
+
+ snd_soc_dapm_sync(&codec->dapm);
+
+ return 0;
+}
+
+static int check_clk_sys(struct snd_soc_dapm_widget *source,
+ struct snd_soc_dapm_widget *sink)
+{
+ int reg = snd_soc_read(source->codec, WM8994_CLOCKING_1);
+ const char *clk;
+
+ /* Check what we're currently using for CLK_SYS */
+ if (reg & WM8994_SYSCLK_SRC)
+ clk = "AIF2CLK";
+ else
+ clk = "AIF1CLK";
+
+ return strcmp(source->name, clk) == 0;
+}
+
+static const char *sidetone_hpf_text[] = {
+ "2.7kHz", "1.35kHz", "675Hz", "370Hz", "180Hz", "90Hz", "45Hz"
+};
+
+static const struct soc_enum sidetone_hpf =
+ SOC_ENUM_SINGLE(WM8994_SIDETONE, 7, 7, sidetone_hpf_text);
+
+static const char *adc_hpf_text[] = {
+ "HiFi", "Voice 1", "Voice 2", "Voice 3"
+};
+
+static const struct soc_enum aif1adc1_hpf =
+ SOC_ENUM_SINGLE(WM8994_AIF1_ADC1_FILTERS, 13, 4, adc_hpf_text);
+
+static const struct soc_enum aif1adc2_hpf =
+ SOC_ENUM_SINGLE(WM8994_AIF1_ADC2_FILTERS, 13, 4, adc_hpf_text);
+
+static const struct soc_enum aif2adc_hpf =
+ SOC_ENUM_SINGLE(WM8994_AIF2_ADC_FILTERS, 13, 4, adc_hpf_text);
+
+static const DECLARE_TLV_DB_SCALE(aif_tlv, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(st_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(wm8994_3d_tlv, -1600, 183, 0);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+
+#define WM8994_DRC_SWITCH(xname, reg, shift) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
+ .put = wm8994_put_drc_sw, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, 1, 0) }
+
+static int wm8994_put_drc_sw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int mask, ret;
+
+ /* Can't enable both ADC and DAC paths simultaneously */
+ if (mc->shift == WM8994_AIF1DAC1_DRC_ENA_SHIFT)
+ mask = WM8994_AIF1ADC1L_DRC_ENA_MASK |
+ WM8994_AIF1ADC1R_DRC_ENA_MASK;
+ else
+ mask = WM8994_AIF1DAC1_DRC_ENA_MASK;
+
+ ret = snd_soc_read(codec, mc->reg);
+ if (ret < 0)
+ return ret;
+ if (ret & mask)
+ return -EINVAL;
+
+ return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+static void wm8994_set_drc(struct snd_soc_codec *codec, int drc)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int base = wm8994_drc_base[drc];
+ int cfg = wm8994->drc_cfg[drc];
+ int save, i;
+
+ /* Save any enables; the configuration should clear them. */
+ save = snd_soc_read(codec, base);
+ save &= WM8994_AIF1DAC1_DRC_ENA | WM8994_AIF1ADC1L_DRC_ENA |
+ WM8994_AIF1ADC1R_DRC_ENA;
+
+ for (i = 0; i < WM8994_DRC_REGS; i++)
+ snd_soc_update_bits(codec, base + i, 0xffff,
+ pdata->drc_cfgs[cfg].regs[i]);
+
+ snd_soc_update_bits(codec, base, WM8994_AIF1DAC1_DRC_ENA |
+ WM8994_AIF1ADC1L_DRC_ENA |
+ WM8994_AIF1ADC1R_DRC_ENA, save);
+}
+
+/* Icky as hell but saves code duplication */
+static int wm8994_get_drc(const char *name)
+{
+ if (strcmp(name, "AIF1DRC1 Mode") == 0)
+ return 0;
+ if (strcmp(name, "AIF1DRC2 Mode") == 0)
+ return 1;
+ if (strcmp(name, "AIF2DRC Mode") == 0)
+ return 2;
+ return -EINVAL;
+}
+
+static int wm8994_put_drc_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int drc = wm8994_get_drc(kcontrol->id.name);
+ int value = ucontrol->value.integer.value[0];
+
+ if (drc < 0)
+ return drc;
+
+ if (value >= pdata->num_drc_cfgs)
+ return -EINVAL;
+
+ wm8994->drc_cfg[drc] = value;
+
+ wm8994_set_drc(codec, drc);
+
+ return 0;
+}
+
+static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int drc = wm8994_get_drc(kcontrol->id.name);
+
+ ucontrol->value.enumerated.item[0] = wm8994->drc_cfg[drc];
+
+ return 0;
+}
+
+static void wm8994_set_retune_mobile(struct snd_soc_codec *codec, int block)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int base = wm8994_retune_mobile_base[block];
+ int iface, best, best_val, save, i, cfg;
+
+ if (!pdata || !wm8994->num_retune_mobile_texts)
+ return;
+
+ switch (block) {
+ case 0:
+ case 1:
+ iface = 0;
+ break;
+ case 2:
+ iface = 1;
+ break;
+ default:
+ return;
+ }
+
+ /* Find the version of the currently selected configuration
+ * with the nearest sample rate. */
+ cfg = wm8994->retune_mobile_cfg[block];
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+ if (strcmp(pdata->retune_mobile_cfgs[i].name,
+ wm8994->retune_mobile_texts[cfg]) == 0 &&
+ abs(pdata->retune_mobile_cfgs[i].rate
+ - wm8994->dac_rates[iface]) < best_val) {
+ best = i;
+ best_val = abs(pdata->retune_mobile_cfgs[i].rate
+ - wm8994->dac_rates[iface]);
+ }
+ }
+
+ dev_dbg(codec->dev, "ReTune Mobile %d %s/%dHz for %dHz sample rate\n",
+ block,
+ pdata->retune_mobile_cfgs[best].name,
+ pdata->retune_mobile_cfgs[best].rate,
+ wm8994->dac_rates[iface]);
+
+ /* The EQ will be disabled while reconfiguring it, remember the
+ * current configuration.
+ */
+ save = snd_soc_read(codec, base);
+ save &= WM8994_AIF1DAC1_EQ_ENA;
+
+ for (i = 0; i < WM8994_EQ_REGS; i++)
+ snd_soc_update_bits(codec, base + i, 0xffff,
+ pdata->retune_mobile_cfgs[best].regs[i]);
+
+ snd_soc_update_bits(codec, base, WM8994_AIF1DAC1_EQ_ENA, save);
+}
+
+/* Icky as hell but saves code duplication */
+static int wm8994_get_retune_mobile_block(const char *name)
+{
+ if (strcmp(name, "AIF1.1 EQ Mode") == 0)
+ return 0;
+ if (strcmp(name, "AIF1.2 EQ Mode") == 0)
+ return 1;
+ if (strcmp(name, "AIF2 EQ Mode") == 0)
+ return 2;
+ return -EINVAL;
+}
+
+static int wm8994_put_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int block = wm8994_get_retune_mobile_block(kcontrol->id.name);
+ int value = ucontrol->value.integer.value[0];
+
+ if (block < 0)
+ return block;
+
+ if (value >= pdata->num_retune_mobile_cfgs)
+ return -EINVAL;
+
+ wm8994->retune_mobile_cfg[block] = value;
+
+ wm8994_set_retune_mobile(codec, block);
+
+ return 0;
+}
+
+static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int block = wm8994_get_retune_mobile_block(kcontrol->id.name);
+
+ ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block];
+
+ return 0;
+}
+
+static const char *aif_chan_src_text[] = {
+ "Left", "Right"
+};
+
+static const struct soc_enum aif1adcl_src =
+ SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_1, 15, 2, aif_chan_src_text);
+
+static const struct soc_enum aif1adcr_src =
+ SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_1, 14, 2, aif_chan_src_text);
+
+static const struct soc_enum aif2adcl_src =
+ SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_1, 15, 2, aif_chan_src_text);
+
+static const struct soc_enum aif2adcr_src =
+ SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_1, 14, 2, aif_chan_src_text);
+
+static const struct soc_enum aif1dacl_src =
+ SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_2, 15, 2, aif_chan_src_text);
+
+static const struct soc_enum aif1dacr_src =
+ SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_2, 14, 2, aif_chan_src_text);
+
+static const struct soc_enum aif2dacl_src =
+ SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 15, 2, aif_chan_src_text);
+
+static const struct soc_enum aif2dacr_src =
+ SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 14, 2, aif_chan_src_text);
+
+static const char *osr_text[] = {
+ "Low Power", "High Performance",
+};
+
+static const struct soc_enum dac_osr =
+ SOC_ENUM_SINGLE(WM8994_OVERSAMPLING, 0, 2, osr_text);
+
+static const struct soc_enum adc_osr =
+ SOC_ENUM_SINGLE(WM8994_OVERSAMPLING, 1, 2, osr_text);
+
+static const struct snd_kcontrol_new wm8994_snd_controls[] = {
+SOC_DOUBLE_R_TLV("AIF1ADC1 Volume", WM8994_AIF1_ADC1_LEFT_VOLUME,
+ WM8994_AIF1_ADC1_RIGHT_VOLUME,
+ 1, 119, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("AIF1ADC2 Volume", WM8994_AIF1_ADC2_LEFT_VOLUME,
+ WM8994_AIF1_ADC2_RIGHT_VOLUME,
+ 1, 119, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("AIF2ADC Volume", WM8994_AIF2_ADC_LEFT_VOLUME,
+ WM8994_AIF2_ADC_RIGHT_VOLUME,
+ 1, 119, 0, digital_tlv),
+
+SOC_ENUM("AIF1ADCL Source", aif1adcl_src),
+SOC_ENUM("AIF1ADCR Source", aif1adcr_src),
+SOC_ENUM("AIF2ADCL Source", aif2adcl_src),
+SOC_ENUM("AIF2ADCR Source", aif2adcr_src),
+
+SOC_ENUM("AIF1DACL Source", aif1dacl_src),
+SOC_ENUM("AIF1DACR Source", aif1dacr_src),
+SOC_ENUM("AIF2DACL Source", aif2dacl_src),
+SOC_ENUM("AIF2DACR Source", aif2dacr_src),
+
+SOC_DOUBLE_R_TLV("AIF1DAC1 Volume", WM8994_AIF1_DAC1_LEFT_VOLUME,
+ WM8994_AIF1_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("AIF1DAC2 Volume", WM8994_AIF1_DAC2_LEFT_VOLUME,
+ WM8994_AIF1_DAC2_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("AIF2DAC Volume", WM8994_AIF2_DAC_LEFT_VOLUME,
+ WM8994_AIF2_DAC_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+
+SOC_SINGLE_TLV("AIF1 Boost Volume", WM8994_AIF1_CONTROL_2, 10, 3, 0, aif_tlv),
+SOC_SINGLE_TLV("AIF2 Boost Volume", WM8994_AIF2_CONTROL_2, 10, 3, 0, aif_tlv),
+
+SOC_SINGLE("AIF1DAC1 EQ Switch", WM8994_AIF1_DAC1_EQ_GAINS_1, 0, 1, 0),
+SOC_SINGLE("AIF1DAC2 EQ Switch", WM8994_AIF1_DAC2_EQ_GAINS_1, 0, 1, 0),
+SOC_SINGLE("AIF2 EQ Switch", WM8994_AIF2_EQ_GAINS_1, 0, 1, 0),
+
+WM8994_DRC_SWITCH("AIF1DAC1 DRC Switch", WM8994_AIF1_DRC1_1, 2),
+WM8994_DRC_SWITCH("AIF1ADC1L DRC Switch", WM8994_AIF1_DRC1_1, 1),
+WM8994_DRC_SWITCH("AIF1ADC1R DRC Switch", WM8994_AIF1_DRC1_1, 0),
+
+WM8994_DRC_SWITCH("AIF1DAC2 DRC Switch", WM8994_AIF1_DRC2_1, 2),
+WM8994_DRC_SWITCH("AIF1ADC2L DRC Switch", WM8994_AIF1_DRC2_1, 1),
+WM8994_DRC_SWITCH("AIF1ADC2R DRC Switch", WM8994_AIF1_DRC2_1, 0),
+
+WM8994_DRC_SWITCH("AIF2DAC DRC Switch", WM8994_AIF2_DRC_1, 2),
+WM8994_DRC_SWITCH("AIF2ADCL DRC Switch", WM8994_AIF2_DRC_1, 1),
+WM8994_DRC_SWITCH("AIF2ADCR DRC Switch", WM8994_AIF2_DRC_1, 0),
+
+SOC_SINGLE_TLV("DAC1 Right Sidetone Volume", WM8994_DAC1_MIXER_VOLUMES,
+ 5, 12, 0, st_tlv),
+SOC_SINGLE_TLV("DAC1 Left Sidetone Volume", WM8994_DAC1_MIXER_VOLUMES,
+ 0, 12, 0, st_tlv),
+SOC_SINGLE_TLV("DAC2 Right Sidetone Volume", WM8994_DAC2_MIXER_VOLUMES,
+ 5, 12, 0, st_tlv),
+SOC_SINGLE_TLV("DAC2 Left Sidetone Volume", WM8994_DAC2_MIXER_VOLUMES,
+ 0, 12, 0, st_tlv),
+SOC_ENUM("Sidetone HPF Mux", sidetone_hpf),
+SOC_SINGLE("Sidetone HPF Switch", WM8994_SIDETONE, 6, 1, 0),
+
+SOC_ENUM("AIF1ADC1 HPF Mode", aif1adc1_hpf),
+SOC_DOUBLE("AIF1ADC1 HPF Switch", WM8994_AIF1_ADC1_FILTERS, 12, 11, 1, 0),
+
+SOC_ENUM("AIF1ADC2 HPF Mode", aif1adc2_hpf),
+SOC_DOUBLE("AIF1ADC2 HPF Switch", WM8994_AIF1_ADC2_FILTERS, 12, 11, 1, 0),
+
+SOC_ENUM("AIF2ADC HPF Mode", aif2adc_hpf),
+SOC_DOUBLE("AIF2ADC HPF Switch", WM8994_AIF2_ADC_FILTERS, 12, 11, 1, 0),
+
+SOC_ENUM("ADC OSR", adc_osr),
+SOC_ENUM("DAC OSR", dac_osr),
+
+SOC_DOUBLE_R_TLV("DAC1 Volume", WM8994_DAC1_LEFT_VOLUME,
+ WM8994_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+SOC_DOUBLE_R("DAC1 Switch", WM8994_DAC1_LEFT_VOLUME,
+ WM8994_DAC1_RIGHT_VOLUME, 9, 1, 1),
+
+SOC_DOUBLE_R_TLV("DAC2 Volume", WM8994_DAC2_LEFT_VOLUME,
+ WM8994_DAC2_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+SOC_DOUBLE_R("DAC2 Switch", WM8994_DAC2_LEFT_VOLUME,
+ WM8994_DAC2_RIGHT_VOLUME, 9, 1, 1),
+
+SOC_SINGLE_TLV("SPKL DAC2 Volume", WM8994_SPKMIXL_ATTENUATION,
+ 6, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKL DAC1 Volume", WM8994_SPKMIXL_ATTENUATION,
+ 2, 1, 1, wm_hubs_spkmix_tlv),
+
+SOC_SINGLE_TLV("SPKR DAC2 Volume", WM8994_SPKMIXR_ATTENUATION,
+ 6, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKR DAC1 Volume", WM8994_SPKMIXR_ATTENUATION,
+ 2, 1, 1, wm_hubs_spkmix_tlv),
+
+SOC_SINGLE_TLV("AIF1DAC1 3D Stereo Volume", WM8994_AIF1_DAC1_FILTERS_2,
+ 10, 15, 0, wm8994_3d_tlv),
+SOC_SINGLE("AIF1DAC1 3D Stereo Switch", WM8994_AIF1_DAC1_FILTERS_2,
+ 8, 1, 0),
+SOC_SINGLE_TLV("AIF1DAC2 3D Stereo Volume", WM8994_AIF1_DAC2_FILTERS_2,
+ 10, 15, 0, wm8994_3d_tlv),
+SOC_SINGLE("AIF1DAC2 3D Stereo Switch", WM8994_AIF1_DAC2_FILTERS_2,
+ 8, 1, 0),
+SOC_SINGLE_TLV("AIF2DAC 3D Stereo Volume", WM8994_AIF2_DAC_FILTERS_2,
+ 10, 15, 0, wm8994_3d_tlv),
+SOC_SINGLE("AIF2DAC 3D Stereo Switch", WM8994_AIF2_DAC_FILTERS_2,
+ 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8994_eq_controls[] = {
+SOC_SINGLE_TLV("AIF1DAC1 EQ1 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 11, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC1 EQ2 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 6, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC1 EQ3 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 1, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC1 EQ4 Volume", WM8994_AIF1_DAC1_EQ_GAINS_2, 11, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC1 EQ5 Volume", WM8994_AIF1_DAC1_EQ_GAINS_2, 6, 31, 0,
+ eq_tlv),
+
+SOC_SINGLE_TLV("AIF1DAC2 EQ1 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 11, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC2 EQ2 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 6, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC2 EQ3 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 1, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC2 EQ4 Volume", WM8994_AIF1_DAC2_EQ_GAINS_2, 11, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC2 EQ5 Volume", WM8994_AIF1_DAC2_EQ_GAINS_2, 6, 31, 0,
+ eq_tlv),
+
+SOC_SINGLE_TLV("AIF2 EQ1 Volume", WM8994_AIF2_EQ_GAINS_1, 11, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF2 EQ2 Volume", WM8994_AIF2_EQ_GAINS_1, 6, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF2 EQ3 Volume", WM8994_AIF2_EQ_GAINS_1, 1, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF2 EQ4 Volume", WM8994_AIF2_EQ_GAINS_2, 11, 31, 0,
+ eq_tlv),
+SOC_SINGLE_TLV("AIF2 EQ5 Volume", WM8994_AIF2_EQ_GAINS_2, 6, 31, 0,
+ eq_tlv),
+};
+
+static const struct snd_kcontrol_new wm8958_snd_controls[] = {
+SOC_SINGLE_TLV("AIF3 Boost Volume", WM8958_AIF3_CONTROL_2, 10, 3, 0, aif_tlv),
+};
+
+static int clk_sys_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ return configure_clock(codec);
+
+ case SND_SOC_DAPM_POST_PMD:
+ configure_clock(codec);
+ break;
+ }
+
+ return 0;
+}
+
+static void wm8994_update_class_w(struct snd_soc_codec *codec)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int enable = 1;
+ int source = 0; /* GCC flow analysis can't track enable */
+ int reg, reg_r;
+
+ /* Only support direct DAC->headphone paths */
+ reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_1);
+ if (!(reg & WM8994_DAC1L_TO_HPOUT1L)) {
+ dev_vdbg(codec->dev, "HPL connected to output mixer\n");
+ enable = 0;
+ }
+
+ reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_2);
+ if (!(reg & WM8994_DAC1R_TO_HPOUT1R)) {
+ dev_vdbg(codec->dev, "HPR connected to output mixer\n");
+ enable = 0;
+ }
+
+ /* We also need the same setting for L/R and only one path */
+ reg = snd_soc_read(codec, WM8994_DAC1_LEFT_MIXER_ROUTING);
+ switch (reg) {
+ case WM8994_AIF2DACL_TO_DAC1L:
+ dev_vdbg(codec->dev, "Class W source AIF2DAC\n");
+ source = 2 << WM8994_CP_DYN_SRC_SEL_SHIFT;
+ break;
+ case WM8994_AIF1DAC2L_TO_DAC1L:
+ dev_vdbg(codec->dev, "Class W source AIF1DAC2\n");
+ source = 1 << WM8994_CP_DYN_SRC_SEL_SHIFT;
+ break;
+ case WM8994_AIF1DAC1L_TO_DAC1L:
+ dev_vdbg(codec->dev, "Class W source AIF1DAC1\n");
+ source = 0 << WM8994_CP_DYN_SRC_SEL_SHIFT;
+ break;
+ default:
+ dev_vdbg(codec->dev, "DAC mixer setting: %x\n", reg);
+ enable = 0;
+ break;
+ }
+
+ reg_r = snd_soc_read(codec, WM8994_DAC1_RIGHT_MIXER_ROUTING);
+ if (reg_r != reg) {
+ dev_vdbg(codec->dev, "Left and right DAC mixers different\n");
+ enable = 0;
+ }
+
+ if (enable) {
+ dev_dbg(codec->dev, "Class W enabled\n");
+ snd_soc_update_bits(codec, WM8994_CLASS_W_1,
+ WM8994_CP_DYN_PWR |
+ WM8994_CP_DYN_SRC_SEL_MASK,
+ source | WM8994_CP_DYN_PWR);
+ wm8994->hubs.class_w = true;
+
+ } else {
+ dev_dbg(codec->dev, "Class W disabled\n");
+ snd_soc_update_bits(codec, WM8994_CLASS_W_1,
+ WM8994_CP_DYN_PWR, 0);
+ wm8994->hubs.class_w = false;
+ }
+}
+
+static int late_enable_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (wm8994->aif1clk_enable) {
+ snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
+ WM8994_AIF1CLK_ENA_MASK,
+ WM8994_AIF1CLK_ENA);
+ wm8994->aif1clk_enable = 0;
+ }
+ if (wm8994->aif2clk_enable) {
+ snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
+ WM8994_AIF2CLK_ENA_MASK,
+ WM8994_AIF2CLK_ENA);
+ wm8994->aif2clk_enable = 0;
+ }
+ break;
+ }
+
+ /* We may also have postponed startup of DSP, handle that. */
+ wm8958_aif_ev(w, kcontrol, event);
+
+ return 0;
+}
+
+static int late_disable_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMD:
+ if (wm8994->aif1clk_disable) {
+ snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
+ WM8994_AIF1CLK_ENA_MASK, 0);
+ wm8994->aif1clk_disable = 0;
+ }
+ if (wm8994->aif2clk_disable) {
+ snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
+ WM8994_AIF2CLK_ENA_MASK, 0);
+ wm8994->aif2clk_disable = 0;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static int aif1clk_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ wm8994->aif1clk_enable = 1;
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ wm8994->aif1clk_disable = 1;
+ break;
+ }
+
+ return 0;
+}
+
+static int aif2clk_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ wm8994->aif2clk_enable = 1;
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ wm8994->aif2clk_disable = 1;
+ break;
+ }
+
+ return 0;
+}
+
+static int adc_mux_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ late_enable_ev(w, kcontrol, event);
+ return 0;
+}
+
+static int micbias_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ late_enable_ev(w, kcontrol, event);
+ return 0;
+}
+
+static int dac_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ unsigned int mask = 1 << w->shift;
+
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5,
+ mask, mask);
+ return 0;
+}
+
+static const char *hp_mux_text[] = {
+ "Mixer",
+ "DAC",
+};
+
+#define WM8994_HP_ENUM(xname, xenum) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_enum_double, \
+ .get = snd_soc_dapm_get_enum_double, \
+ .put = wm8994_put_hp_enum, \
+ .private_value = (unsigned long)&xenum }
+
+static int wm8994_put_hp_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *w = wlist->widgets[0];
+ struct snd_soc_codec *codec = w->codec;
+ int ret;
+
+ ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
+
+ wm8994_update_class_w(codec);
+
+ return ret;
+}
+
+static const struct soc_enum hpl_enum =
+ SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_1, 8, 2, hp_mux_text);
+
+static const struct snd_kcontrol_new hpl_mux =
+ WM8994_HP_ENUM("Left Headphone Mux", hpl_enum);
+
+static const struct soc_enum hpr_enum =
+ SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_2, 8, 2, hp_mux_text);
+
+static const struct snd_kcontrol_new hpr_mux =
+ WM8994_HP_ENUM("Right Headphone Mux", hpr_enum);
+
+static const char *adc_mux_text[] = {
+ "ADC",
+ "DMIC",
+};
+
+static const struct soc_enum adc_enum =
+ SOC_ENUM_SINGLE(0, 0, 2, adc_mux_text);
+
+static const struct snd_kcontrol_new adcl_mux =
+ SOC_DAPM_ENUM_VIRT("ADCL Mux", adc_enum);
+
+static const struct snd_kcontrol_new adcr_mux =
+ SOC_DAPM_ENUM_VIRT("ADCR Mux", adc_enum);
+
+static const struct snd_kcontrol_new left_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 9, 1, 0),
+SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 7, 1, 0),
+SOC_DAPM_SINGLE("IN1LP Switch", WM8994_SPEAKER_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 1, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 8, 1, 0),
+SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 6, 1, 0),
+SOC_DAPM_SINGLE("IN1RP Switch", WM8994_SPEAKER_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 0, 1, 0),
+};
+
+/* Debugging; dump chip status after DAPM transitions */
+static int post_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ dev_dbg(codec->dev, "SRC status: %x\n",
+ snd_soc_read(codec,
+ WM8994_RATE_STATUS));
+ return 0;
+}
+
+static const struct snd_kcontrol_new aif1adc1l_mix[] = {
+SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc1r_mix[] = {
+SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc2l_mix[] = {
+SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc2r_mix[] = {
+SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif2dac2l_mix[] = {
+SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
+ 5, 1, 0),
+SOC_DAPM_SINGLE("Left Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
+ 4, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
+ 2, 1, 0),
+SOC_DAPM_SINGLE("AIF1.2 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("AIF1.1 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif2dac2r_mix[] = {
+SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
+ 5, 1, 0),
+SOC_DAPM_SINGLE("Left Sidetone Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
+ 4, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
+ 2, 1, 0),
+SOC_DAPM_SINGLE("AIF1.2 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("AIF1.1 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+#define WM8994_CLASS_W_SWITCH(xname, reg, shift, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_dapm_get_volsw, .put = wm8994_put_class_w, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+static int wm8994_put_class_w(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *w = wlist->widgets[0];
+ struct snd_soc_codec *codec = w->codec;
+ int ret;
+
+ ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+ wm8994_update_class_w(codec);
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new dac1l_mix[] = {
+WM8994_CLASS_W_SWITCH("Right Sidetone Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
+ 5, 1, 0),
+WM8994_CLASS_W_SWITCH("Left Sidetone Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
+ 4, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF2 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
+ 2, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF1.2 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
+ 1, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF1.1 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dac1r_mix[] = {
+WM8994_CLASS_W_SWITCH("Right Sidetone Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
+ 5, 1, 0),
+WM8994_CLASS_W_SWITCH("Left Sidetone Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
+ 4, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF2 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
+ 2, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF1.2 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
+ 1, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF1.1 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const char *sidetone_text[] = {
+ "ADC/DMIC1", "DMIC2",
+};
+
+static const struct soc_enum sidetone1_enum =
+ SOC_ENUM_SINGLE(WM8994_SIDETONE, 0, 2, sidetone_text);
+
+static const struct snd_kcontrol_new sidetone1_mux =
+ SOC_DAPM_ENUM("Left Sidetone Mux", sidetone1_enum);
+
+static const struct soc_enum sidetone2_enum =
+ SOC_ENUM_SINGLE(WM8994_SIDETONE, 1, 2, sidetone_text);
+
+static const struct snd_kcontrol_new sidetone2_mux =
+ SOC_DAPM_ENUM("Right Sidetone Mux", sidetone2_enum);
+
+static const char *aif1dac_text[] = {
+ "AIF1DACDAT", "AIF3DACDAT",
+};
+
+static const struct soc_enum aif1dac_enum =
+ SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 0, 2, aif1dac_text);
+
+static const struct snd_kcontrol_new aif1dac_mux =
+ SOC_DAPM_ENUM("AIF1DAC Mux", aif1dac_enum);
+
+static const char *aif2dac_text[] = {
+ "AIF2DACDAT", "AIF3DACDAT",
+};
+
+static const struct soc_enum aif2dac_enum =
+ SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 1, 2, aif2dac_text);
+
+static const struct snd_kcontrol_new aif2dac_mux =
+ SOC_DAPM_ENUM("AIF2DAC Mux", aif2dac_enum);
+
+static const char *aif2adc_text[] = {
+ "AIF2ADCDAT", "AIF3DACDAT",
+};
+
+static const struct soc_enum aif2adc_enum =
+ SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 2, 2, aif2adc_text);
+
+static const struct snd_kcontrol_new aif2adc_mux =
+ SOC_DAPM_ENUM("AIF2ADC Mux", aif2adc_enum);
+
+static const char *aif3adc_text[] = {
+ "AIF1ADCDAT", "AIF2ADCDAT", "AIF2DACDAT", "Mono PCM",
+};
+
+static const struct soc_enum wm8994_aif3adc_enum =
+ SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 3, 3, aif3adc_text);
+
+static const struct snd_kcontrol_new wm8994_aif3adc_mux =
+ SOC_DAPM_ENUM("AIF3ADC Mux", wm8994_aif3adc_enum);
+
+static const struct soc_enum wm8958_aif3adc_enum =
+ SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 3, 4, aif3adc_text);
+
+static const struct snd_kcontrol_new wm8958_aif3adc_mux =
+ SOC_DAPM_ENUM("AIF3ADC Mux", wm8958_aif3adc_enum);
+
+static const char *mono_pcm_out_text[] = {
+ "None", "AIF2ADCL", "AIF2ADCR",
+};
+
+static const struct soc_enum mono_pcm_out_enum =
+ SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 9, 3, mono_pcm_out_text);
+
+static const struct snd_kcontrol_new mono_pcm_out_mux =
+ SOC_DAPM_ENUM("Mono PCM Out Mux", mono_pcm_out_enum);
+
+static const char *aif2dac_src_text[] = {
+ "AIF2", "AIF3",
+};
+
+/* Note that these two control shouldn't be simultaneously switched to AIF3 */
+static const struct soc_enum aif2dacl_src_enum =
+ SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 7, 2, aif2dac_src_text);
+
+static const struct snd_kcontrol_new aif2dacl_src_mux =
+ SOC_DAPM_ENUM("AIF2DACL Mux", aif2dacl_src_enum);
+
+static const struct soc_enum aif2dacr_src_enum =
+ SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 8, 2, aif2dac_src_text);
+
+static const struct snd_kcontrol_new aif2dacr_src_mux =
+ SOC_DAPM_ENUM("AIF2DACR Mux", aif2dacr_src_enum);
+
+static const struct snd_soc_dapm_widget wm8994_lateclk_revd_widgets[] = {
+SND_SOC_DAPM_SUPPLY("AIF1CLK", SND_SOC_NOPM, 0, 0, aif1clk_ev,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("AIF2CLK", SND_SOC_NOPM, 0, 0, aif2clk_ev,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_PGA_E("Late DAC1L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
+ late_enable_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_PGA_E("Late DAC1R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
+ late_enable_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_PGA_E("Late DAC2L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
+ late_enable_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_PGA_E("Late DAC2R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
+ late_enable_ev, SND_SOC_DAPM_PRE_PMU),
+
+SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev)
+};
+
+static const struct snd_soc_dapm_widget wm8994_lateclk_widgets[] = {
+SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0)
+};
+
+static const struct snd_soc_dapm_widget wm8994_dac_revd_widgets[] = {
+SND_SOC_DAPM_DAC_E("DAC2L", NULL, SND_SOC_NOPM, 3, 0,
+ dac_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_DAC_E("DAC2R", NULL, SND_SOC_NOPM, 2, 0,
+ dac_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_DAC_E("DAC1L", NULL, SND_SOC_NOPM, 1, 0,
+ dac_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_DAC_E("DAC1R", NULL, SND_SOC_NOPM, 0, 0,
+ dac_ev, SND_SOC_DAPM_PRE_PMU),
+};
+
+static const struct snd_soc_dapm_widget wm8994_dac_widgets[] = {
+SND_SOC_DAPM_DAC("DAC2L", NULL, WM8994_POWER_MANAGEMENT_5, 3, 0),
+SND_SOC_DAPM_DAC("DAC2R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0),
+SND_SOC_DAPM_DAC("DAC1L", NULL, WM8994_POWER_MANAGEMENT_5, 1, 0),
+SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 0, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8994_adc_revd_widgets[] = {
+SND_SOC_DAPM_MUX_E("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux,
+ adc_mux_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_MUX_E("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux,
+ adc_mux_ev, SND_SOC_DAPM_PRE_PMU),
+};
+
+static const struct snd_soc_dapm_widget wm8994_adc_widgets[] = {
+SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux),
+SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux),
+};
+
+static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("DMIC1DAT"),
+SND_SOC_DAPM_INPUT("DMIC2DAT"),
+SND_SOC_DAPM_INPUT("Clock"),
+
+SND_SOC_DAPM_SUPPLY_S("MICBIAS Supply", 1, SND_SOC_NOPM, 0, 0, micbias_ev,
+ SND_SOC_DAPM_PRE_PMU),
+
+SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_SUPPLY("DSP1CLK", WM8994_CLOCKING_1, 3, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("DSP2CLK", WM8994_CLOCKING_1, 2, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("DSPINTCLK", WM8994_CLOCKING_1, 1, 0, NULL, 0),
+
+SND_SOC_DAPM_AIF_OUT("AIF1ADC1L", NULL,
+ 0, WM8994_POWER_MANAGEMENT_4, 9, 0),
+SND_SOC_DAPM_AIF_OUT("AIF1ADC1R", NULL,
+ 0, WM8994_POWER_MANAGEMENT_4, 8, 0),
+SND_SOC_DAPM_AIF_IN_E("AIF1DAC1L", NULL, 0,
+ WM8994_POWER_MANAGEMENT_5, 9, 0, wm8958_aif_ev,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_AIF_IN_E("AIF1DAC1R", NULL, 0,
+ WM8994_POWER_MANAGEMENT_5, 8, 0, wm8958_aif_ev,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_AIF_OUT("AIF1ADC2L", NULL,
+ 0, WM8994_POWER_MANAGEMENT_4, 11, 0),
+SND_SOC_DAPM_AIF_OUT("AIF1ADC2R", NULL,
+ 0, WM8994_POWER_MANAGEMENT_4, 10, 0),
+SND_SOC_DAPM_AIF_IN_E("AIF1DAC2L", NULL, 0,
+ WM8994_POWER_MANAGEMENT_5, 11, 0, wm8958_aif_ev,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_AIF_IN_E("AIF1DAC2R", NULL, 0,
+ WM8994_POWER_MANAGEMENT_5, 10, 0, wm8958_aif_ev,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0,
+ aif1adc1l_mix, ARRAY_SIZE(aif1adc1l_mix)),
+SND_SOC_DAPM_MIXER("AIF1ADC1R Mixer", SND_SOC_NOPM, 0, 0,
+ aif1adc1r_mix, ARRAY_SIZE(aif1adc1r_mix)),
+
+SND_SOC_DAPM_MIXER("AIF1ADC2L Mixer", SND_SOC_NOPM, 0, 0,
+ aif1adc2l_mix, ARRAY_SIZE(aif1adc2l_mix)),
+SND_SOC_DAPM_MIXER("AIF1ADC2R Mixer", SND_SOC_NOPM, 0, 0,
+ aif1adc2r_mix, ARRAY_SIZE(aif1adc2r_mix)),
+
+SND_SOC_DAPM_MIXER("AIF2DAC2L Mixer", SND_SOC_NOPM, 0, 0,
+ aif2dac2l_mix, ARRAY_SIZE(aif2dac2l_mix)),
+SND_SOC_DAPM_MIXER("AIF2DAC2R Mixer", SND_SOC_NOPM, 0, 0,
+ aif2dac2r_mix, ARRAY_SIZE(aif2dac2r_mix)),
+
+SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &sidetone1_mux),
+SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &sidetone2_mux),
+
+SND_SOC_DAPM_MIXER("DAC1L Mixer", SND_SOC_NOPM, 0, 0,
+ dac1l_mix, ARRAY_SIZE(dac1l_mix)),
+SND_SOC_DAPM_MIXER("DAC1R Mixer", SND_SOC_NOPM, 0, 0,
+ dac1r_mix, ARRAY_SIZE(dac1r_mix)),
+
+SND_SOC_DAPM_AIF_OUT("AIF2ADCL", NULL, 0,
+ WM8994_POWER_MANAGEMENT_4, 13, 0),
+SND_SOC_DAPM_AIF_OUT("AIF2ADCR", NULL, 0,
+ WM8994_POWER_MANAGEMENT_4, 12, 0),
+SND_SOC_DAPM_AIF_IN_E("AIF2DACL", NULL, 0,
+ WM8994_POWER_MANAGEMENT_5, 13, 0, wm8958_aif_ev,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_AIF_IN_E("AIF2DACR", NULL, 0,
+ WM8994_POWER_MANAGEMENT_5, 12, 0, wm8958_aif_ev,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_AIF_IN("AIF1DACDAT", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIF2DACDAT", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIF1ADCDAT", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIF2ADCDAT", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("AIF1DAC Mux", SND_SOC_NOPM, 0, 0, &aif1dac_mux),
+SND_SOC_DAPM_MUX("AIF2DAC Mux", SND_SOC_NOPM, 0, 0, &aif2dac_mux),
+SND_SOC_DAPM_MUX("AIF2ADC Mux", SND_SOC_NOPM, 0, 0, &aif2adc_mux),
+
+SND_SOC_DAPM_AIF_IN("AIF3DACDAT", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIF3ADCDAT", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_SUPPLY("TOCLK", WM8994_CLOCKING_1, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_ADC("DMIC2L", NULL, WM8994_POWER_MANAGEMENT_4, 5, 0),
+SND_SOC_DAPM_ADC("DMIC2R", NULL, WM8994_POWER_MANAGEMENT_4, 4, 0),
+SND_SOC_DAPM_ADC("DMIC1L", NULL, WM8994_POWER_MANAGEMENT_4, 3, 0),
+SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0),
+
+/* Power is done with the muxes since the ADC power also controls the
+ * downsampling chain, the chip will automatically manage the analogue
+ * specific portions.
+ */
+SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
+SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
+
+SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
+ left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
+SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
+ right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
+
+SND_SOC_DAPM_POST("Debug log", post_ev),
+};
+
+static const struct snd_soc_dapm_widget wm8994_specific_dapm_widgets[] = {
+SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8994_aif3adc_mux),
+};
+
+static const struct snd_soc_dapm_widget wm8958_dapm_widgets[] = {
+SND_SOC_DAPM_MUX("Mono PCM Out Mux", SND_SOC_NOPM, 0, 0, &mono_pcm_out_mux),
+SND_SOC_DAPM_MUX("AIF2DACL Mux", SND_SOC_NOPM, 0, 0, &aif2dacl_src_mux),
+SND_SOC_DAPM_MUX("AIF2DACR Mux", SND_SOC_NOPM, 0, 0, &aif2dacr_src_mux),
+SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8958_aif3adc_mux),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ { "CLK_SYS", NULL, "AIF1CLK", check_clk_sys },
+ { "CLK_SYS", NULL, "AIF2CLK", check_clk_sys },
+
+ { "DSP1CLK", NULL, "CLK_SYS" },
+ { "DSP2CLK", NULL, "CLK_SYS" },
+ { "DSPINTCLK", NULL, "CLK_SYS" },
+
+ { "AIF1ADC1L", NULL, "AIF1CLK" },
+ { "AIF1ADC1L", NULL, "DSP1CLK" },
+ { "AIF1ADC1R", NULL, "AIF1CLK" },
+ { "AIF1ADC1R", NULL, "DSP1CLK" },
+ { "AIF1ADC1R", NULL, "DSPINTCLK" },
+
+ { "AIF1DAC1L", NULL, "AIF1CLK" },
+ { "AIF1DAC1L", NULL, "DSP1CLK" },
+ { "AIF1DAC1R", NULL, "AIF1CLK" },
+ { "AIF1DAC1R", NULL, "DSP1CLK" },
+ { "AIF1DAC1R", NULL, "DSPINTCLK" },
+
+ { "AIF1ADC2L", NULL, "AIF1CLK" },
+ { "AIF1ADC2L", NULL, "DSP1CLK" },
+ { "AIF1ADC2R", NULL, "AIF1CLK" },
+ { "AIF1ADC2R", NULL, "DSP1CLK" },
+ { "AIF1ADC2R", NULL, "DSPINTCLK" },
+
+ { "AIF1DAC2L", NULL, "AIF1CLK" },
+ { "AIF1DAC2L", NULL, "DSP1CLK" },
+ { "AIF1DAC2R", NULL, "AIF1CLK" },
+ { "AIF1DAC2R", NULL, "DSP1CLK" },
+ { "AIF1DAC2R", NULL, "DSPINTCLK" },
+
+ { "AIF2ADCL", NULL, "AIF2CLK" },
+ { "AIF2ADCL", NULL, "DSP2CLK" },
+ { "AIF2ADCR", NULL, "AIF2CLK" },
+ { "AIF2ADCR", NULL, "DSP2CLK" },
+ { "AIF2ADCR", NULL, "DSPINTCLK" },
+
+ { "AIF2DACL", NULL, "AIF2CLK" },
+ { "AIF2DACL", NULL, "DSP2CLK" },
+ { "AIF2DACR", NULL, "AIF2CLK" },
+ { "AIF2DACR", NULL, "DSP2CLK" },
+ { "AIF2DACR", NULL, "DSPINTCLK" },
+
+ { "DMIC1L", NULL, "DMIC1DAT" },
+ { "DMIC1L", NULL, "CLK_SYS" },
+ { "DMIC1R", NULL, "DMIC1DAT" },
+ { "DMIC1R", NULL, "CLK_SYS" },
+ { "DMIC2L", NULL, "DMIC2DAT" },
+ { "DMIC2L", NULL, "CLK_SYS" },
+ { "DMIC2R", NULL, "DMIC2DAT" },
+ { "DMIC2R", NULL, "CLK_SYS" },
+
+ { "ADCL", NULL, "AIF1CLK" },
+ { "ADCL", NULL, "DSP1CLK" },
+ { "ADCL", NULL, "DSPINTCLK" },
+
+ { "ADCR", NULL, "AIF1CLK" },
+ { "ADCR", NULL, "DSP1CLK" },
+ { "ADCR", NULL, "DSPINTCLK" },
+
+ { "ADCL Mux", "ADC", "ADCL" },
+ { "ADCL Mux", "DMIC", "DMIC1L" },
+ { "ADCR Mux", "ADC", "ADCR" },
+ { "ADCR Mux", "DMIC", "DMIC1R" },
+
+ { "DAC1L", NULL, "AIF1CLK" },
+ { "DAC1L", NULL, "DSP1CLK" },
+ { "DAC1L", NULL, "DSPINTCLK" },
+
+ { "DAC1R", NULL, "AIF1CLK" },
+ { "DAC1R", NULL, "DSP1CLK" },
+ { "DAC1R", NULL, "DSPINTCLK" },
+
+ { "DAC2L", NULL, "AIF2CLK" },
+ { "DAC2L", NULL, "DSP2CLK" },
+ { "DAC2L", NULL, "DSPINTCLK" },
+
+ { "DAC2R", NULL, "AIF2DACR" },
+ { "DAC2R", NULL, "AIF2CLK" },
+ { "DAC2R", NULL, "DSP2CLK" },
+ { "DAC2R", NULL, "DSPINTCLK" },
+
+ { "TOCLK", NULL, "CLK_SYS" },
+
+ /* AIF1 outputs */
+ { "AIF1ADC1L", NULL, "AIF1ADC1L Mixer" },
+ { "AIF1ADC1L Mixer", "ADC/DMIC Switch", "ADCL Mux" },
+ { "AIF1ADC1L Mixer", "AIF2 Switch", "AIF2DACL" },
+
+ { "AIF1ADC1R", NULL, "AIF1ADC1R Mixer" },
+ { "AIF1ADC1R Mixer", "ADC/DMIC Switch", "ADCR Mux" },
+ { "AIF1ADC1R Mixer", "AIF2 Switch", "AIF2DACR" },
+
+ { "AIF1ADC2L", NULL, "AIF1ADC2L Mixer" },
+ { "AIF1ADC2L Mixer", "DMIC Switch", "DMIC2L" },
+ { "AIF1ADC2L Mixer", "AIF2 Switch", "AIF2DACL" },
+
+ { "AIF1ADC2R", NULL, "AIF1ADC2R Mixer" },
+ { "AIF1ADC2R Mixer", "DMIC Switch", "DMIC2R" },
+ { "AIF1ADC2R Mixer", "AIF2 Switch", "AIF2DACR" },
+
+ /* Pin level routing for AIF3 */
+ { "AIF1DAC1L", NULL, "AIF1DAC Mux" },
+ { "AIF1DAC1R", NULL, "AIF1DAC Mux" },
+ { "AIF1DAC2L", NULL, "AIF1DAC Mux" },
+ { "AIF1DAC2R", NULL, "AIF1DAC Mux" },
+
+ { "AIF1DAC Mux", "AIF1DACDAT", "AIF1DACDAT" },
+ { "AIF1DAC Mux", "AIF3DACDAT", "AIF3DACDAT" },
+ { "AIF2DAC Mux", "AIF2DACDAT", "AIF2DACDAT" },
+ { "AIF2DAC Mux", "AIF3DACDAT", "AIF3DACDAT" },
+ { "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCL" },
+ { "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCR" },
+ { "AIF2ADC Mux", "AIF3DACDAT", "AIF3ADCDAT" },
+
+ /* DAC1 inputs */
+ { "DAC1L Mixer", "AIF2 Switch", "AIF2DACL" },
+ { "DAC1L Mixer", "AIF1.2 Switch", "AIF1DAC2L" },
+ { "DAC1L Mixer", "AIF1.1 Switch", "AIF1DAC1L" },
+ { "DAC1L Mixer", "Left Sidetone Switch", "Left Sidetone" },
+ { "DAC1L Mixer", "Right Sidetone Switch", "Right Sidetone" },
+
+ { "DAC1R Mixer", "AIF2 Switch", "AIF2DACR" },
+ { "DAC1R Mixer", "AIF1.2 Switch", "AIF1DAC2R" },
+ { "DAC1R Mixer", "AIF1.1 Switch", "AIF1DAC1R" },
+ { "DAC1R Mixer", "Left Sidetone Switch", "Left Sidetone" },
+ { "DAC1R Mixer", "Right Sidetone Switch", "Right Sidetone" },
+
+ /* DAC2/AIF2 outputs */
+ { "AIF2ADCL", NULL, "AIF2DAC2L Mixer" },
+ { "AIF2DAC2L Mixer", "AIF2 Switch", "AIF2DACL" },
+ { "AIF2DAC2L Mixer", "AIF1.2 Switch", "AIF1DAC2L" },
+ { "AIF2DAC2L Mixer", "AIF1.1 Switch", "AIF1DAC1L" },
+ { "AIF2DAC2L Mixer", "Left Sidetone Switch", "Left Sidetone" },
+ { "AIF2DAC2L Mixer", "Right Sidetone Switch", "Right Sidetone" },
+
+ { "AIF2ADCR", NULL, "AIF2DAC2R Mixer" },
+ { "AIF2DAC2R Mixer", "AIF2 Switch", "AIF2DACR" },
+ { "AIF2DAC2R Mixer", "AIF1.2 Switch", "AIF1DAC2R" },
+ { "AIF2DAC2R Mixer", "AIF1.1 Switch", "AIF1DAC1R" },
+ { "AIF2DAC2R Mixer", "Left Sidetone Switch", "Left Sidetone" },
+ { "AIF2DAC2R Mixer", "Right Sidetone Switch", "Right Sidetone" },
+
+ { "AIF1ADCDAT", NULL, "AIF1ADC1L" },
+ { "AIF1ADCDAT", NULL, "AIF1ADC1R" },
+ { "AIF1ADCDAT", NULL, "AIF1ADC2L" },
+ { "AIF1ADCDAT", NULL, "AIF1ADC2R" },
+
+ { "AIF2ADCDAT", NULL, "AIF2ADC Mux" },
+
+ /* AIF3 output */
+ { "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC1L" },
+ { "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC1R" },
+ { "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC2L" },
+ { "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC2R" },
+ { "AIF3ADCDAT", "AIF2ADCDAT", "AIF2ADCL" },
+ { "AIF3ADCDAT", "AIF2ADCDAT", "AIF2ADCR" },
+ { "AIF3ADCDAT", "AIF2DACDAT", "AIF2DACL" },
+ { "AIF3ADCDAT", "AIF2DACDAT", "AIF2DACR" },
+
+ /* Sidetone */
+ { "Left Sidetone", "ADC/DMIC1", "ADCL Mux" },
+ { "Left Sidetone", "DMIC2", "DMIC2L" },
+ { "Right Sidetone", "ADC/DMIC1", "ADCR Mux" },
+ { "Right Sidetone", "DMIC2", "DMIC2R" },
+
+ /* Output stages */
+ { "Left Output Mixer", "DAC Switch", "DAC1L" },
+ { "Right Output Mixer", "DAC Switch", "DAC1R" },
+
+ { "SPKL", "DAC1 Switch", "DAC1L" },
+ { "SPKL", "DAC2 Switch", "DAC2L" },
+
+ { "SPKR", "DAC1 Switch", "DAC1R" },
+ { "SPKR", "DAC2 Switch", "DAC2R" },
+
+ { "Left Headphone Mux", "DAC", "DAC1L" },
+ { "Right Headphone Mux", "DAC", "DAC1R" },
+};
+
+static const struct snd_soc_dapm_route wm8994_lateclk_revd_intercon[] = {
+ { "DAC1L", NULL, "Late DAC1L Enable PGA" },
+ { "Late DAC1L Enable PGA", NULL, "DAC1L Mixer" },
+ { "DAC1R", NULL, "Late DAC1R Enable PGA" },
+ { "Late DAC1R Enable PGA", NULL, "DAC1R Mixer" },
+ { "DAC2L", NULL, "Late DAC2L Enable PGA" },
+ { "Late DAC2L Enable PGA", NULL, "AIF2DAC2L Mixer" },
+ { "DAC2R", NULL, "Late DAC2R Enable PGA" },
+ { "Late DAC2R Enable PGA", NULL, "AIF2DAC2R Mixer" }
+};
+
+static const struct snd_soc_dapm_route wm8994_lateclk_intercon[] = {
+ { "DAC1L", NULL, "DAC1L Mixer" },
+ { "DAC1R", NULL, "DAC1R Mixer" },
+ { "DAC2L", NULL, "AIF2DAC2L Mixer" },
+ { "DAC2R", NULL, "AIF2DAC2R Mixer" },
+};
+
+static const struct snd_soc_dapm_route wm8994_revd_intercon[] = {
+ { "AIF1DACDAT", NULL, "AIF2DACDAT" },
+ { "AIF2DACDAT", NULL, "AIF1DACDAT" },
+ { "AIF1ADCDAT", NULL, "AIF2ADCDAT" },
+ { "AIF2ADCDAT", NULL, "AIF1ADCDAT" },
+ { "MICBIAS1", NULL, "CLK_SYS" },
+ { "MICBIAS1", NULL, "MICBIAS Supply" },
+ { "MICBIAS2", NULL, "CLK_SYS" },
+ { "MICBIAS2", NULL, "MICBIAS Supply" },
+};
+
+static const struct snd_soc_dapm_route wm8994_intercon[] = {
+ { "AIF2DACL", NULL, "AIF2DAC Mux" },
+ { "AIF2DACR", NULL, "AIF2DAC Mux" },
+};
+
+static const struct snd_soc_dapm_route wm8958_intercon[] = {
+ { "AIF2DACL", NULL, "AIF2DACL Mux" },
+ { "AIF2DACR", NULL, "AIF2DACR Mux" },
+
+ { "AIF2DACL Mux", "AIF2", "AIF2DAC Mux" },
+ { "AIF2DACL Mux", "AIF3", "AIF3DACDAT" },
+ { "AIF2DACR Mux", "AIF2", "AIF2DAC Mux" },
+ { "AIF2DACR Mux", "AIF3", "AIF3DACDAT" },
+
+ { "Mono PCM Out Mux", "AIF2ADCL", "AIF2ADCL" },
+ { "Mono PCM Out Mux", "AIF2ADCR", "AIF2ADCR" },
+
+ { "AIF3ADC Mux", "Mono PCM", "Mono PCM Out Mux" },
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+struct fll_div {
+ u16 outdiv;
+ u16 n;
+ u16 k;
+ u16 clk_ref_div;
+ u16 fll_fratio;
+};
+
+static int wm8994_get_fll_config(struct fll_div *fll,
+ int freq_in, int freq_out)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ pr_debug("FLL input=%dHz, output=%dHz\n", freq_in, freq_out);
+
+ /* Scale the input frequency down to <= 13.5MHz */
+ fll->clk_ref_div = 0;
+ while (freq_in > 13500000) {
+ fll->clk_ref_div++;
+ freq_in /= 2;
+
+ if (fll->clk_ref_div > 3)
+ return -EINVAL;
+ }
+ pr_debug("CLK_REF_DIV=%d, Fref=%dHz\n", fll->clk_ref_div, freq_in);
+
+ /* Scale the output to give 90MHz<=Fvco<=100MHz */
+ fll->outdiv = 3;
+ while (freq_out * (fll->outdiv + 1) < 90000000) {
+ fll->outdiv++;
+ if (fll->outdiv > 63)
+ return -EINVAL;
+ }
+ freq_out *= fll->outdiv + 1;
+ pr_debug("OUTDIV=%d, Fvco=%dHz\n", fll->outdiv, freq_out);
+
+ if (freq_in > 1000000) {
+ fll->fll_fratio = 0;
+ } else if (freq_in > 256000) {
+ fll->fll_fratio = 1;
+ freq_in *= 2;
+ } else if (freq_in > 128000) {
+ fll->fll_fratio = 2;
+ freq_in *= 4;
+ } else if (freq_in > 64000) {
+ fll->fll_fratio = 3;
+ freq_in *= 8;
+ } else {
+ fll->fll_fratio = 4;
+ freq_in *= 16;
+ }
+ pr_debug("FLL_FRATIO=%d, Fref=%dHz\n", fll->fll_fratio, freq_in);
+
+ /* Now, calculate N.K */
+ Ndiv = freq_out / freq_in;
+
+ fll->n = Ndiv;
+ Nmod = freq_out % freq_in;
+ pr_debug("Nmod=%d\n", Nmod);
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, freq_in);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ fll->k = K / 10;
+
+ pr_debug("N=%x K=%x\n", fll->n, fll->k);
+
+ return 0;
+}
+
+static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int reg_offset, ret;
+ struct fll_div fll;
+ u16 reg, aif1, aif2;
+
+ aif1 = snd_soc_read(codec, WM8994_AIF1_CLOCKING_1)
+ & WM8994_AIF1CLK_ENA;
+
+ aif2 = snd_soc_read(codec, WM8994_AIF2_CLOCKING_1)
+ & WM8994_AIF2CLK_ENA;
+
+ switch (id) {
+ case WM8994_FLL1:
+ reg_offset = 0;
+ id = 0;
+ break;
+ case WM8994_FLL2:
+ reg_offset = 0x20;
+ id = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (src) {
+ case 0:
+ /* Allow no source specification when stopping */
+ if (freq_out)
+ return -EINVAL;
+ src = wm8994->fll[id].src;
+ break;
+ case WM8994_FLL_SRC_MCLK1:
+ case WM8994_FLL_SRC_MCLK2:
+ case WM8994_FLL_SRC_LRCLK:
+ case WM8994_FLL_SRC_BCLK:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Are we changing anything? */
+ if (wm8994->fll[id].src == src &&
+ wm8994->fll[id].in == freq_in && wm8994->fll[id].out == freq_out)
+ return 0;
+
+ /* If we're stopping the FLL redo the old config - no
+ * registers will actually be written but we avoid GCC flow
+ * analysis bugs spewing warnings.
+ */
+ if (freq_out)
+ ret = wm8994_get_fll_config(&fll, freq_in, freq_out);
+ else
+ ret = wm8994_get_fll_config(&fll, wm8994->fll[id].in,
+ wm8994->fll[id].out);
+ if (ret < 0)
+ return ret;
+
+ /* Gate the AIF clocks while we reclock */
+ snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
+ WM8994_AIF1CLK_ENA, 0);
+ snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
+ WM8994_AIF2CLK_ENA, 0);
+
+ /* We always need to disable the FLL while reconfiguring */
+ snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset,
+ WM8994_FLL1_ENA, 0);
+
+ reg = (fll.outdiv << WM8994_FLL1_OUTDIV_SHIFT) |
+ (fll.fll_fratio << WM8994_FLL1_FRATIO_SHIFT);
+ snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_2 + reg_offset,
+ WM8994_FLL1_OUTDIV_MASK |
+ WM8994_FLL1_FRATIO_MASK, reg);
+
+ snd_soc_write(codec, WM8994_FLL1_CONTROL_3 + reg_offset, fll.k);
+
+ snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_4 + reg_offset,
+ WM8994_FLL1_N_MASK,
+ fll.n << WM8994_FLL1_N_SHIFT);
+
+ snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset,
+ WM8994_FLL1_REFCLK_DIV_MASK |
+ WM8994_FLL1_REFCLK_SRC_MASK,
+ (fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) |
+ (src - 1));
+
+ /* Enable (with fractional mode if required) */
+ if (freq_out) {
+ if (fll.k)
+ reg = WM8994_FLL1_ENA | WM8994_FLL1_FRAC;
+ else
+ reg = WM8994_FLL1_ENA;
+ snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset,
+ WM8994_FLL1_ENA | WM8994_FLL1_FRAC,
+ reg);
+
+ msleep(5);
+ }
+
+ wm8994->fll[id].in = freq_in;
+ wm8994->fll[id].out = freq_out;
+ wm8994->fll[id].src = src;
+
+ /* Enable any gated AIF clocks */
+ snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
+ WM8994_AIF1CLK_ENA, aif1);
+ snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
+ WM8994_AIF2CLK_ENA, aif2);
+
+ configure_clock(codec);
+
+ return 0;
+}
+
+
+static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 };
+
+static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ return _wm8994_set_fll(dai->codec, id, src, freq_in, freq_out);
+}
+
+static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ switch (dai->id) {
+ case 1:
+ case 2:
+ break;
+
+ default:
+ /* AIF3 shares clocking with AIF1/2 */
+ return -EINVAL;
+ }
+
+ switch (clk_id) {
+ case WM8994_SYSCLK_MCLK1:
+ wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK1;
+ wm8994->mclk[0] = freq;
+ dev_dbg(dai->dev, "AIF%d using MCLK1 at %uHz\n",
+ dai->id, freq);
+ break;
+
+ case WM8994_SYSCLK_MCLK2:
+ /* TODO: Set GPIO AF */
+ wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK2;
+ wm8994->mclk[1] = freq;
+ dev_dbg(dai->dev, "AIF%d using MCLK2 at %uHz\n",
+ dai->id, freq);
+ break;
+
+ case WM8994_SYSCLK_FLL1:
+ wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_FLL1;
+ dev_dbg(dai->dev, "AIF%d using FLL1\n", dai->id);
+ break;
+
+ case WM8994_SYSCLK_FLL2:
+ wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_FLL2;
+ dev_dbg(dai->dev, "AIF%d using FLL2\n", dai->id);
+ break;
+
+ case WM8994_SYSCLK_OPCLK:
+ /* Special case - a division (times 10) is given and
+ * no effect on main clocking.
+ */
+ if (freq) {
+ for (i = 0; i < ARRAY_SIZE(opclk_divs); i++)
+ if (opclk_divs[i] == freq)
+ break;
+ if (i == ARRAY_SIZE(opclk_divs))
+ return -EINVAL;
+ snd_soc_update_bits(codec, WM8994_CLOCKING_2,
+ WM8994_OPCLK_DIV_MASK, i);
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2,
+ WM8994_OPCLK_ENA, WM8994_OPCLK_ENA);
+ } else {
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2,
+ WM8994_OPCLK_ENA, 0);
+ }
+
+ default:
+ return -EINVAL;
+ }
+
+ configure_clock(codec);
+
+ return 0;
+}
+
+static int wm8994_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8994 *control = codec->control_data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID=2x40k */
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_VMID_SEL_MASK, 0x2);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ pm_runtime_get_sync(codec->dev);
+
+ switch (control->type) {
+ case WM8994:
+ if (wm8994->revision < 4) {
+ /* Tweak DC servo and DSP
+ * configuration for improved
+ * performance. */
+ snd_soc_write(codec, 0x102, 0x3);
+ snd_soc_write(codec, 0x56, 0x3);
+ snd_soc_write(codec, 0x817, 0);
+ snd_soc_write(codec, 0x102, 0);
+ }
+ break;
+
+ case WM8958:
+ if (wm8994->revision == 0) {
+ /* Optimise performance for rev A */
+ snd_soc_write(codec, 0x102, 0x3);
+ snd_soc_write(codec, 0xcb, 0x81);
+ snd_soc_write(codec, 0x817, 0);
+ snd_soc_write(codec, 0x102, 0);
+
+ snd_soc_update_bits(codec,
+ WM8958_CHARGE_PUMP_2,
+ WM8958_CP_DISCH,
+ WM8958_CP_DISCH);
+ }
+ break;
+ }
+
+ /* Discharge LINEOUT1 & 2 */
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_1,
+ WM8994_LINEOUT1_DISCH |
+ WM8994_LINEOUT2_DISCH,
+ WM8994_LINEOUT1_DISCH |
+ WM8994_LINEOUT2_DISCH);
+
+ /* Startup bias, VMID ramp & buffer */
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+ WM8994_STARTUP_BIAS_ENA |
+ WM8994_VMID_BUF_ENA |
+ WM8994_VMID_RAMP_MASK,
+ WM8994_STARTUP_BIAS_ENA |
+ WM8994_VMID_BUF_ENA |
+ (0x11 << WM8994_VMID_RAMP_SHIFT));
+
+ /* Main bias enable, VMID=2x40k */
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_BIAS_ENA |
+ WM8994_VMID_SEL_MASK,
+ WM8994_BIAS_ENA | 0x2);
+
+ msleep(20);
+ }
+
+ /* VMID=2x500k */
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_VMID_SEL_MASK, 0x4);
+
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) {
+ /* Switch over to startup biases */
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+ WM8994_BIAS_SRC |
+ WM8994_STARTUP_BIAS_ENA |
+ WM8994_VMID_BUF_ENA |
+ WM8994_VMID_RAMP_MASK,
+ WM8994_BIAS_SRC |
+ WM8994_STARTUP_BIAS_ENA |
+ WM8994_VMID_BUF_ENA |
+ (1 << WM8994_VMID_RAMP_SHIFT));
+
+ /* Disable main biases */
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_BIAS_ENA |
+ WM8994_VMID_SEL_MASK, 0);
+
+ /* Discharge line */
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_1,
+ WM8994_LINEOUT1_DISCH |
+ WM8994_LINEOUT2_DISCH,
+ WM8994_LINEOUT1_DISCH |
+ WM8994_LINEOUT2_DISCH);
+
+ msleep(5);
+
+ /* Switch off startup biases */
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+ WM8994_BIAS_SRC |
+ WM8994_STARTUP_BIAS_ENA |
+ WM8994_VMID_BUF_ENA |
+ WM8994_VMID_RAMP_MASK, 0);
+
+ wm8994->cur_fw = NULL;
+
+ pm_runtime_put(codec->dev);
+ }
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int wm8994_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8994 *control = codec->control_data;
+ int ms_reg;
+ int aif1_reg;
+ int ms = 0;
+ int aif1 = 0;
+
+ switch (dai->id) {
+ case 1:
+ ms_reg = WM8994_AIF1_MASTER_SLAVE;
+ aif1_reg = WM8994_AIF1_CONTROL_1;
+ break;
+ case 2:
+ ms_reg = WM8994_AIF2_MASTER_SLAVE;
+ aif1_reg = WM8994_AIF2_CONTROL_1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ ms = WM8994_AIF1_MSTR;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ms |= WM8994_AIF1_LRCLK_FRC;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_B:
+ aif1 |= WM8994_AIF1_LRCLK_INV;
+ case SND_SOC_DAIFMT_DSP_A:
+ aif1 |= 0x18;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif1 |= 0x10;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif1 |= 0x8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8994_AIF1_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif1 |= WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8994_AIF1_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif1 |= WM8994_AIF1_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* The AIF2 format configuration needs to be mirrored to AIF3
+ * on WM8958 if it's in use so just do it all the time. */
+ if (control->type == WM8958 && dai->id == 2)
+ snd_soc_update_bits(codec, WM8958_AIF3_CONTROL_1,
+ WM8994_AIF1_LRCLK_INV |
+ WM8958_AIF3_FMT_MASK, aif1);
+
+ snd_soc_update_bits(codec, aif1_reg,
+ WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV |
+ WM8994_AIF1_FMT_MASK,
+ aif1);
+ snd_soc_update_bits(codec, ms_reg, WM8994_AIF1_MSTR,
+ ms);
+
+ return 0;
+}
+
+static struct {
+ int val, rate;
+} srs[] = {
+ { 0, 8000 },
+ { 1, 11025 },
+ { 2, 12000 },
+ { 3, 16000 },
+ { 4, 22050 },
+ { 5, 24000 },
+ { 6, 32000 },
+ { 7, 44100 },
+ { 8, 48000 },
+ { 9, 88200 },
+ { 10, 96000 },
+};
+
+static int fs_ratios[] = {
+ 64, 128, 192, 256, 348, 512, 768, 1024, 1408, 1536
+};
+
+static int bclk_divs[] = {
+ 10, 15, 20, 30, 40, 50, 60, 80, 110, 120, 160, 220, 240, 320, 440, 480,
+ 640, 880, 960, 1280, 1760, 1920
+};
+
+static int wm8994_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8994 *control = codec->control_data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int aif1_reg;
+ int aif2_reg;
+ int bclk_reg;
+ int lrclk_reg;
+ int rate_reg;
+ int aif1 = 0;
+ int aif2 = 0;
+ int bclk = 0;
+ int lrclk = 0;
+ int rate_val = 0;
+ int id = dai->id - 1;
+
+ int i, cur_val, best_val, bclk_rate, best;
+
+ switch (dai->id) {
+ case 1:
+ aif1_reg = WM8994_AIF1_CONTROL_1;
+ aif2_reg = WM8994_AIF1_CONTROL_2;
+ bclk_reg = WM8994_AIF1_BCLK;
+ rate_reg = WM8994_AIF1_RATE;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
+ wm8994->lrclk_shared[0]) {
+ lrclk_reg = WM8994_AIF1DAC_LRCLK;
+ } else {
+ lrclk_reg = WM8994_AIF1ADC_LRCLK;
+ dev_dbg(codec->dev, "AIF1 using split LRCLK\n");
+ }
+ break;
+ case 2:
+ aif1_reg = WM8994_AIF2_CONTROL_1;
+ aif2_reg = WM8994_AIF2_CONTROL_2;
+ bclk_reg = WM8994_AIF2_BCLK;
+ rate_reg = WM8994_AIF2_RATE;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
+ wm8994->lrclk_shared[1]) {
+ lrclk_reg = WM8994_AIF2DAC_LRCLK;
+ } else {
+ lrclk_reg = WM8994_AIF2ADC_LRCLK;
+ dev_dbg(codec->dev, "AIF2 using split LRCLK\n");
+ }
+ break;
+ case 3:
+ switch (control->type) {
+ case WM8958:
+ aif1_reg = WM8958_AIF3_CONTROL_1;
+ break;
+ default:
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+
+ bclk_rate = params_rate(params) * 2;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bclk_rate *= 16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ bclk_rate *= 20;
+ aif1 |= 0x20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ bclk_rate *= 24;
+ aif1 |= 0x40;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bclk_rate *= 32;
+ aif1 |= 0x60;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Try to find an appropriate sample rate; look for an exact match. */
+ for (i = 0; i < ARRAY_SIZE(srs); i++)
+ if (srs[i].rate == params_rate(params))
+ break;
+ if (i == ARRAY_SIZE(srs))
+ return -EINVAL;
+ rate_val |= srs[i].val << WM8994_AIF1_SR_SHIFT;
+
+ dev_dbg(dai->dev, "Sample rate is %dHz\n", srs[i].rate);
+ dev_dbg(dai->dev, "AIF%dCLK is %dHz, target BCLK %dHz\n",
+ dai->id, wm8994->aifclk[id], bclk_rate);
+
+ if (params_channels(params) == 1 &&
+ (snd_soc_read(codec, aif1_reg) & 0x18) == 0x18)
+ aif2 |= WM8994_AIF1_MONO;
+
+ if (wm8994->aifclk[id] == 0) {
+ dev_err(dai->dev, "AIF%dCLK not configured\n", dai->id);
+ return -EINVAL;
+ }
+
+ /* AIFCLK/fs ratio; look for a close match in either direction */
+ best = 0;
+ best_val = abs((fs_ratios[0] * params_rate(params))
+ - wm8994->aifclk[id]);
+ for (i = 1; i < ARRAY_SIZE(fs_ratios); i++) {
+ cur_val = abs((fs_ratios[i] * params_rate(params))
+ - wm8994->aifclk[id]);
+ if (cur_val >= best_val)
+ continue;
+ best = i;
+ best_val = cur_val;
+ }
+ dev_dbg(dai->dev, "Selected AIF%dCLK/fs = %d\n",
+ dai->id, fs_ratios[best]);
+ rate_val |= best;
+
+ /* We may not get quite the right frequency if using
+ * approximate clocks so look for the closest match that is
+ * higher than the target (we need to ensure that there enough
+ * BCLKs to clock out the samples).
+ */
+ best = 0;
+ for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+ cur_val = (wm8994->aifclk[id] * 10 / bclk_divs[i]) - bclk_rate;
+ if (cur_val < 0) /* BCLK table is sorted */
+ break;
+ best = i;
+ }
+ bclk_rate = wm8994->aifclk[id] * 10 / bclk_divs[best];
+ dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n",
+ bclk_divs[best], bclk_rate);
+ bclk |= best << WM8994_AIF1_BCLK_DIV_SHIFT;
+
+ lrclk = bclk_rate / params_rate(params);
+ dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n",
+ lrclk, bclk_rate / lrclk);
+
+ snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1);
+ snd_soc_update_bits(codec, aif2_reg, WM8994_AIF1_MONO, aif2);
+ snd_soc_update_bits(codec, bclk_reg, WM8994_AIF1_BCLK_DIV_MASK, bclk);
+ snd_soc_update_bits(codec, lrclk_reg, WM8994_AIF1DAC_RATE_MASK,
+ lrclk);
+ snd_soc_update_bits(codec, rate_reg, WM8994_AIF1_SR_MASK |
+ WM8994_AIF1CLK_RATE_MASK, rate_val);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ switch (dai->id) {
+ case 1:
+ wm8994->dac_rates[0] = params_rate(params);
+ wm8994_set_retune_mobile(codec, 0);
+ wm8994_set_retune_mobile(codec, 1);
+ break;
+ case 2:
+ wm8994->dac_rates[1] = params_rate(params);
+ wm8994_set_retune_mobile(codec, 2);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int wm8994_aif3_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8994 *control = codec->control_data;
+ int aif1_reg;
+ int aif1 = 0;
+
+ switch (dai->id) {
+ case 3:
+ switch (control->type) {
+ case WM8958:
+ aif1_reg = WM8958_AIF3_CONTROL_1;
+ break;
+ default:
+ return 0;
+ }
+ default:
+ return 0;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ aif1 |= 0x20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ aif1 |= 0x40;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ aif1 |= 0x60;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1);
+}
+
+static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int mute_reg;
+ int reg;
+
+ switch (codec_dai->id) {
+ case 1:
+ mute_reg = WM8994_AIF1_DAC1_FILTERS_1;
+ break;
+ case 2:
+ mute_reg = WM8994_AIF2_DAC_FILTERS_1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (mute)
+ reg = WM8994_AIF1DAC1_MUTE;
+ else
+ reg = 0;
+
+ snd_soc_update_bits(codec, mute_reg, WM8994_AIF1DAC1_MUTE, reg);
+
+ return 0;
+}
+
+static int wm8994_set_tristate(struct snd_soc_dai *codec_dai, int tristate)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int reg, val, mask;
+
+ switch (codec_dai->id) {
+ case 1:
+ reg = WM8994_AIF1_MASTER_SLAVE;
+ mask = WM8994_AIF1_TRI;
+ break;
+ case 2:
+ reg = WM8994_AIF2_MASTER_SLAVE;
+ mask = WM8994_AIF2_TRI;
+ break;
+ case 3:
+ reg = WM8994_POWER_MANAGEMENT_6;
+ mask = WM8994_AIF3_TRI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (tristate)
+ val = mask;
+ else
+ val = 0;
+
+ return snd_soc_update_bits(codec, reg, mask, val);
+}
+
+#define WM8994_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8994_aif1_dai_ops = {
+ .set_sysclk = wm8994_set_dai_sysclk,
+ .set_fmt = wm8994_set_dai_fmt,
+ .hw_params = wm8994_hw_params,
+ .digital_mute = wm8994_aif_mute,
+ .set_pll = wm8994_set_fll,
+ .set_tristate = wm8994_set_tristate,
+};
+
+static struct snd_soc_dai_ops wm8994_aif2_dai_ops = {
+ .set_sysclk = wm8994_set_dai_sysclk,
+ .set_fmt = wm8994_set_dai_fmt,
+ .hw_params = wm8994_hw_params,
+ .digital_mute = wm8994_aif_mute,
+ .set_pll = wm8994_set_fll,
+ .set_tristate = wm8994_set_tristate,
+};
+
+static struct snd_soc_dai_ops wm8994_aif3_dai_ops = {
+ .hw_params = wm8994_aif3_hw_params,
+ .set_tristate = wm8994_set_tristate,
+};
+
+static struct snd_soc_dai_driver wm8994_dai[] = {
+ {
+ .name = "wm8994-aif1",
+ .id = 1,
+ .playback = {
+ .stream_name = "AIF1 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8994_RATES,
+ .formats = WM8994_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AIF1 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8994_RATES,
+ .formats = WM8994_FORMATS,
+ },
+ .ops = &wm8994_aif1_dai_ops,
+ },
+ {
+ .name = "wm8994-aif2",
+ .id = 2,
+ .playback = {
+ .stream_name = "AIF2 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8994_RATES,
+ .formats = WM8994_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AIF2 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8994_RATES,
+ .formats = WM8994_FORMATS,
+ },
+ .ops = &wm8994_aif2_dai_ops,
+ },
+ {
+ .name = "wm8994-aif3",
+ .id = 3,
+ .playback = {
+ .stream_name = "AIF3 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8994_RATES,
+ .formats = WM8994_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AIF3 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8994_RATES,
+ .formats = WM8994_FORMATS,
+ },
+ .ops = &wm8994_aif3_dai_ops,
+ }
+};
+
+#ifdef CONFIG_PM
+static int wm8994_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994 *control = codec->control_data;
+ int i, ret;
+
+ pm_runtime_disable(codec->dev);
+
+ switch (control->type) {
+ case WM8994:
+ snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, 0);
+ break;
+ case WM8958:
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+ break;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) {
+ memcpy(&wm8994->fll_suspend[i], &wm8994->fll[i],
+ sizeof(struct wm8994_fll_config));
+ ret = _wm8994_set_fll(codec, i + 1, 0, 0, 0);
+ if (ret < 0)
+ dev_warn(codec->dev, "Failed to stop FLL%d: %d\n",
+ i + 1, ret);
+ }
+
+ wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8994_resume(struct snd_soc_codec *codec)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994 *control = codec->control_data;
+ int i, ret;
+ unsigned int val, mask;
+
+ if (wm8994->revision < 4) {
+ /* force a HW read */
+ val = wm8994_reg_read(codec->control_data,
+ WM8994_POWER_MANAGEMENT_5);
+
+ /* modify the cache only */
+ codec->cache_only = 1;
+ mask = WM8994_DAC1R_ENA | WM8994_DAC1L_ENA |
+ WM8994_DAC2R_ENA | WM8994_DAC2L_ENA;
+ val &= mask;
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5,
+ mask, val);
+ codec->cache_only = 0;
+ }
+
+ /* Restore the registers */
+ ret = snd_soc_cache_sync(codec);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to sync cache: %d\n", ret);
+
+ wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) {
+ if (!wm8994->fll_suspend[i].out)
+ continue;
+
+ ret = _wm8994_set_fll(codec, i + 1,
+ wm8994->fll_suspend[i].src,
+ wm8994->fll_suspend[i].in,
+ wm8994->fll_suspend[i].out);
+ if (ret < 0)
+ dev_warn(codec->dev, "Failed to restore FLL%d: %d\n",
+ i + 1, ret);
+ }
+
+ switch (control->type) {
+ case WM8994:
+ if (wm8994->micdet[0].jack || wm8994->micdet[1].jack)
+ snd_soc_update_bits(codec, WM8994_MICBIAS,
+ WM8994_MICD_ENA, WM8994_MICD_ENA);
+ break;
+ case WM8958:
+ if (wm8994->jack_cb)
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, WM8958_MICD_ENA);
+ break;
+ }
+
+ pm_runtime_enable(codec->dev);
+
+ return 0;
+}
+#else
+#define wm8994_suspend NULL
+#define wm8994_resume NULL
+#endif
+
+static void wm8994_handle_retune_mobile_pdata(struct wm8994_priv *wm8994)
+{
+ struct snd_soc_codec *codec = wm8994->codec;
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ struct snd_kcontrol_new controls[] = {
+ SOC_ENUM_EXT("AIF1.1 EQ Mode",
+ wm8994->retune_mobile_enum,
+ wm8994_get_retune_mobile_enum,
+ wm8994_put_retune_mobile_enum),
+ SOC_ENUM_EXT("AIF1.2 EQ Mode",
+ wm8994->retune_mobile_enum,
+ wm8994_get_retune_mobile_enum,
+ wm8994_put_retune_mobile_enum),
+ SOC_ENUM_EXT("AIF2 EQ Mode",
+ wm8994->retune_mobile_enum,
+ wm8994_get_retune_mobile_enum,
+ wm8994_put_retune_mobile_enum),
+ };
+ int ret, i, j;
+ const char **t;
+
+ /* We need an array of texts for the enum API but the number
+ * of texts is likely to be less than the number of
+ * configurations due to the sample rate dependency of the
+ * configurations. */
+ wm8994->num_retune_mobile_texts = 0;
+ wm8994->retune_mobile_texts = NULL;
+ for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+ for (j = 0; j < wm8994->num_retune_mobile_texts; j++) {
+ if (strcmp(pdata->retune_mobile_cfgs[i].name,
+ wm8994->retune_mobile_texts[j]) == 0)
+ break;
+ }
+
+ if (j != wm8994->num_retune_mobile_texts)
+ continue;
+
+ /* Expand the array... */
+ t = krealloc(wm8994->retune_mobile_texts,
+ sizeof(char *) *
+ (wm8994->num_retune_mobile_texts + 1),
+ GFP_KERNEL);
+ if (t == NULL)
+ continue;
+
+ /* ...store the new entry... */
+ t[wm8994->num_retune_mobile_texts] =
+ pdata->retune_mobile_cfgs[i].name;
+
+ /* ...and remember the new version. */
+ wm8994->num_retune_mobile_texts++;
+ wm8994->retune_mobile_texts = t;
+ }
+
+ dev_dbg(codec->dev, "Allocated %d unique ReTune Mobile names\n",
+ wm8994->num_retune_mobile_texts);
+
+ wm8994->retune_mobile_enum.max = wm8994->num_retune_mobile_texts;
+ wm8994->retune_mobile_enum.texts = wm8994->retune_mobile_texts;
+
+ ret = snd_soc_add_controls(wm8994->codec, controls,
+ ARRAY_SIZE(controls));
+ if (ret != 0)
+ dev_err(wm8994->codec->dev,
+ "Failed to add ReTune Mobile controls: %d\n", ret);
+}
+
+static void wm8994_handle_pdata(struct wm8994_priv *wm8994)
+{
+ struct snd_soc_codec *codec = wm8994->codec;
+ struct wm8994_pdata *pdata = wm8994->pdata;
+ int ret, i;
+
+ if (!pdata)
+ return;
+
+ wm_hubs_handle_analogue_pdata(codec, pdata->lineout1_diff,
+ pdata->lineout2_diff,
+ pdata->lineout1fb,
+ pdata->lineout2fb,
+ pdata->jd_scthr,
+ pdata->jd_thr,
+ pdata->micbias1_lvl,
+ pdata->micbias2_lvl);
+
+ dev_dbg(codec->dev, "%d DRC configurations\n", pdata->num_drc_cfgs);
+
+ if (pdata->num_drc_cfgs) {
+ struct snd_kcontrol_new controls[] = {
+ SOC_ENUM_EXT("AIF1DRC1 Mode", wm8994->drc_enum,
+ wm8994_get_drc_enum, wm8994_put_drc_enum),
+ SOC_ENUM_EXT("AIF1DRC2 Mode", wm8994->drc_enum,
+ wm8994_get_drc_enum, wm8994_put_drc_enum),
+ SOC_ENUM_EXT("AIF2DRC Mode", wm8994->drc_enum,
+ wm8994_get_drc_enum, wm8994_put_drc_enum),
+ };
+
+ /* We need an array of texts for the enum API */
+ wm8994->drc_texts = kmalloc(sizeof(char *)
+ * pdata->num_drc_cfgs, GFP_KERNEL);
+ if (!wm8994->drc_texts) {
+ dev_err(wm8994->codec->dev,
+ "Failed to allocate %d DRC config texts\n",
+ pdata->num_drc_cfgs);
+ return;
+ }
+
+ for (i = 0; i < pdata->num_drc_cfgs; i++)
+ wm8994->drc_texts[i] = pdata->drc_cfgs[i].name;
+
+ wm8994->drc_enum.max = pdata->num_drc_cfgs;
+ wm8994->drc_enum.texts = wm8994->drc_texts;
+
+ ret = snd_soc_add_controls(wm8994->codec, controls,
+ ARRAY_SIZE(controls));
+ if (ret != 0)
+ dev_err(wm8994->codec->dev,
+ "Failed to add DRC mode controls: %d\n", ret);
+
+ for (i = 0; i < WM8994_NUM_DRC; i++)
+ wm8994_set_drc(codec, i);
+ }
+
+ dev_dbg(codec->dev, "%d ReTune Mobile configurations\n",
+ pdata->num_retune_mobile_cfgs);
+
+ if (pdata->num_retune_mobile_cfgs)
+ wm8994_handle_retune_mobile_pdata(wm8994);
+ else
+ snd_soc_add_controls(wm8994->codec, wm8994_eq_controls,
+ ARRAY_SIZE(wm8994_eq_controls));
+
+ for (i = 0; i < ARRAY_SIZE(pdata->micbias); i++) {
+ if (pdata->micbias[i]) {
+ snd_soc_write(codec, WM8958_MICBIAS1 + i,
+ pdata->micbias[i] & 0xffff);
+ }
+ }
+}
+
+/**
+ * wm8994_mic_detect - Enable microphone detection via the WM8994 IRQ
+ *
+ * @codec: WM8994 codec
+ * @jack: jack to report detection events on
+ * @micbias: microphone bias to detect on
+ * @det: value to report for presence detection
+ * @shrt: value to report for short detection
+ *
+ * Enable microphone detection via IRQ on the WM8994. If GPIOs are
+ * being used to bring out signals to the processor then only platform
+ * data configuration is needed for WM8994 and processor GPIOs should
+ * be configured using snd_soc_jack_add_gpios() instead.
+ *
+ * Configuration of detection levels is available via the micbias1_lvl
+ * and micbias2_lvl platform data members.
+ */
+int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+ int micbias, int det, int shrt)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994_micdet *micdet;
+ struct wm8994 *control = codec->control_data;
+ int reg;
+
+ if (control->type != WM8994)
+ return -EINVAL;
+
+ switch (micbias) {
+ case 1:
+ micdet = &wm8994->micdet[0];
+ break;
+ case 2:
+ micdet = &wm8994->micdet[1];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(codec->dev, "Configuring microphone detection on %d: %x %x\n",
+ micbias, det, shrt);
+
+ /* Store the configuration */
+ micdet->jack = jack;
+ micdet->det = det;
+ micdet->shrt = shrt;
+
+ /* If either of the jacks is set up then enable detection */
+ if (wm8994->micdet[0].jack || wm8994->micdet[1].jack)
+ reg = WM8994_MICD_ENA;
+ else
+ reg = 0;
+
+ snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, reg);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8994_mic_detect);
+
+static irqreturn_t wm8994_mic_irq(int irq, void *data)
+{
+ struct wm8994_priv *priv = data;
+ struct snd_soc_codec *codec = priv->codec;
+ int reg;
+ int report;
+
+#ifndef CONFIG_SND_SOC_WM8994_MODULE
+ trace_snd_soc_jack_irq(dev_name(codec->dev));
+#endif
+
+ reg = snd_soc_read(codec, WM8994_INTERRUPT_RAW_STATUS_2);
+ if (reg < 0) {
+ dev_err(codec->dev, "Failed to read microphone status: %d\n",
+ reg);
+ return IRQ_HANDLED;
+ }
+
+ dev_dbg(codec->dev, "Microphone status: %x\n", reg);
+
+ report = 0;
+ if (reg & WM8994_MIC1_DET_STS)
+ report |= priv->micdet[0].det;
+ if (reg & WM8994_MIC1_SHRT_STS)
+ report |= priv->micdet[0].shrt;
+ snd_soc_jack_report(priv->micdet[0].jack, report,
+ priv->micdet[0].det | priv->micdet[0].shrt);
+
+ report = 0;
+ if (reg & WM8994_MIC2_DET_STS)
+ report |= priv->micdet[1].det;
+ if (reg & WM8994_MIC2_SHRT_STS)
+ report |= priv->micdet[1].shrt;
+ snd_soc_jack_report(priv->micdet[1].jack, report,
+ priv->micdet[1].det | priv->micdet[1].shrt);
+
+ return IRQ_HANDLED;
+}
+
+/* Default microphone detection handler for WM8958 - the user can
+ * override this if they wish.
+ */
+static void wm8958_default_micdet(u16 status, void *data)
+{
+ struct snd_soc_codec *codec = data;
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ int report = 0;
+
+ /* If nothing present then clear our statuses */
+ if (!(status & WM8958_MICD_STS))
+ goto done;
+
+ report = SND_JACK_MICROPHONE;
+
+ /* Everything else is buttons; just assign slots */
+ if (status & 0x1c)
+ report |= SND_JACK_BTN_0;
+
+done:
+ snd_soc_jack_report(wm8994->micdet[0].jack, report,
+ SND_JACK_BTN_0 | SND_JACK_MICROPHONE);
+}
+
+/**
+ * wm8958_mic_detect - Enable microphone detection via the WM8958 IRQ
+ *
+ * @codec: WM8958 codec
+ * @jack: jack to report detection events on
+ *
+ * Enable microphone detection functionality for the WM8958. By
+ * default simple detection which supports the detection of up to 6
+ * buttons plus video and microphone functionality is supported.
+ *
+ * The WM8958 has an advanced jack detection facility which is able to
+ * support complex accessory detection, especially when used in
+ * conjunction with external circuitry. In order to provide maximum
+ * flexiblity a callback is provided which allows a completely custom
+ * detection algorithm.
+ */
+int wm8958_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+ wm8958_micdet_cb cb, void *cb_data)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994 *control = codec->control_data;
+
+ if (control->type != WM8958)
+ return -EINVAL;
+
+ if (jack) {
+ if (!cb) {
+ dev_dbg(codec->dev, "Using default micdet callback\n");
+ cb = wm8958_default_micdet;
+ cb_data = codec;
+ }
+
+ wm8994->micdet[0].jack = jack;
+ wm8994->jack_cb = cb;
+ wm8994->jack_cb_data = cb_data;
+
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, WM8958_MICD_ENA);
+ } else {
+ snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
+ WM8958_MICD_ENA, 0);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8958_mic_detect);
+
+static irqreturn_t wm8958_mic_irq(int irq, void *data)
+{
+ struct wm8994_priv *wm8994 = data;
+ struct snd_soc_codec *codec = wm8994->codec;
+ int reg;
+
+ reg = snd_soc_read(codec, WM8958_MIC_DETECT_3);
+ if (reg < 0) {
+ dev_err(codec->dev, "Failed to read mic detect status: %d\n",
+ reg);
+ return IRQ_NONE;
+ }
+
+ if (!(reg & WM8958_MICD_VALID)) {
+ dev_dbg(codec->dev, "Mic detect data not valid\n");
+ goto out;
+ }
+
+#ifndef CONFIG_SND_SOC_WM8994_MODULE
+ trace_snd_soc_jack_irq(dev_name(codec->dev));
+#endif
+
+ if (wm8994->jack_cb)
+ wm8994->jack_cb(reg, wm8994->jack_cb_data);
+ else
+ dev_warn(codec->dev, "Accessory detection with no callback\n");
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int wm8994_codec_probe(struct snd_soc_codec *codec)
+{
+ struct wm8994 *control;
+ struct wm8994_priv *wm8994;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret, i;
+
+ codec->control_data = dev_get_drvdata(codec->dev->parent);
+ control = codec->control_data;
+
+ wm8994 = kzalloc(sizeof(struct wm8994_priv), GFP_KERNEL);
+ if (wm8994 == NULL)
+ return -ENOMEM;
+ snd_soc_codec_set_drvdata(codec, wm8994);
+
+ wm8994->pdata = dev_get_platdata(codec->dev->parent);
+ wm8994->codec = codec;
+
+ if (wm8994->pdata && wm8994->pdata->micdet_irq)
+ wm8994->micdet_irq = wm8994->pdata->micdet_irq;
+ else if (wm8994->pdata && wm8994->pdata->irq_base)
+ wm8994->micdet_irq = wm8994->pdata->irq_base +
+ WM8994_IRQ_MIC1_DET;
+
+ pm_runtime_enable(codec->dev);
+ pm_runtime_resume(codec->dev);
+
+ /* Read our current status back from the chip - we don't want to
+ * reset as this may interfere with the GPIO or LDO operation. */
+ for (i = 0; i < WM8994_CACHE_SIZE; i++) {
+ if (!wm8994_readable(codec, i) || wm8994_volatile(codec, i))
+ continue;
+
+ ret = wm8994_reg_read(codec->control_data, i);
+ if (ret <= 0)
+ continue;
+
+ ret = snd_soc_cache_write(codec, i, ret);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to initialise cache for 0x%x: %d\n",
+ i, ret);
+ goto err;
+ }
+ }
+
+ /* Set revision-specific configuration */
+ wm8994->revision = snd_soc_read(codec, WM8994_CHIP_REVISION);
+ switch (control->type) {
+ case WM8994:
+ switch (wm8994->revision) {
+ case 2:
+ case 3:
+ wm8994->hubs.dcs_codes = -5;
+ wm8994->hubs.hp_startup_mode = 1;
+ wm8994->hubs.dcs_readback_mode = 1;
+ break;
+ default:
+ wm8994->hubs.dcs_readback_mode = 1;
+ break;
+ }
+
+ case WM8958:
+ wm8994->hubs.dcs_readback_mode = 1;
+ break;
+
+ default:
+ break;
+ }
+
+ switch (control->type) {
+ case WM8994:
+ if (wm8994->micdet_irq) {
+ ret = request_threaded_irq(wm8994->micdet_irq, NULL,
+ wm8994_mic_irq,
+ IRQF_TRIGGER_RISING,
+ "Mic1 detect",
+ wm8994);
+ if (ret != 0)
+ dev_warn(codec->dev,
+ "Failed to request Mic1 detect IRQ: %d\n",
+ ret);
+ }
+
+ ret = wm8994_request_irq(codec->control_data,
+ WM8994_IRQ_MIC1_SHRT,
+ wm8994_mic_irq, "Mic 1 short",
+ wm8994);
+ if (ret != 0)
+ dev_warn(codec->dev,
+ "Failed to request Mic1 short IRQ: %d\n",
+ ret);
+
+ ret = wm8994_request_irq(codec->control_data,
+ WM8994_IRQ_MIC2_DET,
+ wm8994_mic_irq, "Mic 2 detect",
+ wm8994);
+ if (ret != 0)
+ dev_warn(codec->dev,
+ "Failed to request Mic2 detect IRQ: %d\n",
+ ret);
+
+ ret = wm8994_request_irq(codec->control_data,
+ WM8994_IRQ_MIC2_SHRT,
+ wm8994_mic_irq, "Mic 2 short",
+ wm8994);
+ if (ret != 0)
+ dev_warn(codec->dev,
+ "Failed to request Mic2 short IRQ: %d\n",
+ ret);
+ break;
+
+ case WM8958:
+ if (wm8994->micdet_irq) {
+ ret = request_threaded_irq(wm8994->micdet_irq, NULL,
+ wm8958_mic_irq,
+ IRQF_TRIGGER_RISING,
+ "Mic detect",
+ wm8994);
+ if (ret != 0)
+ dev_warn(codec->dev,
+ "Failed to request Mic detect IRQ: %d\n",
+ ret);
+ }
+ }
+
+ /* Remember if AIFnLRCLK is configured as a GPIO. This should be
+ * configured on init - if a system wants to do this dynamically
+ * at runtime we can deal with that then.
+ */
+ ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_1);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read GPIO1 state: %d\n", ret);
+ goto err_irq;
+ }
+ if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) {
+ wm8994->lrclk_shared[0] = 1;
+ wm8994_dai[0].symmetric_rates = 1;
+ } else {
+ wm8994->lrclk_shared[0] = 0;
+ }
+
+ ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_6);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read GPIO6 state: %d\n", ret);
+ goto err_irq;
+ }
+ if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) {
+ wm8994->lrclk_shared[1] = 1;
+ wm8994_dai[1].symmetric_rates = 1;
+ } else {
+ wm8994->lrclk_shared[1] = 0;
+ }
+
+ wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Latch volume updates (right only; we always do left then right). */
+ snd_soc_update_bits(codec, WM8994_AIF1_DAC1_LEFT_VOLUME,
+ WM8994_AIF1DAC1_VU, WM8994_AIF1DAC1_VU);
+ snd_soc_update_bits(codec, WM8994_AIF1_DAC1_RIGHT_VOLUME,
+ WM8994_AIF1DAC1_VU, WM8994_AIF1DAC1_VU);
+ snd_soc_update_bits(codec, WM8994_AIF1_DAC2_LEFT_VOLUME,
+ WM8994_AIF1DAC2_VU, WM8994_AIF1DAC2_VU);
+ snd_soc_update_bits(codec, WM8994_AIF1_DAC2_RIGHT_VOLUME,
+ WM8994_AIF1DAC2_VU, WM8994_AIF1DAC2_VU);
+ snd_soc_update_bits(codec, WM8994_AIF2_DAC_LEFT_VOLUME,
+ WM8994_AIF2DAC_VU, WM8994_AIF2DAC_VU);
+ snd_soc_update_bits(codec, WM8994_AIF2_DAC_RIGHT_VOLUME,
+ WM8994_AIF2DAC_VU, WM8994_AIF2DAC_VU);
+ snd_soc_update_bits(codec, WM8994_AIF1_ADC1_LEFT_VOLUME,
+ WM8994_AIF1ADC1_VU, WM8994_AIF1ADC1_VU);
+ snd_soc_update_bits(codec, WM8994_AIF1_ADC1_RIGHT_VOLUME,
+ WM8994_AIF1ADC1_VU, WM8994_AIF1ADC1_VU);
+ snd_soc_update_bits(codec, WM8994_AIF1_ADC2_LEFT_VOLUME,
+ WM8994_AIF1ADC2_VU, WM8994_AIF1ADC2_VU);
+ snd_soc_update_bits(codec, WM8994_AIF1_ADC2_RIGHT_VOLUME,
+ WM8994_AIF1ADC2_VU, WM8994_AIF1ADC2_VU);
+ snd_soc_update_bits(codec, WM8994_AIF2_ADC_LEFT_VOLUME,
+ WM8994_AIF2ADC_VU, WM8994_AIF1ADC2_VU);
+ snd_soc_update_bits(codec, WM8994_AIF2_ADC_RIGHT_VOLUME,
+ WM8994_AIF2ADC_VU, WM8994_AIF1ADC2_VU);
+ snd_soc_update_bits(codec, WM8994_DAC1_LEFT_VOLUME,
+ WM8994_DAC1_VU, WM8994_DAC1_VU);
+ snd_soc_update_bits(codec, WM8994_DAC1_RIGHT_VOLUME,
+ WM8994_DAC1_VU, WM8994_DAC1_VU);
+ snd_soc_update_bits(codec, WM8994_DAC2_LEFT_VOLUME,
+ WM8994_DAC2_VU, WM8994_DAC2_VU);
+ snd_soc_update_bits(codec, WM8994_DAC2_RIGHT_VOLUME,
+ WM8994_DAC2_VU, WM8994_DAC2_VU);
+
+ /* Set the low bit of the 3D stereo depth so TLV matches */
+ snd_soc_update_bits(codec, WM8994_AIF1_DAC1_FILTERS_2,
+ 1 << WM8994_AIF1DAC1_3D_GAIN_SHIFT,
+ 1 << WM8994_AIF1DAC1_3D_GAIN_SHIFT);
+ snd_soc_update_bits(codec, WM8994_AIF1_DAC2_FILTERS_2,
+ 1 << WM8994_AIF1DAC2_3D_GAIN_SHIFT,
+ 1 << WM8994_AIF1DAC2_3D_GAIN_SHIFT);
+ snd_soc_update_bits(codec, WM8994_AIF2_DAC_FILTERS_2,
+ 1 << WM8994_AIF2DAC_3D_GAIN_SHIFT,
+ 1 << WM8994_AIF2DAC_3D_GAIN_SHIFT);
+
+ /* Unconditionally enable AIF1 ADC TDM mode; it only affects
+ * behaviour on idle TDM clock cycles. */
+ snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1,
+ WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM);
+
+ wm8994_update_class_w(codec);
+
+ wm8994_handle_pdata(wm8994);
+
+ wm_hubs_add_analogue_controls(codec);
+ snd_soc_add_controls(codec, wm8994_snd_controls,
+ ARRAY_SIZE(wm8994_snd_controls));
+ snd_soc_dapm_new_controls(dapm, wm8994_dapm_widgets,
+ ARRAY_SIZE(wm8994_dapm_widgets));
+
+ switch (control->type) {
+ case WM8994:
+ snd_soc_dapm_new_controls(dapm, wm8994_specific_dapm_widgets,
+ ARRAY_SIZE(wm8994_specific_dapm_widgets));
+ if (wm8994->revision < 4) {
+ snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets,
+ ARRAY_SIZE(wm8994_lateclk_revd_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_adc_revd_widgets,
+ ARRAY_SIZE(wm8994_adc_revd_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_dac_revd_widgets,
+ ARRAY_SIZE(wm8994_dac_revd_widgets));
+ } else {
+ snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets,
+ ARRAY_SIZE(wm8994_lateclk_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets,
+ ARRAY_SIZE(wm8994_adc_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets,
+ ARRAY_SIZE(wm8994_dac_widgets));
+ }
+ break;
+ case WM8958:
+ snd_soc_add_controls(codec, wm8958_snd_controls,
+ ARRAY_SIZE(wm8958_snd_controls));
+ snd_soc_dapm_new_controls(dapm, wm8958_dapm_widgets,
+ ARRAY_SIZE(wm8958_dapm_widgets));
+ if (wm8994->revision < 1) {
+ snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets,
+ ARRAY_SIZE(wm8994_lateclk_revd_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_adc_revd_widgets,
+ ARRAY_SIZE(wm8994_adc_revd_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_dac_revd_widgets,
+ ARRAY_SIZE(wm8994_dac_revd_widgets));
+ } else {
+ snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets,
+ ARRAY_SIZE(wm8994_lateclk_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets,
+ ARRAY_SIZE(wm8994_adc_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets,
+ ARRAY_SIZE(wm8994_dac_widgets));
+ }
+ break;
+ }
+
+
+ wm_hubs_add_analogue_routes(codec, 0, 0);
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ switch (control->type) {
+ case WM8994:
+ snd_soc_dapm_add_routes(dapm, wm8994_intercon,
+ ARRAY_SIZE(wm8994_intercon));
+
+ if (wm8994->revision < 4) {
+ snd_soc_dapm_add_routes(dapm, wm8994_revd_intercon,
+ ARRAY_SIZE(wm8994_revd_intercon));
+ snd_soc_dapm_add_routes(dapm, wm8994_lateclk_revd_intercon,
+ ARRAY_SIZE(wm8994_lateclk_revd_intercon));
+ } else {
+ snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon,
+ ARRAY_SIZE(wm8994_lateclk_intercon));
+ }
+ break;
+ case WM8958:
+ if (wm8994->revision < 1) {
+ snd_soc_dapm_add_routes(dapm, wm8994_revd_intercon,
+ ARRAY_SIZE(wm8994_revd_intercon));
+ snd_soc_dapm_add_routes(dapm, wm8994_lateclk_revd_intercon,
+ ARRAY_SIZE(wm8994_lateclk_revd_intercon));
+ } else {
+ snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon,
+ ARRAY_SIZE(wm8994_lateclk_intercon));
+ snd_soc_dapm_add_routes(dapm, wm8958_intercon,
+ ARRAY_SIZE(wm8958_intercon));
+ }
+
+ wm8958_dsp2_init(codec);
+ break;
+ }
+
+ return 0;
+
+err_irq:
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994);
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994);
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994);
+ if (wm8994->micdet_irq)
+ free_irq(wm8994->micdet_irq, wm8994);
+err:
+ kfree(wm8994);
+ return ret;
+}
+
+static int wm8994_codec_remove(struct snd_soc_codec *codec)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994 *control = codec->control_data;
+
+ wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ pm_runtime_disable(codec->dev);
+
+ switch (control->type) {
+ case WM8994:
+ if (wm8994->micdet_irq)
+ free_irq(wm8994->micdet_irq, wm8994);
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET,
+ wm8994);
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT,
+ wm8994);
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET,
+ wm8994);
+ break;
+
+ case WM8958:
+ if (wm8994->micdet_irq)
+ free_irq(wm8994->micdet_irq, wm8994);
+ break;
+ }
+ if (wm8994->mbc)
+ release_firmware(wm8994->mbc);
+ if (wm8994->mbc_vss)
+ release_firmware(wm8994->mbc_vss);
+ if (wm8994->enh_eq)
+ release_firmware(wm8994->enh_eq);
+ kfree(wm8994->retune_mobile_texts);
+ kfree(wm8994->drc_texts);
+ kfree(wm8994);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
+ .probe = wm8994_codec_probe,
+ .remove = wm8994_codec_remove,
+ .suspend = wm8994_suspend,
+ .resume = wm8994_resume,
+ .read = wm8994_read,
+ .write = wm8994_write,
+ .readable_register = wm8994_readable,
+ .volatile_register = wm8994_volatile,
+ .set_bias_level = wm8994_set_bias_level,
+
+ .reg_cache_size = WM8994_CACHE_SIZE,
+ .reg_cache_default = wm8994_reg_defaults,
+ .reg_word_size = 2,
+ .compress_type = SND_SOC_RBTREE_COMPRESSION,
+};
+
+static int __devinit wm8994_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,
+ wm8994_dai, ARRAY_SIZE(wm8994_dai));
+}
+
+static int __devexit wm8994_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver wm8994_codec_driver = {
+ .driver = {
+ .name = "wm8994-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8994_probe,
+ .remove = __devexit_p(wm8994_remove),
+};
+
+static __init int wm8994_init(void)
+{
+ return platform_driver_register(&wm8994_codec_driver);
+}
+module_init(wm8994_init);
+
+static __exit void wm8994_exit(void)
+{
+ platform_driver_unregister(&wm8994_codec_driver);
+}
+module_exit(wm8994_exit);
+
+
+MODULE_DESCRIPTION("ASoC WM8994 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm8994-codec");
diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h
new file mode 100644
index 00000000..0a1db04b
--- /dev/null
+++ b/sound/soc/codecs/wm8994.h
@@ -0,0 +1,145 @@
+/*
+ * wm8994.h -- WM8994 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8994_H
+#define _WM8994_H
+
+#include <sound/soc.h>
+#include <linux/firmware.h>
+
+#include "wm_hubs.h"
+
+/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */
+#define WM8994_SYSCLK_MCLK1 1
+#define WM8994_SYSCLK_MCLK2 2
+#define WM8994_SYSCLK_FLL1 3
+#define WM8994_SYSCLK_FLL2 4
+
+/* OPCLK is also configured with set_dai_sysclk, specify division*10 as rate. */
+#define WM8994_SYSCLK_OPCLK 5
+
+#define WM8994_FLL1 1
+#define WM8994_FLL2 2
+
+#define WM8994_FLL_SRC_MCLK1 1
+#define WM8994_FLL_SRC_MCLK2 2
+#define WM8994_FLL_SRC_LRCLK 3
+#define WM8994_FLL_SRC_BCLK 4
+
+typedef void (*wm8958_micdet_cb)(u16 status, void *data);
+
+int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+ int micbias, int det, int shrt);
+int wm8958_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+ wm8958_micdet_cb cb, void *cb_data);
+
+#define WM8994_CACHE_SIZE 1570
+
+struct wm8994_access_mask {
+ unsigned short readable; /* Mask of readable bits */
+ unsigned short writable; /* Mask of writable bits */
+};
+
+extern const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE];
+extern const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE];
+
+int wm8958_aif_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event);
+
+void wm8958_dsp2_init(struct snd_soc_codec *codec);
+
+struct wm8994_micdet {
+ struct snd_soc_jack *jack;
+ int det;
+ int shrt;
+};
+
+/* codec private data */
+struct wm8994_fll_config {
+ int src;
+ int in;
+ int out;
+};
+
+#define WM8994_NUM_DRC 3
+#define WM8994_NUM_EQ 3
+
+struct wm8994_priv {
+ struct wm_hubs_data hubs;
+ enum snd_soc_control_type control_type;
+ void *control_data;
+ struct snd_soc_codec *codec;
+ int sysclk[2];
+ int sysclk_rate[2];
+ int mclk[2];
+ int aifclk[2];
+ struct wm8994_fll_config fll[2], fll_suspend[2];
+
+ int dac_rates[2];
+ int lrclk_shared[2];
+
+ int mbc_ena[3];
+ int hpf1_ena[3];
+ int hpf2_ena[3];
+ int vss_ena[3];
+ int enh_eq_ena[3];
+
+ /* Platform dependant DRC configuration */
+ const char **drc_texts;
+ int drc_cfg[WM8994_NUM_DRC];
+ struct soc_enum drc_enum;
+
+ /* Platform dependant ReTune mobile configuration */
+ int num_retune_mobile_texts;
+ const char **retune_mobile_texts;
+ int retune_mobile_cfg[WM8994_NUM_EQ];
+ struct soc_enum retune_mobile_enum;
+
+ /* Platform dependant MBC configuration */
+ int mbc_cfg;
+ const char **mbc_texts;
+ struct soc_enum mbc_enum;
+
+ /* Platform dependant VSS configuration */
+ int vss_cfg;
+ const char **vss_texts;
+ struct soc_enum vss_enum;
+
+ /* Platform dependant VSS HPF configuration */
+ int vss_hpf_cfg;
+ const char **vss_hpf_texts;
+ struct soc_enum vss_hpf_enum;
+
+ /* Platform dependant enhanced EQ configuration */
+ int enh_eq_cfg;
+ const char **enh_eq_texts;
+ struct soc_enum enh_eq_enum;
+
+ struct wm8994_micdet micdet[2];
+
+ wm8958_micdet_cb jack_cb;
+ void *jack_cb_data;
+ int micdet_irq;
+
+ int revision;
+ struct wm8994_pdata *pdata;
+
+ unsigned int aif1clk_enable:1;
+ unsigned int aif2clk_enable:1;
+
+ unsigned int aif1clk_disable:1;
+ unsigned int aif2clk_disable:1;
+
+ int dsp_active;
+ const struct firmware *cur_fw;
+ const struct firmware *mbc;
+ const struct firmware *mbc_vss;
+ const struct firmware *enh_eq;
+};
+
+#endif
diff --git a/sound/soc/codecs/wm8995.c b/sound/soc/codecs/wm8995.c
new file mode 100644
index 00000000..5ad873fd
--- /dev/null
+++ b/sound/soc/codecs/wm8995.c
@@ -0,0 +1,1911 @@
+/*
+ * wm8995.c -- WM8995 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * Based on wm8994.c and wm_hubs.c by Mark Brown
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8995.h"
+
+#define WM8995_NUM_SUPPLIES 8
+static const char *wm8995_supply_names[WM8995_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD1",
+ "DBVDD2",
+ "DBVDD3",
+ "AVDD1",
+ "AVDD2",
+ "CPVDD",
+ "MICVDD"
+};
+
+static const u16 wm8995_reg_defs[WM8995_MAX_REGISTER + 1] = {
+ [0] = 0x8995, [5] = 0x0100, [16] = 0x000b, [17] = 0x000b,
+ [24] = 0x02c0, [25] = 0x02c0, [26] = 0x02c0, [27] = 0x02c0,
+ [28] = 0x000f, [32] = 0x0005, [33] = 0x0005, [40] = 0x0003,
+ [41] = 0x0013, [48] = 0x0004, [56] = 0x09f8, [64] = 0x1f25,
+ [69] = 0x0004, [82] = 0xaaaa, [84] = 0x2a2a, [146] = 0x0060,
+ [256] = 0x0002, [257] = 0x8004, [520] = 0x0010, [528] = 0x0083,
+ [529] = 0x0083, [548] = 0x0c80, [580] = 0x0c80, [768] = 0x4050,
+ [769] = 0x4000, [771] = 0x0040, [772] = 0x0040, [773] = 0x0040,
+ [774] = 0x0004, [775] = 0x0100, [784] = 0x4050, [785] = 0x4000,
+ [787] = 0x0040, [788] = 0x0040, [789] = 0x0040, [1024] = 0x00c0,
+ [1025] = 0x00c0, [1026] = 0x00c0, [1027] = 0x00c0, [1028] = 0x00c0,
+ [1029] = 0x00c0, [1030] = 0x00c0, [1031] = 0x00c0, [1056] = 0x0200,
+ [1057] = 0x0010, [1058] = 0x0200, [1059] = 0x0010, [1088] = 0x0098,
+ [1089] = 0x0845, [1104] = 0x0098, [1105] = 0x0845, [1152] = 0x6318,
+ [1153] = 0x6300, [1154] = 0x0fca, [1155] = 0x0400, [1156] = 0x00d8,
+ [1157] = 0x1eb5, [1158] = 0xf145, [1159] = 0x0b75, [1160] = 0x01c5,
+ [1161] = 0x1c58, [1162] = 0xf373, [1163] = 0x0a54, [1164] = 0x0558,
+ [1165] = 0x168e, [1166] = 0xf829, [1167] = 0x07ad, [1168] = 0x1103,
+ [1169] = 0x0564, [1170] = 0x0559, [1171] = 0x4000, [1184] = 0x6318,
+ [1185] = 0x6300, [1186] = 0x0fca, [1187] = 0x0400, [1188] = 0x00d8,
+ [1189] = 0x1eb5, [1190] = 0xf145, [1191] = 0x0b75, [1192] = 0x01c5,
+ [1193] = 0x1c58, [1194] = 0xf373, [1195] = 0x0a54, [1196] = 0x0558,
+ [1197] = 0x168e, [1198] = 0xf829, [1199] = 0x07ad, [1200] = 0x1103,
+ [1201] = 0x0564, [1202] = 0x0559, [1203] = 0x4000, [1280] = 0x00c0,
+ [1281] = 0x00c0, [1282] = 0x00c0, [1283] = 0x00c0, [1312] = 0x0200,
+ [1313] = 0x0010, [1344] = 0x0098, [1345] = 0x0845, [1408] = 0x6318,
+ [1409] = 0x6300, [1410] = 0x0fca, [1411] = 0x0400, [1412] = 0x00d8,
+ [1413] = 0x1eb5, [1414] = 0xf145, [1415] = 0x0b75, [1416] = 0x01c5,
+ [1417] = 0x1c58, [1418] = 0xf373, [1419] = 0x0a54, [1420] = 0x0558,
+ [1421] = 0x168e, [1422] = 0xf829, [1423] = 0x07ad, [1424] = 0x1103,
+ [1425] = 0x0564, [1426] = 0x0559, [1427] = 0x4000, [1568] = 0x0002,
+ [1792] = 0xa100, [1793] = 0xa101, [1794] = 0xa101, [1795] = 0xa101,
+ [1796] = 0xa101, [1797] = 0xa101, [1798] = 0xa101, [1799] = 0xa101,
+ [1800] = 0xa101, [1801] = 0xa101, [1802] = 0xa101, [1803] = 0xa101,
+ [1804] = 0xa101, [1805] = 0xa101, [1825] = 0x0055, [1848] = 0x3fff,
+ [1849] = 0x1fff, [2049] = 0x0001, [2050] = 0x0069, [2056] = 0x0002,
+ [2057] = 0x0003, [2058] = 0x0069, [12288] = 0x0001, [12289] = 0x0001,
+ [12291] = 0x0006, [12292] = 0x0040, [12293] = 0x0001, [12294] = 0x000f,
+ [12295] = 0x0006, [12296] = 0x0001, [12297] = 0x0003, [12298] = 0x0104,
+ [12300] = 0x0060, [12301] = 0x0011, [12302] = 0x0401, [12304] = 0x0050,
+ [12305] = 0x0003, [12306] = 0x0100, [12308] = 0x0051, [12309] = 0x0003,
+ [12310] = 0x0104, [12311] = 0x000a, [12312] = 0x0060, [12313] = 0x003b,
+ [12314] = 0x0502, [12315] = 0x0100, [12316] = 0x2fff, [12320] = 0x2fff,
+ [12324] = 0x2fff, [12328] = 0x2fff, [12332] = 0x2fff, [12336] = 0x2fff,
+ [12340] = 0x2fff, [12344] = 0x2fff, [12348] = 0x2fff, [12352] = 0x0001,
+ [12353] = 0x0001, [12355] = 0x0006, [12356] = 0x0040, [12357] = 0x0001,
+ [12358] = 0x000f, [12359] = 0x0006, [12360] = 0x0001, [12361] = 0x0003,
+ [12362] = 0x0104, [12364] = 0x0060, [12365] = 0x0011, [12366] = 0x0401,
+ [12368] = 0x0050, [12369] = 0x0003, [12370] = 0x0100, [12372] = 0x0060,
+ [12373] = 0x003b, [12374] = 0x0502, [12375] = 0x0100, [12376] = 0x2fff,
+ [12380] = 0x2fff, [12384] = 0x2fff, [12388] = 0x2fff, [12392] = 0x2fff,
+ [12396] = 0x2fff, [12400] = 0x2fff, [12404] = 0x2fff, [12408] = 0x2fff,
+ [12412] = 0x2fff, [12416] = 0x0001, [12417] = 0x0001, [12419] = 0x0006,
+ [12420] = 0x0040, [12421] = 0x0001, [12422] = 0x000f, [12423] = 0x0006,
+ [12424] = 0x0001, [12425] = 0x0003, [12426] = 0x0106, [12428] = 0x0061,
+ [12429] = 0x0011, [12430] = 0x0401, [12432] = 0x0050, [12433] = 0x0003,
+ [12434] = 0x0102, [12436] = 0x0051, [12437] = 0x0003, [12438] = 0x0106,
+ [12439] = 0x000a, [12440] = 0x0061, [12441] = 0x003b, [12442] = 0x0502,
+ [12443] = 0x0100, [12444] = 0x2fff, [12448] = 0x2fff, [12452] = 0x2fff,
+ [12456] = 0x2fff, [12460] = 0x2fff, [12464] = 0x2fff, [12468] = 0x2fff,
+ [12472] = 0x2fff, [12476] = 0x2fff, [12480] = 0x0001, [12481] = 0x0001,
+ [12483] = 0x0006, [12484] = 0x0040, [12485] = 0x0001, [12486] = 0x000f,
+ [12487] = 0x0006, [12488] = 0x0001, [12489] = 0x0003, [12490] = 0x0106,
+ [12492] = 0x0061, [12493] = 0x0011, [12494] = 0x0401, [12496] = 0x0050,
+ [12497] = 0x0003, [12498] = 0x0102, [12500] = 0x0061, [12501] = 0x003b,
+ [12502] = 0x0502, [12503] = 0x0100, [12504] = 0x2fff, [12508] = 0x2fff,
+ [12512] = 0x2fff, [12516] = 0x2fff, [12520] = 0x2fff, [12524] = 0x2fff,
+ [12528] = 0x2fff, [12532] = 0x2fff, [12536] = 0x2fff, [12540] = 0x2fff,
+ [12544] = 0x0060, [12546] = 0x0601, [12548] = 0x0050, [12550] = 0x0100,
+ [12552] = 0x0001, [12554] = 0x0104, [12555] = 0x0100, [12556] = 0x2fff,
+ [12560] = 0x2fff, [12564] = 0x2fff, [12568] = 0x2fff, [12572] = 0x2fff,
+ [12576] = 0x2fff, [12580] = 0x2fff, [12584] = 0x2fff, [12588] = 0x2fff,
+ [12592] = 0x2fff, [12596] = 0x2fff, [12600] = 0x2fff, [12604] = 0x2fff,
+ [12608] = 0x0061, [12610] = 0x0601, [12612] = 0x0050, [12614] = 0x0102,
+ [12616] = 0x0001, [12618] = 0x0106, [12619] = 0x0100, [12620] = 0x2fff,
+ [12624] = 0x2fff, [12628] = 0x2fff, [12632] = 0x2fff, [12636] = 0x2fff,
+ [12640] = 0x2fff, [12644] = 0x2fff, [12648] = 0x2fff, [12652] = 0x2fff,
+ [12656] = 0x2fff, [12660] = 0x2fff, [12664] = 0x2fff, [12668] = 0x2fff,
+ [12672] = 0x0060, [12674] = 0x0601, [12676] = 0x0061, [12678] = 0x0601,
+ [12680] = 0x0050, [12682] = 0x0300, [12684] = 0x0001, [12686] = 0x0304,
+ [12688] = 0x0040, [12690] = 0x000f, [12692] = 0x0001, [12695] = 0x0100
+};
+
+struct fll_config {
+ int src;
+ int in;
+ int out;
+};
+
+struct wm8995_priv {
+ enum snd_soc_control_type control_type;
+ int sysclk[2];
+ int mclk[2];
+ int aifclk[2];
+ struct fll_config fll[2], fll_suspend[2];
+ struct regulator_bulk_data supplies[WM8995_NUM_SUPPLIES];
+ struct notifier_block disable_nb[WM8995_NUM_SUPPLIES];
+ struct snd_soc_codec *codec;
+};
+
+/*
+ * We can't use the same notifier block for more than one supply and
+ * there's no way I can see to get from a callback to the caller
+ * except container_of().
+ */
+#define WM8995_REGULATOR_EVENT(n) \
+static int wm8995_regulator_event_##n(struct notifier_block *nb, \
+ unsigned long event, void *data) \
+{ \
+ struct wm8995_priv *wm8995 = container_of(nb, struct wm8995_priv, \
+ disable_nb[n]); \
+ if (event & REGULATOR_EVENT_DISABLE) { \
+ wm8995->codec->cache_sync = 1; \
+ } \
+ return 0; \
+}
+
+WM8995_REGULATOR_EVENT(0)
+WM8995_REGULATOR_EVENT(1)
+WM8995_REGULATOR_EVENT(2)
+WM8995_REGULATOR_EVENT(3)
+WM8995_REGULATOR_EVENT(4)
+WM8995_REGULATOR_EVENT(5)
+WM8995_REGULATOR_EVENT(6)
+WM8995_REGULATOR_EVENT(7)
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(in1lr_pga_tlv, -1650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(in1l_boost_tlv, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 150, 0);
+
+static const char *in1l_text[] = {
+ "Differential", "Single-ended IN1LN", "Single-ended IN1LP"
+};
+
+static const SOC_ENUM_SINGLE_DECL(in1l_enum, WM8995_LEFT_LINE_INPUT_CONTROL,
+ 2, in1l_text);
+
+static const char *in1r_text[] = {
+ "Differential", "Single-ended IN1RN", "Single-ended IN1RP"
+};
+
+static const SOC_ENUM_SINGLE_DECL(in1r_enum, WM8995_LEFT_LINE_INPUT_CONTROL,
+ 0, in1r_text);
+
+static const char *dmic_src_text[] = {
+ "DMICDAT1", "DMICDAT2", "DMICDAT3"
+};
+
+static const SOC_ENUM_SINGLE_DECL(dmic_src1_enum, WM8995_POWER_MANAGEMENT_5,
+ 8, dmic_src_text);
+static const SOC_ENUM_SINGLE_DECL(dmic_src2_enum, WM8995_POWER_MANAGEMENT_5,
+ 6, dmic_src_text);
+
+static const struct snd_kcontrol_new wm8995_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("DAC1 Volume", WM8995_DAC1_LEFT_VOLUME,
+ WM8995_DAC1_RIGHT_VOLUME, 0, 96, 0, digital_tlv),
+ SOC_DOUBLE_R("DAC1 Switch", WM8995_DAC1_LEFT_VOLUME,
+ WM8995_DAC1_RIGHT_VOLUME, 9, 1, 1),
+
+ SOC_DOUBLE_R_TLV("DAC2 Volume", WM8995_DAC2_LEFT_VOLUME,
+ WM8995_DAC2_RIGHT_VOLUME, 0, 96, 0, digital_tlv),
+ SOC_DOUBLE_R("DAC2 Switch", WM8995_DAC2_LEFT_VOLUME,
+ WM8995_DAC2_RIGHT_VOLUME, 9, 1, 1),
+
+ SOC_DOUBLE_R_TLV("AIF1DAC1 Volume", WM8995_AIF1_DAC1_LEFT_VOLUME,
+ WM8995_AIF1_DAC1_RIGHT_VOLUME, 0, 96, 0, digital_tlv),
+ SOC_DOUBLE_R_TLV("AIF1DAC2 Volume", WM8995_AIF1_DAC2_LEFT_VOLUME,
+ WM8995_AIF1_DAC2_RIGHT_VOLUME, 0, 96, 0, digital_tlv),
+ SOC_DOUBLE_R_TLV("AIF2DAC Volume", WM8995_AIF2_DAC_LEFT_VOLUME,
+ WM8995_AIF2_DAC_RIGHT_VOLUME, 0, 96, 0, digital_tlv),
+
+ SOC_DOUBLE_R_TLV("IN1LR Volume", WM8995_LEFT_LINE_INPUT_1_VOLUME,
+ WM8995_RIGHT_LINE_INPUT_1_VOLUME, 0, 31, 0, in1lr_pga_tlv),
+
+ SOC_SINGLE_TLV("IN1L Boost", WM8995_LEFT_LINE_INPUT_CONTROL,
+ 4, 3, 0, in1l_boost_tlv),
+
+ SOC_ENUM("IN1L Mode", in1l_enum),
+ SOC_ENUM("IN1R Mode", in1r_enum),
+
+ SOC_ENUM("DMIC1 SRC", dmic_src1_enum),
+ SOC_ENUM("DMIC2 SRC", dmic_src2_enum),
+
+ SOC_DOUBLE_TLV("DAC1 Sidetone Volume", WM8995_DAC1_MIXER_VOLUMES, 0, 5,
+ 24, 0, sidetone_tlv),
+ SOC_DOUBLE_TLV("DAC2 Sidetone Volume", WM8995_DAC2_MIXER_VOLUMES, 0, 5,
+ 24, 0, sidetone_tlv),
+
+ SOC_DOUBLE_R_TLV("AIF1ADC1 Volume", WM8995_AIF1_ADC1_LEFT_VOLUME,
+ WM8995_AIF1_ADC1_RIGHT_VOLUME, 0, 96, 0, digital_tlv),
+ SOC_DOUBLE_R_TLV("AIF1ADC2 Volume", WM8995_AIF1_ADC2_LEFT_VOLUME,
+ WM8995_AIF1_ADC2_RIGHT_VOLUME, 0, 96, 0, digital_tlv),
+ SOC_DOUBLE_R_TLV("AIF2ADC Volume", WM8995_AIF2_ADC_LEFT_VOLUME,
+ WM8995_AIF2_ADC_RIGHT_VOLUME, 0, 96, 0, digital_tlv)
+};
+
+static void wm8995_update_class_w(struct snd_soc_codec *codec)
+{
+ int enable = 1;
+ int source = 0; /* GCC flow analysis can't track enable */
+ int reg, reg_r;
+
+ /* We also need the same setting for L/R and only one path */
+ reg = snd_soc_read(codec, WM8995_DAC1_LEFT_MIXER_ROUTING);
+ switch (reg) {
+ case WM8995_AIF2DACL_TO_DAC1L:
+ dev_dbg(codec->dev, "Class W source AIF2DAC\n");
+ source = 2 << WM8995_CP_DYN_SRC_SEL_SHIFT;
+ break;
+ case WM8995_AIF1DAC2L_TO_DAC1L:
+ dev_dbg(codec->dev, "Class W source AIF1DAC2\n");
+ source = 1 << WM8995_CP_DYN_SRC_SEL_SHIFT;
+ break;
+ case WM8995_AIF1DAC1L_TO_DAC1L:
+ dev_dbg(codec->dev, "Class W source AIF1DAC1\n");
+ source = 0 << WM8995_CP_DYN_SRC_SEL_SHIFT;
+ break;
+ default:
+ dev_dbg(codec->dev, "DAC mixer setting: %x\n", reg);
+ enable = 0;
+ break;
+ }
+
+ reg_r = snd_soc_read(codec, WM8995_DAC1_RIGHT_MIXER_ROUTING);
+ if (reg_r != reg) {
+ dev_dbg(codec->dev, "Left and right DAC mixers different\n");
+ enable = 0;
+ }
+
+ if (enable) {
+ dev_dbg(codec->dev, "Class W enabled\n");
+ snd_soc_update_bits(codec, WM8995_CLASS_W_1,
+ WM8995_CP_DYN_PWR_MASK |
+ WM8995_CP_DYN_SRC_SEL_MASK,
+ source | WM8995_CP_DYN_PWR);
+ } else {
+ dev_dbg(codec->dev, "Class W disabled\n");
+ snd_soc_update_bits(codec, WM8995_CLASS_W_1,
+ WM8995_CP_DYN_PWR_MASK, 0);
+ }
+}
+
+static int check_clk_sys(struct snd_soc_dapm_widget *source,
+ struct snd_soc_dapm_widget *sink)
+{
+ unsigned int reg;
+ const char *clk;
+
+ reg = snd_soc_read(source->codec, WM8995_CLOCKING_1);
+ /* Check what we're currently using for CLK_SYS */
+ if (reg & WM8995_SYSCLK_SRC)
+ clk = "AIF2CLK";
+ else
+ clk = "AIF1CLK";
+ return !strcmp(source->name, clk);
+}
+
+static int wm8995_put_class_w(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *w = wlist->widgets[0];
+ struct snd_soc_codec *codec;
+ int ret;
+
+ codec = w->codec;
+ ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+ wm8995_update_class_w(codec);
+ return ret;
+}
+
+static int hp_supply_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec;
+ struct wm8995_priv *wm8995;
+
+ codec = w->codec;
+ wm8995 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Enable the headphone amp */
+ snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1,
+ WM8995_HPOUT1L_ENA_MASK |
+ WM8995_HPOUT1R_ENA_MASK,
+ WM8995_HPOUT1L_ENA |
+ WM8995_HPOUT1R_ENA);
+
+ /* Enable the second stage */
+ snd_soc_update_bits(codec, WM8995_ANALOGUE_HP_1,
+ WM8995_HPOUT1L_DLY_MASK |
+ WM8995_HPOUT1R_DLY_MASK,
+ WM8995_HPOUT1L_DLY |
+ WM8995_HPOUT1R_DLY);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_update_bits(codec, WM8995_CHARGE_PUMP_1,
+ WM8995_CP_ENA_MASK, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static void dc_servo_cmd(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val, unsigned int mask)
+{
+ int timeout = 10;
+
+ dev_dbg(codec->dev, "%s: reg = %#x, val = %#x, mask = %#x\n",
+ __func__, reg, val, mask);
+
+ snd_soc_write(codec, reg, val);
+ while (timeout--) {
+ msleep(10);
+ val = snd_soc_read(codec, WM8995_DC_SERVO_READBACK_0);
+ if ((val & mask) == mask)
+ return;
+ }
+
+ dev_err(codec->dev, "Timed out waiting for DC Servo\n");
+}
+
+static int hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec;
+ unsigned int reg;
+
+ codec = w->codec;
+ reg = snd_soc_read(codec, WM8995_ANALOGUE_HP_1);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ snd_soc_update_bits(codec, WM8995_CHARGE_PUMP_1,
+ WM8995_CP_ENA_MASK, WM8995_CP_ENA);
+
+ msleep(5);
+
+ snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1,
+ WM8995_HPOUT1L_ENA_MASK |
+ WM8995_HPOUT1R_ENA_MASK,
+ WM8995_HPOUT1L_ENA | WM8995_HPOUT1R_ENA);
+
+ udelay(20);
+
+ reg |= WM8995_HPOUT1L_DLY | WM8995_HPOUT1R_DLY;
+ snd_soc_write(codec, WM8995_ANALOGUE_HP_1, reg);
+
+ snd_soc_write(codec, WM8995_DC_SERVO_1, WM8995_DCS_ENA_CHAN_0 |
+ WM8995_DCS_ENA_CHAN_1);
+
+ dc_servo_cmd(codec, WM8995_DC_SERVO_2,
+ WM8995_DCS_TRIG_STARTUP_0 |
+ WM8995_DCS_TRIG_STARTUP_1,
+ WM8995_DCS_TRIG_DAC_WR_0 |
+ WM8995_DCS_TRIG_DAC_WR_1);
+
+ reg |= WM8995_HPOUT1R_OUTP | WM8995_HPOUT1R_RMV_SHORT |
+ WM8995_HPOUT1L_OUTP | WM8995_HPOUT1L_RMV_SHORT;
+ snd_soc_write(codec, WM8995_ANALOGUE_HP_1, reg);
+
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_update_bits(codec, WM8995_ANALOGUE_HP_1,
+ WM8995_HPOUT1L_OUTP_MASK |
+ WM8995_HPOUT1R_OUTP_MASK |
+ WM8995_HPOUT1L_RMV_SHORT_MASK |
+ WM8995_HPOUT1R_RMV_SHORT_MASK, 0);
+
+ snd_soc_update_bits(codec, WM8995_ANALOGUE_HP_1,
+ WM8995_HPOUT1L_DLY_MASK |
+ WM8995_HPOUT1R_DLY_MASK, 0);
+
+ snd_soc_write(codec, WM8995_DC_SERVO_1, 0);
+
+ snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1,
+ WM8995_HPOUT1L_ENA_MASK |
+ WM8995_HPOUT1R_ENA_MASK,
+ 0);
+ break;
+ }
+
+ return 0;
+}
+
+static int configure_aif_clock(struct snd_soc_codec *codec, int aif)
+{
+ struct wm8995_priv *wm8995;
+ int rate;
+ int reg1 = 0;
+ int offset;
+
+ wm8995 = snd_soc_codec_get_drvdata(codec);
+
+ if (aif)
+ offset = 4;
+ else
+ offset = 0;
+
+ switch (wm8995->sysclk[aif]) {
+ case WM8995_SYSCLK_MCLK1:
+ rate = wm8995->mclk[0];
+ break;
+ case WM8995_SYSCLK_MCLK2:
+ reg1 |= 0x8;
+ rate = wm8995->mclk[1];
+ break;
+ case WM8995_SYSCLK_FLL1:
+ reg1 |= 0x10;
+ rate = wm8995->fll[0].out;
+ break;
+ case WM8995_SYSCLK_FLL2:
+ reg1 |= 0x18;
+ rate = wm8995->fll[1].out;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (rate >= 13500000) {
+ rate /= 2;
+ reg1 |= WM8995_AIF1CLK_DIV;
+
+ dev_dbg(codec->dev, "Dividing AIF%d clock to %dHz\n",
+ aif + 1, rate);
+ }
+
+ wm8995->aifclk[aif] = rate;
+
+ snd_soc_update_bits(codec, WM8995_AIF1_CLOCKING_1 + offset,
+ WM8995_AIF1CLK_SRC_MASK | WM8995_AIF1CLK_DIV_MASK,
+ reg1);
+ return 0;
+}
+
+static int configure_clock(struct snd_soc_codec *codec)
+{
+ struct wm8995_priv *wm8995;
+ int old, new;
+
+ wm8995 = snd_soc_codec_get_drvdata(codec);
+
+ /* Bring up the AIF clocks first */
+ configure_aif_clock(codec, 0);
+ configure_aif_clock(codec, 1);
+
+ /*
+ * Then switch CLK_SYS over to the higher of them; a change
+ * can only happen as a result of a clocking change which can
+ * only be made outside of DAPM so we can safely redo the
+ * clocking.
+ */
+
+ /* If they're equal it doesn't matter which is used */
+ if (wm8995->aifclk[0] == wm8995->aifclk[1])
+ return 0;
+
+ if (wm8995->aifclk[0] < wm8995->aifclk[1])
+ new = WM8995_SYSCLK_SRC;
+ else
+ new = 0;
+
+ old = snd_soc_read(codec, WM8995_CLOCKING_1) & WM8995_SYSCLK_SRC;
+
+ /* If there's no change then we're done. */
+ if (old == new)
+ return 0;
+
+ snd_soc_update_bits(codec, WM8995_CLOCKING_1,
+ WM8995_SYSCLK_SRC_MASK, new);
+
+ snd_soc_dapm_sync(&codec->dapm);
+
+ return 0;
+}
+
+static int clk_sys_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec;
+
+ codec = w->codec;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ return configure_clock(codec);
+
+ case SND_SOC_DAPM_POST_PMD:
+ configure_clock(codec);
+ break;
+ }
+
+ return 0;
+}
+
+static const char *sidetone_text[] = {
+ "ADC/DMIC1", "DMIC2",
+};
+
+static const struct soc_enum sidetone1_enum =
+ SOC_ENUM_SINGLE(WM8995_SIDETONE, 0, 2, sidetone_text);
+
+static const struct snd_kcontrol_new sidetone1_mux =
+ SOC_DAPM_ENUM("Left Sidetone Mux", sidetone1_enum);
+
+static const struct soc_enum sidetone2_enum =
+ SOC_ENUM_SINGLE(WM8995_SIDETONE, 1, 2, sidetone_text);
+
+static const struct snd_kcontrol_new sidetone2_mux =
+ SOC_DAPM_ENUM("Right Sidetone Mux", sidetone2_enum);
+
+static const struct snd_kcontrol_new aif1adc1l_mix[] = {
+ SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8995_AIF1_ADC1_LEFT_MIXER_ROUTING,
+ 1, 1, 0),
+ SOC_DAPM_SINGLE("AIF2 Switch", WM8995_AIF1_ADC1_LEFT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc1r_mix[] = {
+ SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8995_AIF1_ADC1_RIGHT_MIXER_ROUTING,
+ 1, 1, 0),
+ SOC_DAPM_SINGLE("AIF2 Switch", WM8995_AIF1_ADC1_RIGHT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc2l_mix[] = {
+ SOC_DAPM_SINGLE("DMIC Switch", WM8995_AIF1_ADC2_LEFT_MIXER_ROUTING,
+ 1, 1, 0),
+ SOC_DAPM_SINGLE("AIF2 Switch", WM8995_AIF1_ADC2_LEFT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc2r_mix[] = {
+ SOC_DAPM_SINGLE("DMIC Switch", WM8995_AIF1_ADC2_RIGHT_MIXER_ROUTING,
+ 1, 1, 0),
+ SOC_DAPM_SINGLE("AIF2 Switch", WM8995_AIF1_ADC2_RIGHT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dac1l_mix[] = {
+ WM8995_CLASS_W_SWITCH("Right Sidetone Switch", WM8995_DAC1_LEFT_MIXER_ROUTING,
+ 5, 1, 0),
+ WM8995_CLASS_W_SWITCH("Left Sidetone Switch", WM8995_DAC1_LEFT_MIXER_ROUTING,
+ 4, 1, 0),
+ WM8995_CLASS_W_SWITCH("AIF2 Switch", WM8995_DAC1_LEFT_MIXER_ROUTING,
+ 2, 1, 0),
+ WM8995_CLASS_W_SWITCH("AIF1.2 Switch", WM8995_DAC1_LEFT_MIXER_ROUTING,
+ 1, 1, 0),
+ WM8995_CLASS_W_SWITCH("AIF1.1 Switch", WM8995_DAC1_LEFT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dac1r_mix[] = {
+ WM8995_CLASS_W_SWITCH("Right Sidetone Switch", WM8995_DAC1_RIGHT_MIXER_ROUTING,
+ 5, 1, 0),
+ WM8995_CLASS_W_SWITCH("Left Sidetone Switch", WM8995_DAC1_RIGHT_MIXER_ROUTING,
+ 4, 1, 0),
+ WM8995_CLASS_W_SWITCH("AIF2 Switch", WM8995_DAC1_RIGHT_MIXER_ROUTING,
+ 2, 1, 0),
+ WM8995_CLASS_W_SWITCH("AIF1.2 Switch", WM8995_DAC1_RIGHT_MIXER_ROUTING,
+ 1, 1, 0),
+ WM8995_CLASS_W_SWITCH("AIF1.1 Switch", WM8995_DAC1_RIGHT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif2dac2l_mix[] = {
+ SOC_DAPM_SINGLE("Right Sidetone Switch", WM8995_DAC2_LEFT_MIXER_ROUTING,
+ 5, 1, 0),
+ SOC_DAPM_SINGLE("Left Sidetone Switch", WM8995_DAC2_LEFT_MIXER_ROUTING,
+ 4, 1, 0),
+ SOC_DAPM_SINGLE("AIF2 Switch", WM8995_DAC2_LEFT_MIXER_ROUTING,
+ 2, 1, 0),
+ SOC_DAPM_SINGLE("AIF1.2 Switch", WM8995_DAC2_LEFT_MIXER_ROUTING,
+ 1, 1, 0),
+ SOC_DAPM_SINGLE("AIF1.1 Switch", WM8995_DAC2_LEFT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif2dac2r_mix[] = {
+ SOC_DAPM_SINGLE("Right Sidetone Switch", WM8995_DAC2_RIGHT_MIXER_ROUTING,
+ 5, 1, 0),
+ SOC_DAPM_SINGLE("Left Sidetone Switch", WM8995_DAC2_RIGHT_MIXER_ROUTING,
+ 4, 1, 0),
+ SOC_DAPM_SINGLE("AIF2 Switch", WM8995_DAC2_RIGHT_MIXER_ROUTING,
+ 2, 1, 0),
+ SOC_DAPM_SINGLE("AIF1.2 Switch", WM8995_DAC2_RIGHT_MIXER_ROUTING,
+ 1, 1, 0),
+ SOC_DAPM_SINGLE("AIF1.1 Switch", WM8995_DAC2_RIGHT_MIXER_ROUTING,
+ 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new in1l_pga =
+ SOC_DAPM_SINGLE("IN1L Switch", WM8995_POWER_MANAGEMENT_2, 5, 1, 0);
+
+static const struct snd_kcontrol_new in1r_pga =
+ SOC_DAPM_SINGLE("IN1R Switch", WM8995_POWER_MANAGEMENT_2, 4, 1, 0);
+
+static const char *adc_mux_text[] = {
+ "ADC",
+ "DMIC",
+};
+
+static const struct soc_enum adc_enum =
+ SOC_ENUM_SINGLE(0, 0, 2, adc_mux_text);
+
+static const struct snd_kcontrol_new adcl_mux =
+ SOC_DAPM_ENUM_VIRT("ADCL Mux", adc_enum);
+
+static const struct snd_kcontrol_new adcr_mux =
+ SOC_DAPM_ENUM_VIRT("ADCR Mux", adc_enum);
+
+static const char *spk_src_text[] = {
+ "DAC1L", "DAC1R", "DAC2L", "DAC2R"
+};
+
+static const SOC_ENUM_SINGLE_DECL(spk1l_src_enum, WM8995_LEFT_PDM_SPEAKER_1,
+ 0, spk_src_text);
+static const SOC_ENUM_SINGLE_DECL(spk1r_src_enum, WM8995_RIGHT_PDM_SPEAKER_1,
+ 0, spk_src_text);
+static const SOC_ENUM_SINGLE_DECL(spk2l_src_enum, WM8995_LEFT_PDM_SPEAKER_2,
+ 0, spk_src_text);
+static const SOC_ENUM_SINGLE_DECL(spk2r_src_enum, WM8995_RIGHT_PDM_SPEAKER_2,
+ 0, spk_src_text);
+
+static const struct snd_kcontrol_new spk1l_mux =
+ SOC_DAPM_ENUM("SPK1L SRC", spk1l_src_enum);
+static const struct snd_kcontrol_new spk1r_mux =
+ SOC_DAPM_ENUM("SPK1R SRC", spk1r_src_enum);
+static const struct snd_kcontrol_new spk2l_mux =
+ SOC_DAPM_ENUM("SPK2L SRC", spk2l_src_enum);
+static const struct snd_kcontrol_new spk2r_mux =
+ SOC_DAPM_ENUM("SPK2R SRC", spk2r_src_enum);
+
+static const struct snd_soc_dapm_widget wm8995_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("DMIC1DAT"),
+ SND_SOC_DAPM_INPUT("DMIC2DAT"),
+
+ SND_SOC_DAPM_INPUT("IN1L"),
+ SND_SOC_DAPM_INPUT("IN1R"),
+
+ SND_SOC_DAPM_MIXER("IN1L PGA", SND_SOC_NOPM, 0, 0,
+ &in1l_pga, 1),
+ SND_SOC_DAPM_MIXER("IN1R PGA", SND_SOC_NOPM, 0, 0,
+ &in1r_pga, 1),
+
+ SND_SOC_DAPM_MICBIAS("MICBIAS1", WM8995_POWER_MANAGEMENT_1, 8, 0),
+ SND_SOC_DAPM_MICBIAS("MICBIAS2", WM8995_POWER_MANAGEMENT_1, 9, 0),
+
+ SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8995_AIF1_CLOCKING_1, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8995_AIF2_CLOCKING_1, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("DSP1CLK", WM8995_CLOCKING_1, 3, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("DSP2CLK", WM8995_CLOCKING_1, 2, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("SYSDSPCLK", WM8995_CLOCKING_1, 1, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_AIF_OUT("AIF1ADC1L", "AIF1 Capture", 0,
+ WM8995_POWER_MANAGEMENT_3, 9, 0),
+ SND_SOC_DAPM_AIF_OUT("AIF1ADC1R", "AIF1 Capture", 0,
+ WM8995_POWER_MANAGEMENT_3, 8, 0),
+ SND_SOC_DAPM_AIF_OUT("AIF1ADCDAT", "AIF1 Capture", 0,
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AIF1ADC2L", "AIF1 Capture",
+ 0, WM8995_POWER_MANAGEMENT_3, 11, 0),
+ SND_SOC_DAPM_AIF_OUT("AIF1ADC2R", "AIF1 Capture",
+ 0, WM8995_POWER_MANAGEMENT_3, 10, 0),
+
+ SND_SOC_DAPM_VIRT_MUX("ADCL Mux", SND_SOC_NOPM, 1, 0,
+ &adcl_mux),
+ SND_SOC_DAPM_VIRT_MUX("ADCR Mux", SND_SOC_NOPM, 0, 0,
+ &adcr_mux),
+
+ SND_SOC_DAPM_ADC("DMIC2L", NULL, WM8995_POWER_MANAGEMENT_3, 5, 0),
+ SND_SOC_DAPM_ADC("DMIC2R", NULL, WM8995_POWER_MANAGEMENT_3, 4, 0),
+ SND_SOC_DAPM_ADC("DMIC1L", NULL, WM8995_POWER_MANAGEMENT_3, 3, 0),
+ SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8995_POWER_MANAGEMENT_3, 2, 0),
+
+ SND_SOC_DAPM_ADC("ADCL", NULL, WM8995_POWER_MANAGEMENT_3, 1, 0),
+ SND_SOC_DAPM_ADC("ADCR", NULL, WM8995_POWER_MANAGEMENT_3, 0, 0),
+
+ SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0,
+ aif1adc1l_mix, ARRAY_SIZE(aif1adc1l_mix)),
+ SND_SOC_DAPM_MIXER("AIF1ADC1R Mixer", SND_SOC_NOPM, 0, 0,
+ aif1adc1r_mix, ARRAY_SIZE(aif1adc1r_mix)),
+ SND_SOC_DAPM_MIXER("AIF1ADC2L Mixer", SND_SOC_NOPM, 0, 0,
+ aif1adc2l_mix, ARRAY_SIZE(aif1adc2l_mix)),
+ SND_SOC_DAPM_MIXER("AIF1ADC2R Mixer", SND_SOC_NOPM, 0, 0,
+ aif1adc2r_mix, ARRAY_SIZE(aif1adc2r_mix)),
+
+ SND_SOC_DAPM_AIF_IN("AIF1DAC1L", NULL, 0, WM8995_POWER_MANAGEMENT_4,
+ 9, 0),
+ SND_SOC_DAPM_AIF_IN("AIF1DAC1R", NULL, 0, WM8995_POWER_MANAGEMENT_4,
+ 8, 0),
+ SND_SOC_DAPM_AIF_IN("AIF1DACDAT", "AIF1 Playback", 0, SND_SOC_NOPM,
+ 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("AIF1DAC2L", NULL, 0, WM8995_POWER_MANAGEMENT_4,
+ 11, 0),
+ SND_SOC_DAPM_AIF_IN("AIF1DAC2R", NULL, 0, WM8995_POWER_MANAGEMENT_4,
+ 10, 0),
+
+ SND_SOC_DAPM_MIXER("AIF2DAC2L Mixer", SND_SOC_NOPM, 0, 0,
+ aif2dac2l_mix, ARRAY_SIZE(aif2dac2l_mix)),
+ SND_SOC_DAPM_MIXER("AIF2DAC2R Mixer", SND_SOC_NOPM, 0, 0,
+ aif2dac2r_mix, ARRAY_SIZE(aif2dac2r_mix)),
+
+ SND_SOC_DAPM_DAC("DAC2L", NULL, WM8995_POWER_MANAGEMENT_4, 3, 0),
+ SND_SOC_DAPM_DAC("DAC2R", NULL, WM8995_POWER_MANAGEMENT_4, 2, 0),
+ SND_SOC_DAPM_DAC("DAC1L", NULL, WM8995_POWER_MANAGEMENT_4, 1, 0),
+ SND_SOC_DAPM_DAC("DAC1R", NULL, WM8995_POWER_MANAGEMENT_4, 0, 0),
+
+ SND_SOC_DAPM_MIXER("DAC1L Mixer", SND_SOC_NOPM, 0, 0, dac1l_mix,
+ ARRAY_SIZE(dac1l_mix)),
+ SND_SOC_DAPM_MIXER("DAC1R Mixer", SND_SOC_NOPM, 0, 0, dac1r_mix,
+ ARRAY_SIZE(dac1r_mix)),
+
+ SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &sidetone1_mux),
+ SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &sidetone2_mux),
+
+ SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
+ hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_SUPPLY("Headphone Supply", SND_SOC_NOPM, 0, 0,
+ hp_supply_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_MUX("SPK1L Driver", WM8995_LEFT_PDM_SPEAKER_1,
+ 4, 0, &spk1l_mux),
+ SND_SOC_DAPM_MUX("SPK1R Driver", WM8995_RIGHT_PDM_SPEAKER_1,
+ 4, 0, &spk1r_mux),
+ SND_SOC_DAPM_MUX("SPK2L Driver", WM8995_LEFT_PDM_SPEAKER_2,
+ 4, 0, &spk2l_mux),
+ SND_SOC_DAPM_MUX("SPK2R Driver", WM8995_RIGHT_PDM_SPEAKER_2,
+ 4, 0, &spk2r_mux),
+
+ SND_SOC_DAPM_SUPPLY("LDO2", WM8995_POWER_MANAGEMENT_2, 1, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("HP1L"),
+ SND_SOC_DAPM_OUTPUT("HP1R"),
+ SND_SOC_DAPM_OUTPUT("SPK1L"),
+ SND_SOC_DAPM_OUTPUT("SPK1R"),
+ SND_SOC_DAPM_OUTPUT("SPK2L"),
+ SND_SOC_DAPM_OUTPUT("SPK2R")
+};
+
+static const struct snd_soc_dapm_route wm8995_intercon[] = {
+ { "CLK_SYS", NULL, "AIF1CLK", check_clk_sys },
+ { "CLK_SYS", NULL, "AIF2CLK", check_clk_sys },
+
+ { "DSP1CLK", NULL, "CLK_SYS" },
+ { "DSP2CLK", NULL, "CLK_SYS" },
+ { "SYSDSPCLK", NULL, "CLK_SYS" },
+
+ { "AIF1ADC1L", NULL, "AIF1CLK" },
+ { "AIF1ADC1L", NULL, "DSP1CLK" },
+ { "AIF1ADC1R", NULL, "AIF1CLK" },
+ { "AIF1ADC1R", NULL, "DSP1CLK" },
+ { "AIF1ADC1R", NULL, "SYSDSPCLK" },
+
+ { "AIF1ADC2L", NULL, "AIF1CLK" },
+ { "AIF1ADC2L", NULL, "DSP1CLK" },
+ { "AIF1ADC2R", NULL, "AIF1CLK" },
+ { "AIF1ADC2R", NULL, "DSP1CLK" },
+ { "AIF1ADC2R", NULL, "SYSDSPCLK" },
+
+ { "DMIC1L", NULL, "DMIC1DAT" },
+ { "DMIC1L", NULL, "CLK_SYS" },
+ { "DMIC1R", NULL, "DMIC1DAT" },
+ { "DMIC1R", NULL, "CLK_SYS" },
+ { "DMIC2L", NULL, "DMIC2DAT" },
+ { "DMIC2L", NULL, "CLK_SYS" },
+ { "DMIC2R", NULL, "DMIC2DAT" },
+ { "DMIC2R", NULL, "CLK_SYS" },
+
+ { "ADCL", NULL, "AIF1CLK" },
+ { "ADCL", NULL, "DSP1CLK" },
+ { "ADCL", NULL, "SYSDSPCLK" },
+
+ { "ADCR", NULL, "AIF1CLK" },
+ { "ADCR", NULL, "DSP1CLK" },
+ { "ADCR", NULL, "SYSDSPCLK" },
+
+ { "IN1L PGA", "IN1L Switch", "IN1L" },
+ { "IN1R PGA", "IN1R Switch", "IN1R" },
+ { "IN1L PGA", NULL, "LDO2" },
+ { "IN1R PGA", NULL, "LDO2" },
+
+ { "ADCL", NULL, "IN1L PGA" },
+ { "ADCR", NULL, "IN1R PGA" },
+
+ { "ADCL Mux", "ADC", "ADCL" },
+ { "ADCL Mux", "DMIC", "DMIC1L" },
+ { "ADCR Mux", "ADC", "ADCR" },
+ { "ADCR Mux", "DMIC", "DMIC1R" },
+
+ /* AIF1 outputs */
+ { "AIF1ADC1L", NULL, "AIF1ADC1L Mixer" },
+ { "AIF1ADC1L Mixer", "ADC/DMIC Switch", "ADCL Mux" },
+
+ { "AIF1ADC1R", NULL, "AIF1ADC1R Mixer" },
+ { "AIF1ADC1R Mixer", "ADC/DMIC Switch", "ADCR Mux" },
+
+ { "AIF1ADC2L", NULL, "AIF1ADC2L Mixer" },
+ { "AIF1ADC2L Mixer", "DMIC Switch", "DMIC2L" },
+
+ { "AIF1ADC2R", NULL, "AIF1ADC2R Mixer" },
+ { "AIF1ADC2R Mixer", "DMIC Switch", "DMIC2R" },
+
+ /* Sidetone */
+ { "Left Sidetone", "ADC/DMIC1", "AIF1ADC1L" },
+ { "Left Sidetone", "DMIC2", "AIF1ADC2L" },
+ { "Right Sidetone", "ADC/DMIC1", "AIF1ADC1R" },
+ { "Right Sidetone", "DMIC2", "AIF1ADC2R" },
+
+ { "AIF1DAC1L", NULL, "AIF1CLK" },
+ { "AIF1DAC1L", NULL, "DSP1CLK" },
+ { "AIF1DAC1R", NULL, "AIF1CLK" },
+ { "AIF1DAC1R", NULL, "DSP1CLK" },
+ { "AIF1DAC1R", NULL, "SYSDSPCLK" },
+
+ { "AIF1DAC2L", NULL, "AIF1CLK" },
+ { "AIF1DAC2L", NULL, "DSP1CLK" },
+ { "AIF1DAC2R", NULL, "AIF1CLK" },
+ { "AIF1DAC2R", NULL, "DSP1CLK" },
+ { "AIF1DAC2R", NULL, "SYSDSPCLK" },
+
+ { "DAC1L", NULL, "AIF1CLK" },
+ { "DAC1L", NULL, "DSP1CLK" },
+ { "DAC1L", NULL, "SYSDSPCLK" },
+
+ { "DAC1R", NULL, "AIF1CLK" },
+ { "DAC1R", NULL, "DSP1CLK" },
+ { "DAC1R", NULL, "SYSDSPCLK" },
+
+ { "AIF1DAC1L", NULL, "AIF1DACDAT" },
+ { "AIF1DAC1R", NULL, "AIF1DACDAT" },
+ { "AIF1DAC2L", NULL, "AIF1DACDAT" },
+ { "AIF1DAC2R", NULL, "AIF1DACDAT" },
+
+ /* DAC1 inputs */
+ { "DAC1L", NULL, "DAC1L Mixer" },
+ { "DAC1L Mixer", "AIF1.1 Switch", "AIF1DAC1L" },
+ { "DAC1L Mixer", "AIF1.2 Switch", "AIF1DAC2L" },
+ { "DAC1L Mixer", "Left Sidetone Switch", "Left Sidetone" },
+ { "DAC1L Mixer", "Right Sidetone Switch", "Right Sidetone" },
+
+ { "DAC1R", NULL, "DAC1R Mixer" },
+ { "DAC1R Mixer", "AIF1.1 Switch", "AIF1DAC1R" },
+ { "DAC1R Mixer", "AIF1.2 Switch", "AIF1DAC2R" },
+ { "DAC1R Mixer", "Left Sidetone Switch", "Left Sidetone" },
+ { "DAC1R Mixer", "Right Sidetone Switch", "Right Sidetone" },
+
+ /* DAC2/AIF2 outputs */
+ { "DAC2L", NULL, "AIF2DAC2L Mixer" },
+ { "AIF2DAC2L Mixer", "AIF1.2 Switch", "AIF1DAC2L" },
+ { "AIF2DAC2L Mixer", "AIF1.1 Switch", "AIF1DAC1L" },
+
+ { "DAC2R", NULL, "AIF2DAC2R Mixer" },
+ { "AIF2DAC2R Mixer", "AIF1.2 Switch", "AIF1DAC2R" },
+ { "AIF2DAC2R Mixer", "AIF1.1 Switch", "AIF1DAC1R" },
+
+ /* Output stages */
+ { "Headphone PGA", NULL, "DAC1L" },
+ { "Headphone PGA", NULL, "DAC1R" },
+
+ { "Headphone PGA", NULL, "DAC2L" },
+ { "Headphone PGA", NULL, "DAC2R" },
+
+ { "Headphone PGA", NULL, "Headphone Supply" },
+ { "Headphone PGA", NULL, "CLK_SYS" },
+ { "Headphone PGA", NULL, "LDO2" },
+
+ { "HP1L", NULL, "Headphone PGA" },
+ { "HP1R", NULL, "Headphone PGA" },
+
+ { "SPK1L Driver", "DAC1L", "DAC1L" },
+ { "SPK1L Driver", "DAC1R", "DAC1R" },
+ { "SPK1L Driver", "DAC2L", "DAC2L" },
+ { "SPK1L Driver", "DAC2R", "DAC2R" },
+ { "SPK1L Driver", NULL, "CLK_SYS" },
+
+ { "SPK1R Driver", "DAC1L", "DAC1L" },
+ { "SPK1R Driver", "DAC1R", "DAC1R" },
+ { "SPK1R Driver", "DAC2L", "DAC2L" },
+ { "SPK1R Driver", "DAC2R", "DAC2R" },
+ { "SPK1R Driver", NULL, "CLK_SYS" },
+
+ { "SPK2L Driver", "DAC1L", "DAC1L" },
+ { "SPK2L Driver", "DAC1R", "DAC1R" },
+ { "SPK2L Driver", "DAC2L", "DAC2L" },
+ { "SPK2L Driver", "DAC2R", "DAC2R" },
+ { "SPK2L Driver", NULL, "CLK_SYS" },
+
+ { "SPK2R Driver", "DAC1L", "DAC1L" },
+ { "SPK2R Driver", "DAC1R", "DAC1R" },
+ { "SPK2R Driver", "DAC2L", "DAC2L" },
+ { "SPK2R Driver", "DAC2R", "DAC2R" },
+ { "SPK2R Driver", NULL, "CLK_SYS" },
+
+ { "SPK1L", NULL, "SPK1L Driver" },
+ { "SPK1R", NULL, "SPK1R Driver" },
+ { "SPK2L", NULL, "SPK2L Driver" },
+ { "SPK2R", NULL, "SPK2R Driver" }
+};
+
+static int wm8995_volatile(struct snd_soc_codec *codec, unsigned int reg)
+{
+ /* out of bounds registers are generally considered
+ * volatile to support register banks that are partially
+ * owned by something else for e.g. a DSP
+ */
+ if (reg > WM8995_MAX_CACHED_REGISTER)
+ return 1;
+
+ switch (reg) {
+ case WM8995_SOFTWARE_RESET:
+ case WM8995_DC_SERVO_READBACK_0:
+ case WM8995_INTERRUPT_STATUS_1:
+ case WM8995_INTERRUPT_STATUS_2:
+ case WM8995_INTERRUPT_STATUS_1_MASK:
+ case WM8995_INTERRUPT_STATUS_2_MASK:
+ case WM8995_INTERRUPT_CONTROL:
+ case WM8995_ACCESSORY_DETECT_MODE1:
+ case WM8995_ACCESSORY_DETECT_MODE2:
+ case WM8995_HEADPHONE_DETECT1:
+ case WM8995_HEADPHONE_DETECT2:
+ return 1;
+ }
+
+ return 0;
+}
+
+static int wm8995_aif_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int mute_reg;
+
+ switch (dai->id) {
+ case 0:
+ mute_reg = WM8995_AIF1_DAC1_FILTERS_1;
+ break;
+ case 1:
+ mute_reg = WM8995_AIF2_DAC_FILTERS_1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, mute_reg, WM8995_AIF1DAC1_MUTE_MASK,
+ !!mute << WM8995_AIF1DAC1_MUTE_SHIFT);
+ return 0;
+}
+
+static int wm8995_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec;
+ int master;
+ int aif;
+
+ codec = dai->codec;
+
+ master = 0;
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ master = WM8995_AIF1_MSTR;
+ break;
+ default:
+ dev_err(dai->dev, "Unknown master/slave configuration\n");
+ return -EINVAL;
+ }
+
+ aif = 0;
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_B:
+ aif |= WM8995_AIF1_LRCLK_INV;
+ case SND_SOC_DAIFMT_DSP_A:
+ aif |= (0x3 << WM8995_AIF1_FMT_SHIFT);
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif |= (0x2 << WM8995_AIF1_FMT_SHIFT);
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif |= (0x1 << WM8995_AIF1_FMT_SHIFT);
+ break;
+ default:
+ dev_err(dai->dev, "Unknown dai format\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif |= WM8995_AIF1_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif |= WM8995_AIF1_BCLK_INV | WM8995_AIF1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif |= WM8995_AIF1_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif |= WM8995_AIF1_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8995_AIF1_CONTROL_1,
+ WM8995_AIF1_BCLK_INV_MASK |
+ WM8995_AIF1_LRCLK_INV_MASK |
+ WM8995_AIF1_FMT_MASK, aif);
+ snd_soc_update_bits(codec, WM8995_AIF1_MASTER_SLAVE,
+ WM8995_AIF1_MSTR_MASK, master);
+ return 0;
+}
+
+static const int srs[] = {
+ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100,
+ 48000, 88200, 96000
+};
+
+static const int fs_ratios[] = {
+ -1 /* reserved */,
+ 128, 192, 256, 384, 512, 768, 1024, 1408, 1536
+};
+
+static const int bclk_divs[] = {
+ 10, 15, 20, 30, 40, 55, 60, 80, 110, 120, 160, 220, 240, 320, 440, 480
+};
+
+static int wm8995_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec;
+ struct wm8995_priv *wm8995;
+ int aif1_reg;
+ int bclk_reg;
+ int lrclk_reg;
+ int rate_reg;
+ int bclk_rate;
+ int aif1;
+ int lrclk, bclk;
+ int i, rate_val, best, best_val, cur_val;
+
+ codec = dai->codec;
+ wm8995 = snd_soc_codec_get_drvdata(codec);
+
+ switch (dai->id) {
+ case 0:
+ aif1_reg = WM8995_AIF1_CONTROL_1;
+ bclk_reg = WM8995_AIF1_BCLK;
+ rate_reg = WM8995_AIF1_RATE;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK /* ||
+ wm8995->lrclk_shared[0] */) {
+ lrclk_reg = WM8995_AIF1DAC_LRCLK;
+ } else {
+ lrclk_reg = WM8995_AIF1ADC_LRCLK;
+ dev_dbg(codec->dev, "AIF1 using split LRCLK\n");
+ }
+ break;
+ case 1:
+ aif1_reg = WM8995_AIF2_CONTROL_1;
+ bclk_reg = WM8995_AIF2_BCLK;
+ rate_reg = WM8995_AIF2_RATE;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK /* ||
+ wm8995->lrclk_shared[1] */) {
+ lrclk_reg = WM8995_AIF2DAC_LRCLK;
+ } else {
+ lrclk_reg = WM8995_AIF2ADC_LRCLK;
+ dev_dbg(codec->dev, "AIF2 using split LRCLK\n");
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ bclk_rate = snd_soc_params_to_bclk(params);
+ if (bclk_rate < 0)
+ return bclk_rate;
+
+ aif1 = 0;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ aif1 |= (0x1 << WM8995_AIF1_WL_SHIFT);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ aif1 |= (0x2 << WM8995_AIF1_WL_SHIFT);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ aif1 |= (0x3 << WM8995_AIF1_WL_SHIFT);
+ break;
+ default:
+ dev_err(dai->dev, "Unsupported word length %u\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ /* try to find a suitable sample rate */
+ for (i = 0; i < ARRAY_SIZE(srs); ++i)
+ if (srs[i] == params_rate(params))
+ break;
+ if (i == ARRAY_SIZE(srs)) {
+ dev_err(dai->dev, "Sample rate %d is not supported\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+ rate_val = i << WM8995_AIF1_SR_SHIFT;
+
+ dev_dbg(dai->dev, "Sample rate is %dHz\n", srs[i]);
+ dev_dbg(dai->dev, "AIF%dCLK is %dHz, target BCLK %dHz\n",
+ dai->id + 1, wm8995->aifclk[dai->id], bclk_rate);
+
+ /* AIFCLK/fs ratio; look for a close match in either direction */
+ best = 1;
+ best_val = abs((fs_ratios[1] * params_rate(params))
+ - wm8995->aifclk[dai->id]);
+ for (i = 2; i < ARRAY_SIZE(fs_ratios); i++) {
+ cur_val = abs((fs_ratios[i] * params_rate(params))
+ - wm8995->aifclk[dai->id]);
+ if (cur_val >= best_val)
+ continue;
+ best = i;
+ best_val = cur_val;
+ }
+ rate_val |= best;
+
+ dev_dbg(dai->dev, "Selected AIF%dCLK/fs = %d\n",
+ dai->id + 1, fs_ratios[best]);
+
+ /*
+ * We may not get quite the right frequency if using
+ * approximate clocks so look for the closest match that is
+ * higher than the target (we need to ensure that there enough
+ * BCLKs to clock out the samples).
+ */
+ best = 0;
+ bclk = 0;
+ for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+ cur_val = (wm8995->aifclk[dai->id] * 10 / bclk_divs[i]) - bclk_rate;
+ if (cur_val < 0) /* BCLK table is sorted */
+ break;
+ best = i;
+ }
+ bclk |= best << WM8995_AIF1_BCLK_DIV_SHIFT;
+
+ bclk_rate = wm8995->aifclk[dai->id] * 10 / bclk_divs[best];
+ dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n",
+ bclk_divs[best], bclk_rate);
+
+ lrclk = bclk_rate / params_rate(params);
+ dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n",
+ lrclk, bclk_rate / lrclk);
+
+ snd_soc_update_bits(codec, aif1_reg,
+ WM8995_AIF1_WL_MASK, aif1);
+ snd_soc_update_bits(codec, bclk_reg,
+ WM8995_AIF1_BCLK_DIV_MASK, bclk);
+ snd_soc_update_bits(codec, lrclk_reg,
+ WM8995_AIF1DAC_RATE_MASK, lrclk);
+ snd_soc_update_bits(codec, rate_reg,
+ WM8995_AIF1_SR_MASK |
+ WM8995_AIF1CLK_RATE_MASK, rate_val);
+ return 0;
+}
+
+static int wm8995_set_tristate(struct snd_soc_dai *codec_dai, int tristate)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int reg, val, mask;
+
+ switch (codec_dai->id) {
+ case 0:
+ reg = WM8995_AIF1_MASTER_SLAVE;
+ mask = WM8995_AIF1_TRI;
+ break;
+ case 1:
+ reg = WM8995_AIF2_MASTER_SLAVE;
+ mask = WM8995_AIF2_TRI;
+ break;
+ case 2:
+ reg = WM8995_POWER_MANAGEMENT_5;
+ mask = WM8995_AIF3_TRI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (tristate)
+ val = mask;
+ else
+ val = 0;
+
+ return snd_soc_update_bits(codec, reg, mask, val);
+}
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+struct fll_div {
+ u16 outdiv;
+ u16 n;
+ u16 k;
+ u16 clk_ref_div;
+ u16 fll_fratio;
+};
+
+static int wm8995_get_fll_config(struct fll_div *fll,
+ int freq_in, int freq_out)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ pr_debug("FLL input=%dHz, output=%dHz\n", freq_in, freq_out);
+
+ /* Scale the input frequency down to <= 13.5MHz */
+ fll->clk_ref_div = 0;
+ while (freq_in > 13500000) {
+ fll->clk_ref_div++;
+ freq_in /= 2;
+
+ if (fll->clk_ref_div > 3)
+ return -EINVAL;
+ }
+ pr_debug("CLK_REF_DIV=%d, Fref=%dHz\n", fll->clk_ref_div, freq_in);
+
+ /* Scale the output to give 90MHz<=Fvco<=100MHz */
+ fll->outdiv = 3;
+ while (freq_out * (fll->outdiv + 1) < 90000000) {
+ fll->outdiv++;
+ if (fll->outdiv > 63)
+ return -EINVAL;
+ }
+ freq_out *= fll->outdiv + 1;
+ pr_debug("OUTDIV=%d, Fvco=%dHz\n", fll->outdiv, freq_out);
+
+ if (freq_in > 1000000) {
+ fll->fll_fratio = 0;
+ } else if (freq_in > 256000) {
+ fll->fll_fratio = 1;
+ freq_in *= 2;
+ } else if (freq_in > 128000) {
+ fll->fll_fratio = 2;
+ freq_in *= 4;
+ } else if (freq_in > 64000) {
+ fll->fll_fratio = 3;
+ freq_in *= 8;
+ } else {
+ fll->fll_fratio = 4;
+ freq_in *= 16;
+ }
+ pr_debug("FLL_FRATIO=%d, Fref=%dHz\n", fll->fll_fratio, freq_in);
+
+ /* Now, calculate N.K */
+ Ndiv = freq_out / freq_in;
+
+ fll->n = Ndiv;
+ Nmod = freq_out % freq_in;
+ pr_debug("Nmod=%d\n", Nmod);
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, freq_in);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ fll->k = K / 10;
+
+ pr_debug("N=%x K=%x\n", fll->n, fll->k);
+
+ return 0;
+}
+
+static int wm8995_set_fll(struct snd_soc_dai *dai, int id,
+ int src, unsigned int freq_in,
+ unsigned int freq_out)
+{
+ struct snd_soc_codec *codec;
+ struct wm8995_priv *wm8995;
+ int reg_offset, ret;
+ struct fll_div fll;
+ u16 reg, aif1, aif2;
+
+ codec = dai->codec;
+ wm8995 = snd_soc_codec_get_drvdata(codec);
+
+ aif1 = snd_soc_read(codec, WM8995_AIF1_CLOCKING_1)
+ & WM8995_AIF1CLK_ENA;
+
+ aif2 = snd_soc_read(codec, WM8995_AIF2_CLOCKING_1)
+ & WM8995_AIF2CLK_ENA;
+
+ switch (id) {
+ case WM8995_FLL1:
+ reg_offset = 0;
+ id = 0;
+ break;
+ case WM8995_FLL2:
+ reg_offset = 0x20;
+ id = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (src) {
+ case 0:
+ /* Allow no source specification when stopping */
+ if (freq_out)
+ return -EINVAL;
+ break;
+ case WM8995_FLL_SRC_MCLK1:
+ case WM8995_FLL_SRC_MCLK2:
+ case WM8995_FLL_SRC_LRCLK:
+ case WM8995_FLL_SRC_BCLK:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Are we changing anything? */
+ if (wm8995->fll[id].src == src &&
+ wm8995->fll[id].in == freq_in && wm8995->fll[id].out == freq_out)
+ return 0;
+
+ /* If we're stopping the FLL redo the old config - no
+ * registers will actually be written but we avoid GCC flow
+ * analysis bugs spewing warnings.
+ */
+ if (freq_out)
+ ret = wm8995_get_fll_config(&fll, freq_in, freq_out);
+ else
+ ret = wm8995_get_fll_config(&fll, wm8995->fll[id].in,
+ wm8995->fll[id].out);
+ if (ret < 0)
+ return ret;
+
+ /* Gate the AIF clocks while we reclock */
+ snd_soc_update_bits(codec, WM8995_AIF1_CLOCKING_1,
+ WM8995_AIF1CLK_ENA_MASK, 0);
+ snd_soc_update_bits(codec, WM8995_AIF2_CLOCKING_1,
+ WM8995_AIF2CLK_ENA_MASK, 0);
+
+ /* We always need to disable the FLL while reconfiguring */
+ snd_soc_update_bits(codec, WM8995_FLL1_CONTROL_1 + reg_offset,
+ WM8995_FLL1_ENA_MASK, 0);
+
+ reg = (fll.outdiv << WM8995_FLL1_OUTDIV_SHIFT) |
+ (fll.fll_fratio << WM8995_FLL1_FRATIO_SHIFT);
+ snd_soc_update_bits(codec, WM8995_FLL1_CONTROL_2 + reg_offset,
+ WM8995_FLL1_OUTDIV_MASK |
+ WM8995_FLL1_FRATIO_MASK, reg);
+
+ snd_soc_write(codec, WM8995_FLL1_CONTROL_3 + reg_offset, fll.k);
+
+ snd_soc_update_bits(codec, WM8995_FLL1_CONTROL_4 + reg_offset,
+ WM8995_FLL1_N_MASK,
+ fll.n << WM8995_FLL1_N_SHIFT);
+
+ snd_soc_update_bits(codec, WM8995_FLL1_CONTROL_5 + reg_offset,
+ WM8995_FLL1_REFCLK_DIV_MASK |
+ WM8995_FLL1_REFCLK_SRC_MASK,
+ (fll.clk_ref_div << WM8995_FLL1_REFCLK_DIV_SHIFT) |
+ (src - 1));
+
+ if (freq_out)
+ snd_soc_update_bits(codec, WM8995_FLL1_CONTROL_1 + reg_offset,
+ WM8995_FLL1_ENA_MASK, WM8995_FLL1_ENA);
+
+ wm8995->fll[id].in = freq_in;
+ wm8995->fll[id].out = freq_out;
+ wm8995->fll[id].src = src;
+
+ /* Enable any gated AIF clocks */
+ snd_soc_update_bits(codec, WM8995_AIF1_CLOCKING_1,
+ WM8995_AIF1CLK_ENA_MASK, aif1);
+ snd_soc_update_bits(codec, WM8995_AIF2_CLOCKING_1,
+ WM8995_AIF2CLK_ENA_MASK, aif2);
+
+ configure_clock(codec);
+
+ return 0;
+}
+
+static int wm8995_set_dai_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec;
+ struct wm8995_priv *wm8995;
+
+ codec = dai->codec;
+ wm8995 = snd_soc_codec_get_drvdata(codec);
+
+ switch (dai->id) {
+ case 0:
+ case 1:
+ break;
+ default:
+ /* AIF3 shares clocking with AIF1/2 */
+ return -EINVAL;
+ }
+
+ switch (clk_id) {
+ case WM8995_SYSCLK_MCLK1:
+ wm8995->sysclk[dai->id] = WM8995_SYSCLK_MCLK1;
+ wm8995->mclk[0] = freq;
+ dev_dbg(dai->dev, "AIF%d using MCLK1 at %uHz\n",
+ dai->id + 1, freq);
+ break;
+ case WM8995_SYSCLK_MCLK2:
+ wm8995->sysclk[dai->id] = WM8995_SYSCLK_MCLK1;
+ wm8995->mclk[1] = freq;
+ dev_dbg(dai->dev, "AIF%d using MCLK2 at %uHz\n",
+ dai->id + 1, freq);
+ break;
+ case WM8995_SYSCLK_FLL1:
+ wm8995->sysclk[dai->id] = WM8995_SYSCLK_FLL1;
+ dev_dbg(dai->dev, "AIF%d using FLL1\n", dai->id + 1);
+ break;
+ case WM8995_SYSCLK_FLL2:
+ wm8995->sysclk[dai->id] = WM8995_SYSCLK_FLL2;
+ dev_dbg(dai->dev, "AIF%d using FLL2\n", dai->id + 1);
+ break;
+ case WM8995_SYSCLK_OPCLK:
+ default:
+ dev_err(dai->dev, "Unknown clock source %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ configure_clock(codec);
+
+ return 0;
+}
+
+static int wm8995_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8995_priv *wm8995;
+ int ret;
+
+ wm8995 = snd_soc_codec_get_drvdata(codec);
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies),
+ wm8995->supplies);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_cache_sync(codec);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to sync cache: %d\n", ret);
+ return ret;
+ }
+
+ snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1,
+ WM8995_BG_ENA_MASK, WM8995_BG_ENA);
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1,
+ WM8995_BG_ENA_MASK, 0);
+ regulator_bulk_disable(ARRAY_SIZE(wm8995->supplies),
+ wm8995->supplies);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm8995_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8995_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8995_resume(struct snd_soc_codec *codec)
+{
+ wm8995_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+#else
+#define wm8995_suspend NULL
+#define wm8995_resume NULL
+#endif
+
+static int wm8995_remove(struct snd_soc_codec *codec)
+{
+ struct wm8995_priv *wm8995;
+ struct i2c_client *i2c;
+
+ i2c = container_of(codec->dev, struct i2c_client, dev);
+ wm8995 = snd_soc_codec_get_drvdata(codec);
+ wm8995_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8995_probe(struct snd_soc_codec *codec)
+{
+ struct wm8995_priv *wm8995;
+ int i;
+ int ret;
+
+ codec->dapm.idle_bias_off = 1;
+ wm8995 = snd_soc_codec_get_drvdata(codec);
+ wm8995->codec = codec;
+
+ ret = snd_soc_codec_set_cache_io(codec, 16, 16, wm8995->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache i/o: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++)
+ wm8995->supplies[i].supply = wm8995_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8995->supplies),
+ wm8995->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ wm8995->disable_nb[0].notifier_call = wm8995_regulator_event_0;
+ wm8995->disable_nb[1].notifier_call = wm8995_regulator_event_1;
+ wm8995->disable_nb[2].notifier_call = wm8995_regulator_event_2;
+ wm8995->disable_nb[3].notifier_call = wm8995_regulator_event_3;
+ wm8995->disable_nb[4].notifier_call = wm8995_regulator_event_4;
+ wm8995->disable_nb[5].notifier_call = wm8995_regulator_event_5;
+ wm8995->disable_nb[6].notifier_call = wm8995_regulator_event_6;
+ wm8995->disable_nb[7].notifier_call = wm8995_regulator_event_7;
+
+ /* This should really be moved into the regulator core */
+ for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) {
+ ret = regulator_register_notifier(wm8995->supplies[i].consumer,
+ &wm8995->disable_nb[i]);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to register regulator notifier: %d\n",
+ ret);
+ }
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies),
+ wm8995->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_reg_get;
+ }
+
+ ret = snd_soc_read(codec, WM8995_SOFTWARE_RESET);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read device ID: %d\n", ret);
+ goto err_reg_enable;
+ }
+
+ if (ret != 0x8995) {
+ dev_err(codec->dev, "Invalid device ID: %#x\n", ret);
+ goto err_reg_enable;
+ }
+
+ ret = snd_soc_write(codec, WM8995_SOFTWARE_RESET, 0);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ goto err_reg_enable;
+ }
+
+ wm8995_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Latch volume updates (right only; we always do left then right). */
+ snd_soc_update_bits(codec, WM8995_AIF1_DAC1_RIGHT_VOLUME,
+ WM8995_AIF1DAC1_VU_MASK, WM8995_AIF1DAC1_VU);
+ snd_soc_update_bits(codec, WM8995_AIF1_DAC2_RIGHT_VOLUME,
+ WM8995_AIF1DAC2_VU_MASK, WM8995_AIF1DAC2_VU);
+ snd_soc_update_bits(codec, WM8995_AIF2_DAC_RIGHT_VOLUME,
+ WM8995_AIF2DAC_VU_MASK, WM8995_AIF2DAC_VU);
+ snd_soc_update_bits(codec, WM8995_AIF1_ADC1_RIGHT_VOLUME,
+ WM8995_AIF1ADC1_VU_MASK, WM8995_AIF1ADC1_VU);
+ snd_soc_update_bits(codec, WM8995_AIF1_ADC2_RIGHT_VOLUME,
+ WM8995_AIF1ADC2_VU_MASK, WM8995_AIF1ADC2_VU);
+ snd_soc_update_bits(codec, WM8995_AIF2_ADC_RIGHT_VOLUME,
+ WM8995_AIF2ADC_VU_MASK, WM8995_AIF1ADC2_VU);
+ snd_soc_update_bits(codec, WM8995_DAC1_RIGHT_VOLUME,
+ WM8995_DAC1_VU_MASK, WM8995_DAC1_VU);
+ snd_soc_update_bits(codec, WM8995_DAC2_RIGHT_VOLUME,
+ WM8995_DAC2_VU_MASK, WM8995_DAC2_VU);
+ snd_soc_update_bits(codec, WM8995_RIGHT_LINE_INPUT_1_VOLUME,
+ WM8995_IN1_VU_MASK, WM8995_IN1_VU);
+
+ wm8995_update_class_w(codec);
+
+ snd_soc_add_controls(codec, wm8995_snd_controls,
+ ARRAY_SIZE(wm8995_snd_controls));
+ snd_soc_dapm_new_controls(&codec->dapm, wm8995_dapm_widgets,
+ ARRAY_SIZE(wm8995_dapm_widgets));
+ snd_soc_dapm_add_routes(&codec->dapm, wm8995_intercon,
+ ARRAY_SIZE(wm8995_intercon));
+
+ return 0;
+
+err_reg_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8995->supplies), wm8995->supplies);
+err_reg_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8995->supplies), wm8995->supplies);
+ return ret;
+}
+
+#define WM8995_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8995_aif1_dai_ops = {
+ .set_sysclk = wm8995_set_dai_sysclk,
+ .set_fmt = wm8995_set_dai_fmt,
+ .hw_params = wm8995_hw_params,
+ .digital_mute = wm8995_aif_mute,
+ .set_pll = wm8995_set_fll,
+ .set_tristate = wm8995_set_tristate,
+};
+
+static struct snd_soc_dai_ops wm8995_aif2_dai_ops = {
+ .set_sysclk = wm8995_set_dai_sysclk,
+ .set_fmt = wm8995_set_dai_fmt,
+ .hw_params = wm8995_hw_params,
+ .digital_mute = wm8995_aif_mute,
+ .set_pll = wm8995_set_fll,
+ .set_tristate = wm8995_set_tristate,
+};
+
+static struct snd_soc_dai_ops wm8995_aif3_dai_ops = {
+ .set_tristate = wm8995_set_tristate,
+};
+
+static struct snd_soc_dai_driver wm8995_dai[] = {
+ {
+ .name = "wm8995-aif1",
+ .playback = {
+ .stream_name = "AIF1 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = WM8995_FORMATS
+ },
+ .capture = {
+ .stream_name = "AIF1 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = WM8995_FORMATS
+ },
+ .ops = &wm8995_aif1_dai_ops
+ },
+ {
+ .name = "wm8995-aif2",
+ .playback = {
+ .stream_name = "AIF2 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = WM8995_FORMATS
+ },
+ .capture = {
+ .stream_name = "AIF2 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = WM8995_FORMATS
+ },
+ .ops = &wm8995_aif2_dai_ops
+ },
+ {
+ .name = "wm8995-aif3",
+ .playback = {
+ .stream_name = "AIF3 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = WM8995_FORMATS
+ },
+ .capture = {
+ .stream_name = "AIF3 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = WM8995_FORMATS
+ },
+ .ops = &wm8995_aif3_dai_ops
+ }
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8995 = {
+ .probe = wm8995_probe,
+ .remove = wm8995_remove,
+ .suspend = wm8995_suspend,
+ .resume = wm8995_resume,
+ .set_bias_level = wm8995_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8995_reg_defs),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8995_reg_defs,
+ .volatile_register = wm8995_volatile,
+ .compress_type = SND_SOC_RBTREE_COMPRESSION
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8995_spi_probe(struct spi_device *spi)
+{
+ struct wm8995_priv *wm8995;
+ int ret;
+
+ wm8995 = kzalloc(sizeof *wm8995, GFP_KERNEL);
+ if (!wm8995)
+ return -ENOMEM;
+
+ wm8995->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8995);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8995, wm8995_dai,
+ ARRAY_SIZE(wm8995_dai));
+ if (ret < 0)
+ kfree(wm8995);
+ return ret;
+}
+
+static int __devexit wm8995_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8995_spi_driver = {
+ .driver = {
+ .name = "wm8995",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8995_spi_probe,
+ .remove = __devexit_p(wm8995_spi_remove)
+};
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8995_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8995_priv *wm8995;
+ int ret;
+
+ wm8995 = kzalloc(sizeof *wm8995, GFP_KERNEL);
+ if (!wm8995)
+ return -ENOMEM;
+
+ wm8995->control_type = SND_SOC_I2C;
+ i2c_set_clientdata(i2c, wm8995);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8995, wm8995_dai,
+ ARRAY_SIZE(wm8995_dai));
+ if (ret < 0)
+ kfree(wm8995);
+ return ret;
+}
+
+static __devexit int wm8995_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8995_i2c_id[] = {
+ {"wm8995", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, wm8995_i2c_id);
+
+static struct i2c_driver wm8995_i2c_driver = {
+ .driver = {
+ .name = "wm8995",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8995_i2c_probe,
+ .remove = __devexit_p(wm8995_i2c_remove),
+ .id_table = wm8995_i2c_id
+};
+#endif
+
+static int __init wm8995_modinit(void)
+{
+ int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8995_i2c_driver);
+ if (ret) {
+ printk(KERN_ERR "Failed to register wm8995 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8995_spi_driver);
+ if (ret) {
+ printk(KERN_ERR "Failed to register wm8995 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+
+module_init(wm8995_modinit);
+
+static void __exit wm8995_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8995_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8995_spi_driver);
+#endif
+}
+
+module_exit(wm8995_exit);
+
+MODULE_DESCRIPTION("ASoC WM8995 driver");
+MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8995.h b/sound/soc/codecs/wm8995.h
new file mode 100644
index 00000000..5642121c
--- /dev/null
+++ b/sound/soc/codecs/wm8995.h
@@ -0,0 +1,4269 @@
+/*
+ * wm8995.h -- WM8995 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8995_H
+#define _WM8995_H
+
+#include <asm/types.h>
+
+/*
+ * Register values.
+ */
+#define WM8995_SOFTWARE_RESET 0x00
+#define WM8995_POWER_MANAGEMENT_1 0x01
+#define WM8995_POWER_MANAGEMENT_2 0x02
+#define WM8995_POWER_MANAGEMENT_3 0x03
+#define WM8995_POWER_MANAGEMENT_4 0x04
+#define WM8995_POWER_MANAGEMENT_5 0x05
+#define WM8995_LEFT_LINE_INPUT_1_VOLUME 0x10
+#define WM8995_RIGHT_LINE_INPUT_1_VOLUME 0x11
+#define WM8995_LEFT_LINE_INPUT_CONTROL 0x12
+#define WM8995_DAC1_LEFT_VOLUME 0x18
+#define WM8995_DAC1_RIGHT_VOLUME 0x19
+#define WM8995_DAC2_LEFT_VOLUME 0x1A
+#define WM8995_DAC2_RIGHT_VOLUME 0x1B
+#define WM8995_OUTPUT_VOLUME_ZC_1 0x1C
+#define WM8995_MICBIAS_1 0x20
+#define WM8995_MICBIAS_2 0x21
+#define WM8995_LDO_1 0x28
+#define WM8995_LDO_2 0x29
+#define WM8995_ACCESSORY_DETECT_MODE1 0x30
+#define WM8995_ACCESSORY_DETECT_MODE2 0x31
+#define WM8995_HEADPHONE_DETECT1 0x34
+#define WM8995_HEADPHONE_DETECT2 0x35
+#define WM8995_MIC_DETECT_1 0x38
+#define WM8995_MIC_DETECT_2 0x39
+#define WM8995_CHARGE_PUMP_1 0x40
+#define WM8995_CLASS_W_1 0x45
+#define WM8995_DC_SERVO_1 0x50
+#define WM8995_DC_SERVO_2 0x51
+#define WM8995_DC_SERVO_3 0x52
+#define WM8995_DC_SERVO_5 0x54
+#define WM8995_DC_SERVO_6 0x55
+#define WM8995_DC_SERVO_7 0x56
+#define WM8995_DC_SERVO_READBACK_0 0x57
+#define WM8995_ANALOGUE_HP_1 0x60
+#define WM8995_ANALOGUE_HP_2 0x61
+#define WM8995_CHIP_REVISION 0x100
+#define WM8995_CONTROL_INTERFACE_1 0x101
+#define WM8995_CONTROL_INTERFACE_2 0x102
+#define WM8995_WRITE_SEQUENCER_CTRL_1 0x110
+#define WM8995_WRITE_SEQUENCER_CTRL_2 0x111
+#define WM8995_AIF1_CLOCKING_1 0x200
+#define WM8995_AIF1_CLOCKING_2 0x201
+#define WM8995_AIF2_CLOCKING_1 0x204
+#define WM8995_AIF2_CLOCKING_2 0x205
+#define WM8995_CLOCKING_1 0x208
+#define WM8995_CLOCKING_2 0x209
+#define WM8995_AIF1_RATE 0x210
+#define WM8995_AIF2_RATE 0x211
+#define WM8995_RATE_STATUS 0x212
+#define WM8995_FLL1_CONTROL_1 0x220
+#define WM8995_FLL1_CONTROL_2 0x221
+#define WM8995_FLL1_CONTROL_3 0x222
+#define WM8995_FLL1_CONTROL_4 0x223
+#define WM8995_FLL1_CONTROL_5 0x224
+#define WM8995_FLL2_CONTROL_1 0x240
+#define WM8995_FLL2_CONTROL_2 0x241
+#define WM8995_FLL2_CONTROL_3 0x242
+#define WM8995_FLL2_CONTROL_4 0x243
+#define WM8995_FLL2_CONTROL_5 0x244
+#define WM8995_AIF1_CONTROL_1 0x300
+#define WM8995_AIF1_CONTROL_2 0x301
+#define WM8995_AIF1_MASTER_SLAVE 0x302
+#define WM8995_AIF1_BCLK 0x303
+#define WM8995_AIF1ADC_LRCLK 0x304
+#define WM8995_AIF1DAC_LRCLK 0x305
+#define WM8995_AIF1DAC_DATA 0x306
+#define WM8995_AIF1ADC_DATA 0x307
+#define WM8995_AIF2_CONTROL_1 0x310
+#define WM8995_AIF2_CONTROL_2 0x311
+#define WM8995_AIF2_MASTER_SLAVE 0x312
+#define WM8995_AIF2_BCLK 0x313
+#define WM8995_AIF2ADC_LRCLK 0x314
+#define WM8995_AIF2DAC_LRCLK 0x315
+#define WM8995_AIF2DAC_DATA 0x316
+#define WM8995_AIF2ADC_DATA 0x317
+#define WM8995_AIF1_ADC1_LEFT_VOLUME 0x400
+#define WM8995_AIF1_ADC1_RIGHT_VOLUME 0x401
+#define WM8995_AIF1_DAC1_LEFT_VOLUME 0x402
+#define WM8995_AIF1_DAC1_RIGHT_VOLUME 0x403
+#define WM8995_AIF1_ADC2_LEFT_VOLUME 0x404
+#define WM8995_AIF1_ADC2_RIGHT_VOLUME 0x405
+#define WM8995_AIF1_DAC2_LEFT_VOLUME 0x406
+#define WM8995_AIF1_DAC2_RIGHT_VOLUME 0x407
+#define WM8995_AIF1_ADC1_FILTERS 0x410
+#define WM8995_AIF1_ADC2_FILTERS 0x411
+#define WM8995_AIF1_DAC1_FILTERS_1 0x420
+#define WM8995_AIF1_DAC1_FILTERS_2 0x421
+#define WM8995_AIF1_DAC2_FILTERS_1 0x422
+#define WM8995_AIF1_DAC2_FILTERS_2 0x423
+#define WM8995_AIF1_DRC1_1 0x440
+#define WM8995_AIF1_DRC1_2 0x441
+#define WM8995_AIF1_DRC1_3 0x442
+#define WM8995_AIF1_DRC1_4 0x443
+#define WM8995_AIF1_DRC1_5 0x444
+#define WM8995_AIF1_DRC2_1 0x450
+#define WM8995_AIF1_DRC2_2 0x451
+#define WM8995_AIF1_DRC2_3 0x452
+#define WM8995_AIF1_DRC2_4 0x453
+#define WM8995_AIF1_DRC2_5 0x454
+#define WM8995_AIF1_DAC1_EQ_GAINS_1 0x480
+#define WM8995_AIF1_DAC1_EQ_GAINS_2 0x481
+#define WM8995_AIF1_DAC1_EQ_BAND_1_A 0x482
+#define WM8995_AIF1_DAC1_EQ_BAND_1_B 0x483
+#define WM8995_AIF1_DAC1_EQ_BAND_1_PG 0x484
+#define WM8995_AIF1_DAC1_EQ_BAND_2_A 0x485
+#define WM8995_AIF1_DAC1_EQ_BAND_2_B 0x486
+#define WM8995_AIF1_DAC1_EQ_BAND_2_C 0x487
+#define WM8995_AIF1_DAC1_EQ_BAND_2_PG 0x488
+#define WM8995_AIF1_DAC1_EQ_BAND_3_A 0x489
+#define WM8995_AIF1_DAC1_EQ_BAND_3_B 0x48A
+#define WM8995_AIF1_DAC1_EQ_BAND_3_C 0x48B
+#define WM8995_AIF1_DAC1_EQ_BAND_3_PG 0x48C
+#define WM8995_AIF1_DAC1_EQ_BAND_4_A 0x48D
+#define WM8995_AIF1_DAC1_EQ_BAND_4_B 0x48E
+#define WM8995_AIF1_DAC1_EQ_BAND_4_C 0x48F
+#define WM8995_AIF1_DAC1_EQ_BAND_4_PG 0x490
+#define WM8995_AIF1_DAC1_EQ_BAND_5_A 0x491
+#define WM8995_AIF1_DAC1_EQ_BAND_5_B 0x492
+#define WM8995_AIF1_DAC1_EQ_BAND_5_PG 0x493
+#define WM8995_AIF1_DAC2_EQ_GAINS_1 0x4A0
+#define WM8995_AIF1_DAC2_EQ_GAINS_2 0x4A1
+#define WM8995_AIF1_DAC2_EQ_BAND_1_A 0x4A2
+#define WM8995_AIF1_DAC2_EQ_BAND_1_B 0x4A3
+#define WM8995_AIF1_DAC2_EQ_BAND_1_PG 0x4A4
+#define WM8995_AIF1_DAC2_EQ_BAND_2_A 0x4A5
+#define WM8995_AIF1_DAC2_EQ_BAND_2_B 0x4A6
+#define WM8995_AIF1_DAC2_EQ_BAND_2_C 0x4A7
+#define WM8995_AIF1_DAC2_EQ_BAND_2_PG 0x4A8
+#define WM8995_AIF1_DAC2_EQ_BAND_3_A 0x4A9
+#define WM8995_AIF1_DAC2_EQ_BAND_3_B 0x4AA
+#define WM8995_AIF1_DAC2_EQ_BAND_3_C 0x4AB
+#define WM8995_AIF1_DAC2_EQ_BAND_3_PG 0x4AC
+#define WM8995_AIF1_DAC2_EQ_BAND_4_A 0x4AD
+#define WM8995_AIF1_DAC2_EQ_BAND_4_B 0x4AE
+#define WM8995_AIF1_DAC2_EQ_BAND_4_C 0x4AF
+#define WM8995_AIF1_DAC2_EQ_BAND_4_PG 0x4B0
+#define WM8995_AIF1_DAC2_EQ_BAND_5_A 0x4B1
+#define WM8995_AIF1_DAC2_EQ_BAND_5_B 0x4B2
+#define WM8995_AIF1_DAC2_EQ_BAND_5_PG 0x4B3
+#define WM8995_AIF2_ADC_LEFT_VOLUME 0x500
+#define WM8995_AIF2_ADC_RIGHT_VOLUME 0x501
+#define WM8995_AIF2_DAC_LEFT_VOLUME 0x502
+#define WM8995_AIF2_DAC_RIGHT_VOLUME 0x503
+#define WM8995_AIF2_ADC_FILTERS 0x510
+#define WM8995_AIF2_DAC_FILTERS_1 0x520
+#define WM8995_AIF2_DAC_FILTERS_2 0x521
+#define WM8995_AIF2_DRC_1 0x540
+#define WM8995_AIF2_DRC_2 0x541
+#define WM8995_AIF2_DRC_3 0x542
+#define WM8995_AIF2_DRC_4 0x543
+#define WM8995_AIF2_DRC_5 0x544
+#define WM8995_AIF2_EQ_GAINS_1 0x580
+#define WM8995_AIF2_EQ_GAINS_2 0x581
+#define WM8995_AIF2_EQ_BAND_1_A 0x582
+#define WM8995_AIF2_EQ_BAND_1_B 0x583
+#define WM8995_AIF2_EQ_BAND_1_PG 0x584
+#define WM8995_AIF2_EQ_BAND_2_A 0x585
+#define WM8995_AIF2_EQ_BAND_2_B 0x586
+#define WM8995_AIF2_EQ_BAND_2_C 0x587
+#define WM8995_AIF2_EQ_BAND_2_PG 0x588
+#define WM8995_AIF2_EQ_BAND_3_A 0x589
+#define WM8995_AIF2_EQ_BAND_3_B 0x58A
+#define WM8995_AIF2_EQ_BAND_3_C 0x58B
+#define WM8995_AIF2_EQ_BAND_3_PG 0x58C
+#define WM8995_AIF2_EQ_BAND_4_A 0x58D
+#define WM8995_AIF2_EQ_BAND_4_B 0x58E
+#define WM8995_AIF2_EQ_BAND_4_C 0x58F
+#define WM8995_AIF2_EQ_BAND_4_PG 0x590
+#define WM8995_AIF2_EQ_BAND_5_A 0x591
+#define WM8995_AIF2_EQ_BAND_5_B 0x592
+#define WM8995_AIF2_EQ_BAND_5_PG 0x593
+#define WM8995_DAC1_MIXER_VOLUMES 0x600
+#define WM8995_DAC1_LEFT_MIXER_ROUTING 0x601
+#define WM8995_DAC1_RIGHT_MIXER_ROUTING 0x602
+#define WM8995_DAC2_MIXER_VOLUMES 0x603
+#define WM8995_DAC2_LEFT_MIXER_ROUTING 0x604
+#define WM8995_DAC2_RIGHT_MIXER_ROUTING 0x605
+#define WM8995_AIF1_ADC1_LEFT_MIXER_ROUTING 0x606
+#define WM8995_AIF1_ADC1_RIGHT_MIXER_ROUTING 0x607
+#define WM8995_AIF1_ADC2_LEFT_MIXER_ROUTING 0x608
+#define WM8995_AIF1_ADC2_RIGHT_MIXER_ROUTING 0x609
+#define WM8995_DAC_SOFTMUTE 0x610
+#define WM8995_OVERSAMPLING 0x620
+#define WM8995_SIDETONE 0x621
+#define WM8995_GPIO_1 0x700
+#define WM8995_GPIO_2 0x701
+#define WM8995_GPIO_3 0x702
+#define WM8995_GPIO_4 0x703
+#define WM8995_GPIO_5 0x704
+#define WM8995_GPIO_6 0x705
+#define WM8995_GPIO_7 0x706
+#define WM8995_GPIO_8 0x707
+#define WM8995_GPIO_9 0x708
+#define WM8995_GPIO_10 0x709
+#define WM8995_GPIO_11 0x70A
+#define WM8995_GPIO_12 0x70B
+#define WM8995_GPIO_13 0x70C
+#define WM8995_GPIO_14 0x70D
+#define WM8995_PULL_CONTROL_1 0x720
+#define WM8995_PULL_CONTROL_2 0x721
+#define WM8995_INTERRUPT_STATUS_1 0x730
+#define WM8995_INTERRUPT_STATUS_2 0x731
+#define WM8995_INTERRUPT_RAW_STATUS_2 0x732
+#define WM8995_INTERRUPT_STATUS_1_MASK 0x738
+#define WM8995_INTERRUPT_STATUS_2_MASK 0x739
+#define WM8995_INTERRUPT_CONTROL 0x740
+#define WM8995_LEFT_PDM_SPEAKER_1 0x800
+#define WM8995_RIGHT_PDM_SPEAKER_1 0x801
+#define WM8995_PDM_SPEAKER_1_MUTE_SEQUENCE 0x802
+#define WM8995_LEFT_PDM_SPEAKER_2 0x808
+#define WM8995_RIGHT_PDM_SPEAKER_2 0x809
+#define WM8995_PDM_SPEAKER_2_MUTE_SEQUENCE 0x80A
+#define WM8995_WRITE_SEQUENCER_0 0x3000
+#define WM8995_WRITE_SEQUENCER_1 0x3001
+#define WM8995_WRITE_SEQUENCER_2 0x3002
+#define WM8995_WRITE_SEQUENCER_3 0x3003
+#define WM8995_WRITE_SEQUENCER_4 0x3004
+#define WM8995_WRITE_SEQUENCER_5 0x3005
+#define WM8995_WRITE_SEQUENCER_6 0x3006
+#define WM8995_WRITE_SEQUENCER_7 0x3007
+#define WM8995_WRITE_SEQUENCER_8 0x3008
+#define WM8995_WRITE_SEQUENCER_9 0x3009
+#define WM8995_WRITE_SEQUENCER_10 0x300A
+#define WM8995_WRITE_SEQUENCER_11 0x300B
+#define WM8995_WRITE_SEQUENCER_12 0x300C
+#define WM8995_WRITE_SEQUENCER_13 0x300D
+#define WM8995_WRITE_SEQUENCER_14 0x300E
+#define WM8995_WRITE_SEQUENCER_15 0x300F
+#define WM8995_WRITE_SEQUENCER_16 0x3010
+#define WM8995_WRITE_SEQUENCER_17 0x3011
+#define WM8995_WRITE_SEQUENCER_18 0x3012
+#define WM8995_WRITE_SEQUENCER_19 0x3013
+#define WM8995_WRITE_SEQUENCER_20 0x3014
+#define WM8995_WRITE_SEQUENCER_21 0x3015
+#define WM8995_WRITE_SEQUENCER_22 0x3016
+#define WM8995_WRITE_SEQUENCER_23 0x3017
+#define WM8995_WRITE_SEQUENCER_24 0x3018
+#define WM8995_WRITE_SEQUENCER_25 0x3019
+#define WM8995_WRITE_SEQUENCER_26 0x301A
+#define WM8995_WRITE_SEQUENCER_27 0x301B
+#define WM8995_WRITE_SEQUENCER_28 0x301C
+#define WM8995_WRITE_SEQUENCER_29 0x301D
+#define WM8995_WRITE_SEQUENCER_30 0x301E
+#define WM8995_WRITE_SEQUENCER_31 0x301F
+#define WM8995_WRITE_SEQUENCER_32 0x3020
+#define WM8995_WRITE_SEQUENCER_33 0x3021
+#define WM8995_WRITE_SEQUENCER_34 0x3022
+#define WM8995_WRITE_SEQUENCER_35 0x3023
+#define WM8995_WRITE_SEQUENCER_36 0x3024
+#define WM8995_WRITE_SEQUENCER_37 0x3025
+#define WM8995_WRITE_SEQUENCER_38 0x3026
+#define WM8995_WRITE_SEQUENCER_39 0x3027
+#define WM8995_WRITE_SEQUENCER_40 0x3028
+#define WM8995_WRITE_SEQUENCER_41 0x3029
+#define WM8995_WRITE_SEQUENCER_42 0x302A
+#define WM8995_WRITE_SEQUENCER_43 0x302B
+#define WM8995_WRITE_SEQUENCER_44 0x302C
+#define WM8995_WRITE_SEQUENCER_45 0x302D
+#define WM8995_WRITE_SEQUENCER_46 0x302E
+#define WM8995_WRITE_SEQUENCER_47 0x302F
+#define WM8995_WRITE_SEQUENCER_48 0x3030
+#define WM8995_WRITE_SEQUENCER_49 0x3031
+#define WM8995_WRITE_SEQUENCER_50 0x3032
+#define WM8995_WRITE_SEQUENCER_51 0x3033
+#define WM8995_WRITE_SEQUENCER_52 0x3034
+#define WM8995_WRITE_SEQUENCER_53 0x3035
+#define WM8995_WRITE_SEQUENCER_54 0x3036
+#define WM8995_WRITE_SEQUENCER_55 0x3037
+#define WM8995_WRITE_SEQUENCER_56 0x3038
+#define WM8995_WRITE_SEQUENCER_57 0x3039
+#define WM8995_WRITE_SEQUENCER_58 0x303A
+#define WM8995_WRITE_SEQUENCER_59 0x303B
+#define WM8995_WRITE_SEQUENCER_60 0x303C
+#define WM8995_WRITE_SEQUENCER_61 0x303D
+#define WM8995_WRITE_SEQUENCER_62 0x303E
+#define WM8995_WRITE_SEQUENCER_63 0x303F
+#define WM8995_WRITE_SEQUENCER_64 0x3040
+#define WM8995_WRITE_SEQUENCER_65 0x3041
+#define WM8995_WRITE_SEQUENCER_66 0x3042
+#define WM8995_WRITE_SEQUENCER_67 0x3043
+#define WM8995_WRITE_SEQUENCER_68 0x3044
+#define WM8995_WRITE_SEQUENCER_69 0x3045
+#define WM8995_WRITE_SEQUENCER_70 0x3046
+#define WM8995_WRITE_SEQUENCER_71 0x3047
+#define WM8995_WRITE_SEQUENCER_72 0x3048
+#define WM8995_WRITE_SEQUENCER_73 0x3049
+#define WM8995_WRITE_SEQUENCER_74 0x304A
+#define WM8995_WRITE_SEQUENCER_75 0x304B
+#define WM8995_WRITE_SEQUENCER_76 0x304C
+#define WM8995_WRITE_SEQUENCER_77 0x304D
+#define WM8995_WRITE_SEQUENCER_78 0x304E
+#define WM8995_WRITE_SEQUENCER_79 0x304F
+#define WM8995_WRITE_SEQUENCER_80 0x3050
+#define WM8995_WRITE_SEQUENCER_81 0x3051
+#define WM8995_WRITE_SEQUENCER_82 0x3052
+#define WM8995_WRITE_SEQUENCER_83 0x3053
+#define WM8995_WRITE_SEQUENCER_84 0x3054
+#define WM8995_WRITE_SEQUENCER_85 0x3055
+#define WM8995_WRITE_SEQUENCER_86 0x3056
+#define WM8995_WRITE_SEQUENCER_87 0x3057
+#define WM8995_WRITE_SEQUENCER_88 0x3058
+#define WM8995_WRITE_SEQUENCER_89 0x3059
+#define WM8995_WRITE_SEQUENCER_90 0x305A
+#define WM8995_WRITE_SEQUENCER_91 0x305B
+#define WM8995_WRITE_SEQUENCER_92 0x305C
+#define WM8995_WRITE_SEQUENCER_93 0x305D
+#define WM8995_WRITE_SEQUENCER_94 0x305E
+#define WM8995_WRITE_SEQUENCER_95 0x305F
+#define WM8995_WRITE_SEQUENCER_96 0x3060
+#define WM8995_WRITE_SEQUENCER_97 0x3061
+#define WM8995_WRITE_SEQUENCER_98 0x3062
+#define WM8995_WRITE_SEQUENCER_99 0x3063
+#define WM8995_WRITE_SEQUENCER_100 0x3064
+#define WM8995_WRITE_SEQUENCER_101 0x3065
+#define WM8995_WRITE_SEQUENCER_102 0x3066
+#define WM8995_WRITE_SEQUENCER_103 0x3067
+#define WM8995_WRITE_SEQUENCER_104 0x3068
+#define WM8995_WRITE_SEQUENCER_105 0x3069
+#define WM8995_WRITE_SEQUENCER_106 0x306A
+#define WM8995_WRITE_SEQUENCER_107 0x306B
+#define WM8995_WRITE_SEQUENCER_108 0x306C
+#define WM8995_WRITE_SEQUENCER_109 0x306D
+#define WM8995_WRITE_SEQUENCER_110 0x306E
+#define WM8995_WRITE_SEQUENCER_111 0x306F
+#define WM8995_WRITE_SEQUENCER_112 0x3070
+#define WM8995_WRITE_SEQUENCER_113 0x3071
+#define WM8995_WRITE_SEQUENCER_114 0x3072
+#define WM8995_WRITE_SEQUENCER_115 0x3073
+#define WM8995_WRITE_SEQUENCER_116 0x3074
+#define WM8995_WRITE_SEQUENCER_117 0x3075
+#define WM8995_WRITE_SEQUENCER_118 0x3076
+#define WM8995_WRITE_SEQUENCER_119 0x3077
+#define WM8995_WRITE_SEQUENCER_120 0x3078
+#define WM8995_WRITE_SEQUENCER_121 0x3079
+#define WM8995_WRITE_SEQUENCER_122 0x307A
+#define WM8995_WRITE_SEQUENCER_123 0x307B
+#define WM8995_WRITE_SEQUENCER_124 0x307C
+#define WM8995_WRITE_SEQUENCER_125 0x307D
+#define WM8995_WRITE_SEQUENCER_126 0x307E
+#define WM8995_WRITE_SEQUENCER_127 0x307F
+#define WM8995_WRITE_SEQUENCER_128 0x3080
+#define WM8995_WRITE_SEQUENCER_129 0x3081
+#define WM8995_WRITE_SEQUENCER_130 0x3082
+#define WM8995_WRITE_SEQUENCER_131 0x3083
+#define WM8995_WRITE_SEQUENCER_132 0x3084
+#define WM8995_WRITE_SEQUENCER_133 0x3085
+#define WM8995_WRITE_SEQUENCER_134 0x3086
+#define WM8995_WRITE_SEQUENCER_135 0x3087
+#define WM8995_WRITE_SEQUENCER_136 0x3088
+#define WM8995_WRITE_SEQUENCER_137 0x3089
+#define WM8995_WRITE_SEQUENCER_138 0x308A
+#define WM8995_WRITE_SEQUENCER_139 0x308B
+#define WM8995_WRITE_SEQUENCER_140 0x308C
+#define WM8995_WRITE_SEQUENCER_141 0x308D
+#define WM8995_WRITE_SEQUENCER_142 0x308E
+#define WM8995_WRITE_SEQUENCER_143 0x308F
+#define WM8995_WRITE_SEQUENCER_144 0x3090
+#define WM8995_WRITE_SEQUENCER_145 0x3091
+#define WM8995_WRITE_SEQUENCER_146 0x3092
+#define WM8995_WRITE_SEQUENCER_147 0x3093
+#define WM8995_WRITE_SEQUENCER_148 0x3094
+#define WM8995_WRITE_SEQUENCER_149 0x3095
+#define WM8995_WRITE_SEQUENCER_150 0x3096
+#define WM8995_WRITE_SEQUENCER_151 0x3097
+#define WM8995_WRITE_SEQUENCER_152 0x3098
+#define WM8995_WRITE_SEQUENCER_153 0x3099
+#define WM8995_WRITE_SEQUENCER_154 0x309A
+#define WM8995_WRITE_SEQUENCER_155 0x309B
+#define WM8995_WRITE_SEQUENCER_156 0x309C
+#define WM8995_WRITE_SEQUENCER_157 0x309D
+#define WM8995_WRITE_SEQUENCER_158 0x309E
+#define WM8995_WRITE_SEQUENCER_159 0x309F
+#define WM8995_WRITE_SEQUENCER_160 0x30A0
+#define WM8995_WRITE_SEQUENCER_161 0x30A1
+#define WM8995_WRITE_SEQUENCER_162 0x30A2
+#define WM8995_WRITE_SEQUENCER_163 0x30A3
+#define WM8995_WRITE_SEQUENCER_164 0x30A4
+#define WM8995_WRITE_SEQUENCER_165 0x30A5
+#define WM8995_WRITE_SEQUENCER_166 0x30A6
+#define WM8995_WRITE_SEQUENCER_167 0x30A7
+#define WM8995_WRITE_SEQUENCER_168 0x30A8
+#define WM8995_WRITE_SEQUENCER_169 0x30A9
+#define WM8995_WRITE_SEQUENCER_170 0x30AA
+#define WM8995_WRITE_SEQUENCER_171 0x30AB
+#define WM8995_WRITE_SEQUENCER_172 0x30AC
+#define WM8995_WRITE_SEQUENCER_173 0x30AD
+#define WM8995_WRITE_SEQUENCER_174 0x30AE
+#define WM8995_WRITE_SEQUENCER_175 0x30AF
+#define WM8995_WRITE_SEQUENCER_176 0x30B0
+#define WM8995_WRITE_SEQUENCER_177 0x30B1
+#define WM8995_WRITE_SEQUENCER_178 0x30B2
+#define WM8995_WRITE_SEQUENCER_179 0x30B3
+#define WM8995_WRITE_SEQUENCER_180 0x30B4
+#define WM8995_WRITE_SEQUENCER_181 0x30B5
+#define WM8995_WRITE_SEQUENCER_182 0x30B6
+#define WM8995_WRITE_SEQUENCER_183 0x30B7
+#define WM8995_WRITE_SEQUENCER_184 0x30B8
+#define WM8995_WRITE_SEQUENCER_185 0x30B9
+#define WM8995_WRITE_SEQUENCER_186 0x30BA
+#define WM8995_WRITE_SEQUENCER_187 0x30BB
+#define WM8995_WRITE_SEQUENCER_188 0x30BC
+#define WM8995_WRITE_SEQUENCER_189 0x30BD
+#define WM8995_WRITE_SEQUENCER_190 0x30BE
+#define WM8995_WRITE_SEQUENCER_191 0x30BF
+#define WM8995_WRITE_SEQUENCER_192 0x30C0
+#define WM8995_WRITE_SEQUENCER_193 0x30C1
+#define WM8995_WRITE_SEQUENCER_194 0x30C2
+#define WM8995_WRITE_SEQUENCER_195 0x30C3
+#define WM8995_WRITE_SEQUENCER_196 0x30C4
+#define WM8995_WRITE_SEQUENCER_197 0x30C5
+#define WM8995_WRITE_SEQUENCER_198 0x30C6
+#define WM8995_WRITE_SEQUENCER_199 0x30C7
+#define WM8995_WRITE_SEQUENCER_200 0x30C8
+#define WM8995_WRITE_SEQUENCER_201 0x30C9
+#define WM8995_WRITE_SEQUENCER_202 0x30CA
+#define WM8995_WRITE_SEQUENCER_203 0x30CB
+#define WM8995_WRITE_SEQUENCER_204 0x30CC
+#define WM8995_WRITE_SEQUENCER_205 0x30CD
+#define WM8995_WRITE_SEQUENCER_206 0x30CE
+#define WM8995_WRITE_SEQUENCER_207 0x30CF
+#define WM8995_WRITE_SEQUENCER_208 0x30D0
+#define WM8995_WRITE_SEQUENCER_209 0x30D1
+#define WM8995_WRITE_SEQUENCER_210 0x30D2
+#define WM8995_WRITE_SEQUENCER_211 0x30D3
+#define WM8995_WRITE_SEQUENCER_212 0x30D4
+#define WM8995_WRITE_SEQUENCER_213 0x30D5
+#define WM8995_WRITE_SEQUENCER_214 0x30D6
+#define WM8995_WRITE_SEQUENCER_215 0x30D7
+#define WM8995_WRITE_SEQUENCER_216 0x30D8
+#define WM8995_WRITE_SEQUENCER_217 0x30D9
+#define WM8995_WRITE_SEQUENCER_218 0x30DA
+#define WM8995_WRITE_SEQUENCER_219 0x30DB
+#define WM8995_WRITE_SEQUENCER_220 0x30DC
+#define WM8995_WRITE_SEQUENCER_221 0x30DD
+#define WM8995_WRITE_SEQUENCER_222 0x30DE
+#define WM8995_WRITE_SEQUENCER_223 0x30DF
+#define WM8995_WRITE_SEQUENCER_224 0x30E0
+#define WM8995_WRITE_SEQUENCER_225 0x30E1
+#define WM8995_WRITE_SEQUENCER_226 0x30E2
+#define WM8995_WRITE_SEQUENCER_227 0x30E3
+#define WM8995_WRITE_SEQUENCER_228 0x30E4
+#define WM8995_WRITE_SEQUENCER_229 0x30E5
+#define WM8995_WRITE_SEQUENCER_230 0x30E6
+#define WM8995_WRITE_SEQUENCER_231 0x30E7
+#define WM8995_WRITE_SEQUENCER_232 0x30E8
+#define WM8995_WRITE_SEQUENCER_233 0x30E9
+#define WM8995_WRITE_SEQUENCER_234 0x30EA
+#define WM8995_WRITE_SEQUENCER_235 0x30EB
+#define WM8995_WRITE_SEQUENCER_236 0x30EC
+#define WM8995_WRITE_SEQUENCER_237 0x30ED
+#define WM8995_WRITE_SEQUENCER_238 0x30EE
+#define WM8995_WRITE_SEQUENCER_239 0x30EF
+#define WM8995_WRITE_SEQUENCER_240 0x30F0
+#define WM8995_WRITE_SEQUENCER_241 0x30F1
+#define WM8995_WRITE_SEQUENCER_242 0x30F2
+#define WM8995_WRITE_SEQUENCER_243 0x30F3
+#define WM8995_WRITE_SEQUENCER_244 0x30F4
+#define WM8995_WRITE_SEQUENCER_245 0x30F5
+#define WM8995_WRITE_SEQUENCER_246 0x30F6
+#define WM8995_WRITE_SEQUENCER_247 0x30F7
+#define WM8995_WRITE_SEQUENCER_248 0x30F8
+#define WM8995_WRITE_SEQUENCER_249 0x30F9
+#define WM8995_WRITE_SEQUENCER_250 0x30FA
+#define WM8995_WRITE_SEQUENCER_251 0x30FB
+#define WM8995_WRITE_SEQUENCER_252 0x30FC
+#define WM8995_WRITE_SEQUENCER_253 0x30FD
+#define WM8995_WRITE_SEQUENCER_254 0x30FE
+#define WM8995_WRITE_SEQUENCER_255 0x30FF
+#define WM8995_WRITE_SEQUENCER_256 0x3100
+#define WM8995_WRITE_SEQUENCER_257 0x3101
+#define WM8995_WRITE_SEQUENCER_258 0x3102
+#define WM8995_WRITE_SEQUENCER_259 0x3103
+#define WM8995_WRITE_SEQUENCER_260 0x3104
+#define WM8995_WRITE_SEQUENCER_261 0x3105
+#define WM8995_WRITE_SEQUENCER_262 0x3106
+#define WM8995_WRITE_SEQUENCER_263 0x3107
+#define WM8995_WRITE_SEQUENCER_264 0x3108
+#define WM8995_WRITE_SEQUENCER_265 0x3109
+#define WM8995_WRITE_SEQUENCER_266 0x310A
+#define WM8995_WRITE_SEQUENCER_267 0x310B
+#define WM8995_WRITE_SEQUENCER_268 0x310C
+#define WM8995_WRITE_SEQUENCER_269 0x310D
+#define WM8995_WRITE_SEQUENCER_270 0x310E
+#define WM8995_WRITE_SEQUENCER_271 0x310F
+#define WM8995_WRITE_SEQUENCER_272 0x3110
+#define WM8995_WRITE_SEQUENCER_273 0x3111
+#define WM8995_WRITE_SEQUENCER_274 0x3112
+#define WM8995_WRITE_SEQUENCER_275 0x3113
+#define WM8995_WRITE_SEQUENCER_276 0x3114
+#define WM8995_WRITE_SEQUENCER_277 0x3115
+#define WM8995_WRITE_SEQUENCER_278 0x3116
+#define WM8995_WRITE_SEQUENCER_279 0x3117
+#define WM8995_WRITE_SEQUENCER_280 0x3118
+#define WM8995_WRITE_SEQUENCER_281 0x3119
+#define WM8995_WRITE_SEQUENCER_282 0x311A
+#define WM8995_WRITE_SEQUENCER_283 0x311B
+#define WM8995_WRITE_SEQUENCER_284 0x311C
+#define WM8995_WRITE_SEQUENCER_285 0x311D
+#define WM8995_WRITE_SEQUENCER_286 0x311E
+#define WM8995_WRITE_SEQUENCER_287 0x311F
+#define WM8995_WRITE_SEQUENCER_288 0x3120
+#define WM8995_WRITE_SEQUENCER_289 0x3121
+#define WM8995_WRITE_SEQUENCER_290 0x3122
+#define WM8995_WRITE_SEQUENCER_291 0x3123
+#define WM8995_WRITE_SEQUENCER_292 0x3124
+#define WM8995_WRITE_SEQUENCER_293 0x3125
+#define WM8995_WRITE_SEQUENCER_294 0x3126
+#define WM8995_WRITE_SEQUENCER_295 0x3127
+#define WM8995_WRITE_SEQUENCER_296 0x3128
+#define WM8995_WRITE_SEQUENCER_297 0x3129
+#define WM8995_WRITE_SEQUENCER_298 0x312A
+#define WM8995_WRITE_SEQUENCER_299 0x312B
+#define WM8995_WRITE_SEQUENCER_300 0x312C
+#define WM8995_WRITE_SEQUENCER_301 0x312D
+#define WM8995_WRITE_SEQUENCER_302 0x312E
+#define WM8995_WRITE_SEQUENCER_303 0x312F
+#define WM8995_WRITE_SEQUENCER_304 0x3130
+#define WM8995_WRITE_SEQUENCER_305 0x3131
+#define WM8995_WRITE_SEQUENCER_306 0x3132
+#define WM8995_WRITE_SEQUENCER_307 0x3133
+#define WM8995_WRITE_SEQUENCER_308 0x3134
+#define WM8995_WRITE_SEQUENCER_309 0x3135
+#define WM8995_WRITE_SEQUENCER_310 0x3136
+#define WM8995_WRITE_SEQUENCER_311 0x3137
+#define WM8995_WRITE_SEQUENCER_312 0x3138
+#define WM8995_WRITE_SEQUENCER_313 0x3139
+#define WM8995_WRITE_SEQUENCER_314 0x313A
+#define WM8995_WRITE_SEQUENCER_315 0x313B
+#define WM8995_WRITE_SEQUENCER_316 0x313C
+#define WM8995_WRITE_SEQUENCER_317 0x313D
+#define WM8995_WRITE_SEQUENCER_318 0x313E
+#define WM8995_WRITE_SEQUENCER_319 0x313F
+#define WM8995_WRITE_SEQUENCER_320 0x3140
+#define WM8995_WRITE_SEQUENCER_321 0x3141
+#define WM8995_WRITE_SEQUENCER_322 0x3142
+#define WM8995_WRITE_SEQUENCER_323 0x3143
+#define WM8995_WRITE_SEQUENCER_324 0x3144
+#define WM8995_WRITE_SEQUENCER_325 0x3145
+#define WM8995_WRITE_SEQUENCER_326 0x3146
+#define WM8995_WRITE_SEQUENCER_327 0x3147
+#define WM8995_WRITE_SEQUENCER_328 0x3148
+#define WM8995_WRITE_SEQUENCER_329 0x3149
+#define WM8995_WRITE_SEQUENCER_330 0x314A
+#define WM8995_WRITE_SEQUENCER_331 0x314B
+#define WM8995_WRITE_SEQUENCER_332 0x314C
+#define WM8995_WRITE_SEQUENCER_333 0x314D
+#define WM8995_WRITE_SEQUENCER_334 0x314E
+#define WM8995_WRITE_SEQUENCER_335 0x314F
+#define WM8995_WRITE_SEQUENCER_336 0x3150
+#define WM8995_WRITE_SEQUENCER_337 0x3151
+#define WM8995_WRITE_SEQUENCER_338 0x3152
+#define WM8995_WRITE_SEQUENCER_339 0x3153
+#define WM8995_WRITE_SEQUENCER_340 0x3154
+#define WM8995_WRITE_SEQUENCER_341 0x3155
+#define WM8995_WRITE_SEQUENCER_342 0x3156
+#define WM8995_WRITE_SEQUENCER_343 0x3157
+#define WM8995_WRITE_SEQUENCER_344 0x3158
+#define WM8995_WRITE_SEQUENCER_345 0x3159
+#define WM8995_WRITE_SEQUENCER_346 0x315A
+#define WM8995_WRITE_SEQUENCER_347 0x315B
+#define WM8995_WRITE_SEQUENCER_348 0x315C
+#define WM8995_WRITE_SEQUENCER_349 0x315D
+#define WM8995_WRITE_SEQUENCER_350 0x315E
+#define WM8995_WRITE_SEQUENCER_351 0x315F
+#define WM8995_WRITE_SEQUENCER_352 0x3160
+#define WM8995_WRITE_SEQUENCER_353 0x3161
+#define WM8995_WRITE_SEQUENCER_354 0x3162
+#define WM8995_WRITE_SEQUENCER_355 0x3163
+#define WM8995_WRITE_SEQUENCER_356 0x3164
+#define WM8995_WRITE_SEQUENCER_357 0x3165
+#define WM8995_WRITE_SEQUENCER_358 0x3166
+#define WM8995_WRITE_SEQUENCER_359 0x3167
+#define WM8995_WRITE_SEQUENCER_360 0x3168
+#define WM8995_WRITE_SEQUENCER_361 0x3169
+#define WM8995_WRITE_SEQUENCER_362 0x316A
+#define WM8995_WRITE_SEQUENCER_363 0x316B
+#define WM8995_WRITE_SEQUENCER_364 0x316C
+#define WM8995_WRITE_SEQUENCER_365 0x316D
+#define WM8995_WRITE_SEQUENCER_366 0x316E
+#define WM8995_WRITE_SEQUENCER_367 0x316F
+#define WM8995_WRITE_SEQUENCER_368 0x3170
+#define WM8995_WRITE_SEQUENCER_369 0x3171
+#define WM8995_WRITE_SEQUENCER_370 0x3172
+#define WM8995_WRITE_SEQUENCER_371 0x3173
+#define WM8995_WRITE_SEQUENCER_372 0x3174
+#define WM8995_WRITE_SEQUENCER_373 0x3175
+#define WM8995_WRITE_SEQUENCER_374 0x3176
+#define WM8995_WRITE_SEQUENCER_375 0x3177
+#define WM8995_WRITE_SEQUENCER_376 0x3178
+#define WM8995_WRITE_SEQUENCER_377 0x3179
+#define WM8995_WRITE_SEQUENCER_378 0x317A
+#define WM8995_WRITE_SEQUENCER_379 0x317B
+#define WM8995_WRITE_SEQUENCER_380 0x317C
+#define WM8995_WRITE_SEQUENCER_381 0x317D
+#define WM8995_WRITE_SEQUENCER_382 0x317E
+#define WM8995_WRITE_SEQUENCER_383 0x317F
+#define WM8995_WRITE_SEQUENCER_384 0x3180
+#define WM8995_WRITE_SEQUENCER_385 0x3181
+#define WM8995_WRITE_SEQUENCER_386 0x3182
+#define WM8995_WRITE_SEQUENCER_387 0x3183
+#define WM8995_WRITE_SEQUENCER_388 0x3184
+#define WM8995_WRITE_SEQUENCER_389 0x3185
+#define WM8995_WRITE_SEQUENCER_390 0x3186
+#define WM8995_WRITE_SEQUENCER_391 0x3187
+#define WM8995_WRITE_SEQUENCER_392 0x3188
+#define WM8995_WRITE_SEQUENCER_393 0x3189
+#define WM8995_WRITE_SEQUENCER_394 0x318A
+#define WM8995_WRITE_SEQUENCER_395 0x318B
+#define WM8995_WRITE_SEQUENCER_396 0x318C
+#define WM8995_WRITE_SEQUENCER_397 0x318D
+#define WM8995_WRITE_SEQUENCER_398 0x318E
+#define WM8995_WRITE_SEQUENCER_399 0x318F
+#define WM8995_WRITE_SEQUENCER_400 0x3190
+#define WM8995_WRITE_SEQUENCER_401 0x3191
+#define WM8995_WRITE_SEQUENCER_402 0x3192
+#define WM8995_WRITE_SEQUENCER_403 0x3193
+#define WM8995_WRITE_SEQUENCER_404 0x3194
+#define WM8995_WRITE_SEQUENCER_405 0x3195
+#define WM8995_WRITE_SEQUENCER_406 0x3196
+#define WM8995_WRITE_SEQUENCER_407 0x3197
+#define WM8995_WRITE_SEQUENCER_408 0x3198
+#define WM8995_WRITE_SEQUENCER_409 0x3199
+#define WM8995_WRITE_SEQUENCER_410 0x319A
+#define WM8995_WRITE_SEQUENCER_411 0x319B
+#define WM8995_WRITE_SEQUENCER_412 0x319C
+#define WM8995_WRITE_SEQUENCER_413 0x319D
+#define WM8995_WRITE_SEQUENCER_414 0x319E
+#define WM8995_WRITE_SEQUENCER_415 0x319F
+#define WM8995_WRITE_SEQUENCER_416 0x31A0
+#define WM8995_WRITE_SEQUENCER_417 0x31A1
+#define WM8995_WRITE_SEQUENCER_418 0x31A2
+#define WM8995_WRITE_SEQUENCER_419 0x31A3
+#define WM8995_WRITE_SEQUENCER_420 0x31A4
+#define WM8995_WRITE_SEQUENCER_421 0x31A5
+#define WM8995_WRITE_SEQUENCER_422 0x31A6
+#define WM8995_WRITE_SEQUENCER_423 0x31A7
+#define WM8995_WRITE_SEQUENCER_424 0x31A8
+#define WM8995_WRITE_SEQUENCER_425 0x31A9
+#define WM8995_WRITE_SEQUENCER_426 0x31AA
+#define WM8995_WRITE_SEQUENCER_427 0x31AB
+#define WM8995_WRITE_SEQUENCER_428 0x31AC
+#define WM8995_WRITE_SEQUENCER_429 0x31AD
+#define WM8995_WRITE_SEQUENCER_430 0x31AE
+#define WM8995_WRITE_SEQUENCER_431 0x31AF
+#define WM8995_WRITE_SEQUENCER_432 0x31B0
+#define WM8995_WRITE_SEQUENCER_433 0x31B1
+#define WM8995_WRITE_SEQUENCER_434 0x31B2
+#define WM8995_WRITE_SEQUENCER_435 0x31B3
+#define WM8995_WRITE_SEQUENCER_436 0x31B4
+#define WM8995_WRITE_SEQUENCER_437 0x31B5
+#define WM8995_WRITE_SEQUENCER_438 0x31B6
+#define WM8995_WRITE_SEQUENCER_439 0x31B7
+#define WM8995_WRITE_SEQUENCER_440 0x31B8
+#define WM8995_WRITE_SEQUENCER_441 0x31B9
+#define WM8995_WRITE_SEQUENCER_442 0x31BA
+#define WM8995_WRITE_SEQUENCER_443 0x31BB
+#define WM8995_WRITE_SEQUENCER_444 0x31BC
+#define WM8995_WRITE_SEQUENCER_445 0x31BD
+#define WM8995_WRITE_SEQUENCER_446 0x31BE
+#define WM8995_WRITE_SEQUENCER_447 0x31BF
+#define WM8995_WRITE_SEQUENCER_448 0x31C0
+#define WM8995_WRITE_SEQUENCER_449 0x31C1
+#define WM8995_WRITE_SEQUENCER_450 0x31C2
+#define WM8995_WRITE_SEQUENCER_451 0x31C3
+#define WM8995_WRITE_SEQUENCER_452 0x31C4
+#define WM8995_WRITE_SEQUENCER_453 0x31C5
+#define WM8995_WRITE_SEQUENCER_454 0x31C6
+#define WM8995_WRITE_SEQUENCER_455 0x31C7
+#define WM8995_WRITE_SEQUENCER_456 0x31C8
+#define WM8995_WRITE_SEQUENCER_457 0x31C9
+#define WM8995_WRITE_SEQUENCER_458 0x31CA
+#define WM8995_WRITE_SEQUENCER_459 0x31CB
+#define WM8995_WRITE_SEQUENCER_460 0x31CC
+#define WM8995_WRITE_SEQUENCER_461 0x31CD
+#define WM8995_WRITE_SEQUENCER_462 0x31CE
+#define WM8995_WRITE_SEQUENCER_463 0x31CF
+#define WM8995_WRITE_SEQUENCER_464 0x31D0
+#define WM8995_WRITE_SEQUENCER_465 0x31D1
+#define WM8995_WRITE_SEQUENCER_466 0x31D2
+#define WM8995_WRITE_SEQUENCER_467 0x31D3
+#define WM8995_WRITE_SEQUENCER_468 0x31D4
+#define WM8995_WRITE_SEQUENCER_469 0x31D5
+#define WM8995_WRITE_SEQUENCER_470 0x31D6
+#define WM8995_WRITE_SEQUENCER_471 0x31D7
+#define WM8995_WRITE_SEQUENCER_472 0x31D8
+#define WM8995_WRITE_SEQUENCER_473 0x31D9
+#define WM8995_WRITE_SEQUENCER_474 0x31DA
+#define WM8995_WRITE_SEQUENCER_475 0x31DB
+#define WM8995_WRITE_SEQUENCER_476 0x31DC
+#define WM8995_WRITE_SEQUENCER_477 0x31DD
+#define WM8995_WRITE_SEQUENCER_478 0x31DE
+#define WM8995_WRITE_SEQUENCER_479 0x31DF
+#define WM8995_WRITE_SEQUENCER_480 0x31E0
+#define WM8995_WRITE_SEQUENCER_481 0x31E1
+#define WM8995_WRITE_SEQUENCER_482 0x31E2
+#define WM8995_WRITE_SEQUENCER_483 0x31E3
+#define WM8995_WRITE_SEQUENCER_484 0x31E4
+#define WM8995_WRITE_SEQUENCER_485 0x31E5
+#define WM8995_WRITE_SEQUENCER_486 0x31E6
+#define WM8995_WRITE_SEQUENCER_487 0x31E7
+#define WM8995_WRITE_SEQUENCER_488 0x31E8
+#define WM8995_WRITE_SEQUENCER_489 0x31E9
+#define WM8995_WRITE_SEQUENCER_490 0x31EA
+#define WM8995_WRITE_SEQUENCER_491 0x31EB
+#define WM8995_WRITE_SEQUENCER_492 0x31EC
+#define WM8995_WRITE_SEQUENCER_493 0x31ED
+#define WM8995_WRITE_SEQUENCER_494 0x31EE
+#define WM8995_WRITE_SEQUENCER_495 0x31EF
+#define WM8995_WRITE_SEQUENCER_496 0x31F0
+#define WM8995_WRITE_SEQUENCER_497 0x31F1
+#define WM8995_WRITE_SEQUENCER_498 0x31F2
+#define WM8995_WRITE_SEQUENCER_499 0x31F3
+#define WM8995_WRITE_SEQUENCER_500 0x31F4
+#define WM8995_WRITE_SEQUENCER_501 0x31F5
+#define WM8995_WRITE_SEQUENCER_502 0x31F6
+#define WM8995_WRITE_SEQUENCER_503 0x31F7
+#define WM8995_WRITE_SEQUENCER_504 0x31F8
+#define WM8995_WRITE_SEQUENCER_505 0x31F9
+#define WM8995_WRITE_SEQUENCER_506 0x31FA
+#define WM8995_WRITE_SEQUENCER_507 0x31FB
+#define WM8995_WRITE_SEQUENCER_508 0x31FC
+#define WM8995_WRITE_SEQUENCER_509 0x31FD
+#define WM8995_WRITE_SEQUENCER_510 0x31FE
+#define WM8995_WRITE_SEQUENCER_511 0x31FF
+
+#define WM8995_REGISTER_COUNT 725
+#define WM8995_MAX_REGISTER 0x31FF
+
+#define WM8995_MAX_CACHED_REGISTER WM8995_MAX_REGISTER
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Software Reset
+ */
+#define WM8995_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */
+#define WM8995_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */
+#define WM8995_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM8995_MICB2_ENA 0x0200 /* MICB2_ENA */
+#define WM8995_MICB2_ENA_MASK 0x0200 /* MICB2_ENA */
+#define WM8995_MICB2_ENA_SHIFT 9 /* MICB2_ENA */
+#define WM8995_MICB2_ENA_WIDTH 1 /* MICB2_ENA */
+#define WM8995_MICB1_ENA 0x0100 /* MICB1_ENA */
+#define WM8995_MICB1_ENA_MASK 0x0100 /* MICB1_ENA */
+#define WM8995_MICB1_ENA_SHIFT 8 /* MICB1_ENA */
+#define WM8995_MICB1_ENA_WIDTH 1 /* MICB1_ENA */
+#define WM8995_HPOUT2L_ENA 0x0080 /* HPOUT2L_ENA */
+#define WM8995_HPOUT2L_ENA_MASK 0x0080 /* HPOUT2L_ENA */
+#define WM8995_HPOUT2L_ENA_SHIFT 7 /* HPOUT2L_ENA */
+#define WM8995_HPOUT2L_ENA_WIDTH 1 /* HPOUT2L_ENA */
+#define WM8995_HPOUT2R_ENA 0x0040 /* HPOUT2R_ENA */
+#define WM8995_HPOUT2R_ENA_MASK 0x0040 /* HPOUT2R_ENA */
+#define WM8995_HPOUT2R_ENA_SHIFT 6 /* HPOUT2R_ENA */
+#define WM8995_HPOUT2R_ENA_WIDTH 1 /* HPOUT2R_ENA */
+#define WM8995_HPOUT1L_ENA 0x0020 /* HPOUT1L_ENA */
+#define WM8995_HPOUT1L_ENA_MASK 0x0020 /* HPOUT1L_ENA */
+#define WM8995_HPOUT1L_ENA_SHIFT 5 /* HPOUT1L_ENA */
+#define WM8995_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */
+#define WM8995_HPOUT1R_ENA 0x0010 /* HPOUT1R_ENA */
+#define WM8995_HPOUT1R_ENA_MASK 0x0010 /* HPOUT1R_ENA */
+#define WM8995_HPOUT1R_ENA_SHIFT 4 /* HPOUT1R_ENA */
+#define WM8995_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */
+#define WM8995_BG_ENA 0x0001 /* BG_ENA */
+#define WM8995_BG_ENA_MASK 0x0001 /* BG_ENA */
+#define WM8995_BG_ENA_SHIFT 0 /* BG_ENA */
+#define WM8995_BG_ENA_WIDTH 1 /* BG_ENA */
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM8995_OPCLK_ENA 0x0800 /* OPCLK_ENA */
+#define WM8995_OPCLK_ENA_MASK 0x0800 /* OPCLK_ENA */
+#define WM8995_OPCLK_ENA_SHIFT 11 /* OPCLK_ENA */
+#define WM8995_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */
+#define WM8995_IN1L_ENA 0x0020 /* IN1L_ENA */
+#define WM8995_IN1L_ENA_MASK 0x0020 /* IN1L_ENA */
+#define WM8995_IN1L_ENA_SHIFT 5 /* IN1L_ENA */
+#define WM8995_IN1L_ENA_WIDTH 1 /* IN1L_ENA */
+#define WM8995_IN1R_ENA 0x0010 /* IN1R_ENA */
+#define WM8995_IN1R_ENA_MASK 0x0010 /* IN1R_ENA */
+#define WM8995_IN1R_ENA_SHIFT 4 /* IN1R_ENA */
+#define WM8995_IN1R_ENA_WIDTH 1 /* IN1R_ENA */
+#define WM8995_LDO2_ENA 0x0002 /* LDO2_ENA */
+#define WM8995_LDO2_ENA_MASK 0x0002 /* LDO2_ENA */
+#define WM8995_LDO2_ENA_SHIFT 1 /* LDO2_ENA */
+#define WM8995_LDO2_ENA_WIDTH 1 /* LDO2_ENA */
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM8995_AIF2ADCL_ENA 0x2000 /* AIF2ADCL_ENA */
+#define WM8995_AIF2ADCL_ENA_MASK 0x2000 /* AIF2ADCL_ENA */
+#define WM8995_AIF2ADCL_ENA_SHIFT 13 /* AIF2ADCL_ENA */
+#define WM8995_AIF2ADCL_ENA_WIDTH 1 /* AIF2ADCL_ENA */
+#define WM8995_AIF2ADCR_ENA 0x1000 /* AIF2ADCR_ENA */
+#define WM8995_AIF2ADCR_ENA_MASK 0x1000 /* AIF2ADCR_ENA */
+#define WM8995_AIF2ADCR_ENA_SHIFT 12 /* AIF2ADCR_ENA */
+#define WM8995_AIF2ADCR_ENA_WIDTH 1 /* AIF2ADCR_ENA */
+#define WM8995_AIF1ADC2L_ENA 0x0800 /* AIF1ADC2L_ENA */
+#define WM8995_AIF1ADC2L_ENA_MASK 0x0800 /* AIF1ADC2L_ENA */
+#define WM8995_AIF1ADC2L_ENA_SHIFT 11 /* AIF1ADC2L_ENA */
+#define WM8995_AIF1ADC2L_ENA_WIDTH 1 /* AIF1ADC2L_ENA */
+#define WM8995_AIF1ADC2R_ENA 0x0400 /* AIF1ADC2R_ENA */
+#define WM8995_AIF1ADC2R_ENA_MASK 0x0400 /* AIF1ADC2R_ENA */
+#define WM8995_AIF1ADC2R_ENA_SHIFT 10 /* AIF1ADC2R_ENA */
+#define WM8995_AIF1ADC2R_ENA_WIDTH 1 /* AIF1ADC2R_ENA */
+#define WM8995_AIF1ADC1L_ENA 0x0200 /* AIF1ADC1L_ENA */
+#define WM8995_AIF1ADC1L_ENA_MASK 0x0200 /* AIF1ADC1L_ENA */
+#define WM8995_AIF1ADC1L_ENA_SHIFT 9 /* AIF1ADC1L_ENA */
+#define WM8995_AIF1ADC1L_ENA_WIDTH 1 /* AIF1ADC1L_ENA */
+#define WM8995_AIF1ADC1R_ENA 0x0100 /* AIF1ADC1R_ENA */
+#define WM8995_AIF1ADC1R_ENA_MASK 0x0100 /* AIF1ADC1R_ENA */
+#define WM8995_AIF1ADC1R_ENA_SHIFT 8 /* AIF1ADC1R_ENA */
+#define WM8995_AIF1ADC1R_ENA_WIDTH 1 /* AIF1ADC1R_ENA */
+#define WM8995_DMIC3L_ENA 0x0080 /* DMIC3L_ENA */
+#define WM8995_DMIC3L_ENA_MASK 0x0080 /* DMIC3L_ENA */
+#define WM8995_DMIC3L_ENA_SHIFT 7 /* DMIC3L_ENA */
+#define WM8995_DMIC3L_ENA_WIDTH 1 /* DMIC3L_ENA */
+#define WM8995_DMIC3R_ENA 0x0040 /* DMIC3R_ENA */
+#define WM8995_DMIC3R_ENA_MASK 0x0040 /* DMIC3R_ENA */
+#define WM8995_DMIC3R_ENA_SHIFT 6 /* DMIC3R_ENA */
+#define WM8995_DMIC3R_ENA_WIDTH 1 /* DMIC3R_ENA */
+#define WM8995_DMIC2L_ENA 0x0020 /* DMIC2L_ENA */
+#define WM8995_DMIC2L_ENA_MASK 0x0020 /* DMIC2L_ENA */
+#define WM8995_DMIC2L_ENA_SHIFT 5 /* DMIC2L_ENA */
+#define WM8995_DMIC2L_ENA_WIDTH 1 /* DMIC2L_ENA */
+#define WM8995_DMIC2R_ENA 0x0010 /* DMIC2R_ENA */
+#define WM8995_DMIC2R_ENA_MASK 0x0010 /* DMIC2R_ENA */
+#define WM8995_DMIC2R_ENA_SHIFT 4 /* DMIC2R_ENA */
+#define WM8995_DMIC2R_ENA_WIDTH 1 /* DMIC2R_ENA */
+#define WM8995_DMIC1L_ENA 0x0008 /* DMIC1L_ENA */
+#define WM8995_DMIC1L_ENA_MASK 0x0008 /* DMIC1L_ENA */
+#define WM8995_DMIC1L_ENA_SHIFT 3 /* DMIC1L_ENA */
+#define WM8995_DMIC1L_ENA_WIDTH 1 /* DMIC1L_ENA */
+#define WM8995_DMIC1R_ENA 0x0004 /* DMIC1R_ENA */
+#define WM8995_DMIC1R_ENA_MASK 0x0004 /* DMIC1R_ENA */
+#define WM8995_DMIC1R_ENA_SHIFT 2 /* DMIC1R_ENA */
+#define WM8995_DMIC1R_ENA_WIDTH 1 /* DMIC1R_ENA */
+#define WM8995_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8995_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */
+#define WM8995_ADCL_ENA_SHIFT 1 /* ADCL_ENA */
+#define WM8995_ADCL_ENA_WIDTH 1 /* ADCL_ENA */
+#define WM8995_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8995_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */
+#define WM8995_ADCR_ENA_SHIFT 0 /* ADCR_ENA */
+#define WM8995_ADCR_ENA_WIDTH 1 /* ADCR_ENA */
+
+/*
+ * R4 (0x04) - Power Management (4)
+ */
+#define WM8995_AIF2DACL_ENA 0x2000 /* AIF2DACL_ENA */
+#define WM8995_AIF2DACL_ENA_MASK 0x2000 /* AIF2DACL_ENA */
+#define WM8995_AIF2DACL_ENA_SHIFT 13 /* AIF2DACL_ENA */
+#define WM8995_AIF2DACL_ENA_WIDTH 1 /* AIF2DACL_ENA */
+#define WM8995_AIF2DACR_ENA 0x1000 /* AIF2DACR_ENA */
+#define WM8995_AIF2DACR_ENA_MASK 0x1000 /* AIF2DACR_ENA */
+#define WM8995_AIF2DACR_ENA_SHIFT 12 /* AIF2DACR_ENA */
+#define WM8995_AIF2DACR_ENA_WIDTH 1 /* AIF2DACR_ENA */
+#define WM8995_AIF1DAC2L_ENA 0x0800 /* AIF1DAC2L_ENA */
+#define WM8995_AIF1DAC2L_ENA_MASK 0x0800 /* AIF1DAC2L_ENA */
+#define WM8995_AIF1DAC2L_ENA_SHIFT 11 /* AIF1DAC2L_ENA */
+#define WM8995_AIF1DAC2L_ENA_WIDTH 1 /* AIF1DAC2L_ENA */
+#define WM8995_AIF1DAC2R_ENA 0x0400 /* AIF1DAC2R_ENA */
+#define WM8995_AIF1DAC2R_ENA_MASK 0x0400 /* AIF1DAC2R_ENA */
+#define WM8995_AIF1DAC2R_ENA_SHIFT 10 /* AIF1DAC2R_ENA */
+#define WM8995_AIF1DAC2R_ENA_WIDTH 1 /* AIF1DAC2R_ENA */
+#define WM8995_AIF1DAC1L_ENA 0x0200 /* AIF1DAC1L_ENA */
+#define WM8995_AIF1DAC1L_ENA_MASK 0x0200 /* AIF1DAC1L_ENA */
+#define WM8995_AIF1DAC1L_ENA_SHIFT 9 /* AIF1DAC1L_ENA */
+#define WM8995_AIF1DAC1L_ENA_WIDTH 1 /* AIF1DAC1L_ENA */
+#define WM8995_AIF1DAC1R_ENA 0x0100 /* AIF1DAC1R_ENA */
+#define WM8995_AIF1DAC1R_ENA_MASK 0x0100 /* AIF1DAC1R_ENA */
+#define WM8995_AIF1DAC1R_ENA_SHIFT 8 /* AIF1DAC1R_ENA */
+#define WM8995_AIF1DAC1R_ENA_WIDTH 1 /* AIF1DAC1R_ENA */
+#define WM8995_DAC2L_ENA 0x0008 /* DAC2L_ENA */
+#define WM8995_DAC2L_ENA_MASK 0x0008 /* DAC2L_ENA */
+#define WM8995_DAC2L_ENA_SHIFT 3 /* DAC2L_ENA */
+#define WM8995_DAC2L_ENA_WIDTH 1 /* DAC2L_ENA */
+#define WM8995_DAC2R_ENA 0x0004 /* DAC2R_ENA */
+#define WM8995_DAC2R_ENA_MASK 0x0004 /* DAC2R_ENA */
+#define WM8995_DAC2R_ENA_SHIFT 2 /* DAC2R_ENA */
+#define WM8995_DAC2R_ENA_WIDTH 1 /* DAC2R_ENA */
+#define WM8995_DAC1L_ENA 0x0002 /* DAC1L_ENA */
+#define WM8995_DAC1L_ENA_MASK 0x0002 /* DAC1L_ENA */
+#define WM8995_DAC1L_ENA_SHIFT 1 /* DAC1L_ENA */
+#define WM8995_DAC1L_ENA_WIDTH 1 /* DAC1L_ENA */
+#define WM8995_DAC1R_ENA 0x0001 /* DAC1R_ENA */
+#define WM8995_DAC1R_ENA_MASK 0x0001 /* DAC1R_ENA */
+#define WM8995_DAC1R_ENA_SHIFT 0 /* DAC1R_ENA */
+#define WM8995_DAC1R_ENA_WIDTH 1 /* DAC1R_ENA */
+
+/*
+ * R5 (0x05) - Power Management (5)
+ */
+#define WM8995_DMIC_SRC2_MASK 0x0300 /* DMIC_SRC2 - [9:8] */
+#define WM8995_DMIC_SRC2_SHIFT 8 /* DMIC_SRC2 - [9:8] */
+#define WM8995_DMIC_SRC2_WIDTH 2 /* DMIC_SRC2 - [9:8] */
+#define WM8995_DMIC_SRC1_MASK 0x00C0 /* DMIC_SRC1 - [7:6] */
+#define WM8995_DMIC_SRC1_SHIFT 6 /* DMIC_SRC1 - [7:6] */
+#define WM8995_DMIC_SRC1_WIDTH 2 /* DMIC_SRC1 - [7:6] */
+#define WM8995_AIF3_TRI 0x0020 /* AIF3_TRI */
+#define WM8995_AIF3_TRI_MASK 0x0020 /* AIF3_TRI */
+#define WM8995_AIF3_TRI_SHIFT 5 /* AIF3_TRI */
+#define WM8995_AIF3_TRI_WIDTH 1 /* AIF3_TRI */
+#define WM8995_AIF3_ADCDAT_SRC_MASK 0x0018 /* AIF3_ADCDAT_SRC - [4:3] */
+#define WM8995_AIF3_ADCDAT_SRC_SHIFT 3 /* AIF3_ADCDAT_SRC - [4:3] */
+#define WM8995_AIF3_ADCDAT_SRC_WIDTH 2 /* AIF3_ADCDAT_SRC - [4:3] */
+#define WM8995_AIF2_ADCDAT_SRC 0x0004 /* AIF2_ADCDAT_SRC */
+#define WM8995_AIF2_ADCDAT_SRC_MASK 0x0004 /* AIF2_ADCDAT_SRC */
+#define WM8995_AIF2_ADCDAT_SRC_SHIFT 2 /* AIF2_ADCDAT_SRC */
+#define WM8995_AIF2_ADCDAT_SRC_WIDTH 1 /* AIF2_ADCDAT_SRC */
+#define WM8995_AIF2_DACDAT_SRC 0x0002 /* AIF2_DACDAT_SRC */
+#define WM8995_AIF2_DACDAT_SRC_MASK 0x0002 /* AIF2_DACDAT_SRC */
+#define WM8995_AIF2_DACDAT_SRC_SHIFT 1 /* AIF2_DACDAT_SRC */
+#define WM8995_AIF2_DACDAT_SRC_WIDTH 1 /* AIF2_DACDAT_SRC */
+#define WM8995_AIF1_DACDAT_SRC 0x0001 /* AIF1_DACDAT_SRC */
+#define WM8995_AIF1_DACDAT_SRC_MASK 0x0001 /* AIF1_DACDAT_SRC */
+#define WM8995_AIF1_DACDAT_SRC_SHIFT 0 /* AIF1_DACDAT_SRC */
+#define WM8995_AIF1_DACDAT_SRC_WIDTH 1 /* AIF1_DACDAT_SRC */
+
+/*
+ * R16 (0x10) - Left Line Input 1 Volume
+ */
+#define WM8995_IN1_VU 0x0080 /* IN1_VU */
+#define WM8995_IN1_VU_MASK 0x0080 /* IN1_VU */
+#define WM8995_IN1_VU_SHIFT 7 /* IN1_VU */
+#define WM8995_IN1_VU_WIDTH 1 /* IN1_VU */
+#define WM8995_IN1L_ZC 0x0020 /* IN1L_ZC */
+#define WM8995_IN1L_ZC_MASK 0x0020 /* IN1L_ZC */
+#define WM8995_IN1L_ZC_SHIFT 5 /* IN1L_ZC */
+#define WM8995_IN1L_ZC_WIDTH 1 /* IN1L_ZC */
+#define WM8995_IN1L_VOL_MASK 0x001F /* IN1L_VOL - [4:0] */
+#define WM8995_IN1L_VOL_SHIFT 0 /* IN1L_VOL - [4:0] */
+#define WM8995_IN1L_VOL_WIDTH 5 /* IN1L_VOL - [4:0] */
+
+/*
+ * R17 (0x11) - Right Line Input 1 Volume
+ */
+#define WM8995_IN1_VU 0x0080 /* IN1_VU */
+#define WM8995_IN1_VU_MASK 0x0080 /* IN1_VU */
+#define WM8995_IN1_VU_SHIFT 7 /* IN1_VU */
+#define WM8995_IN1_VU_WIDTH 1 /* IN1_VU */
+#define WM8995_IN1R_ZC 0x0020 /* IN1R_ZC */
+#define WM8995_IN1R_ZC_MASK 0x0020 /* IN1R_ZC */
+#define WM8995_IN1R_ZC_SHIFT 5 /* IN1R_ZC */
+#define WM8995_IN1R_ZC_WIDTH 1 /* IN1R_ZC */
+#define WM8995_IN1R_VOL_MASK 0x001F /* IN1R_VOL - [4:0] */
+#define WM8995_IN1R_VOL_SHIFT 0 /* IN1R_VOL - [4:0] */
+#define WM8995_IN1R_VOL_WIDTH 5 /* IN1R_VOL - [4:0] */
+
+/*
+ * R18 (0x12) - Left Line Input Control
+ */
+#define WM8995_IN1L_BOOST_MASK 0x0030 /* IN1L_BOOST - [5:4] */
+#define WM8995_IN1L_BOOST_SHIFT 4 /* IN1L_BOOST - [5:4] */
+#define WM8995_IN1L_BOOST_WIDTH 2 /* IN1L_BOOST - [5:4] */
+#define WM8995_IN1L_MODE_MASK 0x000C /* IN1L_MODE - [3:2] */
+#define WM8995_IN1L_MODE_SHIFT 2 /* IN1L_MODE - [3:2] */
+#define WM8995_IN1L_MODE_WIDTH 2 /* IN1L_MODE - [3:2] */
+#define WM8995_IN1R_MODE_MASK 0x0003 /* IN1R_MODE - [1:0] */
+#define WM8995_IN1R_MODE_SHIFT 0 /* IN1R_MODE - [1:0] */
+#define WM8995_IN1R_MODE_WIDTH 2 /* IN1R_MODE - [1:0] */
+
+/*
+ * R24 (0x18) - DAC1 Left Volume
+ */
+#define WM8995_DAC1L_MUTE 0x0200 /* DAC1L_MUTE */
+#define WM8995_DAC1L_MUTE_MASK 0x0200 /* DAC1L_MUTE */
+#define WM8995_DAC1L_MUTE_SHIFT 9 /* DAC1L_MUTE */
+#define WM8995_DAC1L_MUTE_WIDTH 1 /* DAC1L_MUTE */
+#define WM8995_DAC1_VU 0x0100 /* DAC1_VU */
+#define WM8995_DAC1_VU_MASK 0x0100 /* DAC1_VU */
+#define WM8995_DAC1_VU_SHIFT 8 /* DAC1_VU */
+#define WM8995_DAC1_VU_WIDTH 1 /* DAC1_VU */
+#define WM8995_DAC1L_VOL_MASK 0x00FF /* DAC1L_VOL - [7:0] */
+#define WM8995_DAC1L_VOL_SHIFT 0 /* DAC1L_VOL - [7:0] */
+#define WM8995_DAC1L_VOL_WIDTH 8 /* DAC1L_VOL - [7:0] */
+
+/*
+ * R25 (0x19) - DAC1 Right Volume
+ */
+#define WM8995_DAC1R_MUTE 0x0200 /* DAC1R_MUTE */
+#define WM8995_DAC1R_MUTE_MASK 0x0200 /* DAC1R_MUTE */
+#define WM8995_DAC1R_MUTE_SHIFT 9 /* DAC1R_MUTE */
+#define WM8995_DAC1R_MUTE_WIDTH 1 /* DAC1R_MUTE */
+#define WM8995_DAC1_VU 0x0100 /* DAC1_VU */
+#define WM8995_DAC1_VU_MASK 0x0100 /* DAC1_VU */
+#define WM8995_DAC1_VU_SHIFT 8 /* DAC1_VU */
+#define WM8995_DAC1_VU_WIDTH 1 /* DAC1_VU */
+#define WM8995_DAC1R_VOL_MASK 0x00FF /* DAC1R_VOL - [7:0] */
+#define WM8995_DAC1R_VOL_SHIFT 0 /* DAC1R_VOL - [7:0] */
+#define WM8995_DAC1R_VOL_WIDTH 8 /* DAC1R_VOL - [7:0] */
+
+/*
+ * R26 (0x1A) - DAC2 Left Volume
+ */
+#define WM8995_DAC2L_MUTE 0x0200 /* DAC2L_MUTE */
+#define WM8995_DAC2L_MUTE_MASK 0x0200 /* DAC2L_MUTE */
+#define WM8995_DAC2L_MUTE_SHIFT 9 /* DAC2L_MUTE */
+#define WM8995_DAC2L_MUTE_WIDTH 1 /* DAC2L_MUTE */
+#define WM8995_DAC2_VU 0x0100 /* DAC2_VU */
+#define WM8995_DAC2_VU_MASK 0x0100 /* DAC2_VU */
+#define WM8995_DAC2_VU_SHIFT 8 /* DAC2_VU */
+#define WM8995_DAC2_VU_WIDTH 1 /* DAC2_VU */
+#define WM8995_DAC2L_VOL_MASK 0x00FF /* DAC2L_VOL - [7:0] */
+#define WM8995_DAC2L_VOL_SHIFT 0 /* DAC2L_VOL - [7:0] */
+#define WM8995_DAC2L_VOL_WIDTH 8 /* DAC2L_VOL - [7:0] */
+
+/*
+ * R27 (0x1B) - DAC2 Right Volume
+ */
+#define WM8995_DAC2R_MUTE 0x0200 /* DAC2R_MUTE */
+#define WM8995_DAC2R_MUTE_MASK 0x0200 /* DAC2R_MUTE */
+#define WM8995_DAC2R_MUTE_SHIFT 9 /* DAC2R_MUTE */
+#define WM8995_DAC2R_MUTE_WIDTH 1 /* DAC2R_MUTE */
+#define WM8995_DAC2_VU 0x0100 /* DAC2_VU */
+#define WM8995_DAC2_VU_MASK 0x0100 /* DAC2_VU */
+#define WM8995_DAC2_VU_SHIFT 8 /* DAC2_VU */
+#define WM8995_DAC2_VU_WIDTH 1 /* DAC2_VU */
+#define WM8995_DAC2R_VOL_MASK 0x00FF /* DAC2R_VOL - [7:0] */
+#define WM8995_DAC2R_VOL_SHIFT 0 /* DAC2R_VOL - [7:0] */
+#define WM8995_DAC2R_VOL_WIDTH 8 /* DAC2R_VOL - [7:0] */
+
+/*
+ * R28 (0x1C) - Output Volume ZC (1)
+ */
+#define WM8995_HPOUT2L_ZC 0x0008 /* HPOUT2L_ZC */
+#define WM8995_HPOUT2L_ZC_MASK 0x0008 /* HPOUT2L_ZC */
+#define WM8995_HPOUT2L_ZC_SHIFT 3 /* HPOUT2L_ZC */
+#define WM8995_HPOUT2L_ZC_WIDTH 1 /* HPOUT2L_ZC */
+#define WM8995_HPOUT2R_ZC 0x0004 /* HPOUT2R_ZC */
+#define WM8995_HPOUT2R_ZC_MASK 0x0004 /* HPOUT2R_ZC */
+#define WM8995_HPOUT2R_ZC_SHIFT 2 /* HPOUT2R_ZC */
+#define WM8995_HPOUT2R_ZC_WIDTH 1 /* HPOUT2R_ZC */
+#define WM8995_HPOUT1L_ZC 0x0002 /* HPOUT1L_ZC */
+#define WM8995_HPOUT1L_ZC_MASK 0x0002 /* HPOUT1L_ZC */
+#define WM8995_HPOUT1L_ZC_SHIFT 1 /* HPOUT1L_ZC */
+#define WM8995_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */
+#define WM8995_HPOUT1R_ZC 0x0001 /* HPOUT1R_ZC */
+#define WM8995_HPOUT1R_ZC_MASK 0x0001 /* HPOUT1R_ZC */
+#define WM8995_HPOUT1R_ZC_SHIFT 0 /* HPOUT1R_ZC */
+#define WM8995_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */
+
+/*
+ * R32 (0x20) - MICBIAS (1)
+ */
+#define WM8995_MICB1_MODE 0x0008 /* MICB1_MODE */
+#define WM8995_MICB1_MODE_MASK 0x0008 /* MICB1_MODE */
+#define WM8995_MICB1_MODE_SHIFT 3 /* MICB1_MODE */
+#define WM8995_MICB1_MODE_WIDTH 1 /* MICB1_MODE */
+#define WM8995_MICB1_LVL_MASK 0x0006 /* MICB1_LVL - [2:1] */
+#define WM8995_MICB1_LVL_SHIFT 1 /* MICB1_LVL - [2:1] */
+#define WM8995_MICB1_LVL_WIDTH 2 /* MICB1_LVL - [2:1] */
+#define WM8995_MICB1_DISCH 0x0001 /* MICB1_DISCH */
+#define WM8995_MICB1_DISCH_MASK 0x0001 /* MICB1_DISCH */
+#define WM8995_MICB1_DISCH_SHIFT 0 /* MICB1_DISCH */
+#define WM8995_MICB1_DISCH_WIDTH 1 /* MICB1_DISCH */
+
+/*
+ * R33 (0x21) - MICBIAS (2)
+ */
+#define WM8995_MICB2_MODE 0x0008 /* MICB2_MODE */
+#define WM8995_MICB2_MODE_MASK 0x0008 /* MICB2_MODE */
+#define WM8995_MICB2_MODE_SHIFT 3 /* MICB2_MODE */
+#define WM8995_MICB2_MODE_WIDTH 1 /* MICB2_MODE */
+#define WM8995_MICB2_LVL_MASK 0x0006 /* MICB2_LVL - [2:1] */
+#define WM8995_MICB2_LVL_SHIFT 1 /* MICB2_LVL - [2:1] */
+#define WM8995_MICB2_LVL_WIDTH 2 /* MICB2_LVL - [2:1] */
+#define WM8995_MICB2_DISCH 0x0001 /* MICB2_DISCH */
+#define WM8995_MICB2_DISCH_MASK 0x0001 /* MICB2_DISCH */
+#define WM8995_MICB2_DISCH_SHIFT 0 /* MICB2_DISCH */
+#define WM8995_MICB2_DISCH_WIDTH 1 /* MICB2_DISCH */
+
+/*
+ * R40 (0x28) - LDO 1
+ */
+#define WM8995_LDO1_MODE 0x0020 /* LDO1_MODE */
+#define WM8995_LDO1_MODE_MASK 0x0020 /* LDO1_MODE */
+#define WM8995_LDO1_MODE_SHIFT 5 /* LDO1_MODE */
+#define WM8995_LDO1_MODE_WIDTH 1 /* LDO1_MODE */
+#define WM8995_LDO1_VSEL_MASK 0x0006 /* LDO1_VSEL - [2:1] */
+#define WM8995_LDO1_VSEL_SHIFT 1 /* LDO1_VSEL - [2:1] */
+#define WM8995_LDO1_VSEL_WIDTH 2 /* LDO1_VSEL - [2:1] */
+#define WM8995_LDO1_DISCH 0x0001 /* LDO1_DISCH */
+#define WM8995_LDO1_DISCH_MASK 0x0001 /* LDO1_DISCH */
+#define WM8995_LDO1_DISCH_SHIFT 0 /* LDO1_DISCH */
+#define WM8995_LDO1_DISCH_WIDTH 1 /* LDO1_DISCH */
+
+/*
+ * R41 (0x29) - LDO 2
+ */
+#define WM8995_LDO2_MODE 0x0020 /* LDO2_MODE */
+#define WM8995_LDO2_MODE_MASK 0x0020 /* LDO2_MODE */
+#define WM8995_LDO2_MODE_SHIFT 5 /* LDO2_MODE */
+#define WM8995_LDO2_MODE_WIDTH 1 /* LDO2_MODE */
+#define WM8995_LDO2_VSEL_MASK 0x001E /* LDO2_VSEL - [4:1] */
+#define WM8995_LDO2_VSEL_SHIFT 1 /* LDO2_VSEL - [4:1] */
+#define WM8995_LDO2_VSEL_WIDTH 4 /* LDO2_VSEL - [4:1] */
+#define WM8995_LDO2_DISCH 0x0001 /* LDO2_DISCH */
+#define WM8995_LDO2_DISCH_MASK 0x0001 /* LDO2_DISCH */
+#define WM8995_LDO2_DISCH_SHIFT 0 /* LDO2_DISCH */
+#define WM8995_LDO2_DISCH_WIDTH 1 /* LDO2_DISCH */
+
+/*
+ * R48 (0x30) - Accessory Detect Mode1
+ */
+#define WM8995_JD_MODE_MASK 0x0003 /* JD_MODE - [1:0] */
+#define WM8995_JD_MODE_SHIFT 0 /* JD_MODE - [1:0] */
+#define WM8995_JD_MODE_WIDTH 2 /* JD_MODE - [1:0] */
+
+/*
+ * R49 (0x31) - Accessory Detect Mode2
+ */
+#define WM8995_VID_ENA 0x0001 /* VID_ENA */
+#define WM8995_VID_ENA_MASK 0x0001 /* VID_ENA */
+#define WM8995_VID_ENA_SHIFT 0 /* VID_ENA */
+#define WM8995_VID_ENA_WIDTH 1 /* VID_ENA */
+
+/*
+ * R52 (0x34) - Headphone Detect1
+ */
+#define WM8995_HP_RAMPRATE 0x0002 /* HP_RAMPRATE */
+#define WM8995_HP_RAMPRATE_MASK 0x0002 /* HP_RAMPRATE */
+#define WM8995_HP_RAMPRATE_SHIFT 1 /* HP_RAMPRATE */
+#define WM8995_HP_RAMPRATE_WIDTH 1 /* HP_RAMPRATE */
+#define WM8995_HP_POLL 0x0001 /* HP_POLL */
+#define WM8995_HP_POLL_MASK 0x0001 /* HP_POLL */
+#define WM8995_HP_POLL_SHIFT 0 /* HP_POLL */
+#define WM8995_HP_POLL_WIDTH 1 /* HP_POLL */
+
+/*
+ * R53 (0x35) - Headphone Detect2
+ */
+#define WM8995_HP_DONE 0x0080 /* HP_DONE */
+#define WM8995_HP_DONE_MASK 0x0080 /* HP_DONE */
+#define WM8995_HP_DONE_SHIFT 7 /* HP_DONE */
+#define WM8995_HP_DONE_WIDTH 1 /* HP_DONE */
+#define WM8995_HP_LVL_MASK 0x007F /* HP_LVL - [6:0] */
+#define WM8995_HP_LVL_SHIFT 0 /* HP_LVL - [6:0] */
+#define WM8995_HP_LVL_WIDTH 7 /* HP_LVL - [6:0] */
+
+/*
+ * R56 (0x38) - Mic Detect (1)
+ */
+#define WM8995_MICD_RATE_MASK 0x7800 /* MICD_RATE - [14:11] */
+#define WM8995_MICD_RATE_SHIFT 11 /* MICD_RATE - [14:11] */
+#define WM8995_MICD_RATE_WIDTH 4 /* MICD_RATE - [14:11] */
+#define WM8995_MICD_LVL_SEL_MASK 0x01F8 /* MICD_LVL_SEL - [8:3] */
+#define WM8995_MICD_LVL_SEL_SHIFT 3 /* MICD_LVL_SEL - [8:3] */
+#define WM8995_MICD_LVL_SEL_WIDTH 6 /* MICD_LVL_SEL - [8:3] */
+#define WM8995_MICD_DBTIME 0x0002 /* MICD_DBTIME */
+#define WM8995_MICD_DBTIME_MASK 0x0002 /* MICD_DBTIME */
+#define WM8995_MICD_DBTIME_SHIFT 1 /* MICD_DBTIME */
+#define WM8995_MICD_DBTIME_WIDTH 1 /* MICD_DBTIME */
+#define WM8995_MICD_ENA 0x0001 /* MICD_ENA */
+#define WM8995_MICD_ENA_MASK 0x0001 /* MICD_ENA */
+#define WM8995_MICD_ENA_SHIFT 0 /* MICD_ENA */
+#define WM8995_MICD_ENA_WIDTH 1 /* MICD_ENA */
+
+/*
+ * R57 (0x39) - Mic Detect (2)
+ */
+#define WM8995_MICD_LVL_MASK 0x01FC /* MICD_LVL - [8:2] */
+#define WM8995_MICD_LVL_SHIFT 2 /* MICD_LVL - [8:2] */
+#define WM8995_MICD_LVL_WIDTH 7 /* MICD_LVL - [8:2] */
+#define WM8995_MICD_VALID 0x0002 /* MICD_VALID */
+#define WM8995_MICD_VALID_MASK 0x0002 /* MICD_VALID */
+#define WM8995_MICD_VALID_SHIFT 1 /* MICD_VALID */
+#define WM8995_MICD_VALID_WIDTH 1 /* MICD_VALID */
+#define WM8995_MICD_STS 0x0001 /* MICD_STS */
+#define WM8995_MICD_STS_MASK 0x0001 /* MICD_STS */
+#define WM8995_MICD_STS_SHIFT 0 /* MICD_STS */
+#define WM8995_MICD_STS_WIDTH 1 /* MICD_STS */
+
+/*
+ * R64 (0x40) - Charge Pump (1)
+ */
+#define WM8995_CP_ENA 0x8000 /* CP_ENA */
+#define WM8995_CP_ENA_MASK 0x8000 /* CP_ENA */
+#define WM8995_CP_ENA_SHIFT 15 /* CP_ENA */
+#define WM8995_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R69 (0x45) - Class W (1)
+ */
+#define WM8995_CP_DYN_SRC_SEL_MASK 0x0300 /* CP_DYN_SRC_SEL - [9:8] */
+#define WM8995_CP_DYN_SRC_SEL_SHIFT 8 /* CP_DYN_SRC_SEL - [9:8] */
+#define WM8995_CP_DYN_SRC_SEL_WIDTH 2 /* CP_DYN_SRC_SEL - [9:8] */
+#define WM8995_CP_DYN_PWR 0x0001 /* CP_DYN_PWR */
+#define WM8995_CP_DYN_PWR_MASK 0x0001 /* CP_DYN_PWR */
+#define WM8995_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR */
+#define WM8995_CP_DYN_PWR_WIDTH 1 /* CP_DYN_PWR */
+
+/*
+ * R80 (0x50) - DC Servo (1)
+ */
+#define WM8995_DCS_ENA_CHAN_3 0x0008 /* DCS_ENA_CHAN_3 */
+#define WM8995_DCS_ENA_CHAN_3_MASK 0x0008 /* DCS_ENA_CHAN_3 */
+#define WM8995_DCS_ENA_CHAN_3_SHIFT 3 /* DCS_ENA_CHAN_3 */
+#define WM8995_DCS_ENA_CHAN_3_WIDTH 1 /* DCS_ENA_CHAN_3 */
+#define WM8995_DCS_ENA_CHAN_2 0x0004 /* DCS_ENA_CHAN_2 */
+#define WM8995_DCS_ENA_CHAN_2_MASK 0x0004 /* DCS_ENA_CHAN_2 */
+#define WM8995_DCS_ENA_CHAN_2_SHIFT 2 /* DCS_ENA_CHAN_2 */
+#define WM8995_DCS_ENA_CHAN_2_WIDTH 1 /* DCS_ENA_CHAN_2 */
+#define WM8995_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM8995_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM8995_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */
+#define WM8995_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */
+#define WM8995_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM8995_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM8995_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */
+#define WM8995_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */
+
+/*
+ * R81 (0x51) - DC Servo (2)
+ */
+#define WM8995_DCS_TRIG_SINGLE_3 0x8000 /* DCS_TRIG_SINGLE_3 */
+#define WM8995_DCS_TRIG_SINGLE_3_MASK 0x8000 /* DCS_TRIG_SINGLE_3 */
+#define WM8995_DCS_TRIG_SINGLE_3_SHIFT 15 /* DCS_TRIG_SINGLE_3 */
+#define WM8995_DCS_TRIG_SINGLE_3_WIDTH 1 /* DCS_TRIG_SINGLE_3 */
+#define WM8995_DCS_TRIG_SINGLE_2 0x4000 /* DCS_TRIG_SINGLE_2 */
+#define WM8995_DCS_TRIG_SINGLE_2_MASK 0x4000 /* DCS_TRIG_SINGLE_2 */
+#define WM8995_DCS_TRIG_SINGLE_2_SHIFT 14 /* DCS_TRIG_SINGLE_2 */
+#define WM8995_DCS_TRIG_SINGLE_2_WIDTH 1 /* DCS_TRIG_SINGLE_2 */
+#define WM8995_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM8995_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM8995_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */
+#define WM8995_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */
+#define WM8995_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM8995_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM8995_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */
+#define WM8995_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */
+#define WM8995_DCS_TRIG_SERIES_3 0x0800 /* DCS_TRIG_SERIES_3 */
+#define WM8995_DCS_TRIG_SERIES_3_MASK 0x0800 /* DCS_TRIG_SERIES_3 */
+#define WM8995_DCS_TRIG_SERIES_3_SHIFT 11 /* DCS_TRIG_SERIES_3 */
+#define WM8995_DCS_TRIG_SERIES_3_WIDTH 1 /* DCS_TRIG_SERIES_3 */
+#define WM8995_DCS_TRIG_SERIES_2 0x0400 /* DCS_TRIG_SERIES_2 */
+#define WM8995_DCS_TRIG_SERIES_2_MASK 0x0400 /* DCS_TRIG_SERIES_2 */
+#define WM8995_DCS_TRIG_SERIES_2_SHIFT 10 /* DCS_TRIG_SERIES_2 */
+#define WM8995_DCS_TRIG_SERIES_2_WIDTH 1 /* DCS_TRIG_SERIES_2 */
+#define WM8995_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM8995_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM8995_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */
+#define WM8995_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */
+#define WM8995_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM8995_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM8995_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */
+#define WM8995_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */
+#define WM8995_DCS_TRIG_STARTUP_3 0x0080 /* DCS_TRIG_STARTUP_3 */
+#define WM8995_DCS_TRIG_STARTUP_3_MASK 0x0080 /* DCS_TRIG_STARTUP_3 */
+#define WM8995_DCS_TRIG_STARTUP_3_SHIFT 7 /* DCS_TRIG_STARTUP_3 */
+#define WM8995_DCS_TRIG_STARTUP_3_WIDTH 1 /* DCS_TRIG_STARTUP_3 */
+#define WM8995_DCS_TRIG_STARTUP_2 0x0040 /* DCS_TRIG_STARTUP_2 */
+#define WM8995_DCS_TRIG_STARTUP_2_MASK 0x0040 /* DCS_TRIG_STARTUP_2 */
+#define WM8995_DCS_TRIG_STARTUP_2_SHIFT 6 /* DCS_TRIG_STARTUP_2 */
+#define WM8995_DCS_TRIG_STARTUP_2_WIDTH 1 /* DCS_TRIG_STARTUP_2 */
+#define WM8995_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM8995_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM8995_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */
+#define WM8995_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */
+#define WM8995_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM8995_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM8995_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */
+#define WM8995_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */
+#define WM8995_DCS_TRIG_DAC_WR_3 0x0008 /* DCS_TRIG_DAC_WR_3 */
+#define WM8995_DCS_TRIG_DAC_WR_3_MASK 0x0008 /* DCS_TRIG_DAC_WR_3 */
+#define WM8995_DCS_TRIG_DAC_WR_3_SHIFT 3 /* DCS_TRIG_DAC_WR_3 */
+#define WM8995_DCS_TRIG_DAC_WR_3_WIDTH 1 /* DCS_TRIG_DAC_WR_3 */
+#define WM8995_DCS_TRIG_DAC_WR_2 0x0004 /* DCS_TRIG_DAC_WR_2 */
+#define WM8995_DCS_TRIG_DAC_WR_2_MASK 0x0004 /* DCS_TRIG_DAC_WR_2 */
+#define WM8995_DCS_TRIG_DAC_WR_2_SHIFT 2 /* DCS_TRIG_DAC_WR_2 */
+#define WM8995_DCS_TRIG_DAC_WR_2_WIDTH 1 /* DCS_TRIG_DAC_WR_2 */
+#define WM8995_DCS_TRIG_DAC_WR_1 0x0002 /* DCS_TRIG_DAC_WR_1 */
+#define WM8995_DCS_TRIG_DAC_WR_1_MASK 0x0002 /* DCS_TRIG_DAC_WR_1 */
+#define WM8995_DCS_TRIG_DAC_WR_1_SHIFT 1 /* DCS_TRIG_DAC_WR_1 */
+#define WM8995_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */
+#define WM8995_DCS_TRIG_DAC_WR_0 0x0001 /* DCS_TRIG_DAC_WR_0 */
+#define WM8995_DCS_TRIG_DAC_WR_0_MASK 0x0001 /* DCS_TRIG_DAC_WR_0 */
+#define WM8995_DCS_TRIG_DAC_WR_0_SHIFT 0 /* DCS_TRIG_DAC_WR_0 */
+#define WM8995_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */
+
+/*
+ * R82 (0x52) - DC Servo (3)
+ */
+#define WM8995_DCS_TIMER_PERIOD_23_MASK 0x0F00 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8995_DCS_TIMER_PERIOD_23_SHIFT 8 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8995_DCS_TIMER_PERIOD_23_WIDTH 4 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8995_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8995_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8995_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */
+
+/*
+ * R84 (0x54) - DC Servo (5)
+ */
+#define WM8995_DCS_SERIES_NO_23_MASK 0x7F00 /* DCS_SERIES_NO_23 - [14:8] */
+#define WM8995_DCS_SERIES_NO_23_SHIFT 8 /* DCS_SERIES_NO_23 - [14:8] */
+#define WM8995_DCS_SERIES_NO_23_WIDTH 7 /* DCS_SERIES_NO_23 - [14:8] */
+#define WM8995_DCS_SERIES_NO_01_MASK 0x007F /* DCS_SERIES_NO_01 - [6:0] */
+#define WM8995_DCS_SERIES_NO_01_SHIFT 0 /* DCS_SERIES_NO_01 - [6:0] */
+#define WM8995_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [6:0] */
+
+/*
+ * R85 (0x55) - DC Servo (6)
+ */
+#define WM8995_DCS_DAC_WR_VAL_3_MASK 0xFF00 /* DCS_DAC_WR_VAL_3 - [15:8] */
+#define WM8995_DCS_DAC_WR_VAL_3_SHIFT 8 /* DCS_DAC_WR_VAL_3 - [15:8] */
+#define WM8995_DCS_DAC_WR_VAL_3_WIDTH 8 /* DCS_DAC_WR_VAL_3 - [15:8] */
+#define WM8995_DCS_DAC_WR_VAL_2_MASK 0x00FF /* DCS_DAC_WR_VAL_2 - [7:0] */
+#define WM8995_DCS_DAC_WR_VAL_2_SHIFT 0 /* DCS_DAC_WR_VAL_2 - [7:0] */
+#define WM8995_DCS_DAC_WR_VAL_2_WIDTH 8 /* DCS_DAC_WR_VAL_2 - [7:0] */
+
+/*
+ * R86 (0x56) - DC Servo (7)
+ */
+#define WM8995_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8995_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8995_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8995_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8995_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8995_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */
+
+/*
+ * R87 (0x57) - DC Servo Readback 0
+ */
+#define WM8995_DCS_CAL_COMPLETE_MASK 0x0F00 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8995_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8995_DCS_CAL_COMPLETE_WIDTH 4 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8995_DCS_DAC_WR_COMPLETE_MASK 0x00F0 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8995_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8995_DCS_DAC_WR_COMPLETE_WIDTH 4 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8995_DCS_STARTUP_COMPLETE_MASK 0x000F /* DCS_STARTUP_COMPLETE - [3:0] */
+#define WM8995_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [3:0] */
+#define WM8995_DCS_STARTUP_COMPLETE_WIDTH 4 /* DCS_STARTUP_COMPLETE - [3:0] */
+
+/*
+ * R96 (0x60) - Analogue HP (1)
+ */
+#define WM8995_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */
+#define WM8995_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */
+#define WM8995_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */
+#define WM8995_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */
+#define WM8995_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */
+#define WM8995_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */
+#define WM8995_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */
+#define WM8995_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */
+#define WM8995_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */
+#define WM8995_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */
+#define WM8995_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */
+#define WM8995_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */
+#define WM8995_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */
+#define WM8995_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */
+#define WM8995_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */
+#define WM8995_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */
+#define WM8995_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */
+#define WM8995_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */
+#define WM8995_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */
+#define WM8995_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */
+#define WM8995_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */
+#define WM8995_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */
+#define WM8995_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */
+#define WM8995_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */
+
+/*
+ * R97 (0x61) - Analogue HP (2)
+ */
+#define WM8995_HPOUT2L_RMV_SHORT 0x0080 /* HPOUT2L_RMV_SHORT */
+#define WM8995_HPOUT2L_RMV_SHORT_MASK 0x0080 /* HPOUT2L_RMV_SHORT */
+#define WM8995_HPOUT2L_RMV_SHORT_SHIFT 7 /* HPOUT2L_RMV_SHORT */
+#define WM8995_HPOUT2L_RMV_SHORT_WIDTH 1 /* HPOUT2L_RMV_SHORT */
+#define WM8995_HPOUT2L_OUTP 0x0040 /* HPOUT2L_OUTP */
+#define WM8995_HPOUT2L_OUTP_MASK 0x0040 /* HPOUT2L_OUTP */
+#define WM8995_HPOUT2L_OUTP_SHIFT 6 /* HPOUT2L_OUTP */
+#define WM8995_HPOUT2L_OUTP_WIDTH 1 /* HPOUT2L_OUTP */
+#define WM8995_HPOUT2L_DLY 0x0020 /* HPOUT2L_DLY */
+#define WM8995_HPOUT2L_DLY_MASK 0x0020 /* HPOUT2L_DLY */
+#define WM8995_HPOUT2L_DLY_SHIFT 5 /* HPOUT2L_DLY */
+#define WM8995_HPOUT2L_DLY_WIDTH 1 /* HPOUT2L_DLY */
+#define WM8995_HPOUT2R_RMV_SHORT 0x0008 /* HPOUT2R_RMV_SHORT */
+#define WM8995_HPOUT2R_RMV_SHORT_MASK 0x0008 /* HPOUT2R_RMV_SHORT */
+#define WM8995_HPOUT2R_RMV_SHORT_SHIFT 3 /* HPOUT2R_RMV_SHORT */
+#define WM8995_HPOUT2R_RMV_SHORT_WIDTH 1 /* HPOUT2R_RMV_SHORT */
+#define WM8995_HPOUT2R_OUTP 0x0004 /* HPOUT2R_OUTP */
+#define WM8995_HPOUT2R_OUTP_MASK 0x0004 /* HPOUT2R_OUTP */
+#define WM8995_HPOUT2R_OUTP_SHIFT 2 /* HPOUT2R_OUTP */
+#define WM8995_HPOUT2R_OUTP_WIDTH 1 /* HPOUT2R_OUTP */
+#define WM8995_HPOUT2R_DLY 0x0002 /* HPOUT2R_DLY */
+#define WM8995_HPOUT2R_DLY_MASK 0x0002 /* HPOUT2R_DLY */
+#define WM8995_HPOUT2R_DLY_SHIFT 1 /* HPOUT2R_DLY */
+#define WM8995_HPOUT2R_DLY_WIDTH 1 /* HPOUT2R_DLY */
+
+/*
+ * R256 (0x100) - Chip Revision
+ */
+#define WM8995_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */
+#define WM8995_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */
+#define WM8995_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */
+
+/*
+ * R257 (0x101) - Control Interface (1)
+ */
+#define WM8995_REG_SYNC 0x8000 /* REG_SYNC */
+#define WM8995_REG_SYNC_MASK 0x8000 /* REG_SYNC */
+#define WM8995_REG_SYNC_SHIFT 15 /* REG_SYNC */
+#define WM8995_REG_SYNC_WIDTH 1 /* REG_SYNC */
+#define WM8995_SPI_CONTRD 0x0040 /* SPI_CONTRD */
+#define WM8995_SPI_CONTRD_MASK 0x0040 /* SPI_CONTRD */
+#define WM8995_SPI_CONTRD_SHIFT 6 /* SPI_CONTRD */
+#define WM8995_SPI_CONTRD_WIDTH 1 /* SPI_CONTRD */
+#define WM8995_SPI_4WIRE 0x0020 /* SPI_4WIRE */
+#define WM8995_SPI_4WIRE_MASK 0x0020 /* SPI_4WIRE */
+#define WM8995_SPI_4WIRE_SHIFT 5 /* SPI_4WIRE */
+#define WM8995_SPI_4WIRE_WIDTH 1 /* SPI_4WIRE */
+#define WM8995_SPI_CFG 0x0010 /* SPI_CFG */
+#define WM8995_SPI_CFG_MASK 0x0010 /* SPI_CFG */
+#define WM8995_SPI_CFG_SHIFT 4 /* SPI_CFG */
+#define WM8995_SPI_CFG_WIDTH 1 /* SPI_CFG */
+#define WM8995_AUTO_INC 0x0004 /* AUTO_INC */
+#define WM8995_AUTO_INC_MASK 0x0004 /* AUTO_INC */
+#define WM8995_AUTO_INC_SHIFT 2 /* AUTO_INC */
+#define WM8995_AUTO_INC_WIDTH 1 /* AUTO_INC */
+
+/*
+ * R258 (0x102) - Control Interface (2)
+ */
+#define WM8995_CTRL_IF_SRC 0x0001 /* CTRL_IF_SRC */
+#define WM8995_CTRL_IF_SRC_MASK 0x0001 /* CTRL_IF_SRC */
+#define WM8995_CTRL_IF_SRC_SHIFT 0 /* CTRL_IF_SRC */
+#define WM8995_CTRL_IF_SRC_WIDTH 1 /* CTRL_IF_SRC */
+
+/*
+ * R272 (0x110) - Write Sequencer Ctrl (1)
+ */
+#define WM8995_WSEQ_ENA 0x8000 /* WSEQ_ENA */
+#define WM8995_WSEQ_ENA_MASK 0x8000 /* WSEQ_ENA */
+#define WM8995_WSEQ_ENA_SHIFT 15 /* WSEQ_ENA */
+#define WM8995_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM8995_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
+#define WM8995_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
+#define WM8995_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
+#define WM8995_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM8995_WSEQ_START 0x0100 /* WSEQ_START */
+#define WM8995_WSEQ_START_MASK 0x0100 /* WSEQ_START */
+#define WM8995_WSEQ_START_SHIFT 8 /* WSEQ_START */
+#define WM8995_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM8995_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */
+#define WM8995_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */
+#define WM8995_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */
+
+/*
+ * R273 (0x111) - Write Sequencer Ctrl (2)
+ */
+#define WM8995_WSEQ_BUSY 0x0100 /* WSEQ_BUSY */
+#define WM8995_WSEQ_BUSY_MASK 0x0100 /* WSEQ_BUSY */
+#define WM8995_WSEQ_BUSY_SHIFT 8 /* WSEQ_BUSY */
+#define WM8995_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+#define WM8995_WSEQ_CURRENT_INDEX_MASK 0x007F /* WSEQ_CURRENT_INDEX - [6:0] */
+#define WM8995_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [6:0] */
+#define WM8995_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [6:0] */
+
+/*
+ * R512 (0x200) - AIF1 Clocking (1)
+ */
+#define WM8995_AIF1CLK_SRC_MASK 0x0018 /* AIF1CLK_SRC - [4:3] */
+#define WM8995_AIF1CLK_SRC_SHIFT 3 /* AIF1CLK_SRC - [4:3] */
+#define WM8995_AIF1CLK_SRC_WIDTH 2 /* AIF1CLK_SRC - [4:3] */
+#define WM8995_AIF1CLK_INV 0x0004 /* AIF1CLK_INV */
+#define WM8995_AIF1CLK_INV_MASK 0x0004 /* AIF1CLK_INV */
+#define WM8995_AIF1CLK_INV_SHIFT 2 /* AIF1CLK_INV */
+#define WM8995_AIF1CLK_INV_WIDTH 1 /* AIF1CLK_INV */
+#define WM8995_AIF1CLK_DIV 0x0002 /* AIF1CLK_DIV */
+#define WM8995_AIF1CLK_DIV_MASK 0x0002 /* AIF1CLK_DIV */
+#define WM8995_AIF1CLK_DIV_SHIFT 1 /* AIF1CLK_DIV */
+#define WM8995_AIF1CLK_DIV_WIDTH 1 /* AIF1CLK_DIV */
+#define WM8995_AIF1CLK_ENA 0x0001 /* AIF1CLK_ENA */
+#define WM8995_AIF1CLK_ENA_MASK 0x0001 /* AIF1CLK_ENA */
+#define WM8995_AIF1CLK_ENA_SHIFT 0 /* AIF1CLK_ENA */
+#define WM8995_AIF1CLK_ENA_WIDTH 1 /* AIF1CLK_ENA */
+
+/*
+ * R513 (0x201) - AIF1 Clocking (2)
+ */
+#define WM8995_AIF1DAC_DIV_MASK 0x0038 /* AIF1DAC_DIV - [5:3] */
+#define WM8995_AIF1DAC_DIV_SHIFT 3 /* AIF1DAC_DIV - [5:3] */
+#define WM8995_AIF1DAC_DIV_WIDTH 3 /* AIF1DAC_DIV - [5:3] */
+#define WM8995_AIF1ADC_DIV_MASK 0x0007 /* AIF1ADC_DIV - [2:0] */
+#define WM8995_AIF1ADC_DIV_SHIFT 0 /* AIF1ADC_DIV - [2:0] */
+#define WM8995_AIF1ADC_DIV_WIDTH 3 /* AIF1ADC_DIV - [2:0] */
+
+/*
+ * R516 (0x204) - AIF2 Clocking (1)
+ */
+#define WM8995_AIF2CLK_SRC_MASK 0x0018 /* AIF2CLK_SRC - [4:3] */
+#define WM8995_AIF2CLK_SRC_SHIFT 3 /* AIF2CLK_SRC - [4:3] */
+#define WM8995_AIF2CLK_SRC_WIDTH 2 /* AIF2CLK_SRC - [4:3] */
+#define WM8995_AIF2CLK_INV 0x0004 /* AIF2CLK_INV */
+#define WM8995_AIF2CLK_INV_MASK 0x0004 /* AIF2CLK_INV */
+#define WM8995_AIF2CLK_INV_SHIFT 2 /* AIF2CLK_INV */
+#define WM8995_AIF2CLK_INV_WIDTH 1 /* AIF2CLK_INV */
+#define WM8995_AIF2CLK_DIV 0x0002 /* AIF2CLK_DIV */
+#define WM8995_AIF2CLK_DIV_MASK 0x0002 /* AIF2CLK_DIV */
+#define WM8995_AIF2CLK_DIV_SHIFT 1 /* AIF2CLK_DIV */
+#define WM8995_AIF2CLK_DIV_WIDTH 1 /* AIF2CLK_DIV */
+#define WM8995_AIF2CLK_ENA 0x0001 /* AIF2CLK_ENA */
+#define WM8995_AIF2CLK_ENA_MASK 0x0001 /* AIF2CLK_ENA */
+#define WM8995_AIF2CLK_ENA_SHIFT 0 /* AIF2CLK_ENA */
+#define WM8995_AIF2CLK_ENA_WIDTH 1 /* AIF2CLK_ENA */
+
+/*
+ * R517 (0x205) - AIF2 Clocking (2)
+ */
+#define WM8995_AIF2DAC_DIV_MASK 0x0038 /* AIF2DAC_DIV - [5:3] */
+#define WM8995_AIF2DAC_DIV_SHIFT 3 /* AIF2DAC_DIV - [5:3] */
+#define WM8995_AIF2DAC_DIV_WIDTH 3 /* AIF2DAC_DIV - [5:3] */
+#define WM8995_AIF2ADC_DIV_MASK 0x0007 /* AIF2ADC_DIV - [2:0] */
+#define WM8995_AIF2ADC_DIV_SHIFT 0 /* AIF2ADC_DIV - [2:0] */
+#define WM8995_AIF2ADC_DIV_WIDTH 3 /* AIF2ADC_DIV - [2:0] */
+
+/*
+ * R520 (0x208) - Clocking (1)
+ */
+#define WM8995_LFCLK_ENA 0x0020 /* LFCLK_ENA */
+#define WM8995_LFCLK_ENA_MASK 0x0020 /* LFCLK_ENA */
+#define WM8995_LFCLK_ENA_SHIFT 5 /* LFCLK_ENA */
+#define WM8995_LFCLK_ENA_WIDTH 1 /* LFCLK_ENA */
+#define WM8995_TOCLK_ENA 0x0010 /* TOCLK_ENA */
+#define WM8995_TOCLK_ENA_MASK 0x0010 /* TOCLK_ENA */
+#define WM8995_TOCLK_ENA_SHIFT 4 /* TOCLK_ENA */
+#define WM8995_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */
+#define WM8995_AIF1DSPCLK_ENA 0x0008 /* AIF1DSPCLK_ENA */
+#define WM8995_AIF1DSPCLK_ENA_MASK 0x0008 /* AIF1DSPCLK_ENA */
+#define WM8995_AIF1DSPCLK_ENA_SHIFT 3 /* AIF1DSPCLK_ENA */
+#define WM8995_AIF1DSPCLK_ENA_WIDTH 1 /* AIF1DSPCLK_ENA */
+#define WM8995_AIF2DSPCLK_ENA 0x0004 /* AIF2DSPCLK_ENA */
+#define WM8995_AIF2DSPCLK_ENA_MASK 0x0004 /* AIF2DSPCLK_ENA */
+#define WM8995_AIF2DSPCLK_ENA_SHIFT 2 /* AIF2DSPCLK_ENA */
+#define WM8995_AIF2DSPCLK_ENA_WIDTH 1 /* AIF2DSPCLK_ENA */
+#define WM8995_SYSDSPCLK_ENA 0x0002 /* SYSDSPCLK_ENA */
+#define WM8995_SYSDSPCLK_ENA_MASK 0x0002 /* SYSDSPCLK_ENA */
+#define WM8995_SYSDSPCLK_ENA_SHIFT 1 /* SYSDSPCLK_ENA */
+#define WM8995_SYSDSPCLK_ENA_WIDTH 1 /* SYSDSPCLK_ENA */
+#define WM8995_SYSCLK_SRC 0x0001 /* SYSCLK_SRC */
+#define WM8995_SYSCLK_SRC_MASK 0x0001 /* SYSCLK_SRC */
+#define WM8995_SYSCLK_SRC_SHIFT 0 /* SYSCLK_SRC */
+#define WM8995_SYSCLK_SRC_WIDTH 1 /* SYSCLK_SRC */
+
+/*
+ * R521 (0x209) - Clocking (2)
+ */
+#define WM8995_TOCLK_DIV_MASK 0x0700 /* TOCLK_DIV - [10:8] */
+#define WM8995_TOCLK_DIV_SHIFT 8 /* TOCLK_DIV - [10:8] */
+#define WM8995_TOCLK_DIV_WIDTH 3 /* TOCLK_DIV - [10:8] */
+#define WM8995_DBCLK_DIV_MASK 0x00F0 /* DBCLK_DIV - [7:4] */
+#define WM8995_DBCLK_DIV_SHIFT 4 /* DBCLK_DIV - [7:4] */
+#define WM8995_DBCLK_DIV_WIDTH 4 /* DBCLK_DIV - [7:4] */
+#define WM8995_OPCLK_DIV_MASK 0x0007 /* OPCLK_DIV - [2:0] */
+#define WM8995_OPCLK_DIV_SHIFT 0 /* OPCLK_DIV - [2:0] */
+#define WM8995_OPCLK_DIV_WIDTH 3 /* OPCLK_DIV - [2:0] */
+
+/*
+ * R528 (0x210) - AIF1 Rate
+ */
+#define WM8995_AIF1_SR_MASK 0x00F0 /* AIF1_SR - [7:4] */
+#define WM8995_AIF1_SR_SHIFT 4 /* AIF1_SR - [7:4] */
+#define WM8995_AIF1_SR_WIDTH 4 /* AIF1_SR - [7:4] */
+#define WM8995_AIF1CLK_RATE_MASK 0x000F /* AIF1CLK_RATE - [3:0] */
+#define WM8995_AIF1CLK_RATE_SHIFT 0 /* AIF1CLK_RATE - [3:0] */
+#define WM8995_AIF1CLK_RATE_WIDTH 4 /* AIF1CLK_RATE - [3:0] */
+
+/*
+ * R529 (0x211) - AIF2 Rate
+ */
+#define WM8995_AIF2_SR_MASK 0x00F0 /* AIF2_SR - [7:4] */
+#define WM8995_AIF2_SR_SHIFT 4 /* AIF2_SR - [7:4] */
+#define WM8995_AIF2_SR_WIDTH 4 /* AIF2_SR - [7:4] */
+#define WM8995_AIF2CLK_RATE_MASK 0x000F /* AIF2CLK_RATE - [3:0] */
+#define WM8995_AIF2CLK_RATE_SHIFT 0 /* AIF2CLK_RATE - [3:0] */
+#define WM8995_AIF2CLK_RATE_WIDTH 4 /* AIF2CLK_RATE - [3:0] */
+
+/*
+ * R530 (0x212) - Rate Status
+ */
+#define WM8995_SR_ERROR_MASK 0x000F /* SR_ERROR - [3:0] */
+#define WM8995_SR_ERROR_SHIFT 0 /* SR_ERROR - [3:0] */
+#define WM8995_SR_ERROR_WIDTH 4 /* SR_ERROR - [3:0] */
+
+/*
+ * R544 (0x220) - FLL1 Control (1)
+ */
+#define WM8995_FLL1_OSC_ENA 0x0002 /* FLL1_OSC_ENA */
+#define WM8995_FLL1_OSC_ENA_MASK 0x0002 /* FLL1_OSC_ENA */
+#define WM8995_FLL1_OSC_ENA_SHIFT 1 /* FLL1_OSC_ENA */
+#define WM8995_FLL1_OSC_ENA_WIDTH 1 /* FLL1_OSC_ENA */
+#define WM8995_FLL1_ENA 0x0001 /* FLL1_ENA */
+#define WM8995_FLL1_ENA_MASK 0x0001 /* FLL1_ENA */
+#define WM8995_FLL1_ENA_SHIFT 0 /* FLL1_ENA */
+#define WM8995_FLL1_ENA_WIDTH 1 /* FLL1_ENA */
+
+/*
+ * R545 (0x221) - FLL1 Control (2)
+ */
+#define WM8995_FLL1_OUTDIV_MASK 0x3F00 /* FLL1_OUTDIV - [13:8] */
+#define WM8995_FLL1_OUTDIV_SHIFT 8 /* FLL1_OUTDIV - [13:8] */
+#define WM8995_FLL1_OUTDIV_WIDTH 6 /* FLL1_OUTDIV - [13:8] */
+#define WM8995_FLL1_CTRL_RATE_MASK 0x0070 /* FLL1_CTRL_RATE - [6:4] */
+#define WM8995_FLL1_CTRL_RATE_SHIFT 4 /* FLL1_CTRL_RATE - [6:4] */
+#define WM8995_FLL1_CTRL_RATE_WIDTH 3 /* FLL1_CTRL_RATE - [6:4] */
+#define WM8995_FLL1_FRATIO_MASK 0x0007 /* FLL1_FRATIO - [2:0] */
+#define WM8995_FLL1_FRATIO_SHIFT 0 /* FLL1_FRATIO - [2:0] */
+#define WM8995_FLL1_FRATIO_WIDTH 3 /* FLL1_FRATIO - [2:0] */
+
+/*
+ * R546 (0x222) - FLL1 Control (3)
+ */
+#define WM8995_FLL1_K_MASK 0xFFFF /* FLL1_K - [15:0] */
+#define WM8995_FLL1_K_SHIFT 0 /* FLL1_K - [15:0] */
+#define WM8995_FLL1_K_WIDTH 16 /* FLL1_K - [15:0] */
+
+/*
+ * R547 (0x223) - FLL1 Control (4)
+ */
+#define WM8995_FLL1_N_MASK 0x7FE0 /* FLL1_N - [14:5] */
+#define WM8995_FLL1_N_SHIFT 5 /* FLL1_N - [14:5] */
+#define WM8995_FLL1_N_WIDTH 10 /* FLL1_N - [14:5] */
+#define WM8995_FLL1_LOOP_GAIN_MASK 0x000F /* FLL1_LOOP_GAIN - [3:0] */
+#define WM8995_FLL1_LOOP_GAIN_SHIFT 0 /* FLL1_LOOP_GAIN - [3:0] */
+#define WM8995_FLL1_LOOP_GAIN_WIDTH 4 /* FLL1_LOOP_GAIN - [3:0] */
+
+/*
+ * R548 (0x224) - FLL1 Control (5)
+ */
+#define WM8995_FLL1_FRC_NCO_VAL_MASK 0x1F80 /* FLL1_FRC_NCO_VAL - [12:7] */
+#define WM8995_FLL1_FRC_NCO_VAL_SHIFT 7 /* FLL1_FRC_NCO_VAL - [12:7] */
+#define WM8995_FLL1_FRC_NCO_VAL_WIDTH 6 /* FLL1_FRC_NCO_VAL - [12:7] */
+#define WM8995_FLL1_FRC_NCO 0x0040 /* FLL1_FRC_NCO */
+#define WM8995_FLL1_FRC_NCO_MASK 0x0040 /* FLL1_FRC_NCO */
+#define WM8995_FLL1_FRC_NCO_SHIFT 6 /* FLL1_FRC_NCO */
+#define WM8995_FLL1_FRC_NCO_WIDTH 1 /* FLL1_FRC_NCO */
+#define WM8995_FLL1_REFCLK_DIV_MASK 0x0018 /* FLL1_REFCLK_DIV - [4:3] */
+#define WM8995_FLL1_REFCLK_DIV_SHIFT 3 /* FLL1_REFCLK_DIV - [4:3] */
+#define WM8995_FLL1_REFCLK_DIV_WIDTH 2 /* FLL1_REFCLK_DIV - [4:3] */
+#define WM8995_FLL1_REFCLK_SRC_MASK 0x0003 /* FLL1_REFCLK_SRC - [1:0] */
+#define WM8995_FLL1_REFCLK_SRC_SHIFT 0 /* FLL1_REFCLK_SRC - [1:0] */
+#define WM8995_FLL1_REFCLK_SRC_WIDTH 2 /* FLL1_REFCLK_SRC - [1:0] */
+
+/*
+ * R576 (0x240) - FLL2 Control (1)
+ */
+#define WM8995_FLL2_OSC_ENA 0x0002 /* FLL2_OSC_ENA */
+#define WM8995_FLL2_OSC_ENA_MASK 0x0002 /* FLL2_OSC_ENA */
+#define WM8995_FLL2_OSC_ENA_SHIFT 1 /* FLL2_OSC_ENA */
+#define WM8995_FLL2_OSC_ENA_WIDTH 1 /* FLL2_OSC_ENA */
+#define WM8995_FLL2_ENA 0x0001 /* FLL2_ENA */
+#define WM8995_FLL2_ENA_MASK 0x0001 /* FLL2_ENA */
+#define WM8995_FLL2_ENA_SHIFT 0 /* FLL2_ENA */
+#define WM8995_FLL2_ENA_WIDTH 1 /* FLL2_ENA */
+
+/*
+ * R577 (0x241) - FLL2 Control (2)
+ */
+#define WM8995_FLL2_OUTDIV_MASK 0x3F00 /* FLL2_OUTDIV - [13:8] */
+#define WM8995_FLL2_OUTDIV_SHIFT 8 /* FLL2_OUTDIV - [13:8] */
+#define WM8995_FLL2_OUTDIV_WIDTH 6 /* FLL2_OUTDIV - [13:8] */
+#define WM8995_FLL2_CTRL_RATE_MASK 0x0070 /* FLL2_CTRL_RATE - [6:4] */
+#define WM8995_FLL2_CTRL_RATE_SHIFT 4 /* FLL2_CTRL_RATE - [6:4] */
+#define WM8995_FLL2_CTRL_RATE_WIDTH 3 /* FLL2_CTRL_RATE - [6:4] */
+#define WM8995_FLL2_FRATIO_MASK 0x0007 /* FLL2_FRATIO - [2:0] */
+#define WM8995_FLL2_FRATIO_SHIFT 0 /* FLL2_FRATIO - [2:0] */
+#define WM8995_FLL2_FRATIO_WIDTH 3 /* FLL2_FRATIO - [2:0] */
+
+/*
+ * R578 (0x242) - FLL2 Control (3)
+ */
+#define WM8995_FLL2_K_MASK 0xFFFF /* FLL2_K - [15:0] */
+#define WM8995_FLL2_K_SHIFT 0 /* FLL2_K - [15:0] */
+#define WM8995_FLL2_K_WIDTH 16 /* FLL2_K - [15:0] */
+
+/*
+ * R579 (0x243) - FLL2 Control (4)
+ */
+#define WM8995_FLL2_N_MASK 0x7FE0 /* FLL2_N - [14:5] */
+#define WM8995_FLL2_N_SHIFT 5 /* FLL2_N - [14:5] */
+#define WM8995_FLL2_N_WIDTH 10 /* FLL2_N - [14:5] */
+#define WM8995_FLL2_LOOP_GAIN_MASK 0x000F /* FLL2_LOOP_GAIN - [3:0] */
+#define WM8995_FLL2_LOOP_GAIN_SHIFT 0 /* FLL2_LOOP_GAIN - [3:0] */
+#define WM8995_FLL2_LOOP_GAIN_WIDTH 4 /* FLL2_LOOP_GAIN - [3:0] */
+
+/*
+ * R580 (0x244) - FLL2 Control (5)
+ */
+#define WM8995_FLL2_FRC_NCO_VAL_MASK 0x1F80 /* FLL2_FRC_NCO_VAL - [12:7] */
+#define WM8995_FLL2_FRC_NCO_VAL_SHIFT 7 /* FLL2_FRC_NCO_VAL - [12:7] */
+#define WM8995_FLL2_FRC_NCO_VAL_WIDTH 6 /* FLL2_FRC_NCO_VAL - [12:7] */
+#define WM8995_FLL2_FRC_NCO 0x0040 /* FLL2_FRC_NCO */
+#define WM8995_FLL2_FRC_NCO_MASK 0x0040 /* FLL2_FRC_NCO */
+#define WM8995_FLL2_FRC_NCO_SHIFT 6 /* FLL2_FRC_NCO */
+#define WM8995_FLL2_FRC_NCO_WIDTH 1 /* FLL2_FRC_NCO */
+#define WM8995_FLL2_REFCLK_DIV_MASK 0x0018 /* FLL2_REFCLK_DIV - [4:3] */
+#define WM8995_FLL2_REFCLK_DIV_SHIFT 3 /* FLL2_REFCLK_DIV - [4:3] */
+#define WM8995_FLL2_REFCLK_DIV_WIDTH 2 /* FLL2_REFCLK_DIV - [4:3] */
+#define WM8995_FLL2_REFCLK_SRC_MASK 0x0003 /* FLL2_REFCLK_SRC - [1:0] */
+#define WM8995_FLL2_REFCLK_SRC_SHIFT 0 /* FLL2_REFCLK_SRC - [1:0] */
+#define WM8995_FLL2_REFCLK_SRC_WIDTH 2 /* FLL2_REFCLK_SRC - [1:0] */
+
+/*
+ * R768 (0x300) - AIF1 Control (1)
+ */
+#define WM8995_AIF1ADCL_SRC 0x8000 /* AIF1ADCL_SRC */
+#define WM8995_AIF1ADCL_SRC_MASK 0x8000 /* AIF1ADCL_SRC */
+#define WM8995_AIF1ADCL_SRC_SHIFT 15 /* AIF1ADCL_SRC */
+#define WM8995_AIF1ADCL_SRC_WIDTH 1 /* AIF1ADCL_SRC */
+#define WM8995_AIF1ADCR_SRC 0x4000 /* AIF1ADCR_SRC */
+#define WM8995_AIF1ADCR_SRC_MASK 0x4000 /* AIF1ADCR_SRC */
+#define WM8995_AIF1ADCR_SRC_SHIFT 14 /* AIF1ADCR_SRC */
+#define WM8995_AIF1ADCR_SRC_WIDTH 1 /* AIF1ADCR_SRC */
+#define WM8995_AIF1ADC_TDM 0x2000 /* AIF1ADC_TDM */
+#define WM8995_AIF1ADC_TDM_MASK 0x2000 /* AIF1ADC_TDM */
+#define WM8995_AIF1ADC_TDM_SHIFT 13 /* AIF1ADC_TDM */
+#define WM8995_AIF1ADC_TDM_WIDTH 1 /* AIF1ADC_TDM */
+#define WM8995_AIF1_BCLK_INV 0x0100 /* AIF1_BCLK_INV */
+#define WM8995_AIF1_BCLK_INV_MASK 0x0100 /* AIF1_BCLK_INV */
+#define WM8995_AIF1_BCLK_INV_SHIFT 8 /* AIF1_BCLK_INV */
+#define WM8995_AIF1_BCLK_INV_WIDTH 1 /* AIF1_BCLK_INV */
+#define WM8995_AIF1_LRCLK_INV 0x0080 /* AIF1_LRCLK_INV */
+#define WM8995_AIF1_LRCLK_INV_MASK 0x0080 /* AIF1_LRCLK_INV */
+#define WM8995_AIF1_LRCLK_INV_SHIFT 7 /* AIF1_LRCLK_INV */
+#define WM8995_AIF1_LRCLK_INV_WIDTH 1 /* AIF1_LRCLK_INV */
+#define WM8995_AIF1_WL_MASK 0x0060 /* AIF1_WL - [6:5] */
+#define WM8995_AIF1_WL_SHIFT 5 /* AIF1_WL - [6:5] */
+#define WM8995_AIF1_WL_WIDTH 2 /* AIF1_WL - [6:5] */
+#define WM8995_AIF1_FMT_MASK 0x0018 /* AIF1_FMT - [4:3] */
+#define WM8995_AIF1_FMT_SHIFT 3 /* AIF1_FMT - [4:3] */
+#define WM8995_AIF1_FMT_WIDTH 2 /* AIF1_FMT - [4:3] */
+
+/*
+ * R769 (0x301) - AIF1 Control (2)
+ */
+#define WM8995_AIF1DACL_SRC 0x8000 /* AIF1DACL_SRC */
+#define WM8995_AIF1DACL_SRC_MASK 0x8000 /* AIF1DACL_SRC */
+#define WM8995_AIF1DACL_SRC_SHIFT 15 /* AIF1DACL_SRC */
+#define WM8995_AIF1DACL_SRC_WIDTH 1 /* AIF1DACL_SRC */
+#define WM8995_AIF1DACR_SRC 0x4000 /* AIF1DACR_SRC */
+#define WM8995_AIF1DACR_SRC_MASK 0x4000 /* AIF1DACR_SRC */
+#define WM8995_AIF1DACR_SRC_SHIFT 14 /* AIF1DACR_SRC */
+#define WM8995_AIF1DACR_SRC_WIDTH 1 /* AIF1DACR_SRC */
+#define WM8995_AIF1DAC_BOOST_MASK 0x0C00 /* AIF1DAC_BOOST - [11:10] */
+#define WM8995_AIF1DAC_BOOST_SHIFT 10 /* AIF1DAC_BOOST - [11:10] */
+#define WM8995_AIF1DAC_BOOST_WIDTH 2 /* AIF1DAC_BOOST - [11:10] */
+#define WM8995_AIF1DAC_COMP 0x0010 /* AIF1DAC_COMP */
+#define WM8995_AIF1DAC_COMP_MASK 0x0010 /* AIF1DAC_COMP */
+#define WM8995_AIF1DAC_COMP_SHIFT 4 /* AIF1DAC_COMP */
+#define WM8995_AIF1DAC_COMP_WIDTH 1 /* AIF1DAC_COMP */
+#define WM8995_AIF1DAC_COMPMODE 0x0008 /* AIF1DAC_COMPMODE */
+#define WM8995_AIF1DAC_COMPMODE_MASK 0x0008 /* AIF1DAC_COMPMODE */
+#define WM8995_AIF1DAC_COMPMODE_SHIFT 3 /* AIF1DAC_COMPMODE */
+#define WM8995_AIF1DAC_COMPMODE_WIDTH 1 /* AIF1DAC_COMPMODE */
+#define WM8995_AIF1ADC_COMP 0x0004 /* AIF1ADC_COMP */
+#define WM8995_AIF1ADC_COMP_MASK 0x0004 /* AIF1ADC_COMP */
+#define WM8995_AIF1ADC_COMP_SHIFT 2 /* AIF1ADC_COMP */
+#define WM8995_AIF1ADC_COMP_WIDTH 1 /* AIF1ADC_COMP */
+#define WM8995_AIF1ADC_COMPMODE 0x0002 /* AIF1ADC_COMPMODE */
+#define WM8995_AIF1ADC_COMPMODE_MASK 0x0002 /* AIF1ADC_COMPMODE */
+#define WM8995_AIF1ADC_COMPMODE_SHIFT 1 /* AIF1ADC_COMPMODE */
+#define WM8995_AIF1ADC_COMPMODE_WIDTH 1 /* AIF1ADC_COMPMODE */
+#define WM8995_AIF1_LOOPBACK 0x0001 /* AIF1_LOOPBACK */
+#define WM8995_AIF1_LOOPBACK_MASK 0x0001 /* AIF1_LOOPBACK */
+#define WM8995_AIF1_LOOPBACK_SHIFT 0 /* AIF1_LOOPBACK */
+#define WM8995_AIF1_LOOPBACK_WIDTH 1 /* AIF1_LOOPBACK */
+
+/*
+ * R770 (0x302) - AIF1 Master/Slave
+ */
+#define WM8995_AIF1_TRI 0x8000 /* AIF1_TRI */
+#define WM8995_AIF1_TRI_MASK 0x8000 /* AIF1_TRI */
+#define WM8995_AIF1_TRI_SHIFT 15 /* AIF1_TRI */
+#define WM8995_AIF1_TRI_WIDTH 1 /* AIF1_TRI */
+#define WM8995_AIF1_MSTR 0x4000 /* AIF1_MSTR */
+#define WM8995_AIF1_MSTR_MASK 0x4000 /* AIF1_MSTR */
+#define WM8995_AIF1_MSTR_SHIFT 14 /* AIF1_MSTR */
+#define WM8995_AIF1_MSTR_WIDTH 1 /* AIF1_MSTR */
+#define WM8995_AIF1_CLK_FRC 0x2000 /* AIF1_CLK_FRC */
+#define WM8995_AIF1_CLK_FRC_MASK 0x2000 /* AIF1_CLK_FRC */
+#define WM8995_AIF1_CLK_FRC_SHIFT 13 /* AIF1_CLK_FRC */
+#define WM8995_AIF1_CLK_FRC_WIDTH 1 /* AIF1_CLK_FRC */
+#define WM8995_AIF1_LRCLK_FRC 0x1000 /* AIF1_LRCLK_FRC */
+#define WM8995_AIF1_LRCLK_FRC_MASK 0x1000 /* AIF1_LRCLK_FRC */
+#define WM8995_AIF1_LRCLK_FRC_SHIFT 12 /* AIF1_LRCLK_FRC */
+#define WM8995_AIF1_LRCLK_FRC_WIDTH 1 /* AIF1_LRCLK_FRC */
+
+/*
+ * R771 (0x303) - AIF1 BCLK
+ */
+#define WM8995_AIF1_BCLK_DIV_MASK 0x00F0 /* AIF1_BCLK_DIV - [7:4] */
+#define WM8995_AIF1_BCLK_DIV_SHIFT 4 /* AIF1_BCLK_DIV - [7:4] */
+#define WM8995_AIF1_BCLK_DIV_WIDTH 4 /* AIF1_BCLK_DIV - [7:4] */
+
+/*
+ * R772 (0x304) - AIF1ADC LRCLK
+ */
+#define WM8995_AIF1ADC_LRCLK_DIR 0x0800 /* AIF1ADC_LRCLK_DIR */
+#define WM8995_AIF1ADC_LRCLK_DIR_MASK 0x0800 /* AIF1ADC_LRCLK_DIR */
+#define WM8995_AIF1ADC_LRCLK_DIR_SHIFT 11 /* AIF1ADC_LRCLK_DIR */
+#define WM8995_AIF1ADC_LRCLK_DIR_WIDTH 1 /* AIF1ADC_LRCLK_DIR */
+#define WM8995_AIF1ADC_RATE_MASK 0x07FF /* AIF1ADC_RATE - [10:0] */
+#define WM8995_AIF1ADC_RATE_SHIFT 0 /* AIF1ADC_RATE - [10:0] */
+#define WM8995_AIF1ADC_RATE_WIDTH 11 /* AIF1ADC_RATE - [10:0] */
+
+/*
+ * R773 (0x305) - AIF1DAC LRCLK
+ */
+#define WM8995_AIF1DAC_LRCLK_DIR 0x0800 /* AIF1DAC_LRCLK_DIR */
+#define WM8995_AIF1DAC_LRCLK_DIR_MASK 0x0800 /* AIF1DAC_LRCLK_DIR */
+#define WM8995_AIF1DAC_LRCLK_DIR_SHIFT 11 /* AIF1DAC_LRCLK_DIR */
+#define WM8995_AIF1DAC_LRCLK_DIR_WIDTH 1 /* AIF1DAC_LRCLK_DIR */
+#define WM8995_AIF1DAC_RATE_MASK 0x07FF /* AIF1DAC_RATE - [10:0] */
+#define WM8995_AIF1DAC_RATE_SHIFT 0 /* AIF1DAC_RATE - [10:0] */
+#define WM8995_AIF1DAC_RATE_WIDTH 11 /* AIF1DAC_RATE - [10:0] */
+
+/*
+ * R774 (0x306) - AIF1DAC Data
+ */
+#define WM8995_AIF1DACL_DAT_INV 0x0002 /* AIF1DACL_DAT_INV */
+#define WM8995_AIF1DACL_DAT_INV_MASK 0x0002 /* AIF1DACL_DAT_INV */
+#define WM8995_AIF1DACL_DAT_INV_SHIFT 1 /* AIF1DACL_DAT_INV */
+#define WM8995_AIF1DACL_DAT_INV_WIDTH 1 /* AIF1DACL_DAT_INV */
+#define WM8995_AIF1DACR_DAT_INV 0x0001 /* AIF1DACR_DAT_INV */
+#define WM8995_AIF1DACR_DAT_INV_MASK 0x0001 /* AIF1DACR_DAT_INV */
+#define WM8995_AIF1DACR_DAT_INV_SHIFT 0 /* AIF1DACR_DAT_INV */
+#define WM8995_AIF1DACR_DAT_INV_WIDTH 1 /* AIF1DACR_DAT_INV */
+
+/*
+ * R775 (0x307) - AIF1ADC Data
+ */
+#define WM8995_AIF1ADCL_DAT_INV 0x0002 /* AIF1ADCL_DAT_INV */
+#define WM8995_AIF1ADCL_DAT_INV_MASK 0x0002 /* AIF1ADCL_DAT_INV */
+#define WM8995_AIF1ADCL_DAT_INV_SHIFT 1 /* AIF1ADCL_DAT_INV */
+#define WM8995_AIF1ADCL_DAT_INV_WIDTH 1 /* AIF1ADCL_DAT_INV */
+#define WM8995_AIF1ADCR_DAT_INV 0x0001 /* AIF1ADCR_DAT_INV */
+#define WM8995_AIF1ADCR_DAT_INV_MASK 0x0001 /* AIF1ADCR_DAT_INV */
+#define WM8995_AIF1ADCR_DAT_INV_SHIFT 0 /* AIF1ADCR_DAT_INV */
+#define WM8995_AIF1ADCR_DAT_INV_WIDTH 1 /* AIF1ADCR_DAT_INV */
+
+/*
+ * R784 (0x310) - AIF2 Control (1)
+ */
+#define WM8995_AIF2ADCL_SRC 0x8000 /* AIF2ADCL_SRC */
+#define WM8995_AIF2ADCL_SRC_MASK 0x8000 /* AIF2ADCL_SRC */
+#define WM8995_AIF2ADCL_SRC_SHIFT 15 /* AIF2ADCL_SRC */
+#define WM8995_AIF2ADCL_SRC_WIDTH 1 /* AIF2ADCL_SRC */
+#define WM8995_AIF2ADCR_SRC 0x4000 /* AIF2ADCR_SRC */
+#define WM8995_AIF2ADCR_SRC_MASK 0x4000 /* AIF2ADCR_SRC */
+#define WM8995_AIF2ADCR_SRC_SHIFT 14 /* AIF2ADCR_SRC */
+#define WM8995_AIF2ADCR_SRC_WIDTH 1 /* AIF2ADCR_SRC */
+#define WM8995_AIF2ADC_TDM 0x2000 /* AIF2ADC_TDM */
+#define WM8995_AIF2ADC_TDM_MASK 0x2000 /* AIF2ADC_TDM */
+#define WM8995_AIF2ADC_TDM_SHIFT 13 /* AIF2ADC_TDM */
+#define WM8995_AIF2ADC_TDM_WIDTH 1 /* AIF2ADC_TDM */
+#define WM8995_AIF2ADC_TDM_CHAN 0x1000 /* AIF2ADC_TDM_CHAN */
+#define WM8995_AIF2ADC_TDM_CHAN_MASK 0x1000 /* AIF2ADC_TDM_CHAN */
+#define WM8995_AIF2ADC_TDM_CHAN_SHIFT 12 /* AIF2ADC_TDM_CHAN */
+#define WM8995_AIF2ADC_TDM_CHAN_WIDTH 1 /* AIF2ADC_TDM_CHAN */
+#define WM8995_AIF2_BCLK_INV 0x0100 /* AIF2_BCLK_INV */
+#define WM8995_AIF2_BCLK_INV_MASK 0x0100 /* AIF2_BCLK_INV */
+#define WM8995_AIF2_BCLK_INV_SHIFT 8 /* AIF2_BCLK_INV */
+#define WM8995_AIF2_BCLK_INV_WIDTH 1 /* AIF2_BCLK_INV */
+#define WM8995_AIF2_LRCLK_INV 0x0080 /* AIF2_LRCLK_INV */
+#define WM8995_AIF2_LRCLK_INV_MASK 0x0080 /* AIF2_LRCLK_INV */
+#define WM8995_AIF2_LRCLK_INV_SHIFT 7 /* AIF2_LRCLK_INV */
+#define WM8995_AIF2_LRCLK_INV_WIDTH 1 /* AIF2_LRCLK_INV */
+#define WM8995_AIF2_WL_MASK 0x0060 /* AIF2_WL - [6:5] */
+#define WM8995_AIF2_WL_SHIFT 5 /* AIF2_WL - [6:5] */
+#define WM8995_AIF2_WL_WIDTH 2 /* AIF2_WL - [6:5] */
+#define WM8995_AIF2_FMT_MASK 0x0018 /* AIF2_FMT - [4:3] */
+#define WM8995_AIF2_FMT_SHIFT 3 /* AIF2_FMT - [4:3] */
+#define WM8995_AIF2_FMT_WIDTH 2 /* AIF2_FMT - [4:3] */
+
+/*
+ * R785 (0x311) - AIF2 Control (2)
+ */
+#define WM8995_AIF2DACL_SRC 0x8000 /* AIF2DACL_SRC */
+#define WM8995_AIF2DACL_SRC_MASK 0x8000 /* AIF2DACL_SRC */
+#define WM8995_AIF2DACL_SRC_SHIFT 15 /* AIF2DACL_SRC */
+#define WM8995_AIF2DACL_SRC_WIDTH 1 /* AIF2DACL_SRC */
+#define WM8995_AIF2DACR_SRC 0x4000 /* AIF2DACR_SRC */
+#define WM8995_AIF2DACR_SRC_MASK 0x4000 /* AIF2DACR_SRC */
+#define WM8995_AIF2DACR_SRC_SHIFT 14 /* AIF2DACR_SRC */
+#define WM8995_AIF2DACR_SRC_WIDTH 1 /* AIF2DACR_SRC */
+#define WM8995_AIF2DAC_TDM 0x2000 /* AIF2DAC_TDM */
+#define WM8995_AIF2DAC_TDM_MASK 0x2000 /* AIF2DAC_TDM */
+#define WM8995_AIF2DAC_TDM_SHIFT 13 /* AIF2DAC_TDM */
+#define WM8995_AIF2DAC_TDM_WIDTH 1 /* AIF2DAC_TDM */
+#define WM8995_AIF2DAC_TDM_CHAN 0x1000 /* AIF2DAC_TDM_CHAN */
+#define WM8995_AIF2DAC_TDM_CHAN_MASK 0x1000 /* AIF2DAC_TDM_CHAN */
+#define WM8995_AIF2DAC_TDM_CHAN_SHIFT 12 /* AIF2DAC_TDM_CHAN */
+#define WM8995_AIF2DAC_TDM_CHAN_WIDTH 1 /* AIF2DAC_TDM_CHAN */
+#define WM8995_AIF2DAC_BOOST_MASK 0x0C00 /* AIF2DAC_BOOST - [11:10] */
+#define WM8995_AIF2DAC_BOOST_SHIFT 10 /* AIF2DAC_BOOST - [11:10] */
+#define WM8995_AIF2DAC_BOOST_WIDTH 2 /* AIF2DAC_BOOST - [11:10] */
+#define WM8995_AIF2DAC_COMP 0x0010 /* AIF2DAC_COMP */
+#define WM8995_AIF2DAC_COMP_MASK 0x0010 /* AIF2DAC_COMP */
+#define WM8995_AIF2DAC_COMP_SHIFT 4 /* AIF2DAC_COMP */
+#define WM8995_AIF2DAC_COMP_WIDTH 1 /* AIF2DAC_COMP */
+#define WM8995_AIF2DAC_COMPMODE 0x0008 /* AIF2DAC_COMPMODE */
+#define WM8995_AIF2DAC_COMPMODE_MASK 0x0008 /* AIF2DAC_COMPMODE */
+#define WM8995_AIF2DAC_COMPMODE_SHIFT 3 /* AIF2DAC_COMPMODE */
+#define WM8995_AIF2DAC_COMPMODE_WIDTH 1 /* AIF2DAC_COMPMODE */
+#define WM8995_AIF2ADC_COMP 0x0004 /* AIF2ADC_COMP */
+#define WM8995_AIF2ADC_COMP_MASK 0x0004 /* AIF2ADC_COMP */
+#define WM8995_AIF2ADC_COMP_SHIFT 2 /* AIF2ADC_COMP */
+#define WM8995_AIF2ADC_COMP_WIDTH 1 /* AIF2ADC_COMP */
+#define WM8995_AIF2ADC_COMPMODE 0x0002 /* AIF2ADC_COMPMODE */
+#define WM8995_AIF2ADC_COMPMODE_MASK 0x0002 /* AIF2ADC_COMPMODE */
+#define WM8995_AIF2ADC_COMPMODE_SHIFT 1 /* AIF2ADC_COMPMODE */
+#define WM8995_AIF2ADC_COMPMODE_WIDTH 1 /* AIF2ADC_COMPMODE */
+#define WM8995_AIF2_LOOPBACK 0x0001 /* AIF2_LOOPBACK */
+#define WM8995_AIF2_LOOPBACK_MASK 0x0001 /* AIF2_LOOPBACK */
+#define WM8995_AIF2_LOOPBACK_SHIFT 0 /* AIF2_LOOPBACK */
+#define WM8995_AIF2_LOOPBACK_WIDTH 1 /* AIF2_LOOPBACK */
+
+/*
+ * R786 (0x312) - AIF2 Master/Slave
+ */
+#define WM8995_AIF2_TRI 0x8000 /* AIF2_TRI */
+#define WM8995_AIF2_TRI_MASK 0x8000 /* AIF2_TRI */
+#define WM8995_AIF2_TRI_SHIFT 15 /* AIF2_TRI */
+#define WM8995_AIF2_TRI_WIDTH 1 /* AIF2_TRI */
+#define WM8995_AIF2_MSTR 0x4000 /* AIF2_MSTR */
+#define WM8995_AIF2_MSTR_MASK 0x4000 /* AIF2_MSTR */
+#define WM8995_AIF2_MSTR_SHIFT 14 /* AIF2_MSTR */
+#define WM8995_AIF2_MSTR_WIDTH 1 /* AIF2_MSTR */
+#define WM8995_AIF2_CLK_FRC 0x2000 /* AIF2_CLK_FRC */
+#define WM8995_AIF2_CLK_FRC_MASK 0x2000 /* AIF2_CLK_FRC */
+#define WM8995_AIF2_CLK_FRC_SHIFT 13 /* AIF2_CLK_FRC */
+#define WM8995_AIF2_CLK_FRC_WIDTH 1 /* AIF2_CLK_FRC */
+#define WM8995_AIF2_LRCLK_FRC 0x1000 /* AIF2_LRCLK_FRC */
+#define WM8995_AIF2_LRCLK_FRC_MASK 0x1000 /* AIF2_LRCLK_FRC */
+#define WM8995_AIF2_LRCLK_FRC_SHIFT 12 /* AIF2_LRCLK_FRC */
+#define WM8995_AIF2_LRCLK_FRC_WIDTH 1 /* AIF2_LRCLK_FRC */
+
+/*
+ * R787 (0x313) - AIF2 BCLK
+ */
+#define WM8995_AIF2_BCLK_DIV_MASK 0x00F0 /* AIF2_BCLK_DIV - [7:4] */
+#define WM8995_AIF2_BCLK_DIV_SHIFT 4 /* AIF2_BCLK_DIV - [7:4] */
+#define WM8995_AIF2_BCLK_DIV_WIDTH 4 /* AIF2_BCLK_DIV - [7:4] */
+
+/*
+ * R788 (0x314) - AIF2ADC LRCLK
+ */
+#define WM8995_AIF2ADC_LRCLK_DIR 0x0800 /* AIF2ADC_LRCLK_DIR */
+#define WM8995_AIF2ADC_LRCLK_DIR_MASK 0x0800 /* AIF2ADC_LRCLK_DIR */
+#define WM8995_AIF2ADC_LRCLK_DIR_SHIFT 11 /* AIF2ADC_LRCLK_DIR */
+#define WM8995_AIF2ADC_LRCLK_DIR_WIDTH 1 /* AIF2ADC_LRCLK_DIR */
+#define WM8995_AIF2ADC_RATE_MASK 0x07FF /* AIF2ADC_RATE - [10:0] */
+#define WM8995_AIF2ADC_RATE_SHIFT 0 /* AIF2ADC_RATE - [10:0] */
+#define WM8995_AIF2ADC_RATE_WIDTH 11 /* AIF2ADC_RATE - [10:0] */
+
+/*
+ * R789 (0x315) - AIF2DAC LRCLK
+ */
+#define WM8995_AIF2DAC_LRCLK_DIR 0x0800 /* AIF2DAC_LRCLK_DIR */
+#define WM8995_AIF2DAC_LRCLK_DIR_MASK 0x0800 /* AIF2DAC_LRCLK_DIR */
+#define WM8995_AIF2DAC_LRCLK_DIR_SHIFT 11 /* AIF2DAC_LRCLK_DIR */
+#define WM8995_AIF2DAC_LRCLK_DIR_WIDTH 1 /* AIF2DAC_LRCLK_DIR */
+#define WM8995_AIF2DAC_RATE_MASK 0x07FF /* AIF2DAC_RATE - [10:0] */
+#define WM8995_AIF2DAC_RATE_SHIFT 0 /* AIF2DAC_RATE - [10:0] */
+#define WM8995_AIF2DAC_RATE_WIDTH 11 /* AIF2DAC_RATE - [10:0] */
+
+/*
+ * R790 (0x316) - AIF2DAC Data
+ */
+#define WM8995_AIF2DACL_DAT_INV 0x0002 /* AIF2DACL_DAT_INV */
+#define WM8995_AIF2DACL_DAT_INV_MASK 0x0002 /* AIF2DACL_DAT_INV */
+#define WM8995_AIF2DACL_DAT_INV_SHIFT 1 /* AIF2DACL_DAT_INV */
+#define WM8995_AIF2DACL_DAT_INV_WIDTH 1 /* AIF2DACL_DAT_INV */
+#define WM8995_AIF2DACR_DAT_INV 0x0001 /* AIF2DACR_DAT_INV */
+#define WM8995_AIF2DACR_DAT_INV_MASK 0x0001 /* AIF2DACR_DAT_INV */
+#define WM8995_AIF2DACR_DAT_INV_SHIFT 0 /* AIF2DACR_DAT_INV */
+#define WM8995_AIF2DACR_DAT_INV_WIDTH 1 /* AIF2DACR_DAT_INV */
+
+/*
+ * R791 (0x317) - AIF2ADC Data
+ */
+#define WM8995_AIF2ADCL_DAT_INV 0x0002 /* AIF2ADCL_DAT_INV */
+#define WM8995_AIF2ADCL_DAT_INV_MASK 0x0002 /* AIF2ADCL_DAT_INV */
+#define WM8995_AIF2ADCL_DAT_INV_SHIFT 1 /* AIF2ADCL_DAT_INV */
+#define WM8995_AIF2ADCL_DAT_INV_WIDTH 1 /* AIF2ADCL_DAT_INV */
+#define WM8995_AIF2ADCR_DAT_INV 0x0001 /* AIF2ADCR_DAT_INV */
+#define WM8995_AIF2ADCR_DAT_INV_MASK 0x0001 /* AIF2ADCR_DAT_INV */
+#define WM8995_AIF2ADCR_DAT_INV_SHIFT 0 /* AIF2ADCR_DAT_INV */
+#define WM8995_AIF2ADCR_DAT_INV_WIDTH 1 /* AIF2ADCR_DAT_INV */
+
+/*
+ * R1024 (0x400) - AIF1 ADC1 Left Volume
+ */
+#define WM8995_AIF1ADC1_VU 0x0100 /* AIF1ADC1_VU */
+#define WM8995_AIF1ADC1_VU_MASK 0x0100 /* AIF1ADC1_VU */
+#define WM8995_AIF1ADC1_VU_SHIFT 8 /* AIF1ADC1_VU */
+#define WM8995_AIF1ADC1_VU_WIDTH 1 /* AIF1ADC1_VU */
+#define WM8995_AIF1ADC1L_VOL_MASK 0x00FF /* AIF1ADC1L_VOL - [7:0] */
+#define WM8995_AIF1ADC1L_VOL_SHIFT 0 /* AIF1ADC1L_VOL - [7:0] */
+#define WM8995_AIF1ADC1L_VOL_WIDTH 8 /* AIF1ADC1L_VOL - [7:0] */
+
+/*
+ * R1025 (0x401) - AIF1 ADC1 Right Volume
+ */
+#define WM8995_AIF1ADC1_VU 0x0100 /* AIF1ADC1_VU */
+#define WM8995_AIF1ADC1_VU_MASK 0x0100 /* AIF1ADC1_VU */
+#define WM8995_AIF1ADC1_VU_SHIFT 8 /* AIF1ADC1_VU */
+#define WM8995_AIF1ADC1_VU_WIDTH 1 /* AIF1ADC1_VU */
+#define WM8995_AIF1ADC1R_VOL_MASK 0x00FF /* AIF1ADC1R_VOL - [7:0] */
+#define WM8995_AIF1ADC1R_VOL_SHIFT 0 /* AIF1ADC1R_VOL - [7:0] */
+#define WM8995_AIF1ADC1R_VOL_WIDTH 8 /* AIF1ADC1R_VOL - [7:0] */
+
+/*
+ * R1026 (0x402) - AIF1 DAC1 Left Volume
+ */
+#define WM8995_AIF1DAC1_VU 0x0100 /* AIF1DAC1_VU */
+#define WM8995_AIF1DAC1_VU_MASK 0x0100 /* AIF1DAC1_VU */
+#define WM8995_AIF1DAC1_VU_SHIFT 8 /* AIF1DAC1_VU */
+#define WM8995_AIF1DAC1_VU_WIDTH 1 /* AIF1DAC1_VU */
+#define WM8995_AIF1DAC1L_VOL_MASK 0x00FF /* AIF1DAC1L_VOL - [7:0] */
+#define WM8995_AIF1DAC1L_VOL_SHIFT 0 /* AIF1DAC1L_VOL - [7:0] */
+#define WM8995_AIF1DAC1L_VOL_WIDTH 8 /* AIF1DAC1L_VOL - [7:0] */
+
+/*
+ * R1027 (0x403) - AIF1 DAC1 Right Volume
+ */
+#define WM8995_AIF1DAC1_VU 0x0100 /* AIF1DAC1_VU */
+#define WM8995_AIF1DAC1_VU_MASK 0x0100 /* AIF1DAC1_VU */
+#define WM8995_AIF1DAC1_VU_SHIFT 8 /* AIF1DAC1_VU */
+#define WM8995_AIF1DAC1_VU_WIDTH 1 /* AIF1DAC1_VU */
+#define WM8995_AIF1DAC1R_VOL_MASK 0x00FF /* AIF1DAC1R_VOL - [7:0] */
+#define WM8995_AIF1DAC1R_VOL_SHIFT 0 /* AIF1DAC1R_VOL - [7:0] */
+#define WM8995_AIF1DAC1R_VOL_WIDTH 8 /* AIF1DAC1R_VOL - [7:0] */
+
+/*
+ * R1028 (0x404) - AIF1 ADC2 Left Volume
+ */
+#define WM8995_AIF1ADC2_VU 0x0100 /* AIF1ADC2_VU */
+#define WM8995_AIF1ADC2_VU_MASK 0x0100 /* AIF1ADC2_VU */
+#define WM8995_AIF1ADC2_VU_SHIFT 8 /* AIF1ADC2_VU */
+#define WM8995_AIF1ADC2_VU_WIDTH 1 /* AIF1ADC2_VU */
+#define WM8995_AIF1ADC2L_VOL_MASK 0x00FF /* AIF1ADC2L_VOL - [7:0] */
+#define WM8995_AIF1ADC2L_VOL_SHIFT 0 /* AIF1ADC2L_VOL - [7:0] */
+#define WM8995_AIF1ADC2L_VOL_WIDTH 8 /* AIF1ADC2L_VOL - [7:0] */
+
+/*
+ * R1029 (0x405) - AIF1 ADC2 Right Volume
+ */
+#define WM8995_AIF1ADC2_VU 0x0100 /* AIF1ADC2_VU */
+#define WM8995_AIF1ADC2_VU_MASK 0x0100 /* AIF1ADC2_VU */
+#define WM8995_AIF1ADC2_VU_SHIFT 8 /* AIF1ADC2_VU */
+#define WM8995_AIF1ADC2_VU_WIDTH 1 /* AIF1ADC2_VU */
+#define WM8995_AIF1ADC2R_VOL_MASK 0x00FF /* AIF1ADC2R_VOL - [7:0] */
+#define WM8995_AIF1ADC2R_VOL_SHIFT 0 /* AIF1ADC2R_VOL - [7:0] */
+#define WM8995_AIF1ADC2R_VOL_WIDTH 8 /* AIF1ADC2R_VOL - [7:0] */
+
+/*
+ * R1030 (0x406) - AIF1 DAC2 Left Volume
+ */
+#define WM8995_AIF1DAC2_VU 0x0100 /* AIF1DAC2_VU */
+#define WM8995_AIF1DAC2_VU_MASK 0x0100 /* AIF1DAC2_VU */
+#define WM8995_AIF1DAC2_VU_SHIFT 8 /* AIF1DAC2_VU */
+#define WM8995_AIF1DAC2_VU_WIDTH 1 /* AIF1DAC2_VU */
+#define WM8995_AIF1DAC2L_VOL_MASK 0x00FF /* AIF1DAC2L_VOL - [7:0] */
+#define WM8995_AIF1DAC2L_VOL_SHIFT 0 /* AIF1DAC2L_VOL - [7:0] */
+#define WM8995_AIF1DAC2L_VOL_WIDTH 8 /* AIF1DAC2L_VOL - [7:0] */
+
+/*
+ * R1031 (0x407) - AIF1 DAC2 Right Volume
+ */
+#define WM8995_AIF1DAC2_VU 0x0100 /* AIF1DAC2_VU */
+#define WM8995_AIF1DAC2_VU_MASK 0x0100 /* AIF1DAC2_VU */
+#define WM8995_AIF1DAC2_VU_SHIFT 8 /* AIF1DAC2_VU */
+#define WM8995_AIF1DAC2_VU_WIDTH 1 /* AIF1DAC2_VU */
+#define WM8995_AIF1DAC2R_VOL_MASK 0x00FF /* AIF1DAC2R_VOL - [7:0] */
+#define WM8995_AIF1DAC2R_VOL_SHIFT 0 /* AIF1DAC2R_VOL - [7:0] */
+#define WM8995_AIF1DAC2R_VOL_WIDTH 8 /* AIF1DAC2R_VOL - [7:0] */
+
+/*
+ * R1040 (0x410) - AIF1 ADC1 Filters
+ */
+#define WM8995_AIF1ADC_4FS 0x8000 /* AIF1ADC_4FS */
+#define WM8995_AIF1ADC_4FS_MASK 0x8000 /* AIF1ADC_4FS */
+#define WM8995_AIF1ADC_4FS_SHIFT 15 /* AIF1ADC_4FS */
+#define WM8995_AIF1ADC_4FS_WIDTH 1 /* AIF1ADC_4FS */
+#define WM8995_AIF1ADC1L_HPF 0x1000 /* AIF1ADC1L_HPF */
+#define WM8995_AIF1ADC1L_HPF_MASK 0x1000 /* AIF1ADC1L_HPF */
+#define WM8995_AIF1ADC1L_HPF_SHIFT 12 /* AIF1ADC1L_HPF */
+#define WM8995_AIF1ADC1L_HPF_WIDTH 1 /* AIF1ADC1L_HPF */
+#define WM8995_AIF1ADC1R_HPF 0x0800 /* AIF1ADC1R_HPF */
+#define WM8995_AIF1ADC1R_HPF_MASK 0x0800 /* AIF1ADC1R_HPF */
+#define WM8995_AIF1ADC1R_HPF_SHIFT 11 /* AIF1ADC1R_HPF */
+#define WM8995_AIF1ADC1R_HPF_WIDTH 1 /* AIF1ADC1R_HPF */
+#define WM8995_AIF1ADC1_HPF_MODE 0x0008 /* AIF1ADC1_HPF_MODE */
+#define WM8995_AIF1ADC1_HPF_MODE_MASK 0x0008 /* AIF1ADC1_HPF_MODE */
+#define WM8995_AIF1ADC1_HPF_MODE_SHIFT 3 /* AIF1ADC1_HPF_MODE */
+#define WM8995_AIF1ADC1_HPF_MODE_WIDTH 1 /* AIF1ADC1_HPF_MODE */
+#define WM8995_AIF1ADC1_HPF_CUT_MASK 0x0007 /* AIF1ADC1_HPF_CUT - [2:0] */
+#define WM8995_AIF1ADC1_HPF_CUT_SHIFT 0 /* AIF1ADC1_HPF_CUT - [2:0] */
+#define WM8995_AIF1ADC1_HPF_CUT_WIDTH 3 /* AIF1ADC1_HPF_CUT - [2:0] */
+
+/*
+ * R1041 (0x411) - AIF1 ADC2 Filters
+ */
+#define WM8995_AIF1ADC2L_HPF 0x1000 /* AIF1ADC2L_HPF */
+#define WM8995_AIF1ADC2L_HPF_MASK 0x1000 /* AIF1ADC2L_HPF */
+#define WM8995_AIF1ADC2L_HPF_SHIFT 12 /* AIF1ADC2L_HPF */
+#define WM8995_AIF1ADC2L_HPF_WIDTH 1 /* AIF1ADC2L_HPF */
+#define WM8995_AIF1ADC2R_HPF 0x0800 /* AIF1ADC2R_HPF */
+#define WM8995_AIF1ADC2R_HPF_MASK 0x0800 /* AIF1ADC2R_HPF */
+#define WM8995_AIF1ADC2R_HPF_SHIFT 11 /* AIF1ADC2R_HPF */
+#define WM8995_AIF1ADC2R_HPF_WIDTH 1 /* AIF1ADC2R_HPF */
+#define WM8995_AIF1ADC2_HPF_MODE 0x0008 /* AIF1ADC2_HPF_MODE */
+#define WM8995_AIF1ADC2_HPF_MODE_MASK 0x0008 /* AIF1ADC2_HPF_MODE */
+#define WM8995_AIF1ADC2_HPF_MODE_SHIFT 3 /* AIF1ADC2_HPF_MODE */
+#define WM8995_AIF1ADC2_HPF_MODE_WIDTH 1 /* AIF1ADC2_HPF_MODE */
+#define WM8995_AIF1ADC2_HPF_CUT_MASK 0x0007 /* AIF1ADC2_HPF_CUT - [2:0] */
+#define WM8995_AIF1ADC2_HPF_CUT_SHIFT 0 /* AIF1ADC2_HPF_CUT - [2:0] */
+#define WM8995_AIF1ADC2_HPF_CUT_WIDTH 3 /* AIF1ADC2_HPF_CUT - [2:0] */
+
+/*
+ * R1056 (0x420) - AIF1 DAC1 Filters (1)
+ */
+#define WM8995_AIF1DAC1_MUTE 0x0200 /* AIF1DAC1_MUTE */
+#define WM8995_AIF1DAC1_MUTE_MASK 0x0200 /* AIF1DAC1_MUTE */
+#define WM8995_AIF1DAC1_MUTE_SHIFT 9 /* AIF1DAC1_MUTE */
+#define WM8995_AIF1DAC1_MUTE_WIDTH 1 /* AIF1DAC1_MUTE */
+#define WM8995_AIF1DAC1_MONO 0x0080 /* AIF1DAC1_MONO */
+#define WM8995_AIF1DAC1_MONO_MASK 0x0080 /* AIF1DAC1_MONO */
+#define WM8995_AIF1DAC1_MONO_SHIFT 7 /* AIF1DAC1_MONO */
+#define WM8995_AIF1DAC1_MONO_WIDTH 1 /* AIF1DAC1_MONO */
+#define WM8995_AIF1DAC1_MUTERATE 0x0020 /* AIF1DAC1_MUTERATE */
+#define WM8995_AIF1DAC1_MUTERATE_MASK 0x0020 /* AIF1DAC1_MUTERATE */
+#define WM8995_AIF1DAC1_MUTERATE_SHIFT 5 /* AIF1DAC1_MUTERATE */
+#define WM8995_AIF1DAC1_MUTERATE_WIDTH 1 /* AIF1DAC1_MUTERATE */
+#define WM8995_AIF1DAC1_UNMUTE_RAMP 0x0010 /* AIF1DAC1_UNMUTE_RAMP */
+#define WM8995_AIF1DAC1_UNMUTE_RAMP_MASK 0x0010 /* AIF1DAC1_UNMUTE_RAMP */
+#define WM8995_AIF1DAC1_UNMUTE_RAMP_SHIFT 4 /* AIF1DAC1_UNMUTE_RAMP */
+#define WM8995_AIF1DAC1_UNMUTE_RAMP_WIDTH 1 /* AIF1DAC1_UNMUTE_RAMP */
+#define WM8995_AIF1DAC1_DEEMP_MASK 0x0006 /* AIF1DAC1_DEEMP - [2:1] */
+#define WM8995_AIF1DAC1_DEEMP_SHIFT 1 /* AIF1DAC1_DEEMP - [2:1] */
+#define WM8995_AIF1DAC1_DEEMP_WIDTH 2 /* AIF1DAC1_DEEMP - [2:1] */
+
+/*
+ * R1057 (0x421) - AIF1 DAC1 Filters (2)
+ */
+#define WM8995_AIF1DAC1_3D_GAIN_MASK 0x3E00 /* AIF1DAC1_3D_GAIN - [13:9] */
+#define WM8995_AIF1DAC1_3D_GAIN_SHIFT 9 /* AIF1DAC1_3D_GAIN - [13:9] */
+#define WM8995_AIF1DAC1_3D_GAIN_WIDTH 5 /* AIF1DAC1_3D_GAIN - [13:9] */
+#define WM8995_AIF1DAC1_3D_ENA 0x0100 /* AIF1DAC1_3D_ENA */
+#define WM8995_AIF1DAC1_3D_ENA_MASK 0x0100 /* AIF1DAC1_3D_ENA */
+#define WM8995_AIF1DAC1_3D_ENA_SHIFT 8 /* AIF1DAC1_3D_ENA */
+#define WM8995_AIF1DAC1_3D_ENA_WIDTH 1 /* AIF1DAC1_3D_ENA */
+
+/*
+ * R1058 (0x422) - AIF1 DAC2 Filters (1)
+ */
+#define WM8995_AIF1DAC2_MUTE 0x0200 /* AIF1DAC2_MUTE */
+#define WM8995_AIF1DAC2_MUTE_MASK 0x0200 /* AIF1DAC2_MUTE */
+#define WM8995_AIF1DAC2_MUTE_SHIFT 9 /* AIF1DAC2_MUTE */
+#define WM8995_AIF1DAC2_MUTE_WIDTH 1 /* AIF1DAC2_MUTE */
+#define WM8995_AIF1DAC2_MONO 0x0080 /* AIF1DAC2_MONO */
+#define WM8995_AIF1DAC2_MONO_MASK 0x0080 /* AIF1DAC2_MONO */
+#define WM8995_AIF1DAC2_MONO_SHIFT 7 /* AIF1DAC2_MONO */
+#define WM8995_AIF1DAC2_MONO_WIDTH 1 /* AIF1DAC2_MONO */
+#define WM8995_AIF1DAC2_MUTERATE 0x0020 /* AIF1DAC2_MUTERATE */
+#define WM8995_AIF1DAC2_MUTERATE_MASK 0x0020 /* AIF1DAC2_MUTERATE */
+#define WM8995_AIF1DAC2_MUTERATE_SHIFT 5 /* AIF1DAC2_MUTERATE */
+#define WM8995_AIF1DAC2_MUTERATE_WIDTH 1 /* AIF1DAC2_MUTERATE */
+#define WM8995_AIF1DAC2_UNMUTE_RAMP 0x0010 /* AIF1DAC2_UNMUTE_RAMP */
+#define WM8995_AIF1DAC2_UNMUTE_RAMP_MASK 0x0010 /* AIF1DAC2_UNMUTE_RAMP */
+#define WM8995_AIF1DAC2_UNMUTE_RAMP_SHIFT 4 /* AIF1DAC2_UNMUTE_RAMP */
+#define WM8995_AIF1DAC2_UNMUTE_RAMP_WIDTH 1 /* AIF1DAC2_UNMUTE_RAMP */
+#define WM8995_AIF1DAC2_DEEMP_MASK 0x0006 /* AIF1DAC2_DEEMP - [2:1] */
+#define WM8995_AIF1DAC2_DEEMP_SHIFT 1 /* AIF1DAC2_DEEMP - [2:1] */
+#define WM8995_AIF1DAC2_DEEMP_WIDTH 2 /* AIF1DAC2_DEEMP - [2:1] */
+
+/*
+ * R1059 (0x423) - AIF1 DAC2 Filters (2)
+ */
+#define WM8995_AIF1DAC2_3D_GAIN_MASK 0x3E00 /* AIF1DAC2_3D_GAIN - [13:9] */
+#define WM8995_AIF1DAC2_3D_GAIN_SHIFT 9 /* AIF1DAC2_3D_GAIN - [13:9] */
+#define WM8995_AIF1DAC2_3D_GAIN_WIDTH 5 /* AIF1DAC2_3D_GAIN - [13:9] */
+#define WM8995_AIF1DAC2_3D_ENA 0x0100 /* AIF1DAC2_3D_ENA */
+#define WM8995_AIF1DAC2_3D_ENA_MASK 0x0100 /* AIF1DAC2_3D_ENA */
+#define WM8995_AIF1DAC2_3D_ENA_SHIFT 8 /* AIF1DAC2_3D_ENA */
+#define WM8995_AIF1DAC2_3D_ENA_WIDTH 1 /* AIF1DAC2_3D_ENA */
+
+/*
+ * R1088 (0x440) - AIF1 DRC1 (1)
+ */
+#define WM8995_AIF1DRC1_SIG_DET_RMS_MASK 0xF800 /* AIF1DRC1_SIG_DET_RMS - [15:11] */
+#define WM8995_AIF1DRC1_SIG_DET_RMS_SHIFT 11 /* AIF1DRC1_SIG_DET_RMS - [15:11] */
+#define WM8995_AIF1DRC1_SIG_DET_RMS_WIDTH 5 /* AIF1DRC1_SIG_DET_RMS - [15:11] */
+#define WM8995_AIF1DRC1_SIG_DET_PK_MASK 0x0600 /* AIF1DRC1_SIG_DET_PK - [10:9] */
+#define WM8995_AIF1DRC1_SIG_DET_PK_SHIFT 9 /* AIF1DRC1_SIG_DET_PK - [10:9] */
+#define WM8995_AIF1DRC1_SIG_DET_PK_WIDTH 2 /* AIF1DRC1_SIG_DET_PK - [10:9] */
+#define WM8995_AIF1DRC1_NG_ENA 0x0100 /* AIF1DRC1_NG_ENA */
+#define WM8995_AIF1DRC1_NG_ENA_MASK 0x0100 /* AIF1DRC1_NG_ENA */
+#define WM8995_AIF1DRC1_NG_ENA_SHIFT 8 /* AIF1DRC1_NG_ENA */
+#define WM8995_AIF1DRC1_NG_ENA_WIDTH 1 /* AIF1DRC1_NG_ENA */
+#define WM8995_AIF1DRC1_SIG_DET_MODE 0x0080 /* AIF1DRC1_SIG_DET_MODE */
+#define WM8995_AIF1DRC1_SIG_DET_MODE_MASK 0x0080 /* AIF1DRC1_SIG_DET_MODE */
+#define WM8995_AIF1DRC1_SIG_DET_MODE_SHIFT 7 /* AIF1DRC1_SIG_DET_MODE */
+#define WM8995_AIF1DRC1_SIG_DET_MODE_WIDTH 1 /* AIF1DRC1_SIG_DET_MODE */
+#define WM8995_AIF1DRC1_SIG_DET 0x0040 /* AIF1DRC1_SIG_DET */
+#define WM8995_AIF1DRC1_SIG_DET_MASK 0x0040 /* AIF1DRC1_SIG_DET */
+#define WM8995_AIF1DRC1_SIG_DET_SHIFT 6 /* AIF1DRC1_SIG_DET */
+#define WM8995_AIF1DRC1_SIG_DET_WIDTH 1 /* AIF1DRC1_SIG_DET */
+#define WM8995_AIF1DRC1_KNEE2_OP_ENA 0x0020 /* AIF1DRC1_KNEE2_OP_ENA */
+#define WM8995_AIF1DRC1_KNEE2_OP_ENA_MASK 0x0020 /* AIF1DRC1_KNEE2_OP_ENA */
+#define WM8995_AIF1DRC1_KNEE2_OP_ENA_SHIFT 5 /* AIF1DRC1_KNEE2_OP_ENA */
+#define WM8995_AIF1DRC1_KNEE2_OP_ENA_WIDTH 1 /* AIF1DRC1_KNEE2_OP_ENA */
+#define WM8995_AIF1DRC1_QR 0x0010 /* AIF1DRC1_QR */
+#define WM8995_AIF1DRC1_QR_MASK 0x0010 /* AIF1DRC1_QR */
+#define WM8995_AIF1DRC1_QR_SHIFT 4 /* AIF1DRC1_QR */
+#define WM8995_AIF1DRC1_QR_WIDTH 1 /* AIF1DRC1_QR */
+#define WM8995_AIF1DRC1_ANTICLIP 0x0008 /* AIF1DRC1_ANTICLIP */
+#define WM8995_AIF1DRC1_ANTICLIP_MASK 0x0008 /* AIF1DRC1_ANTICLIP */
+#define WM8995_AIF1DRC1_ANTICLIP_SHIFT 3 /* AIF1DRC1_ANTICLIP */
+#define WM8995_AIF1DRC1_ANTICLIP_WIDTH 1 /* AIF1DRC1_ANTICLIP */
+#define WM8995_AIF1DAC1_DRC_ENA 0x0004 /* AIF1DAC1_DRC_ENA */
+#define WM8995_AIF1DAC1_DRC_ENA_MASK 0x0004 /* AIF1DAC1_DRC_ENA */
+#define WM8995_AIF1DAC1_DRC_ENA_SHIFT 2 /* AIF1DAC1_DRC_ENA */
+#define WM8995_AIF1DAC1_DRC_ENA_WIDTH 1 /* AIF1DAC1_DRC_ENA */
+#define WM8995_AIF1ADC1L_DRC_ENA 0x0002 /* AIF1ADC1L_DRC_ENA */
+#define WM8995_AIF1ADC1L_DRC_ENA_MASK 0x0002 /* AIF1ADC1L_DRC_ENA */
+#define WM8995_AIF1ADC1L_DRC_ENA_SHIFT 1 /* AIF1ADC1L_DRC_ENA */
+#define WM8995_AIF1ADC1L_DRC_ENA_WIDTH 1 /* AIF1ADC1L_DRC_ENA */
+#define WM8995_AIF1ADC1R_DRC_ENA 0x0001 /* AIF1ADC1R_DRC_ENA */
+#define WM8995_AIF1ADC1R_DRC_ENA_MASK 0x0001 /* AIF1ADC1R_DRC_ENA */
+#define WM8995_AIF1ADC1R_DRC_ENA_SHIFT 0 /* AIF1ADC1R_DRC_ENA */
+#define WM8995_AIF1ADC1R_DRC_ENA_WIDTH 1 /* AIF1ADC1R_DRC_ENA */
+
+/*
+ * R1089 (0x441) - AIF1 DRC1 (2)
+ */
+#define WM8995_AIF1DRC1_ATK_MASK 0x1E00 /* AIF1DRC1_ATK - [12:9] */
+#define WM8995_AIF1DRC1_ATK_SHIFT 9 /* AIF1DRC1_ATK - [12:9] */
+#define WM8995_AIF1DRC1_ATK_WIDTH 4 /* AIF1DRC1_ATK - [12:9] */
+#define WM8995_AIF1DRC1_DCY_MASK 0x01E0 /* AIF1DRC1_DCY - [8:5] */
+#define WM8995_AIF1DRC1_DCY_SHIFT 5 /* AIF1DRC1_DCY - [8:5] */
+#define WM8995_AIF1DRC1_DCY_WIDTH 4 /* AIF1DRC1_DCY - [8:5] */
+#define WM8995_AIF1DRC1_MINGAIN_MASK 0x001C /* AIF1DRC1_MINGAIN - [4:2] */
+#define WM8995_AIF1DRC1_MINGAIN_SHIFT 2 /* AIF1DRC1_MINGAIN - [4:2] */
+#define WM8995_AIF1DRC1_MINGAIN_WIDTH 3 /* AIF1DRC1_MINGAIN - [4:2] */
+#define WM8995_AIF1DRC1_MAXGAIN_MASK 0x0003 /* AIF1DRC1_MAXGAIN - [1:0] */
+#define WM8995_AIF1DRC1_MAXGAIN_SHIFT 0 /* AIF1DRC1_MAXGAIN - [1:0] */
+#define WM8995_AIF1DRC1_MAXGAIN_WIDTH 2 /* AIF1DRC1_MAXGAIN - [1:0] */
+
+/*
+ * R1090 (0x442) - AIF1 DRC1 (3)
+ */
+#define WM8995_AIF1DRC1_NG_MINGAIN_MASK 0xF000 /* AIF1DRC1_NG_MINGAIN - [15:12] */
+#define WM8995_AIF1DRC1_NG_MINGAIN_SHIFT 12 /* AIF1DRC1_NG_MINGAIN - [15:12] */
+#define WM8995_AIF1DRC1_NG_MINGAIN_WIDTH 4 /* AIF1DRC1_NG_MINGAIN - [15:12] */
+#define WM8995_AIF1DRC1_NG_EXP_MASK 0x0C00 /* AIF1DRC1_NG_EXP - [11:10] */
+#define WM8995_AIF1DRC1_NG_EXP_SHIFT 10 /* AIF1DRC1_NG_EXP - [11:10] */
+#define WM8995_AIF1DRC1_NG_EXP_WIDTH 2 /* AIF1DRC1_NG_EXP - [11:10] */
+#define WM8995_AIF1DRC1_QR_THR_MASK 0x0300 /* AIF1DRC1_QR_THR - [9:8] */
+#define WM8995_AIF1DRC1_QR_THR_SHIFT 8 /* AIF1DRC1_QR_THR - [9:8] */
+#define WM8995_AIF1DRC1_QR_THR_WIDTH 2 /* AIF1DRC1_QR_THR - [9:8] */
+#define WM8995_AIF1DRC1_QR_DCY_MASK 0x00C0 /* AIF1DRC1_QR_DCY - [7:6] */
+#define WM8995_AIF1DRC1_QR_DCY_SHIFT 6 /* AIF1DRC1_QR_DCY - [7:6] */
+#define WM8995_AIF1DRC1_QR_DCY_WIDTH 2 /* AIF1DRC1_QR_DCY - [7:6] */
+#define WM8995_AIF1DRC1_HI_COMP_MASK 0x0038 /* AIF1DRC1_HI_COMP - [5:3] */
+#define WM8995_AIF1DRC1_HI_COMP_SHIFT 3 /* AIF1DRC1_HI_COMP - [5:3] */
+#define WM8995_AIF1DRC1_HI_COMP_WIDTH 3 /* AIF1DRC1_HI_COMP - [5:3] */
+#define WM8995_AIF1DRC1_LO_COMP_MASK 0x0007 /* AIF1DRC1_LO_COMP - [2:0] */
+#define WM8995_AIF1DRC1_LO_COMP_SHIFT 0 /* AIF1DRC1_LO_COMP - [2:0] */
+#define WM8995_AIF1DRC1_LO_COMP_WIDTH 3 /* AIF1DRC1_LO_COMP - [2:0] */
+
+/*
+ * R1091 (0x443) - AIF1 DRC1 (4)
+ */
+#define WM8995_AIF1DRC1_KNEE_IP_MASK 0x07E0 /* AIF1DRC1_KNEE_IP - [10:5] */
+#define WM8995_AIF1DRC1_KNEE_IP_SHIFT 5 /* AIF1DRC1_KNEE_IP - [10:5] */
+#define WM8995_AIF1DRC1_KNEE_IP_WIDTH 6 /* AIF1DRC1_KNEE_IP - [10:5] */
+#define WM8995_AIF1DRC1_KNEE_OP_MASK 0x001F /* AIF1DRC1_KNEE_OP - [4:0] */
+#define WM8995_AIF1DRC1_KNEE_OP_SHIFT 0 /* AIF1DRC1_KNEE_OP - [4:0] */
+#define WM8995_AIF1DRC1_KNEE_OP_WIDTH 5 /* AIF1DRC1_KNEE_OP - [4:0] */
+
+/*
+ * R1092 (0x444) - AIF1 DRC1 (5)
+ */
+#define WM8995_AIF1DRC1_KNEE2_IP_MASK 0x03E0 /* AIF1DRC1_KNEE2_IP - [9:5] */
+#define WM8995_AIF1DRC1_KNEE2_IP_SHIFT 5 /* AIF1DRC1_KNEE2_IP - [9:5] */
+#define WM8995_AIF1DRC1_KNEE2_IP_WIDTH 5 /* AIF1DRC1_KNEE2_IP - [9:5] */
+#define WM8995_AIF1DRC1_KNEE2_OP_MASK 0x001F /* AIF1DRC1_KNEE2_OP - [4:0] */
+#define WM8995_AIF1DRC1_KNEE2_OP_SHIFT 0 /* AIF1DRC1_KNEE2_OP - [4:0] */
+#define WM8995_AIF1DRC1_KNEE2_OP_WIDTH 5 /* AIF1DRC1_KNEE2_OP - [4:0] */
+
+/*
+ * R1104 (0x450) - AIF1 DRC2 (1)
+ */
+#define WM8995_AIF1DRC2_SIG_DET_RMS_MASK 0xF800 /* AIF1DRC2_SIG_DET_RMS - [15:11] */
+#define WM8995_AIF1DRC2_SIG_DET_RMS_SHIFT 11 /* AIF1DRC2_SIG_DET_RMS - [15:11] */
+#define WM8995_AIF1DRC2_SIG_DET_RMS_WIDTH 5 /* AIF1DRC2_SIG_DET_RMS - [15:11] */
+#define WM8995_AIF1DRC2_SIG_DET_PK_MASK 0x0600 /* AIF1DRC2_SIG_DET_PK - [10:9] */
+#define WM8995_AIF1DRC2_SIG_DET_PK_SHIFT 9 /* AIF1DRC2_SIG_DET_PK - [10:9] */
+#define WM8995_AIF1DRC2_SIG_DET_PK_WIDTH 2 /* AIF1DRC2_SIG_DET_PK - [10:9] */
+#define WM8995_AIF1DRC2_NG_ENA 0x0100 /* AIF1DRC2_NG_ENA */
+#define WM8995_AIF1DRC2_NG_ENA_MASK 0x0100 /* AIF1DRC2_NG_ENA */
+#define WM8995_AIF1DRC2_NG_ENA_SHIFT 8 /* AIF1DRC2_NG_ENA */
+#define WM8995_AIF1DRC2_NG_ENA_WIDTH 1 /* AIF1DRC2_NG_ENA */
+#define WM8995_AIF1DRC2_SIG_DET_MODE 0x0080 /* AIF1DRC2_SIG_DET_MODE */
+#define WM8995_AIF1DRC2_SIG_DET_MODE_MASK 0x0080 /* AIF1DRC2_SIG_DET_MODE */
+#define WM8995_AIF1DRC2_SIG_DET_MODE_SHIFT 7 /* AIF1DRC2_SIG_DET_MODE */
+#define WM8995_AIF1DRC2_SIG_DET_MODE_WIDTH 1 /* AIF1DRC2_SIG_DET_MODE */
+#define WM8995_AIF1DRC2_SIG_DET 0x0040 /* AIF1DRC2_SIG_DET */
+#define WM8995_AIF1DRC2_SIG_DET_MASK 0x0040 /* AIF1DRC2_SIG_DET */
+#define WM8995_AIF1DRC2_SIG_DET_SHIFT 6 /* AIF1DRC2_SIG_DET */
+#define WM8995_AIF1DRC2_SIG_DET_WIDTH 1 /* AIF1DRC2_SIG_DET */
+#define WM8995_AIF1DRC2_KNEE2_OP_ENA 0x0020 /* AIF1DRC2_KNEE2_OP_ENA */
+#define WM8995_AIF1DRC2_KNEE2_OP_ENA_MASK 0x0020 /* AIF1DRC2_KNEE2_OP_ENA */
+#define WM8995_AIF1DRC2_KNEE2_OP_ENA_SHIFT 5 /* AIF1DRC2_KNEE2_OP_ENA */
+#define WM8995_AIF1DRC2_KNEE2_OP_ENA_WIDTH 1 /* AIF1DRC2_KNEE2_OP_ENA */
+#define WM8995_AIF1DRC2_QR 0x0010 /* AIF1DRC2_QR */
+#define WM8995_AIF1DRC2_QR_MASK 0x0010 /* AIF1DRC2_QR */
+#define WM8995_AIF1DRC2_QR_SHIFT 4 /* AIF1DRC2_QR */
+#define WM8995_AIF1DRC2_QR_WIDTH 1 /* AIF1DRC2_QR */
+#define WM8995_AIF1DRC2_ANTICLIP 0x0008 /* AIF1DRC2_ANTICLIP */
+#define WM8995_AIF1DRC2_ANTICLIP_MASK 0x0008 /* AIF1DRC2_ANTICLIP */
+#define WM8995_AIF1DRC2_ANTICLIP_SHIFT 3 /* AIF1DRC2_ANTICLIP */
+#define WM8995_AIF1DRC2_ANTICLIP_WIDTH 1 /* AIF1DRC2_ANTICLIP */
+#define WM8995_AIF1DAC2_DRC_ENA 0x0004 /* AIF1DAC2_DRC_ENA */
+#define WM8995_AIF1DAC2_DRC_ENA_MASK 0x0004 /* AIF1DAC2_DRC_ENA */
+#define WM8995_AIF1DAC2_DRC_ENA_SHIFT 2 /* AIF1DAC2_DRC_ENA */
+#define WM8995_AIF1DAC2_DRC_ENA_WIDTH 1 /* AIF1DAC2_DRC_ENA */
+#define WM8995_AIF1ADC2L_DRC_ENA 0x0002 /* AIF1ADC2L_DRC_ENA */
+#define WM8995_AIF1ADC2L_DRC_ENA_MASK 0x0002 /* AIF1ADC2L_DRC_ENA */
+#define WM8995_AIF1ADC2L_DRC_ENA_SHIFT 1 /* AIF1ADC2L_DRC_ENA */
+#define WM8995_AIF1ADC2L_DRC_ENA_WIDTH 1 /* AIF1ADC2L_DRC_ENA */
+#define WM8995_AIF1ADC2R_DRC_ENA 0x0001 /* AIF1ADC2R_DRC_ENA */
+#define WM8995_AIF1ADC2R_DRC_ENA_MASK 0x0001 /* AIF1ADC2R_DRC_ENA */
+#define WM8995_AIF1ADC2R_DRC_ENA_SHIFT 0 /* AIF1ADC2R_DRC_ENA */
+#define WM8995_AIF1ADC2R_DRC_ENA_WIDTH 1 /* AIF1ADC2R_DRC_ENA */
+
+/*
+ * R1105 (0x451) - AIF1 DRC2 (2)
+ */
+#define WM8995_AIF1DRC2_ATK_MASK 0x1E00 /* AIF1DRC2_ATK - [12:9] */
+#define WM8995_AIF1DRC2_ATK_SHIFT 9 /* AIF1DRC2_ATK - [12:9] */
+#define WM8995_AIF1DRC2_ATK_WIDTH 4 /* AIF1DRC2_ATK - [12:9] */
+#define WM8995_AIF1DRC2_DCY_MASK 0x01E0 /* AIF1DRC2_DCY - [8:5] */
+#define WM8995_AIF1DRC2_DCY_SHIFT 5 /* AIF1DRC2_DCY - [8:5] */
+#define WM8995_AIF1DRC2_DCY_WIDTH 4 /* AIF1DRC2_DCY - [8:5] */
+#define WM8995_AIF1DRC2_MINGAIN_MASK 0x001C /* AIF1DRC2_MINGAIN - [4:2] */
+#define WM8995_AIF1DRC2_MINGAIN_SHIFT 2 /* AIF1DRC2_MINGAIN - [4:2] */
+#define WM8995_AIF1DRC2_MINGAIN_WIDTH 3 /* AIF1DRC2_MINGAIN - [4:2] */
+#define WM8995_AIF1DRC2_MAXGAIN_MASK 0x0003 /* AIF1DRC2_MAXGAIN - [1:0] */
+#define WM8995_AIF1DRC2_MAXGAIN_SHIFT 0 /* AIF1DRC2_MAXGAIN - [1:0] */
+#define WM8995_AIF1DRC2_MAXGAIN_WIDTH 2 /* AIF1DRC2_MAXGAIN - [1:0] */
+
+/*
+ * R1106 (0x452) - AIF1 DRC2 (3)
+ */
+#define WM8995_AIF1DRC2_NG_MINGAIN_MASK 0xF000 /* AIF1DRC2_NG_MINGAIN - [15:12] */
+#define WM8995_AIF1DRC2_NG_MINGAIN_SHIFT 12 /* AIF1DRC2_NG_MINGAIN - [15:12] */
+#define WM8995_AIF1DRC2_NG_MINGAIN_WIDTH 4 /* AIF1DRC2_NG_MINGAIN - [15:12] */
+#define WM8995_AIF1DRC2_NG_EXP_MASK 0x0C00 /* AIF1DRC2_NG_EXP - [11:10] */
+#define WM8995_AIF1DRC2_NG_EXP_SHIFT 10 /* AIF1DRC2_NG_EXP - [11:10] */
+#define WM8995_AIF1DRC2_NG_EXP_WIDTH 2 /* AIF1DRC2_NG_EXP - [11:10] */
+#define WM8995_AIF1DRC2_QR_THR_MASK 0x0300 /* AIF1DRC2_QR_THR - [9:8] */
+#define WM8995_AIF1DRC2_QR_THR_SHIFT 8 /* AIF1DRC2_QR_THR - [9:8] */
+#define WM8995_AIF1DRC2_QR_THR_WIDTH 2 /* AIF1DRC2_QR_THR - [9:8] */
+#define WM8995_AIF1DRC2_QR_DCY_MASK 0x00C0 /* AIF1DRC2_QR_DCY - [7:6] */
+#define WM8995_AIF1DRC2_QR_DCY_SHIFT 6 /* AIF1DRC2_QR_DCY - [7:6] */
+#define WM8995_AIF1DRC2_QR_DCY_WIDTH 2 /* AIF1DRC2_QR_DCY - [7:6] */
+#define WM8995_AIF1DRC2_HI_COMP_MASK 0x0038 /* AIF1DRC2_HI_COMP - [5:3] */
+#define WM8995_AIF1DRC2_HI_COMP_SHIFT 3 /* AIF1DRC2_HI_COMP - [5:3] */
+#define WM8995_AIF1DRC2_HI_COMP_WIDTH 3 /* AIF1DRC2_HI_COMP - [5:3] */
+#define WM8995_AIF1DRC2_LO_COMP_MASK 0x0007 /* AIF1DRC2_LO_COMP - [2:0] */
+#define WM8995_AIF1DRC2_LO_COMP_SHIFT 0 /* AIF1DRC2_LO_COMP - [2:0] */
+#define WM8995_AIF1DRC2_LO_COMP_WIDTH 3 /* AIF1DRC2_LO_COMP - [2:0] */
+
+/*
+ * R1107 (0x453) - AIF1 DRC2 (4)
+ */
+#define WM8995_AIF1DRC2_KNEE_IP_MASK 0x07E0 /* AIF1DRC2_KNEE_IP - [10:5] */
+#define WM8995_AIF1DRC2_KNEE_IP_SHIFT 5 /* AIF1DRC2_KNEE_IP - [10:5] */
+#define WM8995_AIF1DRC2_KNEE_IP_WIDTH 6 /* AIF1DRC2_KNEE_IP - [10:5] */
+#define WM8995_AIF1DRC2_KNEE_OP_MASK 0x001F /* AIF1DRC2_KNEE_OP - [4:0] */
+#define WM8995_AIF1DRC2_KNEE_OP_SHIFT 0 /* AIF1DRC2_KNEE_OP - [4:0] */
+#define WM8995_AIF1DRC2_KNEE_OP_WIDTH 5 /* AIF1DRC2_KNEE_OP - [4:0] */
+
+/*
+ * R1108 (0x454) - AIF1 DRC2 (5)
+ */
+#define WM8995_AIF1DRC2_KNEE2_IP_MASK 0x03E0 /* AIF1DRC2_KNEE2_IP - [9:5] */
+#define WM8995_AIF1DRC2_KNEE2_IP_SHIFT 5 /* AIF1DRC2_KNEE2_IP - [9:5] */
+#define WM8995_AIF1DRC2_KNEE2_IP_WIDTH 5 /* AIF1DRC2_KNEE2_IP - [9:5] */
+#define WM8995_AIF1DRC2_KNEE2_OP_MASK 0x001F /* AIF1DRC2_KNEE2_OP - [4:0] */
+#define WM8995_AIF1DRC2_KNEE2_OP_SHIFT 0 /* AIF1DRC2_KNEE2_OP - [4:0] */
+#define WM8995_AIF1DRC2_KNEE2_OP_WIDTH 5 /* AIF1DRC2_KNEE2_OP - [4:0] */
+
+/*
+ * R1152 (0x480) - AIF1 DAC1 EQ Gains (1)
+ */
+#define WM8995_AIF1DAC1_EQ_B1_GAIN_MASK 0xF800 /* AIF1DAC1_EQ_B1_GAIN - [15:11] */
+#define WM8995_AIF1DAC1_EQ_B1_GAIN_SHIFT 11 /* AIF1DAC1_EQ_B1_GAIN - [15:11] */
+#define WM8995_AIF1DAC1_EQ_B1_GAIN_WIDTH 5 /* AIF1DAC1_EQ_B1_GAIN - [15:11] */
+#define WM8995_AIF1DAC1_EQ_B2_GAIN_MASK 0x07C0 /* AIF1DAC1_EQ_B2_GAIN - [10:6] */
+#define WM8995_AIF1DAC1_EQ_B2_GAIN_SHIFT 6 /* AIF1DAC1_EQ_B2_GAIN - [10:6] */
+#define WM8995_AIF1DAC1_EQ_B2_GAIN_WIDTH 5 /* AIF1DAC1_EQ_B2_GAIN - [10:6] */
+#define WM8995_AIF1DAC1_EQ_B3_GAIN_MASK 0x003E /* AIF1DAC1_EQ_B3_GAIN - [5:1] */
+#define WM8995_AIF1DAC1_EQ_B3_GAIN_SHIFT 1 /* AIF1DAC1_EQ_B3_GAIN - [5:1] */
+#define WM8995_AIF1DAC1_EQ_B3_GAIN_WIDTH 5 /* AIF1DAC1_EQ_B3_GAIN - [5:1] */
+#define WM8995_AIF1DAC1_EQ_ENA 0x0001 /* AIF1DAC1_EQ_ENA */
+#define WM8995_AIF1DAC1_EQ_ENA_MASK 0x0001 /* AIF1DAC1_EQ_ENA */
+#define WM8995_AIF1DAC1_EQ_ENA_SHIFT 0 /* AIF1DAC1_EQ_ENA */
+#define WM8995_AIF1DAC1_EQ_ENA_WIDTH 1 /* AIF1DAC1_EQ_ENA */
+
+/*
+ * R1153 (0x481) - AIF1 DAC1 EQ Gains (2)
+ */
+#define WM8995_AIF1DAC1_EQ_B4_GAIN_MASK 0xF800 /* AIF1DAC1_EQ_B4_GAIN - [15:11] */
+#define WM8995_AIF1DAC1_EQ_B4_GAIN_SHIFT 11 /* AIF1DAC1_EQ_B4_GAIN - [15:11] */
+#define WM8995_AIF1DAC1_EQ_B4_GAIN_WIDTH 5 /* AIF1DAC1_EQ_B4_GAIN - [15:11] */
+#define WM8995_AIF1DAC1_EQ_B5_GAIN_MASK 0x07C0 /* AIF1DAC1_EQ_B5_GAIN - [10:6] */
+#define WM8995_AIF1DAC1_EQ_B5_GAIN_SHIFT 6 /* AIF1DAC1_EQ_B5_GAIN - [10:6] */
+#define WM8995_AIF1DAC1_EQ_B5_GAIN_WIDTH 5 /* AIF1DAC1_EQ_B5_GAIN - [10:6] */
+
+/*
+ * R1154 (0x482) - AIF1 DAC1 EQ Band 1 A
+ */
+#define WM8995_AIF1DAC1_EQ_B1_A_MASK 0xFFFF /* AIF1DAC1_EQ_B1_A - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B1_A_SHIFT 0 /* AIF1DAC1_EQ_B1_A - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B1_A_WIDTH 16 /* AIF1DAC1_EQ_B1_A - [15:0] */
+
+/*
+ * R1155 (0x483) - AIF1 DAC1 EQ Band 1 B
+ */
+#define WM8995_AIF1DAC1_EQ_B1_B_MASK 0xFFFF /* AIF1DAC1_EQ_B1_B - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B1_B_SHIFT 0 /* AIF1DAC1_EQ_B1_B - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B1_B_WIDTH 16 /* AIF1DAC1_EQ_B1_B - [15:0] */
+
+/*
+ * R1156 (0x484) - AIF1 DAC1 EQ Band 1 PG
+ */
+#define WM8995_AIF1DAC1_EQ_B1_PG_MASK 0xFFFF /* AIF1DAC1_EQ_B1_PG - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B1_PG_SHIFT 0 /* AIF1DAC1_EQ_B1_PG - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B1_PG_WIDTH 16 /* AIF1DAC1_EQ_B1_PG - [15:0] */
+
+/*
+ * R1157 (0x485) - AIF1 DAC1 EQ Band 2 A
+ */
+#define WM8995_AIF1DAC1_EQ_B2_A_MASK 0xFFFF /* AIF1DAC1_EQ_B2_A - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B2_A_SHIFT 0 /* AIF1DAC1_EQ_B2_A - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B2_A_WIDTH 16 /* AIF1DAC1_EQ_B2_A - [15:0] */
+
+/*
+ * R1158 (0x486) - AIF1 DAC1 EQ Band 2 B
+ */
+#define WM8995_AIF1DAC1_EQ_B2_B_MASK 0xFFFF /* AIF1DAC1_EQ_B2_B - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B2_B_SHIFT 0 /* AIF1DAC1_EQ_B2_B - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B2_B_WIDTH 16 /* AIF1DAC1_EQ_B2_B - [15:0] */
+
+/*
+ * R1159 (0x487) - AIF1 DAC1 EQ Band 2 C
+ */
+#define WM8995_AIF1DAC1_EQ_B2_C_MASK 0xFFFF /* AIF1DAC1_EQ_B2_C - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B2_C_SHIFT 0 /* AIF1DAC1_EQ_B2_C - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B2_C_WIDTH 16 /* AIF1DAC1_EQ_B2_C - [15:0] */
+
+/*
+ * R1160 (0x488) - AIF1 DAC1 EQ Band 2 PG
+ */
+#define WM8995_AIF1DAC1_EQ_B2_PG_MASK 0xFFFF /* AIF1DAC1_EQ_B2_PG - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B2_PG_SHIFT 0 /* AIF1DAC1_EQ_B2_PG - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B2_PG_WIDTH 16 /* AIF1DAC1_EQ_B2_PG - [15:0] */
+
+/*
+ * R1161 (0x489) - AIF1 DAC1 EQ Band 3 A
+ */
+#define WM8995_AIF1DAC1_EQ_B3_A_MASK 0xFFFF /* AIF1DAC1_EQ_B3_A - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B3_A_SHIFT 0 /* AIF1DAC1_EQ_B3_A - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B3_A_WIDTH 16 /* AIF1DAC1_EQ_B3_A - [15:0] */
+
+/*
+ * R1162 (0x48A) - AIF1 DAC1 EQ Band 3 B
+ */
+#define WM8995_AIF1DAC1_EQ_B3_B_MASK 0xFFFF /* AIF1DAC1_EQ_B3_B - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B3_B_SHIFT 0 /* AIF1DAC1_EQ_B3_B - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B3_B_WIDTH 16 /* AIF1DAC1_EQ_B3_B - [15:0] */
+
+/*
+ * R1163 (0x48B) - AIF1 DAC1 EQ Band 3 C
+ */
+#define WM8995_AIF1DAC1_EQ_B3_C_MASK 0xFFFF /* AIF1DAC1_EQ_B3_C - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B3_C_SHIFT 0 /* AIF1DAC1_EQ_B3_C - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B3_C_WIDTH 16 /* AIF1DAC1_EQ_B3_C - [15:0] */
+
+/*
+ * R1164 (0x48C) - AIF1 DAC1 EQ Band 3 PG
+ */
+#define WM8995_AIF1DAC1_EQ_B3_PG_MASK 0xFFFF /* AIF1DAC1_EQ_B3_PG - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B3_PG_SHIFT 0 /* AIF1DAC1_EQ_B3_PG - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B3_PG_WIDTH 16 /* AIF1DAC1_EQ_B3_PG - [15:0] */
+
+/*
+ * R1165 (0x48D) - AIF1 DAC1 EQ Band 4 A
+ */
+#define WM8995_AIF1DAC1_EQ_B4_A_MASK 0xFFFF /* AIF1DAC1_EQ_B4_A - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B4_A_SHIFT 0 /* AIF1DAC1_EQ_B4_A - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B4_A_WIDTH 16 /* AIF1DAC1_EQ_B4_A - [15:0] */
+
+/*
+ * R1166 (0x48E) - AIF1 DAC1 EQ Band 4 B
+ */
+#define WM8995_AIF1DAC1_EQ_B4_B_MASK 0xFFFF /* AIF1DAC1_EQ_B4_B - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B4_B_SHIFT 0 /* AIF1DAC1_EQ_B4_B - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B4_B_WIDTH 16 /* AIF1DAC1_EQ_B4_B - [15:0] */
+
+/*
+ * R1167 (0x48F) - AIF1 DAC1 EQ Band 4 C
+ */
+#define WM8995_AIF1DAC1_EQ_B4_C_MASK 0xFFFF /* AIF1DAC1_EQ_B4_C - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B4_C_SHIFT 0 /* AIF1DAC1_EQ_B4_C - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B4_C_WIDTH 16 /* AIF1DAC1_EQ_B4_C - [15:0] */
+
+/*
+ * R1168 (0x490) - AIF1 DAC1 EQ Band 4 PG
+ */
+#define WM8995_AIF1DAC1_EQ_B4_PG_MASK 0xFFFF /* AIF1DAC1_EQ_B4_PG - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B4_PG_SHIFT 0 /* AIF1DAC1_EQ_B4_PG - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B4_PG_WIDTH 16 /* AIF1DAC1_EQ_B4_PG - [15:0] */
+
+/*
+ * R1169 (0x491) - AIF1 DAC1 EQ Band 5 A
+ */
+#define WM8995_AIF1DAC1_EQ_B5_A_MASK 0xFFFF /* AIF1DAC1_EQ_B5_A - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B5_A_SHIFT 0 /* AIF1DAC1_EQ_B5_A - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B5_A_WIDTH 16 /* AIF1DAC1_EQ_B5_A - [15:0] */
+
+/*
+ * R1170 (0x492) - AIF1 DAC1 EQ Band 5 B
+ */
+#define WM8995_AIF1DAC1_EQ_B5_B_MASK 0xFFFF /* AIF1DAC1_EQ_B5_B - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B5_B_SHIFT 0 /* AIF1DAC1_EQ_B5_B - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B5_B_WIDTH 16 /* AIF1DAC1_EQ_B5_B - [15:0] */
+
+/*
+ * R1171 (0x493) - AIF1 DAC1 EQ Band 5 PG
+ */
+#define WM8995_AIF1DAC1_EQ_B5_PG_MASK 0xFFFF /* AIF1DAC1_EQ_B5_PG - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B5_PG_SHIFT 0 /* AIF1DAC1_EQ_B5_PG - [15:0] */
+#define WM8995_AIF1DAC1_EQ_B5_PG_WIDTH 16 /* AIF1DAC1_EQ_B5_PG - [15:0] */
+
+/*
+ * R1184 (0x4A0) - AIF1 DAC2 EQ Gains (1)
+ */
+#define WM8995_AIF1DAC2_EQ_B1_GAIN_MASK 0xF800 /* AIF1DAC2_EQ_B1_GAIN - [15:11] */
+#define WM8995_AIF1DAC2_EQ_B1_GAIN_SHIFT 11 /* AIF1DAC2_EQ_B1_GAIN - [15:11] */
+#define WM8995_AIF1DAC2_EQ_B1_GAIN_WIDTH 5 /* AIF1DAC2_EQ_B1_GAIN - [15:11] */
+#define WM8995_AIF1DAC2_EQ_B2_GAIN_MASK 0x07C0 /* AIF1DAC2_EQ_B2_GAIN - [10:6] */
+#define WM8995_AIF1DAC2_EQ_B2_GAIN_SHIFT 6 /* AIF1DAC2_EQ_B2_GAIN - [10:6] */
+#define WM8995_AIF1DAC2_EQ_B2_GAIN_WIDTH 5 /* AIF1DAC2_EQ_B2_GAIN - [10:6] */
+#define WM8995_AIF1DAC2_EQ_B3_GAIN_MASK 0x003E /* AIF1DAC2_EQ_B3_GAIN - [5:1] */
+#define WM8995_AIF1DAC2_EQ_B3_GAIN_SHIFT 1 /* AIF1DAC2_EQ_B3_GAIN - [5:1] */
+#define WM8995_AIF1DAC2_EQ_B3_GAIN_WIDTH 5 /* AIF1DAC2_EQ_B3_GAIN - [5:1] */
+#define WM8995_AIF1DAC2_EQ_ENA 0x0001 /* AIF1DAC2_EQ_ENA */
+#define WM8995_AIF1DAC2_EQ_ENA_MASK 0x0001 /* AIF1DAC2_EQ_ENA */
+#define WM8995_AIF1DAC2_EQ_ENA_SHIFT 0 /* AIF1DAC2_EQ_ENA */
+#define WM8995_AIF1DAC2_EQ_ENA_WIDTH 1 /* AIF1DAC2_EQ_ENA */
+
+/*
+ * R1185 (0x4A1) - AIF1 DAC2 EQ Gains (2)
+ */
+#define WM8995_AIF1DAC2_EQ_B4_GAIN_MASK 0xF800 /* AIF1DAC2_EQ_B4_GAIN - [15:11] */
+#define WM8995_AIF1DAC2_EQ_B4_GAIN_SHIFT 11 /* AIF1DAC2_EQ_B4_GAIN - [15:11] */
+#define WM8995_AIF1DAC2_EQ_B4_GAIN_WIDTH 5 /* AIF1DAC2_EQ_B4_GAIN - [15:11] */
+#define WM8995_AIF1DAC2_EQ_B5_GAIN_MASK 0x07C0 /* AIF1DAC2_EQ_B5_GAIN - [10:6] */
+#define WM8995_AIF1DAC2_EQ_B5_GAIN_SHIFT 6 /* AIF1DAC2_EQ_B5_GAIN - [10:6] */
+#define WM8995_AIF1DAC2_EQ_B5_GAIN_WIDTH 5 /* AIF1DAC2_EQ_B5_GAIN - [10:6] */
+
+/*
+ * R1186 (0x4A2) - AIF1 DAC2 EQ Band 1 A
+ */
+#define WM8995_AIF1DAC2_EQ_B1_A_MASK 0xFFFF /* AIF1DAC2_EQ_B1_A - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B1_A_SHIFT 0 /* AIF1DAC2_EQ_B1_A - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B1_A_WIDTH 16 /* AIF1DAC2_EQ_B1_A - [15:0] */
+
+/*
+ * R1187 (0x4A3) - AIF1 DAC2 EQ Band 1 B
+ */
+#define WM8995_AIF1DAC2_EQ_B1_B_MASK 0xFFFF /* AIF1DAC2_EQ_B1_B - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B1_B_SHIFT 0 /* AIF1DAC2_EQ_B1_B - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B1_B_WIDTH 16 /* AIF1DAC2_EQ_B1_B - [15:0] */
+
+/*
+ * R1188 (0x4A4) - AIF1 DAC2 EQ Band 1 PG
+ */
+#define WM8995_AIF1DAC2_EQ_B1_PG_MASK 0xFFFF /* AIF1DAC2_EQ_B1_PG - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B1_PG_SHIFT 0 /* AIF1DAC2_EQ_B1_PG - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B1_PG_WIDTH 16 /* AIF1DAC2_EQ_B1_PG - [15:0] */
+
+/*
+ * R1189 (0x4A5) - AIF1 DAC2 EQ Band 2 A
+ */
+#define WM8995_AIF1DAC2_EQ_B2_A_MASK 0xFFFF /* AIF1DAC2_EQ_B2_A - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B2_A_SHIFT 0 /* AIF1DAC2_EQ_B2_A - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B2_A_WIDTH 16 /* AIF1DAC2_EQ_B2_A - [15:0] */
+
+/*
+ * R1190 (0x4A6) - AIF1 DAC2 EQ Band 2 B
+ */
+#define WM8995_AIF1DAC2_EQ_B2_B_MASK 0xFFFF /* AIF1DAC2_EQ_B2_B - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B2_B_SHIFT 0 /* AIF1DAC2_EQ_B2_B - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B2_B_WIDTH 16 /* AIF1DAC2_EQ_B2_B - [15:0] */
+
+/*
+ * R1191 (0x4A7) - AIF1 DAC2 EQ Band 2 C
+ */
+#define WM8995_AIF1DAC2_EQ_B2_C_MASK 0xFFFF /* AIF1DAC2_EQ_B2_C - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B2_C_SHIFT 0 /* AIF1DAC2_EQ_B2_C - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B2_C_WIDTH 16 /* AIF1DAC2_EQ_B2_C - [15:0] */
+
+/*
+ * R1192 (0x4A8) - AIF1 DAC2 EQ Band 2 PG
+ */
+#define WM8995_AIF1DAC2_EQ_B2_PG_MASK 0xFFFF /* AIF1DAC2_EQ_B2_PG - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B2_PG_SHIFT 0 /* AIF1DAC2_EQ_B2_PG - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B2_PG_WIDTH 16 /* AIF1DAC2_EQ_B2_PG - [15:0] */
+
+/*
+ * R1193 (0x4A9) - AIF1 DAC2 EQ Band 3 A
+ */
+#define WM8995_AIF1DAC2_EQ_B3_A_MASK 0xFFFF /* AIF1DAC2_EQ_B3_A - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B3_A_SHIFT 0 /* AIF1DAC2_EQ_B3_A - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B3_A_WIDTH 16 /* AIF1DAC2_EQ_B3_A - [15:0] */
+
+/*
+ * R1194 (0x4AA) - AIF1 DAC2 EQ Band 3 B
+ */
+#define WM8995_AIF1DAC2_EQ_B3_B_MASK 0xFFFF /* AIF1DAC2_EQ_B3_B - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B3_B_SHIFT 0 /* AIF1DAC2_EQ_B3_B - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B3_B_WIDTH 16 /* AIF1DAC2_EQ_B3_B - [15:0] */
+
+/*
+ * R1195 (0x4AB) - AIF1 DAC2 EQ Band 3 C
+ */
+#define WM8995_AIF1DAC2_EQ_B3_C_MASK 0xFFFF /* AIF1DAC2_EQ_B3_C - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B3_C_SHIFT 0 /* AIF1DAC2_EQ_B3_C - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B3_C_WIDTH 16 /* AIF1DAC2_EQ_B3_C - [15:0] */
+
+/*
+ * R1196 (0x4AC) - AIF1 DAC2 EQ Band 3 PG
+ */
+#define WM8995_AIF1DAC2_EQ_B3_PG_MASK 0xFFFF /* AIF1DAC2_EQ_B3_PG - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B3_PG_SHIFT 0 /* AIF1DAC2_EQ_B3_PG - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B3_PG_WIDTH 16 /* AIF1DAC2_EQ_B3_PG - [15:0] */
+
+/*
+ * R1197 (0x4AD) - AIF1 DAC2 EQ Band 4 A
+ */
+#define WM8995_AIF1DAC2_EQ_B4_A_MASK 0xFFFF /* AIF1DAC2_EQ_B4_A - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B4_A_SHIFT 0 /* AIF1DAC2_EQ_B4_A - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B4_A_WIDTH 16 /* AIF1DAC2_EQ_B4_A - [15:0] */
+
+/*
+ * R1198 (0x4AE) - AIF1 DAC2 EQ Band 4 B
+ */
+#define WM8995_AIF1DAC2_EQ_B4_B_MASK 0xFFFF /* AIF1DAC2_EQ_B4_B - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B4_B_SHIFT 0 /* AIF1DAC2_EQ_B4_B - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B4_B_WIDTH 16 /* AIF1DAC2_EQ_B4_B - [15:0] */
+
+/*
+ * R1199 (0x4AF) - AIF1 DAC2 EQ Band 4 C
+ */
+#define WM8995_AIF1DAC2_EQ_B4_C_MASK 0xFFFF /* AIF1DAC2_EQ_B4_C - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B4_C_SHIFT 0 /* AIF1DAC2_EQ_B4_C - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B4_C_WIDTH 16 /* AIF1DAC2_EQ_B4_C - [15:0] */
+
+/*
+ * R1200 (0x4B0) - AIF1 DAC2 EQ Band 4 PG
+ */
+#define WM8995_AIF1DAC2_EQ_B4_PG_MASK 0xFFFF /* AIF1DAC2_EQ_B4_PG - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B4_PG_SHIFT 0 /* AIF1DAC2_EQ_B4_PG - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B4_PG_WIDTH 16 /* AIF1DAC2_EQ_B4_PG - [15:0] */
+
+/*
+ * R1201 (0x4B1) - AIF1 DAC2 EQ Band 5 A
+ */
+#define WM8995_AIF1DAC2_EQ_B5_A_MASK 0xFFFF /* AIF1DAC2_EQ_B5_A - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B5_A_SHIFT 0 /* AIF1DAC2_EQ_B5_A - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B5_A_WIDTH 16 /* AIF1DAC2_EQ_B5_A - [15:0] */
+
+/*
+ * R1202 (0x4B2) - AIF1 DAC2 EQ Band 5 B
+ */
+#define WM8995_AIF1DAC2_EQ_B5_B_MASK 0xFFFF /* AIF1DAC2_EQ_B5_B - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B5_B_SHIFT 0 /* AIF1DAC2_EQ_B5_B - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B5_B_WIDTH 16 /* AIF1DAC2_EQ_B5_B - [15:0] */
+
+/*
+ * R1203 (0x4B3) - AIF1 DAC2 EQ Band 5 PG
+ */
+#define WM8995_AIF1DAC2_EQ_B5_PG_MASK 0xFFFF /* AIF1DAC2_EQ_B5_PG - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B5_PG_SHIFT 0 /* AIF1DAC2_EQ_B5_PG - [15:0] */
+#define WM8995_AIF1DAC2_EQ_B5_PG_WIDTH 16 /* AIF1DAC2_EQ_B5_PG - [15:0] */
+
+/*
+ * R1280 (0x500) - AIF2 ADC Left Volume
+ */
+#define WM8995_AIF2ADC_VU 0x0100 /* AIF2ADC_VU */
+#define WM8995_AIF2ADC_VU_MASK 0x0100 /* AIF2ADC_VU */
+#define WM8995_AIF2ADC_VU_SHIFT 8 /* AIF2ADC_VU */
+#define WM8995_AIF2ADC_VU_WIDTH 1 /* AIF2ADC_VU */
+#define WM8995_AIF2ADCL_VOL_MASK 0x00FF /* AIF2ADCL_VOL - [7:0] */
+#define WM8995_AIF2ADCL_VOL_SHIFT 0 /* AIF2ADCL_VOL - [7:0] */
+#define WM8995_AIF2ADCL_VOL_WIDTH 8 /* AIF2ADCL_VOL - [7:0] */
+
+/*
+ * R1281 (0x501) - AIF2 ADC Right Volume
+ */
+#define WM8995_AIF2ADC_VU 0x0100 /* AIF2ADC_VU */
+#define WM8995_AIF2ADC_VU_MASK 0x0100 /* AIF2ADC_VU */
+#define WM8995_AIF2ADC_VU_SHIFT 8 /* AIF2ADC_VU */
+#define WM8995_AIF2ADC_VU_WIDTH 1 /* AIF2ADC_VU */
+#define WM8995_AIF2ADCR_VOL_MASK 0x00FF /* AIF2ADCR_VOL - [7:0] */
+#define WM8995_AIF2ADCR_VOL_SHIFT 0 /* AIF2ADCR_VOL - [7:0] */
+#define WM8995_AIF2ADCR_VOL_WIDTH 8 /* AIF2ADCR_VOL - [7:0] */
+
+/*
+ * R1282 (0x502) - AIF2 DAC Left Volume
+ */
+#define WM8995_AIF2DAC_VU 0x0100 /* AIF2DAC_VU */
+#define WM8995_AIF2DAC_VU_MASK 0x0100 /* AIF2DAC_VU */
+#define WM8995_AIF2DAC_VU_SHIFT 8 /* AIF2DAC_VU */
+#define WM8995_AIF2DAC_VU_WIDTH 1 /* AIF2DAC_VU */
+#define WM8995_AIF2DACL_VOL_MASK 0x00FF /* AIF2DACL_VOL - [7:0] */
+#define WM8995_AIF2DACL_VOL_SHIFT 0 /* AIF2DACL_VOL - [7:0] */
+#define WM8995_AIF2DACL_VOL_WIDTH 8 /* AIF2DACL_VOL - [7:0] */
+
+/*
+ * R1283 (0x503) - AIF2 DAC Right Volume
+ */
+#define WM8995_AIF2DAC_VU 0x0100 /* AIF2DAC_VU */
+#define WM8995_AIF2DAC_VU_MASK 0x0100 /* AIF2DAC_VU */
+#define WM8995_AIF2DAC_VU_SHIFT 8 /* AIF2DAC_VU */
+#define WM8995_AIF2DAC_VU_WIDTH 1 /* AIF2DAC_VU */
+#define WM8995_AIF2DACR_VOL_MASK 0x00FF /* AIF2DACR_VOL - [7:0] */
+#define WM8995_AIF2DACR_VOL_SHIFT 0 /* AIF2DACR_VOL - [7:0] */
+#define WM8995_AIF2DACR_VOL_WIDTH 8 /* AIF2DACR_VOL - [7:0] */
+
+/*
+ * R1296 (0x510) - AIF2 ADC Filters
+ */
+#define WM8995_AIF2ADC_4FS 0x8000 /* AIF2ADC_4FS */
+#define WM8995_AIF2ADC_4FS_MASK 0x8000 /* AIF2ADC_4FS */
+#define WM8995_AIF2ADC_4FS_SHIFT 15 /* AIF2ADC_4FS */
+#define WM8995_AIF2ADC_4FS_WIDTH 1 /* AIF2ADC_4FS */
+#define WM8995_AIF2ADCL_HPF 0x1000 /* AIF2ADCL_HPF */
+#define WM8995_AIF2ADCL_HPF_MASK 0x1000 /* AIF2ADCL_HPF */
+#define WM8995_AIF2ADCL_HPF_SHIFT 12 /* AIF2ADCL_HPF */
+#define WM8995_AIF2ADCL_HPF_WIDTH 1 /* AIF2ADCL_HPF */
+#define WM8995_AIF2ADCR_HPF 0x0800 /* AIF2ADCR_HPF */
+#define WM8995_AIF2ADCR_HPF_MASK 0x0800 /* AIF2ADCR_HPF */
+#define WM8995_AIF2ADCR_HPF_SHIFT 11 /* AIF2ADCR_HPF */
+#define WM8995_AIF2ADCR_HPF_WIDTH 1 /* AIF2ADCR_HPF */
+#define WM8995_AIF2ADC_HPF_MODE 0x0008 /* AIF2ADC_HPF_MODE */
+#define WM8995_AIF2ADC_HPF_MODE_MASK 0x0008 /* AIF2ADC_HPF_MODE */
+#define WM8995_AIF2ADC_HPF_MODE_SHIFT 3 /* AIF2ADC_HPF_MODE */
+#define WM8995_AIF2ADC_HPF_MODE_WIDTH 1 /* AIF2ADC_HPF_MODE */
+#define WM8995_AIF2ADC_HPF_CUT_MASK 0x0007 /* AIF2ADC_HPF_CUT - [2:0] */
+#define WM8995_AIF2ADC_HPF_CUT_SHIFT 0 /* AIF2ADC_HPF_CUT - [2:0] */
+#define WM8995_AIF2ADC_HPF_CUT_WIDTH 3 /* AIF2ADC_HPF_CUT - [2:0] */
+
+/*
+ * R1312 (0x520) - AIF2 DAC Filters (1)
+ */
+#define WM8995_AIF2DAC_MUTE 0x0200 /* AIF2DAC_MUTE */
+#define WM8995_AIF2DAC_MUTE_MASK 0x0200 /* AIF2DAC_MUTE */
+#define WM8995_AIF2DAC_MUTE_SHIFT 9 /* AIF2DAC_MUTE */
+#define WM8995_AIF2DAC_MUTE_WIDTH 1 /* AIF2DAC_MUTE */
+#define WM8995_AIF2DAC_MONO 0x0080 /* AIF2DAC_MONO */
+#define WM8995_AIF2DAC_MONO_MASK 0x0080 /* AIF2DAC_MONO */
+#define WM8995_AIF2DAC_MONO_SHIFT 7 /* AIF2DAC_MONO */
+#define WM8995_AIF2DAC_MONO_WIDTH 1 /* AIF2DAC_MONO */
+#define WM8995_AIF2DAC_MUTERATE 0x0020 /* AIF2DAC_MUTERATE */
+#define WM8995_AIF2DAC_MUTERATE_MASK 0x0020 /* AIF2DAC_MUTERATE */
+#define WM8995_AIF2DAC_MUTERATE_SHIFT 5 /* AIF2DAC_MUTERATE */
+#define WM8995_AIF2DAC_MUTERATE_WIDTH 1 /* AIF2DAC_MUTERATE */
+#define WM8995_AIF2DAC_UNMUTE_RAMP 0x0010 /* AIF2DAC_UNMUTE_RAMP */
+#define WM8995_AIF2DAC_UNMUTE_RAMP_MASK 0x0010 /* AIF2DAC_UNMUTE_RAMP */
+#define WM8995_AIF2DAC_UNMUTE_RAMP_SHIFT 4 /* AIF2DAC_UNMUTE_RAMP */
+#define WM8995_AIF2DAC_UNMUTE_RAMP_WIDTH 1 /* AIF2DAC_UNMUTE_RAMP */
+#define WM8995_AIF2DAC_DEEMP_MASK 0x0006 /* AIF2DAC_DEEMP - [2:1] */
+#define WM8995_AIF2DAC_DEEMP_SHIFT 1 /* AIF2DAC_DEEMP - [2:1] */
+#define WM8995_AIF2DAC_DEEMP_WIDTH 2 /* AIF2DAC_DEEMP - [2:1] */
+
+/*
+ * R1313 (0x521) - AIF2 DAC Filters (2)
+ */
+#define WM8995_AIF2DAC_3D_GAIN_MASK 0x3E00 /* AIF2DAC_3D_GAIN - [13:9] */
+#define WM8995_AIF2DAC_3D_GAIN_SHIFT 9 /* AIF2DAC_3D_GAIN - [13:9] */
+#define WM8995_AIF2DAC_3D_GAIN_WIDTH 5 /* AIF2DAC_3D_GAIN - [13:9] */
+#define WM8995_AIF2DAC_3D_ENA 0x0100 /* AIF2DAC_3D_ENA */
+#define WM8995_AIF2DAC_3D_ENA_MASK 0x0100 /* AIF2DAC_3D_ENA */
+#define WM8995_AIF2DAC_3D_ENA_SHIFT 8 /* AIF2DAC_3D_ENA */
+#define WM8995_AIF2DAC_3D_ENA_WIDTH 1 /* AIF2DAC_3D_ENA */
+
+/*
+ * R1344 (0x540) - AIF2 DRC (1)
+ */
+#define WM8995_AIF2DRC_SIG_DET_RMS_MASK 0xF800 /* AIF2DRC_SIG_DET_RMS - [15:11] */
+#define WM8995_AIF2DRC_SIG_DET_RMS_SHIFT 11 /* AIF2DRC_SIG_DET_RMS - [15:11] */
+#define WM8995_AIF2DRC_SIG_DET_RMS_WIDTH 5 /* AIF2DRC_SIG_DET_RMS - [15:11] */
+#define WM8995_AIF2DRC_SIG_DET_PK_MASK 0x0600 /* AIF2DRC_SIG_DET_PK - [10:9] */
+#define WM8995_AIF2DRC_SIG_DET_PK_SHIFT 9 /* AIF2DRC_SIG_DET_PK - [10:9] */
+#define WM8995_AIF2DRC_SIG_DET_PK_WIDTH 2 /* AIF2DRC_SIG_DET_PK - [10:9] */
+#define WM8995_AIF2DRC_NG_ENA 0x0100 /* AIF2DRC_NG_ENA */
+#define WM8995_AIF2DRC_NG_ENA_MASK 0x0100 /* AIF2DRC_NG_ENA */
+#define WM8995_AIF2DRC_NG_ENA_SHIFT 8 /* AIF2DRC_NG_ENA */
+#define WM8995_AIF2DRC_NG_ENA_WIDTH 1 /* AIF2DRC_NG_ENA */
+#define WM8995_AIF2DRC_SIG_DET_MODE 0x0080 /* AIF2DRC_SIG_DET_MODE */
+#define WM8995_AIF2DRC_SIG_DET_MODE_MASK 0x0080 /* AIF2DRC_SIG_DET_MODE */
+#define WM8995_AIF2DRC_SIG_DET_MODE_SHIFT 7 /* AIF2DRC_SIG_DET_MODE */
+#define WM8995_AIF2DRC_SIG_DET_MODE_WIDTH 1 /* AIF2DRC_SIG_DET_MODE */
+#define WM8995_AIF2DRC_SIG_DET 0x0040 /* AIF2DRC_SIG_DET */
+#define WM8995_AIF2DRC_SIG_DET_MASK 0x0040 /* AIF2DRC_SIG_DET */
+#define WM8995_AIF2DRC_SIG_DET_SHIFT 6 /* AIF2DRC_SIG_DET */
+#define WM8995_AIF2DRC_SIG_DET_WIDTH 1 /* AIF2DRC_SIG_DET */
+#define WM8995_AIF2DRC_KNEE2_OP_ENA 0x0020 /* AIF2DRC_KNEE2_OP_ENA */
+#define WM8995_AIF2DRC_KNEE2_OP_ENA_MASK 0x0020 /* AIF2DRC_KNEE2_OP_ENA */
+#define WM8995_AIF2DRC_KNEE2_OP_ENA_SHIFT 5 /* AIF2DRC_KNEE2_OP_ENA */
+#define WM8995_AIF2DRC_KNEE2_OP_ENA_WIDTH 1 /* AIF2DRC_KNEE2_OP_ENA */
+#define WM8995_AIF2DRC_QR 0x0010 /* AIF2DRC_QR */
+#define WM8995_AIF2DRC_QR_MASK 0x0010 /* AIF2DRC_QR */
+#define WM8995_AIF2DRC_QR_SHIFT 4 /* AIF2DRC_QR */
+#define WM8995_AIF2DRC_QR_WIDTH 1 /* AIF2DRC_QR */
+#define WM8995_AIF2DRC_ANTICLIP 0x0008 /* AIF2DRC_ANTICLIP */
+#define WM8995_AIF2DRC_ANTICLIP_MASK 0x0008 /* AIF2DRC_ANTICLIP */
+#define WM8995_AIF2DRC_ANTICLIP_SHIFT 3 /* AIF2DRC_ANTICLIP */
+#define WM8995_AIF2DRC_ANTICLIP_WIDTH 1 /* AIF2DRC_ANTICLIP */
+#define WM8995_AIF2DAC_DRC_ENA 0x0004 /* AIF2DAC_DRC_ENA */
+#define WM8995_AIF2DAC_DRC_ENA_MASK 0x0004 /* AIF2DAC_DRC_ENA */
+#define WM8995_AIF2DAC_DRC_ENA_SHIFT 2 /* AIF2DAC_DRC_ENA */
+#define WM8995_AIF2DAC_DRC_ENA_WIDTH 1 /* AIF2DAC_DRC_ENA */
+#define WM8995_AIF2ADCL_DRC_ENA 0x0002 /* AIF2ADCL_DRC_ENA */
+#define WM8995_AIF2ADCL_DRC_ENA_MASK 0x0002 /* AIF2ADCL_DRC_ENA */
+#define WM8995_AIF2ADCL_DRC_ENA_SHIFT 1 /* AIF2ADCL_DRC_ENA */
+#define WM8995_AIF2ADCL_DRC_ENA_WIDTH 1 /* AIF2ADCL_DRC_ENA */
+#define WM8995_AIF2ADCR_DRC_ENA 0x0001 /* AIF2ADCR_DRC_ENA */
+#define WM8995_AIF2ADCR_DRC_ENA_MASK 0x0001 /* AIF2ADCR_DRC_ENA */
+#define WM8995_AIF2ADCR_DRC_ENA_SHIFT 0 /* AIF2ADCR_DRC_ENA */
+#define WM8995_AIF2ADCR_DRC_ENA_WIDTH 1 /* AIF2ADCR_DRC_ENA */
+
+/*
+ * R1345 (0x541) - AIF2 DRC (2)
+ */
+#define WM8995_AIF2DRC_ATK_MASK 0x1E00 /* AIF2DRC_ATK - [12:9] */
+#define WM8995_AIF2DRC_ATK_SHIFT 9 /* AIF2DRC_ATK - [12:9] */
+#define WM8995_AIF2DRC_ATK_WIDTH 4 /* AIF2DRC_ATK - [12:9] */
+#define WM8995_AIF2DRC_DCY_MASK 0x01E0 /* AIF2DRC_DCY - [8:5] */
+#define WM8995_AIF2DRC_DCY_SHIFT 5 /* AIF2DRC_DCY - [8:5] */
+#define WM8995_AIF2DRC_DCY_WIDTH 4 /* AIF2DRC_DCY - [8:5] */
+#define WM8995_AIF2DRC_MINGAIN_MASK 0x001C /* AIF2DRC_MINGAIN - [4:2] */
+#define WM8995_AIF2DRC_MINGAIN_SHIFT 2 /* AIF2DRC_MINGAIN - [4:2] */
+#define WM8995_AIF2DRC_MINGAIN_WIDTH 3 /* AIF2DRC_MINGAIN - [4:2] */
+#define WM8995_AIF2DRC_MAXGAIN_MASK 0x0003 /* AIF2DRC_MAXGAIN - [1:0] */
+#define WM8995_AIF2DRC_MAXGAIN_SHIFT 0 /* AIF2DRC_MAXGAIN - [1:0] */
+#define WM8995_AIF2DRC_MAXGAIN_WIDTH 2 /* AIF2DRC_MAXGAIN - [1:0] */
+
+/*
+ * R1346 (0x542) - AIF2 DRC (3)
+ */
+#define WM8995_AIF2DRC_NG_MINGAIN_MASK 0xF000 /* AIF2DRC_NG_MINGAIN - [15:12] */
+#define WM8995_AIF2DRC_NG_MINGAIN_SHIFT 12 /* AIF2DRC_NG_MINGAIN - [15:12] */
+#define WM8995_AIF2DRC_NG_MINGAIN_WIDTH 4 /* AIF2DRC_NG_MINGAIN - [15:12] */
+#define WM8995_AIF2DRC_NG_EXP_MASK 0x0C00 /* AIF2DRC_NG_EXP - [11:10] */
+#define WM8995_AIF2DRC_NG_EXP_SHIFT 10 /* AIF2DRC_NG_EXP - [11:10] */
+#define WM8995_AIF2DRC_NG_EXP_WIDTH 2 /* AIF2DRC_NG_EXP - [11:10] */
+#define WM8995_AIF2DRC_QR_THR_MASK 0x0300 /* AIF2DRC_QR_THR - [9:8] */
+#define WM8995_AIF2DRC_QR_THR_SHIFT 8 /* AIF2DRC_QR_THR - [9:8] */
+#define WM8995_AIF2DRC_QR_THR_WIDTH 2 /* AIF2DRC_QR_THR - [9:8] */
+#define WM8995_AIF2DRC_QR_DCY_MASK 0x00C0 /* AIF2DRC_QR_DCY - [7:6] */
+#define WM8995_AIF2DRC_QR_DCY_SHIFT 6 /* AIF2DRC_QR_DCY - [7:6] */
+#define WM8995_AIF2DRC_QR_DCY_WIDTH 2 /* AIF2DRC_QR_DCY - [7:6] */
+#define WM8995_AIF2DRC_HI_COMP_MASK 0x0038 /* AIF2DRC_HI_COMP - [5:3] */
+#define WM8995_AIF2DRC_HI_COMP_SHIFT 3 /* AIF2DRC_HI_COMP - [5:3] */
+#define WM8995_AIF2DRC_HI_COMP_WIDTH 3 /* AIF2DRC_HI_COMP - [5:3] */
+#define WM8995_AIF2DRC_LO_COMP_MASK 0x0007 /* AIF2DRC_LO_COMP - [2:0] */
+#define WM8995_AIF2DRC_LO_COMP_SHIFT 0 /* AIF2DRC_LO_COMP - [2:0] */
+#define WM8995_AIF2DRC_LO_COMP_WIDTH 3 /* AIF2DRC_LO_COMP - [2:0] */
+
+/*
+ * R1347 (0x543) - AIF2 DRC (4)
+ */
+#define WM8995_AIF2DRC_KNEE_IP_MASK 0x07E0 /* AIF2DRC_KNEE_IP - [10:5] */
+#define WM8995_AIF2DRC_KNEE_IP_SHIFT 5 /* AIF2DRC_KNEE_IP - [10:5] */
+#define WM8995_AIF2DRC_KNEE_IP_WIDTH 6 /* AIF2DRC_KNEE_IP - [10:5] */
+#define WM8995_AIF2DRC_KNEE_OP_MASK 0x001F /* AIF2DRC_KNEE_OP - [4:0] */
+#define WM8995_AIF2DRC_KNEE_OP_SHIFT 0 /* AIF2DRC_KNEE_OP - [4:0] */
+#define WM8995_AIF2DRC_KNEE_OP_WIDTH 5 /* AIF2DRC_KNEE_OP - [4:0] */
+
+/*
+ * R1348 (0x544) - AIF2 DRC (5)
+ */
+#define WM8995_AIF2DRC_KNEE2_IP_MASK 0x03E0 /* AIF2DRC_KNEE2_IP - [9:5] */
+#define WM8995_AIF2DRC_KNEE2_IP_SHIFT 5 /* AIF2DRC_KNEE2_IP - [9:5] */
+#define WM8995_AIF2DRC_KNEE2_IP_WIDTH 5 /* AIF2DRC_KNEE2_IP - [9:5] */
+#define WM8995_AIF2DRC_KNEE2_OP_MASK 0x001F /* AIF2DRC_KNEE2_OP - [4:0] */
+#define WM8995_AIF2DRC_KNEE2_OP_SHIFT 0 /* AIF2DRC_KNEE2_OP - [4:0] */
+#define WM8995_AIF2DRC_KNEE2_OP_WIDTH 5 /* AIF2DRC_KNEE2_OP - [4:0] */
+
+/*
+ * R1408 (0x580) - AIF2 EQ Gains (1)
+ */
+#define WM8995_AIF2DAC_EQ_B1_GAIN_MASK 0xF800 /* AIF2DAC_EQ_B1_GAIN - [15:11] */
+#define WM8995_AIF2DAC_EQ_B1_GAIN_SHIFT 11 /* AIF2DAC_EQ_B1_GAIN - [15:11] */
+#define WM8995_AIF2DAC_EQ_B1_GAIN_WIDTH 5 /* AIF2DAC_EQ_B1_GAIN - [15:11] */
+#define WM8995_AIF2DAC_EQ_B2_GAIN_MASK 0x07C0 /* AIF2DAC_EQ_B2_GAIN - [10:6] */
+#define WM8995_AIF2DAC_EQ_B2_GAIN_SHIFT 6 /* AIF2DAC_EQ_B2_GAIN - [10:6] */
+#define WM8995_AIF2DAC_EQ_B2_GAIN_WIDTH 5 /* AIF2DAC_EQ_B2_GAIN - [10:6] */
+#define WM8995_AIF2DAC_EQ_B3_GAIN_MASK 0x003E /* AIF2DAC_EQ_B3_GAIN - [5:1] */
+#define WM8995_AIF2DAC_EQ_B3_GAIN_SHIFT 1 /* AIF2DAC_EQ_B3_GAIN - [5:1] */
+#define WM8995_AIF2DAC_EQ_B3_GAIN_WIDTH 5 /* AIF2DAC_EQ_B3_GAIN - [5:1] */
+#define WM8995_AIF2DAC_EQ_ENA 0x0001 /* AIF2DAC_EQ_ENA */
+#define WM8995_AIF2DAC_EQ_ENA_MASK 0x0001 /* AIF2DAC_EQ_ENA */
+#define WM8995_AIF2DAC_EQ_ENA_SHIFT 0 /* AIF2DAC_EQ_ENA */
+#define WM8995_AIF2DAC_EQ_ENA_WIDTH 1 /* AIF2DAC_EQ_ENA */
+
+/*
+ * R1409 (0x581) - AIF2 EQ Gains (2)
+ */
+#define WM8995_AIF2DAC_EQ_B4_GAIN_MASK 0xF800 /* AIF2DAC_EQ_B4_GAIN - [15:11] */
+#define WM8995_AIF2DAC_EQ_B4_GAIN_SHIFT 11 /* AIF2DAC_EQ_B4_GAIN - [15:11] */
+#define WM8995_AIF2DAC_EQ_B4_GAIN_WIDTH 5 /* AIF2DAC_EQ_B4_GAIN - [15:11] */
+#define WM8995_AIF2DAC_EQ_B5_GAIN_MASK 0x07C0 /* AIF2DAC_EQ_B5_GAIN - [10:6] */
+#define WM8995_AIF2DAC_EQ_B5_GAIN_SHIFT 6 /* AIF2DAC_EQ_B5_GAIN - [10:6] */
+#define WM8995_AIF2DAC_EQ_B5_GAIN_WIDTH 5 /* AIF2DAC_EQ_B5_GAIN - [10:6] */
+
+/*
+ * R1410 (0x582) - AIF2 EQ Band 1 A
+ */
+#define WM8995_AIF2DAC_EQ_B1_A_MASK 0xFFFF /* AIF2DAC_EQ_B1_A - [15:0] */
+#define WM8995_AIF2DAC_EQ_B1_A_SHIFT 0 /* AIF2DAC_EQ_B1_A - [15:0] */
+#define WM8995_AIF2DAC_EQ_B1_A_WIDTH 16 /* AIF2DAC_EQ_B1_A - [15:0] */
+
+/*
+ * R1411 (0x583) - AIF2 EQ Band 1 B
+ */
+#define WM8995_AIF2DAC_EQ_B1_B_MASK 0xFFFF /* AIF2DAC_EQ_B1_B - [15:0] */
+#define WM8995_AIF2DAC_EQ_B1_B_SHIFT 0 /* AIF2DAC_EQ_B1_B - [15:0] */
+#define WM8995_AIF2DAC_EQ_B1_B_WIDTH 16 /* AIF2DAC_EQ_B1_B - [15:0] */
+
+/*
+ * R1412 (0x584) - AIF2 EQ Band 1 PG
+ */
+#define WM8995_AIF2DAC_EQ_B1_PG_MASK 0xFFFF /* AIF2DAC_EQ_B1_PG - [15:0] */
+#define WM8995_AIF2DAC_EQ_B1_PG_SHIFT 0 /* AIF2DAC_EQ_B1_PG - [15:0] */
+#define WM8995_AIF2DAC_EQ_B1_PG_WIDTH 16 /* AIF2DAC_EQ_B1_PG - [15:0] */
+
+/*
+ * R1413 (0x585) - AIF2 EQ Band 2 A
+ */
+#define WM8995_AIF2DAC_EQ_B2_A_MASK 0xFFFF /* AIF2DAC_EQ_B2_A - [15:0] */
+#define WM8995_AIF2DAC_EQ_B2_A_SHIFT 0 /* AIF2DAC_EQ_B2_A - [15:0] */
+#define WM8995_AIF2DAC_EQ_B2_A_WIDTH 16 /* AIF2DAC_EQ_B2_A - [15:0] */
+
+/*
+ * R1414 (0x586) - AIF2 EQ Band 2 B
+ */
+#define WM8995_AIF2DAC_EQ_B2_B_MASK 0xFFFF /* AIF2DAC_EQ_B2_B - [15:0] */
+#define WM8995_AIF2DAC_EQ_B2_B_SHIFT 0 /* AIF2DAC_EQ_B2_B - [15:0] */
+#define WM8995_AIF2DAC_EQ_B2_B_WIDTH 16 /* AIF2DAC_EQ_B2_B - [15:0] */
+
+/*
+ * R1415 (0x587) - AIF2 EQ Band 2 C
+ */
+#define WM8995_AIF2DAC_EQ_B2_C_MASK 0xFFFF /* AIF2DAC_EQ_B2_C - [15:0] */
+#define WM8995_AIF2DAC_EQ_B2_C_SHIFT 0 /* AIF2DAC_EQ_B2_C - [15:0] */
+#define WM8995_AIF2DAC_EQ_B2_C_WIDTH 16 /* AIF2DAC_EQ_B2_C - [15:0] */
+
+/*
+ * R1416 (0x588) - AIF2 EQ Band 2 PG
+ */
+#define WM8995_AIF2DAC_EQ_B2_PG_MASK 0xFFFF /* AIF2DAC_EQ_B2_PG - [15:0] */
+#define WM8995_AIF2DAC_EQ_B2_PG_SHIFT 0 /* AIF2DAC_EQ_B2_PG - [15:0] */
+#define WM8995_AIF2DAC_EQ_B2_PG_WIDTH 16 /* AIF2DAC_EQ_B2_PG - [15:0] */
+
+/*
+ * R1417 (0x589) - AIF2 EQ Band 3 A
+ */
+#define WM8995_AIF2DAC_EQ_B3_A_MASK 0xFFFF /* AIF2DAC_EQ_B3_A - [15:0] */
+#define WM8995_AIF2DAC_EQ_B3_A_SHIFT 0 /* AIF2DAC_EQ_B3_A - [15:0] */
+#define WM8995_AIF2DAC_EQ_B3_A_WIDTH 16 /* AIF2DAC_EQ_B3_A - [15:0] */
+
+/*
+ * R1418 (0x58A) - AIF2 EQ Band 3 B
+ */
+#define WM8995_AIF2DAC_EQ_B3_B_MASK 0xFFFF /* AIF2DAC_EQ_B3_B - [15:0] */
+#define WM8995_AIF2DAC_EQ_B3_B_SHIFT 0 /* AIF2DAC_EQ_B3_B - [15:0] */
+#define WM8995_AIF2DAC_EQ_B3_B_WIDTH 16 /* AIF2DAC_EQ_B3_B - [15:0] */
+
+/*
+ * R1419 (0x58B) - AIF2 EQ Band 3 C
+ */
+#define WM8995_AIF2DAC_EQ_B3_C_MASK 0xFFFF /* AIF2DAC_EQ_B3_C - [15:0] */
+#define WM8995_AIF2DAC_EQ_B3_C_SHIFT 0 /* AIF2DAC_EQ_B3_C - [15:0] */
+#define WM8995_AIF2DAC_EQ_B3_C_WIDTH 16 /* AIF2DAC_EQ_B3_C - [15:0] */
+
+/*
+ * R1420 (0x58C) - AIF2 EQ Band 3 PG
+ */
+#define WM8995_AIF2DAC_EQ_B3_PG_MASK 0xFFFF /* AIF2DAC_EQ_B3_PG - [15:0] */
+#define WM8995_AIF2DAC_EQ_B3_PG_SHIFT 0 /* AIF2DAC_EQ_B3_PG - [15:0] */
+#define WM8995_AIF2DAC_EQ_B3_PG_WIDTH 16 /* AIF2DAC_EQ_B3_PG - [15:0] */
+
+/*
+ * R1421 (0x58D) - AIF2 EQ Band 4 A
+ */
+#define WM8995_AIF2DAC_EQ_B4_A_MASK 0xFFFF /* AIF2DAC_EQ_B4_A - [15:0] */
+#define WM8995_AIF2DAC_EQ_B4_A_SHIFT 0 /* AIF2DAC_EQ_B4_A - [15:0] */
+#define WM8995_AIF2DAC_EQ_B4_A_WIDTH 16 /* AIF2DAC_EQ_B4_A - [15:0] */
+
+/*
+ * R1422 (0x58E) - AIF2 EQ Band 4 B
+ */
+#define WM8995_AIF2DAC_EQ_B4_B_MASK 0xFFFF /* AIF2DAC_EQ_B4_B - [15:0] */
+#define WM8995_AIF2DAC_EQ_B4_B_SHIFT 0 /* AIF2DAC_EQ_B4_B - [15:0] */
+#define WM8995_AIF2DAC_EQ_B4_B_WIDTH 16 /* AIF2DAC_EQ_B4_B - [15:0] */
+
+/*
+ * R1423 (0x58F) - AIF2 EQ Band 4 C
+ */
+#define WM8995_AIF2DAC_EQ_B4_C_MASK 0xFFFF /* AIF2DAC_EQ_B4_C - [15:0] */
+#define WM8995_AIF2DAC_EQ_B4_C_SHIFT 0 /* AIF2DAC_EQ_B4_C - [15:0] */
+#define WM8995_AIF2DAC_EQ_B4_C_WIDTH 16 /* AIF2DAC_EQ_B4_C - [15:0] */
+
+/*
+ * R1424 (0x590) - AIF2 EQ Band 4 PG
+ */
+#define WM8995_AIF2DAC_EQ_B4_PG_MASK 0xFFFF /* AIF2DAC_EQ_B4_PG - [15:0] */
+#define WM8995_AIF2DAC_EQ_B4_PG_SHIFT 0 /* AIF2DAC_EQ_B4_PG - [15:0] */
+#define WM8995_AIF2DAC_EQ_B4_PG_WIDTH 16 /* AIF2DAC_EQ_B4_PG - [15:0] */
+
+/*
+ * R1425 (0x591) - AIF2 EQ Band 5 A
+ */
+#define WM8995_AIF2DAC_EQ_B5_A_MASK 0xFFFF /* AIF2DAC_EQ_B5_A - [15:0] */
+#define WM8995_AIF2DAC_EQ_B5_A_SHIFT 0 /* AIF2DAC_EQ_B5_A - [15:0] */
+#define WM8995_AIF2DAC_EQ_B5_A_WIDTH 16 /* AIF2DAC_EQ_B5_A - [15:0] */
+
+/*
+ * R1426 (0x592) - AIF2 EQ Band 5 B
+ */
+#define WM8995_AIF2DAC_EQ_B5_B_MASK 0xFFFF /* AIF2DAC_EQ_B5_B - [15:0] */
+#define WM8995_AIF2DAC_EQ_B5_B_SHIFT 0 /* AIF2DAC_EQ_B5_B - [15:0] */
+#define WM8995_AIF2DAC_EQ_B5_B_WIDTH 16 /* AIF2DAC_EQ_B5_B - [15:0] */
+
+/*
+ * R1427 (0x593) - AIF2 EQ Band 5 PG
+ */
+#define WM8995_AIF2DAC_EQ_B5_PG_MASK 0xFFFF /* AIF2DAC_EQ_B5_PG - [15:0] */
+#define WM8995_AIF2DAC_EQ_B5_PG_SHIFT 0 /* AIF2DAC_EQ_B5_PG - [15:0] */
+#define WM8995_AIF2DAC_EQ_B5_PG_WIDTH 16 /* AIF2DAC_EQ_B5_PG - [15:0] */
+
+/*
+ * R1536 (0x600) - DAC1 Mixer Volumes
+ */
+#define WM8995_ADCR_DAC1_VOL_MASK 0x03E0 /* ADCR_DAC1_VOL - [9:5] */
+#define WM8995_ADCR_DAC1_VOL_SHIFT 5 /* ADCR_DAC1_VOL - [9:5] */
+#define WM8995_ADCR_DAC1_VOL_WIDTH 5 /* ADCR_DAC1_VOL - [9:5] */
+#define WM8995_ADCL_DAC1_VOL_MASK 0x001F /* ADCL_DAC1_VOL - [4:0] */
+#define WM8995_ADCL_DAC1_VOL_SHIFT 0 /* ADCL_DAC1_VOL - [4:0] */
+#define WM8995_ADCL_DAC1_VOL_WIDTH 5 /* ADCL_DAC1_VOL - [4:0] */
+
+/*
+ * R1537 (0x601) - DAC1 Left Mixer Routing
+ */
+#define WM8995_ADCR_TO_DAC1L 0x0020 /* ADCR_TO_DAC1L */
+#define WM8995_ADCR_TO_DAC1L_MASK 0x0020 /* ADCR_TO_DAC1L */
+#define WM8995_ADCR_TO_DAC1L_SHIFT 5 /* ADCR_TO_DAC1L */
+#define WM8995_ADCR_TO_DAC1L_WIDTH 1 /* ADCR_TO_DAC1L */
+#define WM8995_ADCL_TO_DAC1L 0x0010 /* ADCL_TO_DAC1L */
+#define WM8995_ADCL_TO_DAC1L_MASK 0x0010 /* ADCL_TO_DAC1L */
+#define WM8995_ADCL_TO_DAC1L_SHIFT 4 /* ADCL_TO_DAC1L */
+#define WM8995_ADCL_TO_DAC1L_WIDTH 1 /* ADCL_TO_DAC1L */
+#define WM8995_AIF2DACL_TO_DAC1L 0x0004 /* AIF2DACL_TO_DAC1L */
+#define WM8995_AIF2DACL_TO_DAC1L_MASK 0x0004 /* AIF2DACL_TO_DAC1L */
+#define WM8995_AIF2DACL_TO_DAC1L_SHIFT 2 /* AIF2DACL_TO_DAC1L */
+#define WM8995_AIF2DACL_TO_DAC1L_WIDTH 1 /* AIF2DACL_TO_DAC1L */
+#define WM8995_AIF1DAC2L_TO_DAC1L 0x0002 /* AIF1DAC2L_TO_DAC1L */
+#define WM8995_AIF1DAC2L_TO_DAC1L_MASK 0x0002 /* AIF1DAC2L_TO_DAC1L */
+#define WM8995_AIF1DAC2L_TO_DAC1L_SHIFT 1 /* AIF1DAC2L_TO_DAC1L */
+#define WM8995_AIF1DAC2L_TO_DAC1L_WIDTH 1 /* AIF1DAC2L_TO_DAC1L */
+#define WM8995_AIF1DAC1L_TO_DAC1L 0x0001 /* AIF1DAC1L_TO_DAC1L */
+#define WM8995_AIF1DAC1L_TO_DAC1L_MASK 0x0001 /* AIF1DAC1L_TO_DAC1L */
+#define WM8995_AIF1DAC1L_TO_DAC1L_SHIFT 0 /* AIF1DAC1L_TO_DAC1L */
+#define WM8995_AIF1DAC1L_TO_DAC1L_WIDTH 1 /* AIF1DAC1L_TO_DAC1L */
+
+/*
+ * R1538 (0x602) - DAC1 Right Mixer Routing
+ */
+#define WM8995_ADCR_TO_DAC1R 0x0020 /* ADCR_TO_DAC1R */
+#define WM8995_ADCR_TO_DAC1R_MASK 0x0020 /* ADCR_TO_DAC1R */
+#define WM8995_ADCR_TO_DAC1R_SHIFT 5 /* ADCR_TO_DAC1R */
+#define WM8995_ADCR_TO_DAC1R_WIDTH 1 /* ADCR_TO_DAC1R */
+#define WM8995_ADCL_TO_DAC1R 0x0010 /* ADCL_TO_DAC1R */
+#define WM8995_ADCL_TO_DAC1R_MASK 0x0010 /* ADCL_TO_DAC1R */
+#define WM8995_ADCL_TO_DAC1R_SHIFT 4 /* ADCL_TO_DAC1R */
+#define WM8995_ADCL_TO_DAC1R_WIDTH 1 /* ADCL_TO_DAC1R */
+#define WM8995_AIF2DACR_TO_DAC1R 0x0004 /* AIF2DACR_TO_DAC1R */
+#define WM8995_AIF2DACR_TO_DAC1R_MASK 0x0004 /* AIF2DACR_TO_DAC1R */
+#define WM8995_AIF2DACR_TO_DAC1R_SHIFT 2 /* AIF2DACR_TO_DAC1R */
+#define WM8995_AIF2DACR_TO_DAC1R_WIDTH 1 /* AIF2DACR_TO_DAC1R */
+#define WM8995_AIF1DAC2R_TO_DAC1R 0x0002 /* AIF1DAC2R_TO_DAC1R */
+#define WM8995_AIF1DAC2R_TO_DAC1R_MASK 0x0002 /* AIF1DAC2R_TO_DAC1R */
+#define WM8995_AIF1DAC2R_TO_DAC1R_SHIFT 1 /* AIF1DAC2R_TO_DAC1R */
+#define WM8995_AIF1DAC2R_TO_DAC1R_WIDTH 1 /* AIF1DAC2R_TO_DAC1R */
+#define WM8995_AIF1DAC1R_TO_DAC1R 0x0001 /* AIF1DAC1R_TO_DAC1R */
+#define WM8995_AIF1DAC1R_TO_DAC1R_MASK 0x0001 /* AIF1DAC1R_TO_DAC1R */
+#define WM8995_AIF1DAC1R_TO_DAC1R_SHIFT 0 /* AIF1DAC1R_TO_DAC1R */
+#define WM8995_AIF1DAC1R_TO_DAC1R_WIDTH 1 /* AIF1DAC1R_TO_DAC1R */
+
+/*
+ * R1539 (0x603) - DAC2 Mixer Volumes
+ */
+#define WM8995_ADCR_DAC2_VOL_MASK 0x03E0 /* ADCR_DAC2_VOL - [9:5] */
+#define WM8995_ADCR_DAC2_VOL_SHIFT 5 /* ADCR_DAC2_VOL - [9:5] */
+#define WM8995_ADCR_DAC2_VOL_WIDTH 5 /* ADCR_DAC2_VOL - [9:5] */
+#define WM8995_ADCL_DAC2_VOL_MASK 0x001F /* ADCL_DAC2_VOL - [4:0] */
+#define WM8995_ADCL_DAC2_VOL_SHIFT 0 /* ADCL_DAC2_VOL - [4:0] */
+#define WM8995_ADCL_DAC2_VOL_WIDTH 5 /* ADCL_DAC2_VOL - [4:0] */
+
+/*
+ * R1540 (0x604) - DAC2 Left Mixer Routing
+ */
+#define WM8995_ADCR_TO_DAC2L 0x0020 /* ADCR_TO_DAC2L */
+#define WM8995_ADCR_TO_DAC2L_MASK 0x0020 /* ADCR_TO_DAC2L */
+#define WM8995_ADCR_TO_DAC2L_SHIFT 5 /* ADCR_TO_DAC2L */
+#define WM8995_ADCR_TO_DAC2L_WIDTH 1 /* ADCR_TO_DAC2L */
+#define WM8995_ADCL_TO_DAC2L 0x0010 /* ADCL_TO_DAC2L */
+#define WM8995_ADCL_TO_DAC2L_MASK 0x0010 /* ADCL_TO_DAC2L */
+#define WM8995_ADCL_TO_DAC2L_SHIFT 4 /* ADCL_TO_DAC2L */
+#define WM8995_ADCL_TO_DAC2L_WIDTH 1 /* ADCL_TO_DAC2L */
+#define WM8995_AIF2DACL_TO_DAC2L 0x0004 /* AIF2DACL_TO_DAC2L */
+#define WM8995_AIF2DACL_TO_DAC2L_MASK 0x0004 /* AIF2DACL_TO_DAC2L */
+#define WM8995_AIF2DACL_TO_DAC2L_SHIFT 2 /* AIF2DACL_TO_DAC2L */
+#define WM8995_AIF2DACL_TO_DAC2L_WIDTH 1 /* AIF2DACL_TO_DAC2L */
+#define WM8995_AIF1DAC2L_TO_DAC2L 0x0002 /* AIF1DAC2L_TO_DAC2L */
+#define WM8995_AIF1DAC2L_TO_DAC2L_MASK 0x0002 /* AIF1DAC2L_TO_DAC2L */
+#define WM8995_AIF1DAC2L_TO_DAC2L_SHIFT 1 /* AIF1DAC2L_TO_DAC2L */
+#define WM8995_AIF1DAC2L_TO_DAC2L_WIDTH 1 /* AIF1DAC2L_TO_DAC2L */
+#define WM8995_AIF1DAC1L_TO_DAC2L 0x0001 /* AIF1DAC1L_TO_DAC2L */
+#define WM8995_AIF1DAC1L_TO_DAC2L_MASK 0x0001 /* AIF1DAC1L_TO_DAC2L */
+#define WM8995_AIF1DAC1L_TO_DAC2L_SHIFT 0 /* AIF1DAC1L_TO_DAC2L */
+#define WM8995_AIF1DAC1L_TO_DAC2L_WIDTH 1 /* AIF1DAC1L_TO_DAC2L */
+
+/*
+ * R1541 (0x605) - DAC2 Right Mixer Routing
+ */
+#define WM8995_ADCR_TO_DAC2R 0x0020 /* ADCR_TO_DAC2R */
+#define WM8995_ADCR_TO_DAC2R_MASK 0x0020 /* ADCR_TO_DAC2R */
+#define WM8995_ADCR_TO_DAC2R_SHIFT 5 /* ADCR_TO_DAC2R */
+#define WM8995_ADCR_TO_DAC2R_WIDTH 1 /* ADCR_TO_DAC2R */
+#define WM8995_ADCL_TO_DAC2R 0x0010 /* ADCL_TO_DAC2R */
+#define WM8995_ADCL_TO_DAC2R_MASK 0x0010 /* ADCL_TO_DAC2R */
+#define WM8995_ADCL_TO_DAC2R_SHIFT 4 /* ADCL_TO_DAC2R */
+#define WM8995_ADCL_TO_DAC2R_WIDTH 1 /* ADCL_TO_DAC2R */
+#define WM8995_AIF2DACR_TO_DAC2R 0x0004 /* AIF2DACR_TO_DAC2R */
+#define WM8995_AIF2DACR_TO_DAC2R_MASK 0x0004 /* AIF2DACR_TO_DAC2R */
+#define WM8995_AIF2DACR_TO_DAC2R_SHIFT 2 /* AIF2DACR_TO_DAC2R */
+#define WM8995_AIF2DACR_TO_DAC2R_WIDTH 1 /* AIF2DACR_TO_DAC2R */
+#define WM8995_AIF1DAC2R_TO_DAC2R 0x0002 /* AIF1DAC2R_TO_DAC2R */
+#define WM8995_AIF1DAC2R_TO_DAC2R_MASK 0x0002 /* AIF1DAC2R_TO_DAC2R */
+#define WM8995_AIF1DAC2R_TO_DAC2R_SHIFT 1 /* AIF1DAC2R_TO_DAC2R */
+#define WM8995_AIF1DAC2R_TO_DAC2R_WIDTH 1 /* AIF1DAC2R_TO_DAC2R */
+#define WM8995_AIF1DAC1R_TO_DAC2R 0x0001 /* AIF1DAC1R_TO_DAC2R */
+#define WM8995_AIF1DAC1R_TO_DAC2R_MASK 0x0001 /* AIF1DAC1R_TO_DAC2R */
+#define WM8995_AIF1DAC1R_TO_DAC2R_SHIFT 0 /* AIF1DAC1R_TO_DAC2R */
+#define WM8995_AIF1DAC1R_TO_DAC2R_WIDTH 1 /* AIF1DAC1R_TO_DAC2R */
+
+/*
+ * R1542 (0x606) - AIF1 ADC1 Left Mixer Routing
+ */
+#define WM8995_ADC1L_TO_AIF1ADC1L 0x0002 /* ADC1L_TO_AIF1ADC1L */
+#define WM8995_ADC1L_TO_AIF1ADC1L_MASK 0x0002 /* ADC1L_TO_AIF1ADC1L */
+#define WM8995_ADC1L_TO_AIF1ADC1L_SHIFT 1 /* ADC1L_TO_AIF1ADC1L */
+#define WM8995_ADC1L_TO_AIF1ADC1L_WIDTH 1 /* ADC1L_TO_AIF1ADC1L */
+#define WM8995_AIF2DACL_TO_AIF1ADC1L 0x0001 /* AIF2DACL_TO_AIF1ADC1L */
+#define WM8995_AIF2DACL_TO_AIF1ADC1L_MASK 0x0001 /* AIF2DACL_TO_AIF1ADC1L */
+#define WM8995_AIF2DACL_TO_AIF1ADC1L_SHIFT 0 /* AIF2DACL_TO_AIF1ADC1L */
+#define WM8995_AIF2DACL_TO_AIF1ADC1L_WIDTH 1 /* AIF2DACL_TO_AIF1ADC1L */
+
+/*
+ * R1543 (0x607) - AIF1 ADC1 Right Mixer Routing
+ */
+#define WM8995_ADC1R_TO_AIF1ADC1R 0x0002 /* ADC1R_TO_AIF1ADC1R */
+#define WM8995_ADC1R_TO_AIF1ADC1R_MASK 0x0002 /* ADC1R_TO_AIF1ADC1R */
+#define WM8995_ADC1R_TO_AIF1ADC1R_SHIFT 1 /* ADC1R_TO_AIF1ADC1R */
+#define WM8995_ADC1R_TO_AIF1ADC1R_WIDTH 1 /* ADC1R_TO_AIF1ADC1R */
+#define WM8995_AIF2DACR_TO_AIF1ADC1R 0x0001 /* AIF2DACR_TO_AIF1ADC1R */
+#define WM8995_AIF2DACR_TO_AIF1ADC1R_MASK 0x0001 /* AIF2DACR_TO_AIF1ADC1R */
+#define WM8995_AIF2DACR_TO_AIF1ADC1R_SHIFT 0 /* AIF2DACR_TO_AIF1ADC1R */
+#define WM8995_AIF2DACR_TO_AIF1ADC1R_WIDTH 1 /* AIF2DACR_TO_AIF1ADC1R */
+
+/*
+ * R1544 (0x608) - AIF1 ADC2 Left Mixer Routing
+ */
+#define WM8995_ADC2L_TO_AIF1ADC2L 0x0002 /* ADC2L_TO_AIF1ADC2L */
+#define WM8995_ADC2L_TO_AIF1ADC2L_MASK 0x0002 /* ADC2L_TO_AIF1ADC2L */
+#define WM8995_ADC2L_TO_AIF1ADC2L_SHIFT 1 /* ADC2L_TO_AIF1ADC2L */
+#define WM8995_ADC2L_TO_AIF1ADC2L_WIDTH 1 /* ADC2L_TO_AIF1ADC2L */
+#define WM8995_AIF2DACL_TO_AIF1ADC2L 0x0001 /* AIF2DACL_TO_AIF1ADC2L */
+#define WM8995_AIF2DACL_TO_AIF1ADC2L_MASK 0x0001 /* AIF2DACL_TO_AIF1ADC2L */
+#define WM8995_AIF2DACL_TO_AIF1ADC2L_SHIFT 0 /* AIF2DACL_TO_AIF1ADC2L */
+#define WM8995_AIF2DACL_TO_AIF1ADC2L_WIDTH 1 /* AIF2DACL_TO_AIF1ADC2L */
+
+/*
+ * R1545 (0x609) - AIF1 ADC2 Right mixer Routing
+ */
+#define WM8995_ADC2R_TO_AIF1ADC2R 0x0002 /* ADC2R_TO_AIF1ADC2R */
+#define WM8995_ADC2R_TO_AIF1ADC2R_MASK 0x0002 /* ADC2R_TO_AIF1ADC2R */
+#define WM8995_ADC2R_TO_AIF1ADC2R_SHIFT 1 /* ADC2R_TO_AIF1ADC2R */
+#define WM8995_ADC2R_TO_AIF1ADC2R_WIDTH 1 /* ADC2R_TO_AIF1ADC2R */
+#define WM8995_AIF2DACR_TO_AIF1ADC2R 0x0001 /* AIF2DACR_TO_AIF1ADC2R */
+#define WM8995_AIF2DACR_TO_AIF1ADC2R_MASK 0x0001 /* AIF2DACR_TO_AIF1ADC2R */
+#define WM8995_AIF2DACR_TO_AIF1ADC2R_SHIFT 0 /* AIF2DACR_TO_AIF1ADC2R */
+#define WM8995_AIF2DACR_TO_AIF1ADC2R_WIDTH 1 /* AIF2DACR_TO_AIF1ADC2R */
+
+/*
+ * R1552 (0x610) - DAC Softmute
+ */
+#define WM8995_DAC_SOFTMUTEMODE 0x0002 /* DAC_SOFTMUTEMODE */
+#define WM8995_DAC_SOFTMUTEMODE_MASK 0x0002 /* DAC_SOFTMUTEMODE */
+#define WM8995_DAC_SOFTMUTEMODE_SHIFT 1 /* DAC_SOFTMUTEMODE */
+#define WM8995_DAC_SOFTMUTEMODE_WIDTH 1 /* DAC_SOFTMUTEMODE */
+#define WM8995_DAC_MUTERATE 0x0001 /* DAC_MUTERATE */
+#define WM8995_DAC_MUTERATE_MASK 0x0001 /* DAC_MUTERATE */
+#define WM8995_DAC_MUTERATE_SHIFT 0 /* DAC_MUTERATE */
+#define WM8995_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
+
+/*
+ * R1568 (0x620) - Oversampling
+ */
+#define WM8995_ADC_OSR128 0x0002 /* ADC_OSR128 */
+#define WM8995_ADC_OSR128_MASK 0x0002 /* ADC_OSR128 */
+#define WM8995_ADC_OSR128_SHIFT 1 /* ADC_OSR128 */
+#define WM8995_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */
+#define WM8995_DAC_OSR128 0x0001 /* DAC_OSR128 */
+#define WM8995_DAC_OSR128_MASK 0x0001 /* DAC_OSR128 */
+#define WM8995_DAC_OSR128_SHIFT 0 /* DAC_OSR128 */
+#define WM8995_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */
+
+/*
+ * R1569 (0x621) - Sidetone
+ */
+#define WM8995_ST_LPF 0x1000 /* ST_LPF */
+#define WM8995_ST_LPF_MASK 0x1000 /* ST_LPF */
+#define WM8995_ST_LPF_SHIFT 12 /* ST_LPF */
+#define WM8995_ST_LPF_WIDTH 1 /* ST_LPF */
+#define WM8995_ST_HPF_CUT_MASK 0x0380 /* ST_HPF_CUT - [9:7] */
+#define WM8995_ST_HPF_CUT_SHIFT 7 /* ST_HPF_CUT - [9:7] */
+#define WM8995_ST_HPF_CUT_WIDTH 3 /* ST_HPF_CUT - [9:7] */
+#define WM8995_ST_HPF 0x0040 /* ST_HPF */
+#define WM8995_ST_HPF_MASK 0x0040 /* ST_HPF */
+#define WM8995_ST_HPF_SHIFT 6 /* ST_HPF */
+#define WM8995_ST_HPF_WIDTH 1 /* ST_HPF */
+#define WM8995_STR_SEL 0x0002 /* STR_SEL */
+#define WM8995_STR_SEL_MASK 0x0002 /* STR_SEL */
+#define WM8995_STR_SEL_SHIFT 1 /* STR_SEL */
+#define WM8995_STR_SEL_WIDTH 1 /* STR_SEL */
+#define WM8995_STL_SEL 0x0001 /* STL_SEL */
+#define WM8995_STL_SEL_MASK 0x0001 /* STL_SEL */
+#define WM8995_STL_SEL_SHIFT 0 /* STL_SEL */
+#define WM8995_STL_SEL_WIDTH 1 /* STL_SEL */
+
+/*
+ * R1792 (0x700) - GPIO 1
+ */
+#define WM8995_GP1_DIR 0x8000 /* GP1_DIR */
+#define WM8995_GP1_DIR_MASK 0x8000 /* GP1_DIR */
+#define WM8995_GP1_DIR_SHIFT 15 /* GP1_DIR */
+#define WM8995_GP1_DIR_WIDTH 1 /* GP1_DIR */
+#define WM8995_GP1_PU 0x4000 /* GP1_PU */
+#define WM8995_GP1_PU_MASK 0x4000 /* GP1_PU */
+#define WM8995_GP1_PU_SHIFT 14 /* GP1_PU */
+#define WM8995_GP1_PU_WIDTH 1 /* GP1_PU */
+#define WM8995_GP1_PD 0x2000 /* GP1_PD */
+#define WM8995_GP1_PD_MASK 0x2000 /* GP1_PD */
+#define WM8995_GP1_PD_SHIFT 13 /* GP1_PD */
+#define WM8995_GP1_PD_WIDTH 1 /* GP1_PD */
+#define WM8995_GP1_POL 0x0400 /* GP1_POL */
+#define WM8995_GP1_POL_MASK 0x0400 /* GP1_POL */
+#define WM8995_GP1_POL_SHIFT 10 /* GP1_POL */
+#define WM8995_GP1_POL_WIDTH 1 /* GP1_POL */
+#define WM8995_GP1_OP_CFG 0x0200 /* GP1_OP_CFG */
+#define WM8995_GP1_OP_CFG_MASK 0x0200 /* GP1_OP_CFG */
+#define WM8995_GP1_OP_CFG_SHIFT 9 /* GP1_OP_CFG */
+#define WM8995_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */
+#define WM8995_GP1_DB 0x0100 /* GP1_DB */
+#define WM8995_GP1_DB_MASK 0x0100 /* GP1_DB */
+#define WM8995_GP1_DB_SHIFT 8 /* GP1_DB */
+#define WM8995_GP1_DB_WIDTH 1 /* GP1_DB */
+#define WM8995_GP1_LVL 0x0040 /* GP1_LVL */
+#define WM8995_GP1_LVL_MASK 0x0040 /* GP1_LVL */
+#define WM8995_GP1_LVL_SHIFT 6 /* GP1_LVL */
+#define WM8995_GP1_LVL_WIDTH 1 /* GP1_LVL */
+#define WM8995_GP1_FN_MASK 0x001F /* GP1_FN - [4:0] */
+#define WM8995_GP1_FN_SHIFT 0 /* GP1_FN - [4:0] */
+#define WM8995_GP1_FN_WIDTH 5 /* GP1_FN - [4:0] */
+
+/*
+ * R1793 (0x701) - GPIO 2
+ */
+#define WM8995_GP2_DIR 0x8000 /* GP2_DIR */
+#define WM8995_GP2_DIR_MASK 0x8000 /* GP2_DIR */
+#define WM8995_GP2_DIR_SHIFT 15 /* GP2_DIR */
+#define WM8995_GP2_DIR_WIDTH 1 /* GP2_DIR */
+#define WM8995_GP2_PU 0x4000 /* GP2_PU */
+#define WM8995_GP2_PU_MASK 0x4000 /* GP2_PU */
+#define WM8995_GP2_PU_SHIFT 14 /* GP2_PU */
+#define WM8995_GP2_PU_WIDTH 1 /* GP2_PU */
+#define WM8995_GP2_PD 0x2000 /* GP2_PD */
+#define WM8995_GP2_PD_MASK 0x2000 /* GP2_PD */
+#define WM8995_GP2_PD_SHIFT 13 /* GP2_PD */
+#define WM8995_GP2_PD_WIDTH 1 /* GP2_PD */
+#define WM8995_GP2_POL 0x0400 /* GP2_POL */
+#define WM8995_GP2_POL_MASK 0x0400 /* GP2_POL */
+#define WM8995_GP2_POL_SHIFT 10 /* GP2_POL */
+#define WM8995_GP2_POL_WIDTH 1 /* GP2_POL */
+#define WM8995_GP2_OP_CFG 0x0200 /* GP2_OP_CFG */
+#define WM8995_GP2_OP_CFG_MASK 0x0200 /* GP2_OP_CFG */
+#define WM8995_GP2_OP_CFG_SHIFT 9 /* GP2_OP_CFG */
+#define WM8995_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */
+#define WM8995_GP2_DB 0x0100 /* GP2_DB */
+#define WM8995_GP2_DB_MASK 0x0100 /* GP2_DB */
+#define WM8995_GP2_DB_SHIFT 8 /* GP2_DB */
+#define WM8995_GP2_DB_WIDTH 1 /* GP2_DB */
+#define WM8995_GP2_LVL 0x0040 /* GP2_LVL */
+#define WM8995_GP2_LVL_MASK 0x0040 /* GP2_LVL */
+#define WM8995_GP2_LVL_SHIFT 6 /* GP2_LVL */
+#define WM8995_GP2_LVL_WIDTH 1 /* GP2_LVL */
+#define WM8995_GP2_FN_MASK 0x001F /* GP2_FN - [4:0] */
+#define WM8995_GP2_FN_SHIFT 0 /* GP2_FN - [4:0] */
+#define WM8995_GP2_FN_WIDTH 5 /* GP2_FN - [4:0] */
+
+/*
+ * R1794 (0x702) - GPIO 3
+ */
+#define WM8995_GP3_DIR 0x8000 /* GP3_DIR */
+#define WM8995_GP3_DIR_MASK 0x8000 /* GP3_DIR */
+#define WM8995_GP3_DIR_SHIFT 15 /* GP3_DIR */
+#define WM8995_GP3_DIR_WIDTH 1 /* GP3_DIR */
+#define WM8995_GP3_PU 0x4000 /* GP3_PU */
+#define WM8995_GP3_PU_MASK 0x4000 /* GP3_PU */
+#define WM8995_GP3_PU_SHIFT 14 /* GP3_PU */
+#define WM8995_GP3_PU_WIDTH 1 /* GP3_PU */
+#define WM8995_GP3_PD 0x2000 /* GP3_PD */
+#define WM8995_GP3_PD_MASK 0x2000 /* GP3_PD */
+#define WM8995_GP3_PD_SHIFT 13 /* GP3_PD */
+#define WM8995_GP3_PD_WIDTH 1 /* GP3_PD */
+#define WM8995_GP3_POL 0x0400 /* GP3_POL */
+#define WM8995_GP3_POL_MASK 0x0400 /* GP3_POL */
+#define WM8995_GP3_POL_SHIFT 10 /* GP3_POL */
+#define WM8995_GP3_POL_WIDTH 1 /* GP3_POL */
+#define WM8995_GP3_OP_CFG 0x0200 /* GP3_OP_CFG */
+#define WM8995_GP3_OP_CFG_MASK 0x0200 /* GP3_OP_CFG */
+#define WM8995_GP3_OP_CFG_SHIFT 9 /* GP3_OP_CFG */
+#define WM8995_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */
+#define WM8995_GP3_DB 0x0100 /* GP3_DB */
+#define WM8995_GP3_DB_MASK 0x0100 /* GP3_DB */
+#define WM8995_GP3_DB_SHIFT 8 /* GP3_DB */
+#define WM8995_GP3_DB_WIDTH 1 /* GP3_DB */
+#define WM8995_GP3_LVL 0x0040 /* GP3_LVL */
+#define WM8995_GP3_LVL_MASK 0x0040 /* GP3_LVL */
+#define WM8995_GP3_LVL_SHIFT 6 /* GP3_LVL */
+#define WM8995_GP3_LVL_WIDTH 1 /* GP3_LVL */
+#define WM8995_GP3_FN_MASK 0x001F /* GP3_FN - [4:0] */
+#define WM8995_GP3_FN_SHIFT 0 /* GP3_FN - [4:0] */
+#define WM8995_GP3_FN_WIDTH 5 /* GP3_FN - [4:0] */
+
+/*
+ * R1795 (0x703) - GPIO 4
+ */
+#define WM8995_GP4_DIR 0x8000 /* GP4_DIR */
+#define WM8995_GP4_DIR_MASK 0x8000 /* GP4_DIR */
+#define WM8995_GP4_DIR_SHIFT 15 /* GP4_DIR */
+#define WM8995_GP4_DIR_WIDTH 1 /* GP4_DIR */
+#define WM8995_GP4_PU 0x4000 /* GP4_PU */
+#define WM8995_GP4_PU_MASK 0x4000 /* GP4_PU */
+#define WM8995_GP4_PU_SHIFT 14 /* GP4_PU */
+#define WM8995_GP4_PU_WIDTH 1 /* GP4_PU */
+#define WM8995_GP4_PD 0x2000 /* GP4_PD */
+#define WM8995_GP4_PD_MASK 0x2000 /* GP4_PD */
+#define WM8995_GP4_PD_SHIFT 13 /* GP4_PD */
+#define WM8995_GP4_PD_WIDTH 1 /* GP4_PD */
+#define WM8995_GP4_POL 0x0400 /* GP4_POL */
+#define WM8995_GP4_POL_MASK 0x0400 /* GP4_POL */
+#define WM8995_GP4_POL_SHIFT 10 /* GP4_POL */
+#define WM8995_GP4_POL_WIDTH 1 /* GP4_POL */
+#define WM8995_GP4_OP_CFG 0x0200 /* GP4_OP_CFG */
+#define WM8995_GP4_OP_CFG_MASK 0x0200 /* GP4_OP_CFG */
+#define WM8995_GP4_OP_CFG_SHIFT 9 /* GP4_OP_CFG */
+#define WM8995_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */
+#define WM8995_GP4_DB 0x0100 /* GP4_DB */
+#define WM8995_GP4_DB_MASK 0x0100 /* GP4_DB */
+#define WM8995_GP4_DB_SHIFT 8 /* GP4_DB */
+#define WM8995_GP4_DB_WIDTH 1 /* GP4_DB */
+#define WM8995_GP4_LVL 0x0040 /* GP4_LVL */
+#define WM8995_GP4_LVL_MASK 0x0040 /* GP4_LVL */
+#define WM8995_GP4_LVL_SHIFT 6 /* GP4_LVL */
+#define WM8995_GP4_LVL_WIDTH 1 /* GP4_LVL */
+#define WM8995_GP4_FN_MASK 0x001F /* GP4_FN - [4:0] */
+#define WM8995_GP4_FN_SHIFT 0 /* GP4_FN - [4:0] */
+#define WM8995_GP4_FN_WIDTH 5 /* GP4_FN - [4:0] */
+
+/*
+ * R1796 (0x704) - GPIO 5
+ */
+#define WM8995_GP5_DIR 0x8000 /* GP5_DIR */
+#define WM8995_GP5_DIR_MASK 0x8000 /* GP5_DIR */
+#define WM8995_GP5_DIR_SHIFT 15 /* GP5_DIR */
+#define WM8995_GP5_DIR_WIDTH 1 /* GP5_DIR */
+#define WM8995_GP5_PU 0x4000 /* GP5_PU */
+#define WM8995_GP5_PU_MASK 0x4000 /* GP5_PU */
+#define WM8995_GP5_PU_SHIFT 14 /* GP5_PU */
+#define WM8995_GP5_PU_WIDTH 1 /* GP5_PU */
+#define WM8995_GP5_PD 0x2000 /* GP5_PD */
+#define WM8995_GP5_PD_MASK 0x2000 /* GP5_PD */
+#define WM8995_GP5_PD_SHIFT 13 /* GP5_PD */
+#define WM8995_GP5_PD_WIDTH 1 /* GP5_PD */
+#define WM8995_GP5_POL 0x0400 /* GP5_POL */
+#define WM8995_GP5_POL_MASK 0x0400 /* GP5_POL */
+#define WM8995_GP5_POL_SHIFT 10 /* GP5_POL */
+#define WM8995_GP5_POL_WIDTH 1 /* GP5_POL */
+#define WM8995_GP5_OP_CFG 0x0200 /* GP5_OP_CFG */
+#define WM8995_GP5_OP_CFG_MASK 0x0200 /* GP5_OP_CFG */
+#define WM8995_GP5_OP_CFG_SHIFT 9 /* GP5_OP_CFG */
+#define WM8995_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */
+#define WM8995_GP5_DB 0x0100 /* GP5_DB */
+#define WM8995_GP5_DB_MASK 0x0100 /* GP5_DB */
+#define WM8995_GP5_DB_SHIFT 8 /* GP5_DB */
+#define WM8995_GP5_DB_WIDTH 1 /* GP5_DB */
+#define WM8995_GP5_LVL 0x0040 /* GP5_LVL */
+#define WM8995_GP5_LVL_MASK 0x0040 /* GP5_LVL */
+#define WM8995_GP5_LVL_SHIFT 6 /* GP5_LVL */
+#define WM8995_GP5_LVL_WIDTH 1 /* GP5_LVL */
+#define WM8995_GP5_FN_MASK 0x001F /* GP5_FN - [4:0] */
+#define WM8995_GP5_FN_SHIFT 0 /* GP5_FN - [4:0] */
+#define WM8995_GP5_FN_WIDTH 5 /* GP5_FN - [4:0] */
+
+/*
+ * R1797 (0x705) - GPIO 6
+ */
+#define WM8995_GP6_DIR 0x8000 /* GP6_DIR */
+#define WM8995_GP6_DIR_MASK 0x8000 /* GP6_DIR */
+#define WM8995_GP6_DIR_SHIFT 15 /* GP6_DIR */
+#define WM8995_GP6_DIR_WIDTH 1 /* GP6_DIR */
+#define WM8995_GP6_PU 0x4000 /* GP6_PU */
+#define WM8995_GP6_PU_MASK 0x4000 /* GP6_PU */
+#define WM8995_GP6_PU_SHIFT 14 /* GP6_PU */
+#define WM8995_GP6_PU_WIDTH 1 /* GP6_PU */
+#define WM8995_GP6_PD 0x2000 /* GP6_PD */
+#define WM8995_GP6_PD_MASK 0x2000 /* GP6_PD */
+#define WM8995_GP6_PD_SHIFT 13 /* GP6_PD */
+#define WM8995_GP6_PD_WIDTH 1 /* GP6_PD */
+#define WM8995_GP6_POL 0x0400 /* GP6_POL */
+#define WM8995_GP6_POL_MASK 0x0400 /* GP6_POL */
+#define WM8995_GP6_POL_SHIFT 10 /* GP6_POL */
+#define WM8995_GP6_POL_WIDTH 1 /* GP6_POL */
+#define WM8995_GP6_OP_CFG 0x0200 /* GP6_OP_CFG */
+#define WM8995_GP6_OP_CFG_MASK 0x0200 /* GP6_OP_CFG */
+#define WM8995_GP6_OP_CFG_SHIFT 9 /* GP6_OP_CFG */
+#define WM8995_GP6_OP_CFG_WIDTH 1 /* GP6_OP_CFG */
+#define WM8995_GP6_DB 0x0100 /* GP6_DB */
+#define WM8995_GP6_DB_MASK 0x0100 /* GP6_DB */
+#define WM8995_GP6_DB_SHIFT 8 /* GP6_DB */
+#define WM8995_GP6_DB_WIDTH 1 /* GP6_DB */
+#define WM8995_GP6_LVL 0x0040 /* GP6_LVL */
+#define WM8995_GP6_LVL_MASK 0x0040 /* GP6_LVL */
+#define WM8995_GP6_LVL_SHIFT 6 /* GP6_LVL */
+#define WM8995_GP6_LVL_WIDTH 1 /* GP6_LVL */
+#define WM8995_GP6_FN_MASK 0x001F /* GP6_FN - [4:0] */
+#define WM8995_GP6_FN_SHIFT 0 /* GP6_FN - [4:0] */
+#define WM8995_GP6_FN_WIDTH 5 /* GP6_FN - [4:0] */
+
+/*
+ * R1798 (0x706) - GPIO 7
+ */
+#define WM8995_GP7_DIR 0x8000 /* GP7_DIR */
+#define WM8995_GP7_DIR_MASK 0x8000 /* GP7_DIR */
+#define WM8995_GP7_DIR_SHIFT 15 /* GP7_DIR */
+#define WM8995_GP7_DIR_WIDTH 1 /* GP7_DIR */
+#define WM8995_GP7_PU 0x4000 /* GP7_PU */
+#define WM8995_GP7_PU_MASK 0x4000 /* GP7_PU */
+#define WM8995_GP7_PU_SHIFT 14 /* GP7_PU */
+#define WM8995_GP7_PU_WIDTH 1 /* GP7_PU */
+#define WM8995_GP7_PD 0x2000 /* GP7_PD */
+#define WM8995_GP7_PD_MASK 0x2000 /* GP7_PD */
+#define WM8995_GP7_PD_SHIFT 13 /* GP7_PD */
+#define WM8995_GP7_PD_WIDTH 1 /* GP7_PD */
+#define WM8995_GP7_POL 0x0400 /* GP7_POL */
+#define WM8995_GP7_POL_MASK 0x0400 /* GP7_POL */
+#define WM8995_GP7_POL_SHIFT 10 /* GP7_POL */
+#define WM8995_GP7_POL_WIDTH 1 /* GP7_POL */
+#define WM8995_GP7_OP_CFG 0x0200 /* GP7_OP_CFG */
+#define WM8995_GP7_OP_CFG_MASK 0x0200 /* GP7_OP_CFG */
+#define WM8995_GP7_OP_CFG_SHIFT 9 /* GP7_OP_CFG */
+#define WM8995_GP7_OP_CFG_WIDTH 1 /* GP7_OP_CFG */
+#define WM8995_GP7_DB 0x0100 /* GP7_DB */
+#define WM8995_GP7_DB_MASK 0x0100 /* GP7_DB */
+#define WM8995_GP7_DB_SHIFT 8 /* GP7_DB */
+#define WM8995_GP7_DB_WIDTH 1 /* GP7_DB */
+#define WM8995_GP7_LVL 0x0040 /* GP7_LVL */
+#define WM8995_GP7_LVL_MASK 0x0040 /* GP7_LVL */
+#define WM8995_GP7_LVL_SHIFT 6 /* GP7_LVL */
+#define WM8995_GP7_LVL_WIDTH 1 /* GP7_LVL */
+#define WM8995_GP7_FN_MASK 0x001F /* GP7_FN - [4:0] */
+#define WM8995_GP7_FN_SHIFT 0 /* GP7_FN - [4:0] */
+#define WM8995_GP7_FN_WIDTH 5 /* GP7_FN - [4:0] */
+
+/*
+ * R1799 (0x707) - GPIO 8
+ */
+#define WM8995_GP8_DIR 0x8000 /* GP8_DIR */
+#define WM8995_GP8_DIR_MASK 0x8000 /* GP8_DIR */
+#define WM8995_GP8_DIR_SHIFT 15 /* GP8_DIR */
+#define WM8995_GP8_DIR_WIDTH 1 /* GP8_DIR */
+#define WM8995_GP8_PU 0x4000 /* GP8_PU */
+#define WM8995_GP8_PU_MASK 0x4000 /* GP8_PU */
+#define WM8995_GP8_PU_SHIFT 14 /* GP8_PU */
+#define WM8995_GP8_PU_WIDTH 1 /* GP8_PU */
+#define WM8995_GP8_PD 0x2000 /* GP8_PD */
+#define WM8995_GP8_PD_MASK 0x2000 /* GP8_PD */
+#define WM8995_GP8_PD_SHIFT 13 /* GP8_PD */
+#define WM8995_GP8_PD_WIDTH 1 /* GP8_PD */
+#define WM8995_GP8_POL 0x0400 /* GP8_POL */
+#define WM8995_GP8_POL_MASK 0x0400 /* GP8_POL */
+#define WM8995_GP8_POL_SHIFT 10 /* GP8_POL */
+#define WM8995_GP8_POL_WIDTH 1 /* GP8_POL */
+#define WM8995_GP8_OP_CFG 0x0200 /* GP8_OP_CFG */
+#define WM8995_GP8_OP_CFG_MASK 0x0200 /* GP8_OP_CFG */
+#define WM8995_GP8_OP_CFG_SHIFT 9 /* GP8_OP_CFG */
+#define WM8995_GP8_OP_CFG_WIDTH 1 /* GP8_OP_CFG */
+#define WM8995_GP8_DB 0x0100 /* GP8_DB */
+#define WM8995_GP8_DB_MASK 0x0100 /* GP8_DB */
+#define WM8995_GP8_DB_SHIFT 8 /* GP8_DB */
+#define WM8995_GP8_DB_WIDTH 1 /* GP8_DB */
+#define WM8995_GP8_LVL 0x0040 /* GP8_LVL */
+#define WM8995_GP8_LVL_MASK 0x0040 /* GP8_LVL */
+#define WM8995_GP8_LVL_SHIFT 6 /* GP8_LVL */
+#define WM8995_GP8_LVL_WIDTH 1 /* GP8_LVL */
+#define WM8995_GP8_FN_MASK 0x001F /* GP8_FN - [4:0] */
+#define WM8995_GP8_FN_SHIFT 0 /* GP8_FN - [4:0] */
+#define WM8995_GP8_FN_WIDTH 5 /* GP8_FN - [4:0] */
+
+/*
+ * R1800 (0x708) - GPIO 9
+ */
+#define WM8995_GP9_DIR 0x8000 /* GP9_DIR */
+#define WM8995_GP9_DIR_MASK 0x8000 /* GP9_DIR */
+#define WM8995_GP9_DIR_SHIFT 15 /* GP9_DIR */
+#define WM8995_GP9_DIR_WIDTH 1 /* GP9_DIR */
+#define WM8995_GP9_PU 0x4000 /* GP9_PU */
+#define WM8995_GP9_PU_MASK 0x4000 /* GP9_PU */
+#define WM8995_GP9_PU_SHIFT 14 /* GP9_PU */
+#define WM8995_GP9_PU_WIDTH 1 /* GP9_PU */
+#define WM8995_GP9_PD 0x2000 /* GP9_PD */
+#define WM8995_GP9_PD_MASK 0x2000 /* GP9_PD */
+#define WM8995_GP9_PD_SHIFT 13 /* GP9_PD */
+#define WM8995_GP9_PD_WIDTH 1 /* GP9_PD */
+#define WM8995_GP9_POL 0x0400 /* GP9_POL */
+#define WM8995_GP9_POL_MASK 0x0400 /* GP9_POL */
+#define WM8995_GP9_POL_SHIFT 10 /* GP9_POL */
+#define WM8995_GP9_POL_WIDTH 1 /* GP9_POL */
+#define WM8995_GP9_OP_CFG 0x0200 /* GP9_OP_CFG */
+#define WM8995_GP9_OP_CFG_MASK 0x0200 /* GP9_OP_CFG */
+#define WM8995_GP9_OP_CFG_SHIFT 9 /* GP9_OP_CFG */
+#define WM8995_GP9_OP_CFG_WIDTH 1 /* GP9_OP_CFG */
+#define WM8995_GP9_DB 0x0100 /* GP9_DB */
+#define WM8995_GP9_DB_MASK 0x0100 /* GP9_DB */
+#define WM8995_GP9_DB_SHIFT 8 /* GP9_DB */
+#define WM8995_GP9_DB_WIDTH 1 /* GP9_DB */
+#define WM8995_GP9_LVL 0x0040 /* GP9_LVL */
+#define WM8995_GP9_LVL_MASK 0x0040 /* GP9_LVL */
+#define WM8995_GP9_LVL_SHIFT 6 /* GP9_LVL */
+#define WM8995_GP9_LVL_WIDTH 1 /* GP9_LVL */
+#define WM8995_GP9_FN_MASK 0x001F /* GP9_FN - [4:0] */
+#define WM8995_GP9_FN_SHIFT 0 /* GP9_FN - [4:0] */
+#define WM8995_GP9_FN_WIDTH 5 /* GP9_FN - [4:0] */
+
+/*
+ * R1801 (0x709) - GPIO 10
+ */
+#define WM8995_GP10_DIR 0x8000 /* GP10_DIR */
+#define WM8995_GP10_DIR_MASK 0x8000 /* GP10_DIR */
+#define WM8995_GP10_DIR_SHIFT 15 /* GP10_DIR */
+#define WM8995_GP10_DIR_WIDTH 1 /* GP10_DIR */
+#define WM8995_GP10_PU 0x4000 /* GP10_PU */
+#define WM8995_GP10_PU_MASK 0x4000 /* GP10_PU */
+#define WM8995_GP10_PU_SHIFT 14 /* GP10_PU */
+#define WM8995_GP10_PU_WIDTH 1 /* GP10_PU */
+#define WM8995_GP10_PD 0x2000 /* GP10_PD */
+#define WM8995_GP10_PD_MASK 0x2000 /* GP10_PD */
+#define WM8995_GP10_PD_SHIFT 13 /* GP10_PD */
+#define WM8995_GP10_PD_WIDTH 1 /* GP10_PD */
+#define WM8995_GP10_POL 0x0400 /* GP10_POL */
+#define WM8995_GP10_POL_MASK 0x0400 /* GP10_POL */
+#define WM8995_GP10_POL_SHIFT 10 /* GP10_POL */
+#define WM8995_GP10_POL_WIDTH 1 /* GP10_POL */
+#define WM8995_GP10_OP_CFG 0x0200 /* GP10_OP_CFG */
+#define WM8995_GP10_OP_CFG_MASK 0x0200 /* GP10_OP_CFG */
+#define WM8995_GP10_OP_CFG_SHIFT 9 /* GP10_OP_CFG */
+#define WM8995_GP10_OP_CFG_WIDTH 1 /* GP10_OP_CFG */
+#define WM8995_GP10_DB 0x0100 /* GP10_DB */
+#define WM8995_GP10_DB_MASK 0x0100 /* GP10_DB */
+#define WM8995_GP10_DB_SHIFT 8 /* GP10_DB */
+#define WM8995_GP10_DB_WIDTH 1 /* GP10_DB */
+#define WM8995_GP10_LVL 0x0040 /* GP10_LVL */
+#define WM8995_GP10_LVL_MASK 0x0040 /* GP10_LVL */
+#define WM8995_GP10_LVL_SHIFT 6 /* GP10_LVL */
+#define WM8995_GP10_LVL_WIDTH 1 /* GP10_LVL */
+#define WM8995_GP10_FN_MASK 0x001F /* GP10_FN - [4:0] */
+#define WM8995_GP10_FN_SHIFT 0 /* GP10_FN - [4:0] */
+#define WM8995_GP10_FN_WIDTH 5 /* GP10_FN - [4:0] */
+
+/*
+ * R1802 (0x70A) - GPIO 11
+ */
+#define WM8995_GP11_DIR 0x8000 /* GP11_DIR */
+#define WM8995_GP11_DIR_MASK 0x8000 /* GP11_DIR */
+#define WM8995_GP11_DIR_SHIFT 15 /* GP11_DIR */
+#define WM8995_GP11_DIR_WIDTH 1 /* GP11_DIR */
+#define WM8995_GP11_PU 0x4000 /* GP11_PU */
+#define WM8995_GP11_PU_MASK 0x4000 /* GP11_PU */
+#define WM8995_GP11_PU_SHIFT 14 /* GP11_PU */
+#define WM8995_GP11_PU_WIDTH 1 /* GP11_PU */
+#define WM8995_GP11_PD 0x2000 /* GP11_PD */
+#define WM8995_GP11_PD_MASK 0x2000 /* GP11_PD */
+#define WM8995_GP11_PD_SHIFT 13 /* GP11_PD */
+#define WM8995_GP11_PD_WIDTH 1 /* GP11_PD */
+#define WM8995_GP11_POL 0x0400 /* GP11_POL */
+#define WM8995_GP11_POL_MASK 0x0400 /* GP11_POL */
+#define WM8995_GP11_POL_SHIFT 10 /* GP11_POL */
+#define WM8995_GP11_POL_WIDTH 1 /* GP11_POL */
+#define WM8995_GP11_OP_CFG 0x0200 /* GP11_OP_CFG */
+#define WM8995_GP11_OP_CFG_MASK 0x0200 /* GP11_OP_CFG */
+#define WM8995_GP11_OP_CFG_SHIFT 9 /* GP11_OP_CFG */
+#define WM8995_GP11_OP_CFG_WIDTH 1 /* GP11_OP_CFG */
+#define WM8995_GP11_DB 0x0100 /* GP11_DB */
+#define WM8995_GP11_DB_MASK 0x0100 /* GP11_DB */
+#define WM8995_GP11_DB_SHIFT 8 /* GP11_DB */
+#define WM8995_GP11_DB_WIDTH 1 /* GP11_DB */
+#define WM8995_GP11_LVL 0x0040 /* GP11_LVL */
+#define WM8995_GP11_LVL_MASK 0x0040 /* GP11_LVL */
+#define WM8995_GP11_LVL_SHIFT 6 /* GP11_LVL */
+#define WM8995_GP11_LVL_WIDTH 1 /* GP11_LVL */
+#define WM8995_GP11_FN_MASK 0x001F /* GP11_FN - [4:0] */
+#define WM8995_GP11_FN_SHIFT 0 /* GP11_FN - [4:0] */
+#define WM8995_GP11_FN_WIDTH 5 /* GP11_FN - [4:0] */
+
+/*
+ * R1803 (0x70B) - GPIO 12
+ */
+#define WM8995_GP12_DIR 0x8000 /* GP12_DIR */
+#define WM8995_GP12_DIR_MASK 0x8000 /* GP12_DIR */
+#define WM8995_GP12_DIR_SHIFT 15 /* GP12_DIR */
+#define WM8995_GP12_DIR_WIDTH 1 /* GP12_DIR */
+#define WM8995_GP12_PU 0x4000 /* GP12_PU */
+#define WM8995_GP12_PU_MASK 0x4000 /* GP12_PU */
+#define WM8995_GP12_PU_SHIFT 14 /* GP12_PU */
+#define WM8995_GP12_PU_WIDTH 1 /* GP12_PU */
+#define WM8995_GP12_PD 0x2000 /* GP12_PD */
+#define WM8995_GP12_PD_MASK 0x2000 /* GP12_PD */
+#define WM8995_GP12_PD_SHIFT 13 /* GP12_PD */
+#define WM8995_GP12_PD_WIDTH 1 /* GP12_PD */
+#define WM8995_GP12_POL 0x0400 /* GP12_POL */
+#define WM8995_GP12_POL_MASK 0x0400 /* GP12_POL */
+#define WM8995_GP12_POL_SHIFT 10 /* GP12_POL */
+#define WM8995_GP12_POL_WIDTH 1 /* GP12_POL */
+#define WM8995_GP12_OP_CFG 0x0200 /* GP12_OP_CFG */
+#define WM8995_GP12_OP_CFG_MASK 0x0200 /* GP12_OP_CFG */
+#define WM8995_GP12_OP_CFG_SHIFT 9 /* GP12_OP_CFG */
+#define WM8995_GP12_OP_CFG_WIDTH 1 /* GP12_OP_CFG */
+#define WM8995_GP12_DB 0x0100 /* GP12_DB */
+#define WM8995_GP12_DB_MASK 0x0100 /* GP12_DB */
+#define WM8995_GP12_DB_SHIFT 8 /* GP12_DB */
+#define WM8995_GP12_DB_WIDTH 1 /* GP12_DB */
+#define WM8995_GP12_LVL 0x0040 /* GP12_LVL */
+#define WM8995_GP12_LVL_MASK 0x0040 /* GP12_LVL */
+#define WM8995_GP12_LVL_SHIFT 6 /* GP12_LVL */
+#define WM8995_GP12_LVL_WIDTH 1 /* GP12_LVL */
+#define WM8995_GP12_FN_MASK 0x001F /* GP12_FN - [4:0] */
+#define WM8995_GP12_FN_SHIFT 0 /* GP12_FN - [4:0] */
+#define WM8995_GP12_FN_WIDTH 5 /* GP12_FN - [4:0] */
+
+/*
+ * R1804 (0x70C) - GPIO 13
+ */
+#define WM8995_GP13_DIR 0x8000 /* GP13_DIR */
+#define WM8995_GP13_DIR_MASK 0x8000 /* GP13_DIR */
+#define WM8995_GP13_DIR_SHIFT 15 /* GP13_DIR */
+#define WM8995_GP13_DIR_WIDTH 1 /* GP13_DIR */
+#define WM8995_GP13_PU 0x4000 /* GP13_PU */
+#define WM8995_GP13_PU_MASK 0x4000 /* GP13_PU */
+#define WM8995_GP13_PU_SHIFT 14 /* GP13_PU */
+#define WM8995_GP13_PU_WIDTH 1 /* GP13_PU */
+#define WM8995_GP13_PD 0x2000 /* GP13_PD */
+#define WM8995_GP13_PD_MASK 0x2000 /* GP13_PD */
+#define WM8995_GP13_PD_SHIFT 13 /* GP13_PD */
+#define WM8995_GP13_PD_WIDTH 1 /* GP13_PD */
+#define WM8995_GP13_POL 0x0400 /* GP13_POL */
+#define WM8995_GP13_POL_MASK 0x0400 /* GP13_POL */
+#define WM8995_GP13_POL_SHIFT 10 /* GP13_POL */
+#define WM8995_GP13_POL_WIDTH 1 /* GP13_POL */
+#define WM8995_GP13_OP_CFG 0x0200 /* GP13_OP_CFG */
+#define WM8995_GP13_OP_CFG_MASK 0x0200 /* GP13_OP_CFG */
+#define WM8995_GP13_OP_CFG_SHIFT 9 /* GP13_OP_CFG */
+#define WM8995_GP13_OP_CFG_WIDTH 1 /* GP13_OP_CFG */
+#define WM8995_GP13_DB 0x0100 /* GP13_DB */
+#define WM8995_GP13_DB_MASK 0x0100 /* GP13_DB */
+#define WM8995_GP13_DB_SHIFT 8 /* GP13_DB */
+#define WM8995_GP13_DB_WIDTH 1 /* GP13_DB */
+#define WM8995_GP13_LVL 0x0040 /* GP13_LVL */
+#define WM8995_GP13_LVL_MASK 0x0040 /* GP13_LVL */
+#define WM8995_GP13_LVL_SHIFT 6 /* GP13_LVL */
+#define WM8995_GP13_LVL_WIDTH 1 /* GP13_LVL */
+#define WM8995_GP13_FN_MASK 0x001F /* GP13_FN - [4:0] */
+#define WM8995_GP13_FN_SHIFT 0 /* GP13_FN - [4:0] */
+#define WM8995_GP13_FN_WIDTH 5 /* GP13_FN - [4:0] */
+
+/*
+ * R1805 (0x70D) - GPIO 14
+ */
+#define WM8995_GP14_DIR 0x8000 /* GP14_DIR */
+#define WM8995_GP14_DIR_MASK 0x8000 /* GP14_DIR */
+#define WM8995_GP14_DIR_SHIFT 15 /* GP14_DIR */
+#define WM8995_GP14_DIR_WIDTH 1 /* GP14_DIR */
+#define WM8995_GP14_PU 0x4000 /* GP14_PU */
+#define WM8995_GP14_PU_MASK 0x4000 /* GP14_PU */
+#define WM8995_GP14_PU_SHIFT 14 /* GP14_PU */
+#define WM8995_GP14_PU_WIDTH 1 /* GP14_PU */
+#define WM8995_GP14_PD 0x2000 /* GP14_PD */
+#define WM8995_GP14_PD_MASK 0x2000 /* GP14_PD */
+#define WM8995_GP14_PD_SHIFT 13 /* GP14_PD */
+#define WM8995_GP14_PD_WIDTH 1 /* GP14_PD */
+#define WM8995_GP14_POL 0x0400 /* GP14_POL */
+#define WM8995_GP14_POL_MASK 0x0400 /* GP14_POL */
+#define WM8995_GP14_POL_SHIFT 10 /* GP14_POL */
+#define WM8995_GP14_POL_WIDTH 1 /* GP14_POL */
+#define WM8995_GP14_OP_CFG 0x0200 /* GP14_OP_CFG */
+#define WM8995_GP14_OP_CFG_MASK 0x0200 /* GP14_OP_CFG */
+#define WM8995_GP14_OP_CFG_SHIFT 9 /* GP14_OP_CFG */
+#define WM8995_GP14_OP_CFG_WIDTH 1 /* GP14_OP_CFG */
+#define WM8995_GP14_DB 0x0100 /* GP14_DB */
+#define WM8995_GP14_DB_MASK 0x0100 /* GP14_DB */
+#define WM8995_GP14_DB_SHIFT 8 /* GP14_DB */
+#define WM8995_GP14_DB_WIDTH 1 /* GP14_DB */
+#define WM8995_GP14_LVL 0x0040 /* GP14_LVL */
+#define WM8995_GP14_LVL_MASK 0x0040 /* GP14_LVL */
+#define WM8995_GP14_LVL_SHIFT 6 /* GP14_LVL */
+#define WM8995_GP14_LVL_WIDTH 1 /* GP14_LVL */
+#define WM8995_GP14_FN_MASK 0x001F /* GP14_FN - [4:0] */
+#define WM8995_GP14_FN_SHIFT 0 /* GP14_FN - [4:0] */
+#define WM8995_GP14_FN_WIDTH 5 /* GP14_FN - [4:0] */
+
+/*
+ * R1824 (0x720) - Pull Control (1)
+ */
+#define WM8995_DMICDAT3_PD 0x4000 /* DMICDAT3_PD */
+#define WM8995_DMICDAT3_PD_MASK 0x4000 /* DMICDAT3_PD */
+#define WM8995_DMICDAT3_PD_SHIFT 14 /* DMICDAT3_PD */
+#define WM8995_DMICDAT3_PD_WIDTH 1 /* DMICDAT3_PD */
+#define WM8995_DMICDAT2_PD 0x1000 /* DMICDAT2_PD */
+#define WM8995_DMICDAT2_PD_MASK 0x1000 /* DMICDAT2_PD */
+#define WM8995_DMICDAT2_PD_SHIFT 12 /* DMICDAT2_PD */
+#define WM8995_DMICDAT2_PD_WIDTH 1 /* DMICDAT2_PD */
+#define WM8995_DMICDAT1_PD 0x0400 /* DMICDAT1_PD */
+#define WM8995_DMICDAT1_PD_MASK 0x0400 /* DMICDAT1_PD */
+#define WM8995_DMICDAT1_PD_SHIFT 10 /* DMICDAT1_PD */
+#define WM8995_DMICDAT1_PD_WIDTH 1 /* DMICDAT1_PD */
+#define WM8995_MCLK2_PU 0x0200 /* MCLK2_PU */
+#define WM8995_MCLK2_PU_MASK 0x0200 /* MCLK2_PU */
+#define WM8995_MCLK2_PU_SHIFT 9 /* MCLK2_PU */
+#define WM8995_MCLK2_PU_WIDTH 1 /* MCLK2_PU */
+#define WM8995_MCLK2_PD 0x0100 /* MCLK2_PD */
+#define WM8995_MCLK2_PD_MASK 0x0100 /* MCLK2_PD */
+#define WM8995_MCLK2_PD_SHIFT 8 /* MCLK2_PD */
+#define WM8995_MCLK2_PD_WIDTH 1 /* MCLK2_PD */
+#define WM8995_MCLK1_PU 0x0080 /* MCLK1_PU */
+#define WM8995_MCLK1_PU_MASK 0x0080 /* MCLK1_PU */
+#define WM8995_MCLK1_PU_SHIFT 7 /* MCLK1_PU */
+#define WM8995_MCLK1_PU_WIDTH 1 /* MCLK1_PU */
+#define WM8995_MCLK1_PD 0x0040 /* MCLK1_PD */
+#define WM8995_MCLK1_PD_MASK 0x0040 /* MCLK1_PD */
+#define WM8995_MCLK1_PD_SHIFT 6 /* MCLK1_PD */
+#define WM8995_MCLK1_PD_WIDTH 1 /* MCLK1_PD */
+#define WM8995_DACDAT1_PU 0x0020 /* DACDAT1_PU */
+#define WM8995_DACDAT1_PU_MASK 0x0020 /* DACDAT1_PU */
+#define WM8995_DACDAT1_PU_SHIFT 5 /* DACDAT1_PU */
+#define WM8995_DACDAT1_PU_WIDTH 1 /* DACDAT1_PU */
+#define WM8995_DACDAT1_PD 0x0010 /* DACDAT1_PD */
+#define WM8995_DACDAT1_PD_MASK 0x0010 /* DACDAT1_PD */
+#define WM8995_DACDAT1_PD_SHIFT 4 /* DACDAT1_PD */
+#define WM8995_DACDAT1_PD_WIDTH 1 /* DACDAT1_PD */
+#define WM8995_DACLRCLK1_PU 0x0008 /* DACLRCLK1_PU */
+#define WM8995_DACLRCLK1_PU_MASK 0x0008 /* DACLRCLK1_PU */
+#define WM8995_DACLRCLK1_PU_SHIFT 3 /* DACLRCLK1_PU */
+#define WM8995_DACLRCLK1_PU_WIDTH 1 /* DACLRCLK1_PU */
+#define WM8995_DACLRCLK1_PD 0x0004 /* DACLRCLK1_PD */
+#define WM8995_DACLRCLK1_PD_MASK 0x0004 /* DACLRCLK1_PD */
+#define WM8995_DACLRCLK1_PD_SHIFT 2 /* DACLRCLK1_PD */
+#define WM8995_DACLRCLK1_PD_WIDTH 1 /* DACLRCLK1_PD */
+#define WM8995_BCLK1_PU 0x0002 /* BCLK1_PU */
+#define WM8995_BCLK1_PU_MASK 0x0002 /* BCLK1_PU */
+#define WM8995_BCLK1_PU_SHIFT 1 /* BCLK1_PU */
+#define WM8995_BCLK1_PU_WIDTH 1 /* BCLK1_PU */
+#define WM8995_BCLK1_PD 0x0001 /* BCLK1_PD */
+#define WM8995_BCLK1_PD_MASK 0x0001 /* BCLK1_PD */
+#define WM8995_BCLK1_PD_SHIFT 0 /* BCLK1_PD */
+#define WM8995_BCLK1_PD_WIDTH 1 /* BCLK1_PD */
+
+/*
+ * R1825 (0x721) - Pull Control (2)
+ */
+#define WM8995_LDO1ENA_PD 0x0010 /* LDO1ENA_PD */
+#define WM8995_LDO1ENA_PD_MASK 0x0010 /* LDO1ENA_PD */
+#define WM8995_LDO1ENA_PD_SHIFT 4 /* LDO1ENA_PD */
+#define WM8995_LDO1ENA_PD_WIDTH 1 /* LDO1ENA_PD */
+#define WM8995_MODE_PD 0x0004 /* MODE_PD */
+#define WM8995_MODE_PD_MASK 0x0004 /* MODE_PD */
+#define WM8995_MODE_PD_SHIFT 2 /* MODE_PD */
+#define WM8995_MODE_PD_WIDTH 1 /* MODE_PD */
+#define WM8995_CSNADDR_PD 0x0001 /* CSNADDR_PD */
+#define WM8995_CSNADDR_PD_MASK 0x0001 /* CSNADDR_PD */
+#define WM8995_CSNADDR_PD_SHIFT 0 /* CSNADDR_PD */
+#define WM8995_CSNADDR_PD_WIDTH 1 /* CSNADDR_PD */
+
+/*
+ * R1840 (0x730) - Interrupt Status 1
+ */
+#define WM8995_GP14_EINT 0x2000 /* GP14_EINT */
+#define WM8995_GP14_EINT_MASK 0x2000 /* GP14_EINT */
+#define WM8995_GP14_EINT_SHIFT 13 /* GP14_EINT */
+#define WM8995_GP14_EINT_WIDTH 1 /* GP14_EINT */
+#define WM8995_GP13_EINT 0x1000 /* GP13_EINT */
+#define WM8995_GP13_EINT_MASK 0x1000 /* GP13_EINT */
+#define WM8995_GP13_EINT_SHIFT 12 /* GP13_EINT */
+#define WM8995_GP13_EINT_WIDTH 1 /* GP13_EINT */
+#define WM8995_GP12_EINT 0x0800 /* GP12_EINT */
+#define WM8995_GP12_EINT_MASK 0x0800 /* GP12_EINT */
+#define WM8995_GP12_EINT_SHIFT 11 /* GP12_EINT */
+#define WM8995_GP12_EINT_WIDTH 1 /* GP12_EINT */
+#define WM8995_GP11_EINT 0x0400 /* GP11_EINT */
+#define WM8995_GP11_EINT_MASK 0x0400 /* GP11_EINT */
+#define WM8995_GP11_EINT_SHIFT 10 /* GP11_EINT */
+#define WM8995_GP11_EINT_WIDTH 1 /* GP11_EINT */
+#define WM8995_GP10_EINT 0x0200 /* GP10_EINT */
+#define WM8995_GP10_EINT_MASK 0x0200 /* GP10_EINT */
+#define WM8995_GP10_EINT_SHIFT 9 /* GP10_EINT */
+#define WM8995_GP10_EINT_WIDTH 1 /* GP10_EINT */
+#define WM8995_GP9_EINT 0x0100 /* GP9_EINT */
+#define WM8995_GP9_EINT_MASK 0x0100 /* GP9_EINT */
+#define WM8995_GP9_EINT_SHIFT 8 /* GP9_EINT */
+#define WM8995_GP9_EINT_WIDTH 1 /* GP9_EINT */
+#define WM8995_GP8_EINT 0x0080 /* GP8_EINT */
+#define WM8995_GP8_EINT_MASK 0x0080 /* GP8_EINT */
+#define WM8995_GP8_EINT_SHIFT 7 /* GP8_EINT */
+#define WM8995_GP8_EINT_WIDTH 1 /* GP8_EINT */
+#define WM8995_GP7_EINT 0x0040 /* GP7_EINT */
+#define WM8995_GP7_EINT_MASK 0x0040 /* GP7_EINT */
+#define WM8995_GP7_EINT_SHIFT 6 /* GP7_EINT */
+#define WM8995_GP7_EINT_WIDTH 1 /* GP7_EINT */
+#define WM8995_GP6_EINT 0x0020 /* GP6_EINT */
+#define WM8995_GP6_EINT_MASK 0x0020 /* GP6_EINT */
+#define WM8995_GP6_EINT_SHIFT 5 /* GP6_EINT */
+#define WM8995_GP6_EINT_WIDTH 1 /* GP6_EINT */
+#define WM8995_GP5_EINT 0x0010 /* GP5_EINT */
+#define WM8995_GP5_EINT_MASK 0x0010 /* GP5_EINT */
+#define WM8995_GP5_EINT_SHIFT 4 /* GP5_EINT */
+#define WM8995_GP5_EINT_WIDTH 1 /* GP5_EINT */
+#define WM8995_GP4_EINT 0x0008 /* GP4_EINT */
+#define WM8995_GP4_EINT_MASK 0x0008 /* GP4_EINT */
+#define WM8995_GP4_EINT_SHIFT 3 /* GP4_EINT */
+#define WM8995_GP4_EINT_WIDTH 1 /* GP4_EINT */
+#define WM8995_GP3_EINT 0x0004 /* GP3_EINT */
+#define WM8995_GP3_EINT_MASK 0x0004 /* GP3_EINT */
+#define WM8995_GP3_EINT_SHIFT 2 /* GP3_EINT */
+#define WM8995_GP3_EINT_WIDTH 1 /* GP3_EINT */
+#define WM8995_GP2_EINT 0x0002 /* GP2_EINT */
+#define WM8995_GP2_EINT_MASK 0x0002 /* GP2_EINT */
+#define WM8995_GP2_EINT_SHIFT 1 /* GP2_EINT */
+#define WM8995_GP2_EINT_WIDTH 1 /* GP2_EINT */
+#define WM8995_GP1_EINT 0x0001 /* GP1_EINT */
+#define WM8995_GP1_EINT_MASK 0x0001 /* GP1_EINT */
+#define WM8995_GP1_EINT_SHIFT 0 /* GP1_EINT */
+#define WM8995_GP1_EINT_WIDTH 1 /* GP1_EINT */
+
+/*
+ * R1841 (0x731) - Interrupt Status 2
+ */
+#define WM8995_DCS_DONE_23_EINT 0x1000 /* DCS_DONE_23_EINT */
+#define WM8995_DCS_DONE_23_EINT_MASK 0x1000 /* DCS_DONE_23_EINT */
+#define WM8995_DCS_DONE_23_EINT_SHIFT 12 /* DCS_DONE_23_EINT */
+#define WM8995_DCS_DONE_23_EINT_WIDTH 1 /* DCS_DONE_23_EINT */
+#define WM8995_DCS_DONE_01_EINT 0x0800 /* DCS_DONE_01_EINT */
+#define WM8995_DCS_DONE_01_EINT_MASK 0x0800 /* DCS_DONE_01_EINT */
+#define WM8995_DCS_DONE_01_EINT_SHIFT 11 /* DCS_DONE_01_EINT */
+#define WM8995_DCS_DONE_01_EINT_WIDTH 1 /* DCS_DONE_01_EINT */
+#define WM8995_WSEQ_DONE_EINT 0x0400 /* WSEQ_DONE_EINT */
+#define WM8995_WSEQ_DONE_EINT_MASK 0x0400 /* WSEQ_DONE_EINT */
+#define WM8995_WSEQ_DONE_EINT_SHIFT 10 /* WSEQ_DONE_EINT */
+#define WM8995_WSEQ_DONE_EINT_WIDTH 1 /* WSEQ_DONE_EINT */
+#define WM8995_FIFOS_ERR_EINT 0x0200 /* FIFOS_ERR_EINT */
+#define WM8995_FIFOS_ERR_EINT_MASK 0x0200 /* FIFOS_ERR_EINT */
+#define WM8995_FIFOS_ERR_EINT_SHIFT 9 /* FIFOS_ERR_EINT */
+#define WM8995_FIFOS_ERR_EINT_WIDTH 1 /* FIFOS_ERR_EINT */
+#define WM8995_AIF2DRC_SIG_DET_EINT 0x0100 /* AIF2DRC_SIG_DET_EINT */
+#define WM8995_AIF2DRC_SIG_DET_EINT_MASK 0x0100 /* AIF2DRC_SIG_DET_EINT */
+#define WM8995_AIF2DRC_SIG_DET_EINT_SHIFT 8 /* AIF2DRC_SIG_DET_EINT */
+#define WM8995_AIF2DRC_SIG_DET_EINT_WIDTH 1 /* AIF2DRC_SIG_DET_EINT */
+#define WM8995_AIF1DRC2_SIG_DET_EINT 0x0080 /* AIF1DRC2_SIG_DET_EINT */
+#define WM8995_AIF1DRC2_SIG_DET_EINT_MASK 0x0080 /* AIF1DRC2_SIG_DET_EINT */
+#define WM8995_AIF1DRC2_SIG_DET_EINT_SHIFT 7 /* AIF1DRC2_SIG_DET_EINT */
+#define WM8995_AIF1DRC2_SIG_DET_EINT_WIDTH 1 /* AIF1DRC2_SIG_DET_EINT */
+#define WM8995_AIF1DRC1_SIG_DET_EINT 0x0040 /* AIF1DRC1_SIG_DET_EINT */
+#define WM8995_AIF1DRC1_SIG_DET_EINT_MASK 0x0040 /* AIF1DRC1_SIG_DET_EINT */
+#define WM8995_AIF1DRC1_SIG_DET_EINT_SHIFT 6 /* AIF1DRC1_SIG_DET_EINT */
+#define WM8995_AIF1DRC1_SIG_DET_EINT_WIDTH 1 /* AIF1DRC1_SIG_DET_EINT */
+#define WM8995_SRC2_LOCK_EINT 0x0020 /* SRC2_LOCK_EINT */
+#define WM8995_SRC2_LOCK_EINT_MASK 0x0020 /* SRC2_LOCK_EINT */
+#define WM8995_SRC2_LOCK_EINT_SHIFT 5 /* SRC2_LOCK_EINT */
+#define WM8995_SRC2_LOCK_EINT_WIDTH 1 /* SRC2_LOCK_EINT */
+#define WM8995_SRC1_LOCK_EINT 0x0010 /* SRC1_LOCK_EINT */
+#define WM8995_SRC1_LOCK_EINT_MASK 0x0010 /* SRC1_LOCK_EINT */
+#define WM8995_SRC1_LOCK_EINT_SHIFT 4 /* SRC1_LOCK_EINT */
+#define WM8995_SRC1_LOCK_EINT_WIDTH 1 /* SRC1_LOCK_EINT */
+#define WM8995_FLL2_LOCK_EINT 0x0008 /* FLL2_LOCK_EINT */
+#define WM8995_FLL2_LOCK_EINT_MASK 0x0008 /* FLL2_LOCK_EINT */
+#define WM8995_FLL2_LOCK_EINT_SHIFT 3 /* FLL2_LOCK_EINT */
+#define WM8995_FLL2_LOCK_EINT_WIDTH 1 /* FLL2_LOCK_EINT */
+#define WM8995_FLL1_LOCK_EINT 0x0004 /* FLL1_LOCK_EINT */
+#define WM8995_FLL1_LOCK_EINT_MASK 0x0004 /* FLL1_LOCK_EINT */
+#define WM8995_FLL1_LOCK_EINT_SHIFT 2 /* FLL1_LOCK_EINT */
+#define WM8995_FLL1_LOCK_EINT_WIDTH 1 /* FLL1_LOCK_EINT */
+#define WM8995_HP_DONE_EINT 0x0002 /* HP_DONE_EINT */
+#define WM8995_HP_DONE_EINT_MASK 0x0002 /* HP_DONE_EINT */
+#define WM8995_HP_DONE_EINT_SHIFT 1 /* HP_DONE_EINT */
+#define WM8995_HP_DONE_EINT_WIDTH 1 /* HP_DONE_EINT */
+#define WM8995_MICD_EINT 0x0001 /* MICD_EINT */
+#define WM8995_MICD_EINT_MASK 0x0001 /* MICD_EINT */
+#define WM8995_MICD_EINT_SHIFT 0 /* MICD_EINT */
+#define WM8995_MICD_EINT_WIDTH 1 /* MICD_EINT */
+
+/*
+ * R1842 (0x732) - Interrupt Raw Status 2
+ */
+#define WM8995_DCS_DONE_23_STS 0x1000 /* DCS_DONE_23_STS */
+#define WM8995_DCS_DONE_23_STS_MASK 0x1000 /* DCS_DONE_23_STS */
+#define WM8995_DCS_DONE_23_STS_SHIFT 12 /* DCS_DONE_23_STS */
+#define WM8995_DCS_DONE_23_STS_WIDTH 1 /* DCS_DONE_23_STS */
+#define WM8995_DCS_DONE_01_STS 0x0800 /* DCS_DONE_01_STS */
+#define WM8995_DCS_DONE_01_STS_MASK 0x0800 /* DCS_DONE_01_STS */
+#define WM8995_DCS_DONE_01_STS_SHIFT 11 /* DCS_DONE_01_STS */
+#define WM8995_DCS_DONE_01_STS_WIDTH 1 /* DCS_DONE_01_STS */
+#define WM8995_WSEQ_DONE_STS 0x0400 /* WSEQ_DONE_STS */
+#define WM8995_WSEQ_DONE_STS_MASK 0x0400 /* WSEQ_DONE_STS */
+#define WM8995_WSEQ_DONE_STS_SHIFT 10 /* WSEQ_DONE_STS */
+#define WM8995_WSEQ_DONE_STS_WIDTH 1 /* WSEQ_DONE_STS */
+#define WM8995_FIFOS_ERR_STS 0x0200 /* FIFOS_ERR_STS */
+#define WM8995_FIFOS_ERR_STS_MASK 0x0200 /* FIFOS_ERR_STS */
+#define WM8995_FIFOS_ERR_STS_SHIFT 9 /* FIFOS_ERR_STS */
+#define WM8995_FIFOS_ERR_STS_WIDTH 1 /* FIFOS_ERR_STS */
+#define WM8995_AIF2DRC_SIG_DET_STS 0x0100 /* AIF2DRC_SIG_DET_STS */
+#define WM8995_AIF2DRC_SIG_DET_STS_MASK 0x0100 /* AIF2DRC_SIG_DET_STS */
+#define WM8995_AIF2DRC_SIG_DET_STS_SHIFT 8 /* AIF2DRC_SIG_DET_STS */
+#define WM8995_AIF2DRC_SIG_DET_STS_WIDTH 1 /* AIF2DRC_SIG_DET_STS */
+#define WM8995_AIF1DRC2_SIG_DET_STS 0x0080 /* AIF1DRC2_SIG_DET_STS */
+#define WM8995_AIF1DRC2_SIG_DET_STS_MASK 0x0080 /* AIF1DRC2_SIG_DET_STS */
+#define WM8995_AIF1DRC2_SIG_DET_STS_SHIFT 7 /* AIF1DRC2_SIG_DET_STS */
+#define WM8995_AIF1DRC2_SIG_DET_STS_WIDTH 1 /* AIF1DRC2_SIG_DET_STS */
+#define WM8995_AIF1DRC1_SIG_DET_STS 0x0040 /* AIF1DRC1_SIG_DET_STS */
+#define WM8995_AIF1DRC1_SIG_DET_STS_MASK 0x0040 /* AIF1DRC1_SIG_DET_STS */
+#define WM8995_AIF1DRC1_SIG_DET_STS_SHIFT 6 /* AIF1DRC1_SIG_DET_STS */
+#define WM8995_AIF1DRC1_SIG_DET_STS_WIDTH 1 /* AIF1DRC1_SIG_DET_STS */
+#define WM8995_SRC2_LOCK_STS 0x0020 /* SRC2_LOCK_STS */
+#define WM8995_SRC2_LOCK_STS_MASK 0x0020 /* SRC2_LOCK_STS */
+#define WM8995_SRC2_LOCK_STS_SHIFT 5 /* SRC2_LOCK_STS */
+#define WM8995_SRC2_LOCK_STS_WIDTH 1 /* SRC2_LOCK_STS */
+#define WM8995_SRC1_LOCK_STS 0x0010 /* SRC1_LOCK_STS */
+#define WM8995_SRC1_LOCK_STS_MASK 0x0010 /* SRC1_LOCK_STS */
+#define WM8995_SRC1_LOCK_STS_SHIFT 4 /* SRC1_LOCK_STS */
+#define WM8995_SRC1_LOCK_STS_WIDTH 1 /* SRC1_LOCK_STS */
+#define WM8995_FLL2_LOCK_STS 0x0008 /* FLL2_LOCK_STS */
+#define WM8995_FLL2_LOCK_STS_MASK 0x0008 /* FLL2_LOCK_STS */
+#define WM8995_FLL2_LOCK_STS_SHIFT 3 /* FLL2_LOCK_STS */
+#define WM8995_FLL2_LOCK_STS_WIDTH 1 /* FLL2_LOCK_STS */
+#define WM8995_FLL1_LOCK_STS 0x0004 /* FLL1_LOCK_STS */
+#define WM8995_FLL1_LOCK_STS_MASK 0x0004 /* FLL1_LOCK_STS */
+#define WM8995_FLL1_LOCK_STS_SHIFT 2 /* FLL1_LOCK_STS */
+#define WM8995_FLL1_LOCK_STS_WIDTH 1 /* FLL1_LOCK_STS */
+
+/*
+ * R1848 (0x738) - Interrupt Status 1 Mask
+ */
+#define WM8995_IM_GP14_EINT 0x2000 /* IM_GP14_EINT */
+#define WM8995_IM_GP14_EINT_MASK 0x2000 /* IM_GP14_EINT */
+#define WM8995_IM_GP14_EINT_SHIFT 13 /* IM_GP14_EINT */
+#define WM8995_IM_GP14_EINT_WIDTH 1 /* IM_GP14_EINT */
+#define WM8995_IM_GP13_EINT 0x1000 /* IM_GP13_EINT */
+#define WM8995_IM_GP13_EINT_MASK 0x1000 /* IM_GP13_EINT */
+#define WM8995_IM_GP13_EINT_SHIFT 12 /* IM_GP13_EINT */
+#define WM8995_IM_GP13_EINT_WIDTH 1 /* IM_GP13_EINT */
+#define WM8995_IM_GP12_EINT 0x0800 /* IM_GP12_EINT */
+#define WM8995_IM_GP12_EINT_MASK 0x0800 /* IM_GP12_EINT */
+#define WM8995_IM_GP12_EINT_SHIFT 11 /* IM_GP12_EINT */
+#define WM8995_IM_GP12_EINT_WIDTH 1 /* IM_GP12_EINT */
+#define WM8995_IM_GP11_EINT 0x0400 /* IM_GP11_EINT */
+#define WM8995_IM_GP11_EINT_MASK 0x0400 /* IM_GP11_EINT */
+#define WM8995_IM_GP11_EINT_SHIFT 10 /* IM_GP11_EINT */
+#define WM8995_IM_GP11_EINT_WIDTH 1 /* IM_GP11_EINT */
+#define WM8995_IM_GP10_EINT 0x0200 /* IM_GP10_EINT */
+#define WM8995_IM_GP10_EINT_MASK 0x0200 /* IM_GP10_EINT */
+#define WM8995_IM_GP10_EINT_SHIFT 9 /* IM_GP10_EINT */
+#define WM8995_IM_GP10_EINT_WIDTH 1 /* IM_GP10_EINT */
+#define WM8995_IM_GP9_EINT 0x0100 /* IM_GP9_EINT */
+#define WM8995_IM_GP9_EINT_MASK 0x0100 /* IM_GP9_EINT */
+#define WM8995_IM_GP9_EINT_SHIFT 8 /* IM_GP9_EINT */
+#define WM8995_IM_GP9_EINT_WIDTH 1 /* IM_GP9_EINT */
+#define WM8995_IM_GP8_EINT 0x0080 /* IM_GP8_EINT */
+#define WM8995_IM_GP8_EINT_MASK 0x0080 /* IM_GP8_EINT */
+#define WM8995_IM_GP8_EINT_SHIFT 7 /* IM_GP8_EINT */
+#define WM8995_IM_GP8_EINT_WIDTH 1 /* IM_GP8_EINT */
+#define WM8995_IM_GP7_EINT 0x0040 /* IM_GP7_EINT */
+#define WM8995_IM_GP7_EINT_MASK 0x0040 /* IM_GP7_EINT */
+#define WM8995_IM_GP7_EINT_SHIFT 6 /* IM_GP7_EINT */
+#define WM8995_IM_GP7_EINT_WIDTH 1 /* IM_GP7_EINT */
+#define WM8995_IM_GP6_EINT 0x0020 /* IM_GP6_EINT */
+#define WM8995_IM_GP6_EINT_MASK 0x0020 /* IM_GP6_EINT */
+#define WM8995_IM_GP6_EINT_SHIFT 5 /* IM_GP6_EINT */
+#define WM8995_IM_GP6_EINT_WIDTH 1 /* IM_GP6_EINT */
+#define WM8995_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */
+#define WM8995_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */
+#define WM8995_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */
+#define WM8995_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */
+#define WM8995_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */
+#define WM8995_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */
+#define WM8995_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */
+#define WM8995_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */
+#define WM8995_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */
+#define WM8995_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */
+#define WM8995_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */
+#define WM8995_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */
+#define WM8995_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */
+#define WM8995_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */
+#define WM8995_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */
+#define WM8995_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */
+#define WM8995_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */
+#define WM8995_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */
+#define WM8995_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */
+#define WM8995_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */
+
+/*
+ * R1849 (0x739) - Interrupt Status 2 Mask
+ */
+#define WM8995_IM_DCS_DONE_23_EINT 0x1000 /* IM_DCS_DONE_23_EINT */
+#define WM8995_IM_DCS_DONE_23_EINT_MASK 0x1000 /* IM_DCS_DONE_23_EINT */
+#define WM8995_IM_DCS_DONE_23_EINT_SHIFT 12 /* IM_DCS_DONE_23_EINT */
+#define WM8995_IM_DCS_DONE_23_EINT_WIDTH 1 /* IM_DCS_DONE_23_EINT */
+#define WM8995_IM_DCS_DONE_01_EINT 0x0800 /* IM_DCS_DONE_01_EINT */
+#define WM8995_IM_DCS_DONE_01_EINT_MASK 0x0800 /* IM_DCS_DONE_01_EINT */
+#define WM8995_IM_DCS_DONE_01_EINT_SHIFT 11 /* IM_DCS_DONE_01_EINT */
+#define WM8995_IM_DCS_DONE_01_EINT_WIDTH 1 /* IM_DCS_DONE_01_EINT */
+#define WM8995_IM_WSEQ_DONE_EINT 0x0400 /* IM_WSEQ_DONE_EINT */
+#define WM8995_IM_WSEQ_DONE_EINT_MASK 0x0400 /* IM_WSEQ_DONE_EINT */
+#define WM8995_IM_WSEQ_DONE_EINT_SHIFT 10 /* IM_WSEQ_DONE_EINT */
+#define WM8995_IM_WSEQ_DONE_EINT_WIDTH 1 /* IM_WSEQ_DONE_EINT */
+#define WM8995_IM_FIFOS_ERR_EINT 0x0200 /* IM_FIFOS_ERR_EINT */
+#define WM8995_IM_FIFOS_ERR_EINT_MASK 0x0200 /* IM_FIFOS_ERR_EINT */
+#define WM8995_IM_FIFOS_ERR_EINT_SHIFT 9 /* IM_FIFOS_ERR_EINT */
+#define WM8995_IM_FIFOS_ERR_EINT_WIDTH 1 /* IM_FIFOS_ERR_EINT */
+#define WM8995_IM_AIF2DRC_SIG_DET_EINT 0x0100 /* IM_AIF2DRC_SIG_DET_EINT */
+#define WM8995_IM_AIF2DRC_SIG_DET_EINT_MASK 0x0100 /* IM_AIF2DRC_SIG_DET_EINT */
+#define WM8995_IM_AIF2DRC_SIG_DET_EINT_SHIFT 8 /* IM_AIF2DRC_SIG_DET_EINT */
+#define WM8995_IM_AIF2DRC_SIG_DET_EINT_WIDTH 1 /* IM_AIF2DRC_SIG_DET_EINT */
+#define WM8995_IM_AIF1DRC2_SIG_DET_EINT 0x0080 /* IM_AIF1DRC2_SIG_DET_EINT */
+#define WM8995_IM_AIF1DRC2_SIG_DET_EINT_MASK 0x0080 /* IM_AIF1DRC2_SIG_DET_EINT */
+#define WM8995_IM_AIF1DRC2_SIG_DET_EINT_SHIFT 7 /* IM_AIF1DRC2_SIG_DET_EINT */
+#define WM8995_IM_AIF1DRC2_SIG_DET_EINT_WIDTH 1 /* IM_AIF1DRC2_SIG_DET_EINT */
+#define WM8995_IM_AIF1DRC1_SIG_DET_EINT 0x0040 /* IM_AIF1DRC1_SIG_DET_EINT */
+#define WM8995_IM_AIF1DRC1_SIG_DET_EINT_MASK 0x0040 /* IM_AIF1DRC1_SIG_DET_EINT */
+#define WM8995_IM_AIF1DRC1_SIG_DET_EINT_SHIFT 6 /* IM_AIF1DRC1_SIG_DET_EINT */
+#define WM8995_IM_AIF1DRC1_SIG_DET_EINT_WIDTH 1 /* IM_AIF1DRC1_SIG_DET_EINT */
+#define WM8995_IM_SRC2_LOCK_EINT 0x0020 /* IM_SRC2_LOCK_EINT */
+#define WM8995_IM_SRC2_LOCK_EINT_MASK 0x0020 /* IM_SRC2_LOCK_EINT */
+#define WM8995_IM_SRC2_LOCK_EINT_SHIFT 5 /* IM_SRC2_LOCK_EINT */
+#define WM8995_IM_SRC2_LOCK_EINT_WIDTH 1 /* IM_SRC2_LOCK_EINT */
+#define WM8995_IM_SRC1_LOCK_EINT 0x0010 /* IM_SRC1_LOCK_EINT */
+#define WM8995_IM_SRC1_LOCK_EINT_MASK 0x0010 /* IM_SRC1_LOCK_EINT */
+#define WM8995_IM_SRC1_LOCK_EINT_SHIFT 4 /* IM_SRC1_LOCK_EINT */
+#define WM8995_IM_SRC1_LOCK_EINT_WIDTH 1 /* IM_SRC1_LOCK_EINT */
+#define WM8995_IM_FLL2_LOCK_EINT 0x0008 /* IM_FLL2_LOCK_EINT */
+#define WM8995_IM_FLL2_LOCK_EINT_MASK 0x0008 /* IM_FLL2_LOCK_EINT */
+#define WM8995_IM_FLL2_LOCK_EINT_SHIFT 3 /* IM_FLL2_LOCK_EINT */
+#define WM8995_IM_FLL2_LOCK_EINT_WIDTH 1 /* IM_FLL2_LOCK_EINT */
+#define WM8995_IM_FLL1_LOCK_EINT 0x0004 /* IM_FLL1_LOCK_EINT */
+#define WM8995_IM_FLL1_LOCK_EINT_MASK 0x0004 /* IM_FLL1_LOCK_EINT */
+#define WM8995_IM_FLL1_LOCK_EINT_SHIFT 2 /* IM_FLL1_LOCK_EINT */
+#define WM8995_IM_FLL1_LOCK_EINT_WIDTH 1 /* IM_FLL1_LOCK_EINT */
+#define WM8995_IM_HP_DONE_EINT 0x0002 /* IM_HP_DONE_EINT */
+#define WM8995_IM_HP_DONE_EINT_MASK 0x0002 /* IM_HP_DONE_EINT */
+#define WM8995_IM_HP_DONE_EINT_SHIFT 1 /* IM_HP_DONE_EINT */
+#define WM8995_IM_HP_DONE_EINT_WIDTH 1 /* IM_HP_DONE_EINT */
+#define WM8995_IM_MICD_EINT 0x0001 /* IM_MICD_EINT */
+#define WM8995_IM_MICD_EINT_MASK 0x0001 /* IM_MICD_EINT */
+#define WM8995_IM_MICD_EINT_SHIFT 0 /* IM_MICD_EINT */
+#define WM8995_IM_MICD_EINT_WIDTH 1 /* IM_MICD_EINT */
+
+/*
+ * R1856 (0x740) - Interrupt Control
+ */
+#define WM8995_IM_IRQ 0x0001 /* IM_IRQ */
+#define WM8995_IM_IRQ_MASK 0x0001 /* IM_IRQ */
+#define WM8995_IM_IRQ_SHIFT 0 /* IM_IRQ */
+#define WM8995_IM_IRQ_WIDTH 1 /* IM_IRQ */
+
+/*
+ * R2048 (0x800) - Left PDM Speaker 1
+ */
+#define WM8995_SPK1L_ENA 0x0010 /* SPK1L_ENA */
+#define WM8995_SPK1L_ENA_MASK 0x0010 /* SPK1L_ENA */
+#define WM8995_SPK1L_ENA_SHIFT 4 /* SPK1L_ENA */
+#define WM8995_SPK1L_ENA_WIDTH 1 /* SPK1L_ENA */
+#define WM8995_SPK1L_MUTE 0x0008 /* SPK1L_MUTE */
+#define WM8995_SPK1L_MUTE_MASK 0x0008 /* SPK1L_MUTE */
+#define WM8995_SPK1L_MUTE_SHIFT 3 /* SPK1L_MUTE */
+#define WM8995_SPK1L_MUTE_WIDTH 1 /* SPK1L_MUTE */
+#define WM8995_SPK1L_MUTE_ZC 0x0004 /* SPK1L_MUTE_ZC */
+#define WM8995_SPK1L_MUTE_ZC_MASK 0x0004 /* SPK1L_MUTE_ZC */
+#define WM8995_SPK1L_MUTE_ZC_SHIFT 2 /* SPK1L_MUTE_ZC */
+#define WM8995_SPK1L_MUTE_ZC_WIDTH 1 /* SPK1L_MUTE_ZC */
+#define WM8995_SPK1L_SRC_MASK 0x0003 /* SPK1L_SRC - [1:0] */
+#define WM8995_SPK1L_SRC_SHIFT 0 /* SPK1L_SRC - [1:0] */
+#define WM8995_SPK1L_SRC_WIDTH 2 /* SPK1L_SRC - [1:0] */
+
+/*
+ * R2049 (0x801) - Right PDM Speaker 1
+ */
+#define WM8995_SPK1R_ENA 0x0010 /* SPK1R_ENA */
+#define WM8995_SPK1R_ENA_MASK 0x0010 /* SPK1R_ENA */
+#define WM8995_SPK1R_ENA_SHIFT 4 /* SPK1R_ENA */
+#define WM8995_SPK1R_ENA_WIDTH 1 /* SPK1R_ENA */
+#define WM8995_SPK1R_MUTE 0x0008 /* SPK1R_MUTE */
+#define WM8995_SPK1R_MUTE_MASK 0x0008 /* SPK1R_MUTE */
+#define WM8995_SPK1R_MUTE_SHIFT 3 /* SPK1R_MUTE */
+#define WM8995_SPK1R_MUTE_WIDTH 1 /* SPK1R_MUTE */
+#define WM8995_SPK1R_MUTE_ZC 0x0004 /* SPK1R_MUTE_ZC */
+#define WM8995_SPK1R_MUTE_ZC_MASK 0x0004 /* SPK1R_MUTE_ZC */
+#define WM8995_SPK1R_MUTE_ZC_SHIFT 2 /* SPK1R_MUTE_ZC */
+#define WM8995_SPK1R_MUTE_ZC_WIDTH 1 /* SPK1R_MUTE_ZC */
+#define WM8995_SPK1R_SRC_MASK 0x0003 /* SPK1R_SRC - [1:0] */
+#define WM8995_SPK1R_SRC_SHIFT 0 /* SPK1R_SRC - [1:0] */
+#define WM8995_SPK1R_SRC_WIDTH 2 /* SPK1R_SRC - [1:0] */
+
+/*
+ * R2050 (0x802) - PDM Speaker 1 Mute Sequence
+ */
+#define WM8995_SPK1_MUTE_SEQ1_MASK 0x00FF /* SPK1_MUTE_SEQ1 - [7:0] */
+#define WM8995_SPK1_MUTE_SEQ1_SHIFT 0 /* SPK1_MUTE_SEQ1 - [7:0] */
+#define WM8995_SPK1_MUTE_SEQ1_WIDTH 8 /* SPK1_MUTE_SEQ1 - [7:0] */
+
+/*
+ * R2056 (0x808) - Left PDM Speaker 2
+ */
+#define WM8995_SPK2L_ENA 0x0010 /* SPK2L_ENA */
+#define WM8995_SPK2L_ENA_MASK 0x0010 /* SPK2L_ENA */
+#define WM8995_SPK2L_ENA_SHIFT 4 /* SPK2L_ENA */
+#define WM8995_SPK2L_ENA_WIDTH 1 /* SPK2L_ENA */
+#define WM8995_SPK2L_MUTE 0x0008 /* SPK2L_MUTE */
+#define WM8995_SPK2L_MUTE_MASK 0x0008 /* SPK2L_MUTE */
+#define WM8995_SPK2L_MUTE_SHIFT 3 /* SPK2L_MUTE */
+#define WM8995_SPK2L_MUTE_WIDTH 1 /* SPK2L_MUTE */
+#define WM8995_SPK2L_MUTE_ZC 0x0004 /* SPK2L_MUTE_ZC */
+#define WM8995_SPK2L_MUTE_ZC_MASK 0x0004 /* SPK2L_MUTE_ZC */
+#define WM8995_SPK2L_MUTE_ZC_SHIFT 2 /* SPK2L_MUTE_ZC */
+#define WM8995_SPK2L_MUTE_ZC_WIDTH 1 /* SPK2L_MUTE_ZC */
+#define WM8995_SPK2L_SRC_MASK 0x0003 /* SPK2L_SRC - [1:0] */
+#define WM8995_SPK2L_SRC_SHIFT 0 /* SPK2L_SRC - [1:0] */
+#define WM8995_SPK2L_SRC_WIDTH 2 /* SPK2L_SRC - [1:0] */
+
+/*
+ * R2057 (0x809) - Right PDM Speaker 2
+ */
+#define WM8995_SPK2R_ENA 0x0010 /* SPK2R_ENA */
+#define WM8995_SPK2R_ENA_MASK 0x0010 /* SPK2R_ENA */
+#define WM8995_SPK2R_ENA_SHIFT 4 /* SPK2R_ENA */
+#define WM8995_SPK2R_ENA_WIDTH 1 /* SPK2R_ENA */
+#define WM8995_SPK2R_MUTE 0x0008 /* SPK2R_MUTE */
+#define WM8995_SPK2R_MUTE_MASK 0x0008 /* SPK2R_MUTE */
+#define WM8995_SPK2R_MUTE_SHIFT 3 /* SPK2R_MUTE */
+#define WM8995_SPK2R_MUTE_WIDTH 1 /* SPK2R_MUTE */
+#define WM8995_SPK2R_MUTE_ZC 0x0004 /* SPK2R_MUTE_ZC */
+#define WM8995_SPK2R_MUTE_ZC_MASK 0x0004 /* SPK2R_MUTE_ZC */
+#define WM8995_SPK2R_MUTE_ZC_SHIFT 2 /* SPK2R_MUTE_ZC */
+#define WM8995_SPK2R_MUTE_ZC_WIDTH 1 /* SPK2R_MUTE_ZC */
+#define WM8995_SPK2R_SRC_MASK 0x0003 /* SPK2R_SRC - [1:0] */
+#define WM8995_SPK2R_SRC_SHIFT 0 /* SPK2R_SRC - [1:0] */
+#define WM8995_SPK2R_SRC_WIDTH 2 /* SPK2R_SRC - [1:0] */
+
+/*
+ * R2058 (0x80A) - PDM Speaker 2 Mute Sequence
+ */
+#define WM8995_SPK2_MUTE_SEQ1_MASK 0x00FF /* SPK2_MUTE_SEQ1 - [7:0] */
+#define WM8995_SPK2_MUTE_SEQ1_SHIFT 0 /* SPK2_MUTE_SEQ1 - [7:0] */
+#define WM8995_SPK2_MUTE_SEQ1_WIDTH 8 /* SPK2_MUTE_SEQ1 - [7:0] */
+
+#define WM8995_CLASS_W_SWITCH(xname, reg, shift, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_dapm_get_volsw, .put = wm8995_put_class_w, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) \
+}
+
+struct wm8995_reg_access {
+ u16 read;
+ u16 write;
+ u16 vol;
+};
+
+/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */
+enum clk_src {
+ WM8995_SYSCLK_MCLK1 = 1,
+ WM8995_SYSCLK_MCLK2,
+ WM8995_SYSCLK_FLL1,
+ WM8995_SYSCLK_FLL2,
+ WM8995_SYSCLK_OPCLK
+};
+
+#define WM8995_FLL1 1
+#define WM8995_FLL2 2
+
+#define WM8995_FLL_SRC_MCLK1 1
+#define WM8995_FLL_SRC_MCLK2 2
+#define WM8995_FLL_SRC_LRCLK 3
+#define WM8995_FLL_SRC_BCLK 4
+
+#endif /* _WM8995_H */
diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c
new file mode 100644
index 00000000..91c6b39d
--- /dev/null
+++ b/sound/soc/codecs/wm9081.c
@@ -0,0 +1,1395 @@
+/*
+ * wm9081.c -- WM9081 ALSA SoC Audio driver
+ *
+ * Author: Mark Brown
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <sound/wm9081.h>
+#include "wm9081.h"
+
+static u16 wm9081_reg_defaults[] = {
+ 0x0000, /* R0 - Software Reset */
+ 0x0000, /* R1 */
+ 0x00B9, /* R2 - Analogue Lineout */
+ 0x00B9, /* R3 - Analogue Speaker PGA */
+ 0x0001, /* R4 - VMID Control */
+ 0x0068, /* R5 - Bias Control 1 */
+ 0x0000, /* R6 */
+ 0x0000, /* R7 - Analogue Mixer */
+ 0x0000, /* R8 - Anti Pop Control */
+ 0x01DB, /* R9 - Analogue Speaker 1 */
+ 0x0018, /* R10 - Analogue Speaker 2 */
+ 0x0180, /* R11 - Power Management */
+ 0x0000, /* R12 - Clock Control 1 */
+ 0x0038, /* R13 - Clock Control 2 */
+ 0x4000, /* R14 - Clock Control 3 */
+ 0x0000, /* R15 */
+ 0x0000, /* R16 - FLL Control 1 */
+ 0x0200, /* R17 - FLL Control 2 */
+ 0x0000, /* R18 - FLL Control 3 */
+ 0x0204, /* R19 - FLL Control 4 */
+ 0x0000, /* R20 - FLL Control 5 */
+ 0x0000, /* R21 */
+ 0x0000, /* R22 - Audio Interface 1 */
+ 0x0002, /* R23 - Audio Interface 2 */
+ 0x0008, /* R24 - Audio Interface 3 */
+ 0x0022, /* R25 - Audio Interface 4 */
+ 0x0000, /* R26 - Interrupt Status */
+ 0x0006, /* R27 - Interrupt Status Mask */
+ 0x0000, /* R28 - Interrupt Polarity */
+ 0x0000, /* R29 - Interrupt Control */
+ 0x00C0, /* R30 - DAC Digital 1 */
+ 0x0008, /* R31 - DAC Digital 2 */
+ 0x09AF, /* R32 - DRC 1 */
+ 0x4201, /* R33 - DRC 2 */
+ 0x0000, /* R34 - DRC 3 */
+ 0x0000, /* R35 - DRC 4 */
+ 0x0000, /* R36 */
+ 0x0000, /* R37 */
+ 0x0000, /* R38 - Write Sequencer 1 */
+ 0x0000, /* R39 - Write Sequencer 2 */
+ 0x0002, /* R40 - MW Slave 1 */
+ 0x0000, /* R41 */
+ 0x0000, /* R42 - EQ 1 */
+ 0x0000, /* R43 - EQ 2 */
+ 0x0FCA, /* R44 - EQ 3 */
+ 0x0400, /* R45 - EQ 4 */
+ 0x00B8, /* R46 - EQ 5 */
+ 0x1EB5, /* R47 - EQ 6 */
+ 0xF145, /* R48 - EQ 7 */
+ 0x0B75, /* R49 - EQ 8 */
+ 0x01C5, /* R50 - EQ 9 */
+ 0x169E, /* R51 - EQ 10 */
+ 0xF829, /* R52 - EQ 11 */
+ 0x07AD, /* R53 - EQ 12 */
+ 0x1103, /* R54 - EQ 13 */
+ 0x1C58, /* R55 - EQ 14 */
+ 0xF373, /* R56 - EQ 15 */
+ 0x0A54, /* R57 - EQ 16 */
+ 0x0558, /* R58 - EQ 17 */
+ 0x0564, /* R59 - EQ 18 */
+ 0x0559, /* R60 - EQ 19 */
+ 0x4000, /* R61 - EQ 20 */
+};
+
+static struct {
+ int ratio;
+ int clk_sys_rate;
+} clk_sys_rates[] = {
+ { 64, 0 },
+ { 128, 1 },
+ { 192, 2 },
+ { 256, 3 },
+ { 384, 4 },
+ { 512, 5 },
+ { 768, 6 },
+ { 1024, 7 },
+ { 1408, 8 },
+ { 1536, 9 },
+};
+
+static struct {
+ int rate;
+ int sample_rate;
+} sample_rates[] = {
+ { 8000, 0 },
+ { 11025, 1 },
+ { 12000, 2 },
+ { 16000, 3 },
+ { 22050, 4 },
+ { 24000, 5 },
+ { 32000, 6 },
+ { 44100, 7 },
+ { 48000, 8 },
+ { 88200, 9 },
+ { 96000, 10 },
+};
+
+static struct {
+ int div; /* *10 due to .5s */
+ int bclk_div;
+} bclk_divs[] = {
+ { 10, 0 },
+ { 15, 1 },
+ { 20, 2 },
+ { 30, 3 },
+ { 40, 4 },
+ { 50, 5 },
+ { 55, 6 },
+ { 60, 7 },
+ { 80, 8 },
+ { 100, 9 },
+ { 110, 10 },
+ { 120, 11 },
+ { 160, 12 },
+ { 200, 13 },
+ { 220, 14 },
+ { 240, 15 },
+ { 250, 16 },
+ { 300, 17 },
+ { 320, 18 },
+ { 440, 19 },
+ { 480, 20 },
+};
+
+struct wm9081_priv {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+ int sysclk_source;
+ int mclk_rate;
+ int sysclk_rate;
+ int fs;
+ int bclk;
+ int master;
+ int fll_fref;
+ int fll_fout;
+ int tdm_width;
+ struct wm9081_pdata pdata;
+};
+
+static int wm9081_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case WM9081_SOFTWARE_RESET:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int wm9081_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM9081_SOFTWARE_RESET, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(drc_in_tlv, -4500, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_out_tlv, -2250, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_min_tlv, -1800, 600, 0);
+static unsigned int drc_max_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 0, TLV_DB_SCALE_ITEM(1200, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(1800, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(drc_qr_tlv, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -300, 50, 0);
+
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+
+static const char *drc_high_text[] = {
+ "1",
+ "1/2",
+ "1/4",
+ "1/8",
+ "1/16",
+ "0",
+};
+
+static const struct soc_enum drc_high =
+ SOC_ENUM_SINGLE(WM9081_DRC_3, 3, 6, drc_high_text);
+
+static const char *drc_low_text[] = {
+ "1",
+ "1/2",
+ "1/4",
+ "1/8",
+ "0",
+};
+
+static const struct soc_enum drc_low =
+ SOC_ENUM_SINGLE(WM9081_DRC_3, 0, 5, drc_low_text);
+
+static const char *drc_atk_text[] = {
+ "181us",
+ "181us",
+ "363us",
+ "726us",
+ "1.45ms",
+ "2.9ms",
+ "5.8ms",
+ "11.6ms",
+ "23.2ms",
+ "46.4ms",
+ "92.8ms",
+ "185.6ms",
+};
+
+static const struct soc_enum drc_atk =
+ SOC_ENUM_SINGLE(WM9081_DRC_2, 12, 12, drc_atk_text);
+
+static const char *drc_dcy_text[] = {
+ "186ms",
+ "372ms",
+ "743ms",
+ "1.49s",
+ "2.97s",
+ "5.94s",
+ "11.89s",
+ "23.78s",
+ "47.56s",
+};
+
+static const struct soc_enum drc_dcy =
+ SOC_ENUM_SINGLE(WM9081_DRC_2, 8, 9, drc_dcy_text);
+
+static const char *drc_qr_dcy_text[] = {
+ "0.725ms",
+ "1.45ms",
+ "5.8ms",
+};
+
+static const struct soc_enum drc_qr_dcy =
+ SOC_ENUM_SINGLE(WM9081_DRC_2, 4, 3, drc_qr_dcy_text);
+
+static const char *dac_deemph_text[] = {
+ "None",
+ "32kHz",
+ "44.1kHz",
+ "48kHz",
+};
+
+static const struct soc_enum dac_deemph =
+ SOC_ENUM_SINGLE(WM9081_DAC_DIGITAL_2, 1, 4, dac_deemph_text);
+
+static const char *speaker_mode_text[] = {
+ "Class D",
+ "Class AB",
+};
+
+static const struct soc_enum speaker_mode =
+ SOC_ENUM_SINGLE(WM9081_ANALOGUE_SPEAKER_2, 6, 2, speaker_mode_text);
+
+static int speaker_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg;
+
+ reg = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_2);
+ if (reg & WM9081_SPK_MODE)
+ ucontrol->value.integer.value[0] = 1;
+ else
+ ucontrol->value.integer.value[0] = 0;
+
+ return 0;
+}
+
+/*
+ * Stop any attempts to change speaker mode while the speaker is enabled.
+ *
+ * We also have some special anti-pop controls dependent on speaker
+ * mode which must be changed along with the mode.
+ */
+static int speaker_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg_pwr = snd_soc_read(codec, WM9081_POWER_MANAGEMENT);
+ unsigned int reg2 = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_2);
+
+ /* Are we changing anything? */
+ if (ucontrol->value.integer.value[0] ==
+ ((reg2 & WM9081_SPK_MODE) != 0))
+ return 0;
+
+ /* Don't try to change modes while enabled */
+ if (reg_pwr & WM9081_SPK_ENA)
+ return -EINVAL;
+
+ if (ucontrol->value.integer.value[0]) {
+ /* Class AB */
+ reg2 &= ~(WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL);
+ reg2 |= WM9081_SPK_MODE;
+ } else {
+ /* Class D */
+ reg2 |= WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL;
+ reg2 &= ~WM9081_SPK_MODE;
+ }
+
+ snd_soc_write(codec, WM9081_ANALOGUE_SPEAKER_2, reg2);
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new wm9081_snd_controls[] = {
+SOC_SINGLE_TLV("IN1 Volume", WM9081_ANALOGUE_MIXER, 1, 1, 1, in_tlv),
+SOC_SINGLE_TLV("IN2 Volume", WM9081_ANALOGUE_MIXER, 3, 1, 1, in_tlv),
+
+SOC_SINGLE_TLV("Playback Volume", WM9081_DAC_DIGITAL_1, 1, 96, 0, dac_tlv),
+
+SOC_SINGLE("LINEOUT Switch", WM9081_ANALOGUE_LINEOUT, 7, 1, 1),
+SOC_SINGLE("LINEOUT ZC Switch", WM9081_ANALOGUE_LINEOUT, 6, 1, 0),
+SOC_SINGLE_TLV("LINEOUT Volume", WM9081_ANALOGUE_LINEOUT, 0, 63, 0, out_tlv),
+
+SOC_SINGLE("DRC Switch", WM9081_DRC_1, 15, 1, 0),
+SOC_ENUM("DRC High Slope", drc_high),
+SOC_ENUM("DRC Low Slope", drc_low),
+SOC_SINGLE_TLV("DRC Input Volume", WM9081_DRC_4, 5, 60, 1, drc_in_tlv),
+SOC_SINGLE_TLV("DRC Output Volume", WM9081_DRC_4, 0, 30, 1, drc_out_tlv),
+SOC_SINGLE_TLV("DRC Minimum Volume", WM9081_DRC_2, 2, 3, 1, drc_min_tlv),
+SOC_SINGLE_TLV("DRC Maximum Volume", WM9081_DRC_2, 0, 3, 0, drc_max_tlv),
+SOC_ENUM("DRC Attack", drc_atk),
+SOC_ENUM("DRC Decay", drc_dcy),
+SOC_SINGLE("DRC Quick Release Switch", WM9081_DRC_1, 2, 1, 0),
+SOC_SINGLE_TLV("DRC Quick Release Volume", WM9081_DRC_2, 6, 3, 0, drc_qr_tlv),
+SOC_ENUM("DRC Quick Release Decay", drc_qr_dcy),
+SOC_SINGLE_TLV("DRC Startup Volume", WM9081_DRC_1, 6, 18, 0, drc_startup_tlv),
+
+SOC_SINGLE("EQ Switch", WM9081_EQ_1, 0, 1, 0),
+
+SOC_SINGLE("Speaker DC Volume", WM9081_ANALOGUE_SPEAKER_1, 3, 5, 0),
+SOC_SINGLE("Speaker AC Volume", WM9081_ANALOGUE_SPEAKER_1, 0, 5, 0),
+SOC_SINGLE("Speaker Switch", WM9081_ANALOGUE_SPEAKER_PGA, 7, 1, 1),
+SOC_SINGLE("Speaker ZC Switch", WM9081_ANALOGUE_SPEAKER_PGA, 6, 1, 0),
+SOC_SINGLE_TLV("Speaker Volume", WM9081_ANALOGUE_SPEAKER_PGA, 0, 63, 0,
+ out_tlv),
+SOC_ENUM("DAC Deemphasis", dac_deemph),
+SOC_ENUM_EXT("Speaker Mode", speaker_mode, speaker_mode_get, speaker_mode_put),
+};
+
+static const struct snd_kcontrol_new wm9081_eq_controls[] = {
+SOC_SINGLE_TLV("EQ1 Volume", WM9081_EQ_1, 11, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ2 Volume", WM9081_EQ_1, 6, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ3 Volume", WM9081_EQ_1, 1, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ4 Volume", WM9081_EQ_2, 11, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ5 Volume", WM9081_EQ_2, 6, 24, 0, eq_tlv),
+};
+
+static const struct snd_kcontrol_new mixer[] = {
+SOC_DAPM_SINGLE("IN1 Switch", WM9081_ANALOGUE_MIXER, 0, 1, 0),
+SOC_DAPM_SINGLE("IN2 Switch", WM9081_ANALOGUE_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM9081_ANALOGUE_MIXER, 4, 1, 0),
+};
+
+struct _fll_div {
+ u16 fll_fratio;
+ u16 fll_outdiv;
+ u16 fll_clk_ref_div;
+ u16 n;
+ u16 k;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static struct {
+ unsigned int min;
+ unsigned int max;
+ u16 fll_fratio;
+ int ratio;
+} fll_fratios[] = {
+ { 0, 64000, 4, 16 },
+ { 64000, 128000, 3, 8 },
+ { 128000, 256000, 2, 4 },
+ { 256000, 1000000, 1, 2 },
+ { 1000000, 13500000, 0, 1 },
+};
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+ unsigned int Fout)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+ unsigned int div;
+ int i;
+
+ /* Fref must be <=13.5MHz */
+ div = 1;
+ while ((Fref / div) > 13500000) {
+ div *= 2;
+
+ if (div > 8) {
+ pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+ Fref);
+ return -EINVAL;
+ }
+ }
+ fll_div->fll_clk_ref_div = div / 2;
+
+ pr_debug("Fref=%u Fout=%u\n", Fref, Fout);
+
+ /* Apply the division for our remaining calculations */
+ Fref /= div;
+
+ /* Fvco should be 90-100MHz; don't check the upper bound */
+ div = 0;
+ target = Fout * 2;
+ while (target < 90000000) {
+ div++;
+ target *= 2;
+ if (div > 7) {
+ pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+ Fout);
+ return -EINVAL;
+ }
+ }
+ fll_div->fll_outdiv = div;
+
+ pr_debug("Fvco=%dHz\n", target);
+
+ /* Find an appropriate FLL_FRATIO and factor it out of the target */
+ for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+ if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+ fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+ target /= fll_fratios[i].ratio;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(fll_fratios)) {
+ pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+ return -EINVAL;
+ }
+
+ /* Now, calculate N.K */
+ Ndiv = target / Fref;
+
+ fll_div->n = Ndiv;
+ Nmod = target % Fref;
+ pr_debug("Nmod=%d\n", Nmod);
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, Fref);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ fll_div->k = K / 10;
+
+ pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n",
+ fll_div->n, fll_div->k,
+ fll_div->fll_fratio, fll_div->fll_outdiv,
+ fll_div->fll_clk_ref_div);
+
+ return 0;
+}
+
+static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id,
+ unsigned int Fref, unsigned int Fout)
+{
+ struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+ u16 reg1, reg4, reg5;
+ struct _fll_div fll_div;
+ int ret;
+ int clk_sys_reg;
+
+ /* Any change? */
+ if (Fref == wm9081->fll_fref && Fout == wm9081->fll_fout)
+ return 0;
+
+ /* Disable the FLL */
+ if (Fout == 0) {
+ dev_dbg(codec->dev, "FLL disabled\n");
+ wm9081->fll_fref = 0;
+ wm9081->fll_fout = 0;
+
+ return 0;
+ }
+
+ ret = fll_factors(&fll_div, Fref, Fout);
+ if (ret != 0)
+ return ret;
+
+ reg5 = snd_soc_read(codec, WM9081_FLL_CONTROL_5);
+ reg5 &= ~WM9081_FLL_CLK_SRC_MASK;
+
+ switch (fll_id) {
+ case WM9081_SYSCLK_FLL_MCLK:
+ reg5 |= 0x1;
+ break;
+
+ default:
+ dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id);
+ return -EINVAL;
+ }
+
+ /* Disable CLK_SYS while we reconfigure */
+ clk_sys_reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_3);
+ if (clk_sys_reg & WM9081_CLK_SYS_ENA)
+ snd_soc_write(codec, WM9081_CLOCK_CONTROL_3,
+ clk_sys_reg & ~WM9081_CLK_SYS_ENA);
+
+ /* Any FLL configuration change requires that the FLL be
+ * disabled first. */
+ reg1 = snd_soc_read(codec, WM9081_FLL_CONTROL_1);
+ reg1 &= ~WM9081_FLL_ENA;
+ snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1);
+
+ /* Apply the configuration */
+ if (fll_div.k)
+ reg1 |= WM9081_FLL_FRAC_MASK;
+ else
+ reg1 &= ~WM9081_FLL_FRAC_MASK;
+ snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1);
+
+ snd_soc_write(codec, WM9081_FLL_CONTROL_2,
+ (fll_div.fll_outdiv << WM9081_FLL_OUTDIV_SHIFT) |
+ (fll_div.fll_fratio << WM9081_FLL_FRATIO_SHIFT));
+ snd_soc_write(codec, WM9081_FLL_CONTROL_3, fll_div.k);
+
+ reg4 = snd_soc_read(codec, WM9081_FLL_CONTROL_4);
+ reg4 &= ~WM9081_FLL_N_MASK;
+ reg4 |= fll_div.n << WM9081_FLL_N_SHIFT;
+ snd_soc_write(codec, WM9081_FLL_CONTROL_4, reg4);
+
+ reg5 &= ~WM9081_FLL_CLK_REF_DIV_MASK;
+ reg5 |= fll_div.fll_clk_ref_div << WM9081_FLL_CLK_REF_DIV_SHIFT;
+ snd_soc_write(codec, WM9081_FLL_CONTROL_5, reg5);
+
+ /* Set gain to the recommended value */
+ snd_soc_update_bits(codec, WM9081_FLL_CONTROL_4,
+ WM9081_FLL_GAIN_MASK, 0);
+
+ /* Enable the FLL */
+ snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1 | WM9081_FLL_ENA);
+
+ /* Then bring CLK_SYS up again if it was disabled */
+ if (clk_sys_reg & WM9081_CLK_SYS_ENA)
+ snd_soc_write(codec, WM9081_CLOCK_CONTROL_3, clk_sys_reg);
+
+ dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout);
+
+ wm9081->fll_fref = Fref;
+ wm9081->fll_fout = Fout;
+
+ return 0;
+}
+
+static int configure_clock(struct snd_soc_codec *codec)
+{
+ struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+ int new_sysclk, i, target;
+ unsigned int reg;
+ int ret = 0;
+ int mclkdiv = 0;
+ int fll = 0;
+
+ switch (wm9081->sysclk_source) {
+ case WM9081_SYSCLK_MCLK:
+ if (wm9081->mclk_rate > 12225000) {
+ mclkdiv = 1;
+ wm9081->sysclk_rate = wm9081->mclk_rate / 2;
+ } else {
+ wm9081->sysclk_rate = wm9081->mclk_rate;
+ }
+ wm9081_set_fll(codec, WM9081_SYSCLK_FLL_MCLK, 0, 0);
+ break;
+
+ case WM9081_SYSCLK_FLL_MCLK:
+ /* If we have a sample rate calculate a CLK_SYS that
+ * gives us a suitable DAC configuration, plus BCLK.
+ * Ideally we would check to see if we can clock
+ * directly from MCLK and only use the FLL if this is
+ * not the case, though care must be taken with free
+ * running mode.
+ */
+ if (wm9081->master && wm9081->bclk) {
+ /* Make sure we can generate CLK_SYS and BCLK
+ * and that we've got 3MHz for optimal
+ * performance. */
+ for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) {
+ target = wm9081->fs * clk_sys_rates[i].ratio;
+ new_sysclk = target;
+ if (target >= wm9081->bclk &&
+ target > 3000000)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(clk_sys_rates))
+ return -EINVAL;
+
+ } else if (wm9081->fs) {
+ for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) {
+ new_sysclk = clk_sys_rates[i].ratio
+ * wm9081->fs;
+ if (new_sysclk > 3000000)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(clk_sys_rates))
+ return -EINVAL;
+
+ } else {
+ new_sysclk = 12288000;
+ }
+
+ ret = wm9081_set_fll(codec, WM9081_SYSCLK_FLL_MCLK,
+ wm9081->mclk_rate, new_sysclk);
+ if (ret == 0) {
+ wm9081->sysclk_rate = new_sysclk;
+
+ /* Switch SYSCLK over to FLL */
+ fll = 1;
+ } else {
+ wm9081->sysclk_rate = wm9081->mclk_rate;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_1);
+ if (mclkdiv)
+ reg |= WM9081_MCLKDIV2;
+ else
+ reg &= ~WM9081_MCLKDIV2;
+ snd_soc_write(codec, WM9081_CLOCK_CONTROL_1, reg);
+
+ reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_3);
+ if (fll)
+ reg |= WM9081_CLK_SRC_SEL;
+ else
+ reg &= ~WM9081_CLK_SRC_SEL;
+ snd_soc_write(codec, WM9081_CLOCK_CONTROL_3, reg);
+
+ dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm9081->sysclk_rate);
+
+ return ret;
+}
+
+static int clk_sys_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+
+ /* This should be done on init() for bypass paths */
+ switch (wm9081->sysclk_source) {
+ case WM9081_SYSCLK_MCLK:
+ dev_dbg(codec->dev, "Using %dHz MCLK\n", wm9081->mclk_rate);
+ break;
+ case WM9081_SYSCLK_FLL_MCLK:
+ dev_dbg(codec->dev, "Using %dHz MCLK with FLL\n",
+ wm9081->mclk_rate);
+ break;
+ default:
+ dev_err(codec->dev, "System clock not configured\n");
+ return -EINVAL;
+ }
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ configure_clock(codec);
+ break;
+
+ case SND_SOC_DAPM_POST_PMD:
+ /* Disable the FLL if it's running */
+ wm9081_set_fll(codec, 0, 0, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget wm9081_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1"),
+SND_SOC_DAPM_INPUT("IN2"),
+
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM9081_POWER_MANAGEMENT, 0, 0),
+
+SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0,
+ mixer, ARRAY_SIZE(mixer)),
+
+SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("LINEOUT"),
+SND_SOC_DAPM_OUTPUT("SPKN"),
+SND_SOC_DAPM_OUTPUT("SPKP"),
+
+SND_SOC_DAPM_SUPPLY("CLK_SYS", WM9081_CLOCK_CONTROL_3, 0, 0, clk_sys_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM9081_CLOCK_CONTROL_3, 1, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("TOCLK", WM9081_CLOCK_CONTROL_3, 2, 0, NULL, 0),
+};
+
+
+static const struct snd_soc_dapm_route wm9081_audio_paths[] = {
+ { "DAC", NULL, "CLK_SYS" },
+ { "DAC", NULL, "CLK_DSP" },
+
+ { "Mixer", "IN1 Switch", "IN1" },
+ { "Mixer", "IN2 Switch", "IN2" },
+ { "Mixer", "Playback Switch", "DAC" },
+
+ { "LINEOUT PGA", NULL, "Mixer" },
+ { "LINEOUT PGA", NULL, "TOCLK" },
+ { "LINEOUT PGA", NULL, "CLK_SYS" },
+
+ { "LINEOUT", NULL, "LINEOUT PGA" },
+
+ { "Speaker PGA", NULL, "Mixer" },
+ { "Speaker PGA", NULL, "TOCLK" },
+ { "Speaker PGA", NULL, "CLK_SYS" },
+
+ { "Speaker", NULL, "Speaker PGA" },
+
+ { "SPKN", NULL, "Speaker" },
+ { "SPKP", NULL, "Speaker" },
+};
+
+static int wm9081_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID=2*40k */
+ reg = snd_soc_read(codec, WM9081_VMID_CONTROL);
+ reg &= ~WM9081_VMID_SEL_MASK;
+ reg |= 0x2;
+ snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
+
+ /* Normal bias current */
+ reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+ reg &= ~WM9081_STBY_BIAS_ENA;
+ snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ /* Initial cold start */
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Disable LINEOUT discharge */
+ reg = snd_soc_read(codec, WM9081_ANTI_POP_CONTROL);
+ reg &= ~WM9081_LINEOUT_DISCH;
+ snd_soc_write(codec, WM9081_ANTI_POP_CONTROL, reg);
+
+ /* Select startup bias source */
+ reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+ reg |= WM9081_BIAS_SRC | WM9081_BIAS_ENA;
+ snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
+
+ /* VMID 2*4k; Soft VMID ramp enable */
+ reg = snd_soc_read(codec, WM9081_VMID_CONTROL);
+ reg |= WM9081_VMID_RAMP | 0x6;
+ snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
+
+ mdelay(100);
+
+ /* Normal bias enable & soft start off */
+ reg |= WM9081_BIAS_ENA;
+ reg &= ~WM9081_VMID_RAMP;
+ snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
+
+ /* Standard bias source */
+ reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+ reg &= ~WM9081_BIAS_SRC;
+ snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
+ }
+
+ /* VMID 2*240k */
+ reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+ reg &= ~WM9081_VMID_SEL_MASK;
+ reg |= 0x40;
+ snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
+
+ /* Standby bias current on */
+ reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+ reg |= WM9081_STBY_BIAS_ENA;
+ snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Startup bias source */
+ reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+ reg |= WM9081_BIAS_SRC;
+ snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
+
+ /* Disable VMID and biases with soft ramping */
+ reg = snd_soc_read(codec, WM9081_VMID_CONTROL);
+ reg &= ~(WM9081_VMID_SEL_MASK | WM9081_BIAS_ENA);
+ reg |= WM9081_VMID_RAMP;
+ snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
+
+ /* Actively discharge LINEOUT */
+ reg = snd_soc_read(codec, WM9081_ANTI_POP_CONTROL);
+ reg |= WM9081_LINEOUT_DISCH;
+ snd_soc_write(codec, WM9081_ANTI_POP_CONTROL, reg);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static int wm9081_set_dai_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+ unsigned int aif2 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_2);
+
+ aif2 &= ~(WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV |
+ WM9081_BCLK_DIR | WM9081_LRCLK_DIR | WM9081_AIF_FMT_MASK);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ wm9081->master = 0;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ aif2 |= WM9081_LRCLK_DIR;
+ wm9081->master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ aif2 |= WM9081_BCLK_DIR;
+ wm9081->master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif2 |= WM9081_LRCLK_DIR | WM9081_BCLK_DIR;
+ wm9081->master = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_B:
+ aif2 |= WM9081_AIF_LRCLK_INV;
+ case SND_SOC_DAIFMT_DSP_A:
+ aif2 |= 0x3;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif2 |= 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif2 |= 0x1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif2 |= WM9081_AIF_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif2 |= WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif2 |= WM9081_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif2 |= WM9081_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM9081_AUDIO_INTERFACE_2, aif2);
+
+ return 0;
+}
+
+static int wm9081_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+ int ret, i, best, best_val, cur_val;
+ unsigned int clk_ctrl2, aif1, aif2, aif3, aif4;
+
+ clk_ctrl2 = snd_soc_read(codec, WM9081_CLOCK_CONTROL_2);
+ clk_ctrl2 &= ~(WM9081_CLK_SYS_RATE_MASK | WM9081_SAMPLE_RATE_MASK);
+
+ aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1);
+
+ aif2 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_2);
+ aif2 &= ~WM9081_AIF_WL_MASK;
+
+ aif3 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_3);
+ aif3 &= ~WM9081_BCLK_DIV_MASK;
+
+ aif4 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_4);
+ aif4 &= ~WM9081_LRCLK_RATE_MASK;
+
+ wm9081->fs = params_rate(params);
+
+ if (wm9081->tdm_width) {
+ /* If TDM is set up then that fixes our BCLK. */
+ int slots = ((aif1 & WM9081_AIFDAC_TDM_MODE_MASK) >>
+ WM9081_AIFDAC_TDM_MODE_SHIFT) + 1;
+
+ wm9081->bclk = wm9081->fs * wm9081->tdm_width * slots;
+ } else {
+ /* Otherwise work out a BCLK from the sample size */
+ wm9081->bclk = 2 * wm9081->fs;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ wm9081->bclk *= 16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ wm9081->bclk *= 20;
+ aif2 |= 0x4;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ wm9081->bclk *= 24;
+ aif2 |= 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ wm9081->bclk *= 32;
+ aif2 |= 0xc;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm9081->bclk);
+
+ ret = configure_clock(codec);
+ if (ret != 0)
+ return ret;
+
+ /* Select nearest CLK_SYS_RATE */
+ best = 0;
+ best_val = abs((wm9081->sysclk_rate / clk_sys_rates[0].ratio)
+ - wm9081->fs);
+ for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+ cur_val = abs((wm9081->sysclk_rate /
+ clk_sys_rates[i].ratio) - wm9081->fs);
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ dev_dbg(codec->dev, "Selected CLK_SYS_RATIO of %d\n",
+ clk_sys_rates[best].ratio);
+ clk_ctrl2 |= (clk_sys_rates[best].clk_sys_rate
+ << WM9081_CLK_SYS_RATE_SHIFT);
+
+ /* SAMPLE_RATE */
+ best = 0;
+ best_val = abs(wm9081->fs - sample_rates[0].rate);
+ for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+ /* Closest match */
+ cur_val = abs(wm9081->fs - sample_rates[i].rate);
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n",
+ sample_rates[best].rate);
+ clk_ctrl2 |= (sample_rates[best].sample_rate
+ << WM9081_SAMPLE_RATE_SHIFT);
+
+ /* BCLK_DIV */
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+ cur_val = ((wm9081->sysclk_rate * 10) / bclk_divs[i].div)
+ - wm9081->bclk;
+ if (cur_val < 0) /* Table is sorted */
+ break;
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ wm9081->bclk = (wm9081->sysclk_rate * 10) / bclk_divs[best].div;
+ dev_dbg(codec->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n",
+ bclk_divs[best].div, wm9081->bclk);
+ aif3 |= bclk_divs[best].bclk_div;
+
+ /* LRCLK is a simple fraction of BCLK */
+ dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm9081->bclk / wm9081->fs);
+ aif4 |= wm9081->bclk / wm9081->fs;
+
+ /* Apply a ReTune Mobile configuration if it's in use */
+ if (wm9081->pdata.num_retune_configs) {
+ struct wm9081_pdata *pdata = &wm9081->pdata;
+ struct wm9081_retune_mobile_setting *s;
+ int eq1;
+
+ best = 0;
+ best_val = abs(pdata->retune_configs[0].rate - wm9081->fs);
+ for (i = 0; i < pdata->num_retune_configs; i++) {
+ cur_val = abs(pdata->retune_configs[i].rate -
+ wm9081->fs);
+ if (cur_val < best_val) {
+ best_val = cur_val;
+ best = i;
+ }
+ }
+ s = &pdata->retune_configs[best];
+
+ dev_dbg(codec->dev, "ReTune Mobile %s tuned for %dHz\n",
+ s->name, s->rate);
+
+ /* If the EQ is enabled then disable it while we write out */
+ eq1 = snd_soc_read(codec, WM9081_EQ_1) & WM9081_EQ_ENA;
+ if (eq1 & WM9081_EQ_ENA)
+ snd_soc_write(codec, WM9081_EQ_1, 0);
+
+ /* Write out the other values */
+ for (i = 1; i < ARRAY_SIZE(s->config); i++)
+ snd_soc_write(codec, WM9081_EQ_1 + i, s->config[i]);
+
+ eq1 |= (s->config[0] & ~WM9081_EQ_ENA);
+ snd_soc_write(codec, WM9081_EQ_1, eq1);
+ }
+
+ snd_soc_write(codec, WM9081_CLOCK_CONTROL_2, clk_ctrl2);
+ snd_soc_write(codec, WM9081_AUDIO_INTERFACE_2, aif2);
+ snd_soc_write(codec, WM9081_AUDIO_INTERFACE_3, aif3);
+ snd_soc_write(codec, WM9081_AUDIO_INTERFACE_4, aif4);
+
+ return 0;
+}
+
+static int wm9081_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ reg = snd_soc_read(codec, WM9081_DAC_DIGITAL_2);
+
+ if (mute)
+ reg |= WM9081_DAC_MUTE;
+ else
+ reg &= ~WM9081_DAC_MUTE;
+
+ snd_soc_write(codec, WM9081_DAC_DIGITAL_2, reg);
+
+ return 0;
+}
+
+static int wm9081_set_sysclk(struct snd_soc_codec *codec,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+
+ switch (clk_id) {
+ case WM9081_SYSCLK_MCLK:
+ case WM9081_SYSCLK_FLL_MCLK:
+ wm9081->sysclk_source = clk_id;
+ wm9081->mclk_rate = freq;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm9081_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+ unsigned int aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1);
+
+ aif1 &= ~(WM9081_AIFDAC_TDM_SLOT_MASK | WM9081_AIFDAC_TDM_MODE_MASK);
+
+ if (slots < 0 || slots > 4)
+ return -EINVAL;
+
+ wm9081->tdm_width = slot_width;
+
+ if (slots == 0)
+ slots = 1;
+
+ aif1 |= (slots - 1) << WM9081_AIFDAC_TDM_MODE_SHIFT;
+
+ switch (rx_mask) {
+ case 1:
+ break;
+ case 2:
+ aif1 |= 0x10;
+ break;
+ case 4:
+ aif1 |= 0x20;
+ break;
+ case 8:
+ aif1 |= 0x30;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM9081_AUDIO_INTERFACE_1, aif1);
+
+ return 0;
+}
+
+#define WM9081_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM9081_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm9081_dai_ops = {
+ .hw_params = wm9081_hw_params,
+ .set_fmt = wm9081_set_dai_fmt,
+ .digital_mute = wm9081_digital_mute,
+ .set_tdm_slot = wm9081_set_tdm_slot,
+};
+
+/* We report two channels because the CODEC processes a stereo signal, even
+ * though it is only capable of handling a mono output.
+ */
+static struct snd_soc_dai_driver wm9081_dai = {
+ .name = "wm9081-hifi",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9081_RATES,
+ .formats = WM9081_FORMATS,
+ },
+ .ops = &wm9081_dai_ops,
+};
+
+static int wm9081_probe(struct snd_soc_codec *codec)
+{
+ struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+ u16 reg;
+
+ codec->control_data = wm9081->control_data;
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm9081->control_type);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ reg = snd_soc_read(codec, WM9081_SOFTWARE_RESET);
+ if (reg != 0x9081) {
+ dev_err(codec->dev, "Device is not a WM9081: ID=0x%x\n", reg);
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = wm9081_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ reg = 0;
+ if (wm9081->pdata.irq_high)
+ reg |= WM9081_IRQ_POL;
+ if (!wm9081->pdata.irq_cmos)
+ reg |= WM9081_IRQ_OP_CTRL;
+ snd_soc_update_bits(codec, WM9081_INTERRUPT_CONTROL,
+ WM9081_IRQ_POL | WM9081_IRQ_OP_CTRL, reg);
+
+ wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Enable zero cross by default */
+ reg = snd_soc_read(codec, WM9081_ANALOGUE_LINEOUT);
+ snd_soc_write(codec, WM9081_ANALOGUE_LINEOUT, reg | WM9081_LINEOUTZC);
+ reg = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_PGA);
+ snd_soc_write(codec, WM9081_ANALOGUE_SPEAKER_PGA,
+ reg | WM9081_SPKPGAZC);
+
+ snd_soc_add_controls(codec, wm9081_snd_controls,
+ ARRAY_SIZE(wm9081_snd_controls));
+ if (!wm9081->pdata.num_retune_configs) {
+ dev_dbg(codec->dev,
+ "No ReTune Mobile data, using normal EQ\n");
+ snd_soc_add_controls(codec, wm9081_eq_controls,
+ ARRAY_SIZE(wm9081_eq_controls));
+ }
+
+ return ret;
+}
+
+static int wm9081_remove(struct snd_soc_codec *codec)
+{
+ wm9081_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm9081_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm9081_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm9081_resume(struct snd_soc_codec *codec)
+{
+ u16 *reg_cache = codec->reg_cache;
+ int i;
+
+ for (i = 0; i < codec->driver->reg_cache_size; i++) {
+ if (i == WM9081_SOFTWARE_RESET)
+ continue;
+
+ snd_soc_write(codec, i, reg_cache[i]);
+ }
+
+ wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define wm9081_suspend NULL
+#define wm9081_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_wm9081 = {
+ .probe = wm9081_probe,
+ .remove = wm9081_remove,
+ .suspend = wm9081_suspend,
+ .resume = wm9081_resume,
+
+ .set_sysclk = wm9081_set_sysclk,
+ .set_bias_level = wm9081_set_bias_level,
+
+ .reg_cache_size = ARRAY_SIZE(wm9081_reg_defaults),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm9081_reg_defaults,
+ .volatile_register = wm9081_volatile_register,
+
+ .dapm_widgets = wm9081_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm9081_dapm_widgets),
+ .dapm_routes = wm9081_audio_paths,
+ .num_dapm_routes = ARRAY_SIZE(wm9081_audio_paths),
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm9081_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm9081_priv *wm9081;
+ int ret;
+
+ wm9081 = kzalloc(sizeof(struct wm9081_priv), GFP_KERNEL);
+ if (wm9081 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, wm9081);
+ wm9081->control_type = SND_SOC_I2C;
+ wm9081->control_data = i2c;
+
+ if (dev_get_platdata(&i2c->dev))
+ memcpy(&wm9081->pdata, dev_get_platdata(&i2c->dev),
+ sizeof(wm9081->pdata));
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm9081, &wm9081_dai, 1);
+ if (ret < 0)
+ kfree(wm9081);
+ return ret;
+}
+
+static __devexit int wm9081_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm9081_i2c_id[] = {
+ { "wm9081", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm9081_i2c_id);
+
+static struct i2c_driver wm9081_i2c_driver = {
+ .driver = {
+ .name = "wm9081",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm9081_i2c_probe,
+ .remove = __devexit_p(wm9081_i2c_remove),
+ .id_table = wm9081_i2c_id,
+};
+#endif
+
+static int __init wm9081_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm9081_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM9081 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm9081_modinit);
+
+static void __exit wm9081_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm9081_i2c_driver);
+#endif
+}
+module_exit(wm9081_exit);
+
+
+MODULE_DESCRIPTION("ASoC WM9081 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm9081.h b/sound/soc/codecs/wm9081.h
new file mode 100644
index 00000000..871cccb0
--- /dev/null
+++ b/sound/soc/codecs/wm9081.h
@@ -0,0 +1,784 @@
+#ifndef WM9081_H
+#define WM9081_H
+
+/*
+ * wm9081.c -- WM9081 ALSA SoC Audio driver
+ *
+ * Author: Mark Brown
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <sound/soc.h>
+
+/*
+ * SYSCLK sources
+ */
+#define WM9081_SYSCLK_MCLK 1 /* Use MCLK without FLL */
+#define WM9081_SYSCLK_FLL_MCLK 2 /* Use MCLK, enabling FLL if required */
+
+/*
+ * Register values.
+ */
+#define WM9081_SOFTWARE_RESET 0x00
+#define WM9081_ANALOGUE_LINEOUT 0x02
+#define WM9081_ANALOGUE_SPEAKER_PGA 0x03
+#define WM9081_VMID_CONTROL 0x04
+#define WM9081_BIAS_CONTROL_1 0x05
+#define WM9081_ANALOGUE_MIXER 0x07
+#define WM9081_ANTI_POP_CONTROL 0x08
+#define WM9081_ANALOGUE_SPEAKER_1 0x09
+#define WM9081_ANALOGUE_SPEAKER_2 0x0A
+#define WM9081_POWER_MANAGEMENT 0x0B
+#define WM9081_CLOCK_CONTROL_1 0x0C
+#define WM9081_CLOCK_CONTROL_2 0x0D
+#define WM9081_CLOCK_CONTROL_3 0x0E
+#define WM9081_FLL_CONTROL_1 0x10
+#define WM9081_FLL_CONTROL_2 0x11
+#define WM9081_FLL_CONTROL_3 0x12
+#define WM9081_FLL_CONTROL_4 0x13
+#define WM9081_FLL_CONTROL_5 0x14
+#define WM9081_AUDIO_INTERFACE_1 0x16
+#define WM9081_AUDIO_INTERFACE_2 0x17
+#define WM9081_AUDIO_INTERFACE_3 0x18
+#define WM9081_AUDIO_INTERFACE_4 0x19
+#define WM9081_INTERRUPT_STATUS 0x1A
+#define WM9081_INTERRUPT_STATUS_MASK 0x1B
+#define WM9081_INTERRUPT_POLARITY 0x1C
+#define WM9081_INTERRUPT_CONTROL 0x1D
+#define WM9081_DAC_DIGITAL_1 0x1E
+#define WM9081_DAC_DIGITAL_2 0x1F
+#define WM9081_DRC_1 0x20
+#define WM9081_DRC_2 0x21
+#define WM9081_DRC_3 0x22
+#define WM9081_DRC_4 0x23
+#define WM9081_WRITE_SEQUENCER_1 0x26
+#define WM9081_WRITE_SEQUENCER_2 0x27
+#define WM9081_MW_SLAVE_1 0x28
+#define WM9081_EQ_1 0x2A
+#define WM9081_EQ_2 0x2B
+#define WM9081_EQ_3 0x2C
+#define WM9081_EQ_4 0x2D
+#define WM9081_EQ_5 0x2E
+#define WM9081_EQ_6 0x2F
+#define WM9081_EQ_7 0x30
+#define WM9081_EQ_8 0x31
+#define WM9081_EQ_9 0x32
+#define WM9081_EQ_10 0x33
+#define WM9081_EQ_11 0x34
+#define WM9081_EQ_12 0x35
+#define WM9081_EQ_13 0x36
+#define WM9081_EQ_14 0x37
+#define WM9081_EQ_15 0x38
+#define WM9081_EQ_16 0x39
+#define WM9081_EQ_17 0x3A
+#define WM9081_EQ_18 0x3B
+#define WM9081_EQ_19 0x3C
+#define WM9081_EQ_20 0x3D
+
+#define WM9081_REGISTER_COUNT 55
+#define WM9081_MAX_REGISTER 0x3D
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Software Reset
+ */
+#define WM9081_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */
+#define WM9081_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */
+#define WM9081_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */
+
+/*
+ * R2 (0x02) - Analogue Lineout
+ */
+#define WM9081_LINEOUT_MUTE 0x0080 /* LINEOUT_MUTE */
+#define WM9081_LINEOUT_MUTE_MASK 0x0080 /* LINEOUT_MUTE */
+#define WM9081_LINEOUT_MUTE_SHIFT 7 /* LINEOUT_MUTE */
+#define WM9081_LINEOUT_MUTE_WIDTH 1 /* LINEOUT_MUTE */
+#define WM9081_LINEOUTZC 0x0040 /* LINEOUTZC */
+#define WM9081_LINEOUTZC_MASK 0x0040 /* LINEOUTZC */
+#define WM9081_LINEOUTZC_SHIFT 6 /* LINEOUTZC */
+#define WM9081_LINEOUTZC_WIDTH 1 /* LINEOUTZC */
+#define WM9081_LINEOUT_VOL_MASK 0x003F /* LINEOUT_VOL - [5:0] */
+#define WM9081_LINEOUT_VOL_SHIFT 0 /* LINEOUT_VOL - [5:0] */
+#define WM9081_LINEOUT_VOL_WIDTH 6 /* LINEOUT_VOL - [5:0] */
+
+/*
+ * R3 (0x03) - Analogue Speaker PGA
+ */
+#define WM9081_SPKPGA_MUTE 0x0080 /* SPKPGA_MUTE */
+#define WM9081_SPKPGA_MUTE_MASK 0x0080 /* SPKPGA_MUTE */
+#define WM9081_SPKPGA_MUTE_SHIFT 7 /* SPKPGA_MUTE */
+#define WM9081_SPKPGA_MUTE_WIDTH 1 /* SPKPGA_MUTE */
+#define WM9081_SPKPGAZC 0x0040 /* SPKPGAZC */
+#define WM9081_SPKPGAZC_MASK 0x0040 /* SPKPGAZC */
+#define WM9081_SPKPGAZC_SHIFT 6 /* SPKPGAZC */
+#define WM9081_SPKPGAZC_WIDTH 1 /* SPKPGAZC */
+#define WM9081_SPKPGA_VOL_MASK 0x003F /* SPKPGA_VOL - [5:0] */
+#define WM9081_SPKPGA_VOL_SHIFT 0 /* SPKPGA_VOL - [5:0] */
+#define WM9081_SPKPGA_VOL_WIDTH 6 /* SPKPGA_VOL - [5:0] */
+
+/*
+ * R4 (0x04) - VMID Control
+ */
+#define WM9081_VMID_BUF_ENA 0x0020 /* VMID_BUF_ENA */
+#define WM9081_VMID_BUF_ENA_MASK 0x0020 /* VMID_BUF_ENA */
+#define WM9081_VMID_BUF_ENA_SHIFT 5 /* VMID_BUF_ENA */
+#define WM9081_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */
+#define WM9081_VMID_RAMP 0x0008 /* VMID_RAMP */
+#define WM9081_VMID_RAMP_MASK 0x0008 /* VMID_RAMP */
+#define WM9081_VMID_RAMP_SHIFT 3 /* VMID_RAMP */
+#define WM9081_VMID_RAMP_WIDTH 1 /* VMID_RAMP */
+#define WM9081_VMID_SEL_MASK 0x0006 /* VMID_SEL - [2:1] */
+#define WM9081_VMID_SEL_SHIFT 1 /* VMID_SEL - [2:1] */
+#define WM9081_VMID_SEL_WIDTH 2 /* VMID_SEL - [2:1] */
+#define WM9081_VMID_FAST_ST 0x0001 /* VMID_FAST_ST */
+#define WM9081_VMID_FAST_ST_MASK 0x0001 /* VMID_FAST_ST */
+#define WM9081_VMID_FAST_ST_SHIFT 0 /* VMID_FAST_ST */
+#define WM9081_VMID_FAST_ST_WIDTH 1 /* VMID_FAST_ST */
+
+/*
+ * R5 (0x05) - Bias Control 1
+ */
+#define WM9081_BIAS_SRC 0x0040 /* BIAS_SRC */
+#define WM9081_BIAS_SRC_MASK 0x0040 /* BIAS_SRC */
+#define WM9081_BIAS_SRC_SHIFT 6 /* BIAS_SRC */
+#define WM9081_BIAS_SRC_WIDTH 1 /* BIAS_SRC */
+#define WM9081_STBY_BIAS_LVL 0x0020 /* STBY_BIAS_LVL */
+#define WM9081_STBY_BIAS_LVL_MASK 0x0020 /* STBY_BIAS_LVL */
+#define WM9081_STBY_BIAS_LVL_SHIFT 5 /* STBY_BIAS_LVL */
+#define WM9081_STBY_BIAS_LVL_WIDTH 1 /* STBY_BIAS_LVL */
+#define WM9081_STBY_BIAS_ENA 0x0010 /* STBY_BIAS_ENA */
+#define WM9081_STBY_BIAS_ENA_MASK 0x0010 /* STBY_BIAS_ENA */
+#define WM9081_STBY_BIAS_ENA_SHIFT 4 /* STBY_BIAS_ENA */
+#define WM9081_STBY_BIAS_ENA_WIDTH 1 /* STBY_BIAS_ENA */
+#define WM9081_BIAS_LVL_MASK 0x000C /* BIAS_LVL - [3:2] */
+#define WM9081_BIAS_LVL_SHIFT 2 /* BIAS_LVL - [3:2] */
+#define WM9081_BIAS_LVL_WIDTH 2 /* BIAS_LVL - [3:2] */
+#define WM9081_BIAS_ENA 0x0002 /* BIAS_ENA */
+#define WM9081_BIAS_ENA_MASK 0x0002 /* BIAS_ENA */
+#define WM9081_BIAS_ENA_SHIFT 1 /* BIAS_ENA */
+#define WM9081_BIAS_ENA_WIDTH 1 /* BIAS_ENA */
+#define WM9081_STARTUP_BIAS_ENA 0x0001 /* STARTUP_BIAS_ENA */
+#define WM9081_STARTUP_BIAS_ENA_MASK 0x0001 /* STARTUP_BIAS_ENA */
+#define WM9081_STARTUP_BIAS_ENA_SHIFT 0 /* STARTUP_BIAS_ENA */
+#define WM9081_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */
+
+/*
+ * R7 (0x07) - Analogue Mixer
+ */
+#define WM9081_DAC_SEL 0x0010 /* DAC_SEL */
+#define WM9081_DAC_SEL_MASK 0x0010 /* DAC_SEL */
+#define WM9081_DAC_SEL_SHIFT 4 /* DAC_SEL */
+#define WM9081_DAC_SEL_WIDTH 1 /* DAC_SEL */
+#define WM9081_IN2_VOL 0x0008 /* IN2_VOL */
+#define WM9081_IN2_VOL_MASK 0x0008 /* IN2_VOL */
+#define WM9081_IN2_VOL_SHIFT 3 /* IN2_VOL */
+#define WM9081_IN2_VOL_WIDTH 1 /* IN2_VOL */
+#define WM9081_IN2_ENA 0x0004 /* IN2_ENA */
+#define WM9081_IN2_ENA_MASK 0x0004 /* IN2_ENA */
+#define WM9081_IN2_ENA_SHIFT 2 /* IN2_ENA */
+#define WM9081_IN2_ENA_WIDTH 1 /* IN2_ENA */
+#define WM9081_IN1_VOL 0x0002 /* IN1_VOL */
+#define WM9081_IN1_VOL_MASK 0x0002 /* IN1_VOL */
+#define WM9081_IN1_VOL_SHIFT 1 /* IN1_VOL */
+#define WM9081_IN1_VOL_WIDTH 1 /* IN1_VOL */
+#define WM9081_IN1_ENA 0x0001 /* IN1_ENA */
+#define WM9081_IN1_ENA_MASK 0x0001 /* IN1_ENA */
+#define WM9081_IN1_ENA_SHIFT 0 /* IN1_ENA */
+#define WM9081_IN1_ENA_WIDTH 1 /* IN1_ENA */
+
+/*
+ * R8 (0x08) - Anti Pop Control
+ */
+#define WM9081_LINEOUT_DISCH 0x0004 /* LINEOUT_DISCH */
+#define WM9081_LINEOUT_DISCH_MASK 0x0004 /* LINEOUT_DISCH */
+#define WM9081_LINEOUT_DISCH_SHIFT 2 /* LINEOUT_DISCH */
+#define WM9081_LINEOUT_DISCH_WIDTH 1 /* LINEOUT_DISCH */
+#define WM9081_LINEOUT_VROI 0x0002 /* LINEOUT_VROI */
+#define WM9081_LINEOUT_VROI_MASK 0x0002 /* LINEOUT_VROI */
+#define WM9081_LINEOUT_VROI_SHIFT 1 /* LINEOUT_VROI */
+#define WM9081_LINEOUT_VROI_WIDTH 1 /* LINEOUT_VROI */
+#define WM9081_LINEOUT_CLAMP 0x0001 /* LINEOUT_CLAMP */
+#define WM9081_LINEOUT_CLAMP_MASK 0x0001 /* LINEOUT_CLAMP */
+#define WM9081_LINEOUT_CLAMP_SHIFT 0 /* LINEOUT_CLAMP */
+#define WM9081_LINEOUT_CLAMP_WIDTH 1 /* LINEOUT_CLAMP */
+
+/*
+ * R9 (0x09) - Analogue Speaker 1
+ */
+#define WM9081_SPK_DCGAIN_MASK 0x0038 /* SPK_DCGAIN - [5:3] */
+#define WM9081_SPK_DCGAIN_SHIFT 3 /* SPK_DCGAIN - [5:3] */
+#define WM9081_SPK_DCGAIN_WIDTH 3 /* SPK_DCGAIN - [5:3] */
+#define WM9081_SPK_ACGAIN_MASK 0x0007 /* SPK_ACGAIN - [2:0] */
+#define WM9081_SPK_ACGAIN_SHIFT 0 /* SPK_ACGAIN - [2:0] */
+#define WM9081_SPK_ACGAIN_WIDTH 3 /* SPK_ACGAIN - [2:0] */
+
+/*
+ * R10 (0x0A) - Analogue Speaker 2
+ */
+#define WM9081_SPK_MODE 0x0040 /* SPK_MODE */
+#define WM9081_SPK_MODE_MASK 0x0040 /* SPK_MODE */
+#define WM9081_SPK_MODE_SHIFT 6 /* SPK_MODE */
+#define WM9081_SPK_MODE_WIDTH 1 /* SPK_MODE */
+#define WM9081_SPK_INV_MUTE 0x0010 /* SPK_INV_MUTE */
+#define WM9081_SPK_INV_MUTE_MASK 0x0010 /* SPK_INV_MUTE */
+#define WM9081_SPK_INV_MUTE_SHIFT 4 /* SPK_INV_MUTE */
+#define WM9081_SPK_INV_MUTE_WIDTH 1 /* SPK_INV_MUTE */
+#define WM9081_OUT_SPK_CTRL 0x0008 /* OUT_SPK_CTRL */
+#define WM9081_OUT_SPK_CTRL_MASK 0x0008 /* OUT_SPK_CTRL */
+#define WM9081_OUT_SPK_CTRL_SHIFT 3 /* OUT_SPK_CTRL */
+#define WM9081_OUT_SPK_CTRL_WIDTH 1 /* OUT_SPK_CTRL */
+
+/*
+ * R11 (0x0B) - Power Management
+ */
+#define WM9081_TSHUT_ENA 0x0100 /* TSHUT_ENA */
+#define WM9081_TSHUT_ENA_MASK 0x0100 /* TSHUT_ENA */
+#define WM9081_TSHUT_ENA_SHIFT 8 /* TSHUT_ENA */
+#define WM9081_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */
+#define WM9081_TSENSE_ENA 0x0080 /* TSENSE_ENA */
+#define WM9081_TSENSE_ENA_MASK 0x0080 /* TSENSE_ENA */
+#define WM9081_TSENSE_ENA_SHIFT 7 /* TSENSE_ENA */
+#define WM9081_TSENSE_ENA_WIDTH 1 /* TSENSE_ENA */
+#define WM9081_TEMP_SHUT 0x0040 /* TEMP_SHUT */
+#define WM9081_TEMP_SHUT_MASK 0x0040 /* TEMP_SHUT */
+#define WM9081_TEMP_SHUT_SHIFT 6 /* TEMP_SHUT */
+#define WM9081_TEMP_SHUT_WIDTH 1 /* TEMP_SHUT */
+#define WM9081_LINEOUT_ENA 0x0010 /* LINEOUT_ENA */
+#define WM9081_LINEOUT_ENA_MASK 0x0010 /* LINEOUT_ENA */
+#define WM9081_LINEOUT_ENA_SHIFT 4 /* LINEOUT_ENA */
+#define WM9081_LINEOUT_ENA_WIDTH 1 /* LINEOUT_ENA */
+#define WM9081_SPKPGA_ENA 0x0004 /* SPKPGA_ENA */
+#define WM9081_SPKPGA_ENA_MASK 0x0004 /* SPKPGA_ENA */
+#define WM9081_SPKPGA_ENA_SHIFT 2 /* SPKPGA_ENA */
+#define WM9081_SPKPGA_ENA_WIDTH 1 /* SPKPGA_ENA */
+#define WM9081_SPK_ENA 0x0002 /* SPK_ENA */
+#define WM9081_SPK_ENA_MASK 0x0002 /* SPK_ENA */
+#define WM9081_SPK_ENA_SHIFT 1 /* SPK_ENA */
+#define WM9081_SPK_ENA_WIDTH 1 /* SPK_ENA */
+#define WM9081_DAC_ENA 0x0001 /* DAC_ENA */
+#define WM9081_DAC_ENA_MASK 0x0001 /* DAC_ENA */
+#define WM9081_DAC_ENA_SHIFT 0 /* DAC_ENA */
+#define WM9081_DAC_ENA_WIDTH 1 /* DAC_ENA */
+
+/*
+ * R12 (0x0C) - Clock Control 1
+ */
+#define WM9081_CLK_OP_DIV_MASK 0x1C00 /* CLK_OP_DIV - [12:10] */
+#define WM9081_CLK_OP_DIV_SHIFT 10 /* CLK_OP_DIV - [12:10] */
+#define WM9081_CLK_OP_DIV_WIDTH 3 /* CLK_OP_DIV - [12:10] */
+#define WM9081_CLK_TO_DIV_MASK 0x0300 /* CLK_TO_DIV - [9:8] */
+#define WM9081_CLK_TO_DIV_SHIFT 8 /* CLK_TO_DIV - [9:8] */
+#define WM9081_CLK_TO_DIV_WIDTH 2 /* CLK_TO_DIV - [9:8] */
+#define WM9081_MCLKDIV2 0x0080 /* MCLKDIV2 */
+#define WM9081_MCLKDIV2_MASK 0x0080 /* MCLKDIV2 */
+#define WM9081_MCLKDIV2_SHIFT 7 /* MCLKDIV2 */
+#define WM9081_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */
+
+/*
+ * R13 (0x0D) - Clock Control 2
+ */
+#define WM9081_CLK_SYS_RATE_MASK 0x00F0 /* CLK_SYS_RATE - [7:4] */
+#define WM9081_CLK_SYS_RATE_SHIFT 4 /* CLK_SYS_RATE - [7:4] */
+#define WM9081_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [7:4] */
+#define WM9081_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */
+#define WM9081_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */
+#define WM9081_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */
+
+/*
+ * R14 (0x0E) - Clock Control 3
+ */
+#define WM9081_CLK_SRC_SEL 0x2000 /* CLK_SRC_SEL */
+#define WM9081_CLK_SRC_SEL_MASK 0x2000 /* CLK_SRC_SEL */
+#define WM9081_CLK_SRC_SEL_SHIFT 13 /* CLK_SRC_SEL */
+#define WM9081_CLK_SRC_SEL_WIDTH 1 /* CLK_SRC_SEL */
+#define WM9081_CLK_OP_ENA 0x0020 /* CLK_OP_ENA */
+#define WM9081_CLK_OP_ENA_MASK 0x0020 /* CLK_OP_ENA */
+#define WM9081_CLK_OP_ENA_SHIFT 5 /* CLK_OP_ENA */
+#define WM9081_CLK_OP_ENA_WIDTH 1 /* CLK_OP_ENA */
+#define WM9081_CLK_TO_ENA 0x0004 /* CLK_TO_ENA */
+#define WM9081_CLK_TO_ENA_MASK 0x0004 /* CLK_TO_ENA */
+#define WM9081_CLK_TO_ENA_SHIFT 2 /* CLK_TO_ENA */
+#define WM9081_CLK_TO_ENA_WIDTH 1 /* CLK_TO_ENA */
+#define WM9081_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */
+#define WM9081_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */
+#define WM9081_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */
+#define WM9081_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */
+#define WM9081_CLK_SYS_ENA 0x0001 /* CLK_SYS_ENA */
+#define WM9081_CLK_SYS_ENA_MASK 0x0001 /* CLK_SYS_ENA */
+#define WM9081_CLK_SYS_ENA_SHIFT 0 /* CLK_SYS_ENA */
+#define WM9081_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */
+
+/*
+ * R16 (0x10) - FLL Control 1
+ */
+#define WM9081_FLL_HOLD 0x0008 /* FLL_HOLD */
+#define WM9081_FLL_HOLD_MASK 0x0008 /* FLL_HOLD */
+#define WM9081_FLL_HOLD_SHIFT 3 /* FLL_HOLD */
+#define WM9081_FLL_HOLD_WIDTH 1 /* FLL_HOLD */
+#define WM9081_FLL_FRAC 0x0004 /* FLL_FRAC */
+#define WM9081_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */
+#define WM9081_FLL_FRAC_SHIFT 2 /* FLL_FRAC */
+#define WM9081_FLL_FRAC_WIDTH 1 /* FLL_FRAC */
+#define WM9081_FLL_ENA 0x0001 /* FLL_ENA */
+#define WM9081_FLL_ENA_MASK 0x0001 /* FLL_ENA */
+#define WM9081_FLL_ENA_SHIFT 0 /* FLL_ENA */
+#define WM9081_FLL_ENA_WIDTH 1 /* FLL_ENA */
+
+/*
+ * R17 (0x11) - FLL Control 2
+ */
+#define WM9081_FLL_OUTDIV_MASK 0x0700 /* FLL_OUTDIV - [10:8] */
+#define WM9081_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [10:8] */
+#define WM9081_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [10:8] */
+#define WM9081_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */
+#define WM9081_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */
+#define WM9081_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */
+#define WM9081_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */
+#define WM9081_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */
+#define WM9081_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */
+
+/*
+ * R18 (0x12) - FLL Control 3
+ */
+#define WM9081_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */
+#define WM9081_FLL_K_SHIFT 0 /* FLL_K - [15:0] */
+#define WM9081_FLL_K_WIDTH 16 /* FLL_K - [15:0] */
+
+/*
+ * R19 (0x13) - FLL Control 4
+ */
+#define WM9081_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */
+#define WM9081_FLL_N_SHIFT 5 /* FLL_N - [14:5] */
+#define WM9081_FLL_N_WIDTH 10 /* FLL_N - [14:5] */
+#define WM9081_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */
+#define WM9081_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */
+#define WM9081_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */
+
+/*
+ * R20 (0x14) - FLL Control 5
+ */
+#define WM9081_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM9081_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM9081_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM9081_FLL_CLK_SRC_MASK 0x0003 /* FLL_CLK_SRC - [1:0] */
+#define WM9081_FLL_CLK_SRC_SHIFT 0 /* FLL_CLK_SRC - [1:0] */
+#define WM9081_FLL_CLK_SRC_WIDTH 2 /* FLL_CLK_SRC - [1:0] */
+
+/*
+ * R22 (0x16) - Audio Interface 1
+ */
+#define WM9081_AIFDAC_CHAN 0x0040 /* AIFDAC_CHAN */
+#define WM9081_AIFDAC_CHAN_MASK 0x0040 /* AIFDAC_CHAN */
+#define WM9081_AIFDAC_CHAN_SHIFT 6 /* AIFDAC_CHAN */
+#define WM9081_AIFDAC_CHAN_WIDTH 1 /* AIFDAC_CHAN */
+#define WM9081_AIFDAC_TDM_SLOT_MASK 0x0030 /* AIFDAC_TDM_SLOT - [5:4] */
+#define WM9081_AIFDAC_TDM_SLOT_SHIFT 4 /* AIFDAC_TDM_SLOT - [5:4] */
+#define WM9081_AIFDAC_TDM_SLOT_WIDTH 2 /* AIFDAC_TDM_SLOT - [5:4] */
+#define WM9081_AIFDAC_TDM_MODE_MASK 0x000C /* AIFDAC_TDM_MODE - [3:2] */
+#define WM9081_AIFDAC_TDM_MODE_SHIFT 2 /* AIFDAC_TDM_MODE - [3:2] */
+#define WM9081_AIFDAC_TDM_MODE_WIDTH 2 /* AIFDAC_TDM_MODE - [3:2] */
+#define WM9081_DAC_COMP 0x0002 /* DAC_COMP */
+#define WM9081_DAC_COMP_MASK 0x0002 /* DAC_COMP */
+#define WM9081_DAC_COMP_SHIFT 1 /* DAC_COMP */
+#define WM9081_DAC_COMP_WIDTH 1 /* DAC_COMP */
+#define WM9081_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */
+#define WM9081_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */
+#define WM9081_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */
+#define WM9081_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */
+
+/*
+ * R23 (0x17) - Audio Interface 2
+ */
+#define WM9081_AIF_TRIS 0x0200 /* AIF_TRIS */
+#define WM9081_AIF_TRIS_MASK 0x0200 /* AIF_TRIS */
+#define WM9081_AIF_TRIS_SHIFT 9 /* AIF_TRIS */
+#define WM9081_AIF_TRIS_WIDTH 1 /* AIF_TRIS */
+#define WM9081_DAC_DAT_INV 0x0100 /* DAC_DAT_INV */
+#define WM9081_DAC_DAT_INV_MASK 0x0100 /* DAC_DAT_INV */
+#define WM9081_DAC_DAT_INV_SHIFT 8 /* DAC_DAT_INV */
+#define WM9081_DAC_DAT_INV_WIDTH 1 /* DAC_DAT_INV */
+#define WM9081_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */
+#define WM9081_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */
+#define WM9081_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */
+#define WM9081_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */
+#define WM9081_BCLK_DIR 0x0040 /* BCLK_DIR */
+#define WM9081_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */
+#define WM9081_BCLK_DIR_SHIFT 6 /* BCLK_DIR */
+#define WM9081_BCLK_DIR_WIDTH 1 /* BCLK_DIR */
+#define WM9081_LRCLK_DIR 0x0020 /* LRCLK_DIR */
+#define WM9081_LRCLK_DIR_MASK 0x0020 /* LRCLK_DIR */
+#define WM9081_LRCLK_DIR_SHIFT 5 /* LRCLK_DIR */
+#define WM9081_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */
+#define WM9081_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */
+#define WM9081_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */
+#define WM9081_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */
+#define WM9081_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */
+#define WM9081_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */
+#define WM9081_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */
+#define WM9081_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */
+#define WM9081_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */
+#define WM9081_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */
+#define WM9081_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */
+
+/*
+ * R24 (0x18) - Audio Interface 3
+ */
+#define WM9081_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */
+#define WM9081_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */
+#define WM9081_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */
+
+/*
+ * R25 (0x19) - Audio Interface 4
+ */
+#define WM9081_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */
+#define WM9081_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */
+#define WM9081_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */
+
+/*
+ * R26 (0x1A) - Interrupt Status
+ */
+#define WM9081_WSEQ_BUSY_EINT 0x0004 /* WSEQ_BUSY_EINT */
+#define WM9081_WSEQ_BUSY_EINT_MASK 0x0004 /* WSEQ_BUSY_EINT */
+#define WM9081_WSEQ_BUSY_EINT_SHIFT 2 /* WSEQ_BUSY_EINT */
+#define WM9081_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */
+#define WM9081_TSHUT_EINT 0x0001 /* TSHUT_EINT */
+#define WM9081_TSHUT_EINT_MASK 0x0001 /* TSHUT_EINT */
+#define WM9081_TSHUT_EINT_SHIFT 0 /* TSHUT_EINT */
+#define WM9081_TSHUT_EINT_WIDTH 1 /* TSHUT_EINT */
+
+/*
+ * R27 (0x1B) - Interrupt Status Mask
+ */
+#define WM9081_IM_WSEQ_BUSY_EINT 0x0004 /* IM_WSEQ_BUSY_EINT */
+#define WM9081_IM_WSEQ_BUSY_EINT_MASK 0x0004 /* IM_WSEQ_BUSY_EINT */
+#define WM9081_IM_WSEQ_BUSY_EINT_SHIFT 2 /* IM_WSEQ_BUSY_EINT */
+#define WM9081_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */
+#define WM9081_IM_TSHUT_EINT 0x0001 /* IM_TSHUT_EINT */
+#define WM9081_IM_TSHUT_EINT_MASK 0x0001 /* IM_TSHUT_EINT */
+#define WM9081_IM_TSHUT_EINT_SHIFT 0 /* IM_TSHUT_EINT */
+#define WM9081_IM_TSHUT_EINT_WIDTH 1 /* IM_TSHUT_EINT */
+
+/*
+ * R28 (0x1C) - Interrupt Polarity
+ */
+#define WM9081_TSHUT_INV 0x0001 /* TSHUT_INV */
+#define WM9081_TSHUT_INV_MASK 0x0001 /* TSHUT_INV */
+#define WM9081_TSHUT_INV_SHIFT 0 /* TSHUT_INV */
+#define WM9081_TSHUT_INV_WIDTH 1 /* TSHUT_INV */
+
+/*
+ * R29 (0x1D) - Interrupt Control
+ */
+#define WM9081_IRQ_POL 0x8000 /* IRQ_POL */
+#define WM9081_IRQ_POL_MASK 0x8000 /* IRQ_POL */
+#define WM9081_IRQ_POL_SHIFT 15 /* IRQ_POL */
+#define WM9081_IRQ_POL_WIDTH 1 /* IRQ_POL */
+#define WM9081_IRQ_OP_CTRL 0x0001 /* IRQ_OP_CTRL */
+#define WM9081_IRQ_OP_CTRL_MASK 0x0001 /* IRQ_OP_CTRL */
+#define WM9081_IRQ_OP_CTRL_SHIFT 0 /* IRQ_OP_CTRL */
+#define WM9081_IRQ_OP_CTRL_WIDTH 1 /* IRQ_OP_CTRL */
+
+/*
+ * R30 (0x1E) - DAC Digital 1
+ */
+#define WM9081_DAC_VOL_MASK 0x00FF /* DAC_VOL - [7:0] */
+#define WM9081_DAC_VOL_SHIFT 0 /* DAC_VOL - [7:0] */
+#define WM9081_DAC_VOL_WIDTH 8 /* DAC_VOL - [7:0] */
+
+/*
+ * R31 (0x1F) - DAC Digital 2
+ */
+#define WM9081_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */
+#define WM9081_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */
+#define WM9081_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */
+#define WM9081_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
+#define WM9081_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */
+#define WM9081_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */
+#define WM9081_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */
+#define WM9081_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */
+#define WM9081_DAC_MUTE 0x0008 /* DAC_MUTE */
+#define WM9081_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */
+#define WM9081_DAC_MUTE_SHIFT 3 /* DAC_MUTE */
+#define WM9081_DAC_MUTE_WIDTH 1 /* DAC_MUTE */
+#define WM9081_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
+#define WM9081_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
+#define WM9081_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
+
+/*
+ * R32 (0x20) - DRC 1
+ */
+#define WM9081_DRC_ENA 0x8000 /* DRC_ENA */
+#define WM9081_DRC_ENA_MASK 0x8000 /* DRC_ENA */
+#define WM9081_DRC_ENA_SHIFT 15 /* DRC_ENA */
+#define WM9081_DRC_ENA_WIDTH 1 /* DRC_ENA */
+#define WM9081_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM9081_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM9081_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM9081_DRC_FF_DLY 0x0020 /* DRC_FF_DLY */
+#define WM9081_DRC_FF_DLY_MASK 0x0020 /* DRC_FF_DLY */
+#define WM9081_DRC_FF_DLY_SHIFT 5 /* DRC_FF_DLY */
+#define WM9081_DRC_FF_DLY_WIDTH 1 /* DRC_FF_DLY */
+#define WM9081_DRC_QR 0x0004 /* DRC_QR */
+#define WM9081_DRC_QR_MASK 0x0004 /* DRC_QR */
+#define WM9081_DRC_QR_SHIFT 2 /* DRC_QR */
+#define WM9081_DRC_QR_WIDTH 1 /* DRC_QR */
+#define WM9081_DRC_ANTICLIP 0x0002 /* DRC_ANTICLIP */
+#define WM9081_DRC_ANTICLIP_MASK 0x0002 /* DRC_ANTICLIP */
+#define WM9081_DRC_ANTICLIP_SHIFT 1 /* DRC_ANTICLIP */
+#define WM9081_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */
+
+/*
+ * R33 (0x21) - DRC 2
+ */
+#define WM9081_DRC_ATK_MASK 0xF000 /* DRC_ATK - [15:12] */
+#define WM9081_DRC_ATK_SHIFT 12 /* DRC_ATK - [15:12] */
+#define WM9081_DRC_ATK_WIDTH 4 /* DRC_ATK - [15:12] */
+#define WM9081_DRC_DCY_MASK 0x0F00 /* DRC_DCY - [11:8] */
+#define WM9081_DRC_DCY_SHIFT 8 /* DRC_DCY - [11:8] */
+#define WM9081_DRC_DCY_WIDTH 4 /* DRC_DCY - [11:8] */
+#define WM9081_DRC_QR_THR_MASK 0x00C0 /* DRC_QR_THR - [7:6] */
+#define WM9081_DRC_QR_THR_SHIFT 6 /* DRC_QR_THR - [7:6] */
+#define WM9081_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [7:6] */
+#define WM9081_DRC_QR_DCY_MASK 0x0030 /* DRC_QR_DCY - [5:4] */
+#define WM9081_DRC_QR_DCY_SHIFT 4 /* DRC_QR_DCY - [5:4] */
+#define WM9081_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [5:4] */
+#define WM9081_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */
+#define WM9081_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */
+#define WM9081_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */
+#define WM9081_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */
+#define WM9081_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */
+#define WM9081_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R34 (0x22) - DRC 3
+ */
+#define WM9081_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */
+#define WM9081_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */
+#define WM9081_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */
+#define WM9081_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */
+#define WM9081_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */
+#define WM9081_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */
+
+/*
+ * R35 (0x23) - DRC 4
+ */
+#define WM9081_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */
+#define WM9081_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */
+#define WM9081_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */
+#define WM9081_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */
+#define WM9081_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */
+#define WM9081_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */
+
+/*
+ * R38 (0x26) - Write Sequencer 1
+ */
+#define WM9081_WSEQ_ENA 0x8000 /* WSEQ_ENA */
+#define WM9081_WSEQ_ENA_MASK 0x8000 /* WSEQ_ENA */
+#define WM9081_WSEQ_ENA_SHIFT 15 /* WSEQ_ENA */
+#define WM9081_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM9081_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
+#define WM9081_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
+#define WM9081_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
+#define WM9081_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM9081_WSEQ_START 0x0100 /* WSEQ_START */
+#define WM9081_WSEQ_START_MASK 0x0100 /* WSEQ_START */
+#define WM9081_WSEQ_START_SHIFT 8 /* WSEQ_START */
+#define WM9081_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM9081_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */
+#define WM9081_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */
+#define WM9081_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */
+
+/*
+ * R39 (0x27) - Write Sequencer 2
+ */
+#define WM9081_WSEQ_CURRENT_INDEX_MASK 0x07F0 /* WSEQ_CURRENT_INDEX - [10:4] */
+#define WM9081_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [10:4] */
+#define WM9081_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [10:4] */
+#define WM9081_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
+#define WM9081_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
+#define WM9081_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
+#define WM9081_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+
+/*
+ * R40 (0x28) - MW Slave 1
+ */
+#define WM9081_SPI_CFG 0x0020 /* SPI_CFG */
+#define WM9081_SPI_CFG_MASK 0x0020 /* SPI_CFG */
+#define WM9081_SPI_CFG_SHIFT 5 /* SPI_CFG */
+#define WM9081_SPI_CFG_WIDTH 1 /* SPI_CFG */
+#define WM9081_SPI_4WIRE 0x0010 /* SPI_4WIRE */
+#define WM9081_SPI_4WIRE_MASK 0x0010 /* SPI_4WIRE */
+#define WM9081_SPI_4WIRE_SHIFT 4 /* SPI_4WIRE */
+#define WM9081_SPI_4WIRE_WIDTH 1 /* SPI_4WIRE */
+#define WM9081_ARA_ENA 0x0008 /* ARA_ENA */
+#define WM9081_ARA_ENA_MASK 0x0008 /* ARA_ENA */
+#define WM9081_ARA_ENA_SHIFT 3 /* ARA_ENA */
+#define WM9081_ARA_ENA_WIDTH 1 /* ARA_ENA */
+#define WM9081_AUTO_INC 0x0002 /* AUTO_INC */
+#define WM9081_AUTO_INC_MASK 0x0002 /* AUTO_INC */
+#define WM9081_AUTO_INC_SHIFT 1 /* AUTO_INC */
+#define WM9081_AUTO_INC_WIDTH 1 /* AUTO_INC */
+
+/*
+ * R42 (0x2A) - EQ 1
+ */
+#define WM9081_EQ_B1_GAIN_MASK 0xF800 /* EQ_B1_GAIN - [15:11] */
+#define WM9081_EQ_B1_GAIN_SHIFT 11 /* EQ_B1_GAIN - [15:11] */
+#define WM9081_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [15:11] */
+#define WM9081_EQ_B2_GAIN_MASK 0x07C0 /* EQ_B2_GAIN - [10:6] */
+#define WM9081_EQ_B2_GAIN_SHIFT 6 /* EQ_B2_GAIN - [10:6] */
+#define WM9081_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [10:6] */
+#define WM9081_EQ_B4_GAIN_MASK 0x003E /* EQ_B4_GAIN - [5:1] */
+#define WM9081_EQ_B4_GAIN_SHIFT 1 /* EQ_B4_GAIN - [5:1] */
+#define WM9081_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [5:1] */
+#define WM9081_EQ_ENA 0x0001 /* EQ_ENA */
+#define WM9081_EQ_ENA_MASK 0x0001 /* EQ_ENA */
+#define WM9081_EQ_ENA_SHIFT 0 /* EQ_ENA */
+#define WM9081_EQ_ENA_WIDTH 1 /* EQ_ENA */
+
+/*
+ * R43 (0x2B) - EQ 2
+ */
+#define WM9081_EQ_B3_GAIN_MASK 0xF800 /* EQ_B3_GAIN - [15:11] */
+#define WM9081_EQ_B3_GAIN_SHIFT 11 /* EQ_B3_GAIN - [15:11] */
+#define WM9081_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [15:11] */
+#define WM9081_EQ_B5_GAIN_MASK 0x07C0 /* EQ_B5_GAIN - [10:6] */
+#define WM9081_EQ_B5_GAIN_SHIFT 6 /* EQ_B5_GAIN - [10:6] */
+#define WM9081_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [10:6] */
+
+/*
+ * R44 (0x2C) - EQ 3
+ */
+#define WM9081_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */
+#define WM9081_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */
+#define WM9081_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */
+
+/*
+ * R45 (0x2D) - EQ 4
+ */
+#define WM9081_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */
+#define WM9081_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */
+#define WM9081_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */
+
+/*
+ * R46 (0x2E) - EQ 5
+ */
+#define WM9081_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */
+#define WM9081_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */
+#define WM9081_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */
+
+/*
+ * R47 (0x2F) - EQ 6
+ */
+#define WM9081_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */
+#define WM9081_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */
+#define WM9081_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */
+
+/*
+ * R48 (0x30) - EQ 7
+ */
+#define WM9081_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */
+#define WM9081_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */
+#define WM9081_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */
+
+/*
+ * R49 (0x31) - EQ 8
+ */
+#define WM9081_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */
+#define WM9081_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */
+#define WM9081_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */
+
+/*
+ * R50 (0x32) - EQ 9
+ */
+#define WM9081_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */
+#define WM9081_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */
+#define WM9081_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */
+
+/*
+ * R51 (0x33) - EQ 10
+ */
+#define WM9081_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */
+#define WM9081_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */
+#define WM9081_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */
+
+/*
+ * R52 (0x34) - EQ 11
+ */
+#define WM9081_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */
+#define WM9081_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */
+#define WM9081_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */
+
+/*
+ * R53 (0x35) - EQ 12
+ */
+#define WM9081_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */
+#define WM9081_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */
+#define WM9081_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */
+
+/*
+ * R54 (0x36) - EQ 13
+ */
+#define WM9081_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */
+#define WM9081_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */
+#define WM9081_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */
+
+/*
+ * R55 (0x37) - EQ 14
+ */
+#define WM9081_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */
+#define WM9081_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */
+#define WM9081_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */
+
+/*
+ * R56 (0x38) - EQ 15
+ */
+#define WM9081_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */
+#define WM9081_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */
+#define WM9081_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */
+
+/*
+ * R57 (0x39) - EQ 16
+ */
+#define WM9081_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */
+#define WM9081_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */
+#define WM9081_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */
+
+/*
+ * R58 (0x3A) - EQ 17
+ */
+#define WM9081_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */
+#define WM9081_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */
+#define WM9081_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */
+
+/*
+ * R59 (0x3B) - EQ 18
+ */
+#define WM9081_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */
+#define WM9081_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */
+#define WM9081_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */
+
+/*
+ * R60 (0x3C) - EQ 19
+ */
+#define WM9081_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */
+#define WM9081_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */
+#define WM9081_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */
+
+/*
+ * R61 (0x3D) - EQ 20
+ */
+#define WM9081_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */
+#define WM9081_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */
+#define WM9081_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */
+
+
+#endif
diff --git a/sound/soc/codecs/wm9090.c b/sound/soc/codecs/wm9090.c
new file mode 100644
index 00000000..4de12203
--- /dev/null
+++ b/sound/soc/codecs/wm9090.c
@@ -0,0 +1,715 @@
+/*
+ * ALSA SoC WM9090 driver
+ *
+ * Copyright 2009, 2010 Wolfson Microelectronics
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/wm9090.h>
+
+#include "wm9090.h"
+
+static const u16 wm9090_reg_defaults[] = {
+ 0x9093, /* R0 - Software Reset */
+ 0x0006, /* R1 - Power Management (1) */
+ 0x6000, /* R2 - Power Management (2) */
+ 0x0000, /* R3 - Power Management (3) */
+ 0x0000, /* R4 */
+ 0x0000, /* R5 */
+ 0x01C0, /* R6 - Clocking 1 */
+ 0x0000, /* R7 */
+ 0x0000, /* R8 */
+ 0x0000, /* R9 */
+ 0x0000, /* R10 */
+ 0x0000, /* R11 */
+ 0x0000, /* R12 */
+ 0x0000, /* R13 */
+ 0x0000, /* R14 */
+ 0x0000, /* R15 */
+ 0x0000, /* R16 */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 */
+ 0x0000, /* R19 */
+ 0x0000, /* R20 */
+ 0x0000, /* R21 */
+ 0x0003, /* R22 - IN1 Line Control */
+ 0x0003, /* R23 - IN2 Line Control */
+ 0x0083, /* R24 - IN1 Line Input A Volume */
+ 0x0083, /* R25 - IN1 Line Input B Volume */
+ 0x0083, /* R26 - IN2 Line Input A Volume */
+ 0x0083, /* R27 - IN2 Line Input B Volume */
+ 0x002D, /* R28 - Left Output Volume */
+ 0x002D, /* R29 - Right Output Volume */
+ 0x0000, /* R30 */
+ 0x0000, /* R31 */
+ 0x0000, /* R32 */
+ 0x0000, /* R33 */
+ 0x0100, /* R34 - SPKMIXL Attenuation */
+ 0x0000, /* R35 */
+ 0x0010, /* R36 - SPKOUT Mixers */
+ 0x0140, /* R37 - ClassD3 */
+ 0x0039, /* R38 - Speaker Volume Left */
+ 0x0000, /* R39 */
+ 0x0000, /* R40 */
+ 0x0000, /* R41 */
+ 0x0000, /* R42 */
+ 0x0000, /* R43 */
+ 0x0000, /* R44 */
+ 0x0000, /* R45 - Output Mixer1 */
+ 0x0000, /* R46 - Output Mixer2 */
+ 0x0100, /* R47 - Output Mixer3 */
+ 0x0100, /* R48 - Output Mixer4 */
+ 0x0000, /* R49 */
+ 0x0000, /* R50 */
+ 0x0000, /* R51 */
+ 0x0000, /* R52 */
+ 0x0000, /* R53 */
+ 0x0000, /* R54 - Speaker Mixer */
+ 0x0000, /* R55 */
+ 0x0000, /* R56 */
+ 0x000D, /* R57 - AntiPOP2 */
+ 0x0000, /* R58 */
+ 0x0000, /* R59 */
+ 0x0000, /* R60 */
+ 0x0000, /* R61 */
+ 0x0000, /* R62 */
+ 0x0000, /* R63 */
+ 0x0000, /* R64 */
+ 0x0000, /* R65 */
+ 0x0000, /* R66 */
+ 0x0000, /* R67 */
+ 0x0000, /* R68 */
+ 0x0000, /* R69 */
+ 0x0000, /* R70 - Write Sequencer 0 */
+ 0x0000, /* R71 - Write Sequencer 1 */
+ 0x0000, /* R72 - Write Sequencer 2 */
+ 0x0000, /* R73 - Write Sequencer 3 */
+ 0x0000, /* R74 - Write Sequencer 4 */
+ 0x0000, /* R75 - Write Sequencer 5 */
+ 0x1F25, /* R76 - Charge Pump 1 */
+ 0x0000, /* R77 */
+ 0x0000, /* R78 */
+ 0x0000, /* R79 */
+ 0x0000, /* R80 */
+ 0x0000, /* R81 */
+ 0x0000, /* R82 */
+ 0x0000, /* R83 */
+ 0x0000, /* R84 - DC Servo 0 */
+ 0x054A, /* R85 - DC Servo 1 */
+ 0x0000, /* R86 */
+ 0x0000, /* R87 - DC Servo 3 */
+ 0x0000, /* R88 - DC Servo Readback 0 */
+ 0x0000, /* R89 - DC Servo Readback 1 */
+ 0x0000, /* R90 - DC Servo Readback 2 */
+ 0x0000, /* R91 */
+ 0x0000, /* R92 */
+ 0x0000, /* R93 */
+ 0x0000, /* R94 */
+ 0x0000, /* R95 */
+ 0x0100, /* R96 - Analogue HP 0 */
+ 0x0000, /* R97 */
+ 0x8640, /* R98 - AGC Control 0 */
+ 0xC000, /* R99 - AGC Control 1 */
+ 0x0200, /* R100 - AGC Control 2 */
+};
+
+/* This struct is used to save the context */
+struct wm9090_priv {
+ struct mutex mutex;
+ struct wm9090_platform_data pdata;
+ void *control_data;
+};
+
+static int wm9090_volatile(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case WM9090_SOFTWARE_RESET:
+ case WM9090_DC_SERVO_0:
+ case WM9090_DC_SERVO_READBACK_0:
+ case WM9090_DC_SERVO_READBACK_1:
+ case WM9090_DC_SERVO_READBACK_2:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static void wait_for_dc_servo(struct snd_soc_codec *codec)
+{
+ unsigned int reg;
+ int count = 0;
+
+ dev_dbg(codec->dev, "Waiting for DC servo...\n");
+ do {
+ count++;
+ msleep(1);
+ reg = snd_soc_read(codec, WM9090_DC_SERVO_READBACK_0);
+ dev_dbg(codec->dev, "DC servo status: %x\n", reg);
+ } while ((reg & WM9090_DCS_CAL_COMPLETE_MASK)
+ != WM9090_DCS_CAL_COMPLETE_MASK && count < 1000);
+
+ if ((reg & WM9090_DCS_CAL_COMPLETE_MASK)
+ != WM9090_DCS_CAL_COMPLETE_MASK)
+ dev_err(codec->dev, "Timed out waiting for DC Servo\n");
+}
+
+static const unsigned int in_tlv[] = {
+ TLV_DB_RANGE_HEAD(6),
+ 0, 0, TLV_DB_SCALE_ITEM(-600, 0, 0),
+ 1, 3, TLV_DB_SCALE_ITEM(-350, 350, 0),
+ 4, 6, TLV_DB_SCALE_ITEM(600, 600, 0),
+};
+static const unsigned int mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 2, TLV_DB_SCALE_ITEM(-1200, 300, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(0, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+static const unsigned int spkboost_tlv[] = {
+ TLV_DB_RANGE_HEAD(7),
+ 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0),
+ 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0),
+};
+
+static const struct snd_kcontrol_new wm9090_controls[] = {
+SOC_SINGLE_TLV("IN1A Volume", WM9090_IN1_LINE_INPUT_A_VOLUME, 0, 6, 0,
+ in_tlv),
+SOC_SINGLE("IN1A Switch", WM9090_IN1_LINE_INPUT_A_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN1A ZC Switch", WM9090_IN1_LINE_INPUT_A_VOLUME, 6, 1, 0),
+
+SOC_SINGLE_TLV("IN2A Volume", WM9090_IN2_LINE_INPUT_A_VOLUME, 0, 6, 0,
+ in_tlv),
+SOC_SINGLE("IN2A Switch", WM9090_IN2_LINE_INPUT_A_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN2A ZC Switch", WM9090_IN2_LINE_INPUT_A_VOLUME, 6, 1, 0),
+
+SOC_SINGLE("MIXOUTL Switch", WM9090_OUTPUT_MIXER3, 8, 1, 1),
+SOC_SINGLE_TLV("MIXOUTL IN1A Volume", WM9090_OUTPUT_MIXER3, 6, 3, 1,
+ mix_tlv),
+SOC_SINGLE_TLV("MIXOUTL IN2A Volume", WM9090_OUTPUT_MIXER3, 2, 3, 1,
+ mix_tlv),
+
+SOC_SINGLE("MIXOUTR Switch", WM9090_OUTPUT_MIXER4, 8, 1, 1),
+SOC_SINGLE_TLV("MIXOUTR IN1A Volume", WM9090_OUTPUT_MIXER4, 6, 3, 1,
+ mix_tlv),
+SOC_SINGLE_TLV("MIXOUTR IN2A Volume", WM9090_OUTPUT_MIXER4, 2, 3, 1,
+ mix_tlv),
+
+SOC_SINGLE("SPKMIX Switch", WM9090_SPKMIXL_ATTENUATION, 8, 1, 1),
+SOC_SINGLE_TLV("SPKMIX IN1A Volume", WM9090_SPKMIXL_ATTENUATION, 6, 3, 1,
+ mix_tlv),
+SOC_SINGLE_TLV("SPKMIX IN2A Volume", WM9090_SPKMIXL_ATTENUATION, 2, 3, 1,
+ mix_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Volume", WM9090_LEFT_OUTPUT_VOLUME,
+ WM9090_RIGHT_OUTPUT_VOLUME, 0, 63, 0, out_tlv),
+SOC_DOUBLE_R("Headphone Switch", WM9090_LEFT_OUTPUT_VOLUME,
+ WM9090_RIGHT_OUTPUT_VOLUME, 6, 1, 1),
+SOC_DOUBLE_R("Headphone ZC Switch", WM9090_LEFT_OUTPUT_VOLUME,
+ WM9090_RIGHT_OUTPUT_VOLUME, 7, 1, 0),
+
+SOC_SINGLE_TLV("Speaker Volume", WM9090_SPEAKER_VOLUME_LEFT, 0, 63, 0,
+ out_tlv),
+SOC_SINGLE("Speaker Switch", WM9090_SPEAKER_VOLUME_LEFT, 6, 1, 1),
+SOC_SINGLE("Speaker ZC Switch", WM9090_SPEAKER_VOLUME_LEFT, 7, 1, 0),
+SOC_SINGLE_TLV("Speaker Boost Volume", WM9090_CLASSD3, 3, 7, 0, spkboost_tlv),
+};
+
+static const struct snd_kcontrol_new wm9090_in1_se_controls[] = {
+SOC_SINGLE_TLV("IN1B Volume", WM9090_IN1_LINE_INPUT_B_VOLUME, 0, 6, 0,
+ in_tlv),
+SOC_SINGLE("IN1B Switch", WM9090_IN1_LINE_INPUT_B_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN1B ZC Switch", WM9090_IN1_LINE_INPUT_B_VOLUME, 6, 1, 0),
+
+SOC_SINGLE_TLV("SPKMIX IN1B Volume", WM9090_SPKMIXL_ATTENUATION, 4, 3, 1,
+ mix_tlv),
+SOC_SINGLE_TLV("MIXOUTL IN1B Volume", WM9090_OUTPUT_MIXER3, 4, 3, 1,
+ mix_tlv),
+SOC_SINGLE_TLV("MIXOUTR IN1B Volume", WM9090_OUTPUT_MIXER4, 4, 3, 1,
+ mix_tlv),
+};
+
+static const struct snd_kcontrol_new wm9090_in2_se_controls[] = {
+SOC_SINGLE_TLV("IN2B Volume", WM9090_IN2_LINE_INPUT_B_VOLUME, 0, 6, 0,
+ in_tlv),
+SOC_SINGLE("IN2B Switch", WM9090_IN2_LINE_INPUT_B_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN2B ZC Switch", WM9090_IN2_LINE_INPUT_B_VOLUME, 6, 1, 0),
+
+SOC_SINGLE_TLV("SPKMIX IN2B Volume", WM9090_SPKMIXL_ATTENUATION, 0, 3, 1,
+ mix_tlv),
+SOC_SINGLE_TLV("MIXOUTL IN2B Volume", WM9090_OUTPUT_MIXER3, 0, 3, 1,
+ mix_tlv),
+SOC_SINGLE_TLV("MIXOUTR IN2B Volume", WM9090_OUTPUT_MIXER4, 0, 3, 1,
+ mix_tlv),
+};
+
+static int hp_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ unsigned int reg = snd_soc_read(codec, WM9090_ANALOGUE_HP_0);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ snd_soc_update_bits(codec, WM9090_CHARGE_PUMP_1,
+ WM9090_CP_ENA, WM9090_CP_ENA);
+
+ msleep(5);
+
+ snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1,
+ WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA,
+ WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA);
+
+ reg |= WM9090_HPOUT1L_DLY | WM9090_HPOUT1R_DLY;
+ snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg);
+
+ /* Start the DC servo. We don't currently use the
+ * ability to save the state since we don't have full
+ * control of the analogue paths and they can change
+ * DC offsets; see the WM8904 driver for an example of
+ * doing so.
+ */
+ snd_soc_write(codec, WM9090_DC_SERVO_0,
+ WM9090_DCS_ENA_CHAN_0 |
+ WM9090_DCS_ENA_CHAN_1 |
+ WM9090_DCS_TRIG_STARTUP_1 |
+ WM9090_DCS_TRIG_STARTUP_0);
+ wait_for_dc_servo(codec);
+
+ reg |= WM9090_HPOUT1R_OUTP | WM9090_HPOUT1R_RMV_SHORT |
+ WM9090_HPOUT1L_OUTP | WM9090_HPOUT1L_RMV_SHORT;
+ snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ reg &= ~(WM9090_HPOUT1L_RMV_SHORT |
+ WM9090_HPOUT1L_DLY |
+ WM9090_HPOUT1L_OUTP |
+ WM9090_HPOUT1R_RMV_SHORT |
+ WM9090_HPOUT1R_DLY |
+ WM9090_HPOUT1R_OUTP);
+
+ snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg);
+
+ snd_soc_write(codec, WM9090_DC_SERVO_0, 0);
+
+ snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1,
+ WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA,
+ 0);
+
+ snd_soc_update_bits(codec, WM9090_CHARGE_PUMP_1,
+ WM9090_CP_ENA, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new spkmix[] = {
+SOC_DAPM_SINGLE("IN1A Switch", WM9090_SPEAKER_MIXER, 6, 1, 0),
+SOC_DAPM_SINGLE("IN1B Switch", WM9090_SPEAKER_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("IN2A Switch", WM9090_SPEAKER_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("IN2B Switch", WM9090_SPEAKER_MIXER, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new spkout[] = {
+SOC_DAPM_SINGLE("Mixer Switch", WM9090_SPKOUT_MIXERS, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixoutl[] = {
+SOC_DAPM_SINGLE("IN1A Switch", WM9090_OUTPUT_MIXER1, 6, 1, 0),
+SOC_DAPM_SINGLE("IN1B Switch", WM9090_OUTPUT_MIXER1, 4, 1, 0),
+SOC_DAPM_SINGLE("IN2A Switch", WM9090_OUTPUT_MIXER1, 2, 1, 0),
+SOC_DAPM_SINGLE("IN2B Switch", WM9090_OUTPUT_MIXER1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixoutr[] = {
+SOC_DAPM_SINGLE("IN1A Switch", WM9090_OUTPUT_MIXER2, 6, 1, 0),
+SOC_DAPM_SINGLE("IN1B Switch", WM9090_OUTPUT_MIXER2, 4, 1, 0),
+SOC_DAPM_SINGLE("IN2A Switch", WM9090_OUTPUT_MIXER2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN2B Switch", WM9090_OUTPUT_MIXER2, 0, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm9090_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1+"),
+SND_SOC_DAPM_INPUT("IN1-"),
+SND_SOC_DAPM_INPUT("IN2+"),
+SND_SOC_DAPM_INPUT("IN2-"),
+
+SND_SOC_DAPM_SUPPLY("OSC", WM9090_POWER_MANAGEMENT_1, 3, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("IN1A PGA", WM9090_POWER_MANAGEMENT_2, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("IN1B PGA", WM9090_POWER_MANAGEMENT_2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("IN2A PGA", WM9090_POWER_MANAGEMENT_2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("IN2B PGA", WM9090_POWER_MANAGEMENT_2, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("SPKMIX", WM9090_POWER_MANAGEMENT_3, 3, 0,
+ spkmix, ARRAY_SIZE(spkmix)),
+SND_SOC_DAPM_MIXER("MIXOUTL", WM9090_POWER_MANAGEMENT_3, 5, 0,
+ mixoutl, ARRAY_SIZE(mixoutl)),
+SND_SOC_DAPM_MIXER("MIXOUTR", WM9090_POWER_MANAGEMENT_3, 4, 0,
+ mixoutr, ARRAY_SIZE(mixoutr)),
+
+SND_SOC_DAPM_PGA_E("HP PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
+ hp_ev, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_PGA("SPKPGA", WM9090_POWER_MANAGEMENT_3, 8, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("SPKOUT", WM9090_POWER_MANAGEMENT_1, 12, 0,
+ spkout, ARRAY_SIZE(spkout)),
+
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("Speaker"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ { "IN1A PGA", NULL, "IN1+" },
+ { "IN2A PGA", NULL, "IN2+" },
+
+ { "SPKMIX", "IN1A Switch", "IN1A PGA" },
+ { "SPKMIX", "IN2A Switch", "IN2A PGA" },
+
+ { "MIXOUTL", "IN1A Switch", "IN1A PGA" },
+ { "MIXOUTL", "IN2A Switch", "IN2A PGA" },
+
+ { "MIXOUTR", "IN1A Switch", "IN1A PGA" },
+ { "MIXOUTR", "IN2A Switch", "IN2A PGA" },
+
+ { "HP PGA", NULL, "OSC" },
+ { "HP PGA", NULL, "MIXOUTL" },
+ { "HP PGA", NULL, "MIXOUTR" },
+
+ { "HPL", NULL, "HP PGA" },
+ { "HPR", NULL, "HP PGA" },
+
+ { "SPKPGA", NULL, "OSC" },
+ { "SPKPGA", NULL, "SPKMIX" },
+
+ { "SPKOUT", "Mixer Switch", "SPKPGA" },
+
+ { "Speaker", NULL, "SPKOUT" },
+};
+
+static const struct snd_soc_dapm_route audio_map_in1_se[] = {
+ { "IN1B PGA", NULL, "IN1-" },
+
+ { "SPKMIX", "IN1B Switch", "IN1B PGA" },
+ { "MIXOUTL", "IN1B Switch", "IN1B PGA" },
+ { "MIXOUTR", "IN1B Switch", "IN1B PGA" },
+};
+
+static const struct snd_soc_dapm_route audio_map_in1_diff[] = {
+ { "IN1A PGA", NULL, "IN1-" },
+};
+
+static const struct snd_soc_dapm_route audio_map_in2_se[] = {
+ { "IN2B PGA", NULL, "IN2-" },
+
+ { "SPKMIX", "IN2B Switch", "IN2B PGA" },
+ { "MIXOUTL", "IN2B Switch", "IN2B PGA" },
+ { "MIXOUTR", "IN2B Switch", "IN2B PGA" },
+};
+
+static const struct snd_soc_dapm_route audio_map_in2_diff[] = {
+ { "IN2A PGA", NULL, "IN2-" },
+};
+
+static int wm9090_add_controls(struct snd_soc_codec *codec)
+{
+ struct wm9090_priv *wm9090 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int i;
+
+ snd_soc_dapm_new_controls(dapm, wm9090_dapm_widgets,
+ ARRAY_SIZE(wm9090_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_add_controls(codec, wm9090_controls,
+ ARRAY_SIZE(wm9090_controls));
+
+ if (wm9090->pdata.lin1_diff) {
+ snd_soc_dapm_add_routes(dapm, audio_map_in1_diff,
+ ARRAY_SIZE(audio_map_in1_diff));
+ } else {
+ snd_soc_dapm_add_routes(dapm, audio_map_in1_se,
+ ARRAY_SIZE(audio_map_in1_se));
+ snd_soc_add_controls(codec, wm9090_in1_se_controls,
+ ARRAY_SIZE(wm9090_in1_se_controls));
+ }
+
+ if (wm9090->pdata.lin2_diff) {
+ snd_soc_dapm_add_routes(dapm, audio_map_in2_diff,
+ ARRAY_SIZE(audio_map_in2_diff));
+ } else {
+ snd_soc_dapm_add_routes(dapm, audio_map_in2_se,
+ ARRAY_SIZE(audio_map_in2_se));
+ snd_soc_add_controls(codec, wm9090_in2_se_controls,
+ ARRAY_SIZE(wm9090_in2_se_controls));
+ }
+
+ if (wm9090->pdata.agc_ena) {
+ for (i = 0; i < ARRAY_SIZE(wm9090->pdata.agc); i++)
+ snd_soc_write(codec, WM9090_AGC_CONTROL_0 + i,
+ wm9090->pdata.agc[i]);
+ snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_3,
+ WM9090_AGC_ENA, WM9090_AGC_ENA);
+ } else {
+ snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_3,
+ WM9090_AGC_ENA, 0);
+ }
+
+ return 0;
+
+}
+
+/*
+ * The machine driver should call this from their set_bias_level; if there
+ * isn't one then this can just be set as the set_bias_level function.
+ */
+static int wm9090_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 *reg_cache = codec->reg_cache;
+ int i, ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ snd_soc_update_bits(codec, WM9090_ANTIPOP2, WM9090_VMID_ENA,
+ WM9090_VMID_ENA);
+ snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1,
+ WM9090_BIAS_ENA |
+ WM9090_VMID_RES_MASK,
+ WM9090_BIAS_ENA |
+ 1 << WM9090_VMID_RES_SHIFT);
+ msleep(1); /* Probably an overestimate */
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Restore the register cache */
+ for (i = 1; i < codec->driver->reg_cache_size; i++) {
+ if (reg_cache[i] == wm9090_reg_defaults[i])
+ continue;
+ if (wm9090_volatile(codec, i))
+ continue;
+
+ ret = snd_soc_write(codec, i, reg_cache[i]);
+ if (ret != 0)
+ dev_warn(codec->dev,
+ "Failed to restore register %d: %d\n",
+ i, ret);
+ }
+ }
+
+ /* We keep VMID off during standby since the combination of
+ * ground referenced outputs and class D speaker mean that
+ * latency is not an issue.
+ */
+ snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1,
+ WM9090_BIAS_ENA | WM9090_VMID_RES_MASK, 0);
+ snd_soc_update_bits(codec, WM9090_ANTIPOP2,
+ WM9090_VMID_ENA, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static int wm9090_probe(struct snd_soc_codec *codec)
+{
+ struct wm9090_priv *wm9090 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ codec->control_data = wm9090->control_data;
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_read(codec, WM9090_SOFTWARE_RESET);
+ if (ret < 0)
+ return ret;
+ if (ret != wm9090_reg_defaults[WM9090_SOFTWARE_RESET]) {
+ dev_err(codec->dev, "Device is not a WM9090, ID=%x\n", ret);
+ return -EINVAL;
+ }
+
+ ret = snd_soc_write(codec, WM9090_SOFTWARE_RESET, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Configure some defaults; they will be written out when we
+ * bring the bias up.
+ */
+ snd_soc_update_bits(codec, WM9090_IN1_LINE_INPUT_A_VOLUME,
+ WM9090_IN1_VU | WM9090_IN1A_ZC,
+ WM9090_IN1_VU | WM9090_IN1A_ZC);
+ snd_soc_update_bits(codec, WM9090_IN1_LINE_INPUT_B_VOLUME,
+ WM9090_IN1_VU | WM9090_IN1B_ZC,
+ WM9090_IN1_VU | WM9090_IN1B_ZC);
+ snd_soc_update_bits(codec, WM9090_IN2_LINE_INPUT_A_VOLUME,
+ WM9090_IN2_VU | WM9090_IN2A_ZC,
+ WM9090_IN2_VU | WM9090_IN2A_ZC);
+ snd_soc_update_bits(codec, WM9090_IN2_LINE_INPUT_B_VOLUME,
+ WM9090_IN2_VU | WM9090_IN2B_ZC,
+ WM9090_IN2_VU | WM9090_IN2B_ZC);
+ snd_soc_update_bits(codec, WM9090_SPEAKER_VOLUME_LEFT,
+ WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC,
+ WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC);
+ snd_soc_update_bits(codec, WM9090_LEFT_OUTPUT_VOLUME,
+ WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC,
+ WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC);
+ snd_soc_update_bits(codec, WM9090_RIGHT_OUTPUT_VOLUME,
+ WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC,
+ WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC);
+
+ snd_soc_update_bits(codec, WM9090_CLOCKING_1,
+ WM9090_TOCLK_ENA, WM9090_TOCLK_ENA);
+
+ wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ wm9090_add_controls(codec);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm9090_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm9090_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm9090_resume(struct snd_soc_codec *codec)
+{
+ wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define wm9090_suspend NULL
+#define wm9090_resume NULL
+#endif
+
+static int wm9090_remove(struct snd_soc_codec *codec)
+{
+ wm9090_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm9090 = {
+ .probe = wm9090_probe,
+ .remove = wm9090_remove,
+ .suspend = wm9090_suspend,
+ .resume = wm9090_resume,
+ .set_bias_level = wm9090_set_bias_level,
+ .reg_cache_size = (WM9090_MAX_REGISTER + 1),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm9090_reg_defaults,
+ .volatile_register = wm9090_volatile,
+};
+
+static int wm9090_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm9090_priv *wm9090;
+ int ret;
+
+ wm9090 = kzalloc(sizeof(*wm9090), GFP_KERNEL);
+ if (wm9090 == NULL) {
+ dev_err(&i2c->dev, "Can not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ if (i2c->dev.platform_data)
+ memcpy(&wm9090->pdata, i2c->dev.platform_data,
+ sizeof(wm9090->pdata));
+
+ i2c_set_clientdata(i2c, wm9090);
+ wm9090->control_data = i2c;
+ mutex_init(&wm9090->mutex);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm9090, NULL, 0);
+ if (ret < 0)
+ kfree(wm9090);
+ return ret;
+}
+
+static int __devexit wm9090_i2c_remove(struct i2c_client *i2c)
+{
+ struct wm9090_priv *wm9090 = i2c_get_clientdata(i2c);
+
+ snd_soc_unregister_codec(&i2c->dev);
+ kfree(wm9090);
+
+ return 0;
+}
+
+static const struct i2c_device_id wm9090_id[] = {
+ { "wm9090", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm9090_id);
+
+static struct i2c_driver wm9090_i2c_driver = {
+ .driver = {
+ .name = "wm9090-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm9090_i2c_probe,
+ .remove = __devexit_p(wm9090_i2c_remove),
+ .id_table = wm9090_id,
+};
+
+static int __init wm9090_init(void)
+{
+ return i2c_add_driver(&wm9090_i2c_driver);
+}
+module_init(wm9090_init);
+
+static void __exit wm9090_exit(void)
+{
+ i2c_del_driver(&wm9090_i2c_driver);
+}
+module_exit(wm9090_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM9090 ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm9090.h b/sound/soc/codecs/wm9090.h
new file mode 100644
index 00000000..29b9d9fc
--- /dev/null
+++ b/sound/soc/codecs/wm9090.h
@@ -0,0 +1,713 @@
+/*
+ * ALSA SoC WM9090 driver
+ *
+ * Copyright 2009 Wolfson Microelectronics
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef __WM9090_H
+#define __WM9090_H
+
+/*
+ * Register values.
+ */
+#define WM9090_SOFTWARE_RESET 0x00
+#define WM9090_POWER_MANAGEMENT_1 0x01
+#define WM9090_POWER_MANAGEMENT_2 0x02
+#define WM9090_POWER_MANAGEMENT_3 0x03
+#define WM9090_CLOCKING_1 0x06
+#define WM9090_IN1_LINE_CONTROL 0x16
+#define WM9090_IN2_LINE_CONTROL 0x17
+#define WM9090_IN1_LINE_INPUT_A_VOLUME 0x18
+#define WM9090_IN1_LINE_INPUT_B_VOLUME 0x19
+#define WM9090_IN2_LINE_INPUT_A_VOLUME 0x1A
+#define WM9090_IN2_LINE_INPUT_B_VOLUME 0x1B
+#define WM9090_LEFT_OUTPUT_VOLUME 0x1C
+#define WM9090_RIGHT_OUTPUT_VOLUME 0x1D
+#define WM9090_SPKMIXL_ATTENUATION 0x22
+#define WM9090_SPKOUT_MIXERS 0x24
+#define WM9090_CLASSD3 0x25
+#define WM9090_SPEAKER_VOLUME_LEFT 0x26
+#define WM9090_OUTPUT_MIXER1 0x2D
+#define WM9090_OUTPUT_MIXER2 0x2E
+#define WM9090_OUTPUT_MIXER3 0x2F
+#define WM9090_OUTPUT_MIXER4 0x30
+#define WM9090_SPEAKER_MIXER 0x36
+#define WM9090_ANTIPOP2 0x39
+#define WM9090_WRITE_SEQUENCER_0 0x46
+#define WM9090_WRITE_SEQUENCER_1 0x47
+#define WM9090_WRITE_SEQUENCER_2 0x48
+#define WM9090_WRITE_SEQUENCER_3 0x49
+#define WM9090_WRITE_SEQUENCER_4 0x4A
+#define WM9090_WRITE_SEQUENCER_5 0x4B
+#define WM9090_CHARGE_PUMP_1 0x4C
+#define WM9090_DC_SERVO_0 0x54
+#define WM9090_DC_SERVO_1 0x55
+#define WM9090_DC_SERVO_3 0x57
+#define WM9090_DC_SERVO_READBACK_0 0x58
+#define WM9090_DC_SERVO_READBACK_1 0x59
+#define WM9090_DC_SERVO_READBACK_2 0x5A
+#define WM9090_ANALOGUE_HP_0 0x60
+#define WM9090_AGC_CONTROL_0 0x62
+#define WM9090_AGC_CONTROL_1 0x63
+#define WM9090_AGC_CONTROL_2 0x64
+
+#define WM9090_REGISTER_COUNT 40
+#define WM9090_MAX_REGISTER 0x64
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Software Reset
+ */
+#define WM9090_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */
+#define WM9090_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */
+#define WM9090_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM9090_SPKOUTL_ENA 0x1000 /* SPKOUTL_ENA */
+#define WM9090_SPKOUTL_ENA_MASK 0x1000 /* SPKOUTL_ENA */
+#define WM9090_SPKOUTL_ENA_SHIFT 12 /* SPKOUTL_ENA */
+#define WM9090_SPKOUTL_ENA_WIDTH 1 /* SPKOUTL_ENA */
+#define WM9090_HPOUT1L_ENA 0x0200 /* HPOUT1L_ENA */
+#define WM9090_HPOUT1L_ENA_MASK 0x0200 /* HPOUT1L_ENA */
+#define WM9090_HPOUT1L_ENA_SHIFT 9 /* HPOUT1L_ENA */
+#define WM9090_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */
+#define WM9090_HPOUT1R_ENA 0x0100 /* HPOUT1R_ENA */
+#define WM9090_HPOUT1R_ENA_MASK 0x0100 /* HPOUT1R_ENA */
+#define WM9090_HPOUT1R_ENA_SHIFT 8 /* HPOUT1R_ENA */
+#define WM9090_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */
+#define WM9090_OSC_ENA 0x0008 /* OSC_ENA */
+#define WM9090_OSC_ENA_MASK 0x0008 /* OSC_ENA */
+#define WM9090_OSC_ENA_SHIFT 3 /* OSC_ENA */
+#define WM9090_OSC_ENA_WIDTH 1 /* OSC_ENA */
+#define WM9090_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */
+#define WM9090_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */
+#define WM9090_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */
+#define WM9090_BIAS_ENA 0x0001 /* BIAS_ENA */
+#define WM9090_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */
+#define WM9090_BIAS_ENA_SHIFT 0 /* BIAS_ENA */
+#define WM9090_BIAS_ENA_WIDTH 1 /* BIAS_ENA */
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM9090_TSHUT 0x8000 /* TSHUT */
+#define WM9090_TSHUT_MASK 0x8000 /* TSHUT */
+#define WM9090_TSHUT_SHIFT 15 /* TSHUT */
+#define WM9090_TSHUT_WIDTH 1 /* TSHUT */
+#define WM9090_TSHUT_ENA 0x4000 /* TSHUT_ENA */
+#define WM9090_TSHUT_ENA_MASK 0x4000 /* TSHUT_ENA */
+#define WM9090_TSHUT_ENA_SHIFT 14 /* TSHUT_ENA */
+#define WM9090_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */
+#define WM9090_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */
+#define WM9090_TSHUT_OPDIS_MASK 0x2000 /* TSHUT_OPDIS */
+#define WM9090_TSHUT_OPDIS_SHIFT 13 /* TSHUT_OPDIS */
+#define WM9090_TSHUT_OPDIS_WIDTH 1 /* TSHUT_OPDIS */
+#define WM9090_IN1A_ENA 0x0080 /* IN1A_ENA */
+#define WM9090_IN1A_ENA_MASK 0x0080 /* IN1A_ENA */
+#define WM9090_IN1A_ENA_SHIFT 7 /* IN1A_ENA */
+#define WM9090_IN1A_ENA_WIDTH 1 /* IN1A_ENA */
+#define WM9090_IN1B_ENA 0x0040 /* IN1B_ENA */
+#define WM9090_IN1B_ENA_MASK 0x0040 /* IN1B_ENA */
+#define WM9090_IN1B_ENA_SHIFT 6 /* IN1B_ENA */
+#define WM9090_IN1B_ENA_WIDTH 1 /* IN1B_ENA */
+#define WM9090_IN2A_ENA 0x0020 /* IN2A_ENA */
+#define WM9090_IN2A_ENA_MASK 0x0020 /* IN2A_ENA */
+#define WM9090_IN2A_ENA_SHIFT 5 /* IN2A_ENA */
+#define WM9090_IN2A_ENA_WIDTH 1 /* IN2A_ENA */
+#define WM9090_IN2B_ENA 0x0010 /* IN2B_ENA */
+#define WM9090_IN2B_ENA_MASK 0x0010 /* IN2B_ENA */
+#define WM9090_IN2B_ENA_SHIFT 4 /* IN2B_ENA */
+#define WM9090_IN2B_ENA_WIDTH 1 /* IN2B_ENA */
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM9090_AGC_ENA 0x4000 /* AGC_ENA */
+#define WM9090_AGC_ENA_MASK 0x4000 /* AGC_ENA */
+#define WM9090_AGC_ENA_SHIFT 14 /* AGC_ENA */
+#define WM9090_AGC_ENA_WIDTH 1 /* AGC_ENA */
+#define WM9090_SPKLVOL_ENA 0x0100 /* SPKLVOL_ENA */
+#define WM9090_SPKLVOL_ENA_MASK 0x0100 /* SPKLVOL_ENA */
+#define WM9090_SPKLVOL_ENA_SHIFT 8 /* SPKLVOL_ENA */
+#define WM9090_SPKLVOL_ENA_WIDTH 1 /* SPKLVOL_ENA */
+#define WM9090_MIXOUTL_ENA 0x0020 /* MIXOUTL_ENA */
+#define WM9090_MIXOUTL_ENA_MASK 0x0020 /* MIXOUTL_ENA */
+#define WM9090_MIXOUTL_ENA_SHIFT 5 /* MIXOUTL_ENA */
+#define WM9090_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */
+#define WM9090_MIXOUTR_ENA 0x0010 /* MIXOUTR_ENA */
+#define WM9090_MIXOUTR_ENA_MASK 0x0010 /* MIXOUTR_ENA */
+#define WM9090_MIXOUTR_ENA_SHIFT 4 /* MIXOUTR_ENA */
+#define WM9090_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */
+#define WM9090_SPKMIX_ENA 0x0008 /* SPKMIX_ENA */
+#define WM9090_SPKMIX_ENA_MASK 0x0008 /* SPKMIX_ENA */
+#define WM9090_SPKMIX_ENA_SHIFT 3 /* SPKMIX_ENA */
+#define WM9090_SPKMIX_ENA_WIDTH 1 /* SPKMIX_ENA */
+
+/*
+ * R6 (0x06) - Clocking 1
+ */
+#define WM9090_TOCLK_RATE 0x8000 /* TOCLK_RATE */
+#define WM9090_TOCLK_RATE_MASK 0x8000 /* TOCLK_RATE */
+#define WM9090_TOCLK_RATE_SHIFT 15 /* TOCLK_RATE */
+#define WM9090_TOCLK_RATE_WIDTH 1 /* TOCLK_RATE */
+#define WM9090_TOCLK_ENA 0x4000 /* TOCLK_ENA */
+#define WM9090_TOCLK_ENA_MASK 0x4000 /* TOCLK_ENA */
+#define WM9090_TOCLK_ENA_SHIFT 14 /* TOCLK_ENA */
+#define WM9090_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */
+
+/*
+ * R22 (0x16) - IN1 Line Control
+ */
+#define WM9090_IN1_DIFF 0x0002 /* IN1_DIFF */
+#define WM9090_IN1_DIFF_MASK 0x0002 /* IN1_DIFF */
+#define WM9090_IN1_DIFF_SHIFT 1 /* IN1_DIFF */
+#define WM9090_IN1_DIFF_WIDTH 1 /* IN1_DIFF */
+#define WM9090_IN1_CLAMP 0x0001 /* IN1_CLAMP */
+#define WM9090_IN1_CLAMP_MASK 0x0001 /* IN1_CLAMP */
+#define WM9090_IN1_CLAMP_SHIFT 0 /* IN1_CLAMP */
+#define WM9090_IN1_CLAMP_WIDTH 1 /* IN1_CLAMP */
+
+/*
+ * R23 (0x17) - IN2 Line Control
+ */
+#define WM9090_IN2_DIFF 0x0002 /* IN2_DIFF */
+#define WM9090_IN2_DIFF_MASK 0x0002 /* IN2_DIFF */
+#define WM9090_IN2_DIFF_SHIFT 1 /* IN2_DIFF */
+#define WM9090_IN2_DIFF_WIDTH 1 /* IN2_DIFF */
+#define WM9090_IN2_CLAMP 0x0001 /* IN2_CLAMP */
+#define WM9090_IN2_CLAMP_MASK 0x0001 /* IN2_CLAMP */
+#define WM9090_IN2_CLAMP_SHIFT 0 /* IN2_CLAMP */
+#define WM9090_IN2_CLAMP_WIDTH 1 /* IN2_CLAMP */
+
+/*
+ * R24 (0x18) - IN1 Line Input A Volume
+ */
+#define WM9090_IN1_VU 0x0100 /* IN1_VU */
+#define WM9090_IN1_VU_MASK 0x0100 /* IN1_VU */
+#define WM9090_IN1_VU_SHIFT 8 /* IN1_VU */
+#define WM9090_IN1_VU_WIDTH 1 /* IN1_VU */
+#define WM9090_IN1A_MUTE 0x0080 /* IN1A_MUTE */
+#define WM9090_IN1A_MUTE_MASK 0x0080 /* IN1A_MUTE */
+#define WM9090_IN1A_MUTE_SHIFT 7 /* IN1A_MUTE */
+#define WM9090_IN1A_MUTE_WIDTH 1 /* IN1A_MUTE */
+#define WM9090_IN1A_ZC 0x0040 /* IN1A_ZC */
+#define WM9090_IN1A_ZC_MASK 0x0040 /* IN1A_ZC */
+#define WM9090_IN1A_ZC_SHIFT 6 /* IN1A_ZC */
+#define WM9090_IN1A_ZC_WIDTH 1 /* IN1A_ZC */
+#define WM9090_IN1A_VOL_MASK 0x0007 /* IN1A_VOL - [2:0] */
+#define WM9090_IN1A_VOL_SHIFT 0 /* IN1A_VOL - [2:0] */
+#define WM9090_IN1A_VOL_WIDTH 3 /* IN1A_VOL - [2:0] */
+
+/*
+ * R25 (0x19) - IN1 Line Input B Volume
+ */
+#define WM9090_IN1_VU 0x0100 /* IN1_VU */
+#define WM9090_IN1_VU_MASK 0x0100 /* IN1_VU */
+#define WM9090_IN1_VU_SHIFT 8 /* IN1_VU */
+#define WM9090_IN1_VU_WIDTH 1 /* IN1_VU */
+#define WM9090_IN1B_MUTE 0x0080 /* IN1B_MUTE */
+#define WM9090_IN1B_MUTE_MASK 0x0080 /* IN1B_MUTE */
+#define WM9090_IN1B_MUTE_SHIFT 7 /* IN1B_MUTE */
+#define WM9090_IN1B_MUTE_WIDTH 1 /* IN1B_MUTE */
+#define WM9090_IN1B_ZC 0x0040 /* IN1B_ZC */
+#define WM9090_IN1B_ZC_MASK 0x0040 /* IN1B_ZC */
+#define WM9090_IN1B_ZC_SHIFT 6 /* IN1B_ZC */
+#define WM9090_IN1B_ZC_WIDTH 1 /* IN1B_ZC */
+#define WM9090_IN1B_VOL_MASK 0x0007 /* IN1B_VOL - [2:0] */
+#define WM9090_IN1B_VOL_SHIFT 0 /* IN1B_VOL - [2:0] */
+#define WM9090_IN1B_VOL_WIDTH 3 /* IN1B_VOL - [2:0] */
+
+/*
+ * R26 (0x1A) - IN2 Line Input A Volume
+ */
+#define WM9090_IN2_VU 0x0100 /* IN2_VU */
+#define WM9090_IN2_VU_MASK 0x0100 /* IN2_VU */
+#define WM9090_IN2_VU_SHIFT 8 /* IN2_VU */
+#define WM9090_IN2_VU_WIDTH 1 /* IN2_VU */
+#define WM9090_IN2A_MUTE 0x0080 /* IN2A_MUTE */
+#define WM9090_IN2A_MUTE_MASK 0x0080 /* IN2A_MUTE */
+#define WM9090_IN2A_MUTE_SHIFT 7 /* IN2A_MUTE */
+#define WM9090_IN2A_MUTE_WIDTH 1 /* IN2A_MUTE */
+#define WM9090_IN2A_ZC 0x0040 /* IN2A_ZC */
+#define WM9090_IN2A_ZC_MASK 0x0040 /* IN2A_ZC */
+#define WM9090_IN2A_ZC_SHIFT 6 /* IN2A_ZC */
+#define WM9090_IN2A_ZC_WIDTH 1 /* IN2A_ZC */
+#define WM9090_IN2A_VOL_MASK 0x0007 /* IN2A_VOL - [2:0] */
+#define WM9090_IN2A_VOL_SHIFT 0 /* IN2A_VOL - [2:0] */
+#define WM9090_IN2A_VOL_WIDTH 3 /* IN2A_VOL - [2:0] */
+
+/*
+ * R27 (0x1B) - IN2 Line Input B Volume
+ */
+#define WM9090_IN2_VU 0x0100 /* IN2_VU */
+#define WM9090_IN2_VU_MASK 0x0100 /* IN2_VU */
+#define WM9090_IN2_VU_SHIFT 8 /* IN2_VU */
+#define WM9090_IN2_VU_WIDTH 1 /* IN2_VU */
+#define WM9090_IN2B_MUTE 0x0080 /* IN2B_MUTE */
+#define WM9090_IN2B_MUTE_MASK 0x0080 /* IN2B_MUTE */
+#define WM9090_IN2B_MUTE_SHIFT 7 /* IN2B_MUTE */
+#define WM9090_IN2B_MUTE_WIDTH 1 /* IN2B_MUTE */
+#define WM9090_IN2B_ZC 0x0040 /* IN2B_ZC */
+#define WM9090_IN2B_ZC_MASK 0x0040 /* IN2B_ZC */
+#define WM9090_IN2B_ZC_SHIFT 6 /* IN2B_ZC */
+#define WM9090_IN2B_ZC_WIDTH 1 /* IN2B_ZC */
+#define WM9090_IN2B_VOL_MASK 0x0007 /* IN2B_VOL - [2:0] */
+#define WM9090_IN2B_VOL_SHIFT 0 /* IN2B_VOL - [2:0] */
+#define WM9090_IN2B_VOL_WIDTH 3 /* IN2B_VOL - [2:0] */
+
+/*
+ * R28 (0x1C) - Left Output Volume
+ */
+#define WM9090_HPOUT1_VU 0x0100 /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */
+#define WM9090_HPOUT1L_ZC 0x0080 /* HPOUT1L_ZC */
+#define WM9090_HPOUT1L_ZC_MASK 0x0080 /* HPOUT1L_ZC */
+#define WM9090_HPOUT1L_ZC_SHIFT 7 /* HPOUT1L_ZC */
+#define WM9090_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */
+#define WM9090_HPOUT1L_MUTE 0x0040 /* HPOUT1L_MUTE */
+#define WM9090_HPOUT1L_MUTE_MASK 0x0040 /* HPOUT1L_MUTE */
+#define WM9090_HPOUT1L_MUTE_SHIFT 6 /* HPOUT1L_MUTE */
+#define WM9090_HPOUT1L_MUTE_WIDTH 1 /* HPOUT1L_MUTE */
+#define WM9090_HPOUT1L_VOL_MASK 0x003F /* HPOUT1L_VOL - [5:0] */
+#define WM9090_HPOUT1L_VOL_SHIFT 0 /* HPOUT1L_VOL - [5:0] */
+#define WM9090_HPOUT1L_VOL_WIDTH 6 /* HPOUT1L_VOL - [5:0] */
+
+/*
+ * R29 (0x1D) - Right Output Volume
+ */
+#define WM9090_HPOUT1_VU 0x0100 /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */
+#define WM9090_HPOUT1R_ZC 0x0080 /* HPOUT1R_ZC */
+#define WM9090_HPOUT1R_ZC_MASK 0x0080 /* HPOUT1R_ZC */
+#define WM9090_HPOUT1R_ZC_SHIFT 7 /* HPOUT1R_ZC */
+#define WM9090_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */
+#define WM9090_HPOUT1R_MUTE 0x0040 /* HPOUT1R_MUTE */
+#define WM9090_HPOUT1R_MUTE_MASK 0x0040 /* HPOUT1R_MUTE */
+#define WM9090_HPOUT1R_MUTE_SHIFT 6 /* HPOUT1R_MUTE */
+#define WM9090_HPOUT1R_MUTE_WIDTH 1 /* HPOUT1R_MUTE */
+#define WM9090_HPOUT1R_VOL_MASK 0x003F /* HPOUT1R_VOL - [5:0] */
+#define WM9090_HPOUT1R_VOL_SHIFT 0 /* HPOUT1R_VOL - [5:0] */
+#define WM9090_HPOUT1R_VOL_WIDTH 6 /* HPOUT1R_VOL - [5:0] */
+
+/*
+ * R34 (0x22) - SPKMIXL Attenuation
+ */
+#define WM9090_SPKMIX_MUTE 0x0100 /* SPKMIX_MUTE */
+#define WM9090_SPKMIX_MUTE_MASK 0x0100 /* SPKMIX_MUTE */
+#define WM9090_SPKMIX_MUTE_SHIFT 8 /* SPKMIX_MUTE */
+#define WM9090_SPKMIX_MUTE_WIDTH 1 /* SPKMIX_MUTE */
+#define WM9090_IN1A_SPKMIX_VOL_MASK 0x00C0 /* IN1A_SPKMIX_VOL - [7:6] */
+#define WM9090_IN1A_SPKMIX_VOL_SHIFT 6 /* IN1A_SPKMIX_VOL - [7:6] */
+#define WM9090_IN1A_SPKMIX_VOL_WIDTH 2 /* IN1A_SPKMIX_VOL - [7:6] */
+#define WM9090_IN1B_SPKMIX_VOL_MASK 0x0030 /* IN1B_SPKMIX_VOL - [5:4] */
+#define WM9090_IN1B_SPKMIX_VOL_SHIFT 4 /* IN1B_SPKMIX_VOL - [5:4] */
+#define WM9090_IN1B_SPKMIX_VOL_WIDTH 2 /* IN1B_SPKMIX_VOL - [5:4] */
+#define WM9090_IN2A_SPKMIX_VOL_MASK 0x000C /* IN2A_SPKMIX_VOL - [3:2] */
+#define WM9090_IN2A_SPKMIX_VOL_SHIFT 2 /* IN2A_SPKMIX_VOL - [3:2] */
+#define WM9090_IN2A_SPKMIX_VOL_WIDTH 2 /* IN2A_SPKMIX_VOL - [3:2] */
+#define WM9090_IN2B_SPKMIX_VOL_MASK 0x0003 /* IN2B_SPKMIX_VOL - [1:0] */
+#define WM9090_IN2B_SPKMIX_VOL_SHIFT 0 /* IN2B_SPKMIX_VOL - [1:0] */
+#define WM9090_IN2B_SPKMIX_VOL_WIDTH 2 /* IN2B_SPKMIX_VOL - [1:0] */
+
+/*
+ * R36 (0x24) - SPKOUT Mixers
+ */
+#define WM9090_SPKMIXL_TO_SPKOUTL 0x0010 /* SPKMIXL_TO_SPKOUTL */
+#define WM9090_SPKMIXL_TO_SPKOUTL_MASK 0x0010 /* SPKMIXL_TO_SPKOUTL */
+#define WM9090_SPKMIXL_TO_SPKOUTL_SHIFT 4 /* SPKMIXL_TO_SPKOUTL */
+#define WM9090_SPKMIXL_TO_SPKOUTL_WIDTH 1 /* SPKMIXL_TO_SPKOUTL */
+
+/*
+ * R37 (0x25) - ClassD3
+ */
+#define WM9090_SPKOUTL_BOOST_MASK 0x0038 /* SPKOUTL_BOOST - [5:3] */
+#define WM9090_SPKOUTL_BOOST_SHIFT 3 /* SPKOUTL_BOOST - [5:3] */
+#define WM9090_SPKOUTL_BOOST_WIDTH 3 /* SPKOUTL_BOOST - [5:3] */
+
+/*
+ * R38 (0x26) - Speaker Volume Left
+ */
+#define WM9090_SPKOUT_VU 0x0100 /* SPKOUT_VU */
+#define WM9090_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */
+#define WM9090_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */
+#define WM9090_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */
+#define WM9090_SPKOUTL_ZC 0x0080 /* SPKOUTL_ZC */
+#define WM9090_SPKOUTL_ZC_MASK 0x0080 /* SPKOUTL_ZC */
+#define WM9090_SPKOUTL_ZC_SHIFT 7 /* SPKOUTL_ZC */
+#define WM9090_SPKOUTL_ZC_WIDTH 1 /* SPKOUTL_ZC */
+#define WM9090_SPKOUTL_MUTE 0x0040 /* SPKOUTL_MUTE */
+#define WM9090_SPKOUTL_MUTE_MASK 0x0040 /* SPKOUTL_MUTE */
+#define WM9090_SPKOUTL_MUTE_SHIFT 6 /* SPKOUTL_MUTE */
+#define WM9090_SPKOUTL_MUTE_WIDTH 1 /* SPKOUTL_MUTE */
+#define WM9090_SPKOUTL_VOL_MASK 0x003F /* SPKOUTL_VOL - [5:0] */
+#define WM9090_SPKOUTL_VOL_SHIFT 0 /* SPKOUTL_VOL - [5:0] */
+#define WM9090_SPKOUTL_VOL_WIDTH 6 /* SPKOUTL_VOL - [5:0] */
+
+/*
+ * R45 (0x2D) - Output Mixer1
+ */
+#define WM9090_IN1A_TO_MIXOUTL 0x0040 /* IN1A_TO_MIXOUTL */
+#define WM9090_IN1A_TO_MIXOUTL_MASK 0x0040 /* IN1A_TO_MIXOUTL */
+#define WM9090_IN1A_TO_MIXOUTL_SHIFT 6 /* IN1A_TO_MIXOUTL */
+#define WM9090_IN1A_TO_MIXOUTL_WIDTH 1 /* IN1A_TO_MIXOUTL */
+#define WM9090_IN2A_TO_MIXOUTL 0x0004 /* IN2A_TO_MIXOUTL */
+#define WM9090_IN2A_TO_MIXOUTL_MASK 0x0004 /* IN2A_TO_MIXOUTL */
+#define WM9090_IN2A_TO_MIXOUTL_SHIFT 2 /* IN2A_TO_MIXOUTL */
+#define WM9090_IN2A_TO_MIXOUTL_WIDTH 1 /* IN2A_TO_MIXOUTL */
+
+/*
+ * R46 (0x2E) - Output Mixer2
+ */
+#define WM9090_IN1A_TO_MIXOUTR 0x0040 /* IN1A_TO_MIXOUTR */
+#define WM9090_IN1A_TO_MIXOUTR_MASK 0x0040 /* IN1A_TO_MIXOUTR */
+#define WM9090_IN1A_TO_MIXOUTR_SHIFT 6 /* IN1A_TO_MIXOUTR */
+#define WM9090_IN1A_TO_MIXOUTR_WIDTH 1 /* IN1A_TO_MIXOUTR */
+#define WM9090_IN1B_TO_MIXOUTR 0x0010 /* IN1B_TO_MIXOUTR */
+#define WM9090_IN1B_TO_MIXOUTR_MASK 0x0010 /* IN1B_TO_MIXOUTR */
+#define WM9090_IN1B_TO_MIXOUTR_SHIFT 4 /* IN1B_TO_MIXOUTR */
+#define WM9090_IN1B_TO_MIXOUTR_WIDTH 1 /* IN1B_TO_MIXOUTR */
+#define WM9090_IN2A_TO_MIXOUTR 0x0004 /* IN2A_TO_MIXOUTR */
+#define WM9090_IN2A_TO_MIXOUTR_MASK 0x0004 /* IN2A_TO_MIXOUTR */
+#define WM9090_IN2A_TO_MIXOUTR_SHIFT 2 /* IN2A_TO_MIXOUTR */
+#define WM9090_IN2A_TO_MIXOUTR_WIDTH 1 /* IN2A_TO_MIXOUTR */
+#define WM9090_IN2B_TO_MIXOUTR 0x0001 /* IN2B_TO_MIXOUTR */
+#define WM9090_IN2B_TO_MIXOUTR_MASK 0x0001 /* IN2B_TO_MIXOUTR */
+#define WM9090_IN2B_TO_MIXOUTR_SHIFT 0 /* IN2B_TO_MIXOUTR */
+#define WM9090_IN2B_TO_MIXOUTR_WIDTH 1 /* IN2B_TO_MIXOUTR */
+
+/*
+ * R47 (0x2F) - Output Mixer3
+ */
+#define WM9090_MIXOUTL_MUTE 0x0100 /* MIXOUTL_MUTE */
+#define WM9090_MIXOUTL_MUTE_MASK 0x0100 /* MIXOUTL_MUTE */
+#define WM9090_MIXOUTL_MUTE_SHIFT 8 /* MIXOUTL_MUTE */
+#define WM9090_MIXOUTL_MUTE_WIDTH 1 /* MIXOUTL_MUTE */
+#define WM9090_IN1A_MIXOUTL_VOL_MASK 0x00C0 /* IN1A_MIXOUTL_VOL - [7:6] */
+#define WM9090_IN1A_MIXOUTL_VOL_SHIFT 6 /* IN1A_MIXOUTL_VOL - [7:6] */
+#define WM9090_IN1A_MIXOUTL_VOL_WIDTH 2 /* IN1A_MIXOUTL_VOL - [7:6] */
+#define WM9090_IN2A_MIXOUTL_VOL_MASK 0x000C /* IN2A_MIXOUTL_VOL - [3:2] */
+#define WM9090_IN2A_MIXOUTL_VOL_SHIFT 2 /* IN2A_MIXOUTL_VOL - [3:2] */
+#define WM9090_IN2A_MIXOUTL_VOL_WIDTH 2 /* IN2A_MIXOUTL_VOL - [3:2] */
+
+/*
+ * R48 (0x30) - Output Mixer4
+ */
+#define WM9090_MIXOUTR_MUTE 0x0100 /* MIXOUTR_MUTE */
+#define WM9090_MIXOUTR_MUTE_MASK 0x0100 /* MIXOUTR_MUTE */
+#define WM9090_MIXOUTR_MUTE_SHIFT 8 /* MIXOUTR_MUTE */
+#define WM9090_MIXOUTR_MUTE_WIDTH 1 /* MIXOUTR_MUTE */
+#define WM9090_IN1A_MIXOUTR_VOL_MASK 0x00C0 /* IN1A_MIXOUTR_VOL - [7:6] */
+#define WM9090_IN1A_MIXOUTR_VOL_SHIFT 6 /* IN1A_MIXOUTR_VOL - [7:6] */
+#define WM9090_IN1A_MIXOUTR_VOL_WIDTH 2 /* IN1A_MIXOUTR_VOL - [7:6] */
+#define WM9090_IN1B_MIXOUTR_VOL_MASK 0x0030 /* IN1B_MIXOUTR_VOL - [5:4] */
+#define WM9090_IN1B_MIXOUTR_VOL_SHIFT 4 /* IN1B_MIXOUTR_VOL - [5:4] */
+#define WM9090_IN1B_MIXOUTR_VOL_WIDTH 2 /* IN1B_MIXOUTR_VOL - [5:4] */
+#define WM9090_IN2A_MIXOUTR_VOL_MASK 0x000C /* IN2A_MIXOUTR_VOL - [3:2] */
+#define WM9090_IN2A_MIXOUTR_VOL_SHIFT 2 /* IN2A_MIXOUTR_VOL - [3:2] */
+#define WM9090_IN2A_MIXOUTR_VOL_WIDTH 2 /* IN2A_MIXOUTR_VOL - [3:2] */
+#define WM9090_IN2B_MIXOUTR_VOL_MASK 0x0003 /* IN2B_MIXOUTR_VOL - [1:0] */
+#define WM9090_IN2B_MIXOUTR_VOL_SHIFT 0 /* IN2B_MIXOUTR_VOL - [1:0] */
+#define WM9090_IN2B_MIXOUTR_VOL_WIDTH 2 /* IN2B_MIXOUTR_VOL - [1:0] */
+
+/*
+ * R54 (0x36) - Speaker Mixer
+ */
+#define WM9090_IN1A_TO_SPKMIX 0x0040 /* IN1A_TO_SPKMIX */
+#define WM9090_IN1A_TO_SPKMIX_MASK 0x0040 /* IN1A_TO_SPKMIX */
+#define WM9090_IN1A_TO_SPKMIX_SHIFT 6 /* IN1A_TO_SPKMIX */
+#define WM9090_IN1A_TO_SPKMIX_WIDTH 1 /* IN1A_TO_SPKMIX */
+#define WM9090_IN1B_TO_SPKMIX 0x0010 /* IN1B_TO_SPKMIX */
+#define WM9090_IN1B_TO_SPKMIX_MASK 0x0010 /* IN1B_TO_SPKMIX */
+#define WM9090_IN1B_TO_SPKMIX_SHIFT 4 /* IN1B_TO_SPKMIX */
+#define WM9090_IN1B_TO_SPKMIX_WIDTH 1 /* IN1B_TO_SPKMIX */
+#define WM9090_IN2A_TO_SPKMIX 0x0004 /* IN2A_TO_SPKMIX */
+#define WM9090_IN2A_TO_SPKMIX_MASK 0x0004 /* IN2A_TO_SPKMIX */
+#define WM9090_IN2A_TO_SPKMIX_SHIFT 2 /* IN2A_TO_SPKMIX */
+#define WM9090_IN2A_TO_SPKMIX_WIDTH 1 /* IN2A_TO_SPKMIX */
+#define WM9090_IN2B_TO_SPKMIX 0x0001 /* IN2B_TO_SPKMIX */
+#define WM9090_IN2B_TO_SPKMIX_MASK 0x0001 /* IN2B_TO_SPKMIX */
+#define WM9090_IN2B_TO_SPKMIX_SHIFT 0 /* IN2B_TO_SPKMIX */
+#define WM9090_IN2B_TO_SPKMIX_WIDTH 1 /* IN2B_TO_SPKMIX */
+
+/*
+ * R57 (0x39) - AntiPOP2
+ */
+#define WM9090_VMID_BUF_ENA 0x0008 /* VMID_BUF_ENA */
+#define WM9090_VMID_BUF_ENA_MASK 0x0008 /* VMID_BUF_ENA */
+#define WM9090_VMID_BUF_ENA_SHIFT 3 /* VMID_BUF_ENA */
+#define WM9090_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */
+#define WM9090_VMID_ENA 0x0001 /* VMID_ENA */
+#define WM9090_VMID_ENA_MASK 0x0001 /* VMID_ENA */
+#define WM9090_VMID_ENA_SHIFT 0 /* VMID_ENA */
+#define WM9090_VMID_ENA_WIDTH 1 /* VMID_ENA */
+
+/*
+ * R70 (0x46) - Write Sequencer 0
+ */
+#define WM9090_WSEQ_ENA 0x0100 /* WSEQ_ENA */
+#define WM9090_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */
+#define WM9090_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */
+#define WM9090_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM9090_WSEQ_WRITE_INDEX_MASK 0x000F /* WSEQ_WRITE_INDEX - [3:0] */
+#define WM9090_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [3:0] */
+#define WM9090_WSEQ_WRITE_INDEX_WIDTH 4 /* WSEQ_WRITE_INDEX - [3:0] */
+
+/*
+ * R71 (0x47) - Write Sequencer 1
+ */
+#define WM9090_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM9090_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM9090_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM9090_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */
+#define WM9090_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */
+#define WM9090_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */
+#define WM9090_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */
+#define WM9090_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */
+#define WM9090_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R72 (0x48) - Write Sequencer 2
+ */
+#define WM9090_WSEQ_EOS 0x4000 /* WSEQ_EOS */
+#define WM9090_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */
+#define WM9090_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */
+#define WM9090_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */
+#define WM9090_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */
+#define WM9090_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */
+#define WM9090_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */
+#define WM9090_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */
+#define WM9090_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */
+#define WM9090_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */
+
+/*
+ * R73 (0x49) - Write Sequencer 3
+ */
+#define WM9090_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
+#define WM9090_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
+#define WM9090_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
+#define WM9090_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM9090_WSEQ_START 0x0100 /* WSEQ_START */
+#define WM9090_WSEQ_START_MASK 0x0100 /* WSEQ_START */
+#define WM9090_WSEQ_START_SHIFT 8 /* WSEQ_START */
+#define WM9090_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM9090_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */
+#define WM9090_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */
+#define WM9090_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R74 (0x4A) - Write Sequencer 4
+ */
+#define WM9090_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
+#define WM9090_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
+#define WM9090_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
+#define WM9090_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+
+/*
+ * R75 (0x4B) - Write Sequencer 5
+ */
+#define WM9090_WSEQ_CURRENT_INDEX_MASK 0x003F /* WSEQ_CURRENT_INDEX - [5:0] */
+#define WM9090_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [5:0] */
+#define WM9090_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [5:0] */
+
+/*
+ * R76 (0x4C) - Charge Pump 1
+ */
+#define WM9090_CP_ENA 0x8000 /* CP_ENA */
+#define WM9090_CP_ENA_MASK 0x8000 /* CP_ENA */
+#define WM9090_CP_ENA_SHIFT 15 /* CP_ENA */
+#define WM9090_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R84 (0x54) - DC Servo 0
+ */
+#define WM9090_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM9090_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM9090_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */
+#define WM9090_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */
+#define WM9090_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM9090_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM9090_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */
+#define WM9090_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */
+#define WM9090_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM9090_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM9090_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */
+#define WM9090_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */
+#define WM9090_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM9090_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM9090_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */
+#define WM9090_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */
+#define WM9090_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM9090_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM9090_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */
+#define WM9090_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */
+#define WM9090_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM9090_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM9090_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */
+#define WM9090_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */
+#define WM9090_DCS_TRIG_DAC_WR_1 0x0008 /* DCS_TRIG_DAC_WR_1 */
+#define WM9090_DCS_TRIG_DAC_WR_1_MASK 0x0008 /* DCS_TRIG_DAC_WR_1 */
+#define WM9090_DCS_TRIG_DAC_WR_1_SHIFT 3 /* DCS_TRIG_DAC_WR_1 */
+#define WM9090_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */
+#define WM9090_DCS_TRIG_DAC_WR_0 0x0004 /* DCS_TRIG_DAC_WR_0 */
+#define WM9090_DCS_TRIG_DAC_WR_0_MASK 0x0004 /* DCS_TRIG_DAC_WR_0 */
+#define WM9090_DCS_TRIG_DAC_WR_0_SHIFT 2 /* DCS_TRIG_DAC_WR_0 */
+#define WM9090_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */
+#define WM9090_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM9090_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM9090_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */
+#define WM9090_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */
+#define WM9090_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM9090_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM9090_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */
+#define WM9090_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */
+
+/*
+ * R85 (0x55) - DC Servo 1
+ */
+#define WM9090_DCS_SERIES_NO_01_MASK 0x0FE0 /* DCS_SERIES_NO_01 - [11:5] */
+#define WM9090_DCS_SERIES_NO_01_SHIFT 5 /* DCS_SERIES_NO_01 - [11:5] */
+#define WM9090_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [11:5] */
+#define WM9090_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM9090_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM9090_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */
+
+/*
+ * R87 (0x57) - DC Servo 3
+ */
+#define WM9090_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM9090_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM9090_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM9090_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */
+
+/*
+ * R88 (0x58) - DC Servo Readback 0
+ */
+#define WM9090_DCS_CAL_COMPLETE_MASK 0x0300 /* DCS_CAL_COMPLETE - [9:8] */
+#define WM9090_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [9:8] */
+#define WM9090_DCS_CAL_COMPLETE_WIDTH 2 /* DCS_CAL_COMPLETE - [9:8] */
+#define WM9090_DCS_DAC_WR_COMPLETE_MASK 0x0030 /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM9090_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM9090_DCS_DAC_WR_COMPLETE_WIDTH 2 /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM9090_DCS_STARTUP_COMPLETE_MASK 0x0003 /* DCS_STARTUP_COMPLETE - [1:0] */
+#define WM9090_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [1:0] */
+#define WM9090_DCS_STARTUP_COMPLETE_WIDTH 2 /* DCS_STARTUP_COMPLETE - [1:0] */
+
+/*
+ * R89 (0x59) - DC Servo Readback 1
+ */
+#define WM9090_DCS_DAC_WR_VAL_1_RD_MASK 0x00FF /* DCS_DAC_WR_VAL_1_RD - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_1_RD_SHIFT 0 /* DCS_DAC_WR_VAL_1_RD - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_1_RD_WIDTH 8 /* DCS_DAC_WR_VAL_1_RD - [7:0] */
+
+/*
+ * R90 (0x5A) - DC Servo Readback 2
+ */
+#define WM9090_DCS_DAC_WR_VAL_0_RD_MASK 0x00FF /* DCS_DAC_WR_VAL_0_RD - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_0_RD_SHIFT 0 /* DCS_DAC_WR_VAL_0_RD - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_0_RD_WIDTH 8 /* DCS_DAC_WR_VAL_0_RD - [7:0] */
+
+/*
+ * R96 (0x60) - Analogue HP 0
+ */
+#define WM9090_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */
+#define WM9090_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */
+#define WM9090_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */
+#define WM9090_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */
+#define WM9090_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */
+#define WM9090_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */
+#define WM9090_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */
+#define WM9090_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */
+#define WM9090_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */
+#define WM9090_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */
+#define WM9090_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */
+#define WM9090_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */
+#define WM9090_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */
+#define WM9090_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */
+#define WM9090_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */
+#define WM9090_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */
+#define WM9090_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */
+#define WM9090_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */
+#define WM9090_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */
+#define WM9090_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */
+#define WM9090_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */
+#define WM9090_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */
+#define WM9090_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */
+#define WM9090_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */
+
+/*
+ * R98 (0x62) - AGC Control 0
+ */
+#define WM9090_AGC_CLIP_ENA 0x8000 /* AGC_CLIP_ENA */
+#define WM9090_AGC_CLIP_ENA_MASK 0x8000 /* AGC_CLIP_ENA */
+#define WM9090_AGC_CLIP_ENA_SHIFT 15 /* AGC_CLIP_ENA */
+#define WM9090_AGC_CLIP_ENA_WIDTH 1 /* AGC_CLIP_ENA */
+#define WM9090_AGC_CLIP_THR_MASK 0x0F00 /* AGC_CLIP_THR - [11:8] */
+#define WM9090_AGC_CLIP_THR_SHIFT 8 /* AGC_CLIP_THR - [11:8] */
+#define WM9090_AGC_CLIP_THR_WIDTH 4 /* AGC_CLIP_THR - [11:8] */
+#define WM9090_AGC_CLIP_ATK_MASK 0x0070 /* AGC_CLIP_ATK - [6:4] */
+#define WM9090_AGC_CLIP_ATK_SHIFT 4 /* AGC_CLIP_ATK - [6:4] */
+#define WM9090_AGC_CLIP_ATK_WIDTH 3 /* AGC_CLIP_ATK - [6:4] */
+#define WM9090_AGC_CLIP_DCY_MASK 0x0007 /* AGC_CLIP_DCY - [2:0] */
+#define WM9090_AGC_CLIP_DCY_SHIFT 0 /* AGC_CLIP_DCY - [2:0] */
+#define WM9090_AGC_CLIP_DCY_WIDTH 3 /* AGC_CLIP_DCY - [2:0] */
+
+/*
+ * R99 (0x63) - AGC Control 1
+ */
+#define WM9090_AGC_PWR_ENA 0x8000 /* AGC_PWR_ENA */
+#define WM9090_AGC_PWR_ENA_MASK 0x8000 /* AGC_PWR_ENA */
+#define WM9090_AGC_PWR_ENA_SHIFT 15 /* AGC_PWR_ENA */
+#define WM9090_AGC_PWR_ENA_WIDTH 1 /* AGC_PWR_ENA */
+#define WM9090_AGC_PWR_AVG 0x1000 /* AGC_PWR_AVG */
+#define WM9090_AGC_PWR_AVG_MASK 0x1000 /* AGC_PWR_AVG */
+#define WM9090_AGC_PWR_AVG_SHIFT 12 /* AGC_PWR_AVG */
+#define WM9090_AGC_PWR_AVG_WIDTH 1 /* AGC_PWR_AVG */
+#define WM9090_AGC_PWR_THR_MASK 0x0F00 /* AGC_PWR_THR - [11:8] */
+#define WM9090_AGC_PWR_THR_SHIFT 8 /* AGC_PWR_THR - [11:8] */
+#define WM9090_AGC_PWR_THR_WIDTH 4 /* AGC_PWR_THR - [11:8] */
+#define WM9090_AGC_PWR_ATK_MASK 0x0070 /* AGC_PWR_ATK - [6:4] */
+#define WM9090_AGC_PWR_ATK_SHIFT 4 /* AGC_PWR_ATK - [6:4] */
+#define WM9090_AGC_PWR_ATK_WIDTH 3 /* AGC_PWR_ATK - [6:4] */
+#define WM9090_AGC_PWR_DCY_MASK 0x0007 /* AGC_PWR_DCY - [2:0] */
+#define WM9090_AGC_PWR_DCY_SHIFT 0 /* AGC_PWR_DCY - [2:0] */
+#define WM9090_AGC_PWR_DCY_WIDTH 3 /* AGC_PWR_DCY - [2:0] */
+
+/*
+ * R100 (0x64) - AGC Control 2
+ */
+#define WM9090_AGC_RAMP 0x0100 /* AGC_RAMP */
+#define WM9090_AGC_RAMP_MASK 0x0100 /* AGC_RAMP */
+#define WM9090_AGC_RAMP_SHIFT 8 /* AGC_RAMP */
+#define WM9090_AGC_RAMP_WIDTH 1 /* AGC_RAMP */
+#define WM9090_AGC_MINGAIN_MASK 0x003F /* AGC_MINGAIN - [5:0] */
+#define WM9090_AGC_MINGAIN_SHIFT 0 /* AGC_MINGAIN - [5:0] */
+#define WM9090_AGC_MINGAIN_WIDTH 6 /* AGC_MINGAIN - [5:0] */
+
+#endif
diff --git a/sound/soc/codecs/wm9705.c b/sound/soc/codecs/wm9705.c
new file mode 100644
index 00000000..646b58dd
--- /dev/null
+++ b/sound/soc/codecs/wm9705.c
@@ -0,0 +1,423 @@
+/*
+ * wm9705.c -- ALSA Soc WM9705 codec support
+ *
+ * Copyright 2008 Ian Molton <spyro@f2s.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; Version 2 of the License only.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "wm9705.h"
+
+/*
+ * WM9705 register cache
+ */
+static const u16 wm9705_reg[] = {
+ 0x6150, 0x8000, 0x8000, 0x8000, /* 0x0 */
+ 0x0000, 0x8000, 0x8008, 0x8008, /* 0x8 */
+ 0x8808, 0x8808, 0x8808, 0x8808, /* 0x10 */
+ 0x8808, 0x0000, 0x8000, 0x0000, /* 0x18 */
+ 0x0000, 0x0000, 0x0000, 0x000f, /* 0x20 */
+ 0x0605, 0x0000, 0xbb80, 0x0000, /* 0x28 */
+ 0x0000, 0xbb80, 0x0000, 0x0000, /* 0x30 */
+ 0x0000, 0x2000, 0x0000, 0x0000, /* 0x38 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x40 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x48 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x50 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x58 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x60 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x68 */
+ 0x0000, 0x0808, 0x0000, 0x0006, /* 0x70 */
+ 0x0000, 0x0000, 0x574d, 0x4c05, /* 0x78 */
+};
+
+static const struct snd_kcontrol_new wm9705_snd_ac97_controls[] = {
+ SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+ SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1),
+ SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+ SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
+ SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
+ SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1),
+ SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
+ SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+ SOC_SINGLE("PCBeep Playback Volume", AC97_PC_BEEP, 1, 15, 1),
+ SOC_SINGLE("Phone Playback Volume", AC97_PHONE, 0, 31, 1),
+ SOC_DOUBLE("Line Playback Volume", AC97_LINE, 8, 0, 31, 1),
+ SOC_DOUBLE("CD Playback Volume", AC97_CD, 8, 0, 31, 1),
+ SOC_SINGLE("Mic Playback Volume", AC97_MIC, 0, 31, 1),
+ SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 6, 1, 0),
+ SOC_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 15, 0),
+ SOC_SINGLE("Capture Switch", AC97_REC_GAIN, 15, 1, 1),
+};
+
+static const char *wm9705_mic[] = {"Mic 1", "Mic 2"};
+static const char *wm9705_rec_sel[] = {"Mic", "CD", "NC", "NC",
+ "Line", "Stereo Mix", "Mono Mix", "Phone"};
+
+static const struct soc_enum wm9705_enum_mic =
+ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, wm9705_mic);
+static const struct soc_enum wm9705_enum_rec_l =
+ SOC_ENUM_SINGLE(AC97_REC_SEL, 8, 8, wm9705_rec_sel);
+static const struct soc_enum wm9705_enum_rec_r =
+ SOC_ENUM_SINGLE(AC97_REC_SEL, 0, 8, wm9705_rec_sel);
+
+/* Headphone Mixer */
+static const struct snd_kcontrol_new wm9705_hp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Playback Switch", AC97_PC_BEEP, 15, 1, 1),
+ SOC_DAPM_SINGLE("CD Playback Switch", AC97_CD, 15, 1, 1),
+ SOC_DAPM_SINGLE("Mic Playback Switch", AC97_MIC, 15, 1, 1),
+ SOC_DAPM_SINGLE("Phone Playback Switch", AC97_PHONE, 15, 1, 1),
+ SOC_DAPM_SINGLE("Line Playback Switch", AC97_LINE, 15, 1, 1),
+};
+
+/* Mic source */
+static const struct snd_kcontrol_new wm9705_mic_src_controls =
+ SOC_DAPM_ENUM("Route", wm9705_enum_mic);
+
+/* Capture source */
+static const struct snd_kcontrol_new wm9705_capture_selectl_controls =
+ SOC_DAPM_ENUM("Route", wm9705_enum_rec_l);
+static const struct snd_kcontrol_new wm9705_capture_selectr_controls =
+ SOC_DAPM_ENUM("Route", wm9705_enum_rec_r);
+
+/* DAPM widgets */
+static const struct snd_soc_dapm_widget wm9705_dapm_widgets[] = {
+ SND_SOC_DAPM_MUX("Mic Source", SND_SOC_NOPM, 0, 0,
+ &wm9705_mic_src_controls),
+ SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0,
+ &wm9705_capture_selectl_controls),
+ SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0,
+ &wm9705_capture_selectr_controls),
+ SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback",
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback",
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_MIXER_NAMED_CTL("HP Mixer", SND_SOC_NOPM, 0, 0,
+ &wm9705_hp_mixer_controls[0],
+ ARRAY_SIZE(wm9705_hp_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_PGA("Headphone PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Speaker PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Line PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Line out PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mono PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Phone PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("PCBEEP PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("CD PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("ADC PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("HPOUTL"),
+ SND_SOC_DAPM_OUTPUT("HPOUTR"),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+ SND_SOC_DAPM_OUTPUT("MONOOUT"),
+ SND_SOC_DAPM_INPUT("PHONE"),
+ SND_SOC_DAPM_INPUT("LINEINL"),
+ SND_SOC_DAPM_INPUT("LINEINR"),
+ SND_SOC_DAPM_INPUT("CDINL"),
+ SND_SOC_DAPM_INPUT("CDINR"),
+ SND_SOC_DAPM_INPUT("PCBEEP"),
+ SND_SOC_DAPM_INPUT("MIC1"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+};
+
+/* Audio map
+ * WM9705 has no switches to disable the route from the inputs to the HP mixer
+ * so in order to prevent active inputs from forcing the audio outputs to be
+ * constantly enabled, we use the mutes on those inputs to simulate such
+ * controls.
+ */
+static const struct snd_soc_dapm_route wm9705_audio_map[] = {
+ /* HP mixer */
+ {"HP Mixer", "PCBeep Playback Switch", "PCBEEP PGA"},
+ {"HP Mixer", "CD Playback Switch", "CD PGA"},
+ {"HP Mixer", "Mic Playback Switch", "Mic PGA"},
+ {"HP Mixer", "Phone Playback Switch", "Phone PGA"},
+ {"HP Mixer", "Line Playback Switch", "Line PGA"},
+ {"HP Mixer", NULL, "Left DAC"},
+ {"HP Mixer", NULL, "Right DAC"},
+
+ /* mono mixer */
+ {"Mono Mixer", NULL, "HP Mixer"},
+
+ /* outputs */
+ {"Headphone PGA", NULL, "HP Mixer"},
+ {"HPOUTL", NULL, "Headphone PGA"},
+ {"HPOUTR", NULL, "Headphone PGA"},
+ {"Line out PGA", NULL, "HP Mixer"},
+ {"LOUT", NULL, "Line out PGA"},
+ {"ROUT", NULL, "Line out PGA"},
+ {"Mono PGA", NULL, "Mono Mixer"},
+ {"MONOOUT", NULL, "Mono PGA"},
+
+ /* inputs */
+ {"CD PGA", NULL, "CDINL"},
+ {"CD PGA", NULL, "CDINR"},
+ {"Line PGA", NULL, "LINEINL"},
+ {"Line PGA", NULL, "LINEINR"},
+ {"Phone PGA", NULL, "PHONE"},
+ {"Mic Source", "Mic 1", "MIC1"},
+ {"Mic Source", "Mic 2", "MIC2"},
+ {"Mic PGA", NULL, "Mic Source"},
+ {"PCBEEP PGA", NULL, "PCBEEP"},
+
+ /* Left capture selector */
+ {"Left Capture Source", "Mic", "Mic Source"},
+ {"Left Capture Source", "CD", "CDINL"},
+ {"Left Capture Source", "Line", "LINEINL"},
+ {"Left Capture Source", "Stereo Mix", "HP Mixer"},
+ {"Left Capture Source", "Mono Mix", "HP Mixer"},
+ {"Left Capture Source", "Phone", "PHONE"},
+
+ /* Right capture source */
+ {"Right Capture Source", "Mic", "Mic Source"},
+ {"Right Capture Source", "CD", "CDINR"},
+ {"Right Capture Source", "Line", "LINEINR"},
+ {"Right Capture Source", "Stereo Mix", "HP Mixer"},
+ {"Right Capture Source", "Mono Mix", "HP Mixer"},
+ {"Right Capture Source", "Phone", "PHONE"},
+
+ {"ADC PGA", NULL, "Left Capture Source"},
+ {"ADC PGA", NULL, "Right Capture Source"},
+
+ /* ADC's */
+ {"Left ADC", NULL, "ADC PGA"},
+ {"Right ADC", NULL, "ADC PGA"},
+};
+
+/* We use a register cache to enhance read performance. */
+static unsigned int ac97_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ switch (reg) {
+ case AC97_RESET:
+ case AC97_VENDOR_ID1:
+ case AC97_VENDOR_ID2:
+ return soc_ac97_ops.read(codec->ac97, reg);
+ default:
+ reg = reg >> 1;
+
+ if (reg >= (ARRAY_SIZE(wm9705_reg)))
+ return -EIO;
+
+ return cache[reg];
+ }
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ reg = reg >> 1;
+ if (reg < (ARRAY_SIZE(wm9705_reg)))
+ cache[reg] = val;
+
+ return 0;
+}
+
+static int ac97_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ int reg;
+ u16 vra;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = AC97_PCM_FRONT_DAC_RATE;
+ else
+ reg = AC97_PCM_LR_ADC_RATE;
+
+ return ac97_write(codec, reg, runtime->rate);
+}
+
+#define WM9705_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops wm9705_dai_ops = {
+ .prepare = ac97_prepare,
+};
+
+static struct snd_soc_dai_driver wm9705_dai[] = {
+ {
+ .name = "wm9705-hifi",
+ .ac97_control = 1,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9705_AC97_RATES,
+ .formats = SND_SOC_STD_AC97_FMTS,
+ },
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9705_AC97_RATES,
+ .formats = SND_SOC_STD_AC97_FMTS,
+ },
+ .ops = &wm9705_dai_ops,
+ },
+ {
+ .name = "wm9705-aux",
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM9705_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ }
+};
+
+static int wm9705_reset(struct snd_soc_codec *codec)
+{
+ if (soc_ac97_ops.reset) {
+ soc_ac97_ops.reset(codec->ac97);
+ if (ac97_read(codec, 0) == wm9705_reg[0])
+ return 0; /* Success */
+ }
+
+ return -EIO;
+}
+
+#ifdef CONFIG_PM
+static int wm9705_soc_suspend(struct snd_soc_codec *codec, pm_message_t msg)
+{
+ soc_ac97_ops.write(codec->ac97, AC97_POWERDOWN, 0xffff);
+
+ return 0;
+}
+
+static int wm9705_soc_resume(struct snd_soc_codec *codec)
+{
+ int i, ret;
+ u16 *cache = codec->reg_cache;
+
+ ret = wm9705_reset(codec);
+ if (ret < 0) {
+ printk(KERN_ERR "could not reset AC97 codec\n");
+ return ret;
+ }
+
+ for (i = 2; i < ARRAY_SIZE(wm9705_reg) << 1; i += 2) {
+ soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+ }
+
+ return 0;
+}
+#else
+#define wm9705_soc_suspend NULL
+#define wm9705_soc_resume NULL
+#endif
+
+static int wm9705_soc_probe(struct snd_soc_codec *codec)
+{
+ int ret = 0;
+
+ printk(KERN_INFO "WM9705 SoC Audio Codec\n");
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "wm9705: failed to register AC97 codec\n");
+ return ret;
+ }
+
+ ret = wm9705_reset(codec);
+ if (ret)
+ goto reset_err;
+
+ snd_soc_add_controls(codec, wm9705_snd_ac97_controls,
+ ARRAY_SIZE(wm9705_snd_ac97_controls));
+
+ return 0;
+
+reset_err:
+ snd_soc_free_ac97_codec(codec);
+ return ret;
+}
+
+static int wm9705_soc_remove(struct snd_soc_codec *codec)
+{
+ snd_soc_free_ac97_codec(codec);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm9705 = {
+ .probe = wm9705_soc_probe,
+ .remove = wm9705_soc_remove,
+ .suspend = wm9705_soc_suspend,
+ .resume = wm9705_soc_resume,
+ .read = ac97_read,
+ .write = ac97_write,
+ .reg_cache_size = ARRAY_SIZE(wm9705_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+ .reg_cache_default = wm9705_reg,
+ .dapm_widgets = wm9705_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm9705_dapm_widgets),
+ .dapm_routes = wm9705_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(wm9705_audio_map),
+};
+
+static __devinit int wm9705_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_wm9705, wm9705_dai, ARRAY_SIZE(wm9705_dai));
+}
+
+static int __devexit wm9705_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver wm9705_codec_driver = {
+ .driver = {
+ .name = "wm9705-codec",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = wm9705_probe,
+ .remove = __devexit_p(wm9705_remove),
+};
+
+static int __init wm9705_init(void)
+{
+ return platform_driver_register(&wm9705_codec_driver);
+}
+module_init(wm9705_init);
+
+static void __exit wm9705_exit(void)
+{
+ platform_driver_unregister(&wm9705_codec_driver);
+}
+module_exit(wm9705_exit);
+
+MODULE_DESCRIPTION("ASoC WM9705 driver");
+MODULE_AUTHOR("Ian Molton");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/wm9705.h b/sound/soc/codecs/wm9705.h
new file mode 100644
index 00000000..23ea9ce4
--- /dev/null
+++ b/sound/soc/codecs/wm9705.h
@@ -0,0 +1,11 @@
+/*
+ * wm9705.h -- WM9705 Soc Audio driver
+ */
+
+#ifndef _WM9705_H
+#define _WM9705_H
+
+#define WM9705_DAI_AC97_HIFI 0
+#define WM9705_DAI_AC97_AUX 1
+
+#endif
diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c
new file mode 100644
index 00000000..90117f81
--- /dev/null
+++ b/sound/soc/codecs/wm9712.c
@@ -0,0 +1,711 @@
+/*
+ * wm9712.c -- ALSA Soc WM9712 codec support
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include "wm9712.h"
+
+#define WM9712_VERSION "0.4"
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg);
+static int ac97_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val);
+
+/*
+ * WM9712 register cache
+ */
+static const u16 wm9712_reg[] = {
+ 0x6174, 0x8000, 0x8000, 0x8000, /* 6 */
+ 0x0f0f, 0xaaa0, 0xc008, 0x6808, /* e */
+ 0xe808, 0xaaa0, 0xad00, 0x8000, /* 16 */
+ 0xe808, 0x3000, 0x8000, 0x0000, /* 1e */
+ 0x0000, 0x0000, 0x0000, 0x000f, /* 26 */
+ 0x0405, 0x0410, 0xbb80, 0xbb80, /* 2e */
+ 0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */
+ 0x0000, 0x2000, 0x0000, 0x0000, /* 3e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 46 */
+ 0x0000, 0x0000, 0xf83e, 0xffff, /* 4e */
+ 0x0000, 0x0000, 0x0000, 0xf83e, /* 56 */
+ 0x0008, 0x0000, 0x0000, 0x0000, /* 5e */
+ 0xb032, 0x3e00, 0x0000, 0x0000, /* 66 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
+ 0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
+ 0x0001, 0x0000, 0x574d, 0x4c12, /* 7e */
+ 0x0000, 0x0000 /* virtual hp mixers */
+};
+
+/* virtual HP mixers regs */
+#define HPL_MIXER 0x80
+#define HPR_MIXER 0x82
+
+static const char *wm9712_alc_select[] = {"None", "Left", "Right", "Stereo"};
+static const char *wm9712_alc_mux[] = {"Stereo", "Left", "Right", "None"};
+static const char *wm9712_out3_src[] = {"Left", "VREF", "Left + Right",
+ "Mono"};
+static const char *wm9712_spk_src[] = {"Speaker Mix", "Headphone Mix"};
+static const char *wm9712_rec_adc[] = {"Stereo", "Left", "Right", "Mute"};
+static const char *wm9712_base[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm9712_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
+static const char *wm9712_mic[] = {"Mic 1", "Differential", "Mic 2",
+ "Stereo"};
+static const char *wm9712_rec_sel[] = {"Mic", "NC", "NC", "Speaker Mixer",
+ "Line", "Headphone Mixer", "Phone Mixer", "Phone"};
+static const char *wm9712_ng_type[] = {"Constant Gain", "Mute"};
+static const char *wm9712_diff_sel[] = {"Mic", "Line"};
+
+static const struct soc_enum wm9712_enum[] = {
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9712_alc_select),
+SOC_ENUM_SINGLE(AC97_VIDEO, 12, 4, wm9712_alc_mux),
+SOC_ENUM_SINGLE(AC97_AUX, 9, 4, wm9712_out3_src),
+SOC_ENUM_SINGLE(AC97_AUX, 8, 2, wm9712_spk_src),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 12, 4, wm9712_rec_adc),
+SOC_ENUM_SINGLE(AC97_MASTER_TONE, 15, 2, wm9712_base),
+SOC_ENUM_DOUBLE(AC97_REC_GAIN, 14, 6, 2, wm9712_rec_gain),
+SOC_ENUM_SINGLE(AC97_MIC, 5, 4, wm9712_mic),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 8, 8, wm9712_rec_sel),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 0, 8, wm9712_rec_sel),
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9712_ng_type),
+SOC_ENUM_SINGLE(0x5c, 8, 2, wm9712_diff_sel),
+};
+
+static const struct snd_kcontrol_new wm9712_snd_ac97_controls[] = {
+SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1),
+SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
+SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
+
+SOC_SINGLE("Speaker Playback ZC Switch", AC97_MASTER, 7, 1, 0),
+SOC_SINGLE("Speaker Playback Invert Switch", AC97_MASTER, 6, 1, 0),
+SOC_SINGLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 7, 1, 0),
+SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_MONO, 7, 1, 0),
+SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
+SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
+SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
+SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
+SOC_ENUM("ALC Function", wm9712_enum[0]),
+SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
+SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 1),
+SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
+SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
+SOC_ENUM("ALC NG Type", wm9712_enum[10]),
+SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 1),
+
+SOC_SINGLE("Mic Headphone Volume", AC97_VIDEO, 12, 7, 1),
+SOC_SINGLE("ALC Headphone Volume", AC97_VIDEO, 7, 7, 1),
+
+SOC_SINGLE("Out3 Switch", AC97_AUX, 15, 1, 1),
+SOC_SINGLE("Out3 ZC Switch", AC97_AUX, 7, 1, 1),
+SOC_SINGLE("Out3 Volume", AC97_AUX, 0, 31, 1),
+
+SOC_SINGLE("PCBeep Bypass Headphone Volume", AC97_PC_BEEP, 12, 7, 1),
+SOC_SINGLE("PCBeep Bypass Speaker Volume", AC97_PC_BEEP, 8, 7, 1),
+SOC_SINGLE("PCBeep Bypass Phone Volume", AC97_PC_BEEP, 4, 7, 1),
+
+SOC_SINGLE("Aux Playback Headphone Volume", AC97_CD, 12, 7, 1),
+SOC_SINGLE("Aux Playback Speaker Volume", AC97_CD, 8, 7, 1),
+SOC_SINGLE("Aux Playback Phone Volume", AC97_CD, 4, 7, 1),
+
+SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 1),
+SOC_DOUBLE("Line Capture Volume", AC97_LINE, 8, 0, 31, 1),
+
+SOC_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0),
+SOC_SINGLE("Capture to Phone 20dB Boost Switch", AC97_REC_SEL, 11, 1, 1),
+
+SOC_SINGLE("3D Upper Cut-off Switch", AC97_3D_CONTROL, 5, 1, 1),
+SOC_SINGLE("3D Lower Cut-off Switch", AC97_3D_CONTROL, 4, 1, 1),
+SOC_SINGLE("3D Playback Volume", AC97_3D_CONTROL, 0, 15, 0),
+
+SOC_ENUM("Bass Control", wm9712_enum[5]),
+SOC_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1),
+SOC_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1),
+SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0),
+SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 1),
+SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 1),
+
+SOC_SINGLE("Capture ADC Switch", AC97_REC_GAIN, 15, 1, 1),
+SOC_ENUM("Capture Volume Steps", wm9712_enum[6]),
+SOC_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 63, 1),
+SOC_SINGLE("Capture ZC Switch", AC97_REC_GAIN, 7, 1, 0),
+
+SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
+SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
+SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 7, 1, 0),
+};
+
+/* We have to create a fake left and right HP mixers because
+ * the codec only has a single control that is shared by both channels.
+ * This makes it impossible to determine the audio path.
+ */
+static int mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ u16 l, r, beep, line, phone, mic, pcm, aux;
+
+ l = ac97_read(w->codec, HPL_MIXER);
+ r = ac97_read(w->codec, HPR_MIXER);
+ beep = ac97_read(w->codec, AC97_PC_BEEP);
+ mic = ac97_read(w->codec, AC97_VIDEO);
+ phone = ac97_read(w->codec, AC97_PHONE);
+ line = ac97_read(w->codec, AC97_LINE);
+ pcm = ac97_read(w->codec, AC97_PCM);
+ aux = ac97_read(w->codec, AC97_CD);
+
+ if (l & 0x1 || r & 0x1)
+ ac97_write(w->codec, AC97_VIDEO, mic & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_VIDEO, mic | 0x8000);
+
+ if (l & 0x2 || r & 0x2)
+ ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
+
+ if (l & 0x4 || r & 0x4)
+ ac97_write(w->codec, AC97_LINE, line & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_LINE, line | 0x8000);
+
+ if (l & 0x8 || r & 0x8)
+ ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
+
+ if (l & 0x10 || r & 0x10)
+ ac97_write(w->codec, AC97_CD, aux & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_CD, aux | 0x8000);
+
+ if (l & 0x20 || r & 0x20)
+ ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
+
+ return 0;
+}
+
+/* Left Headphone Mixers */
+static const struct snd_kcontrol_new wm9712_hpl_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPL_MIXER, 5, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 4, 1, 0),
+ SOC_DAPM_SINGLE("Phone Bypass Switch", HPL_MIXER, 3, 1, 0),
+ SOC_DAPM_SINGLE("Line Bypass Switch", HPL_MIXER, 2, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 1, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", HPL_MIXER, 0, 1, 0),
+};
+
+/* Right Headphone Mixers */
+static const struct snd_kcontrol_new wm9712_hpr_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPR_MIXER, 5, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 4, 1, 0),
+ SOC_DAPM_SINGLE("Phone Bypass Switch", HPR_MIXER, 3, 1, 0),
+ SOC_DAPM_SINGLE("Line Bypass Switch", HPR_MIXER, 2, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 1, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", HPR_MIXER, 0, 1, 0),
+};
+
+/* Speaker Mixer */
+static const struct snd_kcontrol_new wm9712_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 11, 1, 1),
+ SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 11, 1, 1),
+ SOC_DAPM_SINGLE("Phone Bypass Switch", AC97_PHONE, 14, 1, 1),
+ SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 14, 1, 1),
+ SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 14, 1, 1),
+};
+
+/* Phone Mixer */
+static const struct snd_kcontrol_new wm9712_phone_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 7, 1, 1),
+ SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 7, 1, 1),
+ SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 13, 1, 1),
+ SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 13, 1, 1),
+ SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_MIC, 14, 1, 1),
+ SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_MIC, 13, 1, 1),
+};
+
+/* ALC headphone mux */
+static const struct snd_kcontrol_new wm9712_alc_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[1]);
+
+/* out 3 mux */
+static const struct snd_kcontrol_new wm9712_out3_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[2]);
+
+/* spk mux */
+static const struct snd_kcontrol_new wm9712_spk_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[3]);
+
+/* Capture to Phone mux */
+static const struct snd_kcontrol_new wm9712_capture_phone_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[4]);
+
+/* Capture left select */
+static const struct snd_kcontrol_new wm9712_capture_selectl_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[8]);
+
+/* Capture right select */
+static const struct snd_kcontrol_new wm9712_capture_selectr_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[9]);
+
+/* Mic select */
+static const struct snd_kcontrol_new wm9712_mic_src_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[7]);
+
+/* diff select */
+static const struct snd_kcontrol_new wm9712_diff_sel_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[11]);
+
+static const struct snd_soc_dapm_widget wm9712_dapm_widgets[] = {
+SND_SOC_DAPM_MUX("ALC Sidetone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_alc_mux_controls),
+SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_out3_mux_controls),
+SND_SOC_DAPM_MUX("Speaker Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_spk_mux_controls),
+SND_SOC_DAPM_MUX("Capture Phone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_capture_phone_mux_controls),
+SND_SOC_DAPM_MUX("Left Capture Select", SND_SOC_NOPM, 0, 0,
+ &wm9712_capture_selectl_controls),
+SND_SOC_DAPM_MUX("Right Capture Select", SND_SOC_NOPM, 0, 0,
+ &wm9712_capture_selectr_controls),
+SND_SOC_DAPM_MUX("Mic Select Source", SND_SOC_NOPM, 0, 0,
+ &wm9712_mic_src_controls),
+SND_SOC_DAPM_MUX("Differential Source", SND_SOC_NOPM, 0, 0,
+ &wm9712_diff_sel_controls),
+SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_INT_PAGING, 9, 1,
+ &wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_INT_PAGING, 8, 1,
+ &wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER("Phone Mixer", AC97_INT_PAGING, 6, 1,
+ &wm9712_phone_mixer_controls[0], ARRAY_SIZE(wm9712_phone_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_INT_PAGING, 7, 1,
+ &wm9712_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm9712_speaker_mixer_controls)),
+SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_INT_PAGING, 14, 1),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_INT_PAGING, 13, 1),
+SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_INT_PAGING, 12, 1),
+SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_INT_PAGING, 11, 1),
+SND_SOC_DAPM_PGA("Headphone PGA", AC97_INT_PAGING, 4, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Speaker PGA", AC97_INT_PAGING, 3, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 3 PGA", AC97_INT_PAGING, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Line PGA", AC97_INT_PAGING, 2, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Phone PGA", AC97_INT_PAGING, 1, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic PGA", AC97_INT_PAGING, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_INT_PAGING, 10, 1),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_INPUT("LINEINL"),
+SND_SOC_DAPM_INPUT("LINEINR"),
+SND_SOC_DAPM_INPUT("PHONE"),
+SND_SOC_DAPM_INPUT("PCBEEP"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2"),
+};
+
+static const struct snd_soc_dapm_route wm9712_audio_map[] = {
+ /* virtual mixer - mixes left & right channels for spk and mono */
+ {"AC97 Mixer", NULL, "Left DAC"},
+ {"AC97 Mixer", NULL, "Right DAC"},
+
+ /* Left HP mixer */
+ {"Left HP Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Left HP Mixer", "Phone Bypass Switch", "Phone PGA"},
+ {"Left HP Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Left HP Mixer", "PCM Playback Switch", "Left DAC"},
+ {"Left HP Mixer", "Mic Sidetone Switch", "Mic PGA"},
+ {"Left HP Mixer", NULL, "ALC Sidetone Mux"},
+
+ /* Right HP mixer */
+ {"Right HP Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Right HP Mixer", "Phone Bypass Switch", "Phone PGA"},
+ {"Right HP Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Right HP Mixer", "PCM Playback Switch", "Right DAC"},
+ {"Right HP Mixer", "Mic Sidetone Switch", "Mic PGA"},
+ {"Right HP Mixer", NULL, "ALC Sidetone Mux"},
+
+ /* speaker mixer */
+ {"Speaker Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Speaker Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Speaker Mixer", "Phone Bypass Switch", "Phone PGA"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"},
+
+ /* Phone mixer */
+ {"Phone Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Phone Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Phone Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Phone Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Phone Mixer", "Mic 1 Sidetone Switch", "Mic PGA"},
+ {"Phone Mixer", "Mic 2 Sidetone Switch", "Mic PGA"},
+
+ /* inputs */
+ {"Line PGA", NULL, "LINEINL"},
+ {"Line PGA", NULL, "LINEINR"},
+ {"Phone PGA", NULL, "PHONE"},
+ {"Mic PGA", NULL, "MIC1"},
+ {"Mic PGA", NULL, "MIC2"},
+
+ /* left capture selector */
+ {"Left Capture Select", "Mic", "MIC1"},
+ {"Left Capture Select", "Speaker Mixer", "Speaker Mixer"},
+ {"Left Capture Select", "Line", "LINEINL"},
+ {"Left Capture Select", "Headphone Mixer", "Left HP Mixer"},
+ {"Left Capture Select", "Phone Mixer", "Phone Mixer"},
+ {"Left Capture Select", "Phone", "PHONE"},
+
+ /* right capture selector */
+ {"Right Capture Select", "Mic", "MIC2"},
+ {"Right Capture Select", "Speaker Mixer", "Speaker Mixer"},
+ {"Right Capture Select", "Line", "LINEINR"},
+ {"Right Capture Select", "Headphone Mixer", "Right HP Mixer"},
+ {"Right Capture Select", "Phone Mixer", "Phone Mixer"},
+ {"Right Capture Select", "Phone", "PHONE"},
+
+ /* ALC Sidetone */
+ {"ALC Sidetone Mux", "Stereo", "Left Capture Select"},
+ {"ALC Sidetone Mux", "Stereo", "Right Capture Select"},
+ {"ALC Sidetone Mux", "Left", "Left Capture Select"},
+ {"ALC Sidetone Mux", "Right", "Right Capture Select"},
+
+ /* ADC's */
+ {"Left ADC", NULL, "Left Capture Select"},
+ {"Right ADC", NULL, "Right Capture Select"},
+
+ /* outputs */
+ {"MONOOUT", NULL, "Phone Mixer"},
+ {"HPOUTL", NULL, "Headphone PGA"},
+ {"Headphone PGA", NULL, "Left HP Mixer"},
+ {"HPOUTR", NULL, "Headphone PGA"},
+ {"Headphone PGA", NULL, "Right HP Mixer"},
+
+ /* mono mixer */
+ {"Mono Mixer", NULL, "Left HP Mixer"},
+ {"Mono Mixer", NULL, "Right HP Mixer"},
+
+ /* Out3 Mux */
+ {"Out3 Mux", "Left", "Left HP Mixer"},
+ {"Out3 Mux", "Mono", "Phone Mixer"},
+ {"Out3 Mux", "Left + Right", "Mono Mixer"},
+ {"Out 3 PGA", NULL, "Out3 Mux"},
+ {"OUT3", NULL, "Out 3 PGA"},
+
+ /* speaker Mux */
+ {"Speaker Mux", "Speaker Mix", "Speaker Mixer"},
+ {"Speaker Mux", "Headphone Mix", "Mono Mixer"},
+ {"Speaker PGA", NULL, "Speaker Mux"},
+ {"LOUT2", NULL, "Speaker PGA"},
+ {"ROUT2", NULL, "Speaker PGA"},
+};
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+ reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 ||
+ reg == AC97_REC_GAIN)
+ return soc_ac97_ops.read(codec->ac97, reg);
+ else {
+ reg = reg >> 1;
+
+ if (reg >= (ARRAY_SIZE(wm9712_reg)))
+ return -EIO;
+
+ return cache[reg];
+ }
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg < 0x7c)
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ reg = reg >> 1;
+ if (reg < (ARRAY_SIZE(wm9712_reg)))
+ cache[reg] = val;
+
+ return 0;
+}
+
+static int ac97_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec =rtd->codec;
+ int reg;
+ u16 vra;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = AC97_PCM_FRONT_DAC_RATE;
+ else
+ reg = AC97_PCM_LR_ADC_RATE;
+
+ return ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_aux_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ u16 vra, xsle;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+ xsle = ac97_read(codec, AC97_PCI_SID);
+ ac97_write(codec, AC97_PCI_SID, xsle | 0x8000);
+
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+
+ return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate);
+}
+
+#define WM9712_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops wm9712_dai_ops_hifi = {
+ .prepare = ac97_prepare,
+};
+
+static struct snd_soc_dai_ops wm9712_dai_ops_aux = {
+ .prepare = ac97_aux_prepare,
+};
+
+static struct snd_soc_dai_driver wm9712_dai[] = {
+{
+ .name = "wm9712-hifi",
+ .ac97_control = 1,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9712_AC97_RATES,
+ .formats = SND_SOC_STD_AC97_FMTS,},
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9712_AC97_RATES,
+ .formats = SND_SOC_STD_AC97_FMTS,},
+ .ops = &wm9712_dai_ops_hifi,
+},
+{
+ .name = "wm9712-aux",
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM9712_AC97_RATES,
+ .formats = SND_SOC_STD_AC97_FMTS,},
+ .ops = &wm9712_dai_ops_aux,
+}
+};
+
+static int wm9712_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ ac97_write(codec, AC97_POWERDOWN, 0x0000);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* disable everything including AC link */
+ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+ ac97_write(codec, AC97_POWERDOWN, 0xffff);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int wm9712_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, 0) == wm9712_reg[0])
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ if (soc_ac97_ops.warm_reset)
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, 0) != wm9712_reg[0])
+ goto err;
+ return 0;
+
+err:
+ printk(KERN_ERR "WM9712 AC97 reset failed\n");
+ return -EIO;
+}
+
+static int wm9712_soc_suspend(struct snd_soc_codec *codec,
+ pm_message_t state)
+{
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm9712_soc_resume(struct snd_soc_codec *codec)
+{
+ int i, ret;
+ u16 *cache = codec->reg_cache;
+
+ ret = wm9712_reset(codec, 1);
+ if (ret < 0) {
+ printk(KERN_ERR "could not reset AC97 codec\n");
+ return ret;
+ }
+
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ if (ret == 0) {
+ /* Sync reg_cache with the hardware after cold reset */
+ for (i = 2; i < ARRAY_SIZE(wm9712_reg) << 1; i += 2) {
+ if (i == AC97_INT_PAGING || i == AC97_POWERDOWN ||
+ (i > 0x58 && i != 0x5c))
+ continue;
+ soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+ }
+ }
+
+ return ret;
+}
+
+static int wm9712_soc_probe(struct snd_soc_codec *codec)
+{
+ int ret = 0;
+
+ printk(KERN_INFO "WM9711/WM9712 SoC Audio Codec %s\n", WM9712_VERSION);
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "wm9712: failed to register AC97 codec\n");
+ return ret;
+ }
+
+ ret = wm9712_reset(codec, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "Failed to reset WM9712: AC97 link error\n");
+ goto reset_err;
+ }
+
+ /* set alc mux to none */
+ ac97_write(codec, AC97_VIDEO, ac97_read(codec, AC97_VIDEO) | 0x3000);
+
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ snd_soc_add_controls(codec, wm9712_snd_ac97_controls,
+ ARRAY_SIZE(wm9712_snd_ac97_controls));
+
+ return 0;
+
+reset_err:
+ snd_soc_free_ac97_codec(codec);
+ return ret;
+}
+
+static int wm9712_soc_remove(struct snd_soc_codec *codec)
+{
+ snd_soc_free_ac97_codec(codec);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm9712 = {
+ .probe = wm9712_soc_probe,
+ .remove = wm9712_soc_remove,
+ .suspend = wm9712_soc_suspend,
+ .resume = wm9712_soc_resume,
+ .read = ac97_read,
+ .write = ac97_write,
+ .set_bias_level = wm9712_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm9712_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+ .reg_cache_default = wm9712_reg,
+ .dapm_widgets = wm9712_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm9712_dapm_widgets),
+ .dapm_routes = wm9712_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(wm9712_audio_map),
+};
+
+static __devinit int wm9712_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_wm9712, wm9712_dai, ARRAY_SIZE(wm9712_dai));
+}
+
+static int __devexit wm9712_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver wm9712_codec_driver = {
+ .driver = {
+ .name = "wm9712-codec",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = wm9712_probe,
+ .remove = __devexit_p(wm9712_remove),
+};
+
+static int __init wm9712_init(void)
+{
+ return platform_driver_register(&wm9712_codec_driver);
+}
+module_init(wm9712_init);
+
+static void __exit wm9712_exit(void)
+{
+ platform_driver_unregister(&wm9712_codec_driver);
+}
+module_exit(wm9712_exit);
+
+MODULE_DESCRIPTION("ASoC WM9711/WM9712 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm9712.h b/sound/soc/codecs/wm9712.h
new file mode 100644
index 00000000..fb69c3aa
--- /dev/null
+++ b/sound/soc/codecs/wm9712.h
@@ -0,0 +1,11 @@
+/*
+ * wm9712.h -- WM9712 Soc Audio driver
+ */
+
+#ifndef _WM9712_H
+#define _WM9712_H
+
+#define WM9712_DAI_AC97_HIFI 0
+#define WM9712_DAI_AC97_AUX 1
+
+#endif
diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c
new file mode 100644
index 00000000..7167cb67
--- /dev/null
+++ b/sound/soc/codecs/wm9713.c
@@ -0,0 +1,1294 @@
+/*
+ * wm9713.c -- ALSA Soc WM9713 codec support
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ * Features:-
+ *
+ * o Support for AC97 Codec, Voice DAC and Aux DAC
+ * o Support for DAPM
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+
+#include "wm9713.h"
+
+struct wm9713_priv {
+ u32 pll_in; /* PLL input frequency */
+};
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg);
+static int ac97_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val);
+
+/*
+ * WM9713 register cache
+ * Reg 0x3c bit 15 is used by touch driver.
+ */
+static const u16 wm9713_reg[] = {
+ 0x6174, 0x8080, 0x8080, 0x8080,
+ 0xc880, 0xe808, 0xe808, 0x0808,
+ 0x00da, 0x8000, 0xd600, 0xaaa0,
+ 0xaaa0, 0xaaa0, 0x0000, 0x0000,
+ 0x0f0f, 0x0040, 0x0000, 0x7f00,
+ 0x0405, 0x0410, 0xbb80, 0xbb80,
+ 0x0000, 0xbb80, 0x0000, 0x4523,
+ 0x0000, 0x2000, 0x7eff, 0xffff,
+ 0x0000, 0x0000, 0x0080, 0x0000,
+ 0x0000, 0x0000, 0xfffe, 0xffff,
+ 0x0000, 0x0000, 0x0000, 0xfffe,
+ 0x4000, 0x0000, 0x0000, 0x0000,
+ 0xb032, 0x3e00, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0006,
+ 0x0001, 0x0000, 0x574d, 0x4c13,
+ 0x0000, 0x0000, 0x0000
+};
+
+/* virtual HP mixers regs */
+#define HPL_MIXER 0x80
+#define HPR_MIXER 0x82
+#define MICB_MUX 0x82
+
+static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"};
+static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"};
+static const char *wm9713_rec_src[] =
+ {"Mic 1", "Mic 2", "Line", "Mono In", "Headphone", "Speaker",
+ "Mono Out", "Zh"};
+static const char *wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
+static const char *wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"};
+static const char *wm9713_mono_pga[] = {"Vmid", "Zh", "Mono", "Inv",
+ "Mono Vmid", "Inv Vmid"};
+static const char *wm9713_spk_pga[] =
+ {"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid",
+ "Speaker Vmid", "Inv Vmid"};
+static const char *wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone",
+ "Headphone Vmid"};
+static const char *wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "Inv 1 Vmid"};
+static const char *wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "Inv 2 Vmid"};
+static const char *wm9713_dac_inv[] =
+ {"Off", "Mono", "Speaker", "Left Headphone", "Right Headphone",
+ "Headphone Mono", "NC", "Vmid"};
+static const char *wm9713_bass[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm9713_ng_type[] = {"Constant Gain", "Mute"};
+static const char *wm9713_mic_select[] = {"Mic 1", "Mic 2 A", "Mic 2 B"};
+static const char *wm9713_micb_select[] = {"MPB", "MPA"};
+
+static const struct soc_enum wm9713_enum[] = {
+SOC_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), /* record mic mixer 0 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), /* record mux hp 1 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux), /* record mux mono 2 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src), /* record mux left 3 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src), /* record mux right 4*/
+SOC_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), /* record step size 5 */
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), /* alc source select 6*/
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), /* mono input select 7 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 11, 8, wm9713_spk_pga), /* speaker left input select 8 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 6, 3, wm9713_hp_pga), /* headphone left input 10 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 4, 3, wm9713_hp_pga), /* headphone right input 11 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), /* out 3 source 12 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), /* out 4 source 13 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 13, 8, wm9713_dac_inv), /* dac invert 1 14 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */
+SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */
+SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */
+SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */
+};
+
+static const DECLARE_TLV_DB_SCALE(out_tlv, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(main_tlv, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(misc_tlv, -1500, 300, 0);
+static unsigned int mic_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 2, TLV_DB_SCALE_ITEM(1200, 600, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+
+static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = {
+SOC_DOUBLE_TLV("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1, out_tlv),
+SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1),
+SOC_DOUBLE_TLV("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1,
+ out_tlv),
+SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 7, 1, 1),
+SOC_DOUBLE_TLV("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1, main_tlv),
+SOC_DOUBLE_TLV("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1, main_tlv),
+SOC_SINGLE_TLV("Mic 1 Volume", AC97_MIC, 8, 31, 1, main_tlv),
+SOC_SINGLE_TLV("Mic 2 Volume", AC97_MIC, 0, 31, 1, main_tlv),
+SOC_SINGLE_TLV("Mic 1 Preamp Volume", AC97_3D_CONTROL, 10, 3, 0, mic_tlv),
+SOC_SINGLE_TLV("Mic 2 Preamp Volume", AC97_3D_CONTROL, 12, 3, 0, mic_tlv),
+
+SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0),
+SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1),
+
+SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1),
+SOC_ENUM("Capture Volume Steps", wm9713_enum[5]),
+SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0),
+SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0),
+
+SOC_SINGLE_TLV("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1, misc_tlv),
+SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0),
+SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0),
+
+SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
+SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
+SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
+SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
+SOC_ENUM("ALC Function", wm9713_enum[6]),
+SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
+SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0),
+SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
+SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
+SOC_ENUM("ALC NG Type", wm9713_enum[17]),
+SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0),
+
+SOC_DOUBLE("Speaker Playback ZC Switch", AC97_MASTER, 14, 6, 1, 0),
+SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0),
+
+SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0),
+SOC_SINGLE_TLV("Out4 Playback Volume", AC97_MASTER_MONO, 8, 31, 1, out_tlv),
+
+SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1),
+SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0),
+SOC_SINGLE_TLV("Out3 Playback Volume", AC97_MASTER_MONO, 0, 31, 1, out_tlv),
+
+SOC_SINGLE_TLV("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1, main_tlv),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1),
+SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0),
+SOC_SINGLE_TLV("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1, out_tlv),
+
+SOC_SINGLE_TLV("Headphone Mixer Beep Playback Volume", AC97_AUX, 12, 7, 1,
+ misc_tlv),
+SOC_SINGLE_TLV("Speaker Mixer Beep Playback Volume", AC97_AUX, 8, 7, 1,
+ misc_tlv),
+SOC_SINGLE_TLV("Mono Mixer Beep Playback Volume", AC97_AUX, 4, 7, 1, misc_tlv),
+
+SOC_SINGLE_TLV("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1,
+ misc_tlv),
+SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1),
+SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1),
+
+SOC_SINGLE_TLV("Headphone Mixer Aux Playback Volume", AC97_REC_SEL, 12, 7, 1,
+ misc_tlv),
+
+SOC_SINGLE_TLV("Speaker Mixer Voice Playback Volume", AC97_PCM, 8, 7, 1,
+ misc_tlv),
+SOC_SINGLE_TLV("Speaker Mixer Aux Playback Volume", AC97_REC_SEL, 8, 7, 1,
+ misc_tlv),
+
+SOC_SINGLE_TLV("Mono Mixer Voice Playback Volume", AC97_PCM, 4, 7, 1,
+ misc_tlv),
+SOC_SINGLE_TLV("Mono Mixer Aux Playback Volume", AC97_REC_SEL, 4, 7, 1,
+ misc_tlv),
+
+SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1),
+SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1),
+
+SOC_ENUM("Bass Control", wm9713_enum[16]),
+SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1),
+SOC_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1),
+SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0),
+SOC_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1),
+SOC_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1),
+
+SOC_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0),
+SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0),
+SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1),
+};
+
+static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u16 status, rate;
+
+ BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
+
+ /* Gracefully shut down the voice interface. */
+ status = ac97_read(codec, AC97_EXTENDED_MID) | 0x1000;
+ rate = ac97_read(codec, AC97_HANDSET_RATE) & 0xF0FF;
+ ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0200);
+ schedule_timeout_interruptible(msecs_to_jiffies(1));
+ ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0F00);
+ ac97_write(codec, AC97_EXTENDED_MID, status);
+
+ return 0;
+}
+
+
+/* We have to create a fake left and right HP mixers because
+ * the codec only has a single control that is shared by both channels.
+ * This makes it impossible to determine the audio path using the current
+ * register map, thus we add a new (virtual) register to help determine the
+ * audio route within the device.
+ */
+static int mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u16 l, r, beep, tone, phone, rec, pcm, aux;
+
+ l = ac97_read(w->codec, HPL_MIXER);
+ r = ac97_read(w->codec, HPR_MIXER);
+ beep = ac97_read(w->codec, AC97_PC_BEEP);
+ tone = ac97_read(w->codec, AC97_MASTER_TONE);
+ phone = ac97_read(w->codec, AC97_PHONE);
+ rec = ac97_read(w->codec, AC97_REC_SEL);
+ pcm = ac97_read(w->codec, AC97_PCM);
+ aux = ac97_read(w->codec, AC97_AUX);
+
+ if (event & SND_SOC_DAPM_PRE_REG)
+ return 0;
+ if ((l & 0x1) || (r & 0x1))
+ ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
+
+ if ((l & 0x2) || (r & 0x2))
+ ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000);
+
+ if ((l & 0x4) || (r & 0x4))
+ ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
+
+ if ((l & 0x8) || (r & 0x8))
+ ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000);
+
+ if ((l & 0x10) || (r & 0x10))
+ ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
+
+ if ((l & 0x20) || (r & 0x20))
+ ac97_write(w->codec, AC97_AUX, aux & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_AUX, aux | 0x8000);
+
+ return 0;
+}
+
+/* Left Headphone Mixers */
+static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = {
+SOC_DAPM_SINGLE("Beep Playback Switch", HPL_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0),
+};
+
+/* Right Headphone Mixers */
+static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = {
+SOC_DAPM_SINGLE("Beep Playback Switch", HPR_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0),
+};
+
+/* headphone capture mux */
+static const struct snd_kcontrol_new wm9713_hp_rec_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[1]);
+
+/* headphone mic mux */
+static const struct snd_kcontrol_new wm9713_hp_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[0]);
+
+/* Speaker Mixer */
+static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("Beep Playback Switch", AC97_AUX, 11, 1, 1),
+SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1),
+SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1),
+SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1),
+SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm9713_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Beep Playback Switch", AC97_AUX, 7, 1, 1),
+SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 7, 1, 1),
+SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 7, 1, 1),
+SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 13, 1, 1),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 13, 1, 1),
+SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 13, 1, 1),
+SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_LINE, 7, 1, 1),
+SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_LINE, 6, 1, 1),
+};
+
+/* mono mic mux */
+static const struct snd_kcontrol_new wm9713_mono_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[2]);
+
+/* mono output mux */
+static const struct snd_kcontrol_new wm9713_mono_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[7]);
+
+/* speaker left output mux */
+static const struct snd_kcontrol_new wm9713_hp_spkl_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[8]);
+
+/* speaker right output mux */
+static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[9]);
+
+/* headphone left output mux */
+static const struct snd_kcontrol_new wm9713_hpl_out_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[10]);
+
+/* headphone right output mux */
+static const struct snd_kcontrol_new wm9713_hpr_out_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[11]);
+
+/* Out3 mux */
+static const struct snd_kcontrol_new wm9713_out3_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[12]);
+
+/* Out4 mux */
+static const struct snd_kcontrol_new wm9713_out4_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[13]);
+
+/* DAC inv mux 1 */
+static const struct snd_kcontrol_new wm9713_dac_inv1_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[14]);
+
+/* DAC inv mux 2 */
+static const struct snd_kcontrol_new wm9713_dac_inv2_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[15]);
+
+/* Capture source left */
+static const struct snd_kcontrol_new wm9713_rec_srcl_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[3]);
+
+/* Capture source right */
+static const struct snd_kcontrol_new wm9713_rec_srcr_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[4]);
+
+/* mic source */
+static const struct snd_kcontrol_new wm9713_mic_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[18]);
+
+/* mic source B virtual control */
+static const struct snd_kcontrol_new wm9713_micb_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[19]);
+
+static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = {
+SND_SOC_DAPM_MUX("Capture Headphone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_rec_mux_controls),
+SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_mic_mux_controls),
+SND_SOC_DAPM_MUX("Capture Mono Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_mono_mic_mux_controls),
+SND_SOC_DAPM_MUX("Mono Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_mono_mux_controls),
+SND_SOC_DAPM_MUX("Left Speaker Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_spkl_mux_controls),
+SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_spkr_mux_controls),
+SND_SOC_DAPM_MUX("Left Headphone Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hpl_out_mux_controls),
+SND_SOC_DAPM_MUX("Right Headphone Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hpr_out_mux_controls),
+SND_SOC_DAPM_MUX("Out 3 Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_out3_mux_controls),
+SND_SOC_DAPM_MUX("Out 4 Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_out4_mux_controls),
+SND_SOC_DAPM_MUX("DAC Inv Mux 1", SND_SOC_NOPM, 0, 0,
+ &wm9713_dac_inv1_mux_controls),
+SND_SOC_DAPM_MUX("DAC Inv Mux 2", SND_SOC_NOPM, 0, 0,
+ &wm9713_dac_inv2_mux_controls),
+SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_rec_srcl_mux_controls),
+SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_rec_srcr_mux_controls),
+SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_mic_sel_mux_controls),
+SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_micb_sel_mux_controls),
+SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1,
+ &wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1,
+ &wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1,
+ &wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1,
+ &wm9713_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm9713_speaker_mixer_controls)),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1),
+SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_DAC_E("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1,
+ wm9713_voice_shutdown, SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1),
+SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0),
+SND_SOC_DAPM_ADC("Left HiFi ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Right HiFi ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Left Voice ADC", "Left Voice Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Right Voice ADC", "Right Voice Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker", AC97_EXTENDED_MSTATUS, 7, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 3", AC97_EXTENDED_MSTATUS, 11, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 4", AC97_EXTENDED_MSTATUS, 12, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out", AC97_EXTENDED_MSTATUS, 13, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Line In", AC97_EXTENDED_MSTATUS, 6, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Line In", AC97_EXTENDED_MSTATUS, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mono In", AC97_EXTENDED_MSTATUS, 4, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic A PGA", AC97_EXTENDED_MSTATUS, 3, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic B PGA", AC97_EXTENDED_MSTATUS, 2, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic A Pre Amp", AC97_EXTENDED_MSTATUS, 1, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic B Pre Amp", AC97_EXTENDED_MSTATUS, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_EXTENDED_MSTATUS, 14, 1),
+SND_SOC_DAPM_OUTPUT("MONO"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_OUTPUT("SPKL"),
+SND_SOC_DAPM_OUTPUT("SPKR"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_INPUT("LINEL"),
+SND_SOC_DAPM_INPUT("LINER"),
+SND_SOC_DAPM_INPUT("MONOIN"),
+SND_SOC_DAPM_INPUT("PCBEEP"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2A"),
+SND_SOC_DAPM_INPUT("MIC2B"),
+SND_SOC_DAPM_VMID("VMID"),
+};
+
+static const struct snd_soc_dapm_route wm9713_audio_map[] = {
+ /* left HP mixer */
+ {"Left HP Mixer", "Beep Playback Switch", "PCBEEP"},
+ {"Left HP Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Left HP Mixer", "Bypass Playback Switch", "Left Line In"},
+ {"Left HP Mixer", "PCM Playback Switch", "Left DAC"},
+ {"Left HP Mixer", "MonoIn Playback Switch", "Mono In"},
+ {"Left HP Mixer", NULL, "Capture Headphone Mux"},
+
+ /* right HP mixer */
+ {"Right HP Mixer", "Beep Playback Switch", "PCBEEP"},
+ {"Right HP Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Right HP Mixer", "Bypass Playback Switch", "Right Line In"},
+ {"Right HP Mixer", "PCM Playback Switch", "Right DAC"},
+ {"Right HP Mixer", "MonoIn Playback Switch", "Mono In"},
+ {"Right HP Mixer", NULL, "Capture Headphone Mux"},
+
+ /* virtual mixer - mixes left & right channels for spk and mono */
+ {"AC97 Mixer", NULL, "Left DAC"},
+ {"AC97 Mixer", NULL, "Right DAC"},
+ {"Line Mixer", NULL, "Right Line In"},
+ {"Line Mixer", NULL, "Left Line In"},
+ {"HP Mixer", NULL, "Left HP Mixer"},
+ {"HP Mixer", NULL, "Right HP Mixer"},
+ {"Capture Mixer", NULL, "Left Capture Source"},
+ {"Capture Mixer", NULL, "Right Capture Source"},
+
+ /* speaker mixer */
+ {"Speaker Mixer", "Beep Playback Switch", "PCBEEP"},
+ {"Speaker Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Speaker Mixer", "Bypass Playback Switch", "Line Mixer"},
+ {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Speaker Mixer", "MonoIn Playback Switch", "Mono In"},
+
+ /* mono mixer */
+ {"Mono Mixer", "Beep Playback Switch", "PCBEEP"},
+ {"Mono Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Mono Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Mono Mixer", "Bypass Playback Switch", "Line Mixer"},
+ {"Mono Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Mono Mixer", "Mic 1 Sidetone Switch", "Mic A PGA"},
+ {"Mono Mixer", "Mic 2 Sidetone Switch", "Mic B PGA"},
+ {"Mono Mixer", NULL, "Capture Mono Mux"},
+
+ /* DAC inv mux 1 */
+ {"DAC Inv Mux 1", "Mono", "Mono Mixer"},
+ {"DAC Inv Mux 1", "Speaker", "Speaker Mixer"},
+ {"DAC Inv Mux 1", "Left Headphone", "Left HP Mixer"},
+ {"DAC Inv Mux 1", "Right Headphone", "Right HP Mixer"},
+ {"DAC Inv Mux 1", "Headphone Mono", "HP Mixer"},
+
+ /* DAC inv mux 2 */
+ {"DAC Inv Mux 2", "Mono", "Mono Mixer"},
+ {"DAC Inv Mux 2", "Speaker", "Speaker Mixer"},
+ {"DAC Inv Mux 2", "Left Headphone", "Left HP Mixer"},
+ {"DAC Inv Mux 2", "Right Headphone", "Right HP Mixer"},
+ {"DAC Inv Mux 2", "Headphone Mono", "HP Mixer"},
+
+ /* headphone left mux */
+ {"Left Headphone Out Mux", "Headphone", "Left HP Mixer"},
+
+ /* headphone right mux */
+ {"Right Headphone Out Mux", "Headphone", "Right HP Mixer"},
+
+ /* speaker left mux */
+ {"Left Speaker Out Mux", "Headphone", "Left HP Mixer"},
+ {"Left Speaker Out Mux", "Speaker", "Speaker Mixer"},
+ {"Left Speaker Out Mux", "Inv", "DAC Inv Mux 1"},
+
+ /* speaker right mux */
+ {"Right Speaker Out Mux", "Headphone", "Right HP Mixer"},
+ {"Right Speaker Out Mux", "Speaker", "Speaker Mixer"},
+ {"Right Speaker Out Mux", "Inv", "DAC Inv Mux 2"},
+
+ /* mono mux */
+ {"Mono Out Mux", "Mono", "Mono Mixer"},
+ {"Mono Out Mux", "Inv", "DAC Inv Mux 1"},
+
+ /* out 3 mux */
+ {"Out 3 Mux", "Inv 1", "DAC Inv Mux 1"},
+
+ /* out 4 mux */
+ {"Out 4 Mux", "Inv 2", "DAC Inv Mux 2"},
+
+ /* output pga */
+ {"HPL", NULL, "Left Headphone"},
+ {"Left Headphone", NULL, "Left Headphone Out Mux"},
+ {"HPR", NULL, "Right Headphone"},
+ {"Right Headphone", NULL, "Right Headphone Out Mux"},
+ {"OUT3", NULL, "Out 3"},
+ {"Out 3", NULL, "Out 3 Mux"},
+ {"OUT4", NULL, "Out 4"},
+ {"Out 4", NULL, "Out 4 Mux"},
+ {"SPKL", NULL, "Left Speaker"},
+ {"Left Speaker", NULL, "Left Speaker Out Mux"},
+ {"SPKR", NULL, "Right Speaker"},
+ {"Right Speaker", NULL, "Right Speaker Out Mux"},
+ {"MONO", NULL, "Mono Out"},
+ {"Mono Out", NULL, "Mono Out Mux"},
+
+ /* input pga */
+ {"Left Line In", NULL, "LINEL"},
+ {"Right Line In", NULL, "LINER"},
+ {"Mono In", NULL, "MONOIN"},
+ {"Mic A PGA", NULL, "Mic A Pre Amp"},
+ {"Mic B PGA", NULL, "Mic B Pre Amp"},
+
+ /* left capture select */
+ {"Left Capture Source", "Mic 1", "Mic A Pre Amp"},
+ {"Left Capture Source", "Mic 2", "Mic B Pre Amp"},
+ {"Left Capture Source", "Line", "LINEL"},
+ {"Left Capture Source", "Mono In", "MONOIN"},
+ {"Left Capture Source", "Headphone", "Left HP Mixer"},
+ {"Left Capture Source", "Speaker", "Speaker Mixer"},
+ {"Left Capture Source", "Mono Out", "Mono Mixer"},
+
+ /* right capture select */
+ {"Right Capture Source", "Mic 1", "Mic A Pre Amp"},
+ {"Right Capture Source", "Mic 2", "Mic B Pre Amp"},
+ {"Right Capture Source", "Line", "LINER"},
+ {"Right Capture Source", "Mono In", "MONOIN"},
+ {"Right Capture Source", "Headphone", "Right HP Mixer"},
+ {"Right Capture Source", "Speaker", "Speaker Mixer"},
+ {"Right Capture Source", "Mono Out", "Mono Mixer"},
+
+ /* left ADC */
+ {"Left ADC", NULL, "Left Capture Source"},
+ {"Left Voice ADC", NULL, "Left ADC"},
+ {"Left HiFi ADC", NULL, "Left ADC"},
+
+ /* right ADC */
+ {"Right ADC", NULL, "Right Capture Source"},
+ {"Right Voice ADC", NULL, "Right ADC"},
+ {"Right HiFi ADC", NULL, "Right ADC"},
+
+ /* mic */
+ {"Mic A Pre Amp", NULL, "Mic A Source"},
+ {"Mic A Source", "Mic 1", "MIC1"},
+ {"Mic A Source", "Mic 2 A", "MIC2A"},
+ {"Mic A Source", "Mic 2 B", "Mic B Source"},
+ {"Mic B Pre Amp", "MPB", "Mic B Source"},
+ {"Mic B Source", NULL, "MIC2B"},
+
+ /* headphone capture */
+ {"Capture Headphone Mux", "Stereo", "Capture Mixer"},
+ {"Capture Headphone Mux", "Left", "Left Capture Source"},
+ {"Capture Headphone Mux", "Right", "Right Capture Source"},
+
+ /* mono capture */
+ {"Capture Mono Mux", "Stereo", "Capture Mixer"},
+ {"Capture Mono Mux", "Left", "Left Capture Source"},
+ {"Capture Mono Mux", "Right", "Right Capture Source"},
+};
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+ reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 ||
+ reg == AC97_CD)
+ return soc_ac97_ops.read(codec->ac97, reg);
+ else {
+ reg = reg >> 1;
+
+ if (reg >= (ARRAY_SIZE(wm9713_reg)))
+ return -EIO;
+
+ return cache[reg];
+ }
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < 0x7c)
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ reg = reg >> 1;
+ if (reg < (ARRAY_SIZE(wm9713_reg)))
+ cache[reg] = val;
+
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 divsel:1;
+ u32 divctl:1;
+ u32 lf:1;
+ u32 n:4;
+ u32 k:24;
+};
+
+/* The size in bits of the PLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 22) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+
+ /* The the PLL output is always 98.304MHz. */
+ target = 98304000;
+
+ /* If the input frequency is over 14.4MHz then scale it down. */
+ if (source > 14400000) {
+ source >>= 1;
+ pll_div->divsel = 1;
+
+ if (source > 14400000) {
+ source >>= 1;
+ pll_div->divctl = 1;
+ } else
+ pll_div->divctl = 0;
+
+ } else {
+ pll_div->divsel = 0;
+ pll_div->divctl = 0;
+ }
+
+ /* Low frequency sources require an additional divide in the
+ * loop.
+ */
+ if (source < 8192000) {
+ pll_div->lf = 1;
+ target >>= 2;
+ } else
+ pll_div->lf = 0;
+
+ Ndiv = target / source;
+ if ((Ndiv < 5) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM9713 PLL N value %u out of recommended range!\n",
+ Ndiv);
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+}
+
+/**
+ * Please note that changing the PLL input frequency may require
+ * resynchronisation with the AC97 controller.
+ */
+static int wm9713_set_pll(struct snd_soc_codec *codec,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
+ u16 reg, reg2;
+ struct _pll_div pll_div;
+
+ /* turn PLL off ? */
+ if (freq_in == 0) {
+ /* disable PLL power and select ext source */
+ reg = ac97_read(codec, AC97_HANDSET_RATE);
+ ac97_write(codec, AC97_HANDSET_RATE, reg | 0x0080);
+ reg = ac97_read(codec, AC97_EXTENDED_MID);
+ ac97_write(codec, AC97_EXTENDED_MID, reg | 0x0200);
+ wm9713->pll_in = 0;
+ return 0;
+ }
+
+ pll_factors(&pll_div, freq_in);
+
+ if (pll_div.k == 0) {
+ reg = (pll_div.n << 12) | (pll_div.lf << 11) |
+ (pll_div.divsel << 9) | (pll_div.divctl << 8);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+ } else {
+ /* write the fractional k to the reg 0x46 pages */
+ reg2 = (pll_div.n << 12) | (pll_div.lf << 11) | (1 << 10) |
+ (pll_div.divsel << 9) | (pll_div.divctl << 8);
+
+ /* K [21:20] */
+ reg = reg2 | (0x5 << 4) | (pll_div.k >> 20);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [19:16] */
+ reg = reg2 | (0x4 << 4) | ((pll_div.k >> 16) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [15:12] */
+ reg = reg2 | (0x3 << 4) | ((pll_div.k >> 12) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [11:8] */
+ reg = reg2 | (0x2 << 4) | ((pll_div.k >> 8) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [7:4] */
+ reg = reg2 | (0x1 << 4) | ((pll_div.k >> 4) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ reg = reg2 | (0x0 << 4) | (pll_div.k & 0xf); /* K [3:0] */
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+ }
+
+ /* turn PLL on and select as source */
+ reg = ac97_read(codec, AC97_EXTENDED_MID);
+ ac97_write(codec, AC97_EXTENDED_MID, reg & 0xfdff);
+ reg = ac97_read(codec, AC97_HANDSET_RATE);
+ ac97_write(codec, AC97_HANDSET_RATE, reg & 0xff7f);
+ wm9713->pll_in = freq_in;
+
+ /* wait 10ms AC97 link frames for the link to stabilise */
+ schedule_timeout_interruptible(msecs_to_jiffies(10));
+ return 0;
+}
+
+static int wm9713_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ return wm9713_set_pll(codec, pll_id, freq_in, freq_out);
+}
+
+/*
+ * Tristate the PCM DAI lines, tristate can be disabled by calling
+ * wm9713_set_dai_fmt()
+ */
+static int wm9713_set_dai_tristate(struct snd_soc_dai *codec_dai,
+ int tristate)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0x9fff;
+
+ if (tristate)
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+
+ return 0;
+}
+
+/*
+ * Configure WM9713 clock dividers.
+ * Voice DAC needs 256 FS
+ */
+static int wm9713_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM9713_PCMCLK_DIV:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xf0ff;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_CLKA_MULT:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffd;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_CLKB_MULT:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffb;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_HIFI_DIV:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0x8fff;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_PCMBCLK_DIV:
+ reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xf1ff;
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg | div);
+ break;
+ case WM9713_PCMCLK_PLL_DIV:
+ reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80;
+ ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x60 | div);
+ break;
+ case WM9713_HIFI_PLL_DIV:
+ reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80;
+ ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x70 | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm9713_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 gpio = ac97_read(codec, AC97_GPIO_CFG) & 0xffc5;
+ u16 reg = 0x8000;
+
+ /* clock masters */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ reg |= 0x4000;
+ gpio |= 0x0010;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ reg |= 0x6000;
+ gpio |= 0x0018;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ reg |= 0x2000;
+ gpio |= 0x001a;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ gpio |= 0x0012;
+ break;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ reg |= 0x00c0;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ reg |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ reg |= 0x0040;
+ break;
+ }
+
+ /* DAI format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ reg |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ reg |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ reg |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ reg |= 0x0043;
+ break;
+ }
+
+ ac97_write(codec, AC97_GPIO_CFG, gpio);
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+ return 0;
+}
+
+static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xfff3;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ reg |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ reg |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ reg |= 0x000c;
+ break;
+ }
+
+ /* enable PCM interface in master mode */
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+ return 0;
+}
+
+static int ac97_hifi_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int reg;
+ u16 vra;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = AC97_PCM_FRONT_DAC_RATE;
+ else
+ reg = AC97_PCM_LR_ADC_RATE;
+
+ return ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_aux_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u16 vra, xsle;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+ xsle = ac97_read(codec, AC97_PCI_SID);
+ ac97_write(codec, AC97_PCI_SID, xsle | 0x8000);
+
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+
+ return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate);
+}
+
+#define WM9713_RATES (SNDRV_PCM_RATE_8000 | \
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM9713_PCM_RATES (SNDRV_PCM_RATE_8000 | \
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM9713_PCM_FORMATS \
+ (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
+ SNDRV_PCM_FORMAT_S24_LE)
+
+static struct snd_soc_dai_ops wm9713_dai_ops_hifi = {
+ .prepare = ac97_hifi_prepare,
+ .set_clkdiv = wm9713_set_dai_clkdiv,
+ .set_pll = wm9713_set_dai_pll,
+};
+
+static struct snd_soc_dai_ops wm9713_dai_ops_aux = {
+ .prepare = ac97_aux_prepare,
+ .set_clkdiv = wm9713_set_dai_clkdiv,
+ .set_pll = wm9713_set_dai_pll,
+};
+
+static struct snd_soc_dai_ops wm9713_dai_ops_voice = {
+ .hw_params = wm9713_pcm_hw_params,
+ .set_clkdiv = wm9713_set_dai_clkdiv,
+ .set_pll = wm9713_set_dai_pll,
+ .set_fmt = wm9713_set_dai_fmt,
+ .set_tristate = wm9713_set_dai_tristate,
+};
+
+static struct snd_soc_dai_driver wm9713_dai[] = {
+{
+ .name = "wm9713-hifi",
+ .ac97_control = 1,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9713_RATES,
+ .formats = SND_SOC_STD_AC97_FMTS,},
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9713_RATES,
+ .formats = SND_SOC_STD_AC97_FMTS,},
+ .ops = &wm9713_dai_ops_hifi,
+ },
+ {
+ .name = "wm9713-aux",
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM9713_RATES,
+ .formats = SND_SOC_STD_AC97_FMTS,},
+ .ops = &wm9713_dai_ops_aux,
+ },
+ {
+ .name = "wm9713-voice",
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM9713_PCM_RATES,
+ .formats = WM9713_PCM_FORMATS,},
+ .capture = {
+ .stream_name = "Voice Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9713_PCM_RATES,
+ .formats = WM9713_PCM_FORMATS,},
+ .ops = &wm9713_dai_ops_voice,
+ .symmetric_rates = 1,
+ },
+};
+
+int wm9713_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, 0) == wm9713_reg[0])
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ if (soc_ac97_ops.warm_reset)
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, 0) != wm9713_reg[0])
+ return -EIO;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm9713_reset);
+
+static int wm9713_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* enable thermal shutdown */
+ reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff;
+ ac97_write(codec, AC97_EXTENDED_MID, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* enable master bias and vmid */
+ reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff;
+ ac97_write(codec, AC97_EXTENDED_MID, reg);
+ ac97_write(codec, AC97_POWERDOWN, 0x0000);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* disable everything including AC link */
+ ac97_write(codec, AC97_EXTENDED_MID, 0xffff);
+ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+ ac97_write(codec, AC97_POWERDOWN, 0xffff);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int wm9713_soc_suspend(struct snd_soc_codec *codec,
+ pm_message_t state)
+{
+ u16 reg;
+
+ /* Disable everything except touchpanel - that will be handled
+ * by the touch driver and left disabled if touch is not in
+ * use. */
+ reg = ac97_read(codec, AC97_EXTENDED_MID);
+ ac97_write(codec, AC97_EXTENDED_MID, reg | 0x7fff);
+ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+ ac97_write(codec, AC97_POWERDOWN, 0x6f00);
+ ac97_write(codec, AC97_POWERDOWN, 0xffff);
+
+ return 0;
+}
+
+static int wm9713_soc_resume(struct snd_soc_codec *codec)
+{
+ struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
+ int i, ret;
+ u16 *cache = codec->reg_cache;
+
+ ret = wm9713_reset(codec, 1);
+ if (ret < 0) {
+ printk(KERN_ERR "could not reset AC97 codec\n");
+ return ret;
+ }
+
+ wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* do we need to re-start the PLL ? */
+ if (wm9713->pll_in)
+ wm9713_set_pll(codec, 0, wm9713->pll_in, 0);
+
+ /* only synchronise the codec if warm reset failed */
+ if (ret == 0) {
+ for (i = 2; i < ARRAY_SIZE(wm9713_reg) << 1; i += 2) {
+ if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID ||
+ i == AC97_EXTENDED_MSTATUS || i > 0x66)
+ continue;
+ soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+ }
+ }
+
+ return ret;
+}
+
+static int wm9713_soc_probe(struct snd_soc_codec *codec)
+{
+ struct wm9713_priv *wm9713;
+ int ret = 0, reg;
+
+ wm9713 = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL);
+ if (wm9713 == NULL)
+ return -ENOMEM;
+ snd_soc_codec_set_drvdata(codec, wm9713);
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0)
+ goto codec_err;
+
+ /* do a cold reset for the controller and then try
+ * a warm reset followed by an optional cold reset for codec */
+ wm9713_reset(codec, 0);
+ ret = wm9713_reset(codec, 1);
+ if (ret < 0) {
+ printk(KERN_ERR "Failed to reset WM9713: AC97 link error\n");
+ goto reset_err;
+ }
+
+ wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* unmute the adc - move to kcontrol */
+ reg = ac97_read(codec, AC97_CD) & 0x7fff;
+ ac97_write(codec, AC97_CD, reg);
+
+ snd_soc_add_controls(codec, wm9713_snd_ac97_controls,
+ ARRAY_SIZE(wm9713_snd_ac97_controls));
+
+ return 0;
+
+reset_err:
+ snd_soc_free_ac97_codec(codec);
+codec_err:
+ kfree(wm9713);
+ return ret;
+}
+
+static int wm9713_soc_remove(struct snd_soc_codec *codec)
+{
+ struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
+ snd_soc_free_ac97_codec(codec);
+ kfree(wm9713);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm9713 = {
+ .probe = wm9713_soc_probe,
+ .remove = wm9713_soc_remove,
+ .suspend = wm9713_soc_suspend,
+ .resume = wm9713_soc_resume,
+ .read = ac97_read,
+ .write = ac97_write,
+ .set_bias_level = wm9713_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm9713_reg),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+ .reg_cache_default = wm9713_reg,
+ .dapm_widgets = wm9713_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm9713_dapm_widgets),
+ .dapm_routes = wm9713_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(wm9713_audio_map),
+};
+
+static __devinit int wm9713_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_wm9713, wm9713_dai, ARRAY_SIZE(wm9713_dai));
+}
+
+static int __devexit wm9713_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver wm9713_codec_driver = {
+ .driver = {
+ .name = "wm9713-codec",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = wm9713_probe,
+ .remove = __devexit_p(wm9713_remove),
+};
+
+static int __init wm9713_init(void)
+{
+ return platform_driver_register(&wm9713_codec_driver);
+}
+module_init(wm9713_init);
+
+static void __exit wm9713_exit(void)
+{
+ platform_driver_unregister(&wm9713_codec_driver);
+}
+module_exit(wm9713_exit);
+
+MODULE_DESCRIPTION("ASoC WM9713/WM9714 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm9713.h b/sound/soc/codecs/wm9713.h
new file mode 100644
index 00000000..793da863
--- /dev/null
+++ b/sound/soc/codecs/wm9713.h
@@ -0,0 +1,50 @@
+/*
+ * wm9713.h -- WM9713 Soc Audio driver
+ */
+
+#ifndef _WM9713_H
+#define _WM9713_H
+
+/* clock inputs */
+#define WM9713_CLKA_PIN 0
+#define WM9713_CLKB_PIN 1
+
+/* clock divider ID's */
+#define WM9713_PCMCLK_DIV 0
+#define WM9713_CLKA_MULT 1
+#define WM9713_CLKB_MULT 2
+#define WM9713_HIFI_DIV 3
+#define WM9713_PCMBCLK_DIV 4
+#define WM9713_PCMCLK_PLL_DIV 5
+#define WM9713_HIFI_PLL_DIV 6
+
+/* Calculate the appropriate bit mask for the external PCM clock divider */
+#define WM9713_PCMDIV(x) ((x - 1) << 8)
+
+/* Calculate the appropriate bit mask for the external HiFi clock divider */
+#define WM9713_HIFIDIV(x) ((x - 1) << 12)
+
+/* MCLK clock mulitipliers */
+#define WM9713_CLKA_X1 (0 << 1)
+#define WM9713_CLKA_X2 (1 << 1)
+#define WM9713_CLKB_X1 (0 << 2)
+#define WM9713_CLKB_X2 (1 << 2)
+
+/* MCLK clock MUX */
+#define WM9713_CLK_MUX_A (0 << 0)
+#define WM9713_CLK_MUX_B (1 << 0)
+
+/* Voice DAI BCLK divider */
+#define WM9713_PCMBCLK_DIV_1 (0 << 9)
+#define WM9713_PCMBCLK_DIV_2 (1 << 9)
+#define WM9713_PCMBCLK_DIV_4 (2 << 9)
+#define WM9713_PCMBCLK_DIV_8 (3 << 9)
+#define WM9713_PCMBCLK_DIV_16 (4 << 9)
+
+#define WM9713_DAI_AC97_HIFI 0
+#define WM9713_DAI_AC97_AUX 1
+#define WM9713_DAI_PCM_VOICE 2
+
+int wm9713_reset(struct snd_soc_codec *codec, int try_warm);
+
+#endif
diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c
new file mode 100644
index 00000000..8712a9f6
--- /dev/null
+++ b/sound/soc/codecs/wm_hubs.c
@@ -0,0 +1,946 @@
+/*
+ * wm_hubs.c -- WM8993/4 common code
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8993.h"
+#include "wm_hubs.h"
+
+const DECLARE_TLV_DB_SCALE(wm_hubs_spkmix_tlv, -300, 300, 0);
+EXPORT_SYMBOL_GPL(wm_hubs_spkmix_tlv);
+
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(inmix_sw_tlv, 0, 3000, 0);
+static const DECLARE_TLV_DB_SCALE(inmix_tlv, -1500, 300, 1);
+static const DECLARE_TLV_DB_SCALE(earpiece_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(outmix_tlv, -2100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(spkmixout_tlv, -1800, 600, 1);
+static const DECLARE_TLV_DB_SCALE(outpga_tlv, -5700, 100, 0);
+static const unsigned int spkboost_tlv[] = {
+ TLV_DB_RANGE_HEAD(7),
+ 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0),
+ 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(line_tlv, -600, 600, 0);
+
+static const char *speaker_ref_text[] = {
+ "SPKVDD/2",
+ "VMID",
+};
+
+static const struct soc_enum speaker_ref =
+ SOC_ENUM_SINGLE(WM8993_SPEAKER_MIXER, 8, 2, speaker_ref_text);
+
+static const char *speaker_mode_text[] = {
+ "Class D",
+ "Class AB",
+};
+
+static const struct soc_enum speaker_mode =
+ SOC_ENUM_SINGLE(WM8993_SPKMIXR_ATTENUATION, 8, 2, speaker_mode_text);
+
+static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op)
+{
+ unsigned int reg;
+ int count = 0;
+ unsigned int val;
+
+ val = op | WM8993_DCS_ENA_CHAN_0 | WM8993_DCS_ENA_CHAN_1;
+
+ /* Trigger the command */
+ snd_soc_write(codec, WM8993_DC_SERVO_0, val);
+
+ dev_dbg(codec->dev, "Waiting for DC servo...\n");
+
+ do {
+ count++;
+ msleep(1);
+ reg = snd_soc_read(codec, WM8993_DC_SERVO_0);
+ dev_dbg(codec->dev, "DC servo: %x\n", reg);
+ } while (reg & op && count < 400);
+
+ if (reg & op)
+ dev_err(codec->dev, "Timed out waiting for DC Servo %x\n",
+ op);
+}
+
+/*
+ * Startup calibration of the DC servo
+ */
+static void calibrate_dc_servo(struct snd_soc_codec *codec)
+{
+ struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+ s8 offset;
+ u16 reg, reg_l, reg_r, dcs_cfg;
+
+ /* If we're using a digital only path and have a previously
+ * callibrated DC servo offset stored then use that. */
+ if (hubs->class_w && hubs->class_w_dcs) {
+ dev_dbg(codec->dev, "Using cached DC servo offset %x\n",
+ hubs->class_w_dcs);
+ snd_soc_write(codec, WM8993_DC_SERVO_3, hubs->class_w_dcs);
+ wait_for_dc_servo(codec,
+ WM8993_DCS_TRIG_DAC_WR_0 |
+ WM8993_DCS_TRIG_DAC_WR_1);
+ return;
+ }
+
+ /* Devices not using a DCS code correction have startup mode */
+ if (hubs->dcs_codes) {
+ /* Set for 32 series updates */
+ snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
+ WM8993_DCS_SERIES_NO_01_MASK,
+ 32 << WM8993_DCS_SERIES_NO_01_SHIFT);
+ wait_for_dc_servo(codec,
+ WM8993_DCS_TRIG_SERIES_0 |
+ WM8993_DCS_TRIG_SERIES_1);
+ } else {
+ wait_for_dc_servo(codec,
+ WM8993_DCS_TRIG_STARTUP_0 |
+ WM8993_DCS_TRIG_STARTUP_1);
+ }
+
+ /* Different chips in the family support different readback
+ * methods.
+ */
+ switch (hubs->dcs_readback_mode) {
+ case 0:
+ reg_l = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1)
+ & WM8993_DCS_INTEG_CHAN_0_MASK;
+ reg_r = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2)
+ & WM8993_DCS_INTEG_CHAN_1_MASK;
+ break;
+ case 1:
+ reg = snd_soc_read(codec, WM8993_DC_SERVO_3);
+ reg_l = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)
+ >> WM8993_DCS_DAC_WR_VAL_1_SHIFT;
+ reg_r = reg & WM8993_DCS_DAC_WR_VAL_0_MASK;
+ break;
+ default:
+ WARN(1, "Unknown DCS readback method\n");
+ break;
+ }
+
+ dev_dbg(codec->dev, "DCS input: %x %x\n", reg_l, reg_r);
+
+ /* Apply correction to DC servo result */
+ if (hubs->dcs_codes) {
+ dev_dbg(codec->dev, "Applying %d code DC servo correction\n",
+ hubs->dcs_codes);
+
+ /* HPOUT1L */
+ offset = reg_l;
+ offset += hubs->dcs_codes;
+ dcs_cfg = (u8)offset << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
+
+ /* HPOUT1R */
+ offset = reg_r;
+ offset += hubs->dcs_codes;
+ dcs_cfg |= (u8)offset;
+
+ dev_dbg(codec->dev, "DCS result: %x\n", dcs_cfg);
+
+ /* Do it */
+ snd_soc_write(codec, WM8993_DC_SERVO_3, dcs_cfg);
+ wait_for_dc_servo(codec,
+ WM8993_DCS_TRIG_DAC_WR_0 |
+ WM8993_DCS_TRIG_DAC_WR_1);
+ } else {
+ dcs_cfg = reg_l << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
+ dcs_cfg |= reg_r;
+ }
+
+ /* Save the callibrated offset if we're in class W mode and
+ * therefore don't have any analogue signal mixed in. */
+ if (hubs->class_w)
+ hubs->class_w_dcs = dcs_cfg;
+}
+
+/*
+ * Update the DC servo calibration on gain changes
+ */
+static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+
+ /* Updating the analogue gains invalidates the DC servo cache */
+ hubs->class_w_dcs = 0;
+
+ /* If we're applying an offset correction then updating the
+ * callibration would be likely to introduce further offsets. */
+ if (hubs->dcs_codes)
+ return ret;
+
+ /* Only need to do this if the outputs are active */
+ if (snd_soc_read(codec, WM8993_POWER_MANAGEMENT_1)
+ & (WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA))
+ snd_soc_update_bits(codec,
+ WM8993_DC_SERVO_0,
+ WM8993_DCS_TRIG_SINGLE_0 |
+ WM8993_DCS_TRIG_SINGLE_1,
+ WM8993_DCS_TRIG_SINGLE_0 |
+ WM8993_DCS_TRIG_SINGLE_1);
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new analogue_snd_controls[] = {
+SOC_SINGLE_TLV("IN1L Volume", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 0, 31, 0,
+ inpga_tlv),
+SOC_SINGLE("IN1L Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN1L ZC Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 6, 1, 0),
+
+SOC_SINGLE_TLV("IN1R Volume", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 0, 31, 0,
+ inpga_tlv),
+SOC_SINGLE("IN1R Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN1R ZC Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 6, 1, 0),
+
+
+SOC_SINGLE_TLV("IN2L Volume", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 0, 31, 0,
+ inpga_tlv),
+SOC_SINGLE("IN2L Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN2L ZC Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 6, 1, 0),
+
+SOC_SINGLE_TLV("IN2R Volume", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 0, 31, 0,
+ inpga_tlv),
+SOC_SINGLE("IN2R Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN2R ZC Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 6, 1, 0),
+
+SOC_SINGLE_TLV("MIXINL IN2L Volume", WM8993_INPUT_MIXER3, 7, 1, 0,
+ inmix_sw_tlv),
+SOC_SINGLE_TLV("MIXINL IN1L Volume", WM8993_INPUT_MIXER3, 4, 1, 0,
+ inmix_sw_tlv),
+SOC_SINGLE_TLV("MIXINL Output Record Volume", WM8993_INPUT_MIXER3, 0, 7, 0,
+ inmix_tlv),
+SOC_SINGLE_TLV("MIXINL IN1LP Volume", WM8993_INPUT_MIXER5, 6, 7, 0, inmix_tlv),
+SOC_SINGLE_TLV("MIXINL Direct Voice Volume", WM8993_INPUT_MIXER5, 0, 6, 0,
+ inmix_tlv),
+
+SOC_SINGLE_TLV("MIXINR IN2R Volume", WM8993_INPUT_MIXER4, 7, 1, 0,
+ inmix_sw_tlv),
+SOC_SINGLE_TLV("MIXINR IN1R Volume", WM8993_INPUT_MIXER4, 4, 1, 0,
+ inmix_sw_tlv),
+SOC_SINGLE_TLV("MIXINR Output Record Volume", WM8993_INPUT_MIXER4, 0, 7, 0,
+ inmix_tlv),
+SOC_SINGLE_TLV("MIXINR IN1RP Volume", WM8993_INPUT_MIXER6, 6, 7, 0, inmix_tlv),
+SOC_SINGLE_TLV("MIXINR Direct Voice Volume", WM8993_INPUT_MIXER6, 0, 6, 0,
+ inmix_tlv),
+
+SOC_SINGLE_TLV("Left Output Mixer IN2RN Volume", WM8993_OUTPUT_MIXER5, 6, 7, 1,
+ outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer IN2LN Volume", WM8993_OUTPUT_MIXER3, 6, 7, 1,
+ outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer IN2LP Volume", WM8993_OUTPUT_MIXER3, 9, 7, 1,
+ outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer IN1L Volume", WM8993_OUTPUT_MIXER3, 0, 7, 1,
+ outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer IN1R Volume", WM8993_OUTPUT_MIXER3, 3, 7, 1,
+ outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer Right Input Volume",
+ WM8993_OUTPUT_MIXER5, 3, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer Left Input Volume",
+ WM8993_OUTPUT_MIXER5, 0, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer DAC Volume", WM8993_OUTPUT_MIXER5, 9, 7, 1,
+ outmix_tlv),
+
+SOC_SINGLE_TLV("Right Output Mixer IN2LN Volume",
+ WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer IN2RN Volume",
+ WM8993_OUTPUT_MIXER4, 6, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer IN1L Volume",
+ WM8993_OUTPUT_MIXER4, 3, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer IN1R Volume",
+ WM8993_OUTPUT_MIXER4, 0, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer IN2RP Volume",
+ WM8993_OUTPUT_MIXER4, 9, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer Left Input Volume",
+ WM8993_OUTPUT_MIXER6, 3, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer Right Input Volume",
+ WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer DAC Volume",
+ WM8993_OUTPUT_MIXER6, 9, 7, 1, outmix_tlv),
+
+SOC_DOUBLE_R_TLV("Output Volume", WM8993_LEFT_OPGA_VOLUME,
+ WM8993_RIGHT_OPGA_VOLUME, 0, 63, 0, outpga_tlv),
+SOC_DOUBLE_R("Output Switch", WM8993_LEFT_OPGA_VOLUME,
+ WM8993_RIGHT_OPGA_VOLUME, 6, 1, 0),
+SOC_DOUBLE_R("Output ZC Switch", WM8993_LEFT_OPGA_VOLUME,
+ WM8993_RIGHT_OPGA_VOLUME, 7, 1, 0),
+
+SOC_SINGLE("Earpiece Switch", WM8993_HPOUT2_VOLUME, 5, 1, 1),
+SOC_SINGLE_TLV("Earpiece Volume", WM8993_HPOUT2_VOLUME, 4, 1, 1, earpiece_tlv),
+
+SOC_SINGLE_TLV("SPKL Input Volume", WM8993_SPKMIXL_ATTENUATION,
+ 5, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKL IN1LP Volume", WM8993_SPKMIXL_ATTENUATION,
+ 4, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKL Output Volume", WM8993_SPKMIXL_ATTENUATION,
+ 3, 1, 1, wm_hubs_spkmix_tlv),
+
+SOC_SINGLE_TLV("SPKR Input Volume", WM8993_SPKMIXR_ATTENUATION,
+ 5, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKR IN1RP Volume", WM8993_SPKMIXR_ATTENUATION,
+ 4, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKR Output Volume", WM8993_SPKMIXR_ATTENUATION,
+ 3, 1, 1, wm_hubs_spkmix_tlv),
+
+SOC_DOUBLE_R_TLV("Speaker Mixer Volume",
+ WM8993_SPKMIXL_ATTENUATION, WM8993_SPKMIXR_ATTENUATION,
+ 0, 3, 1, spkmixout_tlv),
+SOC_DOUBLE_R_TLV("Speaker Volume",
+ WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT,
+ 0, 63, 0, outpga_tlv),
+SOC_DOUBLE_R("Speaker Switch",
+ WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT,
+ 6, 1, 0),
+SOC_DOUBLE_R("Speaker ZC Switch",
+ WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT,
+ 7, 1, 0),
+SOC_DOUBLE_TLV("Speaker Boost Volume", WM8993_SPKOUT_BOOST, 3, 0, 7, 0,
+ spkboost_tlv),
+SOC_ENUM("Speaker Reference", speaker_ref),
+SOC_ENUM("Speaker Mode", speaker_mode),
+
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Headphone Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .tlv.p = outpga_tlv,
+ .info = snd_soc_info_volsw_2r,
+ .get = snd_soc_get_volsw_2r, .put = wm8993_put_dc_servo,
+ .private_value = (unsigned long)&(struct soc_mixer_control) {
+ .reg = WM8993_LEFT_OUTPUT_VOLUME,
+ .rreg = WM8993_RIGHT_OUTPUT_VOLUME,
+ .shift = 0, .max = 63
+ },
+},
+SOC_DOUBLE_R("Headphone Switch", WM8993_LEFT_OUTPUT_VOLUME,
+ WM8993_RIGHT_OUTPUT_VOLUME, 6, 1, 0),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8993_LEFT_OUTPUT_VOLUME,
+ WM8993_RIGHT_OUTPUT_VOLUME, 7, 1, 0),
+
+SOC_SINGLE("LINEOUT1N Switch", WM8993_LINE_OUTPUTS_VOLUME, 6, 1, 1),
+SOC_SINGLE("LINEOUT1P Switch", WM8993_LINE_OUTPUTS_VOLUME, 5, 1, 1),
+SOC_SINGLE_TLV("LINEOUT1 Volume", WM8993_LINE_OUTPUTS_VOLUME, 4, 1, 1,
+ line_tlv),
+
+SOC_SINGLE("LINEOUT2N Switch", WM8993_LINE_OUTPUTS_VOLUME, 2, 1, 1),
+SOC_SINGLE("LINEOUT2P Switch", WM8993_LINE_OUTPUTS_VOLUME, 1, 1, 1),
+SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1,
+ line_tlv),
+};
+
+static int hp_supply_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ switch (hubs->hp_startup_mode) {
+ case 0:
+ break;
+ case 1:
+ /* Enable the headphone amp */
+ snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+ WM8993_HPOUT1L_ENA |
+ WM8993_HPOUT1R_ENA,
+ WM8993_HPOUT1L_ENA |
+ WM8993_HPOUT1R_ENA);
+
+ /* Enable the second stage */
+ snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
+ WM8993_HPOUT1L_DLY |
+ WM8993_HPOUT1R_DLY,
+ WM8993_HPOUT1L_DLY |
+ WM8993_HPOUT1R_DLY);
+ break;
+ default:
+ dev_err(codec->dev, "Unknown HP startup mode %d\n",
+ hubs->hp_startup_mode);
+ break;
+ }
+
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
+ WM8993_CP_ENA, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static int hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ unsigned int reg = snd_soc_read(codec, WM8993_ANALOGUE_HP_0);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
+ WM8993_CP_ENA, WM8993_CP_ENA);
+
+ msleep(5);
+
+ snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+ WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA,
+ WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA);
+
+ reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY;
+ snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
+
+ /* Smallest supported update interval */
+ snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
+ WM8993_DCS_TIMER_PERIOD_01_MASK, 1);
+
+ calibrate_dc_servo(codec);
+
+ reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT |
+ WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT;
+ snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
+ WM8993_HPOUT1L_OUTP |
+ WM8993_HPOUT1R_OUTP |
+ WM8993_HPOUT1L_RMV_SHORT |
+ WM8993_HPOUT1R_RMV_SHORT, 0);
+
+ snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
+ WM8993_HPOUT1L_DLY |
+ WM8993_HPOUT1R_DLY, 0);
+
+ snd_soc_write(codec, WM8993_DC_SERVO_0, 0);
+
+ snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+ WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA,
+ 0);
+ break;
+ }
+
+ return 0;
+}
+
+static int earpiece_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *control, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u16 reg = snd_soc_read(codec, WM8993_ANTIPOP1) & ~WM8993_HPOUT2_IN_ENA;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ reg |= WM8993_HPOUT2_IN_ENA;
+ snd_soc_write(codec, WM8993_ANTIPOP1, reg);
+ udelay(50);
+ break;
+
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_write(codec, WM8993_ANTIPOP1, reg);
+ break;
+
+ default:
+ BUG();
+ break;
+ }
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new in1l_pga[] = {
+SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0),
+SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new in1r_pga[] = {
+SOC_DAPM_SINGLE("IN1RP Switch", WM8993_INPUT_MIXER2, 1, 1, 0),
+SOC_DAPM_SINGLE("IN1RN Switch", WM8993_INPUT_MIXER2, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new in2l_pga[] = {
+SOC_DAPM_SINGLE("IN2LP Switch", WM8993_INPUT_MIXER2, 7, 1, 0),
+SOC_DAPM_SINGLE("IN2LN Switch", WM8993_INPUT_MIXER2, 6, 1, 0),
+};
+
+static const struct snd_kcontrol_new in2r_pga[] = {
+SOC_DAPM_SINGLE("IN2RP Switch", WM8993_INPUT_MIXER2, 3, 1, 0),
+SOC_DAPM_SINGLE("IN2RN Switch", WM8993_INPUT_MIXER2, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixinl[] = {
+SOC_DAPM_SINGLE("IN2L Switch", WM8993_INPUT_MIXER3, 8, 1, 0),
+SOC_DAPM_SINGLE("IN1L Switch", WM8993_INPUT_MIXER3, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixinr[] = {
+SOC_DAPM_SINGLE("IN2R Switch", WM8993_INPUT_MIXER4, 8, 1, 0),
+SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_output_mixer[] = {
+SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0),
+SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0),
+SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0),
+SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0),
+SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0),
+SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0),
+SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_output_mixer[] = {
+SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0),
+SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0),
+SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0),
+SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0),
+SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new earpiece_mixer[] = {
+SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_HPOUT2_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Left Output Switch", WM8993_HPOUT2_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Right Output Switch", WM8993_HPOUT2_MIXER, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_speaker_boost[] = {
+SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 5, 1, 0),
+SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 4, 1, 0),
+SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_speaker_boost[] = {
+SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 2, 1, 0),
+SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 1, 1, 0),
+SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new line1_mix[] = {
+SOC_DAPM_SINGLE("IN1R Switch", WM8993_LINE_MIXER1, 2, 1, 0),
+SOC_DAPM_SINGLE("IN1L Switch", WM8993_LINE_MIXER1, 1, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new line1n_mix[] = {
+SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 6, 1, 0),
+SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER1, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new line1p_mix[] = {
+SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new line2_mix[] = {
+SOC_DAPM_SINGLE("IN1L Switch", WM8993_LINE_MIXER2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN1R Switch", WM8993_LINE_MIXER2, 1, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER2, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new line2n_mix[] = {
+SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER2, 5, 1, 0),
+SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 6, 1, 0),
+};
+
+static const struct snd_kcontrol_new line2p_mix[] = {
+SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 0, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget analogue_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1LN"),
+SND_SOC_DAPM_INPUT("IN1LP"),
+SND_SOC_DAPM_INPUT("IN2LN"),
+SND_SOC_DAPM_INPUT("IN2LP:VXRN"),
+SND_SOC_DAPM_INPUT("IN1RN"),
+SND_SOC_DAPM_INPUT("IN1RP"),
+SND_SOC_DAPM_INPUT("IN2RN"),
+SND_SOC_DAPM_INPUT("IN2RP:VXRP"),
+
+SND_SOC_DAPM_MICBIAS("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0),
+SND_SOC_DAPM_MICBIAS("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0),
+
+SND_SOC_DAPM_SUPPLY("LINEOUT_VMID_BUF", WM8993_ANTIPOP1, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("IN1L PGA", WM8993_POWER_MANAGEMENT_2, 6, 0,
+ in1l_pga, ARRAY_SIZE(in1l_pga)),
+SND_SOC_DAPM_MIXER("IN1R PGA", WM8993_POWER_MANAGEMENT_2, 4, 0,
+ in1r_pga, ARRAY_SIZE(in1r_pga)),
+
+SND_SOC_DAPM_MIXER("IN2L PGA", WM8993_POWER_MANAGEMENT_2, 7, 0,
+ in2l_pga, ARRAY_SIZE(in2l_pga)),
+SND_SOC_DAPM_MIXER("IN2R PGA", WM8993_POWER_MANAGEMENT_2, 5, 0,
+ in2r_pga, ARRAY_SIZE(in2r_pga)),
+
+/* Dummy widgets to represent differential paths */
+SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0,
+ mixinl, ARRAY_SIZE(mixinl)),
+SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0,
+ mixinr, ARRAY_SIZE(mixinr)),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8993_POWER_MANAGEMENT_3, 5, 0,
+ left_output_mixer, ARRAY_SIZE(left_output_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0,
+ right_output_mixer, ARRAY_SIZE(right_output_mixer)),
+
+SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("Headphone Supply", SND_SOC_NOPM, 0, 0, hp_supply_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0,
+ NULL, 0,
+ hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0,
+ earpiece_mixer, ARRAY_SIZE(earpiece_mixer)),
+SND_SOC_DAPM_PGA_E("Earpiece Driver", WM8993_POWER_MANAGEMENT_1, 11, 0,
+ NULL, 0, earpiece_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_MIXER("SPKL Boost", SND_SOC_NOPM, 0, 0,
+ left_speaker_boost, ARRAY_SIZE(left_speaker_boost)),
+SND_SOC_DAPM_MIXER("SPKR Boost", SND_SOC_NOPM, 0, 0,
+ right_speaker_boost, ARRAY_SIZE(right_speaker_boost)),
+
+SND_SOC_DAPM_PGA("SPKL Driver", WM8993_POWER_MANAGEMENT_1, 12, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA("SPKR Driver", WM8993_POWER_MANAGEMENT_1, 13, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_MIXER("LINEOUT1 Mixer", SND_SOC_NOPM, 0, 0,
+ line1_mix, ARRAY_SIZE(line1_mix)),
+SND_SOC_DAPM_MIXER("LINEOUT2 Mixer", SND_SOC_NOPM, 0, 0,
+ line2_mix, ARRAY_SIZE(line2_mix)),
+
+SND_SOC_DAPM_MIXER("LINEOUT1N Mixer", SND_SOC_NOPM, 0, 0,
+ line1n_mix, ARRAY_SIZE(line1n_mix)),
+SND_SOC_DAPM_MIXER("LINEOUT1P Mixer", SND_SOC_NOPM, 0, 0,
+ line1p_mix, ARRAY_SIZE(line1p_mix)),
+SND_SOC_DAPM_MIXER("LINEOUT2N Mixer", SND_SOC_NOPM, 0, 0,
+ line2n_mix, ARRAY_SIZE(line2n_mix)),
+SND_SOC_DAPM_MIXER("LINEOUT2P Mixer", SND_SOC_NOPM, 0, 0,
+ line2p_mix, ARRAY_SIZE(line2p_mix)),
+
+SND_SOC_DAPM_PGA("LINEOUT1N Driver", WM8993_POWER_MANAGEMENT_3, 13, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT1P Driver", WM8993_POWER_MANAGEMENT_3, 12, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT2N Driver", WM8993_POWER_MANAGEMENT_3, 11, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT2P Driver", WM8993_POWER_MANAGEMENT_3, 10, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("SPKOUTLP"),
+SND_SOC_DAPM_OUTPUT("SPKOUTLN"),
+SND_SOC_DAPM_OUTPUT("SPKOUTRP"),
+SND_SOC_DAPM_OUTPUT("SPKOUTRN"),
+SND_SOC_DAPM_OUTPUT("HPOUT1L"),
+SND_SOC_DAPM_OUTPUT("HPOUT1R"),
+SND_SOC_DAPM_OUTPUT("HPOUT2P"),
+SND_SOC_DAPM_OUTPUT("HPOUT2N"),
+SND_SOC_DAPM_OUTPUT("LINEOUT1P"),
+SND_SOC_DAPM_OUTPUT("LINEOUT1N"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2P"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2N"),
+};
+
+static const struct snd_soc_dapm_route analogue_routes[] = {
+ { "MICBIAS1", NULL, "CLK_SYS" },
+ { "MICBIAS2", NULL, "CLK_SYS" },
+
+ { "IN1L PGA", "IN1LP Switch", "IN1LP" },
+ { "IN1L PGA", "IN1LN Switch", "IN1LN" },
+
+ { "IN1R PGA", "IN1RP Switch", "IN1RP" },
+ { "IN1R PGA", "IN1RN Switch", "IN1RN" },
+
+ { "IN2L PGA", "IN2LP Switch", "IN2LP:VXRN" },
+ { "IN2L PGA", "IN2LN Switch", "IN2LN" },
+
+ { "IN2R PGA", "IN2RP Switch", "IN2RP:VXRP" },
+ { "IN2R PGA", "IN2RN Switch", "IN2RN" },
+
+ { "Direct Voice", NULL, "IN2LP:VXRN" },
+ { "Direct Voice", NULL, "IN2RP:VXRP" },
+
+ { "MIXINL", "IN1L Switch", "IN1L PGA" },
+ { "MIXINL", "IN2L Switch", "IN2L PGA" },
+ { "MIXINL", NULL, "Direct Voice" },
+ { "MIXINL", NULL, "IN1LP" },
+ { "MIXINL", NULL, "Left Output Mixer" },
+
+ { "MIXINR", "IN1R Switch", "IN1R PGA" },
+ { "MIXINR", "IN2R Switch", "IN2R PGA" },
+ { "MIXINR", NULL, "Direct Voice" },
+ { "MIXINR", NULL, "IN1RP" },
+ { "MIXINR", NULL, "Right Output Mixer" },
+
+ { "ADCL", NULL, "MIXINL" },
+ { "ADCR", NULL, "MIXINR" },
+
+ { "Left Output Mixer", "Left Input Switch", "MIXINL" },
+ { "Left Output Mixer", "Right Input Switch", "MIXINR" },
+ { "Left Output Mixer", "IN2RN Switch", "IN2RN" },
+ { "Left Output Mixer", "IN2LN Switch", "IN2LN" },
+ { "Left Output Mixer", "IN2LP Switch", "IN2LP:VXRN" },
+ { "Left Output Mixer", "IN1L Switch", "IN1L PGA" },
+ { "Left Output Mixer", "IN1R Switch", "IN1R PGA" },
+
+ { "Right Output Mixer", "Left Input Switch", "MIXINL" },
+ { "Right Output Mixer", "Right Input Switch", "MIXINR" },
+ { "Right Output Mixer", "IN2LN Switch", "IN2LN" },
+ { "Right Output Mixer", "IN2RN Switch", "IN2RN" },
+ { "Right Output Mixer", "IN2RP Switch", "IN2RP:VXRP" },
+ { "Right Output Mixer", "IN1L Switch", "IN1L PGA" },
+ { "Right Output Mixer", "IN1R Switch", "IN1R PGA" },
+
+ { "Left Output PGA", NULL, "Left Output Mixer" },
+ { "Left Output PGA", NULL, "TOCLK" },
+
+ { "Right Output PGA", NULL, "Right Output Mixer" },
+ { "Right Output PGA", NULL, "TOCLK" },
+
+ { "Earpiece Mixer", "Direct Voice Switch", "Direct Voice" },
+ { "Earpiece Mixer", "Left Output Switch", "Left Output PGA" },
+ { "Earpiece Mixer", "Right Output Switch", "Right Output PGA" },
+
+ { "Earpiece Driver", NULL, "Earpiece Mixer" },
+ { "HPOUT2N", NULL, "Earpiece Driver" },
+ { "HPOUT2P", NULL, "Earpiece Driver" },
+
+ { "SPKL", "Input Switch", "MIXINL" },
+ { "SPKL", "IN1LP Switch", "IN1LP" },
+ { "SPKL", "Output Switch", "Left Output PGA" },
+ { "SPKL", NULL, "TOCLK" },
+
+ { "SPKR", "Input Switch", "MIXINR" },
+ { "SPKR", "IN1RP Switch", "IN1RP" },
+ { "SPKR", "Output Switch", "Right Output PGA" },
+ { "SPKR", NULL, "TOCLK" },
+
+ { "SPKL Boost", "Direct Voice Switch", "Direct Voice" },
+ { "SPKL Boost", "SPKL Switch", "SPKL" },
+ { "SPKL Boost", "SPKR Switch", "SPKR" },
+
+ { "SPKR Boost", "Direct Voice Switch", "Direct Voice" },
+ { "SPKR Boost", "SPKR Switch", "SPKR" },
+ { "SPKR Boost", "SPKL Switch", "SPKL" },
+
+ { "SPKL Driver", NULL, "SPKL Boost" },
+ { "SPKL Driver", NULL, "CLK_SYS" },
+
+ { "SPKR Driver", NULL, "SPKR Boost" },
+ { "SPKR Driver", NULL, "CLK_SYS" },
+
+ { "SPKOUTLP", NULL, "SPKL Driver" },
+ { "SPKOUTLN", NULL, "SPKL Driver" },
+ { "SPKOUTRP", NULL, "SPKR Driver" },
+ { "SPKOUTRN", NULL, "SPKR Driver" },
+
+ { "Left Headphone Mux", "Mixer", "Left Output PGA" },
+ { "Right Headphone Mux", "Mixer", "Right Output PGA" },
+
+ { "Headphone PGA", NULL, "Left Headphone Mux" },
+ { "Headphone PGA", NULL, "Right Headphone Mux" },
+ { "Headphone PGA", NULL, "CLK_SYS" },
+ { "Headphone PGA", NULL, "Headphone Supply" },
+
+ { "HPOUT1L", NULL, "Headphone PGA" },
+ { "HPOUT1R", NULL, "Headphone PGA" },
+
+ { "LINEOUT1N", NULL, "LINEOUT1N Driver" },
+ { "LINEOUT1P", NULL, "LINEOUT1P Driver" },
+ { "LINEOUT2N", NULL, "LINEOUT2N Driver" },
+ { "LINEOUT2P", NULL, "LINEOUT2P Driver" },
+};
+
+static const struct snd_soc_dapm_route lineout1_diff_routes[] = {
+ { "LINEOUT1 Mixer", "IN1L Switch", "IN1L PGA" },
+ { "LINEOUT1 Mixer", "IN1R Switch", "IN1R PGA" },
+ { "LINEOUT1 Mixer", "Output Switch", "Left Output PGA" },
+
+ { "LINEOUT1N Driver", NULL, "LINEOUT1 Mixer" },
+ { "LINEOUT1P Driver", NULL, "LINEOUT1 Mixer" },
+};
+
+static const struct snd_soc_dapm_route lineout1_se_routes[] = {
+ { "LINEOUT1N Mixer", NULL, "LINEOUT_VMID_BUF" },
+ { "LINEOUT1N Mixer", "Left Output Switch", "Left Output PGA" },
+ { "LINEOUT1N Mixer", "Right Output Switch", "Right Output PGA" },
+
+ { "LINEOUT1P Mixer", NULL, "LINEOUT_VMID_BUF" },
+ { "LINEOUT1P Mixer", "Left Output Switch", "Left Output PGA" },
+
+ { "LINEOUT1N Driver", NULL, "LINEOUT1N Mixer" },
+ { "LINEOUT1P Driver", NULL, "LINEOUT1P Mixer" },
+};
+
+static const struct snd_soc_dapm_route lineout2_diff_routes[] = {
+ { "LINEOUT2 Mixer", "IN1L Switch", "IN1L PGA" },
+ { "LINEOUT2 Mixer", "IN1R Switch", "IN1R PGA" },
+ { "LINEOUT2 Mixer", "Output Switch", "Right Output PGA" },
+
+ { "LINEOUT2N Driver", NULL, "LINEOUT2 Mixer" },
+ { "LINEOUT2P Driver", NULL, "LINEOUT2 Mixer" },
+};
+
+static const struct snd_soc_dapm_route lineout2_se_routes[] = {
+ { "LINEOUT2N Mixer", NULL, "LINEOUT_VMID_BUF" },
+ { "LINEOUT2N Mixer", "Left Output Switch", "Left Output PGA" },
+ { "LINEOUT2N Mixer", "Right Output Switch", "Right Output PGA" },
+
+ { "LINEOUT2P Mixer", NULL, "LINEOUT_VMID_BUF" },
+ { "LINEOUT2P Mixer", "Right Output Switch", "Right Output PGA" },
+
+ { "LINEOUT2N Driver", NULL, "LINEOUT2N Mixer" },
+ { "LINEOUT2P Driver", NULL, "LINEOUT2P Mixer" },
+};
+
+int wm_hubs_add_analogue_controls(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* Latch volume update bits & default ZC on */
+ snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8993_IN1_VU, WM8993_IN1_VU);
+ snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8993_IN1_VU, WM8993_IN1_VU);
+ snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8993_IN2_VU, WM8993_IN2_VU);
+ snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8993_IN2_VU, WM8993_IN2_VU);
+
+ snd_soc_update_bits(codec, WM8993_SPEAKER_VOLUME_LEFT,
+ WM8993_SPKOUT_VU, WM8993_SPKOUT_VU);
+ snd_soc_update_bits(codec, WM8993_SPEAKER_VOLUME_RIGHT,
+ WM8993_SPKOUT_VU, WM8993_SPKOUT_VU);
+
+ snd_soc_update_bits(codec, WM8993_LEFT_OUTPUT_VOLUME,
+ WM8993_HPOUT1_VU | WM8993_HPOUT1L_ZC,
+ WM8993_HPOUT1_VU | WM8993_HPOUT1L_ZC);
+ snd_soc_update_bits(codec, WM8993_RIGHT_OUTPUT_VOLUME,
+ WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC,
+ WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC);
+
+ snd_soc_update_bits(codec, WM8993_LEFT_OPGA_VOLUME,
+ WM8993_MIXOUTL_ZC | WM8993_MIXOUT_VU,
+ WM8993_MIXOUTL_ZC | WM8993_MIXOUT_VU);
+ snd_soc_update_bits(codec, WM8993_RIGHT_OPGA_VOLUME,
+ WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU,
+ WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU);
+
+ snd_soc_add_controls(codec, analogue_snd_controls,
+ ARRAY_SIZE(analogue_snd_controls));
+
+ snd_soc_dapm_new_controls(dapm, analogue_dapm_widgets,
+ ARRAY_SIZE(analogue_dapm_widgets));
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_controls);
+
+int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec,
+ int lineout1_diff, int lineout2_diff)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_add_routes(dapm, analogue_routes,
+ ARRAY_SIZE(analogue_routes));
+
+ if (lineout1_diff)
+ snd_soc_dapm_add_routes(dapm,
+ lineout1_diff_routes,
+ ARRAY_SIZE(lineout1_diff_routes));
+ else
+ snd_soc_dapm_add_routes(dapm,
+ lineout1_se_routes,
+ ARRAY_SIZE(lineout1_se_routes));
+
+ if (lineout2_diff)
+ snd_soc_dapm_add_routes(dapm,
+ lineout2_diff_routes,
+ ARRAY_SIZE(lineout2_diff_routes));
+ else
+ snd_soc_dapm_add_routes(dapm,
+ lineout2_se_routes,
+ ARRAY_SIZE(lineout2_se_routes));
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_routes);
+
+int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *codec,
+ int lineout1_diff, int lineout2_diff,
+ int lineout1fb, int lineout2fb,
+ int jd_scthr, int jd_thr, int micbias1_lvl,
+ int micbias2_lvl)
+{
+ if (!lineout1_diff)
+ snd_soc_update_bits(codec, WM8993_LINE_MIXER1,
+ WM8993_LINEOUT1_MODE,
+ WM8993_LINEOUT1_MODE);
+ if (!lineout2_diff)
+ snd_soc_update_bits(codec, WM8993_LINE_MIXER2,
+ WM8993_LINEOUT2_MODE,
+ WM8993_LINEOUT2_MODE);
+
+ /* If the line outputs are differential then we aren't presenting
+ * VMID as an output and can disable it.
+ */
+ if (lineout1_diff && lineout2_diff)
+ codec->dapm.idle_bias_off = 1;
+
+ if (lineout1fb)
+ snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL,
+ WM8993_LINEOUT1_FB, WM8993_LINEOUT1_FB);
+
+ if (lineout2fb)
+ snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL,
+ WM8993_LINEOUT2_FB, WM8993_LINEOUT2_FB);
+
+ snd_soc_update_bits(codec, WM8993_MICBIAS,
+ WM8993_JD_SCTHR_MASK | WM8993_JD_THR_MASK |
+ WM8993_MICB1_LVL | WM8993_MICB2_LVL,
+ jd_scthr << WM8993_JD_SCTHR_SHIFT |
+ jd_thr << WM8993_JD_THR_SHIFT |
+ micbias1_lvl |
+ micbias2_lvl << WM8993_MICB2_LVL_SHIFT);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm_hubs_handle_analogue_pdata);
+
+MODULE_DESCRIPTION("Shared support for Wolfson hubs products");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h
new file mode 100644
index 00000000..f8a5e976
--- /dev/null
+++ b/sound/soc/codecs/wm_hubs.h
@@ -0,0 +1,39 @@
+/*
+ * wm_hubs.h -- WM899x common code
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM_HUBS_H
+#define _WM_HUBS_H
+
+struct snd_soc_codec;
+
+extern const unsigned int wm_hubs_spkmix_tlv[];
+
+/* This *must* be the first element of the codec->private_data struct */
+struct wm_hubs_data {
+ int dcs_codes;
+ int dcs_readback_mode;
+ int hp_startup_mode;
+
+ bool class_w;
+ u16 class_w_dcs;
+};
+
+extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *);
+extern int wm_hubs_add_analogue_routes(struct snd_soc_codec *, int, int);
+extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *,
+ int lineout1_diff, int lineout2_diff,
+ int lineout1fb, int lineout2fb,
+ int jd_scthr, int jd_thr,
+ int micbias1_lvl, int micbias2_lvl);
+
+#endif
diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig
new file mode 100644
index 00000000..6bbf001f
--- /dev/null
+++ b/sound/soc/davinci/Kconfig
@@ -0,0 +1,86 @@
+config SND_DAVINCI_SOC
+ tristate "SoC Audio for the TI DAVINCI chip"
+ depends on ARCH_DAVINCI
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the DAVINCI AC97 or I2S interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_DAVINCI_SOC_I2S
+ tristate
+
+config SND_DAVINCI_SOC_MCASP
+ tristate
+
+config SND_DAVINCI_SOC_VCIF
+ tristate
+
+config SND_DAVINCI_SOC_EVM
+ tristate "SoC Audio support for DaVinci DM6446, DM355 or DM365 EVM"
+ depends on SND_DAVINCI_SOC
+ depends on MACH_DAVINCI_EVM || MACH_DAVINCI_DM355_EVM || MACH_DAVINCI_DM365_EVM
+ select SND_DAVINCI_SOC_I2S
+ select SND_SOC_TLV320AIC3X
+ help
+ Say Y if you want to add support for SoC audio on TI
+ DaVinci DM6446, DM355 or DM365 EVM platforms.
+
+choice
+ prompt "DM365 codec select"
+ depends on SND_DAVINCI_SOC_EVM
+ depends on MACH_DAVINCI_DM365_EVM
+ default SND_DM365_EXTERNAL_CODEC
+
+config SND_DM365_AIC3X_CODEC
+ bool "Audio Codec - AIC3101"
+ help
+ Say Y if you want to add support for AIC3101 audio codec
+
+config SND_DM365_VOICE_CODEC
+ bool "Voice Codec - CQ93VC"
+ select MFD_DAVINCI_VOICECODEC
+ select SND_DAVINCI_SOC_VCIF
+ select SND_SOC_CQ0093VC
+ help
+ Say Y if you want to add support for SoC On-chip voice codec
+endchoice
+
+config SND_DM6467_SOC_EVM
+ tristate "SoC Audio support for DaVinci DM6467 EVM"
+ depends on SND_DAVINCI_SOC && MACH_DAVINCI_DM6467_EVM
+ select SND_DAVINCI_SOC_MCASP
+ select SND_SOC_TLV320AIC3X
+ select SND_SOC_SPDIF
+
+ help
+ Say Y if you want to add support for SoC audio on TI
+
+config SND_DAVINCI_SOC_SFFSDR
+ tristate "SoC Audio support for SFFSDR"
+ depends on SND_DAVINCI_SOC && MACH_SFFSDR
+ select SND_DAVINCI_SOC_I2S
+ select SND_SOC_PCM3008
+ select SFFSDR_FPGA
+ help
+ Say Y if you want to add support for SoC audio on
+ Lyrtech SFFSDR board.
+
+config SND_DA830_SOC_EVM
+ tristate "SoC Audio support for DA830/OMAP-L137 EVM"
+ depends on SND_DAVINCI_SOC && MACH_DAVINCI_DA830_EVM
+ select SND_DAVINCI_SOC_MCASP
+ select SND_SOC_TLV320AIC3X
+
+ help
+ Say Y if you want to add support for SoC audio on TI
+ DA830/OMAP-L137 EVM
+
+config SND_DA850_SOC_EVM
+ tristate "SoC Audio support for DA850/OMAP-L138 EVM"
+ depends on SND_DAVINCI_SOC && MACH_DAVINCI_DA850_EVM
+ select SND_DAVINCI_SOC_MCASP
+ select SND_SOC_TLV320AIC3X
+ help
+ Say Y if you want to add support for SoC audio on TI
+ DA850/OMAP-L138 EVM
+
diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile
new file mode 100644
index 00000000..a93679d6
--- /dev/null
+++ b/sound/soc/davinci/Makefile
@@ -0,0 +1,20 @@
+# DAVINCI Platform Support
+snd-soc-davinci-objs := davinci-pcm.o
+snd-soc-davinci-i2s-objs := davinci-i2s.o
+snd-soc-davinci-mcasp-objs:= davinci-mcasp.o
+snd-soc-davinci-vcif-objs:= davinci-vcif.o
+
+obj-$(CONFIG_SND_DAVINCI_SOC) += snd-soc-davinci.o
+obj-$(CONFIG_SND_DAVINCI_SOC_I2S) += snd-soc-davinci-i2s.o
+obj-$(CONFIG_SND_DAVINCI_SOC_MCASP) += snd-soc-davinci-mcasp.o
+obj-$(CONFIG_SND_DAVINCI_SOC_VCIF) += snd-soc-davinci-vcif.o
+
+# DAVINCI Machine Support
+snd-soc-evm-objs := davinci-evm.o
+snd-soc-sffsdr-objs := davinci-sffsdr.o
+
+obj-$(CONFIG_SND_DAVINCI_SOC_EVM) += snd-soc-evm.o
+obj-$(CONFIG_SND_DM6467_SOC_EVM) += snd-soc-evm.o
+obj-$(CONFIG_SND_DA830_SOC_EVM) += snd-soc-evm.o
+obj-$(CONFIG_SND_DA850_SOC_EVM) += snd-soc-evm.o
+obj-$(CONFIG_SND_DAVINCI_SOC_SFFSDR) += snd-soc-sffsdr.o
diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c
new file mode 100644
index 00000000..fe798422
--- /dev/null
+++ b/sound/soc/davinci/davinci-evm.c
@@ -0,0 +1,335 @@
+/*
+ * ASoC driver for TI DAVINCI EVM platform
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <asm/mach-types.h>
+
+#include <mach/asp.h>
+#include <mach/edma.h>
+#include <mach/mux.h>
+
+#include "davinci-pcm.h"
+#include "davinci-i2s.h"
+#include "davinci-mcasp.h"
+
+#define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | \
+ SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF)
+static int evm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret = 0;
+ unsigned sysclk;
+
+ /* ASP1 on DM355 EVM is clocked by an external oscillator */
+ if (machine_is_davinci_dm355_evm() || machine_is_davinci_dm6467_evm() ||
+ machine_is_davinci_dm365_evm())
+ sysclk = 27000000;
+
+ /* ASP0 in DM6446 EVM is clocked by U55, as configured by
+ * board-dm644x-evm.c using GPIOs from U18. There are six
+ * options; here we "know" we use a 48 KHz sample rate.
+ */
+ else if (machine_is_davinci_evm())
+ sysclk = 12288000;
+
+ else if (machine_is_davinci_da830_evm() ||
+ machine_is_davinci_da850_evm())
+ sysclk = 24576000;
+
+ else
+ return -EINVAL;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, AUDIO_FORMAT);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, sysclk, SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int evm_spdif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ /* set cpu DAI configuration */
+ return snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT);
+}
+
+static struct snd_soc_ops evm_ops = {
+ .hw_params = evm_hw_params,
+};
+
+static struct snd_soc_ops evm_spdif_ops = {
+ .hw_params = evm_spdif_hw_params,
+};
+
+/* davinci-evm machine dapm widgets */
+static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+/* davinci-evm machine audio_mapnections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Headphone connected to HPLOUT, HPROUT */
+ {"Headphone Jack", NULL, "HPLOUT"},
+ {"Headphone Jack", NULL, "HPROUT"},
+
+ /* Line Out connected to LLOUT, RLOUT */
+ {"Line Out", NULL, "LLOUT"},
+ {"Line Out", NULL, "RLOUT"},
+
+ /* Mic connected to (MIC3L | MIC3R) */
+ {"MIC3L", NULL, "Mic Bias 2V"},
+ {"MIC3R", NULL, "Mic Bias 2V"},
+ {"Mic Bias 2V", NULL, "Mic Jack"},
+
+ /* Line In connected to (LINE1L | LINE2L), (LINE1R | LINE2R) */
+ {"LINE1L", NULL, "Line In"},
+ {"LINE2L", NULL, "Line In"},
+ {"LINE1R", NULL, "Line In"},
+ {"LINE2R", NULL, "Line In"},
+};
+
+/* Logic for a aic3x as connected on a davinci-evm */
+static int evm_aic3x_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* Add davinci-evm specific widgets */
+ snd_soc_dapm_new_controls(dapm, aic3x_dapm_widgets,
+ ARRAY_SIZE(aic3x_dapm_widgets));
+
+ /* Set up davinci-evm specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ /* not connected */
+ snd_soc_dapm_disable_pin(dapm, "MONO_LOUT");
+ snd_soc_dapm_disable_pin(dapm, "HPLCOM");
+ snd_soc_dapm_disable_pin(dapm, "HPRCOM");
+
+ /* always connected */
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Line Out");
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_enable_pin(dapm, "Line In");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+/* davinci-evm digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link dm6446_evm_dai = {
+ .name = "TLV320AIC3X",
+ .stream_name = "AIC3X",
+ .cpu_dai_name = "davinci-mcbsp",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .codec_name = "tlv320aic3x-codec.1-001b",
+ .platform_name = "davinci-pcm-audio",
+ .init = evm_aic3x_init,
+ .ops = &evm_ops,
+};
+
+static struct snd_soc_dai_link dm355_evm_dai = {
+ .name = "TLV320AIC3X",
+ .stream_name = "AIC3X",
+ .cpu_dai_name = "davinci-mcbsp.1",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .codec_name = "tlv320aic3x-codec.1-001b",
+ .platform_name = "davinci-pcm-audio",
+ .init = evm_aic3x_init,
+ .ops = &evm_ops,
+};
+
+static struct snd_soc_dai_link dm365_evm_dai = {
+#ifdef CONFIG_SND_DM365_AIC3X_CODEC
+ .name = "TLV320AIC3X",
+ .stream_name = "AIC3X",
+ .cpu_dai_name = "davinci-mcbsp",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .init = evm_aic3x_init,
+ .codec_name = "tlv320aic3x-codec.1-0018",
+ .ops = &evm_ops,
+#elif defined(CONFIG_SND_DM365_VOICE_CODEC)
+ .name = "Voice Codec - CQ93VC",
+ .stream_name = "CQ93",
+ .cpu_dai_name = "davinci-vcif",
+ .codec_dai_name = "cq93vc-hifi",
+ .codec_name = "cq93vc-codec",
+#endif
+ .platform_name = "davinci-pcm-audio",
+};
+
+static struct snd_soc_dai_link dm6467_evm_dai[] = {
+ {
+ .name = "TLV320AIC3X",
+ .stream_name = "AIC3X",
+ .cpu_dai_name= "davinci-mcasp.0",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .platform_name ="davinci-pcm-audio",
+ .codec_name = "tlv320aic3x-codec.0-001a",
+ .init = evm_aic3x_init,
+ .ops = &evm_ops,
+ },
+ {
+ .name = "McASP",
+ .stream_name = "spdif",
+ .cpu_dai_name= "davinci-mcasp.1",
+ .codec_dai_name = "dit-hifi",
+ .codec_name = "spdif_dit",
+ .platform_name = "davinci-pcm-audio",
+ .ops = &evm_spdif_ops,
+ },
+};
+
+static struct snd_soc_dai_link da830_evm_dai = {
+ .name = "TLV320AIC3X",
+ .stream_name = "AIC3X",
+ .cpu_dai_name = "davinci-mcasp.1",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .codec_name = "tlv320aic3x-codec.1-0018",
+ .platform_name = "davinci-pcm-audio",
+ .init = evm_aic3x_init,
+ .ops = &evm_ops,
+};
+
+static struct snd_soc_dai_link da850_evm_dai = {
+ .name = "TLV320AIC3X",
+ .stream_name = "AIC3X",
+ .cpu_dai_name= "davinci-mcasp.0",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .codec_name = "tlv320aic3x-codec.1-0018",
+ .platform_name = "davinci-pcm-audio",
+ .init = evm_aic3x_init,
+ .ops = &evm_ops,
+};
+
+/* davinci dm6446 evm audio machine driver */
+static struct snd_soc_card dm6446_snd_soc_card_evm = {
+ .name = "DaVinci DM6446 EVM",
+ .dai_link = &dm6446_evm_dai,
+ .num_links = 1,
+};
+
+/* davinci dm355 evm audio machine driver */
+static struct snd_soc_card dm355_snd_soc_card_evm = {
+ .name = "DaVinci DM355 EVM",
+ .dai_link = &dm355_evm_dai,
+ .num_links = 1,
+};
+
+/* davinci dm365 evm audio machine driver */
+static struct snd_soc_card dm365_snd_soc_card_evm = {
+ .name = "DaVinci DM365 EVM",
+ .dai_link = &dm365_evm_dai,
+ .num_links = 1,
+};
+
+/* davinci dm6467 evm audio machine driver */
+static struct snd_soc_card dm6467_snd_soc_card_evm = {
+ .name = "DaVinci DM6467 EVM",
+ .dai_link = dm6467_evm_dai,
+ .num_links = ARRAY_SIZE(dm6467_evm_dai),
+};
+
+static struct snd_soc_card da830_snd_soc_card = {
+ .name = "DA830/OMAP-L137 EVM",
+ .dai_link = &da830_evm_dai,
+ .num_links = 1,
+};
+
+static struct snd_soc_card da850_snd_soc_card = {
+ .name = "DA850/OMAP-L138 EVM",
+ .dai_link = &da850_evm_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *evm_snd_device;
+
+static int __init evm_init(void)
+{
+ struct snd_soc_card *evm_snd_dev_data;
+ int index;
+ int ret;
+
+ if (machine_is_davinci_evm()) {
+ evm_snd_dev_data = &dm6446_snd_soc_card_evm;
+ index = 0;
+ } else if (machine_is_davinci_dm355_evm()) {
+ evm_snd_dev_data = &dm355_snd_soc_card_evm;
+ index = 1;
+ } else if (machine_is_davinci_dm365_evm()) {
+ evm_snd_dev_data = &dm365_snd_soc_card_evm;
+ index = 0;
+ } else if (machine_is_davinci_dm6467_evm()) {
+ evm_snd_dev_data = &dm6467_snd_soc_card_evm;
+ index = 0;
+ } else if (machine_is_davinci_da830_evm()) {
+ evm_snd_dev_data = &da830_snd_soc_card;
+ index = 1;
+ } else if (machine_is_davinci_da850_evm()) {
+ evm_snd_dev_data = &da850_snd_soc_card;
+ index = 0;
+ } else
+ return -EINVAL;
+
+ evm_snd_device = platform_device_alloc("soc-audio", index);
+ if (!evm_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(evm_snd_device, evm_snd_dev_data);
+ ret = platform_device_add(evm_snd_device);
+ if (ret)
+ platform_device_put(evm_snd_device);
+
+ return ret;
+}
+
+static void __exit evm_exit(void)
+{
+ platform_device_unregister(evm_snd_device);
+}
+
+module_init(evm_init);
+module_exit(evm_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("TI DAVINCI EVM ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c
new file mode 100644
index 00000000..d0d60b8a
--- /dev/null
+++ b/sound/soc/davinci/davinci-i2s.c
@@ -0,0 +1,788 @@
+/*
+ * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/asp.h>
+
+#include "davinci-pcm.h"
+#include "davinci-i2s.h"
+
+
+/*
+ * NOTE: terminology here is confusing.
+ *
+ * - This driver supports the "Audio Serial Port" (ASP),
+ * found on dm6446, dm355, and other DaVinci chips.
+ *
+ * - But it labels it a "Multi-channel Buffered Serial Port"
+ * (McBSP) as on older chips like the dm642 ... which was
+ * backward-compatible, possibly explaining that confusion.
+ *
+ * - OMAP chips have a controller called McBSP, which is
+ * incompatible with the DaVinci flavor of McBSP.
+ *
+ * - Newer DaVinci chips have a controller called McASP,
+ * incompatible with ASP and with either McBSP.
+ *
+ * In short: this uses ASP to implement I2S, not McBSP.
+ * And it won't be the only DaVinci implemention of I2S.
+ */
+#define DAVINCI_MCBSP_DRR_REG 0x00
+#define DAVINCI_MCBSP_DXR_REG 0x04
+#define DAVINCI_MCBSP_SPCR_REG 0x08
+#define DAVINCI_MCBSP_RCR_REG 0x0c
+#define DAVINCI_MCBSP_XCR_REG 0x10
+#define DAVINCI_MCBSP_SRGR_REG 0x14
+#define DAVINCI_MCBSP_PCR_REG 0x24
+
+#define DAVINCI_MCBSP_SPCR_RRST (1 << 0)
+#define DAVINCI_MCBSP_SPCR_RINTM(v) ((v) << 4)
+#define DAVINCI_MCBSP_SPCR_XRST (1 << 16)
+#define DAVINCI_MCBSP_SPCR_XINTM(v) ((v) << 20)
+#define DAVINCI_MCBSP_SPCR_GRST (1 << 22)
+#define DAVINCI_MCBSP_SPCR_FRST (1 << 23)
+#define DAVINCI_MCBSP_SPCR_FREE (1 << 25)
+
+#define DAVINCI_MCBSP_RCR_RWDLEN1(v) ((v) << 5)
+#define DAVINCI_MCBSP_RCR_RFRLEN1(v) ((v) << 8)
+#define DAVINCI_MCBSP_RCR_RDATDLY(v) ((v) << 16)
+#define DAVINCI_MCBSP_RCR_RFIG (1 << 18)
+#define DAVINCI_MCBSP_RCR_RWDLEN2(v) ((v) << 21)
+#define DAVINCI_MCBSP_RCR_RFRLEN2(v) ((v) << 24)
+#define DAVINCI_MCBSP_RCR_RPHASE BIT(31)
+
+#define DAVINCI_MCBSP_XCR_XWDLEN1(v) ((v) << 5)
+#define DAVINCI_MCBSP_XCR_XFRLEN1(v) ((v) << 8)
+#define DAVINCI_MCBSP_XCR_XDATDLY(v) ((v) << 16)
+#define DAVINCI_MCBSP_XCR_XFIG (1 << 18)
+#define DAVINCI_MCBSP_XCR_XWDLEN2(v) ((v) << 21)
+#define DAVINCI_MCBSP_XCR_XFRLEN2(v) ((v) << 24)
+#define DAVINCI_MCBSP_XCR_XPHASE BIT(31)
+
+#define DAVINCI_MCBSP_SRGR_FWID(v) ((v) << 8)
+#define DAVINCI_MCBSP_SRGR_FPER(v) ((v) << 16)
+#define DAVINCI_MCBSP_SRGR_FSGM (1 << 28)
+#define DAVINCI_MCBSP_SRGR_CLKSM BIT(29)
+
+#define DAVINCI_MCBSP_PCR_CLKRP (1 << 0)
+#define DAVINCI_MCBSP_PCR_CLKXP (1 << 1)
+#define DAVINCI_MCBSP_PCR_FSRP (1 << 2)
+#define DAVINCI_MCBSP_PCR_FSXP (1 << 3)
+#define DAVINCI_MCBSP_PCR_SCLKME (1 << 7)
+#define DAVINCI_MCBSP_PCR_CLKRM (1 << 8)
+#define DAVINCI_MCBSP_PCR_CLKXM (1 << 9)
+#define DAVINCI_MCBSP_PCR_FSRM (1 << 10)
+#define DAVINCI_MCBSP_PCR_FSXM (1 << 11)
+
+enum {
+ DAVINCI_MCBSP_WORD_8 = 0,
+ DAVINCI_MCBSP_WORD_12,
+ DAVINCI_MCBSP_WORD_16,
+ DAVINCI_MCBSP_WORD_20,
+ DAVINCI_MCBSP_WORD_24,
+ DAVINCI_MCBSP_WORD_32,
+};
+
+static const unsigned char data_type[SNDRV_PCM_FORMAT_S32_LE + 1] = {
+ [SNDRV_PCM_FORMAT_S8] = 1,
+ [SNDRV_PCM_FORMAT_S16_LE] = 2,
+ [SNDRV_PCM_FORMAT_S32_LE] = 4,
+};
+
+static const unsigned char asp_word_length[SNDRV_PCM_FORMAT_S32_LE + 1] = {
+ [SNDRV_PCM_FORMAT_S8] = DAVINCI_MCBSP_WORD_8,
+ [SNDRV_PCM_FORMAT_S16_LE] = DAVINCI_MCBSP_WORD_16,
+ [SNDRV_PCM_FORMAT_S32_LE] = DAVINCI_MCBSP_WORD_32,
+};
+
+static const unsigned char double_fmt[SNDRV_PCM_FORMAT_S32_LE + 1] = {
+ [SNDRV_PCM_FORMAT_S8] = SNDRV_PCM_FORMAT_S16_LE,
+ [SNDRV_PCM_FORMAT_S16_LE] = SNDRV_PCM_FORMAT_S32_LE,
+};
+
+struct davinci_mcbsp_dev {
+ struct device *dev;
+ struct davinci_pcm_dma_params dma_params[2];
+ void __iomem *base;
+#define MOD_DSP_A 0
+#define MOD_DSP_B 1
+ int mode;
+ u32 pcr;
+ struct clk *clk;
+ /*
+ * Combining both channels into 1 element will at least double the
+ * amount of time between servicing the dma channel, increase
+ * effiency, and reduce the chance of overrun/underrun. But,
+ * it will result in the left & right channels being swapped.
+ *
+ * If relabeling the left and right channels is not possible,
+ * you may want to let the codec know to swap them back.
+ *
+ * It may allow x10 the amount of time to service dma requests,
+ * if the codec is master and is using an unnecessarily fast bit clock
+ * (ie. tlvaic23b), independent of the sample rate. So, having an
+ * entire frame at once means it can be serviced at the sample rate
+ * instead of the bit clock rate.
+ *
+ * In the now unlikely case that an underrun still
+ * occurs, both the left and right samples will be repeated
+ * so that no pops are heard, and the left and right channels
+ * won't end up being swapped because of the underrun.
+ */
+ unsigned enable_channel_combine:1;
+
+ unsigned int fmt;
+ int clk_div;
+ int clk_input_pin;
+ bool i2s_accurate_sck;
+};
+
+static inline void davinci_mcbsp_write_reg(struct davinci_mcbsp_dev *dev,
+ int reg, u32 val)
+{
+ __raw_writel(val, dev->base + reg);
+}
+
+static inline u32 davinci_mcbsp_read_reg(struct davinci_mcbsp_dev *dev, int reg)
+{
+ return __raw_readl(dev->base + reg);
+}
+
+static void toggle_clock(struct davinci_mcbsp_dev *dev, int playback)
+{
+ u32 m = playback ? DAVINCI_MCBSP_PCR_CLKXP : DAVINCI_MCBSP_PCR_CLKRP;
+ /* The clock needs to toggle to complete reset.
+ * So, fake it by toggling the clk polarity.
+ */
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr ^ m);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr);
+}
+
+static void davinci_mcbsp_start(struct davinci_mcbsp_dev *dev,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_platform *platform = rtd->platform;
+ int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+ u32 spcr;
+ u32 mask = playback ? DAVINCI_MCBSP_SPCR_XRST : DAVINCI_MCBSP_SPCR_RRST;
+ spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+ if (spcr & mask) {
+ /* start off disabled */
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG,
+ spcr & ~mask);
+ toggle_clock(dev, playback);
+ }
+ if (dev->pcr & (DAVINCI_MCBSP_PCR_FSXM | DAVINCI_MCBSP_PCR_FSRM |
+ DAVINCI_MCBSP_PCR_CLKXM | DAVINCI_MCBSP_PCR_CLKRM)) {
+ /* Start the sample generator */
+ spcr |= DAVINCI_MCBSP_SPCR_GRST;
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+ }
+
+ if (playback) {
+ /* Stop the DMA to avoid data loss */
+ /* while the transmitter is out of reset to handle XSYNCERR */
+ if (platform->driver->ops->trigger) {
+ int ret = platform->driver->ops->trigger(substream,
+ SNDRV_PCM_TRIGGER_STOP);
+ if (ret < 0)
+ printk(KERN_DEBUG "Playback DMA stop failed\n");
+ }
+
+ /* Enable the transmitter */
+ spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+ spcr |= DAVINCI_MCBSP_SPCR_XRST;
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+
+ /* wait for any unexpected frame sync error to occur */
+ udelay(100);
+
+ /* Disable the transmitter to clear any outstanding XSYNCERR */
+ spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+ spcr &= ~DAVINCI_MCBSP_SPCR_XRST;
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+ toggle_clock(dev, playback);
+
+ /* Restart the DMA */
+ if (platform->driver->ops->trigger) {
+ int ret = platform->driver->ops->trigger(substream,
+ SNDRV_PCM_TRIGGER_START);
+ if (ret < 0)
+ printk(KERN_DEBUG "Playback DMA start failed\n");
+ }
+ }
+
+ /* Enable transmitter or receiver */
+ spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+ spcr |= mask;
+
+ if (dev->pcr & (DAVINCI_MCBSP_PCR_FSXM | DAVINCI_MCBSP_PCR_FSRM)) {
+ /* Start frame sync */
+ spcr |= DAVINCI_MCBSP_SPCR_FRST;
+ }
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+}
+
+static void davinci_mcbsp_stop(struct davinci_mcbsp_dev *dev, int playback)
+{
+ u32 spcr;
+
+ /* Reset transmitter/receiver and sample rate/frame sync generators */
+ spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+ spcr &= ~(DAVINCI_MCBSP_SPCR_GRST | DAVINCI_MCBSP_SPCR_FRST);
+ spcr &= playback ? ~DAVINCI_MCBSP_SPCR_XRST : ~DAVINCI_MCBSP_SPCR_RRST;
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+ toggle_clock(dev, playback);
+}
+
+#define DEFAULT_BITPERSAMPLE 16
+
+static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned int pcr;
+ unsigned int srgr;
+ /* Attention srgr is updated by hw_params! */
+ srgr = DAVINCI_MCBSP_SRGR_FSGM |
+ DAVINCI_MCBSP_SRGR_FPER(DEFAULT_BITPERSAMPLE * 2 - 1) |
+ DAVINCI_MCBSP_SRGR_FWID(DEFAULT_BITPERSAMPLE - 1);
+
+ dev->fmt = fmt;
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* cpu is master */
+ pcr = DAVINCI_MCBSP_PCR_FSXM |
+ DAVINCI_MCBSP_PCR_FSRM |
+ DAVINCI_MCBSP_PCR_CLKXM |
+ DAVINCI_MCBSP_PCR_CLKRM;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ pcr = DAVINCI_MCBSP_PCR_FSRM | DAVINCI_MCBSP_PCR_FSXM;
+ /*
+ * Selection of the clock input pin that is the
+ * input for the Sample Rate Generator.
+ * McBSP FSR and FSX are driven by the Sample Rate
+ * Generator.
+ */
+ switch (dev->clk_input_pin) {
+ case MCBSP_CLKS:
+ pcr |= DAVINCI_MCBSP_PCR_CLKXM |
+ DAVINCI_MCBSP_PCR_CLKRM;
+ break;
+ case MCBSP_CLKR:
+ pcr |= DAVINCI_MCBSP_PCR_SCLKME;
+ break;
+ default:
+ dev_err(dev->dev, "bad clk_input_pin\n");
+ return -EINVAL;
+ }
+
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* codec is master */
+ pcr = 0;
+ break;
+ default:
+ printk(KERN_ERR "%s:bad master\n", __func__);
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* Davinci doesn't support TRUE I2S, but some codecs will have
+ * the left and right channels contiguous. This allows
+ * dsp_a mode to be used with an inverted normal frame clk.
+ * If your codec is master and does not have contiguous
+ * channels, then you will have sound on only one channel.
+ * Try using a different mode, or codec as slave.
+ *
+ * The TLV320AIC33 is an example of a codec where this works.
+ * It has a variable bit clock frequency allowing it to have
+ * valid data on every bit clock.
+ *
+ * The TLV320AIC23 is an example of a codec where this does not
+ * work. It has a fixed bit clock frequency with progressively
+ * more empty bit clock slots between channels as the sample
+ * rate is lowered.
+ */
+ fmt ^= SND_SOC_DAIFMT_NB_IF;
+ case SND_SOC_DAIFMT_DSP_A:
+ dev->mode = MOD_DSP_A;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ dev->mode = MOD_DSP_B;
+ break;
+ default:
+ printk(KERN_ERR "%s:bad format\n", __func__);
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ /* CLKRP Receive clock polarity,
+ * 1 - sampled on rising edge of CLKR
+ * valid on rising edge
+ * CLKXP Transmit clock polarity,
+ * 1 - clocked on falling edge of CLKX
+ * valid on rising edge
+ * FSRP Receive frame sync pol, 0 - active high
+ * FSXP Transmit frame sync pol, 0 - active high
+ */
+ pcr |= (DAVINCI_MCBSP_PCR_CLKXP | DAVINCI_MCBSP_PCR_CLKRP);
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ /* CLKRP Receive clock polarity,
+ * 0 - sampled on falling edge of CLKR
+ * valid on falling edge
+ * CLKXP Transmit clock polarity,
+ * 0 - clocked on rising edge of CLKX
+ * valid on falling edge
+ * FSRP Receive frame sync pol, 1 - active low
+ * FSXP Transmit frame sync pol, 1 - active low
+ */
+ pcr |= (DAVINCI_MCBSP_PCR_FSXP | DAVINCI_MCBSP_PCR_FSRP);
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ /* CLKRP Receive clock polarity,
+ * 1 - sampled on rising edge of CLKR
+ * valid on rising edge
+ * CLKXP Transmit clock polarity,
+ * 1 - clocked on falling edge of CLKX
+ * valid on rising edge
+ * FSRP Receive frame sync pol, 1 - active low
+ * FSXP Transmit frame sync pol, 1 - active low
+ */
+ pcr |= (DAVINCI_MCBSP_PCR_CLKXP | DAVINCI_MCBSP_PCR_CLKRP |
+ DAVINCI_MCBSP_PCR_FSXP | DAVINCI_MCBSP_PCR_FSRP);
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ /* CLKRP Receive clock polarity,
+ * 0 - sampled on falling edge of CLKR
+ * valid on falling edge
+ * CLKXP Transmit clock polarity,
+ * 0 - clocked on rising edge of CLKX
+ * valid on falling edge
+ * FSRP Receive frame sync pol, 0 - active high
+ * FSXP Transmit frame sync pol, 0 - active high
+ */
+ break;
+ default:
+ return -EINVAL;
+ }
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
+ dev->pcr = pcr;
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, pcr);
+ return 0;
+}
+
+static int davinci_i2s_dai_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+
+ if (div_id != DAVINCI_MCBSP_CLKGDV)
+ return -ENODEV;
+
+ dev->clk_div = div;
+ return 0;
+}
+
+static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct davinci_pcm_dma_params *dma_params =
+ &dev->dma_params[substream->stream];
+ struct snd_interval *i = NULL;
+ int mcbsp_word_length, master;
+ unsigned int rcr, xcr, srgr, clk_div, freq, framesize;
+ u32 spcr;
+ snd_pcm_format_t fmt;
+ unsigned element_cnt = 1;
+
+ /* general line settings */
+ spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ spcr |= DAVINCI_MCBSP_SPCR_RINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+ } else {
+ spcr |= DAVINCI_MCBSP_SPCR_XINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+ }
+
+ master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+ fmt = params_format(params);
+ mcbsp_word_length = asp_word_length[fmt];
+
+ switch (master) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ freq = clk_get_rate(dev->clk);
+ srgr = DAVINCI_MCBSP_SRGR_FSGM |
+ DAVINCI_MCBSP_SRGR_CLKSM;
+ srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length *
+ 8 - 1);
+ if (dev->i2s_accurate_sck) {
+ clk_div = 256;
+ do {
+ framesize = (freq / (--clk_div)) /
+ params->rate_num *
+ params->rate_den;
+ } while (((framesize < 33) || (framesize > 4095)) &&
+ (clk_div));
+ clk_div--;
+ srgr |= DAVINCI_MCBSP_SRGR_FPER(framesize - 1);
+ } else {
+ /* symmetric waveforms */
+ clk_div = freq / (mcbsp_word_length * 16) /
+ params->rate_num * params->rate_den;
+ srgr |= DAVINCI_MCBSP_SRGR_FPER(mcbsp_word_length *
+ 16 - 1);
+ }
+ clk_div &= 0xFF;
+ srgr |= clk_div;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ srgr = DAVINCI_MCBSP_SRGR_FSGM;
+ clk_div = dev->clk_div - 1;
+ srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length * 8 - 1);
+ srgr |= DAVINCI_MCBSP_SRGR_FPER(mcbsp_word_length * 16 - 1);
+ clk_div &= 0xFF;
+ srgr |= clk_div;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* Clock and frame sync given from external sources */
+ i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
+ srgr = DAVINCI_MCBSP_SRGR_FSGM;
+ srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1);
+ pr_debug("%s - %d FWID set: re-read srgr = %X\n",
+ __func__, __LINE__, snd_interval_value(i) - 1);
+
+ i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS);
+ srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
+
+ rcr = DAVINCI_MCBSP_RCR_RFIG;
+ xcr = DAVINCI_MCBSP_XCR_XFIG;
+ if (dev->mode == MOD_DSP_B) {
+ rcr |= DAVINCI_MCBSP_RCR_RDATDLY(0);
+ xcr |= DAVINCI_MCBSP_XCR_XDATDLY(0);
+ } else {
+ rcr |= DAVINCI_MCBSP_RCR_RDATDLY(1);
+ xcr |= DAVINCI_MCBSP_XCR_XDATDLY(1);
+ }
+ /* Determine xfer data type */
+ fmt = params_format(params);
+ if ((fmt > SNDRV_PCM_FORMAT_S32_LE) || !data_type[fmt]) {
+ printk(KERN_WARNING "davinci-i2s: unsupported PCM format\n");
+ return -EINVAL;
+ }
+
+ if (params_channels(params) == 2) {
+ element_cnt = 2;
+ if (double_fmt[fmt] && dev->enable_channel_combine) {
+ element_cnt = 1;
+ fmt = double_fmt[fmt];
+ }
+ switch (master) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ rcr |= DAVINCI_MCBSP_RCR_RFRLEN2(0);
+ xcr |= DAVINCI_MCBSP_XCR_XFRLEN2(0);
+ rcr |= DAVINCI_MCBSP_RCR_RPHASE;
+ xcr |= DAVINCI_MCBSP_XCR_XPHASE;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ rcr |= DAVINCI_MCBSP_RCR_RFRLEN2(element_cnt - 1);
+ xcr |= DAVINCI_MCBSP_XCR_XFRLEN2(element_cnt - 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ dma_params->acnt = dma_params->data_type = data_type[fmt];
+ dma_params->fifo_level = 0;
+ mcbsp_word_length = asp_word_length[fmt];
+
+ switch (master) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(0);
+ xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(0);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(element_cnt - 1);
+ xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(element_cnt - 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) |
+ DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length);
+ xcr |= DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) |
+ DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr);
+ else
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr);
+
+ pr_debug("%s - %d srgr=%X\n", __func__, __LINE__, srgr);
+ pr_debug("%s - %d xcr=%X\n", __func__, __LINE__, xcr);
+ pr_debug("%s - %d rcr=%X\n", __func__, __LINE__, rcr);
+ return 0;
+}
+
+static int davinci_i2s_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
+ int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+ davinci_mcbsp_stop(dev, playback);
+ return 0;
+}
+
+static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
+ int ret = 0;
+ int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ davinci_mcbsp_start(dev, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ davinci_mcbsp_stop(dev, playback);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static int davinci_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_set_dma_data(dai, substream, dev->dma_params);
+ return 0;
+}
+
+static void davinci_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
+ int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+ davinci_mcbsp_stop(dev, playback);
+}
+
+#define DAVINCI_I2S_RATES SNDRV_PCM_RATE_8000_96000
+
+static struct snd_soc_dai_ops davinci_i2s_dai_ops = {
+ .startup = davinci_i2s_startup,
+ .shutdown = davinci_i2s_shutdown,
+ .prepare = davinci_i2s_prepare,
+ .trigger = davinci_i2s_trigger,
+ .hw_params = davinci_i2s_hw_params,
+ .set_fmt = davinci_i2s_set_dai_fmt,
+ .set_clkdiv = davinci_i2s_dai_set_clkdiv,
+
+};
+
+static struct snd_soc_dai_driver davinci_i2s_dai = {
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = DAVINCI_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = DAVINCI_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &davinci_i2s_dai_ops,
+
+};
+
+static int davinci_i2s_probe(struct platform_device *pdev)
+{
+ struct snd_platform_data *pdata = pdev->dev.platform_data;
+ struct davinci_mcbsp_dev *dev;
+ struct resource *mem, *ioarea, *res;
+ enum dma_event_q asp_chan_q = EVENTQ_0;
+ enum dma_event_q ram_chan_q = EVENTQ_1;
+ int ret;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "no mem resource?\n");
+ return -ENODEV;
+ }
+
+ ioarea = request_mem_region(mem->start, resource_size(mem),
+ pdev->name);
+ if (!ioarea) {
+ dev_err(&pdev->dev, "McBSP region already claimed\n");
+ return -EBUSY;
+ }
+
+ dev = kzalloc(sizeof(struct davinci_mcbsp_dev), GFP_KERNEL);
+ if (!dev) {
+ ret = -ENOMEM;
+ goto err_release_region;
+ }
+ if (pdata) {
+ dev->enable_channel_combine = pdata->enable_channel_combine;
+ dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].sram_size =
+ pdata->sram_size_playback;
+ dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].sram_size =
+ pdata->sram_size_capture;
+ dev->clk_input_pin = pdata->clk_input_pin;
+ dev->i2s_accurate_sck = pdata->i2s_accurate_sck;
+ asp_chan_q = pdata->asp_chan_q;
+ ram_chan_q = pdata->ram_chan_q;
+ }
+
+ dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].asp_chan_q = asp_chan_q;
+ dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].ram_chan_q = ram_chan_q;
+ dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].asp_chan_q = asp_chan_q;
+ dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].ram_chan_q = ram_chan_q;
+
+ dev->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dev->clk)) {
+ ret = -ENODEV;
+ goto err_free_mem;
+ }
+ clk_enable(dev->clk);
+
+ dev->base = ioremap(mem->start, resource_size(mem));
+ if (!dev->base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err_release_clk;
+ }
+
+ dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].dma_addr =
+ (dma_addr_t)(mem->start + DAVINCI_MCBSP_DXR_REG);
+
+ dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].dma_addr =
+ (dma_addr_t)(mem->start + DAVINCI_MCBSP_DRR_REG);
+
+ /* first TX, then RX */
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "no DMA resource\n");
+ ret = -ENXIO;
+ goto err_iounmap;
+ }
+ dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].channel = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!res) {
+ dev_err(&pdev->dev, "no DMA resource\n");
+ ret = -ENXIO;
+ goto err_iounmap;
+ }
+ dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel = res->start;
+ dev->dev = &pdev->dev;
+
+ dev_set_drvdata(&pdev->dev, dev);
+
+ ret = snd_soc_register_dai(&pdev->dev, &davinci_i2s_dai);
+ if (ret != 0)
+ goto err_iounmap;
+
+ return 0;
+
+err_iounmap:
+ iounmap(dev->base);
+err_release_clk:
+ clk_disable(dev->clk);
+ clk_put(dev->clk);
+err_free_mem:
+ kfree(dev);
+err_release_region:
+ release_mem_region(mem->start, resource_size(mem));
+
+ return ret;
+}
+
+static int davinci_i2s_remove(struct platform_device *pdev)
+{
+ struct davinci_mcbsp_dev *dev = dev_get_drvdata(&pdev->dev);
+ struct resource *mem;
+
+ snd_soc_unregister_dai(&pdev->dev);
+ clk_disable(dev->clk);
+ clk_put(dev->clk);
+ dev->clk = NULL;
+ kfree(dev);
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(mem->start, resource_size(mem));
+
+ return 0;
+}
+
+static struct platform_driver davinci_mcbsp_driver = {
+ .probe = davinci_i2s_probe,
+ .remove = davinci_i2s_remove,
+ .driver = {
+ .name = "davinci-mcbsp",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init davinci_i2s_init(void)
+{
+ return platform_driver_register(&davinci_mcbsp_driver);
+}
+module_init(davinci_i2s_init);
+
+static void __exit davinci_i2s_exit(void)
+{
+ platform_driver_unregister(&davinci_mcbsp_driver);
+}
+module_exit(davinci_i2s_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("TI DAVINCI I2S (McBSP) SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/davinci/davinci-i2s.h b/sound/soc/davinci/davinci-i2s.h
new file mode 100644
index 00000000..48dac3e2
--- /dev/null
+++ b/sound/soc/davinci/davinci-i2s.h
@@ -0,0 +1,20 @@
+/*
+ * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _DAVINCI_I2S_H
+#define _DAVINCI_I2S_H
+
+/* McBSP dividers */
+enum davinci_mcbsp_div {
+ DAVINCI_MCBSP_CLKGDV, /* Sample rate generator divider */
+};
+
+#endif
diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c
new file mode 100644
index 00000000..8566238d
--- /dev/null
+++ b/sound/soc/davinci/davinci-mcasp.c
@@ -0,0 +1,1003 @@
+/*
+ * ALSA SoC McASP Audio Layer for TI DAVINCI processor
+ *
+ * Multi-channel Audio Serial Port Driver
+ *
+ * Author: Nirmal Pandey <n-pandey@ti.com>,
+ * Suresh Rajashekara <suresh.r@ti.com>
+ * Steve Chen <schen@.mvista.com>
+ *
+ * Copyright: (C) 2009 MontaVista Software, Inc., <source@mvista.com>
+ * Copyright: (C) 2009 Texas Instruments, India
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "davinci-pcm.h"
+#include "davinci-mcasp.h"
+
+/*
+ * McASP register definitions
+ */
+#define DAVINCI_MCASP_PID_REG 0x00
+#define DAVINCI_MCASP_PWREMUMGT_REG 0x04
+
+#define DAVINCI_MCASP_PFUNC_REG 0x10
+#define DAVINCI_MCASP_PDIR_REG 0x14
+#define DAVINCI_MCASP_PDOUT_REG 0x18
+#define DAVINCI_MCASP_PDSET_REG 0x1c
+
+#define DAVINCI_MCASP_PDCLR_REG 0x20
+
+#define DAVINCI_MCASP_TLGC_REG 0x30
+#define DAVINCI_MCASP_TLMR_REG 0x34
+
+#define DAVINCI_MCASP_GBLCTL_REG 0x44
+#define DAVINCI_MCASP_AMUTE_REG 0x48
+#define DAVINCI_MCASP_LBCTL_REG 0x4c
+
+#define DAVINCI_MCASP_TXDITCTL_REG 0x50
+
+#define DAVINCI_MCASP_GBLCTLR_REG 0x60
+#define DAVINCI_MCASP_RXMASK_REG 0x64
+#define DAVINCI_MCASP_RXFMT_REG 0x68
+#define DAVINCI_MCASP_RXFMCTL_REG 0x6c
+
+#define DAVINCI_MCASP_ACLKRCTL_REG 0x70
+#define DAVINCI_MCASP_AHCLKRCTL_REG 0x74
+#define DAVINCI_MCASP_RXTDM_REG 0x78
+#define DAVINCI_MCASP_EVTCTLR_REG 0x7c
+
+#define DAVINCI_MCASP_RXSTAT_REG 0x80
+#define DAVINCI_MCASP_RXTDMSLOT_REG 0x84
+#define DAVINCI_MCASP_RXCLKCHK_REG 0x88
+#define DAVINCI_MCASP_REVTCTL_REG 0x8c
+
+#define DAVINCI_MCASP_GBLCTLX_REG 0xa0
+#define DAVINCI_MCASP_TXMASK_REG 0xa4
+#define DAVINCI_MCASP_TXFMT_REG 0xa8
+#define DAVINCI_MCASP_TXFMCTL_REG 0xac
+
+#define DAVINCI_MCASP_ACLKXCTL_REG 0xb0
+#define DAVINCI_MCASP_AHCLKXCTL_REG 0xb4
+#define DAVINCI_MCASP_TXTDM_REG 0xb8
+#define DAVINCI_MCASP_EVTCTLX_REG 0xbc
+
+#define DAVINCI_MCASP_TXSTAT_REG 0xc0
+#define DAVINCI_MCASP_TXTDMSLOT_REG 0xc4
+#define DAVINCI_MCASP_TXCLKCHK_REG 0xc8
+#define DAVINCI_MCASP_XEVTCTL_REG 0xcc
+
+/* Left(even TDM Slot) Channel Status Register File */
+#define DAVINCI_MCASP_DITCSRA_REG 0x100
+/* Right(odd TDM slot) Channel Status Register File */
+#define DAVINCI_MCASP_DITCSRB_REG 0x118
+/* Left(even TDM slot) User Data Register File */
+#define DAVINCI_MCASP_DITUDRA_REG 0x130
+/* Right(odd TDM Slot) User Data Register File */
+#define DAVINCI_MCASP_DITUDRB_REG 0x148
+
+/* Serializer n Control Register */
+#define DAVINCI_MCASP_XRSRCTL_BASE_REG 0x180
+#define DAVINCI_MCASP_XRSRCTL_REG(n) (DAVINCI_MCASP_XRSRCTL_BASE_REG + \
+ (n << 2))
+
+/* Transmit Buffer for Serializer n */
+#define DAVINCI_MCASP_TXBUF_REG 0x200
+/* Receive Buffer for Serializer n */
+#define DAVINCI_MCASP_RXBUF_REG 0x280
+
+/* McASP FIFO Registers */
+#define DAVINCI_MCASP_WFIFOCTL (0x1010)
+#define DAVINCI_MCASP_WFIFOSTS (0x1014)
+#define DAVINCI_MCASP_RFIFOCTL (0x1018)
+#define DAVINCI_MCASP_RFIFOSTS (0x101C)
+
+/*
+ * DAVINCI_MCASP_PWREMUMGT_REG - Power Down and Emulation Management
+ * Register Bits
+ */
+#define MCASP_FREE BIT(0)
+#define MCASP_SOFT BIT(1)
+
+/*
+ * DAVINCI_MCASP_PFUNC_REG - Pin Function / GPIO Enable Register Bits
+ */
+#define AXR(n) (1<<n)
+#define PFUNC_AMUTE BIT(25)
+#define ACLKX BIT(26)
+#define AHCLKX BIT(27)
+#define AFSX BIT(28)
+#define ACLKR BIT(29)
+#define AHCLKR BIT(30)
+#define AFSR BIT(31)
+
+/*
+ * DAVINCI_MCASP_PDIR_REG - Pin Direction Register Bits
+ */
+#define AXR(n) (1<<n)
+#define PDIR_AMUTE BIT(25)
+#define ACLKX BIT(26)
+#define AHCLKX BIT(27)
+#define AFSX BIT(28)
+#define ACLKR BIT(29)
+#define AHCLKR BIT(30)
+#define AFSR BIT(31)
+
+/*
+ * DAVINCI_MCASP_TXDITCTL_REG - Transmit DIT Control Register Bits
+ */
+#define DITEN BIT(0) /* Transmit DIT mode enable/disable */
+#define VA BIT(2)
+#define VB BIT(3)
+
+/*
+ * DAVINCI_MCASP_TXFMT_REG - Transmit Bitstream Format Register Bits
+ */
+#define TXROT(val) (val)
+#define TXSEL BIT(3)
+#define TXSSZ(val) (val<<4)
+#define TXPBIT(val) (val<<8)
+#define TXPAD(val) (val<<13)
+#define TXORD BIT(15)
+#define FSXDLY(val) (val<<16)
+
+/*
+ * DAVINCI_MCASP_RXFMT_REG - Receive Bitstream Format Register Bits
+ */
+#define RXROT(val) (val)
+#define RXSEL BIT(3)
+#define RXSSZ(val) (val<<4)
+#define RXPBIT(val) (val<<8)
+#define RXPAD(val) (val<<13)
+#define RXORD BIT(15)
+#define FSRDLY(val) (val<<16)
+
+/*
+ * DAVINCI_MCASP_TXFMCTL_REG - Transmit Frame Control Register Bits
+ */
+#define FSXPOL BIT(0)
+#define AFSXE BIT(1)
+#define FSXDUR BIT(4)
+#define FSXMOD(val) (val<<7)
+
+/*
+ * DAVINCI_MCASP_RXFMCTL_REG - Receive Frame Control Register Bits
+ */
+#define FSRPOL BIT(0)
+#define AFSRE BIT(1)
+#define FSRDUR BIT(4)
+#define FSRMOD(val) (val<<7)
+
+/*
+ * DAVINCI_MCASP_ACLKXCTL_REG - Transmit Clock Control Register Bits
+ */
+#define ACLKXDIV(val) (val)
+#define ACLKXE BIT(5)
+#define TX_ASYNC BIT(6)
+#define ACLKXPOL BIT(7)
+
+/*
+ * DAVINCI_MCASP_ACLKRCTL_REG Receive Clock Control Register Bits
+ */
+#define ACLKRDIV(val) (val)
+#define ACLKRE BIT(5)
+#define RX_ASYNC BIT(6)
+#define ACLKRPOL BIT(7)
+
+/*
+ * DAVINCI_MCASP_AHCLKXCTL_REG - High Frequency Transmit Clock Control
+ * Register Bits
+ */
+#define AHCLKXDIV(val) (val)
+#define AHCLKXPOL BIT(14)
+#define AHCLKXE BIT(15)
+
+/*
+ * DAVINCI_MCASP_AHCLKRCTL_REG - High Frequency Receive Clock Control
+ * Register Bits
+ */
+#define AHCLKRDIV(val) (val)
+#define AHCLKRPOL BIT(14)
+#define AHCLKRE BIT(15)
+
+/*
+ * DAVINCI_MCASP_XRSRCTL_BASE_REG - Serializer Control Register Bits
+ */
+#define MODE(val) (val)
+#define DISMOD (val)(val<<2)
+#define TXSTATE BIT(4)
+#define RXSTATE BIT(5)
+
+/*
+ * DAVINCI_MCASP_LBCTL_REG - Loop Back Control Register Bits
+ */
+#define LBEN BIT(0)
+#define LBORD BIT(1)
+#define LBGENMODE(val) (val<<2)
+
+/*
+ * DAVINCI_MCASP_TXTDMSLOT_REG - Transmit TDM Slot Register configuration
+ */
+#define TXTDMS(n) (1<<n)
+
+/*
+ * DAVINCI_MCASP_RXTDMSLOT_REG - Receive TDM Slot Register configuration
+ */
+#define RXTDMS(n) (1<<n)
+
+/*
+ * DAVINCI_MCASP_GBLCTL_REG - Global Control Register Bits
+ */
+#define RXCLKRST BIT(0) /* Receiver Clock Divider Reset */
+#define RXHCLKRST BIT(1) /* Receiver High Frequency Clock Divider */
+#define RXSERCLR BIT(2) /* Receiver Serializer Clear */
+#define RXSMRST BIT(3) /* Receiver State Machine Reset */
+#define RXFSRST BIT(4) /* Frame Sync Generator Reset */
+#define TXCLKRST BIT(8) /* Transmitter Clock Divider Reset */
+#define TXHCLKRST BIT(9) /* Transmitter High Frequency Clock Divider*/
+#define TXSERCLR BIT(10) /* Transmit Serializer Clear */
+#define TXSMRST BIT(11) /* Transmitter State Machine Reset */
+#define TXFSRST BIT(12) /* Frame Sync Generator Reset */
+
+/*
+ * DAVINCI_MCASP_AMUTE_REG - Mute Control Register Bits
+ */
+#define MUTENA(val) (val)
+#define MUTEINPOL BIT(2)
+#define MUTEINENA BIT(3)
+#define MUTEIN BIT(4)
+#define MUTER BIT(5)
+#define MUTEX BIT(6)
+#define MUTEFSR BIT(7)
+#define MUTEFSX BIT(8)
+#define MUTEBADCLKR BIT(9)
+#define MUTEBADCLKX BIT(10)
+#define MUTERXDMAERR BIT(11)
+#define MUTETXDMAERR BIT(12)
+
+/*
+ * DAVINCI_MCASP_REVTCTL_REG - Receiver DMA Event Control Register bits
+ */
+#define RXDATADMADIS BIT(0)
+
+/*
+ * DAVINCI_MCASP_XEVTCTL_REG - Transmitter DMA Event Control Register bits
+ */
+#define TXDATADMADIS BIT(0)
+
+/*
+ * DAVINCI_MCASP_W[R]FIFOCTL - Write/Read FIFO Control Register bits
+ */
+#define FIFO_ENABLE BIT(16)
+#define NUMEVT_MASK (0xFF << 8)
+#define NUMDMA_MASK (0xFF)
+
+#define DAVINCI_MCASP_NUM_SERIALIZER 16
+
+static inline void mcasp_set_bits(void __iomem *reg, u32 val)
+{
+ __raw_writel(__raw_readl(reg) | val, reg);
+}
+
+static inline void mcasp_clr_bits(void __iomem *reg, u32 val)
+{
+ __raw_writel((__raw_readl(reg) & ~(val)), reg);
+}
+
+static inline void mcasp_mod_bits(void __iomem *reg, u32 val, u32 mask)
+{
+ __raw_writel((__raw_readl(reg) & ~mask) | val, reg);
+}
+
+static inline void mcasp_set_reg(void __iomem *reg, u32 val)
+{
+ __raw_writel(val, reg);
+}
+
+static inline u32 mcasp_get_reg(void __iomem *reg)
+{
+ return (unsigned int)__raw_readl(reg);
+}
+
+static inline void mcasp_set_ctl_reg(void __iomem *regs, u32 val)
+{
+ int i = 0;
+
+ mcasp_set_bits(regs, val);
+
+ /* programming GBLCTL needs to read back from GBLCTL and verfiy */
+ /* loop count is to avoid the lock-up */
+ for (i = 0; i < 1000; i++) {
+ if ((mcasp_get_reg(regs) & val) == val)
+ break;
+ }
+
+ if (i == 1000 && ((mcasp_get_reg(regs) & val) != val))
+ printk(KERN_ERR "GBLCTL write error\n");
+}
+
+static void mcasp_start_rx(struct davinci_audio_dev *dev)
+{
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXHCLKRST);
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXCLKRST);
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSERCLR);
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_RXBUF_REG, 0);
+
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSMRST);
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXFSRST);
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_RXBUF_REG, 0);
+
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSMRST);
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXFSRST);
+}
+
+static void mcasp_start_tx(struct davinci_audio_dev *dev)
+{
+ u8 offset = 0, i;
+ u32 cnt;
+
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST);
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST);
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXSERCLR);
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0);
+
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXSMRST);
+ mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXFSRST);
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0);
+ for (i = 0; i < dev->num_serializer; i++) {
+ if (dev->serial_dir[i] == TX_MODE) {
+ offset = i;
+ break;
+ }
+ }
+
+ /* wait for TX ready */
+ cnt = 0;
+ while (!(mcasp_get_reg(dev->base + DAVINCI_MCASP_XRSRCTL_REG(offset)) &
+ TXSTATE) && (cnt < 100000))
+ cnt++;
+
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0);
+}
+
+static void davinci_mcasp_start(struct davinci_audio_dev *dev, int stream)
+{
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (dev->txnumevt) /* enable FIFO */
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_WFIFOCTL,
+ FIFO_ENABLE);
+ mcasp_start_tx(dev);
+ } else {
+ if (dev->rxnumevt) /* enable FIFO */
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_RFIFOCTL,
+ FIFO_ENABLE);
+ mcasp_start_rx(dev);
+ }
+}
+
+static void mcasp_stop_rx(struct davinci_audio_dev *dev)
+{
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, 0);
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF);
+}
+
+static void mcasp_stop_tx(struct davinci_audio_dev *dev)
+{
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, 0);
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF);
+}
+
+static void davinci_mcasp_stop(struct davinci_audio_dev *dev, int stream)
+{
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (dev->txnumevt) /* disable FIFO */
+ mcasp_clr_bits(dev->base + DAVINCI_MCASP_WFIFOCTL,
+ FIFO_ENABLE);
+ mcasp_stop_tx(dev);
+ } else {
+ if (dev->rxnumevt) /* disable FIFO */
+ mcasp_clr_bits(dev->base + DAVINCI_MCASP_RFIFOCTL,
+ FIFO_ENABLE);
+ mcasp_stop_rx(dev);
+ }
+}
+
+static int davinci_mcasp_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct davinci_audio_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+ void __iomem *base = dev->base;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* codec is clock and frame slave */
+ mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE);
+ mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE);
+
+ mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE);
+ mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE);
+
+ mcasp_set_bits(base + DAVINCI_MCASP_PDIR_REG,
+ ACLKX | AHCLKX | AFSX);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ /* codec is clock master and frame slave */
+ mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE);
+ mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE);
+
+ mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE);
+ mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE);
+
+ mcasp_clr_bits(base + DAVINCI_MCASP_PDIR_REG,
+ ACLKX | ACLKR);
+ mcasp_set_bits(base + DAVINCI_MCASP_PDIR_REG,
+ AFSX | AFSR);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* codec is clock and frame master */
+ mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE);
+ mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE);
+
+ mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE);
+ mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE);
+
+ mcasp_clr_bits(base + DAVINCI_MCASP_PDIR_REG,
+ ACLKX | AHCLKX | AFSX | ACLKR | AHCLKR | AFSR);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_NF:
+ mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
+ mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
+
+ mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
+ mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
+ break;
+
+ case SND_SOC_DAIFMT_NB_IF:
+ mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
+ mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
+
+ mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
+ mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
+ break;
+
+ case SND_SOC_DAIFMT_IB_IF:
+ mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
+ mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
+
+ mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
+ mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
+ break;
+
+ case SND_SOC_DAIFMT_NB_NF:
+ mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
+ mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
+
+ mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
+ mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int davinci_config_channel_size(struct davinci_audio_dev *dev,
+ int channel_size)
+{
+ u32 fmt = 0;
+ u32 mask, rotate;
+
+ switch (channel_size) {
+ case DAVINCI_AUDIO_WORD_8:
+ fmt = 0x03;
+ rotate = 6;
+ mask = 0x000000ff;
+ break;
+
+ case DAVINCI_AUDIO_WORD_12:
+ fmt = 0x05;
+ rotate = 5;
+ mask = 0x00000fff;
+ break;
+
+ case DAVINCI_AUDIO_WORD_16:
+ fmt = 0x07;
+ rotate = 4;
+ mask = 0x0000ffff;
+ break;
+
+ case DAVINCI_AUDIO_WORD_20:
+ fmt = 0x09;
+ rotate = 3;
+ mask = 0x000fffff;
+ break;
+
+ case DAVINCI_AUDIO_WORD_24:
+ fmt = 0x0B;
+ rotate = 2;
+ mask = 0x00ffffff;
+ break;
+
+ case DAVINCI_AUDIO_WORD_28:
+ fmt = 0x0D;
+ rotate = 1;
+ mask = 0x0fffffff;
+ break;
+
+ case DAVINCI_AUDIO_WORD_32:
+ fmt = 0x0F;
+ rotate = 0;
+ mask = 0xffffffff;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ mcasp_mod_bits(dev->base + DAVINCI_MCASP_RXFMT_REG,
+ RXSSZ(fmt), RXSSZ(0x0F));
+ mcasp_mod_bits(dev->base + DAVINCI_MCASP_TXFMT_REG,
+ TXSSZ(fmt), TXSSZ(0x0F));
+ mcasp_mod_bits(dev->base + DAVINCI_MCASP_TXFMT_REG, TXROT(rotate),
+ TXROT(7));
+ mcasp_mod_bits(dev->base + DAVINCI_MCASP_RXFMT_REG, RXROT(rotate),
+ RXROT(7));
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_TXMASK_REG, mask);
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_RXMASK_REG, mask);
+
+ return 0;
+}
+
+static void davinci_hw_common_param(struct davinci_audio_dev *dev, int stream)
+{
+ int i;
+ u8 tx_ser = 0;
+ u8 rx_ser = 0;
+
+ /* Default configuration */
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_PWREMUMGT_REG, MCASP_SOFT);
+
+ /* All PINS as McASP */
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_PFUNC_REG, 0x00000000);
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF);
+ mcasp_clr_bits(dev->base + DAVINCI_MCASP_XEVTCTL_REG,
+ TXDATADMADIS);
+ } else {
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF);
+ mcasp_clr_bits(dev->base + DAVINCI_MCASP_REVTCTL_REG,
+ RXDATADMADIS);
+ }
+
+ for (i = 0; i < dev->num_serializer; i++) {
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_XRSRCTL_REG(i),
+ dev->serial_dir[i]);
+ if (dev->serial_dir[i] == TX_MODE) {
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_PDIR_REG,
+ AXR(i));
+ tx_ser++;
+ } else if (dev->serial_dir[i] == RX_MODE) {
+ mcasp_clr_bits(dev->base + DAVINCI_MCASP_PDIR_REG,
+ AXR(i));
+ rx_ser++;
+ }
+ }
+
+ if (dev->txnumevt && stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (dev->txnumevt * tx_ser > 64)
+ dev->txnumevt = 1;
+
+ mcasp_mod_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, tx_ser,
+ NUMDMA_MASK);
+ mcasp_mod_bits(dev->base + DAVINCI_MCASP_WFIFOCTL,
+ ((dev->txnumevt * tx_ser) << 8), NUMEVT_MASK);
+ }
+
+ if (dev->rxnumevt && stream == SNDRV_PCM_STREAM_CAPTURE) {
+ if (dev->rxnumevt * rx_ser > 64)
+ dev->rxnumevt = 1;
+
+ mcasp_mod_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, rx_ser,
+ NUMDMA_MASK);
+ mcasp_mod_bits(dev->base + DAVINCI_MCASP_RFIFOCTL,
+ ((dev->rxnumevt * rx_ser) << 8), NUMEVT_MASK);
+ }
+}
+
+static void davinci_hw_param(struct davinci_audio_dev *dev, int stream)
+{
+ int i, active_slots;
+ u32 mask = 0;
+
+ active_slots = (dev->tdm_slots > 31) ? 32 : dev->tdm_slots;
+ for (i = 0; i < active_slots; i++)
+ mask |= (1 << i);
+
+ mcasp_clr_bits(dev->base + DAVINCI_MCASP_ACLKXCTL_REG, TX_ASYNC);
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* bit stream is MSB first with no delay */
+ /* DSP_B mode */
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKXCTL_REG,
+ AHCLKXE);
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_TXTDM_REG, mask);
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_TXFMT_REG, TXORD);
+
+ if ((dev->tdm_slots >= 2) && (dev->tdm_slots <= 32))
+ mcasp_mod_bits(dev->base + DAVINCI_MCASP_TXFMCTL_REG,
+ FSXMOD(dev->tdm_slots), FSXMOD(0x1FF));
+ else
+ printk(KERN_ERR "playback tdm slot %d not supported\n",
+ dev->tdm_slots);
+
+ mcasp_clr_bits(dev->base + DAVINCI_MCASP_TXFMCTL_REG, FSXDUR);
+ } else {
+ /* bit stream is MSB first with no delay */
+ /* DSP_B mode */
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_RXFMT_REG, RXORD);
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKRCTL_REG,
+ AHCLKRE);
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_RXTDM_REG, mask);
+
+ if ((dev->tdm_slots >= 2) && (dev->tdm_slots <= 32))
+ mcasp_mod_bits(dev->base + DAVINCI_MCASP_RXFMCTL_REG,
+ FSRMOD(dev->tdm_slots), FSRMOD(0x1FF));
+ else
+ printk(KERN_ERR "capture tdm slot %d not supported\n",
+ dev->tdm_slots);
+
+ mcasp_clr_bits(dev->base + DAVINCI_MCASP_RXFMCTL_REG, FSRDUR);
+ }
+}
+
+/* S/PDIF */
+static void davinci_hw_dit_param(struct davinci_audio_dev *dev)
+{
+ /* Set the PDIR for Serialiser as output */
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_PDIR_REG, AFSX);
+
+ /* TXMASK for 24 bits */
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_TXMASK_REG, 0x00FFFFFF);
+
+ /* Set the TX format : 24 bit right rotation, 32 bit slot, Pad 0
+ and LSB first */
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_TXFMT_REG,
+ TXROT(6) | TXSSZ(15));
+
+ /* Set TX frame synch : DIT Mode, 1 bit width, internal, rising edge */
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_TXFMCTL_REG,
+ AFSXE | FSXMOD(0x180));
+
+ /* Set the TX tdm : for all the slots */
+ mcasp_set_reg(dev->base + DAVINCI_MCASP_TXTDM_REG, 0xFFFFFFFF);
+
+ /* Set the TX clock controls : div = 1 and internal */
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_ACLKXCTL_REG,
+ ACLKXE | TX_ASYNC);
+
+ mcasp_clr_bits(dev->base + DAVINCI_MCASP_XEVTCTL_REG, TXDATADMADIS);
+
+ /* Only 44100 and 48000 are valid, both have the same setting */
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXDIV(3));
+
+ /* Enable the DIT */
+ mcasp_set_bits(dev->base + DAVINCI_MCASP_TXDITCTL_REG, DITEN);
+}
+
+static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct davinci_audio_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+ struct davinci_pcm_dma_params *dma_params =
+ &dev->dma_params[substream->stream];
+ int word_length;
+ u8 fifo_level;
+
+ davinci_hw_common_param(dev, substream->stream);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ fifo_level = dev->txnumevt;
+ else
+ fifo_level = dev->rxnumevt;
+
+ if (dev->op_mode == DAVINCI_MCASP_DIT_MODE)
+ davinci_hw_dit_param(dev);
+ else
+ davinci_hw_param(dev, substream->stream);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ dma_params->data_type = 1;
+ word_length = DAVINCI_AUDIO_WORD_8;
+ break;
+
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dma_params->data_type = 2;
+ word_length = DAVINCI_AUDIO_WORD_16;
+ break;
+
+ case SNDRV_PCM_FORMAT_S32_LE:
+ dma_params->data_type = 4;
+ word_length = DAVINCI_AUDIO_WORD_32;
+ break;
+
+ default:
+ printk(KERN_WARNING "davinci-mcasp: unsupported PCM format");
+ return -EINVAL;
+ }
+
+ if (dev->version == MCASP_VERSION_2 && !fifo_level)
+ dma_params->acnt = 4;
+ else
+ dma_params->acnt = dma_params->data_type;
+
+ dma_params->fifo_level = fifo_level;
+ davinci_config_channel_size(dev, word_length);
+
+ return 0;
+}
+
+static int davinci_mcasp_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *cpu_dai)
+{
+ struct davinci_audio_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!dev->clk_active) {
+ clk_enable(dev->clk);
+ dev->clk_active = 1;
+ }
+ davinci_mcasp_start(dev, substream->stream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ davinci_mcasp_stop(dev, substream->stream);
+ if (dev->clk_active) {
+ clk_disable(dev->clk);
+ dev->clk_active = 0;
+ }
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ davinci_mcasp_stop(dev, substream->stream);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int davinci_mcasp_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct davinci_audio_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_set_dma_data(dai, substream, dev->dma_params);
+ return 0;
+}
+
+static struct snd_soc_dai_ops davinci_mcasp_dai_ops = {
+ .startup = davinci_mcasp_startup,
+ .trigger = davinci_mcasp_trigger,
+ .hw_params = davinci_mcasp_hw_params,
+ .set_fmt = davinci_mcasp_set_dai_fmt,
+
+};
+
+static struct snd_soc_dai_driver davinci_mcasp_dai[] = {
+ {
+ .name = "davinci-mcasp.0",
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = DAVINCI_MCASP_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = DAVINCI_MCASP_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &davinci_mcasp_dai_ops,
+
+ },
+ {
+ "davinci-mcasp.1",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 384,
+ .rates = DAVINCI_MCASP_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &davinci_mcasp_dai_ops,
+ },
+
+};
+
+static int davinci_mcasp_probe(struct platform_device *pdev)
+{
+ struct davinci_pcm_dma_params *dma_data;
+ struct resource *mem, *ioarea, *res;
+ struct snd_platform_data *pdata;
+ struct davinci_audio_dev *dev;
+ int ret = 0;
+
+ dev = kzalloc(sizeof(struct davinci_audio_dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "no mem resource?\n");
+ ret = -ENODEV;
+ goto err_release_data;
+ }
+
+ ioarea = request_mem_region(mem->start,
+ resource_size(mem), pdev->name);
+ if (!ioarea) {
+ dev_err(&pdev->dev, "Audio region already claimed\n");
+ ret = -EBUSY;
+ goto err_release_data;
+ }
+
+ pdata = pdev->dev.platform_data;
+ dev->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dev->clk)) {
+ ret = -ENODEV;
+ goto err_release_region;
+ }
+
+ clk_enable(dev->clk);
+ dev->clk_active = 1;
+
+ dev->base = ioremap(mem->start, resource_size(mem));
+ if (!dev->base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err_release_clk;
+ }
+
+ dev->op_mode = pdata->op_mode;
+ dev->tdm_slots = pdata->tdm_slots;
+ dev->num_serializer = pdata->num_serializer;
+ dev->serial_dir = pdata->serial_dir;
+ dev->codec_fmt = pdata->codec_fmt;
+ dev->version = pdata->version;
+ dev->txnumevt = pdata->txnumevt;
+ dev->rxnumevt = pdata->rxnumevt;
+
+ dma_data = &dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK];
+ dma_data->asp_chan_q = pdata->asp_chan_q;
+ dma_data->ram_chan_q = pdata->ram_chan_q;
+ dma_data->sram_size = pdata->sram_size_playback;
+ dma_data->dma_addr = (dma_addr_t) (pdata->tx_dma_offset +
+ mem->start);
+
+ /* first TX, then RX */
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "no DMA resource\n");
+ ret = -ENODEV;
+ goto err_iounmap;
+ }
+
+ dma_data->channel = res->start;
+
+ dma_data = &dev->dma_params[SNDRV_PCM_STREAM_CAPTURE];
+ dma_data->asp_chan_q = pdata->asp_chan_q;
+ dma_data->ram_chan_q = pdata->ram_chan_q;
+ dma_data->sram_size = pdata->sram_size_capture;
+ dma_data->dma_addr = (dma_addr_t)(pdata->rx_dma_offset +
+ mem->start);
+
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!res) {
+ dev_err(&pdev->dev, "no DMA resource\n");
+ ret = -ENODEV;
+ goto err_iounmap;
+ }
+
+ dma_data->channel = res->start;
+ dev_set_drvdata(&pdev->dev, dev);
+ ret = snd_soc_register_dai(&pdev->dev, &davinci_mcasp_dai[pdata->op_mode]);
+
+ if (ret != 0)
+ goto err_iounmap;
+ return 0;
+
+err_iounmap:
+ iounmap(dev->base);
+err_release_clk:
+ clk_disable(dev->clk);
+ clk_put(dev->clk);
+err_release_region:
+ release_mem_region(mem->start, resource_size(mem));
+err_release_data:
+ kfree(dev);
+
+ return ret;
+}
+
+static int davinci_mcasp_remove(struct platform_device *pdev)
+{
+ struct davinci_audio_dev *dev = dev_get_drvdata(&pdev->dev);
+ struct resource *mem;
+
+ snd_soc_unregister_dai(&pdev->dev);
+ clk_disable(dev->clk);
+ clk_put(dev->clk);
+ dev->clk = NULL;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(mem->start, resource_size(mem));
+
+ kfree(dev);
+
+ return 0;
+}
+
+static struct platform_driver davinci_mcasp_driver = {
+ .probe = davinci_mcasp_probe,
+ .remove = davinci_mcasp_remove,
+ .driver = {
+ .name = "davinci-mcasp",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init davinci_mcasp_init(void)
+{
+ return platform_driver_register(&davinci_mcasp_driver);
+}
+module_init(davinci_mcasp_init);
+
+static void __exit davinci_mcasp_exit(void)
+{
+ platform_driver_unregister(&davinci_mcasp_driver);
+}
+module_exit(davinci_mcasp_exit);
+
+MODULE_AUTHOR("Steve Chen");
+MODULE_DESCRIPTION("TI DAVINCI McASP SoC Interface");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/davinci/davinci-mcasp.h b/sound/soc/davinci/davinci-mcasp.h
new file mode 100644
index 00000000..4681acc6
--- /dev/null
+++ b/sound/soc/davinci/davinci-mcasp.h
@@ -0,0 +1,59 @@
+/*
+ * ALSA SoC McASP Audio Layer for TI DAVINCI processor
+ *
+ * MCASP related definitions
+ *
+ * Author: Nirmal Pandey <n-pandey@ti.com>,
+ * Suresh Rajashekara <suresh.r@ti.com>
+ * Steve Chen <schen@.mvista.com>
+ *
+ * Copyright: (C) 2009 MontaVista Software, Inc., <source@mvista.com>
+ * Copyright: (C) 2009 Texas Instruments, India
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef DAVINCI_MCASP_H
+#define DAVINCI_MCASP_H
+
+#include <linux/io.h>
+#include <mach/asp.h>
+#include "davinci-pcm.h"
+
+#define DAVINCI_MCASP_RATES SNDRV_PCM_RATE_8000_96000
+#define DAVINCI_MCASP_I2S_DAI 0
+#define DAVINCI_MCASP_DIT_DAI 1
+
+enum {
+ DAVINCI_AUDIO_WORD_8 = 0,
+ DAVINCI_AUDIO_WORD_12,
+ DAVINCI_AUDIO_WORD_16,
+ DAVINCI_AUDIO_WORD_20,
+ DAVINCI_AUDIO_WORD_24,
+ DAVINCI_AUDIO_WORD_32,
+ DAVINCI_AUDIO_WORD_28, /* This is only valid for McASP */
+};
+
+struct davinci_audio_dev {
+ struct davinci_pcm_dma_params dma_params[2];
+ void __iomem *base;
+ int sample_rate;
+ struct clk *clk;
+ unsigned int codec_fmt;
+ u8 clk_active;
+
+ /* McASP specific data */
+ int tdm_slots;
+ u8 op_mode;
+ u8 num_serializer;
+ u8 *serial_dir;
+ u8 version;
+
+ /* McASP FIFO related */
+ u8 txnumevt;
+ u8 rxnumevt;
+};
+
+#endif /* DAVINCI_MCASP_H */
diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c
new file mode 100644
index 00000000..9d35b8c1
--- /dev/null
+++ b/sound/soc/davinci/davinci-pcm.c
@@ -0,0 +1,884 @@
+/*
+ * ALSA PCM interface for the TI DAVINCI processor
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ * added SRAM ping/pong (C) 2008 Troy Kisky <troy.kisky@boundarydevices.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <mach/edma.h>
+#include <mach/sram.h>
+
+#include "davinci-pcm.h"
+
+#ifdef DEBUG
+static void print_buf_info(int slot, char *name)
+{
+ struct edmacc_param p;
+ if (slot < 0)
+ return;
+ edma_read_slot(slot, &p);
+ printk(KERN_DEBUG "%s: 0x%x, opt=%x, src=%x, a_b_cnt=%x dst=%x\n",
+ name, slot, p.opt, p.src, p.a_b_cnt, p.dst);
+ printk(KERN_DEBUG " src_dst_bidx=%x link_bcntrld=%x src_dst_cidx=%x ccnt=%x\n",
+ p.src_dst_bidx, p.link_bcntrld, p.src_dst_cidx, p.ccnt);
+}
+#else
+static void print_buf_info(int slot, char *name)
+{
+}
+#endif
+
+static struct snd_pcm_hardware pcm_hardware_playback = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE),
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_KNOT),
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 128 * 1024,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8 * 1024,
+ .periods_min = 16,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static struct snd_pcm_hardware pcm_hardware_capture = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE),
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_KNOT),
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 128 * 1024,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8 * 1024,
+ .periods_min = 16,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+/*
+ * How ping/pong works....
+ *
+ * Playback:
+ * ram_params - copys 2*ping_size from start of SDRAM to iram,
+ * links to ram_link2
+ * ram_link2 - copys rest of SDRAM to iram in ping_size units,
+ * links to ram_link
+ * ram_link - copys entire SDRAM to iram in ping_size uints,
+ * links to self
+ *
+ * asp_params - same as asp_link[0]
+ * asp_link[0] - copys from lower half of iram to asp port
+ * links to asp_link[1], triggers iram copy event on completion
+ * asp_link[1] - copys from upper half of iram to asp port
+ * links to asp_link[0], triggers iram copy event on completion
+ * triggers interrupt only needed to let upper SOC levels update position
+ * in stream on completion
+ *
+ * When playback is started:
+ * ram_params started
+ * asp_params started
+ *
+ * Capture:
+ * ram_params - same as ram_link,
+ * links to ram_link
+ * ram_link - same as playback
+ * links to self
+ *
+ * asp_params - same as playback
+ * asp_link[0] - same as playback
+ * asp_link[1] - same as playback
+ *
+ * When capture is started:
+ * asp_params started
+ */
+struct davinci_runtime_data {
+ spinlock_t lock;
+ int period; /* current DMA period */
+ int asp_channel; /* Master DMA channel */
+ int asp_link[2]; /* asp parameter link channel, ping/pong */
+ struct davinci_pcm_dma_params *params; /* DMA params */
+ int ram_channel;
+ int ram_link;
+ int ram_link2;
+ struct edmacc_param asp_params;
+ struct edmacc_param ram_params;
+};
+
+/*
+ * Not used with ping/pong
+ */
+static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream)
+{
+ struct davinci_runtime_data *prtd = substream->runtime->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int link = prtd->asp_link[0];
+ unsigned int period_size;
+ unsigned int dma_offset;
+ dma_addr_t dma_pos;
+ dma_addr_t src, dst;
+ unsigned short src_bidx, dst_bidx;
+ unsigned short src_cidx, dst_cidx;
+ unsigned int data_type;
+ unsigned short acnt;
+ unsigned int count;
+ unsigned int fifo_level;
+
+ period_size = snd_pcm_lib_period_bytes(substream);
+ dma_offset = prtd->period * period_size;
+ dma_pos = runtime->dma_addr + dma_offset;
+ fifo_level = prtd->params->fifo_level;
+
+ pr_debug("davinci_pcm: audio_set_dma_params_play channel = %d "
+ "dma_ptr = %x period_size=%x\n", link, dma_pos, period_size);
+
+ data_type = prtd->params->data_type;
+ count = period_size / data_type;
+ if (fifo_level)
+ count /= fifo_level;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ src = dma_pos;
+ dst = prtd->params->dma_addr;
+ src_bidx = data_type;
+ dst_bidx = 0;
+ src_cidx = data_type * fifo_level;
+ dst_cidx = 0;
+ } else {
+ src = prtd->params->dma_addr;
+ dst = dma_pos;
+ src_bidx = 0;
+ dst_bidx = data_type;
+ src_cidx = 0;
+ dst_cidx = data_type * fifo_level;
+ }
+
+ acnt = prtd->params->acnt;
+ edma_set_src(link, src, INCR, W8BIT);
+ edma_set_dest(link, dst, INCR, W8BIT);
+
+ edma_set_src_index(link, src_bidx, src_cidx);
+ edma_set_dest_index(link, dst_bidx, dst_cidx);
+
+ if (!fifo_level)
+ edma_set_transfer_params(link, acnt, count, 1, 0, ASYNC);
+ else
+ edma_set_transfer_params(link, acnt, fifo_level, count,
+ fifo_level, ABSYNC);
+
+ prtd->period++;
+ if (unlikely(prtd->period >= runtime->periods))
+ prtd->period = 0;
+}
+
+static void davinci_pcm_dma_irq(unsigned link, u16 ch_status, void *data)
+{
+ struct snd_pcm_substream *substream = data;
+ struct davinci_runtime_data *prtd = substream->runtime->private_data;
+
+ print_buf_info(prtd->ram_channel, "i ram_channel");
+ pr_debug("davinci_pcm: link=%d, status=0x%x\n", link, ch_status);
+
+ if (unlikely(ch_status != DMA_COMPLETE))
+ return;
+
+ if (snd_pcm_running(substream)) {
+ if (prtd->ram_channel < 0) {
+ /* No ping/pong must fix up link dma data*/
+ spin_lock(&prtd->lock);
+ davinci_pcm_enqueue_dma(substream);
+ spin_unlock(&prtd->lock);
+ }
+ snd_pcm_period_elapsed(substream);
+ }
+}
+
+static int allocate_sram(struct snd_pcm_substream *substream, unsigned size,
+ struct snd_pcm_hardware *ppcm)
+{
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ struct snd_dma_buffer *iram_dma = NULL;
+ dma_addr_t iram_phys = 0;
+ void *iram_virt = NULL;
+
+ if (buf->private_data || !size)
+ return 0;
+
+ ppcm->period_bytes_max = size;
+ iram_virt = sram_alloc(size, &iram_phys);
+ if (!iram_virt)
+ goto exit1;
+ iram_dma = kzalloc(sizeof(*iram_dma), GFP_KERNEL);
+ if (!iram_dma)
+ goto exit2;
+ iram_dma->area = iram_virt;
+ iram_dma->addr = iram_phys;
+ memset(iram_dma->area, 0, size);
+ iram_dma->bytes = size;
+ buf->private_data = iram_dma;
+ return 0;
+exit2:
+ if (iram_virt)
+ sram_free(iram_virt, size);
+exit1:
+ return -ENOMEM;
+}
+
+/*
+ * Only used with ping/pong.
+ * This is called after runtime->dma_addr, period_bytes and data_type are valid
+ */
+static int ping_pong_dma_setup(struct snd_pcm_substream *substream)
+{
+ unsigned short ram_src_cidx, ram_dst_cidx;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct davinci_runtime_data *prtd = runtime->private_data;
+ struct snd_dma_buffer *iram_dma =
+ (struct snd_dma_buffer *)substream->dma_buffer.private_data;
+ struct davinci_pcm_dma_params *params = prtd->params;
+ unsigned int data_type = params->data_type;
+ unsigned int acnt = params->acnt;
+ /* divide by 2 for ping/pong */
+ unsigned int ping_size = snd_pcm_lib_period_bytes(substream) >> 1;
+ int link = prtd->asp_link[1];
+ unsigned int fifo_level = prtd->params->fifo_level;
+ unsigned int count;
+ if ((data_type == 0) || (data_type > 4)) {
+ printk(KERN_ERR "%s: data_type=%i\n", __func__, data_type);
+ return -EINVAL;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_addr_t asp_src_pong = iram_dma->addr + ping_size;
+ ram_src_cidx = ping_size;
+ ram_dst_cidx = -ping_size;
+ edma_set_src(link, asp_src_pong, INCR, W8BIT);
+
+ link = prtd->asp_link[0];
+ edma_set_src_index(link, data_type, data_type * fifo_level);
+ link = prtd->asp_link[1];
+ edma_set_src_index(link, data_type, data_type * fifo_level);
+
+ link = prtd->ram_link;
+ edma_set_src(link, runtime->dma_addr, INCR, W32BIT);
+ } else {
+ dma_addr_t asp_dst_pong = iram_dma->addr + ping_size;
+ ram_src_cidx = -ping_size;
+ ram_dst_cidx = ping_size;
+ edma_set_dest(link, asp_dst_pong, INCR, W8BIT);
+
+ link = prtd->asp_link[0];
+ edma_set_dest_index(link, data_type, data_type * fifo_level);
+ link = prtd->asp_link[1];
+ edma_set_dest_index(link, data_type, data_type * fifo_level);
+
+ link = prtd->ram_link;
+ edma_set_dest(link, runtime->dma_addr, INCR, W32BIT);
+ }
+
+ if (!fifo_level) {
+ count = ping_size / data_type;
+ edma_set_transfer_params(prtd->asp_link[0], acnt, count,
+ 1, 0, ASYNC);
+ edma_set_transfer_params(prtd->asp_link[1], acnt, count,
+ 1, 0, ASYNC);
+ } else {
+ count = ping_size / (data_type * fifo_level);
+ edma_set_transfer_params(prtd->asp_link[0], acnt, fifo_level,
+ count, fifo_level, ABSYNC);
+ edma_set_transfer_params(prtd->asp_link[1], acnt, fifo_level,
+ count, fifo_level, ABSYNC);
+ }
+
+ link = prtd->ram_link;
+ edma_set_src_index(link, ping_size, ram_src_cidx);
+ edma_set_dest_index(link, ping_size, ram_dst_cidx);
+ edma_set_transfer_params(link, ping_size, 2,
+ runtime->periods, 2, ASYNC);
+
+ /* init master params */
+ edma_read_slot(prtd->asp_link[0], &prtd->asp_params);
+ edma_read_slot(prtd->ram_link, &prtd->ram_params);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ struct edmacc_param p_ram;
+ /* Copy entire iram buffer before playback started */
+ prtd->ram_params.a_b_cnt = (1 << 16) | (ping_size << 1);
+ /* 0 dst_bidx */
+ prtd->ram_params.src_dst_bidx = (ping_size << 1);
+ /* 0 dst_cidx */
+ prtd->ram_params.src_dst_cidx = (ping_size << 1);
+ prtd->ram_params.ccnt = 1;
+
+ /* Skip 1st period */
+ edma_read_slot(prtd->ram_link, &p_ram);
+ p_ram.src += (ping_size << 1);
+ p_ram.ccnt -= 1;
+ edma_write_slot(prtd->ram_link2, &p_ram);
+ /*
+ * When 1st started, ram -> iram dma channel will fill the
+ * entire iram. Then, whenever a ping/pong asp buffer finishes,
+ * 1/2 iram will be filled.
+ */
+ prtd->ram_params.link_bcntrld =
+ EDMA_CHAN_SLOT(prtd->ram_link2) << 5;
+ }
+ return 0;
+}
+
+/* 1 asp tx or rx channel using 2 parameter channels
+ * 1 ram to/from iram channel using 1 parameter channel
+ *
+ * Playback
+ * ram copy channel kicks off first,
+ * 1st ram copy of entire iram buffer completion kicks off asp channel
+ * asp tcc always kicks off ram copy of 1/2 iram buffer
+ *
+ * Record
+ * asp channel starts, tcc kicks off ram copy
+ */
+static int request_ping_pong(struct snd_pcm_substream *substream,
+ struct davinci_runtime_data *prtd,
+ struct snd_dma_buffer *iram_dma)
+{
+ dma_addr_t asp_src_ping;
+ dma_addr_t asp_dst_ping;
+ int link;
+ struct davinci_pcm_dma_params *params = prtd->params;
+
+ /* Request ram master channel */
+ link = prtd->ram_channel = edma_alloc_channel(EDMA_CHANNEL_ANY,
+ davinci_pcm_dma_irq, substream,
+ prtd->params->ram_chan_q);
+ if (link < 0)
+ goto exit1;
+
+ /* Request ram link channel */
+ link = prtd->ram_link = edma_alloc_slot(
+ EDMA_CTLR(prtd->ram_channel), EDMA_SLOT_ANY);
+ if (link < 0)
+ goto exit2;
+
+ link = prtd->asp_link[1] = edma_alloc_slot(
+ EDMA_CTLR(prtd->asp_channel), EDMA_SLOT_ANY);
+ if (link < 0)
+ goto exit3;
+
+ prtd->ram_link2 = -1;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ link = prtd->ram_link2 = edma_alloc_slot(
+ EDMA_CTLR(prtd->ram_channel), EDMA_SLOT_ANY);
+ if (link < 0)
+ goto exit4;
+ }
+ /* circle ping-pong buffers */
+ edma_link(prtd->asp_link[0], prtd->asp_link[1]);
+ edma_link(prtd->asp_link[1], prtd->asp_link[0]);
+ /* circle ram buffers */
+ edma_link(prtd->ram_link, prtd->ram_link);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ asp_src_ping = iram_dma->addr;
+ asp_dst_ping = params->dma_addr; /* fifo */
+ } else {
+ asp_src_ping = params->dma_addr; /* fifo */
+ asp_dst_ping = iram_dma->addr;
+ }
+ /* ping */
+ link = prtd->asp_link[0];
+ edma_set_src(link, asp_src_ping, INCR, W16BIT);
+ edma_set_dest(link, asp_dst_ping, INCR, W16BIT);
+ edma_set_src_index(link, 0, 0);
+ edma_set_dest_index(link, 0, 0);
+
+ edma_read_slot(link, &prtd->asp_params);
+ prtd->asp_params.opt &= ~(TCCMODE | EDMA_TCC(0x3f) | TCINTEN);
+ prtd->asp_params.opt |= TCCHEN | EDMA_TCC(prtd->ram_channel & 0x3f);
+ edma_write_slot(link, &prtd->asp_params);
+
+ /* pong */
+ link = prtd->asp_link[1];
+ edma_set_src(link, asp_src_ping, INCR, W16BIT);
+ edma_set_dest(link, asp_dst_ping, INCR, W16BIT);
+ edma_set_src_index(link, 0, 0);
+ edma_set_dest_index(link, 0, 0);
+
+ edma_read_slot(link, &prtd->asp_params);
+ prtd->asp_params.opt &= ~(TCCMODE | EDMA_TCC(0x3f));
+ /* interrupt after every pong completion */
+ prtd->asp_params.opt |= TCINTEN | TCCHEN |
+ EDMA_TCC(EDMA_CHAN_SLOT(prtd->ram_channel));
+ edma_write_slot(link, &prtd->asp_params);
+
+ /* ram */
+ link = prtd->ram_link;
+ edma_set_src(link, iram_dma->addr, INCR, W32BIT);
+ edma_set_dest(link, iram_dma->addr, INCR, W32BIT);
+ pr_debug("%s: audio dma channels/slots in use for ram:%u %u %u,"
+ "for asp:%u %u %u\n", __func__,
+ prtd->ram_channel, prtd->ram_link, prtd->ram_link2,
+ prtd->asp_channel, prtd->asp_link[0],
+ prtd->asp_link[1]);
+ return 0;
+exit4:
+ edma_free_channel(prtd->asp_link[1]);
+ prtd->asp_link[1] = -1;
+exit3:
+ edma_free_channel(prtd->ram_link);
+ prtd->ram_link = -1;
+exit2:
+ edma_free_channel(prtd->ram_channel);
+ prtd->ram_channel = -1;
+exit1:
+ return link;
+}
+
+static int davinci_pcm_dma_request(struct snd_pcm_substream *substream)
+{
+ struct snd_dma_buffer *iram_dma;
+ struct davinci_runtime_data *prtd = substream->runtime->private_data;
+ struct davinci_pcm_dma_params *params = prtd->params;
+ int link;
+
+ if (!params)
+ return -ENODEV;
+
+ /* Request asp master DMA channel */
+ link = prtd->asp_channel = edma_alloc_channel(params->channel,
+ davinci_pcm_dma_irq, substream,
+ prtd->params->asp_chan_q);
+ if (link < 0)
+ goto exit1;
+
+ /* Request asp link channels */
+ link = prtd->asp_link[0] = edma_alloc_slot(
+ EDMA_CTLR(prtd->asp_channel), EDMA_SLOT_ANY);
+ if (link < 0)
+ goto exit2;
+
+ iram_dma = (struct snd_dma_buffer *)substream->dma_buffer.private_data;
+ if (iram_dma) {
+ if (request_ping_pong(substream, prtd, iram_dma) == 0)
+ return 0;
+ printk(KERN_WARNING "%s: dma channel allocation failed,"
+ "not using sram\n", __func__);
+ }
+
+ /* Issue transfer completion IRQ when the channel completes a
+ * transfer, then always reload from the same slot (by a kind
+ * of loopback link). The completion IRQ handler will update
+ * the reload slot with a new buffer.
+ *
+ * REVISIT save p_ram here after setting up everything except
+ * the buffer and its length (ccnt) ... use it as a template
+ * so davinci_pcm_enqueue_dma() takes less time in IRQ.
+ */
+ edma_read_slot(link, &prtd->asp_params);
+ prtd->asp_params.opt |= TCINTEN |
+ EDMA_TCC(EDMA_CHAN_SLOT(prtd->asp_channel));
+ prtd->asp_params.link_bcntrld = EDMA_CHAN_SLOT(link) << 5;
+ edma_write_slot(link, &prtd->asp_params);
+ return 0;
+exit2:
+ edma_free_channel(prtd->asp_channel);
+ prtd->asp_channel = -1;
+exit1:
+ return link;
+}
+
+static int davinci_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct davinci_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ spin_lock(&prtd->lock);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ edma_resume(prtd->asp_channel);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ edma_pause(prtd->asp_channel);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock(&prtd->lock);
+
+ return ret;
+}
+
+static int davinci_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct davinci_runtime_data *prtd = substream->runtime->private_data;
+
+ if (prtd->ram_channel >= 0) {
+ int ret = ping_pong_dma_setup(substream);
+ if (ret < 0)
+ return ret;
+
+ edma_write_slot(prtd->ram_channel, &prtd->ram_params);
+ edma_write_slot(prtd->asp_channel, &prtd->asp_params);
+
+ print_buf_info(prtd->ram_channel, "ram_channel");
+ print_buf_info(prtd->ram_link, "ram_link");
+ print_buf_info(prtd->ram_link2, "ram_link2");
+ print_buf_info(prtd->asp_channel, "asp_channel");
+ print_buf_info(prtd->asp_link[0], "asp_link[0]");
+ print_buf_info(prtd->asp_link[1], "asp_link[1]");
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* copy 1st iram buffer */
+ edma_start(prtd->ram_channel);
+ }
+ edma_start(prtd->asp_channel);
+ return 0;
+ }
+ prtd->period = 0;
+ davinci_pcm_enqueue_dma(substream);
+
+ /* Copy self-linked parameter RAM entry into master channel */
+ edma_read_slot(prtd->asp_link[0], &prtd->asp_params);
+ edma_write_slot(prtd->asp_channel, &prtd->asp_params);
+ davinci_pcm_enqueue_dma(substream);
+ edma_start(prtd->asp_channel);
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+davinci_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct davinci_runtime_data *prtd = runtime->private_data;
+ unsigned int offset;
+ int asp_count;
+ dma_addr_t asp_src, asp_dst;
+
+ spin_lock(&prtd->lock);
+ if (prtd->ram_channel >= 0) {
+ int ram_count;
+ int mod_ram;
+ dma_addr_t ram_src, ram_dst;
+ unsigned int period_size = snd_pcm_lib_period_bytes(substream);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* reading ram before asp should be safe
+ * as long as the asp transfers less than a ping size
+ * of bytes between the 2 reads
+ */
+ edma_get_position(prtd->ram_channel,
+ &ram_src, &ram_dst);
+ edma_get_position(prtd->asp_channel,
+ &asp_src, &asp_dst);
+ asp_count = asp_src - prtd->asp_params.src;
+ ram_count = ram_src - prtd->ram_params.src;
+ mod_ram = ram_count % period_size;
+ mod_ram -= asp_count;
+ if (mod_ram < 0)
+ mod_ram += period_size;
+ else if (mod_ram == 0) {
+ if (snd_pcm_running(substream))
+ mod_ram += period_size;
+ }
+ ram_count -= mod_ram;
+ if (ram_count < 0)
+ ram_count += period_size * runtime->periods;
+ } else {
+ edma_get_position(prtd->ram_channel,
+ &ram_src, &ram_dst);
+ ram_count = ram_dst - prtd->ram_params.dst;
+ }
+ asp_count = ram_count;
+ } else {
+ edma_get_position(prtd->asp_channel, &asp_src, &asp_dst);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ asp_count = asp_src - runtime->dma_addr;
+ else
+ asp_count = asp_dst - runtime->dma_addr;
+ }
+ spin_unlock(&prtd->lock);
+
+ offset = bytes_to_frames(runtime, asp_count);
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int davinci_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct davinci_runtime_data *prtd;
+ struct snd_pcm_hardware *ppcm;
+ int ret = 0;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct davinci_pcm_dma_params *pa;
+ struct davinci_pcm_dma_params *params;
+
+ pa = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ if (!pa)
+ return -ENODEV;
+ params = &pa[substream->stream];
+
+ ppcm = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ &pcm_hardware_playback : &pcm_hardware_capture;
+ allocate_sram(substream, params->sram_size, ppcm);
+ snd_soc_set_runtime_hwparams(substream, ppcm);
+ /* ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ prtd = kzalloc(sizeof(struct davinci_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&prtd->lock);
+ prtd->params = params;
+ prtd->asp_channel = -1;
+ prtd->asp_link[0] = prtd->asp_link[1] = -1;
+ prtd->ram_channel = -1;
+ prtd->ram_link = -1;
+ prtd->ram_link2 = -1;
+
+ runtime->private_data = prtd;
+
+ ret = davinci_pcm_dma_request(substream);
+ if (ret) {
+ printk(KERN_ERR "davinci_pcm: Failed to get dma channels\n");
+ kfree(prtd);
+ }
+
+ return ret;
+}
+
+static int davinci_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct davinci_runtime_data *prtd = runtime->private_data;
+
+ if (prtd->ram_channel >= 0)
+ edma_stop(prtd->ram_channel);
+ if (prtd->asp_channel >= 0)
+ edma_stop(prtd->asp_channel);
+ if (prtd->asp_link[0] >= 0)
+ edma_unlink(prtd->asp_link[0]);
+ if (prtd->asp_link[1] >= 0)
+ edma_unlink(prtd->asp_link[1]);
+ if (prtd->ram_link >= 0)
+ edma_unlink(prtd->ram_link);
+
+ if (prtd->asp_link[0] >= 0)
+ edma_free_slot(prtd->asp_link[0]);
+ if (prtd->asp_link[1] >= 0)
+ edma_free_slot(prtd->asp_link[1]);
+ if (prtd->asp_channel >= 0)
+ edma_free_channel(prtd->asp_channel);
+ if (prtd->ram_link >= 0)
+ edma_free_slot(prtd->ram_link);
+ if (prtd->ram_link2 >= 0)
+ edma_free_slot(prtd->ram_link2);
+ if (prtd->ram_channel >= 0)
+ edma_free_channel(prtd->ram_channel);
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static int davinci_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int davinci_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int davinci_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops davinci_pcm_ops = {
+ .open = davinci_pcm_open,
+ .close = davinci_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = davinci_pcm_hw_params,
+ .hw_free = davinci_pcm_hw_free,
+ .prepare = davinci_pcm_prepare,
+ .trigger = davinci_pcm_trigger,
+ .pointer = davinci_pcm_pointer,
+ .mmap = davinci_pcm_mmap,
+};
+
+static int davinci_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream,
+ size_t size)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+
+ pr_debug("davinci_pcm: preallocate_dma_buffer: area=%p, addr=%p, "
+ "size=%d\n", (void *) buf->area, (void *) buf->addr, size);
+
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+ return 0;
+}
+
+static void davinci_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ struct snd_dma_buffer *iram_dma;
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ iram_dma = buf->private_data;
+ if (iram_dma) {
+ sram_free(iram_dma->area, iram_dma->bytes);
+ kfree(iram_dma);
+ }
+ }
+}
+
+static u64 davinci_pcm_dmamask = 0xffffffff;
+
+static int davinci_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &davinci_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->driver->playback.channels_min) {
+ ret = davinci_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK,
+ pcm_hardware_playback.buffer_bytes_max);
+ if (ret)
+ return ret;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = davinci_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE,
+ pcm_hardware_capture.buffer_bytes_max);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_platform_driver davinci_soc_platform = {
+ .ops = &davinci_pcm_ops,
+ .pcm_new = davinci_pcm_new,
+ .pcm_free = davinci_pcm_free,
+};
+
+static int __devinit davinci_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &davinci_soc_platform);
+}
+
+static int __devexit davinci_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver davinci_pcm_driver = {
+ .driver = {
+ .name = "davinci-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = davinci_soc_platform_probe,
+ .remove = __devexit_p(davinci_soc_platform_remove),
+};
+
+static int __init snd_davinci_pcm_init(void)
+{
+ return platform_driver_register(&davinci_pcm_driver);
+}
+module_init(snd_davinci_pcm_init);
+
+static void __exit snd_davinci_pcm_exit(void)
+{
+ platform_driver_unregister(&davinci_pcm_driver);
+}
+module_exit(snd_davinci_pcm_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("TI DAVINCI PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h
new file mode 100644
index 00000000..c0d6c9be
--- /dev/null
+++ b/sound/soc/davinci/davinci-pcm.h
@@ -0,0 +1,31 @@
+/*
+ * ALSA PCM interface for the TI DAVINCI processor
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _DAVINCI_PCM_H
+#define _DAVINCI_PCM_H
+
+#include <mach/edma.h>
+#include <mach/asp.h>
+
+
+struct davinci_pcm_dma_params {
+ int channel; /* sync dma channel ID */
+ unsigned short acnt;
+ dma_addr_t dma_addr; /* device physical address for DMA */
+ unsigned sram_size;
+ enum dma_event_q asp_chan_q; /* event queue number for ASP channel */
+ enum dma_event_q ram_chan_q; /* event queue number for RAM channel */
+ unsigned char data_type; /* xfer data type */
+ unsigned char convert_mono_stereo;
+ unsigned int fifo_level;
+};
+
+#endif
diff --git a/sound/soc/davinci/davinci-sffsdr.c b/sound/soc/davinci/davinci-sffsdr.c
new file mode 100644
index 00000000..0fe558c6
--- /dev/null
+++ b/sound/soc/davinci/davinci-sffsdr.c
@@ -0,0 +1,180 @@
+/*
+ * ASoC driver for Lyrtech SFFSDR board.
+ *
+ * Author: Hugo Villeneuve
+ * Copyright (C) 2008 Lyrtech inc
+ *
+ * Based on ASoC driver for TI DAVINCI EVM platform, original copyright follow:
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <asm/mach-types.h>
+#ifdef CONFIG_SFFSDR_FPGA
+#include <asm/plat-sffsdr/sffsdr-fpga.h>
+#endif
+
+#include <mach/edma.h>
+
+#include "../codecs/pcm3008.h"
+#include "davinci-pcm.h"
+#include "davinci-i2s.h"
+
+/*
+ * CLKX and CLKR are the inputs for the Sample Rate Generator.
+ * FSX and FSR are outputs, driven by the sample Rate Generator.
+ */
+#define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | \
+ SND_SOC_DAIFMT_CBM_CFS | \
+ SND_SOC_DAIFMT_IB_NF)
+
+static int sffsdr_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int fs;
+ int ret = 0;
+
+ /* Fsref can be 32000, 44100 or 48000. */
+ fs = params_rate(params);
+
+#ifndef CONFIG_SFFSDR_FPGA
+ /* Without the FPGA module, the Fs is fixed at 44100 Hz */
+ if (fs != 44100) {
+ pr_debug("warning: only 44.1 kHz is supported without SFFSDR FPGA module\n");
+ return -EINVAL;
+ }
+#endif
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT);
+ if (ret < 0)
+ return ret;
+
+ pr_debug("sffsdr_hw_params: rate = %d Hz\n", fs);
+
+#ifndef CONFIG_SFFSDR_FPGA
+ return 0;
+#else
+ return sffsdr_fpga_set_codec_fs(fs);
+#endif
+}
+
+static struct snd_soc_ops sffsdr_ops = {
+ .hw_params = sffsdr_hw_params,
+};
+
+/* davinci-sffsdr digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link sffsdr_dai = {
+ .name = "PCM3008", /* Codec name */
+ .stream_name = "PCM3008 HiFi",
+ .cpu_dai_name = "davinci-mcbsp",
+ .codec_dai_name = "pcm3008-hifi",
+ .codec_name = "pcm3008-codec",
+ .platform_name = "davinci-pcm-audio",
+ .ops = &sffsdr_ops,
+};
+
+/* davinci-sffsdr audio machine driver */
+static struct snd_soc_card snd_soc_sffsdr = {
+ .name = "DaVinci SFFSDR",
+ .dai_link = &sffsdr_dai,
+ .num_links = 1,
+};
+
+/* sffsdr audio private data */
+static struct pcm3008_setup_data sffsdr_pcm3008_setup = {
+ .dem0_pin = GPIO(45),
+ .dem1_pin = GPIO(46),
+ .pdad_pin = GPIO(47),
+ .pdda_pin = GPIO(38),
+};
+
+struct platform_device pcm3008_codec = {
+ .name = "pcm3008-codec",
+ .id = 0,
+ .dev = {
+ .platform_data = &sffsdr_pcm3008_setup,
+ },
+};
+
+static struct resource sffsdr_snd_resources[] = {
+ {
+ .start = DAVINCI_MCBSP_BASE,
+ .end = DAVINCI_MCBSP_BASE + SZ_8K - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct evm_snd_platform_data sffsdr_snd_data = {
+ .tx_dma_ch = DAVINCI_DMA_MCBSP_TX,
+ .rx_dma_ch = DAVINCI_DMA_MCBSP_RX,
+};
+
+static struct platform_device *sffsdr_snd_device;
+
+static int __init sffsdr_init(void)
+{
+ int ret;
+
+ if (!machine_is_sffsdr())
+ return -EINVAL;
+
+ platform_device_register(&pcm3008_codec);
+
+ sffsdr_snd_device = platform_device_alloc("soc-audio", 0);
+ if (!sffsdr_snd_device) {
+ printk(KERN_ERR "platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(sffsdr_snd_device, &snd_soc_sffsdr);
+ platform_device_add_data(sffsdr_snd_device, &sffsdr_snd_data,
+ sizeof(sffsdr_snd_data));
+
+ ret = platform_device_add_resources(sffsdr_snd_device,
+ sffsdr_snd_resources,
+ ARRAY_SIZE(sffsdr_snd_resources));
+ if (ret) {
+ printk(KERN_ERR "platform device add resources failed\n");
+ goto error;
+ }
+
+ ret = platform_device_add(sffsdr_snd_device);
+ if (ret)
+ goto error;
+
+ return ret;
+
+error:
+ platform_device_put(sffsdr_snd_device);
+ return ret;
+}
+
+static void __exit sffsdr_exit(void)
+{
+ platform_device_unregister(sffsdr_snd_device);
+ platform_device_unregister(&pcm3008_codec);
+}
+
+module_init(sffsdr_init);
+module_exit(sffsdr_exit);
+
+MODULE_AUTHOR("Hugo Villeneuve");
+MODULE_DESCRIPTION("Lyrtech SFFSDR ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/davinci/davinci-vcif.c b/sound/soc/davinci/davinci-vcif.c
new file mode 100644
index 00000000..1f11525d
--- /dev/null
+++ b/sound/soc/davinci/davinci-vcif.c
@@ -0,0 +1,282 @@
+/*
+ * ALSA SoC Voice Codec Interface for TI DAVINCI processor
+ *
+ * Copyright (C) 2010 Texas Instruments.
+ *
+ * Author: Miguel Aguilar <miguel.aguilar@ridgerun.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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/mfd/davinci_voicecodec.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "davinci-pcm.h"
+#include "davinci-i2s.h"
+
+#define MOD_REG_BIT(val, mask, set) do { \
+ if (set) { \
+ val |= mask; \
+ } else { \
+ val &= ~mask; \
+ } \
+} while (0)
+
+struct davinci_vcif_dev {
+ struct davinci_vc *davinci_vc;
+ struct davinci_pcm_dma_params dma_params[2];
+};
+
+static void davinci_vcif_start(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct davinci_vcif_dev *davinci_vcif_dev =
+ snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc;
+ u32 w;
+
+ /* Start the sample generator and enable transmitter/receiver */
+ w = readl(davinci_vc->base + DAVINCI_VC_CTRL);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTDAC, 0);
+ else
+ MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTADC, 0);
+
+ writel(w, davinci_vc->base + DAVINCI_VC_CTRL);
+}
+
+static void davinci_vcif_stop(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct davinci_vcif_dev *davinci_vcif_dev =
+ snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc;
+ u32 w;
+
+ /* Reset transmitter/receiver and sample rate/frame sync generators */
+ w = readl(davinci_vc->base + DAVINCI_VC_CTRL);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTDAC, 1);
+ else
+ MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTADC, 1);
+
+ writel(w, davinci_vc->base + DAVINCI_VC_CTRL);
+}
+
+static int davinci_vcif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct davinci_vcif_dev *davinci_vcif_dev = snd_soc_dai_get_drvdata(dai);
+ struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc;
+ struct davinci_pcm_dma_params *dma_params =
+ &davinci_vcif_dev->dma_params[substream->stream];
+ u32 w;
+
+ /* Restart the codec before setup */
+ davinci_vcif_stop(substream);
+ davinci_vcif_start(substream);
+
+ /* General line settings */
+ writel(DAVINCI_VC_CTRL_MASK, davinci_vc->base + DAVINCI_VC_CTRL);
+
+ writel(DAVINCI_VC_INT_MASK, davinci_vc->base + DAVINCI_VC_INTCLR);
+
+ writel(DAVINCI_VC_INT_MASK, davinci_vc->base + DAVINCI_VC_INTEN);
+
+ w = readl(davinci_vc->base + DAVINCI_VC_CTRL);
+
+ /* Determine xfer data type */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_U8:
+ dma_params->data_type = 0;
+
+ MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 |
+ DAVINCI_VC_CTRL_RD_UNSIGNED |
+ DAVINCI_VC_CTRL_WD_BITS_8 |
+ DAVINCI_VC_CTRL_WD_UNSIGNED, 1);
+ break;
+ case SNDRV_PCM_FORMAT_S8:
+ dma_params->data_type = 1;
+
+ MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 |
+ DAVINCI_VC_CTRL_WD_BITS_8, 1);
+
+ MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_UNSIGNED |
+ DAVINCI_VC_CTRL_WD_UNSIGNED, 0);
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dma_params->data_type = 2;
+
+ MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 |
+ DAVINCI_VC_CTRL_RD_UNSIGNED |
+ DAVINCI_VC_CTRL_WD_BITS_8 |
+ DAVINCI_VC_CTRL_WD_UNSIGNED, 0);
+ break;
+ default:
+ printk(KERN_WARNING "davinci-vcif: unsupported PCM format");
+ return -EINVAL;
+ }
+
+ dma_params->acnt = dma_params->data_type;
+
+ writel(w, davinci_vc->base + DAVINCI_VC_CTRL);
+
+ return 0;
+}
+
+static int davinci_vcif_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ davinci_vcif_start(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ davinci_vcif_stop(substream);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int davinci_vcif_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct davinci_vcif_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_set_dma_data(dai, substream, dev->dma_params);
+ return 0;
+}
+
+#define DAVINCI_VCIF_RATES SNDRV_PCM_RATE_8000_48000
+
+static struct snd_soc_dai_ops davinci_vcif_dai_ops = {
+ .startup = davinci_vcif_startup,
+ .trigger = davinci_vcif_trigger,
+ .hw_params = davinci_vcif_hw_params,
+};
+
+static struct snd_soc_dai_driver davinci_vcif_dai = {
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = DAVINCI_VCIF_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = DAVINCI_VCIF_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &davinci_vcif_dai_ops,
+
+};
+
+static int davinci_vcif_probe(struct platform_device *pdev)
+{
+ struct davinci_vc *davinci_vc = pdev->dev.platform_data;
+ struct davinci_vcif_dev *davinci_vcif_dev;
+ int ret;
+
+ davinci_vcif_dev = kzalloc(sizeof(struct davinci_vcif_dev), GFP_KERNEL);
+ if (!davinci_vcif_dev) {
+ dev_dbg(&pdev->dev,
+ "could not allocate memory for private data\n");
+ return -ENOMEM;
+ }
+
+ /* DMA tx params */
+ davinci_vcif_dev->davinci_vc = davinci_vc;
+ davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].channel =
+ davinci_vc->davinci_vcif.dma_tx_channel;
+ davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].dma_addr =
+ davinci_vc->davinci_vcif.dma_tx_addr;
+
+ /* DMA rx params */
+ davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel =
+ davinci_vc->davinci_vcif.dma_rx_channel;
+ davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].dma_addr =
+ davinci_vc->davinci_vcif.dma_rx_addr;
+
+ dev_set_drvdata(&pdev->dev, davinci_vcif_dev);
+
+ ret = snd_soc_register_dai(&pdev->dev, &davinci_vcif_dai);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "could not register dai\n");
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ kfree(davinci_vcif_dev);
+
+ return ret;
+}
+
+static int davinci_vcif_remove(struct platform_device *pdev)
+{
+ struct davinci_vcif_dev *davinci_vcif_dev = dev_get_drvdata(&pdev->dev);
+
+ snd_soc_unregister_dai(&pdev->dev);
+ kfree(davinci_vcif_dev);
+
+ return 0;
+}
+
+static struct platform_driver davinci_vcif_driver = {
+ .probe = davinci_vcif_probe,
+ .remove = davinci_vcif_remove,
+ .driver = {
+ .name = "davinci-vcif",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init davinci_vcif_init(void)
+{
+ return platform_driver_probe(&davinci_vcif_driver, davinci_vcif_probe);
+}
+module_init(davinci_vcif_init);
+
+static void __exit davinci_vcif_exit(void)
+{
+ platform_driver_unregister(&davinci_vcif_driver);
+}
+module_exit(davinci_vcif_exit);
+
+MODULE_AUTHOR("Miguel Aguilar");
+MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC Voice Codec Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ep93xx/Kconfig b/sound/soc/ep93xx/Kconfig
new file mode 100644
index 00000000..91a28de9
--- /dev/null
+++ b/sound/soc/ep93xx/Kconfig
@@ -0,0 +1,41 @@
+config SND_EP93XX_SOC
+ tristate "SoC Audio support for the Cirrus Logic EP93xx series"
+ depends on ARCH_EP93XX && SND_SOC
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the EP93xx I2S or AC97 interfaces.
+
+config SND_EP93XX_SOC_I2S
+ tristate
+
+config SND_EP93XX_SOC_AC97
+ tristate
+ select AC97_BUS
+ select SND_SOC_AC97_BUS
+
+config SND_EP93XX_SOC_SNAPPERCL15
+ tristate "SoC Audio support for Bluewater Systems Snapper CL15 module"
+ depends on SND_EP93XX_SOC && MACH_SNAPPER_CL15
+ select SND_EP93XX_SOC_I2S
+ select SND_SOC_TLV320AIC23
+ help
+ Say Y or M here if you want to add support for I2S audio on the
+ Bluewater Systems Snapper CL15 module.
+
+config SND_EP93XX_SOC_SIMONE
+ tristate "SoC Audio support for Simplemachines Sim.One board"
+ depends on SND_EP93XX_SOC && MACH_SIM_ONE
+ select SND_EP93XX_SOC_AC97
+ select SND_SOC_AC97_CODEC
+ help
+ Say Y or M here if you want to add support for AC97 audio on the
+ Simplemachines Sim.One board.
+
+config SND_EP93XX_SOC_EDB93XX
+ tristate "SoC Audio support for Cirrus Logic EDB93xx boards"
+ depends on SND_EP93XX_SOC && (MACH_EDB9301 || MACH_EDB9302 || MACH_EDB9302A || MACH_EDB9307A || MACH_EDB9315A)
+ select SND_EP93XX_SOC_I2S
+ select SND_SOC_CS4271
+ help
+ Say Y or M here if you want to add support for I2S audio on the
+ Cirrus Logic EDB93xx boards.
diff --git a/sound/soc/ep93xx/Makefile b/sound/soc/ep93xx/Makefile
new file mode 100644
index 00000000..5514146c
--- /dev/null
+++ b/sound/soc/ep93xx/Makefile
@@ -0,0 +1,17 @@
+# EP93xx Platform Support
+snd-soc-ep93xx-objs := ep93xx-pcm.o
+snd-soc-ep93xx-i2s-objs := ep93xx-i2s.o
+snd-soc-ep93xx-ac97-objs := ep93xx-ac97.o
+
+obj-$(CONFIG_SND_EP93XX_SOC) += snd-soc-ep93xx.o
+obj-$(CONFIG_SND_EP93XX_SOC_I2S) += snd-soc-ep93xx-i2s.o
+obj-$(CONFIG_SND_EP93XX_SOC_AC97) += snd-soc-ep93xx-ac97.o
+
+# EP93XX Machine Support
+snd-soc-snappercl15-objs := snappercl15.o
+snd-soc-simone-objs := simone.o
+snd-soc-edb93xx-objs := edb93xx.o
+
+obj-$(CONFIG_SND_EP93XX_SOC_SNAPPERCL15) += snd-soc-snappercl15.o
+obj-$(CONFIG_SND_EP93XX_SOC_SIMONE) += snd-soc-simone.o
+obj-$(CONFIG_SND_EP93XX_SOC_EDB93XX) += snd-soc-edb93xx.o
diff --git a/sound/soc/ep93xx/edb93xx.c b/sound/soc/ep93xx/edb93xx.c
new file mode 100644
index 00000000..d3aa1511
--- /dev/null
+++ b/sound/soc/ep93xx/edb93xx.c
@@ -0,0 +1,142 @@
+/*
+ * SoC audio for EDB93xx
+ *
+ * Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru>
+ *
+ * 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
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * This driver support CS4271 codec being master or slave, working
+ * in control port mode, connected either via SPI or I2C.
+ * The data format accepted is I2S or left-justified.
+ * DAPM support not implemented.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include "ep93xx-pcm.h"
+
+#define edb93xx_has_audio() (machine_is_edb9301() || \
+ machine_is_edb9302() || \
+ machine_is_edb9302a() || \
+ machine_is_edb9307a() || \
+ machine_is_edb9315a())
+
+static int edb93xx_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int err;
+ unsigned int mclk_rate;
+ unsigned int rate = params_rate(params);
+
+ /*
+ * According to CS4271 datasheet we use MCLK/LRCK=256 for
+ * rates below 50kHz and 128 for higher sample rates
+ */
+ if (rate < 50000)
+ mclk_rate = rate * 64 * 4;
+ else
+ mclk_rate = rate * 64 * 2;
+
+ err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err)
+ return err;
+
+ err = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err)
+ return err;
+
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate,
+ SND_SOC_CLOCK_IN);
+ if (err)
+ return err;
+
+ return snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate,
+ SND_SOC_CLOCK_OUT);
+}
+
+static struct snd_soc_ops edb93xx_ops = {
+ .hw_params = edb93xx_hw_params,
+};
+
+static struct snd_soc_dai_link edb93xx_dai = {
+ .name = "CS4271",
+ .stream_name = "CS4271 HiFi",
+ .platform_name = "ep93xx-pcm-audio",
+ .cpu_dai_name = "ep93xx-i2s",
+ .codec_name = "spi0.0",
+ .codec_dai_name = "cs4271-hifi",
+ .ops = &edb93xx_ops,
+};
+
+static struct snd_soc_card snd_soc_edb93xx = {
+ .name = "EDB93XX",
+ .dai_link = &edb93xx_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *edb93xx_snd_device;
+
+static int __init edb93xx_init(void)
+{
+ int ret;
+
+ if (!edb93xx_has_audio())
+ return -ENODEV;
+
+ ret = ep93xx_i2s_acquire(EP93XX_SYSCON_DEVCFG_I2SONAC97,
+ EP93XX_SYSCON_I2SCLKDIV_ORIDE |
+ EP93XX_SYSCON_I2SCLKDIV_SPOL);
+ if (ret)
+ return ret;
+
+ edb93xx_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!edb93xx_snd_device) {
+ ret = -ENOMEM;
+ goto free_i2s;
+ }
+
+ platform_set_drvdata(edb93xx_snd_device, &snd_soc_edb93xx);
+ ret = platform_device_add(edb93xx_snd_device);
+ if (ret)
+ goto device_put;
+
+ return 0;
+
+device_put:
+ platform_device_put(edb93xx_snd_device);
+free_i2s:
+ ep93xx_i2s_release();
+ return ret;
+}
+module_init(edb93xx_init);
+
+static void __exit edb93xx_exit(void)
+{
+ platform_device_unregister(edb93xx_snd_device);
+ ep93xx_i2s_release();
+}
+module_exit(edb93xx_exit);
+
+MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>");
+MODULE_DESCRIPTION("ALSA SoC EDB93xx");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ep93xx/ep93xx-ac97.c b/sound/soc/ep93xx/ep93xx-ac97.c
new file mode 100644
index 00000000..104e95cd
--- /dev/null
+++ b/sound/soc/ep93xx/ep93xx-ac97.c
@@ -0,0 +1,467 @@
+/*
+ * ASoC driver for Cirrus Logic EP93xx AC97 controller.
+ *
+ * Copyright (c) 2010 Mika Westerberg
+ *
+ * Based on s3c-ac97 ASoC driver by Jaswinder Singh.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include "ep93xx-pcm.h"
+
+/*
+ * Per channel (1-4) registers.
+ */
+#define AC97CH(n) (((n) - 1) * 0x20)
+
+#define AC97DR(n) (AC97CH(n) + 0x0000)
+
+#define AC97RXCR(n) (AC97CH(n) + 0x0004)
+#define AC97RXCR_REN BIT(0)
+#define AC97RXCR_RX3 BIT(3)
+#define AC97RXCR_RX4 BIT(4)
+#define AC97RXCR_CM BIT(15)
+
+#define AC97TXCR(n) (AC97CH(n) + 0x0008)
+#define AC97TXCR_TEN BIT(0)
+#define AC97TXCR_TX3 BIT(3)
+#define AC97TXCR_TX4 BIT(4)
+#define AC97TXCR_CM BIT(15)
+
+#define AC97SR(n) (AC97CH(n) + 0x000c)
+#define AC97SR_TXFE BIT(1)
+#define AC97SR_TXUE BIT(6)
+
+#define AC97RISR(n) (AC97CH(n) + 0x0010)
+#define AC97ISR(n) (AC97CH(n) + 0x0014)
+#define AC97IE(n) (AC97CH(n) + 0x0018)
+
+/*
+ * Global AC97 controller registers.
+ */
+#define AC97S1DATA 0x0080
+#define AC97S2DATA 0x0084
+#define AC97S12DATA 0x0088
+
+#define AC97RGIS 0x008c
+#define AC97GIS 0x0090
+#define AC97IM 0x0094
+/*
+ * Common bits for RGIS, GIS and IM registers.
+ */
+#define AC97_SLOT2RXVALID BIT(1)
+#define AC97_CODECREADY BIT(5)
+#define AC97_SLOT2TXCOMPLETE BIT(6)
+
+#define AC97EOI 0x0098
+#define AC97EOI_WINT BIT(0)
+#define AC97EOI_CODECREADY BIT(1)
+
+#define AC97GCR 0x009c
+#define AC97GCR_AC97IFE BIT(0)
+
+#define AC97RESET 0x00a0
+#define AC97RESET_TIMEDRESET BIT(0)
+
+#define AC97SYNC 0x00a4
+#define AC97SYNC_TIMEDSYNC BIT(0)
+
+#define AC97_TIMEOUT msecs_to_jiffies(5)
+
+/**
+ * struct ep93xx_ac97_info - EP93xx AC97 controller info structure
+ * @lock: mutex serializing access to the bus (slot 1 & 2 ops)
+ * @dev: pointer to the platform device dev structure
+ * @mem: physical memory resource for the registers
+ * @regs: mapped AC97 controller registers
+ * @irq: AC97 interrupt number
+ * @done: bus ops wait here for an interrupt
+ */
+struct ep93xx_ac97_info {
+ struct mutex lock;
+ struct device *dev;
+ struct resource *mem;
+ void __iomem *regs;
+ int irq;
+ struct completion done;
+};
+
+/* currently ALSA only supports a single AC97 device */
+static struct ep93xx_ac97_info *ep93xx_ac97_info;
+
+static struct ep93xx_pcm_dma_params ep93xx_ac97_pcm_out = {
+ .name = "ac97-pcm-out",
+ .dma_port = EP93XX_DMA_M2P_PORT_AAC1,
+};
+
+static struct ep93xx_pcm_dma_params ep93xx_ac97_pcm_in = {
+ .name = "ac97-pcm-in",
+ .dma_port = EP93XX_DMA_M2P_PORT_AAC1,
+};
+
+static inline unsigned ep93xx_ac97_read_reg(struct ep93xx_ac97_info *info,
+ unsigned reg)
+{
+ return __raw_readl(info->regs + reg);
+}
+
+static inline void ep93xx_ac97_write_reg(struct ep93xx_ac97_info *info,
+ unsigned reg, unsigned val)
+{
+ __raw_writel(val, info->regs + reg);
+}
+
+static unsigned short ep93xx_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ struct ep93xx_ac97_info *info = ep93xx_ac97_info;
+ unsigned short val;
+
+ mutex_lock(&info->lock);
+
+ ep93xx_ac97_write_reg(info, AC97S1DATA, reg);
+ ep93xx_ac97_write_reg(info, AC97IM, AC97_SLOT2RXVALID);
+ if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) {
+ dev_warn(info->dev, "timeout reading register %x\n", reg);
+ mutex_unlock(&info->lock);
+ return -ETIMEDOUT;
+ }
+ val = (unsigned short)ep93xx_ac97_read_reg(info, AC97S2DATA);
+
+ mutex_unlock(&info->lock);
+ return val;
+}
+
+static void ep93xx_ac97_write(struct snd_ac97 *ac97,
+ unsigned short reg,
+ unsigned short val)
+{
+ struct ep93xx_ac97_info *info = ep93xx_ac97_info;
+
+ mutex_lock(&info->lock);
+
+ /*
+ * Writes to the codec need to be done so that slot 2 is filled in
+ * before slot 1.
+ */
+ ep93xx_ac97_write_reg(info, AC97S2DATA, val);
+ ep93xx_ac97_write_reg(info, AC97S1DATA, reg);
+
+ ep93xx_ac97_write_reg(info, AC97IM, AC97_SLOT2TXCOMPLETE);
+ if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT))
+ dev_warn(info->dev, "timeout writing register %x\n", reg);
+
+ mutex_unlock(&info->lock);
+}
+
+static void ep93xx_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ struct ep93xx_ac97_info *info = ep93xx_ac97_info;
+
+ mutex_lock(&info->lock);
+
+ /*
+ * We are assuming that before this functions gets called, the codec
+ * BIT_CLK is stopped by forcing the codec into powerdown mode. We can
+ * control the SYNC signal directly via AC97SYNC register. Using
+ * TIMEDSYNC the controller will keep the SYNC high > 1us.
+ */
+ ep93xx_ac97_write_reg(info, AC97SYNC, AC97SYNC_TIMEDSYNC);
+ ep93xx_ac97_write_reg(info, AC97IM, AC97_CODECREADY);
+ if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT))
+ dev_warn(info->dev, "codec warm reset timeout\n");
+
+ mutex_unlock(&info->lock);
+}
+
+static void ep93xx_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ struct ep93xx_ac97_info *info = ep93xx_ac97_info;
+
+ mutex_lock(&info->lock);
+
+ /*
+ * For doing cold reset, we disable the AC97 controller interface, clear
+ * WINT and CODECREADY bits, and finally enable the interface again.
+ */
+ ep93xx_ac97_write_reg(info, AC97GCR, 0);
+ ep93xx_ac97_write_reg(info, AC97EOI, AC97EOI_CODECREADY | AC97EOI_WINT);
+ ep93xx_ac97_write_reg(info, AC97GCR, AC97GCR_AC97IFE);
+
+ /*
+ * Now, assert the reset and wait for the codec to become ready.
+ */
+ ep93xx_ac97_write_reg(info, AC97RESET, AC97RESET_TIMEDRESET);
+ ep93xx_ac97_write_reg(info, AC97IM, AC97_CODECREADY);
+ if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT))
+ dev_warn(info->dev, "codec cold reset timeout\n");
+
+ /*
+ * Give the codec some time to come fully out from the reset. This way
+ * we ensure that the subsequent reads/writes will work.
+ */
+ usleep_range(15000, 20000);
+
+ mutex_unlock(&info->lock);
+}
+
+static irqreturn_t ep93xx_ac97_interrupt(int irq, void *dev_id)
+{
+ struct ep93xx_ac97_info *info = dev_id;
+ unsigned status, mask;
+
+ /*
+ * Just mask out the interrupt and wake up the waiting thread.
+ * Interrupts are cleared via reading/writing to slot 1 & 2 registers by
+ * the waiting thread.
+ */
+ status = ep93xx_ac97_read_reg(info, AC97GIS);
+ mask = ep93xx_ac97_read_reg(info, AC97IM);
+ mask &= ~status;
+ ep93xx_ac97_write_reg(info, AC97IM, mask);
+
+ complete(&info->done);
+ return IRQ_HANDLED;
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = ep93xx_ac97_read,
+ .write = ep93xx_ac97_write,
+ .reset = ep93xx_ac97_cold_reset,
+ .warm_reset = ep93xx_ac97_warm_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int ep93xx_ac97_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct ep93xx_ac97_info *info = snd_soc_dai_get_drvdata(dai);
+ unsigned v = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /*
+ * Enable compact mode, TX slots 3 & 4, and the TX FIFO
+ * itself.
+ */
+ v |= AC97TXCR_CM;
+ v |= AC97TXCR_TX3 | AC97TXCR_TX4;
+ v |= AC97TXCR_TEN;
+ ep93xx_ac97_write_reg(info, AC97TXCR(1), v);
+ } else {
+ /*
+ * Enable compact mode, RX slots 3 & 4, and the RX FIFO
+ * itself.
+ */
+ v |= AC97RXCR_CM;
+ v |= AC97RXCR_RX3 | AC97RXCR_RX4;
+ v |= AC97RXCR_REN;
+ ep93xx_ac97_write_reg(info, AC97RXCR(1), v);
+ }
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /*
+ * As per Cirrus EP93xx errata described below:
+ *
+ * http://www.cirrus.com/en/pubs/errata/ER667E2B.pdf
+ *
+ * we will wait for the TX FIFO to be empty before
+ * clearing the TEN bit.
+ */
+ unsigned long timeout = jiffies + AC97_TIMEOUT;
+
+ do {
+ v = ep93xx_ac97_read_reg(info, AC97SR(1));
+ if (time_after(jiffies, timeout)) {
+ dev_warn(info->dev, "TX timeout\n");
+ break;
+ }
+ } while (!(v & (AC97SR_TXFE | AC97SR_TXUE)));
+
+ /* disable the TX FIFO */
+ ep93xx_ac97_write_reg(info, AC97TXCR(1), 0);
+ } else {
+ /* disable the RX FIFO */
+ ep93xx_ac97_write_reg(info, AC97RXCR(1), 0);
+ }
+ break;
+
+ default:
+ dev_warn(info->dev, "unknown command %d\n", cmd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ep93xx_ac97_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct ep93xx_pcm_dma_params *dma_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &ep93xx_ac97_pcm_out;
+ else
+ dma_data = &ep93xx_ac97_pcm_in;
+
+ snd_soc_dai_set_dma_data(dai, substream, dma_data);
+ return 0;
+}
+
+static struct snd_soc_dai_ops ep93xx_ac97_dai_ops = {
+ .startup = ep93xx_ac97_startup,
+ .trigger = ep93xx_ac97_trigger,
+};
+
+struct snd_soc_dai_driver ep93xx_ac97_dai = {
+ .name = "ep93xx-ac97",
+ .id = 0,
+ .ac97_control = 1,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &ep93xx_ac97_dai_ops,
+};
+
+static int __devinit ep93xx_ac97_probe(struct platform_device *pdev)
+{
+ struct ep93xx_ac97_info *info;
+ int ret;
+
+ info = kzalloc(sizeof(struct ep93xx_ac97_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ dev_set_drvdata(&pdev->dev, info);
+
+ mutex_init(&info->lock);
+ init_completion(&info->done);
+ info->dev = &pdev->dev;
+
+ info->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!info->mem) {
+ ret = -ENXIO;
+ goto fail_free_info;
+ }
+
+ info->irq = platform_get_irq(pdev, 0);
+ if (!info->irq) {
+ ret = -ENXIO;
+ goto fail_free_info;
+ }
+
+ if (!request_mem_region(info->mem->start, resource_size(info->mem),
+ pdev->name)) {
+ ret = -EBUSY;
+ goto fail_free_info;
+ }
+
+ info->regs = ioremap(info->mem->start, resource_size(info->mem));
+ if (!info->regs) {
+ ret = -ENOMEM;
+ goto fail_release_mem;
+ }
+
+ ret = request_irq(info->irq, ep93xx_ac97_interrupt, IRQF_TRIGGER_HIGH,
+ pdev->name, info);
+ if (ret)
+ goto fail_unmap_mem;
+
+ ep93xx_ac97_info = info;
+ platform_set_drvdata(pdev, info);
+
+ ret = snd_soc_register_dai(&pdev->dev, &ep93xx_ac97_dai);
+ if (ret)
+ goto fail_free_irq;
+
+ return 0;
+
+fail_free_irq:
+ platform_set_drvdata(pdev, NULL);
+ free_irq(info->irq, info);
+fail_unmap_mem:
+ iounmap(info->regs);
+fail_release_mem:
+ release_mem_region(info->mem->start, resource_size(info->mem));
+fail_free_info:
+ kfree(info);
+
+ return ret;
+}
+
+static int __devexit ep93xx_ac97_remove(struct platform_device *pdev)
+{
+ struct ep93xx_ac97_info *info = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ /* disable the AC97 controller */
+ ep93xx_ac97_write_reg(info, AC97GCR, 0);
+
+ free_irq(info->irq, info);
+ iounmap(info->regs);
+ release_mem_region(info->mem->start, resource_size(info->mem));
+ platform_set_drvdata(pdev, NULL);
+ kfree(info);
+
+ return 0;
+}
+
+static struct platform_driver ep93xx_ac97_driver = {
+ .probe = ep93xx_ac97_probe,
+ .remove = __devexit_p(ep93xx_ac97_remove),
+ .driver = {
+ .name = "ep93xx-ac97",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ep93xx_ac97_init(void)
+{
+ return platform_driver_register(&ep93xx_ac97_driver);
+}
+module_init(ep93xx_ac97_init);
+
+static void __exit ep93xx_ac97_exit(void)
+{
+ platform_driver_unregister(&ep93xx_ac97_driver);
+}
+module_exit(ep93xx_ac97_exit);
+
+MODULE_DESCRIPTION("EP93xx AC97 ASoC Driver");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ep93xx-ac97");
diff --git a/sound/soc/ep93xx/ep93xx-i2s.c b/sound/soc/ep93xx/ep93xx-i2s.c
new file mode 100644
index 00000000..042f4e93
--- /dev/null
+++ b/sound/soc/ep93xx/ep93xx-i2s.c
@@ -0,0 +1,482 @@
+/*
+ * linux/sound/soc/ep93xx-i2s.c
+ * EP93xx I2S driver
+ *
+ * Copyright (C) 2010 Ryan Mallon <ryan@bluewatersys.com>
+ *
+ * Based on the original driver by:
+ * Copyright (C) 2007 Chase Douglas <chasedouglas@gmail>
+ * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+#include <mach/ep93xx-regs.h>
+#include <mach/dma.h>
+
+#include "ep93xx-pcm.h"
+
+#define EP93XX_I2S_TXCLKCFG 0x00
+#define EP93XX_I2S_RXCLKCFG 0x04
+#define EP93XX_I2S_GLCTRL 0x0C
+
+#define EP93XX_I2S_TXLINCTRLDATA 0x28
+#define EP93XX_I2S_TXCTRL 0x2C
+#define EP93XX_I2S_TXWRDLEN 0x30
+#define EP93XX_I2S_TX0EN 0x34
+
+#define EP93XX_I2S_RXLINCTRLDATA 0x58
+#define EP93XX_I2S_RXCTRL 0x5C
+#define EP93XX_I2S_RXWRDLEN 0x60
+#define EP93XX_I2S_RX0EN 0x64
+
+#define EP93XX_I2S_WRDLEN_16 (0 << 0)
+#define EP93XX_I2S_WRDLEN_24 (1 << 0)
+#define EP93XX_I2S_WRDLEN_32 (2 << 0)
+
+#define EP93XX_I2S_LINCTRLDATA_R_JUST (1 << 2) /* Right justify */
+
+#define EP93XX_I2S_CLKCFG_LRS (1 << 0) /* lrclk polarity */
+#define EP93XX_I2S_CLKCFG_CKP (1 << 1) /* Bit clock polarity */
+#define EP93XX_I2S_CLKCFG_REL (1 << 2) /* First bit transition */
+#define EP93XX_I2S_CLKCFG_MASTER (1 << 3) /* Master mode */
+#define EP93XX_I2S_CLKCFG_NBCG (1 << 4) /* Not bit clock gating */
+
+struct ep93xx_i2s_info {
+ struct clk *mclk;
+ struct clk *sclk;
+ struct clk *lrclk;
+ struct ep93xx_pcm_dma_params *dma_params;
+ struct resource *mem;
+ void __iomem *regs;
+};
+
+struct ep93xx_pcm_dma_params ep93xx_i2s_dma_params[] = {
+ [SNDRV_PCM_STREAM_PLAYBACK] = {
+ .name = "i2s-pcm-out",
+ .dma_port = EP93XX_DMA_M2P_PORT_I2S1,
+ },
+ [SNDRV_PCM_STREAM_CAPTURE] = {
+ .name = "i2s-pcm-in",
+ .dma_port = EP93XX_DMA_M2P_PORT_I2S1,
+ },
+};
+
+static inline void ep93xx_i2s_write_reg(struct ep93xx_i2s_info *info,
+ unsigned reg, unsigned val)
+{
+ __raw_writel(val, info->regs + reg);
+}
+
+static inline unsigned ep93xx_i2s_read_reg(struct ep93xx_i2s_info *info,
+ unsigned reg)
+{
+ return __raw_readl(info->regs + reg);
+}
+
+static void ep93xx_i2s_enable(struct ep93xx_i2s_info *info, int stream)
+{
+ unsigned base_reg;
+ int i;
+
+ if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 &&
+ (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) {
+ /* Enable clocks */
+ clk_enable(info->mclk);
+ clk_enable(info->sclk);
+ clk_enable(info->lrclk);
+
+ /* Enable i2s */
+ ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 1);
+ }
+
+ /* Enable fifos */
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ base_reg = EP93XX_I2S_TX0EN;
+ else
+ base_reg = EP93XX_I2S_RX0EN;
+ for (i = 0; i < 3; i++)
+ ep93xx_i2s_write_reg(info, base_reg + (i * 4), 1);
+}
+
+static void ep93xx_i2s_disable(struct ep93xx_i2s_info *info, int stream)
+{
+ unsigned base_reg;
+ int i;
+
+ /* Disable fifos */
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ base_reg = EP93XX_I2S_TX0EN;
+ else
+ base_reg = EP93XX_I2S_RX0EN;
+ for (i = 0; i < 3; i++)
+ ep93xx_i2s_write_reg(info, base_reg + (i * 4), 0);
+
+ if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 &&
+ (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) {
+ /* Disable i2s */
+ ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 0);
+
+ /* Disable clocks */
+ clk_disable(info->lrclk);
+ clk_disable(info->sclk);
+ clk_disable(info->mclk);
+ }
+}
+
+static int ep93xx_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream,
+ &info->dma_params[substream->stream]);
+ return 0;
+}
+
+static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+
+ ep93xx_i2s_disable(info, substream->stream);
+}
+
+static int ep93xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned int clk_cfg, lin_ctrl;
+
+ clk_cfg = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXCLKCFG);
+ lin_ctrl = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXLINCTRLDATA);
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ clk_cfg |= EP93XX_I2S_CLKCFG_REL;
+ lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST;
+ break;
+
+ case SND_SOC_DAIFMT_LEFT_J:
+ clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
+ lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST;
+ break;
+
+ case SND_SOC_DAIFMT_RIGHT_J:
+ clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
+ lin_ctrl |= EP93XX_I2S_LINCTRLDATA_R_JUST;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* CPU is master */
+ clk_cfg |= EP93XX_I2S_CLKCFG_MASTER;
+ break;
+
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* Codec is master */
+ clk_cfg &= ~EP93XX_I2S_CLKCFG_MASTER;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ /* Negative bit clock, lrclk low on left word */
+ clk_cfg &= ~(EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL);
+ break;
+
+ case SND_SOC_DAIFMT_NB_IF:
+ /* Negative bit clock, lrclk low on right word */
+ clk_cfg &= ~EP93XX_I2S_CLKCFG_CKP;
+ clk_cfg |= EP93XX_I2S_CLKCFG_REL;
+ break;
+
+ case SND_SOC_DAIFMT_IB_NF:
+ /* Positive bit clock, lrclk low on left word */
+ clk_cfg |= EP93XX_I2S_CLKCFG_CKP;
+ clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
+ break;
+
+ case SND_SOC_DAIFMT_IB_IF:
+ /* Positive bit clock, lrclk low on right word */
+ clk_cfg |= EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL;
+ break;
+ }
+
+ /* Write new register values */
+ ep93xx_i2s_write_reg(info, EP93XX_I2S_RXCLKCFG, clk_cfg);
+ ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCLKCFG, clk_cfg);
+ ep93xx_i2s_write_reg(info, EP93XX_I2S_RXLINCTRLDATA, lin_ctrl);
+ ep93xx_i2s_write_reg(info, EP93XX_I2S_TXLINCTRLDATA, lin_ctrl);
+ return 0;
+}
+
+static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+ unsigned word_len, div, sdiv, lrdiv;
+ int err;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ word_len = EP93XX_I2S_WRDLEN_16;
+ break;
+
+ case SNDRV_PCM_FORMAT_S24_LE:
+ word_len = EP93XX_I2S_WRDLEN_24;
+ break;
+
+ case SNDRV_PCM_FORMAT_S32_LE:
+ word_len = EP93XX_I2S_WRDLEN_32;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ep93xx_i2s_write_reg(info, EP93XX_I2S_TXWRDLEN, word_len);
+ else
+ ep93xx_i2s_write_reg(info, EP93XX_I2S_RXWRDLEN, word_len);
+
+ /*
+ * EP93xx I2S module can be setup so SCLK / LRCLK value can be
+ * 32, 64, 128. MCLK / SCLK value can be 2 and 4.
+ * We set LRCLK equal to `rate' and minimum SCLK / LRCLK
+ * value is 64, because our sample size is 32 bit * 2 channels.
+ * I2S standard permits us to transmit more bits than
+ * the codec uses.
+ */
+ div = clk_get_rate(info->mclk) / params_rate(params);
+ sdiv = 4;
+ if (div > (256 + 512) / 2) {
+ lrdiv = 128;
+ } else {
+ lrdiv = 64;
+ if (div < (128 + 256) / 2)
+ sdiv = 2;
+ }
+
+ err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv);
+ if (err)
+ return err;
+
+ err = clk_set_rate(info->lrclk, clk_get_rate(info->sclk) / lrdiv);
+ if (err)
+ return err;
+
+ ep93xx_i2s_enable(info, substream->stream);
+ return 0;
+}
+
+static int ep93xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai);
+
+ if (dir == SND_SOC_CLOCK_IN || clk_id != 0)
+ return -EINVAL;
+
+ return clk_set_rate(info->mclk, freq);
+}
+
+#ifdef CONFIG_PM
+static int ep93xx_i2s_suspend(struct snd_soc_dai *dai)
+{
+ struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+
+ if (!dai->active)
+ return 0;
+
+ ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK);
+ ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE);
+
+ return 0;
+}
+
+static int ep93xx_i2s_resume(struct snd_soc_dai *dai)
+{
+ struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+
+ if (!dai->active)
+ return 0;
+
+ ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK);
+ ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE);
+
+ return 0;
+}
+#else
+#define ep93xx_i2s_suspend NULL
+#define ep93xx_i2s_resume NULL
+#endif
+
+static struct snd_soc_dai_ops ep93xx_i2s_dai_ops = {
+ .startup = ep93xx_i2s_startup,
+ .shutdown = ep93xx_i2s_shutdown,
+ .hw_params = ep93xx_i2s_hw_params,
+ .set_sysclk = ep93xx_i2s_set_sysclk,
+ .set_fmt = ep93xx_i2s_set_dai_fmt,
+};
+
+#define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver ep93xx_i2s_dai = {
+ .symmetric_rates= 1,
+ .suspend = ep93xx_i2s_suspend,
+ .resume = ep93xx_i2s_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = EP93XX_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = EP93XX_I2S_FORMATS,
+ },
+ .ops = &ep93xx_i2s_dai_ops,
+};
+
+static int ep93xx_i2s_probe(struct platform_device *pdev)
+{
+ struct ep93xx_i2s_info *info;
+ struct resource *res;
+ int err;
+
+ info = kzalloc(sizeof(struct ep93xx_i2s_info), GFP_KERNEL);
+ if (!info) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ dev_set_drvdata(&pdev->dev, info);
+ info->dma_params = ep93xx_i2s_dma_params;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ err = -ENODEV;
+ goto fail;
+ }
+
+ info->mem = request_mem_region(res->start, resource_size(res),
+ pdev->name);
+ if (!info->mem) {
+ err = -EBUSY;
+ goto fail;
+ }
+
+ info->regs = ioremap(info->mem->start, resource_size(info->mem));
+ if (!info->regs) {
+ err = -ENXIO;
+ goto fail_release_mem;
+ }
+
+ info->mclk = clk_get(&pdev->dev, "mclk");
+ if (IS_ERR(info->mclk)) {
+ err = PTR_ERR(info->mclk);
+ goto fail_unmap_mem;
+ }
+
+ info->sclk = clk_get(&pdev->dev, "sclk");
+ if (IS_ERR(info->sclk)) {
+ err = PTR_ERR(info->sclk);
+ goto fail_put_mclk;
+ }
+
+ info->lrclk = clk_get(&pdev->dev, "lrclk");
+ if (IS_ERR(info->lrclk)) {
+ err = PTR_ERR(info->lrclk);
+ goto fail_put_sclk;
+ }
+
+ err = snd_soc_register_dai(&pdev->dev, &ep93xx_i2s_dai);
+ if (err)
+ goto fail_put_lrclk;
+
+ return 0;
+
+fail_put_lrclk:
+ clk_put(info->lrclk);
+fail_put_sclk:
+ clk_put(info->sclk);
+fail_put_mclk:
+ clk_put(info->mclk);
+fail_unmap_mem:
+ iounmap(info->regs);
+fail_release_mem:
+ release_mem_region(info->mem->start, resource_size(info->mem));
+ kfree(info);
+fail:
+ return err;
+}
+
+static int __devexit ep93xx_i2s_remove(struct platform_device *pdev)
+{
+ struct ep93xx_i2s_info *info = dev_get_drvdata(&pdev->dev);
+
+ snd_soc_unregister_dai(&pdev->dev);
+ clk_put(info->lrclk);
+ clk_put(info->sclk);
+ clk_put(info->mclk);
+ iounmap(info->regs);
+ release_mem_region(info->mem->start, resource_size(info->mem));
+ kfree(info);
+ return 0;
+}
+
+static struct platform_driver ep93xx_i2s_driver = {
+ .probe = ep93xx_i2s_probe,
+ .remove = __devexit_p(ep93xx_i2s_remove),
+ .driver = {
+ .name = "ep93xx-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ep93xx_i2s_init(void)
+{
+ return platform_driver_register(&ep93xx_i2s_driver);
+}
+
+static void __exit ep93xx_i2s_exit(void)
+{
+ platform_driver_unregister(&ep93xx_i2s_driver);
+}
+
+module_init(ep93xx_i2s_init);
+module_exit(ep93xx_i2s_exit);
+
+MODULE_ALIAS("platform:ep93xx-i2s");
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_DESCRIPTION("EP93XX I2S driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ep93xx/ep93xx-pcm.c b/sound/soc/ep93xx/ep93xx-pcm.c
new file mode 100644
index 00000000..a456e491
--- /dev/null
+++ b/sound/soc/ep93xx/ep93xx-pcm.c
@@ -0,0 +1,338 @@
+/*
+ * linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface
+ *
+ * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
+ * Copyright (C) 2006 Applied Data Systems
+ *
+ * Rewritten for the SoC audio subsystem (Based on PXA2xx code):
+ * Copyright (c) 2008 Ryan Mallon <ryan@bluewatersys.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include <mach/hardware.h>
+#include <mach/ep93xx-regs.h>
+
+#include "ep93xx-pcm.h"
+
+static const struct snd_pcm_hardware ep93xx_pcm_hardware = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER),
+
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .rate_min = SNDRV_PCM_RATE_8000,
+ .rate_max = SNDRV_PCM_RATE_192000,
+
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE),
+
+ .buffer_bytes_max = 131072,
+ .period_bytes_min = 32,
+ .period_bytes_max = 32768,
+ .periods_min = 1,
+ .periods_max = 32,
+ .fifo_size = 32,
+};
+
+struct ep93xx_runtime_data
+{
+ struct ep93xx_dma_m2p_client cl;
+ struct ep93xx_pcm_dma_params *params;
+ int pointer_bytes;
+ struct tasklet_struct period_tasklet;
+ int periods;
+ struct ep93xx_dma_buffer buf[32];
+};
+
+static void ep93xx_pcm_period_elapsed(unsigned long data)
+{
+ struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
+ snd_pcm_period_elapsed(substream);
+}
+
+static void ep93xx_pcm_buffer_started(void *cookie,
+ struct ep93xx_dma_buffer *buf)
+{
+}
+
+static void ep93xx_pcm_buffer_finished(void *cookie,
+ struct ep93xx_dma_buffer *buf,
+ int bytes, int error)
+{
+ struct snd_pcm_substream *substream = cookie;
+ struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+
+ if (buf == rtd->buf + rtd->periods - 1)
+ rtd->pointer_bytes = 0;
+ else
+ rtd->pointer_bytes += buf->size;
+
+ if (!error) {
+ ep93xx_dma_m2p_submit_recursive(&rtd->cl, buf);
+ tasklet_schedule(&rtd->period_tasklet);
+ } else {
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+ }
+}
+
+static int ep93xx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = soc_rtd->cpu_dai;
+ struct ep93xx_pcm_dma_params *dma_params;
+ struct ep93xx_runtime_data *rtd;
+ int ret;
+
+ dma_params = snd_soc_dai_get_dma_data(cpu_dai, substream);
+ snd_soc_set_runtime_hwparams(substream, &ep93xx_pcm_hardware);
+
+ rtd = kmalloc(sizeof(*rtd), GFP_KERNEL);
+ if (!rtd)
+ return -ENOMEM;
+
+ memset(&rtd->period_tasklet, 0, sizeof(rtd->period_tasklet));
+ rtd->period_tasklet.func = ep93xx_pcm_period_elapsed;
+ rtd->period_tasklet.data = (unsigned long)substream;
+
+ rtd->cl.name = dma_params->name;
+ rtd->cl.flags = dma_params->dma_port | EP93XX_DMA_M2P_IGNORE_ERROR |
+ ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ EP93XX_DMA_M2P_TX : EP93XX_DMA_M2P_RX);
+ rtd->cl.cookie = substream;
+ rtd->cl.buffer_started = ep93xx_pcm_buffer_started;
+ rtd->cl.buffer_finished = ep93xx_pcm_buffer_finished;
+ ret = ep93xx_dma_m2p_client_register(&rtd->cl);
+ if (ret < 0) {
+ kfree(rtd);
+ return ret;
+ }
+
+ substream->runtime->private_data = rtd;
+ return 0;
+}
+
+static int ep93xx_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+
+ ep93xx_dma_m2p_client_unregister(&rtd->cl);
+ kfree(rtd);
+ return 0;
+}
+
+static int ep93xx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ep93xx_runtime_data *rtd = runtime->private_data;
+ size_t totsize = params_buffer_bytes(params);
+ size_t period = params_period_bytes(params);
+ int i;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = totsize;
+
+ rtd->periods = (totsize + period - 1) / period;
+ for (i = 0; i < rtd->periods; i++) {
+ rtd->buf[i].bus_addr = runtime->dma_addr + (i * period);
+ rtd->buf[i].size = period;
+ if ((i + 1) * period > totsize)
+ rtd->buf[i].size = totsize - (i * period);
+ }
+
+ return 0;
+}
+
+static int ep93xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static int ep93xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+ int ret;
+ int i;
+
+ ret = 0;
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ rtd->pointer_bytes = 0;
+ for (i = 0; i < rtd->periods; i++)
+ ep93xx_dma_m2p_submit(&rtd->cl, rtd->buf + i);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ep93xx_dma_m2p_flush(&rtd->cl);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t ep93xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+
+ /* FIXME: implement this with sub-period granularity */
+ return bytes_to_frames(runtime, rtd->pointer_bytes);
+}
+
+static int ep93xx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops ep93xx_pcm_ops = {
+ .open = ep93xx_pcm_open,
+ .close = ep93xx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = ep93xx_pcm_hw_params,
+ .hw_free = ep93xx_pcm_hw_free,
+ .trigger = ep93xx_pcm_trigger,
+ .pointer = ep93xx_pcm_pointer,
+ .mmap = ep93xx_pcm_mmap,
+};
+
+static int ep93xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = ep93xx_pcm_hardware.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ buf->bytes = size;
+
+ return (buf->area == NULL) ? -ENOMEM : 0;
+}
+
+static void ep93xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area,
+ buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static u64 ep93xx_pcm_dmamask = 0xffffffff;
+
+static int ep93xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &ep93xx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->driver->playback.channels_min) {
+ ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ return ret;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_platform_driver ep93xx_soc_platform = {
+ .ops = &ep93xx_pcm_ops,
+ .pcm_new = &ep93xx_pcm_new,
+ .pcm_free = &ep93xx_pcm_free_dma_buffers,
+};
+
+static int __devinit ep93xx_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &ep93xx_soc_platform);
+}
+
+static int __devexit ep93xx_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver ep93xx_pcm_driver = {
+ .driver = {
+ .name = "ep93xx-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = ep93xx_soc_platform_probe,
+ .remove = __devexit_p(ep93xx_soc_platform_remove),
+};
+
+static int __init ep93xx_soc_platform_init(void)
+{
+ return platform_driver_register(&ep93xx_pcm_driver);
+}
+
+static void __exit ep93xx_soc_platform_exit(void)
+{
+ platform_driver_unregister(&ep93xx_pcm_driver);
+}
+
+module_init(ep93xx_soc_platform_init);
+module_exit(ep93xx_soc_platform_exit);
+
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_DESCRIPTION("EP93xx ALSA PCM interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ep93xx/ep93xx-pcm.h b/sound/soc/ep93xx/ep93xx-pcm.h
new file mode 100644
index 00000000..111e1121
--- /dev/null
+++ b/sound/soc/ep93xx/ep93xx-pcm.h
@@ -0,0 +1,20 @@
+/*
+ * sound/soc/ep93xx/ep93xx-pcm.h - EP93xx ALSA PCM interface
+ *
+ * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
+ * Copyright (C) 2006 Applied Data Systems
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _EP93XX_SND_SOC_PCM_H
+#define _EP93XX_SND_SOC_PCM_H
+
+struct ep93xx_pcm_dma_params {
+ char *name;
+ int dma_port;
+};
+
+#endif /* _EP93XX_SND_SOC_PCM_H */
diff --git a/sound/soc/ep93xx/simone.c b/sound/soc/ep93xx/simone.c
new file mode 100644
index 00000000..28681794
--- /dev/null
+++ b/sound/soc/ep93xx/simone.c
@@ -0,0 +1,91 @@
+/*
+ * simone.c -- ASoC audio for Simplemachines Sim.One board
+ *
+ * Copyright (c) 2010 Mika Westerberg
+ *
+ * Based on snappercl15 machine driver by Ryan Mallon.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+#include "ep93xx-pcm.h"
+
+static struct snd_soc_dai_link simone_dai = {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "ep93xx-ac97",
+ .codec_dai_name = "ac97-hifi",
+ .codec_name = "ac97-codec",
+ .platform_name = "ep93xx-pcm-audio",
+};
+
+static struct snd_soc_card snd_soc_simone = {
+ .name = "Sim.One",
+ .dai_link = &simone_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *simone_snd_ac97_device;
+static struct platform_device *simone_snd_device;
+
+static int __init simone_init(void)
+{
+ int ret;
+
+ if (!machine_is_sim_one())
+ return -ENODEV;
+
+ simone_snd_ac97_device = platform_device_alloc("ac97-codec", -1);
+ if (!simone_snd_ac97_device)
+ return -ENOMEM;
+
+ ret = platform_device_add(simone_snd_ac97_device);
+ if (ret)
+ goto fail1;
+
+ simone_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!simone_snd_device) {
+ ret = -ENOMEM;
+ goto fail2;
+ }
+
+ platform_set_drvdata(simone_snd_device, &snd_soc_simone);
+ ret = platform_device_add(simone_snd_device);
+ if (ret)
+ goto fail3;
+
+ return 0;
+
+fail3:
+ platform_device_put(simone_snd_device);
+fail2:
+ platform_device_del(simone_snd_ac97_device);
+fail1:
+ platform_device_put(simone_snd_ac97_device);
+ return ret;
+}
+module_init(simone_init);
+
+static void __exit simone_exit(void)
+{
+ platform_device_unregister(simone_snd_device);
+ platform_device_unregister(simone_snd_ac97_device);
+}
+module_exit(simone_exit);
+
+MODULE_DESCRIPTION("ALSA SoC Simplemachines Sim.One");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ep93xx/snappercl15.c b/sound/soc/ep93xx/snappercl15.c
new file mode 100644
index 00000000..dfe1d7f7
--- /dev/null
+++ b/sound/soc/ep93xx/snappercl15.c
@@ -0,0 +1,146 @@
+/*
+ * snappercl15.c -- SoC audio for Bluewater Systems Snapper CL15 module
+ *
+ * Copyright (C) 2008 Bluewater Systems Ltd
+ * Author: Ryan Mallon <ryan@bluewatersys.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+#include "../codecs/tlv320aic23.h"
+#include "ep93xx-pcm.h"
+
+#define CODEC_CLOCK 5644800
+
+static int snappercl15_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int err;
+
+ err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBS_CFS);
+
+ err = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err)
+ return err;
+
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK,
+ SND_SOC_CLOCK_IN);
+ if (err)
+ return err;
+
+ err = snd_soc_dai_set_sysclk(cpu_dai, 0, CODEC_CLOCK,
+ SND_SOC_CLOCK_OUT);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static struct snd_soc_ops snappercl15_ops = {
+ .hw_params = snappercl15_hw_params,
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Jack", NULL, "LHPOUT"},
+ {"Headphone Jack", NULL, "RHPOUT"},
+
+ {"LLINEIN", NULL, "Line In"},
+ {"RLINEIN", NULL, "Line In"},
+
+ {"MICIN", NULL, "Mic Jack"},
+};
+
+static int snappercl15_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, tlv320aic23_dapm_widgets,
+ ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+ return 0;
+}
+
+static struct snd_soc_dai_link snappercl15_dai = {
+ .name = "tlv320aic23",
+ .stream_name = "AIC23",
+ .cpu_dai_name = "ep93xx-i2s",
+ .codec_dai_name = "tlv320aic23-hifi",
+ .codec_name = "tlv320aic23-codec.0-001a",
+ .platform_name = "ep93xx-pcm-audio",
+ .init = snappercl15_tlv320aic23_init,
+ .ops = &snappercl15_ops,
+};
+
+static struct snd_soc_card snd_soc_snappercl15 = {
+ .name = "Snapper CL15",
+ .dai_link = &snappercl15_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *snappercl15_snd_device;
+
+static int __init snappercl15_init(void)
+{
+ int ret;
+
+ if (!machine_is_snapper_cl15())
+ return -ENODEV;
+
+ ret = ep93xx_i2s_acquire(EP93XX_SYSCON_DEVCFG_I2SONAC97,
+ EP93XX_SYSCON_I2SCLKDIV_ORIDE |
+ EP93XX_SYSCON_I2SCLKDIV_SPOL);
+ if (ret)
+ return ret;
+
+ snappercl15_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!snappercl15_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(snappercl15_snd_device, &snd_soc_snappercl15);
+ ret = platform_device_add(snappercl15_snd_device);
+ if (ret)
+ platform_device_put(snappercl15_snd_device);
+
+ return ret;
+}
+
+static void __exit snappercl15_exit(void)
+{
+ platform_device_unregister(snappercl15_snd_device);
+ ep93xx_i2s_release();
+}
+
+module_init(snappercl15_init);
+module_exit(snappercl15_exit);
+
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_DESCRIPTION("ALSA SoC Snapper CL15");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
new file mode 100644
index 00000000..d754d34d
--- /dev/null
+++ b/sound/soc/fsl/Kconfig
@@ -0,0 +1,67 @@
+config SND_MPC52xx_DMA
+ tristate
+
+# ASoC platform support for the Freescale PowerPC SOCs that have an SSI and
+# an Elo DMA controller, such as the MPC8610 and P1022. You will still need to
+# select a platform driver and a codec driver.
+config SND_SOC_POWERPC_SSI
+ tristate
+ depends on FSL_SOC
+
+config SND_SOC_MPC8610_HPCD
+ tristate "ALSA SoC support for the Freescale MPC8610 HPCD board"
+ # I2C is necessary for the CS4270 driver
+ depends on MPC8610_HPCD && I2C
+ select SND_SOC_POWERPC_SSI
+ select SND_SOC_CS4270
+ select SND_SOC_CS4270_VD33_ERRATA
+ default y if MPC8610_HPCD
+ help
+ Say Y if you want to enable audio on the Freescale MPC8610 HPCD.
+
+config SND_SOC_P1022_DS
+ tristate "ALSA SoC support for the Freescale P1022 DS board"
+ # I2C is necessary for the WM8776 driver
+ depends on P1022_DS && I2C
+ select SND_SOC_POWERPC_SSI
+ select SND_SOC_WM8776
+ default y if P1022_DS
+ help
+ Say Y if you want to enable audio on the Freescale P1022 DS board.
+ This will also include the Wolfson Microelectronics WM8776 codec
+ driver.
+
+config SND_SOC_MPC5200_I2S
+ tristate "Freescale MPC5200 PSC in I2S mode driver"
+ depends on PPC_MPC52xx && PPC_BESTCOMM
+ select SND_MPC52xx_DMA
+ select PPC_BESTCOMM_GEN_BD
+ help
+ Say Y here to support the MPC5200 PSCs in I2S mode.
+
+config SND_SOC_MPC5200_AC97
+ tristate "Freescale MPC5200 PSC in AC97 mode driver"
+ depends on PPC_MPC52xx && PPC_BESTCOMM
+ select SND_SOC_AC97_BUS
+ select SND_MPC52xx_DMA
+ select PPC_BESTCOMM_GEN_BD
+ help
+ Say Y here to support the MPC5200 PSCs in AC97 mode.
+
+config SND_MPC52xx_SOC_PCM030
+ tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712"
+ depends on PPC_MPC5200_SIMPLE
+ select SND_SOC_MPC5200_AC97
+ select SND_SOC_WM9712
+ help
+ Say Y if you want to add support for sound on the Phytec pcm030
+ baseboard.
+
+config SND_MPC52xx_SOC_EFIKA
+ tristate "SoC AC97 Audio support for bbplan Efika and STAC9766"
+ depends on PPC_EFIKA
+ select SND_SOC_MPC5200_AC97
+ select SND_SOC_STAC9766
+ help
+ Say Y if you want to add support for sound on the Efika.
+
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
new file mode 100644
index 00000000..b4a38c0a
--- /dev/null
+++ b/sound/soc/fsl/Makefile
@@ -0,0 +1,22 @@
+# MPC8610 HPCD Machine Support
+snd-soc-mpc8610-hpcd-objs := mpc8610_hpcd.o
+obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += snd-soc-mpc8610-hpcd.o
+
+# P1022 DS Machine Support
+snd-soc-p1022-ds-objs := p1022_ds.o
+obj-$(CONFIG_SND_SOC_P1022_DS) += snd-soc-p1022-ds.o
+
+# Freescale PowerPC SSI/DMA Platform Support
+snd-soc-fsl-ssi-objs := fsl_ssi.o
+snd-soc-fsl-dma-objs := fsl_dma.o
+obj-$(CONFIG_SND_SOC_POWERPC_SSI) += snd-soc-fsl-ssi.o snd-soc-fsl-dma.o
+
+# MPC5200 Platform Support
+obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
+obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o
+obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o
+
+# MPC5200 Machine Support
+obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o
+obj-$(CONFIG_SND_MPC52xx_SOC_EFIKA) += efika-audio-fabric.o
+
diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c
new file mode 100644
index 00000000..108b5d8b
--- /dev/null
+++ b/sound/soc/fsl/efika-audio-fabric.c
@@ -0,0 +1,91 @@
+/*
+ * Efika driver for the PSC of the Freescale MPC52xx
+ * configured as AC97 interface
+ *
+ * Copyright 2008 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "mpc5200_dma.h"
+#include "mpc5200_psc_ac97.h"
+#include "../codecs/stac9766.h"
+
+#define DRV_NAME "efika-audio-fabric"
+
+static struct snd_soc_card card;
+
+static struct snd_soc_dai_link efika_fabric_dai[] = {
+{
+ .name = "AC97",
+ .stream_name = "AC97 Analog",
+ .codec_dai_name = "stac9766-hifi-analog",
+ .cpu_dai_name = "mpc5200-psc-ac97.0",
+ .platform_name = "mpc5200-pcm-audio",
+ .codec_name = "stac9766-codec",
+},
+{
+ .name = "AC97",
+ .stream_name = "AC97 IEC958",
+ .codec_dai_name = "stac9766-hifi-IEC958",
+ .cpu_dai_name = "mpc5200-psc-ac97.1",
+ .platform_name = "mpc5200-pcm-audio",
+ .codec_name = "stac9766-codec",
+},
+};
+
+static __init int efika_fabric_init(void)
+{
+ struct platform_device *pdev;
+ int rc;
+
+ if (!of_machine_is_compatible("bplan,efika"))
+ return -ENODEV;
+
+ card.name = "Efika";
+ card.dai_link = efika_fabric_dai;
+ card.num_links = ARRAY_SIZE(efika_fabric_dai);
+
+
+ pdev = platform_device_alloc("soc-audio", 1);
+ if (!pdev) {
+ pr_err("efika_fabric_init: platform_device_alloc() failed\n");
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, &card);
+
+ rc = platform_device_add(pdev);
+ if (rc) {
+ pr_err("efika_fabric_init: platform_device_add() failed\n");
+ platform_device_put(pdev);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+module_init(efika_fabric_init);
+
+
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_DESCRIPTION(DRV_NAME ": mpc5200 Efika fabric driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c
new file mode 100644
index 00000000..6680c0b4
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma.c
@@ -0,0 +1,1009 @@
+/*
+ * Freescale DMA ALSA SoC PCM driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ * This driver implements ASoC support for the Elo DMA controller, which is
+ * the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms,
+ * the PCM driver is what handles the DMA buffer.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include <linux/of_platform.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/io.h>
+
+#include "fsl_dma.h"
+#include "fsl_ssi.h" /* For the offset of stx0 and srx0 */
+
+/*
+ * The formats that the DMA controller supports, which is anything
+ * that is 8, 16, or 32 bits.
+ */
+#define FSLDMA_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_U8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_U16_LE | \
+ SNDRV_PCM_FMTBIT_U16_BE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S24_BE | \
+ SNDRV_PCM_FMTBIT_U24_LE | \
+ SNDRV_PCM_FMTBIT_U24_BE | \
+ SNDRV_PCM_FMTBIT_S32_LE | \
+ SNDRV_PCM_FMTBIT_S32_BE | \
+ SNDRV_PCM_FMTBIT_U32_LE | \
+ SNDRV_PCM_FMTBIT_U32_BE)
+
+#define FSLDMA_PCM_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+ SNDRV_PCM_RATE_CONTINUOUS)
+
+struct dma_object {
+ struct snd_soc_platform_driver dai;
+ dma_addr_t ssi_stx_phys;
+ dma_addr_t ssi_srx_phys;
+ unsigned int ssi_fifo_depth;
+ struct ccsr_dma_channel __iomem *channel;
+ unsigned int irq;
+ bool assigned;
+ char path[1];
+};
+
+/*
+ * The number of DMA links to use. Two is the bare minimum, but if you
+ * have really small links you might need more.
+ */
+#define NUM_DMA_LINKS 2
+
+/** fsl_dma_private: p-substream DMA data
+ *
+ * Each substream has a 1-to-1 association with a DMA channel.
+ *
+ * The link[] array is first because it needs to be aligned on a 32-byte
+ * boundary, so putting it first will ensure alignment without padding the
+ * structure.
+ *
+ * @link[]: array of link descriptors
+ * @dma_channel: pointer to the DMA channel's registers
+ * @irq: IRQ for this DMA channel
+ * @substream: pointer to the substream object, needed by the ISR
+ * @ssi_sxx_phys: bus address of the STX or SRX register to use
+ * @ld_buf_phys: physical address of the LD buffer
+ * @current_link: index into link[] of the link currently being processed
+ * @dma_buf_phys: physical address of the DMA buffer
+ * @dma_buf_next: physical address of the next period to process
+ * @dma_buf_end: physical address of the byte after the end of the DMA
+ * @buffer period_size: the size of a single period
+ * @num_periods: the number of periods in the DMA buffer
+ */
+struct fsl_dma_private {
+ struct fsl_dma_link_descriptor link[NUM_DMA_LINKS];
+ struct ccsr_dma_channel __iomem *dma_channel;
+ unsigned int irq;
+ struct snd_pcm_substream *substream;
+ dma_addr_t ssi_sxx_phys;
+ unsigned int ssi_fifo_depth;
+ dma_addr_t ld_buf_phys;
+ unsigned int current_link;
+ dma_addr_t dma_buf_phys;
+ dma_addr_t dma_buf_next;
+ dma_addr_t dma_buf_end;
+ size_t period_size;
+ unsigned int num_periods;
+};
+
+/**
+ * fsl_dma_hardare: define characteristics of the PCM hardware.
+ *
+ * The PCM hardware is the Freescale DMA controller. This structure defines
+ * the capabilities of that hardware.
+ *
+ * Since the sampling rate and data format are not controlled by the DMA
+ * controller, we specify no limits for those values. The only exception is
+ * period_bytes_min, which is set to a reasonably low value to prevent the
+ * DMA controller from generating too many interrupts per second.
+ *
+ * Since each link descriptor has a 32-bit byte count field, we set
+ * period_bytes_max to the largest 32-bit number. We also have no maximum
+ * number of periods.
+ *
+ * Note that we specify SNDRV_PCM_INFO_JOINT_DUPLEX here, but only because a
+ * limitation in the SSI driver requires the sample rates for playback and
+ * capture to be the same.
+ */
+static const struct snd_pcm_hardware fsl_dma_hardware = {
+
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_JOINT_DUPLEX |
+ SNDRV_PCM_INFO_PAUSE,
+ .formats = FSLDMA_PCM_FORMATS,
+ .rates = FSLDMA_PCM_RATES,
+ .rate_min = 5512,
+ .rate_max = 192000,
+ .period_bytes_min = 512, /* A reasonable limit */
+ .period_bytes_max = (u32) -1,
+ .periods_min = NUM_DMA_LINKS,
+ .periods_max = (unsigned int) -1,
+ .buffer_bytes_max = 128 * 1024, /* A reasonable limit */
+};
+
+/**
+ * fsl_dma_abort_stream: tell ALSA that the DMA transfer has aborted
+ *
+ * This function should be called by the ISR whenever the DMA controller
+ * halts data transfer.
+ */
+static void fsl_dma_abort_stream(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+
+ snd_pcm_stream_lock_irqsave(substream, flags);
+
+ if (snd_pcm_running(substream))
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+
+ snd_pcm_stream_unlock_irqrestore(substream, flags);
+}
+
+/**
+ * fsl_dma_update_pointers - update LD pointers to point to the next period
+ *
+ * As each period is completed, this function changes the the link
+ * descriptor pointers for that period to point to the next period.
+ */
+static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private)
+{
+ struct fsl_dma_link_descriptor *link =
+ &dma_private->link[dma_private->current_link];
+
+ /* Update our link descriptors to point to the next period. On a 36-bit
+ * system, we also need to update the ESAD bits. We also set (keep) the
+ * snoop bits. See the comments in fsl_dma_hw_params() about snooping.
+ */
+ if (dma_private->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ link->source_addr = cpu_to_be32(dma_private->dma_buf_next);
+#ifdef CONFIG_PHYS_64BIT
+ link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+ upper_32_bits(dma_private->dma_buf_next));
+#endif
+ } else {
+ link->dest_addr = cpu_to_be32(dma_private->dma_buf_next);
+#ifdef CONFIG_PHYS_64BIT
+ link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+ upper_32_bits(dma_private->dma_buf_next));
+#endif
+ }
+
+ /* Update our variables for next time */
+ dma_private->dma_buf_next += dma_private->period_size;
+
+ if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
+ dma_private->dma_buf_next = dma_private->dma_buf_phys;
+
+ if (++dma_private->current_link >= NUM_DMA_LINKS)
+ dma_private->current_link = 0;
+}
+
+/**
+ * fsl_dma_isr: interrupt handler for the DMA controller
+ *
+ * @irq: IRQ of the DMA channel
+ * @dev_id: pointer to the dma_private structure for this DMA channel
+ */
+static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
+{
+ struct fsl_dma_private *dma_private = dev_id;
+ struct snd_pcm_substream *substream = dma_private->substream;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+ irqreturn_t ret = IRQ_NONE;
+ u32 sr, sr2 = 0;
+
+ /* We got an interrupt, so read the status register to see what we
+ were interrupted for.
+ */
+ sr = in_be32(&dma_channel->sr);
+
+ if (sr & CCSR_DMA_SR_TE) {
+ dev_err(dev, "dma transmit error\n");
+ fsl_dma_abort_stream(substream);
+ sr2 |= CCSR_DMA_SR_TE;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sr & CCSR_DMA_SR_CH)
+ ret = IRQ_HANDLED;
+
+ if (sr & CCSR_DMA_SR_PE) {
+ dev_err(dev, "dma programming error\n");
+ fsl_dma_abort_stream(substream);
+ sr2 |= CCSR_DMA_SR_PE;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sr & CCSR_DMA_SR_EOLNI) {
+ sr2 |= CCSR_DMA_SR_EOLNI;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sr & CCSR_DMA_SR_CB)
+ ret = IRQ_HANDLED;
+
+ if (sr & CCSR_DMA_SR_EOSI) {
+ /* Tell ALSA we completed a period. */
+ snd_pcm_period_elapsed(substream);
+
+ /*
+ * Update our link descriptors to point to the next period. We
+ * only need to do this if the number of periods is not equal to
+ * the number of links.
+ */
+ if (dma_private->num_periods != NUM_DMA_LINKS)
+ fsl_dma_update_pointers(dma_private);
+
+ sr2 |= CCSR_DMA_SR_EOSI;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sr & CCSR_DMA_SR_EOLSI) {
+ sr2 |= CCSR_DMA_SR_EOLSI;
+ ret = IRQ_HANDLED;
+ }
+
+ /* Clear the bits that we set */
+ if (sr2)
+ out_be32(&dma_channel->sr, sr2);
+
+ return ret;
+}
+
+/**
+ * fsl_dma_new: initialize this PCM driver.
+ *
+ * This function is called when the codec driver calls snd_soc_new_pcms(),
+ * once for each .dai_link in the machine driver's snd_soc_card
+ * structure.
+ *
+ * snd_dma_alloc_pages() is just a front-end to dma_alloc_coherent(), which
+ * (currently) always allocates the DMA buffer in lowmem, even if GFP_HIGHMEM
+ * is specified. Therefore, any DMA buffers we allocate will always be in low
+ * memory, but we support for 36-bit physical addresses anyway.
+ *
+ * Regardless of where the memory is actually allocated, since the device can
+ * technically DMA to any 36-bit address, we do need to set the DMA mask to 36.
+ */
+static int fsl_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ static u64 fsl_dma_dmamask = DMA_BIT_MASK(36);
+ int ret;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &fsl_dma_dmamask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = fsl_dma_dmamask;
+
+ /* Some codecs have separate DAIs for playback and capture, so we
+ * should allocate a DMA buffer only for the streams that are valid.
+ */
+
+ if (pcm->streams[0].substream) {
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
+ fsl_dma_hardware.buffer_bytes_max,
+ &pcm->streams[0].substream->dma_buffer);
+ if (ret) {
+ dev_err(card->dev, "can't alloc playback dma buffer\n");
+ return ret;
+ }
+ }
+
+ if (pcm->streams[1].substream) {
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
+ fsl_dma_hardware.buffer_bytes_max,
+ &pcm->streams[1].substream->dma_buffer);
+ if (ret) {
+ dev_err(card->dev, "can't alloc capture dma buffer\n");
+ snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_dma_open: open a new substream.
+ *
+ * Each substream has its own DMA buffer.
+ *
+ * ALSA divides the DMA buffer into N periods. We create NUM_DMA_LINKS link
+ * descriptors that ping-pong from one period to the next. For example, if
+ * there are six periods and two link descriptors, this is how they look
+ * before playback starts:
+ *
+ * The last link descriptor
+ * ____________ points back to the first
+ * | |
+ * V |
+ * ___ ___ |
+ * | |->| |->|
+ * |___| |___|
+ * | |
+ * | |
+ * V V
+ * _________________________________________
+ * | | | | | | | The DMA buffer is
+ * | | | | | | | divided into 6 parts
+ * |______|______|______|______|______|______|
+ *
+ * and here's how they look after the first period is finished playing:
+ *
+ * ____________
+ * | |
+ * V |
+ * ___ ___ |
+ * | |->| |->|
+ * |___| |___|
+ * | |
+ * |______________
+ * | |
+ * V V
+ * _________________________________________
+ * | | | | | | |
+ * | | | | | | |
+ * |______|______|______|______|______|______|
+ *
+ * The first link descriptor now points to the third period. The DMA
+ * controller is currently playing the second period. When it finishes, it
+ * will jump back to the first descriptor and play the third period.
+ *
+ * There are four reasons we do this:
+ *
+ * 1. The only way to get the DMA controller to automatically restart the
+ * transfer when it gets to the end of the buffer is to use chaining
+ * mode. Basic direct mode doesn't offer that feature.
+ * 2. We need to receive an interrupt at the end of every period. The DMA
+ * controller can generate an interrupt at the end of every link transfer
+ * (aka segment). Making each period into a DMA segment will give us the
+ * interrupts we need.
+ * 3. By creating only two link descriptors, regardless of the number of
+ * periods, we do not need to reallocate the link descriptors if the
+ * number of periods changes.
+ * 4. All of the audio data is still stored in a single, contiguous DMA
+ * buffer, which is what ALSA expects. We're just dividing it into
+ * contiguous parts, and creating a link descriptor for each one.
+ */
+static int fsl_dma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct dma_object *dma =
+ container_of(rtd->platform->driver, struct dma_object, dai);
+ struct fsl_dma_private *dma_private;
+ struct ccsr_dma_channel __iomem *dma_channel;
+ dma_addr_t ld_buf_phys;
+ u64 temp_link; /* Pointer to next link descriptor */
+ u32 mr;
+ unsigned int channel;
+ int ret = 0;
+ unsigned int i;
+
+ /*
+ * Reject any DMA buffer whose size is not a multiple of the period
+ * size. We need to make sure that the DMA buffer can be evenly divided
+ * into periods.
+ */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ dev_err(dev, "invalid buffer size\n");
+ return ret;
+ }
+
+ channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+ if (dma->assigned) {
+ dev_err(dev, "dma channel already assigned\n");
+ return -EBUSY;
+ }
+
+ dma_private = dma_alloc_coherent(dev, sizeof(struct fsl_dma_private),
+ &ld_buf_phys, GFP_KERNEL);
+ if (!dma_private) {
+ dev_err(dev, "can't allocate dma private data\n");
+ return -ENOMEM;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_private->ssi_sxx_phys = dma->ssi_stx_phys;
+ else
+ dma_private->ssi_sxx_phys = dma->ssi_srx_phys;
+
+ dma_private->ssi_fifo_depth = dma->ssi_fifo_depth;
+ dma_private->dma_channel = dma->channel;
+ dma_private->irq = dma->irq;
+ dma_private->substream = substream;
+ dma_private->ld_buf_phys = ld_buf_phys;
+ dma_private->dma_buf_phys = substream->dma_buffer.addr;
+
+ ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "fsldma-audio",
+ dma_private);
+ if (ret) {
+ dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n",
+ dma_private->irq, ret);
+ dma_free_coherent(dev, sizeof(struct fsl_dma_private),
+ dma_private, dma_private->ld_buf_phys);
+ return ret;
+ }
+
+ dma->assigned = 1;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware);
+ runtime->private_data = dma_private;
+
+ /* Program the fixed DMA controller parameters */
+
+ dma_channel = dma_private->dma_channel;
+
+ temp_link = dma_private->ld_buf_phys +
+ sizeof(struct fsl_dma_link_descriptor);
+
+ for (i = 0; i < NUM_DMA_LINKS; i++) {
+ dma_private->link[i].next = cpu_to_be64(temp_link);
+
+ temp_link += sizeof(struct fsl_dma_link_descriptor);
+ }
+ /* The last link descriptor points to the first */
+ dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys);
+
+ /* Tell the DMA controller where the first link descriptor is */
+ out_be32(&dma_channel->clndar,
+ CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys));
+ out_be32(&dma_channel->eclndar,
+ CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys));
+
+ /* The manual says the BCR must be clear before enabling EMP */
+ out_be32(&dma_channel->bcr, 0);
+
+ /*
+ * Program the mode register for interrupts, external master control,
+ * and source/destination hold. Also clear the Channel Abort bit.
+ */
+ mr = in_be32(&dma_channel->mr) &
+ ~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE);
+
+ /*
+ * We want External Master Start and External Master Pause enabled,
+ * because the SSI is controlling the DMA controller. We want the DMA
+ * controller to be set up in advance, and then we signal only the SSI
+ * to start transferring.
+ *
+ * We want End-Of-Segment Interrupts enabled, because this will generate
+ * an interrupt at the end of each segment (each link descriptor
+ * represents one segment). Each DMA segment is the same thing as an
+ * ALSA period, so this is how we get an interrupt at the end of every
+ * period.
+ *
+ * We want Error Interrupt enabled, so that we can get an error if
+ * the DMA controller is mis-programmed somehow.
+ */
+ mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN |
+ CCSR_DMA_MR_EMS_EN;
+
+ /* For playback, we want the destination address to be held. For
+ capture, set the source address to be held. */
+ mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE;
+
+ out_be32(&dma_channel->mr, mr);
+
+ return 0;
+}
+
+/**
+ * fsl_dma_hw_params: continue initializing the DMA links
+ *
+ * This function obtains hardware parameters about the opened stream and
+ * programs the DMA controller accordingly.
+ *
+ * One drawback of big-endian is that when copying integers of different
+ * sizes to a fixed-sized register, the address to which the integer must be
+ * copied is dependent on the size of the integer.
+ *
+ * For example, if P is the address of a 32-bit register, and X is a 32-bit
+ * integer, then X should be copied to address P. However, if X is a 16-bit
+ * integer, then it should be copied to P+2. If X is an 8-bit register,
+ * then it should be copied to P+3.
+ *
+ * So for playback of 8-bit samples, the DMA controller must transfer single
+ * bytes from the DMA buffer to the last byte of the STX0 register, i.e.
+ * offset by 3 bytes. For 16-bit samples, the offset is two bytes.
+ *
+ * For 24-bit samples, the offset is 1 byte. However, the DMA controller
+ * does not support 3-byte copies (the DAHTS register supports only 1, 2, 4,
+ * and 8 bytes at a time). So we do not support packed 24-bit samples.
+ * 24-bit data must be padded to 32 bits.
+ */
+static int fsl_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+
+ /* Number of bits per sample */
+ unsigned int sample_bits =
+ snd_pcm_format_physical_width(params_format(hw_params));
+
+ /* Number of bytes per frame */
+ unsigned int sample_bytes = sample_bits / 8;
+
+ /* Bus address of SSI STX register */
+ dma_addr_t ssi_sxx_phys = dma_private->ssi_sxx_phys;
+
+ /* Size of the DMA buffer, in bytes */
+ size_t buffer_size = params_buffer_bytes(hw_params);
+
+ /* Number of bytes per period */
+ size_t period_size = params_period_bytes(hw_params);
+
+ /* Pointer to next period */
+ dma_addr_t temp_addr = substream->dma_buffer.addr;
+
+ /* Pointer to DMA controller */
+ struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+
+ u32 mr; /* DMA Mode Register */
+
+ unsigned int i;
+
+ /* Initialize our DMA tracking variables */
+ dma_private->period_size = period_size;
+ dma_private->num_periods = params_periods(hw_params);
+ dma_private->dma_buf_end = dma_private->dma_buf_phys + buffer_size;
+ dma_private->dma_buf_next = dma_private->dma_buf_phys +
+ (NUM_DMA_LINKS * period_size);
+
+ if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
+ /* This happens if the number of periods == NUM_DMA_LINKS */
+ dma_private->dma_buf_next = dma_private->dma_buf_phys;
+
+ mr = in_be32(&dma_channel->mr) & ~(CCSR_DMA_MR_BWC_MASK |
+ CCSR_DMA_MR_SAHTS_MASK | CCSR_DMA_MR_DAHTS_MASK);
+
+ /* Due to a quirk of the SSI's STX register, the target address
+ * for the DMA operations depends on the sample size. So we calculate
+ * that offset here. While we're at it, also tell the DMA controller
+ * how much data to transfer per sample.
+ */
+ switch (sample_bits) {
+ case 8:
+ mr |= CCSR_DMA_MR_DAHTS_1 | CCSR_DMA_MR_SAHTS_1;
+ ssi_sxx_phys += 3;
+ break;
+ case 16:
+ mr |= CCSR_DMA_MR_DAHTS_2 | CCSR_DMA_MR_SAHTS_2;
+ ssi_sxx_phys += 2;
+ break;
+ case 32:
+ mr |= CCSR_DMA_MR_DAHTS_4 | CCSR_DMA_MR_SAHTS_4;
+ break;
+ default:
+ /* We should never get here */
+ dev_err(dev, "unsupported sample size %u\n", sample_bits);
+ return -EINVAL;
+ }
+
+ /*
+ * BWC determines how many bytes are sent/received before the DMA
+ * controller checks the SSI to see if it needs to stop. BWC should
+ * always be a multiple of the frame size, so that we always transmit
+ * whole frames. Each frame occupies two slots in the FIFO. The
+ * parameter for CCSR_DMA_MR_BWC() is rounded down the next power of two
+ * (MR[BWC] can only represent even powers of two).
+ *
+ * To simplify the process, we set BWC to the largest value that is
+ * less than or equal to the FIFO watermark. For playback, this ensures
+ * that we transfer the maximum amount without overrunning the FIFO.
+ * For capture, this ensures that we transfer the maximum amount without
+ * underrunning the FIFO.
+ *
+ * f = SSI FIFO depth
+ * w = SSI watermark value (which equals f - 2)
+ * b = DMA bandwidth count (in bytes)
+ * s = sample size (in bytes, which equals frame_size * 2)
+ *
+ * For playback, we never transmit more than the transmit FIFO
+ * watermark, otherwise we might write more data than the FIFO can hold.
+ * The watermark is equal to the FIFO depth minus two.
+ *
+ * For capture, two equations must hold:
+ * w > f - (b / s)
+ * w >= b / s
+ *
+ * So, b > 2 * s, but b must also be <= s * w. To simplify, we set
+ * b = s * w, which is equal to
+ * (dma_private->ssi_fifo_depth - 2) * sample_bytes.
+ */
+ mr |= CCSR_DMA_MR_BWC((dma_private->ssi_fifo_depth - 2) * sample_bytes);
+
+ out_be32(&dma_channel->mr, mr);
+
+ for (i = 0; i < NUM_DMA_LINKS; i++) {
+ struct fsl_dma_link_descriptor *link = &dma_private->link[i];
+
+ link->count = cpu_to_be32(period_size);
+
+ /* The snoop bit tells the DMA controller whether it should tell
+ * the ECM to snoop during a read or write to an address. For
+ * audio, we use DMA to transfer data between memory and an I/O
+ * device (the SSI's STX0 or SRX0 register). Snooping is only
+ * needed if there is a cache, so we need to snoop memory
+ * addresses only. For playback, that means we snoop the source
+ * but not the destination. For capture, we snoop the
+ * destination but not the source.
+ *
+ * Note that failing to snoop properly is unlikely to cause
+ * cache incoherency if the period size is larger than the
+ * size of L1 cache. This is because filling in one period will
+ * flush out the data for the previous period. So if you
+ * increased period_bytes_min to a large enough size, you might
+ * get more performance by not snooping, and you'll still be
+ * okay. You'll need to update fsl_dma_update_pointers() also.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ link->source_addr = cpu_to_be32(temp_addr);
+ link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+ upper_32_bits(temp_addr));
+
+ link->dest_addr = cpu_to_be32(ssi_sxx_phys);
+ link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP |
+ upper_32_bits(ssi_sxx_phys));
+ } else {
+ link->source_addr = cpu_to_be32(ssi_sxx_phys);
+ link->source_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP |
+ upper_32_bits(ssi_sxx_phys));
+
+ link->dest_addr = cpu_to_be32(temp_addr);
+ link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+ upper_32_bits(temp_addr));
+ }
+
+ temp_addr += period_size;
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_dma_pointer: determine the current position of the DMA transfer
+ *
+ * This function is called by ALSA when ALSA wants to know where in the
+ * stream buffer the hardware currently is.
+ *
+ * For playback, the SAR register contains the physical address of the most
+ * recent DMA transfer. For capture, the value is in the DAR register.
+ *
+ * The base address of the buffer is stored in the source_addr field of the
+ * first link descriptor.
+ */
+static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+ dma_addr_t position;
+ snd_pcm_uframes_t frames;
+
+ /* Obtain the current DMA pointer, but don't read the ESAD bits if we
+ * only have 32-bit DMA addresses. This function is typically called
+ * in interrupt context, so we need to optimize it.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ position = in_be32(&dma_channel->sar);
+#ifdef CONFIG_PHYS_64BIT
+ position |= (u64)(in_be32(&dma_channel->satr) &
+ CCSR_DMA_ATR_ESAD_MASK) << 32;
+#endif
+ } else {
+ position = in_be32(&dma_channel->dar);
+#ifdef CONFIG_PHYS_64BIT
+ position |= (u64)(in_be32(&dma_channel->datr) &
+ CCSR_DMA_ATR_ESAD_MASK) << 32;
+#endif
+ }
+
+ /*
+ * When capture is started, the SSI immediately starts to fill its FIFO.
+ * This means that the DMA controller is not started until the FIFO is
+ * full. However, ALSA calls this function before that happens, when
+ * MR.DAR is still zero. In this case, just return zero to indicate
+ * that nothing has been received yet.
+ */
+ if (!position)
+ return 0;
+
+ if ((position < dma_private->dma_buf_phys) ||
+ (position > dma_private->dma_buf_end)) {
+ dev_err(dev, "dma pointer is out of range, halting stream\n");
+ return SNDRV_PCM_POS_XRUN;
+ }
+
+ frames = bytes_to_frames(runtime, position - dma_private->dma_buf_phys);
+
+ /*
+ * If the current address is just past the end of the buffer, wrap it
+ * around.
+ */
+ if (frames == runtime->buffer_size)
+ frames = 0;
+
+ return frames;
+}
+
+/**
+ * fsl_dma_hw_free: release resources allocated in fsl_dma_hw_params()
+ *
+ * Release the resources allocated in fsl_dma_hw_params() and de-program the
+ * registers.
+ *
+ * This function can be called multiple times.
+ */
+static int fsl_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+
+ if (dma_private) {
+ struct ccsr_dma_channel __iomem *dma_channel;
+
+ dma_channel = dma_private->dma_channel;
+
+ /* Stop the DMA */
+ out_be32(&dma_channel->mr, CCSR_DMA_MR_CA);
+ out_be32(&dma_channel->mr, 0);
+
+ /* Reset all the other registers */
+ out_be32(&dma_channel->sr, -1);
+ out_be32(&dma_channel->clndar, 0);
+ out_be32(&dma_channel->eclndar, 0);
+ out_be32(&dma_channel->satr, 0);
+ out_be32(&dma_channel->sar, 0);
+ out_be32(&dma_channel->datr, 0);
+ out_be32(&dma_channel->dar, 0);
+ out_be32(&dma_channel->bcr, 0);
+ out_be32(&dma_channel->nlndar, 0);
+ out_be32(&dma_channel->enlndar, 0);
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_dma_close: close the stream.
+ */
+static int fsl_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct dma_object *dma =
+ container_of(rtd->platform->driver, struct dma_object, dai);
+
+ if (dma_private) {
+ if (dma_private->irq)
+ free_irq(dma_private->irq, dma_private);
+
+ if (dma_private->ld_buf_phys) {
+ dma_unmap_single(dev, dma_private->ld_buf_phys,
+ sizeof(dma_private->link),
+ DMA_TO_DEVICE);
+ }
+
+ /* Deallocate the fsl_dma_private structure */
+ dma_free_coherent(dev, sizeof(struct fsl_dma_private),
+ dma_private, dma_private->ld_buf_phys);
+ substream->runtime->private_data = NULL;
+ }
+
+ dma->assigned = 0;
+
+ return 0;
+}
+
+/*
+ * Remove this PCM driver.
+ */
+static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) {
+ substream = pcm->streams[i].substream;
+ if (substream) {
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer.area = NULL;
+ substream->dma_buffer.addr = 0;
+ }
+ }
+}
+
+/**
+ * find_ssi_node -- returns the SSI node that points to his DMA channel node
+ *
+ * Although this DMA driver attempts to operate independently of the other
+ * devices, it still needs to determine some information about the SSI device
+ * that it's working with. Unfortunately, the device tree does not contain
+ * a pointer from the DMA channel node to the SSI node -- the pointer goes the
+ * other way. So we need to scan the device tree for SSI nodes until we find
+ * the one that points to the given DMA channel node. It's ugly, but at least
+ * it's contained in this one function.
+ */
+static struct device_node *find_ssi_node(struct device_node *dma_channel_np)
+{
+ struct device_node *ssi_np, *np;
+
+ for_each_compatible_node(ssi_np, NULL, "fsl,mpc8610-ssi") {
+ /* Check each DMA phandle to see if it points to us. We
+ * assume that device_node pointers are a valid comparison.
+ */
+ np = of_parse_phandle(ssi_np, "fsl,playback-dma", 0);
+ if (np == dma_channel_np)
+ return ssi_np;
+
+ np = of_parse_phandle(ssi_np, "fsl,capture-dma", 0);
+ if (np == dma_channel_np)
+ return ssi_np;
+ }
+
+ return NULL;
+}
+
+static struct snd_pcm_ops fsl_dma_ops = {
+ .open = fsl_dma_open,
+ .close = fsl_dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = fsl_dma_hw_params,
+ .hw_free = fsl_dma_hw_free,
+ .pointer = fsl_dma_pointer,
+};
+
+static int __devinit fsl_soc_dma_probe(struct platform_device *pdev)
+ {
+ struct dma_object *dma;
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *ssi_np;
+ struct resource res;
+ const uint32_t *iprop;
+ int ret;
+
+ /* Find the SSI node that points to us. */
+ ssi_np = find_ssi_node(np);
+ if (!ssi_np) {
+ dev_err(&pdev->dev, "cannot find parent SSI node\n");
+ return -ENODEV;
+ }
+
+ ret = of_address_to_resource(ssi_np, 0, &res);
+ if (ret) {
+ dev_err(&pdev->dev, "could not determine resources for %s\n",
+ ssi_np->full_name);
+ of_node_put(ssi_np);
+ return ret;
+ }
+
+ dma = kzalloc(sizeof(*dma) + strlen(np->full_name), GFP_KERNEL);
+ if (!dma) {
+ dev_err(&pdev->dev, "could not allocate dma object\n");
+ of_node_put(ssi_np);
+ return -ENOMEM;
+ }
+
+ strcpy(dma->path, np->full_name);
+ dma->dai.ops = &fsl_dma_ops;
+ dma->dai.pcm_new = fsl_dma_new;
+ dma->dai.pcm_free = fsl_dma_free_dma_buffers;
+
+ /* Store the SSI-specific information that we need */
+ dma->ssi_stx_phys = res.start + offsetof(struct ccsr_ssi, stx0);
+ dma->ssi_srx_phys = res.start + offsetof(struct ccsr_ssi, srx0);
+
+ iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL);
+ if (iprop)
+ dma->ssi_fifo_depth = *iprop;
+ else
+ /* Older 8610 DTs didn't have the fifo-depth property */
+ dma->ssi_fifo_depth = 8;
+
+ of_node_put(ssi_np);
+
+ ret = snd_soc_register_platform(&pdev->dev, &dma->dai);
+ if (ret) {
+ dev_err(&pdev->dev, "could not register platform\n");
+ kfree(dma);
+ return ret;
+ }
+
+ dma->channel = of_iomap(np, 0);
+ dma->irq = irq_of_parse_and_map(np, 0);
+
+ dev_set_drvdata(&pdev->dev, dma);
+
+ return 0;
+}
+
+static int __devexit fsl_soc_dma_remove(struct platform_device *pdev)
+{
+ struct dma_object *dma = dev_get_drvdata(&pdev->dev);
+
+ snd_soc_unregister_platform(&pdev->dev);
+ iounmap(dma->channel);
+ irq_dispose_mapping(dma->irq);
+ kfree(dma);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_soc_dma_ids[] = {
+ { .compatible = "fsl,ssi-dma-channel", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_soc_dma_ids);
+
+static struct platform_driver fsl_soc_dma_driver = {
+ .driver = {
+ .name = "fsl-pcm-audio",
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_soc_dma_ids,
+ },
+ .probe = fsl_soc_dma_probe,
+ .remove = __devexit_p(fsl_soc_dma_remove),
+};
+
+static int __init fsl_soc_dma_init(void)
+{
+ pr_info("Freescale Elo DMA ASoC PCM Driver\n");
+
+ return platform_driver_register(&fsl_soc_dma_driver);
+}
+
+static void __exit fsl_soc_dma_exit(void)
+{
+ platform_driver_unregister(&fsl_soc_dma_driver);
+}
+
+module_init(fsl_soc_dma_init);
+module_exit(fsl_soc_dma_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h
new file mode 100644
index 00000000..78fee97e
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma.h
@@ -0,0 +1,129 @@
+/*
+ * mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _MPC8610_PCM_H
+#define _MPC8610_PCM_H
+
+struct ccsr_dma {
+ u8 res0[0x100];
+ struct ccsr_dma_channel {
+ __be32 mr; /* Mode register */
+ __be32 sr; /* Status register */
+ __be32 eclndar; /* Current link descriptor extended addr reg */
+ __be32 clndar; /* Current link descriptor address register */
+ __be32 satr; /* Source attributes register */
+ __be32 sar; /* Source address register */
+ __be32 datr; /* Destination attributes register */
+ __be32 dar; /* Destination address register */
+ __be32 bcr; /* Byte count register */
+ __be32 enlndar; /* Next link descriptor extended address reg */
+ __be32 nlndar; /* Next link descriptor address register */
+ u8 res1[4];
+ __be32 eclsdar; /* Current list descriptor extended addr reg */
+ __be32 clsdar; /* Current list descriptor address register */
+ __be32 enlsdar; /* Next list descriptor extended address reg */
+ __be32 nlsdar; /* Next list descriptor address register */
+ __be32 ssr; /* Source stride register */
+ __be32 dsr; /* Destination stride register */
+ u8 res2[0x38];
+ } channel[4];
+ __be32 dgsr;
+};
+
+#define CCSR_DMA_MR_BWC_DISABLED 0x0F000000
+#define CCSR_DMA_MR_BWC_SHIFT 24
+#define CCSR_DMA_MR_BWC_MASK 0x0F000000
+#define CCSR_DMA_MR_BWC(x) \
+ ((ilog2(x) << CCSR_DMA_MR_BWC_SHIFT) & CCSR_DMA_MR_BWC_MASK)
+#define CCSR_DMA_MR_EMP_EN 0x00200000
+#define CCSR_DMA_MR_EMS_EN 0x00040000
+#define CCSR_DMA_MR_DAHTS_MASK 0x00030000
+#define CCSR_DMA_MR_DAHTS_1 0x00000000
+#define CCSR_DMA_MR_DAHTS_2 0x00010000
+#define CCSR_DMA_MR_DAHTS_4 0x00020000
+#define CCSR_DMA_MR_DAHTS_8 0x00030000
+#define CCSR_DMA_MR_SAHTS_MASK 0x0000C000
+#define CCSR_DMA_MR_SAHTS_1 0x00000000
+#define CCSR_DMA_MR_SAHTS_2 0x00004000
+#define CCSR_DMA_MR_SAHTS_4 0x00008000
+#define CCSR_DMA_MR_SAHTS_8 0x0000C000
+#define CCSR_DMA_MR_DAHE 0x00002000
+#define CCSR_DMA_MR_SAHE 0x00001000
+#define CCSR_DMA_MR_SRW 0x00000400
+#define CCSR_DMA_MR_EOSIE 0x00000200
+#define CCSR_DMA_MR_EOLNIE 0x00000100
+#define CCSR_DMA_MR_EOLSIE 0x00000080
+#define CCSR_DMA_MR_EIE 0x00000040
+#define CCSR_DMA_MR_XFE 0x00000020
+#define CCSR_DMA_MR_CDSM_SWSM 0x00000010
+#define CCSR_DMA_MR_CA 0x00000008
+#define CCSR_DMA_MR_CTM 0x00000004
+#define CCSR_DMA_MR_CC 0x00000002
+#define CCSR_DMA_MR_CS 0x00000001
+
+#define CCSR_DMA_SR_TE 0x00000080
+#define CCSR_DMA_SR_CH 0x00000020
+#define CCSR_DMA_SR_PE 0x00000010
+#define CCSR_DMA_SR_EOLNI 0x00000008
+#define CCSR_DMA_SR_CB 0x00000004
+#define CCSR_DMA_SR_EOSI 0x00000002
+#define CCSR_DMA_SR_EOLSI 0x00000001
+
+/* ECLNDAR takes bits 32-36 of the CLNDAR register */
+static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x)
+{
+ return (x >> 32) & 0xf;
+}
+
+#define CCSR_DMA_CLNDAR_ADDR(x) ((x) & 0xFFFFFFFE)
+#define CCSR_DMA_CLNDAR_EOSIE 0x00000008
+
+/* SATR and DATR, combined */
+#define CCSR_DMA_ATR_PBATMU 0x20000000
+#define CCSR_DMA_ATR_TFLOWLVL_0 0x00000000
+#define CCSR_DMA_ATR_TFLOWLVL_1 0x06000000
+#define CCSR_DMA_ATR_TFLOWLVL_2 0x08000000
+#define CCSR_DMA_ATR_TFLOWLVL_3 0x0C000000
+#define CCSR_DMA_ATR_PCIORDER 0x02000000
+#define CCSR_DMA_ATR_SME 0x01000000
+#define CCSR_DMA_ATR_NOSNOOP 0x00040000
+#define CCSR_DMA_ATR_SNOOP 0x00050000
+#define CCSR_DMA_ATR_ESAD_MASK 0x0000000F
+
+/**
+ * List Descriptor for extended chaining mode DMA operations.
+ *
+ * The CLSDAR register points to the first (in a linked-list) List
+ * Descriptor. Each object must be aligned on a 32-byte boundary. Each
+ * list descriptor points to a linked-list of link Descriptors.
+ */
+struct fsl_dma_list_descriptor {
+ __be64 next; /* Address of next list descriptor */
+ __be64 first_link; /* Address of first link descriptor */
+ __be32 source; /* Source stride */
+ __be32 dest; /* Destination stride */
+ u8 res[8]; /* Reserved */
+} __attribute__ ((aligned(32), packed));
+
+/**
+ * Link Descriptor for basic and extended chaining mode DMA operations.
+ *
+ * A Link Descriptor points to a single DMA buffer. Each link descriptor
+ * must be aligned on a 32-byte boundary.
+ */
+struct fsl_dma_link_descriptor {
+ __be32 source_attr; /* Programmed into SATR register */
+ __be32 source_addr; /* Programmed into SAR register */
+ __be32 dest_attr; /* Programmed into DATR register */
+ __be32 dest_addr; /* Programmed into DAR register */
+ __be64 next; /* Address of next link descriptor */
+ __be32 count; /* Byte count */
+ u8 res[4]; /* Reserved */
+} __attribute__ ((aligned(32), packed));
+
+#endif
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
new file mode 100644
index 00000000..bd811a04
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -0,0 +1,804 @@
+/*
+ * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/of_platform.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "fsl_ssi.h"
+
+/**
+ * FSLSSI_I2S_RATES: sample rates supported by the I2S
+ *
+ * This driver currently only supports the SSI running in I2S slave mode,
+ * which means the codec determines the sample rate. Therefore, we tell
+ * ALSA that we support all rates and let the codec driver decide what rates
+ * are really supported.
+ */
+#define FSLSSI_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+ SNDRV_PCM_RATE_CONTINUOUS)
+
+/**
+ * FSLSSI_I2S_FORMATS: audio formats supported by the SSI
+ *
+ * This driver currently only supports the SSI running in I2S slave mode.
+ *
+ * The SSI has a limitation in that the samples must be in the same byte
+ * order as the host CPU. This is because when multiple bytes are written
+ * to the STX register, the bytes and bits must be written in the same
+ * order. The STX is a shift register, so all the bits need to be aligned
+ * (bit-endianness must match byte-endianness). Processors typically write
+ * the bits within a byte in the same order that the bytes of a word are
+ * written in. So if the host CPU is big-endian, then only big-endian
+ * samples will be written to STX properly.
+ */
+#ifdef __BIG_ENDIAN
+#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_S20_3BE | \
+ SNDRV_PCM_FMTBIT_S24_3BE | SNDRV_PCM_FMTBIT_S24_BE)
+#else
+#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE)
+#endif
+
+/* SIER bitflag of interrupts to enable */
+#define SIER_FLAGS (CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE | \
+ CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN | \
+ CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN | \
+ CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE | \
+ CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN)
+
+/**
+ * fsl_ssi_private: per-SSI private data
+ *
+ * @ssi: pointer to the SSI's registers
+ * @ssi_phys: physical address of the SSI registers
+ * @irq: IRQ of this SSI
+ * @first_stream: pointer to the stream that was opened first
+ * @second_stream: pointer to second stream
+ * @playback: the number of playback streams opened
+ * @capture: the number of capture streams opened
+ * @asynchronous: 0=synchronous mode, 1=asynchronous mode
+ * @cpu_dai: the CPU DAI for this device
+ * @dev_attr: the sysfs device attribute structure
+ * @stats: SSI statistics
+ * @name: name for this device
+ */
+struct fsl_ssi_private {
+ struct ccsr_ssi __iomem *ssi;
+ dma_addr_t ssi_phys;
+ unsigned int irq;
+ struct snd_pcm_substream *first_stream;
+ struct snd_pcm_substream *second_stream;
+ unsigned int playback;
+ unsigned int capture;
+ int asynchronous;
+ unsigned int fifo_depth;
+ struct snd_soc_dai_driver cpu_dai_drv;
+ struct device_attribute dev_attr;
+ struct platform_device *pdev;
+
+ struct {
+ unsigned int rfrc;
+ unsigned int tfrc;
+ unsigned int cmdau;
+ unsigned int cmddu;
+ unsigned int rxt;
+ unsigned int rdr1;
+ unsigned int rdr0;
+ unsigned int tde1;
+ unsigned int tde0;
+ unsigned int roe1;
+ unsigned int roe0;
+ unsigned int tue1;
+ unsigned int tue0;
+ unsigned int tfs;
+ unsigned int rfs;
+ unsigned int tls;
+ unsigned int rls;
+ unsigned int rff1;
+ unsigned int rff0;
+ unsigned int tfe1;
+ unsigned int tfe0;
+ } stats;
+
+ char name[1];
+};
+
+/**
+ * fsl_ssi_isr: SSI interrupt handler
+ *
+ * Although it's possible to use the interrupt handler to send and receive
+ * data to/from the SSI, we use the DMA instead. Programming is more
+ * complicated, but the performance is much better.
+ *
+ * This interrupt handler is used only to gather statistics.
+ *
+ * @irq: IRQ of the SSI device
+ * @dev_id: pointer to the ssi_private structure for this SSI device
+ */
+static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
+{
+ struct fsl_ssi_private *ssi_private = dev_id;
+ struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ irqreturn_t ret = IRQ_NONE;
+ __be32 sisr;
+ __be32 sisr2 = 0;
+
+ /* We got an interrupt, so read the status register to see what we
+ were interrupted for. We mask it with the Interrupt Enable register
+ so that we only check for events that we're interested in.
+ */
+ sisr = in_be32(&ssi->sisr) & SIER_FLAGS;
+
+ if (sisr & CCSR_SSI_SISR_RFRC) {
+ ssi_private->stats.rfrc++;
+ sisr2 |= CCSR_SSI_SISR_RFRC;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TFRC) {
+ ssi_private->stats.tfrc++;
+ sisr2 |= CCSR_SSI_SISR_TFRC;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_CMDAU) {
+ ssi_private->stats.cmdau++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_CMDDU) {
+ ssi_private->stats.cmddu++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RXT) {
+ ssi_private->stats.rxt++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RDR1) {
+ ssi_private->stats.rdr1++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RDR0) {
+ ssi_private->stats.rdr0++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TDE1) {
+ ssi_private->stats.tde1++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TDE0) {
+ ssi_private->stats.tde0++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_ROE1) {
+ ssi_private->stats.roe1++;
+ sisr2 |= CCSR_SSI_SISR_ROE1;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_ROE0) {
+ ssi_private->stats.roe0++;
+ sisr2 |= CCSR_SSI_SISR_ROE0;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TUE1) {
+ ssi_private->stats.tue1++;
+ sisr2 |= CCSR_SSI_SISR_TUE1;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TUE0) {
+ ssi_private->stats.tue0++;
+ sisr2 |= CCSR_SSI_SISR_TUE0;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TFS) {
+ ssi_private->stats.tfs++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RFS) {
+ ssi_private->stats.rfs++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TLS) {
+ ssi_private->stats.tls++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RLS) {
+ ssi_private->stats.rls++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RFF1) {
+ ssi_private->stats.rff1++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RFF0) {
+ ssi_private->stats.rff0++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TFE1) {
+ ssi_private->stats.tfe1++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TFE0) {
+ ssi_private->stats.tfe0++;
+ ret = IRQ_HANDLED;
+ }
+
+ /* Clear the bits that we set */
+ if (sisr2)
+ out_be32(&ssi->sisr, sisr2);
+
+ return ret;
+}
+
+/**
+ * fsl_ssi_startup: create a new substream
+ *
+ * This is the first function called when a stream is opened.
+ *
+ * If this is the first stream open, then grab the IRQ and program most of
+ * the SSI registers.
+ */
+static int fsl_ssi_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+ /*
+ * If this is the first stream opened, then request the IRQ
+ * and initialize the SSI registers.
+ */
+ if (!ssi_private->playback && !ssi_private->capture) {
+ struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ int ret;
+
+ /* The 'name' should not have any slashes in it. */
+ ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0,
+ ssi_private->name, ssi_private);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev,
+ "could not claim irq %u\n", ssi_private->irq);
+ return ret;
+ }
+
+ /*
+ * Section 16.5 of the MPC8610 reference manual says that the
+ * SSI needs to be disabled before updating the registers we set
+ * here.
+ */
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+
+ /*
+ * Program the SSI into I2S Slave Non-Network Synchronous mode.
+ * Also enable the transmit and receive FIFO.
+ *
+ * FIXME: Little-endian samples require a different shift dir
+ */
+ clrsetbits_be32(&ssi->scr,
+ CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN,
+ CCSR_SSI_SCR_TFR_CLK_DIS | CCSR_SSI_SCR_I2S_MODE_SLAVE
+ | (ssi_private->asynchronous ? 0 : CCSR_SSI_SCR_SYN));
+
+ out_be32(&ssi->stcr,
+ CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
+ CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS |
+ CCSR_SSI_STCR_TSCKP);
+
+ out_be32(&ssi->srcr,
+ CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 |
+ CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS |
+ CCSR_SSI_SRCR_RSCKP);
+
+ /*
+ * The DC and PM bits are only used if the SSI is the clock
+ * master.
+ */
+
+ /* 4. Enable the interrupts and DMA requests */
+ out_be32(&ssi->sier, SIER_FLAGS);
+
+ /*
+ * Set the watermark for transmit FIFI 0 and receive FIFO 0. We
+ * don't use FIFO 1. We program the transmit water to signal a
+ * DMA transfer if there are only two (or fewer) elements left
+ * in the FIFO. Two elements equals one frame (left channel,
+ * right channel). This value, however, depends on the depth of
+ * the transmit buffer.
+ *
+ * We program the receive FIFO to notify us if at least two
+ * elements (one frame) have been written to the FIFO. We could
+ * make this value larger (and maybe we should), but this way
+ * data will be written to memory as soon as it's available.
+ */
+ out_be32(&ssi->sfcsr,
+ CCSR_SSI_SFCSR_TFWM0(ssi_private->fifo_depth - 2) |
+ CCSR_SSI_SFCSR_RFWM0(ssi_private->fifo_depth - 2));
+
+ /*
+ * We keep the SSI disabled because if we enable it, then the
+ * DMA controller will start. It's not supposed to start until
+ * the SCR.TE (or SCR.RE) bit is set, but it does anyway. The
+ * DMA controller will transfer one "BWC" of data (i.e. the
+ * amount of data that the MR.BWC bits are set to). The reason
+ * this is bad is because at this point, the PCM driver has not
+ * finished initializing the DMA controller.
+ */
+ }
+
+ if (!ssi_private->first_stream)
+ ssi_private->first_stream = substream;
+ else {
+ /* This is the second stream open, so we need to impose sample
+ * rate and maybe sample size constraints. Note that this can
+ * cause a race condition if the second stream is opened before
+ * the first stream is fully initialized.
+ *
+ * We provide some protection by checking to make sure the first
+ * stream is initialized, but it's not perfect. ALSA sometimes
+ * re-initializes the driver with a different sample rate or
+ * size. If the second stream is opened before the first stream
+ * has received its final parameters, then the second stream may
+ * be constrained to the wrong sample rate or size.
+ *
+ * FIXME: This code does not handle opening and closing streams
+ * repeatedly. If you open two streams and then close the first
+ * one, you may not be able to open another stream until you
+ * close the second one as well.
+ */
+ struct snd_pcm_runtime *first_runtime =
+ ssi_private->first_stream->runtime;
+
+ if (!first_runtime->sample_bits) {
+ dev_err(substream->pcm->card->dev,
+ "set sample size in %s stream first\n",
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+ ? "capture" : "playback");
+ return -EAGAIN;
+ }
+
+ /* If we're in synchronous mode, then we need to constrain
+ * the sample size as well. We don't support independent sample
+ * rates in asynchronous mode.
+ */
+ if (!ssi_private->asynchronous)
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ first_runtime->sample_bits,
+ first_runtime->sample_bits);
+
+ ssi_private->second_stream = substream;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ssi_private->playback++;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ ssi_private->capture++;
+
+ return 0;
+}
+
+/**
+ * fsl_ssi_hw_params - program the sample size
+ *
+ * Most of the SSI registers have been programmed in the startup function,
+ * but the word length must be programmed here. Unfortunately, programming
+ * the SxCCR.WL bits requires the SSI to be temporarily disabled. This can
+ * cause a problem with supporting simultaneous playback and capture. If
+ * the SSI is already playing a stream, then that stream may be temporarily
+ * stopped when you start capture.
+ *
+ * Note: The SxCCR.DC and SxCCR.PM bits are only used if the SSI is the
+ * clock master.
+ */
+static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai);
+
+ if (substream == ssi_private->first_stream) {
+ struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ unsigned int sample_size =
+ snd_pcm_format_width(params_format(hw_params));
+ u32 wl = CCSR_SSI_SxCCR_WL(sample_size);
+
+ /* The SSI should always be disabled at this points (SSIEN=0) */
+
+ /* In synchronous mode, the SSI uses STCCR for capture */
+ if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ||
+ !ssi_private->asynchronous)
+ clrsetbits_be32(&ssi->stccr,
+ CCSR_SSI_SxCCR_WL_MASK, wl);
+ else
+ clrsetbits_be32(&ssi->srccr,
+ CCSR_SSI_SxCCR_WL_MASK, wl);
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_ssi_trigger: start and stop the DMA transfer.
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the DMA
+ * transfer of data.
+ *
+ * The DMA channel is in external master start and pause mode, which
+ * means the SSI completely controls the flow of data.
+ */
+static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ setbits32(&ssi->scr,
+ CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE);
+ else
+ setbits32(&ssi->scr,
+ CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_TE);
+ else
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_RE);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_ssi_shutdown: shutdown the SSI
+ *
+ * Shutdown the SSI if there are no other substreams open.
+ */
+static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ssi_private->playback--;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ ssi_private->capture--;
+
+ if (ssi_private->first_stream == substream)
+ ssi_private->first_stream = ssi_private->second_stream;
+
+ ssi_private->second_stream = NULL;
+
+ /*
+ * If this is the last active substream, disable the SSI and release
+ * the IRQ.
+ */
+ if (!ssi_private->playback && !ssi_private->capture) {
+ struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+
+ free_irq(ssi_private->irq, ssi_private);
+ }
+}
+
+static struct snd_soc_dai_ops fsl_ssi_dai_ops = {
+ .startup = fsl_ssi_startup,
+ .hw_params = fsl_ssi_hw_params,
+ .shutdown = fsl_ssi_shutdown,
+ .trigger = fsl_ssi_trigger,
+};
+
+/* Template for the CPU dai driver structure */
+static struct snd_soc_dai_driver fsl_ssi_dai_template = {
+ .playback = {
+ /* The SSI does not support monaural audio. */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSLSSI_I2S_RATES,
+ .formats = FSLSSI_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSLSSI_I2S_RATES,
+ .formats = FSLSSI_I2S_FORMATS,
+ },
+ .ops = &fsl_ssi_dai_ops,
+};
+
+/* Show the statistics of a flag only if its interrupt is enabled. The
+ * compiler will optimze this code to a no-op if the interrupt is not
+ * enabled.
+ */
+#define SIER_SHOW(flag, name) \
+ do { \
+ if (SIER_FLAGS & CCSR_SSI_SIER_##flag) \
+ length += sprintf(buf + length, #name "=%u\n", \
+ ssi_private->stats.name); \
+ } while (0)
+
+
+/**
+ * fsl_sysfs_ssi_show: display SSI statistics
+ *
+ * Display the statistics for the current SSI device. To avoid confusion,
+ * we only show those counts that are enabled.
+ */
+static ssize_t fsl_sysfs_ssi_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fsl_ssi_private *ssi_private =
+ container_of(attr, struct fsl_ssi_private, dev_attr);
+ ssize_t length = 0;
+
+ SIER_SHOW(RFRC_EN, rfrc);
+ SIER_SHOW(TFRC_EN, tfrc);
+ SIER_SHOW(CMDAU_EN, cmdau);
+ SIER_SHOW(CMDDU_EN, cmddu);
+ SIER_SHOW(RXT_EN, rxt);
+ SIER_SHOW(RDR1_EN, rdr1);
+ SIER_SHOW(RDR0_EN, rdr0);
+ SIER_SHOW(TDE1_EN, tde1);
+ SIER_SHOW(TDE0_EN, tde0);
+ SIER_SHOW(ROE1_EN, roe1);
+ SIER_SHOW(ROE0_EN, roe0);
+ SIER_SHOW(TUE1_EN, tue1);
+ SIER_SHOW(TUE0_EN, tue0);
+ SIER_SHOW(TFS_EN, tfs);
+ SIER_SHOW(RFS_EN, rfs);
+ SIER_SHOW(TLS_EN, tls);
+ SIER_SHOW(RLS_EN, rls);
+ SIER_SHOW(RFF1_EN, rff1);
+ SIER_SHOW(RFF0_EN, rff0);
+ SIER_SHOW(TFE1_EN, tfe1);
+ SIER_SHOW(TFE0_EN, tfe0);
+
+ return length;
+}
+
+/**
+ * Make every character in a string lower-case
+ */
+static void make_lowercase(char *s)
+{
+ char *p = s;
+ char c;
+
+ while ((c = *p)) {
+ if ((c >= 'A') && (c <= 'Z'))
+ *p = c + ('a' - 'A');
+ p++;
+ }
+}
+
+static int __devinit fsl_ssi_probe(struct platform_device *pdev)
+{
+ struct fsl_ssi_private *ssi_private;
+ int ret = 0;
+ struct device_attribute *dev_attr = NULL;
+ struct device_node *np = pdev->dev.of_node;
+ const char *p, *sprop;
+ const uint32_t *iprop;
+ struct resource res;
+ char name[64];
+
+ /* SSIs that are not connected on the board should have a
+ * status = "disabled"
+ * property in their device tree nodes.
+ */
+ if (!of_device_is_available(np))
+ return -ENODEV;
+
+ /* Check for a codec-handle property. */
+ if (!of_get_property(np, "codec-handle", NULL)) {
+ dev_err(&pdev->dev, "missing codec-handle property\n");
+ return -ENODEV;
+ }
+
+ /* We only support the SSI in "I2S Slave" mode */
+ sprop = of_get_property(np, "fsl,mode", NULL);
+ if (!sprop || strcmp(sprop, "i2s-slave")) {
+ dev_notice(&pdev->dev, "mode %s is unsupported\n", sprop);
+ return -ENODEV;
+ }
+
+ /* The DAI name is the last part of the full name of the node. */
+ p = strrchr(np->full_name, '/') + 1;
+ ssi_private = kzalloc(sizeof(struct fsl_ssi_private) + strlen(p),
+ GFP_KERNEL);
+ if (!ssi_private) {
+ dev_err(&pdev->dev, "could not allocate DAI object\n");
+ return -ENOMEM;
+ }
+
+ strcpy(ssi_private->name, p);
+
+ /* Initialize this copy of the CPU DAI driver structure */
+ memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_dai_template,
+ sizeof(fsl_ssi_dai_template));
+ ssi_private->cpu_dai_drv.name = ssi_private->name;
+
+ /* Get the addresses and IRQ */
+ ret = of_address_to_resource(np, 0, &res);
+ if (ret) {
+ dev_err(&pdev->dev, "could not determine device resources\n");
+ kfree(ssi_private);
+ return ret;
+ }
+ ssi_private->ssi = ioremap(res.start, 1 + res.end - res.start);
+ ssi_private->ssi_phys = res.start;
+ ssi_private->irq = irq_of_parse_and_map(np, 0);
+
+ /* Are the RX and the TX clocks locked? */
+ if (of_find_property(np, "fsl,ssi-asynchronous", NULL))
+ ssi_private->asynchronous = 1;
+ else
+ ssi_private->cpu_dai_drv.symmetric_rates = 1;
+
+ /* Determine the FIFO depth. */
+ iprop = of_get_property(np, "fsl,fifo-depth", NULL);
+ if (iprop)
+ ssi_private->fifo_depth = *iprop;
+ else
+ /* Older 8610 DTs didn't have the fifo-depth property */
+ ssi_private->fifo_depth = 8;
+
+ /* Initialize the the device_attribute structure */
+ dev_attr = &ssi_private->dev_attr;
+ sysfs_attr_init(&dev_attr->attr);
+ dev_attr->attr.name = "statistics";
+ dev_attr->attr.mode = S_IRUGO;
+ dev_attr->show = fsl_sysfs_ssi_show;
+
+ ret = device_create_file(&pdev->dev, dev_attr);
+ if (ret) {
+ dev_err(&pdev->dev, "could not create sysfs %s file\n",
+ ssi_private->dev_attr.attr.name);
+ goto error;
+ }
+
+ /* Register with ASoC */
+ dev_set_drvdata(&pdev->dev, ssi_private);
+
+ ret = snd_soc_register_dai(&pdev->dev, &ssi_private->cpu_dai_drv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register DAI: %d\n", ret);
+ goto error;
+ }
+
+ /* Trigger the machine driver's probe function. The platform driver
+ * name of the machine driver is taken from the /model property of the
+ * device tree. We also pass the address of the CPU DAI driver
+ * structure.
+ */
+ sprop = of_get_property(of_find_node_by_path("/"), "model", NULL);
+ /* Sometimes the model name has a "fsl," prefix, so we strip that. */
+ p = strrchr(sprop, ',');
+ if (p)
+ sprop = p + 1;
+ snprintf(name, sizeof(name), "snd-soc-%s", sprop);
+ make_lowercase(name);
+
+ ssi_private->pdev =
+ platform_device_register_data(&pdev->dev, name, 0, NULL, 0);
+ if (IS_ERR(ssi_private->pdev)) {
+ ret = PTR_ERR(ssi_private->pdev);
+ dev_err(&pdev->dev, "failed to register platform: %d\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ snd_soc_unregister_dai(&pdev->dev);
+ dev_set_drvdata(&pdev->dev, NULL);
+ if (dev_attr)
+ device_remove_file(&pdev->dev, dev_attr);
+ irq_dispose_mapping(ssi_private->irq);
+ iounmap(ssi_private->ssi);
+ kfree(ssi_private);
+
+ return ret;
+}
+
+static int fsl_ssi_remove(struct platform_device *pdev)
+{
+ struct fsl_ssi_private *ssi_private = dev_get_drvdata(&pdev->dev);
+
+ platform_device_unregister(ssi_private->pdev);
+ snd_soc_unregister_dai(&pdev->dev);
+ device_remove_file(&pdev->dev, &ssi_private->dev_attr);
+
+ kfree(ssi_private);
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_ssi_ids[] = {
+ { .compatible = "fsl,mpc8610-ssi", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_ssi_ids);
+
+static struct platform_driver fsl_ssi_driver = {
+ .driver = {
+ .name = "fsl-ssi-dai",
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_ssi_ids,
+ },
+ .probe = fsl_ssi_probe,
+ .remove = fsl_ssi_remove,
+};
+
+static int __init fsl_ssi_init(void)
+{
+ printk(KERN_INFO "Freescale Synchronous Serial Interface (SSI) ASoC Driver\n");
+
+ return platform_driver_register(&fsl_ssi_driver);
+}
+
+static void __exit fsl_ssi_exit(void)
+{
+ platform_driver_unregister(&fsl_ssi_driver);
+}
+
+module_init(fsl_ssi_init);
+module_exit(fsl_ssi_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h
new file mode 100644
index 00000000..21730002
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi.h
@@ -0,0 +1,200 @@
+/*
+ * fsl_ssi.h - ALSA SSI interface for the Freescale MPC8610 SoC
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
+ * under the terms of the GNU General Public License version 2. This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ */
+
+#ifndef _MPC8610_I2S_H
+#define _MPC8610_I2S_H
+
+/* SSI Register Map */
+struct ccsr_ssi {
+ __be32 stx0; /* 0x.0000 - SSI Transmit Data Register 0 */
+ __be32 stx1; /* 0x.0004 - SSI Transmit Data Register 1 */
+ __be32 srx0; /* 0x.0008 - SSI Receive Data Register 0 */
+ __be32 srx1; /* 0x.000C - SSI Receive Data Register 1 */
+ __be32 scr; /* 0x.0010 - SSI Control Register */
+ __be32 sisr; /* 0x.0014 - SSI Interrupt Status Register Mixed */
+ __be32 sier; /* 0x.0018 - SSI Interrupt Enable Register */
+ __be32 stcr; /* 0x.001C - SSI Transmit Configuration Register */
+ __be32 srcr; /* 0x.0020 - SSI Receive Configuration Register */
+ __be32 stccr; /* 0x.0024 - SSI Transmit Clock Control Register */
+ __be32 srccr; /* 0x.0028 - SSI Receive Clock Control Register */
+ __be32 sfcsr; /* 0x.002C - SSI FIFO Control/Status Register */
+ __be32 str; /* 0x.0030 - SSI Test Register */
+ __be32 sor; /* 0x.0034 - SSI Option Register */
+ __be32 sacnt; /* 0x.0038 - SSI AC97 Control Register */
+ __be32 sacadd; /* 0x.003C - SSI AC97 Command Address Register */
+ __be32 sacdat; /* 0x.0040 - SSI AC97 Command Data Register */
+ __be32 satag; /* 0x.0044 - SSI AC97 Tag Register */
+ __be32 stmsk; /* 0x.0048 - SSI Transmit Time Slot Mask Register */
+ __be32 srmsk; /* 0x.004C - SSI Receive Time Slot Mask Register */
+ __be32 saccst; /* 0x.0050 - SSI AC97 Channel Status Register */
+ __be32 saccen; /* 0x.0054 - SSI AC97 Channel Enable Register */
+ __be32 saccdis; /* 0x.0058 - SSI AC97 Channel Disable Register */
+};
+
+#define CCSR_SSI_SCR_RFR_CLK_DIS 0x00000800
+#define CCSR_SSI_SCR_TFR_CLK_DIS 0x00000400
+#define CCSR_SSI_SCR_TCH_EN 0x00000100
+#define CCSR_SSI_SCR_SYS_CLK_EN 0x00000080
+#define CCSR_SSI_SCR_I2S_MODE_MASK 0x00000060
+#define CCSR_SSI_SCR_I2S_MODE_NORMAL 0x00000000
+#define CCSR_SSI_SCR_I2S_MODE_MASTER 0x00000020
+#define CCSR_SSI_SCR_I2S_MODE_SLAVE 0x00000040
+#define CCSR_SSI_SCR_SYN 0x00000010
+#define CCSR_SSI_SCR_NET 0x00000008
+#define CCSR_SSI_SCR_RE 0x00000004
+#define CCSR_SSI_SCR_TE 0x00000002
+#define CCSR_SSI_SCR_SSIEN 0x00000001
+
+#define CCSR_SSI_SISR_RFRC 0x01000000
+#define CCSR_SSI_SISR_TFRC 0x00800000
+#define CCSR_SSI_SISR_CMDAU 0x00040000
+#define CCSR_SSI_SISR_CMDDU 0x00020000
+#define CCSR_SSI_SISR_RXT 0x00010000
+#define CCSR_SSI_SISR_RDR1 0x00008000
+#define CCSR_SSI_SISR_RDR0 0x00004000
+#define CCSR_SSI_SISR_TDE1 0x00002000
+#define CCSR_SSI_SISR_TDE0 0x00001000
+#define CCSR_SSI_SISR_ROE1 0x00000800
+#define CCSR_SSI_SISR_ROE0 0x00000400
+#define CCSR_SSI_SISR_TUE1 0x00000200
+#define CCSR_SSI_SISR_TUE0 0x00000100
+#define CCSR_SSI_SISR_TFS 0x00000080
+#define CCSR_SSI_SISR_RFS 0x00000040
+#define CCSR_SSI_SISR_TLS 0x00000020
+#define CCSR_SSI_SISR_RLS 0x00000010
+#define CCSR_SSI_SISR_RFF1 0x00000008
+#define CCSR_SSI_SISR_RFF0 0x00000004
+#define CCSR_SSI_SISR_TFE1 0x00000002
+#define CCSR_SSI_SISR_TFE0 0x00000001
+
+#define CCSR_SSI_SIER_RFRC_EN 0x01000000
+#define CCSR_SSI_SIER_TFRC_EN 0x00800000
+#define CCSR_SSI_SIER_RDMAE 0x00400000
+#define CCSR_SSI_SIER_RIE 0x00200000
+#define CCSR_SSI_SIER_TDMAE 0x00100000
+#define CCSR_SSI_SIER_TIE 0x00080000
+#define CCSR_SSI_SIER_CMDAU_EN 0x00040000
+#define CCSR_SSI_SIER_CMDDU_EN 0x00020000
+#define CCSR_SSI_SIER_RXT_EN 0x00010000
+#define CCSR_SSI_SIER_RDR1_EN 0x00008000
+#define CCSR_SSI_SIER_RDR0_EN 0x00004000
+#define CCSR_SSI_SIER_TDE1_EN 0x00002000
+#define CCSR_SSI_SIER_TDE0_EN 0x00001000
+#define CCSR_SSI_SIER_ROE1_EN 0x00000800
+#define CCSR_SSI_SIER_ROE0_EN 0x00000400
+#define CCSR_SSI_SIER_TUE1_EN 0x00000200
+#define CCSR_SSI_SIER_TUE0_EN 0x00000100
+#define CCSR_SSI_SIER_TFS_EN 0x00000080
+#define CCSR_SSI_SIER_RFS_EN 0x00000040
+#define CCSR_SSI_SIER_TLS_EN 0x00000020
+#define CCSR_SSI_SIER_RLS_EN 0x00000010
+#define CCSR_SSI_SIER_RFF1_EN 0x00000008
+#define CCSR_SSI_SIER_RFF0_EN 0x00000004
+#define CCSR_SSI_SIER_TFE1_EN 0x00000002
+#define CCSR_SSI_SIER_TFE0_EN 0x00000001
+
+#define CCSR_SSI_STCR_TXBIT0 0x00000200
+#define CCSR_SSI_STCR_TFEN1 0x00000100
+#define CCSR_SSI_STCR_TFEN0 0x00000080
+#define CCSR_SSI_STCR_TFDIR 0x00000040
+#define CCSR_SSI_STCR_TXDIR 0x00000020
+#define CCSR_SSI_STCR_TSHFD 0x00000010
+#define CCSR_SSI_STCR_TSCKP 0x00000008
+#define CCSR_SSI_STCR_TFSI 0x00000004
+#define CCSR_SSI_STCR_TFSL 0x00000002
+#define CCSR_SSI_STCR_TEFS 0x00000001
+
+#define CCSR_SSI_SRCR_RXEXT 0x00000400
+#define CCSR_SSI_SRCR_RXBIT0 0x00000200
+#define CCSR_SSI_SRCR_RFEN1 0x00000100
+#define CCSR_SSI_SRCR_RFEN0 0x00000080
+#define CCSR_SSI_SRCR_RFDIR 0x00000040
+#define CCSR_SSI_SRCR_RXDIR 0x00000020
+#define CCSR_SSI_SRCR_RSHFD 0x00000010
+#define CCSR_SSI_SRCR_RSCKP 0x00000008
+#define CCSR_SSI_SRCR_RFSI 0x00000004
+#define CCSR_SSI_SRCR_RFSL 0x00000002
+#define CCSR_SSI_SRCR_REFS 0x00000001
+
+/* STCCR and SRCCR */
+#define CCSR_SSI_SxCCR_DIV2 0x00040000
+#define CCSR_SSI_SxCCR_PSR 0x00020000
+#define CCSR_SSI_SxCCR_WL_SHIFT 13
+#define CCSR_SSI_SxCCR_WL_MASK 0x0001E000
+#define CCSR_SSI_SxCCR_WL(x) \
+ (((((x) / 2) - 1) << CCSR_SSI_SxCCR_WL_SHIFT) & CCSR_SSI_SxCCR_WL_MASK)
+#define CCSR_SSI_SxCCR_DC_SHIFT 8
+#define CCSR_SSI_SxCCR_DC_MASK 0x00001F00
+#define CCSR_SSI_SxCCR_DC(x) \
+ ((((x) - 1) << CCSR_SSI_SxCCR_DC_SHIFT) & CCSR_SSI_SxCCR_DC_MASK)
+#define CCSR_SSI_SxCCR_PM_SHIFT 0
+#define CCSR_SSI_SxCCR_PM_MASK 0x000000FF
+#define CCSR_SSI_SxCCR_PM(x) \
+ ((((x) - 1) << CCSR_SSI_SxCCR_PM_SHIFT) & CCSR_SSI_SxCCR_PM_MASK)
+
+/*
+ * The xFCNT bits are read-only, and the xFWM bits are read/write. Use the
+ * CCSR_SSI_SFCSR_xFCNTy() macros to read the FIFO counters, and use the
+ * CCSR_SSI_SFCSR_xFWMy() macros to set the watermarks.
+ */
+#define CCSR_SSI_SFCSR_RFCNT1_SHIFT 28
+#define CCSR_SSI_SFCSR_RFCNT1_MASK 0xF0000000
+#define CCSR_SSI_SFCSR_RFCNT1(x) \
+ (((x) & CCSR_SSI_SFCSR_RFCNT1_MASK) >> CCSR_SSI_SFCSR_RFCNT1_SHIFT)
+#define CCSR_SSI_SFCSR_TFCNT1_SHIFT 24
+#define CCSR_SSI_SFCSR_TFCNT1_MASK 0x0F000000
+#define CCSR_SSI_SFCSR_TFCNT1(x) \
+ (((x) & CCSR_SSI_SFCSR_TFCNT1_MASK) >> CCSR_SSI_SFCSR_TFCNT1_SHIFT)
+#define CCSR_SSI_SFCSR_RFWM1_SHIFT 20
+#define CCSR_SSI_SFCSR_RFWM1_MASK 0x00F00000
+#define CCSR_SSI_SFCSR_RFWM1(x) \
+ (((x) << CCSR_SSI_SFCSR_RFWM1_SHIFT) & CCSR_SSI_SFCSR_RFWM1_MASK)
+#define CCSR_SSI_SFCSR_TFWM1_SHIFT 16
+#define CCSR_SSI_SFCSR_TFWM1_MASK 0x000F0000
+#define CCSR_SSI_SFCSR_TFWM1(x) \
+ (((x) << CCSR_SSI_SFCSR_TFWM1_SHIFT) & CCSR_SSI_SFCSR_TFWM1_MASK)
+#define CCSR_SSI_SFCSR_RFCNT0_SHIFT 12
+#define CCSR_SSI_SFCSR_RFCNT0_MASK 0x0000F000
+#define CCSR_SSI_SFCSR_RFCNT0(x) \
+ (((x) & CCSR_SSI_SFCSR_RFCNT0_MASK) >> CCSR_SSI_SFCSR_RFCNT0_SHIFT)
+#define CCSR_SSI_SFCSR_TFCNT0_SHIFT 8
+#define CCSR_SSI_SFCSR_TFCNT0_MASK 0x00000F00
+#define CCSR_SSI_SFCSR_TFCNT0(x) \
+ (((x) & CCSR_SSI_SFCSR_TFCNT0_MASK) >> CCSR_SSI_SFCSR_TFCNT0_SHIFT)
+#define CCSR_SSI_SFCSR_RFWM0_SHIFT 4
+#define CCSR_SSI_SFCSR_RFWM0_MASK 0x000000F0
+#define CCSR_SSI_SFCSR_RFWM0(x) \
+ (((x) << CCSR_SSI_SFCSR_RFWM0_SHIFT) & CCSR_SSI_SFCSR_RFWM0_MASK)
+#define CCSR_SSI_SFCSR_TFWM0_SHIFT 0
+#define CCSR_SSI_SFCSR_TFWM0_MASK 0x0000000F
+#define CCSR_SSI_SFCSR_TFWM0(x) \
+ (((x) << CCSR_SSI_SFCSR_TFWM0_SHIFT) & CCSR_SSI_SFCSR_TFWM0_MASK)
+
+#define CCSR_SSI_STR_TEST 0x00008000
+#define CCSR_SSI_STR_RCK2TCK 0x00004000
+#define CCSR_SSI_STR_RFS2TFS 0x00002000
+#define CCSR_SSI_STR_RXSTATE(x) (((x) >> 8) & 0x1F)
+#define CCSR_SSI_STR_TXD2RXD 0x00000080
+#define CCSR_SSI_STR_TCK2RCK 0x00000040
+#define CCSR_SSI_STR_TFS2RFS 0x00000020
+#define CCSR_SSI_STR_TXSTATE(x) ((x) & 0x1F)
+
+#define CCSR_SSI_SOR_CLKOFF 0x00000040
+#define CCSR_SSI_SOR_RX_CLR 0x00000020
+#define CCSR_SSI_SOR_TX_CLR 0x00000010
+#define CCSR_SSI_SOR_INIT 0x00000008
+#define CCSR_SSI_SOR_WAIT_SHIFT 1
+#define CCSR_SSI_SOR_WAIT_MASK 0x00000006
+#define CCSR_SSI_SOR_WAIT(x) (((x) & 3) << CCSR_SSI_SOR_WAIT_SHIFT)
+#define CCSR_SSI_SOR_SYNRST 0x00000001
+
+#endif
+
diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c
new file mode 100644
index 00000000..cbaf8b78
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_dma.c
@@ -0,0 +1,542 @@
+/*
+ * Freescale MPC5200 PSC DMA
+ * ALSA SoC Platform driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ * Copyright (C) 2009 Jon Smirl, Digispeaker
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/of_platform.h>
+
+#include <sound/soc.h>
+
+#include <sysdev/bestcomm/bestcomm.h>
+#include <sysdev/bestcomm/gen_bd.h>
+#include <asm/mpc52xx_psc.h>
+
+#include "mpc5200_dma.h"
+
+/*
+ * Interrupt handlers
+ */
+static irqreturn_t psc_dma_status_irq(int irq, void *_psc_dma)
+{
+ struct psc_dma *psc_dma = _psc_dma;
+ struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+ u16 isr;
+
+ isr = in_be16(&regs->mpc52xx_psc_isr);
+
+ /* Playback underrun error */
+ if (psc_dma->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP))
+ psc_dma->stats.underrun_count++;
+
+ /* Capture overrun error */
+ if (psc_dma->capture.active && (isr & MPC52xx_PSC_IMR_ORERR))
+ psc_dma->stats.overrun_count++;
+
+ out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * psc_dma_bcom_enqueue_next_buffer - Enqueue another audio buffer
+ * @s: pointer to stream private data structure
+ *
+ * Enqueues another audio period buffer into the bestcomm queue.
+ *
+ * Note: The routine must only be called when there is space available in
+ * the queue. Otherwise the enqueue will fail and the audio ring buffer
+ * will get out of sync
+ */
+static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s)
+{
+ struct bcom_bd *bd;
+
+ /* Prepare and enqueue the next buffer descriptor */
+ bd = bcom_prepare_next_buffer(s->bcom_task);
+ bd->status = s->period_bytes;
+ bd->data[0] = s->runtime->dma_addr + (s->period_next * s->period_bytes);
+ bcom_submit_next_buffer(s->bcom_task, NULL);
+
+ /* Update for next period */
+ s->period_next = (s->period_next + 1) % s->runtime->periods;
+}
+
+/* Bestcomm DMA irq handler */
+static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream)
+{
+ struct psc_dma_stream *s = _psc_dma_stream;
+
+ spin_lock(&s->psc_dma->lock);
+ /* For each finished period, dequeue the completed period buffer
+ * and enqueue a new one in it's place. */
+ while (bcom_buffer_done(s->bcom_task)) {
+ bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
+
+ s->period_current = (s->period_current+1) % s->runtime->periods;
+ s->period_count++;
+
+ psc_dma_bcom_enqueue_next_buffer(s);
+ }
+ spin_unlock(&s->psc_dma->lock);
+
+ /* If the stream is active, then also inform the PCM middle layer
+ * of the period finished event. */
+ if (s->active)
+ snd_pcm_period_elapsed(s->stream);
+
+ return IRQ_HANDLED;
+}
+
+static int psc_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+/**
+ * psc_dma_trigger: start and stop the DMA transfer.
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the DMA
+ * transfer of data.
+ */
+static int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
+ struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+ u16 imr;
+ unsigned long flags;
+ int i;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ dev_dbg(psc_dma->dev, "START: stream=%i fbits=%u ps=%u #p=%u\n",
+ substream->pstr->stream, runtime->frame_bits,
+ (int)runtime->period_size, runtime->periods);
+ s->period_bytes = frames_to_bytes(runtime,
+ runtime->period_size);
+ s->period_next = 0;
+ s->period_current = 0;
+ s->active = 1;
+ s->period_count = 0;
+ s->runtime = runtime;
+
+ /* Fill up the bestcomm bd queue and enable DMA.
+ * This will begin filling the PSC's fifo.
+ */
+ spin_lock_irqsave(&psc_dma->lock, flags);
+
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ bcom_gen_bd_rx_reset(s->bcom_task);
+ else
+ bcom_gen_bd_tx_reset(s->bcom_task);
+
+ for (i = 0; i < runtime->periods; i++)
+ if (!bcom_queue_full(s->bcom_task))
+ psc_dma_bcom_enqueue_next_buffer(s);
+
+ bcom_enable(s->bcom_task);
+ spin_unlock_irqrestore(&psc_dma->lock, flags);
+
+ out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ dev_dbg(psc_dma->dev, "STOP: stream=%i periods_count=%i\n",
+ substream->pstr->stream, s->period_count);
+ s->active = 0;
+
+ spin_lock_irqsave(&psc_dma->lock, flags);
+ bcom_disable(s->bcom_task);
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ bcom_gen_bd_rx_reset(s->bcom_task);
+ else
+ bcom_gen_bd_tx_reset(s->bcom_task);
+ spin_unlock_irqrestore(&psc_dma->lock, flags);
+
+ break;
+
+ default:
+ dev_dbg(psc_dma->dev, "unhandled trigger: stream=%i cmd=%i\n",
+ substream->pstr->stream, cmd);
+ return -EINVAL;
+ }
+
+ /* Update interrupt enable settings */
+ imr = 0;
+ if (psc_dma->playback.active)
+ imr |= MPC52xx_PSC_IMR_TXEMP;
+ if (psc_dma->capture.active)
+ imr |= MPC52xx_PSC_IMR_ORERR;
+ out_be16(&regs->isr_imr.imr, psc_dma->imr | imr);
+
+ return 0;
+}
+
+
+/* ---------------------------------------------------------------------
+ * The PSC DMA 'ASoC platform' driver
+ *
+ * Can be referenced by an 'ASoC machine' driver
+ * This driver only deals with the audio bus; it doesn't have any
+ * interaction with the attached codec
+ */
+
+static const struct snd_pcm_hardware psc_dma_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_BATCH,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .period_bytes_max = 1024 * 1024,
+ .period_bytes_min = 32,
+ .periods_min = 2,
+ .periods_max = 256,
+ .buffer_bytes_max = 2 * 1024 * 1024,
+ .fifo_size = 512,
+};
+
+static int psc_dma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ struct psc_dma_stream *s;
+ int rc;
+
+ dev_dbg(psc_dma->dev, "psc_dma_open(substream=%p)\n", substream);
+
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s = &psc_dma->capture;
+ else
+ s = &psc_dma->playback;
+
+ snd_soc_set_runtime_hwparams(substream, &psc_dma_hardware);
+
+ rc = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (rc < 0) {
+ dev_err(substream->pcm->card->dev, "invalid buffer size\n");
+ return rc;
+ }
+
+ s->stream = substream;
+ return 0;
+}
+
+static int psc_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ struct psc_dma_stream *s;
+
+ dev_dbg(psc_dma->dev, "psc_dma_close(substream=%p)\n", substream);
+
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s = &psc_dma->capture;
+ else
+ s = &psc_dma->playback;
+
+ if (!psc_dma->playback.active &&
+ !psc_dma->capture.active) {
+
+ /* Disable all interrupts and reset the PSC */
+ out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
+ out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */
+ }
+ s->stream = NULL;
+ return 0;
+}
+
+static snd_pcm_uframes_t
+psc_dma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ struct psc_dma_stream *s;
+ dma_addr_t count;
+
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s = &psc_dma->capture;
+ else
+ s = &psc_dma->playback;
+
+ count = s->period_current * s->period_bytes;
+
+ return bytes_to_frames(substream->runtime, count);
+}
+
+static int
+psc_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ return 0;
+}
+
+static struct snd_pcm_ops psc_dma_ops = {
+ .open = psc_dma_open,
+ .close = psc_dma_close,
+ .hw_free = psc_dma_hw_free,
+ .ioctl = snd_pcm_lib_ioctl,
+ .pointer = psc_dma_pointer,
+ .trigger = psc_dma_trigger,
+ .hw_params = psc_dma_hw_params,
+};
+
+static u64 psc_dma_dmamask = 0xffffffff;
+static int psc_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ size_t size = psc_dma_hardware.buffer_bytes_max;
+ int rc = 0;
+
+ dev_dbg(rtd->platform->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n",
+ card, dai, pcm);
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &psc_dma_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (pcm->streams[0].substream) {
+ rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+ size, &pcm->streams[0].substream->dma_buffer);
+ if (rc)
+ goto playback_alloc_err;
+ }
+
+ if (pcm->streams[1].substream) {
+ rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+ size, &pcm->streams[1].substream->dma_buffer);
+ if (rc)
+ goto capture_alloc_err;
+ }
+
+ if (rtd->codec->ac97)
+ rtd->codec->ac97->private_data = psc_dma;
+
+ return 0;
+
+ capture_alloc_err:
+ if (pcm->streams[0].substream)
+ snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
+
+ playback_alloc_err:
+ dev_err(card->dev, "Cannot allocate buffer(s)\n");
+
+ return -ENOMEM;
+}
+
+static void psc_dma_free(struct snd_pcm *pcm)
+{
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_pcm_substream *substream;
+ int stream;
+
+ dev_dbg(rtd->platform->dev, "psc_dma_free(pcm=%p)\n", pcm);
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (substream) {
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer.area = NULL;
+ substream->dma_buffer.addr = 0;
+ }
+ }
+}
+
+static struct snd_soc_platform_driver mpc5200_audio_dma_platform = {
+ .ops = &psc_dma_ops,
+ .pcm_new = &psc_dma_new,
+ .pcm_free = &psc_dma_free,
+};
+
+static int mpc5200_hpcd_probe(struct platform_device *op)
+{
+ phys_addr_t fifo;
+ struct psc_dma *psc_dma;
+ struct resource res;
+ int size, irq, rc;
+ const __be32 *prop;
+ void __iomem *regs;
+ int ret;
+
+ /* Fetch the registers and IRQ of the PSC */
+ irq = irq_of_parse_and_map(op->dev.of_node, 0);
+ if (of_address_to_resource(op->dev.of_node, 0, &res)) {
+ dev_err(&op->dev, "Missing reg property\n");
+ return -ENODEV;
+ }
+ regs = ioremap(res.start, 1 + res.end - res.start);
+ if (!regs) {
+ dev_err(&op->dev, "Could not map registers\n");
+ return -ENODEV;
+ }
+
+ /* Allocate and initialize the driver private data */
+ psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL);
+ if (!psc_dma) {
+ ret = -ENOMEM;
+ goto out_unmap;
+ }
+
+ /* Get the PSC ID */
+ prop = of_get_property(op->dev.of_node, "cell-index", &size);
+ if (!prop || size < sizeof *prop) {
+ ret = -ENODEV;
+ goto out_free;
+ }
+
+ spin_lock_init(&psc_dma->lock);
+ mutex_init(&psc_dma->mutex);
+ psc_dma->id = be32_to_cpu(*prop);
+ psc_dma->irq = irq;
+ psc_dma->psc_regs = regs;
+ psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs;
+ psc_dma->dev = &op->dev;
+ psc_dma->playback.psc_dma = psc_dma;
+ psc_dma->capture.psc_dma = psc_dma;
+ snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_dma->id);
+
+ /* Find the address of the fifo data registers and setup the
+ * DMA tasks */
+ fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32);
+ psc_dma->capture.bcom_task =
+ bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512);
+ psc_dma->playback.bcom_task =
+ bcom_psc_gen_bd_tx_init(psc_dma->id, 10, fifo);
+ if (!psc_dma->capture.bcom_task ||
+ !psc_dma->playback.bcom_task) {
+ dev_err(&op->dev, "Could not allocate bestcomm tasks\n");
+ ret = -ENODEV;
+ goto out_free;
+ }
+
+ /* Disable all interrupts and reset the PSC */
+ out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
+ /* reset receiver */
+ out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_RX);
+ /* reset transmitter */
+ out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_TX);
+ /* reset error */
+ out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_ERR_STAT);
+ /* reset mode */
+ out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_SEL_MODE_REG_1);
+
+ /* Set up mode register;
+ * First write: RxRdy (FIFO Alarm) generates rx FIFO irq
+ * Second write: register Normal mode for non loopback
+ */
+ out_8(&psc_dma->psc_regs->mode, 0);
+ out_8(&psc_dma->psc_regs->mode, 0);
+
+ /* Set the TX and RX fifo alarm thresholds */
+ out_be16(&psc_dma->fifo_regs->rfalarm, 0x100);
+ out_8(&psc_dma->fifo_regs->rfcntl, 0x4);
+ out_be16(&psc_dma->fifo_regs->tfalarm, 0x100);
+ out_8(&psc_dma->fifo_regs->tfcntl, 0x7);
+
+ /* Lookup the IRQ numbers */
+ psc_dma->playback.irq =
+ bcom_get_task_irq(psc_dma->playback.bcom_task);
+ psc_dma->capture.irq =
+ bcom_get_task_irq(psc_dma->capture.bcom_task);
+
+ rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED,
+ "psc-dma-status", psc_dma);
+ rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED,
+ "psc-dma-capture", &psc_dma->capture);
+ rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED,
+ "psc-dma-playback", &psc_dma->playback);
+ if (rc) {
+ ret = -ENODEV;
+ goto out_irq;
+ }
+
+ /* Save what we've done so it can be found again later */
+ dev_set_drvdata(&op->dev, psc_dma);
+
+ /* Tell the ASoC OF helpers about it */
+ return snd_soc_register_platform(&op->dev, &mpc5200_audio_dma_platform);
+out_irq:
+ free_irq(psc_dma->irq, psc_dma);
+ free_irq(psc_dma->capture.irq, &psc_dma->capture);
+ free_irq(psc_dma->playback.irq, &psc_dma->playback);
+out_free:
+ kfree(psc_dma);
+out_unmap:
+ iounmap(regs);
+ return ret;
+}
+
+static int mpc5200_hpcd_remove(struct platform_device *op)
+{
+ struct psc_dma *psc_dma = dev_get_drvdata(&op->dev);
+
+ dev_dbg(&op->dev, "mpc5200_audio_dma_destroy()\n");
+
+ snd_soc_unregister_platform(&op->dev);
+
+ bcom_gen_bd_rx_release(psc_dma->capture.bcom_task);
+ bcom_gen_bd_tx_release(psc_dma->playback.bcom_task);
+
+ /* Release irqs */
+ free_irq(psc_dma->irq, psc_dma);
+ free_irq(psc_dma->capture.irq, &psc_dma->capture);
+ free_irq(psc_dma->playback.irq, &psc_dma->playback);
+
+ iounmap(psc_dma->psc_regs);
+ kfree(psc_dma);
+ dev_set_drvdata(&op->dev, NULL);
+
+ return 0;
+}
+
+static struct of_device_id mpc5200_hpcd_match[] = {
+ { .compatible = "fsl,mpc5200-pcm", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mpc5200_hpcd_match);
+
+static struct platform_driver mpc5200_hpcd_of_driver = {
+ .probe = mpc5200_hpcd_probe,
+ .remove = mpc5200_hpcd_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "mpc5200-pcm-audio",
+ .of_match_table = mpc5200_hpcd_match,
+ }
+};
+
+static int __init mpc5200_hpcd_init(void)
+{
+ return platform_driver_register(&mpc5200_hpcd_of_driver);
+}
+module_init(mpc5200_hpcd_init);
+
+static void __exit mpc5200_hpcd_exit(void)
+{
+ platform_driver_unregister(&mpc5200_hpcd_of_driver);
+}
+module_exit(mpc5200_hpcd_exit);
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h
new file mode 100644
index 00000000..a3c0cd53
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_dma.h
@@ -0,0 +1,84 @@
+/*
+ * Freescale MPC5200 Audio DMA driver
+ */
+
+#ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__
+#define __SOUND_SOC_FSL_MPC5200_DMA_H__
+
+#define PSC_STREAM_NAME_LEN 32
+
+/**
+ * psc_ac97_stream - Data specific to a single stream (playback or capture)
+ * @active: flag indicating if the stream is active
+ * @psc_dma: pointer back to parent psc_dma data structure
+ * @bcom_task: bestcomm task structure
+ * @irq: irq number for bestcomm task
+ * @period_end: physical address of end of DMA region
+ * @period_next_pt: physical address of next DMA buffer to enqueue
+ * @period_bytes: size of DMA period in bytes
+ * @ac97_slot_bits: Enable bits for turning on the correct AC97 slot
+ */
+struct psc_dma_stream {
+ struct snd_pcm_runtime *runtime;
+ int active;
+ struct psc_dma *psc_dma;
+ struct bcom_task *bcom_task;
+ int irq;
+ struct snd_pcm_substream *stream;
+ int period_next;
+ int period_current;
+ int period_bytes;
+ int period_count;
+
+ /* AC97 state */
+ u32 ac97_slot_bits;
+};
+
+/**
+ * psc_dma - Private driver data
+ * @name: short name for this device ("PSC0", "PSC1", etc)
+ * @psc_regs: pointer to the PSC's registers
+ * @fifo_regs: pointer to the PSC's FIFO registers
+ * @irq: IRQ of this PSC
+ * @dev: struct device pointer
+ * @dai: the CPU DAI for this device
+ * @sicr: Base value used in serial interface control register; mode is ORed
+ * with this value.
+ * @playback: Playback stream context data
+ * @capture: Capture stream context data
+ */
+struct psc_dma {
+ char name[32];
+ struct mpc52xx_psc __iomem *psc_regs;
+ struct mpc52xx_psc_fifo __iomem *fifo_regs;
+ unsigned int irq;
+ struct device *dev;
+ spinlock_t lock;
+ struct mutex mutex;
+ u32 sicr;
+ uint sysclk;
+ int imr;
+ int id;
+ unsigned int slots;
+
+ /* per-stream data */
+ struct psc_dma_stream playback;
+ struct psc_dma_stream capture;
+
+ /* Statistics */
+ struct {
+ unsigned long overrun_count;
+ unsigned long underrun_count;
+ } stats;
+};
+
+/* Utility for retrieving psc_dma_stream structure from a substream */
+static inline struct psc_dma_stream *
+to_psc_dma_stream(struct snd_pcm_substream *substream, struct psc_dma *psc_dma)
+{
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ return &psc_dma->capture;
+ return &psc_dma->playback;
+}
+
+#endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */
diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c
new file mode 100644
index 00000000..ad36b095
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_psc_ac97.c
@@ -0,0 +1,347 @@
+/*
+ * linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip.
+ *
+ * Copyright (C) 2009 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/time.h>
+#include <asm/delay.h>
+#include <asm/mpc52xx.h>
+#include <asm/mpc52xx_psc.h>
+
+#include "mpc5200_dma.h"
+#include "mpc5200_psc_ac97.h"
+
+#define DRV_NAME "mpc5200-psc-ac97"
+
+/* ALSA only supports a single AC97 device so static is recommend here */
+static struct psc_dma *psc_dma;
+
+static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+ int status;
+ unsigned int val;
+
+ mutex_lock(&psc_dma->mutex);
+
+ /* Wait for command send status zero = ready */
+ status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) &
+ MPC52xx_PSC_SR_CMDSEND), 100, 0);
+ if (status == 0) {
+ pr_err("timeout on ac97 bus (rdy)\n");
+ mutex_unlock(&psc_dma->mutex);
+ return -ENODEV;
+ }
+
+ /* Force clear the data valid bit */
+ in_be32(&psc_dma->psc_regs->ac97_data);
+
+ /* Send the read */
+ out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24));
+
+ /* Wait for the answer */
+ status = spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) &
+ MPC52xx_PSC_SR_DATA_VAL), 100, 0);
+ if (status == 0) {
+ pr_err("timeout on ac97 read (val) %x\n",
+ in_be16(&psc_dma->psc_regs->sr_csr.status));
+ mutex_unlock(&psc_dma->mutex);
+ return -ENODEV;
+ }
+ /* Get the data */
+ val = in_be32(&psc_dma->psc_regs->ac97_data);
+ if (((val >> 24) & 0x7f) != reg) {
+ pr_err("reg echo error on ac97 read\n");
+ mutex_unlock(&psc_dma->mutex);
+ return -ENODEV;
+ }
+ val = (val >> 8) & 0xffff;
+
+ mutex_unlock(&psc_dma->mutex);
+ return (unsigned short) val;
+}
+
+static void psc_ac97_write(struct snd_ac97 *ac97,
+ unsigned short reg, unsigned short val)
+{
+ int status;
+
+ mutex_lock(&psc_dma->mutex);
+
+ /* Wait for command status zero = ready */
+ status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) &
+ MPC52xx_PSC_SR_CMDSEND), 100, 0);
+ if (status == 0) {
+ pr_err("timeout on ac97 bus (write)\n");
+ goto out;
+ }
+ /* Write data */
+ out_be32(&psc_dma->psc_regs->ac97_cmd,
+ ((reg & 0x7f) << 24) | (val << 8));
+
+ out:
+ mutex_unlock(&psc_dma->mutex);
+}
+
+static void psc_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+
+ mutex_lock(&psc_dma->mutex);
+
+ out_be32(&regs->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR);
+ udelay(3);
+ out_be32(&regs->sicr, psc_dma->sicr);
+
+ mutex_unlock(&psc_dma->mutex);
+}
+
+static void psc_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+
+ mutex_lock(&psc_dma->mutex);
+ dev_dbg(psc_dma->dev, "cold reset\n");
+
+ mpc5200_psc_ac97_gpio_reset(psc_dma->id);
+
+ /* Notify the PSC that a reset has occurred */
+ out_be32(&regs->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB);
+
+ /* Re-enable RX and TX */
+ out_8(&regs->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
+
+ mutex_unlock(&psc_dma->mutex);
+
+ msleep(1);
+ psc_ac97_warm_reset(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = psc_ac97_read,
+ .write = psc_ac97_write,
+ .reset = psc_ac97_cold_reset,
+ .warm_reset = psc_ac97_warm_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int psc_ac97_hw_analog_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+ struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
+
+ dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
+ " periods=%i buffer_size=%i buffer_bytes=%i channels=%i"
+ " rate=%i format=%i\n",
+ __func__, substream, params_period_size(params),
+ params_period_bytes(params), params_periods(params),
+ params_buffer_size(params), params_buffer_bytes(params),
+ params_channels(params), params_rate(params),
+ params_format(params));
+
+ /* Determine the set of enable bits to turn on */
+ s->ac97_slot_bits = (params_channels(params) == 1) ? 0x100 : 0x300;
+ if (substream->pstr->stream != SNDRV_PCM_STREAM_CAPTURE)
+ s->ac97_slot_bits <<= 16;
+ return 0;
+}
+
+static int psc_ac97_hw_digital_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+
+ dev_dbg(psc_dma->dev, "%s(substream=%p)\n", __func__, substream);
+
+ if (params_channels(params) == 1)
+ out_be32(&psc_dma->psc_regs->ac97_slots, 0x01000000);
+ else
+ out_be32(&psc_dma->psc_regs->ac97_slots, 0x03000000);
+
+ return 0;
+}
+
+static int psc_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(dai);
+ struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ dev_dbg(psc_dma->dev, "AC97 START: stream=%i\n",
+ substream->pstr->stream);
+
+ /* Set the slot enable bits */
+ psc_dma->slots |= s->ac97_slot_bits;
+ out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ dev_dbg(psc_dma->dev, "AC97 STOP: stream=%i\n",
+ substream->pstr->stream);
+
+ /* Clear the slot enable bits */
+ psc_dma->slots &= ~(s->ac97_slot_bits);
+ out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots);
+ break;
+ }
+ return 0;
+}
+
+static int psc_ac97_probe(struct snd_soc_dai *cpu_dai)
+{
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+ struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+
+ /* Go */
+ out_8(&regs->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
+ return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * ALSA SoC Bindings
+ *
+ * - Digital Audio Interface (DAI) template
+ * - create/destroy dai hooks
+ */
+
+/**
+ * psc_ac97_dai_template: template CPU Digital Audio Interface
+ */
+static struct snd_soc_dai_ops psc_ac97_analog_ops = {
+ .hw_params = psc_ac97_hw_analog_params,
+ .trigger = psc_ac97_trigger,
+};
+
+static struct snd_soc_dai_ops psc_ac97_digital_ops = {
+ .hw_params = psc_ac97_hw_digital_params,
+};
+
+static struct snd_soc_dai_driver psc_ac97_dai[] = {
+{
+ .ac97_control = 1,
+ .probe = psc_ac97_probe,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_BE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_BE,
+ },
+ .ops = &psc_ac97_analog_ops,
+},
+{
+ .ac97_control = 1,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE,
+ },
+ .ops = &psc_ac97_digital_ops,
+} };
+
+
+
+/* ---------------------------------------------------------------------
+ * OF platform bus binding code:
+ * - Probe/remove operations
+ * - OF device match table
+ */
+static int __devinit psc_ac97_of_probe(struct platform_device *op)
+{
+ int rc;
+ struct snd_ac97 ac97;
+ struct mpc52xx_psc __iomem *regs;
+
+ rc = snd_soc_register_dais(&op->dev, psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai));
+ if (rc != 0) {
+ dev_err(&op->dev, "Failed to register DAI\n");
+ return rc;
+ }
+
+ psc_dma = dev_get_drvdata(&op->dev);
+ regs = psc_dma->psc_regs;
+ ac97.private_data = psc_dma;
+
+ psc_dma->imr = 0;
+ out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
+
+ /* Configure the serial interface mode to AC97 */
+ psc_dma->sicr = MPC52xx_PSC_SICR_SIM_AC97 | MPC52xx_PSC_SICR_ENAC97;
+ out_be32(&regs->sicr, psc_dma->sicr);
+
+ /* No slots active */
+ out_be32(&regs->ac97_slots, 0x00000000);
+
+ return 0;
+}
+
+static int __devexit psc_ac97_of_remove(struct platform_device *op)
+{
+ snd_soc_unregister_dais(&op->dev, ARRAY_SIZE(psc_ac97_dai));
+ return 0;
+}
+
+/* Match table for of_platform binding */
+static struct of_device_id psc_ac97_match[] __devinitdata = {
+ { .compatible = "fsl,mpc5200-psc-ac97", },
+ { .compatible = "fsl,mpc5200b-psc-ac97", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, psc_ac97_match);
+
+static struct platform_driver psc_ac97_driver = {
+ .probe = psc_ac97_of_probe,
+ .remove = __devexit_p(psc_ac97_of_remove),
+ .driver = {
+ .name = "mpc5200-psc-ac97",
+ .owner = THIS_MODULE,
+ .of_match_table = psc_ac97_match,
+ },
+};
+
+/* ---------------------------------------------------------------------
+ * Module setup and teardown; simply register the of_platform driver
+ * for the PSC in AC97 mode.
+ */
+static int __init psc_ac97_init(void)
+{
+ return platform_driver_register(&psc_ac97_driver);
+}
+module_init(psc_ac97_init);
+
+static void __exit psc_ac97_exit(void)
+{
+ platform_driver_unregister(&psc_ac97_driver);
+}
+module_exit(psc_ac97_exit);
+
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_DESCRIPTION("mpc5200 AC97 module");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/fsl/mpc5200_psc_ac97.h b/sound/soc/fsl/mpc5200_psc_ac97.h
new file mode 100644
index 00000000..e881e784
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_psc_ac97.h
@@ -0,0 +1,13 @@
+/*
+ * Freescale MPC5200 PSC in AC97 mode
+ * ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ */
+
+#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__
+#define __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__
+
+#define MPC5200_AC97_NORMAL 0
+#define MPC5200_AC97_SPDIF 1
+
+#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ */
diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c
new file mode 100644
index 00000000..87cf2a5c
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_psc_i2s.c
@@ -0,0 +1,244 @@
+/*
+ * Freescale MPC5200 PSC in I2S mode
+ * ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ * Copyright (C) 2009 Jon Smirl, Digispeaker
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mpc52xx_psc.h>
+
+#include "mpc5200_dma.h"
+
+/**
+ * PSC_I2S_RATES: sample rates supported by the I2S
+ *
+ * This driver currently only supports the PSC running in I2S slave mode,
+ * which means the codec determines the sample rate. Therefore, we tell
+ * ALSA that we support all rates and let the codec driver decide what rates
+ * are really supported.
+ */
+#define PSC_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+ SNDRV_PCM_RATE_CONTINUOUS)
+
+/**
+ * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode
+ */
+#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
+
+static int psc_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ u32 mode;
+
+ dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
+ " periods=%i buffer_size=%i buffer_bytes=%i\n",
+ __func__, substream, params_period_size(params),
+ params_period_bytes(params), params_periods(params),
+ params_buffer_size(params), params_buffer_bytes(params));
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ mode = MPC52xx_PSC_SICR_SIM_CODEC_8;
+ break;
+ case SNDRV_PCM_FORMAT_S16_BE:
+ mode = MPC52xx_PSC_SICR_SIM_CODEC_16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_BE:
+ mode = MPC52xx_PSC_SICR_SIM_CODEC_24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_BE:
+ mode = MPC52xx_PSC_SICR_SIM_CODEC_32;
+ break;
+ default:
+ dev_dbg(psc_dma->dev, "invalid format\n");
+ return -EINVAL;
+ }
+ out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | mode);
+
+ return 0;
+}
+
+/**
+ * psc_i2s_set_sysclk: set the clock frequency and direction
+ *
+ * This function is called by the machine driver to tell us what the clock
+ * frequency and direction are.
+ *
+ * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
+ * and we don't care about the frequency. Return an error if the direction
+ * is not SND_SOC_CLOCK_IN.
+ *
+ * @clk_id: reserved, should be zero
+ * @freq: the frequency of the given clock ID, currently ignored
+ * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
+ */
+static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+ dev_dbg(psc_dma->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n",
+ cpu_dai, dir);
+ return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
+}
+
+/**
+ * psc_i2s_set_fmt: set the serial format.
+ *
+ * This function is called by the machine driver to tell us what serial
+ * format to use.
+ *
+ * This driver only supports I2S mode. Return an error if the format is
+ * not SND_SOC_DAIFMT_I2S.
+ *
+ * @format: one of SND_SOC_DAIFMT_xxx
+ */
+static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
+{
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+ dev_dbg(psc_dma->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n",
+ cpu_dai, format);
+ return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
+}
+
+/* ---------------------------------------------------------------------
+ * ALSA SoC Bindings
+ *
+ * - Digital Audio Interface (DAI) template
+ * - create/destroy dai hooks
+ */
+
+/**
+ * psc_i2s_dai_template: template CPU Digital Audio Interface
+ */
+static struct snd_soc_dai_ops psc_i2s_dai_ops = {
+ .hw_params = psc_i2s_hw_params,
+ .set_sysclk = psc_i2s_set_sysclk,
+ .set_fmt = psc_i2s_set_fmt,
+};
+
+static struct snd_soc_dai_driver psc_i2s_dai[] = {{
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PSC_I2S_RATES,
+ .formats = PSC_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PSC_I2S_RATES,
+ .formats = PSC_I2S_FORMATS,
+ },
+ .ops = &psc_i2s_dai_ops,
+} };
+
+/* ---------------------------------------------------------------------
+ * OF platform bus binding code:
+ * - Probe/remove operations
+ * - OF device match table
+ */
+static int __devinit psc_i2s_of_probe(struct platform_device *op)
+{
+ int rc;
+ struct psc_dma *psc_dma;
+ struct mpc52xx_psc __iomem *regs;
+
+ rc = snd_soc_register_dais(&op->dev, psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai));
+ if (rc != 0) {
+ pr_err("Failed to register DAI\n");
+ return rc;
+ }
+
+ psc_dma = dev_get_drvdata(&op->dev);
+ regs = psc_dma->psc_regs;
+
+ /* Configure the serial interface mode; defaulting to CODEC8 mode */
+ psc_dma->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S |
+ MPC52xx_PSC_SICR_CLKPOL;
+ out_be32(&psc_dma->psc_regs->sicr,
+ psc_dma->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8);
+
+ /* Check for the codec handle. If it is not present then we
+ * are done */
+ if (!of_get_property(op->dev.of_node, "codec-handle", NULL))
+ return 0;
+
+ /* Due to errata in the dma mode; need to line up enabling
+ * the transmitter with a transition on the frame sync
+ * line */
+
+ /* first make sure it is low */
+ while ((in_8(&regs->ipcr_acr.ipcr) & 0x80) != 0)
+ ;
+ /* then wait for the transition to high */
+ while ((in_8(&regs->ipcr_acr.ipcr) & 0x80) == 0)
+ ;
+ /* Finally, enable the PSC.
+ * Receiver must always be enabled; even when we only want
+ * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */
+
+ /* Go */
+ out_8(&psc_dma->psc_regs->command,
+ MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
+
+ return 0;
+
+}
+
+static int __devexit psc_i2s_of_remove(struct platform_device *op)
+{
+ snd_soc_unregister_dais(&op->dev, ARRAY_SIZE(psc_i2s_dai));
+ return 0;
+}
+
+/* Match table for of_platform binding */
+static struct of_device_id psc_i2s_match[] __devinitdata = {
+ { .compatible = "fsl,mpc5200-psc-i2s", },
+ { .compatible = "fsl,mpc5200b-psc-i2s", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, psc_i2s_match);
+
+static struct platform_driver psc_i2s_driver = {
+ .probe = psc_i2s_of_probe,
+ .remove = __devexit_p(psc_i2s_of_remove),
+ .driver = {
+ .name = "mpc5200-psc-i2s",
+ .owner = THIS_MODULE,
+ .of_match_table = psc_i2s_match,
+ },
+};
+
+/* ---------------------------------------------------------------------
+ * Module setup and teardown; simply register the of_platform driver
+ * for the PSC in I2S mode.
+ */
+static int __init psc_i2s_init(void)
+{
+ return platform_driver_register(&psc_i2s_driver);
+}
+module_init(psc_i2s_init);
+
+static void __exit psc_i2s_exit(void)
+{
+ platform_driver_unregister(&psc_i2s_driver);
+}
+module_exit(psc_i2s_exit);
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c
new file mode 100644
index 00000000..c16c6b2e
--- /dev/null
+++ b/sound/soc/fsl/mpc8610_hpcd.c
@@ -0,0 +1,588 @@
+/**
+ * Freescale MPC8610HPCD ALSA SoC Machine driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <asm/fsl_guts.h>
+
+#include "fsl_dma.h"
+#include "fsl_ssi.h"
+
+/* There's only one global utilities register */
+static phys_addr_t guts_phys;
+
+#define DAI_NAME_SIZE 32
+
+/**
+ * mpc8610_hpcd_data: machine-specific ASoC device data
+ *
+ * This structure contains data for a single sound platform device on an
+ * MPC8610 HPCD. Some of the data is taken from the device tree.
+ */
+struct mpc8610_hpcd_data {
+ struct snd_soc_dai_link dai[2];
+ struct snd_soc_card card;
+ unsigned int dai_format;
+ unsigned int codec_clk_direction;
+ unsigned int cpu_clk_direction;
+ unsigned int clk_frequency;
+ unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */
+ unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */
+ unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
+ char codec_dai_name[DAI_NAME_SIZE];
+ char codec_name[DAI_NAME_SIZE];
+ char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
+};
+
+/**
+ * mpc8610_hpcd_machine_probe: initialize the board
+ *
+ * This function is used to initialize the board-specific hardware.
+ *
+ * Here we program the DMACR and PMUXCR registers.
+ */
+static int mpc8610_hpcd_machine_probe(struct snd_soc_card *card)
+{
+ struct mpc8610_hpcd_data *machine_data =
+ container_of(card, struct mpc8610_hpcd_data, card);
+ struct ccsr_guts_86xx __iomem *guts;
+
+ guts = ioremap(guts_phys, sizeof(struct ccsr_guts_86xx));
+ if (!guts) {
+ dev_err(card->dev, "could not map global utilities\n");
+ return -ENOMEM;
+ }
+
+ /* Program the signal routing between the SSI and the DMA */
+ guts_set_dmacr(guts, machine_data->dma_id[0],
+ machine_data->dma_channel_id[0],
+ CCSR_GUTS_DMACR_DEV_SSI);
+ guts_set_dmacr(guts, machine_data->dma_id[1],
+ machine_data->dma_channel_id[1],
+ CCSR_GUTS_DMACR_DEV_SSI);
+
+ guts_set_pmuxcr_dma(guts, machine_data->dma_id[0],
+ machine_data->dma_channel_id[0], 0);
+ guts_set_pmuxcr_dma(guts, machine_data->dma_id[1],
+ machine_data->dma_channel_id[1], 0);
+
+ switch (machine_data->ssi_id) {
+ case 0:
+ clrsetbits_be32(&guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
+ break;
+ case 1:
+ clrsetbits_be32(&guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
+ break;
+ }
+
+ iounmap(guts);
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_startup: program the board with various hardware parameters
+ *
+ * This function takes board-specific information, like clock frequencies
+ * and serial data formats, and passes that information to the codec and
+ * transport drivers.
+ */
+static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct mpc8610_hpcd_data *machine_data =
+ container_of(rtd->card, struct mpc8610_hpcd_data, card);
+ struct device *dev = rtd->card->dev;
+ int ret = 0;
+
+ /* Tell the codec driver what the serial protocol is. */
+ ret = snd_soc_dai_set_fmt(rtd->codec_dai, machine_data->dai_format);
+ if (ret < 0) {
+ dev_err(dev, "could not set codec driver audio format\n");
+ return ret;
+ }
+
+ /*
+ * Tell the codec driver what the MCLK frequency is, and whether it's
+ * a slave or master.
+ */
+ ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0,
+ machine_data->clk_frequency,
+ machine_data->codec_clk_direction);
+ if (ret < 0) {
+ dev_err(dev, "could not set codec driver clock params\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_machine_remove: Remove the sound device
+ *
+ * This function is called to remove the sound device for one SSI. We
+ * de-program the DMACR and PMUXCR register.
+ */
+static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card)
+{
+ struct mpc8610_hpcd_data *machine_data =
+ container_of(card, struct mpc8610_hpcd_data, card);
+ struct ccsr_guts_86xx __iomem *guts;
+
+ guts = ioremap(guts_phys, sizeof(struct ccsr_guts_86xx));
+ if (!guts) {
+ dev_err(card->dev, "could not map global utilities\n");
+ return -ENOMEM;
+ }
+
+ /* Restore the signal routing */
+
+ guts_set_dmacr(guts, machine_data->dma_id[0],
+ machine_data->dma_channel_id[0], 0);
+ guts_set_dmacr(guts, machine_data->dma_id[1],
+ machine_data->dma_channel_id[1], 0);
+
+ switch (machine_data->ssi_id) {
+ case 0:
+ clrsetbits_be32(&guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
+ break;
+ case 1:
+ clrsetbits_be32(&guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
+ break;
+ }
+
+ iounmap(guts);
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_ops: ASoC machine driver operations
+ */
+static struct snd_soc_ops mpc8610_hpcd_ops = {
+ .startup = mpc8610_hpcd_startup,
+};
+
+/**
+ * get_node_by_phandle_name - get a node by its phandle name
+ *
+ * This function takes a node, the name of a property in that node, and a
+ * compatible string. Assuming the property is a phandle to another node,
+ * it returns that node, (optionally) if that node is compatible.
+ *
+ * If the property is not a phandle, or the node it points to is not compatible
+ * with the specific string, then NULL is returned.
+ */
+static struct device_node *get_node_by_phandle_name(struct device_node *np,
+ const char *name,
+ const char *compatible)
+{
+ const phandle *ph;
+ int len;
+
+ ph = of_get_property(np, name, &len);
+ if (!ph || (len != sizeof(phandle)))
+ return NULL;
+
+ np = of_find_node_by_phandle(*ph);
+ if (!np)
+ return NULL;
+
+ if (compatible && !of_device_is_compatible(np, compatible)) {
+ of_node_put(np);
+ return NULL;
+ }
+
+ return np;
+}
+
+/**
+ * get_parent_cell_index -- return the cell-index of the parent of a node
+ *
+ * Return the value of the cell-index property of the parent of the given
+ * node. This is used for DMA channel nodes that need to know the DMA ID
+ * of the controller they are on.
+ */
+static int get_parent_cell_index(struct device_node *np)
+{
+ struct device_node *parent = of_get_parent(np);
+ const u32 *iprop;
+
+ if (!parent)
+ return -1;
+
+ iprop = of_get_property(parent, "cell-index", NULL);
+ of_node_put(parent);
+
+ if (!iprop)
+ return -1;
+
+ return *iprop;
+}
+
+/**
+ * codec_node_dev_name - determine the dev_name for a codec node
+ *
+ * This function determines the dev_name for an I2C node. This is the name
+ * that would be returned by dev_name() if this device_node were part of a
+ * 'struct device' It's ugly and hackish, but it works.
+ *
+ * The dev_name for such devices include the bus number and I2C address. For
+ * example, "cs4270-codec.0-004f".
+ */
+static int codec_node_dev_name(struct device_node *np, char *buf, size_t len)
+{
+ const u32 *iprop;
+ int bus, addr;
+ char temp[DAI_NAME_SIZE];
+
+ of_modalias_node(np, temp, DAI_NAME_SIZE);
+
+ iprop = of_get_property(np, "reg", NULL);
+ if (!iprop)
+ return -EINVAL;
+
+ addr = *iprop;
+
+ bus = get_parent_cell_index(np);
+ if (bus < 0)
+ return bus;
+
+ snprintf(buf, len, "%s-codec.%u-%04x", temp, bus, addr);
+
+ return 0;
+}
+
+static int get_dma_channel(struct device_node *ssi_np,
+ const char *compatible,
+ struct snd_soc_dai_link *dai,
+ unsigned int *dma_channel_id,
+ unsigned int *dma_id)
+{
+ struct resource res;
+ struct device_node *dma_channel_np;
+ const u32 *iprop;
+ int ret;
+
+ dma_channel_np = get_node_by_phandle_name(ssi_np, compatible,
+ "fsl,ssi-dma-channel");
+ if (!dma_channel_np)
+ return -EINVAL;
+
+ /* Determine the dev_name for the device_node. This code mimics the
+ * behavior of of_device_make_bus_id(). We need this because ASoC uses
+ * the dev_name() of the device to match the platform (DMA) device with
+ * the CPU (SSI) device. It's all ugly and hackish, but it works (for
+ * now).
+ *
+ * dai->platform name should already point to an allocated buffer.
+ */
+ ret = of_address_to_resource(dma_channel_np, 0, &res);
+ if (ret)
+ return ret;
+ snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s",
+ (unsigned long long) res.start, dma_channel_np->name);
+
+ iprop = of_get_property(dma_channel_np, "cell-index", NULL);
+ if (!iprop) {
+ of_node_put(dma_channel_np);
+ return -EINVAL;
+ }
+
+ *dma_channel_id = *iprop;
+ *dma_id = get_parent_cell_index(dma_channel_np);
+ of_node_put(dma_channel_np);
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_probe: platform probe function for the machine driver
+ *
+ * Although this is a machine driver, the SSI node is the "master" node with
+ * respect to audio hardware connections. Therefore, we create a new ASoC
+ * device for each new SSI node that has a codec attached.
+ */
+static int mpc8610_hpcd_probe(struct platform_device *pdev)
+{
+ struct device *dev = pdev->dev.parent;
+ /* ssi_pdev is the platform device for the SSI node that probed us */
+ struct platform_device *ssi_pdev =
+ container_of(dev, struct platform_device, dev);
+ struct device_node *np = ssi_pdev->dev.of_node;
+ struct device_node *codec_np = NULL;
+ struct platform_device *sound_device = NULL;
+ struct mpc8610_hpcd_data *machine_data;
+ int ret = -ENODEV;
+ const char *sprop;
+ const u32 *iprop;
+
+ /* We are only interested in SSIs with a codec phandle in them,
+ * so let's make sure this SSI has one. The MPC8610 HPCD only
+ * knows about the CS4270 codec, so reject anything else.
+ */
+ codec_np = get_node_by_phandle_name(np, "codec-handle",
+ "cirrus,cs4270");
+ if (!codec_np) {
+ dev_err(dev, "invalid codec node\n");
+ return -EINVAL;
+ }
+
+ machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
+ if (!machine_data)
+ return -ENOMEM;
+
+ machine_data->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
+ machine_data->dai[0].ops = &mpc8610_hpcd_ops;
+
+ /* Determine the codec name, it will be used as the codec DAI name */
+ ret = codec_node_dev_name(codec_np, machine_data->codec_name,
+ DAI_NAME_SIZE);
+ if (ret) {
+ dev_err(&pdev->dev, "invalid codec node %s\n",
+ codec_np->full_name);
+ ret = -EINVAL;
+ goto error;
+ }
+ machine_data->dai[0].codec_name = machine_data->codec_name;
+
+ /* The DAI name from the codec (snd_soc_dai_driver.name) */
+ machine_data->dai[0].codec_dai_name = "cs4270-hifi";
+
+ /* We register two DAIs per SSI, one for playback and the other for
+ * capture. Currently, we only support codecs that have one DAI for
+ * both playback and capture.
+ */
+ memcpy(&machine_data->dai[1], &machine_data->dai[0],
+ sizeof(struct snd_soc_dai_link));
+
+ /* Get the device ID */
+ iprop = of_get_property(np, "cell-index", NULL);
+ if (!iprop) {
+ dev_err(&pdev->dev, "cell-index property not found\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ machine_data->ssi_id = *iprop;
+
+ /* Get the serial format and clock direction. */
+ sprop = of_get_property(np, "fsl,mode", NULL);
+ if (!sprop) {
+ dev_err(&pdev->dev, "fsl,mode property not found\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (strcasecmp(sprop, "i2s-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_I2S;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+
+ /* In i2s-slave mode, the codec has its own clock source, so we
+ * need to get the frequency from the device tree and pass it to
+ * the codec driver.
+ */
+ iprop = of_get_property(codec_np, "clock-frequency", NULL);
+ if (!iprop || !*iprop) {
+ dev_err(&pdev->dev, "codec bus-frequency "
+ "property is missing or invalid\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ machine_data->clk_frequency = *iprop;
+ } else if (strcasecmp(sprop, "i2s-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_I2S;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "lj-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "lj-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "rj-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "rj-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "ac97-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_AC97;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "ac97-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_AC97;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else {
+ dev_err(&pdev->dev,
+ "unrecognized fsl,mode property '%s'\n", sprop);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (!machine_data->clk_frequency) {
+ dev_err(&pdev->dev, "unknown clock frequency\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* Find the playback DMA channel to use. */
+ machine_data->dai[0].platform_name = machine_data->platform_name[0];
+ ret = get_dma_channel(np, "fsl,playback-dma", &machine_data->dai[0],
+ &machine_data->dma_channel_id[0],
+ &machine_data->dma_id[0]);
+ if (ret) {
+ dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
+ goto error;
+ }
+
+ /* Find the capture DMA channel to use. */
+ machine_data->dai[1].platform_name = machine_data->platform_name[1];
+ ret = get_dma_channel(np, "fsl,capture-dma", &machine_data->dai[1],
+ &machine_data->dma_channel_id[1],
+ &machine_data->dma_id[1]);
+ if (ret) {
+ dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
+ goto error;
+ }
+
+ /* Initialize our DAI data structure. */
+ machine_data->dai[0].stream_name = "playback";
+ machine_data->dai[1].stream_name = "capture";
+ machine_data->dai[0].name = machine_data->dai[0].stream_name;
+ machine_data->dai[1].name = machine_data->dai[1].stream_name;
+
+ machine_data->card.probe = mpc8610_hpcd_machine_probe;
+ machine_data->card.remove = mpc8610_hpcd_machine_remove;
+ machine_data->card.name = pdev->name; /* The platform driver name */
+ machine_data->card.num_links = 2;
+ machine_data->card.dai_link = machine_data->dai;
+
+ /* Allocate a new audio platform device structure */
+ sound_device = platform_device_alloc("soc-audio", -1);
+ if (!sound_device) {
+ dev_err(&pdev->dev, "platform device alloc failed\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ /* Associate the card data with the sound device */
+ platform_set_drvdata(sound_device, &machine_data->card);
+
+ /* Register with ASoC */
+ ret = platform_device_add(sound_device);
+ if (ret) {
+ dev_err(&pdev->dev, "platform device add failed\n");
+ goto error;
+ }
+ dev_set_drvdata(&pdev->dev, sound_device);
+
+ of_node_put(codec_np);
+
+ return 0;
+
+error:
+ of_node_put(codec_np);
+
+ if (sound_device)
+ platform_device_unregister(sound_device);
+
+ kfree(machine_data);
+
+ return ret;
+}
+
+/**
+ * mpc8610_hpcd_remove: remove the platform device
+ *
+ * This function is called when the platform device is removed.
+ */
+static int __devexit mpc8610_hpcd_remove(struct platform_device *pdev)
+{
+ struct platform_device *sound_device = dev_get_drvdata(&pdev->dev);
+ struct snd_soc_card *card = platform_get_drvdata(sound_device);
+ struct mpc8610_hpcd_data *machine_data =
+ container_of(card, struct mpc8610_hpcd_data, card);
+
+ platform_device_unregister(sound_device);
+
+ kfree(machine_data);
+ sound_device->dev.platform_data = NULL;
+
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver mpc8610_hpcd_driver = {
+ .probe = mpc8610_hpcd_probe,
+ .remove = __devexit_p(mpc8610_hpcd_remove),
+ .driver = {
+ /* The name must match the 'model' property in the device tree,
+ * in lowercase letters.
+ */
+ .name = "snd-soc-mpc8610hpcd",
+ .owner = THIS_MODULE,
+ },
+};
+
+/**
+ * mpc8610_hpcd_init: machine driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init mpc8610_hpcd_init(void)
+{
+ struct device_node *guts_np;
+ struct resource res;
+
+ pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n");
+
+ /* Get the physical address of the global utilities registers */
+ guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
+ if (of_address_to_resource(guts_np, 0, &res)) {
+ pr_err("mpc8610-hpcd: missing/invalid global utilities node\n");
+ return -EINVAL;
+ }
+ guts_phys = res.start;
+
+ return platform_driver_register(&mpc8610_hpcd_driver);
+}
+
+/**
+ * mpc8610_hpcd_exit: machine driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit mpc8610_hpcd_exit(void)
+{
+ platform_driver_unregister(&mpc8610_hpcd_driver);
+}
+
+module_init(mpc8610_hpcd_init);
+module_exit(mpc8610_hpcd_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c
new file mode 100644
index 00000000..66e0b68a
--- /dev/null
+++ b/sound/soc/fsl/p1022_ds.c
@@ -0,0 +1,590 @@
+/**
+ * Freescale P1022DS ALSA SoC Machine driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <asm/fsl_guts.h>
+
+#include "fsl_dma.h"
+#include "fsl_ssi.h"
+
+/* P1022-specific PMUXCR and DMUXCR bit definitions */
+
+#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000
+#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000
+#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000
+
+#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00
+#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000
+
+#define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */
+#define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */
+
+/*
+ * Set the DMACR register in the GUTS
+ *
+ * The DMACR register determines the source of initiated transfers for each
+ * channel on each DMA controller. Rather than have a bunch of repetitive
+ * macros for the bit patterns, we just have a function that calculates
+ * them.
+ *
+ * guts: Pointer to GUTS structure
+ * co: The DMA controller (0 or 1)
+ * ch: The channel on the DMA controller (0, 1, 2, or 3)
+ * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx)
+ */
+static inline void guts_set_dmuxcr(struct ccsr_guts_85xx __iomem *guts,
+ unsigned int co, unsigned int ch, unsigned int device)
+{
+ unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch));
+
+ clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift);
+}
+
+/* There's only one global utilities register */
+static phys_addr_t guts_phys;
+
+#define DAI_NAME_SIZE 32
+
+/**
+ * machine_data: machine-specific ASoC device data
+ *
+ * This structure contains data for a single sound platform device on an
+ * P1022 DS. Some of the data is taken from the device tree.
+ */
+struct machine_data {
+ struct snd_soc_dai_link dai[2];
+ struct snd_soc_card card;
+ unsigned int dai_format;
+ unsigned int codec_clk_direction;
+ unsigned int cpu_clk_direction;
+ unsigned int clk_frequency;
+ unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */
+ unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */
+ unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
+ char codec_name[DAI_NAME_SIZE];
+ char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
+};
+
+/**
+ * p1022_ds_machine_probe: initialize the board
+ *
+ * This function is used to initialize the board-specific hardware.
+ *
+ * Here we program the DMACR and PMUXCR registers.
+ */
+static int p1022_ds_machine_probe(struct snd_soc_card *card)
+{
+ struct machine_data *mdata =
+ container_of(card, struct machine_data, card);
+ struct ccsr_guts_85xx __iomem *guts;
+
+ guts = ioremap(guts_phys, sizeof(struct ccsr_guts_85xx));
+ if (!guts) {
+ dev_err(card->dev, "could not map global utilities\n");
+ return -ENOMEM;
+ }
+
+ /* Enable SSI Tx signal */
+ clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK,
+ CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI);
+
+ /* Enable SSI Rx signal */
+ clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK,
+ CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI);
+
+ /* Enable DMA Channel for SSI */
+ guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0],
+ CCSR_GUTS_DMUXCR_SSI);
+
+ guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1],
+ CCSR_GUTS_DMUXCR_SSI);
+
+ iounmap(guts);
+
+ return 0;
+}
+
+/**
+ * p1022_ds_startup: program the board with various hardware parameters
+ *
+ * This function takes board-specific information, like clock frequencies
+ * and serial data formats, and passes that information to the codec and
+ * transport drivers.
+ */
+static int p1022_ds_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct machine_data *mdata =
+ container_of(rtd->card, struct machine_data, card);
+ struct device *dev = rtd->card->dev;
+ int ret = 0;
+
+ /* Tell the codec driver what the serial protocol is. */
+ ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format);
+ if (ret < 0) {
+ dev_err(dev, "could not set codec driver audio format\n");
+ return ret;
+ }
+
+ /*
+ * Tell the codec driver what the MCLK frequency is, and whether it's
+ * a slave or master.
+ */
+ ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency,
+ mdata->codec_clk_direction);
+ if (ret < 0) {
+ dev_err(dev, "could not set codec driver clock params\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * p1022_ds_machine_remove: Remove the sound device
+ *
+ * This function is called to remove the sound device for one SSI. We
+ * de-program the DMACR and PMUXCR register.
+ */
+static int p1022_ds_machine_remove(struct snd_soc_card *card)
+{
+ struct machine_data *mdata =
+ container_of(card, struct machine_data, card);
+ struct ccsr_guts_85xx __iomem *guts;
+
+ guts = ioremap(guts_phys, sizeof(struct ccsr_guts_85xx));
+ if (!guts) {
+ dev_err(card->dev, "could not map global utilities\n");
+ return -ENOMEM;
+ }
+
+ /* Restore the signal routing */
+ clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK);
+ clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK);
+ guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0);
+ guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0);
+
+ iounmap(guts);
+
+ return 0;
+}
+
+/**
+ * p1022_ds_ops: ASoC machine driver operations
+ */
+static struct snd_soc_ops p1022_ds_ops = {
+ .startup = p1022_ds_startup,
+};
+
+/**
+ * get_node_by_phandle_name - get a node by its phandle name
+ *
+ * This function takes a node, the name of a property in that node, and a
+ * compatible string. Assuming the property is a phandle to another node,
+ * it returns that node, (optionally) if that node is compatible.
+ *
+ * If the property is not a phandle, or the node it points to is not compatible
+ * with the specific string, then NULL is returned.
+ */
+static struct device_node *get_node_by_phandle_name(struct device_node *np,
+ const char *name, const char *compatible)
+{
+ np = of_parse_phandle(np, name, 0);
+ if (!np)
+ return NULL;
+
+ if (!of_device_is_compatible(np, compatible)) {
+ of_node_put(np);
+ return NULL;
+ }
+
+ return np;
+}
+
+/**
+ * get_parent_cell_index -- return the cell-index of the parent of a node
+ *
+ * Return the value of the cell-index property of the parent of the given
+ * node. This is used for DMA channel nodes that need to know the DMA ID
+ * of the controller they are on.
+ */
+static int get_parent_cell_index(struct device_node *np)
+{
+ struct device_node *parent = of_get_parent(np);
+ const u32 *iprop;
+ int ret = -1;
+
+ if (!parent)
+ return -1;
+
+ iprop = of_get_property(parent, "cell-index", NULL);
+ if (iprop)
+ ret = *iprop;
+
+ of_node_put(parent);
+
+ return ret;
+}
+
+/**
+ * codec_node_dev_name - determine the dev_name for a codec node
+ *
+ * This function determines the dev_name for an I2C node. This is the name
+ * that would be returned by dev_name() if this device_node were part of a
+ * 'struct device' It's ugly and hackish, but it works.
+ *
+ * The dev_name for such devices include the bus number and I2C address. For
+ * example, "cs4270-codec.0-004f".
+ */
+static int codec_node_dev_name(struct device_node *np, char *buf, size_t len)
+{
+ const u32 *iprop;
+ int bus, addr;
+ char temp[DAI_NAME_SIZE];
+
+ of_modalias_node(np, temp, DAI_NAME_SIZE);
+
+ iprop = of_get_property(np, "reg", NULL);
+ if (!iprop)
+ return -EINVAL;
+
+ addr = *iprop;
+
+ bus = get_parent_cell_index(np);
+ if (bus < 0)
+ return bus;
+
+ snprintf(buf, len, "%s-codec.%u-%04x", temp, bus, addr);
+
+ return 0;
+}
+
+static int get_dma_channel(struct device_node *ssi_np,
+ const char *compatible,
+ struct snd_soc_dai_link *dai,
+ unsigned int *dma_channel_id,
+ unsigned int *dma_id)
+{
+ struct resource res;
+ struct device_node *dma_channel_np;
+ const u32 *iprop;
+ int ret;
+
+ dma_channel_np = get_node_by_phandle_name(ssi_np, compatible,
+ "fsl,ssi-dma-channel");
+ if (!dma_channel_np)
+ return -EINVAL;
+
+ /* Determine the dev_name for the device_node. This code mimics the
+ * behavior of of_device_make_bus_id(). We need this because ASoC uses
+ * the dev_name() of the device to match the platform (DMA) device with
+ * the CPU (SSI) device. It's all ugly and hackish, but it works (for
+ * now).
+ *
+ * dai->platform name should already point to an allocated buffer.
+ */
+ ret = of_address_to_resource(dma_channel_np, 0, &res);
+ if (ret)
+ return ret;
+ snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s",
+ (unsigned long long) res.start, dma_channel_np->name);
+
+ iprop = of_get_property(dma_channel_np, "cell-index", NULL);
+ if (!iprop) {
+ of_node_put(dma_channel_np);
+ return -EINVAL;
+ }
+
+ *dma_channel_id = *iprop;
+ *dma_id = get_parent_cell_index(dma_channel_np);
+ of_node_put(dma_channel_np);
+
+ return 0;
+}
+
+/**
+ * p1022_ds_probe: platform probe function for the machine driver
+ *
+ * Although this is a machine driver, the SSI node is the "master" node with
+ * respect to audio hardware connections. Therefore, we create a new ASoC
+ * device for each new SSI node that has a codec attached.
+ */
+static int p1022_ds_probe(struct platform_device *pdev)
+{
+ struct device *dev = pdev->dev.parent;
+ /* ssi_pdev is the platform device for the SSI node that probed us */
+ struct platform_device *ssi_pdev =
+ container_of(dev, struct platform_device, dev);
+ struct device_node *np = ssi_pdev->dev.of_node;
+ struct device_node *codec_np = NULL;
+ struct platform_device *sound_device = NULL;
+ struct machine_data *mdata;
+ int ret = -ENODEV;
+ const char *sprop;
+ const u32 *iprop;
+
+ /* Find the codec node for this SSI. */
+ codec_np = of_parse_phandle(np, "codec-handle", 0);
+ if (!codec_np) {
+ dev_err(dev, "could not find codec node\n");
+ return -EINVAL;
+ }
+
+ mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL);
+ if (!mdata) {
+ ret = -ENOMEM;
+ goto error_put;
+ }
+
+ mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
+ mdata->dai[0].ops = &p1022_ds_ops;
+
+ /* Determine the codec name, it will be used as the codec DAI name */
+ ret = codec_node_dev_name(codec_np, mdata->codec_name, DAI_NAME_SIZE);
+ if (ret) {
+ dev_err(&pdev->dev, "invalid codec node %s\n",
+ codec_np->full_name);
+ ret = -EINVAL;
+ goto error;
+ }
+ mdata->dai[0].codec_name = mdata->codec_name;
+
+ /* We register two DAIs per SSI, one for playback and the other for
+ * capture. We support codecs that have separate DAIs for both playback
+ * and capture.
+ */
+ memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link));
+
+ /* The DAI names from the codec (snd_soc_dai_driver.name) */
+ mdata->dai[0].codec_dai_name = "wm8776-hifi-playback";
+ mdata->dai[1].codec_dai_name = "wm8776-hifi-capture";
+
+ /* Get the device ID */
+ iprop = of_get_property(np, "cell-index", NULL);
+ if (!iprop) {
+ dev_err(&pdev->dev, "cell-index property not found\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ mdata->ssi_id = *iprop;
+
+ /* Get the serial format and clock direction. */
+ sprop = of_get_property(np, "fsl,mode", NULL);
+ if (!sprop) {
+ dev_err(&pdev->dev, "fsl,mode property not found\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (strcasecmp(sprop, "i2s-slave") == 0) {
+ mdata->dai_format = SND_SOC_DAIFMT_I2S;
+ mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+
+ /* In i2s-slave mode, the codec has its own clock source, so we
+ * need to get the frequency from the device tree and pass it to
+ * the codec driver.
+ */
+ iprop = of_get_property(codec_np, "clock-frequency", NULL);
+ if (!iprop || !*iprop) {
+ dev_err(&pdev->dev, "codec bus-frequency "
+ "property is missing or invalid\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ mdata->clk_frequency = *iprop;
+ } else if (strcasecmp(sprop, "i2s-master") == 0) {
+ mdata->dai_format = SND_SOC_DAIFMT_I2S;
+ mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+ mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "lj-slave") == 0) {
+ mdata->dai_format = SND_SOC_DAIFMT_LEFT_J;
+ mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "lj-master") == 0) {
+ mdata->dai_format = SND_SOC_DAIFMT_LEFT_J;
+ mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+ mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "rj-slave") == 0) {
+ mdata->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+ mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "rj-master") == 0) {
+ mdata->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+ mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+ mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "ac97-slave") == 0) {
+ mdata->dai_format = SND_SOC_DAIFMT_AC97;
+ mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "ac97-master") == 0) {
+ mdata->dai_format = SND_SOC_DAIFMT_AC97;
+ mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+ mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else {
+ dev_err(&pdev->dev,
+ "unrecognized fsl,mode property '%s'\n", sprop);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (!mdata->clk_frequency) {
+ dev_err(&pdev->dev, "unknown clock frequency\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* Find the playback DMA channel to use. */
+ mdata->dai[0].platform_name = mdata->platform_name[0];
+ ret = get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0],
+ &mdata->dma_channel_id[0],
+ &mdata->dma_id[0]);
+ if (ret) {
+ dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
+ goto error;
+ }
+
+ /* Find the capture DMA channel to use. */
+ mdata->dai[1].platform_name = mdata->platform_name[1];
+ ret = get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1],
+ &mdata->dma_channel_id[1],
+ &mdata->dma_id[1]);
+ if (ret) {
+ dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
+ goto error;
+ }
+
+ /* Initialize our DAI data structure. */
+ mdata->dai[0].stream_name = "playback";
+ mdata->dai[1].stream_name = "capture";
+ mdata->dai[0].name = mdata->dai[0].stream_name;
+ mdata->dai[1].name = mdata->dai[1].stream_name;
+
+ mdata->card.probe = p1022_ds_machine_probe;
+ mdata->card.remove = p1022_ds_machine_remove;
+ mdata->card.name = pdev->name; /* The platform driver name */
+ mdata->card.num_links = 2;
+ mdata->card.dai_link = mdata->dai;
+
+ /* Allocate a new audio platform device structure */
+ sound_device = platform_device_alloc("soc-audio", -1);
+ if (!sound_device) {
+ dev_err(&pdev->dev, "platform device alloc failed\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ /* Associate the card data with the sound device */
+ platform_set_drvdata(sound_device, &mdata->card);
+
+ /* Register with ASoC */
+ ret = platform_device_add(sound_device);
+ if (ret) {
+ dev_err(&pdev->dev, "platform device add failed\n");
+ goto error;
+ }
+ dev_set_drvdata(&pdev->dev, sound_device);
+
+ of_node_put(codec_np);
+
+ return 0;
+
+error:
+ if (sound_device)
+ platform_device_unregister(sound_device);
+
+ kfree(mdata);
+error_put:
+ of_node_put(codec_np);
+ return ret;
+}
+
+/**
+ * p1022_ds_remove: remove the platform device
+ *
+ * This function is called when the platform device is removed.
+ */
+static int __devexit p1022_ds_remove(struct platform_device *pdev)
+{
+ struct platform_device *sound_device = dev_get_drvdata(&pdev->dev);
+ struct snd_soc_card *card = platform_get_drvdata(sound_device);
+ struct machine_data *mdata =
+ container_of(card, struct machine_data, card);
+
+ platform_device_unregister(sound_device);
+
+ kfree(mdata);
+ sound_device->dev.platform_data = NULL;
+
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver p1022_ds_driver = {
+ .probe = p1022_ds_probe,
+ .remove = __devexit_p(p1022_ds_remove),
+ .driver = {
+ /* The name must match the 'model' property in the device tree,
+ * in lowercase letters, but only the part after that last
+ * comma. This is because some model properties have a "fsl,"
+ * prefix.
+ */
+ .name = "snd-soc-p1022",
+ .owner = THIS_MODULE,
+ },
+};
+
+/**
+ * p1022_ds_init: machine driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init p1022_ds_init(void)
+{
+ struct device_node *guts_np;
+ struct resource res;
+
+ pr_info("Freescale P1022 DS ALSA SoC machine driver\n");
+
+ /* Get the physical address of the global utilities registers */
+ guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
+ if (of_address_to_resource(guts_np, 0, &res)) {
+ pr_err("p1022-ds: missing/invalid global utilities node\n");
+ return -EINVAL;
+ }
+ guts_phys = res.start;
+ of_node_put(guts_np);
+
+ return platform_driver_register(&p1022_ds_driver);
+}
+
+/**
+ * p1022_ds_exit: machine driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit p1022_ds_exit(void)
+{
+ platform_driver_unregister(&p1022_ds_driver);
+}
+
+module_init(p1022_ds_init);
+module_exit(p1022_ds_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c
new file mode 100644
index 00000000..ba4d85e3
--- /dev/null
+++ b/sound/soc/fsl/pcm030-audio-fabric.c
@@ -0,0 +1,91 @@
+/*
+ * Phytec pcm030 driver for the PSC of the Freescale MPC52xx
+ * configured as AC97 interface
+ *
+ * Copyright 2008 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "mpc5200_dma.h"
+#include "mpc5200_psc_ac97.h"
+#include "../codecs/wm9712.h"
+
+#define DRV_NAME "pcm030-audio-fabric"
+
+static struct snd_soc_card card;
+
+static struct snd_soc_dai_link pcm030_fabric_dai[] = {
+{
+ .name = "AC97",
+ .stream_name = "AC97 Analog",
+ .codec_dai_name = "wm9712-hifi",
+ .cpu_dai_name = "mpc5200-psc-ac97.0",
+ .platform_name = "mpc5200-pcm-audio",
+ .codec_name = "wm9712-codec",
+},
+{
+ .name = "AC97",
+ .stream_name = "AC97 IEC958",
+ .codec_dai_name = "wm9712-aux",
+ .cpu_dai_name = "mpc5200-psc-ac97.1",
+ .platform_name = "mpc5200-pcm-audio",
+ .codec_name = "wm9712-codec",
+},
+};
+
+static __init int pcm030_fabric_init(void)
+{
+ struct platform_device *pdev;
+ int rc;
+
+ if (!of_machine_is_compatible("phytec,pcm030"))
+ return -ENODEV;
+
+
+ card.name = "pcm030";
+ card.dai_link = pcm030_fabric_dai;
+ card.num_links = ARRAY_SIZE(pcm030_fabric_dai);
+
+ pdev = platform_device_alloc("soc-audio", 1);
+ if (!pdev) {
+ pr_err("pcm030_fabric_init: platform_device_alloc() failed\n");
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, &card);
+
+ rc = platform_device_add(pdev);
+ if (rc) {
+ pr_err("pcm030_fabric_init: platform_device_add() failed\n");
+ platform_device_put(pdev);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+module_init(pcm030_fabric_init);
+
+
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_DESCRIPTION(DRV_NAME ": mpc5200 pcm030 fabric driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig
new file mode 100644
index 00000000..e30ebbe7
--- /dev/null
+++ b/sound/soc/imx/Kconfig
@@ -0,0 +1,133 @@
+menuconfig SND_IMX_SOC
+ tristate "SoC Audio for Freescale i.MX CPUs"
+ depends on ARCH_MXC
+ select SND_PCM
+ select FIQ
+ select SND_SOC_AC97_BUS
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the i.MX SSI interface.
+
+
+if SND_IMX_SOC
+
+config SND_MXC_SOC_ESAI
+ tristate
+
+config SND_MXC_SOC_FIQ
+ tristate
+
+config SND_MXC_SOC_MX2
+ tristate
+
+config SND_MXC_SOC_SPDIF_DAI
+ tristate
+
+config SND_MXC_SOC_WM1133_EV1
+ tristate "Audio on the the i.MX31ADS with WM1133-EV1 fitted"
+ depends on MACH_MX31ADS_WM1133_EV1 && EXPERIMENTAL
+ select SND_SOC_WM8350
+ select SND_MXC_SOC_FIQ
+ help
+ Enable support for audio on the i.MX31ADS with the WM1133-EV1
+ PMIC board with WM8835x fitted.
+
+config SND_SOC_MX27VIS_AIC32X4
+ tristate "SoC audio support for Visstrim M10 boards"
+ depends on MACH_IMX27_VISSTRIM_M10
+ select SND_SOC_TVL320AIC32X4
+ select SND_MXC_SOC_MX2
+ help
+ Say Y if you want to add support for SoC audio on Visstrim SM10
+ board with TLV320AIC32X4 codec.
+
+config SND_SOC_PHYCORE_AC97
+ tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards"
+ depends on MACH_PCM043 || MACH_PCA100
+ select SND_SOC_WM9712
+ select SND_MXC_SOC_FIQ
+ help
+ Say Y if you want to add support for SoC audio on Phytec phyCORE
+ and phyCARD boards in AC97 mode
+
+config SND_SOC_IMX_SGTL5000
+ tristate "SoC Audio support for i.MX boards with sgtl5000"
+ depends on I2C && (MACH_MX35_3DS || MACH_MX51_BABBAGE \
+ || MACH_MX6Q_SABRELITE || MACH_MX6Q_ARM2)
+ select SND_SOC_SGTL5000
+ select SND_MXC_SOC_MX2
+ help
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a sgtl5000 codec.
+
+config SND_SOC_IMX_WM8958
+ tristate "SoC Audio support for IMX boards with WM8958"
+ depends on MFD_WM8994
+ select SND_MXC_SOC_MX2
+ select SND_SOC_WM8994
+ help
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a WM8958 codec.
+
+config SND_SOC_IMX_WM8962
+ tristate "SoC Audio support for IMX boards with WM8962"
+ select SND_MXC_SOC_MX2
+ select SND_SOC_WM8962
+ help
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a WM8962 codec.
+
+config SND_SOC_IMX_CS42888
+ tristate "SoC Audio support for i.MX boards with cs42888"
+ depends on I2C && (MACH_MX6Q_ARM2 || MACH_MX6Q_SABREAUTO || MACH_MX53_ARD)
+ select SND_SOC_CS42888
+ select SND_MXC_SOC_MX2
+ help
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a cs42888 codec
+
+config SND_SOC_IMX_SI4763
+ tristate "SoC Audio support for IMX SI4763"
+ select SND_MXC_SOC_SSI
+ select SND_SOC_SI4763
+ help
+ Say Y if you want to add support for Soc audio for the AMFM Tuner chip
+ SI4763 module.
+
+config SND_SOC_EUKREA_TLV320
+ tristate "Eukrea TLV320"
+ depends on MACH_EUKREA_MBIMX27_BASEBOARD \
+ || MACH_EUKREA_MBIMXSD25_BASEBOARD \
+ || MACH_EUKREA_MBIMXSD35_BASEBOARD \
+ || MACH_EUKREA_MBIMXSD51_BASEBOARD
+ select SND_SOC_TLV320AIC23
+ select SND_MXC_SOC_FIQ
+ help
+ Enable I2S based access to the TLV320AIC23B codec attached
+ to the SSI interface
+
+config SND_SOC_IMX_SPDIF
+ tristate "SoC Audio support for IMX - S/PDIF"
+ default n
+ select SND_MXC_SOC_SPDIF_DAI
+ select SND_SOC_MXC_SPDIF
+ select SND_MXC_SOC_MX2
+ help
+ Say Y if you want to add support for SoC audio on a IMX development
+ board with S/PDIF.
+
+config SND_SOC_IMX_HDMI
+ tristate "SoC Audio support for IMX - HDMI"
+ default n
+ select SND_SOC_MXC_HDMI
+ select MFD_MXC_HDMI
+ help
+ Say Y if you want to add support for SoC audio through IMX HDMI.
+
+config SND_MXC_SOC_IRAM
+ bool "Locate SSI Audio DMA playback buffers in IRAM"
+ depends on MACH_MX6SL_EVK && MACH_MX6SL_ARM2
+ help
+ Say Y if you don't want Audio playback buffers in external ram
+
+endif # SND_IMX_SOC
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile
new file mode 100644
index 00000000..adeef06f
--- /dev/null
+++ b/sound/soc/imx/Makefile
@@ -0,0 +1,37 @@
+# i.MX Platform Support
+snd-soc-imx-objs := imx-ssi.o imx-esai.o
+snd-soc-imx-fiq-objs := imx-pcm-fiq.o
+snd-soc-imx-mx2-objs := imx-pcm-dma-mx2.o
+snd-soc-imx-spdif-dai-objs := imx-spdif-dai.o
+
+obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o
+obj-$(CONFIG_SND_MXC_SOC_FIQ) += snd-soc-imx-fiq.o
+obj-$(CONFIG_SND_MXC_SOC_MX2) += snd-soc-imx-mx2.o
+obj-$(CONFIG_SND_MXC_SOC_SPDIF_DAI) += snd-soc-imx-spdif-dai.o
+
+# i.MX Machine Support
+snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
+snd-soc-phycore-ac97-objs := phycore-ac97.o
+snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o
+snd-soc-wm1133-ev1-objs := wm1133-ev1.o
+snd-soc-imx-wm8958-objs := imx-wm8958.o
+snd-soc-imx-wm8962-objs := imx-wm8962.o
+snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
+snd-soc-imx-cs42888-objs := imx-cs42888.o
+snd-soc-imx-spdif-objs := imx-spdif.o
+snd-soc-imx-si4763-objs := imx-si4763.o
+snd-soc-imx-hdmi-objs := imx-hdmi.o imx-hdmi-dai.o imx-hdmi-dma.o hdmi_pcm.o
+
+obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
+obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
+obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
+obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
+obj-$(CONFIG_SND_SOC_IMX_WM8958) += snd-soc-imx-wm8958.o
+obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o
+obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
+obj-$(CONFIG_SND_SOC_IMX_CS42888) += snd-soc-imx-cs42888.o
+obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
+obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o
+obj-$(CONFIG_SND_SOC_IMX_SI4763) += snd-soc-imx-si4763.o
+
+AFLAGS_hdmi_pcm.o := -march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=softfp
diff --git a/sound/soc/imx/eukrea-tlv320.c b/sound/soc/imx/eukrea-tlv320.c
new file mode 100644
index 00000000..75fb4b83
--- /dev/null
+++ b/sound/soc/imx/eukrea-tlv320.c
@@ -0,0 +1,131 @@
+/*
+ * eukrea-tlv320.c -- SoC audio for eukrea_cpuimxXX in I2S mode
+ *
+ * Copyright 2010 Eric Bénard, Eukréa Electromatique <eric@eukrea.com>
+ *
+ * based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c
+ * which is Copyright 2009 Simtec Electronics
+ * and on sound/soc/imx/phycore-ac97.c which is
+ * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <asm/mach-types.h>
+
+#include "../codecs/tlv320aic23.h"
+#include "imx-ssi.h"
+
+#define CODEC_CLOCK 12000000
+
+static int eukrea_tlv320_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret) {
+ pr_err("%s: failed set cpu dai format\n", __func__);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret) {
+ pr_err("%s: failed set codec dai format\n", __func__);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ CODEC_CLOCK, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ pr_err("%s: failed setting codec sysclk\n", __func__);
+ return ret;
+ }
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0);
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret) {
+ pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops eukrea_tlv320_snd_ops = {
+ .hw_params = eukrea_tlv320_hw_params,
+};
+
+static struct snd_soc_dai_link eukrea_tlv320_dai = {
+ .name = "tlv320aic23",
+ .stream_name = "TLV320AIC23",
+ .codec_dai_name = "tlv320aic23-hifi",
+ .platform_name = "imx-fiq-pcm-audio.0",
+ .codec_name = "tlv320aic23-codec.0-001a",
+ .cpu_dai_name = "imx-ssi.0",
+ .ops = &eukrea_tlv320_snd_ops,
+};
+
+static struct snd_soc_card eukrea_tlv320 = {
+ .name = "cpuimx-audio",
+ .dai_link = &eukrea_tlv320_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *eukrea_tlv320_snd_device;
+
+static int __init eukrea_tlv320_init(void)
+{
+ int ret;
+
+ if (!machine_is_eukrea_cpuimx27() && !machine_is_eukrea_cpuimx25sd()
+ && !machine_is_eukrea_cpuimx35sd()
+ && !machine_is_eukrea_cpuimx51sd())
+ /* return happy. We might run on a totally different machine */
+ return 0;
+
+ eukrea_tlv320_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!eukrea_tlv320_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(eukrea_tlv320_snd_device, &eukrea_tlv320);
+ ret = platform_device_add(eukrea_tlv320_snd_device);
+
+ if (ret) {
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+ platform_device_put(eukrea_tlv320_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit eukrea_tlv320_exit(void)
+{
+ platform_device_unregister(eukrea_tlv320_snd_device);
+}
+
+module_init(eukrea_tlv320_init);
+module_exit(eukrea_tlv320_exit);
+
+MODULE_AUTHOR("Eric Bénard <eric@eukrea.com>");
+MODULE_DESCRIPTION("CPUIMX ALSA SoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/hdmi_pcm.S b/sound/soc/imx/hdmi_pcm.S
new file mode 100644
index 00000000..6e97e1a4
--- /dev/null
+++ b/sound/soc/imx/hdmi_pcm.S
@@ -0,0 +1,238 @@
+/*
+* Copyright (C) 2010-2012 Freescale Semiconductor, Inc. All Rights Reserved.
+*
+* 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 of the License, or
+* (at your option) any later version.
+
+* 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, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+.section .text
+
+.global hdmi_dma_copy_16_neon_lut
+.global hdmi_dma_copy_16_neon_fast
+.global hdmi_dma_copy_24_neon_lut
+.global hdmi_dma_copy_24_neon_fast
+
+
+/* hdmi_dma_copy_16_neon_lut
+ * Convert pcm sample to iec sample. Pcm sample is 16 bits.
+ * Frame index is between 0 and 47 inclusively. Channel count can be 1, 2, 4 or 8.
+ * Frame count should be multipliable by 4. Sample count should be multipliable by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst,
+ * int samples, unsigned char *lookup_table);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM16 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ * lookup_table Preconstructed header table. Channels interleaved.
+ */
+
+hdmi_dma_copy_16_neon_lut:
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d6, r12
+
+hdmi_dma_copy_16_neon_lut_start:
+
+ /* get 8 samples to q0 */
+ vld1.16 {d0, d1}, [r0]! /* TODO: aligned */
+
+/* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q1, q0 /* count of 1s */
+ vpadd.i8 d2, d2, d3 /* only care about the least bit in every element */
+ vand d2, d2, d6 /* clear other bits while keep the least bit */
+ vshl.u8 d2, d2, #3 /* bit p: d2 = d2 << 3 */
+
+ /* get packet header */
+ vld1.8 {d5}, [r3]!
+ veor d4, d5, d2 /* xor bit c */
+
+ /* store: (d4 << 16 | q0) << 8 */
+ vmovl.u8 q2, d4 /* expand from char to short */
+ vzip.16 q0, q2
+ vshl.u32 q0, q0, #8
+ vshl.u32 q1, q2, #8
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_16_neon_lut_start
+
+ mov pc, lr
+
+/* hdmi_dma_copy_16_neon_fast
+ * Convert pcm sample to iec sample. Pcm sample is 16 bits.
+ * Frame index is between 48 and 191 inclusively. Channel count can be 1, 2, 4 or 8.
+ * Frame count should be multipliable by 4. Sample count should be multipliable by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_16_neon_fast(unsigned short *src, unsigned int *dst, int samples);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM16 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ */
+
+hdmi_dma_copy_16_neon_fast:
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d6, r12
+
+hdmi_dma_copy_16_neon_fast_start:
+ /* get 8 samples to q0 */
+ vld1.16 {d0, d1}, [r0]! /* TODO: aligned */
+
+/* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q1, q0 /* count of 1s */
+ vpadd.i8 d2, d2, d3
+ vand d2, d2, d6 /* clear other bits while keep the least bit */
+ vshl.u8 d4, d2, #3 /* bit p: d2 = d2 << 3 */ /* finally we construct packet header */
+
+ /* get packet header: always 0 */
+
+ /* store: (d4 << 16 | q0) << 8 */
+ vmovl.u8 q2, d4 /* expand from char to short */
+ vzip.16 q0, q2
+ vshl.u32 q0, q0, #8
+ vshl.u32 q1, q2, #8
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_16_neon_fast_start
+
+ mov pc, lr
+
+
+
+/* hdmi_dma_copy_24_neon_lut
+ * Convert pcm sample to iec sample. Pcm sample is 24 bits.
+ * Frame index is between 0 and 47 inclusively. Channel count can be 1, 2, 4 or 8.
+ * Frame count should be multipliable by 4. Sample count should be multipliable by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst,
+ * int samples, unsigned char *lookup_table);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM24 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ * lookup_table Preconstructed header table. Channels interleaved.
+ */
+
+hdmi_dma_copy_24_neon_lut:
+ vpush {d8}
+
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d8, r12
+
+hdmi_dma_copy_24_neon_lut_start:
+
+ /* get 8 samples to q0 and q1 */
+ vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */
+
+/* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q2, q0 /* count of 1s */
+ vpadd.i8 d4, d4, d5 /* only care about the least bit in every element */
+ vcnt.8 q3, q1
+ vpadd.i8 d6, d6, d7
+ vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */
+ vand d4, d4, d8 /* clear other bits while keep the least bit */
+ vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */
+
+ /* get packet header */
+ vld1.8 {d5}, [r3]! /* d5: original header */
+ veor d5, d5, d4 /* fix bit p */
+
+ /* store: (d5 << 24 | q0) */
+ vmovl.u8 q3, d5 /* expand from char to short */
+ vmovl.u16 q2, d6 /* expand from short to int */
+ vmovl.u16 q3, d7
+ vshl.u32 q2, q2, #24
+ vshl.u32 q3, q3, #24
+ vorr q0, q0, q2
+ vorr q1, q1, q3
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_24_neon_lut_start
+
+ vpop {d8}
+ mov pc, lr
+
+/* hdmi_dma_copy_24_neon_fast
+ * Convert pcm sample to iec sample. Pcm sample is 24 bits.
+ * Frame index is between 48 and 191 inclusively. Channel count can be 1, 2, 4 or 8.
+ * Frame count should be multipliable by 4. Sample count should be multipliable by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_24_neon_fast(unsigned int *src, unsigned int *dst, int samples);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM24 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ */
+
+hdmi_dma_copy_24_neon_fast:
+ vpush {d8}
+
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d8, r12
+
+hdmi_dma_copy_24_neon_fast_start:
+ /* get 8 samples to q0 and q1 */
+ vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */
+
+/* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q2, q0 /* count of 1s */
+ vpadd.i8 d4, d4, d5 /* only care about the least bit in every element */
+ vcnt.8 q3, q1
+ vpadd.i8 d6, d6, d7
+ vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */
+ vand d4, d4, d8 /* clear other bits while keep the least bit */
+ vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */
+
+ /* store: (d4 << 24 | q0) */
+ vmovl.u8 q3, d4 /* expand from char to short */
+ vmovl.u16 q2, d6 /* expand from short to int */
+ vmovl.u16 q3, d7
+ vshl.u32 q2, q2, #24
+ vshl.u32 q3, q3, #24
+ vorr q0, q0, q2
+ vorr q1, q1, q3
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_24_neon_fast_start
+
+ vpop {d8}
+ mov pc, lr
+
diff --git a/sound/soc/imx/imx-cs42888.c b/sound/soc/imx/imx-cs42888.c
new file mode 100644
index 00000000..62d221fb
--- /dev/null
+++ b/sound/soc/imx/imx-cs42888.c
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2010-2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/fsl_devices.h>
+#include <linux/gpio.h>
+#include <linux/mxc_asrc.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/soc-dai.h>
+#include <sound/pcm_params.h>
+
+#include <mach/hardware.h>
+#include <mach/clock.h>
+#include <asm/mach-types.h>
+
+#include "imx-esai.h"
+#include "../codecs/cs42888.h"
+#include "imx-pcm.h"
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_IMX_HAVE_PLATFORM_IMX_ASRC)
+
+static enum asrc_word_width get_asrc_input_width(
+ struct snd_pcm_hw_params *params)
+{
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_U16:
+ case SNDRV_PCM_FORMAT_S16_LE:
+ case SNDRV_PCM_FORMAT_S16_BE:
+ return ASRC_WIDTH_16_BIT;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ case SNDRV_PCM_FORMAT_S20_3BE:
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ case SNDRV_PCM_FORMAT_S24_3BE:
+ case SNDRV_PCM_FORMAT_S24_BE:
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_U24_BE:
+ case SNDRV_PCM_FORMAT_U24_LE:
+ case SNDRV_PCM_FORMAT_U24_3BE:
+ case SNDRV_PCM_FORMAT_U24_3LE:
+ return ASRC_WIDTH_24_BIT;
+ case SNDRV_PCM_FORMAT_S8:
+ case SNDRV_PCM_FORMAT_U8:
+ case SNDRV_PCM_FORMAT_S32:
+ case SNDRV_PCM_FORMAT_U32:
+ default:
+ pr_err("Format is not support!\n");
+ return -EINVAL;
+ }
+}
+
+static int config_asrc(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ unsigned int rate = params_rate(params);
+ unsigned int channel = params_channels(params);
+ struct imx_pcm_runtime_data *iprtd =
+ substream->runtime->private_data;
+ struct asrc_config config = {0};
+ int ret = 0;
+
+ if ((channel != 2) && (channel != 4) && (channel != 6))
+ return -EINVAL;
+
+ ret = iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_req_pair(channel, &iprtd->asrc_index);
+ if (ret < 0) {
+ pr_err("Fail to request asrc pair\n");
+ return -EINVAL;
+ }
+
+ config.input_word_width = get_asrc_input_width(params);
+ config.output_word_width = iprtd->p2p->p2p_width;
+ config.pair = iprtd->asrc_index;
+ config.channel_num = channel;
+ config.input_sample_rate = rate;
+ config.output_sample_rate = iprtd->p2p->p2p_rate;
+ config.inclk = INCLK_NONE;
+ config.outclk = OUTCLK_ESAI_TX;
+
+ ret = iprtd->asrc_pcm_p2p_ops_ko->asrc_p2p_config_pair(&config);
+ if (ret < 0) {
+ pr_err("Fail to config asrc\n");
+ return ret;
+ }
+
+ return 0;
+}
+#endif
+
+struct imx_priv_state {
+ int hw;
+};
+
+static struct asrc_p2p_params *esai_asrc;
+static struct imx_priv_state hw_state;
+unsigned int mclk_freq;
+
+static int imx_3stack_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ if (!cpu_dai->active) {
+ hw_state.hw = 0;
+ }
+
+ return 0;
+}
+
+static void imx_3stack_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ if (!cpu_dai->active)
+ hw_state.hw = 0;
+}
+
+static int imx_3stack_surround_hw_free(struct snd_pcm_substream *substream)
+{
+ struct imx_pcm_runtime_data *iprtd = substream->runtime->private_data;
+
+ if (iprtd->asrc_enable) {
+ if (iprtd->asrc_index != -1) {
+ iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_release_pair(
+ iprtd->asrc_index);
+ iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_finish_conv(iprtd->asrc_index);
+ }
+ iprtd->asrc_index = -1;
+ }
+
+ return 0;
+}
+static int imx_3stack_surround_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_pcm_runtime_data *iprtd = substream->runtime->private_data;
+ unsigned int rate = params_rate(params);
+ u32 dai_format;
+ unsigned int lrclk_ratio = 0;
+ int err = 0;
+
+ if (iprtd->asrc_enable) {
+ err = config_asrc(substream, params);
+ if (err < 0)
+ return err;
+ rate = iprtd->p2p->p2p_rate;
+ }
+
+ if (hw_state.hw)
+ return 0;
+ hw_state.hw = 1;
+
+ if (cpu_is_mx53() || machine_is_mx6q_sabreauto()) {
+ switch (rate) {
+ case 32000:
+ lrclk_ratio = 3;
+ break;
+ case 48000:
+ lrclk_ratio = 3;
+ break;
+ case 64000:
+ lrclk_ratio = 1;
+ break;
+ case 96000:
+ lrclk_ratio = 1;
+ break;
+ case 128000:
+ lrclk_ratio = 1;
+ break;
+ case 44100:
+ lrclk_ratio = 3;
+ break;
+ case 88200:
+ lrclk_ratio = 1;
+ break;
+ case 176400:
+ lrclk_ratio = 0;
+ break;
+ case 192000:
+ lrclk_ratio = 0;
+ break;
+ default:
+ pr_info("Rate not support.\n");
+ return -EINVAL;;
+ }
+ } else if (cpu_is_mx6q() || cpu_is_mx6dl()) {
+ switch (rate) {
+ case 32000:
+ lrclk_ratio = 5;
+ break;
+ case 48000:
+ lrclk_ratio = 5;
+ break;
+ case 64000:
+ lrclk_ratio = 2;
+ break;
+ case 96000:
+ lrclk_ratio = 2;
+ break;
+ case 128000:
+ lrclk_ratio = 2;
+ break;
+ case 44100:
+ lrclk_ratio = 5;
+ break;
+ case 88200:
+ lrclk_ratio = 2;
+ break;
+ case 176400:
+ lrclk_ratio = 0;
+ break;
+ case 192000:
+ lrclk_ratio = 0;
+ break;
+ default:
+ pr_info("Rate not support.\n");
+ return -EINVAL;;
+ }
+ }
+ dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set cpu DAI configuration */
+ snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32);
+ /* set the ESAI system clock as output */
+ if (cpu_is_mx53() || machine_is_mx6q_sabreauto()) {
+ snd_soc_dai_set_sysclk(cpu_dai, ESAI_CLK_EXTAL,
+ mclk_freq, SND_SOC_CLOCK_OUT);
+ } else if (cpu_is_mx6q() || cpu_is_mx6dl()) {
+ snd_soc_dai_set_sysclk(cpu_dai, ESAI_CLK_EXTAL_DIV,
+ mclk_freq, SND_SOC_CLOCK_OUT);
+ }
+ /* set the ratio */
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PSR, 1);
+ if (cpu_is_mx53() || machine_is_mx6q_sabreauto())
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PM, 0);
+ else if (cpu_is_mx6q() || cpu_is_mx6dl())
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PM, 2);
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_FP, lrclk_ratio);
+
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PSR, 1);
+ if (cpu_is_mx53() || machine_is_mx6q_sabreauto())
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PM, 0);
+ else if (cpu_is_mx6q() || cpu_is_mx6dl())
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PM, 2);
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_FP, lrclk_ratio);
+
+ /* set codec DAI configuration */
+ snd_soc_dai_set_fmt(codec_dai, dai_format);
+ /* set codec Master clock */
+ snd_soc_dai_set_sysclk(codec_dai, 0, mclk_freq, SND_SOC_CLOCK_IN);
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_3stack_surround_ops = {
+ .startup = imx_3stack_startup,
+ .shutdown = imx_3stack_shutdown,
+ .hw_params = imx_3stack_surround_hw_params,
+ .hw_free = imx_3stack_surround_hw_free,
+};
+
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Line out jack */
+ {"Line Out Jack", NULL, "AOUT1L"},
+ {"Line Out Jack", NULL, "AOUT1R"},
+ {"Line Out Jack", NULL, "AOUT2L"},
+ {"Line Out Jack", NULL, "AOUT2R"},
+ {"Line Out Jack", NULL, "AOUT3L"},
+ {"Line Out Jack", NULL, "AOUT3R"},
+ {"Line Out Jack", NULL, "AOUT4L"},
+ {"Line Out Jack", NULL, "AOUT4R"},
+ {"AIN1L", NULL, "Line In Jack"},
+ {"AIN1R", NULL, "Line In Jack"},
+ {"AIN2L", NULL, "Line In Jack"},
+ {"AIN2R", NULL, "Line In Jack"},
+};
+
+static int imx_3stack_cs42888_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+
+ snd_soc_dapm_new_controls(&codec->dapm, imx_3stack_dapm_widgets,
+ ARRAY_SIZE(imx_3stack_dapm_widgets));
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map));
+ snd_soc_dapm_sync(&codec->dapm);
+ return 0;
+}
+static int imx_asrc_cs42888_init(struct snd_soc_pcm_runtime *rtd)
+{
+
+ snd_soc_pcm_set_drvdata(rtd, (void *)esai_asrc);
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_3stack_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "CS42888",
+#ifdef CONFIG_SOC_IMX53
+ .codec_name = "cs42888.1-0048",
+#endif
+#ifdef CONFIG_SOC_IMX6Q
+ .codec_name = "cs42888.0-0048",
+#endif
+ .cpu_dai_name = "imx-esai.0",
+ .platform_name = "imx-pcm-audio.0",
+ .init = imx_3stack_cs42888_init,
+ .ops = &imx_3stack_surround_ops,
+ },
+ {
+ .name = "HiFi_ASRC",
+ .stream_name = "HIFI_ASRC",
+ .codec_dai_name = "CS42888_ASRC",
+#ifdef CONFIG_SOC_IMX53
+ .codec_name = "cs42888.1-0048",
+#endif
+#ifdef CONFIG_SOC_IMX6Q
+ .codec_name = "cs42888.0-0048",
+#endif
+ .cpu_dai_name = "imx-esai.0",
+ .platform_name = "imx-pcm-audio.0",
+ .init = imx_asrc_cs42888_init,
+ .ops = &imx_3stack_surround_ops,
+ },
+};
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "cs42888-audio",
+ .dai_link = imx_3stack_dai,
+ .num_links = ARRAY_SIZE(imx_3stack_dai),
+};
+
+/*
+ * This function will register the snd_soc_pcm_link drivers.
+ */
+static int __devinit imx_3stack_cs42888_probe(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat_data = pdev->dev.platform_data;
+
+ if (!plat_data) {
+ dev_err(&pdev->dev, "plat_data is missing\n");
+ return -EINVAL;
+ }
+ mclk_freq = plat_data->sysclk;
+ if (plat_data->codec_name) {
+ imx_3stack_dai[0].codec_name = plat_data->codec_name;
+ imx_3stack_dai[1].codec_name = plat_data->codec_name;
+ }
+ esai_asrc = kzalloc(sizeof(struct asrc_p2p_params), GFP_KERNEL);
+ if (plat_data->priv)
+ memcpy(esai_asrc, plat_data->priv,
+ sizeof(struct asrc_p2p_params));
+ return 0;
+}
+
+static int __devexit imx_3stack_cs42888_remove(struct platform_device *pdev)
+{
+ if (esai_asrc)
+ kfree(esai_asrc);
+ return 0;
+}
+
+static struct platform_driver imx_3stack_cs42888_driver = {
+ .probe = imx_3stack_cs42888_probe,
+ .remove = __devexit_p(imx_3stack_cs42888_remove),
+ .driver = {
+ .name = "imx-cs42888",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_asoc_init(void)
+{
+ int ret;
+ pr_info("imx_3stack asoc driver\n");
+ ret = platform_driver_register(&imx_3stack_cs42888_driver);
+ if (ret < 0)
+ goto exit;
+
+ imx_3stack_snd_device = platform_device_alloc("soc-audio", 2);
+ if (!imx_3stack_snd_device)
+ goto err_device_alloc;
+ platform_set_drvdata(imx_3stack_snd_device, &snd_soc_card_imx_3stack);
+ ret = platform_device_add(imx_3stack_snd_device);
+ if (0 == ret)
+ goto exit;
+
+ platform_device_unregister(imx_3stack_snd_device);
+err_device_alloc:
+ platform_driver_unregister(&imx_3stack_cs42888_driver);
+exit:
+ return ret;
+}
+
+static void __exit imx_3stack_asoc_exit(void)
+{
+ platform_driver_unregister(&imx_3stack_cs42888_driver);
+ platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_asoc_init);
+module_exit(imx_3stack_asoc_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("ALSA SoC cs42888 Machine Layer Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-esai.c b/sound/soc/imx/imx-esai.c
new file mode 100644
index 00000000..8951a81f
--- /dev/null
+++ b/sound/soc/imx/imx-esai.c
@@ -0,0 +1,757 @@
+/*
+ * Copyright 2008-2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+ /*!
+ * @file imx-esai.c
+ * @brief this file implements the esai interface
+ * in according to ASoC architeture
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/fsl_devices.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <mach/clock.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/esai.h>
+
+#include "imx-esai.h"
+#include "imx-pcm.h"
+
+static struct imx_esai *local_esai;
+
+static int imx_esai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 ecr, tccr, rccr;
+
+ ecr = readl(esai->base + ESAI_ECR);
+ tccr = readl(esai->base + ESAI_TCCR);
+ rccr = readl(esai->base + ESAI_RCCR);
+
+ if (dir == SND_SOC_CLOCK_IN) {
+ tccr &= ~(ESAI_TCCR_THCKD | ESAI_TCCR_TCKD | ESAI_TCCR_TFSD);
+ rccr &= ~(ESAI_RCCR_RHCKD | ESAI_RCCR_RCKD | ESAI_RCCR_RFSD);
+ } else {
+ tccr |= ESAI_TCCR_THCKD | ESAI_TCCR_TCKD | ESAI_TCCR_TFSD;
+ rccr |= ESAI_RCCR_RHCKD | ESAI_RCCR_RCKD | ESAI_RCCR_RFSD;
+
+ if (clk_id == ESAI_CLK_FSYS) {
+ ecr &= ~(ESAI_ECR_ETI | ESAI_ECR_ETO);
+ ecr &= ~(ESAI_ECR_ERI | ESAI_ECR_ERO);
+ } else if (clk_id == ESAI_CLK_EXTAL) {
+ ecr |= ESAI_ECR_ETI;
+ ecr |= ESAI_ECR_ETO;
+ ecr |= ESAI_ECR_ERI;
+ ecr |= ESAI_ECR_ERO;
+ } else if (clk_id == ESAI_CLK_EXTAL_DIV) {
+ ecr |= ESAI_ECR_ETI;
+ ecr &= ~ESAI_ECR_ETO;
+ ecr |= ESAI_ECR_ERI;
+ ecr &= ~ESAI_ECR_ERO;
+ }
+ }
+
+ writel(ecr, esai->base + ESAI_ECR);
+ writel(tccr, esai->base + ESAI_TCCR);
+ writel(rccr, esai->base + ESAI_RCCR);
+
+ ESAI_DUMP();
+ return 0;
+}
+
+static int imx_esai_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 tccr, rccr;
+
+ tccr = readl(esai->base + ESAI_TCCR);
+ rccr = readl(esai->base + ESAI_RCCR);
+
+ switch (div_id) {
+ case ESAI_TX_DIV_PSR:
+ tccr &= ESAI_TCCR_TPSR_MASK;
+ if (div)
+ tccr |= ESAI_TCCR_TPSR_BYPASS;
+ else
+ tccr &= ~ESAI_TCCR_TPSR_DIV8;
+ break;
+ case ESAI_TX_DIV_PM:
+ tccr &= ESAI_TCCR_TPM_MASK;
+ tccr |= ESAI_TCCR_TPM(div);
+ break;
+ case ESAI_TX_DIV_FP:
+ tccr &= ESAI_TCCR_TFP_MASK;
+ tccr |= ESAI_TCCR_TFP(div);
+ break;
+ case ESAI_RX_DIV_PSR:
+ rccr &= ESAI_RCCR_RPSR_MASK;
+ if (div)
+ rccr |= ESAI_RCCR_RPSR_BYPASS;
+ else
+ rccr &= ~ESAI_RCCR_RPSR_DIV8;
+ break;
+ case ESAI_RX_DIV_PM:
+ rccr &= ESAI_RCCR_RPM_MASK;
+ rccr |= ESAI_RCCR_RPM(div);
+ break;
+ case ESAI_RX_DIV_FP:
+ rccr &= ESAI_RCCR_RFP_MASK;
+ rccr |= ESAI_RCCR_RFP(div);
+ break;
+ return -EINVAL;
+ }
+ writel(tccr, esai->base + ESAI_TCCR);
+ writel(rccr, esai->base + ESAI_RCCR);
+ return 0;
+}
+
+/*
+ * ESAI Network Mode or TDM slots configuration.
+ */
+static int imx_esai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+ unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+ struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 tccr, rccr;
+
+ tccr = readl(esai->base + ESAI_TCCR);
+
+ tccr &= ESAI_TCCR_TDC_MASK;
+ tccr |= ESAI_TCCR_TDC(slots - 1);
+
+ writel(tccr, esai->base + ESAI_TCCR);
+ writel((tx_mask & 0xffff), esai->base + ESAI_TSMA);
+ writel(((tx_mask >> 16) & 0xffff), esai->base + ESAI_TSMB);
+
+ rccr = readl(esai->base + ESAI_RCCR);
+
+ rccr &= ESAI_RCCR_RDC_MASK;
+ rccr |= ESAI_RCCR_RDC(slots - 1);
+
+ writel(rccr, esai->base + ESAI_RCCR);
+ writel((rx_mask & 0xffff), esai->base + ESAI_RSMA);
+ writel(((rx_mask >> 16) & 0xffff), esai->base + ESAI_RSMB);
+
+ ESAI_DUMP();
+ return 0;
+}
+
+/*
+ * ESAI DAI format configuration.
+ */
+static int imx_esai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 tcr, tccr, rcr, rccr, saicr;
+
+ tcr = readl(esai->base + ESAI_TCR);
+ tccr = readl(esai->base + ESAI_TCCR);
+ rcr = readl(esai->base + ESAI_RCR);
+ rccr = readl(esai->base + ESAI_RCCR);
+ saicr = readl(esai->base + ESAI_SAICR);
+
+ /* DAI mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* data on rising edge of bclk, frame low 1clk before data */
+ tcr &= ~ESAI_TCR_TFSL;
+ tcr |= ESAI_TCR_TFSR;
+ rcr &= ~ESAI_RCR_RFSL;
+ rcr |= ESAI_RCR_RFSR;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ /* data on rising edge of bclk, frame high with data */
+ tcr &= ~(ESAI_TCR_TFSL | ESAI_TCR_TFSR);
+ rcr &= ~(ESAI_RCR_RFSL | ESAI_RCR_RFSR);
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ /* data on rising edge of bclk, frame high with data */
+ tcr |= ESAI_TCR_TFSL;
+ rcr |= ESAI_RCR_RFSL;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ /* data on rising edge of bclk, frame high 1clk before data */
+ tcr |= ESAI_TCR_TFSL;
+ rcr |= ESAI_RCR_RFSL;
+ break;
+ }
+
+ /* DAI clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ tccr |= ESAI_TCCR_TFSP;
+ tccr &= ~(ESAI_TCCR_TCKP | ESAI_TCCR_THCKP);
+ rccr &= ~(ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP);
+ rccr |= ESAI_RCCR_RFSP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ tccr &= ~(ESAI_TCCR_TCKP | ESAI_TCCR_THCKP | ESAI_TCCR_TFSP);
+ rccr &= ~(ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP | ESAI_RCCR_RFSP);
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ tccr |= ESAI_TCCR_TCKP | ESAI_TCCR_THCKP | ESAI_TCCR_TFSP;
+ rccr |= ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP | ESAI_RCCR_RFSP;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ tccr &= ~ESAI_TCCR_TFSP;
+ tccr |= ESAI_TCCR_TCKP | ESAI_TCCR_THCKP;
+ rccr &= ~ESAI_RCCR_RFSP;
+ rccr |= ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP;
+ break;
+ }
+
+ /* DAI clock master masks */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ tccr &= ~(ESAI_TCCR_TFSD | ESAI_TCCR_TCKD);
+ rccr &= ~(ESAI_RCCR_RFSD | ESAI_RCCR_RCKD);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ tccr &= ~ESAI_TCCR_TFSD;
+ tccr |= ESAI_TCCR_TCKD;
+ rccr &= ~ESAI_RCCR_RFSD;
+ rccr |= ESAI_RCCR_RCKD;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ tccr &= ~ESAI_TCCR_TCKD;
+ tccr |= ESAI_TCCR_TFSD;
+ rccr &= ~ESAI_RCCR_RCKD;
+ rccr |= ESAI_RCCR_RFSD;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ tccr |= (ESAI_TCCR_TFSD | ESAI_TCCR_TCKD);
+ rccr |= (ESAI_RCCR_RFSD | ESAI_RCCR_RCKD);
+ }
+
+ /* sync */
+ if (esai->flags & IMX_ESAI_SYN)
+ saicr |= ESAI_SAICR_SYNC;
+ else
+ saicr &= ~ESAI_SAICR_SYNC;
+
+ tcr &= ESAI_TCR_TMOD_MASK;
+ rcr &= ESAI_RCR_RMOD_MASK;
+ if (esai->flags & IMX_ESAI_NET) {
+ tcr |= ESAI_TCR_TMOD_NETWORK;
+ rcr |= ESAI_RCR_RMOD_NETWORK;
+ } else {
+ tcr |= ESAI_TCR_TMOD_NORMAL;
+ rcr |= ESAI_RCR_RMOD_NORMAL;
+ }
+
+ writel(tcr, esai->base + ESAI_TCR);
+ writel(tccr, esai->base + ESAI_TCCR);
+ writel(rcr, esai->base + ESAI_RCR);
+ writel(rccr, esai->base + ESAI_RCCR);
+
+ writel(saicr, esai->base + ESAI_SAICR);
+
+ ESAI_DUMP();
+ return 0;
+}
+
+static int imx_esai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ (local_esai->imx_esai_txrx_state & IMX_DAI_ESAI_TX)) {
+ pr_err("error: too much esai playback!\n");
+ return -EINVAL;
+ }
+
+ if (!(local_esai->imx_esai_txrx_state & IMX_DAI_ESAI_TXRX)) {
+ clk_enable(esai->clk);
+
+ writel(ESAI_GPIO_ESAI, esai->base + ESAI_PRRC);
+ writel(ESAI_GPIO_ESAI, esai->base + ESAI_PCRC);
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ local_esai->imx_esai_txrx_state |= IMX_DAI_ESAI_TX;
+ else
+ local_esai->imx_esai_txrx_state |= IMX_DAI_ESAI_RX;
+
+ ESAI_DUMP();
+ return 0;
+}
+
+/*
+ * This function is called to initialize the TX port before enable
+ * the tx port.
+ */
+static int imx_esai_hw_tx_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_pcm_runtime_data *iprtd = substream->runtime->private_data;
+
+ struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 tcr, tfcr;
+ unsigned int channels;
+
+ tcr = readl(esai->base + ESAI_TCR);
+ tfcr = readl(esai->base + ESAI_TFCR);
+
+ tfcr |= ESAI_TFCR_TFR;
+ writel(tfcr, esai->base + ESAI_TFCR);
+ tfcr &= ~ESAI_TFCR_TFR;
+ /* DAI data (word) size */
+ tfcr &= ESAI_TFCR_TWA_MASK;
+ tcr &= ESAI_TCR_TSWS_MASK;
+
+ if (iprtd->asrc_enable) {
+ switch (iprtd->p2p->p2p_width) {
+ case ASRC_WIDTH_16_BIT:
+ tfcr |= ESAI_WORD_LEN_16;
+ tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL16;
+ break;
+ case ASRC_WIDTH_24_BIT:
+ tfcr |= ESAI_WORD_LEN_24;
+ tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL24;
+ break;
+ default:
+ return -EINVAL;
+
+ }
+ tfcr |= ESAI_WORD_LEN_24;
+ tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL24;
+ } else {
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ tfcr |= ESAI_WORD_LEN_16;
+ tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ tfcr |= ESAI_WORD_LEN_20;
+ tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ tfcr |= ESAI_WORD_LEN_24;
+ tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL24;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ channels = params_channels(params);
+ tfcr &= ESAI_TFCR_TE_MASK;
+ tfcr |= ESAI_TFCR_TE(channels);
+
+ tfcr |= ESAI_TFCR_TFWM(64);
+
+ /* Left aligned, Zero padding */
+ tcr |= ESAI_TCR_PADC;
+ /* TDR initialized from the FIFO */
+ tfcr |= ESAI_TFCR_TIEN;
+
+ writel(tcr, esai->base + ESAI_TCR);
+ writel(tfcr, esai->base + ESAI_TFCR);
+
+ ESAI_DUMP();
+ return 0;
+}
+
+/*
+ * This function is called to initialize the RX port before enable
+ * the rx port.
+ */
+static int imx_esai_hw_rx_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 rcr, rfcr;
+ unsigned int channels;
+
+ rcr = readl(esai->base + ESAI_RCR);
+ rfcr = readl(esai->base + ESAI_RFCR);
+
+ rfcr |= ESAI_RFCR_RFR;
+ writel(rfcr, esai->base + ESAI_RFCR);
+ rfcr &= ~ESAI_RFCR_RFR;
+
+ rfcr &= ESAI_RFCR_RWA_MASK;
+ rcr &= ESAI_RCR_RSWS_MASK;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ rfcr |= ESAI_WORD_LEN_16;
+ rcr |= ESAI_RCR_RSHFD_MSB | ESAI_RCR_RSWS_STL32_WDL16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ rfcr |= ESAI_WORD_LEN_20;
+ rcr |= ESAI_RCR_RSHFD_MSB | ESAI_RCR_RSWS_STL32_WDL20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ rfcr |= ESAI_WORD_LEN_24;
+ rcr |= ESAI_RCR_RSHFD_MSB | ESAI_RCR_RSWS_STL32_WDL24;
+ break;
+ }
+
+ channels = params_channels(params);
+ rfcr &= ESAI_RFCR_RE_MASK;
+ rfcr |= ESAI_RFCR_RE(channels);
+
+ rfcr |= ESAI_RFCR_RFWM(64);
+
+ writel(rcr, esai->base + ESAI_RCR);
+ writel(rfcr, esai->base + ESAI_RFCR);
+
+ ESAI_DUMP();
+ return 0;
+}
+
+/*
+ * This function is called to initialize the TX or RX port,
+ */
+static int imx_esai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ struct imx_pcm_dma_params *dma_data;
+
+ /* Tx/Rx config */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (readl(esai->base + ESAI_TCR) & ESAI_TCR_TE0)
+ return 0;
+
+ dma_data = &esai->dma_params_tx;
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+ return imx_esai_hw_tx_params(substream, params, cpu_dai);
+ } else {
+ if (readl(esai->base + ESAI_RCR) & ESAI_RCR_RE1)
+ return 0;
+
+ dma_data = &esai->dma_params_rx;
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+ return imx_esai_hw_rx_params(substream, params, cpu_dai);
+ }
+}
+
+static void imx_esai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ local_esai->imx_esai_txrx_state &= ~IMX_DAI_ESAI_TX;
+ else
+ local_esai->imx_esai_txrx_state &= ~IMX_DAI_ESAI_RX;
+
+ if (!(local_esai->imx_esai_txrx_state & IMX_DAI_ESAI_TXRX))
+ /* close easi clock */
+ clk_disable(esai->clk);
+}
+
+static int imx_esai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 reg, tfcr = 0, rfcr = 0;
+ int i;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ tfcr = readl(esai->base + ESAI_TFCR);
+ reg = readl(esai->base + ESAI_TCR);
+ } else {
+ rfcr = readl(esai->base + ESAI_RFCR);
+ reg = readl(esai->base + ESAI_RCR);
+ }
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ tfcr |= ESAI_TFCR_TFEN;
+ writel(tfcr, esai->base + ESAI_TFCR);
+ /* write initial words to ETDR register */
+ for (i = 0; i < substream->runtime->channels; i++)
+ writel(0x0, esai->base + ESAI_ETDR);
+ reg |= ESAI_TCR_TE(substream->runtime->channels);
+ writel(reg, esai->base + ESAI_TCR);
+ } else {
+ rfcr |= ESAI_RFCR_RFEN;
+ writel(rfcr, esai->base + ESAI_RFCR);
+ reg |= ESAI_RCR_RE(substream->runtime->channels);
+ writel(reg, esai->base + ESAI_RCR);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ reg &= ~ESAI_TCR_TE(substream->runtime->channels);
+ writel(reg, esai->base + ESAI_TCR);
+ tfcr |= ESAI_TFCR_TFR;
+ tfcr &= ~ESAI_TFCR_TFEN;
+ writel(tfcr, esai->base + ESAI_TFCR);
+ tfcr &= ~ESAI_TFCR_TFR;
+ writel(tfcr, esai->base + ESAI_TFCR);
+ } else {
+ reg &= ~ESAI_RCR_RE(substream->runtime->channels);
+ writel(reg, esai->base + ESAI_RCR);
+ rfcr |= ESAI_RFCR_RFR;
+ rfcr &= ~ESAI_RFCR_RFEN;
+ writel(rfcr, esai->base + ESAI_RFCR);
+ rfcr &= ~ESAI_RFCR_RFR;
+ writel(rfcr, esai->base + ESAI_RFCR);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ESAI_DUMP();
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int imx_esai_suspend(struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+
+static int imx_esai_resume(struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+
+#else
+#define imx_esai_suspend NULL
+#define imx_esai_resume NULL
+#endif
+
+
+#define IMX_ESAI_RATES SNDRV_PCM_RATE_8000_192000
+
+#define IMX_ESAI_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops imx_esai_dai_ops = {
+ .startup = imx_esai_startup,
+ .shutdown = imx_esai_shutdown,
+ .trigger = imx_esai_trigger,
+ .hw_params = imx_esai_hw_params,
+ .set_sysclk = imx_esai_set_dai_sysclk,
+ .set_clkdiv = imx_esai_set_dai_clkdiv,
+ .set_fmt = imx_esai_set_dai_fmt,
+ .set_tdm_slot = imx_esai_set_dai_tdm_slot,
+};
+
+static int imx_esai_dai_probe(struct snd_soc_dai *dai)
+{
+ struct imx_esai *esai = dev_get_drvdata(dai->dev);
+ local_esai->imx_esai_txrx_state = 0;
+ snd_soc_dai_set_drvdata(dai, esai);
+ return 0;
+}
+
+static struct snd_soc_dai_driver imx_esai_dai = {
+ .name = "imx-esai.0",
+ .probe = imx_esai_dai_probe,
+ .suspend = imx_esai_suspend,
+ .resume = imx_esai_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 12,
+ .rates = IMX_ESAI_RATES,
+ .formats = IMX_ESAI_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = IMX_ESAI_RATES,
+ .formats = IMX_ESAI_FORMATS,
+ },
+ .ops = &imx_esai_dai_ops,
+};
+
+static int imx_esai_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct imx_esai *esai;
+ struct imx_esai_platform_data *pdata = pdev->dev.platform_data;
+ int ret = 0;
+
+ esai = kzalloc(sizeof(*esai), GFP_KERNEL);
+ if (!esai)
+ return -ENOMEM;
+ local_esai = esai;
+ dev_set_drvdata(&pdev->dev, esai);
+
+ if (pdata)
+ esai->flags = pdata->flags;
+
+ esai->irq = platform_get_irq(pdev, 0);
+
+ esai->clk = clk_get(&pdev->dev, "esai_clk");
+ if (IS_ERR(esai->clk)) {
+ ret = PTR_ERR(esai->clk);
+ dev_err(&pdev->dev, "Cannot get the clock: %d\n",
+ ret);
+ goto failed_clk;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENODEV;
+ goto failed_get_resource;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) {
+ dev_err(&pdev->dev, "request_mem_region failed\n");
+ ret = -EBUSY;
+ goto failed_get_resource;
+ }
+
+ esai->base = ioremap(res->start, resource_size(res));
+ if (!esai->base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENODEV;
+ goto failed_ioremap;
+ }
+
+ esai->dma_params_rx.dma_addr = res->start + ESAI_ERDR;
+ esai->dma_params_tx.dma_addr = res->start + ESAI_ETDR;
+
+ esai->dma_params_tx.peripheral_type = IMX_DMATYPE_ESAI;
+ esai->dma_params_rx.peripheral_type = IMX_DMATYPE_ESAI;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx");
+ if (res)
+ esai->dma_params_tx.dma = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx");
+ if (res)
+ esai->dma_params_rx.dma = res->start;
+
+ platform_set_drvdata(pdev, esai);
+ ret = snd_soc_register_dai(&pdev->dev, &imx_esai_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "register DAI failed\n");
+ goto failed_register;
+ }
+
+ esai->soc_platform_pdev_fiq = \
+ platform_device_alloc("imx-fiq-pcm-audio", pdev->id);
+ if (!esai->soc_platform_pdev_fiq) {
+ ret = -ENOMEM;
+ goto failed_pdev_fiq_alloc;
+ }
+
+ platform_set_drvdata(esai->soc_platform_pdev_fiq, esai);
+ ret = platform_device_add(esai->soc_platform_pdev_fiq);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add platform device\n");
+ goto failed_pdev_fiq_add;
+ }
+
+ esai->soc_platform_pdev = \
+ platform_device_alloc("imx-pcm-audio", pdev->id);
+ if (!esai->soc_platform_pdev) {
+ ret = -ENOMEM;
+ goto failed_pdev_alloc;
+ }
+
+ platform_set_drvdata(esai->soc_platform_pdev, esai);
+ ret = platform_device_add(esai->soc_platform_pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add platform device\n");
+ goto failed_pdev_add;
+ }
+
+ writel(ESAI_ECR_ERST, esai->base + ESAI_ECR);
+ writel(ESAI_ECR_ESAIEN, esai->base + ESAI_ECR);
+
+ return 0;
+
+failed_pdev_add:
+ platform_device_put(esai->soc_platform_pdev);
+failed_pdev_alloc:
+ platform_device_del(esai->soc_platform_pdev_fiq);
+failed_pdev_fiq_add:
+ platform_device_put(esai->soc_platform_pdev_fiq);
+failed_pdev_fiq_alloc:
+ snd_soc_unregister_dai(&pdev->dev);
+failed_register:
+ iounmap(esai->base);
+failed_ioremap:
+ release_mem_region(res->start, resource_size(res));
+failed_get_resource:
+ clk_disable(esai->clk);
+ clk_put(esai->clk);
+failed_clk:
+ kfree(esai);
+
+ return ret;
+}
+static int __devexit imx_esai_remove(struct platform_device *pdev)
+{
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ struct imx_esai *esai = platform_get_drvdata(pdev);
+
+ platform_device_unregister(esai->soc_platform_pdev);
+ platform_device_unregister(esai->soc_platform_pdev_fiq);
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ iounmap(esai->base);
+ release_mem_region(res->start, resource_size(res));
+ clk_put(esai->clk);
+ kfree(esai);
+
+ return 0;
+}
+
+
+static struct platform_driver imx_esai_driver = {
+ .probe = imx_esai_probe,
+ .remove = __devexit_p(imx_esai_remove),
+ .driver = {
+ .name = "imx-esai",
+ },
+};
+
+static int __init imx_esai_init(void)
+{
+ return platform_driver_register(&imx_esai_driver);
+}
+
+static void __exit imx_esai_exit(void)
+{
+ platform_driver_unregister(&imx_esai_driver);
+}
+
+module_init(imx_esai_init);
+module_exit(imx_esai_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX ASoC ESAI driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-esai.h b/sound/soc/imx/imx-esai.h
new file mode 100644
index 00000000..53c899e1
--- /dev/null
+++ b/sound/soc/imx/imx-esai.h
@@ -0,0 +1,334 @@
+/*
+ * imx-esai.h -- ESAI driver header file for Freescale IMX
+ *
+ * Copyright 2008-2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef _MXC_ESAI_H
+#define _MXC_ESAI_H
+
+#ifdef IMX_ESAI_DUMP
+#define ESAI_DUMP() \
+ do {pr_info("dump @ %s\n", __func__); \
+ pr_info("ESAI_ECR 0x%08x\n", readl(esai->base + ESAI_ECR)); \
+ pr_info("ESAI_ESR 0x%08x\n", readl(esai->base + ESAI_ESR)); \
+ pr_info("ESAI_TFCR 0x%08x\n", readl(esai->base + ESAI_TFCR)); \
+ pr_info("ESAI_TFSR 0x%08x\n", readl(esai->base + ESAI_TFSR)); \
+ pr_info("ESAI_RFCR 0x%08x\n", readl(esai->base + ESAI_RFCR)); \
+ pr_info("ESAI_RFSR 0x%08x\n", readl(esai->base + ESAI_RFSR)); \
+ pr_info("ESAI_TSR 0x%08x\n", readl(esai->base + ESAI_TSR)); \
+ pr_info("ESAI_SAISR 0x%08x\n", readl(esai->base + ESAI_SAISR)); \
+ pr_info("ESAI_SAICR 0x%08x\n", readl(esai->base + ESAI_SAICR)); \
+ pr_info("ESAI_TCR 0x%08x\n", readl(esai->base + ESAI_TCR)); \
+ pr_info("ESAI_TCCR 0x%08x\n", readl(esai->base + ESAI_TCCR)); \
+ pr_info("ESAI_RCR 0x%08x\n", readl(esai->base + ESAI_RCR)); \
+ pr_info("ESAI_RCCR 0x%08x\n", readl(esai->base + ESAI_RCCR)); \
+ pr_info("ESAI_TSMA 0x%08x\n", readl(esai->base + ESAI_TSMA)); \
+ pr_info("ESAI_TSMB 0x%08x\n", readl(esai->base + ESAI_TSMB)); \
+ pr_info("ESAI_RSMA 0x%08x\n", readl(esai->base + ESAI_RSMA)); \
+ pr_info("ESAI_RSMB 0x%08x\n", readl(esai->base + ESAI_RSMB)); \
+ pr_info("ESAI_PRRC 0x%08x\n", readl(esai->base + ESAI_PRRC)); \
+ pr_info("ESAI_PCRC 0x%08x\n", readl(esai->base + ESAI_PCRC)); \
+ } while (0);
+#else
+#define ESAI_DUMP()
+#endif
+
+#define ESAI_ETDR 0x00
+#define ESAI_ERDR 0x04
+#define ESAI_ECR 0x08
+#define ESAI_ESR 0x0C
+#define ESAI_TFCR 0x10
+#define ESAI_TFSR 0x14
+#define ESAI_RFCR 0x18
+#define ESAI_RFSR 0x1C
+#define ESAI_TX0 0x80
+#define ESAI_TX1 0x84
+#define ESAI_TX2 0x88
+#define ESAI_TX3 0x8C
+#define ESAI_TX4 0x90
+#define ESAI_TX5 0x94
+#define ESAI_TSR 0x98
+#define ESAI_RX0 0xA0
+#define ESAI_RX1 0xA4
+#define ESAI_RX2 0xA8
+#define ESAI_RX3 0xAC
+#define ESAI_SAISR 0xCC
+#define ESAI_SAICR 0xD0
+#define ESAI_TCR 0xD4
+#define ESAI_TCCR 0xD8
+#define ESAI_RCR 0xDC
+#define ESAI_RCCR 0xE0
+#define ESAI_TSMA 0xE4
+#define ESAI_TSMB 0xE8
+#define ESAI_RSMA 0xEC
+#define ESAI_RSMB 0xF0
+#define ESAI_PRRC 0xF8
+#define ESAI_PCRC 0xFC
+
+#define ESAI_ECR_ETI (1 << 19)
+#define ESAI_ECR_ETO (1 << 18)
+#define ESAI_ECR_ERI (1 << 17)
+#define ESAI_ECR_ERO (1 << 16)
+#define ESAI_ECR_ERST (1 << 1)
+#define ESAI_ECR_ESAIEN (1 << 0)
+
+#define ESAI_ESR_TINIT (1 << 10)
+#define ESAI_ESR_RFF (1 << 9)
+#define ESAI_ESR_TFE (1 << 8)
+#define ESAI_ESR_TLS (1 << 7)
+#define ESAI_ESR_TDE (1 << 6)
+#define ESAI_ESR_TED (1 << 5)
+#define ESAI_ESR_TD (1 << 4)
+#define ESAI_ESR_RLS (1 << 3)
+#define ESAI_ESR_RDE (1 << 2)
+#define ESAI_ESR_RED (1 << 1)
+#define ESAI_ESR_RD (1 << 0)
+
+#define ESAI_TFCR_TIEN (1 << 19)
+#define ESAI_TFCR_TE5 (1 << 7)
+#define ESAI_TFCR_TE4 (1 << 6)
+#define ESAI_TFCR_TE3 (1 << 5)
+#define ESAI_TFCR_TE2 (1 << 4)
+#define ESAI_TFCR_TE1 (1 << 3)
+#define ESAI_TFCR_TE0 (1 << 2)
+#define ESAI_TFCR_TFR (1 << 1)
+#define ESAI_TFCR_TFEN (1 << 0)
+#define ESAI_TFCR_TE(x) ((0x3f >> (6 - ((x + 1) >> 1))) << 2)
+#define ESAI_TFCR_TE_MASK 0xfff03
+#define ESAI_TFCR_TFWM(x) ((x - 1) << 8)
+#define ESAI_TFCR_TWA_MASK 0xf8ffff
+
+#define ESAI_RFCR_REXT (1 << 19)
+#define ESAI_RFCR_RE3 (1 << 5)
+#define ESAI_RFCR_RE2 (1 << 4)
+#define ESAI_RFCR_RE1 (1 << 3)
+#define ESAI_RFCR_RE0 (1 << 2)
+#define ESAI_RFCR_RFR (1 << 1)
+#define ESAI_RFCR_RFEN (1 << 0)
+#define ESAI_RFCR_RE(x) ((0xf >> (4 - ((x + 1) >> 1))) << 2)
+#define ESAI_RFCR_RE_MASK 0xfffc3
+#define ESAI_RFCR_RFWM(x) ((x-1) << 8)
+#define ESAI_RFCR_RWA_MASK 0xf8ffff
+
+#define ESAI_WORD_LEN_32 (0x00 << 16)
+#define ESAI_WORD_LEN_28 (0x01 << 16)
+#define ESAI_WORD_LEN_24 (0x02 << 16)
+#define ESAI_WORD_LEN_20 (0x03 << 16)
+#define ESAI_WORD_LEN_16 (0x04 << 16)
+#define ESAI_WORD_LEN_12 (0x05 << 16)
+#define ESAI_WORD_LEN_8 (0x06 << 16)
+#define ESAI_WORD_LEN_4 (0x07 << 16)
+
+#define ESAI_SAISR_TODFE (1 << 17)
+#define ESAI_SAISR_TEDE (1 << 16)
+#define ESAI_SAISR_TDE (1 << 15)
+#define ESAI_SAISR_TUE (1 << 14)
+#define ESAI_SAISR_TFS (1 << 13)
+#define ESAI_SAISR_RODF (1 << 10)
+#define ESAI_SAISR_REDF (1 << 9)
+#define ESAI_SAISR_RDF (1 << 8)
+#define ESAI_SAISR_ROE (1 << 7)
+#define ESAI_SAISR_RFS (1 << 6)
+#define ESAI_SAISR_IF2 (1 << 2)
+#define ESAI_SAISR_IF1 (1 << 1)
+#define ESAI_SAISR_IF0 (1 << 0)
+
+#define ESAI_SAICR_ALC (1 << 8)
+#define ESAI_SAICR_TEBE (1 << 7)
+#define ESAI_SAICR_SYNC (1 << 6)
+#define ESAI_SAICR_OF2 (1 << 2)
+#define ESAI_SAICR_OF1 (1 << 1)
+#define ESAI_SAICR_OF0 (1 << 0)
+
+#define ESAI_TCR_TLIE (1 << 23)
+#define ESAI_TCR_TIE (1 << 22)
+#define ESAI_TCR_TEDIE (1 << 21)
+#define ESAI_TCR_TEIE (1 << 20)
+#define ESAI_TCR_TPR (1 << 19)
+#define ESAI_TCR_PADC (1 << 17)
+#define ESAI_TCR_TFSR (1 << 16)
+#define ESAI_TCR_TFSL (1 << 15)
+#define ESAI_TCR_TWA (1 << 7)
+#define ESAI_TCR_TSHFD_MSB (0 << 6)
+#define ESAI_TCR_TSHFD_LSB (1 << 6)
+#define ESAI_TCR_TE5 (1 << 5)
+#define ESAI_TCR_TE4 (1 << 4)
+#define ESAI_TCR_TE3 (1 << 3)
+#define ESAI_TCR_TE2 (1 << 2)
+#define ESAI_TCR_TE1 (1 << 1)
+#define ESAI_TCR_TE0 (1 << 0)
+#define ESAI_TCR_TE(x) (0x3f >> (6 - ((x + 1) >> 1)))
+
+#define ESAI_TCR_TSWS_MASK 0xff83ff
+#define ESAI_TCR_TSWS_STL8_WDL8 (0x00 << 10)
+#define ESAI_TCR_TSWS_STL12_WDL8 (0x04 << 10)
+#define ESAI_TCR_TSWS_STL12_WDL12 (0x01 << 10)
+#define ESAI_TCR_TSWS_STL16_WDL8 (0x08 << 10)
+#define ESAI_TCR_TSWS_STL16_WDL12 (0x05 << 10)
+#define ESAI_TCR_TSWS_STL16_WDL16 (0x02 << 10)
+#define ESAI_TCR_TSWS_STL20_WDL8 (0x0c << 10)
+#define ESAI_TCR_TSWS_STL20_WDL12 (0x09 << 10)
+#define ESAI_TCR_TSWS_STL20_WDL16 (0x06 << 10)
+#define ESAI_TCR_TSWS_STL20_WDL20 (0x03 << 10)
+#define ESAI_TCR_TSWS_STL24_WDL8 (0x10 << 10)
+#define ESAI_TCR_TSWS_STL24_WDL12 (0x0d << 10)
+#define ESAI_TCR_TSWS_STL24_WDL16 (0x0a << 10)
+#define ESAI_TCR_TSWS_STL24_WDL20 (0x07 << 10)
+#define ESAI_TCR_TSWS_STL24_WDL24 (0x1e << 10)
+#define ESAI_TCR_TSWS_STL32_WDL8 (0x18 << 10)
+#define ESAI_TCR_TSWS_STL32_WDL12 (0x15 << 10)
+#define ESAI_TCR_TSWS_STL32_WDL16 (0x12 << 10)
+#define ESAI_TCR_TSWS_STL32_WDL20 (0x0f << 10)
+#define ESAI_TCR_TSWS_STL32_WDL24 (0x1f << 10)
+
+#define ESAI_TCR_TMOD_MASK 0xfffcff
+#define ESAI_TCR_TMOD_NORMAL (0x00 << 8)
+#define ESAI_TCR_TMOD_ONDEMAND (0x01 << 8)
+#define ESAI_TCR_TMOD_NETWORK (0x01 << 8)
+#define ESAI_TCR_TMOD_RESERVED (0x02 << 8)
+#define ESAI_TCR_TMOD_AC97 (0x03 << 8)
+
+#define ESAI_TCCR_THCKD (1 << 23)
+#define ESAI_TCCR_TFSD (1 << 22)
+#define ESAI_TCCR_TCKD (1 << 21)
+#define ESAI_TCCR_THCKP (1 << 20)
+#define ESAI_TCCR_TFSP (1 << 19)
+#define ESAI_TCCR_TCKP (1 << 18)
+
+#define ESAI_TCCR_TPSR_MASK 0xfffeff
+#define ESAI_TCCR_TPSR_BYPASS (1 << 8)
+#define ESAI_TCCR_TPSR_DIV8 (0 << 8)
+
+#define ESAI_TCCR_TFP_MASK 0xfc3fff
+#define ESAI_TCCR_TFP(x) ((x & 0xf) << 14)
+
+#define ESAI_TCCR_TDC_MASK 0xffc1ff
+#define ESAI_TCCR_TDC(x) (((x) & 0x1f) << 9)
+
+#define ESAI_TCCR_TPM_MASK 0xffff00
+#define ESAI_TCCR_TPM(x) (x & 0xff)
+
+#define ESAI_RCR_RLIE (1 << 23)
+#define ESAI_RCR_RIE (1 << 22)
+#define ESAI_RCR_REDIE (1 << 21)
+#define ESAI_RCR_REIE (1 << 20)
+#define ESAI_RCR_RPR (1 << 19)
+#define ESAI_RCR_RFSR (1 << 16)
+#define ESAI_RCR_RFSL (1 << 15)
+#define ESAI_RCR_RWA (1 << 7)
+#define ESAI_RCR_RSHFD_MSB (0 << 6)
+#define ESAI_RCR_RSHFD_LSB (1 << 6)
+#define ESAI_RCR_RE3 (1 << 3)
+#define ESAI_RCR_RE2 (1 << 2)
+#define ESAI_RCR_RE1 (1 << 1)
+#define ESAI_RCR_RE0 (1 << 0)
+#define ESAI_RCR_RE(x) (0xf >> (4 - ((x + 1) >> 1)))
+
+#define ESAI_RCR_RSWS_MASK 0xff83ff
+#define ESAI_RCR_RSWS_STL8_WDL8 (0x00 << 10)
+#define ESAI_RCR_RSWS_STL12_WDL8 (0x04 << 10)
+#define ESAI_RCR_RSWS_STL12_WDL12 (0x01 << 10)
+#define ESAI_RCR_RSWS_STL16_WDL8 (0x08 << 10)
+#define ESAI_RCR_RSWS_STL16_WDL12 (0x05 << 10)
+#define ESAI_RCR_RSWS_STL16_WDL16 (0x02 << 10)
+#define ESAI_RCR_RSWS_STL20_WDL8 (0x0c << 10)
+#define ESAI_RCR_RSWS_STL20_WDL12 (0x09 << 10)
+#define ESAI_RCR_RSWS_STL20_WDL16 (0x06 << 10)
+#define ESAI_RCR_RSWS_STL20_WDL20 (0x03 << 10)
+#define ESAI_RCR_RSWS_STL24_WDL8 (0x10 << 10)
+#define ESAI_RCR_RSWS_STL24_WDL12 (0x0d << 10)
+#define ESAI_RCR_RSWS_STL24_WDL16 (0x0a << 10)
+#define ESAI_RCR_RSWS_STL24_WDL20 (0x07 << 10)
+#define ESAI_RCR_RSWS_STL24_WDL24 (0x1e << 10)
+#define ESAI_RCR_RSWS_STL32_WDL8 (0x18 << 10)
+#define ESAI_RCR_RSWS_STL32_WDL12 (0x15 << 10)
+#define ESAI_RCR_RSWS_STL32_WDL16 (0x12 << 10)
+#define ESAI_RCR_RSWS_STL32_WDL20 (0x0f << 10)
+#define ESAI_RCR_RSWS_STL32_WDL24 (0x1f << 10)
+
+#define ESAI_RCR_RMOD_MASK 0xfffcff
+#define ESAI_RCR_RMOD_NORMAL (0x00 << 8)
+#define ESAI_RCR_RMOD_ONDEMAND (0x01 << 8)
+#define ESAI_RCR_RMOD_NETWORK (0x01 << 8)
+#define ESAI_RCR_RMOD_RESERVED (0x02 << 8)
+#define ESAI_RCR_RMOD_AC97 (0x03 << 8)
+
+#define ESAI_RCCR_RHCKD (1 << 23)
+#define ESAI_RCCR_RFSD (1 << 22)
+#define ESAI_RCCR_RCKD (1 << 21)
+#define ESAI_RCCR_RHCKP (1 << 20)
+#define ESAI_RCCR_RFSP (1 << 19)
+#define ESAI_RCCR_RCKP (1 << 18)
+
+#define ESAI_RCCR_RPSR_MASK 0xfffeff
+#define ESAI_RCCR_RPSR_BYPASS (1 << 8)
+#define ESAI_RCCR_RPSR_DIV8 (0 << 8)
+
+#define ESAI_RCCR_RFP_MASK 0xfc3fff
+#define ESAI_RCCR_RFP(x) ((x & 0xf) << 14)
+
+#define ESAI_RCCR_RDC_MASK 0xffc1ff
+#define ESAI_RCCR_RDC(x) (((x) & 0x1f) << 9)
+
+#define ESAI_RCCR_RPM_MASK 0xffff00
+#define ESAI_RCCR_RPM(x) (x & 0xff)
+
+#define ESAI_GPIO_ESAI 0xfff
+
+/* ESAI clock source */
+#define ESAI_CLK_FSYS 0
+#define ESAI_CLK_EXTAL 1
+#define ESAI_CLK_EXTAL_DIV 2
+
+/* ESAI clock divider */
+#define ESAI_TX_DIV_PSR 0
+#define ESAI_TX_DIV_PM 1
+#define ESAI_TX_DIV_FP 2
+#define ESAI_RX_DIV_PSR 3
+#define ESAI_RX_DIV_PM 4
+#define ESAI_RX_DIV_FP 5
+
+#define DRV_NAME "imx-esai"
+
+#include <linux/dmaengine.h>
+#include <mach/dma.h>
+
+#define IMX_DAI_ESAI_TX 0x04
+#define IMX_DAI_ESAI_RX 0x08
+#define IMX_DAI_ESAI_TXRX (IMX_DAI_ESAI_TX | IMX_DAI_ESAI_RX)
+
+struct imx_esai {
+ struct platform_device *ac97_dev;
+ struct snd_soc_dai *imx_ac97;
+ struct clk *clk;
+ void __iomem *base;
+ int irq;
+ int fiq_enable;
+ unsigned int offset;
+
+ unsigned int flags;
+
+ void (*ac97_reset) (struct snd_ac97 *ac97);
+ void (*ac97_warm_reset)(struct snd_ac97 *ac97);
+
+ struct imx_pcm_dma_params dma_params_rx;
+ struct imx_pcm_dma_params dma_params_tx;
+
+ int enabled;
+ int imx_esai_txrx_state;
+
+ struct platform_device *soc_platform_pdev;
+ struct platform_device *soc_platform_pdev_fiq;
+};
+
+#endif
diff --git a/sound/soc/imx/imx-hdmi-dai.c b/sound/soc/imx/imx-hdmi-dai.c
new file mode 100644
index 00000000..0a1686bf
--- /dev/null
+++ b/sound/soc/imx/imx-hdmi-dai.c
@@ -0,0 +1,129 @@
+/*
+ * ALSA SoC HDMI Audio Layer for MXS
+ *
+ * Copyright (C) 2011-2012 Freescale Semiconductor, Inc.
+ *
+ * Based on stmp3xxx_spdif_dai.c
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <mach/hardware.h>
+
+#include <linux/mfd/mxc-hdmi-core.h>
+#include "imx-hdmi.h"
+
+static struct snd_soc_dai_driver imx_hdmi_dai = {
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = MXC_HDMI_RATES_PLAYBACK,
+ .formats = MXC_HDMI_FORMATS_PLAYBACK,
+ },
+};
+
+static int imx_hdmi_dai_probe(struct platform_device *pdev)
+{
+ struct imx_hdmi *hdmi_data;
+ int ret = 0;
+
+ if (!hdmi_get_registered())
+ return -ENOMEM;
+
+ hdmi_data = kzalloc(sizeof(*hdmi_data), GFP_KERNEL);
+ if (!hdmi_data)
+ return -ENOMEM;
+
+ dev_set_drvdata(&pdev->dev, hdmi_data);
+ hdmi_data->pdev = pdev;
+
+ hdmi_data->irq = platform_get_irq(pdev, 0);
+ if (hdmi_data->irq <= 0) {
+ dev_err(&pdev->dev, "MXC hdmi: invalid irq number (%d)\n",
+ hdmi_data->irq);
+ goto e_reg_dai;
+ }
+
+ ret = snd_soc_register_dai(&pdev->dev, &imx_hdmi_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "register DAI failed\n");
+ goto e_reg_dai;
+ }
+
+ hdmi_data->soc_platform_pdev =
+ platform_device_alloc("imx-hdmi-soc-audio", 0);
+
+ if (!hdmi_data->soc_platform_pdev) {
+ dev_err(&pdev->dev, "failed platform_device_alloc\n");
+ goto e_alloc_dai;
+ }
+
+ platform_set_drvdata(hdmi_data->soc_platform_pdev, hdmi_data);
+
+ ret = platform_device_add(hdmi_data->soc_platform_pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add platform device\n");
+ platform_device_put(hdmi_data->soc_platform_pdev);
+ goto e_pdev_add;
+ }
+
+ return 0;
+e_pdev_add:
+ platform_device_put(hdmi_data->soc_platform_pdev);
+e_alloc_dai:
+ snd_soc_unregister_dai(&pdev->dev);
+e_reg_dai:
+ kfree(hdmi_data);
+ return ret;
+}
+
+static int __devexit imx_hdmi_dai_remove(struct platform_device *pdev)
+{
+ struct imx_hdmi *hdmi_data = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_dai(&pdev->dev);
+ kfree(hdmi_data);
+
+ return 0;
+}
+
+static struct platform_driver imx_hdmi_driver = {
+ .probe = imx_hdmi_dai_probe,
+ .remove = __devexit_p(imx_hdmi_dai_remove),
+ .driver = {
+ .name = "imx-hdmi-soc-dai",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init imx_hdmi_dai_init(void)
+{
+ return platform_driver_register(&imx_hdmi_driver);
+}
+
+static void __exit imx_hdmi_dai_exit(void)
+{
+ platform_driver_unregister(&imx_hdmi_driver);
+}
+
+module_init(imx_hdmi_dai_init);
+module_exit(imx_hdmi_dai_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX HDMI TX DAI");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-hdmi-dma.c b/sound/soc/imx/imx-hdmi-dma.c
new file mode 100644
index 00000000..daca010b
--- /dev/null
+++ b/sound/soc/imx/imx-hdmi-dma.c
@@ -0,0 +1,1436 @@
+/*
+ * imx-hdmi-dma.c -- HDMI DMA driver for ALSA Soc Audio Layer
+ *
+ * Copyright (C) 2011-2012 Freescale Semiconductor, Inc.
+ *
+ * based on imx-pcm-dma-mx2.c
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <mach/mxc_hdmi.h>
+#include "imx-hdmi.h"
+
+#include <linux/dmaengine.h>
+#include <mach/dma.h>
+#include <linux/iram_alloc.h>
+#include <linux/io.h>
+#include <asm/mach/map.h>
+#include <mach/hardware.h>
+
+#define HDMI_DMA_BURST_UNSPECIFIED_LEGNTH 0
+#define HDMI_DMA_BURST_INCR4 1
+#define HDMI_DMA_BURST_INCR8 2
+#define HDMI_DMA_BURST_INCR16 3
+
+
+
+#define HDMI_BASE_ADDR 0x00120000
+
+struct hdmi_sdma_script_data {
+ int control_reg_addr;
+ int status_reg_addr;
+ int dma_start_addr;
+ u32 buffer[20];
+};
+
+struct imx_hdmi_sdma_params {
+ u32 buffer_num;
+ dma_addr_t phyaddr;
+};
+
+
+struct imx_hdmi_dma_runtime_data {
+ struct snd_pcm_substream *tx_substream;
+
+ unsigned long buffer_bytes;
+ struct snd_dma_buffer hw_buffer;
+ unsigned long appl_bytes;
+ int period_time;
+
+ int periods;
+ int period_bytes;
+ int dma_period_bytes;
+ int buffer_ratio;
+
+ unsigned long offset;
+
+ int channels;
+ snd_pcm_format_t format;
+ int rate;
+ int sample_align;
+ int sample_bits;
+
+ int frame_idx;
+
+ int irq;
+ struct clk *isfr_clk;
+ struct clk *iahb_clk;
+
+ bool tx_active;
+ spinlock_t irq_lock;
+
+ /*SDMA part*/
+ unsigned int dma_event;
+ struct dma_chan *dma_channel;
+ struct imx_dma_data dma_data;
+ struct hdmi_sdma_script_data *hdmi_sdma_t;
+ dma_addr_t phy_hdmi_sdma_t;
+ struct dma_async_tx_descriptor *desc;
+ struct imx_hdmi_sdma_params sdma_params;
+};
+
+/* bit 0:0:0:b:p(0):c:(u)0:(v)0*/
+/* max 8 channels supported; channels are interleaved*/
+static unsigned char g_packet_head_table[48*8];
+
+void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst,
+ int samples, unsigned char *lookup_table);
+void hdmi_dma_copy_16_neon_fast(unsigned short *src, unsigned int *dst,
+ int samples);
+void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst,
+ int samples, unsigned char *lookup_table);
+void hdmi_dma_copy_24_neon_fast(unsigned int *src, unsigned int *dst,
+ int samples);
+
+hdmi_audio_header_t iec_header;
+
+/*
+ * Note that the period size for DMA != period size for ALSA because the
+ * driver adds iec frame info to the audio samples (in hdmi_dma_copy).
+ *
+ * Each 4 byte subframe = 1 byte of iec data + 3 byte audio sample.
+ *
+ * A 16 bit audio sample becomes 32 bits including the frame info. Ratio=2
+ * A 24 bit audio sample becomes 32 bits including the frame info. Ratio=3:4
+ * If the 24 bit raw audio is in 32 bit words, the
+ *
+ * Original Packed into subframe Ratio of size Format
+ * sample how many size of DMA buffer
+ * (bits) bits to ALSA buffer
+ * -------- ----------- -------- -------------- ------------------------
+ * 16 16 32 2 SNDRV_PCM_FORMAT_S16_LE
+ * 24 24 32 1.33 SNDRV_PCM_FORMAT_S24_3LE*
+ * 24 32 32 1 SNDRV_PCM_FORMAT_S24_LE
+ *
+ * *so SNDRV_PCM_FORMAT_S24_3LE is not supported.
+ */
+
+/*
+ * The minimum dma period is one IEC audio frame (192 * 4 * channels).
+ * The maximum dma period for the HDMI DMA is 8K.
+ *
+ * channels minimum maximum
+ * dma period dma period
+ * -------- ------------------ ----------
+ * 2 192 * 4 * 2 = 1536 * 4 = 6144
+ * 4 192 * 4 * 4 = 3072 * 2 = 6144
+ * 6 192 * 4 * 6 = 4608 * 1 = 4608
+ * 8 192 * 4 * 8 = 6144 * 1 = 6144
+ *
+ * Bottom line:
+ * 1. Must keep the ratio of DMA buffer to ALSA buffer consistent.
+ * 2. frame_idx is saved in the private data, so even if a frame cannot be
+ * transmitted in a period, it can be continued in the next period. This
+ * is necessary for 6 ch.
+ */
+#define HDMI_DMA_PERIOD_BYTES (6144)
+#define HDMI_DMA_BUF_SIZE (64 * 1024)
+#define HDMI_PCM_BUF_SIZE (64 * 1024)
+
+struct imx_hdmi_dma_runtime_data *hdmi_dma_priv;
+
+#ifdef DEBUG
+static void dumpregs(void)
+{
+ pr_debug("\n");
+ pr_debug("HDMI_AHB_DMA_CONF0 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_CONF0));
+ pr_debug("HDMI_AHB_DMA_START 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_START));
+ pr_debug("HDMI_AHB_DMA_STOP 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_STOP));
+ pr_debug("HDMI_AHB_DMA_THRSLD 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_THRSLD));
+ pr_debug("HDMI_AHB_DMA_STRADDR[0-3] 0x%08x\n", hdmi_read4(HDMI_AHB_DMA_STRADDR0));
+ pr_debug("HDMI_AHB_DMA_STPADDR[0-3] 0x%08x\n", hdmi_read4(HDMI_AHB_DMA_STPADDR0));
+ pr_debug("HDMI_AHB_DMA_BSTADDR[0-3] 0x%08x\n", hdmi_read4(HDMI_AHB_DMA_BSTADDR0));
+ pr_debug("HDMI_AHB_DMA_MBLENGTH0 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_MBLENGTH0));
+ pr_debug("HDMI_AHB_DMA_MBLENGTH1 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_MBLENGTH1));
+ pr_debug("HDMI_AHB_DMA_STAT 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_STAT));
+ pr_debug("HDMI_AHB_DMA_INT 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_INT));
+ pr_debug("HDMI_AHB_DMA_MASK 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_MASK));
+ pr_debug("HDMI_AHB_DMA_POL 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_POL));
+ pr_debug("HDMI_AHB_DMA_CONF1 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_CONF1));
+ pr_debug("HDMI_AHB_DMA_BUFFSTAT 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_BUFFSTAT));
+ pr_debug("HDMI_AHB_DMA_BUFFINT 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_BUFFINT));
+ pr_debug("HDMI_AHB_DMA_BUFFMASK 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_BUFFMASK));
+ pr_debug("HDMI_AHB_DMA_BUFFPOL 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_BUFFPOL));
+ pr_debug("HDMI_IH_MUTE_AHBDMAAUD_STAT0 0x%02x\n", hdmi_readb(HDMI_IH_MUTE_AHBDMAAUD_STAT0));
+ pr_debug("HDMI_IH_AHBDMAAUD_STAT0 0x%02x\n", hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0));
+ pr_debug("HDMI_IH_MUTE 0x%02x\n", hdmi_readb(HDMI_IH_MUTE));
+ pr_debug("\n");
+}
+
+static void dumprtd(struct imx_hdmi_dma_runtime_data *rtd)
+{
+ pr_debug("\n");
+ pr_debug("channels = %d\n", rtd->channels);
+ pr_debug("periods = %d\n", rtd->periods);
+ pr_debug("period_bytes = %d\n", rtd->period_bytes);
+ pr_debug("dma period_bytes = %d\n", rtd->dma_period_bytes);
+ pr_debug("buffer_ratio = %d\n", rtd->buffer_ratio);
+ pr_debug("hw dma buffer = 0x%08x\n", (int)rtd->hw_buffer.addr);
+ pr_debug("dma buf size = %d\n", (int)rtd->buffer_bytes);
+ pr_debug("sample_rate = %d\n", (int)rtd->rate);
+}
+#else
+static void dumpregs(void)
+{
+}
+
+static void dumprtd(struct imx_hdmi_dma_runtime_data *rtd)
+{
+}
+#endif
+
+static void hdmi_dma_start(void)
+{
+ hdmi_mask_writeb(1, HDMI_AHB_DMA_START,
+ HDMI_AHB_DMA_START_START_OFFSET,
+ HDMI_AHB_DMA_START_START_MASK);
+}
+
+static void hdmi_dma_stop(void)
+{
+ hdmi_mask_writeb(1, HDMI_AHB_DMA_STOP,
+ HDMI_AHB_DMA_STOP_STOP_OFFSET,
+ HDMI_AHB_DMA_STOP_STOP_MASK);
+}
+
+static void hdmi_fifo_reset(void)
+{
+ hdmi_mask_writeb(1, HDMI_AHB_DMA_CONF0,
+ HDMI_AHB_DMA_CONF0_SW_FIFO_RST_OFFSET,
+ HDMI_AHB_DMA_CONF0_SW_FIFO_RST_MASK);
+}
+
+/*
+ * Conditions for DMA to work:
+ * ((final_addr - initial_addr)>>2)+1) < 2k. So max period is 8k.
+ * (inital_addr & 0x3) == 0
+ * (final_addr & 0x3) == 0x3
+ *
+ * The DMA Period should be an integer multiple of the IEC 60958 audio
+ * frame size, which is 768 bytes (192 * 4).
+ */
+static void hdmi_dma_set_addr(int start_addr, int dma_period_bytes)
+{
+ int final_addr = start_addr + dma_period_bytes - 1;
+
+ hdmi_write4(start_addr, HDMI_AHB_DMA_STRADDR0);
+ hdmi_write4(final_addr, HDMI_AHB_DMA_STPADDR0);
+}
+
+static u8 hdmi_dma_get_irq_status(void)
+{
+ return hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0);
+}
+
+static void hdmi_dma_clear_irq_status(u8 status)
+{
+ hdmi_writeb(status, HDMI_IH_AHBDMAAUD_STAT0);
+}
+
+static void hdmi_dma_irq_mask(int mask)
+{
+ u8 regvalue;
+ regvalue = hdmi_readb(HDMI_AHB_DMA_MASK);
+
+ if (mask) {
+ regvalue |= HDMI_AHB_DMA_DONE;
+ hdmi_writeb(regvalue, HDMI_AHB_DMA_MASK);
+ } else {
+ regvalue &= (u8)~HDMI_AHB_DMA_DONE;
+ hdmi_writeb(regvalue, HDMI_AHB_DMA_MASK);
+ }
+}
+
+static void hdmi_mask(int mask)
+{
+ u8 regvalue;
+ regvalue = hdmi_readb(HDMI_AHB_DMA_MASK);
+
+ if (mask) {
+ regvalue |= HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY;
+ hdmi_writeb(regvalue, HDMI_AHB_DMA_MASK);
+ } else {
+ regvalue &= (u8)~(HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY);
+ hdmi_writeb(regvalue, HDMI_AHB_DMA_MASK);
+ }
+}
+
+
+static void hdmi_dma_irq_mute(int mute)
+{
+ if (mute)
+ hdmi_writeb(0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+ else
+ hdmi_writeb(0x00, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+}
+
+int odd_ones(unsigned a)
+{
+ a ^= a >> 8;
+ a ^= a >> 4;
+ a ^= a >> 2;
+ a ^= a >> 1;
+
+ return a & 1;
+}
+
+/* Add frame information for one pcm subframe */
+static u32 hdmi_dma_add_frame_info(struct imx_hdmi_dma_runtime_data *rtd,
+ u32 pcm_data, int subframe_idx)
+{
+ hdmi_audio_dma_data_t subframe;
+
+ subframe.U = 0;
+ iec_header.B.channel = subframe_idx;
+
+ /* fill b (start-of-block) */
+ subframe.B.b = (rtd->frame_idx == 0) ? 1 : 0;
+
+ /* fill c (channel status) */
+ if (rtd->frame_idx < 42)
+ subframe.B.c = (iec_header.U >> rtd->frame_idx) & 0x1;
+ else
+ subframe.B.c = 0;
+
+ subframe.B.p = odd_ones(pcm_data);
+ subframe.B.p ^= subframe.B.c;
+ subframe.B.p ^= subframe.B.u;
+ subframe.B.p ^= subframe.B.v;
+
+ /* fill data */
+ if (rtd->sample_bits == 16)
+ subframe.B.data = pcm_data << 8;
+ else
+ subframe.B.data = pcm_data;
+
+ return subframe.U;
+}
+
+/* Increment the frame index. We save frame_idx in case a frame
+ * spans more than one dma period. */
+static void hdmi_dma_incr_frame_idx(struct imx_hdmi_dma_runtime_data *rtd)
+{
+ rtd->frame_idx++;
+ if (rtd->frame_idx == 192)
+ rtd->frame_idx = 0;
+}
+
+static void init_table(int channels)
+{
+ int i;
+ int ch = 0;
+ unsigned char *p = g_packet_head_table;
+
+ for (i = 0; i < 48; i++) {
+ int b = 0;
+ if (i == 0)
+ b = 1;
+
+ for (ch = 0; ch < channels; ch++) {
+ int c = 0;
+ if (i < 42) {
+ iec_header.B.channel = ch+1;
+ c = (iec_header.U >> i) & 0x1;
+ }
+ /* preset bit p as c */
+ *p++ = (b << 4) | (c << 2) | (c << 3);
+ }
+ }
+}
+
+#if 1
+
+/* C code optimization for IEC head */
+static void hdmi_dma_copy_16_c_lut(unsigned short *src, unsigned int *dst, int samples, unsigned char *lookup_table)
+{
+ int i;
+ unsigned int sample;
+ unsigned int p;
+ unsigned int head;
+
+ for (i = 0; i < samples; i++) {
+ /* get source sample */
+ sample = *src++;
+
+ /* xor every bit */
+ p = sample ^ (sample >> 8);
+ p ^= (p >> 4);
+ p ^= (p >> 2);
+ p ^= (p >> 1);
+ p &= 1; /* only want last bit */
+ p <<= 3; /* bit p */
+
+ /* get packet header */
+ head = *lookup_table++;
+
+ /* fix head */
+ head ^= p;
+
+ /* store */
+ *dst++ = (head << 24) | (sample << 8);
+ }
+}
+
+static void hdmi_dma_copy_16_c_fast(unsigned short *src, unsigned int *dst, int samples)
+{
+ int i;
+ unsigned int sample;
+ unsigned int p;
+
+ for (i = 0; i < samples; i++) {
+ /* get source sample */
+ sample = *src++;
+
+ /* xor every bit */
+ p = sample ^ (sample >> 8);
+ p ^= (p >> 4);
+ p ^= (p >> 2);
+ p ^= (p >> 1);
+ p &= 1; /* only want last bit */
+ p <<= 3; /* bit p */
+
+ /* store */
+ *dst++ = (p << 24) | (sample << 8);
+ }
+}
+
+static void hdmi_dma_copy_16(unsigned short *src, unsigned int *dest, int framecount, int channelcount)
+{
+ /* split input frames into 192-frame each */
+ int count_in_192 = (framecount + 191) / 192;
+ int i;
+
+ for (i = 0; i < count_in_192; i++) {
+ int count;
+ int samples;
+
+ /* handles frame index [0, 48) */
+ count = (framecount < 48) ? framecount : 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_16_c_lut(src, dest, samples, g_packet_head_table);
+ framecount -= count;
+ if (framecount == 0)
+ break;
+
+ src += samples;
+ dest += samples;
+
+ /* handles frame index [48, 192) */
+ count = (framecount < 192 - 48) ? framecount : 192 - 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_16_c_fast(src, dest, samples);
+ framecount -= count;
+ src += samples;
+ dest += samples;
+ }
+}
+
+#else
+
+/* NEON optimization for IEC head*/
+
+/* Convert pcm samples to iec samples suitable for HDMI transfer.
+* PCM sample is 16 bits length.
+* Frame index always starts from 0.
+* Channel count can be 1, 2, 4, 6, or 8
+* Sample count (frame_count * channel_count) is multipliable by 8.
+*/
+static void hdmi_dma_copy_16(u16 *src, u32 *dest, int framecount, int channelcount)
+{
+ /* split input frames into 192-frame each */
+ int count_in_192 = (framecount + 191) / 192;
+ int i;
+
+ for (i = 0; i < count_in_192; i++) {
+ int count;
+ int samples;
+
+ /* handles frame index [0, 48) */
+ count = (framecount < 48) ? framecount : 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_16_neon_lut(src, dest, samples, g_packet_head_table);
+ framecount -= count;
+ if (framecount == 0)
+ break;
+
+ src += samples;
+ dest += samples;
+
+ /* handles frame index [48, 192) */
+ count = (framecount < 192 - 48) ? framecount : 192 - 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_16_neon_fast(src, dest, samples);
+ framecount -= count;
+ src += samples;
+ dest += samples;
+ }
+}
+
+/* Convert pcm samples to iec samples suitable for HDMI transfer.
+* PCM sample is 24 bits length.
+* Frame index always starts from 0.
+* Channel count can be 1, 2, 4, 6, or 8
+* Sample count (frame_count * channel_count) is multipliable by 8.
+*/
+static void hdmi_dma_copy_24(u32 *src, u32 *dest, int framecount, int channelcount)
+{
+ /* split input frames into 192-frame each */
+ int count_in_192 = (framecount + 191) / 192;
+ int i;
+
+ for (i = 0; i < count_in_192; i++) {
+ int count;
+ int samples;
+
+ /* handles frame index [0, 48) */
+ count = (framecount < 48) ? framecount : 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_24_neon_lut(src, dest, samples, g_packet_head_table);
+ framecount -= count;
+ if (framecount == 0)
+ break;
+
+ src += samples;
+ dest += samples;
+
+ /* handles frame index [48, 192) */
+ count = (framecount < 192 - 48) ? framecount : 192 - 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_24_neon_fast(src, dest, samples);
+ framecount -= count;
+ src += samples;
+ dest += samples;
+ }
+}
+#endif
+
+static void hdmi_dma_mmap_copy(struct snd_pcm_substream *substream,
+ int offset, int count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+ u32 framecount;
+/* u32 *src32; */
+ u32 *dest;
+ u16 *src16;
+
+ framecount = count/(rtd->sample_align * rtd->channels);
+
+ /* hw_buffer is the destination for pcm data plus frame info. */
+ dest = (u32 *)(rtd->hw_buffer.area + (offset * rtd->buffer_ratio));
+
+ switch (rtd->format) {
+
+ case SNDRV_PCM_FORMAT_S16_LE:
+ /* dma_buffer is the mmapped buffer we are copying pcm from. */
+ src16 = (u16 *)(runtime->dma_area + offset);
+ hdmi_dma_copy_16(src16, dest, framecount, rtd->channels);
+ break;
+
+/* 24bit not support now. */
+/*
+ case SNDRV_PCM_FORMAT_S24_LE:
+ src32 = (u32 *)(runtime->dma_area + offset);
+ hdmi_dma_copy_24(src32, dest, framecount, rtd->channels);
+ break;
+*/
+ default:
+ pr_err("%s HDMI Audio invalid sample format (%d)\n",
+ __func__, rtd->format);
+ return;
+ }
+}
+
+static void hdmi_sdma_isr(void *data)
+{
+ struct imx_hdmi_dma_runtime_data *rtd = data;
+ struct snd_pcm_substream *substream = rtd->tx_substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long offset, count, appl_bytes;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rtd->irq_lock, flags);
+
+ if (runtime && runtime->dma_area && rtd->tx_active) {
+
+ rtd->offset += rtd->period_bytes;
+ rtd->offset %= rtd->period_bytes * rtd->periods;
+
+ /* For mmap access, need to copy data from dma_buffer
+ * to hw_buffer and add the frame info. */
+ if (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) {
+ appl_bytes = frames_to_bytes(runtime,
+ runtime->status->hw_ptr);
+ appl_bytes += 2 * rtd->period_bytes;
+ offset = appl_bytes % rtd->buffer_bytes;
+ count = rtd->period_bytes;
+ hdmi_dma_mmap_copy(substream, offset, count);
+ }
+ snd_pcm_period_elapsed(substream);
+
+ }
+
+ spin_unlock_irqrestore(&rtd->irq_lock, flags);
+
+ return;
+}
+
+
+static irqreturn_t hdmi_dma_isr(int irq, void *dev_id)
+{
+ struct imx_hdmi_dma_runtime_data *rtd = dev_id;
+ struct snd_pcm_substream *substream = rtd->tx_substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long offset, count, appl_bytes;
+ unsigned long flags;
+ unsigned int status;
+
+ spin_lock_irqsave(&rtd->irq_lock, flags);
+
+ hdmi_dma_irq_mute(1);
+ status = hdmi_dma_get_irq_status();
+ hdmi_dma_clear_irq_status(status);
+
+ if (rtd->tx_active && (status & HDMI_IH_AHBDMAAUD_STAT0_DONE)) {
+ rtd->offset += rtd->period_bytes;
+ rtd->offset %= rtd->period_bytes * rtd->periods;
+
+ /* For mmap access, need to copy data from dma_buffer
+ * to hw_buffer and add the frame info. */
+ if (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) {
+ appl_bytes = frames_to_bytes(runtime,
+ runtime->status->hw_ptr);
+ appl_bytes += 2 * rtd->period_bytes;
+ offset = appl_bytes % rtd->buffer_bytes;
+ count = rtd->period_bytes;
+ hdmi_dma_mmap_copy(substream, offset, count);
+ }
+ snd_pcm_period_elapsed(substream);
+
+ hdmi_dma_set_addr(rtd->hw_buffer.addr +
+ (rtd->offset * rtd->buffer_ratio),
+ rtd->dma_period_bytes);
+ hdmi_dma_start();
+ }
+
+ hdmi_dma_irq_mute(0);
+
+ spin_unlock_irqrestore(&rtd->irq_lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static void hdmi_dma_enable_hlock(int enable)
+{
+ hdmi_mask_writeb(enable, HDMI_AHB_DMA_CONF0,
+ HDMI_AHB_DMA_CONF0_EN_HLOCK_OFFSET,
+ HDMI_AHB_DMA_CONF0_EN_HLOCK_MASK);
+}
+
+static void hdmi_dma_set_incr_type(int incr_type)
+{
+ u8 value = hdmi_readb(HDMI_AHB_DMA_CONF0) &
+ ~(HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK);
+
+ switch (incr_type) {
+ case HDMI_DMA_BURST_UNSPECIFIED_LEGNTH:
+ break;
+ case HDMI_DMA_BURST_INCR4:
+ value |= HDMI_AHB_DMA_CONF0_BURST_MODE;
+ break;
+ case HDMI_DMA_BURST_INCR8:
+ value |= HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR8;
+ break;
+ case HDMI_DMA_BURST_INCR16:
+ value |= HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR16;
+ break;
+ default:
+ pr_err("%s: invalid increment type: %d", __func__, incr_type);
+ BUG();
+ return;
+ }
+
+ hdmi_writeb(value, HDMI_AHB_DMA_CONF0);
+}
+
+static void hdmi_dma_enable_channels(int channels)
+{
+ switch (channels) {
+ case 2:
+ hdmi_writeb(0x03, HDMI_AHB_DMA_CONF1);
+ break;
+ case 4:
+ hdmi_writeb(0x0f, HDMI_AHB_DMA_CONF1);
+ break;
+ case 6:
+ hdmi_writeb(0x3f, HDMI_AHB_DMA_CONF1);
+ break;
+ case 8:
+ hdmi_writeb(0xff, HDMI_AHB_DMA_CONF1);
+ break;
+ default:
+ WARN(1, "%s - invalid audio channels number: %d\n",
+ __func__, channels);
+ break;
+ }
+}
+
+static void hdmi_dma_set_thrsld_incrtype(int channels)
+{
+ int rev = hdmi_readb(HDMI_REVISION_ID);
+
+ switch (rev) {
+ case 0x0a:
+ {
+ switch (channels) {
+ case 2:
+ hdmi_dma_set_incr_type(HDMI_DMA_BURST_INCR4);
+ hdmi_writeb(126, HDMI_AHB_DMA_THRSLD);
+ break;
+ case 4:
+ case 6:
+ case 8:
+ hdmi_dma_set_incr_type(HDMI_DMA_BURST_INCR4);
+ hdmi_writeb(124, HDMI_AHB_DMA_THRSLD);
+ break;
+ default:
+ pr_debug("unsupport channel!\r\n");
+ }
+ }
+ break;
+ case 0x1a:
+ hdmi_writeb(128, HDMI_AHB_DMA_THRSLD);
+ hdmi_dma_set_incr_type(HDMI_DMA_BURST_INCR8);
+ break;
+ default:
+ pr_debug("error:unrecognized hdmi controller!\r\n");
+ break;
+ }
+
+ pr_debug("HDMI_AHB_DMA_THRSLD 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_THRSLD));
+}
+
+static void hdmi_dma_configure_dma(int channels)
+{
+ hdmi_dma_enable_hlock(1);
+ hdmi_dma_set_thrsld_incrtype(channels);
+ hdmi_dma_enable_channels(channels);
+}
+
+
+static void hdmi_dma_init_iec_header(void)
+{
+ iec_header.U = 0;
+
+ iec_header.B.consumer = 0; /* Consumer use */
+ iec_header.B.linear_pcm = 0; /* linear pcm audio */
+ iec_header.B.copyright = 1; /* no copyright */
+ iec_header.B.pre_emphasis = 0; /* 2 channels without pre-emphasis */
+ iec_header.B.mode = 0; /* Mode 0 */
+
+ iec_header.B.category_code = 0;
+
+ iec_header.B.source = 2; /* stereo */
+ iec_header.B.channel = 0;
+
+ iec_header.B.sample_freq = 0x02; /* 48 KHz */
+ iec_header.B.clock_acc = 0; /* Level II */
+
+ iec_header.B.word_length = 0x02; /* 16 bits */
+ iec_header.B.org_sample_freq = 0x0D; /* 48 KHz */
+
+ iec_header.B.cgms_a = 0; /* Copying is permitted without restriction */
+}
+
+static int hdmi_dma_update_iec_header(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+
+ iec_header.B.source = rtd->channels;
+
+ switch (rtd->rate) {
+ case 32000:
+ iec_header.B.sample_freq = 0x03;
+ iec_header.B.org_sample_freq = 0x0C;
+ break;
+ case 44100:
+ iec_header.B.sample_freq = 0x00;
+ iec_header.B.org_sample_freq = 0x0F;
+ break;
+ case 48000:
+ iec_header.B.sample_freq = 0x02;
+ iec_header.B.org_sample_freq = 0x0D;
+ break;
+ case 88200:
+ iec_header.B.sample_freq = 0x08;
+ iec_header.B.org_sample_freq = 0x07;
+ break;
+ case 96000:
+ iec_header.B.sample_freq = 0x0A;
+ iec_header.B.org_sample_freq = 0x05;
+ break;
+ case 176400:
+ iec_header.B.sample_freq = 0x0C;
+ iec_header.B.org_sample_freq = 0x03;
+ break;
+ case 192000:
+ iec_header.B.sample_freq = 0x0E;
+ iec_header.B.org_sample_freq = 0x01;
+ break;
+ default:
+ pr_err("HDMI Audio sample rate error");
+ return -EFAULT;
+ }
+
+ switch (rtd->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iec_header.B.word_length = 0x02;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iec_header.B.word_length = 0x0b;
+ break;
+ default:
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+/*
+ * The HDMI block transmits the audio data without adding any of the audio
+ * frame bits. So we have to copy the raw dma data from the ALSA buffer
+ * to the DMA buffer, adding the frame information.
+ */
+static int hdmi_dma_copy(struct snd_pcm_substream *substream, int channel,
+ snd_pcm_uframes_t pos, void __user *buf,
+ snd_pcm_uframes_t frames)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+ unsigned int count = frames_to_bytes(runtime, frames);
+ unsigned int pos_bytes = frames_to_bytes(runtime, pos);
+ u32 *hw_buf;
+ int subframe_idx;
+ u32 pcm_data;
+
+ /* Copy pcm data from userspace and add frame info.
+ * Destination is hw_buffer. */
+ hw_buf = (u32 *)(rtd->hw_buffer.area + (pos_bytes * rtd->buffer_ratio));
+
+ while (count > 0) {
+ for (subframe_idx = 1 ; subframe_idx <= rtd->channels ; subframe_idx++) {
+
+ if (copy_from_user(&pcm_data, buf, rtd->sample_align))
+ return -EFAULT;
+
+ buf += rtd->sample_align;
+ count -= rtd->sample_align;
+
+ /* Save the header info to the audio dma buffer */
+ *hw_buf++ = hdmi_dma_add_frame_info(rtd, pcm_data, subframe_idx);
+ }
+ hdmi_dma_incr_frame_idx(rtd);
+ }
+
+ return 0;
+}
+
+static bool hdmi_filter(struct dma_chan *chan, void *param)
+{
+
+ if (!imx_dma_is_general_purpose(chan))
+ return false;
+
+ chan->private = param;
+ return true;
+}
+
+
+static int hdmi_init_sdma_buffer(struct imx_hdmi_dma_runtime_data *params)
+{
+ int i;
+ u32 *tmp_addr1, *tmp_addr2;
+
+ if (!params->hdmi_sdma_t) {
+ dev_err(&params->dma_channel->dev->device,
+ "hdmi private addr invalid!!!\n");
+ return -EINVAL;
+ }
+
+ params->hdmi_sdma_t->control_reg_addr =
+ HDMI_BASE_ADDR + HDMI_AHB_DMA_START;
+ params->hdmi_sdma_t->status_reg_addr =
+ HDMI_BASE_ADDR + HDMI_IH_AHBDMAAUD_STAT0;
+ params->hdmi_sdma_t->dma_start_addr =
+ HDMI_BASE_ADDR + HDMI_AHB_DMA_STRADDR0;
+
+ tmp_addr1 = (u32 *)params->hdmi_sdma_t + 3;
+ tmp_addr2 = (u32 *)params->hdmi_sdma_t + 4;
+ for (i = 0; i < params->sdma_params.buffer_num; i++) {
+ *tmp_addr1 = params->hw_buffer.addr +
+ i * params->period_bytes * params->buffer_ratio;
+ *tmp_addr2 = *tmp_addr1 + params->dma_period_bytes - 1;
+ tmp_addr1 += 2;
+ tmp_addr2 += 2;
+ }
+
+ return 0;
+}
+
+static int hdmi_sdma_alloc(struct imx_hdmi_dma_runtime_data *params)
+{
+ dma_cap_mask_t mask;
+
+ params->dma_data.peripheral_type = IMX_DMATYPE_HDMI;
+ params->dma_data.priority = DMA_PRIO_HIGH;
+ params->dma_data.dma_request = MX6Q_DMA_REQ_EXT_DMA_REQ_0;
+ params->dma_data.private = &params->sdma_params;
+
+ /* Try to grab a DMA channel */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ params->dma_channel = dma_request_channel(
+ mask, hdmi_filter, &params->dma_data);
+ if (params->dma_channel == NULL) {
+ dev_err(&params->dma_channel->dev->device,
+ "HDMI:unable to alloc dma_channel channel\n");
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int hdmi_sdma_config(struct imx_hdmi_dma_runtime_data *params)
+{
+ struct dma_slave_config slave_config;
+ int ret;
+
+ slave_config.direction = DMA_TRANS_NONE;
+ ret = dmaengine_slave_config(params->dma_channel, &slave_config);
+ if (ret) {
+ dev_err(&params->dma_channel->dev->device,
+ "%s failed\r\n", __func__);
+ return -EINVAL;
+ }
+
+ params->desc =
+ params->dma_channel->device->device_prep_dma_cyclic(
+ params->dma_channel, 0,
+ 0,
+ 0,
+ DMA_TRANS_NONE);
+ if (!params->desc) {
+ dev_err(&params->dma_channel->dev->device,
+ "cannot prepare slave dma\n");
+ return -EINVAL;
+ }
+
+ params->desc->callback = hdmi_sdma_isr;
+ params->desc->callback_param = (void *)hdmi_dma_priv;
+
+ return 0;
+}
+
+
+
+static int hdmi_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *params = runtime->private_data;
+ if (hdmi_SDMA_check() && (params->dma_channel)) {
+ dma_release_channel(params->dma_channel);
+ params->dma_channel = NULL;
+ }
+ return 0;
+}
+
+static int hdmi_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+ int err;
+
+ rtd->buffer_bytes = params_buffer_bytes(params);
+ rtd->periods = params_periods(params);
+ rtd->period_bytes = params_period_bytes(params);
+ rtd->channels = params_channels(params);
+ rtd->format = params_format(params);
+ rtd->rate = params_rate(params);
+
+ rtd->offset = 0;
+ rtd->period_time = HZ / (params_rate(params) / params_period_size(params));
+
+ switch (rtd->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ rtd->buffer_ratio = 2;
+ rtd->sample_align = 2;
+ rtd->sample_bits = 16;
+ break;
+
+ case SNDRV_PCM_FORMAT_S24_LE: /* 24 bit audio in 32 bit word */
+ rtd->buffer_ratio = 1;
+ rtd->sample_align = 4;
+ rtd->sample_bits = 24;
+ break;
+ default:
+ pr_err("%s HDMI Audio invalid sample format (%d)\n",
+ __func__, rtd->format);
+ return -EINVAL;
+ }
+
+ rtd->dma_period_bytes = rtd->period_bytes * rtd->buffer_ratio;
+ if (hdmi_SDMA_check()) {
+ rtd->sdma_params.buffer_num = rtd->periods;
+ rtd->sdma_params.phyaddr = rtd->phy_hdmi_sdma_t;
+
+ /* Allocate SDMA channel for HDMI */
+ err = hdmi_init_sdma_buffer(rtd);
+ if (err)
+ return err;
+
+ err = hdmi_sdma_alloc(rtd);
+ if (err)
+ return err;
+
+ err = hdmi_sdma_config(rtd);
+ if (err)
+ return err;
+ }
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ hdmi_dma_configure_dma(rtd->channels);
+ hdmi_dma_set_addr(rtd->hw_buffer.addr, rtd->dma_period_bytes);
+
+ dumprtd(rtd);
+
+ hdmi_dma_update_iec_header(substream);
+
+ /* Init par for mmap optimizate */
+ init_table(rtd->channels);
+
+ rtd->appl_bytes = 0;
+
+ return 0;
+}
+
+static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+ unsigned long offset, count, space_to_end, appl_bytes;
+ unsigned int status;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!check_hdmi_state())
+ return 0;
+ rtd->frame_idx = 0;
+ if (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) {
+ appl_bytes = frames_to_bytes(runtime,
+ runtime->status->hw_ptr);
+ offset = appl_bytes % rtd->buffer_bytes;
+ count = rtd->buffer_bytes;
+ space_to_end = rtd->buffer_bytes - offset;
+
+ if (count <= space_to_end) {
+ hdmi_dma_mmap_copy(substream, offset, count);
+ } else {
+ hdmi_dma_mmap_copy(substream,
+ offset, space_to_end);
+ hdmi_dma_mmap_copy(substream,
+ 0, count - space_to_end);
+ }
+ }
+ dumpregs();
+
+ hdmi_fifo_reset();
+ udelay(1);
+
+ status = hdmi_dma_get_irq_status();
+ hdmi_dma_clear_irq_status(status);
+
+ hdmi_dma_priv->tx_active = true;
+ hdmi_dma_start();
+ hdmi_dma_irq_mask(0);
+ hdmi_set_dma_mode(1);
+ if (hdmi_SDMA_check())
+ dmaengine_submit(rtd->desc);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ hdmi_dma_priv->tx_active = false;
+ hdmi_dma_stop();
+ hdmi_set_dma_mode(0);
+ hdmi_dma_irq_mask(1);
+ if (hdmi_SDMA_check())
+ dmaengine_terminate_all(rtd->dma_channel);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t hdmi_dma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+
+ return bytes_to_frames(substream->runtime, rtd->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = MXC_HDMI_FORMATS_PLAYBACK,
+ .rate_min = 32000,
+ .channels_min = 2,
+ .channels_max = 8,
+ .buffer_bytes_max = HDMI_PCM_BUF_SIZE,
+ .period_bytes_min = HDMI_DMA_PERIOD_BYTES / 2,
+ .period_bytes_max = HDMI_DMA_PERIOD_BYTES / 2,
+ .periods_min = 8,
+ .periods_max = 8,
+ .fifo_size = 0,
+};
+
+static void hdmi_dma_irq_enable(struct imx_hdmi_dma_runtime_data *rtd)
+{
+ unsigned long flags;
+
+ hdmi_writeb(0xff, HDMI_AHB_DMA_POL);
+ hdmi_writeb(0xff, HDMI_AHB_DMA_BUFFPOL);
+
+ spin_lock_irqsave(&hdmi_dma_priv->irq_lock, flags);
+
+ hdmi_dma_clear_irq_status(0xff);
+ if (hdmi_SDMA_check())
+ hdmi_dma_irq_mute(1);
+ else
+ hdmi_dma_irq_mute(0);
+ hdmi_dma_irq_mask(0);
+
+ hdmi_mask(0);
+
+ spin_unlock_irqrestore(&hdmi_dma_priv->irq_lock, flags);
+
+}
+
+static void hdmi_dma_irq_disable(struct imx_hdmi_dma_runtime_data *rtd)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&rtd->irq_lock, flags);
+
+ hdmi_dma_irq_mask(1);
+ hdmi_dma_irq_mute(1);
+ hdmi_dma_clear_irq_status(0xff);
+
+ hdmi_mask(1);
+
+ spin_unlock_irqrestore(&rtd->irq_lock, flags);
+}
+
+static int hdmi_dma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ runtime->private_data = hdmi_dma_priv;
+
+ clk_enable(hdmi_dma_priv->isfr_clk);
+ clk_enable(hdmi_dma_priv->iahb_clk);
+
+ pr_debug("%s hdmi clks: isfr:%d iahb:%d\n", __func__,
+ (int)clk_get_rate(hdmi_dma_priv->isfr_clk),
+ (int)clk_get_rate(hdmi_dma_priv->iahb_clk));
+
+ ret = mxc_hdmi_register_audio(substream);
+ if (ret < 0) {
+ pr_err("ERROR: HDMI is not ready!\n");
+ return ret;
+ }
+
+ hdmi_fifo_reset();
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+ hdmi_dma_irq_enable(hdmi_dma_priv);
+
+ return 0;
+}
+
+static int hdmi_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+
+ hdmi_dma_irq_disable(rtd);
+ mxc_hdmi_unregister_audio(substream);
+
+ clk_disable(rtd->iahb_clk);
+ clk_disable(rtd->isfr_clk);
+
+ return 0;
+}
+
+static struct snd_pcm_ops imx_hdmi_dma_pcm_ops = {
+ .open = hdmi_dma_open,
+ .close = hdmi_dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = hdmi_dma_hw_params,
+ .hw_free = hdmi_dma_hw_free,
+ .trigger = hdmi_dma_trigger,
+ .pointer = hdmi_dma_pointer,
+ .copy = hdmi_dma_copy,
+};
+
+static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ struct snd_dma_buffer *hw_buffer = &hdmi_dma_priv->hw_buffer;
+
+ /* The 'dma_buffer' is the buffer alsa knows about.
+ * It contains only raw audio. */
+ buf->area = dma_alloc_writethrough(pcm->card->dev,
+ HDMI_PCM_BUF_SIZE,
+ &buf->addr, GFP_KERNEL);
+
+ buf->bytes = HDMI_PCM_BUF_SIZE;
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+
+ hdmi_dma_priv->tx_substream = substream;
+
+ /* For mmap access, isr will copy from
+ * the dma_buffer to the hw_buffer */
+ hw_buffer->area = dma_alloc_writethrough(pcm->card->dev,
+ HDMI_DMA_BUF_SIZE,
+ &hw_buffer->addr, GFP_KERNEL);
+ if (!hw_buffer->area)
+ return -ENOMEM;
+
+ hw_buffer->bytes = HDMI_DMA_BUF_SIZE;
+
+ return 0;
+}
+
+static u64 hdmi_dmamask = DMA_BIT_MASK(32);
+
+static int imx_hdmi_dma_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &hdmi_dmamask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ ret = imx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+out:
+ return ret;
+}
+
+static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ /* free each dma_buffer */
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+
+ /* free the hw_buffer */
+ buf = &hdmi_dma_priv->hw_buffer;
+ if (buf->area) {
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static struct snd_soc_platform_driver imx_soc_platform_mx2 = {
+ .ops = &imx_hdmi_dma_pcm_ops,
+ .pcm_new = imx_hdmi_dma_pcm_new,
+ .pcm_free = imx_hdmi_dma_pcm_free,
+};
+
+static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
+{
+ struct imx_hdmi *hdmi_drvdata = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ hdmi_dma_priv = kzalloc(sizeof(*hdmi_dma_priv), GFP_KERNEL);
+ if (hdmi_dma_priv == NULL)
+ return -ENOMEM;
+
+ if (hdmi_SDMA_check()) {
+ /*To alloc a buffer non cacheable for hdmi script use*/
+ hdmi_dma_priv->hdmi_sdma_t =
+ dma_alloc_noncacheable(NULL,
+ sizeof(struct hdmi_sdma_script_data),
+ &hdmi_dma_priv->phy_hdmi_sdma_t,
+ GFP_KERNEL);
+ if (hdmi_dma_priv->hdmi_sdma_t == NULL)
+ return -ENOMEM;
+ }
+
+ hdmi_dma_priv->tx_active = false;
+ spin_lock_init(&hdmi_dma_priv->irq_lock);
+ hdmi_dma_priv->irq = hdmi_drvdata->irq;
+
+ hdmi_dma_init_iec_header();
+
+ hdmi_dma_priv->isfr_clk = clk_get(&pdev->dev, "hdmi_isfr_clk");
+ if (IS_ERR(hdmi_dma_priv->isfr_clk)) {
+ ret = PTR_ERR(hdmi_dma_priv->isfr_clk);
+ dev_err(&pdev->dev, "Unable to get HDMI isfr clk: %d\n", ret);
+ goto e_clk_get1;
+ }
+
+ hdmi_dma_priv->iahb_clk = clk_get(&pdev->dev, "hdmi_iahb_clk");
+ if (IS_ERR(hdmi_dma_priv->iahb_clk)) {
+ ret = PTR_ERR(hdmi_dma_priv->iahb_clk);
+ dev_err(&pdev->dev, "Unable to get HDMI ahb clk: %d\n", ret);
+ goto e_clk_get2;
+ }
+ if (!hdmi_SDMA_check()) {
+ if (request_irq(hdmi_dma_priv->irq, hdmi_dma_isr, IRQF_SHARED,
+ "hdmi dma", hdmi_dma_priv)) {
+ dev_err(&pdev->dev,
+ "MXC hdmi: failed to request irq %d\n",
+ hdmi_dma_priv->irq);
+ ret = -EBUSY;
+ goto e_irq;
+ }
+ }
+
+ ret = snd_soc_register_platform(&pdev->dev, &imx_soc_platform_mx2);
+ if (ret)
+ goto e_irq;
+
+ return 0;
+
+e_irq:
+ clk_put(hdmi_dma_priv->iahb_clk);
+e_clk_get2:
+ clk_put(hdmi_dma_priv->isfr_clk);
+e_clk_get1:
+ kfree(hdmi_dma_priv);
+
+ return ret;
+}
+
+static int __devexit imx_soc_platform_remove(struct platform_device *pdev)
+{
+ free_irq(hdmi_dma_priv->irq, hdmi_dma_priv);
+ if (hdmi_SDMA_check()) {
+ dma_free_coherent(NULL,
+ sizeof(struct hdmi_sdma_script_data),
+ hdmi_dma_priv->hdmi_sdma_t,
+ hdmi_dma_priv->phy_hdmi_sdma_t);
+ }
+ snd_soc_unregister_platform(&pdev->dev);
+ kfree(hdmi_dma_priv);
+ return 0;
+}
+
+static struct platform_driver imx_hdmi_dma_driver = {
+ .driver = {
+ .name = "imx-hdmi-soc-audio",
+ .owner = THIS_MODULE,
+ },
+ .probe = imx_soc_platform_probe,
+ .remove = __devexit_p(imx_soc_platform_remove),
+};
+
+static int __init hdmi_dma_init(void)
+{
+ return platform_driver_register(&imx_hdmi_dma_driver);
+}
+module_init(hdmi_dma_init);
+
+static void __exit hdmi_dma_exit(void)
+{
+ platform_driver_unregister(&imx_hdmi_dma_driver);
+}
+module_exit(hdmi_dma_exit);
diff --git a/sound/soc/imx/imx-hdmi.c b/sound/soc/imx/imx-hdmi.c
new file mode 100644
index 00000000..a30d478a
--- /dev/null
+++ b/sound/soc/imx/imx-hdmi.c
@@ -0,0 +1,90 @@
+/*
+ * ASoC HDMI Transmitter driver for IMX development boards
+ *
+ * Copyright (C) 2011-2012 Freescale Semiconductor, Inc.
+ *
+ * based on stmp3780_devb_spdif.c
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+
+#include <mach/mxc_hdmi.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include "imx-hdmi.h"
+
+/* imx digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link imx_hdmi_dai_link = {
+ .name = "IMX HDMI TX",
+ .stream_name = "IMX HDMI TX",
+ .codec_dai_name = "mxc-hdmi-soc",
+ .codec_name = "mxc_hdmi_soc.0",
+ .cpu_dai_name = "imx-hdmi-soc-dai.0",
+ .platform_name = "imx-hdmi-soc-audio.0",
+};
+
+static struct snd_soc_card snd_soc_card_imx_hdmi = {
+ .name = "imx-hdmi-soc",
+ .dai_link = &imx_hdmi_dai_link,
+ .num_links = 1,
+};
+
+static struct platform_device *imx_hdmi_snd_device;
+
+static int __init imx_hdmi_init(void)
+{
+ int ret = 0;
+
+ if (!hdmi_get_registered())
+ return -ENOMEM;
+
+ imx_hdmi_snd_device = platform_device_alloc("soc-audio", 4);
+ if (!imx_hdmi_snd_device) {
+ pr_err("%s - failed platform_device_alloc\n", __func__);
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(imx_hdmi_snd_device, &snd_soc_card_imx_hdmi);
+
+ ret = platform_device_add(imx_hdmi_snd_device);
+ if (ret) {
+ pr_err("ASoC HDMI TX: Platform device allocation failed\n");
+ platform_device_put(imx_hdmi_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit imx_hdmi_exit(void)
+{
+ platform_device_unregister(imx_hdmi_snd_device);
+}
+
+module_init(imx_hdmi_init);
+module_exit(imx_hdmi_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX HDMI TX ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-hdmi.h b/sound/soc/imx/imx-hdmi.h
new file mode 100644
index 00000000..11737579
--- /dev/null
+++ b/sound/soc/imx/imx-hdmi.h
@@ -0,0 +1,95 @@
+/*
+ * MXC HDMI ALSA Soc Codec Driver
+ *
+ * Copyright (C) 2011-2012 Freescale Semiconductor, Inc.
+ */
+
+/*
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __IMX_HDMI_H
+#define __IMX_HDMI_H
+
+#define DRV_NAME "imx-hdmi"
+
+struct imx_hdmi {
+ int irq;
+ struct platform_device *pdev;
+ struct platform_device *soc_platform_pdev;
+};
+
+#define HDMI_MAX_RATES 7
+#define HDMI_MAX_SAMPLE_SIZE 3
+#define HDMI_MAX_CHANNEL_CONSTRAINTS 4
+
+#define MXC_HDMI_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_176400 | \
+ SNDRV_PCM_RATE_192000)
+
+#define MXC_HDMI_FORMATS_PLAYBACK (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+typedef union {
+ uint64_t U;
+ struct {
+ unsigned consumer:1;
+ unsigned linear_pcm:1;
+ unsigned copyright:1;
+ unsigned pre_emphasis:3;
+ unsigned mode:2;
+
+ unsigned category_code:8;
+
+ unsigned source:4;
+ unsigned channel:4;
+
+ unsigned sample_freq:4;
+ unsigned clock_acc:2;
+ unsigned reserved0:2;
+
+ unsigned word_length:4;
+ unsigned org_sample_freq:4;
+
+ unsigned cgms_a:2;
+ unsigned reserved1:6;
+
+ unsigned reserved2:8;
+
+ unsigned reserved3:8;
+ } B;
+ unsigned char status[8];
+} hdmi_audio_header_t;
+
+typedef union {
+ uint32_t U;
+ struct {
+ unsigned data:24;
+ unsigned v:1;
+ unsigned u:1;
+ unsigned c:1;
+ unsigned p:1;
+ unsigned b:1;
+ unsigned reserved:3;
+ } B;
+} hdmi_audio_dma_data_t;
+
+extern hdmi_audio_header_t iec_header;
+
+#endif /* __IMX_HDMI_H */
diff --git a/sound/soc/imx/imx-pcm-dma-mx2.c b/sound/soc/imx/imx-pcm-dma-mx2.c
new file mode 100644
index 00000000..047a5db6
--- /dev/null
+++ b/sound/soc/imx/imx-pcm-dma-mx2.c
@@ -0,0 +1,543 @@
+/*
+ * imx-pcm-dma-mx2.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+#include <linux/delay.h>
+#include <linux/mxc_asrc.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+
+#include "imx-ssi.h"
+#include "imx-pcm.h"
+
+struct asrc_p2p_ops *asrc_pcm_p2p_ops;
+
+void asrc_p2p_hook(struct asrc_p2p_ops *asrc_p2p_ct)
+{
+ asrc_pcm_p2p_ops = asrc_p2p_ct;
+ return ;
+}
+EXPORT_SYMBOL(asrc_p2p_hook);
+
+static void audio_dma_irq(void *data)
+{
+ struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ iprtd->offset += iprtd->period_bytes;
+ iprtd->offset %= iprtd->period_bytes * iprtd->periods;
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+ struct imx_pcm_runtime_data *iprtd = param;
+
+ if (!imx_dma_is_general_purpose(chan))
+ return false;
+
+ chan->private = &iprtd->dma_data;
+
+ return true;
+}
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_IMX_HAVE_PLATFORM_IMX_ASRC)
+static bool asrc_filter(struct dma_chan *chan, void *param)
+{
+ struct imx_pcm_runtime_data *iprtd = param;
+ if (!imx_dma_is_general_purpose(chan))
+ return false;
+ chan->private = &iprtd->asrc_dma_data;
+ return true;
+}
+static bool asrc_p2p_filter(struct dma_chan *chan, void *param)
+{
+ struct imx_pcm_runtime_data *iprtd = param;
+ if (!imx_dma_is_general_purpose(chan))
+ return false;
+ chan->private = &iprtd->asrc_p2p_dma_data;
+ return true;
+}
+static int imx_ssi_asrc_dma_alloc(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_pcm_dma_params *dma_params;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+ struct dma_slave_config slave_config;
+
+ dma_cap_mask_t mask;
+ enum dma_slave_buswidth buswidth;
+ int ret;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ case SNDRV_PCM_FORMAT_S24_LE:
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ break;
+ default:
+ goto error;
+ }
+
+ dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ /*config m2p dma channel*/
+ iprtd->asrc_dma_data.peripheral_type = IMX_DMATYPE_ASRC;
+ iprtd->asrc_dma_data.priority = DMA_PRIO_HIGH;
+ iprtd->asrc_dma_data.dma_request =
+ iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_get_dma_request(iprtd->asrc_index, 1);
+ iprtd->asrc_dma_chan = dma_request_channel(mask, asrc_filter, iprtd);
+
+ if (!iprtd->asrc_dma_chan)
+ goto error;
+
+ slave_config.direction = DMA_TO_DEVICE;
+ slave_config.dst_addr = iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_per_addr(iprtd->asrc_index, 1);
+ slave_config.dst_addr_width = buswidth;
+ slave_config.dst_maxburst = dma_params->burstsize * buswidth;
+
+ ret = dmaengine_slave_config(iprtd->asrc_dma_chan, &slave_config);
+ if (ret)
+ goto error;
+ /*config p2p dma channel*/
+ iprtd->asrc_p2p_dma_data.peripheral_type = IMX_DMATYPE_ASRC;
+ iprtd->asrc_p2p_dma_data.priority = DMA_PRIO_HIGH;
+ iprtd->asrc_p2p_dma_data.dma_request =
+ iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_get_dma_request(iprtd->asrc_index, 0);
+ iprtd->asrc_p2p_dma_data.dma_request_p2p = dma_params->dma;
+ iprtd->asrc_p2p_dma_chan =
+ dma_request_channel(mask, asrc_p2p_filter, iprtd);
+ if (!iprtd->asrc_p2p_dma_chan)
+ goto error;
+
+ switch (iprtd->p2p->p2p_width) {
+ case ASRC_WIDTH_16_BIT:
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case ASRC_WIDTH_24_BIT:
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ slave_config.direction = DMA_DEV_TO_DEV;
+ slave_config.src_addr = iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_per_addr(iprtd->asrc_index, 0);
+ slave_config.src_addr_width = buswidth;
+ slave_config.src_maxburst = dma_params->burstsize * buswidth;
+ slave_config.dst_addr = dma_params->dma_addr;
+ slave_config.dst_addr_width = buswidth;
+ slave_config.dst_maxburst = dma_params->burstsize * buswidth;
+
+ ret = dmaengine_slave_config(iprtd->asrc_p2p_dma_chan, &slave_config);
+ if (ret)
+ goto error;
+
+ return 0;
+error:
+ if (iprtd->asrc_dma_chan) {
+ dma_release_channel(iprtd->asrc_dma_chan);
+ iprtd->asrc_dma_chan = NULL;
+ }
+ if (iprtd->asrc_p2p_dma_chan) {
+ dma_release_channel(iprtd->asrc_p2p_dma_chan);
+ iprtd->asrc_p2p_dma_chan = NULL;
+ }
+ return -EINVAL;
+}
+#endif
+
+static int imx_ssi_dma_alloc(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_pcm_dma_params *dma_params;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+ struct dma_slave_config slave_config;
+ dma_cap_mask_t mask;
+ enum dma_slave_buswidth buswidth;
+ int ret;
+
+ dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ iprtd->dma_data.peripheral_type = dma_params->peripheral_type;
+ iprtd->dma_data.priority = DMA_PRIO_HIGH;
+ iprtd->dma_data.dma_request = dma_params->dma;
+
+ /* Try to grab a DMA channel */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ iprtd->dma_chan = dma_request_channel(mask, filter, iprtd);
+ if (!iprtd->dma_chan)
+ return -EINVAL;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ case SNDRV_PCM_FORMAT_S24_LE:
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ break;
+ default:
+ return 0;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ slave_config.direction = DMA_TO_DEVICE;
+ slave_config.dst_addr = dma_params->dma_addr;
+ slave_config.dst_addr_width = buswidth;
+ slave_config.dst_maxburst = dma_params->burstsize * buswidth;
+ } else {
+ slave_config.direction = DMA_FROM_DEVICE;
+ slave_config.src_addr = dma_params->dma_addr;
+ slave_config.src_addr_width = buswidth;
+ slave_config.src_maxburst = dma_params->burstsize * buswidth;
+ }
+
+ ret = dmaengine_slave_config(iprtd->dma_chan, &slave_config);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+ unsigned long dma_addr;
+ struct dma_chan *chan;
+ struct imx_pcm_dma_params *dma_params;
+ int ret;
+
+ dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ if (iprtd->asrc_enable) {
+ ret = imx_ssi_asrc_dma_alloc(substream, params);
+ if (ret)
+ return ret;
+ chan = iprtd->asrc_p2p_dma_chan;
+ iprtd->asrc_p2p_desc =
+ chan->device->device_prep_dma_cyclic(chan, 0xffff,
+ 64,
+ 64,
+ DMA_DEV_TO_DEV);
+ if (!iprtd->asrc_p2p_desc) {
+ dev_err(&chan->dev->device,
+ "cannot prepare slave dma\n");
+ return -EINVAL;
+ }
+ chan = iprtd->asrc_dma_chan;
+ } else {
+ ret = imx_ssi_dma_alloc(substream, params);
+ if (ret)
+ return ret;
+ chan = iprtd->dma_chan;
+ }
+
+ iprtd->size = params_buffer_bytes(params);
+ iprtd->periods = params_periods(params);
+ iprtd->period_bytes = params_period_bytes(params);
+ iprtd->offset = 0;
+ iprtd->period_time = HZ / (params_rate(params) /
+ params_period_size(params));
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ dma_addr = runtime->dma_addr;
+
+ iprtd->buf = (unsigned int *)substream->dma_buffer.area;
+
+ if (iprtd->asrc_enable) {
+ iprtd->asrc_desc =
+ chan->device->device_prep_dma_cyclic(chan, dma_addr,
+ iprtd->period_bytes * iprtd->periods,
+ iprtd->period_bytes,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ DMA_TO_DEVICE : DMA_FROM_DEVICE);
+ if (!iprtd->asrc_desc) {
+ dev_err(&chan->dev->device,
+ "cannot prepare slave dma\n");
+ return -EINVAL;
+ }
+
+ iprtd->asrc_desc->callback = audio_dma_irq;
+ iprtd->asrc_desc->callback_param = substream;
+ } else {
+ iprtd->desc = chan->device->device_prep_dma_cyclic(
+ chan, dma_addr,
+ iprtd->period_bytes * iprtd->periods,
+ iprtd->period_bytes,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ DMA_TO_DEVICE : DMA_FROM_DEVICE);
+ if (!iprtd->desc) {
+ dev_err(&chan->dev->device,
+ "cannot prepare slave dma\n");
+ return -EINVAL;
+ }
+
+ iprtd->desc->callback = audio_dma_irq;
+ iprtd->desc->callback_param = substream;
+ }
+
+ return 0;
+}
+
+static int snd_imx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ if (iprtd->asrc_enable) {
+ if (iprtd->asrc_dma_chan) {
+ dma_release_channel(iprtd->asrc_dma_chan);
+ iprtd->asrc_dma_chan = NULL;
+ }
+ if (iprtd->asrc_p2p_dma_chan) {
+ dma_release_channel(iprtd->asrc_p2p_dma_chan);
+ iprtd->asrc_p2p_dma_chan = NULL;
+ }
+ iprtd->dma_chan = NULL;
+ } else {
+ if (iprtd->dma_chan) {
+ dma_release_channel(iprtd->dma_chan);
+ iprtd->dma_chan = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_pcm_dma_params *dma_params;
+
+ dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ return 0;
+}
+
+static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (iprtd->asrc_enable) {
+ dmaengine_submit(iprtd->asrc_p2p_desc);
+ dmaengine_submit(iprtd->asrc_desc);
+ iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_start_conv(iprtd->asrc_index);
+ mdelay(1);
+ } else {
+ dmaengine_submit(iprtd->desc);
+ }
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (iprtd->asrc_enable) {
+ dmaengine_terminate_all(iprtd->asrc_dma_chan);
+ dmaengine_terminate_all(iprtd->asrc_p2p_dma_chan);
+ iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_stop_conv(iprtd->asrc_index);
+ } else {
+ dmaengine_terminate_all(iprtd->dma_chan);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ pr_debug("%s: %ld %ld\n", __func__, iprtd->offset,
+ bytes_to_frames(substream->runtime, iprtd->offset));
+
+ return bytes_to_frames(substream->runtime, iprtd->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rate_min = 8000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = 65535, /* Limited by SDMA engine */
+ .periods_min = 2,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static int snd_imx_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_pcm_runtime_data *iprtd;
+ int ret;
+
+ iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
+ if (iprtd == NULL)
+ return -ENOMEM;
+ if (!strcmp(rtd->dai_link->name, "HiFi_ASRC")) {
+ iprtd->asrc_enable = true;
+ iprtd->p2p =
+ (struct asrc_p2p_params *)snd_soc_pcm_get_drvdata(rtd);
+ iprtd->asrc_index = -1;
+ if (!asrc_pcm_p2p_ops) {
+ pr_err("ASRC is not loaded!\n");
+ return -EINVAL;
+ }
+ iprtd->asrc_pcm_p2p_ops_ko = asrc_pcm_p2p_ops;
+ }
+
+ runtime->private_data = iprtd;
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ kfree(iprtd);
+ return ret;
+ }
+
+ if (!strncmp(rtd->cpu_dai->name, "imx-ssi", strlen("imx-ssi")))
+ snd_imx_hardware.buffer_bytes_max = IMX_SSI_DMABUF_SIZE;
+ else if (!strncmp(rtd->cpu_dai->name, "imx-esai", strlen("imx-esai")))
+ snd_imx_hardware.buffer_bytes_max = IMX_ESAI_DMABUF_SIZE;
+ else if (!strncmp(rtd->cpu_dai->name, "imx-spdif", strlen("imx-spdif")))
+ snd_imx_hardware.buffer_bytes_max = IMX_SPDIF_DMABUF_SIZE;
+ else
+ snd_imx_hardware.buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE;
+
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+ return 0;
+}
+
+static int snd_imx_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ kfree(iprtd);
+
+ return 0;
+}
+
+static struct snd_pcm_ops imx_pcm_ops = {
+ .open = snd_imx_open,
+ .close = snd_imx_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_imx_pcm_hw_params,
+ .hw_free = snd_imx_pcm_hw_free,
+ .prepare = snd_imx_pcm_prepare,
+ .trigger = snd_imx_pcm_trigger,
+ .pointer = snd_imx_pcm_pointer,
+ .mmap = snd_imx_pcm_mmap,
+};
+
+static struct snd_soc_platform_driver imx_soc_platform_mx2 = {
+ .ops = &imx_pcm_ops,
+ .pcm_new = imx_pcm_new,
+ .pcm_free = imx_pcm_free,
+};
+
+static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
+{
+ struct imx_ssi *ssi = platform_get_drvdata(pdev);
+
+ if (ssi->dma_params_tx.burstsize == 0
+ && ssi->dma_params_rx.burstsize == 0) {
+ ssi->dma_params_tx.burstsize = 6;
+ ssi->dma_params_rx.burstsize = 4;
+ }
+
+ return snd_soc_register_platform(&pdev->dev, &imx_soc_platform_mx2);
+}
+
+static int __devexit imx_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver imx_pcm_driver = {
+ .driver = {
+ .name = "imx-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+ .probe = imx_soc_platform_probe,
+ .remove = __devexit_p(imx_soc_platform_remove),
+};
+
+static int __init snd_imx_pcm_init(void)
+{
+ return platform_driver_register(&imx_pcm_driver);
+}
+module_init(snd_imx_pcm_init);
+
+static void __exit snd_imx_pcm_exit(void)
+{
+ platform_driver_unregister(&imx_pcm_driver);
+}
+module_exit(snd_imx_pcm_exit);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-pcm-audio");
diff --git a/sound/soc/imx/imx-pcm-fiq.c b/sound/soc/imx/imx-pcm-fiq.c
new file mode 100644
index 00000000..413b78da
--- /dev/null
+++ b/sound/soc/imx/imx-pcm-fiq.c
@@ -0,0 +1,345 @@
+/*
+ * imx-pcm-fiq.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/fiq.h>
+
+#include <mach/ssi.h>
+
+#include "imx-ssi.h"
+
+struct imx_pcm_runtime_data {
+ int period;
+ int periods;
+ unsigned long offset;
+ unsigned long last_offset;
+ unsigned long size;
+ struct hrtimer hrt;
+ int poll_time_ns;
+ struct snd_pcm_substream *substream;
+ atomic_t running;
+};
+
+static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
+{
+ struct imx_pcm_runtime_data *iprtd =
+ container_of(hrt, struct imx_pcm_runtime_data, hrt);
+ struct snd_pcm_substream *substream = iprtd->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct pt_regs regs;
+ unsigned long delta;
+
+ if (!atomic_read(&iprtd->running))
+ return HRTIMER_NORESTART;
+
+ get_fiq_regs(&regs);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ iprtd->offset = regs.ARM_r8 & 0xffff;
+ else
+ iprtd->offset = regs.ARM_r9 & 0xffff;
+
+ /* How much data have we transferred since the last period report? */
+ if (iprtd->offset >= iprtd->last_offset)
+ delta = iprtd->offset - iprtd->last_offset;
+ else
+ delta = runtime->buffer_size + iprtd->offset
+ - iprtd->last_offset;
+
+ /* If we've transferred at least a period then report it and
+ * reset our poll time */
+ if (delta >= iprtd->period) {
+ snd_pcm_period_elapsed(substream);
+ iprtd->last_offset = iprtd->offset;
+ }
+
+ hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns));
+
+ return HRTIMER_RESTART;
+}
+
+static struct fiq_handler fh = {
+ .name = DRV_NAME,
+};
+
+static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ iprtd->size = params_buffer_bytes(params);
+ iprtd->periods = params_periods(params);
+ iprtd->period = params_period_bytes(params) ;
+ iprtd->offset = 0;
+ iprtd->last_offset = 0;
+ iprtd->poll_time_ns = 1000000000 / params_rate(params) *
+ params_period_size(params);
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ return 0;
+}
+
+static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+ struct pt_regs regs;
+
+ get_fiq_regs(&regs);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16;
+ else
+ regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16;
+
+ set_fiq_regs(&regs);
+
+ return 0;
+}
+
+static int fiq_enable;
+static int imx_pcm_fiq;
+
+static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ atomic_set(&iprtd->running, 1);
+ hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns),
+ HRTIMER_MODE_REL);
+ if (++fiq_enable == 1)
+ enable_fiq(imx_pcm_fiq);
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ atomic_set(&iprtd->running, 0);
+
+ if (--fiq_enable == 0)
+ disable_fiq(imx_pcm_fiq);
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ return bytes_to_frames(substream->runtime, iprtd->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rate_min = 8000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = 16 * 1024,
+ .periods_min = 4,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static int snd_imx_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd;
+ int ret;
+
+ iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
+ if (iprtd == NULL)
+ return -ENOMEM;
+ runtime->private_data = iprtd;
+
+ iprtd->substream = substream;
+
+ atomic_set(&iprtd->running, 0);
+ hrtimer_init(&iprtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ iprtd->hrt.function = snd_hrtimer_callback;
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ kfree(iprtd);
+ return ret;
+ }
+
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+ return 0;
+}
+
+static int snd_imx_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ hrtimer_cancel(&iprtd->hrt);
+
+ kfree(iprtd);
+
+ return 0;
+}
+
+static struct snd_pcm_ops imx_pcm_ops = {
+ .open = snd_imx_open,
+ .close = snd_imx_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_imx_pcm_hw_params,
+ .prepare = snd_imx_pcm_prepare,
+ .trigger = snd_imx_pcm_trigger,
+ .pointer = snd_imx_pcm_pointer,
+ .mmap = snd_imx_pcm_mmap,
+};
+
+static int ssi_irq = 0;
+
+static int imx_pcm_fiq_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret;
+
+ ret = imx_pcm_new(card, dai, pcm);
+ if (ret)
+ return ret;
+
+ if (dai->driver->playback.channels_min) {
+ struct snd_pcm_substream *substream =
+ pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ imx_ssi_fiq_tx_buffer = (unsigned long)buf->area;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ struct snd_pcm_substream *substream =
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ imx_ssi_fiq_rx_buffer = (unsigned long)buf->area;
+ }
+
+ set_fiq_handler(&imx_ssi_fiq_start,
+ &imx_ssi_fiq_end - &imx_ssi_fiq_start);
+
+ return 0;
+}
+
+static void imx_pcm_fiq_free(struct snd_pcm *pcm)
+{
+ mxc_set_irq_fiq(ssi_irq, 0);
+ release_fiq(&fh);
+ imx_pcm_free(pcm);
+}
+
+static struct snd_soc_platform_driver imx_soc_platform_fiq = {
+ .ops = &imx_pcm_ops,
+ .pcm_new = imx_pcm_fiq_new,
+ .pcm_free = imx_pcm_fiq_free,
+};
+
+static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
+{
+ struct imx_ssi *ssi = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = claim_fiq(&fh);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim fiq: %d", ret);
+ return ret;
+ }
+
+ mxc_set_irq_fiq(ssi->irq, 1);
+ ssi_irq = ssi->irq;
+
+ imx_pcm_fiq = ssi->irq;
+
+ imx_ssi_fiq_base = (unsigned long)ssi->base;
+
+ ssi->dma_params_tx.burstsize = 4;
+ ssi->dma_params_rx.burstsize = 6;
+
+ ret = snd_soc_register_platform(&pdev->dev, &imx_soc_platform_fiq);
+ if (ret)
+ goto failed_register;
+
+ return 0;
+
+failed_register:
+ mxc_set_irq_fiq(ssi_irq, 0);
+ release_fiq(&fh);
+
+ return ret;
+}
+
+static int __devexit imx_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver imx_pcm_driver = {
+ .driver = {
+ .name = "imx-fiq-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = imx_soc_platform_probe,
+ .remove = __devexit_p(imx_soc_platform_remove),
+};
+
+static int __init snd_imx_pcm_init(void)
+{
+ return platform_driver_register(&imx_pcm_driver);
+}
+module_init(snd_imx_pcm_init);
+
+static void __exit snd_imx_pcm_exit(void)
+{
+ platform_driver_unregister(&imx_pcm_driver);
+}
+module_exit(snd_imx_pcm_exit);
diff --git a/sound/soc/imx/imx-pcm.h b/sound/soc/imx/imx-pcm.h
new file mode 100644
index 00000000..1b126be7
--- /dev/null
+++ b/sound/soc/imx/imx-pcm.h
@@ -0,0 +1,76 @@
+/*
+ * MXC ALSA Soc Driver
+ *
+ * Copyright (C) 2012 Freescale Semiconductor, Inc.
+ */
+
+/*
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _IMX_PCM_H
+#define _IMX_PCM_H
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+#include <linux/mxc_asrc.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+
+#define IMX_DEFAULT_DMABUF_SIZE (64 * 1024)
+#define IMX_SSI_DMABUF_SIZE (64 * 1024)
+#define IMX_ESAI_DMABUF_SIZE (256 * 1024)
+#define IMX_SPDIF_DMABUF_SIZE (64 * 1024)
+
+struct imx_pcm_runtime_data {
+ int period_bytes;
+ int periods;
+ int dma;
+ unsigned long offset;
+ unsigned long size;
+ void *buf;
+ int period_time;
+ struct dma_async_tx_descriptor *desc;
+ struct dma_chan *dma_chan;
+ struct imx_dma_data dma_data;
+ int asrc_enable;
+ struct asrc_p2p_ops *asrc_pcm_p2p_ops_ko;
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_IMX_HAVE_PLATFORM_IMX_ASRC)
+ enum asrc_pair_index asrc_index;
+ struct dma_async_tx_descriptor *asrc_desc;
+ struct dma_chan *asrc_dma_chan;
+ struct imx_dma_data asrc_dma_data;
+ struct dma_async_tx_descriptor *asrc_p2p_desc;
+ struct dma_chan *asrc_p2p_dma_chan;
+ struct imx_dma_data asrc_p2p_dma_data;
+ struct asrc_p2p_params *p2p;
+#endif
+};
+#endif
diff --git a/sound/soc/imx/imx-sgtl5000.c b/sound/soc/imx/imx-sgtl5000.c
new file mode 100644
index 00000000..9325dc8e
--- /dev/null
+++ b/sound/soc/imx/imx-sgtl5000.c
@@ -0,0 +1,398 @@
+/*
+ * sound/soc/imx/3ds-sgtl5000.c -- SoC audio for i.MX 3ds boards with
+ * sgtl5000 codec
+ *
+ * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/fsl_devices.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
+#include <mach/audmux.h>
+
+#include "../codecs/sgtl5000.h"
+#include "imx-ssi.h"
+
+static struct imx_sgtl5000_priv {
+ int sysclk;
+ int hw;
+ struct platform_device *pdev;
+} card_priv;
+
+static struct snd_soc_jack hs_jack;
+static struct snd_soc_card imx_sgtl5000;
+
+/* Headphones jack detection DAPM pins */
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+/* Headphones jack detection gpios */
+static struct snd_soc_jack_gpio hs_jack_gpios[] = {
+ [0] = {
+ /* gpio is set on per-platform basis */
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 200,
+ },
+};
+
+static int sgtl5000_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ u32 dai_format;
+ int ret;
+ unsigned int channels = params_channels(params);
+
+ snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, card_priv.sysclk, 0);
+
+ snd_soc_dai_set_sysclk(codec_dai, SGTL5000_LRCLK, params_rate(params), 0);
+
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+
+ /* TODO: The SSI driver should figure this out for us */
+ switch (channels) {
+ case 2:
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffc, 0xfffffffc, 2, 0);
+ break;
+ case 1:
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffe, 0xfffffffe, 1, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set cpu DAI configuration */
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_sgtl5000_hifi_ops = {
+ .hw_params = sgtl5000_params,
+};
+
+static int sgtl5000_jack_func;
+static int sgtl5000_spk_func;
+static int sgtl5000_line_in_func;
+
+static const char *jack_function[] = { "off", "on"};
+
+static const char *spk_function[] = { "off", "on" };
+
+static const char *line_in_function[] = { "off", "on" };
+
+static const struct soc_enum sgtl5000_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+ SOC_ENUM_SINGLE_EXT(2, line_in_function),
+};
+
+static int sgtl5000_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = sgtl5000_jack_func;
+ return 0;
+}
+
+static int sgtl5000_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (sgtl5000_jack_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sgtl5000_jack_func = ucontrol->value.enumerated.item[0];
+ if (sgtl5000_jack_func)
+ snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(&codec->dapm, "Headphone Jack");
+
+ snd_soc_dapm_sync(&codec->dapm);
+ return 1;
+}
+
+static int sgtl5000_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = sgtl5000_spk_func;
+ return 0;
+}
+
+static int sgtl5000_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (sgtl5000_spk_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sgtl5000_spk_func = ucontrol->value.enumerated.item[0];
+ if (sgtl5000_spk_func)
+ snd_soc_dapm_enable_pin(&codec->dapm, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk");
+
+ snd_soc_dapm_sync(&codec->dapm);
+ return 1;
+}
+
+static int sgtl5000_get_line_in(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = sgtl5000_line_in_func;
+ return 0;
+}
+
+static int sgtl5000_set_line_in(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (sgtl5000_line_in_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sgtl5000_line_in_func = ucontrol->value.enumerated.item[0];
+ if (sgtl5000_line_in_func)
+ snd_soc_dapm_enable_pin(&codec->dapm, "Line In Jack");
+ else
+ snd_soc_dapm_disable_pin(&codec->dapm, "Line In Jack");
+
+ snd_soc_dapm_sync(&codec->dapm);
+ return 1;
+}
+
+/* imx_3stack card dapm widgets */
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_kcontrol_new sgtl5000_machine_controls[] = {
+ SOC_ENUM_EXT("Jack Function", sgtl5000_enum[0], sgtl5000_get_jack,
+ sgtl5000_set_jack),
+ SOC_ENUM_EXT("Speaker Function", sgtl5000_enum[1], sgtl5000_get_spk,
+ sgtl5000_set_spk),
+ SOC_ENUM_EXT("Line In Function", sgtl5000_enum[1], sgtl5000_get_line_in,
+ sgtl5000_set_line_in),
+};
+
+/* imx_3stack machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* Mic Jack --> MIC_IN (with automatic bias) */
+ {"MIC_IN", NULL, "Mic Jack"},
+
+ /* Line in Jack --> LINE_IN */
+ {"LINE_IN", NULL, "Line In Jack"},
+
+ /* HP_OUT --> Headphone Jack */
+ {"Headphone Jack", NULL, "HP_OUT"},
+
+ /* LINE_OUT --> Ext Speaker */
+ {"Ext Spk", NULL, "LINE_OUT"},
+};
+
+static int imx_3stack_sgtl5000_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ int ret;
+
+ ret = snd_soc_add_controls(codec, sgtl5000_machine_controls,
+ ARRAY_SIZE(sgtl5000_machine_controls));
+ if (ret)
+ return ret;
+
+ /* Add imx_3stack specific widgets */
+ snd_soc_dapm_new_controls(&codec->dapm, imx_3stack_dapm_widgets,
+ ARRAY_SIZE(imx_3stack_dapm_widgets));
+
+ /* Set up imx_3stack specific audio path audio_map */
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_disable_pin(&codec->dapm, "Line In Jack");
+ snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack");
+ snd_soc_dapm_sync(&codec->dapm);
+
+ if (hs_jack_gpios[0].gpio != -1) {
+ /* Jack detection API stuff */
+ ret = snd_soc_jack_new(codec, "Headphone Jack",
+ SND_JACK_HEADPHONE, &hs_jack);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+ hs_jack_pins);
+ if (ret) {
+ printk(KERN_ERR "failed to call snd_soc_jack_add_pins\n");
+ return ret;
+ }
+
+ ret = snd_soc_jack_add_gpios(&hs_jack,
+ ARRAY_SIZE(hs_jack_gpios), hs_jack_gpios);
+ if (ret)
+ printk(KERN_WARNING "failed to call snd_soc_jack_add_gpios\n");
+ }
+
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_sgtl5000_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "sgtl5000",
+ .codec_name = "sgtl5000.1-000a",
+ .cpu_dai_name = "imx-ssi.1",
+ .platform_name = "imx-pcm-audio.1",
+ .init = imx_3stack_sgtl5000_init,
+ .ops = &imx_sgtl5000_hifi_ops,
+ },
+};
+
+static struct snd_soc_card imx_sgtl5000 = {
+ .name = "sgtl5000-audio",
+ .dai_link = imx_sgtl5000_dai,
+ .num_links = ARRAY_SIZE(imx_sgtl5000_dai),
+};
+
+static struct platform_device *imx_sgtl5000_snd_device;
+
+static int imx_audmux_config(int slave, int master)
+{
+ unsigned int ptcr, pdcr;
+ slave = slave - 1;
+ master = master - 1;
+
+ /* SSI0 mastered by port 5 */
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN |
+ MXC_AUDMUX_V2_PTCR_TFSDIR |
+ MXC_AUDMUX_V2_PTCR_TFSEL(master) |
+ MXC_AUDMUX_V2_PTCR_TCLKDIR |
+ MXC_AUDMUX_V2_PTCR_TCSEL(master);
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(master);
+ mxc_audmux_v2_configure_port(slave, ptcr, pdcr);
+
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN;
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(slave);
+ mxc_audmux_v2_configure_port(master, ptcr, pdcr);
+
+ return 0;
+}
+
+static int __devinit imx_sgtl5000_probe(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ int ret = 0;
+
+ card_priv.pdev = pdev;
+
+ imx_audmux_config(plat->src_port, plat->ext_port);
+
+ ret = -EINVAL;
+ if (plat->init && plat->init())
+ return ret;
+
+ card_priv.sysclk = plat->sysclk;
+
+ hs_jack_gpios[0].gpio = plat->hp_gpio;
+ hs_jack_gpios[0].invert = plat->hp_active_low;
+
+ return 0;
+}
+
+static int imx_sgtl5000_remove(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ if (plat->finit)
+ plat->finit();
+
+ return 0;
+}
+
+static struct platform_driver imx_sgtl5000_audio_driver = {
+ .probe = imx_sgtl5000_probe,
+ .remove = imx_sgtl5000_remove,
+ .driver = {
+ .name = "imx-sgtl5000",
+ },
+};
+
+static int __init imx_sgtl5000_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_sgtl5000_audio_driver);
+ if (ret)
+ return -ENOMEM;
+
+ if (machine_is_mx35_3ds() || machine_is_mx6q_sabrelite())
+ imx_sgtl5000_dai[0].codec_name = "sgtl5000.0-000a";
+ else
+ imx_sgtl5000_dai[0].codec_name = "sgtl5000.1-000a";
+
+ imx_sgtl5000_snd_device = platform_device_alloc("soc-audio", 1);
+ if (!imx_sgtl5000_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(imx_sgtl5000_snd_device, &imx_sgtl5000);
+
+ ret = platform_device_add(imx_sgtl5000_snd_device);
+
+ if (ret) {
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+ platform_device_put(imx_sgtl5000_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit imx_sgtl5000_exit(void)
+{
+ platform_driver_unregister(&imx_sgtl5000_audio_driver);
+ platform_device_unregister(imx_sgtl5000_snd_device);
+}
+
+module_init(imx_sgtl5000_init);
+module_exit(imx_sgtl5000_exit);
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("PhyCORE ALSA SoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-si4763.c b/sound/soc/imx/imx-si4763.c
new file mode 100644
index 00000000..7b2458ed
--- /dev/null
+++ b/sound/soc/imx/imx-si4763.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2008-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/fsl_devices.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <mach/audmux.h>
+#include <mach/ssi.h>
+
+#include "imx-ssi.h"
+
+static struct imx_si4763_priv {
+ int sysclk;
+ int hw;
+ int active;
+ struct platform_device *pdev;
+} card_priv;
+
+static int imx_audmux_config(int slave, int master)
+{
+ unsigned int ptcr, pdcr;
+ slave = slave - 1;
+ master = master - 1;
+
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN |
+ MXC_AUDMUX_V2_PTCR_TFSDIR |
+ MXC_AUDMUX_V2_PTCR_TFSEL(slave) |
+ MXC_AUDMUX_V2_PTCR_TCLKDIR |
+ MXC_AUDMUX_V2_PTCR_TCSEL(slave);
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(slave);
+ mxc_audmux_v2_configure_port(master, ptcr, pdcr);
+
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN;
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(master);
+ mxc_audmux_v2_configure_port(slave, ptcr, pdcr);
+
+ return 0;
+}
+
+
+static int imx_3stack_si4763_startup(struct snd_pcm_substream *substream)
+{
+ struct imx_si4763_priv *priv = &card_priv;
+
+ priv->active++;
+ return 0;
+}
+
+static int imx_3stack_si4763_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int channels = params_channels(params);
+ struct imx_ssi *ssi_mode = snd_soc_dai_get_drvdata(cpu_dai);
+ int ret = 0;
+ u32 dai_format;
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ ssi_mode->flags |= IMX_SSI_SYN;
+
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ channels == 1 ? 0xfffffffe : 0xfffffffc,
+ channels == 1 ? 0xfffffffe : 0xfffffffc,
+ 2, 32);
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set the SSI system clock as input (unused) */
+ snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN);
+
+ snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_TX_DIV_PM, 4);
+ snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_TX_DIV_2, 1);
+ snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_TX_DIV_PSR, 0);
+ return 0;
+}
+
+static void imx_3stack_si4763_shutdown(struct snd_pcm_substream *substream)
+{
+ struct imx_si4763_priv *priv = &card_priv;
+ priv->active--;
+}
+
+/*
+ * imx_3stack bt DAI opserations.
+ */
+static struct snd_soc_ops imx_3stack_si4763_ops = {
+ .startup = imx_3stack_si4763_startup,
+ .hw_params = imx_3stack_si4763_hw_params,
+ .shutdown = imx_3stack_si4763_shutdown,
+};
+
+static struct snd_soc_dai_link imx_3stack_dai = {
+ .name = "imx-si4763",
+ .stream_name = "imx-si4763",
+ .codec_dai_name = "si4763",
+ .codec_name = "si4763.0",
+ .cpu_dai_name = "imx-ssi.1",
+ .platform_name = "imx-pcm-audio.1",
+ .ops = &imx_3stack_si4763_ops,
+};
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "imx-audio-si4763",
+ .dai_link = &imx_3stack_dai,
+ .num_links = 1,
+};
+
+static int __devinit imx_3stack_si4763_probe(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ card_priv.pdev = pdev;
+ card_priv.sysclk = plat->sysclk;
+ imx_audmux_config(plat->src_port, plat->ext_port);
+
+ return 0;
+
+}
+
+static int __devexit imx_3stack_si4763_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver imx_3stack_si4763_driver = {
+ .probe = imx_3stack_si4763_probe,
+ .remove = __devexit_p(imx_3stack_si4763_remove),
+ .driver = {
+ .name = "imx-tuner-si4763",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_asoc_init(void)
+{
+ int ret;
+ ret = platform_driver_register(&imx_3stack_si4763_driver);
+ if (ret < 0)
+ goto exit;
+
+ imx_3stack_snd_device = platform_device_alloc("soc-audio", 6);
+ if (!imx_3stack_snd_device)
+ goto err_device_alloc;
+
+ platform_set_drvdata(imx_3stack_snd_device, &snd_soc_card_imx_3stack);
+ ret = platform_device_add(imx_3stack_snd_device);
+ if (0 == ret)
+ goto exit;
+
+
+ platform_device_put(imx_3stack_snd_device);
+err_device_alloc:
+ platform_driver_unregister(&imx_3stack_si4763_driver);
+exit:
+ return ret;
+}
+
+static void __exit imx_3stack_asoc_exit(void)
+{
+ platform_driver_unregister(&imx_3stack_si4763_driver);
+ platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_asoc_init);
+module_exit(imx_3stack_asoc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("ALSA SoC si4763 imx");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-si4763.h b/sound/soc/imx/imx-si4763.h
new file mode 100644
index 00000000..8a641a6a
--- /dev/null
+++ b/sound/soc/imx/imx-si4763.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2008-2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef _MXC_SI4763PCM_H
+#define _MXC_SI4763PCM_H
+
+extern struct snd_soc_dai si4763_dai;
+extern struct snd_soc_codec_device soc_codec_dev_si4763;
+#endif
diff --git a/sound/soc/imx/imx-spdif-dai.c b/sound/soc/imx/imx-spdif-dai.c
new file mode 100644
index 00000000..a3b38658
--- /dev/null
+++ b/sound/soc/imx/imx-spdif-dai.c
@@ -0,0 +1,137 @@
+/*
+ * ALSA SoC SPDIF Audio Layer for MXS
+ *
+ * Copyright (C) 2008-2011 Freescale Semiconductor, Inc.
+ *
+ * Based on stmp3xxx_spdif_dai.c
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <mach/hardware.h>
+
+#include "../codecs/mxc_spdif.h"
+
+#define MXC_SPDIF_TXFIFO_WML 0x8
+#define MXC_SPDIF_RXFIFO_WML 0x8
+
+struct imx_pcm_dma_params dma_params_tx;
+struct imx_pcm_dma_params dma_params_rx;
+
+static int imx_spdif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ /* Tx/Rx config */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dai_set_dma_data(cpu_dai, substream, &dma_params_tx);
+ else
+ snd_soc_dai_set_dma_data(cpu_dai, substream, &dma_params_rx);
+
+ return 0;
+}
+
+struct snd_soc_dai_ops imx_spdif_dai_ops = {
+ .hw_params = imx_spdif_hw_params,
+};
+
+struct snd_soc_dai_driver imx_spdif_dai = {
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXC_SPDIF_RATES_PLAYBACK,
+ .formats = MXC_SPDIF_FORMATS_PLAYBACK,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXC_SPDIF_RATES_CAPTURE,
+ .formats = MXC_SPDIF_FORMATS_CAPTURE,
+ },
+ .ops = &imx_spdif_dai_ops,
+};
+
+static int imx_spdif_dai_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret = 0;
+
+ dma_params_tx.burstsize = MXC_SPDIF_TXFIFO_WML;
+ dma_params_rx.burstsize = MXC_SPDIF_RXFIFO_WML;
+
+ dma_params_tx.peripheral_type = IMX_DMATYPE_SPDIF;
+ dma_params_rx.peripheral_type = IMX_DMATYPE_SPDIF;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx");
+ if (res)
+ dma_params_tx.dma = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx");
+ if (res)
+ dma_params_rx.dma = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx_reg");
+ if (res)
+ dma_params_tx.dma_addr = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx_reg");
+ if (res)
+ dma_params_rx.dma_addr = res->start;
+
+ ret = snd_soc_register_dai(&pdev->dev, &imx_spdif_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "register DAI failed\n");
+ goto failed_register;
+ }
+
+ return 0;
+
+failed_register:
+ return ret;
+}
+
+static int __devexit imx_spdif_dai_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver imx_ssi_driver = {
+ .probe = imx_spdif_dai_probe,
+ .remove = __devexit_p(imx_spdif_dai_remove),
+
+ .driver = {
+ .name = "imx-spdif-dai",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init imx_spdif_dai_init(void)
+{
+ return platform_driver_register(&imx_ssi_driver);
+}
+
+static void __exit imx_spdif_dai_exit(void)
+{
+ platform_driver_unregister(&imx_ssi_driver);
+}
+module_init(imx_spdif_dai_init);
+module_exit(imx_spdif_dai_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX SPDIF DAI");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-spdif.c b/sound/soc/imx/imx-spdif.c
new file mode 100644
index 00000000..13c97aeb
--- /dev/null
+++ b/sound/soc/imx/imx-spdif.c
@@ -0,0 +1,149 @@
+/*
+ * ASoC S/PDIF driver for IMX development boards
+ *
+ * Copyright (C) 2008-2012 Freescale Semiconductor, Inc.
+ *
+ * based on stmp3780_devb_spdif.c
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+
+#include "imx-ssi.h"
+#include "../codecs/mxc_spdif.h"
+
+/* imx digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link imx_spdif_dai_link = {
+ .name = "IMX SPDIF",
+ .stream_name = "IMX SPDIF",
+ .codec_dai_name = "mxc-spdif",
+ .codec_name = "mxc_spdif.0",
+ .cpu_dai_name = "imx-spdif-dai.0",
+ .platform_name = "imx-pcm-audio.2", /* imx-pcm-dma-mx2 */
+};
+
+static struct snd_soc_card snd_soc_card_imx_spdif = {
+ .name = "imx-spdif",
+ .dai_link = &imx_spdif_dai_link,
+ .num_links = 1,
+};
+
+static int __devinit imx_spdif_audio_probe(struct platform_device *pdev)
+{
+ struct imx_ssi *ssi;
+ int ret;
+
+ ssi = kzalloc(sizeof(*ssi), GFP_KERNEL);
+ if (!ssi)
+ return -ENOMEM;
+
+ dev_set_drvdata(&pdev->dev, ssi);
+
+ ssi->soc_platform_pdev = platform_device_alloc("imx-pcm-audio", 2);
+ if (!ssi->soc_platform_pdev) {
+ pr_err("%s failed platform_device_alloc\n", __func__);
+ return -ENOMEM;
+ }
+
+ /* It may seem weird to be using struct imx_ssi here because S/PDIF
+ doesn't use ssi. We only use soc_platform_pdev in struct imx_ssi.
+ But we need it because imx-pcm-dma-mx2 initializes other fields. */
+ platform_set_drvdata(ssi->soc_platform_pdev, ssi);
+
+ ret = platform_device_add(ssi->soc_platform_pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add platform device\n");
+ platform_device_put(ssi->soc_platform_pdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit imx_spdif_audio_remove(struct platform_device *pdev)
+{
+ struct imx_ssi *ssi = platform_get_drvdata(pdev);
+
+ platform_device_unregister(ssi->soc_platform_pdev);
+ snd_soc_unregister_dai(&pdev->dev);
+ kfree(ssi);
+
+ return 0;
+}
+
+static struct platform_driver imx_spdif_audio_driver = {
+ .probe = imx_spdif_audio_probe,
+ .remove = __devexit_p(imx_spdif_audio_remove),
+ .driver = {
+ .name = "imx-spdif-audio-device",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *imx_spdif_snd_device;
+
+static int __init imx_spdif_init(void)
+{
+ int ret = 0;
+
+ ret = platform_driver_register(&imx_spdif_audio_driver);
+ if (ret) {
+ pr_err("%s - failed platform_driver_register\n", __func__);
+ return -ENOMEM;
+ }
+
+ imx_spdif_snd_device = platform_device_alloc("soc-audio", 3);
+ if (!imx_spdif_snd_device) {
+ pr_err("%s - failed platform_device_alloc\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (machine_is_mx6sl_evk()) {
+ imx_spdif_dai_link.name = "HDMI-Audio";
+ imx_spdif_dai_link.stream_name = "HDMI-Audio";
+ }
+
+ platform_set_drvdata(imx_spdif_snd_device, &snd_soc_card_imx_spdif);
+
+ ret = platform_device_add(imx_spdif_snd_device);
+ if (ret) {
+ pr_err("ASoC S/PDIF: Platform device allocation failed\n");
+ platform_device_put(imx_spdif_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit imx_spdif_exit(void)
+{
+ platform_driver_unregister(&imx_spdif_audio_driver);
+ platform_device_unregister(imx_spdif_snd_device);
+}
+
+module_init(imx_spdif_init);
+module_exit(imx_spdif_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX EVK development board ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c
new file mode 100644
index 00000000..d3983078
--- /dev/null
+++ b/sound/soc/imx/imx-ssi.c
@@ -0,0 +1,968 @@
+/*
+ * imx-ssi.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2010-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * The i.MX SSI core has some nasty limitations in AC97 mode. While most
+ * sane processor vendors have a FIFO per AC97 slot, the i.MX has only
+ * one FIFO which combines all valid receive slots. We cannot even select
+ * which slots we want to receive. The WM9712 with which this driver
+ * was developed with always sends GPIO status data in slot 12 which
+ * we receive in our (PCM-) data stream. The only chance we have is to
+ * manually skip this data in the FIQ handler. With sampling rates different
+ * from 48000Hz not every frame has valid receive data, so the ratio
+ * between pcm data and GPIO status data changes. Our FIQ handler is not
+ * able to handle this, hence this driver only works with 48000Hz sampling
+ * rate.
+ * Reading and writing AC97 registers is another challenge. The core
+ * provides us status bits when the read register is updated with *another*
+ * value. When we read the same register two times (and the register still
+ * contains the same value) these status bits are not set. We work
+ * around this by not polling these bits but only wait a fixed delay.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/ssi.h>
+#include <mach/iram.h>
+#include <mach/hardware.h>
+
+#include "imx-ssi.h"
+#include "imx-pcm.h"
+
+#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV)
+#define IMX_SSI_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+#ifdef CONFIG_SND_MXC_SOC_IRAM
+static int UseIRAM;
+#endif
+
+/*
+ * SSI Network Mode or TDM slots configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+ unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+ struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 sccr;
+
+ sccr = readl(ssi->base + SSI_STCCR);
+ sccr &= ~SSI_STCCR_DC_MASK;
+ sccr |= SSI_STCCR_DC(slots - 1);
+ writel(sccr, ssi->base + SSI_STCCR);
+
+ sccr = readl(ssi->base + SSI_SRCCR);
+ sccr &= ~SSI_STCCR_DC_MASK;
+ sccr |= SSI_STCCR_DC(slots - 1);
+ writel(sccr, ssi->base + SSI_SRCCR);
+
+ writel(tx_mask, ssi->base + SSI_STMSK);
+ writel(rx_mask, ssi->base + SSI_SRMSK);
+
+ return 0;
+}
+
+/*
+ * SSI DAI format configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 strcr = 0, scr;
+
+ scr = readl(ssi->base + SSI_SCR) & ~SSI_SCR_SYN;
+
+ /* DAI mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* data on rising edge of bclk, frame low 1clk before data */
+ strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ /* data on rising edge of bclk, frame high with data */
+ strcr |= SSI_STCR_TXBIT0;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ /* data on rising edge of bclk, frame high with data */
+ strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ /* data on rising edge of bclk, frame high 1clk before data */
+ strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0 | SSI_STCR_TEFS;
+ break;
+ }
+
+ /* DAI clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ strcr |= SSI_STCR_TFSI;
+ strcr &= ~SSI_STCR_TSCKP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ strcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ strcr &= ~SSI_STCR_TFSI;
+ strcr |= SSI_STCR_TSCKP;
+ break;
+ }
+
+ /* DAI clock master masks */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ strcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR;
+ if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) {
+ scr &= ~SSI_I2S_MODE_MASK;
+ scr |= SSI_SCR_I2S_MODE_MSTR;
+ }
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ strcr &= ~(SSI_STCR_TFDIR | SSI_STCR_TXDIR);
+ if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) {
+ scr &= ~SSI_I2S_MODE_MASK;
+ scr |= SSI_SCR_I2S_MODE_SLAVE;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ strcr |= SSI_STCR_TFEN0;
+
+ if (ssi->flags & IMX_SSI_SYN)
+ scr |= SSI_SCR_SYN;
+
+ /* Dual-FIFO support */
+ strcr |= SSI_STCR_TFEN1;
+ scr |= SSI_SCR_TCH_EN;
+
+ writel(strcr, ssi->base + SSI_STCR);
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBS_CFS)
+ strcr &= ~(SSI_STCR_TFDIR | SSI_STCR_TXDIR);
+ writel(strcr, ssi->base + SSI_SRCR);
+ writel(scr, ssi->base + SSI_SCR);
+
+ return 0;
+}
+
+/*
+ * SSI system clock configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 scr;
+
+ scr = readl(ssi->base + SSI_SCR);
+
+ switch (clk_id) {
+ case IMX_SSP_SYS_CLK:
+ if (dir == SND_SOC_CLOCK_OUT)
+ scr |= SSI_SCR_SYS_CLK_EN;
+ else
+ scr &= ~SSI_SCR_SYS_CLK_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(scr, ssi->base + SSI_SCR);
+
+ return 0;
+}
+
+/*
+ * SSI Clock dividers
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 stccr, srccr;
+
+ stccr = readl(ssi->base + SSI_STCCR);
+ srccr = readl(ssi->base + SSI_SRCCR);
+
+ switch (div_id) {
+ case IMX_SSI_TX_DIV_2:
+ if (div & (~SSI_STCCR_DIV2))
+ return EINVAL;
+ stccr &= ~SSI_STCCR_DIV2;
+ stccr |= div;
+ break;
+ case IMX_SSI_TX_DIV_PSR:
+ if (div & (~SSI_STCCR_PSR))
+ return EINVAL;
+ stccr &= ~SSI_STCCR_PSR;
+ stccr |= div;
+ break;
+ case IMX_SSI_TX_DIV_PM:
+ if (div & (~SSI_STCCR_PM_MASK))
+ return EINVAL;
+ stccr &= ~SSI_STCCR_PM_MASK;
+ stccr |= SSI_STCCR_PM(div);
+ break;
+ case IMX_SSI_RX_DIV_2:
+ if (div & (~SSI_SRCCR_DIV2))
+ return EINVAL;
+ srccr &= ~SSI_SRCCR_DIV2;
+ srccr |= div;
+ break;
+ case IMX_SSI_RX_DIV_PSR:
+ if (div & (~SSI_SRCCR_PSR))
+ return EINVAL;
+ srccr &= ~SSI_SRCCR_PSR;
+ srccr |= div;
+ break;
+ case IMX_SSI_RX_DIV_PM:
+ if (div & (~SSI_SRCCR_PM_MASK))
+ return EINVAL;
+ srccr &= ~SSI_SRCCR_PM_MASK;
+ srccr |= SSI_STCCR_PM(div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(stccr, ssi->base + SSI_STCCR);
+ writel(srccr, ssi->base + SSI_SRCCR);
+
+ return 0;
+}
+
+/*
+ * Should only be called when port is inactive (i.e. SSIEN = 0),
+ * although can be called multiple times by upper layers.
+ */
+static int imx_ssi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+ struct imx_pcm_dma_params *dma_data;
+ u32 reg, sccr, scr;
+ unsigned int channels = params_channels(params);
+
+ /* Tx/Rx config */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ reg = SSI_STCCR;
+ dma_data = &ssi->dma_params_tx;
+ } else {
+ reg = SSI_SRCCR;
+ dma_data = &ssi->dma_params_rx;
+ }
+
+ if (ssi->flags & IMX_SSI_SYN)
+ reg = SSI_STCCR;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+ sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK;
+
+ /* DAI data (word) size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ sccr |= SSI_SRCCR_WL(16);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ sccr |= SSI_SRCCR_WL(20);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ sccr |= SSI_SRCCR_WL(24);
+ break;
+ }
+
+ writel(sccr, ssi->base + reg);
+
+ scr = readl(ssi->base + SSI_SCR);
+
+ if (channels == 1) {
+ scr &= ~SSI_SCR_NET;
+ scr &= ~SSI_I2S_MODE_MASK;
+ } else
+ scr |= SSI_SCR_NET;
+
+ writel(scr, ssi->base + SSI_SCR);
+ return 0;
+}
+
+static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct imx_ssi *ssi = snd_soc_dai_get_drvdata(dai);
+ unsigned int sier_bits, sier;
+ unsigned int scr;
+
+ scr = readl(ssi->base + SSI_SCR);
+ sier = readl(ssi->base + SSI_SIER);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (ssi->flags & IMX_SSI_DMA)
+ sier_bits = SSI_SIER_TDMAE;
+ else
+ sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN;
+ } else {
+ if (ssi->flags & IMX_SSI_DMA)
+ sier_bits = SSI_SIER_RDMAE;
+ else
+ sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN;
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ scr |= SSI_SCR_TE;
+ else
+ scr |= SSI_SCR_RE;
+ sier |= sier_bits;
+
+ if (++ssi->enabled == 1)
+ scr |= SSI_SCR_SSIEN;
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ scr &= ~SSI_SCR_TE;
+ else
+ scr &= ~SSI_SCR_RE;
+ sier &= ~sier_bits;
+
+ if (--ssi->enabled == 0)
+ scr &= ~SSI_SCR_SSIEN;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!(ssi->flags & IMX_SSI_USE_AC97))
+ /* rx/tx are always enabled to access ac97 registers */
+ writel(scr, ssi->base + SSI_SCR);
+
+ writel(sier, ssi->base + SSI_SIER);
+
+ return 0;
+}
+
+static int imx_ssi_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+
+ if (cpu_dai->playback_active || cpu_dai->capture_active)
+ return 0;
+
+ clk_enable(ssi->clk);
+
+ return 0;
+}
+
+static void imx_ssi_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+
+ /* shutdown SSI if neither Tx or Rx is active */
+ if (cpu_dai->playback_active || cpu_dai->capture_active)
+ return;
+
+ clk_disable(ssi->clk);
+}
+
+static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
+ .hw_params = imx_ssi_hw_params,
+ .set_fmt = imx_ssi_set_dai_fmt,
+ .set_clkdiv = imx_ssi_set_dai_clkdiv,
+ .set_sysclk = imx_ssi_set_dai_sysclk,
+ .set_tdm_slot = imx_ssi_set_dai_tdm_slot,
+ .trigger = imx_ssi_trigger,
+ .startup = imx_ssi_startup,
+ .shutdown = imx_ssi_shutdown,
+};
+
+#ifdef CONFIG_SND_MXC_SOC_IRAM
+
+static struct vm_operations_struct snd_mxc_audio_playback_vm_ops = {
+ .open = snd_pcm_mmap_data_open,
+ .close = snd_pcm_mmap_data_close,
+};
+
+/*
+ enable user space access to iram buffer
+*/
+static int imx_iram_audio_playback_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *area)
+{
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ unsigned long off;
+ unsigned long phys;
+ unsigned long size;
+ int ret = 0;
+
+ area->vm_ops = &snd_mxc_audio_playback_vm_ops;
+ area->vm_private_data = substream;
+
+ off = area->vm_pgoff << PAGE_SHIFT;
+ phys = buf->addr + off;
+ size = area->vm_end - area->vm_start;
+
+ area->vm_page_prot = pgprot_writecombine(area->vm_page_prot);
+ area->vm_flags |= VM_IO;
+ ret =
+ remap_pfn_range(area, area->vm_start, phys >> PAGE_SHIFT,
+ size, area->vm_page_prot);
+
+ return ret;
+}
+#endif
+
+int snd_imx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+#ifdef CONFIG_SND_MXC_SOC_IRAM
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ if ((!strncmp(rtd->cpu_dai->name, "imx-ssi", strlen("imx-ssi")))
+ && (UseIRAM & (1<<substream->stream)))
+ ret = imx_iram_audio_playback_mmap(substream, vma);
+ else
+ ret = dma_mmap_coherent(NULL, vma, runtime->dma_area,
+ runtime->dma_addr, runtime->dma_bytes);
+#else
+ ret = dma_mmap_coherent(NULL, vma, runtime->dma_area,
+ runtime->dma_addr, runtime->dma_bytes);
+#endif
+ pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_imx_pcm_mmap);
+
+static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size;
+#ifdef CONFIG_SND_MXC_SOC_IRAM
+ unsigned long buf_paddr;
+#endif
+
+ if (!strncmp(rtd->cpu_dai->name, "imx-ssi", strlen("imx-ssi")))
+ size = IMX_SSI_DMABUF_SIZE;
+ else if (!strncmp(rtd->cpu_dai->name, "imx-esai", strlen("imx-esai")))
+ size = IMX_ESAI_DMABUF_SIZE;
+ else if (!strncmp(rtd->cpu_dai->name, "imx-spdif", strlen("imx-spdif")))
+ size = IMX_SPDIF_DMABUF_SIZE;
+ else
+ size = IMX_DEFAULT_DMABUF_SIZE;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->bytes = size;
+
+#ifdef CONFIG_SND_MXC_SOC_IRAM
+ if (!strncmp(rtd->cpu_dai->name, "imx-ssi", strlen("imx-ssi"))) {
+ buf->area = iram_alloc(size, &buf_paddr);
+ if (!buf->area) {
+ buf->area =
+ dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+ } else {
+ buf->addr = buf_paddr;
+ UseIRAM |= 1<<substream->stream;
+ }
+ } else {
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ }
+#else
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+#endif
+
+ return 0;
+}
+
+static u64 imx_pcm_dmamask = DMA_BIT_MASK(32);
+
+int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &imx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+ if (dai->driver->playback.channels_min) {
+ ret = imx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = imx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(imx_pcm_new);
+
+void imx_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+#ifdef CONFIG_SND_MXC_SOC_IRAM
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+#endif
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+#ifdef CONFIG_SND_MXC_SOC_IRAM
+ if ((!strncmp(rtd->cpu_dai->name, "imx-ssi", strlen("imx-ssi")))
+ && (UseIRAM & (1<<substream->stream))) {
+ iram_free(buf->addr, IMX_SSI_DMABUF_SIZE);
+ UseIRAM &= ~(1<<substream->stream);
+ } else {
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ }
+#else
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+#endif
+ buf->area = NULL;
+ }
+}
+EXPORT_SYMBOL_GPL(imx_pcm_free);
+
+static int imx_ssi_dai_probe(struct snd_soc_dai *dai)
+{
+ struct imx_ssi *ssi = dev_get_drvdata(dai->dev);
+ uint32_t val;
+
+ snd_soc_dai_set_drvdata(dai, ssi);
+
+ val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.burstsize) |
+ SSI_SFCSR_RFWM0(ssi->dma_params_rx.burstsize) |
+ SSI_SFCSR_TFWM1(ssi->dma_params_tx.burstsize) |
+ SSI_SFCSR_RFWM1(ssi->dma_params_rx.burstsize) ;
+ writel(val, ssi->base + SSI_SFCSR);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int imx_ssi_dai_suspend(struct snd_soc_dai *dai)
+{
+ return 0;
+}
+
+static int imx_ssi_dai_resume(struct snd_soc_dai *dai)
+{
+ return 0;
+}
+#else
+#define imx_ssi_suspend NULL
+#define imx_ssi_resume NULL
+#endif
+
+static struct snd_soc_dai_driver imx_ssi_dai = {
+ .probe = imx_ssi_dai_probe,
+ .suspend = imx_ssi_dai_suspend,
+ .resume = imx_ssi_dai_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = IMX_SSI_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = IMX_SSI_FORMATS,
+ },
+ .ops = &imx_ssi_pcm_dai_ops,
+};
+
+static struct snd_soc_dai_driver imx_ac97_dai = {
+ .probe = imx_ssi_dai_probe,
+ .ac97_control = 1,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &imx_ssi_pcm_dai_ops,
+};
+
+static void setup_channel_to_ac97(struct imx_ssi *imx_ssi)
+{
+ void __iomem *base = imx_ssi->base;
+
+ writel(0x0, base + SSI_SCR);
+ writel(0x0, base + SSI_STCR);
+ writel(0x0, base + SSI_SRCR);
+
+ writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR);
+
+ writel(SSI_SFCSR_RFWM0(imx_ssi->dma_params_rx.burstsize) |
+ SSI_SFCSR_TFWM0(imx_ssi->dma_params_tx.burstsize) |
+ SSI_SFCSR_RFWM1(imx_ssi->dma_params_rx.burstsize) |
+ SSI_SFCSR_TFWM1(imx_ssi->dma_params_tx.burstsize), base + SSI_SFCSR);
+
+ writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR);
+ writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR);
+
+ writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR);
+ writel(SSI_SOR_WAIT(3), base + SSI_SOR);
+
+ writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN |
+ SSI_SCR_TE | SSI_SCR_RE,
+ base + SSI_SCR);
+
+ writel(SSI_SACNT_DEFAULT, base + SSI_SACNT);
+ writel(0xff, base + SSI_SACCDIS);
+ writel(0x300, base + SSI_SACCEN);
+}
+
+static struct imx_ssi *ac97_ssi;
+
+static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ struct imx_ssi *imx_ssi = ac97_ssi;
+ void __iomem *base = imx_ssi->base;
+ unsigned int lreg;
+ unsigned int lval;
+
+ if (reg > 0x7f)
+ return;
+
+ pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+ lreg = reg << 12;
+ writel(lreg, base + SSI_SACADD);
+
+ lval = val << 4;
+ writel(lval , base + SSI_SACDAT);
+
+ writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT);
+ udelay(100);
+}
+
+static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ struct imx_ssi *imx_ssi = ac97_ssi;
+ void __iomem *base = imx_ssi->base;
+
+ unsigned short val = -1;
+ unsigned int lreg;
+
+ lreg = (reg & 0x7f) << 12 ;
+ writel(lreg, base + SSI_SACADD);
+ writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT);
+
+ udelay(100);
+
+ val = (readl(base + SSI_SACDAT) >> 4) & 0xffff;
+
+ pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+ return val;
+}
+
+static void imx_ssi_ac97_reset(struct snd_ac97 *ac97)
+{
+ struct imx_ssi *imx_ssi = ac97_ssi;
+
+ if (imx_ssi->ac97_reset)
+ imx_ssi->ac97_reset(ac97);
+}
+
+static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ struct imx_ssi *imx_ssi = ac97_ssi;
+
+ if (imx_ssi->ac97_warm_reset)
+ imx_ssi->ac97_warm_reset(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = imx_ssi_ac97_read,
+ .write = imx_ssi_ac97_write,
+ .reset = imx_ssi_ac97_reset,
+ .warm_reset = imx_ssi_ac97_warm_reset
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int imx_ssi_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct imx_ssi *ssi;
+ struct imx_ssi_platform_data *pdata = pdev->dev.platform_data;
+ int ret = 0;
+ struct snd_soc_dai_driver *dai;
+
+ ssi = kzalloc(sizeof(*ssi), GFP_KERNEL);
+ if (!ssi)
+ return -ENOMEM;
+ dev_set_drvdata(&pdev->dev, ssi);
+
+ if (pdata) {
+ ssi->ac97_reset = pdata->ac97_reset;
+ ssi->ac97_warm_reset = pdata->ac97_warm_reset;
+ ssi->flags = pdata->flags;
+ }
+
+ ssi->irq = platform_get_irq(pdev, 0);
+
+ ssi->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(ssi->clk)) {
+ ret = PTR_ERR(ssi->clk);
+ dev_err(&pdev->dev, "Cannot get the clock: %d\n",
+ ret);
+ goto failed_clk;
+ }
+ clk_enable(ssi->clk);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENODEV;
+ goto failed_get_resource;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) {
+ dev_err(&pdev->dev, "request_mem_region failed\n");
+ ret = -EBUSY;
+ goto failed_get_resource;
+ }
+
+ ssi->base = ioremap(res->start, resource_size(res));
+ if (!ssi->base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENODEV;
+ goto failed_ioremap;
+ }
+
+ if (ssi->flags & IMX_SSI_USE_AC97) {
+ if (ac97_ssi) {
+ ret = -EBUSY;
+ goto failed_ac97;
+ }
+ ac97_ssi = ssi;
+ setup_channel_to_ac97(ssi);
+ dai = &imx_ac97_dai;
+ } else
+ dai = &imx_ssi_dai;
+
+ writel(0x0, ssi->base + SSI_SIER);
+ clk_disable(ssi->clk);
+
+ ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0;
+ ssi->dma_params_tx.dma_addr = res->start + SSI_STX0;
+
+ ssi->dma_params_tx.burstsize = 6;
+ ssi->dma_params_rx.burstsize = 4;
+
+ ssi->dma_params_tx.peripheral_type = IMX_DMATYPE_SSI_SP;
+ ssi->dma_params_rx.peripheral_type = IMX_DMATYPE_SSI_SP;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
+ if (res)
+ ssi->dma_params_tx.dma = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
+ if (res)
+ ssi->dma_params_rx.dma = res->start;
+
+ platform_set_drvdata(pdev, ssi);
+
+ ret = snd_soc_register_dai(&pdev->dev, dai);
+ if (ret) {
+ dev_err(&pdev->dev, "register DAI failed\n");
+ goto failed_register;
+ }
+
+ ssi->soc_platform_pdev_fiq = platform_device_alloc("imx-fiq-pcm-audio", pdev->id);
+ if (!ssi->soc_platform_pdev_fiq) {
+ ret = -ENOMEM;
+ goto failed_pdev_fiq_alloc;
+ }
+
+ platform_set_drvdata(ssi->soc_platform_pdev_fiq, ssi);
+ ret = platform_device_add(ssi->soc_platform_pdev_fiq);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add platform device\n");
+ goto failed_pdev_fiq_add;
+ }
+
+ ssi->soc_platform_pdev = platform_device_alloc("imx-pcm-audio", pdev->id);
+ if (!ssi->soc_platform_pdev) {
+ ret = -ENOMEM;
+ goto failed_pdev_alloc;
+ }
+
+ platform_set_drvdata(ssi->soc_platform_pdev, ssi);
+ ret = platform_device_add(ssi->soc_platform_pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add platform device\n");
+ goto failed_pdev_add;
+ }
+
+ return 0;
+
+failed_pdev_add:
+ platform_device_put(ssi->soc_platform_pdev);
+failed_pdev_alloc:
+ platform_device_del(ssi->soc_platform_pdev_fiq);
+failed_pdev_fiq_add:
+ platform_device_put(ssi->soc_platform_pdev_fiq);
+failed_pdev_fiq_alloc:
+ snd_soc_unregister_dai(&pdev->dev);
+failed_register:
+failed_ac97:
+ iounmap(ssi->base);
+failed_ioremap:
+ release_mem_region(res->start, resource_size(res));
+failed_get_resource:
+ clk_disable(ssi->clk);
+ clk_put(ssi->clk);
+failed_clk:
+ kfree(ssi);
+
+ return ret;
+}
+
+static int __devexit imx_ssi_remove(struct platform_device *pdev)
+{
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ struct imx_ssi *ssi = platform_get_drvdata(pdev);
+
+ platform_device_unregister(ssi->soc_platform_pdev);
+ platform_device_unregister(ssi->soc_platform_pdev_fiq);
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ if (ssi->flags & IMX_SSI_USE_AC97)
+ ac97_ssi = NULL;
+
+ iounmap(ssi->base);
+ release_mem_region(res->start, resource_size(res));
+ clk_disable(ssi->clk);
+ clk_put(ssi->clk);
+ kfree(ssi);
+
+ return 0;
+}
+
+static struct platform_driver imx_ssi_driver = {
+ .probe = imx_ssi_probe,
+ .remove = __devexit_p(imx_ssi_remove),
+
+ .driver = {
+ .name = "imx-ssi",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init imx_ssi_init(void)
+{
+ return platform_driver_register(&imx_ssi_driver);
+}
+
+static void __exit imx_ssi_exit(void)
+{
+ platform_driver_unregister(&imx_ssi_driver);
+}
+
+module_init(imx_ssi_init);
+module_exit(imx_ssi_exit);
+
+/* Module information */
+MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-ssi");
diff --git a/sound/soc/imx/imx-ssi.h b/sound/soc/imx/imx-ssi.h
new file mode 100644
index 00000000..af8e5a2a
--- /dev/null
+++ b/sound/soc/imx/imx-ssi.h
@@ -0,0 +1,225 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _IMX_SSI_H
+#define _IMX_SSI_H
+
+#define SSI_STX0 0x00
+#define SSI_STX1 0x04
+#define SSI_SRX0 0x08
+#define SSI_SRX1 0x0c
+
+#define SSI_SCR 0x10
+#define SSI_SCR_CLK_IST (1 << 9)
+#define SSI_SCR_CLK_IST_SHIFT 9
+#define SSI_SCR_TCH_EN (1 << 8)
+#define SSI_SCR_SYS_CLK_EN (1 << 7)
+#define SSI_SCR_I2S_MODE_NORM (0 << 5)
+#define SSI_SCR_I2S_MODE_MSTR (1 << 5)
+#define SSI_SCR_I2S_MODE_SLAVE (2 << 5)
+#define SSI_I2S_MODE_MASK (3 << 5)
+#define SSI_SCR_SYN (1 << 4)
+#define SSI_SCR_NET (1 << 3)
+#define SSI_SCR_RE (1 << 2)
+#define SSI_SCR_TE (1 << 1)
+#define SSI_SCR_SSIEN (1 << 0)
+
+#define SSI_SISR 0x14
+#define SSI_SISR_MASK ((1 << 19) - 1)
+#define SSI_SISR_CMDAU (1 << 18)
+#define SSI_SISR_CMDDU (1 << 17)
+#define SSI_SISR_RXT (1 << 16)
+#define SSI_SISR_RDR1 (1 << 15)
+#define SSI_SISR_RDR0 (1 << 14)
+#define SSI_SISR_TDE1 (1 << 13)
+#define SSI_SISR_TDE0 (1 << 12)
+#define SSI_SISR_ROE1 (1 << 11)
+#define SSI_SISR_ROE0 (1 << 10)
+#define SSI_SISR_TUE1 (1 << 9)
+#define SSI_SISR_TUE0 (1 << 8)
+#define SSI_SISR_TFS (1 << 7)
+#define SSI_SISR_RFS (1 << 6)
+#define SSI_SISR_TLS (1 << 5)
+#define SSI_SISR_RLS (1 << 4)
+#define SSI_SISR_RFF1 (1 << 3)
+#define SSI_SISR_RFF0 (1 << 2)
+#define SSI_SISR_TFE1 (1 << 1)
+#define SSI_SISR_TFE0 (1 << 0)
+
+#define SSI_SIER 0x18
+#define SSI_SIER_RDMAE (1 << 22)
+#define SSI_SIER_RIE (1 << 21)
+#define SSI_SIER_TDMAE (1 << 20)
+#define SSI_SIER_TIE (1 << 19)
+#define SSI_SIER_CMDAU_EN (1 << 18)
+#define SSI_SIER_CMDDU_EN (1 << 17)
+#define SSI_SIER_RXT_EN (1 << 16)
+#define SSI_SIER_RDR1_EN (1 << 15)
+#define SSI_SIER_RDR0_EN (1 << 14)
+#define SSI_SIER_TDE1_EN (1 << 13)
+#define SSI_SIER_TDE0_EN (1 << 12)
+#define SSI_SIER_ROE1_EN (1 << 11)
+#define SSI_SIER_ROE0_EN (1 << 10)
+#define SSI_SIER_TUE1_EN (1 << 9)
+#define SSI_SIER_TUE0_EN (1 << 8)
+#define SSI_SIER_TFS_EN (1 << 7)
+#define SSI_SIER_RFS_EN (1 << 6)
+#define SSI_SIER_TLS_EN (1 << 5)
+#define SSI_SIER_RLS_EN (1 << 4)
+#define SSI_SIER_RFF1_EN (1 << 3)
+#define SSI_SIER_RFF0_EN (1 << 2)
+#define SSI_SIER_TFE1_EN (1 << 1)
+#define SSI_SIER_TFE0_EN (1 << 0)
+
+#define SSI_STCR 0x1c
+#define SSI_STCR_TXBIT0 (1 << 9)
+#define SSI_STCR_TFEN1 (1 << 8)
+#define SSI_STCR_TFEN0 (1 << 7)
+#define SSI_FIFO_ENABLE_0_SHIFT 7
+#define SSI_STCR_TFDIR (1 << 6)
+#define SSI_STCR_TXDIR (1 << 5)
+#define SSI_STCR_TSHFD (1 << 4)
+#define SSI_STCR_TSCKP (1 << 3)
+#define SSI_STCR_TFSI (1 << 2)
+#define SSI_STCR_TFSL (1 << 1)
+#define SSI_STCR_TEFS (1 << 0)
+
+#define SSI_SRCR 0x20
+#define SSI_SRCR_RXBIT0 (1 << 9)
+#define SSI_SRCR_RFEN1 (1 << 8)
+#define SSI_SRCR_RFEN0 (1 << 7)
+#define SSI_FIFO_ENABLE_0_SHIFT 7
+#define SSI_SRCR_RFDIR (1 << 6)
+#define SSI_SRCR_RXDIR (1 << 5)
+#define SSI_SRCR_RSHFD (1 << 4)
+#define SSI_SRCR_RSCKP (1 << 3)
+#define SSI_SRCR_RFSI (1 << 2)
+#define SSI_SRCR_RFSL (1 << 1)
+#define SSI_SRCR_REFS (1 << 0)
+
+#define SSI_SRCCR 0x28
+#define SSI_SRCCR_DIV2 (1 << 18)
+#define SSI_SRCCR_PSR (1 << 17)
+#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13)
+#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8)
+#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0)
+#define SSI_SRCCR_WL_MASK (0xf << 13)
+#define SSI_SRCCR_DC_MASK (0x1f << 8)
+#define SSI_SRCCR_PM_MASK (0xff << 0)
+
+#define SSI_STCCR 0x24
+#define SSI_STCCR_DIV2 (1 << 18)
+#define SSI_STCCR_PSR (1 << 17)
+#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13)
+#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8)
+#define SSI_STCCR_PM(x) (((x) & 0xff) << 0)
+#define SSI_STCCR_WL_MASK (0xf << 13)
+#define SSI_STCCR_DC_MASK (0x1f << 8)
+#define SSI_STCCR_PM_MASK (0xff << 0)
+
+#define SSI_SFCSR 0x2c
+#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28)
+#define SSI_RX_FIFO_1_COUNT_SHIFT 28
+#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24)
+#define SSI_TX_FIFO_1_COUNT_SHIFT 24
+#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20)
+#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16)
+#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12)
+#define SSI_RX_FIFO_0_COUNT_SHIFT 12
+#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8)
+#define SSI_TX_FIFO_0_COUNT_SHIFT 8
+#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4)
+#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0)
+#define SSI_SFCSR_RFWM0_MASK (0xf << 4)
+#define SSI_SFCSR_TFWM0_MASK (0xf << 0)
+
+#define SSI_STR 0x30
+#define SSI_STR_TEST (1 << 15)
+#define SSI_STR_RCK2TCK (1 << 14)
+#define SSI_STR_RFS2TFS (1 << 13)
+#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8)
+#define SSI_STR_TXD2RXD (1 << 7)
+#define SSI_STR_TCK2RCK (1 << 6)
+#define SSI_STR_TFS2RFS (1 << 5)
+#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0)
+
+#define SSI_SOR 0x34
+#define SSI_SOR_CLKOFF (1 << 6)
+#define SSI_SOR_RX_CLR (1 << 5)
+#define SSI_SOR_TX_CLR (1 << 4)
+#define SSI_SOR_INIT (1 << 3)
+#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1)
+#define SSI_SOR_WAIT_MASK (0x3 << 1)
+#define SSI_SOR_SYNRST (1 << 0)
+
+#define SSI_SACNT 0x38
+#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5)
+#define SSI_SACNT_WR (1 << 4)
+#define SSI_SACNT_RD (1 << 3)
+#define SSI_SACNT_TIF (1 << 2)
+#define SSI_SACNT_FV (1 << 1)
+#define SSI_SACNT_AC97EN (1 << 0)
+
+#define SSI_SACADD 0x3c
+#define SSI_SACDAT 0x40
+#define SSI_SATAG 0x44
+#define SSI_STMSK 0x48
+#define SSI_SRMSK 0x4c
+#define SSI_SACCST 0x50
+#define SSI_SACCEN 0x54
+#define SSI_SACCDIS 0x58
+
+/* SSI clock sources */
+#define IMX_SSP_SYS_CLK 0
+
+/* SSI audio dividers */
+#define IMX_SSI_TX_DIV_2 0
+#define IMX_SSI_TX_DIV_PSR 1
+#define IMX_SSI_TX_DIV_PM 2
+#define IMX_SSI_RX_DIV_2 3
+#define IMX_SSI_RX_DIV_PSR 4
+#define IMX_SSI_RX_DIV_PM 5
+
+#define DRV_NAME "imx-ssi"
+
+#include <linux/dmaengine.h>
+#include <mach/dma.h>
+
+struct imx_ssi {
+ struct platform_device *ac97_dev;
+
+ struct snd_soc_dai *imx_ac97;
+ struct clk *clk;
+ void __iomem *base;
+ int irq;
+ int fiq_enable;
+ unsigned int offset;
+
+ unsigned int flags;
+
+ void (*ac97_reset) (struct snd_ac97 *ac97);
+ void (*ac97_warm_reset)(struct snd_ac97 *ac97);
+
+ struct imx_pcm_dma_params dma_params_rx;
+ struct imx_pcm_dma_params dma_params_tx;
+
+ int enabled;
+
+ struct platform_device *soc_platform_pdev;
+ struct platform_device *soc_platform_pdev_fiq;
+};
+
+struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev,
+ struct imx_ssi *ssi);
+void imx_ssi_fiq_exit(struct platform_device *pdev, struct imx_ssi *ssi);
+struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev,
+ struct imx_ssi *ssi);
+
+int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
+int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm);
+void imx_pcm_free(struct snd_pcm *pcm);
+#endif /* _IMX_SSI_H */
diff --git a/sound/soc/imx/imx-wm8958.c b/sound/soc/imx/imx-wm8958.c
new file mode 100755
index 00000000..dc7a1f59
--- /dev/null
+++ b/sound/soc/imx/imx-wm8958.c
@@ -0,0 +1,362 @@
+/*
+ * imx-wm8958.c
+ *
+ * Copyright (C) 2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/fsl_devices.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/jack.h>
+#include <mach/dma.h>
+#include <mach/clock.h>
+#include <mach/audmux.h>
+
+#include "imx-ssi.h"
+#include "../codecs/wm8994.h"
+#include <linux/mfd/wm8994/registers.h>
+
+struct imx_priv {
+ int sysclk; /*mclk from the outside*/
+ int codec_sysclk;
+ int dai_hifi;
+ struct platform_device *pdev;
+ struct wm8994 *wm8958;
+};
+static struct imx_priv card_priv;
+static struct snd_soc_card snd_soc_card_imx;
+struct clk *codec_mclk;
+static struct snd_soc_jack hs_jack;
+
+/* Headphones jack detection DAPM pins */
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+/* Headphones jack detection gpios */
+static struct snd_soc_jack_gpio hs_jack_gpios[] = {
+ [0] = {
+ /* gpio is set on per-platform basis */
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 200,
+ },
+};
+
+static int imx_hifi_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+ if (!codec_dai->active)
+ clk_enable(codec_mclk);
+
+ return 0;
+}
+
+static void imx_hifi_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+ if (!codec_dai->active)
+ clk_disable(codec_mclk);
+
+ return;
+}
+
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ unsigned int channels = params_channels(params);
+ int ret = 0;
+ unsigned int pll_out;
+ u32 dai_format;
+
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ channels == 1 ? 0xfffffffe : 0xfffffffc,
+ channels == 1 ? 0xfffffffe : 0xfffffffc,
+ 2, 32);
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ if (params_rate(params) == 8000 || params_rate(params) == 11025)
+ pll_out = params_rate(params) * 512;
+ else
+ pll_out = params_rate(params) * 256;
+
+ ret =
+ snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
+ priv->sysclk, pll_out);
+ if (ret < 0)
+ return ret;
+
+ ret =
+ snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, pll_out,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* imx card dapm widgets */
+static const struct snd_soc_dapm_widget imx_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Main Mic", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+/* imx machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* ----input ------------------- */
+ /* Mic Jack --> MIC_IN (with automatic bias) */
+ {"MICBIAS2", NULL, "Headset Mic"},
+ {"IN1LP", NULL, "MICBIAS2"},
+ {"IN1LN", NULL, "Headset Mic"},
+
+ {"MICBIAS1", NULL, "Main Mic"},
+ {"IN1RP", NULL, "MICBIAS1"},
+ {"IN1RN", NULL, "Main Mic"},
+
+ /* ----output------------------- */
+ /* HP_OUT --> Headphone Jack */
+ {"Headphone Jack", NULL, "HPOUT1L"},
+ {"Headphone Jack", NULL, "HPOUT1R"},
+
+ /* LINE_OUT --> Ext Speaker */
+ {"Ext Spk", NULL, "SPKOUTLP"},
+ {"Ext Spk", NULL, "SPKOUTLN"},
+ {"Ext Spk", NULL, "SPKOUTRP"},
+ {"Ext Spk", NULL, "SPKOUTRN"},
+
+};
+
+static int imx_wm8958_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ int ret;
+
+/* Add imx specific widgets */
+ snd_soc_dapm_new_controls(&codec->dapm, imx_dapm_widgets,
+ ARRAY_SIZE(imx_dapm_widgets));
+
+ /* Set up imx specific audio path audio_map */
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack");
+
+ snd_soc_dapm_sync(&codec->dapm);
+
+ if (hs_jack_gpios[0].gpio != -1) {
+ /* Jack detection API stuff */
+ ret = snd_soc_jack_new(codec, "Headphone Jack",
+ SND_JACK_HEADPHONE, &hs_jack);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+ hs_jack_pins);
+ if (ret) {
+ printk(KERN_ERR "failed to call snd_soc_jack_add_pins\n");
+ return ret;
+ }
+
+ ret = snd_soc_jack_add_gpios(&hs_jack,
+ ARRAY_SIZE(hs_jack_gpios), hs_jack_gpios);
+ if (ret)
+ printk(KERN_WARNING "failed to call snd_soc_jack_add_gpios\n");
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .startup = imx_hifi_startup,
+ .shutdown = imx_hifi_shutdown,
+ .hw_params = imx_hifi_hw_params,
+};
+
+static struct snd_soc_dai_link imx_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "wm8994-aif1",
+ .codec_name = "wm8994-codec",
+ .cpu_dai_name = "imx-ssi.1",
+ .platform_name = "imx-pcm-audio.1",
+ .init = imx_wm8958_init,
+ .ops = &imx_hifi_ops,
+ },
+};
+
+static struct snd_soc_card snd_soc_card_imx = {
+ .name = "wm8958-audio",
+ .dai_link = imx_dai,
+ .num_links = ARRAY_SIZE(imx_dai),
+};
+
+static int imx_audmux_config(int slave, int master)
+{
+ unsigned int ptcr, pdcr;
+ slave = slave - 1;
+ master = master - 1;
+
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN |
+ MXC_AUDMUX_V2_PTCR_TFSDIR |
+ MXC_AUDMUX_V2_PTCR_TFSEL(master) |
+ MXC_AUDMUX_V2_PTCR_TCLKDIR |
+ MXC_AUDMUX_V2_PTCR_TCSEL(master);
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(master);
+ mxc_audmux_v2_configure_port(slave, ptcr, pdcr);
+
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN;
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(slave);
+ mxc_audmux_v2_configure_port(master, ptcr, pdcr);
+
+ return 0;
+}
+
+/*
+ * This function will register the snd_soc_pcm_link drivers.
+ */
+static int __devinit imx_wm8958_probe(struct platform_device *pdev)
+{
+
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ struct imx_priv *priv = &card_priv;
+ struct wm8994 *wm8958 = plat->priv;
+ int ret = 0;
+
+ codec_mclk = clk_get(NULL, "clko_clk");
+ if (IS_ERR(codec_mclk)) {
+ printk(KERN_ERR "can't get CLKO clock.\n");
+ return PTR_ERR(codec_mclk);
+ }
+
+ priv->pdev = pdev;
+ priv->wm8958 = wm8958;
+
+ imx_audmux_config(plat->src_port, plat->ext_port);
+
+ if (plat->init && plat->init()) {
+ ret = -EINVAL;
+ return ret;
+ }
+
+ priv->sysclk = plat->sysclk;
+ hs_jack_gpios[0].gpio = plat->hp_gpio;
+ hs_jack_gpios[0].invert = plat->hp_active_low;
+
+ return ret;
+}
+
+static int __devexit imx_wm8958_remove(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ if (plat->finit)
+ plat->finit();
+
+ clk_disable(codec_mclk);
+ clk_put(codec_mclk);
+
+ return 0;
+}
+
+static struct platform_driver imx_wm8958_driver = {
+ .probe = imx_wm8958_probe,
+ .remove = imx_wm8958_remove,
+ .driver = {
+ .name = "imx-wm8958",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *imx_snd_device;
+
+static int __init imx_asoc_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_wm8958_driver);
+ if (ret < 0)
+ goto exit;
+
+ imx_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!imx_snd_device)
+ goto err_device_alloc;
+
+ platform_set_drvdata(imx_snd_device, &snd_soc_card_imx);
+
+ ret = platform_device_add(imx_snd_device);
+
+ if (0 == ret)
+ goto exit;
+
+ platform_device_put(imx_snd_device);
+
+err_device_alloc:
+ platform_driver_unregister(&imx_wm8958_driver);
+exit:
+ return ret;
+}
+
+static void __exit imx_asoc_exit(void)
+{
+ platform_driver_unregister(&imx_wm8958_driver);
+ platform_device_unregister(imx_snd_device);
+}
+
+module_init(imx_asoc_init);
+module_exit(imx_asoc_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC imx wm8958");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-wm8962.c b/sound/soc/imx/imx-wm8962.c
new file mode 100644
index 00000000..d6ab88d7
--- /dev/null
+++ b/sound/soc/imx/imx-wm8962.c
@@ -0,0 +1,678 @@
+/*
+ * imx-wm8962.c
+ *
+ * Copyright (C) 2012-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/fsl_devices.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/kthread.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/jack.h>
+#include <mach/dma.h>
+#include <mach/clock.h>
+#include <mach/audmux.h>
+#include <mach/gpio.h>
+#include <asm/mach-types.h>
+
+#include "imx-ssi.h"
+#include "../codecs/wm8962.h"
+
+struct imx_priv {
+ int sysclk; /*mclk from the outside*/
+ int codec_sysclk;
+ int dai_hifi;
+ int hp_irq;
+ int hp_status;
+ int amic_irq;
+ int amic_status;
+ struct platform_device *pdev;
+ struct snd_pcm_substream *first_stream;
+ struct snd_pcm_substream *second_stream;
+};
+unsigned int sample_format = SNDRV_PCM_FMTBIT_S16_LE;
+static struct imx_priv card_priv;
+static struct snd_soc_card snd_soc_card_imx;
+static struct snd_soc_codec *gcodec;
+
+static struct snd_soc_jack imx_hp_jack;
+static struct snd_soc_jack_pin imx_hp_jack_pins[] = {
+ {
+ .pin = "Ext Spk",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 150,
+ .invert = 0,
+};
+
+static struct snd_soc_jack imx_mic_jack;
+static struct snd_soc_jack_pin imx_mic_jack_pins[] = {
+ {
+ .pin = "DMIC",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+static struct snd_soc_jack_gpio imx_mic_jack_gpio = {
+ .name = "micphone detect",
+ .report = SND_JACK_MICROPHONE,
+ .debounce_time = 150,
+ .invert = 0,
+};
+
+static int imx_hifi_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct mxc_audio_platform_data *plat = priv->pdev->dev.platform_data;
+
+ if (!codec_dai->active)
+ plat->clock_enable(1);
+
+ return 0;
+}
+
+static void imx_hifi_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct mxc_audio_platform_data *plat = priv->pdev->dev.platform_data;
+
+ if (!codec_dai->active)
+ plat->clock_enable(0);
+
+ return;
+}
+
+static int check_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct imx_priv *priv = &card_priv;
+ unsigned int channels = params_channels(params);
+ unsigned int sample_rate = params_rate(params);
+ snd_pcm_format_t sample_format = params_format(params);
+
+ substream->runtime->sample_bits =
+ snd_pcm_format_physical_width(sample_format);
+ substream->runtime->rate = sample_rate;
+ substream->runtime->format = sample_format;
+ substream->runtime->channels = channels;
+
+ if (!priv->first_stream) {
+ priv->first_stream = substream;
+ } else {
+ priv->second_stream = substream;
+
+ /* Check two sample rates of two streams */
+ if (priv->first_stream->runtime->rate !=
+ priv->second_stream->runtime->rate) {
+ pr_err("\n!KEEP THE SAME SAMPLE RATE: %d!\n",
+ priv->first_stream->runtime->rate);
+ return -EINVAL;
+ }
+
+ /* Check two sample bits of two streams */
+ if (priv->first_stream->runtime->sample_bits !=
+ priv->second_stream->runtime->sample_bits) {
+ snd_pcm_format_t first_format =
+ priv->first_stream->runtime->format;
+
+ pr_err("\n!KEEP THE SAME FORMAT: %s!\n",
+ snd_pcm_format_name(first_format));
+ return -EINVAL;
+ }
+
+ /* Check two channel numbers of two streams */
+ if (priv->first_stream->runtime->channels !=
+ priv->second_stream->runtime->channels) {
+ pr_err("\n!KEEP THE SAME CHANNEL NUMBER: %d!\n",
+ priv->first_stream->runtime->channels);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ unsigned int channels = params_channels(params);
+ unsigned int sample_rate = 44100;
+ int ret = 0;
+ u32 dai_format;
+ unsigned int pll_out;
+
+ /*
+ * WM8962 doesn't support two substreams in different parameters
+ * (i.e. different sample rates, audio formats, channel numbers)
+ * So we here check the three parameters above of two substreams
+ * if they are running in the same time.
+ */
+ ret = check_hw_params(substream, params);
+ if (ret < 0) {
+ pr_err("Failed to match hw params: %d\n", ret);
+ return ret;
+ }
+
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ channels == 1 ? 0xfffffffe : 0xfffffffc,
+ channels == 1 ? 0xfffffffe : 0xfffffffc,
+ 2, 32);
+
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ sample_rate = params_rate(params);
+ sample_format = params_format(params);
+
+ if (sample_format == SNDRV_PCM_FORMAT_S24_LE)
+ pll_out = sample_rate * 192;
+ else
+ pll_out = sample_rate * 256;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL_MCLK,
+ WM8962_FLL_MCLK, priv->sysclk,
+ pll_out);
+ if (ret < 0)
+ pr_err("Failed to start FLL: %d\n", ret);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ WM8962_SYSCLK_FLL,
+ pll_out,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err("Failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ int ret;
+
+ if (priv->first_stream == substream)
+ priv->first_stream = priv->second_stream;
+ priv->second_stream = NULL;
+
+ if (!priv->first_stream) {
+ /*
+ * wm8962 doesn't allow us to continuously setting FLL,
+ * So we set MCLK as sysclk once, which'd remove the limitation.
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
+ 0, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err("Failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Continuously setting FLL would cause playback distortion.
+ * We can fix it just by mute codec after playback.
+ */
+ ret = snd_soc_dai_digital_mute(codec_dai, 1);
+ if (ret < 0) {
+ pr_err("Failed to set MUTE: %d\n", ret);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void imx_resume_event(struct work_struct *wor)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ struct snd_soc_jack *jack;
+ int enable;
+ int report;
+
+ if (plat->hp_gpio != -1) {
+ jack = imx_hp_jack_gpio.jack;
+
+ enable = gpio_get_value_cansleep(imx_hp_jack_gpio.gpio);
+ if (imx_hp_jack_gpio.invert)
+ enable = !enable;
+
+ if (enable)
+ report = imx_hp_jack_gpio.report;
+ else
+ report = 0;
+
+ snd_soc_jack_report(jack, report, imx_hp_jack_gpio.report);
+ }
+
+ if (plat->mic_gpio != -1) {
+ jack = imx_mic_jack_gpio.jack;
+
+ enable = gpio_get_value_cansleep(imx_mic_jack_gpio.gpio);
+ if (imx_mic_jack_gpio.invert)
+ enable = !enable;
+
+ if (enable)
+ report = imx_mic_jack_gpio.report;
+ else
+ report = 0;
+
+ snd_soc_jack_report(jack, report, imx_mic_jack_gpio.report);
+ }
+
+ return;
+}
+
+static int imx_event_hp(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ char *envp[3];
+ char *buf;
+
+ if (plat->hp_gpio != -1) {
+ priv->hp_status = gpio_get_value(plat->hp_gpio);
+
+ buf = kmalloc(32, GFP_ATOMIC);
+ if (!buf) {
+ pr_err("%s kmalloc failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (priv->hp_status != plat->hp_active_low)
+ snprintf(buf, 32, "STATE=%d", 2);
+ else
+ snprintf(buf, 32, "STATE=%d", 0);
+
+ envp[0] = "NAME=headphone";
+ envp[1] = buf;
+ envp[2] = NULL;
+ kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
+ kfree(buf);
+ }
+
+ return 0;
+}
+
+static int imx_event_mic(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ char *envp[3];
+ char *buf;
+
+ if (plat->mic_gpio != -1) {
+ priv->amic_status = gpio_get_value(plat->mic_gpio);
+
+ buf = kmalloc(32, GFP_ATOMIC);
+ if (!buf) {
+ pr_err("%s kmalloc failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (priv->amic_status == 0)
+ snprintf(buf, 32, "STATE=%d", 2);
+ else
+ snprintf(buf, 32, "STATE=%d", 0);
+
+ envp[0] = "NAME=amic";
+ envp[1] = buf;
+ envp[2] = NULL;
+ kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
+ kfree(buf);
+ }
+
+ return 0;
+}
+
+
+static const struct snd_kcontrol_new controls[] = {
+ SOC_DAPM_PIN_SWITCH("Ext Spk"),
+};
+
+/* imx card dapm widgets */
+static const struct snd_soc_dapm_widget imx_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", imx_event_hp),
+ SND_SOC_DAPM_MIC("AMIC", NULL),
+ SND_SOC_DAPM_MIC("DMIC", imx_event_mic),
+};
+
+/* imx machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+ { "Headphone Jack", NULL, "HPOUTL" },
+ { "Headphone Jack", NULL, "HPOUTR" },
+
+ { "Ext Spk", NULL, "SPKOUTL" },
+ { "Ext Spk", NULL, "SPKOUTR" },
+
+ { "MICBIAS", NULL, "AMIC" },
+ { "IN3R", NULL, "MICBIAS" },
+
+ { "DMIC", NULL, "MICBIAS" },
+ { "DMICDAT", NULL, "DMIC" },
+
+};
+
+static ssize_t show_headphone(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ /* determine whether hp is plugged in */
+ priv->hp_status = gpio_get_value(plat->hp_gpio);
+
+ if (priv->hp_status != plat->hp_active_low)
+ strcpy(buf, "headphone\n");
+ else
+ strcpy(buf, "speaker\n");
+
+ return strlen(buf);
+}
+
+static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
+
+static ssize_t show_amic(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ /* determine whether amic is plugged in */
+ priv->amic_status = gpio_get_value(plat->hp_gpio);
+
+ if (priv->amic_status != plat->mic_active_low)
+ strcpy(buf, "amic\n");
+ else
+ strcpy(buf, "dmic\n");
+
+ return strlen(buf);
+}
+
+static DRIVER_ATTR(amic, S_IRUGO | S_IWUSR, show_amic, NULL);
+
+static DECLARE_DELAYED_WORK(resume_hp_event, imx_resume_event);
+
+int imx_hifi_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ if (SNDRV_PCM_TRIGGER_RESUME == cmd) {
+ if ((plat->hp_gpio != -1) || (plat->mic_gpio != -1))
+ schedule_delayed_work(&resume_hp_event,
+ msecs_to_jiffies(200));
+ }
+
+ return 0;
+}
+
+static int imx_wm8962_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ int ret = 0;
+
+ gcodec = rtd->codec;
+
+ /* Add imx specific widgets */
+ snd_soc_dapm_new_controls(&codec->dapm, imx_dapm_widgets,
+ ARRAY_SIZE(imx_dapm_widgets));
+
+ /* Set up imx specific audio path audio_map */
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(&codec->dapm, "AMIC");
+
+ if (plat->hp_gpio != -1) {
+ imx_hp_jack_gpio.gpio = plat->hp_gpio;
+ snd_soc_jack_new(codec, "Ext Spk", SND_JACK_LINEOUT,
+ &imx_hp_jack);
+ snd_soc_jack_add_pins(&imx_hp_jack,
+ ARRAY_SIZE(imx_hp_jack_pins),
+ imx_hp_jack_pins);
+ snd_soc_jack_add_gpios(&imx_hp_jack,
+ 1, &imx_hp_jack_gpio);
+
+ ret = driver_create_file(pdev->dev.driver,
+ &driver_attr_headphone);
+ if (ret < 0) {
+ ret = -EINVAL;
+ return ret;
+ }
+ }
+
+ if (plat->mic_gpio != -1) {
+ imx_mic_jack_gpio.gpio = plat->mic_gpio;
+ snd_soc_jack_new(codec, "DMIC", SND_JACK_MICROPHONE,
+ &imx_mic_jack);
+ snd_soc_jack_add_pins(&imx_mic_jack,
+ ARRAY_SIZE(imx_mic_jack_pins),
+ imx_mic_jack_pins);
+ snd_soc_jack_add_gpios(&imx_mic_jack,
+ 1, &imx_mic_jack_gpio);
+
+ ret = driver_create_file(pdev->dev.driver,
+ &driver_attr_amic);
+ if (ret < 0) {
+ ret = -EINVAL;
+ return ret;
+ }
+ } else {
+ snd_soc_dapm_nc_pin(&codec->dapm, "DMIC");
+ }
+
+ snd_soc_dapm_sync(&codec->dapm);
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .startup = imx_hifi_startup,
+ .shutdown = imx_hifi_shutdown,
+ .hw_params = imx_hifi_hw_params,
+ .hw_free = imx_hifi_hw_free,
+ .trigger = imx_hifi_trigger,
+};
+
+static struct snd_soc_dai_link imx_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "wm8962",
+ .codec_name = "wm8962.0-001a",
+ .cpu_dai_name = "imx-ssi.1",
+ .platform_name = "imx-pcm-audio.1",
+ .init = imx_wm8962_init,
+ .ops = &imx_hifi_ops,
+ },
+};
+
+static struct snd_soc_card snd_soc_card_imx = {
+ .name = "wm8962-audio",
+ .dai_link = imx_dai,
+ .num_links = ARRAY_SIZE(imx_dai),
+};
+
+static int imx_audmux_config(int slave, int master)
+{
+ unsigned int ptcr, pdcr;
+ slave = slave - 1;
+ master = master - 1;
+
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN |
+ MXC_AUDMUX_V2_PTCR_TFSDIR |
+ MXC_AUDMUX_V2_PTCR_TFSEL(master) |
+ MXC_AUDMUX_V2_PTCR_TCLKDIR |
+ MXC_AUDMUX_V2_PTCR_TCSEL(master);
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(master);
+ mxc_audmux_v2_configure_port(slave, ptcr, pdcr);
+
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN;
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(slave);
+ mxc_audmux_v2_configure_port(master, ptcr, pdcr);
+
+ return 0;
+}
+
+/*
+ * This function will register the snd_soc_pcm_link drivers.
+ */
+static int __devinit imx_wm8962_probe(struct platform_device *pdev)
+{
+
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ struct imx_priv *priv = &card_priv;
+ int ret = 0;
+
+ priv->pdev = pdev;
+
+ imx_audmux_config(plat->src_port, plat->ext_port);
+
+ if (plat->init && plat->init()) {
+ ret = -EINVAL;
+ return ret;
+ }
+
+ priv->sysclk = plat->sysclk;
+
+ priv->first_stream = NULL;
+ priv->second_stream = NULL;
+
+ return ret;
+}
+
+static int __devexit imx_wm8962_remove(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ struct imx_priv *priv = &card_priv;
+
+ if (plat->finit)
+ plat->finit();
+
+ if (priv->hp_irq)
+ free_irq(priv->hp_irq, priv);
+ if (priv->amic_irq)
+ free_irq(priv->amic_irq, priv);
+
+ return 0;
+}
+
+static struct platform_driver imx_wm8962_driver = {
+ .probe = imx_wm8962_probe,
+ .remove = imx_wm8962_remove,
+ .driver = {
+ .name = "imx-wm8962",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *imx_snd_device;
+
+static int __init imx_asoc_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_wm8962_driver);
+ if (ret < 0)
+ goto exit;
+
+ if (machine_is_mx6q_sabresd())
+ imx_dai[0].codec_name = "wm8962.0-001a";
+ else if (machine_is_mx6sl_arm2() | machine_is_mx6sl_evk())
+ imx_dai[0].codec_name = "wm8962.1-001a";
+
+ imx_snd_device = platform_device_alloc("soc-audio", 5);
+ if (!imx_snd_device)
+ goto err_device_alloc;
+
+ platform_set_drvdata(imx_snd_device, &snd_soc_card_imx);
+
+ ret = platform_device_add(imx_snd_device);
+
+ if (0 == ret)
+ goto exit;
+
+ platform_device_put(imx_snd_device);
+
+err_device_alloc:
+ platform_driver_unregister(&imx_wm8962_driver);
+exit:
+ return ret;
+}
+
+static void __exit imx_asoc_exit(void)
+{
+ platform_driver_unregister(&imx_wm8962_driver);
+ platform_device_unregister(imx_snd_device);
+}
+
+module_init(imx_asoc_init);
+module_exit(imx_asoc_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC imx wm8962");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/mx27vis-aic32x4.c b/sound/soc/imx/mx27vis-aic32x4.c
new file mode 100644
index 00000000..054110b9
--- /dev/null
+++ b/sound/soc/imx/mx27vis-aic32x4.c
@@ -0,0 +1,137 @@
+/*
+ * mx27vis-aic32x4.c
+ *
+ * Copyright 2011 Vista Silicon S.L.
+ *
+ * Author: Javier Martin <javier.martin@vista-silicon.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 of the License, or (at your
+ * option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
+#include <mach/audmux.h>
+
+#include "../codecs/tlv320aic32x4.h"
+#include "imx-ssi.h"
+
+static int mx27vis_aic32x4_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+ u32 dai_format;
+
+ dai_format = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ /* set codec DAI configuration */
+ snd_soc_dai_set_fmt(codec_dai, dai_format);
+
+ /* set cpu DAI configuration */
+ snd_soc_dai_set_fmt(cpu_dai, dai_format);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ 25000000, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ pr_err("%s: failed setting codec sysclk\n", __func__);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret) {
+ pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops mx27vis_aic32x4_snd_ops = {
+ .hw_params = mx27vis_aic32x4_hw_params,
+};
+
+static struct snd_soc_dai_link mx27vis_aic32x4_dai = {
+ .name = "tlv320aic32x4",
+ .stream_name = "TLV320AIC32X4",
+ .codec_dai_name = "tlv320aic32x4-hifi",
+ .platform_name = "imx-pcm-audio.0",
+ .codec_name = "tlv320aic32x4.0-0018",
+ .cpu_dai_name = "imx-ssi.0",
+ .ops = &mx27vis_aic32x4_snd_ops,
+};
+
+static struct snd_soc_card mx27vis_aic32x4 = {
+ .name = "visstrim_m10-audio",
+ .dai_link = &mx27vis_aic32x4_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *mx27vis_aic32x4_snd_device;
+
+static int __init mx27vis_aic32x4_init(void)
+{
+ int ret;
+
+ mx27vis_aic32x4_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!mx27vis_aic32x4_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(mx27vis_aic32x4_snd_device, &mx27vis_aic32x4);
+ ret = platform_device_add(mx27vis_aic32x4_snd_device);
+
+ if (ret) {
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+ platform_device_put(mx27vis_aic32x4_snd_device);
+ }
+
+ /* Connect SSI0 as clock slave to SSI1 external pins */
+ mxc_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0,
+ MXC_AUDMUX_V1_PCR_SYN |
+ MXC_AUDMUX_V1_PCR_TFSDIR |
+ MXC_AUDMUX_V1_PCR_TCLKDIR |
+ MXC_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) |
+ MXC_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1)
+ );
+ mxc_audmux_v1_configure_port(MX27_AUDMUX_PPCR1_SSI_PINS_1,
+ MXC_AUDMUX_V1_PCR_SYN |
+ MXC_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0)
+ );
+
+ return ret;
+}
+
+static void __exit mx27vis_aic32x4_exit(void)
+{
+ platform_device_unregister(mx27vis_aic32x4_snd_device);
+}
+
+module_init(mx27vis_aic32x4_init);
+module_exit(mx27vis_aic32x4_exit);
+
+MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>");
+MODULE_DESCRIPTION("ALSA SoC AIC32X4 mx27 visstrim");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/phycore-ac97.c b/sound/soc/imx/phycore-ac97.c
new file mode 100644
index 00000000..a7deb5cb
--- /dev/null
+++ b/sound/soc/imx/phycore-ac97.c
@@ -0,0 +1,99 @@
+/*
+ * phycore-ac97.c -- SoC audio for imx_phycore in AC97 mode
+ *
+ * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <asm/mach-types.h>
+
+static struct snd_soc_card imx_phycore;
+
+static struct snd_soc_ops imx_phycore_hifi_ops = {
+};
+
+static struct snd_soc_dai_link imx_phycore_dai_ac97[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "wm9712-hifi",
+ .codec_name = "wm9712-codec",
+ .cpu_dai_name = "imx-ssi.0",
+ .platform_name = "imx-fiq-pcm-audio.0",
+ .ops = &imx_phycore_hifi_ops,
+ },
+};
+
+static struct snd_soc_card imx_phycore = {
+ .name = "PhyCORE-ac97-audio",
+ .dai_link = imx_phycore_dai_ac97,
+ .num_links = ARRAY_SIZE(imx_phycore_dai_ac97),
+};
+
+static struct platform_device *imx_phycore_snd_ac97_device;
+static struct platform_device *imx_phycore_snd_device;
+
+static int __init imx_phycore_init(void)
+{
+ int ret;
+
+ if (!machine_is_pcm043() && !machine_is_pca100())
+ /* return happy. We might run on a totally different machine */
+ return 0;
+
+ imx_phycore_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+ if (!imx_phycore_snd_ac97_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(imx_phycore_snd_ac97_device, &imx_phycore);
+ ret = platform_device_add(imx_phycore_snd_ac97_device);
+ if (ret)
+ goto fail1;
+
+ imx_phycore_snd_device = platform_device_alloc("wm9712-codec", -1);
+ if (!imx_phycore_snd_device) {
+ ret = -ENOMEM;
+ goto fail2;
+ }
+ ret = platform_device_add(imx_phycore_snd_device);
+
+ if (ret) {
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+ goto fail3;
+ }
+
+ return 0;
+
+fail3:
+ platform_device_put(imx_phycore_snd_device);
+fail2:
+ platform_device_del(imx_phycore_snd_ac97_device);
+fail1:
+ platform_device_put(imx_phycore_snd_ac97_device);
+ return ret;
+}
+
+static void __exit imx_phycore_exit(void)
+{
+ platform_device_unregister(imx_phycore_snd_device);
+ platform_device_unregister(imx_phycore_snd_ac97_device);
+}
+
+late_initcall(imx_phycore_init);
+module_exit(imx_phycore_exit);
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("PhyCORE ALSA SoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/wm1133-ev1.c b/sound/soc/imx/wm1133-ev1.c
new file mode 100644
index 00000000..75b4c727
--- /dev/null
+++ b/sound/soc/imx/wm1133-ev1.c
@@ -0,0 +1,303 @@
+/*
+ * wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS
+ *
+ * Copyright (c) 2010 Wolfson Microelectronics plc
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * Based on an earlier driver for the same hardware by Liam Girdwood.
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/audmux.h>
+
+#include "imx-ssi.h"
+#include "../codecs/wm8350.h"
+
+/* There is a silicon mic on the board optionally connected via a solder pad
+ * SP1. Define this to enable it.
+ */
+#undef USE_SIMIC
+
+struct _wm8350_audio {
+ unsigned int channels;
+ snd_pcm_format_t format;
+ unsigned int rate;
+ unsigned int sysclk;
+ unsigned int bclkdiv;
+ unsigned int clkdiv;
+ unsigned int lr_rate;
+};
+
+/* in order of power consumption per rate (lowest first) */
+static const struct _wm8350_audio wm8350_audio[] = {
+ /* 16bit mono modes */
+ {1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000 >> 1,
+ WM8350_BCLK_DIV_48, WM8350_DACDIV_3, 16,},
+
+ /* 16 bit stereo modes */
+ {2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000,
+ WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000,
+ WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000,
+ WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600,
+ WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600,
+ WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+
+ /* 24bit stereo modes */
+ {2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000,
+ WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+ {2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000,
+ WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+ {2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600,
+ WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+ {2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200,
+ WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+};
+
+static int wm1133_ev1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int i, found = 0;
+ snd_pcm_format_t format = params_format(params);
+ unsigned int rate = params_rate(params);
+ unsigned int channels = params_channels(params);
+ u32 dai_format;
+
+ /* find the correct audio parameters */
+ for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) {
+ if (rate == wm8350_audio[i].rate &&
+ format == wm8350_audio[i].format &&
+ channels == wm8350_audio[i].channels) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ return -EINVAL;
+
+ /* codec FLL input is 14.75 MHz from MCLK */
+ snd_soc_dai_set_pll(codec_dai, 0, 0, 14750000, wm8350_audio[i].sysclk);
+
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ /* set codec DAI configuration */
+ snd_soc_dai_set_fmt(codec_dai, dai_format);
+
+ /* set cpu DAI configuration */
+ snd_soc_dai_set_fmt(cpu_dai, dai_format);
+
+ /* TODO: The SSI driver should figure this out for us */
+ switch (channels) {
+ case 2:
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0);
+ break;
+ case 1:
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffe, 0xffffffe, 1, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set MCLK as the codec system clock for DAC and ADC */
+ snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_MCLK,
+ wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN);
+
+ /* set codec BCLK division for sample rate */
+ snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV,
+ wm8350_audio[i].bclkdiv);
+
+ /* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */
+ snd_soc_dai_set_clkdiv(codec_dai,
+ WM8350_DACLR_CLKDIV, wm8350_audio[i].lr_rate);
+ snd_soc_dai_set_clkdiv(codec_dai,
+ WM8350_ADCLR_CLKDIV, wm8350_audio[i].lr_rate);
+
+ /* now configure DAC and ADC clocks */
+ snd_soc_dai_set_clkdiv(codec_dai,
+ WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv);
+
+ snd_soc_dai_set_clkdiv(codec_dai,
+ WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv);
+
+ return 0;
+}
+
+static struct snd_soc_ops wm1133_ev1_ops = {
+ .hw_params = wm1133_ev1_hw_params,
+};
+
+static const struct snd_soc_dapm_widget wm1133_ev1_widgets[] = {
+#ifdef USE_SIMIC
+ SND_SOC_DAPM_MIC("SiMIC", NULL),
+#endif
+ SND_SOC_DAPM_MIC("Mic1 Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic2 Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+/* imx32ads soc_card audio map */
+static const struct snd_soc_dapm_route wm1133_ev1_map[] = {
+
+#ifdef USE_SIMIC
+ /* SiMIC --> IN1LN (with automatic bias) via SP1 */
+ { "IN1LN", NULL, "Mic Bias" },
+ { "Mic Bias", NULL, "SiMIC" },
+#endif
+
+ /* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */
+ { "IN1LN", NULL, "Mic Bias" },
+ { "IN1LP", NULL, "Mic1 Jack" },
+ { "Mic Bias", NULL, "Mic1 Jack" },
+
+ /* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */
+ { "IN1RN", NULL, "Mic Bias" },
+ { "IN1RP", NULL, "Mic2 Jack" },
+ { "Mic Bias", NULL, "Mic2 Jack" },
+
+ /* Line in Jack --> AUX (L+R) */
+ { "IN3R", NULL, "Line In Jack" },
+ { "IN3L", NULL, "Line In Jack" },
+
+ /* Out1 --> Headphone Jack */
+ { "Headphone Jack", NULL, "OUT1R" },
+ { "Headphone Jack", NULL, "OUT1L" },
+
+ /* Out1 --> Line Out Jack */
+ { "Line Out Jack", NULL, "OUT2R" },
+ { "Line Out Jack", NULL, "OUT2L" },
+};
+
+static struct snd_soc_jack hp_jack;
+
+static struct snd_soc_jack_pin hp_jack_pins[] = {
+ { .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE },
+};
+
+static struct snd_soc_jack mic_jack;
+
+static struct snd_soc_jack_pin mic_jack_pins[] = {
+ { .pin = "Mic1 Jack", .mask = SND_JACK_MICROPHONE },
+ { .pin = "Mic2 Jack", .mask = SND_JACK_MICROPHONE },
+};
+
+static int wm1133_ev1_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, wm1133_ev1_widgets,
+ ARRAY_SIZE(wm1133_ev1_widgets));
+
+ snd_soc_dapm_add_routes(dapm, wm1133_ev1_map,
+ ARRAY_SIZE(wm1133_ev1_map));
+
+ /* Headphone jack detection */
+ snd_soc_jack_new(codec, "Headphone", SND_JACK_HEADPHONE, &hp_jack);
+ snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins),
+ hp_jack_pins);
+ wm8350_hp_jack_detect(codec, WM8350_JDR, &hp_jack, SND_JACK_HEADPHONE);
+
+ /* Microphone jack detection */
+ snd_soc_jack_new(codec, "Microphone",
+ SND_JACK_MICROPHONE | SND_JACK_BTN_0, &mic_jack);
+ snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins),
+ mic_jack_pins);
+ wm8350_mic_jack_detect(codec, &mic_jack, SND_JACK_MICROPHONE,
+ SND_JACK_BTN_0);
+
+ snd_soc_dapm_force_enable_pin(dapm, "Mic Bias");
+
+ return 0;
+}
+
+
+static struct snd_soc_dai_link wm1133_ev1_dai = {
+ .name = "WM1133-EV1",
+ .stream_name = "Audio",
+ .cpu_dai_name = "imx-ssi.0",
+ .codec_dai_name = "wm8350-hifi",
+ .platform_name = "imx-fiq-pcm-audio.0",
+ .codec_name = "wm8350-codec.0-0x1a",
+ .init = wm1133_ev1_init,
+ .ops = &wm1133_ev1_ops,
+ .symmetric_rates = 1,
+};
+
+static struct snd_soc_card wm1133_ev1 = {
+ .name = "WM1133-EV1",
+ .dai_link = &wm1133_ev1_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *wm1133_ev1_snd_device;
+
+static int __init wm1133_ev1_audio_init(void)
+{
+ int ret;
+ unsigned int ptcr, pdcr;
+
+ /* SSI0 mastered by port 5 */
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN |
+ MXC_AUDMUX_V2_PTCR_TFSDIR |
+ MXC_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT5_SSI_PINS_5) |
+ MXC_AUDMUX_V2_PTCR_TCLKDIR |
+ MXC_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT5_SSI_PINS_5);
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT5_SSI_PINS_5);
+ mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, ptcr, pdcr);
+
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN;
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0);
+ mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT5_SSI_PINS_5, ptcr, pdcr);
+
+ wm1133_ev1_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!wm1133_ev1_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(wm1133_ev1_snd_device, &wm1133_ev1);
+ ret = platform_device_add(wm1133_ev1_snd_device);
+
+ if (ret)
+ platform_device_put(wm1133_ev1_snd_device);
+
+ return ret;
+}
+module_init(wm1133_ev1_audio_init);
+
+static void __exit wm1133_ev1_audio_exit(void)
+{
+ platform_device_unregister(wm1133_ev1_snd_device);
+}
+module_exit(wm1133_ev1_audio_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("Audio for WM1133-EV1 on i.MX31ADS");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig
new file mode 100644
index 00000000..5351cba6
--- /dev/null
+++ b/sound/soc/jz4740/Kconfig
@@ -0,0 +1,23 @@
+config SND_JZ4740_SOC
+ tristate "SoC Audio for Ingenic JZ4740 SoC"
+ depends on MACH_JZ4740 && SND_SOC
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the JZ4740 I2S interface. You will also need to select the audio
+ interfaces to support below.
+
+config SND_JZ4740_SOC_I2S
+ depends on SND_JZ4740_SOC
+ tristate "SoC Audio (I2S protocol) for Ingenic JZ4740 SoC"
+ help
+ Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740
+ based boards.
+
+config SND_JZ4740_SOC_QI_LB60
+ tristate "SoC Audio support for Qi LB60"
+ depends on SND_JZ4740_SOC && JZ4740_QI_LB60
+ select SND_JZ4740_SOC_I2S
+ select SND_SOC_JZ4740_CODEC
+ help
+ Say Y if you want to add support for ASoC audio on the Qi LB60 board
+ a.k.a Qi Ben NanoNote.
diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile
new file mode 100644
index 00000000..be873c1b
--- /dev/null
+++ b/sound/soc/jz4740/Makefile
@@ -0,0 +1,13 @@
+#
+# Jz4740 Platform Support
+#
+snd-soc-jz4740-objs := jz4740-pcm.o
+snd-soc-jz4740-i2s-objs := jz4740-i2s.o
+
+obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o
+obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o
+
+# Jz4740 Machine Support
+snd-soc-qi-lb60-objs := qi_lb60.o
+
+obj-$(CONFIG_SND_JZ4740_SOC_QI_LB60) += snd-soc-qi-lb60.o
diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c
new file mode 100644
index 00000000..cd22a54b
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-i2s.c
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "jz4740-i2s.h"
+#include "jz4740-pcm.h"
+
+#define JZ_REG_AIC_CONF 0x00
+#define JZ_REG_AIC_CTRL 0x04
+#define JZ_REG_AIC_I2S_FMT 0x10
+#define JZ_REG_AIC_FIFO_STATUS 0x14
+#define JZ_REG_AIC_I2S_STATUS 0x1c
+#define JZ_REG_AIC_CLK_DIV 0x30
+#define JZ_REG_AIC_FIFO 0x34
+
+#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_MASK (0xf << 12)
+#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_MASK (0xf << 8)
+#define JZ_AIC_CONF_OVERFLOW_PLAY_LAST BIT(6)
+#define JZ_AIC_CONF_INTERNAL_CODEC BIT(5)
+#define JZ_AIC_CONF_I2S BIT(4)
+#define JZ_AIC_CONF_RESET BIT(3)
+#define JZ_AIC_CONF_BIT_CLK_MASTER BIT(2)
+#define JZ_AIC_CONF_SYNC_CLK_MASTER BIT(1)
+#define JZ_AIC_CONF_ENABLE BIT(0)
+
+#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12
+#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8
+
+#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19)
+#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16)
+#define JZ_AIC_CTRL_ENABLE_RX_DMA BIT(15)
+#define JZ_AIC_CTRL_ENABLE_TX_DMA BIT(14)
+#define JZ_AIC_CTRL_MONO_TO_STEREO BIT(11)
+#define JZ_AIC_CTRL_SWITCH_ENDIANNESS BIT(10)
+#define JZ_AIC_CTRL_SIGNED_TO_UNSIGNED BIT(9)
+#define JZ_AIC_CTRL_FLUSH BIT(8)
+#define JZ_AIC_CTRL_ENABLE_ROR_INT BIT(6)
+#define JZ_AIC_CTRL_ENABLE_TUR_INT BIT(5)
+#define JZ_AIC_CTRL_ENABLE_RFS_INT BIT(4)
+#define JZ_AIC_CTRL_ENABLE_TFS_INT BIT(3)
+#define JZ_AIC_CTRL_ENABLE_LOOPBACK BIT(2)
+#define JZ_AIC_CTRL_ENABLE_PLAYBACK BIT(1)
+#define JZ_AIC_CTRL_ENABLE_CAPTURE BIT(0)
+
+#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET 19
+#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET 16
+
+#define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12)
+#define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4)
+#define JZ_AIC_I2S_FMT_MSB BIT(0)
+
+#define JZ_AIC_I2S_STATUS_BUSY BIT(2)
+
+#define JZ_AIC_CLK_DIV_MASK 0xf
+
+struct jz4740_i2s {
+ struct resource *mem;
+ void __iomem *base;
+ dma_addr_t phys_base;
+
+ struct clk *clk_aic;
+ struct clk *clk_i2s;
+
+ struct jz4740_pcm_config pcm_config_playback;
+ struct jz4740_pcm_config pcm_config_capture;
+};
+
+static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s,
+ unsigned int reg)
+{
+ return readl(i2s->base + reg);
+}
+
+static inline void jz4740_i2s_write(const struct jz4740_i2s *i2s,
+ unsigned int reg, uint32_t value)
+{
+ writel(value, i2s->base + reg);
+}
+
+static int jz4740_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ uint32_t conf, ctrl;
+
+ if (dai->active)
+ return 0;
+
+ ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+ ctrl |= JZ_AIC_CTRL_FLUSH;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+ clk_enable(i2s->clk_i2s);
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf |= JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ return 0;
+}
+
+static void jz4740_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ uint32_t conf;
+
+ if (dai->active)
+ return;
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf &= ~JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ clk_disable(i2s->clk_i2s);
+}
+
+static int jz4740_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ uint32_t ctrl;
+ uint32_t mask;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ mask = JZ_AIC_CTRL_ENABLE_PLAYBACK | JZ_AIC_CTRL_ENABLE_TX_DMA;
+ else
+ mask = JZ_AIC_CTRL_ENABLE_CAPTURE | JZ_AIC_CTRL_ENABLE_RX_DMA;
+
+ ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ctrl |= mask;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ctrl &= ~mask;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+ return 0;
+}
+
+static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ uint32_t format = 0;
+ uint32_t conf;
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+
+ conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ conf |= JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER;
+ format |= JZ_AIC_I2S_FMT_ENABLE_SYS_CLK;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ conf |= JZ_AIC_CONF_SYNC_CLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ conf |= JZ_AIC_CONF_BIT_CLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_MSB:
+ format |= JZ_AIC_I2S_FMT_MSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+ jz4740_i2s_write(i2s, JZ_REG_AIC_I2S_FMT, format);
+
+ return 0;
+}
+
+static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ enum jz4740_dma_width dma_width;
+ struct jz4740_pcm_config *pcm_config;
+ unsigned int sample_size;
+ uint32_t ctrl;
+
+ ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ sample_size = 0;
+ dma_width = JZ4740_DMA_WIDTH_8BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S16:
+ sample_size = 1;
+ dma_width = JZ4740_DMA_WIDTH_16BIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ctrl &= ~JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK;
+ ctrl |= sample_size << JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET;
+ if (params_channels(params) == 1)
+ ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO;
+ else
+ ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO;
+
+ pcm_config = &i2s->pcm_config_playback;
+ pcm_config->dma_config.dst_width = dma_width;
+
+ } else {
+ ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK;
+ ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET;
+
+ pcm_config = &i2s->pcm_config_capture;
+ pcm_config->dma_config.src_width = dma_width;
+ }
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+ snd_soc_dai_set_dma_data(dai, substream, pcm_config);
+
+ return 0;
+}
+
+static int jz4740_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ struct clk *parent;
+ int ret = 0;
+
+ switch (clk_id) {
+ case JZ4740_I2S_CLKSRC_EXT:
+ parent = clk_get(NULL, "ext");
+ clk_set_parent(i2s->clk_i2s, parent);
+ break;
+ case JZ4740_I2S_CLKSRC_PLL:
+ parent = clk_get(NULL, "pll half");
+ clk_set_parent(i2s->clk_i2s, parent);
+ ret = clk_set_rate(i2s->clk_i2s, freq);
+ break;
+ default:
+ return -EINVAL;
+ }
+ clk_put(parent);
+
+ return ret;
+}
+
+static int jz4740_i2s_suspend(struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ uint32_t conf;
+
+ if (dai->active) {
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf &= ~JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ clk_disable(i2s->clk_i2s);
+ }
+
+ clk_disable(i2s->clk_aic);
+
+ return 0;
+}
+
+static int jz4740_i2s_resume(struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ uint32_t conf;
+
+ clk_enable(i2s->clk_aic);
+
+ if (dai->active) {
+ clk_enable(i2s->clk_i2s);
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf |= JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+ }
+
+ return 0;
+}
+
+static void jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s)
+{
+ struct jz4740_dma_config *dma_config;
+
+ /* Playback */
+ dma_config = &i2s->pcm_config_playback.dma_config;
+ dma_config->src_width = JZ4740_DMA_WIDTH_32BIT,
+ dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE;
+ dma_config->request_type = JZ4740_DMA_TYPE_AIC_TRANSMIT;
+ dma_config->flags = JZ4740_DMA_SRC_AUTOINC;
+ dma_config->mode = JZ4740_DMA_MODE_SINGLE;
+ i2s->pcm_config_playback.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO;
+
+ /* Capture */
+ dma_config = &i2s->pcm_config_capture.dma_config;
+ dma_config->dst_width = JZ4740_DMA_WIDTH_32BIT,
+ dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE;
+ dma_config->request_type = JZ4740_DMA_TYPE_AIC_RECEIVE;
+ dma_config->flags = JZ4740_DMA_DST_AUTOINC;
+ dma_config->mode = JZ4740_DMA_MODE_SINGLE;
+ i2s->pcm_config_capture.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO;
+}
+
+static int jz4740_i2s_dai_probe(struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ uint32_t conf;
+
+ clk_enable(i2s->clk_aic);
+
+ jz4740_i2c_init_pcm_config(i2s);
+
+ conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) |
+ (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) |
+ JZ_AIC_CONF_OVERFLOW_PLAY_LAST |
+ JZ_AIC_CONF_I2S |
+ JZ_AIC_CONF_INTERNAL_CODEC;
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET);
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ return 0;
+}
+
+static int jz4740_i2s_dai_remove(struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ clk_disable(i2s->clk_aic);
+ return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_i2s_dai_ops = {
+ .startup = jz4740_i2s_startup,
+ .shutdown = jz4740_i2s_shutdown,
+ .trigger = jz4740_i2s_trigger,
+ .hw_params = jz4740_i2s_hw_params,
+ .set_fmt = jz4740_i2s_set_fmt,
+ .set_sysclk = jz4740_i2s_set_sysclk,
+};
+
+#define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE)
+
+static struct snd_soc_dai_driver jz4740_i2s_dai = {
+ .probe = jz4740_i2s_dai_probe,
+ .remove = jz4740_i2s_dai_remove,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = JZ4740_I2S_FMTS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = JZ4740_I2S_FMTS,
+ },
+ .symmetric_rates = 1,
+ .ops = &jz4740_i2s_dai_ops,
+ .suspend = jz4740_i2s_suspend,
+ .resume = jz4740_i2s_resume,
+};
+
+static int __devinit jz4740_i2s_dev_probe(struct platform_device *pdev)
+{
+ struct jz4740_i2s *i2s;
+ int ret;
+
+ i2s = kzalloc(sizeof(*i2s), GFP_KERNEL);
+
+ if (!i2s)
+ return -ENOMEM;
+
+ i2s->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!i2s->mem) {
+ ret = -ENOENT;
+ goto err_free;
+ }
+
+ i2s->mem = request_mem_region(i2s->mem->start, resource_size(i2s->mem),
+ pdev->name);
+ if (!i2s->mem) {
+ ret = -EBUSY;
+ goto err_free;
+ }
+
+ i2s->base = ioremap_nocache(i2s->mem->start, resource_size(i2s->mem));
+ if (!i2s->base) {
+ ret = -EBUSY;
+ goto err_release_mem_region;
+ }
+
+ i2s->phys_base = i2s->mem->start;
+
+ i2s->clk_aic = clk_get(&pdev->dev, "aic");
+ if (IS_ERR(i2s->clk_aic)) {
+ ret = PTR_ERR(i2s->clk_aic);
+ goto err_iounmap;
+ }
+
+ i2s->clk_i2s = clk_get(&pdev->dev, "i2s");
+ if (IS_ERR(i2s->clk_i2s)) {
+ ret = PTR_ERR(i2s->clk_i2s);
+ goto err_clk_put_aic;
+ }
+
+ platform_set_drvdata(pdev, i2s);
+ ret = snd_soc_register_dai(&pdev->dev, &jz4740_i2s_dai);
+
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register DAI\n");
+ goto err_clk_put_i2s;
+ }
+
+ return 0;
+
+err_clk_put_i2s:
+ clk_put(i2s->clk_i2s);
+err_clk_put_aic:
+ clk_put(i2s->clk_aic);
+err_iounmap:
+ iounmap(i2s->base);
+err_release_mem_region:
+ release_mem_region(i2s->mem->start, resource_size(i2s->mem));
+err_free:
+ kfree(i2s);
+
+ return ret;
+}
+
+static int __devexit jz4740_i2s_dev_remove(struct platform_device *pdev)
+{
+ struct jz4740_i2s *i2s = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ clk_put(i2s->clk_i2s);
+ clk_put(i2s->clk_aic);
+
+ iounmap(i2s->base);
+ release_mem_region(i2s->mem->start, resource_size(i2s->mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(i2s);
+
+ return 0;
+}
+
+static struct platform_driver jz4740_i2s_driver = {
+ .probe = jz4740_i2s_dev_probe,
+ .remove = __devexit_p(jz4740_i2s_dev_remove),
+ .driver = {
+ .name = "jz4740-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_i2s_init(void)
+{
+ return platform_driver_register(&jz4740_i2s_driver);
+}
+module_init(jz4740_i2s_init);
+
+static void __exit jz4740_i2s_exit(void)
+{
+ platform_driver_unregister(&jz4740_i2s_driver);
+}
+module_exit(jz4740_i2s_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen, <lars@metafoo.de>");
+MODULE_DESCRIPTION("Ingenic JZ4740 SoC I2S driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-i2s");
diff --git a/sound/soc/jz4740/jz4740-i2s.h b/sound/soc/jz4740/jz4740-i2s.h
new file mode 100644
index 00000000..5e49339d
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-i2s.h
@@ -0,0 +1,16 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _JZ4740_I2S_H
+#define _JZ4740_I2S_H
+
+/* I2S clock source */
+#define JZ4740_I2S_CLKSRC_EXT 0
+#define JZ4740_I2S_CLKSRC_PLL 1
+
+#define JZ4740_I2S_BIT_CLK 0
+
+#endif
diff --git a/sound/soc/jz4740/jz4740-pcm.c b/sound/soc/jz4740/jz4740-pcm.c
new file mode 100644
index 00000000..fb1483f7
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-pcm.c
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mach-jz4740/dma.h>
+#include "jz4740-pcm.h"
+
+struct jz4740_runtime_data {
+ unsigned long dma_period;
+ dma_addr_t dma_start;
+ dma_addr_t dma_pos;
+ dma_addr_t dma_end;
+
+ struct jz4740_dma_chan *dma;
+
+ dma_addr_t fifo_addr;
+};
+
+/* identify hardware playback capabilities */
+static const struct snd_pcm_hardware jz4740_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .period_bytes_min = 16,
+ .period_bytes_max = 2 * PAGE_SIZE,
+ .periods_min = 2,
+ .periods_max = 128,
+ .buffer_bytes_max = 128 * 2 * PAGE_SIZE,
+ .fifo_size = 32,
+};
+
+static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd,
+ struct snd_pcm_substream *substream)
+{
+ unsigned long count;
+
+ if (prtd->dma_pos == prtd->dma_end)
+ prtd->dma_pos = prtd->dma_start;
+
+ if (prtd->dma_pos + prtd->dma_period > prtd->dma_end)
+ count = prtd->dma_end - prtd->dma_pos;
+ else
+ count = prtd->dma_period;
+
+ jz4740_dma_disable(prtd->dma);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos);
+ jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr);
+ } else {
+ jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr);
+ jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos);
+ }
+
+ jz4740_dma_set_transfer_count(prtd->dma, count);
+
+ prtd->dma_pos += count;
+
+ jz4740_dma_enable(prtd->dma);
+}
+
+static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err,
+ void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+
+ snd_pcm_period_elapsed(substream);
+
+ jz4740_pcm_start_transfer(prtd, substream);
+}
+
+static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct jz4740_pcm_config *config;
+
+ config = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ if (!config)
+ return 0;
+
+ if (!prtd->dma) {
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ prtd->dma = jz4740_dma_request(substream, "PCM Capture");
+ else
+ prtd->dma = jz4740_dma_request(substream, "PCM Playback");
+ }
+
+ if (!prtd->dma)
+ return -EBUSY;
+
+ jz4740_dma_configure(prtd->dma, &config->dma_config);
+ prtd->fifo_addr = config->fifo_addr;
+
+ jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done);
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ prtd->dma_period = params_period_bytes(params);
+ prtd->dma_start = runtime->dma_addr;
+ prtd->dma_pos = prtd->dma_start;
+ prtd->dma_end = prtd->dma_start + runtime->dma_bytes;
+
+ return 0;
+}
+
+static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct jz4740_runtime_data *prtd = substream->runtime->private_data;
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ if (prtd->dma) {
+ jz4740_dma_free(prtd->dma);
+ prtd->dma = NULL;
+ }
+
+ return 0;
+}
+
+static int jz4740_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct jz4740_runtime_data *prtd = substream->runtime->private_data;
+
+ if (!prtd->dma)
+ return -EBUSY;
+
+ prtd->dma_pos = prtd->dma_start;
+
+ return 0;
+}
+
+static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ jz4740_pcm_start_transfer(prtd, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ jz4740_dma_disable(prtd->dma);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+ unsigned long byte_offset;
+ snd_pcm_uframes_t offset;
+ struct jz4740_dma_chan *dma = prtd->dma;
+
+ /* prtd->dma_pos points to the end of the current transfer. So by
+ * subtracting prdt->dma_start we get the offset to the end of the
+ * current period in bytes. By subtracting the residue of the transfer
+ * we get the current offset in bytes. */
+ byte_offset = prtd->dma_pos - prtd->dma_start;
+ byte_offset -= jz4740_dma_get_residue(dma);
+
+ offset = bytes_to_frames(runtime, byte_offset);
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int jz4740_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd;
+
+ prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware);
+
+ runtime->private_data = prtd;
+
+ return 0;
+}
+
+static int jz4740_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static int jz4740_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ return remap_pfn_range(vma, vma->vm_start,
+ substream->dma_buffer.addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+static struct snd_pcm_ops jz4740_pcm_ops = {
+ .open = jz4740_pcm_open,
+ .close = jz4740_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = jz4740_pcm_hw_params,
+ .hw_free = jz4740_pcm_hw_free,
+ .prepare = jz4740_pcm_prepare,
+ .trigger = jz4740_pcm_trigger,
+ .pointer = jz4740_pcm_pointer,
+ .mmap = jz4740_pcm_mmap,
+};
+
+static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = jz4740_pcm_hardware.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+
+ buf->area = dma_alloc_noncoherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+
+ return 0;
+}
+
+static void jz4740_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area,
+ buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32);
+
+int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &jz4740_pcm_dmamask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ if (dai->driver->playback.channels_min) {
+ ret = jz4740_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto err;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = jz4740_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto err;
+ }
+
+err:
+ return ret;
+}
+
+static struct snd_soc_platform_driver jz4740_soc_platform = {
+ .ops = &jz4740_pcm_ops,
+ .pcm_new = jz4740_pcm_new,
+ .pcm_free = jz4740_pcm_free,
+};
+
+static int __devinit jz4740_pcm_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &jz4740_soc_platform);
+}
+
+static int __devexit jz4740_pcm_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver jz4740_pcm_driver = {
+ .probe = jz4740_pcm_probe,
+ .remove = __devexit_p(jz4740_pcm_remove),
+ .driver = {
+ .name = "jz4740-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_soc_platform_init(void)
+{
+ return platform_driver_register(&jz4740_pcm_driver);
+}
+module_init(jz4740_soc_platform_init);
+
+static void __exit jz4740_soc_platform_exit(void)
+{
+ return platform_driver_unregister(&jz4740_pcm_driver);
+}
+module_exit(jz4740_soc_platform_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/jz4740/jz4740-pcm.h b/sound/soc/jz4740/jz4740-pcm.h
new file mode 100644
index 00000000..1220cbb4
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-pcm.h
@@ -0,0 +1,20 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _JZ4740_PCM_H
+#define _JZ4740_PCM_H
+
+#include <linux/dma-mapping.h>
+#include <asm/mach-jz4740/dma.h>
+
+
+struct jz4740_pcm_config {
+ struct jz4740_dma_config dma_config;
+ phys_addr_t fifo_addr;
+};
+
+#endif
diff --git a/sound/soc/jz4740/qi_lb60.c b/sound/soc/jz4740/qi_lb60.c
new file mode 100644
index 00000000..c5fc339f
--- /dev/null
+++ b/sound/soc/jz4740/qi_lb60.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <linux/gpio.h>
+
+#define QI_LB60_SND_GPIO JZ_GPIO_PORTB(29)
+#define QI_LB60_AMP_GPIO JZ_GPIO_PORTD(4)
+
+static int qi_lb60_spk_event(struct snd_soc_dapm_widget *widget,
+ struct snd_kcontrol *ctrl, int event)
+{
+ int on = !SND_SOC_DAPM_EVENT_OFF(event);
+
+ gpio_set_value(QI_LB60_SND_GPIO, on);
+ gpio_set_value(QI_LB60_AMP_GPIO, on);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget qi_lb60_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", qi_lb60_spk_event),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route qi_lb60_routes[] = {
+ {"Mic", NULL, "MIC"},
+ {"Speaker", NULL, "LOUT"},
+ {"Speaker", NULL, "ROUT"},
+};
+
+#define QI_LB60_DAIFMT (SND_SOC_DAIFMT_I2S | \
+ SND_SOC_DAIFMT_NB_NF | \
+ SND_SOC_DAIFMT_CBM_CFM)
+
+static int qi_lb60_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ snd_soc_dapm_nc_pin(dapm, "LIN");
+ snd_soc_dapm_nc_pin(dapm, "RIN");
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, QI_LB60_DAIFMT);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cpu dai format: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_dai_link qi_lb60_dai = {
+ .name = "jz4740",
+ .stream_name = "jz4740",
+ .cpu_dai_name = "jz4740-i2s",
+ .platform_name = "jz4740-pcm-audio",
+ .codec_dai_name = "jz4740-hifi",
+ .codec_name = "jz4740-codec",
+ .init = qi_lb60_codec_init,
+};
+
+static struct snd_soc_card qi_lb60 = {
+ .name = "QI LB60",
+ .dai_link = &qi_lb60_dai,
+ .num_links = 1,
+
+ .dapm_widgets = qi_lb60_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(qi_lb60_widgets),
+ .dapm_routes = qi_lb60_routes,
+ .num_dapm_routes = ARRAY_SIZE(qi_lb60_routes),
+};
+
+static struct platform_device *qi_lb60_snd_device;
+
+static const struct gpio qi_lb60_gpios[] = {
+ { QI_LB60_SND_GPIO, GPIOF_OUT_INIT_LOW, "SND" },
+ { QI_LB60_AMP_GPIO, GPIOF_OUT_INIT_LOW, "AMP" },
+};
+
+static int __init qi_lb60_init(void)
+{
+ int ret;
+
+ qi_lb60_snd_device = platform_device_alloc("soc-audio", -1);
+
+ if (!qi_lb60_snd_device)
+ return -ENOMEM;
+
+ ret = gpio_request_array(qi_lb60_gpios, ARRAY_SIZE(qi_lb60_gpios));
+ if (ret) {
+ pr_err("qi_lb60 snd: Failed to request gpios: %d\n", ret);
+ goto err_device_put;
+ }
+
+ platform_set_drvdata(qi_lb60_snd_device, &qi_lb60);
+
+ ret = platform_device_add(qi_lb60_snd_device);
+ if (ret) {
+ pr_err("qi_lb60 snd: Failed to add snd soc device: %d\n", ret);
+ goto err_unset_pdata;
+ }
+
+ return 0;
+
+err_unset_pdata:
+ platform_set_drvdata(qi_lb60_snd_device, NULL);
+/*err_gpio_free_array:*/
+ gpio_free_array(qi_lb60_gpios, ARRAY_SIZE(qi_lb60_gpios));
+err_device_put:
+ platform_device_put(qi_lb60_snd_device);
+
+ return ret;
+}
+module_init(qi_lb60_init);
+
+static void __exit qi_lb60_exit(void)
+{
+ platform_device_unregister(qi_lb60_snd_device);
+ gpio_free_array(qi_lb60_gpios, ARRAY_SIZE(qi_lb60_gpios));
+}
+module_exit(qi_lb60_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/kirkwood/Kconfig b/sound/soc/kirkwood/Kconfig
new file mode 100644
index 00000000..8f49e165
--- /dev/null
+++ b/sound/soc/kirkwood/Kconfig
@@ -0,0 +1,29 @@
+config SND_KIRKWOOD_SOC
+ tristate "SoC Audio for the Marvell Kirkwood chip"
+ depends on ARCH_KIRKWOOD
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the Kirkwood I2S interface. You will also need to select the
+ audio interfaces to support below.
+
+config SND_KIRKWOOD_SOC_I2S
+ tristate
+
+config SND_KIRKWOOD_SOC_OPENRD
+ tristate "SoC Audio support for Kirkwood Openrd Client"
+ depends on SND_KIRKWOOD_SOC && (MACH_OPENRD_CLIENT || MACH_OPENRD_ULTIMATE)
+ select SND_KIRKWOOD_SOC_I2S
+ select SND_SOC_CS42L51
+ help
+ Say Y if you want to add support for SoC audio on
+ Openrd Client.
+
+config SND_KIRKWOOD_SOC_T5325
+ tristate "SoC Audio support for HP t5325"
+ depends on SND_KIRKWOOD_SOC && MACH_T5325
+ select SND_KIRKWOOD_SOC_I2S
+ select SND_SOC_ALC5623
+ help
+ Say Y if you want to add support for SoC audio on
+ the HP t5325 thin client.
+
diff --git a/sound/soc/kirkwood/Makefile b/sound/soc/kirkwood/Makefile
new file mode 100644
index 00000000..3e62ae9e
--- /dev/null
+++ b/sound/soc/kirkwood/Makefile
@@ -0,0 +1,11 @@
+snd-soc-kirkwood-objs := kirkwood-dma.o
+snd-soc-kirkwood-i2s-objs := kirkwood-i2s.o
+
+obj-$(CONFIG_SND_KIRKWOOD_SOC) += snd-soc-kirkwood.o
+obj-$(CONFIG_SND_KIRKWOOD_SOC_I2S) += snd-soc-kirkwood-i2s.o
+
+snd-soc-openrd-objs := kirkwood-openrd.o
+snd-soc-t5325-objs := kirkwood-t5325.o
+
+obj-$(CONFIG_SND_KIRKWOOD_SOC_OPENRD) += snd-soc-openrd.o
+obj-$(CONFIG_SND_KIRKWOOD_SOC_T5325) += snd-soc-t5325.o
diff --git a/sound/soc/kirkwood/kirkwood-dma.c b/sound/soc/kirkwood/kirkwood-dma.c
new file mode 100644
index 00000000..e13c6ce4
--- /dev/null
+++ b/sound/soc/kirkwood/kirkwood-dma.c
@@ -0,0 +1,404 @@
+/*
+ * kirkwood-dma.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/mbus.h>
+#include <sound/soc.h>
+#include "kirkwood.h"
+
+#define KIRKWOOD_RATES \
+ (SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+#define KIRKWOOD_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+struct kirkwood_dma_priv {
+ struct snd_pcm_substream *play_stream;
+ struct snd_pcm_substream *rec_stream;
+ struct kirkwood_dma_data *data;
+};
+
+static struct snd_pcm_hardware kirkwood_dma_snd_hw = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = KIRKWOOD_FORMATS,
+ .rates = KIRKWOOD_RATES,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS,
+ .period_bytes_min = KIRKWOOD_SND_MIN_PERIOD_BYTES,
+ .period_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES,
+ .periods_min = KIRKWOOD_SND_MIN_PERIODS,
+ .periods_max = KIRKWOOD_SND_MAX_PERIODS,
+ .fifo_size = 0,
+};
+
+static u64 kirkwood_dma_dmamask = 0xFFFFFFFFUL;
+
+static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id)
+{
+ struct kirkwood_dma_priv *prdata = dev_id;
+ struct kirkwood_dma_data *priv = prdata->data;
+ unsigned long mask, status, cause;
+
+ mask = readl(priv->io + KIRKWOOD_INT_MASK);
+ status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask;
+
+ cause = readl(priv->io + KIRKWOOD_ERR_CAUSE);
+ if (unlikely(cause)) {
+ printk(KERN_WARNING "%s: got err interrupt 0x%lx\n",
+ __func__, cause);
+ writel(cause, priv->io + KIRKWOOD_ERR_CAUSE);
+ return IRQ_HANDLED;
+ }
+
+ /* we've enabled only bytes interrupts ... */
+ if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \
+ KIRKWOOD_INT_CAUSE_REC_BYTES)) {
+ printk(KERN_WARNING "%s: unexpected interrupt %lx\n",
+ __func__, status);
+ return IRQ_NONE;
+ }
+
+ /* ack int */
+ writel(status, priv->io + KIRKWOOD_INT_CAUSE);
+
+ if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES)
+ snd_pcm_period_elapsed(prdata->play_stream);
+
+ if (status & KIRKWOOD_INT_CAUSE_REC_BYTES)
+ snd_pcm_period_elapsed(prdata->rec_stream);
+
+ return IRQ_HANDLED;
+}
+
+static void kirkwood_dma_conf_mbus_windows(void __iomem *base, int win,
+ unsigned long dma,
+ struct mbus_dram_target_info *dram)
+{
+ int i;
+
+ /* First disable and clear windows */
+ writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
+ writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
+
+ /* try to find matching cs for current dma address */
+ for (i = 0; i < dram->num_cs; i++) {
+ struct mbus_dram_window *cs = dram->cs + i;
+ if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) {
+ writel(cs->base & 0xffff0000,
+ base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
+ writel(((cs->size - 1) & 0xffff0000) |
+ (cs->mbus_attr << 8) |
+ (dram->mbus_dram_target_id << 4) | 1,
+ base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
+ }
+ }
+}
+
+static int kirkwood_dma_open(struct snd_pcm_substream *substream)
+{
+ int err;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct snd_soc_platform *platform = soc_runtime->platform;
+ struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
+ struct kirkwood_dma_data *priv;
+ struct kirkwood_dma_priv *prdata = snd_soc_platform_get_drvdata(platform);
+ unsigned long addr;
+
+ priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+ snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw);
+
+ /* Ensure that all constraints linked to dma burst are fulfilled */
+ err = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ priv->burst * 2,
+ KIRKWOOD_AUDIO_BUF_MAX-1);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ priv->burst);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ priv->burst);
+ if (err < 0)
+ return err;
+
+ if (prdata == NULL) {
+ prdata = kzalloc(sizeof(struct kirkwood_dma_priv), GFP_KERNEL);
+ if (prdata == NULL)
+ return -ENOMEM;
+
+ prdata->data = priv;
+
+ err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED,
+ "kirkwood-i2s", prdata);
+ if (err) {
+ kfree(prdata);
+ return -EBUSY;
+ }
+
+ snd_soc_platform_set_drvdata(platform, prdata);
+
+ /*
+ * Enable Error interrupts. We're only ack'ing them but
+ * it's useful for diagnostics
+ */
+ writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK);
+ }
+
+ addr = virt_to_phys(substream->dma_buffer.area);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ prdata->play_stream = substream;
+ kirkwood_dma_conf_mbus_windows(priv->io,
+ KIRKWOOD_PLAYBACK_WIN, addr, priv->dram);
+ } else {
+ prdata->rec_stream = substream;
+ kirkwood_dma_conf_mbus_windows(priv->io,
+ KIRKWOOD_RECORD_WIN, addr, priv->dram);
+ }
+
+ return 0;
+}
+
+static int kirkwood_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
+ struct snd_soc_platform *platform = soc_runtime->platform;
+ struct kirkwood_dma_priv *prdata = snd_soc_platform_get_drvdata(platform);
+ struct kirkwood_dma_data *priv;
+
+ priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ if (!prdata || !priv)
+ return 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ prdata->play_stream = NULL;
+ else
+ prdata->rec_stream = NULL;
+
+ if (!prdata->play_stream && !prdata->rec_stream) {
+ writel(0, priv->io + KIRKWOOD_ERR_MASK);
+ free_irq(priv->irq, prdata);
+ kfree(prdata);
+ snd_soc_platform_set_drvdata(platform, NULL);
+ }
+
+ return 0;
+}
+
+static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ return 0;
+}
+
+static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static int kirkwood_dma_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
+ struct kirkwood_dma_data *priv;
+ unsigned long size, count;
+
+ priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ /* compute buffer size in term of "words" as requested in specs */
+ size = frames_to_bytes(runtime, runtime->buffer_size);
+ size = (size>>2)-1;
+ count = snd_pcm_lib_period_bytes(substream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT);
+ writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR);
+ writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE);
+ } else {
+ writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT);
+ writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR);
+ writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE);
+ }
+
+
+ return 0;
+}
+
+static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream
+ *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
+ struct kirkwood_dma_data *priv;
+ snd_pcm_uframes_t count;
+
+ priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ count = bytes_to_frames(substream->runtime,
+ readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT));
+ else
+ count = bytes_to_frames(substream->runtime,
+ readl(priv->io + KIRKWOOD_REC_BYTE_COUNT));
+
+ return count;
+}
+
+struct snd_pcm_ops kirkwood_dma_ops = {
+ .open = kirkwood_dma_open,
+ .close = kirkwood_dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = kirkwood_dma_hw_params,
+ .hw_free = kirkwood_dma_hw_free,
+ .prepare = kirkwood_dma_prepare,
+ .pointer = kirkwood_dma_pointer,
+};
+
+static int kirkwood_dma_preallocate_dma_buffer(struct snd_pcm *pcm,
+ int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = kirkwood_dma_snd_hw.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+ buf->bytes = size;
+ buf->private_data = NULL;
+
+ return 0;
+}
+
+static int kirkwood_dma_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &kirkwood_dma_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->driver->playback.channels_min) {
+ ret = kirkwood_dma_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ return ret;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = kirkwood_dma_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void kirkwood_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_coherent(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static struct snd_soc_platform_driver kirkwood_soc_platform = {
+ .ops = &kirkwood_dma_ops,
+ .pcm_new = kirkwood_dma_new,
+ .pcm_free = kirkwood_dma_free_dma_buffers,
+};
+
+static int __devinit kirkwood_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &kirkwood_soc_platform);
+}
+
+static int __devexit kirkwood_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver kirkwood_pcm_driver = {
+ .driver = {
+ .name = "kirkwood-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = kirkwood_soc_platform_probe,
+ .remove = __devexit_p(kirkwood_soc_platform_remove),
+};
+
+static int __init kirkwood_pcm_init(void)
+{
+ return platform_driver_register(&kirkwood_pcm_driver);
+}
+module_init(kirkwood_pcm_init);
+
+static void __exit kirkwood_pcm_exit(void)
+{
+ platform_driver_unregister(&kirkwood_pcm_driver);
+}
+module_exit(kirkwood_pcm_exit);
+
+MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
+MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kirkwood-pcm-audio");
diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c
new file mode 100644
index 00000000..a33fc51f
--- /dev/null
+++ b/sound/soc/kirkwood/kirkwood-i2s.c
@@ -0,0 +1,502 @@
+/*
+ * kirkwood-i2s.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/mbus.h>
+#include <linux/delay.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <plat/audio.h>
+#include "kirkwood.h"
+
+#define DRV_NAME "kirkwood-i2s"
+
+#define KIRKWOOD_I2S_RATES \
+ (SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+#define KIRKWOOD_I2S_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static int kirkwood_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned long mask;
+ unsigned long value;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ mask = KIRKWOOD_I2S_CTL_RJ;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ mask = KIRKWOOD_I2S_CTL_LJ;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ mask = KIRKWOOD_I2S_CTL_I2S;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Set same format for playback and record
+ * This avoids some troubles.
+ */
+ value = readl(priv->io+KIRKWOOD_I2S_PLAYCTL);
+ value &= ~KIRKWOOD_I2S_CTL_JUST_MASK;
+ value |= mask;
+ writel(value, priv->io+KIRKWOOD_I2S_PLAYCTL);
+
+ value = readl(priv->io+KIRKWOOD_I2S_RECCTL);
+ value &= ~KIRKWOOD_I2S_CTL_JUST_MASK;
+ value |= mask;
+ writel(value, priv->io+KIRKWOOD_I2S_RECCTL);
+
+ return 0;
+}
+
+static inline void kirkwood_set_dco(void __iomem *io, unsigned long rate)
+{
+ unsigned long value;
+
+ value = KIRKWOOD_DCO_CTL_OFFSET_0;
+ switch (rate) {
+ default:
+ case 44100:
+ value |= KIRKWOOD_DCO_CTL_FREQ_11;
+ break;
+ case 48000:
+ value |= KIRKWOOD_DCO_CTL_FREQ_12;
+ break;
+ case 96000:
+ value |= KIRKWOOD_DCO_CTL_FREQ_24;
+ break;
+ }
+ writel(value, io + KIRKWOOD_DCO_CTL);
+
+ /* wait for dco locked */
+ do {
+ cpu_relax();
+ value = readl(io + KIRKWOOD_DCO_SPCR_STATUS);
+ value &= KIRKWOOD_DCO_SPCR_STATUS;
+ } while (value == 0);
+}
+
+static int kirkwood_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_set_dma_data(dai, substream, priv);
+ return 0;
+}
+
+static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
+ unsigned int i2s_reg, reg;
+ unsigned long i2s_value, value;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ i2s_reg = KIRKWOOD_I2S_PLAYCTL;
+ reg = KIRKWOOD_PLAYCTL;
+ } else {
+ i2s_reg = KIRKWOOD_I2S_RECCTL;
+ reg = KIRKWOOD_RECCTL;
+ }
+
+ /* set dco conf */
+ kirkwood_set_dco(priv->io, params_rate(params));
+
+ i2s_value = readl(priv->io+i2s_reg);
+ i2s_value &= ~KIRKWOOD_I2S_CTL_SIZE_MASK;
+
+ value = readl(priv->io+reg);
+ value &= ~KIRKWOOD_PLAYCTL_SIZE_MASK;
+
+ /*
+ * Size settings in play/rec i2s control regs and play/rec control
+ * regs must be the same.
+ */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ i2s_value |= KIRKWOOD_I2S_CTL_SIZE_16;
+ value |= KIRKWOOD_PLAYCTL_SIZE_16_C;
+ break;
+ /*
+ * doesn't work... S20_3LE != kirkwood 20bit format ?
+ *
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ i2s_value |= KIRKWOOD_I2S_CTL_SIZE_20;
+ value |= KIRKWOOD_PLAYCTL_SIZE_20;
+ break;
+ */
+ case SNDRV_PCM_FORMAT_S24_LE:
+ i2s_value |= KIRKWOOD_I2S_CTL_SIZE_24;
+ value |= KIRKWOOD_PLAYCTL_SIZE_24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ i2s_value |= KIRKWOOD_I2S_CTL_SIZE_32;
+ value |= KIRKWOOD_PLAYCTL_SIZE_32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ value &= ~KIRKWOOD_PLAYCTL_MONO_MASK;
+ if (params_channels(params) == 1)
+ value |= KIRKWOOD_PLAYCTL_MONO_BOTH;
+ else
+ value |= KIRKWOOD_PLAYCTL_MONO_OFF;
+ }
+
+ writel(i2s_value, priv->io+i2s_reg);
+ writel(value, priv->io+reg);
+
+ return 0;
+}
+
+static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
+ unsigned long value;
+
+ /*
+ * specs says KIRKWOOD_PLAYCTL must be read 2 times before
+ * changing it. So read 1 time here and 1 later.
+ */
+ value = readl(priv->io + KIRKWOOD_PLAYCTL);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* stop audio, enable interrupts */
+ value = readl(priv->io + KIRKWOOD_PLAYCTL);
+ value |= KIRKWOOD_PLAYCTL_PAUSE;
+ writel(value, priv->io + KIRKWOOD_PLAYCTL);
+
+ value = readl(priv->io + KIRKWOOD_INT_MASK);
+ value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES;
+ writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+ /* configure audio & enable i2s playback */
+ value = readl(priv->io + KIRKWOOD_PLAYCTL);
+ value &= ~KIRKWOOD_PLAYCTL_BURST_MASK;
+ value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE
+ | KIRKWOOD_PLAYCTL_SPDIF_EN);
+
+ if (priv->burst == 32)
+ value |= KIRKWOOD_PLAYCTL_BURST_32;
+ else
+ value |= KIRKWOOD_PLAYCTL_BURST_128;
+ value |= KIRKWOOD_PLAYCTL_I2S_EN;
+ writel(value, priv->io + KIRKWOOD_PLAYCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* stop audio, disable interrupts */
+ value = readl(priv->io + KIRKWOOD_PLAYCTL);
+ value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE;
+ writel(value, priv->io + KIRKWOOD_PLAYCTL);
+
+ value = readl(priv->io + KIRKWOOD_INT_MASK);
+ value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES;
+ writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+ /* disable all playbacks */
+ value = readl(priv->io + KIRKWOOD_PLAYCTL);
+ value &= ~(KIRKWOOD_PLAYCTL_I2S_EN | KIRKWOOD_PLAYCTL_SPDIF_EN);
+ writel(value, priv->io + KIRKWOOD_PLAYCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ value = readl(priv->io + KIRKWOOD_PLAYCTL);
+ value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE;
+ writel(value, priv->io + KIRKWOOD_PLAYCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ value = readl(priv->io + KIRKWOOD_PLAYCTL);
+ value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE);
+ writel(value, priv->io + KIRKWOOD_PLAYCTL);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
+ unsigned long value;
+
+ value = readl(priv->io + KIRKWOOD_RECCTL);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* stop audio, enable interrupts */
+ value = readl(priv->io + KIRKWOOD_RECCTL);
+ value |= KIRKWOOD_RECCTL_PAUSE;
+ writel(value, priv->io + KIRKWOOD_RECCTL);
+
+ value = readl(priv->io + KIRKWOOD_INT_MASK);
+ value |= KIRKWOOD_INT_CAUSE_REC_BYTES;
+ writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+ /* configure audio & enable i2s record */
+ value = readl(priv->io + KIRKWOOD_RECCTL);
+ value &= ~KIRKWOOD_RECCTL_BURST_MASK;
+ value &= ~KIRKWOOD_RECCTL_MONO;
+ value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE
+ | KIRKWOOD_RECCTL_SPDIF_EN);
+
+ if (priv->burst == 32)
+ value |= KIRKWOOD_RECCTL_BURST_32;
+ else
+ value |= KIRKWOOD_RECCTL_BURST_128;
+ value |= KIRKWOOD_RECCTL_I2S_EN;
+
+ writel(value, priv->io + KIRKWOOD_RECCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* stop audio, disable interrupts */
+ value = readl(priv->io + KIRKWOOD_RECCTL);
+ value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE;
+ writel(value, priv->io + KIRKWOOD_RECCTL);
+
+ value = readl(priv->io + KIRKWOOD_INT_MASK);
+ value &= ~KIRKWOOD_INT_CAUSE_REC_BYTES;
+ writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+ /* disable all records */
+ value = readl(priv->io + KIRKWOOD_RECCTL);
+ value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN);
+ writel(value, priv->io + KIRKWOOD_RECCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ value = readl(priv->io + KIRKWOOD_RECCTL);
+ value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE;
+ writel(value, priv->io + KIRKWOOD_RECCTL);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ value = readl(priv->io + KIRKWOOD_RECCTL);
+ value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE);
+ writel(value, priv->io + KIRKWOOD_RECCTL);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int kirkwood_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return kirkwood_i2s_play_trigger(substream, cmd, dai);
+ else
+ return kirkwood_i2s_rec_trigger(substream, cmd, dai);
+
+ return 0;
+}
+
+static int kirkwood_i2s_probe(struct snd_soc_dai *dai)
+{
+ struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
+ unsigned long value;
+ unsigned int reg_data;
+
+ /* put system in a "safe" state : */
+ /* disable audio interrupts */
+ writel(0xffffffff, priv->io + KIRKWOOD_INT_CAUSE);
+ writel(0, priv->io + KIRKWOOD_INT_MASK);
+
+ reg_data = readl(priv->io + 0x1200);
+ reg_data &= (~(0x333FF8));
+ reg_data |= 0x111D18;
+ writel(reg_data, priv->io + 0x1200);
+
+ msleep(500);
+
+ reg_data = readl(priv->io + 0x1200);
+ reg_data &= (~(0x333FF8));
+ reg_data |= 0x111D18;
+ writel(reg_data, priv->io + 0x1200);
+
+ /* disable playback/record */
+ value = readl(priv->io + KIRKWOOD_PLAYCTL);
+ value &= ~(KIRKWOOD_PLAYCTL_I2S_EN|KIRKWOOD_PLAYCTL_SPDIF_EN);
+ writel(value, priv->io + KIRKWOOD_PLAYCTL);
+
+ value = readl(priv->io + KIRKWOOD_RECCTL);
+ value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN);
+ writel(value, priv->io + KIRKWOOD_RECCTL);
+
+ return 0;
+
+}
+
+static int kirkwood_i2s_remove(struct snd_soc_dai *dai)
+{
+ return 0;
+}
+
+static struct snd_soc_dai_ops kirkwood_i2s_dai_ops = {
+ .startup = kirkwood_i2s_startup,
+ .trigger = kirkwood_i2s_trigger,
+ .hw_params = kirkwood_i2s_hw_params,
+ .set_fmt = kirkwood_i2s_set_fmt,
+};
+
+
+static struct snd_soc_dai_driver kirkwood_i2s_dai = {
+ .probe = kirkwood_i2s_probe,
+ .remove = kirkwood_i2s_remove,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = KIRKWOOD_I2S_RATES,
+ .formats = KIRKWOOD_I2S_FORMATS,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = KIRKWOOD_I2S_RATES,
+ .formats = KIRKWOOD_I2S_FORMATS,},
+ .ops = &kirkwood_i2s_dai_ops,
+};
+
+static __devinit int kirkwood_i2s_dev_probe(struct platform_device *pdev)
+{
+ struct resource *mem;
+ struct kirkwood_asoc_platform_data *data =
+ pdev->dev.platform_data;
+ struct kirkwood_dma_data *priv;
+ int err;
+
+ priv = kzalloc(sizeof(struct kirkwood_dma_data), GFP_KERNEL);
+ if (!priv) {
+ dev_err(&pdev->dev, "allocation failed\n");
+ err = -ENOMEM;
+ goto error;
+ }
+ dev_set_drvdata(&pdev->dev, priv);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "platform_get_resource failed\n");
+ err = -ENXIO;
+ goto err_alloc;
+ }
+
+ priv->mem = request_mem_region(mem->start, SZ_16K, DRV_NAME);
+ if (!priv->mem) {
+ dev_err(&pdev->dev, "request_mem_region failed\n");
+ err = -EBUSY;
+ goto error;
+ }
+
+ priv->io = ioremap(priv->mem->start, SZ_16K);
+ if (!priv->io) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ err = -ENOMEM;
+ goto err_iomem;
+ }
+
+ priv->irq = platform_get_irq(pdev, 0);
+ if (priv->irq <= 0) {
+ dev_err(&pdev->dev, "platform_get_irq failed\n");
+ err = -ENXIO;
+ goto err_ioremap;
+ }
+
+ if (!data || !data->dram) {
+ dev_err(&pdev->dev, "no platform data ?!\n");
+ err = -EINVAL;
+ goto err_ioremap;
+ }
+
+ priv->dram = data->dram;
+ priv->burst = data->burst;
+
+ return snd_soc_register_dai(&pdev->dev, &kirkwood_i2s_dai);
+
+err_ioremap:
+ iounmap(priv->io);
+err_iomem:
+ release_mem_region(priv->mem->start, SZ_16K);
+err_alloc:
+ kfree(priv);
+error:
+ return err;
+}
+
+static __devexit int kirkwood_i2s_dev_remove(struct platform_device *pdev)
+{
+ struct kirkwood_dma_data *priv = dev_get_drvdata(&pdev->dev);
+
+ snd_soc_unregister_dai(&pdev->dev);
+ iounmap(priv->io);
+ release_mem_region(priv->mem->start, SZ_16K);
+ kfree(priv);
+
+ return 0;
+}
+
+static struct platform_driver kirkwood_i2s_driver = {
+ .probe = kirkwood_i2s_dev_probe,
+ .remove = kirkwood_i2s_dev_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init kirkwood_i2s_init(void)
+{
+ return platform_driver_register(&kirkwood_i2s_driver);
+}
+module_init(kirkwood_i2s_init);
+
+static void __exit kirkwood_i2s_exit(void)
+{
+ platform_driver_unregister(&kirkwood_i2s_driver);
+}
+module_exit(kirkwood_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Arnaud Patard, <arnaud.patard@rtp-net.org>");
+MODULE_DESCRIPTION("Kirkwood I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kirkwood-i2s");
diff --git a/sound/soc/kirkwood/kirkwood-openrd.c b/sound/soc/kirkwood/kirkwood-openrd.c
new file mode 100644
index 00000000..d863afb3
--- /dev/null
+++ b/sound/soc/kirkwood/kirkwood-openrd.c
@@ -0,0 +1,120 @@
+/*
+ * kirkwood-openrd.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <mach/kirkwood.h>
+#include <plat/audio.h>
+#include <asm/mach-types.h>
+#include "../codecs/cs42l51.h"
+
+static int openrd_client_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+ unsigned int freq, fmt;
+
+ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret < 0)
+ return ret;
+
+ switch (params_rate(params)) {
+ default:
+ case 44100:
+ freq = 11289600;
+ break;
+ case 48000:
+ freq = 12288000;
+ break;
+ case 96000:
+ freq = 24576000;
+ break;
+ }
+
+ return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN);
+
+}
+
+static struct snd_soc_ops openrd_client_ops = {
+ .hw_params = openrd_client_hw_params,
+};
+
+
+static struct snd_soc_dai_link openrd_client_dai[] = {
+{
+ .name = "CS42L51",
+ .stream_name = "CS42L51 HiFi",
+ .cpu_dai_name = "kirkwood-i2s",
+ .platform_name = "kirkwood-pcm-audio",
+ .codec_dai_name = "cs42l51-hifi",
+ .codec_name = "cs42l51-codec.0-004a",
+ .ops = &openrd_client_ops,
+},
+};
+
+
+static struct snd_soc_card openrd_client = {
+ .name = "OpenRD Client",
+ .dai_link = openrd_client_dai,
+ .num_links = ARRAY_SIZE(openrd_client_dai),
+};
+
+static struct platform_device *openrd_client_snd_device;
+
+static int __init openrd_client_init(void)
+{
+ int ret;
+
+ if (!machine_is_openrd_client() && !machine_is_openrd_ultimate())
+ return 0;
+
+ openrd_client_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!openrd_client_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(openrd_client_snd_device,
+ &openrd_client);
+
+ ret = platform_device_add(openrd_client_snd_device);
+ if (ret) {
+ printk(KERN_ERR "%s: platform_device_add failed\n", __func__);
+ platform_device_put(openrd_client_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit openrd_client_exit(void)
+{
+ platform_device_unregister(openrd_client_snd_device);
+}
+
+module_init(openrd_client_init);
+module_exit(openrd_client_exit);
+
+/* Module information */
+MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
+MODULE_DESCRIPTION("ALSA SoC OpenRD Client");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:soc-audio");
diff --git a/sound/soc/kirkwood/kirkwood-t5325.c b/sound/soc/kirkwood/kirkwood-t5325.c
new file mode 100644
index 00000000..c8d21956
--- /dev/null
+++ b/sound/soc/kirkwood/kirkwood-t5325.c
@@ -0,0 +1,141 @@
+/*
+ * kirkwood-t5325.c
+ *
+ * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <mach/kirkwood.h>
+#include <plat/audio.h>
+#include <asm/mach-types.h>
+#include "../codecs/alc5623.h"
+
+static int t5325_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+ unsigned int freq, fmt;
+
+ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret < 0)
+ return ret;
+
+ freq = params_rate(params) * 256;
+
+ return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN);
+
+}
+
+static struct snd_soc_ops t5325_ops = {
+ .hw_params = t5325_hw_params,
+};
+
+static const struct snd_soc_dapm_widget t5325_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route t5325_route[] = {
+ { "Headphone Jack", NULL, "HPL" },
+ { "Headphone Jack", NULL, "HPR" },
+
+ {"Speaker", NULL, "SPKOUT"},
+ {"Speaker", NULL, "SPKOUTN"},
+
+ { "MIC1", NULL, "Mic Jack" },
+ { "MIC2", NULL, "Mic Jack" },
+};
+
+static int t5325_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, t5325_dapm_widgets,
+ ARRAY_SIZE(t5325_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, t5325_route, ARRAY_SIZE(t5325_route));
+
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Speaker");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link t5325_dai[] = {
+{
+ .name = "ALC5621",
+ .stream_name = "ALC5621 HiFi",
+ .cpu_dai_name = "kirkwood-i2s",
+ .platform_name = "kirkwood-pcm-audio",
+ .codec_dai_name = "alc5621-hifi",
+ .codec_name = "alc562x-codec.0-001a",
+ .ops = &t5325_ops,
+ .init = t5325_dai_init,
+},
+};
+
+
+static struct snd_soc_card t5325 = {
+ .name = "t5325",
+ .dai_link = t5325_dai,
+ .num_links = ARRAY_SIZE(t5325_dai),
+};
+
+static struct platform_device *t5325_snd_device;
+
+static int __init t5325_init(void)
+{
+ int ret;
+
+ if (!machine_is_t5325())
+ return 0;
+
+ t5325_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!t5325_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(t5325_snd_device,
+ &t5325);
+
+ ret = platform_device_add(t5325_snd_device);
+ if (ret) {
+ printk(KERN_ERR "%s: platform_device_add failed\n", __func__);
+ platform_device_put(t5325_snd_device);
+ }
+
+ return ret;
+}
+module_init(t5325_init);
+
+static void __exit t5325_exit(void)
+{
+ platform_device_unregister(t5325_snd_device);
+}
+module_exit(t5325_exit);
+
+MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
+MODULE_DESCRIPTION("ALSA SoC t5325 audio client");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/kirkwood/kirkwood.h b/sound/soc/kirkwood/kirkwood.h
new file mode 100644
index 00000000..bb6e6a56
--- /dev/null
+++ b/sound/soc/kirkwood/kirkwood.h
@@ -0,0 +1,129 @@
+/*
+ * kirkwood.h
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _KIRKWOOD_AUDIO_H
+#define _KIRKWOOD_AUDIO_H
+
+#define KIRKWOOD_RECORD_WIN 0
+#define KIRKWOOD_PLAYBACK_WIN 1
+#define KIRKWOOD_MAX_AUDIO_WIN 2
+
+#define KIRKWOOD_AUDIO_WIN_BASE_REG(win) (0xA00 + ((win)<<3))
+#define KIRKWOOD_AUDIO_WIN_CTRL_REG(win) (0xA04 + ((win)<<3))
+
+
+#define KIRKWOOD_RECCTL 0x1000
+#define KIRKWOOD_RECCTL_SPDIF_EN (1<<11)
+#define KIRKWOOD_RECCTL_I2S_EN (1<<10)
+#define KIRKWOOD_RECCTL_PAUSE (1<<9)
+#define KIRKWOOD_RECCTL_MUTE (1<<8)
+#define KIRKWOOD_RECCTL_BURST_MASK (3<<5)
+#define KIRKWOOD_RECCTL_BURST_128 (2<<5)
+#define KIRKWOOD_RECCTL_BURST_32 (1<<5)
+#define KIRKWOOD_RECCTL_MONO (1<<4)
+#define KIRKWOOD_RECCTL_MONO_CHAN_RIGHT (1<<3)
+#define KIRKWOOD_RECCTL_MONO_CHAN_LEFT (0<<3)
+#define KIRKWOOD_RECCTL_SIZE_MASK (7<<0)
+#define KIRKWOOD_RECCTL_SIZE_16 (7<<0)
+#define KIRKWOOD_RECCTL_SIZE_16_C (3<<0)
+#define KIRKWOOD_RECCTL_SIZE_20 (2<<0)
+#define KIRKWOOD_RECCTL_SIZE_24 (1<<0)
+#define KIRKWOOD_RECCTL_SIZE_32 (0<<0)
+
+#define KIRKWOOD_REC_BUF_ADDR 0x1004
+#define KIRKWOOD_REC_BUF_SIZE 0x1008
+#define KIRKWOOD_REC_BYTE_COUNT 0x100C
+
+#define KIRKWOOD_PLAYCTL 0x1100
+#define KIRKWOOD_PLAYCTL_PLAY_BUSY (1<<16)
+#define KIRKWOOD_PLAYCTL_BURST_MASK (3<<11)
+#define KIRKWOOD_PLAYCTL_BURST_128 (2<<11)
+#define KIRKWOOD_PLAYCTL_BURST_32 (1<<11)
+#define KIRKWOOD_PLAYCTL_PAUSE (1<<9)
+#define KIRKWOOD_PLAYCTL_SPDIF_MUTE (1<<8)
+#define KIRKWOOD_PLAYCTL_MONO_MASK (3<<5)
+#define KIRKWOOD_PLAYCTL_MONO_BOTH (3<<5)
+#define KIRKWOOD_PLAYCTL_MONO_OFF (0<<5)
+#define KIRKWOOD_PLAYCTL_I2S_MUTE (1<<7)
+#define KIRKWOOD_PLAYCTL_SPDIF_EN (1<<4)
+#define KIRKWOOD_PLAYCTL_I2S_EN (1<<3)
+#define KIRKWOOD_PLAYCTL_SIZE_MASK (7<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_16 (7<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_16_C (3<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_20 (2<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_24 (1<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_32 (0<<0)
+
+#define KIRKWOOD_PLAY_BUF_ADDR 0x1104
+#define KIRKWOOD_PLAY_BUF_SIZE 0x1108
+#define KIRKWOOD_PLAY_BYTE_COUNT 0x110C
+
+#define KIRKWOOD_DCO_CTL 0x1204
+#define KIRKWOOD_DCO_CTL_OFFSET_MASK (0xFFF<<2)
+#define KIRKWOOD_DCO_CTL_OFFSET_0 (0x800<<2)
+#define KIRKWOOD_DCO_CTL_FREQ_MASK (3<<0)
+#define KIRKWOOD_DCO_CTL_FREQ_11 (0<<0)
+#define KIRKWOOD_DCO_CTL_FREQ_12 (1<<0)
+#define KIRKWOOD_DCO_CTL_FREQ_24 (2<<0)
+
+#define KIRKWOOD_DCO_SPCR_STATUS 0x120c
+#define KIRKWOOD_DCO_SPCR_STATUS_DCO_LOCK (1<<16)
+
+#define KIRKWOOD_ERR_CAUSE 0x1300
+#define KIRKWOOD_ERR_MASK 0x1304
+
+#define KIRKWOOD_INT_CAUSE 0x1308
+#define KIRKWOOD_INT_MASK 0x130C
+#define KIRKWOOD_INT_CAUSE_PLAY_BYTES (1<<14)
+#define KIRKWOOD_INT_CAUSE_REC_BYTES (1<<13)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_END (1<<7)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_3Q (1<<6)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_HALF (1<<5)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_1Q (1<<4)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_END (1<<3)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_3Q (1<<2)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_HALF (1<<1)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_1Q (1<<0)
+
+#define KIRKWOOD_REC_BYTE_INT_COUNT 0x1310
+#define KIRKWOOD_PLAY_BYTE_INT_COUNT 0x1314
+#define KIRKWOOD_BYTE_INT_COUNT_MASK 0xffffff
+
+#define KIRKWOOD_I2S_PLAYCTL 0x2508
+#define KIRKWOOD_I2S_RECCTL 0x2408
+#define KIRKWOOD_I2S_CTL_JUST_MASK (0xf<<26)
+#define KIRKWOOD_I2S_CTL_LJ (0<<26)
+#define KIRKWOOD_I2S_CTL_I2S (5<<26)
+#define KIRKWOOD_I2S_CTL_RJ (8<<26)
+#define KIRKWOOD_I2S_CTL_SIZE_MASK (3<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_16 (3<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_20 (2<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_24 (1<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_32 (0<<30)
+
+#define KIRKWOOD_AUDIO_BUF_MAX (16*1024*1024)
+
+/* Theses values come from the marvell alsa driver */
+/* need to find where they come from */
+#define KIRKWOOD_SND_MIN_PERIODS 8
+#define KIRKWOOD_SND_MAX_PERIODS 16
+#define KIRKWOOD_SND_MIN_PERIOD_BYTES 0x4000
+#define KIRKWOOD_SND_MAX_PERIOD_BYTES 0x4000
+
+struct kirkwood_dma_data {
+ struct resource *mem;
+ void __iomem *io;
+ int irq;
+ int burst;
+ struct mbus_dram_target_info *dram;
+};
+
+#endif
diff --git a/sound/soc/mid-x86/Kconfig b/sound/soc/mid-x86/Kconfig
new file mode 100644
index 00000000..29350428
--- /dev/null
+++ b/sound/soc/mid-x86/Kconfig
@@ -0,0 +1,14 @@
+config SND_MFLD_MACHINE
+ tristate "SOC Machine Audio driver for Intel Medfield MID platform"
+ depends on INTEL_SCU_IPC
+ depends on SND_INTEL_SST
+ select SND_SOC_SN95031
+ select SND_SST_PLATFORM
+ help
+ This adds support for ASoC machine driver for Intel(R) MID Medfield platform
+ used as alsa device in audio substem in Intel(R) MID devices
+ Say Y if you have such a device
+ If unsure select "N".
+
+config SND_SST_PLATFORM
+ tristate
diff --git a/sound/soc/mid-x86/Makefile b/sound/soc/mid-x86/Makefile
new file mode 100644
index 00000000..63988333
--- /dev/null
+++ b/sound/soc/mid-x86/Makefile
@@ -0,0 +1,5 @@
+snd-soc-sst-platform-objs := sst_platform.o
+snd-soc-mfld-machine-objs := mfld_machine.o
+
+obj-$(CONFIG_SND_SST_PLATFORM) += snd-soc-sst-platform.o
+obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o
diff --git a/sound/soc/mid-x86/mfld_machine.c b/sound/soc/mid-x86/mfld_machine.c
new file mode 100644
index 00000000..429aa1be
--- /dev/null
+++ b/sound/soc/mid-x86/mfld_machine.c
@@ -0,0 +1,452 @@
+/*
+ * mfld_machine.c - ASoc Machine driver for Intel Medfield MID platform
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@intel.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; version 2 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, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include "../codecs/sn95031.h"
+
+#define MID_MONO 1
+#define MID_STEREO 2
+#define MID_MAX_CAP 5
+#define MFLD_JACK_INSERT 0x04
+
+enum soc_mic_bias_zones {
+ MFLD_MV_START = 0,
+ /* mic bias volutage range for Headphones*/
+ MFLD_MV_HP = 400,
+ /* mic bias volutage range for American Headset*/
+ MFLD_MV_AM_HS = 650,
+ /* mic bias volutage range for Headset*/
+ MFLD_MV_HS = 2000,
+ MFLD_MV_UNDEFINED,
+};
+
+static unsigned int hs_switch;
+static unsigned int lo_dac;
+
+struct mfld_mc_private {
+ struct platform_device *socdev;
+ void __iomem *int_base;
+ struct snd_soc_codec *codec;
+ u8 interrupt_status;
+};
+
+struct snd_soc_jack mfld_jack;
+
+/*Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin mfld_jack_pins[] = {
+ {
+ .pin = "Headphones",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "AMIC1",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+/* jack detection voltage zones */
+static struct snd_soc_jack_zone mfld_zones[] = {
+ {MFLD_MV_START, MFLD_MV_AM_HS, SND_JACK_HEADPHONE},
+ {MFLD_MV_AM_HS, MFLD_MV_HS, SND_JACK_HEADSET},
+};
+
+/* sound card controls */
+static const char *headset_switch_text[] = {"Earpiece", "Headset"};
+
+static const char *lo_text[] = {"Vibra", "Headset", "IHF", "None"};
+
+static const struct soc_enum headset_enum =
+ SOC_ENUM_SINGLE_EXT(2, headset_switch_text);
+
+static const struct soc_enum lo_enum =
+ SOC_ENUM_SINGLE_EXT(4, lo_text);
+
+static int headset_get_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = hs_switch;
+ return 0;
+}
+
+static int headset_set_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.integer.value[0] == hs_switch)
+ return 0;
+
+ if (ucontrol->value.integer.value[0]) {
+ pr_debug("hs_set HS path\n");
+ snd_soc_dapm_enable_pin(&codec->dapm, "Headphones");
+ snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT");
+ } else {
+ pr_debug("hs_set EP path\n");
+ snd_soc_dapm_disable_pin(&codec->dapm, "Headphones");
+ snd_soc_dapm_enable_pin(&codec->dapm, "EPOUT");
+ }
+ snd_soc_dapm_sync(&codec->dapm);
+ hs_switch = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static void lo_enable_out_pins(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_enable_pin(&codec->dapm, "IHFOUTL");
+ snd_soc_dapm_enable_pin(&codec->dapm, "IHFOUTR");
+ snd_soc_dapm_enable_pin(&codec->dapm, "LINEOUTL");
+ snd_soc_dapm_enable_pin(&codec->dapm, "LINEOUTR");
+ snd_soc_dapm_enable_pin(&codec->dapm, "VIB1OUT");
+ snd_soc_dapm_enable_pin(&codec->dapm, "VIB2OUT");
+ if (hs_switch) {
+ snd_soc_dapm_enable_pin(&codec->dapm, "Headphones");
+ snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT");
+ } else {
+ snd_soc_dapm_disable_pin(&codec->dapm, "Headphones");
+ snd_soc_dapm_enable_pin(&codec->dapm, "EPOUT");
+ }
+}
+
+static int lo_get_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = lo_dac;
+ return 0;
+}
+
+static int lo_set_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.integer.value[0] == lo_dac)
+ return 0;
+
+ /* we dont want to work with last state of lineout so just enable all
+ * pins and then disable pins not required
+ */
+ lo_enable_out_pins(codec);
+ switch (ucontrol->value.integer.value[0]) {
+ case 0:
+ pr_debug("set vibra path\n");
+ snd_soc_dapm_disable_pin(&codec->dapm, "VIB1OUT");
+ snd_soc_dapm_disable_pin(&codec->dapm, "VIB2OUT");
+ snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0);
+ break;
+
+ case 1:
+ pr_debug("set hs path\n");
+ snd_soc_dapm_disable_pin(&codec->dapm, "Headphones");
+ snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT");
+ snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x22);
+ break;
+
+ case 2:
+ pr_debug("set spkr path\n");
+ snd_soc_dapm_disable_pin(&codec->dapm, "IHFOUTL");
+ snd_soc_dapm_disable_pin(&codec->dapm, "IHFOUTR");
+ snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x44);
+ break;
+
+ case 3:
+ pr_debug("set null path\n");
+ snd_soc_dapm_disable_pin(&codec->dapm, "LINEOUTL");
+ snd_soc_dapm_disable_pin(&codec->dapm, "LINEOUTR");
+ snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x66);
+ break;
+ }
+ snd_soc_dapm_sync(&codec->dapm);
+ lo_dac = ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static const struct snd_kcontrol_new mfld_snd_controls[] = {
+ SOC_ENUM_EXT("Playback Switch", headset_enum,
+ headset_get_switch, headset_set_switch),
+ SOC_ENUM_EXT("Lineout Mux", lo_enum,
+ lo_get_switch, lo_set_switch),
+};
+
+static const struct snd_soc_dapm_widget mfld_widgets[] = {
+ SND_SOC_DAPM_HP("Headphones", NULL),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route mfld_map[] = {
+ {"Headphones", NULL, "HPOUTR"},
+ {"Headphones", NULL, "HPOUTL"},
+ {"Mic", NULL, "AMIC1"},
+};
+
+static void mfld_jack_check(unsigned int intr_status)
+{
+ struct mfld_jack_data jack_data;
+
+ jack_data.mfld_jack = &mfld_jack;
+ jack_data.intr_id = intr_status;
+
+ sn95031_jack_detection(&jack_data);
+ /* TODO: add american headset detection post gpiolib support */
+}
+
+static int mfld_init(struct snd_soc_pcm_runtime *runtime)
+{
+ struct snd_soc_codec *codec = runtime->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret_val;
+
+ /* Add jack sense widgets */
+ snd_soc_dapm_new_controls(dapm, mfld_widgets, ARRAY_SIZE(mfld_widgets));
+
+ /* Set up the map */
+ snd_soc_dapm_add_routes(dapm, mfld_map, ARRAY_SIZE(mfld_map));
+
+ /* always connected */
+ snd_soc_dapm_enable_pin(dapm, "Headphones");
+ snd_soc_dapm_enable_pin(dapm, "Mic");
+ snd_soc_dapm_sync(dapm);
+
+ ret_val = snd_soc_add_controls(codec, mfld_snd_controls,
+ ARRAY_SIZE(mfld_snd_controls));
+ if (ret_val) {
+ pr_err("soc_add_controls failed %d", ret_val);
+ return ret_val;
+ }
+ /* default is earpiece pin, userspace sets it explcitly */
+ snd_soc_dapm_disable_pin(dapm, "Headphones");
+ /* default is lineout NC, userspace sets it explcitly */
+ snd_soc_dapm_disable_pin(dapm, "LINEOUTL");
+ snd_soc_dapm_disable_pin(dapm, "LINEOUTR");
+ lo_dac = 3;
+ hs_switch = 0;
+ /* we dont use linein in this so set to NC */
+ snd_soc_dapm_disable_pin(dapm, "LINEINL");
+ snd_soc_dapm_disable_pin(dapm, "LINEINR");
+ snd_soc_dapm_sync(dapm);
+
+ /* Headset and button jack detection */
+ ret_val = snd_soc_jack_new(codec, "Intel(R) MID Audio Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1, &mfld_jack);
+ if (ret_val) {
+ pr_err("jack creation failed\n");
+ return ret_val;
+ }
+
+ ret_val = snd_soc_jack_add_pins(&mfld_jack,
+ ARRAY_SIZE(mfld_jack_pins), mfld_jack_pins);
+ if (ret_val) {
+ pr_err("adding jack pins failed\n");
+ return ret_val;
+ }
+ ret_val = snd_soc_jack_add_zones(&mfld_jack,
+ ARRAY_SIZE(mfld_zones), mfld_zones);
+ if (ret_val) {
+ pr_err("adding jack zones failed\n");
+ return ret_val;
+ }
+
+ /* we want to check if anything is inserted at boot,
+ * so send a fake event to codec and it will read adc
+ * to find if anything is there or not */
+ mfld_jack_check(MFLD_JACK_INSERT);
+ return ret_val;
+}
+
+struct snd_soc_dai_link mfld_msic_dailink[] = {
+ {
+ .name = "Medfield Headset",
+ .stream_name = "Headset",
+ .cpu_dai_name = "Headset-cpu-dai",
+ .codec_dai_name = "SN95031 Headset",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = mfld_init,
+ },
+ {
+ .name = "Medfield Speaker",
+ .stream_name = "Speaker",
+ .cpu_dai_name = "Speaker-cpu-dai",
+ .codec_dai_name = "SN95031 Speaker",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = NULL,
+ },
+ {
+ .name = "Medfield Vibra",
+ .stream_name = "Vibra1",
+ .cpu_dai_name = "Vibra1-cpu-dai",
+ .codec_dai_name = "SN95031 Vibra1",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = NULL,
+ },
+ {
+ .name = "Medfield Haptics",
+ .stream_name = "Vibra2",
+ .cpu_dai_name = "Vibra2-cpu-dai",
+ .codec_dai_name = "SN95031 Vibra2",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = NULL,
+ },
+};
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_mfld = {
+ .name = "medfield_audio",
+ .dai_link = mfld_msic_dailink,
+ .num_links = ARRAY_SIZE(mfld_msic_dailink),
+};
+
+static irqreturn_t snd_mfld_jack_intr_handler(int irq, void *dev)
+{
+ struct mfld_mc_private *mc_private = (struct mfld_mc_private *) dev;
+
+ memcpy_fromio(&mc_private->interrupt_status,
+ ((void *)(mc_private->int_base)),
+ sizeof(u8));
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t snd_mfld_jack_detection(int irq, void *data)
+{
+ struct mfld_mc_private *mc_drv_ctx = (struct mfld_mc_private *) data;
+
+ if (mfld_jack.codec == NULL)
+ return IRQ_HANDLED;
+ mfld_jack_check(mc_drv_ctx->interrupt_status);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit snd_mfld_mc_probe(struct platform_device *pdev)
+{
+ int ret_val = 0, irq;
+ struct mfld_mc_private *mc_drv_ctx;
+ struct resource *irq_mem;
+
+ pr_debug("snd_mfld_mc_probe called\n");
+
+ /* retrive the irq number */
+ irq = platform_get_irq(pdev, 0);
+
+ /* audio interrupt base of SRAM location where
+ * interrupts are stored by System FW */
+ mc_drv_ctx = kzalloc(sizeof(*mc_drv_ctx), GFP_ATOMIC);
+ if (!mc_drv_ctx) {
+ pr_err("allocation failed\n");
+ return -ENOMEM;
+ }
+
+ irq_mem = platform_get_resource_byname(
+ pdev, IORESOURCE_MEM, "IRQ_BASE");
+ if (!irq_mem) {
+ pr_err("no mem resource given\n");
+ ret_val = -ENODEV;
+ goto unalloc;
+ }
+ mc_drv_ctx->int_base = ioremap_nocache(irq_mem->start,
+ resource_size(irq_mem));
+ if (!mc_drv_ctx->int_base) {
+ pr_err("Mapping of cache failed\n");
+ ret_val = -ENOMEM;
+ goto unalloc;
+ }
+ /* register for interrupt */
+ ret_val = request_threaded_irq(irq, snd_mfld_jack_intr_handler,
+ snd_mfld_jack_detection,
+ IRQF_SHARED, pdev->dev.driver->name, mc_drv_ctx);
+ if (ret_val) {
+ pr_err("cannot register IRQ\n");
+ goto unalloc;
+ }
+ /* register the soc card */
+ snd_soc_card_mfld.dev = &pdev->dev;
+ ret_val = snd_soc_register_card(&snd_soc_card_mfld);
+ if (ret_val) {
+ pr_debug("snd_soc_register_card failed %d\n", ret_val);
+ goto freeirq;
+ }
+ platform_set_drvdata(pdev, mc_drv_ctx);
+ pr_debug("successfully exited probe\n");
+ return ret_val;
+
+freeirq:
+ free_irq(irq, mc_drv_ctx);
+unalloc:
+ kfree(mc_drv_ctx);
+ return ret_val;
+}
+
+static int __devexit snd_mfld_mc_remove(struct platform_device *pdev)
+{
+ struct mfld_mc_private *mc_drv_ctx = platform_get_drvdata(pdev);
+
+ pr_debug("snd_mfld_mc_remove called\n");
+ free_irq(platform_get_irq(pdev, 0), mc_drv_ctx);
+ snd_soc_unregister_card(&snd_soc_card_mfld);
+ kfree(mc_drv_ctx);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver snd_mfld_mc_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "msic_audio",
+ },
+ .probe = snd_mfld_mc_probe,
+ .remove = __devexit_p(snd_mfld_mc_remove),
+};
+
+static int __init snd_mfld_driver_init(void)
+{
+ pr_debug("snd_mfld_driver_init called\n");
+ return platform_driver_register(&snd_mfld_mc_driver);
+}
+module_init(snd_mfld_driver_init);
+
+static void __exit snd_mfld_driver_exit(void)
+{
+ pr_debug("snd_mfld_driver_exit called\n");
+ platform_driver_unregister(&snd_mfld_mc_driver);
+}
+module_exit(snd_mfld_driver_exit);
+
+MODULE_DESCRIPTION("ASoC Intel(R) MID Machine driver");
+MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:msic-audio");
diff --git a/sound/soc/mid-x86/sst_platform.c b/sound/soc/mid-x86/sst_platform.c
new file mode 100644
index 00000000..5a946b41
--- /dev/null
+++ b/sound/soc/mid-x86/sst_platform.c
@@ -0,0 +1,486 @@
+/*
+ * sst_platform.c - Intel MID Platform driver
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@intel.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; version 2 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, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "../../../drivers/staging/intel_sst/intel_sst_ioctl.h"
+#include "../../../drivers/staging/intel_sst/intel_sst.h"
+#include "sst_platform.h"
+
+static struct snd_pcm_hardware sst_platform_pcm_hw = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_DOUBLE |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_MMAP|
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = (SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_U16 |
+ SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_U24 |
+ SNDRV_PCM_FMTBIT_S32 | SNDRV_PCM_FMTBIT_U32),
+ .rates = (SNDRV_PCM_RATE_8000|
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000),
+ .rate_min = SST_MIN_RATE,
+ .rate_max = SST_MAX_RATE,
+ .channels_min = SST_MIN_CHANNEL,
+ .channels_max = SST_MAX_CHANNEL,
+ .buffer_bytes_max = SST_MAX_BUFFER,
+ .period_bytes_min = SST_MIN_PERIOD_BYTES,
+ .period_bytes_max = SST_MAX_PERIOD_BYTES,
+ .periods_min = SST_MIN_PERIODS,
+ .periods_max = SST_MAX_PERIODS,
+ .fifo_size = SST_FIFO_SIZE,
+};
+
+/* MFLD - MSIC */
+struct snd_soc_dai_driver sst_platform_dai[] = {
+{
+ .name = "Headset-cpu-dai",
+ .id = 0,
+ .playback = {
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 5,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Speaker-cpu-dai",
+ .id = 1,
+ .playback = {
+ .channels_min = SST_MONO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Vibra1-cpu-dai",
+ .id = 2,
+ .playback = {
+ .channels_min = SST_MONO,
+ .channels_max = SST_MONO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Vibra2-cpu-dai",
+ .id = 3,
+ .playback = {
+ .channels_min = SST_MONO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+};
+
+/* helper functions */
+static inline void sst_set_stream_status(struct sst_runtime_stream *stream,
+ int state)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&stream->status_lock, flags);
+ stream->stream_status = state;
+ spin_unlock_irqrestore(&stream->status_lock, flags);
+}
+
+static inline int sst_get_stream_status(struct sst_runtime_stream *stream)
+{
+ int state;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stream->status_lock, flags);
+ state = stream->stream_status;
+ spin_unlock_irqrestore(&stream->status_lock, flags);
+ return state;
+}
+
+static void sst_fill_pcm_params(struct snd_pcm_substream *substream,
+ struct snd_sst_stream_params *param)
+{
+
+ param->uc.pcm_params.codec = SST_CODEC_TYPE_PCM;
+ param->uc.pcm_params.num_chan = (u8) substream->runtime->channels;
+ param->uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits;
+ param->uc.pcm_params.reserved = 0;
+ param->uc.pcm_params.sfreq = substream->runtime->rate;
+ param->uc.pcm_params.ring_buffer_size =
+ snd_pcm_lib_buffer_bytes(substream);
+ param->uc.pcm_params.period_count = substream->runtime->period_size;
+ param->uc.pcm_params.ring_buffer_addr =
+ virt_to_phys(substream->dma_buffer.area);
+ pr_debug("period_cnt = %d\n", param->uc.pcm_params.period_count);
+ pr_debug("sfreq= %d, wd_sz = %d\n",
+ param->uc.pcm_params.sfreq, param->uc.pcm_params.pcm_wd_sz);
+}
+
+static int sst_platform_alloc_stream(struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream =
+ substream->runtime->private_data;
+ struct snd_sst_stream_params param = {{{0,},},};
+ struct snd_sst_params str_params = {0};
+ int ret_val;
+
+ /* set codec params and inform SST driver the same */
+ sst_fill_pcm_params(substream, &param);
+ substream->runtime->dma_area = substream->dma_buffer.area;
+ str_params.sparams = param;
+ str_params.codec = param.uc.pcm_params.codec;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ str_params.ops = STREAM_OPS_PLAYBACK;
+ str_params.device_type = substream->pcm->device + 1;
+ pr_debug("Playbck stream,Device %d\n",
+ substream->pcm->device);
+ } else {
+ str_params.ops = STREAM_OPS_CAPTURE;
+ str_params.device_type = SND_SST_DEVICE_CAPTURE;
+ pr_debug("Capture stream,Device %d\n",
+ substream->pcm->device);
+ }
+ ret_val = stream->sstdrv_ops->pcm_control->open(&str_params);
+ pr_debug("SST_SND_PLAY/CAPTURE ret_val = %x\n", ret_val);
+ if (ret_val < 0)
+ return ret_val;
+
+ stream->stream_info.str_id = ret_val;
+ pr_debug("str id : %d\n", stream->stream_info.str_id);
+ return ret_val;
+}
+
+static void sst_period_elapsed(void *mad_substream)
+{
+ struct snd_pcm_substream *substream = mad_substream;
+ struct sst_runtime_stream *stream;
+ int status;
+
+ if (!substream || !substream->runtime)
+ return;
+ stream = substream->runtime->private_data;
+ if (!stream)
+ return;
+ status = sst_get_stream_status(stream);
+ if (status != SST_PLATFORM_RUNNING)
+ return;
+ snd_pcm_period_elapsed(substream);
+}
+
+static int sst_platform_init_stream(struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream =
+ substream->runtime->private_data;
+ int ret_val;
+
+ pr_debug("setting buffer ptr param\n");
+ sst_set_stream_status(stream, SST_PLATFORM_INIT);
+ stream->stream_info.period_elapsed = sst_period_elapsed;
+ stream->stream_info.mad_substream = substream;
+ stream->stream_info.buffer_ptr = 0;
+ stream->stream_info.sfreq = substream->runtime->rate;
+ ret_val = stream->sstdrv_ops->pcm_control->device_control(
+ SST_SND_STREAM_INIT, &stream->stream_info);
+ if (ret_val)
+ pr_err("control_set ret error %d\n", ret_val);
+ return ret_val;
+
+}
+/* end -- helper functions */
+
+static int sst_platform_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime;
+ struct sst_runtime_stream *stream;
+ int ret_val = 0;
+
+ pr_debug("sst_platform_open called\n");
+ runtime = substream->runtime;
+ runtime->hw = sst_platform_pcm_hw;
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return -ENOMEM;
+ spin_lock_init(&stream->status_lock);
+ stream->stream_info.str_id = 0;
+ sst_set_stream_status(stream, SST_PLATFORM_INIT);
+ stream->stream_info.mad_substream = substream;
+ /* allocate memory for SST API set */
+ stream->sstdrv_ops = kzalloc(sizeof(*stream->sstdrv_ops),
+ GFP_KERNEL);
+ if (!stream->sstdrv_ops) {
+ pr_err("sst: mem allocation for ops fail\n");
+ kfree(stream);
+ return -ENOMEM;
+ }
+ stream->sstdrv_ops->vendor_id = MSIC_VENDOR_ID;
+ stream->sstdrv_ops->module_name = SST_CARD_NAMES;
+ /* registering with SST driver to get access to SST APIs to use */
+ ret_val = register_sst_card(stream->sstdrv_ops);
+ if (ret_val) {
+ pr_err("sst: sst card registration failed\n");
+ kfree(stream->sstdrv_ops);
+ kfree(stream);
+ return ret_val;
+ }
+ runtime->private_data = stream;
+ return snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+}
+
+static int sst_platform_close(struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val = 0, str_id;
+
+ pr_debug("sst_platform_close called\n");
+ stream = substream->runtime->private_data;
+ str_id = stream->stream_info.str_id;
+ if (str_id)
+ ret_val = stream->sstdrv_ops->pcm_control->close(str_id);
+ unregister_sst_card(stream->sstdrv_ops);
+ kfree(stream->sstdrv_ops);
+ kfree(stream);
+ return ret_val;
+}
+
+static int sst_platform_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val = 0, str_id;
+
+ pr_debug("sst_platform_pcm_prepare called\n");
+ stream = substream->runtime->private_data;
+ str_id = stream->stream_info.str_id;
+ if (stream->stream_info.str_id) {
+ ret_val = stream->sstdrv_ops->pcm_control->device_control(
+ SST_SND_DROP, &str_id);
+ return ret_val;
+ }
+
+ ret_val = sst_platform_alloc_stream(substream);
+ if (ret_val < 0)
+ return ret_val;
+ snprintf(substream->pcm->id, sizeof(substream->pcm->id),
+ "%d", stream->stream_info.str_id);
+
+ ret_val = sst_platform_init_stream(substream);
+ if (ret_val)
+ return ret_val;
+ substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER;
+ return ret_val;
+}
+
+static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int ret_val = 0, str_id;
+ struct sst_runtime_stream *stream;
+ int str_cmd, status;
+
+ pr_debug("sst_platform_pcm_trigger called\n");
+ stream = substream->runtime->private_data;
+ str_id = stream->stream_info.str_id;
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ pr_debug("sst: Trigger Start\n");
+ str_cmd = SST_SND_START;
+ status = SST_PLATFORM_RUNNING;
+ stream->stream_info.mad_substream = substream;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ pr_debug("sst: in stop\n");
+ str_cmd = SST_SND_DROP;
+ status = SST_PLATFORM_DROPPED;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ pr_debug("sst: in pause\n");
+ str_cmd = SST_SND_PAUSE;
+ status = SST_PLATFORM_PAUSED;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ pr_debug("sst: in pause release\n");
+ str_cmd = SST_SND_RESUME;
+ status = SST_PLATFORM_RUNNING;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ret_val = stream->sstdrv_ops->pcm_control->device_control(str_cmd,
+ &str_id);
+ if (!ret_val)
+ sst_set_stream_status(stream, status);
+
+ return ret_val;
+}
+
+
+static snd_pcm_uframes_t sst_platform_pcm_pointer
+ (struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val, status;
+ struct pcm_stream_info *str_info;
+
+ stream = substream->runtime->private_data;
+ status = sst_get_stream_status(stream);
+ if (status == SST_PLATFORM_INIT)
+ return 0;
+ str_info = &stream->stream_info;
+ ret_val = stream->sstdrv_ops->pcm_control->device_control(
+ SST_SND_BUFFER_POINTER, str_info);
+ if (ret_val) {
+ pr_err("sst: error code = %d\n", ret_val);
+ return ret_val;
+ }
+ return stream->stream_info.buffer_ptr;
+}
+
+static int sst_platform_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ memset(substream->runtime->dma_area, 0, params_buffer_bytes(params));
+
+ return 0;
+}
+
+static int sst_platform_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static struct snd_pcm_ops sst_platform_ops = {
+ .open = sst_platform_open,
+ .close = sst_platform_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .prepare = sst_platform_pcm_prepare,
+ .trigger = sst_platform_pcm_trigger,
+ .pointer = sst_platform_pcm_pointer,
+ .hw_params = sst_platform_pcm_hw_params,
+ .hw_free = sst_platform_pcm_hw_free,
+};
+
+static void sst_pcm_free(struct snd_pcm *pcm)
+{
+ pr_debug("sst_pcm_free called\n");
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int sst_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int retval = 0;
+
+ pr_debug("sst_pcm_new called\n");
+ if (dai->driver->playback.channels_min ||
+ dai->driver->capture.channels_min) {
+ retval = snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ SST_MIN_BUFFER, SST_MAX_BUFFER);
+ if (retval) {
+ pr_err("dma buffer allocationf fail\n");
+ return retval;
+ }
+ }
+ return retval;
+}
+struct snd_soc_platform_driver sst_soc_platform_drv = {
+ .ops = &sst_platform_ops,
+ .pcm_new = sst_pcm_new,
+ .pcm_free = sst_pcm_free,
+};
+
+static int sst_platform_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ pr_debug("sst_platform_probe called\n");
+ ret = snd_soc_register_platform(&pdev->dev, &sst_soc_platform_drv);
+ if (ret) {
+ pr_err("registering soc platform failed\n");
+ return ret;
+ }
+
+ ret = snd_soc_register_dais(&pdev->dev,
+ sst_platform_dai, ARRAY_SIZE(sst_platform_dai));
+ if (ret) {
+ pr_err("registering cpu dais failed\n");
+ snd_soc_unregister_platform(&pdev->dev);
+ }
+ return ret;
+}
+
+static int sst_platform_remove(struct platform_device *pdev)
+{
+
+ snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(sst_platform_dai));
+ snd_soc_unregister_platform(&pdev->dev);
+ pr_debug("sst_platform_remove success\n");
+ return 0;
+}
+
+static struct platform_driver sst_platform_driver = {
+ .driver = {
+ .name = "sst-platform",
+ .owner = THIS_MODULE,
+ },
+ .probe = sst_platform_probe,
+ .remove = sst_platform_remove,
+};
+
+static int __init sst_soc_platform_init(void)
+{
+ pr_debug("sst_soc_platform_init called\n");
+ return platform_driver_register(&sst_platform_driver);
+}
+module_init(sst_soc_platform_init);
+
+static void __exit sst_soc_platform_exit(void)
+{
+ platform_driver_unregister(&sst_platform_driver);
+ pr_debug("sst_soc_platform_exit success\n");
+}
+module_exit(sst_soc_platform_exit);
+
+MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver");
+MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sst-platform");
diff --git a/sound/soc/mid-x86/sst_platform.h b/sound/soc/mid-x86/sst_platform.h
new file mode 100644
index 00000000..df370286
--- /dev/null
+++ b/sound/soc/mid-x86/sst_platform.h
@@ -0,0 +1,63 @@
+/*
+ * sst_platform.h - Intel MID Platform driver header file
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@intel.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; version 2 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, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *
+ */
+
+#ifndef __SST_PLATFORMDRV_H__
+#define __SST_PLATFORMDRV_H__
+
+#define SST_MONO 1
+#define SST_STEREO 2
+#define SST_MAX_CAP 5
+
+#define SST_MIN_RATE 8000
+#define SST_MAX_RATE 48000
+#define SST_MIN_CHANNEL 1
+#define SST_MAX_CHANNEL 5
+#define SST_MAX_BUFFER (800*1024)
+#define SST_MIN_BUFFER (800*1024)
+#define SST_MIN_PERIOD_BYTES 32
+#define SST_MAX_PERIOD_BYTES SST_MAX_BUFFER
+#define SST_MIN_PERIODS 2
+#define SST_MAX_PERIODS (1024*2)
+#define SST_FIFO_SIZE 0
+#define SST_CARD_NAMES "intel_mid_card"
+#define MSIC_VENDOR_ID 3
+
+struct sst_runtime_stream {
+ int stream_status;
+ struct pcm_stream_info stream_info;
+ struct intel_sst_card_ops *sstdrv_ops;
+ spinlock_t status_lock;
+};
+
+enum sst_drv_status {
+ SST_PLATFORM_INIT = 1,
+ SST_PLATFORM_STARTED,
+ SST_PLATFORM_RUNNING,
+ SST_PLATFORM_PAUSED,
+ SST_PLATFORM_DROPPED,
+};
+
+#endif
diff --git a/sound/soc/nuc900/Kconfig b/sound/soc/nuc900/Kconfig
new file mode 100644
index 00000000..a0ed1c61
--- /dev/null
+++ b/sound/soc/nuc900/Kconfig
@@ -0,0 +1,27 @@
+##
+## NUC900 series AC97 API
+##
+config SND_SOC_NUC900
+ tristate "SoC Audio for NUC900 series"
+ depends on ARCH_W90X900
+ help
+ This option enables support for AC97 mode on the NUC900 SoC.
+
+config SND_SOC_NUC900_AC97
+ tristate
+ select AC97_BUS
+ select SND_AC97_CODEC
+ select SND_SOC_AC97_BUS
+
+
+##
+## Boards
+##
+config SND_SOC_NUC900EVB
+ tristate "NUC900 AC97 support for demo board"
+ depends on SND_SOC_NUC900
+ select SND_SOC_NUC900_AC97
+ select SND_SOC_AC97_CODEC
+ help
+ Select this option to enable audio (AC97) on the
+ NUC900 demoboard.
diff --git a/sound/soc/nuc900/Makefile b/sound/soc/nuc900/Makefile
new file mode 100644
index 00000000..7e46c715
--- /dev/null
+++ b/sound/soc/nuc900/Makefile
@@ -0,0 +1,11 @@
+# NUC900 series audio
+snd-soc-nuc900-pcm-objs := nuc900-pcm.o
+snd-soc-nuc900-ac97-objs := nuc900-ac97.o
+
+obj-$(CONFIG_SND_SOC_NUC900) += snd-soc-nuc900-pcm.o
+obj-$(CONFIG_SND_SOC_NUC900_AC97) += snd-soc-nuc900-ac97.o
+
+# Boards
+snd-soc-nuc900-audio-objs := nuc900-audio.o
+
+obj-$(CONFIG_SND_SOC_NUC900EVB) += snd-soc-nuc900-audio.o
diff --git a/sound/soc/nuc900/nuc900-ac97.c b/sound/soc/nuc900/nuc900-ac97.c
new file mode 100644
index 00000000..dac6732d
--- /dev/null
+++ b/sound/soc/nuc900/nuc900-ac97.c
@@ -0,0 +1,424 @@
+/*
+ * Copyright (c) 2009-2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/suspend.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <linux/clk.h>
+
+#include <mach/mfp.h>
+
+#include "nuc900-audio.h"
+
+static DEFINE_MUTEX(ac97_mutex);
+struct nuc900_audio *nuc900_ac97_data;
+
+static int nuc900_checkready(void)
+{
+ struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+
+ if (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS0) & CODEC_READY))
+ return -EPERM;
+
+ return 0;
+}
+
+/* AC97 controller reads codec register */
+static unsigned short nuc900_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+ unsigned long timeout = 0x10000, val;
+
+ mutex_lock(&ac97_mutex);
+
+ val = nuc900_checkready();
+ if (val) {
+ dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
+ goto out;
+ }
+
+ /* set the R_WB bit and write register index */
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, R_WB | reg);
+
+ /* set the valid frame bit and valid slots */
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+ val |= (VALID_FRAME | SLOT1_VALID);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val);
+
+ udelay(100);
+
+ /* polling the AC_R_FINISH */
+ while (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON) & AC_R_FINISH)
+ && timeout--)
+ mdelay(1);
+
+ if (!timeout) {
+ dev_err(nuc900_audio->dev, "AC97 read register time out !\n");
+ val = -EPERM;
+ goto out;
+ }
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0) ;
+ val &= ~SLOT1_VALID;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val);
+
+ if (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS1) >> 2 != reg) {
+ dev_err(nuc900_audio->dev,
+ "R_INDEX of REG_ACTL_ACIS1 not match!\n");
+ }
+
+ udelay(100);
+ val = (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS2) & 0xFFFF);
+
+out:
+ mutex_unlock(&ac97_mutex);
+ return val;
+}
+
+/* AC97 controller writes to codec register */
+static void nuc900_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+ unsigned long tmp, timeout = 0x10000;
+
+ mutex_lock(&ac97_mutex);
+
+ tmp = nuc900_checkready();
+ if (tmp)
+ dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
+
+ /* clear the R_WB bit and write register index */
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, reg);
+
+ /* write register value */
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS2, val);
+
+ /* set the valid frame bit and valid slots */
+ tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+ tmp |= SLOT1_VALID | SLOT2_VALID | VALID_FRAME;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+ udelay(100);
+
+ /* polling the AC_W_FINISH */
+ while ((AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON) & AC_W_FINISH)
+ && timeout--)
+ mdelay(1);
+
+ if (!timeout)
+ dev_err(nuc900_audio->dev, "AC97 write register time out !\n");
+
+ tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+ tmp &= ~(SLOT1_VALID | SLOT2_VALID);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+ mutex_unlock(&ac97_mutex);
+
+}
+
+static void nuc900_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+ unsigned long val;
+
+ mutex_lock(&ac97_mutex);
+
+ /* warm reset AC 97 */
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
+ val |= AC_W_RES;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
+
+ udelay(100);
+
+ val = nuc900_checkready();
+ if (val)
+ dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
+
+ mutex_unlock(&ac97_mutex);
+}
+
+static void nuc900_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+ unsigned long val;
+
+ mutex_lock(&ac97_mutex);
+
+ /* reset Audio Controller */
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+ val |= ACTL_RESET_BIT;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+ val &= (~ACTL_RESET_BIT);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+ /* reset AC-link interface */
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+ val |= AC_RESET;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+ val &= ~AC_RESET;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+ /* cold reset AC 97 */
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
+ val |= AC_C_RES;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
+ val &= (~AC_C_RES);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
+
+ udelay(100);
+
+ mutex_unlock(&ac97_mutex);
+
+}
+
+/* AC97 controller operations */
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = nuc900_ac97_read,
+ .write = nuc900_ac97_write,
+ .reset = nuc900_ac97_cold_reset,
+ .warm_reset = nuc900_ac97_warm_reset,
+}
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int nuc900_ac97_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+ int ret;
+ unsigned long val, tmp;
+
+ ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+ tmp |= (SLOT3_VALID | SLOT4_VALID | VALID_FRAME);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+ tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
+ tmp |= (P_DMA_END_IRQ | P_DMA_MIDDLE_IRQ);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, tmp);
+ val |= AC_PLAY;
+ } else {
+ tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
+ tmp |= (R_DMA_END_IRQ | R_DMA_MIDDLE_IRQ);
+
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, tmp);
+ val |= AC_RECORD;
+ }
+
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+ tmp &= ~(SLOT3_VALID | SLOT4_VALID);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, RESET_PRSR);
+ val &= ~AC_PLAY;
+ } else {
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, RESET_PRSR);
+ val &= ~AC_RECORD;
+ }
+
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int nuc900_ac97_probe(struct snd_soc_dai *dai)
+{
+ struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+ unsigned long val;
+
+ mutex_lock(&ac97_mutex);
+
+ /* enable unit clock */
+ clk_enable(nuc900_audio->clk);
+
+ /* enable audio controller and AC-link interface */
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+ val |= (IIS_AC_PIN_SEL | ACLINK_EN);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
+
+ mutex_unlock(&ac97_mutex);
+
+ return 0;
+}
+
+static int nuc900_ac97_remove(struct snd_soc_dai *dai)
+{
+ struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+
+ clk_disable(nuc900_audio->clk);
+ return 0;
+}
+
+static struct snd_soc_dai_ops nuc900_ac97_dai_ops = {
+ .trigger = nuc900_ac97_trigger,
+};
+
+static struct snd_soc_dai_driver nuc900_ac97_dai = {
+ .probe = nuc900_ac97_probe,
+ .remove = nuc900_ac97_remove,
+ .ac97_control = 1,
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+ .ops = &nuc900_ac97_dai_ops,
+};
+
+static int __devinit nuc900_ac97_drvprobe(struct platform_device *pdev)
+{
+ struct nuc900_audio *nuc900_audio;
+ int ret;
+
+ if (nuc900_ac97_data)
+ return -EBUSY;
+
+ nuc900_audio = kzalloc(sizeof(struct nuc900_audio), GFP_KERNEL);
+ if (!nuc900_audio)
+ return -ENOMEM;
+
+ spin_lock_init(&nuc900_audio->lock);
+
+ nuc900_audio->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!nuc900_audio->res) {
+ ret = -ENODEV;
+ goto out0;
+ }
+
+ if (!request_mem_region(nuc900_audio->res->start,
+ resource_size(nuc900_audio->res), pdev->name)) {
+ ret = -EBUSY;
+ goto out0;
+ }
+
+ nuc900_audio->mmio = ioremap(nuc900_audio->res->start,
+ resource_size(nuc900_audio->res));
+ if (!nuc900_audio->mmio) {
+ ret = -ENOMEM;
+ goto out1;
+ }
+
+ nuc900_audio->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(nuc900_audio->clk)) {
+ ret = PTR_ERR(nuc900_audio->clk);
+ goto out2;
+ }
+
+ nuc900_audio->irq_num = platform_get_irq(pdev, 0);
+ if (!nuc900_audio->irq_num) {
+ ret = -EBUSY;
+ goto out2;
+ }
+
+ nuc900_ac97_data = nuc900_audio;
+
+ ret = snd_soc_register_dai(&pdev->dev, &nuc900_ac97_dai);
+ if (ret)
+ goto out3;
+
+ mfp_set_groupg(nuc900_audio->dev); /* enbale ac97 multifunction pin*/
+
+ return 0;
+
+out3:
+ clk_put(nuc900_audio->clk);
+out2:
+ iounmap(nuc900_audio->mmio);
+out1:
+ release_mem_region(nuc900_audio->res->start,
+ resource_size(nuc900_audio->res));
+out0:
+ kfree(nuc900_audio);
+ return ret;
+}
+
+static int __devexit nuc900_ac97_drvremove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+
+ clk_put(nuc900_ac97_data->clk);
+ iounmap(nuc900_ac97_data->mmio);
+ release_mem_region(nuc900_ac97_data->res->start,
+ resource_size(nuc900_ac97_data->res));
+
+ kfree(nuc900_ac97_data);
+ nuc900_ac97_data = NULL;
+
+ return 0;
+}
+
+static struct platform_driver nuc900_ac97_driver = {
+ .driver = {
+ .name = "nuc900-ac97",
+ .owner = THIS_MODULE,
+ },
+ .probe = nuc900_ac97_drvprobe,
+ .remove = __devexit_p(nuc900_ac97_drvremove),
+};
+
+static int __init nuc900_ac97_init(void)
+{
+ return platform_driver_register(&nuc900_ac97_driver);
+}
+
+static void __exit nuc900_ac97_exit(void)
+{
+ platform_driver_unregister(&nuc900_ac97_driver);
+}
+
+module_init(nuc900_ac97_init);
+module_exit(nuc900_ac97_exit);
+
+MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
+MODULE_DESCRIPTION("NUC900 AC97 SoC driver!");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nuc900-ac97");
diff --git a/sound/soc/nuc900/nuc900-audio.c b/sound/soc/nuc900/nuc900-audio.c
new file mode 100644
index 00000000..38a2d0d8
--- /dev/null
+++ b/sound/soc/nuc900/nuc900-audio.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include "nuc900-audio.h"
+
+static struct snd_soc_dai_link nuc900evb_ac97_dai = {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "nuc900-ac97",
+ .codec_dai_name = "ac97-hifi",
+ .codec_name = "ac97-codec",
+ .platform_name = "nuc900-pcm-audio",
+};
+
+static struct snd_soc_card nuc900evb_audio_machine = {
+ .name = "NUC900EVB_AC97",
+ .dai_link = &nuc900evb_ac97_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *nuc900evb_asoc_dev;
+
+static int __init nuc900evb_audio_init(void)
+{
+ int ret;
+
+ ret = -ENOMEM;
+ nuc900evb_asoc_dev = platform_device_alloc("soc-audio", -1);
+ if (!nuc900evb_asoc_dev)
+ goto out;
+
+ /* nuc900 board audio device */
+ platform_set_drvdata(nuc900evb_asoc_dev, &nuc900evb_audio_machine);
+
+ ret = platform_device_add(nuc900evb_asoc_dev);
+
+ if (ret) {
+ platform_device_put(nuc900evb_asoc_dev);
+ nuc900evb_asoc_dev = NULL;
+ }
+
+out:
+ return ret;
+}
+
+static void __exit nuc900evb_audio_exit(void)
+{
+ platform_device_unregister(nuc900evb_asoc_dev);
+}
+
+module_init(nuc900evb_audio_init);
+module_exit(nuc900evb_audio_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("NUC900 Series ASoC audio support");
+MODULE_AUTHOR("Wan ZongShun");
diff --git a/sound/soc/nuc900/nuc900-audio.h b/sound/soc/nuc900/nuc900-audio.h
new file mode 100644
index 00000000..59f7e8ed
--- /dev/null
+++ b/sound/soc/nuc900/nuc900-audio.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
+ *
+ */
+
+#ifndef _NUC900_AUDIO_H
+#define _NUC900_AUDIO_H
+
+#include <linux/io.h>
+
+/* Audio Control Registers */
+#define ACTL_CON 0x00
+#define ACTL_RESET 0x04
+#define ACTL_RDSTB 0x08
+#define ACTL_RDST_LENGTH 0x0C
+#define ACTL_RDSTC 0x10
+#define ACTL_RSR 0x14
+#define ACTL_PDSTB 0x18
+#define ACTL_PDST_LENGTH 0x1C
+#define ACTL_PDSTC 0x20
+#define ACTL_PSR 0x24
+#define ACTL_IISCON 0x28
+#define ACTL_ACCON 0x2C
+#define ACTL_ACOS0 0x30
+#define ACTL_ACOS1 0x34
+#define ACTL_ACOS2 0x38
+#define ACTL_ACIS0 0x3C
+#define ACTL_ACIS1 0x40
+#define ACTL_ACIS2 0x44
+#define ACTL_COUNTER 0x48
+
+/* bit definition of REG_ACTL_CON register */
+#define R_DMA_IRQ 0x1000
+#define T_DMA_IRQ 0x0800
+#define IIS_AC_PIN_SEL 0x0100
+#define FIFO_TH 0x0080
+#define ADC_EN 0x0010
+#define M80_EN 0x0008
+#define ACLINK_EN 0x0004
+#define IIS_EN 0x0002
+
+/* bit definition of REG_ACTL_RESET register */
+#define W5691_PLAY 0x20000
+#define ACTL_RESET_BIT 0x10000
+#define RECORD_RIGHT_CHNNEL 0x08000
+#define RECORD_LEFT_CHNNEL 0x04000
+#define PLAY_RIGHT_CHNNEL 0x02000
+#define PLAY_LEFT_CHNNEL 0x01000
+#define DAC_PLAY 0x00800
+#define ADC_RECORD 0x00400
+#define M80_PLAY 0x00200
+#define AC_RECORD 0x00100
+#define AC_PLAY 0x00080
+#define IIS_RECORD 0x00040
+#define IIS_PLAY 0x00020
+#define DAC_RESET 0x00010
+#define ADC_RESET 0x00008
+#define M80_RESET 0x00004
+#define AC_RESET 0x00002
+#define IIS_RESET 0x00001
+
+/* bit definition of REG_ACTL_ACCON register */
+#define AC_BCLK_PU_EN 0x20
+#define AC_R_FINISH 0x10
+#define AC_W_FINISH 0x08
+#define AC_W_RES 0x04
+#define AC_C_RES 0x02
+
+/* bit definition of ACTL_RSR register */
+#define R_FIFO_EMPTY 0x04
+#define R_DMA_END_IRQ 0x02
+#define R_DMA_MIDDLE_IRQ 0x01
+
+/* bit definition of ACTL_PSR register */
+#define P_FIFO_EMPTY 0x04
+#define P_DMA_END_IRQ 0x02
+#define P_DMA_MIDDLE_IRQ 0x01
+
+/* bit definition of ACTL_ACOS0 register */
+#define SLOT1_VALID 0x01
+#define SLOT2_VALID 0x02
+#define SLOT3_VALID 0x04
+#define SLOT4_VALID 0x08
+#define VALID_FRAME 0x10
+
+/* bit definition of ACTL_ACOS1 register */
+#define R_WB 0x80
+
+#define CODEC_READY 0x10
+#define RESET_PRSR 0x00
+#define AUDIO_WRITE(addr, val) __raw_writel(val, addr)
+#define AUDIO_READ(addr) __raw_readl(addr)
+
+struct nuc900_audio {
+ void __iomem *mmio;
+ spinlock_t lock;
+ dma_addr_t dma_addr[2];
+ unsigned long buffersize[2];
+ unsigned long irq_num;
+ struct snd_pcm_substream *substream;
+ struct resource *res;
+ struct clk *clk;
+ struct device *dev;
+
+};
+
+extern struct nuc900_audio *nuc900_ac97_data;
+
+#endif /*end _NUC900_AUDIO_H */
diff --git a/sound/soc/nuc900/nuc900-pcm.c b/sound/soc/nuc900/nuc900-pcm.c
new file mode 100644
index 00000000..8263f56d
--- /dev/null
+++ b/sound/soc/nuc900/nuc900-pcm.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) 2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+
+#include "nuc900-audio.h"
+
+static const struct snd_pcm_hardware nuc900_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = 4*1024,
+ .period_bytes_min = 1*1024,
+ .period_bytes_max = 4*1024,
+ .periods_min = 1,
+ .periods_max = 1024,
+};
+
+static int nuc900_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nuc900_audio *nuc900_audio = runtime->private_data;
+ unsigned long flags;
+ int ret = 0;
+
+ ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ if (ret < 0)
+ return ret;
+
+ spin_lock_irqsave(&nuc900_audio->lock, flags);
+
+ nuc900_audio->substream = substream;
+ nuc900_audio->dma_addr[substream->stream] = runtime->dma_addr;
+ nuc900_audio->buffersize[substream->stream] =
+ params_buffer_bytes(params);
+
+ spin_unlock_irqrestore(&nuc900_audio->lock, flags);
+
+ return ret;
+}
+
+static void nuc900_update_dma_register(struct snd_pcm_substream *substream,
+ dma_addr_t dma_addr, size_t count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nuc900_audio *nuc900_audio = runtime->private_data;
+ void __iomem *mmio_addr, *mmio_len;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ mmio_addr = nuc900_audio->mmio + ACTL_PDSTB;
+ mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH;
+ } else {
+ mmio_addr = nuc900_audio->mmio + ACTL_RDSTB;
+ mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH;
+ }
+
+ AUDIO_WRITE(mmio_addr, dma_addr);
+ AUDIO_WRITE(mmio_len, count);
+}
+
+static void nuc900_dma_start(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nuc900_audio *nuc900_audio = runtime->private_data;
+ unsigned long val;
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+ val |= (T_DMA_IRQ | R_DMA_IRQ);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
+}
+
+static void nuc900_dma_stop(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nuc900_audio *nuc900_audio = runtime->private_data;
+ unsigned long val;
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+ val &= ~(T_DMA_IRQ | R_DMA_IRQ);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
+}
+
+static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct nuc900_audio *nuc900_audio = substream->runtime->private_data;
+ unsigned long val;
+
+ spin_lock(&nuc900_audio->lock);
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+
+ if (val & R_DMA_IRQ) {
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ);
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
+
+ if (val & R_DMA_MIDDLE_IRQ) {
+ val |= R_DMA_MIDDLE_IRQ;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
+ }
+
+ if (val & R_DMA_END_IRQ) {
+ val |= R_DMA_END_IRQ;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
+ }
+ } else if (val & T_DMA_IRQ) {
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ);
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
+
+ if (val & P_DMA_MIDDLE_IRQ) {
+ val |= P_DMA_MIDDLE_IRQ;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
+ }
+
+ if (val & P_DMA_END_IRQ) {
+ val |= P_DMA_END_IRQ;
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
+ }
+ } else {
+ dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n");
+ spin_unlock(&nuc900_audio->lock);
+ return IRQ_HANDLED;
+ }
+
+ spin_unlock(&nuc900_audio->lock);
+
+ snd_pcm_period_elapsed(substream);
+
+ return IRQ_HANDLED;
+}
+
+static int nuc900_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_lib_free_pages(substream);
+ return 0;
+}
+
+static int nuc900_dma_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nuc900_audio *nuc900_audio = runtime->private_data;
+ unsigned long flags, val;
+ int ret = 0;
+
+ spin_lock_irqsave(&nuc900_audio->lock, flags);
+
+ nuc900_update_dma_register(substream,
+ nuc900_audio->dma_addr[substream->stream],
+ nuc900_audio->buffersize[substream->stream]);
+
+ val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+
+ switch (runtime->channels) {
+ case 1:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
+ val |= PLAY_RIGHT_CHNNEL;
+ } else {
+ val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
+ val |= RECORD_RIGHT_CHNNEL;
+ }
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+ break;
+ case 2:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
+ else
+ val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
+ AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ spin_unlock_irqrestore(&nuc900_audio->lock, flags);
+ return ret;
+}
+
+static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ nuc900_dma_start(substream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ nuc900_dma_stop(substream);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+int nuc900_dma_getposition(struct snd_pcm_substream *substream,
+ dma_addr_t *src, dma_addr_t *dst)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nuc900_audio *nuc900_audio = runtime->private_data;
+
+ if (src != NULL)
+ *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC);
+
+ if (dst != NULL)
+ *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC);
+
+ return 0;
+}
+
+static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ dma_addr_t src, dst;
+ unsigned long res;
+
+ nuc900_dma_getposition(substream, &src, &dst);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ res = dst - runtime->dma_addr;
+ else
+ res = src - runtime->dma_addr;
+
+ return bytes_to_frames(substream->runtime, res);
+}
+
+static int nuc900_dma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nuc900_audio *nuc900_audio;
+
+ snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware);
+
+ nuc900_audio = nuc900_ac97_data;
+
+ if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt,
+ IRQF_DISABLED, "nuc900-dma", substream))
+ return -EBUSY;
+
+ runtime->private_data = nuc900_audio;
+
+ return 0;
+}
+
+static int nuc900_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct nuc900_audio *nuc900_audio = runtime->private_data;
+
+ free_irq(nuc900_audio->irq_num, substream);
+
+ return 0;
+}
+
+static int nuc900_dma_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops nuc900_dma_ops = {
+ .open = nuc900_dma_open,
+ .close = nuc900_dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = nuc900_dma_hw_params,
+ .hw_free = nuc900_dma_hw_free,
+ .prepare = nuc900_dma_prepare,
+ .trigger = nuc900_dma_trigger,
+ .pointer = nuc900_dma_pointer,
+ .mmap = nuc900_dma_mmap,
+};
+
+static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32);
+static int nuc900_dma_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &nuc900_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev, 4 * 1024, (4 * 1024) - 1);
+
+ return 0;
+}
+
+static struct snd_soc_platform_driver nuc900_soc_platform = {
+ .ops = &nuc900_dma_ops,
+ .pcm_new = nuc900_dma_new,
+ .pcm_free = nuc900_dma_free_dma_buffers,
+};
+
+static int __devinit nuc900_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &nuc900_soc_platform);
+}
+
+static int __devexit nuc900_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver nuc900_pcm_driver = {
+ .driver = {
+ .name = "nuc900-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = nuc900_soc_platform_probe,
+ .remove = __devexit_p(nuc900_soc_platform_remove),
+};
+
+static int __init nuc900_pcm_init(void)
+{
+ return platform_driver_register(&nuc900_pcm_driver);
+}
+module_init(nuc900_pcm_init);
+
+static void __exit nuc900_pcm_exit(void)
+{
+ platform_driver_unregister(&nuc900_pcm_driver);
+}
+module_exit(nuc900_pcm_exit);
+
+MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>");
+MODULE_DESCRIPTION("nuc900 Audio DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig
new file mode 100644
index 00000000..99054cf1
--- /dev/null
+++ b/sound/soc/omap/Kconfig
@@ -0,0 +1,135 @@
+config SND_OMAP_SOC
+ tristate "SoC Audio for the Texas Instruments OMAP chips"
+ depends on ARCH_OMAP
+
+config SND_OMAP_SOC_MCBSP
+ tristate
+ select OMAP_MCBSP
+
+config SND_OMAP_SOC_MCPDM
+ tristate
+
+config SND_OMAP_SOC_N810
+ tristate "SoC Audio support for Nokia N810"
+ depends on SND_OMAP_SOC && MACH_NOKIA_N810 && I2C
+ depends on OMAP_MUX
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TLV320AIC3X
+ help
+ Say Y if you want to add support for SoC audio on Nokia N810.
+
+config SND_OMAP_SOC_RX51
+ tristate "SoC Audio support for Nokia RX-51"
+ depends on SND_OMAP_SOC && MACH_NOKIA_RX51
+ select OMAP_MCBSP
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TLV320AIC3X
+ select SND_SOC_TPA6130A2
+ help
+ Say Y if you want to add support for SoC audio on Nokia RX-51
+ hardware. This is also known as Nokia N900 product.
+
+config SND_OMAP_SOC_AMS_DELTA
+ tristate "SoC Audio support for Amstrad E3 (Delta) videophone"
+ depends on SND_OMAP_SOC && MACH_AMS_DELTA
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_CX20442
+ help
+ Say Y if you want to add support for SoC audio device connected to
+ a handset and a speakerphone found on Amstrad E3 (Delta) videophone.
+
+ Note that in order to get those devices fully supported, you have to
+ build the kernel with standard serial port driver included and
+ configured for at least 4 ports. Then, from userspace, you must load
+ a line discipline #19 on the modem (ttyS3) serial line. The simplest
+ way to achieve this is to install util-linux-ng and use the included
+ ldattach utility. This can be started automatically from udev,
+ a simple rule like this one should do the trick (it does for me):
+ ACTION=="add", KERNEL=="controlC0", \
+ RUN+="/usr/sbin/ldattach 19 /dev/ttyS3"
+
+config SND_OMAP_SOC_OSK5912
+ tristate "SoC Audio support for omap osk5912"
+ depends on SND_OMAP_SOC && MACH_OMAP_OSK && I2C
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TLV320AIC23
+ help
+ Say Y if you want to add support for SoC audio on osk5912.
+
+config SND_OMAP_SOC_OVERO
+ tristate "SoC Audio support for Gumstix Overo and CompuLab CM-T35"
+ depends on TWL4030_CORE && SND_OMAP_SOC && (MACH_OVERO || MACH_CM_T35)
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TWL4030
+ help
+ Say Y if you want to add support for SoC audio on the
+ Gumstix Overo or CompuLab CM-T35
+
+config SND_OMAP_SOC_OMAP3EVM
+ tristate "SoC Audio support for OMAP3EVM board"
+ depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3EVM
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TWL4030
+ help
+ Say Y if you want to add support for SoC audio on the omap3evm board.
+
+config SND_OMAP_SOC_AM3517EVM
+ tristate "SoC Audio support for OMAP3517 / AM3517 EVM"
+ depends on SND_OMAP_SOC && MACH_OMAP3517EVM && I2C
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TLV320AIC23
+ help
+ Say Y if you want to add support for SoC audio on the OMAP3517 / AM3517
+ EVM.
+
+config SND_OMAP_SOC_SDP3430
+ tristate "SoC Audio support for Texas Instruments SDP3430"
+ depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_3430SDP
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TWL4030
+ help
+ Say Y if you want to add support for SoC audio on Texas Instruments
+ SDP3430.
+
+config SND_OMAP_SOC_SDP4430
+ tristate "SoC Audio support for Texas Instruments SDP4430"
+ depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_4430SDP
+ select SND_OMAP_SOC_MCPDM
+ select SND_SOC_TWL6040
+ help
+ Say Y if you want to add support for SoC audio on Texas Instruments
+ SDP4430.
+
+config SND_OMAP_SOC_OMAP3_PANDORA
+ tristate "SoC Audio support for OMAP3 Pandora"
+ depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3_PANDORA
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TWL4030
+ help
+ Say Y if you want to add support for SoC audio on the OMAP3 Pandora.
+
+config SND_OMAP_SOC_OMAP3_BEAGLE
+ tristate "SoC Audio support for OMAP3 Beagle and Devkit8000"
+ depends on TWL4030_CORE && SND_OMAP_SOC
+ depends on (MACH_OMAP3_BEAGLE || MACH_DEVKIT8000)
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TWL4030
+ help
+ Say Y if you want to add support for SoC audio on the Beagleboard or
+ the clone Devkit8000.
+
+config SND_OMAP_SOC_ZOOM2
+ tristate "SoC Audio support for Zoom2"
+ depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_ZOOM2
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TWL4030
+ help
+ Say Y if you want to add support for Soc audio on Zoom2 board.
+
+config SND_OMAP_SOC_IGEP0020
+ tristate "SoC Audio support for IGEP v2"
+ depends on TWL4030_CORE && SND_OMAP_SOC && MACH_IGEP0020
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TWL4030
+ help
+ Say Y if you want to add support for Soc audio on IGEP v2 board.
diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile
new file mode 100644
index 00000000..6c2c87ee
--- /dev/null
+++ b/sound/soc/omap/Makefile
@@ -0,0 +1,38 @@
+# OMAP Platform Support
+snd-soc-omap-objs := omap-pcm.o
+snd-soc-omap-mcbsp-objs := omap-mcbsp.o
+snd-soc-omap-mcpdm-objs := omap-mcpdm.o mcpdm.o
+
+obj-$(CONFIG_SND_OMAP_SOC) += snd-soc-omap.o
+obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o
+obj-$(CONFIG_SND_OMAP_SOC_MCPDM) += snd-soc-omap-mcpdm.o
+
+# OMAP Machine Support
+snd-soc-n810-objs := n810.o
+snd-soc-rx51-objs := rx51.o
+snd-soc-ams-delta-objs := ams-delta.o
+snd-soc-osk5912-objs := osk5912.o
+snd-soc-overo-objs := overo.o
+snd-soc-omap3evm-objs := omap3evm.o
+snd-soc-am3517evm-objs := am3517evm.o
+snd-soc-sdp3430-objs := sdp3430.o
+snd-soc-sdp4430-objs := sdp4430.o
+snd-soc-omap3pandora-objs := omap3pandora.o
+snd-soc-omap3beagle-objs := omap3beagle.o
+snd-soc-zoom2-objs := zoom2.o
+snd-soc-igep0020-objs := igep0020.o
+
+obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o
+obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o
+obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o
+obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o
+obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o
+obj-$(CONFIG_SND_OMAP_SOC_OMAP2EVM) += snd-soc-omap2evm.o
+obj-$(CONFIG_SND_OMAP_SOC_OMAP3EVM) += snd-soc-omap3evm.o
+obj-$(CONFIG_SND_OMAP_SOC_AM3517EVM) += snd-soc-am3517evm.o
+obj-$(CONFIG_SND_OMAP_SOC_SDP3430) += snd-soc-sdp3430.o
+obj-$(CONFIG_SND_OMAP_SOC_SDP4430) += snd-soc-sdp4430.o
+obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o
+obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o
+obj-$(CONFIG_SND_OMAP_SOC_ZOOM2) += snd-soc-zoom2.o
+obj-$(CONFIG_SND_OMAP_SOC_IGEP0020) += snd-soc-igep0020.o
diff --git a/sound/soc/omap/am3517evm.c b/sound/soc/omap/am3517evm.c
new file mode 100644
index 00000000..73dde4a1
--- /dev/null
+++ b/sound/soc/omap/am3517evm.c
@@ -0,0 +1,195 @@
+/*
+ * am3517evm.c -- ALSA SoC support for OMAP3517 / AM3517 EVM
+ *
+ * Author: Anuj Aggarwal <anuj.aggarwal@ti.com>
+ *
+ * Based on sound/soc/omap/beagle.c by Steve Sakoman
+ *
+ * Copyright (C) 2009 Texas Instruments Incorporated
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
+ * whether express or implied; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#include "../codecs/tlv320aic23.h"
+
+#define CODEC_CLOCK 12000000
+
+static int am3517evm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* Set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return ret;
+ }
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ CODEC_CLOCK, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_CLKR_SRC_CLKX, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set CPU system clock OMAP_MCBSP_CLKR_SRC_CLKX\n");
+ return ret;
+ }
+
+ snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_FSR_SRC_FSX, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set CPU system clock OMAP_MCBSP_FSR_SRC_FSX\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops am3517evm_ops = {
+ .hw_params = am3517evm_hw_params,
+};
+
+/* am3517evm machine dapm widgets */
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Line Out", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_MIC("Mic In", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Line Out connected to LLOUT, RLOUT */
+ {"Line Out", NULL, "LOUT"},
+ {"Line Out", NULL, "ROUT"},
+
+ {"LLINEIN", NULL, "Line In"},
+ {"RLINEIN", NULL, "Line In"},
+
+ {"MICIN", NULL, "Mic In"},
+};
+
+static int am3517evm_aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* Add am3517-evm specific widgets */
+ snd_soc_dapm_new_controls(dapm, tlv320aic23_dapm_widgets,
+ ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+ /* Set up davinci-evm specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ /* always connected */
+ snd_soc_dapm_enable_pin(dapm, "Line Out");
+ snd_soc_dapm_enable_pin(dapm, "Line In");
+ snd_soc_dapm_enable_pin(dapm, "Mic In");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link am3517evm_dai = {
+ .name = "TLV320AIC23",
+ .stream_name = "AIC23",
+ .cpu_dai_name ="omap-mcbsp-dai.0",
+ .codec_dai_name = "tlv320aic23-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "tlv320aic23-codec.2-001a",
+ .init = am3517evm_aic23_init,
+ .ops = &am3517evm_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_am3517evm = {
+ .name = "am3517evm",
+ .dai_link = &am3517evm_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *am3517evm_snd_device;
+
+static int __init am3517evm_soc_init(void)
+{
+ int ret;
+
+ if (!machine_is_omap3517evm())
+ return -ENODEV;
+ pr_info("OMAP3517 / AM3517 EVM SoC init\n");
+
+ am3517evm_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!am3517evm_snd_device) {
+ printk(KERN_ERR "Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(am3517evm_snd_device, &snd_soc_am3517evm);
+
+ ret = platform_device_add(am3517evm_snd_device);
+ if (ret)
+ goto err1;
+
+ return 0;
+
+err1:
+ printk(KERN_ERR "Unable to add platform device\n");
+ platform_device_put(am3517evm_snd_device);
+
+ return ret;
+}
+
+static void __exit am3517evm_soc_exit(void)
+{
+ platform_device_unregister(am3517evm_snd_device);
+}
+
+module_init(am3517evm_soc_init);
+module_exit(am3517evm_soc_exit);
+
+MODULE_AUTHOR("Anuj Aggarwal <anuj.aggarwal@ti.com>");
+MODULE_DESCRIPTION("ALSA SoC OMAP3517 / AM3517 EVM");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/omap/ams-delta.c b/sound/soc/omap/ams-delta.c
new file mode 100644
index 00000000..462cbcbe
--- /dev/null
+++ b/sound/soc/omap/ams-delta.c
@@ -0,0 +1,660 @@
+/*
+ * ams-delta.c -- SoC audio for Amstrad E3 (Delta) videophone
+ *
+ * Copyright (C) 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
+ *
+ * Initially based on sound/soc/omap/osk5912.x
+ * Copyright (C) 2008 Mistral Solutions
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/gpio.h>
+#include <linux/spinlock.h>
+#include <linux/tty.h>
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+
+#include <plat/board-ams-delta.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+#include "../codecs/cx20442.h"
+
+
+/* Board specific DAPM widgets */
+static const struct snd_soc_dapm_widget ams_delta_dapm_widgets[] = {
+ /* Handset */
+ SND_SOC_DAPM_MIC("Mouthpiece", NULL),
+ SND_SOC_DAPM_HP("Earpiece", NULL),
+ /* Handsfree/Speakerphone */
+ SND_SOC_DAPM_MIC("Microphone", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+/* How they are connected to codec pins */
+static const struct snd_soc_dapm_route ams_delta_audio_map[] = {
+ {"TELIN", NULL, "Mouthpiece"},
+ {"Earpiece", NULL, "TELOUT"},
+
+ {"MIC", NULL, "Microphone"},
+ {"Speaker", NULL, "SPKOUT"},
+};
+
+/*
+ * Controls, functional after the modem line discipline is activated.
+ */
+
+/* Virtual switch: audio input/output constellations */
+static const char *ams_delta_audio_mode[] =
+ {"Mixed", "Handset", "Handsfree", "Speakerphone"};
+
+/* Selection <-> pin translation */
+#define AMS_DELTA_MOUTHPIECE 0
+#define AMS_DELTA_EARPIECE 1
+#define AMS_DELTA_MICROPHONE 2
+#define AMS_DELTA_SPEAKER 3
+#define AMS_DELTA_AGC 4
+
+#define AMS_DELTA_MIXED ((1 << AMS_DELTA_EARPIECE) | \
+ (1 << AMS_DELTA_MICROPHONE))
+#define AMS_DELTA_HANDSET ((1 << AMS_DELTA_MOUTHPIECE) | \
+ (1 << AMS_DELTA_EARPIECE))
+#define AMS_DELTA_HANDSFREE ((1 << AMS_DELTA_MICROPHONE) | \
+ (1 << AMS_DELTA_SPEAKER))
+#define AMS_DELTA_SPEAKERPHONE (AMS_DELTA_HANDSFREE | (1 << AMS_DELTA_AGC))
+
+static const unsigned short ams_delta_audio_mode_pins[] = {
+ AMS_DELTA_MIXED,
+ AMS_DELTA_HANDSET,
+ AMS_DELTA_HANDSFREE,
+ AMS_DELTA_SPEAKERPHONE,
+};
+
+static unsigned short ams_delta_audio_agc;
+
+static int ams_delta_set_audio_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ struct soc_enum *control = (struct soc_enum *)kcontrol->private_value;
+ unsigned short pins;
+ int pin, changed = 0;
+
+ /* Refuse any mode changes if we are not able to control the codec. */
+ if (!codec->hw_write)
+ return -EUNATCH;
+
+ if (ucontrol->value.enumerated.item[0] >= control->max)
+ return -EINVAL;
+
+ mutex_lock(&codec->mutex);
+
+ /* Translate selection to bitmap */
+ pins = ams_delta_audio_mode_pins[ucontrol->value.enumerated.item[0]];
+
+ /* Setup pins after corresponding bits if changed */
+ pin = !!(pins & (1 << AMS_DELTA_MOUTHPIECE));
+ if (pin != snd_soc_dapm_get_pin_status(dapm, "Mouthpiece")) {
+ changed = 1;
+ if (pin)
+ snd_soc_dapm_enable_pin(dapm, "Mouthpiece");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Mouthpiece");
+ }
+ pin = !!(pins & (1 << AMS_DELTA_EARPIECE));
+ if (pin != snd_soc_dapm_get_pin_status(dapm, "Earpiece")) {
+ changed = 1;
+ if (pin)
+ snd_soc_dapm_enable_pin(dapm, "Earpiece");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Earpiece");
+ }
+ pin = !!(pins & (1 << AMS_DELTA_MICROPHONE));
+ if (pin != snd_soc_dapm_get_pin_status(dapm, "Microphone")) {
+ changed = 1;
+ if (pin)
+ snd_soc_dapm_enable_pin(dapm, "Microphone");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Microphone");
+ }
+ pin = !!(pins & (1 << AMS_DELTA_SPEAKER));
+ if (pin != snd_soc_dapm_get_pin_status(dapm, "Speaker")) {
+ changed = 1;
+ if (pin)
+ snd_soc_dapm_enable_pin(dapm, "Speaker");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Speaker");
+ }
+ pin = !!(pins & (1 << AMS_DELTA_AGC));
+ if (pin != ams_delta_audio_agc) {
+ ams_delta_audio_agc = pin;
+ changed = 1;
+ if (pin)
+ snd_soc_dapm_enable_pin(dapm, "AGCIN");
+ else
+ snd_soc_dapm_disable_pin(dapm, "AGCIN");
+ }
+ if (changed)
+ snd_soc_dapm_sync(dapm);
+
+ mutex_unlock(&codec->mutex);
+
+ return changed;
+}
+
+static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ unsigned short pins, mode;
+
+ pins = ((snd_soc_dapm_get_pin_status(dapm, "Mouthpiece") <<
+ AMS_DELTA_MOUTHPIECE) |
+ (snd_soc_dapm_get_pin_status(dapm, "Earpiece") <<
+ AMS_DELTA_EARPIECE));
+ if (pins)
+ pins |= (snd_soc_dapm_get_pin_status(dapm, "Microphone") <<
+ AMS_DELTA_MICROPHONE);
+ else
+ pins = ((snd_soc_dapm_get_pin_status(dapm, "Microphone") <<
+ AMS_DELTA_MICROPHONE) |
+ (snd_soc_dapm_get_pin_status(dapm, "Speaker") <<
+ AMS_DELTA_SPEAKER) |
+ (ams_delta_audio_agc << AMS_DELTA_AGC));
+
+ for (mode = 0; mode < ARRAY_SIZE(ams_delta_audio_mode); mode++)
+ if (pins == ams_delta_audio_mode_pins[mode])
+ break;
+
+ if (mode >= ARRAY_SIZE(ams_delta_audio_mode))
+ return -EINVAL;
+
+ ucontrol->value.enumerated.item[0] = mode;
+
+ return 0;
+}
+
+static const struct soc_enum ams_delta_audio_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ams_delta_audio_mode),
+ ams_delta_audio_mode),
+};
+
+static const struct snd_kcontrol_new ams_delta_audio_controls[] = {
+ SOC_ENUM_EXT("Audio Mode", ams_delta_audio_enum[0],
+ ams_delta_get_audio_mode, ams_delta_set_audio_mode),
+};
+
+/* Hook switch */
+static struct snd_soc_jack ams_delta_hook_switch;
+static struct snd_soc_jack_gpio ams_delta_hook_switch_gpios[] = {
+ {
+ .gpio = 4,
+ .name = "hook_switch",
+ .report = SND_JACK_HEADSET,
+ .invert = 1,
+ .debounce_time = 150,
+ }
+};
+
+/* After we are able to control the codec over the modem,
+ * the hook switch can be used for dynamic DAPM reconfiguration. */
+static struct snd_soc_jack_pin ams_delta_hook_switch_pins[] = {
+ /* Handset */
+ {
+ .pin = "Mouthpiece",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Earpiece",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ /* Handsfree */
+ {
+ .pin = "Microphone",
+ .mask = SND_JACK_MICROPHONE,
+ .invert = 1,
+ },
+ {
+ .pin = "Speaker",
+ .mask = SND_JACK_HEADPHONE,
+ .invert = 1,
+ },
+};
+
+
+/*
+ * Modem line discipline, required for making above controls functional.
+ * Activated from userspace with ldattach, possibly invoked from udev rule.
+ */
+
+/* To actually apply any modem controlled configuration changes to the codec,
+ * we must connect codec DAI pins to the modem for a moment. Be careful not
+ * to interfere with our digital mute function that shares the same hardware. */
+static struct timer_list cx81801_timer;
+static bool cx81801_cmd_pending;
+static bool ams_delta_muted;
+static DEFINE_SPINLOCK(ams_delta_lock);
+
+static void cx81801_timeout(unsigned long data)
+{
+ int muted;
+
+ spin_lock(&ams_delta_lock);
+ cx81801_cmd_pending = 0;
+ muted = ams_delta_muted;
+ spin_unlock(&ams_delta_lock);
+
+ /* Reconnect the codec DAI back from the modem to the CPU DAI
+ * only if digital mute still off */
+ if (!muted)
+ ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC, 0);
+}
+
+/*
+ * Used for passing a codec structure pointer
+ * from the board initialization code to the tty line discipline.
+ */
+static struct snd_soc_codec *cx20442_codec;
+
+/* Line discipline .open() */
+static int cx81801_open(struct tty_struct *tty)
+{
+ int ret;
+
+ if (!cx20442_codec)
+ return -ENODEV;
+
+ /*
+ * Pass the codec structure pointer for use by other ldisc callbacks,
+ * both the card and the codec specific parts.
+ */
+ tty->disc_data = cx20442_codec;
+
+ ret = v253_ops.open(tty);
+
+ if (ret < 0)
+ tty->disc_data = NULL;
+
+ return ret;
+}
+
+/* Line discipline .close() */
+static void cx81801_close(struct tty_struct *tty)
+{
+ struct snd_soc_codec *codec = tty->disc_data;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ del_timer_sync(&cx81801_timer);
+
+ /* Prevent the hook switch from further changing the DAPM pins */
+ INIT_LIST_HEAD(&ams_delta_hook_switch.pins);
+
+ if (!codec)
+ return;
+
+ v253_ops.close(tty);
+
+ /* Revert back to default audio input/output constellation */
+ snd_soc_dapm_disable_pin(dapm, "Mouthpiece");
+ snd_soc_dapm_enable_pin(dapm, "Earpiece");
+ snd_soc_dapm_enable_pin(dapm, "Microphone");
+ snd_soc_dapm_disable_pin(dapm, "Speaker");
+ snd_soc_dapm_disable_pin(dapm, "AGCIN");
+ snd_soc_dapm_sync(dapm);
+}
+
+/* Line discipline .hangup() */
+static int cx81801_hangup(struct tty_struct *tty)
+{
+ cx81801_close(tty);
+ return 0;
+}
+
+/* Line discipline .recieve_buf() */
+static void cx81801_receive(struct tty_struct *tty,
+ const unsigned char *cp, char *fp, int count)
+{
+ struct snd_soc_codec *codec = tty->disc_data;
+ const unsigned char *c;
+ int apply, ret;
+
+ if (!codec)
+ return;
+
+ if (!codec->hw_write) {
+ /* First modem response, complete setup procedure */
+
+ /* Initialize timer used for config pulse generation */
+ setup_timer(&cx81801_timer, cx81801_timeout, 0);
+
+ v253_ops.receive_buf(tty, cp, fp, count);
+
+ /* Link hook switch to DAPM pins */
+ ret = snd_soc_jack_add_pins(&ams_delta_hook_switch,
+ ARRAY_SIZE(ams_delta_hook_switch_pins),
+ ams_delta_hook_switch_pins);
+ if (ret)
+ dev_warn(codec->dev,
+ "Failed to link hook switch to DAPM pins, "
+ "will continue with hook switch unlinked.\n");
+
+ return;
+ }
+
+ v253_ops.receive_buf(tty, cp, fp, count);
+
+ for (c = &cp[count - 1]; c >= cp; c--) {
+ if (*c != '\r')
+ continue;
+ /* Complete modem response received, apply config to codec */
+
+ spin_lock_bh(&ams_delta_lock);
+ mod_timer(&cx81801_timer, jiffies + msecs_to_jiffies(150));
+ apply = !ams_delta_muted && !cx81801_cmd_pending;
+ cx81801_cmd_pending = 1;
+ spin_unlock_bh(&ams_delta_lock);
+
+ /* Apply config pulse by connecting the codec to the modem
+ * if not already done */
+ if (apply)
+ ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC,
+ AMS_DELTA_LATCH2_MODEM_CODEC);
+ break;
+ }
+}
+
+/* Line discipline .write_wakeup() */
+static void cx81801_wakeup(struct tty_struct *tty)
+{
+ v253_ops.write_wakeup(tty);
+}
+
+static struct tty_ldisc_ops cx81801_ops = {
+ .magic = TTY_LDISC_MAGIC,
+ .name = "cx81801",
+ .owner = THIS_MODULE,
+ .open = cx81801_open,
+ .close = cx81801_close,
+ .hangup = cx81801_hangup,
+ .receive_buf = cx81801_receive,
+ .write_wakeup = cx81801_wakeup,
+};
+
+
+/*
+ * Even if not very useful, the sound card can still work without any of the
+ * above functonality activated. You can still control its audio input/output
+ * constellation and speakerphone gain from userspace by issuing AT commands
+ * over the modem port.
+ */
+
+static int ams_delta_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ /* Set cpu DAI configuration */
+ return snd_soc_dai_set_fmt(rtd->cpu_dai,
+ SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+}
+
+static struct snd_soc_ops ams_delta_ops = {
+ .hw_params = ams_delta_hw_params,
+};
+
+
+/* Board specific codec bias level control */
+static int ams_delta_set_bias_level(struct snd_soc_card *card,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_codec *codec = card->rtd->codec;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF)
+ ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_NRESET,
+ AMS_DELTA_LATCH2_MODEM_NRESET);
+ break;
+ case SND_SOC_BIAS_OFF:
+ if (codec->dapm.bias_level != SND_SOC_BIAS_OFF)
+ ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_NRESET,
+ 0);
+ }
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+/* Digital mute implemented using modem/CPU multiplexer.
+ * Shares hardware with codec config pulse generation */
+static bool ams_delta_muted = 1;
+
+static int ams_delta_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ int apply;
+
+ if (ams_delta_muted == mute)
+ return 0;
+
+ spin_lock_bh(&ams_delta_lock);
+ ams_delta_muted = mute;
+ apply = !cx81801_cmd_pending;
+ spin_unlock_bh(&ams_delta_lock);
+
+ if (apply)
+ ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC,
+ mute ? AMS_DELTA_LATCH2_MODEM_CODEC : 0);
+ return 0;
+}
+
+/* Our codec DAI probably doesn't have its own .ops structure */
+static struct snd_soc_dai_ops ams_delta_dai_ops = {
+ .digital_mute = ams_delta_digital_mute,
+};
+
+/* Will be used if the codec ever has its own digital_mute function */
+static int ams_delta_startup(struct snd_pcm_substream *substream)
+{
+ return ams_delta_digital_mute(NULL, 0);
+}
+
+static void ams_delta_shutdown(struct snd_pcm_substream *substream)
+{
+ ams_delta_digital_mute(NULL, 1);
+}
+
+
+/*
+ * Card initialization
+ */
+
+static int ams_delta_cx20442_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+ /* Codec is ready, now add/activate board specific controls */
+
+ /* Store a pointer to the codec structure for tty ldisc use */
+ cx20442_codec = codec;
+
+ /* Set up digital mute if not provided by the codec */
+ if (!codec_dai->driver->ops) {
+ codec_dai->driver->ops = &ams_delta_dai_ops;
+ } else {
+ ams_delta_ops.startup = ams_delta_startup;
+ ams_delta_ops.shutdown = ams_delta_shutdown;
+ }
+
+ /* Set codec bias level */
+ ams_delta_set_bias_level(card, SND_SOC_BIAS_STANDBY);
+
+ /* Add hook switch - can be used to control the codec from userspace
+ * even if line discipline fails */
+ ret = snd_soc_jack_new(rtd->codec, "hook_switch",
+ SND_JACK_HEADSET, &ams_delta_hook_switch);
+ if (ret)
+ dev_warn(card->dev,
+ "Failed to allocate resources for hook switch, "
+ "will continue without one.\n");
+ else {
+ ret = snd_soc_jack_add_gpios(&ams_delta_hook_switch,
+ ARRAY_SIZE(ams_delta_hook_switch_gpios),
+ ams_delta_hook_switch_gpios);
+ if (ret)
+ dev_warn(card->dev,
+ "Failed to set up hook switch GPIO line, "
+ "will continue with hook switch inactive.\n");
+ }
+
+ /* Register optional line discipline for over the modem control */
+ ret = tty_register_ldisc(N_V253, &cx81801_ops);
+ if (ret) {
+ dev_warn(card->dev,
+ "Failed to register line discipline, "
+ "will continue without any controls.\n");
+ return 0;
+ }
+
+ /* Add board specific DAPM widgets and routes */
+ ret = snd_soc_dapm_new_controls(dapm, ams_delta_dapm_widgets,
+ ARRAY_SIZE(ams_delta_dapm_widgets));
+ if (ret) {
+ dev_warn(card->dev,
+ "Failed to register DAPM controls, "
+ "will continue without any.\n");
+ return 0;
+ }
+
+ ret = snd_soc_dapm_add_routes(dapm, ams_delta_audio_map,
+ ARRAY_SIZE(ams_delta_audio_map));
+ if (ret) {
+ dev_warn(card->dev,
+ "Failed to set up DAPM routes, "
+ "will continue with codec default map.\n");
+ return 0;
+ }
+
+ /* Set up initial pin constellation */
+ snd_soc_dapm_disable_pin(dapm, "Mouthpiece");
+ snd_soc_dapm_enable_pin(dapm, "Earpiece");
+ snd_soc_dapm_enable_pin(dapm, "Microphone");
+ snd_soc_dapm_disable_pin(dapm, "Speaker");
+ snd_soc_dapm_disable_pin(dapm, "AGCIN");
+ snd_soc_dapm_disable_pin(dapm, "AGCOUT");
+ snd_soc_dapm_sync(dapm);
+
+ /* Add virtual switch */
+ ret = snd_soc_add_controls(codec, ams_delta_audio_controls,
+ ARRAY_SIZE(ams_delta_audio_controls));
+ if (ret)
+ dev_warn(card->dev,
+ "Failed to register audio mode control, "
+ "will continue without it.\n");
+
+ return 0;
+}
+
+/* DAI glue - connects codec <--> CPU */
+static struct snd_soc_dai_link ams_delta_dai_link = {
+ .name = "CX20442",
+ .stream_name = "CX20442",
+ .cpu_dai_name ="omap-mcbsp-dai.0",
+ .codec_dai_name = "cx20442-voice",
+ .init = ams_delta_cx20442_init,
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "cx20442-codec",
+ .ops = &ams_delta_ops,
+};
+
+/* Audio card driver */
+static struct snd_soc_card ams_delta_audio_card = {
+ .name = "AMS_DELTA",
+ .dai_link = &ams_delta_dai_link,
+ .num_links = 1,
+ .set_bias_level = ams_delta_set_bias_level,
+};
+
+/* Module init/exit */
+static struct platform_device *ams_delta_audio_platform_device;
+static struct platform_device *cx20442_platform_device;
+
+static int __init ams_delta_module_init(void)
+{
+ int ret;
+
+ if (!(machine_is_ams_delta()))
+ return -ENODEV;
+
+ ams_delta_audio_platform_device =
+ platform_device_alloc("soc-audio", -1);
+ if (!ams_delta_audio_platform_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(ams_delta_audio_platform_device,
+ &ams_delta_audio_card);
+
+ ret = platform_device_add(ams_delta_audio_platform_device);
+ if (ret)
+ goto err;
+
+ /*
+ * Codec platform device could be registered from elsewhere (board?),
+ * but I do it here as it makes sense only if used with the card.
+ */
+ cx20442_platform_device =
+ platform_device_register_simple("cx20442-codec", -1, NULL, 0);
+ return 0;
+err:
+ platform_device_put(ams_delta_audio_platform_device);
+ return ret;
+}
+module_init(ams_delta_module_init);
+
+static void __exit ams_delta_module_exit(void)
+{
+ if (tty_unregister_ldisc(N_V253) != 0)
+ dev_warn(&ams_delta_audio_platform_device->dev,
+ "failed to unregister V253 line discipline\n");
+
+ snd_soc_jack_free_gpios(&ams_delta_hook_switch,
+ ARRAY_SIZE(ams_delta_hook_switch_gpios),
+ ams_delta_hook_switch_gpios);
+
+ /* Keep modem power on */
+ ams_delta_set_bias_level(&ams_delta_audio_card, SND_SOC_BIAS_STANDBY);
+
+ platform_device_unregister(cx20442_platform_device);
+ platform_device_unregister(ams_delta_audio_platform_device);
+}
+module_exit(ams_delta_module_exit);
+
+MODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>");
+MODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/igep0020.c b/sound/soc/omap/igep0020.c
new file mode 100644
index 00000000..0ae34702
--- /dev/null
+++ b/sound/soc/omap/igep0020.c
@@ -0,0 +1,137 @@
+/*
+ * igep0020.c -- SoC audio for IGEP v2
+ *
+ * Based on sound/soc/omap/overo.c by Steve Sakoman
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+static int igep2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* Set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return ret;
+ }
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops igep2_ops = {
+ .hw_params = igep2_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link igep2_dai = {
+ .name = "TWL4030",
+ .stream_name = "TWL4030",
+ .cpu_dai_name = "omap-mcbsp-dai.1",
+ .codec_dai_name = "twl4030-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "twl4030-codec",
+ .ops = &igep2_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_card_igep2 = {
+ .name = "igep2",
+ .dai_link = &igep2_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *igep2_snd_device;
+
+static int __init igep2_soc_init(void)
+{
+ int ret;
+
+ if (!machine_is_igep0020())
+ return -ENODEV;
+ printk(KERN_INFO "IGEP v2 SoC init\n");
+
+ igep2_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!igep2_snd_device) {
+ printk(KERN_ERR "Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(igep2_snd_device, &snd_soc_card_igep2);
+
+ ret = platform_device_add(igep2_snd_device);
+ if (ret)
+ goto err1;
+
+ return 0;
+
+err1:
+ printk(KERN_ERR "Unable to add platform device\n");
+ platform_device_put(igep2_snd_device);
+
+ return ret;
+}
+module_init(igep2_soc_init);
+
+static void __exit igep2_soc_exit(void)
+{
+ platform_device_unregister(igep2_snd_device);
+}
+module_exit(igep2_soc_exit);
+
+MODULE_AUTHOR("Enric Balletbo i Serra <eballetbo@iseebcn.com>");
+MODULE_DESCRIPTION("ALSA SoC IGEP v2");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/mcpdm.c b/sound/soc/omap/mcpdm.c
new file mode 100644
index 00000000..928f0370
--- /dev/null
+++ b/sound/soc/omap/mcpdm.c
@@ -0,0 +1,470 @@
+/*
+ * mcpdm.c -- McPDM interface driver
+ *
+ * Author: Jorge Eduardo Candelaria <x0107209@ti.com>
+ * Copyright (C) 2009 - Texas Instruments, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+
+#include "mcpdm.h"
+
+static struct omap_mcpdm *mcpdm;
+
+static inline void omap_mcpdm_write(u16 reg, u32 val)
+{
+ __raw_writel(val, mcpdm->io_base + reg);
+}
+
+static inline int omap_mcpdm_read(u16 reg)
+{
+ return __raw_readl(mcpdm->io_base + reg);
+}
+
+static void omap_mcpdm_reg_dump(void)
+{
+ dev_dbg(mcpdm->dev, "***********************\n");
+ dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_IRQSTATUS_RAW));
+ dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_IRQSTATUS));
+ dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_IRQENABLE_SET));
+ dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_IRQENABLE_CLR));
+ dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_IRQWAKE_EN));
+ dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_DMAENABLE_SET));
+ dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_DMAENABLE_CLR));
+ dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_DMAWAKEEN));
+ dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_CTRL));
+ dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_DN_DATA));
+ dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_UP_DATA));
+ dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_FIFO_CTRL_DN));
+ dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_FIFO_CTRL_UP));
+ dev_dbg(mcpdm->dev, "DN_OFFSET: 0x%04x\n",
+ omap_mcpdm_read(MCPDM_DN_OFFSET));
+ dev_dbg(mcpdm->dev, "***********************\n");
+}
+
+/*
+ * Takes the McPDM module in and out of reset state.
+ * Uplink and downlink can be reset individually.
+ */
+static void omap_mcpdm_reset_capture(int reset)
+{
+ int ctrl = omap_mcpdm_read(MCPDM_CTRL);
+
+ if (reset)
+ ctrl |= SW_UP_RST;
+ else
+ ctrl &= ~SW_UP_RST;
+
+ omap_mcpdm_write(MCPDM_CTRL, ctrl);
+}
+
+static void omap_mcpdm_reset_playback(int reset)
+{
+ int ctrl = omap_mcpdm_read(MCPDM_CTRL);
+
+ if (reset)
+ ctrl |= SW_DN_RST;
+ else
+ ctrl &= ~SW_DN_RST;
+
+ omap_mcpdm_write(MCPDM_CTRL, ctrl);
+}
+
+/*
+ * Enables the transfer through the PDM interface to/from the Phoenix
+ * codec by enabling the corresponding UP or DN channels.
+ */
+void omap_mcpdm_start(int stream)
+{
+ int ctrl = omap_mcpdm_read(MCPDM_CTRL);
+
+ if (stream)
+ ctrl |= mcpdm->up_channels;
+ else
+ ctrl |= mcpdm->dn_channels;
+
+ omap_mcpdm_write(MCPDM_CTRL, ctrl);
+}
+
+/*
+ * Disables the transfer through the PDM interface to/from the Phoenix
+ * codec by disabling the corresponding UP or DN channels.
+ */
+void omap_mcpdm_stop(int stream)
+{
+ int ctrl = omap_mcpdm_read(MCPDM_CTRL);
+
+ if (stream)
+ ctrl &= ~mcpdm->up_channels;
+ else
+ ctrl &= ~mcpdm->dn_channels;
+
+ omap_mcpdm_write(MCPDM_CTRL, ctrl);
+}
+
+/*
+ * Configures McPDM uplink for audio recording.
+ * This function should be called before omap_mcpdm_start.
+ */
+int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink)
+{
+ int irq_mask = 0;
+ int ctrl;
+
+ if (!uplink)
+ return -EINVAL;
+
+ mcpdm->uplink = uplink;
+
+ /* Enable irq request generation */
+ irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK;
+ omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask);
+
+ /* Configure uplink threshold */
+ if (uplink->threshold > UP_THRES_MAX)
+ uplink->threshold = UP_THRES_MAX;
+
+ omap_mcpdm_write(MCPDM_FIFO_CTRL_UP, uplink->threshold);
+
+ /* Configure DMA controller */
+ omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_UP_ENABLE);
+
+ /* Set pdm out format */
+ ctrl = omap_mcpdm_read(MCPDM_CTRL);
+ ctrl &= ~PDMOUTFORMAT;
+ ctrl |= uplink->format & PDMOUTFORMAT;
+
+ /* Uplink channels */
+ mcpdm->up_channels = uplink->channels & (PDM_UP_MASK | PDM_STATUS_MASK);
+
+ omap_mcpdm_write(MCPDM_CTRL, ctrl);
+
+ return 0;
+}
+
+/*
+ * Configures McPDM downlink for audio playback.
+ * This function should be called before omap_mcpdm_start.
+ */
+int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink)
+{
+ int irq_mask = 0;
+ int ctrl;
+
+ if (!downlink)
+ return -EINVAL;
+
+ mcpdm->downlink = downlink;
+
+ /* Enable irq request generation */
+ irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK;
+ omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask);
+
+ /* Configure uplink threshold */
+ if (downlink->threshold > DN_THRES_MAX)
+ downlink->threshold = DN_THRES_MAX;
+
+ omap_mcpdm_write(MCPDM_FIFO_CTRL_DN, downlink->threshold);
+
+ /* Enable DMA request generation */
+ omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_DN_ENABLE);
+
+ /* Set pdm out format */
+ ctrl = omap_mcpdm_read(MCPDM_CTRL);
+ ctrl &= ~PDMOUTFORMAT;
+ ctrl |= downlink->format & PDMOUTFORMAT;
+
+ /* Downlink channels */
+ mcpdm->dn_channels = downlink->channels & (PDM_DN_MASK | PDM_CMD_MASK);
+
+ omap_mcpdm_write(MCPDM_CTRL, ctrl);
+
+ return 0;
+}
+
+/*
+ * Cleans McPDM uplink configuration.
+ * This function should be called when the stream is closed.
+ */
+int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink)
+{
+ int irq_mask = 0;
+
+ if (!uplink)
+ return -EINVAL;
+
+ /* Disable irq request generation */
+ irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK;
+ omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask);
+
+ /* Disable DMA request generation */
+ omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_UP_ENABLE);
+
+ /* Clear Downlink channels */
+ mcpdm->up_channels = 0;
+
+ mcpdm->uplink = NULL;
+
+ return 0;
+}
+
+/*
+ * Cleans McPDM downlink configuration.
+ * This function should be called when the stream is closed.
+ */
+int omap_mcpdm_playback_close(struct omap_mcpdm_link *downlink)
+{
+ int irq_mask = 0;
+
+ if (!downlink)
+ return -EINVAL;
+
+ /* Disable irq request generation */
+ irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK;
+ omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask);
+
+ /* Disable DMA request generation */
+ omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_DN_ENABLE);
+
+ /* clear Downlink channels */
+ mcpdm->dn_channels = 0;
+
+ mcpdm->downlink = NULL;
+
+ return 0;
+}
+
+static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id)
+{
+ struct omap_mcpdm *mcpdm_irq = dev_id;
+ int irq_status;
+
+ irq_status = omap_mcpdm_read(MCPDM_IRQSTATUS);
+
+ /* Acknowledge irq event */
+ omap_mcpdm_write(MCPDM_IRQSTATUS, irq_status);
+
+ if (irq & MCPDM_DN_IRQ_FULL) {
+ dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status);
+ omap_mcpdm_reset_playback(1);
+ omap_mcpdm_playback_open(mcpdm_irq->downlink);
+ omap_mcpdm_reset_playback(0);
+ }
+
+ if (irq & MCPDM_DN_IRQ_EMPTY) {
+ dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status);
+ omap_mcpdm_reset_playback(1);
+ omap_mcpdm_playback_open(mcpdm_irq->downlink);
+ omap_mcpdm_reset_playback(0);
+ }
+
+ if (irq & MCPDM_DN_IRQ) {
+ dev_dbg(mcpdm_irq->dev, "DN write request\n");
+ }
+
+ if (irq & MCPDM_UP_IRQ_FULL) {
+ dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status);
+ omap_mcpdm_reset_capture(1);
+ omap_mcpdm_capture_open(mcpdm_irq->uplink);
+ omap_mcpdm_reset_capture(0);
+ }
+
+ if (irq & MCPDM_UP_IRQ_EMPTY) {
+ dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status);
+ omap_mcpdm_reset_capture(1);
+ omap_mcpdm_capture_open(mcpdm_irq->uplink);
+ omap_mcpdm_reset_capture(0);
+ }
+
+ if (irq & MCPDM_UP_IRQ) {
+ dev_dbg(mcpdm_irq->dev, "UP write request\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+int omap_mcpdm_request(void)
+{
+ int ret;
+
+ clk_enable(mcpdm->clk);
+
+ spin_lock(&mcpdm->lock);
+
+ if (!mcpdm->free) {
+ dev_err(mcpdm->dev, "McPDM interface is in use\n");
+ spin_unlock(&mcpdm->lock);
+ ret = -EBUSY;
+ goto err;
+ }
+ mcpdm->free = 0;
+
+ spin_unlock(&mcpdm->lock);
+
+ /* Disable lines while request is ongoing */
+ omap_mcpdm_write(MCPDM_CTRL, 0x00);
+
+ ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler,
+ 0, "McPDM", (void *)mcpdm);
+ if (ret) {
+ dev_err(mcpdm->dev, "Request for McPDM IRQ failed\n");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ clk_disable(mcpdm->clk);
+ return ret;
+}
+
+void omap_mcpdm_free(void)
+{
+ spin_lock(&mcpdm->lock);
+ if (mcpdm->free) {
+ dev_err(mcpdm->dev, "McPDM interface is already free\n");
+ spin_unlock(&mcpdm->lock);
+ return;
+ }
+ mcpdm->free = 1;
+ spin_unlock(&mcpdm->lock);
+
+ clk_disable(mcpdm->clk);
+
+ free_irq(mcpdm->irq, (void *)mcpdm);
+}
+
+/* Enable/disable DC offset cancelation for the analog
+ * headset path (PDM channels 1 and 2).
+ */
+int omap_mcpdm_set_offset(int offset1, int offset2)
+{
+ int offset;
+
+ if ((offset1 > DN_OFST_MAX) || (offset2 > DN_OFST_MAX))
+ return -EINVAL;
+
+ offset = (offset1 << DN_OFST_RX1) | (offset2 << DN_OFST_RX2);
+
+ /* offset cancellation for channel 1 */
+ if (offset1)
+ offset |= DN_OFST_RX1_EN;
+ else
+ offset &= ~DN_OFST_RX1_EN;
+
+ /* offset cancellation for channel 2 */
+ if (offset2)
+ offset |= DN_OFST_RX2_EN;
+ else
+ offset &= ~DN_OFST_RX2_EN;
+
+ omap_mcpdm_write(MCPDM_DN_OFFSET, offset);
+
+ return 0;
+}
+
+int __devinit omap_mcpdm_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret = 0;
+
+ mcpdm = kzalloc(sizeof(struct omap_mcpdm), GFP_KERNEL);
+ if (!mcpdm) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "no resource\n");
+ goto err_resource;
+ }
+
+ spin_lock_init(&mcpdm->lock);
+ mcpdm->free = 1;
+ mcpdm->io_base = ioremap(res->start, resource_size(res));
+ if (!mcpdm->io_base) {
+ ret = -ENOMEM;
+ goto err_resource;
+ }
+
+ mcpdm->irq = platform_get_irq(pdev, 0);
+
+ mcpdm->clk = clk_get(&pdev->dev, "pdm_ck");
+ if (IS_ERR(mcpdm->clk)) {
+ ret = PTR_ERR(mcpdm->clk);
+ dev_err(&pdev->dev, "unable to get pdm_ck: %d\n", ret);
+ goto err_clk;
+ }
+
+ mcpdm->dev = &pdev->dev;
+ platform_set_drvdata(pdev, mcpdm);
+
+ return 0;
+
+err_clk:
+ iounmap(mcpdm->io_base);
+err_resource:
+ kfree(mcpdm);
+exit:
+ return ret;
+}
+
+int __devexit omap_mcpdm_remove(struct platform_device *pdev)
+{
+ struct omap_mcpdm *mcpdm_ptr = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ clk_put(mcpdm_ptr->clk);
+
+ iounmap(mcpdm_ptr->io_base);
+
+ mcpdm_ptr->clk = NULL;
+ mcpdm_ptr->free = 0;
+ mcpdm_ptr->dev = NULL;
+
+ kfree(mcpdm_ptr);
+
+ return 0;
+}
+
diff --git a/sound/soc/omap/mcpdm.h b/sound/soc/omap/mcpdm.h
new file mode 100644
index 00000000..df3e16fb
--- /dev/null
+++ b/sound/soc/omap/mcpdm.h
@@ -0,0 +1,153 @@
+/*
+ * mcpdm.h -- Defines for McPDM driver
+ *
+ * Author: Jorge Eduardo Candelaria <x0107209@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/* McPDM registers */
+
+#define MCPDM_REVISION 0x00
+#define MCPDM_SYSCONFIG 0x10
+#define MCPDM_IRQSTATUS_RAW 0x24
+#define MCPDM_IRQSTATUS 0x28
+#define MCPDM_IRQENABLE_SET 0x2C
+#define MCPDM_IRQENABLE_CLR 0x30
+#define MCPDM_IRQWAKE_EN 0x34
+#define MCPDM_DMAENABLE_SET 0x38
+#define MCPDM_DMAENABLE_CLR 0x3C
+#define MCPDM_DMAWAKEEN 0x40
+#define MCPDM_CTRL 0x44
+#define MCPDM_DN_DATA 0x48
+#define MCPDM_UP_DATA 0x4C
+#define MCPDM_FIFO_CTRL_DN 0x50
+#define MCPDM_FIFO_CTRL_UP 0x54
+#define MCPDM_DN_OFFSET 0x58
+
+/*
+ * MCPDM_IRQ bit fields
+ * IRQSTATUS_RAW, IRQSTATUS, IRQENABLE_SET, IRQENABLE_CLR
+ */
+
+#define MCPDM_DN_IRQ (1 << 0)
+#define MCPDM_DN_IRQ_EMPTY (1 << 1)
+#define MCPDM_DN_IRQ_ALMST_EMPTY (1 << 2)
+#define MCPDM_DN_IRQ_FULL (1 << 3)
+
+#define MCPDM_UP_IRQ (1 << 8)
+#define MCPDM_UP_IRQ_EMPTY (1 << 9)
+#define MCPDM_UP_IRQ_ALMST_FULL (1 << 10)
+#define MCPDM_UP_IRQ_FULL (1 << 11)
+
+#define MCPDM_DOWNLINK_IRQ_MASK 0x00F
+#define MCPDM_UPLINK_IRQ_MASK 0xF00
+
+/*
+ * MCPDM_DMAENABLE bit fields
+ */
+
+#define DMA_DN_ENABLE 0x1
+#define DMA_UP_ENABLE 0x2
+
+/*
+ * MCPDM_CTRL bit fields
+ */
+
+#define PDM_UP1_EN 0x0001
+#define PDM_UP2_EN 0x0002
+#define PDM_UP3_EN 0x0004
+#define PDM_DN1_EN 0x0008
+#define PDM_DN2_EN 0x0010
+#define PDM_DN3_EN 0x0020
+#define PDM_DN4_EN 0x0040
+#define PDM_DN5_EN 0x0080
+#define PDMOUTFORMAT 0x0100
+#define CMD_INT 0x0200
+#define STATUS_INT 0x0400
+#define SW_UP_RST 0x0800
+#define SW_DN_RST 0x1000
+#define PDM_UP_MASK 0x007
+#define PDM_DN_MASK 0x0F8
+#define PDM_CMD_MASK 0x200
+#define PDM_STATUS_MASK 0x400
+
+
+#define PDMOUTFORMAT_LJUST (0 << 8)
+#define PDMOUTFORMAT_RJUST (1 << 8)
+
+/*
+ * MCPDM_FIFO_CTRL bit fields
+ */
+
+#define UP_THRES_MAX 0xF
+#define DN_THRES_MAX 0xF
+
+/*
+ * MCPDM_DN_OFFSET bit fields
+ */
+
+#define DN_OFST_RX1_EN 0x0001
+#define DN_OFST_RX2_EN 0x0100
+
+#define DN_OFST_RX1 1
+#define DN_OFST_RX2 9
+#define DN_OFST_MAX 0x1F
+
+#define MCPDM_UPLINK 1
+#define MCPDM_DOWNLINK 2
+
+struct omap_mcpdm_link {
+ int irq_mask;
+ int threshold;
+ int format;
+ int channels;
+};
+
+struct omap_mcpdm_platform_data {
+ unsigned long phys_base;
+ u16 irq;
+};
+
+struct omap_mcpdm {
+ struct device *dev;
+ unsigned long phys_base;
+ void __iomem *io_base;
+ u8 free;
+ int irq;
+
+ spinlock_t lock;
+ struct omap_mcpdm_platform_data *pdata;
+ struct clk *clk;
+ struct omap_mcpdm_link *downlink;
+ struct omap_mcpdm_link *uplink;
+ struct completion irq_completion;
+
+ int dn_channels;
+ int up_channels;
+};
+
+extern void omap_mcpdm_start(int stream);
+extern void omap_mcpdm_stop(int stream);
+extern int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink);
+extern int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink);
+extern int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink);
+extern int omap_mcpdm_playback_close(struct omap_mcpdm_link *downlink);
+extern int omap_mcpdm_request(void);
+extern void omap_mcpdm_free(void);
+extern int omap_mcpdm_set_offset(int offset1, int offset2);
+int __devinit omap_mcpdm_probe(struct platform_device *pdev);
+int __devexit omap_mcpdm_remove(struct platform_device *pdev);
diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c
new file mode 100644
index 00000000..83d213bf
--- /dev/null
+++ b/sound/soc/omap/n810.c
@@ -0,0 +1,407 @@
+/*
+ * n810.c -- SoC audio for Nokia N810
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jhnikula@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <linux/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#define N810_HEADSET_AMP_GPIO 10
+#define N810_SPEAKER_AMP_GPIO 101
+
+enum {
+ N810_JACK_DISABLED,
+ N810_JACK_HP,
+ N810_JACK_HS,
+ N810_JACK_MIC,
+};
+
+static struct clk *sys_clkout2;
+static struct clk *sys_clkout2_src;
+static struct clk *func96m_clk;
+
+static int n810_spk_func;
+static int n810_jack_func;
+static int n810_dmic_func;
+
+static void n810_ext_control(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int hp = 0, line1l = 0;
+
+ switch (n810_jack_func) {
+ case N810_JACK_HS:
+ line1l = 1;
+ case N810_JACK_HP:
+ hp = 1;
+ break;
+ case N810_JACK_MIC:
+ line1l = 1;
+ break;
+ }
+
+ if (n810_spk_func)
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Ext Spk");
+
+ if (hp)
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ if (line1l)
+ snd_soc_dapm_enable_pin(dapm, "LINE1L");
+ else
+ snd_soc_dapm_disable_pin(dapm, "LINE1L");
+
+ if (n810_dmic_func)
+ snd_soc_dapm_enable_pin(dapm, "DMic");
+ else
+ snd_soc_dapm_disable_pin(dapm, "DMic");
+
+ snd_soc_dapm_sync(dapm);
+}
+
+static int n810_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2);
+
+ n810_ext_control(codec);
+ return clk_enable(sys_clkout2);
+}
+
+static void n810_shutdown(struct snd_pcm_substream *substream)
+{
+ clk_disable(sys_clkout2);
+}
+
+static int n810_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int err;
+
+ /* Set codec DAI configuration */
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0)
+ return err;
+
+ /* Set cpu DAI configuration */
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0)
+ return err;
+
+ /* Set the codec system clock for DAC and ADC */
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, 12000000,
+ SND_SOC_CLOCK_IN);
+
+ return err;
+}
+
+static struct snd_soc_ops n810_ops = {
+ .startup = n810_startup,
+ .hw_params = n810_hw_params,
+ .shutdown = n810_shutdown,
+};
+
+static int n810_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = n810_spk_func;
+
+ return 0;
+}
+
+static int n810_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (n810_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ n810_spk_func = ucontrol->value.integer.value[0];
+ n810_ext_control(codec);
+
+ return 1;
+}
+
+static int n810_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = n810_jack_func;
+
+ return 0;
+}
+
+static int n810_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (n810_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ n810_jack_func = ucontrol->value.integer.value[0];
+ n810_ext_control(codec);
+
+ return 1;
+}
+
+static int n810_get_input(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = n810_dmic_func;
+
+ return 0;
+}
+
+static int n810_set_input(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (n810_dmic_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ n810_dmic_func = ucontrol->value.integer.value[0];
+ n810_ext_control(codec);
+
+ return 1;
+}
+
+static int n810_spk_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpio_set_value(N810_SPEAKER_AMP_GPIO, 1);
+ else
+ gpio_set_value(N810_SPEAKER_AMP_GPIO, 0);
+
+ return 0;
+}
+
+static int n810_jack_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpio_set_value(N810_HEADSET_AMP_GPIO, 1);
+ else
+ gpio_set_value(N810_HEADSET_AMP_GPIO, 0);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget aic33_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Ext Spk", n810_spk_event),
+ SND_SOC_DAPM_HP("Headphone Jack", n810_jack_event),
+ SND_SOC_DAPM_MIC("DMic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Jack", NULL, "HPLOUT"},
+ {"Headphone Jack", NULL, "HPROUT"},
+
+ {"Ext Spk", NULL, "LLOUT"},
+ {"Ext Spk", NULL, "RLOUT"},
+
+ {"DMic Rate 64", NULL, "Mic Bias 2V"},
+ {"Mic Bias 2V", NULL, "DMic"},
+};
+
+static const char *spk_function[] = {"Off", "On"};
+static const char *jack_function[] = {"Off", "Headphone", "Headset", "Mic"};
+static const char *input_function[] = {"ADC", "Digital Mic"};
+static const struct soc_enum n810_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function),
+};
+
+static const struct snd_kcontrol_new aic33_n810_controls[] = {
+ SOC_ENUM_EXT("Speaker Function", n810_enum[0],
+ n810_get_spk, n810_set_spk),
+ SOC_ENUM_EXT("Jack Function", n810_enum[1],
+ n810_get_jack, n810_set_jack),
+ SOC_ENUM_EXT("Input Select", n810_enum[2],
+ n810_get_input, n810_set_input),
+};
+
+static int n810_aic33_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ /* Not connected */
+ snd_soc_dapm_nc_pin(dapm, "MONO_LOUT");
+ snd_soc_dapm_nc_pin(dapm, "HPLCOM");
+ snd_soc_dapm_nc_pin(dapm, "HPRCOM");
+ snd_soc_dapm_nc_pin(dapm, "MIC3L");
+ snd_soc_dapm_nc_pin(dapm, "MIC3R");
+ snd_soc_dapm_nc_pin(dapm, "LINE1R");
+ snd_soc_dapm_nc_pin(dapm, "LINE2L");
+ snd_soc_dapm_nc_pin(dapm, "LINE2R");
+
+ /* Add N810 specific controls */
+ err = snd_soc_add_controls(codec, aic33_n810_controls,
+ ARRAY_SIZE(aic33_n810_controls));
+ if (err < 0)
+ return err;
+
+ /* Add N810 specific widgets */
+ snd_soc_dapm_new_controls(dapm, aic33_dapm_widgets,
+ ARRAY_SIZE(aic33_dapm_widgets));
+
+ /* Set up N810 specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link n810_dai = {
+ .name = "TLV320AIC33",
+ .stream_name = "AIC33",
+ .cpu_dai_name = "omap-mcbsp-dai.1",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "tlv320aic3x-codec.2-0018",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .init = n810_aic33_init,
+ .ops = &n810_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_n810 = {
+ .name = "N810",
+ .dai_link = &n810_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *n810_snd_device;
+
+static int __init n810_soc_init(void)
+{
+ int err;
+ struct device *dev;
+
+ if (!(machine_is_nokia_n810() || machine_is_nokia_n810_wimax()))
+ return -ENODEV;
+
+ n810_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!n810_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(n810_snd_device, &snd_soc_n810);
+ err = platform_device_add(n810_snd_device);
+ if (err)
+ goto err1;
+
+ dev = &n810_snd_device->dev;
+
+ sys_clkout2_src = clk_get(dev, "sys_clkout2_src");
+ if (IS_ERR(sys_clkout2_src)) {
+ dev_err(dev, "Could not get sys_clkout2_src clock\n");
+ err = PTR_ERR(sys_clkout2_src);
+ goto err2;
+ }
+ sys_clkout2 = clk_get(dev, "sys_clkout2");
+ if (IS_ERR(sys_clkout2)) {
+ dev_err(dev, "Could not get sys_clkout2\n");
+ err = PTR_ERR(sys_clkout2);
+ goto err3;
+ }
+ /*
+ * Configure 12 MHz output on SYS_CLKOUT2. Therefore we must use
+ * 96 MHz as its parent in order to get 12 MHz
+ */
+ func96m_clk = clk_get(dev, "func_96m_ck");
+ if (IS_ERR(func96m_clk)) {
+ dev_err(dev, "Could not get func 96M clock\n");
+ err = PTR_ERR(func96m_clk);
+ goto err4;
+ }
+ clk_set_parent(sys_clkout2_src, func96m_clk);
+ clk_set_rate(sys_clkout2, 12000000);
+
+ BUG_ON((gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) ||
+ (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0));
+
+ gpio_direction_output(N810_HEADSET_AMP_GPIO, 0);
+ gpio_direction_output(N810_SPEAKER_AMP_GPIO, 0);
+
+ return 0;
+err4:
+ clk_put(sys_clkout2);
+err3:
+ clk_put(sys_clkout2_src);
+err2:
+ platform_device_del(n810_snd_device);
+err1:
+ platform_device_put(n810_snd_device);
+
+ return err;
+}
+
+static void __exit n810_soc_exit(void)
+{
+ gpio_free(N810_SPEAKER_AMP_GPIO);
+ gpio_free(N810_HEADSET_AMP_GPIO);
+ clk_put(sys_clkout2_src);
+ clk_put(sys_clkout2);
+ clk_put(func96m_clk);
+
+ platform_device_unregister(n810_snd_device);
+}
+
+module_init(n810_soc_init);
+module_exit(n810_soc_exit);
+
+MODULE_AUTHOR("Jarkko Nikula <jhnikula@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC Nokia N810");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c
new file mode 100644
index 00000000..4b82290c
--- /dev/null
+++ b/sound/soc/omap/omap-mcbsp.c
@@ -0,0 +1,791 @@
+/*
+ * omap-mcbsp.c -- OMAP ALSA SoC DAI driver using McBSP port
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jhnikula@gmail.com>
+ * Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <plat/dma.h>
+#include <plat/mcbsp.h>
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#define OMAP_MCBSP_RATES (SNDRV_PCM_RATE_8000_96000)
+
+#define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax, \
+ xhandler_get, xhandler_put) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = omap_mcbsp_st_info_volsw, \
+ .get = xhandler_get, .put = xhandler_put, \
+ .private_value = (unsigned long) &(struct soc_mixer_control) \
+ {.min = xmin, .max = xmax} }
+
+struct omap_mcbsp_data {
+ unsigned int bus_id;
+ struct omap_mcbsp_reg_cfg regs;
+ unsigned int fmt;
+ /*
+ * Flags indicating is the bus already activated and configured by
+ * another substream
+ */
+ int active;
+ int configured;
+ unsigned int in_freq;
+ int clk_div;
+ int wlen;
+};
+
+static struct omap_mcbsp_data mcbsp_data[NUM_LINKS];
+
+/*
+ * Stream DMA parameters. DMA request line and port address are set runtime
+ * since they are different between OMAP1 and later OMAPs
+ */
+static struct omap_pcm_dma_data omap_mcbsp_dai_dma_params[NUM_LINKS][2];
+
+static void omap_mcbsp_set_threshold(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+ struct omap_pcm_dma_data *dma_data;
+ int dma_op_mode = omap_mcbsp_get_dma_op_mode(mcbsp_data->bus_id);
+ int words;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
+ if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD)
+ /*
+ * Configure McBSP threshold based on either:
+ * packet_size, when the sDMA is in packet mode, or
+ * based on the period size.
+ */
+ if (dma_data->packet_size)
+ words = dma_data->packet_size;
+ else
+ words = snd_pcm_lib_period_bytes(substream) /
+ (mcbsp_data->wlen / 8);
+ else
+ words = 1;
+
+ /* Configure McBSP internal buffer usage */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ omap_mcbsp_set_tx_threshold(mcbsp_data->bus_id, words);
+ else
+ omap_mcbsp_set_rx_threshold(mcbsp_data->bus_id, words);
+}
+
+static int omap_mcbsp_hwrule_min_buffersize(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_interval *buffer_size = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct omap_mcbsp_data *mcbsp_data = rule->private;
+ struct snd_interval frames;
+ int size;
+
+ snd_interval_any(&frames);
+ size = omap_mcbsp_get_fifo_size(mcbsp_data->bus_id);
+
+ frames.min = size / channels->min;
+ frames.integer = 1;
+ return snd_interval_refine(buffer_size, &frames);
+}
+
+static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+ int bus_id = mcbsp_data->bus_id;
+ int err = 0;
+
+ if (!cpu_dai->active)
+ err = omap_mcbsp_request(bus_id);
+
+ /*
+ * OMAP3 McBSP FIFO is word structured.
+ * McBSP2 has 1024 + 256 = 1280 word long buffer,
+ * McBSP1,3,4,5 has 128 word long buffer
+ * This means that the size of the FIFO depends on the sample format.
+ * For example on McBSP3:
+ * 16bit samples: size is 128 * 2 = 256 bytes
+ * 32bit samples: size is 128 * 4 = 512 bytes
+ * It is simpler to place constraint for buffer and period based on
+ * channels.
+ * McBSP3 as example again (16 or 32 bit samples):
+ * 1 channel (mono): size is 128 frames (128 words)
+ * 2 channels (stereo): size is 128 / 2 = 64 frames (2 * 64 words)
+ * 4 channels: size is 128 / 4 = 32 frames (4 * 32 words)
+ */
+ if (cpu_is_omap34xx() || cpu_is_omap44xx()) {
+ /*
+ * Rule for the buffer size. We should not allow
+ * smaller buffer than the FIFO size to avoid underruns
+ */
+ snd_pcm_hw_rule_add(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ omap_mcbsp_hwrule_min_buffersize,
+ mcbsp_data,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1);
+
+ /* Make sure, that the period size is always even */
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2);
+ }
+
+ return err;
+}
+
+static void omap_mcbsp_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+
+ if (!cpu_dai->active) {
+ omap_mcbsp_free(mcbsp_data->bus_id);
+ mcbsp_data->configured = 0;
+ }
+}
+
+static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+ int err = 0, play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ mcbsp_data->active++;
+ omap_mcbsp_start(mcbsp_data->bus_id, play, !play);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ omap_mcbsp_stop(mcbsp_data->bus_id, play, !play);
+ mcbsp_data->active--;
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static snd_pcm_sframes_t omap_mcbsp_dai_delay(
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+ u16 fifo_use;
+ snd_pcm_sframes_t delay;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ fifo_use = omap_mcbsp_get_tx_delay(mcbsp_data->bus_id);
+ else
+ fifo_use = omap_mcbsp_get_rx_delay(mcbsp_data->bus_id);
+
+ /*
+ * Divide the used locations with the channel count to get the
+ * FIFO usage in samples (don't care about partial samples in the
+ * buffer).
+ */
+ delay = fifo_use / substream->runtime->channels;
+
+ return delay;
+}
+
+static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+ struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+ struct omap_pcm_dma_data *dma_data;
+ int dma, bus_id = mcbsp_data->bus_id;
+ int wlen, channels, wpf, sync_mode = OMAP_DMA_SYNC_ELEMENT;
+ int pkt_size = 0;
+ unsigned long port;
+ unsigned int format, div, framesize, master;
+
+ dma_data = &omap_mcbsp_dai_dma_params[cpu_dai->id][substream->stream];
+
+ dma = omap_mcbsp_dma_ch_params(bus_id, substream->stream);
+ port = omap_mcbsp_dma_reg_params(bus_id, substream->stream);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dma_data->data_type = OMAP_DMA_DATA_TYPE_S16;
+ wlen = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ dma_data->data_type = OMAP_DMA_DATA_TYPE_S32;
+ wlen = 32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (cpu_is_omap34xx()) {
+ dma_data->set_threshold = omap_mcbsp_set_threshold;
+ /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
+ if (omap_mcbsp_get_dma_op_mode(bus_id) ==
+ MCBSP_DMA_MODE_THRESHOLD) {
+ int period_words, max_thrsh;
+
+ period_words = params_period_bytes(params) / (wlen / 8);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ max_thrsh = omap_mcbsp_get_max_tx_threshold(
+ mcbsp_data->bus_id);
+ else
+ max_thrsh = omap_mcbsp_get_max_rx_threshold(
+ mcbsp_data->bus_id);
+ /*
+ * If the period contains less or equal number of words,
+ * we are using the original threshold mode setup:
+ * McBSP threshold = sDMA frame size = period_size
+ * Otherwise we switch to sDMA packet mode:
+ * McBSP threshold = sDMA packet size
+ * sDMA frame size = period size
+ */
+ if (period_words > max_thrsh) {
+ int divider = 0;
+
+ /*
+ * Look for the biggest threshold value, which
+ * divides the period size evenly.
+ */
+ divider = period_words / max_thrsh;
+ if (period_words % max_thrsh)
+ divider++;
+ while (period_words % divider &&
+ divider < period_words)
+ divider++;
+ if (divider == period_words)
+ return -EINVAL;
+
+ pkt_size = period_words / divider;
+ sync_mode = OMAP_DMA_SYNC_PACKET;
+ } else {
+ sync_mode = OMAP_DMA_SYNC_FRAME;
+ }
+ }
+ }
+
+ dma_data->name = substream->stream ? "Audio Capture" : "Audio Playback";
+ dma_data->dma_req = dma;
+ dma_data->port_addr = port;
+ dma_data->sync_mode = sync_mode;
+ dma_data->packet_size = pkt_size;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+ if (mcbsp_data->configured) {
+ /* McBSP already configured by another stream */
+ return 0;
+ }
+
+ format = mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+ wpf = channels = params_channels(params);
+ if (channels == 2 && (format == SND_SOC_DAIFMT_I2S ||
+ format == SND_SOC_DAIFMT_LEFT_J)) {
+ /* Use dual-phase frames */
+ regs->rcr2 |= RPHASE;
+ regs->xcr2 |= XPHASE;
+ /* Set 1 word per (McBSP) frame for phase1 and phase2 */
+ wpf--;
+ regs->rcr2 |= RFRLEN2(wpf - 1);
+ regs->xcr2 |= XFRLEN2(wpf - 1);
+ }
+
+ regs->rcr1 |= RFRLEN1(wpf - 1);
+ regs->xcr1 |= XFRLEN1(wpf - 1);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ /* Set word lengths */
+ regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_16);
+ regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_16);
+ regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_16);
+ regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_16);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ /* Set word lengths */
+ regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_32);
+ regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_32);
+ regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_32);
+ regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_32);
+ break;
+ default:
+ /* Unsupported PCM format */
+ return -EINVAL;
+ }
+
+ /* In McBSP master modes, FRAME (i.e. sample rate) is generated
+ * by _counting_ BCLKs. Calculate frame size in BCLKs */
+ master = mcbsp_data->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+ if (master == SND_SOC_DAIFMT_CBS_CFS) {
+ div = mcbsp_data->clk_div ? mcbsp_data->clk_div : 1;
+ framesize = (mcbsp_data->in_freq / div) / params_rate(params);
+
+ if (framesize < wlen * channels) {
+ printk(KERN_ERR "%s: not enough bandwidth for desired rate and "
+ "channels\n", __func__);
+ return -EINVAL;
+ }
+ } else
+ framesize = wlen * channels;
+
+ /* Set FS period and length in terms of bit clock periods */
+ switch (format) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_LEFT_J:
+ regs->srgr2 |= FPER(framesize - 1);
+ regs->srgr1 |= FWID((framesize >> 1) - 1);
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ regs->srgr2 |= FPER(framesize - 1);
+ regs->srgr1 |= FWID(0);
+ break;
+ }
+
+ omap_mcbsp_config(bus_id, &mcbsp_data->regs);
+ mcbsp_data->wlen = wlen;
+ mcbsp_data->configured = 1;
+
+ return 0;
+}
+
+/*
+ * This must be called before _set_clkdiv and _set_sysclk since McBSP register
+ * cache is initialized here
+ */
+static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+ struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+ unsigned int temp_fmt = fmt;
+
+ if (mcbsp_data->configured)
+ return 0;
+
+ mcbsp_data->fmt = fmt;
+ memset(regs, 0, sizeof(*regs));
+ /* Generic McBSP register settings */
+ regs->spcr2 |= XINTM(3) | FREE;
+ regs->spcr1 |= RINTM(3);
+ /* RFIG and XFIG are not defined in 34xx */
+ if (!cpu_is_omap34xx() && !cpu_is_omap44xx()) {
+ regs->rcr2 |= RFIG;
+ regs->xcr2 |= XFIG;
+ }
+ if (cpu_is_omap2430() || cpu_is_omap34xx() || cpu_is_omap44xx()) {
+ regs->xccr = DXENDLY(1) | XDMAEN | XDISABLE;
+ regs->rccr = RFULL_CYCLE | RDMAEN | RDISABLE;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* 1-bit data delay */
+ regs->rcr2 |= RDATDLY(1);
+ regs->xcr2 |= XDATDLY(1);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ /* 0-bit data delay */
+ regs->rcr2 |= RDATDLY(0);
+ regs->xcr2 |= XDATDLY(0);
+ regs->spcr1 |= RJUST(2);
+ /* Invert FS polarity configuration */
+ temp_fmt ^= SND_SOC_DAIFMT_NB_IF;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ /* 1-bit data delay */
+ regs->rcr2 |= RDATDLY(1);
+ regs->xcr2 |= XDATDLY(1);
+ /* Invert FS polarity configuration */
+ temp_fmt ^= SND_SOC_DAIFMT_NB_IF;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ /* 0-bit data delay */
+ regs->rcr2 |= RDATDLY(0);
+ regs->xcr2 |= XDATDLY(0);
+ /* Invert FS polarity configuration */
+ temp_fmt ^= SND_SOC_DAIFMT_NB_IF;
+ break;
+ default:
+ /* Unsupported data format */
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* McBSP master. Set FS and bit clocks as outputs */
+ regs->pcr0 |= FSXM | FSRM |
+ CLKXM | CLKRM;
+ /* Sample rate generator drives the FS */
+ regs->srgr2 |= FSGM;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* McBSP slave */
+ break;
+ default:
+ /* Unsupported master/slave configuration */
+ return -EINVAL;
+ }
+
+ /* Set bit clock (CLKX/CLKR) and FS polarities */
+ switch (temp_fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ /*
+ * Normal BCLK + FS.
+ * FS active low. TX data driven on falling edge of bit clock
+ * and RX data sampled on rising edge of bit clock.
+ */
+ regs->pcr0 |= FSXP | FSRP |
+ CLKXP | CLKRP;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ regs->pcr0 |= CLKXP | CLKRP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ regs->pcr0 |= FSXP | FSRP;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int omap_mcbsp_dai_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+ struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+
+ if (div_id != OMAP_MCBSP_CLKGDV)
+ return -ENODEV;
+
+ mcbsp_data->clk_div = div;
+ regs->srgr1 |= CLKGDV(div - 1);
+
+ return 0;
+}
+
+static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq,
+ int dir)
+{
+ struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+ struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+ int err = 0;
+
+ if (mcbsp_data->active)
+ if (freq == mcbsp_data->in_freq)
+ return 0;
+ else
+ return -EBUSY;
+
+ /* The McBSP signal muxing functions are only available on McBSP1 */
+ if (clk_id == OMAP_MCBSP_CLKR_SRC_CLKR ||
+ clk_id == OMAP_MCBSP_CLKR_SRC_CLKX ||
+ clk_id == OMAP_MCBSP_FSR_SRC_FSR ||
+ clk_id == OMAP_MCBSP_FSR_SRC_FSX)
+ if (cpu_class_is_omap1() || mcbsp_data->bus_id != 0)
+ return -EINVAL;
+
+ mcbsp_data->in_freq = freq;
+
+ switch (clk_id) {
+ case OMAP_MCBSP_SYSCLK_CLK:
+ regs->srgr2 |= CLKSM;
+ break;
+ case OMAP_MCBSP_SYSCLK_CLKS_FCLK:
+ if (cpu_class_is_omap1()) {
+ err = -EINVAL;
+ break;
+ }
+ err = omap2_mcbsp_set_clks_src(mcbsp_data->bus_id,
+ MCBSP_CLKS_PRCM_SRC);
+ break;
+ case OMAP_MCBSP_SYSCLK_CLKS_EXT:
+ if (cpu_class_is_omap1()) {
+ err = 0;
+ break;
+ }
+ err = omap2_mcbsp_set_clks_src(mcbsp_data->bus_id,
+ MCBSP_CLKS_PAD_SRC);
+ break;
+
+ case OMAP_MCBSP_SYSCLK_CLKX_EXT:
+ regs->srgr2 |= CLKSM;
+ case OMAP_MCBSP_SYSCLK_CLKR_EXT:
+ regs->pcr0 |= SCLKME;
+ break;
+
+
+ case OMAP_MCBSP_CLKR_SRC_CLKR:
+ if (cpu_class_is_omap1())
+ break;
+ omap2_mcbsp1_mux_clkr_src(CLKR_SRC_CLKR);
+ break;
+ case OMAP_MCBSP_CLKR_SRC_CLKX:
+ if (cpu_class_is_omap1())
+ break;
+ omap2_mcbsp1_mux_clkr_src(CLKR_SRC_CLKX);
+ break;
+ case OMAP_MCBSP_FSR_SRC_FSR:
+ if (cpu_class_is_omap1())
+ break;
+ omap2_mcbsp1_mux_fsr_src(FSR_SRC_FSR);
+ break;
+ case OMAP_MCBSP_FSR_SRC_FSX:
+ if (cpu_class_is_omap1())
+ break;
+ omap2_mcbsp1_mux_fsr_src(FSR_SRC_FSX);
+ break;
+ default:
+ err = -ENODEV;
+ }
+
+ return err;
+}
+
+static struct snd_soc_dai_ops mcbsp_dai_ops = {
+ .startup = omap_mcbsp_dai_startup,
+ .shutdown = omap_mcbsp_dai_shutdown,
+ .trigger = omap_mcbsp_dai_trigger,
+ .delay = omap_mcbsp_dai_delay,
+ .hw_params = omap_mcbsp_dai_hw_params,
+ .set_fmt = omap_mcbsp_dai_set_dai_fmt,
+ .set_clkdiv = omap_mcbsp_dai_set_clkdiv,
+ .set_sysclk = omap_mcbsp_dai_set_dai_sysclk,
+};
+
+static int mcbsp_dai_probe(struct snd_soc_dai *dai)
+{
+ mcbsp_data[dai->id].bus_id = dai->id;
+ snd_soc_dai_set_drvdata(dai, &mcbsp_data[dai->id].bus_id);
+ return 0;
+}
+
+static struct snd_soc_dai_driver omap_mcbsp_dai =
+{
+ .probe = mcbsp_dai_probe,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 16,
+ .rates = OMAP_MCBSP_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 16,
+ .rates = OMAP_MCBSP_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &mcbsp_dai_ops,
+};
+
+static int omap_mcbsp_st_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int max = mc->max;
+ int min = mc->min;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = min;
+ uinfo->value.integer.max = max;
+ return 0;
+}
+
+#define OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(id, channel) \
+static int \
+omap_mcbsp##id##_set_st_ch##channel##_volume(struct snd_kcontrol *kc, \
+ struct snd_ctl_elem_value *uc) \
+{ \
+ struct soc_mixer_control *mc = \
+ (struct soc_mixer_control *)kc->private_value; \
+ int max = mc->max; \
+ int min = mc->min; \
+ int val = uc->value.integer.value[0]; \
+ \
+ if (val < min || val > max) \
+ return -EINVAL; \
+ \
+ /* OMAP McBSP implementation uses index values 0..4 */ \
+ return omap_st_set_chgain((id)-1, channel, val); \
+}
+
+#define OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(id, channel) \
+static int \
+omap_mcbsp##id##_get_st_ch##channel##_volume(struct snd_kcontrol *kc, \
+ struct snd_ctl_elem_value *uc) \
+{ \
+ s16 chgain; \
+ \
+ if (omap_st_get_chgain((id)-1, channel, &chgain)) \
+ return -EAGAIN; \
+ \
+ uc->value.integer.value[0] = chgain; \
+ return 0; \
+}
+
+OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(2, 0)
+OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(2, 1)
+OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(3, 0)
+OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(3, 1)
+OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(2, 0)
+OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(2, 1)
+OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(3, 0)
+OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(3, 1)
+
+static int omap_mcbsp_st_put_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ u8 value = ucontrol->value.integer.value[0];
+
+ if (value == omap_st_is_enabled(mc->reg))
+ return 0;
+
+ if (value)
+ omap_st_enable(mc->reg);
+ else
+ omap_st_disable(mc->reg);
+
+ return 1;
+}
+
+static int omap_mcbsp_st_get_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+
+ ucontrol->value.integer.value[0] = omap_st_is_enabled(mc->reg);
+ return 0;
+}
+
+static const struct snd_kcontrol_new omap_mcbsp2_st_controls[] = {
+ SOC_SINGLE_EXT("McBSP2 Sidetone Switch", 1, 0, 1, 0,
+ omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode),
+ OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP2 Sidetone Channel 0 Volume",
+ -32768, 32767,
+ omap_mcbsp2_get_st_ch0_volume,
+ omap_mcbsp2_set_st_ch0_volume),
+ OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP2 Sidetone Channel 1 Volume",
+ -32768, 32767,
+ omap_mcbsp2_get_st_ch1_volume,
+ omap_mcbsp2_set_st_ch1_volume),
+};
+
+static const struct snd_kcontrol_new omap_mcbsp3_st_controls[] = {
+ SOC_SINGLE_EXT("McBSP3 Sidetone Switch", 2, 0, 1, 0,
+ omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode),
+ OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP3 Sidetone Channel 0 Volume",
+ -32768, 32767,
+ omap_mcbsp3_get_st_ch0_volume,
+ omap_mcbsp3_set_st_ch0_volume),
+ OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP3 Sidetone Channel 1 Volume",
+ -32768, 32767,
+ omap_mcbsp3_get_st_ch1_volume,
+ omap_mcbsp3_set_st_ch1_volume),
+};
+
+int omap_mcbsp_st_add_controls(struct snd_soc_codec *codec, int mcbsp_id)
+{
+ if (!cpu_is_omap34xx())
+ return -ENODEV;
+
+ switch (mcbsp_id) {
+ case 1: /* McBSP 2 */
+ return snd_soc_add_controls(codec, omap_mcbsp2_st_controls,
+ ARRAY_SIZE(omap_mcbsp2_st_controls));
+ case 2: /* McBSP 3 */
+ return snd_soc_add_controls(codec, omap_mcbsp3_st_controls,
+ ARRAY_SIZE(omap_mcbsp3_st_controls));
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(omap_mcbsp_st_add_controls);
+
+static __devinit int asoc_mcbsp_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_dai(&pdev->dev, &omap_mcbsp_dai);
+}
+
+static int __devexit asoc_mcbsp_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver asoc_mcbsp_driver = {
+ .driver = {
+ .name = "omap-mcbsp-dai",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = asoc_mcbsp_probe,
+ .remove = __devexit_p(asoc_mcbsp_remove),
+};
+
+static int __init snd_omap_mcbsp_init(void)
+{
+ return platform_driver_register(&asoc_mcbsp_driver);
+}
+module_init(snd_omap_mcbsp_init);
+
+static void __exit snd_omap_mcbsp_exit(void)
+{
+ platform_driver_unregister(&asoc_mcbsp_driver);
+}
+module_exit(snd_omap_mcbsp_exit);
+
+MODULE_AUTHOR("Jarkko Nikula <jhnikula@gmail.com>");
+MODULE_DESCRIPTION("OMAP I2S SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/omap-mcbsp.h b/sound/soc/omap/omap-mcbsp.h
new file mode 100644
index 00000000..9a7dedd6
--- /dev/null
+++ b/sound/soc/omap/omap-mcbsp.h
@@ -0,0 +1,64 @@
+/*
+ * omap-mcbsp.h
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jhnikula@gmail.com>
+ * Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __OMAP_I2S_H__
+#define __OMAP_I2S_H__
+
+/* Source clocks for McBSP sample rate generator */
+enum omap_mcbsp_clksrg_clk {
+ OMAP_MCBSP_SYSCLK_CLKS_FCLK, /* Internal FCLK */
+ OMAP_MCBSP_SYSCLK_CLKS_EXT, /* External CLKS pin */
+ OMAP_MCBSP_SYSCLK_CLK, /* Internal ICLK */
+ OMAP_MCBSP_SYSCLK_CLKX_EXT, /* External CLKX pin */
+ OMAP_MCBSP_SYSCLK_CLKR_EXT, /* External CLKR pin */
+ OMAP_MCBSP_CLKR_SRC_CLKR, /* CLKR from CLKR pin */
+ OMAP_MCBSP_CLKR_SRC_CLKX, /* CLKR from CLKX pin */
+ OMAP_MCBSP_FSR_SRC_FSR, /* FSR from FSR pin */
+ OMAP_MCBSP_FSR_SRC_FSX, /* FSR from FSX pin */
+};
+
+/* McBSP dividers */
+enum omap_mcbsp_div {
+ OMAP_MCBSP_CLKGDV, /* Sample rate generator divider */
+};
+
+#if defined(CONFIG_SOC_OMAP2420)
+#define NUM_LINKS 2
+#endif
+#if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX)
+#undef NUM_LINKS
+#define NUM_LINKS 3
+#endif
+#if defined(CONFIG_ARCH_OMAP4)
+#undef NUM_LINKS
+#define NUM_LINKS 4
+#endif
+#if defined(CONFIG_ARCH_OMAP3) || defined(CONFIG_SOC_OMAP2430)
+#undef NUM_LINKS
+#define NUM_LINKS 5
+#endif
+
+int omap_mcbsp_st_add_controls(struct snd_soc_codec *codec, int mcbsp_id);
+
+#endif
diff --git a/sound/soc/omap/omap-mcpdm.c b/sound/soc/omap/omap-mcpdm.c
new file mode 100644
index 00000000..bed09c27
--- /dev/null
+++ b/sound/soc/omap/omap-mcpdm.c
@@ -0,0 +1,272 @@
+/*
+ * omap-mcpdm.c -- OMAP ALSA SoC DAI driver using McPDM port
+ *
+ * Copyright (C) 2009 Texas Instruments
+ *
+ * Author: Misael Lopez Cruz <x0052729@ti.com>
+ * Contact: Jorge Eduardo Candelaria <x0107209@ti.com>
+ * Margarita Olaya <magi.olaya@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <plat/dma.h>
+#include <plat/mcbsp.h>
+#include "mcpdm.h"
+#include "omap-pcm.h"
+
+struct omap_mcpdm_data {
+ struct omap_mcpdm_link *links;
+ int active;
+};
+
+static struct omap_mcpdm_link omap_mcpdm_links[] = {
+ /* downlink */
+ {
+ .irq_mask = MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL,
+ .threshold = 1,
+ .format = PDMOUTFORMAT_LJUST,
+ },
+ /* uplink */
+ {
+ .irq_mask = MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL,
+ .threshold = 1,
+ .format = PDMOUTFORMAT_LJUST,
+ },
+};
+
+static struct omap_mcpdm_data mcpdm_data = {
+ .links = omap_mcpdm_links,
+ .active = 0,
+};
+
+/*
+ * Stream DMA parameters
+ */
+static struct omap_pcm_dma_data omap_mcpdm_dai_dma_params[] = {
+ {
+ .name = "Audio playback",
+ .dma_req = OMAP44XX_DMA_MCPDM_DL,
+ .data_type = OMAP_DMA_DATA_TYPE_S32,
+ .sync_mode = OMAP_DMA_SYNC_PACKET,
+ .packet_size = 16,
+ .port_addr = OMAP44XX_MCPDM_L3_BASE + MCPDM_DN_DATA,
+ },
+ {
+ .name = "Audio capture",
+ .dma_req = OMAP44XX_DMA_MCPDM_UP,
+ .data_type = OMAP_DMA_DATA_TYPE_S32,
+ .sync_mode = OMAP_DMA_SYNC_PACKET,
+ .packet_size = 16,
+ .port_addr = OMAP44XX_MCPDM_L3_BASE + MCPDM_UP_DATA,
+ },
+};
+
+static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int err = 0;
+
+ if (!dai->active)
+ err = omap_mcpdm_request();
+
+ return err;
+}
+
+static void omap_mcpdm_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ omap_mcpdm_free();
+}
+
+static int omap_mcpdm_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct omap_mcpdm_data *mcpdm_priv = snd_soc_dai_get_drvdata(dai);
+ int stream = substream->stream;
+ int err = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!mcpdm_priv->active++)
+ omap_mcpdm_start(stream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (!--mcpdm_priv->active)
+ omap_mcpdm_stop(stream);
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static int omap_mcpdm_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct omap_mcpdm_data *mcpdm_priv = snd_soc_dai_get_drvdata(dai);
+ struct omap_mcpdm_link *mcpdm_links = mcpdm_priv->links;
+ int stream = substream->stream;
+ int channels, err, link_mask = 0;
+
+ snd_soc_dai_set_dma_data(dai, substream,
+ &omap_mcpdm_dai_dma_params[stream]);
+
+ channels = params_channels(params);
+ switch (channels) {
+ case 4:
+ if (stream == SNDRV_PCM_STREAM_CAPTURE)
+ /* up to 2 channels for capture */
+ return -EINVAL;
+ link_mask |= 1 << 3;
+ case 3:
+ if (stream == SNDRV_PCM_STREAM_CAPTURE)
+ /* up to 2 channels for capture */
+ return -EINVAL;
+ link_mask |= 1 << 2;
+ case 2:
+ link_mask |= 1 << 1;
+ case 1:
+ link_mask |= 1 << 0;
+ break;
+ default:
+ /* unsupported number of channels */
+ return -EINVAL;
+ }
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ mcpdm_links[stream].channels = link_mask << 3;
+ err = omap_mcpdm_playback_open(&mcpdm_links[stream]);
+ } else {
+ mcpdm_links[stream].channels = link_mask << 0;
+ err = omap_mcpdm_capture_open(&mcpdm_links[stream]);
+ }
+
+ return err;
+}
+
+static int omap_mcpdm_dai_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct omap_mcpdm_data *mcpdm_priv = snd_soc_dai_get_drvdata(dai);
+ struct omap_mcpdm_link *mcpdm_links = mcpdm_priv->links;
+ int stream = substream->stream;
+ int err;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ err = omap_mcpdm_playback_close(&mcpdm_links[stream]);
+ else
+ err = omap_mcpdm_capture_close(&mcpdm_links[stream]);
+
+ return err;
+}
+
+static struct snd_soc_dai_ops omap_mcpdm_dai_ops = {
+ .startup = omap_mcpdm_dai_startup,
+ .shutdown = omap_mcpdm_dai_shutdown,
+ .trigger = omap_mcpdm_dai_trigger,
+ .hw_params = omap_mcpdm_dai_hw_params,
+ .hw_free = omap_mcpdm_dai_hw_free,
+};
+
+#define OMAP_MCPDM_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+#define OMAP_MCPDM_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
+
+static int omap_mcpdm_dai_probe(struct snd_soc_dai *dai)
+{
+ snd_soc_dai_set_drvdata(dai, &mcpdm_data);
+ return 0;
+}
+
+static struct snd_soc_dai_driver omap_mcpdm_dai = {
+ .probe = omap_mcpdm_dai_probe,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = OMAP_MCPDM_RATES,
+ .formats = OMAP_MCPDM_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = OMAP_MCPDM_RATES,
+ .formats = OMAP_MCPDM_FORMATS,
+ },
+ .ops = &omap_mcpdm_dai_ops,
+};
+
+static __devinit int asoc_mcpdm_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = omap_mcpdm_probe(pdev);
+ if (ret < 0)
+ return ret;
+ ret = snd_soc_register_dai(&pdev->dev, &omap_mcpdm_dai);
+ if (ret < 0)
+ omap_mcpdm_remove(pdev);
+ return ret;
+}
+
+static int __devexit asoc_mcpdm_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+ omap_mcpdm_remove(pdev);
+ return 0;
+}
+
+static struct platform_driver asoc_mcpdm_driver = {
+ .driver = {
+ .name = "omap-mcpdm-dai",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = asoc_mcpdm_probe,
+ .remove = __devexit_p(asoc_mcpdm_remove),
+};
+
+static int __init snd_omap_mcpdm_init(void)
+{
+ return platform_driver_register(&asoc_mcpdm_driver);
+}
+module_init(snd_omap_mcpdm_init);
+
+static void __exit snd_omap_mcpdm_exit(void)
+{
+ platform_driver_unregister(&asoc_mcpdm_driver);
+}
+module_exit(snd_omap_mcpdm_exit);
+
+MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>");
+MODULE_DESCRIPTION("OMAP PDM SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c
new file mode 100644
index 00000000..e6a6b991
--- /dev/null
+++ b/sound/soc/omap/omap-pcm.c
@@ -0,0 +1,439 @@
+/*
+ * omap-pcm.c -- ALSA PCM interface for the OMAP SoC
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jhnikula@gmail.com>
+ * Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <plat/dma.h>
+#include "omap-pcm.h"
+
+static const struct snd_pcm_hardware omap_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .period_bytes_min = 32,
+ .period_bytes_max = 64 * 1024,
+ .periods_min = 2,
+ .periods_max = 255,
+ .buffer_bytes_max = 128 * 1024,
+};
+
+struct omap_runtime_data {
+ spinlock_t lock;
+ struct omap_pcm_dma_data *dma_data;
+ int dma_ch;
+ int period_index;
+};
+
+static void omap_pcm_dma_irq(int ch, u16 stat, void *data)
+{
+ struct snd_pcm_substream *substream = data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd = runtime->private_data;
+ unsigned long flags;
+
+ if ((cpu_is_omap1510())) {
+ /*
+ * OMAP1510 doesn't fully support DMA progress counter
+ * and there is no software emulation implemented yet,
+ * so have to maintain our own progress counters
+ * that can be used by omap_pcm_pointer() instead.
+ */
+ spin_lock_irqsave(&prtd->lock, flags);
+ if ((stat == OMAP_DMA_LAST_IRQ) &&
+ (prtd->period_index == runtime->periods - 1)) {
+ /* we are in sync, do nothing */
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ return;
+ }
+ if (prtd->period_index >= 0) {
+ if (stat & OMAP_DMA_BLOCK_IRQ) {
+ /* end of buffer reached, loop back */
+ prtd->period_index = 0;
+ } else if (stat & OMAP_DMA_LAST_IRQ) {
+ /* update the counter for the last period */
+ prtd->period_index = runtime->periods - 1;
+ } else if (++prtd->period_index >= runtime->periods) {
+ /* end of buffer missed? loop back */
+ prtd->period_index = 0;
+ }
+ }
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ }
+
+ snd_pcm_period_elapsed(substream);
+}
+
+/* this may get called several times by oss emulation */
+static int omap_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct omap_runtime_data *prtd = runtime->private_data;
+ struct omap_pcm_dma_data *dma_data;
+
+ int err = 0;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!dma_data)
+ return 0;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ if (prtd->dma_data)
+ return 0;
+ prtd->dma_data = dma_data;
+ err = omap_request_dma(dma_data->dma_req, dma_data->name,
+ omap_pcm_dma_irq, substream, &prtd->dma_ch);
+ if (!err) {
+ /*
+ * Link channel with itself so DMA doesn't need any
+ * reprogramming while looping the buffer
+ */
+ omap_dma_link_lch(prtd->dma_ch, prtd->dma_ch);
+ }
+
+ return err;
+}
+
+static int omap_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd = runtime->private_data;
+
+ if (prtd->dma_data == NULL)
+ return 0;
+
+ omap_dma_unlink_lch(prtd->dma_ch, prtd->dma_ch);
+ omap_free_dma(prtd->dma_ch);
+ prtd->dma_data = NULL;
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ return 0;
+}
+
+static int omap_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd = runtime->private_data;
+ struct omap_pcm_dma_data *dma_data = prtd->dma_data;
+ struct omap_dma_channel_params dma_params;
+ int bytes;
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!prtd->dma_data)
+ return 0;
+
+ memset(&dma_params, 0, sizeof(dma_params));
+ dma_params.data_type = dma_data->data_type;
+ dma_params.trigger = dma_data->dma_req;
+ dma_params.sync_mode = dma_data->sync_mode;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_params.src_amode = OMAP_DMA_AMODE_POST_INC;
+ dma_params.dst_amode = OMAP_DMA_AMODE_CONSTANT;
+ dma_params.src_or_dst_synch = OMAP_DMA_DST_SYNC;
+ dma_params.src_start = runtime->dma_addr;
+ dma_params.dst_start = dma_data->port_addr;
+ dma_params.dst_port = OMAP_DMA_PORT_MPUI;
+ dma_params.dst_fi = dma_data->packet_size;
+ } else {
+ dma_params.src_amode = OMAP_DMA_AMODE_CONSTANT;
+ dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC;
+ dma_params.src_or_dst_synch = OMAP_DMA_SRC_SYNC;
+ dma_params.src_start = dma_data->port_addr;
+ dma_params.dst_start = runtime->dma_addr;
+ dma_params.src_port = OMAP_DMA_PORT_MPUI;
+ dma_params.src_fi = dma_data->packet_size;
+ }
+ /*
+ * Set DMA transfer frame size equal to ALSA period size and frame
+ * count as no. of ALSA periods. Then with DMA frame interrupt enabled,
+ * we can transfer the whole ALSA buffer with single DMA transfer but
+ * still can get an interrupt at each period bounary
+ */
+ bytes = snd_pcm_lib_period_bytes(substream);
+ dma_params.elem_count = bytes >> dma_data->data_type;
+ dma_params.frame_count = runtime->periods;
+ omap_set_dma_params(prtd->dma_ch, &dma_params);
+
+ if ((cpu_is_omap1510()))
+ omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ |
+ OMAP_DMA_LAST_IRQ | OMAP_DMA_BLOCK_IRQ);
+ else if (!substream->runtime->no_period_wakeup)
+ omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ);
+
+ if (!(cpu_class_is_omap1())) {
+ omap_set_dma_src_burst_mode(prtd->dma_ch,
+ OMAP_DMA_DATA_BURST_16);
+ omap_set_dma_dest_burst_mode(prtd->dma_ch,
+ OMAP_DMA_DATA_BURST_16);
+ }
+
+ return 0;
+}
+
+static int omap_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd = runtime->private_data;
+ struct omap_pcm_dma_data *dma_data = prtd->dma_data;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&prtd->lock, flags);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ prtd->period_index = 0;
+ /* Configure McBSP internal buffer usage */
+ if (dma_data->set_threshold)
+ dma_data->set_threshold(substream);
+
+ omap_start_dma(prtd->dma_ch);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ prtd->period_index = -1;
+ omap_stop_dma(prtd->dma_ch);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ spin_unlock_irqrestore(&prtd->lock, flags);
+
+ return ret;
+}
+
+static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd = runtime->private_data;
+ dma_addr_t ptr;
+ snd_pcm_uframes_t offset;
+
+ if (cpu_is_omap1510()) {
+ offset = prtd->period_index * runtime->period_size;
+ } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ ptr = omap_get_dma_dst_pos(prtd->dma_ch);
+ offset = bytes_to_frames(runtime, ptr - runtime->dma_addr);
+ } else {
+ ptr = omap_get_dma_src_pos(prtd->dma_ch);
+ offset = bytes_to_frames(runtime, ptr - runtime->dma_addr);
+ }
+
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int omap_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd;
+ int ret;
+
+ snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware);
+
+ /* Ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+ if (prtd == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ spin_lock_init(&prtd->lock);
+ runtime->private_data = prtd;
+
+out:
+ return ret;
+}
+
+static int omap_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ kfree(runtime->private_data);
+ return 0;
+}
+
+static int omap_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops omap_pcm_ops = {
+ .open = omap_pcm_open,
+ .close = omap_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = omap_pcm_hw_params,
+ .hw_free = omap_pcm_hw_free,
+ .prepare = omap_pcm_prepare,
+ .trigger = omap_pcm_trigger,
+ .pointer = omap_pcm_pointer,
+ .mmap = omap_pcm_mmap,
+};
+
+static u64 omap_pcm_dmamask = DMA_BIT_MASK(64);
+
+static int omap_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
+ int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = omap_pcm_hardware.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+ return 0;
+}
+
+static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &omap_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(64);
+
+ if (dai->driver->playback.channels_min) {
+ ret = omap_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = omap_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static struct snd_soc_platform_driver omap_soc_platform = {
+ .ops = &omap_pcm_ops,
+ .pcm_new = omap_pcm_new,
+ .pcm_free = omap_pcm_free_dma_buffers,
+};
+
+static __devinit int omap_pcm_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev,
+ &omap_soc_platform);
+}
+
+static int __devexit omap_pcm_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver omap_pcm_driver = {
+ .driver = {
+ .name = "omap-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = omap_pcm_probe,
+ .remove = __devexit_p(omap_pcm_remove),
+};
+
+static int __init snd_omap_pcm_init(void)
+{
+ return platform_driver_register(&omap_pcm_driver);
+}
+module_init(snd_omap_pcm_init);
+
+static void __exit snd_omap_pcm_exit(void)
+{
+ platform_driver_unregister(&omap_pcm_driver);
+}
+module_exit(snd_omap_pcm_exit);
+
+MODULE_AUTHOR("Jarkko Nikula <jhnikula@gmail.com>");
+MODULE_DESCRIPTION("OMAP PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/omap-pcm.h b/sound/soc/omap/omap-pcm.h
new file mode 100644
index 00000000..a0ed1dbb
--- /dev/null
+++ b/sound/soc/omap/omap-pcm.h
@@ -0,0 +1,38 @@
+/*
+ * omap-pcm.h
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jhnikula@gmail.com>
+ * Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __OMAP_PCM_H__
+#define __OMAP_PCM_H__
+
+struct omap_pcm_dma_data {
+ char *name; /* stream identifier */
+ int dma_req; /* DMA request line */
+ unsigned long port_addr; /* transmit/receive register */
+ void (*set_threshold)(struct snd_pcm_substream *substream);
+ int data_type; /* data type 8,16,32 */
+ int sync_mode; /* DMA sync mode */
+ int packet_size; /* packet size only in PACKET mode */
+};
+
+#endif
diff --git a/sound/soc/omap/omap3beagle.c b/sound/soc/omap/omap3beagle.c
new file mode 100644
index 00000000..40db813c
--- /dev/null
+++ b/sound/soc/omap/omap3beagle.c
@@ -0,0 +1,149 @@
+/*
+ * omap3beagle.c -- SoC audio for OMAP3 Beagle
+ *
+ * Author: Steve Sakoman <steve@sakoman.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+static int omap3beagle_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int fmt;
+ int ret;
+
+ switch (params_channels(params)) {
+ case 2: /* Stereo I2S mode */
+ fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ break;
+ case 4: /* Four channel TDM mode */
+ fmt = SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return ret;
+ }
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops omap3beagle_ops = {
+ .hw_params = omap3beagle_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link omap3beagle_dai = {
+ .name = "TWL4030",
+ .stream_name = "TWL4030",
+ .cpu_dai_name = "omap-mcbsp-dai.1",
+ .platform_name = "omap-pcm-audio",
+ .codec_dai_name = "twl4030-hifi",
+ .codec_name = "twl4030-codec",
+ .ops = &omap3beagle_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_omap3beagle = {
+ .name = "omap3beagle",
+ .owner = THIS_MODULE,
+ .dai_link = &omap3beagle_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *omap3beagle_snd_device;
+
+static int __init omap3beagle_soc_init(void)
+{
+ int ret;
+
+ if (!(machine_is_omap3_beagle() || machine_is_devkit8000()))
+ return -ENODEV;
+ pr_info("OMAP3 Beagle/Devkit8000 SoC init\n");
+
+ omap3beagle_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!omap3beagle_snd_device) {
+ printk(KERN_ERR "Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(omap3beagle_snd_device, &snd_soc_omap3beagle);
+
+ ret = platform_device_add(omap3beagle_snd_device);
+ if (ret)
+ goto err1;
+
+ return 0;
+
+err1:
+ printk(KERN_ERR "Unable to add platform device\n");
+ platform_device_put(omap3beagle_snd_device);
+
+ return ret;
+}
+
+static void __exit omap3beagle_soc_exit(void)
+{
+ platform_device_unregister(omap3beagle_snd_device);
+}
+
+module_init(omap3beagle_soc_init);
+module_exit(omap3beagle_soc_exit);
+
+MODULE_AUTHOR("Steve Sakoman <steve@sakoman.com>");
+MODULE_DESCRIPTION("ALSA SoC OMAP3 Beagle");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/omap3evm.c b/sound/soc/omap/omap3evm.c
new file mode 100644
index 00000000..0daa0446
--- /dev/null
+++ b/sound/soc/omap/omap3evm.c
@@ -0,0 +1,135 @@
+/*
+ * omap3evm.c -- ALSA SoC support for OMAP3 EVM
+ *
+ * Author: Anuj Aggarwal <anuj.aggarwal@ti.com>
+ *
+ * Based on sound/soc/omap/beagle.c by Steve Sakoman
+ *
+ * Copyright (C) 2008 Texas Instruments, Incorporated
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
+ * whether express or implied; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+static int omap3evm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* Set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "Can't set codec DAI configuration\n");
+ return ret;
+ }
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "Can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "Can't set codec system clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops omap3evm_ops = {
+ .hw_params = omap3evm_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link omap3evm_dai = {
+ .name = "TWL4030",
+ .stream_name = "TWL4030",
+ .cpu_dai_name = "omap-mcbsp-dai.1",
+ .codec_dai_name = "twl4030-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "twl4030-codec",
+ .ops = &omap3evm_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_omap3evm = {
+ .name = "omap3evm",
+ .dai_link = &omap3evm_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *omap3evm_snd_device;
+
+static int __init omap3evm_soc_init(void)
+{
+ int ret;
+
+ if (!machine_is_omap3evm())
+ return -ENODEV;
+ pr_info("OMAP3 EVM SoC init\n");
+
+ omap3evm_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!omap3evm_snd_device) {
+ printk(KERN_ERR "Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(omap3evm_snd_device, &snd_soc_omap3evm);
+ ret = platform_device_add(omap3evm_snd_device);
+ if (ret)
+ goto err1;
+
+ return 0;
+
+err1:
+ printk(KERN_ERR "Unable to add platform device\n");
+ platform_device_put(omap3evm_snd_device);
+
+ return ret;
+}
+
+static void __exit omap3evm_soc_exit(void)
+{
+ platform_device_unregister(omap3evm_snd_device);
+}
+
+module_init(omap3evm_soc_init);
+module_exit(omap3evm_soc_exit);
+
+MODULE_AUTHOR("Anuj Aggarwal <anuj.aggarwal@ti.com>");
+MODULE_DESCRIPTION("ALSA SoC OMAP3 EVM");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c
new file mode 100644
index 00000000..8047c521
--- /dev/null
+++ b/sound/soc/omap/omap3pandora.c
@@ -0,0 +1,339 @@
+/*
+ * omap3pandora.c -- SoC audio for Pandora Handheld Console
+ *
+ * Author: Gražvydas Ignotas <notasas@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#define OMAP3_PANDORA_DAC_POWER_GPIO 118
+#define OMAP3_PANDORA_AMP_POWER_GPIO 14
+
+#define PREFIX "ASoC omap3pandora: "
+
+static struct regulator *omap3pandora_dac_reg;
+
+static int omap3pandora_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+ int ret;
+
+ /* Set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret < 0) {
+ pr_err(PREFIX "can't set codec DAI configuration\n");
+ return ret;
+ }
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0) {
+ pr_err(PREFIX "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err(PREFIX "can't set codec system clock\n");
+ return ret;
+ }
+
+ /* Set McBSP clock to external */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_EXT,
+ 256 * params_rate(params),
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err(PREFIX "can't set cpu system clock\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, 8);
+ if (ret < 0) {
+ pr_err(PREFIX "can't set SRG clock divider\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int omap3pandora_dac_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ /*
+ * The PCM1773 DAC datasheet requires 1ms delay between switching
+ * VCC power on/off and /PD pin high/low
+ */
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ regulator_enable(omap3pandora_dac_reg);
+ mdelay(1);
+ gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 1);
+ } else {
+ gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
+ mdelay(1);
+ regulator_disable(omap3pandora_dac_reg);
+ }
+
+ return 0;
+}
+
+static int omap3pandora_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 1);
+ else
+ gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
+
+ return 0;
+}
+
+/*
+ * Audio paths on Pandora board:
+ *
+ * |O| ---> PCM DAC +-> AMP -> Headphone Jack
+ * |M| A +--------> Line Out
+ * |A| <~~clk~~+
+ * |P| <--- TWL4030 <--------- Line In and MICs
+ */
+static const struct snd_soc_dapm_widget omap3pandora_out_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC_E("PCM DAC", "HiFi Playback", SND_SOC_NOPM,
+ 0, 0, omap3pandora_dac_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("Headphone Amplifier", SND_SOC_NOPM,
+ 0, 0, NULL, 0, omap3pandora_hp_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+};
+
+static const struct snd_soc_dapm_widget omap3pandora_in_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Mic (internal)", NULL),
+ SND_SOC_DAPM_MIC("Mic (external)", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+static const struct snd_soc_dapm_route omap3pandora_out_map[] = {
+ {"PCM DAC", NULL, "APLL Enable"},
+ {"Headphone Amplifier", NULL, "PCM DAC"},
+ {"Line Out", NULL, "PCM DAC"},
+ {"Headphone Jack", NULL, "Headphone Amplifier"},
+};
+
+static const struct snd_soc_dapm_route omap3pandora_in_map[] = {
+ {"AUXL", NULL, "Line In"},
+ {"AUXR", NULL, "Line In"},
+
+ {"MAINMIC", NULL, "Mic Bias 1"},
+ {"Mic Bias 1", NULL, "Mic (internal)"},
+
+ {"SUBMIC", NULL, "Mic Bias 2"},
+ {"Mic Bias 2", NULL, "Mic (external)"},
+};
+
+static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ /* All TWL4030 output pins are floating */
+ snd_soc_dapm_nc_pin(dapm, "EARPIECE");
+ snd_soc_dapm_nc_pin(dapm, "PREDRIVEL");
+ snd_soc_dapm_nc_pin(dapm, "PREDRIVER");
+ snd_soc_dapm_nc_pin(dapm, "HSOL");
+ snd_soc_dapm_nc_pin(dapm, "HSOR");
+ snd_soc_dapm_nc_pin(dapm, "CARKITL");
+ snd_soc_dapm_nc_pin(dapm, "CARKITR");
+ snd_soc_dapm_nc_pin(dapm, "HFL");
+ snd_soc_dapm_nc_pin(dapm, "HFR");
+ snd_soc_dapm_nc_pin(dapm, "VIBRA");
+
+ ret = snd_soc_dapm_new_controls(dapm, omap3pandora_out_dapm_widgets,
+ ARRAY_SIZE(omap3pandora_out_dapm_widgets));
+ if (ret < 0)
+ return ret;
+
+ snd_soc_dapm_add_routes(dapm, omap3pandora_out_map,
+ ARRAY_SIZE(omap3pandora_out_map));
+
+ return snd_soc_dapm_sync(dapm);
+}
+
+static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ /* Not comnnected */
+ snd_soc_dapm_nc_pin(dapm, "HSMIC");
+ snd_soc_dapm_nc_pin(dapm, "CARKITMIC");
+ snd_soc_dapm_nc_pin(dapm, "DIGIMIC0");
+ snd_soc_dapm_nc_pin(dapm, "DIGIMIC1");
+
+ ret = snd_soc_dapm_new_controls(dapm, omap3pandora_in_dapm_widgets,
+ ARRAY_SIZE(omap3pandora_in_dapm_widgets));
+ if (ret < 0)
+ return ret;
+
+ snd_soc_dapm_add_routes(dapm, omap3pandora_in_map,
+ ARRAY_SIZE(omap3pandora_in_map));
+
+ return snd_soc_dapm_sync(dapm);
+}
+
+static struct snd_soc_ops omap3pandora_ops = {
+ .hw_params = omap3pandora_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link omap3pandora_dai[] = {
+ {
+ .name = "PCM1773",
+ .stream_name = "HiFi Out",
+ .cpu_dai_name = "omap-mcbsp-dai.1",
+ .codec_dai_name = "twl4030-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "twl4030-codec",
+ .ops = &omap3pandora_ops,
+ .init = omap3pandora_out_init,
+ }, {
+ .name = "TWL4030",
+ .stream_name = "Line/Mic In",
+ .cpu_dai_name = "omap-mcbsp-dai.3",
+ .codec_dai_name = "twl4030-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "twl4030-codec",
+ .ops = &omap3pandora_ops,
+ .init = omap3pandora_in_init,
+ }
+};
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_omap3pandora = {
+ .name = "omap3pandora",
+ .dai_link = omap3pandora_dai,
+ .num_links = ARRAY_SIZE(omap3pandora_dai),
+};
+
+static struct platform_device *omap3pandora_snd_device;
+
+static int __init omap3pandora_soc_init(void)
+{
+ int ret;
+
+ if (!machine_is_omap3_pandora())
+ return -ENODEV;
+
+ pr_info("OMAP3 Pandora SoC init\n");
+
+ ret = gpio_request(OMAP3_PANDORA_DAC_POWER_GPIO, "dac_power");
+ if (ret) {
+ pr_err(PREFIX "Failed to get DAC power GPIO\n");
+ return ret;
+ }
+
+ ret = gpio_direction_output(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
+ if (ret) {
+ pr_err(PREFIX "Failed to set DAC power GPIO direction\n");
+ goto fail0;
+ }
+
+ ret = gpio_request(OMAP3_PANDORA_AMP_POWER_GPIO, "amp_power");
+ if (ret) {
+ pr_err(PREFIX "Failed to get amp power GPIO\n");
+ goto fail0;
+ }
+
+ ret = gpio_direction_output(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
+ if (ret) {
+ pr_err(PREFIX "Failed to set amp power GPIO direction\n");
+ goto fail1;
+ }
+
+ omap3pandora_snd_device = platform_device_alloc("soc-audio", -1);
+ if (omap3pandora_snd_device == NULL) {
+ pr_err(PREFIX "Platform device allocation failed\n");
+ ret = -ENOMEM;
+ goto fail1;
+ }
+
+ platform_set_drvdata(omap3pandora_snd_device, &snd_soc_card_omap3pandora);
+
+ ret = platform_device_add(omap3pandora_snd_device);
+ if (ret) {
+ pr_err(PREFIX "Unable to add platform device\n");
+ goto fail2;
+ }
+
+ omap3pandora_dac_reg = regulator_get(&omap3pandora_snd_device->dev, "vcc");
+ if (IS_ERR(omap3pandora_dac_reg)) {
+ pr_err(PREFIX "Failed to get DAC regulator from %s: %ld\n",
+ dev_name(&omap3pandora_snd_device->dev),
+ PTR_ERR(omap3pandora_dac_reg));
+ ret = PTR_ERR(omap3pandora_dac_reg);
+ goto fail3;
+ }
+
+ return 0;
+
+fail3:
+ platform_device_del(omap3pandora_snd_device);
+fail2:
+ platform_device_put(omap3pandora_snd_device);
+fail1:
+ gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
+fail0:
+ gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
+ return ret;
+}
+module_init(omap3pandora_soc_init);
+
+static void __exit omap3pandora_soc_exit(void)
+{
+ regulator_put(omap3pandora_dac_reg);
+ platform_device_unregister(omap3pandora_snd_device);
+ gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
+ gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
+}
+module_exit(omap3pandora_soc_exit);
+
+MODULE_AUTHOR("Grazvydas Ignotas <notasas@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC OMAP3 Pandora");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/osk5912.c b/sound/soc/omap/osk5912.c
new file mode 100644
index 00000000..7e75e775
--- /dev/null
+++ b/sound/soc/omap/osk5912.c
@@ -0,0 +1,223 @@
+/*
+ * osk5912.c -- SoC audio for OSK 5912
+ *
+ * Copyright (C) 2008 Mistral Solutions
+ *
+ * Contact: Arun KS <arunks@mistralsolutions.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <linux/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+#include "../codecs/tlv320aic23.h"
+
+#define CODEC_CLOCK 12000000
+
+static struct clk *tlv320aic23_mclk;
+
+static int osk_startup(struct snd_pcm_substream *substream)
+{
+ return clk_enable(tlv320aic23_mclk);
+}
+
+static void osk_shutdown(struct snd_pcm_substream *substream)
+{
+ clk_disable(tlv320aic23_mclk);
+}
+
+static int osk_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int err;
+
+ /* Set codec DAI configuration */
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return err;
+ }
+
+ /* Set cpu DAI configuration */
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return err;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ err =
+ snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN);
+
+ if (err < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return err;
+ }
+
+ return err;
+}
+
+static struct snd_soc_ops osk_ops = {
+ .startup = osk_startup,
+ .hw_params = osk_hw_params,
+ .shutdown = osk_shutdown,
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Jack", NULL, "LHPOUT"},
+ {"Headphone Jack", NULL, "RHPOUT"},
+
+ {"LLINEIN", NULL, "Line In"},
+ {"RLINEIN", NULL, "Line In"},
+
+ {"MICIN", NULL, "Mic Jack"},
+};
+
+static int osk_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* Add osk5912 specific widgets */
+ snd_soc_dapm_new_controls(dapm, tlv320aic23_dapm_widgets,
+ ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+ /* Set up osk5912 specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Line In");
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link osk_dai = {
+ .name = "TLV320AIC23",
+ .stream_name = "AIC23",
+ .cpu_dai_name = "omap-mcbsp-dai.0",
+ .codec_dai_name = "tlv320aic23-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "tlv320aic23-codec",
+ .init = osk_tlv320aic23_init,
+ .ops = &osk_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_card_osk = {
+ .name = "OSK5912",
+ .dai_link = &osk_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *osk_snd_device;
+
+static int __init osk_soc_init(void)
+{
+ int err;
+ u32 curRate;
+ struct device *dev;
+
+ if (!(machine_is_omap_osk()))
+ return -ENODEV;
+
+ osk_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!osk_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(osk_snd_device, &snd_soc_card_osk);
+ err = platform_device_add(osk_snd_device);
+ if (err)
+ goto err1;
+
+ dev = &osk_snd_device->dev;
+
+ tlv320aic23_mclk = clk_get(dev, "mclk");
+ if (IS_ERR(tlv320aic23_mclk)) {
+ printk(KERN_ERR "Could not get mclk clock\n");
+ err = PTR_ERR(tlv320aic23_mclk);
+ goto err2;
+ }
+
+ /*
+ * Configure 12 MHz output on MCLK.
+ */
+ curRate = (uint) clk_get_rate(tlv320aic23_mclk);
+ if (curRate != CODEC_CLOCK) {
+ if (clk_set_rate(tlv320aic23_mclk, CODEC_CLOCK)) {
+ printk(KERN_ERR "Cannot set MCLK for AIC23 CODEC\n");
+ err = -ECANCELED;
+ goto err3;
+ }
+ }
+
+ printk(KERN_INFO "MCLK = %d [%d]\n",
+ (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK);
+
+ return 0;
+
+err3:
+ clk_put(tlv320aic23_mclk);
+err2:
+ platform_device_del(osk_snd_device);
+err1:
+ platform_device_put(osk_snd_device);
+
+ return err;
+
+}
+
+static void __exit osk_soc_exit(void)
+{
+ clk_put(tlv320aic23_mclk);
+ platform_device_unregister(osk_snd_device);
+}
+
+module_init(osk_soc_init);
+module_exit(osk_soc_exit);
+
+MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>");
+MODULE_DESCRIPTION("ALSA SoC OSK 5912");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/overo.c b/sound/soc/omap/overo.c
new file mode 100644
index 00000000..bbcf380b
--- /dev/null
+++ b/sound/soc/omap/overo.c
@@ -0,0 +1,139 @@
+/*
+ * overo.c -- SoC audio for Gumstix Overo
+ *
+ * Author: Steve Sakoman <steve@sakoman.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+static int overo_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* Set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return ret;
+ }
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops overo_ops = {
+ .hw_params = overo_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link overo_dai = {
+ .name = "TWL4030",
+ .stream_name = "TWL4030",
+ .cpu_dai_name = "omap-mcbsp-dai.1",
+ .codec_dai_name = "twl4030-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "twl4030-codec",
+ .ops = &overo_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_card_overo = {
+ .name = "overo",
+ .dai_link = &overo_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *overo_snd_device;
+
+static int __init overo_soc_init(void)
+{
+ int ret;
+
+ if (!(machine_is_overo() || machine_is_cm_t35())) {
+ pr_debug("Incomatible machine!\n");
+ return -ENODEV;
+ }
+ printk(KERN_INFO "overo SoC init\n");
+
+ overo_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!overo_snd_device) {
+ printk(KERN_ERR "Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(overo_snd_device, &snd_soc_card_overo);
+
+ ret = platform_device_add(overo_snd_device);
+ if (ret)
+ goto err1;
+
+ return 0;
+
+err1:
+ printk(KERN_ERR "Unable to add platform device\n");
+ platform_device_put(overo_snd_device);
+
+ return ret;
+}
+module_init(overo_soc_init);
+
+static void __exit overo_soc_exit(void)
+{
+ platform_device_unregister(overo_snd_device);
+}
+module_exit(overo_soc_exit);
+
+MODULE_AUTHOR("Steve Sakoman <steve@sakoman.com>");
+MODULE_DESCRIPTION("ALSA SoC overo");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c
new file mode 100644
index 00000000..0aae998b
--- /dev/null
+++ b/sound/soc/omap/rx51.c
@@ -0,0 +1,468 @@
+/*
+ * rx51.c -- SoC audio for Nokia RX-51
+ *
+ * Copyright (C) 2008 - 2009 Nokia Corporation
+ *
+ * Contact: Peter Ujfalusi <peter.ujfalusi@ti.com>
+ * Eduardo Valentin <eduardo.valentin@nokia.com>
+ * Jarkko Nikula <jhnikula@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <plat/mcbsp.h>
+#include "../codecs/tpa6130a2.h"
+
+#include <asm/mach-types.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#define RX51_TVOUT_SEL_GPIO 40
+#define RX51_JACK_DETECT_GPIO 177
+#define RX51_ECI_SW_GPIO 182
+/*
+ * REVISIT: TWL4030 GPIO base in RX-51. Now statically defined to 192. This
+ * gpio is reserved in arch/arm/mach-omap2/board-rx51-peripherals.c
+ */
+#define RX51_SPEAKER_AMP_TWL_GPIO (192 + 7)
+
+enum {
+ RX51_JACK_DISABLED,
+ RX51_JACK_TVOUT, /* tv-out with stereo output */
+ RX51_JACK_HP, /* headphone: stereo output, no mic */
+ RX51_JACK_HS, /* headset: stereo output with mic */
+};
+
+static int rx51_spk_func;
+static int rx51_dmic_func;
+static int rx51_jack_func;
+
+static void rx51_ext_control(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int hp = 0, hs = 0, tvout = 0;
+
+ switch (rx51_jack_func) {
+ case RX51_JACK_TVOUT:
+ tvout = 1;
+ hp = 1;
+ break;
+ case RX51_JACK_HS:
+ hs = 1;
+ case RX51_JACK_HP:
+ hp = 1;
+ break;
+ }
+
+ if (rx51_spk_func)
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Ext Spk");
+ if (rx51_dmic_func)
+ snd_soc_dapm_enable_pin(dapm, "DMic");
+ else
+ snd_soc_dapm_disable_pin(dapm, "DMic");
+ if (hp)
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ if (hs)
+ snd_soc_dapm_enable_pin(dapm, "HS Mic");
+ else
+ snd_soc_dapm_disable_pin(dapm, "HS Mic");
+
+ gpio_set_value(RX51_TVOUT_SEL_GPIO, tvout);
+
+ snd_soc_dapm_sync(dapm);
+}
+
+static int rx51_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2);
+ rx51_ext_control(codec);
+
+ return 0;
+}
+
+static int rx51_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int err;
+
+ /* Set codec DAI configuration */
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0)
+ return err;
+
+ /* Set cpu DAI configuration */
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0)
+ return err;
+
+ /* Set the codec system clock for DAC and ADC */
+ return snd_soc_dai_set_sysclk(codec_dai, 0, 19200000,
+ SND_SOC_CLOCK_IN);
+}
+
+static struct snd_soc_ops rx51_ops = {
+ .startup = rx51_startup,
+ .hw_params = rx51_hw_params,
+};
+
+static int rx51_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = rx51_spk_func;
+
+ return 0;
+}
+
+static int rx51_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (rx51_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ rx51_spk_func = ucontrol->value.integer.value[0];
+ rx51_ext_control(codec);
+
+ return 1;
+}
+
+static int rx51_spk_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpio_set_value_cansleep(RX51_SPEAKER_AMP_TWL_GPIO, 1);
+ else
+ gpio_set_value_cansleep(RX51_SPEAKER_AMP_TWL_GPIO, 0);
+
+ return 0;
+}
+
+static int rx51_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_codec *codec = w->dapm->codec;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ tpa6130a2_stereo_enable(codec, 1);
+ else
+ tpa6130a2_stereo_enable(codec, 0);
+
+ return 0;
+}
+
+static int rx51_get_input(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = rx51_dmic_func;
+
+ return 0;
+}
+
+static int rx51_set_input(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (rx51_dmic_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ rx51_dmic_func = ucontrol->value.integer.value[0];
+ rx51_ext_control(codec);
+
+ return 1;
+}
+
+static int rx51_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = rx51_jack_func;
+
+ return 0;
+}
+
+static int rx51_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (rx51_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ rx51_jack_func = ucontrol->value.integer.value[0];
+ rx51_ext_control(codec);
+
+ return 1;
+}
+
+static struct snd_soc_jack rx51_av_jack;
+
+static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = {
+ {
+ .gpio = RX51_JACK_DETECT_GPIO,
+ .name = "avdet-gpio",
+ .report = SND_JACK_HEADSET,
+ .invert = 1,
+ .debounce_time = 200,
+ },
+};
+
+static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event),
+ SND_SOC_DAPM_MIC("DMic", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", rx51_hp_event),
+ SND_SOC_DAPM_MIC("HS Mic", NULL),
+ SND_SOC_DAPM_LINE("FM Transmitter", NULL),
+};
+
+static const struct snd_soc_dapm_widget aic34_dapm_widgetsb[] = {
+ SND_SOC_DAPM_SPK("Earphone", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Ext Spk", NULL, "HPLOUT"},
+ {"Ext Spk", NULL, "HPROUT"},
+ {"Headphone Jack", NULL, "LLOUT"},
+ {"Headphone Jack", NULL, "RLOUT"},
+ {"FM Transmitter", NULL, "LLOUT"},
+ {"FM Transmitter", NULL, "RLOUT"},
+
+ {"DMic Rate 64", NULL, "Mic Bias 2V"},
+ {"Mic Bias 2V", NULL, "DMic"},
+};
+
+static const struct snd_soc_dapm_route audio_mapb[] = {
+ {"b LINE2R", NULL, "MONO_LOUT"},
+ {"Earphone", NULL, "b HPLOUT"},
+
+ {"LINE1L", NULL, "b Mic Bias 2.5V"},
+ {"b Mic Bias 2.5V", NULL, "HS Mic"}
+};
+
+static const char *spk_function[] = {"Off", "On"};
+static const char *input_function[] = {"ADC", "Digital Mic"};
+static const char *jack_function[] = {"Off", "TV-OUT", "Headphone", "Headset"};
+
+static const struct soc_enum rx51_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function),
+};
+
+static const struct snd_kcontrol_new aic34_rx51_controls[] = {
+ SOC_ENUM_EXT("Speaker Function", rx51_enum[0],
+ rx51_get_spk, rx51_set_spk),
+ SOC_ENUM_EXT("Input Select", rx51_enum[1],
+ rx51_get_input, rx51_set_input),
+ SOC_ENUM_EXT("Jack Function", rx51_enum[2],
+ rx51_get_jack, rx51_set_jack),
+ SOC_DAPM_PIN_SWITCH("FM Transmitter"),
+};
+
+static const struct snd_kcontrol_new aic34_rx51_controlsb[] = {
+ SOC_DAPM_PIN_SWITCH("Earphone"),
+};
+
+static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ /* Set up NC codec pins */
+ snd_soc_dapm_nc_pin(dapm, "MIC3L");
+ snd_soc_dapm_nc_pin(dapm, "MIC3R");
+ snd_soc_dapm_nc_pin(dapm, "LINE1R");
+
+ /* Add RX-51 specific controls */
+ err = snd_soc_add_controls(codec, aic34_rx51_controls,
+ ARRAY_SIZE(aic34_rx51_controls));
+ if (err < 0)
+ return err;
+
+ /* Add RX-51 specific widgets */
+ snd_soc_dapm_new_controls(dapm, aic34_dapm_widgets,
+ ARRAY_SIZE(aic34_dapm_widgets));
+
+ /* Set up RX-51 specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ err = tpa6130a2_add_controls(codec);
+ if (err < 0)
+ return err;
+ snd_soc_limit_volume(codec, "TPA6130A2 Headphone Playback Volume", 42);
+
+ err = omap_mcbsp_st_add_controls(codec, 1);
+ if (err < 0)
+ return err;
+
+ snd_soc_dapm_sync(dapm);
+
+ /* AV jack detection */
+ err = snd_soc_jack_new(codec, "AV Jack",
+ SND_JACK_HEADSET | SND_JACK_VIDEOOUT,
+ &rx51_av_jack);
+ if (err)
+ return err;
+ err = snd_soc_jack_add_gpios(&rx51_av_jack,
+ ARRAY_SIZE(rx51_av_jack_gpios),
+ rx51_av_jack_gpios);
+
+ return err;
+}
+
+static int rx51_aic34b_init(struct snd_soc_dapm_context *dapm)
+{
+ int err;
+
+ err = snd_soc_add_controls(dapm->codec, aic34_rx51_controlsb,
+ ARRAY_SIZE(aic34_rx51_controlsb));
+ if (err < 0)
+ return err;
+
+ err = snd_soc_dapm_new_controls(dapm, aic34_dapm_widgetsb,
+ ARRAY_SIZE(aic34_dapm_widgetsb));
+ if (err < 0)
+ return 0;
+
+ return snd_soc_dapm_add_routes(dapm, audio_mapb,
+ ARRAY_SIZE(audio_mapb));
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link rx51_dai[] = {
+ {
+ .name = "TLV320AIC34",
+ .stream_name = "AIC34",
+ .cpu_dai_name = "omap-mcbsp-dai.1",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "tlv320aic3x-codec.2-0018",
+ .init = rx51_aic34_init,
+ .ops = &rx51_ops,
+ },
+};
+
+struct snd_soc_aux_dev rx51_aux_dev[] = {
+ {
+ .name = "TLV320AIC34b",
+ .codec_name = "tlv320aic3x-codec.2-0019",
+ .init = rx51_aic34b_init,
+ },
+};
+
+static struct snd_soc_codec_conf rx51_codec_conf[] = {
+ {
+ .dev_name = "tlv320aic3x-codec.2-0019",
+ .name_prefix = "b",
+ },
+};
+
+/* Audio card */
+static struct snd_soc_card rx51_sound_card = {
+ .name = "RX-51",
+ .dai_link = rx51_dai,
+ .num_links = ARRAY_SIZE(rx51_dai),
+ .aux_dev = rx51_aux_dev,
+ .num_aux_devs = ARRAY_SIZE(rx51_aux_dev),
+ .codec_conf = rx51_codec_conf,
+ .num_configs = ARRAY_SIZE(rx51_codec_conf),
+};
+
+static struct platform_device *rx51_snd_device;
+
+static int __init rx51_soc_init(void)
+{
+ int err;
+
+ if (!machine_is_nokia_rx51())
+ return -ENODEV;
+
+ err = gpio_request_one(RX51_TVOUT_SEL_GPIO,
+ GPIOF_DIR_OUT | GPIOF_INIT_LOW, "tvout_sel");
+ if (err)
+ goto err_gpio_tvout_sel;
+ err = gpio_request_one(RX51_ECI_SW_GPIO,
+ GPIOF_DIR_OUT | GPIOF_INIT_HIGH, "eci_sw");
+ if (err)
+ goto err_gpio_eci_sw;
+
+ rx51_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!rx51_snd_device) {
+ err = -ENOMEM;
+ goto err1;
+ }
+
+ platform_set_drvdata(rx51_snd_device, &rx51_sound_card);
+
+ err = platform_device_add(rx51_snd_device);
+ if (err)
+ goto err2;
+
+ return 0;
+err2:
+ platform_device_put(rx51_snd_device);
+err1:
+ gpio_free(RX51_ECI_SW_GPIO);
+err_gpio_eci_sw:
+ gpio_free(RX51_TVOUT_SEL_GPIO);
+err_gpio_tvout_sel:
+
+ return err;
+}
+
+static void __exit rx51_soc_exit(void)
+{
+ snd_soc_jack_free_gpios(&rx51_av_jack, ARRAY_SIZE(rx51_av_jack_gpios),
+ rx51_av_jack_gpios);
+
+ platform_device_unregister(rx51_snd_device);
+ gpio_free(RX51_ECI_SW_GPIO);
+ gpio_free(RX51_TVOUT_SEL_GPIO);
+}
+
+module_init(rx51_soc_init);
+module_exit(rx51_soc_exit);
+
+MODULE_AUTHOR("Nokia Corporation");
+MODULE_DESCRIPTION("ALSA SoC Nokia RX-51");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/sdp3430.c b/sound/soc/omap/sdp3430.c
new file mode 100644
index 00000000..3f72d17d
--- /dev/null
+++ b/sound/soc/omap/sdp3430.c
@@ -0,0 +1,345 @@
+/*
+ * sdp3430.c -- SoC audio for TI OMAP3430 SDP
+ *
+ * Author: Misael Lopez Cruz <x0052729@ti.com>
+ *
+ * Based on:
+ * Author: Steve Sakoman <steve@sakoman.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+/* Register descriptions for twl4030 codec part */
+#include <linux/mfd/twl4030-codec.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+/* TWL4030 PMBR1 Register */
+#define TWL4030_INTBR_PMBR1 0x0D
+/* TWL4030 PMBR1 Register GPIO6 mux bit */
+#define TWL4030_GPIO6_PWM0_MUTE(value) (value << 2)
+
+static struct snd_soc_card snd_soc_sdp3430;
+
+static int sdp3430_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* Set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return ret;
+ }
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops sdp3430_ops = {
+ .hw_params = sdp3430_hw_params,
+};
+
+static int sdp3430_hw_voice_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* Set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return ret;
+ }
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops sdp3430_voice_ops = {
+ .hw_params = sdp3430_hw_voice_params,
+};
+
+/* Headset jack */
+static struct snd_soc_jack hs_jack;
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headset Stereophone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+/* Headset jack detection gpios */
+static struct snd_soc_jack_gpio hs_jack_gpios[] = {
+ {
+ .gpio = (OMAP_MAX_GPIO_LINES + 2),
+ .name = "hsdet-gpio",
+ .report = SND_JACK_HEADSET,
+ .debounce_time = 200,
+ },
+};
+
+/* SDP3430 machine DAPM */
+static const struct snd_soc_dapm_widget sdp3430_twl4030_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Ext Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* External Mics: MAINMIC, SUBMIC with bias*/
+ {"MAINMIC", NULL, "Mic Bias 1"},
+ {"SUBMIC", NULL, "Mic Bias 2"},
+ {"Mic Bias 1", NULL, "Ext Mic"},
+ {"Mic Bias 2", NULL, "Ext Mic"},
+
+ /* External Speakers: HFL, HFR */
+ {"Ext Spk", NULL, "HFL"},
+ {"Ext Spk", NULL, "HFR"},
+
+ /* Headset Mic: HSMIC with bias */
+ {"HSMIC", NULL, "Headset Mic Bias"},
+ {"Headset Mic Bias", NULL, "Headset Mic"},
+
+ /* Headset Stereophone (Headphone): HSOL, HSOR */
+ {"Headset Stereophone", NULL, "HSOL"},
+ {"Headset Stereophone", NULL, "HSOR"},
+};
+
+static int sdp3430_twl4030_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ /* Add SDP3430 specific widgets */
+ ret = snd_soc_dapm_new_controls(dapm, sdp3430_twl4030_dapm_widgets,
+ ARRAY_SIZE(sdp3430_twl4030_dapm_widgets));
+ if (ret)
+ return ret;
+
+ /* Set up SDP3430 specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ /* SDP3430 connected pins */
+ snd_soc_dapm_enable_pin(dapm, "Ext Mic");
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ snd_soc_dapm_disable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_disable_pin(dapm, "Headset Stereophone");
+
+ /* TWL4030 not connected pins */
+ snd_soc_dapm_nc_pin(dapm, "AUXL");
+ snd_soc_dapm_nc_pin(dapm, "AUXR");
+ snd_soc_dapm_nc_pin(dapm, "CARKITMIC");
+ snd_soc_dapm_nc_pin(dapm, "DIGIMIC0");
+ snd_soc_dapm_nc_pin(dapm, "DIGIMIC1");
+
+ snd_soc_dapm_nc_pin(dapm, "OUTL");
+ snd_soc_dapm_nc_pin(dapm, "OUTR");
+ snd_soc_dapm_nc_pin(dapm, "EARPIECE");
+ snd_soc_dapm_nc_pin(dapm, "PREDRIVEL");
+ snd_soc_dapm_nc_pin(dapm, "PREDRIVER");
+ snd_soc_dapm_nc_pin(dapm, "CARKITL");
+ snd_soc_dapm_nc_pin(dapm, "CARKITR");
+
+ ret = snd_soc_dapm_sync(dapm);
+ if (ret)
+ return ret;
+
+ /* Headset jack detection */
+ ret = snd_soc_jack_new(codec, "Headset Jack",
+ SND_JACK_HEADSET, &hs_jack);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+ hs_jack_pins);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios),
+ hs_jack_gpios);
+
+ return ret;
+}
+
+static int sdp3430_twl4030_voice_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ unsigned short reg;
+
+ /* Enable voice interface */
+ reg = codec->driver->read(codec, TWL4030_REG_VOICE_IF);
+ reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN;
+ codec->driver->write(codec, TWL4030_REG_VOICE_IF, reg);
+
+ return 0;
+}
+
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link sdp3430_dai[] = {
+ {
+ .name = "TWL4030 I2S",
+ .stream_name = "TWL4030 Audio",
+ .cpu_dai_name = "omap-mcbsp-dai.1",
+ .codec_dai_name = "twl4030-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "twl4030-codec",
+ .init = sdp3430_twl4030_init,
+ .ops = &sdp3430_ops,
+ },
+ {
+ .name = "TWL4030 PCM",
+ .stream_name = "TWL4030 Voice",
+ .cpu_dai_name = "omap-mcbsp-dai.2",
+ .codec_dai_name = "twl4030-voice",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "twl4030-codec",
+ .init = sdp3430_twl4030_voice_init,
+ .ops = &sdp3430_voice_ops,
+ },
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_sdp3430 = {
+ .name = "SDP3430",
+ .dai_link = sdp3430_dai,
+ .num_links = ARRAY_SIZE(sdp3430_dai),
+};
+
+static struct platform_device *sdp3430_snd_device;
+
+static int __init sdp3430_soc_init(void)
+{
+ int ret;
+ u8 pin_mux;
+
+ if (!machine_is_omap_3430sdp())
+ return -ENODEV;
+ printk(KERN_INFO "SDP3430 SoC init\n");
+
+ sdp3430_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!sdp3430_snd_device) {
+ printk(KERN_ERR "Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(sdp3430_snd_device, &snd_soc_sdp3430);
+
+ /* Set TWL4030 GPIO6 as EXTMUTE signal */
+ twl_i2c_read_u8(TWL4030_MODULE_INTBR, &pin_mux,
+ TWL4030_INTBR_PMBR1);
+ pin_mux &= ~TWL4030_GPIO6_PWM0_MUTE(0x03);
+ pin_mux |= TWL4030_GPIO6_PWM0_MUTE(0x02);
+ twl_i2c_write_u8(TWL4030_MODULE_INTBR, pin_mux,
+ TWL4030_INTBR_PMBR1);
+
+ ret = platform_device_add(sdp3430_snd_device);
+ if (ret)
+ goto err1;
+
+ return 0;
+
+err1:
+ printk(KERN_ERR "Unable to add platform device\n");
+ platform_device_put(sdp3430_snd_device);
+
+ return ret;
+}
+module_init(sdp3430_soc_init);
+
+static void __exit sdp3430_soc_exit(void)
+{
+ snd_soc_jack_free_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios),
+ hs_jack_gpios);
+
+ platform_device_unregister(sdp3430_snd_device);
+}
+module_exit(sdp3430_soc_exit);
+
+MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>");
+MODULE_DESCRIPTION("ALSA SoC SDP3430");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c
new file mode 100644
index 00000000..189e0390
--- /dev/null
+++ b/sound/soc/omap/sdp4430.c
@@ -0,0 +1,261 @@
+/*
+ * sdp4430.c -- SoC audio for TI OMAP4430 SDP
+ *
+ * Author: Misael Lopez Cruz <x0052729@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+#include <plat/hardware.h>
+#include <plat/mux.h>
+
+#include "mcpdm.h"
+#include "omap-pcm.h"
+#include "../codecs/twl6040.h"
+
+static int twl6040_power_mode;
+
+static int sdp4430_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int clk_id, freq;
+ int ret;
+
+ if (twl6040_power_mode) {
+ clk_id = TWL6040_SYSCLK_SEL_HPPLL;
+ freq = 38400000;
+ } else {
+ clk_id = TWL6040_SYSCLK_SEL_LPPLL;
+ freq = 32768;
+ }
+
+ /* set the codec mclk */
+ ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, freq,
+ SND_SOC_CLOCK_IN);
+ if (ret) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return ret;
+ }
+ return ret;
+}
+
+static struct snd_soc_ops sdp4430_ops = {
+ .hw_params = sdp4430_hw_params,
+};
+
+/* Headset jack */
+static struct snd_soc_jack hs_jack;
+
+/*Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headset Stereophone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static int sdp4430_get_power_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = twl6040_power_mode;
+ return 0;
+}
+
+static int sdp4430_set_power_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ if (twl6040_power_mode == ucontrol->value.integer.value[0])
+ return 0;
+
+ twl6040_power_mode = ucontrol->value.integer.value[0];
+
+ return 1;
+}
+
+static const char *power_texts[] = {"Low-Power", "High-Performance"};
+
+static const struct soc_enum sdp4430_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, power_texts),
+};
+
+static const struct snd_kcontrol_new sdp4430_controls[] = {
+ SOC_ENUM_EXT("TWL6040 Power Mode", sdp4430_enum[0],
+ sdp4430_get_power_mode, sdp4430_set_power_mode),
+};
+
+/* SDP4430 machine DAPM */
+static const struct snd_soc_dapm_widget sdp4430_twl6040_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Ext Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+ SND_SOC_DAPM_SPK("Earphone Spk", NULL),
+ SND_SOC_DAPM_INPUT("Aux/FM Stereo In"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* External Mics: MAINMIC, SUBMIC with bias*/
+ {"MAINMIC", NULL, "Main Mic Bias"},
+ {"SUBMIC", NULL, "Main Mic Bias"},
+ {"Main Mic Bias", NULL, "Ext Mic"},
+
+ /* External Speakers: HFL, HFR */
+ {"Ext Spk", NULL, "HFL"},
+ {"Ext Spk", NULL, "HFR"},
+
+ /* Headset Mic: HSMIC with bias */
+ {"HSMIC", NULL, "Headset Mic Bias"},
+ {"Headset Mic Bias", NULL, "Headset Mic"},
+
+ /* Headset Stereophone (Headphone): HSOL, HSOR */
+ {"Headset Stereophone", NULL, "HSOL"},
+ {"Headset Stereophone", NULL, "HSOR"},
+
+ /* Earphone speaker */
+ {"Earphone Spk", NULL, "EP"},
+
+ /* Aux/FM Stereo In: AFML, AFMR */
+ {"AFML", NULL, "Aux/FM Stereo In"},
+ {"AFMR", NULL, "Aux/FM Stereo In"},
+};
+
+static int sdp4430_twl6040_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ /* Add SDP4430 specific controls */
+ ret = snd_soc_add_controls(codec, sdp4430_controls,
+ ARRAY_SIZE(sdp4430_controls));
+ if (ret)
+ return ret;
+
+ /* Add SDP4430 specific widgets */
+ ret = snd_soc_dapm_new_controls(dapm, sdp4430_twl6040_dapm_widgets,
+ ARRAY_SIZE(sdp4430_twl6040_dapm_widgets));
+ if (ret)
+ return ret;
+
+ /* Set up SDP4430 specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ /* SDP4430 connected pins */
+ snd_soc_dapm_enable_pin(dapm, "Ext Mic");
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ snd_soc_dapm_enable_pin(dapm, "AFML");
+ snd_soc_dapm_enable_pin(dapm, "AFMR");
+ snd_soc_dapm_enable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_enable_pin(dapm, "Headset Stereophone");
+
+ ret = snd_soc_dapm_sync(dapm);
+ if (ret)
+ return ret;
+
+ /* Headset jack detection */
+ ret = snd_soc_jack_new(codec, "Headset Jack",
+ SND_JACK_HEADSET, &hs_jack);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+ hs_jack_pins);
+
+ if (machine_is_omap_4430sdp())
+ twl6040_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADSET);
+ else
+ snd_soc_jack_report(&hs_jack, SND_JACK_HEADSET, SND_JACK_HEADSET);
+
+ return ret;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link sdp4430_dai = {
+ .name = "TWL6040",
+ .stream_name = "TWL6040",
+ .cpu_dai_name ="omap-mcpdm-dai",
+ .codec_dai_name = "twl6040-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "twl6040-codec",
+ .init = sdp4430_twl6040_init,
+ .ops = &sdp4430_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_sdp4430 = {
+ .name = "SDP4430",
+ .dai_link = &sdp4430_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *sdp4430_snd_device;
+
+static int __init sdp4430_soc_init(void)
+{
+ int ret;
+
+ if (!machine_is_omap_4430sdp())
+ return -ENODEV;
+ printk(KERN_INFO "SDP4430 SoC init\n");
+
+ sdp4430_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!sdp4430_snd_device) {
+ printk(KERN_ERR "Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(sdp4430_snd_device, &snd_soc_sdp4430);
+
+ ret = platform_device_add(sdp4430_snd_device);
+ if (ret)
+ goto err;
+
+ /* Codec starts in HP mode */
+ twl6040_power_mode = 1;
+
+ return 0;
+
+err:
+ printk(KERN_ERR "Unable to add platform device\n");
+ platform_device_put(sdp4430_snd_device);
+ return ret;
+}
+module_init(sdp4430_soc_init);
+
+static void __exit sdp4430_soc_exit(void)
+{
+ platform_device_unregister(sdp4430_snd_device);
+}
+module_exit(sdp4430_soc_exit);
+
+MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>");
+MODULE_DESCRIPTION("ALSA SoC SDP4430");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/omap/zoom2.c b/sound/soc/omap/zoom2.c
new file mode 100644
index 00000000..01709940
--- /dev/null
+++ b/sound/soc/omap/zoom2.c
@@ -0,0 +1,291 @@
+/*
+ * zoom2.c -- SoC audio for Zoom2
+ *
+ * Author: Misael Lopez Cruz <x0052729@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <mach/board-zoom.h>
+#include <plat/mcbsp.h>
+
+/* Register descriptions for twl4030 codec part */
+#include <linux/mfd/twl4030-codec.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#define ZOOM2_HEADSET_MUX_GPIO (OMAP_MAX_GPIO_LINES + 15)
+
+static int zoom2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* Set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return ret;
+ }
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops zoom2_ops = {
+ .hw_params = zoom2_hw_params,
+};
+
+static int zoom2_hw_voice_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* Set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return ret;
+ }
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops zoom2_voice_ops = {
+ .hw_params = zoom2_hw_voice_params,
+};
+
+/* Zoom2 machine DAPM */
+static const struct snd_soc_dapm_widget zoom2_twl4030_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Ext Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+ SND_SOC_DAPM_LINE("Aux In", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* External Mics: MAINMIC, SUBMIC with bias*/
+ {"MAINMIC", NULL, "Mic Bias 1"},
+ {"SUBMIC", NULL, "Mic Bias 2"},
+ {"Mic Bias 1", NULL, "Ext Mic"},
+ {"Mic Bias 2", NULL, "Ext Mic"},
+
+ /* External Speakers: HFL, HFR */
+ {"Ext Spk", NULL, "HFL"},
+ {"Ext Spk", NULL, "HFR"},
+
+ /* Headset Stereophone: HSOL, HSOR */
+ {"Headset Stereophone", NULL, "HSOL"},
+ {"Headset Stereophone", NULL, "HSOR"},
+
+ /* Headset Mic: HSMIC with bias */
+ {"HSMIC", NULL, "Headset Mic Bias"},
+ {"Headset Mic Bias", NULL, "Headset Mic"},
+
+ /* Aux In: AUXL, AUXR */
+ {"Aux In", NULL, "AUXL"},
+ {"Aux In", NULL, "AUXR"},
+};
+
+static int zoom2_twl4030_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ /* Add Zoom2 specific widgets */
+ ret = snd_soc_dapm_new_controls(dapm, zoom2_twl4030_dapm_widgets,
+ ARRAY_SIZE(zoom2_twl4030_dapm_widgets));
+ if (ret)
+ return ret;
+
+ /* Set up Zoom2 specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ /* Zoom2 connected pins */
+ snd_soc_dapm_enable_pin(dapm, "Ext Mic");
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ snd_soc_dapm_enable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_enable_pin(dapm, "Headset Stereophone");
+ snd_soc_dapm_enable_pin(dapm, "Aux In");
+
+ /* TWL4030 not connected pins */
+ snd_soc_dapm_nc_pin(dapm, "CARKITMIC");
+ snd_soc_dapm_nc_pin(dapm, "DIGIMIC0");
+ snd_soc_dapm_nc_pin(dapm, "DIGIMIC1");
+ snd_soc_dapm_nc_pin(dapm, "EARPIECE");
+ snd_soc_dapm_nc_pin(dapm, "PREDRIVEL");
+ snd_soc_dapm_nc_pin(dapm, "PREDRIVER");
+ snd_soc_dapm_nc_pin(dapm, "CARKITL");
+ snd_soc_dapm_nc_pin(dapm, "CARKITR");
+
+ ret = snd_soc_dapm_sync(dapm);
+
+ return ret;
+}
+
+static int zoom2_twl4030_voice_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ unsigned short reg;
+
+ /* Enable voice interface */
+ reg = codec->driver->read(codec, TWL4030_REG_VOICE_IF);
+ reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN;
+ codec->driver->write(codec, TWL4030_REG_VOICE_IF, reg);
+
+ return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link zoom2_dai[] = {
+ {
+ .name = "TWL4030 I2S",
+ .stream_name = "TWL4030 Audio",
+ .cpu_dai_name = "omap-mcbsp-dai.1",
+ .codec_dai_name = "twl4030-hifi",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "twl4030-codec",
+ .init = zoom2_twl4030_init,
+ .ops = &zoom2_ops,
+ },
+ {
+ .name = "TWL4030 PCM",
+ .stream_name = "TWL4030 Voice",
+ .cpu_dai_name = "omap-mcbsp-dai.2",
+ .codec_dai_name = "twl4030-voice",
+ .platform_name = "omap-pcm-audio",
+ .codec_name = "twl4030-codec",
+ .init = zoom2_twl4030_voice_init,
+ .ops = &zoom2_voice_ops,
+ },
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_zoom2 = {
+ .name = "Zoom2",
+ .dai_link = zoom2_dai,
+ .num_links = ARRAY_SIZE(zoom2_dai),
+};
+
+static struct platform_device *zoom2_snd_device;
+
+static int __init zoom2_soc_init(void)
+{
+ int ret;
+
+ if (!machine_is_omap_zoom2())
+ return -ENODEV;
+ printk(KERN_INFO "Zoom2 SoC init\n");
+
+ zoom2_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!zoom2_snd_device) {
+ printk(KERN_ERR "Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(zoom2_snd_device, &snd_soc_zoom2);
+ ret = platform_device_add(zoom2_snd_device);
+ if (ret)
+ goto err1;
+
+ BUG_ON(gpio_request(ZOOM2_HEADSET_MUX_GPIO, "hs_mux") < 0);
+ gpio_direction_output(ZOOM2_HEADSET_MUX_GPIO, 0);
+
+ BUG_ON(gpio_request(ZOOM2_HEADSET_EXTMUTE_GPIO, "ext_mute") < 0);
+ gpio_direction_output(ZOOM2_HEADSET_EXTMUTE_GPIO, 0);
+
+ return 0;
+
+err1:
+ printk(KERN_ERR "Unable to add platform device\n");
+ platform_device_put(zoom2_snd_device);
+
+ return ret;
+}
+module_init(zoom2_soc_init);
+
+static void __exit zoom2_soc_exit(void)
+{
+ gpio_free(ZOOM2_HEADSET_MUX_GPIO);
+ gpio_free(ZOOM2_HEADSET_EXTMUTE_GPIO);
+
+ platform_device_unregister(zoom2_snd_device);
+}
+module_exit(zoom2_soc_exit);
+
+MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>");
+MODULE_DESCRIPTION("ALSA SoC Zoom2");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
new file mode 100644
index 00000000..33ebc46b
--- /dev/null
+++ b/sound/soc/pxa/Kconfig
@@ -0,0 +1,193 @@
+config SND_PXA2XX_SOC
+ tristate "SoC Audio for the Intel PXA2xx chip"
+ depends on ARCH_PXA
+ select SND_ARM
+ select SND_PXA2XX_LIB
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the PXA2xx AC97, I2S or SSP interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_PXA2XX_AC97
+ tristate
+ select SND_AC97_CODEC
+
+config SND_PXA2XX_SOC_AC97
+ tristate
+ select AC97_BUS
+ select SND_ARM
+ select SND_PXA2XX_LIB_AC97
+ select SND_SOC_AC97_BUS
+
+config SND_PXA2XX_SOC_I2S
+ tristate
+
+config SND_PXA_SOC_SSP
+ tristate
+ select PXA_SSP
+
+config SND_PXA2XX_SOC_CORGI
+ tristate "SoC Audio support for Sharp Zaurus SL-C7x0"
+ depends on SND_PXA2XX_SOC && PXA_SHARP_C7xx
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_WM8731
+ help
+ Say Y if you want to add support for SoC audio on Sharp
+ Zaurus SL-C7x0 models (Corgi, Shepherd, Husky).
+
+config SND_PXA2XX_SOC_SPITZ
+ tristate "SoC Audio support for Sharp Zaurus SL-Cxx00"
+ depends on SND_PXA2XX_SOC && PXA_SHARP_Cxx00
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_WM8750
+ help
+ Say Y if you want to add support for SoC audio on Sharp
+ Zaurus SL-Cxx00 models (Spitz, Borzoi and Akita).
+
+config SND_PXA2XX_SOC_Z2
+ tristate "SoC Audio support for Zipit Z2"
+ depends on SND_PXA2XX_SOC && MACH_ZIPIT2
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_WM8750
+ help
+ Say Y if you want to add support for SoC audio on Zipit Z2.
+
+config SND_PXA2XX_SOC_POODLE
+ tristate "SoC Audio support for Poodle"
+ depends on SND_PXA2XX_SOC && MACH_POODLE
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_WM8731
+ help
+ Say Y if you want to add support for SoC audio on Sharp
+ Zaurus SL-5600 model (Poodle).
+
+config SND_PXA2XX_SOC_TOSA
+ tristate "SoC AC97 Audio support for Tosa"
+ depends on SND_PXA2XX_SOC && MACH_TOSA
+ depends on MFD_TC6393XB
+ select SND_PXA2XX_SOC_AC97
+ select SND_SOC_WM9712
+ help
+ Say Y if you want to add support for SoC audio on Sharp
+ Zaurus SL-C6000x models (Tosa).
+
+config SND_PXA2XX_SOC_E740
+ tristate "SoC AC97 Audio support for e740"
+ depends on SND_PXA2XX_SOC && MACH_E740
+ select SND_SOC_WM9705
+ select SND_PXA2XX_SOC_AC97
+ help
+ Say Y if you want to add support for SoC audio on the
+ toshiba e740 PDA
+
+config SND_PXA2XX_SOC_E750
+ tristate "SoC AC97 Audio support for e750"
+ depends on SND_PXA2XX_SOC && MACH_E750
+ select SND_SOC_WM9705
+ select SND_PXA2XX_SOC_AC97
+ help
+ Say Y if you want to add support for SoC audio on the
+ toshiba e750 PDA
+
+config SND_PXA2XX_SOC_E800
+ tristate "SoC AC97 Audio support for e800"
+ depends on SND_PXA2XX_SOC && MACH_E800
+ select SND_SOC_WM9712
+ select SND_PXA2XX_SOC_AC97
+ help
+ Say Y if you want to add support for SoC audio on the
+ Toshiba e800 PDA
+
+config SND_PXA2XX_SOC_EM_X270
+ tristate "SoC Audio support for CompuLab EM-x270, eXeda and CM-X300"
+ depends on SND_PXA2XX_SOC && (MACH_EM_X270 || MACH_EXEDA || \
+ MACH_CM_X300)
+ select SND_PXA2XX_SOC_AC97
+ select SND_SOC_WM9712
+ help
+ Say Y if you want to add support for SoC audio on
+ CompuLab EM-x270, eXeda and CM-X300 machines.
+
+config SND_PXA2XX_SOC_PALM27X
+ bool "SoC Audio support for Palm T|X, T5, E2 and LifeDrive"
+ depends on SND_PXA2XX_SOC && (MACH_PALMLD || MACH_PALMTX || \
+ MACH_PALMT5 || MACH_PALMTE2)
+ select SND_PXA2XX_SOC_AC97
+ select SND_SOC_WM9712
+ help
+ Say Y if you want to add support for SoC audio on
+ Palm T|X, T5, E2 or LifeDrive handheld computer.
+
+config SND_SOC_SAARB
+ tristate "SoC Audio support for Marvell Saarb"
+ depends on SND_PXA2XX_SOC && MACH_SAARB
+ select SND_PXA_SOC_SSP
+ select SND_SOC_88PM860X
+ help
+ Say Y if you want to add support for SoC audio on the
+ Marvell Saarb reference platform.
+
+config SND_SOC_TAVOREVB3
+ tristate "SoC Audio support for Marvell Tavor EVB3"
+ depends on SND_PXA2XX_SOC && MACH_TAVOREVB3
+ select SND_PXA_SOC_SSP
+ select SND_SOC_88PM860X
+ help
+ Say Y if you want to add support for SoC audio on the
+ Marvell Saarb reference platform.
+
+config SND_SOC_ZYLONITE
+ tristate "SoC Audio support for Marvell Zylonite"
+ depends on SND_PXA2XX_SOC && MACH_ZYLONITE
+ select SND_PXA2XX_SOC_AC97
+ select SND_PXA_SOC_SSP
+ select SND_SOC_WM9713
+ help
+ Say Y if you want to add support for SoC audio on the
+ Marvell Zylonite reference platform.
+
+config SND_SOC_RAUMFELD
+ tristate "SoC Audio support Raumfeld audio adapter"
+ depends on SND_PXA2XX_SOC && (MACH_RAUMFELD_SPEAKER || MACH_RAUMFELD_CONNECTOR)
+ select SND_PXA_SOC_SSP
+ select SND_SOC_CS4270
+ select SND_SOC_AK4104
+ help
+ Say Y if you want to add support for SoC audio on Raumfeld devices
+
+config SND_PXA2XX_SOC_HX4700
+ tristate "SoC Audio support for HP iPAQ hx4700"
+ depends on SND_PXA2XX_SOC && MACH_H4700
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_AK4641
+ help
+ Say Y if you want to add support for SoC audio on the
+ HP iPAQ hx4700.
+
+config SND_PXA2XX_SOC_MAGICIAN
+ tristate "SoC Audio support for HTC Magician"
+ depends on SND_PXA2XX_SOC && MACH_MAGICIAN
+ select SND_PXA2XX_SOC_I2S
+ select SND_PXA_SOC_SSP
+ select SND_SOC_UDA1380
+ help
+ Say Y if you want to add support for SoC audio on the
+ HTC Magician.
+
+config SND_PXA2XX_SOC_MIOA701
+ tristate "SoC Audio support for MIO A701"
+ depends on SND_PXA2XX_SOC && MACH_MIOA701
+ select SND_PXA2XX_SOC_AC97
+ select SND_SOC_WM9713
+ help
+ Say Y if you want to add support for SoC audio on the
+ MIO A701.
+
+config SND_PXA2XX_SOC_IMOTE2
+ tristate "SoC Audio support for IMote 2"
+ depends on SND_PXA2XX_SOC && MACH_INTELMOTE2 && I2C
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_WM8940
+ help
+ Say Y if you want to add support for SoC audio on the
+ IMote 2.
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile
new file mode 100644
index 00000000..af357623
--- /dev/null
+++ b/sound/soc/pxa/Makefile
@@ -0,0 +1,49 @@
+# PXA Platform Support
+snd-soc-pxa2xx-objs := pxa2xx-pcm.o
+snd-soc-pxa2xx-ac97-objs := pxa2xx-ac97.o
+snd-soc-pxa2xx-i2s-objs := pxa2xx-i2s.o
+snd-soc-pxa-ssp-objs := pxa-ssp.o
+
+obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o
+obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o
+obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o
+obj-$(CONFIG_SND_PXA_SOC_SSP) += snd-soc-pxa-ssp.o
+
+# PXA Machine Support
+snd-soc-corgi-objs := corgi.o
+snd-soc-poodle-objs := poodle.o
+snd-soc-tosa-objs := tosa.o
+snd-soc-e740-objs := e740_wm9705.o
+snd-soc-e750-objs := e750_wm9705.o
+snd-soc-e800-objs := e800_wm9712.o
+snd-soc-spitz-objs := spitz.o
+snd-soc-em-x270-objs := em-x270.o
+snd-soc-palm27x-objs := palm27x.o
+snd-soc-saarb-objs := saarb.o
+snd-soc-tavorevb3-objs := tavorevb3.o
+snd-soc-zylonite-objs := zylonite.o
+snd-soc-hx4700-objs := hx4700.o
+snd-soc-magician-objs := magician.o
+snd-soc-mioa701-objs := mioa701_wm9713.o
+snd-soc-z2-objs := z2.o
+snd-soc-imote2-objs := imote2.o
+snd-soc-raumfeld-objs := raumfeld.o
+
+obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o
+obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o
+obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o
+obj-$(CONFIG_SND_PXA2XX_SOC_E740) += snd-soc-e740.o
+obj-$(CONFIG_SND_PXA2XX_SOC_E750) += snd-soc-e750.o
+obj-$(CONFIG_SND_PXA2XX_SOC_E800) += snd-soc-e800.o
+obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o
+obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o
+obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o
+obj-$(CONFIG_SND_PXA2XX_SOC_HX4700) += snd-soc-hx4700.o
+obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o
+obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o
+obj-$(CONFIG_SND_PXA2XX_SOC_Z2) += snd-soc-z2.o
+obj-$(CONFIG_SND_SOC_SAARB) += snd-soc-saarb.o
+obj-$(CONFIG_SND_SOC_TAVOREVB3) += snd-soc-tavorevb3.o
+obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o
+obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o
+obj-$(CONFIG_SND_SOC_RAUMFELD) += snd-soc-raumfeld.o
diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c
new file mode 100644
index 00000000..28757fb9
--- /dev/null
+++ b/sound/soc/pxa/corgi.c
@@ -0,0 +1,359 @@
+/*
+ * corgi.c -- SoC audio for Corgi
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/corgi.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm8731.h"
+#include "pxa2xx-i2s.h"
+
+#define CORGI_HP 0
+#define CORGI_MIC 1
+#define CORGI_LINE 2
+#define CORGI_HEADSET 3
+#define CORGI_HP_OFF 4
+#define CORGI_SPK_ON 0
+#define CORGI_SPK_OFF 1
+
+ /* audio clock in Hz - rounded from 12.235MHz */
+#define CORGI_AUDIO_CLOCK 12288000
+
+static int corgi_jack_func;
+static int corgi_spk_func;
+
+static void corgi_ext_control(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* set up jack connection */
+ switch (corgi_jack_func) {
+ case CORGI_HP:
+ /* set = unmute headphone */
+ gpio_set_value(CORGI_GPIO_MUTE_L, 1);
+ gpio_set_value(CORGI_GPIO_MUTE_R, 1);
+ snd_soc_dapm_disable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin(dapm, "Line Jack");
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headset Jack");
+ break;
+ case CORGI_MIC:
+ /* reset = mute headphone */
+ gpio_set_value(CORGI_GPIO_MUTE_L, 0);
+ gpio_set_value(CORGI_GPIO_MUTE_R, 0);
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin(dapm, "Line Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headset Jack");
+ break;
+ case CORGI_LINE:
+ gpio_set_value(CORGI_GPIO_MUTE_L, 0);
+ gpio_set_value(CORGI_GPIO_MUTE_R, 0);
+ snd_soc_dapm_disable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_enable_pin(dapm, "Line Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headset Jack");
+ break;
+ case CORGI_HEADSET:
+ gpio_set_value(CORGI_GPIO_MUTE_L, 0);
+ gpio_set_value(CORGI_GPIO_MUTE_R, 1);
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin(dapm, "Line Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Headset Jack");
+ break;
+ }
+
+ if (corgi_spk_func == CORGI_SPK_ON)
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Ext Spk");
+
+ /* signal a DAPM event */
+ snd_soc_dapm_sync(dapm);
+}
+
+static int corgi_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ mutex_lock(&codec->mutex);
+
+ /* check the jack status at stream startup */
+ corgi_ext_control(codec);
+
+ mutex_unlock(&codec->mutex);
+
+ return 0;
+}
+
+/* we need to unmute the HP at shutdown as the mute burns power on corgi */
+static void corgi_shutdown(struct snd_pcm_substream *substream)
+{
+ /* set = unmute headphone */
+ gpio_set_value(CORGI_GPIO_MUTE_L, 1);
+ gpio_set_value(CORGI_GPIO_MUTE_R, 1);
+}
+
+static int corgi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as input (unused) */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops corgi_ops = {
+ .startup = corgi_startup,
+ .hw_params = corgi_hw_params,
+ .shutdown = corgi_shutdown,
+};
+
+static int corgi_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = corgi_jack_func;
+ return 0;
+}
+
+static int corgi_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (corgi_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ corgi_jack_func = ucontrol->value.integer.value[0];
+ corgi_ext_control(codec);
+ return 1;
+}
+
+static int corgi_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = corgi_spk_func;
+ return 0;
+}
+
+static int corgi_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (corgi_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ corgi_spk_func = ucontrol->value.integer.value[0];
+ corgi_ext_control(codec);
+ return 1;
+}
+
+static int corgi_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(CORGI_GPIO_APM_ON, SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+static int corgi_mic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(CORGI_GPIO_MIC_BIAS, SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+/* corgi machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+SND_SOC_DAPM_HP("Headphone Jack", NULL),
+SND_SOC_DAPM_MIC("Mic Jack", corgi_mic_event),
+SND_SOC_DAPM_SPK("Ext Spk", corgi_amp_event),
+SND_SOC_DAPM_LINE("Line Jack", NULL),
+SND_SOC_DAPM_HP("Headset Jack", NULL),
+};
+
+/* Corgi machine audio map (connections to the codec pins) */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headset Jack - in = micin, out = LHPOUT*/
+ {"Headset Jack", NULL, "LHPOUT"},
+
+ /* headphone connected to LHPOUT1, RHPOUT1 */
+ {"Headphone Jack", NULL, "LHPOUT"},
+ {"Headphone Jack", NULL, "RHPOUT"},
+
+ /* speaker connected to LOUT, ROUT */
+ {"Ext Spk", NULL, "ROUT"},
+ {"Ext Spk", NULL, "LOUT"},
+
+ /* mic is connected to MICIN (via right channel of headphone jack) */
+ {"MICIN", NULL, "Mic Jack"},
+
+ /* Same as the above but no mic bias for line signals */
+ {"MICIN", NULL, "Line Jack"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
+ "Off"};
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum corgi_enum[] = {
+ SOC_ENUM_SINGLE_EXT(5, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8731_corgi_controls[] = {
+ SOC_ENUM_EXT("Jack Function", corgi_enum[0], corgi_get_jack,
+ corgi_set_jack),
+ SOC_ENUM_EXT("Speaker Function", corgi_enum[1], corgi_get_spk,
+ corgi_set_spk),
+};
+
+/*
+ * Logic for a wm8731 as connected on a Sharp SL-C7x0 Device
+ */
+static int corgi_wm8731_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ snd_soc_dapm_nc_pin(dapm, "LLINEIN");
+ snd_soc_dapm_nc_pin(dapm, "RLINEIN");
+
+ /* Add corgi specific controls */
+ err = snd_soc_add_controls(codec, wm8731_corgi_controls,
+ ARRAY_SIZE(wm8731_corgi_controls));
+ if (err < 0)
+ return err;
+
+ /* Add corgi specific widgets */
+ snd_soc_dapm_new_controls(dapm, wm8731_dapm_widgets,
+ ARRAY_SIZE(wm8731_dapm_widgets));
+
+ /* Set up corgi specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(dapm);
+ return 0;
+}
+
+/* corgi digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link corgi_dai = {
+ .name = "WM8731",
+ .stream_name = "WM8731",
+ .cpu_dai_name = "pxa2xx-i2s",
+ .codec_dai_name = "wm8731-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm8731.0-001b",
+ .init = corgi_wm8731_init,
+ .ops = &corgi_ops,
+};
+
+/* corgi audio machine driver */
+static struct snd_soc_card snd_soc_corgi = {
+ .name = "Corgi",
+ .dai_link = &corgi_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *corgi_snd_device;
+
+static int __init corgi_init(void)
+{
+ int ret;
+
+ if (!(machine_is_corgi() || machine_is_shepherd() ||
+ machine_is_husky()))
+ return -ENODEV;
+
+ corgi_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!corgi_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(corgi_snd_device, &snd_soc_corgi);
+ ret = platform_device_add(corgi_snd_device);
+
+ if (ret)
+ platform_device_put(corgi_snd_device);
+
+ return ret;
+}
+
+static void __exit corgi_exit(void)
+{
+ platform_device_unregister(corgi_snd_device);
+}
+
+module_init(corgi_init);
+module_exit(corgi_exit);
+
+/* Module information */
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Corgi");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/e740_wm9705.c b/sound/soc/pxa/e740_wm9705.c
new file mode 100644
index 00000000..dc65650a
--- /dev/null
+++ b/sound/soc/pxa/e740_wm9705.c
@@ -0,0 +1,212 @@
+/*
+ * e740-wm9705.c -- SoC audio for e740
+ *
+ * Copyright 2007 (c) Ian Molton <spyro@f2s.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; version 2 ONLY.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <mach/audio.h>
+#include <mach/eseries-gpio.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/wm9705.h"
+#include "pxa2xx-ac97.h"
+
+
+#define E740_AUDIO_OUT 1
+#define E740_AUDIO_IN 2
+
+static int e740_audio_power;
+
+static void e740_sync_audio_power(int status)
+{
+ gpio_set_value(GPIO_E740_WM9705_nAVDD2, !status);
+ gpio_set_value(GPIO_E740_AMP_ON, (status & E740_AUDIO_OUT) ? 1 : 0);
+ gpio_set_value(GPIO_E740_MIC_ON, (status & E740_AUDIO_IN) ? 1 : 0);
+}
+
+static int e740_mic_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (event & SND_SOC_DAPM_PRE_PMU)
+ e740_audio_power |= E740_AUDIO_IN;
+ else if (event & SND_SOC_DAPM_POST_PMD)
+ e740_audio_power &= ~E740_AUDIO_IN;
+
+ e740_sync_audio_power(e740_audio_power);
+
+ return 0;
+}
+
+static int e740_output_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (event & SND_SOC_DAPM_PRE_PMU)
+ e740_audio_power |= E740_AUDIO_OUT;
+ else if (event & SND_SOC_DAPM_POST_PMD)
+ e740_audio_power &= ~E740_AUDIO_OUT;
+
+ e740_sync_audio_power(e740_audio_power);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget e740_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("Mic (Internal)", NULL),
+ SND_SOC_DAPM_PGA_E("Output Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+ e740_output_amp_event, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_PGA_E("Mic Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+ e740_mic_amp_event, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Output Amp", NULL, "LOUT"},
+ {"Output Amp", NULL, "ROUT"},
+ {"Output Amp", NULL, "MONOOUT"},
+
+ {"Speaker", NULL, "Output Amp"},
+ {"Headphone Jack", NULL, "Output Amp"},
+
+ {"MIC1", NULL, "Mic Amp"},
+ {"Mic Amp", NULL, "Mic (Internal)"},
+};
+
+static int e740_ac97_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_nc_pin(dapm, "HPOUTL");
+ snd_soc_dapm_nc_pin(dapm, "HPOUTR");
+ snd_soc_dapm_nc_pin(dapm, "PHONE");
+ snd_soc_dapm_nc_pin(dapm, "LINEINL");
+ snd_soc_dapm_nc_pin(dapm, "LINEINR");
+ snd_soc_dapm_nc_pin(dapm, "CDINL");
+ snd_soc_dapm_nc_pin(dapm, "CDINR");
+ snd_soc_dapm_nc_pin(dapm, "PCBEEP");
+ snd_soc_dapm_nc_pin(dapm, "MIC2");
+
+ snd_soc_dapm_new_controls(dapm, e740_dapm_widgets,
+ ARRAY_SIZE(e740_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link e740_dai[] = {
+ {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "pxa2xx-ac97",
+ .codec_dai_name = "wm9705-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm9705-codec",
+ .init = e740_ac97_init,
+ },
+ {
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .cpu_dai_name = "pxa2xx-ac97-aux",
+ .codec_dai_name = "wm9705-aux",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm9705-codec",
+ },
+};
+
+static struct snd_soc_card e740 = {
+ .name = "Toshiba e740",
+ .dai_link = e740_dai,
+ .num_links = ARRAY_SIZE(e740_dai),
+};
+
+static struct platform_device *e740_snd_device;
+
+static int __init e740_init(void)
+{
+ int ret;
+
+ if (!machine_is_e740())
+ return -ENODEV;
+
+ ret = gpio_request(GPIO_E740_MIC_ON, "Mic amp");
+ if (ret)
+ return ret;
+
+ ret = gpio_request(GPIO_E740_AMP_ON, "Output amp");
+ if (ret)
+ goto free_mic_amp_gpio;
+
+ ret = gpio_request(GPIO_E740_WM9705_nAVDD2, "Audio power");
+ if (ret)
+ goto free_op_amp_gpio;
+
+ /* Disable audio */
+ ret = gpio_direction_output(GPIO_E740_MIC_ON, 0);
+ if (ret)
+ goto free_apwr_gpio;
+ ret = gpio_direction_output(GPIO_E740_AMP_ON, 0);
+ if (ret)
+ goto free_apwr_gpio;
+ ret = gpio_direction_output(GPIO_E740_WM9705_nAVDD2, 1);
+ if (ret)
+ goto free_apwr_gpio;
+
+ e740_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!e740_snd_device) {
+ ret = -ENOMEM;
+ goto free_apwr_gpio;
+ }
+
+ platform_set_drvdata(e740_snd_device, &e740);
+ ret = platform_device_add(e740_snd_device);
+
+ if (!ret)
+ return 0;
+
+/* Fail gracefully */
+ platform_device_put(e740_snd_device);
+free_apwr_gpio:
+ gpio_free(GPIO_E740_WM9705_nAVDD2);
+free_op_amp_gpio:
+ gpio_free(GPIO_E740_AMP_ON);
+free_mic_amp_gpio:
+ gpio_free(GPIO_E740_MIC_ON);
+
+ return ret;
+}
+
+static void __exit e740_exit(void)
+{
+ platform_device_unregister(e740_snd_device);
+ gpio_free(GPIO_E740_WM9705_nAVDD2);
+ gpio_free(GPIO_E740_AMP_ON);
+ gpio_free(GPIO_E740_MIC_ON);
+}
+
+module_init(e740_init);
+module_exit(e740_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>");
+MODULE_DESCRIPTION("ALSA SoC driver for e740");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/pxa/e750_wm9705.c b/sound/soc/pxa/e750_wm9705.c
new file mode 100644
index 00000000..51897fcd
--- /dev/null
+++ b/sound/soc/pxa/e750_wm9705.c
@@ -0,0 +1,185 @@
+/*
+ * e750-wm9705.c -- SoC audio for e750
+ *
+ * Copyright 2007 (c) Ian Molton <spyro@f2s.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; version 2 ONLY.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <mach/audio.h>
+#include <mach/eseries-gpio.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/wm9705.h"
+#include "pxa2xx-ac97.h"
+
+static int e750_spk_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (event & SND_SOC_DAPM_PRE_PMU)
+ gpio_set_value(GPIO_E750_SPK_AMP_OFF, 0);
+ else if (event & SND_SOC_DAPM_POST_PMD)
+ gpio_set_value(GPIO_E750_SPK_AMP_OFF, 1);
+
+ return 0;
+}
+
+static int e750_hp_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (event & SND_SOC_DAPM_PRE_PMU)
+ gpio_set_value(GPIO_E750_HP_AMP_OFF, 0);
+ else if (event & SND_SOC_DAPM_POST_PMD)
+ gpio_set_value(GPIO_E750_HP_AMP_OFF, 1);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget e750_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("Mic (Internal)", NULL),
+ SND_SOC_DAPM_PGA_E("Headphone Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+ e750_hp_amp_event, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_PGA_E("Speaker Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+ e750_spk_amp_event, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Amp", NULL, "HPOUTL"},
+ {"Headphone Amp", NULL, "HPOUTR"},
+ {"Headphone Jack", NULL, "Headphone Amp"},
+
+ {"Speaker Amp", NULL, "MONOOUT"},
+ {"Speaker", NULL, "Speaker Amp"},
+
+ {"MIC1", NULL, "Mic (Internal)"},
+};
+
+static int e750_ac97_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_nc_pin(dapm, "LOUT");
+ snd_soc_dapm_nc_pin(dapm, "ROUT");
+ snd_soc_dapm_nc_pin(dapm, "PHONE");
+ snd_soc_dapm_nc_pin(dapm, "LINEINL");
+ snd_soc_dapm_nc_pin(dapm, "LINEINR");
+ snd_soc_dapm_nc_pin(dapm, "CDINL");
+ snd_soc_dapm_nc_pin(dapm, "CDINR");
+ snd_soc_dapm_nc_pin(dapm, "PCBEEP");
+ snd_soc_dapm_nc_pin(dapm, "MIC2");
+
+ snd_soc_dapm_new_controls(dapm, e750_dapm_widgets,
+ ARRAY_SIZE(e750_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link e750_dai[] = {
+ {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "pxa2xx-ac97",
+ .codec_dai_name = "wm9705-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm9705-codec",
+ .init = e750_ac97_init,
+ /* use ops to check startup state */
+ },
+ {
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .cpu_dai_name = "pxa2xx-ac97-aux",
+ .codec_dai_name ="wm9705-aux",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm9705-codec",
+ },
+};
+
+static struct snd_soc_card e750 = {
+ .name = "Toshiba e750",
+ .dai_link = e750_dai,
+ .num_links = ARRAY_SIZE(e750_dai),
+};
+
+static struct platform_device *e750_snd_device;
+
+static int __init e750_init(void)
+{
+ int ret;
+
+ if (!machine_is_e750())
+ return -ENODEV;
+
+ ret = gpio_request(GPIO_E750_HP_AMP_OFF, "Headphone amp");
+ if (ret)
+ return ret;
+
+ ret = gpio_request(GPIO_E750_SPK_AMP_OFF, "Speaker amp");
+ if (ret)
+ goto free_hp_amp_gpio;
+
+ ret = gpio_direction_output(GPIO_E750_HP_AMP_OFF, 1);
+ if (ret)
+ goto free_spk_amp_gpio;
+
+ ret = gpio_direction_output(GPIO_E750_SPK_AMP_OFF, 1);
+ if (ret)
+ goto free_spk_amp_gpio;
+
+ e750_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!e750_snd_device) {
+ ret = -ENOMEM;
+ goto free_spk_amp_gpio;
+ }
+
+ platform_set_drvdata(e750_snd_device, &e750);
+ ret = platform_device_add(e750_snd_device);
+
+ if (!ret)
+ return 0;
+
+/* Fail gracefully */
+ platform_device_put(e750_snd_device);
+free_spk_amp_gpio:
+ gpio_free(GPIO_E750_SPK_AMP_OFF);
+free_hp_amp_gpio:
+ gpio_free(GPIO_E750_HP_AMP_OFF);
+
+ return ret;
+}
+
+static void __exit e750_exit(void)
+{
+ platform_device_unregister(e750_snd_device);
+ gpio_free(GPIO_E750_SPK_AMP_OFF);
+ gpio_free(GPIO_E750_HP_AMP_OFF);
+}
+
+module_init(e750_init);
+module_exit(e750_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>");
+MODULE_DESCRIPTION("ALSA SoC driver for e750");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/pxa/e800_wm9712.c b/sound/soc/pxa/e800_wm9712.c
new file mode 100644
index 00000000..053ed208
--- /dev/null
+++ b/sound/soc/pxa/e800_wm9712.c
@@ -0,0 +1,172 @@
+/*
+ * e800-wm9712.c -- SoC audio for e800
+ *
+ * Copyright 2007 (c) Ian Molton <spyro@f2s.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; version 2 ONLY.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/audio.h>
+#include <mach/eseries-gpio.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-ac97.h"
+
+static int e800_spk_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (event & SND_SOC_DAPM_PRE_PMU)
+ gpio_set_value(GPIO_E800_SPK_AMP_ON, 1);
+ else if (event & SND_SOC_DAPM_POST_PMD)
+ gpio_set_value(GPIO_E800_SPK_AMP_ON, 0);
+
+ return 0;
+}
+
+static int e800_hp_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (event & SND_SOC_DAPM_PRE_PMU)
+ gpio_set_value(GPIO_E800_HP_AMP_OFF, 0);
+ else if (event & SND_SOC_DAPM_POST_PMD)
+ gpio_set_value(GPIO_E800_HP_AMP_OFF, 1);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget e800_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic (Internal1)", NULL),
+ SND_SOC_DAPM_MIC("Mic (Internal2)", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_PGA_E("Headphone Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+ e800_hp_amp_event, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_PGA_E("Speaker Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+ e800_spk_amp_event, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Jack", NULL, "HPOUTL"},
+ {"Headphone Jack", NULL, "HPOUTR"},
+ {"Headphone Jack", NULL, "Headphone Amp"},
+
+ {"Speaker Amp", NULL, "MONOOUT"},
+ {"Speaker", NULL, "Speaker Amp"},
+
+ {"MIC1", NULL, "Mic (Internal1)"},
+ {"MIC2", NULL, "Mic (Internal2)"},
+};
+
+static int e800_ac97_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, e800_dapm_widgets,
+ ARRAY_SIZE(e800_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link e800_dai[] = {
+ {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "pxa2xx-ac97",
+ .codec_dai_name = "wm9712-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm9712-codec",
+ .init = e800_ac97_init,
+ },
+ {
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .cpu_dai_name = "pxa2xx-ac97-aux",
+ .codec_dai_name ="wm9712-aux",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm9712-codec",
+ },
+};
+
+static struct snd_soc_card e800 = {
+ .name = "Toshiba e800",
+ .dai_link = e800_dai,
+ .num_links = ARRAY_SIZE(e800_dai),
+};
+
+static struct platform_device *e800_snd_device;
+
+static int __init e800_init(void)
+{
+ int ret;
+
+ if (!machine_is_e800())
+ return -ENODEV;
+
+ ret = gpio_request(GPIO_E800_HP_AMP_OFF, "Headphone amp");
+ if (ret)
+ return ret;
+
+ ret = gpio_request(GPIO_E800_SPK_AMP_ON, "Speaker amp");
+ if (ret)
+ goto free_hp_amp_gpio;
+
+ ret = gpio_direction_output(GPIO_E800_HP_AMP_OFF, 1);
+ if (ret)
+ goto free_spk_amp_gpio;
+
+ ret = gpio_direction_output(GPIO_E800_SPK_AMP_ON, 1);
+ if (ret)
+ goto free_spk_amp_gpio;
+
+ e800_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!e800_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(e800_snd_device, &e800);
+ ret = platform_device_add(e800_snd_device);
+
+ if (!ret)
+ return 0;
+
+/* Fail gracefully */
+ platform_device_put(e800_snd_device);
+free_spk_amp_gpio:
+ gpio_free(GPIO_E800_SPK_AMP_ON);
+free_hp_amp_gpio:
+ gpio_free(GPIO_E800_HP_AMP_OFF);
+
+ return ret;
+}
+
+static void __exit e800_exit(void)
+{
+ platform_device_unregister(e800_snd_device);
+ gpio_free(GPIO_E800_SPK_AMP_ON);
+ gpio_free(GPIO_E800_HP_AMP_OFF);
+}
+
+module_init(e800_init);
+module_exit(e800_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>");
+MODULE_DESCRIPTION("ALSA SoC driver for e800");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/pxa/em-x270.c b/sound/soc/pxa/em-x270.c
new file mode 100644
index 00000000..b13a4252
--- /dev/null
+++ b/sound/soc/pxa/em-x270.c
@@ -0,0 +1,95 @@
+/*
+ * SoC audio driver for EM-X270, eXeda and CM-X300
+ *
+ * Copyright 2007, 2009 CompuLab, Ltd.
+ *
+ * Author: Mike Rapoport <mike@compulab.co.il>
+ *
+ * Copied from tosa.c:
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-ac97.h"
+
+static struct snd_soc_dai_link em_x270_dai[] = {
+ {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "pxa2xx-ac97",
+ .codec_dai_name = "wm9712-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm9712-codec",
+ },
+ {
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .cpu_dai_name = "pxa2xx-ac97-aux",
+ .codec_dai_name ="wm9712-aux",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm9712-codec",
+ },
+};
+
+static struct snd_soc_card em_x270 = {
+ .name = "EM-X270",
+ .dai_link = em_x270_dai,
+ .num_links = ARRAY_SIZE(em_x270_dai),
+};
+
+static struct platform_device *em_x270_snd_device;
+
+static int __init em_x270_init(void)
+{
+ int ret;
+
+ if (!(machine_is_em_x270() || machine_is_exeda()
+ || machine_is_cm_x300()))
+ return -ENODEV;
+
+ em_x270_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!em_x270_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(em_x270_snd_device, &em_x270);
+ ret = platform_device_add(em_x270_snd_device);
+
+ if (ret)
+ platform_device_put(em_x270_snd_device);
+
+ return ret;
+}
+
+static void __exit em_x270_exit(void)
+{
+ platform_device_unregister(em_x270_snd_device);
+}
+
+module_init(em_x270_init);
+module_exit(em_x270_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mike Rapoport");
+MODULE_DESCRIPTION("ALSA SoC EM-X270, eXeda and CM-X300");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/hx4700.c b/sound/soc/pxa/hx4700.c
new file mode 100644
index 00000000..65c12483
--- /dev/null
+++ b/sound/soc/pxa/hx4700.c
@@ -0,0 +1,255 @@
+/*
+ * SoC audio for HP iPAQ hx4700
+ *
+ * Copyright (c) 2009 Philipp Zabel
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/hx4700.h>
+#include <asm/mach-types.h>
+#include "pxa2xx-i2s.h"
+
+#include "../codecs/ak4641.h"
+
+static struct snd_soc_jack hs_jack;
+
+/* Headphones jack detection DAPM pin */
+static struct snd_soc_jack_pin hs_jack_pin[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Speaker",
+ /* disable speaker when hp jack is inserted */
+ .mask = SND_JACK_HEADPHONE,
+ .invert = 1,
+ },
+};
+
+/* Headphones jack detection GPIO */
+static struct snd_soc_jack_gpio hs_jack_gpio = {
+ .gpio = GPIO75_HX4700_EARPHONE_nDET,
+ .invert = true,
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 200,
+};
+
+/*
+ * iPAQ hx4700 uses I2S for capture and playback.
+ */
+static int hx4700_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret = 0;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as output */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ /* inform codec driver about clock freq *
+ * (PXA I2S always uses divider 256) */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 256 * params_rate(params),
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops hx4700_ops = {
+ .hw_params = hx4700_hw_params,
+};
+
+static int hx4700_spk_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(GPIO107_HX4700_SPK_nSD, !!SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+static int hx4700_hp_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(GPIO92_HX4700_HP_DRIVER, !!SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+/* hx4700 machine dapm widgets */
+static const struct snd_soc_dapm_widget hx4700_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", hx4700_hp_power),
+ SND_SOC_DAPM_SPK("Speaker", hx4700_spk_power),
+ SND_SOC_DAPM_MIC("Built-in Microphone", NULL),
+};
+
+/* hx4700 machine audio_map */
+static const struct snd_soc_dapm_route hx4700_audio_map[] = {
+
+ /* Headphone connected to LOUT, ROUT */
+ {"Headphone Jack", NULL, "LOUT"},
+ {"Headphone Jack", NULL, "ROUT"},
+
+ /* Speaker connected to MOUT2 */
+ {"Speaker", NULL, "MOUT2"},
+
+ /* Microphone connected to MICIN */
+ {"MICIN", NULL, "Built-in Microphone"},
+ {"AIN", NULL, "MICOUT"},
+};
+
+/*
+ * Logic for a ak4641 as connected on a HP iPAQ hx4700
+ */
+static int hx4700_ak4641_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ /* NC codec pins */
+ /* FIXME: is anything connected here? */
+ snd_soc_dapm_nc_pin(dapm, "MOUT1");
+ snd_soc_dapm_nc_pin(dapm, "MICEXT");
+ snd_soc_dapm_nc_pin(dapm, "AUX");
+
+ /* Jack detection API stuff */
+ err = snd_soc_jack_new(codec, "Headphone Jack",
+ SND_JACK_HEADPHONE, &hs_jack);
+ if (err)
+ return err;
+
+ err = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pin),
+ hs_jack_pin);
+ if (err)
+ return err;
+
+ err = snd_soc_jack_add_gpios(&hs_jack, 1, &hs_jack_gpio);
+
+ return err;
+}
+
+/* hx4700 digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link hx4700_dai = {
+ .name = "ak4641",
+ .stream_name = "AK4641",
+ .cpu_dai_name = "pxa2xx-i2s",
+ .codec_dai_name = "ak4641-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "ak4641.0-0012",
+ .init = hx4700_ak4641_init,
+ .ops = &hx4700_ops,
+};
+
+/* hx4700 audio machine driver */
+static struct snd_soc_card snd_soc_card_hx4700 = {
+ .name = "iPAQ hx4700",
+ .dai_link = &hx4700_dai,
+ .num_links = 1,
+ .dapm_widgets = hx4700_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(hx4700_dapm_widgets),
+ .dapm_routes = hx4700_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(hx4700_audio_map),
+};
+
+static struct gpio hx4700_audio_gpios[] = {
+ { GPIO107_HX4700_SPK_nSD, GPIOF_OUT_INIT_HIGH, "SPK_POWER" },
+ { GPIO92_HX4700_HP_DRIVER, GPIOF_OUT_INIT_LOW, "EP_POWER" },
+};
+
+static int __devinit hx4700_audio_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ if (!machine_is_h4700())
+ return -ENODEV;
+
+ ret = gpio_request_array(hx4700_audio_gpios,
+ ARRAY_SIZE(hx4700_audio_gpios));
+ if (ret)
+ return ret;
+
+ snd_soc_card_hx4700.dev = &pdev->dev;
+ ret = snd_soc_register_card(&snd_soc_card_hx4700);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int __devexit hx4700_audio_remove(struct platform_device *pdev)
+{
+ snd_soc_jack_free_gpios(&hs_jack, 1, &hs_jack_gpio);
+ snd_soc_unregister_card(&snd_soc_card_hx4700);
+
+ gpio_set_value(GPIO92_HX4700_HP_DRIVER, 0);
+ gpio_set_value(GPIO107_HX4700_SPK_nSD, 0);
+
+ gpio_free_array(hx4700_audio_gpios, ARRAY_SIZE(hx4700_audio_gpios));
+ return 0;
+}
+
+static struct platform_driver hx4700_audio_driver = {
+ .driver = {
+ .name = "hx4700-audio",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = hx4700_audio_probe,
+ .remove = __devexit_p(hx4700_audio_remove),
+};
+
+static int __init hx4700_modinit(void)
+{
+ return platform_driver_register(&hx4700_audio_driver);
+}
+module_init(hx4700_modinit);
+
+static void __exit hx4700_modexit(void)
+{
+ platform_driver_unregister(&hx4700_audio_driver);
+}
+
+module_exit(hx4700_modexit);
+
+MODULE_AUTHOR("Philipp Zabel");
+MODULE_DESCRIPTION("ALSA SoC iPAQ hx4700");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:hx4700-audio");
diff --git a/sound/soc/pxa/imote2.c b/sound/soc/pxa/imote2.c
new file mode 100644
index 00000000..154fc6f2
--- /dev/null
+++ b/sound/soc/pxa/imote2.c
@@ -0,0 +1,108 @@
+
+#include <linux/module.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/wm8940.h"
+#include "pxa2xx-i2s.h"
+
+static int imote2_asoc_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int clk = 0;
+ int ret;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* CPU should be clock master */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as input (unused) */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, clk,
+ SND_SOC_CLOCK_OUT);
+
+ return ret;
+}
+
+static struct snd_soc_ops imote2_asoc_ops = {
+ .hw_params = imote2_asoc_hw_params,
+};
+
+static struct snd_soc_dai_link imote2_dai = {
+ .name = "WM8940",
+ .stream_name = "WM8940",
+ .cpu_dai_name = "pxa2xx-i2s",
+ .codec_dai_name = "wm8940-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm8940-codec.0-0034",
+ .ops = &imote2_asoc_ops,
+};
+
+static struct snd_soc_card snd_soc_imote2 = {
+ .name = "Imote2",
+ .dai_link = &imote2_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *imote2_snd_device;
+
+static int __init imote2_asoc_init(void)
+{
+ int ret;
+
+ if (!machine_is_intelmote2())
+ return -ENODEV;
+ imote2_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!imote2_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(imote2_snd_device, &snd_soc_imote2);
+ ret = platform_device_add(imote2_snd_device);
+ if (ret)
+ platform_device_put(imote2_snd_device);
+
+ return ret;
+}
+module_init(imote2_asoc_init);
+
+static void __exit imote2_asoc_exit(void)
+{
+ platform_device_unregister(imote2_snd_device);
+}
+module_exit(imote2_asoc_exit);
+
+MODULE_AUTHOR("Jonathan Cameron");
+MODULE_DESCRIPTION("ALSA SoC Imote 2");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c
new file mode 100644
index 00000000..67dcc36c
--- /dev/null
+++ b/sound/soc/pxa/magician.c
@@ -0,0 +1,564 @@
+/*
+ * SoC audio for HTC Magician
+ *
+ * Copyright (c) 2006 Philipp Zabel <philipp.zabel@gmail.com>
+ *
+ * based on spitz.c,
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/uda1380.h>
+
+#include <mach/magician.h>
+#include <asm/mach-types.h>
+#include "../codecs/uda1380.h"
+#include "pxa2xx-i2s.h"
+#include "pxa-ssp.h"
+
+#define MAGICIAN_MIC 0
+#define MAGICIAN_MIC_EXT 1
+
+static int magician_hp_switch;
+static int magician_spk_switch = 1;
+static int magician_in_sel = MAGICIAN_MIC;
+
+static void magician_ext_control(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ if (magician_spk_switch)
+ snd_soc_dapm_enable_pin(dapm, "Speaker");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Speaker");
+ if (magician_hp_switch)
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+
+ switch (magician_in_sel) {
+ case MAGICIAN_MIC:
+ snd_soc_dapm_disable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_enable_pin(dapm, "Call Mic");
+ break;
+ case MAGICIAN_MIC_EXT:
+ snd_soc_dapm_disable_pin(dapm, "Call Mic");
+ snd_soc_dapm_enable_pin(dapm, "Headset Mic");
+ break;
+ }
+
+ snd_soc_dapm_sync(dapm);
+}
+
+static int magician_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ mutex_lock(&codec->mutex);
+
+ /* check the jack status at stream startup */
+ magician_ext_control(codec);
+
+ mutex_unlock(&codec->mutex);
+
+ return 0;
+}
+
+/*
+ * Magician uses SSP port for playback.
+ */
+static int magician_playback_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int acps, acds, width, rate;
+ unsigned int div4 = PXA_SSP_CLK_SCDB_4;
+ int ret = 0;
+
+ rate = params_rate(params);
+ width = snd_pcm_format_physical_width(params_format(params));
+
+ /*
+ * rate = SSPSCLK / (2 * width(16 or 32))
+ * SSPSCLK = (ACPS / ACDS) / SSPSCLKDIV(div4 or div1)
+ */
+ switch (params_rate(params)) {
+ case 8000:
+ /* off by a factor of 2: bug in the PXA27x audio clock? */
+ acps = 32842000;
+ switch (width) {
+ case 16:
+ /* 513156 Hz ~= _2_ * 8000 Hz * 32 (+0.23%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_16;
+ break;
+ default: /* 32 */
+ /* 1026312 Hz ~= _2_ * 8000 Hz * 64 (+0.23%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_8;
+ }
+ break;
+ case 11025:
+ acps = 5622000;
+ switch (width) {
+ case 16:
+ /* 351375 Hz ~= 11025 Hz * 32 (-0.41%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_4;
+ break;
+ default: /* 32 */
+ /* 702750 Hz ~= 11025 Hz * 64 (-0.41%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_2;
+ }
+ break;
+ case 22050:
+ acps = 5622000;
+ switch (width) {
+ case 16:
+ /* 702750 Hz ~= 22050 Hz * 32 (-0.41%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_2;
+ break;
+ default: /* 32 */
+ /* 1405500 Hz ~= 22050 Hz * 64 (-0.41%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_1;
+ }
+ break;
+ case 44100:
+ acps = 5622000;
+ switch (width) {
+ case 16:
+ /* 1405500 Hz ~= 44100 Hz * 32 (-0.41%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_2;
+ break;
+ default: /* 32 */
+ /* 2811000 Hz ~= 44100 Hz * 64 (-0.41%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_1;
+ }
+ break;
+ case 48000:
+ acps = 12235000;
+ switch (width) {
+ case 16:
+ /* 1529375 Hz ~= 48000 Hz * 32 (-0.44%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_2;
+ break;
+ default: /* 32 */
+ /* 3058750 Hz ~= 48000 Hz * 64 (-0.44%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_1;
+ }
+ break;
+ case 96000:
+ default:
+ acps = 12235000;
+ switch (width) {
+ case 16:
+ /* 3058750 Hz ~= 96000 Hz * 32 (-0.44%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_1;
+ break;
+ default: /* 32 */
+ /* 6117500 Hz ~= 96000 Hz * 64 (-0.44%) */
+ acds = PXA_SSP_CLK_AUDIO_DIV_2;
+ div4 = PXA_SSP_CLK_SCDB_1;
+ break;
+ }
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_MSB |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 1, 0, 1, width);
+ if (ret < 0)
+ return ret;
+
+ /* set audio clock as clock source */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_AUDIO, 0,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ /* set the SSP audio system clock ACDS divider */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai,
+ PXA_SSP_AUDIO_DIV_ACDS, acds);
+ if (ret < 0)
+ return ret;
+
+ /* set the SSP audio system clock SCDB divider4 */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai,
+ PXA_SSP_AUDIO_DIV_SCDB, div4);
+ if (ret < 0)
+ return ret;
+
+ /* set SSP audio pll clock */
+ ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, acps);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * Magician uses I2S for capture.
+ */
+static int magician_capture_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret = 0;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as output */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops magician_capture_ops = {
+ .startup = magician_startup,
+ .hw_params = magician_capture_hw_params,
+};
+
+static struct snd_soc_ops magician_playback_ops = {
+ .startup = magician_startup,
+ .hw_params = magician_playback_hw_params,
+};
+
+static int magician_get_hp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = magician_hp_switch;
+ return 0;
+}
+
+static int magician_set_hp(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (magician_hp_switch == ucontrol->value.integer.value[0])
+ return 0;
+
+ magician_hp_switch = ucontrol->value.integer.value[0];
+ magician_ext_control(codec);
+ return 1;
+}
+
+static int magician_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = magician_spk_switch;
+ return 0;
+}
+
+static int magician_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (magician_spk_switch == ucontrol->value.integer.value[0])
+ return 0;
+
+ magician_spk_switch = ucontrol->value.integer.value[0];
+ magician_ext_control(codec);
+ return 1;
+}
+
+static int magician_get_input(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = magician_in_sel;
+ return 0;
+}
+
+static int magician_set_input(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ if (magician_in_sel == ucontrol->value.integer.value[0])
+ return 0;
+
+ magician_in_sel = ucontrol->value.integer.value[0];
+
+ switch (magician_in_sel) {
+ case MAGICIAN_MIC:
+ gpio_set_value(EGPIO_MAGICIAN_IN_SEL1, 1);
+ break;
+ case MAGICIAN_MIC_EXT:
+ gpio_set_value(EGPIO_MAGICIAN_IN_SEL1, 0);
+ }
+
+ return 1;
+}
+
+static int magician_spk_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(EGPIO_MAGICIAN_SPK_POWER, SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+static int magician_hp_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(EGPIO_MAGICIAN_EP_POWER, SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+static int magician_mic_bias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(EGPIO_MAGICIAN_MIC_POWER, SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+/* magician machine dapm widgets */
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", magician_hp_power),
+ SND_SOC_DAPM_SPK("Speaker", magician_spk_power),
+ SND_SOC_DAPM_MIC("Call Mic", magician_mic_bias),
+ SND_SOC_DAPM_MIC("Headset Mic", magician_mic_bias),
+};
+
+/* magician machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* Headphone connected to VOUTL, VOUTR */
+ {"Headphone Jack", NULL, "VOUTL"},
+ {"Headphone Jack", NULL, "VOUTR"},
+
+ /* Speaker connected to VOUTL, VOUTR */
+ {"Speaker", NULL, "VOUTL"},
+ {"Speaker", NULL, "VOUTR"},
+
+ /* Mics are connected to VINM */
+ {"VINM", NULL, "Headset Mic"},
+ {"VINM", NULL, "Call Mic"},
+};
+
+static const char *input_select[] = {"Call Mic", "Headset Mic"};
+static const struct soc_enum magician_in_sel_enum =
+ SOC_ENUM_SINGLE_EXT(2, input_select);
+
+static const struct snd_kcontrol_new uda1380_magician_controls[] = {
+ SOC_SINGLE_BOOL_EXT("Headphone Switch",
+ (unsigned long)&magician_hp_switch,
+ magician_get_hp, magician_set_hp),
+ SOC_SINGLE_BOOL_EXT("Speaker Switch",
+ (unsigned long)&magician_spk_switch,
+ magician_get_spk, magician_set_spk),
+ SOC_ENUM_EXT("Input Select", magician_in_sel_enum,
+ magician_get_input, magician_set_input),
+};
+
+/*
+ * Logic for a uda1380 as connected on a HTC Magician
+ */
+static int magician_uda1380_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ /* NC codec pins */
+ snd_soc_dapm_nc_pin(dapm, "VOUTLHP");
+ snd_soc_dapm_nc_pin(dapm, "VOUTRHP");
+
+ /* FIXME: is anything connected here? */
+ snd_soc_dapm_nc_pin(dapm, "VINL");
+ snd_soc_dapm_nc_pin(dapm, "VINR");
+
+ /* Add magician specific controls */
+ err = snd_soc_add_controls(codec, uda1380_magician_controls,
+ ARRAY_SIZE(uda1380_magician_controls));
+ if (err < 0)
+ return err;
+
+ /* Add magician specific widgets */
+ snd_soc_dapm_new_controls(dapm, uda1380_dapm_widgets,
+ ARRAY_SIZE(uda1380_dapm_widgets));
+
+ /* Set up magician specific audio path interconnects */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(dapm);
+ return 0;
+}
+
+/* magician digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link magician_dai[] = {
+{
+ .name = "uda1380",
+ .stream_name = "UDA1380 Playback",
+ .cpu_dai_name = "pxa-ssp-dai.0",
+ .codec_dai_name = "uda1380-hifi-playback",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "uda1380-codec.0-0018",
+ .init = magician_uda1380_init,
+ .ops = &magician_playback_ops,
+},
+{
+ .name = "uda1380",
+ .stream_name = "UDA1380 Capture",
+ .cpu_dai_name = "pxa2xx-i2s",
+ .codec_dai_name = "uda1380-hifi-capture",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "uda1380-codec.0-0018",
+ .ops = &magician_capture_ops,
+}
+};
+
+/* magician audio machine driver */
+static struct snd_soc_card snd_soc_card_magician = {
+ .name = "Magician",
+ .dai_link = magician_dai,
+ .num_links = ARRAY_SIZE(magician_dai),
+
+};
+
+static struct platform_device *magician_snd_device;
+
+/*
+ * FIXME: move into magician board file once merged into the pxa tree
+ */
+static struct uda1380_platform_data uda1380_info = {
+ .gpio_power = EGPIO_MAGICIAN_CODEC_POWER,
+ .gpio_reset = EGPIO_MAGICIAN_CODEC_RESET,
+ .dac_clk = UDA1380_DAC_CLK_WSPLL,
+};
+
+static struct i2c_board_info i2c_board_info[] = {
+ {
+ I2C_BOARD_INFO("uda1380", 0x18),
+ .platform_data = &uda1380_info,
+ },
+};
+
+static int __init magician_init(void)
+{
+ int ret;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+
+ if (!machine_is_magician())
+ return -ENODEV;
+
+ adapter = i2c_get_adapter(0);
+ if (!adapter)
+ return -ENODEV;
+ client = i2c_new_device(adapter, i2c_board_info);
+ i2c_put_adapter(adapter);
+ if (!client)
+ return -ENODEV;
+
+ ret = gpio_request(EGPIO_MAGICIAN_SPK_POWER, "SPK_POWER");
+ if (ret)
+ goto err_request_spk;
+ ret = gpio_request(EGPIO_MAGICIAN_EP_POWER, "EP_POWER");
+ if (ret)
+ goto err_request_ep;
+ ret = gpio_request(EGPIO_MAGICIAN_MIC_POWER, "MIC_POWER");
+ if (ret)
+ goto err_request_mic;
+ ret = gpio_request(EGPIO_MAGICIAN_IN_SEL0, "IN_SEL0");
+ if (ret)
+ goto err_request_in_sel0;
+ ret = gpio_request(EGPIO_MAGICIAN_IN_SEL1, "IN_SEL1");
+ if (ret)
+ goto err_request_in_sel1;
+
+ gpio_set_value(EGPIO_MAGICIAN_IN_SEL0, 0);
+
+ magician_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!magician_snd_device) {
+ ret = -ENOMEM;
+ goto err_pdev;
+ }
+
+ platform_set_drvdata(magician_snd_device, &snd_soc_card_magician);
+ ret = platform_device_add(magician_snd_device);
+ if (ret) {
+ platform_device_put(magician_snd_device);
+ goto err_pdev;
+ }
+
+ return 0;
+
+err_pdev:
+ gpio_free(EGPIO_MAGICIAN_IN_SEL1);
+err_request_in_sel1:
+ gpio_free(EGPIO_MAGICIAN_IN_SEL0);
+err_request_in_sel0:
+ gpio_free(EGPIO_MAGICIAN_MIC_POWER);
+err_request_mic:
+ gpio_free(EGPIO_MAGICIAN_EP_POWER);
+err_request_ep:
+ gpio_free(EGPIO_MAGICIAN_SPK_POWER);
+err_request_spk:
+ return ret;
+}
+
+static void __exit magician_exit(void)
+{
+ platform_device_unregister(magician_snd_device);
+
+ gpio_set_value(EGPIO_MAGICIAN_SPK_POWER, 0);
+ gpio_set_value(EGPIO_MAGICIAN_EP_POWER, 0);
+ gpio_set_value(EGPIO_MAGICIAN_MIC_POWER, 0);
+
+ gpio_free(EGPIO_MAGICIAN_IN_SEL1);
+ gpio_free(EGPIO_MAGICIAN_IN_SEL0);
+ gpio_free(EGPIO_MAGICIAN_MIC_POWER);
+ gpio_free(EGPIO_MAGICIAN_EP_POWER);
+ gpio_free(EGPIO_MAGICIAN_SPK_POWER);
+}
+
+module_init(magician_init);
+module_exit(magician_exit);
+
+MODULE_AUTHOR("Philipp Zabel");
+MODULE_DESCRIPTION("ALSA SoC Magician");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/mioa701_wm9713.c b/sound/soc/pxa/mioa701_wm9713.c
new file mode 100644
index 00000000..38ca6759
--- /dev/null
+++ b/sound/soc/pxa/mioa701_wm9713.c
@@ -0,0 +1,247 @@
+/*
+ * Handles the Mitac mioa701 SoC system
+ *
+ * Copyright (C) 2008 Robert Jarzmik
+ *
+ * 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 in version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This is a little schema of the sound interconnections :
+ *
+ * Sagem X200 Wolfson WM9713
+ * +--------+ +-------------------+ Rear Speaker
+ * | | | | /-+
+ * | +--->----->---+MONOIN SPKL+--->----+-+ |
+ * | GSM | | | | | |
+ * | +--->----->---+PCBEEP SPKR+--->----+-+ |
+ * | CHIP | | | \-+
+ * | +---<-----<---+MONO |
+ * | | | | Front Speaker
+ * +--------+ | | /-+
+ * | HPL+--->----+-+ |
+ * | | | | |
+ * | OUT3+--->----+-+ |
+ * | | \-+
+ * | |
+ * | | Front Micro
+ * | | +
+ * | MIC1+-----<--+o+
+ * | | +
+ * +-------------------+ ---
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+
+#include <asm/mach-types.h>
+#include <mach/audio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/ac97_codec.h>
+
+#include "pxa2xx-ac97.h"
+#include "../codecs/wm9713.h"
+
+#define ARRAY_AND_SIZE(x) (x), ARRAY_SIZE(x)
+
+#define AC97_GPIO_PULL 0x58
+
+/* Use GPIO8 for rear speaker amplifier */
+static int rear_amp_power(struct snd_soc_codec *codec, int power)
+{
+ unsigned short reg;
+
+ if (power) {
+ reg = snd_soc_read(codec, AC97_GPIO_CFG);
+ snd_soc_write(codec, AC97_GPIO_CFG, reg | 0x0100);
+ reg = snd_soc_read(codec, AC97_GPIO_PULL);
+ snd_soc_write(codec, AC97_GPIO_PULL, reg | (1<<15));
+ } else {
+ reg = snd_soc_read(codec, AC97_GPIO_CFG);
+ snd_soc_write(codec, AC97_GPIO_CFG, reg & ~0x0100);
+ reg = snd_soc_read(codec, AC97_GPIO_PULL);
+ snd_soc_write(codec, AC97_GPIO_PULL, reg & ~(1<<15));
+ }
+
+ return 0;
+}
+
+static int rear_amp_event(struct snd_soc_dapm_widget *widget,
+ struct snd_kcontrol *kctl, int event)
+{
+ struct snd_soc_codec *codec = widget->codec;
+
+ return rear_amp_power(codec, SND_SOC_DAPM_EVENT_ON(event));
+}
+
+/* mioa701 machine dapm widgets */
+static const struct snd_soc_dapm_widget mioa701_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Front Speaker", NULL),
+ SND_SOC_DAPM_SPK("Rear Speaker", rear_amp_event),
+ SND_SOC_DAPM_MIC("Headset", NULL),
+ SND_SOC_DAPM_LINE("GSM Line Out", NULL),
+ SND_SOC_DAPM_LINE("GSM Line In", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Front Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Call Mic */
+ {"Mic Bias", NULL, "Front Mic"},
+ {"MIC1", NULL, "Mic Bias"},
+
+ /* Headset Mic */
+ {"LINEL", NULL, "Headset Mic"},
+ {"LINER", NULL, "Headset Mic"},
+
+ /* GSM Module */
+ {"MONOIN", NULL, "GSM Line Out"},
+ {"PCBEEP", NULL, "GSM Line Out"},
+ {"GSM Line In", NULL, "MONO"},
+
+ /* headphone connected to HPL, HPR */
+ {"Headset", NULL, "HPL"},
+ {"Headset", NULL, "HPR"},
+
+ /* front speaker connected to HPL, OUT3 */
+ {"Front Speaker", NULL, "HPL"},
+ {"Front Speaker", NULL, "OUT3"},
+
+ /* rear speaker connected to SPKL, SPKR */
+ {"Rear Speaker", NULL, "SPKL"},
+ {"Rear Speaker", NULL, "SPKR"},
+};
+
+static int mioa701_wm9713_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ unsigned short reg;
+
+ /* Add mioa701 specific widgets */
+ snd_soc_dapm_new_controls(dapm, ARRAY_AND_SIZE(mioa701_dapm_widgets));
+
+ /* Set up mioa701 specific audio path audio_mapnects */
+ snd_soc_dapm_add_routes(dapm, ARRAY_AND_SIZE(audio_map));
+
+ /* Prepare GPIO8 for rear speaker amplifier */
+ reg = codec->driver->read(codec, AC97_GPIO_CFG);
+ codec->driver->write(codec, AC97_GPIO_CFG, reg | 0x0100);
+
+ /* Prepare MIC input */
+ reg = codec->driver->read(codec, AC97_3D_CONTROL);
+ codec->driver->write(codec, AC97_3D_CONTROL, reg | 0xc000);
+
+ snd_soc_dapm_enable_pin(dapm, "Front Speaker");
+ snd_soc_dapm_enable_pin(dapm, "Rear Speaker");
+ snd_soc_dapm_enable_pin(dapm, "Front Mic");
+ snd_soc_dapm_enable_pin(dapm, "GSM Line In");
+ snd_soc_dapm_enable_pin(dapm, "GSM Line Out");
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_ops mioa701_ops;
+
+static struct snd_soc_dai_link mioa701_dai[] = {
+ {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "pxa2xx-ac97",
+ .codec_dai_name = "wm9713-hifi",
+ .codec_name = "wm9713-codec",
+ .init = mioa701_wm9713_init,
+ .platform_name = "pxa-pcm-audio",
+ .ops = &mioa701_ops,
+ },
+ {
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .cpu_dai_name = "pxa2xx-ac97-aux",
+ .codec_dai_name ="wm9713-aux",
+ .codec_name = "wm9713-codec",
+ .platform_name = "pxa-pcm-audio",
+ .ops = &mioa701_ops,
+ },
+};
+
+static struct snd_soc_card mioa701 = {
+ .name = "MioA701",
+ .dai_link = mioa701_dai,
+ .num_links = ARRAY_SIZE(mioa701_dai),
+};
+
+static struct platform_device *mioa701_snd_device;
+
+static int mioa701_wm9713_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ if (!machine_is_mioa701())
+ return -ENODEV;
+
+ dev_warn(&pdev->dev, "Be warned that incorrect mixers/muxes setup will"
+ "lead to overheating and possible destruction of your device."
+ "Do not use without a good knowledge of mio's board design!\n");
+
+ mioa701_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!mioa701_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(mioa701_snd_device, &mioa701);
+
+ ret = platform_device_add(mioa701_snd_device);
+ if (!ret)
+ return 0;
+
+ platform_device_put(mioa701_snd_device);
+ return ret;
+}
+
+static int __devexit mioa701_wm9713_remove(struct platform_device *pdev)
+{
+ platform_device_unregister(mioa701_snd_device);
+ return 0;
+}
+
+static struct platform_driver mioa701_wm9713_driver = {
+ .probe = mioa701_wm9713_probe,
+ .remove = __devexit_p(mioa701_wm9713_remove),
+ .driver = {
+ .name = "mioa701-wm9713",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mioa701_asoc_init(void)
+{
+ return platform_driver_register(&mioa701_wm9713_driver);
+}
+
+static void __exit mioa701_asoc_exit(void)
+{
+ platform_driver_unregister(&mioa701_wm9713_driver);
+}
+
+module_init(mioa701_asoc_init);
+module_exit(mioa701_asoc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Robert Jarzmik (rjarzmik@free.fr)");
+MODULE_DESCRIPTION("ALSA SoC WM9713 MIO A701");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/palm27x.c b/sound/soc/pxa/palm27x.c
new file mode 100644
index 00000000..504e4004
--- /dev/null
+++ b/sound/soc/pxa/palm27x.c
@@ -0,0 +1,224 @@
+/*
+ * linux/sound/soc/pxa/palm27x.c
+ *
+ * SoC Audio driver for Palm T|X, T5 and LifeDrive
+ *
+ * based on tosa.c
+ *
+ * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+#include <mach/audio.h>
+#include <mach/palmasoc.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-ac97.h"
+
+static struct snd_soc_jack hs_jack;
+
+/* Headphones jack detection DAPM pins */
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+/* Headphones jack detection gpios */
+static struct snd_soc_jack_gpio hs_jack_gpios[] = {
+ [0] = {
+ /* gpio is set on per-platform basis */
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 200,
+ },
+};
+
+/* Palm27x machine dapm widgets */
+static const struct snd_soc_dapm_widget palm27x_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext. Speaker", NULL),
+ SND_SOC_DAPM_MIC("Ext. Microphone", NULL),
+};
+
+/* PalmTX audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* headphone connected to HPOUTL, HPOUTR */
+ {"Headphone Jack", NULL, "HPOUTL"},
+ {"Headphone Jack", NULL, "HPOUTR"},
+
+ /* ext speaker connected to ROUT2, LOUT2 */
+ {"Ext. Speaker", NULL, "LOUT2"},
+ {"Ext. Speaker", NULL, "ROUT2"},
+
+ /* mic connected to MIC1 */
+ {"Ext. Microphone", NULL, "MIC1"},
+};
+
+static struct snd_soc_card palm27x_asoc;
+
+static int palm27x_ac97_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ /* add palm27x specific widgets */
+ err = snd_soc_dapm_new_controls(dapm, palm27x_dapm_widgets,
+ ARRAY_SIZE(palm27x_dapm_widgets));
+ if (err)
+ return err;
+
+ /* set up palm27x specific audio path audio_map */
+ err = snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+ if (err)
+ return err;
+
+ /* connected pins */
+ if (machine_is_palmld())
+ snd_soc_dapm_enable_pin(dapm, "MIC1");
+ snd_soc_dapm_enable_pin(dapm, "HPOUTL");
+ snd_soc_dapm_enable_pin(dapm, "HPOUTR");
+ snd_soc_dapm_enable_pin(dapm, "LOUT2");
+ snd_soc_dapm_enable_pin(dapm, "ROUT2");
+
+ /* not connected pins */
+ snd_soc_dapm_nc_pin(dapm, "OUT3");
+ snd_soc_dapm_nc_pin(dapm, "MONOOUT");
+ snd_soc_dapm_nc_pin(dapm, "LINEINL");
+ snd_soc_dapm_nc_pin(dapm, "LINEINR");
+ snd_soc_dapm_nc_pin(dapm, "PCBEEP");
+ snd_soc_dapm_nc_pin(dapm, "PHONE");
+ snd_soc_dapm_nc_pin(dapm, "MIC2");
+
+ err = snd_soc_dapm_sync(dapm);
+ if (err)
+ return err;
+
+ /* Jack detection API stuff */
+ err = snd_soc_jack_new(codec, "Headphone Jack",
+ SND_JACK_HEADPHONE, &hs_jack);
+ if (err)
+ return err;
+
+ err = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+ hs_jack_pins);
+ if (err)
+ return err;
+
+ err = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios),
+ hs_jack_gpios);
+
+ return err;
+}
+
+static struct snd_soc_dai_link palm27x_dai[] = {
+{
+ .name = "AC97 HiFi",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "pxa2xx-ac97",
+ .codec_dai_name = "wm9712-hifi",
+ .codec_name = "wm9712-codec",
+ .platform_name = "pxa-pcm-audio",
+ .init = palm27x_ac97_init,
+},
+{
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .cpu_dai_name = "pxa2xx-ac97-aux",
+ .codec_dai_name = "wm9712-aux",
+ .codec_name = "wm9712-codec",
+ .platform_name = "pxa-pcm-audio",
+},
+};
+
+static struct snd_soc_card palm27x_asoc = {
+ .name = "Palm/PXA27x",
+ .dai_link = palm27x_dai,
+ .num_links = ARRAY_SIZE(palm27x_dai),
+};
+
+static struct platform_device *palm27x_snd_device;
+
+static int palm27x_asoc_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ if (!(machine_is_palmtx() || machine_is_palmt5() ||
+ machine_is_palmld() || machine_is_palmte2()))
+ return -ENODEV;
+
+ if (!pdev->dev.platform_data) {
+ dev_err(&pdev->dev, "please supply platform_data\n");
+ return -ENODEV;
+ }
+
+ hs_jack_gpios[0].gpio = ((struct palm27x_asoc_info *)
+ (pdev->dev.platform_data))->jack_gpio;
+
+ palm27x_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!palm27x_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(palm27x_snd_device, &palm27x_asoc);
+ ret = platform_device_add(palm27x_snd_device);
+
+ if (ret != 0)
+ goto put_device;
+
+ return 0;
+
+put_device:
+ platform_device_put(palm27x_snd_device);
+
+ return ret;
+}
+
+static int __devexit palm27x_asoc_remove(struct platform_device *pdev)
+{
+ platform_device_unregister(palm27x_snd_device);
+ return 0;
+}
+
+static struct platform_driver palm27x_wm9712_driver = {
+ .probe = palm27x_asoc_probe,
+ .remove = __devexit_p(palm27x_asoc_remove),
+ .driver = {
+ .name = "palm27x-asoc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init palm27x_asoc_init(void)
+{
+ return platform_driver_register(&palm27x_wm9712_driver);
+}
+
+static void __exit palm27x_asoc_exit(void)
+{
+ platform_driver_unregister(&palm27x_wm9712_driver);
+}
+
+module_init(palm27x_asoc_init);
+module_exit(palm27x_asoc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC Palm T|X, T5 and LifeDrive");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c
new file mode 100644
index 00000000..da3ae431
--- /dev/null
+++ b/sound/soc/pxa/poodle.c
@@ -0,0 +1,333 @@
+/*
+ * poodle.c -- SoC audio for Poodle
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <asm/hardware/locomo.h>
+#include <mach/poodle.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm8731.h"
+#include "pxa2xx-i2s.h"
+
+#define POODLE_HP 1
+#define POODLE_HP_OFF 0
+#define POODLE_SPK_ON 1
+#define POODLE_SPK_OFF 0
+
+ /* audio clock in Hz - rounded from 12.235MHz */
+#define POODLE_AUDIO_CLOCK 12288000
+
+static int poodle_jack_func;
+static int poodle_spk_func;
+
+static void poodle_ext_control(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* set up jack connection */
+ if (poodle_jack_func == POODLE_HP) {
+ /* set = unmute headphone */
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_L, 1);
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_R, 1);
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ } else {
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_L, 0);
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_R, 0);
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ }
+
+ /* set the enpoints to their new connetion states */
+ if (poodle_spk_func == POODLE_SPK_ON)
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Ext Spk");
+
+ /* signal a DAPM event */
+ snd_soc_dapm_sync(dapm);
+}
+
+static int poodle_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ mutex_lock(&codec->mutex);
+
+ /* check the jack status at stream startup */
+ poodle_ext_control(codec);
+
+ mutex_unlock(&codec->mutex);
+
+ return 0;
+}
+
+/* we need to unmute the HP at shutdown as the mute burns power on poodle */
+static void poodle_shutdown(struct snd_pcm_substream *substream)
+{
+ /* set = unmute headphone */
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_L, 1);
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_R, 1);
+}
+
+static int poodle_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as input (unused) */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops poodle_ops = {
+ .startup = poodle_startup,
+ .hw_params = poodle_hw_params,
+ .shutdown = poodle_shutdown,
+};
+
+static int poodle_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = poodle_jack_func;
+ return 0;
+}
+
+static int poodle_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (poodle_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ poodle_jack_func = ucontrol->value.integer.value[0];
+ poodle_ext_control(codec);
+ return 1;
+}
+
+static int poodle_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = poodle_spk_func;
+ return 0;
+}
+
+static int poodle_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (poodle_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ poodle_spk_func = ucontrol->value.integer.value[0];
+ poodle_ext_control(codec);
+ return 1;
+}
+
+static int poodle_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_AMP_ON, 0);
+ else
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_AMP_ON, 1);
+
+ return 0;
+}
+
+/* poodle machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+SND_SOC_DAPM_HP("Headphone Jack", NULL),
+SND_SOC_DAPM_SPK("Ext Spk", poodle_amp_event),
+};
+
+/* Corgi machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headphone connected to LHPOUT1, RHPOUT1 */
+ {"Headphone Jack", NULL, "LHPOUT"},
+ {"Headphone Jack", NULL, "RHPOUT"},
+
+ /* speaker connected to LOUT, ROUT */
+ {"Ext Spk", NULL, "ROUT"},
+ {"Ext Spk", NULL, "LOUT"},
+};
+
+static const char *jack_function[] = {"Off", "Headphone"};
+static const char *spk_function[] = {"Off", "On"};
+static const struct soc_enum poodle_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8731_poodle_controls[] = {
+ SOC_ENUM_EXT("Jack Function", poodle_enum[0], poodle_get_jack,
+ poodle_set_jack),
+ SOC_ENUM_EXT("Speaker Function", poodle_enum[1], poodle_get_spk,
+ poodle_set_spk),
+};
+
+/*
+ * Logic for a wm8731 as connected on a Sharp SL-C7x0 Device
+ */
+static int poodle_wm8731_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ snd_soc_dapm_nc_pin(dapm, "LLINEIN");
+ snd_soc_dapm_nc_pin(dapm, "RLINEIN");
+ snd_soc_dapm_enable_pin(dapm, "MICIN");
+
+ /* Add poodle specific controls */
+ err = snd_soc_add_controls(codec, wm8731_poodle_controls,
+ ARRAY_SIZE(wm8731_poodle_controls));
+ if (err < 0)
+ return err;
+
+ /* Add poodle specific widgets */
+ snd_soc_dapm_new_controls(dapm, wm8731_dapm_widgets,
+ ARRAY_SIZE(wm8731_dapm_widgets));
+
+ /* Set up poodle specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(dapm);
+ return 0;
+}
+
+/* poodle digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link poodle_dai = {
+ .name = "WM8731",
+ .stream_name = "WM8731",
+ .cpu_dai_name = "pxa2xx-i2s",
+ .codec_dai_name = "wm8731-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm8731.0-001b",
+ .init = poodle_wm8731_init,
+ .ops = &poodle_ops,
+};
+
+/* poodle audio machine driver */
+static struct snd_soc_card snd_soc_poodle = {
+ .name = "Poodle",
+ .dai_link = &poodle_dai,
+ .num_links = 1,
+ .owner = THIS_MODULE,
+};
+
+static struct platform_device *poodle_snd_device;
+
+static int __init poodle_init(void)
+{
+ int ret;
+
+ if (!machine_is_poodle())
+ return -ENODEV;
+
+ locomo_gpio_set_dir(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_AMP_ON, 0);
+ /* should we mute HP at startup - burning power ?*/
+ locomo_gpio_set_dir(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_L, 0);
+ locomo_gpio_set_dir(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_R, 0);
+
+ poodle_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!poodle_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(poodle_snd_device, &snd_soc_poodle);
+ ret = platform_device_add(poodle_snd_device);
+
+ if (ret)
+ platform_device_put(poodle_snd_device);
+
+ return ret;
+}
+
+static void __exit poodle_exit(void)
+{
+ platform_device_unregister(poodle_snd_device);
+}
+
+module_init(poodle_init);
+module_exit(poodle_exit);
+
+/* Module information */
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Poodle");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c
new file mode 100644
index 00000000..b583e604
--- /dev/null
+++ b/sound/soc/pxa/pxa-ssp.c
@@ -0,0 +1,854 @@
+/*
+ * pxa-ssp.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2005,2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * Mark Brown <broonie@opensource.wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ *
+ * TODO:
+ * o Test network mode for > 16bit sample size
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/pxa2xx_ssp.h>
+
+#include <asm/irq.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <mach/hardware.h>
+#include <mach/dma.h>
+#include <mach/audio.h>
+
+#include "../../arm/pxa2xx-pcm.h"
+#include "pxa-ssp.h"
+
+/*
+ * SSP audio private data
+ */
+struct ssp_priv {
+ struct ssp_device *ssp;
+ unsigned int sysclk;
+ int dai_fmt;
+#ifdef CONFIG_PM
+ uint32_t cr0;
+ uint32_t cr1;
+ uint32_t to;
+ uint32_t psp;
+#endif
+};
+
+static void dump_registers(struct ssp_device *ssp)
+{
+ dev_dbg(&ssp->pdev->dev, "SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x\n",
+ pxa_ssp_read_reg(ssp, SSCR0), pxa_ssp_read_reg(ssp, SSCR1),
+ pxa_ssp_read_reg(ssp, SSTO));
+
+ dev_dbg(&ssp->pdev->dev, "SSPSP 0x%08x SSSR 0x%08x SSACD 0x%08x\n",
+ pxa_ssp_read_reg(ssp, SSPSP), pxa_ssp_read_reg(ssp, SSSR),
+ pxa_ssp_read_reg(ssp, SSACD));
+}
+
+static void pxa_ssp_enable(struct ssp_device *ssp)
+{
+ uint32_t sscr0;
+
+ sscr0 = __raw_readl(ssp->mmio_base + SSCR0) | SSCR0_SSE;
+ __raw_writel(sscr0, ssp->mmio_base + SSCR0);
+}
+
+static void pxa_ssp_disable(struct ssp_device *ssp)
+{
+ uint32_t sscr0;
+
+ sscr0 = __raw_readl(ssp->mmio_base + SSCR0) & ~SSCR0_SSE;
+ __raw_writel(sscr0, ssp->mmio_base + SSCR0);
+}
+
+struct pxa2xx_pcm_dma_data {
+ struct pxa2xx_pcm_dma_params params;
+ char name[20];
+};
+
+static struct pxa2xx_pcm_dma_params *
+pxa_ssp_get_dma_params(struct ssp_device *ssp, int width4, int out)
+{
+ struct pxa2xx_pcm_dma_data *dma;
+
+ dma = kzalloc(sizeof(struct pxa2xx_pcm_dma_data), GFP_KERNEL);
+ if (dma == NULL)
+ return NULL;
+
+ snprintf(dma->name, 20, "SSP%d PCM %s %s", ssp->port_id,
+ width4 ? "32-bit" : "16-bit", out ? "out" : "in");
+
+ dma->params.name = dma->name;
+ dma->params.drcmr = &DRCMR(out ? ssp->drcmr_tx : ssp->drcmr_rx);
+ dma->params.dcmd = (out ? (DCMD_INCSRCADDR | DCMD_FLOWTRG) :
+ (DCMD_INCTRGADDR | DCMD_FLOWSRC)) |
+ (width4 ? DCMD_WIDTH4 : DCMD_WIDTH2) | DCMD_BURST16;
+ dma->params.dev_addr = ssp->phys_base + SSDR;
+
+ return &dma->params;
+}
+
+static int pxa_ssp_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ int ret = 0;
+
+ if (!cpu_dai->active) {
+ clk_enable(ssp->clk);
+ pxa_ssp_disable(ssp);
+ }
+
+ kfree(snd_soc_dai_get_dma_data(cpu_dai, substream));
+ snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
+
+ return ret;
+}
+
+static void pxa_ssp_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+
+ if (!cpu_dai->active) {
+ pxa_ssp_disable(ssp);
+ clk_disable(ssp->clk);
+ }
+
+ kfree(snd_soc_dai_get_dma_data(cpu_dai, substream));
+ snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
+}
+
+#ifdef CONFIG_PM
+
+static int pxa_ssp_suspend(struct snd_soc_dai *cpu_dai)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+
+ if (!cpu_dai->active)
+ clk_enable(ssp->clk);
+
+ priv->cr0 = __raw_readl(ssp->mmio_base + SSCR0);
+ priv->cr1 = __raw_readl(ssp->mmio_base + SSCR1);
+ priv->to = __raw_readl(ssp->mmio_base + SSTO);
+ priv->psp = __raw_readl(ssp->mmio_base + SSPSP);
+
+ pxa_ssp_disable(ssp);
+ clk_disable(ssp->clk);
+ return 0;
+}
+
+static int pxa_ssp_resume(struct snd_soc_dai *cpu_dai)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ uint32_t sssr = SSSR_ROR | SSSR_TUR | SSSR_BCE;
+
+ clk_enable(ssp->clk);
+
+ __raw_writel(sssr, ssp->mmio_base + SSSR);
+ __raw_writel(priv->cr0 & ~SSCR0_SSE, ssp->mmio_base + SSCR0);
+ __raw_writel(priv->cr1, ssp->mmio_base + SSCR1);
+ __raw_writel(priv->to, ssp->mmio_base + SSTO);
+ __raw_writel(priv->psp, ssp->mmio_base + SSPSP);
+
+ if (cpu_dai->active)
+ pxa_ssp_enable(ssp);
+ else
+ clk_disable(ssp->clk);
+
+ return 0;
+}
+
+#else
+#define pxa_ssp_suspend NULL
+#define pxa_ssp_resume NULL
+#endif
+
+/**
+ * ssp_set_clkdiv - set SSP clock divider
+ * @div: serial clock rate divider
+ */
+static void pxa_ssp_set_scr(struct ssp_device *ssp, u32 div)
+{
+ u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
+
+ if (cpu_is_pxa25x() && ssp->type == PXA25x_SSP) {
+ sscr0 &= ~0x0000ff00;
+ sscr0 |= ((div - 2)/2) << 8; /* 2..512 */
+ } else {
+ sscr0 &= ~0x000fff00;
+ sscr0 |= (div - 1) << 8; /* 1..4096 */
+ }
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+}
+
+/**
+ * pxa_ssp_get_clkdiv - get SSP clock divider
+ */
+static u32 pxa_ssp_get_scr(struct ssp_device *ssp)
+{
+ u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
+ u32 div;
+
+ if (cpu_is_pxa25x() && ssp->type == PXA25x_SSP)
+ div = ((sscr0 >> 8) & 0xff) * 2 + 2;
+ else
+ div = ((sscr0 >> 8) & 0xfff) + 1;
+ return div;
+}
+
+/*
+ * Set the SSP ports SYSCLK.
+ */
+static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ int val;
+
+ u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0) &
+ ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS);
+
+ dev_dbg(&ssp->pdev->dev,
+ "pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %u\n",
+ cpu_dai->id, clk_id, freq);
+
+ switch (clk_id) {
+ case PXA_SSP_CLK_NET_PLL:
+ sscr0 |= SSCR0_MOD;
+ break;
+ case PXA_SSP_CLK_PLL:
+ /* Internal PLL is fixed */
+ if (cpu_is_pxa25x())
+ priv->sysclk = 1843200;
+ else
+ priv->sysclk = 13000000;
+ break;
+ case PXA_SSP_CLK_EXT:
+ priv->sysclk = freq;
+ sscr0 |= SSCR0_ECS;
+ break;
+ case PXA_SSP_CLK_NET:
+ priv->sysclk = freq;
+ sscr0 |= SSCR0_NCS | SSCR0_MOD;
+ break;
+ case PXA_SSP_CLK_AUDIO:
+ priv->sysclk = 0;
+ pxa_ssp_set_scr(ssp, 1);
+ sscr0 |= SSCR0_ACS;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ /* The SSP clock must be disabled when changing SSP clock mode
+ * on PXA2xx. On PXA3xx it must be enabled when doing so. */
+ if (!cpu_is_pxa3xx())
+ clk_disable(ssp->clk);
+ val = pxa_ssp_read_reg(ssp, SSCR0) | sscr0;
+ pxa_ssp_write_reg(ssp, SSCR0, val);
+ if (!cpu_is_pxa3xx())
+ clk_enable(ssp->clk);
+
+ return 0;
+}
+
+/*
+ * Set the SSP clock dividers.
+ */
+static int pxa_ssp_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ int val;
+
+ switch (div_id) {
+ case PXA_SSP_AUDIO_DIV_ACDS:
+ val = (pxa_ssp_read_reg(ssp, SSACD) & ~0x7) | SSACD_ACDS(div);
+ pxa_ssp_write_reg(ssp, SSACD, val);
+ break;
+ case PXA_SSP_AUDIO_DIV_SCDB:
+ val = pxa_ssp_read_reg(ssp, SSACD);
+ val &= ~SSACD_SCDB;
+#if defined(CONFIG_PXA3xx)
+ if (cpu_is_pxa3xx())
+ val &= ~SSACD_SCDX8;
+#endif
+ switch (div) {
+ case PXA_SSP_CLK_SCDB_1:
+ val |= SSACD_SCDB;
+ break;
+ case PXA_SSP_CLK_SCDB_4:
+ break;
+#if defined(CONFIG_PXA3xx)
+ case PXA_SSP_CLK_SCDB_8:
+ if (cpu_is_pxa3xx())
+ val |= SSACD_SCDX8;
+ else
+ return -EINVAL;
+ break;
+#endif
+ default:
+ return -EINVAL;
+ }
+ pxa_ssp_write_reg(ssp, SSACD, val);
+ break;
+ case PXA_SSP_DIV_SCR:
+ pxa_ssp_set_scr(ssp, div);
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/*
+ * Configure the PLL frequency pxa27x and (afaik - pxa320 only)
+ */
+static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ u32 ssacd = pxa_ssp_read_reg(ssp, SSACD) & ~0x70;
+
+#if defined(CONFIG_PXA3xx)
+ if (cpu_is_pxa3xx())
+ pxa_ssp_write_reg(ssp, SSACDD, 0);
+#endif
+
+ switch (freq_out) {
+ case 5622000:
+ break;
+ case 11345000:
+ ssacd |= (0x1 << 4);
+ break;
+ case 12235000:
+ ssacd |= (0x2 << 4);
+ break;
+ case 14857000:
+ ssacd |= (0x3 << 4);
+ break;
+ case 32842000:
+ ssacd |= (0x4 << 4);
+ break;
+ case 48000000:
+ ssacd |= (0x5 << 4);
+ break;
+ case 0:
+ /* Disable */
+ break;
+
+ default:
+#ifdef CONFIG_PXA3xx
+ /* PXA3xx has a clock ditherer which can be used to generate
+ * a wider range of frequencies - calculate a value for it.
+ */
+ if (cpu_is_pxa3xx()) {
+ u32 val;
+ u64 tmp = 19968;
+ tmp *= 1000000;
+ do_div(tmp, freq_out);
+ val = tmp;
+
+ val = (val << 16) | 64;
+ pxa_ssp_write_reg(ssp, SSACDD, val);
+
+ ssacd |= (0x6 << 4);
+
+ dev_dbg(&ssp->pdev->dev,
+ "Using SSACDD %x to supply %uHz\n",
+ val, freq_out);
+ break;
+ }
+#endif
+
+ return -EINVAL;
+ }
+
+ pxa_ssp_write_reg(ssp, SSACD, ssacd);
+
+ return 0;
+}
+
+/*
+ * Set the active slots in TDM/Network mode
+ */
+static int pxa_ssp_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+ unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ u32 sscr0;
+
+ sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
+ sscr0 &= ~(SSCR0_MOD | SSCR0_SlotsPerFrm(8) | SSCR0_EDSS | SSCR0_DSS);
+
+ /* set slot width */
+ if (slot_width > 16)
+ sscr0 |= SSCR0_EDSS | SSCR0_DataSize(slot_width - 16);
+ else
+ sscr0 |= SSCR0_DataSize(slot_width);
+
+ if (slots > 1) {
+ /* enable network mode */
+ sscr0 |= SSCR0_MOD;
+
+ /* set number of active slots */
+ sscr0 |= SSCR0_SlotsPerFrm(slots);
+
+ /* set active slot mask */
+ pxa_ssp_write_reg(ssp, SSTSA, tx_mask);
+ pxa_ssp_write_reg(ssp, SSRSA, rx_mask);
+ }
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+
+ return 0;
+}
+
+/*
+ * Tristate the SSP DAI lines
+ */
+static int pxa_ssp_set_dai_tristate(struct snd_soc_dai *cpu_dai,
+ int tristate)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ u32 sscr1;
+
+ sscr1 = pxa_ssp_read_reg(ssp, SSCR1);
+ if (tristate)
+ sscr1 &= ~SSCR1_TTE;
+ else
+ sscr1 |= SSCR1_TTE;
+ pxa_ssp_write_reg(ssp, SSCR1, sscr1);
+
+ return 0;
+}
+
+/*
+ * Set up the SSP DAI format.
+ * The SSP Port must be inactive before calling this function as the
+ * physical interface format is changed.
+ */
+static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ u32 sscr0, sscr1, sspsp, scfr;
+
+ /* check if we need to change anything at all */
+ if (priv->dai_fmt == fmt)
+ return 0;
+
+ /* we can only change the settings if the port is not in use */
+ if (pxa_ssp_read_reg(ssp, SSCR0) & SSCR0_SSE) {
+ dev_err(&ssp->pdev->dev,
+ "can't change hardware dai format: stream is in use");
+ return -EINVAL;
+ }
+
+ /* reset port settings */
+ sscr0 = pxa_ssp_read_reg(ssp, SSCR0) &
+ ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS);
+ sscr1 = SSCR1_RxTresh(8) | SSCR1_TxTresh(7);
+ sspsp = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ sscr1 |= SSCR1_SCLKDIR | SSCR1_SFRMDIR | SSCR1_SCFR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ sscr1 |= SSCR1_SCLKDIR | SSCR1_SCFR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ sspsp |= SSPSP_SFRMP;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ sspsp |= SSPSP_SCMODE(2);
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ sscr0 |= SSCR0_PSP;
+ sscr1 |= SSCR1_RWOT | SSCR1_TRAIL;
+ /* See hw_params() */
+ break;
+
+ case SND_SOC_DAIFMT_DSP_A:
+ sspsp |= SSPSP_FSRT;
+ case SND_SOC_DAIFMT_DSP_B:
+ sscr0 |= SSCR0_MOD | SSCR0_PSP;
+ sscr1 |= SSCR1_TRAIL | SSCR1_RWOT;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+ pxa_ssp_write_reg(ssp, SSCR1, sscr1);
+ pxa_ssp_write_reg(ssp, SSPSP, sspsp);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ scfr = pxa_ssp_read_reg(ssp, SSCR1) | SSCR1_SCFR;
+ pxa_ssp_write_reg(ssp, SSCR1, scfr);
+
+ while (pxa_ssp_read_reg(ssp, SSSR) & SSSR_BSY)
+ cpu_relax();
+ break;
+ }
+
+ dump_registers(ssp);
+
+ /* Since we are configuring the timings for the format by hand
+ * we have to defer some things until hw_params() where we
+ * know parameters like the sample size.
+ */
+ priv->dai_fmt = fmt;
+
+ return 0;
+}
+
+/*
+ * Set the SSP audio DMA parameters and sample size.
+ * Can be called multiple times by oss emulation.
+ */
+static int pxa_ssp_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ int chn = params_channels(params);
+ u32 sscr0;
+ u32 sspsp;
+ int width = snd_pcm_format_physical_width(params_format(params));
+ int ttsa = pxa_ssp_read_reg(ssp, SSTSA) & 0xf;
+ struct pxa2xx_pcm_dma_params *dma_data;
+
+ dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ /* generate correct DMA params */
+ kfree(dma_data);
+
+ /* Network mode with one active slot (ttsa == 1) can be used
+ * to force 16-bit frame width on the wire (for S16_LE), even
+ * with two channels. Use 16-bit DMA transfers for this case.
+ */
+ dma_data = pxa_ssp_get_dma_params(ssp,
+ ((chn == 2) && (ttsa != 1)) || (width == 32),
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+ /* we can only change the settings if the port is not in use */
+ if (pxa_ssp_read_reg(ssp, SSCR0) & SSCR0_SSE)
+ return 0;
+
+ /* clear selected SSP bits */
+ sscr0 = pxa_ssp_read_reg(ssp, SSCR0) & ~(SSCR0_DSS | SSCR0_EDSS);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+#ifdef CONFIG_PXA3xx
+ if (cpu_is_pxa3xx())
+ sscr0 |= SSCR0_FPCKE;
+#endif
+ sscr0 |= SSCR0_DataSize(16);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(8));
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(16));
+ break;
+ }
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+
+ switch (priv->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ sspsp = pxa_ssp_read_reg(ssp, SSPSP);
+
+ if ((pxa_ssp_get_scr(ssp) == 4) && (width == 16)) {
+ /* This is a special case where the bitclk is 64fs
+ * and we're not dealing with 2*32 bits of audio
+ * samples.
+ *
+ * The SSP values used for that are all found out by
+ * trying and failing a lot; some of the registers
+ * needed for that mode are only available on PXA3xx.
+ */
+
+#ifdef CONFIG_PXA3xx
+ if (!cpu_is_pxa3xx())
+ return -EINVAL;
+
+ sspsp |= SSPSP_SFRMWDTH(width * 2);
+ sspsp |= SSPSP_SFRMDLY(width * 4);
+ sspsp |= SSPSP_EDMYSTOP(3);
+ sspsp |= SSPSP_DMYSTOP(3);
+ sspsp |= SSPSP_DMYSTRT(1);
+#else
+ return -EINVAL;
+#endif
+ } else {
+ /* The frame width is the width the LRCLK is
+ * asserted for; the delay is expressed in
+ * half cycle units. We need the extra cycle
+ * because the data starts clocking out one BCLK
+ * after LRCLK changes polarity.
+ */
+ sspsp |= SSPSP_SFRMWDTH(width + 1);
+ sspsp |= SSPSP_SFRMDLY((width + 1) * 2);
+ sspsp |= SSPSP_DMYSTRT(1);
+ }
+
+ pxa_ssp_write_reg(ssp, SSPSP, sspsp);
+ break;
+ default:
+ break;
+ }
+
+ /* When we use a network mode, we always require TDM slots
+ * - complain loudly and fail if they've not been set up yet.
+ */
+ if ((sscr0 & SSCR0_MOD) && !ttsa) {
+ dev_err(&ssp->pdev->dev, "No TDM timeslot configured\n");
+ return -EINVAL;
+ }
+
+ dump_registers(ssp);
+
+ return 0;
+}
+
+static void pxa_ssp_set_running_bit(struct snd_pcm_substream *substream,
+ struct ssp_device *ssp, int value)
+{
+ uint32_t sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
+ uint32_t sscr1 = pxa_ssp_read_reg(ssp, SSCR1);
+ uint32_t sspsp = pxa_ssp_read_reg(ssp, SSPSP);
+ uint32_t sssr = pxa_ssp_read_reg(ssp, SSSR);
+
+ if (value && (sscr0 & SSCR0_SSE))
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0 & ~SSCR0_SSE);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (value)
+ sscr1 |= SSCR1_TSRE;
+ else
+ sscr1 &= ~SSCR1_TSRE;
+ } else {
+ if (value)
+ sscr1 |= SSCR1_RSRE;
+ else
+ sscr1 &= ~SSCR1_RSRE;
+ }
+
+ pxa_ssp_write_reg(ssp, SSCR1, sscr1);
+
+ if (value) {
+ pxa_ssp_write_reg(ssp, SSSR, sssr);
+ pxa_ssp_write_reg(ssp, SSPSP, sspsp);
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0 | SSCR0_SSE);
+ }
+}
+
+static int pxa_ssp_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
+{
+ int ret = 0;
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ int val;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ pxa_ssp_enable(ssp);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ pxa_ssp_set_running_bit(substream, ssp, 1);
+ val = pxa_ssp_read_reg(ssp, SSSR);
+ pxa_ssp_write_reg(ssp, SSSR, val);
+ break;
+ case SNDRV_PCM_TRIGGER_START:
+ pxa_ssp_set_running_bit(substream, ssp, 1);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ pxa_ssp_set_running_bit(substream, ssp, 0);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ pxa_ssp_disable(ssp);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ pxa_ssp_set_running_bit(substream, ssp, 0);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ dump_registers(ssp);
+
+ return ret;
+}
+
+static int pxa_ssp_probe(struct snd_soc_dai *dai)
+{
+ struct ssp_priv *priv;
+ int ret;
+
+ priv = kzalloc(sizeof(struct ssp_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->ssp = pxa_ssp_request(dai->id + 1, "SoC audio");
+ if (priv->ssp == NULL) {
+ ret = -ENODEV;
+ goto err_priv;
+ }
+
+ priv->dai_fmt = (unsigned int) -1;
+ snd_soc_dai_set_drvdata(dai, priv);
+
+ return 0;
+
+err_priv:
+ kfree(priv);
+ return ret;
+}
+
+static int pxa_ssp_remove(struct snd_soc_dai *dai)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(dai);
+
+ pxa_ssp_free(priv->ssp);
+ kfree(priv);
+ return 0;
+}
+
+#define PXA_SSP_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define PXA_SSP_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops pxa_ssp_dai_ops = {
+ .startup = pxa_ssp_startup,
+ .shutdown = pxa_ssp_shutdown,
+ .trigger = pxa_ssp_trigger,
+ .hw_params = pxa_ssp_hw_params,
+ .set_sysclk = pxa_ssp_set_dai_sysclk,
+ .set_clkdiv = pxa_ssp_set_dai_clkdiv,
+ .set_pll = pxa_ssp_set_dai_pll,
+ .set_fmt = pxa_ssp_set_dai_fmt,
+ .set_tdm_slot = pxa_ssp_set_dai_tdm_slot,
+ .set_tristate = pxa_ssp_set_dai_tristate,
+};
+
+static struct snd_soc_dai_driver pxa_ssp_dai = {
+ .probe = pxa_ssp_probe,
+ .remove = pxa_ssp_remove,
+ .suspend = pxa_ssp_suspend,
+ .resume = pxa_ssp_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = PXA_SSP_RATES,
+ .formats = PXA_SSP_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = PXA_SSP_RATES,
+ .formats = PXA_SSP_FORMATS,
+ },
+ .ops = &pxa_ssp_dai_ops,
+};
+
+static __devinit int asoc_ssp_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_dai(&pdev->dev, &pxa_ssp_dai);
+}
+
+static int __devexit asoc_ssp_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver asoc_ssp_driver = {
+ .driver = {
+ .name = "pxa-ssp-dai",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = asoc_ssp_probe,
+ .remove = __devexit_p(asoc_ssp_remove),
+};
+
+static int __init pxa_ssp_init(void)
+{
+ return platform_driver_register(&asoc_ssp_driver);
+}
+module_init(pxa_ssp_init);
+
+static void __exit pxa_ssp_exit(void)
+{
+ platform_driver_unregister(&asoc_ssp_driver);
+}
+module_exit(pxa_ssp_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("PXA SSP/PCM SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/pxa-ssp.h b/sound/soc/pxa/pxa-ssp.h
new file mode 100644
index 00000000..bc79da22
--- /dev/null
+++ b/sound/soc/pxa/pxa-ssp.h
@@ -0,0 +1,45 @@
+/*
+ * ASoC PXA SSP port support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PXA_SSP_H
+#define _PXA_SSP_H
+
+/* pxa DAI SSP IDs */
+#define PXA_DAI_SSP1 0
+#define PXA_DAI_SSP2 1
+#define PXA_DAI_SSP3 2
+#define PXA_DAI_SSP4 3
+
+/* SSP clock sources */
+#define PXA_SSP_CLK_PLL 0
+#define PXA_SSP_CLK_EXT 1
+#define PXA_SSP_CLK_NET 2
+#define PXA_SSP_CLK_AUDIO 3
+#define PXA_SSP_CLK_NET_PLL 4
+
+/* SSP audio dividers */
+#define PXA_SSP_AUDIO_DIV_ACDS 0
+#define PXA_SSP_AUDIO_DIV_SCDB 1
+#define PXA_SSP_DIV_SCR 2
+
+/* SSP ACDS audio dividers values */
+#define PXA_SSP_CLK_AUDIO_DIV_1 0
+#define PXA_SSP_CLK_AUDIO_DIV_2 1
+#define PXA_SSP_CLK_AUDIO_DIV_4 2
+#define PXA_SSP_CLK_AUDIO_DIV_8 3
+#define PXA_SSP_CLK_AUDIO_DIV_16 4
+#define PXA_SSP_CLK_AUDIO_DIV_32 5
+
+/* SSP divider bypass */
+#define PXA_SSP_CLK_SCDB_4 0
+#define PXA_SSP_CLK_SCDB_1 1
+#define PXA_SSP_CLK_SCDB_8 2
+
+#define PXA_SSP_PLL_OUT 0
+
+#endif
diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c
new file mode 100644
index 00000000..ac51c6d2
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-ac97.c
@@ -0,0 +1,280 @@
+/*
+ * linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip.
+ *
+ * Author: Nicolas Pitre
+ * Created: Dec 02, 2004
+ * Copyright: MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <mach/hardware.h>
+#include <mach/regs-ac97.h>
+#include <mach/dma.h>
+#include <mach/audio.h>
+
+#include "pxa2xx-ac97.h"
+
+static void pxa2xx_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ pxa2xx_ac97_try_warm_reset(ac97);
+
+ pxa2xx_ac97_finish_reset(ac97);
+}
+
+static void pxa2xx_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ pxa2xx_ac97_try_cold_reset(ac97);
+
+ pxa2xx_ac97_finish_reset(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = pxa2xx_ac97_read,
+ .write = pxa2xx_ac97_write,
+ .warm_reset = pxa2xx_ac97_warm_reset,
+ .reset = pxa2xx_ac97_cold_reset,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_stereo_out = {
+ .name = "AC97 PCM Stereo out",
+ .dev_addr = __PREG(PCDR),
+ .drcmr = &DRCMR(12),
+ .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
+ DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_stereo_in = {
+ .name = "AC97 PCM Stereo in",
+ .dev_addr = __PREG(PCDR),
+ .drcmr = &DRCMR(11),
+ .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
+ DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_aux_mono_out = {
+ .name = "AC97 Aux PCM (Slot 5) Mono out",
+ .dev_addr = __PREG(MODR),
+ .drcmr = &DRCMR(10),
+ .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
+ DCMD_BURST16 | DCMD_WIDTH2,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_aux_mono_in = {
+ .name = "AC97 Aux PCM (Slot 5) Mono in",
+ .dev_addr = __PREG(MODR),
+ .drcmr = &DRCMR(9),
+ .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
+ DCMD_BURST16 | DCMD_WIDTH2,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_mic_mono_in = {
+ .name = "AC97 Mic PCM (Slot 6) Mono in",
+ .dev_addr = __PREG(MCDR),
+ .drcmr = &DRCMR(8),
+ .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
+ DCMD_BURST16 | DCMD_WIDTH2,
+};
+
+#ifdef CONFIG_PM
+static int pxa2xx_ac97_suspend(struct snd_soc_dai *dai)
+{
+ return pxa2xx_ac97_hw_suspend();
+}
+
+static int pxa2xx_ac97_resume(struct snd_soc_dai *dai)
+{
+ return pxa2xx_ac97_hw_resume();
+}
+
+#else
+#define pxa2xx_ac97_suspend NULL
+#define pxa2xx_ac97_resume NULL
+#endif
+
+static int pxa2xx_ac97_probe(struct snd_soc_dai *dai)
+{
+ return pxa2xx_ac97_hw_probe(to_platform_device(dai->dev));
+}
+
+static int pxa2xx_ac97_remove(struct snd_soc_dai *dai)
+{
+ pxa2xx_ac97_hw_remove(to_platform_device(dai->dev));
+ return 0;
+}
+
+static int pxa2xx_ac97_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct pxa2xx_pcm_dma_params *dma_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &pxa2xx_ac97_pcm_stereo_out;
+ else
+ dma_data = &pxa2xx_ac97_pcm_stereo_in;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+ return 0;
+}
+
+static int pxa2xx_ac97_hw_aux_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct pxa2xx_pcm_dma_params *dma_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &pxa2xx_ac97_pcm_aux_mono_out;
+ else
+ dma_data = &pxa2xx_ac97_pcm_aux_mono_in;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+ return 0;
+}
+
+static int pxa2xx_ac97_hw_mic_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+ else
+ snd_soc_dai_set_dma_data(cpu_dai, substream,
+ &pxa2xx_ac97_pcm_mic_mono_in);
+
+ return 0;
+}
+
+#define PXA2XX_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops pxa_ac97_hifi_dai_ops = {
+ .hw_params = pxa2xx_ac97_hw_params,
+};
+
+static struct snd_soc_dai_ops pxa_ac97_aux_dai_ops = {
+ .hw_params = pxa2xx_ac97_hw_aux_params,
+};
+
+static struct snd_soc_dai_ops pxa_ac97_mic_dai_ops = {
+ .hw_params = pxa2xx_ac97_hw_mic_params,
+};
+
+/*
+ * There is only 1 physical AC97 interface for pxa2xx, but it
+ * has extra fifo's that can be used for aux DACs and ADCs.
+ */
+static struct snd_soc_dai_driver pxa_ac97_dai[] = {
+{
+ .name = "pxa2xx-ac97",
+ .ac97_control = 1,
+ .probe = pxa2xx_ac97_probe,
+ .remove = pxa2xx_ac97_remove,
+ .suspend = pxa2xx_ac97_suspend,
+ .resume = pxa2xx_ac97_resume,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &pxa_ac97_hifi_dai_ops,
+},
+{
+ .name = "pxa2xx-ac97-aux",
+ .ac97_control = 1,
+ .playback = {
+ .stream_name = "AC97 Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Aux Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &pxa_ac97_aux_dai_ops,
+},
+{
+ .name = "pxa2xx-ac97-mic",
+ .ac97_control = 1,
+ .capture = {
+ .stream_name = "AC97 Mic Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &pxa_ac97_mic_dai_ops,
+},
+};
+
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static __devinit int pxa2xx_ac97_dev_probe(struct platform_device *pdev)
+{
+ if (pdev->id != -1) {
+ dev_err(&pdev->dev, "PXA2xx has only one AC97 port.\n");
+ return -ENXIO;
+ }
+
+ /* Punt most of the init to the SoC probe; we may need the machine
+ * driver to do interesting things with the clocking to get us up
+ * and running.
+ */
+ return snd_soc_register_dais(&pdev->dev, pxa_ac97_dai,
+ ARRAY_SIZE(pxa_ac97_dai));
+}
+
+static int __devexit pxa2xx_ac97_dev_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(pxa_ac97_dai));
+ return 0;
+}
+
+static struct platform_driver pxa2xx_ac97_driver = {
+ .probe = pxa2xx_ac97_dev_probe,
+ .remove = __devexit_p(pxa2xx_ac97_dev_remove),
+ .driver = {
+ .name = "pxa2xx-ac97",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pxa_ac97_init(void)
+{
+ return platform_driver_register(&pxa2xx_ac97_driver);
+}
+module_init(pxa_ac97_init);
+
+static void __exit pxa_ac97_exit(void)
+{
+ platform_driver_unregister(&pxa2xx_ac97_driver);
+}
+module_exit(pxa_ac97_exit);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/pxa2xx-ac97.h b/sound/soc/pxa/pxa2xx-ac97.h
new file mode 100644
index 00000000..eda891e6
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-ac97.h
@@ -0,0 +1,20 @@
+/*
+ * linux/sound/soc/pxa/pxa2xx-ac97.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PXA2XX_AC97_H
+#define _PXA2XX_AC97_H
+
+/* pxa2xx DAI ID's */
+#define PXA2XX_DAI_AC97_HIFI 0
+#define PXA2XX_DAI_AC97_AUX 1
+#define PXA2XX_DAI_AC97_MIC 2
+
+/* platform data */
+extern struct snd_ac97_bus_ops pxa2xx_ac97_ops;
+
+#endif
diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c
new file mode 100644
index 00000000..11be5952
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-i2s.c
@@ -0,0 +1,401 @@
+/*
+ * pxa2xx-i2s.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * lrg@slimlogic.co.uk
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <mach/hardware.h>
+#include <mach/dma.h>
+#include <mach/audio.h>
+
+#include "pxa2xx-i2s.h"
+
+/*
+ * I2S Controller Register and Bit Definitions
+ */
+#define SACR0 __REG(0x40400000) /* Global Control Register */
+#define SACR1 __REG(0x40400004) /* Serial Audio I 2 S/MSB-Justified Control Register */
+#define SASR0 __REG(0x4040000C) /* Serial Audio I 2 S/MSB-Justified Interface and FIFO Status Register */
+#define SAIMR __REG(0x40400014) /* Serial Audio Interrupt Mask Register */
+#define SAICR __REG(0x40400018) /* Serial Audio Interrupt Clear Register */
+#define SADIV __REG(0x40400060) /* Audio Clock Divider Register. */
+#define SADR __REG(0x40400080) /* Serial Audio Data Register (TX and RX FIFO access Register). */
+
+#define SACR0_RFTH(x) ((x) << 12) /* Rx FIFO Interrupt or DMA Trigger Threshold */
+#define SACR0_TFTH(x) ((x) << 8) /* Tx FIFO Interrupt or DMA Trigger Threshold */
+#define SACR0_STRF (1 << 5) /* FIFO Select for EFWR Special Function */
+#define SACR0_EFWR (1 << 4) /* Enable EFWR Function */
+#define SACR0_RST (1 << 3) /* FIFO, i2s Register Reset */
+#define SACR0_BCKD (1 << 2) /* Bit Clock Direction */
+#define SACR0_ENB (1 << 0) /* Enable I2S Link */
+#define SACR1_ENLBF (1 << 5) /* Enable Loopback */
+#define SACR1_DRPL (1 << 4) /* Disable Replaying Function */
+#define SACR1_DREC (1 << 3) /* Disable Recording Function */
+#define SACR1_AMSL (1 << 0) /* Specify Alternate Mode */
+
+#define SASR0_I2SOFF (1 << 7) /* Controller Status */
+#define SASR0_ROR (1 << 6) /* Rx FIFO Overrun */
+#define SASR0_TUR (1 << 5) /* Tx FIFO Underrun */
+#define SASR0_RFS (1 << 4) /* Rx FIFO Service Request */
+#define SASR0_TFS (1 << 3) /* Tx FIFO Service Request */
+#define SASR0_BSY (1 << 2) /* I2S Busy */
+#define SASR0_RNE (1 << 1) /* Rx FIFO Not Empty */
+#define SASR0_TNF (1 << 0) /* Tx FIFO Not Empty */
+
+#define SAICR_ROR (1 << 6) /* Clear Rx FIFO Overrun Interrupt */
+#define SAICR_TUR (1 << 5) /* Clear Tx FIFO Underrun Interrupt */
+
+#define SAIMR_ROR (1 << 6) /* Enable Rx FIFO Overrun Condition Interrupt */
+#define SAIMR_TUR (1 << 5) /* Enable Tx FIFO Underrun Condition Interrupt */
+#define SAIMR_RFS (1 << 4) /* Enable Rx FIFO Service Interrupt */
+#define SAIMR_TFS (1 << 3) /* Enable Tx FIFO Service Interrupt */
+
+struct pxa_i2s_port {
+ u32 sadiv;
+ u32 sacr0;
+ u32 sacr1;
+ u32 saimr;
+ int master;
+ u32 fmt;
+};
+static struct pxa_i2s_port pxa_i2s;
+static struct clk *clk_i2s;
+static int clk_ena = 0;
+
+static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_out = {
+ .name = "I2S PCM Stereo out",
+ .dev_addr = __PREG(SADR),
+ .drcmr = &DRCMR(3),
+ .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
+ DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_in = {
+ .name = "I2S PCM Stereo in",
+ .dev_addr = __PREG(SADR),
+ .drcmr = &DRCMR(2),
+ .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
+ DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ if (IS_ERR(clk_i2s))
+ return PTR_ERR(clk_i2s);
+
+ if (!cpu_dai->active)
+ SACR0 = 0;
+
+ return 0;
+}
+
+/* wait for I2S controller to be ready */
+static int pxa_i2s_wait(void)
+{
+ int i;
+
+ /* flush the Rx FIFO */
+ for(i = 0; i < 16; i++)
+ SADR;
+ return 0;
+}
+
+static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ pxa_i2s.fmt = 0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ pxa_i2s.fmt = SACR1_AMSL;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ pxa_i2s.master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ pxa_i2s.master = 0;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int pxa2xx_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ if (clk_id != PXA2XX_I2S_SYSCLK)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct pxa2xx_pcm_dma_params *dma_data;
+
+ BUG_ON(IS_ERR(clk_i2s));
+ clk_enable(clk_i2s);
+ clk_ena = 1;
+ pxa_i2s_wait();
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &pxa2xx_i2s_pcm_stereo_out;
+ else
+ dma_data = &pxa2xx_i2s_pcm_stereo_in;
+
+ snd_soc_dai_set_dma_data(dai, substream, dma_data);
+
+ /* is port used by another stream */
+ if (!(SACR0 & SACR0_ENB)) {
+ SACR0 = 0;
+ if (pxa_i2s.master)
+ SACR0 |= SACR0_BCKD;
+
+ SACR0 |= SACR0_RFTH(14) | SACR0_TFTH(1);
+ SACR1 |= pxa_i2s.fmt;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ SAIMR |= SAIMR_TFS;
+ else
+ SAIMR |= SAIMR_RFS;
+
+ switch (params_rate(params)) {
+ case 8000:
+ SADIV = 0x48;
+ break;
+ case 11025:
+ SADIV = 0x34;
+ break;
+ case 16000:
+ SADIV = 0x24;
+ break;
+ case 22050:
+ SADIV = 0x1a;
+ break;
+ case 44100:
+ SADIV = 0xd;
+ break;
+ case 48000:
+ SADIV = 0xc;
+ break;
+ case 96000: /* not in manual and possibly slightly inaccurate */
+ SADIV = 0x6;
+ break;
+ }
+
+ return 0;
+}
+
+static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ SACR1 &= ~SACR1_DRPL;
+ else
+ SACR1 &= ~SACR1_DREC;
+ SACR0 |= SACR0_ENB;
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ SACR1 |= SACR1_DRPL;
+ SAIMR &= ~SAIMR_TFS;
+ } else {
+ SACR1 |= SACR1_DREC;
+ SAIMR &= ~SAIMR_RFS;
+ }
+
+ if ((SACR1 & (SACR1_DREC | SACR1_DRPL)) == (SACR1_DREC | SACR1_DRPL)) {
+ SACR0 &= ~SACR0_ENB;
+ pxa_i2s_wait();
+ if (clk_ena) {
+ clk_disable(clk_i2s);
+ clk_ena = 0;
+ }
+ }
+}
+
+#ifdef CONFIG_PM
+static int pxa2xx_i2s_suspend(struct snd_soc_dai *dai)
+{
+ /* store registers */
+ pxa_i2s.sacr0 = SACR0;
+ pxa_i2s.sacr1 = SACR1;
+ pxa_i2s.saimr = SAIMR;
+ pxa_i2s.sadiv = SADIV;
+
+ /* deactivate link */
+ SACR0 &= ~SACR0_ENB;
+ pxa_i2s_wait();
+ return 0;
+}
+
+static int pxa2xx_i2s_resume(struct snd_soc_dai *dai)
+{
+ pxa_i2s_wait();
+
+ SACR0 = pxa_i2s.sacr0 & ~SACR0_ENB;
+ SACR1 = pxa_i2s.sacr1;
+ SAIMR = pxa_i2s.saimr;
+ SADIV = pxa_i2s.sadiv;
+
+ SACR0 = pxa_i2s.sacr0;
+
+ return 0;
+}
+
+#else
+#define pxa2xx_i2s_suspend NULL
+#define pxa2xx_i2s_resume NULL
+#endif
+
+static int pxa2xx_i2s_probe(struct snd_soc_dai *dai)
+{
+ clk_i2s = clk_get(dai->dev, "I2SCLK");
+ if (IS_ERR(clk_i2s))
+ return PTR_ERR(clk_i2s);
+
+ /*
+ * PXA Developer's Manual:
+ * If SACR0[ENB] is toggled in the middle of a normal operation,
+ * the SACR0[RST] bit must also be set and cleared to reset all
+ * I2S controller registers.
+ */
+ SACR0 = SACR0_RST;
+ SACR0 = 0;
+ /* Make sure RPL and REC are disabled */
+ SACR1 = SACR1_DRPL | SACR1_DREC;
+ /* Along with FIFO servicing */
+ SAIMR &= ~(SAIMR_RFS | SAIMR_TFS);
+
+ return 0;
+}
+
+static int pxa2xx_i2s_remove(struct snd_soc_dai *dai)
+{
+ clk_put(clk_i2s);
+ clk_i2s = ERR_PTR(-ENOENT);
+ return 0;
+}
+
+#define PXA2XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+
+static struct snd_soc_dai_ops pxa_i2s_dai_ops = {
+ .startup = pxa2xx_i2s_startup,
+ .shutdown = pxa2xx_i2s_shutdown,
+ .trigger = pxa2xx_i2s_trigger,
+ .hw_params = pxa2xx_i2s_hw_params,
+ .set_fmt = pxa2xx_i2s_set_dai_fmt,
+ .set_sysclk = pxa2xx_i2s_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver pxa_i2s_dai = {
+ .probe = pxa2xx_i2s_probe,
+ .remove = pxa2xx_i2s_remove,
+ .suspend = pxa2xx_i2s_suspend,
+ .resume = pxa2xx_i2s_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &pxa_i2s_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int pxa2xx_i2s_drv_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_dai(&pdev->dev, &pxa_i2s_dai);
+}
+
+static int __devexit pxa2xx_i2s_drv_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver pxa2xx_i2s_driver = {
+ .probe = pxa2xx_i2s_drv_probe,
+ .remove = __devexit_p(pxa2xx_i2s_drv_remove),
+
+ .driver = {
+ .name = "pxa2xx-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pxa2xx_i2s_init(void)
+{
+ clk_i2s = ERR_PTR(-ENOENT);
+ return platform_driver_register(&pxa2xx_i2s_driver);
+}
+
+static void __exit pxa2xx_i2s_exit(void)
+{
+ platform_driver_unregister(&pxa2xx_i2s_driver);
+}
+
+module_init(pxa2xx_i2s_init);
+module_exit(pxa2xx_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
+MODULE_DESCRIPTION("pxa2xx I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa2xx-i2s");
diff --git a/sound/soc/pxa/pxa2xx-i2s.h b/sound/soc/pxa/pxa2xx-i2s.h
new file mode 100644
index 00000000..070f3c60
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-i2s.h
@@ -0,0 +1,18 @@
+/*
+ * linux/sound/soc/pxa/pxa2xx-i2s.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PXA2XX_I2S_H
+#define _PXA2XX_I2S_H
+
+/* pxa2xx DAI ID's */
+#define PXA2XX_DAI_I2S 0
+
+/* I2S clock */
+#define PXA2XX_I2S_SYSCLK 0
+
+#endif
diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c
new file mode 100644
index 00000000..fab20a54
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-pcm.c
@@ -0,0 +1,156 @@
+/*
+ * linux/sound/arm/pxa2xx-pcm.c -- ALSA PCM interface for the Intel PXA2xx chip
+ *
+ * Author: Nicolas Pitre
+ * Created: Nov 30, 2004
+ * Copyright: (C) 2004 MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include "../../arm/pxa2xx-pcm.h"
+
+static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct pxa2xx_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct pxa2xx_pcm_dma_params *dma;
+ int ret;
+
+ dma = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!dma)
+ return 0;
+
+ /* this may get called several times by oss emulation
+ * with different params */
+ if (prtd->params == NULL) {
+ prtd->params = dma;
+ ret = pxa_request_dma(prtd->params->name, DMA_PRIO_LOW,
+ pxa2xx_pcm_dma_irq, substream);
+ if (ret < 0)
+ return ret;
+ prtd->dma_ch = ret;
+ } else if (prtd->params != dma) {
+ pxa_free_dma(prtd->dma_ch);
+ prtd->params = dma;
+ ret = pxa_request_dma(prtd->params->name, DMA_PRIO_LOW,
+ pxa2xx_pcm_dma_irq, substream);
+ if (ret < 0)
+ return ret;
+ prtd->dma_ch = ret;
+ }
+
+ return __pxa2xx_pcm_hw_params(substream, params);
+}
+
+static int pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct pxa2xx_runtime_data *prtd = substream->runtime->private_data;
+
+ __pxa2xx_pcm_hw_free(substream);
+
+ if (prtd->dma_ch >= 0) {
+ pxa_free_dma(prtd->dma_ch);
+ prtd->dma_ch = -1;
+ prtd->params = NULL;
+ }
+
+ return 0;
+}
+
+static struct snd_pcm_ops pxa2xx_pcm_ops = {
+ .open = __pxa2xx_pcm_open,
+ .close = __pxa2xx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = pxa2xx_pcm_hw_params,
+ .hw_free = pxa2xx_pcm_hw_free,
+ .prepare = __pxa2xx_pcm_prepare,
+ .trigger = pxa2xx_pcm_trigger,
+ .pointer = pxa2xx_pcm_pointer,
+ .mmap = pxa2xx_pcm_mmap,
+};
+
+static u64 pxa2xx_pcm_dmamask = DMA_BIT_MASK(32);
+
+static int pxa2xx_soc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &pxa2xx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ ret = pxa2xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ ret = pxa2xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+static struct snd_soc_platform_driver pxa2xx_soc_platform = {
+ .ops = &pxa2xx_pcm_ops,
+ .pcm_new = pxa2xx_soc_pcm_new,
+ .pcm_free = pxa2xx_pcm_free_dma_buffers,
+};
+
+static int __devinit pxa2xx_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &pxa2xx_soc_platform);
+}
+
+static int __devexit pxa2xx_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver pxa_pcm_driver = {
+ .driver = {
+ .name = "pxa-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = pxa2xx_soc_platform_probe,
+ .remove = __devexit_p(pxa2xx_soc_platform_remove),
+};
+
+static int __init snd_pxa_pcm_init(void)
+{
+ return platform_driver_register(&pxa_pcm_driver);
+}
+module_init(snd_pxa_pcm_init);
+
+static void __exit snd_pxa_pcm_exit(void)
+{
+ platform_driver_unregister(&pxa_pcm_driver);
+}
+module_exit(snd_pxa_pcm_exit);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/raumfeld.c b/sound/soc/pxa/raumfeld.c
new file mode 100644
index 00000000..1a591f1e
--- /dev/null
+++ b/sound/soc/pxa/raumfeld.c
@@ -0,0 +1,335 @@
+/*
+ * raumfeld_audio.c -- SoC audio for Raumfeld audio devices
+ *
+ * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
+ *
+ * based on code from:
+ *
+ * Wolfson Microelectronics PLC.
+ * Openedhand Ltd.
+ * Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+
+#include "pxa-ssp.h"
+
+#define GPIO_SPDIF_RESET (38)
+#define GPIO_MCLK_RESET (111)
+#define GPIO_CODEC_RESET (120)
+
+static struct i2c_client *max9486_client;
+static struct i2c_board_info max9486_hwmon_info = {
+ I2C_BOARD_INFO("max9485", 0x63),
+};
+
+#define MAX9485_MCLK_FREQ_112896 0x22
+#define MAX9485_MCLK_FREQ_122880 0x23
+#define MAX9485_MCLK_FREQ_225792 0x32
+#define MAX9485_MCLK_FREQ_245760 0x33
+
+static void set_max9485_clk(char clk)
+{
+ i2c_master_send(max9486_client, &clk, 1);
+}
+
+static void raumfeld_enable_audio(bool en)
+{
+ if (en) {
+ gpio_set_value(GPIO_MCLK_RESET, 1);
+
+ /* wait some time to let the clocks become stable */
+ msleep(100);
+
+ gpio_set_value(GPIO_SPDIF_RESET, 1);
+ gpio_set_value(GPIO_CODEC_RESET, 1);
+ } else {
+ gpio_set_value(GPIO_MCLK_RESET, 0);
+ gpio_set_value(GPIO_SPDIF_RESET, 0);
+ gpio_set_value(GPIO_CODEC_RESET, 0);
+ }
+}
+
+/* CS4270 */
+static int raumfeld_cs4270_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+ /* set freq to 0 to enable all possible codec sample rates */
+ return snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0);
+}
+
+static void raumfeld_cs4270_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+ /* set freq to 0 to enable all possible codec sample rates */
+ snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0);
+}
+
+static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int fmt, clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 44100:
+ set_max9485_clk(MAX9485_MCLK_FREQ_112896);
+ clk = 11289600;
+ break;
+ case 48000:
+ set_max9485_clk(MAX9485_MCLK_FREQ_122880);
+ clk = 12288000;
+ break;
+ case 88200:
+ set_max9485_clk(MAX9485_MCLK_FREQ_225792);
+ clk = 22579200;
+ break;
+ case 96000:
+ set_max9485_clk(MAX9485_MCLK_FREQ_245760);
+ clk = 24576000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* setup the CODEC DAI */
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, 0);
+ if (ret < 0)
+ return ret;
+
+ /* setup the CPU DAI */
+ ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, clk);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, PXA_SSP_DIV_SCR, 4);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops raumfeld_cs4270_ops = {
+ .startup = raumfeld_cs4270_startup,
+ .shutdown = raumfeld_cs4270_shutdown,
+ .hw_params = raumfeld_cs4270_hw_params,
+};
+
+static int raumfeld_analog_suspend(struct snd_soc_card *card)
+{
+ raumfeld_enable_audio(false);
+ return 0;
+}
+
+static int raumfeld_analog_resume(struct snd_soc_card *card)
+{
+ raumfeld_enable_audio(true);
+ return 0;
+}
+
+/* AK4104 */
+
+static int raumfeld_ak4104_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int fmt, ret = 0, clk = 0;
+
+ switch (params_rate(params)) {
+ case 44100:
+ set_max9485_clk(MAX9485_MCLK_FREQ_112896);
+ clk = 11289600;
+ break;
+ case 48000:
+ set_max9485_clk(MAX9485_MCLK_FREQ_122880);
+ clk = 12288000;
+ break;
+ case 88200:
+ set_max9485_clk(MAX9485_MCLK_FREQ_225792);
+ clk = 22579200;
+ break;
+ case 96000:
+ set_max9485_clk(MAX9485_MCLK_FREQ_245760);
+ clk = 24576000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
+
+ /* setup the CODEC DAI */
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* setup the CPU DAI */
+ ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, clk);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, PXA_SSP_DIV_SCR, 4);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops raumfeld_ak4104_ops = {
+ .hw_params = raumfeld_ak4104_hw_params,
+};
+
+#define DAI_LINK_CS4270 \
+{ \
+ .name = "CS4270", \
+ .stream_name = "CS4270", \
+ .cpu_dai_name = "pxa-ssp-dai.0", \
+ .platform_name = "pxa-pcm-audio", \
+ .codec_dai_name = "cs4270-hifi", \
+ .codec_name = "cs4270-codec.0-0048", \
+ .ops = &raumfeld_cs4270_ops, \
+}
+
+#define DAI_LINK_AK4104 \
+{ \
+ .name = "ak4104", \
+ .stream_name = "Playback", \
+ .cpu_dai_name = "pxa-ssp-dai.1", \
+ .codec_dai_name = "ak4104-hifi", \
+ .platform_name = "pxa-pcm-audio", \
+ .ops = &raumfeld_ak4104_ops, \
+ .codec_name = "spi0.0", \
+}
+
+static struct snd_soc_dai_link snd_soc_raumfeld_connector_dai[] =
+{
+ DAI_LINK_CS4270,
+ DAI_LINK_AK4104,
+};
+
+static struct snd_soc_dai_link snd_soc_raumfeld_speaker_dai[] =
+{
+ DAI_LINK_CS4270,
+};
+
+static struct snd_soc_card snd_soc_raumfeld_connector = {
+ .name = "Raumfeld Connector",
+ .dai_link = snd_soc_raumfeld_connector_dai,
+ .num_links = ARRAY_SIZE(snd_soc_raumfeld_connector_dai),
+ .suspend_post = raumfeld_analog_suspend,
+ .resume_pre = raumfeld_analog_resume,
+};
+
+static struct snd_soc_card snd_soc_raumfeld_speaker = {
+ .name = "Raumfeld Speaker",
+ .dai_link = snd_soc_raumfeld_speaker_dai,
+ .num_links = ARRAY_SIZE(snd_soc_raumfeld_speaker_dai),
+ .suspend_post = raumfeld_analog_suspend,
+ .resume_pre = raumfeld_analog_resume,
+};
+
+static struct platform_device *raumfeld_audio_device;
+
+static int __init raumfeld_audio_init(void)
+{
+ int ret;
+
+ if (!machine_is_raumfeld_speaker() &&
+ !machine_is_raumfeld_connector())
+ return 0;
+
+ max9486_client = i2c_new_device(i2c_get_adapter(0),
+ &max9486_hwmon_info);
+
+ if (!max9486_client)
+ return -ENOMEM;
+
+ set_max9485_clk(MAX9485_MCLK_FREQ_122880);
+
+ /* Register analog device */
+ raumfeld_audio_device = platform_device_alloc("soc-audio", 0);
+ if (!raumfeld_audio_device)
+ return -ENOMEM;
+
+ if (machine_is_raumfeld_speaker())
+ platform_set_drvdata(raumfeld_audio_device,
+ &snd_soc_raumfeld_speaker);
+
+ if (machine_is_raumfeld_connector())
+ platform_set_drvdata(raumfeld_audio_device,
+ &snd_soc_raumfeld_connector);
+
+ ret = platform_device_add(raumfeld_audio_device);
+ if (ret < 0)
+ return ret;
+
+ raumfeld_enable_audio(true);
+ return 0;
+}
+
+static void __exit raumfeld_audio_exit(void)
+{
+ raumfeld_enable_audio(false);
+
+ platform_device_unregister(raumfeld_audio_device);
+
+ i2c_unregister_device(max9486_client);
+
+ gpio_free(GPIO_MCLK_RESET);
+ gpio_free(GPIO_CODEC_RESET);
+ gpio_free(GPIO_SPDIF_RESET);
+}
+
+module_init(raumfeld_audio_init);
+module_exit(raumfeld_audio_exit);
+
+/* Module information */
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("Raumfeld audio SoC");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/saarb.c b/sound/soc/pxa/saarb.c
new file mode 100644
index 00000000..9595189f
--- /dev/null
+++ b/sound/soc/pxa/saarb.c
@@ -0,0 +1,200 @@
+/*
+ * saarb.c -- SoC audio for saarb
+ *
+ * Copyright (C) 2010 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/88pm860x-codec.h"
+#include "pxa-ssp.h"
+
+static int saarb_pm860x_init(struct snd_soc_pcm_runtime *rtd);
+
+static struct platform_device *saarb_snd_device;
+
+static struct snd_soc_jack hs_jack, mic_jack;
+
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ { .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE, },
+};
+
+static struct snd_soc_jack_pin mic_jack_pins[] = {
+ { .pin = "Headset Mic 2", .mask = SND_JACK_MICROPHONE, },
+};
+
+/* saarb machine dapm widgets */
+static const struct snd_soc_dapm_widget saarb_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Stereophone", NULL),
+ SND_SOC_DAPM_LINE("Lineout Out 1", NULL),
+ SND_SOC_DAPM_LINE("Lineout Out 2", NULL),
+ SND_SOC_DAPM_SPK("Ext Speaker", NULL),
+ SND_SOC_DAPM_MIC("Ext Mic 1", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Ext Mic 3", NULL),
+};
+
+/* saarb machine audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headset Stereophone", NULL, "HS1"},
+ {"Headset Stereophone", NULL, "HS2"},
+
+ {"Ext Speaker", NULL, "LSP"},
+ {"Ext Speaker", NULL, "LSN"},
+
+ {"Lineout Out 1", NULL, "LINEOUT1"},
+ {"Lineout Out 2", NULL, "LINEOUT2"},
+
+ {"MIC1P", NULL, "Mic1 Bias"},
+ {"MIC1N", NULL, "Mic1 Bias"},
+ {"Mic1 Bias", NULL, "Ext Mic 1"},
+
+ {"MIC2P", NULL, "Mic1 Bias"},
+ {"MIC2N", NULL, "Mic1 Bias"},
+ {"Mic1 Bias", NULL, "Headset Mic 2"},
+
+ {"MIC3P", NULL, "Mic3 Bias"},
+ {"MIC3N", NULL, "Mic3 Bias"},
+ {"Mic3 Bias", NULL, "Ext Mic 3"},
+};
+
+static int saarb_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int width = snd_pcm_format_physical_width(params_format(params));
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_NET_PLL, 0,
+ PM860X_CLK_DIR_OUT);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 0, PM860X_CLK_DIR_OUT);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 3, 3, 2, width);
+
+ return ret;
+}
+
+static struct snd_soc_ops saarb_i2s_ops = {
+ .hw_params = saarb_i2s_hw_params,
+};
+
+static struct snd_soc_dai_link saarb_dai[] = {
+ {
+ .name = "88PM860x I2S",
+ .stream_name = "I2S Audio",
+ .cpu_dai_name = "pxa-ssp-dai.1",
+ .codec_dai_name = "88pm860x-i2s",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "88pm860x-codec",
+ .init = saarb_pm860x_init,
+ .ops = &saarb_i2s_ops,
+ },
+};
+
+static struct snd_soc_card snd_soc_card_saarb = {
+ .name = "Saarb",
+ .dai_link = saarb_dai,
+ .num_links = ARRAY_SIZE(saarb_dai),
+};
+
+static int saarb_pm860x_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ snd_soc_dapm_new_controls(dapm, saarb_dapm_widgets,
+ ARRAY_SIZE(saarb_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ /* connected pins */
+ snd_soc_dapm_enable_pin(dapm, "Ext Speaker");
+ snd_soc_dapm_enable_pin(dapm, "Ext Mic 1");
+ snd_soc_dapm_enable_pin(dapm, "Ext Mic 3");
+ snd_soc_dapm_disable_pin(dapm, "Headset Mic 2");
+ snd_soc_dapm_disable_pin(dapm, "Headset Stereophone");
+
+ ret = snd_soc_dapm_sync(dapm);
+ if (ret)
+ return ret;
+
+ /* Headset jack detection */
+ snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE
+ | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2,
+ &hs_jack);
+ snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+ hs_jack_pins);
+ snd_soc_jack_new(codec, "Microphone Jack", SND_JACK_MICROPHONE,
+ &mic_jack);
+ snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins),
+ mic_jack_pins);
+
+ /* headphone, microphone detection & headset short detection */
+ pm860x_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADPHONE,
+ SND_JACK_BTN_0, SND_JACK_BTN_1, SND_JACK_BTN_2);
+ pm860x_mic_jack_detect(codec, &hs_jack, SND_JACK_MICROPHONE);
+ return 0;
+}
+
+static int __init saarb_init(void)
+{
+ int ret;
+
+ if (!machine_is_saarb())
+ return -ENODEV;
+ saarb_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!saarb_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(saarb_snd_device, &snd_soc_card_saarb);
+
+ ret = platform_device_add(saarb_snd_device);
+ if (ret)
+ platform_device_put(saarb_snd_device);
+
+ return ret;
+}
+
+static void __exit saarb_exit(void)
+{
+ platform_device_unregister(saarb_snd_device);
+}
+
+module_init(saarb_init);
+module_exit(saarb_exit);
+
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
+MODULE_DESCRIPTION("ALSA SoC 88PM860x Saarb");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c
new file mode 100644
index 00000000..b253d864
--- /dev/null
+++ b/sound/soc/pxa/spitz.c
@@ -0,0 +1,382 @@
+/*
+ * spitz.c -- SoC audio for Sharp SL-Cxx00 models Spitz, Borzoi and Akita
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/spitz.h>
+#include "../codecs/wm8750.h"
+#include "pxa2xx-i2s.h"
+
+#define SPITZ_HP 0
+#define SPITZ_MIC 1
+#define SPITZ_LINE 2
+#define SPITZ_HEADSET 3
+#define SPITZ_HP_OFF 4
+#define SPITZ_SPK_ON 0
+#define SPITZ_SPK_OFF 1
+
+ /* audio clock in Hz - rounded from 12.235MHz */
+#define SPITZ_AUDIO_CLOCK 12288000
+
+static int spitz_jack_func;
+static int spitz_spk_func;
+static int spitz_mic_gpio;
+
+static void spitz_ext_control(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ if (spitz_spk_func == SPITZ_SPK_ON)
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Ext Spk");
+
+ /* set up jack connection */
+ switch (spitz_jack_func) {
+ case SPITZ_HP:
+ /* enable and unmute hp jack, disable mic bias */
+ snd_soc_dapm_disable_pin(dapm, "Headset Jack");
+ snd_soc_dapm_disable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin(dapm, "Line Jack");
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ gpio_set_value(SPITZ_GPIO_MUTE_L, 1);
+ gpio_set_value(SPITZ_GPIO_MUTE_R, 1);
+ break;
+ case SPITZ_MIC:
+ /* enable mic jack and bias, mute hp */
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headset Jack");
+ snd_soc_dapm_disable_pin(dapm, "Line Jack");
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+ gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+ gpio_set_value(SPITZ_GPIO_MUTE_R, 0);
+ break;
+ case SPITZ_LINE:
+ /* enable line jack, disable mic bias and mute hp */
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headset Jack");
+ snd_soc_dapm_disable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_enable_pin(dapm, "Line Jack");
+ gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+ gpio_set_value(SPITZ_GPIO_MUTE_R, 0);
+ break;
+ case SPITZ_HEADSET:
+ /* enable and unmute headset jack enable mic bias, mute L hp */
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin(dapm, "Line Jack");
+ snd_soc_dapm_enable_pin(dapm, "Headset Jack");
+ gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+ gpio_set_value(SPITZ_GPIO_MUTE_R, 1);
+ break;
+ case SPITZ_HP_OFF:
+
+ /* jack removed, everything off */
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headset Jack");
+ snd_soc_dapm_disable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin(dapm, "Line Jack");
+ gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+ gpio_set_value(SPITZ_GPIO_MUTE_R, 0);
+ break;
+ }
+ snd_soc_dapm_sync(dapm);
+}
+
+static int spitz_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ mutex_lock(&codec->mutex);
+
+ /* check the jack status at stream startup */
+ spitz_ext_control(codec);
+
+ mutex_unlock(&codec->mutex);
+
+ return 0;
+}
+
+static int spitz_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as input (unused) */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops spitz_ops = {
+ .startup = spitz_startup,
+ .hw_params = spitz_hw_params,
+};
+
+static int spitz_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = spitz_jack_func;
+ return 0;
+}
+
+static int spitz_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (spitz_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ spitz_jack_func = ucontrol->value.integer.value[0];
+ spitz_ext_control(codec);
+ return 1;
+}
+
+static int spitz_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = spitz_spk_func;
+ return 0;
+}
+
+static int spitz_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (spitz_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ spitz_spk_func = ucontrol->value.integer.value[0];
+ spitz_ext_control(codec);
+ return 1;
+}
+
+static int spitz_mic_bias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value_cansleep(spitz_mic_gpio, SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+/* spitz machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", spitz_mic_bias),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_LINE("Line Jack", NULL),
+
+ /* headset is a mic and mono headphone */
+ SND_SOC_DAPM_HP("Headset Jack", NULL),
+};
+
+/* Spitz machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headphone connected to LOUT1, ROUT1 */
+ {"Headphone Jack", NULL, "LOUT1"},
+ {"Headphone Jack", NULL, "ROUT1"},
+
+ /* headset connected to ROUT1 and LINPUT1 with bias (def below) */
+ {"Headset Jack", NULL, "ROUT1"},
+
+ /* ext speaker connected to LOUT2, ROUT2 */
+ {"Ext Spk", NULL , "ROUT2"},
+ {"Ext Spk", NULL , "LOUT2"},
+
+ /* mic is connected to input 1 - with bias */
+ {"LINPUT1", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Mic Jack"},
+
+ /* line is connected to input 1 - no bias */
+ {"LINPUT1", NULL, "Line Jack"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
+ "Off"};
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum spitz_enum[] = {
+ SOC_ENUM_SINGLE_EXT(5, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8750_spitz_controls[] = {
+ SOC_ENUM_EXT("Jack Function", spitz_enum[0], spitz_get_jack,
+ spitz_set_jack),
+ SOC_ENUM_EXT("Speaker Function", spitz_enum[1], spitz_get_spk,
+ spitz_set_spk),
+};
+
+/*
+ * Logic for a wm8750 as connected on a Sharp SL-Cxx00 Device
+ */
+static int spitz_wm8750_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ /* NC codec pins */
+ snd_soc_dapm_nc_pin(dapm, "RINPUT1");
+ snd_soc_dapm_nc_pin(dapm, "LINPUT2");
+ snd_soc_dapm_nc_pin(dapm, "RINPUT2");
+ snd_soc_dapm_nc_pin(dapm, "LINPUT3");
+ snd_soc_dapm_nc_pin(dapm, "RINPUT3");
+ snd_soc_dapm_nc_pin(dapm, "OUT3");
+ snd_soc_dapm_nc_pin(dapm, "MONO1");
+
+ /* Add spitz specific controls */
+ err = snd_soc_add_controls(codec, wm8750_spitz_controls,
+ ARRAY_SIZE(wm8750_spitz_controls));
+ if (err < 0)
+ return err;
+
+ /* Add spitz specific widgets */
+ snd_soc_dapm_new_controls(dapm, wm8750_dapm_widgets,
+ ARRAY_SIZE(wm8750_dapm_widgets));
+
+ /* Set up spitz specific audio paths */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(dapm);
+ return 0;
+}
+
+/* spitz digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link spitz_dai = {
+ .name = "wm8750",
+ .stream_name = "WM8750",
+ .cpu_dai_name = "pxa2xx-i2s",
+ .codec_dai_name = "wm8750-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm8750-codec.0-001b",
+ .init = spitz_wm8750_init,
+ .ops = &spitz_ops,
+};
+
+/* spitz audio machine driver */
+static struct snd_soc_card snd_soc_spitz = {
+ .name = "Spitz",
+ .dai_link = &spitz_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *spitz_snd_device;
+
+static int __init spitz_init(void)
+{
+ int ret;
+
+ if (!(machine_is_spitz() || machine_is_borzoi() || machine_is_akita()))
+ return -ENODEV;
+
+ if (machine_is_borzoi() || machine_is_spitz())
+ spitz_mic_gpio = SPITZ_GPIO_MIC_BIAS;
+ else
+ spitz_mic_gpio = AKITA_GPIO_MIC_BIAS;
+
+ ret = gpio_request(spitz_mic_gpio, "MIC GPIO");
+ if (ret)
+ goto err1;
+
+ ret = gpio_direction_output(spitz_mic_gpio, 0);
+ if (ret)
+ goto err2;
+
+ spitz_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!spitz_snd_device) {
+ ret = -ENOMEM;
+ goto err2;
+ }
+
+ platform_set_drvdata(spitz_snd_device, &snd_soc_spitz);
+
+ ret = platform_device_add(spitz_snd_device);
+ if (ret)
+ goto err3;
+
+ return 0;
+
+err3:
+ platform_device_put(spitz_snd_device);
+err2:
+ gpio_free(spitz_mic_gpio);
+err1:
+ return ret;
+}
+
+static void __exit spitz_exit(void)
+{
+ platform_device_unregister(spitz_snd_device);
+ gpio_free(spitz_mic_gpio);
+}
+
+module_init(spitz_init);
+module_exit(spitz_exit);
+
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Spitz");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/tavorevb3.c b/sound/soc/pxa/tavorevb3.c
new file mode 100644
index 00000000..f881f65e
--- /dev/null
+++ b/sound/soc/pxa/tavorevb3.c
@@ -0,0 +1,200 @@
+/*
+ * tavorevb3.c -- SoC audio for Tavor EVB3
+ *
+ * Copyright (C) 2010 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/88pm860x-codec.h"
+#include "pxa-ssp.h"
+
+static int evb3_pm860x_init(struct snd_soc_pcm_runtime *rtd);
+
+static struct platform_device *evb3_snd_device;
+
+static struct snd_soc_jack hs_jack, mic_jack;
+
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ { .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE, },
+};
+
+static struct snd_soc_jack_pin mic_jack_pins[] = {
+ { .pin = "Headset Mic 2", .mask = SND_JACK_MICROPHONE, },
+};
+
+/* tavorevb3 machine dapm widgets */
+static const struct snd_soc_dapm_widget evb3_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+ SND_SOC_DAPM_LINE("Lineout Out 1", NULL),
+ SND_SOC_DAPM_LINE("Lineout Out 2", NULL),
+ SND_SOC_DAPM_SPK("Ext Speaker", NULL),
+ SND_SOC_DAPM_MIC("Ext Mic 1", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic 2", NULL),
+ SND_SOC_DAPM_MIC("Ext Mic 3", NULL),
+};
+
+/* tavorevb3 machine audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headset Stereophone", NULL, "HS1"},
+ {"Headset Stereophone", NULL, "HS2"},
+
+ {"Ext Speaker", NULL, "LSP"},
+ {"Ext Speaker", NULL, "LSN"},
+
+ {"Lineout Out 1", NULL, "LINEOUT1"},
+ {"Lineout Out 2", NULL, "LINEOUT2"},
+
+ {"MIC1P", NULL, "Mic1 Bias"},
+ {"MIC1N", NULL, "Mic1 Bias"},
+ {"Mic1 Bias", NULL, "Ext Mic 1"},
+
+ {"MIC2P", NULL, "Mic1 Bias"},
+ {"MIC2N", NULL, "Mic1 Bias"},
+ {"Mic1 Bias", NULL, "Headset Mic 2"},
+
+ {"MIC3P", NULL, "Mic3 Bias"},
+ {"MIC3N", NULL, "Mic3 Bias"},
+ {"Mic3 Bias", NULL, "Ext Mic 3"},
+};
+
+static int evb3_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int width = snd_pcm_format_physical_width(params_format(params));
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_NET_PLL, 0,
+ PM860X_CLK_DIR_OUT);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, 0, PM860X_CLK_DIR_OUT);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 3, 3, 2, width);
+ return ret;
+}
+
+static struct snd_soc_ops evb3_i2s_ops = {
+ .hw_params = evb3_i2s_hw_params,
+};
+
+static struct snd_soc_dai_link evb3_dai[] = {
+ {
+ .name = "88PM860x I2S",
+ .stream_name = "I2S Audio",
+ .cpu_dai_name = "pxa-ssp-dai.1",
+ .codec_dai_name = "88pm860x-i2s",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "88pm860x-codec",
+ .init = evb3_pm860x_init,
+ .ops = &evb3_i2s_ops,
+ },
+};
+
+static struct snd_soc_card snd_soc_card_evb3 = {
+ .name = "Tavor EVB3",
+ .dai_link = evb3_dai,
+ .num_links = ARRAY_SIZE(evb3_dai),
+};
+
+static int evb3_pm860x_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ snd_soc_dapm_new_controls(dapm, evb3_dapm_widgets,
+ ARRAY_SIZE(evb3_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ /* connected pins */
+ snd_soc_dapm_enable_pin(dapm, "Ext Speaker");
+ snd_soc_dapm_enable_pin(dapm, "Ext Mic 1");
+ snd_soc_dapm_enable_pin(dapm, "Ext Mic 3");
+ snd_soc_dapm_disable_pin(dapm, "Headset Mic 2");
+ snd_soc_dapm_disable_pin(dapm, "Headset Stereophone");
+
+ ret = snd_soc_dapm_sync(dapm);
+ if (ret)
+ return ret;
+
+ /* Headset jack detection */
+ snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE
+ | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2,
+ &hs_jack);
+ snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+ hs_jack_pins);
+ snd_soc_jack_new(codec, "Microphone Jack", SND_JACK_MICROPHONE,
+ &mic_jack);
+ snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins),
+ mic_jack_pins);
+
+ /* headphone, microphone detection & headset short detection */
+ pm860x_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADPHONE,
+ SND_JACK_BTN_0, SND_JACK_BTN_1, SND_JACK_BTN_2);
+ pm860x_mic_jack_detect(codec, &hs_jack, SND_JACK_MICROPHONE);
+ return 0;
+}
+
+static int __init tavorevb3_init(void)
+{
+ int ret;
+
+ if (!machine_is_tavorevb3())
+ return -ENODEV;
+ evb3_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!evb3_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(evb3_snd_device, &snd_soc_card_evb3);
+
+ ret = platform_device_add(evb3_snd_device);
+ if (ret)
+ platform_device_put(evb3_snd_device);
+
+ return ret;
+}
+
+static void __exit tavorevb3_exit(void)
+{
+ platform_device_unregister(evb3_snd_device);
+}
+
+module_init(tavorevb3_init);
+module_exit(tavorevb3_exit);
+
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
+MODULE_DESCRIPTION("ALSA SoC 88PM860x Tavor EVB3");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/tosa.c b/sound/soc/pxa/tosa.c
new file mode 100644
index 00000000..9a235136
--- /dev/null
+++ b/sound/soc/pxa/tosa.c
@@ -0,0 +1,306 @@
+/*
+ * tosa.c -- SoC audio for Tosa
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.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 of the License, or (at your
+ * option) any later version.
+ *
+ * GPIO's
+ * 1 - Jack Insertion
+ * 5 - Hookswitch (headset answer/hang up switch)
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/tosa.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-ac97.h"
+
+static struct snd_soc_card tosa;
+
+#define TOSA_HP 0
+#define TOSA_MIC_INT 1
+#define TOSA_HEADSET 2
+#define TOSA_HP_OFF 3
+#define TOSA_SPK_ON 0
+#define TOSA_SPK_OFF 1
+
+static int tosa_jack_func;
+static int tosa_spk_func;
+
+static void tosa_ext_control(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* set up jack connection */
+ switch (tosa_jack_func) {
+ case TOSA_HP:
+ snd_soc_dapm_disable_pin(dapm, "Mic (Internal)");
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headset Jack");
+ break;
+ case TOSA_MIC_INT:
+ snd_soc_dapm_enable_pin(dapm, "Mic (Internal)");
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin(dapm, "Headset Jack");
+ break;
+ case TOSA_HEADSET:
+ snd_soc_dapm_disable_pin(dapm, "Mic (Internal)");
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Headset Jack");
+ break;
+ }
+
+ if (tosa_spk_func == TOSA_SPK_ON)
+ snd_soc_dapm_enable_pin(dapm, "Speaker");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Speaker");
+
+ snd_soc_dapm_sync(dapm);
+}
+
+static int tosa_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ mutex_lock(&codec->mutex);
+
+ /* check the jack status at stream startup */
+ tosa_ext_control(codec);
+
+ mutex_unlock(&codec->mutex);
+
+ return 0;
+}
+
+static struct snd_soc_ops tosa_ops = {
+ .startup = tosa_startup,
+};
+
+static int tosa_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = tosa_jack_func;
+ return 0;
+}
+
+static int tosa_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (tosa_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ tosa_jack_func = ucontrol->value.integer.value[0];
+ tosa_ext_control(codec);
+ return 1;
+}
+
+static int tosa_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = tosa_spk_func;
+ return 0;
+}
+
+static int tosa_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (tosa_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ tosa_spk_func = ucontrol->value.integer.value[0];
+ tosa_ext_control(codec);
+ return 1;
+}
+
+/* tosa dapm event handlers */
+static int tosa_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 :0);
+ return 0;
+}
+
+/* tosa machine dapm widgets */
+static const struct snd_soc_dapm_widget tosa_dapm_widgets[] = {
+SND_SOC_DAPM_HP("Headphone Jack", tosa_hp_event),
+SND_SOC_DAPM_HP("Headset Jack", NULL),
+SND_SOC_DAPM_MIC("Mic (Internal)", NULL),
+SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+/* tosa audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headphone connected to HPOUTL, HPOUTR */
+ {"Headphone Jack", NULL, "HPOUTL"},
+ {"Headphone Jack", NULL, "HPOUTR"},
+
+ /* ext speaker connected to LOUT2, ROUT2 */
+ {"Speaker", NULL, "LOUT2"},
+ {"Speaker", NULL, "ROUT2"},
+
+ /* internal mic is connected to mic1, mic2 differential - with bias */
+ {"MIC1", NULL, "Mic Bias"},
+ {"MIC2", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Mic (Internal)"},
+
+ /* headset is connected to HPOUTR, and LINEINR with bias */
+ {"Headset Jack", NULL, "HPOUTR"},
+ {"LINEINR", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Headset Jack"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
+ "Off"};
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum tosa_enum[] = {
+ SOC_ENUM_SINGLE_EXT(5, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new tosa_controls[] = {
+ SOC_ENUM_EXT("Jack Function", tosa_enum[0], tosa_get_jack,
+ tosa_set_jack),
+ SOC_ENUM_EXT("Speaker Function", tosa_enum[1], tosa_get_spk,
+ tosa_set_spk),
+};
+
+static int tosa_ac97_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ snd_soc_dapm_nc_pin(dapm, "OUT3");
+ snd_soc_dapm_nc_pin(dapm, "MONOOUT");
+
+ /* add tosa specific controls */
+ err = snd_soc_add_controls(codec, tosa_controls,
+ ARRAY_SIZE(tosa_controls));
+ if (err < 0)
+ return err;
+
+ /* add tosa specific widgets */
+ snd_soc_dapm_new_controls(dapm, tosa_dapm_widgets,
+ ARRAY_SIZE(tosa_dapm_widgets));
+
+ /* set up tosa specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(dapm);
+ return 0;
+}
+
+static struct snd_soc_dai_link tosa_dai[] = {
+{
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "pxa2xx-ac97",
+ .codec_dai_name = "wm9712-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm9712-codec",
+ .init = tosa_ac97_init,
+ .ops = &tosa_ops,
+},
+{
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .cpu_dai_name = "pxa2xx-ac97-aux",
+ .codec_dai_name = "wm9712-aux",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm9712-codec",
+ .ops = &tosa_ops,
+},
+};
+
+static int tosa_probe(struct snd_soc_card *card)
+{
+ int ret;
+
+ ret = gpio_request(TOSA_GPIO_L_MUTE, "Headphone Jack");
+ if (ret)
+ return ret;
+ ret = gpio_direction_output(TOSA_GPIO_L_MUTE, 0);
+ if (ret)
+ gpio_free(TOSA_GPIO_L_MUTE);
+
+ return ret;
+}
+
+static int tosa_remove(struct snd_soc_card *card)
+{
+ gpio_free(TOSA_GPIO_L_MUTE);
+ return 0;
+}
+
+static struct snd_soc_card tosa = {
+ .name = "Tosa",
+ .dai_link = tosa_dai,
+ .num_links = ARRAY_SIZE(tosa_dai),
+ .probe = tosa_probe,
+ .remove = tosa_remove,
+};
+
+static struct platform_device *tosa_snd_device;
+
+static int __init tosa_init(void)
+{
+ int ret;
+
+ if (!machine_is_tosa())
+ return -ENODEV;
+
+ tosa_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!tosa_snd_device) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ platform_set_drvdata(tosa_snd_device, &tosa);
+ ret = platform_device_add(tosa_snd_device);
+
+ if (!ret)
+ return 0;
+
+ platform_device_put(tosa_snd_device);
+
+err_alloc:
+ return ret;
+}
+
+static void __exit tosa_exit(void)
+{
+ platform_device_unregister(tosa_snd_device);
+}
+
+module_init(tosa_init);
+module_exit(tosa_exit);
+
+/* Module information */
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Tosa");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/z2.c b/sound/soc/pxa/z2.c
new file mode 100644
index 00000000..d69d9fc3
--- /dev/null
+++ b/sound/soc/pxa/z2.c
@@ -0,0 +1,246 @@
+/*
+ * linux/sound/soc/pxa/z2.c
+ *
+ * SoC Audio driver for Aeronix Zipit Z2
+ *
+ * Copyright (C) 2009 Ken McGuire <kenm@desertweyr.com>
+ * Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/audio.h>
+#include <mach/z2.h>
+
+#include "../codecs/wm8750.h"
+#include "pxa2xx-i2s.h"
+
+static struct snd_soc_card snd_soc_z2;
+
+static int z2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as input (unused) */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_jack hs_jack;
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Ext Spk",
+ .mask = SND_JACK_HEADPHONE,
+ .invert = 1
+ },
+};
+
+/* Headset jack detection gpios */
+static struct snd_soc_jack_gpio hs_jack_gpios[] = {
+ {
+ .gpio = GPIO37_ZIPITZ2_HEADSET_DETECT,
+ .name = "hsdet-gpio",
+ .report = SND_JACK_HEADSET,
+ .debounce_time = 200,
+ .invert = 1,
+ },
+};
+
+/* z2 machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+
+ /* headset is a mic and mono headphone */
+ SND_SOC_DAPM_HP("Headset Jack", NULL),
+};
+
+/* Z2 machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headphone connected to LOUT1, ROUT1 */
+ {"Headphone Jack", NULL, "LOUT1"},
+ {"Headphone Jack", NULL, "ROUT1"},
+
+ /* ext speaker connected to LOUT2, ROUT2 */
+ {"Ext Spk", NULL , "ROUT2"},
+ {"Ext Spk", NULL , "LOUT2"},
+
+ /* mic is connected to R input 2 - with bias */
+ {"RINPUT2", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Mic Jack"},
+};
+
+/*
+ * Logic for a wm8750 as connected on a Z2 Device
+ */
+static int z2_wm8750_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ /* NC codec pins */
+ snd_soc_dapm_disable_pin(dapm, "LINPUT3");
+ snd_soc_dapm_disable_pin(dapm, "RINPUT3");
+ snd_soc_dapm_disable_pin(dapm, "OUT3");
+ snd_soc_dapm_disable_pin(dapm, "MONO1");
+
+ /* Add z2 specific widgets */
+ snd_soc_dapm_new_controls(dapm, wm8750_dapm_widgets,
+ ARRAY_SIZE(wm8750_dapm_widgets));
+
+ /* Set up z2 specific audio paths */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ ret = snd_soc_dapm_sync(dapm);
+ if (ret)
+ goto err;
+
+ /* Jack detection API stuff */
+ ret = snd_soc_jack_new(codec, "Headset Jack", SND_JACK_HEADSET,
+ &hs_jack);
+ if (ret)
+ goto err;
+
+ ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+ hs_jack_pins);
+ if (ret)
+ goto err;
+
+ ret = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios),
+ hs_jack_gpios);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ return ret;
+}
+
+static struct snd_soc_ops z2_ops = {
+ .hw_params = z2_hw_params,
+};
+
+/* z2 digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link z2_dai = {
+ .name = "wm8750",
+ .stream_name = "WM8750",
+ .cpu_dai_name = "pxa2xx-i2s",
+ .codec_dai_name = "wm8750-hifi",
+ .platform_name = "pxa-pcm-audio",
+ .codec_name = "wm8750-codec.0-001b",
+ .init = z2_wm8750_init,
+ .ops = &z2_ops,
+};
+
+/* z2 audio machine driver */
+static struct snd_soc_card snd_soc_z2 = {
+ .name = "Z2",
+ .dai_link = &z2_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *z2_snd_device;
+
+static int __init z2_init(void)
+{
+ int ret;
+
+ if (!machine_is_zipit2())
+ return -ENODEV;
+
+ z2_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!z2_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(z2_snd_device, &snd_soc_z2);
+ ret = platform_device_add(z2_snd_device);
+
+ if (ret)
+ platform_device_put(z2_snd_device);
+
+ return ret;
+}
+
+static void __exit z2_exit(void)
+{
+ platform_device_unregister(z2_snd_device);
+}
+
+module_init(z2_init);
+module_exit(z2_exit);
+
+MODULE_AUTHOR("Ken McGuire <kenm@desertweyr.com>, "
+ "Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC ZipitZ2");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/zylonite.c b/sound/soc/pxa/zylonite.c
new file mode 100644
index 00000000..2b8350b5
--- /dev/null
+++ b/sound/soc/pxa/zylonite.c
@@ -0,0 +1,291 @@
+/*
+ * zylonite.c -- SoC audio for Zylonite
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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 of the
+ * License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "../codecs/wm9713.h"
+#include "pxa2xx-ac97.h"
+#include "pxa-ssp.h"
+
+/*
+ * There is a physical switch SW15 on the board which changes the MCLK
+ * for the WM9713 between the standard AC97 master clock and the
+ * output of the CLK_POUT signal from the PXA.
+ */
+static int clk_pout;
+module_param(clk_pout, int, 0);
+MODULE_PARM_DESC(clk_pout, "Use CLK_POUT as WM9713 MCLK (SW15 on board).");
+
+static struct clk *pout;
+
+static struct snd_soc_card zylonite;
+
+static const struct snd_soc_dapm_widget zylonite_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Microphone", NULL),
+ SND_SOC_DAPM_MIC("Handset Microphone", NULL),
+ SND_SOC_DAPM_SPK("Multiactor", NULL),
+ SND_SOC_DAPM_SPK("Headset Earpiece", NULL),
+};
+
+/* Currently supported audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* Headphone output connected to HPL/HPR */
+ { "Headphone", NULL, "HPL" },
+ { "Headphone", NULL, "HPR" },
+
+ /* On-board earpiece */
+ { "Headset Earpiece", NULL, "OUT3" },
+
+ /* Headphone mic */
+ { "MIC2A", NULL, "Mic Bias" },
+ { "Mic Bias", NULL, "Headset Microphone" },
+
+ /* On-board mic */
+ { "MIC1", NULL, "Mic Bias" },
+ { "Mic Bias", NULL, "Handset Microphone" },
+
+ /* Multiactor differentially connected over SPKL/SPKR */
+ { "Multiactor", NULL, "SPKL" },
+ { "Multiactor", NULL, "SPKR" },
+};
+
+static int zylonite_wm9713_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ if (clk_pout)
+ snd_soc_dai_set_pll(rtd->codec_dai, 0, 0,
+ clk_get_rate(pout), 0);
+
+ snd_soc_dapm_new_controls(dapm, zylonite_dapm_widgets,
+ ARRAY_SIZE(zylonite_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ /* Static setup for now */
+ snd_soc_dapm_enable_pin(dapm, "Headphone");
+ snd_soc_dapm_enable_pin(dapm, "Headset Earpiece");
+
+ snd_soc_dapm_sync(dapm);
+ return 0;
+}
+
+static int zylonite_voice_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int pll_out = 0;
+ unsigned int wm9713_div = 0;
+ int ret = 0;
+ int rate = params_rate(params);
+ int width = snd_pcm_format_physical_width(params_format(params));
+
+ /* Only support ratios that we can generate neatly from the AC97
+ * based master clock - in particular, this excludes 44.1kHz.
+ * In most applications the voice DAC will be used for telephony
+ * data so multiples of 8kHz will be the common case.
+ */
+ switch (rate) {
+ case 8000:
+ wm9713_div = 12;
+ break;
+ case 16000:
+ wm9713_div = 6;
+ break;
+ case 48000:
+ wm9713_div = 2;
+ break;
+ default:
+ /* Don't support OSS emulation */
+ return -EINVAL;
+ }
+
+ /* Add 1 to the width for the leading clock cycle */
+ pll_out = rate * (width + 1) * 8;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_AUDIO, 0, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, pll_out);
+ if (ret < 0)
+ return ret;
+
+ if (clk_pout)
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM9713_PCMCLK_PLL_DIV,
+ WM9713_PCMDIV(wm9713_div));
+ else
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM9713_PCMCLK_DIV,
+ WM9713_PCMDIV(wm9713_div));
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops zylonite_voice_ops = {
+ .hw_params = zylonite_voice_hw_params,
+};
+
+static struct snd_soc_dai_link zylonite_dai[] = {
+{
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .codec_name = "wm9713-codec",
+ .platform_name = "pxa-pcm-audio",
+ .cpu_dai_name = "pxa2xx-ac97",
+ .codec_dai_name = "wm9713-hifi",
+ .init = zylonite_wm9713_init,
+},
+{
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .codec_name = "wm9713-codec",
+ .platform_name = "pxa-pcm-audio",
+ .cpu_dai_name = "pxa2xx-ac97-aux",
+ .codec_dai_name = "wm9713-aux",
+},
+{
+ .name = "WM9713 Voice",
+ .stream_name = "WM9713 Voice",
+ .codec_name = "wm9713-codec",
+ .platform_name = "pxa-pcm-audio",
+ .cpu_dai_name = "pxa-ssp-dai.2",
+ .codec_dai_name = "wm9713-voice",
+ .ops = &zylonite_voice_ops,
+},
+};
+
+static int zylonite_probe(struct snd_soc_card *card)
+{
+ int ret;
+
+ if (clk_pout) {
+ pout = clk_get(NULL, "CLK_POUT");
+ if (IS_ERR(pout)) {
+ dev_err(card->dev, "Unable to obtain CLK_POUT: %ld\n",
+ PTR_ERR(pout));
+ return PTR_ERR(pout);
+ }
+
+ ret = clk_enable(pout);
+ if (ret != 0) {
+ dev_err(card->dev, "Unable to enable CLK_POUT: %d\n",
+ ret);
+ clk_put(pout);
+ return ret;
+ }
+
+ dev_dbg(card->dev, "MCLK enabled at %luHz\n",
+ clk_get_rate(pout));
+ }
+
+ return 0;
+}
+
+static int zylonite_remove(struct snd_soc_card *card)
+{
+ if (clk_pout) {
+ clk_disable(pout);
+ clk_put(pout);
+ }
+
+ return 0;
+}
+
+static int zylonite_suspend_post(struct snd_soc_card *card)
+{
+ if (clk_pout)
+ clk_disable(pout);
+
+ return 0;
+}
+
+static int zylonite_resume_pre(struct snd_soc_card *card)
+{
+ int ret = 0;
+
+ if (clk_pout) {
+ ret = clk_enable(pout);
+ if (ret != 0)
+ dev_err(card->dev, "Unable to enable CLK_POUT: %d\n",
+ ret);
+ }
+
+ return ret;
+}
+
+static struct snd_soc_card zylonite = {
+ .name = "Zylonite",
+ .probe = &zylonite_probe,
+ .remove = &zylonite_remove,
+ .suspend_post = &zylonite_suspend_post,
+ .resume_pre = &zylonite_resume_pre,
+ .dai_link = zylonite_dai,
+ .num_links = ARRAY_SIZE(zylonite_dai),
+ .owner = THIS_MODULE,
+};
+
+static struct platform_device *zylonite_snd_ac97_device;
+
+static int __init zylonite_init(void)
+{
+ int ret;
+
+ zylonite_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+ if (!zylonite_snd_ac97_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(zylonite_snd_ac97_device, &zylonite);
+
+ ret = platform_device_add(zylonite_snd_ac97_device);
+ if (ret != 0)
+ platform_device_put(zylonite_snd_ac97_device);
+
+ return ret;
+}
+
+static void __exit zylonite_exit(void)
+{
+ platform_device_unregister(zylonite_snd_ac97_device);
+}
+
+module_init(zylonite_init);
+module_exit(zylonite_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("ALSA SoC WM9713 Zylonite");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s6000/Kconfig b/sound/soc/s6000/Kconfig
new file mode 100644
index 00000000..c74eb3d4
--- /dev/null
+++ b/sound/soc/s6000/Kconfig
@@ -0,0 +1,19 @@
+config SND_S6000_SOC
+ tristate "SoC Audio for the Stretch s6000 family"
+ depends on XTENSA_VARIANT_S6000
+ help
+ Say Y or M if you want to add support for codecs attached to
+ s6000 family chips. You will also need to select the platform
+ to support below.
+
+config SND_S6000_SOC_I2S
+ tristate
+
+config SND_S6000_SOC_S6IPCAM
+ tristate "SoC Audio support for Stretch 6105 IP Camera"
+ depends on SND_S6000_SOC && XTENSA_PLATFORM_S6105
+ select SND_S6000_SOC_I2S
+ select SND_SOC_TLV320AIC3X
+ help
+ Say Y if you want to add support for SoC audio on the
+ Stretch s6105 IP Camera Reference Design.
diff --git a/sound/soc/s6000/Makefile b/sound/soc/s6000/Makefile
new file mode 100644
index 00000000..7a613612
--- /dev/null
+++ b/sound/soc/s6000/Makefile
@@ -0,0 +1,11 @@
+# s6000 Platform Support
+snd-soc-s6000-objs := s6000-pcm.o
+snd-soc-s6000-i2s-objs := s6000-i2s.o
+
+obj-$(CONFIG_SND_S6000_SOC) += snd-soc-s6000.o
+obj-$(CONFIG_SND_S6000_SOC_I2S) += snd-soc-s6000-i2s.o
+
+# s6105 Machine Support
+snd-soc-s6ipcam-objs := s6105-ipcam.o
+
+obj-$(CONFIG_SND_S6000_SOC_S6IPCAM) += snd-soc-s6ipcam.o
diff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.c
new file mode 100644
index 00000000..3052f64b
--- /dev/null
+++ b/sound/soc/s6000/s6000-i2s.c
@@ -0,0 +1,621 @@
+/*
+ * ALSA SoC I2S Audio Layer for the Stretch S6000 family
+ *
+ * Author: Daniel Gloeckner, <dg@emlix.com>
+ * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "s6000-i2s.h"
+#include "s6000-pcm.h"
+
+struct s6000_i2s_dev {
+ dma_addr_t sifbase;
+ u8 __iomem *scbbase;
+ unsigned int wide;
+ unsigned int channel_in;
+ unsigned int channel_out;
+ unsigned int lines_in;
+ unsigned int lines_out;
+ struct s6000_pcm_dma_params dma_params;
+};
+
+#define S6_I2S_INTERRUPT_STATUS 0x00
+#define S6_I2S_INT_OVERRUN 1
+#define S6_I2S_INT_UNDERRUN 2
+#define S6_I2S_INT_ALIGNMENT 4
+#define S6_I2S_INTERRUPT_ENABLE 0x04
+#define S6_I2S_INTERRUPT_RAW 0x08
+#define S6_I2S_INTERRUPT_CLEAR 0x0C
+#define S6_I2S_INTERRUPT_SET 0x10
+#define S6_I2S_MODE 0x20
+#define S6_I2S_DUAL 0
+#define S6_I2S_WIDE 1
+#define S6_I2S_TX_DEFAULT 0x24
+#define S6_I2S_DATA_CFG(c) (0x40 + 0x10 * (c))
+#define S6_I2S_IN 0
+#define S6_I2S_OUT 1
+#define S6_I2S_UNUSED 2
+#define S6_I2S_INTERFACE_CFG(c) (0x44 + 0x10 * (c))
+#define S6_I2S_DIV_MASK 0x001fff
+#define S6_I2S_16BIT 0x000000
+#define S6_I2S_20BIT 0x002000
+#define S6_I2S_24BIT 0x004000
+#define S6_I2S_32BIT 0x006000
+#define S6_I2S_BITS_MASK 0x006000
+#define S6_I2S_MEM_16BIT 0x000000
+#define S6_I2S_MEM_32BIT 0x008000
+#define S6_I2S_MEM_MASK 0x008000
+#define S6_I2S_CHANNELS_SHIFT 16
+#define S6_I2S_CHANNELS_MASK 0x030000
+#define S6_I2S_SCK_IN 0x000000
+#define S6_I2S_SCK_OUT 0x040000
+#define S6_I2S_SCK_DIR 0x040000
+#define S6_I2S_WS_IN 0x000000
+#define S6_I2S_WS_OUT 0x080000
+#define S6_I2S_WS_DIR 0x080000
+#define S6_I2S_LEFT_FIRST 0x000000
+#define S6_I2S_RIGHT_FIRST 0x100000
+#define S6_I2S_FIRST 0x100000
+#define S6_I2S_CUR_SCK 0x200000
+#define S6_I2S_CUR_WS 0x400000
+#define S6_I2S_ENABLE(c) (0x48 + 0x10 * (c))
+#define S6_I2S_DISABLE_IF 0x02
+#define S6_I2S_ENABLE_IF 0x03
+#define S6_I2S_IS_BUSY 0x04
+#define S6_I2S_DMA_ACTIVE 0x08
+#define S6_I2S_IS_ENABLED 0x10
+
+#define S6_I2S_NUM_LINES 4
+
+#define S6_I2S_SIF_PORT0 0x0000000
+#define S6_I2S_SIF_PORT1 0x0000080 /* docs say 0x0000010 */
+
+static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val)
+{
+ writel(val, dev->scbbase + reg);
+}
+
+static inline u32 s6_i2s_read_reg(struct s6000_i2s_dev *dev, int reg)
+{
+ return readl(dev->scbbase + reg);
+}
+
+static inline void s6_i2s_mod_reg(struct s6000_i2s_dev *dev, int reg,
+ u32 mask, u32 val)
+{
+ val ^= s6_i2s_read_reg(dev, reg) & ~mask;
+ s6_i2s_write_reg(dev, reg, val);
+}
+
+static void s6000_i2s_start_channel(struct s6000_i2s_dev *dev, int channel)
+{
+ int i, j, cur, prev;
+
+ /*
+ * Wait for WCLK to toggle 5 times before enabling the channel
+ * s6000 Family Datasheet 3.6.4:
+ * "At least two cycles of WS must occur between commands
+ * to disable or enable the interface"
+ */
+ j = 0;
+ prev = ~S6_I2S_CUR_WS;
+ for (i = 1000000; --i && j < 6; ) {
+ cur = s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(channel))
+ & S6_I2S_CUR_WS;
+ if (prev != cur) {
+ prev = cur;
+ j++;
+ }
+ }
+ if (j < 6)
+ printk(KERN_WARNING "s6000-i2s: timeout waiting for WCLK\n");
+
+ s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_ENABLE_IF);
+}
+
+static void s6000_i2s_stop_channel(struct s6000_i2s_dev *dev, int channel)
+{
+ s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_DISABLE_IF);
+}
+
+static void s6000_i2s_start(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ int channel;
+
+ channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ dev->channel_out : dev->channel_in;
+
+ s6000_i2s_start_channel(dev, channel);
+}
+
+static void s6000_i2s_stop(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ int channel;
+
+ channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ dev->channel_out : dev->channel_in;
+
+ s6000_i2s_stop_channel(dev, channel);
+}
+
+static int s6000_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ int after)
+{
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) ^ !after)
+ s6000_i2s_start(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (!after)
+ s6000_i2s_stop(substream);
+ }
+ return 0;
+}
+
+static unsigned int s6000_i2s_int_sources(struct s6000_i2s_dev *dev)
+{
+ unsigned int pending;
+ pending = s6_i2s_read_reg(dev, S6_I2S_INTERRUPT_RAW);
+ pending &= S6_I2S_INT_ALIGNMENT |
+ S6_I2S_INT_UNDERRUN |
+ S6_I2S_INT_OVERRUN;
+ s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, pending);
+
+ return pending;
+}
+
+static unsigned int s6000_i2s_check_xrun(struct snd_soc_dai *cpu_dai)
+{
+ struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned int errors;
+ unsigned int ret;
+
+ errors = s6000_i2s_int_sources(dev);
+ if (likely(!errors))
+ return 0;
+
+ ret = 0;
+ if (errors & S6_I2S_INT_ALIGNMENT)
+ printk(KERN_ERR "s6000-i2s: WCLK misaligned\n");
+ if (errors & S6_I2S_INT_UNDERRUN)
+ ret |= 1 << SNDRV_PCM_STREAM_PLAYBACK;
+ if (errors & S6_I2S_INT_OVERRUN)
+ ret |= 1 << SNDRV_PCM_STREAM_CAPTURE;
+ return ret;
+}
+
+static void s6000_i2s_wait_disabled(struct s6000_i2s_dev *dev)
+{
+ int channel;
+ int n = 50;
+ for (channel = 0; channel < 2; channel++) {
+ while (--n >= 0) {
+ int v = s6_i2s_read_reg(dev, S6_I2S_ENABLE(channel));
+ if ((v & S6_I2S_IS_ENABLED)
+ || !(v & (S6_I2S_DMA_ACTIVE | S6_I2S_IS_BUSY)))
+ break;
+ udelay(20);
+ }
+ }
+ if (n < 0)
+ printk(KERN_WARNING "s6000-i2s: timeout disabling interfaces");
+}
+
+static int s6000_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 w;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ w = S6_I2S_SCK_IN | S6_I2S_WS_IN;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ w = S6_I2S_SCK_OUT | S6_I2S_WS_IN;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ w = S6_I2S_SCK_IN | S6_I2S_WS_OUT;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ w = S6_I2S_SCK_OUT | S6_I2S_WS_OUT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ w |= S6_I2S_LEFT_FIRST;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ w |= S6_I2S_RIGHT_FIRST;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(0),
+ S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);
+ s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(1),
+ S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);
+
+ return 0;
+}
+
+static int s6000_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
+{
+ struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ if (!div || (div & 1) || div > (S6_I2S_DIV_MASK + 1) * 2)
+ return -EINVAL;
+
+ s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(div_id),
+ S6_I2S_DIV_MASK, div / 2 - 1);
+ return 0;
+}
+
+static int s6000_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ int interf;
+ u32 w = 0;
+
+ if (dev->wide)
+ interf = 0;
+ else {
+ w |= (((params_channels(params) - 2) / 2)
+ << S6_I2S_CHANNELS_SHIFT) & S6_I2S_CHANNELS_MASK;
+ interf = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ? dev->channel_out : dev->channel_in;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ w |= S6_I2S_16BIT | S6_I2S_MEM_16BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ w |= S6_I2S_32BIT | S6_I2S_MEM_32BIT;
+ break;
+ default:
+ printk(KERN_WARNING "s6000-i2s: unsupported PCM format %x\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ if (s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(interf))
+ & S6_I2S_IS_ENABLED) {
+ printk(KERN_ERR "s6000-i2s: interface already enabled\n");
+ return -EBUSY;
+ }
+
+ s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(interf),
+ S6_I2S_CHANNELS_MASK|S6_I2S_MEM_MASK|S6_I2S_BITS_MASK,
+ w);
+
+ return 0;
+}
+
+static int s6000_i2s_dai_probe(struct snd_soc_dai *dai)
+{
+ struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct s6000_snd_platform_data *pdata = dai->dev->platform_data;
+
+ if (!pdata)
+ return -EINVAL;
+
+ dai->capture_dma_data = &dev->dma_params;
+ dai->playback_dma_data = &dev->dma_params;
+
+ dev->wide = pdata->wide;
+ dev->channel_in = pdata->channel_in;
+ dev->channel_out = pdata->channel_out;
+ dev->lines_in = pdata->lines_in;
+ dev->lines_out = pdata->lines_out;
+
+ s6_i2s_write_reg(dev, S6_I2S_MODE,
+ dev->wide ? S6_I2S_WIDE : S6_I2S_DUAL);
+
+ if (dev->wide) {
+ int i;
+
+ if (dev->lines_in + dev->lines_out > S6_I2S_NUM_LINES)
+ return -EINVAL;
+
+ dev->channel_in = 0;
+ dev->channel_out = 1;
+ dai->driver->capture.channels_min = 2 * dev->lines_in;
+ dai->driver->capture.channels_max = dai->driver->capture.channels_min;
+ dai->driver->playback.channels_min = 2 * dev->lines_out;
+ dai->driver->playback.channels_max = dai->driver->playback.channels_min;
+
+ for (i = 0; i < dev->lines_out; i++)
+ s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_OUT);
+
+ for (; i < S6_I2S_NUM_LINES - dev->lines_in; i++)
+ s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i),
+ S6_I2S_UNUSED);
+
+ for (; i < S6_I2S_NUM_LINES; i++)
+ s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_IN);
+ } else {
+ unsigned int cfg[2] = {S6_I2S_UNUSED, S6_I2S_UNUSED};
+
+ if (dev->lines_in > 1 || dev->lines_out > 1)
+ return -EINVAL;
+
+ dai->driver->capture.channels_min = 2 * dev->lines_in;
+ dai->driver->capture.channels_max = 8 * dev->lines_in;
+ dai->driver->playback.channels_min = 2 * dev->lines_out;
+ dai->driver->playback.channels_max = 8 * dev->lines_out;
+
+ if (dev->lines_in)
+ cfg[dev->channel_in] = S6_I2S_IN;
+ if (dev->lines_out)
+ cfg[dev->channel_out] = S6_I2S_OUT;
+
+ s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(0), cfg[0]);
+ s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(1), cfg[1]);
+ }
+
+ if (dev->lines_out) {
+ if (dev->lines_in) {
+ if (!dev->dma_params.dma_out)
+ return -ENODEV;
+ } else {
+ dev->dma_params.dma_out = dev->dma_params.dma_in;
+ dev->dma_params.dma_in = 0;
+ }
+ }
+ dev->dma_params.sif_in = dev->sifbase + (dev->channel_in ?
+ S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);
+ dev->dma_params.sif_out = dev->sifbase + (dev->channel_out ?
+ S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);
+ dev->dma_params.same_rate = pdata->same_rate | pdata->wide;
+ return 0;
+}
+
+#define S6000_I2S_RATES (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \
+ SNDRV_PCM_RATE_8000_192000)
+#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops s6000_i2s_dai_ops = {
+ .set_fmt = s6000_i2s_set_dai_fmt,
+ .set_clkdiv = s6000_i2s_set_clkdiv,
+ .hw_params = s6000_i2s_hw_params,
+};
+
+static struct snd_soc_dai_driver s6000_i2s_dai = {
+ .probe = s6000_i2s_dai_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 8,
+ .formats = S6000_I2S_FORMATS,
+ .rates = S6000_I2S_RATES,
+ .rate_min = 0,
+ .rate_max = 1562500,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 8,
+ .formats = S6000_I2S_FORMATS,
+ .rates = S6000_I2S_RATES,
+ .rate_min = 0,
+ .rate_max = 1562500,
+ },
+ .ops = &s6000_i2s_dai_ops,
+};
+
+static int __devinit s6000_i2s_probe(struct platform_device *pdev)
+{
+ struct s6000_i2s_dev *dev;
+ struct resource *scbmem, *sifmem, *region, *dma1, *dma2;
+ u8 __iomem *mmio;
+ int ret;
+
+ scbmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!scbmem) {
+ dev_err(&pdev->dev, "no mem resource?\n");
+ ret = -ENODEV;
+ goto err_release_none;
+ }
+
+ region = request_mem_region(scbmem->start, resource_size(scbmem),
+ pdev->name);
+ if (!region) {
+ dev_err(&pdev->dev, "I2S SCB region already claimed\n");
+ ret = -EBUSY;
+ goto err_release_none;
+ }
+
+ mmio = ioremap(scbmem->start, resource_size(scbmem));
+ if (!mmio) {
+ dev_err(&pdev->dev, "can't ioremap SCB region\n");
+ ret = -ENOMEM;
+ goto err_release_scb;
+ }
+
+ sifmem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!sifmem) {
+ dev_err(&pdev->dev, "no second mem resource?\n");
+ ret = -ENODEV;
+ goto err_release_map;
+ }
+
+ region = request_mem_region(sifmem->start, resource_size(sifmem),
+ pdev->name);
+ if (!region) {
+ dev_err(&pdev->dev, "I2S SIF region already claimed\n");
+ ret = -EBUSY;
+ goto err_release_map;
+ }
+
+ dma1 = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!dma1) {
+ dev_err(&pdev->dev, "no dma resource?\n");
+ ret = -ENODEV;
+ goto err_release_sif;
+ }
+
+ region = request_mem_region(dma1->start, resource_size(dma1),
+ pdev->name);
+ if (!region) {
+ dev_err(&pdev->dev, "I2S DMA region already claimed\n");
+ ret = -EBUSY;
+ goto err_release_sif;
+ }
+
+ dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (dma2) {
+ region = request_mem_region(dma2->start, resource_size(dma2),
+ pdev->name);
+ if (!region) {
+ dev_err(&pdev->dev,
+ "I2S DMA region already claimed\n");
+ ret = -EBUSY;
+ goto err_release_dma1;
+ }
+ }
+
+ dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL);
+ if (!dev) {
+ ret = -ENOMEM;
+ goto err_release_dma2;
+ }
+ dev_set_drvdata(&pdev->dev, dev);
+
+ dev->sifbase = sifmem->start;
+ dev->scbbase = mmio;
+
+ s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);
+ s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR,
+ S6_I2S_INT_ALIGNMENT |
+ S6_I2S_INT_UNDERRUN |
+ S6_I2S_INT_OVERRUN);
+
+ s6000_i2s_stop_channel(dev, 0);
+ s6000_i2s_stop_channel(dev, 1);
+ s6000_i2s_wait_disabled(dev);
+
+ dev->dma_params.check_xrun = s6000_i2s_check_xrun;
+ dev->dma_params.trigger = s6000_i2s_trigger;
+ dev->dma_params.dma_in = dma1->start;
+ dev->dma_params.dma_out = dma2 ? dma2->start : 0;
+ dev->dma_params.irq = platform_get_irq(pdev, 0);
+ if (dev->dma_params.irq < 0) {
+ dev_err(&pdev->dev, "no irq resource?\n");
+ ret = -ENODEV;
+ goto err_release_dev;
+ }
+
+ s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE,
+ S6_I2S_INT_ALIGNMENT |
+ S6_I2S_INT_UNDERRUN |
+ S6_I2S_INT_OVERRUN);
+
+ ret = snd_soc_register_dai(&pdev->dev, &s6000_i2s_dai);
+ if (ret)
+ goto err_release_dev;
+
+ return 0;
+
+err_release_dev:
+ kfree(dev);
+err_release_dma2:
+ if (dma2)
+ release_mem_region(dma2->start, resource_size(dma2));
+err_release_dma1:
+ release_mem_region(dma1->start, resource_size(dma1));
+err_release_sif:
+ release_mem_region(sifmem->start, resource_size(sifmem));
+err_release_map:
+ iounmap(mmio);
+err_release_scb:
+ release_mem_region(scbmem->start, resource_size(scbmem));
+err_release_none:
+ return ret;
+}
+
+static void __devexit s6000_i2s_remove(struct platform_device *pdev)
+{
+ struct s6000_i2s_dev *dev = dev_get_drvdata(&pdev->dev);
+ struct resource *region;
+ void __iomem *mmio = dev->scbbase;
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ s6000_i2s_stop_channel(dev, 0);
+ s6000_i2s_stop_channel(dev, 1);
+
+ s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);
+ kfree(dev);
+
+ region = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ release_mem_region(region->start, resource_size(region));
+
+ region = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (region)
+ release_mem_region(region->start, resource_size(region));
+
+ region = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(region->start, resource_size(region));
+
+ iounmap(mmio);
+ region = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ release_mem_region(region->start, resource_size(region));
+}
+
+static struct platform_driver s6000_i2s_driver = {
+ .probe = s6000_i2s_probe,
+ .remove = __devexit_p(s6000_i2s_remove),
+ .driver = {
+ .name = "s6000-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s6000_i2s_init(void)
+{
+ return platform_driver_register(&s6000_i2s_driver);
+}
+module_init(s6000_i2s_init);
+
+static void __exit s6000_i2s_exit(void)
+{
+ platform_driver_unregister(&s6000_i2s_driver);
+}
+module_exit(s6000_i2s_exit);
+
+MODULE_AUTHOR("Daniel Gloeckner");
+MODULE_DESCRIPTION("Stretch s6000 family I2S SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s6000/s6000-i2s.h b/sound/soc/s6000/s6000-i2s.h
new file mode 100644
index 00000000..86aa1921
--- /dev/null
+++ b/sound/soc/s6000/s6000-i2s.h
@@ -0,0 +1,23 @@
+/*
+ * ALSA SoC I2S Audio Layer for the Stretch s6000 family
+ *
+ * Author: Daniel Gloeckner, <dg@emlix.com>
+ * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _S6000_I2S_H
+#define _S6000_I2S_H
+
+struct s6000_snd_platform_data {
+ int lines_in;
+ int lines_out;
+ int channel_in;
+ int channel_out;
+ int wide;
+ int same_rate;
+};
+#endif
diff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.c
new file mode 100644
index 00000000..ab3ccaec
--- /dev/null
+++ b/sound/soc/s6000/s6000-pcm.c
@@ -0,0 +1,537 @@
+/*
+ * ALSA PCM interface for the Stetch s6000 family
+ *
+ * Author: Daniel Gloeckner, <dg@emlix.com>
+ * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <variant/dmac.h>
+
+#include "s6000-pcm.h"
+
+#define S6_PCM_PREALLOCATE_SIZE (96 * 1024)
+#define S6_PCM_PREALLOCATE_MAX (2048 * 1024)
+
+static struct snd_pcm_hardware s6000_pcm_hardware = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_JOINT_DUPLEX),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),
+ .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \
+ SNDRV_PCM_RATE_8000_192000),
+ .rate_min = 0,
+ .rate_max = 1562500,
+ .channels_min = 2,
+ .channels_max = 8,
+ .buffer_bytes_max = 0x7ffffff0,
+ .period_bytes_min = 16,
+ .period_bytes_max = 0xfffff0,
+ .periods_min = 2,
+ .periods_max = 1024, /* no limit */
+ .fifo_size = 0,
+};
+
+struct s6000_runtime_data {
+ spinlock_t lock;
+ int period; /* current DMA period */
+};
+
+static void s6000_pcm_enqueue_dma(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s6000_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct s6000_pcm_dma_params *par;
+ int channel;
+ unsigned int period_size;
+ unsigned int dma_offset;
+ dma_addr_t dma_pos;
+ dma_addr_t src, dst;
+
+ par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+ period_size = snd_pcm_lib_period_bytes(substream);
+ dma_offset = prtd->period * period_size;
+ dma_pos = runtime->dma_addr + dma_offset;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ src = dma_pos;
+ dst = par->sif_out;
+ channel = par->dma_out;
+ } else {
+ src = par->sif_in;
+ dst = dma_pos;
+ channel = par->dma_in;
+ }
+
+ if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel),
+ DMA_INDEX_CHNL(channel)))
+ return;
+
+ if (s6dmac_fifo_full(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel))) {
+ printk(KERN_ERR "s6000-pcm: fifo full\n");
+ return;
+ }
+
+ BUG_ON(period_size & 15);
+ s6dmac_put_fifo(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel),
+ src, dst, period_size);
+
+ prtd->period++;
+ if (unlikely(prtd->period >= runtime->periods))
+ prtd->period = 0;
+}
+
+static irqreturn_t s6000_pcm_irq(int irq, void *data)
+{
+ struct snd_pcm *pcm = data;
+ struct snd_soc_pcm_runtime *runtime = pcm->private_data;
+ struct s6000_runtime_data *prtd;
+ unsigned int has_xrun;
+ int i, ret = IRQ_NONE;
+
+ for (i = 0; i < 2; ++i) {
+ struct snd_pcm_substream *substream = pcm->streams[i].substream;
+ struct s6000_pcm_dma_params *params =
+ snd_soc_dai_get_dma_data(runtime->cpu_dai, substream);
+ u32 channel;
+ unsigned int pending;
+
+ if (substream == SNDRV_PCM_STREAM_PLAYBACK)
+ channel = params->dma_out;
+ else
+ channel = params->dma_in;
+
+ has_xrun = params->check_xrun(runtime->cpu_dai);
+
+ if (!channel)
+ continue;
+
+ if (unlikely(has_xrun & (1 << i)) &&
+ substream->runtime &&
+ snd_pcm_running(substream)) {
+ dev_dbg(pcm->dev, "xrun\n");
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+ ret = IRQ_HANDLED;
+ }
+
+ pending = s6dmac_int_sources(DMA_MASK_DMAC(channel),
+ DMA_INDEX_CHNL(channel));
+
+ if (pending & 1) {
+ ret = IRQ_HANDLED;
+ if (likely(substream->runtime &&
+ snd_pcm_running(substream))) {
+ snd_pcm_period_elapsed(substream);
+ dev_dbg(pcm->dev, "period elapsed %x %x\n",
+ s6dmac_cur_src(DMA_MASK_DMAC(channel),
+ DMA_INDEX_CHNL(channel)),
+ s6dmac_cur_dst(DMA_MASK_DMAC(channel),
+ DMA_INDEX_CHNL(channel)));
+ prtd = substream->runtime->private_data;
+ spin_lock(&prtd->lock);
+ s6000_pcm_enqueue_dma(substream);
+ spin_unlock(&prtd->lock);
+ }
+ }
+
+ if (unlikely(pending & ~7)) {
+ if (pending & (1 << 3))
+ printk(KERN_WARNING
+ "s6000-pcm: DMA %x Underflow\n",
+ channel);
+ if (pending & (1 << 4))
+ printk(KERN_WARNING
+ "s6000-pcm: DMA %x Overflow\n",
+ channel);
+ if (pending & 0x1e0)
+ printk(KERN_WARNING
+ "s6000-pcm: DMA %x Master Error "
+ "(mask %x)\n",
+ channel, pending >> 5);
+
+ }
+ }
+
+ return ret;
+}
+
+static int s6000_pcm_start(struct snd_pcm_substream *substream)
+{
+ struct s6000_runtime_data *prtd = substream->runtime->private_data;
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct s6000_pcm_dma_params *par;
+ unsigned long flags;
+ int srcinc;
+ u32 dma;
+
+ par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+ spin_lock_irqsave(&prtd->lock, flags);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ srcinc = 1;
+ dma = par->dma_out;
+ } else {
+ srcinc = 0;
+ dma = par->dma_in;
+ }
+ s6dmac_enable_chan(DMA_MASK_DMAC(dma), DMA_INDEX_CHNL(dma),
+ 1 /* priority 1 (0 is max) */,
+ 0 /* peripheral requests w/o xfer length mode */,
+ srcinc /* source address increment */,
+ srcinc^1 /* destination address increment */,
+ 0 /* chunksize 0 (skip impossible on this dma) */,
+ 0 /* source skip after chunk (impossible) */,
+ 0 /* destination skip after chunk (impossible) */,
+ 4 /* 16 byte burst size */,
+ -1 /* don't conserve bandwidth */,
+ 0 /* low watermark irq descriptor threshold */,
+ 0 /* disable hardware timestamps */,
+ 1 /* enable channel */);
+
+ s6000_pcm_enqueue_dma(substream);
+ s6000_pcm_enqueue_dma(substream);
+
+ spin_unlock_irqrestore(&prtd->lock, flags);
+
+ return 0;
+}
+
+static int s6000_pcm_stop(struct snd_pcm_substream *substream)
+{
+ struct s6000_runtime_data *prtd = substream->runtime->private_data;
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct s6000_pcm_dma_params *par;
+ unsigned long flags;
+ u32 channel;
+
+ par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ channel = par->dma_out;
+ else
+ channel = par->dma_in;
+
+ s6dmac_set_terminal_count(DMA_MASK_DMAC(channel),
+ DMA_INDEX_CHNL(channel), 0);
+
+ spin_lock_irqsave(&prtd->lock, flags);
+
+ s6dmac_disable_chan(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel));
+
+ spin_unlock_irqrestore(&prtd->lock, flags);
+
+ return 0;
+}
+
+static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct s6000_pcm_dma_params *par;
+ int ret;
+
+ par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+ ret = par->trigger(substream, cmd, 0);
+ if (ret < 0)
+ return ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = s6000_pcm_start(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = s6000_pcm_stop(substream);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ if (ret < 0)
+ return ret;
+
+ return par->trigger(substream, cmd, 1);
+}
+
+static int s6000_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct s6000_runtime_data *prtd = substream->runtime->private_data;
+
+ prtd->period = 0;
+
+ return 0;
+}
+
+static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct s6000_pcm_dma_params *par;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s6000_runtime_data *prtd = runtime->private_data;
+ unsigned long flags;
+ unsigned int offset;
+ dma_addr_t count;
+
+ par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+ spin_lock_irqsave(&prtd->lock, flags);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ count = s6dmac_cur_src(DMA_MASK_DMAC(par->dma_out),
+ DMA_INDEX_CHNL(par->dma_out));
+ else
+ count = s6dmac_cur_dst(DMA_MASK_DMAC(par->dma_in),
+ DMA_INDEX_CHNL(par->dma_in));
+
+ count -= runtime->dma_addr;
+
+ spin_unlock_irqrestore(&prtd->lock, flags);
+
+ offset = bytes_to_frames(runtime, count);
+ if (unlikely(offset >= runtime->buffer_size))
+ offset = 0;
+
+ return offset;
+}
+
+static int s6000_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct s6000_pcm_dma_params *par;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s6000_runtime_data *prtd;
+ int ret;
+
+ par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+ snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16);
+ if (ret < 0)
+ return ret;
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16);
+ if (ret < 0)
+ return ret;
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ if (par->same_rate) {
+ int rate;
+ spin_lock(&par->lock); /* needed? */
+ rate = par->rate;
+ spin_unlock(&par->lock);
+ if (rate != -1) {
+ ret = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ rate, rate);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ prtd = kzalloc(sizeof(struct s6000_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&prtd->lock);
+
+ runtime->private_data = prtd;
+
+ return 0;
+}
+
+static int s6000_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s6000_runtime_data *prtd = runtime->private_data;
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static int s6000_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct s6000_pcm_dma_params *par;
+ int ret;
+ ret = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ if (ret < 0) {
+ printk(KERN_WARNING "s6000-pcm: allocation of memory failed\n");
+ return ret;
+ }
+
+ par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+ if (par->same_rate) {
+ spin_lock(&par->lock);
+ if (par->rate == -1 ||
+ !(par->in_use & ~(1 << substream->stream))) {
+ par->rate = params_rate(hw_params);
+ par->in_use |= 1 << substream->stream;
+ } else if (params_rate(hw_params) != par->rate) {
+ snd_pcm_lib_free_pages(substream);
+ par->in_use &= ~(1 << substream->stream);
+ ret = -EBUSY;
+ }
+ spin_unlock(&par->lock);
+ }
+ return ret;
+}
+
+static int s6000_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+ struct s6000_pcm_dma_params *par =
+ snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+ spin_lock(&par->lock);
+ par->in_use &= ~(1 << substream->stream);
+ if (!par->in_use)
+ par->rate = -1;
+ spin_unlock(&par->lock);
+
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static struct snd_pcm_ops s6000_pcm_ops = {
+ .open = s6000_pcm_open,
+ .close = s6000_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = s6000_pcm_hw_params,
+ .hw_free = s6000_pcm_hw_free,
+ .trigger = s6000_pcm_trigger,
+ .prepare = s6000_pcm_prepare,
+ .pointer = s6000_pcm_pointer,
+};
+
+static void s6000_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_soc_pcm_runtime *runtime = pcm->private_data;
+ struct s6000_pcm_dma_params *params =
+ snd_soc_dai_get_dma_data(runtime->cpu_dai, pcm->streams[0].substream);
+
+ free_irq(params->irq, pcm);
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static u64 s6000_pcm_dmamask = DMA_BIT_MASK(32);
+
+static int s6000_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ struct snd_soc_pcm_runtime *runtime = pcm->private_data;
+ struct s6000_pcm_dma_params *params;
+ int res;
+
+ params = snd_soc_dai_get_dma_data(runtime->cpu_dai,
+ pcm->streams[0].substream);
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &s6000_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ if (params->dma_in) {
+ s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_in),
+ DMA_INDEX_CHNL(params->dma_in));
+ s6dmac_int_sources(DMA_MASK_DMAC(params->dma_in),
+ DMA_INDEX_CHNL(params->dma_in));
+ }
+
+ if (params->dma_out) {
+ s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_out),
+ DMA_INDEX_CHNL(params->dma_out));
+ s6dmac_int_sources(DMA_MASK_DMAC(params->dma_out),
+ DMA_INDEX_CHNL(params->dma_out));
+ }
+
+ res = request_irq(params->irq, s6000_pcm_irq, IRQF_SHARED,
+ "s6000-audio", pcm);
+ if (res) {
+ printk(KERN_ERR "s6000-pcm couldn't get IRQ\n");
+ return res;
+ }
+
+ res = snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_DEV,
+ card->dev,
+ S6_PCM_PREALLOCATE_SIZE,
+ S6_PCM_PREALLOCATE_MAX);
+ if (res)
+ printk(KERN_WARNING "s6000-pcm: preallocation failed\n");
+
+ spin_lock_init(&params->lock);
+ params->in_use = 0;
+ params->rate = -1;
+ return 0;
+}
+
+static struct snd_soc_platform_driver s6000_soc_platform = {
+ .ops = &s6000_pcm_ops,
+ .pcm_new = s6000_pcm_new,
+ .pcm_free = s6000_pcm_free,
+};
+
+static int __devinit s6000_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &s6000_soc_platform);
+}
+
+static int __devexit s6000_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver s6000_pcm_driver = {
+ .driver = {
+ .name = "s6000-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = s6000_soc_platform_probe,
+ .remove = __devexit_p(s6000_soc_platform_remove),
+};
+
+static int __init snd_s6000_pcm_init(void)
+{
+ return platform_driver_register(&s6000_pcm_driver);
+}
+module_init(snd_s6000_pcm_init);
+
+static void __exit snd_s6000_pcm_exit(void)
+{
+ platform_driver_unregister(&s6000_pcm_driver);
+}
+module_exit(snd_s6000_pcm_exit);
+
+MODULE_AUTHOR("Daniel Gloeckner");
+MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s6000/s6000-pcm.h b/sound/soc/s6000/s6000-pcm.h
new file mode 100644
index 00000000..09d9b883
--- /dev/null
+++ b/sound/soc/s6000/s6000-pcm.h
@@ -0,0 +1,33 @@
+/*
+ * ALSA PCM interface for the Stretch s6000 family
+ *
+ * Author: Daniel Gloeckner, <dg@emlix.com>
+ * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _S6000_PCM_H
+#define _S6000_PCM_H
+
+struct snd_soc_dai;
+struct snd_pcm_substream;
+
+struct s6000_pcm_dma_params {
+ unsigned int (*check_xrun)(struct snd_soc_dai *cpu_dai);
+ int (*trigger)(struct snd_pcm_substream *substream, int cmd, int after);
+ dma_addr_t sif_in;
+ dma_addr_t sif_out;
+ u32 dma_in;
+ u32 dma_out;
+ int irq;
+ int same_rate;
+
+ spinlock_t lock;
+ int in_use;
+ int rate;
+};
+
+#endif
diff --git a/sound/soc/s6000/s6105-ipcam.c b/sound/soc/s6000/s6105-ipcam.c
new file mode 100644
index 00000000..5890e431
--- /dev/null
+++ b/sound/soc/s6000/s6105-ipcam.c
@@ -0,0 +1,243 @@
+/*
+ * ASoC driver for Stretch s6105 IP camera platform
+ *
+ * Author: Daniel Gloeckner, <dg@emlix.com>
+ * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <variant/dmac.h>
+
+#include "s6000-pcm.h"
+#include "s6000-i2s.h"
+
+#define S6105_CAM_CODEC_CLOCK 12288000
+
+static int s6105_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret = 0;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM |
+ SND_SOC_DAIFMT_NB_NF);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, S6105_CAM_CODEC_CLOCK,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops s6105_ops = {
+ .hw_params = s6105_hw_params,
+};
+
+/* s6105 machine dapm widgets */
+static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Audio Out Differential", NULL),
+ SND_SOC_DAPM_LINE("Audio Out Stereo", NULL),
+ SND_SOC_DAPM_LINE("Audio In", NULL),
+};
+
+/* s6105 machine audio_mapnections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Audio Out connected to HPLOUT, HPLCOM, HPROUT */
+ {"Audio Out Differential", NULL, "HPLOUT"},
+ {"Audio Out Differential", NULL, "HPLCOM"},
+ {"Audio Out Stereo", NULL, "HPLOUT"},
+ {"Audio Out Stereo", NULL, "HPROUT"},
+
+ /* Audio In connected to LINE1L, LINE1R */
+ {"LINE1L", NULL, "Audio In"},
+ {"LINE1R", NULL, "Audio In"},
+};
+
+static int output_type_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 2;
+ if (uinfo->value.enumerated.item) {
+ uinfo->value.enumerated.item = 1;
+ strcpy(uinfo->value.enumerated.name, "HPLOUT/HPROUT");
+ } else {
+ strcpy(uinfo->value.enumerated.name, "HPLOUT/HPLCOM");
+ }
+ return 0;
+}
+
+static int output_type_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = kcontrol->private_value;
+ return 0;
+}
+
+static int output_type_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = kcontrol->private_data;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ unsigned int val = (ucontrol->value.enumerated.item[0] != 0);
+ char *differential = "Audio Out Differential";
+ char *stereo = "Audio Out Stereo";
+
+ if (kcontrol->private_value == val)
+ return 0;
+ kcontrol->private_value = val;
+ snd_soc_dapm_disable_pin(dapm, val ? differential : stereo);
+ snd_soc_dapm_sync(dapm);
+ snd_soc_dapm_enable_pin(dapm, val ? stereo : differential);
+ snd_soc_dapm_sync(dapm);
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new audio_out_mux = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Output Mux",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = output_type_info,
+ .get = output_type_get,
+ .put = output_type_put,
+ .private_value = 1 /* default to stereo */
+};
+
+/* Logic for a aic3x as connected on the s6105 ip camera ref design */
+static int s6105_aic3x_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* Add s6105 specific widgets */
+ snd_soc_dapm_new_controls(dapm, aic3x_dapm_widgets,
+ ARRAY_SIZE(aic3x_dapm_widgets));
+
+ /* Set up s6105 specific audio path audio_map */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ /* not present */
+ snd_soc_dapm_nc_pin(dapm, "MONO_LOUT");
+ snd_soc_dapm_nc_pin(dapm, "LINE2L");
+ snd_soc_dapm_nc_pin(dapm, "LINE2R");
+
+ /* not connected */
+ snd_soc_dapm_nc_pin(dapm, "MIC3L"); /* LINE2L on this chip */
+ snd_soc_dapm_nc_pin(dapm, "MIC3R"); /* LINE2R on this chip */
+ snd_soc_dapm_nc_pin(dapm, "LLOUT");
+ snd_soc_dapm_nc_pin(dapm, "RLOUT");
+ snd_soc_dapm_nc_pin(dapm, "HPRCOM");
+
+ /* always connected */
+ snd_soc_dapm_enable_pin(dapm, "Audio In");
+
+ /* must correspond to audio_out_mux.private_value initializer */
+ snd_soc_dapm_disable_pin(dapm, "Audio Out Differential");
+ snd_soc_dapm_sync(dapm);
+ snd_soc_dapm_enable_pin(dapm, "Audio Out Stereo");
+
+ snd_soc_dapm_sync(dapm);
+
+ snd_ctl_add(codec->card->snd_card, snd_ctl_new1(&audio_out_mux, codec));
+
+ return 0;
+}
+
+/* s6105 digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link s6105_dai = {
+ .name = "TLV320AIC31",
+ .stream_name = "AIC31",
+ .cpu_dai_name = "s6000-i2s",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .platform_name = "s6000-pcm-audio",
+ .codec_name = "tlv320aic3x-codec.0-001a",
+ .init = s6105_aic3x_init,
+ .ops = &s6105_ops,
+};
+
+/* s6105 audio machine driver */
+static struct snd_soc_card snd_soc_card_s6105 = {
+ .name = "Stretch IP Camera",
+ .dai_link = &s6105_dai,
+ .num_links = 1,
+};
+
+static struct s6000_snd_platform_data __initdata s6105_snd_data = {
+ .wide = 0,
+ .channel_in = 0,
+ .channel_out = 1,
+ .lines_in = 1,
+ .lines_out = 1,
+ .same_rate = 1,
+};
+
+static struct platform_device *s6105_snd_device;
+
+/* temporary i2c device creation until this can be moved into the machine
+ * support file.
+*/
+static struct i2c_board_info i2c_device[] = {
+ { I2C_BOARD_INFO("tlv320aic33", 0x18), }
+};
+
+static int __init s6105_init(void)
+{
+ int ret;
+
+ i2c_register_board_info(0, i2c_device, ARRAY_SIZE(i2c_device));
+
+ s6105_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!s6105_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(s6105_snd_device, &snd_soc_card_s6105);
+ platform_device_add_data(s6105_snd_device, &s6105_snd_data,
+ sizeof(s6105_snd_data));
+
+ ret = platform_device_add(s6105_snd_device);
+ if (ret)
+ platform_device_put(s6105_snd_device);
+
+ return ret;
+}
+
+static void __exit s6105_exit(void)
+{
+ platform_device_unregister(s6105_snd_device);
+}
+
+module_init(s6105_init);
+module_exit(s6105_exit);
+
+MODULE_AUTHOR("Daniel Gloeckner");
+MODULE_DESCRIPTION("Stretch s6105 IP camera ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig
new file mode 100644
index 00000000..d155cbb5
--- /dev/null
+++ b/sound/soc/samsung/Kconfig
@@ -0,0 +1,179 @@
+config SND_SOC_SAMSUNG
+ tristate "ASoC support for Samsung"
+ depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_S5P64X0 || ARCH_EXYNOS4
+ select S3C64XX_DMA if ARCH_S3C64XX
+ select S3C2410_DMA if ARCH_S3C2410
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the Samsung SoCs' Audio interfaces. You will also need to
+ select the audio interfaces to support below.
+
+config SND_S3C24XX_I2S
+ tristate
+ select S3C2410_DMA
+
+config SND_S3C_I2SV2_SOC
+ tristate
+
+config SND_S3C2412_SOC_I2S
+ tristate
+ select SND_S3C_I2SV2_SOC
+ select S3C2410_DMA
+
+config SND_SAMSUNG_PCM
+ tristate
+
+config SND_SAMSUNG_AC97
+ tristate
+ select SND_SOC_AC97_BUS
+
+config SND_SAMSUNG_SPDIF
+ tristate
+ select SND_SOC_SPDIF
+
+config SND_SAMSUNG_I2S
+ tristate
+
+config SND_SOC_SAMSUNG_NEO1973_WM8753
+ tristate "Audio support for Openmoko Neo1973 Smartphones (GTA01/GTA02)"
+ depends on SND_SOC_SAMSUNG && (MACH_NEO1973_GTA01 || MACH_NEO1973_GTA02)
+ select SND_S3C24XX_I2S
+ select SND_SOC_WM8753
+ select SND_SOC_LM4857 if MACH_NEO1973_GTA01
+ select SND_SOC_DFBMCS320
+ help
+ Say Y here to enable audio support for the Openmoko Neo1973
+ Smartphones.
+
+config SND_SOC_SAMSUNG_JIVE_WM8750
+ tristate "SoC I2S Audio support for Jive"
+ depends on SND_SOC_SAMSUNG && MACH_JIVE
+ select SND_SOC_WM8750
+ select SND_S3C2412_SOC_I2S
+ help
+ Sat Y if you want to add support for SoC audio on the Jive.
+
+config SND_SOC_SAMSUNG_SMDK_WM8580
+ tristate "SoC I2S Audio support for WM8580 on SMDK"
+ depends on SND_SOC_SAMSUNG && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDK6440 || MACH_SMDK6450 || MACH_SMDKV210 || MACH_SMDKC110)
+ select SND_SOC_WM8580
+ select SND_SAMSUNG_I2S
+ help
+ Say Y if you want to add support for SoC audio on the SMDKs.
+
+config SND_SOC_SAMSUNG_SMDK_WM8994
+ tristate "SoC I2S Audio support for WM8994 on SMDK"
+ depends on SND_SOC_SAMSUNG && (MACH_SMDKV310 || MACH_SMDKC210)
+ select SND_SOC_WM8994
+ select SND_SAMSUNG_I2S
+ help
+ Say Y if you want to add support for SoC audio on the SMDKs.
+
+config SND_SOC_SAMSUNG_SMDK2443_WM9710
+ tristate "SoC AC97 Audio support for SMDK2443 - WM9710"
+ depends on SND_SOC_SAMSUNG && MACH_SMDK2443
+ select S3C2410_DMA
+ select AC97_BUS
+ select SND_SOC_AC97_CODEC
+ select SND_SAMSUNG_AC97
+ help
+ Say Y if you want to add support for SoC audio on smdk2443
+ with the WM9710.
+
+config SND_SOC_SAMSUNG_LN2440SBC_ALC650
+ tristate "SoC AC97 Audio support for LN2440SBC - ALC650"
+ depends on SND_SOC_SAMSUNG && ARCH_S3C2410
+ select S3C2410_DMA
+ select AC97_BUS
+ select SND_SOC_AC97_CODEC
+ select SND_SAMSUNG_AC97
+ help
+ Say Y if you want to add support for SoC audio on ln2440sbc
+ with the ALC650.
+
+config SND_SOC_SAMSUNG_S3C24XX_UDA134X
+ tristate "SoC I2S Audio support UDA134X wired to a S3C24XX"
+ depends on SND_SOC_SAMSUNG && ARCH_S3C2410
+ select SND_S3C24XX_I2S
+ select SND_SOC_L3
+ select SND_SOC_UDA134X
+
+config SND_SOC_SAMSUNG_SIMTEC
+ tristate
+ help
+ Internal node for common S3C24XX/Simtec suppor
+
+config SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23
+ tristate "SoC I2S Audio support for TLV320AIC23 on Simtec boards"
+ depends on SND_SOC_SAMSUNG && ARCH_S3C2410
+ select SND_S3C24XX_I2S
+ select SND_SOC_TLV320AIC23
+ select SND_SOC_SAMSUNG_SIMTEC
+
+config SND_SOC_SAMSUNG_SIMTEC_HERMES
+ tristate "SoC I2S Audio support for Simtec Hermes board"
+ depends on SND_SOC_SAMSUNG && ARCH_S3C2410
+ select SND_S3C24XX_I2S
+ select SND_SOC_TLV320AIC3X
+ select SND_SOC_SAMSUNG_SIMTEC
+
+config SND_SOC_SAMSUNG_H1940_UDA1380
+ tristate "Audio support for the HP iPAQ H1940"
+ depends on SND_SOC_SAMSUNG && ARCH_H1940
+ select SND_S3C24XX_I2S
+ select SND_SOC_UDA1380
+ help
+ This driver provides audio support for HP iPAQ h1940 PDA.
+
+config SND_SOC_SAMSUNG_RX1950_UDA1380
+ tristate "Audio support for the HP iPAQ RX1950"
+ depends on SND_SOC_SAMSUNG && MACH_RX1950
+ select SND_S3C24XX_I2S
+ select SND_SOC_UDA1380
+ help
+ This driver provides audio support for HP iPAQ RX1950 PDA.
+
+config SND_SOC_SAMSUNG_SMDK_WM9713
+ tristate "SoC AC97 Audio support for SMDK with WM9713"
+ depends on SND_SOC_SAMSUNG && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDKV210 || MACH_SMDKC110 || MACH_SMDKV310 || MACH_SMDKC210)
+ select SND_SOC_WM9713
+ select SND_SAMSUNG_AC97
+ help
+ Sat Y if you want to add support for SoC audio on the SMDK.
+
+config SND_SOC_SMARTQ
+ tristate "SoC I2S Audio support for SmartQ board"
+ depends on SND_SOC_SAMSUNG && MACH_SMARTQ
+ select SND_SAMSUNG_I2S
+ select SND_SOC_WM8750
+
+config SND_SOC_GONI_AQUILA_WM8994
+ tristate "SoC I2S Audio support for AQUILA/GONI - WM8994"
+ depends on SND_SOC_SAMSUNG && (MACH_GONI || MACH_AQUILA)
+ select SND_SAMSUNG_I2S
+ select SND_SOC_WM8994
+ help
+ Say Y if you want to add support for SoC audio on goni or aquila
+ with the WM8994.
+
+config SND_SOC_SAMSUNG_SMDK_SPDIF
+ tristate "SoC S/PDIF Audio support for SMDK"
+ depends on SND_SOC_SAMSUNG && (MACH_SMDKC100 || MACH_SMDKC110 || MACH_SMDKV210)
+ select SND_SAMSUNG_SPDIF
+ help
+ Say Y if you want to add support for SoC S/PDIF audio on the SMDK.
+
+config SND_SOC_SMDK_WM8580_PCM
+ tristate "SoC PCM Audio support for WM8580 on SMDK"
+ depends on SND_SOC_SAMSUNG && (MACH_SMDK6450 || MACH_SMDKV210 || MACH_SMDKC110)
+ select SND_SOC_WM8580
+ select SND_SAMSUNG_PCM
+ help
+ Say Y if you want to add support for SoC audio on the SMDK.
+
+config SND_SOC_SPEYSIDE
+ tristate "Audio support for Wolfson Speyside"
+ depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
+ select SND_SAMSUNG_I2S
+ select SND_SOC_WM8915
+ select SND_SOC_WM9081
diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile
new file mode 100644
index 00000000..683843a2
--- /dev/null
+++ b/sound/soc/samsung/Makefile
@@ -0,0 +1,57 @@
+# S3c24XX Platform Support
+snd-soc-s3c24xx-objs := dma.o
+snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
+snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o
+snd-soc-ac97-objs := ac97.o
+snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o
+snd-soc-samsung-spdif-objs := spdif.o
+snd-soc-pcm-objs := pcm.o
+snd-soc-i2s-objs := i2s.o
+
+obj-$(CONFIG_SND_SOC_SAMSUNG) += snd-soc-s3c24xx.o
+obj-$(CONFIG_SND_S3C24XX_I2S) += snd-soc-s3c24xx-i2s.o
+obj-$(CONFIG_SND_SAMSUNG_AC97) += snd-soc-ac97.o
+obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o
+obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o
+obj-$(CONFIG_SND_SAMSUNG_SPDIF) += snd-soc-samsung-spdif.o
+obj-$(CONFIG_SND_SAMSUNG_PCM) += snd-soc-pcm.o
+obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o
+
+# S3C24XX Machine Support
+snd-soc-jive-wm8750-objs := jive_wm8750.o
+snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o
+snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o
+snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o
+snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o
+snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o
+snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o
+snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o
+snd-soc-h1940-uda1380-objs := h1940_uda1380.o
+snd-soc-rx1950-uda1380-objs := rx1950_uda1380.o
+snd-soc-smdk-wm8580-objs := smdk_wm8580.o
+snd-soc-smdk-wm8994-objs := smdk_wm8994.o
+snd-soc-smdk-wm9713-objs := smdk_wm9713.o
+snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o
+snd-soc-goni-wm8994-objs := goni_wm8994.o
+snd-soc-smdk-spdif-objs := smdk_spdif.o
+snd-soc-smdk-wm8580pcm-objs := smdk_wm8580pcm.o
+snd-soc-speyside-objs := speyside.o
+
+obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC) += snd-soc-s3c24xx-simtec.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_H1940_UDA1380) += snd-soc-h1940-uda1380.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_RX1950_UDA1380) += snd-soc-rx1950-uda1380.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8580) += snd-soc-smdk-wm8580.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8994) += snd-soc-smdk-wm8994.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM9713) += snd-soc-smdk-wm9713.o
+obj-$(CONFIG_SND_SOC_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_SPDIF) += snd-soc-smdk-spdif.o
+obj-$(CONFIG_SND_SOC_GONI_AQUILA_WM8994) += snd-soc-goni-wm8994.o
+obj-$(CONFIG_SND_SOC_SMDK_WM8580_PCM) += snd-soc-smdk-wm8580pcm.o
+obj-$(CONFIG_SND_SOC_SPEYSIDE) += snd-soc-speyside.o
diff --git a/sound/soc/samsung/ac97.c b/sound/soc/samsung/ac97.c
new file mode 100644
index 00000000..f97110e7
--- /dev/null
+++ b/sound/soc/samsung/ac97.c
@@ -0,0 +1,520 @@
+/* sound/soc/samsung/ac97.c
+ *
+ * ALSA SoC Audio Layer - S3C AC97 Controller driver
+ * Evolved from s3c2443-ac97.c
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * Author: Jaswinder Singh <jassi.brar@samsung.com>
+ * Credits: Graeme Gregory, Sean Choi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include <plat/regs-ac97.h>
+#include <plat/audio.h>
+
+#include "dma.h"
+
+#define AC_CMD_ADDR(x) (x << 16)
+#define AC_CMD_DATA(x) (x & 0xffff)
+
+#define S3C_AC97_DAI_PCM 0
+#define S3C_AC97_DAI_MIC 1
+
+struct s3c_ac97_info {
+ struct clk *ac97_clk;
+ void __iomem *regs;
+ struct mutex lock;
+ struct completion done;
+};
+static struct s3c_ac97_info s3c_ac97;
+
+static struct s3c2410_dma_client s3c_dma_client_out = {
+ .name = "AC97 PCMOut"
+};
+
+static struct s3c2410_dma_client s3c_dma_client_in = {
+ .name = "AC97 PCMIn"
+};
+
+static struct s3c2410_dma_client s3c_dma_client_micin = {
+ .name = "AC97 MicIn"
+};
+
+static struct s3c_dma_params s3c_ac97_pcm_out = {
+ .client = &s3c_dma_client_out,
+ .dma_size = 4,
+};
+
+static struct s3c_dma_params s3c_ac97_pcm_in = {
+ .client = &s3c_dma_client_in,
+ .dma_size = 4,
+};
+
+static struct s3c_dma_params s3c_ac97_mic_in = {
+ .client = &s3c_dma_client_micin,
+ .dma_size = 4,
+};
+
+static void s3c_ac97_activate(struct snd_ac97 *ac97)
+{
+ u32 ac_glbctrl, stat;
+
+ stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
+ if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
+ return; /* Return if already active */
+
+ INIT_COMPLETION(s3c_ac97.done);
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
+ pr_err("AC97: Unable to activate!");
+}
+
+static unsigned short s3c_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ u32 ac_glbctrl, ac_codec_cmd;
+ u32 stat, addr, data;
+
+ mutex_lock(&s3c_ac97.lock);
+
+ s3c_ac97_activate(ac97);
+
+ INIT_COMPLETION(s3c_ac97.done);
+
+ ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+ ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
+ writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+
+ udelay(50);
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
+ pr_err("AC97: Unable to read!");
+
+ stat = readl(s3c_ac97.regs + S3C_AC97_STAT);
+ addr = (stat >> 16) & 0x7f;
+ data = (stat & 0xffff);
+
+ if (addr != reg)
+ pr_err("ac97: req addr = %02x, rep addr = %02x\n",
+ reg, addr);
+
+ mutex_unlock(&s3c_ac97.lock);
+
+ return (unsigned short)data;
+}
+
+static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ u32 ac_glbctrl, ac_codec_cmd;
+
+ mutex_lock(&s3c_ac97.lock);
+
+ s3c_ac97_activate(ac97);
+
+ INIT_COMPLETION(s3c_ac97.done);
+
+ ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+ ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
+ writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+
+ udelay(50);
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
+ pr_err("AC97: Unable to write!");
+
+ ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+ ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
+ writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+
+ mutex_unlock(&s3c_ac97.lock);
+}
+
+static void s3c_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ pr_debug("AC97: Cold reset\n");
+ writel(S3C_AC97_GLBCTRL_COLDRESET,
+ s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+}
+
+static void s3c_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ u32 stat;
+
+ stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
+ if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
+ return; /* Return if already active */
+
+ pr_debug("AC97: Warm reset\n");
+
+ writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ s3c_ac97_activate(ac97);
+}
+
+static irqreturn_t s3c_ac97_irq(int irq, void *dev_id)
+{
+ u32 ac_glbctrl, ac_glbstat;
+
+ ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
+
+ if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ complete(&s3c_ac97.done);
+ }
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl |= (1<<30); /* Clear interrupt */
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ return IRQ_HANDLED;
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = s3c_ac97_read,
+ .write = s3c_ac97_write,
+ .warm_reset = s3c_ac97_warm_reset,
+ .reset = s3c_ac97_cold_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int s3c_ac97_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct s3c_dma_params *dma_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &s3c_ac97_pcm_out;
+ else
+ dma_data = &s3c_ac97_pcm_in;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+ return 0;
+}
+
+static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ u32 ac_glbctrl;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct s3c_dma_params *dma_data =
+ snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
+ else
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
+ else
+ ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ }
+
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED);
+
+ return 0;
+}
+
+static int s3c_ac97_hw_mic_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+ else
+ snd_soc_dai_set_dma_data(cpu_dai, substream, &s3c_ac97_mic_in);
+
+ return 0;
+}
+
+static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ u32 ac_glbctrl;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct s3c_dma_params *dma_data =
+ snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ }
+
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops s3c_ac97_dai_ops = {
+ .hw_params = s3c_ac97_hw_params,
+ .trigger = s3c_ac97_trigger,
+};
+
+static struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
+ .hw_params = s3c_ac97_hw_mic_params,
+ .trigger = s3c_ac97_mic_trigger,
+};
+
+static struct snd_soc_dai_driver s3c_ac97_dai[] = {
+ [S3C_AC97_DAI_PCM] = {
+ .name = "samsung-ac97",
+ .ac97_control = 1,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &s3c_ac97_dai_ops,
+ },
+ [S3C_AC97_DAI_MIC] = {
+ .name = "samsung-ac97-mic",
+ .ac97_control = 1,
+ .capture = {
+ .stream_name = "AC97 Mic Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &s3c_ac97_mic_dai_ops,
+ },
+};
+
+static __devinit int s3c_ac97_probe(struct platform_device *pdev)
+{
+ struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res;
+ struct s3c_audio_pdata *ac97_pdata;
+ int ret;
+
+ ac97_pdata = pdev->dev.platform_data;
+ if (!ac97_pdata || !ac97_pdata->cfg_gpio) {
+ dev_err(&pdev->dev, "cfg_gpio callback not provided!\n");
+ return -EINVAL;
+ }
+
+ /* Check for availability of necessary resource */
+ dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!dmatx_res) {
+ dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n");
+ return -ENXIO;
+ }
+
+ dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!dmarx_res) {
+ dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n");
+ return -ENXIO;
+ }
+
+ dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
+ if (!dmamic_res) {
+ dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n");
+ return -ENXIO;
+ }
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem_res) {
+ dev_err(&pdev->dev, "Unable to get register resource\n");
+ return -ENXIO;
+ }
+
+ irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!irq_res) {
+ dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
+ return -ENXIO;
+ }
+
+ if (!request_mem_region(mem_res->start,
+ resource_size(mem_res), "ac97")) {
+ dev_err(&pdev->dev, "Unable to request register region\n");
+ return -EBUSY;
+ }
+
+ s3c_ac97_pcm_out.channel = dmatx_res->start;
+ s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
+ s3c_ac97_pcm_in.channel = dmarx_res->start;
+ s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
+ s3c_ac97_mic_in.channel = dmamic_res->start;
+ s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA;
+
+ init_completion(&s3c_ac97.done);
+ mutex_init(&s3c_ac97.lock);
+
+ s3c_ac97.regs = ioremap(mem_res->start, resource_size(mem_res));
+ if (s3c_ac97.regs == NULL) {
+ dev_err(&pdev->dev, "Unable to ioremap register region\n");
+ ret = -ENXIO;
+ goto err1;
+ }
+
+ s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
+ if (IS_ERR(s3c_ac97.ac97_clk)) {
+ dev_err(&pdev->dev, "ac97 failed to get ac97_clock\n");
+ ret = -ENODEV;
+ goto err2;
+ }
+ clk_enable(s3c_ac97.ac97_clk);
+
+ if (ac97_pdata->cfg_gpio(pdev)) {
+ dev_err(&pdev->dev, "Unable to configure gpio\n");
+ ret = -EINVAL;
+ goto err3;
+ }
+
+ ret = request_irq(irq_res->start, s3c_ac97_irq,
+ IRQF_DISABLED, "AC97", NULL);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "ac97: interrupt request failed.\n");
+ goto err4;
+ }
+
+ ret = snd_soc_register_dais(&pdev->dev, s3c_ac97_dai,
+ ARRAY_SIZE(s3c_ac97_dai));
+ if (ret)
+ goto err5;
+
+ return 0;
+
+err5:
+ free_irq(irq_res->start, NULL);
+err4:
+err3:
+ clk_disable(s3c_ac97.ac97_clk);
+ clk_put(s3c_ac97.ac97_clk);
+err2:
+ iounmap(s3c_ac97.regs);
+err1:
+ release_mem_region(mem_res->start, resource_size(mem_res));
+
+ return ret;
+}
+
+static __devexit int s3c_ac97_remove(struct platform_device *pdev)
+{
+ struct resource *mem_res, *irq_res;
+
+ snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(s3c_ac97_dai));
+
+ irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (irq_res)
+ free_irq(irq_res->start, NULL);
+
+ clk_disable(s3c_ac97.ac97_clk);
+ clk_put(s3c_ac97.ac97_clk);
+
+ iounmap(s3c_ac97.regs);
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (mem_res)
+ release_mem_region(mem_res->start, resource_size(mem_res));
+
+ return 0;
+}
+
+static struct platform_driver s3c_ac97_driver = {
+ .probe = s3c_ac97_probe,
+ .remove = s3c_ac97_remove,
+ .driver = {
+ .name = "samsung-ac97",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s3c_ac97_init(void)
+{
+ return platform_driver_register(&s3c_ac97_driver);
+}
+module_init(s3c_ac97_init);
+
+static void __exit s3c_ac97_exit(void)
+{
+ platform_driver_unregister(&s3c_ac97_driver);
+}
+module_exit(s3c_ac97_exit);
+
+MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("AC97 driver for the Samsung SoC");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:samsung-ac97");
diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c
new file mode 100644
index 00000000..5cb3b880
--- /dev/null
+++ b/sound/soc/samsung/dma.c
@@ -0,0 +1,499 @@
+/*
+ * dma.c -- ALSA Soc Audio Layer
+ *
+ * (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * Copyright 2004-2005 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <asm/dma.h>
+#include <mach/hardware.h>
+#include <mach/dma.h>
+
+#include "dma.h"
+
+#define ST_RUNNING (1<<0)
+#define ST_OPENED (1<<1)
+
+static const struct snd_pcm_hardware dma_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S8,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 128*1024,
+ .period_bytes_min = PAGE_SIZE,
+ .period_bytes_max = PAGE_SIZE*2,
+ .periods_min = 2,
+ .periods_max = 128,
+ .fifo_size = 32,
+};
+
+struct runtime_data {
+ spinlock_t lock;
+ int state;
+ unsigned int dma_loaded;
+ unsigned int dma_limit;
+ unsigned int dma_period;
+ dma_addr_t dma_start;
+ dma_addr_t dma_pos;
+ dma_addr_t dma_end;
+ struct s3c_dma_params *params;
+};
+
+/* dma_enqueue
+ *
+ * place a dma buffer onto the queue for the dma system
+ * to handle.
+*/
+static void dma_enqueue(struct snd_pcm_substream *substream)
+{
+ struct runtime_data *prtd = substream->runtime->private_data;
+ dma_addr_t pos = prtd->dma_pos;
+ unsigned int limit;
+ int ret;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (s3c_dma_has_circular())
+ limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
+ else
+ limit = prtd->dma_limit;
+
+ pr_debug("%s: loaded %d, limit %d\n",
+ __func__, prtd->dma_loaded, limit);
+
+ while (prtd->dma_loaded < limit) {
+ unsigned long len = prtd->dma_period;
+
+ pr_debug("dma_loaded: %d\n", prtd->dma_loaded);
+
+ if ((pos + len) > prtd->dma_end) {
+ len = prtd->dma_end - pos;
+ pr_debug("%s: corrected dma len %ld\n", __func__, len);
+ }
+
+ ret = s3c2410_dma_enqueue(prtd->params->channel,
+ substream, pos, len);
+
+ if (ret == 0) {
+ prtd->dma_loaded++;
+ pos += prtd->dma_period;
+ if (pos >= prtd->dma_end)
+ pos = prtd->dma_start;
+ } else
+ break;
+ }
+
+ prtd->dma_pos = pos;
+}
+
+static void audio_buffdone(struct s3c2410_dma_chan *channel,
+ void *dev_id, int size,
+ enum s3c2410_dma_buffresult result)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct runtime_data *prtd;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR)
+ return;
+
+ prtd = substream->runtime->private_data;
+
+ if (substream)
+ snd_pcm_period_elapsed(substream);
+
+ spin_lock(&prtd->lock);
+ if (prtd->state & ST_RUNNING && !s3c_dma_has_circular()) {
+ prtd->dma_loaded--;
+ dma_enqueue(substream);
+ }
+
+ spin_unlock(&prtd->lock);
+}
+
+static int dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ unsigned long totbytes = params_buffer_bytes(params);
+ struct s3c_dma_params *dma =
+ snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ int ret = 0;
+
+
+ pr_debug("Entered %s\n", __func__);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!dma)
+ return 0;
+
+ /* this may get called several times by oss emulation
+ * with different params -HW */
+ if (prtd->params == NULL) {
+ /* prepare DMA */
+ prtd->params = dma;
+
+ pr_debug("params %p, client %p, channel %d\n", prtd->params,
+ prtd->params->client, prtd->params->channel);
+
+ ret = s3c2410_dma_request(prtd->params->channel,
+ prtd->params->client, NULL);
+
+ if (ret < 0) {
+ printk(KERN_ERR "failed to get dma channel\n");
+ return ret;
+ }
+
+ /* use the circular buffering if we have it available. */
+ if (s3c_dma_has_circular())
+ s3c2410_dma_setflags(prtd->params->channel,
+ S3C2410_DMAF_CIRCULAR);
+ }
+
+ s3c2410_dma_set_buffdone_fn(prtd->params->channel,
+ audio_buffdone);
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ runtime->dma_bytes = totbytes;
+
+ spin_lock_irq(&prtd->lock);
+ prtd->dma_loaded = 0;
+ prtd->dma_limit = runtime->hw.periods_min;
+ prtd->dma_period = params_period_bytes(params);
+ prtd->dma_start = runtime->dma_addr;
+ prtd->dma_pos = prtd->dma_start;
+ prtd->dma_end = prtd->dma_start + totbytes;
+ spin_unlock_irq(&prtd->lock);
+
+ return 0;
+}
+
+static int dma_hw_free(struct snd_pcm_substream *substream)
+{
+ struct runtime_data *prtd = substream->runtime->private_data;
+
+ pr_debug("Entered %s\n", __func__);
+
+ /* TODO - do we need to ensure DMA flushed */
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ if (prtd->params) {
+ s3c2410_dma_free(prtd->params->channel, prtd->params->client);
+ prtd->params = NULL;
+ }
+
+ return 0;
+}
+
+static int dma_prepare(struct snd_pcm_substream *substream)
+{
+ struct runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ pr_debug("Entered %s\n", __func__);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!prtd->params)
+ return 0;
+
+ /* channel needs configuring for mem=>device, increment memory addr,
+ * sync to pclk, half-word transfers to the IIS-FIFO. */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ s3c2410_dma_devconfig(prtd->params->channel,
+ S3C2410_DMASRC_MEM,
+ prtd->params->dma_addr);
+ } else {
+ s3c2410_dma_devconfig(prtd->params->channel,
+ S3C2410_DMASRC_HW,
+ prtd->params->dma_addr);
+ }
+
+ s3c2410_dma_config(prtd->params->channel,
+ prtd->params->dma_size);
+
+ /* flush the DMA channel */
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH);
+ prtd->dma_loaded = 0;
+ prtd->dma_pos = prtd->dma_start;
+
+ /* enqueue dma buffers */
+ dma_enqueue(substream);
+
+ return ret;
+}
+
+static int dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ pr_debug("Entered %s\n", __func__);
+
+ spin_lock(&prtd->lock);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ prtd->state |= ST_RUNNING;
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ prtd->state &= ~ST_RUNNING;
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STOP);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock(&prtd->lock);
+
+ return ret;
+}
+
+static snd_pcm_uframes_t
+dma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct runtime_data *prtd = runtime->private_data;
+ unsigned long res;
+ dma_addr_t src, dst;
+
+ pr_debug("Entered %s\n", __func__);
+
+ spin_lock(&prtd->lock);
+ s3c2410_dma_getposition(prtd->params->channel, &src, &dst);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ res = dst - prtd->dma_start;
+ else
+ res = src - prtd->dma_start;
+
+ spin_unlock(&prtd->lock);
+
+ pr_debug("Pointer %x %x\n", src, dst);
+
+ /* we seem to be getting the odd error from the pcm library due
+ * to out-of-bounds pointers. this is maybe due to the dma engine
+ * not having loaded the new values for the channel before being
+ * called... (todo - fix )
+ */
+
+ if (res >= snd_pcm_lib_buffer_bytes(substream)) {
+ if (res == snd_pcm_lib_buffer_bytes(substream))
+ res = 0;
+ }
+
+ return bytes_to_frames(substream->runtime, res);
+}
+
+static int dma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct runtime_data *prtd;
+
+ pr_debug("Entered %s\n", __func__);
+
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ snd_soc_set_runtime_hwparams(substream, &dma_hardware);
+
+ prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&prtd->lock);
+
+ runtime->private_data = prtd;
+ return 0;
+}
+
+static int dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct runtime_data *prtd = runtime->private_data;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (!prtd)
+ pr_debug("dma_close called with prtd == NULL\n");
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static int dma_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ pr_debug("Entered %s\n", __func__);
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops dma_ops = {
+ .open = dma_open,
+ .close = dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = dma_hw_params,
+ .hw_free = dma_hw_free,
+ .prepare = dma_prepare,
+ .trigger = dma_trigger,
+ .pointer = dma_pointer,
+ .mmap = dma_mmap,
+};
+
+static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = dma_hardware.buffer_bytes_max;
+
+ pr_debug("Entered %s\n", __func__);
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+ buf->bytes = size;
+ return 0;
+}
+
+static void dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ pr_debug("Entered %s\n", __func__);
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static u64 dma_mask = DMA_BIT_MASK(32);
+
+static int dma_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &dma_mask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->driver->playback.channels_min) {
+ ret = preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+out:
+ return ret;
+}
+
+static struct snd_soc_platform_driver samsung_asoc_platform = {
+ .ops = &dma_ops,
+ .pcm_new = dma_new,
+ .pcm_free = dma_free_dma_buffers,
+};
+
+static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
+}
+
+static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver asoc_dma_driver = {
+ .driver = {
+ .name = "samsung-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = samsung_asoc_platform_probe,
+ .remove = __devexit_p(samsung_asoc_platform_remove),
+};
+
+static int __init samsung_asoc_init(void)
+{
+ return platform_driver_register(&asoc_dma_driver);
+}
+module_init(samsung_asoc_init);
+
+static void __exit samsung_asoc_exit(void)
+{
+ platform_driver_unregister(&asoc_dma_driver);
+}
+module_exit(samsung_asoc_exit);
+
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("Samsung ASoC DMA Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:samsung-audio");
diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h
new file mode 100644
index 00000000..c5065926
--- /dev/null
+++ b/sound/soc/samsung/dma.h
@@ -0,0 +1,22 @@
+/*
+ * dma.h --
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ * ALSA PCM interface for the Samsung S3C24xx CPU
+ */
+
+#ifndef _S3C_AUDIO_H
+#define _S3C_AUDIO_H
+
+struct s3c_dma_params {
+ struct s3c2410_dma_client *client; /* stream identifier */
+ int channel; /* Channel ID */
+ dma_addr_t dma_addr;
+ int dma_size; /* Size of the DMA transfer */
+};
+
+#endif
diff --git a/sound/soc/samsung/goni_wm8994.c b/sound/soc/samsung/goni_wm8994.c
new file mode 100644
index 00000000..eb6d72ed
--- /dev/null
+++ b/sound/soc/samsung/goni_wm8994.c
@@ -0,0 +1,305 @@
+/*
+ * goni_wm8994.c
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ * Author: Chanwoo Choi <cw00.choi@samsung.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+#include <mach/gpio.h>
+
+#include "../codecs/wm8994.h"
+
+#define MACHINE_NAME 0
+#define CPU_VOICE_DAI 1
+
+static const char *aquila_str[] = {
+ [MACHINE_NAME] = "aquila",
+ [CPU_VOICE_DAI] = "aquila-voice-dai",
+};
+
+static struct snd_soc_card goni;
+static struct platform_device *goni_snd_device;
+
+/* 3.5 pie jack */
+static struct snd_soc_jack jack;
+
+/* 3.5 pie jack detection DAPM pins */
+static struct snd_soc_jack_pin jack_pins[] = {
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ }, {
+ .pin = "Headset Stereophone",
+ .mask = SND_JACK_HEADPHONE | SND_JACK_MECHANICAL |
+ SND_JACK_AVOUT,
+ },
+};
+
+/* 3.5 pie jack detection gpios */
+static struct snd_soc_jack_gpio jack_gpios[] = {
+ {
+ .gpio = S5PV210_GPH0(6),
+ .name = "DET_3.5",
+ .report = SND_JACK_HEADSET | SND_JACK_MECHANICAL |
+ SND_JACK_AVOUT,
+ .debounce_time = 200,
+ },
+};
+
+static const struct snd_soc_dapm_widget goni_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Ext Left Spk", NULL),
+ SND_SOC_DAPM_SPK("Ext Right Spk", NULL),
+ SND_SOC_DAPM_SPK("Ext Rcv", NULL),
+ SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Main Mic", NULL),
+ SND_SOC_DAPM_MIC("2nd Mic", NULL),
+ SND_SOC_DAPM_LINE("Radio In", NULL),
+};
+
+static const struct snd_soc_dapm_route goni_dapm_routes[] = {
+ {"Ext Left Spk", NULL, "SPKOUTLP"},
+ {"Ext Left Spk", NULL, "SPKOUTLN"},
+
+ {"Ext Right Spk", NULL, "SPKOUTRP"},
+ {"Ext Right Spk", NULL, "SPKOUTRN"},
+
+ {"Ext Rcv", NULL, "HPOUT2N"},
+ {"Ext Rcv", NULL, "HPOUT2P"},
+
+ {"Headset Stereophone", NULL, "HPOUT1L"},
+ {"Headset Stereophone", NULL, "HPOUT1R"},
+
+ {"IN1RN", NULL, "Headset Mic"},
+ {"IN1RP", NULL, "Headset Mic"},
+
+ {"IN1RN", NULL, "2nd Mic"},
+ {"IN1RP", NULL, "2nd Mic"},
+
+ {"IN1LN", NULL, "Main Mic"},
+ {"IN1LP", NULL, "Main Mic"},
+
+ {"IN2LN", NULL, "Radio In"},
+ {"IN2RN", NULL, "Radio In"},
+};
+
+static int goni_wm8994_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ /* add goni specific widgets */
+ snd_soc_dapm_new_controls(dapm, goni_dapm_widgets,
+ ARRAY_SIZE(goni_dapm_widgets));
+
+ /* set up goni specific audio routes */
+ snd_soc_dapm_add_routes(dapm, goni_dapm_routes,
+ ARRAY_SIZE(goni_dapm_routes));
+
+ /* set endpoints to not connected */
+ snd_soc_dapm_nc_pin(dapm, "IN2LP:VXRN");
+ snd_soc_dapm_nc_pin(dapm, "IN2RP:VXRP");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT1N");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT1P");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT2N");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT2P");
+
+ if (machine_is_aquila()) {
+ snd_soc_dapm_nc_pin(dapm, "SPKOUTRN");
+ snd_soc_dapm_nc_pin(dapm, "SPKOUTRP");
+ }
+
+ snd_soc_dapm_sync(dapm);
+
+ /* Headset jack detection */
+ ret = snd_soc_jack_new(codec, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_MECHANICAL | SND_JACK_AVOUT,
+ &jack);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_pins(&jack, ARRAY_SIZE(jack_pins), jack_pins);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_gpios(&jack, ARRAY_SIZE(jack_gpios), jack_gpios);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int goni_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int pll_out = 24000000;
+ int ret = 0;
+
+ /* set the cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec FLL */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, pll_out,
+ params_rate(params) * 256);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+ params_rate(params) * 256, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops goni_hifi_ops = {
+ .hw_params = goni_hifi_hw_params,
+};
+
+static int goni_voice_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ unsigned int pll_out = 24000000;
+ int ret = 0;
+
+ if (params_rate(params) != 8000)
+ return -EINVAL;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec FLL */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, 0, pll_out,
+ params_rate(params) * 256);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2,
+ params_rate(params) * 256, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver voice_dai = {
+ .name = "goni-voice-dai",
+ .id = 0,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+};
+
+static struct snd_soc_ops goni_voice_ops = {
+ .hw_params = goni_voice_hw_params,
+};
+
+static struct snd_soc_dai_link goni_dai[] = {
+{
+ .name = "WM8994",
+ .stream_name = "WM8994 HiFi",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8994-aif1",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8994-codec.0-001a",
+ .init = goni_wm8994_init,
+ .ops = &goni_hifi_ops,
+}, {
+ .name = "WM8994 Voice",
+ .stream_name = "Voice",
+ .cpu_dai_name = "goni-voice-dai",
+ .codec_dai_name = "wm8994-aif2",
+ .codec_name = "wm8994-codec.0-001a",
+ .ops = &goni_voice_ops,
+},
+};
+
+static struct snd_soc_card goni = {
+ .name = "goni",
+ .dai_link = goni_dai,
+ .num_links = ARRAY_SIZE(goni_dai),
+};
+
+static int __init goni_init(void)
+{
+ int ret;
+
+ if (machine_is_aquila()) {
+ voice_dai.name = aquila_str[CPU_VOICE_DAI];
+ goni_dai[1].cpu_dai_name = aquila_str[CPU_VOICE_DAI];
+ goni.name = aquila_str[MACHINE_NAME];
+ } else if (!machine_is_goni())
+ return -ENODEV;
+
+ goni_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!goni_snd_device)
+ return -ENOMEM;
+
+ /* register voice DAI here */
+ ret = snd_soc_register_dai(&goni_snd_device->dev, &voice_dai);
+ if (ret) {
+ platform_device_put(goni_snd_device);
+ return ret;
+ }
+
+ platform_set_drvdata(goni_snd_device, &goni);
+ ret = platform_device_add(goni_snd_device);
+
+ if (ret) {
+ snd_soc_unregister_dai(&goni_snd_device->dev);
+ platform_device_put(goni_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit goni_exit(void)
+{
+ snd_soc_unregister_dai(&goni_snd_device->dev);
+ platform_device_unregister(goni_snd_device);
+}
+
+module_init(goni_init);
+module_exit(goni_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC WM8994 GONI(S5PV210)");
+MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/h1940_uda1380.c b/sound/soc/samsung/h1940_uda1380.c
new file mode 100644
index 00000000..241f55d0
--- /dev/null
+++ b/sound/soc/samsung/h1940_uda1380.c
@@ -0,0 +1,287 @@
+/*
+ * h1940-uda1380.c -- ALSA Soc Audio Layer
+ *
+ * Copyright (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
+ * Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com>
+ *
+ * Based on version from Arnaud Patard <arnaud.patard@rtp-net.org>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/gpio.h>
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <plat/regs-iis.h>
+#include <mach/h1940-latch.h>
+#include <asm/mach-types.h>
+
+#include "s3c24xx-i2s.h"
+
+static unsigned int rates[] = {
+ 11025,
+ 22050,
+ 44100,
+};
+
+static struct snd_pcm_hw_constraint_list hw_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static struct snd_soc_jack hp_jack;
+
+static struct snd_soc_jack_pin hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Speaker",
+ .mask = SND_JACK_HEADPHONE,
+ .invert = 1,
+ },
+};
+
+static struct snd_soc_jack_gpio hp_jack_gpios[] = {
+ {
+ .gpio = S3C2410_GPG(4),
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .invert = 1,
+ .debounce_time = 200,
+ },
+};
+
+static int h1940_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.rate_min = hw_rates.list[0];
+ runtime->hw.rate_max = hw_rates.list[hw_rates.count - 1];
+ runtime->hw.rates = SNDRV_PCM_RATE_KNOT;
+
+ return snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_rates);
+}
+
+static int h1940_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int div;
+ int ret;
+ unsigned int rate = params_rate(params);
+
+ switch (rate) {
+ case 11025:
+ case 22050:
+ case 44100:
+ div = s3c24xx_i2s_get_clockrate() / (384 * rate);
+ if (s3c24xx_i2s_get_clockrate() % (384 * rate) > (192 * rate))
+ div++;
+ break;
+ default:
+ dev_err(&rtd->dev, "%s: rate %d is not supported\n",
+ __func__, rate);
+ return -EINVAL;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* select clock source */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_PCLK, rate,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ /* set MCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+ S3C2410_IISMOD_384FS);
+ if (ret < 0)
+ return ret;
+
+ /* set BCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
+ S3C2410_IISMOD_32FS);
+ if (ret < 0)
+ return ret;
+
+ /* set prescaler division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ S3C24XX_PRESCALE(div, div));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops h1940_ops = {
+ .startup = h1940_startup,
+ .hw_params = h1940_hw_params,
+};
+
+static int h1940_spk_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpio_set_value(H1940_LATCH_AUDIO_POWER, 1);
+ else
+ gpio_set_value(H1940_LATCH_AUDIO_POWER, 0);
+
+ return 0;
+}
+
+/* h1940 machine dapm widgets */
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", h1940_spk_power),
+};
+
+/* h1940 machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* headphone connected to VOUTLHP, VOUTRHP */
+ {"Headphone Jack", NULL, "VOUTLHP"},
+ {"Headphone Jack", NULL, "VOUTRHP"},
+
+ /* ext speaker connected to VOUTL, VOUTR */
+ {"Speaker", NULL, "VOUTL"},
+ {"Speaker", NULL, "VOUTR"},
+
+ /* mic is connected to VINM */
+ {"VINM", NULL, "Mic Jack"},
+};
+
+static struct platform_device *s3c24xx_snd_device;
+
+static int h1940_uda1380_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ /* Add h1940 specific widgets */
+ err = snd_soc_dapm_new_controls(dapm, uda1380_dapm_widgets,
+ ARRAY_SIZE(uda1380_dapm_widgets));
+ if (err)
+ return err;
+
+ /* Set up h1940 specific audio path audio_mapnects */
+ err = snd_soc_dapm_add_routes(dapm, audio_map,
+ ARRAY_SIZE(audio_map));
+ if (err)
+ return err;
+
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Speaker");
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+
+ snd_soc_dapm_sync(dapm);
+
+ snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE,
+ &hp_jack);
+
+ snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins),
+ hp_jack_pins);
+
+ snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
+ hp_jack_gpios);
+
+ return 0;
+}
+
+/* s3c24xx digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link h1940_uda1380_dai[] = {
+ {
+ .name = "uda1380",
+ .stream_name = "UDA1380 Duplex",
+ .cpu_dai_name = "s3c24xx-iis",
+ .codec_dai_name = "uda1380-hifi",
+ .init = h1940_uda1380_init,
+ .platform_name = "samsung-audio",
+ .codec_name = "uda1380-codec.0-001a",
+ .ops = &h1940_ops,
+ },
+};
+
+static struct snd_soc_card h1940_asoc = {
+ .name = "h1940",
+ .dai_link = h1940_uda1380_dai,
+ .num_links = ARRAY_SIZE(h1940_uda1380_dai),
+};
+
+static int __init h1940_init(void)
+{
+ int ret;
+
+ if (!machine_is_h1940())
+ return -ENODEV;
+
+ /* configure some gpios */
+ ret = gpio_request(H1940_LATCH_AUDIO_POWER, "speaker-power");
+ if (ret)
+ goto err_out;
+
+ ret = gpio_direction_output(H1940_LATCH_AUDIO_POWER, 0);
+ if (ret)
+ goto err_gpio;
+
+ s3c24xx_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!s3c24xx_snd_device) {
+ ret = -ENOMEM;
+ goto err_gpio;
+ }
+
+ platform_set_drvdata(s3c24xx_snd_device, &h1940_asoc);
+ ret = platform_device_add(s3c24xx_snd_device);
+
+ if (ret)
+ goto err_plat;
+
+ return 0;
+
+err_plat:
+ platform_device_put(s3c24xx_snd_device);
+err_gpio:
+ gpio_free(H1940_LATCH_AUDIO_POWER);
+
+err_out:
+ return ret;
+}
+
+static void __exit h1940_exit(void)
+{
+ platform_device_unregister(s3c24xx_snd_device);
+ snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
+ hp_jack_gpios);
+ gpio_free(H1940_LATCH_AUDIO_POWER);
+}
+
+module_init(h1940_init);
+module_exit(h1940_exit);
+
+/* Module information */
+MODULE_AUTHOR("Arnaud Patard, Vasily Khoruzhick");
+MODULE_DESCRIPTION("ALSA SoC H1940");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c
new file mode 100644
index 00000000..992a732b
--- /dev/null
+++ b/sound/soc/samsung/i2s.c
@@ -0,0 +1,1257 @@
+/* sound/soc/samsung/i2s.c
+ *
+ * ALSA SoC Audio Layer - Samsung I2S Controller driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd.
+ * Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <plat/audio.h>
+
+#include "dma.h"
+#include "i2s.h"
+
+#define I2SCON 0x0
+#define I2SMOD 0x4
+#define I2SFIC 0x8
+#define I2SPSR 0xc
+#define I2STXD 0x10
+#define I2SRXD 0x14
+#define I2SFICS 0x18
+#define I2STXDS 0x1c
+
+#define CON_RSTCLR (1 << 31)
+#define CON_FRXOFSTATUS (1 << 26)
+#define CON_FRXORINTEN (1 << 25)
+#define CON_FTXSURSTAT (1 << 24)
+#define CON_FTXSURINTEN (1 << 23)
+#define CON_TXSDMA_PAUSE (1 << 20)
+#define CON_TXSDMA_ACTIVE (1 << 18)
+
+#define CON_FTXURSTATUS (1 << 17)
+#define CON_FTXURINTEN (1 << 16)
+#define CON_TXFIFO2_EMPTY (1 << 15)
+#define CON_TXFIFO1_EMPTY (1 << 14)
+#define CON_TXFIFO2_FULL (1 << 13)
+#define CON_TXFIFO1_FULL (1 << 12)
+
+#define CON_LRINDEX (1 << 11)
+#define CON_TXFIFO_EMPTY (1 << 10)
+#define CON_RXFIFO_EMPTY (1 << 9)
+#define CON_TXFIFO_FULL (1 << 8)
+#define CON_RXFIFO_FULL (1 << 7)
+#define CON_TXDMA_PAUSE (1 << 6)
+#define CON_RXDMA_PAUSE (1 << 5)
+#define CON_TXCH_PAUSE (1 << 4)
+#define CON_RXCH_PAUSE (1 << 3)
+#define CON_TXDMA_ACTIVE (1 << 2)
+#define CON_RXDMA_ACTIVE (1 << 1)
+#define CON_ACTIVE (1 << 0)
+
+#define MOD_OPCLK_CDCLK_OUT (0 << 30)
+#define MOD_OPCLK_CDCLK_IN (1 << 30)
+#define MOD_OPCLK_BCLK_OUT (2 << 30)
+#define MOD_OPCLK_PCLK (3 << 30)
+#define MOD_OPCLK_MASK (3 << 30)
+#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */
+
+#define MOD_BLCS_SHIFT 26
+#define MOD_BLCS_16BIT (0 << MOD_BLCS_SHIFT)
+#define MOD_BLCS_8BIT (1 << MOD_BLCS_SHIFT)
+#define MOD_BLCS_24BIT (2 << MOD_BLCS_SHIFT)
+#define MOD_BLCS_MASK (3 << MOD_BLCS_SHIFT)
+#define MOD_BLCP_SHIFT 24
+#define MOD_BLCP_16BIT (0 << MOD_BLCP_SHIFT)
+#define MOD_BLCP_8BIT (1 << MOD_BLCP_SHIFT)
+#define MOD_BLCP_24BIT (2 << MOD_BLCP_SHIFT)
+#define MOD_BLCP_MASK (3 << MOD_BLCP_SHIFT)
+
+#define MOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */
+#define MOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */
+#define MOD_C1DD_HHALF (1 << 19)
+#define MOD_C1DD_LHALF (1 << 18)
+#define MOD_DC2_EN (1 << 17)
+#define MOD_DC1_EN (1 << 16)
+#define MOD_BLC_16BIT (0 << 13)
+#define MOD_BLC_8BIT (1 << 13)
+#define MOD_BLC_24BIT (2 << 13)
+#define MOD_BLC_MASK (3 << 13)
+
+#define MOD_IMS_SYSMUX (1 << 10)
+#define MOD_SLAVE (1 << 11)
+#define MOD_TXONLY (0 << 8)
+#define MOD_RXONLY (1 << 8)
+#define MOD_TXRX (2 << 8)
+#define MOD_MASK (3 << 8)
+#define MOD_LR_LLOW (0 << 7)
+#define MOD_LR_RLOW (1 << 7)
+#define MOD_SDF_IIS (0 << 5)
+#define MOD_SDF_MSB (1 << 5)
+#define MOD_SDF_LSB (2 << 5)
+#define MOD_SDF_MASK (3 << 5)
+#define MOD_RCLK_256FS (0 << 3)
+#define MOD_RCLK_512FS (1 << 3)
+#define MOD_RCLK_384FS (2 << 3)
+#define MOD_RCLK_768FS (3 << 3)
+#define MOD_RCLK_MASK (3 << 3)
+#define MOD_BCLK_32FS (0 << 1)
+#define MOD_BCLK_48FS (1 << 1)
+#define MOD_BCLK_16FS (2 << 1)
+#define MOD_BCLK_24FS (3 << 1)
+#define MOD_BCLK_MASK (3 << 1)
+#define MOD_8BIT (1 << 0)
+
+#define MOD_CDCLKCON (1 << 12)
+
+#define PSR_PSREN (1 << 15)
+
+#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf)
+#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf)
+
+#define FIC_TXFLUSH (1 << 15)
+#define FIC_RXFLUSH (1 << 7)
+#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf)
+#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf)
+#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f)
+
+#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
+
+struct i2s_dai {
+ /* Platform device for this DAI */
+ struct platform_device *pdev;
+ /* IOREMAP'd SFRs */
+ void __iomem *addr;
+ /* Physical base address of SFRs */
+ u32 base;
+ /* Rate of RCLK source clock */
+ unsigned long rclk_srcrate;
+ /* Frame Clock */
+ unsigned frmclk;
+ /*
+ * Specifically requested RCLK,BCLK by MACHINE Driver.
+ * 0 indicates CPU driver is free to choose any value.
+ */
+ unsigned rfs, bfs;
+ /* I2S Controller's core clock */
+ struct clk *clk;
+ /* Clock for generating I2S signals */
+ struct clk *op_clk;
+ /* Array of clock names for op_clk */
+ const char **src_clk;
+ /* Pointer to the Primary_Fifo if this is Sec_Fifo, NULL otherwise */
+ struct i2s_dai *pri_dai;
+ /* Pointer to the Secondary_Fifo if it has one, NULL otherwise */
+ struct i2s_dai *sec_dai;
+#define DAI_OPENED (1 << 0) /* Dai is opened */
+#define DAI_MANAGER (1 << 1) /* Dai is the manager */
+ unsigned mode;
+ /* Driver for this DAI */
+ struct snd_soc_dai_driver i2s_dai_drv;
+ /* DMA parameters */
+ struct s3c_dma_params dma_playback;
+ struct s3c_dma_params dma_capture;
+ u32 quirks;
+ u32 suspend_i2smod;
+ u32 suspend_i2scon;
+ u32 suspend_i2spsr;
+};
+
+/* Lock for cross i/f checks */
+static DEFINE_SPINLOCK(lock);
+
+/* If this is the 'overlay' stereo DAI */
+static inline bool is_secondary(struct i2s_dai *i2s)
+{
+ return i2s->pri_dai ? true : false;
+}
+
+/* If operating in SoC-Slave mode */
+static inline bool is_slave(struct i2s_dai *i2s)
+{
+ return (readl(i2s->addr + I2SMOD) & MOD_SLAVE) ? true : false;
+}
+
+/* If this interface of the controller is transmitting data */
+static inline bool tx_active(struct i2s_dai *i2s)
+{
+ u32 active;
+
+ if (!i2s)
+ return false;
+
+ active = readl(i2s->addr + I2SCON);
+
+ if (is_secondary(i2s))
+ active &= CON_TXSDMA_ACTIVE;
+ else
+ active &= CON_TXDMA_ACTIVE;
+
+ return active ? true : false;
+}
+
+/* If the other interface of the controller is transmitting data */
+static inline bool other_tx_active(struct i2s_dai *i2s)
+{
+ struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
+
+ return tx_active(other);
+}
+
+/* If any interface of the controller is transmitting data */
+static inline bool any_tx_active(struct i2s_dai *i2s)
+{
+ return tx_active(i2s) || other_tx_active(i2s);
+}
+
+/* If this interface of the controller is receiving data */
+static inline bool rx_active(struct i2s_dai *i2s)
+{
+ u32 active;
+
+ if (!i2s)
+ return false;
+
+ active = readl(i2s->addr + I2SCON) & CON_RXDMA_ACTIVE;
+
+ return active ? true : false;
+}
+
+/* If the other interface of the controller is receiving data */
+static inline bool other_rx_active(struct i2s_dai *i2s)
+{
+ struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
+
+ return rx_active(other);
+}
+
+/* If any interface of the controller is receiving data */
+static inline bool any_rx_active(struct i2s_dai *i2s)
+{
+ return rx_active(i2s) || other_rx_active(i2s);
+}
+
+/* If the other DAI is transmitting or receiving data */
+static inline bool other_active(struct i2s_dai *i2s)
+{
+ return other_rx_active(i2s) || other_tx_active(i2s);
+}
+
+/* If this DAI is transmitting or receiving data */
+static inline bool this_active(struct i2s_dai *i2s)
+{
+ return tx_active(i2s) || rx_active(i2s);
+}
+
+/* If the controller is active anyway */
+static inline bool any_active(struct i2s_dai *i2s)
+{
+ return this_active(i2s) || other_active(i2s);
+}
+
+static inline struct i2s_dai *to_info(struct snd_soc_dai *dai)
+{
+ return snd_soc_dai_get_drvdata(dai);
+}
+
+static inline bool is_opened(struct i2s_dai *i2s)
+{
+ if (i2s && (i2s->mode & DAI_OPENED))
+ return true;
+ else
+ return false;
+}
+
+static inline bool is_manager(struct i2s_dai *i2s)
+{
+ if (is_opened(i2s) && (i2s->mode & DAI_MANAGER))
+ return true;
+ else
+ return false;
+}
+
+/* Read RCLK of I2S (in multiples of LRCLK) */
+static inline unsigned get_rfs(struct i2s_dai *i2s)
+{
+ u32 rfs = (readl(i2s->addr + I2SMOD) >> 3) & 0x3;
+
+ switch (rfs) {
+ case 3: return 768;
+ case 2: return 384;
+ case 1: return 512;
+ default: return 256;
+ }
+}
+
+/* Write RCLK of I2S (in multiples of LRCLK) */
+static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs)
+{
+ u32 mod = readl(i2s->addr + I2SMOD);
+
+ mod &= ~MOD_RCLK_MASK;
+
+ switch (rfs) {
+ case 768:
+ mod |= MOD_RCLK_768FS;
+ break;
+ case 512:
+ mod |= MOD_RCLK_512FS;
+ break;
+ case 384:
+ mod |= MOD_RCLK_384FS;
+ break;
+ default:
+ mod |= MOD_RCLK_256FS;
+ break;
+ }
+
+ writel(mod, i2s->addr + I2SMOD);
+}
+
+/* Read Bit-Clock of I2S (in multiples of LRCLK) */
+static inline unsigned get_bfs(struct i2s_dai *i2s)
+{
+ u32 bfs = (readl(i2s->addr + I2SMOD) >> 1) & 0x3;
+
+ switch (bfs) {
+ case 3: return 24;
+ case 2: return 16;
+ case 1: return 48;
+ default: return 32;
+ }
+}
+
+/* Write Bit-Clock of I2S (in multiples of LRCLK) */
+static inline void set_bfs(struct i2s_dai *i2s, unsigned bfs)
+{
+ u32 mod = readl(i2s->addr + I2SMOD);
+
+ mod &= ~MOD_BCLK_MASK;
+
+ switch (bfs) {
+ case 48:
+ mod |= MOD_BCLK_48FS;
+ break;
+ case 32:
+ mod |= MOD_BCLK_32FS;
+ break;
+ case 24:
+ mod |= MOD_BCLK_24FS;
+ break;
+ case 16:
+ mod |= MOD_BCLK_16FS;
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "Wrong BCLK Divider!\n");
+ return;
+ }
+
+ writel(mod, i2s->addr + I2SMOD);
+}
+
+/* Sample-Size */
+static inline int get_blc(struct i2s_dai *i2s)
+{
+ int blc = readl(i2s->addr + I2SMOD);
+
+ blc = (blc >> 13) & 0x3;
+
+ switch (blc) {
+ case 2: return 24;
+ case 1: return 8;
+ default: return 16;
+ }
+}
+
+/* TX Channel Control */
+static void i2s_txctrl(struct i2s_dai *i2s, int on)
+{
+ void __iomem *addr = i2s->addr;
+ u32 con = readl(addr + I2SCON);
+ u32 mod = readl(addr + I2SMOD) & ~MOD_MASK;
+
+ if (on) {
+ con |= CON_ACTIVE;
+ con &= ~CON_TXCH_PAUSE;
+
+ if (is_secondary(i2s)) {
+ con |= CON_TXSDMA_ACTIVE;
+ con &= ~CON_TXSDMA_PAUSE;
+ } else {
+ con |= CON_TXDMA_ACTIVE;
+ con &= ~CON_TXDMA_PAUSE;
+ }
+
+ if (any_rx_active(i2s))
+ mod |= MOD_TXRX;
+ else
+ mod |= MOD_TXONLY;
+ } else {
+ if (is_secondary(i2s)) {
+ con |= CON_TXSDMA_PAUSE;
+ con &= ~CON_TXSDMA_ACTIVE;
+ } else {
+ con |= CON_TXDMA_PAUSE;
+ con &= ~CON_TXDMA_ACTIVE;
+ }
+
+ if (other_tx_active(i2s)) {
+ writel(con, addr + I2SCON);
+ return;
+ }
+
+ con |= CON_TXCH_PAUSE;
+
+ if (any_rx_active(i2s))
+ mod |= MOD_RXONLY;
+ else
+ con &= ~CON_ACTIVE;
+ }
+
+ writel(mod, addr + I2SMOD);
+ writel(con, addr + I2SCON);
+}
+
+/* RX Channel Control */
+static void i2s_rxctrl(struct i2s_dai *i2s, int on)
+{
+ void __iomem *addr = i2s->addr;
+ u32 con = readl(addr + I2SCON);
+ u32 mod = readl(addr + I2SMOD) & ~MOD_MASK;
+
+ if (on) {
+ con |= CON_RXDMA_ACTIVE | CON_ACTIVE;
+ con &= ~(CON_RXDMA_PAUSE | CON_RXCH_PAUSE);
+
+ if (any_tx_active(i2s))
+ mod |= MOD_TXRX;
+ else
+ mod |= MOD_RXONLY;
+ } else {
+ con |= CON_RXDMA_PAUSE | CON_RXCH_PAUSE;
+ con &= ~CON_RXDMA_ACTIVE;
+
+ if (any_tx_active(i2s))
+ mod |= MOD_TXONLY;
+ else
+ con &= ~CON_ACTIVE;
+ }
+
+ writel(mod, addr + I2SMOD);
+ writel(con, addr + I2SCON);
+}
+
+/* Flush FIFO of an interface */
+static inline void i2s_fifo(struct i2s_dai *i2s, u32 flush)
+{
+ void __iomem *fic;
+ u32 val;
+
+ if (!i2s)
+ return;
+
+ if (is_secondary(i2s))
+ fic = i2s->addr + I2SFICS;
+ else
+ fic = i2s->addr + I2SFIC;
+
+ /* Flush the FIFO */
+ writel(readl(fic) | flush, fic);
+
+ /* Be patient */
+ val = msecs_to_loops(1) / 1000; /* 1 usec */
+ while (--val)
+ cpu_relax();
+
+ writel(readl(fic) & ~flush, fic);
+}
+
+static int i2s_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int rfs, int dir)
+{
+ struct i2s_dai *i2s = to_info(dai);
+ struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
+ u32 mod = readl(i2s->addr + I2SMOD);
+
+ switch (clk_id) {
+ case SAMSUNG_I2S_CDCLK:
+ /* Shouldn't matter in GATING(CLOCK_IN) mode */
+ if (dir == SND_SOC_CLOCK_IN)
+ rfs = 0;
+
+ if ((rfs && other->rfs && (other->rfs != rfs)) ||
+ (any_active(i2s) &&
+ (((dir == SND_SOC_CLOCK_IN)
+ && !(mod & MOD_CDCLKCON)) ||
+ ((dir == SND_SOC_CLOCK_OUT)
+ && (mod & MOD_CDCLKCON))))) {
+ dev_err(&i2s->pdev->dev,
+ "%s:%d Other DAI busy\n", __func__, __LINE__);
+ return -EAGAIN;
+ }
+
+ if (dir == SND_SOC_CLOCK_IN)
+ mod |= MOD_CDCLKCON;
+ else
+ mod &= ~MOD_CDCLKCON;
+
+ i2s->rfs = rfs;
+ break;
+
+ case SAMSUNG_I2S_RCLKSRC_0: /* clock corrsponding to IISMOD[10] := 0 */
+ case SAMSUNG_I2S_RCLKSRC_1: /* clock corrsponding to IISMOD[10] := 1 */
+ if ((i2s->quirks & QUIRK_NO_MUXPSR)
+ || (clk_id == SAMSUNG_I2S_RCLKSRC_0))
+ clk_id = 0;
+ else
+ clk_id = 1;
+
+ if (!any_active(i2s)) {
+ if (i2s->op_clk) {
+ if ((clk_id && !(mod & MOD_IMS_SYSMUX)) ||
+ (!clk_id && (mod & MOD_IMS_SYSMUX))) {
+ clk_disable(i2s->op_clk);
+ clk_put(i2s->op_clk);
+ } else {
+ i2s->rclk_srcrate =
+ clk_get_rate(i2s->op_clk);
+ return 0;
+ }
+ }
+
+ i2s->op_clk = clk_get(&i2s->pdev->dev,
+ i2s->src_clk[clk_id]);
+ clk_enable(i2s->op_clk);
+ i2s->rclk_srcrate = clk_get_rate(i2s->op_clk);
+
+ /* Over-ride the other's */
+ if (other) {
+ other->op_clk = i2s->op_clk;
+ other->rclk_srcrate = i2s->rclk_srcrate;
+ }
+ } else if ((!clk_id && (mod & MOD_IMS_SYSMUX))
+ || (clk_id && !(mod & MOD_IMS_SYSMUX))) {
+ dev_err(&i2s->pdev->dev,
+ "%s:%d Other DAI busy\n", __func__, __LINE__);
+ return -EAGAIN;
+ } else {
+ /* Call can't be on the active DAI */
+ i2s->op_clk = other->op_clk;
+ i2s->rclk_srcrate = other->rclk_srcrate;
+ return 0;
+ }
+
+ if (clk_id == 0)
+ mod &= ~MOD_IMS_SYSMUX;
+ else
+ mod |= MOD_IMS_SYSMUX;
+ break;
+
+ default:
+ dev_err(&i2s->pdev->dev, "We don't serve that!\n");
+ return -EINVAL;
+ }
+
+ writel(mod, i2s->addr + I2SMOD);
+
+ return 0;
+}
+
+static int i2s_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct i2s_dai *i2s = to_info(dai);
+ u32 mod = readl(i2s->addr + I2SMOD);
+ u32 tmp = 0;
+
+ /* Format is priority */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ tmp |= MOD_LR_RLOW;
+ tmp |= MOD_SDF_MSB;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ tmp |= MOD_LR_RLOW;
+ tmp |= MOD_SDF_LSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ tmp |= MOD_SDF_IIS;
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "Format not supported\n");
+ return -EINVAL;
+ }
+
+ /*
+ * INV flag is relative to the FORMAT flag - if set it simply
+ * flips the polarity specified by the Standard
+ */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ if (tmp & MOD_LR_RLOW)
+ tmp &= ~MOD_LR_RLOW;
+ else
+ tmp |= MOD_LR_RLOW;
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "Polarity not supported\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ tmp |= MOD_SLAVE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* Set default source clock in Master mode */
+ if (i2s->rclk_srcrate == 0)
+ i2s_set_sysclk(dai, SAMSUNG_I2S_RCLKSRC_0,
+ 0, SND_SOC_CLOCK_IN);
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "master/slave format not supported\n");
+ return -EINVAL;
+ }
+
+ if (any_active(i2s) &&
+ ((mod & (MOD_SDF_MASK | MOD_LR_RLOW
+ | MOD_SLAVE)) != tmp)) {
+ dev_err(&i2s->pdev->dev,
+ "%s:%d Other DAI busy\n", __func__, __LINE__);
+ return -EAGAIN;
+ }
+
+ mod &= ~(MOD_SDF_MASK | MOD_LR_RLOW | MOD_SLAVE);
+ mod |= tmp;
+ writel(mod, i2s->addr + I2SMOD);
+
+ return 0;
+}
+
+static int i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct i2s_dai *i2s = to_info(dai);
+ u32 mod = readl(i2s->addr + I2SMOD);
+
+ if (!is_secondary(i2s))
+ mod &= ~(MOD_DC2_EN | MOD_DC1_EN);
+
+ switch (params_channels(params)) {
+ case 6:
+ mod |= MOD_DC2_EN;
+ case 4:
+ mod |= MOD_DC1_EN;
+ break;
+ case 2:
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "%d channels not supported\n",
+ params_channels(params));
+ return -EINVAL;
+ }
+
+ if (is_secondary(i2s))
+ mod &= ~MOD_BLCS_MASK;
+ else
+ mod &= ~MOD_BLCP_MASK;
+
+ if (is_manager(i2s))
+ mod &= ~MOD_BLC_MASK;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ if (is_secondary(i2s))
+ mod |= MOD_BLCS_8BIT;
+ else
+ mod |= MOD_BLCP_8BIT;
+ if (is_manager(i2s))
+ mod |= MOD_BLC_8BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (is_secondary(i2s))
+ mod |= MOD_BLCS_16BIT;
+ else
+ mod |= MOD_BLCP_16BIT;
+ if (is_manager(i2s))
+ mod |= MOD_BLC_16BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ if (is_secondary(i2s))
+ mod |= MOD_BLCS_24BIT;
+ else
+ mod |= MOD_BLCP_24BIT;
+ if (is_manager(i2s))
+ mod |= MOD_BLC_24BIT;
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "Format(%d) not supported\n",
+ params_format(params));
+ return -EINVAL;
+ }
+ writel(mod, i2s->addr + I2SMOD);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dai_set_dma_data(dai, substream,
+ (void *)&i2s->dma_playback);
+ else
+ snd_soc_dai_set_dma_data(dai, substream,
+ (void *)&i2s->dma_capture);
+
+ i2s->frmclk = params_rate(params);
+
+ return 0;
+}
+
+/* We set constraints on the substream acc to the version of I2S */
+static int i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct i2s_dai *i2s = to_info(dai);
+ struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
+ unsigned long flags;
+
+ spin_lock_irqsave(&lock, flags);
+
+ i2s->mode |= DAI_OPENED;
+
+ if (is_manager(other))
+ i2s->mode &= ~DAI_MANAGER;
+ else
+ i2s->mode |= DAI_MANAGER;
+
+ /* Enforce set_sysclk in Master mode */
+ i2s->rclk_srcrate = 0;
+
+ spin_unlock_irqrestore(&lock, flags);
+
+ return 0;
+}
+
+static void i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct i2s_dai *i2s = to_info(dai);
+ struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
+ unsigned long flags;
+
+ spin_lock_irqsave(&lock, flags);
+
+ i2s->mode &= ~DAI_OPENED;
+ i2s->mode &= ~DAI_MANAGER;
+
+ if (is_opened(other))
+ other->mode |= DAI_MANAGER;
+
+ /* Reset any constraint on RFS and BFS */
+ i2s->rfs = 0;
+ i2s->bfs = 0;
+
+ spin_unlock_irqrestore(&lock, flags);
+
+ /* Gate CDCLK by default */
+ if (!is_opened(other))
+ i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK,
+ 0, SND_SOC_CLOCK_IN);
+}
+
+static int config_setup(struct i2s_dai *i2s)
+{
+ struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
+ unsigned rfs, bfs, blc;
+ u32 psr;
+
+ blc = get_blc(i2s);
+
+ bfs = i2s->bfs;
+
+ if (!bfs && other)
+ bfs = other->bfs;
+
+ /* Select least possible multiple(2) if no constraint set */
+ if (!bfs)
+ bfs = blc * 2;
+
+ rfs = i2s->rfs;
+
+ if (!rfs && other)
+ rfs = other->rfs;
+
+ if ((rfs == 256 || rfs == 512) && (blc == 24)) {
+ dev_err(&i2s->pdev->dev,
+ "%d-RFS not supported for 24-blc\n", rfs);
+ return -EINVAL;
+ }
+
+ if (!rfs) {
+ if (bfs == 16 || bfs == 32)
+ rfs = 256;
+ else
+ rfs = 384;
+ }
+
+ /* If already setup and running */
+ if (any_active(i2s) && (get_rfs(i2s) != rfs || get_bfs(i2s) != bfs)) {
+ dev_err(&i2s->pdev->dev,
+ "%s:%d Other DAI busy\n", __func__, __LINE__);
+ return -EAGAIN;
+ }
+
+ /* Don't bother RFS, BFS & PSR in Slave mode */
+ if (is_slave(i2s))
+ return 0;
+
+ set_bfs(i2s, bfs);
+ set_rfs(i2s, rfs);
+
+ if (!(i2s->quirks & QUIRK_NO_MUXPSR)) {
+ psr = i2s->rclk_srcrate / i2s->frmclk / rfs;
+ writel(((psr - 1) << 8) | PSR_PSREN, i2s->addr + I2SPSR);
+ dev_dbg(&i2s->pdev->dev,
+ "RCLK_SRC=%luHz PSR=%u, RCLK=%dfs, BCLK=%dfs\n",
+ i2s->rclk_srcrate, psr, rfs, bfs);
+ }
+
+ return 0;
+}
+
+static int i2s_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct i2s_dai *i2s = to_info(rtd->cpu_dai);
+ unsigned long flags;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ local_irq_save(flags);
+
+ if (config_setup(i2s)) {
+ local_irq_restore(flags);
+ return -EINVAL;
+ }
+
+ if (capture)
+ i2s_rxctrl(i2s, 1);
+ else
+ i2s_txctrl(i2s, 1);
+
+ local_irq_restore(flags);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ local_irq_save(flags);
+
+ if (capture)
+ i2s_rxctrl(i2s, 0);
+ else
+ i2s_txctrl(i2s, 0);
+
+ if (capture)
+ i2s_fifo(i2s, FIC_RXFLUSH);
+ else
+ i2s_fifo(i2s, FIC_TXFLUSH);
+
+ local_irq_restore(flags);
+ break;
+ }
+
+ return 0;
+}
+
+static int i2s_set_clkdiv(struct snd_soc_dai *dai,
+ int div_id, int div)
+{
+ struct i2s_dai *i2s = to_info(dai);
+ struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
+
+ switch (div_id) {
+ case SAMSUNG_I2S_DIV_BCLK:
+ if ((any_active(i2s) && div && (get_bfs(i2s) != div))
+ || (other && other->bfs && (other->bfs != div))) {
+ dev_err(&i2s->pdev->dev,
+ "%s:%d Other DAI busy\n", __func__, __LINE__);
+ return -EAGAIN;
+ }
+ i2s->bfs = div;
+ break;
+ default:
+ dev_err(&i2s->pdev->dev,
+ "Invalid clock divider(%d)\n", div_id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_sframes_t
+i2s_delay(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct i2s_dai *i2s = to_info(dai);
+ u32 reg = readl(i2s->addr + I2SFIC);
+ snd_pcm_sframes_t delay;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ delay = FIC_RXCOUNT(reg);
+ else if (is_secondary(i2s))
+ delay = FICS_TXCOUNT(readl(i2s->addr + I2SFICS));
+ else
+ delay = FIC_TXCOUNT(reg);
+
+ return delay;
+}
+
+#ifdef CONFIG_PM
+static int i2s_suspend(struct snd_soc_dai *dai)
+{
+ struct i2s_dai *i2s = to_info(dai);
+
+ if (dai->active) {
+ i2s->suspend_i2smod = readl(i2s->addr + I2SMOD);
+ i2s->suspend_i2scon = readl(i2s->addr + I2SCON);
+ i2s->suspend_i2spsr = readl(i2s->addr + I2SPSR);
+ }
+
+ return 0;
+}
+
+static int i2s_resume(struct snd_soc_dai *dai)
+{
+ struct i2s_dai *i2s = to_info(dai);
+
+ if (dai->active) {
+ writel(i2s->suspend_i2scon, i2s->addr + I2SCON);
+ writel(i2s->suspend_i2smod, i2s->addr + I2SMOD);
+ writel(i2s->suspend_i2spsr, i2s->addr + I2SPSR);
+ }
+
+ return 0;
+}
+#else
+#define i2s_suspend NULL
+#define i2s_resume NULL
+#endif
+
+static int samsung_i2s_dai_probe(struct snd_soc_dai *dai)
+{
+ struct i2s_dai *i2s = to_info(dai);
+ struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
+
+ if (other && other->clk) /* If this is probe on secondary */
+ goto probe_exit;
+
+ i2s->addr = ioremap(i2s->base, 0x100);
+ if (i2s->addr == NULL) {
+ dev_err(&i2s->pdev->dev, "cannot ioremap registers\n");
+ return -ENXIO;
+ }
+
+ i2s->clk = clk_get(&i2s->pdev->dev, "iis");
+ if (IS_ERR(i2s->clk)) {
+ dev_err(&i2s->pdev->dev, "failed to get i2s_clock\n");
+ iounmap(i2s->addr);
+ return -ENOENT;
+ }
+ clk_enable(i2s->clk);
+
+ if (other) {
+ other->addr = i2s->addr;
+ other->clk = i2s->clk;
+ }
+
+ if (i2s->quirks & QUIRK_NEED_RSTCLR)
+ writel(CON_RSTCLR, i2s->addr + I2SCON);
+
+probe_exit:
+ /* Reset any constraint on RFS and BFS */
+ i2s->rfs = 0;
+ i2s->bfs = 0;
+ i2s_txctrl(i2s, 0);
+ i2s_rxctrl(i2s, 0);
+ i2s_fifo(i2s, FIC_TXFLUSH);
+ i2s_fifo(other, FIC_TXFLUSH);
+ i2s_fifo(i2s, FIC_RXFLUSH);
+
+ /* Gate CDCLK by default */
+ if (!is_opened(other))
+ i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK,
+ 0, SND_SOC_CLOCK_IN);
+
+ return 0;
+}
+
+static int samsung_i2s_dai_remove(struct snd_soc_dai *dai)
+{
+ struct i2s_dai *i2s = snd_soc_dai_get_drvdata(dai);
+ struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
+
+ if (!other || !other->clk) {
+
+ if (i2s->quirks & QUIRK_NEED_RSTCLR)
+ writel(0, i2s->addr + I2SCON);
+
+ clk_disable(i2s->clk);
+ clk_put(i2s->clk);
+
+ iounmap(i2s->addr);
+ }
+
+ i2s->clk = NULL;
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops samsung_i2s_dai_ops = {
+ .trigger = i2s_trigger,
+ .hw_params = i2s_hw_params,
+ .set_fmt = i2s_set_fmt,
+ .set_clkdiv = i2s_set_clkdiv,
+ .set_sysclk = i2s_set_sysclk,
+ .startup = i2s_startup,
+ .shutdown = i2s_shutdown,
+ .delay = i2s_delay,
+};
+
+#define SAMSUNG_I2S_RATES SNDRV_PCM_RATE_8000_96000
+
+#define SAMSUNG_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static __devinit
+struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec)
+{
+ struct i2s_dai *i2s;
+
+ i2s = kzalloc(sizeof(struct i2s_dai), GFP_KERNEL);
+ if (i2s == NULL)
+ return NULL;
+
+ i2s->pdev = pdev;
+ i2s->pri_dai = NULL;
+ i2s->sec_dai = NULL;
+ i2s->i2s_dai_drv.symmetric_rates = 1;
+ i2s->i2s_dai_drv.probe = samsung_i2s_dai_probe;
+ i2s->i2s_dai_drv.remove = samsung_i2s_dai_remove;
+ i2s->i2s_dai_drv.ops = &samsung_i2s_dai_ops;
+ i2s->i2s_dai_drv.suspend = i2s_suspend;
+ i2s->i2s_dai_drv.resume = i2s_resume;
+ i2s->i2s_dai_drv.playback.channels_min = 2;
+ i2s->i2s_dai_drv.playback.channels_max = 2;
+ i2s->i2s_dai_drv.playback.rates = SAMSUNG_I2S_RATES;
+ i2s->i2s_dai_drv.playback.formats = SAMSUNG_I2S_FMTS;
+
+ if (!sec) {
+ i2s->i2s_dai_drv.capture.channels_min = 2;
+ i2s->i2s_dai_drv.capture.channels_max = 2;
+ i2s->i2s_dai_drv.capture.rates = SAMSUNG_I2S_RATES;
+ i2s->i2s_dai_drv.capture.formats = SAMSUNG_I2S_FMTS;
+ } else { /* Create a new platform_device for Secondary */
+ i2s->pdev = platform_device_register_resndata(NULL,
+ pdev->name, pdev->id + SAMSUNG_I2S_SECOFF,
+ NULL, 0, NULL, 0);
+ if (IS_ERR(i2s->pdev)) {
+ kfree(i2s);
+ return NULL;
+ }
+ }
+
+ /* Pre-assign snd_soc_dai_set_drvdata */
+ dev_set_drvdata(&i2s->pdev->dev, i2s);
+
+ return i2s;
+}
+
+static __devinit int samsung_i2s_probe(struct platform_device *pdev)
+{
+ u32 dma_pl_chan, dma_cp_chan, dma_pl_sec_chan;
+ struct i2s_dai *pri_dai, *sec_dai = NULL;
+ struct s3c_audio_pdata *i2s_pdata;
+ struct samsung_i2s *i2s_cfg;
+ struct resource *res;
+ u32 regs_base, quirks;
+ int ret = 0;
+
+ /* Call during Seconday interface registration */
+ if (pdev->id >= SAMSUNG_I2S_SECOFF) {
+ sec_dai = dev_get_drvdata(&pdev->dev);
+ snd_soc_register_dai(&sec_dai->pdev->dev,
+ &sec_dai->i2s_dai_drv);
+ return 0;
+ }
+
+ i2s_pdata = pdev->dev.platform_data;
+ if (i2s_pdata == NULL) {
+ dev_err(&pdev->dev, "Can't work without s3c_audio_pdata\n");
+ return -EINVAL;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n");
+ return -ENXIO;
+ }
+ dma_pl_chan = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!res) {
+ dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n");
+ return -ENXIO;
+ }
+ dma_cp_chan = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
+ if (res)
+ dma_pl_sec_chan = res->start;
+ else
+ dma_pl_sec_chan = 0;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Unable to get I2S SFR address\n");
+ return -ENXIO;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res),
+ "samsung-i2s")) {
+ dev_err(&pdev->dev, "Unable to request SFR region\n");
+ return -EBUSY;
+ }
+ regs_base = res->start;
+
+ i2s_cfg = &i2s_pdata->type.i2s;
+ quirks = i2s_cfg->quirks;
+
+ pri_dai = i2s_alloc_dai(pdev, false);
+ if (!pri_dai) {
+ dev_err(&pdev->dev, "Unable to alloc I2S_pri\n");
+ ret = -ENOMEM;
+ goto err1;
+ }
+
+ pri_dai->dma_playback.dma_addr = regs_base + I2STXD;
+ pri_dai->dma_capture.dma_addr = regs_base + I2SRXD;
+ pri_dai->dma_playback.client =
+ (struct s3c2410_dma_client *)&pri_dai->dma_playback;
+ pri_dai->dma_capture.client =
+ (struct s3c2410_dma_client *)&pri_dai->dma_capture;
+ pri_dai->dma_playback.channel = dma_pl_chan;
+ pri_dai->dma_capture.channel = dma_cp_chan;
+ pri_dai->src_clk = i2s_cfg->src_clk;
+ pri_dai->dma_playback.dma_size = 4;
+ pri_dai->dma_capture.dma_size = 4;
+ pri_dai->base = regs_base;
+ pri_dai->quirks = quirks;
+
+ if (quirks & QUIRK_PRI_6CHAN)
+ pri_dai->i2s_dai_drv.playback.channels_max = 6;
+
+ if (quirks & QUIRK_SEC_DAI) {
+ sec_dai = i2s_alloc_dai(pdev, true);
+ if (!sec_dai) {
+ dev_err(&pdev->dev, "Unable to alloc I2S_sec\n");
+ ret = -ENOMEM;
+ goto err2;
+ }
+ sec_dai->dma_playback.dma_addr = regs_base + I2STXDS;
+ sec_dai->dma_playback.client =
+ (struct s3c2410_dma_client *)&sec_dai->dma_playback;
+ /* Use iDMA always if SysDMA not provided */
+ sec_dai->dma_playback.channel = dma_pl_sec_chan ? : -1;
+ sec_dai->src_clk = i2s_cfg->src_clk;
+ sec_dai->dma_playback.dma_size = 4;
+ sec_dai->base = regs_base;
+ sec_dai->quirks = quirks;
+ sec_dai->pri_dai = pri_dai;
+ pri_dai->sec_dai = sec_dai;
+ }
+
+ if (i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) {
+ dev_err(&pdev->dev, "Unable to configure gpio\n");
+ ret = -EINVAL;
+ goto err3;
+ }
+
+ snd_soc_register_dai(&pri_dai->pdev->dev, &pri_dai->i2s_dai_drv);
+
+ return 0;
+err3:
+ kfree(sec_dai);
+err2:
+ kfree(pri_dai);
+err1:
+ release_mem_region(regs_base, resource_size(res));
+
+ return ret;
+}
+
+static __devexit int samsung_i2s_remove(struct platform_device *pdev)
+{
+ struct i2s_dai *i2s, *other;
+
+ i2s = dev_get_drvdata(&pdev->dev);
+ other = i2s->pri_dai ? : i2s->sec_dai;
+
+ if (other) {
+ other->pri_dai = NULL;
+ other->sec_dai = NULL;
+ } else {
+ struct resource *res;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res)
+ release_mem_region(res->start, resource_size(res));
+ }
+
+ i2s->pri_dai = NULL;
+ i2s->sec_dai = NULL;
+
+ kfree(i2s);
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver samsung_i2s_driver = {
+ .probe = samsung_i2s_probe,
+ .remove = samsung_i2s_remove,
+ .driver = {
+ .name = "samsung-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init samsung_i2s_init(void)
+{
+ return platform_driver_register(&samsung_i2s_driver);
+}
+module_init(samsung_i2s_init);
+
+static void __exit samsung_i2s_exit(void)
+{
+ platform_driver_unregister(&samsung_i2s_driver);
+}
+module_exit(samsung_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("Samsung I2S Interface");
+MODULE_ALIAS("platform:samsung-i2s");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/i2s.h b/sound/soc/samsung/i2s.h
new file mode 100644
index 00000000..8e15f6a6
--- /dev/null
+++ b/sound/soc/samsung/i2s.h
@@ -0,0 +1,29 @@
+/* sound/soc/samsung/i2s.h
+ *
+ * ALSA SoC Audio Layer - Samsung I2S Controller driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd.
+ * Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __SND_SOC_SAMSUNG_I2S_H
+#define __SND_SOC_SAMSUNG_I2S_H
+
+/*
+ * Maximum number of I2S blocks that any SoC can have.
+ * The secondary interface of a CPU dai(if there exists any),
+ * is indexed at [cpu-dai's ID + SAMSUNG_I2S_SECOFF]
+ */
+#define SAMSUNG_I2S_SECOFF 4
+
+#define SAMSUNG_I2S_DIV_BCLK 1
+
+#define SAMSUNG_I2S_RCLKSRC_0 0
+#define SAMSUNG_I2S_RCLKSRC_1 1
+#define SAMSUNG_I2S_CDCLK 2
+
+#endif /* __SND_SOC_SAMSUNG_I2S_H */
diff --git a/sound/soc/samsung/jive_wm8750.c b/sound/soc/samsung/jive_wm8750.c
new file mode 100644
index 00000000..14eb6ea6
--- /dev/null
+++ b/sound/soc/samsung/jive_wm8750.c
@@ -0,0 +1,180 @@
+/* sound/soc/samsung/jive_wm8750.c
+ *
+ * Copyright 2007,2008 Simtec Electronics
+ *
+ * Based on sound/soc/pxa/spitz.c
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+
+#include "s3c2412-i2s.h"
+#include "../codecs/wm8750.h"
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ { "Headphone Jack", NULL, "LOUT1" },
+ { "Headphone Jack", NULL, "ROUT1" },
+ { "Internal Speaker", NULL, "LOUT2" },
+ { "Internal Speaker", NULL, "ROUT2" },
+ { "LINPUT1", NULL, "Line Input" },
+ { "RINPUT1", NULL, "Line Input" },
+};
+
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Internal Speaker", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+static int jive_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct s3c_i2sv2_rate_calc div;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params),
+ s3c_i2sv2_get_clock(cpu_dai));
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_RCLK, div.fs_div);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_PRESCALER,
+ div.clk_div - 1);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops jive_ops = {
+ .hw_params = jive_hw_params,
+};
+
+static int jive_wm8750_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ /* These endpoints are not being used. */
+ snd_soc_dapm_nc_pin(dapm, "LINPUT2");
+ snd_soc_dapm_nc_pin(dapm, "RINPUT2");
+ snd_soc_dapm_nc_pin(dapm, "LINPUT3");
+ snd_soc_dapm_nc_pin(dapm, "RINPUT3");
+ snd_soc_dapm_nc_pin(dapm, "OUT3");
+ snd_soc_dapm_nc_pin(dapm, "MONO");
+
+ /* Add jive specific widgets */
+ err = snd_soc_dapm_new_controls(dapm, wm8750_dapm_widgets,
+ ARRAY_SIZE(wm8750_dapm_widgets));
+ if (err) {
+ printk(KERN_ERR "%s: failed to add widgets (%d)\n",
+ __func__, err);
+ return err;
+ }
+
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link jive_dai = {
+ .name = "wm8750",
+ .stream_name = "WM8750",
+ .cpu_dai_name = "s3c2412-i2s",
+ .codec_dai_name = "wm8750-hifi",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8750-codec.0-001a",
+ .init = jive_wm8750_init,
+ .ops = &jive_ops,
+};
+
+/* jive audio machine driver */
+static struct snd_soc_card snd_soc_machine_jive = {
+ .name = "Jive",
+ .dai_link = &jive_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *jive_snd_device;
+
+static int __init jive_init(void)
+{
+ int ret;
+
+ if (!machine_is_jive())
+ return 0;
+
+ printk("JIVE WM8750 Audio support\n");
+
+ jive_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!jive_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(jive_snd_device, &snd_soc_machine_jive);
+ ret = platform_device_add(jive_snd_device);
+
+ if (ret)
+ platform_device_put(jive_snd_device);
+
+ return ret;
+}
+
+static void __exit jive_exit(void)
+{
+ platform_device_unregister(jive_snd_device);
+}
+
+module_init(jive_init);
+module_exit(jive_exit);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Jive Audio support");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/ln2440sbc_alc650.c b/sound/soc/samsung/ln2440sbc_alc650.c
new file mode 100644
index 00000000..bd91c19a
--- /dev/null
+++ b/sound/soc/samsung/ln2440sbc_alc650.c
@@ -0,0 +1,70 @@
+/*
+ * SoC audio for ln2440sbc
+ *
+ * Copyright 2007 KonekTel, a.s.
+ * Author: Ivan Kuten
+ * ivan.kuten@promwad.com
+ *
+ * Heavily based on smdk2443_wm9710.c
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <sound/soc.h>
+
+static struct snd_soc_card ln2440sbc;
+
+static struct snd_soc_dai_link ln2440sbc_dai[] = {
+{
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "samsung-ac97",
+ .codec_dai_name = "ac97-hifi",
+ .codec_name = "ac97-codec",
+ .platform_name = "samsung-audio",
+},
+};
+
+static struct snd_soc_card ln2440sbc = {
+ .name = "LN2440SBC",
+ .dai_link = ln2440sbc_dai,
+ .num_links = ARRAY_SIZE(ln2440sbc_dai),
+};
+
+static struct platform_device *ln2440sbc_snd_ac97_device;
+
+static int __init ln2440sbc_init(void)
+{
+ int ret;
+
+ ln2440sbc_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+ if (!ln2440sbc_snd_ac97_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(ln2440sbc_snd_ac97_device, &ln2440sbc);
+ ret = platform_device_add(ln2440sbc_snd_ac97_device);
+
+ if (ret)
+ platform_device_put(ln2440sbc_snd_ac97_device);
+
+ return ret;
+}
+
+static void __exit ln2440sbc_exit(void)
+{
+ platform_device_unregister(ln2440sbc_snd_ac97_device);
+}
+
+module_init(ln2440sbc_init);
+module_exit(ln2440sbc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ivan Kuten");
+MODULE_DESCRIPTION("ALSA SoC ALC650 LN2440SBC");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/neo1973_wm8753.c b/sound/soc/samsung/neo1973_wm8753.c
new file mode 100644
index 00000000..c1290da7
--- /dev/null
+++ b/sound/soc/samsung/neo1973_wm8753.c
@@ -0,0 +1,538 @@
+/*
+ * neo1973_wm8753.c -- SoC audio for Openmoko Neo1973 and Freerunner devices
+ *
+ * Copyright 2007 Openmoko Inc
+ * Author: Graeme Gregory <graeme@openmoko.org>
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ * Copyright 2009 Wolfson Microelectronics
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <plat/regs-iis.h>
+#include <mach/gta02.h>
+
+#include "../codecs/wm8753.h"
+#include "s3c24xx-i2s.h"
+
+static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int pll_out = 0, bclk = 0;
+ int ret = 0;
+ unsigned long iis_clkrate;
+
+ iis_clkrate = s3c24xx_i2s_get_clockrate();
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ pll_out = 12288000;
+ break;
+ case 48000:
+ bclk = WM8753_BCLK_DIV_4;
+ pll_out = 12288000;
+ break;
+ case 96000:
+ bclk = WM8753_BCLK_DIV_2;
+ pll_out = 12288000;
+ break;
+ case 11025:
+ bclk = WM8753_BCLK_DIV_16;
+ pll_out = 11289600;
+ break;
+ case 22050:
+ bclk = WM8753_BCLK_DIV_8;
+ pll_out = 11289600;
+ break;
+ case 44100:
+ bclk = WM8753_BCLK_DIV_4;
+ pll_out = 11289600;
+ break;
+ case 88200:
+ bclk = WM8753_BCLK_DIV_2;
+ pll_out = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set MCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+ S3C2410_IISMOD_32FS);
+ if (ret < 0)
+ return ret;
+
+ /* set codec BCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk);
+ if (ret < 0)
+ return ret;
+
+ /* set prescaler division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ S3C24XX_PRESCALE(4, 4));
+ if (ret < 0)
+ return ret;
+
+ /* codec PLL input is PCLK/4 */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0,
+ iis_clkrate / 4, pll_out);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+ /* disable the PLL */
+ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0);
+}
+
+/*
+ * Neo1973 WM8753 HiFi DAI opserations.
+ */
+static struct snd_soc_ops neo1973_hifi_ops = {
+ .hw_params = neo1973_hifi_hw_params,
+ .hw_free = neo1973_hifi_hw_free,
+};
+
+static int neo1973_voice_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ unsigned int pcmdiv = 0;
+ int ret = 0;
+ unsigned long iis_clkrate;
+
+ iis_clkrate = s3c24xx_i2s_get_clockrate();
+
+ if (params_rate(params) != 8000)
+ return -EINVAL;
+ if (params_channels(params) != 1)
+ return -EINVAL;
+
+ pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */
+
+ /* todo: gg check mode (DSP_B) against CSR datasheet */
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, 12288000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set codec PCM division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv);
+ if (ret < 0)
+ return ret;
+
+ /* configure and enable PLL for 12.288MHz output */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0,
+ iis_clkrate / 4, 12288000);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int neo1973_voice_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+ /* disable the PLL */
+ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0);
+}
+
+static struct snd_soc_ops neo1973_voice_ops = {
+ .hw_params = neo1973_voice_hw_params,
+ .hw_free = neo1973_voice_hw_free,
+};
+
+/* Shared routes and controls */
+
+static const struct snd_soc_dapm_widget neo1973_wm8753_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("GSM Line Out", NULL),
+ SND_SOC_DAPM_LINE("GSM Line In", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Handset Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route neo1973_wm8753_routes[] = {
+ /* Connections to the GSM Module */
+ {"GSM Line Out", NULL, "MONO1"},
+ {"GSM Line Out", NULL, "MONO2"},
+ {"RXP", NULL, "GSM Line In"},
+ {"RXN", NULL, "GSM Line In"},
+
+ /* Connections to Headset */
+ {"MIC1", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Headset Mic"},
+
+ /* Call Mic */
+ {"MIC2", NULL, "Mic Bias"},
+ {"MIC2N", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Handset Mic"},
+
+ /* Connect the ALC pins */
+ {"ACIN", NULL, "ACOP"},
+};
+
+static const struct snd_kcontrol_new neo1973_wm8753_controls[] = {
+ SOC_DAPM_PIN_SWITCH("GSM Line Out"),
+ SOC_DAPM_PIN_SWITCH("GSM Line In"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Handset Mic"),
+};
+
+/* GTA02 specific routes and controls */
+
+#ifdef CONFIG_MACH_NEO1973_GTA02
+
+static int gta02_speaker_enabled;
+
+static int lm4853_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ gta02_speaker_enabled = ucontrol->value.integer.value[0];
+
+ gpio_set_value(GTA02_GPIO_HP_IN, !gta02_speaker_enabled);
+
+ return 0;
+}
+
+static int lm4853_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = gta02_speaker_enabled;
+ return 0;
+}
+
+static int lm4853_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(GTA02_GPIO_AMP_SHUT, SND_SOC_DAPM_EVENT_OFF(event));
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_route neo1973_gta02_routes[] = {
+ /* Connections to the amp */
+ {"Stereo Out", NULL, "LOUT1"},
+ {"Stereo Out", NULL, "ROUT1"},
+
+ /* Call Speaker */
+ {"Handset Spk", NULL, "LOUT2"},
+ {"Handset Spk", NULL, "ROUT2"},
+};
+
+static const struct snd_kcontrol_new neo1973_gta02_wm8753_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Handset Spk"),
+ SOC_DAPM_PIN_SWITCH("Stereo Out"),
+
+ SOC_SINGLE_BOOL_EXT("Amp Spk Switch", 0,
+ lm4853_get_spk,
+ lm4853_set_spk),
+};
+
+static const struct snd_soc_dapm_widget neo1973_gta02_wm8753_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Handset Spk", NULL),
+ SND_SOC_DAPM_SPK("Stereo Out", lm4853_event),
+};
+
+static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, neo1973_gta02_wm8753_dapm_widgets,
+ ARRAY_SIZE(neo1973_gta02_wm8753_dapm_widgets));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_add_routes(dapm, neo1973_gta02_routes,
+ ARRAY_SIZE(neo1973_gta02_routes));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_add_controls(codec, neo1973_gta02_wm8753_controls,
+ ARRAY_SIZE(neo1973_gta02_wm8753_controls));
+ if (ret)
+ return ret;
+
+ snd_soc_dapm_disable_pin(dapm, "Stereo Out");
+ snd_soc_dapm_disable_pin(dapm, "Handset Spk");
+ snd_soc_dapm_ignore_suspend(dapm, "Stereo Out");
+ snd_soc_dapm_ignore_suspend(dapm, "Handset Spk");
+
+ return 0;
+}
+
+#else
+static int neo1973_gta02_wm8753_init(struct snd_soc_code *codec) { return 0; }
+#endif
+
+static int neo1973_wm8753_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ /* set up NC codec pins */
+ if (machine_is_neo1973_gta01()) {
+ snd_soc_dapm_nc_pin(dapm, "LOUT2");
+ snd_soc_dapm_nc_pin(dapm, "ROUT2");
+ }
+ snd_soc_dapm_nc_pin(dapm, "OUT3");
+ snd_soc_dapm_nc_pin(dapm, "OUT4");
+ snd_soc_dapm_nc_pin(dapm, "LINE1");
+ snd_soc_dapm_nc_pin(dapm, "LINE2");
+
+ /* Add neo1973 specific widgets */
+ ret = snd_soc_dapm_new_controls(dapm, neo1973_wm8753_dapm_widgets,
+ ARRAY_SIZE(neo1973_wm8753_dapm_widgets));
+ if (ret)
+ return ret;
+
+ /* add neo1973 specific controls */
+ ret = snd_soc_add_controls(codec, neo1973_wm8753_controls,
+ ARRAY_SIZE(neo1973_wm8753_controls));
+ if (ret)
+ return ret;
+
+ /* set up neo1973 specific audio routes */
+ ret = snd_soc_dapm_add_routes(dapm, neo1973_wm8753_routes,
+ ARRAY_SIZE(neo1973_wm8753_routes));
+ if (ret)
+ return ret;
+
+ /* set endpoints to default off mode */
+ snd_soc_dapm_disable_pin(dapm, "GSM Line Out");
+ snd_soc_dapm_disable_pin(dapm, "GSM Line In");
+ snd_soc_dapm_disable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_disable_pin(dapm, "Handset Mic");
+
+ /* allow audio paths from the GSM modem to run during suspend */
+ snd_soc_dapm_ignore_suspend(dapm, "GSM Line Out");
+ snd_soc_dapm_ignore_suspend(dapm, "GSM Line In");
+ snd_soc_dapm_ignore_suspend(dapm, "Headset Mic");
+ snd_soc_dapm_ignore_suspend(dapm, "Handset Mic");
+
+ if (machine_is_neo1973_gta02()) {
+ ret = neo1973_gta02_wm8753_init(codec);
+ if (ret)
+ return ret;
+ }
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+/* GTA01 specific controls */
+
+#ifdef CONFIG_MACH_NEO1973_GTA01
+
+static const struct snd_soc_dapm_route neo1973_lm4857_routes[] = {
+ {"Amp IN", NULL, "ROUT1"},
+ {"Amp IN", NULL, "LOUT1"},
+
+ {"Handset Spk", NULL, "Amp EP"},
+ {"Stereo Out", NULL, "Amp LS"},
+ {"Headphone", NULL, "Amp HP"},
+};
+
+static const struct snd_soc_dapm_widget neo1973_lm4857_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Handset Spk", NULL),
+ SND_SOC_DAPM_SPK("Stereo Out", NULL),
+ SND_SOC_DAPM_HP("Headphone", NULL),
+};
+
+static int neo1973_lm4857_init(struct snd_soc_dapm_context *dapm)
+{
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, neo1973_lm4857_dapm_widgets,
+ ARRAY_SIZE(neo1973_lm4857_dapm_widgets));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_add_routes(dapm, neo1973_lm4857_routes,
+ ARRAY_SIZE(neo1973_lm4857_routes));
+ if (ret)
+ return ret;
+
+ snd_soc_dapm_ignore_suspend(dapm, "Stereo Out");
+ snd_soc_dapm_ignore_suspend(dapm, "Handset Spk");
+ snd_soc_dapm_ignore_suspend(dapm, "Headphone");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+#else
+static int neo1973_lm4857_init(struct snd_soc_dapm_context *dapm) { return 0; };
+#endif
+
+static struct snd_soc_dai_link neo1973_dai[] = {
+{ /* Hifi Playback - for similatious use with voice below */
+ .name = "WM8753",
+ .stream_name = "WM8753 HiFi",
+ .platform_name = "samsung-audio",
+ .cpu_dai_name = "s3c24xx-iis",
+ .codec_dai_name = "wm8753-hifi",
+ .codec_name = "wm8753.0-001a",
+ .init = neo1973_wm8753_init,
+ .ops = &neo1973_hifi_ops,
+},
+{ /* Voice via BT */
+ .name = "Bluetooth",
+ .stream_name = "Voice",
+ .cpu_dai_name = "dfbmcs320-pcm",
+ .codec_dai_name = "wm8753-voice",
+ .codec_name = "wm8753.0-001a",
+ .ops = &neo1973_voice_ops,
+},
+};
+
+static struct snd_soc_aux_dev neo1973_aux_devs[] = {
+ {
+ .name = "dfbmcs320",
+ .codec_name = "dfbmcs320.0",
+ },
+ {
+ .name = "lm4857",
+ .codec_name = "lm4857.0-007c",
+ .init = neo1973_lm4857_init,
+ },
+};
+
+static struct snd_soc_codec_conf neo1973_codec_conf[] = {
+ {
+ .dev_name = "lm4857.0-007c",
+ .name_prefix = "Amp",
+ },
+};
+
+#ifdef CONFIG_MACH_NEO1973_GTA02
+static const struct gpio neo1973_gta02_gpios[] = {
+ { GTA02_GPIO_HP_IN, GPIOF_OUT_INIT_HIGH, "GTA02_HP_IN" },
+ { GTA02_GPIO_AMP_SHUT, GPIOF_OUT_INIT_HIGH, "GTA02_AMP_SHUT" },
+};
+#else
+static const struct gpio neo1973_gta02_gpios[] = {};
+#endif
+
+static struct snd_soc_card neo1973 = {
+ .name = "neo1973",
+ .dai_link = neo1973_dai,
+ .num_links = ARRAY_SIZE(neo1973_dai),
+ .aux_dev = neo1973_aux_devs,
+ .num_aux_devs = ARRAY_SIZE(neo1973_aux_devs),
+ .codec_conf = neo1973_codec_conf,
+ .num_configs = ARRAY_SIZE(neo1973_codec_conf),
+};
+
+static struct platform_device *neo1973_snd_device;
+
+static int __init neo1973_init(void)
+{
+ int ret;
+
+ if (!machine_is_neo1973_gta01() && !machine_is_neo1973_gta02())
+ return -ENODEV;
+
+ if (machine_is_neo1973_gta02()) {
+ neo1973.name = "neo1973gta02";
+ neo1973.num_aux_devs = 1;
+
+ ret = gpio_request_array(neo1973_gta02_gpios,
+ ARRAY_SIZE(neo1973_gta02_gpios));
+ if (ret)
+ return ret;
+ }
+
+ neo1973_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!neo1973_snd_device) {
+ ret = -ENOMEM;
+ goto err_gpio_free;
+ }
+
+ platform_set_drvdata(neo1973_snd_device, &neo1973);
+ ret = platform_device_add(neo1973_snd_device);
+
+ if (ret)
+ goto err_put_device;
+
+ return 0;
+
+err_put_device:
+ platform_device_put(neo1973_snd_device);
+err_gpio_free:
+ if (machine_is_neo1973_gta02()) {
+ gpio_free_array(neo1973_gta02_gpios,
+ ARRAY_SIZE(neo1973_gta02_gpios));
+ }
+ return ret;
+}
+module_init(neo1973_init);
+
+static void __exit neo1973_exit(void)
+{
+ platform_device_unregister(neo1973_snd_device);
+
+ if (machine_is_neo1973_gta02()) {
+ gpio_free_array(neo1973_gta02_gpios,
+ ARRAY_SIZE(neo1973_gta02_gpios));
+ }
+}
+module_exit(neo1973_exit);
+
+/* Module information */
+MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org");
+MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 and Frerunner");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c
new file mode 100644
index 00000000..9c7e8b48
--- /dev/null
+++ b/sound/soc/samsung/pcm.c
@@ -0,0 +1,650 @@
+/* sound/soc/samsung/pcm.c
+ *
+ * ALSA SoC Audio Layer - S3C PCM-Controller driver
+ *
+ * Copyright (c) 2009 Samsung Electronics Co. Ltd
+ * Author: Jaswinder Singh <jassi.brar@samsung.com>
+ * based upon I2S drivers by Ben Dooks.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <plat/audio.h>
+#include <plat/dma.h>
+
+#include "dma.h"
+#include "pcm.h"
+
+/*Register Offsets */
+#define S3C_PCM_CTL 0x00
+#define S3C_PCM_CLKCTL 0x04
+#define S3C_PCM_TXFIFO 0x08
+#define S3C_PCM_RXFIFO 0x0C
+#define S3C_PCM_IRQCTL 0x10
+#define S3C_PCM_IRQSTAT 0x14
+#define S3C_PCM_FIFOSTAT 0x18
+#define S3C_PCM_CLRINT 0x20
+
+/* PCM_CTL Bit-Fields */
+#define S3C_PCM_CTL_TXDIPSTICK_MASK 0x3f
+#define S3C_PCM_CTL_TXDIPSTICK_SHIFT 13
+#define S3C_PCM_CTL_RXDIPSTICK_MASK 0x3f
+#define S3C_PCM_CTL_RXDIPSTICK_SHIFT 7
+#define S3C_PCM_CTL_TXDMA_EN (0x1 << 6)
+#define S3C_PCM_CTL_RXDMA_EN (0x1 << 5)
+#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1 << 4)
+#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1 << 3)
+#define S3C_PCM_CTL_TXFIFO_EN (0x1 << 2)
+#define S3C_PCM_CTL_RXFIFO_EN (0x1 << 1)
+#define S3C_PCM_CTL_ENABLE (0x1 << 0)
+
+/* PCM_CLKCTL Bit-Fields */
+#define S3C_PCM_CLKCTL_SERCLK_EN (0x1 << 19)
+#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1 << 18)
+#define S3C_PCM_CLKCTL_SCLKDIV_MASK 0x1ff
+#define S3C_PCM_CLKCTL_SYNCDIV_MASK 0x1ff
+#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT 9
+#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT 0
+
+/* PCM_TXFIFO Bit-Fields */
+#define S3C_PCM_TXFIFO_DVALID (0x1 << 16)
+#define S3C_PCM_TXFIFO_DATA_MSK (0xffff << 0)
+
+/* PCM_RXFIFO Bit-Fields */
+#define S3C_PCM_RXFIFO_DVALID (0x1 << 16)
+#define S3C_PCM_RXFIFO_DATA_MSK (0xffff << 0)
+
+/* PCM_IRQCTL Bit-Fields */
+#define S3C_PCM_IRQCTL_IRQEN (0x1 << 14)
+#define S3C_PCM_IRQCTL_WRDEN (0x1 << 12)
+#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1 << 11)
+#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1 << 10)
+#define S3C_PCM_IRQCTL_TXFULLEN (0x1 << 9)
+#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1 << 8)
+#define S3C_PCM_IRQCTL_TXSTARVEN (0x1 << 7)
+#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1 << 6)
+#define S3C_PCM_IRQCTL_RXEMPTEN (0x1 << 5)
+#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1 << 4)
+#define S3C_PCM_IRQCTL_RXFULLEN (0x1 << 3)
+#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1 << 2)
+#define S3C_PCM_IRQCTL_RXSTARVEN (0x1 << 1)
+#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1 << 0)
+
+/* PCM_IRQSTAT Bit-Fields */
+#define S3C_PCM_IRQSTAT_IRQPND (0x1 << 13)
+#define S3C_PCM_IRQSTAT_WRD_XFER (0x1 << 12)
+#define S3C_PCM_IRQSTAT_TXEMPTY (0x1 << 11)
+#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1 << 10)
+#define S3C_PCM_IRQSTAT_TXFULL (0x1 << 9)
+#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1 << 8)
+#define S3C_PCM_IRQSTAT_TXSTARV (0x1 << 7)
+#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1 << 6)
+#define S3C_PCM_IRQSTAT_RXEMPT (0x1 << 5)
+#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1 << 4)
+#define S3C_PCM_IRQSTAT_RXFULL (0x1 << 3)
+#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1 << 2)
+#define S3C_PCM_IRQSTAT_RXSTARV (0x1 << 1)
+#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1 << 0)
+
+/* PCM_FIFOSTAT Bit-Fields */
+#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f << 14)
+#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1 << 13)
+#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1 << 12)
+#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1 << 11)
+#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1 << 10)
+#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f << 4)
+#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1 << 3)
+#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1 << 2)
+#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1 << 1)
+#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1 << 0)
+
+/**
+ * struct s3c_pcm_info - S3C PCM Controller information
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device register block.
+ * @dma_playback: DMA information for playback channel.
+ * @dma_capture: DMA information for capture channel.
+ */
+struct s3c_pcm_info {
+ spinlock_t lock;
+ struct device *dev;
+ void __iomem *regs;
+
+ unsigned int sclk_per_fs;
+
+ /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */
+ unsigned int idleclk;
+
+ struct clk *pclk;
+ struct clk *cclk;
+
+ struct s3c_dma_params *dma_playback;
+ struct s3c_dma_params *dma_capture;
+};
+
+static struct s3c2410_dma_client s3c_pcm_dma_client_out = {
+ .name = "PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c_pcm_dma_client_in = {
+ .name = "PCM Stereo in"
+};
+
+static struct s3c_dma_params s3c_pcm_stereo_out[] = {
+ [0] = {
+ .client = &s3c_pcm_dma_client_out,
+ .dma_size = 4,
+ },
+ [1] = {
+ .client = &s3c_pcm_dma_client_out,
+ .dma_size = 4,
+ },
+};
+
+static struct s3c_dma_params s3c_pcm_stereo_in[] = {
+ [0] = {
+ .client = &s3c_pcm_dma_client_in,
+ .dma_size = 4,
+ },
+ [1] = {
+ .client = &s3c_pcm_dma_client_in,
+ .dma_size = 4,
+ },
+};
+
+static struct s3c_pcm_info s3c_pcm[2];
+
+static void s3c_pcm_snd_txctrl(struct s3c_pcm_info *pcm, int on)
+{
+ void __iomem *regs = pcm->regs;
+ u32 ctl, clkctl;
+
+ clkctl = readl(regs + S3C_PCM_CLKCTL);
+ ctl = readl(regs + S3C_PCM_CTL);
+ ctl &= ~(S3C_PCM_CTL_TXDIPSTICK_MASK
+ << S3C_PCM_CTL_TXDIPSTICK_SHIFT);
+
+ if (on) {
+ ctl |= S3C_PCM_CTL_TXDMA_EN;
+ ctl |= S3C_PCM_CTL_TXFIFO_EN;
+ ctl |= S3C_PCM_CTL_ENABLE;
+ ctl |= (0x4<<S3C_PCM_CTL_TXDIPSTICK_SHIFT);
+ clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+ } else {
+ ctl &= ~S3C_PCM_CTL_TXDMA_EN;
+ ctl &= ~S3C_PCM_CTL_TXFIFO_EN;
+
+ if (!(ctl & S3C_PCM_CTL_RXFIFO_EN)) {
+ ctl &= ~S3C_PCM_CTL_ENABLE;
+ if (!pcm->idleclk)
+ clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+ }
+ }
+
+ writel(clkctl, regs + S3C_PCM_CLKCTL);
+ writel(ctl, regs + S3C_PCM_CTL);
+}
+
+static void s3c_pcm_snd_rxctrl(struct s3c_pcm_info *pcm, int on)
+{
+ void __iomem *regs = pcm->regs;
+ u32 ctl, clkctl;
+
+ ctl = readl(regs + S3C_PCM_CTL);
+ clkctl = readl(regs + S3C_PCM_CLKCTL);
+ ctl &= ~(S3C_PCM_CTL_RXDIPSTICK_MASK
+ << S3C_PCM_CTL_RXDIPSTICK_SHIFT);
+
+ if (on) {
+ ctl |= S3C_PCM_CTL_RXDMA_EN;
+ ctl |= S3C_PCM_CTL_RXFIFO_EN;
+ ctl |= S3C_PCM_CTL_ENABLE;
+ ctl |= (0x20<<S3C_PCM_CTL_RXDIPSTICK_SHIFT);
+ clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+ } else {
+ ctl &= ~S3C_PCM_CTL_RXDMA_EN;
+ ctl &= ~S3C_PCM_CTL_RXFIFO_EN;
+
+ if (!(ctl & S3C_PCM_CTL_TXFIFO_EN)) {
+ ctl &= ~S3C_PCM_CTL_ENABLE;
+ if (!pcm->idleclk)
+ clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+ }
+ }
+
+ writel(clkctl, regs + S3C_PCM_CLKCTL);
+ writel(ctl, regs + S3C_PCM_CTL);
+}
+
+static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ unsigned long flags;
+
+ dev_dbg(pcm->dev, "Entered %s\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&pcm->lock, flags);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s3c_pcm_snd_rxctrl(pcm, 1);
+ else
+ s3c_pcm_snd_txctrl(pcm, 1);
+
+ spin_unlock_irqrestore(&pcm->lock, flags);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&pcm->lock, flags);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s3c_pcm_snd_rxctrl(pcm, 0);
+ else
+ s3c_pcm_snd_txctrl(pcm, 0);
+
+ spin_unlock_irqrestore(&pcm->lock, flags);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *socdai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ struct s3c_dma_params *dma_data;
+ void __iomem *regs = pcm->regs;
+ struct clk *clk;
+ int sclk_div, sync_div;
+ unsigned long flags;
+ u32 clkctl;
+
+ dev_dbg(pcm->dev, "Entered %s\n", __func__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = pcm->dma_playback;
+ else
+ dma_data = pcm->dma_capture;
+
+ snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);
+
+ /* Strictly check for sample size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&pcm->lock, flags);
+
+ /* Get hold of the PCMSOURCE_CLK */
+ clkctl = readl(regs + S3C_PCM_CLKCTL);
+ if (clkctl & S3C_PCM_CLKCTL_SERCLKSEL_PCLK)
+ clk = pcm->pclk;
+ else
+ clk = pcm->cclk;
+
+ /* Set the SCLK divider */
+ sclk_div = clk_get_rate(clk) / pcm->sclk_per_fs /
+ params_rate(params) / 2 - 1;
+
+ clkctl &= ~(S3C_PCM_CLKCTL_SCLKDIV_MASK
+ << S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
+ clkctl |= ((sclk_div & S3C_PCM_CLKCTL_SCLKDIV_MASK)
+ << S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
+
+ /* Set the SYNC divider */
+ sync_div = pcm->sclk_per_fs - 1;
+
+ clkctl &= ~(S3C_PCM_CLKCTL_SYNCDIV_MASK
+ << S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
+ clkctl |= ((sync_div & S3C_PCM_CLKCTL_SYNCDIV_MASK)
+ << S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
+
+ writel(clkctl, regs + S3C_PCM_CLKCTL);
+
+ spin_unlock_irqrestore(&pcm->lock, flags);
+
+ dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n",
+ clk_get_rate(clk), pcm->sclk_per_fs,
+ sclk_div, sync_div);
+
+ return 0;
+}
+
+static int s3c_pcm_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
+ void __iomem *regs = pcm->regs;
+ unsigned long flags;
+ int ret = 0;
+ u32 ctl;
+
+ dev_dbg(pcm->dev, "Entered %s\n", __func__);
+
+ spin_lock_irqsave(&pcm->lock, flags);
+
+ ctl = readl(regs + S3C_PCM_CTL);
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_NF:
+ /* Nothing to do, IB_NF by default */
+ break;
+ default:
+ dev_err(pcm->dev, "Unsupported clock inversion!\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* Nothing to do, Master by default */
+ break;
+ default:
+ dev_err(pcm->dev, "Unsupported master/slave format!\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
+ case SND_SOC_DAIFMT_CONT:
+ pcm->idleclk = 1;
+ break;
+ case SND_SOC_DAIFMT_GATED:
+ pcm->idleclk = 0;
+ break;
+ default:
+ dev_err(pcm->dev, "Invalid Clock gating request!\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ ctl |= S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
+ ctl |= S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ ctl &= ~S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
+ ctl &= ~S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
+ break;
+ default:
+ dev_err(pcm->dev, "Unsupported data format!\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ writel(ctl, regs + S3C_PCM_CTL);
+
+exit:
+ spin_unlock_irqrestore(&pcm->lock, flags);
+
+ return ret;
+}
+
+static int s3c_pcm_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
+
+ switch (div_id) {
+ case S3C_PCM_SCLK_PER_FS:
+ pcm->sclk_per_fs = div;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int s3c_pcm_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
+ void __iomem *regs = pcm->regs;
+ u32 clkctl = readl(regs + S3C_PCM_CLKCTL);
+
+ switch (clk_id) {
+ case S3C_PCM_CLKSRC_PCLK:
+ clkctl |= S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
+ break;
+
+ case S3C_PCM_CLKSRC_MUX:
+ clkctl &= ~S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
+
+ if (clk_get_rate(pcm->cclk) != freq)
+ clk_set_rate(pcm->cclk, freq);
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ writel(clkctl, regs + S3C_PCM_CLKCTL);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops s3c_pcm_dai_ops = {
+ .set_sysclk = s3c_pcm_set_sysclk,
+ .set_clkdiv = s3c_pcm_set_clkdiv,
+ .trigger = s3c_pcm_trigger,
+ .hw_params = s3c_pcm_hw_params,
+ .set_fmt = s3c_pcm_set_fmt,
+};
+
+#define S3C_PCM_RATES SNDRV_PCM_RATE_8000_96000
+
+#define S3C_PCM_DAI_DECLARE \
+ .symmetric_rates = 1, \
+ .ops = &s3c_pcm_dai_ops, \
+ .playback = { \
+ .channels_min = 2, \
+ .channels_max = 2, \
+ .rates = S3C_PCM_RATES, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, \
+ }, \
+ .capture = { \
+ .channels_min = 2, \
+ .channels_max = 2, \
+ .rates = S3C_PCM_RATES, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, \
+ }
+
+struct snd_soc_dai_driver s3c_pcm_dai[] = {
+ [0] = {
+ .name = "samsung-pcm.0",
+ S3C_PCM_DAI_DECLARE,
+ },
+ [1] = {
+ .name = "samsung-pcm.1",
+ S3C_PCM_DAI_DECLARE,
+ },
+};
+EXPORT_SYMBOL_GPL(s3c_pcm_dai);
+
+static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev)
+{
+ struct s3c_pcm_info *pcm;
+ struct resource *mem_res, *dmatx_res, *dmarx_res;
+ struct s3c_audio_pdata *pcm_pdata;
+ int ret;
+
+ /* Check for valid device index */
+ if ((pdev->id < 0) || pdev->id >= ARRAY_SIZE(s3c_pcm)) {
+ dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
+ return -EINVAL;
+ }
+
+ pcm_pdata = pdev->dev.platform_data;
+
+ /* Check for availability of necessary resource */
+ dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!dmatx_res) {
+ dev_err(&pdev->dev, "Unable to get PCM-TX dma resource\n");
+ return -ENXIO;
+ }
+
+ dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!dmarx_res) {
+ dev_err(&pdev->dev, "Unable to get PCM-RX dma resource\n");
+ return -ENXIO;
+ }
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem_res) {
+ dev_err(&pdev->dev, "Unable to get register resource\n");
+ return -ENXIO;
+ }
+
+ if (pcm_pdata && pcm_pdata->cfg_gpio && pcm_pdata->cfg_gpio(pdev)) {
+ dev_err(&pdev->dev, "Unable to configure gpio\n");
+ return -EINVAL;
+ }
+
+ pcm = &s3c_pcm[pdev->id];
+ pcm->dev = &pdev->dev;
+
+ spin_lock_init(&pcm->lock);
+
+ /* Default is 128fs */
+ pcm->sclk_per_fs = 128;
+
+ pcm->cclk = clk_get(&pdev->dev, "audio-bus");
+ if (IS_ERR(pcm->cclk)) {
+ dev_err(&pdev->dev, "failed to get audio-bus\n");
+ ret = PTR_ERR(pcm->cclk);
+ goto err1;
+ }
+ clk_enable(pcm->cclk);
+
+ /* record our pcm structure for later use in the callbacks */
+ dev_set_drvdata(&pdev->dev, pcm);
+
+ if (!request_mem_region(mem_res->start,
+ resource_size(mem_res), "samsung-pcm")) {
+ dev_err(&pdev->dev, "Unable to request register region\n");
+ ret = -EBUSY;
+ goto err2;
+ }
+
+ pcm->regs = ioremap(mem_res->start, 0x100);
+ if (pcm->regs == NULL) {
+ dev_err(&pdev->dev, "cannot ioremap registers\n");
+ ret = -ENXIO;
+ goto err3;
+ }
+
+ pcm->pclk = clk_get(&pdev->dev, "pcm");
+ if (IS_ERR(pcm->pclk)) {
+ dev_err(&pdev->dev, "failed to get pcm_clock\n");
+ ret = -ENOENT;
+ goto err4;
+ }
+ clk_enable(pcm->pclk);
+
+ ret = snd_soc_register_dai(&pdev->dev, &s3c_pcm_dai[pdev->id]);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "failed to get pcm_clock\n");
+ goto err5;
+ }
+
+ s3c_pcm_stereo_in[pdev->id].dma_addr = mem_res->start
+ + S3C_PCM_RXFIFO;
+ s3c_pcm_stereo_out[pdev->id].dma_addr = mem_res->start
+ + S3C_PCM_TXFIFO;
+
+ s3c_pcm_stereo_in[pdev->id].channel = dmarx_res->start;
+ s3c_pcm_stereo_out[pdev->id].channel = dmatx_res->start;
+
+ pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id];
+ pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id];
+
+ return 0;
+
+err5:
+ clk_disable(pcm->pclk);
+ clk_put(pcm->pclk);
+err4:
+ iounmap(pcm->regs);
+err3:
+ release_mem_region(mem_res->start, resource_size(mem_res));
+err2:
+ clk_disable(pcm->cclk);
+ clk_put(pcm->cclk);
+err1:
+ return ret;
+}
+
+static __devexit int s3c_pcm_dev_remove(struct platform_device *pdev)
+{
+ struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id];
+ struct resource *mem_res;
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ iounmap(pcm->regs);
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(mem_res->start, resource_size(mem_res));
+
+ clk_disable(pcm->cclk);
+ clk_disable(pcm->pclk);
+ clk_put(pcm->pclk);
+ clk_put(pcm->cclk);
+
+ return 0;
+}
+
+static struct platform_driver s3c_pcm_driver = {
+ .probe = s3c_pcm_dev_probe,
+ .remove = s3c_pcm_dev_remove,
+ .driver = {
+ .name = "samsung-pcm",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s3c_pcm_init(void)
+{
+ return platform_driver_register(&s3c_pcm_driver);
+}
+module_init(s3c_pcm_init);
+
+static void __exit s3c_pcm_exit(void)
+{
+ platform_driver_unregister(&s3c_pcm_driver);
+}
+module_exit(s3c_pcm_exit);
+
+/* Module information */
+MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("S3C PCM Controller Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:samsung-pcm");
diff --git a/sound/soc/samsung/pcm.h b/sound/soc/samsung/pcm.h
new file mode 100644
index 00000000..726baf81
--- /dev/null
+++ b/sound/soc/samsung/pcm.h
@@ -0,0 +1,17 @@
+/* sound/soc/samsung/pcm.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __S3C_PCM_H
+#define __S3C_PCM_H __FILE__
+
+#define S3C_PCM_CLKSRC_PCLK 0
+#define S3C_PCM_CLKSRC_MUX 1
+
+#define S3C_PCM_SCLK_PER_FS 0
+
+#endif /* __S3C_PCM_H */
diff --git a/sound/soc/samsung/regs-i2s-v2.h b/sound/soc/samsung/regs-i2s-v2.h
new file mode 100644
index 00000000..5e5e5680
--- /dev/null
+++ b/sound/soc/samsung/regs-i2s-v2.h
@@ -0,0 +1,115 @@
+/* linux/include/asm-arm/plat-s3c24xx/regs-s3c2412-iis.h
+ *
+ * Copyright 2007 Simtec Electronics <linux@simtec.co.uk>
+ * http://armlinux.simtec.co.uk/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * S3C2412 IIS register definition
+*/
+
+#ifndef __ASM_ARCH_REGS_S3C2412_IIS_H
+#define __ASM_ARCH_REGS_S3C2412_IIS_H
+
+#define S3C2412_IISCON (0x00)
+#define S3C2412_IISMOD (0x04)
+#define S3C2412_IISFIC (0x08)
+#define S3C2412_IISPSR (0x0C)
+#define S3C2412_IISTXD (0x10)
+#define S3C2412_IISRXD (0x14)
+
+#define S5PC1XX_IISFICS 0x18
+#define S5PC1XX_IISTXDS 0x1C
+
+#define S5PC1XX_IISCON_SW_RST (1 << 31)
+#define S5PC1XX_IISCON_FRXOFSTATUS (1 << 26)
+#define S5PC1XX_IISCON_FRXORINTEN (1 << 25)
+#define S5PC1XX_IISCON_FTXSURSTAT (1 << 24)
+#define S5PC1XX_IISCON_FTXSURINTEN (1 << 23)
+#define S5PC1XX_IISCON_TXSDMAPAUSE (1 << 20)
+#define S5PC1XX_IISCON_TXSDMACTIVE (1 << 18)
+
+#define S3C64XX_IISCON_FTXURSTATUS (1 << 17)
+#define S3C64XX_IISCON_FTXURINTEN (1 << 16)
+#define S3C64XX_IISCON_TXFIFO2_EMPTY (1 << 15)
+#define S3C64XX_IISCON_TXFIFO1_EMPTY (1 << 14)
+#define S3C64XX_IISCON_TXFIFO2_FULL (1 << 13)
+#define S3C64XX_IISCON_TXFIFO1_FULL (1 << 12)
+
+#define S3C2412_IISCON_LRINDEX (1 << 11)
+#define S3C2412_IISCON_TXFIFO_EMPTY (1 << 10)
+#define S3C2412_IISCON_RXFIFO_EMPTY (1 << 9)
+#define S3C2412_IISCON_TXFIFO_FULL (1 << 8)
+#define S3C2412_IISCON_RXFIFO_FULL (1 << 7)
+#define S3C2412_IISCON_TXDMA_PAUSE (1 << 6)
+#define S3C2412_IISCON_RXDMA_PAUSE (1 << 5)
+#define S3C2412_IISCON_TXCH_PAUSE (1 << 4)
+#define S3C2412_IISCON_RXCH_PAUSE (1 << 3)
+#define S3C2412_IISCON_TXDMA_ACTIVE (1 << 2)
+#define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1)
+#define S3C2412_IISCON_IIS_ACTIVE (1 << 0)
+
+#define S5PC1XX_IISMOD_OPCLK_CDCLK_OUT (0 << 30)
+#define S5PC1XX_IISMOD_OPCLK_CDCLK_IN (1 << 30)
+#define S5PC1XX_IISMOD_OPCLK_BCLK_OUT (2 << 30)
+#define S5PC1XX_IISMOD_OPCLK_PCLK (3 << 30)
+#define S5PC1XX_IISMOD_OPCLK_MASK (3 << 30)
+#define S5PC1XX_IISMOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */
+#define S5PC1XX_IISMOD_BLCS_MASK 0x3
+#define S5PC1XX_IISMOD_BLCS_SHIFT 26
+#define S5PC1XX_IISMOD_BLCP_MASK 0x3
+#define S5PC1XX_IISMOD_BLCP_SHIFT 24
+
+#define S3C64XX_IISMOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */
+#define S3C64XX_IISMOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */
+#define S3C64XX_IISMOD_C1DD_HHALF (1 << 19)
+#define S3C64XX_IISMOD_C1DD_LHALF (1 << 18)
+#define S3C64XX_IISMOD_DC2_EN (1 << 17)
+#define S3C64XX_IISMOD_DC1_EN (1 << 16)
+#define S3C64XX_IISMOD_BLC_16BIT (0 << 13)
+#define S3C64XX_IISMOD_BLC_8BIT (1 << 13)
+#define S3C64XX_IISMOD_BLC_24BIT (2 << 13)
+#define S3C64XX_IISMOD_BLC_MASK (3 << 13)
+
+#define S3C2412_IISMOD_IMS_SYSMUX (1 << 10)
+#define S3C2412_IISMOD_SLAVE (1 << 11)
+#define S3C2412_IISMOD_MODE_TXONLY (0 << 8)
+#define S3C2412_IISMOD_MODE_RXONLY (1 << 8)
+#define S3C2412_IISMOD_MODE_TXRX (2 << 8)
+#define S3C2412_IISMOD_MODE_MASK (3 << 8)
+#define S3C2412_IISMOD_LR_LLOW (0 << 7)
+#define S3C2412_IISMOD_LR_RLOW (1 << 7)
+#define S3C2412_IISMOD_SDF_IIS (0 << 5)
+#define S3C2412_IISMOD_SDF_MSB (1 << 5)
+#define S3C2412_IISMOD_SDF_LSB (2 << 5)
+#define S3C2412_IISMOD_SDF_MASK (3 << 5)
+#define S3C2412_IISMOD_RCLK_256FS (0 << 3)
+#define S3C2412_IISMOD_RCLK_512FS (1 << 3)
+#define S3C2412_IISMOD_RCLK_384FS (2 << 3)
+#define S3C2412_IISMOD_RCLK_768FS (3 << 3)
+#define S3C2412_IISMOD_RCLK_MASK (3 << 3)
+#define S3C2412_IISMOD_BCLK_32FS (0 << 1)
+#define S3C2412_IISMOD_BCLK_48FS (1 << 1)
+#define S3C2412_IISMOD_BCLK_16FS (2 << 1)
+#define S3C2412_IISMOD_BCLK_24FS (3 << 1)
+#define S3C2412_IISMOD_BCLK_MASK (3 << 1)
+#define S3C2412_IISMOD_8BIT (1 << 0)
+
+#define S3C64XX_IISMOD_CDCLKCON (1 << 12)
+
+#define S3C2412_IISPSR_PSREN (1 << 15)
+
+#define S3C64XX_IISFIC_TX2COUNT(x) (((x) >> 24) & 0xf)
+#define S3C64XX_IISFIC_TX1COUNT(x) (((x) >> 16) & 0xf)
+
+#define S3C2412_IISFIC_TXFLUSH (1 << 15)
+#define S3C2412_IISFIC_RXFLUSH (1 << 7)
+#define S3C2412_IISFIC_TXCOUNT(x) (((x) >> 8) & 0xf)
+#define S3C2412_IISFIC_RXCOUNT(x) (((x) >> 0) & 0xf)
+
+#define S5PC1XX_IISFICS_TXFLUSH (1 << 15)
+#define S5PC1XX_IISFICS_TXCOUNT(x) (((x) >> 8) & 0x7f)
+
+#endif /* __ASM_ARCH_REGS_S3C2412_IIS_H */
diff --git a/sound/soc/samsung/rx1950_uda1380.c b/sound/soc/samsung/rx1950_uda1380.c
new file mode 100644
index 00000000..1e574a5d
--- /dev/null
+++ b/sound/soc/samsung/rx1950_uda1380.c
@@ -0,0 +1,309 @@
+/*
+ * rx1950.c -- ALSA Soc Audio Layer
+ *
+ * Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com>
+ *
+ * Based on smdk2440.c and magician.c
+ *
+ * Authors: Graeme Gregory graeme.gregory@wolfsonmicro.com
+ * Philipp Zabel <philipp.zabel@gmail.com>
+ * Denis Grigoriev <dgreenday@gmail.com>
+ * Vasily Khoruzhick <anarsoul@gmail.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/gpio.h>
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <plat/regs-iis.h>
+#include <asm/mach-types.h>
+
+#include "s3c24xx-i2s.h"
+
+static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd);
+static int rx1950_startup(struct snd_pcm_substream *substream);
+static int rx1950_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params);
+static int rx1950_spk_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event);
+
+static unsigned int rates[] = {
+ 16000,
+ 44100,
+ 48000,
+};
+
+static struct snd_pcm_hw_constraint_list hw_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static struct snd_soc_jack hp_jack;
+
+static struct snd_soc_jack_pin hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Speaker",
+ .mask = SND_JACK_HEADPHONE,
+ .invert = 1,
+ },
+};
+
+static struct snd_soc_jack_gpio hp_jack_gpios[] = {
+ [0] = {
+ .gpio = S3C2410_GPG(12),
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .invert = 1,
+ .debounce_time = 200,
+ },
+};
+
+static struct snd_soc_ops rx1950_ops = {
+ .startup = rx1950_startup,
+ .hw_params = rx1950_hw_params,
+};
+
+/* s3c24xx digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link rx1950_uda1380_dai[] = {
+ {
+ .name = "uda1380",
+ .stream_name = "UDA1380 Duplex",
+ .cpu_dai_name = "s3c24xx-iis",
+ .codec_dai_name = "uda1380-hifi",
+ .init = rx1950_uda1380_init,
+ .platform_name = "samsung-audio",
+ .codec_name = "uda1380-codec.0-001a",
+ .ops = &rx1950_ops,
+ },
+};
+
+static struct snd_soc_card rx1950_asoc = {
+ .name = "rx1950",
+ .dai_link = rx1950_uda1380_dai,
+ .num_links = ARRAY_SIZE(rx1950_uda1380_dai),
+};
+
+/* rx1950 machine dapm widgets */
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", rx1950_spk_power),
+};
+
+/* rx1950 machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* headphone connected to VOUTLHP, VOUTRHP */
+ {"Headphone Jack", NULL, "VOUTLHP"},
+ {"Headphone Jack", NULL, "VOUTRHP"},
+
+ /* ext speaker connected to VOUTL, VOUTR */
+ {"Speaker", NULL, "VOUTL"},
+ {"Speaker", NULL, "VOUTR"},
+
+ /* mic is connected to VINM */
+ {"VINM", NULL, "Mic Jack"},
+};
+
+static struct platform_device *s3c24xx_snd_device;
+
+static int rx1950_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.rate_min = hw_rates.list[0];
+ runtime->hw.rate_max = hw_rates.list[hw_rates.count - 1];
+ runtime->hw.rates = SNDRV_PCM_RATE_KNOT;
+
+ return snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_rates);
+}
+
+static int rx1950_spk_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpio_set_value(S3C2410_GPA(1), 1);
+ else
+ gpio_set_value(S3C2410_GPA(1), 0);
+
+ return 0;
+}
+
+static int rx1950_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int div;
+ int ret;
+ unsigned int rate = params_rate(params);
+ int clk_source, fs_mode;
+
+ switch (rate) {
+ case 16000:
+ case 48000:
+ clk_source = S3C24XX_CLKSRC_PCLK;
+ fs_mode = S3C2410_IISMOD_256FS;
+ div = s3c24xx_i2s_get_clockrate() / (256 * rate);
+ if (s3c24xx_i2s_get_clockrate() % (256 * rate) > (128 * rate))
+ div++;
+ break;
+ case 44100:
+ case 88200:
+ clk_source = S3C24XX_CLKSRC_MPLL;
+ fs_mode = S3C2410_IISMOD_384FS;
+ div = 1;
+ break;
+ default:
+ printk(KERN_ERR "%s: rate %d is not supported\n",
+ __func__, rate);
+ return -EINVAL;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* select clock source */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source, rate,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ /* set MCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+ fs_mode);
+ if (ret < 0)
+ return ret;
+
+ /* set BCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
+ S3C2410_IISMOD_32FS);
+ if (ret < 0)
+ return ret;
+
+ /* set prescaler division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ S3C24XX_PRESCALE(div, div));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err;
+
+ /* Add rx1950 specific widgets */
+ err = snd_soc_dapm_new_controls(dapm, uda1380_dapm_widgets,
+ ARRAY_SIZE(uda1380_dapm_widgets));
+
+ if (err)
+ return err;
+
+ /* Set up rx1950 specific audio path audio_mapnects */
+ err = snd_soc_dapm_add_routes(dapm, audio_map,
+ ARRAY_SIZE(audio_map));
+
+ if (err)
+ return err;
+
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Speaker");
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+
+ snd_soc_dapm_sync(dapm);
+
+ snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE,
+ &hp_jack);
+
+ snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins),
+ hp_jack_pins);
+
+ snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
+ hp_jack_gpios);
+
+ return 0;
+}
+
+static int __init rx1950_init(void)
+{
+ int ret;
+
+ if (!machine_is_rx1950())
+ return -ENODEV;
+
+ /* configure some gpios */
+ ret = gpio_request(S3C2410_GPA(1), "speaker-power");
+ if (ret)
+ goto err_gpio;
+
+ ret = gpio_direction_output(S3C2410_GPA(1), 0);
+ if (ret)
+ goto err_gpio_conf;
+
+ s3c24xx_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!s3c24xx_snd_device) {
+ ret = -ENOMEM;
+ goto err_plat_alloc;
+ }
+
+ platform_set_drvdata(s3c24xx_snd_device, &rx1950_asoc);
+ ret = platform_device_add(s3c24xx_snd_device);
+
+ if (ret) {
+ platform_device_put(s3c24xx_snd_device);
+ goto err_plat_add;
+ }
+
+ return 0;
+
+err_plat_add:
+err_plat_alloc:
+err_gpio_conf:
+ gpio_free(S3C2410_GPA(1));
+
+err_gpio:
+ return ret;
+}
+
+static void __exit rx1950_exit(void)
+{
+ platform_device_unregister(s3c24xx_snd_device);
+ snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
+ hp_jack_gpios);
+ gpio_free(S3C2410_GPA(1));
+}
+
+module_init(rx1950_init);
+module_exit(rx1950_exit);
+
+/* Module information */
+MODULE_AUTHOR("Vasily Khoruzhick");
+MODULE_DESCRIPTION("ALSA SoC RX1950");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c
new file mode 100644
index 00000000..52074a2b
--- /dev/null
+++ b/sound/soc/samsung/s3c-i2s-v2.c
@@ -0,0 +1,756 @@
+/* sound/soc/samsung/s3c-i2c-v2.c
+ *
+ * ALSA Soc Audio Layer - I2S core for newer Samsung SoCs.
+ *
+ * Copyright (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com
+ * linux@wolfsonmicro.com
+ *
+ * Copyright (c) 2008, 2007, 2004-2005 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <mach/dma.h>
+
+#include "regs-i2s-v2.h"
+#include "s3c-i2s-v2.h"
+#include "dma.h"
+
+#undef S3C_IIS_V2_SUPPORTED
+
+#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) \
+ || defined(CONFIG_CPU_S5PV210)
+#define S3C_IIS_V2_SUPPORTED
+#endif
+
+#ifdef CONFIG_PLAT_S3C64XX
+#define S3C_IIS_V2_SUPPORTED
+#endif
+
+#ifndef S3C_IIS_V2_SUPPORTED
+#error Unsupported CPU model
+#endif
+
+#define S3C2412_I2S_DEBUG_CON 0
+
+static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai)
+{
+ return snd_soc_dai_get_drvdata(cpu_dai);
+}
+
+#define bit_set(v, b) (((v) & (b)) ? 1 : 0)
+
+#if S3C2412_I2S_DEBUG_CON
+static void dbg_showcon(const char *fn, u32 con)
+{
+ printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn,
+ bit_set(con, S3C2412_IISCON_LRINDEX),
+ bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY),
+ bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY),
+ bit_set(con, S3C2412_IISCON_TXFIFO_FULL),
+ bit_set(con, S3C2412_IISCON_RXFIFO_FULL));
+
+ printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n",
+ fn,
+ bit_set(con, S3C2412_IISCON_TXDMA_PAUSE),
+ bit_set(con, S3C2412_IISCON_RXDMA_PAUSE),
+ bit_set(con, S3C2412_IISCON_TXCH_PAUSE),
+ bit_set(con, S3C2412_IISCON_RXCH_PAUSE));
+ printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn,
+ bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE),
+ bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE),
+ bit_set(con, S3C2412_IISCON_IIS_ACTIVE));
+}
+#else
+static inline void dbg_showcon(const char *fn, u32 con)
+{
+}
+#endif
+
+
+/* Turn on or off the transmission path. */
+static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on)
+{
+ void __iomem *regs = i2s->regs;
+ u32 fic, con, mod;
+
+ pr_debug("%s(%d)\n", __func__, on);
+
+ fic = readl(regs + S3C2412_IISFIC);
+ con = readl(regs + S3C2412_IISCON);
+ mod = readl(regs + S3C2412_IISMOD);
+
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+
+ if (on) {
+ con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+ con &= ~S3C2412_IISCON_TXDMA_PAUSE;
+ con &= ~S3C2412_IISCON_TXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXONLY:
+ case S3C2412_IISMOD_MODE_TXRX:
+ /* do nothing, we are in the right mode */
+ break;
+
+ case S3C2412_IISMOD_MODE_RXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXRX;
+ break;
+
+ default:
+ dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ break;
+ }
+
+ writel(con, regs + S3C2412_IISCON);
+ writel(mod, regs + S3C2412_IISMOD);
+ } else {
+ /* Note, we do not have any indication that the FIFO problems
+ * tha the S3C2410/2440 had apply here, so we should be able
+ * to disable the DMA and TX without resetting the FIFOS.
+ */
+
+ con |= S3C2412_IISCON_TXDMA_PAUSE;
+ con |= S3C2412_IISCON_TXCH_PAUSE;
+ con &= ~S3C2412_IISCON_TXDMA_ACTIVE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXRX:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_RXONLY;
+ break;
+
+ case S3C2412_IISMOD_MODE_TXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ con &= ~S3C2412_IISCON_IIS_ACTIVE;
+ break;
+
+ default:
+ dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ break;
+ }
+
+ writel(mod, regs + S3C2412_IISMOD);
+ writel(con, regs + S3C2412_IISCON);
+ }
+
+ fic = readl(regs + S3C2412_IISFIC);
+ dbg_showcon(__func__, con);
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on)
+{
+ void __iomem *regs = i2s->regs;
+ u32 fic, con, mod;
+
+ pr_debug("%s(%d)\n", __func__, on);
+
+ fic = readl(regs + S3C2412_IISFIC);
+ con = readl(regs + S3C2412_IISCON);
+ mod = readl(regs + S3C2412_IISMOD);
+
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+
+ if (on) {
+ con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+ con &= ~S3C2412_IISCON_RXDMA_PAUSE;
+ con &= ~S3C2412_IISCON_RXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXRX:
+ case S3C2412_IISMOD_MODE_RXONLY:
+ /* do nothing, we are in the right mode */
+ break;
+
+ case S3C2412_IISMOD_MODE_TXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXRX;
+ break;
+
+ default:
+ dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ }
+
+ writel(mod, regs + S3C2412_IISMOD);
+ writel(con, regs + S3C2412_IISCON);
+ } else {
+ /* See txctrl notes on FIFOs. */
+
+ con &= ~S3C2412_IISCON_RXDMA_ACTIVE;
+ con |= S3C2412_IISCON_RXDMA_PAUSE;
+ con |= S3C2412_IISCON_RXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_RXONLY:
+ con &= ~S3C2412_IISCON_IIS_ACTIVE;
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ break;
+
+ case S3C2412_IISMOD_MODE_TXRX:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXONLY;
+ break;
+
+ default:
+ dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ }
+
+ writel(con, regs + S3C2412_IISCON);
+ writel(mod, regs + S3C2412_IISMOD);
+ }
+
+ fic = readl(regs + S3C2412_IISFIC);
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
+
+/*
+ * Wait for the LR signal to allow synchronisation to the L/R clock
+ * from the codec. May only be needed for slave mode.
+ */
+static int s3c2412_snd_lrsync(struct s3c_i2sv2_info *i2s)
+{
+ u32 iiscon;
+ unsigned long loops = msecs_to_loops(5);
+
+ pr_debug("Entered %s\n", __func__);
+
+ while (--loops) {
+ iiscon = readl(i2s->regs + S3C2412_IISCON);
+ if (iiscon & S3C2412_IISCON_LRINDEX)
+ break;
+
+ cpu_relax();
+ }
+
+ if (!loops) {
+ printk(KERN_ERR "%s: timeout\n", __func__);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/*
+ * Set S3C2412 I2S DAI format
+ */
+static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ pr_debug("hw_params r: IISMOD: %x \n", iismod);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ i2s->master = 0;
+ iismod |= S3C2412_IISMOD_SLAVE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ i2s->master = 1;
+ iismod &= ~S3C2412_IISMOD_SLAVE;
+ break;
+ default:
+ pr_err("unknwon master/slave format\n");
+ return -EINVAL;
+ }
+
+ iismod &= ~S3C2412_IISMOD_SDF_MASK;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ iismod |= S3C2412_IISMOD_LR_RLOW;
+ iismod |= S3C2412_IISMOD_SDF_MSB;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iismod |= S3C2412_IISMOD_LR_RLOW;
+ iismod |= S3C2412_IISMOD_SDF_LSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ iismod &= ~S3C2412_IISMOD_LR_RLOW;
+ iismod |= S3C2412_IISMOD_SDF_IIS;
+ break;
+ default:
+ pr_err("Unknown data format\n");
+ return -EINVAL;
+ }
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ pr_debug("hw_params w: IISMOD: %x \n", iismod);
+ return 0;
+}
+
+static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+ struct s3c_dma_params *dma_data;
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = i2s->dma_playback;
+ else
+ dma_data = i2s->dma_capture;
+
+ snd_soc_dai_set_dma_data(dai, substream, dma_data);
+
+ /* Working copies of register */
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: r: IISMOD: %x\n", __func__, iismod);
+
+ iismod &= ~S3C64XX_IISMOD_BLC_MASK;
+ /* Sample size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ iismod |= S3C64XX_IISMOD_BLC_8BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iismod |= S3C64XX_IISMOD_BLC_24BIT;
+ break;
+ }
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: w: IISMOD: %x\n", __func__, iismod);
+
+ return 0;
+}
+
+static int s3c_i2sv2_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+ pr_debug("Entered %s\n", __func__);
+ pr_debug("%s r: IISMOD: %x\n", __func__, iismod);
+
+ switch (clk_id) {
+ case S3C_I2SV2_CLKSRC_PCLK:
+ iismod &= ~S3C2412_IISMOD_IMS_SYSMUX;
+ break;
+
+ case S3C_I2SV2_CLKSRC_AUDIOBUS:
+ iismod |= S3C2412_IISMOD_IMS_SYSMUX;
+ break;
+
+ case S3C_I2SV2_CLKSRC_CDCLK:
+ /* Error if controller doesn't have the CDCLKCON bit */
+ if (!(i2s->feature & S3C_FEATURE_CDCLKCON))
+ return -EINVAL;
+
+ switch (dir) {
+ case SND_SOC_CLOCK_IN:
+ iismod |= S3C64XX_IISMOD_CDCLKCON;
+ break;
+ case SND_SOC_CLOCK_OUT:
+ iismod &= ~S3C64XX_IISMOD_CDCLKCON;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s w: IISMOD: %x\n", __func__, iismod);
+
+ return 0;
+}
+
+static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct s3c_i2sv2_info *i2s = to_info(rtd->cpu_dai);
+ int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+ unsigned long irqs;
+ int ret = 0;
+ struct s3c_dma_params *dma_data =
+ snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ pr_debug("Entered %s\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* On start, ensure that the FIFOs are cleared and reset. */
+
+ writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH,
+ i2s->regs + S3C2412_IISFIC);
+
+ /* clear again, just in case */
+ writel(0x0, i2s->regs + S3C2412_IISFIC);
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!i2s->master) {
+ ret = s3c2412_snd_lrsync(i2s);
+ if (ret)
+ goto exit_err;
+ }
+
+ local_irq_save(irqs);
+
+ if (capture)
+ s3c2412_snd_rxctrl(i2s, 1);
+ else
+ s3c2412_snd_txctrl(i2s, 1);
+
+ local_irq_restore(irqs);
+
+ /*
+ * Load the next buffer to DMA to meet the reqirement
+ * of the auto reload mechanism of S3C24XX.
+ * This call won't bother S3C64XX.
+ */
+ s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED);
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ local_irq_save(irqs);
+
+ if (capture)
+ s3c2412_snd_rxctrl(i2s, 0);
+ else
+ s3c2412_snd_txctrl(i2s, 0);
+
+ local_irq_restore(irqs);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+exit_err:
+ return ret;
+}
+
+/*
+ * Set S3C2412 Clock dividers
+ */
+static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 reg;
+
+ pr_debug("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div);
+
+ switch (div_id) {
+ case S3C_I2SV2_DIV_BCLK:
+ switch (div) {
+ case 16:
+ div = S3C2412_IISMOD_BCLK_16FS;
+ break;
+
+ case 32:
+ div = S3C2412_IISMOD_BCLK_32FS;
+ break;
+
+ case 24:
+ div = S3C2412_IISMOD_BCLK_24FS;
+ break;
+
+ case 48:
+ div = S3C2412_IISMOD_BCLK_48FS;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ reg = readl(i2s->regs + S3C2412_IISMOD);
+ reg &= ~S3C2412_IISMOD_BCLK_MASK;
+ writel(reg | div, i2s->regs + S3C2412_IISMOD);
+
+ pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
+ break;
+
+ case S3C_I2SV2_DIV_RCLK:
+ switch (div) {
+ case 256:
+ div = S3C2412_IISMOD_RCLK_256FS;
+ break;
+
+ case 384:
+ div = S3C2412_IISMOD_RCLK_384FS;
+ break;
+
+ case 512:
+ div = S3C2412_IISMOD_RCLK_512FS;
+ break;
+
+ case 768:
+ div = S3C2412_IISMOD_RCLK_768FS;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ reg = readl(i2s->regs + S3C2412_IISMOD);
+ reg &= ~S3C2412_IISMOD_RCLK_MASK;
+ writel(reg | div, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
+ break;
+
+ case S3C_I2SV2_DIV_PRESCALER:
+ if (div >= 0) {
+ writel((div << 8) | S3C2412_IISPSR_PSREN,
+ i2s->regs + S3C2412_IISPSR);
+ } else {
+ writel(0x0, i2s->regs + S3C2412_IISPSR);
+ }
+ pr_debug("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR));
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_sframes_t s3c2412_i2s_delay(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+ u32 reg = readl(i2s->regs + S3C2412_IISFIC);
+ snd_pcm_sframes_t delay;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ delay = S3C2412_IISFIC_TXCOUNT(reg);
+ else
+ delay = S3C2412_IISFIC_RXCOUNT(reg);
+
+ return delay;
+}
+
+struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+ if (iismod & S3C2412_IISMOD_IMS_SYSMUX)
+ return i2s->iis_cclk;
+ else
+ return i2s->iis_pclk;
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_get_clock);
+
+/* default table of all avaialable root fs divisors */
+static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 };
+
+int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
+ unsigned int *fstab,
+ unsigned int rate, struct clk *clk)
+{
+ unsigned long clkrate = clk_get_rate(clk);
+ unsigned int div;
+ unsigned int fsclk;
+ unsigned int actual;
+ unsigned int fs;
+ unsigned int fsdiv;
+ signed int deviation = 0;
+ unsigned int best_fs = 0;
+ unsigned int best_div = 0;
+ unsigned int best_rate = 0;
+ unsigned int best_deviation = INT_MAX;
+
+ pr_debug("Input clock rate %ldHz\n", clkrate);
+
+ if (fstab == NULL)
+ fstab = iis_fs_tab;
+
+ for (fs = 0; fs < ARRAY_SIZE(iis_fs_tab); fs++) {
+ fsdiv = iis_fs_tab[fs];
+
+ fsclk = clkrate / fsdiv;
+ div = fsclk / rate;
+
+ if ((fsclk % rate) > (rate / 2))
+ div++;
+
+ if (div <= 1)
+ continue;
+
+ actual = clkrate / (fsdiv * div);
+ deviation = actual - rate;
+
+ printk(KERN_DEBUG "%ufs: div %u => result %u, deviation %d\n",
+ fsdiv, div, actual, deviation);
+
+ deviation = abs(deviation);
+
+ if (deviation < best_deviation) {
+ best_fs = fsdiv;
+ best_div = div;
+ best_rate = actual;
+ best_deviation = deviation;
+ }
+
+ if (deviation == 0)
+ break;
+ }
+
+ printk(KERN_DEBUG "best: fs=%u, div=%u, rate=%u\n",
+ best_fs, best_div, best_rate);
+
+ info->fs_div = best_fs;
+ info->clk_div = best_div;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_iis_calc_rate);
+
+int s3c_i2sv2_probe(struct snd_soc_dai *dai,
+ struct s3c_i2sv2_info *i2s,
+ unsigned long base)
+{
+ struct device *dev = dai->dev;
+ unsigned int iismod;
+
+ i2s->dev = dev;
+
+ /* record our i2s structure for later use in the callbacks */
+ snd_soc_dai_set_drvdata(dai, i2s);
+
+ i2s->regs = ioremap(base, 0x100);
+ if (i2s->regs == NULL) {
+ dev_err(dev, "cannot ioremap registers\n");
+ return -ENXIO;
+ }
+
+ i2s->iis_pclk = clk_get(dev, "iis");
+ if (IS_ERR(i2s->iis_pclk)) {
+ dev_err(dev, "failed to get iis_clock\n");
+ iounmap(i2s->regs);
+ return -ENOENT;
+ }
+
+ clk_enable(i2s->iis_pclk);
+
+ /* Mark ourselves as in TXRX mode so we can run through our cleanup
+ * process without warnings. */
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ iismod |= S3C2412_IISMOD_MODE_TXRX;
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ s3c2412_snd_txctrl(i2s, 0);
+ s3c2412_snd_rxctrl(i2s, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_probe);
+
+#ifdef CONFIG_PM
+static int s3c2412_i2s_suspend(struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+ u32 iismod;
+
+ if (dai->active) {
+ i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD);
+ i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON);
+ i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR);
+
+ /* some basic suspend checks */
+
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+ if (iismod & S3C2412_IISCON_RXDMA_ACTIVE)
+ pr_warning("%s: RXDMA active?\n", __func__);
+
+ if (iismod & S3C2412_IISCON_TXDMA_ACTIVE)
+ pr_warning("%s: TXDMA active?\n", __func__);
+
+ if (iismod & S3C2412_IISCON_IIS_ACTIVE)
+ pr_warning("%s: IIS active\n", __func__);
+ }
+
+ return 0;
+}
+
+static int s3c2412_i2s_resume(struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+
+ pr_info("dai_active %d, IISMOD %08x, IISCON %08x\n",
+ dai->active, i2s->suspend_iismod, i2s->suspend_iiscon);
+
+ if (dai->active) {
+ writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON);
+ writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD);
+ writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR);
+
+ writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH,
+ i2s->regs + S3C2412_IISFIC);
+
+ ndelay(250);
+ writel(0x0, i2s->regs + S3C2412_IISFIC);
+ }
+
+ return 0;
+}
+#else
+#define s3c2412_i2s_suspend NULL
+#define s3c2412_i2s_resume NULL
+#endif
+
+int s3c_i2sv2_register_dai(struct device *dev, int id,
+ struct snd_soc_dai_driver *drv)
+{
+ struct snd_soc_dai_ops *ops = drv->ops;
+
+ ops->trigger = s3c2412_i2s_trigger;
+ if (!ops->hw_params)
+ ops->hw_params = s3c_i2sv2_hw_params;
+ ops->set_fmt = s3c2412_i2s_set_fmt;
+ ops->set_clkdiv = s3c2412_i2s_set_clkdiv;
+ ops->set_sysclk = s3c_i2sv2_set_sysclk;
+
+ /* Allow overriding by (for example) IISv4 */
+ if (!ops->delay)
+ ops->delay = s3c2412_i2s_delay;
+
+ drv->suspend = s3c2412_i2s_suspend;
+ drv->resume = s3c2412_i2s_resume;
+
+ return snd_soc_register_dai(dev, drv);
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_register_dai);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c-i2s-v2.h b/sound/soc/samsung/s3c-i2s-v2.h
new file mode 100644
index 00000000..f8297d9b
--- /dev/null
+++ b/sound/soc/samsung/s3c-i2s-v2.h
@@ -0,0 +1,106 @@
+/* sound/soc/samsung/s3c-i2s-v2.h
+ *
+ * ALSA Soc Audio Layer - S3C_I2SV2 I2S driver
+ *
+ * Copyright (c) 2007 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+*/
+
+/* This code is the core support for the I2S block found in a number of
+ * Samsung SoC devices which is unofficially named I2S-V2. Currently the
+ * S3C2412 and the S3C64XX series use this block to provide 1 or 2 I2S
+ * channels via configurable GPIO.
+ */
+
+#ifndef __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H
+#define __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H __FILE__
+
+#define S3C_I2SV2_DIV_BCLK (1)
+#define S3C_I2SV2_DIV_RCLK (2)
+#define S3C_I2SV2_DIV_PRESCALER (3)
+
+#define S3C_I2SV2_CLKSRC_PCLK 0
+#define S3C_I2SV2_CLKSRC_AUDIOBUS 1
+#define S3C_I2SV2_CLKSRC_CDCLK 2
+
+/* Set this flag for I2S controllers that have the bit IISMOD[12]
+ * bridge/break RCLK signal and external Xi2sCDCLK pin.
+ */
+#define S3C_FEATURE_CDCLKCON (1 << 0)
+
+/**
+ * struct s3c_i2sv2_info - S3C I2S-V2 information
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device registe block.
+ * @feature: Set of bit-flags indicating features of the controller.
+ * @master: True if the I2S core is the I2S bit clock master.
+ * @dma_playback: DMA information for playback channel.
+ * @dma_capture: DMA information for capture channel.
+ * @suspend_iismod: PM save for the IISMOD register.
+ * @suspend_iiscon: PM save for the IISCON register.
+ * @suspend_iispsr: PM save for the IISPSR register.
+ *
+ * This is the private codec state for the hardware associated with an
+ * I2S channel such as the register mappings and clock sources.
+ */
+struct s3c_i2sv2_info {
+ struct device *dev;
+ void __iomem *regs;
+
+ u32 feature;
+
+ struct clk *iis_pclk;
+ struct clk *iis_cclk;
+
+ unsigned char master;
+
+ struct s3c_dma_params *dma_playback;
+ struct s3c_dma_params *dma_capture;
+
+ u32 suspend_iismod;
+ u32 suspend_iiscon;
+ u32 suspend_iispsr;
+
+ unsigned long base;
+};
+
+extern struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai);
+
+struct s3c_i2sv2_rate_calc {
+ unsigned int clk_div; /* for prescaler */
+ unsigned int fs_div; /* for root frame clock */
+};
+
+extern int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
+ unsigned int *fstab,
+ unsigned int rate, struct clk *clk);
+
+/**
+ * s3c_i2sv2_probe - probe for i2s device helper
+ * @dai: The ASoC DAI structure supplied to the original probe.
+ * @i2s: Our local i2s structure to fill in.
+ * @base: The base address for the registers.
+ */
+extern int s3c_i2sv2_probe(struct snd_soc_dai *dai,
+ struct s3c_i2sv2_info *i2s,
+ unsigned long base);
+
+/**
+ * s3c_i2sv2_register_dai - register dai with soc core
+ * @dev: DAI device
+ * @id: DAI ID
+ * @drv: The driver structure to register
+ *
+ * Fill in any missing fields and then register the given dai with the
+ * soc core.
+ */
+extern int s3c_i2sv2_register_dai(struct device *dev, int id,
+ struct snd_soc_dai_driver *drv);
+
+#endif /* __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H */
diff --git a/sound/soc/samsung/s3c2412-i2s.c b/sound/soc/samsung/s3c2412-i2s.c
new file mode 100644
index 00000000..841ab14c
--- /dev/null
+++ b/sound/soc/samsung/s3c2412-i2s.c
@@ -0,0 +1,202 @@
+/* sound/soc/samsung/s3c2412-i2s.c
+ *
+ * ALSA Soc Audio Layer - S3C2412 I2S driver
+ *
+ * Copyright (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com
+ * linux@wolfsonmicro.com
+ *
+ * Copyright (c) 2007, 2004-2005 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <mach/regs-gpio.h>
+#include <mach/dma.h>
+
+#include "dma.h"
+#include "regs-i2s-v2.h"
+#include "s3c2412-i2s.h"
+
+static struct s3c2410_dma_client s3c2412_dma_client_out = {
+ .name = "I2S PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c2412_dma_client_in = {
+ .name = "I2S PCM Stereo in"
+};
+
+static struct s3c_dma_params s3c2412_i2s_pcm_stereo_out = {
+ .client = &s3c2412_dma_client_out,
+ .channel = DMACH_I2S_OUT,
+ .dma_addr = S3C2410_PA_IIS + S3C2412_IISTXD,
+ .dma_size = 4,
+};
+
+static struct s3c_dma_params s3c2412_i2s_pcm_stereo_in = {
+ .client = &s3c2412_dma_client_in,
+ .channel = DMACH_I2S_IN,
+ .dma_addr = S3C2410_PA_IIS + S3C2412_IISRXD,
+ .dma_size = 4,
+};
+
+static struct s3c_i2sv2_info s3c2412_i2s;
+
+static int s3c2412_i2s_probe(struct snd_soc_dai *dai)
+{
+ int ret;
+
+ pr_debug("Entered %s\n", __func__);
+
+ ret = s3c_i2sv2_probe(dai, &s3c2412_i2s, S3C2410_PA_IIS);
+ if (ret)
+ return ret;
+
+ s3c2412_i2s.dma_capture = &s3c2412_i2s_pcm_stereo_in;
+ s3c2412_i2s.dma_playback = &s3c2412_i2s_pcm_stereo_out;
+
+ s3c2412_i2s.iis_cclk = clk_get(dai->dev, "i2sclk");
+ if (s3c2412_i2s.iis_cclk == NULL) {
+ pr_err("failed to get i2sclk clock\n");
+ iounmap(s3c2412_i2s.regs);
+ return -ENODEV;
+ }
+
+ /* Set MPLL as the source for IIS CLK */
+
+ clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll"));
+ clk_enable(s3c2412_i2s.iis_cclk);
+
+ s3c2412_i2s.iis_cclk = s3c2412_i2s.iis_pclk;
+
+ /* Configure the I2S pins in correct mode */
+ s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
+ s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
+
+ return 0;
+}
+
+static int s3c2412_i2s_remove(struct snd_soc_dai *dai)
+{
+ clk_disable(s3c2412_i2s.iis_cclk);
+ clk_put(s3c2412_i2s.iis_cclk);
+ iounmap(s3c2412_i2s.regs);
+
+ return 0;
+}
+
+static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct s3c_i2sv2_info *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ struct s3c_dma_params *dma_data;
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = i2s->dma_playback;
+ else
+ dma_data = i2s->dma_capture;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: r: IISMOD: %x\n", __func__, iismod);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ iismod |= S3C2412_IISMOD_8BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iismod &= ~S3C2412_IISMOD_8BIT;
+ break;
+ }
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: w: IISMOD: %x\n", __func__, iismod);
+
+ return 0;
+}
+
+#define S3C2412_I2S_RATES \
+ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+static struct snd_soc_dai_ops s3c2412_i2s_dai_ops = {
+ .hw_params = s3c2412_i2s_hw_params,
+};
+
+static struct snd_soc_dai_driver s3c2412_i2s_dai = {
+ .probe = s3c2412_i2s_probe,
+ .remove = s3c2412_i2s_remove,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C2412_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C2412_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &s3c2412_i2s_dai_ops,
+};
+
+static __devinit int s3c2412_iis_dev_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_dai(&pdev->dev, &s3c2412_i2s_dai);
+}
+
+static __devexit int s3c2412_iis_dev_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver s3c2412_iis_driver = {
+ .probe = s3c2412_iis_dev_probe,
+ .remove = s3c2412_iis_dev_remove,
+ .driver = {
+ .name = "s3c2412-iis",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s3c2412_i2s_init(void)
+{
+ return platform_driver_register(&s3c2412_iis_driver);
+}
+module_init(s3c2412_i2s_init);
+
+static void __exit s3c2412_i2s_exit(void)
+{
+ platform_driver_unregister(&s3c2412_iis_driver);
+}
+module_exit(s3c2412_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("S3C2412 I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s3c2412-iis");
diff --git a/sound/soc/samsung/s3c2412-i2s.h b/sound/soc/samsung/s3c2412-i2s.h
new file mode 100644
index 00000000..02ad5794
--- /dev/null
+++ b/sound/soc/samsung/s3c2412-i2s.h
@@ -0,0 +1,27 @@
+/* sound/soc/samsung/s3c2412-i2s.c
+ *
+ * ALSA Soc Audio Layer - S3C2412 I2S driver
+ *
+ * Copyright (c) 2007 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+*/
+
+#ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H
+#define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__
+
+#include "s3c-i2s-v2.h"
+
+#define S3C2412_DIV_BCLK S3C_I2SV2_DIV_BCLK
+#define S3C2412_DIV_RCLK S3C_I2SV2_DIV_RCLK
+#define S3C2412_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER
+
+#define S3C2412_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK
+#define S3C2412_CLKSRC_I2SCLK S3C_I2SV2_CLKSRC_AUDIOBUS
+
+#endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */
diff --git a/sound/soc/samsung/s3c24xx-i2s.c b/sound/soc/samsung/s3c24xx-i2s.c
new file mode 100644
index 00000000..63d8849d
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx-i2s.c
@@ -0,0 +1,507 @@
+/*
+ * s3c24xx-i2s.c -- ALSA Soc Audio Layer
+ *
+ * (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * Copyright 2004-2005 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <mach/regs-gpio.h>
+#include <mach/dma.h>
+#include <plat/regs-iis.h>
+
+#include "dma.h"
+#include "s3c24xx-i2s.h"
+
+static struct s3c2410_dma_client s3c24xx_dma_client_out = {
+ .name = "I2S PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c24xx_dma_client_in = {
+ .name = "I2S PCM Stereo in"
+};
+
+static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_out = {
+ .client = &s3c24xx_dma_client_out,
+ .channel = DMACH_I2S_OUT,
+ .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO,
+ .dma_size = 2,
+};
+
+static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_in = {
+ .client = &s3c24xx_dma_client_in,
+ .channel = DMACH_I2S_IN,
+ .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO,
+ .dma_size = 2,
+};
+
+struct s3c24xx_i2s_info {
+ void __iomem *regs;
+ struct clk *iis_clk;
+ u32 iiscon;
+ u32 iismod;
+ u32 iisfcon;
+ u32 iispsr;
+};
+static struct s3c24xx_i2s_info s3c24xx_i2s;
+
+static void s3c24xx_snd_txctrl(int on)
+{
+ u32 iisfcon;
+ u32 iiscon;
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+ iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+ pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+
+ if (on) {
+ iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE;
+ iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
+ iiscon &= ~S3C2410_IISCON_TXIDLE;
+ iismod |= S3C2410_IISMOD_TXMODE;
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ } else {
+ /* note, we have to disable the FIFOs otherwise bad things
+ * seem to happen when the DMA stops. According to the
+ * Samsung supplied kernel, this should allow the DMA
+ * engine and FIFOs to reset. If this isn't allowed, the
+ * DMA engine will simply freeze randomly.
+ */
+
+ iisfcon &= ~S3C2410_IISFCON_TXENABLE;
+ iisfcon &= ~S3C2410_IISFCON_TXDMA;
+ iiscon |= S3C2410_IISCON_TXIDLE;
+ iiscon &= ~S3C2410_IISCON_TXDMAEN;
+ iismod &= ~S3C2410_IISMOD_TXMODE;
+
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ }
+
+ pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+}
+
+static void s3c24xx_snd_rxctrl(int on)
+{
+ u32 iisfcon;
+ u32 iiscon;
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+ iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+ pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+
+ if (on) {
+ iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE;
+ iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
+ iiscon &= ~S3C2410_IISCON_RXIDLE;
+ iismod |= S3C2410_IISMOD_RXMODE;
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ } else {
+ /* note, we have to disable the FIFOs otherwise bad things
+ * seem to happen when the DMA stops. According to the
+ * Samsung supplied kernel, this should allow the DMA
+ * engine and FIFOs to reset. If this isn't allowed, the
+ * DMA engine will simply freeze randomly.
+ */
+
+ iisfcon &= ~S3C2410_IISFCON_RXENABLE;
+ iisfcon &= ~S3C2410_IISFCON_RXDMA;
+ iiscon |= S3C2410_IISCON_RXIDLE;
+ iiscon &= ~S3C2410_IISCON_RXDMAEN;
+ iismod &= ~S3C2410_IISMOD_RXMODE;
+
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ }
+
+ pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+}
+
+/*
+ * Wait for the LR signal to allow synchronisation to the L/R clock
+ * from the codec. May only be needed for slave mode.
+ */
+static int s3c24xx_snd_lrsync(void)
+{
+ u32 iiscon;
+ int timeout = 50; /* 5ms */
+
+ pr_debug("Entered %s\n", __func__);
+
+ while (1) {
+ iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ if (iiscon & S3C2410_IISCON_LRINDEX)
+ break;
+
+ if (!timeout--)
+ return -ETIMEDOUT;
+ udelay(100);
+ }
+
+ return 0;
+}
+
+/*
+ * Check whether CPU is the master or slave
+ */
+static inline int s3c24xx_snd_is_clkmaster(void)
+{
+ pr_debug("Entered %s\n", __func__);
+
+ return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1;
+}
+
+/*
+ * Set S3C24xx I2S DAI format
+ */
+static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+ pr_debug("hw_params r: IISMOD: %x \n", iismod);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iismod |= S3C2410_IISMOD_SLAVE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ iismod &= ~S3C2410_IISMOD_SLAVE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ iismod |= S3C2410_IISMOD_MSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ iismod &= ~S3C2410_IISMOD_MSB;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ pr_debug("hw_params w: IISMOD: %x \n", iismod);
+ return 0;
+}
+
+static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct s3c_dma_params *dma_data;
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &s3c24xx_i2s_pcm_stereo_out;
+ else
+ dma_data = &s3c24xx_i2s_pcm_stereo_in;
+
+ snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);
+
+ /* Working copies of register */
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+ pr_debug("hw_params r: IISMOD: %x\n", iismod);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ iismod &= ~S3C2410_IISMOD_16BIT;
+ dma_data->dma_size = 1;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iismod |= S3C2410_IISMOD_16BIT;
+ dma_data->dma_size = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ pr_debug("hw_params w: IISMOD: %x\n", iismod);
+ return 0;
+}
+
+static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+ struct s3c_dma_params *dma_data =
+ snd_soc_dai_get_dma_data(dai, substream);
+
+ pr_debug("Entered %s\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!s3c24xx_snd_is_clkmaster()) {
+ ret = s3c24xx_snd_lrsync();
+ if (ret)
+ goto exit_err;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s3c24xx_snd_rxctrl(1);
+ else
+ s3c24xx_snd_txctrl(1);
+
+ s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s3c24xx_snd_rxctrl(0);
+ else
+ s3c24xx_snd_txctrl(0);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+exit_err:
+ return ret;
+}
+
+/*
+ * Set S3C24xx Clock source
+ */
+static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+ pr_debug("Entered %s\n", __func__);
+
+ iismod &= ~S3C2440_IISMOD_MPLL;
+
+ switch (clk_id) {
+ case S3C24XX_CLKSRC_PCLK:
+ break;
+ case S3C24XX_CLKSRC_MPLL:
+ iismod |= S3C2440_IISMOD_MPLL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ return 0;
+}
+
+/*
+ * Set S3C24xx Clock dividers
+ */
+static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ u32 reg;
+
+ pr_debug("Entered %s\n", __func__);
+
+ switch (div_id) {
+ case S3C24XX_DIV_BCLK:
+ reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
+ writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ break;
+ case S3C24XX_DIV_MCLK:
+ reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
+ writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ break;
+ case S3C24XX_DIV_PRESCALER:
+ writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
+ reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * To avoid duplicating clock code, allow machine driver to
+ * get the clockrate from here.
+ */
+u32 s3c24xx_i2s_get_clockrate(void)
+{
+ return clk_get_rate(s3c24xx_i2s.iis_clk);
+}
+EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate);
+
+static int s3c24xx_i2s_probe(struct snd_soc_dai *dai)
+{
+ pr_debug("Entered %s\n", __func__);
+
+ s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
+ if (s3c24xx_i2s.regs == NULL)
+ return -ENXIO;
+
+ s3c24xx_i2s.iis_clk = clk_get(dai->dev, "iis");
+ if (s3c24xx_i2s.iis_clk == NULL) {
+ pr_err("failed to get iis_clock\n");
+ iounmap(s3c24xx_i2s.regs);
+ return -ENODEV;
+ }
+ clk_enable(s3c24xx_i2s.iis_clk);
+
+ /* Configure the I2S pins in correct mode */
+ s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
+ s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
+
+ writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
+
+ s3c24xx_snd_txctrl(0);
+ s3c24xx_snd_rxctrl(0);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai)
+{
+ pr_debug("Entered %s\n", __func__);
+
+ s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+ s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+ s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);
+
+ clk_disable(s3c24xx_i2s.iis_clk);
+
+ return 0;
+}
+
+static int s3c24xx_i2s_resume(struct snd_soc_dai *cpu_dai)
+{
+ pr_debug("Entered %s\n", __func__);
+ clk_enable(s3c24xx_i2s.iis_clk);
+
+ writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);
+
+ return 0;
+}
+#else
+#define s3c24xx_i2s_suspend NULL
+#define s3c24xx_i2s_resume NULL
+#endif
+
+
+#define S3C24XX_I2S_RATES \
+ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+static struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
+ .trigger = s3c24xx_i2s_trigger,
+ .hw_params = s3c24xx_i2s_hw_params,
+ .set_fmt = s3c24xx_i2s_set_fmt,
+ .set_clkdiv = s3c24xx_i2s_set_clkdiv,
+ .set_sysclk = s3c24xx_i2s_set_sysclk,
+};
+
+static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
+ .probe = s3c24xx_i2s_probe,
+ .suspend = s3c24xx_i2s_suspend,
+ .resume = s3c24xx_i2s_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C24XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C24XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &s3c24xx_i2s_dai_ops,
+};
+
+static __devinit int s3c24xx_iis_dev_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
+}
+
+static __devexit int s3c24xx_iis_dev_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver s3c24xx_iis_driver = {
+ .probe = s3c24xx_iis_dev_probe,
+ .remove = s3c24xx_iis_dev_remove,
+ .driver = {
+ .name = "s3c24xx-iis",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s3c24xx_i2s_init(void)
+{
+ return platform_driver_register(&s3c24xx_iis_driver);
+}
+module_init(s3c24xx_i2s_init);
+
+static void __exit s3c24xx_i2s_exit(void)
+{
+ platform_driver_unregister(&s3c24xx_iis_driver);
+}
+module_exit(s3c24xx_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s3c24xx-iis");
diff --git a/sound/soc/samsung/s3c24xx-i2s.h b/sound/soc/samsung/s3c24xx-i2s.h
new file mode 100644
index 00000000..f9ca04ed
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx-i2s.h
@@ -0,0 +1,35 @@
+/*
+ * s3c24xx-i2s.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ *
+ * Revision history
+ * 10th Nov 2006 Initial version.
+ */
+
+#ifndef S3C24XXI2S_H_
+#define S3C24XXI2S_H_
+
+/* clock sources */
+#define S3C24XX_CLKSRC_PCLK 0
+#define S3C24XX_CLKSRC_MPLL 1
+
+/* Clock dividers */
+#define S3C24XX_DIV_MCLK 0
+#define S3C24XX_DIV_BCLK 1
+#define S3C24XX_DIV_PRESCALER 2
+
+/* prescaler */
+#define S3C24XX_PRESCALE(a,b) \
+ (((a - 1) << S3C2410_IISPSR_INTSHIFT) | ((b - 1) << S3C2410_IISPSR_EXTSHFIT))
+
+u32 s3c24xx_i2s_get_clockrate(void);
+
+#endif /*S3C24XXI2S_H_*/
diff --git a/sound/soc/samsung/s3c24xx_simtec.c b/sound/soc/samsung/s3c24xx_simtec.c
new file mode 100644
index 00000000..349566f0
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx_simtec.c
@@ -0,0 +1,387 @@
+/* sound/soc/samsung/s3c24xx_simtec.c
+ *
+ * Copyright 2009 Simtec Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/gpio.h>
+#include <linux/clk.h>
+
+#include <sound/soc.h>
+
+#include <plat/audio-simtec.h>
+
+#include "s3c24xx-i2s.h"
+#include "s3c24xx_simtec.h"
+
+static struct s3c24xx_audio_simtec_pdata *pdata;
+static struct clk *xtal_clk;
+
+static int spk_gain;
+static int spk_unmute;
+
+/**
+ * speaker_gain_get - read the speaker gain setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be updated.
+ *
+ * Read the value for the AMP gain control.
+ */
+static int speaker_gain_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = spk_gain;
+ return 0;
+}
+
+/**
+ * speaker_gain_set - set the value of the speaker amp gain
+ * @value: The value to write.
+ */
+static void speaker_gain_set(int value)
+{
+ gpio_set_value_cansleep(pdata->amp_gain[0], value & 1);
+ gpio_set_value_cansleep(pdata->amp_gain[1], value >> 1);
+}
+
+/**
+ * speaker_gain_put - set the speaker gain setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be set.
+ *
+ * Set the value of the speaker gain from the specified
+ * @ucontrol setting.
+ *
+ * Note, if the speaker amp is muted, then we do not set a gain value
+ * as at-least one of the ICs that is fitted will try and power up even
+ * if the main control is set to off.
+ */
+static int speaker_gain_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int value = ucontrol->value.integer.value[0];
+
+ spk_gain = value;
+
+ if (!spk_unmute)
+ speaker_gain_set(value);
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new amp_gain_controls[] = {
+ SOC_SINGLE_EXT("Speaker Gain", 0, 0, 3, 0,
+ speaker_gain_get, speaker_gain_put),
+};
+
+/**
+ * spk_unmute_state - set the unmute state of the speaker
+ * @to: zero to unmute, non-zero to ununmute.
+ */
+static void spk_unmute_state(int to)
+{
+ pr_debug("%s: to=%d\n", __func__, to);
+
+ spk_unmute = to;
+ gpio_set_value(pdata->amp_gpio, to);
+
+ /* if we're umuting, also re-set the gain */
+ if (to && pdata->amp_gain[0] > 0)
+ speaker_gain_set(spk_gain);
+}
+
+/**
+ * speaker_unmute_get - read the speaker unmute setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be updated.
+ *
+ * Read the value for the AMP gain control.
+ */
+static int speaker_unmute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = spk_unmute;
+ return 0;
+}
+
+/**
+ * speaker_unmute_put - set the speaker unmute setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be set.
+ *
+ * Set the value of the speaker gain from the specified
+ * @ucontrol setting.
+ */
+static int speaker_unmute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ spk_unmute_state(ucontrol->value.integer.value[0]);
+ return 0;
+}
+
+/* This is added as a manual control as the speaker amps create clicks
+ * when their power state is changed, which are far more noticeable than
+ * anything produced by the CODEC itself.
+ */
+static const struct snd_kcontrol_new amp_unmute_controls[] = {
+ SOC_SINGLE_EXT("Speaker Switch", 0, 0, 1, 0,
+ speaker_unmute_get, speaker_unmute_put),
+};
+
+void simtec_audio_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+
+ if (pdata->amp_gpio > 0) {
+ pr_debug("%s: adding amp routes\n", __func__);
+
+ snd_soc_add_controls(codec, amp_unmute_controls,
+ ARRAY_SIZE(amp_unmute_controls));
+ }
+
+ if (pdata->amp_gain[0] > 0) {
+ pr_debug("%s: adding amp controls\n", __func__);
+ snd_soc_add_controls(codec, amp_gain_controls,
+ ARRAY_SIZE(amp_gain_controls));
+ }
+}
+EXPORT_SYMBOL_GPL(simtec_audio_init);
+
+#define CODEC_CLOCK 12000000
+
+/**
+ * simtec_hw_params - update hardware parameters
+ * @substream: The audio substream instance.
+ * @params: The parameters requested.
+ *
+ * Update the codec data routing and configuration settings
+ * from the supplied data.
+ */
+static int simtec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ /* Set the CODEC as the bus clock master, I2S */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret) {
+ pr_err("%s: failed set cpu dai format\n", __func__);
+ return ret;
+ }
+
+ /* Set the CODEC as the bus clock master */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret) {
+ pr_err("%s: failed set codec dai format\n", __func__);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ CODEC_CLOCK, SND_SOC_CLOCK_IN);
+ if (ret) {
+ pr_err( "%s: failed setting codec sysclk\n", __func__);
+ return ret;
+ }
+
+ if (pdata->use_mpllin) {
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_MPLL,
+ 0, SND_SOC_CLOCK_OUT);
+
+ if (ret) {
+ pr_err("%s: failed to set MPLLin as clksrc\n",
+ __func__);
+ return ret;
+ }
+ }
+
+ if (pdata->output_cdclk) {
+ int cdclk_scale;
+
+ cdclk_scale = clk_get_rate(xtal_clk) / CODEC_CLOCK;
+ cdclk_scale--;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ cdclk_scale);
+ }
+
+ return 0;
+}
+
+static int simtec_call_startup(struct s3c24xx_audio_simtec_pdata *pd)
+{
+ /* call any board supplied startup code, this currently only
+ * covers the bast/vr1000 which have a CPLD in the way of the
+ * LRCLK */
+ if (pd->startup)
+ pd->startup();
+
+ return 0;
+}
+
+static struct snd_soc_ops simtec_snd_ops = {
+ .hw_params = simtec_hw_params,
+};
+
+/**
+ * attach_gpio_amp - get and configure the necessary gpios
+ * @dev: The device we're probing.
+ * @pd: The platform data supplied by the board.
+ *
+ * If there is a GPIO based amplifier attached to the board, claim
+ * the necessary GPIO lines for it, and set default values.
+ */
+static int attach_gpio_amp(struct device *dev,
+ struct s3c24xx_audio_simtec_pdata *pd)
+{
+ int ret;
+
+ /* attach gpio amp gain (if any) */
+ if (pdata->amp_gain[0] > 0) {
+ ret = gpio_request(pd->amp_gain[0], "gpio-amp-gain0");
+ if (ret) {
+ dev_err(dev, "cannot get amp gpio gain0\n");
+ return ret;
+ }
+
+ ret = gpio_request(pd->amp_gain[1], "gpio-amp-gain1");
+ if (ret) {
+ dev_err(dev, "cannot get amp gpio gain1\n");
+ gpio_free(pdata->amp_gain[0]);
+ return ret;
+ }
+
+ gpio_direction_output(pd->amp_gain[0], 0);
+ gpio_direction_output(pd->amp_gain[1], 0);
+ }
+
+ /* note, currently we assume GPA0 isn't valid amp */
+ if (pdata->amp_gpio > 0) {
+ ret = gpio_request(pd->amp_gpio, "gpio-amp");
+ if (ret) {
+ dev_err(dev, "cannot get amp gpio %d (%d)\n",
+ pd->amp_gpio, ret);
+ goto err_amp;
+ }
+
+ /* set the amp off at startup */
+ spk_unmute_state(0);
+ }
+
+ return 0;
+
+err_amp:
+ if (pd->amp_gain[0] > 0) {
+ gpio_free(pd->amp_gain[0]);
+ gpio_free(pd->amp_gain[1]);
+ }
+
+ return ret;
+}
+
+static void detach_gpio_amp(struct s3c24xx_audio_simtec_pdata *pd)
+{
+ if (pd->amp_gain[0] > 0) {
+ gpio_free(pd->amp_gain[0]);
+ gpio_free(pd->amp_gain[1]);
+ }
+
+ if (pd->amp_gpio > 0)
+ gpio_free(pd->amp_gpio);
+}
+
+#ifdef CONFIG_PM
+int simtec_audio_resume(struct device *dev)
+{
+ simtec_call_startup(pdata);
+ return 0;
+}
+
+const struct dev_pm_ops simtec_audio_pmops = {
+ .resume = simtec_audio_resume,
+};
+EXPORT_SYMBOL_GPL(simtec_audio_pmops);
+#endif
+
+int __devinit simtec_audio_core_probe(struct platform_device *pdev,
+ struct snd_soc_card *card)
+{
+ struct platform_device *snd_dev;
+ int ret;
+
+ card->dai_link->ops = &simtec_snd_ops;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data supplied\n");
+ return -EINVAL;
+ }
+
+ simtec_call_startup(pdata);
+
+ xtal_clk = clk_get(&pdev->dev, "xtal");
+ if (IS_ERR(xtal_clk)) {
+ dev_err(&pdev->dev, "could not get clkout0\n");
+ return -EINVAL;
+ }
+
+ dev_info(&pdev->dev, "xtal rate is %ld\n", clk_get_rate(xtal_clk));
+
+ ret = attach_gpio_amp(&pdev->dev, pdata);
+ if (ret)
+ goto err_clk;
+
+ snd_dev = platform_device_alloc("soc-audio", -1);
+ if (!snd_dev) {
+ dev_err(&pdev->dev, "failed to alloc soc-audio devicec\n");
+ ret = -ENOMEM;
+ goto err_gpio;
+ }
+
+ platform_set_drvdata(snd_dev, card);
+
+ ret = platform_device_add(snd_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add soc-audio dev\n");
+ goto err_pdev;
+ }
+
+ platform_set_drvdata(pdev, snd_dev);
+ return 0;
+
+err_pdev:
+ platform_device_put(snd_dev);
+
+err_gpio:
+ detach_gpio_amp(pdata);
+
+err_clk:
+ clk_put(xtal_clk);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(simtec_audio_core_probe);
+
+int __devexit simtec_audio_remove(struct platform_device *pdev)
+{
+ struct platform_device *snd_dev = platform_get_drvdata(pdev);
+
+ platform_device_unregister(snd_dev);
+
+ detach_gpio_amp(pdata);
+ clk_put(xtal_clk);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(simtec_audio_remove);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Simtec Audio common support");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c24xx_simtec.h b/sound/soc/samsung/s3c24xx_simtec.h
new file mode 100644
index 00000000..8270748a
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx_simtec.h
@@ -0,0 +1,22 @@
+/* sound/soc/samsung/s3c24xx_simtec.h
+ *
+ * Copyright 2009 Simtec Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+extern void simtec_audio_init(struct snd_soc_pcm_runtime *rtd);
+
+extern int simtec_audio_core_probe(struct platform_device *pdev,
+ struct snd_soc_card *card);
+
+extern int simtec_audio_remove(struct platform_device *pdev);
+
+#ifdef CONFIG_PM
+extern const struct dev_pm_ops simtec_audio_pmops;
+#define simtec_audio_pm &simtec_audio_pmops
+#else
+#define simtec_audio_pm NULL
+#endif
diff --git a/sound/soc/samsung/s3c24xx_simtec_hermes.c b/sound/soc/samsung/s3c24xx_simtec_hermes.c
new file mode 100644
index 00000000..ce6aef60
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx_simtec_hermes.c
@@ -0,0 +1,134 @@
+/* sound/soc/samsung/s3c24xx_simtec_hermes.c
+ *
+ * Copyright 2009 Simtec Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <sound/soc.h>
+
+#include "s3c24xx_simtec.h"
+
+static const struct snd_soc_dapm_widget dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("GSM Out", NULL),
+ SND_SOC_DAPM_LINE("GSM In", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+ SND_SOC_DAPM_LINE("ZV", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route base_map[] = {
+ /* Headphone connected to HP{L,R}OUT and HP{L,R}COM */
+
+ { "Headphone Jack", NULL, "HPLOUT" },
+ { "Headphone Jack", NULL, "HPLCOM" },
+ { "Headphone Jack", NULL, "HPROUT" },
+ { "Headphone Jack", NULL, "HPRCOM" },
+
+ /* ZV connected to Line1 */
+
+ { "LINE1L", NULL, "ZV" },
+ { "LINE1R", NULL, "ZV" },
+
+ /* Line In connected to Line2 */
+
+ { "LINE2L", NULL, "Line In" },
+ { "LINE2R", NULL, "Line In" },
+
+ /* Microphone connected to MIC3R and MIC_BIAS */
+
+ { "MIC3L", NULL, "Mic Jack" },
+
+ /* GSM connected to MONO_LOUT and MIC3L (in) */
+
+ { "GSM Out", NULL, "MONO_LOUT" },
+ { "MIC3L", NULL, "GSM In" },
+
+ /* Speaker is connected to LINEOUT{LN,LP,RN,RP}, however we are
+ * not using the DAPM to power it up and down as there it makes
+ * a click when powering up. */
+};
+
+/**
+ * simtec_hermes_init - initialise and add controls
+ * @codec; The codec instance to attach to.
+ *
+ * Attach our controls and configure the necessary codec
+ * mappings for our sound card instance.
+*/
+static int simtec_hermes_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, dapm_widgets,
+ ARRAY_SIZE(dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, base_map, ARRAY_SIZE(base_map));
+
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Line In");
+ snd_soc_dapm_enable_pin(dapm, "Line Out");
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+
+ simtec_audio_init(rtd);
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link simtec_dai_aic33 = {
+ .name = "tlv320aic33",
+ .stream_name = "TLV320AIC33",
+ .codec_name = "tlv320aic3x-codec.0-001a",
+ .cpu_dai_name = "s3c24xx-iis",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .platform_name = "samsung-audio",
+ .init = simtec_hermes_init,
+};
+
+/* simtec audio machine driver */
+static struct snd_soc_card snd_soc_machine_simtec_aic33 = {
+ .name = "Simtec-Hermes",
+ .dai_link = &simtec_dai_aic33,
+ .num_links = 1,
+};
+
+static int __devinit simtec_audio_hermes_probe(struct platform_device *pd)
+{
+ dev_info(&pd->dev, "probing....\n");
+ return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic33);
+}
+
+static struct platform_driver simtec_audio_hermes_platdrv = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "s3c24xx-simtec-hermes-snd",
+ .pm = simtec_audio_pm,
+ },
+ .probe = simtec_audio_hermes_probe,
+ .remove = __devexit_p(simtec_audio_remove),
+};
+
+MODULE_ALIAS("platform:s3c24xx-simtec-hermes-snd");
+
+static int __init simtec_hermes_modinit(void)
+{
+ return platform_driver_register(&simtec_audio_hermes_platdrv);
+}
+
+static void __exit simtec_hermes_modexit(void)
+{
+ platform_driver_unregister(&simtec_audio_hermes_platdrv);
+}
+
+module_init(simtec_hermes_modinit);
+module_exit(simtec_hermes_modexit);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Simtec Audio support");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
new file mode 100644
index 00000000..a7ef7db5
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
@@ -0,0 +1,122 @@
+/* sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
+ *
+ * Copyright 2009 Simtec Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <sound/soc.h>
+
+#include "s3c24xx_simtec.h"
+
+/* supported machines:
+ *
+ * Machine Connections AMP
+ * ------- ----------- ---
+ * BAST MIC, HPOUT, LOUT, LIN TPA2001D1 (HPOUTL,R) (gain hardwired)
+ * VR1000 HPOUT, LIN None
+ * VR2000 LIN, LOUT, MIC, HP LM4871 (HPOUTL,R)
+ * DePicture LIN, LOUT, MIC, HP LM4871 (HPOUTL,R)
+ * Anubis LIN, LOUT, MIC, HP TPA2001D1 (HPOUTL,R)
+ */
+
+static const struct snd_soc_dapm_widget dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route base_map[] = {
+ { "Headphone Jack", NULL, "LHPOUT"},
+ { "Headphone Jack", NULL, "RHPOUT"},
+
+ { "Line Out", NULL, "LOUT" },
+ { "Line Out", NULL, "ROUT" },
+
+ { "LLINEIN", NULL, "Line In"},
+ { "RLINEIN", NULL, "Line In"},
+
+ { "MICIN", NULL, "Mic Jack"},
+};
+
+/**
+ * simtec_tlv320aic23_init - initialise and add controls
+ * @codec; The codec instance to attach to.
+ *
+ * Attach our controls and configure the necessary codec
+ * mappings for our sound card instance.
+*/
+static int simtec_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, dapm_widgets,
+ ARRAY_SIZE(dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, base_map, ARRAY_SIZE(base_map));
+
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin(dapm, "Line In");
+ snd_soc_dapm_enable_pin(dapm, "Line Out");
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+
+ simtec_audio_init(rtd);
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link simtec_dai_aic23 = {
+ .name = "tlv320aic23",
+ .stream_name = "TLV320AIC23",
+ .codec_name = "tlv320aic3x-codec.0-001a",
+ .cpu_dai_name = "s3c24xx-iis",
+ .codec_dai_name = "tlv320aic3x-hifi",
+ .platform_name = "samsung-audio",
+ .init = simtec_tlv320aic23_init,
+};
+
+/* simtec audio machine driver */
+static struct snd_soc_card snd_soc_machine_simtec_aic23 = {
+ .name = "Simtec",
+ .dai_link = &simtec_dai_aic23,
+ .num_links = 1,
+};
+
+static int __devinit simtec_audio_tlv320aic23_probe(struct platform_device *pd)
+{
+ return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic23);
+}
+
+static struct platform_driver simtec_audio_tlv320aic23_platdrv = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "s3c24xx-simtec-tlv320aic23",
+ .pm = simtec_audio_pm,
+ },
+ .probe = simtec_audio_tlv320aic23_probe,
+ .remove = __devexit_p(simtec_audio_remove),
+};
+
+MODULE_ALIAS("platform:s3c24xx-simtec-tlv320aic23");
+
+static int __init simtec_tlv320aic23_modinit(void)
+{
+ return platform_driver_register(&simtec_audio_tlv320aic23_platdrv);
+}
+
+static void __exit simtec_tlv320aic23_modexit(void)
+{
+ platform_driver_unregister(&simtec_audio_tlv320aic23_platdrv);
+}
+
+module_init(simtec_tlv320aic23_modinit);
+module_exit(simtec_tlv320aic23_modexit);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Simtec Audio support");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c24xx_uda134x.c b/sound/soc/samsung/s3c24xx_uda134x.c
new file mode 100644
index 00000000..dc9d551f
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx_uda134x.c
@@ -0,0 +1,361 @@
+/*
+ * Modifications by Christian Pellegrin <chripell@evolware.org>
+ *
+ * s3c24xx_uda134x.c -- S3C24XX_UDA134X ALSA SoC Audio board driver
+ *
+ * Copyright 2007 Dension Audio Systems Ltd.
+ * Author: Zoltan Devai
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/gpio.h>
+
+#include <sound/soc.h>
+#include <sound/s3c24xx_uda134x.h>
+
+#include <plat/regs-iis.h>
+
+#include "s3c24xx-i2s.h"
+
+/* #define ENFORCE_RATES 1 */
+/*
+ Unfortunately the S3C24XX in master mode has a limited capacity of
+ generating the clock for the codec. If you define this only rates
+ that are really available will be enforced. But be careful, most
+ user level application just want the usual sampling frequencies (8,
+ 11.025, 22.050, 44.1 kHz) and anyway resampling is a costly
+ operation for embedded systems. So if you aren't very lucky or your
+ hardware engineer wasn't very forward-looking it's better to leave
+ this undefined. If you do so an approximate value for the requested
+ sampling rate in the range -/+ 5% will be chosen. If this in not
+ possible an error will be returned.
+*/
+
+static struct clk *xtal;
+static struct clk *pclk;
+/* this is need because we don't have a place where to keep the
+ * pointers to the clocks in each substream. We get the clocks only
+ * when we are actually using them so we don't block stuff like
+ * frequency change or oscillator power-off */
+static int clk_users;
+static DEFINE_MUTEX(clk_lock);
+
+static unsigned int rates[33 * 2];
+#ifdef ENFORCE_RATES
+static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+#endif
+
+static struct platform_device *s3c24xx_uda134x_snd_device;
+
+static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream)
+{
+ int ret = 0;
+#ifdef ENFORCE_RATES
+ struct snd_pcm_runtime *runtime = substream->runtime;
+#endif
+
+ mutex_lock(&clk_lock);
+ pr_debug("%s %d\n", __func__, clk_users);
+ if (clk_users == 0) {
+ xtal = clk_get(&s3c24xx_uda134x_snd_device->dev, "xtal");
+ if (!xtal) {
+ printk(KERN_ERR "%s cannot get xtal\n", __func__);
+ ret = -EBUSY;
+ } else {
+ pclk = clk_get(&s3c24xx_uda134x_snd_device->dev,
+ "pclk");
+ if (!pclk) {
+ printk(KERN_ERR "%s cannot get pclk\n",
+ __func__);
+ clk_put(xtal);
+ ret = -EBUSY;
+ }
+ }
+ if (!ret) {
+ int i, j;
+
+ for (i = 0; i < 2; i++) {
+ int fs = i ? 256 : 384;
+
+ rates[i*33] = clk_get_rate(xtal) / fs;
+ for (j = 1; j < 33; j++)
+ rates[i*33 + j] = clk_get_rate(pclk) /
+ (j * fs);
+ }
+ }
+ }
+ clk_users += 1;
+ mutex_unlock(&clk_lock);
+ if (!ret) {
+#ifdef ENFORCE_RATES
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_constraints_rates);
+ if (ret < 0)
+ printk(KERN_ERR "%s cannot set constraints\n",
+ __func__);
+#endif
+ }
+ return ret;
+}
+
+static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream)
+{
+ mutex_lock(&clk_lock);
+ pr_debug("%s %d\n", __func__, clk_users);
+ clk_users -= 1;
+ if (clk_users == 0) {
+ clk_put(xtal);
+ xtal = NULL;
+ clk_put(pclk);
+ pclk = NULL;
+ }
+ mutex_unlock(&clk_lock);
+}
+
+static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int clk = 0;
+ int ret = 0;
+ int clk_source, fs_mode;
+ unsigned long rate = params_rate(params);
+ long err, cerr;
+ unsigned int div;
+ int i, bi;
+
+ err = 999999;
+ bi = 0;
+ for (i = 0; i < 2*33; i++) {
+ cerr = rates[i] - rate;
+ if (cerr < 0)
+ cerr = -cerr;
+ if (cerr < err) {
+ err = cerr;
+ bi = i;
+ }
+ }
+ if (bi / 33 == 1)
+ fs_mode = S3C2410_IISMOD_256FS;
+ else
+ fs_mode = S3C2410_IISMOD_384FS;
+ if (bi % 33 == 0) {
+ clk_source = S3C24XX_CLKSRC_MPLL;
+ div = 1;
+ } else {
+ clk_source = S3C24XX_CLKSRC_PCLK;
+ div = bi % 33;
+ }
+ pr_debug("%s desired rate %lu, %d\n", __func__, rate, bi);
+
+ clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate;
+ pr_debug("%s will use: %s %s %d sysclk %d err %ld\n", __func__,
+ fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS",
+ clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK",
+ div, clk, err);
+
+ if ((err * 100 / rate) > 5) {
+ printk(KERN_ERR "S3C24XX_UDA134X: effective frequency "
+ "too different from desired (%ld%%)\n",
+ err * 100 / rate);
+ return -EINVAL;
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
+ S3C2410_IISMOD_32FS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ S3C24XX_PRESCALE(div, div));
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops s3c24xx_uda134x_ops = {
+ .startup = s3c24xx_uda134x_startup,
+ .shutdown = s3c24xx_uda134x_shutdown,
+ .hw_params = s3c24xx_uda134x_hw_params,
+};
+
+static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
+ .name = "UDA134X",
+ .stream_name = "UDA134X",
+ .codec_name = "uda134x-codec",
+ .codec_dai_name = "uda134x-hifi",
+ .cpu_dai_name = "s3c24xx-iis",
+ .ops = &s3c24xx_uda134x_ops,
+ .platform_name = "samsung-audio",
+};
+
+static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
+ .name = "S3C24XX_UDA134X",
+ .dai_link = &s3c24xx_uda134x_dai_link,
+ .num_links = 1,
+};
+
+static struct s3c24xx_uda134x_platform_data *s3c24xx_uda134x_l3_pins;
+
+static void setdat(int v)
+{
+ gpio_set_value(s3c24xx_uda134x_l3_pins->l3_data, v > 0);
+}
+
+static void setclk(int v)
+{
+ gpio_set_value(s3c24xx_uda134x_l3_pins->l3_clk, v > 0);
+}
+
+static void setmode(int v)
+{
+ gpio_set_value(s3c24xx_uda134x_l3_pins->l3_mode, v > 0);
+}
+
+/* FIXME - This must be codec platform data but in which board file ?? */
+static struct uda134x_platform_data s3c24xx_uda134x = {
+ .l3 = {
+ .setdat = setdat,
+ .setclk = setclk,
+ .setmode = setmode,
+ .data_hold = 1,
+ .data_setup = 1,
+ .clock_high = 1,
+ .mode_hold = 1,
+ .mode = 1,
+ .mode_setup = 1,
+ },
+};
+
+static int s3c24xx_uda134x_setup_pin(int pin, char *fun)
+{
+ if (gpio_request(pin, "s3c24xx_uda134x") < 0) {
+ printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
+ "l3 %s pin already in use", fun);
+ return -EBUSY;
+ }
+ gpio_direction_output(pin, 0);
+ return 0;
+}
+
+static int s3c24xx_uda134x_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver\n");
+
+ s3c24xx_uda134x_l3_pins = pdev->dev.platform_data;
+ if (s3c24xx_uda134x_l3_pins == NULL) {
+ printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
+ "unable to find platform data\n");
+ return -ENODEV;
+ }
+ s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power;
+ s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model;
+
+ if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,
+ "data") < 0)
+ return -EBUSY;
+ if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,
+ "clk") < 0) {
+ gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
+ return -EBUSY;
+ }
+ if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,
+ "mode") < 0) {
+ gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
+ gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
+ return -EBUSY;
+ }
+
+ s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!s3c24xx_uda134x_snd_device) {
+ printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
+ "Unable to register\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(s3c24xx_uda134x_snd_device,
+ &snd_soc_s3c24xx_uda134x);
+ platform_device_add_data(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x, sizeof(s3c24xx_uda134x));
+ ret = platform_device_add(s3c24xx_uda134x_snd_device);
+ if (ret) {
+ printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: Unable to add\n");
+ platform_device_put(s3c24xx_uda134x_snd_device);
+ }
+
+ return ret;
+}
+
+static int s3c24xx_uda134x_remove(struct platform_device *pdev)
+{
+ platform_device_unregister(s3c24xx_uda134x_snd_device);
+ gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
+ gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
+ gpio_free(s3c24xx_uda134x_l3_pins->l3_mode);
+ return 0;
+}
+
+static struct platform_driver s3c24xx_uda134x_driver = {
+ .probe = s3c24xx_uda134x_probe,
+ .remove = s3c24xx_uda134x_remove,
+ .driver = {
+ .name = "s3c24xx_uda134x",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s3c24xx_uda134x_init(void)
+{
+ return platform_driver_register(&s3c24xx_uda134x_driver);
+}
+
+static void __exit s3c24xx_uda134x_exit(void)
+{
+ platform_driver_unregister(&s3c24xx_uda134x_driver);
+}
+
+
+module_init(s3c24xx_uda134x_init);
+module_exit(s3c24xx_uda134x_exit);
+
+MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
+MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c
new file mode 100644
index 00000000..0a2c4f22
--- /dev/null
+++ b/sound/soc/samsung/smartq_wm8987.c
@@ -0,0 +1,284 @@
+/* sound/soc/samsung/smartq_wm8987.c
+ *
+ * Copyright 2010 Maurus Cuelenaere <mcuelenaere@gmail.com>
+ *
+ * Based on smdk6410_wm8987.c
+ * Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com
+ * Graeme Gregory - graeme.gregory@wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/gpio.h>
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+
+#include "i2s.h"
+#include "../codecs/wm8750.h"
+
+/*
+ * WM8987 is register compatible with WM8750, so using that as base driver.
+ */
+
+static struct snd_soc_card snd_soc_smartq;
+
+static int smartq_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int clk = 0;
+ int ret;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ clk = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* Use PCLK for I2S signal generation */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0,
+ 0, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* Gate the RCLK output on PAD */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
+ 0, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * SmartQ WM8987 HiFi DAI operations.
+ */
+static struct snd_soc_ops smartq_hifi_ops = {
+ .hw_params = smartq_hifi_hw_params,
+};
+
+static struct snd_soc_jack smartq_jack;
+
+static struct snd_soc_jack_pin smartq_jack_pins[] = {
+ /* Disable speaker when headphone is plugged in */
+ {
+ .pin = "Internal Speaker",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static struct snd_soc_jack_gpio smartq_jack_gpios[] = {
+ {
+ .gpio = S3C64XX_GPL(12),
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 200,
+ },
+};
+
+static const struct snd_kcontrol_new wm8987_smartq_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Internal Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Internal Mic"),
+};
+
+static int smartq_speaker_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k,
+ int event)
+{
+ gpio_set_value(S3C64XX_GPK(12), SND_SOC_DAPM_EVENT_OFF(event));
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Internal Speaker", smartq_speaker_event),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Internal Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Jack", NULL, "LOUT2"},
+ {"Headphone Jack", NULL, "ROUT2"},
+
+ {"Internal Speaker", NULL, "LOUT2"},
+ {"Internal Speaker", NULL, "ROUT2"},
+
+ {"Mic Bias", NULL, "Internal Mic"},
+ {"LINPUT2", NULL, "Mic Bias"},
+};
+
+static int smartq_wm8987_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int err = 0;
+
+ /* Add SmartQ specific widgets */
+ snd_soc_dapm_new_controls(dapm, wm8987_dapm_widgets,
+ ARRAY_SIZE(wm8987_dapm_widgets));
+
+ /* add SmartQ specific controls */
+ err = snd_soc_add_controls(codec, wm8987_smartq_controls,
+ ARRAY_SIZE(wm8987_smartq_controls));
+
+ if (err < 0)
+ return err;
+
+ /* setup SmartQ specific audio path */
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ /* set endpoints to not connected */
+ snd_soc_dapm_nc_pin(dapm, "LINPUT1");
+ snd_soc_dapm_nc_pin(dapm, "RINPUT1");
+ snd_soc_dapm_nc_pin(dapm, "OUT3");
+ snd_soc_dapm_nc_pin(dapm, "ROUT1");
+
+ /* set endpoints to default off mode */
+ snd_soc_dapm_enable_pin(dapm, "Internal Speaker");
+ snd_soc_dapm_enable_pin(dapm, "Internal Mic");
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+
+ err = snd_soc_dapm_sync(dapm);
+ if (err)
+ return err;
+
+ /* Headphone jack detection */
+ err = snd_soc_jack_new(codec, "Headphone Jack",
+ SND_JACK_HEADPHONE, &smartq_jack);
+ if (err)
+ return err;
+
+ err = snd_soc_jack_add_pins(&smartq_jack, ARRAY_SIZE(smartq_jack_pins),
+ smartq_jack_pins);
+ if (err)
+ return err;
+
+ err = snd_soc_jack_add_gpios(&smartq_jack,
+ ARRAY_SIZE(smartq_jack_gpios),
+ smartq_jack_gpios);
+
+ return err;
+}
+
+static struct snd_soc_dai_link smartq_dai[] = {
+ {
+ .name = "wm8987",
+ .stream_name = "SmartQ Hi-Fi",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8750-hifi",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8750-codec.0-0x1a",
+ .init = smartq_wm8987_init,
+ .ops = &smartq_hifi_ops,
+ },
+};
+
+static struct snd_soc_card snd_soc_smartq = {
+ .name = "SmartQ",
+ .dai_link = smartq_dai,
+ .num_links = ARRAY_SIZE(smartq_dai),
+};
+
+static struct platform_device *smartq_snd_device;
+
+static int __init smartq_init(void)
+{
+ int ret;
+
+ if (!machine_is_smartq7() && !machine_is_smartq5()) {
+ pr_info("Only SmartQ is supported by this ASoC driver\n");
+ return -ENODEV;
+ }
+
+ smartq_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!smartq_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(smartq_snd_device, &snd_soc_smartq);
+
+ ret = platform_device_add(smartq_snd_device);
+ if (ret) {
+ platform_device_put(smartq_snd_device);
+ return ret;
+ }
+
+ /* Initialise GPIOs used by amplifiers */
+ ret = gpio_request(S3C64XX_GPK(12), "amplifiers shutdown");
+ if (ret) {
+ dev_err(&smartq_snd_device->dev, "Failed to register GPK12\n");
+ goto err_unregister_device;
+ }
+
+ /* Disable amplifiers */
+ ret = gpio_direction_output(S3C64XX_GPK(12), 1);
+ if (ret) {
+ dev_err(&smartq_snd_device->dev, "Failed to configure GPK12\n");
+ goto err_free_gpio_amp_shut;
+ }
+
+ return 0;
+
+err_free_gpio_amp_shut:
+ gpio_free(S3C64XX_GPK(12));
+err_unregister_device:
+ platform_device_unregister(smartq_snd_device);
+
+ return ret;
+}
+
+static void __exit smartq_exit(void)
+{
+ gpio_free(S3C64XX_GPK(12));
+ snd_soc_jack_free_gpios(&smartq_jack, ARRAY_SIZE(smartq_jack_gpios),
+ smartq_jack_gpios);
+
+ platform_device_unregister(smartq_snd_device);
+}
+
+module_init(smartq_init);
+module_exit(smartq_exit);
+
+/* Module information */
+MODULE_AUTHOR("Maurus Cuelenaere <mcuelenaere@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC SmartQ WM8987");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smdk2443_wm9710.c b/sound/soc/samsung/smdk2443_wm9710.c
new file mode 100644
index 00000000..3a0dbfc7
--- /dev/null
+++ b/sound/soc/samsung/smdk2443_wm9710.c
@@ -0,0 +1,66 @@
+/*
+ * smdk2443_wm9710.c -- SoC audio for smdk2443
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <sound/soc.h>
+
+static struct snd_soc_card smdk2443;
+
+static struct snd_soc_dai_link smdk2443_dai[] = {
+{
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "samsung-ac97",
+ .codec_dai_name = "ac97-hifi",
+ .codec_name = "ac97-codec",
+ .platform_name = "samsung-audio",
+},
+};
+
+static struct snd_soc_card smdk2443 = {
+ .name = "SMDK2443",
+ .dai_link = smdk2443_dai,
+ .num_links = ARRAY_SIZE(smdk2443_dai),
+};
+
+static struct platform_device *smdk2443_snd_ac97_device;
+
+static int __init smdk2443_init(void)
+{
+ int ret;
+
+ smdk2443_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+ if (!smdk2443_snd_ac97_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(smdk2443_snd_ac97_device, &smdk2443);
+ ret = platform_device_add(smdk2443_snd_ac97_device);
+
+ if (ret)
+ platform_device_put(smdk2443_snd_ac97_device);
+
+ return ret;
+}
+
+static void __exit smdk2443_exit(void)
+{
+ platform_device_unregister(smdk2443_snd_ac97_device);
+}
+
+module_init(smdk2443_init);
+module_exit(smdk2443_exit);
+
+/* Module information */
+MODULE_AUTHOR("Graeme Gregory, graeme.gregory@wolfsonmicro.com, www.wolfsonmicro.com");
+MODULE_DESCRIPTION("ALSA SoC WM9710 SMDK2443");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c
new file mode 100644
index 00000000..e8ac961c
--- /dev/null
+++ b/sound/soc/samsung/smdk_spdif.c
@@ -0,0 +1,221 @@
+/*
+ * smdk_spdif.c -- S/PDIF audio for SMDK
+ *
+ * Copyright 2010 Samsung Electronics Co. Ltd.
+ *
+ * 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 of the
+ * License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/clk.h>
+
+#include <sound/soc.h>
+
+#include "spdif.h"
+
+/* Audio clock settings are belonged to board specific part. Every
+ * board can set audio source clock setting which is matched with H/W
+ * like this function-'set_audio_clock_heirachy'.
+ */
+static int set_audio_clock_heirachy(struct platform_device *pdev)
+{
+ struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif;
+ int ret = 0;
+
+ fout_epll = clk_get(NULL, "fout_epll");
+ if (IS_ERR(fout_epll)) {
+ printk(KERN_WARNING "%s: Cannot find fout_epll.\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ mout_epll = clk_get(NULL, "mout_epll");
+ if (IS_ERR(mout_epll)) {
+ printk(KERN_WARNING "%s: Cannot find mout_epll.\n",
+ __func__);
+ ret = -EINVAL;
+ goto out1;
+ }
+
+ sclk_audio0 = clk_get(&pdev->dev, "sclk_audio");
+ if (IS_ERR(sclk_audio0)) {
+ printk(KERN_WARNING "%s: Cannot find sclk_audio.\n",
+ __func__);
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ sclk_spdif = clk_get(NULL, "sclk_spdif");
+ if (IS_ERR(sclk_spdif)) {
+ printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n",
+ __func__);
+ ret = -EINVAL;
+ goto out3;
+ }
+
+ /* Set audio clock hierarchy for S/PDIF */
+ clk_set_parent(mout_epll, fout_epll);
+ clk_set_parent(sclk_audio0, mout_epll);
+ clk_set_parent(sclk_spdif, sclk_audio0);
+
+ clk_put(sclk_spdif);
+out3:
+ clk_put(sclk_audio0);
+out2:
+ clk_put(mout_epll);
+out1:
+ clk_put(fout_epll);
+
+ return ret;
+}
+
+/* We should haved to set clock directly on this part because of clock
+ * scheme of Samsudng SoCs did not support to set rates from abstrct
+ * clock of it's hierarchy.
+ */
+static int set_audio_clock_rate(unsigned long epll_rate,
+ unsigned long audio_rate)
+{
+ struct clk *fout_epll, *sclk_spdif;
+
+ fout_epll = clk_get(NULL, "fout_epll");
+ if (IS_ERR(fout_epll)) {
+ printk(KERN_ERR "%s: failed to get fout_epll\n", __func__);
+ return -ENOENT;
+ }
+
+ clk_set_rate(fout_epll, epll_rate);
+ clk_put(fout_epll);
+
+ sclk_spdif = clk_get(NULL, "sclk_spdif");
+ if (IS_ERR(sclk_spdif)) {
+ printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__);
+ return -ENOENT;
+ }
+
+ clk_set_rate(sclk_spdif, audio_rate);
+ clk_put(sclk_spdif);
+
+ return 0;
+}
+
+static int smdk_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned long pll_out, rclk_rate;
+ int ret, ratio;
+
+ switch (params_rate(params)) {
+ case 44100:
+ pll_out = 45158400;
+ break;
+ case 32000:
+ case 48000:
+ case 96000:
+ pll_out = 49152000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Setting ratio to 512fs helps to use S/PDIF with HDMI without
+ * modify S/PDIF ASoC machine driver.
+ */
+ ratio = 512;
+ rclk_rate = params_rate(params) * ratio;
+
+ /* Set audio source clock rates */
+ ret = set_audio_clock_rate(pll_out, rclk_rate);
+ if (ret < 0)
+ return ret;
+
+ /* Set S/PDIF uses internal source clock */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK,
+ rclk_rate, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+static struct snd_soc_ops smdk_spdif_ops = {
+ .hw_params = smdk_hw_params,
+};
+
+static struct snd_soc_dai_link smdk_dai = {
+ .name = "S/PDIF",
+ .stream_name = "S/PDIF PCM Playback",
+ .platform_name = "samsung-audio",
+ .cpu_dai_name = "samsung-spdif",
+ .codec_dai_name = "dit-hifi",
+ .codec_name = "spdif-dit",
+ .ops = &smdk_spdif_ops,
+};
+
+static struct snd_soc_card smdk = {
+ .name = "SMDK-S/PDIF",
+ .dai_link = &smdk_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *smdk_snd_spdif_dit_device;
+static struct platform_device *smdk_snd_spdif_device;
+
+static int __init smdk_init(void)
+{
+ int ret;
+
+ smdk_snd_spdif_dit_device = platform_device_alloc("spdif-dit", -1);
+ if (!smdk_snd_spdif_dit_device)
+ return -ENOMEM;
+
+ ret = platform_device_add(smdk_snd_spdif_dit_device);
+ if (ret)
+ goto err1;
+
+ smdk_snd_spdif_device = platform_device_alloc("soc-audio", -1);
+ if (!smdk_snd_spdif_device) {
+ ret = -ENOMEM;
+ goto err2;
+ }
+
+ platform_set_drvdata(smdk_snd_spdif_device, &smdk);
+
+ ret = platform_device_add(smdk_snd_spdif_device);
+ if (ret)
+ goto err3;
+
+ /* Set audio clock hierarchy manually */
+ ret = set_audio_clock_heirachy(smdk_snd_spdif_device);
+ if (ret)
+ goto err4;
+
+ return 0;
+err4:
+ platform_device_del(smdk_snd_spdif_device);
+err3:
+ platform_device_put(smdk_snd_spdif_device);
+err2:
+ platform_device_del(smdk_snd_spdif_dit_device);
+err1:
+ platform_device_put(smdk_snd_spdif_dit_device);
+ return ret;
+}
+
+static void __exit smdk_exit(void)
+{
+ platform_device_unregister(smdk_snd_spdif_device);
+ platform_device_unregister(smdk_snd_spdif_dit_device);
+}
+
+module_init(smdk_init);
+module_exit(smdk_exit);
+
+MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
+MODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smdk_wm8580.c b/sound/soc/samsung/smdk_wm8580.c
new file mode 100644
index 00000000..3d26f660
--- /dev/null
+++ b/sound/soc/samsung/smdk_wm8580.c
@@ -0,0 +1,287 @@
+/*
+ * smdk_wm8580.c
+ *
+ * Copyright (c) 2009 Samsung Electronics Co. Ltd
+ * Author: Jaswinder Singh <jassi.brar@samsung.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/wm8580.h"
+#include "i2s.h"
+
+/*
+ * Default CFG switch settings to use this driver:
+ *
+ * SMDK6410: Set CFG1 1-3 Off, CFG2 1-4 On
+ */
+
+/* SMDK has a 12MHZ crystal attached to WM8580 */
+#define SMDK_WM8580_FREQ 12000000
+
+static int smdk_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ unsigned int pll_out;
+ int bfs, rfs, ret;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_U8:
+ case SNDRV_PCM_FORMAT_S8:
+ bfs = 16;
+ break;
+ case SNDRV_PCM_FORMAT_U16_LE:
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bfs = 32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* The Fvco for WM8580 PLLs must fall within [90,100]MHz.
+ * This criterion can't be met if we request PLL output
+ * as {8000x256, 64000x256, 11025x256}Hz.
+ * As a wayout, we rather change rfs to a minimum value that
+ * results in (params_rate(params) * rfs), and itself, acceptable
+ * to both - the CODEC and the CPU.
+ */
+ switch (params_rate(params)) {
+ case 16000:
+ case 22050:
+ case 32000:
+ case 44100:
+ case 48000:
+ case 88200:
+ case 96000:
+ rfs = 256;
+ break;
+ case 64000:
+ rfs = 384;
+ break;
+ case 8000:
+ case 11025:
+ rfs = 512;
+ break;
+ default:
+ return -EINVAL;
+ }
+ pll_out = params_rate(params) * rfs;
+
+ /* Set the Codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* Set the AP DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* Set WM8580 to drive MCLK from its PLLA */
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK,
+ WM8580_CLKSRC_PLLA);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8580_PLLA, 0,
+ SMDK_WM8580_FREQ, pll_out);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_PLLA,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * SMDK WM8580 DAI operations.
+ */
+static struct snd_soc_ops smdk_ops = {
+ .hw_params = smdk_hw_params,
+};
+
+/* SMDK Playback widgets */
+static const struct snd_soc_dapm_widget wm8580_dapm_widgets_pbk[] = {
+ SND_SOC_DAPM_HP("Front", NULL),
+ SND_SOC_DAPM_HP("Center+Sub", NULL),
+ SND_SOC_DAPM_HP("Rear", NULL),
+};
+
+/* SMDK Capture widgets */
+static const struct snd_soc_dapm_widget wm8580_dapm_widgets_cpt[] = {
+ SND_SOC_DAPM_MIC("MicIn", NULL),
+ SND_SOC_DAPM_LINE("LineIn", NULL),
+};
+
+/* SMDK-PAIFTX connections */
+static const struct snd_soc_dapm_route audio_map_tx[] = {
+ /* MicIn feeds AINL */
+ {"AINL", NULL, "MicIn"},
+
+ /* LineIn feeds AINL/R */
+ {"AINL", NULL, "LineIn"},
+ {"AINR", NULL, "LineIn"},
+};
+
+/* SMDK-PAIFRX connections */
+static const struct snd_soc_dapm_route audio_map_rx[] = {
+ /* Front Left/Right are fed VOUT1L/R */
+ {"Front", NULL, "VOUT1L"},
+ {"Front", NULL, "VOUT1R"},
+
+ /* Center/Sub are fed VOUT2L/R */
+ {"Center+Sub", NULL, "VOUT2L"},
+ {"Center+Sub", NULL, "VOUT2R"},
+
+ /* Rear Left/Right are fed VOUT3L/R */
+ {"Rear", NULL, "VOUT3L"},
+ {"Rear", NULL, "VOUT3R"},
+};
+
+static int smdk_wm8580_init_paiftx(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* Add smdk specific Capture widgets */
+ snd_soc_dapm_new_controls(dapm, wm8580_dapm_widgets_cpt,
+ ARRAY_SIZE(wm8580_dapm_widgets_cpt));
+
+ /* Set up PAIFTX audio path */
+ snd_soc_dapm_add_routes(dapm, audio_map_tx, ARRAY_SIZE(audio_map_tx));
+
+ /* Enabling the microphone requires the fitting of a 0R
+ * resistor to connect the line from the microphone jack.
+ */
+ snd_soc_dapm_disable_pin(dapm, "MicIn");
+
+ /* signal a DAPM event */
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static int smdk_wm8580_init_paifrx(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* Add smdk specific Playback widgets */
+ snd_soc_dapm_new_controls(dapm, wm8580_dapm_widgets_pbk,
+ ARRAY_SIZE(wm8580_dapm_widgets_pbk));
+
+ /* Set up PAIFRX audio path */
+ snd_soc_dapm_add_routes(dapm, audio_map_rx, ARRAY_SIZE(audio_map_rx));
+
+ /* signal a DAPM event */
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+enum {
+ PRI_PLAYBACK = 0,
+ PRI_CAPTURE,
+ SEC_PLAYBACK,
+};
+
+static struct snd_soc_dai_link smdk_dai[] = {
+ [PRI_PLAYBACK] = { /* Primary Playback i/f */
+ .name = "WM8580 PAIF RX",
+ .stream_name = "Playback",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8580-hifi-playback",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8580-codec.0-001b",
+ .init = smdk_wm8580_init_paifrx,
+ .ops = &smdk_ops,
+ },
+ [PRI_CAPTURE] = { /* Primary Capture i/f */
+ .name = "WM8580 PAIF TX",
+ .stream_name = "Capture",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8580-hifi-capture",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8580-codec.0-001b",
+ .init = smdk_wm8580_init_paiftx,
+ .ops = &smdk_ops,
+ },
+ [SEC_PLAYBACK] = { /* Sec_Fifo Playback i/f */
+ .name = "Sec_FIFO TX",
+ .stream_name = "Playback",
+ .cpu_dai_name = "samsung-i2s.x",
+ .codec_dai_name = "wm8580-hifi-playback",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8580-codec.0-001b",
+ .init = smdk_wm8580_init_paifrx,
+ .ops = &smdk_ops,
+ },
+};
+
+static struct snd_soc_card smdk = {
+ .name = "SMDK-I2S",
+ .dai_link = smdk_dai,
+ .num_links = 2,
+};
+
+static struct platform_device *smdk_snd_device;
+
+static int __init smdk_audio_init(void)
+{
+ int ret;
+ char *str;
+
+ if (machine_is_smdkc100()
+ || machine_is_smdkv210() || machine_is_smdkc110()) {
+ smdk.num_links = 3;
+ /* Secondary is at offset SAMSUNG_I2S_SECOFF from Primary */
+ str = (char *)smdk_dai[SEC_PLAYBACK].cpu_dai_name;
+ str[strlen(str) - 1] = '0' + SAMSUNG_I2S_SECOFF;
+ } else if (machine_is_smdk6410()) {
+ str = (char *)smdk_dai[PRI_PLAYBACK].cpu_dai_name;
+ str[strlen(str) - 1] = '2';
+ str = (char *)smdk_dai[PRI_CAPTURE].cpu_dai_name;
+ str[strlen(str) - 1] = '2';
+ }
+
+ smdk_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!smdk_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(smdk_snd_device, &smdk);
+ ret = platform_device_add(smdk_snd_device);
+
+ if (ret)
+ platform_device_put(smdk_snd_device);
+
+ return ret;
+}
+module_init(smdk_audio_init);
+
+static void __exit smdk_audio_exit(void)
+{
+ platform_device_unregister(smdk_snd_device);
+}
+module_exit(smdk_audio_exit);
+
+MODULE_AUTHOR("Jaswinder Singh, jassi.brar@samsung.com");
+MODULE_DESCRIPTION("ALSA SoC SMDK WM8580");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smdk_wm8580pcm.c b/sound/soc/samsung/smdk_wm8580pcm.c
new file mode 100644
index 00000000..0d12092d
--- /dev/null
+++ b/sound/soc/samsung/smdk_wm8580pcm.c
@@ -0,0 +1,206 @@
+/*
+ * sound/soc/samsung/smdk_wm8580pcm.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co. Ltd
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/wm8580.h"
+#include "dma.h"
+#include "pcm.h"
+
+/*
+ * Board Settings:
+ * o '1' means 'ON'
+ * o '0' means 'OFF'
+ * o 'X' means 'Don't care'
+ *
+ * SMDK6410, SMDK6440, SMDK6450 Base B/D: CFG1-0000, CFG2-1111
+ * SMDKC110, SMDKV210: CFGB11-100100, CFGB12-0000
+ */
+
+#define SMDK_WM8580_EXT_OSC 12000000
+#define SMDK_WM8580_EXT_MCLK 4096000
+#define SMDK_WM8580_EXT_VOICE 2048000
+
+static unsigned long mclk_freq;
+static unsigned long xtal_freq;
+
+/*
+ * If MCLK clock directly gets from XTAL, we don't have to use PLL
+ * to make MCLK, but if XTAL clock source connects with other codec
+ * pin (like XTI), we should have to set codec's PLL to make MCLK.
+ * Because Samsung SoC does not support pcmcdclk output like I2S.
+ */
+
+static int smdk_wm8580_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int rfs, ret;
+
+ switch (params_rate(params)) {
+ case 8000:
+ break;
+ default:
+ printk(KERN_ERR "%s:%d Sampling Rate %u not supported!\n",
+ __func__, __LINE__, params_rate(params));
+ return -EINVAL;
+ }
+
+ rfs = mclk_freq / params_rate(params) / 2;
+
+ /* Set the codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B
+ | SND_SOC_DAIFMT_IB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* Set the cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_B
+ | SND_SOC_DAIFMT_IB_NF
+ | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ if (mclk_freq == xtal_freq) {
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_MCLK,
+ mclk_freq, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK,
+ WM8580_CLKSRC_MCLK);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_PLLA,
+ mclk_freq, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK,
+ WM8580_CLKSRC_PLLA);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8580_PLLA, 0,
+ xtal_freq, mclk_freq);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Set PCM source clock on CPU */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_PCM_CLKSRC_MUX,
+ mclk_freq, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* Set SCLK_DIV for making bclk */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_PCM_SCLK_PER_FS, rfs);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops smdk_wm8580_pcm_ops = {
+ .hw_params = smdk_wm8580_pcm_hw_params,
+};
+
+static struct snd_soc_dai_link smdk_dai[] = {
+ {
+ .name = "WM8580 PAIF PCM RX",
+ .stream_name = "Playback",
+ .cpu_dai_name = "samsung-pcm.0",
+ .codec_dai_name = "wm8580-hifi-playback",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8580-codec.0-001b",
+ .ops = &smdk_wm8580_pcm_ops,
+ }, {
+ .name = "WM8580 PAIF PCM TX",
+ .stream_name = "Capture",
+ .cpu_dai_name = "samsung-pcm.0",
+ .codec_dai_name = "wm8580-hifi-capture",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8580-codec.0-001b",
+ .ops = &smdk_wm8580_pcm_ops,
+ },
+};
+
+static struct snd_soc_card smdk_pcm = {
+ .name = "SMDK-PCM",
+ .dai_link = smdk_dai,
+ .num_links = 2,
+};
+
+/*
+ * After SMDKC110 Base Board's Rev is '0.1', 12MHz External OSC(X1)
+ * is absent (or not connected), so we connect EXT_VOICE_CLK(OSC4),
+ * 2.0484Mhz, directly with MCLK both Codec and SoC.
+ */
+static int __devinit snd_smdk_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ xtal_freq = SMDK_WM8580_EXT_OSC;
+ mclk_freq = SMDK_WM8580_EXT_MCLK;
+
+ if (machine_is_smdkc110() || machine_is_smdkv210())
+ xtal_freq = mclk_freq = SMDK_WM8580_EXT_VOICE;
+
+ smdk_pcm.dev = &pdev->dev;
+ ret = snd_soc_register_card(&smdk_pcm);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit snd_smdk_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_card(&smdk_pcm);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver snd_smdk_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "samsung-smdk-pcm",
+ },
+ .probe = snd_smdk_probe,
+ .remove = __devexit_p(snd_smdk_remove),
+};
+
+static int __init smdk_audio_init(void)
+{
+ return platform_driver_register(&snd_smdk_driver);
+}
+
+module_init(smdk_audio_init);
+
+static void __exit smdk_audio_exit(void)
+{
+ platform_driver_unregister(&snd_smdk_driver);
+}
+
+module_exit(smdk_audio_exit);
+
+MODULE_AUTHOR("Sangbeom Kim, <sbkim73@samsung.com>");
+MODULE_DESCRIPTION("ALSA SoC SMDK WM8580 for PCM");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smdk_wm8994.c b/sound/soc/samsung/smdk_wm8994.c
new file mode 100644
index 00000000..e7c1009a
--- /dev/null
+++ b/sound/soc/samsung/smdk_wm8994.c
@@ -0,0 +1,176 @@
+/*
+ * smdk_wm8994.c
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include "../codecs/wm8994.h"
+
+ /*
+ * Default CFG switch settings to use this driver:
+ * SMDKV310: CFG5-1000, CFG7-111111
+ */
+
+ /*
+ * Configure audio route as :-
+ * $ amixer sset 'DAC1' on,on
+ * $ amixer sset 'Right Headphone Mux' 'DAC'
+ * $ amixer sset 'Left Headphone Mux' 'DAC'
+ * $ amixer sset 'DAC1R Mixer AIF1.1' on
+ * $ amixer sset 'DAC1L Mixer AIF1.1' on
+ * $ amixer sset 'IN2L' on
+ * $ amixer sset 'IN2L PGA IN2LN' on
+ * $ amixer sset 'MIXINL IN2L' on
+ * $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on
+ * $ amixer sset 'IN2R' on
+ * $ amixer sset 'IN2R PGA IN2RN' on
+ * $ amixer sset 'MIXINR IN2R' on
+ * $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on
+ */
+
+/* SMDK has a 16.934MHZ crystal attached to WM8994 */
+#define SMDK_WM8994_FREQ 16934000
+
+static int smdk_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ unsigned int pll_out;
+ int ret;
+
+ /* AIF1CLK should be >=3MHz for optimal performance */
+ if (params_rate(params) == 8000 || params_rate(params) == 11025)
+ pll_out = params_rate(params) * 512;
+ else
+ pll_out = params_rate(params) * 256;
+
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
+ SMDK_WM8994_FREQ, pll_out);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * SMDK WM8994 DAI operations.
+ */
+static struct snd_soc_ops smdk_ops = {
+ .hw_params = smdk_hw_params,
+};
+
+static int smdk_wm8994_init_paiftx(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* HeadPhone */
+ snd_soc_dapm_enable_pin(dapm, "HPOUT1R");
+ snd_soc_dapm_enable_pin(dapm, "HPOUT1L");
+
+ /* MicIn */
+ snd_soc_dapm_enable_pin(dapm, "IN1LN");
+ snd_soc_dapm_enable_pin(dapm, "IN1RN");
+
+ /* LineIn */
+ snd_soc_dapm_enable_pin(dapm, "IN2LN");
+ snd_soc_dapm_enable_pin(dapm, "IN2RN");
+
+ /* Other pins NC */
+ snd_soc_dapm_nc_pin(dapm, "HPOUT2P");
+ snd_soc_dapm_nc_pin(dapm, "HPOUT2N");
+ snd_soc_dapm_nc_pin(dapm, "SPKOUTLN");
+ snd_soc_dapm_nc_pin(dapm, "SPKOUTLP");
+ snd_soc_dapm_nc_pin(dapm, "SPKOUTRP");
+ snd_soc_dapm_nc_pin(dapm, "SPKOUTRN");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT1N");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT1P");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT2N");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT2P");
+ snd_soc_dapm_nc_pin(dapm, "IN1LP");
+ snd_soc_dapm_nc_pin(dapm, "IN2LP:VXRN");
+ snd_soc_dapm_nc_pin(dapm, "IN1RP");
+ snd_soc_dapm_nc_pin(dapm, "IN2RP:VXRP");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link smdk_dai[] = {
+ { /* Primary DAI i/f */
+ .name = "WM8994 AIF1",
+ .stream_name = "Pri_Dai",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8994-aif1",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8994-codec",
+ .init = smdk_wm8994_init_paiftx,
+ .ops = &smdk_ops,
+ }, { /* Sec_Fifo Playback i/f */
+ .name = "Sec_FIFO TX",
+ .stream_name = "Sec_Dai",
+ .cpu_dai_name = "samsung-i2s.4",
+ .codec_dai_name = "wm8994-aif1",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8994-codec",
+ .ops = &smdk_ops,
+ },
+};
+
+static struct snd_soc_card smdk = {
+ .name = "SMDK-I2S",
+ .dai_link = smdk_dai,
+ .num_links = ARRAY_SIZE(smdk_dai),
+};
+
+static struct platform_device *smdk_snd_device;
+
+static int __init smdk_audio_init(void)
+{
+ int ret;
+
+ smdk_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!smdk_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(smdk_snd_device, &smdk);
+
+ ret = platform_device_add(smdk_snd_device);
+ if (ret)
+ platform_device_put(smdk_snd_device);
+
+ return ret;
+}
+module_init(smdk_audio_init);
+
+static void __exit smdk_audio_exit(void)
+{
+ platform_device_unregister(smdk_snd_device);
+}
+module_exit(smdk_audio_exit);
+
+MODULE_DESCRIPTION("ALSA SoC SMDK WM8994");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smdk_wm9713.c b/sound/soc/samsung/smdk_wm9713.c
new file mode 100644
index 00000000..fffe3c1d
--- /dev/null
+++ b/sound/soc/samsung/smdk_wm9713.c
@@ -0,0 +1,106 @@
+/*
+ * smdk_wm9713.c -- SoC audio for SMDK
+ *
+ * Copyright 2010 Samsung Electronics Co. Ltd.
+ * Author: Jaswinder Singh Brar <jassi.brar@samsung.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 of the
+ * License, or (at your option) any later version.
+ *
+ */
+
+#include <sound/soc.h>
+
+static struct snd_soc_card smdk;
+
+/*
+ * Default CFG switch settings to use this driver:
+ *
+ * SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off
+ * SMDKC100: Set CFG6 1-3 On, CFG7 1 On
+ * SMDKC110: Set CFGB10 1-2 Off, CFGB12 1-3 On
+ * SMDKV210: Set CFGB10 1-2 Off, CFGB12 1-3 On
+ * SMDKV310: Set CFG2 1-2 Off, CFG4 All On, CFG7 All Off, CFG8 1-On
+ */
+
+/*
+ Playback (HeadPhone):-
+ $ amixer sset 'Headphone' unmute
+ $ amixer sset 'Right Headphone Out Mux' 'Headphone'
+ $ amixer sset 'Left Headphone Out Mux' 'Headphone'
+ $ amixer sset 'Right HP Mixer PCM' unmute
+ $ amixer sset 'Left HP Mixer PCM' unmute
+
+ Capture (LineIn):-
+ $ amixer sset 'Right Capture Source' 'Line'
+ $ amixer sset 'Left Capture Source' 'Line'
+*/
+
+static struct snd_soc_dai_link smdk_dai = {
+ .name = "AC97",
+ .stream_name = "AC97 PCM",
+ .platform_name = "samsung-audio",
+ .cpu_dai_name = "samsung-ac97",
+ .codec_dai_name = "wm9713-hifi",
+ .codec_name = "wm9713-codec",
+};
+
+static struct snd_soc_card smdk = {
+ .name = "SMDK WM9713",
+ .dai_link = &smdk_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *smdk_snd_wm9713_device;
+static struct platform_device *smdk_snd_ac97_device;
+
+static int __init smdk_init(void)
+{
+ int ret;
+
+ smdk_snd_wm9713_device = platform_device_alloc("wm9713-codec", -1);
+ if (!smdk_snd_wm9713_device)
+ return -ENOMEM;
+
+ ret = platform_device_add(smdk_snd_wm9713_device);
+ if (ret)
+ goto err1;
+
+ smdk_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+ if (!smdk_snd_ac97_device) {
+ ret = -ENOMEM;
+ goto err2;
+ }
+
+ platform_set_drvdata(smdk_snd_ac97_device, &smdk);
+
+ ret = platform_device_add(smdk_snd_ac97_device);
+ if (ret)
+ goto err3;
+
+ return 0;
+
+err3:
+ platform_device_put(smdk_snd_ac97_device);
+err2:
+ platform_device_del(smdk_snd_wm9713_device);
+err1:
+ platform_device_put(smdk_snd_wm9713_device);
+ return ret;
+}
+
+static void __exit smdk_exit(void)
+{
+ platform_device_unregister(smdk_snd_ac97_device);
+ platform_device_unregister(smdk_snd_wm9713_device);
+}
+
+module_init(smdk_init);
+module_exit(smdk_exit);
+
+/* Module information */
+MODULE_AUTHOR("Jaswinder Singh Brar, jassi.brar@samsung.com");
+MODULE_DESCRIPTION("ALSA SoC SMDK+WM9713");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c
new file mode 100644
index 00000000..28c491da
--- /dev/null
+++ b/sound/soc/samsung/spdif.c
@@ -0,0 +1,500 @@
+/* sound/soc/samsung/spdif.c
+ *
+ * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * http://www.samsung.com/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <plat/audio.h>
+#include <mach/dma.h>
+
+#include "dma.h"
+#include "spdif.h"
+
+/* Registers */
+#define CLKCON 0x00
+#define CON 0x04
+#define BSTAS 0x08
+#define CSTAS 0x0C
+#define DATA_OUTBUF 0x10
+#define DCNT 0x14
+#define BSTAS_S 0x18
+#define DCNT_S 0x1C
+
+#define CLKCTL_MASK 0x7
+#define CLKCTL_MCLK_EXT (0x1 << 2)
+#define CLKCTL_PWR_ON (0x1 << 0)
+
+#define CON_MASK 0x3ffffff
+#define CON_FIFO_TH_SHIFT 19
+#define CON_FIFO_TH_MASK (0x7 << 19)
+#define CON_USERDATA_23RDBIT (0x1 << 12)
+
+#define CON_SW_RESET (0x1 << 5)
+
+#define CON_MCLKDIV_MASK (0x3 << 3)
+#define CON_MCLKDIV_256FS (0x0 << 3)
+#define CON_MCLKDIV_384FS (0x1 << 3)
+#define CON_MCLKDIV_512FS (0x2 << 3)
+
+#define CON_PCM_MASK (0x3 << 1)
+#define CON_PCM_16BIT (0x0 << 1)
+#define CON_PCM_20BIT (0x1 << 1)
+#define CON_PCM_24BIT (0x2 << 1)
+
+#define CON_PCM_DATA (0x1 << 0)
+
+#define CSTAS_MASK 0x3fffffff
+#define CSTAS_SAMP_FREQ_MASK (0xF << 24)
+#define CSTAS_SAMP_FREQ_44 (0x0 << 24)
+#define CSTAS_SAMP_FREQ_48 (0x2 << 24)
+#define CSTAS_SAMP_FREQ_32 (0x3 << 24)
+#define CSTAS_SAMP_FREQ_96 (0xA << 24)
+
+#define CSTAS_CATEGORY_MASK (0xFF << 8)
+#define CSTAS_CATEGORY_CODE_CDP (0x01 << 8)
+
+#define CSTAS_NO_COPYRIGHT (0x1 << 2)
+
+/**
+ * struct samsung_spdif_info - Samsung S/PDIF Controller information
+ * @lock: Spin lock for S/PDIF.
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device register block.
+ * @clk_rate: Current clock rate for calcurate ratio.
+ * @pclk: The peri-clock pointer for spdif master operation.
+ * @sclk: The source clock pointer for making sync signals.
+ * @save_clkcon: Backup clkcon reg. in suspend.
+ * @save_con: Backup con reg. in suspend.
+ * @save_cstas: Backup cstas reg. in suspend.
+ * @dma_playback: DMA information for playback channel.
+ */
+struct samsung_spdif_info {
+ spinlock_t lock;
+ struct device *dev;
+ void __iomem *regs;
+ unsigned long clk_rate;
+ struct clk *pclk;
+ struct clk *sclk;
+ u32 saved_clkcon;
+ u32 saved_con;
+ u32 saved_cstas;
+ struct s3c_dma_params *dma_playback;
+};
+
+static struct s3c2410_dma_client spdif_dma_client_out = {
+ .name = "S/PDIF Stereo out",
+};
+
+static struct s3c_dma_params spdif_stereo_out;
+static struct samsung_spdif_info spdif_info;
+
+static inline struct samsung_spdif_info *to_info(struct snd_soc_dai *cpu_dai)
+{
+ return snd_soc_dai_get_drvdata(cpu_dai);
+}
+
+static void spdif_snd_txctrl(struct samsung_spdif_info *spdif, int on)
+{
+ void __iomem *regs = spdif->regs;
+ u32 clkcon;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
+ if (on)
+ writel(clkcon | CLKCTL_PWR_ON, regs + CLKCON);
+ else
+ writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
+}
+
+static int spdif_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct samsung_spdif_info *spdif = to_info(cpu_dai);
+ u32 clkcon;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ clkcon = readl(spdif->regs + CLKCON);
+
+ if (clk_id == SND_SOC_SPDIF_INT_MCLK)
+ clkcon &= ~CLKCTL_MCLK_EXT;
+ else
+ clkcon |= CLKCTL_MCLK_EXT;
+
+ writel(clkcon, spdif->regs + CLKCON);
+
+ spdif->clk_rate = freq;
+
+ return 0;
+}
+
+static int spdif_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai);
+ unsigned long flags;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&spdif->lock, flags);
+ spdif_snd_txctrl(spdif, 1);
+ spin_unlock_irqrestore(&spdif->lock, flags);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&spdif->lock, flags);
+ spdif_snd_txctrl(spdif, 0);
+ spin_unlock_irqrestore(&spdif->lock, flags);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int spdif_sysclk_ratios[] = {
+ 512, 384, 256,
+};
+
+static int spdif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *socdai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai);
+ void __iomem *regs = spdif->regs;
+ struct s3c_dma_params *dma_data;
+ u32 con, clkcon, cstas;
+ unsigned long flags;
+ int i, ratio;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = spdif->dma_playback;
+ else {
+ dev_err(spdif->dev, "Capture is not supported\n");
+ return -EINVAL;
+ }
+
+ snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);
+
+ spin_lock_irqsave(&spdif->lock, flags);
+
+ con = readl(regs + CON) & CON_MASK;
+ cstas = readl(regs + CSTAS) & CSTAS_MASK;
+ clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
+
+ con &= ~CON_FIFO_TH_MASK;
+ con |= (0x7 << CON_FIFO_TH_SHIFT);
+ con |= CON_USERDATA_23RDBIT;
+ con |= CON_PCM_DATA;
+
+ con &= ~CON_PCM_MASK;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ con |= CON_PCM_16BIT;
+ break;
+ default:
+ dev_err(spdif->dev, "Unsupported data size.\n");
+ goto err;
+ }
+
+ ratio = spdif->clk_rate / params_rate(params);
+ for (i = 0; i < ARRAY_SIZE(spdif_sysclk_ratios); i++)
+ if (ratio == spdif_sysclk_ratios[i])
+ break;
+ if (i == ARRAY_SIZE(spdif_sysclk_ratios)) {
+ dev_err(spdif->dev, "Invalid clock ratio %ld/%d\n",
+ spdif->clk_rate, params_rate(params));
+ goto err;
+ }
+
+ con &= ~CON_MCLKDIV_MASK;
+ switch (ratio) {
+ case 256:
+ con |= CON_MCLKDIV_256FS;
+ break;
+ case 384:
+ con |= CON_MCLKDIV_384FS;
+ break;
+ case 512:
+ con |= CON_MCLKDIV_512FS;
+ break;
+ }
+
+ cstas &= ~CSTAS_SAMP_FREQ_MASK;
+ switch (params_rate(params)) {
+ case 44100:
+ cstas |= CSTAS_SAMP_FREQ_44;
+ break;
+ case 48000:
+ cstas |= CSTAS_SAMP_FREQ_48;
+ break;
+ case 32000:
+ cstas |= CSTAS_SAMP_FREQ_32;
+ break;
+ case 96000:
+ cstas |= CSTAS_SAMP_FREQ_96;
+ break;
+ default:
+ dev_err(spdif->dev, "Invalid sampling rate %d\n",
+ params_rate(params));
+ goto err;
+ }
+
+ cstas &= ~CSTAS_CATEGORY_MASK;
+ cstas |= CSTAS_CATEGORY_CODE_CDP;
+ cstas |= CSTAS_NO_COPYRIGHT;
+
+ writel(con, regs + CON);
+ writel(cstas, regs + CSTAS);
+ writel(clkcon, regs + CLKCON);
+
+ spin_unlock_irqrestore(&spdif->lock, flags);
+
+ return 0;
+err:
+ spin_unlock_irqrestore(&spdif->lock, flags);
+ return -EINVAL;
+}
+
+static void spdif_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai);
+ void __iomem *regs = spdif->regs;
+ u32 con, clkcon;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ con = readl(regs + CON) & CON_MASK;
+ clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
+
+ writel(con | CON_SW_RESET, regs + CON);
+ cpu_relax();
+
+ writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
+}
+
+#ifdef CONFIG_PM
+static int spdif_suspend(struct snd_soc_dai *cpu_dai)
+{
+ struct samsung_spdif_info *spdif = to_info(cpu_dai);
+ u32 con = spdif->saved_con;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ spdif->saved_clkcon = readl(spdif->regs + CLKCON) & CLKCTL_MASK;
+ spdif->saved_con = readl(spdif->regs + CON) & CON_MASK;
+ spdif->saved_cstas = readl(spdif->regs + CSTAS) & CSTAS_MASK;
+
+ writel(con | CON_SW_RESET, spdif->regs + CON);
+ cpu_relax();
+
+ return 0;
+}
+
+static int spdif_resume(struct snd_soc_dai *cpu_dai)
+{
+ struct samsung_spdif_info *spdif = to_info(cpu_dai);
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ writel(spdif->saved_clkcon, spdif->regs + CLKCON);
+ writel(spdif->saved_con, spdif->regs + CON);
+ writel(spdif->saved_cstas, spdif->regs + CSTAS);
+
+ return 0;
+}
+#else
+#define spdif_suspend NULL
+#define spdif_resume NULL
+#endif
+
+static struct snd_soc_dai_ops spdif_dai_ops = {
+ .set_sysclk = spdif_set_sysclk,
+ .trigger = spdif_trigger,
+ .hw_params = spdif_hw_params,
+ .shutdown = spdif_shutdown,
+};
+
+struct snd_soc_dai_driver samsung_spdif_dai = {
+ .name = "samsung-spdif",
+ .playback = {
+ .stream_name = "S/PDIF Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = (SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .ops = &spdif_dai_ops,
+ .suspend = spdif_suspend,
+ .resume = spdif_resume,
+};
+
+static __devinit int spdif_probe(struct platform_device *pdev)
+{
+ struct s3c_audio_pdata *spdif_pdata;
+ struct resource *mem_res, *dma_res;
+ struct samsung_spdif_info *spdif;
+ int ret;
+
+ spdif_pdata = pdev->dev.platform_data;
+
+ dev_dbg(&pdev->dev, "Entered %s\n", __func__);
+
+ dma_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!dma_res) {
+ dev_err(&pdev->dev, "Unable to get dma resource.\n");
+ return -ENXIO;
+ }
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem_res) {
+ dev_err(&pdev->dev, "Unable to get register resource.\n");
+ return -ENXIO;
+ }
+
+ if (spdif_pdata && spdif_pdata->cfg_gpio
+ && spdif_pdata->cfg_gpio(pdev)) {
+ dev_err(&pdev->dev, "Unable to configure GPIO pins\n");
+ return -EINVAL;
+ }
+
+ spdif = &spdif_info;
+ spdif->dev = &pdev->dev;
+
+ spin_lock_init(&spdif->lock);
+
+ spdif->pclk = clk_get(&pdev->dev, "spdif");
+ if (IS_ERR(spdif->pclk)) {
+ dev_err(&pdev->dev, "failed to get peri-clock\n");
+ ret = -ENOENT;
+ goto err0;
+ }
+ clk_enable(spdif->pclk);
+
+ spdif->sclk = clk_get(&pdev->dev, "sclk_spdif");
+ if (IS_ERR(spdif->sclk)) {
+ dev_err(&pdev->dev, "failed to get internal source clock\n");
+ ret = -ENOENT;
+ goto err1;
+ }
+ clk_enable(spdif->sclk);
+
+ /* Request S/PDIF Register's memory region */
+ if (!request_mem_region(mem_res->start,
+ resource_size(mem_res), "samsung-spdif")) {
+ dev_err(&pdev->dev, "Unable to request register region\n");
+ ret = -EBUSY;
+ goto err2;
+ }
+
+ spdif->regs = ioremap(mem_res->start, 0x100);
+ if (spdif->regs == NULL) {
+ dev_err(&pdev->dev, "Cannot ioremap registers\n");
+ ret = -ENXIO;
+ goto err3;
+ }
+
+ dev_set_drvdata(&pdev->dev, spdif);
+
+ ret = snd_soc_register_dai(&pdev->dev, &samsung_spdif_dai);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "fail to register dai\n");
+ goto err4;
+ }
+
+ spdif_stereo_out.dma_size = 2;
+ spdif_stereo_out.client = &spdif_dma_client_out;
+ spdif_stereo_out.dma_addr = mem_res->start + DATA_OUTBUF;
+ spdif_stereo_out.channel = dma_res->start;
+
+ spdif->dma_playback = &spdif_stereo_out;
+
+ return 0;
+
+err4:
+ iounmap(spdif->regs);
+err3:
+ release_mem_region(mem_res->start, resource_size(mem_res));
+err2:
+ clk_disable(spdif->sclk);
+ clk_put(spdif->sclk);
+err1:
+ clk_disable(spdif->pclk);
+ clk_put(spdif->pclk);
+err0:
+ return ret;
+}
+
+static __devexit int spdif_remove(struct platform_device *pdev)
+{
+ struct samsung_spdif_info *spdif = &spdif_info;
+ struct resource *mem_res;
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ iounmap(spdif->regs);
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (mem_res)
+ release_mem_region(mem_res->start, resource_size(mem_res));
+
+ clk_disable(spdif->sclk);
+ clk_put(spdif->sclk);
+ clk_disable(spdif->pclk);
+ clk_put(spdif->pclk);
+
+ return 0;
+}
+
+static struct platform_driver samsung_spdif_driver = {
+ .probe = spdif_probe,
+ .remove = spdif_remove,
+ .driver = {
+ .name = "samsung-spdif",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init spdif_init(void)
+{
+ return platform_driver_register(&samsung_spdif_driver);
+}
+module_init(spdif_init);
+
+static void __exit spdif_exit(void)
+{
+ platform_driver_unregister(&samsung_spdif_driver);
+}
+module_exit(spdif_exit);
+
+MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
+MODULE_DESCRIPTION("Samsung S/PDIF Controller Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:samsung-spdif");
diff --git a/sound/soc/samsung/spdif.h b/sound/soc/samsung/spdif.h
new file mode 100644
index 00000000..4f72cb44
--- /dev/null
+++ b/sound/soc/samsung/spdif.h
@@ -0,0 +1,19 @@
+/* sound/soc/samsung/spdif.h
+ *
+ * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * http://www.samsung.com/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __SND_SOC_SAMSUNG_SPDIF_H
+#define __SND_SOC_SAMSUNG_SPDIF_H __FILE__
+
+#define SND_SOC_SPDIF_INT_MCLK 0
+#define SND_SOC_SPDIF_EXT_MCLK 1
+
+#endif /* __SND_SOC_SAMSUNG_SPDIF_H */
diff --git a/sound/soc/samsung/speyside.c b/sound/soc/samsung/speyside.c
new file mode 100644
index 00000000..360a333c
--- /dev/null
+++ b/sound/soc/samsung/speyside.c
@@ -0,0 +1,332 @@
+/*
+ * Speyside audio support
+ *
+ * Copyright 2011 Wolfson Microelectronics
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <linux/gpio.h>
+
+#include "../codecs/wm8915.h"
+#include "../codecs/wm9081.h"
+
+#define WM8915_HPSEL_GPIO 214
+
+static int speyside_set_bias_level(struct snd_soc_card *card,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8915_SYSCLK_MCLK1,
+ 32768, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8915_FLL_MCLK1,
+ 0, 0, 0);
+ if (ret < 0) {
+ pr_err("Failed to stop FLL\n");
+ return ret;
+ }
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int speyside_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int ret;
+
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0, WM8915_FLL_MCLK1,
+ 32768, 256 * 48000);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8915_SYSCLK_FLL,
+ 256 * 48000, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops speyside_ops = {
+ .hw_params = speyside_hw_params,
+};
+
+static struct snd_soc_jack speyside_headset;
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin speyside_headset_pins[] = {
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+/* Default the headphone selection to active high */
+static int speyside_jack_polarity;
+
+static int speyside_get_micbias(struct snd_soc_dapm_widget *source,
+ struct snd_soc_dapm_widget *sink)
+{
+ if (speyside_jack_polarity && (strcmp(source->name, "MICB1") == 0))
+ return 1;
+ if (!speyside_jack_polarity && (strcmp(source->name, "MICB2") == 0))
+ return 1;
+
+ return 0;
+}
+
+static void speyside_set_polarity(struct snd_soc_codec *codec,
+ int polarity)
+{
+ speyside_jack_polarity = !polarity;
+ gpio_direction_output(WM8915_HPSEL_GPIO, speyside_jack_polarity);
+
+ /* Re-run DAPM to make sure we're using the correct mic bias */
+ snd_soc_dapm_sync(&codec->dapm);
+}
+
+static int speyside_wm8915_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *dai = rtd->codec_dai;
+ struct snd_soc_codec *codec = rtd->codec;
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(dai, WM8915_SYSCLK_MCLK1, 32768, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = gpio_request(WM8915_HPSEL_GPIO, "HP_SEL");
+ if (ret != 0)
+ pr_err("Failed to request HP_SEL GPIO: %d\n", ret);
+ gpio_direction_output(WM8915_HPSEL_GPIO, speyside_jack_polarity);
+
+ ret = snd_soc_jack_new(codec, "Headset",
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ &speyside_headset);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_pins(&speyside_headset,
+ ARRAY_SIZE(speyside_headset_pins),
+ speyside_headset_pins);
+ if (ret)
+ return ret;
+
+ wm8915_detect(codec, &speyside_headset, speyside_set_polarity);
+
+ return 0;
+}
+
+static int speyside_late_probe(struct snd_soc_card *card)
+{
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Headset Mic");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Main AMIC");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Main DMIC");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Output");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Input");
+
+ return 0;
+}
+
+static struct snd_soc_dai_link speyside_dai[] = {
+ {
+ .name = "CPU",
+ .stream_name = "CPU",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "wm8915-aif1",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8915.1-001a",
+ .init = speyside_wm8915_init,
+ .ops = &speyside_ops,
+ },
+ {
+ .name = "Baseband",
+ .stream_name = "Baseband",
+ .cpu_dai_name = "wm8915-aif2",
+ .codec_dai_name = "wm1250-ev1",
+ .codec_name = "wm1250-ev1.1-0027",
+ .ops = &speyside_ops,
+ .ignore_suspend = 1,
+ },
+};
+
+static int speyside_wm9081_init(struct snd_soc_dapm_context *dapm)
+{
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT");
+
+ /* At any time the WM9081 is active it will have this clock */
+ return snd_soc_codec_set_sysclk(dapm->codec, WM9081_SYSCLK_MCLK,
+ 48000 * 256, 0);
+}
+
+static struct snd_soc_aux_dev speyside_aux_dev[] = {
+ {
+ .name = "wm9081",
+ .codec_name = "wm9081.1-006c",
+ .init = speyside_wm9081_init,
+ },
+};
+
+static struct snd_soc_codec_conf speyside_codec_conf[] = {
+ {
+ .dev_name = "wm9081.1-006c",
+ .name_prefix = "Sub",
+ },
+};
+
+static const struct snd_kcontrol_new controls[] = {
+ SOC_DAPM_PIN_SWITCH("Main Speaker"),
+ SOC_DAPM_PIN_SWITCH("Main DMIC"),
+ SOC_DAPM_PIN_SWITCH("Main AMIC"),
+ SOC_DAPM_PIN_SWITCH("WM1250 Input"),
+ SOC_DAPM_PIN_SWITCH("WM1250 Output"),
+};
+
+static struct snd_soc_dapm_widget widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+
+ SND_SOC_DAPM_SPK("Main Speaker", NULL),
+
+ SND_SOC_DAPM_MIC("Main AMIC", NULL),
+ SND_SOC_DAPM_MIC("Main DMIC", NULL),
+};
+
+static struct snd_soc_dapm_route audio_paths[] = {
+ { "IN1RN", NULL, "MICB1" },
+ { "IN1RP", NULL, "MICB1" },
+ { "IN1RN", NULL, "MICB2" },
+ { "IN1RP", NULL, "MICB2" },
+ { "MICB1", NULL, "Headset Mic", speyside_get_micbias },
+ { "MICB2", NULL, "Headset Mic", speyside_get_micbias },
+
+ { "IN1LP", NULL, "MICB2" },
+ { "IN1RN", NULL, "MICB1" },
+ { "MICB2", NULL, "Main AMIC" },
+
+ { "DMIC1DAT", NULL, "MICB1" },
+ { "DMIC2DAT", NULL, "MICB1" },
+ { "MICB1", NULL, "Main DMIC" },
+
+ { "Headphone", NULL, "HPOUT1L" },
+ { "Headphone", NULL, "HPOUT1R" },
+
+ { "Sub IN1", NULL, "HPOUT2L" },
+ { "Sub IN2", NULL, "HPOUT2R" },
+
+ { "Main Speaker", NULL, "Sub SPKN" },
+ { "Main Speaker", NULL, "Sub SPKP" },
+ { "Main Speaker", NULL, "SPKDAT" },
+};
+
+static struct snd_soc_card speyside = {
+ .name = "Speyside",
+ .dai_link = speyside_dai,
+ .num_links = ARRAY_SIZE(speyside_dai),
+ .aux_dev = speyside_aux_dev,
+ .num_aux_devs = ARRAY_SIZE(speyside_aux_dev),
+ .codec_conf = speyside_codec_conf,
+ .num_configs = ARRAY_SIZE(speyside_codec_conf),
+
+ .set_bias_level = speyside_set_bias_level,
+
+ .controls = controls,
+ .num_controls = ARRAY_SIZE(controls),
+ .dapm_widgets = widgets,
+ .num_dapm_widgets = ARRAY_SIZE(widgets),
+ .dapm_routes = audio_paths,
+ .num_dapm_routes = ARRAY_SIZE(audio_paths),
+
+ .late_probe = speyside_late_probe,
+};
+
+static __devinit int speyside_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &speyside;
+ int ret;
+
+ card->dev = &pdev->dev;
+
+ ret = snd_soc_register_card(card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit speyside_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_card(card);
+
+ return 0;
+}
+
+static struct platform_driver speyside_driver = {
+ .driver = {
+ .name = "speyside",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = speyside_probe,
+ .remove = __devexit_p(speyside_remove),
+};
+
+static int __init speyside_audio_init(void)
+{
+ return platform_driver_register(&speyside_driver);
+}
+module_init(speyside_audio_init);
+
+static void __exit speyside_audio_exit(void)
+{
+ platform_driver_unregister(&speyside_driver);
+}
+module_exit(speyside_audio_exit);
+
+MODULE_DESCRIPTION("Speyside audio support");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:speyside");
diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
new file mode 100644
index 00000000..d8e06a60
--- /dev/null
+++ b/sound/soc/sh/Kconfig
@@ -0,0 +1,80 @@
+menu "SoC Audio support for SuperH"
+ depends on SUPERH || ARCH_SHMOBILE
+
+config SND_SOC_PCM_SH7760
+ tristate "SoC Audio support for Renesas SH7760"
+ depends on CPU_SUBTYPE_SH7760 && SH_DMABRG
+ help
+ Enable this option for SH7760 AC97/I2S audio support.
+
+
+##
+## Audio unit modules
+##
+
+config SND_SOC_SH4_HAC
+ tristate
+ select AC97_BUS
+ select SND_SOC_AC97_BUS
+
+config SND_SOC_SH4_SSI
+ tristate
+
+config SND_SOC_SH4_FSI
+ tristate "SH4 FSI support"
+ help
+ This option enables FSI sound support
+
+config SND_SOC_SH4_SIU
+ tristate
+ depends on (SUPERH || ARCH_SHMOBILE) && HAVE_CLK
+ select DMA_ENGINE
+ select DMADEVICES
+ select SH_DMAE
+ select FW_LOADER
+
+##
+## Boards
+##
+
+config SND_SH7760_AC97
+ tristate "SH7760 AC97 sound support"
+ depends on CPU_SUBTYPE_SH7760 && SND_SOC_PCM_SH7760
+ select SND_SOC_SH4_HAC
+ select SND_SOC_AC97_CODEC
+ help
+ This option enables generic sound support for the first
+ AC97 unit of the SH7760.
+
+config SND_FSI_AK4642
+ tristate "FSI-AK4642 sound support"
+ depends on SND_SOC_SH4_FSI && I2C
+ select SND_SOC_AK4642
+ help
+ This option enables generic sound support for the
+ FSI - AK4642 unit
+
+config SND_FSI_DA7210
+ tristate "FSI-DA7210 sound support"
+ depends on SND_SOC_SH4_FSI && I2C
+ select SND_SOC_DA7210
+ help
+ This option enables generic sound support for the
+ FSI - DA7210 unit
+
+config SND_FSI_HDMI
+ tristate "FSI-HDMI sound support"
+ depends on SND_SOC_SH4_FSI && FB_SH_MOBILE_HDMI
+ help
+ This option enables generic sound support for the
+ FSI - HDMI unit
+
+config SND_SIU_MIGOR
+ tristate "SIU sound support on Migo-R"
+ depends on SH_MIGOR
+ select SND_SOC_SH4_SIU
+ select SND_SOC_WM8978
+ help
+ This option enables sound support for the SH7722 Migo-R board
+
+endmenu
diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile
new file mode 100644
index 00000000..94476d4c
--- /dev/null
+++ b/sound/soc/sh/Makefile
@@ -0,0 +1,26 @@
+## DMA engines
+snd-soc-dma-sh7760-objs := dma-sh7760.o
+obj-$(CONFIG_SND_SOC_PCM_SH7760) += snd-soc-dma-sh7760.o
+
+## audio units found on some SH-4
+snd-soc-hac-objs := hac.o
+snd-soc-ssi-objs := ssi.o
+snd-soc-fsi-objs := fsi.o
+snd-soc-siu-objs := siu_pcm.o siu_dai.o
+obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o
+obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o
+obj-$(CONFIG_SND_SOC_SH4_FSI) += snd-soc-fsi.o
+obj-$(CONFIG_SND_SOC_SH4_SIU) += snd-soc-siu.o
+
+## boards
+snd-soc-sh7760-ac97-objs := sh7760-ac97.o
+snd-soc-fsi-ak4642-objs := fsi-ak4642.o
+snd-soc-fsi-da7210-objs := fsi-da7210.o
+snd-soc-fsi-hdmi-objs := fsi-hdmi.o
+snd-soc-migor-objs := migor.o
+
+obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o
+obj-$(CONFIG_SND_FSI_AK4642) += snd-soc-fsi-ak4642.o
+obj-$(CONFIG_SND_FSI_DA7210) += snd-soc-fsi-da7210.o
+obj-$(CONFIG_SND_FSI_HDMI) += snd-soc-fsi-hdmi.o
+obj-$(CONFIG_SND_SIU_MIGOR) += snd-soc-migor.o
diff --git a/sound/soc/sh/dma-sh7760.c b/sound/soc/sh/dma-sh7760.c
new file mode 100644
index 00000000..c326d299
--- /dev/null
+++ b/sound/soc/sh/dma-sh7760.c
@@ -0,0 +1,386 @@
+/*
+ * SH7760 ("camelot") DMABRG audio DMA unit support
+ *
+ * Copyright (C) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ * licensed under the terms outlined in the file COPYING at the root
+ * of the linux kernel sources.
+ *
+ * The SH7760 DMABRG provides 4 dma channels (2x rec, 2x play), which
+ * trigger an interrupt when one half of the programmed transfer size
+ * has been xmitted.
+ *
+ * FIXME: little-endian only for now
+ */
+
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <asm/dmabrg.h>
+
+
+/* registers and bits */
+#define BRGATXSAR 0x00
+#define BRGARXDAR 0x04
+#define BRGATXTCR 0x08
+#define BRGARXTCR 0x0C
+#define BRGACR 0x10
+#define BRGATXTCNT 0x14
+#define BRGARXTCNT 0x18
+
+#define ACR_RAR (1 << 18)
+#define ACR_RDS (1 << 17)
+#define ACR_RDE (1 << 16)
+#define ACR_TAR (1 << 2)
+#define ACR_TDS (1 << 1)
+#define ACR_TDE (1 << 0)
+
+/* receiver/transmitter data alignment */
+#define ACR_RAM_NONE (0 << 24)
+#define ACR_RAM_4BYTE (1 << 24)
+#define ACR_RAM_2WORD (2 << 24)
+#define ACR_TAM_NONE (0 << 8)
+#define ACR_TAM_4BYTE (1 << 8)
+#define ACR_TAM_2WORD (2 << 8)
+
+
+struct camelot_pcm {
+ unsigned long mmio; /* DMABRG audio channel control reg MMIO */
+ unsigned int txid; /* ID of first DMABRG IRQ for this unit */
+
+ struct snd_pcm_substream *tx_ss;
+ unsigned long tx_period_size;
+ unsigned int tx_period;
+
+ struct snd_pcm_substream *rx_ss;
+ unsigned long rx_period_size;
+ unsigned int rx_period;
+
+} cam_pcm_data[2] = {
+ {
+ .mmio = 0xFE3C0040,
+ .txid = DMABRGIRQ_A0TXF,
+ },
+ {
+ .mmio = 0xFE3C0060,
+ .txid = DMABRGIRQ_A1TXF,
+ },
+};
+
+#define BRGREG(x) (*(unsigned long *)(cam->mmio + (x)))
+
+/*
+ * set a minimum of 16kb per period, to avoid interrupt-"storm" and
+ * resulting skipping. In general, the bigger the minimum size, the
+ * better for overall system performance. (The SH7760 is a puny CPU
+ * with a slow SDRAM interface and poor internal bus bandwidth,
+ * *especially* when the LCDC is active). The minimum for the DMAC
+ * is 8 bytes; 16kbytes are enough to get skip-free playback of a
+ * 44kHz/16bit/stereo MP3 on a lightly loaded system, and maintain
+ * reasonable responsiveness in MPlayer.
+ */
+#define DMABRG_PERIOD_MIN 16 * 1024
+#define DMABRG_PERIOD_MAX 0x03fffffc
+#define DMABRG_PREALLOC_BUFFER 32 * 1024
+#define DMABRG_PREALLOC_BUFFER_MAX 32 * 1024
+
+/* support everything the SSI supports */
+#define DMABRG_RATES \
+ SNDRV_PCM_RATE_8000_192000
+
+#define DMABRG_FMTS \
+ (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE)
+
+static struct snd_pcm_hardware camelot_pcm_hardware = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH),
+ .formats = DMABRG_FMTS,
+ .rates = DMABRG_RATES,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 2,
+ .channels_max = 8, /* max of the SSI */
+ .buffer_bytes_max = DMABRG_PERIOD_MAX,
+ .period_bytes_min = DMABRG_PERIOD_MIN,
+ .period_bytes_max = DMABRG_PERIOD_MAX / 2,
+ .periods_min = 2,
+ .periods_max = 2,
+ .fifo_size = 128,
+};
+
+static void camelot_txdma(void *data)
+{
+ struct camelot_pcm *cam = data;
+ cam->tx_period ^= 1;
+ snd_pcm_period_elapsed(cam->tx_ss);
+}
+
+static void camelot_rxdma(void *data)
+{
+ struct camelot_pcm *cam = data;
+ cam->rx_period ^= 1;
+ snd_pcm_period_elapsed(cam->rx_ss);
+}
+
+static int camelot_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+ int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+ int ret, dmairq;
+
+ snd_soc_set_runtime_hwparams(substream, &camelot_pcm_hardware);
+
+ /* DMABRG buffer half/full events */
+ dmairq = (recv) ? cam->txid + 2 : cam->txid;
+ if (recv) {
+ cam->rx_ss = substream;
+ ret = dmabrg_request_irq(dmairq, camelot_rxdma, cam);
+ if (unlikely(ret)) {
+ pr_debug("audio unit %d irqs already taken!\n",
+ rtd->cpu_dai->id);
+ return -EBUSY;
+ }
+ (void)dmabrg_request_irq(dmairq + 1,camelot_rxdma, cam);
+ } else {
+ cam->tx_ss = substream;
+ ret = dmabrg_request_irq(dmairq, camelot_txdma, cam);
+ if (unlikely(ret)) {
+ pr_debug("audio unit %d irqs already taken!\n",
+ rtd->cpu_dai->id);
+ return -EBUSY;
+ }
+ (void)dmabrg_request_irq(dmairq + 1, camelot_txdma, cam);
+ }
+ return 0;
+}
+
+static int camelot_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+ int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+ int dmairq;
+
+ dmairq = (recv) ? cam->txid + 2 : cam->txid;
+
+ if (recv)
+ cam->rx_ss = NULL;
+ else
+ cam->tx_ss = NULL;
+
+ dmabrg_free_irq(dmairq + 1);
+ dmabrg_free_irq(dmairq);
+
+ return 0;
+}
+
+static int camelot_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+ int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+ int ret;
+
+ ret = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ if (ret < 0)
+ return ret;
+
+ if (recv) {
+ cam->rx_period_size = params_period_bytes(hw_params);
+ cam->rx_period = 0;
+ } else {
+ cam->tx_period_size = params_period_bytes(hw_params);
+ cam->tx_period = 0;
+ }
+ return 0;
+}
+
+static int camelot_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int camelot_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+
+ pr_debug("PCM data: addr 0x%08ulx len %d\n",
+ (u32)runtime->dma_addr, runtime->dma_bytes);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ BRGREG(BRGATXSAR) = (unsigned long)runtime->dma_area;
+ BRGREG(BRGATXTCR) = runtime->dma_bytes;
+ } else {
+ BRGREG(BRGARXDAR) = (unsigned long)runtime->dma_area;
+ BRGREG(BRGARXTCR) = runtime->dma_bytes;
+ }
+
+ return 0;
+}
+
+static inline void dmabrg_play_dma_start(struct camelot_pcm *cam)
+{
+ unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+ /* start DMABRG engine: XFER start, auto-addr-reload */
+ BRGREG(BRGACR) = acr | ACR_TDE | ACR_TAR | ACR_TAM_2WORD;
+}
+
+static inline void dmabrg_play_dma_stop(struct camelot_pcm *cam)
+{
+ unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+ /* forcibly terminate data transmission */
+ BRGREG(BRGACR) = acr | ACR_TDS;
+}
+
+static inline void dmabrg_rec_dma_start(struct camelot_pcm *cam)
+{
+ unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+ /* start DMABRG engine: recv start, auto-reload */
+ BRGREG(BRGACR) = acr | ACR_RDE | ACR_RAR | ACR_RAM_2WORD;
+}
+
+static inline void dmabrg_rec_dma_stop(struct camelot_pcm *cam)
+{
+ unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+ /* forcibly terminate data receiver */
+ BRGREG(BRGACR) = acr | ACR_RDS;
+}
+
+static int camelot_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+ int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (recv)
+ dmabrg_rec_dma_start(cam);
+ else
+ dmabrg_play_dma_start(cam);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (recv)
+ dmabrg_rec_dma_stop(cam);
+ else
+ dmabrg_play_dma_stop(cam);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t camelot_pos(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+ int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+ unsigned long pos;
+
+ /* cannot use the DMABRG pointer register: under load, by the
+ * time ALSA comes around to read the register, it is already
+ * far ahead (or worse, already done with the fragment) of the
+ * position at the time the IRQ was triggered, which results in
+ * fast-playback sound in my test application (ScummVM)
+ */
+ if (recv)
+ pos = cam->rx_period ? cam->rx_period_size : 0;
+ else
+ pos = cam->tx_period ? cam->tx_period_size : 0;
+
+ return bytes_to_frames(runtime, pos);
+}
+
+static struct snd_pcm_ops camelot_pcm_ops = {
+ .open = camelot_pcm_open,
+ .close = camelot_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = camelot_hw_params,
+ .hw_free = camelot_hw_free,
+ .prepare = camelot_prepare,
+ .trigger = camelot_trigger,
+ .pointer = camelot_pos,
+};
+
+static void camelot_pcm_free(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int camelot_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ /* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel
+ * in MMAP mode (i.e. aplay -M)
+ */
+ snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ DMABRG_PREALLOC_BUFFER, DMABRG_PREALLOC_BUFFER_MAX);
+
+ return 0;
+}
+
+static struct snd_soc_platform sh7760_soc_platform = {
+ .pcm_ops = &camelot_pcm_ops,
+ .pcm_new = camelot_pcm_new,
+ .pcm_free = camelot_pcm_free,
+};
+
+static int __devinit sh7760_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &sh7760_soc_platform);
+}
+
+static int __devexit sh7760_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver sh7760_pcm_driver = {
+ .driver = {
+ .name = "sh7760-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = sh7760_soc_platform_probe,
+ .remove = __devexit_p(sh7760_soc_platform_remove),
+};
+
+static int __init snd_sh7760_pcm_init(void)
+{
+ return platform_driver_register(&sh7760_pcm_driver);
+}
+module_init(snd_sh7760_pcm_init);
+
+static void __exit snd_sh7760_pcm_exit(void)
+{
+ platform_driver_unregister(&sh7760_pcm_driver);
+}
+module_exit(snd_sh7760_pcm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SH7760 Audio DMA (DMABRG) driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/sh/fsi-ak4642.c b/sound/soc/sh/fsi-ak4642.c
new file mode 100644
index 00000000..770a71a1
--- /dev/null
+++ b/sound/soc/sh/fsi-ak4642.c
@@ -0,0 +1,209 @@
+/*
+ * FSI-AK464x sound support for ms7724se
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/platform_device.h>
+#include <sound/sh_fsi.h>
+
+struct fsi_ak4642_data {
+ const char *name;
+ const char *card;
+ const char *cpu_dai;
+ const char *codec;
+ const char *platform;
+ int id;
+};
+
+static int fsi_ak4642_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *codec = rtd->codec_dai;
+ struct snd_soc_dai *cpu = rtd->cpu_dai;
+ int ret;
+
+ ret = snd_soc_dai_set_fmt(codec, SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec, 0, 11289600, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_CBS_CFS);
+
+ return ret;
+}
+
+static struct snd_soc_dai_link fsi_dai_link = {
+ .codec_dai_name = "ak4642-hifi",
+ .init = fsi_ak4642_dai_init,
+};
+
+static struct snd_soc_card fsi_soc_card = {
+ .dai_link = &fsi_dai_link,
+ .num_links = 1,
+};
+
+static struct platform_device *fsi_snd_device;
+
+static int fsi_ak4642_probe(struct platform_device *pdev)
+{
+ int ret = -ENOMEM;
+ const struct platform_device_id *id_entry;
+ struct fsi_ak4642_data *pdata;
+
+ id_entry = pdev->id_entry;
+ if (!id_entry) {
+ dev_err(&pdev->dev, "unknown fsi ak4642\n");
+ return -ENODEV;
+ }
+
+ pdata = (struct fsi_ak4642_data *)id_entry->driver_data;
+
+ fsi_snd_device = platform_device_alloc("soc-audio", pdata->id);
+ if (!fsi_snd_device)
+ goto out;
+
+ fsi_dai_link.name = pdata->name;
+ fsi_dai_link.stream_name = pdata->name;
+ fsi_dai_link.cpu_dai_name = pdata->cpu_dai;
+ fsi_dai_link.platform_name = pdata->platform;
+ fsi_dai_link.codec_name = pdata->codec;
+ fsi_soc_card.name = pdata->card;
+
+ platform_set_drvdata(fsi_snd_device, &fsi_soc_card);
+ ret = platform_device_add(fsi_snd_device);
+
+ if (ret)
+ platform_device_put(fsi_snd_device);
+
+out:
+ return ret;
+}
+
+static int fsi_ak4642_remove(struct platform_device *pdev)
+{
+ platform_device_unregister(fsi_snd_device);
+ return 0;
+}
+
+static struct fsi_ak4642_data fsi_a_ak4642 = {
+ .name = "AK4642",
+ .card = "FSIA-AK4642",
+ .cpu_dai = "fsia-dai",
+ .codec = "ak4642-codec.0-0012",
+ .platform = "sh_fsi.0",
+ .id = FSI_PORT_A,
+};
+
+static struct fsi_ak4642_data fsi_b_ak4642 = {
+ .name = "AK4642",
+ .card = "FSIB-AK4642",
+ .cpu_dai = "fsib-dai",
+ .codec = "ak4642-codec.0-0012",
+ .platform = "sh_fsi.0",
+ .id = FSI_PORT_B,
+};
+
+static struct fsi_ak4642_data fsi_a_ak4643 = {
+ .name = "AK4643",
+ .card = "FSIA-AK4643",
+ .cpu_dai = "fsia-dai",
+ .codec = "ak4642-codec.0-0013",
+ .platform = "sh_fsi.0",
+ .id = FSI_PORT_A,
+};
+
+static struct fsi_ak4642_data fsi_b_ak4643 = {
+ .name = "AK4643",
+ .card = "FSIB-AK4643",
+ .cpu_dai = "fsib-dai",
+ .codec = "ak4642-codec.0-0013",
+ .platform = "sh_fsi.0",
+ .id = FSI_PORT_B,
+};
+
+static struct fsi_ak4642_data fsi2_a_ak4642 = {
+ .name = "AK4642",
+ .card = "FSI2A-AK4642",
+ .cpu_dai = "fsia-dai",
+ .codec = "ak4642-codec.0-0012",
+ .platform = "sh_fsi2",
+ .id = FSI_PORT_A,
+};
+
+static struct fsi_ak4642_data fsi2_b_ak4642 = {
+ .name = "AK4642",
+ .card = "FSI2B-AK4642",
+ .cpu_dai = "fsib-dai",
+ .codec = "ak4642-codec.0-0012",
+ .platform = "sh_fsi2",
+ .id = FSI_PORT_B,
+};
+
+static struct fsi_ak4642_data fsi2_a_ak4643 = {
+ .name = "AK4643",
+ .card = "FSI2A-AK4643",
+ .cpu_dai = "fsia-dai",
+ .codec = "ak4642-codec.0-0013",
+ .platform = "sh_fsi2",
+ .id = FSI_PORT_A,
+};
+
+static struct fsi_ak4642_data fsi2_b_ak4643 = {
+ .name = "AK4643",
+ .card = "FSI2B-AK4643",
+ .cpu_dai = "fsib-dai",
+ .codec = "ak4642-codec.0-0013",
+ .platform = "sh_fsi2",
+ .id = FSI_PORT_B,
+};
+
+static struct platform_device_id fsi_id_table[] = {
+ /* FSI */
+ { "sh_fsi_a_ak4642", (kernel_ulong_t)&fsi_a_ak4642 },
+ { "sh_fsi_b_ak4642", (kernel_ulong_t)&fsi_b_ak4642 },
+ { "sh_fsi_a_ak4643", (kernel_ulong_t)&fsi_a_ak4643 },
+ { "sh_fsi_b_ak4643", (kernel_ulong_t)&fsi_b_ak4643 },
+
+ /* FSI 2 */
+ { "sh_fsi2_a_ak4642", (kernel_ulong_t)&fsi2_a_ak4642 },
+ { "sh_fsi2_b_ak4642", (kernel_ulong_t)&fsi2_b_ak4642 },
+ { "sh_fsi2_a_ak4643", (kernel_ulong_t)&fsi2_a_ak4643 },
+ { "sh_fsi2_b_ak4643", (kernel_ulong_t)&fsi2_b_ak4643 },
+ {},
+};
+
+static struct platform_driver fsi_ak4642 = {
+ .driver = {
+ .name = "fsi-ak4642-audio",
+ },
+ .probe = fsi_ak4642_probe,
+ .remove = fsi_ak4642_remove,
+ .id_table = fsi_id_table,
+};
+
+static int __init fsi_ak4642_init(void)
+{
+ return platform_driver_register(&fsi_ak4642);
+}
+
+static void __exit fsi_ak4642_exit(void)
+{
+ platform_driver_unregister(&fsi_ak4642);
+}
+
+module_init(fsi_ak4642_init);
+module_exit(fsi_ak4642_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Generic SH4 FSI-AK4642 sound card");
+MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
diff --git a/sound/soc/sh/fsi-da7210.c b/sound/soc/sh/fsi-da7210.c
new file mode 100644
index 00000000..59553fd8
--- /dev/null
+++ b/sound/soc/sh/fsi-da7210.c
@@ -0,0 +1,79 @@
+/*
+ * fsi-da7210.c
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/platform_device.h>
+#include <sound/sh_fsi.h>
+
+static int fsi_da7210_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *codec = rtd->codec_dai;
+ struct snd_soc_dai *cpu = rtd->cpu_dai;
+ int ret;
+
+ ret = snd_soc_dai_set_fmt(codec,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_CBS_CFS);
+
+ return ret;
+}
+
+static struct snd_soc_dai_link fsi_da7210_dai = {
+ .name = "DA7210",
+ .stream_name = "DA7210",
+ .cpu_dai_name = "fsib-dai", /* FSI B */
+ .codec_dai_name = "da7210-hifi",
+ .platform_name = "sh_fsi.0",
+ .codec_name = "da7210-codec.0-001a",
+ .init = fsi_da7210_init,
+};
+
+static struct snd_soc_card fsi_soc_card = {
+ .name = "FSI-DA7210",
+ .dai_link = &fsi_da7210_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *fsi_da7210_snd_device;
+
+static int __init fsi_da7210_sound_init(void)
+{
+ int ret;
+
+ fsi_da7210_snd_device = platform_device_alloc("soc-audio", FSI_PORT_B);
+ if (!fsi_da7210_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(fsi_da7210_snd_device, &fsi_soc_card);
+ ret = platform_device_add(fsi_da7210_snd_device);
+ if (ret)
+ platform_device_put(fsi_da7210_snd_device);
+
+ return ret;
+}
+
+static void __exit fsi_da7210_sound_exit(void)
+{
+ platform_device_unregister(fsi_da7210_snd_device);
+}
+
+module_init(fsi_da7210_sound_init);
+module_exit(fsi_da7210_sound_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC FSI DA2710");
+MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/sh/fsi-hdmi.c b/sound/soc/sh/fsi-hdmi.c
new file mode 100644
index 00000000..d3d9fd88
--- /dev/null
+++ b/sound/soc/sh/fsi-hdmi.c
@@ -0,0 +1,127 @@
+/*
+ * FSI - HDMI sound support
+ *
+ * Copyright (C) 2010 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/platform_device.h>
+#include <sound/sh_fsi.h>
+
+struct fsi_hdmi_data {
+ const char *cpu_dai;
+ const char *card;
+ int id;
+};
+
+static int fsi_hdmi_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu = rtd->cpu_dai;
+ int ret;
+
+ ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_CBM_CFM);
+
+ return ret;
+}
+
+static struct snd_soc_dai_link fsi_dai_link = {
+ .name = "HDMI",
+ .stream_name = "HDMI",
+ .codec_dai_name = "sh_mobile_hdmi-hifi",
+ .platform_name = "sh_fsi2",
+ .codec_name = "sh-mobile-hdmi",
+ .init = fsi_hdmi_dai_init,
+};
+
+static struct snd_soc_card fsi_soc_card = {
+ .dai_link = &fsi_dai_link,
+ .num_links = 1,
+};
+
+static struct platform_device *fsi_snd_device;
+
+static int fsi_hdmi_probe(struct platform_device *pdev)
+{
+ int ret = -ENOMEM;
+ const struct platform_device_id *id_entry;
+ struct fsi_hdmi_data *pdata;
+
+ id_entry = pdev->id_entry;
+ if (!id_entry) {
+ dev_err(&pdev->dev, "unknown fsi hdmi\n");
+ return -ENODEV;
+ }
+
+ pdata = (struct fsi_hdmi_data *)id_entry->driver_data;
+
+ fsi_snd_device = platform_device_alloc("soc-audio", pdata->id);
+ if (!fsi_snd_device)
+ goto out;
+
+ fsi_dai_link.cpu_dai_name = pdata->cpu_dai;
+ fsi_soc_card.name = pdata->card;
+
+ platform_set_drvdata(fsi_snd_device, &fsi_soc_card);
+ ret = platform_device_add(fsi_snd_device);
+
+ if (ret)
+ platform_device_put(fsi_snd_device);
+
+out:
+ return ret;
+}
+
+static int fsi_hdmi_remove(struct platform_device *pdev)
+{
+ platform_device_unregister(fsi_snd_device);
+ return 0;
+}
+
+static struct fsi_hdmi_data fsi2_a_hdmi = {
+ .cpu_dai = "fsia-dai",
+ .card = "FSI2A-HDMI",
+ .id = FSI_PORT_A,
+};
+
+static struct fsi_hdmi_data fsi2_b_hdmi = {
+ .cpu_dai = "fsib-dai",
+ .card = "FSI2B-HDMI",
+ .id = FSI_PORT_B,
+};
+
+static struct platform_device_id fsi_id_table[] = {
+ /* FSI 2 */
+ { "sh_fsi2_a_hdmi", (kernel_ulong_t)&fsi2_a_hdmi },
+ { "sh_fsi2_b_hdmi", (kernel_ulong_t)&fsi2_b_hdmi },
+ {},
+};
+
+static struct platform_driver fsi_hdmi = {
+ .driver = {
+ .name = "fsi-hdmi-audio",
+ },
+ .probe = fsi_hdmi_probe,
+ .remove = fsi_hdmi_remove,
+ .id_table = fsi_id_table,
+};
+
+static int __init fsi_hdmi_init(void)
+{
+ return platform_driver_register(&fsi_hdmi);
+}
+
+static void __exit fsi_hdmi_exit(void)
+{
+ platform_driver_unregister(&fsi_hdmi);
+}
+
+module_init(fsi_hdmi_init);
+module_exit(fsi_hdmi_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Generic SH4 FSI-HDMI sound card");
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c
new file mode 100644
index 00000000..4a9da6b5
--- /dev/null
+++ b/sound/soc/sh/fsi.c
@@ -0,0 +1,1455 @@
+/*
+ * Fifo-attached Serial Interface (FSI) support for SH7724
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * Based on ssi.c
+ * Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/sh_fsi.h>
+
+/* PortA/PortB register */
+#define REG_DO_FMT 0x0000
+#define REG_DOFF_CTL 0x0004
+#define REG_DOFF_ST 0x0008
+#define REG_DI_FMT 0x000C
+#define REG_DIFF_CTL 0x0010
+#define REG_DIFF_ST 0x0014
+#define REG_CKG1 0x0018
+#define REG_CKG2 0x001C
+#define REG_DIDT 0x0020
+#define REG_DODT 0x0024
+#define REG_MUTE_ST 0x0028
+#define REG_OUT_SEL 0x0030
+
+/* master register */
+#define MST_CLK_RST 0x0210
+#define MST_SOFT_RST 0x0214
+#define MST_FIFO_SZ 0x0218
+
+/* core register (depend on FSI version) */
+#define A_MST_CTLR 0x0180
+#define B_MST_CTLR 0x01A0
+#define CPU_INT_ST 0x01F4
+#define CPU_IEMSK 0x01F8
+#define CPU_IMSK 0x01FC
+#define INT_ST 0x0200
+#define IEMSK 0x0204
+#define IMSK 0x0208
+
+/* DO_FMT */
+/* DI_FMT */
+#define CR_BWS_24 (0x0 << 20) /* FSI2 */
+#define CR_BWS_16 (0x1 << 20) /* FSI2 */
+#define CR_BWS_20 (0x2 << 20) /* FSI2 */
+
+#define CR_DTMD_PCM (0x0 << 8) /* FSI2 */
+#define CR_DTMD_SPDIF_PCM (0x1 << 8) /* FSI2 */
+#define CR_DTMD_SPDIF_STREAM (0x2 << 8) /* FSI2 */
+
+#define CR_MONO (0x0 << 4)
+#define CR_MONO_D (0x1 << 4)
+#define CR_PCM (0x2 << 4)
+#define CR_I2S (0x3 << 4)
+#define CR_TDM (0x4 << 4)
+#define CR_TDM_D (0x5 << 4)
+
+/* DOFF_CTL */
+/* DIFF_CTL */
+#define IRQ_HALF 0x00100000
+#define FIFO_CLR 0x00000001
+
+/* DOFF_ST */
+#define ERR_OVER 0x00000010
+#define ERR_UNDER 0x00000001
+#define ST_ERR (ERR_OVER | ERR_UNDER)
+
+/* CKG1 */
+#define ACKMD_MASK 0x00007000
+#define BPFMD_MASK 0x00000700
+#define DIMD (1 << 4)
+#define DOMD (1 << 0)
+
+/* A/B MST_CTLR */
+#define BP (1 << 4) /* Fix the signal of Biphase output */
+#define SE (1 << 0) /* Fix the master clock */
+
+/* CLK_RST */
+#define CRB (1 << 4)
+#define CRA (1 << 0)
+
+/* IO SHIFT / MACRO */
+#define BI_SHIFT 12
+#define BO_SHIFT 8
+#define AI_SHIFT 4
+#define AO_SHIFT 0
+#define AB_IO(param, shift) (param << shift)
+
+/* SOFT_RST */
+#define PBSR (1 << 12) /* Port B Software Reset */
+#define PASR (1 << 8) /* Port A Software Reset */
+#define IR (1 << 4) /* Interrupt Reset */
+#define FSISR (1 << 0) /* Software Reset */
+
+/* OUT_SEL (FSI2) */
+#define DMMD (1 << 4) /* SPDIF output timing 0: Biphase only */
+ /* 1: Biphase and serial */
+
+/* FIFO_SZ */
+#define FIFO_SZ_MASK 0x7
+
+#define FSI_RATES SNDRV_PCM_RATE_8000_96000
+
+#define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
+
+typedef int (*set_rate_func)(struct device *dev, int is_porta, int rate, int enable);
+
+/*
+ * FSI driver use below type name for variable
+ *
+ * xxx_len : data length
+ * xxx_width : data width
+ * xxx_offset : data offset
+ * xxx_num : number of data
+ */
+
+/*
+ * struct
+ */
+
+struct fsi_stream {
+ struct snd_pcm_substream *substream;
+
+ int fifo_max_num;
+
+ int buff_offset;
+ int buff_len;
+ int period_len;
+ int period_num;
+
+ int uerr_num;
+ int oerr_num;
+};
+
+struct fsi_priv {
+ void __iomem *base;
+ struct fsi_master *master;
+
+ struct fsi_stream playback;
+ struct fsi_stream capture;
+
+ int chan_num:16;
+ int clk_master:1;
+
+ long rate;
+
+ /* for suspend/resume */
+ u32 saved_do_fmt;
+ u32 saved_di_fmt;
+ u32 saved_ckg1;
+ u32 saved_ckg2;
+ u32 saved_out_sel;
+};
+
+struct fsi_core {
+ int ver;
+
+ u32 int_st;
+ u32 iemsk;
+ u32 imsk;
+ u32 a_mclk;
+ u32 b_mclk;
+};
+
+struct fsi_master {
+ void __iomem *base;
+ int irq;
+ struct fsi_priv fsia;
+ struct fsi_priv fsib;
+ struct fsi_core *core;
+ struct sh_fsi_platform_info *info;
+ spinlock_t lock;
+
+ /* for suspend/resume */
+ u32 saved_a_mclk;
+ u32 saved_b_mclk;
+ u32 saved_iemsk;
+ u32 saved_imsk;
+ u32 saved_clk_rst;
+ u32 saved_soft_rst;
+};
+
+/*
+ * basic read write function
+ */
+
+static void __fsi_reg_write(u32 reg, u32 data)
+{
+ /* valid data area is 24bit */
+ data &= 0x00ffffff;
+
+ __raw_writel(data, reg);
+}
+
+static u32 __fsi_reg_read(u32 reg)
+{
+ return __raw_readl(reg);
+}
+
+static void __fsi_reg_mask_set(u32 reg, u32 mask, u32 data)
+{
+ u32 val = __fsi_reg_read(reg);
+
+ val &= ~mask;
+ val |= data & mask;
+
+ __fsi_reg_write(reg, val);
+}
+
+#define fsi_reg_write(p, r, d)\
+ __fsi_reg_write((u32)(p->base + REG_##r), d)
+
+#define fsi_reg_read(p, r)\
+ __fsi_reg_read((u32)(p->base + REG_##r))
+
+#define fsi_reg_mask_set(p, r, m, d)\
+ __fsi_reg_mask_set((u32)(p->base + REG_##r), m, d)
+
+#define fsi_master_read(p, r) _fsi_master_read(p, MST_##r)
+#define fsi_core_read(p, r) _fsi_master_read(p, p->core->r)
+static u32 _fsi_master_read(struct fsi_master *master, u32 reg)
+{
+ u32 ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&master->lock, flags);
+ ret = __fsi_reg_read((u32)(master->base + reg));
+ spin_unlock_irqrestore(&master->lock, flags);
+
+ return ret;
+}
+
+#define fsi_master_mask_set(p, r, m, d) _fsi_master_mask_set(p, MST_##r, m, d)
+#define fsi_core_mask_set(p, r, m, d) _fsi_master_mask_set(p, p->core->r, m, d)
+static void _fsi_master_mask_set(struct fsi_master *master,
+ u32 reg, u32 mask, u32 data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&master->lock, flags);
+ __fsi_reg_mask_set((u32)(master->base + reg), mask, data);
+ spin_unlock_irqrestore(&master->lock, flags);
+}
+
+/*
+ * basic function
+ */
+
+static struct fsi_master *fsi_get_master(struct fsi_priv *fsi)
+{
+ return fsi->master;
+}
+
+static int fsi_is_clk_master(struct fsi_priv *fsi)
+{
+ return fsi->clk_master;
+}
+
+static int fsi_is_port_a(struct fsi_priv *fsi)
+{
+ return fsi->master->base == fsi->base;
+}
+
+static struct snd_soc_dai *fsi_get_dai(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ return rtd->cpu_dai;
+}
+
+static struct fsi_priv *fsi_get_priv_frm_dai(struct snd_soc_dai *dai)
+{
+ struct fsi_master *master = snd_soc_dai_get_drvdata(dai);
+
+ if (dai->id == 0)
+ return &master->fsia;
+ else
+ return &master->fsib;
+}
+
+static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream)
+{
+ return fsi_get_priv_frm_dai(fsi_get_dai(substream));
+}
+
+static set_rate_func fsi_get_info_set_rate(struct fsi_master *master)
+{
+ if (!master->info)
+ return NULL;
+
+ return master->info->set_rate;
+}
+
+static u32 fsi_get_info_flags(struct fsi_priv *fsi)
+{
+ int is_porta = fsi_is_port_a(fsi);
+ struct fsi_master *master = fsi_get_master(fsi);
+
+ if (!master->info)
+ return 0;
+
+ return is_porta ? master->info->porta_flags :
+ master->info->portb_flags;
+}
+
+static inline int fsi_stream_is_play(int stream)
+{
+ return stream == SNDRV_PCM_STREAM_PLAYBACK;
+}
+
+static inline int fsi_is_play(struct snd_pcm_substream *substream)
+{
+ return fsi_stream_is_play(substream->stream);
+}
+
+static inline struct fsi_stream *fsi_get_stream(struct fsi_priv *fsi,
+ int is_play)
+{
+ return is_play ? &fsi->playback : &fsi->capture;
+}
+
+static u32 fsi_get_port_shift(struct fsi_priv *fsi, int is_play)
+{
+ int is_porta = fsi_is_port_a(fsi);
+ u32 shift;
+
+ if (is_porta)
+ shift = is_play ? AO_SHIFT : AI_SHIFT;
+ else
+ shift = is_play ? BO_SHIFT : BI_SHIFT;
+
+ return shift;
+}
+
+static void fsi_stream_push(struct fsi_priv *fsi,
+ int is_play,
+ struct snd_pcm_substream *substream,
+ u32 buffer_len,
+ u32 period_len)
+{
+ struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+
+ io->substream = substream;
+ io->buff_len = buffer_len;
+ io->buff_offset = 0;
+ io->period_len = period_len;
+ io->period_num = 0;
+ io->oerr_num = -1; /* ignore 1st err */
+ io->uerr_num = -1; /* ignore 1st err */
+}
+
+static void fsi_stream_pop(struct fsi_priv *fsi, int is_play)
+{
+ struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+ struct snd_soc_dai *dai = fsi_get_dai(io->substream);
+
+
+ if (io->oerr_num > 0)
+ dev_err(dai->dev, "over_run = %d\n", io->oerr_num);
+
+ if (io->uerr_num > 0)
+ dev_err(dai->dev, "under_run = %d\n", io->uerr_num);
+
+ io->substream = NULL;
+ io->buff_len = 0;
+ io->buff_offset = 0;
+ io->period_len = 0;
+ io->period_num = 0;
+ io->oerr_num = 0;
+ io->uerr_num = 0;
+}
+
+static int fsi_get_fifo_data_num(struct fsi_priv *fsi, int is_play)
+{
+ u32 status;
+ int data_num;
+
+ status = is_play ?
+ fsi_reg_read(fsi, DOFF_ST) :
+ fsi_reg_read(fsi, DIFF_ST);
+
+ data_num = 0x1ff & (status >> 8);
+ data_num *= fsi->chan_num;
+
+ return data_num;
+}
+
+static int fsi_len2num(int len, int width)
+{
+ return len / width;
+}
+
+#define fsi_num2offset(a, b) fsi_num2len(a, b)
+static int fsi_num2len(int num, int width)
+{
+ return num * width;
+}
+
+static int fsi_get_frame_width(struct fsi_priv *fsi, int is_play)
+{
+ struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+ struct snd_pcm_substream *substream = io->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return frames_to_bytes(runtime, 1) / fsi->chan_num;
+}
+
+static void fsi_count_fifo_err(struct fsi_priv *fsi)
+{
+ u32 ostatus = fsi_reg_read(fsi, DOFF_ST);
+ u32 istatus = fsi_reg_read(fsi, DIFF_ST);
+
+ if (ostatus & ERR_OVER)
+ fsi->playback.oerr_num++;
+
+ if (ostatus & ERR_UNDER)
+ fsi->playback.uerr_num++;
+
+ if (istatus & ERR_OVER)
+ fsi->capture.oerr_num++;
+
+ if (istatus & ERR_UNDER)
+ fsi->capture.uerr_num++;
+
+ fsi_reg_write(fsi, DOFF_ST, 0);
+ fsi_reg_write(fsi, DIFF_ST, 0);
+}
+
+/*
+ * dma function
+ */
+
+static u8 *fsi_dma_get_area(struct fsi_priv *fsi, int stream)
+{
+ int is_play = fsi_stream_is_play(stream);
+ struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+
+ return io->substream->runtime->dma_area + io->buff_offset;
+}
+
+static void fsi_dma_soft_push16(struct fsi_priv *fsi, int num)
+{
+ u16 *start;
+ int i;
+
+ start = (u16 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_PLAYBACK);
+
+ for (i = 0; i < num; i++)
+ fsi_reg_write(fsi, DODT, ((u32)*(start + i) << 8));
+}
+
+static void fsi_dma_soft_pop16(struct fsi_priv *fsi, int num)
+{
+ u16 *start;
+ int i;
+
+ start = (u16 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_CAPTURE);
+
+
+ for (i = 0; i < num; i++)
+ *(start + i) = (u16)(fsi_reg_read(fsi, DIDT) >> 8);
+}
+
+static void fsi_dma_soft_push32(struct fsi_priv *fsi, int num)
+{
+ u32 *start;
+ int i;
+
+ start = (u32 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_PLAYBACK);
+
+
+ for (i = 0; i < num; i++)
+ fsi_reg_write(fsi, DODT, *(start + i));
+}
+
+static void fsi_dma_soft_pop32(struct fsi_priv *fsi, int num)
+{
+ u32 *start;
+ int i;
+
+ start = (u32 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_CAPTURE);
+
+ for (i = 0; i < num; i++)
+ *(start + i) = fsi_reg_read(fsi, DIDT);
+}
+
+/*
+ * irq function
+ */
+
+static void fsi_irq_enable(struct fsi_priv *fsi, int is_play)
+{
+ u32 data = AB_IO(1, fsi_get_port_shift(fsi, is_play));
+ struct fsi_master *master = fsi_get_master(fsi);
+
+ fsi_core_mask_set(master, imsk, data, data);
+ fsi_core_mask_set(master, iemsk, data, data);
+}
+
+static void fsi_irq_disable(struct fsi_priv *fsi, int is_play)
+{
+ u32 data = AB_IO(1, fsi_get_port_shift(fsi, is_play));
+ struct fsi_master *master = fsi_get_master(fsi);
+
+ fsi_core_mask_set(master, imsk, data, 0);
+ fsi_core_mask_set(master, iemsk, data, 0);
+}
+
+static u32 fsi_irq_get_status(struct fsi_master *master)
+{
+ return fsi_core_read(master, int_st);
+}
+
+static void fsi_irq_clear_status(struct fsi_priv *fsi)
+{
+ u32 data = 0;
+ struct fsi_master *master = fsi_get_master(fsi);
+
+ data |= AB_IO(1, fsi_get_port_shift(fsi, 0));
+ data |= AB_IO(1, fsi_get_port_shift(fsi, 1));
+
+ /* clear interrupt factor */
+ fsi_core_mask_set(master, int_st, data, 0);
+}
+
+/*
+ * SPDIF master clock function
+ *
+ * These functions are used later FSI2
+ */
+static void fsi_spdif_clk_ctrl(struct fsi_priv *fsi, int enable)
+{
+ struct fsi_master *master = fsi_get_master(fsi);
+ u32 mask, val;
+
+ if (master->core->ver < 2) {
+ pr_err("fsi: register access err (%s)\n", __func__);
+ return;
+ }
+
+ mask = BP | SE;
+ val = enable ? mask : 0;
+
+ fsi_is_port_a(fsi) ?
+ fsi_core_mask_set(master, a_mclk, mask, val) :
+ fsi_core_mask_set(master, b_mclk, mask, val);
+}
+
+/*
+ * clock function
+ */
+#define fsi_module_init(m, d) __fsi_module_clk_ctrl(m, d, 1)
+#define fsi_module_kill(m, d) __fsi_module_clk_ctrl(m, d, 0)
+static void __fsi_module_clk_ctrl(struct fsi_master *master,
+ struct device *dev,
+ int enable)
+{
+ pm_runtime_get_sync(dev);
+
+ if (enable) {
+ /* enable only SR */
+ fsi_master_mask_set(master, SOFT_RST, FSISR, FSISR);
+ fsi_master_mask_set(master, SOFT_RST, PASR | PBSR, 0);
+ } else {
+ /* clear all registers */
+ fsi_master_mask_set(master, SOFT_RST, FSISR, 0);
+ }
+
+ pm_runtime_put_sync(dev);
+}
+
+#define fsi_port_start(f) __fsi_port_clk_ctrl(f, 1)
+#define fsi_port_stop(f) __fsi_port_clk_ctrl(f, 0)
+static void __fsi_port_clk_ctrl(struct fsi_priv *fsi, int enable)
+{
+ struct fsi_master *master = fsi_get_master(fsi);
+ u32 soft = fsi_is_port_a(fsi) ? PASR : PBSR;
+ u32 clk = fsi_is_port_a(fsi) ? CRA : CRB;
+ int is_master = fsi_is_clk_master(fsi);
+
+ fsi_master_mask_set(master, SOFT_RST, soft, (enable) ? soft : 0);
+ if (is_master)
+ fsi_master_mask_set(master, CLK_RST, clk, (enable) ? clk : 0);
+}
+
+/*
+ * ctrl function
+ */
+static void fsi_fifo_init(struct fsi_priv *fsi,
+ int is_play,
+ struct snd_soc_dai *dai)
+{
+ struct fsi_master *master = fsi_get_master(fsi);
+ struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+ u32 shift, i;
+
+ /* get on-chip RAM capacity */
+ shift = fsi_master_read(master, FIFO_SZ);
+ shift >>= fsi_get_port_shift(fsi, is_play);
+ shift &= FIFO_SZ_MASK;
+ io->fifo_max_num = 256 << shift;
+ dev_dbg(dai->dev, "fifo = %d words\n", io->fifo_max_num);
+
+ /*
+ * The maximum number of sample data varies depending
+ * on the number of channels selected for the format.
+ *
+ * FIFOs are used in 4-channel units in 3-channel mode
+ * and in 8-channel units in 5- to 7-channel mode
+ * meaning that more FIFOs than the required size of DPRAM
+ * are used.
+ *
+ * ex) if 256 words of DP-RAM is connected
+ * 1 channel: 256 (256 x 1 = 256)
+ * 2 channels: 128 (128 x 2 = 256)
+ * 3 channels: 64 ( 64 x 3 = 192)
+ * 4 channels: 64 ( 64 x 4 = 256)
+ * 5 channels: 32 ( 32 x 5 = 160)
+ * 6 channels: 32 ( 32 x 6 = 192)
+ * 7 channels: 32 ( 32 x 7 = 224)
+ * 8 channels: 32 ( 32 x 8 = 256)
+ */
+ for (i = 1; i < fsi->chan_num; i <<= 1)
+ io->fifo_max_num >>= 1;
+ dev_dbg(dai->dev, "%d channel %d store\n",
+ fsi->chan_num, io->fifo_max_num);
+
+ /*
+ * set interrupt generation factor
+ * clear FIFO
+ */
+ if (is_play) {
+ fsi_reg_write(fsi, DOFF_CTL, IRQ_HALF);
+ fsi_reg_mask_set(fsi, DOFF_CTL, FIFO_CLR, FIFO_CLR);
+ } else {
+ fsi_reg_write(fsi, DIFF_CTL, IRQ_HALF);
+ fsi_reg_mask_set(fsi, DIFF_CTL, FIFO_CLR, FIFO_CLR);
+ }
+}
+
+static int fsi_fifo_data_ctrl(struct fsi_priv *fsi, int stream)
+{
+ struct snd_pcm_runtime *runtime;
+ struct snd_pcm_substream *substream = NULL;
+ int is_play = fsi_stream_is_play(stream);
+ struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+ int data_residue_num;
+ int data_num;
+ int data_num_max;
+ int ch_width;
+ int over_period;
+ void (*fn)(struct fsi_priv *fsi, int size);
+
+ if (!fsi ||
+ !io->substream ||
+ !io->substream->runtime)
+ return -EINVAL;
+
+ over_period = 0;
+ substream = io->substream;
+ runtime = substream->runtime;
+
+ /* FSI FIFO has limit.
+ * So, this driver can not send periods data at a time
+ */
+ if (io->buff_offset >=
+ fsi_num2offset(io->period_num + 1, io->period_len)) {
+
+ over_period = 1;
+ io->period_num = (io->period_num + 1) % runtime->periods;
+
+ if (0 == io->period_num)
+ io->buff_offset = 0;
+ }
+
+ /* get 1 channel data width */
+ ch_width = fsi_get_frame_width(fsi, is_play);
+
+ /* get residue data number of alsa */
+ data_residue_num = fsi_len2num(io->buff_len - io->buff_offset,
+ ch_width);
+
+ if (is_play) {
+ /*
+ * for play-back
+ *
+ * data_num_max : number of FSI fifo free space
+ * data_num : number of ALSA residue data
+ */
+ data_num_max = io->fifo_max_num * fsi->chan_num;
+ data_num_max -= fsi_get_fifo_data_num(fsi, is_play);
+
+ data_num = data_residue_num;
+
+ switch (ch_width) {
+ case 2:
+ fn = fsi_dma_soft_push16;
+ break;
+ case 4:
+ fn = fsi_dma_soft_push32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ /*
+ * for capture
+ *
+ * data_num_max : number of ALSA free space
+ * data_num : number of data in FSI fifo
+ */
+ data_num_max = data_residue_num;
+ data_num = fsi_get_fifo_data_num(fsi, is_play);
+
+ switch (ch_width) {
+ case 2:
+ fn = fsi_dma_soft_pop16;
+ break;
+ case 4:
+ fn = fsi_dma_soft_pop32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ data_num = min(data_num, data_num_max);
+
+ fn(fsi, data_num);
+
+ /* update buff_offset */
+ io->buff_offset += fsi_num2offset(data_num, ch_width);
+
+ if (over_period)
+ snd_pcm_period_elapsed(substream);
+
+ return 0;
+}
+
+static int fsi_data_pop(struct fsi_priv *fsi)
+{
+ return fsi_fifo_data_ctrl(fsi, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int fsi_data_push(struct fsi_priv *fsi)
+{
+ return fsi_fifo_data_ctrl(fsi, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static irqreturn_t fsi_interrupt(int irq, void *data)
+{
+ struct fsi_master *master = data;
+ u32 int_st = fsi_irq_get_status(master);
+
+ /* clear irq status */
+ fsi_master_mask_set(master, SOFT_RST, IR, 0);
+ fsi_master_mask_set(master, SOFT_RST, IR, IR);
+
+ if (int_st & AB_IO(1, AO_SHIFT))
+ fsi_data_push(&master->fsia);
+ if (int_st & AB_IO(1, BO_SHIFT))
+ fsi_data_push(&master->fsib);
+ if (int_st & AB_IO(1, AI_SHIFT))
+ fsi_data_pop(&master->fsia);
+ if (int_st & AB_IO(1, BI_SHIFT))
+ fsi_data_pop(&master->fsib);
+
+ fsi_count_fifo_err(&master->fsia);
+ fsi_count_fifo_err(&master->fsib);
+
+ fsi_irq_clear_status(&master->fsia);
+ fsi_irq_clear_status(&master->fsib);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * dai ops
+ */
+
+static int fsi_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsi_priv *fsi = fsi_get_priv(substream);
+ u32 flags = fsi_get_info_flags(fsi);
+ u32 data;
+ int is_play = fsi_is_play(substream);
+
+ pm_runtime_get_sync(dai->dev);
+
+
+ /* clock inversion (CKG2) */
+ data = 0;
+ if (SH_FSI_LRM_INV & flags)
+ data |= 1 << 12;
+ if (SH_FSI_BRM_INV & flags)
+ data |= 1 << 8;
+ if (SH_FSI_LRS_INV & flags)
+ data |= 1 << 4;
+ if (SH_FSI_BRS_INV & flags)
+ data |= 1 << 0;
+
+ fsi_reg_write(fsi, CKG2, data);
+
+ /* irq clear */
+ fsi_irq_disable(fsi, is_play);
+ fsi_irq_clear_status(fsi);
+
+ /* fifo init */
+ fsi_fifo_init(fsi, is_play, dai);
+
+ return 0;
+}
+
+static void fsi_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsi_priv *fsi = fsi_get_priv(substream);
+ int is_play = fsi_is_play(substream);
+ struct fsi_master *master = fsi_get_master(fsi);
+ set_rate_func set_rate = fsi_get_info_set_rate(master);
+
+ fsi_irq_disable(fsi, is_play);
+
+ if (fsi_is_clk_master(fsi))
+ set_rate(dai->dev, fsi_is_port_a(fsi), fsi->rate, 0);
+
+ fsi->rate = 0;
+
+ pm_runtime_put_sync(dai->dev);
+}
+
+static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct fsi_priv *fsi = fsi_get_priv(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int is_play = fsi_is_play(substream);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ fsi_stream_push(fsi, is_play, substream,
+ frames_to_bytes(runtime, runtime->buffer_size),
+ frames_to_bytes(runtime, runtime->period_size));
+ ret = is_play ? fsi_data_push(fsi) : fsi_data_pop(fsi);
+ fsi_irq_enable(fsi, is_play);
+ fsi_port_start(fsi);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ fsi_port_stop(fsi);
+ fsi_irq_disable(fsi, is_play);
+ fsi_stream_pop(fsi, is_play);
+ break;
+ }
+
+ return ret;
+}
+
+static int fsi_set_fmt_dai(struct fsi_priv *fsi, unsigned int fmt)
+{
+ u32 data = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ data = CR_I2S;
+ fsi->chan_num = 2;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ data = CR_PCM;
+ fsi->chan_num = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fsi_reg_write(fsi, DO_FMT, data);
+ fsi_reg_write(fsi, DI_FMT, data);
+
+ return 0;
+}
+
+static int fsi_set_fmt_spdif(struct fsi_priv *fsi)
+{
+ struct fsi_master *master = fsi_get_master(fsi);
+ u32 data = 0;
+
+ if (master->core->ver < 2)
+ return -EINVAL;
+
+ data = CR_BWS_16 | CR_DTMD_SPDIF_PCM | CR_PCM;
+ fsi->chan_num = 2;
+ fsi_spdif_clk_ctrl(fsi, 1);
+ fsi_reg_mask_set(fsi, OUT_SEL, DMMD, DMMD);
+
+ fsi_reg_write(fsi, DO_FMT, data);
+ fsi_reg_write(fsi, DI_FMT, data);
+
+ return 0;
+}
+
+static int fsi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct fsi_priv *fsi = fsi_get_priv_frm_dai(dai);
+ struct fsi_master *master = fsi_get_master(fsi);
+ set_rate_func set_rate = fsi_get_info_set_rate(master);
+ u32 flags = fsi_get_info_flags(fsi);
+ u32 data = 0;
+ int ret;
+
+ pm_runtime_get_sync(dai->dev);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ data = DIMD | DOMD;
+ fsi->clk_master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ ret = -EINVAL;
+ goto set_fmt_exit;
+ }
+
+ if (fsi_is_clk_master(fsi) && !set_rate) {
+ dev_err(dai->dev, "platform doesn't have set_rate\n");
+ ret = -EINVAL;
+ goto set_fmt_exit;
+ }
+
+ fsi_reg_mask_set(fsi, CKG1, (DIMD | DOMD), data);
+
+ /* set format */
+ switch (flags & SH_FSI_FMT_MASK) {
+ case SH_FSI_FMT_DAI:
+ ret = fsi_set_fmt_dai(fsi, fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+ break;
+ case SH_FSI_FMT_SPDIF:
+ ret = fsi_set_fmt_spdif(fsi);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+set_fmt_exit:
+ pm_runtime_put_sync(dai->dev);
+
+ return ret;
+}
+
+static int fsi_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct fsi_priv *fsi = fsi_get_priv(substream);
+ struct fsi_master *master = fsi_get_master(fsi);
+ set_rate_func set_rate = fsi_get_info_set_rate(master);
+ int fsi_ver = master->core->ver;
+ long rate = params_rate(params);
+ int ret;
+
+ if (!fsi_is_clk_master(fsi))
+ return 0;
+
+ ret = set_rate(dai->dev, fsi_is_port_a(fsi), rate, 1);
+ if (ret < 0) /* error */
+ return ret;
+
+ fsi->rate = rate;
+ if (ret > 0) {
+ u32 data = 0;
+
+ switch (ret & SH_FSI_ACKMD_MASK) {
+ default:
+ /* FALL THROUGH */
+ case SH_FSI_ACKMD_512:
+ data |= (0x0 << 12);
+ break;
+ case SH_FSI_ACKMD_256:
+ data |= (0x1 << 12);
+ break;
+ case SH_FSI_ACKMD_128:
+ data |= (0x2 << 12);
+ break;
+ case SH_FSI_ACKMD_64:
+ data |= (0x3 << 12);
+ break;
+ case SH_FSI_ACKMD_32:
+ if (fsi_ver < 2)
+ dev_err(dai->dev, "unsupported ACKMD\n");
+ else
+ data |= (0x4 << 12);
+ break;
+ }
+
+ switch (ret & SH_FSI_BPFMD_MASK) {
+ default:
+ /* FALL THROUGH */
+ case SH_FSI_BPFMD_32:
+ data |= (0x0 << 8);
+ break;
+ case SH_FSI_BPFMD_64:
+ data |= (0x1 << 8);
+ break;
+ case SH_FSI_BPFMD_128:
+ data |= (0x2 << 8);
+ break;
+ case SH_FSI_BPFMD_256:
+ data |= (0x3 << 8);
+ break;
+ case SH_FSI_BPFMD_512:
+ data |= (0x4 << 8);
+ break;
+ case SH_FSI_BPFMD_16:
+ if (fsi_ver < 2)
+ dev_err(dai->dev, "unsupported ACKMD\n");
+ else
+ data |= (0x7 << 8);
+ break;
+ }
+
+ fsi_reg_mask_set(fsi, CKG1, (ACKMD_MASK | BPFMD_MASK) , data);
+ udelay(10);
+ ret = 0;
+ }
+
+ return ret;
+
+}
+
+static struct snd_soc_dai_ops fsi_dai_ops = {
+ .startup = fsi_dai_startup,
+ .shutdown = fsi_dai_shutdown,
+ .trigger = fsi_dai_trigger,
+ .set_fmt = fsi_dai_set_fmt,
+ .hw_params = fsi_dai_hw_params,
+};
+
+/*
+ * pcm ops
+ */
+
+static struct snd_pcm_hardware fsi_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE,
+ .formats = FSI_FMTS,
+ .rates = FSI_RATES,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = 64 * 1024,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 32,
+ .fifo_size = 256,
+};
+
+static int fsi_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret = 0;
+
+ snd_soc_set_runtime_hwparams(substream, &fsi_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+
+ return ret;
+}
+
+static int fsi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int fsi_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static snd_pcm_uframes_t fsi_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsi_priv *fsi = fsi_get_priv(substream);
+ struct fsi_stream *io = fsi_get_stream(fsi, fsi_is_play(substream));
+ long location;
+
+ location = (io->buff_offset - 1);
+ if (location < 0)
+ location = 0;
+
+ return bytes_to_frames(runtime, location);
+}
+
+static struct snd_pcm_ops fsi_pcm_ops = {
+ .open = fsi_pcm_open,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = fsi_hw_params,
+ .hw_free = fsi_hw_free,
+ .pointer = fsi_pointer,
+};
+
+/*
+ * snd_soc_platform
+ */
+
+#define PREALLOC_BUFFER (32 * 1024)
+#define PREALLOC_BUFFER_MAX (32 * 1024)
+
+static void fsi_pcm_free(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int fsi_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ /*
+ * dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel
+ * in MMAP mode (i.e. aplay -M)
+ */
+ return snd_pcm_lib_preallocate_pages_for_all(
+ pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ PREALLOC_BUFFER, PREALLOC_BUFFER_MAX);
+}
+
+/*
+ * alsa struct
+ */
+
+static struct snd_soc_dai_driver fsi_soc_dai[] = {
+ {
+ .name = "fsia-dai",
+ .playback = {
+ .rates = FSI_RATES,
+ .formats = FSI_FMTS,
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .rates = FSI_RATES,
+ .formats = FSI_FMTS,
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .ops = &fsi_dai_ops,
+ },
+ {
+ .name = "fsib-dai",
+ .playback = {
+ .rates = FSI_RATES,
+ .formats = FSI_FMTS,
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .rates = FSI_RATES,
+ .formats = FSI_FMTS,
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .ops = &fsi_dai_ops,
+ },
+};
+
+static struct snd_soc_platform_driver fsi_soc_platform = {
+ .ops = &fsi_pcm_ops,
+ .pcm_new = fsi_pcm_new,
+ .pcm_free = fsi_pcm_free,
+};
+
+/*
+ * platform function
+ */
+
+static int fsi_probe(struct platform_device *pdev)
+{
+ struct fsi_master *master;
+ const struct platform_device_id *id_entry;
+ struct resource *res;
+ unsigned int irq;
+ int ret;
+
+ id_entry = pdev->id_entry;
+ if (!id_entry) {
+ dev_err(&pdev->dev, "unknown fsi device\n");
+ return -ENODEV;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (!res || (int)irq <= 0) {
+ dev_err(&pdev->dev, "Not enough FSI platform resources.\n");
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ master = kzalloc(sizeof(*master), GFP_KERNEL);
+ if (!master) {
+ dev_err(&pdev->dev, "Could not allocate master\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ master->base = ioremap_nocache(res->start, resource_size(res));
+ if (!master->base) {
+ ret = -ENXIO;
+ dev_err(&pdev->dev, "Unable to ioremap FSI registers.\n");
+ goto exit_kfree;
+ }
+
+ /* master setting */
+ master->irq = irq;
+ master->info = pdev->dev.platform_data;
+ master->core = (struct fsi_core *)id_entry->driver_data;
+ spin_lock_init(&master->lock);
+
+ /* FSI A setting */
+ master->fsia.base = master->base;
+ master->fsia.master = master;
+
+ /* FSI B setting */
+ master->fsib.base = master->base + 0x40;
+ master->fsib.master = master;
+
+ pm_runtime_enable(&pdev->dev);
+ dev_set_drvdata(&pdev->dev, master);
+
+ fsi_module_init(master, &pdev->dev);
+
+ ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED,
+ id_entry->name, master);
+ if (ret) {
+ dev_err(&pdev->dev, "irq request err\n");
+ goto exit_iounmap;
+ }
+
+ ret = snd_soc_register_platform(&pdev->dev, &fsi_soc_platform);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot snd soc register\n");
+ goto exit_free_irq;
+ }
+
+ ret = snd_soc_register_dais(&pdev->dev, fsi_soc_dai,
+ ARRAY_SIZE(fsi_soc_dai));
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot snd dai register\n");
+ goto exit_snd_soc;
+ }
+
+ return ret;
+
+exit_snd_soc:
+ snd_soc_unregister_platform(&pdev->dev);
+exit_free_irq:
+ free_irq(irq, master);
+exit_iounmap:
+ iounmap(master->base);
+ pm_runtime_disable(&pdev->dev);
+exit_kfree:
+ kfree(master);
+ master = NULL;
+exit:
+ return ret;
+}
+
+static int fsi_remove(struct platform_device *pdev)
+{
+ struct fsi_master *master;
+
+ master = dev_get_drvdata(&pdev->dev);
+
+ fsi_module_kill(master, &pdev->dev);
+
+ free_irq(master->irq, master);
+ pm_runtime_disable(&pdev->dev);
+
+ snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(fsi_soc_dai));
+ snd_soc_unregister_platform(&pdev->dev);
+
+ iounmap(master->base);
+ kfree(master);
+
+ return 0;
+}
+
+static void __fsi_suspend(struct fsi_priv *fsi,
+ struct device *dev,
+ set_rate_func set_rate)
+{
+ fsi->saved_do_fmt = fsi_reg_read(fsi, DO_FMT);
+ fsi->saved_di_fmt = fsi_reg_read(fsi, DI_FMT);
+ fsi->saved_ckg1 = fsi_reg_read(fsi, CKG1);
+ fsi->saved_ckg2 = fsi_reg_read(fsi, CKG2);
+ fsi->saved_out_sel = fsi_reg_read(fsi, OUT_SEL);
+
+ if (fsi_is_clk_master(fsi))
+ set_rate(dev, fsi_is_port_a(fsi), fsi->rate, 0);
+}
+
+static void __fsi_resume(struct fsi_priv *fsi,
+ struct device *dev,
+ set_rate_func set_rate)
+{
+ fsi_reg_write(fsi, DO_FMT, fsi->saved_do_fmt);
+ fsi_reg_write(fsi, DI_FMT, fsi->saved_di_fmt);
+ fsi_reg_write(fsi, CKG1, fsi->saved_ckg1);
+ fsi_reg_write(fsi, CKG2, fsi->saved_ckg2);
+ fsi_reg_write(fsi, OUT_SEL, fsi->saved_out_sel);
+
+ if (fsi_is_clk_master(fsi))
+ set_rate(dev, fsi_is_port_a(fsi), fsi->rate, 1);
+}
+
+static int fsi_suspend(struct device *dev)
+{
+ struct fsi_master *master = dev_get_drvdata(dev);
+ set_rate_func set_rate = fsi_get_info_set_rate(master);
+
+ pm_runtime_get_sync(dev);
+
+ __fsi_suspend(&master->fsia, dev, set_rate);
+ __fsi_suspend(&master->fsib, dev, set_rate);
+
+ master->saved_a_mclk = fsi_core_read(master, a_mclk);
+ master->saved_b_mclk = fsi_core_read(master, b_mclk);
+ master->saved_iemsk = fsi_core_read(master, iemsk);
+ master->saved_imsk = fsi_core_read(master, imsk);
+ master->saved_clk_rst = fsi_master_read(master, CLK_RST);
+ master->saved_soft_rst = fsi_master_read(master, SOFT_RST);
+
+ fsi_module_kill(master, dev);
+
+ pm_runtime_put_sync(dev);
+
+ return 0;
+}
+
+static int fsi_resume(struct device *dev)
+{
+ struct fsi_master *master = dev_get_drvdata(dev);
+ set_rate_func set_rate = fsi_get_info_set_rate(master);
+
+ pm_runtime_get_sync(dev);
+
+ fsi_module_init(master, dev);
+
+ fsi_master_mask_set(master, SOFT_RST, 0xffff, master->saved_soft_rst);
+ fsi_master_mask_set(master, CLK_RST, 0xffff, master->saved_clk_rst);
+ fsi_core_mask_set(master, a_mclk, 0xffff, master->saved_a_mclk);
+ fsi_core_mask_set(master, b_mclk, 0xffff, master->saved_b_mclk);
+ fsi_core_mask_set(master, iemsk, 0xffff, master->saved_iemsk);
+ fsi_core_mask_set(master, imsk, 0xffff, master->saved_imsk);
+
+ __fsi_resume(&master->fsia, dev, set_rate);
+ __fsi_resume(&master->fsib, dev, set_rate);
+
+ pm_runtime_put_sync(dev);
+
+ return 0;
+}
+
+static int fsi_runtime_nop(struct device *dev)
+{
+ /* Runtime PM callback shared between ->runtime_suspend()
+ * and ->runtime_resume(). Simply returns success.
+ *
+ * This driver re-initializes all registers after
+ * pm_runtime_get_sync() anyway so there is no need
+ * to save and restore registers here.
+ */
+ return 0;
+}
+
+static struct dev_pm_ops fsi_pm_ops = {
+ .suspend = fsi_suspend,
+ .resume = fsi_resume,
+ .runtime_suspend = fsi_runtime_nop,
+ .runtime_resume = fsi_runtime_nop,
+};
+
+static struct fsi_core fsi1_core = {
+ .ver = 1,
+
+ /* Interrupt */
+ .int_st = INT_ST,
+ .iemsk = IEMSK,
+ .imsk = IMSK,
+};
+
+static struct fsi_core fsi2_core = {
+ .ver = 2,
+
+ /* Interrupt */
+ .int_st = CPU_INT_ST,
+ .iemsk = CPU_IEMSK,
+ .imsk = CPU_IMSK,
+ .a_mclk = A_MST_CTLR,
+ .b_mclk = B_MST_CTLR,
+};
+
+static struct platform_device_id fsi_id_table[] = {
+ { "sh_fsi", (kernel_ulong_t)&fsi1_core },
+ { "sh_fsi2", (kernel_ulong_t)&fsi2_core },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, fsi_id_table);
+
+static struct platform_driver fsi_driver = {
+ .driver = {
+ .name = "fsi-pcm-audio",
+ .pm = &fsi_pm_ops,
+ },
+ .probe = fsi_probe,
+ .remove = fsi_remove,
+ .id_table = fsi_id_table,
+};
+
+static int __init fsi_mobile_init(void)
+{
+ return platform_driver_register(&fsi_driver);
+}
+
+static void __exit fsi_mobile_exit(void)
+{
+ platform_driver_unregister(&fsi_driver);
+}
+
+module_init(fsi_mobile_init);
+module_exit(fsi_mobile_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SuperH onchip FSI audio driver");
+MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
+MODULE_ALIAS("platform:fsi-pcm-audio");
diff --git a/sound/soc/sh/hac.c b/sound/soc/sh/hac.c
new file mode 100644
index 00000000..c87e3ff2
--- /dev/null
+++ b/sound/soc/sh/hac.c
@@ -0,0 +1,349 @@
+/*
+ * Hitachi Audio Controller (AC97) support for SH7760/SH7780
+ *
+ * Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ * licensed under the terms outlined in the file COPYING at the root
+ * of the linux kernel sources.
+ *
+ * dont forget to set IPSEL/OMSEL register bits (in your board code) to
+ * enable HAC output pins!
+ */
+
+/* BIG FAT FIXME: although the SH7760 has 2 independent AC97 units, only
+ * the FIRST can be used since ASoC does not pass any information to the
+ * ac97_read/write() functions regarding WHICH unit to use. You'll have
+ * to edit the code a bit to use the other AC97 unit. --mlau
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+/* regs and bits */
+#define HACCR 0x08
+#define HACCSAR 0x20
+#define HACCSDR 0x24
+#define HACPCML 0x28
+#define HACPCMR 0x2C
+#define HACTIER 0x50
+#define HACTSR 0x54
+#define HACRIER 0x58
+#define HACRSR 0x5C
+#define HACACR 0x60
+
+#define CR_CR (1 << 15) /* "codec-ready" indicator */
+#define CR_CDRT (1 << 11) /* cold reset */
+#define CR_WMRT (1 << 10) /* warm reset */
+#define CR_B9 (1 << 9) /* the mysterious "bit 9" */
+#define CR_ST (1 << 5) /* AC97 link start bit */
+
+#define CSAR_RD (1 << 19) /* AC97 data read bit */
+#define CSAR_WR (0)
+
+#define TSR_CMDAMT (1 << 31)
+#define TSR_CMDDMT (1 << 30)
+
+#define RSR_STARY (1 << 22)
+#define RSR_STDRY (1 << 21)
+
+#define ACR_DMARX16 (1 << 30)
+#define ACR_DMATX16 (1 << 29)
+#define ACR_TX12ATOM (1 << 26)
+#define ACR_DMARX20 ((1 << 24) | (1 << 22))
+#define ACR_DMATX20 ((1 << 23) | (1 << 21))
+
+#define CSDR_SHIFT 4
+#define CSDR_MASK (0xffff << CSDR_SHIFT)
+#define CSAR_SHIFT 12
+#define CSAR_MASK (0x7f << CSAR_SHIFT)
+
+#define AC97_WRITE_RETRY 1
+#define AC97_READ_RETRY 5
+
+/* manual-suggested AC97 codec access timeouts (us) */
+#define TMO_E1 500 /* 21 < E1 < 1000 */
+#define TMO_E2 13 /* 13 < E2 */
+#define TMO_E3 21 /* 21 < E3 */
+#define TMO_E4 500 /* 21 < E4 < 1000 */
+
+struct hac_priv {
+ unsigned long mmio; /* HAC base address */
+} hac_cpu_data[] = {
+#if defined(CONFIG_CPU_SUBTYPE_SH7760)
+ {
+ .mmio = 0xFE240000,
+ },
+ {
+ .mmio = 0xFE250000,
+ },
+#elif defined(CONFIG_CPU_SUBTYPE_SH7780)
+ {
+ .mmio = 0xFFE40000,
+ },
+#else
+#error "Unsupported SuperH SoC"
+#endif
+};
+
+#define HACREG(reg) (*(unsigned long *)(hac->mmio + (reg)))
+
+/*
+ * AC97 read/write flow as outlined in the SH7760 manual (pages 903-906)
+ */
+static int hac_get_codec_data(struct hac_priv *hac, unsigned short r,
+ unsigned short *v)
+{
+ unsigned int to1, to2, i;
+ unsigned short adr;
+
+ for (i = AC97_READ_RETRY; i; i--) {
+ *v = 0;
+ /* wait for HAC to receive something from the codec */
+ for (to1 = TMO_E4;
+ to1 && !(HACREG(HACRSR) & RSR_STARY);
+ --to1)
+ udelay(1);
+ for (to2 = TMO_E4;
+ to2 && !(HACREG(HACRSR) & RSR_STDRY);
+ --to2)
+ udelay(1);
+
+ if (!to1 && !to2)
+ return 0; /* codec comm is down */
+
+ adr = ((HACREG(HACCSAR) & CSAR_MASK) >> CSAR_SHIFT);
+ *v = ((HACREG(HACCSDR) & CSDR_MASK) >> CSDR_SHIFT);
+
+ HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY);
+
+ if (r == adr)
+ break;
+
+ /* manual says: wait at least 21 usec before retrying */
+ udelay(21);
+ }
+ HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY);
+ return i;
+}
+
+static unsigned short hac_read_codec_aux(struct hac_priv *hac,
+ unsigned short reg)
+{
+ unsigned short val;
+ unsigned int i, to;
+
+ for (i = AC97_READ_RETRY; i; i--) {
+ /* send_read_request */
+ local_irq_disable();
+ HACREG(HACTSR) &= ~(TSR_CMDAMT);
+ HACREG(HACCSAR) = (reg << CSAR_SHIFT) | CSAR_RD;
+ local_irq_enable();
+
+ for (to = TMO_E3;
+ to && !(HACREG(HACTSR) & TSR_CMDAMT);
+ --to)
+ udelay(1);
+
+ HACREG(HACTSR) &= ~TSR_CMDAMT;
+ val = 0;
+ if (hac_get_codec_data(hac, reg, &val) != 0)
+ break;
+ }
+
+ return i ? val : ~0;
+}
+
+static void hac_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ int unit_id = 0 /* ac97->private_data */;
+ struct hac_priv *hac = &hac_cpu_data[unit_id];
+ unsigned int i, to;
+ /* write_codec_aux */
+ for (i = AC97_WRITE_RETRY; i; i--) {
+ /* send_write_request */
+ local_irq_disable();
+ HACREG(HACTSR) &= ~(TSR_CMDDMT | TSR_CMDAMT);
+ HACREG(HACCSDR) = (val << CSDR_SHIFT);
+ HACREG(HACCSAR) = (reg << CSAR_SHIFT) & (~CSAR_RD);
+ local_irq_enable();
+
+ /* poll-wait for CMDAMT and CMDDMT */
+ for (to = TMO_E1;
+ to && !(HACREG(HACTSR) & (TSR_CMDAMT|TSR_CMDDMT));
+ --to)
+ udelay(1);
+
+ HACREG(HACTSR) &= ~(TSR_CMDAMT | TSR_CMDDMT);
+ if (to)
+ break;
+ /* timeout, try again */
+ }
+}
+
+static unsigned short hac_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ int unit_id = 0 /* ac97->private_data */;
+ struct hac_priv *hac = &hac_cpu_data[unit_id];
+ return hac_read_codec_aux(hac, reg);
+}
+
+static void hac_ac97_warmrst(struct snd_ac97 *ac97)
+{
+ int unit_id = 0 /* ac97->private_data */;
+ struct hac_priv *hac = &hac_cpu_data[unit_id];
+ unsigned int tmo;
+
+ HACREG(HACCR) = CR_WMRT | CR_ST | CR_B9;
+ msleep(10);
+ HACREG(HACCR) = CR_ST | CR_B9;
+ for (tmo = 1000; (tmo > 0) && !(HACREG(HACCR) & CR_CR); tmo--)
+ udelay(1);
+
+ if (!tmo)
+ printk(KERN_INFO "hac: reset: AC97 link down!\n");
+ /* settings this bit lets us have a conversation with codec */
+ HACREG(HACACR) |= ACR_TX12ATOM;
+}
+
+static void hac_ac97_coldrst(struct snd_ac97 *ac97)
+{
+ int unit_id = 0 /* ac97->private_data */;
+ struct hac_priv *hac;
+ hac = &hac_cpu_data[unit_id];
+
+ HACREG(HACCR) = 0;
+ HACREG(HACCR) = CR_CDRT | CR_ST | CR_B9;
+ msleep(10);
+ hac_ac97_warmrst(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = hac_ac97_read,
+ .write = hac_ac97_write,
+ .reset = hac_ac97_coldrst,
+ .warm_reset = hac_ac97_warmrst,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int hac_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct hac_priv *hac = &hac_cpu_data[dai->id];
+ int d = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+ switch (params->msbits) {
+ case 16:
+ HACREG(HACACR) |= d ? ACR_DMARX16 : ACR_DMATX16;
+ HACREG(HACACR) &= d ? ~ACR_DMARX20 : ~ACR_DMATX20;
+ break;
+ case 20:
+ HACREG(HACACR) &= d ? ~ACR_DMARX16 : ~ACR_DMATX16;
+ HACREG(HACACR) |= d ? ACR_DMARX20 : ACR_DMATX20;
+ break;
+ default:
+ pr_debug("hac: invalid depth %d bit\n", params->msbits);
+ return -EINVAL;
+ break;
+ }
+
+ return 0;
+}
+
+#define AC97_RATES \
+ SNDRV_PCM_RATE_8000_192000
+
+#define AC97_FMTS \
+ SNDRV_PCM_FMTBIT_S16_LE
+
+static struct snd_soc_dai_ops hac_dai_ops = {
+ .hw_params = hac_hw_params,
+};
+
+static struct snd_soc_dai_driver sh4_hac_dai[] = {
+{
+ .name = "hac-dai.0",
+ .ac97_control = 1,
+ .playback = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .capture = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = &hac_dai_ops,
+},
+#ifdef CONFIG_CPU_SUBTYPE_SH7760
+{
+ .name = "hac-dai.1",
+ .id = 1,
+ .playback = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .capture = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = &hac_dai_ops,
+
+},
+#endif
+};
+
+static int __devinit hac_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_dais(&pdev->dev, sh4_hac_dai,
+ ARRAY_SIZE(sh4_hac_dai));
+}
+
+static int __devexit hac_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(sh4_hac_dai));
+ return 0;
+}
+
+static struct platform_driver hac_pcm_driver = {
+ .driver = {
+ .name = "hac-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = hac_soc_platform_probe,
+ .remove = __devexit_p(hac_soc_platform_remove),
+};
+
+static int __init sh4_hac_pcm_init(void)
+{
+ return platform_driver_register(&hac_pcm_driver);
+}
+module_init(sh4_hac_pcm_init);
+
+static void __exit sh4_hac_pcm_exit(void)
+{
+ platform_driver_unregister(&hac_pcm_driver);
+}
+module_exit(sh4_hac_pcm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SuperH onchip HAC (AC97) audio driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/sh/migor.c b/sound/soc/sh/migor.c
new file mode 100644
index 00000000..6088a6a3
--- /dev/null
+++ b/sound/soc/sh/migor.c
@@ -0,0 +1,224 @@
+/*
+ * ALSA SoC driver for Migo-R
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clkdev.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+
+#include <asm/clock.h>
+
+#include <cpu/sh7722.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include "../codecs/wm8978.h"
+#include "siu.h"
+
+/* Default 8000Hz sampling frequency */
+static unsigned long codec_freq = 8000 * 512;
+
+static unsigned int use_count;
+
+/* External clock, sourced from the codec at the SIUMCKB pin */
+static unsigned long siumckb_recalc(struct clk *clk)
+{
+ return codec_freq;
+}
+
+static struct clk_ops siumckb_clk_ops = {
+ .recalc = siumckb_recalc,
+};
+
+static struct clk siumckb_clk = {
+ .ops = &siumckb_clk_ops,
+ .rate = 0, /* initialised at run-time */
+};
+
+static struct clk_lookup *siumckb_lookup;
+
+static int migor_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int ret;
+ unsigned int rate = params_rate(params);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ codec_freq = rate * 512;
+ /*
+ * This propagates the parent frequency change to children and
+ * recalculates the frequency table
+ */
+ clk_set_rate(&siumckb_clk, codec_freq);
+ dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq);
+
+ ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, SIU_CLKB_EXT,
+ codec_freq / 2, SND_SOC_CLOCK_IN);
+
+ if (!ret)
+ use_count++;
+
+ return ret;
+}
+
+static int migor_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+ if (use_count) {
+ use_count--;
+
+ if (!use_count)
+ snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0,
+ SND_SOC_CLOCK_IN);
+ } else {
+ dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n");
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops migor_dai_ops = {
+ .hw_params = migor_hw_params,
+ .hw_free = migor_hw_free,
+};
+
+static const struct snd_soc_dapm_widget migor_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Onboard Microphone", NULL),
+ SND_SOC_DAPM_MIC("External Microphone", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Headphone output connected to LHP/RHP, enable OUT4 for VMID */
+ { "Headphone", NULL, "OUT4 VMID" },
+ { "OUT4 VMID", NULL, "LHP" },
+ { "OUT4 VMID", NULL, "RHP" },
+
+ /* On-board microphone */
+ { "RMICN", NULL, "Mic Bias" },
+ { "RMICP", NULL, "Mic Bias" },
+ { "Mic Bias", NULL, "Onboard Microphone" },
+
+ /* External microphone */
+ { "LMICN", NULL, "Mic Bias" },
+ { "LMICP", NULL, "Mic Bias" },
+ { "Mic Bias", NULL, "External Microphone" },
+};
+
+static int migor_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_new_controls(dapm, migor_dapm_widgets,
+ ARRAY_SIZE(migor_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+/* migor digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link migor_dai = {
+ .name = "wm8978",
+ .stream_name = "WM8978",
+ .cpu_dai_name = "siu-i2s-dai",
+ .codec_dai_name = "wm8978-hifi",
+ .platform_name = "siu-pcm-audio",
+ .codec_name = "wm8978.0-001a",
+ .ops = &migor_dai_ops,
+ .init = migor_dai_init,
+};
+
+/* migor audio machine driver */
+static struct snd_soc_card snd_soc_migor = {
+ .name = "Migo-R",
+ .dai_link = &migor_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *migor_snd_device;
+
+static int __init migor_init(void)
+{
+ int ret;
+
+ ret = clk_register(&siumckb_clk);
+ if (ret < 0)
+ return ret;
+
+ siumckb_lookup = clkdev_alloc(&siumckb_clk, "siumckb_clk", NULL);
+ if (!siumckb_lookup) {
+ ret = -ENOMEM;
+ goto eclkdevalloc;
+ }
+ clkdev_add(siumckb_lookup);
+
+ /* Port number used on this machine: port B */
+ migor_snd_device = platform_device_alloc("soc-audio", 1);
+ if (!migor_snd_device) {
+ ret = -ENOMEM;
+ goto epdevalloc;
+ }
+
+ platform_set_drvdata(migor_snd_device, &snd_soc_migor);
+
+ ret = platform_device_add(migor_snd_device);
+ if (ret)
+ goto epdevadd;
+
+ return 0;
+
+epdevadd:
+ platform_device_put(migor_snd_device);
+epdevalloc:
+ clkdev_drop(siumckb_lookup);
+eclkdevalloc:
+ clk_unregister(&siumckb_clk);
+ return ret;
+}
+
+static void __exit migor_exit(void)
+{
+ clkdev_drop(siumckb_lookup);
+ clk_unregister(&siumckb_clk);
+ platform_device_unregister(migor_snd_device);
+}
+
+module_init(migor_init);
+module_exit(migor_exit);
+
+MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
+MODULE_DESCRIPTION("ALSA SoC Migor");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/sh/sh7760-ac97.c b/sound/soc/sh/sh7760-ac97.c
new file mode 100644
index 00000000..917d3cea
--- /dev/null
+++ b/sound/soc/sh/sh7760-ac97.c
@@ -0,0 +1,83 @@
+/*
+ * Generic AC97 sound support for SH7760
+ *
+ * (c) 2007 Manuel Lauss
+ *
+ * Licensed under the GPLv2.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <asm/io.h>
+
+#define IPSEL 0xFE400034
+
+/* platform specific structs can be declared here */
+extern struct snd_soc_dai_driver sh4_hac_dai[2];
+extern struct snd_soc_platform_driver sh7760_soc_platform;
+
+static int machine_init(struct snd_soc_pcm_runtime *rtd)
+{
+ snd_soc_dapm_sync(&rtd->codec->dapm);
+ return 0;
+}
+
+static struct snd_soc_dai_link sh7760_ac97_dai = {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "hac-dai.0", /* HAC0 */
+ .codec_dai_name = "ac97-hifi",
+ .platform_name = "sh7760-pcm-audio",
+ .codec_name = "ac97-codec",
+ .init = machine_init,
+ .ops = NULL,
+};
+
+static struct snd_soc_card sh7760_ac97_soc_machine = {
+ .name = "SH7760 AC97",
+ .dai_link = &sh7760_ac97_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *sh7760_ac97_snd_device;
+
+static int __init sh7760_ac97_init(void)
+{
+ int ret;
+ unsigned short ipsel;
+
+ /* enable both AC97 controllers in pinmux reg */
+ ipsel = __raw_readw(IPSEL);
+ __raw_writew(ipsel | (3 << 10), IPSEL);
+
+ ret = -ENOMEM;
+ sh7760_ac97_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!sh7760_ac97_snd_device)
+ goto out;
+
+ platform_set_drvdata(sh7760_ac97_snd_device,
+ &sh7760_ac97_soc_machine);
+ ret = platform_device_add(sh7760_ac97_snd_device);
+
+ if (ret)
+ platform_device_put(sh7760_ac97_snd_device);
+
+out:
+ return ret;
+}
+
+static void __exit sh7760_ac97_exit(void)
+{
+ platform_device_unregister(sh7760_ac97_snd_device);
+}
+
+module_init(sh7760_ac97_init);
+module_exit(sh7760_ac97_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Generic SH7760 AC97 sound machine");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/sh/siu.h b/sound/soc/sh/siu.h
new file mode 100644
index 00000000..83c3430a
--- /dev/null
+++ b/sound/soc/sh/siu.h
@@ -0,0 +1,194 @@
+/*
+ * siu.h - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral.
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2006 Carlos Munoz <carlos@kenati.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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef SIU_H
+#define SIU_H
+
+/* Common kernel and user-space firmware-building defines and types */
+
+#define YRAM0_SIZE (0x0040 / 4) /* 16 */
+#define YRAM1_SIZE (0x0080 / 4) /* 32 */
+#define YRAM2_SIZE (0x0040 / 4) /* 16 */
+#define YRAM3_SIZE (0x0080 / 4) /* 32 */
+#define YRAM4_SIZE (0x0080 / 4) /* 32 */
+#define YRAM_DEF_SIZE (YRAM0_SIZE + YRAM1_SIZE + YRAM2_SIZE + \
+ YRAM3_SIZE + YRAM4_SIZE)
+#define YRAM_FIR_SIZE (0x0400 / 4) /* 256 */
+#define YRAM_IIR_SIZE (0x0200 / 4) /* 128 */
+
+#define XRAM0_SIZE (0x0400 / 4) /* 256 */
+#define XRAM1_SIZE (0x0200 / 4) /* 128 */
+#define XRAM2_SIZE (0x0200 / 4) /* 128 */
+
+/* PRAM program array size */
+#define PRAM0_SIZE (0x0100 / 4) /* 64 */
+#define PRAM1_SIZE ((0x2000 - 0x0100) / 4) /* 1984 */
+
+#include <linux/types.h>
+
+struct siu_spb_param {
+ __u32 ab1a; /* input FIFO address */
+ __u32 ab0a; /* output FIFO address */
+ __u32 dir; /* 0=the ather except CPUOUTPUT, 1=CPUINPUT */
+ __u32 event; /* SPB program starting conditions */
+ __u32 stfifo; /* STFIFO register setting value */
+ __u32 trdat; /* TRDAT register setting value */
+};
+
+struct siu_firmware {
+ __u32 yram_fir_coeff[YRAM_FIR_SIZE];
+ __u32 pram0[PRAM0_SIZE];
+ __u32 pram1[PRAM1_SIZE];
+ __u32 yram0[YRAM0_SIZE];
+ __u32 yram1[YRAM1_SIZE];
+ __u32 yram2[YRAM2_SIZE];
+ __u32 yram3[YRAM3_SIZE];
+ __u32 yram4[YRAM4_SIZE];
+ __u32 spbpar_num;
+ struct siu_spb_param spbpar[32];
+};
+
+#ifdef __KERNEL__
+
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/sh_dma.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#define SIU_PERIOD_BYTES_MAX 8192 /* DMA transfer/period size */
+#define SIU_PERIOD_BYTES_MIN 256 /* DMA transfer/period size */
+#define SIU_PERIODS_MAX 64 /* Max periods in buffer */
+#define SIU_PERIODS_MIN 4 /* Min periods in buffer */
+#define SIU_BUFFER_BYTES_MAX (SIU_PERIOD_BYTES_MAX * SIU_PERIODS_MAX)
+
+/* SIU ports: only one can be used at a time */
+enum {
+ SIU_PORT_A,
+ SIU_PORT_B,
+ SIU_PORT_NUM,
+};
+
+/* SIU clock configuration */
+enum {
+ SIU_CLKA_PLL,
+ SIU_CLKA_EXT,
+ SIU_CLKB_PLL,
+ SIU_CLKB_EXT
+};
+
+struct device;
+struct siu_info {
+ struct device *dev;
+ int port_id;
+ u32 __iomem *pram;
+ u32 __iomem *xram;
+ u32 __iomem *yram;
+ u32 __iomem *reg;
+ struct siu_firmware fw;
+};
+
+struct siu_stream {
+ struct tasklet_struct tasklet;
+ struct snd_pcm_substream *substream;
+ snd_pcm_format_t format;
+ size_t buf_bytes;
+ size_t period_bytes;
+ int cur_period; /* Period currently in dma */
+ u32 volume;
+ snd_pcm_sframes_t xfer_cnt; /* Number of frames */
+ u8 rw_flg; /* transfer status */
+ /* DMA status */
+ struct dma_chan *chan; /* DMA channel */
+ struct dma_async_tx_descriptor *tx_desc;
+ dma_cookie_t cookie;
+ struct sh_dmae_slave param;
+};
+
+struct siu_port {
+ unsigned long play_cap; /* Used to track full duplex */
+ struct snd_pcm *pcm;
+ struct siu_stream playback;
+ struct siu_stream capture;
+ u32 stfifo; /* STFIFO value from firmware */
+ u32 trdat; /* TRDAT value from firmware */
+};
+
+extern struct siu_port *siu_ports[SIU_PORT_NUM];
+
+static inline struct siu_port *siu_port_info(struct snd_pcm_substream *substream)
+{
+ struct platform_device *pdev =
+ to_platform_device(substream->pcm->card->dev);
+ return siu_ports[pdev->id];
+}
+
+/* Register access */
+static inline void siu_write32(u32 __iomem *addr, u32 val)
+{
+ __raw_writel(val, addr);
+}
+
+static inline u32 siu_read32(u32 __iomem *addr)
+{
+ return __raw_readl(addr);
+}
+
+/* SIU registers */
+#define SIU_IFCTL (0x000 / sizeof(u32))
+#define SIU_SRCTL (0x004 / sizeof(u32))
+#define SIU_SFORM (0x008 / sizeof(u32))
+#define SIU_CKCTL (0x00c / sizeof(u32))
+#define SIU_TRDAT (0x010 / sizeof(u32))
+#define SIU_STFIFO (0x014 / sizeof(u32))
+#define SIU_DPAK (0x01c / sizeof(u32))
+#define SIU_CKREV (0x020 / sizeof(u32))
+#define SIU_EVNTC (0x028 / sizeof(u32))
+#define SIU_SBCTL (0x040 / sizeof(u32))
+#define SIU_SBPSET (0x044 / sizeof(u32))
+#define SIU_SBFSTS (0x068 / sizeof(u32))
+#define SIU_SBDVCA (0x06c / sizeof(u32))
+#define SIU_SBDVCB (0x070 / sizeof(u32))
+#define SIU_SBACTIV (0x074 / sizeof(u32))
+#define SIU_DMAIA (0x090 / sizeof(u32))
+#define SIU_DMAIB (0x094 / sizeof(u32))
+#define SIU_DMAOA (0x098 / sizeof(u32))
+#define SIU_DMAOB (0x09c / sizeof(u32))
+#define SIU_DMAML (0x0a0 / sizeof(u32))
+#define SIU_SPSTS (0x0cc / sizeof(u32))
+#define SIU_SPCTL (0x0d0 / sizeof(u32))
+#define SIU_BRGASEL (0x100 / sizeof(u32))
+#define SIU_BRRA (0x104 / sizeof(u32))
+#define SIU_BRGBSEL (0x108 / sizeof(u32))
+#define SIU_BRRB (0x10c / sizeof(u32))
+
+extern struct snd_soc_platform_driver siu_platform;
+extern struct siu_info *siu_i2s_data;
+
+int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card);
+void siu_free_port(struct siu_port *port_info);
+
+#endif
+
+#endif /* SIU_H */
diff --git a/sound/soc/sh/siu_dai.c b/sound/soc/sh/siu_dai.c
new file mode 100644
index 00000000..4973c293
--- /dev/null
+++ b/sound/soc/sh/siu_dai.c
@@ -0,0 +1,869 @@
+/*
+ * siu_dai.c - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral.
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2006 Carlos Munoz <carlos@kenati.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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <asm/clock.h>
+#include <asm/siu.h>
+
+#include <sound/control.h>
+#include <sound/soc.h>
+
+#include "siu.h"
+
+/* Board specifics */
+#if defined(CONFIG_CPU_SUBTYPE_SH7722)
+# define SIU_MAX_VOLUME 0x1000
+#else
+# define SIU_MAX_VOLUME 0x7fff
+#endif
+
+#define PRAM_SIZE 0x2000
+#define XRAM_SIZE 0x800
+#define YRAM_SIZE 0x800
+
+#define XRAM_OFFSET 0x4000
+#define YRAM_OFFSET 0x6000
+#define REG_OFFSET 0xc000
+
+#define PLAYBACK_ENABLED 1
+#define CAPTURE_ENABLED 2
+
+#define VOLUME_CAPTURE 0
+#define VOLUME_PLAYBACK 1
+#define DFLT_VOLUME_LEVEL 0x08000800
+
+/*
+ * SPDIF is only available on port A and on some SIU implementations it is only
+ * available for input. Due to the lack of hardware to test it, SPDIF is left
+ * disabled in this driver version
+ */
+struct format_flag {
+ u32 i2s;
+ u32 pcm;
+ u32 spdif;
+ u32 mask;
+};
+
+struct port_flag {
+ struct format_flag playback;
+ struct format_flag capture;
+};
+
+struct siu_info *siu_i2s_data;
+
+static struct port_flag siu_flags[SIU_PORT_NUM] = {
+ [SIU_PORT_A] = {
+ .playback = {
+ .i2s = 0x50000000,
+ .pcm = 0x40000000,
+ .spdif = 0x80000000, /* not on all SIU versions */
+ .mask = 0xd0000000,
+ },
+ .capture = {
+ .i2s = 0x05000000,
+ .pcm = 0x04000000,
+ .spdif = 0x08000000,
+ .mask = 0x0d000000,
+ },
+ },
+ [SIU_PORT_B] = {
+ .playback = {
+ .i2s = 0x00500000,
+ .pcm = 0x00400000,
+ .spdif = 0, /* impossible - turn off */
+ .mask = 0x00500000,
+ },
+ .capture = {
+ .i2s = 0x00050000,
+ .pcm = 0x00040000,
+ .spdif = 0, /* impossible - turn off */
+ .mask = 0x00050000,
+ },
+ },
+};
+
+static void siu_dai_start(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+
+ dev_dbg(port_info->pcm->card->dev, "%s\n", __func__);
+
+ /* Turn on SIU clock */
+ pm_runtime_get_sync(info->dev);
+
+ /* Issue software reset to siu */
+ siu_write32(base + SIU_SRCTL, 0);
+
+ /* Wait for the reset to take effect */
+ udelay(1);
+
+ port_info->stfifo = 0;
+ port_info->trdat = 0;
+
+ /* portA, portB, SIU operate */
+ siu_write32(base + SIU_SRCTL, 0x301);
+
+ /* portA=256fs, portB=256fs */
+ siu_write32(base + SIU_CKCTL, 0x40400000);
+
+ /* portA's BRG does not divide SIUCKA */
+ siu_write32(base + SIU_BRGASEL, 0);
+ siu_write32(base + SIU_BRRA, 0);
+
+ /* portB's BRG divides SIUCKB by half */
+ siu_write32(base + SIU_BRGBSEL, 1);
+ siu_write32(base + SIU_BRRB, 0);
+
+ siu_write32(base + SIU_IFCTL, 0x44440000);
+
+ /* portA: 32 bit/fs, master; portB: 32 bit/fs, master */
+ siu_write32(base + SIU_SFORM, 0x0c0c0000);
+
+ /*
+ * Volume levels: looks like the DSP firmware implements volume controls
+ * differently from what's described in the datasheet
+ */
+ siu_write32(base + SIU_SBDVCA, port_info->playback.volume);
+ siu_write32(base + SIU_SBDVCB, port_info->capture.volume);
+}
+
+static void siu_dai_stop(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+
+ /* SIU software reset */
+ siu_write32(base + SIU_SRCTL, 0);
+
+ /* Turn off SIU clock */
+ pm_runtime_put_sync(info->dev);
+}
+
+static void siu_dai_spbAselect(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_data;
+ struct siu_firmware *fw = &info->fw;
+ u32 *ydef = fw->yram0;
+ u32 idx;
+
+ /* path A use */
+ if (!info->port_id)
+ idx = 1; /* portA */
+ else
+ idx = 2; /* portB */
+
+ ydef[0] = (fw->spbpar[idx].ab1a << 16) |
+ (fw->spbpar[idx].ab0a << 8) |
+ (fw->spbpar[idx].dir << 7) | 3;
+ ydef[1] = fw->yram0[1]; /* 0x03000300 */
+ ydef[2] = (16 / 2) << 24;
+ ydef[3] = fw->yram0[3]; /* 0 */
+ ydef[4] = fw->yram0[4]; /* 0 */
+ ydef[7] = fw->spbpar[idx].event;
+ port_info->stfifo |= fw->spbpar[idx].stfifo;
+ port_info->trdat |= fw->spbpar[idx].trdat;
+}
+
+static void siu_dai_spbBselect(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_data;
+ struct siu_firmware *fw = &info->fw;
+ u32 *ydef = fw->yram0;
+ u32 idx;
+
+ /* path B use */
+ if (!info->port_id)
+ idx = 7; /* portA */
+ else
+ idx = 8; /* portB */
+
+ ydef[5] = (fw->spbpar[idx].ab1a << 16) |
+ (fw->spbpar[idx].ab0a << 8) | 1;
+ ydef[6] = fw->spbpar[idx].event;
+ port_info->stfifo |= fw->spbpar[idx].stfifo;
+ port_info->trdat |= fw->spbpar[idx].trdat;
+}
+
+static void siu_dai_open(struct siu_stream *siu_stream)
+{
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+ u32 srctl, ifctl;
+
+ srctl = siu_read32(base + SIU_SRCTL);
+ ifctl = siu_read32(base + SIU_IFCTL);
+
+ switch (info->port_id) {
+ case SIU_PORT_A:
+ /* portA operates */
+ srctl |= 0x200;
+ ifctl &= ~0xc2;
+ break;
+ case SIU_PORT_B:
+ /* portB operates */
+ srctl |= 0x100;
+ ifctl &= ~0x31;
+ break;
+ }
+
+ siu_write32(base + SIU_SRCTL, srctl);
+ /* Unmute and configure portA */
+ siu_write32(base + SIU_IFCTL, ifctl);
+}
+
+/*
+ * At the moment only fixed Left-upper, Left-lower, Right-upper, Right-lower
+ * packing is supported
+ */
+static void siu_dai_pcmdatapack(struct siu_stream *siu_stream)
+{
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+ u32 dpak;
+
+ dpak = siu_read32(base + SIU_DPAK);
+
+ switch (info->port_id) {
+ case SIU_PORT_A:
+ dpak &= ~0xc0000000;
+ break;
+ case SIU_PORT_B:
+ dpak &= ~0x00c00000;
+ break;
+ }
+
+ siu_write32(base + SIU_DPAK, dpak);
+}
+
+static int siu_dai_spbstart(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+ struct siu_firmware *fw = &info->fw;
+ u32 *ydef = fw->yram0;
+ int cnt;
+ u32 __iomem *add;
+ u32 *ptr;
+
+ /* Load SPB Program in PRAM */
+ ptr = fw->pram0;
+ add = info->pram;
+ for (cnt = 0; cnt < PRAM0_SIZE; cnt++, add++, ptr++)
+ siu_write32(add, *ptr);
+
+ ptr = fw->pram1;
+ add = info->pram + (0x0100 / sizeof(u32));
+ for (cnt = 0; cnt < PRAM1_SIZE; cnt++, add++, ptr++)
+ siu_write32(add, *ptr);
+
+ /* XRAM initialization */
+ add = info->xram;
+ for (cnt = 0; cnt < XRAM0_SIZE + XRAM1_SIZE + XRAM2_SIZE; cnt++, add++)
+ siu_write32(add, 0);
+
+ /* YRAM variable area initialization */
+ add = info->yram;
+ for (cnt = 0; cnt < YRAM_DEF_SIZE; cnt++, add++)
+ siu_write32(add, ydef[cnt]);
+
+ /* YRAM FIR coefficient area initialization */
+ add = info->yram + (0x0200 / sizeof(u32));
+ for (cnt = 0; cnt < YRAM_FIR_SIZE; cnt++, add++)
+ siu_write32(add, fw->yram_fir_coeff[cnt]);
+
+ /* YRAM IIR coefficient area initialization */
+ add = info->yram + (0x0600 / sizeof(u32));
+ for (cnt = 0; cnt < YRAM_IIR_SIZE; cnt++, add++)
+ siu_write32(add, 0);
+
+ siu_write32(base + SIU_TRDAT, port_info->trdat);
+ port_info->trdat = 0x0;
+
+
+ /* SPB start condition: software */
+ siu_write32(base + SIU_SBACTIV, 0);
+ /* Start SPB */
+ siu_write32(base + SIU_SBCTL, 0xc0000000);
+ /* Wait for program to halt */
+ cnt = 0x10000;
+ while (--cnt && siu_read32(base + SIU_SBCTL) != 0x80000000)
+ cpu_relax();
+
+ if (!cnt)
+ return -EBUSY;
+
+ /* SPB program start address setting */
+ siu_write32(base + SIU_SBPSET, 0x00400000);
+ /* SPB hardware start(FIFOCTL source) */
+ siu_write32(base + SIU_SBACTIV, 0xc0000000);
+
+ return 0;
+}
+
+static void siu_dai_spbstop(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+
+ siu_write32(base + SIU_SBACTIV, 0);
+ /* SPB stop */
+ siu_write32(base + SIU_SBCTL, 0);
+
+ port_info->stfifo = 0;
+}
+
+/* API functions */
+
+/* Playback and capture hardware properties are identical */
+static struct snd_pcm_hardware siu_dai_pcm_hw = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = SIU_BUFFER_BYTES_MAX,
+ .period_bytes_min = SIU_PERIOD_BYTES_MIN,
+ .period_bytes_max = SIU_PERIOD_BYTES_MAX,
+ .periods_min = SIU_PERIODS_MIN,
+ .periods_max = SIU_PERIODS_MAX,
+};
+
+static int siu_dai_info_volume(struct snd_kcontrol *kctrl,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct siu_port *port_info = snd_kcontrol_chip(kctrl);
+
+ dev_dbg(port_info->pcm->card->dev, "%s\n", __func__);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = SIU_MAX_VOLUME;
+
+ return 0;
+}
+
+static int siu_dai_get_volume(struct snd_kcontrol *kctrl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct siu_port *port_info = snd_kcontrol_chip(kctrl);
+ struct device *dev = port_info->pcm->card->dev;
+ u32 vol;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ switch (kctrl->private_value) {
+ case VOLUME_PLAYBACK:
+ /* Playback is always on port 0 */
+ vol = port_info->playback.volume;
+ ucontrol->value.integer.value[0] = vol & 0xffff;
+ ucontrol->value.integer.value[1] = vol >> 16 & 0xffff;
+ break;
+ case VOLUME_CAPTURE:
+ /* Capture is always on port 1 */
+ vol = port_info->capture.volume;
+ ucontrol->value.integer.value[0] = vol & 0xffff;
+ ucontrol->value.integer.value[1] = vol >> 16 & 0xffff;
+ break;
+ default:
+ dev_err(dev, "%s() invalid private_value=%ld\n",
+ __func__, kctrl->private_value);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int siu_dai_put_volume(struct snd_kcontrol *kctrl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct siu_port *port_info = snd_kcontrol_chip(kctrl);
+ struct device *dev = port_info->pcm->card->dev;
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+ u32 new_vol;
+ u32 cur_vol;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (ucontrol->value.integer.value[0] < 0 ||
+ ucontrol->value.integer.value[0] > SIU_MAX_VOLUME ||
+ ucontrol->value.integer.value[1] < 0 ||
+ ucontrol->value.integer.value[1] > SIU_MAX_VOLUME)
+ return -EINVAL;
+
+ new_vol = ucontrol->value.integer.value[0] |
+ ucontrol->value.integer.value[1] << 16;
+
+ /* See comment above - DSP firmware implementation */
+ switch (kctrl->private_value) {
+ case VOLUME_PLAYBACK:
+ /* Playback is always on port 0 */
+ cur_vol = port_info->playback.volume;
+ siu_write32(base + SIU_SBDVCA, new_vol);
+ port_info->playback.volume = new_vol;
+ break;
+ case VOLUME_CAPTURE:
+ /* Capture is always on port 1 */
+ cur_vol = port_info->capture.volume;
+ siu_write32(base + SIU_SBDVCB, new_vol);
+ port_info->capture.volume = new_vol;
+ break;
+ default:
+ dev_err(dev, "%s() invalid private_value=%ld\n",
+ __func__, kctrl->private_value);
+ return -EINVAL;
+ }
+
+ if (cur_vol != new_vol)
+ return 1;
+
+ return 0;
+}
+
+static struct snd_kcontrol_new playback_controls = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .index = 0,
+ .info = siu_dai_info_volume,
+ .get = siu_dai_get_volume,
+ .put = siu_dai_put_volume,
+ .private_value = VOLUME_PLAYBACK,
+};
+
+static struct snd_kcontrol_new capture_controls = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Capture Volume",
+ .index = 0,
+ .info = siu_dai_info_volume,
+ .get = siu_dai_get_volume,
+ .put = siu_dai_put_volume,
+ .private_value = VOLUME_CAPTURE,
+};
+
+int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card)
+{
+ struct device *dev = card->dev;
+ struct snd_kcontrol *kctrl;
+ int ret;
+
+ *port_info = kzalloc(sizeof(**port_info), GFP_KERNEL);
+ if (!*port_info)
+ return -ENOMEM;
+
+ dev_dbg(dev, "%s: port #%d@%p\n", __func__, port, *port_info);
+
+ (*port_info)->playback.volume = DFLT_VOLUME_LEVEL;
+ (*port_info)->capture.volume = DFLT_VOLUME_LEVEL;
+
+ /*
+ * Add mixer support. The SPB is used to change the volume. Both
+ * ports use the same SPB. Therefore, we only register one
+ * control instance since it will be used by both channels.
+ * In error case we continue without controls.
+ */
+ kctrl = snd_ctl_new1(&playback_controls, *port_info);
+ ret = snd_ctl_add(card, kctrl);
+ if (ret < 0)
+ dev_err(dev,
+ "failed to add playback controls %p port=%d err=%d\n",
+ kctrl, port, ret);
+
+ kctrl = snd_ctl_new1(&capture_controls, *port_info);
+ ret = snd_ctl_add(card, kctrl);
+ if (ret < 0)
+ dev_err(dev,
+ "failed to add capture controls %p port=%d err=%d\n",
+ kctrl, port, ret);
+
+ return 0;
+}
+
+void siu_free_port(struct siu_port *port_info)
+{
+ kfree(port_info);
+}
+
+static int siu_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct siu_info *info = snd_soc_dai_get_drvdata(dai);
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct siu_port *port_info = siu_port_info(substream);
+ int ret;
+
+ dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__,
+ info->port_id, port_info);
+
+ snd_soc_set_runtime_hwparams(substream, &siu_dai_pcm_hw);
+
+ ret = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
+ if (unlikely(ret < 0))
+ return ret;
+
+ siu_dai_start(port_info);
+
+ return 0;
+}
+
+static void siu_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct siu_info *info = snd_soc_dai_get_drvdata(dai);
+ struct siu_port *port_info = siu_port_info(substream);
+
+ dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__,
+ info->port_id, port_info);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ port_info->play_cap &= ~PLAYBACK_ENABLED;
+ else
+ port_info->play_cap &= ~CAPTURE_ENABLED;
+
+ /* Stop the siu if the other stream is not using it */
+ if (!port_info->play_cap) {
+ /* during stmread or stmwrite ? */
+ BUG_ON(port_info->playback.rw_flg || port_info->capture.rw_flg);
+ siu_dai_spbstop(port_info);
+ siu_dai_stop(port_info);
+ }
+}
+
+/* PCM part of siu_dai_playback_prepare() / siu_dai_capture_prepare() */
+static int siu_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct siu_info *info = snd_soc_dai_get_drvdata(dai);
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct siu_port *port_info = siu_port_info(substream);
+ struct siu_stream *siu_stream;
+ int self, ret;
+
+ dev_dbg(substream->pcm->card->dev,
+ "%s: port %d, active streams %lx, %d channels\n",
+ __func__, info->port_id, port_info->play_cap, rt->channels);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ self = PLAYBACK_ENABLED;
+ siu_stream = &port_info->playback;
+ } else {
+ self = CAPTURE_ENABLED;
+ siu_stream = &port_info->capture;
+ }
+
+ /* Set up the siu if not already done */
+ if (!port_info->play_cap) {
+ siu_stream->rw_flg = 0; /* stream-data transfer flag */
+
+ siu_dai_spbAselect(port_info);
+ siu_dai_spbBselect(port_info);
+
+ siu_dai_open(siu_stream);
+
+ siu_dai_pcmdatapack(siu_stream);
+
+ ret = siu_dai_spbstart(port_info);
+ if (ret < 0)
+ goto fail;
+ } else {
+ ret = 0;
+ }
+
+ port_info->play_cap |= self;
+
+fail:
+ return ret;
+}
+
+/*
+ * SIU can set bus format to I2S / PCM / SPDIF independently for playback and
+ * capture, however, the current API sets the bus format globally for a DAI.
+ */
+static int siu_dai_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct siu_info *info = snd_soc_dai_get_drvdata(dai);
+ u32 __iomem *base = info->reg;
+ u32 ifctl;
+
+ dev_dbg(dai->dev, "%s: fmt 0x%x on port %d\n",
+ __func__, fmt, info->port_id);
+
+ if (info->port_id < 0)
+ return -ENODEV;
+
+ /* Here select between I2S / PCM / SPDIF */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ ifctl = siu_flags[info->port_id].playback.i2s |
+ siu_flags[info->port_id].capture.i2s;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ifctl = siu_flags[info->port_id].playback.pcm |
+ siu_flags[info->port_id].capture.pcm;
+ break;
+ /* SPDIF disabled - see comment at the top */
+ default:
+ return -EINVAL;
+ }
+
+ ifctl |= ~(siu_flags[info->port_id].playback.mask |
+ siu_flags[info->port_id].capture.mask) &
+ siu_read32(base + SIU_IFCTL);
+ siu_write32(base + SIU_IFCTL, ifctl);
+
+ return 0;
+}
+
+static int siu_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct clk *siu_clk, *parent_clk;
+ char *siu_name, *parent_name;
+ int ret;
+
+ if (dir != SND_SOC_CLOCK_IN)
+ return -EINVAL;
+
+ dev_dbg(dai->dev, "%s: using clock %d\n", __func__, clk_id);
+
+ switch (clk_id) {
+ case SIU_CLKA_PLL:
+ siu_name = "siua_clk";
+ parent_name = "pll_clk";
+ break;
+ case SIU_CLKA_EXT:
+ siu_name = "siua_clk";
+ parent_name = "siumcka_clk";
+ break;
+ case SIU_CLKB_PLL:
+ siu_name = "siub_clk";
+ parent_name = "pll_clk";
+ break;
+ case SIU_CLKB_EXT:
+ siu_name = "siub_clk";
+ parent_name = "siumckb_clk";
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ siu_clk = clk_get(dai->dev, siu_name);
+ if (IS_ERR(siu_clk)) {
+ dev_err(dai->dev, "%s: cannot get a SIU clock: %ld\n", __func__,
+ PTR_ERR(siu_clk));
+ return PTR_ERR(siu_clk);
+ }
+
+ parent_clk = clk_get(dai->dev, parent_name);
+ if (IS_ERR(parent_clk)) {
+ ret = PTR_ERR(parent_clk);
+ dev_err(dai->dev, "cannot get a SIU clock parent: %d\n", ret);
+ goto epclkget;
+ }
+
+ ret = clk_set_parent(siu_clk, parent_clk);
+ if (ret < 0) {
+ dev_err(dai->dev, "cannot reparent the SIU clock: %d\n", ret);
+ goto eclksetp;
+ }
+
+ ret = clk_set_rate(siu_clk, freq);
+ if (ret < 0)
+ dev_err(dai->dev, "cannot set SIU clock rate: %d\n", ret);
+
+ /* TODO: when clkdev gets reference counting we'll move these to siu_dai_shutdown() */
+eclksetp:
+ clk_put(parent_clk);
+epclkget:
+ clk_put(siu_clk);
+
+ return ret;
+}
+
+static struct snd_soc_dai_ops siu_dai_ops = {
+ .startup = siu_dai_startup,
+ .shutdown = siu_dai_shutdown,
+ .prepare = siu_dai_prepare,
+ .set_sysclk = siu_dai_set_sysclk,
+ .set_fmt = siu_dai_set_fmt,
+};
+
+static struct snd_soc_dai_driver siu_i2s_dai = {
+ .name = "siu-i2s-dai",
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ },
+ .ops = &siu_dai_ops,
+};
+
+static int __devinit siu_probe(struct platform_device *pdev)
+{
+ const struct firmware *fw_entry;
+ struct resource *res, *region;
+ struct siu_info *info;
+ int ret;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ siu_i2s_data = info;
+ info->dev = &pdev->dev;
+
+ ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev);
+ if (ret)
+ goto ereqfw;
+
+ /*
+ * Loaded firmware is "const" - read only, but we have to modify it in
+ * snd_siu_sh7343_spbAselect() and snd_siu_sh7343_spbBselect()
+ */
+ memcpy(&info->fw, fw_entry->data, fw_entry->size);
+
+ release_firmware(fw_entry);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENODEV;
+ goto egetres;
+ }
+
+ region = request_mem_region(res->start, resource_size(res),
+ pdev->name);
+ if (!region) {
+ dev_err(&pdev->dev, "SIU region already claimed\n");
+ ret = -EBUSY;
+ goto ereqmemreg;
+ }
+
+ ret = -ENOMEM;
+ info->pram = ioremap(res->start, PRAM_SIZE);
+ if (!info->pram)
+ goto emappram;
+ info->xram = ioremap(res->start + XRAM_OFFSET, XRAM_SIZE);
+ if (!info->xram)
+ goto emapxram;
+ info->yram = ioremap(res->start + YRAM_OFFSET, YRAM_SIZE);
+ if (!info->yram)
+ goto emapyram;
+ info->reg = ioremap(res->start + REG_OFFSET, resource_size(res) -
+ REG_OFFSET);
+ if (!info->reg)
+ goto emapreg;
+
+ dev_set_drvdata(&pdev->dev, info);
+
+ /* register using ARRAY version so we can keep dai name */
+ ret = snd_soc_register_dais(&pdev->dev, &siu_i2s_dai, 1);
+ if (ret < 0)
+ goto edaiinit;
+
+ ret = snd_soc_register_platform(&pdev->dev, &siu_platform);
+ if (ret < 0)
+ goto esocregp;
+
+ pm_runtime_enable(&pdev->dev);
+
+ return ret;
+
+esocregp:
+ snd_soc_unregister_dai(&pdev->dev);
+edaiinit:
+ iounmap(info->reg);
+emapreg:
+ iounmap(info->yram);
+emapyram:
+ iounmap(info->xram);
+emapxram:
+ iounmap(info->pram);
+emappram:
+ release_mem_region(res->start, resource_size(res));
+ereqmemreg:
+egetres:
+ereqfw:
+ kfree(info);
+
+ return ret;
+}
+
+static int __devexit siu_remove(struct platform_device *pdev)
+{
+ struct siu_info *info = dev_get_drvdata(&pdev->dev);
+ struct resource *res;
+
+ pm_runtime_disable(&pdev->dev);
+
+ snd_soc_unregister_platform(&pdev->dev);
+ snd_soc_unregister_dai(&pdev->dev);
+
+ iounmap(info->reg);
+ iounmap(info->yram);
+ iounmap(info->xram);
+ iounmap(info->pram);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res)
+ release_mem_region(res->start, resource_size(res));
+ kfree(info);
+
+ return 0;
+}
+
+static struct platform_driver siu_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "siu-pcm-audio",
+ },
+ .probe = siu_probe,
+ .remove = __devexit_p(siu_remove),
+};
+
+static int __init siu_init(void)
+{
+ return platform_driver_register(&siu_driver);
+}
+
+static void __exit siu_exit(void)
+{
+ platform_driver_unregister(&siu_driver);
+}
+
+module_init(siu_init)
+module_exit(siu_exit)
+
+MODULE_AUTHOR("Carlos Munoz <carlos@kenati.com>");
+MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/sh/siu_pcm.c b/sound/soc/sh/siu_pcm.c
new file mode 100644
index 00000000..a423babc
--- /dev/null
+++ b/sound/soc/sh/siu_pcm.c
@@ -0,0 +1,616 @@
+/*
+ * siu_pcm.c - ALSA driver for Renesas SH7343, SH7722 SIU peripheral.
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2006 Carlos Munoz <carlos@kenati.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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/siu.h>
+
+#include "siu.h"
+
+#define GET_MAX_PERIODS(buf_bytes, period_bytes) \
+ ((buf_bytes) / (period_bytes))
+#define PERIOD_OFFSET(buf_addr, period_num, period_bytes) \
+ ((buf_addr) + ((period_num) * (period_bytes)))
+
+#define RWF_STM_RD 0x01 /* Read in progress */
+#define RWF_STM_WT 0x02 /* Write in progress */
+
+struct siu_port *siu_ports[SIU_PORT_NUM];
+
+/* transfersize is number of u32 dma transfers per period */
+static int siu_pcm_stmwrite_stop(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+ struct siu_stream *siu_stream = &port_info->playback;
+ u32 stfifo;
+
+ if (!siu_stream->rw_flg)
+ return -EPERM;
+
+ /* output FIFO disable */
+ stfifo = siu_read32(base + SIU_STFIFO);
+ siu_write32(base + SIU_STFIFO, stfifo & ~0x0c180c18);
+ pr_debug("%s: STFIFO %x -> %x\n", __func__,
+ stfifo, stfifo & ~0x0c180c18);
+
+ /* during stmwrite clear */
+ siu_stream->rw_flg = 0;
+
+ return 0;
+}
+
+static int siu_pcm_stmwrite_start(struct siu_port *port_info)
+{
+ struct siu_stream *siu_stream = &port_info->playback;
+
+ if (siu_stream->rw_flg)
+ return -EPERM;
+
+ /* Current period in buffer */
+ port_info->playback.cur_period = 0;
+
+ /* during stmwrite flag set */
+ siu_stream->rw_flg = RWF_STM_WT;
+
+ /* DMA transfer start */
+ tasklet_schedule(&siu_stream->tasklet);
+
+ return 0;
+}
+
+static void siu_dma_tx_complete(void *arg)
+{
+ struct siu_stream *siu_stream = arg;
+
+ if (!siu_stream->rw_flg)
+ return;
+
+ /* Update completed period count */
+ if (++siu_stream->cur_period >=
+ GET_MAX_PERIODS(siu_stream->buf_bytes,
+ siu_stream->period_bytes))
+ siu_stream->cur_period = 0;
+
+ pr_debug("%s: done period #%d (%u/%u bytes), cookie %d\n",
+ __func__, siu_stream->cur_period,
+ siu_stream->cur_period * siu_stream->period_bytes,
+ siu_stream->buf_bytes, siu_stream->cookie);
+
+ tasklet_schedule(&siu_stream->tasklet);
+
+ /* Notify alsa: a period is done */
+ snd_pcm_period_elapsed(siu_stream->substream);
+}
+
+static int siu_pcm_wr_set(struct siu_port *port_info,
+ dma_addr_t buff, u32 size)
+{
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+ struct siu_stream *siu_stream = &port_info->playback;
+ struct snd_pcm_substream *substream = siu_stream->substream;
+ struct device *dev = substream->pcm->card->dev;
+ struct dma_async_tx_descriptor *desc;
+ dma_cookie_t cookie;
+ struct scatterlist sg;
+ u32 stfifo;
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)),
+ size, offset_in_page(buff));
+ sg_dma_len(&sg) = size;
+ sg_dma_address(&sg) = buff;
+
+ desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan,
+ &sg, 1, DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc) {
+ dev_err(dev, "Failed to allocate a dma descriptor\n");
+ return -ENOMEM;
+ }
+
+ desc->callback = siu_dma_tx_complete;
+ desc->callback_param = siu_stream;
+ cookie = desc->tx_submit(desc);
+ if (cookie < 0) {
+ dev_err(dev, "Failed to submit a dma transfer\n");
+ return cookie;
+ }
+
+ siu_stream->tx_desc = desc;
+ siu_stream->cookie = cookie;
+
+ dma_async_issue_pending(siu_stream->chan);
+
+ /* only output FIFO enable */
+ stfifo = siu_read32(base + SIU_STFIFO);
+ siu_write32(base + SIU_STFIFO, stfifo | (port_info->stfifo & 0x0c180c18));
+ dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
+ stfifo, stfifo | (port_info->stfifo & 0x0c180c18));
+
+ return 0;
+}
+
+static int siu_pcm_rd_set(struct siu_port *port_info,
+ dma_addr_t buff, size_t size)
+{
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+ struct siu_stream *siu_stream = &port_info->capture;
+ struct snd_pcm_substream *substream = siu_stream->substream;
+ struct device *dev = substream->pcm->card->dev;
+ struct dma_async_tx_descriptor *desc;
+ dma_cookie_t cookie;
+ struct scatterlist sg;
+ u32 stfifo;
+
+ dev_dbg(dev, "%s: %u@%llx\n", __func__, size, (unsigned long long)buff);
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)),
+ size, offset_in_page(buff));
+ sg_dma_len(&sg) = size;
+ sg_dma_address(&sg) = buff;
+
+ desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan,
+ &sg, 1, DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc) {
+ dev_err(dev, "Failed to allocate dma descriptor\n");
+ return -ENOMEM;
+ }
+
+ desc->callback = siu_dma_tx_complete;
+ desc->callback_param = siu_stream;
+ cookie = desc->tx_submit(desc);
+ if (cookie < 0) {
+ dev_err(dev, "Failed to submit dma descriptor\n");
+ return cookie;
+ }
+
+ siu_stream->tx_desc = desc;
+ siu_stream->cookie = cookie;
+
+ dma_async_issue_pending(siu_stream->chan);
+
+ /* only input FIFO enable */
+ stfifo = siu_read32(base + SIU_STFIFO);
+ siu_write32(base + SIU_STFIFO, siu_read32(base + SIU_STFIFO) |
+ (port_info->stfifo & 0x13071307));
+ dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
+ stfifo, stfifo | (port_info->stfifo & 0x13071307));
+
+ return 0;
+}
+
+static void siu_io_tasklet(unsigned long data)
+{
+ struct siu_stream *siu_stream = (struct siu_stream *)data;
+ struct snd_pcm_substream *substream = siu_stream->substream;
+ struct device *dev = substream->pcm->card->dev;
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct siu_port *port_info = siu_port_info(substream);
+
+ dev_dbg(dev, "%s: flags %x\n", __func__, siu_stream->rw_flg);
+
+ if (!siu_stream->rw_flg) {
+ dev_dbg(dev, "%s: stream inactive\n", __func__);
+ return;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ dma_addr_t buff;
+ size_t count;
+ u8 *virt;
+
+ buff = (dma_addr_t)PERIOD_OFFSET(rt->dma_addr,
+ siu_stream->cur_period,
+ siu_stream->period_bytes);
+ virt = PERIOD_OFFSET(rt->dma_area,
+ siu_stream->cur_period,
+ siu_stream->period_bytes);
+ count = siu_stream->period_bytes;
+
+ /* DMA transfer start */
+ siu_pcm_rd_set(port_info, buff, count);
+ } else {
+ siu_pcm_wr_set(port_info,
+ (dma_addr_t)PERIOD_OFFSET(rt->dma_addr,
+ siu_stream->cur_period,
+ siu_stream->period_bytes),
+ siu_stream->period_bytes);
+ }
+}
+
+/* Capture */
+static int siu_pcm_stmread_start(struct siu_port *port_info)
+{
+ struct siu_stream *siu_stream = &port_info->capture;
+
+ if (siu_stream->xfer_cnt > 0x1000000)
+ return -EINVAL;
+ if (siu_stream->rw_flg)
+ return -EPERM;
+
+ /* Current period in buffer */
+ siu_stream->cur_period = 0;
+
+ /* during stmread flag set */
+ siu_stream->rw_flg = RWF_STM_RD;
+
+ tasklet_schedule(&siu_stream->tasklet);
+
+ return 0;
+}
+
+static int siu_pcm_stmread_stop(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+ struct siu_stream *siu_stream = &port_info->capture;
+ struct device *dev = siu_stream->substream->pcm->card->dev;
+ u32 stfifo;
+
+ if (!siu_stream->rw_flg)
+ return -EPERM;
+
+ /* input FIFO disable */
+ stfifo = siu_read32(base + SIU_STFIFO);
+ siu_write32(base + SIU_STFIFO, stfifo & ~0x13071307);
+ dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
+ stfifo, stfifo & ~0x13071307);
+
+ /* during stmread flag clear */
+ siu_stream->rw_flg = 0;
+
+ return 0;
+}
+
+static int siu_pcm_hw_params(struct snd_pcm_substream *ss,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct siu_info *info = siu_i2s_data;
+ struct device *dev = ss->pcm->card->dev;
+ int ret;
+
+ dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
+
+ ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params));
+ if (ret < 0)
+ dev_err(dev, "snd_pcm_lib_malloc_pages() failed\n");
+
+ return ret;
+}
+
+static int siu_pcm_hw_free(struct snd_pcm_substream *ss)
+{
+ struct siu_info *info = siu_i2s_data;
+ struct siu_port *port_info = siu_port_info(ss);
+ struct device *dev = ss->pcm->card->dev;
+ struct siu_stream *siu_stream;
+
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ siu_stream = &port_info->playback;
+ else
+ siu_stream = &port_info->capture;
+
+ dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
+
+ return snd_pcm_lib_free_pages(ss);
+}
+
+static bool filter(struct dma_chan *chan, void *slave)
+{
+ struct sh_dmae_slave *param = slave;
+
+ pr_debug("%s: slave ID %d\n", __func__, param->slave_id);
+
+ if (unlikely(param->dma_dev != chan->device->dev))
+ return false;
+
+ chan->private = param;
+ return true;
+}
+
+static int siu_pcm_open(struct snd_pcm_substream *ss)
+{
+ /* Playback / Capture */
+ struct snd_soc_pcm_runtime *rtd = ss->private_data;
+ struct siu_platform *pdata = rtd->platform->dev->platform_data;
+ struct siu_info *info = siu_i2s_data;
+ struct siu_port *port_info = siu_port_info(ss);
+ struct siu_stream *siu_stream;
+ u32 port = info->port_id;
+ struct device *dev = ss->pcm->card->dev;
+ dma_cap_mask_t mask;
+ struct sh_dmae_slave *param;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ dev_dbg(dev, "%s, port=%d@%p\n", __func__, port, port_info);
+
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ siu_stream = &port_info->playback;
+ param = &siu_stream->param;
+ param->slave_id = port ? pdata->dma_slave_tx_b :
+ pdata->dma_slave_tx_a;
+ } else {
+ siu_stream = &port_info->capture;
+ param = &siu_stream->param;
+ param->slave_id = port ? pdata->dma_slave_rx_b :
+ pdata->dma_slave_rx_a;
+ }
+
+ param->dma_dev = pdata->dma_dev;
+ /* Get DMA channel */
+ siu_stream->chan = dma_request_channel(mask, filter, param);
+ if (!siu_stream->chan) {
+ dev_err(dev, "DMA channel allocation failed!\n");
+ return -EBUSY;
+ }
+
+ siu_stream->substream = ss;
+
+ return 0;
+}
+
+static int siu_pcm_close(struct snd_pcm_substream *ss)
+{
+ struct siu_info *info = siu_i2s_data;
+ struct device *dev = ss->pcm->card->dev;
+ struct siu_port *port_info = siu_port_info(ss);
+ struct siu_stream *siu_stream;
+
+ dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
+
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ siu_stream = &port_info->playback;
+ else
+ siu_stream = &port_info->capture;
+
+ dma_release_channel(siu_stream->chan);
+ siu_stream->chan = NULL;
+
+ siu_stream->substream = NULL;
+
+ return 0;
+}
+
+static int siu_pcm_prepare(struct snd_pcm_substream *ss)
+{
+ struct siu_info *info = siu_i2s_data;
+ struct siu_port *port_info = siu_port_info(ss);
+ struct device *dev = ss->pcm->card->dev;
+ struct snd_pcm_runtime *rt = ss->runtime;
+ struct siu_stream *siu_stream;
+ snd_pcm_sframes_t xfer_cnt;
+
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ siu_stream = &port_info->playback;
+ else
+ siu_stream = &port_info->capture;
+
+ rt = siu_stream->substream->runtime;
+
+ siu_stream->buf_bytes = snd_pcm_lib_buffer_bytes(ss);
+ siu_stream->period_bytes = snd_pcm_lib_period_bytes(ss);
+
+ dev_dbg(dev, "%s: port=%d, %d channels, period=%u bytes\n", __func__,
+ info->port_id, rt->channels, siu_stream->period_bytes);
+
+ /* We only support buffers that are multiples of the period */
+ if (siu_stream->buf_bytes % siu_stream->period_bytes) {
+ dev_err(dev, "%s() - buffer=%d not multiple of period=%d\n",
+ __func__, siu_stream->buf_bytes,
+ siu_stream->period_bytes);
+ return -EINVAL;
+ }
+
+ xfer_cnt = bytes_to_frames(rt, siu_stream->period_bytes);
+ if (!xfer_cnt || xfer_cnt > 0x1000000)
+ return -EINVAL;
+
+ siu_stream->format = rt->format;
+ siu_stream->xfer_cnt = xfer_cnt;
+
+ dev_dbg(dev, "port=%d buf=%lx buf_bytes=%d period_bytes=%d "
+ "format=%d channels=%d xfer_cnt=%d\n", info->port_id,
+ (unsigned long)rt->dma_addr, siu_stream->buf_bytes,
+ siu_stream->period_bytes,
+ siu_stream->format, rt->channels, (int)xfer_cnt);
+
+ return 0;
+}
+
+static int siu_pcm_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+ struct siu_info *info = siu_i2s_data;
+ struct device *dev = ss->pcm->card->dev;
+ struct siu_port *port_info = siu_port_info(ss);
+ int ret;
+
+ dev_dbg(dev, "%s: port=%d@%p, cmd=%d\n", __func__,
+ info->port_id, port_info, cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = siu_pcm_stmwrite_start(port_info);
+ else
+ ret = siu_pcm_stmread_start(port_info);
+
+ if (ret < 0)
+ dev_warn(dev, "%s: start failed on port=%d\n",
+ __func__, info->port_id);
+
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ siu_pcm_stmwrite_stop(port_info);
+ else
+ siu_pcm_stmread_stop(port_info);
+ ret = 0;
+
+ break;
+ default:
+ dev_err(dev, "%s() unsupported cmd=%d\n", __func__, cmd);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/*
+ * So far only resolution of one period is supported, subject to extending the
+ * dmangine API
+ */
+static snd_pcm_uframes_t siu_pcm_pointer_dma(struct snd_pcm_substream *ss)
+{
+ struct device *dev = ss->pcm->card->dev;
+ struct siu_info *info = siu_i2s_data;
+ u32 __iomem *base = info->reg;
+ struct siu_port *port_info = siu_port_info(ss);
+ struct snd_pcm_runtime *rt = ss->runtime;
+ size_t ptr;
+ struct siu_stream *siu_stream;
+
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ siu_stream = &port_info->playback;
+ else
+ siu_stream = &port_info->capture;
+
+ /*
+ * ptr is the offset into the buffer where the dma is currently at. We
+ * check if the dma buffer has just wrapped.
+ */
+ ptr = PERIOD_OFFSET(rt->dma_addr,
+ siu_stream->cur_period,
+ siu_stream->period_bytes) - rt->dma_addr;
+
+ dev_dbg(dev,
+ "%s: port=%d, events %x, FSTS %x, xferred %u/%u, cookie %d\n",
+ __func__, info->port_id, siu_read32(base + SIU_EVNTC),
+ siu_read32(base + SIU_SBFSTS), ptr, siu_stream->buf_bytes,
+ siu_stream->cookie);
+
+ if (ptr >= siu_stream->buf_bytes)
+ ptr = 0;
+
+ return bytes_to_frames(ss->runtime, ptr);
+}
+
+static int siu_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ /* card->dev == socdev->dev, see snd_soc_new_pcms() */
+ struct siu_info *info = siu_i2s_data;
+ struct platform_device *pdev = to_platform_device(card->dev);
+ int ret;
+ int i;
+
+ /* pdev->id selects between SIUA and SIUB */
+ if (pdev->id < 0 || pdev->id >= SIU_PORT_NUM)
+ return -EINVAL;
+
+ info->port_id = pdev->id;
+
+ /*
+ * While the siu has 2 ports, only one port can be on at a time (only 1
+ * SPB). So far all the boards using the siu had only one of the ports
+ * wired to a codec. To simplify things, we only register one port with
+ * alsa. In case both ports are needed, it should be changed here
+ */
+ for (i = pdev->id; i < pdev->id + 1; i++) {
+ struct siu_port **port_info = &siu_ports[i];
+
+ ret = siu_init_port(i, port_info, card);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_DEV, NULL,
+ SIU_BUFFER_BYTES_MAX, SIU_BUFFER_BYTES_MAX);
+ if (ret < 0) {
+ dev_err(card->dev,
+ "snd_pcm_lib_preallocate_pages_for_all() err=%d",
+ ret);
+ goto fail;
+ }
+
+ (*port_info)->pcm = pcm;
+
+ /* IO tasklets */
+ tasklet_init(&(*port_info)->playback.tasklet, siu_io_tasklet,
+ (unsigned long)&(*port_info)->playback);
+ tasklet_init(&(*port_info)->capture.tasklet, siu_io_tasklet,
+ (unsigned long)&(*port_info)->capture);
+ }
+
+ dev_info(card->dev, "SuperH SIU driver initialized.\n");
+ return 0;
+
+fail:
+ siu_free_port(siu_ports[pdev->id]);
+ dev_err(card->dev, "SIU: failed to initialize.\n");
+ return ret;
+}
+
+static void siu_pcm_free(struct snd_pcm *pcm)
+{
+ struct platform_device *pdev = to_platform_device(pcm->card->dev);
+ struct siu_port *port_info = siu_ports[pdev->id];
+
+ tasklet_kill(&port_info->capture.tasklet);
+ tasklet_kill(&port_info->playback.tasklet);
+
+ siu_free_port(port_info);
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+
+ dev_dbg(pcm->card->dev, "%s\n", __func__);
+}
+
+static struct snd_pcm_ops siu_pcm_ops = {
+ .open = siu_pcm_open,
+ .close = siu_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = siu_pcm_hw_params,
+ .hw_free = siu_pcm_hw_free,
+ .prepare = siu_pcm_prepare,
+ .trigger = siu_pcm_trigger,
+ .pointer = siu_pcm_pointer_dma,
+};
+
+struct snd_soc_platform_driver siu_platform = {
+ .ops = &siu_pcm_ops,
+ .pcm_new = siu_pcm_new,
+ .pcm_free = siu_pcm_free,
+};
+EXPORT_SYMBOL_GPL(siu_platform);
diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c
new file mode 100644
index 00000000..05192d97
--- /dev/null
+++ b/sound/soc/sh/ssi.c
@@ -0,0 +1,418 @@
+/*
+ * Serial Sound Interface (I2S) support for SH7760/SH7780
+ *
+ * Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ *
+ * licensed under the terms outlined in the file COPYING at the root
+ * of the linux kernel sources.
+ *
+ * dont forget to set IPSEL/OMSEL register bits (in your board code) to
+ * enable SSI output pins!
+ */
+
+/*
+ * LIMITATIONS:
+ * The SSI unit has only one physical data line, so full duplex is
+ * impossible. This can be remedied on the SH7760 by using the
+ * other SSI unit for recording; however the SH7780 has only 1 SSI
+ * unit, and its pins are shared with the AC97 unit, among others.
+ *
+ * FEATURES:
+ * The SSI features "compressed mode": in this mode it continuously
+ * streams PCM data over the I2S lines and uses LRCK as a handshake
+ * signal. Can be used to send compressed data (AC3/DTS) to a DSP.
+ * The number of bits sent over the wire in a frame can be adjusted
+ * and can be independent from the actual sample bit depth. This is
+ * useful to support TDM mode codecs like the AD1939 which have a
+ * fixed TDM slot size, regardless of sample resolution.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <asm/io.h>
+
+#define SSICR 0x00
+#define SSISR 0x04
+
+#define CR_DMAEN (1 << 28)
+#define CR_CHNL_SHIFT 22
+#define CR_CHNL_MASK (3 << CR_CHNL_SHIFT)
+#define CR_DWL_SHIFT 19
+#define CR_DWL_MASK (7 << CR_DWL_SHIFT)
+#define CR_SWL_SHIFT 16
+#define CR_SWL_MASK (7 << CR_SWL_SHIFT)
+#define CR_SCK_MASTER (1 << 15) /* bitclock master bit */
+#define CR_SWS_MASTER (1 << 14) /* wordselect master bit */
+#define CR_SCKP (1 << 13) /* I2Sclock polarity */
+#define CR_SWSP (1 << 12) /* LRCK polarity */
+#define CR_SPDP (1 << 11)
+#define CR_SDTA (1 << 10) /* i2s alignment (msb/lsb) */
+#define CR_PDTA (1 << 9) /* fifo data alignment */
+#define CR_DEL (1 << 8) /* delay data by 1 i2sclk */
+#define CR_BREN (1 << 7) /* clock gating in burst mode */
+#define CR_CKDIV_SHIFT 4
+#define CR_CKDIV_MASK (7 << CR_CKDIV_SHIFT) /* bitclock divider */
+#define CR_MUTE (1 << 3) /* SSI mute */
+#define CR_CPEN (1 << 2) /* compressed mode */
+#define CR_TRMD (1 << 1) /* transmit/receive select */
+#define CR_EN (1 << 0) /* enable SSI */
+
+#define SSIREG(reg) (*(unsigned long *)(ssi->mmio + (reg)))
+
+struct ssi_priv {
+ unsigned long mmio;
+ unsigned long sysclk;
+ int inuse;
+} ssi_cpu_data[] = {
+#if defined(CONFIG_CPU_SUBTYPE_SH7760)
+ {
+ .mmio = 0xFE680000,
+ },
+ {
+ .mmio = 0xFE690000,
+ },
+#elif defined(CONFIG_CPU_SUBTYPE_SH7780)
+ {
+ .mmio = 0xFFE70000,
+ },
+#else
+#error "Unsupported SuperH SoC"
+#endif
+};
+
+/*
+ * track usage of the SSI; it is simplex-only so prevent attempts of
+ * concurrent playback + capture. FIXME: any locking required?
+ */
+static int ssi_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+ if (ssi->inuse) {
+ pr_debug("ssi: already in use!\n");
+ return -EBUSY;
+ } else
+ ssi->inuse = 1;
+ return 0;
+}
+
+static void ssi_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+
+ ssi->inuse = 0;
+}
+
+static int ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ SSIREG(SSICR) |= CR_DMAEN | CR_EN;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ SSIREG(SSICR) &= ~(CR_DMAEN | CR_EN);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ssi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+ unsigned long ssicr = SSIREG(SSICR);
+ unsigned int bits, channels, swl, recv, i;
+
+ channels = params_channels(params);
+ bits = params->msbits;
+ recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1;
+
+ pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr);
+ pr_debug("bits: %u channels: %u\n", bits, channels);
+
+ ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA |
+ CR_SWL_MASK);
+
+ /* direction (send/receive) */
+ if (!recv)
+ ssicr |= CR_TRMD; /* transmit */
+
+ /* channels */
+ if ((channels < 2) || (channels > 8) || (channels & 1)) {
+ pr_debug("ssi: invalid number of channels\n");
+ return -EINVAL;
+ }
+ ssicr |= ((channels >> 1) - 1) << CR_CHNL_SHIFT;
+
+ /* DATA WORD LENGTH (DWL): databits in audio sample */
+ i = 0;
+ switch (bits) {
+ case 32: ++i;
+ case 24: ++i;
+ case 22: ++i;
+ case 20: ++i;
+ case 18: ++i;
+ case 16: ++i;
+ ssicr |= i << CR_DWL_SHIFT;
+ case 8: break;
+ default:
+ pr_debug("ssi: invalid sample width\n");
+ return -EINVAL;
+ }
+
+ /*
+ * SYSTEM WORD LENGTH: size in bits of half a frame over the I2S
+ * wires. This is usually bits_per_sample x channels/2; i.e. in
+ * Stereo mode the SWL equals DWL. SWL can be bigger than the
+ * product of (channels_per_slot x samplebits), e.g. for codecs
+ * like the AD1939 which only accept 32bit wide TDM slots. For
+ * "standard" I2S operation we set SWL = chans / 2 * DWL here.
+ * Waiting for ASoC to get TDM support ;-)
+ */
+ if ((bits > 16) && (bits <= 24)) {
+ bits = 24; /* these are padded by the SSI */
+ /*ssicr |= CR_PDTA;*/ /* cpu/data endianness ? */
+ }
+ i = 0;
+ swl = (bits * channels) / 2;
+ switch (swl) {
+ case 256: ++i;
+ case 128: ++i;
+ case 64: ++i;
+ case 48: ++i;
+ case 32: ++i;
+ case 16: ++i;
+ ssicr |= i << CR_SWL_SHIFT;
+ case 8: break;
+ default:
+ pr_debug("ssi: invalid system word length computed\n");
+ return -EINVAL;
+ }
+
+ SSIREG(SSICR) = ssicr;
+
+ pr_debug("ssi_hw_params() leave\nssicr is now %08lx\n", ssicr);
+ return 0;
+}
+
+static int ssi_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct ssi_priv *ssi = &ssi_cpu_data[cpu_dai->id];
+
+ ssi->sysclk = freq;
+
+ return 0;
+}
+
+/*
+ * This divider is used to generate the SSI_SCK (I2S bitclock) from the
+ * clock at the HAC_BIT_CLK ("oversampling clock") pin.
+ */
+static int ssi_set_clkdiv(struct snd_soc_dai *dai, int did, int div)
+{
+ struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+ unsigned long ssicr;
+ int i;
+
+ i = 0;
+ ssicr = SSIREG(SSICR) & ~CR_CKDIV_MASK;
+ switch (div) {
+ case 16: ++i;
+ case 8: ++i;
+ case 4: ++i;
+ case 2: ++i;
+ SSIREG(SSICR) = ssicr | (i << CR_CKDIV_SHIFT);
+ case 1: break;
+ default:
+ pr_debug("ssi: invalid sck divider %d\n", div);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ssi_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+ unsigned long ssicr = SSIREG(SSICR);
+
+ pr_debug("ssi_set_fmt()\nssicr was 0x%08lx\n", ssicr);
+
+ ssicr &= ~(CR_DEL | CR_PDTA | CR_BREN | CR_SWSP | CR_SCKP |
+ CR_SWS_MASTER | CR_SCK_MASTER);
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ ssicr |= CR_DEL | CR_PDTA;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ssicr |= CR_DEL;
+ break;
+ default:
+ pr_debug("ssi: unsupported format\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
+ case SND_SOC_DAIFMT_CONT:
+ break;
+ case SND_SOC_DAIFMT_GATED:
+ ssicr |= CR_BREN;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ ssicr |= CR_SCKP; /* sample data at low clkedge */
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ ssicr |= CR_SCKP | CR_SWSP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ ssicr |= CR_SWSP; /* word select starts low */
+ break;
+ default:
+ pr_debug("ssi: invalid inversion\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ ssicr |= CR_SCK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ ssicr |= CR_SWS_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ ssicr |= CR_SWS_MASTER | CR_SCK_MASTER;
+ break;
+ default:
+ pr_debug("ssi: invalid master/slave configuration\n");
+ return -EINVAL;
+ }
+
+ SSIREG(SSICR) = ssicr;
+ pr_debug("ssi_set_fmt() leave\nssicr is now 0x%08lx\n", ssicr);
+
+ return 0;
+}
+
+/* the SSI depends on an external clocksource (at HAC_BIT_CLK) even in
+ * Master mode, so really this is board specific; the SSI can do any
+ * rate with the right bitclk and divider settings.
+ */
+#define SSI_RATES \
+ SNDRV_PCM_RATE_8000_192000
+
+/* the SSI can do 8-32 bit samples, with 8 possible channels */
+#define SSI_FMTS \
+ (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE)
+
+static struct snd_soc_dai_ops ssi_dai_ops = {
+ .startup = ssi_startup,
+ .shutdown = ssi_shutdown,
+ .trigger = ssi_trigger,
+ .hw_params = ssi_hw_params,
+ .set_sysclk = ssi_set_sysclk,
+ .set_clkdiv = ssi_set_clkdiv,
+ .set_fmt = ssi_set_fmt,
+};
+
+struct snd_soc_dai_driver sh4_ssi_dai[] = {
+{
+ .name = "ssi-dai.0",
+ .playback = {
+ .rates = SSI_RATES,
+ .formats = SSI_FMTS,
+ .channels_min = 2,
+ .channels_max = 8,
+ },
+ .capture = {
+ .rates = SSI_RATES,
+ .formats = SSI_FMTS,
+ .channels_min = 2,
+ .channels_max = 8,
+ },
+ .ops = &ssi_dai_ops,
+},
+#ifdef CONFIG_CPU_SUBTYPE_SH7760
+{
+ .name = "ssi-dai.1",
+ .playback = {
+ .rates = SSI_RATES,
+ .formats = SSI_FMTS,
+ .channels_min = 2,
+ .channels_max = 8,
+ },
+ .capture = {
+ .rates = SSI_RATES,
+ .formats = SSI_FMTS,
+ .channels_min = 2,
+ .channels_max = 8,
+ },
+ .ops = &ssi_dai_ops,
+},
+#endif
+};
+
+static int __devinit sh4_soc_dai_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_dais(&pdev->dev, sh4_ssi_dai,
+ ARRAY_SIZE(sh4_ssi_dai));
+}
+
+static int __devexit sh4_soc_dai_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(sh4_ssi_dai));
+ return 0;
+}
+
+static struct platform_driver sh4_ssi_driver = {
+ .driver = {
+ .name = "sh4-ssi-dai",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = sh4_soc_dai_probe,
+ .remove = __devexit_p(sh4_soc_dai_remove),
+};
+
+static int __init snd_sh4_ssi_init(void)
+{
+ return platform_driver_register(&sh4_ssi_driver);
+}
+module_init(snd_sh4_ssi_init);
+
+static void __exit snd_sh4_ssi_exit(void)
+{
+ platform_driver_unregister(&sh4_ssi_driver);
+}
+module_exit(snd_sh4_ssi_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SuperH onchip SSI (I2S) audio driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c
new file mode 100644
index 00000000..039b9532
--- /dev/null
+++ b/sound/soc/soc-cache.c
@@ -0,0 +1,1363 @@
+/*
+ * soc-cache.c -- ASoC register cache helpers
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <sound/soc.h>
+#include <linux/lzo.h>
+#include <linux/bitmap.h>
+#include <linux/rbtree.h>
+
+#include <trace/events/asoc.h>
+
+#ifdef CONFIG_SPI_MASTER
+static int do_spi_write(void *control, const char *data, int len)
+{
+ struct spi_device *spi = control;
+ int ret;
+
+ ret = spi_write(spi, data, len);
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+#endif
+
+static int do_hw_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value, const void *data, int len)
+{
+ int ret;
+
+ if (!snd_soc_codec_volatile_register(codec, reg) &&
+ reg < codec->driver->reg_cache_size &&
+ !codec->cache_bypass) {
+ ret = snd_soc_cache_write(codec, reg, value);
+ if (ret < 0)
+ return -1;
+ }
+
+ if (codec->cache_only) {
+ codec->cache_sync = 1;
+ return 0;
+ }
+
+ ret = codec->hw_write(codec->control_data, data, len);
+ if (ret == len)
+ return 0;
+ if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+}
+
+static unsigned int do_hw_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ int ret;
+ unsigned int val;
+
+ if (reg >= codec->driver->reg_cache_size ||
+ snd_soc_codec_volatile_register(codec, reg) ||
+ codec->cache_bypass) {
+ if (codec->cache_only)
+ return -1;
+
+ BUG_ON(!codec->hw_read);
+ return codec->hw_read(codec, reg);
+ }
+
+ ret = snd_soc_cache_read(codec, reg, &val);
+ if (ret < 0)
+ return -1;
+ return val;
+}
+
+static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return do_hw_read(codec, reg);
+}
+
+static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u16 data;
+
+ data = cpu_to_be16((reg << 12) | (value & 0xffffff));
+
+ return do_hw_write(codec, reg, value, &data, 2);
+}
+
+static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return do_hw_read(codec, reg);
+}
+
+static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ return do_hw_write(codec, reg, value, data, 2);
+}
+
+static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ reg &= 0xff;
+ data[0] = reg;
+ data[1] = value & 0xff;
+
+ return do_hw_write(codec, reg, value, data, 2);
+}
+
+static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return do_hw_read(codec, reg);
+}
+
+static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ data[0] = reg;
+ data[1] = (value >> 8) & 0xff;
+ data[2] = value & 0xff;
+
+ return do_hw_write(codec, reg, value, data, 3);
+}
+
+static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return do_hw_read(codec, reg);
+}
+
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+static unsigned int do_i2c_read(struct snd_soc_codec *codec,
+ void *reg, int reglen,
+ void *data, int datalen)
+{
+ struct i2c_msg xfer[2];
+ int ret;
+ struct i2c_client *client = codec->control_data;
+
+ /* Write register */
+ xfer[0].addr = client->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = reglen;
+ xfer[0].buf = reg;
+
+ /* Read data */
+ xfer[1].addr = client->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = datalen;
+ xfer[1].buf = data;
+
+ ret = i2c_transfer(client->adapter, xfer, 2);
+ if (ret == 2)
+ return 0;
+ else if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+}
+#endif
+
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+static unsigned int snd_soc_8_8_read_i2c(struct snd_soc_codec *codec,
+ unsigned int r)
+{
+ u8 reg = r;
+ u8 data;
+ int ret;
+
+ ret = do_i2c_read(codec, &reg, 1, &data, 1);
+ if (ret < 0)
+ return 0;
+ return data;
+}
+#else
+#define snd_soc_8_8_read_i2c NULL
+#endif
+
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec,
+ unsigned int r)
+{
+ u8 reg = r;
+ u16 data;
+ int ret;
+
+ ret = do_i2c_read(codec, &reg, 1, &data, 2);
+ if (ret < 0)
+ return 0;
+ return (data >> 8) | ((data & 0xff) << 8);
+}
+#else
+#define snd_soc_8_16_read_i2c NULL
+#endif
+
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+static unsigned int snd_soc_16_8_read_i2c(struct snd_soc_codec *codec,
+ unsigned int r)
+{
+ u16 reg = r;
+ u8 data;
+ int ret;
+
+ ret = do_i2c_read(codec, &reg, 2, &data, 1);
+ if (ret < 0)
+ return 0;
+ return data;
+}
+#else
+#define snd_soc_16_8_read_i2c NULL
+#endif
+
+static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return do_hw_read(codec, reg);
+}
+
+static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ data[0] = (reg >> 8) & 0xff;
+ data[1] = reg & 0xff;
+ data[2] = value;
+
+ return do_hw_write(codec, reg, value, data, 3);
+}
+
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+static unsigned int snd_soc_16_16_read_i2c(struct snd_soc_codec *codec,
+ unsigned int r)
+{
+ u16 reg = cpu_to_be16(r);
+ u16 data;
+ int ret;
+
+ ret = do_i2c_read(codec, &reg, 2, &data, 2);
+ if (ret < 0)
+ return 0;
+ return be16_to_cpu(data);
+}
+#else
+#define snd_soc_16_16_read_i2c NULL
+#endif
+
+static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return do_hw_read(codec, reg);
+}
+
+static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[4];
+
+ data[0] = (reg >> 8) & 0xff;
+ data[1] = reg & 0xff;
+ data[2] = (value >> 8) & 0xff;
+ data[3] = value & 0xff;
+
+ return do_hw_write(codec, reg, value, data, 4);
+}
+
+/* Primitive bulk write support for soc-cache. The data pointed to by
+ * `data' needs to already be in the form the hardware expects
+ * including any leading register specific data. Any data written
+ * through this function will not go through the cache as it only
+ * handles writing to volatile or out of bounds registers.
+ */
+static int snd_soc_hw_bulk_write_raw(struct snd_soc_codec *codec, unsigned int reg,
+ const void *data, size_t len)
+{
+ int ret;
+
+ /* To ensure that we don't get out of sync with the cache, check
+ * whether the base register is volatile or if we've directly asked
+ * to bypass the cache. Out of bounds registers are considered
+ * volatile.
+ */
+ if (!codec->cache_bypass
+ && !snd_soc_codec_volatile_register(codec, reg)
+ && reg < codec->driver->reg_cache_size)
+ return -EINVAL;
+
+ switch (codec->control_type) {
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+ case SND_SOC_I2C:
+ ret = i2c_master_send(codec->control_data, data, len);
+ break;
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ case SND_SOC_SPI:
+ ret = spi_write(codec->control_data, data, len);
+ break;
+#endif
+ default:
+ BUG();
+ }
+
+ if (ret == len)
+ return 0;
+ if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+}
+
+static struct {
+ int addr_bits;
+ int data_bits;
+ int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int);
+ unsigned int (*read)(struct snd_soc_codec *, unsigned int);
+ unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int);
+} io_types[] = {
+ {
+ .addr_bits = 4, .data_bits = 12,
+ .write = snd_soc_4_12_write, .read = snd_soc_4_12_read,
+ },
+ {
+ .addr_bits = 7, .data_bits = 9,
+ .write = snd_soc_7_9_write, .read = snd_soc_7_9_read,
+ },
+ {
+ .addr_bits = 8, .data_bits = 8,
+ .write = snd_soc_8_8_write, .read = snd_soc_8_8_read,
+ .i2c_read = snd_soc_8_8_read_i2c,
+ },
+ {
+ .addr_bits = 8, .data_bits = 16,
+ .write = snd_soc_8_16_write, .read = snd_soc_8_16_read,
+ .i2c_read = snd_soc_8_16_read_i2c,
+ },
+ {
+ .addr_bits = 16, .data_bits = 8,
+ .write = snd_soc_16_8_write, .read = snd_soc_16_8_read,
+ .i2c_read = snd_soc_16_8_read_i2c,
+ },
+ {
+ .addr_bits = 16, .data_bits = 16,
+ .write = snd_soc_16_16_write, .read = snd_soc_16_16_read,
+ .i2c_read = snd_soc_16_16_read_i2c,
+ },
+};
+
+/**
+ * snd_soc_codec_set_cache_io: Set up standard I/O functions.
+ *
+ * @codec: CODEC to configure.
+ * @addr_bits: Number of bits of register address data.
+ * @data_bits: Number of bits of data per register.
+ * @control: Control bus used.
+ *
+ * Register formats are frequently shared between many I2C and SPI
+ * devices. In order to promote code reuse the ASoC core provides
+ * some standard implementations of CODEC read and write operations
+ * which can be set up using this function.
+ *
+ * The caller is responsible for allocating and initialising the
+ * actual cache.
+ *
+ * Note that at present this code cannot be used by CODECs with
+ * volatile registers.
+ */
+int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
+ int addr_bits, int data_bits,
+ enum snd_soc_control_type control)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(io_types); i++)
+ if (io_types[i].addr_bits == addr_bits &&
+ io_types[i].data_bits == data_bits)
+ break;
+ if (i == ARRAY_SIZE(io_types)) {
+ printk(KERN_ERR
+ "No I/O functions for %d bit address %d bit data\n",
+ addr_bits, data_bits);
+ return -EINVAL;
+ }
+
+ codec->write = io_types[i].write;
+ codec->read = io_types[i].read;
+ codec->bulk_write_raw = snd_soc_hw_bulk_write_raw;
+
+ switch (control) {
+ case SND_SOC_I2C:
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+ codec->hw_write = (hw_write_t)i2c_master_send;
+#endif
+ if (io_types[i].i2c_read)
+ codec->hw_read = io_types[i].i2c_read;
+
+ codec->control_data = container_of(codec->dev,
+ struct i2c_client,
+ dev);
+ break;
+
+ case SND_SOC_SPI:
+#ifdef CONFIG_SPI_MASTER
+ codec->hw_write = do_spi_write;
+#endif
+
+ codec->control_data = container_of(codec->dev,
+ struct spi_device,
+ dev);
+ break;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io);
+
+static bool snd_soc_set_cache_val(void *base, unsigned int idx,
+ unsigned int val, unsigned int word_size)
+{
+ switch (word_size) {
+ case 1: {
+ u8 *cache = base;
+ if (cache[idx] == val)
+ return true;
+ cache[idx] = val;
+ break;
+ }
+ case 2: {
+ u16 *cache = base;
+ if (cache[idx] == val)
+ return true;
+ cache[idx] = val;
+ break;
+ }
+ default:
+ BUG();
+ }
+ return false;
+}
+
+static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx,
+ unsigned int word_size)
+{
+ if (!base)
+ return -1;
+
+ switch (word_size) {
+ case 1: {
+ const u8 *cache = base;
+ return cache[idx];
+ }
+ case 2: {
+ const u16 *cache = base;
+ return cache[idx];
+ }
+ default:
+ BUG();
+ }
+ /* unreachable */
+ return -1;
+}
+
+struct snd_soc_rbtree_node {
+ struct rb_node node;
+ unsigned int reg;
+ unsigned int value;
+ unsigned int defval;
+} __attribute__ ((packed));
+
+struct snd_soc_rbtree_ctx {
+ struct rb_root root;
+};
+
+static struct snd_soc_rbtree_node *snd_soc_rbtree_lookup(
+ struct rb_root *root, unsigned int reg)
+{
+ struct rb_node *node;
+ struct snd_soc_rbtree_node *rbnode;
+
+ node = root->rb_node;
+ while (node) {
+ rbnode = container_of(node, struct snd_soc_rbtree_node, node);
+ if (rbnode->reg < reg)
+ node = node->rb_left;
+ else if (rbnode->reg > reg)
+ node = node->rb_right;
+ else
+ return rbnode;
+ }
+
+ return NULL;
+}
+
+static int snd_soc_rbtree_insert(struct rb_root *root,
+ struct snd_soc_rbtree_node *rbnode)
+{
+ struct rb_node **new, *parent;
+ struct snd_soc_rbtree_node *rbnode_tmp;
+
+ parent = NULL;
+ new = &root->rb_node;
+ while (*new) {
+ rbnode_tmp = container_of(*new, struct snd_soc_rbtree_node,
+ node);
+ parent = *new;
+ if (rbnode_tmp->reg < rbnode->reg)
+ new = &((*new)->rb_left);
+ else if (rbnode_tmp->reg > rbnode->reg)
+ new = &((*new)->rb_right);
+ else
+ return 0;
+ }
+
+ /* insert the node into the rbtree */
+ rb_link_node(&rbnode->node, parent, new);
+ rb_insert_color(&rbnode->node, root);
+
+ return 1;
+}
+
+static int snd_soc_rbtree_cache_sync(struct snd_soc_codec *codec)
+{
+ struct snd_soc_rbtree_ctx *rbtree_ctx;
+ struct rb_node *node;
+ struct snd_soc_rbtree_node *rbnode;
+ unsigned int val;
+ int ret;
+
+ rbtree_ctx = codec->reg_cache;
+ for (node = rb_first(&rbtree_ctx->root); node; node = rb_next(node)) {
+ rbnode = rb_entry(node, struct snd_soc_rbtree_node, node);
+ if (rbnode->value == rbnode->defval)
+ continue;
+ WARN_ON(codec->writable_register &&
+ codec->writable_register(codec, rbnode->reg));
+ ret = snd_soc_cache_read(codec, rbnode->reg, &val);
+ if (ret)
+ return ret;
+ codec->cache_bypass = 1;
+ ret = snd_soc_write(codec, rbnode->reg, val);
+ codec->cache_bypass = 0;
+ if (ret)
+ return ret;
+ dev_dbg(codec->dev, "Synced register %#x, value = %#x\n",
+ rbnode->reg, val);
+ }
+
+ return 0;
+}
+
+static int snd_soc_rbtree_cache_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ struct snd_soc_rbtree_ctx *rbtree_ctx;
+ struct snd_soc_rbtree_node *rbnode;
+
+ rbtree_ctx = codec->reg_cache;
+ rbnode = snd_soc_rbtree_lookup(&rbtree_ctx->root, reg);
+ if (rbnode) {
+ if (rbnode->value == value)
+ return 0;
+ rbnode->value = value;
+ } else {
+ /* bail out early, no need to create the rbnode yet */
+ if (!value)
+ return 0;
+ /*
+ * for uninitialized registers whose value is changed
+ * from the default zero, create an rbnode and insert
+ * it into the tree.
+ */
+ rbnode = kzalloc(sizeof *rbnode, GFP_KERNEL);
+ if (!rbnode)
+ return -ENOMEM;
+ rbnode->reg = reg;
+ rbnode->value = value;
+ snd_soc_rbtree_insert(&rbtree_ctx->root, rbnode);
+ }
+
+ return 0;
+}
+
+static int snd_soc_rbtree_cache_read(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int *value)
+{
+ struct snd_soc_rbtree_ctx *rbtree_ctx;
+ struct snd_soc_rbtree_node *rbnode;
+
+ rbtree_ctx = codec->reg_cache;
+ rbnode = snd_soc_rbtree_lookup(&rbtree_ctx->root, reg);
+ if (rbnode) {
+ *value = rbnode->value;
+ } else {
+ /* uninitialized registers default to 0 */
+ *value = 0;
+ }
+
+ return 0;
+}
+
+static int snd_soc_rbtree_cache_exit(struct snd_soc_codec *codec)
+{
+ struct rb_node *next;
+ struct snd_soc_rbtree_ctx *rbtree_ctx;
+ struct snd_soc_rbtree_node *rbtree_node;
+
+ /* if we've already been called then just return */
+ rbtree_ctx = codec->reg_cache;
+ if (!rbtree_ctx)
+ return 0;
+
+ /* free up the rbtree */
+ next = rb_first(&rbtree_ctx->root);
+ while (next) {
+ rbtree_node = rb_entry(next, struct snd_soc_rbtree_node, node);
+ next = rb_next(&rbtree_node->node);
+ rb_erase(&rbtree_node->node, &rbtree_ctx->root);
+ kfree(rbtree_node);
+ }
+
+ /* release the resources */
+ kfree(codec->reg_cache);
+ codec->reg_cache = NULL;
+
+ return 0;
+}
+
+static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec)
+{
+ struct snd_soc_rbtree_node *rbtree_node;
+ struct snd_soc_rbtree_ctx *rbtree_ctx;
+ unsigned int val;
+ unsigned int word_size;
+ int i;
+ int ret;
+
+ codec->reg_cache = kmalloc(sizeof *rbtree_ctx, GFP_KERNEL);
+ if (!codec->reg_cache)
+ return -ENOMEM;
+
+ rbtree_ctx = codec->reg_cache;
+ rbtree_ctx->root = RB_ROOT;
+
+ if (!codec->reg_def_copy)
+ return 0;
+
+ /*
+ * populate the rbtree with the initialized registers. All other
+ * registers will be inserted when they are first modified.
+ */
+ word_size = codec->driver->reg_word_size;
+ for (i = 0; i < codec->driver->reg_cache_size; ++i) {
+ val = snd_soc_get_cache_val(codec->reg_def_copy, i, word_size);
+ if (!val)
+ continue;
+ rbtree_node = kzalloc(sizeof *rbtree_node, GFP_KERNEL);
+ if (!rbtree_node) {
+ ret = -ENOMEM;
+ snd_soc_cache_exit(codec);
+ break;
+ }
+ rbtree_node->reg = i;
+ rbtree_node->value = val;
+ rbtree_node->defval = val;
+ snd_soc_rbtree_insert(&rbtree_ctx->root, rbtree_node);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_SND_SOC_CACHE_LZO
+struct snd_soc_lzo_ctx {
+ void *wmem;
+ void *dst;
+ const void *src;
+ size_t src_len;
+ size_t dst_len;
+ size_t decompressed_size;
+ unsigned long *sync_bmp;
+ int sync_bmp_nbits;
+};
+
+#define LZO_BLOCK_NUM 8
+static int snd_soc_lzo_block_count(void)
+{
+ return LZO_BLOCK_NUM;
+}
+
+static int snd_soc_lzo_prepare(struct snd_soc_lzo_ctx *lzo_ctx)
+{
+ lzo_ctx->wmem = kmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL);
+ if (!lzo_ctx->wmem)
+ return -ENOMEM;
+ return 0;
+}
+
+static int snd_soc_lzo_compress(struct snd_soc_lzo_ctx *lzo_ctx)
+{
+ size_t compress_size;
+ int ret;
+
+ ret = lzo1x_1_compress(lzo_ctx->src, lzo_ctx->src_len,
+ lzo_ctx->dst, &compress_size, lzo_ctx->wmem);
+ if (ret != LZO_E_OK || compress_size > lzo_ctx->dst_len)
+ return -EINVAL;
+ lzo_ctx->dst_len = compress_size;
+ return 0;
+}
+
+static int snd_soc_lzo_decompress(struct snd_soc_lzo_ctx *lzo_ctx)
+{
+ size_t dst_len;
+ int ret;
+
+ dst_len = lzo_ctx->dst_len;
+ ret = lzo1x_decompress_safe(lzo_ctx->src, lzo_ctx->src_len,
+ lzo_ctx->dst, &dst_len);
+ if (ret != LZO_E_OK || dst_len != lzo_ctx->dst_len)
+ return -EINVAL;
+ return 0;
+}
+
+static int snd_soc_lzo_compress_cache_block(struct snd_soc_codec *codec,
+ struct snd_soc_lzo_ctx *lzo_ctx)
+{
+ int ret;
+
+ lzo_ctx->dst_len = lzo1x_worst_compress(PAGE_SIZE);
+ lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL);
+ if (!lzo_ctx->dst) {
+ lzo_ctx->dst_len = 0;
+ return -ENOMEM;
+ }
+
+ ret = snd_soc_lzo_compress(lzo_ctx);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int snd_soc_lzo_decompress_cache_block(struct snd_soc_codec *codec,
+ struct snd_soc_lzo_ctx *lzo_ctx)
+{
+ int ret;
+
+ lzo_ctx->dst_len = lzo_ctx->decompressed_size;
+ lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL);
+ if (!lzo_ctx->dst) {
+ lzo_ctx->dst_len = 0;
+ return -ENOMEM;
+ }
+
+ ret = snd_soc_lzo_decompress(lzo_ctx);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static inline int snd_soc_lzo_get_blkindex(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ const struct snd_soc_codec_driver *codec_drv;
+
+ codec_drv = codec->driver;
+ return (reg * codec_drv->reg_word_size) /
+ DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count());
+}
+
+static inline int snd_soc_lzo_get_blkpos(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ const struct snd_soc_codec_driver *codec_drv;
+
+ codec_drv = codec->driver;
+ return reg % (DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()) /
+ codec_drv->reg_word_size);
+}
+
+static inline int snd_soc_lzo_get_blksize(struct snd_soc_codec *codec)
+{
+ const struct snd_soc_codec_driver *codec_drv;
+
+ codec_drv = codec->driver;
+ return DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count());
+}
+
+static int snd_soc_lzo_cache_sync(struct snd_soc_codec *codec)
+{
+ struct snd_soc_lzo_ctx **lzo_blocks;
+ unsigned int val;
+ int i;
+ int ret;
+
+ lzo_blocks = codec->reg_cache;
+ for_each_set_bit(i, lzo_blocks[0]->sync_bmp, lzo_blocks[0]->sync_bmp_nbits) {
+ WARN_ON(codec->writable_register &&
+ codec->writable_register(codec, i));
+ ret = snd_soc_cache_read(codec, i, &val);
+ if (ret)
+ return ret;
+ codec->cache_bypass = 1;
+ ret = snd_soc_write(codec, i, val);
+ codec->cache_bypass = 0;
+ if (ret)
+ return ret;
+ dev_dbg(codec->dev, "Synced register %#x, value = %#x\n",
+ i, val);
+ }
+
+ return 0;
+}
+
+static int snd_soc_lzo_cache_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ struct snd_soc_lzo_ctx *lzo_block, **lzo_blocks;
+ int ret, blkindex, blkpos;
+ size_t blksize, tmp_dst_len;
+ void *tmp_dst;
+
+ /* index of the compressed lzo block */
+ blkindex = snd_soc_lzo_get_blkindex(codec, reg);
+ /* register index within the decompressed block */
+ blkpos = snd_soc_lzo_get_blkpos(codec, reg);
+ /* size of the compressed block */
+ blksize = snd_soc_lzo_get_blksize(codec);
+ lzo_blocks = codec->reg_cache;
+ lzo_block = lzo_blocks[blkindex];
+
+ /* save the pointer and length of the compressed block */
+ tmp_dst = lzo_block->dst;
+ tmp_dst_len = lzo_block->dst_len;
+
+ /* prepare the source to be the compressed block */
+ lzo_block->src = lzo_block->dst;
+ lzo_block->src_len = lzo_block->dst_len;
+
+ /* decompress the block */
+ ret = snd_soc_lzo_decompress_cache_block(codec, lzo_block);
+ if (ret < 0) {
+ kfree(lzo_block->dst);
+ goto out;
+ }
+
+ /* write the new value to the cache */
+ if (snd_soc_set_cache_val(lzo_block->dst, blkpos, value,
+ codec->driver->reg_word_size)) {
+ kfree(lzo_block->dst);
+ goto out;
+ }
+
+ /* prepare the source to be the decompressed block */
+ lzo_block->src = lzo_block->dst;
+ lzo_block->src_len = lzo_block->dst_len;
+
+ /* compress the block */
+ ret = snd_soc_lzo_compress_cache_block(codec, lzo_block);
+ if (ret < 0) {
+ kfree(lzo_block->dst);
+ kfree(lzo_block->src);
+ goto out;
+ }
+
+ /* set the bit so we know we have to sync this register */
+ set_bit(reg, lzo_block->sync_bmp);
+ kfree(tmp_dst);
+ kfree(lzo_block->src);
+ return 0;
+out:
+ lzo_block->dst = tmp_dst;
+ lzo_block->dst_len = tmp_dst_len;
+ return ret;
+}
+
+static int snd_soc_lzo_cache_read(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int *value)
+{
+ struct snd_soc_lzo_ctx *lzo_block, **lzo_blocks;
+ int ret, blkindex, blkpos;
+ size_t blksize, tmp_dst_len;
+ void *tmp_dst;
+
+ *value = 0;
+ /* index of the compressed lzo block */
+ blkindex = snd_soc_lzo_get_blkindex(codec, reg);
+ /* register index within the decompressed block */
+ blkpos = snd_soc_lzo_get_blkpos(codec, reg);
+ /* size of the compressed block */
+ blksize = snd_soc_lzo_get_blksize(codec);
+ lzo_blocks = codec->reg_cache;
+ lzo_block = lzo_blocks[blkindex];
+
+ /* save the pointer and length of the compressed block */
+ tmp_dst = lzo_block->dst;
+ tmp_dst_len = lzo_block->dst_len;
+
+ /* prepare the source to be the compressed block */
+ lzo_block->src = lzo_block->dst;
+ lzo_block->src_len = lzo_block->dst_len;
+
+ /* decompress the block */
+ ret = snd_soc_lzo_decompress_cache_block(codec, lzo_block);
+ if (ret >= 0)
+ /* fetch the value from the cache */
+ *value = snd_soc_get_cache_val(lzo_block->dst, blkpos,
+ codec->driver->reg_word_size);
+
+ kfree(lzo_block->dst);
+ /* restore the pointer and length of the compressed block */
+ lzo_block->dst = tmp_dst;
+ lzo_block->dst_len = tmp_dst_len;
+ return 0;
+}
+
+static int snd_soc_lzo_cache_exit(struct snd_soc_codec *codec)
+{
+ struct snd_soc_lzo_ctx **lzo_blocks;
+ int i, blkcount;
+
+ lzo_blocks = codec->reg_cache;
+ if (!lzo_blocks)
+ return 0;
+
+ blkcount = snd_soc_lzo_block_count();
+ /*
+ * the pointer to the bitmap used for syncing the cache
+ * is shared amongst all lzo_blocks. Ensure it is freed
+ * only once.
+ */
+ if (lzo_blocks[0])
+ kfree(lzo_blocks[0]->sync_bmp);
+ for (i = 0; i < blkcount; ++i) {
+ if (lzo_blocks[i]) {
+ kfree(lzo_blocks[i]->wmem);
+ kfree(lzo_blocks[i]->dst);
+ }
+ /* each lzo_block is a pointer returned by kmalloc or NULL */
+ kfree(lzo_blocks[i]);
+ }
+ kfree(lzo_blocks);
+ codec->reg_cache = NULL;
+ return 0;
+}
+
+static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec)
+{
+ struct snd_soc_lzo_ctx **lzo_blocks;
+ size_t bmp_size;
+ const struct snd_soc_codec_driver *codec_drv;
+ int ret, tofree, i, blksize, blkcount;
+ const char *p, *end;
+ unsigned long *sync_bmp;
+
+ ret = 0;
+ codec_drv = codec->driver;
+
+ /*
+ * If we have not been given a default register cache
+ * then allocate a dummy zero-ed out region, compress it
+ * and remember to free it afterwards.
+ */
+ tofree = 0;
+ if (!codec->reg_def_copy)
+ tofree = 1;
+
+ if (!codec->reg_def_copy) {
+ codec->reg_def_copy = kzalloc(codec->reg_size, GFP_KERNEL);
+ if (!codec->reg_def_copy)
+ return -ENOMEM;
+ }
+
+ blkcount = snd_soc_lzo_block_count();
+ codec->reg_cache = kzalloc(blkcount * sizeof *lzo_blocks,
+ GFP_KERNEL);
+ if (!codec->reg_cache) {
+ ret = -ENOMEM;
+ goto err_tofree;
+ }
+ lzo_blocks = codec->reg_cache;
+
+ /*
+ * allocate a bitmap to be used when syncing the cache with
+ * the hardware. Each time a register is modified, the corresponding
+ * bit is set in the bitmap, so we know that we have to sync
+ * that register.
+ */
+ bmp_size = codec_drv->reg_cache_size;
+ sync_bmp = kmalloc(BITS_TO_LONGS(bmp_size) * sizeof(long),
+ GFP_KERNEL);
+ if (!sync_bmp) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ bitmap_zero(sync_bmp, bmp_size);
+
+ /* allocate the lzo blocks and initialize them */
+ for (i = 0; i < blkcount; ++i) {
+ lzo_blocks[i] = kzalloc(sizeof **lzo_blocks,
+ GFP_KERNEL);
+ if (!lzo_blocks[i]) {
+ kfree(sync_bmp);
+ ret = -ENOMEM;
+ goto err;
+ }
+ lzo_blocks[i]->sync_bmp = sync_bmp;
+ lzo_blocks[i]->sync_bmp_nbits = bmp_size;
+ /* alloc the working space for the compressed block */
+ ret = snd_soc_lzo_prepare(lzo_blocks[i]);
+ if (ret < 0)
+ goto err;
+ }
+
+ blksize = snd_soc_lzo_get_blksize(codec);
+ p = codec->reg_def_copy;
+ end = codec->reg_def_copy + codec->reg_size;
+ /* compress the register map and fill the lzo blocks */
+ for (i = 0; i < blkcount; ++i, p += blksize) {
+ lzo_blocks[i]->src = p;
+ if (p + blksize > end)
+ lzo_blocks[i]->src_len = end - p;
+ else
+ lzo_blocks[i]->src_len = blksize;
+ ret = snd_soc_lzo_compress_cache_block(codec,
+ lzo_blocks[i]);
+ if (ret < 0)
+ goto err;
+ lzo_blocks[i]->decompressed_size =
+ lzo_blocks[i]->src_len;
+ }
+
+ if (tofree) {
+ kfree(codec->reg_def_copy);
+ codec->reg_def_copy = NULL;
+ }
+ return 0;
+err:
+ snd_soc_cache_exit(codec);
+err_tofree:
+ if (tofree) {
+ kfree(codec->reg_def_copy);
+ codec->reg_def_copy = NULL;
+ }
+ return ret;
+}
+#endif
+
+static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec)
+{
+ int i;
+ int ret;
+ const struct snd_soc_codec_driver *codec_drv;
+ unsigned int val;
+
+ codec_drv = codec->driver;
+ for (i = 0; i < codec_drv->reg_cache_size; ++i) {
+ WARN_ON(codec->writable_register &&
+ codec->writable_register(codec, i));
+ ret = snd_soc_cache_read(codec, i, &val);
+ if (ret)
+ return ret;
+ if (codec->reg_def_copy)
+ if (snd_soc_get_cache_val(codec->reg_def_copy,
+ i, codec_drv->reg_word_size) == val)
+ continue;
+ ret = snd_soc_write(codec, i, val);
+ if (ret)
+ return ret;
+ dev_dbg(codec->dev, "Synced register %#x, value = %#x\n",
+ i, val);
+ }
+ return 0;
+}
+
+static int snd_soc_flat_cache_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ snd_soc_set_cache_val(codec->reg_cache, reg, value,
+ codec->driver->reg_word_size);
+ return 0;
+}
+
+static int snd_soc_flat_cache_read(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int *value)
+{
+ *value = snd_soc_get_cache_val(codec->reg_cache, reg,
+ codec->driver->reg_word_size);
+ return 0;
+}
+
+static int snd_soc_flat_cache_exit(struct snd_soc_codec *codec)
+{
+ if (!codec->reg_cache)
+ return 0;
+ kfree(codec->reg_cache);
+ codec->reg_cache = NULL;
+ return 0;
+}
+
+static int snd_soc_flat_cache_init(struct snd_soc_codec *codec)
+{
+ const struct snd_soc_codec_driver *codec_drv;
+
+ codec_drv = codec->driver;
+
+ if (codec->reg_def_copy)
+ codec->reg_cache = kmemdup(codec->reg_def_copy,
+ codec->reg_size, GFP_KERNEL);
+ else
+ codec->reg_cache = kzalloc(codec->reg_size, GFP_KERNEL);
+ if (!codec->reg_cache)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/* an array of all supported compression types */
+static const struct snd_soc_cache_ops cache_types[] = {
+ /* Flat *must* be the first entry for fallback */
+ {
+ .id = SND_SOC_FLAT_COMPRESSION,
+ .name = "flat",
+ .init = snd_soc_flat_cache_init,
+ .exit = snd_soc_flat_cache_exit,
+ .read = snd_soc_flat_cache_read,
+ .write = snd_soc_flat_cache_write,
+ .sync = snd_soc_flat_cache_sync
+ },
+#ifdef CONFIG_SND_SOC_CACHE_LZO
+ {
+ .id = SND_SOC_LZO_COMPRESSION,
+ .name = "LZO",
+ .init = snd_soc_lzo_cache_init,
+ .exit = snd_soc_lzo_cache_exit,
+ .read = snd_soc_lzo_cache_read,
+ .write = snd_soc_lzo_cache_write,
+ .sync = snd_soc_lzo_cache_sync
+ },
+#endif
+ {
+ .id = SND_SOC_RBTREE_COMPRESSION,
+ .name = "rbtree",
+ .init = snd_soc_rbtree_cache_init,
+ .exit = snd_soc_rbtree_cache_exit,
+ .read = snd_soc_rbtree_cache_read,
+ .write = snd_soc_rbtree_cache_write,
+ .sync = snd_soc_rbtree_cache_sync
+ }
+};
+
+int snd_soc_cache_init(struct snd_soc_codec *codec)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cache_types); ++i)
+ if (cache_types[i].id == codec->compress_type)
+ break;
+
+ /* Fall back to flat compression */
+ if (i == ARRAY_SIZE(cache_types)) {
+ dev_warn(codec->dev, "Could not match compress type: %d\n",
+ codec->compress_type);
+ i = 0;
+ }
+
+ mutex_init(&codec->cache_rw_mutex);
+ codec->cache_ops = &cache_types[i];
+
+ if (codec->cache_ops->init) {
+ if (codec->cache_ops->name)
+ dev_dbg(codec->dev, "Initializing %s cache for %s codec\n",
+ codec->cache_ops->name, codec->name);
+ return codec->cache_ops->init(codec);
+ }
+ return -ENOSYS;
+}
+
+/*
+ * NOTE: keep in mind that this function might be called
+ * multiple times.
+ */
+int snd_soc_cache_exit(struct snd_soc_codec *codec)
+{
+ if (codec->cache_ops && codec->cache_ops->exit) {
+ if (codec->cache_ops->name)
+ dev_dbg(codec->dev, "Destroying %s cache for %s codec\n",
+ codec->cache_ops->name, codec->name);
+ return codec->cache_ops->exit(codec);
+ }
+ return -ENOSYS;
+}
+
+/**
+ * snd_soc_cache_read: Fetch the value of a given register from the cache.
+ *
+ * @codec: CODEC to configure.
+ * @reg: The register index.
+ * @value: The value to be returned.
+ */
+int snd_soc_cache_read(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int *value)
+{
+ int ret;
+
+ mutex_lock(&codec->cache_rw_mutex);
+
+ if (value && codec->cache_ops && codec->cache_ops->read) {
+ ret = codec->cache_ops->read(codec, reg, value);
+ mutex_unlock(&codec->cache_rw_mutex);
+ return ret;
+ }
+
+ mutex_unlock(&codec->cache_rw_mutex);
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(snd_soc_cache_read);
+
+/**
+ * snd_soc_cache_write: Set the value of a given register in the cache.
+ *
+ * @codec: CODEC to configure.
+ * @reg: The register index.
+ * @value: The new register value.
+ */
+int snd_soc_cache_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ int ret;
+
+ mutex_lock(&codec->cache_rw_mutex);
+
+ if (codec->cache_ops && codec->cache_ops->write) {
+ ret = codec->cache_ops->write(codec, reg, value);
+ mutex_unlock(&codec->cache_rw_mutex);
+ return ret;
+ }
+
+ mutex_unlock(&codec->cache_rw_mutex);
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(snd_soc_cache_write);
+
+/**
+ * snd_soc_cache_sync: Sync the register cache with the hardware.
+ *
+ * @codec: CODEC to configure.
+ *
+ * Any registers that should not be synced should be marked as
+ * volatile. In general drivers can choose not to use the provided
+ * syncing functionality if they so require.
+ */
+int snd_soc_cache_sync(struct snd_soc_codec *codec)
+{
+ int ret;
+ const char *name;
+
+ if (!codec->cache_sync) {
+ return 0;
+ }
+
+ if (!codec->cache_ops || !codec->cache_ops->sync)
+ return -ENOSYS;
+
+ if (codec->cache_ops->name)
+ name = codec->cache_ops->name;
+ else
+ name = "unknown";
+
+ if (codec->cache_ops->name)
+ dev_dbg(codec->dev, "Syncing %s cache for %s codec\n",
+ codec->cache_ops->name, codec->name);
+ trace_snd_soc_cache_sync(codec, name, "start");
+ ret = codec->cache_ops->sync(codec);
+ if (!ret)
+ codec->cache_sync = 0;
+ trace_snd_soc_cache_sync(codec, name, "end");
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_cache_sync);
+
+static int snd_soc_get_reg_access_index(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ const struct snd_soc_codec_driver *codec_drv;
+ unsigned int min, max, index;
+
+ codec_drv = codec->driver;
+ min = 0;
+ max = codec_drv->reg_access_size - 1;
+ do {
+ index = (min + max) / 2;
+ if (codec_drv->reg_access_default[index].reg == reg)
+ return index;
+ if (codec_drv->reg_access_default[index].reg < reg)
+ min = index + 1;
+ else
+ max = index;
+ } while (min <= max);
+ return -1;
+}
+
+int snd_soc_default_volatile_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ int index;
+
+ if (reg >= codec->driver->reg_cache_size)
+ return 1;
+ index = snd_soc_get_reg_access_index(codec, reg);
+ if (index < 0)
+ return 0;
+ return codec->driver->reg_access_default[index].vol;
+}
+EXPORT_SYMBOL_GPL(snd_soc_default_volatile_register);
+
+int snd_soc_default_readable_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ int index;
+
+ if (reg >= codec->driver->reg_cache_size)
+ return 1;
+ index = snd_soc_get_reg_access_index(codec, reg);
+ if (index < 0)
+ return 0;
+ return codec->driver->reg_access_default[index].read;
+}
+EXPORT_SYMBOL_GPL(snd_soc_default_readable_register);
+
+int snd_soc_default_writable_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ int index;
+
+ if (reg >= codec->driver->reg_cache_size)
+ return 1;
+ index = snd_soc_get_reg_access_index(codec, reg);
+ if (index < 0)
+ return 0;
+ return codec->driver->reg_access_default[index].write;
+}
+EXPORT_SYMBOL_GPL(snd_soc_default_writable_register);
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
new file mode 100644
index 00000000..b3ab1246
--- /dev/null
+++ b/sound/soc/soc-core.c
@@ -0,0 +1,3962 @@
+/*
+ * soc-core.c -- ALSA SoC Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ * Copyright (C) 2010 Slimlogic Ltd.
+ * Copyright (C) 2010 Texas Instruments Inc.
+ * Copyright (C) 2012 Freescale Semiconductor, Inc.
+ *
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ * with code, comments and ideas from :-
+ * Richard Purdie <richard@openedhand.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 of the License, or (at your
+ * option) any later version.
+ *
+ * TODO:
+ * o Add hw rules to enforce rates, etc.
+ * o More testing with other codecs/machines.
+ * o Add more codecs and platforms to ensure good API coverage.
+ * o Support TDM on PCM and I2S
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/platform_device.h>
+#include <linux/ctype.h>
+#include <linux/slab.h>
+#include <sound/ac97_codec.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/asoc.h>
+
+#define NAME_SIZE 32
+
+static DEFINE_MUTEX(pcm_mutex);
+static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq);
+
+#ifdef CONFIG_DEBUG_FS
+struct dentry *snd_soc_debugfs_root;
+EXPORT_SYMBOL_GPL(snd_soc_debugfs_root);
+#endif
+
+static DEFINE_MUTEX(client_mutex);
+static LIST_HEAD(card_list);
+static LIST_HEAD(dai_list);
+static LIST_HEAD(platform_list);
+static LIST_HEAD(codec_list);
+
+static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num);
+
+/*
+ * This is a timeout to do a DAPM powerdown after a stream is closed().
+ * It can be used to eliminate pops between different playback streams, e.g.
+ * between two audio tracks.
+ */
+static int pmdown_time = 5000;
+module_param(pmdown_time, int, 0);
+MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
+
+/* returns the minimum number of bytes needed to represent
+ * a particular given value */
+static int min_bytes_needed(unsigned long val)
+{
+ int c = 0;
+ int i;
+
+ for (i = (sizeof val * 8) - 1; i >= 0; --i, ++c)
+ if (val & (1UL << i))
+ break;
+ c = (sizeof val * 8) - c;
+ if (!c || (c % 8))
+ c = (c + 8) / 8;
+ else
+ c /= 8;
+ return c;
+}
+
+/* fill buf which is 'len' bytes with a formatted
+ * string of the form 'reg: value\n' */
+static int format_register_str(struct snd_soc_codec *codec,
+ unsigned int reg, char *buf, size_t len)
+{
+ int wordsize = min_bytes_needed(codec->driver->reg_cache_size) * 2;
+ int regsize = codec->driver->reg_word_size * 2;
+ int ret;
+ char tmpbuf[len + 1];
+ char regbuf[regsize + 1];
+
+ /* since tmpbuf is allocated on the stack, warn the callers if they
+ * try to abuse this function */
+ WARN_ON(len > 63);
+
+ /* +2 for ': ' and + 1 for '\n' */
+ if (wordsize + regsize + 2 + 1 != len)
+ return -EINVAL;
+
+ ret = snd_soc_read(codec , reg);
+ if (ret < 0) {
+ memset(regbuf, 'X', regsize);
+ regbuf[regsize] = '\0';
+ } else {
+ snprintf(regbuf, regsize + 1, "%.*x", regsize, ret);
+ }
+
+ /* prepare the buffer */
+ snprintf(tmpbuf, len + 1, "%.*x: %s\n", wordsize, reg, regbuf);
+ /* copy it back to the caller without the '\0' */
+ memcpy(buf, tmpbuf, len);
+
+ return 0;
+}
+
+/* codec register dump */
+static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf,
+ size_t count, loff_t pos)
+{
+ int i, step = 1;
+ int wordsize, regsize;
+ int len;
+ size_t total = 0;
+ loff_t p = 0;
+
+ wordsize = min_bytes_needed(codec->driver->reg_cache_size) * 2;
+ regsize = codec->driver->reg_word_size * 2;
+
+ len = wordsize + regsize + 2 + 1;
+
+ if (!codec->driver->reg_cache_size)
+ return 0;
+
+ if (codec->driver->reg_cache_step)
+ step = codec->driver->reg_cache_step;
+
+ for (i = 0; i < codec->driver->reg_cache_size; i += step) {
+ if (codec->readable_register && !codec->readable_register(codec, i))
+ continue;
+ if (codec->driver->display_register) {
+ count += codec->driver->display_register(codec, buf + count,
+ PAGE_SIZE - count, i);
+ } else {
+ /* only support larger than PAGE_SIZE bytes debugfs
+ * entries for the default case */
+ if (p >= pos) {
+ if (total + len >= count - 1)
+ break;
+ format_register_str(codec, i, buf + total, len);
+ total += len;
+ }
+ p += len;
+ }
+ }
+
+ total = min(total, count - 1);
+
+ return total;
+}
+
+static ssize_t codec_reg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_pcm_runtime *rtd =
+ container_of(dev, struct snd_soc_pcm_runtime, dev);
+
+ return soc_codec_reg_show(rtd->codec, buf, PAGE_SIZE, 0);
+}
+
+static DEVICE_ATTR(codec_reg, 0444, codec_reg_show, NULL);
+
+static ssize_t pmdown_time_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_pcm_runtime *rtd =
+ container_of(dev, struct snd_soc_pcm_runtime, dev);
+
+ return sprintf(buf, "%ld\n", rtd->pmdown_time);
+}
+
+static ssize_t pmdown_time_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct snd_soc_pcm_runtime *rtd =
+ container_of(dev, struct snd_soc_pcm_runtime, dev);
+ int ret;
+
+ ret = strict_strtol(buf, 10, &rtd->pmdown_time);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(pmdown_time, 0644, pmdown_time_show, pmdown_time_set);
+
+#ifdef CONFIG_DEBUG_FS
+static int codec_reg_open_file(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return 0;
+}
+
+static ssize_t codec_reg_read_file(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ ssize_t ret;
+ struct snd_soc_codec *codec = file->private_data;
+ char *buf;
+
+ if (*ppos < 0 || !count)
+ return -EINVAL;
+
+ buf = kmalloc(count, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = soc_codec_reg_show(codec, buf, count, *ppos);
+ if (ret >= 0) {
+ if (copy_to_user(user_buf, buf, ret)) {
+ kfree(buf);
+ return -EFAULT;
+ }
+ *ppos += ret;
+ }
+
+ kfree(buf);
+ return ret;
+}
+
+static ssize_t codec_reg_write_file(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ char buf[32];
+ size_t buf_size;
+ char *start = buf;
+ unsigned long reg, value;
+ int step = 1;
+ struct snd_soc_codec *codec = file->private_data;
+
+ buf_size = min(count, (sizeof(buf)-1));
+ if (copy_from_user(buf, user_buf, buf_size))
+ return -EFAULT;
+ buf[buf_size] = 0;
+
+ if (codec->driver->reg_cache_step)
+ step = codec->driver->reg_cache_step;
+
+ while (*start == ' ')
+ start++;
+ reg = simple_strtoul(start, &start, 16);
+ while (*start == ' ')
+ start++;
+ if (strict_strtoul(start, 16, &value))
+ return -EINVAL;
+
+ /* Userspace has been fiddling around behind the kernel's back */
+ add_taint(TAINT_USER);
+
+ snd_soc_write(codec, reg, value);
+ return buf_size;
+}
+
+static const struct file_operations codec_reg_fops = {
+ .open = codec_reg_open_file,
+ .read = codec_reg_read_file,
+ .write = codec_reg_write_file,
+ .llseek = default_llseek,
+};
+
+static void soc_init_codec_debugfs(struct snd_soc_codec *codec)
+{
+ struct dentry *debugfs_card_root = codec->card->debugfs_card_root;
+
+ codec->debugfs_codec_root = debugfs_create_dir(codec->name,
+ debugfs_card_root);
+ if (!codec->debugfs_codec_root) {
+ printk(KERN_WARNING
+ "ASoC: Failed to create codec debugfs directory\n");
+ return;
+ }
+
+ debugfs_create_bool("cache_sync", 0444, codec->debugfs_codec_root,
+ &codec->cache_sync);
+ debugfs_create_bool("cache_only", 0444, codec->debugfs_codec_root,
+ &codec->cache_only);
+
+ codec->debugfs_reg = debugfs_create_file("codec_reg", 0644,
+ codec->debugfs_codec_root,
+ codec, &codec_reg_fops);
+ if (!codec->debugfs_reg)
+ printk(KERN_WARNING
+ "ASoC: Failed to create codec register debugfs file\n");
+
+ snd_soc_dapm_debugfs_init(&codec->dapm, codec->debugfs_codec_root);
+}
+
+static void soc_cleanup_codec_debugfs(struct snd_soc_codec *codec)
+{
+ debugfs_remove_recursive(codec->debugfs_codec_root);
+}
+
+static ssize_t codec_list_read_file(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ ssize_t len, ret = 0;
+ struct snd_soc_codec *codec;
+
+ if (!buf)
+ return -ENOMEM;
+
+ list_for_each_entry(codec, &codec_list, list) {
+ len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n",
+ codec->name);
+ if (len >= 0)
+ ret += len;
+ if (ret > PAGE_SIZE) {
+ ret = PAGE_SIZE;
+ break;
+ }
+ }
+
+ if (ret >= 0)
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static const struct file_operations codec_list_fops = {
+ .read = codec_list_read_file,
+ .llseek = default_llseek,/* read accesses f_pos */
+};
+
+static ssize_t dai_list_read_file(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ ssize_t len, ret = 0;
+ struct snd_soc_dai *dai;
+
+ if (!buf)
+ return -ENOMEM;
+
+ list_for_each_entry(dai, &dai_list, list) {
+ len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n", dai->name);
+ if (len >= 0)
+ ret += len;
+ if (ret > PAGE_SIZE) {
+ ret = PAGE_SIZE;
+ break;
+ }
+ }
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static const struct file_operations dai_list_fops = {
+ .read = dai_list_read_file,
+ .llseek = default_llseek,/* read accesses f_pos */
+};
+
+static ssize_t platform_list_read_file(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ ssize_t len, ret = 0;
+ struct snd_soc_platform *platform;
+
+ if (!buf)
+ return -ENOMEM;
+
+ list_for_each_entry(platform, &platform_list, list) {
+ len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n",
+ platform->name);
+ if (len >= 0)
+ ret += len;
+ if (ret > PAGE_SIZE) {
+ ret = PAGE_SIZE;
+ break;
+ }
+ }
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static const struct file_operations platform_list_fops = {
+ .read = platform_list_read_file,
+ .llseek = default_llseek,/* read accesses f_pos */
+};
+
+static void soc_init_card_debugfs(struct snd_soc_card *card)
+{
+ card->debugfs_card_root = debugfs_create_dir(card->name,
+ snd_soc_debugfs_root);
+ if (!card->debugfs_card_root) {
+ dev_warn(card->dev,
+ "ASoC: Failed to create codec debugfs directory\n");
+ return;
+ }
+
+ card->debugfs_pop_time = debugfs_create_u32("dapm_pop_time", 0644,
+ card->debugfs_card_root,
+ &card->pop_time);
+ if (!card->debugfs_pop_time)
+ dev_warn(card->dev,
+ "Failed to create pop time debugfs file\n");
+}
+
+static void soc_cleanup_card_debugfs(struct snd_soc_card *card)
+{
+ debugfs_remove_recursive(card->debugfs_card_root);
+}
+
+#else
+
+static inline void soc_init_codec_debugfs(struct snd_soc_codec *codec)
+{
+}
+
+static inline void soc_cleanup_codec_debugfs(struct snd_soc_codec *codec)
+{
+}
+
+static inline void soc_init_card_debugfs(struct snd_soc_card *card)
+{
+}
+
+static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card)
+{
+}
+#endif
+
+#ifdef CONFIG_SND_SOC_AC97_BUS
+/* unregister ac97 codec */
+static int soc_ac97_dev_unregister(struct snd_soc_codec *codec)
+{
+ if (codec->ac97->dev.bus)
+ device_unregister(&codec->ac97->dev);
+ return 0;
+}
+
+/* stop no dev release warning */
+static void soc_ac97_device_release(struct device *dev){}
+
+/* register ac97 codec to bus */
+static int soc_ac97_dev_register(struct snd_soc_codec *codec)
+{
+ int err;
+
+ codec->ac97->dev.bus = &ac97_bus_type;
+ codec->ac97->dev.parent = codec->card->dev;
+ codec->ac97->dev.release = soc_ac97_device_release;
+
+ dev_set_name(&codec->ac97->dev, "%d-%d:%s",
+ codec->card->snd_card->number, 0, codec->name);
+ err = device_register(&codec->ac97->dev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "Can't register ac97 bus\n");
+ codec->ac97->dev.bus = NULL;
+ return err;
+ }
+ return 0;
+}
+#endif
+
+static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int ret;
+
+ if (!codec_dai->driver->symmetric_rates &&
+ !cpu_dai->driver->symmetric_rates &&
+ !rtd->dai_link->symmetric_rates)
+ return 0;
+
+ /* This can happen if multiple streams are starting simultaneously -
+ * the second can need to get its constraints before the first has
+ * picked a rate. Complain and allow the application to carry on.
+ */
+ if (!rtd->rate) {
+ dev_warn(&rtd->dev,
+ "Not enforcing symmetric_rates due to race\n");
+ return 0;
+ }
+
+ dev_dbg(&rtd->dev, "Symmetry forces %dHz rate\n", rtd->rate);
+
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ rtd->rate, rtd->rate);
+ if (ret < 0) {
+ dev_err(&rtd->dev,
+ "Unable to apply rate symmetry constraint: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Called by ALSA when a PCM substream is opened, the runtime->hw record is
+ * then initialized and any private data can be allocated. This also calls
+ * startup for the cpu DAI, platform, machine and codec DAI.
+ */
+static int soc_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver;
+ struct snd_soc_dai_driver *codec_dai_drv = codec_dai->driver;
+ int ret = 0;
+
+ mutex_lock(&pcm_mutex);
+
+ /* startup the audio subsystem */
+ if (cpu_dai->driver->ops->startup) {
+ ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't open interface %s\n",
+ cpu_dai->name);
+ goto out;
+ }
+ }
+
+ if (platform->driver->ops && platform->driver->ops->open) {
+ ret = platform->driver->ops->open(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
+ goto platform_err;
+ }
+ }
+
+ if (codec_dai->driver->ops->startup) {
+ ret = codec_dai->driver->ops->startup(substream, codec_dai);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't open codec %s\n",
+ codec_dai->name);
+ goto codec_dai_err;
+ }
+ }
+
+ if (rtd->dai_link->ops && rtd->dai_link->ops->startup) {
+ ret = rtd->dai_link->ops->startup(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: %s startup failed\n", rtd->dai_link->name);
+ goto machine_err;
+ }
+ }
+
+ /* Check that the codec and cpu DAIs are compatible */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ runtime->hw.rate_min =
+ max(codec_dai_drv->playback.rate_min,
+ cpu_dai_drv->playback.rate_min);
+ runtime->hw.rate_max =
+ min(codec_dai_drv->playback.rate_max,
+ cpu_dai_drv->playback.rate_max);
+ runtime->hw.channels_min =
+ max(codec_dai_drv->playback.channels_min,
+ cpu_dai_drv->playback.channels_min);
+ runtime->hw.channels_max =
+ min(codec_dai_drv->playback.channels_max,
+ cpu_dai_drv->playback.channels_max);
+ runtime->hw.formats =
+ codec_dai_drv->playback.formats & cpu_dai_drv->playback.formats;
+ runtime->hw.rates =
+ codec_dai_drv->playback.rates & cpu_dai_drv->playback.rates;
+ if (codec_dai_drv->playback.rates
+ & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
+ runtime->hw.rates |= cpu_dai_drv->playback.rates;
+ if (cpu_dai_drv->playback.rates
+ & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
+ runtime->hw.rates |= codec_dai_drv->playback.rates;
+ } else {
+ runtime->hw.rate_min =
+ max(codec_dai_drv->capture.rate_min,
+ cpu_dai_drv->capture.rate_min);
+ runtime->hw.rate_max =
+ min(codec_dai_drv->capture.rate_max,
+ cpu_dai_drv->capture.rate_max);
+ runtime->hw.channels_min =
+ max(codec_dai_drv->capture.channels_min,
+ cpu_dai_drv->capture.channels_min);
+ runtime->hw.channels_max =
+ min(codec_dai_drv->capture.channels_max,
+ cpu_dai_drv->capture.channels_max);
+ runtime->hw.formats =
+ codec_dai_drv->capture.formats & cpu_dai_drv->capture.formats;
+ runtime->hw.rates =
+ codec_dai_drv->capture.rates & cpu_dai_drv->capture.rates;
+ if (codec_dai_drv->capture.rates
+ & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
+ runtime->hw.rates |= cpu_dai_drv->capture.rates;
+ if (cpu_dai_drv->capture.rates
+ & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
+ runtime->hw.rates |= codec_dai_drv->capture.rates;
+ }
+
+ ret = -EINVAL;
+ snd_pcm_limit_hw_rates(runtime);
+ if (!runtime->hw.rates) {
+ printk(KERN_ERR "asoc: %s <-> %s No matching rates\n",
+ codec_dai->name, cpu_dai->name);
+ goto config_err;
+ }
+ if (!runtime->hw.formats) {
+ printk(KERN_ERR "asoc: %s <-> %s No matching formats\n",
+ codec_dai->name, cpu_dai->name);
+ goto config_err;
+ }
+ if (!runtime->hw.channels_min || !runtime->hw.channels_max ||
+ runtime->hw.channels_min > runtime->hw.channels_max) {
+ printk(KERN_ERR "asoc: %s <-> %s No matching channels\n",
+ codec_dai->name, cpu_dai->name);
+ goto config_err;
+ }
+
+ /* Symmetry only applies if we've already got an active stream. */
+ if (cpu_dai->active || codec_dai->active) {
+ ret = soc_pcm_apply_symmetry(substream);
+ if (ret != 0)
+ goto config_err;
+ }
+
+ pr_debug("asoc: %s <-> %s info:\n",
+ codec_dai->name, cpu_dai->name);
+ pr_debug("asoc: rate mask 0x%x\n", runtime->hw.rates);
+ pr_debug("asoc: min ch %d max ch %d\n", runtime->hw.channels_min,
+ runtime->hw.channels_max);
+ pr_debug("asoc: min rate %d max rate %d\n", runtime->hw.rate_min,
+ runtime->hw.rate_max);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ cpu_dai->playback_active++;
+ codec_dai->playback_active++;
+ } else {
+ cpu_dai->capture_active++;
+ codec_dai->capture_active++;
+ }
+ cpu_dai->active++;
+ codec_dai->active++;
+ rtd->codec->active++;
+ mutex_unlock(&pcm_mutex);
+ return 0;
+
+config_err:
+ if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
+ rtd->dai_link->ops->shutdown(substream);
+
+machine_err:
+ if (codec_dai->driver->ops->shutdown)
+ codec_dai->driver->ops->shutdown(substream, codec_dai);
+
+codec_dai_err:
+ if (platform->driver->ops && platform->driver->ops->close)
+ platform->driver->ops->close(substream);
+
+platform_err:
+ if (cpu_dai->driver->ops->shutdown)
+ cpu_dai->driver->ops->shutdown(substream, cpu_dai);
+out:
+ mutex_unlock(&pcm_mutex);
+ return ret;
+}
+
+/*
+ * Power down the audio subsystem pmdown_time msecs after close is called.
+ * This is to ensure there are no pops or clicks in between any music tracks
+ * due to DAPM power cycling.
+ */
+static void close_delayed_work(struct work_struct *work)
+{
+ struct snd_soc_pcm_runtime *rtd =
+ container_of(work, struct snd_soc_pcm_runtime, delayed_work.work);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+ mutex_lock(&pcm_mutex);
+
+ pr_debug("pop wq checking: %s status: %s waiting: %s\n",
+ codec_dai->driver->playback.stream_name,
+ codec_dai->playback_active ? "active" : "inactive",
+ codec_dai->pop_wait ? "yes" : "no");
+
+ /* are we waiting on this codec DAI stream */
+ if (codec_dai->pop_wait == 1) {
+ codec_dai->pop_wait = 0;
+ snd_soc_dapm_stream_event(rtd,
+ codec_dai->driver->playback.stream_name,
+ SND_SOC_DAPM_STREAM_STOP);
+ }
+
+ mutex_unlock(&pcm_mutex);
+}
+
+/*
+ * Called by ALSA when a PCM substream is closed. Private data can be
+ * freed here. The cpu DAI, codec DAI, machine and platform are also
+ * shutdown.
+ */
+static int soc_codec_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ mutex_lock(&pcm_mutex);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ cpu_dai->playback_active--;
+ codec_dai->playback_active--;
+ } else {
+ cpu_dai->capture_active--;
+ codec_dai->capture_active--;
+ }
+
+ cpu_dai->active--;
+ codec_dai->active--;
+ codec->active--;
+
+ if (!cpu_dai->active && !codec_dai->active)
+ rtd->rate = 0;
+
+ /* Muting the DAC suppresses artifacts caused during digital
+ * shutdown, for example from stopping clocks.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dai_digital_mute(codec_dai, 1);
+
+ if (cpu_dai->driver->ops->shutdown)
+ cpu_dai->driver->ops->shutdown(substream, cpu_dai);
+
+ if (codec_dai->driver->ops->shutdown)
+ codec_dai->driver->ops->shutdown(substream, codec_dai);
+
+ if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
+ rtd->dai_link->ops->shutdown(substream);
+
+ if (platform->driver->ops && platform->driver->ops->close)
+ platform->driver->ops->close(substream);
+ cpu_dai->runtime = NULL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* start delayed pop wq here for playback streams */
+ codec_dai->pop_wait = 1;
+ schedule_delayed_work(&rtd->delayed_work,
+ msecs_to_jiffies(rtd->pmdown_time));
+ } else {
+ /* capture streams can be powered down now */
+ snd_soc_dapm_stream_event(rtd,
+ codec_dai->driver->capture.stream_name,
+ SND_SOC_DAPM_STREAM_STOP);
+ }
+
+ mutex_unlock(&pcm_mutex);
+ return 0;
+}
+
+/*
+ * Called by ALSA when the PCM substream is prepared, can set format, sample
+ * rate, etc. This function is non atomic and can be called multiple times,
+ * it can refer to the runtime info.
+ */
+static int soc_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int ret = 0;
+
+ mutex_lock(&pcm_mutex);
+
+ if (rtd->dai_link->ops && rtd->dai_link->ops->prepare) {
+ ret = rtd->dai_link->ops->prepare(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: machine prepare error\n");
+ goto out;
+ }
+ }
+
+ if (platform->driver->ops && platform->driver->ops->prepare) {
+ ret = platform->driver->ops->prepare(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: platform prepare error\n");
+ goto out;
+ }
+ }
+
+ if (codec_dai->driver->ops->prepare) {
+ ret = codec_dai->driver->ops->prepare(substream, codec_dai);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: codec DAI prepare error\n");
+ goto out;
+ }
+ }
+
+ if (cpu_dai->driver->ops->prepare) {
+ ret = cpu_dai->driver->ops->prepare(substream, cpu_dai);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: cpu DAI prepare error\n");
+ goto out;
+ }
+ }
+
+ /* cancel any delayed stream shutdown that is pending */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ codec_dai->pop_wait) {
+ codec_dai->pop_wait = 0;
+ cancel_delayed_work(&rtd->delayed_work);
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dapm_stream_event(rtd,
+ codec_dai->driver->playback.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+ else
+ snd_soc_dapm_stream_event(rtd,
+ codec_dai->driver->capture.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+
+ snd_soc_dai_digital_mute(codec_dai, 0);
+
+out:
+ mutex_unlock(&pcm_mutex);
+ return ret;
+}
+
+/*
+ * Called by ALSA when the hardware params are set by application. This
+ * function can also be called multiple times and can allocate buffers
+ * (using snd_pcm_lib_* ). It's non-atomic.
+ */
+static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int ret = 0;
+
+ mutex_lock(&pcm_mutex);
+
+ if (rtd->dai_link->ops && rtd->dai_link->ops->hw_params) {
+ ret = rtd->dai_link->ops->hw_params(substream, params);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: machine hw_params failed\n");
+ goto out;
+ }
+ }
+
+ if (codec_dai->driver->ops->hw_params) {
+ ret = codec_dai->driver->ops->hw_params(substream, params, codec_dai);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't set codec %s hw params\n",
+ codec_dai->name);
+ goto codec_err;
+ }
+ }
+
+ if (cpu_dai->driver->ops->hw_params) {
+ ret = cpu_dai->driver->ops->hw_params(substream, params, cpu_dai);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: interface %s hw params failed\n",
+ cpu_dai->name);
+ goto interface_err;
+ }
+ }
+
+ if (platform->driver->ops && platform->driver->ops->hw_params) {
+ ret = platform->driver->ops->hw_params(substream, params);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: platform %s hw params failed\n",
+ platform->name);
+ goto platform_err;
+ }
+ }
+
+ rtd->rate = params_rate(params);
+
+out:
+ mutex_unlock(&pcm_mutex);
+ return ret;
+
+platform_err:
+ if (cpu_dai->driver->ops->hw_free)
+ cpu_dai->driver->ops->hw_free(substream, cpu_dai);
+
+interface_err:
+ if (codec_dai->driver->ops->hw_free)
+ codec_dai->driver->ops->hw_free(substream, codec_dai);
+
+codec_err:
+ if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free)
+ rtd->dai_link->ops->hw_free(substream);
+
+ mutex_unlock(&pcm_mutex);
+ return ret;
+}
+
+/*
+ * Frees resources allocated by hw_params, can be called multiple times
+ */
+static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_codec *codec = rtd->codec;
+
+ mutex_lock(&pcm_mutex);
+
+ /* apply codec digital mute */
+ if (!codec->active)
+ snd_soc_dai_digital_mute(codec_dai, 1);
+
+ /* free any machine hw params */
+ if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free)
+ rtd->dai_link->ops->hw_free(substream);
+
+ /* free any DMA resources */
+ if (platform->driver->ops && platform->driver->ops->hw_free)
+ platform->driver->ops->hw_free(substream);
+
+ /* now free hw params for the DAIs */
+ if (codec_dai->driver->ops->hw_free)
+ codec_dai->driver->ops->hw_free(substream, codec_dai);
+
+ if (cpu_dai->driver->ops->hw_free)
+ cpu_dai->driver->ops->hw_free(substream, cpu_dai);
+
+ mutex_unlock(&pcm_mutex);
+ return 0;
+}
+
+static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int ret;
+
+ if (codec_dai->driver->ops->trigger) {
+ ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (platform->driver->ops && platform->driver->ops->trigger) {
+ ret = platform->driver->ops->trigger(substream, cmd);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (cpu_dai->driver->ops->trigger) {
+ ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (rtd->dai_link->ops && rtd->dai_link->ops->trigger) {
+ ret = rtd->dai_link->ops->trigger(substream, cmd);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int soc_pcm_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int ret;
+
+ if (rtd->dai_link->ops && rtd->dai_link->ops->ioctl) {
+ ret = rtd->dai_link->ops->ioctl(substream, cmd, arg);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (codec_dai->driver->ops->ioctl) {
+ ret = codec_dai->driver->ops->ioctl(substream, cmd, arg, codec_dai);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (platform->driver->ops->ioctl) {
+ ret = platform->driver->ops->ioctl(substream, cmd, arg);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (cpu_dai->driver->ops->ioctl) {
+ ret = cpu_dai->driver->ops->ioctl(substream, cmd, arg, cpu_dai);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * soc level wrapper for pointer callback
+ * If cpu_dai, codec_dai, platform driver has the delay callback, than
+ * the runtime->delay will be updated accordingly.
+ */
+static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_pcm_uframes_t offset = 0;
+ snd_pcm_sframes_t delay = 0;
+
+ if (platform->driver->ops && platform->driver->ops->pointer)
+ offset = platform->driver->ops->pointer(substream);
+
+ if (cpu_dai->driver->ops->delay)
+ delay += cpu_dai->driver->ops->delay(substream, cpu_dai);
+
+ if (codec_dai->driver->ops->delay)
+ delay += codec_dai->driver->ops->delay(substream, codec_dai);
+
+ if (platform->driver->delay)
+ delay += platform->driver->delay(substream, codec_dai);
+
+ runtime->delay = delay;
+
+ return offset;
+}
+
+#ifdef CONFIG_PM_SLEEP
+/* powers down audio subsystem for suspend */
+int snd_soc_suspend(struct device *dev)
+{
+ struct snd_soc_card *card = dev_get_drvdata(dev);
+ struct snd_soc_codec *codec;
+ int i;
+
+ /* If the initialization of this soc device failed, there is no codec
+ * associated with it. Just bail out in this case.
+ */
+ if (list_empty(&card->codec_dev_list))
+ return 0;
+
+ /* Due to the resume being scheduled into a workqueue we could
+ * suspend before that's finished - wait for it to complete.
+ */
+ snd_power_lock(card->snd_card);
+ snd_power_wait(card->snd_card, SNDRV_CTL_POWER_D0);
+ snd_power_unlock(card->snd_card);
+
+ /* we're going to block userspace touching us until resume completes */
+ snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D3hot);
+
+ /* mute any active DACs */
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_dai *dai = card->rtd[i].codec_dai;
+ struct snd_soc_dai_driver *drv = dai->driver;
+
+ if (card->rtd[i].dai_link->ignore_suspend)
+ continue;
+
+ if (drv->ops->digital_mute && dai->playback_active)
+ drv->ops->digital_mute(dai, 1);
+ }
+
+ /* suspend all pcms */
+ for (i = 0; i < card->num_rtd; i++) {
+ if (card->rtd[i].dai_link->ignore_suspend)
+ continue;
+
+ snd_pcm_suspend_all(card->rtd[i].pcm);
+ }
+
+ if (card->suspend_pre)
+ card->suspend_pre(card);
+
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
+ struct snd_soc_platform *platform = card->rtd[i].platform;
+
+ if (card->rtd[i].dai_link->ignore_suspend)
+ continue;
+
+ if (cpu_dai->driver->suspend && !cpu_dai->driver->ac97_control)
+ cpu_dai->driver->suspend(cpu_dai);
+ if (platform->driver->suspend && !platform->suspended) {
+ platform->driver->suspend(cpu_dai);
+ platform->suspended = 1;
+ }
+ }
+
+ /* close any waiting streams and save state */
+ for (i = 0; i < card->num_rtd; i++) {
+ flush_delayed_work_sync(&card->rtd[i].delayed_work);
+ card->rtd[i].codec->dapm.suspend_bias_level = card->rtd[i].codec->dapm.bias_level;
+ }
+
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_dai_driver *driver = card->rtd[i].codec_dai->driver;
+
+ if (card->rtd[i].dai_link->ignore_suspend)
+ continue;
+
+ if (driver->playback.stream_name != NULL)
+ snd_soc_dapm_stream_event(&card->rtd[i], driver->playback.stream_name,
+ SND_SOC_DAPM_STREAM_SUSPEND);
+
+ if (driver->capture.stream_name != NULL)
+ snd_soc_dapm_stream_event(&card->rtd[i], driver->capture.stream_name,
+ SND_SOC_DAPM_STREAM_SUSPEND);
+ }
+
+ /* suspend all CODECs */
+ list_for_each_entry(codec, &card->codec_dev_list, card_list) {
+ /* If there are paths active then the CODEC will be held with
+ * bias _ON and should not be suspended. */
+ if (!codec->suspended && codec->driver->suspend) {
+ switch (codec->dapm.bias_level) {
+ case SND_SOC_BIAS_STANDBY:
+ case SND_SOC_BIAS_OFF:
+ codec->driver->suspend(codec, PMSG_SUSPEND);
+ codec->suspended = 1;
+ codec->cache_sync = 1;
+ break;
+ default:
+ dev_dbg(codec->dev, "CODEC is on over suspend\n");
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
+
+ if (card->rtd[i].dai_link->ignore_suspend)
+ continue;
+
+ if (cpu_dai->driver->suspend && cpu_dai->driver->ac97_control)
+ cpu_dai->driver->suspend(cpu_dai);
+ }
+
+ if (card->suspend_post)
+ card->suspend_post(card);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_suspend);
+
+/* deferred resume work, so resume can complete before we finished
+ * setting our codec back up, which can be very slow on I2C
+ */
+static void soc_resume_deferred(struct work_struct *work)
+{
+ struct snd_soc_card *card =
+ container_of(work, struct snd_soc_card, deferred_resume_work);
+ struct snd_soc_codec *codec;
+ int i;
+
+ /* our power state is still SNDRV_CTL_POWER_D3hot from suspend time,
+ * so userspace apps are blocked from touching us
+ */
+
+ dev_dbg(card->dev, "starting resume work\n");
+
+ /* Bring us up into D2 so that DAPM starts enabling things */
+ snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2);
+
+ if (card->resume_pre)
+ card->resume_pre(card);
+
+ /* resume AC97 DAIs */
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
+
+ if (card->rtd[i].dai_link->ignore_suspend)
+ continue;
+
+ if (cpu_dai->driver->resume && cpu_dai->driver->ac97_control)
+ cpu_dai->driver->resume(cpu_dai);
+ }
+
+ list_for_each_entry(codec, &card->codec_dev_list, card_list) {
+ /* If the CODEC was idle over suspend then it will have been
+ * left with bias OFF or STANDBY and suspended so we must now
+ * resume. Otherwise the suspend was suppressed.
+ */
+ if (codec->driver->resume && codec->suspended) {
+ switch (codec->dapm.bias_level) {
+ case SND_SOC_BIAS_STANDBY:
+ case SND_SOC_BIAS_OFF:
+ codec->driver->resume(codec);
+ codec->suspended = 0;
+ break;
+ default:
+ dev_dbg(codec->dev, "CODEC was on over suspend\n");
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_dai_driver *driver = card->rtd[i].codec_dai->driver;
+
+ if (card->rtd[i].dai_link->ignore_suspend)
+ continue;
+
+ if (driver->playback.stream_name != NULL)
+ snd_soc_dapm_stream_event(&card->rtd[i], driver->playback.stream_name,
+ SND_SOC_DAPM_STREAM_RESUME);
+
+ if (driver->capture.stream_name != NULL)
+ snd_soc_dapm_stream_event(&card->rtd[i], driver->capture.stream_name,
+ SND_SOC_DAPM_STREAM_RESUME);
+ }
+
+ /* unmute any active DACs */
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_dai *dai = card->rtd[i].codec_dai;
+ struct snd_soc_dai_driver *drv = dai->driver;
+
+ if (card->rtd[i].dai_link->ignore_suspend)
+ continue;
+
+ if (drv->ops->digital_mute && dai->playback_active)
+ drv->ops->digital_mute(dai, 0);
+ }
+
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
+ struct snd_soc_platform *platform = card->rtd[i].platform;
+
+ if (card->rtd[i].dai_link->ignore_suspend)
+ continue;
+
+ if (cpu_dai->driver->resume && !cpu_dai->driver->ac97_control)
+ cpu_dai->driver->resume(cpu_dai);
+ if (platform->driver->resume && platform->suspended) {
+ platform->driver->resume(cpu_dai);
+ platform->suspended = 0;
+ }
+ }
+
+ if (card->resume_post)
+ card->resume_post(card);
+
+ dev_dbg(card->dev, "resume work completed\n");
+
+ /* userspace can access us now we are back as we were before */
+ snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0);
+}
+
+/* powers up audio subsystem after a suspend */
+int snd_soc_resume(struct device *dev)
+{
+ struct snd_soc_card *card = dev_get_drvdata(dev);
+ int i, ac97_control = 0;
+
+ /* AC97 devices might have other drivers hanging off them so
+ * need to resume immediately. Other drivers don't have that
+ * problem and may take a substantial amount of time to resume
+ * due to I/O costs and anti-pop so handle them out of line.
+ */
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
+ ac97_control |= cpu_dai->driver->ac97_control;
+ }
+ if (card->num_rtd) {
+ if (ac97_control) {
+ dev_dbg(dev, "Resuming AC97 immediately\n");
+ soc_resume_deferred(&card->deferred_resume_work);
+ } else {
+ dev_dbg(dev, "Scheduling resume work\n");
+ if (!schedule_work(&card->deferred_resume_work))
+ dev_err(dev, "resume work item may be lost\n");
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_resume);
+#else
+#define snd_soc_suspend NULL
+#define snd_soc_resume NULL
+#endif
+
+static struct snd_soc_dai_ops null_dai_ops = {
+};
+
+static int soc_bind_dai_link(struct snd_soc_card *card, int num)
+{
+ struct snd_soc_dai_link *dai_link = &card->dai_link[num];
+ struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
+ struct snd_soc_codec *codec;
+ struct snd_soc_platform *platform;
+ struct snd_soc_dai *codec_dai, *cpu_dai;
+ const char *platform_name;
+
+ if (rtd->complete)
+ return 1;
+ dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num);
+
+ /* do we already have the CPU DAI for this link ? */
+ if (rtd->cpu_dai) {
+ goto find_codec;
+ }
+ /* no, then find CPU DAI from registered DAIs*/
+ list_for_each_entry(cpu_dai, &dai_list, list) {
+ if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) {
+ rtd->cpu_dai = cpu_dai;
+ goto find_codec;
+ }
+ }
+ dev_dbg(card->dev, "CPU DAI %s not registered\n",
+ dai_link->cpu_dai_name);
+
+find_codec:
+ /* do we already have the CODEC for this link ? */
+ if (rtd->codec) {
+ goto find_platform;
+ }
+
+ /* no, then find CODEC from registered CODECs*/
+ list_for_each_entry(codec, &codec_list, list) {
+ if (!strcmp(codec->name, dai_link->codec_name)) {
+ rtd->codec = codec;
+
+ /* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
+ list_for_each_entry(codec_dai, &dai_list, list) {
+ if (codec->dev == codec_dai->dev &&
+ !strcmp(codec_dai->name, dai_link->codec_dai_name)) {
+ rtd->codec_dai = codec_dai;
+ goto find_platform;
+ }
+ }
+ dev_dbg(card->dev, "CODEC DAI %s not registered\n",
+ dai_link->codec_dai_name);
+
+ goto find_platform;
+ }
+ }
+ dev_dbg(card->dev, "CODEC %s not registered\n",
+ dai_link->codec_name);
+
+find_platform:
+ /* do we need a platform? */
+ if (rtd->platform)
+ goto out;
+
+ /* if there's no platform we match on the empty platform */
+ platform_name = dai_link->platform_name;
+ if (!platform_name)
+ platform_name = "snd-soc-dummy";
+
+ /* no, then find one from the set of registered platforms */
+ list_for_each_entry(platform, &platform_list, list) {
+ if (!strcmp(platform->name, platform_name)) {
+ rtd->platform = platform;
+ goto out;
+ }
+ }
+
+ dev_dbg(card->dev, "platform %s not registered\n",
+ dai_link->platform_name);
+ return 0;
+
+out:
+ /* mark rtd as complete if we found all 4 of our client devices */
+ if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) {
+ rtd->complete = 1;
+ card->num_rtd++;
+ }
+ return 1;
+}
+
+static void soc_remove_codec(struct snd_soc_codec *codec)
+{
+ int err;
+
+ if (codec->driver->remove) {
+ err = codec->driver->remove(codec);
+ if (err < 0)
+ dev_err(codec->dev,
+ "asoc: failed to remove %s: %d\n",
+ codec->name, err);
+ }
+
+ /* Make sure all DAPM widgets are freed */
+ snd_soc_dapm_free(&codec->dapm);
+
+ soc_cleanup_codec_debugfs(codec);
+ codec->probed = 0;
+ list_del(&codec->card_list);
+ module_put(codec->dev->driver->owner);
+}
+
+static void soc_remove_dai_link(struct snd_soc_card *card, int num)
+{
+ struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;
+ int err;
+
+ /* unregister the rtd device */
+ if (rtd->dev_registered) {
+ device_remove_file(&rtd->dev, &dev_attr_pmdown_time);
+ device_remove_file(&rtd->dev, &dev_attr_codec_reg);
+ device_unregister(&rtd->dev);
+ rtd->dev_registered = 0;
+ }
+
+ /* remove the CODEC DAI */
+ if (codec_dai && codec_dai->probed) {
+ if (codec_dai->driver->remove) {
+ err = codec_dai->driver->remove(codec_dai);
+ if (err < 0)
+ printk(KERN_ERR "asoc: failed to remove %s\n", codec_dai->name);
+ }
+ codec_dai->probed = 0;
+ list_del(&codec_dai->card_list);
+ }
+
+ /* remove the platform */
+ if (platform && platform->probed) {
+ if (platform->driver->remove) {
+ err = platform->driver->remove(platform);
+ if (err < 0)
+ printk(KERN_ERR "asoc: failed to remove %s\n", platform->name);
+ }
+ platform->probed = 0;
+ list_del(&platform->card_list);
+ module_put(platform->dev->driver->owner);
+ }
+
+ /* remove the CODEC */
+ if (codec && codec->probed)
+ soc_remove_codec(codec);
+
+ /* remove the cpu_dai */
+ if (cpu_dai && cpu_dai->probed) {
+ if (cpu_dai->driver->remove) {
+ err = cpu_dai->driver->remove(cpu_dai);
+ if (err < 0)
+ printk(KERN_ERR "asoc: failed to remove %s\n", cpu_dai->name);
+ }
+ cpu_dai->probed = 0;
+ list_del(&cpu_dai->card_list);
+ module_put(cpu_dai->dev->driver->owner);
+ }
+}
+
+static void soc_remove_dai_links(struct snd_soc_card *card)
+{
+ int i;
+
+ for (i = 0; i < card->num_rtd; i++)
+ soc_remove_dai_link(card, i);
+
+ card->num_rtd = 0;
+}
+
+static void soc_set_name_prefix(struct snd_soc_card *card,
+ struct snd_soc_codec *codec)
+{
+ int i;
+
+ if (card->codec_conf == NULL)
+ return;
+
+ for (i = 0; i < card->num_configs; i++) {
+ struct snd_soc_codec_conf *map = &card->codec_conf[i];
+ if (map->dev_name && !strcmp(codec->name, map->dev_name)) {
+ codec->name_prefix = map->name_prefix;
+ break;
+ }
+ }
+}
+
+static int soc_probe_codec(struct snd_soc_card *card,
+ struct snd_soc_codec *codec)
+{
+ int ret = 0;
+ const struct snd_soc_codec_driver *driver = codec->driver;
+
+ codec->card = card;
+ codec->dapm.card = card;
+ soc_set_name_prefix(card, codec);
+
+ if (!try_module_get(codec->dev->driver->owner))
+ return -ENODEV;
+
+ soc_init_codec_debugfs(codec);
+
+ if (driver->dapm_widgets)
+ snd_soc_dapm_new_controls(&codec->dapm, driver->dapm_widgets,
+ driver->num_dapm_widgets);
+
+ if (driver->probe) {
+ ret = driver->probe(codec);
+ if (ret < 0) {
+ dev_err(codec->dev,
+ "asoc: failed to probe CODEC %s: %d\n",
+ codec->name, ret);
+ goto err_probe;
+ }
+ }
+
+ if (driver->controls)
+ snd_soc_add_controls(codec, driver->controls,
+ driver->num_controls);
+ if (driver->dapm_routes)
+ snd_soc_dapm_add_routes(&codec->dapm, driver->dapm_routes,
+ driver->num_dapm_routes);
+
+ /* mark codec as probed and add to card codec list */
+ codec->probed = 1;
+ list_add(&codec->card_list, &card->codec_dev_list);
+ list_add(&codec->dapm.list, &card->dapm_list);
+
+ return 0;
+
+err_probe:
+ soc_cleanup_codec_debugfs(codec);
+ module_put(codec->dev->driver->owner);
+
+ return ret;
+}
+
+static void rtd_release(struct device *dev) {}
+
+static int soc_post_component_init(struct snd_soc_card *card,
+ struct snd_soc_codec *codec,
+ int num, int dailess)
+{
+ struct snd_soc_dai_link *dai_link = NULL;
+ struct snd_soc_aux_dev *aux_dev = NULL;
+ struct snd_soc_pcm_runtime *rtd;
+ const char *temp, *name;
+ int ret = 0;
+
+ if (!dailess) {
+ dai_link = &card->dai_link[num];
+ rtd = &card->rtd[num];
+ name = dai_link->name;
+ } else {
+ aux_dev = &card->aux_dev[num];
+ rtd = &card->rtd_aux[num];
+ name = aux_dev->name;
+ }
+ rtd->card = card;
+
+ /* machine controls, routes and widgets are not prefixed */
+ temp = codec->name_prefix;
+ codec->name_prefix = NULL;
+
+ /* do machine specific initialization */
+ if (!dailess && dai_link->init)
+ ret = dai_link->init(rtd);
+ else if (dailess && aux_dev->init)
+ ret = aux_dev->init(&codec->dapm);
+ if (ret < 0) {
+ dev_err(card->dev, "asoc: failed to init %s: %d\n", name, ret);
+ return ret;
+ }
+ codec->name_prefix = temp;
+
+ /* Make sure all DAPM widgets are instantiated */
+ snd_soc_dapm_new_widgets(&codec->dapm);
+
+ /* register the rtd device */
+ rtd->codec = codec;
+ rtd->dev.parent = card->dev;
+ rtd->dev.release = rtd_release;
+ rtd->dev.init_name = name;
+ ret = device_register(&rtd->dev);
+ if (ret < 0) {
+ dev_err(card->dev,
+ "asoc: failed to register runtime device: %d\n", ret);
+ return ret;
+ }
+ rtd->dev_registered = 1;
+
+ /* add DAPM sysfs entries for this codec */
+ ret = snd_soc_dapm_sys_add(&rtd->dev);
+ if (ret < 0)
+ dev_err(codec->dev,
+ "asoc: failed to add codec dapm sysfs entries: %d\n",
+ ret);
+
+ /* add codec sysfs entries */
+ ret = device_create_file(&rtd->dev, &dev_attr_codec_reg);
+ if (ret < 0)
+ dev_err(codec->dev,
+ "asoc: failed to add codec sysfs files: %d\n", ret);
+
+ return 0;
+}
+
+static int soc_probe_dai_link(struct snd_soc_card *card, int num)
+{
+ struct snd_soc_dai_link *dai_link = &card->dai_link[num];
+ struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;
+ int ret;
+
+ dev_dbg(card->dev, "probe %s dai link %d\n", card->name, num);
+
+ /* config components */
+ codec_dai->codec = codec;
+ cpu_dai->platform = platform;
+ codec_dai->card = card;
+ cpu_dai->card = card;
+
+ /* set default power off timeout */
+ rtd->pmdown_time = pmdown_time;
+
+ /* probe the cpu_dai */
+ if (!cpu_dai->probed) {
+ if (!try_module_get(cpu_dai->dev->driver->owner))
+ return -ENODEV;
+
+ if (cpu_dai->driver->probe) {
+ ret = cpu_dai->driver->probe(cpu_dai);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n",
+ cpu_dai->name);
+ module_put(cpu_dai->dev->driver->owner);
+ return ret;
+ }
+ }
+ cpu_dai->probed = 1;
+ /* mark cpu_dai as probed and add to card cpu_dai list */
+ list_add(&cpu_dai->card_list, &card->dai_dev_list);
+ }
+
+ /* probe the CODEC */
+ if (!codec->probed) {
+ ret = soc_probe_codec(card, codec);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* probe the platform */
+ if (!platform->probed) {
+ if (!try_module_get(platform->dev->driver->owner))
+ return -ENODEV;
+
+ if (platform->driver->probe) {
+ ret = platform->driver->probe(platform);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: failed to probe platform %s\n",
+ platform->name);
+ module_put(platform->dev->driver->owner);
+ return ret;
+ }
+ }
+ /* mark platform as probed and add to card platform list */
+ platform->probed = 1;
+ list_add(&platform->card_list, &card->platform_dev_list);
+ }
+
+ /* probe the CODEC DAI */
+ if (!codec_dai->probed) {
+ if (codec_dai->driver->probe) {
+ ret = codec_dai->driver->probe(codec_dai);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: failed to probe CODEC DAI %s\n",
+ codec_dai->name);
+ return ret;
+ }
+ }
+
+ /* mark cpu_dai as probed and add to card cpu_dai list */
+ codec_dai->probed = 1;
+ list_add(&codec_dai->card_list, &card->dai_dev_list);
+ }
+
+ /* DAPM dai link stream work */
+ INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
+
+ ret = soc_post_component_init(card, codec, num, 0);
+ if (ret)
+ return ret;
+
+ ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);
+ if (ret < 0)
+ printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");
+
+ /* create the pcm */
+ ret = soc_new_pcm(rtd, num);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't create pcm %s\n", dai_link->stream_name);
+ return ret;
+ }
+
+ /* add platform data for AC97 devices */
+ if (rtd->codec_dai->driver->ac97_control)
+ snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);
+
+ return 0;
+}
+
+#ifdef CONFIG_SND_SOC_AC97_BUS
+static int soc_register_ac97_dai_link(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+
+ /* Only instantiate AC97 if not already done by the adaptor
+ * for the generic AC97 subsystem.
+ */
+ if (rtd->codec_dai->driver->ac97_control && !rtd->codec->ac97_registered) {
+ /*
+ * It is possible that the AC97 device is already registered to
+ * the device subsystem. This happens when the device is created
+ * via snd_ac97_mixer(). Currently only SoC codec that does so
+ * is the generic AC97 glue but others migh emerge.
+ *
+ * In those cases we don't try to register the device again.
+ */
+ if (!rtd->codec->ac97_created)
+ return 0;
+
+ ret = soc_ac97_dev_register(rtd->codec);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: AC97 device register failed\n");
+ return ret;
+ }
+
+ rtd->codec->ac97_registered = 1;
+ }
+ return 0;
+}
+
+static void soc_unregister_ac97_dai_link(struct snd_soc_codec *codec)
+{
+ if (codec->ac97_registered) {
+ soc_ac97_dev_unregister(codec);
+ codec->ac97_registered = 0;
+ }
+}
+#endif
+
+static int soc_probe_aux_dev(struct snd_soc_card *card, int num)
+{
+ struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num];
+ struct snd_soc_codec *codec;
+ int ret = -ENODEV;
+
+ /* find CODEC from registered CODECs*/
+ list_for_each_entry(codec, &codec_list, list) {
+ if (!strcmp(codec->name, aux_dev->codec_name)) {
+ if (codec->probed) {
+ dev_err(codec->dev,
+ "asoc: codec already probed");
+ ret = -EBUSY;
+ goto out;
+ }
+ goto found;
+ }
+ }
+ /* codec not found */
+ dev_err(card->dev, "asoc: codec %s not found", aux_dev->codec_name);
+ goto out;
+
+found:
+ ret = soc_probe_codec(card, codec);
+ if (ret < 0)
+ return ret;
+
+ ret = soc_post_component_init(card, codec, num, 1);
+
+out:
+ return ret;
+}
+
+static void soc_remove_aux_dev(struct snd_soc_card *card, int num)
+{
+ struct snd_soc_pcm_runtime *rtd = &card->rtd_aux[num];
+ struct snd_soc_codec *codec = rtd->codec;
+
+ /* unregister the rtd device */
+ if (rtd->dev_registered) {
+ device_remove_file(&rtd->dev, &dev_attr_codec_reg);
+ device_unregister(&rtd->dev);
+ rtd->dev_registered = 0;
+ }
+
+ if (codec && codec->probed)
+ soc_remove_codec(codec);
+}
+
+static int snd_soc_init_codec_cache(struct snd_soc_codec *codec,
+ enum snd_soc_compress_type compress_type)
+{
+ int ret;
+
+ if (codec->cache_init)
+ return 0;
+
+ /* override the compress_type if necessary */
+ if (compress_type && codec->compress_type != compress_type)
+ codec->compress_type = compress_type;
+ ret = snd_soc_cache_init(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache compression type: %d\n",
+ ret);
+ return ret;
+ }
+ codec->cache_init = 1;
+ return 0;
+}
+
+static void snd_soc_instantiate_card(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec;
+ struct snd_soc_codec_conf *codec_conf;
+ enum snd_soc_compress_type compress_type;
+ int ret, i;
+
+ mutex_lock(&card->mutex);
+
+ if (card->instantiated) {
+ mutex_unlock(&card->mutex);
+ return;
+ }
+
+ /* bind DAIs */
+ for (i = 0; i < card->num_links; i++)
+ soc_bind_dai_link(card, i);
+
+ /* bind completed ? */
+ if (card->num_rtd != card->num_links) {
+ mutex_unlock(&card->mutex);
+ return;
+ }
+
+ /* initialize the register cache for each available codec */
+ list_for_each_entry(codec, &codec_list, list) {
+ if (codec->cache_init)
+ continue;
+ /* by default we don't override the compress_type */
+ compress_type = 0;
+ /* check to see if we need to override the compress_type */
+ for (i = 0; i < card->num_configs; ++i) {
+ codec_conf = &card->codec_conf[i];
+ if (!strcmp(codec->name, codec_conf->dev_name)) {
+ compress_type = codec_conf->compress_type;
+ if (compress_type && compress_type
+ != codec->compress_type)
+ break;
+ }
+ }
+ ret = snd_soc_init_codec_cache(codec, compress_type);
+ if (ret < 0) {
+ mutex_unlock(&card->mutex);
+ return;
+ }
+ }
+
+ /* card bind complete so register a sound card */
+ ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ card->owner, 0, &card->snd_card);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't create sound card for card %s\n",
+ card->name);
+ mutex_unlock(&card->mutex);
+ return;
+ }
+ card->snd_card->dev = card->dev;
+
+ card->dapm.bias_level = SND_SOC_BIAS_OFF;
+ card->dapm.dev = card->dev;
+ card->dapm.card = card;
+ list_add(&card->dapm.list, &card->dapm_list);
+
+#ifdef CONFIG_DEBUG_FS
+ snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+ /* deferred resume work */
+ INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
+#endif
+
+ if (card->dapm_widgets)
+ snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
+ card->num_dapm_widgets);
+
+ /* initialise the sound card only once */
+ if (card->probe) {
+ ret = card->probe(card);
+ if (ret < 0)
+ goto card_probe_error;
+ }
+
+ for (i = 0; i < card->num_links; i++) {
+ ret = soc_probe_dai_link(card, i);
+ if (ret < 0) {
+ pr_err("asoc: failed to instantiate card %s: %d\n",
+ card->name, ret);
+ goto probe_dai_err;
+ }
+ }
+
+ for (i = 0; i < card->num_aux_devs; i++) {
+ ret = soc_probe_aux_dev(card, i);
+ if (ret < 0) {
+ pr_err("asoc: failed to add auxiliary devices %s: %d\n",
+ card->name, ret);
+ goto probe_aux_dev_err;
+ }
+ }
+
+ /* We should have a non-codec control add function but we don't */
+ if (card->controls)
+ snd_soc_add_controls(list_first_entry(&card->codec_dev_list,
+ struct snd_soc_codec,
+ card_list),
+ card->controls,
+ card->num_controls);
+
+ if (card->dapm_routes)
+ snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
+ card->num_dapm_routes);
+
+ snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
+ "%s", card->name);
+ snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
+ "%s", card->long_name ? card->long_name : card->name);
+ snprintf(card->snd_card->driver, sizeof(card->snd_card->driver),
+ "%s", card->driver_name ? card->driver_name : card->name);
+ for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) {
+ switch (card->snd_card->driver[i]) {
+ case '_':
+ case '-':
+ case '\0':
+ break;
+ default:
+ if (!isalnum(card->snd_card->driver[i]))
+ card->snd_card->driver[i] = '_';
+ break;
+ }
+ }
+
+ if (card->late_probe) {
+ ret = card->late_probe(card);
+ if (ret < 0) {
+ dev_err(card->dev, "%s late_probe() failed: %d\n",
+ card->name, ret);
+ goto probe_aux_dev_err;
+ }
+ }
+
+ ret = snd_card_register(card->snd_card);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
+ goto probe_aux_dev_err;
+ }
+
+#ifdef CONFIG_SND_SOC_AC97_BUS
+ /* register any AC97 codecs */
+ for (i = 0; i < card->num_rtd; i++) {
+ ret = soc_register_ac97_dai_link(&card->rtd[i]);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);
+ while (--i >= 0)
+ soc_unregister_ac97_dai_link(card->rtd[i].codec);
+ goto probe_aux_dev_err;
+ }
+ }
+#endif
+
+ card->instantiated = 1;
+ mutex_unlock(&card->mutex);
+ return;
+
+probe_aux_dev_err:
+ for (i = 0; i < card->num_aux_devs; i++)
+ soc_remove_aux_dev(card, i);
+
+probe_dai_err:
+ soc_remove_dai_links(card);
+
+card_probe_error:
+ if (card->remove)
+ card->remove(card);
+
+ snd_card_free(card->snd_card);
+
+ mutex_unlock(&card->mutex);
+}
+
+/*
+ * Attempt to initialise any uninitialised cards. Must be called with
+ * client_mutex.
+ */
+static void snd_soc_instantiate_cards(void)
+{
+ struct snd_soc_card *card;
+ list_for_each_entry(card, &card_list, list)
+ snd_soc_instantiate_card(card);
+}
+
+/* probes a new socdev */
+static int soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ /*
+ * no card, so machine driver should be registering card
+ * we should not be here in that case so ret error
+ */
+ if (!card)
+ return -EINVAL;
+
+ /* Bodge while we unpick instantiation */
+ card->dev = &pdev->dev;
+
+ ret = snd_soc_register_card(card);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to register card\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int soc_cleanup_card_resources(struct snd_soc_card *card)
+{
+ int i;
+
+ /* make sure any delayed work runs */
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
+ flush_delayed_work_sync(&rtd->delayed_work);
+ }
+
+ /* remove auxiliary devices */
+ for (i = 0; i < card->num_aux_devs; i++)
+ soc_remove_aux_dev(card, i);
+
+ /* remove and free each DAI */
+ soc_remove_dai_links(card);
+
+ soc_cleanup_card_debugfs(card);
+
+ /* remove the card */
+ if (card->remove)
+ card->remove(card);
+
+ snd_soc_dapm_free(&card->dapm);
+
+ kfree(card->rtd->ops);
+ kfree(card->rtd);
+ snd_card_free(card->snd_card);
+ return 0;
+
+}
+
+/* removes a socdev */
+static int soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_card(card);
+ return 0;
+}
+
+int snd_soc_poweroff(struct device *dev)
+{
+ struct snd_soc_card *card = dev_get_drvdata(dev);
+ int i;
+
+ if (!card->instantiated)
+ return 0;
+
+ /* Flush out pmdown_time work - we actually do want to run it
+ * now, we're shutting down so no imminent restart. */
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
+ flush_delayed_work_sync(&rtd->delayed_work);
+ }
+
+ snd_soc_dapm_shutdown(card);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_poweroff);
+
+const struct dev_pm_ops snd_soc_pm_ops = {
+ .suspend = snd_soc_suspend,
+ .resume = snd_soc_resume,
+ .poweroff = snd_soc_poweroff,
+};
+EXPORT_SYMBOL_GPL(snd_soc_pm_ops);
+
+/* ASoC platform driver */
+static struct platform_driver soc_driver = {
+ .driver = {
+ .name = "soc-audio",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = soc_probe,
+ .remove = soc_remove,
+};
+
+/* create a new pcm */
+static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_pcm *pcm;
+ struct snd_pcm_ops *soc_pcm_ops;
+ char new_name[64];
+ int ret = 0, playback = 0, capture = 0;
+
+ /* ASoC PCM operations */
+ soc_pcm_ops = kzalloc(sizeof(struct snd_pcm_ops), GFP_KERNEL);
+ if (soc_pcm_ops == NULL) {
+ printk(KERN_ERR "asoc: can't create pcm ops for codec %s\n", codec->name);
+ return -ENOMEM;
+ }
+
+ /* check client and interface hw capabilities */
+ snprintf(new_name, sizeof(new_name), "%s %s-%d",
+ rtd->dai_link->stream_name, codec_dai->name, num);
+
+ if (codec_dai->driver->playback.channels_min)
+ playback = 1;
+ if (codec_dai->driver->capture.channels_min)
+ capture = 1;
+
+ dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name);
+ ret = snd_pcm_new(rtd->card->snd_card, new_name,
+ num, playback, capture, &pcm);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name);
+ kfree(soc_pcm_ops);
+ return ret;
+ }
+
+ rtd->pcm = pcm;
+ pcm->private_data = rtd;
+
+ soc_pcm_ops->open = soc_pcm_open;
+ soc_pcm_ops->close = soc_codec_close;
+ soc_pcm_ops->hw_params = soc_pcm_hw_params;
+ soc_pcm_ops->hw_free = soc_pcm_hw_free;
+ soc_pcm_ops->prepare = soc_pcm_prepare;
+ soc_pcm_ops->pointer = soc_pcm_pointer;
+ soc_pcm_ops->trigger = soc_pcm_trigger;
+ soc_pcm_ops->ioctl = soc_pcm_ioctl;
+
+ if (platform->driver->ops) {
+ soc_pcm_ops->mmap = platform->driver->ops->mmap;
+ soc_pcm_ops->copy = platform->driver->ops->copy;
+ soc_pcm_ops->silence = platform->driver->ops->silence;
+ soc_pcm_ops->ack = platform->driver->ops->ack;
+ soc_pcm_ops->page = platform->driver->ops->page;
+ }
+
+ rtd->ops = soc_pcm_ops;
+
+ if (playback)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);
+
+ if (capture)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);
+
+ if (platform->driver->pcm_new) {
+ ret = platform->driver->pcm_new(rtd->card->snd_card,
+ codec_dai, pcm);
+ if (ret < 0) {
+ pr_err("asoc: platform pcm constructor failed\n");
+ kfree(rtd->ops);
+ return ret;
+ }
+ }
+
+ pcm->private_free = platform->driver->pcm_free;
+ printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,
+ cpu_dai->name);
+ return ret;
+}
+
+/**
+ * snd_soc_codec_volatile_register: Report if a register is volatile.
+ *
+ * @codec: CODEC to query.
+ * @reg: Register to query.
+ *
+ * Boolean function indiciating if a CODEC register is volatile.
+ */
+int snd_soc_codec_volatile_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ if (codec->volatile_register)
+ return codec->volatile_register(codec, reg);
+ else
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_volatile_register);
+
+/**
+ * snd_soc_codec_readable_register: Report if a register is readable.
+ *
+ * @codec: CODEC to query.
+ * @reg: Register to query.
+ *
+ * Boolean function indicating if a CODEC register is readable.
+ */
+int snd_soc_codec_readable_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ if (codec->readable_register)
+ return codec->readable_register(codec, reg);
+ else
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_readable_register);
+
+/**
+ * snd_soc_codec_writable_register: Report if a register is writable.
+ *
+ * @codec: CODEC to query.
+ * @reg: Register to query.
+ *
+ * Boolean function indicating if a CODEC register is writable.
+ */
+int snd_soc_codec_writable_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ if (codec->writable_register)
+ return codec->writable_register(codec, reg);
+ else
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_writable_register);
+
+/**
+ * snd_soc_new_ac97_codec - initailise AC97 device
+ * @codec: audio codec
+ * @ops: AC97 bus operations
+ * @num: AC97 codec number
+ *
+ * Initialises AC97 codec resources for use by ad-hoc devices only.
+ */
+int snd_soc_new_ac97_codec(struct snd_soc_codec *codec,
+ struct snd_ac97_bus_ops *ops, int num)
+{
+ mutex_lock(&codec->mutex);
+
+ codec->ac97 = kzalloc(sizeof(struct snd_ac97), GFP_KERNEL);
+ if (codec->ac97 == NULL) {
+ mutex_unlock(&codec->mutex);
+ return -ENOMEM;
+ }
+
+ codec->ac97->bus = kzalloc(sizeof(struct snd_ac97_bus), GFP_KERNEL);
+ if (codec->ac97->bus == NULL) {
+ kfree(codec->ac97);
+ codec->ac97 = NULL;
+ mutex_unlock(&codec->mutex);
+ return -ENOMEM;
+ }
+
+ codec->ac97->bus->ops = ops;
+ codec->ac97->num = num;
+
+ /*
+ * Mark the AC97 device to be created by us. This way we ensure that the
+ * device will be registered with the device subsystem later on.
+ */
+ codec->ac97_created = 1;
+
+ mutex_unlock(&codec->mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_new_ac97_codec);
+
+/**
+ * snd_soc_free_ac97_codec - free AC97 codec device
+ * @codec: audio codec
+ *
+ * Frees AC97 codec device resources.
+ */
+void snd_soc_free_ac97_codec(struct snd_soc_codec *codec)
+{
+ mutex_lock(&codec->mutex);
+#ifdef CONFIG_SND_SOC_AC97_BUS
+ soc_unregister_ac97_dai_link(codec);
+#endif
+ kfree(codec->ac97->bus);
+ kfree(codec->ac97);
+ codec->ac97 = NULL;
+ codec->ac97_created = 0;
+ mutex_unlock(&codec->mutex);
+}
+EXPORT_SYMBOL_GPL(snd_soc_free_ac97_codec);
+
+unsigned int snd_soc_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ unsigned int ret;
+
+ ret = codec->read(codec, reg);
+ dev_dbg(codec->dev, "read %x => %x\n", reg, ret);
+ trace_snd_soc_reg_read(codec, reg, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_read);
+
+unsigned int snd_soc_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val)
+{
+ dev_dbg(codec->dev, "write %x = %x\n", reg, val);
+ trace_snd_soc_reg_write(codec, reg, val);
+ return codec->write(codec, reg, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_write);
+
+unsigned int snd_soc_bulk_write_raw(struct snd_soc_codec *codec,
+ unsigned int reg, const void *data, size_t len)
+{
+ return codec->bulk_write_raw(codec, reg, data, len);
+}
+EXPORT_SYMBOL_GPL(snd_soc_bulk_write_raw);
+
+/**
+ * snd_soc_update_bits - update codec register bits
+ * @codec: audio codec
+ * @reg: codec register
+ * @mask: register mask
+ * @value: new value
+ *
+ * Writes new register value.
+ *
+ * Returns 1 for change, 0 for no change, or negative error code.
+ */
+int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
+ unsigned int mask, unsigned int value)
+{
+ int change;
+ unsigned int old, new;
+ int ret;
+
+ ret = snd_soc_read(codec, reg);
+ if (ret < 0)
+ return ret;
+
+ old = ret;
+ new = (old & ~mask) | value;
+ change = old != new;
+ if (change) {
+ ret = snd_soc_write(codec, reg, new);
+ if (ret < 0)
+ return ret;
+ }
+
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_soc_update_bits);
+
+/**
+ * snd_soc_update_bits_locked - update codec register bits
+ * @codec: audio codec
+ * @reg: codec register
+ * @mask: register mask
+ * @value: new value
+ *
+ * Writes new register value, and takes the codec mutex.
+ *
+ * Returns 1 for change else 0.
+ */
+int snd_soc_update_bits_locked(struct snd_soc_codec *codec,
+ unsigned short reg, unsigned int mask,
+ unsigned int value)
+{
+ int change;
+
+ mutex_lock(&codec->mutex);
+ change = snd_soc_update_bits(codec, reg, mask, value);
+ mutex_unlock(&codec->mutex);
+
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_soc_update_bits_locked);
+
+/**
+ * snd_soc_test_bits - test register for change
+ * @codec: audio codec
+ * @reg: codec register
+ * @mask: register mask
+ * @value: new value
+ *
+ * Tests a register with a new value and checks if the new value is
+ * different from the old value.
+ *
+ * Returns 1 for change else 0.
+ */
+int snd_soc_test_bits(struct snd_soc_codec *codec, unsigned short reg,
+ unsigned int mask, unsigned int value)
+{
+ int change;
+ unsigned int old, new;
+
+ old = snd_soc_read(codec, reg);
+ new = (old & ~mask) | value;
+ change = old != new;
+
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_soc_test_bits);
+
+/**
+ * snd_soc_set_runtime_hwparams - set the runtime hardware parameters
+ * @substream: the pcm substream
+ * @hw: the hardware parameters
+ *
+ * Sets the substream runtime hardware parameters.
+ */
+int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,
+ const struct snd_pcm_hardware *hw)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ runtime->hw.info = hw->info;
+ runtime->hw.formats = hw->formats;
+ runtime->hw.period_bytes_min = hw->period_bytes_min;
+ runtime->hw.period_bytes_max = hw->period_bytes_max;
+ runtime->hw.periods_min = hw->periods_min;
+ runtime->hw.periods_max = hw->periods_max;
+ runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
+ runtime->hw.fifo_size = hw->fifo_size;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams);
+
+/**
+ * snd_soc_cnew - create new control
+ * @_template: control template
+ * @data: control private data
+ * @long_name: control long name
+ * @prefix: control name prefix
+ *
+ * Create a new mixer control from a template control.
+ *
+ * Returns 0 for success, else error.
+ */
+struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
+ void *data, char *long_name,
+ const char *prefix)
+{
+ struct snd_kcontrol_new template;
+ struct snd_kcontrol *kcontrol;
+ char *name = NULL;
+ int name_len;
+
+ memcpy(&template, _template, sizeof(template));
+ template.index = 0;
+
+ if (!long_name)
+ long_name = template.name;
+
+ if (prefix) {
+ name_len = strlen(long_name) + strlen(prefix) + 2;
+ name = kmalloc(name_len, GFP_ATOMIC);
+ if (!name)
+ return NULL;
+
+ snprintf(name, name_len, "%s %s", prefix, long_name);
+
+ template.name = name;
+ } else {
+ template.name = long_name;
+ }
+
+ kcontrol = snd_ctl_new1(&template, data);
+
+ kfree(name);
+
+ return kcontrol;
+}
+EXPORT_SYMBOL_GPL(snd_soc_cnew);
+
+/**
+ * snd_soc_add_controls - add an array of controls to a codec.
+ * Convienience function to add a list of controls. Many codecs were
+ * duplicating this code.
+ *
+ * @codec: codec to add controls to
+ * @controls: array of controls to add
+ * @num_controls: number of elements in the array
+ *
+ * Return 0 for success, else error.
+ */
+int snd_soc_add_controls(struct snd_soc_codec *codec,
+ const struct snd_kcontrol_new *controls, int num_controls)
+{
+ struct snd_card *card = codec->card->snd_card;
+ int err, i;
+
+ for (i = 0; i < num_controls; i++) {
+ const struct snd_kcontrol_new *control = &controls[i];
+ err = snd_ctl_add(card, snd_soc_cnew(control, codec,
+ control->name,
+ codec->name_prefix));
+ if (err < 0) {
+ dev_err(codec->dev, "%s: Failed to add %s: %d\n",
+ codec->name, control->name, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_add_controls);
+
+/**
+ * snd_soc_info_enum_double - enumerated double mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a double enumerated
+ * mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = e->shift_l == e->shift_r ? 1 : 2;
+ uinfo->value.enumerated.items = e->max;
+
+ if (uinfo->value.enumerated.item > e->max - 1)
+ uinfo->value.enumerated.item = e->max - 1;
+ strcpy(uinfo->value.enumerated.name,
+ e->texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_enum_double);
+
+/**
+ * snd_soc_get_enum_double - enumerated double mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a double enumerated mixer.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int val, bitmask;
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ val = snd_soc_read(codec, e->reg);
+ ucontrol->value.enumerated.item[0]
+ = (val >> e->shift_l) & (bitmask - 1);
+ if (e->shift_l != e->shift_r)
+ ucontrol->value.enumerated.item[1] =
+ (val >> e->shift_r) & (bitmask - 1);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_enum_double);
+
+/**
+ * snd_soc_put_enum_double - enumerated double mixer put callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a double enumerated mixer.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int val;
+ unsigned int mask, bitmask;
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+ val = ucontrol->value.enumerated.item[0] << e->shift_l;
+ mask = (bitmask - 1) << e->shift_l;
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->max - 1)
+ return -EINVAL;
+ val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+ mask |= (bitmask - 1) << e->shift_r;
+ }
+
+ return snd_soc_update_bits_locked(codec, e->reg, mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_enum_double);
+
+/**
+ * snd_soc_get_value_enum_double - semi enumerated double mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a double semi enumerated mixer.
+ *
+ * Semi enumerated mixer: the enumerated items are referred as values. Can be
+ * used for handling bitfield coded enumeration for example.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_value_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int reg_val, val, mux;
+
+ reg_val = snd_soc_read(codec, e->reg);
+ val = (reg_val >> e->shift_l) & e->mask;
+ for (mux = 0; mux < e->max; mux++) {
+ if (val == e->values[mux])
+ break;
+ }
+ ucontrol->value.enumerated.item[0] = mux;
+ if (e->shift_l != e->shift_r) {
+ val = (reg_val >> e->shift_r) & e->mask;
+ for (mux = 0; mux < e->max; mux++) {
+ if (val == e->values[mux])
+ break;
+ }
+ ucontrol->value.enumerated.item[1] = mux;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_value_enum_double);
+
+/**
+ * snd_soc_put_value_enum_double - semi enumerated double mixer put callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a double semi enumerated mixer.
+ *
+ * Semi enumerated mixer: the enumerated items are referred as values. Can be
+ * used for handling bitfield coded enumeration for example.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_value_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int val;
+ unsigned int mask;
+
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+ val = e->values[ucontrol->value.enumerated.item[0]] << e->shift_l;
+ mask = e->mask << e->shift_l;
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->max - 1)
+ return -EINVAL;
+ val |= e->values[ucontrol->value.enumerated.item[1]] << e->shift_r;
+ mask |= e->mask << e->shift_r;
+ }
+
+ return snd_soc_update_bits_locked(codec, e->reg, mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_value_enum_double);
+
+/**
+ * snd_soc_info_enum_ext - external enumerated single mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about an external enumerated
+ * single mixer.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = e->max;
+
+ if (uinfo->value.enumerated.item > e->max - 1)
+ uinfo->value.enumerated.item = e->max - 1;
+ strcpy(uinfo->value.enumerated.name,
+ e->texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_enum_ext);
+
+/**
+ * snd_soc_info_volsw_ext - external single mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a single external mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_ext(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int max = kcontrol->private_value;
+
+ if (max == 1 && !strstr(kcontrol->id.name, " Volume"))
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = max;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext);
+
+/**
+ * snd_soc_info_volsw - single mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a single mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int platform_max;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+
+ if (!mc->platform_max)
+ mc->platform_max = mc->max;
+ platform_max = mc->platform_max;
+
+ if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume"))
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+ uinfo->count = shift == rshift ? 1 : 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = platform_max;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw);
+
+/**
+ * snd_soc_get_volsw - single mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a single mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+
+ ucontrol->value.integer.value[0] =
+ (snd_soc_read(codec, reg) >> shift) & mask;
+ if (shift != rshift)
+ ucontrol->value.integer.value[1] =
+ (snd_soc_read(codec, reg) >> rshift) & mask;
+ if (invert) {
+ ucontrol->value.integer.value[0] =
+ max - ucontrol->value.integer.value[0];
+ if (shift != rshift)
+ ucontrol->value.integer.value[1] =
+ max - ucontrol->value.integer.value[1];
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw);
+
+/**
+ * snd_soc_put_volsw - single mixer put callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a single mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+ unsigned int val, val2, val_mask;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+ if (invert)
+ val = max - val;
+ val_mask = mask << shift;
+ val = val << shift;
+ if (shift != rshift) {
+ val2 = (ucontrol->value.integer.value[1] & mask);
+ if (invert)
+ val2 = max - val2;
+ val_mask |= mask << rshift;
+ val |= val2 << rshift;
+ }
+ return snd_soc_update_bits_locked(codec, reg, val_mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw);
+
+/**
+ * snd_soc_info_volsw_2r - double mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a double mixer control that
+ * spans 2 codec registers.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int platform_max;
+
+ if (!mc->platform_max)
+ mc->platform_max = mc->max;
+ platform_max = mc->platform_max;
+
+ if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume"))
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = platform_max;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r);
+
+/**
+ * snd_soc_get_volsw_2r - double mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a double mixer control that spans 2 registers.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+
+ ucontrol->value.integer.value[0] =
+ (snd_soc_read(codec, reg) >> shift) & mask;
+ ucontrol->value.integer.value[1] =
+ (snd_soc_read(codec, reg2) >> shift) & mask;
+ if (invert) {
+ ucontrol->value.integer.value[0] =
+ max - ucontrol->value.integer.value[0];
+ ucontrol->value.integer.value[1] =
+ max - ucontrol->value.integer.value[1];
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r);
+
+/**
+ * snd_soc_put_volsw_2r - double mixer set callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a double mixer control that spans 2 registers.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+ int err;
+ unsigned int val, val2, val_mask;
+
+ val_mask = mask << shift;
+ val = (ucontrol->value.integer.value[0] & mask);
+ val2 = (ucontrol->value.integer.value[1] & mask);
+
+ if (invert) {
+ val = max - val;
+ val2 = max - val2;
+ }
+
+ val = val << shift;
+ val2 = val2 << shift;
+
+ err = snd_soc_update_bits_locked(codec, reg, val_mask, val);
+ if (err < 0)
+ return err;
+
+ err = snd_soc_update_bits_locked(codec, reg2, val_mask, val2);
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r);
+
+/**
+ * snd_soc_info_volsw_s8 - signed mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a signed mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int platform_max;
+ int min = mc->min;
+
+ if (!mc->platform_max)
+ mc->platform_max = mc->max;
+ platform_max = mc->platform_max;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = platform_max - min;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_s8);
+
+/**
+ * snd_soc_get_volsw_s8 - signed mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a signed mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ int min = mc->min;
+ int val = snd_soc_read(codec, reg);
+
+ ucontrol->value.integer.value[0] =
+ ((signed char)(val & 0xff))-min;
+ ucontrol->value.integer.value[1] =
+ ((signed char)((val >> 8) & 0xff))-min;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw_s8);
+
+/**
+ * snd_soc_put_volsw_sgn - signed mixer put callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a signed mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ int min = mc->min;
+ unsigned int val;
+
+ val = (ucontrol->value.integer.value[0]+min) & 0xff;
+ val |= ((ucontrol->value.integer.value[1]+min) & 0xff) << 8;
+
+ return snd_soc_update_bits_locked(codec, reg, 0xffff, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8);
+
+/**
+ * snd_soc_limit_volume - Set new limit to an existing volume control.
+ *
+ * @codec: where to look for the control
+ * @name: Name of the control
+ * @max: new maximum limit
+ *
+ * Return 0 for success, else error.
+ */
+int snd_soc_limit_volume(struct snd_soc_codec *codec,
+ const char *name, int max)
+{
+ struct snd_card *card = codec->card->snd_card;
+ struct snd_kcontrol *kctl;
+ struct soc_mixer_control *mc;
+ int found = 0;
+ int ret = -EINVAL;
+
+ /* Sanity check for name and max */
+ if (unlikely(!name || max <= 0))
+ return -EINVAL;
+
+ list_for_each_entry(kctl, &card->controls, list) {
+ if (!strncmp(kctl->id.name, name, sizeof(kctl->id.name))) {
+ found = 1;
+ break;
+ }
+ }
+ if (found) {
+ mc = (struct soc_mixer_control *)kctl->private_value;
+ if (max <= mc->max) {
+ mc->platform_max = max;
+ ret = 0;
+ }
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_limit_volume);
+
+/**
+ * snd_soc_info_volsw_2r_sx - double with tlv and variable data size
+ * mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int max = mc->max;
+ int min = mc->min;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = max-min;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r_sx);
+
+/**
+ * snd_soc_get_volsw_2r_sx - double with tlv and variable data size
+ * mixer get callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int mask = (1<<mc->shift)-1;
+ int min = mc->min;
+ int val = snd_soc_read(codec, mc->reg) & mask;
+ int valr = snd_soc_read(codec, mc->rreg) & mask;
+
+ ucontrol->value.integer.value[0] = ((val & 0xff)-min) & mask;
+ ucontrol->value.integer.value[1] = ((valr & 0xff)-min) & mask;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx);
+
+/**
+ * snd_soc_put_volsw_2r_sx - double with tlv and variable data size
+ * mixer put callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int mask = (1<<mc->shift)-1;
+ int min = mc->min;
+ int ret;
+ unsigned int val, valr, oval, ovalr;
+
+ val = ((ucontrol->value.integer.value[0]+min) & 0xff);
+ val &= mask;
+ valr = ((ucontrol->value.integer.value[1]+min) & 0xff);
+ valr &= mask;
+
+ oval = snd_soc_read(codec, mc->reg) & mask;
+ ovalr = snd_soc_read(codec, mc->rreg) & mask;
+
+ ret = 0;
+ if (oval != val) {
+ ret = snd_soc_write(codec, mc->reg, val);
+ if (ret < 0)
+ return ret;
+ }
+ if (ovalr != valr) {
+ ret = snd_soc_write(codec, mc->rreg, valr);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r_sx);
+
+/**
+ * snd_soc_dai_set_sysclk - configure DAI system or master clock.
+ * @dai: DAI
+ * @clk_id: DAI specific clock ID
+ * @freq: new clock frequency in Hz
+ * @dir: new clock direction - input/output.
+ *
+ * Configures the DAI master (MCLK) or system (SYSCLK) clocking.
+ */
+int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ if (dai->driver && dai->driver->ops->set_sysclk)
+ return dai->driver->ops->set_sysclk(dai, clk_id, freq, dir);
+ else if (dai->codec && dai->codec->driver->set_sysclk)
+ return dai->codec->driver->set_sysclk(dai->codec, clk_id,
+ freq, dir);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk);
+
+/**
+ * snd_soc_codec_set_sysclk - configure CODEC system or master clock.
+ * @codec: CODEC
+ * @clk_id: DAI specific clock ID
+ * @freq: new clock frequency in Hz
+ * @dir: new clock direction - input/output.
+ *
+ * Configures the CODEC master (MCLK) or system (SYSCLK) clocking.
+ */
+int snd_soc_codec_set_sysclk(struct snd_soc_codec *codec, int clk_id,
+ unsigned int freq, int dir)
+{
+ if (codec->driver->set_sysclk)
+ return codec->driver->set_sysclk(codec, clk_id, freq, dir);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_set_sysclk);
+
+/**
+ * snd_soc_dai_set_clkdiv - configure DAI clock dividers.
+ * @dai: DAI
+ * @div_id: DAI specific clock divider ID
+ * @div: new clock divisor.
+ *
+ * Configures the clock dividers. This is used to derive the best DAI bit and
+ * frame clocks from the system or master clock. It's best to set the DAI bit
+ * and frame clocks as low as possible to save system power.
+ */
+int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai,
+ int div_id, int div)
+{
+ if (dai->driver && dai->driver->ops->set_clkdiv)
+ return dai->driver->ops->set_clkdiv(dai, div_id, div);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv);
+
+/**
+ * snd_soc_dai_set_pll - configure DAI PLL.
+ * @dai: DAI
+ * @pll_id: DAI specific PLL ID
+ * @source: DAI specific source for the PLL
+ * @freq_in: PLL input clock frequency in Hz
+ * @freq_out: requested PLL output clock frequency in Hz
+ *
+ * Configures and enables PLL to generate output clock based on input clock.
+ */
+int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ if (dai->driver && dai->driver->ops->set_pll)
+ return dai->driver->ops->set_pll(dai, pll_id, source,
+ freq_in, freq_out);
+ else if (dai->codec && dai->codec->driver->set_pll)
+ return dai->codec->driver->set_pll(dai->codec, pll_id, source,
+ freq_in, freq_out);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll);
+
+/*
+ * snd_soc_codec_set_pll - configure codec PLL.
+ * @codec: CODEC
+ * @pll_id: DAI specific PLL ID
+ * @source: DAI specific source for the PLL
+ * @freq_in: PLL input clock frequency in Hz
+ * @freq_out: requested PLL output clock frequency in Hz
+ *
+ * Configures and enables PLL to generate output clock based on input clock.
+ */
+int snd_soc_codec_set_pll(struct snd_soc_codec *codec, int pll_id, int source,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ if (codec->driver->set_pll)
+ return codec->driver->set_pll(codec, pll_id, source,
+ freq_in, freq_out);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_set_pll);
+
+/**
+ * snd_soc_dai_set_fmt - configure DAI hardware audio format.
+ * @dai: DAI
+ * @fmt: SND_SOC_DAIFMT_ format value.
+ *
+ * Configures the DAI hardware format and clocking.
+ */
+int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ if (dai->driver && dai->driver->ops->set_fmt)
+ return dai->driver->ops->set_fmt(dai, fmt);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt);
+
+/**
+ * snd_soc_dai_set_tdm_slot - configure DAI TDM.
+ * @dai: DAI
+ * @tx_mask: bitmask representing active TX slots.
+ * @rx_mask: bitmask representing active RX slots.
+ * @slots: Number of slots in use.
+ * @slot_width: Width in bits for each slot.
+ *
+ * Configures a DAI for TDM operation. Both mask and slots are codec and DAI
+ * specific.
+ */
+int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+ if (dai->driver && dai->driver->ops->set_tdm_slot)
+ return dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask,
+ slots, slot_width);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot);
+
+/**
+ * snd_soc_dai_set_channel_map - configure DAI audio channel map
+ * @dai: DAI
+ * @tx_num: how many TX channels
+ * @tx_slot: pointer to an array which imply the TX slot number channel
+ * 0~num-1 uses
+ * @rx_num: how many RX channels
+ * @rx_slot: pointer to an array which imply the RX slot number channel
+ * 0~num-1 uses
+ *
+ * configure the relationship between channel number and TDM slot number.
+ */
+int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai,
+ unsigned int tx_num, unsigned int *tx_slot,
+ unsigned int rx_num, unsigned int *rx_slot)
+{
+ if (dai->driver && dai->driver->ops->set_channel_map)
+ return dai->driver->ops->set_channel_map(dai, tx_num, tx_slot,
+ rx_num, rx_slot);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map);
+
+/**
+ * snd_soc_dai_set_tristate - configure DAI system or master clock.
+ * @dai: DAI
+ * @tristate: tristate enable
+ *
+ * Tristates the DAI so that others can use it.
+ */
+int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+ if (dai->driver && dai->driver->ops->set_tristate)
+ return dai->driver->ops->set_tristate(dai, tristate);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate);
+
+/**
+ * snd_soc_dai_digital_mute - configure DAI system or master clock.
+ * @dai: DAI
+ * @mute: mute enable
+ *
+ * Mutes the DAI DAC.
+ */
+int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ if (dai->driver && dai->driver->ops->digital_mute)
+ return dai->driver->ops->digital_mute(dai, mute);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute);
+
+/**
+ * snd_soc_register_card - Register a card with the ASoC core
+ *
+ * @card: Card to register
+ *
+ */
+int snd_soc_register_card(struct snd_soc_card *card)
+{
+ int i;
+
+ if (!card->name || !card->dev)
+ return -EINVAL;
+
+ dev_set_drvdata(card->dev, card);
+
+ snd_soc_initialize_card_lists(card);
+
+ soc_init_card_debugfs(card);
+
+ card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) *
+ (card->num_links + card->num_aux_devs),
+ GFP_KERNEL);
+ if (card->rtd == NULL)
+ return -ENOMEM;
+ card->rtd_aux = &card->rtd[card->num_links];
+
+ for (i = 0; i < card->num_links; i++)
+ card->rtd[i].dai_link = &card->dai_link[i];
+
+ INIT_LIST_HEAD(&card->list);
+ card->instantiated = 0;
+ mutex_init(&card->mutex);
+
+ mutex_lock(&client_mutex);
+ list_add(&card->list, &card_list);
+ snd_soc_instantiate_cards();
+ mutex_unlock(&client_mutex);
+
+ dev_dbg(card->dev, "Registered card '%s'\n", card->name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_register_card);
+
+/**
+ * snd_soc_unregister_card - Unregister a card with the ASoC core
+ *
+ * @card: Card to unregister
+ *
+ */
+int snd_soc_unregister_card(struct snd_soc_card *card)
+{
+ if (card->instantiated)
+ soc_cleanup_card_resources(card);
+ mutex_lock(&client_mutex);
+ list_del(&card->list);
+ mutex_unlock(&client_mutex);
+ dev_dbg(card->dev, "Unregistered card '%s'\n", card->name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_unregister_card);
+
+/*
+ * Simplify DAI link configuration by removing ".-1" from device names
+ * and sanitizing names.
+ */
+static char *fmt_single_name(struct device *dev, int *id)
+{
+ char *found, name[NAME_SIZE];
+ int id1, id2;
+
+ if (dev_name(dev) == NULL)
+ return NULL;
+
+ strlcpy(name, dev_name(dev), NAME_SIZE);
+
+ /* are we a "%s.%d" name (platform and SPI components) */
+ found = strstr(name, dev->driver->name);
+ if (found) {
+ /* get ID */
+ if (sscanf(&found[strlen(dev->driver->name)], ".%d", id) == 1) {
+
+ /* discard ID from name if ID == -1 */
+ if (*id == -1)
+ found[strlen(dev->driver->name)] = '\0';
+ }
+
+ } else {
+ /* I2C component devices are named "bus-addr" */
+ if (sscanf(name, "%x-%x", &id1, &id2) == 2) {
+ char tmp[NAME_SIZE];
+
+ /* create unique ID number from I2C addr and bus */
+ *id = ((id1 & 0xffff) << 16) + id2;
+
+ /* sanitize component name for DAI link creation */
+ snprintf(tmp, NAME_SIZE, "%s.%s", dev->driver->name, name);
+ strlcpy(name, tmp, NAME_SIZE);
+ } else
+ *id = 0;
+ }
+
+ return kstrdup(name, GFP_KERNEL);
+}
+
+/*
+ * Simplify DAI link naming for single devices with multiple DAIs by removing
+ * any ".-1" and using the DAI name (instead of device name).
+ */
+static inline char *fmt_multiple_name(struct device *dev,
+ struct snd_soc_dai_driver *dai_drv)
+{
+ if (dai_drv->name == NULL) {
+ printk(KERN_ERR "asoc: error - multiple DAI %s registered with no name\n",
+ dev_name(dev));
+ return NULL;
+ }
+
+ return kstrdup(dai_drv->name, GFP_KERNEL);
+}
+
+/**
+ * snd_soc_register_dai - Register a DAI with the ASoC core
+ *
+ * @dai: DAI to register
+ */
+int snd_soc_register_dai(struct device *dev,
+ struct snd_soc_dai_driver *dai_drv)
+{
+ struct snd_soc_dai *dai;
+
+ dev_dbg(dev, "dai register %s\n", dev_name(dev));
+
+ dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
+ if (dai == NULL)
+ return -ENOMEM;
+
+ /* create DAI component name */
+ dai->name = fmt_single_name(dev, &dai->id);
+ if (dai->name == NULL) {
+ kfree(dai);
+ return -ENOMEM;
+ }
+
+ dai->dev = dev;
+ dai->driver = dai_drv;
+ if (!dai->driver->ops)
+ dai->driver->ops = &null_dai_ops;
+
+ mutex_lock(&client_mutex);
+ list_add(&dai->list, &dai_list);
+ snd_soc_instantiate_cards();
+ mutex_unlock(&client_mutex);
+
+ pr_debug("Registered DAI '%s'\n", dai->name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_register_dai);
+
+/**
+ * snd_soc_unregister_dai - Unregister a DAI from the ASoC core
+ *
+ * @dai: DAI to unregister
+ */
+void snd_soc_unregister_dai(struct device *dev)
+{
+ struct snd_soc_dai *dai;
+
+ list_for_each_entry(dai, &dai_list, list) {
+ if (dev == dai->dev)
+ goto found;
+ }
+ return;
+
+found:
+ mutex_lock(&client_mutex);
+ list_del(&dai->list);
+ mutex_unlock(&client_mutex);
+
+ pr_debug("Unregistered DAI '%s'\n", dai->name);
+ kfree(dai->name);
+ kfree(dai);
+}
+EXPORT_SYMBOL_GPL(snd_soc_unregister_dai);
+
+/**
+ * snd_soc_register_dais - Register multiple DAIs with the ASoC core
+ *
+ * @dai: Array of DAIs to register
+ * @count: Number of DAIs
+ */
+int snd_soc_register_dais(struct device *dev,
+ struct snd_soc_dai_driver *dai_drv, size_t count)
+{
+ struct snd_soc_dai *dai;
+ int i, ret = 0;
+
+ dev_dbg(dev, "dai register %s #%Zu\n", dev_name(dev), count);
+
+ for (i = 0; i < count; i++) {
+
+ dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
+ if (dai == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* create DAI component name */
+ dai->name = fmt_multiple_name(dev, &dai_drv[i]);
+ if (dai->name == NULL) {
+ kfree(dai);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ dai->dev = dev;
+ dai->driver = &dai_drv[i];
+ if (dai->driver->id)
+ dai->id = dai->driver->id;
+ else
+ dai->id = i;
+ if (!dai->driver->ops)
+ dai->driver->ops = &null_dai_ops;
+
+ mutex_lock(&client_mutex);
+ list_add(&dai->list, &dai_list);
+ mutex_unlock(&client_mutex);
+
+ pr_debug("Registered DAI '%s'\n", dai->name);
+ }
+
+ mutex_lock(&client_mutex);
+ snd_soc_instantiate_cards();
+ mutex_unlock(&client_mutex);
+ return 0;
+
+err:
+ for (i--; i >= 0; i--)
+ snd_soc_unregister_dai(dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_register_dais);
+
+/**
+ * snd_soc_unregister_dais - Unregister multiple DAIs from the ASoC core
+ *
+ * @dai: Array of DAIs to unregister
+ * @count: Number of DAIs
+ */
+void snd_soc_unregister_dais(struct device *dev, size_t count)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ snd_soc_unregister_dai(dev);
+}
+EXPORT_SYMBOL_GPL(snd_soc_unregister_dais);
+
+/**
+ * snd_soc_register_platform - Register a platform with the ASoC core
+ *
+ * @platform: platform to register
+ */
+int snd_soc_register_platform(struct device *dev,
+ struct snd_soc_platform_driver *platform_drv)
+{
+ struct snd_soc_platform *platform;
+
+ dev_dbg(dev, "platform register %s\n", dev_name(dev));
+
+ platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
+ if (platform == NULL)
+ return -ENOMEM;
+
+ /* create platform component name */
+ platform->name = fmt_single_name(dev, &platform->id);
+ if (platform->name == NULL) {
+ kfree(platform);
+ return -ENOMEM;
+ }
+
+ platform->dev = dev;
+ platform->driver = platform_drv;
+
+ mutex_lock(&client_mutex);
+ list_add(&platform->list, &platform_list);
+ snd_soc_instantiate_cards();
+ mutex_unlock(&client_mutex);
+
+ pr_debug("Registered platform '%s'\n", platform->name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_register_platform);
+
+/**
+ * snd_soc_unregister_platform - Unregister a platform from the ASoC core
+ *
+ * @platform: platform to unregister
+ */
+void snd_soc_unregister_platform(struct device *dev)
+{
+ struct snd_soc_platform *platform;
+
+ list_for_each_entry(platform, &platform_list, list) {
+ if (dev == platform->dev)
+ goto found;
+ }
+ return;
+
+found:
+ mutex_lock(&client_mutex);
+ list_del(&platform->list);
+ mutex_unlock(&client_mutex);
+
+ pr_debug("Unregistered platform '%s'\n", platform->name);
+ kfree(platform->name);
+ kfree(platform);
+}
+EXPORT_SYMBOL_GPL(snd_soc_unregister_platform);
+
+static u64 codec_format_map[] = {
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE,
+ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE,
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE,
+ SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE,
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE,
+ SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE,
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3BE,
+ SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_U24_3BE,
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE,
+ SNDRV_PCM_FMTBIT_U20_3LE | SNDRV_PCM_FMTBIT_U20_3BE,
+ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE,
+ SNDRV_PCM_FMTBIT_U18_3LE | SNDRV_PCM_FMTBIT_U18_3BE,
+ SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE,
+ SNDRV_PCM_FMTBIT_FLOAT64_LE | SNDRV_PCM_FMTBIT_FLOAT64_BE,
+ SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE
+ | SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE,
+};
+
+/* Fix up the DAI formats for endianness: codecs don't actually see
+ * the endianness of the data but we're using the CPU format
+ * definitions which do need to include endianness so we ensure that
+ * codec DAIs always have both big and little endian variants set.
+ */
+static void fixup_codec_formats(struct snd_soc_pcm_stream *stream)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(codec_format_map); i++)
+ if (stream->formats & codec_format_map[i])
+ stream->formats |= codec_format_map[i];
+}
+
+/**
+ * snd_soc_register_codec - Register a codec with the ASoC core
+ *
+ * @codec: codec to register
+ */
+int snd_soc_register_codec(struct device *dev,
+ const struct snd_soc_codec_driver *codec_drv,
+ struct snd_soc_dai_driver *dai_drv,
+ int num_dai)
+{
+ size_t reg_size;
+ struct snd_soc_codec *codec;
+ int ret, i;
+
+ dev_dbg(dev, "codec register %s\n", dev_name(dev));
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ /* create CODEC component name */
+ codec->name = fmt_single_name(dev, &codec->id);
+ if (codec->name == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ if (codec_drv->compress_type)
+ codec->compress_type = codec_drv->compress_type;
+ else
+ codec->compress_type = SND_SOC_FLAT_COMPRESSION;
+
+ codec->write = codec_drv->write;
+ codec->read = codec_drv->read;
+ codec->volatile_register = codec_drv->volatile_register;
+ codec->readable_register = codec_drv->readable_register;
+ codec->writable_register = codec_drv->writable_register;
+ codec->dapm.bias_level = SND_SOC_BIAS_OFF;
+ codec->dapm.dev = dev;
+ codec->dapm.codec = codec;
+ codec->dapm.seq_notifier = codec_drv->seq_notifier;
+ codec->dev = dev;
+ codec->driver = codec_drv;
+ codec->num_dai = num_dai;
+ mutex_init(&codec->mutex);
+
+ /* allocate CODEC register cache */
+ if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {
+ reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
+ codec->reg_size = reg_size;
+ /* it is necessary to make a copy of the default register cache
+ * because in the case of using a compression type that requires
+ * the default register cache to be marked as __devinitconst the
+ * kernel might have freed the array by the time we initialize
+ * the cache.
+ */
+ if (codec_drv->reg_cache_default)
+ codec->reg_def_copy =
+ kmemdup(codec_drv->reg_cache_default,
+ reg_size, GFP_KERNEL);
+ else
+ codec->reg_def_copy = kzalloc(reg_size, GFP_KERNEL);
+
+ if (!codec->reg_def_copy) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (codec_drv->reg_access_size && codec_drv->reg_access_default) {
+ if (!codec->volatile_register)
+ codec->volatile_register = snd_soc_default_volatile_register;
+ if (!codec->readable_register)
+ codec->readable_register = snd_soc_default_readable_register;
+ if (!codec->writable_register)
+ codec->writable_register = snd_soc_default_writable_register;
+ }
+
+ for (i = 0; i < num_dai; i++) {
+ fixup_codec_formats(&dai_drv[i].playback);
+ fixup_codec_formats(&dai_drv[i].capture);
+ }
+
+ /* register any DAIs */
+ if (num_dai) {
+ ret = snd_soc_register_dais(dev, dai_drv, num_dai);
+ if (ret < 0)
+ goto fail;
+ }
+
+ mutex_lock(&client_mutex);
+ list_add(&codec->list, &codec_list);
+ snd_soc_instantiate_cards();
+ mutex_unlock(&client_mutex);
+
+ pr_debug("Registered codec '%s'\n", codec->name);
+ return 0;
+
+fail:
+ kfree(codec->reg_def_copy);
+ codec->reg_def_copy = NULL;
+ kfree(codec->name);
+ kfree(codec);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_register_codec);
+
+/**
+ * snd_soc_unregister_codec - Unregister a codec from the ASoC core
+ *
+ * @codec: codec to unregister
+ */
+void snd_soc_unregister_codec(struct device *dev)
+{
+ struct snd_soc_codec *codec;
+ int i;
+
+ list_for_each_entry(codec, &codec_list, list) {
+ if (dev == codec->dev)
+ goto found;
+ }
+ return;
+
+found:
+ if (codec->num_dai)
+ for (i = 0; i < codec->num_dai; i++)
+ snd_soc_unregister_dai(dev);
+
+ mutex_lock(&client_mutex);
+ list_del(&codec->list);
+ mutex_unlock(&client_mutex);
+
+ pr_debug("Unregistered codec '%s'\n", codec->name);
+
+ snd_soc_cache_exit(codec);
+ kfree(codec->reg_def_copy);
+ kfree(codec->name);
+ kfree(codec);
+}
+EXPORT_SYMBOL_GPL(snd_soc_unregister_codec);
+
+static int __init snd_soc_init(void)
+{
+#ifdef CONFIG_DEBUG_FS
+ snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL);
+ if (IS_ERR(snd_soc_debugfs_root) || !snd_soc_debugfs_root) {
+ printk(KERN_WARNING
+ "ASoC: Failed to create debugfs directory\n");
+ snd_soc_debugfs_root = NULL;
+ }
+
+ if (!debugfs_create_file("codecs", 0444, snd_soc_debugfs_root, NULL,
+ &codec_list_fops))
+ pr_warn("ASoC: Failed to create CODEC list debugfs file\n");
+
+ if (!debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL,
+ &dai_list_fops))
+ pr_warn("ASoC: Failed to create DAI list debugfs file\n");
+
+ if (!debugfs_create_file("platforms", 0444, snd_soc_debugfs_root, NULL,
+ &platform_list_fops))
+ pr_warn("ASoC: Failed to create platform list debugfs file\n");
+#endif
+
+ snd_soc_util_init();
+
+ return platform_driver_register(&soc_driver);
+}
+module_init(snd_soc_init);
+
+static void __exit snd_soc_exit(void)
+{
+ snd_soc_util_exit();
+
+#ifdef CONFIG_DEBUG_FS
+ debugfs_remove_recursive(snd_soc_debugfs_root);
+#endif
+ platform_driver_unregister(&soc_driver);
+}
+module_exit(snd_soc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
+MODULE_DESCRIPTION("ALSA SoC Core");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:soc-audio");
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
new file mode 100644
index 00000000..d5ec2060
--- /dev/null
+++ b/sound/soc/soc-dapm.c
@@ -0,0 +1,2648 @@
+/*
+ * soc-dapm.c -- ALSA SoC Dynamic Audio Power Management
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ *
+ * Features:
+ * o Changes power status of internal codec blocks depending on the
+ * dynamic configuration of codec internal audio paths and active
+ * DACs/ADCs.
+ * o Platform power domain - can support external components i.e. amps and
+ * mic/meadphone insertion events.
+ * o Automatic Mic Bias support
+ * o Jack insertion power event initiation - e.g. hp insertion will enable
+ * sinks, dacs, etc
+ * o Delayed powerdown of audio susbsystem to reduce pops between a quick
+ * device reopen.
+ *
+ * Todo:
+ * o DAPM power change sequencing - allow for configurable per
+ * codec sequences.
+ * o Support for analogue bias optimisation.
+ * o Support for reduced codec oversampling rates.
+ * o Support for reduced codec bias currents.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/async.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/jiffies.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include <trace/events/asoc.h>
+
+/* dapm power sequences - make this per codec in the future */
+static int dapm_up_seq[] = {
+ [snd_soc_dapm_pre] = 0,
+ [snd_soc_dapm_supply] = 1,
+ [snd_soc_dapm_micbias] = 2,
+ [snd_soc_dapm_aif_in] = 3,
+ [snd_soc_dapm_aif_out] = 3,
+ [snd_soc_dapm_mic] = 4,
+ [snd_soc_dapm_mux] = 5,
+ [snd_soc_dapm_virt_mux] = 5,
+ [snd_soc_dapm_value_mux] = 5,
+ [snd_soc_dapm_dac] = 6,
+ [snd_soc_dapm_mixer] = 7,
+ [snd_soc_dapm_mixer_named_ctl] = 7,
+ [snd_soc_dapm_pga] = 8,
+ [snd_soc_dapm_adc] = 9,
+ [snd_soc_dapm_out_drv] = 10,
+ [snd_soc_dapm_hp] = 10,
+ [snd_soc_dapm_spk] = 10,
+ [snd_soc_dapm_line] = 10,
+ [snd_soc_dapm_post] = 11,
+};
+
+static int dapm_down_seq[] = {
+ [snd_soc_dapm_pre] = 0,
+ [snd_soc_dapm_adc] = 1,
+ [snd_soc_dapm_hp] = 2,
+ [snd_soc_dapm_spk] = 2,
+ [snd_soc_dapm_line] = 2,
+ [snd_soc_dapm_out_drv] = 2,
+ [snd_soc_dapm_pga] = 4,
+ [snd_soc_dapm_mixer_named_ctl] = 5,
+ [snd_soc_dapm_mixer] = 5,
+ [snd_soc_dapm_dac] = 6,
+ [snd_soc_dapm_mic] = 7,
+ [snd_soc_dapm_micbias] = 8,
+ [snd_soc_dapm_mux] = 9,
+ [snd_soc_dapm_virt_mux] = 9,
+ [snd_soc_dapm_value_mux] = 9,
+ [snd_soc_dapm_aif_in] = 10,
+ [snd_soc_dapm_aif_out] = 10,
+ [snd_soc_dapm_supply] = 11,
+ [snd_soc_dapm_post] = 12,
+};
+
+static void pop_wait(u32 pop_time)
+{
+ if (pop_time)
+ schedule_timeout_uninterruptible(msecs_to_jiffies(pop_time));
+}
+
+static void pop_dbg(struct device *dev, u32 pop_time, const char *fmt, ...)
+{
+ va_list args;
+ char *buf;
+
+ if (!pop_time)
+ return;
+
+ buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (buf == NULL)
+ return;
+
+ va_start(args, fmt);
+ vsnprintf(buf, PAGE_SIZE, fmt, args);
+ dev_info(dev, "%s", buf);
+ va_end(args);
+
+ kfree(buf);
+}
+
+/* create a new dapm widget */
+static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
+ const struct snd_soc_dapm_widget *_widget)
+{
+ return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL);
+}
+
+/**
+ * snd_soc_dapm_set_bias_level - set the bias level for the system
+ * @dapm: DAPM context
+ * @level: level to configure
+ *
+ * Configure the bias (power) levels for the SoC audio device.
+ *
+ * Returns 0 for success else error.
+ */
+static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_card *card = dapm->card;
+ int ret = 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ dev_dbg(dapm->dev, "Setting full bias\n");
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ dev_dbg(dapm->dev, "Setting bias prepare\n");
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ dev_dbg(dapm->dev, "Setting standby bias\n");
+ break;
+ case SND_SOC_BIAS_OFF:
+ dev_dbg(dapm->dev, "Setting bias off\n");
+ break;
+ default:
+ dev_err(dapm->dev, "Setting invalid bias %d\n", level);
+ return -EINVAL;
+ }
+
+ trace_snd_soc_bias_level_start(card, level);
+
+ if (card && card->set_bias_level)
+ ret = card->set_bias_level(card, level);
+ if (ret == 0) {
+ if (dapm->codec && dapm->codec->driver->set_bias_level)
+ ret = dapm->codec->driver->set_bias_level(dapm->codec, level);
+ else
+ dapm->bias_level = level;
+ }
+ if (ret == 0) {
+ if (card && card->set_bias_level_post)
+ ret = card->set_bias_level_post(card, level);
+ }
+
+ trace_snd_soc_bias_level_done(card, level);
+
+ return ret;
+}
+
+/* set up initial codec paths */
+static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
+ struct snd_soc_dapm_path *p, int i)
+{
+ switch (w->id) {
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_mixer:
+ case snd_soc_dapm_mixer_named_ctl: {
+ int val;
+ struct soc_mixer_control *mc = (struct soc_mixer_control *)
+ w->kcontrol_news[i].private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+
+ val = snd_soc_read(w->codec, reg);
+ val = (val >> shift) & mask;
+
+ if ((invert && !val) || (!invert && val))
+ p->connect = 1;
+ else
+ p->connect = 0;
+ }
+ break;
+ case snd_soc_dapm_mux: {
+ struct soc_enum *e = (struct soc_enum *)
+ w->kcontrol_news[i].private_value;
+ int val, item, bitmask;
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ val = snd_soc_read(w->codec, e->reg);
+ item = (val >> e->shift_l) & (bitmask - 1);
+
+ p->connect = 0;
+ for (i = 0; i < e->max; i++) {
+ if (!(strcmp(p->name, e->texts[i])) && item == i)
+ p->connect = 1;
+ }
+ }
+ break;
+ case snd_soc_dapm_virt_mux: {
+ struct soc_enum *e = (struct soc_enum *)
+ w->kcontrol_news[i].private_value;
+
+ p->connect = 0;
+ /* since a virtual mux has no backing registers to
+ * decide which path to connect, it will try to match
+ * with the first enumeration. This is to ensure
+ * that the default mux choice (the first) will be
+ * correctly powered up during initialization.
+ */
+ if (!strcmp(p->name, e->texts[0]))
+ p->connect = 1;
+ }
+ break;
+ case snd_soc_dapm_value_mux: {
+ struct soc_enum *e = (struct soc_enum *)
+ w->kcontrol_news[i].private_value;
+ int val, item;
+
+ val = snd_soc_read(w->codec, e->reg);
+ val = (val >> e->shift_l) & e->mask;
+ for (item = 0; item < e->max; item++) {
+ if (val == e->values[item])
+ break;
+ }
+
+ p->connect = 0;
+ for (i = 0; i < e->max; i++) {
+ if (!(strcmp(p->name, e->texts[i])) && item == i)
+ p->connect = 1;
+ }
+ }
+ break;
+ /* does not effect routing - always connected */
+ case snd_soc_dapm_pga:
+ case snd_soc_dapm_out_drv:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_adc:
+ case snd_soc_dapm_input:
+ case snd_soc_dapm_dac:
+ case snd_soc_dapm_micbias:
+ case snd_soc_dapm_vmid:
+ case snd_soc_dapm_supply:
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ p->connect = 1;
+ break;
+ /* does effect routing - dynamically connected */
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_spk:
+ case snd_soc_dapm_line:
+ case snd_soc_dapm_pre:
+ case snd_soc_dapm_post:
+ p->connect = 0;
+ break;
+ }
+}
+
+/* connect mux widget to its interconnecting audio paths */
+static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
+ struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
+ struct snd_soc_dapm_path *path, const char *control_name,
+ const struct snd_kcontrol_new *kcontrol)
+{
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ int i;
+
+ for (i = 0; i < e->max; i++) {
+ if (!(strcmp(control_name, e->texts[i]))) {
+ list_add(&path->list, &dapm->card->paths);
+ list_add(&path->list_sink, &dest->sources);
+ list_add(&path->list_source, &src->sinks);
+ path->name = (char*)e->texts[i];
+ dapm_set_path_status(dest, path, 0);
+ return 0;
+ }
+ }
+
+ return -ENODEV;
+}
+
+/* connect mixer widget to its interconnecting audio paths */
+static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,
+ struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
+ struct snd_soc_dapm_path *path, const char *control_name)
+{
+ int i;
+
+ /* search for mixer kcontrol */
+ for (i = 0; i < dest->num_kcontrols; i++) {
+ if (!strcmp(control_name, dest->kcontrol_news[i].name)) {
+ list_add(&path->list, &dapm->card->paths);
+ list_add(&path->list_sink, &dest->sources);
+ list_add(&path->list_source, &src->sinks);
+ path->name = dest->kcontrol_news[i].name;
+ dapm_set_path_status(dest, path, i);
+ return 0;
+ }
+ }
+ return -ENODEV;
+}
+
+static int dapm_is_shared_kcontrol(struct snd_soc_dapm_context *dapm,
+ struct snd_soc_dapm_widget *kcontrolw,
+ const struct snd_kcontrol_new *kcontrol_new,
+ struct snd_kcontrol **kcontrol)
+{
+ struct snd_soc_dapm_widget *w;
+ int i;
+
+ *kcontrol = NULL;
+
+ list_for_each_entry(w, &dapm->card->widgets, list) {
+ if (w == kcontrolw || w->dapm != kcontrolw->dapm)
+ continue;
+ for (i = 0; i < w->num_kcontrols; i++) {
+ if (&w->kcontrol_news[i] == kcontrol_new) {
+ if (w->kcontrols)
+ *kcontrol = w->kcontrols[i];
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* create new dapm mixer control */
+static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ int i, ret = 0;
+ size_t name_len, prefix_len;
+ struct snd_soc_dapm_path *path;
+ struct snd_card *card = dapm->card->snd_card;
+ const char *prefix;
+ struct snd_soc_dapm_widget_list *wlist;
+ size_t wlistsize;
+
+ if (dapm->codec)
+ prefix = dapm->codec->name_prefix;
+ else
+ prefix = NULL;
+
+ if (prefix)
+ prefix_len = strlen(prefix) + 1;
+ else
+ prefix_len = 0;
+
+ /* add kcontrol */
+ for (i = 0; i < w->num_kcontrols; i++) {
+
+ /* match name */
+ list_for_each_entry(path, &w->sources, list_sink) {
+
+ /* mixer/mux paths name must match control name */
+ if (path->name != (char *)w->kcontrol_news[i].name)
+ continue;
+
+ wlistsize = sizeof(struct snd_soc_dapm_widget_list) +
+ sizeof(struct snd_soc_dapm_widget *),
+ wlist = kzalloc(wlistsize, GFP_KERNEL);
+ if (wlist == NULL) {
+ dev_err(dapm->dev,
+ "asoc: can't allocate widget list for %s\n",
+ w->name);
+ return -ENOMEM;
+ }
+ wlist->num_widgets = 1;
+ wlist->widgets[0] = w;
+
+ /* add dapm control with long name.
+ * for dapm_mixer this is the concatenation of the
+ * mixer and kcontrol name.
+ * for dapm_mixer_named_ctl this is simply the
+ * kcontrol name.
+ */
+ name_len = strlen(w->kcontrol_news[i].name) + 1;
+ if (w->id != snd_soc_dapm_mixer_named_ctl)
+ name_len += 1 + strlen(w->name);
+
+ path->long_name = kmalloc(name_len, GFP_KERNEL);
+
+ if (path->long_name == NULL) {
+ kfree(wlist);
+ return -ENOMEM;
+ }
+
+ switch (w->id) {
+ default:
+ /* The control will get a prefix from
+ * the control creation process but
+ * we're also using the same prefix
+ * for widgets so cut the prefix off
+ * the front of the widget name.
+ */
+ snprintf(path->long_name, name_len, "%s %s",
+ w->name + prefix_len,
+ w->kcontrol_news[i].name);
+ break;
+ case snd_soc_dapm_mixer_named_ctl:
+ snprintf(path->long_name, name_len, "%s",
+ w->kcontrol_news[i].name);
+ break;
+ }
+
+ path->long_name[name_len - 1] = '\0';
+
+ path->kcontrol = snd_soc_cnew(&w->kcontrol_news[i],
+ wlist, path->long_name,
+ prefix);
+ ret = snd_ctl_add(card, path->kcontrol);
+ if (ret < 0) {
+ dev_err(dapm->dev,
+ "asoc: failed to add dapm kcontrol %s: %d\n",
+ path->long_name, ret);
+ kfree(wlist);
+ kfree(path->long_name);
+ path->long_name = NULL;
+ return ret;
+ }
+ w->kcontrols[i] = path->kcontrol;
+ }
+ }
+ return ret;
+}
+
+/* create new dapm mux control */
+static int dapm_new_mux(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_dapm_path *path = NULL;
+ struct snd_kcontrol *kcontrol;
+ struct snd_card *card = dapm->card->snd_card;
+ const char *prefix;
+ size_t prefix_len;
+ int ret;
+ struct snd_soc_dapm_widget_list *wlist;
+ int shared, wlistentries;
+ size_t wlistsize;
+ char *name;
+
+ if (w->num_kcontrols != 1) {
+ dev_err(dapm->dev,
+ "asoc: mux %s has incorrect number of controls\n",
+ w->name);
+ return -EINVAL;
+ }
+
+ shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[0],
+ &kcontrol);
+ if (kcontrol) {
+ wlist = kcontrol->private_data;
+ wlistentries = wlist->num_widgets + 1;
+ } else {
+ wlist = NULL;
+ wlistentries = 1;
+ }
+ wlistsize = sizeof(struct snd_soc_dapm_widget_list) +
+ wlistentries * sizeof(struct snd_soc_dapm_widget *),
+ wlist = krealloc(wlist, wlistsize, GFP_KERNEL);
+ if (wlist == NULL) {
+ dev_err(dapm->dev,
+ "asoc: can't allocate widget list for %s\n", w->name);
+ return -ENOMEM;
+ }
+ wlist->num_widgets = wlistentries;
+ wlist->widgets[wlistentries - 1] = w;
+
+ if (!kcontrol) {
+ if (dapm->codec)
+ prefix = dapm->codec->name_prefix;
+ else
+ prefix = NULL;
+
+ if (shared) {
+ name = w->kcontrol_news[0].name;
+ prefix_len = 0;
+ } else {
+ name = w->name;
+ if (prefix)
+ prefix_len = strlen(prefix) + 1;
+ else
+ prefix_len = 0;
+ }
+
+ /*
+ * The control will get a prefix from the control creation
+ * process but we're also using the same prefix for widgets so
+ * cut the prefix off the front of the widget name.
+ */
+ kcontrol = snd_soc_cnew(&w->kcontrol_news[0], wlist,
+ name + prefix_len, prefix);
+ ret = snd_ctl_add(card, kcontrol);
+ if (ret < 0) {
+ dev_err(dapm->dev,
+ "asoc: failed to add kcontrol %s\n", w->name);
+ kfree(wlist);
+ return ret;
+ }
+ }
+
+ kcontrol->private_data = wlist;
+
+ w->kcontrols[0] = kcontrol;
+
+ list_for_each_entry(path, &w->sources, list_sink)
+ path->kcontrol = kcontrol;
+
+ return 0;
+}
+
+/* create new dapm volume control */
+static int dapm_new_pga(struct snd_soc_dapm_widget *w)
+{
+ if (w->num_kcontrols)
+ dev_err(w->dapm->dev,
+ "asoc: PGA controls not supported: '%s'\n", w->name);
+
+ return 0;
+}
+
+/* reset 'walked' bit for each dapm path */
+static inline void dapm_clear_walk(struct snd_soc_dapm_context *dapm)
+{
+ struct snd_soc_dapm_path *p;
+
+ list_for_each_entry(p, &dapm->card->paths, list)
+ p->walked = 0;
+}
+
+/* We implement power down on suspend by checking the power state of
+ * the ALSA card - when we are suspending the ALSA state for the card
+ * is set to D3.
+ */
+static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget)
+{
+ int level = snd_power_get_state(widget->dapm->card->snd_card);
+
+ switch (level) {
+ case SNDRV_CTL_POWER_D3hot:
+ case SNDRV_CTL_POWER_D3cold:
+ if (widget->ignore_suspend)
+ dev_dbg(widget->dapm->dev, "%s ignoring suspend\n",
+ widget->name);
+ return widget->ignore_suspend;
+ default:
+ return 1;
+ }
+}
+
+/*
+ * Recursively check for a completed path to an active or physically connected
+ * output widget. Returns number of complete paths.
+ */
+static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
+{
+ struct snd_soc_dapm_path *path;
+ int con = 0;
+
+ if (widget->id == snd_soc_dapm_supply)
+ return 0;
+
+ switch (widget->id) {
+ case snd_soc_dapm_adc:
+ case snd_soc_dapm_aif_out:
+ if (widget->active)
+ return snd_soc_dapm_suspend_check(widget);
+ default:
+ break;
+ }
+
+ if (widget->connected) {
+ /* connected pin ? */
+ if (widget->id == snd_soc_dapm_output && !widget->ext)
+ return snd_soc_dapm_suspend_check(widget);
+
+ /* connected jack or spk ? */
+ if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||
+ (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources)))
+ return snd_soc_dapm_suspend_check(widget);
+ }
+
+ list_for_each_entry(path, &widget->sinks, list_source) {
+ if (path->walked)
+ continue;
+
+ if (path->sink && path->connect) {
+ path->walked = 1;
+ con += is_connected_output_ep(path->sink);
+ }
+ }
+
+ return con;
+}
+
+/*
+ * Recursively check for a completed path to an active or physically connected
+ * input widget. Returns number of complete paths.
+ */
+static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
+{
+ struct snd_soc_dapm_path *path;
+ int con = 0;
+
+ if (widget->id == snd_soc_dapm_supply)
+ return 0;
+
+ /* active stream ? */
+ switch (widget->id) {
+ case snd_soc_dapm_dac:
+ case snd_soc_dapm_aif_in:
+ if (widget->active)
+ return snd_soc_dapm_suspend_check(widget);
+ default:
+ break;
+ }
+
+ if (widget->connected) {
+ /* connected pin ? */
+ if (widget->id == snd_soc_dapm_input && !widget->ext)
+ return snd_soc_dapm_suspend_check(widget);
+
+ /* connected VMID/Bias for lower pops */
+ if (widget->id == snd_soc_dapm_vmid)
+ return snd_soc_dapm_suspend_check(widget);
+
+ /* connected jack ? */
+ if (widget->id == snd_soc_dapm_mic ||
+ (widget->id == snd_soc_dapm_line && !list_empty(&widget->sinks)))
+ return snd_soc_dapm_suspend_check(widget);
+ }
+
+ list_for_each_entry(path, &widget->sources, list_sink) {
+ if (path->walked)
+ continue;
+
+ if (path->source && path->connect) {
+ path->walked = 1;
+ con += is_connected_input_ep(path->source);
+ }
+ }
+
+ return con;
+}
+
+/*
+ * Handler for generic register modifier widget.
+ */
+int dapm_reg_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ unsigned int val;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ val = w->on_val;
+ else
+ val = w->off_val;
+
+ snd_soc_update_bits(w->codec, -(w->reg + 1),
+ w->mask << w->shift, val << w->shift);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dapm_reg_event);
+
+/* Generic check to see if a widget should be powered.
+ */
+static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
+{
+ int in, out;
+
+ in = is_connected_input_ep(w);
+ dapm_clear_walk(w->dapm);
+ out = is_connected_output_ep(w);
+ dapm_clear_walk(w->dapm);
+ return out != 0 && in != 0;
+}
+
+/* Check to see if an ADC has power */
+static int dapm_adc_check_power(struct snd_soc_dapm_widget *w)
+{
+ int in;
+
+ if (w->active) {
+ in = is_connected_input_ep(w);
+ dapm_clear_walk(w->dapm);
+ return in != 0;
+ } else {
+ return dapm_generic_check_power(w);
+ }
+}
+
+/* Check to see if a DAC has power */
+static int dapm_dac_check_power(struct snd_soc_dapm_widget *w)
+{
+ int out;
+
+ if (w->active) {
+ out = is_connected_output_ep(w);
+ dapm_clear_walk(w->dapm);
+ return out != 0;
+ } else {
+ return dapm_generic_check_power(w);
+ }
+}
+
+/* Check to see if a power supply is needed */
+static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_path *path;
+ int power = 0;
+
+ /* Check if one of our outputs is connected */
+ list_for_each_entry(path, &w->sinks, list_source) {
+ if (path->connected &&
+ !path->connected(path->source, path->sink))
+ continue;
+
+ if (!path->sink)
+ continue;
+
+ if (path->sink->force) {
+ power = 1;
+ break;
+ }
+
+ if (path->sink->power_check &&
+ path->sink->power_check(path->sink)) {
+ power = 1;
+ break;
+ }
+ }
+
+ dapm_clear_walk(w->dapm);
+
+ return power;
+}
+
+static int dapm_seq_compare(struct snd_soc_dapm_widget *a,
+ struct snd_soc_dapm_widget *b,
+ bool power_up)
+{
+ int *sort;
+
+ if (power_up)
+ sort = dapm_up_seq;
+ else
+ sort = dapm_down_seq;
+
+ if (sort[a->id] != sort[b->id])
+ return sort[a->id] - sort[b->id];
+ if (a->subseq != b->subseq) {
+ if (power_up)
+ return a->subseq - b->subseq;
+ else
+ return b->subseq - a->subseq;
+ }
+ if (a->reg != b->reg)
+ return a->reg - b->reg;
+ if (a->dapm != b->dapm)
+ return (unsigned long)a->dapm - (unsigned long)b->dapm;
+
+ return 0;
+}
+
+/* Insert a widget in order into a DAPM power sequence. */
+static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget,
+ struct list_head *list,
+ bool power_up)
+{
+ struct snd_soc_dapm_widget *w;
+
+ list_for_each_entry(w, list, power_list)
+ if (dapm_seq_compare(new_widget, w, power_up) < 0) {
+ list_add_tail(&new_widget->power_list, &w->power_list);
+ return;
+ }
+
+ list_add_tail(&new_widget->power_list, list);
+}
+
+static void dapm_seq_check_event(struct snd_soc_dapm_context *dapm,
+ struct snd_soc_dapm_widget *w, int event)
+{
+ struct snd_soc_card *card = dapm->card;
+ const char *ev_name;
+ int power, ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ev_name = "PRE_PMU";
+ power = 1;
+ break;
+ case SND_SOC_DAPM_POST_PMU:
+ ev_name = "POST_PMU";
+ power = 1;
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ ev_name = "PRE_PMD";
+ power = 0;
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ ev_name = "POST_PMD";
+ power = 0;
+ break;
+ default:
+ BUG();
+ return;
+ }
+
+ if (w->power != power)
+ return;
+
+ if (w->event && (w->event_flags & event)) {
+ pop_dbg(dapm->dev, card->pop_time, "pop test : %s %s\n",
+ w->name, ev_name);
+ trace_snd_soc_dapm_widget_event_start(w, event);
+ ret = w->event(w, NULL, event);
+ trace_snd_soc_dapm_widget_event_done(w, event);
+ if (ret < 0)
+ pr_err("%s: %s event failed: %d\n",
+ ev_name, w->name, ret);
+ }
+}
+
+/* Apply the coalesced changes from a DAPM sequence */
+static void dapm_seq_run_coalesced(struct snd_soc_dapm_context *dapm,
+ struct list_head *pending)
+{
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dapm_widget *w;
+ int reg, power;
+ unsigned int value = 0;
+ unsigned int mask = 0;
+ unsigned int cur_mask;
+
+ reg = list_first_entry(pending, struct snd_soc_dapm_widget,
+ power_list)->reg;
+
+ list_for_each_entry(w, pending, power_list) {
+ cur_mask = 1 << w->shift;
+ BUG_ON(reg != w->reg);
+
+ if (w->invert)
+ power = !w->power;
+ else
+ power = w->power;
+
+ mask |= cur_mask;
+ if (power)
+ value |= cur_mask;
+
+ pop_dbg(dapm->dev, card->pop_time,
+ "pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n",
+ w->name, reg, value, mask);
+
+ /* Check for events */
+ dapm_seq_check_event(dapm, w, SND_SOC_DAPM_PRE_PMU);
+ dapm_seq_check_event(dapm, w, SND_SOC_DAPM_PRE_PMD);
+ }
+
+ if (reg >= 0) {
+ pop_dbg(dapm->dev, card->pop_time,
+ "pop test : Applying 0x%x/0x%x to %x in %dms\n",
+ value, mask, reg, card->pop_time);
+ pop_wait(card->pop_time);
+ snd_soc_update_bits(dapm->codec, reg, mask, value);
+ }
+
+ list_for_each_entry(w, pending, power_list) {
+ dapm_seq_check_event(dapm, w, SND_SOC_DAPM_POST_PMU);
+ dapm_seq_check_event(dapm, w, SND_SOC_DAPM_POST_PMD);
+ }
+}
+
+/* Apply a DAPM power sequence.
+ *
+ * We walk over a pre-sorted list of widgets to apply power to. In
+ * order to minimise the number of writes to the device required
+ * multiple widgets will be updated in a single write where possible.
+ * Currently anything that requires more than a single write is not
+ * handled.
+ */
+static void dapm_seq_run(struct snd_soc_dapm_context *dapm,
+ struct list_head *list, int event, bool power_up)
+{
+ struct snd_soc_dapm_widget *w, *n;
+ LIST_HEAD(pending);
+ int cur_sort = -1;
+ int cur_subseq = -1;
+ int cur_reg = SND_SOC_NOPM;
+ struct snd_soc_dapm_context *cur_dapm = NULL;
+ int ret, i;
+ int *sort;
+
+ if (power_up)
+ sort = dapm_up_seq;
+ else
+ sort = dapm_down_seq;
+
+ list_for_each_entry_safe(w, n, list, power_list) {
+ ret = 0;
+
+ /* Do we need to apply any queued changes? */
+ if (sort[w->id] != cur_sort || w->reg != cur_reg ||
+ w->dapm != cur_dapm || w->subseq != cur_subseq) {
+ if (!list_empty(&pending))
+ dapm_seq_run_coalesced(cur_dapm, &pending);
+
+ if (cur_dapm && cur_dapm->seq_notifier) {
+ for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
+ if (sort[i] == cur_sort)
+ cur_dapm->seq_notifier(cur_dapm,
+ i,
+ cur_subseq);
+ }
+
+ INIT_LIST_HEAD(&pending);
+ cur_sort = -1;
+ cur_subseq = -1;
+ cur_reg = SND_SOC_NOPM;
+ cur_dapm = NULL;
+ }
+
+ switch (w->id) {
+ case snd_soc_dapm_pre:
+ if (!w->event)
+ list_for_each_entry_safe_continue(w, n, list,
+ power_list);
+
+ if (event == SND_SOC_DAPM_STREAM_START)
+ ret = w->event(w,
+ NULL, SND_SOC_DAPM_PRE_PMU);
+ else if (event == SND_SOC_DAPM_STREAM_STOP)
+ ret = w->event(w,
+ NULL, SND_SOC_DAPM_PRE_PMD);
+ break;
+
+ case snd_soc_dapm_post:
+ if (!w->event)
+ list_for_each_entry_safe_continue(w, n, list,
+ power_list);
+
+ if (event == SND_SOC_DAPM_STREAM_START)
+ ret = w->event(w,
+ NULL, SND_SOC_DAPM_POST_PMU);
+ else if (event == SND_SOC_DAPM_STREAM_STOP)
+ ret = w->event(w,
+ NULL, SND_SOC_DAPM_POST_PMD);
+ break;
+
+ default:
+ /* Queue it up for application */
+ cur_sort = sort[w->id];
+ cur_subseq = w->subseq;
+ cur_reg = w->reg;
+ cur_dapm = w->dapm;
+ list_move(&w->power_list, &pending);
+ break;
+ }
+
+ if (ret < 0)
+ dev_err(w->dapm->dev,
+ "Failed to apply widget power: %d\n", ret);
+ }
+
+ if (!list_empty(&pending))
+ dapm_seq_run_coalesced(cur_dapm, &pending);
+
+ if (cur_dapm && cur_dapm->seq_notifier) {
+ for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
+ if (sort[i] == cur_sort)
+ cur_dapm->seq_notifier(cur_dapm,
+ i, cur_subseq);
+ }
+}
+
+static void dapm_widget_update(struct snd_soc_dapm_context *dapm)
+{
+ struct snd_soc_dapm_update *update = dapm->update;
+ struct snd_soc_dapm_widget *w;
+ int ret;
+
+ if (!update)
+ return;
+
+ w = update->widget;
+
+ if (w->event &&
+ (w->event_flags & SND_SOC_DAPM_PRE_REG)) {
+ ret = w->event(w, update->kcontrol, SND_SOC_DAPM_PRE_REG);
+ if (ret != 0)
+ pr_err("%s DAPM pre-event failed: %d\n",
+ w->name, ret);
+ }
+
+ ret = snd_soc_update_bits(w->codec, update->reg, update->mask,
+ update->val);
+ if (ret < 0)
+ pr_err("%s DAPM update failed: %d\n", w->name, ret);
+
+ if (w->event &&
+ (w->event_flags & SND_SOC_DAPM_POST_REG)) {
+ ret = w->event(w, update->kcontrol, SND_SOC_DAPM_POST_REG);
+ if (ret != 0)
+ pr_err("%s DAPM post-event failed: %d\n",
+ w->name, ret);
+ }
+}
+
+/* Async callback run prior to DAPM sequences - brings to _PREPARE if
+ * they're changing state.
+ */
+static void dapm_pre_sequence_async(void *data, async_cookie_t cookie)
+{
+ struct snd_soc_dapm_context *d = data;
+ int ret;
+
+ if (d->dev_power && d->bias_level == SND_SOC_BIAS_OFF) {
+ ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);
+ if (ret != 0)
+ dev_err(d->dev,
+ "Failed to turn on bias: %d\n", ret);
+ }
+
+ /* If we're changing to all on or all off then prepare */
+ if ((d->dev_power && d->bias_level == SND_SOC_BIAS_STANDBY) ||
+ (!d->dev_power && d->bias_level == SND_SOC_BIAS_ON)) {
+ ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE);
+ if (ret != 0)
+ dev_err(d->dev,
+ "Failed to prepare bias: %d\n", ret);
+ }
+}
+
+/* Async callback run prior to DAPM sequences - brings to their final
+ * state.
+ */
+static void dapm_post_sequence_async(void *data, async_cookie_t cookie)
+{
+ struct snd_soc_dapm_context *d = data;
+ int ret;
+
+ /* If we just powered the last thing off drop to standby bias */
+ if (d->bias_level == SND_SOC_BIAS_PREPARE && !d->dev_power) {
+ ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);
+ if (ret != 0)
+ dev_err(d->dev, "Failed to apply standby bias: %d\n",
+ ret);
+ }
+
+ /* If we're in standby and can support bias off then do that */
+ if (d->bias_level == SND_SOC_BIAS_STANDBY && d->idle_bias_off) {
+ ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_OFF);
+ if (ret != 0)
+ dev_err(d->dev, "Failed to turn off bias: %d\n", ret);
+ }
+
+ /* If we just powered up then move to active bias */
+ if (d->bias_level == SND_SOC_BIAS_PREPARE && d->dev_power) {
+ ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_ON);
+ if (ret != 0)
+ dev_err(d->dev, "Failed to apply active bias: %d\n",
+ ret);
+ }
+}
+
+/*
+ * Scan each dapm widget for complete audio path.
+ * A complete path is a route that has valid endpoints i.e.:-
+ *
+ * o DAC to output pin.
+ * o Input Pin to ADC.
+ * o Input pin to Output pin (bypass, sidetone)
+ * o DAC to ADC (loopback).
+ */
+static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
+{
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_dapm_context *d;
+ LIST_HEAD(up_list);
+ LIST_HEAD(down_list);
+ LIST_HEAD(async_domain);
+ int power;
+
+ trace_snd_soc_dapm_start(card);
+
+ list_for_each_entry(d, &card->dapm_list, list)
+ if (d->n_widgets || d->codec == NULL)
+ d->dev_power = 0;
+
+ /* Check which widgets we need to power and store them in
+ * lists indicating if they should be powered up or down.
+ */
+ list_for_each_entry(w, &card->widgets, list) {
+ switch (w->id) {
+ case snd_soc_dapm_pre:
+ dapm_seq_insert(w, &down_list, false);
+ break;
+ case snd_soc_dapm_post:
+ dapm_seq_insert(w, &up_list, true);
+ break;
+
+ default:
+ if (!w->power_check)
+ continue;
+
+ if (!w->force)
+ power = w->power_check(w);
+ else
+ power = 1;
+ if (power)
+ w->dapm->dev_power = 1;
+
+ if (w->power == power)
+ continue;
+
+ trace_snd_soc_dapm_widget_power(w, power);
+
+ if (power)
+ dapm_seq_insert(w, &up_list, true);
+ else
+ dapm_seq_insert(w, &down_list, false);
+
+ w->power = power;
+ break;
+ }
+ }
+
+ /* If there are no DAPM widgets then try to figure out power from the
+ * event type.
+ */
+ if (!dapm->n_widgets) {
+ switch (event) {
+ case SND_SOC_DAPM_STREAM_START:
+ case SND_SOC_DAPM_STREAM_RESUME:
+ dapm->dev_power = 1;
+ break;
+ case SND_SOC_DAPM_STREAM_STOP:
+ dapm->dev_power = !!dapm->codec->active;
+ break;
+ case SND_SOC_DAPM_STREAM_SUSPEND:
+ dapm->dev_power = 0;
+ break;
+ case SND_SOC_DAPM_STREAM_NOP:
+ switch (dapm->bias_level) {
+ case SND_SOC_BIAS_STANDBY:
+ case SND_SOC_BIAS_OFF:
+ dapm->dev_power = 0;
+ break;
+ default:
+ dapm->dev_power = 1;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Force all contexts in the card to the same bias state */
+ power = 0;
+ list_for_each_entry(d, &card->dapm_list, list)
+ if (d->dev_power)
+ power = 1;
+ list_for_each_entry(d, &card->dapm_list, list)
+ d->dev_power = power;
+
+
+ /* Run all the bias changes in parallel */
+ list_for_each_entry(d, &dapm->card->dapm_list, list)
+ async_schedule_domain(dapm_pre_sequence_async, d,
+ &async_domain);
+ async_synchronize_full_domain(&async_domain);
+
+ /* Power down widgets first; try to avoid amplifying pops. */
+ dapm_seq_run(dapm, &down_list, event, false);
+
+ dapm_widget_update(dapm);
+
+ /* Now power up. */
+ dapm_seq_run(dapm, &up_list, event, true);
+
+ /* Run all the bias changes in parallel */
+ list_for_each_entry(d, &dapm->card->dapm_list, list)
+ async_schedule_domain(dapm_post_sequence_async, d,
+ &async_domain);
+ async_synchronize_full_domain(&async_domain);
+
+ pop_dbg(dapm->dev, card->pop_time,
+ "DAPM sequencing finished, waiting %dms\n", card->pop_time);
+ pop_wait(card->pop_time);
+
+ trace_snd_soc_dapm_done(card);
+
+ return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int dapm_widget_power_open_file(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return 0;
+}
+
+static ssize_t dapm_widget_power_read_file(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct snd_soc_dapm_widget *w = file->private_data;
+ char *buf;
+ int in, out;
+ ssize_t ret;
+ struct snd_soc_dapm_path *p = NULL;
+
+ buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ in = is_connected_input_ep(w);
+ dapm_clear_walk(w->dapm);
+ out = is_connected_output_ep(w);
+ dapm_clear_walk(w->dapm);
+
+ ret = snprintf(buf, PAGE_SIZE, "%s: %s in %d out %d",
+ w->name, w->power ? "On" : "Off", in, out);
+
+ if (w->reg >= 0)
+ ret += snprintf(buf + ret, PAGE_SIZE - ret,
+ " - R%d(0x%x) bit %d",
+ w->reg, w->reg, w->shift);
+
+ ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
+
+ if (w->sname)
+ ret += snprintf(buf + ret, PAGE_SIZE - ret, " stream %s %s\n",
+ w->sname,
+ w->active ? "active" : "inactive");
+
+ list_for_each_entry(p, &w->sources, list_sink) {
+ if (p->connected && !p->connected(w, p->sink))
+ continue;
+
+ if (p->connect)
+ ret += snprintf(buf + ret, PAGE_SIZE - ret,
+ " in \"%s\" \"%s\"\n",
+ p->name ? p->name : "static",
+ p->source->name);
+ }
+ list_for_each_entry(p, &w->sinks, list_source) {
+ if (p->connected && !p->connected(w, p->sink))
+ continue;
+
+ if (p->connect)
+ ret += snprintf(buf + ret, PAGE_SIZE - ret,
+ " out \"%s\" \"%s\"\n",
+ p->name ? p->name : "static",
+ p->sink->name);
+ }
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+ kfree(buf);
+ return ret;
+}
+
+static const struct file_operations dapm_widget_power_fops = {
+ .open = dapm_widget_power_open_file,
+ .read = dapm_widget_power_read_file,
+ .llseek = default_llseek,
+};
+
+static int dapm_bias_open_file(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return 0;
+}
+
+static ssize_t dapm_bias_read_file(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct snd_soc_dapm_context *dapm = file->private_data;
+ char *level;
+
+ switch (dapm->bias_level) {
+ case SND_SOC_BIAS_ON:
+ level = "On\n";
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ level = "Prepare\n";
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ level = "Standby\n";
+ break;
+ case SND_SOC_BIAS_OFF:
+ level = "Off\n";
+ break;
+ default:
+ BUG();
+ level = "Unknown\n";
+ break;
+ }
+
+ return simple_read_from_buffer(user_buf, count, ppos, level,
+ strlen(level));
+}
+
+static const struct file_operations dapm_bias_fops = {
+ .open = dapm_bias_open_file,
+ .read = dapm_bias_read_file,
+ .llseek = default_llseek,
+};
+
+void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm,
+ struct dentry *parent)
+{
+ struct dentry *d;
+
+ dapm->debugfs_dapm = debugfs_create_dir("dapm", parent);
+
+ if (!dapm->debugfs_dapm) {
+ printk(KERN_WARNING
+ "Failed to create DAPM debugfs directory\n");
+ return;
+ }
+
+ d = debugfs_create_file("bias_level", 0444,
+ dapm->debugfs_dapm, dapm,
+ &dapm_bias_fops);
+ if (!d)
+ dev_warn(dapm->dev,
+ "ASoC: Failed to create bias level debugfs file\n");
+}
+
+static void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct dentry *d;
+
+ if (!dapm->debugfs_dapm || !w->name)
+ return;
+
+ d = debugfs_create_file(w->name, 0444,
+ dapm->debugfs_dapm, w,
+ &dapm_widget_power_fops);
+ if (!d)
+ dev_warn(w->dapm->dev,
+ "ASoC: Failed to create %s debugfs file\n",
+ w->name);
+}
+
+static void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm)
+{
+ debugfs_remove_recursive(dapm->debugfs_dapm);
+}
+
+#else
+void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm,
+ struct dentry *parent)
+{
+}
+
+static inline void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w)
+{
+}
+
+static inline void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm)
+{
+}
+
+#endif
+
+/* test and update the power status of a mux widget */
+static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
+ struct snd_kcontrol *kcontrol, int change,
+ int mux, struct soc_enum *e)
+{
+ struct snd_soc_dapm_path *path;
+ int found = 0;
+
+ if (widget->id != snd_soc_dapm_mux &&
+ widget->id != snd_soc_dapm_virt_mux &&
+ widget->id != snd_soc_dapm_value_mux)
+ return -ENODEV;
+
+ if (!change)
+ return 0;
+
+ /* find dapm widget path assoc with kcontrol */
+ list_for_each_entry(path, &widget->dapm->card->paths, list) {
+ if (path->kcontrol != kcontrol)
+ continue;
+
+ if (!path->name || !e->texts[mux])
+ continue;
+
+ found = 1;
+ /* we now need to match the string in the enum to the path */
+ if (!(strcmp(path->name, e->texts[mux])))
+ path->connect = 1; /* new connection */
+ else
+ path->connect = 0; /* old connection must be powered down */
+ }
+
+ if (found)
+ dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP);
+
+ return 0;
+}
+
+/* test and update the power status of a mixer or switch widget */
+static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
+ struct snd_kcontrol *kcontrol, int connect)
+{
+ struct snd_soc_dapm_path *path;
+ int found = 0;
+
+ if (widget->id != snd_soc_dapm_mixer &&
+ widget->id != snd_soc_dapm_mixer_named_ctl &&
+ widget->id != snd_soc_dapm_switch)
+ return -ENODEV;
+
+ /* find dapm widget path assoc with kcontrol */
+ list_for_each_entry(path, &widget->dapm->card->paths, list) {
+ if (path->kcontrol != kcontrol)
+ continue;
+
+ /* found, now check type */
+ found = 1;
+ path->connect = connect;
+ break;
+ }
+
+ if (found)
+ dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP);
+
+ return 0;
+}
+
+/* show dapm widget status in sys fs */
+static ssize_t dapm_widget_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_pcm_runtime *rtd =
+ container_of(dev, struct snd_soc_pcm_runtime, dev);
+ struct snd_soc_codec *codec =rtd->codec;
+ struct snd_soc_dapm_widget *w;
+ int count = 0;
+ char *state = "not set";
+
+ list_for_each_entry(w, &codec->card->widgets, list) {
+ if (w->dapm != &codec->dapm)
+ continue;
+
+ /* only display widgets that burnm power */
+ switch (w->id) {
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_spk:
+ case snd_soc_dapm_line:
+ case snd_soc_dapm_micbias:
+ case snd_soc_dapm_dac:
+ case snd_soc_dapm_adc:
+ case snd_soc_dapm_pga:
+ case snd_soc_dapm_out_drv:
+ case snd_soc_dapm_mixer:
+ case snd_soc_dapm_mixer_named_ctl:
+ case snd_soc_dapm_supply:
+ if (w->name)
+ count += sprintf(buf + count, "%s: %s\n",
+ w->name, w->power ? "On":"Off");
+ break;
+ default:
+ break;
+ }
+ }
+
+ switch (codec->dapm.bias_level) {
+ case SND_SOC_BIAS_ON:
+ state = "On";
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ state = "Prepare";
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ state = "Standby";
+ break;
+ case SND_SOC_BIAS_OFF:
+ state = "Off";
+ break;
+ }
+ count += sprintf(buf + count, "PM State: %s\n", state);
+
+ return count;
+}
+
+static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL);
+
+int snd_soc_dapm_sys_add(struct device *dev)
+{
+ return device_create_file(dev, &dev_attr_dapm_widget);
+}
+
+static void snd_soc_dapm_sys_remove(struct device *dev)
+{
+ device_remove_file(dev, &dev_attr_dapm_widget);
+}
+
+/* free all dapm widgets and resources */
+static void dapm_free_widgets(struct snd_soc_dapm_context *dapm)
+{
+ struct snd_soc_dapm_widget *w, *next_w;
+ struct snd_soc_dapm_path *p, *next_p;
+
+ list_for_each_entry_safe(w, next_w, &dapm->card->widgets, list) {
+ if (w->dapm != dapm)
+ continue;
+ list_del(&w->list);
+ /*
+ * remove source and sink paths associated to this widget.
+ * While removing the path, remove reference to it from both
+ * source and sink widgets so that path is removed only once.
+ */
+ list_for_each_entry_safe(p, next_p, &w->sources, list_sink) {
+ list_del(&p->list_sink);
+ list_del(&p->list_source);
+ list_del(&p->list);
+ kfree(p->long_name);
+ kfree(p);
+ }
+ list_for_each_entry_safe(p, next_p, &w->sinks, list_source) {
+ list_del(&p->list_sink);
+ list_del(&p->list_source);
+ list_del(&p->list);
+ kfree(p->long_name);
+ kfree(p);
+ }
+ kfree(w->kcontrols);
+ kfree(w->name);
+ kfree(w);
+ }
+}
+
+static struct snd_soc_dapm_widget *dapm_find_widget(
+ struct snd_soc_dapm_context *dapm, const char *pin,
+ bool search_other_contexts)
+{
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_dapm_widget *fallback = NULL;
+
+ list_for_each_entry(w, &dapm->card->widgets, list) {
+ if (!strcmp(w->name, pin)) {
+ if (w->dapm == dapm)
+ return w;
+ else
+ fallback = w;
+ }
+ }
+
+ if (search_other_contexts)
+ return fallback;
+
+ return NULL;
+}
+
+static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm,
+ const char *pin, int status)
+{
+ struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true);
+
+ if (!w) {
+ dev_err(dapm->dev, "dapm: unknown pin %s\n", pin);
+ return -EINVAL;
+ }
+
+ w->connected = status;
+ if (status == 0)
+ w->force = 0;
+
+ return 0;
+}
+
+/**
+ * snd_soc_dapm_sync - scan and power dapm paths
+ * @dapm: DAPM context
+ *
+ * Walks all dapm audio paths and powers widgets according to their
+ * stream or path usage.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_sync(struct snd_soc_dapm_context *dapm)
+{
+ return dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_sync);
+
+static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
+ const struct snd_soc_dapm_route *route)
+{
+ struct snd_soc_dapm_path *path;
+ struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
+ struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
+ const char *sink;
+ const char *control = route->control;
+ const char *source;
+ char prefixed_sink[80];
+ char prefixed_source[80];
+ int ret = 0;
+
+ if (dapm->codec && dapm->codec->name_prefix) {
+ snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s",
+ dapm->codec->name_prefix, route->sink);
+ sink = prefixed_sink;
+ snprintf(prefixed_source, sizeof(prefixed_source), "%s %s",
+ dapm->codec->name_prefix, route->source);
+ source = prefixed_source;
+ } else {
+ sink = route->sink;
+ source = route->source;
+ }
+
+ /*
+ * find src and dest widgets over all widgets but favor a widget from
+ * current DAPM context
+ */
+ list_for_each_entry(w, &dapm->card->widgets, list) {
+ if (!wsink && !(strcmp(w->name, sink))) {
+ wtsink = w;
+ if (w->dapm == dapm)
+ wsink = w;
+ continue;
+ }
+ if (!wsource && !(strcmp(w->name, source))) {
+ wtsource = w;
+ if (w->dapm == dapm)
+ wsource = w;
+ }
+ }
+ /* use widget from another DAPM context if not found from this */
+ if (!wsink)
+ wsink = wtsink;
+ if (!wsource)
+ wsource = wtsource;
+
+ if (wsource == NULL || wsink == NULL)
+ return -ENODEV;
+
+ path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
+ if (!path)
+ return -ENOMEM;
+
+ path->source = wsource;
+ path->sink = wsink;
+ path->connected = route->connected;
+ INIT_LIST_HEAD(&path->list);
+ INIT_LIST_HEAD(&path->list_source);
+ INIT_LIST_HEAD(&path->list_sink);
+
+ /* check for external widgets */
+ if (wsink->id == snd_soc_dapm_input) {
+ if (wsource->id == snd_soc_dapm_micbias ||
+ wsource->id == snd_soc_dapm_mic ||
+ wsource->id == snd_soc_dapm_line ||
+ wsource->id == snd_soc_dapm_output)
+ wsink->ext = 1;
+ }
+ if (wsource->id == snd_soc_dapm_output) {
+ if (wsink->id == snd_soc_dapm_spk ||
+ wsink->id == snd_soc_dapm_hp ||
+ wsink->id == snd_soc_dapm_line ||
+ wsink->id == snd_soc_dapm_input)
+ wsource->ext = 1;
+ }
+
+ /* connect static paths */
+ if (control == NULL) {
+ list_add(&path->list, &dapm->card->paths);
+ list_add(&path->list_sink, &wsink->sources);
+ list_add(&path->list_source, &wsource->sinks);
+ path->connect = 1;
+ return 0;
+ }
+
+ /* connect dynamic paths */
+ switch (wsink->id) {
+ case snd_soc_dapm_adc:
+ case snd_soc_dapm_dac:
+ case snd_soc_dapm_pga:
+ case snd_soc_dapm_out_drv:
+ case snd_soc_dapm_input:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_micbias:
+ case snd_soc_dapm_vmid:
+ case snd_soc_dapm_pre:
+ case snd_soc_dapm_post:
+ case snd_soc_dapm_supply:
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ list_add(&path->list, &dapm->card->paths);
+ list_add(&path->list_sink, &wsink->sources);
+ list_add(&path->list_source, &wsource->sinks);
+ path->connect = 1;
+ return 0;
+ case snd_soc_dapm_mux:
+ case snd_soc_dapm_virt_mux:
+ case snd_soc_dapm_value_mux:
+ ret = dapm_connect_mux(dapm, wsource, wsink, path, control,
+ &wsink->kcontrol_news[0]);
+ if (ret != 0)
+ goto err;
+ break;
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_mixer:
+ case snd_soc_dapm_mixer_named_ctl:
+ ret = dapm_connect_mixer(dapm, wsource, wsink, path, control);
+ if (ret != 0)
+ goto err;
+ break;
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_line:
+ case snd_soc_dapm_spk:
+ list_add(&path->list, &dapm->card->paths);
+ list_add(&path->list_sink, &wsink->sources);
+ list_add(&path->list_source, &wsource->sinks);
+ path->connect = 0;
+ return 0;
+ }
+ return 0;
+
+err:
+ dev_warn(dapm->dev, "asoc: no dapm match for %s --> %s --> %s\n",
+ source, control, sink);
+ kfree(path);
+ return ret;
+}
+
+/**
+ * snd_soc_dapm_add_routes - Add routes between DAPM widgets
+ * @dapm: DAPM context
+ * @route: audio routes
+ * @num: number of routes
+ *
+ * Connects 2 dapm widgets together via a named audio path. The sink is
+ * the widget receiving the audio signal, whilst the source is the sender
+ * of the audio signal.
+ *
+ * Returns 0 for success else error. On error all resources can be freed
+ * with a call to snd_soc_card_free().
+ */
+int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
+ const struct snd_soc_dapm_route *route, int num)
+{
+ int i, ret;
+
+ for (i = 0; i < num; i++) {
+ ret = snd_soc_dapm_add_route(dapm, route);
+ if (ret < 0) {
+ dev_err(dapm->dev, "Failed to add route %s->%s\n",
+ route->source, route->sink);
+ return ret;
+ }
+ route++;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_add_routes);
+
+/**
+ * snd_soc_dapm_new_widgets - add new dapm widgets
+ * @dapm: DAPM context
+ *
+ * Checks the codec for any new dapm widgets and creates them if found.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
+{
+ struct snd_soc_dapm_widget *w;
+ unsigned int val;
+
+ list_for_each_entry(w, &dapm->card->widgets, list)
+ {
+ if (w->new)
+ continue;
+
+ if (w->num_kcontrols) {
+ w->kcontrols = kzalloc(w->num_kcontrols *
+ sizeof(struct snd_kcontrol *),
+ GFP_KERNEL);
+ if (!w->kcontrols)
+ return -ENOMEM;
+ }
+
+ switch(w->id) {
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_mixer:
+ case snd_soc_dapm_mixer_named_ctl:
+ w->power_check = dapm_generic_check_power;
+ dapm_new_mixer(w);
+ break;
+ case snd_soc_dapm_mux:
+ case snd_soc_dapm_virt_mux:
+ case snd_soc_dapm_value_mux:
+ w->power_check = dapm_generic_check_power;
+ dapm_new_mux(w);
+ break;
+ case snd_soc_dapm_adc:
+ case snd_soc_dapm_aif_out:
+ w->power_check = dapm_adc_check_power;
+ break;
+ case snd_soc_dapm_dac:
+ case snd_soc_dapm_aif_in:
+ w->power_check = dapm_dac_check_power;
+ break;
+ case snd_soc_dapm_pga:
+ case snd_soc_dapm_out_drv:
+ w->power_check = dapm_generic_check_power;
+ dapm_new_pga(w);
+ break;
+ case snd_soc_dapm_input:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_micbias:
+ case snd_soc_dapm_spk:
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_line:
+ w->power_check = dapm_generic_check_power;
+ break;
+ case snd_soc_dapm_supply:
+ w->power_check = dapm_supply_check_power;
+ case snd_soc_dapm_vmid:
+ case snd_soc_dapm_pre:
+ case snd_soc_dapm_post:
+ break;
+ }
+
+ /* Read the initial power state from the device */
+ if (w->reg >= 0) {
+ val = snd_soc_read(w->codec, w->reg);
+ val &= 1 << w->shift;
+ if (w->invert)
+ val = !val;
+
+ if (val)
+ w->power = 1;
+ }
+
+ w->new = 1;
+
+ dapm_debugfs_add_widget(w);
+ }
+
+ dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets);
+
+/**
+ * snd_soc_dapm_get_volsw - dapm mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a dapm mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+ int max = mc->max;
+ unsigned int invert = mc->invert;
+ unsigned int mask = (1 << fls(max)) - 1;
+
+ ucontrol->value.integer.value[0] =
+ (snd_soc_read(widget->codec, reg) >> shift) & mask;
+ if (shift != rshift)
+ ucontrol->value.integer.value[1] =
+ (snd_soc_read(widget->codec, reg) >> rshift) & mask;
+ if (invert) {
+ ucontrol->value.integer.value[0] =
+ max - ucontrol->value.integer.value[0];
+ if (shift != rshift)
+ ucontrol->value.integer.value[1] =
+ max - ucontrol->value.integer.value[1];
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_volsw);
+
+/**
+ * snd_soc_dapm_put_volsw - dapm mixer set callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a dapm mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+ unsigned int val;
+ int connect, change;
+ struct snd_soc_dapm_update update;
+ int wi;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+
+ if (invert)
+ val = max - val;
+ mask = mask << shift;
+ val = val << shift;
+
+ if (val)
+ /* new connection */
+ connect = invert ? 0 : 1;
+ else
+ /* old connection must be powered down */
+ connect = invert ? 1 : 0;
+
+ mutex_lock(&codec->mutex);
+
+ change = snd_soc_test_bits(widget->codec, reg, mask, val);
+ if (change) {
+ for (wi = 0; wi < wlist->num_widgets; wi++) {
+ widget = wlist->widgets[wi];
+
+ widget->value = val;
+
+ update.kcontrol = kcontrol;
+ update.widget = widget;
+ update.reg = reg;
+ update.mask = mask;
+ update.val = val;
+ widget->dapm->update = &update;
+
+ dapm_mixer_update_power(widget, kcontrol, connect);
+
+ widget->dapm->update = NULL;
+ }
+ }
+
+ mutex_unlock(&codec->mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw);
+
+/**
+ * snd_soc_dapm_get_enum_double - dapm enumerated double mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a dapm enumerated double mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int val, bitmask;
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ val = snd_soc_read(widget->codec, e->reg);
+ ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1);
+ if (e->shift_l != e->shift_r)
+ ucontrol->value.enumerated.item[1] =
+ (val >> e->shift_r) & (bitmask - 1);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_double);
+
+/**
+ * snd_soc_dapm_put_enum_double - dapm enumerated double mixer set callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a dapm enumerated double mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int val, mux, change;
+ unsigned int mask, bitmask;
+ struct snd_soc_dapm_update update;
+ int wi;
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+ mux = ucontrol->value.enumerated.item[0];
+ val = mux << e->shift_l;
+ mask = (bitmask - 1) << e->shift_l;
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->max - 1)
+ return -EINVAL;
+ val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+ mask |= (bitmask - 1) << e->shift_r;
+ }
+
+ mutex_lock(&codec->mutex);
+
+ change = snd_soc_test_bits(widget->codec, e->reg, mask, val);
+ if (change) {
+ for (wi = 0; wi < wlist->num_widgets; wi++) {
+ widget = wlist->widgets[wi];
+
+ widget->value = val;
+
+ update.kcontrol = kcontrol;
+ update.widget = widget;
+ update.reg = e->reg;
+ update.mask = mask;
+ update.val = val;
+ widget->dapm->update = &update;
+
+ dapm_mux_update_power(widget, kcontrol, change, mux, e);
+
+ widget->dapm->update = NULL;
+ }
+ }
+
+ mutex_unlock(&codec->mutex);
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double);
+
+/**
+ * snd_soc_dapm_get_enum_virt - Get virtual DAPM mux
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_get_enum_virt(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+
+ ucontrol->value.enumerated.item[0] = widget->value;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_virt);
+
+/**
+ * snd_soc_dapm_put_enum_virt - Set virtual DAPM mux
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct soc_enum *e =
+ (struct soc_enum *)kcontrol->private_value;
+ int change;
+ int ret = 0;
+ int wi;
+
+ if (ucontrol->value.enumerated.item[0] >= e->max)
+ return -EINVAL;
+
+ mutex_lock(&codec->mutex);
+
+ change = widget->value != ucontrol->value.enumerated.item[0];
+ if (change) {
+ for (wi = 0; wi < wlist->num_widgets; wi++) {
+ widget = wlist->widgets[wi];
+
+ widget->value = ucontrol->value.enumerated.item[0];
+
+ dapm_mux_update_power(widget, kcontrol, change,
+ widget->value, e);
+ }
+ }
+
+ mutex_unlock(&codec->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_virt);
+
+/**
+ * snd_soc_dapm_get_value_enum_double - dapm semi enumerated double mixer get
+ * callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a dapm semi enumerated double mixer control.
+ *
+ * Semi enumerated mixer: the enumerated items are referred as values. Can be
+ * used for handling bitfield coded enumeration for example.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_get_value_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int reg_val, val, mux;
+
+ reg_val = snd_soc_read(widget->codec, e->reg);
+ val = (reg_val >> e->shift_l) & e->mask;
+ for (mux = 0; mux < e->max; mux++) {
+ if (val == e->values[mux])
+ break;
+ }
+ ucontrol->value.enumerated.item[0] = mux;
+ if (e->shift_l != e->shift_r) {
+ val = (reg_val >> e->shift_r) & e->mask;
+ for (mux = 0; mux < e->max; mux++) {
+ if (val == e->values[mux])
+ break;
+ }
+ ucontrol->value.enumerated.item[1] = mux;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_value_enum_double);
+
+/**
+ * snd_soc_dapm_put_value_enum_double - dapm semi enumerated double mixer set
+ * callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a dapm semi enumerated double mixer control.
+ *
+ * Semi enumerated mixer: the enumerated items are referred as values. Can be
+ * used for handling bitfield coded enumeration for example.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int val, mux, change;
+ unsigned int mask;
+ struct snd_soc_dapm_update update;
+ int wi;
+
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+ mux = ucontrol->value.enumerated.item[0];
+ val = e->values[ucontrol->value.enumerated.item[0]] << e->shift_l;
+ mask = e->mask << e->shift_l;
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->max - 1)
+ return -EINVAL;
+ val |= e->values[ucontrol->value.enumerated.item[1]] << e->shift_r;
+ mask |= e->mask << e->shift_r;
+ }
+
+ mutex_lock(&codec->mutex);
+
+ change = snd_soc_test_bits(widget->codec, e->reg, mask, val);
+ if (change) {
+ for (wi = 0; wi < wlist->num_widgets; wi++) {
+ widget = wlist->widgets[wi];
+
+ widget->value = val;
+
+ update.kcontrol = kcontrol;
+ update.widget = widget;
+ update.reg = e->reg;
+ update.mask = mask;
+ update.val = val;
+ widget->dapm->update = &update;
+
+ dapm_mux_update_power(widget, kcontrol, change, mux, e);
+
+ widget->dapm->update = NULL;
+ }
+ }
+
+ mutex_unlock(&codec->mutex);
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_value_enum_double);
+
+/**
+ * snd_soc_dapm_info_pin_switch - Info for a pin switch
+ *
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a pin switch control.
+ */
+int snd_soc_dapm_info_pin_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_info_pin_switch);
+
+/**
+ * snd_soc_dapm_get_pin_switch - Get information for a pin switch
+ *
+ * @kcontrol: mixer control
+ * @ucontrol: Value
+ */
+int snd_soc_dapm_get_pin_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ const char *pin = (const char *)kcontrol->private_value;
+
+ mutex_lock(&codec->mutex);
+
+ ucontrol->value.integer.value[0] =
+ snd_soc_dapm_get_pin_status(&codec->dapm, pin);
+
+ mutex_unlock(&codec->mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_switch);
+
+/**
+ * snd_soc_dapm_put_pin_switch - Set information for a pin switch
+ *
+ * @kcontrol: mixer control
+ * @ucontrol: Value
+ */
+int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ const char *pin = (const char *)kcontrol->private_value;
+
+ mutex_lock(&codec->mutex);
+
+ if (ucontrol->value.integer.value[0])
+ snd_soc_dapm_enable_pin(&codec->dapm, pin);
+ else
+ snd_soc_dapm_disable_pin(&codec->dapm, pin);
+
+ snd_soc_dapm_sync(&codec->dapm);
+
+ mutex_unlock(&codec->mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_pin_switch);
+
+/**
+ * snd_soc_dapm_new_control - create new dapm control
+ * @dapm: DAPM context
+ * @widget: widget template
+ *
+ * Creates a new dapm control based upon the template.
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
+ const struct snd_soc_dapm_widget *widget)
+{
+ struct snd_soc_dapm_widget *w;
+ size_t name_len;
+
+ if ((w = dapm_cnew_widget(widget)) == NULL)
+ return -ENOMEM;
+
+ name_len = strlen(widget->name) + 1;
+ if (dapm->codec && dapm->codec->name_prefix)
+ name_len += 1 + strlen(dapm->codec->name_prefix);
+ w->name = kmalloc(name_len, GFP_KERNEL);
+ if (w->name == NULL) {
+ kfree(w);
+ return -ENOMEM;
+ }
+ if (dapm->codec && dapm->codec->name_prefix)
+ snprintf(w->name, name_len, "%s %s",
+ dapm->codec->name_prefix, widget->name);
+ else
+ snprintf(w->name, name_len, "%s", widget->name);
+
+ dapm->n_widgets++;
+ w->dapm = dapm;
+ w->codec = dapm->codec;
+ INIT_LIST_HEAD(&w->sources);
+ INIT_LIST_HEAD(&w->sinks);
+ INIT_LIST_HEAD(&w->list);
+ list_add(&w->list, &dapm->card->widgets);
+
+ /* machine layer set ups unconnected pins and insertions */
+ w->connected = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control);
+
+/**
+ * snd_soc_dapm_new_controls - create new dapm controls
+ * @dapm: DAPM context
+ * @widget: widget array
+ * @num: number of widgets
+ *
+ * Creates new DAPM controls based upon the templates.
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
+ const struct snd_soc_dapm_widget *widget,
+ int num)
+{
+ int i, ret;
+
+ for (i = 0; i < num; i++) {
+ ret = snd_soc_dapm_new_control(dapm, widget);
+ if (ret < 0) {
+ dev_err(dapm->dev,
+ "ASoC: Failed to create DAPM control %s: %d\n",
+ widget->name, ret);
+ return ret;
+ }
+ widget++;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls);
+
+static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm,
+ const char *stream, int event)
+{
+ struct snd_soc_dapm_widget *w;
+
+ list_for_each_entry(w, &dapm->card->widgets, list)
+ {
+ if (!w->sname || w->dapm != dapm)
+ continue;
+ dev_dbg(w->dapm->dev, "widget %s\n %s stream %s event %d\n",
+ w->name, w->sname, stream, event);
+ if (strstr(w->sname, stream)) {
+ switch(event) {
+ case SND_SOC_DAPM_STREAM_START:
+ w->active = 1;
+ break;
+ case SND_SOC_DAPM_STREAM_STOP:
+ w->active = 0;
+ break;
+ case SND_SOC_DAPM_STREAM_SUSPEND:
+ case SND_SOC_DAPM_STREAM_RESUME:
+ case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
+ case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
+ break;
+ }
+ }
+ }
+
+ dapm_power_widgets(dapm, event);
+}
+
+/**
+ * snd_soc_dapm_stream_event - send a stream event to the dapm core
+ * @rtd: PCM runtime data
+ * @stream: stream name
+ * @event: stream event
+ *
+ * Sends a stream event to the dapm core. The core then makes any
+ * necessary widget power changes.
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd,
+ const char *stream, int event)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+
+ if (stream == NULL)
+ return 0;
+
+ mutex_lock(&codec->mutex);
+ soc_dapm_stream_event(&codec->dapm, stream, event);
+ mutex_unlock(&codec->mutex);
+ return 0;
+}
+
+/**
+ * snd_soc_dapm_enable_pin - enable pin.
+ * @dapm: DAPM context
+ * @pin: pin name
+ *
+ * Enables input/output pin and its parents or children widgets iff there is
+ * a valid audio route and active audio stream.
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_enable_pin(struct snd_soc_dapm_context *dapm, const char *pin)
+{
+ return snd_soc_dapm_set_pin(dapm, pin, 1);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin);
+
+/**
+ * snd_soc_dapm_force_enable_pin - force a pin to be enabled
+ * @dapm: DAPM context
+ * @pin: pin name
+ *
+ * Enables input/output pin regardless of any other state. This is
+ * intended for use with microphone bias supplies used in microphone
+ * jack detection.
+ *
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_force_enable_pin(struct snd_soc_dapm_context *dapm,
+ const char *pin)
+{
+ struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true);
+
+ if (!w) {
+ dev_err(dapm->dev, "dapm: unknown pin %s\n", pin);
+ return -EINVAL;
+ }
+
+ dev_dbg(w->dapm->dev, "dapm: force enable pin %s\n", pin);
+ w->connected = 1;
+ w->force = 1;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_force_enable_pin);
+
+/**
+ * snd_soc_dapm_disable_pin - disable pin.
+ * @dapm: DAPM context
+ * @pin: pin name
+ *
+ * Disables input/output pin and its parents or children widgets.
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_disable_pin(struct snd_soc_dapm_context *dapm,
+ const char *pin)
+{
+ return snd_soc_dapm_set_pin(dapm, pin, 0);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_disable_pin);
+
+/**
+ * snd_soc_dapm_nc_pin - permanently disable pin.
+ * @dapm: DAPM context
+ * @pin: pin name
+ *
+ * Marks the specified pin as being not connected, disabling it along
+ * any parent or child widgets. At present this is identical to
+ * snd_soc_dapm_disable_pin() but in future it will be extended to do
+ * additional things such as disabling controls which only affect
+ * paths through the pin.
+ *
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_nc_pin(struct snd_soc_dapm_context *dapm, const char *pin)
+{
+ return snd_soc_dapm_set_pin(dapm, pin, 0);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_nc_pin);
+
+/**
+ * snd_soc_dapm_get_pin_status - get audio pin status
+ * @dapm: DAPM context
+ * @pin: audio signal pin endpoint (or start point)
+ *
+ * Get audio pin status - connected or disconnected.
+ *
+ * Returns 1 for connected otherwise 0.
+ */
+int snd_soc_dapm_get_pin_status(struct snd_soc_dapm_context *dapm,
+ const char *pin)
+{
+ struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true);
+
+ if (w)
+ return w->connected;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_status);
+
+/**
+ * snd_soc_dapm_ignore_suspend - ignore suspend status for DAPM endpoint
+ * @dapm: DAPM context
+ * @pin: audio signal pin endpoint (or start point)
+ *
+ * Mark the given endpoint or pin as ignoring suspend. When the
+ * system is disabled a path between two endpoints flagged as ignoring
+ * suspend will not be disabled. The path must already be enabled via
+ * normal means at suspend time, it will not be turned on if it was not
+ * already enabled.
+ */
+int snd_soc_dapm_ignore_suspend(struct snd_soc_dapm_context *dapm,
+ const char *pin)
+{
+ struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, false);
+
+ if (!w) {
+ dev_err(dapm->dev, "dapm: unknown pin %s\n", pin);
+ return -EINVAL;
+ }
+
+ w->ignore_suspend = 1;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend);
+
+/**
+ * snd_soc_dapm_free - free dapm resources
+ * @card: SoC device
+ *
+ * Free all dapm widgets and resources.
+ */
+void snd_soc_dapm_free(struct snd_soc_dapm_context *dapm)
+{
+ snd_soc_dapm_sys_remove(dapm->dev);
+ dapm_debugfs_cleanup(dapm);
+ dapm_free_widgets(dapm);
+ list_del(&dapm->list);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_free);
+
+static void soc_dapm_shutdown_codec(struct snd_soc_dapm_context *dapm)
+{
+ struct snd_soc_dapm_widget *w;
+ LIST_HEAD(down_list);
+ int powerdown = 0;
+
+ list_for_each_entry(w, &dapm->card->widgets, list) {
+ if (w->dapm != dapm)
+ continue;
+ if (w->power) {
+ dapm_seq_insert(w, &down_list, false);
+ w->power = 0;
+ powerdown = 1;
+ }
+ }
+
+ /* If there were no widgets to power down we're already in
+ * standby.
+ */
+ if (powerdown) {
+ if (dapm->bias_level == SND_SOC_BIAS_ON)
+ snd_soc_dapm_set_bias_level(dapm,
+ SND_SOC_BIAS_PREPARE);
+ dapm_seq_run(dapm, &down_list, 0, false);
+ if (dapm->bias_level == SND_SOC_BIAS_PREPARE)
+ snd_soc_dapm_set_bias_level(dapm,
+ SND_SOC_BIAS_STANDBY);
+ }
+}
+
+/*
+ * snd_soc_dapm_shutdown - callback for system shutdown
+ */
+void snd_soc_dapm_shutdown(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec;
+
+ list_for_each_entry(codec, &card->codec_dev_list, list) {
+ soc_dapm_shutdown_codec(&codec->dapm);
+ if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY)
+ snd_soc_dapm_set_bias_level(&codec->dapm,
+ SND_SOC_BIAS_OFF);
+ }
+}
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
+MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c
new file mode 100644
index 00000000..fa31d9c2
--- /dev/null
+++ b/sound/soc/soc-jack.c
@@ -0,0 +1,386 @@
+/*
+ * soc-jack.c -- ALSA SoC jack handling
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <trace/events/asoc.h>
+
+/**
+ * snd_soc_jack_new - Create a new jack
+ * @card: ASoC card
+ * @id: an identifying string for this jack
+ * @type: a bitmask of enum snd_jack_type values that can be detected by
+ * this jack
+ * @jack: structure to use for the jack
+ *
+ * Creates a new jack object.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ * On success jack will be initialised.
+ */
+int snd_soc_jack_new(struct snd_soc_codec *codec, const char *id, int type,
+ struct snd_soc_jack *jack)
+{
+ jack->codec = codec;
+ INIT_LIST_HEAD(&jack->pins);
+ INIT_LIST_HEAD(&jack->jack_zones);
+ BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier);
+
+ return snd_jack_new(codec->card->snd_card, id, type, &jack->jack);
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_new);
+
+/**
+ * snd_soc_jack_report - Report the current status for a jack
+ *
+ * @jack: the jack
+ * @status: a bitmask of enum snd_jack_type values that are currently detected.
+ * @mask: a bitmask of enum snd_jack_type values that being reported.
+ *
+ * If configured using snd_soc_jack_add_pins() then the associated
+ * DAPM pins will be enabled or disabled as appropriate and DAPM
+ * synchronised.
+ *
+ * Note: This function uses mutexes and should be called from a
+ * context which can sleep (such as a workqueue).
+ */
+void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask)
+{
+ struct snd_soc_codec *codec;
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_jack_pin *pin;
+ int enable;
+ int oldstatus;
+
+ trace_snd_soc_jack_report(jack, mask, status);
+
+ if (!jack)
+ return;
+
+ codec = jack->codec;
+ dapm = &codec->dapm;
+
+ mutex_lock(&codec->mutex);
+
+ oldstatus = jack->status;
+
+ jack->status &= ~mask;
+ jack->status |= status & mask;
+
+ /* The DAPM sync is expensive enough to be worth skipping.
+ * However, empty mask means pin synchronization is desired. */
+ if (mask && (jack->status == oldstatus))
+ goto out;
+
+ trace_snd_soc_jack_notify(jack, status);
+
+ list_for_each_entry(pin, &jack->pins, list) {
+ enable = pin->mask & jack->status;
+
+ if (pin->invert)
+ enable = !enable;
+
+ if (enable)
+ snd_soc_dapm_enable_pin(dapm, pin->pin);
+ else
+ snd_soc_dapm_disable_pin(dapm, pin->pin);
+ }
+
+ /* Report before the DAPM sync to help users updating micbias status */
+ blocking_notifier_call_chain(&jack->notifier, status, jack);
+
+ snd_soc_dapm_sync(dapm);
+
+ snd_jack_report(jack->jack, jack->status);
+
+out:
+ mutex_unlock(&codec->mutex);
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_report);
+
+/**
+ * snd_soc_jack_add_zones - Associate voltage zones with jack
+ *
+ * @jack: ASoC jack
+ * @count: Number of zones
+ * @zone: Array of zones
+ *
+ * After this function has been called the zones specified in the
+ * array will be associated with the jack.
+ */
+int snd_soc_jack_add_zones(struct snd_soc_jack *jack, int count,
+ struct snd_soc_jack_zone *zones)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ INIT_LIST_HEAD(&zones[i].list);
+ list_add(&(zones[i].list), &jack->jack_zones);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_add_zones);
+
+/**
+ * snd_soc_jack_get_type - Based on the mic bias value, this function returns
+ * the type of jack from the zones delcared in the jack type
+ *
+ * @micbias_voltage: mic bias voltage at adc channel when jack is plugged in
+ *
+ * Based on the mic bias value passed, this function helps identify
+ * the type of jack from the already delcared jack zones
+ */
+int snd_soc_jack_get_type(struct snd_soc_jack *jack, int micbias_voltage)
+{
+ struct snd_soc_jack_zone *zone;
+
+ list_for_each_entry(zone, &jack->jack_zones, list) {
+ if (micbias_voltage >= zone->min_mv &&
+ micbias_voltage < zone->max_mv)
+ return zone->jack_type;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_get_type);
+
+/**
+ * snd_soc_jack_add_pins - Associate DAPM pins with an ASoC jack
+ *
+ * @jack: ASoC jack
+ * @count: Number of pins
+ * @pins: Array of pins
+ *
+ * After this function has been called the DAPM pins specified in the
+ * pins array will have their status updated to reflect the current
+ * state of the jack whenever the jack status is updated.
+ */
+int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count,
+ struct snd_soc_jack_pin *pins)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (!pins[i].pin) {
+ printk(KERN_ERR "No name for pin %d\n", i);
+ return -EINVAL;
+ }
+ if (!pins[i].mask) {
+ printk(KERN_ERR "No mask for pin %d (%s)\n", i,
+ pins[i].pin);
+ return -EINVAL;
+ }
+
+ INIT_LIST_HEAD(&pins[i].list);
+ list_add(&(pins[i].list), &jack->pins);
+ }
+
+ /* Update to reflect the last reported status; canned jack
+ * implementations are likely to set their state before the
+ * card has an opportunity to associate pins.
+ */
+ snd_soc_jack_report(jack, 0, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_add_pins);
+
+/**
+ * snd_soc_jack_notifier_register - Register a notifier for jack status
+ *
+ * @jack: ASoC jack
+ * @nb: Notifier block to register
+ *
+ * Register for notification of the current status of the jack. Note
+ * that it is not possible to report additional jack events in the
+ * callback from the notifier, this is intended to support
+ * applications such as enabling electrical detection only when a
+ * mechanical detection event has occurred.
+ */
+void snd_soc_jack_notifier_register(struct snd_soc_jack *jack,
+ struct notifier_block *nb)
+{
+ blocking_notifier_chain_register(&jack->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_register);
+
+/**
+ * snd_soc_jack_notifier_unregister - Unregister a notifier for jack status
+ *
+ * @jack: ASoC jack
+ * @nb: Notifier block to unregister
+ *
+ * Stop notifying for status changes.
+ */
+void snd_soc_jack_notifier_unregister(struct snd_soc_jack *jack,
+ struct notifier_block *nb)
+{
+ blocking_notifier_chain_unregister(&jack->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_unregister);
+
+#ifdef CONFIG_GPIOLIB
+/* gpio detect */
+static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio)
+{
+ struct snd_soc_jack *jack = gpio->jack;
+ int enable;
+ int report;
+
+ enable = gpio_get_value_cansleep(gpio->gpio);
+ if (gpio->invert)
+ enable = !enable;
+
+ if (enable)
+ report = gpio->report;
+ else
+ report = 0;
+
+ if (gpio->jack_status_check)
+ report = gpio->jack_status_check();
+
+ snd_soc_jack_report(jack, report, gpio->report);
+}
+
+/* irq handler for gpio pin */
+static irqreturn_t gpio_handler(int irq, void *data)
+{
+ struct snd_soc_jack_gpio *gpio = data;
+ struct device *dev = gpio->jack->codec->card->dev;
+
+ trace_snd_soc_jack_irq(gpio->name);
+
+ if (device_may_wakeup(dev))
+ pm_wakeup_event(dev, gpio->debounce_time + 50);
+
+ schedule_delayed_work(&gpio->work,
+ msecs_to_jiffies(gpio->debounce_time));
+
+ return IRQ_HANDLED;
+}
+
+/* gpio work */
+static void gpio_work(struct work_struct *work)
+{
+ struct snd_soc_jack_gpio *gpio;
+
+ gpio = container_of(work, struct snd_soc_jack_gpio, work.work);
+ snd_soc_jack_gpio_detect(gpio);
+}
+
+/**
+ * snd_soc_jack_add_gpios - Associate GPIO pins with an ASoC jack
+ *
+ * @jack: ASoC jack
+ * @count: number of pins
+ * @gpios: array of gpio pins
+ *
+ * This function will request gpio, set data direction and request irq
+ * for each gpio in the array.
+ */
+int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,
+ struct snd_soc_jack_gpio *gpios)
+{
+ int i, ret;
+
+ for (i = 0; i < count; i++) {
+ if (!gpio_is_valid(gpios[i].gpio)) {
+ printk(KERN_ERR "Invalid gpio %d\n",
+ gpios[i].gpio);
+ ret = -EINVAL;
+ goto undo;
+ }
+ if (!gpios[i].name) {
+ printk(KERN_ERR "No name for gpio %d\n",
+ gpios[i].gpio);
+ ret = -EINVAL;
+ goto undo;
+ }
+
+ ret = gpio_request(gpios[i].gpio, gpios[i].name);
+ if (ret)
+ goto undo;
+
+ ret = gpio_direction_input(gpios[i].gpio);
+ if (ret)
+ goto err;
+
+ INIT_DELAYED_WORK(&gpios[i].work, gpio_work);
+ gpios[i].jack = jack;
+
+ ret = request_any_context_irq(gpio_to_irq(gpios[i].gpio),
+ gpio_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ gpios[i].name,
+ &gpios[i]);
+ if (ret < 0)
+ goto err;
+
+ if (gpios[i].wake) {
+ ret = irq_set_irq_wake(gpio_to_irq(gpios[i].gpio), 1);
+ if (ret != 0)
+ printk(KERN_ERR
+ "Failed to mark GPIO %d as wake source: %d\n",
+ gpios[i].gpio, ret);
+ }
+
+#ifdef CONFIG_GPIO_SYSFS
+ /* Expose GPIO value over sysfs for diagnostic purposes */
+ gpio_export(gpios[i].gpio, false);
+#endif
+
+ /* Update initial jack status */
+ snd_soc_jack_gpio_detect(&gpios[i]);
+ }
+
+ return 0;
+
+err:
+ gpio_free(gpios[i].gpio);
+undo:
+ snd_soc_jack_free_gpios(jack, i, gpios);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_add_gpios);
+
+/**
+ * snd_soc_jack_free_gpios - Release GPIO pins' resources of an ASoC jack
+ *
+ * @jack: ASoC jack
+ * @count: number of pins
+ * @gpios: array of gpio pins
+ *
+ * Release gpio and irq resources for gpio pins associated with an ASoC jack.
+ */
+void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count,
+ struct snd_soc_jack_gpio *gpios)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+#ifdef CONFIG_GPIO_SYSFS
+ gpio_unexport(gpios[i].gpio);
+#endif
+ free_irq(gpio_to_irq(gpios[i].gpio), &gpios[i]);
+ cancel_delayed_work_sync(&gpios[i].work);
+ gpio_free(gpios[i].gpio);
+ gpios[i].jack = NULL;
+ }
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_free_gpios);
+#endif /* CONFIG_GPIOLIB */
diff --git a/sound/soc/soc-utils.c b/sound/soc/soc-utils.c
new file mode 100644
index 00000000..cd987de3
--- /dev/null
+++ b/sound/soc/soc-utils.c
@@ -0,0 +1,139 @@
+/*
+ * soc-util.c -- ALSA SoC Audio Layer utility functions
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ * Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ *
+ * 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 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots)
+{
+ return sample_size * channels * tdm_slots;
+}
+EXPORT_SYMBOL_GPL(snd_soc_calc_frame_size);
+
+int snd_soc_params_to_frame_size(struct snd_pcm_hw_params *params)
+{
+ int sample_size;
+
+ sample_size = snd_pcm_format_width(params_format(params));
+ if (sample_size < 0)
+ return sample_size;
+
+ return snd_soc_calc_frame_size(sample_size, params_channels(params),
+ 1);
+}
+EXPORT_SYMBOL_GPL(snd_soc_params_to_frame_size);
+
+int snd_soc_calc_bclk(int fs, int sample_size, int channels, int tdm_slots)
+{
+ return fs * snd_soc_calc_frame_size(sample_size, channels, tdm_slots);
+}
+EXPORT_SYMBOL_GPL(snd_soc_calc_bclk);
+
+int snd_soc_params_to_bclk(struct snd_pcm_hw_params *params)
+{
+ int ret;
+
+ ret = snd_soc_params_to_frame_size(params);
+
+ if (ret > 0)
+ return ret * params_rate(params);
+ else
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_params_to_bclk);
+
+static const struct snd_pcm_hardware dummy_dma_hardware = {
+ .formats = 0xffffffff,
+ .channels_min = 1,
+ .channels_max = UINT_MAX,
+
+ /* Random values to keep userspace happy when checking constraints */
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .buffer_bytes_max = 128*1024,
+ .period_bytes_min = PAGE_SIZE,
+ .period_bytes_max = PAGE_SIZE*2,
+ .periods_min = 2,
+ .periods_max = 128,
+};
+
+static int dummy_dma_open(struct snd_pcm_substream *substream)
+{
+ snd_soc_set_runtime_hwparams(substream, &dummy_dma_hardware);
+
+ return 0;
+}
+
+static struct snd_pcm_ops dummy_dma_ops = {
+ .open = dummy_dma_open,
+ .ioctl = snd_pcm_lib_ioctl,
+};
+
+static struct snd_soc_platform_driver dummy_platform = {
+ .ops = &dummy_dma_ops,
+};
+
+static __devinit int snd_soc_dummy_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &dummy_platform);
+}
+
+static __devexit int snd_soc_dummy_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver soc_dummy_driver = {
+ .driver = {
+ .name = "snd-soc-dummy",
+ .owner = THIS_MODULE,
+ },
+ .probe = snd_soc_dummy_probe,
+ .remove = __devexit_p(snd_soc_dummy_remove),
+};
+
+static struct platform_device *soc_dummy_dev;
+
+int __init snd_soc_util_init(void)
+{
+ int ret;
+
+ soc_dummy_dev = platform_device_alloc("snd-soc-dummy", -1);
+ if (!soc_dummy_dev)
+ return -ENOMEM;
+
+ ret = platform_device_add(soc_dummy_dev);
+ if (ret != 0) {
+ platform_device_put(soc_dummy_dev);
+ return ret;
+ }
+
+ ret = platform_driver_register(&soc_dummy_driver);
+ if (ret != 0)
+ platform_device_unregister(soc_dummy_dev);
+
+ return ret;
+}
+
+void __exit snd_soc_util_exit(void)
+{
+ platform_device_unregister(soc_dummy_dev);
+ platform_driver_unregister(&soc_dummy_driver);
+}
diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig
new file mode 100644
index 00000000..035d39a4
--- /dev/null
+++ b/sound/soc/tegra/Kconfig
@@ -0,0 +1,40 @@
+config SND_SOC_TEGRA
+ tristate "SoC Audio for the Tegra System-on-Chip"
+ depends on ARCH_TEGRA && TEGRA_SYSTEM_DMA
+ help
+ Say Y or M here if you want support for SoC audio on Tegra.
+
+config SND_SOC_TEGRA_I2S
+ tristate
+ depends on SND_SOC_TEGRA
+ help
+ Say Y or M if you want to add support for codecs attached to the
+ Tegra I2S interface. You will also need to select the individual
+ machine drivers to support below.
+
+config MACH_HAS_SND_SOC_TEGRA_WM8903
+ bool
+ help
+ Machines that use the SND_SOC_TEGRA_WM8903 driver should select
+ this config option, in order to allow the user to enable
+ SND_SOC_TEGRA_WM8903.
+
+config SND_SOC_TEGRA_WM8903
+ tristate "SoC Audio support for Tegra boards using a WM8903 codec"
+ depends on SND_SOC_TEGRA && I2C
+ depends on MACH_HAS_SND_SOC_TEGRA_WM8903
+ select SND_SOC_TEGRA_I2S
+ select SND_SOC_WM8903
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the WM8093 codec. Currently, the supported boards are
+ Harmony, Ventana, Seaboard, Kaen, and Aebl.
+
+config SND_SOC_TEGRA_TRIMSLICE
+ tristate "SoC Audio support for TrimSlice board"
+ depends on SND_SOC_TEGRA && MACH_TRIMSLICE && I2C
+ select SND_SOC_TEGRA_I2S
+ select SND_SOC_TLV320AIC23
+ help
+ Say Y or M here if you want to add support for SoC audio on the
+ TrimSlice platform.
diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile
new file mode 100644
index 00000000..fa6574d9
--- /dev/null
+++ b/sound/soc/tegra/Makefile
@@ -0,0 +1,17 @@
+# Tegra platform Support
+snd-soc-tegra-das-objs := tegra_das.o
+snd-soc-tegra-pcm-objs := tegra_pcm.o
+snd-soc-tegra-i2s-objs := tegra_i2s.o
+snd-soc-tegra-utils-objs += tegra_asoc_utils.o
+
+obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o
+obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-das.o
+obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o
+obj-$(CONFIG_SND_SOC_TEGRA_I2S) += snd-soc-tegra-i2s.o
+
+# Tegra machine Support
+snd-soc-tegra-wm8903-objs := tegra_wm8903.o
+snd-soc-tegra-trimslice-objs := trimslice.o
+
+obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o
+obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o
diff --git a/sound/soc/tegra/tegra_asoc_utils.c b/sound/soc/tegra/tegra_asoc_utils.c
new file mode 100644
index 00000000..dfa85cbb
--- /dev/null
+++ b/sound/soc/tegra/tegra_asoc_utils.c
@@ -0,0 +1,156 @@
+/*
+ * tegra_asoc_utils.c - Harmony machine ASoC driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+
+#include "tegra_asoc_utils.h"
+
+int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate,
+ int mclk)
+{
+ int new_baseclock;
+ bool clk_change;
+ int err;
+
+ switch (srate) {
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ new_baseclock = 56448000;
+ break;
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 64000:
+ case 96000:
+ new_baseclock = 73728000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ clk_change = ((new_baseclock != data->set_baseclock) ||
+ (mclk != data->set_mclk));
+ if (!clk_change)
+ return 0;
+
+ data->set_baseclock = 0;
+ data->set_mclk = 0;
+
+ clk_disable(data->clk_cdev1);
+ clk_disable(data->clk_pll_a_out0);
+ clk_disable(data->clk_pll_a);
+
+ err = clk_set_rate(data->clk_pll_a, new_baseclock);
+ if (err) {
+ dev_err(data->dev, "Can't set pll_a rate: %d\n", err);
+ return err;
+ }
+
+ err = clk_set_rate(data->clk_pll_a_out0, mclk);
+ if (err) {
+ dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err);
+ return err;
+ }
+
+ /* Don't set cdev1 rate; its locked to pll_a_out0 */
+
+ err = clk_enable(data->clk_pll_a);
+ if (err) {
+ dev_err(data->dev, "Can't enable pll_a: %d\n", err);
+ return err;
+ }
+
+ err = clk_enable(data->clk_pll_a_out0);
+ if (err) {
+ dev_err(data->dev, "Can't enable pll_a_out0: %d\n", err);
+ return err;
+ }
+
+ err = clk_enable(data->clk_cdev1);
+ if (err) {
+ dev_err(data->dev, "Can't enable cdev1: %d\n", err);
+ return err;
+ }
+
+ data->set_baseclock = new_baseclock;
+ data->set_mclk = mclk;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_rate);
+
+int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data,
+ struct device *dev)
+{
+ int ret;
+
+ data->dev = dev;
+
+ data->clk_pll_a = clk_get_sys(NULL, "pll_a");
+ if (IS_ERR(data->clk_pll_a)) {
+ dev_err(data->dev, "Can't retrieve clk pll_a\n");
+ ret = PTR_ERR(data->clk_pll_a);
+ goto err;
+ }
+
+ data->clk_pll_a_out0 = clk_get_sys(NULL, "pll_a_out0");
+ if (IS_ERR(data->clk_pll_a_out0)) {
+ dev_err(data->dev, "Can't retrieve clk pll_a_out0\n");
+ ret = PTR_ERR(data->clk_pll_a_out0);
+ goto err_put_pll_a;
+ }
+
+ data->clk_cdev1 = clk_get_sys(NULL, "cdev1");
+ if (IS_ERR(data->clk_cdev1)) {
+ dev_err(data->dev, "Can't retrieve clk cdev1\n");
+ ret = PTR_ERR(data->clk_cdev1);
+ goto err_put_pll_a_out0;
+ }
+
+ return 0;
+
+err_put_pll_a_out0:
+ clk_put(data->clk_pll_a_out0);
+err_put_pll_a:
+ clk_put(data->clk_pll_a);
+err:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(tegra_asoc_utils_init);
+
+void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data)
+{
+ clk_put(data->clk_cdev1);
+ clk_put(data->clk_pll_a_out0);
+ clk_put(data->clk_pll_a);
+}
+EXPORT_SYMBOL_GPL(tegra_asoc_utils_fini);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra ASoC utility code");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/tegra/tegra_asoc_utils.h b/sound/soc/tegra/tegra_asoc_utils.h
new file mode 100644
index 00000000..4818195d
--- /dev/null
+++ b/sound/soc/tegra/tegra_asoc_utils.h
@@ -0,0 +1,45 @@
+/*
+ * tegra_asoc_utils.h - Definitions for Tegra DAS driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TEGRA_ASOC_UTILS_H__
+#define __TEGRA_ASOC_UTILS_H_
+
+struct clk;
+struct device;
+
+struct tegra_asoc_utils_data {
+ struct device *dev;
+ struct clk *clk_pll_a;
+ struct clk *clk_pll_a_out0;
+ struct clk *clk_cdev1;
+ int set_baseclock;
+ int set_mclk;
+};
+
+int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate,
+ int mclk);
+int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data,
+ struct device *dev);
+void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data);
+
+#endif
+
diff --git a/sound/soc/tegra/tegra_das.c b/sound/soc/tegra/tegra_das.c
new file mode 100644
index 00000000..9f24ef73
--- /dev/null
+++ b/sound/soc/tegra/tegra_das.c
@@ -0,0 +1,265 @@
+/*
+ * tegra_das.c - Tegra DAS driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <mach/iomap.h>
+#include <sound/soc.h>
+#include "tegra_das.h"
+
+#define DRV_NAME "tegra-das"
+
+static struct tegra_das *das;
+
+static inline void tegra_das_write(u32 reg, u32 val)
+{
+ __raw_writel(val, das->regs + reg);
+}
+
+static inline u32 tegra_das_read(u32 reg)
+{
+ return __raw_readl(das->regs + reg);
+}
+
+int tegra_das_connect_dap_to_dac(int dap, int dac)
+{
+ u32 addr;
+ u32 reg;
+
+ if (!das)
+ return -ENODEV;
+
+ addr = TEGRA_DAS_DAP_CTRL_SEL +
+ (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE);
+ reg = dac << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P;
+
+ tegra_das_write(addr, reg);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dac);
+
+int tegra_das_connect_dap_to_dap(int dap, int otherdap, int master,
+ int sdata1rx, int sdata2rx)
+{
+ u32 addr;
+ u32 reg;
+
+ if (!das)
+ return -ENODEV;
+
+ addr = TEGRA_DAS_DAP_CTRL_SEL +
+ (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE);
+ reg = otherdap << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P |
+ !!sdata2rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P |
+ !!sdata1rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P |
+ !!master << TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P;
+
+ tegra_das_write(addr, reg);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dap);
+
+int tegra_das_connect_dac_to_dap(int dac, int dap)
+{
+ u32 addr;
+ u32 reg;
+
+ if (!das)
+ return -ENODEV;
+
+ addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL +
+ (dac * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE);
+ reg = dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P |
+ dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P |
+ dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P;
+
+ tegra_das_write(addr, reg);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_das_connect_dac_to_dap);
+
+#ifdef CONFIG_DEBUG_FS
+static int tegra_das_show(struct seq_file *s, void *unused)
+{
+ int i;
+ u32 addr;
+ u32 reg;
+
+ for (i = 0; i < TEGRA_DAS_DAP_CTRL_SEL_COUNT; i++) {
+ addr = TEGRA_DAS_DAP_CTRL_SEL +
+ (i * TEGRA_DAS_DAP_CTRL_SEL_STRIDE);
+ reg = tegra_das_read(addr);
+ seq_printf(s, "TEGRA_DAS_DAP_CTRL_SEL[%d] = %08x\n", i, reg);
+ }
+
+ for (i = 0; i < TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT; i++) {
+ addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL +
+ (i * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE);
+ reg = tegra_das_read(addr);
+ seq_printf(s, "TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL[%d] = %08x\n",
+ i, reg);
+ }
+
+ return 0;
+}
+
+static int tegra_das_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, tegra_das_show, inode->i_private);
+}
+
+static const struct file_operations tegra_das_debug_fops = {
+ .open = tegra_das_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void tegra_das_debug_add(struct tegra_das *das)
+{
+ das->debug = debugfs_create_file(DRV_NAME, S_IRUGO,
+ snd_soc_debugfs_root, das,
+ &tegra_das_debug_fops);
+}
+
+static void tegra_das_debug_remove(struct tegra_das *das)
+{
+ if (das->debug)
+ debugfs_remove(das->debug);
+}
+#else
+static inline void tegra_das_debug_add(struct tegra_das *das)
+{
+}
+
+static inline void tegra_das_debug_remove(struct tegra_das *das)
+{
+}
+#endif
+
+static int __devinit tegra_das_probe(struct platform_device *pdev)
+{
+ struct resource *res, *region;
+ int ret = 0;
+
+ if (das)
+ return -ENODEV;
+
+ das = kzalloc(sizeof(struct tegra_das), GFP_KERNEL);
+ if (!das) {
+ dev_err(&pdev->dev, "Can't allocate tegra_das\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+ das->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "No memory resource\n");
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ region = request_mem_region(res->start, resource_size(res),
+ pdev->name);
+ if (!region) {
+ dev_err(&pdev->dev, "Memory region already claimed\n");
+ ret = -EBUSY;
+ goto err_free;
+ }
+
+ das->regs = ioremap(res->start, resource_size(res));
+ if (!das->regs) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err_release;
+ }
+
+ tegra_das_debug_add(das);
+
+ platform_set_drvdata(pdev, das);
+
+ return 0;
+
+err_release:
+ release_mem_region(res->start, resource_size(res));
+err_free:
+ kfree(das);
+ das = 0;
+exit:
+ return ret;
+}
+
+static int __devexit tegra_das_remove(struct platform_device *pdev)
+{
+ struct resource *res;
+
+ if (!das)
+ return -ENODEV;
+
+ platform_set_drvdata(pdev, NULL);
+
+ tegra_das_debug_remove(das);
+
+ iounmap(das->regs);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(res->start, resource_size(res));
+
+ kfree(das);
+ das = 0;
+
+ return 0;
+}
+
+static struct platform_driver tegra_das_driver = {
+ .probe = tegra_das_probe,
+ .remove = __devexit_p(tegra_das_remove),
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static int __init tegra_das_modinit(void)
+{
+ return platform_driver_register(&tegra_das_driver);
+}
+module_init(tegra_das_modinit);
+
+static void __exit tegra_das_modexit(void)
+{
+ platform_driver_unregister(&tegra_das_driver);
+}
+module_exit(tegra_das_modexit);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra DAS driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/tegra/tegra_das.h b/sound/soc/tegra/tegra_das.h
new file mode 100644
index 00000000..2c96c7b3
--- /dev/null
+++ b/sound/soc/tegra/tegra_das.h
@@ -0,0 +1,135 @@
+/*
+ * tegra_das.h - Definitions for Tegra DAS driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TEGRA_DAS_H__
+#define __TEGRA_DAS_H__
+
+/* Register TEGRA_DAS_DAP_CTRL_SEL */
+#define TEGRA_DAS_DAP_CTRL_SEL 0x00
+#define TEGRA_DAS_DAP_CTRL_SEL_COUNT 5
+#define TEGRA_DAS_DAP_CTRL_SEL_STRIDE 4
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5
+
+/* Values for field TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */
+#define TEGRA_DAS_DAP_SEL_DAC1 0
+#define TEGRA_DAS_DAP_SEL_DAC2 1
+#define TEGRA_DAS_DAP_SEL_DAC3 2
+#define TEGRA_DAS_DAP_SEL_DAP1 16
+#define TEGRA_DAS_DAP_SEL_DAP2 17
+#define TEGRA_DAS_DAP_SEL_DAP3 18
+#define TEGRA_DAS_DAP_SEL_DAP4 19
+#define TEGRA_DAS_DAP_SEL_DAP5 20
+
+/* Register TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL */
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL 0x40
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4
+
+/*
+ * Values for:
+ * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL
+ * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL
+ * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL
+ */
+#define TEGRA_DAS_DAC_SEL_DAP1 0
+#define TEGRA_DAS_DAC_SEL_DAP2 1
+#define TEGRA_DAS_DAC_SEL_DAP3 2
+#define TEGRA_DAS_DAC_SEL_DAP4 3
+#define TEGRA_DAS_DAC_SEL_DAP5 4
+
+/*
+ * Names/IDs of the DACs/DAPs.
+ */
+
+#define TEGRA_DAS_DAP_ID_1 0
+#define TEGRA_DAS_DAP_ID_2 1
+#define TEGRA_DAS_DAP_ID_3 2
+#define TEGRA_DAS_DAP_ID_4 3
+#define TEGRA_DAS_DAP_ID_5 4
+
+#define TEGRA_DAS_DAC_ID_1 0
+#define TEGRA_DAS_DAC_ID_2 1
+#define TEGRA_DAS_DAC_ID_3 2
+
+struct tegra_das {
+ struct device *dev;
+ void __iomem *regs;
+ struct dentry *debug;
+};
+
+/*
+ * Terminology:
+ * DAS: Digital audio switch (HW module controlled by this driver)
+ * DAP: Digital audio port (port/pins on Tegra device)
+ * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere)
+ *
+ * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific
+ * DAC, or another DAP. When DAPs are connected, one must be the master and
+ * one the slave. Each DAC allows selection of a specific DAP for input, to
+ * cater for the case where N DAPs are connected to 1 DAC for broadcast
+ * output.
+ *
+ * This driver is dumb; no attempt is made to ensure that a valid routing
+ * configuration is programmed.
+ */
+
+/*
+ * Connect a DAP to to a DAC
+ * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_*
+ * dac_sel: DAC to connect to: TEGRA_DAS_DAP_SEL_DAC*
+ */
+extern int tegra_das_connect_dap_to_dac(int dap_id, int dac_sel);
+
+/*
+ * Connect a DAP to to another DAP
+ * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_*
+ * other_dap_sel: DAP to connect to: TEGRA_DAS_DAP_SEL_DAP*
+ * master: Is this DAP the master (1) or slave (0)
+ * sdata1rx: Is this DAP's SDATA1 pin RX (1) or TX (0)
+ * sdata2rx: Is this DAP's SDATA2 pin RX (1) or TX (0)
+ */
+extern int tegra_das_connect_dap_to_dap(int dap_id, int other_dap_sel,
+ int master, int sdata1rx,
+ int sdata2rx);
+
+/*
+ * Connect a DAC's input to a DAP
+ * (DAC outputs are selected by the DAP)
+ * dac_id: DAC ID to connect: TEGRA_DAS_DAC_ID_*
+ * dap_sel: DAP to receive input from: TEGRA_DAS_DAC_SEL_DAP*
+ */
+extern int tegra_das_connect_dac_to_dap(int dac_id, int dap_sel);
+
+#endif
diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c
new file mode 100644
index 00000000..95f03c10
--- /dev/null
+++ b/sound/soc/tegra/tegra_i2s.c
@@ -0,0 +1,509 @@
+/*
+ * tegra_i2s.c - Tegra I2S driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <mach/iomap.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "tegra_das.h"
+#include "tegra_i2s.h"
+
+#define DRV_NAME "tegra-i2s"
+
+static inline void tegra_i2s_write(struct tegra_i2s *i2s, u32 reg, u32 val)
+{
+ __raw_writel(val, i2s->regs + reg);
+}
+
+static inline u32 tegra_i2s_read(struct tegra_i2s *i2s, u32 reg)
+{
+ return __raw_readl(i2s->regs + reg);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int tegra_i2s_show(struct seq_file *s, void *unused)
+{
+#define REG(r) { r, #r }
+ static const struct {
+ int offset;
+ const char *name;
+ } regs[] = {
+ REG(TEGRA_I2S_CTRL),
+ REG(TEGRA_I2S_STATUS),
+ REG(TEGRA_I2S_TIMING),
+ REG(TEGRA_I2S_FIFO_SCR),
+ REG(TEGRA_I2S_PCM_CTRL),
+ REG(TEGRA_I2S_NW_CTRL),
+ REG(TEGRA_I2S_TDM_CTRL),
+ REG(TEGRA_I2S_TDM_TX_RX_CTRL),
+ };
+#undef REG
+
+ struct tegra_i2s *i2s = s->private;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ u32 val = tegra_i2s_read(i2s, regs[i].offset);
+ seq_printf(s, "%s = %08x\n", regs[i].name, val);
+ }
+
+ return 0;
+}
+
+static int tegra_i2s_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, tegra_i2s_show, inode->i_private);
+}
+
+static const struct file_operations tegra_i2s_debug_fops = {
+ .open = tegra_i2s_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void tegra_i2s_debug_add(struct tegra_i2s *i2s, int id)
+{
+ char name[] = DRV_NAME ".0";
+
+ snprintf(name, sizeof(name), DRV_NAME".%1d", id);
+ i2s->debug = debugfs_create_file(name, S_IRUGO, snd_soc_debugfs_root,
+ i2s, &tegra_i2s_debug_fops);
+}
+
+static void tegra_i2s_debug_remove(struct tegra_i2s *i2s)
+{
+ if (i2s->debug)
+ debugfs_remove(i2s->debug);
+}
+#else
+static inline void tegra_i2s_debug_add(struct tegra_i2s *i2s, int id)
+{
+}
+
+static inline void tegra_i2s_debug_remove(struct tegra_i2s *i2s)
+{
+}
+#endif
+
+static int tegra_i2s_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_MASTER_ENABLE;
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_MASTER_ENABLE;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ i2s->reg_ctrl &= ~(TEGRA_I2S_CTRL_BIT_FORMAT_MASK |
+ TEGRA_I2S_CTRL_LRCK_MASK);
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP;
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP;
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_R_LOW;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_I2S;
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_RJM;
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_LJM;
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tegra_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct device *dev = substream->pcm->card->dev;
+ struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ u32 reg;
+ int ret, sample_size, srate, i2sclock, bitcnt;
+
+ i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_BIT_SIZE_MASK;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_16;
+ sample_size = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_24;
+ sample_size = 24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_32;
+ sample_size = 32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ srate = params_rate(params);
+
+ /* Final "* 2" required by Tegra hardware */
+ i2sclock = srate * params_channels(params) * sample_size * 2;
+
+ ret = clk_set_rate(i2s->clk_i2s, i2sclock);
+ if (ret) {
+ dev_err(dev, "Can't set I2S clock rate: %d\n", ret);
+ return ret;
+ }
+
+ bitcnt = (i2sclock / (2 * srate)) - 1;
+ if (bitcnt < 0 || bitcnt > TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US)
+ return -EINVAL;
+ reg = bitcnt << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT;
+
+ if (i2sclock % (2 * srate))
+ reg |= TEGRA_I2S_TIMING_NON_SYM_ENABLE;
+
+ if (!i2s->clk_refs)
+ clk_enable(i2s->clk_i2s);
+
+ tegra_i2s_write(i2s, TEGRA_I2S_TIMING, reg);
+
+ tegra_i2s_write(i2s, TEGRA_I2S_FIFO_SCR,
+ TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS |
+ TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS);
+
+ if (!i2s->clk_refs)
+ clk_disable(i2s->clk_i2s);
+
+ return 0;
+}
+
+static void tegra_i2s_start_playback(struct tegra_i2s *i2s)
+{
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO1_ENABLE;
+ tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl);
+}
+
+static void tegra_i2s_stop_playback(struct tegra_i2s *i2s)
+{
+ i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO1_ENABLE;
+ tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl);
+}
+
+static void tegra_i2s_start_capture(struct tegra_i2s *i2s)
+{
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO2_ENABLE;
+ tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl);
+}
+
+static void tegra_i2s_stop_capture(struct tegra_i2s *i2s)
+{
+ i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO2_ENABLE;
+ tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl);
+}
+
+static int tegra_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (!i2s->clk_refs)
+ clk_enable(i2s->clk_i2s);
+ i2s->clk_refs++;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tegra_i2s_start_playback(i2s);
+ else
+ tegra_i2s_start_capture(i2s);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tegra_i2s_stop_playback(i2s);
+ else
+ tegra_i2s_stop_capture(i2s);
+ i2s->clk_refs--;
+ if (!i2s->clk_refs)
+ clk_disable(i2s->clk_i2s);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tegra_i2s_probe(struct snd_soc_dai *dai)
+{
+ struct tegra_i2s * i2s = snd_soc_dai_get_drvdata(dai);
+
+ dai->capture_dma_data = &i2s->capture_dma_data;
+ dai->playback_dma_data = &i2s->playback_dma_data;
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops tegra_i2s_dai_ops = {
+ .set_fmt = tegra_i2s_set_fmt,
+ .hw_params = tegra_i2s_hw_params,
+ .trigger = tegra_i2s_trigger,
+};
+
+struct snd_soc_dai_driver tegra_i2s_dai[] = {
+ {
+ .name = DRV_NAME ".0",
+ .probe = tegra_i2s_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra_i2s_dai_ops,
+ .symmetric_rates = 1,
+ },
+ {
+ .name = DRV_NAME ".1",
+ .probe = tegra_i2s_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra_i2s_dai_ops,
+ .symmetric_rates = 1,
+ },
+};
+
+static __devinit int tegra_i2s_platform_probe(struct platform_device *pdev)
+{
+ struct tegra_i2s * i2s;
+ char clk_name[12]; /* tegra-i2s.0 */
+ struct resource *mem, *memregion, *dmareq;
+ int ret;
+
+ if ((pdev->id < 0) ||
+ (pdev->id >= ARRAY_SIZE(tegra_i2s_dai))) {
+ dev_err(&pdev->dev, "ID %d out of range\n", pdev->id);
+ return -EINVAL;
+ }
+
+ /*
+ * FIXME: Until a codec driver exists for the tegra DAS, hard-code a
+ * 1:1 mapping between audio controllers and audio ports.
+ */
+ ret = tegra_das_connect_dap_to_dac(TEGRA_DAS_DAP_ID_1 + pdev->id,
+ TEGRA_DAS_DAP_SEL_DAC1 + pdev->id);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't set up DAP connection\n");
+ return ret;
+ }
+ ret = tegra_das_connect_dac_to_dap(TEGRA_DAS_DAC_ID_1 + pdev->id,
+ TEGRA_DAS_DAC_SEL_DAP1 + pdev->id);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't set up DAC connection\n");
+ return ret;
+ }
+
+ i2s = kzalloc(sizeof(struct tegra_i2s), GFP_KERNEL);
+ if (!i2s) {
+ dev_err(&pdev->dev, "Can't allocate tegra_i2s\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+ dev_set_drvdata(&pdev->dev, i2s);
+
+ snprintf(clk_name, sizeof(clk_name), DRV_NAME ".%d", pdev->id);
+ i2s->clk_i2s = clk_get_sys(clk_name, NULL);
+ if (IS_ERR(i2s->clk_i2s)) {
+ dev_err(&pdev->dev, "Can't retrieve i2s clock\n");
+ ret = PTR_ERR(i2s->clk_i2s);
+ goto err_free;
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "No memory resource\n");
+ ret = -ENODEV;
+ goto err_clk_put;
+ }
+
+ dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!dmareq) {
+ dev_err(&pdev->dev, "No DMA resource\n");
+ ret = -ENODEV;
+ goto err_clk_put;
+ }
+
+ memregion = request_mem_region(mem->start, resource_size(mem),
+ DRV_NAME);
+ if (!memregion) {
+ dev_err(&pdev->dev, "Memory region already claimed\n");
+ ret = -EBUSY;
+ goto err_clk_put;
+ }
+
+ i2s->regs = ioremap(mem->start, resource_size(mem));
+ if (!i2s->regs) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err_release;
+ }
+
+ i2s->capture_dma_data.addr = mem->start + TEGRA_I2S_FIFO2;
+ i2s->capture_dma_data.wrap = 4;
+ i2s->capture_dma_data.width = 32;
+ i2s->capture_dma_data.req_sel = dmareq->start;
+
+ i2s->playback_dma_data.addr = mem->start + TEGRA_I2S_FIFO1;
+ i2s->playback_dma_data.wrap = 4;
+ i2s->playback_dma_data.width = 32;
+ i2s->playback_dma_data.req_sel = dmareq->start;
+
+ i2s->reg_ctrl = TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED;
+
+ ret = snd_soc_register_dai(&pdev->dev, &tegra_i2s_dai[pdev->id]);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
+ ret = -ENOMEM;
+ goto err_unmap;
+ }
+
+ tegra_i2s_debug_add(i2s, pdev->id);
+
+ return 0;
+
+err_unmap:
+ iounmap(i2s->regs);
+err_release:
+ release_mem_region(mem->start, resource_size(mem));
+err_clk_put:
+ clk_put(i2s->clk_i2s);
+err_free:
+ kfree(i2s);
+exit:
+ return ret;
+}
+
+static int __devexit tegra_i2s_platform_remove(struct platform_device *pdev)
+{
+ struct tegra_i2s *i2s = dev_get_drvdata(&pdev->dev);
+ struct resource *res;
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ tegra_i2s_debug_remove(i2s);
+
+ iounmap(i2s->regs);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(res->start, resource_size(res));
+
+ clk_put(i2s->clk_i2s);
+
+ kfree(i2s);
+
+ return 0;
+}
+
+static struct platform_driver tegra_i2s_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = tegra_i2s_platform_probe,
+ .remove = __devexit_p(tegra_i2s_platform_remove),
+};
+
+static int __init snd_tegra_i2s_init(void)
+{
+ return platform_driver_register(&tegra_i2s_driver);
+}
+module_init(snd_tegra_i2s_init);
+
+static void __exit snd_tegra_i2s_exit(void)
+{
+ platform_driver_unregister(&tegra_i2s_driver);
+}
+module_exit(snd_tegra_i2s_exit);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra I2S ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/tegra/tegra_i2s.h b/sound/soc/tegra/tegra_i2s.h
new file mode 100644
index 00000000..2b38a096
--- /dev/null
+++ b/sound/soc/tegra/tegra_i2s.h
@@ -0,0 +1,165 @@
+/*
+ * tegra_i2s.h - Definitions for Tegra I2S driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TEGRA_I2S_H__
+#define __TEGRA_I2S_H__
+
+#include "tegra_pcm.h"
+
+/* Register offsets from TEGRA_I2S1_BASE and TEGRA_I2S2_BASE */
+
+#define TEGRA_I2S_CTRL 0x00
+#define TEGRA_I2S_STATUS 0x04
+#define TEGRA_I2S_TIMING 0x08
+#define TEGRA_I2S_FIFO_SCR 0x0c
+#define TEGRA_I2S_PCM_CTRL 0x10
+#define TEGRA_I2S_NW_CTRL 0x14
+#define TEGRA_I2S_TDM_CTRL 0x20
+#define TEGRA_I2S_TDM_TX_RX_CTRL 0x24
+#define TEGRA_I2S_FIFO1 0x40
+#define TEGRA_I2S_FIFO2 0x80
+
+/* Fields in TEGRA_I2S_CTRL */
+
+#define TEGRA_I2S_CTRL_FIFO2_TX_ENABLE (1 << 30)
+#define TEGRA_I2S_CTRL_FIFO1_ENABLE (1 << 29)
+#define TEGRA_I2S_CTRL_FIFO2_ENABLE (1 << 28)
+#define TEGRA_I2S_CTRL_FIFO1_RX_ENABLE (1 << 27)
+#define TEGRA_I2S_CTRL_FIFO_LPBK_ENABLE (1 << 26)
+#define TEGRA_I2S_CTRL_MASTER_ENABLE (1 << 25)
+
+#define TEGRA_I2S_LRCK_LEFT_LOW 0
+#define TEGRA_I2S_LRCK_RIGHT_LOW 1
+
+#define TEGRA_I2S_CTRL_LRCK_SHIFT 24
+#define TEGRA_I2S_CTRL_LRCK_MASK (1 << TEGRA_I2S_CTRL_LRCK_SHIFT)
+#define TEGRA_I2S_CTRL_LRCK_L_LOW (TEGRA_I2S_LRCK_LEFT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT)
+#define TEGRA_I2S_CTRL_LRCK_R_LOW (TEGRA_I2S_LRCK_RIGHT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT)
+
+#define TEGRA_I2S_BIT_FORMAT_I2S 0
+#define TEGRA_I2S_BIT_FORMAT_RJM 1
+#define TEGRA_I2S_BIT_FORMAT_LJM 2
+#define TEGRA_I2S_BIT_FORMAT_DSP 3
+
+#define TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT 10
+#define TEGRA_I2S_CTRL_BIT_FORMAT_MASK (3 << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_FORMAT_I2S (TEGRA_I2S_BIT_FORMAT_I2S << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_FORMAT_RJM (TEGRA_I2S_BIT_FORMAT_RJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_FORMAT_LJM (TEGRA_I2S_BIT_FORMAT_LJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_FORMAT_DSP (TEGRA_I2S_BIT_FORMAT_DSP << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT)
+
+#define TEGRA_I2S_BIT_SIZE_16 0
+#define TEGRA_I2S_BIT_SIZE_20 1
+#define TEGRA_I2S_BIT_SIZE_24 2
+#define TEGRA_I2S_BIT_SIZE_32 3
+
+#define TEGRA_I2S_CTRL_BIT_SIZE_SHIFT 8
+#define TEGRA_I2S_CTRL_BIT_SIZE_MASK (3 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_SIZE_16 (TEGRA_I2S_BIT_SIZE_16 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_SIZE_20 (TEGRA_I2S_BIT_SIZE_20 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_SIZE_24 (TEGRA_I2S_BIT_SIZE_24 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_SIZE_32 (TEGRA_I2S_BIT_SIZE_32 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT)
+
+#define TEGRA_I2S_FIFO_16_LSB 0
+#define TEGRA_I2S_FIFO_20_LSB 1
+#define TEGRA_I2S_FIFO_24_LSB 2
+#define TEGRA_I2S_FIFO_32 3
+#define TEGRA_I2S_FIFO_PACKED 7
+
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT 4
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_MASK (7 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_16_LSB (TEGRA_I2S_FIFO_16_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_20_LSB (TEGRA_I2S_FIFO_20_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_24_LSB (TEGRA_I2S_FIFO_24_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_32 (TEGRA_I2S_FIFO_32 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED (TEGRA_I2S_FIFO_PACKED << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+
+#define TEGRA_I2S_CTRL_IE_FIFO1_ERR (1 << 3)
+#define TEGRA_I2S_CTRL_IE_FIFO2_ERR (1 << 2)
+#define TEGRA_I2S_CTRL_QE_FIFO1 (1 << 1)
+#define TEGRA_I2S_CTRL_QE_FIFO2 (1 << 0)
+
+/* Fields in TEGRA_I2S_STATUS */
+
+#define TEGRA_I2S_STATUS_FIFO1_RDY (1 << 31)
+#define TEGRA_I2S_STATUS_FIFO2_RDY (1 << 30)
+#define TEGRA_I2S_STATUS_FIFO1_BSY (1 << 29)
+#define TEGRA_I2S_STATUS_FIFO2_BSY (1 << 28)
+#define TEGRA_I2S_STATUS_FIFO1_ERR (1 << 3)
+#define TEGRA_I2S_STATUS_FIFO2_ERR (1 << 2)
+#define TEGRA_I2S_STATUS_QS_FIFO1 (1 << 1)
+#define TEGRA_I2S_STATUS_QS_FIFO2 (1 << 0)
+
+/* Fields in TEGRA_I2S_TIMING */
+
+#define TEGRA_I2S_TIMING_NON_SYM_ENABLE (1 << 12)
+#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0
+#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7fff
+#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT)
+
+/* Fields in TEGRA_I2S_FIFO_SCR */
+
+#define TEGRA_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT 24
+#define TEGRA_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT 16
+#define TEGRA_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK 0x3f
+
+#define TEGRA_I2S_FIFO_SCR_FIFO2_CLR (1 << 12)
+#define TEGRA_I2S_FIFO_SCR_FIFO1_CLR (1 << 8)
+
+#define TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT 0
+#define TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS 1
+#define TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS 2
+#define TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS 3
+
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT 4
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT 0
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+
+struct tegra_i2s {
+ struct clk *clk_i2s;
+ int clk_refs;
+ struct tegra_pcm_dma_params capture_dma_data;
+ struct tegra_pcm_dma_params playback_dma_data;
+ void __iomem *regs;
+ struct dentry *debug;
+ u32 reg_ctrl;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c
new file mode 100644
index 00000000..62017105
--- /dev/null
+++ b/sound/soc/tegra/tegra_pcm.c
@@ -0,0 +1,409 @@
+/*
+ * tegra_pcm.c - Tegra PCM driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ * Vijay Mali <vmali@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "tegra_pcm.h"
+
+#define DRV_NAME "tegra-pcm-audio"
+
+static const struct snd_pcm_hardware tegra_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 2,
+ .channels_max = 2,
+ .period_bytes_min = 1024,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = 2,
+ .periods_max = 8,
+ .buffer_bytes_max = PAGE_SIZE * 8,
+ .fifo_size = 4,
+};
+
+static void tegra_pcm_queue_dma(struct tegra_runtime_data *prtd)
+{
+ struct snd_pcm_substream *substream = prtd->substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ struct tegra_dma_req *dma_req;
+ unsigned long addr;
+
+ dma_req = &prtd->dma_req[prtd->dma_req_idx];
+ prtd->dma_req_idx = 1 - prtd->dma_req_idx;
+
+ addr = buf->addr + prtd->dma_pos;
+ prtd->dma_pos += dma_req->size;
+ if (prtd->dma_pos >= prtd->dma_pos_end)
+ prtd->dma_pos = 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_req->source_addr = addr;
+ else
+ dma_req->dest_addr = addr;
+
+ tegra_dma_enqueue_req(prtd->dma_chan, dma_req);
+}
+
+static void dma_complete_callback(struct tegra_dma_req *req)
+{
+ struct tegra_runtime_data *prtd = (struct tegra_runtime_data *)req->dev;
+ struct snd_pcm_substream *substream = prtd->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ spin_lock(&prtd->lock);
+
+ if (!prtd->running) {
+ spin_unlock(&prtd->lock);
+ return;
+ }
+
+ if (++prtd->period_index >= runtime->periods)
+ prtd->period_index = 0;
+
+ tegra_pcm_queue_dma(prtd);
+
+ spin_unlock(&prtd->lock);
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static void setup_dma_tx_request(struct tegra_dma_req *req,
+ struct tegra_pcm_dma_params * dmap)
+{
+ req->complete = dma_complete_callback;
+ req->to_memory = false;
+ req->dest_addr = dmap->addr;
+ req->dest_wrap = dmap->wrap;
+ req->source_bus_width = 32;
+ req->source_wrap = 0;
+ req->dest_bus_width = dmap->width;
+ req->req_sel = dmap->req_sel;
+}
+
+static void setup_dma_rx_request(struct tegra_dma_req *req,
+ struct tegra_pcm_dma_params * dmap)
+{
+ req->complete = dma_complete_callback;
+ req->to_memory = true;
+ req->source_addr = dmap->addr;
+ req->dest_wrap = 0;
+ req->source_bus_width = dmap->width;
+ req->source_wrap = dmap->wrap;
+ req->dest_bus_width = 32;
+ req->req_sel = dmap->req_sel;
+}
+
+static int tegra_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct tegra_pcm_dma_params * dmap;
+ int ret = 0;
+
+ prtd = kzalloc(sizeof(struct tegra_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ runtime->private_data = prtd;
+ prtd->substream = substream;
+
+ spin_lock_init(&prtd->lock);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ setup_dma_tx_request(&prtd->dma_req[0], dmap);
+ setup_dma_tx_request(&prtd->dma_req[1], dmap);
+ } else {
+ dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ setup_dma_rx_request(&prtd->dma_req[0], dmap);
+ setup_dma_rx_request(&prtd->dma_req[1], dmap);
+ }
+
+ prtd->dma_req[0].dev = prtd;
+ prtd->dma_req[1].dev = prtd;
+
+ prtd->dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT);
+ if (prtd->dma_chan == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* Set HW params now that initialization is complete */
+ snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware);
+
+ /* Ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto err;
+
+ return 0;
+
+err:
+ if (prtd->dma_chan) {
+ tegra_dma_free_channel(prtd->dma_chan);
+ }
+
+ kfree(prtd);
+
+ return ret;
+}
+
+static int tegra_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd = runtime->private_data;
+
+ tegra_dma_free_channel(prtd->dma_chan);
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static int tegra_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd = runtime->private_data;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ prtd->dma_req[0].size = params_period_bytes(params);
+ prtd->dma_req[1].size = prtd->dma_req[0].size;
+
+ return 0;
+}
+
+static int tegra_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ return 0;
+}
+
+static int tegra_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd = runtime->private_data;
+ unsigned long flags;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ prtd->dma_pos = 0;
+ prtd->dma_pos_end = frames_to_bytes(runtime, runtime->periods * runtime->period_size);
+ prtd->period_index = 0;
+ prtd->dma_req_idx = 0;
+ /* Fall-through */
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&prtd->lock, flags);
+ prtd->running = 1;
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ tegra_pcm_queue_dma(prtd);
+ tegra_pcm_queue_dma(prtd);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&prtd->lock, flags);
+ prtd->running = 0;
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[0]);
+ tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[1]);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t tegra_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd = runtime->private_data;
+
+ return prtd->period_index * runtime->period_size;
+}
+
+
+static int tegra_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops tegra_pcm_ops = {
+ .open = tegra_pcm_open,
+ .close = tegra_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = tegra_pcm_hw_params,
+ .hw_free = tegra_pcm_hw_free,
+ .trigger = tegra_pcm_trigger,
+ .pointer = tegra_pcm_pointer,
+ .mmap = tegra_pcm_mmap,
+};
+
+static int tegra_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = tegra_pcm_hardware.buffer_bytes_max;
+
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->bytes = size;
+
+ return 0;
+}
+
+static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ return;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ return;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+}
+
+static u64 tegra_dma_mask = DMA_BIT_MASK(32);
+
+static int tegra_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &tegra_dma_mask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->driver->playback.channels_min) {
+ ret = tegra_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto err;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = tegra_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto err_free_play;
+ }
+
+ return 0;
+
+err_free_play:
+ tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
+err:
+ return ret;
+}
+
+static void tegra_pcm_free(struct snd_pcm *pcm)
+{
+ tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE);
+ tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+struct snd_soc_platform_driver tegra_pcm_platform = {
+ .ops = &tegra_pcm_ops,
+ .pcm_new = tegra_pcm_new,
+ .pcm_free = tegra_pcm_free,
+};
+
+static int __devinit tegra_pcm_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &tegra_pcm_platform);
+}
+
+static int __devexit tegra_pcm_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver tegra_pcm_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = tegra_pcm_platform_probe,
+ .remove = __devexit_p(tegra_pcm_platform_remove),
+};
+
+static int __init snd_tegra_pcm_init(void)
+{
+ return platform_driver_register(&tegra_pcm_driver);
+}
+module_init(snd_tegra_pcm_init);
+
+static void __exit snd_tegra_pcm_exit(void)
+{
+ platform_driver_unregister(&tegra_pcm_driver);
+}
+module_exit(snd_tegra_pcm_exit);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra PCM ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h
new file mode 100644
index 00000000..dbb90339
--- /dev/null
+++ b/sound/soc/tegra/tegra_pcm.h
@@ -0,0 +1,55 @@
+/*
+ * tegra_pcm.h - Definitions for Tegra PCM driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TEGRA_PCM_H__
+#define __TEGRA_PCM_H__
+
+#include <mach/dma.h>
+
+struct tegra_pcm_dma_params {
+ unsigned long addr;
+ unsigned long wrap;
+ unsigned long width;
+ unsigned long req_sel;
+};
+
+struct tegra_runtime_data {
+ struct snd_pcm_substream *substream;
+ spinlock_t lock;
+ int running;
+ int dma_pos;
+ int dma_pos_end;
+ int period_index;
+ int dma_req_idx;
+ struct tegra_dma_req dma_req[2];
+ struct tegra_dma_channel *dma_chan;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c
new file mode 100644
index 00000000..77664784
--- /dev/null
+++ b/sound/soc/tegra/tegra_wm8903.c
@@ -0,0 +1,482 @@
+/*
+ * tegra_wm8903.c - Tegra machine ASoC driver for boards using WM8903 codec.
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010-2011 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd.
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <asm/mach-types.h>
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+
+#include <mach/tegra_wm8903_pdata.h>
+
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "../codecs/wm8903.h"
+
+#include "tegra_das.h"
+#include "tegra_i2s.h"
+#include "tegra_pcm.h"
+#include "tegra_asoc_utils.h"
+
+#define DRV_NAME "tegra-snd-wm8903"
+
+#define GPIO_SPKR_EN BIT(0)
+#define GPIO_HP_MUTE BIT(1)
+#define GPIO_INT_MIC_EN BIT(2)
+#define GPIO_EXT_MIC_EN BIT(3)
+#define GPIO_HP_DET BIT(4)
+
+struct tegra_wm8903 {
+ struct tegra_asoc_utils_data util_data;
+ struct tegra_wm8903_platform_data *pdata;
+ int gpio_requested;
+};
+
+static int tegra_wm8903_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_card *card = codec->card;
+ struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card);
+ int srate, mclk;
+ int err;
+
+ srate = params_rate(params);
+ switch (srate) {
+ case 64000:
+ case 88200:
+ case 96000:
+ mclk = 128 * srate;
+ break;
+ default:
+ mclk = 256 * srate;
+ break;
+ }
+ /* FIXME: Codec only requires >= 3MHz if OSR==0 */
+ while (mclk < 6000000)
+ mclk *= 2;
+
+ err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk);
+ if (err < 0) {
+ dev_err(card->dev, "Can't configure clocks\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai fmt not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err < 0) {
+ dev_err(card->dev, "cpu_dai fmt not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
+ SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai clock not set\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops tegra_wm8903_ops = {
+ .hw_params = tegra_wm8903_hw_params,
+};
+
+static struct snd_soc_jack tegra_wm8903_hp_jack;
+
+static struct snd_soc_jack_pin tegra_wm8903_hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static struct snd_soc_jack_gpio tegra_wm8903_hp_jack_gpio = {
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 150,
+ .invert = 1,
+};
+
+static struct snd_soc_jack tegra_wm8903_mic_jack;
+
+static struct snd_soc_jack_pin tegra_wm8903_mic_jack_pins[] = {
+ {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int tegra_wm8903_event_int_spk(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_wm8903_platform_data *pdata = machine->pdata;
+
+ if (!(machine->gpio_requested & GPIO_SPKR_EN))
+ return 0;
+
+ gpio_set_value_cansleep(pdata->gpio_spkr_en,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ return 0;
+}
+
+static int tegra_wm8903_event_hp(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_wm8903_platform_data *pdata = machine->pdata;
+
+ if (!(machine->gpio_requested & GPIO_HP_MUTE))
+ return 0;
+
+ gpio_set_value_cansleep(pdata->gpio_hp_mute,
+ !SND_SOC_DAPM_EVENT_ON(event));
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget tegra_wm8903_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Int Spk", tegra_wm8903_event_int_spk),
+ SND_SOC_DAPM_HP("Headphone Jack", tegra_wm8903_event_hp),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route harmony_audio_map[] = {
+ {"Headphone Jack", NULL, "HPOUTR"},
+ {"Headphone Jack", NULL, "HPOUTL"},
+ {"Int Spk", NULL, "ROP"},
+ {"Int Spk", NULL, "RON"},
+ {"Int Spk", NULL, "LOP"},
+ {"Int Spk", NULL, "LON"},
+ {"Mic Bias", NULL, "Mic Jack"},
+ {"IN1L", NULL, "Mic Bias"},
+};
+
+static const struct snd_soc_dapm_route seaboard_audio_map[] = {
+ {"Headphone Jack", NULL, "HPOUTR"},
+ {"Headphone Jack", NULL, "HPOUTL"},
+ {"Int Spk", NULL, "ROP"},
+ {"Int Spk", NULL, "RON"},
+ {"Int Spk", NULL, "LOP"},
+ {"Int Spk", NULL, "LON"},
+ {"Mic Bias", NULL, "Mic Jack"},
+ {"IN1R", NULL, "Mic Bias"},
+};
+
+static const struct snd_soc_dapm_route kaen_audio_map[] = {
+ {"Headphone Jack", NULL, "HPOUTR"},
+ {"Headphone Jack", NULL, "HPOUTL"},
+ {"Int Spk", NULL, "ROP"},
+ {"Int Spk", NULL, "RON"},
+ {"Int Spk", NULL, "LOP"},
+ {"Int Spk", NULL, "LON"},
+ {"Mic Bias", NULL, "Mic Jack"},
+ {"IN2R", NULL, "Mic Bias"},
+};
+
+static const struct snd_soc_dapm_route aebl_audio_map[] = {
+ {"Headphone Jack", NULL, "HPOUTR"},
+ {"Headphone Jack", NULL, "HPOUTL"},
+ {"Int Spk", NULL, "LINEOUTR"},
+ {"Int Spk", NULL, "LINEOUTL"},
+ {"Mic Bias", NULL, "Mic Jack"},
+ {"IN1R", NULL, "Mic Bias"},
+};
+
+static const struct snd_kcontrol_new tegra_wm8903_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Int Spk"),
+};
+
+static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ struct snd_soc_card *card = codec->card;
+ struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_wm8903_platform_data *pdata = machine->pdata;
+ int ret;
+
+ if (gpio_is_valid(pdata->gpio_spkr_en)) {
+ ret = gpio_request(pdata->gpio_spkr_en, "spkr_en");
+ if (ret) {
+ dev_err(card->dev, "cannot get spkr_en gpio\n");
+ return ret;
+ }
+ machine->gpio_requested |= GPIO_SPKR_EN;
+
+ gpio_direction_output(pdata->gpio_spkr_en, 0);
+ }
+
+ if (gpio_is_valid(pdata->gpio_hp_mute)) {
+ ret = gpio_request(pdata->gpio_hp_mute, "hp_mute");
+ if (ret) {
+ dev_err(card->dev, "cannot get hp_mute gpio\n");
+ return ret;
+ }
+ machine->gpio_requested |= GPIO_HP_MUTE;
+
+ gpio_direction_output(pdata->gpio_hp_mute, 0);
+ }
+
+ if (gpio_is_valid(pdata->gpio_int_mic_en)) {
+ ret = gpio_request(pdata->gpio_int_mic_en, "int_mic_en");
+ if (ret) {
+ dev_err(card->dev, "cannot get int_mic_en gpio\n");
+ return ret;
+ }
+ machine->gpio_requested |= GPIO_INT_MIC_EN;
+
+ /* Disable int mic; enable signal is active-high */
+ gpio_direction_output(pdata->gpio_int_mic_en, 0);
+ }
+
+ if (gpio_is_valid(pdata->gpio_ext_mic_en)) {
+ ret = gpio_request(pdata->gpio_ext_mic_en, "ext_mic_en");
+ if (ret) {
+ dev_err(card->dev, "cannot get ext_mic_en gpio\n");
+ return ret;
+ }
+ machine->gpio_requested |= GPIO_EXT_MIC_EN;
+
+ /* Enable ext mic; enable signal is active-low */
+ gpio_direction_output(pdata->gpio_ext_mic_en, 0);
+ }
+
+ if (gpio_is_valid(pdata->gpio_hp_det)) {
+ tegra_wm8903_hp_jack_gpio.gpio = pdata->gpio_hp_det;
+ snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE,
+ &tegra_wm8903_hp_jack);
+ snd_soc_jack_add_pins(&tegra_wm8903_hp_jack,
+ ARRAY_SIZE(tegra_wm8903_hp_jack_pins),
+ tegra_wm8903_hp_jack_pins);
+ snd_soc_jack_add_gpios(&tegra_wm8903_hp_jack,
+ 1,
+ &tegra_wm8903_hp_jack_gpio);
+ machine->gpio_requested |= GPIO_HP_DET;
+ }
+
+ snd_soc_jack_new(codec, "Mic Jack", SND_JACK_MICROPHONE,
+ &tegra_wm8903_mic_jack);
+ snd_soc_jack_add_pins(&tegra_wm8903_mic_jack,
+ ARRAY_SIZE(tegra_wm8903_mic_jack_pins),
+ tegra_wm8903_mic_jack_pins);
+ wm8903_mic_detect(codec, &tegra_wm8903_mic_jack, SND_JACK_MICROPHONE,
+ 0);
+
+ snd_soc_dapm_force_enable_pin(dapm, "Mic Bias");
+
+ /* FIXME: Calculate automatically based on DAPM routes? */
+ if (!machine_is_harmony() && !machine_is_ventana())
+ snd_soc_dapm_nc_pin(dapm, "IN1L");
+ if (!machine_is_seaboard() && !machine_is_aebl())
+ snd_soc_dapm_nc_pin(dapm, "IN1R");
+ snd_soc_dapm_nc_pin(dapm, "IN2L");
+ if (!machine_is_kaen())
+ snd_soc_dapm_nc_pin(dapm, "IN2R");
+ snd_soc_dapm_nc_pin(dapm, "IN3L");
+ snd_soc_dapm_nc_pin(dapm, "IN3R");
+
+ if (machine_is_aebl()) {
+ snd_soc_dapm_nc_pin(dapm, "LON");
+ snd_soc_dapm_nc_pin(dapm, "RON");
+ snd_soc_dapm_nc_pin(dapm, "ROP");
+ snd_soc_dapm_nc_pin(dapm, "LOP");
+ } else {
+ snd_soc_dapm_nc_pin(dapm, "LINEOUTR");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUTL");
+ }
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link tegra_wm8903_dai = {
+ .name = "WM8903",
+ .stream_name = "WM8903 PCM",
+ .codec_name = "wm8903.0-001a",
+ .platform_name = "tegra-pcm-audio",
+ .cpu_dai_name = "tegra-i2s.0",
+ .codec_dai_name = "wm8903-hifi",
+ .init = tegra_wm8903_init,
+ .ops = &tegra_wm8903_ops,
+};
+
+static struct snd_soc_card snd_soc_tegra_wm8903 = {
+ .name = "tegra-wm8903",
+ .dai_link = &tegra_wm8903_dai,
+ .num_links = 1,
+
+ .controls = tegra_wm8903_controls,
+ .num_controls = ARRAY_SIZE(tegra_wm8903_controls),
+ .dapm_widgets = tegra_wm8903_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra_wm8903_dapm_widgets),
+};
+
+static __devinit int tegra_wm8903_driver_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &snd_soc_tegra_wm8903;
+ struct tegra_wm8903 *machine;
+ struct tegra_wm8903_platform_data *pdata;
+ int ret;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "No platform data supplied\n");
+ return -EINVAL;
+ }
+
+ machine = kzalloc(sizeof(struct tegra_wm8903), GFP_KERNEL);
+ if (!machine) {
+ dev_err(&pdev->dev, "Can't allocate tegra_wm8903 struct\n");
+ return -ENOMEM;
+ }
+
+ machine->pdata = pdata;
+
+ ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev);
+ if (ret)
+ goto err_free_machine;
+
+ card->dev = &pdev->dev;
+ platform_set_drvdata(pdev, card);
+ snd_soc_card_set_drvdata(card, machine);
+
+ if (machine_is_harmony() || machine_is_ventana()) {
+ card->dapm_routes = harmony_audio_map;
+ card->num_dapm_routes = ARRAY_SIZE(harmony_audio_map);
+ } else if (machine_is_seaboard()) {
+ card->dapm_routes = seaboard_audio_map;
+ card->num_dapm_routes = ARRAY_SIZE(seaboard_audio_map);
+ } else if (machine_is_kaen()) {
+ card->dapm_routes = kaen_audio_map;
+ card->num_dapm_routes = ARRAY_SIZE(kaen_audio_map);
+ } else {
+ card->dapm_routes = aebl_audio_map;
+ card->num_dapm_routes = ARRAY_SIZE(aebl_audio_map);
+ }
+
+ ret = snd_soc_register_card(card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
+ ret);
+ goto err_fini_utils;
+ }
+
+ return 0;
+
+err_fini_utils:
+ tegra_asoc_utils_fini(&machine->util_data);
+err_free_machine:
+ kfree(machine);
+ return ret;
+}
+
+static int __devexit tegra_wm8903_driver_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_wm8903_platform_data *pdata = machine->pdata;
+
+ if (machine->gpio_requested & GPIO_HP_DET)
+ snd_soc_jack_free_gpios(&tegra_wm8903_hp_jack,
+ 1,
+ &tegra_wm8903_hp_jack_gpio);
+ if (machine->gpio_requested & GPIO_EXT_MIC_EN)
+ gpio_free(pdata->gpio_ext_mic_en);
+ if (machine->gpio_requested & GPIO_INT_MIC_EN)
+ gpio_free(pdata->gpio_int_mic_en);
+ if (machine->gpio_requested & GPIO_HP_MUTE)
+ gpio_free(pdata->gpio_hp_mute);
+ if (machine->gpio_requested & GPIO_SPKR_EN)
+ gpio_free(pdata->gpio_spkr_en);
+ machine->gpio_requested = 0;
+
+ snd_soc_unregister_card(card);
+
+ tegra_asoc_utils_fini(&machine->util_data);
+
+ kfree(machine);
+
+ return 0;
+}
+
+static struct platform_driver tegra_wm8903_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = tegra_wm8903_driver_probe,
+ .remove = __devexit_p(tegra_wm8903_driver_remove),
+};
+
+static int __init tegra_wm8903_modinit(void)
+{
+ return platform_driver_register(&tegra_wm8903_driver);
+}
+module_init(tegra_wm8903_modinit);
+
+static void __exit tegra_wm8903_modexit(void)
+{
+ platform_driver_unregister(&tegra_wm8903_driver);
+}
+module_exit(tegra_wm8903_modexit);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra+WM8903 machine ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c
new file mode 100644
index 00000000..8fc07e9a
--- /dev/null
+++ b/sound/soc/tegra/trimslice.c
@@ -0,0 +1,228 @@
+/*
+ * trimslice.c - TrimSlice machine ASoC driver
+ *
+ * Copyright (C) 2011 - CompuLab, Ltd.
+ * Author: Mike Rapoport <mike@compulab.co.il>
+ *
+ * Based on code copyright/by:
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010-2011 - NVIDIA, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <asm/mach-types.h>
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "../codecs/tlv320aic23.h"
+
+#include "tegra_das.h"
+#include "tegra_i2s.h"
+#include "tegra_pcm.h"
+#include "tegra_asoc_utils.h"
+
+#define DRV_NAME "tegra-snd-trimslice"
+
+struct tegra_trimslice {
+ struct tegra_asoc_utils_data util_data;
+};
+
+static int trimslice_asoc_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_card *card = codec->card;
+ struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card);
+ int srate, mclk;
+ int err;
+
+ srate = params_rate(params);
+ mclk = 128 * srate;
+
+ err = tegra_asoc_utils_set_rate(&trimslice->util_data, srate, mclk);
+ if (err < 0) {
+ dev_err(card->dev, "Can't configure clocks\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai fmt not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err < 0) {
+ dev_err(card->dev, "cpu_dai fmt not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
+ SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai clock not set\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops trimslice_asoc_ops = {
+ .hw_params = trimslice_asoc_hw_params,
+};
+
+static const struct snd_soc_dapm_widget trimslice_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Line Out", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+static const struct snd_soc_dapm_route trimslice_audio_map[] = {
+ {"Line Out", NULL, "LOUT"},
+ {"Line Out", NULL, "ROUT"},
+
+ {"LLINEIN", NULL, "Line In"},
+ {"RLINEIN", NULL, "Line In"},
+};
+
+static int trimslice_asoc_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ snd_soc_dapm_nc_pin(dapm, "LHPOUT");
+ snd_soc_dapm_nc_pin(dapm, "RHPOUT");
+ snd_soc_dapm_nc_pin(dapm, "MICIN");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link trimslice_tlv320aic23_dai = {
+ .name = "TLV320AIC23",
+ .stream_name = "AIC23",
+ .codec_name = "tlv320aic23-codec.2-001a",
+ .platform_name = "tegra-pcm-audio",
+ .cpu_dai_name = "tegra-i2s.0",
+ .codec_dai_name = "tlv320aic23-hifi",
+ .init = trimslice_asoc_init,
+ .ops = &trimslice_asoc_ops,
+};
+
+static struct snd_soc_card snd_soc_trimslice = {
+ .name = "tegra-trimslice",
+ .dai_link = &trimslice_tlv320aic23_dai,
+ .num_links = 1,
+
+ .dapm_widgets = trimslice_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(trimslice_dapm_widgets),
+ .dapm_routes = trimslice_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(trimslice_audio_map),
+};
+
+static __devinit int tegra_snd_trimslice_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &snd_soc_trimslice;
+ struct tegra_trimslice *trimslice;
+ int ret;
+
+ trimslice = kzalloc(sizeof(struct tegra_trimslice), GFP_KERNEL);
+ if (!trimslice) {
+ dev_err(&pdev->dev, "Can't allocate tegra_trimslice\n");
+ return -ENOMEM;
+ }
+
+ ret = tegra_asoc_utils_init(&trimslice->util_data, &pdev->dev);
+ if (ret)
+ goto err_free_trimslice;
+
+ card->dev = &pdev->dev;
+ platform_set_drvdata(pdev, card);
+ snd_soc_card_set_drvdata(card, trimslice);
+
+ ret = snd_soc_register_card(card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
+ ret);
+ goto err_fini_utils;
+ }
+
+ return 0;
+
+err_fini_utils:
+ tegra_asoc_utils_fini(&trimslice->util_data);
+err_free_trimslice:
+ kfree(trimslice);
+ return ret;
+}
+
+static int __devexit tegra_snd_trimslice_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card);
+
+ snd_soc_unregister_card(card);
+
+ tegra_asoc_utils_fini(&trimslice->util_data);
+
+ kfree(trimslice);
+
+ return 0;
+}
+
+static struct platform_driver tegra_snd_trimslice_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = tegra_snd_trimslice_probe,
+ .remove = __devexit_p(tegra_snd_trimslice_remove),
+};
+
+static int __init snd_tegra_trimslice_init(void)
+{
+ return platform_driver_register(&tegra_snd_trimslice_driver);
+}
+module_init(snd_tegra_trimslice_init);
+
+static void __exit snd_tegra_trimslice_exit(void)
+{
+ platform_driver_unregister(&tegra_snd_trimslice_driver);
+}
+module_exit(snd_tegra_trimslice_exit);
+
+MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>");
+MODULE_DESCRIPTION("Trimslice machine ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/txx9/Kconfig b/sound/soc/txx9/Kconfig
new file mode 100644
index 00000000..ebc9327e
--- /dev/null
+++ b/sound/soc/txx9/Kconfig
@@ -0,0 +1,29 @@
+##
+## TXx9 ACLC
+##
+config SND_SOC_TXX9ACLC
+ tristate "SoC Audio for TXx9"
+ depends on HAS_TXX9_ACLC && TXX9_DMAC
+ help
+ This option enables support for the AC Link Controllers in TXx9 SoC.
+
+config HAS_TXX9_ACLC
+ bool
+
+config SND_SOC_TXX9ACLC_AC97
+ tristate
+ select AC97_BUS
+ select SND_AC97_CODEC
+ select SND_SOC_AC97_BUS
+
+
+##
+## Boards
+##
+config SND_SOC_TXX9ACLC_GENERIC
+ tristate "Generic TXx9 ACLC sound machine"
+ depends on SND_SOC_TXX9ACLC
+ select SND_SOC_TXX9ACLC_AC97
+ select SND_SOC_AC97_CODEC
+ help
+ This is a generic AC97 sound machine for use in TXx9 based systems.
diff --git a/sound/soc/txx9/Makefile b/sound/soc/txx9/Makefile
new file mode 100644
index 00000000..551f16c0
--- /dev/null
+++ b/sound/soc/txx9/Makefile
@@ -0,0 +1,11 @@
+# Platform
+snd-soc-txx9aclc-objs := txx9aclc.o
+snd-soc-txx9aclc-ac97-objs := txx9aclc-ac97.o
+
+obj-$(CONFIG_SND_SOC_TXX9ACLC) += snd-soc-txx9aclc.o
+obj-$(CONFIG_SND_SOC_TXX9ACLC_AC97) += snd-soc-txx9aclc-ac97.o
+
+# Machine
+snd-soc-txx9aclc-generic-objs := txx9aclc-generic.o
+
+obj-$(CONFIG_SND_SOC_TXX9ACLC_GENERIC) += snd-soc-txx9aclc-generic.o
diff --git a/sound/soc/txx9/txx9aclc-ac97.c b/sound/soc/txx9/txx9aclc-ac97.c
new file mode 100644
index 00000000..743d07b8
--- /dev/null
+++ b/sound/soc/txx9/txx9aclc-ac97.c
@@ -0,0 +1,242 @@
+/*
+ * TXx9 ACLC AC97 driver
+ *
+ * Copyright (C) 2009 Atsushi Nemoto
+ *
+ * Based on RBTX49xx patch from CELF patch archive.
+ * (C) Copyright TOSHIBA CORPORATION 2004-2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/gfp.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include "txx9aclc.h"
+
+#define AC97_DIR \
+ (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
+
+#define AC97_RATES \
+ SNDRV_PCM_RATE_8000_48000
+
+#ifdef __BIG_ENDIAN
+#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_BE
+#else
+#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_LE
+#endif
+
+static DECLARE_WAIT_QUEUE_HEAD(ac97_waitq);
+
+/* REVISIT: How to find txx9aclc_drvdata from snd_ac97? */
+static struct txx9aclc_plat_drvdata *txx9aclc_drvdata;
+
+static int txx9aclc_regready(struct txx9aclc_plat_drvdata *drvdata)
+{
+ return __raw_readl(drvdata->base + ACINTSTS) & ACINT_REGACCRDY;
+}
+
+/* AC97 controller reads codec register */
+static unsigned short txx9aclc_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
+ void __iomem *base = drvdata->base;
+ u32 dat;
+
+ if (!(__raw_readl(base + ACINTSTS) & ACINT_CODECRDY(ac97->num)))
+ return 0xffff;
+ reg |= ac97->num << 7;
+ dat = (reg << ACREGACC_REG_SHIFT) | ACREGACC_READ;
+ __raw_writel(dat, base + ACREGACC);
+ __raw_writel(ACINT_REGACCRDY, base + ACINTEN);
+ if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(txx9aclc_drvdata), HZ)) {
+ __raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
+ printk(KERN_ERR "ac97 read timeout (reg %#x)\n", reg);
+ dat = 0xffff;
+ goto done;
+ }
+ dat = __raw_readl(base + ACREGACC);
+ if (((dat >> ACREGACC_REG_SHIFT) & 0xff) != reg) {
+ printk(KERN_ERR "reg mismatch %x with %x\n",
+ dat, reg);
+ dat = 0xffff;
+ goto done;
+ }
+ dat = (dat >> ACREGACC_DAT_SHIFT) & 0xffff;
+done:
+ __raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
+ return dat;
+}
+
+/* AC97 controller writes to codec register */
+static void txx9aclc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
+ void __iomem *base = drvdata->base;
+
+ __raw_writel(((reg | (ac97->num << 7)) << ACREGACC_REG_SHIFT) |
+ (val << ACREGACC_DAT_SHIFT),
+ base + ACREGACC);
+ __raw_writel(ACINT_REGACCRDY, base + ACINTEN);
+ if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(txx9aclc_drvdata), HZ)) {
+ printk(KERN_ERR
+ "ac97 write timeout (reg %#x)\n", reg);
+ }
+ __raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
+}
+
+static void txx9aclc_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
+ void __iomem *base = drvdata->base;
+ u32 ready = ACINT_CODECRDY(ac97->num) | ACINT_REGACCRDY;
+
+ __raw_writel(ACCTL_ENLINK, base + ACCTLDIS);
+ mmiowb();
+ udelay(1);
+ __raw_writel(ACCTL_ENLINK, base + ACCTLEN);
+ /* wait for primary codec ready status */
+ __raw_writel(ready, base + ACINTEN);
+ if (!wait_event_timeout(ac97_waitq,
+ (__raw_readl(base + ACINTSTS) & ready) == ready,
+ HZ)) {
+ dev_err(&ac97->dev, "primary codec is not ready "
+ "(status %#x)\n",
+ __raw_readl(base + ACINTSTS));
+ }
+ __raw_writel(ACINT_REGACCRDY, base + ACINTSTS);
+ __raw_writel(ready, base + ACINTDIS);
+}
+
+/* AC97 controller operations */
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = txx9aclc_ac97_read,
+ .write = txx9aclc_ac97_write,
+ .reset = txx9aclc_ac97_cold_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static irqreturn_t txx9aclc_ac97_irq(int irq, void *dev_id)
+{
+ struct txx9aclc_plat_drvdata *drvdata = dev_id;
+ void __iomem *base = drvdata->base;
+
+ __raw_writel(__raw_readl(base + ACINTMSTS), base + ACINTDIS);
+ wake_up(&ac97_waitq);
+ return IRQ_HANDLED;
+}
+
+static int txx9aclc_ac97_probe(struct snd_soc_dai *dai)
+{
+ txx9aclc_drvdata = snd_soc_dai_get_drvdata(dai);
+ return 0;
+}
+
+static int txx9aclc_ac97_remove(struct snd_soc_dai *dai)
+{
+ struct txx9aclc_plat_drvdata *drvdata = snd_soc_dai_get_drvdata(dai);
+
+ /* disable AC-link */
+ __raw_writel(ACCTL_ENLINK, drvdata->base + ACCTLDIS);
+ txx9aclc_drvdata = NULL;
+ return 0;
+}
+
+static struct snd_soc_dai_driver txx9aclc_ac97_dai = {
+ .ac97_control = 1,
+ .probe = txx9aclc_ac97_probe,
+ .remove = txx9aclc_ac97_remove,
+ .playback = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .capture = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+};
+
+static int __devinit txx9aclc_ac97_dev_probe(struct platform_device *pdev)
+{
+ struct txx9aclc_plat_drvdata *drvdata;
+ struct resource *r;
+ int err;
+ int irq;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r)
+ return -EBUSY;
+
+ if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r),
+ dev_name(&pdev->dev)))
+ return -EBUSY;
+
+ drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, drvdata);
+ drvdata->physbase = r->start;
+ if (sizeof(drvdata->physbase) > sizeof(r->start) &&
+ r->start >= TXX9_DIRECTMAP_BASE &&
+ r->start < TXX9_DIRECTMAP_BASE + 0x400000)
+ drvdata->physbase |= 0xf00000000ull;
+ drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
+ if (!drvdata->base)
+ return -EBUSY;
+ err = devm_request_irq(&pdev->dev, irq, txx9aclc_ac97_irq,
+ IRQF_DISABLED, dev_name(&pdev->dev), drvdata);
+ if (err < 0)
+ return err;
+
+ return snd_soc_register_dai(&pdev->dev, &txx9aclc_ac97_dai);
+}
+
+static int __devexit txx9aclc_ac97_dev_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver txx9aclc_ac97_driver = {
+ .probe = txx9aclc_ac97_dev_probe,
+ .remove = __devexit_p(txx9aclc_ac97_dev_remove),
+ .driver = {
+ .name = "txx9aclc-ac97",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init txx9aclc_ac97_init(void)
+{
+ return platform_driver_register(&txx9aclc_ac97_driver);
+}
+
+static void __exit txx9aclc_ac97_exit(void)
+{
+ platform_driver_unregister(&txx9aclc_ac97_driver);
+}
+
+module_init(txx9aclc_ac97_init);
+module_exit(txx9aclc_ac97_exit);
+
+MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
+MODULE_DESCRIPTION("TXx9 ACLC AC97 driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:txx9aclc-ac97");
diff --git a/sound/soc/txx9/txx9aclc-generic.c b/sound/soc/txx9/txx9aclc-generic.c
new file mode 100644
index 00000000..6770e716
--- /dev/null
+++ b/sound/soc/txx9/txx9aclc-generic.c
@@ -0,0 +1,89 @@
+/*
+ * Generic TXx9 ACLC machine driver
+ *
+ * Copyright (C) 2009 Atsushi Nemoto
+ *
+ * Based on RBTX49xx patch from CELF patch archive.
+ * (C) Copyright TOSHIBA CORPORATION 2004-2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This is a very generic AC97 sound machine driver for boards which
+ * have (AC97) audio at ACLC (e.g. RBTX49XX boards).
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include "txx9aclc.h"
+
+static struct snd_soc_dai_link txx9aclc_generic_dai = {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai_name = "txx9aclc-ac97",
+ .codec_dai_name = "ac97-hifi",
+ .platform_name = "txx9aclc-pcm-audio",
+ .codec_name = "ac97-codec",
+};
+
+static struct snd_soc_card txx9aclc_generic_card = {
+ .name = "Generic TXx9 ACLC Audio",
+ .dai_link = &txx9aclc_generic_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *soc_pdev;
+
+static int __init txx9aclc_generic_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ soc_pdev = platform_device_alloc("soc-audio", -1);
+ if (!soc_pdev)
+ return -ENOMEM;
+ platform_set_drvdata(soc_pdev, &txx9aclc_generic_card);
+ ret = platform_device_add(soc_pdev);
+ if (ret) {
+ platform_device_put(soc_pdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __exit txx9aclc_generic_remove(struct platform_device *pdev)
+{
+ platform_device_unregister(soc_pdev);
+ return 0;
+}
+
+static struct platform_driver txx9aclc_generic_driver = {
+ .remove = txx9aclc_generic_remove,
+ .driver = {
+ .name = "txx9aclc-generic",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init txx9aclc_generic_init(void)
+{
+ return platform_driver_probe(&txx9aclc_generic_driver,
+ txx9aclc_generic_probe);
+}
+
+static void __exit txx9aclc_generic_exit(void)
+{
+ platform_driver_unregister(&txx9aclc_generic_driver);
+}
+
+module_init(txx9aclc_generic_init);
+module_exit(txx9aclc_generic_exit);
+
+MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
+MODULE_DESCRIPTION("Generic TXx9 ACLC ALSA SoC audio driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:txx9aclc-generic");
diff --git a/sound/soc/txx9/txx9aclc.c b/sound/soc/txx9/txx9aclc.c
new file mode 100644
index 00000000..f4aa4e03
--- /dev/null
+++ b/sound/soc/txx9/txx9aclc.c
@@ -0,0 +1,453 @@
+/*
+ * Generic TXx9 ACLC platform driver
+ *
+ * Copyright (C) 2009 Atsushi Nemoto
+ *
+ * Based on RBTX49xx patch from CELF patch archive.
+ * (C) Copyright TOSHIBA CORPORATION 2004-2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "txx9aclc.h"
+
+static struct txx9aclc_soc_device {
+ struct txx9aclc_dmadata dmadata[2];
+} txx9aclc_soc_device;
+
+/* REVISIT: How to find txx9aclc_drvdata from snd_ac97? */
+static struct txx9aclc_plat_drvdata *txx9aclc_drvdata;
+
+static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev,
+ struct txx9aclc_dmadata *dmadata);
+
+static const struct snd_pcm_hardware txx9aclc_pcm_hardware = {
+ /*
+ * REVISIT: SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
+ * needs more works for noncoherent MIPS.
+ */
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_PAUSE,
+#ifdef __BIG_ENDIAN
+ .formats = SNDRV_PCM_FMTBIT_S16_BE,
+#else
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+#endif
+ .period_bytes_min = 1024,
+ .period_bytes_max = 8 * 1024,
+ .periods_min = 2,
+ .periods_max = 4096,
+ .buffer_bytes_max = 32 * 1024,
+};
+
+static int txx9aclc_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct txx9aclc_dmadata *dmadata = runtime->private_data;
+ int ret;
+
+ ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(rtd->platform->dev,
+ "runtime->dma_area = %#lx dma_addr = %#lx dma_bytes = %zd "
+ "runtime->min_align %ld\n",
+ (unsigned long)runtime->dma_area,
+ (unsigned long)runtime->dma_addr, runtime->dma_bytes,
+ runtime->min_align);
+ dev_dbg(rtd->platform->dev,
+ "periods %d period_bytes %d stream %d\n",
+ params_periods(params), params_period_bytes(params),
+ substream->stream);
+
+ dmadata->substream = substream;
+ dmadata->pos = 0;
+ return 0;
+}
+
+static int txx9aclc_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int txx9aclc_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct txx9aclc_dmadata *dmadata = runtime->private_data;
+
+ dmadata->dma_addr = runtime->dma_addr;
+ dmadata->buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
+ dmadata->period_bytes = snd_pcm_lib_period_bytes(substream);
+
+ if (dmadata->buffer_bytes == dmadata->period_bytes) {
+ dmadata->frag_bytes = dmadata->period_bytes >> 1;
+ dmadata->frags = 2;
+ } else {
+ dmadata->frag_bytes = dmadata->period_bytes;
+ dmadata->frags = dmadata->buffer_bytes / dmadata->period_bytes;
+ }
+ dmadata->frag_count = 0;
+ dmadata->pos = 0;
+ return 0;
+}
+
+static void txx9aclc_dma_complete(void *arg)
+{
+ struct txx9aclc_dmadata *dmadata = arg;
+ unsigned long flags;
+
+ /* dma completion handler cannot submit new operations */
+ spin_lock_irqsave(&dmadata->dma_lock, flags);
+ if (dmadata->frag_count >= 0) {
+ dmadata->dmacount--;
+ BUG_ON(dmadata->dmacount < 0);
+ tasklet_schedule(&dmadata->tasklet);
+ }
+ spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+}
+
+static struct dma_async_tx_descriptor *
+txx9aclc_dma_submit(struct txx9aclc_dmadata *dmadata, dma_addr_t buf_dma_addr)
+{
+ struct dma_chan *chan = dmadata->dma_chan;
+ struct dma_async_tx_descriptor *desc;
+ struct scatterlist sg;
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, pfn_to_page(PFN_DOWN(buf_dma_addr)),
+ dmadata->frag_bytes, buf_dma_addr & (PAGE_SIZE - 1));
+ sg_dma_address(&sg) = buf_dma_addr;
+ desc = chan->device->device_prep_slave_sg(chan, &sg, 1,
+ dmadata->substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ DMA_TO_DEVICE : DMA_FROM_DEVICE,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc) {
+ dev_err(&chan->dev->device, "cannot prepare slave dma\n");
+ return NULL;
+ }
+ desc->callback = txx9aclc_dma_complete;
+ desc->callback_param = dmadata;
+ desc->tx_submit(desc);
+ return desc;
+}
+
+#define NR_DMA_CHAIN 2
+
+static void txx9aclc_dma_tasklet(unsigned long data)
+{
+ struct txx9aclc_dmadata *dmadata = (struct txx9aclc_dmadata *)data;
+ struct dma_chan *chan = dmadata->dma_chan;
+ struct dma_async_tx_descriptor *desc;
+ struct snd_pcm_substream *substream = dmadata->substream;
+ u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ ACCTL_AUDODMA : ACCTL_AUDIDMA;
+ int i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dmadata->dma_lock, flags);
+ if (dmadata->frag_count < 0) {
+ struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
+ void __iomem *base = drvdata->base;
+
+ spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+ chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
+ /* first time */
+ for (i = 0; i < NR_DMA_CHAIN; i++) {
+ desc = txx9aclc_dma_submit(dmadata,
+ dmadata->dma_addr + i * dmadata->frag_bytes);
+ if (!desc)
+ return;
+ }
+ dmadata->dmacount = NR_DMA_CHAIN;
+ chan->device->device_issue_pending(chan);
+ spin_lock_irqsave(&dmadata->dma_lock, flags);
+ __raw_writel(ctlbit, base + ACCTLEN);
+ dmadata->frag_count = NR_DMA_CHAIN % dmadata->frags;
+ spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+ return;
+ }
+ BUG_ON(dmadata->dmacount >= NR_DMA_CHAIN);
+ while (dmadata->dmacount < NR_DMA_CHAIN) {
+ dmadata->dmacount++;
+ spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+ desc = txx9aclc_dma_submit(dmadata,
+ dmadata->dma_addr +
+ dmadata->frag_count * dmadata->frag_bytes);
+ if (!desc)
+ return;
+ chan->device->device_issue_pending(chan);
+
+ spin_lock_irqsave(&dmadata->dma_lock, flags);
+ dmadata->frag_count++;
+ dmadata->frag_count %= dmadata->frags;
+ dmadata->pos += dmadata->frag_bytes;
+ dmadata->pos %= dmadata->buffer_bytes;
+ if ((dmadata->frag_count * dmadata->frag_bytes) %
+ dmadata->period_bytes == 0)
+ snd_pcm_period_elapsed(substream);
+ }
+ spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+}
+
+static int txx9aclc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct txx9aclc_dmadata *dmadata = substream->runtime->private_data;
+ struct txx9aclc_plat_drvdata *drvdata =txx9aclc_drvdata;
+ void __iomem *base = drvdata->base;
+ unsigned long flags;
+ int ret = 0;
+ u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ ACCTL_AUDODMA : ACCTL_AUDIDMA;
+
+ spin_lock_irqsave(&dmadata->dma_lock, flags);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ dmadata->frag_count = -1;
+ tasklet_schedule(&dmadata->tasklet);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ __raw_writel(ctlbit, base + ACCTLDIS);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ __raw_writel(ctlbit, base + ACCTLEN);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+ return ret;
+}
+
+static snd_pcm_uframes_t
+txx9aclc_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct txx9aclc_dmadata *dmadata = substream->runtime->private_data;
+
+ return bytes_to_frames(substream->runtime, dmadata->pos);
+}
+
+static int txx9aclc_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct txx9aclc_soc_device *dev = &txx9aclc_soc_device;
+ struct txx9aclc_dmadata *dmadata = &dev->dmadata[substream->stream];
+ int ret;
+
+ ret = snd_soc_set_runtime_hwparams(substream, &txx9aclc_pcm_hardware);
+ if (ret)
+ return ret;
+ /* ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+ substream->runtime->private_data = dmadata;
+ return 0;
+}
+
+static int txx9aclc_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct txx9aclc_dmadata *dmadata = substream->runtime->private_data;
+ struct dma_chan *chan = dmadata->dma_chan;
+
+ dmadata->frag_count = -1;
+ chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
+ return 0;
+}
+
+static struct snd_pcm_ops txx9aclc_pcm_ops = {
+ .open = txx9aclc_pcm_open,
+ .close = txx9aclc_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = txx9aclc_pcm_hw_params,
+ .hw_free = txx9aclc_pcm_hw_free,
+ .prepare = txx9aclc_pcm_prepare,
+ .trigger = txx9aclc_pcm_trigger,
+ .pointer = txx9aclc_pcm_pointer,
+};
+
+static void txx9aclc_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int txx9aclc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ struct platform_device *pdev = to_platform_device(dai->platform->dev);
+ struct txx9aclc_soc_device *dev;
+ struct resource *r;
+ int i;
+ int ret;
+
+ /* at this point onwards the AC97 component has probed and this will be valid */
+ dev = snd_soc_dai_get_drvdata(dai);
+
+ dev->dmadata[0].stream = SNDRV_PCM_STREAM_PLAYBACK;
+ dev->dmadata[1].stream = SNDRV_PCM_STREAM_CAPTURE;
+ for (i = 0; i < 2; i++) {
+ r = platform_get_resource(pdev, IORESOURCE_DMA, i);
+ if (!r) {
+ ret = -EBUSY;
+ goto exit;
+ }
+ dev->dmadata[i].dma_res = r;
+ ret = txx9aclc_dma_init(dev, &dev->dmadata[i]);
+ if (ret)
+ goto exit;
+ }
+ return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev, 64 * 1024, 4 * 1024 * 1024);
+
+exit:
+ for (i = 0; i < 2; i++) {
+ if (dev->dmadata[i].dma_chan)
+ dma_release_channel(dev->dmadata[i].dma_chan);
+ dev->dmadata[i].dma_chan = NULL;
+ }
+ return ret;
+}
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+ struct txx9aclc_dmadata *dmadata = param;
+ char *devname;
+ bool found = false;
+
+ devname = kasprintf(GFP_KERNEL, "%s.%d", dmadata->dma_res->name,
+ (int)dmadata->dma_res->start);
+ if (strcmp(dev_name(chan->device->dev), devname) == 0) {
+ chan->private = &dmadata->dma_slave;
+ found = true;
+ }
+ kfree(devname);
+ return found;
+}
+
+static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev,
+ struct txx9aclc_dmadata *dmadata)
+{
+ struct txx9aclc_plat_drvdata *drvdata =txx9aclc_drvdata;
+ struct txx9dmac_slave *ds = &dmadata->dma_slave;
+ dma_cap_mask_t mask;
+
+ spin_lock_init(&dmadata->dma_lock);
+
+ ds->reg_width = sizeof(u32);
+ if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ds->tx_reg = drvdata->physbase + ACAUDODAT;
+ ds->rx_reg = 0;
+ } else {
+ ds->tx_reg = 0;
+ ds->rx_reg = drvdata->physbase + ACAUDIDAT;
+ }
+
+ /* Try to grab a DMA channel */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dmadata->dma_chan = dma_request_channel(mask, filter, dmadata);
+ if (!dmadata->dma_chan) {
+ printk(KERN_ERR
+ "DMA channel for %s is not available\n",
+ dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "playback" : "capture");
+ return -EBUSY;
+ }
+ tasklet_init(&dmadata->tasklet, txx9aclc_dma_tasklet,
+ (unsigned long)dmadata);
+ return 0;
+}
+
+static int txx9aclc_pcm_probe(struct snd_soc_platform *platform)
+{
+ snd_soc_platform_set_drvdata(platform, &txx9aclc_soc_device);
+ return 0;
+}
+
+static int txx9aclc_pcm_remove(struct snd_soc_platform *platform)
+{
+ struct txx9aclc_soc_device *dev = snd_soc_platform_get_drvdata(platform);
+ struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
+ void __iomem *base = drvdata->base;
+ int i;
+
+ /* disable all FIFO DMAs */
+ __raw_writel(ACCTL_AUDODMA | ACCTL_AUDIDMA, base + ACCTLDIS);
+ /* dummy R/W to clear pending DMAREQ if any */
+ __raw_writel(__raw_readl(base + ACAUDIDAT), base + ACAUDODAT);
+
+ for (i = 0; i < 2; i++) {
+ struct txx9aclc_dmadata *dmadata = &dev->dmadata[i];
+ struct dma_chan *chan = dmadata->dma_chan;
+ if (chan) {
+ dmadata->frag_count = -1;
+ chan->device->device_control(chan,
+ DMA_TERMINATE_ALL, 0);
+ dma_release_channel(chan);
+ }
+ dev->dmadata[i].dma_chan = NULL;
+ }
+ return 0;
+}
+
+static struct snd_soc_platform_driver txx9aclc_soc_platform = {
+ .probe = txx9aclc_pcm_probe,
+ .remove = txx9aclc_pcm_remove,
+ .ops = &txx9aclc_pcm_ops,
+ .pcm_new = txx9aclc_pcm_new,
+ .pcm_free = txx9aclc_pcm_free_dma_buffers,
+};
+
+static int __devinit txx9aclc_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &txx9aclc_soc_platform);
+}
+
+static int __devexit txx9aclc_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver txx9aclc_pcm_driver = {
+ .driver = {
+ .name = "txx9aclc-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = txx9aclc_soc_platform_probe,
+ .remove = __devexit_p(txx9aclc_soc_platform_remove),
+};
+
+static int __init snd_txx9aclc_pcm_init(void)
+{
+ return platform_driver_register(&txx9aclc_pcm_driver);
+}
+module_init(snd_txx9aclc_pcm_init);
+
+static void __exit snd_txx9aclc_pcm_exit(void)
+{
+ platform_driver_unregister(&txx9aclc_pcm_driver);
+}
+module_exit(snd_txx9aclc_pcm_exit);
+
+MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
+MODULE_DESCRIPTION("TXx9 ACLC Audio DMA driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/txx9/txx9aclc.h b/sound/soc/txx9/txx9aclc.h
new file mode 100644
index 00000000..9c2de84f
--- /dev/null
+++ b/sound/soc/txx9/txx9aclc.h
@@ -0,0 +1,74 @@
+/*
+ * TXx9 SoC AC Link Controller
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __TXX9ACLC_H
+#define __TXX9ACLC_H
+
+#include <linux/interrupt.h>
+#include <asm/txx9/dmac.h>
+
+#define ACCTLEN 0x00 /* control enable */
+#define ACCTLDIS 0x04 /* control disable */
+#define ACCTL_ENLINK 0x00000001 /* enable/disable AC-link */
+#define ACCTL_AUDODMA 0x00000100 /* AUDODMA enable/disable */
+#define ACCTL_AUDIDMA 0x00001000 /* AUDIDMA enable/disable */
+#define ACCTL_AUDOEHLT 0x00010000 /* AUDO error halt
+ enable/disable */
+#define ACCTL_AUDIEHLT 0x00100000 /* AUDI error halt
+ enable/disable */
+#define ACREGACC 0x08 /* codec register access */
+#define ACREGACC_DAT_SHIFT 0 /* data field */
+#define ACREGACC_REG_SHIFT 16 /* address field */
+#define ACREGACC_CODECID_SHIFT 24 /* CODEC ID field */
+#define ACREGACC_READ 0x80000000 /* CODEC read */
+#define ACREGACC_WRITE 0x00000000 /* CODEC write */
+#define ACINTSTS 0x10 /* interrupt status */
+#define ACINTMSTS 0x14 /* interrupt masked status */
+#define ACINTEN 0x18 /* interrupt enable */
+#define ACINTDIS 0x1c /* interrupt disable */
+#define ACINT_CODECRDY(n) (0x00000001 << (n)) /* CODECn ready */
+#define ACINT_REGACCRDY 0x00000010 /* ACREGACC ready */
+#define ACINT_AUDOERR 0x00000100 /* AUDO underrun error */
+#define ACINT_AUDIERR 0x00001000 /* AUDI overrun error */
+#define ACDMASTS 0x80 /* DMA request status */
+#define ACDMA_AUDO 0x00000001 /* AUDODMA pending */
+#define ACDMA_AUDI 0x00000010 /* AUDIDMA pending */
+#define ACAUDODAT 0xa0 /* audio out data */
+#define ACAUDIDAT 0xb0 /* audio in data */
+#define ACREVID 0xfc /* revision ID */
+
+struct txx9aclc_dmadata {
+ struct resource *dma_res;
+ struct txx9dmac_slave dma_slave;
+ struct dma_chan *dma_chan;
+ struct tasklet_struct tasklet;
+ spinlock_t dma_lock;
+ int stream; /* SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE */
+ struct snd_pcm_substream *substream;
+ unsigned long pos;
+ dma_addr_t dma_addr;
+ unsigned long buffer_bytes;
+ unsigned long period_bytes;
+ unsigned long frag_bytes;
+ int frags;
+ int frag_count;
+ int dmacount;
+};
+
+struct txx9aclc_plat_drvdata {
+ void __iomem *base;
+ u64 physbase;
+};
+
+static inline struct txx9aclc_plat_drvdata *txx9aclc_get_plat_drvdata(
+ struct snd_soc_dai *dai)
+{
+ return dev_get_drvdata(dai->dev);
+}
+
+#endif /* __TXX9ACLC_H */