summaryrefslogtreecommitdiffstats
path: root/sound/pci/ctxfi
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /sound/pci/ctxfi
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--sound/pci/ctxfi/Makefile6
-rw-r--r--sound/pci/ctxfi/ct20k1reg.h631
-rw-r--r--sound/pci/ctxfi/ct20k2reg.h86
-rw-r--r--sound/pci/ctxfi/ctamixer.c484
-rw-r--r--sound/pci/ctxfi/ctamixer.h95
-rw-r--r--sound/pci/ctxfi/ctatc.c1748
-rw-r--r--sound/pci/ctxfi/ctatc.h156
-rw-r--r--sound/pci/ctxfi/ctdaio.c755
-rw-r--r--sound/pci/ctxfi/ctdaio.h121
-rw-r--r--sound/pci/ctxfi/cthardware.c89
-rw-r--r--sound/pci/ctxfi/cthardware.h214
-rw-r--r--sound/pci/ctxfi/cthw20k1.c2277
-rw-r--r--sound/pci/ctxfi/cthw20k1.h22
-rw-r--r--sound/pci/ctxfi/cthw20k2.c2346
-rw-r--r--sound/pci/ctxfi/cthw20k2.h22
-rw-r--r--sound/pci/ctxfi/ctimap.c108
-rw-r--r--sound/pci/ctxfi/ctimap.h36
-rw-r--r--sound/pci/ctxfi/ctmixer.c1225
-rw-r--r--sound/pci/ctxfi/ctmixer.h66
-rw-r--r--sound/pci/ctxfi/ctpcm.c476
-rw-r--r--sound/pci/ctxfi/ctpcm.h23
-rw-r--r--sound/pci/ctxfi/ctresource.c289
-rw-r--r--sound/pci/ctxfi/ctresource.h69
-rw-r--r--sound/pci/ctxfi/ctsrc.c882
-rw-r--r--sound/pci/ctxfi/ctsrc.h148
-rw-r--r--sound/pci/ctxfi/cttimer.c439
-rw-r--r--sound/pci/ctxfi/cttimer.h30
-rw-r--r--sound/pci/ctxfi/ctvmem.c242
-rw-r--r--sound/pci/ctxfi/ctvmem.h60
-rw-r--r--sound/pci/ctxfi/xfi.c158
30 files changed, 13303 insertions, 0 deletions
diff --git a/sound/pci/ctxfi/Makefile b/sound/pci/ctxfi/Makefile
new file mode 100644
index 000000000..70888706a
--- /dev/null
+++ b/sound/pci/ctxfi/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-ctxfi-objs := xfi.o ctatc.o ctvmem.o ctpcm.o ctmixer.o ctresource.o \
+ ctsrc.o ctamixer.o ctdaio.o ctimap.o cthardware.o cttimer.o \
+ cthw20k2.o cthw20k1.o
+
+obj-$(CONFIG_SND_CTXFI) += snd-ctxfi.o
diff --git a/sound/pci/ctxfi/ct20k1reg.h b/sound/pci/ctxfi/ct20k1reg.h
new file mode 100644
index 000000000..05bb006c0
--- /dev/null
+++ b/sound/pci/ctxfi/ct20k1reg.h
@@ -0,0 +1,631 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ */
+
+#ifndef CT20K1REG_H
+#define CT20K1REG_H
+
+/* 20k1 registers */
+#define DSPXRAM_START 0x000000
+#define DSPXRAM_END 0x013FFC
+#define DSPAXRAM_START 0x020000
+#define DSPAXRAM_END 0x023FFC
+#define DSPYRAM_START 0x040000
+#define DSPYRAM_END 0x04FFFC
+#define DSPAYRAM_START 0x020000
+#define DSPAYRAM_END 0x063FFC
+#define DSPMICRO_START 0x080000
+#define DSPMICRO_END 0x0B3FFC
+#define DSP0IO_START 0x100000
+#define DSP0IO_END 0x101FFC
+#define AUDIORINGIPDSP0_START 0x100000
+#define AUDIORINGIPDSP0_END 0x1003FC
+#define AUDIORINGOPDSP0_START 0x100400
+#define AUDIORINGOPDSP0_END 0x1007FC
+#define AUDPARARINGIODSP0_START 0x100800
+#define AUDPARARINGIODSP0_END 0x100BFC
+#define DSP0LOCALHWREG_START 0x100C00
+#define DSP0LOCALHWREG_END 0x100C3C
+#define DSP0XYRAMAGINDEX_START 0x100C40
+#define DSP0XYRAMAGINDEX_END 0x100C5C
+#define DSP0XYRAMAGMDFR_START 0x100C60
+#define DSP0XYRAMAGMDFR_END 0x100C7C
+#define DSP0INTCONTLVEC_START 0x100C80
+#define DSP0INTCONTLVEC_END 0x100CD8
+#define INTCONTLGLOBALREG_START 0x100D1C
+#define INTCONTLGLOBALREG_END 0x100D3C
+#define HOSTINTFPORTADDRCONTDSP0 0x100D40
+#define HOSTINTFPORTDATADSP0 0x100D44
+#define TIME0PERENBDSP0 0x100D60
+#define TIME0COUNTERDSP0 0x100D64
+#define TIME1PERENBDSP0 0x100D68
+#define TIME1COUNTERDSP0 0x100D6C
+#define TIME2PERENBDSP0 0x100D70
+#define TIME2COUNTERDSP0 0x100D74
+#define TIME3PERENBDSP0 0x100D78
+#define TIME3COUNTERDSP0 0x100D7C
+#define XRAMINDOPERREFNOUP_STARTDSP0 0x100D80
+#define XRAMINDOPERREFNOUP_ENDDSP0 0x100D9C
+#define XRAMINDOPERREFUP_STARTDSP0 0x100DA0
+#define XRAMINDOPERREFUP_ENDDSP0 0x100DBC
+#define YRAMINDOPERREFNOUP_STARTDSP0 0x100DC0
+#define YRAMINDOPERREFNOUP_ENDDSP0 0x100DDC
+#define YRAMINDOPERREFUP_STARTDSP0 0x100DE0
+#define YRAMINDOPERREFUP_ENDDSP0 0x100DFC
+#define DSP0CONDCODE 0x100E00
+#define DSP0STACKFLAG 0x100E04
+#define DSP0PROGCOUNTSTACKPTREG 0x100E08
+#define DSP0PROGCOUNTSTACKDATAREG 0x100E0C
+#define DSP0CURLOOPADDRREG 0x100E10
+#define DSP0CURLOOPCOUNT 0x100E14
+#define DSP0TOPLOOPCOUNTSTACK 0x100E18
+#define DSP0TOPLOOPADDRSTACK 0x100E1C
+#define DSP0LOOPSTACKPTR 0x100E20
+#define DSP0STASSTACKDATAREG 0x100E24
+#define DSP0STASSTACKPTR 0x100E28
+#define DSP0PROGCOUNT 0x100E2C
+#define GLOBDSPDEBGREG 0x100E30
+#define GLOBDSPBREPTRREG 0x100E30
+#define DSP0XYRAMBASE_START 0x100EA0
+#define DSP0XYRAMBASE_END 0x100EBC
+#define DSP0XYRAMLENG_START 0x100EC0
+#define DSP0XYRAMLENG_END 0x100EDC
+#define SEMAPHOREREGDSP0 0x100EE0
+#define DSP0INTCONTMASKREG 0x100EE4
+#define DSP0INTCONTPENDREG 0x100EE8
+#define DSP0INTCONTSERVINT 0x100EEC
+#define DSPINTCONTEXTINTMODREG 0x100EEC
+#define GPIODSP0 0x100EFC
+#define DMADSPBASEADDRREG_STARTDSP0 0x100F00
+#define DMADSPBASEADDRREG_ENDDSP0 0x100F1C
+#define DMAHOSTBASEADDRREG_STARTDSP0 0x100F20
+#define DMAHOSTBASEADDRREG_ENDDSP0 0x100F3C
+#define DMADSPCURADDRREG_STARTDSP0 0x100F40
+#define DMADSPCURADDRREG_ENDDSP0 0x100F5C
+#define DMAHOSTCURADDRREG_STARTDSP0 0x100F60
+#define DMAHOSTCURADDRREG_ENDDSP0 0x100F7C
+#define DMATANXCOUNTREG_STARTDSP0 0x100F80
+#define DMATANXCOUNTREG_ENDDSP0 0x100F9C
+#define DMATIMEBUGREG_STARTDSP0 0x100FA0
+#define DMATIMEBUGREG_ENDDSP0 0x100FAC
+#define DMACNTLMODFREG_STARTDSP0 0x100FA0
+#define DMACNTLMODFREG_ENDDSP0 0x100FAC
+
+#define DMAGLOBSTATSREGDSP0 0x100FEC
+#define DSP0XGPRAM_START 0x101000
+#define DSP0XGPRAM_END 0x1017FC
+#define DSP0YGPRAM_START 0x101800
+#define DSP0YGPRAM_END 0x101FFC
+
+
+
+
+#define AUDIORINGIPDSP1_START 0x102000
+#define AUDIORINGIPDSP1_END 0x1023FC
+#define AUDIORINGOPDSP1_START 0x102400
+#define AUDIORINGOPDSP1_END 0x1027FC
+#define AUDPARARINGIODSP1_START 0x102800
+#define AUDPARARINGIODSP1_END 0x102BFC
+#define DSP1LOCALHWREG_START 0x102C00
+#define DSP1LOCALHWREG_END 0x102C3C
+#define DSP1XYRAMAGINDEX_START 0x102C40
+#define DSP1XYRAMAGINDEX_END 0x102C5C
+#define DSP1XYRAMAGMDFR_START 0x102C60
+#define DSP1XYRAMAGMDFR_END 0x102C7C
+#define DSP1INTCONTLVEC_START 0x102C80
+#define DSP1INTCONTLVEC_END 0x102CD8
+#define HOSTINTFPORTADDRCONTDSP1 0x102D40
+#define HOSTINTFPORTDATADSP1 0x102D44
+#define TIME0PERENBDSP1 0x102D60
+#define TIME0COUNTERDSP1 0x102D64
+#define TIME1PERENBDSP1 0x102D68
+#define TIME1COUNTERDSP1 0x102D6C
+#define TIME2PERENBDSP1 0x102D70
+#define TIME2COUNTERDSP1 0x102D74
+#define TIME3PERENBDSP1 0x102D78
+#define TIME3COUNTERDSP1 0x102D7C
+#define XRAMINDOPERREFNOUP_STARTDSP1 0x102D80
+#define XRAMINDOPERREFNOUP_ENDDSP1 0x102D9C
+#define XRAMINDOPERREFUP_STARTDSP1 0x102DA0
+#define XRAMINDOPERREFUP_ENDDSP1 0x102DBC
+#define YRAMINDOPERREFNOUP_STARTDSP1 0x102DC0
+#define YRAMINDOPERREFNOUP_ENDDSP1 0x102DDC
+#define YRAMINDOPERREFUP_STARTDSP1 0x102DE0
+#define YRAMINDOPERREFUP_ENDDSP1 0x102DFC
+
+#define DSP1CONDCODE 0x102E00
+#define DSP1STACKFLAG 0x102E04
+#define DSP1PROGCOUNTSTACKPTREG 0x102E08
+#define DSP1PROGCOUNTSTACKDATAREG 0x102E0C
+#define DSP1CURLOOPADDRREG 0x102E10
+#define DSP1CURLOOPCOUNT 0x102E14
+#define DSP1TOPLOOPCOUNTSTACK 0x102E18
+#define DSP1TOPLOOPADDRSTACK 0x102E1C
+#define DSP1LOOPSTACKPTR 0x102E20
+#define DSP1STASSTACKDATAREG 0x102E24
+#define DSP1STASSTACKPTR 0x102E28
+#define DSP1PROGCOUNT 0x102E2C
+#define DSP1XYRAMBASE_START 0x102EA0
+#define DSP1XYRAMBASE_END 0x102EBC
+#define DSP1XYRAMLENG_START 0x102EC0
+#define DSP1XYRAMLENG_END 0x102EDC
+#define SEMAPHOREREGDSP1 0x102EE0
+#define DSP1INTCONTMASKREG 0x102EE4
+#define DSP1INTCONTPENDREG 0x102EE8
+#define DSP1INTCONTSERVINT 0x102EEC
+#define GPIODSP1 0x102EFC
+#define DMADSPBASEADDRREG_STARTDSP1 0x102F00
+#define DMADSPBASEADDRREG_ENDDSP1 0x102F1C
+#define DMAHOSTBASEADDRREG_STARTDSP1 0x102F20
+#define DMAHOSTBASEADDRREG_ENDDSP1 0x102F3C
+#define DMADSPCURADDRREG_STARTDSP1 0x102F40
+#define DMADSPCURADDRREG_ENDDSP1 0x102F5C
+#define DMAHOSTCURADDRREG_STARTDSP1 0x102F60
+#define DMAHOSTCURADDRREG_ENDDSP1 0x102F7C
+#define DMATANXCOUNTREG_STARTDSP1 0x102F80
+#define DMATANXCOUNTREG_ENDDSP1 0x102F9C
+#define DMATIMEBUGREG_STARTDSP1 0x102FA0
+#define DMATIMEBUGREG_ENDDSP1 0x102FAC
+#define DMACNTLMODFREG_STARTDSP1 0x102FA0
+#define DMACNTLMODFREG_ENDDSP1 0x102FAC
+
+#define DMAGLOBSTATSREGDSP1 0x102FEC
+#define DSP1XGPRAM_START 0x103000
+#define DSP1XGPRAM_END 0x1033FC
+#define DSP1YGPRAM_START 0x103400
+#define DSP1YGPRAM_END 0x1037FC
+
+
+
+#define AUDIORINGIPDSP2_START 0x104000
+#define AUDIORINGIPDSP2_END 0x1043FC
+#define AUDIORINGOPDSP2_START 0x104400
+#define AUDIORINGOPDSP2_END 0x1047FC
+#define AUDPARARINGIODSP2_START 0x104800
+#define AUDPARARINGIODSP2_END 0x104BFC
+#define DSP2LOCALHWREG_START 0x104C00
+#define DSP2LOCALHWREG_END 0x104C3C
+#define DSP2XYRAMAGINDEX_START 0x104C40
+#define DSP2XYRAMAGINDEX_END 0x104C5C
+#define DSP2XYRAMAGMDFR_START 0x104C60
+#define DSP2XYRAMAGMDFR_END 0x104C7C
+#define DSP2INTCONTLVEC_START 0x104C80
+#define DSP2INTCONTLVEC_END 0x104CD8
+#define HOSTINTFPORTADDRCONTDSP2 0x104D40
+#define HOSTINTFPORTDATADSP2 0x104D44
+#define TIME0PERENBDSP2 0x104D60
+#define TIME0COUNTERDSP2 0x104D64
+#define TIME1PERENBDSP2 0x104D68
+#define TIME1COUNTERDSP2 0x104D6C
+#define TIME2PERENBDSP2 0x104D70
+#define TIME2COUNTERDSP2 0x104D74
+#define TIME3PERENBDSP2 0x104D78
+#define TIME3COUNTERDSP2 0x104D7C
+#define XRAMINDOPERREFNOUP_STARTDSP2 0x104D80
+#define XRAMINDOPERREFNOUP_ENDDSP2 0x104D9C
+#define XRAMINDOPERREFUP_STARTDSP2 0x104DA0
+#define XRAMINDOPERREFUP_ENDDSP2 0x104DBC
+#define YRAMINDOPERREFNOUP_STARTDSP2 0x104DC0
+#define YRAMINDOPERREFNOUP_ENDDSP2 0x104DDC
+#define YRAMINDOPERREFUP_STARTDSP2 0x104DE0
+#define YRAMINDOPERREFUP_ENDDSP2 0x104DFC
+#define DSP2CONDCODE 0x104E00
+#define DSP2STACKFLAG 0x104E04
+#define DSP2PROGCOUNTSTACKPTREG 0x104E08
+#define DSP2PROGCOUNTSTACKDATAREG 0x104E0C
+#define DSP2CURLOOPADDRREG 0x104E10
+#define DSP2CURLOOPCOUNT 0x104E14
+#define DSP2TOPLOOPCOUNTSTACK 0x104E18
+#define DSP2TOPLOOPADDRSTACK 0x104E1C
+#define DSP2LOOPSTACKPTR 0x104E20
+#define DSP2STASSTACKDATAREG 0x104E24
+#define DSP2STASSTACKPTR 0x104E28
+#define DSP2PROGCOUNT 0x104E2C
+#define DSP2XYRAMBASE_START 0x104EA0
+#define DSP2XYRAMBASE_END 0x104EBC
+#define DSP2XYRAMLENG_START 0x104EC0
+#define DSP2XYRAMLENG_END 0x104EDC
+#define SEMAPHOREREGDSP2 0x104EE0
+#define DSP2INTCONTMASKREG 0x104EE4
+#define DSP2INTCONTPENDREG 0x104EE8
+#define DSP2INTCONTSERVINT 0x104EEC
+#define GPIODSP2 0x104EFC
+#define DMADSPBASEADDRREG_STARTDSP2 0x104F00
+#define DMADSPBASEADDRREG_ENDDSP2 0x104F1C
+#define DMAHOSTBASEADDRREG_STARTDSP2 0x104F20
+#define DMAHOSTBASEADDRREG_ENDDSP2 0x104F3C
+#define DMADSPCURADDRREG_STARTDSP2 0x104F40
+#define DMADSPCURADDRREG_ENDDSP2 0x104F5C
+#define DMAHOSTCURADDRREG_STARTDSP2 0x104F60
+#define DMAHOSTCURADDRREG_ENDDSP2 0x104F7C
+#define DMATANXCOUNTREG_STARTDSP2 0x104F80
+#define DMATANXCOUNTREG_ENDDSP2 0x104F9C
+#define DMATIMEBUGREG_STARTDSP2 0x104FA0
+#define DMATIMEBUGREG_ENDDSP2 0x104FAC
+#define DMACNTLMODFREG_STARTDSP2 0x104FA0
+#define DMACNTLMODFREG_ENDDSP2 0x104FAC
+
+#define DMAGLOBSTATSREGDSP2 0x104FEC
+#define DSP2XGPRAM_START 0x105000
+#define DSP2XGPRAM_END 0x1051FC
+#define DSP2YGPRAM_START 0x105800
+#define DSP2YGPRAM_END 0x1059FC
+
+
+
+#define AUDIORINGIPDSP3_START 0x106000
+#define AUDIORINGIPDSP3_END 0x1063FC
+#define AUDIORINGOPDSP3_START 0x106400
+#define AUDIORINGOPDSP3_END 0x1067FC
+#define AUDPARARINGIODSP3_START 0x106800
+#define AUDPARARINGIODSP3_END 0x106BFC
+#define DSP3LOCALHWREG_START 0x106C00
+#define DSP3LOCALHWREG_END 0x106C3C
+#define DSP3XYRAMAGINDEX_START 0x106C40
+#define DSP3XYRAMAGINDEX_END 0x106C5C
+#define DSP3XYRAMAGMDFR_START 0x106C60
+#define DSP3XYRAMAGMDFR_END 0x106C7C
+#define DSP3INTCONTLVEC_START 0x106C80
+#define DSP3INTCONTLVEC_END 0x106CD8
+#define HOSTINTFPORTADDRCONTDSP3 0x106D40
+#define HOSTINTFPORTDATADSP3 0x106D44
+#define TIME0PERENBDSP3 0x106D60
+#define TIME0COUNTERDSP3 0x106D64
+#define TIME1PERENBDSP3 0x106D68
+#define TIME1COUNTERDSP3 0x106D6C
+#define TIME2PERENBDSP3 0x106D70
+#define TIME2COUNTERDSP3 0x106D74
+#define TIME3PERENBDSP3 0x106D78
+#define TIME3COUNTERDSP3 0x106D7C
+#define XRAMINDOPERREFNOUP_STARTDSP3 0x106D80
+#define XRAMINDOPERREFNOUP_ENDDSP3 0x106D9C
+#define XRAMINDOPERREFUP_STARTDSP3 0x106DA0
+#define XRAMINDOPERREFUP_ENDDSP3 0x106DBC
+#define YRAMINDOPERREFNOUP_STARTDSP3 0x106DC0
+#define YRAMINDOPERREFNOUP_ENDDSP3 0x106DDC
+#define YRAMINDOPERREFUP_STARTDSP3 0x106DE0
+#define YRAMINDOPERREFUP_ENDDSP3 0x100DFC
+
+#define DSP3CONDCODE 0x106E00
+#define DSP3STACKFLAG 0x106E04
+#define DSP3PROGCOUNTSTACKPTREG 0x106E08
+#define DSP3PROGCOUNTSTACKDATAREG 0x106E0C
+#define DSP3CURLOOPADDRREG 0x106E10
+#define DSP3CURLOOPCOUNT 0x106E14
+#define DSP3TOPLOOPCOUNTSTACK 0x106E18
+#define DSP3TOPLOOPADDRSTACK 0x106E1C
+#define DSP3LOOPSTACKPTR 0x106E20
+#define DSP3STASSTACKDATAREG 0x106E24
+#define DSP3STASSTACKPTR 0x106E28
+#define DSP3PROGCOUNT 0x106E2C
+#define DSP3XYRAMBASE_START 0x106EA0
+#define DSP3XYRAMBASE_END 0x106EBC
+#define DSP3XYRAMLENG_START 0x106EC0
+#define DSP3XYRAMLENG_END 0x106EDC
+#define SEMAPHOREREGDSP3 0x106EE0
+#define DSP3INTCONTMASKREG 0x106EE4
+#define DSP3INTCONTPENDREG 0x106EE8
+#define DSP3INTCONTSERVINT 0x106EEC
+#define GPIODSP3 0x106EFC
+#define DMADSPBASEADDRREG_STARTDSP3 0x106F00
+#define DMADSPBASEADDRREG_ENDDSP3 0x106F1C
+#define DMAHOSTBASEADDRREG_STARTDSP3 0x106F20
+#define DMAHOSTBASEADDRREG_ENDDSP3 0x106F3C
+#define DMADSPCURADDRREG_STARTDSP3 0x106F40
+#define DMADSPCURADDRREG_ENDDSP3 0x106F5C
+#define DMAHOSTCURADDRREG_STARTDSP3 0x106F60
+#define DMAHOSTCURADDRREG_ENDDSP3 0x106F7C
+#define DMATANXCOUNTREG_STARTDSP3 0x106F80
+#define DMATANXCOUNTREG_ENDDSP3 0x106F9C
+#define DMATIMEBUGREG_STARTDSP3 0x106FA0
+#define DMATIMEBUGREG_ENDDSP3 0x106FAC
+#define DMACNTLMODFREG_STARTDSP3 0x106FA0
+#define DMACNTLMODFREG_ENDDSP3 0x106FAC
+
+#define DMAGLOBSTATSREGDSP3 0x106FEC
+#define DSP3XGPRAM_START 0x107000
+#define DSP3XGPRAM_END 0x1071FC
+#define DSP3YGPRAM_START 0x107800
+#define DSP3YGPRAM_END 0x1079FC
+
+/* end of DSP reg definitions */
+
+#define DSPAIMAP_START 0x108000
+#define DSPAIMAP_END 0x1083FC
+#define DSPPIMAP_START 0x108400
+#define DSPPIMAP_END 0x1087FC
+#define DSPPOMAP_START 0x108800
+#define DSPPOMAP_END 0x108BFC
+#define DSPPOCTL 0x108C00
+#define TKCTL_START 0x110000
+#define TKCTL_END 0x110FFC
+#define TKCC_START 0x111000
+#define TKCC_END 0x111FFC
+#define TKIMAP_START 0x112000
+#define TKIMAP_END 0x112FFC
+#define TKDCTR16 0x113000
+#define TKPB16 0x113004
+#define TKBS16 0x113008
+#define TKDCTR32 0x11300C
+#define TKPB32 0x113010
+#define TKBS32 0x113014
+#define ICDCTR16 0x113018
+#define ITBS16 0x11301C
+#define ICDCTR32 0x113020
+#define ITBS32 0x113024
+#define ITSTART 0x113028
+#define TKSQ 0x11302C
+
+#define TKSCCTL_START 0x114000
+#define TKSCCTL_END 0x11403C
+#define TKSCADR_START 0x114100
+#define TKSCADR_END 0x11413C
+#define TKSCDATAX_START 0x114800
+#define TKSCDATAX_END 0x1149FC
+#define TKPCDATAX_START 0x120000
+#define TKPCDATAX_END 0x12FFFC
+
+#define MALSA 0x130000
+#define MAPPHA 0x130004
+#define MAPPLA 0x130008
+#define MALSB 0x130010
+#define MAPPHB 0x130014
+#define MAPPLB 0x130018
+
+#define TANSPORTMAPABREGS_START 0x130020
+#define TANSPORTMAPABREGS_END 0x13A2FC
+
+#define PTPAHX 0x13B000
+#define PTPALX 0x13B004
+
+#define TANSPPAGETABLEPHYADDR015_START 0x13B008
+#define TANSPPAGETABLEPHYADDR015_END 0x13B07C
+#define TRNQADRX_START 0x13B100
+#define TRNQADRX_END 0x13B13C
+#define TRNQTIMX_START 0x13B200
+#define TRNQTIMX_END 0x13B23C
+#define TRNQAPARMX_START 0x13B300
+#define TRNQAPARMX_END 0x13B33C
+
+#define TRNQCNT 0x13B400
+#define TRNCTL 0x13B404
+#define TRNIS 0x13B408
+#define TRNCURTS 0x13B40C
+
+#define AMOP_START 0x140000
+#define AMOPLO 0x140000
+#define AMOPHI 0x140004
+#define AMOP_END 0x147FFC
+#define PMOP_START 0x148000
+#define PMOPLO 0x148000
+#define PMOPHI 0x148004
+#define PMOP_END 0x14FFFC
+#define PCURR_START 0x150000
+#define PCURR_END 0x153FFC
+#define PTRAG_START 0x154000
+#define PTRAG_END 0x157FFC
+#define PSR_START 0x158000
+#define PSR_END 0x15BFFC
+
+#define PFSTAT4SEG_START 0x160000
+#define PFSTAT4SEG_END 0x160BFC
+#define PFSTAT2SEG_START 0x160C00
+#define PFSTAT2SEG_END 0x1617FC
+#define PFTARG4SEG_START 0x164000
+#define PFTARG4SEG_END 0x164BFC
+#define PFTARG2SEG_START 0x164C00
+#define PFTARG2SEG_END 0x1657FC
+#define PFSR4SEG_START 0x168000
+#define PFSR4SEG_END 0x168BFC
+#define PFSR2SEG_START 0x168C00
+#define PFSR2SEG_END 0x1697FC
+#define PCURRMS4SEG_START 0x16C000
+#define PCURRMS4SEG_END 0x16CCFC
+#define PCURRMS2SEG_START 0x16CC00
+#define PCURRMS2SEG_END 0x16D7FC
+#define PTARGMS4SEG_START 0x170000
+#define PTARGMS4SEG_END 0x172FFC
+#define PTARGMS2SEG_START 0x173000
+#define PTARGMS2SEG_END 0x1747FC
+#define PSRMS4SEG_START 0x170000
+#define PSRMS4SEG_END 0x172FFC
+#define PSRMS2SEG_START 0x173000
+#define PSRMS2SEG_END 0x1747FC
+
+#define PRING_LO_START 0x190000
+#define PRING_LO_END 0x193FFC
+#define PRING_HI_START 0x194000
+#define PRING_HI_END 0x197FFC
+#define PRING_LO_HI_START 0x198000
+#define PRING_LO_HI 0x198000
+#define PRING_LO_HI_END 0x19BFFC
+
+#define PINTFIFO 0x1A0000
+#define SRCCTL 0x1B0000
+#define SRCCCR 0x1B0004
+#define SRCIMAP 0x1B0008
+#define SRCODDC 0x1B000C
+#define SRCCA 0x1B0010
+#define SRCCF 0x1B0014
+#define SRCSA 0x1B0018
+#define SRCLA 0x1B001C
+#define SRCCTLSWR 0x1B0020
+
+/* SRC HERE */
+#define SRCALBA 0x1B002C
+#define SRCMCTL 0x1B012C
+#define SRCCERR 0x1B022C
+#define SRCITB 0x1B032C
+#define SRCIPM 0x1B082C
+#define SRCIP 0x1B102C
+#define SRCENBSTAT 0x1B202C
+#define SRCENBLO 0x1B212C
+#define SRCENBHI 0x1B222C
+#define SRCENBS 0x1B232C
+#define SRCENB 0x1B282C
+#define SRCENB07 0x1B282C
+#define SRCENBS07 0x1B302C
+
+#define SRCDN0Z 0x1B0030
+#define SRCDN0Z0 0x1B0030
+#define SRCDN0Z1 0x1B0034
+#define SRCDN0Z2 0x1B0038
+#define SRCDN0Z3 0x1B003C
+#define SRCDN1Z 0x1B0040
+#define SRCDN1Z0 0x1B0040
+#define SRCDN1Z1 0x1B0044
+#define SRCDN1Z2 0x1B0048
+#define SRCDN1Z3 0x1B004C
+#define SRCDN1Z4 0x1B0050
+#define SRCDN1Z5 0x1B0054
+#define SRCDN1Z6 0x1B0058
+#define SRCDN1Z7 0x1B005C
+#define SRCUPZ 0x1B0060
+#define SRCUPZ0 0x1B0060
+#define SRCUPZ1 0x1B0064
+#define SRCUPZ2 0x1B0068
+#define SRCUPZ3 0x1B006C
+#define SRCUPZ4 0x1B0070
+#define SRCUPZ5 0x1B0074
+#define SRCUPZ6 0x1B0078
+#define SRCUPZ7 0x1B007C
+#define SRCCD0 0x1B0080
+#define SRCCD1 0x1B0084
+#define SRCCD2 0x1B0088
+#define SRCCD3 0x1B008C
+#define SRCCD4 0x1B0090
+#define SRCCD5 0x1B0094
+#define SRCCD6 0x1B0098
+#define SRCCD7 0x1B009C
+#define SRCCD8 0x1B00A0
+#define SRCCD9 0x1B00A4
+#define SRCCDA 0x1B00A8
+#define SRCCDB 0x1B00AC
+#define SRCCDC 0x1B00B0
+#define SRCCDD 0x1B00B4
+#define SRCCDE 0x1B00B8
+#define SRCCDF 0x1B00BC
+#define SRCCD10 0x1B00C0
+#define SRCCD11 0x1B00C4
+#define SRCCD12 0x1B00C8
+#define SRCCD13 0x1B00CC
+#define SRCCD14 0x1B00D0
+#define SRCCD15 0x1B00D4
+#define SRCCD16 0x1B00D8
+#define SRCCD17 0x1B00DC
+#define SRCCD18 0x1B00E0
+#define SRCCD19 0x1B00E4
+#define SRCCD1A 0x1B00E8
+#define SRCCD1B 0x1B00EC
+#define SRCCD1C 0x1B00F0
+#define SRCCD1D 0x1B00F4
+#define SRCCD1E 0x1B00F8
+#define SRCCD1F 0x1B00FC
+
+#define SRCCONTRBLOCK_START 0x1B0100
+#define SRCCONTRBLOCK_END 0x1BFFFC
+#define FILTOP_START 0x1C0000
+#define FILTOP_END 0x1C05FC
+#define FILTIMAP_START 0x1C0800
+#define FILTIMAP_END 0x1C0DFC
+#define FILTZ1_START 0x1C1000
+#define FILTZ1_END 0x1C15FC
+#define FILTZ2_START 0x1C1800
+#define FILTZ2_END 0x1C1DFC
+#define DAOIMAP_START 0x1C5000
+#define DAOIMAP 0x1C5000
+#define DAOIMAP_END 0x1C5124
+
+#define AC97D 0x1C5400
+#define AC97A 0x1C5404
+#define AC97CTL 0x1C5408
+#define I2SCTL 0x1C5420
+
+#define SPOS 0x1C5440
+#define SPOSA 0x1C5440
+#define SPOSB 0x1C5444
+#define SPOSC 0x1C5448
+#define SPOSD 0x1C544C
+
+#define SPISA 0x1C5450
+#define SPISB 0x1C5454
+#define SPISC 0x1C5458
+#define SPISD 0x1C545C
+
+#define SPFSCTL 0x1C5460
+
+#define SPFS0 0x1C5468
+#define SPFS1 0x1C546C
+#define SPFS2 0x1C5470
+#define SPFS3 0x1C5474
+#define SPFS4 0x1C5478
+#define SPFS5 0x1C547C
+
+#define SPOCTL 0x1C5480
+#define SPICTL 0x1C5484
+#define SPISTS 0x1C5488
+#define SPINTP 0x1C548C
+#define SPINTE 0x1C5490
+#define SPUTCTLAB 0x1C5494
+#define SPUTCTLCD 0x1C5498
+
+#define SRTSPA 0x1C54C0
+#define SRTSPB 0x1C54C4
+#define SRTSPC 0x1C54C8
+#define SRTSPD 0x1C54CC
+
+#define SRTSCTL 0x1C54D0
+#define SRTSCTLA 0x1C54D0
+#define SRTSCTLB 0x1C54D4
+#define SRTSCTLC 0x1C54D8
+#define SRTSCTLD 0x1C54DC
+
+#define SRTI2S 0x1C54E0
+#define SRTICTL 0x1C54F0
+
+#define WC 0x1C6000
+#define TIMR 0x1C6004
+# define TIMR_IE (1<<15)
+# define TIMR_IP (1<<14)
+
+#define GIP 0x1C6010
+#define GIE 0x1C6014
+#define DIE 0x1C6018
+#define DIC 0x1C601C
+#define GPIO 0x1C6020
+#define GPIOCTL 0x1C6024
+#define GPIP 0x1C6028
+#define GPIE 0x1C602C
+#define DSPINT0 0x1C6030
+#define DSPEIOC 0x1C6034
+#define MUADAT 0x1C6040
+#define MUACMD 0x1C6044
+#define MUASTAT 0x1C6044
+#define MUBDAT 0x1C6048
+#define MUBCMD 0x1C604C
+#define MUBSTAT 0x1C604C
+#define UARTCMA 0x1C6050
+#define UARTCMB 0x1C6054
+#define UARTIP 0x1C6058
+#define UARTIE 0x1C605C
+#define PLLCTL 0x1C6060
+#define PLLDCD 0x1C6064
+#define GCTL 0x1C6070
+#define ID0 0x1C6080
+#define ID1 0x1C6084
+#define ID2 0x1C6088
+#define ID3 0x1C608C
+#define SDRCTL 0x1C7000
+
+
+#define I2SA_L 0x0L
+#define I2SA_R 0x1L
+#define I2SB_L 0x8L
+#define I2SB_R 0x9L
+#define I2SC_L 0x10L
+#define I2SC_R 0x11L
+#define I2SD_L 0x18L
+#define I2SD_R 0x19L
+
+#endif /* CT20K1REG_H */
diff --git a/sound/pci/ctxfi/ct20k2reg.h b/sound/pci/ctxfi/ct20k2reg.h
new file mode 100644
index 000000000..02f67828e
--- /dev/null
+++ b/sound/pci/ctxfi/ct20k2reg.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ */
+
+#ifndef _20K2REGISTERS_H_
+#define _20K2REGISTERS_H_
+
+
+/* Timer Registers */
+#define WC 0x1b7000
+#define TIMR 0x1b7004
+# define TIMR_IE (1<<15)
+# define TIMR_IP (1<<14)
+#define GIP 0x1b7010
+#define GIE 0x1b7014
+
+/* I2C Registers */
+#define I2C_IF_ADDRESS 0x1B9000
+#define I2C_IF_WDATA 0x1B9004
+#define I2C_IF_RDATA 0x1B9008
+#define I2C_IF_STATUS 0x1B900C
+#define I2C_IF_WLOCK 0x1B9010
+
+/* Global Control Registers */
+#define GLOBAL_CNTL_GCTL 0x1B7090
+
+/* PLL Registers */
+#define PLL_CTL 0x1B7080
+#define PLL_STAT 0x1B7084
+#define PLL_ENB 0x1B7088
+
+/* SRC Registers */
+#define SRC_CTL 0x1A0000 /* 0x1A0000 + (256 * Chn) */
+#define SRC_CCR 0x1A0004 /* 0x1A0004 + (256 * Chn) */
+#define SRC_IMAP 0x1A0008 /* 0x1A0008 + (256 * Chn) */
+#define SRC_CA 0x1A0010 /* 0x1A0010 + (256 * Chn) */
+#define SRC_CF 0x1A0014 /* 0x1A0014 + (256 * Chn) */
+#define SRC_SA 0x1A0018 /* 0x1A0018 + (256 * Chn) */
+#define SRC_LA 0x1A001C /* 0x1A001C + (256 * Chn) */
+#define SRC_CTLSWR 0x1A0020 /* 0x1A0020 + (256 * Chn) */
+#define SRC_CD 0x1A0080 /* 0x1A0080 + (256 * Chn) + (4 * Regn) */
+#define SRC_MCTL 0x1A012C
+#define SRC_IP 0x1A102C /* 0x1A102C + (256 * Regn) */
+#define SRC_ENB 0x1A282C /* 0x1A282C + (256 * Regn) */
+#define SRC_ENBSTAT 0x1A202C
+#define SRC_ENBSA 0x1A232C
+#define SRC_DN0Z 0x1A0030
+#define SRC_DN1Z 0x1A0040
+#define SRC_UPZ 0x1A0060
+
+/* GPIO Registers */
+#define GPIO_DATA 0x1B7020
+#define GPIO_CTRL 0x1B7024
+#define GPIO_EXT_DATA 0x1B70A0
+
+/* Virtual memory registers */
+#define VMEM_PTPAL 0x1C6300 /* 0x1C6300 + (16 * Chn) */
+#define VMEM_PTPAH 0x1C6304 /* 0x1C6304 + (16 * Chn) */
+#define VMEM_CTL 0x1C7000
+
+/* Transport Registers */
+#define TRANSPORT_ENB 0x1B6000
+#define TRANSPORT_CTL 0x1B6004
+#define TRANSPORT_INT 0x1B6008
+
+/* Audio IO */
+#define AUDIO_IO_AIM 0x1B5000 /* 0x1B5000 + (0x04 * Chn) */
+#define AUDIO_IO_TX_CTL 0x1B5400 /* 0x1B5400 + (0x40 * Chn) */
+#define AUDIO_IO_TX_CSTAT_L 0x1B5408 /* 0x1B5408 + (0x40 * Chn) */
+#define AUDIO_IO_TX_CSTAT_H 0x1B540C /* 0x1B540C + (0x40 * Chn) */
+#define AUDIO_IO_RX_CTL 0x1B5410 /* 0x1B5410 + (0x40 * Chn) */
+#define AUDIO_IO_RX_SRT_CTL 0x1B5420 /* 0x1B5420 + (0x40 * Chn) */
+#define AUDIO_IO_MCLK 0x1B5600
+#define AUDIO_IO_TX_BLRCLK 0x1B5604
+#define AUDIO_IO_RX_BLRCLK 0x1B5608
+
+/* Mixer */
+#define MIXER_AMOPLO 0x130000 /* 0x130000 + (8 * Chn) [4095 : 0] */
+#define MIXER_AMOPHI 0x130004 /* 0x130004 + (8 * Chn) [4095 : 0] */
+#define MIXER_PRING_LO_HI 0x188000 /* 0x188000 + (4 * Chn) [4095 : 0] */
+#define MIXER_PMOPLO 0x138000 /* 0x138000 + (8 * Chn) [4095 : 0] */
+#define MIXER_PMOPHI 0x138004 /* 0x138004 + (8 * Chn) [4095 : 0] */
+#define MIXER_AR_ENABLE 0x19000C
+
+#endif
diff --git a/sound/pci/ctxfi/ctamixer.c b/sound/pci/ctxfi/ctamixer.c
new file mode 100644
index 000000000..d074727c3
--- /dev/null
+++ b/sound/pci/ctxfi/ctamixer.c
@@ -0,0 +1,484 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctamixer.c
+ *
+ * @Brief
+ * This file contains the implementation of the Audio Mixer
+ * resource management object.
+ *
+ * @Author Liu Chun
+ * @Date May 21 2008
+ */
+
+#include "ctamixer.h"
+#include "cthardware.h"
+#include <linux/slab.h>
+
+#define AMIXER_RESOURCE_NUM 256
+#define SUM_RESOURCE_NUM 256
+
+#define AMIXER_Y_IMMEDIATE 1
+
+#define BLANK_SLOT 4094
+
+static void amixer_master(struct rsc *rsc)
+{
+ rsc->conj = 0;
+ rsc->idx = container_of(rsc, struct amixer, rsc)->idx[0];
+}
+
+static void amixer_next_conj(struct rsc *rsc)
+{
+ rsc->conj++;
+}
+
+static int amixer_index(const struct rsc *rsc)
+{
+ return container_of(rsc, struct amixer, rsc)->idx[rsc->conj];
+}
+
+static int amixer_output_slot(const struct rsc *rsc)
+{
+ return (amixer_index(rsc) << 4) + 0x4;
+}
+
+static const struct rsc_ops amixer_basic_rsc_ops = {
+ .master = amixer_master,
+ .next_conj = amixer_next_conj,
+ .index = amixer_index,
+ .output_slot = amixer_output_slot,
+};
+
+static int amixer_set_input(struct amixer *amixer, struct rsc *rsc)
+{
+ struct hw *hw;
+
+ hw = amixer->rsc.hw;
+ hw->amixer_set_mode(amixer->rsc.ctrl_blk, AMIXER_Y_IMMEDIATE);
+ amixer->input = rsc;
+ if (!rsc)
+ hw->amixer_set_x(amixer->rsc.ctrl_blk, BLANK_SLOT);
+ else
+ hw->amixer_set_x(amixer->rsc.ctrl_blk,
+ rsc->ops->output_slot(rsc));
+
+ return 0;
+}
+
+/* y is a 14-bit immediate constant */
+static int amixer_set_y(struct amixer *amixer, unsigned int y)
+{
+ struct hw *hw;
+
+ hw = amixer->rsc.hw;
+ hw->amixer_set_y(amixer->rsc.ctrl_blk, y);
+
+ return 0;
+}
+
+static int amixer_set_invalid_squash(struct amixer *amixer, unsigned int iv)
+{
+ struct hw *hw;
+
+ hw = amixer->rsc.hw;
+ hw->amixer_set_iv(amixer->rsc.ctrl_blk, iv);
+
+ return 0;
+}
+
+static int amixer_set_sum(struct amixer *amixer, struct sum *sum)
+{
+ struct hw *hw;
+
+ hw = amixer->rsc.hw;
+ amixer->sum = sum;
+ if (!sum) {
+ hw->amixer_set_se(amixer->rsc.ctrl_blk, 0);
+ } else {
+ hw->amixer_set_se(amixer->rsc.ctrl_blk, 1);
+ hw->amixer_set_sadr(amixer->rsc.ctrl_blk,
+ sum->rsc.ops->index(&sum->rsc));
+ }
+
+ return 0;
+}
+
+static int amixer_commit_write(struct amixer *amixer)
+{
+ struct hw *hw;
+ unsigned int index;
+ int i;
+ struct rsc *input;
+ struct sum *sum;
+
+ hw = amixer->rsc.hw;
+ input = amixer->input;
+ sum = amixer->sum;
+
+ /* Program master and conjugate resources */
+ amixer->rsc.ops->master(&amixer->rsc);
+ if (input)
+ input->ops->master(input);
+
+ if (sum)
+ sum->rsc.ops->master(&sum->rsc);
+
+ for (i = 0; i < amixer->rsc.msr; i++) {
+ hw->amixer_set_dirty_all(amixer->rsc.ctrl_blk);
+ if (input) {
+ hw->amixer_set_x(amixer->rsc.ctrl_blk,
+ input->ops->output_slot(input));
+ input->ops->next_conj(input);
+ }
+ if (sum) {
+ hw->amixer_set_sadr(amixer->rsc.ctrl_blk,
+ sum->rsc.ops->index(&sum->rsc));
+ sum->rsc.ops->next_conj(&sum->rsc);
+ }
+ index = amixer->rsc.ops->output_slot(&amixer->rsc);
+ hw->amixer_commit_write(hw, index, amixer->rsc.ctrl_blk);
+ amixer->rsc.ops->next_conj(&amixer->rsc);
+ }
+ amixer->rsc.ops->master(&amixer->rsc);
+ if (input)
+ input->ops->master(input);
+
+ if (sum)
+ sum->rsc.ops->master(&sum->rsc);
+
+ return 0;
+}
+
+static int amixer_commit_raw_write(struct amixer *amixer)
+{
+ struct hw *hw;
+ unsigned int index;
+
+ hw = amixer->rsc.hw;
+ index = amixer->rsc.ops->output_slot(&amixer->rsc);
+ hw->amixer_commit_write(hw, index, amixer->rsc.ctrl_blk);
+
+ return 0;
+}
+
+static int amixer_get_y(struct amixer *amixer)
+{
+ struct hw *hw;
+
+ hw = amixer->rsc.hw;
+ return hw->amixer_get_y(amixer->rsc.ctrl_blk);
+}
+
+static int amixer_setup(struct amixer *amixer, struct rsc *input,
+ unsigned int scale, struct sum *sum)
+{
+ amixer_set_input(amixer, input);
+ amixer_set_y(amixer, scale);
+ amixer_set_sum(amixer, sum);
+ amixer_commit_write(amixer);
+ return 0;
+}
+
+static const struct amixer_rsc_ops amixer_ops = {
+ .set_input = amixer_set_input,
+ .set_invalid_squash = amixer_set_invalid_squash,
+ .set_scale = amixer_set_y,
+ .set_sum = amixer_set_sum,
+ .commit_write = amixer_commit_write,
+ .commit_raw_write = amixer_commit_raw_write,
+ .setup = amixer_setup,
+ .get_scale = amixer_get_y,
+};
+
+static int amixer_rsc_init(struct amixer *amixer,
+ const struct amixer_desc *desc,
+ struct amixer_mgr *mgr)
+{
+ int err;
+
+ err = rsc_init(&amixer->rsc, amixer->idx[0],
+ AMIXER, desc->msr, mgr->mgr.hw);
+ if (err)
+ return err;
+
+ /* Set amixer specific operations */
+ amixer->rsc.ops = &amixer_basic_rsc_ops;
+ amixer->ops = &amixer_ops;
+ amixer->input = NULL;
+ amixer->sum = NULL;
+
+ amixer_setup(amixer, NULL, 0, NULL);
+
+ return 0;
+}
+
+static int amixer_rsc_uninit(struct amixer *amixer)
+{
+ amixer_setup(amixer, NULL, 0, NULL);
+ rsc_uninit(&amixer->rsc);
+ amixer->ops = NULL;
+ amixer->input = NULL;
+ amixer->sum = NULL;
+ return 0;
+}
+
+static int get_amixer_rsc(struct amixer_mgr *mgr,
+ const struct amixer_desc *desc,
+ struct amixer **ramixer)
+{
+ int err, i;
+ unsigned int idx;
+ struct amixer *amixer;
+ unsigned long flags;
+
+ *ramixer = NULL;
+
+ /* Allocate mem for amixer resource */
+ amixer = kzalloc(sizeof(*amixer), GFP_KERNEL);
+ if (!amixer)
+ return -ENOMEM;
+
+ /* Check whether there are sufficient
+ * amixer resources to meet request. */
+ err = 0;
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < desc->msr; i++) {
+ err = mgr_get_resource(&mgr->mgr, 1, &idx);
+ if (err)
+ break;
+
+ amixer->idx[i] = idx;
+ }
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ if (err) {
+ dev_err(mgr->card->dev,
+ "Can't meet AMIXER resource request!\n");
+ goto error;
+ }
+
+ err = amixer_rsc_init(amixer, desc, mgr);
+ if (err)
+ goto error;
+
+ *ramixer = amixer;
+
+ return 0;
+
+error:
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i--; i >= 0; i--)
+ mgr_put_resource(&mgr->mgr, 1, amixer->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ kfree(amixer);
+ return err;
+}
+
+static int put_amixer_rsc(struct amixer_mgr *mgr, struct amixer *amixer)
+{
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < amixer->rsc.msr; i++)
+ mgr_put_resource(&mgr->mgr, 1, amixer->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ amixer_rsc_uninit(amixer);
+ kfree(amixer);
+
+ return 0;
+}
+
+int amixer_mgr_create(struct hw *hw, struct amixer_mgr **ramixer_mgr)
+{
+ int err;
+ struct amixer_mgr *amixer_mgr;
+
+ *ramixer_mgr = NULL;
+ amixer_mgr = kzalloc(sizeof(*amixer_mgr), GFP_KERNEL);
+ if (!amixer_mgr)
+ return -ENOMEM;
+
+ err = rsc_mgr_init(&amixer_mgr->mgr, AMIXER, AMIXER_RESOURCE_NUM, hw);
+ if (err)
+ goto error;
+
+ spin_lock_init(&amixer_mgr->mgr_lock);
+
+ amixer_mgr->get_amixer = get_amixer_rsc;
+ amixer_mgr->put_amixer = put_amixer_rsc;
+ amixer_mgr->card = hw->card;
+
+ *ramixer_mgr = amixer_mgr;
+
+ return 0;
+
+error:
+ kfree(amixer_mgr);
+ return err;
+}
+
+int amixer_mgr_destroy(struct amixer_mgr *amixer_mgr)
+{
+ rsc_mgr_uninit(&amixer_mgr->mgr);
+ kfree(amixer_mgr);
+ return 0;
+}
+
+/* SUM resource management */
+
+static void sum_master(struct rsc *rsc)
+{
+ rsc->conj = 0;
+ rsc->idx = container_of(rsc, struct sum, rsc)->idx[0];
+}
+
+static void sum_next_conj(struct rsc *rsc)
+{
+ rsc->conj++;
+}
+
+static int sum_index(const struct rsc *rsc)
+{
+ return container_of(rsc, struct sum, rsc)->idx[rsc->conj];
+}
+
+static int sum_output_slot(const struct rsc *rsc)
+{
+ return (sum_index(rsc) << 4) + 0xc;
+}
+
+static const struct rsc_ops sum_basic_rsc_ops = {
+ .master = sum_master,
+ .next_conj = sum_next_conj,
+ .index = sum_index,
+ .output_slot = sum_output_slot,
+};
+
+static int sum_rsc_init(struct sum *sum,
+ const struct sum_desc *desc,
+ struct sum_mgr *mgr)
+{
+ int err;
+
+ err = rsc_init(&sum->rsc, sum->idx[0], SUM, desc->msr, mgr->mgr.hw);
+ if (err)
+ return err;
+
+ sum->rsc.ops = &sum_basic_rsc_ops;
+
+ return 0;
+}
+
+static int sum_rsc_uninit(struct sum *sum)
+{
+ rsc_uninit(&sum->rsc);
+ return 0;
+}
+
+static int get_sum_rsc(struct sum_mgr *mgr,
+ const struct sum_desc *desc,
+ struct sum **rsum)
+{
+ int err, i;
+ unsigned int idx;
+ struct sum *sum;
+ unsigned long flags;
+
+ *rsum = NULL;
+
+ /* Allocate mem for sum resource */
+ sum = kzalloc(sizeof(*sum), GFP_KERNEL);
+ if (!sum)
+ return -ENOMEM;
+
+ /* Check whether there are sufficient sum resources to meet request. */
+ err = 0;
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < desc->msr; i++) {
+ err = mgr_get_resource(&mgr->mgr, 1, &idx);
+ if (err)
+ break;
+
+ sum->idx[i] = idx;
+ }
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ if (err) {
+ dev_err(mgr->card->dev,
+ "Can't meet SUM resource request!\n");
+ goto error;
+ }
+
+ err = sum_rsc_init(sum, desc, mgr);
+ if (err)
+ goto error;
+
+ *rsum = sum;
+
+ return 0;
+
+error:
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i--; i >= 0; i--)
+ mgr_put_resource(&mgr->mgr, 1, sum->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ kfree(sum);
+ return err;
+}
+
+static int put_sum_rsc(struct sum_mgr *mgr, struct sum *sum)
+{
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < sum->rsc.msr; i++)
+ mgr_put_resource(&mgr->mgr, 1, sum->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ sum_rsc_uninit(sum);
+ kfree(sum);
+
+ return 0;
+}
+
+int sum_mgr_create(struct hw *hw, struct sum_mgr **rsum_mgr)
+{
+ int err;
+ struct sum_mgr *sum_mgr;
+
+ *rsum_mgr = NULL;
+ sum_mgr = kzalloc(sizeof(*sum_mgr), GFP_KERNEL);
+ if (!sum_mgr)
+ return -ENOMEM;
+
+ err = rsc_mgr_init(&sum_mgr->mgr, SUM, SUM_RESOURCE_NUM, hw);
+ if (err)
+ goto error;
+
+ spin_lock_init(&sum_mgr->mgr_lock);
+
+ sum_mgr->get_sum = get_sum_rsc;
+ sum_mgr->put_sum = put_sum_rsc;
+ sum_mgr->card = hw->card;
+
+ *rsum_mgr = sum_mgr;
+
+ return 0;
+
+error:
+ kfree(sum_mgr);
+ return err;
+}
+
+int sum_mgr_destroy(struct sum_mgr *sum_mgr)
+{
+ rsc_mgr_uninit(&sum_mgr->mgr);
+ kfree(sum_mgr);
+ return 0;
+}
+
diff --git a/sound/pci/ctxfi/ctamixer.h b/sound/pci/ctxfi/ctamixer.h
new file mode 100644
index 000000000..4498e6139
--- /dev/null
+++ b/sound/pci/ctxfi/ctamixer.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctamixer.h
+ *
+ * @Brief
+ * This file contains the definition of the Audio Mixer
+ * resource management object.
+ *
+ * @Author Liu Chun
+ * @Date May 21 2008
+ */
+
+#ifndef CTAMIXER_H
+#define CTAMIXER_H
+
+#include "ctresource.h"
+#include <linux/spinlock.h>
+#include <sound/core.h>
+
+/* Define the descriptor of a summation node resource */
+struct sum {
+ struct rsc rsc; /* Basic resource info */
+ unsigned char idx[8];
+};
+
+/* Define sum resource request description info */
+struct sum_desc {
+ unsigned int msr;
+};
+
+struct sum_mgr {
+ struct rsc_mgr mgr; /* Basic resource manager info */
+ struct snd_card *card; /* pointer to this card */
+ spinlock_t mgr_lock;
+
+ /* request one sum resource */
+ int (*get_sum)(struct sum_mgr *mgr,
+ const struct sum_desc *desc, struct sum **rsum);
+ /* return one sum resource */
+ int (*put_sum)(struct sum_mgr *mgr, struct sum *sum);
+};
+
+/* Constructor and destructor of daio resource manager */
+int sum_mgr_create(struct hw *hw, struct sum_mgr **rsum_mgr);
+int sum_mgr_destroy(struct sum_mgr *sum_mgr);
+
+/* Define the descriptor of a amixer resource */
+struct amixer_rsc_ops;
+
+struct amixer {
+ struct rsc rsc; /* Basic resource info */
+ unsigned char idx[8];
+ struct rsc *input; /* pointer to a resource acting as source */
+ struct sum *sum; /* Put amixer output to this summation node */
+ const struct amixer_rsc_ops *ops; /* AMixer specific operations */
+};
+
+struct amixer_rsc_ops {
+ int (*set_input)(struct amixer *amixer, struct rsc *rsc);
+ int (*set_scale)(struct amixer *amixer, unsigned int scale);
+ int (*set_invalid_squash)(struct amixer *amixer, unsigned int iv);
+ int (*set_sum)(struct amixer *amixer, struct sum *sum);
+ int (*commit_write)(struct amixer *amixer);
+ /* Only for interleaved recording */
+ int (*commit_raw_write)(struct amixer *amixer);
+ int (*setup)(struct amixer *amixer, struct rsc *input,
+ unsigned int scale, struct sum *sum);
+ int (*get_scale)(struct amixer *amixer);
+};
+
+/* Define amixer resource request description info */
+struct amixer_desc {
+ unsigned int msr;
+};
+
+struct amixer_mgr {
+ struct rsc_mgr mgr; /* Basic resource manager info */
+ struct snd_card *card; /* pointer to this card */
+ spinlock_t mgr_lock;
+
+ /* request one amixer resource */
+ int (*get_amixer)(struct amixer_mgr *mgr,
+ const struct amixer_desc *desc,
+ struct amixer **ramixer);
+ /* return one amixer resource */
+ int (*put_amixer)(struct amixer_mgr *mgr, struct amixer *amixer);
+};
+
+/* Constructor and destructor of amixer resource manager */
+int amixer_mgr_create(struct hw *hw, struct amixer_mgr **ramixer_mgr);
+int amixer_mgr_destroy(struct amixer_mgr *amixer_mgr);
+
+#endif /* CTAMIXER_H */
diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c
new file mode 100644
index 000000000..fbdb8a3d5
--- /dev/null
+++ b/sound/pci/ctxfi/ctatc.c
@@ -0,0 +1,1748 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctatc.c
+ *
+ * @Brief
+ * This file contains the implementation of the device resource management
+ * object.
+ *
+ * @Author Liu Chun
+ * @Date Mar 28 2008
+ */
+
+#include "ctatc.h"
+#include "ctpcm.h"
+#include "ctmixer.h"
+#include "ctsrc.h"
+#include "ctamixer.h"
+#include "ctdaio.h"
+#include "cttimer.h"
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/asoundef.h>
+
+#define MONO_SUM_SCALE 0x19a8 /* 2^(-0.5) in 14-bit floating format */
+#define MAX_MULTI_CHN 8
+
+#define IEC958_DEFAULT_CON ((IEC958_AES0_NONAUDIO \
+ | IEC958_AES0_CON_NOT_COPYRIGHT) \
+ | ((IEC958_AES1_CON_MIXER \
+ | IEC958_AES1_CON_ORIGINAL) << 8) \
+ | (0x10 << 16) \
+ | ((IEC958_AES3_CON_FS_48000) << 24))
+
+static const struct snd_pci_quirk subsys_20k1_list[] = {
+ SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0021, "SB046x", CTSB046X),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0022, "SB055x", CTSB055X),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x002f, "SB055x", CTSB055X),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0029, "SB073x", CTSB073X),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0031, "SB073x", CTSB073X),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_CREATIVE, 0xf000, 0x6000,
+ "UAA", CTUAA),
+ { } /* terminator */
+};
+
+static const struct snd_pci_quirk subsys_20k2_list[] = {
+ SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB0760,
+ "SB0760", CTSB0760),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB1270,
+ "SB1270", CTSB1270),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB08801,
+ "SB0880", CTSB0880),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB08802,
+ "SB0880", CTSB0880),
+ SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB08803,
+ "SB0880", CTSB0880),
+ SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_CREATIVE, 0xf000,
+ PCI_SUBDEVICE_ID_CREATIVE_HENDRIX, "HENDRIX",
+ CTHENDRIX),
+ { } /* terminator */
+};
+
+static const char *ct_subsys_name[NUM_CTCARDS] = {
+ /* 20k1 models */
+ [CTSB046X] = "SB046x",
+ [CTSB055X] = "SB055x",
+ [CTSB073X] = "SB073x",
+ [CTUAA] = "UAA",
+ [CT20K1_UNKNOWN] = "Unknown",
+ /* 20k2 models */
+ [CTSB0760] = "SB076x",
+ [CTHENDRIX] = "Hendrix",
+ [CTSB0880] = "SB0880",
+ [CTSB1270] = "SB1270",
+ [CT20K2_UNKNOWN] = "Unknown",
+};
+
+static struct {
+ int (*create)(struct ct_atc *atc,
+ enum CTALSADEVS device, const char *device_name);
+ int (*destroy)(void *alsa_dev);
+ const char *public_name;
+} alsa_dev_funcs[NUM_CTALSADEVS] = {
+ [FRONT] = { .create = ct_alsa_pcm_create,
+ .destroy = NULL,
+ .public_name = "Front/WaveIn"},
+ [SURROUND] = { .create = ct_alsa_pcm_create,
+ .destroy = NULL,
+ .public_name = "Surround"},
+ [CLFE] = { .create = ct_alsa_pcm_create,
+ .destroy = NULL,
+ .public_name = "Center/LFE"},
+ [SIDE] = { .create = ct_alsa_pcm_create,
+ .destroy = NULL,
+ .public_name = "Side"},
+ [IEC958] = { .create = ct_alsa_pcm_create,
+ .destroy = NULL,
+ .public_name = "IEC958 Non-audio"},
+
+ [MIXER] = { .create = ct_alsa_mix_create,
+ .destroy = NULL,
+ .public_name = "Mixer"}
+};
+
+typedef int (*create_t)(struct hw *, void **);
+typedef int (*destroy_t)(void *);
+
+static struct {
+ int (*create)(struct hw *hw, void **rmgr);
+ int (*destroy)(void *mgr);
+} rsc_mgr_funcs[NUM_RSCTYP] = {
+ [SRC] = { .create = (create_t)src_mgr_create,
+ .destroy = (destroy_t)src_mgr_destroy },
+ [SRCIMP] = { .create = (create_t)srcimp_mgr_create,
+ .destroy = (destroy_t)srcimp_mgr_destroy },
+ [AMIXER] = { .create = (create_t)amixer_mgr_create,
+ .destroy = (destroy_t)amixer_mgr_destroy },
+ [SUM] = { .create = (create_t)sum_mgr_create,
+ .destroy = (destroy_t)sum_mgr_destroy },
+ [DAIO] = { .create = (create_t)daio_mgr_create,
+ .destroy = (destroy_t)daio_mgr_destroy }
+};
+
+static int
+atc_pcm_release_resources(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+
+/* *
+ * Only mono and interleaved modes are supported now.
+ * Always allocates a contiguous channel block.
+ * */
+
+static int ct_map_audio_buffer(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct snd_pcm_runtime *runtime;
+ struct ct_vm *vm;
+
+ if (!apcm->substream)
+ return 0;
+
+ runtime = apcm->substream->runtime;
+ vm = atc->vm;
+
+ apcm->vm_block = vm->map(vm, apcm->substream, runtime->dma_bytes);
+
+ if (!apcm->vm_block)
+ return -ENOENT;
+
+ return 0;
+}
+
+static void ct_unmap_audio_buffer(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct ct_vm *vm;
+
+ if (!apcm->vm_block)
+ return;
+
+ vm = atc->vm;
+
+ vm->unmap(vm, apcm->vm_block);
+
+ apcm->vm_block = NULL;
+}
+
+static unsigned long atc_get_ptp_phys(struct ct_atc *atc, int index)
+{
+ return atc->vm->get_ptp_phys(atc->vm, index);
+}
+
+static unsigned int convert_format(snd_pcm_format_t snd_format,
+ struct snd_card *card)
+{
+ switch (snd_format) {
+ case SNDRV_PCM_FORMAT_U8:
+ return SRC_SF_U8;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ return SRC_SF_S16;
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ return SRC_SF_S24;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ return SRC_SF_S32;
+ case SNDRV_PCM_FORMAT_FLOAT_LE:
+ return SRC_SF_F32;
+ default:
+ dev_err(card->dev, "not recognized snd format is %d\n",
+ snd_format);
+ return SRC_SF_S16;
+ }
+}
+
+static unsigned int
+atc_get_pitch(unsigned int input_rate, unsigned int output_rate)
+{
+ unsigned int pitch;
+ int b;
+
+ /* get pitch and convert to fixed-point 8.24 format. */
+ pitch = (input_rate / output_rate) << 24;
+ input_rate %= output_rate;
+ input_rate /= 100;
+ output_rate /= 100;
+ for (b = 31; ((b >= 0) && !(input_rate >> b)); )
+ b--;
+
+ if (b >= 0) {
+ input_rate <<= (31 - b);
+ input_rate /= output_rate;
+ b = 24 - (31 - b);
+ if (b >= 0)
+ input_rate <<= b;
+ else
+ input_rate >>= -b;
+
+ pitch |= input_rate;
+ }
+
+ return pitch;
+}
+
+static int select_rom(unsigned int pitch)
+{
+ if (pitch > 0x00428f5c && pitch < 0x01b851ec) {
+ /* 0.26 <= pitch <= 1.72 */
+ return 1;
+ } else if (pitch == 0x01d66666 || pitch == 0x01d66667) {
+ /* pitch == 1.8375 */
+ return 2;
+ } else if (pitch == 0x02000000) {
+ /* pitch == 2 */
+ return 3;
+ } else if (pitch <= 0x08000000) {
+ /* 0 <= pitch <= 8 */
+ return 0;
+ } else {
+ return -ENOENT;
+ }
+}
+
+static int atc_pcm_playback_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct src_mgr *src_mgr = atc->rsc_mgrs[SRC];
+ struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER];
+ struct src_desc desc = {0};
+ struct amixer_desc mix_dsc = {0};
+ struct src *src;
+ struct amixer *amixer;
+ int err;
+ int n_amixer = apcm->substream->runtime->channels, i = 0;
+ int device = apcm->substream->pcm->device;
+ unsigned int pitch;
+
+ /* first release old resources */
+ atc_pcm_release_resources(atc, apcm);
+
+ /* Get SRC resource */
+ desc.multi = apcm->substream->runtime->channels;
+ desc.msr = atc->msr;
+ desc.mode = MEMRD;
+ err = src_mgr->get_src(src_mgr, &desc, (struct src **)&apcm->src);
+ if (err)
+ goto error1;
+
+ pitch = atc_get_pitch(apcm->substream->runtime->rate,
+ (atc->rsr * atc->msr));
+ src = apcm->src;
+ src->ops->set_pitch(src, pitch);
+ src->ops->set_rom(src, select_rom(pitch));
+ src->ops->set_sf(src, convert_format(apcm->substream->runtime->format,
+ atc->card));
+ src->ops->set_pm(src, (src->ops->next_interleave(src) != NULL));
+
+ /* Get AMIXER resource */
+ n_amixer = (n_amixer < 2) ? 2 : n_amixer;
+ apcm->amixers = kcalloc(n_amixer, sizeof(void *), GFP_KERNEL);
+ if (!apcm->amixers) {
+ err = -ENOMEM;
+ goto error1;
+ }
+ mix_dsc.msr = atc->msr;
+ for (i = 0, apcm->n_amixer = 0; i < n_amixer; i++) {
+ err = amixer_mgr->get_amixer(amixer_mgr, &mix_dsc,
+ (struct amixer **)&apcm->amixers[i]);
+ if (err)
+ goto error1;
+
+ apcm->n_amixer++;
+ }
+
+ /* Set up device virtual mem map */
+ err = ct_map_audio_buffer(atc, apcm);
+ if (err < 0)
+ goto error1;
+
+ /* Connect resources */
+ src = apcm->src;
+ for (i = 0; i < n_amixer; i++) {
+ amixer = apcm->amixers[i];
+ mutex_lock(&atc->atc_mutex);
+ amixer->ops->setup(amixer, &src->rsc,
+ INIT_VOL, atc->pcm[i+device*2]);
+ mutex_unlock(&atc->atc_mutex);
+ src = src->ops->next_interleave(src);
+ if (!src)
+ src = apcm->src;
+ }
+
+ ct_timer_prepare(apcm->timer);
+
+ return 0;
+
+error1:
+ atc_pcm_release_resources(atc, apcm);
+ return err;
+}
+
+static int
+atc_pcm_release_resources(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct src_mgr *src_mgr = atc->rsc_mgrs[SRC];
+ struct srcimp_mgr *srcimp_mgr = atc->rsc_mgrs[SRCIMP];
+ struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER];
+ struct sum_mgr *sum_mgr = atc->rsc_mgrs[SUM];
+ struct srcimp *srcimp;
+ int i;
+
+ if (apcm->srcimps) {
+ for (i = 0; i < apcm->n_srcimp; i++) {
+ srcimp = apcm->srcimps[i];
+ srcimp->ops->unmap(srcimp);
+ srcimp_mgr->put_srcimp(srcimp_mgr, srcimp);
+ apcm->srcimps[i] = NULL;
+ }
+ kfree(apcm->srcimps);
+ apcm->srcimps = NULL;
+ }
+
+ if (apcm->srccs) {
+ for (i = 0; i < apcm->n_srcc; i++) {
+ src_mgr->put_src(src_mgr, apcm->srccs[i]);
+ apcm->srccs[i] = NULL;
+ }
+ kfree(apcm->srccs);
+ apcm->srccs = NULL;
+ }
+
+ if (apcm->amixers) {
+ for (i = 0; i < apcm->n_amixer; i++) {
+ amixer_mgr->put_amixer(amixer_mgr, apcm->amixers[i]);
+ apcm->amixers[i] = NULL;
+ }
+ kfree(apcm->amixers);
+ apcm->amixers = NULL;
+ }
+
+ if (apcm->mono) {
+ sum_mgr->put_sum(sum_mgr, apcm->mono);
+ apcm->mono = NULL;
+ }
+
+ if (apcm->src) {
+ src_mgr->put_src(src_mgr, apcm->src);
+ apcm->src = NULL;
+ }
+
+ if (apcm->vm_block) {
+ /* Undo device virtual mem map */
+ ct_unmap_audio_buffer(atc, apcm);
+ apcm->vm_block = NULL;
+ }
+
+ return 0;
+}
+
+static int atc_pcm_playback_start(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ unsigned int max_cisz;
+ struct src *src = apcm->src;
+
+ if (apcm->started)
+ return 0;
+ apcm->started = 1;
+
+ max_cisz = src->multi * src->rsc.msr;
+ max_cisz = 0x80 * (max_cisz < 8 ? max_cisz : 8);
+
+ src->ops->set_sa(src, apcm->vm_block->addr);
+ src->ops->set_la(src, apcm->vm_block->addr + apcm->vm_block->size);
+ src->ops->set_ca(src, apcm->vm_block->addr + max_cisz);
+ src->ops->set_cisz(src, max_cisz);
+
+ src->ops->set_bm(src, 1);
+ src->ops->set_state(src, SRC_STATE_INIT);
+ src->ops->commit_write(src);
+
+ ct_timer_start(apcm->timer);
+ return 0;
+}
+
+static int atc_pcm_stop(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct src *src;
+ int i;
+
+ ct_timer_stop(apcm->timer);
+
+ src = apcm->src;
+ src->ops->set_bm(src, 0);
+ src->ops->set_state(src, SRC_STATE_OFF);
+ src->ops->commit_write(src);
+
+ if (apcm->srccs) {
+ for (i = 0; i < apcm->n_srcc; i++) {
+ src = apcm->srccs[i];
+ src->ops->set_bm(src, 0);
+ src->ops->set_state(src, SRC_STATE_OFF);
+ src->ops->commit_write(src);
+ }
+ }
+
+ apcm->started = 0;
+
+ return 0;
+}
+
+static int
+atc_pcm_playback_position(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct src *src = apcm->src;
+ u32 size, max_cisz;
+ int position;
+
+ if (!src)
+ return 0;
+ position = src->ops->get_ca(src);
+
+ if (position < apcm->vm_block->addr) {
+ dev_dbg(atc->card->dev,
+ "bad ca - ca=0x%08x, vba=0x%08x, vbs=0x%08x\n",
+ position, apcm->vm_block->addr, apcm->vm_block->size);
+ position = apcm->vm_block->addr;
+ }
+
+ size = apcm->vm_block->size;
+ max_cisz = src->multi * src->rsc.msr;
+ max_cisz = 128 * (max_cisz < 8 ? max_cisz : 8);
+
+ return (position + size - max_cisz - apcm->vm_block->addr) % size;
+}
+
+struct src_node_conf_t {
+ unsigned int pitch;
+ unsigned int msr:8;
+ unsigned int mix_msr:8;
+ unsigned int imp_msr:8;
+ unsigned int vo:1;
+};
+
+static void setup_src_node_conf(struct ct_atc *atc, struct ct_atc_pcm *apcm,
+ struct src_node_conf_t *conf, int *n_srcc)
+{
+ unsigned int pitch;
+
+ /* get pitch and convert to fixed-point 8.24 format. */
+ pitch = atc_get_pitch((atc->rsr * atc->msr),
+ apcm->substream->runtime->rate);
+ *n_srcc = 0;
+
+ if (1 == atc->msr) { /* FIXME: do we really need SRC here if pitch==1 */
+ *n_srcc = apcm->substream->runtime->channels;
+ conf[0].pitch = pitch;
+ conf[0].mix_msr = conf[0].imp_msr = conf[0].msr = 1;
+ conf[0].vo = 1;
+ } else if (2 <= atc->msr) {
+ if (0x8000000 < pitch) {
+ /* Need two-stage SRCs, SRCIMPs and
+ * AMIXERs for converting format */
+ conf[0].pitch = (atc->msr << 24);
+ conf[0].msr = conf[0].mix_msr = 1;
+ conf[0].imp_msr = atc->msr;
+ conf[0].vo = 0;
+ conf[1].pitch = atc_get_pitch(atc->rsr,
+ apcm->substream->runtime->rate);
+ conf[1].msr = conf[1].mix_msr = conf[1].imp_msr = 1;
+ conf[1].vo = 1;
+ *n_srcc = apcm->substream->runtime->channels * 2;
+ } else if (0x1000000 < pitch) {
+ /* Need one-stage SRCs, SRCIMPs and
+ * AMIXERs for converting format */
+ conf[0].pitch = pitch;
+ conf[0].msr = conf[0].mix_msr
+ = conf[0].imp_msr = atc->msr;
+ conf[0].vo = 1;
+ *n_srcc = apcm->substream->runtime->channels;
+ }
+ }
+}
+
+static int
+atc_pcm_capture_get_resources(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct src_mgr *src_mgr = atc->rsc_mgrs[SRC];
+ struct srcimp_mgr *srcimp_mgr = atc->rsc_mgrs[SRCIMP];
+ struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER];
+ struct sum_mgr *sum_mgr = atc->rsc_mgrs[SUM];
+ struct src_desc src_dsc = {0};
+ struct src *src;
+ struct srcimp_desc srcimp_dsc = {0};
+ struct srcimp *srcimp;
+ struct amixer_desc mix_dsc = {0};
+ struct sum_desc sum_dsc = {0};
+ unsigned int pitch;
+ int multi, err, i;
+ int n_srcimp, n_amixer, n_srcc, n_sum;
+ struct src_node_conf_t src_node_conf[2] = {{0} };
+
+ /* first release old resources */
+ atc_pcm_release_resources(atc, apcm);
+
+ /* The numbers of converting SRCs and SRCIMPs should be determined
+ * by pitch value. */
+
+ multi = apcm->substream->runtime->channels;
+
+ /* get pitch and convert to fixed-point 8.24 format. */
+ pitch = atc_get_pitch((atc->rsr * atc->msr),
+ apcm->substream->runtime->rate);
+
+ setup_src_node_conf(atc, apcm, src_node_conf, &n_srcc);
+ n_sum = (1 == multi) ? 1 : 0;
+ n_amixer = n_sum * 2 + n_srcc;
+ n_srcimp = n_srcc;
+ if ((multi > 1) && (0x8000000 >= pitch)) {
+ /* Need extra AMIXERs and SRCIMPs for special treatment
+ * of interleaved recording of conjugate channels */
+ n_amixer += multi * atc->msr;
+ n_srcimp += multi * atc->msr;
+ } else {
+ n_srcimp += multi;
+ }
+
+ if (n_srcc) {
+ apcm->srccs = kcalloc(n_srcc, sizeof(void *), GFP_KERNEL);
+ if (!apcm->srccs)
+ return -ENOMEM;
+ }
+ if (n_amixer) {
+ apcm->amixers = kcalloc(n_amixer, sizeof(void *), GFP_KERNEL);
+ if (!apcm->amixers) {
+ err = -ENOMEM;
+ goto error1;
+ }
+ }
+ apcm->srcimps = kcalloc(n_srcimp, sizeof(void *), GFP_KERNEL);
+ if (!apcm->srcimps) {
+ err = -ENOMEM;
+ goto error1;
+ }
+
+ /* Allocate SRCs for sample rate conversion if needed */
+ src_dsc.multi = 1;
+ src_dsc.mode = ARCRW;
+ for (i = 0, apcm->n_srcc = 0; i < n_srcc; i++) {
+ src_dsc.msr = src_node_conf[i/multi].msr;
+ err = src_mgr->get_src(src_mgr, &src_dsc,
+ (struct src **)&apcm->srccs[i]);
+ if (err)
+ goto error1;
+
+ src = apcm->srccs[i];
+ pitch = src_node_conf[i/multi].pitch;
+ src->ops->set_pitch(src, pitch);
+ src->ops->set_rom(src, select_rom(pitch));
+ src->ops->set_vo(src, src_node_conf[i/multi].vo);
+
+ apcm->n_srcc++;
+ }
+
+ /* Allocate AMIXERs for routing SRCs of conversion if needed */
+ for (i = 0, apcm->n_amixer = 0; i < n_amixer; i++) {
+ if (i < (n_sum*2))
+ mix_dsc.msr = atc->msr;
+ else if (i < (n_sum*2+n_srcc))
+ mix_dsc.msr = src_node_conf[(i-n_sum*2)/multi].mix_msr;
+ else
+ mix_dsc.msr = 1;
+
+ err = amixer_mgr->get_amixer(amixer_mgr, &mix_dsc,
+ (struct amixer **)&apcm->amixers[i]);
+ if (err)
+ goto error1;
+
+ apcm->n_amixer++;
+ }
+
+ /* Allocate a SUM resource to mix all input channels together */
+ sum_dsc.msr = atc->msr;
+ err = sum_mgr->get_sum(sum_mgr, &sum_dsc, (struct sum **)&apcm->mono);
+ if (err)
+ goto error1;
+
+ pitch = atc_get_pitch((atc->rsr * atc->msr),
+ apcm->substream->runtime->rate);
+ /* Allocate SRCIMP resources */
+ for (i = 0, apcm->n_srcimp = 0; i < n_srcimp; i++) {
+ if (i < (n_srcc))
+ srcimp_dsc.msr = src_node_conf[i/multi].imp_msr;
+ else if (1 == multi)
+ srcimp_dsc.msr = (pitch <= 0x8000000) ? atc->msr : 1;
+ else
+ srcimp_dsc.msr = 1;
+
+ err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc, &srcimp);
+ if (err)
+ goto error1;
+
+ apcm->srcimps[i] = srcimp;
+ apcm->n_srcimp++;
+ }
+
+ /* Allocate a SRC for writing data to host memory */
+ src_dsc.multi = apcm->substream->runtime->channels;
+ src_dsc.msr = 1;
+ src_dsc.mode = MEMWR;
+ err = src_mgr->get_src(src_mgr, &src_dsc, (struct src **)&apcm->src);
+ if (err)
+ goto error1;
+
+ src = apcm->src;
+ src->ops->set_pitch(src, pitch);
+
+ /* Set up device virtual mem map */
+ err = ct_map_audio_buffer(atc, apcm);
+ if (err < 0)
+ goto error1;
+
+ return 0;
+
+error1:
+ atc_pcm_release_resources(atc, apcm);
+ return err;
+}
+
+static int atc_pcm_capture_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct src *src;
+ struct amixer *amixer;
+ struct srcimp *srcimp;
+ struct ct_mixer *mixer = atc->mixer;
+ struct sum *mono;
+ struct rsc *out_ports[8] = {NULL};
+ int err, i, j, n_sum, multi;
+ unsigned int pitch;
+ int mix_base = 0, imp_base = 0;
+
+ atc_pcm_release_resources(atc, apcm);
+
+ /* Get needed resources. */
+ err = atc_pcm_capture_get_resources(atc, apcm);
+ if (err)
+ return err;
+
+ /* Connect resources */
+ mixer->get_output_ports(mixer, MIX_PCMO_FRONT,
+ &out_ports[0], &out_ports[1]);
+
+ multi = apcm->substream->runtime->channels;
+ if (1 == multi) {
+ mono = apcm->mono;
+ for (i = 0; i < 2; i++) {
+ amixer = apcm->amixers[i];
+ amixer->ops->setup(amixer, out_ports[i],
+ MONO_SUM_SCALE, mono);
+ }
+ out_ports[0] = &mono->rsc;
+ n_sum = 1;
+ mix_base = n_sum * 2;
+ }
+
+ for (i = 0; i < apcm->n_srcc; i++) {
+ src = apcm->srccs[i];
+ srcimp = apcm->srcimps[imp_base+i];
+ amixer = apcm->amixers[mix_base+i];
+ srcimp->ops->map(srcimp, src, out_ports[i%multi]);
+ amixer->ops->setup(amixer, &src->rsc, INIT_VOL, NULL);
+ out_ports[i%multi] = &amixer->rsc;
+ }
+
+ pitch = atc_get_pitch((atc->rsr * atc->msr),
+ apcm->substream->runtime->rate);
+
+ if ((multi > 1) && (pitch <= 0x8000000)) {
+ /* Special connection for interleaved
+ * recording with conjugate channels */
+ for (i = 0; i < multi; i++) {
+ out_ports[i]->ops->master(out_ports[i]);
+ for (j = 0; j < atc->msr; j++) {
+ amixer = apcm->amixers[apcm->n_srcc+j*multi+i];
+ amixer->ops->set_input(amixer, out_ports[i]);
+ amixer->ops->set_scale(amixer, INIT_VOL);
+ amixer->ops->set_sum(amixer, NULL);
+ amixer->ops->commit_raw_write(amixer);
+ out_ports[i]->ops->next_conj(out_ports[i]);
+
+ srcimp = apcm->srcimps[apcm->n_srcc+j*multi+i];
+ srcimp->ops->map(srcimp, apcm->src,
+ &amixer->rsc);
+ }
+ }
+ } else {
+ for (i = 0; i < multi; i++) {
+ srcimp = apcm->srcimps[apcm->n_srcc+i];
+ srcimp->ops->map(srcimp, apcm->src, out_ports[i]);
+ }
+ }
+
+ ct_timer_prepare(apcm->timer);
+
+ return 0;
+}
+
+static int atc_pcm_capture_start(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct src *src;
+ struct src_mgr *src_mgr = atc->rsc_mgrs[SRC];
+ int i, multi;
+
+ if (apcm->started)
+ return 0;
+
+ apcm->started = 1;
+ multi = apcm->substream->runtime->channels;
+ /* Set up converting SRCs */
+ for (i = 0; i < apcm->n_srcc; i++) {
+ src = apcm->srccs[i];
+ src->ops->set_pm(src, ((i%multi) != (multi-1)));
+ src_mgr->src_disable(src_mgr, src);
+ }
+
+ /* Set up recording SRC */
+ src = apcm->src;
+ src->ops->set_sf(src, convert_format(apcm->substream->runtime->format,
+ atc->card));
+ src->ops->set_sa(src, apcm->vm_block->addr);
+ src->ops->set_la(src, apcm->vm_block->addr + apcm->vm_block->size);
+ src->ops->set_ca(src, apcm->vm_block->addr);
+ src_mgr->src_disable(src_mgr, src);
+
+ /* Disable relevant SRCs firstly */
+ src_mgr->commit_write(src_mgr);
+
+ /* Enable SRCs respectively */
+ for (i = 0; i < apcm->n_srcc; i++) {
+ src = apcm->srccs[i];
+ src->ops->set_state(src, SRC_STATE_RUN);
+ src->ops->commit_write(src);
+ src_mgr->src_enable_s(src_mgr, src);
+ }
+ src = apcm->src;
+ src->ops->set_bm(src, 1);
+ src->ops->set_state(src, SRC_STATE_RUN);
+ src->ops->commit_write(src);
+ src_mgr->src_enable_s(src_mgr, src);
+
+ /* Enable relevant SRCs synchronously */
+ src_mgr->commit_write(src_mgr);
+
+ ct_timer_start(apcm->timer);
+ return 0;
+}
+
+static int
+atc_pcm_capture_position(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct src *src = apcm->src;
+
+ if (!src)
+ return 0;
+ return src->ops->get_ca(src) - apcm->vm_block->addr;
+}
+
+static int spdif_passthru_playback_get_resources(struct ct_atc *atc,
+ struct ct_atc_pcm *apcm)
+{
+ struct src_mgr *src_mgr = atc->rsc_mgrs[SRC];
+ struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER];
+ struct src_desc desc = {0};
+ struct amixer_desc mix_dsc = {0};
+ struct src *src;
+ int err;
+ int n_amixer = apcm->substream->runtime->channels, i;
+ unsigned int pitch, rsr = atc->pll_rate;
+
+ /* first release old resources */
+ atc_pcm_release_resources(atc, apcm);
+
+ /* Get SRC resource */
+ desc.multi = apcm->substream->runtime->channels;
+ desc.msr = 1;
+ while (apcm->substream->runtime->rate > (rsr * desc.msr))
+ desc.msr <<= 1;
+
+ desc.mode = MEMRD;
+ err = src_mgr->get_src(src_mgr, &desc, (struct src **)&apcm->src);
+ if (err)
+ goto error1;
+
+ pitch = atc_get_pitch(apcm->substream->runtime->rate, (rsr * desc.msr));
+ src = apcm->src;
+ src->ops->set_pitch(src, pitch);
+ src->ops->set_rom(src, select_rom(pitch));
+ src->ops->set_sf(src, convert_format(apcm->substream->runtime->format,
+ atc->card));
+ src->ops->set_pm(src, (src->ops->next_interleave(src) != NULL));
+ src->ops->set_bp(src, 1);
+
+ /* Get AMIXER resource */
+ n_amixer = (n_amixer < 2) ? 2 : n_amixer;
+ apcm->amixers = kcalloc(n_amixer, sizeof(void *), GFP_KERNEL);
+ if (!apcm->amixers) {
+ err = -ENOMEM;
+ goto error1;
+ }
+ mix_dsc.msr = desc.msr;
+ for (i = 0, apcm->n_amixer = 0; i < n_amixer; i++) {
+ err = amixer_mgr->get_amixer(amixer_mgr, &mix_dsc,
+ (struct amixer **)&apcm->amixers[i]);
+ if (err)
+ goto error1;
+
+ apcm->n_amixer++;
+ }
+
+ /* Set up device virtual mem map */
+ err = ct_map_audio_buffer(atc, apcm);
+ if (err < 0)
+ goto error1;
+
+ return 0;
+
+error1:
+ atc_pcm_release_resources(atc, apcm);
+ return err;
+}
+
+static int atc_pll_init(struct ct_atc *atc, int rate)
+{
+ struct hw *hw = atc->hw;
+ int err;
+ err = hw->pll_init(hw, rate);
+ atc->pll_rate = err ? 0 : rate;
+ return err;
+}
+
+static int
+spdif_passthru_playback_setup(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct dao *dao = container_of(atc->daios[SPDIFOO], struct dao, daio);
+ unsigned int rate = apcm->substream->runtime->rate;
+ unsigned int status;
+ int err = 0;
+ unsigned char iec958_con_fs;
+
+ switch (rate) {
+ case 48000:
+ iec958_con_fs = IEC958_AES3_CON_FS_48000;
+ break;
+ case 44100:
+ iec958_con_fs = IEC958_AES3_CON_FS_44100;
+ break;
+ case 32000:
+ iec958_con_fs = IEC958_AES3_CON_FS_32000;
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ mutex_lock(&atc->atc_mutex);
+ dao->ops->get_spos(dao, &status);
+ if (((status >> 24) & IEC958_AES3_CON_FS) != iec958_con_fs) {
+ status &= ~(IEC958_AES3_CON_FS << 24);
+ status |= (iec958_con_fs << 24);
+ dao->ops->set_spos(dao, status);
+ dao->ops->commit_write(dao);
+ }
+ if ((rate != atc->pll_rate) && (32000 != rate))
+ err = atc_pll_init(atc, rate);
+ mutex_unlock(&atc->atc_mutex);
+
+ return err;
+}
+
+static int
+spdif_passthru_playback_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+ struct src *src;
+ struct amixer *amixer;
+ struct dao *dao;
+ int err;
+ int i;
+
+ atc_pcm_release_resources(atc, apcm);
+
+ /* Configure SPDIFOO and PLL to passthrough mode;
+ * determine pll_rate. */
+ err = spdif_passthru_playback_setup(atc, apcm);
+ if (err)
+ return err;
+
+ /* Get needed resources. */
+ err = spdif_passthru_playback_get_resources(atc, apcm);
+ if (err)
+ return err;
+
+ /* Connect resources */
+ src = apcm->src;
+ for (i = 0; i < apcm->n_amixer; i++) {
+ amixer = apcm->amixers[i];
+ amixer->ops->setup(amixer, &src->rsc, INIT_VOL, NULL);
+ src = src->ops->next_interleave(src);
+ if (!src)
+ src = apcm->src;
+ }
+ /* Connect to SPDIFOO */
+ mutex_lock(&atc->atc_mutex);
+ dao = container_of(atc->daios[SPDIFOO], struct dao, daio);
+ amixer = apcm->amixers[0];
+ dao->ops->set_left_input(dao, &amixer->rsc);
+ amixer = apcm->amixers[1];
+ dao->ops->set_right_input(dao, &amixer->rsc);
+ mutex_unlock(&atc->atc_mutex);
+
+ ct_timer_prepare(apcm->timer);
+
+ return 0;
+}
+
+static int atc_select_line_in(struct ct_atc *atc)
+{
+ struct hw *hw = atc->hw;
+ struct ct_mixer *mixer = atc->mixer;
+ struct src *src;
+
+ if (hw->is_adc_source_selected(hw, ADC_LINEIN))
+ return 0;
+
+ mixer->set_input_left(mixer, MIX_MIC_IN, NULL);
+ mixer->set_input_right(mixer, MIX_MIC_IN, NULL);
+
+ hw->select_adc_source(hw, ADC_LINEIN);
+
+ src = atc->srcs[2];
+ mixer->set_input_left(mixer, MIX_LINE_IN, &src->rsc);
+ src = atc->srcs[3];
+ mixer->set_input_right(mixer, MIX_LINE_IN, &src->rsc);
+
+ return 0;
+}
+
+static int atc_select_mic_in(struct ct_atc *atc)
+{
+ struct hw *hw = atc->hw;
+ struct ct_mixer *mixer = atc->mixer;
+ struct src *src;
+
+ if (hw->is_adc_source_selected(hw, ADC_MICIN))
+ return 0;
+
+ mixer->set_input_left(mixer, MIX_LINE_IN, NULL);
+ mixer->set_input_right(mixer, MIX_LINE_IN, NULL);
+
+ hw->select_adc_source(hw, ADC_MICIN);
+
+ src = atc->srcs[2];
+ mixer->set_input_left(mixer, MIX_MIC_IN, &src->rsc);
+ src = atc->srcs[3];
+ mixer->set_input_right(mixer, MIX_MIC_IN, &src->rsc);
+
+ return 0;
+}
+
+static struct capabilities atc_capabilities(struct ct_atc *atc)
+{
+ struct hw *hw = atc->hw;
+
+ return hw->capabilities(hw);
+}
+
+static int atc_output_switch_get(struct ct_atc *atc)
+{
+ struct hw *hw = atc->hw;
+
+ return hw->output_switch_get(hw);
+}
+
+static int atc_output_switch_put(struct ct_atc *atc, int position)
+{
+ struct hw *hw = atc->hw;
+
+ return hw->output_switch_put(hw, position);
+}
+
+static int atc_mic_source_switch_get(struct ct_atc *atc)
+{
+ struct hw *hw = atc->hw;
+
+ return hw->mic_source_switch_get(hw);
+}
+
+static int atc_mic_source_switch_put(struct ct_atc *atc, int position)
+{
+ struct hw *hw = atc->hw;
+
+ return hw->mic_source_switch_put(hw, position);
+}
+
+static int atc_select_digit_io(struct ct_atc *atc)
+{
+ struct hw *hw = atc->hw;
+
+ if (hw->is_adc_source_selected(hw, ADC_NONE))
+ return 0;
+
+ hw->select_adc_source(hw, ADC_NONE);
+
+ return 0;
+}
+
+static int atc_daio_unmute(struct ct_atc *atc, unsigned char state, int type)
+{
+ struct daio_mgr *daio_mgr = atc->rsc_mgrs[DAIO];
+
+ if (state)
+ daio_mgr->daio_enable(daio_mgr, atc->daios[type]);
+ else
+ daio_mgr->daio_disable(daio_mgr, atc->daios[type]);
+
+ daio_mgr->commit_write(daio_mgr);
+
+ return 0;
+}
+
+static int
+atc_dao_get_status(struct ct_atc *atc, unsigned int *status, int type)
+{
+ struct dao *dao = container_of(atc->daios[type], struct dao, daio);
+ return dao->ops->get_spos(dao, status);
+}
+
+static int
+atc_dao_set_status(struct ct_atc *atc, unsigned int status, int type)
+{
+ struct dao *dao = container_of(atc->daios[type], struct dao, daio);
+
+ dao->ops->set_spos(dao, status);
+ dao->ops->commit_write(dao);
+ return 0;
+}
+
+static int atc_line_front_unmute(struct ct_atc *atc, unsigned char state)
+{
+ return atc_daio_unmute(atc, state, LINEO1);
+}
+
+static int atc_line_surround_unmute(struct ct_atc *atc, unsigned char state)
+{
+ return atc_daio_unmute(atc, state, LINEO2);
+}
+
+static int atc_line_clfe_unmute(struct ct_atc *atc, unsigned char state)
+{
+ return atc_daio_unmute(atc, state, LINEO3);
+}
+
+static int atc_line_rear_unmute(struct ct_atc *atc, unsigned char state)
+{
+ return atc_daio_unmute(atc, state, LINEO4);
+}
+
+static int atc_line_in_unmute(struct ct_atc *atc, unsigned char state)
+{
+ return atc_daio_unmute(atc, state, LINEIM);
+}
+
+static int atc_mic_unmute(struct ct_atc *atc, unsigned char state)
+{
+ return atc_daio_unmute(atc, state, MIC);
+}
+
+static int atc_spdif_out_unmute(struct ct_atc *atc, unsigned char state)
+{
+ return atc_daio_unmute(atc, state, SPDIFOO);
+}
+
+static int atc_spdif_in_unmute(struct ct_atc *atc, unsigned char state)
+{
+ return atc_daio_unmute(atc, state, SPDIFIO);
+}
+
+static int atc_spdif_out_get_status(struct ct_atc *atc, unsigned int *status)
+{
+ return atc_dao_get_status(atc, status, SPDIFOO);
+}
+
+static int atc_spdif_out_set_status(struct ct_atc *atc, unsigned int status)
+{
+ return atc_dao_set_status(atc, status, SPDIFOO);
+}
+
+static int atc_spdif_out_passthru(struct ct_atc *atc, unsigned char state)
+{
+ struct dao_desc da_dsc = {0};
+ struct dao *dao;
+ int err;
+ struct ct_mixer *mixer = atc->mixer;
+ struct rsc *rscs[2] = {NULL};
+ unsigned int spos = 0;
+
+ mutex_lock(&atc->atc_mutex);
+ dao = container_of(atc->daios[SPDIFOO], struct dao, daio);
+ da_dsc.msr = state ? 1 : atc->msr;
+ da_dsc.passthru = state ? 1 : 0;
+ err = dao->ops->reinit(dao, &da_dsc);
+ if (state) {
+ spos = IEC958_DEFAULT_CON;
+ } else {
+ mixer->get_output_ports(mixer, MIX_SPDIF_OUT,
+ &rscs[0], &rscs[1]);
+ dao->ops->set_left_input(dao, rscs[0]);
+ dao->ops->set_right_input(dao, rscs[1]);
+ /* Restore PLL to atc->rsr if needed. */
+ if (atc->pll_rate != atc->rsr)
+ err = atc_pll_init(atc, atc->rsr);
+ }
+ dao->ops->set_spos(dao, spos);
+ dao->ops->commit_write(dao);
+ mutex_unlock(&atc->atc_mutex);
+
+ return err;
+}
+
+static int atc_release_resources(struct ct_atc *atc)
+{
+ int i;
+ struct daio_mgr *daio_mgr = NULL;
+ struct dao *dao = NULL;
+ struct daio *daio = NULL;
+ struct sum_mgr *sum_mgr = NULL;
+ struct src_mgr *src_mgr = NULL;
+ struct srcimp_mgr *srcimp_mgr = NULL;
+ struct srcimp *srcimp = NULL;
+ struct ct_mixer *mixer = NULL;
+
+ /* disconnect internal mixer objects */
+ if (atc->mixer) {
+ mixer = atc->mixer;
+ mixer->set_input_left(mixer, MIX_LINE_IN, NULL);
+ mixer->set_input_right(mixer, MIX_LINE_IN, NULL);
+ mixer->set_input_left(mixer, MIX_MIC_IN, NULL);
+ mixer->set_input_right(mixer, MIX_MIC_IN, NULL);
+ mixer->set_input_left(mixer, MIX_SPDIF_IN, NULL);
+ mixer->set_input_right(mixer, MIX_SPDIF_IN, NULL);
+ }
+
+ if (atc->daios) {
+ daio_mgr = (struct daio_mgr *)atc->rsc_mgrs[DAIO];
+ for (i = 0; i < atc->n_daio; i++) {
+ daio = atc->daios[i];
+ if (daio->type < LINEIM) {
+ dao = container_of(daio, struct dao, daio);
+ dao->ops->clear_left_input(dao);
+ dao->ops->clear_right_input(dao);
+ }
+ daio_mgr->put_daio(daio_mgr, daio);
+ }
+ kfree(atc->daios);
+ atc->daios = NULL;
+ }
+
+ if (atc->pcm) {
+ sum_mgr = atc->rsc_mgrs[SUM];
+ for (i = 0; i < atc->n_pcm; i++)
+ sum_mgr->put_sum(sum_mgr, atc->pcm[i]);
+
+ kfree(atc->pcm);
+ atc->pcm = NULL;
+ }
+
+ if (atc->srcs) {
+ src_mgr = atc->rsc_mgrs[SRC];
+ for (i = 0; i < atc->n_src; i++)
+ src_mgr->put_src(src_mgr, atc->srcs[i]);
+
+ kfree(atc->srcs);
+ atc->srcs = NULL;
+ }
+
+ if (atc->srcimps) {
+ srcimp_mgr = atc->rsc_mgrs[SRCIMP];
+ for (i = 0; i < atc->n_srcimp; i++) {
+ srcimp = atc->srcimps[i];
+ srcimp->ops->unmap(srcimp);
+ srcimp_mgr->put_srcimp(srcimp_mgr, atc->srcimps[i]);
+ }
+ kfree(atc->srcimps);
+ atc->srcimps = NULL;
+ }
+
+ return 0;
+}
+
+static int ct_atc_destroy(struct ct_atc *atc)
+{
+ int i = 0;
+
+ if (!atc)
+ return 0;
+
+ if (atc->timer) {
+ ct_timer_free(atc->timer);
+ atc->timer = NULL;
+ }
+
+ atc_release_resources(atc);
+
+ /* Destroy internal mixer objects */
+ if (atc->mixer)
+ ct_mixer_destroy(atc->mixer);
+
+ for (i = 0; i < NUM_RSCTYP; i++) {
+ if (rsc_mgr_funcs[i].destroy && atc->rsc_mgrs[i])
+ rsc_mgr_funcs[i].destroy(atc->rsc_mgrs[i]);
+
+ }
+
+ if (atc->hw)
+ destroy_hw_obj(atc->hw);
+
+ /* Destroy device virtual memory manager object */
+ if (atc->vm) {
+ ct_vm_destroy(atc->vm);
+ atc->vm = NULL;
+ }
+
+ kfree(atc);
+
+ return 0;
+}
+
+static int atc_dev_free(struct snd_device *dev)
+{
+ struct ct_atc *atc = dev->device_data;
+ return ct_atc_destroy(atc);
+}
+
+static int atc_identify_card(struct ct_atc *atc, unsigned int ssid)
+{
+ const struct snd_pci_quirk *p;
+ const struct snd_pci_quirk *list;
+ u16 vendor_id, device_id;
+
+ switch (atc->chip_type) {
+ case ATC20K1:
+ atc->chip_name = "20K1";
+ list = subsys_20k1_list;
+ break;
+ case ATC20K2:
+ atc->chip_name = "20K2";
+ list = subsys_20k2_list;
+ break;
+ default:
+ return -ENOENT;
+ }
+ if (ssid) {
+ vendor_id = ssid >> 16;
+ device_id = ssid & 0xffff;
+ } else {
+ vendor_id = atc->pci->subsystem_vendor;
+ device_id = atc->pci->subsystem_device;
+ }
+ p = snd_pci_quirk_lookup_id(vendor_id, device_id, list);
+ if (p) {
+ if (p->value < 0) {
+ dev_err(atc->card->dev,
+ "Device %04x:%04x is on the denylist\n",
+ vendor_id, device_id);
+ return -ENOENT;
+ }
+ atc->model = p->value;
+ } else {
+ if (atc->chip_type == ATC20K1)
+ atc->model = CT20K1_UNKNOWN;
+ else
+ atc->model = CT20K2_UNKNOWN;
+ }
+ atc->model_name = ct_subsys_name[atc->model];
+ dev_info(atc->card->dev, "chip %s model %s (%04x:%04x) is found\n",
+ atc->chip_name, atc->model_name,
+ vendor_id, device_id);
+ return 0;
+}
+
+int ct_atc_create_alsa_devs(struct ct_atc *atc)
+{
+ enum CTALSADEVS i;
+ int err;
+
+ alsa_dev_funcs[MIXER].public_name = atc->chip_name;
+
+ for (i = 0; i < NUM_CTALSADEVS; i++) {
+ if (!alsa_dev_funcs[i].create)
+ continue;
+
+ err = alsa_dev_funcs[i].create(atc, i,
+ alsa_dev_funcs[i].public_name);
+ if (err) {
+ dev_err(atc->card->dev,
+ "Creating alsa device %d failed!\n", i);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int atc_create_hw_devs(struct ct_atc *atc)
+{
+ struct hw *hw;
+ struct card_conf info = {0};
+ int i, err;
+
+ err = create_hw_obj(atc->pci, atc->chip_type, atc->model, &hw);
+ if (err) {
+ dev_err(atc->card->dev, "Failed to create hw obj!!!\n");
+ return err;
+ }
+ hw->card = atc->card;
+ atc->hw = hw;
+
+ /* Initialize card hardware. */
+ info.rsr = atc->rsr;
+ info.msr = atc->msr;
+ info.vm_pgt_phys = atc_get_ptp_phys(atc, 0);
+ err = hw->card_init(hw, &info);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < NUM_RSCTYP; i++) {
+ if (!rsc_mgr_funcs[i].create)
+ continue;
+
+ err = rsc_mgr_funcs[i].create(atc->hw, &atc->rsc_mgrs[i]);
+ if (err) {
+ dev_err(atc->card->dev,
+ "Failed to create rsc_mgr %d!!!\n", i);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int atc_get_resources(struct ct_atc *atc)
+{
+ struct daio_desc da_desc = {0};
+ struct daio_mgr *daio_mgr;
+ struct src_desc src_dsc = {0};
+ struct src_mgr *src_mgr;
+ struct srcimp_desc srcimp_dsc = {0};
+ struct srcimp_mgr *srcimp_mgr;
+ struct sum_desc sum_dsc = {0};
+ struct sum_mgr *sum_mgr;
+ int err, i, num_srcs, num_daios;
+
+ num_daios = ((atc->model == CTSB1270) ? 8 : 7);
+ num_srcs = ((atc->model == CTSB1270) ? 6 : 4);
+
+ atc->daios = kcalloc(num_daios, sizeof(void *), GFP_KERNEL);
+ if (!atc->daios)
+ return -ENOMEM;
+
+ atc->srcs = kcalloc(num_srcs, sizeof(void *), GFP_KERNEL);
+ if (!atc->srcs)
+ return -ENOMEM;
+
+ atc->srcimps = kcalloc(num_srcs, sizeof(void *), GFP_KERNEL);
+ if (!atc->srcimps)
+ return -ENOMEM;
+
+ atc->pcm = kcalloc(2 * 4, sizeof(void *), GFP_KERNEL);
+ if (!atc->pcm)
+ return -ENOMEM;
+
+ daio_mgr = (struct daio_mgr *)atc->rsc_mgrs[DAIO];
+ da_desc.msr = atc->msr;
+ for (i = 0, atc->n_daio = 0; i < num_daios; i++) {
+ da_desc.type = (atc->model != CTSB073X) ? i :
+ ((i == SPDIFIO) ? SPDIFI1 : i);
+ err = daio_mgr->get_daio(daio_mgr, &da_desc,
+ (struct daio **)&atc->daios[i]);
+ if (err) {
+ dev_err(atc->card->dev,
+ "Failed to get DAIO resource %d!!!\n",
+ i);
+ return err;
+ }
+ atc->n_daio++;
+ }
+
+ src_mgr = atc->rsc_mgrs[SRC];
+ src_dsc.multi = 1;
+ src_dsc.msr = atc->msr;
+ src_dsc.mode = ARCRW;
+ for (i = 0, atc->n_src = 0; i < num_srcs; i++) {
+ err = src_mgr->get_src(src_mgr, &src_dsc,
+ (struct src **)&atc->srcs[i]);
+ if (err)
+ return err;
+
+ atc->n_src++;
+ }
+
+ srcimp_mgr = atc->rsc_mgrs[SRCIMP];
+ srcimp_dsc.msr = 8;
+ for (i = 0, atc->n_srcimp = 0; i < num_srcs; i++) {
+ err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc,
+ (struct srcimp **)&atc->srcimps[i]);
+ if (err)
+ return err;
+
+ atc->n_srcimp++;
+ }
+
+ sum_mgr = atc->rsc_mgrs[SUM];
+ sum_dsc.msr = atc->msr;
+ for (i = 0, atc->n_pcm = 0; i < (2*4); i++) {
+ err = sum_mgr->get_sum(sum_mgr, &sum_dsc,
+ (struct sum **)&atc->pcm[i]);
+ if (err)
+ return err;
+
+ atc->n_pcm++;
+ }
+
+ return 0;
+}
+
+static void
+atc_connect_dai(struct src_mgr *src_mgr, struct dai *dai,
+ struct src **srcs, struct srcimp **srcimps)
+{
+ struct rsc *rscs[2] = {NULL};
+ struct src *src;
+ struct srcimp *srcimp;
+ int i = 0;
+
+ rscs[0] = &dai->daio.rscl;
+ rscs[1] = &dai->daio.rscr;
+ for (i = 0; i < 2; i++) {
+ src = srcs[i];
+ srcimp = srcimps[i];
+ srcimp->ops->map(srcimp, src, rscs[i]);
+ src_mgr->src_disable(src_mgr, src);
+ }
+
+ src_mgr->commit_write(src_mgr); /* Actually disable SRCs */
+
+ src = srcs[0];
+ src->ops->set_pm(src, 1);
+ for (i = 0; i < 2; i++) {
+ src = srcs[i];
+ src->ops->set_state(src, SRC_STATE_RUN);
+ src->ops->commit_write(src);
+ src_mgr->src_enable_s(src_mgr, src);
+ }
+
+ dai->ops->set_srt_srcl(dai, &(srcs[0]->rsc));
+ dai->ops->set_srt_srcr(dai, &(srcs[1]->rsc));
+
+ dai->ops->set_enb_src(dai, 1);
+ dai->ops->set_enb_srt(dai, 1);
+ dai->ops->commit_write(dai);
+
+ src_mgr->commit_write(src_mgr); /* Synchronously enable SRCs */
+}
+
+static void atc_connect_resources(struct ct_atc *atc)
+{
+ struct dai *dai;
+ struct dao *dao;
+ struct src *src;
+ struct sum *sum;
+ struct ct_mixer *mixer;
+ struct rsc *rscs[2] = {NULL};
+ int i, j;
+
+ mixer = atc->mixer;
+
+ for (i = MIX_WAVE_FRONT, j = LINEO1; i <= MIX_SPDIF_OUT; i++, j++) {
+ mixer->get_output_ports(mixer, i, &rscs[0], &rscs[1]);
+ dao = container_of(atc->daios[j], struct dao, daio);
+ dao->ops->set_left_input(dao, rscs[0]);
+ dao->ops->set_right_input(dao, rscs[1]);
+ }
+
+ dai = container_of(atc->daios[LINEIM], struct dai, daio);
+ atc_connect_dai(atc->rsc_mgrs[SRC], dai,
+ (struct src **)&atc->srcs[2],
+ (struct srcimp **)&atc->srcimps[2]);
+ src = atc->srcs[2];
+ mixer->set_input_left(mixer, MIX_LINE_IN, &src->rsc);
+ src = atc->srcs[3];
+ mixer->set_input_right(mixer, MIX_LINE_IN, &src->rsc);
+
+ if (atc->model == CTSB1270) {
+ /* Titanium HD has a dedicated ADC for the Mic. */
+ dai = container_of(atc->daios[MIC], struct dai, daio);
+ atc_connect_dai(atc->rsc_mgrs[SRC], dai,
+ (struct src **)&atc->srcs[4],
+ (struct srcimp **)&atc->srcimps[4]);
+ src = atc->srcs[4];
+ mixer->set_input_left(mixer, MIX_MIC_IN, &src->rsc);
+ src = atc->srcs[5];
+ mixer->set_input_right(mixer, MIX_MIC_IN, &src->rsc);
+ }
+
+ dai = container_of(atc->daios[SPDIFIO], struct dai, daio);
+ atc_connect_dai(atc->rsc_mgrs[SRC], dai,
+ (struct src **)&atc->srcs[0],
+ (struct srcimp **)&atc->srcimps[0]);
+
+ src = atc->srcs[0];
+ mixer->set_input_left(mixer, MIX_SPDIF_IN, &src->rsc);
+ src = atc->srcs[1];
+ mixer->set_input_right(mixer, MIX_SPDIF_IN, &src->rsc);
+
+ for (i = MIX_PCMI_FRONT, j = 0; i <= MIX_PCMI_SURROUND; i++, j += 2) {
+ sum = atc->pcm[j];
+ mixer->set_input_left(mixer, i, &sum->rsc);
+ sum = atc->pcm[j+1];
+ mixer->set_input_right(mixer, i, &sum->rsc);
+ }
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int atc_suspend(struct ct_atc *atc)
+{
+ struct hw *hw = atc->hw;
+
+ snd_power_change_state(atc->card, SNDRV_CTL_POWER_D3hot);
+
+ atc_release_resources(atc);
+
+ hw->suspend(hw);
+
+ return 0;
+}
+
+static int atc_hw_resume(struct ct_atc *atc)
+{
+ struct hw *hw = atc->hw;
+ struct card_conf info = {0};
+
+ /* Re-initialize card hardware. */
+ info.rsr = atc->rsr;
+ info.msr = atc->msr;
+ info.vm_pgt_phys = atc_get_ptp_phys(atc, 0);
+ return hw->resume(hw, &info);
+}
+
+static int atc_resources_resume(struct ct_atc *atc)
+{
+ struct ct_mixer *mixer;
+ int err = 0;
+
+ /* Get resources */
+ err = atc_get_resources(atc);
+ if (err < 0) {
+ atc_release_resources(atc);
+ return err;
+ }
+
+ /* Build topology */
+ atc_connect_resources(atc);
+
+ mixer = atc->mixer;
+ mixer->resume(mixer);
+
+ return 0;
+}
+
+static int atc_resume(struct ct_atc *atc)
+{
+ int err = 0;
+
+ /* Do hardware resume. */
+ err = atc_hw_resume(atc);
+ if (err < 0) {
+ dev_err(atc->card->dev,
+ "pci_enable_device failed, disabling device\n");
+ snd_card_disconnect(atc->card);
+ return err;
+ }
+
+ err = atc_resources_resume(atc);
+ if (err < 0)
+ return err;
+
+ snd_power_change_state(atc->card, SNDRV_CTL_POWER_D0);
+
+ return 0;
+}
+#endif
+
+static const struct ct_atc atc_preset = {
+ .map_audio_buffer = ct_map_audio_buffer,
+ .unmap_audio_buffer = ct_unmap_audio_buffer,
+ .pcm_playback_prepare = atc_pcm_playback_prepare,
+ .pcm_release_resources = atc_pcm_release_resources,
+ .pcm_playback_start = atc_pcm_playback_start,
+ .pcm_playback_stop = atc_pcm_stop,
+ .pcm_playback_position = atc_pcm_playback_position,
+ .pcm_capture_prepare = atc_pcm_capture_prepare,
+ .pcm_capture_start = atc_pcm_capture_start,
+ .pcm_capture_stop = atc_pcm_stop,
+ .pcm_capture_position = atc_pcm_capture_position,
+ .spdif_passthru_playback_prepare = spdif_passthru_playback_prepare,
+ .get_ptp_phys = atc_get_ptp_phys,
+ .select_line_in = atc_select_line_in,
+ .select_mic_in = atc_select_mic_in,
+ .select_digit_io = atc_select_digit_io,
+ .line_front_unmute = atc_line_front_unmute,
+ .line_surround_unmute = atc_line_surround_unmute,
+ .line_clfe_unmute = atc_line_clfe_unmute,
+ .line_rear_unmute = atc_line_rear_unmute,
+ .line_in_unmute = atc_line_in_unmute,
+ .mic_unmute = atc_mic_unmute,
+ .spdif_out_unmute = atc_spdif_out_unmute,
+ .spdif_in_unmute = atc_spdif_in_unmute,
+ .spdif_out_get_status = atc_spdif_out_get_status,
+ .spdif_out_set_status = atc_spdif_out_set_status,
+ .spdif_out_passthru = atc_spdif_out_passthru,
+ .capabilities = atc_capabilities,
+ .output_switch_get = atc_output_switch_get,
+ .output_switch_put = atc_output_switch_put,
+ .mic_source_switch_get = atc_mic_source_switch_get,
+ .mic_source_switch_put = atc_mic_source_switch_put,
+#ifdef CONFIG_PM_SLEEP
+ .suspend = atc_suspend,
+ .resume = atc_resume,
+#endif
+};
+
+/**
+ * ct_atc_create - create and initialize a hardware manager
+ * @card: corresponding alsa card object
+ * @pci: corresponding kernel pci device object
+ * @rsr: reference sampling rate
+ * @msr: master sampling rate
+ * @chip_type: CHIPTYP enum values
+ * @ssid: vendor ID (upper 16 bits) and device ID (lower 16 bits)
+ * @ratc: return created object address in it
+ *
+ * Creates and initializes a hardware manager.
+ *
+ * Creates kmallocated ct_atc structure. Initializes hardware.
+ * Returns 0 if succeeds, or negative error code if fails.
+ */
+
+int ct_atc_create(struct snd_card *card, struct pci_dev *pci,
+ unsigned int rsr, unsigned int msr,
+ int chip_type, unsigned int ssid,
+ struct ct_atc **ratc)
+{
+ struct ct_atc *atc;
+ static const struct snd_device_ops ops = {
+ .dev_free = atc_dev_free,
+ };
+ int err;
+
+ *ratc = NULL;
+
+ atc = kzalloc(sizeof(*atc), GFP_KERNEL);
+ if (!atc)
+ return -ENOMEM;
+
+ /* Set operations */
+ *atc = atc_preset;
+
+ atc->card = card;
+ atc->pci = pci;
+ atc->rsr = rsr;
+ atc->msr = msr;
+ atc->chip_type = chip_type;
+
+ mutex_init(&atc->atc_mutex);
+
+ /* Find card model */
+ err = atc_identify_card(atc, ssid);
+ if (err < 0) {
+ dev_err(card->dev, "ctatc: Card not recognised\n");
+ goto error1;
+ }
+
+ /* Set up device virtual memory management object */
+ err = ct_vm_create(&atc->vm, pci);
+ if (err < 0)
+ goto error1;
+
+ /* Create all atc hw devices */
+ err = atc_create_hw_devs(atc);
+ if (err < 0)
+ goto error1;
+
+ err = ct_mixer_create(atc, (struct ct_mixer **)&atc->mixer);
+ if (err) {
+ dev_err(card->dev, "Failed to create mixer obj!!!\n");
+ goto error1;
+ }
+
+ /* Get resources */
+ err = atc_get_resources(atc);
+ if (err < 0)
+ goto error1;
+
+ /* Build topology */
+ atc_connect_resources(atc);
+
+ atc->timer = ct_timer_new(atc);
+ if (!atc->timer) {
+ err = -ENOMEM;
+ goto error1;
+ }
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, atc, &ops);
+ if (err < 0)
+ goto error1;
+
+ *ratc = atc;
+ return 0;
+
+error1:
+ ct_atc_destroy(atc);
+ dev_err(card->dev, "Something wrong!!!\n");
+ return err;
+}
diff --git a/sound/pci/ctxfi/ctatc.h b/sound/pci/ctxfi/ctatc.h
new file mode 100644
index 000000000..0bc7b71d9
--- /dev/null
+++ b/sound/pci/ctxfi/ctatc.h
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctatc.h
+ *
+ * @Brief
+ * This file contains the definition of the device resource management object.
+ *
+ * @Author Liu Chun
+ * @Date Mar 28 2008
+ */
+
+#ifndef CTATC_H
+#define CTATC_H
+
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/timer.h>
+#include <sound/core.h>
+
+#include "ctvmem.h"
+#include "cthardware.h"
+#include "ctresource.h"
+
+enum CTALSADEVS { /* Types of alsa devices */
+ FRONT,
+ SURROUND,
+ CLFE,
+ SIDE,
+ IEC958,
+ MIXER,
+ NUM_CTALSADEVS /* This should always be the last */
+};
+
+struct ct_atc_chip_sub_details {
+ u16 subsys;
+ const char *nm_model;
+};
+
+struct ct_atc_chip_details {
+ u16 vendor;
+ u16 device;
+ const struct ct_atc_chip_sub_details *sub_details;
+ const char *nm_card;
+};
+
+struct ct_atc;
+struct ct_timer;
+struct ct_timer_instance;
+
+/* alsa pcm stream descriptor */
+struct ct_atc_pcm {
+ struct snd_pcm_substream *substream;
+ void (*interrupt)(struct ct_atc_pcm *apcm);
+ struct ct_timer_instance *timer;
+ unsigned int started:1;
+
+ /* Only mono and interleaved modes are supported now. */
+ struct ct_vm_block *vm_block;
+ void *src; /* SRC for interacting with host memory */
+ void **srccs; /* SRCs for sample rate conversion */
+ void **srcimps; /* SRC Input Mappers */
+ void **amixers; /* AMIXERs for routing converted data */
+ void *mono; /* A SUM resource for mixing chs to one */
+ unsigned char n_srcc; /* Number of converting SRCs */
+ unsigned char n_srcimp; /* Number of SRC Input Mappers */
+ unsigned char n_amixer; /* Number of AMIXERs */
+};
+
+/* Chip resource management object */
+struct ct_atc {
+ struct pci_dev *pci;
+ struct snd_card *card;
+ unsigned int rsr; /* reference sample rate in Hz */
+ unsigned int msr; /* master sample rate in rsr */
+ unsigned int pll_rate; /* current rate of Phase Lock Loop */
+
+ int chip_type;
+ int model;
+ const char *chip_name;
+ const char *model_name;
+
+ struct ct_vm *vm; /* device virtual memory manager for this card */
+ int (*map_audio_buffer)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+ void (*unmap_audio_buffer)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+ unsigned long (*get_ptp_phys)(struct ct_atc *atc, int index);
+
+ struct mutex atc_mutex;
+
+ int (*pcm_playback_prepare)(struct ct_atc *atc,
+ struct ct_atc_pcm *apcm);
+ int (*pcm_playback_start)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+ int (*pcm_playback_stop)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+ int (*pcm_playback_position)(struct ct_atc *atc,
+ struct ct_atc_pcm *apcm);
+ int (*spdif_passthru_playback_prepare)(struct ct_atc *atc,
+ struct ct_atc_pcm *apcm);
+ int (*pcm_capture_prepare)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+ int (*pcm_capture_start)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+ int (*pcm_capture_stop)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+ int (*pcm_capture_position)(struct ct_atc *atc,
+ struct ct_atc_pcm *apcm);
+ int (*pcm_release_resources)(struct ct_atc *atc,
+ struct ct_atc_pcm *apcm);
+ int (*select_line_in)(struct ct_atc *atc);
+ int (*select_mic_in)(struct ct_atc *atc);
+ int (*select_digit_io)(struct ct_atc *atc);
+ int (*line_front_unmute)(struct ct_atc *atc, unsigned char state);
+ int (*line_surround_unmute)(struct ct_atc *atc, unsigned char state);
+ int (*line_clfe_unmute)(struct ct_atc *atc, unsigned char state);
+ int (*line_rear_unmute)(struct ct_atc *atc, unsigned char state);
+ int (*line_in_unmute)(struct ct_atc *atc, unsigned char state);
+ int (*mic_unmute)(struct ct_atc *atc, unsigned char state);
+ int (*spdif_out_unmute)(struct ct_atc *atc, unsigned char state);
+ int (*spdif_in_unmute)(struct ct_atc *atc, unsigned char state);
+ int (*spdif_out_get_status)(struct ct_atc *atc, unsigned int *status);
+ int (*spdif_out_set_status)(struct ct_atc *atc, unsigned int status);
+ int (*spdif_out_passthru)(struct ct_atc *atc, unsigned char state);
+ struct capabilities (*capabilities)(struct ct_atc *atc);
+ int (*output_switch_get)(struct ct_atc *atc);
+ int (*output_switch_put)(struct ct_atc *atc, int position);
+ int (*mic_source_switch_get)(struct ct_atc *atc);
+ int (*mic_source_switch_put)(struct ct_atc *atc, int position);
+
+ /* Don't touch! Used for internal object. */
+ void *rsc_mgrs[NUM_RSCTYP]; /* chip resource managers */
+ void *mixer; /* internal mixer object */
+ struct hw *hw; /* chip specific hardware access object */
+ void **daios; /* digital audio io resources */
+ void **pcm; /* SUMs for collecting all pcm stream */
+ void **srcs; /* Sample Rate Converters for input signal */
+ void **srcimps; /* input mappers for SRCs */
+ unsigned char n_daio;
+ unsigned char n_src;
+ unsigned char n_srcimp;
+ unsigned char n_pcm;
+
+ struct ct_timer *timer;
+
+#ifdef CONFIG_PM_SLEEP
+ int (*suspend)(struct ct_atc *atc);
+ int (*resume)(struct ct_atc *atc);
+#define NUM_PCMS (NUM_CTALSADEVS - 1)
+ struct snd_pcm *pcms[NUM_PCMS];
+#endif
+};
+
+
+int ct_atc_create(struct snd_card *card, struct pci_dev *pci,
+ unsigned int rsr, unsigned int msr, int chip_type,
+ unsigned int subsysid, struct ct_atc **ratc);
+int ct_atc_create_alsa_devs(struct ct_atc *atc);
+
+#endif /* CTATC_H */
diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c
new file mode 100644
index 000000000..7fc720046
--- /dev/null
+++ b/sound/pci/ctxfi/ctdaio.c
@@ -0,0 +1,755 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctdaio.c
+ *
+ * @Brief
+ * This file contains the implementation of Digital Audio Input Output
+ * resource management object.
+ *
+ * @Author Liu Chun
+ * @Date May 23 2008
+ */
+
+#include "ctdaio.h"
+#include "cthardware.h"
+#include "ctimap.h"
+#include <linux/slab.h>
+#include <linux/kernel.h>
+
+#define DAIO_OUT_MAX SPDIFOO
+
+struct daio_usage {
+ unsigned short data;
+};
+
+struct daio_rsc_idx {
+ unsigned short left;
+ unsigned short right;
+};
+
+static const struct daio_rsc_idx idx_20k1[NUM_DAIOTYP] = {
+ [LINEO1] = {.left = 0x00, .right = 0x01},
+ [LINEO2] = {.left = 0x18, .right = 0x19},
+ [LINEO3] = {.left = 0x08, .right = 0x09},
+ [LINEO4] = {.left = 0x10, .right = 0x11},
+ [LINEIM] = {.left = 0x1b5, .right = 0x1bd},
+ [SPDIFOO] = {.left = 0x20, .right = 0x21},
+ [SPDIFIO] = {.left = 0x15, .right = 0x1d},
+ [SPDIFI1] = {.left = 0x95, .right = 0x9d},
+};
+
+static const struct daio_rsc_idx idx_20k2[NUM_DAIOTYP] = {
+ [LINEO1] = {.left = 0x40, .right = 0x41},
+ [LINEO2] = {.left = 0x60, .right = 0x61},
+ [LINEO3] = {.left = 0x50, .right = 0x51},
+ [LINEO4] = {.left = 0x70, .right = 0x71},
+ [LINEIM] = {.left = 0x45, .right = 0xc5},
+ [MIC] = {.left = 0x55, .right = 0xd5},
+ [SPDIFOO] = {.left = 0x00, .right = 0x01},
+ [SPDIFIO] = {.left = 0x05, .right = 0x85},
+};
+
+static void daio_master(struct rsc *rsc)
+{
+ /* Actually, this is not the resource index of DAIO.
+ * For DAO, it is the input mapper index. And, for DAI,
+ * it is the output time-slot index. */
+ rsc->conj = rsc->idx;
+}
+
+static int daio_index(const struct rsc *rsc)
+{
+ return rsc->conj;
+}
+
+static void daio_out_next_conj(struct rsc *rsc)
+{
+ rsc->conj += 2;
+}
+
+static void daio_in_next_conj_20k1(struct rsc *rsc)
+{
+ rsc->conj += 0x200;
+}
+
+static void daio_in_next_conj_20k2(struct rsc *rsc)
+{
+ rsc->conj += 0x100;
+}
+
+static const struct rsc_ops daio_out_rsc_ops = {
+ .master = daio_master,
+ .next_conj = daio_out_next_conj,
+ .index = daio_index,
+ .output_slot = NULL,
+};
+
+static const struct rsc_ops daio_in_rsc_ops_20k1 = {
+ .master = daio_master,
+ .next_conj = daio_in_next_conj_20k1,
+ .index = NULL,
+ .output_slot = daio_index,
+};
+
+static const struct rsc_ops daio_in_rsc_ops_20k2 = {
+ .master = daio_master,
+ .next_conj = daio_in_next_conj_20k2,
+ .index = NULL,
+ .output_slot = daio_index,
+};
+
+static unsigned int daio_device_index(enum DAIOTYP type, struct hw *hw)
+{
+ switch (hw->chip_type) {
+ case ATC20K1:
+ switch (type) {
+ case SPDIFOO: return 0;
+ case SPDIFIO: return 0;
+ case SPDIFI1: return 1;
+ case LINEO1: return 4;
+ case LINEO2: return 7;
+ case LINEO3: return 5;
+ case LINEO4: return 6;
+ case LINEIM: return 7;
+ default: return -EINVAL;
+ }
+ case ATC20K2:
+ switch (type) {
+ case SPDIFOO: return 0;
+ case SPDIFIO: return 0;
+ case LINEO1: return 4;
+ case LINEO2: return 7;
+ case LINEO3: return 5;
+ case LINEO4: return 6;
+ case LINEIM: return 4;
+ case MIC: return 5;
+ default: return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int dao_rsc_reinit(struct dao *dao, const struct dao_desc *desc);
+
+static int dao_spdif_get_spos(struct dao *dao, unsigned int *spos)
+{
+ dao->hw->dao_get_spos(dao->ctrl_blk, spos);
+ return 0;
+}
+
+static int dao_spdif_set_spos(struct dao *dao, unsigned int spos)
+{
+ dao->hw->dao_set_spos(dao->ctrl_blk, spos);
+ return 0;
+}
+
+static int dao_commit_write(struct dao *dao)
+{
+ dao->hw->dao_commit_write(dao->hw,
+ daio_device_index(dao->daio.type, dao->hw), dao->ctrl_blk);
+ return 0;
+}
+
+static int dao_set_left_input(struct dao *dao, struct rsc *input)
+{
+ struct imapper *entry;
+ struct daio *daio = &dao->daio;
+ int i;
+
+ entry = kzalloc((sizeof(*entry) * daio->rscl.msr), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ dao->ops->clear_left_input(dao);
+ /* Program master and conjugate resources */
+ input->ops->master(input);
+ daio->rscl.ops->master(&daio->rscl);
+ for (i = 0; i < daio->rscl.msr; i++, entry++) {
+ entry->slot = input->ops->output_slot(input);
+ entry->user = entry->addr = daio->rscl.ops->index(&daio->rscl);
+ dao->mgr->imap_add(dao->mgr, entry);
+ dao->imappers[i] = entry;
+
+ input->ops->next_conj(input);
+ daio->rscl.ops->next_conj(&daio->rscl);
+ }
+ input->ops->master(input);
+ daio->rscl.ops->master(&daio->rscl);
+
+ return 0;
+}
+
+static int dao_set_right_input(struct dao *dao, struct rsc *input)
+{
+ struct imapper *entry;
+ struct daio *daio = &dao->daio;
+ int i;
+
+ entry = kzalloc((sizeof(*entry) * daio->rscr.msr), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ dao->ops->clear_right_input(dao);
+ /* Program master and conjugate resources */
+ input->ops->master(input);
+ daio->rscr.ops->master(&daio->rscr);
+ for (i = 0; i < daio->rscr.msr; i++, entry++) {
+ entry->slot = input->ops->output_slot(input);
+ entry->user = entry->addr = daio->rscr.ops->index(&daio->rscr);
+ dao->mgr->imap_add(dao->mgr, entry);
+ dao->imappers[daio->rscl.msr + i] = entry;
+
+ input->ops->next_conj(input);
+ daio->rscr.ops->next_conj(&daio->rscr);
+ }
+ input->ops->master(input);
+ daio->rscr.ops->master(&daio->rscr);
+
+ return 0;
+}
+
+static int dao_clear_left_input(struct dao *dao)
+{
+ struct imapper *entry;
+ struct daio *daio = &dao->daio;
+ int i;
+
+ if (!dao->imappers[0])
+ return 0;
+
+ entry = dao->imappers[0];
+ dao->mgr->imap_delete(dao->mgr, entry);
+ /* Program conjugate resources */
+ for (i = 1; i < daio->rscl.msr; i++) {
+ entry = dao->imappers[i];
+ dao->mgr->imap_delete(dao->mgr, entry);
+ dao->imappers[i] = NULL;
+ }
+
+ kfree(dao->imappers[0]);
+ dao->imappers[0] = NULL;
+
+ return 0;
+}
+
+static int dao_clear_right_input(struct dao *dao)
+{
+ struct imapper *entry;
+ struct daio *daio = &dao->daio;
+ int i;
+
+ if (!dao->imappers[daio->rscl.msr])
+ return 0;
+
+ entry = dao->imappers[daio->rscl.msr];
+ dao->mgr->imap_delete(dao->mgr, entry);
+ /* Program conjugate resources */
+ for (i = 1; i < daio->rscr.msr; i++) {
+ entry = dao->imappers[daio->rscl.msr + i];
+ dao->mgr->imap_delete(dao->mgr, entry);
+ dao->imappers[daio->rscl.msr + i] = NULL;
+ }
+
+ kfree(dao->imappers[daio->rscl.msr]);
+ dao->imappers[daio->rscl.msr] = NULL;
+
+ return 0;
+}
+
+static const struct dao_rsc_ops dao_ops = {
+ .set_spos = dao_spdif_set_spos,
+ .commit_write = dao_commit_write,
+ .get_spos = dao_spdif_get_spos,
+ .reinit = dao_rsc_reinit,
+ .set_left_input = dao_set_left_input,
+ .set_right_input = dao_set_right_input,
+ .clear_left_input = dao_clear_left_input,
+ .clear_right_input = dao_clear_right_input,
+};
+
+static int dai_set_srt_srcl(struct dai *dai, struct rsc *src)
+{
+ src->ops->master(src);
+ dai->hw->dai_srt_set_srcm(dai->ctrl_blk, src->ops->index(src));
+ return 0;
+}
+
+static int dai_set_srt_srcr(struct dai *dai, struct rsc *src)
+{
+ src->ops->master(src);
+ dai->hw->dai_srt_set_srco(dai->ctrl_blk, src->ops->index(src));
+ return 0;
+}
+
+static int dai_set_srt_msr(struct dai *dai, unsigned int msr)
+{
+ unsigned int rsr;
+
+ for (rsr = 0; msr > 1; msr >>= 1)
+ rsr++;
+
+ dai->hw->dai_srt_set_rsr(dai->ctrl_blk, rsr);
+ return 0;
+}
+
+static int dai_set_enb_src(struct dai *dai, unsigned int enb)
+{
+ dai->hw->dai_srt_set_ec(dai->ctrl_blk, enb);
+ return 0;
+}
+
+static int dai_set_enb_srt(struct dai *dai, unsigned int enb)
+{
+ dai->hw->dai_srt_set_et(dai->ctrl_blk, enb);
+ return 0;
+}
+
+static int dai_commit_write(struct dai *dai)
+{
+ dai->hw->dai_commit_write(dai->hw,
+ daio_device_index(dai->daio.type, dai->hw), dai->ctrl_blk);
+ return 0;
+}
+
+static const struct dai_rsc_ops dai_ops = {
+ .set_srt_srcl = dai_set_srt_srcl,
+ .set_srt_srcr = dai_set_srt_srcr,
+ .set_srt_msr = dai_set_srt_msr,
+ .set_enb_src = dai_set_enb_src,
+ .set_enb_srt = dai_set_enb_srt,
+ .commit_write = dai_commit_write,
+};
+
+static int daio_rsc_init(struct daio *daio,
+ const struct daio_desc *desc,
+ struct hw *hw)
+{
+ int err;
+ unsigned int idx_l, idx_r;
+
+ switch (hw->chip_type) {
+ case ATC20K1:
+ idx_l = idx_20k1[desc->type].left;
+ idx_r = idx_20k1[desc->type].right;
+ break;
+ case ATC20K2:
+ idx_l = idx_20k2[desc->type].left;
+ idx_r = idx_20k2[desc->type].right;
+ break;
+ default:
+ return -EINVAL;
+ }
+ err = rsc_init(&daio->rscl, idx_l, DAIO, desc->msr, hw);
+ if (err)
+ return err;
+
+ err = rsc_init(&daio->rscr, idx_r, DAIO, desc->msr, hw);
+ if (err)
+ goto error1;
+
+ /* Set daio->rscl/r->ops to daio specific ones */
+ if (desc->type <= DAIO_OUT_MAX) {
+ daio->rscl.ops = daio->rscr.ops = &daio_out_rsc_ops;
+ } else {
+ switch (hw->chip_type) {
+ case ATC20K1:
+ daio->rscl.ops = daio->rscr.ops = &daio_in_rsc_ops_20k1;
+ break;
+ case ATC20K2:
+ daio->rscl.ops = daio->rscr.ops = &daio_in_rsc_ops_20k2;
+ break;
+ default:
+ break;
+ }
+ }
+ daio->type = desc->type;
+
+ return 0;
+
+error1:
+ rsc_uninit(&daio->rscl);
+ return err;
+}
+
+static int daio_rsc_uninit(struct daio *daio)
+{
+ rsc_uninit(&daio->rscl);
+ rsc_uninit(&daio->rscr);
+
+ return 0;
+}
+
+static int dao_rsc_init(struct dao *dao,
+ const struct daio_desc *desc,
+ struct daio_mgr *mgr)
+{
+ struct hw *hw = mgr->mgr.hw;
+ unsigned int conf;
+ int err;
+
+ err = daio_rsc_init(&dao->daio, desc, mgr->mgr.hw);
+ if (err)
+ return err;
+
+ dao->imappers = kzalloc(array3_size(sizeof(void *), desc->msr, 2),
+ GFP_KERNEL);
+ if (!dao->imappers) {
+ err = -ENOMEM;
+ goto error1;
+ }
+ dao->ops = &dao_ops;
+ dao->mgr = mgr;
+ dao->hw = hw;
+ err = hw->dao_get_ctrl_blk(&dao->ctrl_blk);
+ if (err)
+ goto error2;
+
+ hw->daio_mgr_dsb_dao(mgr->mgr.ctrl_blk,
+ daio_device_index(dao->daio.type, hw));
+ hw->daio_mgr_commit_write(hw, mgr->mgr.ctrl_blk);
+
+ conf = (desc->msr & 0x7) | (desc->passthru << 3);
+ hw->daio_mgr_dao_init(mgr->mgr.ctrl_blk,
+ daio_device_index(dao->daio.type, hw), conf);
+ hw->daio_mgr_enb_dao(mgr->mgr.ctrl_blk,
+ daio_device_index(dao->daio.type, hw));
+ hw->daio_mgr_commit_write(hw, mgr->mgr.ctrl_blk);
+
+ return 0;
+
+error2:
+ kfree(dao->imappers);
+ dao->imappers = NULL;
+error1:
+ daio_rsc_uninit(&dao->daio);
+ return err;
+}
+
+static int dao_rsc_uninit(struct dao *dao)
+{
+ if (dao->imappers) {
+ if (dao->imappers[0])
+ dao_clear_left_input(dao);
+
+ if (dao->imappers[dao->daio.rscl.msr])
+ dao_clear_right_input(dao);
+
+ kfree(dao->imappers);
+ dao->imappers = NULL;
+ }
+ dao->hw->dao_put_ctrl_blk(dao->ctrl_blk);
+ dao->hw = dao->ctrl_blk = NULL;
+ daio_rsc_uninit(&dao->daio);
+
+ return 0;
+}
+
+static int dao_rsc_reinit(struct dao *dao, const struct dao_desc *desc)
+{
+ struct daio_mgr *mgr = dao->mgr;
+ struct daio_desc dsc = {0};
+
+ dsc.type = dao->daio.type;
+ dsc.msr = desc->msr;
+ dsc.passthru = desc->passthru;
+ dao_rsc_uninit(dao);
+ return dao_rsc_init(dao, &dsc, mgr);
+}
+
+static int dai_rsc_init(struct dai *dai,
+ const struct daio_desc *desc,
+ struct daio_mgr *mgr)
+{
+ int err;
+ struct hw *hw = mgr->mgr.hw;
+ unsigned int rsr, msr;
+
+ err = daio_rsc_init(&dai->daio, desc, mgr->mgr.hw);
+ if (err)
+ return err;
+
+ dai->ops = &dai_ops;
+ dai->hw = mgr->mgr.hw;
+ err = hw->dai_get_ctrl_blk(&dai->ctrl_blk);
+ if (err)
+ goto error1;
+
+ for (rsr = 0, msr = desc->msr; msr > 1; msr >>= 1)
+ rsr++;
+
+ hw->dai_srt_set_rsr(dai->ctrl_blk, rsr);
+ hw->dai_srt_set_drat(dai->ctrl_blk, 0);
+ /* default to disabling control of a SRC */
+ hw->dai_srt_set_ec(dai->ctrl_blk, 0);
+ hw->dai_srt_set_et(dai->ctrl_blk, 0); /* default to disabling SRT */
+ hw->dai_commit_write(hw,
+ daio_device_index(dai->daio.type, dai->hw), dai->ctrl_blk);
+
+ return 0;
+
+error1:
+ daio_rsc_uninit(&dai->daio);
+ return err;
+}
+
+static int dai_rsc_uninit(struct dai *dai)
+{
+ dai->hw->dai_put_ctrl_blk(dai->ctrl_blk);
+ dai->hw = dai->ctrl_blk = NULL;
+ daio_rsc_uninit(&dai->daio);
+ return 0;
+}
+
+static int daio_mgr_get_rsc(struct rsc_mgr *mgr, enum DAIOTYP type)
+{
+ if (((struct daio_usage *)mgr->rscs)->data & (0x1 << type))
+ return -ENOENT;
+
+ ((struct daio_usage *)mgr->rscs)->data |= (0x1 << type);
+
+ return 0;
+}
+
+static int daio_mgr_put_rsc(struct rsc_mgr *mgr, enum DAIOTYP type)
+{
+ ((struct daio_usage *)mgr->rscs)->data &= ~(0x1 << type);
+
+ return 0;
+}
+
+static int get_daio_rsc(struct daio_mgr *mgr,
+ const struct daio_desc *desc,
+ struct daio **rdaio)
+{
+ int err;
+ unsigned long flags;
+
+ *rdaio = NULL;
+
+ /* Check whether there are sufficient daio resources to meet request. */
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ err = daio_mgr_get_rsc(&mgr->mgr, desc->type);
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ if (err) {
+ dev_err(mgr->card->dev,
+ "Can't meet DAIO resource request!\n");
+ return err;
+ }
+
+ err = -ENOMEM;
+ /* Allocate mem for daio resource */
+ if (desc->type <= DAIO_OUT_MAX) {
+ struct dao *dao = kzalloc(sizeof(*dao), GFP_KERNEL);
+ if (!dao)
+ goto error;
+
+ err = dao_rsc_init(dao, desc, mgr);
+ if (err) {
+ kfree(dao);
+ goto error;
+ }
+
+ *rdaio = &dao->daio;
+ } else {
+ struct dai *dai = kzalloc(sizeof(*dai), GFP_KERNEL);
+ if (!dai)
+ goto error;
+
+ err = dai_rsc_init(dai, desc, mgr);
+ if (err) {
+ kfree(dai);
+ goto error;
+ }
+
+ *rdaio = &dai->daio;
+ }
+
+ mgr->daio_enable(mgr, *rdaio);
+ mgr->commit_write(mgr);
+
+ return 0;
+
+error:
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ daio_mgr_put_rsc(&mgr->mgr, desc->type);
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ return err;
+}
+
+static int put_daio_rsc(struct daio_mgr *mgr, struct daio *daio)
+{
+ unsigned long flags;
+
+ mgr->daio_disable(mgr, daio);
+ mgr->commit_write(mgr);
+
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ daio_mgr_put_rsc(&mgr->mgr, daio->type);
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+
+ if (daio->type <= DAIO_OUT_MAX) {
+ dao_rsc_uninit(container_of(daio, struct dao, daio));
+ kfree(container_of(daio, struct dao, daio));
+ } else {
+ dai_rsc_uninit(container_of(daio, struct dai, daio));
+ kfree(container_of(daio, struct dai, daio));
+ }
+
+ return 0;
+}
+
+static int daio_mgr_enb_daio(struct daio_mgr *mgr, struct daio *daio)
+{
+ struct hw *hw = mgr->mgr.hw;
+
+ if (DAIO_OUT_MAX >= daio->type) {
+ hw->daio_mgr_enb_dao(mgr->mgr.ctrl_blk,
+ daio_device_index(daio->type, hw));
+ } else {
+ hw->daio_mgr_enb_dai(mgr->mgr.ctrl_blk,
+ daio_device_index(daio->type, hw));
+ }
+ return 0;
+}
+
+static int daio_mgr_dsb_daio(struct daio_mgr *mgr, struct daio *daio)
+{
+ struct hw *hw = mgr->mgr.hw;
+
+ if (DAIO_OUT_MAX >= daio->type) {
+ hw->daio_mgr_dsb_dao(mgr->mgr.ctrl_blk,
+ daio_device_index(daio->type, hw));
+ } else {
+ hw->daio_mgr_dsb_dai(mgr->mgr.ctrl_blk,
+ daio_device_index(daio->type, hw));
+ }
+ return 0;
+}
+
+static int daio_map_op(void *data, struct imapper *entry)
+{
+ struct rsc_mgr *mgr = &((struct daio_mgr *)data)->mgr;
+ struct hw *hw = mgr->hw;
+
+ hw->daio_mgr_set_imaparc(mgr->ctrl_blk, entry->slot);
+ hw->daio_mgr_set_imapnxt(mgr->ctrl_blk, entry->next);
+ hw->daio_mgr_set_imapaddr(mgr->ctrl_blk, entry->addr);
+ hw->daio_mgr_commit_write(mgr->hw, mgr->ctrl_blk);
+
+ return 0;
+}
+
+static int daio_imap_add(struct daio_mgr *mgr, struct imapper *entry)
+{
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&mgr->imap_lock, flags);
+ if (!entry->addr && mgr->init_imap_added) {
+ input_mapper_delete(&mgr->imappers, mgr->init_imap,
+ daio_map_op, mgr);
+ mgr->init_imap_added = 0;
+ }
+ err = input_mapper_add(&mgr->imappers, entry, daio_map_op, mgr);
+ spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+ return err;
+}
+
+static int daio_imap_delete(struct daio_mgr *mgr, struct imapper *entry)
+{
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&mgr->imap_lock, flags);
+ err = input_mapper_delete(&mgr->imappers, entry, daio_map_op, mgr);
+ if (list_empty(&mgr->imappers)) {
+ input_mapper_add(&mgr->imappers, mgr->init_imap,
+ daio_map_op, mgr);
+ mgr->init_imap_added = 1;
+ }
+ spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+ return err;
+}
+
+static int daio_mgr_commit_write(struct daio_mgr *mgr)
+{
+ struct hw *hw = mgr->mgr.hw;
+
+ hw->daio_mgr_commit_write(hw, mgr->mgr.ctrl_blk);
+ return 0;
+}
+
+int daio_mgr_create(struct hw *hw, struct daio_mgr **rdaio_mgr)
+{
+ int err, i;
+ struct daio_mgr *daio_mgr;
+ struct imapper *entry;
+
+ *rdaio_mgr = NULL;
+ daio_mgr = kzalloc(sizeof(*daio_mgr), GFP_KERNEL);
+ if (!daio_mgr)
+ return -ENOMEM;
+
+ err = rsc_mgr_init(&daio_mgr->mgr, DAIO, NUM_DAIOTYP, hw);
+ if (err)
+ goto error1;
+
+ spin_lock_init(&daio_mgr->mgr_lock);
+ spin_lock_init(&daio_mgr->imap_lock);
+ INIT_LIST_HEAD(&daio_mgr->imappers);
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry) {
+ err = -ENOMEM;
+ goto error2;
+ }
+ entry->slot = entry->addr = entry->next = entry->user = 0;
+ list_add(&entry->list, &daio_mgr->imappers);
+ daio_mgr->init_imap = entry;
+ daio_mgr->init_imap_added = 1;
+
+ daio_mgr->get_daio = get_daio_rsc;
+ daio_mgr->put_daio = put_daio_rsc;
+ daio_mgr->daio_enable = daio_mgr_enb_daio;
+ daio_mgr->daio_disable = daio_mgr_dsb_daio;
+ daio_mgr->imap_add = daio_imap_add;
+ daio_mgr->imap_delete = daio_imap_delete;
+ daio_mgr->commit_write = daio_mgr_commit_write;
+ daio_mgr->card = hw->card;
+
+ for (i = 0; i < 8; i++) {
+ hw->daio_mgr_dsb_dao(daio_mgr->mgr.ctrl_blk, i);
+ hw->daio_mgr_dsb_dai(daio_mgr->mgr.ctrl_blk, i);
+ }
+ hw->daio_mgr_commit_write(hw, daio_mgr->mgr.ctrl_blk);
+
+ *rdaio_mgr = daio_mgr;
+
+ return 0;
+
+error2:
+ rsc_mgr_uninit(&daio_mgr->mgr);
+error1:
+ kfree(daio_mgr);
+ return err;
+}
+
+int daio_mgr_destroy(struct daio_mgr *daio_mgr)
+{
+ unsigned long flags;
+
+ /* free daio input mapper list */
+ spin_lock_irqsave(&daio_mgr->imap_lock, flags);
+ free_input_mapper_list(&daio_mgr->imappers);
+ spin_unlock_irqrestore(&daio_mgr->imap_lock, flags);
+
+ rsc_mgr_uninit(&daio_mgr->mgr);
+ kfree(daio_mgr);
+
+ return 0;
+}
+
diff --git a/sound/pci/ctxfi/ctdaio.h b/sound/pci/ctxfi/ctdaio.h
new file mode 100644
index 000000000..bd6310f48
--- /dev/null
+++ b/sound/pci/ctxfi/ctdaio.h
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctdaio.h
+ *
+ * @Brief
+ * This file contains the definition of Digital Audio Input Output
+ * resource management object.
+ *
+ * @Author Liu Chun
+ * @Date May 23 2008
+ */
+
+#ifndef CTDAIO_H
+#define CTDAIO_H
+
+#include "ctresource.h"
+#include "ctimap.h"
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <sound/core.h>
+
+/* Define the descriptor of a daio resource */
+enum DAIOTYP {
+ LINEO1,
+ LINEO2,
+ LINEO3,
+ LINEO4,
+ SPDIFOO, /* S/PDIF Out (Flexijack/Optical) */
+ LINEIM,
+ SPDIFIO, /* S/PDIF In (Flexijack/Optical) on the card */
+ MIC, /* Dedicated mic on Titanium HD */
+ SPDIFI1, /* S/PDIF In on internal Drive Bay */
+ NUM_DAIOTYP
+};
+
+struct dao_rsc_ops;
+struct dai_rsc_ops;
+struct daio_mgr;
+
+struct daio {
+ struct rsc rscl; /* Basic resource info for left TX/RX */
+ struct rsc rscr; /* Basic resource info for right TX/RX */
+ enum DAIOTYP type;
+};
+
+struct dao {
+ struct daio daio;
+ const struct dao_rsc_ops *ops; /* DAO specific operations */
+ struct imapper **imappers;
+ struct daio_mgr *mgr;
+ struct hw *hw;
+ void *ctrl_blk;
+};
+
+struct dai {
+ struct daio daio;
+ const struct dai_rsc_ops *ops; /* DAI specific operations */
+ struct hw *hw;
+ void *ctrl_blk;
+};
+
+struct dao_desc {
+ unsigned int msr:4;
+ unsigned int passthru:1;
+};
+
+struct dao_rsc_ops {
+ int (*set_spos)(struct dao *dao, unsigned int spos);
+ int (*commit_write)(struct dao *dao);
+ int (*get_spos)(struct dao *dao, unsigned int *spos);
+ int (*reinit)(struct dao *dao, const struct dao_desc *desc);
+ int (*set_left_input)(struct dao *dao, struct rsc *input);
+ int (*set_right_input)(struct dao *dao, struct rsc *input);
+ int (*clear_left_input)(struct dao *dao);
+ int (*clear_right_input)(struct dao *dao);
+};
+
+struct dai_rsc_ops {
+ int (*set_srt_srcl)(struct dai *dai, struct rsc *src);
+ int (*set_srt_srcr)(struct dai *dai, struct rsc *src);
+ int (*set_srt_msr)(struct dai *dai, unsigned int msr);
+ int (*set_enb_src)(struct dai *dai, unsigned int enb);
+ int (*set_enb_srt)(struct dai *dai, unsigned int enb);
+ int (*commit_write)(struct dai *dai);
+};
+
+/* Define daio resource request description info */
+struct daio_desc {
+ unsigned int type:4;
+ unsigned int msr:4;
+ unsigned int passthru:1;
+};
+
+struct daio_mgr {
+ struct rsc_mgr mgr; /* Basic resource manager info */
+ struct snd_card *card; /* pointer to this card */
+ spinlock_t mgr_lock;
+ spinlock_t imap_lock;
+ struct list_head imappers;
+ struct imapper *init_imap;
+ unsigned int init_imap_added;
+
+ /* request one daio resource */
+ int (*get_daio)(struct daio_mgr *mgr,
+ const struct daio_desc *desc, struct daio **rdaio);
+ /* return one daio resource */
+ int (*put_daio)(struct daio_mgr *mgr, struct daio *daio);
+ int (*daio_enable)(struct daio_mgr *mgr, struct daio *daio);
+ int (*daio_disable)(struct daio_mgr *mgr, struct daio *daio);
+ int (*imap_add)(struct daio_mgr *mgr, struct imapper *entry);
+ int (*imap_delete)(struct daio_mgr *mgr, struct imapper *entry);
+ int (*commit_write)(struct daio_mgr *mgr);
+};
+
+/* Constructor and destructor of daio resource manager */
+int daio_mgr_create(struct hw *hw, struct daio_mgr **rdaio_mgr);
+int daio_mgr_destroy(struct daio_mgr *daio_mgr);
+
+#endif /* CTDAIO_H */
diff --git a/sound/pci/ctxfi/cthardware.c b/sound/pci/ctxfi/cthardware.c
new file mode 100644
index 000000000..1d5064486
--- /dev/null
+++ b/sound/pci/ctxfi/cthardware.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File cthardware.c
+ *
+ * @Brief
+ * This file contains the implementation of hardware access methord.
+ *
+ * @Author Liu Chun
+ * @Date Jun 26 2008
+ */
+
+#include "cthardware.h"
+#include "cthw20k1.h"
+#include "cthw20k2.h"
+#include <linux/bug.h>
+
+int create_hw_obj(struct pci_dev *pci, enum CHIPTYP chip_type,
+ enum CTCARDS model, struct hw **rhw)
+{
+ int err;
+
+ switch (chip_type) {
+ case ATC20K1:
+ err = create_20k1_hw_obj(rhw);
+ break;
+ case ATC20K2:
+ err = create_20k2_hw_obj(rhw);
+ break;
+ default:
+ err = -ENODEV;
+ break;
+ }
+ if (err)
+ return err;
+
+ (*rhw)->pci = pci;
+ (*rhw)->chip_type = chip_type;
+ (*rhw)->model = model;
+
+ return 0;
+}
+
+int destroy_hw_obj(struct hw *hw)
+{
+ int err;
+
+ switch (hw->pci->device) {
+ case 0x0005: /* 20k1 device */
+ err = destroy_20k1_hw_obj(hw);
+ break;
+ case 0x000B: /* 20k2 device */
+ err = destroy_20k2_hw_obj(hw);
+ break;
+ default:
+ err = -ENODEV;
+ break;
+ }
+
+ return err;
+}
+
+unsigned int get_field(unsigned int data, unsigned int field)
+{
+ int i;
+
+ if (WARN_ON(!field))
+ return 0;
+ /* @field should always be greater than 0 */
+ for (i = 0; !(field & (1 << i)); )
+ i++;
+
+ return (data & field) >> i;
+}
+
+void set_field(unsigned int *data, unsigned int field, unsigned int value)
+{
+ int i;
+
+ if (WARN_ON(!field))
+ return;
+ /* @field should always be greater than 0 */
+ for (i = 0; !(field & (1 << i)); )
+ i++;
+
+ *data = (*data & (~field)) | ((value << i) & field);
+}
+
diff --git a/sound/pci/ctxfi/cthardware.h b/sound/pci/ctxfi/cthardware.h
new file mode 100644
index 000000000..2875cec83
--- /dev/null
+++ b/sound/pci/ctxfi/cthardware.h
@@ -0,0 +1,214 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File cthardware.h
+ *
+ * @Brief
+ * This file contains the definition of hardware access methord.
+ *
+ * @Author Liu Chun
+ * @Date May 13 2008
+ */
+
+#ifndef CTHARDWARE_H
+#define CTHARDWARE_H
+
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+
+enum CHIPTYP {
+ ATC20K1,
+ ATC20K2,
+ ATCNONE
+};
+
+enum CTCARDS {
+ /* 20k1 models */
+ CTSB046X,
+ CT20K1_MODEL_FIRST = CTSB046X,
+ CTSB055X,
+ CTSB073X,
+ CTUAA,
+ CT20K1_UNKNOWN,
+ /* 20k2 models */
+ CTSB0760,
+ CT20K2_MODEL_FIRST = CTSB0760,
+ CTHENDRIX,
+ CTSB0880,
+ CTSB1270,
+ CT20K2_UNKNOWN,
+ NUM_CTCARDS /* This should always be the last */
+};
+
+/* Type of input source for ADC */
+enum ADCSRC{
+ ADC_MICIN,
+ ADC_LINEIN,
+ ADC_VIDEO,
+ ADC_AUX,
+ ADC_NONE /* Switch to digital input */
+};
+
+struct card_conf {
+ /* device virtual mem page table page physical addr
+ * (supporting one page table page now) */
+ unsigned long vm_pgt_phys;
+ unsigned int rsr; /* reference sample rate in Hzs*/
+ unsigned int msr; /* master sample rate in rsrs */
+};
+
+struct capabilities {
+ unsigned int digit_io_switch:1;
+ unsigned int dedicated_mic:1;
+ unsigned int output_switch:1;
+ unsigned int mic_source_switch:1;
+};
+
+struct hw {
+ int (*card_init)(struct hw *hw, struct card_conf *info);
+ int (*card_stop)(struct hw *hw);
+ int (*pll_init)(struct hw *hw, unsigned int rsr);
+#ifdef CONFIG_PM_SLEEP
+ int (*suspend)(struct hw *hw);
+ int (*resume)(struct hw *hw, struct card_conf *info);
+#endif
+ int (*is_adc_source_selected)(struct hw *hw, enum ADCSRC source);
+ int (*select_adc_source)(struct hw *hw, enum ADCSRC source);
+ struct capabilities (*capabilities)(struct hw *hw);
+ int (*output_switch_get)(struct hw *hw);
+ int (*output_switch_put)(struct hw *hw, int position);
+ int (*mic_source_switch_get)(struct hw *hw);
+ int (*mic_source_switch_put)(struct hw *hw, int position);
+
+ /* SRC operations */
+ int (*src_rsc_get_ctrl_blk)(void **rblk);
+ int (*src_rsc_put_ctrl_blk)(void *blk);
+ int (*src_set_state)(void *blk, unsigned int state);
+ int (*src_set_bm)(void *blk, unsigned int bm);
+ int (*src_set_rsr)(void *blk, unsigned int rsr);
+ int (*src_set_sf)(void *blk, unsigned int sf);
+ int (*src_set_wr)(void *blk, unsigned int wr);
+ int (*src_set_pm)(void *blk, unsigned int pm);
+ int (*src_set_rom)(void *blk, unsigned int rom);
+ int (*src_set_vo)(void *blk, unsigned int vo);
+ int (*src_set_st)(void *blk, unsigned int st);
+ int (*src_set_ie)(void *blk, unsigned int ie);
+ int (*src_set_ilsz)(void *blk, unsigned int ilsz);
+ int (*src_set_bp)(void *blk, unsigned int bp);
+ int (*src_set_cisz)(void *blk, unsigned int cisz);
+ int (*src_set_ca)(void *blk, unsigned int ca);
+ int (*src_set_sa)(void *blk, unsigned int sa);
+ int (*src_set_la)(void *blk, unsigned int la);
+ int (*src_set_pitch)(void *blk, unsigned int pitch);
+ int (*src_set_clear_zbufs)(void *blk, unsigned int clear);
+ int (*src_set_dirty)(void *blk, unsigned int flags);
+ int (*src_set_dirty_all)(void *blk);
+ int (*src_commit_write)(struct hw *hw, unsigned int idx, void *blk);
+ int (*src_get_ca)(struct hw *hw, unsigned int idx, void *blk);
+ unsigned int (*src_get_dirty)(void *blk);
+ unsigned int (*src_dirty_conj_mask)(void);
+ int (*src_mgr_get_ctrl_blk)(void **rblk);
+ int (*src_mgr_put_ctrl_blk)(void *blk);
+ /* syncly enable src @idx */
+ int (*src_mgr_enbs_src)(void *blk, unsigned int idx);
+ /* enable src @idx */
+ int (*src_mgr_enb_src)(void *blk, unsigned int idx);
+ /* disable src @idx */
+ int (*src_mgr_dsb_src)(void *blk, unsigned int idx);
+ int (*src_mgr_commit_write)(struct hw *hw, void *blk);
+
+ /* SRC Input Mapper operations */
+ int (*srcimp_mgr_get_ctrl_blk)(void **rblk);
+ int (*srcimp_mgr_put_ctrl_blk)(void *blk);
+ int (*srcimp_mgr_set_imaparc)(void *blk, unsigned int slot);
+ int (*srcimp_mgr_set_imapuser)(void *blk, unsigned int user);
+ int (*srcimp_mgr_set_imapnxt)(void *blk, unsigned int next);
+ int (*srcimp_mgr_set_imapaddr)(void *blk, unsigned int addr);
+ int (*srcimp_mgr_commit_write)(struct hw *hw, void *blk);
+
+ /* AMIXER operations */
+ int (*amixer_rsc_get_ctrl_blk)(void **rblk);
+ int (*amixer_rsc_put_ctrl_blk)(void *blk);
+ int (*amixer_mgr_get_ctrl_blk)(void **rblk);
+ int (*amixer_mgr_put_ctrl_blk)(void *blk);
+ int (*amixer_set_mode)(void *blk, unsigned int mode);
+ int (*amixer_set_iv)(void *blk, unsigned int iv);
+ int (*amixer_set_x)(void *blk, unsigned int x);
+ int (*amixer_set_y)(void *blk, unsigned int y);
+ int (*amixer_set_sadr)(void *blk, unsigned int sadr);
+ int (*amixer_set_se)(void *blk, unsigned int se);
+ int (*amixer_set_dirty)(void *blk, unsigned int flags);
+ int (*amixer_set_dirty_all)(void *blk);
+ int (*amixer_commit_write)(struct hw *hw, unsigned int idx, void *blk);
+ int (*amixer_get_y)(void *blk);
+ unsigned int (*amixer_get_dirty)(void *blk);
+
+ /* DAIO operations */
+ int (*dai_get_ctrl_blk)(void **rblk);
+ int (*dai_put_ctrl_blk)(void *blk);
+ int (*dai_srt_set_srco)(void *blk, unsigned int src);
+ int (*dai_srt_set_srcm)(void *blk, unsigned int src);
+ int (*dai_srt_set_rsr)(void *blk, unsigned int rsr);
+ int (*dai_srt_set_drat)(void *blk, unsigned int drat);
+ int (*dai_srt_set_ec)(void *blk, unsigned int ec);
+ int (*dai_srt_set_et)(void *blk, unsigned int et);
+ int (*dai_commit_write)(struct hw *hw, unsigned int idx, void *blk);
+ int (*dao_get_ctrl_blk)(void **rblk);
+ int (*dao_put_ctrl_blk)(void *blk);
+ int (*dao_set_spos)(void *blk, unsigned int spos);
+ int (*dao_commit_write)(struct hw *hw, unsigned int idx, void *blk);
+ int (*dao_get_spos)(void *blk, unsigned int *spos);
+
+ int (*daio_mgr_get_ctrl_blk)(struct hw *hw, void **rblk);
+ int (*daio_mgr_put_ctrl_blk)(void *blk);
+ int (*daio_mgr_enb_dai)(void *blk, unsigned int idx);
+ int (*daio_mgr_dsb_dai)(void *blk, unsigned int idx);
+ int (*daio_mgr_enb_dao)(void *blk, unsigned int idx);
+ int (*daio_mgr_dsb_dao)(void *blk, unsigned int idx);
+ int (*daio_mgr_dao_init)(void *blk, unsigned int idx,
+ unsigned int conf);
+ int (*daio_mgr_set_imaparc)(void *blk, unsigned int slot);
+ int (*daio_mgr_set_imapnxt)(void *blk, unsigned int next);
+ int (*daio_mgr_set_imapaddr)(void *blk, unsigned int addr);
+ int (*daio_mgr_commit_write)(struct hw *hw, void *blk);
+
+ int (*set_timer_irq)(struct hw *hw, int enable);
+ int (*set_timer_tick)(struct hw *hw, unsigned int tick);
+ unsigned int (*get_wc)(struct hw *hw);
+
+ void (*irq_callback)(void *data, unsigned int bit);
+ void *irq_callback_data;
+
+ struct pci_dev *pci; /* the pci kernel structure of this card */
+ struct snd_card *card; /* pointer to this card */
+ int irq;
+ unsigned long io_base;
+ void __iomem *mem_base;
+
+ enum CHIPTYP chip_type;
+ enum CTCARDS model;
+};
+
+int create_hw_obj(struct pci_dev *pci, enum CHIPTYP chip_type,
+ enum CTCARDS model, struct hw **rhw);
+int destroy_hw_obj(struct hw *hw);
+
+unsigned int get_field(unsigned int data, unsigned int field);
+void set_field(unsigned int *data, unsigned int field, unsigned int value);
+
+/* IRQ bits */
+#define PLL_INT (1 << 10) /* PLL input-clock out-of-range */
+#define FI_INT (1 << 9) /* forced interrupt */
+#define IT_INT (1 << 8) /* timer interrupt */
+#define PCI_INT (1 << 7) /* PCI bus error pending */
+#define URT_INT (1 << 6) /* UART Tx/Rx */
+#define GPI_INT (1 << 5) /* GPI pin */
+#define MIX_INT (1 << 4) /* mixer parameter segment FIFO channels */
+#define DAI_INT (1 << 3) /* DAI (SR-tracker or SPDIF-receiver) */
+#define TP_INT (1 << 2) /* transport priority queue */
+#define DSP_INT (1 << 1) /* DSP */
+#define SRC_INT (1 << 0) /* SRC channels */
+
+#endif /* CTHARDWARE_H */
diff --git a/sound/pci/ctxfi/cthw20k1.c b/sound/pci/ctxfi/cthw20k1.c
new file mode 100644
index 000000000..9edbf5d8c
--- /dev/null
+++ b/sound/pci/ctxfi/cthw20k1.c
@@ -0,0 +1,2277 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File cthw20k1.c
+ *
+ * @Brief
+ * This file contains the implementation of hardware access methord for 20k1.
+ *
+ * @Author Liu Chun
+ * @Date Jun 24 2008
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include "cthw20k1.h"
+#include "ct20k1reg.h"
+
+struct hw20k1 {
+ struct hw hw;
+ spinlock_t reg_20k1_lock;
+ spinlock_t reg_pci_lock;
+};
+
+static u32 hw_read_20kx(struct hw *hw, u32 reg);
+static void hw_write_20kx(struct hw *hw, u32 reg, u32 data);
+static u32 hw_read_pci(struct hw *hw, u32 reg);
+static void hw_write_pci(struct hw *hw, u32 reg, u32 data);
+
+/*
+ * Type definition block.
+ * The layout of control structures can be directly applied on 20k2 chip.
+ */
+
+/*
+ * SRC control block definitions.
+ */
+
+/* SRC resource control block */
+#define SRCCTL_STATE 0x00000007
+#define SRCCTL_BM 0x00000008
+#define SRCCTL_RSR 0x00000030
+#define SRCCTL_SF 0x000001C0
+#define SRCCTL_WR 0x00000200
+#define SRCCTL_PM 0x00000400
+#define SRCCTL_ROM 0x00001800
+#define SRCCTL_VO 0x00002000
+#define SRCCTL_ST 0x00004000
+#define SRCCTL_IE 0x00008000
+#define SRCCTL_ILSZ 0x000F0000
+#define SRCCTL_BP 0x00100000
+
+#define SRCCCR_CISZ 0x000007FF
+#define SRCCCR_CWA 0x001FF800
+#define SRCCCR_D 0x00200000
+#define SRCCCR_RS 0x01C00000
+#define SRCCCR_NAL 0x3E000000
+#define SRCCCR_RA 0xC0000000
+
+#define SRCCA_CA 0x03FFFFFF
+#define SRCCA_RS 0x1C000000
+#define SRCCA_NAL 0xE0000000
+
+#define SRCSA_SA 0x03FFFFFF
+
+#define SRCLA_LA 0x03FFFFFF
+
+/* Mixer Parameter Ring ram Low and Hight register.
+ * Fixed-point value in 8.24 format for parameter channel */
+#define MPRLH_PITCH 0xFFFFFFFF
+
+/* SRC resource register dirty flags */
+union src_dirty {
+ struct {
+ u16 ctl:1;
+ u16 ccr:1;
+ u16 sa:1;
+ u16 la:1;
+ u16 ca:1;
+ u16 mpr:1;
+ u16 czbfs:1; /* Clear Z-Buffers */
+ u16 rsv:9;
+ } bf;
+ u16 data;
+};
+
+struct src_rsc_ctrl_blk {
+ unsigned int ctl;
+ unsigned int ccr;
+ unsigned int ca;
+ unsigned int sa;
+ unsigned int la;
+ unsigned int mpr;
+ union src_dirty dirty;
+};
+
+/* SRC manager control block */
+union src_mgr_dirty {
+ struct {
+ u16 enb0:1;
+ u16 enb1:1;
+ u16 enb2:1;
+ u16 enb3:1;
+ u16 enb4:1;
+ u16 enb5:1;
+ u16 enb6:1;
+ u16 enb7:1;
+ u16 enbsa:1;
+ u16 rsv:7;
+ } bf;
+ u16 data;
+};
+
+struct src_mgr_ctrl_blk {
+ unsigned int enbsa;
+ unsigned int enb[8];
+ union src_mgr_dirty dirty;
+};
+
+/* SRCIMP manager control block */
+#define SRCAIM_ARC 0x00000FFF
+#define SRCAIM_NXT 0x00FF0000
+#define SRCAIM_SRC 0xFF000000
+
+struct srcimap {
+ unsigned int srcaim;
+ unsigned int idx;
+};
+
+/* SRCIMP manager register dirty flags */
+union srcimp_mgr_dirty {
+ struct {
+ u16 srcimap:1;
+ u16 rsv:15;
+ } bf;
+ u16 data;
+};
+
+struct srcimp_mgr_ctrl_blk {
+ struct srcimap srcimap;
+ union srcimp_mgr_dirty dirty;
+};
+
+/*
+ * Function implementation block.
+ */
+
+static int src_get_rsc_ctrl_blk(void **rblk)
+{
+ struct src_rsc_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int src_put_rsc_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int src_set_state(void *blk, unsigned int state)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_STATE, state);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_bm(void *blk, unsigned int bm)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_BM, bm);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_rsr(void *blk, unsigned int rsr)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_RSR, rsr);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_sf(void *blk, unsigned int sf)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_SF, sf);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_wr(void *blk, unsigned int wr)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_WR, wr);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_pm(void *blk, unsigned int pm)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_PM, pm);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_rom(void *blk, unsigned int rom)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_ROM, rom);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_vo(void *blk, unsigned int vo)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_VO, vo);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_st(void *blk, unsigned int st)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_ST, st);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_ie(void *blk, unsigned int ie)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_IE, ie);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_ilsz(void *blk, unsigned int ilsz)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_ILSZ, ilsz);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_bp(void *blk, unsigned int bp)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_BP, bp);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_cisz(void *blk, unsigned int cisz)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ccr, SRCCCR_CISZ, cisz);
+ ctl->dirty.bf.ccr = 1;
+ return 0;
+}
+
+static int src_set_ca(void *blk, unsigned int ca)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ca, SRCCA_CA, ca);
+ ctl->dirty.bf.ca = 1;
+ return 0;
+}
+
+static int src_set_sa(void *blk, unsigned int sa)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->sa, SRCSA_SA, sa);
+ ctl->dirty.bf.sa = 1;
+ return 0;
+}
+
+static int src_set_la(void *blk, unsigned int la)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->la, SRCLA_LA, la);
+ ctl->dirty.bf.la = 1;
+ return 0;
+}
+
+static int src_set_pitch(void *blk, unsigned int pitch)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->mpr, MPRLH_PITCH, pitch);
+ ctl->dirty.bf.mpr = 1;
+ return 0;
+}
+
+static int src_set_clear_zbufs(void *blk, unsigned int clear)
+{
+ ((struct src_rsc_ctrl_blk *)blk)->dirty.bf.czbfs = (clear ? 1 : 0);
+ return 0;
+}
+
+static int src_set_dirty(void *blk, unsigned int flags)
+{
+ ((struct src_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff);
+ return 0;
+}
+
+static int src_set_dirty_all(void *blk)
+{
+ ((struct src_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0);
+ return 0;
+}
+
+#define AR_SLOT_SIZE 4096
+#define AR_SLOT_BLOCK_SIZE 16
+#define AR_PTS_PITCH 6
+#define AR_PARAM_SRC_OFFSET 0x60
+
+static unsigned int src_param_pitch_mixer(unsigned int src_idx)
+{
+ return ((src_idx << 4) + AR_PTS_PITCH + AR_SLOT_SIZE
+ - AR_PARAM_SRC_OFFSET) % AR_SLOT_SIZE;
+
+}
+
+static int src_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+ int i;
+
+ if (ctl->dirty.bf.czbfs) {
+ /* Clear Z-Buffer registers */
+ for (i = 0; i < 8; i++)
+ hw_write_20kx(hw, SRCUPZ+idx*0x100+i*0x4, 0);
+
+ for (i = 0; i < 4; i++)
+ hw_write_20kx(hw, SRCDN0Z+idx*0x100+i*0x4, 0);
+
+ for (i = 0; i < 8; i++)
+ hw_write_20kx(hw, SRCDN1Z+idx*0x100+i*0x4, 0);
+
+ ctl->dirty.bf.czbfs = 0;
+ }
+ if (ctl->dirty.bf.mpr) {
+ /* Take the parameter mixer resource in the same group as that
+ * the idx src is in for simplicity. Unlike src, all conjugate
+ * parameter mixer resources must be programmed for
+ * corresponding conjugate src resources. */
+ unsigned int pm_idx = src_param_pitch_mixer(idx);
+ hw_write_20kx(hw, PRING_LO_HI+4*pm_idx, ctl->mpr);
+ hw_write_20kx(hw, PMOPLO+8*pm_idx, 0x3);
+ hw_write_20kx(hw, PMOPHI+8*pm_idx, 0x0);
+ ctl->dirty.bf.mpr = 0;
+ }
+ if (ctl->dirty.bf.sa) {
+ hw_write_20kx(hw, SRCSA+idx*0x100, ctl->sa);
+ ctl->dirty.bf.sa = 0;
+ }
+ if (ctl->dirty.bf.la) {
+ hw_write_20kx(hw, SRCLA+idx*0x100, ctl->la);
+ ctl->dirty.bf.la = 0;
+ }
+ if (ctl->dirty.bf.ca) {
+ hw_write_20kx(hw, SRCCA+idx*0x100, ctl->ca);
+ ctl->dirty.bf.ca = 0;
+ }
+
+ /* Write srccf register */
+ hw_write_20kx(hw, SRCCF+idx*0x100, 0x0);
+
+ if (ctl->dirty.bf.ccr) {
+ hw_write_20kx(hw, SRCCCR+idx*0x100, ctl->ccr);
+ ctl->dirty.bf.ccr = 0;
+ }
+ if (ctl->dirty.bf.ctl) {
+ hw_write_20kx(hw, SRCCTL+idx*0x100, ctl->ctl);
+ ctl->dirty.bf.ctl = 0;
+ }
+
+ return 0;
+}
+
+static int src_get_ca(struct hw *hw, unsigned int idx, void *blk)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ ctl->ca = hw_read_20kx(hw, SRCCA+idx*0x100);
+ ctl->dirty.bf.ca = 0;
+
+ return get_field(ctl->ca, SRCCA_CA);
+}
+
+static unsigned int src_get_dirty(void *blk)
+{
+ return ((struct src_rsc_ctrl_blk *)blk)->dirty.data;
+}
+
+static unsigned int src_dirty_conj_mask(void)
+{
+ return 0x20;
+}
+
+static int src_mgr_enbs_src(void *blk, unsigned int idx)
+{
+ ((struct src_mgr_ctrl_blk *)blk)->enbsa = ~(0x0);
+ ((struct src_mgr_ctrl_blk *)blk)->dirty.bf.enbsa = 1;
+ ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32));
+ return 0;
+}
+
+static int src_mgr_enb_src(void *blk, unsigned int idx)
+{
+ ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32));
+ ((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32));
+ return 0;
+}
+
+static int src_mgr_dsb_src(void *blk, unsigned int idx)
+{
+ ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] &= ~(0x1 << (idx%32));
+ ((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32));
+ return 0;
+}
+
+static int src_mgr_commit_write(struct hw *hw, void *blk)
+{
+ struct src_mgr_ctrl_blk *ctl = blk;
+ int i;
+ unsigned int ret;
+
+ if (ctl->dirty.bf.enbsa) {
+ do {
+ ret = hw_read_20kx(hw, SRCENBSTAT);
+ } while (ret & 0x1);
+ hw_write_20kx(hw, SRCENBS, ctl->enbsa);
+ ctl->dirty.bf.enbsa = 0;
+ }
+ for (i = 0; i < 8; i++) {
+ if ((ctl->dirty.data & (0x1 << i))) {
+ hw_write_20kx(hw, SRCENB+(i*0x100), ctl->enb[i]);
+ ctl->dirty.data &= ~(0x1 << i);
+ }
+ }
+
+ return 0;
+}
+
+static int src_mgr_get_ctrl_blk(void **rblk)
+{
+ struct src_mgr_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int src_mgr_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int srcimp_mgr_get_ctrl_blk(void **rblk)
+{
+ struct srcimp_mgr_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int srcimp_mgr_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int srcimp_mgr_set_imaparc(void *blk, unsigned int slot)
+{
+ struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srcimap.srcaim, SRCAIM_ARC, slot);
+ ctl->dirty.bf.srcimap = 1;
+ return 0;
+}
+
+static int srcimp_mgr_set_imapuser(void *blk, unsigned int user)
+{
+ struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srcimap.srcaim, SRCAIM_SRC, user);
+ ctl->dirty.bf.srcimap = 1;
+ return 0;
+}
+
+static int srcimp_mgr_set_imapnxt(void *blk, unsigned int next)
+{
+ struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srcimap.srcaim, SRCAIM_NXT, next);
+ ctl->dirty.bf.srcimap = 1;
+ return 0;
+}
+
+static int srcimp_mgr_set_imapaddr(void *blk, unsigned int addr)
+{
+ struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+ ctl->srcimap.idx = addr;
+ ctl->dirty.bf.srcimap = 1;
+ return 0;
+}
+
+static int srcimp_mgr_commit_write(struct hw *hw, void *blk)
+{
+ struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+ if (ctl->dirty.bf.srcimap) {
+ hw_write_20kx(hw, SRCIMAP+ctl->srcimap.idx*0x100,
+ ctl->srcimap.srcaim);
+ ctl->dirty.bf.srcimap = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * AMIXER control block definitions.
+ */
+
+#define AMOPLO_M 0x00000003
+#define AMOPLO_X 0x0003FFF0
+#define AMOPLO_Y 0xFFFC0000
+
+#define AMOPHI_SADR 0x000000FF
+#define AMOPHI_SE 0x80000000
+
+/* AMIXER resource register dirty flags */
+union amixer_dirty {
+ struct {
+ u16 amoplo:1;
+ u16 amophi:1;
+ u16 rsv:14;
+ } bf;
+ u16 data;
+};
+
+/* AMIXER resource control block */
+struct amixer_rsc_ctrl_blk {
+ unsigned int amoplo;
+ unsigned int amophi;
+ union amixer_dirty dirty;
+};
+
+static int amixer_set_mode(void *blk, unsigned int mode)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amoplo, AMOPLO_M, mode);
+ ctl->dirty.bf.amoplo = 1;
+ return 0;
+}
+
+static int amixer_set_iv(void *blk, unsigned int iv)
+{
+ /* 20k1 amixer does not have this field */
+ return 0;
+}
+
+static int amixer_set_x(void *blk, unsigned int x)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amoplo, AMOPLO_X, x);
+ ctl->dirty.bf.amoplo = 1;
+ return 0;
+}
+
+static int amixer_set_y(void *blk, unsigned int y)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amoplo, AMOPLO_Y, y);
+ ctl->dirty.bf.amoplo = 1;
+ return 0;
+}
+
+static int amixer_set_sadr(void *blk, unsigned int sadr)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amophi, AMOPHI_SADR, sadr);
+ ctl->dirty.bf.amophi = 1;
+ return 0;
+}
+
+static int amixer_set_se(void *blk, unsigned int se)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amophi, AMOPHI_SE, se);
+ ctl->dirty.bf.amophi = 1;
+ return 0;
+}
+
+static int amixer_set_dirty(void *blk, unsigned int flags)
+{
+ ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff);
+ return 0;
+}
+
+static int amixer_set_dirty_all(void *blk)
+{
+ ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0);
+ return 0;
+}
+
+static int amixer_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ if (ctl->dirty.bf.amoplo || ctl->dirty.bf.amophi) {
+ hw_write_20kx(hw, AMOPLO+idx*8, ctl->amoplo);
+ ctl->dirty.bf.amoplo = 0;
+ hw_write_20kx(hw, AMOPHI+idx*8, ctl->amophi);
+ ctl->dirty.bf.amophi = 0;
+ }
+
+ return 0;
+}
+
+static int amixer_get_y(void *blk)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ return get_field(ctl->amoplo, AMOPLO_Y);
+}
+
+static unsigned int amixer_get_dirty(void *blk)
+{
+ return ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data;
+}
+
+static int amixer_rsc_get_ctrl_blk(void **rblk)
+{
+ struct amixer_rsc_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int amixer_rsc_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int amixer_mgr_get_ctrl_blk(void **rblk)
+{
+ /*amixer_mgr_ctrl_blk_t *blk;*/
+
+ *rblk = NULL;
+ /*blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;*/
+
+ return 0;
+}
+
+static int amixer_mgr_put_ctrl_blk(void *blk)
+{
+ /*kfree((amixer_mgr_ctrl_blk_t *)blk);*/
+
+ return 0;
+}
+
+/*
+ * DAIO control block definitions.
+ */
+
+/* Receiver Sample Rate Tracker Control register */
+#define SRTCTL_SRCR 0x000000FF
+#define SRTCTL_SRCL 0x0000FF00
+#define SRTCTL_RSR 0x00030000
+#define SRTCTL_DRAT 0x000C0000
+#define SRTCTL_RLE 0x10000000
+#define SRTCTL_RLP 0x20000000
+#define SRTCTL_EC 0x40000000
+#define SRTCTL_ET 0x80000000
+
+/* DAIO Receiver register dirty flags */
+union dai_dirty {
+ struct {
+ u16 srtctl:1;
+ u16 rsv:15;
+ } bf;
+ u16 data;
+};
+
+/* DAIO Receiver control block */
+struct dai_ctrl_blk {
+ unsigned int srtctl;
+ union dai_dirty dirty;
+};
+
+/* S/PDIF Transmitter register dirty flags */
+union dao_dirty {
+ struct {
+ u16 spos:1;
+ u16 rsv:15;
+ } bf;
+ u16 data;
+};
+
+/* S/PDIF Transmitter control block */
+struct dao_ctrl_blk {
+ unsigned int spos; /* S/PDIF Output Channel Status Register */
+ union dao_dirty dirty;
+};
+
+/* Audio Input Mapper RAM */
+#define AIM_ARC 0x00000FFF
+#define AIM_NXT 0x007F0000
+
+struct daoimap {
+ unsigned int aim;
+ unsigned int idx;
+};
+
+/* I2S Transmitter/Receiver Control register */
+#define I2SCTL_EA 0x00000004
+#define I2SCTL_EI 0x00000010
+
+/* S/PDIF Transmitter Control register */
+#define SPOCTL_OE 0x00000001
+#define SPOCTL_OS 0x0000000E
+#define SPOCTL_RIV 0x00000010
+#define SPOCTL_LIV 0x00000020
+#define SPOCTL_SR 0x000000C0
+
+/* S/PDIF Receiver Control register */
+#define SPICTL_EN 0x00000001
+#define SPICTL_I24 0x00000002
+#define SPICTL_IB 0x00000004
+#define SPICTL_SM 0x00000008
+#define SPICTL_VM 0x00000010
+
+/* DAIO manager register dirty flags */
+union daio_mgr_dirty {
+ struct {
+ u32 i2soctl:4;
+ u32 i2sictl:4;
+ u32 spoctl:4;
+ u32 spictl:4;
+ u32 daoimap:1;
+ u32 rsv:15;
+ } bf;
+ u32 data;
+};
+
+/* DAIO manager control block */
+struct daio_mgr_ctrl_blk {
+ unsigned int i2sctl;
+ unsigned int spoctl;
+ unsigned int spictl;
+ struct daoimap daoimap;
+ union daio_mgr_dirty dirty;
+};
+
+static int dai_srt_set_srcr(void *blk, unsigned int src)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srtctl, SRTCTL_SRCR, src);
+ ctl->dirty.bf.srtctl = 1;
+ return 0;
+}
+
+static int dai_srt_set_srcl(void *blk, unsigned int src)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srtctl, SRTCTL_SRCL, src);
+ ctl->dirty.bf.srtctl = 1;
+ return 0;
+}
+
+static int dai_srt_set_rsr(void *blk, unsigned int rsr)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srtctl, SRTCTL_RSR, rsr);
+ ctl->dirty.bf.srtctl = 1;
+ return 0;
+}
+
+static int dai_srt_set_drat(void *blk, unsigned int drat)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srtctl, SRTCTL_DRAT, drat);
+ ctl->dirty.bf.srtctl = 1;
+ return 0;
+}
+
+static int dai_srt_set_ec(void *blk, unsigned int ec)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srtctl, SRTCTL_EC, ec ? 1 : 0);
+ ctl->dirty.bf.srtctl = 1;
+ return 0;
+}
+
+static int dai_srt_set_et(void *blk, unsigned int et)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srtctl, SRTCTL_ET, et ? 1 : 0);
+ ctl->dirty.bf.srtctl = 1;
+ return 0;
+}
+
+static int dai_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ if (ctl->dirty.bf.srtctl) {
+ if (idx < 4) {
+ /* S/PDIF SRTs */
+ hw_write_20kx(hw, SRTSCTL+0x4*idx, ctl->srtctl);
+ } else {
+ /* I2S SRT */
+ hw_write_20kx(hw, SRTICTL, ctl->srtctl);
+ }
+ ctl->dirty.bf.srtctl = 0;
+ }
+
+ return 0;
+}
+
+static int dai_get_ctrl_blk(void **rblk)
+{
+ struct dai_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int dai_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int dao_set_spos(void *blk, unsigned int spos)
+{
+ ((struct dao_ctrl_blk *)blk)->spos = spos;
+ ((struct dao_ctrl_blk *)blk)->dirty.bf.spos = 1;
+ return 0;
+}
+
+static int dao_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+ struct dao_ctrl_blk *ctl = blk;
+
+ if (ctl->dirty.bf.spos) {
+ if (idx < 4) {
+ /* S/PDIF SPOSx */
+ hw_write_20kx(hw, SPOS+0x4*idx, ctl->spos);
+ }
+ ctl->dirty.bf.spos = 0;
+ }
+
+ return 0;
+}
+
+static int dao_get_spos(void *blk, unsigned int *spos)
+{
+ *spos = ((struct dao_ctrl_blk *)blk)->spos;
+ return 0;
+}
+
+static int dao_get_ctrl_blk(void **rblk)
+{
+ struct dao_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int dao_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int daio_mgr_enb_dai(void *blk, unsigned int idx)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ if (idx < 4) {
+ /* S/PDIF input */
+ set_field(&ctl->spictl, SPICTL_EN << (idx*8), 1);
+ ctl->dirty.bf.spictl |= (0x1 << idx);
+ } else {
+ /* I2S input */
+ idx %= 4;
+ set_field(&ctl->i2sctl, I2SCTL_EI << (idx*8), 1);
+ ctl->dirty.bf.i2sictl |= (0x1 << idx);
+ }
+ return 0;
+}
+
+static int daio_mgr_dsb_dai(void *blk, unsigned int idx)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ if (idx < 4) {
+ /* S/PDIF input */
+ set_field(&ctl->spictl, SPICTL_EN << (idx*8), 0);
+ ctl->dirty.bf.spictl |= (0x1 << idx);
+ } else {
+ /* I2S input */
+ idx %= 4;
+ set_field(&ctl->i2sctl, I2SCTL_EI << (idx*8), 0);
+ ctl->dirty.bf.i2sictl |= (0x1 << idx);
+ }
+ return 0;
+}
+
+static int daio_mgr_enb_dao(void *blk, unsigned int idx)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ if (idx < 4) {
+ /* S/PDIF output */
+ set_field(&ctl->spoctl, SPOCTL_OE << (idx*8), 1);
+ ctl->dirty.bf.spoctl |= (0x1 << idx);
+ } else {
+ /* I2S output */
+ idx %= 4;
+ set_field(&ctl->i2sctl, I2SCTL_EA << (idx*8), 1);
+ ctl->dirty.bf.i2soctl |= (0x1 << idx);
+ }
+ return 0;
+}
+
+static int daio_mgr_dsb_dao(void *blk, unsigned int idx)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ if (idx < 4) {
+ /* S/PDIF output */
+ set_field(&ctl->spoctl, SPOCTL_OE << (idx*8), 0);
+ ctl->dirty.bf.spoctl |= (0x1 << idx);
+ } else {
+ /* I2S output */
+ idx %= 4;
+ set_field(&ctl->i2sctl, I2SCTL_EA << (idx*8), 0);
+ ctl->dirty.bf.i2soctl |= (0x1 << idx);
+ }
+ return 0;
+}
+
+static int daio_mgr_dao_init(void *blk, unsigned int idx, unsigned int conf)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ if (idx < 4) {
+ /* S/PDIF output */
+ switch ((conf & 0x7)) {
+ case 0:
+ set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 3);
+ break; /* CDIF */
+ case 1:
+ set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 0);
+ break;
+ case 2:
+ set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 1);
+ break;
+ case 4:
+ set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 2);
+ break;
+ default:
+ break;
+ }
+ set_field(&ctl->spoctl, SPOCTL_LIV << (idx*8),
+ (conf >> 4) & 0x1); /* Non-audio */
+ set_field(&ctl->spoctl, SPOCTL_RIV << (idx*8),
+ (conf >> 4) & 0x1); /* Non-audio */
+ set_field(&ctl->spoctl, SPOCTL_OS << (idx*8),
+ ((conf >> 3) & 0x1) ? 2 : 2); /* Raw */
+
+ ctl->dirty.bf.spoctl |= (0x1 << idx);
+ } else {
+ /* I2S output */
+ /*idx %= 4; */
+ }
+ return 0;
+}
+
+static int daio_mgr_set_imaparc(void *blk, unsigned int slot)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->daoimap.aim, AIM_ARC, slot);
+ ctl->dirty.bf.daoimap = 1;
+ return 0;
+}
+
+static int daio_mgr_set_imapnxt(void *blk, unsigned int next)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->daoimap.aim, AIM_NXT, next);
+ ctl->dirty.bf.daoimap = 1;
+ return 0;
+}
+
+static int daio_mgr_set_imapaddr(void *blk, unsigned int addr)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ ctl->daoimap.idx = addr;
+ ctl->dirty.bf.daoimap = 1;
+ return 0;
+}
+
+static int daio_mgr_commit_write(struct hw *hw, void *blk)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+ int i;
+
+ if (ctl->dirty.bf.i2sictl || ctl->dirty.bf.i2soctl) {
+ for (i = 0; i < 4; i++) {
+ if ((ctl->dirty.bf.i2sictl & (0x1 << i)))
+ ctl->dirty.bf.i2sictl &= ~(0x1 << i);
+
+ if ((ctl->dirty.bf.i2soctl & (0x1 << i)))
+ ctl->dirty.bf.i2soctl &= ~(0x1 << i);
+ }
+ hw_write_20kx(hw, I2SCTL, ctl->i2sctl);
+ mdelay(1);
+ }
+ if (ctl->dirty.bf.spoctl) {
+ for (i = 0; i < 4; i++) {
+ if ((ctl->dirty.bf.spoctl & (0x1 << i)))
+ ctl->dirty.bf.spoctl &= ~(0x1 << i);
+ }
+ hw_write_20kx(hw, SPOCTL, ctl->spoctl);
+ mdelay(1);
+ }
+ if (ctl->dirty.bf.spictl) {
+ for (i = 0; i < 4; i++) {
+ if ((ctl->dirty.bf.spictl & (0x1 << i)))
+ ctl->dirty.bf.spictl &= ~(0x1 << i);
+ }
+ hw_write_20kx(hw, SPICTL, ctl->spictl);
+ mdelay(1);
+ }
+ if (ctl->dirty.bf.daoimap) {
+ hw_write_20kx(hw, DAOIMAP+ctl->daoimap.idx*4,
+ ctl->daoimap.aim);
+ ctl->dirty.bf.daoimap = 0;
+ }
+
+ return 0;
+}
+
+static int daio_mgr_get_ctrl_blk(struct hw *hw, void **rblk)
+{
+ struct daio_mgr_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ blk->i2sctl = hw_read_20kx(hw, I2SCTL);
+ blk->spoctl = hw_read_20kx(hw, SPOCTL);
+ blk->spictl = hw_read_20kx(hw, SPICTL);
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int daio_mgr_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+/* Timer interrupt */
+static int set_timer_irq(struct hw *hw, int enable)
+{
+ hw_write_20kx(hw, GIE, enable ? IT_INT : 0);
+ return 0;
+}
+
+static int set_timer_tick(struct hw *hw, unsigned int ticks)
+{
+ if (ticks)
+ ticks |= TIMR_IE | TIMR_IP;
+ hw_write_20kx(hw, TIMR, ticks);
+ return 0;
+}
+
+static unsigned int get_wc(struct hw *hw)
+{
+ return hw_read_20kx(hw, WC);
+}
+
+/* Card hardware initialization block */
+struct dac_conf {
+ unsigned int msr; /* master sample rate in rsrs */
+};
+
+struct adc_conf {
+ unsigned int msr; /* master sample rate in rsrs */
+ unsigned char input; /* the input source of ADC */
+ unsigned char mic20db; /* boost mic by 20db if input is microphone */
+};
+
+struct daio_conf {
+ unsigned int msr; /* master sample rate in rsrs */
+};
+
+struct trn_conf {
+ unsigned long vm_pgt_phys;
+};
+
+static int hw_daio_init(struct hw *hw, const struct daio_conf *info)
+{
+ u32 i2sorg;
+ u32 spdorg;
+
+ /* Read I2S CTL. Keep original value. */
+ /*i2sorg = hw_read_20kx(hw, I2SCTL);*/
+ i2sorg = 0x94040404; /* enable all audio out and I2S-D input */
+ /* Program I2S with proper master sample rate and enable
+ * the correct I2S channel. */
+ i2sorg &= 0xfffffffc;
+
+ /* Enable S/PDIF-out-A in fixed 24-bit data
+ * format and default to 48kHz. */
+ /* Disable all before doing any changes. */
+ hw_write_20kx(hw, SPOCTL, 0x0);
+ spdorg = 0x05;
+
+ switch (info->msr) {
+ case 1:
+ i2sorg |= 1;
+ spdorg |= (0x0 << 6);
+ break;
+ case 2:
+ i2sorg |= 2;
+ spdorg |= (0x1 << 6);
+ break;
+ case 4:
+ i2sorg |= 3;
+ spdorg |= (0x2 << 6);
+ break;
+ default:
+ i2sorg |= 1;
+ break;
+ }
+
+ hw_write_20kx(hw, I2SCTL, i2sorg);
+ hw_write_20kx(hw, SPOCTL, spdorg);
+
+ /* Enable S/PDIF-in-A in fixed 24-bit data format. */
+ /* Disable all before doing any changes. */
+ hw_write_20kx(hw, SPICTL, 0x0);
+ mdelay(1);
+ spdorg = 0x0a0a0a0a;
+ hw_write_20kx(hw, SPICTL, spdorg);
+ mdelay(1);
+
+ return 0;
+}
+
+/* TRANSPORT operations */
+static int hw_trn_init(struct hw *hw, const struct trn_conf *info)
+{
+ u32 trnctl;
+ u32 ptp_phys_low, ptp_phys_high;
+
+ /* Set up device page table */
+ if ((~0UL) == info->vm_pgt_phys) {
+ dev_err(hw->card->dev,
+ "Wrong device page table page address!\n");
+ return -1;
+ }
+
+ trnctl = 0x13; /* 32-bit, 4k-size page */
+ ptp_phys_low = (u32)info->vm_pgt_phys;
+ ptp_phys_high = upper_32_bits(info->vm_pgt_phys);
+ if (sizeof(void *) == 8) /* 64bit address */
+ trnctl |= (1 << 2);
+#if 0 /* Only 4k h/w pages for simplicitiy */
+#if PAGE_SIZE == 8192
+ trnctl |= (1<<5);
+#endif
+#endif
+ hw_write_20kx(hw, PTPALX, ptp_phys_low);
+ hw_write_20kx(hw, PTPAHX, ptp_phys_high);
+ hw_write_20kx(hw, TRNCTL, trnctl);
+ hw_write_20kx(hw, TRNIS, 0x200c01); /* really needed? */
+
+ return 0;
+}
+
+/* Card initialization */
+#define GCTL_EAC 0x00000001
+#define GCTL_EAI 0x00000002
+#define GCTL_BEP 0x00000004
+#define GCTL_BES 0x00000008
+#define GCTL_DSP 0x00000010
+#define GCTL_DBP 0x00000020
+#define GCTL_ABP 0x00000040
+#define GCTL_TBP 0x00000080
+#define GCTL_SBP 0x00000100
+#define GCTL_FBP 0x00000200
+#define GCTL_XA 0x00000400
+#define GCTL_ET 0x00000800
+#define GCTL_PR 0x00001000
+#define GCTL_MRL 0x00002000
+#define GCTL_SDE 0x00004000
+#define GCTL_SDI 0x00008000
+#define GCTL_SM 0x00010000
+#define GCTL_SR 0x00020000
+#define GCTL_SD 0x00040000
+#define GCTL_SE 0x00080000
+#define GCTL_AID 0x00100000
+
+static int hw_pll_init(struct hw *hw, unsigned int rsr)
+{
+ unsigned int pllctl;
+ int i;
+
+ pllctl = (48000 == rsr) ? 0x1480a001 : 0x1480a731;
+ for (i = 0; i < 3; i++) {
+ if (hw_read_20kx(hw, PLLCTL) == pllctl)
+ break;
+
+ hw_write_20kx(hw, PLLCTL, pllctl);
+ msleep(40);
+ }
+ if (i >= 3) {
+ dev_alert(hw->card->dev, "PLL initialization failed!!!\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int hw_auto_init(struct hw *hw)
+{
+ unsigned int gctl;
+ int i;
+
+ gctl = hw_read_20kx(hw, GCTL);
+ set_field(&gctl, GCTL_EAI, 0);
+ hw_write_20kx(hw, GCTL, gctl);
+ set_field(&gctl, GCTL_EAI, 1);
+ hw_write_20kx(hw, GCTL, gctl);
+ mdelay(10);
+ for (i = 0; i < 400000; i++) {
+ gctl = hw_read_20kx(hw, GCTL);
+ if (get_field(gctl, GCTL_AID))
+ break;
+ }
+ if (!get_field(gctl, GCTL_AID)) {
+ dev_alert(hw->card->dev, "Card Auto-init failed!!!\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int i2c_unlock(struct hw *hw)
+{
+ if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa)
+ return 0;
+
+ hw_write_pci(hw, 0xcc, 0x8c);
+ hw_write_pci(hw, 0xcc, 0x0e);
+ if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa)
+ return 0;
+
+ hw_write_pci(hw, 0xcc, 0xee);
+ hw_write_pci(hw, 0xcc, 0xaa);
+ if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa)
+ return 0;
+
+ return -1;
+}
+
+static void i2c_lock(struct hw *hw)
+{
+ if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa)
+ hw_write_pci(hw, 0xcc, 0x00);
+}
+
+static void i2c_write(struct hw *hw, u32 device, u32 addr, u32 data)
+{
+ unsigned int ret;
+
+ do {
+ ret = hw_read_pci(hw, 0xEC);
+ } while (!(ret & 0x800000));
+ hw_write_pci(hw, 0xE0, device);
+ hw_write_pci(hw, 0xE4, (data << 8) | (addr & 0xff));
+}
+
+/* DAC operations */
+
+static int hw_reset_dac(struct hw *hw)
+{
+ u32 i;
+ u16 gpioorg;
+ unsigned int ret;
+
+ if (i2c_unlock(hw))
+ return -1;
+
+ do {
+ ret = hw_read_pci(hw, 0xEC);
+ } while (!(ret & 0x800000));
+ hw_write_pci(hw, 0xEC, 0x05); /* write to i2c status control */
+
+ /* To be effective, need to reset the DAC twice. */
+ for (i = 0; i < 2; i++) {
+ /* set gpio */
+ msleep(100);
+ gpioorg = (u16)hw_read_20kx(hw, GPIO);
+ gpioorg &= 0xfffd;
+ hw_write_20kx(hw, GPIO, gpioorg);
+ mdelay(1);
+ hw_write_20kx(hw, GPIO, gpioorg | 0x2);
+ }
+
+ i2c_write(hw, 0x00180080, 0x01, 0x80);
+ i2c_write(hw, 0x00180080, 0x02, 0x10);
+
+ i2c_lock(hw);
+
+ return 0;
+}
+
+static int hw_dac_init(struct hw *hw, const struct dac_conf *info)
+{
+ u32 data;
+ u16 gpioorg;
+ unsigned int ret;
+
+ if (hw->model == CTSB055X) {
+ /* SB055x, unmute outputs */
+ gpioorg = (u16)hw_read_20kx(hw, GPIO);
+ gpioorg &= 0xffbf; /* set GPIO6 to low */
+ gpioorg |= 2; /* set GPIO1 to high */
+ hw_write_20kx(hw, GPIO, gpioorg);
+ return 0;
+ }
+
+ /* mute outputs */
+ gpioorg = (u16)hw_read_20kx(hw, GPIO);
+ gpioorg &= 0xffbf;
+ hw_write_20kx(hw, GPIO, gpioorg);
+
+ hw_reset_dac(hw);
+
+ if (i2c_unlock(hw))
+ return -1;
+
+ hw_write_pci(hw, 0xEC, 0x05); /* write to i2c status control */
+ do {
+ ret = hw_read_pci(hw, 0xEC);
+ } while (!(ret & 0x800000));
+
+ switch (info->msr) {
+ case 1:
+ data = 0x24;
+ break;
+ case 2:
+ data = 0x25;
+ break;
+ case 4:
+ data = 0x26;
+ break;
+ default:
+ data = 0x24;
+ break;
+ }
+
+ i2c_write(hw, 0x00180080, 0x06, data);
+ i2c_write(hw, 0x00180080, 0x09, data);
+ i2c_write(hw, 0x00180080, 0x0c, data);
+ i2c_write(hw, 0x00180080, 0x0f, data);
+
+ i2c_lock(hw);
+
+ /* unmute outputs */
+ gpioorg = (u16)hw_read_20kx(hw, GPIO);
+ gpioorg = gpioorg | 0x40;
+ hw_write_20kx(hw, GPIO, gpioorg);
+
+ return 0;
+}
+
+/* ADC operations */
+
+static int is_adc_input_selected_SB055x(struct hw *hw, enum ADCSRC type)
+{
+ return 0;
+}
+
+static int is_adc_input_selected_SBx(struct hw *hw, enum ADCSRC type)
+{
+ u32 data;
+
+ data = hw_read_20kx(hw, GPIO);
+ switch (type) {
+ case ADC_MICIN:
+ data = ((data & (0x1<<7)) && (data & (0x1<<8)));
+ break;
+ case ADC_LINEIN:
+ data = (!(data & (0x1<<7)) && (data & (0x1<<8)));
+ break;
+ case ADC_NONE: /* Digital I/O */
+ data = (!(data & (0x1<<8)));
+ break;
+ default:
+ data = 0;
+ }
+ return data;
+}
+
+static int is_adc_input_selected_hendrix(struct hw *hw, enum ADCSRC type)
+{
+ u32 data;
+
+ data = hw_read_20kx(hw, GPIO);
+ switch (type) {
+ case ADC_MICIN:
+ data = (data & (0x1 << 7)) ? 1 : 0;
+ break;
+ case ADC_LINEIN:
+ data = (data & (0x1 << 7)) ? 0 : 1;
+ break;
+ default:
+ data = 0;
+ }
+ return data;
+}
+
+static int hw_is_adc_input_selected(struct hw *hw, enum ADCSRC type)
+{
+ switch (hw->model) {
+ case CTSB055X:
+ return is_adc_input_selected_SB055x(hw, type);
+ case CTSB073X:
+ return is_adc_input_selected_hendrix(hw, type);
+ case CTUAA:
+ return is_adc_input_selected_hendrix(hw, type);
+ default:
+ return is_adc_input_selected_SBx(hw, type);
+ }
+}
+
+static int
+adc_input_select_SB055x(struct hw *hw, enum ADCSRC type, unsigned char boost)
+{
+ u32 data;
+
+ /*
+ * check and set the following GPIO bits accordingly
+ * ADC_Gain = GPIO2
+ * DRM_off = GPIO3
+ * Mic_Pwr_on = GPIO7
+ * Digital_IO_Sel = GPIO8
+ * Mic_Sw = GPIO9
+ * Aux/MicLine_Sw = GPIO12
+ */
+ data = hw_read_20kx(hw, GPIO);
+ data &= 0xec73;
+ switch (type) {
+ case ADC_MICIN:
+ data |= (0x1<<7) | (0x1<<8) | (0x1<<9) ;
+ data |= boost ? (0x1<<2) : 0;
+ break;
+ case ADC_LINEIN:
+ data |= (0x1<<8);
+ break;
+ case ADC_AUX:
+ data |= (0x1<<8) | (0x1<<12);
+ break;
+ case ADC_NONE:
+ data |= (0x1<<12); /* set to digital */
+ break;
+ default:
+ return -1;
+ }
+
+ hw_write_20kx(hw, GPIO, data);
+
+ return 0;
+}
+
+
+static int
+adc_input_select_SBx(struct hw *hw, enum ADCSRC type, unsigned char boost)
+{
+ u32 data;
+ u32 i2c_data;
+ unsigned int ret;
+
+ if (i2c_unlock(hw))
+ return -1;
+
+ do {
+ ret = hw_read_pci(hw, 0xEC);
+ } while (!(ret & 0x800000)); /* i2c ready poll */
+ /* set i2c access mode as Direct Control */
+ hw_write_pci(hw, 0xEC, 0x05);
+
+ data = hw_read_20kx(hw, GPIO);
+ switch (type) {
+ case ADC_MICIN:
+ data |= ((0x1 << 7) | (0x1 << 8));
+ i2c_data = 0x1; /* Mic-in */
+ break;
+ case ADC_LINEIN:
+ data &= ~(0x1 << 7);
+ data |= (0x1 << 8);
+ i2c_data = 0x2; /* Line-in */
+ break;
+ case ADC_NONE:
+ data &= ~(0x1 << 8);
+ i2c_data = 0x0; /* set to Digital */
+ break;
+ default:
+ i2c_lock(hw);
+ return -1;
+ }
+ hw_write_20kx(hw, GPIO, data);
+ i2c_write(hw, 0x001a0080, 0x2a, i2c_data);
+ if (boost) {
+ i2c_write(hw, 0x001a0080, 0x1c, 0xe7); /* +12dB boost */
+ i2c_write(hw, 0x001a0080, 0x1e, 0xe7); /* +12dB boost */
+ } else {
+ i2c_write(hw, 0x001a0080, 0x1c, 0xcf); /* No boost */
+ i2c_write(hw, 0x001a0080, 0x1e, 0xcf); /* No boost */
+ }
+
+ i2c_lock(hw);
+
+ return 0;
+}
+
+static int
+adc_input_select_hendrix(struct hw *hw, enum ADCSRC type, unsigned char boost)
+{
+ u32 data;
+ u32 i2c_data;
+ unsigned int ret;
+
+ if (i2c_unlock(hw))
+ return -1;
+
+ do {
+ ret = hw_read_pci(hw, 0xEC);
+ } while (!(ret & 0x800000)); /* i2c ready poll */
+ /* set i2c access mode as Direct Control */
+ hw_write_pci(hw, 0xEC, 0x05);
+
+ data = hw_read_20kx(hw, GPIO);
+ switch (type) {
+ case ADC_MICIN:
+ data |= (0x1 << 7);
+ i2c_data = 0x1; /* Mic-in */
+ break;
+ case ADC_LINEIN:
+ data &= ~(0x1 << 7);
+ i2c_data = 0x2; /* Line-in */
+ break;
+ default:
+ i2c_lock(hw);
+ return -1;
+ }
+ hw_write_20kx(hw, GPIO, data);
+ i2c_write(hw, 0x001a0080, 0x2a, i2c_data);
+ if (boost) {
+ i2c_write(hw, 0x001a0080, 0x1c, 0xe7); /* +12dB boost */
+ i2c_write(hw, 0x001a0080, 0x1e, 0xe7); /* +12dB boost */
+ } else {
+ i2c_write(hw, 0x001a0080, 0x1c, 0xcf); /* No boost */
+ i2c_write(hw, 0x001a0080, 0x1e, 0xcf); /* No boost */
+ }
+
+ i2c_lock(hw);
+
+ return 0;
+}
+
+static int hw_adc_input_select(struct hw *hw, enum ADCSRC type)
+{
+ int state = type == ADC_MICIN;
+
+ switch (hw->model) {
+ case CTSB055X:
+ return adc_input_select_SB055x(hw, type, state);
+ case CTSB073X:
+ return adc_input_select_hendrix(hw, type, state);
+ case CTUAA:
+ return adc_input_select_hendrix(hw, type, state);
+ default:
+ return adc_input_select_SBx(hw, type, state);
+ }
+}
+
+static int adc_init_SB055x(struct hw *hw, int input, int mic20db)
+{
+ return adc_input_select_SB055x(hw, input, mic20db);
+}
+
+static int adc_init_SBx(struct hw *hw, int input, int mic20db)
+{
+ u16 gpioorg;
+ u16 input_source;
+ u32 adcdata;
+ unsigned int ret;
+
+ input_source = 0x100; /* default to analog */
+ switch (input) {
+ case ADC_MICIN:
+ adcdata = 0x1;
+ input_source = 0x180; /* set GPIO7 to select Mic */
+ break;
+ case ADC_LINEIN:
+ adcdata = 0x2;
+ break;
+ case ADC_VIDEO:
+ adcdata = 0x4;
+ break;
+ case ADC_AUX:
+ adcdata = 0x8;
+ break;
+ case ADC_NONE:
+ adcdata = 0x0;
+ input_source = 0x0; /* set to Digital */
+ break;
+ default:
+ adcdata = 0x0;
+ break;
+ }
+
+ if (i2c_unlock(hw))
+ return -1;
+
+ do {
+ ret = hw_read_pci(hw, 0xEC);
+ } while (!(ret & 0x800000)); /* i2c ready poll */
+ hw_write_pci(hw, 0xEC, 0x05); /* write to i2c status control */
+
+ i2c_write(hw, 0x001a0080, 0x0e, 0x08);
+ i2c_write(hw, 0x001a0080, 0x18, 0x0a);
+ i2c_write(hw, 0x001a0080, 0x28, 0x86);
+ i2c_write(hw, 0x001a0080, 0x2a, adcdata);
+
+ if (mic20db) {
+ i2c_write(hw, 0x001a0080, 0x1c, 0xf7);
+ i2c_write(hw, 0x001a0080, 0x1e, 0xf7);
+ } else {
+ i2c_write(hw, 0x001a0080, 0x1c, 0xcf);
+ i2c_write(hw, 0x001a0080, 0x1e, 0xcf);
+ }
+
+ if (!(hw_read_20kx(hw, ID0) & 0x100))
+ i2c_write(hw, 0x001a0080, 0x16, 0x26);
+
+ i2c_lock(hw);
+
+ gpioorg = (u16)hw_read_20kx(hw, GPIO);
+ gpioorg &= 0xfe7f;
+ gpioorg |= input_source;
+ hw_write_20kx(hw, GPIO, gpioorg);
+
+ return 0;
+}
+
+static int hw_adc_init(struct hw *hw, const struct adc_conf *info)
+{
+ if (hw->model == CTSB055X)
+ return adc_init_SB055x(hw, info->input, info->mic20db);
+ else
+ return adc_init_SBx(hw, info->input, info->mic20db);
+}
+
+static struct capabilities hw_capabilities(struct hw *hw)
+{
+ struct capabilities cap;
+
+ /* SB073x and Vista compatible cards have no digit IO switch */
+ cap.digit_io_switch = !(hw->model == CTSB073X || hw->model == CTUAA);
+ cap.dedicated_mic = 0;
+ cap.output_switch = 0;
+ cap.mic_source_switch = 0;
+
+ return cap;
+}
+
+#define CTLBITS(a, b, c, d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
+
+#define UAA_CFG_PWRSTATUS 0x44
+#define UAA_CFG_SPACE_FLAG 0xA0
+#define UAA_CORE_CHANGE 0x3FFC
+static int uaa_to_xfi(struct pci_dev *pci)
+{
+ unsigned int bar0, bar1, bar2, bar3, bar4, bar5;
+ unsigned int cmd, irq, cl_size, l_timer, pwr;
+ unsigned int is_uaa;
+ unsigned int data[4] = {0};
+ unsigned int io_base;
+ void __iomem *mem_base;
+ int i;
+ const u32 CTLX = CTLBITS('C', 'T', 'L', 'X');
+ const u32 CTL_ = CTLBITS('C', 'T', 'L', '-');
+ const u32 CTLF = CTLBITS('C', 'T', 'L', 'F');
+ const u32 CTLi = CTLBITS('C', 'T', 'L', 'i');
+ const u32 CTLA = CTLBITS('C', 'T', 'L', 'A');
+ const u32 CTLZ = CTLBITS('C', 'T', 'L', 'Z');
+ const u32 CTLL = CTLBITS('C', 'T', 'L', 'L');
+
+ /* By default, Hendrix card UAA Bar0 should be using memory... */
+ io_base = pci_resource_start(pci, 0);
+ mem_base = ioremap(io_base, pci_resource_len(pci, 0));
+ if (!mem_base)
+ return -ENOENT;
+
+ /* Read current mode from Mode Change Register */
+ for (i = 0; i < 4; i++)
+ data[i] = readl(mem_base + UAA_CORE_CHANGE);
+
+ /* Determine current mode... */
+ if (data[0] == CTLA) {
+ is_uaa = ((data[1] == CTLZ && data[2] == CTLL
+ && data[3] == CTLA) || (data[1] == CTLA
+ && data[2] == CTLZ && data[3] == CTLL));
+ } else if (data[0] == CTLZ) {
+ is_uaa = (data[1] == CTLL
+ && data[2] == CTLA && data[3] == CTLA);
+ } else if (data[0] == CTLL) {
+ is_uaa = (data[1] == CTLA
+ && data[2] == CTLA && data[3] == CTLZ);
+ } else {
+ is_uaa = 0;
+ }
+
+ if (!is_uaa) {
+ /* Not in UAA mode currently. Return directly. */
+ iounmap(mem_base);
+ return 0;
+ }
+
+ pci_read_config_dword(pci, PCI_BASE_ADDRESS_0, &bar0);
+ pci_read_config_dword(pci, PCI_BASE_ADDRESS_1, &bar1);
+ pci_read_config_dword(pci, PCI_BASE_ADDRESS_2, &bar2);
+ pci_read_config_dword(pci, PCI_BASE_ADDRESS_3, &bar3);
+ pci_read_config_dword(pci, PCI_BASE_ADDRESS_4, &bar4);
+ pci_read_config_dword(pci, PCI_BASE_ADDRESS_5, &bar5);
+ pci_read_config_dword(pci, PCI_INTERRUPT_LINE, &irq);
+ pci_read_config_dword(pci, PCI_CACHE_LINE_SIZE, &cl_size);
+ pci_read_config_dword(pci, PCI_LATENCY_TIMER, &l_timer);
+ pci_read_config_dword(pci, UAA_CFG_PWRSTATUS, &pwr);
+ pci_read_config_dword(pci, PCI_COMMAND, &cmd);
+
+ /* Set up X-Fi core PCI configuration space. */
+ /* Switch to X-Fi config space with BAR0 exposed. */
+ pci_write_config_dword(pci, UAA_CFG_SPACE_FLAG, 0x87654321);
+ /* Copy UAA's BAR5 into X-Fi BAR0 */
+ pci_write_config_dword(pci, PCI_BASE_ADDRESS_0, bar5);
+ /* Switch to X-Fi config space without BAR0 exposed. */
+ pci_write_config_dword(pci, UAA_CFG_SPACE_FLAG, 0x12345678);
+ pci_write_config_dword(pci, PCI_BASE_ADDRESS_1, bar1);
+ pci_write_config_dword(pci, PCI_BASE_ADDRESS_2, bar2);
+ pci_write_config_dword(pci, PCI_BASE_ADDRESS_3, bar3);
+ pci_write_config_dword(pci, PCI_BASE_ADDRESS_4, bar4);
+ pci_write_config_dword(pci, PCI_INTERRUPT_LINE, irq);
+ pci_write_config_dword(pci, PCI_CACHE_LINE_SIZE, cl_size);
+ pci_write_config_dword(pci, PCI_LATENCY_TIMER, l_timer);
+ pci_write_config_dword(pci, UAA_CFG_PWRSTATUS, pwr);
+ pci_write_config_dword(pci, PCI_COMMAND, cmd);
+
+ /* Switch to X-Fi mode */
+ writel(CTLX, (mem_base + UAA_CORE_CHANGE));
+ writel(CTL_, (mem_base + UAA_CORE_CHANGE));
+ writel(CTLF, (mem_base + UAA_CORE_CHANGE));
+ writel(CTLi, (mem_base + UAA_CORE_CHANGE));
+
+ iounmap(mem_base);
+
+ return 0;
+}
+
+static irqreturn_t ct_20k1_interrupt(int irq, void *dev_id)
+{
+ struct hw *hw = dev_id;
+ unsigned int status;
+
+ status = hw_read_20kx(hw, GIP);
+ if (!status)
+ return IRQ_NONE;
+
+ if (hw->irq_callback)
+ hw->irq_callback(hw->irq_callback_data, status);
+
+ hw_write_20kx(hw, GIP, status);
+ return IRQ_HANDLED;
+}
+
+static int hw_card_start(struct hw *hw)
+{
+ int err;
+ struct pci_dev *pci = hw->pci;
+ const unsigned int dma_bits = BITS_PER_LONG;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ /* Set DMA transfer mask */
+ if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(dma_bits)))
+ dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32));
+
+ if (!hw->io_base) {
+ err = pci_request_regions(pci, "XFi");
+ if (err < 0)
+ goto error1;
+
+ if (hw->model == CTUAA)
+ hw->io_base = pci_resource_start(pci, 5);
+ else
+ hw->io_base = pci_resource_start(pci, 0);
+
+ }
+
+ /* Switch to X-Fi mode from UAA mode if needed */
+ if (hw->model == CTUAA) {
+ err = uaa_to_xfi(pci);
+ if (err)
+ goto error2;
+
+ }
+
+ if (hw->irq < 0) {
+ err = request_irq(pci->irq, ct_20k1_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, hw);
+ if (err < 0) {
+ dev_err(hw->card->dev,
+ "XFi: Cannot get irq %d\n", pci->irq);
+ goto error2;
+ }
+ hw->irq = pci->irq;
+ hw->card->sync_irq = hw->irq;
+ }
+
+ pci_set_master(pci);
+
+ return 0;
+
+error2:
+ pci_release_regions(pci);
+ hw->io_base = 0;
+error1:
+ pci_disable_device(pci);
+ return err;
+}
+
+static int hw_card_stop(struct hw *hw)
+{
+ unsigned int data;
+
+ /* disable transport bus master and queueing of request */
+ hw_write_20kx(hw, TRNCTL, 0x00);
+
+ /* disable pll */
+ data = hw_read_20kx(hw, PLLCTL);
+ hw_write_20kx(hw, PLLCTL, (data & (~(0x0F<<12))));
+
+ return 0;
+}
+
+static int hw_card_shutdown(struct hw *hw)
+{
+ if (hw->irq >= 0)
+ free_irq(hw->irq, hw);
+
+ hw->irq = -1;
+ iounmap(hw->mem_base);
+ hw->mem_base = NULL;
+
+ if (hw->io_base)
+ pci_release_regions(hw->pci);
+
+ hw->io_base = 0;
+
+ pci_disable_device(hw->pci);
+
+ return 0;
+}
+
+static int hw_card_init(struct hw *hw, struct card_conf *info)
+{
+ int err;
+ unsigned int gctl;
+ u32 data;
+ struct dac_conf dac_info = {0};
+ struct adc_conf adc_info = {0};
+ struct daio_conf daio_info = {0};
+ struct trn_conf trn_info = {0};
+
+ /* Get PCI io port base address and do Hendrix switch if needed. */
+ err = hw_card_start(hw);
+ if (err)
+ return err;
+
+ /* PLL init */
+ err = hw_pll_init(hw, info->rsr);
+ if (err < 0)
+ return err;
+
+ /* kick off auto-init */
+ err = hw_auto_init(hw);
+ if (err < 0)
+ return err;
+
+ /* Enable audio ring */
+ gctl = hw_read_20kx(hw, GCTL);
+ set_field(&gctl, GCTL_EAC, 1);
+ set_field(&gctl, GCTL_DBP, 1);
+ set_field(&gctl, GCTL_TBP, 1);
+ set_field(&gctl, GCTL_FBP, 1);
+ set_field(&gctl, GCTL_ET, 1);
+ hw_write_20kx(hw, GCTL, gctl);
+ mdelay(10);
+
+ /* Reset all global pending interrupts */
+ hw_write_20kx(hw, GIE, 0);
+ /* Reset all SRC pending interrupts */
+ hw_write_20kx(hw, SRCIP, 0);
+ msleep(30);
+
+ /* Detect the card ID and configure GPIO accordingly. */
+ switch (hw->model) {
+ case CTSB055X:
+ hw_write_20kx(hw, GPIOCTL, 0x13fe);
+ break;
+ case CTSB073X:
+ hw_write_20kx(hw, GPIOCTL, 0x00e6);
+ break;
+ case CTUAA:
+ hw_write_20kx(hw, GPIOCTL, 0x00c2);
+ break;
+ default:
+ hw_write_20kx(hw, GPIOCTL, 0x01e6);
+ break;
+ }
+
+ trn_info.vm_pgt_phys = info->vm_pgt_phys;
+ err = hw_trn_init(hw, &trn_info);
+ if (err < 0)
+ return err;
+
+ daio_info.msr = info->msr;
+ err = hw_daio_init(hw, &daio_info);
+ if (err < 0)
+ return err;
+
+ dac_info.msr = info->msr;
+ err = hw_dac_init(hw, &dac_info);
+ if (err < 0)
+ return err;
+
+ adc_info.msr = info->msr;
+ adc_info.input = ADC_LINEIN;
+ adc_info.mic20db = 0;
+ err = hw_adc_init(hw, &adc_info);
+ if (err < 0)
+ return err;
+
+ data = hw_read_20kx(hw, SRCMCTL);
+ data |= 0x1; /* Enables input from the audio ring */
+ hw_write_20kx(hw, SRCMCTL, data);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int hw_suspend(struct hw *hw)
+{
+ struct pci_dev *pci = hw->pci;
+
+ hw_card_stop(hw);
+
+ if (hw->model == CTUAA) {
+ /* Switch to UAA config space. */
+ pci_write_config_dword(pci, UAA_CFG_SPACE_FLAG, 0x0);
+ }
+
+ return 0;
+}
+
+static int hw_resume(struct hw *hw, struct card_conf *info)
+{
+ /* Re-initialize card hardware. */
+ return hw_card_init(hw, info);
+}
+#endif
+
+static u32 hw_read_20kx(struct hw *hw, u32 reg)
+{
+ u32 value;
+ unsigned long flags;
+
+ spin_lock_irqsave(
+ &container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+ outl(reg, hw->io_base + 0x0);
+ value = inl(hw->io_base + 0x4);
+ spin_unlock_irqrestore(
+ &container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+
+ return value;
+}
+
+static void hw_write_20kx(struct hw *hw, u32 reg, u32 data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(
+ &container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+ outl(reg, hw->io_base + 0x0);
+ outl(data, hw->io_base + 0x4);
+ spin_unlock_irqrestore(
+ &container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+
+}
+
+static u32 hw_read_pci(struct hw *hw, u32 reg)
+{
+ u32 value;
+ unsigned long flags;
+
+ spin_lock_irqsave(
+ &container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+ outl(reg, hw->io_base + 0x10);
+ value = inl(hw->io_base + 0x14);
+ spin_unlock_irqrestore(
+ &container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+
+ return value;
+}
+
+static void hw_write_pci(struct hw *hw, u32 reg, u32 data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(
+ &container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+ outl(reg, hw->io_base + 0x10);
+ outl(data, hw->io_base + 0x14);
+ spin_unlock_irqrestore(
+ &container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+}
+
+static const struct hw ct20k1_preset = {
+ .irq = -1,
+
+ .card_init = hw_card_init,
+ .card_stop = hw_card_stop,
+ .pll_init = hw_pll_init,
+ .is_adc_source_selected = hw_is_adc_input_selected,
+ .select_adc_source = hw_adc_input_select,
+ .capabilities = hw_capabilities,
+#ifdef CONFIG_PM_SLEEP
+ .suspend = hw_suspend,
+ .resume = hw_resume,
+#endif
+
+ .src_rsc_get_ctrl_blk = src_get_rsc_ctrl_blk,
+ .src_rsc_put_ctrl_blk = src_put_rsc_ctrl_blk,
+ .src_mgr_get_ctrl_blk = src_mgr_get_ctrl_blk,
+ .src_mgr_put_ctrl_blk = src_mgr_put_ctrl_blk,
+ .src_set_state = src_set_state,
+ .src_set_bm = src_set_bm,
+ .src_set_rsr = src_set_rsr,
+ .src_set_sf = src_set_sf,
+ .src_set_wr = src_set_wr,
+ .src_set_pm = src_set_pm,
+ .src_set_rom = src_set_rom,
+ .src_set_vo = src_set_vo,
+ .src_set_st = src_set_st,
+ .src_set_ie = src_set_ie,
+ .src_set_ilsz = src_set_ilsz,
+ .src_set_bp = src_set_bp,
+ .src_set_cisz = src_set_cisz,
+ .src_set_ca = src_set_ca,
+ .src_set_sa = src_set_sa,
+ .src_set_la = src_set_la,
+ .src_set_pitch = src_set_pitch,
+ .src_set_dirty = src_set_dirty,
+ .src_set_clear_zbufs = src_set_clear_zbufs,
+ .src_set_dirty_all = src_set_dirty_all,
+ .src_commit_write = src_commit_write,
+ .src_get_ca = src_get_ca,
+ .src_get_dirty = src_get_dirty,
+ .src_dirty_conj_mask = src_dirty_conj_mask,
+ .src_mgr_enbs_src = src_mgr_enbs_src,
+ .src_mgr_enb_src = src_mgr_enb_src,
+ .src_mgr_dsb_src = src_mgr_dsb_src,
+ .src_mgr_commit_write = src_mgr_commit_write,
+
+ .srcimp_mgr_get_ctrl_blk = srcimp_mgr_get_ctrl_blk,
+ .srcimp_mgr_put_ctrl_blk = srcimp_mgr_put_ctrl_blk,
+ .srcimp_mgr_set_imaparc = srcimp_mgr_set_imaparc,
+ .srcimp_mgr_set_imapuser = srcimp_mgr_set_imapuser,
+ .srcimp_mgr_set_imapnxt = srcimp_mgr_set_imapnxt,
+ .srcimp_mgr_set_imapaddr = srcimp_mgr_set_imapaddr,
+ .srcimp_mgr_commit_write = srcimp_mgr_commit_write,
+
+ .amixer_rsc_get_ctrl_blk = amixer_rsc_get_ctrl_blk,
+ .amixer_rsc_put_ctrl_blk = amixer_rsc_put_ctrl_blk,
+ .amixer_mgr_get_ctrl_blk = amixer_mgr_get_ctrl_blk,
+ .amixer_mgr_put_ctrl_blk = amixer_mgr_put_ctrl_blk,
+ .amixer_set_mode = amixer_set_mode,
+ .amixer_set_iv = amixer_set_iv,
+ .amixer_set_x = amixer_set_x,
+ .amixer_set_y = amixer_set_y,
+ .amixer_set_sadr = amixer_set_sadr,
+ .amixer_set_se = amixer_set_se,
+ .amixer_set_dirty = amixer_set_dirty,
+ .amixer_set_dirty_all = amixer_set_dirty_all,
+ .amixer_commit_write = amixer_commit_write,
+ .amixer_get_y = amixer_get_y,
+ .amixer_get_dirty = amixer_get_dirty,
+
+ .dai_get_ctrl_blk = dai_get_ctrl_blk,
+ .dai_put_ctrl_blk = dai_put_ctrl_blk,
+ .dai_srt_set_srco = dai_srt_set_srcr,
+ .dai_srt_set_srcm = dai_srt_set_srcl,
+ .dai_srt_set_rsr = dai_srt_set_rsr,
+ .dai_srt_set_drat = dai_srt_set_drat,
+ .dai_srt_set_ec = dai_srt_set_ec,
+ .dai_srt_set_et = dai_srt_set_et,
+ .dai_commit_write = dai_commit_write,
+
+ .dao_get_ctrl_blk = dao_get_ctrl_blk,
+ .dao_put_ctrl_blk = dao_put_ctrl_blk,
+ .dao_set_spos = dao_set_spos,
+ .dao_commit_write = dao_commit_write,
+ .dao_get_spos = dao_get_spos,
+
+ .daio_mgr_get_ctrl_blk = daio_mgr_get_ctrl_blk,
+ .daio_mgr_put_ctrl_blk = daio_mgr_put_ctrl_blk,
+ .daio_mgr_enb_dai = daio_mgr_enb_dai,
+ .daio_mgr_dsb_dai = daio_mgr_dsb_dai,
+ .daio_mgr_enb_dao = daio_mgr_enb_dao,
+ .daio_mgr_dsb_dao = daio_mgr_dsb_dao,
+ .daio_mgr_dao_init = daio_mgr_dao_init,
+ .daio_mgr_set_imaparc = daio_mgr_set_imaparc,
+ .daio_mgr_set_imapnxt = daio_mgr_set_imapnxt,
+ .daio_mgr_set_imapaddr = daio_mgr_set_imapaddr,
+ .daio_mgr_commit_write = daio_mgr_commit_write,
+
+ .set_timer_irq = set_timer_irq,
+ .set_timer_tick = set_timer_tick,
+ .get_wc = get_wc,
+};
+
+int create_20k1_hw_obj(struct hw **rhw)
+{
+ struct hw20k1 *hw20k1;
+
+ *rhw = NULL;
+ hw20k1 = kzalloc(sizeof(*hw20k1), GFP_KERNEL);
+ if (!hw20k1)
+ return -ENOMEM;
+
+ spin_lock_init(&hw20k1->reg_20k1_lock);
+ spin_lock_init(&hw20k1->reg_pci_lock);
+
+ hw20k1->hw = ct20k1_preset;
+
+ *rhw = &hw20k1->hw;
+
+ return 0;
+}
+
+int destroy_20k1_hw_obj(struct hw *hw)
+{
+ if (hw->io_base)
+ hw_card_shutdown(hw);
+
+ kfree(container_of(hw, struct hw20k1, hw));
+ return 0;
+}
diff --git a/sound/pci/ctxfi/cthw20k1.h b/sound/pci/ctxfi/cthw20k1.h
new file mode 100644
index 000000000..ffb019abf
--- /dev/null
+++ b/sound/pci/ctxfi/cthw20k1.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File cthw20k1.h
+ *
+ * @Brief
+ * This file contains the definition of hardware access methord.
+ *
+ * @Author Liu Chun
+ * @Date May 13 2008
+ */
+
+#ifndef CTHW20K1_H
+#define CTHW20K1_H
+
+#include "cthardware.h"
+
+int create_20k1_hw_obj(struct hw **rhw);
+int destroy_20k1_hw_obj(struct hw *hw);
+
+#endif /* CTHW20K1_H */
diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c
new file mode 100644
index 000000000..55af8ef29
--- /dev/null
+++ b/sound/pci/ctxfi/cthw20k2.c
@@ -0,0 +1,2346 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File cthw20k2.c
+ *
+ * @Brief
+ * This file contains the implementation of hardware access method for 20k2.
+ *
+ * @Author Liu Chun
+ * @Date May 14 2008
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include "cthw20k2.h"
+#include "ct20k2reg.h"
+
+struct hw20k2 {
+ struct hw hw;
+ /* for i2c */
+ unsigned char dev_id;
+ unsigned char addr_size;
+ unsigned char data_size;
+
+ int mic_source;
+};
+
+static u32 hw_read_20kx(struct hw *hw, u32 reg);
+static void hw_write_20kx(struct hw *hw, u32 reg, u32 data);
+
+/*
+ * Type definition block.
+ * The layout of control structures can be directly applied on 20k2 chip.
+ */
+
+/*
+ * SRC control block definitions.
+ */
+
+/* SRC resource control block */
+#define SRCCTL_STATE 0x00000007
+#define SRCCTL_BM 0x00000008
+#define SRCCTL_RSR 0x00000030
+#define SRCCTL_SF 0x000001C0
+#define SRCCTL_WR 0x00000200
+#define SRCCTL_PM 0x00000400
+#define SRCCTL_ROM 0x00001800
+#define SRCCTL_VO 0x00002000
+#define SRCCTL_ST 0x00004000
+#define SRCCTL_IE 0x00008000
+#define SRCCTL_ILSZ 0x000F0000
+#define SRCCTL_BP 0x00100000
+
+#define SRCCCR_CISZ 0x000007FF
+#define SRCCCR_CWA 0x001FF800
+#define SRCCCR_D 0x00200000
+#define SRCCCR_RS 0x01C00000
+#define SRCCCR_NAL 0x3E000000
+#define SRCCCR_RA 0xC0000000
+
+#define SRCCA_CA 0x0FFFFFFF
+#define SRCCA_RS 0xE0000000
+
+#define SRCSA_SA 0x0FFFFFFF
+
+#define SRCLA_LA 0x0FFFFFFF
+
+/* Mixer Parameter Ring ram Low and Hight register.
+ * Fixed-point value in 8.24 format for parameter channel */
+#define MPRLH_PITCH 0xFFFFFFFF
+
+/* SRC resource register dirty flags */
+union src_dirty {
+ struct {
+ u16 ctl:1;
+ u16 ccr:1;
+ u16 sa:1;
+ u16 la:1;
+ u16 ca:1;
+ u16 mpr:1;
+ u16 czbfs:1; /* Clear Z-Buffers */
+ u16 rsv:9;
+ } bf;
+ u16 data;
+};
+
+struct src_rsc_ctrl_blk {
+ unsigned int ctl;
+ unsigned int ccr;
+ unsigned int ca;
+ unsigned int sa;
+ unsigned int la;
+ unsigned int mpr;
+ union src_dirty dirty;
+};
+
+/* SRC manager control block */
+union src_mgr_dirty {
+ struct {
+ u16 enb0:1;
+ u16 enb1:1;
+ u16 enb2:1;
+ u16 enb3:1;
+ u16 enb4:1;
+ u16 enb5:1;
+ u16 enb6:1;
+ u16 enb7:1;
+ u16 enbsa:1;
+ u16 rsv:7;
+ } bf;
+ u16 data;
+};
+
+struct src_mgr_ctrl_blk {
+ unsigned int enbsa;
+ unsigned int enb[8];
+ union src_mgr_dirty dirty;
+};
+
+/* SRCIMP manager control block */
+#define SRCAIM_ARC 0x00000FFF
+#define SRCAIM_NXT 0x00FF0000
+#define SRCAIM_SRC 0xFF000000
+
+struct srcimap {
+ unsigned int srcaim;
+ unsigned int idx;
+};
+
+/* SRCIMP manager register dirty flags */
+union srcimp_mgr_dirty {
+ struct {
+ u16 srcimap:1;
+ u16 rsv:15;
+ } bf;
+ u16 data;
+};
+
+struct srcimp_mgr_ctrl_blk {
+ struct srcimap srcimap;
+ union srcimp_mgr_dirty dirty;
+};
+
+/*
+ * Function implementation block.
+ */
+
+static int src_get_rsc_ctrl_blk(void **rblk)
+{
+ struct src_rsc_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int src_put_rsc_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int src_set_state(void *blk, unsigned int state)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_STATE, state);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_bm(void *blk, unsigned int bm)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_BM, bm);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_rsr(void *blk, unsigned int rsr)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_RSR, rsr);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_sf(void *blk, unsigned int sf)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_SF, sf);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_wr(void *blk, unsigned int wr)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_WR, wr);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_pm(void *blk, unsigned int pm)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_PM, pm);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_rom(void *blk, unsigned int rom)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_ROM, rom);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_vo(void *blk, unsigned int vo)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_VO, vo);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_st(void *blk, unsigned int st)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_ST, st);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_ie(void *blk, unsigned int ie)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_IE, ie);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_ilsz(void *blk, unsigned int ilsz)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_ILSZ, ilsz);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_bp(void *blk, unsigned int bp)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ctl, SRCCTL_BP, bp);
+ ctl->dirty.bf.ctl = 1;
+ return 0;
+}
+
+static int src_set_cisz(void *blk, unsigned int cisz)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ccr, SRCCCR_CISZ, cisz);
+ ctl->dirty.bf.ccr = 1;
+ return 0;
+}
+
+static int src_set_ca(void *blk, unsigned int ca)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->ca, SRCCA_CA, ca);
+ ctl->dirty.bf.ca = 1;
+ return 0;
+}
+
+static int src_set_sa(void *blk, unsigned int sa)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->sa, SRCSA_SA, sa);
+ ctl->dirty.bf.sa = 1;
+ return 0;
+}
+
+static int src_set_la(void *blk, unsigned int la)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->la, SRCLA_LA, la);
+ ctl->dirty.bf.la = 1;
+ return 0;
+}
+
+static int src_set_pitch(void *blk, unsigned int pitch)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->mpr, MPRLH_PITCH, pitch);
+ ctl->dirty.bf.mpr = 1;
+ return 0;
+}
+
+static int src_set_clear_zbufs(void *blk, unsigned int clear)
+{
+ ((struct src_rsc_ctrl_blk *)blk)->dirty.bf.czbfs = (clear ? 1 : 0);
+ return 0;
+}
+
+static int src_set_dirty(void *blk, unsigned int flags)
+{
+ ((struct src_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff);
+ return 0;
+}
+
+static int src_set_dirty_all(void *blk)
+{
+ ((struct src_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0);
+ return 0;
+}
+
+#define AR_SLOT_SIZE 4096
+#define AR_SLOT_BLOCK_SIZE 16
+#define AR_PTS_PITCH 6
+#define AR_PARAM_SRC_OFFSET 0x60
+
+static unsigned int src_param_pitch_mixer(unsigned int src_idx)
+{
+ return ((src_idx << 4) + AR_PTS_PITCH + AR_SLOT_SIZE
+ - AR_PARAM_SRC_OFFSET) % AR_SLOT_SIZE;
+
+}
+
+static int src_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+ int i;
+
+ if (ctl->dirty.bf.czbfs) {
+ /* Clear Z-Buffer registers */
+ for (i = 0; i < 8; i++)
+ hw_write_20kx(hw, SRC_UPZ+idx*0x100+i*0x4, 0);
+
+ for (i = 0; i < 4; i++)
+ hw_write_20kx(hw, SRC_DN0Z+idx*0x100+i*0x4, 0);
+
+ for (i = 0; i < 8; i++)
+ hw_write_20kx(hw, SRC_DN1Z+idx*0x100+i*0x4, 0);
+
+ ctl->dirty.bf.czbfs = 0;
+ }
+ if (ctl->dirty.bf.mpr) {
+ /* Take the parameter mixer resource in the same group as that
+ * the idx src is in for simplicity. Unlike src, all conjugate
+ * parameter mixer resources must be programmed for
+ * corresponding conjugate src resources. */
+ unsigned int pm_idx = src_param_pitch_mixer(idx);
+ hw_write_20kx(hw, MIXER_PRING_LO_HI+4*pm_idx, ctl->mpr);
+ hw_write_20kx(hw, MIXER_PMOPLO+8*pm_idx, 0x3);
+ hw_write_20kx(hw, MIXER_PMOPHI+8*pm_idx, 0x0);
+ ctl->dirty.bf.mpr = 0;
+ }
+ if (ctl->dirty.bf.sa) {
+ hw_write_20kx(hw, SRC_SA+idx*0x100, ctl->sa);
+ ctl->dirty.bf.sa = 0;
+ }
+ if (ctl->dirty.bf.la) {
+ hw_write_20kx(hw, SRC_LA+idx*0x100, ctl->la);
+ ctl->dirty.bf.la = 0;
+ }
+ if (ctl->dirty.bf.ca) {
+ hw_write_20kx(hw, SRC_CA+idx*0x100, ctl->ca);
+ ctl->dirty.bf.ca = 0;
+ }
+
+ /* Write srccf register */
+ hw_write_20kx(hw, SRC_CF+idx*0x100, 0x0);
+
+ if (ctl->dirty.bf.ccr) {
+ hw_write_20kx(hw, SRC_CCR+idx*0x100, ctl->ccr);
+ ctl->dirty.bf.ccr = 0;
+ }
+ if (ctl->dirty.bf.ctl) {
+ hw_write_20kx(hw, SRC_CTL+idx*0x100, ctl->ctl);
+ ctl->dirty.bf.ctl = 0;
+ }
+
+ return 0;
+}
+
+static int src_get_ca(struct hw *hw, unsigned int idx, void *blk)
+{
+ struct src_rsc_ctrl_blk *ctl = blk;
+
+ ctl->ca = hw_read_20kx(hw, SRC_CA+idx*0x100);
+ ctl->dirty.bf.ca = 0;
+
+ return get_field(ctl->ca, SRCCA_CA);
+}
+
+static unsigned int src_get_dirty(void *blk)
+{
+ return ((struct src_rsc_ctrl_blk *)blk)->dirty.data;
+}
+
+static unsigned int src_dirty_conj_mask(void)
+{
+ return 0x20;
+}
+
+static int src_mgr_enbs_src(void *blk, unsigned int idx)
+{
+ ((struct src_mgr_ctrl_blk *)blk)->enbsa |= (0x1 << ((idx%128)/4));
+ ((struct src_mgr_ctrl_blk *)blk)->dirty.bf.enbsa = 1;
+ ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32));
+ return 0;
+}
+
+static int src_mgr_enb_src(void *blk, unsigned int idx)
+{
+ ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32));
+ ((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32));
+ return 0;
+}
+
+static int src_mgr_dsb_src(void *blk, unsigned int idx)
+{
+ ((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] &= ~(0x1 << (idx%32));
+ ((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32));
+ return 0;
+}
+
+static int src_mgr_commit_write(struct hw *hw, void *blk)
+{
+ struct src_mgr_ctrl_blk *ctl = blk;
+ int i;
+ unsigned int ret;
+
+ if (ctl->dirty.bf.enbsa) {
+ do {
+ ret = hw_read_20kx(hw, SRC_ENBSTAT);
+ } while (ret & 0x1);
+ hw_write_20kx(hw, SRC_ENBSA, ctl->enbsa);
+ ctl->dirty.bf.enbsa = 0;
+ }
+ for (i = 0; i < 8; i++) {
+ if ((ctl->dirty.data & (0x1 << i))) {
+ hw_write_20kx(hw, SRC_ENB+(i*0x100), ctl->enb[i]);
+ ctl->dirty.data &= ~(0x1 << i);
+ }
+ }
+
+ return 0;
+}
+
+static int src_mgr_get_ctrl_blk(void **rblk)
+{
+ struct src_mgr_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int src_mgr_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int srcimp_mgr_get_ctrl_blk(void **rblk)
+{
+ struct srcimp_mgr_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int srcimp_mgr_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int srcimp_mgr_set_imaparc(void *blk, unsigned int slot)
+{
+ struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srcimap.srcaim, SRCAIM_ARC, slot);
+ ctl->dirty.bf.srcimap = 1;
+ return 0;
+}
+
+static int srcimp_mgr_set_imapuser(void *blk, unsigned int user)
+{
+ struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srcimap.srcaim, SRCAIM_SRC, user);
+ ctl->dirty.bf.srcimap = 1;
+ return 0;
+}
+
+static int srcimp_mgr_set_imapnxt(void *blk, unsigned int next)
+{
+ struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srcimap.srcaim, SRCAIM_NXT, next);
+ ctl->dirty.bf.srcimap = 1;
+ return 0;
+}
+
+static int srcimp_mgr_set_imapaddr(void *blk, unsigned int addr)
+{
+ ((struct srcimp_mgr_ctrl_blk *)blk)->srcimap.idx = addr;
+ ((struct srcimp_mgr_ctrl_blk *)blk)->dirty.bf.srcimap = 1;
+ return 0;
+}
+
+static int srcimp_mgr_commit_write(struct hw *hw, void *blk)
+{
+ struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+ if (ctl->dirty.bf.srcimap) {
+ hw_write_20kx(hw, SRC_IMAP+ctl->srcimap.idx*0x100,
+ ctl->srcimap.srcaim);
+ ctl->dirty.bf.srcimap = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * AMIXER control block definitions.
+ */
+
+#define AMOPLO_M 0x00000003
+#define AMOPLO_IV 0x00000004
+#define AMOPLO_X 0x0003FFF0
+#define AMOPLO_Y 0xFFFC0000
+
+#define AMOPHI_SADR 0x000000FF
+#define AMOPHI_SE 0x80000000
+
+/* AMIXER resource register dirty flags */
+union amixer_dirty {
+ struct {
+ u16 amoplo:1;
+ u16 amophi:1;
+ u16 rsv:14;
+ } bf;
+ u16 data;
+};
+
+/* AMIXER resource control block */
+struct amixer_rsc_ctrl_blk {
+ unsigned int amoplo;
+ unsigned int amophi;
+ union amixer_dirty dirty;
+};
+
+static int amixer_set_mode(void *blk, unsigned int mode)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amoplo, AMOPLO_M, mode);
+ ctl->dirty.bf.amoplo = 1;
+ return 0;
+}
+
+static int amixer_set_iv(void *blk, unsigned int iv)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amoplo, AMOPLO_IV, iv);
+ ctl->dirty.bf.amoplo = 1;
+ return 0;
+}
+
+static int amixer_set_x(void *blk, unsigned int x)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amoplo, AMOPLO_X, x);
+ ctl->dirty.bf.amoplo = 1;
+ return 0;
+}
+
+static int amixer_set_y(void *blk, unsigned int y)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amoplo, AMOPLO_Y, y);
+ ctl->dirty.bf.amoplo = 1;
+ return 0;
+}
+
+static int amixer_set_sadr(void *blk, unsigned int sadr)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amophi, AMOPHI_SADR, sadr);
+ ctl->dirty.bf.amophi = 1;
+ return 0;
+}
+
+static int amixer_set_se(void *blk, unsigned int se)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->amophi, AMOPHI_SE, se);
+ ctl->dirty.bf.amophi = 1;
+ return 0;
+}
+
+static int amixer_set_dirty(void *blk, unsigned int flags)
+{
+ ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff);
+ return 0;
+}
+
+static int amixer_set_dirty_all(void *blk)
+{
+ ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0);
+ return 0;
+}
+
+static int amixer_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ if (ctl->dirty.bf.amoplo || ctl->dirty.bf.amophi) {
+ hw_write_20kx(hw, MIXER_AMOPLO+idx*8, ctl->amoplo);
+ ctl->dirty.bf.amoplo = 0;
+ hw_write_20kx(hw, MIXER_AMOPHI+idx*8, ctl->amophi);
+ ctl->dirty.bf.amophi = 0;
+ }
+
+ return 0;
+}
+
+static int amixer_get_y(void *blk)
+{
+ struct amixer_rsc_ctrl_blk *ctl = blk;
+
+ return get_field(ctl->amoplo, AMOPLO_Y);
+}
+
+static unsigned int amixer_get_dirty(void *blk)
+{
+ return ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data;
+}
+
+static int amixer_rsc_get_ctrl_blk(void **rblk)
+{
+ struct amixer_rsc_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int amixer_rsc_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int amixer_mgr_get_ctrl_blk(void **rblk)
+{
+ *rblk = NULL;
+
+ return 0;
+}
+
+static int amixer_mgr_put_ctrl_blk(void *blk)
+{
+ return 0;
+}
+
+/*
+ * DAIO control block definitions.
+ */
+
+/* Receiver Sample Rate Tracker Control register */
+#define SRTCTL_SRCO 0x000000FF
+#define SRTCTL_SRCM 0x0000FF00
+#define SRTCTL_RSR 0x00030000
+#define SRTCTL_DRAT 0x00300000
+#define SRTCTL_EC 0x01000000
+#define SRTCTL_ET 0x10000000
+
+/* DAIO Receiver register dirty flags */
+union dai_dirty {
+ struct {
+ u16 srt:1;
+ u16 rsv:15;
+ } bf;
+ u16 data;
+};
+
+/* DAIO Receiver control block */
+struct dai_ctrl_blk {
+ unsigned int srt;
+ union dai_dirty dirty;
+};
+
+/* Audio Input Mapper RAM */
+#define AIM_ARC 0x00000FFF
+#define AIM_NXT 0x007F0000
+
+struct daoimap {
+ unsigned int aim;
+ unsigned int idx;
+};
+
+/* Audio Transmitter Control and Status register */
+#define ATXCTL_EN 0x00000001
+#define ATXCTL_MODE 0x00000010
+#define ATXCTL_CD 0x00000020
+#define ATXCTL_RAW 0x00000100
+#define ATXCTL_MT 0x00000200
+#define ATXCTL_NUC 0x00003000
+#define ATXCTL_BEN 0x00010000
+#define ATXCTL_BMUX 0x00700000
+#define ATXCTL_B24 0x01000000
+#define ATXCTL_CPF 0x02000000
+#define ATXCTL_RIV 0x10000000
+#define ATXCTL_LIV 0x20000000
+#define ATXCTL_RSAT 0x40000000
+#define ATXCTL_LSAT 0x80000000
+
+/* XDIF Transmitter register dirty flags */
+union dao_dirty {
+ struct {
+ u16 atxcsl:1;
+ u16 rsv:15;
+ } bf;
+ u16 data;
+};
+
+/* XDIF Transmitter control block */
+struct dao_ctrl_blk {
+ /* XDIF Transmitter Channel Status Low Register */
+ unsigned int atxcsl;
+ union dao_dirty dirty;
+};
+
+/* Audio Receiver Control register */
+#define ARXCTL_EN 0x00000001
+
+/* DAIO manager register dirty flags */
+union daio_mgr_dirty {
+ struct {
+ u32 atxctl:8;
+ u32 arxctl:8;
+ u32 daoimap:1;
+ u32 rsv:15;
+ } bf;
+ u32 data;
+};
+
+/* DAIO manager control block */
+struct daio_mgr_ctrl_blk {
+ struct daoimap daoimap;
+ unsigned int txctl[8];
+ unsigned int rxctl[8];
+ union daio_mgr_dirty dirty;
+};
+
+static int dai_srt_set_srco(void *blk, unsigned int src)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srt, SRTCTL_SRCO, src);
+ ctl->dirty.bf.srt = 1;
+ return 0;
+}
+
+static int dai_srt_set_srcm(void *blk, unsigned int src)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srt, SRTCTL_SRCM, src);
+ ctl->dirty.bf.srt = 1;
+ return 0;
+}
+
+static int dai_srt_set_rsr(void *blk, unsigned int rsr)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srt, SRTCTL_RSR, rsr);
+ ctl->dirty.bf.srt = 1;
+ return 0;
+}
+
+static int dai_srt_set_drat(void *blk, unsigned int drat)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srt, SRTCTL_DRAT, drat);
+ ctl->dirty.bf.srt = 1;
+ return 0;
+}
+
+static int dai_srt_set_ec(void *blk, unsigned int ec)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srt, SRTCTL_EC, ec ? 1 : 0);
+ ctl->dirty.bf.srt = 1;
+ return 0;
+}
+
+static int dai_srt_set_et(void *blk, unsigned int et)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->srt, SRTCTL_ET, et ? 1 : 0);
+ ctl->dirty.bf.srt = 1;
+ return 0;
+}
+
+static int dai_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+ struct dai_ctrl_blk *ctl = blk;
+
+ if (ctl->dirty.bf.srt) {
+ hw_write_20kx(hw, AUDIO_IO_RX_SRT_CTL+0x40*idx, ctl->srt);
+ ctl->dirty.bf.srt = 0;
+ }
+
+ return 0;
+}
+
+static int dai_get_ctrl_blk(void **rblk)
+{
+ struct dai_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int dai_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int dao_set_spos(void *blk, unsigned int spos)
+{
+ ((struct dao_ctrl_blk *)blk)->atxcsl = spos;
+ ((struct dao_ctrl_blk *)blk)->dirty.bf.atxcsl = 1;
+ return 0;
+}
+
+static int dao_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+ struct dao_ctrl_blk *ctl = blk;
+
+ if (ctl->dirty.bf.atxcsl) {
+ if (idx < 4) {
+ /* S/PDIF SPOSx */
+ hw_write_20kx(hw, AUDIO_IO_TX_CSTAT_L+0x40*idx,
+ ctl->atxcsl);
+ }
+ ctl->dirty.bf.atxcsl = 0;
+ }
+
+ return 0;
+}
+
+static int dao_get_spos(void *blk, unsigned int *spos)
+{
+ *spos = ((struct dao_ctrl_blk *)blk)->atxcsl;
+ return 0;
+}
+
+static int dao_get_ctrl_blk(void **rblk)
+{
+ struct dao_ctrl_blk *blk;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int dao_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+static int daio_mgr_enb_dai(void *blk, unsigned int idx)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->rxctl[idx], ARXCTL_EN, 1);
+ ctl->dirty.bf.arxctl |= (0x1 << idx);
+ return 0;
+}
+
+static int daio_mgr_dsb_dai(void *blk, unsigned int idx)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->rxctl[idx], ARXCTL_EN, 0);
+
+ ctl->dirty.bf.arxctl |= (0x1 << idx);
+ return 0;
+}
+
+static int daio_mgr_enb_dao(void *blk, unsigned int idx)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->txctl[idx], ATXCTL_EN, 1);
+ ctl->dirty.bf.atxctl |= (0x1 << idx);
+ return 0;
+}
+
+static int daio_mgr_dsb_dao(void *blk, unsigned int idx)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->txctl[idx], ATXCTL_EN, 0);
+ ctl->dirty.bf.atxctl |= (0x1 << idx);
+ return 0;
+}
+
+static int daio_mgr_dao_init(void *blk, unsigned int idx, unsigned int conf)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ if (idx < 4) {
+ /* S/PDIF output */
+ switch ((conf & 0xf)) {
+ case 1:
+ set_field(&ctl->txctl[idx], ATXCTL_NUC, 0);
+ break;
+ case 2:
+ set_field(&ctl->txctl[idx], ATXCTL_NUC, 1);
+ break;
+ case 4:
+ set_field(&ctl->txctl[idx], ATXCTL_NUC, 2);
+ break;
+ case 8:
+ set_field(&ctl->txctl[idx], ATXCTL_NUC, 3);
+ break;
+ default:
+ break;
+ }
+ /* CDIF */
+ set_field(&ctl->txctl[idx], ATXCTL_CD, (!(conf & 0x7)));
+ /* Non-audio */
+ set_field(&ctl->txctl[idx], ATXCTL_LIV, (conf >> 4) & 0x1);
+ /* Non-audio */
+ set_field(&ctl->txctl[idx], ATXCTL_RIV, (conf >> 4) & 0x1);
+ set_field(&ctl->txctl[idx], ATXCTL_RAW,
+ ((conf >> 3) & 0x1) ? 0 : 0);
+ ctl->dirty.bf.atxctl |= (0x1 << idx);
+ } else {
+ /* I2S output */
+ /*idx %= 4; */
+ }
+ return 0;
+}
+
+static int daio_mgr_set_imaparc(void *blk, unsigned int slot)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->daoimap.aim, AIM_ARC, slot);
+ ctl->dirty.bf.daoimap = 1;
+ return 0;
+}
+
+static int daio_mgr_set_imapnxt(void *blk, unsigned int next)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+
+ set_field(&ctl->daoimap.aim, AIM_NXT, next);
+ ctl->dirty.bf.daoimap = 1;
+ return 0;
+}
+
+static int daio_mgr_set_imapaddr(void *blk, unsigned int addr)
+{
+ ((struct daio_mgr_ctrl_blk *)blk)->daoimap.idx = addr;
+ ((struct daio_mgr_ctrl_blk *)blk)->dirty.bf.daoimap = 1;
+ return 0;
+}
+
+static int daio_mgr_commit_write(struct hw *hw, void *blk)
+{
+ struct daio_mgr_ctrl_blk *ctl = blk;
+ unsigned int data;
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ if ((ctl->dirty.bf.atxctl & (0x1 << i))) {
+ data = ctl->txctl[i];
+ hw_write_20kx(hw, (AUDIO_IO_TX_CTL+(0x40*i)), data);
+ ctl->dirty.bf.atxctl &= ~(0x1 << i);
+ mdelay(1);
+ }
+ if ((ctl->dirty.bf.arxctl & (0x1 << i))) {
+ data = ctl->rxctl[i];
+ hw_write_20kx(hw, (AUDIO_IO_RX_CTL+(0x40*i)), data);
+ ctl->dirty.bf.arxctl &= ~(0x1 << i);
+ mdelay(1);
+ }
+ }
+ if (ctl->dirty.bf.daoimap) {
+ hw_write_20kx(hw, AUDIO_IO_AIM+ctl->daoimap.idx*4,
+ ctl->daoimap.aim);
+ ctl->dirty.bf.daoimap = 0;
+ }
+
+ return 0;
+}
+
+static int daio_mgr_get_ctrl_blk(struct hw *hw, void **rblk)
+{
+ struct daio_mgr_ctrl_blk *blk;
+ int i;
+
+ *rblk = NULL;
+ blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+ if (!blk)
+ return -ENOMEM;
+
+ for (i = 0; i < 8; i++) {
+ blk->txctl[i] = hw_read_20kx(hw, AUDIO_IO_TX_CTL+(0x40*i));
+ blk->rxctl[i] = hw_read_20kx(hw, AUDIO_IO_RX_CTL+(0x40*i));
+ }
+
+ *rblk = blk;
+
+ return 0;
+}
+
+static int daio_mgr_put_ctrl_blk(void *blk)
+{
+ kfree(blk);
+
+ return 0;
+}
+
+/* Timer interrupt */
+static int set_timer_irq(struct hw *hw, int enable)
+{
+ hw_write_20kx(hw, GIE, enable ? IT_INT : 0);
+ return 0;
+}
+
+static int set_timer_tick(struct hw *hw, unsigned int ticks)
+{
+ if (ticks)
+ ticks |= TIMR_IE | TIMR_IP;
+ hw_write_20kx(hw, TIMR, ticks);
+ return 0;
+}
+
+static unsigned int get_wc(struct hw *hw)
+{
+ return hw_read_20kx(hw, WC);
+}
+
+/* Card hardware initialization block */
+struct dac_conf {
+ unsigned int msr; /* master sample rate in rsrs */
+};
+
+struct adc_conf {
+ unsigned int msr; /* master sample rate in rsrs */
+ unsigned char input; /* the input source of ADC */
+ unsigned char mic20db; /* boost mic by 20db if input is microphone */
+};
+
+struct daio_conf {
+ unsigned int msr; /* master sample rate in rsrs */
+};
+
+struct trn_conf {
+ unsigned long vm_pgt_phys;
+};
+
+static int hw_daio_init(struct hw *hw, const struct daio_conf *info)
+{
+ u32 data;
+ int i;
+
+ /* Program I2S with proper sample rate and enable the correct I2S
+ * channel. ED(0/8/16/24): Enable all I2S/I2X master clock output */
+ if (1 == info->msr) {
+ hw_write_20kx(hw, AUDIO_IO_MCLK, 0x01010101);
+ hw_write_20kx(hw, AUDIO_IO_TX_BLRCLK, 0x01010101);
+ hw_write_20kx(hw, AUDIO_IO_RX_BLRCLK, 0);
+ } else if (2 == info->msr) {
+ if (hw->model != CTSB1270) {
+ hw_write_20kx(hw, AUDIO_IO_MCLK, 0x11111111);
+ } else {
+ /* PCM4220 on Titanium HD is different. */
+ hw_write_20kx(hw, AUDIO_IO_MCLK, 0x11011111);
+ }
+ /* Specify all playing 96khz
+ * EA [0] - Enabled
+ * RTA [4:5] - 96kHz
+ * EB [8] - Enabled
+ * RTB [12:13] - 96kHz
+ * EC [16] - Enabled
+ * RTC [20:21] - 96kHz
+ * ED [24] - Enabled
+ * RTD [28:29] - 96kHz */
+ hw_write_20kx(hw, AUDIO_IO_TX_BLRCLK, 0x11111111);
+ hw_write_20kx(hw, AUDIO_IO_RX_BLRCLK, 0);
+ } else if ((4 == info->msr) && (hw->model == CTSB1270)) {
+ hw_write_20kx(hw, AUDIO_IO_MCLK, 0x21011111);
+ hw_write_20kx(hw, AUDIO_IO_TX_BLRCLK, 0x21212121);
+ hw_write_20kx(hw, AUDIO_IO_RX_BLRCLK, 0);
+ } else {
+ dev_alert(hw->card->dev,
+ "ERROR!!! Invalid sampling rate!!!\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 8; i++) {
+ if (i <= 3) {
+ /* This comment looks wrong since loop is over 4 */
+ /* channels and emu20k2 supports 4 spdif IOs. */
+ /* 1st 3 channels are SPDIFs (SB0960) */
+ if (i == 3)
+ data = 0x1001001;
+ else
+ data = 0x1000001;
+
+ hw_write_20kx(hw, (AUDIO_IO_TX_CTL+(0x40*i)), data);
+ hw_write_20kx(hw, (AUDIO_IO_RX_CTL+(0x40*i)), data);
+
+ /* Initialize the SPDIF Out Channel status registers.
+ * The value specified here is based on the typical
+ * values provided in the specification, namely: Clock
+ * Accuracy of 1000ppm, Sample Rate of 48KHz,
+ * unspecified source number, Generation status = 1,
+ * Category code = 0x12 (Digital Signal Mixer),
+ * Mode = 0, Emph = 0, Copy Permitted, AN = 0
+ * (indicating that we're transmitting digital audio,
+ * and the Professional Use bit is 0. */
+
+ hw_write_20kx(hw, AUDIO_IO_TX_CSTAT_L+(0x40*i),
+ 0x02109204); /* Default to 48kHz */
+
+ hw_write_20kx(hw, AUDIO_IO_TX_CSTAT_H+(0x40*i), 0x0B);
+ } else {
+ /* Again, loop is over 4 channels not 5. */
+ /* Next 5 channels are I2S (SB0960) */
+ data = 0x11;
+ hw_write_20kx(hw, AUDIO_IO_RX_CTL+(0x40*i), data);
+ if (2 == info->msr) {
+ /* Four channels per sample period */
+ data |= 0x1000;
+ } else if (4 == info->msr) {
+ /* FIXME: check this against the chip spec */
+ data |= 0x2000;
+ }
+ hw_write_20kx(hw, AUDIO_IO_TX_CTL+(0x40*i), data);
+ }
+ }
+
+ return 0;
+}
+
+/* TRANSPORT operations */
+static int hw_trn_init(struct hw *hw, const struct trn_conf *info)
+{
+ u32 vmctl, data;
+ u32 ptp_phys_low, ptp_phys_high;
+ int i;
+
+ /* Set up device page table */
+ if ((~0UL) == info->vm_pgt_phys) {
+ dev_alert(hw->card->dev,
+ "Wrong device page table page address!!!\n");
+ return -1;
+ }
+
+ vmctl = 0x80000C0F; /* 32-bit, 4k-size page */
+ ptp_phys_low = (u32)info->vm_pgt_phys;
+ ptp_phys_high = upper_32_bits(info->vm_pgt_phys);
+ if (sizeof(void *) == 8) /* 64bit address */
+ vmctl |= (3 << 8);
+ /* Write page table physical address to all PTPAL registers */
+ for (i = 0; i < 64; i++) {
+ hw_write_20kx(hw, VMEM_PTPAL+(16*i), ptp_phys_low);
+ hw_write_20kx(hw, VMEM_PTPAH+(16*i), ptp_phys_high);
+ }
+ /* Enable virtual memory transfer */
+ hw_write_20kx(hw, VMEM_CTL, vmctl);
+ /* Enable transport bus master and queueing of request */
+ hw_write_20kx(hw, TRANSPORT_CTL, 0x03);
+ hw_write_20kx(hw, TRANSPORT_INT, 0x200c01);
+ /* Enable transport ring */
+ data = hw_read_20kx(hw, TRANSPORT_ENB);
+ hw_write_20kx(hw, TRANSPORT_ENB, (data | 0x03));
+
+ return 0;
+}
+
+/* Card initialization */
+#define GCTL_AIE 0x00000001
+#define GCTL_UAA 0x00000002
+#define GCTL_DPC 0x00000004
+#define GCTL_DBP 0x00000008
+#define GCTL_ABP 0x00000010
+#define GCTL_TBP 0x00000020
+#define GCTL_SBP 0x00000040
+#define GCTL_FBP 0x00000080
+#define GCTL_ME 0x00000100
+#define GCTL_AID 0x00001000
+
+#define PLLCTL_SRC 0x00000007
+#define PLLCTL_SPE 0x00000008
+#define PLLCTL_RD 0x000000F0
+#define PLLCTL_FD 0x0001FF00
+#define PLLCTL_OD 0x00060000
+#define PLLCTL_B 0x00080000
+#define PLLCTL_AS 0x00100000
+#define PLLCTL_LF 0x03E00000
+#define PLLCTL_SPS 0x1C000000
+#define PLLCTL_AD 0x60000000
+
+#define PLLSTAT_CCS 0x00000007
+#define PLLSTAT_SPL 0x00000008
+#define PLLSTAT_CRD 0x000000F0
+#define PLLSTAT_CFD 0x0001FF00
+#define PLLSTAT_SL 0x00020000
+#define PLLSTAT_FAS 0x00040000
+#define PLLSTAT_B 0x00080000
+#define PLLSTAT_PD 0x00100000
+#define PLLSTAT_OCA 0x00200000
+#define PLLSTAT_NCA 0x00400000
+
+static int hw_pll_init(struct hw *hw, unsigned int rsr)
+{
+ unsigned int pllenb;
+ unsigned int pllctl;
+ unsigned int pllstat;
+ int i;
+
+ pllenb = 0xB;
+ hw_write_20kx(hw, PLL_ENB, pllenb);
+ pllctl = 0x20C00000;
+ set_field(&pllctl, PLLCTL_B, 0);
+ set_field(&pllctl, PLLCTL_FD, 48000 == rsr ? 16 - 4 : 147 - 4);
+ set_field(&pllctl, PLLCTL_RD, 48000 == rsr ? 1 - 1 : 10 - 1);
+ hw_write_20kx(hw, PLL_CTL, pllctl);
+ msleep(40);
+
+ pllctl = hw_read_20kx(hw, PLL_CTL);
+ set_field(&pllctl, PLLCTL_FD, 48000 == rsr ? 16 - 2 : 147 - 2);
+ hw_write_20kx(hw, PLL_CTL, pllctl);
+ msleep(40);
+
+ for (i = 0; i < 1000; i++) {
+ pllstat = hw_read_20kx(hw, PLL_STAT);
+ if (get_field(pllstat, PLLSTAT_PD))
+ continue;
+
+ if (get_field(pllstat, PLLSTAT_B) !=
+ get_field(pllctl, PLLCTL_B))
+ continue;
+
+ if (get_field(pllstat, PLLSTAT_CCS) !=
+ get_field(pllctl, PLLCTL_SRC))
+ continue;
+
+ if (get_field(pllstat, PLLSTAT_CRD) !=
+ get_field(pllctl, PLLCTL_RD))
+ continue;
+
+ if (get_field(pllstat, PLLSTAT_CFD) !=
+ get_field(pllctl, PLLCTL_FD))
+ continue;
+
+ break;
+ }
+ if (i >= 1000) {
+ dev_alert(hw->card->dev,
+ "PLL initialization failed!!!\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int hw_auto_init(struct hw *hw)
+{
+ unsigned int gctl;
+ int i;
+
+ gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL);
+ set_field(&gctl, GCTL_AIE, 0);
+ hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl);
+ set_field(&gctl, GCTL_AIE, 1);
+ hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl);
+ mdelay(10);
+ for (i = 0; i < 400000; i++) {
+ gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL);
+ if (get_field(gctl, GCTL_AID))
+ break;
+ }
+ if (!get_field(gctl, GCTL_AID)) {
+ dev_alert(hw->card->dev, "Card Auto-init failed!!!\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+/* DAC operations */
+
+#define CS4382_MC1 0x1
+#define CS4382_MC2 0x2
+#define CS4382_MC3 0x3
+#define CS4382_FC 0x4
+#define CS4382_IC 0x5
+#define CS4382_XC1 0x6
+#define CS4382_VCA1 0x7
+#define CS4382_VCB1 0x8
+#define CS4382_XC2 0x9
+#define CS4382_VCA2 0xA
+#define CS4382_VCB2 0xB
+#define CS4382_XC3 0xC
+#define CS4382_VCA3 0xD
+#define CS4382_VCB3 0xE
+#define CS4382_XC4 0xF
+#define CS4382_VCA4 0x10
+#define CS4382_VCB4 0x11
+#define CS4382_CREV 0x12
+
+/* I2C status */
+#define STATE_LOCKED 0x00
+#define STATE_UNLOCKED 0xAA
+#define DATA_READY 0x800000 /* Used with I2C_IF_STATUS */
+#define DATA_ABORT 0x10000 /* Used with I2C_IF_STATUS */
+
+#define I2C_STATUS_DCM 0x00000001
+#define I2C_STATUS_BC 0x00000006
+#define I2C_STATUS_APD 0x00000008
+#define I2C_STATUS_AB 0x00010000
+#define I2C_STATUS_DR 0x00800000
+
+#define I2C_ADDRESS_PTAD 0x0000FFFF
+#define I2C_ADDRESS_SLAD 0x007F0000
+
+struct regs_cs4382 {
+ u32 mode_control_1;
+ u32 mode_control_2;
+ u32 mode_control_3;
+
+ u32 filter_control;
+ u32 invert_control;
+
+ u32 mix_control_P1;
+ u32 vol_control_A1;
+ u32 vol_control_B1;
+
+ u32 mix_control_P2;
+ u32 vol_control_A2;
+ u32 vol_control_B2;
+
+ u32 mix_control_P3;
+ u32 vol_control_A3;
+ u32 vol_control_B3;
+
+ u32 mix_control_P4;
+ u32 vol_control_A4;
+ u32 vol_control_B4;
+};
+
+static int hw20k2_i2c_unlock_full_access(struct hw *hw)
+{
+ u8 UnlockKeySequence_FLASH_FULLACCESS_MODE[2] = {0xB3, 0xD4};
+
+ /* Send keys for forced BIOS mode */
+ hw_write_20kx(hw, I2C_IF_WLOCK,
+ UnlockKeySequence_FLASH_FULLACCESS_MODE[0]);
+ hw_write_20kx(hw, I2C_IF_WLOCK,
+ UnlockKeySequence_FLASH_FULLACCESS_MODE[1]);
+ /* Check whether the chip is unlocked */
+ if (hw_read_20kx(hw, I2C_IF_WLOCK) == STATE_UNLOCKED)
+ return 0;
+
+ return -1;
+}
+
+static int hw20k2_i2c_lock_chip(struct hw *hw)
+{
+ /* Write twice */
+ hw_write_20kx(hw, I2C_IF_WLOCK, STATE_LOCKED);
+ hw_write_20kx(hw, I2C_IF_WLOCK, STATE_LOCKED);
+ if (hw_read_20kx(hw, I2C_IF_WLOCK) == STATE_LOCKED)
+ return 0;
+
+ return -1;
+}
+
+static int hw20k2_i2c_init(struct hw *hw, u8 dev_id, u8 addr_size, u8 data_size)
+{
+ struct hw20k2 *hw20k2 = (struct hw20k2 *)hw;
+ int err;
+ unsigned int i2c_status;
+ unsigned int i2c_addr;
+
+ err = hw20k2_i2c_unlock_full_access(hw);
+ if (err < 0)
+ return err;
+
+ hw20k2->addr_size = addr_size;
+ hw20k2->data_size = data_size;
+ hw20k2->dev_id = dev_id;
+
+ i2c_addr = 0;
+ set_field(&i2c_addr, I2C_ADDRESS_SLAD, dev_id);
+
+ hw_write_20kx(hw, I2C_IF_ADDRESS, i2c_addr);
+
+ i2c_status = hw_read_20kx(hw, I2C_IF_STATUS);
+
+ set_field(&i2c_status, I2C_STATUS_DCM, 1); /* Direct control mode */
+
+ hw_write_20kx(hw, I2C_IF_STATUS, i2c_status);
+
+ return 0;
+}
+
+static int hw20k2_i2c_uninit(struct hw *hw)
+{
+ unsigned int i2c_status;
+ unsigned int i2c_addr;
+
+ i2c_addr = 0;
+ set_field(&i2c_addr, I2C_ADDRESS_SLAD, 0x57); /* I2C id */
+
+ hw_write_20kx(hw, I2C_IF_ADDRESS, i2c_addr);
+
+ i2c_status = hw_read_20kx(hw, I2C_IF_STATUS);
+
+ set_field(&i2c_status, I2C_STATUS_DCM, 0); /* I2C mode */
+
+ hw_write_20kx(hw, I2C_IF_STATUS, i2c_status);
+
+ return hw20k2_i2c_lock_chip(hw);
+}
+
+static int hw20k2_i2c_wait_data_ready(struct hw *hw)
+{
+ int i = 0x400000;
+ unsigned int ret;
+
+ do {
+ ret = hw_read_20kx(hw, I2C_IF_STATUS);
+ } while ((!(ret & DATA_READY)) && --i);
+
+ return i;
+}
+
+static int hw20k2_i2c_read(struct hw *hw, u16 addr, u32 *datap)
+{
+ struct hw20k2 *hw20k2 = (struct hw20k2 *)hw;
+ unsigned int i2c_status;
+
+ i2c_status = hw_read_20kx(hw, I2C_IF_STATUS);
+ set_field(&i2c_status, I2C_STATUS_BC,
+ (4 == hw20k2->addr_size) ? 0 : hw20k2->addr_size);
+ hw_write_20kx(hw, I2C_IF_STATUS, i2c_status);
+ if (!hw20k2_i2c_wait_data_ready(hw))
+ return -1;
+
+ hw_write_20kx(hw, I2C_IF_WDATA, addr);
+ if (!hw20k2_i2c_wait_data_ready(hw))
+ return -1;
+
+ /* Force a read operation */
+ hw_write_20kx(hw, I2C_IF_RDATA, 0);
+ if (!hw20k2_i2c_wait_data_ready(hw))
+ return -1;
+
+ *datap = hw_read_20kx(hw, I2C_IF_RDATA);
+
+ return 0;
+}
+
+static int hw20k2_i2c_write(struct hw *hw, u16 addr, u32 data)
+{
+ struct hw20k2 *hw20k2 = (struct hw20k2 *)hw;
+ unsigned int i2c_data = (data << (hw20k2->addr_size * 8)) | addr;
+ unsigned int i2c_status;
+
+ i2c_status = hw_read_20kx(hw, I2C_IF_STATUS);
+
+ set_field(&i2c_status, I2C_STATUS_BC,
+ (4 == (hw20k2->addr_size + hw20k2->data_size)) ?
+ 0 : (hw20k2->addr_size + hw20k2->data_size));
+
+ hw_write_20kx(hw, I2C_IF_STATUS, i2c_status);
+ hw20k2_i2c_wait_data_ready(hw);
+ /* Dummy write to trigger the write operation */
+ hw_write_20kx(hw, I2C_IF_WDATA, 0);
+ hw20k2_i2c_wait_data_ready(hw);
+
+ /* This is the real data */
+ hw_write_20kx(hw, I2C_IF_WDATA, i2c_data);
+ hw20k2_i2c_wait_data_ready(hw);
+
+ return 0;
+}
+
+static void hw_dac_stop(struct hw *hw)
+{
+ u32 data;
+ data = hw_read_20kx(hw, GPIO_DATA);
+ data &= 0xFFFFFFFD;
+ hw_write_20kx(hw, GPIO_DATA, data);
+ usleep_range(10000, 11000);
+}
+
+static void hw_dac_start(struct hw *hw)
+{
+ u32 data;
+ data = hw_read_20kx(hw, GPIO_DATA);
+ data |= 0x2;
+ hw_write_20kx(hw, GPIO_DATA, data);
+ msleep(50);
+}
+
+static void hw_dac_reset(struct hw *hw)
+{
+ hw_dac_stop(hw);
+ hw_dac_start(hw);
+}
+
+static int hw_dac_init(struct hw *hw, const struct dac_conf *info)
+{
+ int err;
+ u32 data;
+ int i;
+ struct regs_cs4382 cs_read = {0};
+ struct regs_cs4382 cs_def = {
+ .mode_control_1 = 0x00000001, /* Mode Control 1 */
+ .mode_control_2 = 0x00000000, /* Mode Control 2 */
+ .mode_control_3 = 0x00000084, /* Mode Control 3 */
+ .filter_control = 0x00000000, /* Filter Control */
+ .invert_control = 0x00000000, /* Invert Control */
+ .mix_control_P1 = 0x00000024, /* Mixing Control Pair 1 */
+ .vol_control_A1 = 0x00000000, /* Vol Control A1 */
+ .vol_control_B1 = 0x00000000, /* Vol Control B1 */
+ .mix_control_P2 = 0x00000024, /* Mixing Control Pair 2 */
+ .vol_control_A2 = 0x00000000, /* Vol Control A2 */
+ .vol_control_B2 = 0x00000000, /* Vol Control B2 */
+ .mix_control_P3 = 0x00000024, /* Mixing Control Pair 3 */
+ .vol_control_A3 = 0x00000000, /* Vol Control A3 */
+ .vol_control_B3 = 0x00000000, /* Vol Control B3 */
+ .mix_control_P4 = 0x00000024, /* Mixing Control Pair 4 */
+ .vol_control_A4 = 0x00000000, /* Vol Control A4 */
+ .vol_control_B4 = 0x00000000 /* Vol Control B4 */
+ };
+
+ if (hw->model == CTSB1270) {
+ hw_dac_stop(hw);
+ data = hw_read_20kx(hw, GPIO_DATA);
+ data &= ~0x0600;
+ if (1 == info->msr)
+ data |= 0x0000; /* Single Speed Mode 0-50kHz */
+ else if (2 == info->msr)
+ data |= 0x0200; /* Double Speed Mode 50-100kHz */
+ else
+ data |= 0x0600; /* Quad Speed Mode 100-200kHz */
+ hw_write_20kx(hw, GPIO_DATA, data);
+ hw_dac_start(hw);
+ return 0;
+ }
+
+ /* Set DAC reset bit as output */
+ data = hw_read_20kx(hw, GPIO_CTRL);
+ data |= 0x02;
+ hw_write_20kx(hw, GPIO_CTRL, data);
+
+ err = hw20k2_i2c_init(hw, 0x18, 1, 1);
+ if (err < 0)
+ goto End;
+
+ for (i = 0; i < 2; i++) {
+ /* Reset DAC twice just in-case the chip
+ * didn't initialized properly */
+ hw_dac_reset(hw);
+ hw_dac_reset(hw);
+
+ if (hw20k2_i2c_read(hw, CS4382_MC1, &cs_read.mode_control_1))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_MC2, &cs_read.mode_control_2))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_MC3, &cs_read.mode_control_3))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_FC, &cs_read.filter_control))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_IC, &cs_read.invert_control))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_XC1, &cs_read.mix_control_P1))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_VCA1, &cs_read.vol_control_A1))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_VCB1, &cs_read.vol_control_B1))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_XC2, &cs_read.mix_control_P2))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_VCA2, &cs_read.vol_control_A2))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_VCB2, &cs_read.vol_control_B2))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_XC3, &cs_read.mix_control_P3))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_VCA3, &cs_read.vol_control_A3))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_VCB3, &cs_read.vol_control_B3))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_XC4, &cs_read.mix_control_P4))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_VCA4, &cs_read.vol_control_A4))
+ continue;
+
+ if (hw20k2_i2c_read(hw, CS4382_VCB4, &cs_read.vol_control_B4))
+ continue;
+
+ if (memcmp(&cs_read, &cs_def, sizeof(cs_read)))
+ continue;
+ else
+ break;
+ }
+
+ if (i >= 2)
+ goto End;
+
+ /* Note: Every I2C write must have some delay.
+ * This is not a requirement but the delay works here... */
+ hw20k2_i2c_write(hw, CS4382_MC1, 0x80);
+ hw20k2_i2c_write(hw, CS4382_MC2, 0x10);
+ if (1 == info->msr) {
+ hw20k2_i2c_write(hw, CS4382_XC1, 0x24);
+ hw20k2_i2c_write(hw, CS4382_XC2, 0x24);
+ hw20k2_i2c_write(hw, CS4382_XC3, 0x24);
+ hw20k2_i2c_write(hw, CS4382_XC4, 0x24);
+ } else if (2 == info->msr) {
+ hw20k2_i2c_write(hw, CS4382_XC1, 0x25);
+ hw20k2_i2c_write(hw, CS4382_XC2, 0x25);
+ hw20k2_i2c_write(hw, CS4382_XC3, 0x25);
+ hw20k2_i2c_write(hw, CS4382_XC4, 0x25);
+ } else {
+ hw20k2_i2c_write(hw, CS4382_XC1, 0x26);
+ hw20k2_i2c_write(hw, CS4382_XC2, 0x26);
+ hw20k2_i2c_write(hw, CS4382_XC3, 0x26);
+ hw20k2_i2c_write(hw, CS4382_XC4, 0x26);
+ }
+
+ return 0;
+End:
+
+ hw20k2_i2c_uninit(hw);
+ return -1;
+}
+
+/* ADC operations */
+#define MAKE_WM8775_ADDR(addr, data) (u32)(((addr<<1)&0xFE)|((data>>8)&0x1))
+#define MAKE_WM8775_DATA(data) (u32)(data&0xFF)
+
+#define WM8775_IC 0x0B
+#define WM8775_MMC 0x0C
+#define WM8775_AADCL 0x0E
+#define WM8775_AADCR 0x0F
+#define WM8775_ADCMC 0x15
+#define WM8775_RESET 0x17
+
+static int hw_is_adc_input_selected(struct hw *hw, enum ADCSRC type)
+{
+ u32 data;
+ if (hw->model == CTSB1270) {
+ /* Titanium HD has two ADC chips, one for line in and one */
+ /* for MIC. We don't need to switch the ADC input. */
+ return 1;
+ }
+ data = hw_read_20kx(hw, GPIO_DATA);
+ switch (type) {
+ case ADC_MICIN:
+ data = (data & (0x1 << 14)) ? 1 : 0;
+ break;
+ case ADC_LINEIN:
+ data = (data & (0x1 << 14)) ? 0 : 1;
+ break;
+ default:
+ data = 0;
+ }
+ return data;
+}
+
+#define MIC_BOOST_0DB 0xCF
+#define MIC_BOOST_STEPS_PER_DB 2
+
+static void hw_wm8775_input_select(struct hw *hw, u8 input, s8 gain_in_db)
+{
+ u32 adcmc, gain;
+
+ if (input > 3)
+ input = 3;
+
+ adcmc = ((u32)1 << input) | 0x100; /* Link L+R gain... */
+
+ hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_ADCMC, adcmc),
+ MAKE_WM8775_DATA(adcmc));
+
+ if (gain_in_db < -103)
+ gain_in_db = -103;
+ if (gain_in_db > 24)
+ gain_in_db = 24;
+
+ gain = gain_in_db * MIC_BOOST_STEPS_PER_DB + MIC_BOOST_0DB;
+
+ hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCL, gain),
+ MAKE_WM8775_DATA(gain));
+ /* ...so there should be no need for the following. */
+ hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCR, gain),
+ MAKE_WM8775_DATA(gain));
+}
+
+static int hw_adc_input_select(struct hw *hw, enum ADCSRC type)
+{
+ u32 data;
+ data = hw_read_20kx(hw, GPIO_DATA);
+ switch (type) {
+ case ADC_MICIN:
+ data |= (0x1 << 14);
+ hw_write_20kx(hw, GPIO_DATA, data);
+ hw_wm8775_input_select(hw, 0, 20); /* Mic, 20dB */
+ break;
+ case ADC_LINEIN:
+ data &= ~(0x1 << 14);
+ hw_write_20kx(hw, GPIO_DATA, data);
+ hw_wm8775_input_select(hw, 1, 0); /* Line-in, 0dB */
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int hw_adc_init(struct hw *hw, const struct adc_conf *info)
+{
+ int err;
+ u32 data, ctl;
+
+ /* Set ADC reset bit as output */
+ data = hw_read_20kx(hw, GPIO_CTRL);
+ data |= (0x1 << 15);
+ hw_write_20kx(hw, GPIO_CTRL, data);
+
+ /* Initialize I2C */
+ err = hw20k2_i2c_init(hw, 0x1A, 1, 1);
+ if (err < 0) {
+ dev_alert(hw->card->dev, "Failure to acquire I2C!!!\n");
+ goto error;
+ }
+
+ /* Reset the ADC (reset is active low). */
+ data = hw_read_20kx(hw, GPIO_DATA);
+ data &= ~(0x1 << 15);
+ hw_write_20kx(hw, GPIO_DATA, data);
+
+ if (hw->model == CTSB1270) {
+ /* Set up the PCM4220 ADC on Titanium HD */
+ data &= ~0x0C;
+ if (1 == info->msr)
+ data |= 0x00; /* Single Speed Mode 32-50kHz */
+ else if (2 == info->msr)
+ data |= 0x08; /* Double Speed Mode 50-108kHz */
+ else
+ data |= 0x04; /* Quad Speed Mode 108kHz-216kHz */
+ hw_write_20kx(hw, GPIO_DATA, data);
+ }
+
+ usleep_range(10000, 11000);
+ /* Return the ADC to normal operation. */
+ data |= (0x1 << 15);
+ hw_write_20kx(hw, GPIO_DATA, data);
+ msleep(50);
+
+ /* I2C write to register offset 0x0B to set ADC LRCLK polarity */
+ /* invert bit, interface format to I2S, word length to 24-bit, */
+ /* enable ADC high pass filter. Fixes bug 5323? */
+ hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_IC, 0x26),
+ MAKE_WM8775_DATA(0x26));
+
+ /* Set the master mode (256fs) */
+ if (1 == info->msr) {
+ /* slave mode, 128x oversampling 256fs */
+ hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_MMC, 0x02),
+ MAKE_WM8775_DATA(0x02));
+ } else if ((2 == info->msr) || (4 == info->msr)) {
+ /* slave mode, 64x oversampling, 256fs */
+ hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_MMC, 0x0A),
+ MAKE_WM8775_DATA(0x0A));
+ } else {
+ dev_alert(hw->card->dev,
+ "Invalid master sampling rate (msr %d)!!!\n",
+ info->msr);
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (hw->model != CTSB1270) {
+ /* Configure GPIO bit 14 change to line-in/mic-in */
+ ctl = hw_read_20kx(hw, GPIO_CTRL);
+ ctl |= 0x1 << 14;
+ hw_write_20kx(hw, GPIO_CTRL, ctl);
+ hw_adc_input_select(hw, ADC_LINEIN);
+ } else {
+ hw_wm8775_input_select(hw, 0, 0);
+ }
+
+ return 0;
+error:
+ hw20k2_i2c_uninit(hw);
+ return err;
+}
+
+static struct capabilities hw_capabilities(struct hw *hw)
+{
+ struct capabilities cap;
+
+ cap.digit_io_switch = 0;
+ cap.dedicated_mic = hw->model == CTSB1270;
+ cap.output_switch = hw->model == CTSB1270;
+ cap.mic_source_switch = hw->model == CTSB1270;
+
+ return cap;
+}
+
+static int hw_output_switch_get(struct hw *hw)
+{
+ u32 data = hw_read_20kx(hw, GPIO_EXT_DATA);
+
+ switch (data & 0x30) {
+ case 0x00:
+ return 0;
+ case 0x10:
+ return 1;
+ case 0x20:
+ return 2;
+ default:
+ return 3;
+ }
+}
+
+static int hw_output_switch_put(struct hw *hw, int position)
+{
+ u32 data;
+
+ if (position == hw_output_switch_get(hw))
+ return 0;
+
+ /* Mute line and headphones (intended for anti-pop). */
+ data = hw_read_20kx(hw, GPIO_DATA);
+ data |= (0x03 << 11);
+ hw_write_20kx(hw, GPIO_DATA, data);
+
+ data = hw_read_20kx(hw, GPIO_EXT_DATA) & ~0x30;
+ switch (position) {
+ case 0:
+ break;
+ case 1:
+ data |= 0x10;
+ break;
+ default:
+ data |= 0x20;
+ }
+ hw_write_20kx(hw, GPIO_EXT_DATA, data);
+
+ /* Unmute line and headphones. */
+ data = hw_read_20kx(hw, GPIO_DATA);
+ data &= ~(0x03 << 11);
+ hw_write_20kx(hw, GPIO_DATA, data);
+
+ return 1;
+}
+
+static int hw_mic_source_switch_get(struct hw *hw)
+{
+ struct hw20k2 *hw20k2 = (struct hw20k2 *)hw;
+
+ return hw20k2->mic_source;
+}
+
+static int hw_mic_source_switch_put(struct hw *hw, int position)
+{
+ struct hw20k2 *hw20k2 = (struct hw20k2 *)hw;
+
+ if (position == hw20k2->mic_source)
+ return 0;
+
+ switch (position) {
+ case 0:
+ hw_wm8775_input_select(hw, 0, 0); /* Mic, 0dB */
+ break;
+ case 1:
+ hw_wm8775_input_select(hw, 1, 0); /* FP Mic, 0dB */
+ break;
+ case 2:
+ hw_wm8775_input_select(hw, 3, 0); /* Aux Ext, 0dB */
+ break;
+ default:
+ return 0;
+ }
+
+ hw20k2->mic_source = position;
+
+ return 1;
+}
+
+static irqreturn_t ct_20k2_interrupt(int irq, void *dev_id)
+{
+ struct hw *hw = dev_id;
+ unsigned int status;
+
+ status = hw_read_20kx(hw, GIP);
+ if (!status)
+ return IRQ_NONE;
+
+ if (hw->irq_callback)
+ hw->irq_callback(hw->irq_callback_data, status);
+
+ hw_write_20kx(hw, GIP, status);
+ return IRQ_HANDLED;
+}
+
+static int hw_card_start(struct hw *hw)
+{
+ int err = 0;
+ struct pci_dev *pci = hw->pci;
+ unsigned int gctl;
+ const unsigned int dma_bits = BITS_PER_LONG;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ /* Set DMA transfer mask */
+ if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(dma_bits)))
+ dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32));
+
+ if (!hw->io_base) {
+ err = pci_request_regions(pci, "XFi");
+ if (err < 0)
+ goto error1;
+
+ hw->io_base = pci_resource_start(hw->pci, 2);
+ hw->mem_base = ioremap(hw->io_base,
+ pci_resource_len(hw->pci, 2));
+ if (!hw->mem_base) {
+ err = -ENOENT;
+ goto error2;
+ }
+ }
+
+ /* Switch to 20k2 mode from UAA mode. */
+ gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL);
+ set_field(&gctl, GCTL_UAA, 0);
+ hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl);
+
+ if (hw->irq < 0) {
+ err = request_irq(pci->irq, ct_20k2_interrupt, IRQF_SHARED,
+ KBUILD_MODNAME, hw);
+ if (err < 0) {
+ dev_err(hw->card->dev,
+ "XFi: Cannot get irq %d\n", pci->irq);
+ goto error2;
+ }
+ hw->irq = pci->irq;
+ hw->card->sync_irq = hw->irq;
+ }
+
+ pci_set_master(pci);
+
+ return 0;
+
+/*error3:
+ iounmap((void *)hw->mem_base);
+ hw->mem_base = (unsigned long)NULL;*/
+error2:
+ pci_release_regions(pci);
+ hw->io_base = 0;
+error1:
+ pci_disable_device(pci);
+ return err;
+}
+
+static int hw_card_stop(struct hw *hw)
+{
+ unsigned int data;
+
+ /* disable transport bus master and queueing of request */
+ hw_write_20kx(hw, TRANSPORT_CTL, 0x00);
+
+ /* disable pll */
+ data = hw_read_20kx(hw, PLL_ENB);
+ hw_write_20kx(hw, PLL_ENB, (data & (~0x07)));
+
+ /* TODO: Disable interrupt and so on... */
+ return 0;
+}
+
+static int hw_card_shutdown(struct hw *hw)
+{
+ if (hw->irq >= 0)
+ free_irq(hw->irq, hw);
+
+ hw->irq = -1;
+ iounmap(hw->mem_base);
+ hw->mem_base = NULL;
+
+ if (hw->io_base)
+ pci_release_regions(hw->pci);
+
+ hw->io_base = 0;
+
+ pci_disable_device(hw->pci);
+
+ return 0;
+}
+
+static int hw_card_init(struct hw *hw, struct card_conf *info)
+{
+ int err;
+ unsigned int gctl;
+ u32 data = 0;
+ struct dac_conf dac_info = {0};
+ struct adc_conf adc_info = {0};
+ struct daio_conf daio_info = {0};
+ struct trn_conf trn_info = {0};
+
+ /* Get PCI io port/memory base address and
+ * do 20kx core switch if needed. */
+ err = hw_card_start(hw);
+ if (err)
+ return err;
+
+ /* PLL init */
+ err = hw_pll_init(hw, info->rsr);
+ if (err < 0)
+ return err;
+
+ /* kick off auto-init */
+ err = hw_auto_init(hw);
+ if (err < 0)
+ return err;
+
+ gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL);
+ set_field(&gctl, GCTL_DBP, 1);
+ set_field(&gctl, GCTL_TBP, 1);
+ set_field(&gctl, GCTL_FBP, 1);
+ set_field(&gctl, GCTL_DPC, 0);
+ hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl);
+
+ /* Reset all global pending interrupts */
+ hw_write_20kx(hw, GIE, 0);
+ /* Reset all SRC pending interrupts */
+ hw_write_20kx(hw, SRC_IP, 0);
+
+ if (hw->model != CTSB1270) {
+ /* TODO: detect the card ID and configure GPIO accordingly. */
+ /* Configures GPIO (0xD802 0x98028) */
+ /*hw_write_20kx(hw, GPIO_CTRL, 0x7F07);*/
+ /* Configures GPIO (SB0880) */
+ /*hw_write_20kx(hw, GPIO_CTRL, 0xFF07);*/
+ hw_write_20kx(hw, GPIO_CTRL, 0xD802);
+ } else {
+ hw_write_20kx(hw, GPIO_CTRL, 0x9E5F);
+ }
+ /* Enable audio ring */
+ hw_write_20kx(hw, MIXER_AR_ENABLE, 0x01);
+
+ trn_info.vm_pgt_phys = info->vm_pgt_phys;
+ err = hw_trn_init(hw, &trn_info);
+ if (err < 0)
+ return err;
+
+ daio_info.msr = info->msr;
+ err = hw_daio_init(hw, &daio_info);
+ if (err < 0)
+ return err;
+
+ dac_info.msr = info->msr;
+ err = hw_dac_init(hw, &dac_info);
+ if (err < 0)
+ return err;
+
+ adc_info.msr = info->msr;
+ adc_info.input = ADC_LINEIN;
+ adc_info.mic20db = 0;
+ err = hw_adc_init(hw, &adc_info);
+ if (err < 0)
+ return err;
+
+ data = hw_read_20kx(hw, SRC_MCTL);
+ data |= 0x1; /* Enables input from the audio ring */
+ hw_write_20kx(hw, SRC_MCTL, data);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int hw_suspend(struct hw *hw)
+{
+ hw_card_stop(hw);
+ return 0;
+}
+
+static int hw_resume(struct hw *hw, struct card_conf *info)
+{
+ /* Re-initialize card hardware. */
+ return hw_card_init(hw, info);
+}
+#endif
+
+static u32 hw_read_20kx(struct hw *hw, u32 reg)
+{
+ return readl(hw->mem_base + reg);
+}
+
+static void hw_write_20kx(struct hw *hw, u32 reg, u32 data)
+{
+ writel(data, hw->mem_base + reg);
+}
+
+static const struct hw ct20k2_preset = {
+ .irq = -1,
+
+ .card_init = hw_card_init,
+ .card_stop = hw_card_stop,
+ .pll_init = hw_pll_init,
+ .is_adc_source_selected = hw_is_adc_input_selected,
+ .select_adc_source = hw_adc_input_select,
+ .capabilities = hw_capabilities,
+ .output_switch_get = hw_output_switch_get,
+ .output_switch_put = hw_output_switch_put,
+ .mic_source_switch_get = hw_mic_source_switch_get,
+ .mic_source_switch_put = hw_mic_source_switch_put,
+#ifdef CONFIG_PM_SLEEP
+ .suspend = hw_suspend,
+ .resume = hw_resume,
+#endif
+
+ .src_rsc_get_ctrl_blk = src_get_rsc_ctrl_blk,
+ .src_rsc_put_ctrl_blk = src_put_rsc_ctrl_blk,
+ .src_mgr_get_ctrl_blk = src_mgr_get_ctrl_blk,
+ .src_mgr_put_ctrl_blk = src_mgr_put_ctrl_blk,
+ .src_set_state = src_set_state,
+ .src_set_bm = src_set_bm,
+ .src_set_rsr = src_set_rsr,
+ .src_set_sf = src_set_sf,
+ .src_set_wr = src_set_wr,
+ .src_set_pm = src_set_pm,
+ .src_set_rom = src_set_rom,
+ .src_set_vo = src_set_vo,
+ .src_set_st = src_set_st,
+ .src_set_ie = src_set_ie,
+ .src_set_ilsz = src_set_ilsz,
+ .src_set_bp = src_set_bp,
+ .src_set_cisz = src_set_cisz,
+ .src_set_ca = src_set_ca,
+ .src_set_sa = src_set_sa,
+ .src_set_la = src_set_la,
+ .src_set_pitch = src_set_pitch,
+ .src_set_dirty = src_set_dirty,
+ .src_set_clear_zbufs = src_set_clear_zbufs,
+ .src_set_dirty_all = src_set_dirty_all,
+ .src_commit_write = src_commit_write,
+ .src_get_ca = src_get_ca,
+ .src_get_dirty = src_get_dirty,
+ .src_dirty_conj_mask = src_dirty_conj_mask,
+ .src_mgr_enbs_src = src_mgr_enbs_src,
+ .src_mgr_enb_src = src_mgr_enb_src,
+ .src_mgr_dsb_src = src_mgr_dsb_src,
+ .src_mgr_commit_write = src_mgr_commit_write,
+
+ .srcimp_mgr_get_ctrl_blk = srcimp_mgr_get_ctrl_blk,
+ .srcimp_mgr_put_ctrl_blk = srcimp_mgr_put_ctrl_blk,
+ .srcimp_mgr_set_imaparc = srcimp_mgr_set_imaparc,
+ .srcimp_mgr_set_imapuser = srcimp_mgr_set_imapuser,
+ .srcimp_mgr_set_imapnxt = srcimp_mgr_set_imapnxt,
+ .srcimp_mgr_set_imapaddr = srcimp_mgr_set_imapaddr,
+ .srcimp_mgr_commit_write = srcimp_mgr_commit_write,
+
+ .amixer_rsc_get_ctrl_blk = amixer_rsc_get_ctrl_blk,
+ .amixer_rsc_put_ctrl_blk = amixer_rsc_put_ctrl_blk,
+ .amixer_mgr_get_ctrl_blk = amixer_mgr_get_ctrl_blk,
+ .amixer_mgr_put_ctrl_blk = amixer_mgr_put_ctrl_blk,
+ .amixer_set_mode = amixer_set_mode,
+ .amixer_set_iv = amixer_set_iv,
+ .amixer_set_x = amixer_set_x,
+ .amixer_set_y = amixer_set_y,
+ .amixer_set_sadr = amixer_set_sadr,
+ .amixer_set_se = amixer_set_se,
+ .amixer_set_dirty = amixer_set_dirty,
+ .amixer_set_dirty_all = amixer_set_dirty_all,
+ .amixer_commit_write = amixer_commit_write,
+ .amixer_get_y = amixer_get_y,
+ .amixer_get_dirty = amixer_get_dirty,
+
+ .dai_get_ctrl_blk = dai_get_ctrl_blk,
+ .dai_put_ctrl_blk = dai_put_ctrl_blk,
+ .dai_srt_set_srco = dai_srt_set_srco,
+ .dai_srt_set_srcm = dai_srt_set_srcm,
+ .dai_srt_set_rsr = dai_srt_set_rsr,
+ .dai_srt_set_drat = dai_srt_set_drat,
+ .dai_srt_set_ec = dai_srt_set_ec,
+ .dai_srt_set_et = dai_srt_set_et,
+ .dai_commit_write = dai_commit_write,
+
+ .dao_get_ctrl_blk = dao_get_ctrl_blk,
+ .dao_put_ctrl_blk = dao_put_ctrl_blk,
+ .dao_set_spos = dao_set_spos,
+ .dao_commit_write = dao_commit_write,
+ .dao_get_spos = dao_get_spos,
+
+ .daio_mgr_get_ctrl_blk = daio_mgr_get_ctrl_blk,
+ .daio_mgr_put_ctrl_blk = daio_mgr_put_ctrl_blk,
+ .daio_mgr_enb_dai = daio_mgr_enb_dai,
+ .daio_mgr_dsb_dai = daio_mgr_dsb_dai,
+ .daio_mgr_enb_dao = daio_mgr_enb_dao,
+ .daio_mgr_dsb_dao = daio_mgr_dsb_dao,
+ .daio_mgr_dao_init = daio_mgr_dao_init,
+ .daio_mgr_set_imaparc = daio_mgr_set_imaparc,
+ .daio_mgr_set_imapnxt = daio_mgr_set_imapnxt,
+ .daio_mgr_set_imapaddr = daio_mgr_set_imapaddr,
+ .daio_mgr_commit_write = daio_mgr_commit_write,
+
+ .set_timer_irq = set_timer_irq,
+ .set_timer_tick = set_timer_tick,
+ .get_wc = get_wc,
+};
+
+int create_20k2_hw_obj(struct hw **rhw)
+{
+ struct hw20k2 *hw20k2;
+
+ *rhw = NULL;
+ hw20k2 = kzalloc(sizeof(*hw20k2), GFP_KERNEL);
+ if (!hw20k2)
+ return -ENOMEM;
+
+ hw20k2->hw = ct20k2_preset;
+ *rhw = &hw20k2->hw;
+
+ return 0;
+}
+
+int destroy_20k2_hw_obj(struct hw *hw)
+{
+ if (hw->io_base)
+ hw_card_shutdown(hw);
+
+ kfree(hw);
+ return 0;
+}
diff --git a/sound/pci/ctxfi/cthw20k2.h b/sound/pci/ctxfi/cthw20k2.h
new file mode 100644
index 000000000..6993a3d52
--- /dev/null
+++ b/sound/pci/ctxfi/cthw20k2.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File cthw20k2.h
+ *
+ * @Brief
+ * This file contains the definition of hardware access methord.
+ *
+ * @Author Liu Chun
+ * @Date May 13 2008
+ */
+
+#ifndef CTHW20K2_H
+#define CTHW20K2_H
+
+#include "cthardware.h"
+
+int create_20k2_hw_obj(struct hw **rhw);
+int destroy_20k2_hw_obj(struct hw *hw);
+
+#endif /* CTHW20K2_H */
diff --git a/sound/pci/ctxfi/ctimap.c b/sound/pci/ctxfi/ctimap.c
new file mode 100644
index 000000000..d5a53d2f5
--- /dev/null
+++ b/sound/pci/ctxfi/ctimap.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctimap.c
+ *
+ * @Brief
+ * This file contains the implementation of generic input mapper operations
+ * for input mapper management.
+ *
+ * @Author Liu Chun
+ * @Date May 23 2008
+ */
+
+#include "ctimap.h"
+#include <linux/slab.h>
+
+int input_mapper_add(struct list_head *mappers, struct imapper *entry,
+ int (*map_op)(void *, struct imapper *), void *data)
+{
+ struct list_head *pos, *pre, *head;
+ struct imapper *pre_ent, *pos_ent;
+
+ head = mappers;
+
+ if (list_empty(head)) {
+ entry->next = entry->addr;
+ map_op(data, entry);
+ list_add(&entry->list, head);
+ return 0;
+ }
+
+ list_for_each(pos, head) {
+ pos_ent = list_entry(pos, struct imapper, list);
+ if (pos_ent->slot > entry->slot) {
+ /* found a position in list */
+ break;
+ }
+ }
+
+ if (pos != head) {
+ pre = pos->prev;
+ if (pre == head)
+ pre = head->prev;
+
+ __list_add(&entry->list, pos->prev, pos);
+ } else {
+ pre = head->prev;
+ pos = head->next;
+ list_add_tail(&entry->list, head);
+ }
+
+ pre_ent = list_entry(pre, struct imapper, list);
+ pos_ent = list_entry(pos, struct imapper, list);
+
+ entry->next = pos_ent->addr;
+ map_op(data, entry);
+ pre_ent->next = entry->addr;
+ map_op(data, pre_ent);
+
+ return 0;
+}
+
+int input_mapper_delete(struct list_head *mappers, struct imapper *entry,
+ int (*map_op)(void *, struct imapper *), void *data)
+{
+ struct list_head *next, *pre, *head;
+ struct imapper *pre_ent, *next_ent;
+
+ head = mappers;
+
+ if (list_empty(head))
+ return 0;
+
+ pre = (entry->list.prev == head) ? head->prev : entry->list.prev;
+ next = (entry->list.next == head) ? head->next : entry->list.next;
+
+ if (pre == &entry->list) {
+ /* entry is the only one node in mappers list */
+ entry->next = entry->addr = entry->user = entry->slot = 0;
+ map_op(data, entry);
+ list_del(&entry->list);
+ return 0;
+ }
+
+ pre_ent = list_entry(pre, struct imapper, list);
+ next_ent = list_entry(next, struct imapper, list);
+
+ pre_ent->next = next_ent->addr;
+ map_op(data, pre_ent);
+ list_del(&entry->list);
+
+ return 0;
+}
+
+void free_input_mapper_list(struct list_head *head)
+{
+ struct imapper *entry;
+ struct list_head *pos;
+
+ while (!list_empty(head)) {
+ pos = head->next;
+ list_del(pos);
+ entry = list_entry(pos, struct imapper, list);
+ kfree(entry);
+ }
+}
+
diff --git a/sound/pci/ctxfi/ctimap.h b/sound/pci/ctxfi/ctimap.h
new file mode 100644
index 000000000..49b1bb831
--- /dev/null
+++ b/sound/pci/ctxfi/ctimap.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctimap.h
+ *
+ * @Brief
+ * This file contains the definition of generic input mapper operations
+ * for input mapper management.
+ *
+ * @Author Liu Chun
+ * @Date May 23 2008
+ */
+
+#ifndef CTIMAP_H
+#define CTIMAP_H
+
+#include <linux/list.h>
+
+struct imapper {
+ unsigned short slot; /* the id of the slot containing input data */
+ unsigned short user; /* the id of the user resource consuming data */
+ unsigned short addr; /* the input mapper ram id */
+ unsigned short next; /* the next input mapper ram id */
+ struct list_head list;
+};
+
+int input_mapper_add(struct list_head *mappers, struct imapper *entry,
+ int (*map_op)(void *, struct imapper *), void *data);
+
+int input_mapper_delete(struct list_head *mappers, struct imapper *entry,
+ int (*map_op)(void *, struct imapper *), void *data);
+
+void free_input_mapper_list(struct list_head *mappers);
+
+#endif /* CTIMAP_H */
diff --git a/sound/pci/ctxfi/ctmixer.c b/sound/pci/ctxfi/ctmixer.c
new file mode 100644
index 000000000..6797fde3d
--- /dev/null
+++ b/sound/pci/ctxfi/ctmixer.c
@@ -0,0 +1,1225 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctmixer.c
+ *
+ * @Brief
+ * This file contains the implementation of alsa mixer device functions.
+ *
+ * @Author Liu Chun
+ * @Date May 28 2008
+ */
+
+
+#include "ctmixer.h"
+#include "ctamixer.h"
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+
+enum CT_SUM_CTL {
+ SUM_IN_F,
+ SUM_IN_R,
+ SUM_IN_C,
+ SUM_IN_S,
+ SUM_IN_F_C,
+
+ NUM_CT_SUMS
+};
+
+enum CT_AMIXER_CTL {
+ /* volume control mixers */
+ AMIXER_MASTER_F,
+ AMIXER_MASTER_R,
+ AMIXER_MASTER_C,
+ AMIXER_MASTER_S,
+ AMIXER_PCM_F,
+ AMIXER_PCM_R,
+ AMIXER_PCM_C,
+ AMIXER_PCM_S,
+ AMIXER_SPDIFI,
+ AMIXER_LINEIN,
+ AMIXER_MIC,
+ AMIXER_SPDIFO,
+ AMIXER_WAVE_F,
+ AMIXER_WAVE_R,
+ AMIXER_WAVE_C,
+ AMIXER_WAVE_S,
+ AMIXER_MASTER_F_C,
+ AMIXER_PCM_F_C,
+ AMIXER_SPDIFI_C,
+ AMIXER_LINEIN_C,
+ AMIXER_MIC_C,
+
+ /* this should always be the last one */
+ NUM_CT_AMIXERS
+};
+
+enum CTALSA_MIXER_CTL {
+ /* volume control mixers */
+ MIXER_MASTER_P,
+ MIXER_PCM_P,
+ MIXER_LINEIN_P,
+ MIXER_MIC_P,
+ MIXER_SPDIFI_P,
+ MIXER_SPDIFO_P,
+ MIXER_WAVEF_P,
+ MIXER_WAVER_P,
+ MIXER_WAVEC_P,
+ MIXER_WAVES_P,
+ MIXER_MASTER_C,
+ MIXER_PCM_C,
+ MIXER_LINEIN_C,
+ MIXER_MIC_C,
+ MIXER_SPDIFI_C,
+
+ /* switch control mixers */
+ MIXER_PCM_C_S,
+ MIXER_LINEIN_C_S,
+ MIXER_MIC_C_S,
+ MIXER_SPDIFI_C_S,
+ MIXER_SPDIFO_P_S,
+ MIXER_WAVEF_P_S,
+ MIXER_WAVER_P_S,
+ MIXER_WAVEC_P_S,
+ MIXER_WAVES_P_S,
+ MIXER_DIGITAL_IO_S,
+ MIXER_IEC958_MASK,
+ MIXER_IEC958_DEFAULT,
+ MIXER_IEC958_STREAM,
+
+ /* this should always be the last one */
+ NUM_CTALSA_MIXERS
+};
+
+#define VOL_MIXER_START MIXER_MASTER_P
+#define VOL_MIXER_END MIXER_SPDIFI_C
+#define VOL_MIXER_NUM (VOL_MIXER_END - VOL_MIXER_START + 1)
+#define SWH_MIXER_START MIXER_PCM_C_S
+#define SWH_MIXER_END MIXER_DIGITAL_IO_S
+#define SWH_CAPTURE_START MIXER_PCM_C_S
+#define SWH_CAPTURE_END MIXER_SPDIFI_C_S
+
+#define CHN_NUM 2
+
+struct ct_kcontrol_init {
+ unsigned char ctl;
+ char *name;
+};
+
+static struct ct_kcontrol_init
+ct_kcontrol_init_table[NUM_CTALSA_MIXERS] = {
+ [MIXER_MASTER_P] = {
+ .ctl = 1,
+ .name = "Master Playback Volume",
+ },
+ [MIXER_MASTER_C] = {
+ .ctl = 1,
+ .name = "Master Capture Volume",
+ },
+ [MIXER_PCM_P] = {
+ .ctl = 1,
+ .name = "PCM Playback Volume",
+ },
+ [MIXER_PCM_C] = {
+ .ctl = 1,
+ .name = "PCM Capture Volume",
+ },
+ [MIXER_LINEIN_P] = {
+ .ctl = 1,
+ .name = "Line Playback Volume",
+ },
+ [MIXER_LINEIN_C] = {
+ .ctl = 1,
+ .name = "Line Capture Volume",
+ },
+ [MIXER_MIC_P] = {
+ .ctl = 1,
+ .name = "Mic Playback Volume",
+ },
+ [MIXER_MIC_C] = {
+ .ctl = 1,
+ .name = "Mic Capture Volume",
+ },
+ [MIXER_SPDIFI_P] = {
+ .ctl = 1,
+ .name = "IEC958 Playback Volume",
+ },
+ [MIXER_SPDIFI_C] = {
+ .ctl = 1,
+ .name = "IEC958 Capture Volume",
+ },
+ [MIXER_SPDIFO_P] = {
+ .ctl = 1,
+ .name = "Digital Playback Volume",
+ },
+ [MIXER_WAVEF_P] = {
+ .ctl = 1,
+ .name = "Front Playback Volume",
+ },
+ [MIXER_WAVES_P] = {
+ .ctl = 1,
+ .name = "Side Playback Volume",
+ },
+ [MIXER_WAVEC_P] = {
+ .ctl = 1,
+ .name = "Center/LFE Playback Volume",
+ },
+ [MIXER_WAVER_P] = {
+ .ctl = 1,
+ .name = "Surround Playback Volume",
+ },
+ [MIXER_PCM_C_S] = {
+ .ctl = 1,
+ .name = "PCM Capture Switch",
+ },
+ [MIXER_LINEIN_C_S] = {
+ .ctl = 1,
+ .name = "Line Capture Switch",
+ },
+ [MIXER_MIC_C_S] = {
+ .ctl = 1,
+ .name = "Mic Capture Switch",
+ },
+ [MIXER_SPDIFI_C_S] = {
+ .ctl = 1,
+ .name = "IEC958 Capture Switch",
+ },
+ [MIXER_SPDIFO_P_S] = {
+ .ctl = 1,
+ .name = "Digital Playback Switch",
+ },
+ [MIXER_WAVEF_P_S] = {
+ .ctl = 1,
+ .name = "Front Playback Switch",
+ },
+ [MIXER_WAVES_P_S] = {
+ .ctl = 1,
+ .name = "Side Playback Switch",
+ },
+ [MIXER_WAVEC_P_S] = {
+ .ctl = 1,
+ .name = "Center/LFE Playback Switch",
+ },
+ [MIXER_WAVER_P_S] = {
+ .ctl = 1,
+ .name = "Surround Playback Switch",
+ },
+ [MIXER_DIGITAL_IO_S] = {
+ .ctl = 0,
+ .name = "Digit-IO Playback Switch",
+ },
+};
+
+static void
+ct_mixer_recording_select(struct ct_mixer *mixer, enum CT_AMIXER_CTL type);
+
+static void
+ct_mixer_recording_unselect(struct ct_mixer *mixer, enum CT_AMIXER_CTL type);
+
+/* FIXME: this static looks like it would fail if more than one card was */
+/* installed. */
+static struct snd_kcontrol *kctls[2] = {NULL};
+
+static enum CT_AMIXER_CTL get_amixer_index(enum CTALSA_MIXER_CTL alsa_index)
+{
+ switch (alsa_index) {
+ case MIXER_MASTER_P: return AMIXER_MASTER_F;
+ case MIXER_MASTER_C: return AMIXER_MASTER_F_C;
+ case MIXER_PCM_P: return AMIXER_PCM_F;
+ case MIXER_PCM_C:
+ case MIXER_PCM_C_S: return AMIXER_PCM_F_C;
+ case MIXER_LINEIN_P: return AMIXER_LINEIN;
+ case MIXER_LINEIN_C:
+ case MIXER_LINEIN_C_S: return AMIXER_LINEIN_C;
+ case MIXER_MIC_P: return AMIXER_MIC;
+ case MIXER_MIC_C:
+ case MIXER_MIC_C_S: return AMIXER_MIC_C;
+ case MIXER_SPDIFI_P: return AMIXER_SPDIFI;
+ case MIXER_SPDIFI_C:
+ case MIXER_SPDIFI_C_S: return AMIXER_SPDIFI_C;
+ case MIXER_SPDIFO_P: return AMIXER_SPDIFO;
+ case MIXER_WAVEF_P: return AMIXER_WAVE_F;
+ case MIXER_WAVES_P: return AMIXER_WAVE_S;
+ case MIXER_WAVEC_P: return AMIXER_WAVE_C;
+ case MIXER_WAVER_P: return AMIXER_WAVE_R;
+ default: return NUM_CT_AMIXERS;
+ }
+}
+
+static enum CT_AMIXER_CTL get_recording_amixer(enum CT_AMIXER_CTL index)
+{
+ switch (index) {
+ case AMIXER_MASTER_F: return AMIXER_MASTER_F_C;
+ case AMIXER_PCM_F: return AMIXER_PCM_F_C;
+ case AMIXER_SPDIFI: return AMIXER_SPDIFI_C;
+ case AMIXER_LINEIN: return AMIXER_LINEIN_C;
+ case AMIXER_MIC: return AMIXER_MIC_C;
+ default: return NUM_CT_AMIXERS;
+ }
+}
+
+static unsigned char
+get_switch_state(struct ct_mixer *mixer, enum CTALSA_MIXER_CTL type)
+{
+ return (mixer->switch_state & (0x1 << (type - SWH_MIXER_START)))
+ ? 1 : 0;
+}
+
+static void
+set_switch_state(struct ct_mixer *mixer,
+ enum CTALSA_MIXER_CTL type, unsigned char state)
+{
+ if (state)
+ mixer->switch_state |= (0x1 << (type - SWH_MIXER_START));
+ else
+ mixer->switch_state &= ~(0x1 << (type - SWH_MIXER_START));
+}
+
+#if 0 /* not used */
+/* Map integer value ranging from 0 to 65535 to 14-bit float value ranging
+ * from 2^-6 to (1+1023/1024) */
+static unsigned int uint16_to_float14(unsigned int x)
+{
+ unsigned int i;
+
+ if (x < 17)
+ return 0;
+
+ x *= 2031;
+ x /= 65535;
+ x += 16;
+
+ /* i <= 6 */
+ for (i = 0; !(x & 0x400); i++)
+ x <<= 1;
+
+ x = (((7 - i) & 0x7) << 10) | (x & 0x3ff);
+
+ return x;
+}
+
+static unsigned int float14_to_uint16(unsigned int x)
+{
+ unsigned int e;
+
+ if (!x)
+ return x;
+
+ e = (x >> 10) & 0x7;
+ x &= 0x3ff;
+ x += 1024;
+ x >>= (7 - e);
+ x -= 16;
+ x *= 65535;
+ x /= 2031;
+
+ return x;
+}
+#endif /* not used */
+
+#define VOL_SCALE 0x1c
+#define VOL_MAX 0x100
+
+static const DECLARE_TLV_DB_SCALE(ct_vol_db_scale, -6400, 25, 1);
+
+static int ct_alsa_mix_volume_info(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 = VOL_MAX;
+
+ return 0;
+}
+
+static int ct_alsa_mix_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+ enum CT_AMIXER_CTL type = get_amixer_index(kcontrol->private_value);
+ struct amixer *amixer;
+ int i, val;
+
+ for (i = 0; i < 2; i++) {
+ amixer = ((struct ct_mixer *)atc->mixer)->
+ amixers[type*CHN_NUM+i];
+ val = amixer->ops->get_scale(amixer) / VOL_SCALE;
+ if (val < 0)
+ val = 0;
+ else if (val > VOL_MAX)
+ val = VOL_MAX;
+ ucontrol->value.integer.value[i] = val;
+ }
+
+ return 0;
+}
+
+static int ct_alsa_mix_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+ struct ct_mixer *mixer = atc->mixer;
+ enum CT_AMIXER_CTL type = get_amixer_index(kcontrol->private_value);
+ struct amixer *amixer;
+ int i, j, val, oval, change = 0;
+
+ for (i = 0; i < 2; i++) {
+ val = ucontrol->value.integer.value[i];
+ if (val < 0)
+ val = 0;
+ else if (val > VOL_MAX)
+ val = VOL_MAX;
+ val *= VOL_SCALE;
+ amixer = mixer->amixers[type*CHN_NUM+i];
+ oval = amixer->ops->get_scale(amixer);
+ if (val != oval) {
+ amixer->ops->set_scale(amixer, val);
+ amixer->ops->commit_write(amixer);
+ change = 1;
+ /* Synchronize Master/PCM playback AMIXERs. */
+ if (AMIXER_MASTER_F == type || AMIXER_PCM_F == type) {
+ for (j = 1; j < 4; j++) {
+ amixer = mixer->
+ amixers[(type+j)*CHN_NUM+i];
+ amixer->ops->set_scale(amixer, val);
+ amixer->ops->commit_write(amixer);
+ }
+ }
+ }
+ }
+
+ return change;
+}
+
+static struct snd_kcontrol_new vol_ctl = {
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = ct_alsa_mix_volume_info,
+ .get = ct_alsa_mix_volume_get,
+ .put = ct_alsa_mix_volume_put,
+ .tlv = { .p = ct_vol_db_scale },
+};
+
+static int output_switch_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *info)
+{
+ static const char *const names[3] = {
+ "FP Headphones", "Headphones", "Speakers"
+ };
+
+ return snd_ctl_enum_info(info, 1, 3, names);
+}
+
+static int output_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.enumerated.item[0] = atc->output_switch_get(atc);
+ return 0;
+}
+
+static int output_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+ if (ucontrol->value.enumerated.item[0] > 2)
+ return -EINVAL;
+ return atc->output_switch_put(atc, ucontrol->value.enumerated.item[0]);
+}
+
+static struct snd_kcontrol_new output_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Output Playback Enum",
+ .info = output_switch_info,
+ .get = output_switch_get,
+ .put = output_switch_put,
+};
+
+static int mic_source_switch_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *info)
+{
+ static const char *const names[3] = {
+ "Mic", "FP Mic", "Aux"
+ };
+
+ return snd_ctl_enum_info(info, 1, 3, names);
+}
+
+static int mic_source_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.enumerated.item[0] = atc->mic_source_switch_get(atc);
+ return 0;
+}
+
+static int mic_source_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+ if (ucontrol->value.enumerated.item[0] > 2)
+ return -EINVAL;
+ return atc->mic_source_switch_put(atc,
+ ucontrol->value.enumerated.item[0]);
+}
+
+static struct snd_kcontrol_new mic_source_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic Source Capture Enum",
+ .info = mic_source_switch_info,
+ .get = mic_source_switch_get,
+ .put = mic_source_switch_put,
+};
+
+static void
+do_line_mic_switch(struct ct_atc *atc, enum CTALSA_MIXER_CTL type)
+{
+
+ if (MIXER_LINEIN_C_S == type) {
+ atc->select_line_in(atc);
+ set_switch_state(atc->mixer, MIXER_MIC_C_S, 0);
+ snd_ctl_notify(atc->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &kctls[1]->id);
+ } else if (MIXER_MIC_C_S == type) {
+ atc->select_mic_in(atc);
+ set_switch_state(atc->mixer, MIXER_LINEIN_C_S, 0);
+ snd_ctl_notify(atc->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &kctls[0]->id);
+ }
+}
+
+static void
+do_digit_io_switch(struct ct_atc *atc, int state)
+{
+ struct ct_mixer *mixer = atc->mixer;
+
+ if (state) {
+ atc->select_digit_io(atc);
+ atc->spdif_out_unmute(atc,
+ get_switch_state(mixer, MIXER_SPDIFO_P_S));
+ atc->spdif_in_unmute(atc, 1);
+ atc->line_in_unmute(atc, 0);
+ return;
+ }
+
+ if (get_switch_state(mixer, MIXER_LINEIN_C_S))
+ atc->select_line_in(atc);
+ else if (get_switch_state(mixer, MIXER_MIC_C_S))
+ atc->select_mic_in(atc);
+
+ atc->spdif_out_unmute(atc, 0);
+ atc->spdif_in_unmute(atc, 0);
+ atc->line_in_unmute(atc, 1);
+ return;
+}
+
+static void do_switch(struct ct_atc *atc, enum CTALSA_MIXER_CTL type, int state)
+{
+ struct ct_mixer *mixer = atc->mixer;
+ struct capabilities cap = atc->capabilities(atc);
+
+ /* Do changes in mixer. */
+ if ((SWH_CAPTURE_START <= type) && (SWH_CAPTURE_END >= type)) {
+ if (state) {
+ ct_mixer_recording_select(mixer,
+ get_amixer_index(type));
+ } else {
+ ct_mixer_recording_unselect(mixer,
+ get_amixer_index(type));
+ }
+ }
+ /* Do changes out of mixer. */
+ if (!cap.dedicated_mic &&
+ (MIXER_LINEIN_C_S == type || MIXER_MIC_C_S == type)) {
+ if (state)
+ do_line_mic_switch(atc, type);
+ atc->line_in_unmute(atc, state);
+ } else if (cap.dedicated_mic && (MIXER_LINEIN_C_S == type))
+ atc->line_in_unmute(atc, state);
+ else if (cap.dedicated_mic && (MIXER_MIC_C_S == type))
+ atc->mic_unmute(atc, state);
+ else if (MIXER_SPDIFI_C_S == type)
+ atc->spdif_in_unmute(atc, state);
+ else if (MIXER_WAVEF_P_S == type)
+ atc->line_front_unmute(atc, state);
+ else if (MIXER_WAVES_P_S == type)
+ atc->line_surround_unmute(atc, state);
+ else if (MIXER_WAVEC_P_S == type)
+ atc->line_clfe_unmute(atc, state);
+ else if (MIXER_WAVER_P_S == type)
+ atc->line_rear_unmute(atc, state);
+ else if (MIXER_SPDIFO_P_S == type)
+ atc->spdif_out_unmute(atc, state);
+ else if (MIXER_DIGITAL_IO_S == type)
+ do_digit_io_switch(atc, state);
+
+ return;
+}
+
+static int ct_alsa_mix_switch_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;
+ uinfo->value.integer.step = 1;
+
+ return 0;
+}
+
+static int ct_alsa_mix_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct ct_mixer *mixer =
+ ((struct ct_atc *)snd_kcontrol_chip(kcontrol))->mixer;
+ enum CTALSA_MIXER_CTL type = kcontrol->private_value;
+
+ ucontrol->value.integer.value[0] = get_switch_state(mixer, type);
+ return 0;
+}
+
+static int ct_alsa_mix_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+ struct ct_mixer *mixer = atc->mixer;
+ enum CTALSA_MIXER_CTL type = kcontrol->private_value;
+ int state;
+
+ state = ucontrol->value.integer.value[0];
+ if (get_switch_state(mixer, type) == state)
+ return 0;
+
+ set_switch_state(mixer, type, state);
+ do_switch(atc, type, state);
+
+ return 1;
+}
+
+static struct snd_kcontrol_new swh_ctl = {
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = ct_alsa_mix_switch_info,
+ .get = ct_alsa_mix_switch_get,
+ .put = ct_alsa_mix_switch_put
+};
+
+static int ct_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 ct_spdif_get_mask(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.iec958.status[0] = 0xff;
+ ucontrol->value.iec958.status[1] = 0xff;
+ ucontrol->value.iec958.status[2] = 0xff;
+ ucontrol->value.iec958.status[3] = 0xff;
+ return 0;
+}
+
+static int ct_spdif_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+ unsigned int status;
+
+ atc->spdif_out_get_status(atc, &status);
+
+ if (status == 0)
+ status = SNDRV_PCM_DEFAULT_CON_SPDIF;
+
+ ucontrol->value.iec958.status[0] = (status >> 0) & 0xff;
+ ucontrol->value.iec958.status[1] = (status >> 8) & 0xff;
+ ucontrol->value.iec958.status[2] = (status >> 16) & 0xff;
+ ucontrol->value.iec958.status[3] = (status >> 24) & 0xff;
+
+ return 0;
+}
+
+static int ct_spdif_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+ int change;
+ unsigned int status, old_status;
+
+ status = (ucontrol->value.iec958.status[0] << 0) |
+ (ucontrol->value.iec958.status[1] << 8) |
+ (ucontrol->value.iec958.status[2] << 16) |
+ (ucontrol->value.iec958.status[3] << 24);
+
+ atc->spdif_out_get_status(atc, &old_status);
+ change = (old_status != status);
+ if (change)
+ atc->spdif_out_set_status(atc, status);
+
+ return change;
+}
+
+static struct snd_kcontrol_new iec958_mask_ctl = {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
+ .count = 1,
+ .info = ct_spdif_info,
+ .get = ct_spdif_get_mask,
+ .private_value = MIXER_IEC958_MASK
+};
+
+static struct snd_kcontrol_new iec958_default_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .count = 1,
+ .info = ct_spdif_info,
+ .get = ct_spdif_get,
+ .put = ct_spdif_put,
+ .private_value = MIXER_IEC958_DEFAULT
+};
+
+static struct snd_kcontrol_new iec958_ctl = {
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
+ .count = 1,
+ .info = ct_spdif_info,
+ .get = ct_spdif_get,
+ .put = ct_spdif_put,
+ .private_value = MIXER_IEC958_STREAM
+};
+
+#define NUM_IEC958_CTL 3
+
+static int
+ct_mixer_kcontrol_new(struct ct_mixer *mixer, struct snd_kcontrol_new *new)
+{
+ struct snd_kcontrol *kctl;
+ int err;
+
+ kctl = snd_ctl_new1(new, mixer->atc);
+ if (!kctl)
+ return -ENOMEM;
+
+ if (SNDRV_CTL_ELEM_IFACE_PCM == kctl->id.iface)
+ kctl->id.device = IEC958;
+
+ err = snd_ctl_add(mixer->atc->card, kctl);
+ if (err)
+ return err;
+
+ switch (new->private_value) {
+ case MIXER_LINEIN_C_S:
+ kctls[0] = kctl; break;
+ case MIXER_MIC_C_S:
+ kctls[1] = kctl; break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int ct_mixer_kcontrols_create(struct ct_mixer *mixer)
+{
+ enum CTALSA_MIXER_CTL type;
+ struct ct_atc *atc = mixer->atc;
+ struct capabilities cap = atc->capabilities(atc);
+ int err;
+
+ /* Create snd kcontrol instances on demand */
+ for (type = VOL_MIXER_START; type <= VOL_MIXER_END; type++) {
+ if (ct_kcontrol_init_table[type].ctl) {
+ vol_ctl.name = ct_kcontrol_init_table[type].name;
+ vol_ctl.private_value = (unsigned long)type;
+ err = ct_mixer_kcontrol_new(mixer, &vol_ctl);
+ if (err)
+ return err;
+ }
+ }
+
+ ct_kcontrol_init_table[MIXER_DIGITAL_IO_S].ctl = cap.digit_io_switch;
+
+ for (type = SWH_MIXER_START; type <= SWH_MIXER_END; type++) {
+ if (ct_kcontrol_init_table[type].ctl) {
+ swh_ctl.name = ct_kcontrol_init_table[type].name;
+ swh_ctl.private_value = (unsigned long)type;
+ err = ct_mixer_kcontrol_new(mixer, &swh_ctl);
+ if (err)
+ return err;
+ }
+ }
+
+ err = ct_mixer_kcontrol_new(mixer, &iec958_mask_ctl);
+ if (err)
+ return err;
+
+ err = ct_mixer_kcontrol_new(mixer, &iec958_default_ctl);
+ if (err)
+ return err;
+
+ err = ct_mixer_kcontrol_new(mixer, &iec958_ctl);
+ if (err)
+ return err;
+
+ if (cap.output_switch) {
+ err = ct_mixer_kcontrol_new(mixer, &output_ctl);
+ if (err)
+ return err;
+ }
+
+ if (cap.mic_source_switch) {
+ err = ct_mixer_kcontrol_new(mixer, &mic_source_ctl);
+ if (err)
+ return err;
+ }
+ atc->line_front_unmute(atc, 1);
+ set_switch_state(mixer, MIXER_WAVEF_P_S, 1);
+ atc->line_surround_unmute(atc, 0);
+ set_switch_state(mixer, MIXER_WAVES_P_S, 0);
+ atc->line_clfe_unmute(atc, 0);
+ set_switch_state(mixer, MIXER_WAVEC_P_S, 0);
+ atc->line_rear_unmute(atc, 0);
+ set_switch_state(mixer, MIXER_WAVER_P_S, 0);
+ atc->spdif_out_unmute(atc, 0);
+ set_switch_state(mixer, MIXER_SPDIFO_P_S, 0);
+ atc->line_in_unmute(atc, 0);
+ if (cap.dedicated_mic)
+ atc->mic_unmute(atc, 0);
+ atc->spdif_in_unmute(atc, 0);
+ set_switch_state(mixer, MIXER_PCM_C_S, 0);
+ set_switch_state(mixer, MIXER_LINEIN_C_S, 0);
+ set_switch_state(mixer, MIXER_SPDIFI_C_S, 0);
+
+ return 0;
+}
+
+static void
+ct_mixer_recording_select(struct ct_mixer *mixer, enum CT_AMIXER_CTL type)
+{
+ struct amixer *amix_d;
+ struct sum *sum_c;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ amix_d = mixer->amixers[type*CHN_NUM+i];
+ sum_c = mixer->sums[SUM_IN_F_C*CHN_NUM+i];
+ amix_d->ops->set_sum(amix_d, sum_c);
+ amix_d->ops->commit_write(amix_d);
+ }
+}
+
+static void
+ct_mixer_recording_unselect(struct ct_mixer *mixer, enum CT_AMIXER_CTL type)
+{
+ struct amixer *amix_d;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ amix_d = mixer->amixers[type*CHN_NUM+i];
+ amix_d->ops->set_sum(amix_d, NULL);
+ amix_d->ops->commit_write(amix_d);
+ }
+}
+
+static int ct_mixer_get_resources(struct ct_mixer *mixer)
+{
+ struct sum_mgr *sum_mgr;
+ struct sum *sum;
+ struct sum_desc sum_desc = {0};
+ struct amixer_mgr *amixer_mgr;
+ struct amixer *amixer;
+ struct amixer_desc am_desc = {0};
+ int err;
+ int i;
+
+ /* Allocate sum resources for mixer obj */
+ sum_mgr = (struct sum_mgr *)mixer->atc->rsc_mgrs[SUM];
+ sum_desc.msr = mixer->atc->msr;
+ for (i = 0; i < (NUM_CT_SUMS * CHN_NUM); i++) {
+ err = sum_mgr->get_sum(sum_mgr, &sum_desc, &sum);
+ if (err) {
+ dev_err(mixer->atc->card->dev,
+ "Failed to get sum resources for front output!\n");
+ break;
+ }
+ mixer->sums[i] = sum;
+ }
+ if (err)
+ goto error1;
+
+ /* Allocate amixer resources for mixer obj */
+ amixer_mgr = (struct amixer_mgr *)mixer->atc->rsc_mgrs[AMIXER];
+ am_desc.msr = mixer->atc->msr;
+ for (i = 0; i < (NUM_CT_AMIXERS * CHN_NUM); i++) {
+ err = amixer_mgr->get_amixer(amixer_mgr, &am_desc, &amixer);
+ if (err) {
+ dev_err(mixer->atc->card->dev,
+ "Failed to get amixer resources for mixer obj!\n");
+ break;
+ }
+ mixer->amixers[i] = amixer;
+ }
+ if (err)
+ goto error2;
+
+ return 0;
+
+error2:
+ for (i = 0; i < (NUM_CT_AMIXERS * CHN_NUM); i++) {
+ if (NULL != mixer->amixers[i]) {
+ amixer = mixer->amixers[i];
+ amixer_mgr->put_amixer(amixer_mgr, amixer);
+ mixer->amixers[i] = NULL;
+ }
+ }
+error1:
+ for (i = 0; i < (NUM_CT_SUMS * CHN_NUM); i++) {
+ if (NULL != mixer->sums[i]) {
+ sum_mgr->put_sum(sum_mgr, (struct sum *)mixer->sums[i]);
+ mixer->sums[i] = NULL;
+ }
+ }
+
+ return err;
+}
+
+static int ct_mixer_get_mem(struct ct_mixer **rmixer)
+{
+ struct ct_mixer *mixer;
+ int err;
+
+ *rmixer = NULL;
+ /* Allocate mem for mixer obj */
+ mixer = kzalloc(sizeof(*mixer), GFP_KERNEL);
+ if (!mixer)
+ return -ENOMEM;
+
+ mixer->amixers = kcalloc(NUM_CT_AMIXERS * CHN_NUM, sizeof(void *),
+ GFP_KERNEL);
+ if (!mixer->amixers) {
+ err = -ENOMEM;
+ goto error1;
+ }
+ mixer->sums = kcalloc(NUM_CT_SUMS * CHN_NUM, sizeof(void *),
+ GFP_KERNEL);
+ if (!mixer->sums) {
+ err = -ENOMEM;
+ goto error2;
+ }
+
+ *rmixer = mixer;
+ return 0;
+
+error2:
+ kfree(mixer->amixers);
+error1:
+ kfree(mixer);
+ return err;
+}
+
+static int ct_mixer_topology_build(struct ct_mixer *mixer)
+{
+ struct sum *sum;
+ struct amixer *amix_d, *amix_s;
+ enum CT_AMIXER_CTL i, j;
+ enum CT_SUM_CTL k;
+
+ /* Build topology from destination to source */
+
+ /* Set up Master mixer */
+ for (i = AMIXER_MASTER_F, k = SUM_IN_F;
+ i <= AMIXER_MASTER_S; i++, k++) {
+ amix_d = mixer->amixers[i*CHN_NUM];
+ sum = mixer->sums[k*CHN_NUM];
+ amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL);
+ amix_d = mixer->amixers[i*CHN_NUM+1];
+ sum = mixer->sums[k*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL);
+ }
+
+ /* Set up Wave-out mixer */
+ for (i = AMIXER_WAVE_F, j = AMIXER_MASTER_F;
+ i <= AMIXER_WAVE_S; i++, j++) {
+ amix_d = mixer->amixers[i*CHN_NUM];
+ amix_s = mixer->amixers[j*CHN_NUM];
+ amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL);
+ amix_d = mixer->amixers[i*CHN_NUM+1];
+ amix_s = mixer->amixers[j*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL);
+ }
+
+ /* Set up S/PDIF-out mixer */
+ amix_d = mixer->amixers[AMIXER_SPDIFO*CHN_NUM];
+ amix_s = mixer->amixers[AMIXER_MASTER_F*CHN_NUM];
+ amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL);
+ amix_d = mixer->amixers[AMIXER_SPDIFO*CHN_NUM+1];
+ amix_s = mixer->amixers[AMIXER_MASTER_F*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL);
+
+ /* Set up PCM-in mixer */
+ for (i = AMIXER_PCM_F, k = SUM_IN_F; i <= AMIXER_PCM_S; i++, k++) {
+ amix_d = mixer->amixers[i*CHN_NUM];
+ sum = mixer->sums[k*CHN_NUM];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+ amix_d = mixer->amixers[i*CHN_NUM+1];
+ sum = mixer->sums[k*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+ }
+
+ /* Set up Line-in mixer */
+ amix_d = mixer->amixers[AMIXER_LINEIN*CHN_NUM];
+ sum = mixer->sums[SUM_IN_F*CHN_NUM];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+ amix_d = mixer->amixers[AMIXER_LINEIN*CHN_NUM+1];
+ sum = mixer->sums[SUM_IN_F*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+ /* Set up Mic-in mixer */
+ amix_d = mixer->amixers[AMIXER_MIC*CHN_NUM];
+ sum = mixer->sums[SUM_IN_F*CHN_NUM];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+ amix_d = mixer->amixers[AMIXER_MIC*CHN_NUM+1];
+ sum = mixer->sums[SUM_IN_F*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+ /* Set up S/PDIF-in mixer */
+ amix_d = mixer->amixers[AMIXER_SPDIFI*CHN_NUM];
+ sum = mixer->sums[SUM_IN_F*CHN_NUM];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+ amix_d = mixer->amixers[AMIXER_SPDIFI*CHN_NUM+1];
+ sum = mixer->sums[SUM_IN_F*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+ /* Set up Master recording mixer */
+ amix_d = mixer->amixers[AMIXER_MASTER_F_C*CHN_NUM];
+ sum = mixer->sums[SUM_IN_F_C*CHN_NUM];
+ amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL);
+ amix_d = mixer->amixers[AMIXER_MASTER_F_C*CHN_NUM+1];
+ sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL);
+
+ /* Set up PCM-in recording mixer */
+ amix_d = mixer->amixers[AMIXER_PCM_F_C*CHN_NUM];
+ sum = mixer->sums[SUM_IN_F_C*CHN_NUM];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+ amix_d = mixer->amixers[AMIXER_PCM_F_C*CHN_NUM+1];
+ sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+ /* Set up Line-in recording mixer */
+ amix_d = mixer->amixers[AMIXER_LINEIN_C*CHN_NUM];
+ sum = mixer->sums[SUM_IN_F_C*CHN_NUM];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+ amix_d = mixer->amixers[AMIXER_LINEIN_C*CHN_NUM+1];
+ sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+ /* Set up Mic-in recording mixer */
+ amix_d = mixer->amixers[AMIXER_MIC_C*CHN_NUM];
+ sum = mixer->sums[SUM_IN_F_C*CHN_NUM];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+ amix_d = mixer->amixers[AMIXER_MIC_C*CHN_NUM+1];
+ sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+ /* Set up S/PDIF-in recording mixer */
+ amix_d = mixer->amixers[AMIXER_SPDIFI_C*CHN_NUM];
+ sum = mixer->sums[SUM_IN_F_C*CHN_NUM];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+ amix_d = mixer->amixers[AMIXER_SPDIFI_C*CHN_NUM+1];
+ sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1];
+ amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+ return 0;
+}
+
+static int mixer_set_input_port(struct amixer *amixer, struct rsc *rsc)
+{
+ amixer->ops->set_input(amixer, rsc);
+ amixer->ops->commit_write(amixer);
+
+ return 0;
+}
+
+static enum CT_AMIXER_CTL port_to_amixer(enum MIXER_PORT_T type)
+{
+ switch (type) {
+ case MIX_WAVE_FRONT: return AMIXER_WAVE_F;
+ case MIX_WAVE_SURROUND: return AMIXER_WAVE_S;
+ case MIX_WAVE_CENTLFE: return AMIXER_WAVE_C;
+ case MIX_WAVE_REAR: return AMIXER_WAVE_R;
+ case MIX_PCMO_FRONT: return AMIXER_MASTER_F_C;
+ case MIX_SPDIF_OUT: return AMIXER_SPDIFO;
+ case MIX_LINE_IN: return AMIXER_LINEIN;
+ case MIX_MIC_IN: return AMIXER_MIC;
+ case MIX_SPDIF_IN: return AMIXER_SPDIFI;
+ case MIX_PCMI_FRONT: return AMIXER_PCM_F;
+ case MIX_PCMI_SURROUND: return AMIXER_PCM_S;
+ case MIX_PCMI_CENTLFE: return AMIXER_PCM_C;
+ case MIX_PCMI_REAR: return AMIXER_PCM_R;
+ default: return 0;
+ }
+}
+
+static int mixer_get_output_ports(struct ct_mixer *mixer,
+ enum MIXER_PORT_T type,
+ struct rsc **rleft, struct rsc **rright)
+{
+ enum CT_AMIXER_CTL amix = port_to_amixer(type);
+
+ if (NULL != rleft)
+ *rleft = &((struct amixer *)mixer->amixers[amix*CHN_NUM])->rsc;
+
+ if (NULL != rright)
+ *rright =
+ &((struct amixer *)mixer->amixers[amix*CHN_NUM+1])->rsc;
+
+ return 0;
+}
+
+static int mixer_set_input_left(struct ct_mixer *mixer,
+ enum MIXER_PORT_T type, struct rsc *rsc)
+{
+ enum CT_AMIXER_CTL amix = port_to_amixer(type);
+
+ mixer_set_input_port(mixer->amixers[amix*CHN_NUM], rsc);
+ amix = get_recording_amixer(amix);
+ if (amix < NUM_CT_AMIXERS)
+ mixer_set_input_port(mixer->amixers[amix*CHN_NUM], rsc);
+
+ return 0;
+}
+
+static int
+mixer_set_input_right(struct ct_mixer *mixer,
+ enum MIXER_PORT_T type, struct rsc *rsc)
+{
+ enum CT_AMIXER_CTL amix = port_to_amixer(type);
+
+ mixer_set_input_port(mixer->amixers[amix*CHN_NUM+1], rsc);
+ amix = get_recording_amixer(amix);
+ if (amix < NUM_CT_AMIXERS)
+ mixer_set_input_port(mixer->amixers[amix*CHN_NUM+1], rsc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mixer_resume(struct ct_mixer *mixer)
+{
+ int i, state;
+ struct amixer *amixer;
+
+ /* resume topology and volume gain. */
+ for (i = 0; i < NUM_CT_AMIXERS*CHN_NUM; i++) {
+ amixer = mixer->amixers[i];
+ amixer->ops->commit_write(amixer);
+ }
+
+ /* resume switch state. */
+ for (i = SWH_MIXER_START; i <= SWH_MIXER_END; i++) {
+ state = get_switch_state(mixer, i);
+ do_switch(mixer->atc, i, state);
+ }
+
+ return 0;
+}
+#endif
+
+int ct_mixer_destroy(struct ct_mixer *mixer)
+{
+ struct sum_mgr *sum_mgr = (struct sum_mgr *)mixer->atc->rsc_mgrs[SUM];
+ struct amixer_mgr *amixer_mgr =
+ (struct amixer_mgr *)mixer->atc->rsc_mgrs[AMIXER];
+ struct amixer *amixer;
+ int i = 0;
+
+ /* Release amixer resources */
+ for (i = 0; i < (NUM_CT_AMIXERS * CHN_NUM); i++) {
+ if (NULL != mixer->amixers[i]) {
+ amixer = mixer->amixers[i];
+ amixer_mgr->put_amixer(amixer_mgr, amixer);
+ }
+ }
+
+ /* Release sum resources */
+ for (i = 0; i < (NUM_CT_SUMS * CHN_NUM); i++) {
+ if (NULL != mixer->sums[i])
+ sum_mgr->put_sum(sum_mgr, (struct sum *)mixer->sums[i]);
+ }
+
+ /* Release mem assigned to mixer object */
+ kfree(mixer->sums);
+ kfree(mixer->amixers);
+ kfree(mixer);
+
+ return 0;
+}
+
+int ct_mixer_create(struct ct_atc *atc, struct ct_mixer **rmixer)
+{
+ struct ct_mixer *mixer;
+ int err;
+
+ *rmixer = NULL;
+
+ /* Allocate mem for mixer obj */
+ err = ct_mixer_get_mem(&mixer);
+ if (err)
+ return err;
+
+ mixer->switch_state = 0;
+ mixer->atc = atc;
+ /* Set operations */
+ mixer->get_output_ports = mixer_get_output_ports;
+ mixer->set_input_left = mixer_set_input_left;
+ mixer->set_input_right = mixer_set_input_right;
+#ifdef CONFIG_PM_SLEEP
+ mixer->resume = mixer_resume;
+#endif
+
+ /* Allocate chip resources for mixer obj */
+ err = ct_mixer_get_resources(mixer);
+ if (err)
+ goto error;
+
+ /* Build internal mixer topology */
+ ct_mixer_topology_build(mixer);
+
+ *rmixer = mixer;
+
+ return 0;
+
+error:
+ ct_mixer_destroy(mixer);
+ return err;
+}
+
+int ct_alsa_mix_create(struct ct_atc *atc,
+ enum CTALSADEVS device,
+ const char *device_name)
+{
+ int err;
+
+ /* Create snd kcontrol instances on demand */
+ /* vol_ctl.device = swh_ctl.device = device; */ /* better w/ device 0 */
+ err = ct_mixer_kcontrols_create((struct ct_mixer *)atc->mixer);
+ if (err)
+ return err;
+
+ strcpy(atc->card->mixername, device_name);
+
+ return 0;
+}
diff --git a/sound/pci/ctxfi/ctmixer.h b/sound/pci/ctxfi/ctmixer.h
new file mode 100644
index 000000000..e812f6c93
--- /dev/null
+++ b/sound/pci/ctxfi/ctmixer.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctmixer.h
+ *
+ * @Brief
+ * This file contains the definition of the mixer device functions.
+ *
+ * @Author Liu Chun
+ * @Date Mar 28 2008
+ */
+
+#ifndef CTMIXER_H
+#define CTMIXER_H
+
+#include "ctatc.h"
+#include "ctresource.h"
+
+#define INIT_VOL 0x1c00
+
+enum MIXER_PORT_T {
+ MIX_WAVE_FRONT,
+ MIX_WAVE_REAR,
+ MIX_WAVE_CENTLFE,
+ MIX_WAVE_SURROUND,
+ MIX_SPDIF_OUT,
+ MIX_PCMO_FRONT,
+ MIX_MIC_IN,
+ MIX_LINE_IN,
+ MIX_SPDIF_IN,
+ MIX_PCMI_FRONT,
+ MIX_PCMI_REAR,
+ MIX_PCMI_CENTLFE,
+ MIX_PCMI_SURROUND,
+
+ NUM_MIX_PORTS
+};
+
+/* alsa mixer descriptor */
+struct ct_mixer {
+ struct ct_atc *atc;
+
+ void **amixers; /* amixer resources for volume control */
+ void **sums; /* sum resources for signal collection */
+ unsigned int switch_state; /* A bit-map to indicate state of switches */
+
+ int (*get_output_ports)(struct ct_mixer *mixer, enum MIXER_PORT_T type,
+ struct rsc **rleft, struct rsc **rright);
+
+ int (*set_input_left)(struct ct_mixer *mixer,
+ enum MIXER_PORT_T type, struct rsc *rsc);
+ int (*set_input_right)(struct ct_mixer *mixer,
+ enum MIXER_PORT_T type, struct rsc *rsc);
+#ifdef CONFIG_PM_SLEEP
+ int (*resume)(struct ct_mixer *mixer);
+#endif
+};
+
+int ct_alsa_mix_create(struct ct_atc *atc,
+ enum CTALSADEVS device,
+ const char *device_name);
+int ct_mixer_create(struct ct_atc *atc, struct ct_mixer **rmixer);
+int ct_mixer_destroy(struct ct_mixer *mixer);
+
+#endif /* CTMIXER_H */
diff --git a/sound/pci/ctxfi/ctpcm.c b/sound/pci/ctxfi/ctpcm.c
new file mode 100644
index 000000000..81dfc6a76
--- /dev/null
+++ b/sound/pci/ctxfi/ctpcm.c
@@ -0,0 +1,476 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctpcm.c
+ *
+ * @Brief
+ * This file contains the definition of the pcm device functions.
+ *
+ * @Author Liu Chun
+ * @Date Apr 2 2008
+ */
+
+#include "ctpcm.h"
+#include "cttimer.h"
+#include <linux/slab.h>
+#include <sound/pcm.h>
+
+/* Hardware descriptions for playback */
+static const struct snd_pcm_hardware ct_pcm_playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = (SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_3LE |
+ SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_FLOAT_LE),
+ .rates = (SNDRV_PCM_RATE_CONTINUOUS |
+ SNDRV_PCM_RATE_8000_192000),
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = (64),
+ .period_bytes_max = (128*1024),
+ .periods_min = 2,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware ct_spdif_passthru_playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = (SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_32000),
+ .rate_min = 32000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = (64),
+ .period_bytes_max = (128*1024),
+ .periods_min = 2,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+/* Hardware descriptions for capture */
+static const struct snd_pcm_hardware ct_pcm_capture_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = (SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_3LE |
+ SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_FLOAT_LE),
+ .rates = (SNDRV_PCM_RATE_CONTINUOUS |
+ SNDRV_PCM_RATE_8000_96000),
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = (384),
+ .period_bytes_max = (64*1024),
+ .periods_min = 2,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static void ct_atc_pcm_interrupt(struct ct_atc_pcm *atc_pcm)
+{
+ struct ct_atc_pcm *apcm = atc_pcm;
+
+ if (!apcm->substream)
+ return;
+
+ snd_pcm_period_elapsed(apcm->substream);
+}
+
+static void ct_atc_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+ struct ct_atc_pcm *apcm = runtime->private_data;
+ struct ct_atc *atc = snd_pcm_substream_chip(apcm->substream);
+
+ atc->pcm_release_resources(atc, apcm);
+ ct_timer_instance_free(apcm->timer);
+ kfree(apcm);
+ runtime->private_data = NULL;
+}
+
+/* pcm playback operations */
+static int ct_pcm_playback_open(struct snd_pcm_substream *substream)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm;
+ int err;
+
+ apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
+ if (!apcm)
+ return -ENOMEM;
+
+ apcm->substream = substream;
+ apcm->interrupt = ct_atc_pcm_interrupt;
+ if (IEC958 == substream->pcm->device) {
+ runtime->hw = ct_spdif_passthru_playback_hw;
+ atc->spdif_out_passthru(atc, 1);
+ } else {
+ runtime->hw = ct_pcm_playback_hw;
+ if (FRONT == substream->pcm->device)
+ runtime->hw.channels_max = 8;
+ }
+
+ err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (err < 0)
+ goto free_pcm;
+
+ err = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ 1024, UINT_MAX);
+ if (err < 0)
+ goto free_pcm;
+
+ apcm->timer = ct_timer_instance_new(atc->timer, apcm);
+ if (!apcm->timer) {
+ err = -ENOMEM;
+ goto free_pcm;
+ }
+ runtime->private_data = apcm;
+ runtime->private_free = ct_atc_pcm_free_substream;
+
+ return 0;
+
+free_pcm:
+ kfree(apcm);
+ return err;
+}
+
+static int ct_pcm_playback_close(struct snd_pcm_substream *substream)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+
+ /* TODO: Notify mixer inactive. */
+ if (IEC958 == substream->pcm->device)
+ atc->spdif_out_passthru(atc, 0);
+
+ /* The ct_atc_pcm object will be freed by runtime->private_free */
+
+ return 0;
+}
+
+static int ct_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct ct_atc_pcm *apcm = substream->runtime->private_data;
+
+ /* clear previous resources */
+ atc->pcm_release_resources(atc, apcm);
+ return 0;
+}
+
+static int ct_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct ct_atc_pcm *apcm = substream->runtime->private_data;
+
+ /* clear previous resources */
+ atc->pcm_release_resources(atc, apcm);
+ return 0;
+}
+
+
+static int ct_pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+ int err;
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ if (IEC958 == substream->pcm->device)
+ err = atc->spdif_passthru_playback_prepare(atc, apcm);
+ else
+ err = atc->pcm_playback_prepare(atc, apcm);
+
+ if (err < 0) {
+ dev_err(atc->card->dev,
+ "Preparing pcm playback failed!!!\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int
+ct_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ atc->pcm_playback_start(atc, apcm);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ atc->pcm_playback_stop(atc, apcm);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+ct_pcm_playback_pointer(struct snd_pcm_substream *substream)
+{
+ unsigned long position;
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ /* Read out playback position */
+ position = atc->pcm_playback_position(atc, apcm);
+ position = bytes_to_frames(runtime, position);
+ if (position >= runtime->buffer_size)
+ position = 0;
+ return position;
+}
+
+/* pcm capture operations */
+static int ct_pcm_capture_open(struct snd_pcm_substream *substream)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm;
+ int err;
+
+ apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
+ if (!apcm)
+ return -ENOMEM;
+
+ apcm->started = 0;
+ apcm->substream = substream;
+ apcm->interrupt = ct_atc_pcm_interrupt;
+ runtime->hw = ct_pcm_capture_hw;
+ runtime->hw.rate_max = atc->rsr * atc->msr;
+
+ err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (err < 0)
+ goto free_pcm;
+
+ err = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ 1024, UINT_MAX);
+ if (err < 0)
+ goto free_pcm;
+
+ apcm->timer = ct_timer_instance_new(atc->timer, apcm);
+ if (!apcm->timer) {
+ err = -ENOMEM;
+ goto free_pcm;
+ }
+ runtime->private_data = apcm;
+ runtime->private_free = ct_atc_pcm_free_substream;
+
+ return 0;
+
+free_pcm:
+ kfree(apcm);
+ return err;
+}
+
+static int ct_pcm_capture_close(struct snd_pcm_substream *substream)
+{
+ /* The ct_atc_pcm object will be freed by runtime->private_free */
+ /* TODO: Notify mixer inactive. */
+ return 0;
+}
+
+static int ct_pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+ int err;
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ err = atc->pcm_capture_prepare(atc, apcm);
+ if (err < 0) {
+ dev_err(atc->card->dev,
+ "Preparing pcm capture failed!!!\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int
+ct_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ atc->pcm_capture_start(atc, apcm);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ atc->pcm_capture_stop(atc, apcm);
+ break;
+ default:
+ atc->pcm_capture_stop(atc, apcm);
+ break;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+ct_pcm_capture_pointer(struct snd_pcm_substream *substream)
+{
+ unsigned long position;
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ /* Read out playback position */
+ position = atc->pcm_capture_position(atc, apcm);
+ position = bytes_to_frames(runtime, position);
+ if (position >= runtime->buffer_size)
+ position = 0;
+ return position;
+}
+
+/* PCM operators for playback */
+static const struct snd_pcm_ops ct_pcm_playback_ops = {
+ .open = ct_pcm_playback_open,
+ .close = ct_pcm_playback_close,
+ .hw_params = ct_pcm_hw_params,
+ .hw_free = ct_pcm_hw_free,
+ .prepare = ct_pcm_playback_prepare,
+ .trigger = ct_pcm_playback_trigger,
+ .pointer = ct_pcm_playback_pointer,
+};
+
+/* PCM operators for capture */
+static const struct snd_pcm_ops ct_pcm_capture_ops = {
+ .open = ct_pcm_capture_open,
+ .close = ct_pcm_capture_close,
+ .hw_params = ct_pcm_hw_params,
+ .hw_free = ct_pcm_hw_free,
+ .prepare = ct_pcm_capture_prepare,
+ .trigger = ct_pcm_capture_trigger,
+ .pointer = ct_pcm_capture_pointer,
+};
+
+static const struct snd_pcm_chmap_elem surround_map[] = {
+ { .channels = 1,
+ .map = { SNDRV_CHMAP_MONO } },
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+ { }
+};
+
+static const struct snd_pcm_chmap_elem clfe_map[] = {
+ { .channels = 1,
+ .map = { SNDRV_CHMAP_MONO } },
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
+ { }
+};
+
+static const struct snd_pcm_chmap_elem side_map[] = {
+ { .channels = 1,
+ .map = { SNDRV_CHMAP_MONO } },
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
+ { }
+};
+
+/* Create ALSA pcm device */
+int ct_alsa_pcm_create(struct ct_atc *atc,
+ enum CTALSADEVS device,
+ const char *device_name)
+{
+ struct snd_pcm *pcm;
+ const struct snd_pcm_chmap_elem *map;
+ int chs;
+ int err;
+ int playback_count, capture_count;
+
+ playback_count = (IEC958 == device) ? 1 : 256;
+ capture_count = (FRONT == device) ? 1 : 0;
+ err = snd_pcm_new(atc->card, "ctxfi", device,
+ playback_count, capture_count, &pcm);
+ if (err < 0) {
+ dev_err(atc->card->dev, "snd_pcm_new failed!! Err=%d\n",
+ err);
+ return err;
+ }
+
+ pcm->private_data = atc;
+ pcm->info_flags = 0;
+ pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+ strscpy(pcm->name, device_name, sizeof(pcm->name));
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ct_pcm_playback_ops);
+
+ if (FRONT == device)
+ snd_pcm_set_ops(pcm,
+ SNDRV_PCM_STREAM_CAPTURE, &ct_pcm_capture_ops);
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+ &atc->pci->dev, 128*1024, 128*1024);
+
+ chs = 2;
+ switch (device) {
+ case FRONT:
+ chs = 8;
+ map = snd_pcm_std_chmaps;
+ break;
+ case SURROUND:
+ map = surround_map;
+ break;
+ case CLFE:
+ map = clfe_map;
+ break;
+ case SIDE:
+ map = side_map;
+ break;
+ default:
+ map = snd_pcm_std_chmaps;
+ break;
+ }
+ err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, map, chs,
+ 0, NULL);
+ if (err < 0)
+ return err;
+
+#ifdef CONFIG_PM_SLEEP
+ atc->pcms[device] = pcm;
+#endif
+
+ return 0;
+}
diff --git a/sound/pci/ctxfi/ctpcm.h b/sound/pci/ctxfi/ctpcm.h
new file mode 100644
index 000000000..8b39bdd26
--- /dev/null
+++ b/sound/pci/ctxfi/ctpcm.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctpcm.h
+ *
+ * @Brief
+ * This file contains the definition of the pcm device functions.
+ *
+ * @Author Liu Chun
+ * @Date Mar 28 2008
+ */
+
+#ifndef CTPCM_H
+#define CTPCM_H
+
+#include "ctatc.h"
+
+int ct_alsa_pcm_create(struct ct_atc *atc,
+ enum CTALSADEVS device,
+ const char *device_name);
+
+#endif /* CTPCM_H */
diff --git a/sound/pci/ctxfi/ctresource.c b/sound/pci/ctxfi/ctresource.c
new file mode 100644
index 000000000..be1d3e613
--- /dev/null
+++ b/sound/pci/ctxfi/ctresource.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctresource.c
+ *
+ * @Brief
+ * This file contains the implementation of some generic helper functions.
+ *
+ * @Author Liu Chun
+ * @Date May 15 2008
+ */
+
+#include "ctresource.h"
+#include "cthardware.h"
+#include <linux/err.h>
+#include <linux/slab.h>
+
+#define AUDIO_SLOT_BLOCK_NUM 256
+
+/* Resource allocation based on bit-map management mechanism */
+static int
+get_resource(u8 *rscs, unsigned int amount,
+ unsigned int multi, unsigned int *ridx)
+{
+ int i, j, k, n;
+
+ /* Check whether there are sufficient resources to meet request. */
+ for (i = 0, n = multi; i < amount; i++) {
+ j = i / 8;
+ k = i % 8;
+ if (rscs[j] & ((u8)1 << k)) {
+ n = multi;
+ continue;
+ }
+ if (!(--n))
+ break; /* found sufficient contiguous resources */
+ }
+
+ if (i >= amount) {
+ /* Can not find sufficient contiguous resources */
+ return -ENOENT;
+ }
+
+ /* Mark the contiguous bits in resource bit-map as used */
+ for (n = multi; n > 0; n--) {
+ j = i / 8;
+ k = i % 8;
+ rscs[j] |= ((u8)1 << k);
+ i--;
+ }
+
+ *ridx = i + 1;
+
+ return 0;
+}
+
+static int put_resource(u8 *rscs, unsigned int multi, unsigned int idx)
+{
+ unsigned int i, j, k, n;
+
+ /* Mark the contiguous bits in resource bit-map as used */
+ for (n = multi, i = idx; n > 0; n--) {
+ j = i / 8;
+ k = i % 8;
+ rscs[j] &= ~((u8)1 << k);
+ i++;
+ }
+
+ return 0;
+}
+
+int mgr_get_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int *ridx)
+{
+ int err;
+
+ if (n > mgr->avail)
+ return -ENOENT;
+
+ err = get_resource(mgr->rscs, mgr->amount, n, ridx);
+ if (!err)
+ mgr->avail -= n;
+
+ return err;
+}
+
+int mgr_put_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int idx)
+{
+ put_resource(mgr->rscs, n, idx);
+ mgr->avail += n;
+
+ return 0;
+}
+
+static const unsigned char offset_in_audio_slot_block[NUM_RSCTYP] = {
+ /* SRC channel is at Audio Ring slot 1 every 16 slots. */
+ [SRC] = 0x1,
+ [AMIXER] = 0x4,
+ [SUM] = 0xc,
+};
+
+static int rsc_index(const struct rsc *rsc)
+{
+ return rsc->conj;
+}
+
+static int audio_ring_slot(const struct rsc *rsc)
+{
+ return (rsc->conj << 4) + offset_in_audio_slot_block[rsc->type];
+}
+
+static void rsc_next_conj(struct rsc *rsc)
+{
+ unsigned int i;
+ for (i = 0; (i < 8) && (!(rsc->msr & (0x1 << i))); )
+ i++;
+ rsc->conj += (AUDIO_SLOT_BLOCK_NUM >> i);
+}
+
+static void rsc_master(struct rsc *rsc)
+{
+ rsc->conj = rsc->idx;
+}
+
+static const struct rsc_ops rsc_generic_ops = {
+ .index = rsc_index,
+ .output_slot = audio_ring_slot,
+ .master = rsc_master,
+ .next_conj = rsc_next_conj,
+};
+
+int
+rsc_init(struct rsc *rsc, u32 idx, enum RSCTYP type, u32 msr, struct hw *hw)
+{
+ int err = 0;
+
+ rsc->idx = idx;
+ rsc->conj = idx;
+ rsc->type = type;
+ rsc->msr = msr;
+ rsc->hw = hw;
+ rsc->ops = &rsc_generic_ops;
+ if (!hw) {
+ rsc->ctrl_blk = NULL;
+ return 0;
+ }
+
+ switch (type) {
+ case SRC:
+ err = hw->src_rsc_get_ctrl_blk(&rsc->ctrl_blk);
+ break;
+ case AMIXER:
+ err = hw->amixer_rsc_get_ctrl_blk(&rsc->ctrl_blk);
+ break;
+ case SRCIMP:
+ case SUM:
+ case DAIO:
+ break;
+ default:
+ dev_err(((struct hw *)hw)->card->dev,
+ "Invalid resource type value %d!\n", type);
+ return -EINVAL;
+ }
+
+ if (err) {
+ dev_err(((struct hw *)hw)->card->dev,
+ "Failed to get resource control block!\n");
+ return err;
+ }
+
+ return 0;
+}
+
+int rsc_uninit(struct rsc *rsc)
+{
+ if ((NULL != rsc->hw) && (NULL != rsc->ctrl_blk)) {
+ switch (rsc->type) {
+ case SRC:
+ rsc->hw->src_rsc_put_ctrl_blk(rsc->ctrl_blk);
+ break;
+ case AMIXER:
+ rsc->hw->amixer_rsc_put_ctrl_blk(rsc->ctrl_blk);
+ break;
+ case SUM:
+ case DAIO:
+ break;
+ default:
+ dev_err(((struct hw *)rsc->hw)->card->dev,
+ "Invalid resource type value %d!\n",
+ rsc->type);
+ break;
+ }
+
+ rsc->hw = rsc->ctrl_blk = NULL;
+ }
+
+ rsc->idx = rsc->conj = 0;
+ rsc->type = NUM_RSCTYP;
+ rsc->msr = 0;
+
+ return 0;
+}
+
+int rsc_mgr_init(struct rsc_mgr *mgr, enum RSCTYP type,
+ unsigned int amount, struct hw *hw)
+{
+ int err = 0;
+
+ mgr->type = NUM_RSCTYP;
+
+ mgr->rscs = kzalloc(DIV_ROUND_UP(amount, 8), GFP_KERNEL);
+ if (!mgr->rscs)
+ return -ENOMEM;
+
+ switch (type) {
+ case SRC:
+ err = hw->src_mgr_get_ctrl_blk(&mgr->ctrl_blk);
+ break;
+ case SRCIMP:
+ err = hw->srcimp_mgr_get_ctrl_blk(&mgr->ctrl_blk);
+ break;
+ case AMIXER:
+ err = hw->amixer_mgr_get_ctrl_blk(&mgr->ctrl_blk);
+ break;
+ case DAIO:
+ err = hw->daio_mgr_get_ctrl_blk(hw, &mgr->ctrl_blk);
+ break;
+ case SUM:
+ break;
+ default:
+ dev_err(hw->card->dev,
+ "Invalid resource type value %d!\n", type);
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (err) {
+ dev_err(hw->card->dev,
+ "Failed to get manager control block!\n");
+ goto error;
+ }
+
+ mgr->type = type;
+ mgr->avail = mgr->amount = amount;
+ mgr->hw = hw;
+
+ return 0;
+
+error:
+ kfree(mgr->rscs);
+ return err;
+}
+
+int rsc_mgr_uninit(struct rsc_mgr *mgr)
+{
+ kfree(mgr->rscs);
+ mgr->rscs = NULL;
+
+ if ((NULL != mgr->hw) && (NULL != mgr->ctrl_blk)) {
+ switch (mgr->type) {
+ case SRC:
+ mgr->hw->src_mgr_put_ctrl_blk(mgr->ctrl_blk);
+ break;
+ case SRCIMP:
+ mgr->hw->srcimp_mgr_put_ctrl_blk(mgr->ctrl_blk);
+ break;
+ case AMIXER:
+ mgr->hw->amixer_mgr_put_ctrl_blk(mgr->ctrl_blk);
+ break;
+ case DAIO:
+ mgr->hw->daio_mgr_put_ctrl_blk(mgr->ctrl_blk);
+ break;
+ case SUM:
+ break;
+ default:
+ dev_err(((struct hw *)mgr->hw)->card->dev,
+ "Invalid resource type value %d!\n",
+ mgr->type);
+ break;
+ }
+
+ mgr->hw = mgr->ctrl_blk = NULL;
+ }
+
+ mgr->type = NUM_RSCTYP;
+ mgr->avail = mgr->amount = 0;
+
+ return 0;
+}
diff --git a/sound/pci/ctxfi/ctresource.h b/sound/pci/ctxfi/ctresource.h
new file mode 100644
index 000000000..58553bda4
--- /dev/null
+++ b/sound/pci/ctxfi/ctresource.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctresource.h
+ *
+ * @Brief
+ * This file contains the definition of generic hardware resources for
+ * resource management.
+ *
+ * @Author Liu Chun
+ * @Date May 13 2008
+ */
+
+#ifndef CTRESOURCE_H
+#define CTRESOURCE_H
+
+#include <linux/types.h>
+
+enum RSCTYP {
+ SRC,
+ SRCIMP,
+ AMIXER,
+ SUM,
+ DAIO,
+ NUM_RSCTYP /* This must be the last one and less than 16 */
+};
+
+struct rsc_ops;
+
+struct rsc {
+ u32 idx:12; /* The index of a resource */
+ u32 type:4; /* The type (RSCTYP) of a resource */
+ u32 conj:12; /* Current conjugate index */
+ u32 msr:4; /* The Master Sample Rate a resource working on */
+ void *ctrl_blk; /* Chip specific control info block for a resource */
+ struct hw *hw; /* Chip specific object for hardware access means */
+ const struct rsc_ops *ops; /* Generic resource operations */
+};
+
+struct rsc_ops {
+ void (*master)(struct rsc *rsc); /* Move to master resource */
+ void (*next_conj)(struct rsc *rsc); /* Move to next conjugate resource */
+ int (*index)(const struct rsc *rsc); /* Return the index of resource */
+ /* Return the output slot number */
+ int (*output_slot)(const struct rsc *rsc);
+};
+
+int
+rsc_init(struct rsc *rsc, u32 idx, enum RSCTYP type, u32 msr, struct hw *hw);
+int rsc_uninit(struct rsc *rsc);
+
+struct rsc_mgr {
+ enum RSCTYP type; /* The type (RSCTYP) of resource to manage */
+ unsigned int amount; /* The total amount of a kind of resource */
+ unsigned int avail; /* The amount of currently available resources */
+ unsigned char *rscs; /* The bit-map for resource allocation */
+ void *ctrl_blk; /* Chip specific control info block */
+ struct hw *hw; /* Chip specific object for hardware access */
+};
+
+/* Resource management is based on bit-map mechanism */
+int rsc_mgr_init(struct rsc_mgr *mgr, enum RSCTYP type,
+ unsigned int amount, struct hw *hw);
+int rsc_mgr_uninit(struct rsc_mgr *mgr);
+int mgr_get_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int *ridx);
+int mgr_put_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int idx);
+
+#endif /* CTRESOURCE_H */
diff --git a/sound/pci/ctxfi/ctsrc.c b/sound/pci/ctxfi/ctsrc.c
new file mode 100644
index 000000000..4a94b4708
--- /dev/null
+++ b/sound/pci/ctxfi/ctsrc.c
@@ -0,0 +1,882 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctsrc.c
+ *
+ * @Brief
+ * This file contains the implementation of the Sample Rate Convertor
+ * resource management object.
+ *
+ * @Author Liu Chun
+ * @Date May 13 2008
+ */
+
+#include "ctsrc.h"
+#include "cthardware.h"
+#include <linux/slab.h>
+
+#define SRC_RESOURCE_NUM 256
+#define SRCIMP_RESOURCE_NUM 256
+
+static unsigned int conj_mask;
+
+static int src_default_config_memrd(struct src *src);
+static int src_default_config_memwr(struct src *src);
+static int src_default_config_arcrw(struct src *src);
+
+static int (*src_default_config[3])(struct src *) = {
+ [MEMRD] = src_default_config_memrd,
+ [MEMWR] = src_default_config_memwr,
+ [ARCRW] = src_default_config_arcrw
+};
+
+static int src_set_state(struct src *src, unsigned int state)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_state(src->rsc.ctrl_blk, state);
+
+ return 0;
+}
+
+static int src_set_bm(struct src *src, unsigned int bm)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_bm(src->rsc.ctrl_blk, bm);
+
+ return 0;
+}
+
+static int src_set_sf(struct src *src, unsigned int sf)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_sf(src->rsc.ctrl_blk, sf);
+
+ return 0;
+}
+
+static int src_set_pm(struct src *src, unsigned int pm)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_pm(src->rsc.ctrl_blk, pm);
+
+ return 0;
+}
+
+static int src_set_rom(struct src *src, unsigned int rom)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_rom(src->rsc.ctrl_blk, rom);
+
+ return 0;
+}
+
+static int src_set_vo(struct src *src, unsigned int vo)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_vo(src->rsc.ctrl_blk, vo);
+
+ return 0;
+}
+
+static int src_set_st(struct src *src, unsigned int st)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_st(src->rsc.ctrl_blk, st);
+
+ return 0;
+}
+
+static int src_set_bp(struct src *src, unsigned int bp)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_bp(src->rsc.ctrl_blk, bp);
+
+ return 0;
+}
+
+static int src_set_cisz(struct src *src, unsigned int cisz)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_cisz(src->rsc.ctrl_blk, cisz);
+
+ return 0;
+}
+
+static int src_set_ca(struct src *src, unsigned int ca)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_ca(src->rsc.ctrl_blk, ca);
+
+ return 0;
+}
+
+static int src_set_sa(struct src *src, unsigned int sa)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_sa(src->rsc.ctrl_blk, sa);
+
+ return 0;
+}
+
+static int src_set_la(struct src *src, unsigned int la)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_la(src->rsc.ctrl_blk, la);
+
+ return 0;
+}
+
+static int src_set_pitch(struct src *src, unsigned int pitch)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_pitch(src->rsc.ctrl_blk, pitch);
+
+ return 0;
+}
+
+static int src_set_clear_zbufs(struct src *src)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1);
+
+ return 0;
+}
+
+static int src_commit_write(struct src *src)
+{
+ struct hw *hw;
+ int i;
+ unsigned int dirty = 0;
+
+ hw = src->rsc.hw;
+ src->rsc.ops->master(&src->rsc);
+ if (src->rsc.msr > 1) {
+ /* Save dirty flags for conjugate resource programming */
+ dirty = hw->src_get_dirty(src->rsc.ctrl_blk) & conj_mask;
+ }
+ hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+ src->rsc.ctrl_blk);
+
+ /* Program conjugate parameter mixer resources */
+ if (MEMWR == src->mode)
+ return 0;
+
+ for (i = 1; i < src->rsc.msr; i++) {
+ src->rsc.ops->next_conj(&src->rsc);
+ hw->src_set_dirty(src->rsc.ctrl_blk, dirty);
+ hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+ src->rsc.ctrl_blk);
+ }
+ src->rsc.ops->master(&src->rsc);
+
+ return 0;
+}
+
+static int src_get_ca(struct src *src)
+{
+ struct hw *hw;
+
+ hw = src->rsc.hw;
+ return hw->src_get_ca(hw, src->rsc.ops->index(&src->rsc),
+ src->rsc.ctrl_blk);
+}
+
+static int src_init(struct src *src)
+{
+ src_default_config[src->mode](src);
+
+ return 0;
+}
+
+static struct src *src_next_interleave(struct src *src)
+{
+ return src->intlv;
+}
+
+static int src_default_config_memrd(struct src *src)
+{
+ struct hw *hw = src->rsc.hw;
+ unsigned int rsr, msr;
+
+ hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF);
+ hw->src_set_bm(src->rsc.ctrl_blk, 1);
+ for (rsr = 0, msr = src->rsc.msr; msr > 1; msr >>= 1)
+ rsr++;
+
+ hw->src_set_rsr(src->rsc.ctrl_blk, rsr);
+ hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_S16);
+ hw->src_set_wr(src->rsc.ctrl_blk, 0);
+ hw->src_set_pm(src->rsc.ctrl_blk, 0);
+ hw->src_set_rom(src->rsc.ctrl_blk, 0);
+ hw->src_set_vo(src->rsc.ctrl_blk, 0);
+ hw->src_set_st(src->rsc.ctrl_blk, 0);
+ hw->src_set_ilsz(src->rsc.ctrl_blk, src->multi - 1);
+ hw->src_set_cisz(src->rsc.ctrl_blk, 0x80);
+ hw->src_set_sa(src->rsc.ctrl_blk, 0x0);
+ hw->src_set_la(src->rsc.ctrl_blk, 0x1000);
+ hw->src_set_ca(src->rsc.ctrl_blk, 0x80);
+ hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000);
+ hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1);
+
+ src->rsc.ops->master(&src->rsc);
+ hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+ src->rsc.ctrl_blk);
+
+ for (msr = 1; msr < src->rsc.msr; msr++) {
+ src->rsc.ops->next_conj(&src->rsc);
+ hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000);
+ hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+ src->rsc.ctrl_blk);
+ }
+ src->rsc.ops->master(&src->rsc);
+
+ return 0;
+}
+
+static int src_default_config_memwr(struct src *src)
+{
+ struct hw *hw = src->rsc.hw;
+
+ hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF);
+ hw->src_set_bm(src->rsc.ctrl_blk, 1);
+ hw->src_set_rsr(src->rsc.ctrl_blk, 0);
+ hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_S16);
+ hw->src_set_wr(src->rsc.ctrl_blk, 1);
+ hw->src_set_pm(src->rsc.ctrl_blk, 0);
+ hw->src_set_rom(src->rsc.ctrl_blk, 0);
+ hw->src_set_vo(src->rsc.ctrl_blk, 0);
+ hw->src_set_st(src->rsc.ctrl_blk, 0);
+ hw->src_set_ilsz(src->rsc.ctrl_blk, 0);
+ hw->src_set_cisz(src->rsc.ctrl_blk, 0x80);
+ hw->src_set_sa(src->rsc.ctrl_blk, 0x0);
+ hw->src_set_la(src->rsc.ctrl_blk, 0x1000);
+ hw->src_set_ca(src->rsc.ctrl_blk, 0x80);
+ hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000);
+ hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1);
+
+ src->rsc.ops->master(&src->rsc);
+ hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+ src->rsc.ctrl_blk);
+
+ return 0;
+}
+
+static int src_default_config_arcrw(struct src *src)
+{
+ struct hw *hw = src->rsc.hw;
+ unsigned int rsr, msr;
+ unsigned int dirty;
+
+ hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF);
+ hw->src_set_bm(src->rsc.ctrl_blk, 0);
+ for (rsr = 0, msr = src->rsc.msr; msr > 1; msr >>= 1)
+ rsr++;
+
+ hw->src_set_rsr(src->rsc.ctrl_blk, rsr);
+ hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_F32);
+ hw->src_set_wr(src->rsc.ctrl_blk, 0);
+ hw->src_set_pm(src->rsc.ctrl_blk, 0);
+ hw->src_set_rom(src->rsc.ctrl_blk, 0);
+ hw->src_set_vo(src->rsc.ctrl_blk, 0);
+ hw->src_set_st(src->rsc.ctrl_blk, 0);
+ hw->src_set_ilsz(src->rsc.ctrl_blk, 0);
+ hw->src_set_cisz(src->rsc.ctrl_blk, 0x80);
+ hw->src_set_sa(src->rsc.ctrl_blk, 0x0);
+ /*hw->src_set_sa(src->rsc.ctrl_blk, 0x100);*/
+ hw->src_set_la(src->rsc.ctrl_blk, 0x1000);
+ /*hw->src_set_la(src->rsc.ctrl_blk, 0x03ffffe0);*/
+ hw->src_set_ca(src->rsc.ctrl_blk, 0x80);
+ hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000);
+ hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1);
+
+ dirty = hw->src_get_dirty(src->rsc.ctrl_blk);
+ src->rsc.ops->master(&src->rsc);
+ for (msr = 0; msr < src->rsc.msr; msr++) {
+ hw->src_set_dirty(src->rsc.ctrl_blk, dirty);
+ hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+ src->rsc.ctrl_blk);
+ src->rsc.ops->next_conj(&src->rsc);
+ }
+ src->rsc.ops->master(&src->rsc);
+
+ return 0;
+}
+
+static const struct src_rsc_ops src_rsc_ops = {
+ .set_state = src_set_state,
+ .set_bm = src_set_bm,
+ .set_sf = src_set_sf,
+ .set_pm = src_set_pm,
+ .set_rom = src_set_rom,
+ .set_vo = src_set_vo,
+ .set_st = src_set_st,
+ .set_bp = src_set_bp,
+ .set_cisz = src_set_cisz,
+ .set_ca = src_set_ca,
+ .set_sa = src_set_sa,
+ .set_la = src_set_la,
+ .set_pitch = src_set_pitch,
+ .set_clr_zbufs = src_set_clear_zbufs,
+ .commit_write = src_commit_write,
+ .get_ca = src_get_ca,
+ .init = src_init,
+ .next_interleave = src_next_interleave,
+};
+
+static int
+src_rsc_init(struct src *src, u32 idx,
+ const struct src_desc *desc, struct src_mgr *mgr)
+{
+ int err;
+ int i, n;
+ struct src *p;
+
+ n = (MEMRD == desc->mode) ? desc->multi : 1;
+ for (i = 0, p = src; i < n; i++, p++) {
+ err = rsc_init(&p->rsc, idx + i, SRC, desc->msr, mgr->mgr.hw);
+ if (err)
+ goto error1;
+
+ /* Initialize src specific rsc operations */
+ p->ops = &src_rsc_ops;
+ p->multi = (0 == i) ? desc->multi : 1;
+ p->mode = desc->mode;
+ src_default_config[desc->mode](p);
+ mgr->src_enable(mgr, p);
+ p->intlv = p + 1;
+ }
+ (--p)->intlv = NULL; /* Set @intlv of the last SRC to NULL */
+
+ mgr->commit_write(mgr);
+
+ return 0;
+
+error1:
+ for (i--, p--; i >= 0; i--, p--) {
+ mgr->src_disable(mgr, p);
+ rsc_uninit(&p->rsc);
+ }
+ mgr->commit_write(mgr);
+ return err;
+}
+
+static int src_rsc_uninit(struct src *src, struct src_mgr *mgr)
+{
+ int i, n;
+ struct src *p;
+
+ n = (MEMRD == src->mode) ? src->multi : 1;
+ for (i = 0, p = src; i < n; i++, p++) {
+ mgr->src_disable(mgr, p);
+ rsc_uninit(&p->rsc);
+ p->multi = 0;
+ p->ops = NULL;
+ p->mode = NUM_SRCMODES;
+ p->intlv = NULL;
+ }
+ mgr->commit_write(mgr);
+
+ return 0;
+}
+
+static int
+get_src_rsc(struct src_mgr *mgr, const struct src_desc *desc, struct src **rsrc)
+{
+ unsigned int idx = SRC_RESOURCE_NUM;
+ int err;
+ struct src *src;
+ unsigned long flags;
+
+ *rsrc = NULL;
+
+ /* Check whether there are sufficient src resources to meet request. */
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ if (MEMRD == desc->mode)
+ err = mgr_get_resource(&mgr->mgr, desc->multi, &idx);
+ else
+ err = mgr_get_resource(&mgr->mgr, 1, &idx);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ if (err) {
+ dev_err(mgr->card->dev,
+ "Can't meet SRC resource request!\n");
+ return err;
+ }
+
+ /* Allocate mem for master src resource */
+ if (MEMRD == desc->mode)
+ src = kcalloc(desc->multi, sizeof(*src), GFP_KERNEL);
+ else
+ src = kzalloc(sizeof(*src), GFP_KERNEL);
+
+ if (!src) {
+ err = -ENOMEM;
+ goto error1;
+ }
+
+ err = src_rsc_init(src, idx, desc, mgr);
+ if (err)
+ goto error2;
+
+ *rsrc = src;
+
+ return 0;
+
+error2:
+ kfree(src);
+error1:
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ if (MEMRD == desc->mode)
+ mgr_put_resource(&mgr->mgr, desc->multi, idx);
+ else
+ mgr_put_resource(&mgr->mgr, 1, idx);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ return err;
+}
+
+static int put_src_rsc(struct src_mgr *mgr, struct src *src)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ src->rsc.ops->master(&src->rsc);
+ if (MEMRD == src->mode)
+ mgr_put_resource(&mgr->mgr, src->multi,
+ src->rsc.ops->index(&src->rsc));
+ else
+ mgr_put_resource(&mgr->mgr, 1, src->rsc.ops->index(&src->rsc));
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ src_rsc_uninit(src, mgr);
+ kfree(src);
+
+ return 0;
+}
+
+static int src_enable_s(struct src_mgr *mgr, struct src *src)
+{
+ struct hw *hw = mgr->mgr.hw;
+ int i;
+
+ src->rsc.ops->master(&src->rsc);
+ for (i = 0; i < src->rsc.msr; i++) {
+ hw->src_mgr_enbs_src(mgr->mgr.ctrl_blk,
+ src->rsc.ops->index(&src->rsc));
+ src->rsc.ops->next_conj(&src->rsc);
+ }
+ src->rsc.ops->master(&src->rsc);
+
+ return 0;
+}
+
+static int src_enable(struct src_mgr *mgr, struct src *src)
+{
+ struct hw *hw = mgr->mgr.hw;
+ int i;
+
+ src->rsc.ops->master(&src->rsc);
+ for (i = 0; i < src->rsc.msr; i++) {
+ hw->src_mgr_enb_src(mgr->mgr.ctrl_blk,
+ src->rsc.ops->index(&src->rsc));
+ src->rsc.ops->next_conj(&src->rsc);
+ }
+ src->rsc.ops->master(&src->rsc);
+
+ return 0;
+}
+
+static int src_disable(struct src_mgr *mgr, struct src *src)
+{
+ struct hw *hw = mgr->mgr.hw;
+ int i;
+
+ src->rsc.ops->master(&src->rsc);
+ for (i = 0; i < src->rsc.msr; i++) {
+ hw->src_mgr_dsb_src(mgr->mgr.ctrl_blk,
+ src->rsc.ops->index(&src->rsc));
+ src->rsc.ops->next_conj(&src->rsc);
+ }
+ src->rsc.ops->master(&src->rsc);
+
+ return 0;
+}
+
+static int src_mgr_commit_write(struct src_mgr *mgr)
+{
+ struct hw *hw = mgr->mgr.hw;
+
+ hw->src_mgr_commit_write(hw, mgr->mgr.ctrl_blk);
+
+ return 0;
+}
+
+int src_mgr_create(struct hw *hw, struct src_mgr **rsrc_mgr)
+{
+ int err, i;
+ struct src_mgr *src_mgr;
+
+ *rsrc_mgr = NULL;
+ src_mgr = kzalloc(sizeof(*src_mgr), GFP_KERNEL);
+ if (!src_mgr)
+ return -ENOMEM;
+
+ err = rsc_mgr_init(&src_mgr->mgr, SRC, SRC_RESOURCE_NUM, hw);
+ if (err)
+ goto error1;
+
+ spin_lock_init(&src_mgr->mgr_lock);
+ conj_mask = hw->src_dirty_conj_mask();
+
+ src_mgr->get_src = get_src_rsc;
+ src_mgr->put_src = put_src_rsc;
+ src_mgr->src_enable_s = src_enable_s;
+ src_mgr->src_enable = src_enable;
+ src_mgr->src_disable = src_disable;
+ src_mgr->commit_write = src_mgr_commit_write;
+ src_mgr->card = hw->card;
+
+ /* Disable all SRC resources. */
+ for (i = 0; i < 256; i++)
+ hw->src_mgr_dsb_src(src_mgr->mgr.ctrl_blk, i);
+
+ hw->src_mgr_commit_write(hw, src_mgr->mgr.ctrl_blk);
+
+ *rsrc_mgr = src_mgr;
+
+ return 0;
+
+error1:
+ kfree(src_mgr);
+ return err;
+}
+
+int src_mgr_destroy(struct src_mgr *src_mgr)
+{
+ rsc_mgr_uninit(&src_mgr->mgr);
+ kfree(src_mgr);
+
+ return 0;
+}
+
+/* SRCIMP resource manager operations */
+
+static void srcimp_master(struct rsc *rsc)
+{
+ rsc->conj = 0;
+ rsc->idx = container_of(rsc, struct srcimp, rsc)->idx[0];
+}
+
+static void srcimp_next_conj(struct rsc *rsc)
+{
+ rsc->conj++;
+}
+
+static int srcimp_index(const struct rsc *rsc)
+{
+ return container_of(rsc, struct srcimp, rsc)->idx[rsc->conj];
+}
+
+static const struct rsc_ops srcimp_basic_rsc_ops = {
+ .master = srcimp_master,
+ .next_conj = srcimp_next_conj,
+ .index = srcimp_index,
+ .output_slot = NULL,
+};
+
+static int srcimp_map(struct srcimp *srcimp, struct src *src, struct rsc *input)
+{
+ struct imapper *entry;
+ int i;
+
+ srcimp->rsc.ops->master(&srcimp->rsc);
+ src->rsc.ops->master(&src->rsc);
+ input->ops->master(input);
+
+ /* Program master and conjugate resources */
+ for (i = 0; i < srcimp->rsc.msr; i++) {
+ entry = &srcimp->imappers[i];
+ entry->slot = input->ops->output_slot(input);
+ entry->user = src->rsc.ops->index(&src->rsc);
+ entry->addr = srcimp->rsc.ops->index(&srcimp->rsc);
+ srcimp->mgr->imap_add(srcimp->mgr, entry);
+ srcimp->mapped |= (0x1 << i);
+
+ srcimp->rsc.ops->next_conj(&srcimp->rsc);
+ input->ops->next_conj(input);
+ }
+
+ srcimp->rsc.ops->master(&srcimp->rsc);
+ input->ops->master(input);
+
+ return 0;
+}
+
+static int srcimp_unmap(struct srcimp *srcimp)
+{
+ int i;
+
+ /* Program master and conjugate resources */
+ for (i = 0; i < srcimp->rsc.msr; i++) {
+ if (srcimp->mapped & (0x1 << i)) {
+ srcimp->mgr->imap_delete(srcimp->mgr,
+ &srcimp->imappers[i]);
+ srcimp->mapped &= ~(0x1 << i);
+ }
+ }
+
+ return 0;
+}
+
+static const struct srcimp_rsc_ops srcimp_ops = {
+ .map = srcimp_map,
+ .unmap = srcimp_unmap
+};
+
+static int srcimp_rsc_init(struct srcimp *srcimp,
+ const struct srcimp_desc *desc,
+ struct srcimp_mgr *mgr)
+{
+ int err;
+
+ err = rsc_init(&srcimp->rsc, srcimp->idx[0],
+ SRCIMP, desc->msr, mgr->mgr.hw);
+ if (err)
+ return err;
+
+ /* Reserve memory for imapper nodes */
+ srcimp->imappers = kcalloc(desc->msr, sizeof(struct imapper),
+ GFP_KERNEL);
+ if (!srcimp->imappers) {
+ err = -ENOMEM;
+ goto error1;
+ }
+
+ /* Set srcimp specific operations */
+ srcimp->rsc.ops = &srcimp_basic_rsc_ops;
+ srcimp->ops = &srcimp_ops;
+ srcimp->mgr = mgr;
+
+ srcimp->rsc.ops->master(&srcimp->rsc);
+
+ return 0;
+
+error1:
+ rsc_uninit(&srcimp->rsc);
+ return err;
+}
+
+static int srcimp_rsc_uninit(struct srcimp *srcimp)
+{
+ kfree(srcimp->imappers);
+ srcimp->imappers = NULL;
+ srcimp->ops = NULL;
+ srcimp->mgr = NULL;
+ rsc_uninit(&srcimp->rsc);
+
+ return 0;
+}
+
+static int get_srcimp_rsc(struct srcimp_mgr *mgr,
+ const struct srcimp_desc *desc,
+ struct srcimp **rsrcimp)
+{
+ int err, i;
+ unsigned int idx;
+ struct srcimp *srcimp;
+ unsigned long flags;
+
+ *rsrcimp = NULL;
+
+ /* Allocate mem for SRCIMP resource */
+ srcimp = kzalloc(sizeof(*srcimp), GFP_KERNEL);
+ if (!srcimp)
+ return -ENOMEM;
+
+ /* Check whether there are sufficient SRCIMP resources. */
+ err = 0;
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < desc->msr; i++) {
+ err = mgr_get_resource(&mgr->mgr, 1, &idx);
+ if (err)
+ break;
+
+ srcimp->idx[i] = idx;
+ }
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ if (err) {
+ dev_err(mgr->card->dev,
+ "Can't meet SRCIMP resource request!\n");
+ goto error1;
+ }
+
+ err = srcimp_rsc_init(srcimp, desc, mgr);
+ if (err)
+ goto error1;
+
+ *rsrcimp = srcimp;
+
+ return 0;
+
+error1:
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i--; i >= 0; i--)
+ mgr_put_resource(&mgr->mgr, 1, srcimp->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ kfree(srcimp);
+ return err;
+}
+
+static int put_srcimp_rsc(struct srcimp_mgr *mgr, struct srcimp *srcimp)
+{
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&mgr->mgr_lock, flags);
+ for (i = 0; i < srcimp->rsc.msr; i++)
+ mgr_put_resource(&mgr->mgr, 1, srcimp->idx[i]);
+
+ spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+ srcimp_rsc_uninit(srcimp);
+ kfree(srcimp);
+
+ return 0;
+}
+
+static int srcimp_map_op(void *data, struct imapper *entry)
+{
+ struct rsc_mgr *mgr = &((struct srcimp_mgr *)data)->mgr;
+ struct hw *hw = mgr->hw;
+
+ hw->srcimp_mgr_set_imaparc(mgr->ctrl_blk, entry->slot);
+ hw->srcimp_mgr_set_imapuser(mgr->ctrl_blk, entry->user);
+ hw->srcimp_mgr_set_imapnxt(mgr->ctrl_blk, entry->next);
+ hw->srcimp_mgr_set_imapaddr(mgr->ctrl_blk, entry->addr);
+ hw->srcimp_mgr_commit_write(mgr->hw, mgr->ctrl_blk);
+
+ return 0;
+}
+
+static int srcimp_imap_add(struct srcimp_mgr *mgr, struct imapper *entry)
+{
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&mgr->imap_lock, flags);
+ if ((0 == entry->addr) && (mgr->init_imap_added)) {
+ input_mapper_delete(&mgr->imappers,
+ mgr->init_imap, srcimp_map_op, mgr);
+ mgr->init_imap_added = 0;
+ }
+ err = input_mapper_add(&mgr->imappers, entry, srcimp_map_op, mgr);
+ spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+ return err;
+}
+
+static int srcimp_imap_delete(struct srcimp_mgr *mgr, struct imapper *entry)
+{
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&mgr->imap_lock, flags);
+ err = input_mapper_delete(&mgr->imappers, entry, srcimp_map_op, mgr);
+ if (list_empty(&mgr->imappers)) {
+ input_mapper_add(&mgr->imappers, mgr->init_imap,
+ srcimp_map_op, mgr);
+ mgr->init_imap_added = 1;
+ }
+ spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+ return err;
+}
+
+int srcimp_mgr_create(struct hw *hw, struct srcimp_mgr **rsrcimp_mgr)
+{
+ int err;
+ struct srcimp_mgr *srcimp_mgr;
+ struct imapper *entry;
+
+ *rsrcimp_mgr = NULL;
+ srcimp_mgr = kzalloc(sizeof(*srcimp_mgr), GFP_KERNEL);
+ if (!srcimp_mgr)
+ return -ENOMEM;
+
+ err = rsc_mgr_init(&srcimp_mgr->mgr, SRCIMP, SRCIMP_RESOURCE_NUM, hw);
+ if (err)
+ goto error1;
+
+ spin_lock_init(&srcimp_mgr->mgr_lock);
+ spin_lock_init(&srcimp_mgr->imap_lock);
+ INIT_LIST_HEAD(&srcimp_mgr->imappers);
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry) {
+ err = -ENOMEM;
+ goto error2;
+ }
+ entry->slot = entry->addr = entry->next = entry->user = 0;
+ list_add(&entry->list, &srcimp_mgr->imappers);
+ srcimp_mgr->init_imap = entry;
+ srcimp_mgr->init_imap_added = 1;
+
+ srcimp_mgr->get_srcimp = get_srcimp_rsc;
+ srcimp_mgr->put_srcimp = put_srcimp_rsc;
+ srcimp_mgr->imap_add = srcimp_imap_add;
+ srcimp_mgr->imap_delete = srcimp_imap_delete;
+ srcimp_mgr->card = hw->card;
+
+ *rsrcimp_mgr = srcimp_mgr;
+
+ return 0;
+
+error2:
+ rsc_mgr_uninit(&srcimp_mgr->mgr);
+error1:
+ kfree(srcimp_mgr);
+ return err;
+}
+
+int srcimp_mgr_destroy(struct srcimp_mgr *srcimp_mgr)
+{
+ unsigned long flags;
+
+ /* free src input mapper list */
+ spin_lock_irqsave(&srcimp_mgr->imap_lock, flags);
+ free_input_mapper_list(&srcimp_mgr->imappers);
+ spin_unlock_irqrestore(&srcimp_mgr->imap_lock, flags);
+
+ rsc_mgr_uninit(&srcimp_mgr->mgr);
+ kfree(srcimp_mgr);
+
+ return 0;
+}
diff --git a/sound/pci/ctxfi/ctsrc.h b/sound/pci/ctxfi/ctsrc.h
new file mode 100644
index 000000000..1124daf50
--- /dev/null
+++ b/sound/pci/ctxfi/ctsrc.h
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctsrc.h
+ *
+ * @Brief
+ * This file contains the definition of the Sample Rate Convertor
+ * resource management object.
+ *
+ * @Author Liu Chun
+ * @Date May 13 2008
+ */
+
+#ifndef CTSRC_H
+#define CTSRC_H
+
+#include "ctresource.h"
+#include "ctimap.h"
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <sound/core.h>
+
+#define SRC_STATE_OFF 0x0
+#define SRC_STATE_INIT 0x4
+#define SRC_STATE_RUN 0x5
+
+#define SRC_SF_U8 0x0
+#define SRC_SF_S16 0x1
+#define SRC_SF_S24 0x2
+#define SRC_SF_S32 0x3
+#define SRC_SF_F32 0x4
+
+/* Define the descriptor of a src resource */
+enum SRCMODE {
+ MEMRD, /* Read data from host memory */
+ MEMWR, /* Write data to host memory */
+ ARCRW, /* Read from and write to audio ring channel */
+ NUM_SRCMODES
+};
+
+struct src_rsc_ops;
+
+struct src {
+ struct rsc rsc; /* Basic resource info */
+ struct src *intlv; /* Pointer to next interleaved SRC in a series */
+ const struct src_rsc_ops *ops; /* SRC specific operations */
+ /* Number of contiguous srcs for interleaved usage */
+ unsigned char multi;
+ unsigned char mode; /* Working mode of this SRC resource */
+};
+
+struct src_rsc_ops {
+ int (*set_state)(struct src *src, unsigned int state);
+ int (*set_bm)(struct src *src, unsigned int bm);
+ int (*set_sf)(struct src *src, unsigned int sf);
+ int (*set_pm)(struct src *src, unsigned int pm);
+ int (*set_rom)(struct src *src, unsigned int rom);
+ int (*set_vo)(struct src *src, unsigned int vo);
+ int (*set_st)(struct src *src, unsigned int st);
+ int (*set_bp)(struct src *src, unsigned int bp);
+ int (*set_cisz)(struct src *src, unsigned int cisz);
+ int (*set_ca)(struct src *src, unsigned int ca);
+ int (*set_sa)(struct src *src, unsigned int sa);
+ int (*set_la)(struct src *src, unsigned int la);
+ int (*set_pitch)(struct src *src, unsigned int pitch);
+ int (*set_clr_zbufs)(struct src *src);
+ int (*commit_write)(struct src *src);
+ int (*get_ca)(struct src *src);
+ int (*init)(struct src *src);
+ struct src* (*next_interleave)(struct src *src);
+};
+
+/* Define src resource request description info */
+struct src_desc {
+ /* Number of contiguous master srcs for interleaved usage */
+ unsigned char multi;
+ unsigned char msr;
+ unsigned char mode; /* Working mode of the requested srcs */
+};
+
+/* Define src manager object */
+struct src_mgr {
+ struct rsc_mgr mgr; /* Basic resource manager info */
+ struct snd_card *card; /* pointer to this card */
+ spinlock_t mgr_lock;
+
+ /* request src resource */
+ int (*get_src)(struct src_mgr *mgr,
+ const struct src_desc *desc, struct src **rsrc);
+ /* return src resource */
+ int (*put_src)(struct src_mgr *mgr, struct src *src);
+ int (*src_enable_s)(struct src_mgr *mgr, struct src *src);
+ int (*src_enable)(struct src_mgr *mgr, struct src *src);
+ int (*src_disable)(struct src_mgr *mgr, struct src *src);
+ int (*commit_write)(struct src_mgr *mgr);
+};
+
+/* Define the descriptor of a SRC Input Mapper resource */
+struct srcimp_mgr;
+struct srcimp_rsc_ops;
+
+struct srcimp {
+ struct rsc rsc;
+ unsigned char idx[8];
+ struct imapper *imappers;
+ unsigned int mapped; /* A bit-map indicating which conj rsc is mapped */
+ struct srcimp_mgr *mgr;
+ const struct srcimp_rsc_ops *ops;
+};
+
+struct srcimp_rsc_ops {
+ int (*map)(struct srcimp *srcimp, struct src *user, struct rsc *input);
+ int (*unmap)(struct srcimp *srcimp);
+};
+
+/* Define SRCIMP resource request description info */
+struct srcimp_desc {
+ unsigned int msr;
+};
+
+struct srcimp_mgr {
+ struct rsc_mgr mgr; /* Basic resource manager info */
+ struct snd_card *card; /* pointer to this card */
+ spinlock_t mgr_lock;
+ spinlock_t imap_lock;
+ struct list_head imappers;
+ struct imapper *init_imap;
+ unsigned int init_imap_added;
+
+ /* request srcimp resource */
+ int (*get_srcimp)(struct srcimp_mgr *mgr,
+ const struct srcimp_desc *desc,
+ struct srcimp **rsrcimp);
+ /* return srcimp resource */
+ int (*put_srcimp)(struct srcimp_mgr *mgr, struct srcimp *srcimp);
+ int (*imap_add)(struct srcimp_mgr *mgr, struct imapper *entry);
+ int (*imap_delete)(struct srcimp_mgr *mgr, struct imapper *entry);
+};
+
+/* Constructor and destructor of SRC resource manager */
+int src_mgr_create(struct hw *hw, struct src_mgr **rsrc_mgr);
+int src_mgr_destroy(struct src_mgr *src_mgr);
+/* Constructor and destructor of SRCIMP resource manager */
+int srcimp_mgr_create(struct hw *hw, struct srcimp_mgr **rsrc_mgr);
+int srcimp_mgr_destroy(struct srcimp_mgr *srcimp_mgr);
+
+#endif /* CTSRC_H */
diff --git a/sound/pci/ctxfi/cttimer.c b/sound/pci/ctxfi/cttimer.c
new file mode 100644
index 000000000..0bb447ccd
--- /dev/null
+++ b/sound/pci/ctxfi/cttimer.c
@@ -0,0 +1,439 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCM timer handling on ctxfi
+ */
+
+#include <linux/slab.h>
+#include <linux/math64.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "ctatc.h"
+#include "cthardware.h"
+#include "cttimer.h"
+
+static bool use_system_timer;
+MODULE_PARM_DESC(use_system_timer, "Force to use system-timer");
+module_param(use_system_timer, bool, 0444);
+
+struct ct_timer_ops {
+ void (*init)(struct ct_timer_instance *);
+ void (*prepare)(struct ct_timer_instance *);
+ void (*start)(struct ct_timer_instance *);
+ void (*stop)(struct ct_timer_instance *);
+ void (*free_instance)(struct ct_timer_instance *);
+ void (*interrupt)(struct ct_timer *);
+ void (*free_global)(struct ct_timer *);
+};
+
+/* timer instance -- assigned to each PCM stream */
+struct ct_timer_instance {
+ spinlock_t lock;
+ struct ct_timer *timer_base;
+ struct ct_atc_pcm *apcm;
+ struct snd_pcm_substream *substream;
+ struct timer_list timer;
+ struct list_head instance_list;
+ struct list_head running_list;
+ unsigned int position;
+ unsigned int frag_count;
+ unsigned int running:1;
+ unsigned int need_update:1;
+};
+
+/* timer instance manager */
+struct ct_timer {
+ spinlock_t lock; /* global timer lock (for xfitimer) */
+ spinlock_t list_lock; /* lock for instance list */
+ struct ct_atc *atc;
+ const struct ct_timer_ops *ops;
+ struct list_head instance_head;
+ struct list_head running_head;
+ unsigned int wc; /* current wallclock */
+ unsigned int irq_handling:1; /* in IRQ handling */
+ unsigned int reprogram:1; /* need to reprogram the internval */
+ unsigned int running:1; /* global timer running */
+};
+
+
+/*
+ * system-timer-based updates
+ */
+
+static void ct_systimer_callback(struct timer_list *t)
+{
+ struct ct_timer_instance *ti = from_timer(ti, t, timer);
+ struct snd_pcm_substream *substream = ti->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = ti->apcm;
+ unsigned int period_size = runtime->period_size;
+ unsigned int buffer_size = runtime->buffer_size;
+ unsigned long flags;
+ unsigned int position, dist, interval;
+
+ position = substream->ops->pointer(substream);
+ dist = (position + buffer_size - ti->position) % buffer_size;
+ if (dist >= period_size ||
+ position / period_size != ti->position / period_size) {
+ apcm->interrupt(apcm);
+ ti->position = position;
+ }
+ /* Add extra HZ*5/1000 to avoid overrun issue when recording
+ * at 8kHz in 8-bit format or at 88kHz in 24-bit format. */
+ interval = ((period_size - (position % period_size))
+ * HZ + (runtime->rate - 1)) / runtime->rate + HZ * 5 / 1000;
+ spin_lock_irqsave(&ti->lock, flags);
+ if (ti->running)
+ mod_timer(&ti->timer, jiffies + interval);
+ spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_init(struct ct_timer_instance *ti)
+{
+ timer_setup(&ti->timer, ct_systimer_callback, 0);
+}
+
+static void ct_systimer_start(struct ct_timer_instance *ti)
+{
+ struct snd_pcm_runtime *runtime = ti->substream->runtime;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ti->lock, flags);
+ ti->running = 1;
+ mod_timer(&ti->timer,
+ jiffies + (runtime->period_size * HZ +
+ (runtime->rate - 1)) / runtime->rate);
+ spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_stop(struct ct_timer_instance *ti)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ti->lock, flags);
+ ti->running = 0;
+ del_timer(&ti->timer);
+ spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_prepare(struct ct_timer_instance *ti)
+{
+ ct_systimer_stop(ti);
+ try_to_del_timer_sync(&ti->timer);
+}
+
+#define ct_systimer_free ct_systimer_prepare
+
+static const struct ct_timer_ops ct_systimer_ops = {
+ .init = ct_systimer_init,
+ .free_instance = ct_systimer_free,
+ .prepare = ct_systimer_prepare,
+ .start = ct_systimer_start,
+ .stop = ct_systimer_stop,
+};
+
+
+/*
+ * Handling multiple streams using a global emu20k1 timer irq
+ */
+
+#define CT_TIMER_FREQ 48000
+#define MIN_TICKS 1
+#define MAX_TICKS ((1 << 13) - 1)
+
+static void ct_xfitimer_irq_rearm(struct ct_timer *atimer, int ticks)
+{
+ struct hw *hw = atimer->atc->hw;
+ if (ticks > MAX_TICKS)
+ ticks = MAX_TICKS;
+ hw->set_timer_tick(hw, ticks);
+ if (!atimer->running)
+ hw->set_timer_irq(hw, 1);
+ atimer->running = 1;
+}
+
+static void ct_xfitimer_irq_stop(struct ct_timer *atimer)
+{
+ if (atimer->running) {
+ struct hw *hw = atimer->atc->hw;
+ hw->set_timer_irq(hw, 0);
+ hw->set_timer_tick(hw, 0);
+ atimer->running = 0;
+ }
+}
+
+static inline unsigned int ct_xfitimer_get_wc(struct ct_timer *atimer)
+{
+ struct hw *hw = atimer->atc->hw;
+ return hw->get_wc(hw);
+}
+
+/*
+ * reprogram the timer interval;
+ * checks the running instance list and determines the next timer interval.
+ * also updates the each stream position, returns the number of streams
+ * to call snd_pcm_period_elapsed() appropriately
+ *
+ * call this inside the lock and irq disabled
+ */
+static int ct_xfitimer_reprogram(struct ct_timer *atimer, int can_update)
+{
+ struct ct_timer_instance *ti;
+ unsigned int min_intr = (unsigned int)-1;
+ int updates = 0;
+ unsigned int wc, diff;
+
+ if (list_empty(&atimer->running_head)) {
+ ct_xfitimer_irq_stop(atimer);
+ atimer->reprogram = 0; /* clear flag */
+ return 0;
+ }
+
+ wc = ct_xfitimer_get_wc(atimer);
+ diff = wc - atimer->wc;
+ atimer->wc = wc;
+ list_for_each_entry(ti, &atimer->running_head, running_list) {
+ if (ti->frag_count > diff)
+ ti->frag_count -= diff;
+ else {
+ unsigned int pos;
+ unsigned int period_size, rate;
+
+ period_size = ti->substream->runtime->period_size;
+ rate = ti->substream->runtime->rate;
+ pos = ti->substream->ops->pointer(ti->substream);
+ if (pos / period_size != ti->position / period_size) {
+ ti->need_update = 1;
+ ti->position = pos;
+ updates++;
+ }
+ pos %= period_size;
+ pos = period_size - pos;
+ ti->frag_count = div_u64((u64)pos * CT_TIMER_FREQ +
+ rate - 1, rate);
+ }
+ if (ti->need_update && !can_update)
+ min_intr = 0; /* pending to the next irq */
+ if (ti->frag_count < min_intr)
+ min_intr = ti->frag_count;
+ }
+
+ if (min_intr < MIN_TICKS)
+ min_intr = MIN_TICKS;
+ ct_xfitimer_irq_rearm(atimer, min_intr);
+ atimer->reprogram = 0; /* clear flag */
+ return updates;
+}
+
+/* look through the instance list and call period_elapsed if needed */
+static void ct_xfitimer_check_period(struct ct_timer *atimer)
+{
+ struct ct_timer_instance *ti;
+ unsigned long flags;
+
+ spin_lock_irqsave(&atimer->list_lock, flags);
+ list_for_each_entry(ti, &atimer->instance_head, instance_list) {
+ if (ti->running && ti->need_update) {
+ ti->need_update = 0;
+ ti->apcm->interrupt(ti->apcm);
+ }
+ }
+ spin_unlock_irqrestore(&atimer->list_lock, flags);
+}
+
+/* Handle timer-interrupt */
+static void ct_xfitimer_callback(struct ct_timer *atimer)
+{
+ int update;
+ unsigned long flags;
+
+ spin_lock_irqsave(&atimer->lock, flags);
+ atimer->irq_handling = 1;
+ do {
+ update = ct_xfitimer_reprogram(atimer, 1);
+ spin_unlock(&atimer->lock);
+ if (update)
+ ct_xfitimer_check_period(atimer);
+ spin_lock(&atimer->lock);
+ } while (atimer->reprogram);
+ atimer->irq_handling = 0;
+ spin_unlock_irqrestore(&atimer->lock, flags);
+}
+
+static void ct_xfitimer_prepare(struct ct_timer_instance *ti)
+{
+ ti->frag_count = ti->substream->runtime->period_size;
+ ti->running = 0;
+ ti->need_update = 0;
+}
+
+
+/* start/stop the timer */
+static void ct_xfitimer_update(struct ct_timer *atimer)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&atimer->lock, flags);
+ if (atimer->irq_handling) {
+ /* reached from IRQ handler; let it handle later */
+ atimer->reprogram = 1;
+ spin_unlock_irqrestore(&atimer->lock, flags);
+ return;
+ }
+
+ ct_xfitimer_irq_stop(atimer);
+ ct_xfitimer_reprogram(atimer, 0);
+ spin_unlock_irqrestore(&atimer->lock, flags);
+}
+
+static void ct_xfitimer_start(struct ct_timer_instance *ti)
+{
+ struct ct_timer *atimer = ti->timer_base;
+ unsigned long flags;
+
+ spin_lock_irqsave(&atimer->lock, flags);
+ if (list_empty(&ti->running_list))
+ atimer->wc = ct_xfitimer_get_wc(atimer);
+ ti->running = 1;
+ ti->need_update = 0;
+ list_add(&ti->running_list, &atimer->running_head);
+ spin_unlock_irqrestore(&atimer->lock, flags);
+ ct_xfitimer_update(atimer);
+}
+
+static void ct_xfitimer_stop(struct ct_timer_instance *ti)
+{
+ struct ct_timer *atimer = ti->timer_base;
+ unsigned long flags;
+
+ spin_lock_irqsave(&atimer->lock, flags);
+ list_del_init(&ti->running_list);
+ ti->running = 0;
+ spin_unlock_irqrestore(&atimer->lock, flags);
+ ct_xfitimer_update(atimer);
+}
+
+static void ct_xfitimer_free_global(struct ct_timer *atimer)
+{
+ ct_xfitimer_irq_stop(atimer);
+}
+
+static const struct ct_timer_ops ct_xfitimer_ops = {
+ .prepare = ct_xfitimer_prepare,
+ .start = ct_xfitimer_start,
+ .stop = ct_xfitimer_stop,
+ .interrupt = ct_xfitimer_callback,
+ .free_global = ct_xfitimer_free_global,
+};
+
+/*
+ * timer instance
+ */
+
+struct ct_timer_instance *
+ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm)
+{
+ struct ct_timer_instance *ti;
+
+ ti = kzalloc(sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return NULL;
+ spin_lock_init(&ti->lock);
+ INIT_LIST_HEAD(&ti->instance_list);
+ INIT_LIST_HEAD(&ti->running_list);
+ ti->timer_base = atimer;
+ ti->apcm = apcm;
+ ti->substream = apcm->substream;
+ if (atimer->ops->init)
+ atimer->ops->init(ti);
+
+ spin_lock_irq(&atimer->list_lock);
+ list_add(&ti->instance_list, &atimer->instance_head);
+ spin_unlock_irq(&atimer->list_lock);
+
+ return ti;
+}
+
+void ct_timer_prepare(struct ct_timer_instance *ti)
+{
+ if (ti->timer_base->ops->prepare)
+ ti->timer_base->ops->prepare(ti);
+ ti->position = 0;
+ ti->running = 0;
+}
+
+void ct_timer_start(struct ct_timer_instance *ti)
+{
+ struct ct_timer *atimer = ti->timer_base;
+ atimer->ops->start(ti);
+}
+
+void ct_timer_stop(struct ct_timer_instance *ti)
+{
+ struct ct_timer *atimer = ti->timer_base;
+ atimer->ops->stop(ti);
+}
+
+void ct_timer_instance_free(struct ct_timer_instance *ti)
+{
+ struct ct_timer *atimer = ti->timer_base;
+
+ atimer->ops->stop(ti); /* to be sure */
+ if (atimer->ops->free_instance)
+ atimer->ops->free_instance(ti);
+
+ spin_lock_irq(&atimer->list_lock);
+ list_del(&ti->instance_list);
+ spin_unlock_irq(&atimer->list_lock);
+
+ kfree(ti);
+}
+
+/*
+ * timer manager
+ */
+
+static void ct_timer_interrupt(void *data, unsigned int status)
+{
+ struct ct_timer *timer = data;
+
+ /* Interval timer interrupt */
+ if ((status & IT_INT) && timer->ops->interrupt)
+ timer->ops->interrupt(timer);
+}
+
+struct ct_timer *ct_timer_new(struct ct_atc *atc)
+{
+ struct ct_timer *atimer;
+ struct hw *hw;
+
+ atimer = kzalloc(sizeof(*atimer), GFP_KERNEL);
+ if (!atimer)
+ return NULL;
+ spin_lock_init(&atimer->lock);
+ spin_lock_init(&atimer->list_lock);
+ INIT_LIST_HEAD(&atimer->instance_head);
+ INIT_LIST_HEAD(&atimer->running_head);
+ atimer->atc = atc;
+ hw = atc->hw;
+ if (!use_system_timer && hw->set_timer_irq) {
+ dev_info(atc->card->dev, "Use xfi-native timer\n");
+ atimer->ops = &ct_xfitimer_ops;
+ hw->irq_callback_data = atimer;
+ hw->irq_callback = ct_timer_interrupt;
+ } else {
+ dev_info(atc->card->dev, "Use system timer\n");
+ atimer->ops = &ct_systimer_ops;
+ }
+ return atimer;
+}
+
+void ct_timer_free(struct ct_timer *atimer)
+{
+ struct hw *hw = atimer->atc->hw;
+ hw->irq_callback = NULL;
+ if (atimer->ops->free_global)
+ atimer->ops->free_global(atimer);
+ kfree(atimer);
+}
+
diff --git a/sound/pci/ctxfi/cttimer.h b/sound/pci/ctxfi/cttimer.h
new file mode 100644
index 000000000..9c5cb403b
--- /dev/null
+++ b/sound/pci/ctxfi/cttimer.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Timer handling
+ */
+
+#ifndef __CTTIMER_H
+#define __CTTIMER_H
+
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+
+struct snd_pcm_substream;
+struct ct_atc;
+struct ct_atc_pcm;
+
+struct ct_timer;
+struct ct_timer_instance;
+
+struct ct_timer *ct_timer_new(struct ct_atc *atc);
+void ct_timer_free(struct ct_timer *atimer);
+
+struct ct_timer_instance *
+ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm);
+void ct_timer_instance_free(struct ct_timer_instance *ti);
+void ct_timer_start(struct ct_timer_instance *ti);
+void ct_timer_stop(struct ct_timer_instance *ti);
+void ct_timer_prepare(struct ct_timer_instance *ti);
+
+#endif /* __CTTIMER_H */
diff --git a/sound/pci/ctxfi/ctvmem.c b/sound/pci/ctxfi/ctvmem.c
new file mode 100644
index 000000000..7a805c4a5
--- /dev/null
+++ b/sound/pci/ctxfi/ctvmem.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctvmem.c
+ *
+ * @Brief
+ * This file contains the implementation of virtual memory management object
+ * for card device.
+ *
+ * @Author Liu Chun
+ * @Date Apr 1 2008
+ */
+
+#include "ctvmem.h"
+#include "ctatc.h"
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <sound/pcm.h>
+
+#define CT_PTES_PER_PAGE (CT_PAGE_SIZE / sizeof(void *))
+#define CT_ADDRS_PER_PAGE (CT_PTES_PER_PAGE * CT_PAGE_SIZE)
+
+/* *
+ * Find or create vm block based on requested @size.
+ * @size must be page aligned.
+ * */
+static struct ct_vm_block *
+get_vm_block(struct ct_vm *vm, unsigned int size, struct ct_atc *atc)
+{
+ struct ct_vm_block *block = NULL, *entry;
+ struct list_head *pos;
+
+ size = CT_PAGE_ALIGN(size);
+ if (size > vm->size) {
+ dev_err(atc->card->dev,
+ "Fail! No sufficient device virtual memory space available!\n");
+ return NULL;
+ }
+
+ mutex_lock(&vm->lock);
+ list_for_each(pos, &vm->unused) {
+ entry = list_entry(pos, struct ct_vm_block, list);
+ if (entry->size >= size)
+ break; /* found a block that is big enough */
+ }
+ if (pos == &vm->unused)
+ goto out;
+
+ if (entry->size == size) {
+ /* Move the vm node from unused list to used list directly */
+ list_move(&entry->list, &vm->used);
+ vm->size -= size;
+ block = entry;
+ goto out;
+ }
+
+ block = kzalloc(sizeof(*block), GFP_KERNEL);
+ if (!block)
+ goto out;
+
+ block->addr = entry->addr;
+ block->size = size;
+ list_add(&block->list, &vm->used);
+ entry->addr += size;
+ entry->size -= size;
+ vm->size -= size;
+
+ out:
+ mutex_unlock(&vm->lock);
+ return block;
+}
+
+static void put_vm_block(struct ct_vm *vm, struct ct_vm_block *block)
+{
+ struct ct_vm_block *entry, *pre_ent;
+ struct list_head *pos, *pre;
+
+ block->size = CT_PAGE_ALIGN(block->size);
+
+ mutex_lock(&vm->lock);
+ list_del(&block->list);
+ vm->size += block->size;
+
+ list_for_each(pos, &vm->unused) {
+ entry = list_entry(pos, struct ct_vm_block, list);
+ if (entry->addr >= (block->addr + block->size))
+ break; /* found a position */
+ }
+ if (pos == &vm->unused) {
+ list_add_tail(&block->list, &vm->unused);
+ entry = block;
+ } else {
+ if ((block->addr + block->size) == entry->addr) {
+ entry->addr = block->addr;
+ entry->size += block->size;
+ kfree(block);
+ } else {
+ __list_add(&block->list, pos->prev, pos);
+ entry = block;
+ }
+ }
+
+ pos = &entry->list;
+ pre = pos->prev;
+ while (pre != &vm->unused) {
+ entry = list_entry(pos, struct ct_vm_block, list);
+ pre_ent = list_entry(pre, struct ct_vm_block, list);
+ if ((pre_ent->addr + pre_ent->size) > entry->addr)
+ break;
+
+ pre_ent->size += entry->size;
+ list_del(pos);
+ kfree(entry);
+ pos = pre;
+ pre = pos->prev;
+ }
+ mutex_unlock(&vm->lock);
+}
+
+/* Map host addr (kmalloced/vmalloced) to device logical addr. */
+static struct ct_vm_block *
+ct_vm_map(struct ct_vm *vm, struct snd_pcm_substream *substream, int size)
+{
+ struct ct_vm_block *block;
+ unsigned int pte_start;
+ unsigned i, pages;
+ unsigned long *ptp;
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+
+ block = get_vm_block(vm, size, atc);
+ if (block == NULL) {
+ dev_err(atc->card->dev,
+ "No virtual memory block that is big enough to allocate!\n");
+ return NULL;
+ }
+
+ ptp = (unsigned long *)vm->ptp[0].area;
+ pte_start = (block->addr >> CT_PAGE_SHIFT);
+ pages = block->size >> CT_PAGE_SHIFT;
+ for (i = 0; i < pages; i++) {
+ unsigned long addr;
+ addr = snd_pcm_sgbuf_get_addr(substream, i << CT_PAGE_SHIFT);
+ ptp[pte_start + i] = addr;
+ }
+
+ block->size = size;
+ return block;
+}
+
+static void ct_vm_unmap(struct ct_vm *vm, struct ct_vm_block *block)
+{
+ /* do unmapping */
+ put_vm_block(vm, block);
+}
+
+/* *
+ * return the host physical addr of the @index-th device
+ * page table page on success, or ~0UL on failure.
+ * The first returned ~0UL indicates the termination.
+ * */
+static dma_addr_t
+ct_get_ptp_phys(struct ct_vm *vm, int index)
+{
+ return (index >= CT_PTP_NUM) ? ~0UL : vm->ptp[index].addr;
+}
+
+int ct_vm_create(struct ct_vm **rvm, struct pci_dev *pci)
+{
+ struct ct_vm *vm;
+ struct ct_vm_block *block;
+ int i, err = 0;
+
+ *rvm = NULL;
+
+ vm = kzalloc(sizeof(*vm), GFP_KERNEL);
+ if (!vm)
+ return -ENOMEM;
+
+ mutex_init(&vm->lock);
+
+ /* Allocate page table pages */
+ for (i = 0; i < CT_PTP_NUM; i++) {
+ err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+ &pci->dev,
+ PAGE_SIZE, &vm->ptp[i]);
+ if (err < 0)
+ break;
+ }
+ if (err < 0) {
+ /* no page table pages are allocated */
+ ct_vm_destroy(vm);
+ return -ENOMEM;
+ }
+ vm->size = CT_ADDRS_PER_PAGE * i;
+ vm->map = ct_vm_map;
+ vm->unmap = ct_vm_unmap;
+ vm->get_ptp_phys = ct_get_ptp_phys;
+ INIT_LIST_HEAD(&vm->unused);
+ INIT_LIST_HEAD(&vm->used);
+ block = kzalloc(sizeof(*block), GFP_KERNEL);
+ if (NULL != block) {
+ block->addr = 0;
+ block->size = vm->size;
+ list_add(&block->list, &vm->unused);
+ }
+
+ *rvm = vm;
+ return 0;
+}
+
+/* The caller must ensure no mapping pages are being used
+ * by hardware before calling this function */
+void ct_vm_destroy(struct ct_vm *vm)
+{
+ int i;
+ struct list_head *pos;
+ struct ct_vm_block *entry;
+
+ /* free used and unused list nodes */
+ while (!list_empty(&vm->used)) {
+ pos = vm->used.next;
+ list_del(pos);
+ entry = list_entry(pos, struct ct_vm_block, list);
+ kfree(entry);
+ }
+ while (!list_empty(&vm->unused)) {
+ pos = vm->unused.next;
+ list_del(pos);
+ entry = list_entry(pos, struct ct_vm_block, list);
+ kfree(entry);
+ }
+
+ /* free allocated page table pages */
+ for (i = 0; i < CT_PTP_NUM; i++)
+ snd_dma_free_pages(&vm->ptp[i]);
+
+ vm->size = 0;
+
+ kfree(vm);
+}
diff --git a/sound/pci/ctxfi/ctvmem.h b/sound/pci/ctxfi/ctvmem.h
new file mode 100644
index 000000000..da54cbcdb
--- /dev/null
+++ b/sound/pci/ctxfi/ctvmem.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * @File ctvmem.h
+ *
+ * @Brief
+ * This file contains the definition of virtual memory management object
+ * for card device.
+ *
+ * @Author Liu Chun
+ * @Date Mar 28 2008
+ */
+
+#ifndef CTVMEM_H
+#define CTVMEM_H
+
+#define CT_PTP_NUM 4 /* num of device page table pages */
+
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <sound/memalloc.h>
+
+/* The chip can handle the page table of 4k pages
+ * (emu20k1 can handle even 8k pages, but we don't use it right now)
+ */
+#define CT_PAGE_SIZE 4096
+#define CT_PAGE_SHIFT 12
+#define CT_PAGE_MASK (~(PAGE_SIZE - 1))
+#define CT_PAGE_ALIGN(addr) ALIGN(addr, CT_PAGE_SIZE)
+
+struct ct_vm_block {
+ unsigned int addr; /* starting logical addr of this block */
+ unsigned int size; /* size of this device virtual mem block */
+ struct list_head list;
+};
+
+struct snd_pcm_substream;
+
+/* Virtual memory management object for card device */
+struct ct_vm {
+ struct snd_dma_buffer ptp[CT_PTP_NUM]; /* Device page table pages */
+ unsigned int size; /* Available addr space in bytes */
+ struct list_head unused; /* List of unused blocks */
+ struct list_head used; /* List of used blocks */
+ struct mutex lock;
+
+ /* Map host addr (kmalloced/vmalloced) to device logical addr. */
+ struct ct_vm_block *(*map)(struct ct_vm *, struct snd_pcm_substream *,
+ int size);
+ /* Unmap device logical addr area. */
+ void (*unmap)(struct ct_vm *, struct ct_vm_block *block);
+ dma_addr_t (*get_ptp_phys)(struct ct_vm *vm, int index);
+};
+
+int ct_vm_create(struct ct_vm **rvm, struct pci_dev *pci);
+void ct_vm_destroy(struct ct_vm *vm);
+
+#endif /* CTVMEM_H */
diff --git a/sound/pci/ctxfi/xfi.c b/sound/pci/ctxfi/xfi.c
new file mode 100644
index 000000000..713d36ea4
--- /dev/null
+++ b/sound/pci/ctxfi/xfi.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * xfi linux driver.
+ *
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/pci_ids.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "ctatc.h"
+#include "cthardware.h"
+
+MODULE_AUTHOR("Creative Technology Ltd");
+MODULE_DESCRIPTION("X-Fi driver version 1.03");
+MODULE_LICENSE("GPL v2");
+
+static unsigned int reference_rate = 48000;
+static unsigned int multiple = 2;
+MODULE_PARM_DESC(reference_rate, "Reference rate (default=48000)");
+module_param(reference_rate, uint, 0444);
+MODULE_PARM_DESC(multiple, "Rate multiplier (default=2)");
+module_param(multiple, uint, 0444);
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+static unsigned int subsystem[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Creative X-Fi driver");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Creative X-Fi driver");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Creative X-Fi driver");
+module_param_array(subsystem, int, NULL, 0444);
+MODULE_PARM_DESC(subsystem, "Override subsystem ID for Creative X-Fi driver");
+
+static const struct pci_device_id ct_pci_dev_ids[] = {
+ /* only X-Fi is supported, so... */
+ { PCI_DEVICE(PCI_VENDOR_ID_CREATIVE, PCI_DEVICE_ID_CREATIVE_20K1),
+ .driver_data = ATC20K1,
+ },
+ { PCI_DEVICE(PCI_VENDOR_ID_CREATIVE, PCI_DEVICE_ID_CREATIVE_20K2),
+ .driver_data = ATC20K2,
+ },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, ct_pci_dev_ids);
+
+static int
+ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+ static int dev;
+ struct snd_card *card;
+ struct ct_atc *atc;
+ int err;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+ err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err)
+ return err;
+ if ((reference_rate != 48000) && (reference_rate != 44100)) {
+ dev_err(card->dev,
+ "Invalid reference_rate value %u!!!\n",
+ reference_rate);
+ dev_err(card->dev,
+ "The valid values for reference_rate are 48000 and 44100, Value 48000 is assumed.\n");
+ reference_rate = 48000;
+ }
+ if ((multiple != 1) && (multiple != 2) && (multiple != 4)) {
+ dev_err(card->dev, "Invalid multiple value %u!!!\n",
+ multiple);
+ dev_err(card->dev,
+ "The valid values for multiple are 1, 2 and 4, Value 2 is assumed.\n");
+ multiple = 2;
+ }
+ err = ct_atc_create(card, pci, reference_rate, multiple,
+ pci_id->driver_data, subsystem[dev], &atc);
+ if (err < 0)
+ goto error;
+
+ card->private_data = atc;
+
+ /* Create alsa devices supported by this card */
+ err = ct_atc_create_alsa_devs(atc);
+ if (err < 0)
+ goto error;
+
+ strcpy(card->driver, "SB-XFi");
+ strcpy(card->shortname, "Creative X-Fi");
+ snprintf(card->longname, sizeof(card->longname), "%s %s %s",
+ card->shortname, atc->chip_name, atc->model_name);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto error;
+
+ pci_set_drvdata(pci, card);
+ dev++;
+
+ return 0;
+
+error:
+ snd_card_free(card);
+ return err;
+}
+
+static void ct_card_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ct_card_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct ct_atc *atc = card->private_data;
+
+ return atc->suspend(atc);
+}
+
+static int ct_card_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct ct_atc *atc = card->private_data;
+
+ return atc->resume(atc);
+}
+
+static SIMPLE_DEV_PM_OPS(ct_card_pm, ct_card_suspend, ct_card_resume);
+#define CT_CARD_PM_OPS &ct_card_pm
+#else
+#define CT_CARD_PM_OPS NULL
+#endif
+
+static struct pci_driver ct_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = ct_pci_dev_ids,
+ .probe = ct_card_probe,
+ .remove = ct_card_remove,
+ .driver = {
+ .pm = CT_CARD_PM_OPS,
+ },
+};
+
+module_pci_driver(ct_driver);