Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Driver for Sound Core PDAudioCF soundcard | |
3 | * | |
c1017a4c | 4 | * Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz> |
1da177e4 LT |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 | */ | |
20 | ||
1da177e4 LT |
21 | #include <linux/delay.h> |
22 | #include <sound/core.h> | |
23 | #include <sound/info.h> | |
24 | #include "pdaudiocf.h" | |
25 | #include <sound/initval.h> | |
26 | ||
27 | /* | |
28 | * | |
29 | */ | |
30 | static unsigned char pdacf_ak4117_read(void *private_data, unsigned char reg) | |
31 | { | |
db131548 | 32 | struct snd_pdacf *chip = private_data; |
1da177e4 LT |
33 | unsigned long timeout; |
34 | unsigned long flags; | |
35 | unsigned char res; | |
36 | ||
37 | spin_lock_irqsave(&chip->ak4117_lock, flags); | |
38 | timeout = 1000; | |
39 | while (pdacf_reg_read(chip, PDAUDIOCF_REG_SCR) & PDAUDIOCF_AK_SBP) { | |
40 | udelay(5); | |
41 | if (--timeout == 0) { | |
42 | spin_unlock_irqrestore(&chip->ak4117_lock, flags); | |
43 | snd_printk(KERN_ERR "AK4117 ready timeout (read)\n"); | |
44 | return 0; | |
45 | } | |
46 | } | |
47 | pdacf_reg_write(chip, PDAUDIOCF_REG_AK_IFR, (u16)reg << 8); | |
48 | timeout = 1000; | |
49 | while (pdacf_reg_read(chip, PDAUDIOCF_REG_SCR) & PDAUDIOCF_AK_SBP) { | |
50 | udelay(5); | |
51 | if (--timeout == 0) { | |
52 | spin_unlock_irqrestore(&chip->ak4117_lock, flags); | |
53 | snd_printk(KERN_ERR "AK4117 read timeout (read2)\n"); | |
54 | return 0; | |
55 | } | |
56 | } | |
57 | res = (unsigned char)pdacf_reg_read(chip, PDAUDIOCF_REG_AK_IFR); | |
58 | spin_unlock_irqrestore(&chip->ak4117_lock, flags); | |
59 | return res; | |
60 | } | |
61 | ||
62 | static void pdacf_ak4117_write(void *private_data, unsigned char reg, unsigned char val) | |
63 | { | |
db131548 | 64 | struct snd_pdacf *chip = private_data; |
1da177e4 LT |
65 | unsigned long timeout; |
66 | unsigned long flags; | |
67 | ||
68 | spin_lock_irqsave(&chip->ak4117_lock, flags); | |
69 | timeout = 1000; | |
70 | while (inw(chip->port + PDAUDIOCF_REG_SCR) & PDAUDIOCF_AK_SBP) { | |
71 | udelay(5); | |
72 | if (--timeout == 0) { | |
73 | spin_unlock_irqrestore(&chip->ak4117_lock, flags); | |
74 | snd_printk(KERN_ERR "AK4117 ready timeout (write)\n"); | |
75 | return; | |
76 | } | |
77 | } | |
78 | outw((u16)reg << 8 | val | (1<<13), chip->port + PDAUDIOCF_REG_AK_IFR); | |
79 | spin_unlock_irqrestore(&chip->ak4117_lock, flags); | |
80 | } | |
81 | ||
82 | #if 0 | |
db131548 | 83 | void pdacf_dump(struct snd_pdacf *chip) |
1da177e4 | 84 | { |
2ebfb8ee TI |
85 | printk(KERN_DEBUG "PDAUDIOCF DUMP (0x%lx):\n", chip->port); |
86 | printk(KERN_DEBUG "WPD : 0x%x\n", | |
87 | inw(chip->port + PDAUDIOCF_REG_WDP)); | |
88 | printk(KERN_DEBUG "RDP : 0x%x\n", | |
89 | inw(chip->port + PDAUDIOCF_REG_RDP)); | |
90 | printk(KERN_DEBUG "TCR : 0x%x\n", | |
91 | inw(chip->port + PDAUDIOCF_REG_TCR)); | |
92 | printk(KERN_DEBUG "SCR : 0x%x\n", | |
93 | inw(chip->port + PDAUDIOCF_REG_SCR)); | |
94 | printk(KERN_DEBUG "ISR : 0x%x\n", | |
95 | inw(chip->port + PDAUDIOCF_REG_ISR)); | |
96 | printk(KERN_DEBUG "IER : 0x%x\n", | |
97 | inw(chip->port + PDAUDIOCF_REG_IER)); | |
98 | printk(KERN_DEBUG "AK_IFR : 0x%x\n", | |
99 | inw(chip->port + PDAUDIOCF_REG_AK_IFR)); | |
1da177e4 LT |
100 | } |
101 | #endif | |
102 | ||
db131548 | 103 | static int pdacf_reset(struct snd_pdacf *chip, int powerdown) |
1da177e4 LT |
104 | { |
105 | u16 val; | |
106 | ||
107 | val = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR); | |
108 | val |= PDAUDIOCF_PDN; | |
109 | val &= ~PDAUDIOCF_RECORD; /* for sure */ | |
110 | pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); | |
111 | udelay(5); | |
112 | val |= PDAUDIOCF_RST; | |
113 | pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); | |
114 | udelay(200); | |
115 | val &= ~PDAUDIOCF_RST; | |
116 | pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); | |
117 | udelay(5); | |
118 | if (!powerdown) { | |
119 | val &= ~PDAUDIOCF_PDN; | |
120 | pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); | |
121 | udelay(200); | |
122 | } | |
123 | return 0; | |
124 | } | |
125 | ||
db131548 | 126 | void pdacf_reinit(struct snd_pdacf *chip, int resume) |
1da177e4 LT |
127 | { |
128 | pdacf_reset(chip, 0); | |
129 | if (resume) | |
130 | pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, chip->suspend_reg_scr); | |
131 | snd_ak4117_reinit(chip->ak4117); | |
132 | pdacf_reg_write(chip, PDAUDIOCF_REG_TCR, chip->regmap[PDAUDIOCF_REG_TCR>>1]); | |
133 | pdacf_reg_write(chip, PDAUDIOCF_REG_IER, chip->regmap[PDAUDIOCF_REG_IER>>1]); | |
134 | } | |
135 | ||
db131548 TI |
136 | static void pdacf_proc_read(struct snd_info_entry * entry, |
137 | struct snd_info_buffer *buffer) | |
1da177e4 | 138 | { |
db131548 | 139 | struct snd_pdacf *chip = entry->private_data; |
1da177e4 LT |
140 | u16 tmp; |
141 | ||
142 | snd_iprintf(buffer, "PDAudioCF\n\n"); | |
143 | tmp = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR); | |
144 | snd_iprintf(buffer, "FPGA revision : 0x%x\n", PDAUDIOCF_FPGAREV(tmp)); | |
145 | ||
146 | } | |
147 | ||
db131548 | 148 | static void pdacf_proc_init(struct snd_pdacf *chip) |
1da177e4 | 149 | { |
db131548 | 150 | struct snd_info_entry *entry; |
1da177e4 LT |
151 | |
152 | if (! snd_card_proc_new(chip->card, "pdaudiocf", &entry)) | |
bf850204 | 153 | snd_info_set_text_ops(entry, chip, pdacf_proc_read); |
1da177e4 LT |
154 | } |
155 | ||
db131548 | 156 | struct snd_pdacf *snd_pdacf_create(struct snd_card *card) |
1da177e4 | 157 | { |
db131548 | 158 | struct snd_pdacf *chip; |
1da177e4 | 159 | |
561b220a | 160 | chip = kzalloc(sizeof(*chip), GFP_KERNEL); |
1da177e4 LT |
161 | if (chip == NULL) |
162 | return NULL; | |
163 | chip->card = card; | |
164 | spin_lock_init(&chip->reg_lock); | |
165 | spin_lock_init(&chip->ak4117_lock); | |
166 | tasklet_init(&chip->tq, pdacf_tasklet, (unsigned long)chip); | |
167 | card->private_data = chip; | |
168 | ||
169 | pdacf_proc_init(chip); | |
170 | return chip; | |
171 | } | |
172 | ||
db131548 | 173 | static void snd_pdacf_ak4117_change(struct ak4117 *ak4117, unsigned char c0, unsigned char c1) |
1da177e4 | 174 | { |
db131548 | 175 | struct snd_pdacf *chip = ak4117->change_callback_private; |
1da177e4 LT |
176 | unsigned long flags; |
177 | u16 val; | |
178 | ||
179 | if (!(c0 & AK4117_UNLCK)) | |
180 | return; | |
181 | spin_lock_irqsave(&chip->reg_lock, flags); | |
182 | val = chip->regmap[PDAUDIOCF_REG_SCR>>1]; | |
183 | if (ak4117->rcs0 & AK4117_UNLCK) | |
184 | val |= PDAUDIOCF_BLUE_LED_OFF; | |
185 | else | |
186 | val &= ~PDAUDIOCF_BLUE_LED_OFF; | |
187 | pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); | |
188 | spin_unlock_irqrestore(&chip->reg_lock, flags); | |
189 | } | |
190 | ||
db131548 | 191 | int snd_pdacf_ak4117_create(struct snd_pdacf *chip) |
1da177e4 LT |
192 | { |
193 | int err; | |
194 | u16 val; | |
195 | /* design note: if we unmask PLL unlock, parity, valid, audio or auto bit interrupts */ | |
196 | /* from AK4117 then INT1 pin from AK4117 will be high all time, because PCMCIA interrupts are */ | |
197 | /* egde based and FPGA does logical OR for all interrupt sources, we cannot use these */ | |
198 | /* high-rate sources */ | |
199 | static unsigned char pgm[5] = { | |
200 | AK4117_XTL_24_576M | AK4117_EXCT, /* AK4117_REG_PWRDN */ | |
201 | AK4117_CM_PLL_XTAL | AK4117_PKCS_128fs | AK4117_XCKS_128fs, /* AK4117_REQ_CLOCK */ | |
202 | AK4117_EFH_1024LRCLK | AK4117_DIF_24R | AK4117_IPS, /* AK4117_REG_IO */ | |
203 | 0xff, /* AK4117_REG_INT0_MASK */ | |
204 | AK4117_MAUTO | AK4117_MAUD | AK4117_MULK | AK4117_MPAR | AK4117_MV, /* AK4117_REG_INT1_MASK */ | |
205 | }; | |
206 | ||
207 | err = pdacf_reset(chip, 0); | |
208 | if (err < 0) | |
209 | return err; | |
210 | err = snd_ak4117_create(chip->card, pdacf_ak4117_read, pdacf_ak4117_write, pgm, chip, &chip->ak4117); | |
211 | if (err < 0) | |
212 | return err; | |
213 | ||
214 | val = pdacf_reg_read(chip, PDAUDIOCF_REG_TCR); | |
215 | #if 1 /* normal operation */ | |
216 | val &= ~(PDAUDIOCF_ELIMAKMBIT|PDAUDIOCF_TESTDATASEL); | |
217 | #else /* debug */ | |
218 | val |= PDAUDIOCF_ELIMAKMBIT; | |
219 | val &= ~PDAUDIOCF_TESTDATASEL; | |
220 | #endif | |
221 | pdacf_reg_write(chip, PDAUDIOCF_REG_TCR, val); | |
222 | ||
223 | /* setup the FPGA to match AK4117 setup */ | |
224 | val = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR); | |
225 | val &= ~(PDAUDIOCF_CLKDIV0 | PDAUDIOCF_CLKDIV1); /* use 24.576Mhz clock */ | |
226 | val &= ~(PDAUDIOCF_RED_LED_OFF|PDAUDIOCF_BLUE_LED_OFF); | |
227 | val |= PDAUDIOCF_DATAFMT0 | PDAUDIOCF_DATAFMT1; /* 24-bit data */ | |
228 | pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); | |
229 | ||
230 | /* setup LEDs and IRQ */ | |
231 | val = pdacf_reg_read(chip, PDAUDIOCF_REG_IER); | |
232 | val &= ~(PDAUDIOCF_IRQLVLEN0 | PDAUDIOCF_IRQLVLEN1); | |
233 | val &= ~(PDAUDIOCF_BLUEDUTY0 | PDAUDIOCF_REDDUTY0 | PDAUDIOCF_REDDUTY1); | |
234 | val |= PDAUDIOCF_BLUEDUTY1 | PDAUDIOCF_HALFRATE; | |
235 | val |= PDAUDIOCF_IRQOVREN | PDAUDIOCF_IRQAKMEN; | |
236 | pdacf_reg_write(chip, PDAUDIOCF_REG_IER, val); | |
237 | ||
238 | chip->ak4117->change_callback_private = chip; | |
239 | chip->ak4117->change_callback = snd_pdacf_ak4117_change; | |
240 | ||
241 | /* update LED status */ | |
242 | snd_pdacf_ak4117_change(chip->ak4117, AK4117_UNLCK, 0); | |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
db131548 | 247 | void snd_pdacf_powerdown(struct snd_pdacf *chip) |
1da177e4 LT |
248 | { |
249 | u16 val; | |
250 | ||
251 | val = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR); | |
252 | chip->suspend_reg_scr = val; | |
253 | val |= PDAUDIOCF_RED_LED_OFF | PDAUDIOCF_BLUE_LED_OFF; | |
254 | pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); | |
255 | /* disable interrupts, but use direct write to preserve old register value in chip->regmap */ | |
256 | val = inw(chip->port + PDAUDIOCF_REG_IER); | |
257 | val &= ~(PDAUDIOCF_IRQOVREN|PDAUDIOCF_IRQAKMEN|PDAUDIOCF_IRQLVLEN0|PDAUDIOCF_IRQLVLEN1); | |
258 | outw(val, chip->port + PDAUDIOCF_REG_IER); | |
259 | pdacf_reset(chip, 1); | |
260 | } | |
261 | ||
262 | #ifdef CONFIG_PM | |
263 | ||
e4f163d9 | 264 | int snd_pdacf_suspend(struct snd_pdacf *chip, pm_message_t state) |
1da177e4 | 265 | { |
1da177e4 LT |
266 | u16 val; |
267 | ||
e4f163d9 | 268 | snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); |
1da177e4 LT |
269 | snd_pcm_suspend_all(chip->pcm); |
270 | /* disable interrupts, but use direct write to preserve old register value in chip->regmap */ | |
271 | val = inw(chip->port + PDAUDIOCF_REG_IER); | |
272 | val &= ~(PDAUDIOCF_IRQOVREN|PDAUDIOCF_IRQAKMEN|PDAUDIOCF_IRQLVLEN0|PDAUDIOCF_IRQLVLEN1); | |
273 | outw(val, chip->port + PDAUDIOCF_REG_IER); | |
274 | chip->chip_status |= PDAUDIOCF_STAT_IS_SUSPENDED; /* ignore interrupts from now */ | |
275 | snd_pdacf_powerdown(chip); | |
276 | return 0; | |
277 | } | |
278 | ||
db131548 | 279 | static inline int check_signal(struct snd_pdacf *chip) |
1da177e4 LT |
280 | { |
281 | return (chip->ak4117->rcs0 & AK4117_UNLCK) == 0; | |
282 | } | |
283 | ||
e4f163d9 | 284 | int snd_pdacf_resume(struct snd_pdacf *chip) |
1da177e4 | 285 | { |
1da177e4 LT |
286 | int timeout = 40; |
287 | ||
288 | pdacf_reinit(chip, 1); | |
289 | /* wait for AK4117's PLL */ | |
290 | while (timeout-- > 0 && | |
291 | (snd_ak4117_external_rate(chip->ak4117) <= 0 || !check_signal(chip))) | |
292 | mdelay(1); | |
293 | chip->chip_status &= ~PDAUDIOCF_STAT_IS_SUSPENDED; | |
e4f163d9 | 294 | snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); |
1da177e4 LT |
295 | return 0; |
296 | } | |
297 | #endif |