18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Driver for S3 SonicVibes soundcard
48c2ecf20Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci *  BUGS:
78c2ecf20Sopenharmony_ci *    It looks like 86c617 rev 3 doesn't supports DDMA buffers above 16MB?
88c2ecf20Sopenharmony_ci *    Driver sometimes hangs... Nobody knows why at this moment...
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/delay.h>
128c2ecf20Sopenharmony_ci#include <linux/init.h>
138c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
148c2ecf20Sopenharmony_ci#include <linux/pci.h>
158c2ecf20Sopenharmony_ci#include <linux/slab.h>
168c2ecf20Sopenharmony_ci#include <linux/gameport.h>
178c2ecf20Sopenharmony_ci#include <linux/module.h>
188c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h>
198c2ecf20Sopenharmony_ci#include <linux/io.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <sound/core.h>
228c2ecf20Sopenharmony_ci#include <sound/pcm.h>
238c2ecf20Sopenharmony_ci#include <sound/info.h>
248c2ecf20Sopenharmony_ci#include <sound/control.h>
258c2ecf20Sopenharmony_ci#include <sound/mpu401.h>
268c2ecf20Sopenharmony_ci#include <sound/opl3.h>
278c2ecf20Sopenharmony_ci#include <sound/initval.h>
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
308c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("S3 SonicVibes PCI");
318c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
328c2ecf20Sopenharmony_ciMODULE_SUPPORTED_DEVICE("{{S3,SonicVibes PCI}}");
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#if IS_REACHABLE(CONFIG_GAMEPORT)
358c2ecf20Sopenharmony_ci#define SUPPORT_JOYSTICK 1
368c2ecf20Sopenharmony_ci#endif
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistatic int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
398c2ecf20Sopenharmony_cistatic char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
408c2ecf20Sopenharmony_cistatic bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
418c2ecf20Sopenharmony_cistatic bool reverb[SNDRV_CARDS];
428c2ecf20Sopenharmony_cistatic bool mge[SNDRV_CARDS];
438c2ecf20Sopenharmony_cistatic unsigned int dmaio = 0x7a00;	/* DDMA i/o address */
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cimodule_param_array(index, int, NULL, 0444);
468c2ecf20Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for S3 SonicVibes soundcard.");
478c2ecf20Sopenharmony_cimodule_param_array(id, charp, NULL, 0444);
488c2ecf20Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for S3 SonicVibes soundcard.");
498c2ecf20Sopenharmony_cimodule_param_array(enable, bool, NULL, 0444);
508c2ecf20Sopenharmony_ciMODULE_PARM_DESC(enable, "Enable S3 SonicVibes soundcard.");
518c2ecf20Sopenharmony_cimodule_param_array(reverb, bool, NULL, 0444);
528c2ecf20Sopenharmony_ciMODULE_PARM_DESC(reverb, "Enable reverb (SRAM is present) for S3 SonicVibes soundcard.");
538c2ecf20Sopenharmony_cimodule_param_array(mge, bool, NULL, 0444);
548c2ecf20Sopenharmony_ciMODULE_PARM_DESC(mge, "MIC Gain Enable for S3 SonicVibes soundcard.");
558c2ecf20Sopenharmony_cimodule_param_hw(dmaio, uint, ioport, 0444);
568c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dmaio, "DDMA i/o base address for S3 SonicVibes soundcard.");
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci/*
598c2ecf20Sopenharmony_ci * Enhanced port direct registers
608c2ecf20Sopenharmony_ci */
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci#define SV_REG(sonic, x) ((sonic)->enh_port + SV_REG_##x)
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci#define SV_REG_CONTROL	0x00	/* R/W: CODEC/Mixer control register */
658c2ecf20Sopenharmony_ci#define   SV_ENHANCED	  0x01	/* audio mode select - enhanced mode */
668c2ecf20Sopenharmony_ci#define   SV_TEST	  0x02	/* test bit */
678c2ecf20Sopenharmony_ci#define   SV_REVERB	  0x04	/* reverb enable */
688c2ecf20Sopenharmony_ci#define   SV_WAVETABLE	  0x08	/* wavetable active / FM active if not set */
698c2ecf20Sopenharmony_ci#define   SV_INTA	  0x20	/* INTA driving - should be always 1 */
708c2ecf20Sopenharmony_ci#define   SV_RESET	  0x80	/* reset chip */
718c2ecf20Sopenharmony_ci#define SV_REG_IRQMASK	0x01	/* R/W: CODEC/Mixer interrupt mask register */
728c2ecf20Sopenharmony_ci#define   SV_DMAA_MASK	  0x01	/* mask DMA-A interrupt */
738c2ecf20Sopenharmony_ci#define   SV_DMAC_MASK	  0x04	/* mask DMA-C interrupt */
748c2ecf20Sopenharmony_ci#define   SV_SPEC_MASK	  0x08	/* special interrupt mask - should be always masked */
758c2ecf20Sopenharmony_ci#define   SV_UD_MASK	  0x40	/* Up/Down button interrupt mask */
768c2ecf20Sopenharmony_ci#define   SV_MIDI_MASK	  0x80	/* mask MIDI interrupt */
778c2ecf20Sopenharmony_ci#define SV_REG_STATUS	0x02	/* R/O: CODEC/Mixer status register */
788c2ecf20Sopenharmony_ci#define   SV_DMAA_IRQ	  0x01	/* DMA-A interrupt */
798c2ecf20Sopenharmony_ci#define   SV_DMAC_IRQ	  0x04	/* DMA-C interrupt */
808c2ecf20Sopenharmony_ci#define   SV_SPEC_IRQ	  0x08	/* special interrupt */
818c2ecf20Sopenharmony_ci#define   SV_UD_IRQ	  0x40	/* Up/Down interrupt */
828c2ecf20Sopenharmony_ci#define   SV_MIDI_IRQ	  0x80	/* MIDI interrupt */
838c2ecf20Sopenharmony_ci#define SV_REG_INDEX	0x04	/* R/W: CODEC/Mixer index address register */
848c2ecf20Sopenharmony_ci#define   SV_MCE          0x40	/* mode change enable */
858c2ecf20Sopenharmony_ci#define   SV_TRD	  0x80	/* DMA transfer request disabled */
868c2ecf20Sopenharmony_ci#define SV_REG_DATA	0x05	/* R/W: CODEC/Mixer index data register */
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci/*
898c2ecf20Sopenharmony_ci * Enhanced port indirect registers
908c2ecf20Sopenharmony_ci */
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci#define SV_IREG_LEFT_ADC	0x00	/* Left ADC Input Control */
938c2ecf20Sopenharmony_ci#define SV_IREG_RIGHT_ADC	0x01	/* Right ADC Input Control */
948c2ecf20Sopenharmony_ci#define SV_IREG_LEFT_AUX1	0x02	/* Left AUX1 Input Control */
958c2ecf20Sopenharmony_ci#define SV_IREG_RIGHT_AUX1	0x03	/* Right AUX1 Input Control */
968c2ecf20Sopenharmony_ci#define SV_IREG_LEFT_CD		0x04	/* Left CD Input Control */
978c2ecf20Sopenharmony_ci#define SV_IREG_RIGHT_CD	0x05	/* Right CD Input Control */
988c2ecf20Sopenharmony_ci#define SV_IREG_LEFT_LINE	0x06	/* Left Line Input Control */
998c2ecf20Sopenharmony_ci#define SV_IREG_RIGHT_LINE	0x07	/* Right Line Input Control */
1008c2ecf20Sopenharmony_ci#define SV_IREG_MIC		0x08	/* MIC Input Control */
1018c2ecf20Sopenharmony_ci#define SV_IREG_GAME_PORT	0x09	/* Game Port Control */
1028c2ecf20Sopenharmony_ci#define SV_IREG_LEFT_SYNTH	0x0a	/* Left Synth Input Control */
1038c2ecf20Sopenharmony_ci#define SV_IREG_RIGHT_SYNTH	0x0b	/* Right Synth Input Control */
1048c2ecf20Sopenharmony_ci#define SV_IREG_LEFT_AUX2	0x0c	/* Left AUX2 Input Control */
1058c2ecf20Sopenharmony_ci#define SV_IREG_RIGHT_AUX2	0x0d	/* Right AUX2 Input Control */
1068c2ecf20Sopenharmony_ci#define SV_IREG_LEFT_ANALOG	0x0e	/* Left Analog Mixer Output Control */
1078c2ecf20Sopenharmony_ci#define SV_IREG_RIGHT_ANALOG	0x0f	/* Right Analog Mixer Output Control */
1088c2ecf20Sopenharmony_ci#define SV_IREG_LEFT_PCM	0x10	/* Left PCM Input Control */
1098c2ecf20Sopenharmony_ci#define SV_IREG_RIGHT_PCM	0x11	/* Right PCM Input Control */
1108c2ecf20Sopenharmony_ci#define SV_IREG_DMA_DATA_FMT	0x12	/* DMA Data Format */
1118c2ecf20Sopenharmony_ci#define SV_IREG_PC_ENABLE	0x13	/* Playback/Capture Enable Register */
1128c2ecf20Sopenharmony_ci#define SV_IREG_UD_BUTTON	0x14	/* Up/Down Button Register */
1138c2ecf20Sopenharmony_ci#define SV_IREG_REVISION	0x15	/* Revision */
1148c2ecf20Sopenharmony_ci#define SV_IREG_ADC_OUTPUT_CTRL	0x16	/* ADC Output Control */
1158c2ecf20Sopenharmony_ci#define SV_IREG_DMA_A_UPPER	0x18	/* DMA A Upper Base Count */
1168c2ecf20Sopenharmony_ci#define SV_IREG_DMA_A_LOWER	0x19	/* DMA A Lower Base Count */
1178c2ecf20Sopenharmony_ci#define SV_IREG_DMA_C_UPPER	0x1c	/* DMA C Upper Base Count */
1188c2ecf20Sopenharmony_ci#define SV_IREG_DMA_C_LOWER	0x1d	/* DMA C Lower Base Count */
1198c2ecf20Sopenharmony_ci#define SV_IREG_PCM_RATE_LOW	0x1e	/* PCM Sampling Rate Low Byte */
1208c2ecf20Sopenharmony_ci#define SV_IREG_PCM_RATE_HIGH	0x1f	/* PCM Sampling Rate High Byte */
1218c2ecf20Sopenharmony_ci#define SV_IREG_SYNTH_RATE_LOW	0x20	/* Synthesizer Sampling Rate Low Byte */
1228c2ecf20Sopenharmony_ci#define SV_IREG_SYNTH_RATE_HIGH 0x21	/* Synthesizer Sampling Rate High Byte */
1238c2ecf20Sopenharmony_ci#define SV_IREG_ADC_CLOCK	0x22	/* ADC Clock Source Selection */
1248c2ecf20Sopenharmony_ci#define SV_IREG_ADC_ALT_RATE	0x23	/* ADC Alternative Sampling Rate Selection */
1258c2ecf20Sopenharmony_ci#define SV_IREG_ADC_PLL_M	0x24	/* ADC PLL M Register */
1268c2ecf20Sopenharmony_ci#define SV_IREG_ADC_PLL_N	0x25	/* ADC PLL N Register */
1278c2ecf20Sopenharmony_ci#define SV_IREG_SYNTH_PLL_M	0x26	/* Synthesizer PLL M Register */
1288c2ecf20Sopenharmony_ci#define SV_IREG_SYNTH_PLL_N	0x27	/* Synthesizer PLL N Register */
1298c2ecf20Sopenharmony_ci#define SV_IREG_MPU401		0x2a	/* MPU-401 UART Operation */
1308c2ecf20Sopenharmony_ci#define SV_IREG_DRIVE_CTRL	0x2b	/* Drive Control */
1318c2ecf20Sopenharmony_ci#define SV_IREG_SRS_SPACE	0x2c	/* SRS Space Control */
1328c2ecf20Sopenharmony_ci#define SV_IREG_SRS_CENTER	0x2d	/* SRS Center Control */
1338c2ecf20Sopenharmony_ci#define SV_IREG_WAVE_SOURCE	0x2e	/* Wavetable Sample Source Select */
1348c2ecf20Sopenharmony_ci#define SV_IREG_ANALOG_POWER	0x30	/* Analog Power Down Control */
1358c2ecf20Sopenharmony_ci#define SV_IREG_DIGITAL_POWER	0x31	/* Digital Power Down Control */
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci#define SV_IREG_ADC_PLL		SV_IREG_ADC_PLL_M
1388c2ecf20Sopenharmony_ci#define SV_IREG_SYNTH_PLL	SV_IREG_SYNTH_PLL_M
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci/*
1418c2ecf20Sopenharmony_ci *  DMA registers
1428c2ecf20Sopenharmony_ci */
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci#define SV_DMA_ADDR0		0x00
1458c2ecf20Sopenharmony_ci#define SV_DMA_ADDR1		0x01
1468c2ecf20Sopenharmony_ci#define SV_DMA_ADDR2		0x02
1478c2ecf20Sopenharmony_ci#define SV_DMA_ADDR3		0x03
1488c2ecf20Sopenharmony_ci#define SV_DMA_COUNT0		0x04
1498c2ecf20Sopenharmony_ci#define SV_DMA_COUNT1		0x05
1508c2ecf20Sopenharmony_ci#define SV_DMA_COUNT2		0x06
1518c2ecf20Sopenharmony_ci#define SV_DMA_MODE		0x0b
1528c2ecf20Sopenharmony_ci#define SV_DMA_RESET		0x0d
1538c2ecf20Sopenharmony_ci#define SV_DMA_MASK		0x0f
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci/*
1568c2ecf20Sopenharmony_ci *  Record sources
1578c2ecf20Sopenharmony_ci */
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci#define SV_RECSRC_RESERVED	(0x00<<5)
1608c2ecf20Sopenharmony_ci#define SV_RECSRC_CD		(0x01<<5)
1618c2ecf20Sopenharmony_ci#define SV_RECSRC_DAC		(0x02<<5)
1628c2ecf20Sopenharmony_ci#define SV_RECSRC_AUX2		(0x03<<5)
1638c2ecf20Sopenharmony_ci#define SV_RECSRC_LINE		(0x04<<5)
1648c2ecf20Sopenharmony_ci#define SV_RECSRC_AUX1		(0x05<<5)
1658c2ecf20Sopenharmony_ci#define SV_RECSRC_MIC		(0x06<<5)
1668c2ecf20Sopenharmony_ci#define SV_RECSRC_OUT		(0x07<<5)
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci/*
1698c2ecf20Sopenharmony_ci *  constants
1708c2ecf20Sopenharmony_ci */
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci#define SV_FULLRATE		48000
1738c2ecf20Sopenharmony_ci#define SV_REFFREQUENCY		24576000
1748c2ecf20Sopenharmony_ci#define SV_ADCMULT		512
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci#define SV_MODE_PLAY		1
1778c2ecf20Sopenharmony_ci#define SV_MODE_CAPTURE		2
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci/*
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci */
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_cistruct sonicvibes {
1848c2ecf20Sopenharmony_ci	unsigned long dma1size;
1858c2ecf20Sopenharmony_ci	unsigned long dma2size;
1868c2ecf20Sopenharmony_ci	int irq;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	unsigned long sb_port;
1898c2ecf20Sopenharmony_ci	unsigned long enh_port;
1908c2ecf20Sopenharmony_ci	unsigned long synth_port;
1918c2ecf20Sopenharmony_ci	unsigned long midi_port;
1928c2ecf20Sopenharmony_ci	unsigned long game_port;
1938c2ecf20Sopenharmony_ci	unsigned int dmaa_port;
1948c2ecf20Sopenharmony_ci	struct resource *res_dmaa;
1958c2ecf20Sopenharmony_ci	unsigned int dmac_port;
1968c2ecf20Sopenharmony_ci	struct resource *res_dmac;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	unsigned char enable;
1998c2ecf20Sopenharmony_ci	unsigned char irqmask;
2008c2ecf20Sopenharmony_ci	unsigned char revision;
2018c2ecf20Sopenharmony_ci	unsigned char format;
2028c2ecf20Sopenharmony_ci	unsigned char srs_space;
2038c2ecf20Sopenharmony_ci	unsigned char srs_center;
2048c2ecf20Sopenharmony_ci	unsigned char mpu_switch;
2058c2ecf20Sopenharmony_ci	unsigned char wave_source;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	unsigned int mode;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	struct pci_dev *pci;
2108c2ecf20Sopenharmony_ci	struct snd_card *card;
2118c2ecf20Sopenharmony_ci	struct snd_pcm *pcm;
2128c2ecf20Sopenharmony_ci	struct snd_pcm_substream *playback_substream;
2138c2ecf20Sopenharmony_ci	struct snd_pcm_substream *capture_substream;
2148c2ecf20Sopenharmony_ci	struct snd_rawmidi *rmidi;
2158c2ecf20Sopenharmony_ci	struct snd_hwdep *fmsynth;	/* S3FM */
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	spinlock_t reg_lock;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	unsigned int p_dma_size;
2208c2ecf20Sopenharmony_ci	unsigned int c_dma_size;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	struct snd_kcontrol *master_mute;
2238c2ecf20Sopenharmony_ci	struct snd_kcontrol *master_volume;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci#ifdef SUPPORT_JOYSTICK
2268c2ecf20Sopenharmony_ci	struct gameport *gameport;
2278c2ecf20Sopenharmony_ci#endif
2288c2ecf20Sopenharmony_ci};
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_cistatic const struct pci_device_id snd_sonic_ids[] = {
2318c2ecf20Sopenharmony_ci	{ PCI_VDEVICE(S3, 0xca00), 0, },
2328c2ecf20Sopenharmony_ci        { 0, }
2338c2ecf20Sopenharmony_ci};
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, snd_sonic_ids);
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_cistatic const struct snd_ratden sonicvibes_adc_clock = {
2388c2ecf20Sopenharmony_ci	.num_min = 4000 * 65536,
2398c2ecf20Sopenharmony_ci	.num_max = 48000UL * 65536,
2408c2ecf20Sopenharmony_ci	.num_step = 1,
2418c2ecf20Sopenharmony_ci	.den = 65536,
2428c2ecf20Sopenharmony_ci};
2438c2ecf20Sopenharmony_cistatic const struct snd_pcm_hw_constraint_ratdens snd_sonicvibes_hw_constraints_adc_clock = {
2448c2ecf20Sopenharmony_ci	.nrats = 1,
2458c2ecf20Sopenharmony_ci	.rats = &sonicvibes_adc_clock,
2468c2ecf20Sopenharmony_ci};
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci/*
2498c2ecf20Sopenharmony_ci *  common I/O routines
2508c2ecf20Sopenharmony_ci */
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_cistatic inline void snd_sonicvibes_setdmaa(struct sonicvibes * sonic,
2538c2ecf20Sopenharmony_ci					  unsigned int addr,
2548c2ecf20Sopenharmony_ci					  unsigned int count)
2558c2ecf20Sopenharmony_ci{
2568c2ecf20Sopenharmony_ci	count--;
2578c2ecf20Sopenharmony_ci	outl(addr, sonic->dmaa_port + SV_DMA_ADDR0);
2588c2ecf20Sopenharmony_ci	outl(count, sonic->dmaa_port + SV_DMA_COUNT0);
2598c2ecf20Sopenharmony_ci	outb(0x18, sonic->dmaa_port + SV_DMA_MODE);
2608c2ecf20Sopenharmony_ci#if 0
2618c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev, "program dmaa: addr = 0x%x, paddr = 0x%x\n",
2628c2ecf20Sopenharmony_ci	       addr, inl(sonic->dmaa_port + SV_DMA_ADDR0));
2638c2ecf20Sopenharmony_ci#endif
2648c2ecf20Sopenharmony_ci}
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_cistatic inline void snd_sonicvibes_setdmac(struct sonicvibes * sonic,
2678c2ecf20Sopenharmony_ci					  unsigned int addr,
2688c2ecf20Sopenharmony_ci					  unsigned int count)
2698c2ecf20Sopenharmony_ci{
2708c2ecf20Sopenharmony_ci	/* note: dmac is working in word mode!!! */
2718c2ecf20Sopenharmony_ci	count >>= 1;
2728c2ecf20Sopenharmony_ci	count--;
2738c2ecf20Sopenharmony_ci	outl(addr, sonic->dmac_port + SV_DMA_ADDR0);
2748c2ecf20Sopenharmony_ci	outl(count, sonic->dmac_port + SV_DMA_COUNT0);
2758c2ecf20Sopenharmony_ci	outb(0x14, sonic->dmac_port + SV_DMA_MODE);
2768c2ecf20Sopenharmony_ci#if 0
2778c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev, "program dmac: addr = 0x%x, paddr = 0x%x\n",
2788c2ecf20Sopenharmony_ci	       addr, inl(sonic->dmac_port + SV_DMA_ADDR0));
2798c2ecf20Sopenharmony_ci#endif
2808c2ecf20Sopenharmony_ci}
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_cistatic inline unsigned int snd_sonicvibes_getdmaa(struct sonicvibes * sonic)
2838c2ecf20Sopenharmony_ci{
2848c2ecf20Sopenharmony_ci	return (inl(sonic->dmaa_port + SV_DMA_COUNT0) & 0xffffff) + 1;
2858c2ecf20Sopenharmony_ci}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_cistatic inline unsigned int snd_sonicvibes_getdmac(struct sonicvibes * sonic)
2888c2ecf20Sopenharmony_ci{
2898c2ecf20Sopenharmony_ci	/* note: dmac is working in word mode!!! */
2908c2ecf20Sopenharmony_ci	return ((inl(sonic->dmac_port + SV_DMA_COUNT0) & 0xffffff) + 1) << 1;
2918c2ecf20Sopenharmony_ci}
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_cistatic void snd_sonicvibes_out1(struct sonicvibes * sonic,
2948c2ecf20Sopenharmony_ci				unsigned char reg,
2958c2ecf20Sopenharmony_ci				unsigned char value)
2968c2ecf20Sopenharmony_ci{
2978c2ecf20Sopenharmony_ci	outb(reg, SV_REG(sonic, INDEX));
2988c2ecf20Sopenharmony_ci	udelay(10);
2998c2ecf20Sopenharmony_ci	outb(value, SV_REG(sonic, DATA));
3008c2ecf20Sopenharmony_ci	udelay(10);
3018c2ecf20Sopenharmony_ci}
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_cistatic void snd_sonicvibes_out(struct sonicvibes * sonic,
3048c2ecf20Sopenharmony_ci			       unsigned char reg,
3058c2ecf20Sopenharmony_ci			       unsigned char value)
3068c2ecf20Sopenharmony_ci{
3078c2ecf20Sopenharmony_ci	unsigned long flags;
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	spin_lock_irqsave(&sonic->reg_lock, flags);
3108c2ecf20Sopenharmony_ci	outb(reg, SV_REG(sonic, INDEX));
3118c2ecf20Sopenharmony_ci	udelay(10);
3128c2ecf20Sopenharmony_ci	outb(value, SV_REG(sonic, DATA));
3138c2ecf20Sopenharmony_ci	udelay(10);
3148c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&sonic->reg_lock, flags);
3158c2ecf20Sopenharmony_ci}
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_cistatic unsigned char snd_sonicvibes_in1(struct sonicvibes * sonic, unsigned char reg)
3188c2ecf20Sopenharmony_ci{
3198c2ecf20Sopenharmony_ci	unsigned char value;
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	outb(reg, SV_REG(sonic, INDEX));
3228c2ecf20Sopenharmony_ci	udelay(10);
3238c2ecf20Sopenharmony_ci	value = inb(SV_REG(sonic, DATA));
3248c2ecf20Sopenharmony_ci	udelay(10);
3258c2ecf20Sopenharmony_ci	return value;
3268c2ecf20Sopenharmony_ci}
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_cistatic unsigned char snd_sonicvibes_in(struct sonicvibes * sonic, unsigned char reg)
3298c2ecf20Sopenharmony_ci{
3308c2ecf20Sopenharmony_ci	unsigned long flags;
3318c2ecf20Sopenharmony_ci	unsigned char value;
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_ci	spin_lock_irqsave(&sonic->reg_lock, flags);
3348c2ecf20Sopenharmony_ci	outb(reg, SV_REG(sonic, INDEX));
3358c2ecf20Sopenharmony_ci	udelay(10);
3368c2ecf20Sopenharmony_ci	value = inb(SV_REG(sonic, DATA));
3378c2ecf20Sopenharmony_ci	udelay(10);
3388c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&sonic->reg_lock, flags);
3398c2ecf20Sopenharmony_ci	return value;
3408c2ecf20Sopenharmony_ci}
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_ci#if 0
3438c2ecf20Sopenharmony_cistatic void snd_sonicvibes_debug(struct sonicvibes * sonic)
3448c2ecf20Sopenharmony_ci{
3458c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3468c2ecf20Sopenharmony_ci		"SV REGS:          INDEX = 0x%02x                   STATUS = 0x%02x\n",
3478c2ecf20Sopenharmony_ci		inb(SV_REG(sonic, INDEX)), inb(SV_REG(sonic, STATUS)));
3488c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3498c2ecf20Sopenharmony_ci		"  0x00: left input      = 0x%02x    0x20: synth rate low  = 0x%02x\n",
3508c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x00), snd_sonicvibes_in(sonic, 0x20));
3518c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3528c2ecf20Sopenharmony_ci		"  0x01: right input     = 0x%02x    0x21: synth rate high = 0x%02x\n",
3538c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x01), snd_sonicvibes_in(sonic, 0x21));
3548c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3558c2ecf20Sopenharmony_ci		"  0x02: left AUX1       = 0x%02x    0x22: ADC clock       = 0x%02x\n",
3568c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x02), snd_sonicvibes_in(sonic, 0x22));
3578c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3588c2ecf20Sopenharmony_ci		"  0x03: right AUX1      = 0x%02x    0x23: ADC alt rate    = 0x%02x\n",
3598c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x03), snd_sonicvibes_in(sonic, 0x23));
3608c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3618c2ecf20Sopenharmony_ci		"  0x04: left CD         = 0x%02x    0x24: ADC pll M       = 0x%02x\n",
3628c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x04), snd_sonicvibes_in(sonic, 0x24));
3638c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3648c2ecf20Sopenharmony_ci		"  0x05: right CD        = 0x%02x    0x25: ADC pll N       = 0x%02x\n",
3658c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x05), snd_sonicvibes_in(sonic, 0x25));
3668c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3678c2ecf20Sopenharmony_ci		"  0x06: left line       = 0x%02x    0x26: Synth pll M     = 0x%02x\n",
3688c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x06), snd_sonicvibes_in(sonic, 0x26));
3698c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3708c2ecf20Sopenharmony_ci		"  0x07: right line      = 0x%02x    0x27: Synth pll N     = 0x%02x\n",
3718c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x07), snd_sonicvibes_in(sonic, 0x27));
3728c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3738c2ecf20Sopenharmony_ci		"  0x08: MIC             = 0x%02x    0x28: ---             = 0x%02x\n",
3748c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x08), snd_sonicvibes_in(sonic, 0x28));
3758c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3768c2ecf20Sopenharmony_ci		"  0x09: Game port       = 0x%02x    0x29: ---             = 0x%02x\n",
3778c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x09), snd_sonicvibes_in(sonic, 0x29));
3788c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3798c2ecf20Sopenharmony_ci		"  0x0a: left synth      = 0x%02x    0x2a: MPU401          = 0x%02x\n",
3808c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x0a), snd_sonicvibes_in(sonic, 0x2a));
3818c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3828c2ecf20Sopenharmony_ci		"  0x0b: right synth     = 0x%02x    0x2b: drive ctrl      = 0x%02x\n",
3838c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x0b), snd_sonicvibes_in(sonic, 0x2b));
3848c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3858c2ecf20Sopenharmony_ci		"  0x0c: left AUX2       = 0x%02x    0x2c: SRS space       = 0x%02x\n",
3868c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x0c), snd_sonicvibes_in(sonic, 0x2c));
3878c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3888c2ecf20Sopenharmony_ci		"  0x0d: right AUX2      = 0x%02x    0x2d: SRS center      = 0x%02x\n",
3898c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x0d), snd_sonicvibes_in(sonic, 0x2d));
3908c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3918c2ecf20Sopenharmony_ci		"  0x0e: left analog     = 0x%02x    0x2e: wave source     = 0x%02x\n",
3928c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x0e), snd_sonicvibes_in(sonic, 0x2e));
3938c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3948c2ecf20Sopenharmony_ci		"  0x0f: right analog    = 0x%02x    0x2f: ---             = 0x%02x\n",
3958c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x0f), snd_sonicvibes_in(sonic, 0x2f));
3968c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
3978c2ecf20Sopenharmony_ci		"  0x10: left PCM        = 0x%02x    0x30: analog power    = 0x%02x\n",
3988c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x10), snd_sonicvibes_in(sonic, 0x30));
3998c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4008c2ecf20Sopenharmony_ci		"  0x11: right PCM       = 0x%02x    0x31: analog power    = 0x%02x\n",
4018c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x11), snd_sonicvibes_in(sonic, 0x31));
4028c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4038c2ecf20Sopenharmony_ci		"  0x12: DMA data format = 0x%02x    0x32: ---             = 0x%02x\n",
4048c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x12), snd_sonicvibes_in(sonic, 0x32));
4058c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4068c2ecf20Sopenharmony_ci		"  0x13: P/C enable      = 0x%02x    0x33: ---             = 0x%02x\n",
4078c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x13), snd_sonicvibes_in(sonic, 0x33));
4088c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4098c2ecf20Sopenharmony_ci		"  0x14: U/D button      = 0x%02x    0x34: ---             = 0x%02x\n",
4108c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x14), snd_sonicvibes_in(sonic, 0x34));
4118c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4128c2ecf20Sopenharmony_ci		"  0x15: revision        = 0x%02x    0x35: ---             = 0x%02x\n",
4138c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x15), snd_sonicvibes_in(sonic, 0x35));
4148c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4158c2ecf20Sopenharmony_ci		"  0x16: ADC output ctrl = 0x%02x    0x36: ---             = 0x%02x\n",
4168c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x16), snd_sonicvibes_in(sonic, 0x36));
4178c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4188c2ecf20Sopenharmony_ci		"  0x17: ---             = 0x%02x    0x37: ---             = 0x%02x\n",
4198c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x17), snd_sonicvibes_in(sonic, 0x37));
4208c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4218c2ecf20Sopenharmony_ci		"  0x18: DMA A upper cnt = 0x%02x    0x38: ---             = 0x%02x\n",
4228c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x18), snd_sonicvibes_in(sonic, 0x38));
4238c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4248c2ecf20Sopenharmony_ci		"  0x19: DMA A lower cnt = 0x%02x    0x39: ---             = 0x%02x\n",
4258c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x19), snd_sonicvibes_in(sonic, 0x39));
4268c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4278c2ecf20Sopenharmony_ci		"  0x1a: ---             = 0x%02x    0x3a: ---             = 0x%02x\n",
4288c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x1a), snd_sonicvibes_in(sonic, 0x3a));
4298c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4308c2ecf20Sopenharmony_ci		"  0x1b: ---             = 0x%02x    0x3b: ---             = 0x%02x\n",
4318c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x1b), snd_sonicvibes_in(sonic, 0x3b));
4328c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4338c2ecf20Sopenharmony_ci		"  0x1c: DMA C upper cnt = 0x%02x    0x3c: ---             = 0x%02x\n",
4348c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x1c), snd_sonicvibes_in(sonic, 0x3c));
4358c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4368c2ecf20Sopenharmony_ci		"  0x1d: DMA C upper cnt = 0x%02x    0x3d: ---             = 0x%02x\n",
4378c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x1d), snd_sonicvibes_in(sonic, 0x3d));
4388c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4398c2ecf20Sopenharmony_ci		"  0x1e: PCM rate low    = 0x%02x    0x3e: ---             = 0x%02x\n",
4408c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x1e), snd_sonicvibes_in(sonic, 0x3e));
4418c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
4428c2ecf20Sopenharmony_ci		"  0x1f: PCM rate high   = 0x%02x    0x3f: ---             = 0x%02x\n",
4438c2ecf20Sopenharmony_ci		snd_sonicvibes_in(sonic, 0x1f), snd_sonicvibes_in(sonic, 0x3f));
4448c2ecf20Sopenharmony_ci}
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ci#endif
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_cistatic void snd_sonicvibes_setfmt(struct sonicvibes * sonic,
4498c2ecf20Sopenharmony_ci                                  unsigned char mask,
4508c2ecf20Sopenharmony_ci                                  unsigned char value)
4518c2ecf20Sopenharmony_ci{
4528c2ecf20Sopenharmony_ci	unsigned long flags;
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	spin_lock_irqsave(&sonic->reg_lock, flags);
4558c2ecf20Sopenharmony_ci	outb(SV_MCE | SV_IREG_DMA_DATA_FMT, SV_REG(sonic, INDEX));
4568c2ecf20Sopenharmony_ci	if (mask) {
4578c2ecf20Sopenharmony_ci		sonic->format = inb(SV_REG(sonic, DATA));
4588c2ecf20Sopenharmony_ci		udelay(10);
4598c2ecf20Sopenharmony_ci	}
4608c2ecf20Sopenharmony_ci	sonic->format = (sonic->format & mask) | value;
4618c2ecf20Sopenharmony_ci	outb(sonic->format, SV_REG(sonic, DATA));
4628c2ecf20Sopenharmony_ci	udelay(10);
4638c2ecf20Sopenharmony_ci	outb(0, SV_REG(sonic, INDEX));
4648c2ecf20Sopenharmony_ci	udelay(10);
4658c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&sonic->reg_lock, flags);
4668c2ecf20Sopenharmony_ci}
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_cistatic void snd_sonicvibes_pll(unsigned int rate,
4698c2ecf20Sopenharmony_ci			       unsigned int *res_r,
4708c2ecf20Sopenharmony_ci			       unsigned int *res_m,
4718c2ecf20Sopenharmony_ci			       unsigned int *res_n)
4728c2ecf20Sopenharmony_ci{
4738c2ecf20Sopenharmony_ci	unsigned int r, m = 0, n = 0;
4748c2ecf20Sopenharmony_ci	unsigned int xm, xn, xr, xd, metric = ~0U;
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	if (rate < 625000 / SV_ADCMULT)
4778c2ecf20Sopenharmony_ci		rate = 625000 / SV_ADCMULT;
4788c2ecf20Sopenharmony_ci	if (rate > 150000000 / SV_ADCMULT)
4798c2ecf20Sopenharmony_ci		rate = 150000000 / SV_ADCMULT;
4808c2ecf20Sopenharmony_ci	/* slight violation of specs, needed for continuous sampling rates */
4818c2ecf20Sopenharmony_ci	for (r = 0; rate < 75000000 / SV_ADCMULT; r += 0x20, rate <<= 1);
4828c2ecf20Sopenharmony_ci	for (xn = 3; xn < 33; xn++)	/* 35 */
4838c2ecf20Sopenharmony_ci		for (xm = 3; xm < 257; xm++) {
4848c2ecf20Sopenharmony_ci			xr = ((SV_REFFREQUENCY / SV_ADCMULT) * xm) / xn;
4858c2ecf20Sopenharmony_ci			if (xr >= rate)
4868c2ecf20Sopenharmony_ci				xd = xr - rate;
4878c2ecf20Sopenharmony_ci			else
4888c2ecf20Sopenharmony_ci				xd = rate - xr;
4898c2ecf20Sopenharmony_ci			if (xd < metric) {
4908c2ecf20Sopenharmony_ci				metric = xd;
4918c2ecf20Sopenharmony_ci				m = xm - 2;
4928c2ecf20Sopenharmony_ci				n = xn - 2;
4938c2ecf20Sopenharmony_ci			}
4948c2ecf20Sopenharmony_ci		}
4958c2ecf20Sopenharmony_ci	*res_r = r;
4968c2ecf20Sopenharmony_ci	*res_m = m;
4978c2ecf20Sopenharmony_ci	*res_n = n;
4988c2ecf20Sopenharmony_ci#if 0
4998c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
5008c2ecf20Sopenharmony_ci		"metric = %i, xm = %i, xn = %i\n", metric, xm, xn);
5018c2ecf20Sopenharmony_ci	dev_dbg(sonic->card->dev,
5028c2ecf20Sopenharmony_ci		"pll: m = 0x%x, r = 0x%x, n = 0x%x\n", reg, m, r, n);
5038c2ecf20Sopenharmony_ci#endif
5048c2ecf20Sopenharmony_ci}
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_cistatic void snd_sonicvibes_setpll(struct sonicvibes * sonic,
5078c2ecf20Sopenharmony_ci                                  unsigned char reg,
5088c2ecf20Sopenharmony_ci                                  unsigned int rate)
5098c2ecf20Sopenharmony_ci{
5108c2ecf20Sopenharmony_ci	unsigned long flags;
5118c2ecf20Sopenharmony_ci	unsigned int r, m, n;
5128c2ecf20Sopenharmony_ci
5138c2ecf20Sopenharmony_ci	snd_sonicvibes_pll(rate, &r, &m, &n);
5148c2ecf20Sopenharmony_ci	if (sonic != NULL) {
5158c2ecf20Sopenharmony_ci		spin_lock_irqsave(&sonic->reg_lock, flags);
5168c2ecf20Sopenharmony_ci		snd_sonicvibes_out1(sonic, reg, m);
5178c2ecf20Sopenharmony_ci		snd_sonicvibes_out1(sonic, reg + 1, r | n);
5188c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&sonic->reg_lock, flags);
5198c2ecf20Sopenharmony_ci	}
5208c2ecf20Sopenharmony_ci}
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_cistatic void snd_sonicvibes_set_adc_rate(struct sonicvibes * sonic, unsigned int rate)
5238c2ecf20Sopenharmony_ci{
5248c2ecf20Sopenharmony_ci	unsigned long flags;
5258c2ecf20Sopenharmony_ci	unsigned int div;
5268c2ecf20Sopenharmony_ci	unsigned char clock;
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_ci	div = 48000 / rate;
5298c2ecf20Sopenharmony_ci	if (div > 8)
5308c2ecf20Sopenharmony_ci		div = 8;
5318c2ecf20Sopenharmony_ci	if ((48000 / div) == rate) {	/* use the alternate clock */
5328c2ecf20Sopenharmony_ci		clock = 0x10;
5338c2ecf20Sopenharmony_ci	} else {			/* use the PLL source */
5348c2ecf20Sopenharmony_ci		clock = 0x00;
5358c2ecf20Sopenharmony_ci		snd_sonicvibes_setpll(sonic, SV_IREG_ADC_PLL, rate);
5368c2ecf20Sopenharmony_ci	}
5378c2ecf20Sopenharmony_ci	spin_lock_irqsave(&sonic->reg_lock, flags);
5388c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, SV_IREG_ADC_ALT_RATE, (div - 1) << 4);
5398c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, SV_IREG_ADC_CLOCK, clock);
5408c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&sonic->reg_lock, flags);
5418c2ecf20Sopenharmony_ci}
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_cistatic int snd_sonicvibes_hw_constraint_dac_rate(struct snd_pcm_hw_params *params,
5448c2ecf20Sopenharmony_ci						 struct snd_pcm_hw_rule *rule)
5458c2ecf20Sopenharmony_ci{
5468c2ecf20Sopenharmony_ci	unsigned int rate, div, r, m, n;
5478c2ecf20Sopenharmony_ci
5488c2ecf20Sopenharmony_ci	if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min ==
5498c2ecf20Sopenharmony_ci	    hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->max) {
5508c2ecf20Sopenharmony_ci		rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min;
5518c2ecf20Sopenharmony_ci		div = 48000 / rate;
5528c2ecf20Sopenharmony_ci		if (div > 8)
5538c2ecf20Sopenharmony_ci			div = 8;
5548c2ecf20Sopenharmony_ci		if ((48000 / div) == rate) {
5558c2ecf20Sopenharmony_ci			params->rate_num = rate;
5568c2ecf20Sopenharmony_ci			params->rate_den = 1;
5578c2ecf20Sopenharmony_ci		} else {
5588c2ecf20Sopenharmony_ci			snd_sonicvibes_pll(rate, &r, &m, &n);
5598c2ecf20Sopenharmony_ci			snd_BUG_ON(SV_REFFREQUENCY % 16);
5608c2ecf20Sopenharmony_ci			snd_BUG_ON(SV_ADCMULT % 512);
5618c2ecf20Sopenharmony_ci			params->rate_num = (SV_REFFREQUENCY/16) * (n+2) * r;
5628c2ecf20Sopenharmony_ci			params->rate_den = (SV_ADCMULT/512) * (m+2);
5638c2ecf20Sopenharmony_ci		}
5648c2ecf20Sopenharmony_ci	}
5658c2ecf20Sopenharmony_ci	return 0;
5668c2ecf20Sopenharmony_ci}
5678c2ecf20Sopenharmony_ci
5688c2ecf20Sopenharmony_cistatic void snd_sonicvibes_set_dac_rate(struct sonicvibes * sonic, unsigned int rate)
5698c2ecf20Sopenharmony_ci{
5708c2ecf20Sopenharmony_ci	unsigned int div;
5718c2ecf20Sopenharmony_ci	unsigned long flags;
5728c2ecf20Sopenharmony_ci
5738c2ecf20Sopenharmony_ci	div = (rate * 65536 + SV_FULLRATE / 2) / SV_FULLRATE;
5748c2ecf20Sopenharmony_ci	if (div > 65535)
5758c2ecf20Sopenharmony_ci		div = 65535;
5768c2ecf20Sopenharmony_ci	spin_lock_irqsave(&sonic->reg_lock, flags);
5778c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, SV_IREG_PCM_RATE_HIGH, div >> 8);
5788c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, SV_IREG_PCM_RATE_LOW, div);
5798c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&sonic->reg_lock, flags);
5808c2ecf20Sopenharmony_ci}
5818c2ecf20Sopenharmony_ci
5828c2ecf20Sopenharmony_cistatic int snd_sonicvibes_trigger(struct sonicvibes * sonic, int what, int cmd)
5838c2ecf20Sopenharmony_ci{
5848c2ecf20Sopenharmony_ci	int result = 0;
5858c2ecf20Sopenharmony_ci
5868c2ecf20Sopenharmony_ci	spin_lock(&sonic->reg_lock);
5878c2ecf20Sopenharmony_ci	if (cmd == SNDRV_PCM_TRIGGER_START) {
5888c2ecf20Sopenharmony_ci		if (!(sonic->enable & what)) {
5898c2ecf20Sopenharmony_ci			sonic->enable |= what;
5908c2ecf20Sopenharmony_ci			snd_sonicvibes_out1(sonic, SV_IREG_PC_ENABLE, sonic->enable);
5918c2ecf20Sopenharmony_ci		}
5928c2ecf20Sopenharmony_ci	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
5938c2ecf20Sopenharmony_ci		if (sonic->enable & what) {
5948c2ecf20Sopenharmony_ci			sonic->enable &= ~what;
5958c2ecf20Sopenharmony_ci			snd_sonicvibes_out1(sonic, SV_IREG_PC_ENABLE, sonic->enable);
5968c2ecf20Sopenharmony_ci		}
5978c2ecf20Sopenharmony_ci	} else {
5988c2ecf20Sopenharmony_ci		result = -EINVAL;
5998c2ecf20Sopenharmony_ci	}
6008c2ecf20Sopenharmony_ci	spin_unlock(&sonic->reg_lock);
6018c2ecf20Sopenharmony_ci	return result;
6028c2ecf20Sopenharmony_ci}
6038c2ecf20Sopenharmony_ci
6048c2ecf20Sopenharmony_cistatic irqreturn_t snd_sonicvibes_interrupt(int irq, void *dev_id)
6058c2ecf20Sopenharmony_ci{
6068c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = dev_id;
6078c2ecf20Sopenharmony_ci	unsigned char status;
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ci	status = inb(SV_REG(sonic, STATUS));
6108c2ecf20Sopenharmony_ci	if (!(status & (SV_DMAA_IRQ | SV_DMAC_IRQ | SV_MIDI_IRQ)))
6118c2ecf20Sopenharmony_ci		return IRQ_NONE;
6128c2ecf20Sopenharmony_ci	if (status == 0xff) {	/* failure */
6138c2ecf20Sopenharmony_ci		outb(sonic->irqmask = ~0, SV_REG(sonic, IRQMASK));
6148c2ecf20Sopenharmony_ci		dev_err(sonic->card->dev,
6158c2ecf20Sopenharmony_ci			"IRQ failure - interrupts disabled!!\n");
6168c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
6178c2ecf20Sopenharmony_ci	}
6188c2ecf20Sopenharmony_ci	if (sonic->pcm) {
6198c2ecf20Sopenharmony_ci		if (status & SV_DMAA_IRQ)
6208c2ecf20Sopenharmony_ci			snd_pcm_period_elapsed(sonic->playback_substream);
6218c2ecf20Sopenharmony_ci		if (status & SV_DMAC_IRQ)
6228c2ecf20Sopenharmony_ci			snd_pcm_period_elapsed(sonic->capture_substream);
6238c2ecf20Sopenharmony_ci	}
6248c2ecf20Sopenharmony_ci	if (sonic->rmidi) {
6258c2ecf20Sopenharmony_ci		if (status & SV_MIDI_IRQ)
6268c2ecf20Sopenharmony_ci			snd_mpu401_uart_interrupt(irq, sonic->rmidi->private_data);
6278c2ecf20Sopenharmony_ci	}
6288c2ecf20Sopenharmony_ci	if (status & SV_UD_IRQ) {
6298c2ecf20Sopenharmony_ci		unsigned char udreg;
6308c2ecf20Sopenharmony_ci		int vol, oleft, oright, mleft, mright;
6318c2ecf20Sopenharmony_ci
6328c2ecf20Sopenharmony_ci		spin_lock(&sonic->reg_lock);
6338c2ecf20Sopenharmony_ci		udreg = snd_sonicvibes_in1(sonic, SV_IREG_UD_BUTTON);
6348c2ecf20Sopenharmony_ci		vol = udreg & 0x3f;
6358c2ecf20Sopenharmony_ci		if (!(udreg & 0x40))
6368c2ecf20Sopenharmony_ci			vol = -vol;
6378c2ecf20Sopenharmony_ci		oleft = mleft = snd_sonicvibes_in1(sonic, SV_IREG_LEFT_ANALOG);
6388c2ecf20Sopenharmony_ci		oright = mright = snd_sonicvibes_in1(sonic, SV_IREG_RIGHT_ANALOG);
6398c2ecf20Sopenharmony_ci		oleft &= 0x1f;
6408c2ecf20Sopenharmony_ci		oright &= 0x1f;
6418c2ecf20Sopenharmony_ci		oleft += vol;
6428c2ecf20Sopenharmony_ci		if (oleft < 0)
6438c2ecf20Sopenharmony_ci			oleft = 0;
6448c2ecf20Sopenharmony_ci		if (oleft > 0x1f)
6458c2ecf20Sopenharmony_ci			oleft = 0x1f;
6468c2ecf20Sopenharmony_ci		oright += vol;
6478c2ecf20Sopenharmony_ci		if (oright < 0)
6488c2ecf20Sopenharmony_ci			oright = 0;
6498c2ecf20Sopenharmony_ci		if (oright > 0x1f)
6508c2ecf20Sopenharmony_ci			oright = 0x1f;
6518c2ecf20Sopenharmony_ci		if (udreg & 0x80) {
6528c2ecf20Sopenharmony_ci			mleft ^= 0x80;
6538c2ecf20Sopenharmony_ci			mright ^= 0x80;
6548c2ecf20Sopenharmony_ci		}
6558c2ecf20Sopenharmony_ci		oleft |= mleft & 0x80;
6568c2ecf20Sopenharmony_ci		oright |= mright & 0x80;
6578c2ecf20Sopenharmony_ci		snd_sonicvibes_out1(sonic, SV_IREG_LEFT_ANALOG, oleft);
6588c2ecf20Sopenharmony_ci		snd_sonicvibes_out1(sonic, SV_IREG_RIGHT_ANALOG, oright);
6598c2ecf20Sopenharmony_ci		spin_unlock(&sonic->reg_lock);
6608c2ecf20Sopenharmony_ci		snd_ctl_notify(sonic->card, SNDRV_CTL_EVENT_MASK_VALUE, &sonic->master_mute->id);
6618c2ecf20Sopenharmony_ci		snd_ctl_notify(sonic->card, SNDRV_CTL_EVENT_MASK_VALUE, &sonic->master_volume->id);
6628c2ecf20Sopenharmony_ci	}
6638c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
6648c2ecf20Sopenharmony_ci}
6658c2ecf20Sopenharmony_ci
6668c2ecf20Sopenharmony_ci/*
6678c2ecf20Sopenharmony_ci *  PCM part
6688c2ecf20Sopenharmony_ci */
6698c2ecf20Sopenharmony_ci
6708c2ecf20Sopenharmony_cistatic int snd_sonicvibes_playback_trigger(struct snd_pcm_substream *substream,
6718c2ecf20Sopenharmony_ci					   int cmd)
6728c2ecf20Sopenharmony_ci{
6738c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
6748c2ecf20Sopenharmony_ci	return snd_sonicvibes_trigger(sonic, 1, cmd);
6758c2ecf20Sopenharmony_ci}
6768c2ecf20Sopenharmony_ci
6778c2ecf20Sopenharmony_cistatic int snd_sonicvibes_capture_trigger(struct snd_pcm_substream *substream,
6788c2ecf20Sopenharmony_ci					  int cmd)
6798c2ecf20Sopenharmony_ci{
6808c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
6818c2ecf20Sopenharmony_ci	return snd_sonicvibes_trigger(sonic, 2, cmd);
6828c2ecf20Sopenharmony_ci}
6838c2ecf20Sopenharmony_ci
6848c2ecf20Sopenharmony_cistatic int snd_sonicvibes_playback_prepare(struct snd_pcm_substream *substream)
6858c2ecf20Sopenharmony_ci{
6868c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
6878c2ecf20Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
6888c2ecf20Sopenharmony_ci	unsigned char fmt = 0;
6898c2ecf20Sopenharmony_ci	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
6908c2ecf20Sopenharmony_ci	unsigned int count = snd_pcm_lib_period_bytes(substream);
6918c2ecf20Sopenharmony_ci
6928c2ecf20Sopenharmony_ci	sonic->p_dma_size = size;
6938c2ecf20Sopenharmony_ci	count--;
6948c2ecf20Sopenharmony_ci	if (runtime->channels > 1)
6958c2ecf20Sopenharmony_ci		fmt |= 1;
6968c2ecf20Sopenharmony_ci	if (snd_pcm_format_width(runtime->format) == 16)
6978c2ecf20Sopenharmony_ci		fmt |= 2;
6988c2ecf20Sopenharmony_ci	snd_sonicvibes_setfmt(sonic, ~3, fmt);
6998c2ecf20Sopenharmony_ci	snd_sonicvibes_set_dac_rate(sonic, runtime->rate);
7008c2ecf20Sopenharmony_ci	spin_lock_irq(&sonic->reg_lock);
7018c2ecf20Sopenharmony_ci	snd_sonicvibes_setdmaa(sonic, runtime->dma_addr, size);
7028c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, SV_IREG_DMA_A_UPPER, count >> 8);
7038c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, SV_IREG_DMA_A_LOWER, count);
7048c2ecf20Sopenharmony_ci	spin_unlock_irq(&sonic->reg_lock);
7058c2ecf20Sopenharmony_ci	return 0;
7068c2ecf20Sopenharmony_ci}
7078c2ecf20Sopenharmony_ci
7088c2ecf20Sopenharmony_cistatic int snd_sonicvibes_capture_prepare(struct snd_pcm_substream *substream)
7098c2ecf20Sopenharmony_ci{
7108c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
7118c2ecf20Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
7128c2ecf20Sopenharmony_ci	unsigned char fmt = 0;
7138c2ecf20Sopenharmony_ci	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
7148c2ecf20Sopenharmony_ci	unsigned int count = snd_pcm_lib_period_bytes(substream);
7158c2ecf20Sopenharmony_ci
7168c2ecf20Sopenharmony_ci	sonic->c_dma_size = size;
7178c2ecf20Sopenharmony_ci	count >>= 1;
7188c2ecf20Sopenharmony_ci	count--;
7198c2ecf20Sopenharmony_ci	if (runtime->channels > 1)
7208c2ecf20Sopenharmony_ci		fmt |= 0x10;
7218c2ecf20Sopenharmony_ci	if (snd_pcm_format_width(runtime->format) == 16)
7228c2ecf20Sopenharmony_ci		fmt |= 0x20;
7238c2ecf20Sopenharmony_ci	snd_sonicvibes_setfmt(sonic, ~0x30, fmt);
7248c2ecf20Sopenharmony_ci	snd_sonicvibes_set_adc_rate(sonic, runtime->rate);
7258c2ecf20Sopenharmony_ci	spin_lock_irq(&sonic->reg_lock);
7268c2ecf20Sopenharmony_ci	snd_sonicvibes_setdmac(sonic, runtime->dma_addr, size);
7278c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, SV_IREG_DMA_C_UPPER, count >> 8);
7288c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, SV_IREG_DMA_C_LOWER, count);
7298c2ecf20Sopenharmony_ci	spin_unlock_irq(&sonic->reg_lock);
7308c2ecf20Sopenharmony_ci	return 0;
7318c2ecf20Sopenharmony_ci}
7328c2ecf20Sopenharmony_ci
7338c2ecf20Sopenharmony_cistatic snd_pcm_uframes_t snd_sonicvibes_playback_pointer(struct snd_pcm_substream *substream)
7348c2ecf20Sopenharmony_ci{
7358c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
7368c2ecf20Sopenharmony_ci	size_t ptr;
7378c2ecf20Sopenharmony_ci
7388c2ecf20Sopenharmony_ci	if (!(sonic->enable & 1))
7398c2ecf20Sopenharmony_ci		return 0;
7408c2ecf20Sopenharmony_ci	ptr = sonic->p_dma_size - snd_sonicvibes_getdmaa(sonic);
7418c2ecf20Sopenharmony_ci	return bytes_to_frames(substream->runtime, ptr);
7428c2ecf20Sopenharmony_ci}
7438c2ecf20Sopenharmony_ci
7448c2ecf20Sopenharmony_cistatic snd_pcm_uframes_t snd_sonicvibes_capture_pointer(struct snd_pcm_substream *substream)
7458c2ecf20Sopenharmony_ci{
7468c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
7478c2ecf20Sopenharmony_ci	size_t ptr;
7488c2ecf20Sopenharmony_ci	if (!(sonic->enable & 2))
7498c2ecf20Sopenharmony_ci		return 0;
7508c2ecf20Sopenharmony_ci	ptr = sonic->c_dma_size - snd_sonicvibes_getdmac(sonic);
7518c2ecf20Sopenharmony_ci	return bytes_to_frames(substream->runtime, ptr);
7528c2ecf20Sopenharmony_ci}
7538c2ecf20Sopenharmony_ci
7548c2ecf20Sopenharmony_cistatic const struct snd_pcm_hardware snd_sonicvibes_playback =
7558c2ecf20Sopenharmony_ci{
7568c2ecf20Sopenharmony_ci	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
7578c2ecf20Sopenharmony_ci				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
7588c2ecf20Sopenharmony_ci				 SNDRV_PCM_INFO_MMAP_VALID),
7598c2ecf20Sopenharmony_ci	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
7608c2ecf20Sopenharmony_ci	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
7618c2ecf20Sopenharmony_ci	.rate_min =		4000,
7628c2ecf20Sopenharmony_ci	.rate_max =		48000,
7638c2ecf20Sopenharmony_ci	.channels_min =		1,
7648c2ecf20Sopenharmony_ci	.channels_max =		2,
7658c2ecf20Sopenharmony_ci	.buffer_bytes_max =	(128*1024),
7668c2ecf20Sopenharmony_ci	.period_bytes_min =	32,
7678c2ecf20Sopenharmony_ci	.period_bytes_max =	(128*1024),
7688c2ecf20Sopenharmony_ci	.periods_min =		1,
7698c2ecf20Sopenharmony_ci	.periods_max =		1024,
7708c2ecf20Sopenharmony_ci	.fifo_size =		0,
7718c2ecf20Sopenharmony_ci};
7728c2ecf20Sopenharmony_ci
7738c2ecf20Sopenharmony_cistatic const struct snd_pcm_hardware snd_sonicvibes_capture =
7748c2ecf20Sopenharmony_ci{
7758c2ecf20Sopenharmony_ci	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
7768c2ecf20Sopenharmony_ci				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
7778c2ecf20Sopenharmony_ci				 SNDRV_PCM_INFO_MMAP_VALID),
7788c2ecf20Sopenharmony_ci	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
7798c2ecf20Sopenharmony_ci	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
7808c2ecf20Sopenharmony_ci	.rate_min =		4000,
7818c2ecf20Sopenharmony_ci	.rate_max =		48000,
7828c2ecf20Sopenharmony_ci	.channels_min =		1,
7838c2ecf20Sopenharmony_ci	.channels_max =		2,
7848c2ecf20Sopenharmony_ci	.buffer_bytes_max =	(128*1024),
7858c2ecf20Sopenharmony_ci	.period_bytes_min =	32,
7868c2ecf20Sopenharmony_ci	.period_bytes_max =	(128*1024),
7878c2ecf20Sopenharmony_ci	.periods_min =		1,
7888c2ecf20Sopenharmony_ci	.periods_max =		1024,
7898c2ecf20Sopenharmony_ci	.fifo_size =		0,
7908c2ecf20Sopenharmony_ci};
7918c2ecf20Sopenharmony_ci
7928c2ecf20Sopenharmony_cistatic int snd_sonicvibes_playback_open(struct snd_pcm_substream *substream)
7938c2ecf20Sopenharmony_ci{
7948c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
7958c2ecf20Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
7968c2ecf20Sopenharmony_ci
7978c2ecf20Sopenharmony_ci	sonic->mode |= SV_MODE_PLAY;
7988c2ecf20Sopenharmony_ci	sonic->playback_substream = substream;
7998c2ecf20Sopenharmony_ci	runtime->hw = snd_sonicvibes_playback;
8008c2ecf20Sopenharmony_ci	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, snd_sonicvibes_hw_constraint_dac_rate, NULL, SNDRV_PCM_HW_PARAM_RATE, -1);
8018c2ecf20Sopenharmony_ci	return 0;
8028c2ecf20Sopenharmony_ci}
8038c2ecf20Sopenharmony_ci
8048c2ecf20Sopenharmony_cistatic int snd_sonicvibes_capture_open(struct snd_pcm_substream *substream)
8058c2ecf20Sopenharmony_ci{
8068c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
8078c2ecf20Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
8088c2ecf20Sopenharmony_ci
8098c2ecf20Sopenharmony_ci	sonic->mode |= SV_MODE_CAPTURE;
8108c2ecf20Sopenharmony_ci	sonic->capture_substream = substream;
8118c2ecf20Sopenharmony_ci	runtime->hw = snd_sonicvibes_capture;
8128c2ecf20Sopenharmony_ci	snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
8138c2ecf20Sopenharmony_ci				      &snd_sonicvibes_hw_constraints_adc_clock);
8148c2ecf20Sopenharmony_ci	return 0;
8158c2ecf20Sopenharmony_ci}
8168c2ecf20Sopenharmony_ci
8178c2ecf20Sopenharmony_cistatic int snd_sonicvibes_playback_close(struct snd_pcm_substream *substream)
8188c2ecf20Sopenharmony_ci{
8198c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
8208c2ecf20Sopenharmony_ci
8218c2ecf20Sopenharmony_ci	sonic->playback_substream = NULL;
8228c2ecf20Sopenharmony_ci	sonic->mode &= ~SV_MODE_PLAY;
8238c2ecf20Sopenharmony_ci	return 0;
8248c2ecf20Sopenharmony_ci}
8258c2ecf20Sopenharmony_ci
8268c2ecf20Sopenharmony_cistatic int snd_sonicvibes_capture_close(struct snd_pcm_substream *substream)
8278c2ecf20Sopenharmony_ci{
8288c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
8298c2ecf20Sopenharmony_ci
8308c2ecf20Sopenharmony_ci	sonic->capture_substream = NULL;
8318c2ecf20Sopenharmony_ci	sonic->mode &= ~SV_MODE_CAPTURE;
8328c2ecf20Sopenharmony_ci	return 0;
8338c2ecf20Sopenharmony_ci}
8348c2ecf20Sopenharmony_ci
8358c2ecf20Sopenharmony_cistatic const struct snd_pcm_ops snd_sonicvibes_playback_ops = {
8368c2ecf20Sopenharmony_ci	.open =		snd_sonicvibes_playback_open,
8378c2ecf20Sopenharmony_ci	.close =	snd_sonicvibes_playback_close,
8388c2ecf20Sopenharmony_ci	.prepare =	snd_sonicvibes_playback_prepare,
8398c2ecf20Sopenharmony_ci	.trigger =	snd_sonicvibes_playback_trigger,
8408c2ecf20Sopenharmony_ci	.pointer =	snd_sonicvibes_playback_pointer,
8418c2ecf20Sopenharmony_ci};
8428c2ecf20Sopenharmony_ci
8438c2ecf20Sopenharmony_cistatic const struct snd_pcm_ops snd_sonicvibes_capture_ops = {
8448c2ecf20Sopenharmony_ci	.open =		snd_sonicvibes_capture_open,
8458c2ecf20Sopenharmony_ci	.close =	snd_sonicvibes_capture_close,
8468c2ecf20Sopenharmony_ci	.prepare =	snd_sonicvibes_capture_prepare,
8478c2ecf20Sopenharmony_ci	.trigger =	snd_sonicvibes_capture_trigger,
8488c2ecf20Sopenharmony_ci	.pointer =	snd_sonicvibes_capture_pointer,
8498c2ecf20Sopenharmony_ci};
8508c2ecf20Sopenharmony_ci
8518c2ecf20Sopenharmony_cistatic int snd_sonicvibes_pcm(struct sonicvibes *sonic, int device)
8528c2ecf20Sopenharmony_ci{
8538c2ecf20Sopenharmony_ci	struct snd_pcm *pcm;
8548c2ecf20Sopenharmony_ci	int err;
8558c2ecf20Sopenharmony_ci
8568c2ecf20Sopenharmony_ci	if ((err = snd_pcm_new(sonic->card, "s3_86c617", device, 1, 1, &pcm)) < 0)
8578c2ecf20Sopenharmony_ci		return err;
8588c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!pcm))
8598c2ecf20Sopenharmony_ci		return -EINVAL;
8608c2ecf20Sopenharmony_ci
8618c2ecf20Sopenharmony_ci	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sonicvibes_playback_ops);
8628c2ecf20Sopenharmony_ci	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sonicvibes_capture_ops);
8638c2ecf20Sopenharmony_ci
8648c2ecf20Sopenharmony_ci	pcm->private_data = sonic;
8658c2ecf20Sopenharmony_ci	pcm->info_flags = 0;
8668c2ecf20Sopenharmony_ci	strcpy(pcm->name, "S3 SonicVibes");
8678c2ecf20Sopenharmony_ci	sonic->pcm = pcm;
8688c2ecf20Sopenharmony_ci
8698c2ecf20Sopenharmony_ci	snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
8708c2ecf20Sopenharmony_ci				       &sonic->pci->dev, 64*1024, 128*1024);
8718c2ecf20Sopenharmony_ci
8728c2ecf20Sopenharmony_ci	return 0;
8738c2ecf20Sopenharmony_ci}
8748c2ecf20Sopenharmony_ci
8758c2ecf20Sopenharmony_ci/*
8768c2ecf20Sopenharmony_ci *  Mixer part
8778c2ecf20Sopenharmony_ci */
8788c2ecf20Sopenharmony_ci
8798c2ecf20Sopenharmony_ci#define SONICVIBES_MUX(xname, xindex) \
8808c2ecf20Sopenharmony_ci{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
8818c2ecf20Sopenharmony_ci  .info = snd_sonicvibes_info_mux, \
8828c2ecf20Sopenharmony_ci  .get = snd_sonicvibes_get_mux, .put = snd_sonicvibes_put_mux }
8838c2ecf20Sopenharmony_ci
8848c2ecf20Sopenharmony_cistatic int snd_sonicvibes_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
8858c2ecf20Sopenharmony_ci{
8868c2ecf20Sopenharmony_ci	static const char * const texts[7] = {
8878c2ecf20Sopenharmony_ci		"CD", "PCM", "Aux1", "Line", "Aux0", "Mic", "Mix"
8888c2ecf20Sopenharmony_ci	};
8898c2ecf20Sopenharmony_ci
8908c2ecf20Sopenharmony_ci	return snd_ctl_enum_info(uinfo, 2, 7, texts);
8918c2ecf20Sopenharmony_ci}
8928c2ecf20Sopenharmony_ci
8938c2ecf20Sopenharmony_cistatic int snd_sonicvibes_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
8948c2ecf20Sopenharmony_ci{
8958c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
8968c2ecf20Sopenharmony_ci
8978c2ecf20Sopenharmony_ci	spin_lock_irq(&sonic->reg_lock);
8988c2ecf20Sopenharmony_ci	ucontrol->value.enumerated.item[0] = ((snd_sonicvibes_in1(sonic, SV_IREG_LEFT_ADC) & SV_RECSRC_OUT) >> 5) - 1;
8998c2ecf20Sopenharmony_ci	ucontrol->value.enumerated.item[1] = ((snd_sonicvibes_in1(sonic, SV_IREG_RIGHT_ADC) & SV_RECSRC_OUT) >> 5) - 1;
9008c2ecf20Sopenharmony_ci	spin_unlock_irq(&sonic->reg_lock);
9018c2ecf20Sopenharmony_ci	return 0;
9028c2ecf20Sopenharmony_ci}
9038c2ecf20Sopenharmony_ci
9048c2ecf20Sopenharmony_cistatic int snd_sonicvibes_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
9058c2ecf20Sopenharmony_ci{
9068c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
9078c2ecf20Sopenharmony_ci	unsigned short left, right, oval1, oval2;
9088c2ecf20Sopenharmony_ci	int change;
9098c2ecf20Sopenharmony_ci
9108c2ecf20Sopenharmony_ci	if (ucontrol->value.enumerated.item[0] >= 7 ||
9118c2ecf20Sopenharmony_ci	    ucontrol->value.enumerated.item[1] >= 7)
9128c2ecf20Sopenharmony_ci		return -EINVAL;
9138c2ecf20Sopenharmony_ci	left = (ucontrol->value.enumerated.item[0] + 1) << 5;
9148c2ecf20Sopenharmony_ci	right = (ucontrol->value.enumerated.item[1] + 1) << 5;
9158c2ecf20Sopenharmony_ci	spin_lock_irq(&sonic->reg_lock);
9168c2ecf20Sopenharmony_ci	oval1 = snd_sonicvibes_in1(sonic, SV_IREG_LEFT_ADC);
9178c2ecf20Sopenharmony_ci	oval2 = snd_sonicvibes_in1(sonic, SV_IREG_RIGHT_ADC);
9188c2ecf20Sopenharmony_ci	left = (oval1 & ~SV_RECSRC_OUT) | left;
9198c2ecf20Sopenharmony_ci	right = (oval2 & ~SV_RECSRC_OUT) | right;
9208c2ecf20Sopenharmony_ci	change = left != oval1 || right != oval2;
9218c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, SV_IREG_LEFT_ADC, left);
9228c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, SV_IREG_RIGHT_ADC, right);
9238c2ecf20Sopenharmony_ci	spin_unlock_irq(&sonic->reg_lock);
9248c2ecf20Sopenharmony_ci	return change;
9258c2ecf20Sopenharmony_ci}
9268c2ecf20Sopenharmony_ci
9278c2ecf20Sopenharmony_ci#define SONICVIBES_SINGLE(xname, xindex, reg, shift, mask, invert) \
9288c2ecf20Sopenharmony_ci{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
9298c2ecf20Sopenharmony_ci  .info = snd_sonicvibes_info_single, \
9308c2ecf20Sopenharmony_ci  .get = snd_sonicvibes_get_single, .put = snd_sonicvibes_put_single, \
9318c2ecf20Sopenharmony_ci  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
9328c2ecf20Sopenharmony_ci
9338c2ecf20Sopenharmony_cistatic int snd_sonicvibes_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
9348c2ecf20Sopenharmony_ci{
9358c2ecf20Sopenharmony_ci	int mask = (kcontrol->private_value >> 16) & 0xff;
9368c2ecf20Sopenharmony_ci
9378c2ecf20Sopenharmony_ci	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
9388c2ecf20Sopenharmony_ci	uinfo->count = 1;
9398c2ecf20Sopenharmony_ci	uinfo->value.integer.min = 0;
9408c2ecf20Sopenharmony_ci	uinfo->value.integer.max = mask;
9418c2ecf20Sopenharmony_ci	return 0;
9428c2ecf20Sopenharmony_ci}
9438c2ecf20Sopenharmony_ci
9448c2ecf20Sopenharmony_cistatic int snd_sonicvibes_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
9458c2ecf20Sopenharmony_ci{
9468c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
9478c2ecf20Sopenharmony_ci	int reg = kcontrol->private_value & 0xff;
9488c2ecf20Sopenharmony_ci	int shift = (kcontrol->private_value >> 8) & 0xff;
9498c2ecf20Sopenharmony_ci	int mask = (kcontrol->private_value >> 16) & 0xff;
9508c2ecf20Sopenharmony_ci	int invert = (kcontrol->private_value >> 24) & 0xff;
9518c2ecf20Sopenharmony_ci
9528c2ecf20Sopenharmony_ci	spin_lock_irq(&sonic->reg_lock);
9538c2ecf20Sopenharmony_ci	ucontrol->value.integer.value[0] = (snd_sonicvibes_in1(sonic, reg)>> shift) & mask;
9548c2ecf20Sopenharmony_ci	spin_unlock_irq(&sonic->reg_lock);
9558c2ecf20Sopenharmony_ci	if (invert)
9568c2ecf20Sopenharmony_ci		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
9578c2ecf20Sopenharmony_ci	return 0;
9588c2ecf20Sopenharmony_ci}
9598c2ecf20Sopenharmony_ci
9608c2ecf20Sopenharmony_cistatic int snd_sonicvibes_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
9618c2ecf20Sopenharmony_ci{
9628c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
9638c2ecf20Sopenharmony_ci	int reg = kcontrol->private_value & 0xff;
9648c2ecf20Sopenharmony_ci	int shift = (kcontrol->private_value >> 8) & 0xff;
9658c2ecf20Sopenharmony_ci	int mask = (kcontrol->private_value >> 16) & 0xff;
9668c2ecf20Sopenharmony_ci	int invert = (kcontrol->private_value >> 24) & 0xff;
9678c2ecf20Sopenharmony_ci	int change;
9688c2ecf20Sopenharmony_ci	unsigned short val, oval;
9698c2ecf20Sopenharmony_ci
9708c2ecf20Sopenharmony_ci	val = (ucontrol->value.integer.value[0] & mask);
9718c2ecf20Sopenharmony_ci	if (invert)
9728c2ecf20Sopenharmony_ci		val = mask - val;
9738c2ecf20Sopenharmony_ci	val <<= shift;
9748c2ecf20Sopenharmony_ci	spin_lock_irq(&sonic->reg_lock);
9758c2ecf20Sopenharmony_ci	oval = snd_sonicvibes_in1(sonic, reg);
9768c2ecf20Sopenharmony_ci	val = (oval & ~(mask << shift)) | val;
9778c2ecf20Sopenharmony_ci	change = val != oval;
9788c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, reg, val);
9798c2ecf20Sopenharmony_ci	spin_unlock_irq(&sonic->reg_lock);
9808c2ecf20Sopenharmony_ci	return change;
9818c2ecf20Sopenharmony_ci}
9828c2ecf20Sopenharmony_ci
9838c2ecf20Sopenharmony_ci#define SONICVIBES_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
9848c2ecf20Sopenharmony_ci{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
9858c2ecf20Sopenharmony_ci  .info = snd_sonicvibes_info_double, \
9868c2ecf20Sopenharmony_ci  .get = snd_sonicvibes_get_double, .put = snd_sonicvibes_put_double, \
9878c2ecf20Sopenharmony_ci  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
9888c2ecf20Sopenharmony_ci
9898c2ecf20Sopenharmony_cistatic int snd_sonicvibes_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
9908c2ecf20Sopenharmony_ci{
9918c2ecf20Sopenharmony_ci	int mask = (kcontrol->private_value >> 24) & 0xff;
9928c2ecf20Sopenharmony_ci
9938c2ecf20Sopenharmony_ci	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
9948c2ecf20Sopenharmony_ci	uinfo->count = 2;
9958c2ecf20Sopenharmony_ci	uinfo->value.integer.min = 0;
9968c2ecf20Sopenharmony_ci	uinfo->value.integer.max = mask;
9978c2ecf20Sopenharmony_ci	return 0;
9988c2ecf20Sopenharmony_ci}
9998c2ecf20Sopenharmony_ci
10008c2ecf20Sopenharmony_cistatic int snd_sonicvibes_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
10018c2ecf20Sopenharmony_ci{
10028c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
10038c2ecf20Sopenharmony_ci	int left_reg = kcontrol->private_value & 0xff;
10048c2ecf20Sopenharmony_ci	int right_reg = (kcontrol->private_value >> 8) & 0xff;
10058c2ecf20Sopenharmony_ci	int shift_left = (kcontrol->private_value >> 16) & 0x07;
10068c2ecf20Sopenharmony_ci	int shift_right = (kcontrol->private_value >> 19) & 0x07;
10078c2ecf20Sopenharmony_ci	int mask = (kcontrol->private_value >> 24) & 0xff;
10088c2ecf20Sopenharmony_ci	int invert = (kcontrol->private_value >> 22) & 1;
10098c2ecf20Sopenharmony_ci
10108c2ecf20Sopenharmony_ci	spin_lock_irq(&sonic->reg_lock);
10118c2ecf20Sopenharmony_ci	ucontrol->value.integer.value[0] = (snd_sonicvibes_in1(sonic, left_reg) >> shift_left) & mask;
10128c2ecf20Sopenharmony_ci	ucontrol->value.integer.value[1] = (snd_sonicvibes_in1(sonic, right_reg) >> shift_right) & mask;
10138c2ecf20Sopenharmony_ci	spin_unlock_irq(&sonic->reg_lock);
10148c2ecf20Sopenharmony_ci	if (invert) {
10158c2ecf20Sopenharmony_ci		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
10168c2ecf20Sopenharmony_ci		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
10178c2ecf20Sopenharmony_ci	}
10188c2ecf20Sopenharmony_ci	return 0;
10198c2ecf20Sopenharmony_ci}
10208c2ecf20Sopenharmony_ci
10218c2ecf20Sopenharmony_cistatic int snd_sonicvibes_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
10228c2ecf20Sopenharmony_ci{
10238c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
10248c2ecf20Sopenharmony_ci	int left_reg = kcontrol->private_value & 0xff;
10258c2ecf20Sopenharmony_ci	int right_reg = (kcontrol->private_value >> 8) & 0xff;
10268c2ecf20Sopenharmony_ci	int shift_left = (kcontrol->private_value >> 16) & 0x07;
10278c2ecf20Sopenharmony_ci	int shift_right = (kcontrol->private_value >> 19) & 0x07;
10288c2ecf20Sopenharmony_ci	int mask = (kcontrol->private_value >> 24) & 0xff;
10298c2ecf20Sopenharmony_ci	int invert = (kcontrol->private_value >> 22) & 1;
10308c2ecf20Sopenharmony_ci	int change;
10318c2ecf20Sopenharmony_ci	unsigned short val1, val2, oval1, oval2;
10328c2ecf20Sopenharmony_ci
10338c2ecf20Sopenharmony_ci	val1 = ucontrol->value.integer.value[0] & mask;
10348c2ecf20Sopenharmony_ci	val2 = ucontrol->value.integer.value[1] & mask;
10358c2ecf20Sopenharmony_ci	if (invert) {
10368c2ecf20Sopenharmony_ci		val1 = mask - val1;
10378c2ecf20Sopenharmony_ci		val2 = mask - val2;
10388c2ecf20Sopenharmony_ci	}
10398c2ecf20Sopenharmony_ci	val1 <<= shift_left;
10408c2ecf20Sopenharmony_ci	val2 <<= shift_right;
10418c2ecf20Sopenharmony_ci	spin_lock_irq(&sonic->reg_lock);
10428c2ecf20Sopenharmony_ci	oval1 = snd_sonicvibes_in1(sonic, left_reg);
10438c2ecf20Sopenharmony_ci	oval2 = snd_sonicvibes_in1(sonic, right_reg);
10448c2ecf20Sopenharmony_ci	val1 = (oval1 & ~(mask << shift_left)) | val1;
10458c2ecf20Sopenharmony_ci	val2 = (oval2 & ~(mask << shift_right)) | val2;
10468c2ecf20Sopenharmony_ci	change = val1 != oval1 || val2 != oval2;
10478c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, left_reg, val1);
10488c2ecf20Sopenharmony_ci	snd_sonicvibes_out1(sonic, right_reg, val2);
10498c2ecf20Sopenharmony_ci	spin_unlock_irq(&sonic->reg_lock);
10508c2ecf20Sopenharmony_ci	return change;
10518c2ecf20Sopenharmony_ci}
10528c2ecf20Sopenharmony_ci
10538c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new snd_sonicvibes_controls[] = {
10548c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Capture Volume", 0, SV_IREG_LEFT_ADC, SV_IREG_RIGHT_ADC, 0, 0, 15, 0),
10558c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Aux Playback Switch", 0, SV_IREG_LEFT_AUX1, SV_IREG_RIGHT_AUX1, 7, 7, 1, 1),
10568c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Aux Playback Volume", 0, SV_IREG_LEFT_AUX1, SV_IREG_RIGHT_AUX1, 0, 0, 31, 1),
10578c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("CD Playback Switch", 0, SV_IREG_LEFT_CD, SV_IREG_RIGHT_CD, 7, 7, 1, 1),
10588c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("CD Playback Volume", 0, SV_IREG_LEFT_CD, SV_IREG_RIGHT_CD, 0, 0, 31, 1),
10598c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Line Playback Switch", 0, SV_IREG_LEFT_LINE, SV_IREG_RIGHT_LINE, 7, 7, 1, 1),
10608c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Line Playback Volume", 0, SV_IREG_LEFT_LINE, SV_IREG_RIGHT_LINE, 0, 0, 31, 1),
10618c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("Mic Playback Switch", 0, SV_IREG_MIC, 7, 1, 1),
10628c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("Mic Playback Volume", 0, SV_IREG_MIC, 0, 15, 1),
10638c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("Mic Boost", 0, SV_IREG_LEFT_ADC, 4, 1, 0),
10648c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Synth Playback Switch", 0, SV_IREG_LEFT_SYNTH, SV_IREG_RIGHT_SYNTH, 7, 7, 1, 1),
10658c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Synth Playback Volume", 0, SV_IREG_LEFT_SYNTH, SV_IREG_RIGHT_SYNTH, 0, 0, 31, 1),
10668c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Aux Playback Switch", 1, SV_IREG_LEFT_AUX2, SV_IREG_RIGHT_AUX2, 7, 7, 1, 1),
10678c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Aux Playback Volume", 1, SV_IREG_LEFT_AUX2, SV_IREG_RIGHT_AUX2, 0, 0, 31, 1),
10688c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Master Playback Switch", 0, SV_IREG_LEFT_ANALOG, SV_IREG_RIGHT_ANALOG, 7, 7, 1, 1),
10698c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("Master Playback Volume", 0, SV_IREG_LEFT_ANALOG, SV_IREG_RIGHT_ANALOG, 0, 0, 31, 1),
10708c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("PCM Playback Switch", 0, SV_IREG_LEFT_PCM, SV_IREG_RIGHT_PCM, 7, 7, 1, 1),
10718c2ecf20Sopenharmony_ciSONICVIBES_DOUBLE("PCM Playback Volume", 0, SV_IREG_LEFT_PCM, SV_IREG_RIGHT_PCM, 0, 0, 63, 1),
10728c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("Loopback Capture Switch", 0, SV_IREG_ADC_OUTPUT_CTRL, 0, 1, 0),
10738c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("Loopback Capture Volume", 0, SV_IREG_ADC_OUTPUT_CTRL, 2, 63, 1),
10748c2ecf20Sopenharmony_ciSONICVIBES_MUX("Capture Source", 0)
10758c2ecf20Sopenharmony_ci};
10768c2ecf20Sopenharmony_ci
10778c2ecf20Sopenharmony_cistatic void snd_sonicvibes_master_free(struct snd_kcontrol *kcontrol)
10788c2ecf20Sopenharmony_ci{
10798c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
10808c2ecf20Sopenharmony_ci	sonic->master_mute = NULL;
10818c2ecf20Sopenharmony_ci	sonic->master_volume = NULL;
10828c2ecf20Sopenharmony_ci}
10838c2ecf20Sopenharmony_ci
10848c2ecf20Sopenharmony_cistatic int snd_sonicvibes_mixer(struct sonicvibes *sonic)
10858c2ecf20Sopenharmony_ci{
10868c2ecf20Sopenharmony_ci	struct snd_card *card;
10878c2ecf20Sopenharmony_ci	struct snd_kcontrol *kctl;
10888c2ecf20Sopenharmony_ci	unsigned int idx;
10898c2ecf20Sopenharmony_ci	int err;
10908c2ecf20Sopenharmony_ci
10918c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!sonic || !sonic->card))
10928c2ecf20Sopenharmony_ci		return -EINVAL;
10938c2ecf20Sopenharmony_ci	card = sonic->card;
10948c2ecf20Sopenharmony_ci	strcpy(card->mixername, "S3 SonicVibes");
10958c2ecf20Sopenharmony_ci
10968c2ecf20Sopenharmony_ci	for (idx = 0; idx < ARRAY_SIZE(snd_sonicvibes_controls); idx++) {
10978c2ecf20Sopenharmony_ci		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_sonicvibes_controls[idx], sonic))) < 0)
10988c2ecf20Sopenharmony_ci			return err;
10998c2ecf20Sopenharmony_ci		switch (idx) {
11008c2ecf20Sopenharmony_ci		case 0:
11018c2ecf20Sopenharmony_ci		case 1: kctl->private_free = snd_sonicvibes_master_free; break;
11028c2ecf20Sopenharmony_ci		}
11038c2ecf20Sopenharmony_ci	}
11048c2ecf20Sopenharmony_ci	return 0;
11058c2ecf20Sopenharmony_ci}
11068c2ecf20Sopenharmony_ci
11078c2ecf20Sopenharmony_ci/*
11088c2ecf20Sopenharmony_ci
11098c2ecf20Sopenharmony_ci */
11108c2ecf20Sopenharmony_ci
11118c2ecf20Sopenharmony_cistatic void snd_sonicvibes_proc_read(struct snd_info_entry *entry,
11128c2ecf20Sopenharmony_ci				     struct snd_info_buffer *buffer)
11138c2ecf20Sopenharmony_ci{
11148c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = entry->private_data;
11158c2ecf20Sopenharmony_ci	unsigned char tmp;
11168c2ecf20Sopenharmony_ci
11178c2ecf20Sopenharmony_ci	tmp = sonic->srs_space & 0x0f;
11188c2ecf20Sopenharmony_ci	snd_iprintf(buffer, "SRS 3D           : %s\n",
11198c2ecf20Sopenharmony_ci		    sonic->srs_space & 0x80 ? "off" : "on");
11208c2ecf20Sopenharmony_ci	snd_iprintf(buffer, "SRS Space        : %s\n",
11218c2ecf20Sopenharmony_ci		    tmp == 0x00 ? "100%" :
11228c2ecf20Sopenharmony_ci		    tmp == 0x01 ? "75%" :
11238c2ecf20Sopenharmony_ci		    tmp == 0x02 ? "50%" :
11248c2ecf20Sopenharmony_ci		    tmp == 0x03 ? "25%" : "0%");
11258c2ecf20Sopenharmony_ci	tmp = sonic->srs_center & 0x0f;
11268c2ecf20Sopenharmony_ci	snd_iprintf(buffer, "SRS Center       : %s\n",
11278c2ecf20Sopenharmony_ci		    tmp == 0x00 ? "100%" :
11288c2ecf20Sopenharmony_ci		    tmp == 0x01 ? "75%" :
11298c2ecf20Sopenharmony_ci		    tmp == 0x02 ? "50%" :
11308c2ecf20Sopenharmony_ci		    tmp == 0x03 ? "25%" : "0%");
11318c2ecf20Sopenharmony_ci	tmp = sonic->wave_source & 0x03;
11328c2ecf20Sopenharmony_ci	snd_iprintf(buffer, "WaveTable Source : %s\n",
11338c2ecf20Sopenharmony_ci		    tmp == 0x00 ? "on-board ROM" :
11348c2ecf20Sopenharmony_ci		    tmp == 0x01 ? "PCI bus" : "on-board ROM + PCI bus");
11358c2ecf20Sopenharmony_ci	tmp = sonic->mpu_switch;
11368c2ecf20Sopenharmony_ci	snd_iprintf(buffer, "Onboard synth    : %s\n", tmp & 0x01 ? "on" : "off");
11378c2ecf20Sopenharmony_ci	snd_iprintf(buffer, "Ext. Rx to synth : %s\n", tmp & 0x02 ? "on" : "off");
11388c2ecf20Sopenharmony_ci	snd_iprintf(buffer, "MIDI to ext. Tx  : %s\n", tmp & 0x04 ? "on" : "off");
11398c2ecf20Sopenharmony_ci}
11408c2ecf20Sopenharmony_ci
11418c2ecf20Sopenharmony_cistatic void snd_sonicvibes_proc_init(struct sonicvibes *sonic)
11428c2ecf20Sopenharmony_ci{
11438c2ecf20Sopenharmony_ci	snd_card_ro_proc_new(sonic->card, "sonicvibes", sonic,
11448c2ecf20Sopenharmony_ci			     snd_sonicvibes_proc_read);
11458c2ecf20Sopenharmony_ci}
11468c2ecf20Sopenharmony_ci
11478c2ecf20Sopenharmony_ci/*
11488c2ecf20Sopenharmony_ci
11498c2ecf20Sopenharmony_ci */
11508c2ecf20Sopenharmony_ci
11518c2ecf20Sopenharmony_ci#ifdef SUPPORT_JOYSTICK
11528c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new snd_sonicvibes_game_control =
11538c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("Joystick Speed", 0, SV_IREG_GAME_PORT, 1, 15, 0);
11548c2ecf20Sopenharmony_ci
11558c2ecf20Sopenharmony_cistatic int snd_sonicvibes_create_gameport(struct sonicvibes *sonic)
11568c2ecf20Sopenharmony_ci{
11578c2ecf20Sopenharmony_ci	struct gameport *gp;
11588c2ecf20Sopenharmony_ci	int err;
11598c2ecf20Sopenharmony_ci
11608c2ecf20Sopenharmony_ci	sonic->gameport = gp = gameport_allocate_port();
11618c2ecf20Sopenharmony_ci	if (!gp) {
11628c2ecf20Sopenharmony_ci		dev_err(sonic->card->dev,
11638c2ecf20Sopenharmony_ci			"sonicvibes: cannot allocate memory for gameport\n");
11648c2ecf20Sopenharmony_ci		return -ENOMEM;
11658c2ecf20Sopenharmony_ci	}
11668c2ecf20Sopenharmony_ci
11678c2ecf20Sopenharmony_ci	gameport_set_name(gp, "SonicVibes Gameport");
11688c2ecf20Sopenharmony_ci	gameport_set_phys(gp, "pci%s/gameport0", pci_name(sonic->pci));
11698c2ecf20Sopenharmony_ci	gameport_set_dev_parent(gp, &sonic->pci->dev);
11708c2ecf20Sopenharmony_ci	gp->io = sonic->game_port;
11718c2ecf20Sopenharmony_ci
11728c2ecf20Sopenharmony_ci	gameport_register_port(gp);
11738c2ecf20Sopenharmony_ci
11748c2ecf20Sopenharmony_ci	err = snd_ctl_add(sonic->card,
11758c2ecf20Sopenharmony_ci		snd_ctl_new1(&snd_sonicvibes_game_control, sonic));
11768c2ecf20Sopenharmony_ci	if (err < 0)
11778c2ecf20Sopenharmony_ci		return err;
11788c2ecf20Sopenharmony_ci
11798c2ecf20Sopenharmony_ci	return 0;
11808c2ecf20Sopenharmony_ci}
11818c2ecf20Sopenharmony_ci
11828c2ecf20Sopenharmony_cistatic void snd_sonicvibes_free_gameport(struct sonicvibes *sonic)
11838c2ecf20Sopenharmony_ci{
11848c2ecf20Sopenharmony_ci	if (sonic->gameport) {
11858c2ecf20Sopenharmony_ci		gameport_unregister_port(sonic->gameport);
11868c2ecf20Sopenharmony_ci		sonic->gameport = NULL;
11878c2ecf20Sopenharmony_ci	}
11888c2ecf20Sopenharmony_ci}
11898c2ecf20Sopenharmony_ci#else
11908c2ecf20Sopenharmony_cistatic inline int snd_sonicvibes_create_gameport(struct sonicvibes *sonic) { return -ENOSYS; }
11918c2ecf20Sopenharmony_cistatic inline void snd_sonicvibes_free_gameport(struct sonicvibes *sonic) { }
11928c2ecf20Sopenharmony_ci#endif
11938c2ecf20Sopenharmony_ci
11948c2ecf20Sopenharmony_cistatic int snd_sonicvibes_free(struct sonicvibes *sonic)
11958c2ecf20Sopenharmony_ci{
11968c2ecf20Sopenharmony_ci	snd_sonicvibes_free_gameport(sonic);
11978c2ecf20Sopenharmony_ci	pci_write_config_dword(sonic->pci, 0x40, sonic->dmaa_port);
11988c2ecf20Sopenharmony_ci	pci_write_config_dword(sonic->pci, 0x48, sonic->dmac_port);
11998c2ecf20Sopenharmony_ci	if (sonic->irq >= 0)
12008c2ecf20Sopenharmony_ci		free_irq(sonic->irq, sonic);
12018c2ecf20Sopenharmony_ci	release_and_free_resource(sonic->res_dmaa);
12028c2ecf20Sopenharmony_ci	release_and_free_resource(sonic->res_dmac);
12038c2ecf20Sopenharmony_ci	pci_release_regions(sonic->pci);
12048c2ecf20Sopenharmony_ci	pci_disable_device(sonic->pci);
12058c2ecf20Sopenharmony_ci	kfree(sonic);
12068c2ecf20Sopenharmony_ci	return 0;
12078c2ecf20Sopenharmony_ci}
12088c2ecf20Sopenharmony_ci
12098c2ecf20Sopenharmony_cistatic int snd_sonicvibes_dev_free(struct snd_device *device)
12108c2ecf20Sopenharmony_ci{
12118c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = device->device_data;
12128c2ecf20Sopenharmony_ci	return snd_sonicvibes_free(sonic);
12138c2ecf20Sopenharmony_ci}
12148c2ecf20Sopenharmony_ci
12158c2ecf20Sopenharmony_cistatic int snd_sonicvibes_create(struct snd_card *card,
12168c2ecf20Sopenharmony_ci				 struct pci_dev *pci,
12178c2ecf20Sopenharmony_ci				 int reverb,
12188c2ecf20Sopenharmony_ci				 int mge,
12198c2ecf20Sopenharmony_ci				 struct sonicvibes **rsonic)
12208c2ecf20Sopenharmony_ci{
12218c2ecf20Sopenharmony_ci	struct sonicvibes *sonic;
12228c2ecf20Sopenharmony_ci	unsigned int dmaa, dmac;
12238c2ecf20Sopenharmony_ci	int err;
12248c2ecf20Sopenharmony_ci	static const struct snd_device_ops ops = {
12258c2ecf20Sopenharmony_ci		.dev_free =	snd_sonicvibes_dev_free,
12268c2ecf20Sopenharmony_ci	};
12278c2ecf20Sopenharmony_ci
12288c2ecf20Sopenharmony_ci	*rsonic = NULL;
12298c2ecf20Sopenharmony_ci	/* enable PCI device */
12308c2ecf20Sopenharmony_ci	if ((err = pci_enable_device(pci)) < 0)
12318c2ecf20Sopenharmony_ci		return err;
12328c2ecf20Sopenharmony_ci	/* check, if we can restrict PCI DMA transfers to 24 bits */
12338c2ecf20Sopenharmony_ci	if (dma_set_mask(&pci->dev, DMA_BIT_MASK(24)) < 0 ||
12348c2ecf20Sopenharmony_ci	    dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(24)) < 0) {
12358c2ecf20Sopenharmony_ci		dev_err(card->dev,
12368c2ecf20Sopenharmony_ci			"architecture does not support 24bit PCI busmaster DMA\n");
12378c2ecf20Sopenharmony_ci		pci_disable_device(pci);
12388c2ecf20Sopenharmony_ci                return -ENXIO;
12398c2ecf20Sopenharmony_ci        }
12408c2ecf20Sopenharmony_ci
12418c2ecf20Sopenharmony_ci	sonic = kzalloc(sizeof(*sonic), GFP_KERNEL);
12428c2ecf20Sopenharmony_ci	if (sonic == NULL) {
12438c2ecf20Sopenharmony_ci		pci_disable_device(pci);
12448c2ecf20Sopenharmony_ci		return -ENOMEM;
12458c2ecf20Sopenharmony_ci	}
12468c2ecf20Sopenharmony_ci	spin_lock_init(&sonic->reg_lock);
12478c2ecf20Sopenharmony_ci	sonic->card = card;
12488c2ecf20Sopenharmony_ci	sonic->pci = pci;
12498c2ecf20Sopenharmony_ci	sonic->irq = -1;
12508c2ecf20Sopenharmony_ci
12518c2ecf20Sopenharmony_ci	if ((err = pci_request_regions(pci, "S3 SonicVibes")) < 0) {
12528c2ecf20Sopenharmony_ci		kfree(sonic);
12538c2ecf20Sopenharmony_ci		pci_disable_device(pci);
12548c2ecf20Sopenharmony_ci		return err;
12558c2ecf20Sopenharmony_ci	}
12568c2ecf20Sopenharmony_ci
12578c2ecf20Sopenharmony_ci	sonic->sb_port = pci_resource_start(pci, 0);
12588c2ecf20Sopenharmony_ci	sonic->enh_port = pci_resource_start(pci, 1);
12598c2ecf20Sopenharmony_ci	sonic->synth_port = pci_resource_start(pci, 2);
12608c2ecf20Sopenharmony_ci	sonic->midi_port = pci_resource_start(pci, 3);
12618c2ecf20Sopenharmony_ci	sonic->game_port = pci_resource_start(pci, 4);
12628c2ecf20Sopenharmony_ci
12638c2ecf20Sopenharmony_ci	if (request_irq(pci->irq, snd_sonicvibes_interrupt, IRQF_SHARED,
12648c2ecf20Sopenharmony_ci			KBUILD_MODNAME, sonic)) {
12658c2ecf20Sopenharmony_ci		dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
12668c2ecf20Sopenharmony_ci		snd_sonicvibes_free(sonic);
12678c2ecf20Sopenharmony_ci		return -EBUSY;
12688c2ecf20Sopenharmony_ci	}
12698c2ecf20Sopenharmony_ci	sonic->irq = pci->irq;
12708c2ecf20Sopenharmony_ci	card->sync_irq = sonic->irq;
12718c2ecf20Sopenharmony_ci
12728c2ecf20Sopenharmony_ci	pci_read_config_dword(pci, 0x40, &dmaa);
12738c2ecf20Sopenharmony_ci	pci_read_config_dword(pci, 0x48, &dmac);
12748c2ecf20Sopenharmony_ci	dmaio &= ~0x0f;
12758c2ecf20Sopenharmony_ci	dmaa &= ~0x0f;
12768c2ecf20Sopenharmony_ci	dmac &= ~0x0f;
12778c2ecf20Sopenharmony_ci	if (!dmaa) {
12788c2ecf20Sopenharmony_ci		dmaa = dmaio;
12798c2ecf20Sopenharmony_ci		dmaio += 0x10;
12808c2ecf20Sopenharmony_ci		dev_info(card->dev,
12818c2ecf20Sopenharmony_ci			 "BIOS did not allocate DDMA channel A i/o, allocated at 0x%x\n",
12828c2ecf20Sopenharmony_ci			 dmaa);
12838c2ecf20Sopenharmony_ci	}
12848c2ecf20Sopenharmony_ci	if (!dmac) {
12858c2ecf20Sopenharmony_ci		dmac = dmaio;
12868c2ecf20Sopenharmony_ci		dmaio += 0x10;
12878c2ecf20Sopenharmony_ci		dev_info(card->dev,
12888c2ecf20Sopenharmony_ci			 "BIOS did not allocate DDMA channel C i/o, allocated at 0x%x\n",
12898c2ecf20Sopenharmony_ci			 dmac);
12908c2ecf20Sopenharmony_ci	}
12918c2ecf20Sopenharmony_ci	pci_write_config_dword(pci, 0x40, dmaa);
12928c2ecf20Sopenharmony_ci	pci_write_config_dword(pci, 0x48, dmac);
12938c2ecf20Sopenharmony_ci
12948c2ecf20Sopenharmony_ci	if ((sonic->res_dmaa = request_region(dmaa, 0x10, "S3 SonicVibes DDMA-A")) == NULL) {
12958c2ecf20Sopenharmony_ci		snd_sonicvibes_free(sonic);
12968c2ecf20Sopenharmony_ci		dev_err(card->dev,
12978c2ecf20Sopenharmony_ci			"unable to grab DDMA-A port at 0x%x-0x%x\n",
12988c2ecf20Sopenharmony_ci			dmaa, dmaa + 0x10 - 1);
12998c2ecf20Sopenharmony_ci		return -EBUSY;
13008c2ecf20Sopenharmony_ci	}
13018c2ecf20Sopenharmony_ci	if ((sonic->res_dmac = request_region(dmac, 0x10, "S3 SonicVibes DDMA-C")) == NULL) {
13028c2ecf20Sopenharmony_ci		snd_sonicvibes_free(sonic);
13038c2ecf20Sopenharmony_ci		dev_err(card->dev,
13048c2ecf20Sopenharmony_ci			"unable to grab DDMA-C port at 0x%x-0x%x\n",
13058c2ecf20Sopenharmony_ci			dmac, dmac + 0x10 - 1);
13068c2ecf20Sopenharmony_ci		return -EBUSY;
13078c2ecf20Sopenharmony_ci	}
13088c2ecf20Sopenharmony_ci
13098c2ecf20Sopenharmony_ci	pci_read_config_dword(pci, 0x40, &sonic->dmaa_port);
13108c2ecf20Sopenharmony_ci	pci_read_config_dword(pci, 0x48, &sonic->dmac_port);
13118c2ecf20Sopenharmony_ci	sonic->dmaa_port &= ~0x0f;
13128c2ecf20Sopenharmony_ci	sonic->dmac_port &= ~0x0f;
13138c2ecf20Sopenharmony_ci	pci_write_config_dword(pci, 0x40, sonic->dmaa_port | 9);	/* enable + enhanced */
13148c2ecf20Sopenharmony_ci	pci_write_config_dword(pci, 0x48, sonic->dmac_port | 9);	/* enable */
13158c2ecf20Sopenharmony_ci	/* ok.. initialize S3 SonicVibes chip */
13168c2ecf20Sopenharmony_ci	outb(SV_RESET, SV_REG(sonic, CONTROL));		/* reset chip */
13178c2ecf20Sopenharmony_ci	udelay(100);
13188c2ecf20Sopenharmony_ci	outb(0, SV_REG(sonic, CONTROL));	/* release reset */
13198c2ecf20Sopenharmony_ci	udelay(100);
13208c2ecf20Sopenharmony_ci	outb(SV_ENHANCED | SV_INTA | (reverb ? SV_REVERB : 0), SV_REG(sonic, CONTROL));
13218c2ecf20Sopenharmony_ci	inb(SV_REG(sonic, STATUS));	/* clear IRQs */
13228c2ecf20Sopenharmony_ci#if 1
13238c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_DRIVE_CTRL, 0);	/* drive current 16mA */
13248c2ecf20Sopenharmony_ci#else
13258c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_DRIVE_CTRL, 0x40);	/* drive current 8mA */
13268c2ecf20Sopenharmony_ci#endif
13278c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_PC_ENABLE, sonic->enable = 0);	/* disable playback & capture */
13288c2ecf20Sopenharmony_ci	outb(sonic->irqmask = ~(SV_DMAA_MASK | SV_DMAC_MASK | SV_UD_MASK), SV_REG(sonic, IRQMASK));
13298c2ecf20Sopenharmony_ci	inb(SV_REG(sonic, STATUS));	/* clear IRQs */
13308c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_ADC_CLOCK, 0);	/* use PLL as clock source */
13318c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_ANALOG_POWER, 0);	/* power up analog parts */
13328c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_DIGITAL_POWER, 0);	/* power up digital parts */
13338c2ecf20Sopenharmony_ci	snd_sonicvibes_setpll(sonic, SV_IREG_ADC_PLL, 8000);
13348c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_SRS_SPACE, sonic->srs_space = 0x80);	/* SRS space off */
13358c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_SRS_CENTER, sonic->srs_center = 0x00);/* SRS center off */
13368c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_MPU401, sonic->mpu_switch = 0x05);	/* MPU-401 switch */
13378c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_WAVE_SOURCE, sonic->wave_source = 0x00);	/* onboard ROM */
13388c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_PCM_RATE_LOW, (8000 * 65536 / SV_FULLRATE) & 0xff);
13398c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_PCM_RATE_HIGH, ((8000 * 65536 / SV_FULLRATE) >> 8) & 0xff);
13408c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_LEFT_ADC, mge ? 0xd0 : 0xc0);
13418c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_ADC, 0xc0);
13428c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_LEFT_AUX1, 0x9f);
13438c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_AUX1, 0x9f);
13448c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_LEFT_CD, 0x9f);
13458c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_CD, 0x9f);
13468c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_LEFT_LINE, 0x9f);
13478c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_LINE, 0x9f);
13488c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_MIC, 0x8f);
13498c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_LEFT_SYNTH, 0x9f);
13508c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_SYNTH, 0x9f);
13518c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_LEFT_AUX2, 0x9f);
13528c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_AUX2, 0x9f);
13538c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_LEFT_ANALOG, 0x9f);
13548c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_ANALOG, 0x9f);
13558c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_LEFT_PCM, 0xbf);
13568c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_PCM, 0xbf);
13578c2ecf20Sopenharmony_ci	snd_sonicvibes_out(sonic, SV_IREG_ADC_OUTPUT_CTRL, 0xfc);
13588c2ecf20Sopenharmony_ci#if 0
13598c2ecf20Sopenharmony_ci	snd_sonicvibes_debug(sonic);
13608c2ecf20Sopenharmony_ci#endif
13618c2ecf20Sopenharmony_ci	sonic->revision = snd_sonicvibes_in(sonic, SV_IREG_REVISION);
13628c2ecf20Sopenharmony_ci
13638c2ecf20Sopenharmony_ci	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, sonic, &ops)) < 0) {
13648c2ecf20Sopenharmony_ci		snd_sonicvibes_free(sonic);
13658c2ecf20Sopenharmony_ci		return err;
13668c2ecf20Sopenharmony_ci	}
13678c2ecf20Sopenharmony_ci
13688c2ecf20Sopenharmony_ci	snd_sonicvibes_proc_init(sonic);
13698c2ecf20Sopenharmony_ci
13708c2ecf20Sopenharmony_ci	*rsonic = sonic;
13718c2ecf20Sopenharmony_ci	return 0;
13728c2ecf20Sopenharmony_ci}
13738c2ecf20Sopenharmony_ci
13748c2ecf20Sopenharmony_ci/*
13758c2ecf20Sopenharmony_ci *  MIDI section
13768c2ecf20Sopenharmony_ci */
13778c2ecf20Sopenharmony_ci
13788c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new snd_sonicvibes_midi_controls[] = {
13798c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("SonicVibes Wave Source RAM", 0, SV_IREG_WAVE_SOURCE, 0, 1, 0),
13808c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("SonicVibes Wave Source RAM+ROM", 0, SV_IREG_WAVE_SOURCE, 1, 1, 0),
13818c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("SonicVibes Onboard Synth", 0, SV_IREG_MPU401, 0, 1, 0),
13828c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("SonicVibes External Rx to Synth", 0, SV_IREG_MPU401, 1, 1, 0),
13838c2ecf20Sopenharmony_ciSONICVIBES_SINGLE("SonicVibes External Tx", 0, SV_IREG_MPU401, 2, 1, 0)
13848c2ecf20Sopenharmony_ci};
13858c2ecf20Sopenharmony_ci
13868c2ecf20Sopenharmony_cistatic int snd_sonicvibes_midi_input_open(struct snd_mpu401 * mpu)
13878c2ecf20Sopenharmony_ci{
13888c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = mpu->private_data;
13898c2ecf20Sopenharmony_ci	outb(sonic->irqmask &= ~SV_MIDI_MASK, SV_REG(sonic, IRQMASK));
13908c2ecf20Sopenharmony_ci	return 0;
13918c2ecf20Sopenharmony_ci}
13928c2ecf20Sopenharmony_ci
13938c2ecf20Sopenharmony_cistatic void snd_sonicvibes_midi_input_close(struct snd_mpu401 * mpu)
13948c2ecf20Sopenharmony_ci{
13958c2ecf20Sopenharmony_ci	struct sonicvibes *sonic = mpu->private_data;
13968c2ecf20Sopenharmony_ci	outb(sonic->irqmask |= SV_MIDI_MASK, SV_REG(sonic, IRQMASK));
13978c2ecf20Sopenharmony_ci}
13988c2ecf20Sopenharmony_ci
13998c2ecf20Sopenharmony_cistatic int snd_sonicvibes_midi(struct sonicvibes *sonic,
14008c2ecf20Sopenharmony_ci			       struct snd_rawmidi *rmidi)
14018c2ecf20Sopenharmony_ci{
14028c2ecf20Sopenharmony_ci	struct snd_mpu401 * mpu = rmidi->private_data;
14038c2ecf20Sopenharmony_ci	struct snd_card *card = sonic->card;
14048c2ecf20Sopenharmony_ci	unsigned int idx;
14058c2ecf20Sopenharmony_ci	int err;
14068c2ecf20Sopenharmony_ci
14078c2ecf20Sopenharmony_ci	mpu->private_data = sonic;
14088c2ecf20Sopenharmony_ci	mpu->open_input = snd_sonicvibes_midi_input_open;
14098c2ecf20Sopenharmony_ci	mpu->close_input = snd_sonicvibes_midi_input_close;
14108c2ecf20Sopenharmony_ci	for (idx = 0; idx < ARRAY_SIZE(snd_sonicvibes_midi_controls); idx++)
14118c2ecf20Sopenharmony_ci		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_sonicvibes_midi_controls[idx], sonic))) < 0)
14128c2ecf20Sopenharmony_ci			return err;
14138c2ecf20Sopenharmony_ci	return 0;
14148c2ecf20Sopenharmony_ci}
14158c2ecf20Sopenharmony_ci
14168c2ecf20Sopenharmony_cistatic int snd_sonic_probe(struct pci_dev *pci,
14178c2ecf20Sopenharmony_ci			   const struct pci_device_id *pci_id)
14188c2ecf20Sopenharmony_ci{
14198c2ecf20Sopenharmony_ci	static int dev;
14208c2ecf20Sopenharmony_ci	struct snd_card *card;
14218c2ecf20Sopenharmony_ci	struct sonicvibes *sonic;
14228c2ecf20Sopenharmony_ci	struct snd_rawmidi *midi_uart;
14238c2ecf20Sopenharmony_ci	struct snd_opl3 *opl3;
14248c2ecf20Sopenharmony_ci	int idx, err;
14258c2ecf20Sopenharmony_ci
14268c2ecf20Sopenharmony_ci	if (dev >= SNDRV_CARDS)
14278c2ecf20Sopenharmony_ci		return -ENODEV;
14288c2ecf20Sopenharmony_ci	if (!enable[dev]) {
14298c2ecf20Sopenharmony_ci		dev++;
14308c2ecf20Sopenharmony_ci		return -ENOENT;
14318c2ecf20Sopenharmony_ci	}
14328c2ecf20Sopenharmony_ci
14338c2ecf20Sopenharmony_ci	err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
14348c2ecf20Sopenharmony_ci			   0, &card);
14358c2ecf20Sopenharmony_ci	if (err < 0)
14368c2ecf20Sopenharmony_ci		return err;
14378c2ecf20Sopenharmony_ci	for (idx = 0; idx < 5; idx++) {
14388c2ecf20Sopenharmony_ci		if (pci_resource_start(pci, idx) == 0 ||
14398c2ecf20Sopenharmony_ci		    !(pci_resource_flags(pci, idx) & IORESOURCE_IO)) {
14408c2ecf20Sopenharmony_ci			snd_card_free(card);
14418c2ecf20Sopenharmony_ci			return -ENODEV;
14428c2ecf20Sopenharmony_ci		}
14438c2ecf20Sopenharmony_ci	}
14448c2ecf20Sopenharmony_ci	if ((err = snd_sonicvibes_create(card, pci,
14458c2ecf20Sopenharmony_ci					 reverb[dev] ? 1 : 0,
14468c2ecf20Sopenharmony_ci					 mge[dev] ? 1 : 0,
14478c2ecf20Sopenharmony_ci					 &sonic)) < 0) {
14488c2ecf20Sopenharmony_ci		snd_card_free(card);
14498c2ecf20Sopenharmony_ci		return err;
14508c2ecf20Sopenharmony_ci	}
14518c2ecf20Sopenharmony_ci
14528c2ecf20Sopenharmony_ci	strcpy(card->driver, "SonicVibes");
14538c2ecf20Sopenharmony_ci	strcpy(card->shortname, "S3 SonicVibes");
14548c2ecf20Sopenharmony_ci	sprintf(card->longname, "%s rev %i at 0x%llx, irq %i",
14558c2ecf20Sopenharmony_ci		card->shortname,
14568c2ecf20Sopenharmony_ci		sonic->revision,
14578c2ecf20Sopenharmony_ci		(unsigned long long)pci_resource_start(pci, 1),
14588c2ecf20Sopenharmony_ci		sonic->irq);
14598c2ecf20Sopenharmony_ci
14608c2ecf20Sopenharmony_ci	if ((err = snd_sonicvibes_pcm(sonic, 0)) < 0) {
14618c2ecf20Sopenharmony_ci		snd_card_free(card);
14628c2ecf20Sopenharmony_ci		return err;
14638c2ecf20Sopenharmony_ci	}
14648c2ecf20Sopenharmony_ci	if ((err = snd_sonicvibes_mixer(sonic)) < 0) {
14658c2ecf20Sopenharmony_ci		snd_card_free(card);
14668c2ecf20Sopenharmony_ci		return err;
14678c2ecf20Sopenharmony_ci	}
14688c2ecf20Sopenharmony_ci	if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_SONICVIBES,
14698c2ecf20Sopenharmony_ci				       sonic->midi_port,
14708c2ecf20Sopenharmony_ci				       MPU401_INFO_INTEGRATED |
14718c2ecf20Sopenharmony_ci				       MPU401_INFO_IRQ_HOOK,
14728c2ecf20Sopenharmony_ci				       -1, &midi_uart)) < 0) {
14738c2ecf20Sopenharmony_ci		snd_card_free(card);
14748c2ecf20Sopenharmony_ci		return err;
14758c2ecf20Sopenharmony_ci	}
14768c2ecf20Sopenharmony_ci	snd_sonicvibes_midi(sonic, midi_uart);
14778c2ecf20Sopenharmony_ci	if ((err = snd_opl3_create(card, sonic->synth_port,
14788c2ecf20Sopenharmony_ci				   sonic->synth_port + 2,
14798c2ecf20Sopenharmony_ci				   OPL3_HW_OPL3_SV, 1, &opl3)) < 0) {
14808c2ecf20Sopenharmony_ci		snd_card_free(card);
14818c2ecf20Sopenharmony_ci		return err;
14828c2ecf20Sopenharmony_ci	}
14838c2ecf20Sopenharmony_ci	if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
14848c2ecf20Sopenharmony_ci		snd_card_free(card);
14858c2ecf20Sopenharmony_ci		return err;
14868c2ecf20Sopenharmony_ci	}
14878c2ecf20Sopenharmony_ci
14888c2ecf20Sopenharmony_ci	err = snd_sonicvibes_create_gameport(sonic);
14898c2ecf20Sopenharmony_ci	if (err < 0) {
14908c2ecf20Sopenharmony_ci		snd_card_free(card);
14918c2ecf20Sopenharmony_ci		return err;
14928c2ecf20Sopenharmony_ci	}
14938c2ecf20Sopenharmony_ci
14948c2ecf20Sopenharmony_ci	if ((err = snd_card_register(card)) < 0) {
14958c2ecf20Sopenharmony_ci		snd_card_free(card);
14968c2ecf20Sopenharmony_ci		return err;
14978c2ecf20Sopenharmony_ci	}
14988c2ecf20Sopenharmony_ci
14998c2ecf20Sopenharmony_ci	pci_set_drvdata(pci, card);
15008c2ecf20Sopenharmony_ci	dev++;
15018c2ecf20Sopenharmony_ci	return 0;
15028c2ecf20Sopenharmony_ci}
15038c2ecf20Sopenharmony_ci
15048c2ecf20Sopenharmony_cistatic void snd_sonic_remove(struct pci_dev *pci)
15058c2ecf20Sopenharmony_ci{
15068c2ecf20Sopenharmony_ci	snd_card_free(pci_get_drvdata(pci));
15078c2ecf20Sopenharmony_ci}
15088c2ecf20Sopenharmony_ci
15098c2ecf20Sopenharmony_cistatic struct pci_driver sonicvibes_driver = {
15108c2ecf20Sopenharmony_ci	.name = KBUILD_MODNAME,
15118c2ecf20Sopenharmony_ci	.id_table = snd_sonic_ids,
15128c2ecf20Sopenharmony_ci	.probe = snd_sonic_probe,
15138c2ecf20Sopenharmony_ci	.remove = snd_sonic_remove,
15148c2ecf20Sopenharmony_ci};
15158c2ecf20Sopenharmony_ci
15168c2ecf20Sopenharmony_cimodule_pci_driver(sonicvibes_driver);
1517