diff options
author | Atsushi Nemoto <anemo@mba.ocn.ne.jp> | 2009-05-19 09:12:15 -0400 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2009-05-19 14:54:28 -0400 |
commit | e24805dd85283ac0912b9c400768a4d171b400ff (patch) | |
tree | e67bb07cab9358ac46f77bff9f5a0aef490271a5 /sound/soc/txx9/txx9aclc-ac97.c | |
parent | 11a728110633320d95935a1ba79c038db303596f (diff) |
ASoC: Add TXx9 AC link controller driver (v3)
This patch adds support for the integrated ACLC of the TXx9 family.
Signed-off-by: Atsushi Nemoto <anemo@mba.ocn.ne.jp>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/txx9/txx9aclc-ac97.c')
-rw-r--r-- | sound/soc/txx9/txx9aclc-ac97.c | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/sound/soc/txx9/txx9aclc-ac97.c b/sound/soc/txx9/txx9aclc-ac97.c new file mode 100644 index 000000000000..0f83bdb9b16f --- /dev/null +++ b/sound/soc/txx9/txx9aclc-ac97.c | |||
@@ -0,0 +1,255 @@ | |||
1 | /* | ||
2 | * TXx9 ACLC AC97 driver | ||
3 | * | ||
4 | * Copyright (C) 2009 Atsushi Nemoto | ||
5 | * | ||
6 | * Based on RBTX49xx patch from CELF patch archive. | ||
7 | * (C) Copyright TOSHIBA CORPORATION 2004-2006 | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | */ | ||
13 | |||
14 | #include <linux/init.h> | ||
15 | #include <linux/module.h> | ||
16 | #include <linux/delay.h> | ||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/io.h> | ||
19 | #include <sound/core.h> | ||
20 | #include <sound/pcm.h> | ||
21 | #include <sound/soc.h> | ||
22 | #include "txx9aclc.h" | ||
23 | |||
24 | #define AC97_DIR \ | ||
25 | (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) | ||
26 | |||
27 | #define AC97_RATES \ | ||
28 | SNDRV_PCM_RATE_8000_48000 | ||
29 | |||
30 | #ifdef __BIG_ENDIAN | ||
31 | #define AC97_FMTS SNDRV_PCM_FMTBIT_S16_BE | ||
32 | #else | ||
33 | #define AC97_FMTS SNDRV_PCM_FMTBIT_S16_LE | ||
34 | #endif | ||
35 | |||
36 | static DECLARE_WAIT_QUEUE_HEAD(ac97_waitq); | ||
37 | |||
38 | /* REVISIT: How to find txx9aclc_soc_device from snd_ac97? */ | ||
39 | static struct txx9aclc_soc_device *txx9aclc_soc_dev; | ||
40 | |||
41 | static int txx9aclc_regready(struct txx9aclc_soc_device *dev) | ||
42 | { | ||
43 | struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); | ||
44 | |||
45 | return __raw_readl(drvdata->base + ACINTSTS) & ACINT_REGACCRDY; | ||
46 | } | ||
47 | |||
48 | /* AC97 controller reads codec register */ | ||
49 | static unsigned short txx9aclc_ac97_read(struct snd_ac97 *ac97, | ||
50 | unsigned short reg) | ||
51 | { | ||
52 | struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; | ||
53 | struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); | ||
54 | void __iomem *base = drvdata->base; | ||
55 | u32 dat; | ||
56 | |||
57 | if (!(__raw_readl(base + ACINTSTS) & ACINT_CODECRDY(ac97->num))) | ||
58 | return 0xffff; | ||
59 | reg |= ac97->num << 7; | ||
60 | dat = (reg << ACREGACC_REG_SHIFT) | ACREGACC_READ; | ||
61 | __raw_writel(dat, base + ACREGACC); | ||
62 | __raw_writel(ACINT_REGACCRDY, base + ACINTEN); | ||
63 | if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(dev), HZ)) { | ||
64 | __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); | ||
65 | dev_err(dev->soc_dev.dev, "ac97 read timeout (reg %#x)\n", reg); | ||
66 | dat = 0xffff; | ||
67 | goto done; | ||
68 | } | ||
69 | dat = __raw_readl(base + ACREGACC); | ||
70 | if (((dat >> ACREGACC_REG_SHIFT) & 0xff) != reg) { | ||
71 | dev_err(dev->soc_dev.dev, "reg mismatch %x with %x\n", | ||
72 | dat, reg); | ||
73 | dat = 0xffff; | ||
74 | goto done; | ||
75 | } | ||
76 | dat = (dat >> ACREGACC_DAT_SHIFT) & 0xffff; | ||
77 | done: | ||
78 | __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); | ||
79 | return dat; | ||
80 | } | ||
81 | |||
82 | /* AC97 controller writes to codec register */ | ||
83 | static void txx9aclc_ac97_write(struct snd_ac97 *ac97, unsigned short reg, | ||
84 | unsigned short val) | ||
85 | { | ||
86 | struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; | ||
87 | struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); | ||
88 | void __iomem *base = drvdata->base; | ||
89 | |||
90 | __raw_writel(((reg | (ac97->num << 7)) << ACREGACC_REG_SHIFT) | | ||
91 | (val << ACREGACC_DAT_SHIFT), | ||
92 | base + ACREGACC); | ||
93 | __raw_writel(ACINT_REGACCRDY, base + ACINTEN); | ||
94 | if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(dev), HZ)) { | ||
95 | dev_err(dev->soc_dev.dev, | ||
96 | "ac97 write timeout (reg %#x)\n", reg); | ||
97 | } | ||
98 | __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); | ||
99 | } | ||
100 | |||
101 | static void txx9aclc_ac97_cold_reset(struct snd_ac97 *ac97) | ||
102 | { | ||
103 | struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; | ||
104 | struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); | ||
105 | void __iomem *base = drvdata->base; | ||
106 | u32 ready = ACINT_CODECRDY(ac97->num) | ACINT_REGACCRDY; | ||
107 | |||
108 | __raw_writel(ACCTL_ENLINK, base + ACCTLDIS); | ||
109 | mmiowb(); | ||
110 | udelay(1); | ||
111 | __raw_writel(ACCTL_ENLINK, base + ACCTLEN); | ||
112 | /* wait for primary codec ready status */ | ||
113 | __raw_writel(ready, base + ACINTEN); | ||
114 | if (!wait_event_timeout(ac97_waitq, | ||
115 | (__raw_readl(base + ACINTSTS) & ready) == ready, | ||
116 | HZ)) { | ||
117 | dev_err(&ac97->dev, "primary codec is not ready " | ||
118 | "(status %#x)\n", | ||
119 | __raw_readl(base + ACINTSTS)); | ||
120 | } | ||
121 | __raw_writel(ACINT_REGACCRDY, base + ACINTSTS); | ||
122 | __raw_writel(ready, base + ACINTDIS); | ||
123 | } | ||
124 | |||
125 | /* AC97 controller operations */ | ||
126 | struct snd_ac97_bus_ops soc_ac97_ops = { | ||
127 | .read = txx9aclc_ac97_read, | ||
128 | .write = txx9aclc_ac97_write, | ||
129 | .reset = txx9aclc_ac97_cold_reset, | ||
130 | }; | ||
131 | EXPORT_SYMBOL_GPL(soc_ac97_ops); | ||
132 | |||
133 | static irqreturn_t txx9aclc_ac97_irq(int irq, void *dev_id) | ||
134 | { | ||
135 | struct txx9aclc_plat_drvdata *drvdata = dev_id; | ||
136 | void __iomem *base = drvdata->base; | ||
137 | |||
138 | __raw_writel(__raw_readl(base + ACINTMSTS), base + ACINTDIS); | ||
139 | wake_up(&ac97_waitq); | ||
140 | return IRQ_HANDLED; | ||
141 | } | ||
142 | |||
143 | static int txx9aclc_ac97_probe(struct platform_device *pdev, | ||
144 | struct snd_soc_dai *dai) | ||
145 | { | ||
146 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
147 | struct txx9aclc_soc_device *dev = | ||
148 | container_of(socdev, struct txx9aclc_soc_device, soc_dev); | ||
149 | |||
150 | dev->aclc_pdev = to_platform_device(dai->dev); | ||
151 | txx9aclc_soc_dev = dev; | ||
152 | return 0; | ||
153 | } | ||
154 | |||
155 | static void txx9aclc_ac97_remove(struct platform_device *pdev, | ||
156 | struct snd_soc_dai *dai) | ||
157 | { | ||
158 | struct platform_device *aclc_pdev = to_platform_device(dai->dev); | ||
159 | struct txx9aclc_plat_drvdata *drvdata = platform_get_drvdata(aclc_pdev); | ||
160 | |||
161 | /* disable AC-link */ | ||
162 | __raw_writel(ACCTL_ENLINK, drvdata->base + ACCTLDIS); | ||
163 | txx9aclc_soc_dev = NULL; | ||
164 | } | ||
165 | |||
166 | struct snd_soc_dai txx9aclc_ac97_dai = { | ||
167 | .name = "txx9aclc_ac97", | ||
168 | .ac97_control = 1, | ||
169 | .probe = txx9aclc_ac97_probe, | ||
170 | .remove = txx9aclc_ac97_remove, | ||
171 | .playback = { | ||
172 | .rates = AC97_RATES, | ||
173 | .formats = AC97_FMTS, | ||
174 | .channels_min = 2, | ||
175 | .channels_max = 2, | ||
176 | }, | ||
177 | .capture = { | ||
178 | .rates = AC97_RATES, | ||
179 | .formats = AC97_FMTS, | ||
180 | .channels_min = 2, | ||
181 | .channels_max = 2, | ||
182 | }, | ||
183 | }; | ||
184 | EXPORT_SYMBOL_GPL(txx9aclc_ac97_dai); | ||
185 | |||
186 | static int __devinit txx9aclc_ac97_dev_probe(struct platform_device *pdev) | ||
187 | { | ||
188 | struct txx9aclc_plat_drvdata *drvdata; | ||
189 | struct resource *r; | ||
190 | int err; | ||
191 | int irq; | ||
192 | |||
193 | irq = platform_get_irq(pdev, 0); | ||
194 | if (irq < 0) | ||
195 | return irq; | ||
196 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
197 | if (!r) | ||
198 | return -EBUSY; | ||
199 | |||
200 | if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r), | ||
201 | dev_name(&pdev->dev))) | ||
202 | return -EBUSY; | ||
203 | |||
204 | drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); | ||
205 | if (!drvdata) | ||
206 | return -ENOMEM; | ||
207 | platform_set_drvdata(pdev, drvdata); | ||
208 | drvdata->physbase = r->start; | ||
209 | if (sizeof(drvdata->physbase) > sizeof(r->start) && | ||
210 | r->start >= TXX9_DIRECTMAP_BASE && | ||
211 | r->start < TXX9_DIRECTMAP_BASE + 0x400000) | ||
212 | drvdata->physbase |= 0xf00000000ull; | ||
213 | drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); | ||
214 | if (!drvdata->base) | ||
215 | return -EBUSY; | ||
216 | err = devm_request_irq(&pdev->dev, irq, txx9aclc_ac97_irq, | ||
217 | IRQF_DISABLED, dev_name(&pdev->dev), drvdata); | ||
218 | if (err < 0) | ||
219 | return err; | ||
220 | |||
221 | txx9aclc_ac97_dai.dev = &pdev->dev; | ||
222 | return snd_soc_register_dai(&txx9aclc_ac97_dai); | ||
223 | } | ||
224 | |||
225 | static int __devexit txx9aclc_ac97_dev_remove(struct platform_device *pdev) | ||
226 | { | ||
227 | snd_soc_unregister_dai(&txx9aclc_ac97_dai); | ||
228 | return 0; | ||
229 | } | ||
230 | |||
231 | static struct platform_driver txx9aclc_ac97_driver = { | ||
232 | .probe = txx9aclc_ac97_dev_probe, | ||
233 | .remove = __devexit_p(txx9aclc_ac97_dev_remove), | ||
234 | .driver = { | ||
235 | .name = "txx9aclc-ac97", | ||
236 | .owner = THIS_MODULE, | ||
237 | }, | ||
238 | }; | ||
239 | |||
240 | static int __init txx9aclc_ac97_init(void) | ||
241 | { | ||
242 | return platform_driver_register(&txx9aclc_ac97_driver); | ||
243 | } | ||
244 | |||
245 | static void __exit txx9aclc_ac97_exit(void) | ||
246 | { | ||
247 | platform_driver_unregister(&txx9aclc_ac97_driver); | ||
248 | } | ||
249 | |||
250 | module_init(txx9aclc_ac97_init); | ||
251 | module_exit(txx9aclc_ac97_exit); | ||
252 | |||
253 | MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>"); | ||
254 | MODULE_DESCRIPTION("TXx9 ACLC AC97 driver"); | ||
255 | MODULE_LICENSE("GPL"); | ||