diff options
Diffstat (limited to 'sound/soc/tegra')
-rw-r--r-- | sound/soc/tegra/Kconfig | 40 | ||||
-rw-r--r-- | sound/soc/tegra/Makefile | 17 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_asoc_utils.c | 156 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_asoc_utils.h | 45 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_das.c | 265 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_das.h | 135 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_i2s.c | 509 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_i2s.h | 165 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_pcm.c | 404 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_pcm.h | 55 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_wm8903.c | 475 | ||||
-rw-r--r-- | sound/soc/tegra/trimslice.c | 228 |
12 files changed, 2494 insertions, 0 deletions
diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig new file mode 100644 index 000000000000..035d39a4beb4 --- /dev/null +++ b/sound/soc/tegra/Kconfig | |||
@@ -0,0 +1,40 @@ | |||
1 | config SND_SOC_TEGRA | ||
2 | tristate "SoC Audio for the Tegra System-on-Chip" | ||
3 | depends on ARCH_TEGRA && TEGRA_SYSTEM_DMA | ||
4 | help | ||
5 | Say Y or M here if you want support for SoC audio on Tegra. | ||
6 | |||
7 | config SND_SOC_TEGRA_I2S | ||
8 | tristate | ||
9 | depends on SND_SOC_TEGRA | ||
10 | help | ||
11 | Say Y or M if you want to add support for codecs attached to the | ||
12 | Tegra I2S interface. You will also need to select the individual | ||
13 | machine drivers to support below. | ||
14 | |||
15 | config MACH_HAS_SND_SOC_TEGRA_WM8903 | ||
16 | bool | ||
17 | help | ||
18 | Machines that use the SND_SOC_TEGRA_WM8903 driver should select | ||
19 | this config option, in order to allow the user to enable | ||
20 | SND_SOC_TEGRA_WM8903. | ||
21 | |||
22 | config SND_SOC_TEGRA_WM8903 | ||
23 | tristate "SoC Audio support for Tegra boards using a WM8903 codec" | ||
24 | depends on SND_SOC_TEGRA && I2C | ||
25 | depends on MACH_HAS_SND_SOC_TEGRA_WM8903 | ||
26 | select SND_SOC_TEGRA_I2S | ||
27 | select SND_SOC_WM8903 | ||
28 | help | ||
29 | Say Y or M here if you want to add support for SoC audio on Tegra | ||
30 | boards using the WM8093 codec. Currently, the supported boards are | ||
31 | Harmony, Ventana, Seaboard, Kaen, and Aebl. | ||
32 | |||
33 | config SND_SOC_TEGRA_TRIMSLICE | ||
34 | tristate "SoC Audio support for TrimSlice board" | ||
35 | depends on SND_SOC_TEGRA && MACH_TRIMSLICE && I2C | ||
36 | select SND_SOC_TEGRA_I2S | ||
37 | select SND_SOC_TLV320AIC23 | ||
38 | help | ||
39 | Say Y or M here if you want to add support for SoC audio on the | ||
40 | TrimSlice platform. | ||
diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile new file mode 100644 index 000000000000..fa6574d92a31 --- /dev/null +++ b/sound/soc/tegra/Makefile | |||
@@ -0,0 +1,17 @@ | |||
1 | # Tegra platform Support | ||
2 | snd-soc-tegra-das-objs := tegra_das.o | ||
3 | snd-soc-tegra-pcm-objs := tegra_pcm.o | ||
4 | snd-soc-tegra-i2s-objs := tegra_i2s.o | ||
5 | snd-soc-tegra-utils-objs += tegra_asoc_utils.o | ||
6 | |||
7 | obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o | ||
8 | obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-das.o | ||
9 | obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o | ||
10 | obj-$(CONFIG_SND_SOC_TEGRA_I2S) += snd-soc-tegra-i2s.o | ||
11 | |||
12 | # Tegra machine Support | ||
13 | snd-soc-tegra-wm8903-objs := tegra_wm8903.o | ||
14 | snd-soc-tegra-trimslice-objs := trimslice.o | ||
15 | |||
16 | obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o | ||
17 | obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o | ||
diff --git a/sound/soc/tegra/tegra_asoc_utils.c b/sound/soc/tegra/tegra_asoc_utils.c new file mode 100644 index 000000000000..dfa85cbb05c8 --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.c | |||
@@ -0,0 +1,156 @@ | |||
1 | /* | ||
2 | * tegra_asoc_utils.c - Harmony machine ASoC driver | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010 - NVIDIA, Inc. | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License | ||
9 | * version 2 as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * 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., 51 Franklin St, Fifth Floor, Boston, MA | ||
19 | * 02110-1301 USA | ||
20 | * | ||
21 | */ | ||
22 | |||
23 | #include <linux/clk.h> | ||
24 | #include <linux/device.h> | ||
25 | #include <linux/err.h> | ||
26 | #include <linux/kernel.h> | ||
27 | |||
28 | #include "tegra_asoc_utils.h" | ||
29 | |||
30 | int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, | ||
31 | int mclk) | ||
32 | { | ||
33 | int new_baseclock; | ||
34 | bool clk_change; | ||
35 | int err; | ||
36 | |||
37 | switch (srate) { | ||
38 | case 11025: | ||
39 | case 22050: | ||
40 | case 44100: | ||
41 | case 88200: | ||
42 | new_baseclock = 56448000; | ||
43 | break; | ||
44 | case 8000: | ||
45 | case 16000: | ||
46 | case 32000: | ||
47 | case 48000: | ||
48 | case 64000: | ||
49 | case 96000: | ||
50 | new_baseclock = 73728000; | ||
51 | break; | ||
52 | default: | ||
53 | return -EINVAL; | ||
54 | } | ||
55 | |||
56 | clk_change = ((new_baseclock != data->set_baseclock) || | ||
57 | (mclk != data->set_mclk)); | ||
58 | if (!clk_change) | ||
59 | return 0; | ||
60 | |||
61 | data->set_baseclock = 0; | ||
62 | data->set_mclk = 0; | ||
63 | |||
64 | clk_disable(data->clk_cdev1); | ||
65 | clk_disable(data->clk_pll_a_out0); | ||
66 | clk_disable(data->clk_pll_a); | ||
67 | |||
68 | err = clk_set_rate(data->clk_pll_a, new_baseclock); | ||
69 | if (err) { | ||
70 | dev_err(data->dev, "Can't set pll_a rate: %d\n", err); | ||
71 | return err; | ||
72 | } | ||
73 | |||
74 | err = clk_set_rate(data->clk_pll_a_out0, mclk); | ||
75 | if (err) { | ||
76 | dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err); | ||
77 | return err; | ||
78 | } | ||
79 | |||
80 | /* Don't set cdev1 rate; its locked to pll_a_out0 */ | ||
81 | |||
82 | err = clk_enable(data->clk_pll_a); | ||
83 | if (err) { | ||
84 | dev_err(data->dev, "Can't enable pll_a: %d\n", err); | ||
85 | return err; | ||
86 | } | ||
87 | |||
88 | err = clk_enable(data->clk_pll_a_out0); | ||
89 | if (err) { | ||
90 | dev_err(data->dev, "Can't enable pll_a_out0: %d\n", err); | ||
91 | return err; | ||
92 | } | ||
93 | |||
94 | err = clk_enable(data->clk_cdev1); | ||
95 | if (err) { | ||
96 | dev_err(data->dev, "Can't enable cdev1: %d\n", err); | ||
97 | return err; | ||
98 | } | ||
99 | |||
100 | data->set_baseclock = new_baseclock; | ||
101 | data->set_mclk = mclk; | ||
102 | |||
103 | return 0; | ||
104 | } | ||
105 | EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_rate); | ||
106 | |||
107 | int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, | ||
108 | struct device *dev) | ||
109 | { | ||
110 | int ret; | ||
111 | |||
112 | data->dev = dev; | ||
113 | |||
114 | data->clk_pll_a = clk_get_sys(NULL, "pll_a"); | ||
115 | if (IS_ERR(data->clk_pll_a)) { | ||
116 | dev_err(data->dev, "Can't retrieve clk pll_a\n"); | ||
117 | ret = PTR_ERR(data->clk_pll_a); | ||
118 | goto err; | ||
119 | } | ||
120 | |||
121 | data->clk_pll_a_out0 = clk_get_sys(NULL, "pll_a_out0"); | ||
122 | if (IS_ERR(data->clk_pll_a_out0)) { | ||
123 | dev_err(data->dev, "Can't retrieve clk pll_a_out0\n"); | ||
124 | ret = PTR_ERR(data->clk_pll_a_out0); | ||
125 | goto err_put_pll_a; | ||
126 | } | ||
127 | |||
128 | data->clk_cdev1 = clk_get_sys(NULL, "cdev1"); | ||
129 | if (IS_ERR(data->clk_cdev1)) { | ||
130 | dev_err(data->dev, "Can't retrieve clk cdev1\n"); | ||
131 | ret = PTR_ERR(data->clk_cdev1); | ||
132 | goto err_put_pll_a_out0; | ||
133 | } | ||
134 | |||
135 | return 0; | ||
136 | |||
137 | err_put_pll_a_out0: | ||
138 | clk_put(data->clk_pll_a_out0); | ||
139 | err_put_pll_a: | ||
140 | clk_put(data->clk_pll_a); | ||
141 | err: | ||
142 | return ret; | ||
143 | } | ||
144 | EXPORT_SYMBOL_GPL(tegra_asoc_utils_init); | ||
145 | |||
146 | void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data) | ||
147 | { | ||
148 | clk_put(data->clk_cdev1); | ||
149 | clk_put(data->clk_pll_a_out0); | ||
150 | clk_put(data->clk_pll_a); | ||
151 | } | ||
152 | EXPORT_SYMBOL_GPL(tegra_asoc_utils_fini); | ||
153 | |||
154 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); | ||
155 | MODULE_DESCRIPTION("Tegra ASoC utility code"); | ||
156 | MODULE_LICENSE("GPL"); | ||
diff --git a/sound/soc/tegra/tegra_asoc_utils.h b/sound/soc/tegra/tegra_asoc_utils.h new file mode 100644 index 000000000000..4818195da25c --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.h | |||
@@ -0,0 +1,45 @@ | |||
1 | /* | ||
2 | * tegra_asoc_utils.h - Definitions for Tegra DAS driver | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010 - NVIDIA, Inc. | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License | ||
9 | * version 2 as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * 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., 51 Franklin St, Fifth Floor, Boston, MA | ||
19 | * 02110-1301 USA | ||
20 | * | ||
21 | */ | ||
22 | |||
23 | #ifndef __TEGRA_ASOC_UTILS_H__ | ||
24 | #define __TEGRA_ASOC_UTILS_H_ | ||
25 | |||
26 | struct clk; | ||
27 | struct device; | ||
28 | |||
29 | struct tegra_asoc_utils_data { | ||
30 | struct device *dev; | ||
31 | struct clk *clk_pll_a; | ||
32 | struct clk *clk_pll_a_out0; | ||
33 | struct clk *clk_cdev1; | ||
34 | int set_baseclock; | ||
35 | int set_mclk; | ||
36 | }; | ||
37 | |||
38 | int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, | ||
39 | int mclk); | ||
40 | int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, | ||
41 | struct device *dev); | ||
42 | void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data); | ||
43 | |||
44 | #endif | ||
45 | |||
diff --git a/sound/soc/tegra/tegra_das.c b/sound/soc/tegra/tegra_das.c new file mode 100644 index 000000000000..9f24ef73f2cb --- /dev/null +++ b/sound/soc/tegra/tegra_das.c | |||
@@ -0,0 +1,265 @@ | |||
1 | /* | ||
2 | * tegra_das.c - Tegra DAS driver | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010 - NVIDIA, Inc. | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License | ||
9 | * version 2 as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * 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., 51 Franklin St, Fifth Floor, Boston, MA | ||
19 | * 02110-1301 USA | ||
20 | * | ||
21 | */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/debugfs.h> | ||
25 | #include <linux/device.h> | ||
26 | #include <linux/platform_device.h> | ||
27 | #include <linux/seq_file.h> | ||
28 | #include <linux/slab.h> | ||
29 | #include <linux/io.h> | ||
30 | #include <mach/iomap.h> | ||
31 | #include <sound/soc.h> | ||
32 | #include "tegra_das.h" | ||
33 | |||
34 | #define DRV_NAME "tegra-das" | ||
35 | |||
36 | static struct tegra_das *das; | ||
37 | |||
38 | static inline void tegra_das_write(u32 reg, u32 val) | ||
39 | { | ||
40 | __raw_writel(val, das->regs + reg); | ||
41 | } | ||
42 | |||
43 | static inline u32 tegra_das_read(u32 reg) | ||
44 | { | ||
45 | return __raw_readl(das->regs + reg); | ||
46 | } | ||
47 | |||
48 | int tegra_das_connect_dap_to_dac(int dap, int dac) | ||
49 | { | ||
50 | u32 addr; | ||
51 | u32 reg; | ||
52 | |||
53 | if (!das) | ||
54 | return -ENODEV; | ||
55 | |||
56 | addr = TEGRA_DAS_DAP_CTRL_SEL + | ||
57 | (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); | ||
58 | reg = dac << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P; | ||
59 | |||
60 | tegra_das_write(addr, reg); | ||
61 | |||
62 | return 0; | ||
63 | } | ||
64 | EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dac); | ||
65 | |||
66 | int tegra_das_connect_dap_to_dap(int dap, int otherdap, int master, | ||
67 | int sdata1rx, int sdata2rx) | ||
68 | { | ||
69 | u32 addr; | ||
70 | u32 reg; | ||
71 | |||
72 | if (!das) | ||
73 | return -ENODEV; | ||
74 | |||
75 | addr = TEGRA_DAS_DAP_CTRL_SEL + | ||
76 | (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); | ||
77 | reg = otherdap << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P | | ||
78 | !!sdata2rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P | | ||
79 | !!sdata1rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P | | ||
80 | !!master << TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P; | ||
81 | |||
82 | tegra_das_write(addr, reg); | ||
83 | |||
84 | return 0; | ||
85 | } | ||
86 | EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dap); | ||
87 | |||
88 | int tegra_das_connect_dac_to_dap(int dac, int dap) | ||
89 | { | ||
90 | u32 addr; | ||
91 | u32 reg; | ||
92 | |||
93 | if (!das) | ||
94 | return -ENODEV; | ||
95 | |||
96 | addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL + | ||
97 | (dac * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); | ||
98 | reg = dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P | | ||
99 | dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P | | ||
100 | dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P; | ||
101 | |||
102 | tegra_das_write(addr, reg); | ||
103 | |||
104 | return 0; | ||
105 | } | ||
106 | EXPORT_SYMBOL_GPL(tegra_das_connect_dac_to_dap); | ||
107 | |||
108 | #ifdef CONFIG_DEBUG_FS | ||
109 | static int tegra_das_show(struct seq_file *s, void *unused) | ||
110 | { | ||
111 | int i; | ||
112 | u32 addr; | ||
113 | u32 reg; | ||
114 | |||
115 | for (i = 0; i < TEGRA_DAS_DAP_CTRL_SEL_COUNT; i++) { | ||
116 | addr = TEGRA_DAS_DAP_CTRL_SEL + | ||
117 | (i * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); | ||
118 | reg = tegra_das_read(addr); | ||
119 | seq_printf(s, "TEGRA_DAS_DAP_CTRL_SEL[%d] = %08x\n", i, reg); | ||
120 | } | ||
121 | |||
122 | for (i = 0; i < TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT; i++) { | ||
123 | addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL + | ||
124 | (i * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); | ||
125 | reg = tegra_das_read(addr); | ||
126 | seq_printf(s, "TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL[%d] = %08x\n", | ||
127 | i, reg); | ||
128 | } | ||
129 | |||
130 | return 0; | ||
131 | } | ||
132 | |||
133 | static int tegra_das_debug_open(struct inode *inode, struct file *file) | ||
134 | { | ||
135 | return single_open(file, tegra_das_show, inode->i_private); | ||
136 | } | ||
137 | |||
138 | static const struct file_operations tegra_das_debug_fops = { | ||
139 | .open = tegra_das_debug_open, | ||
140 | .read = seq_read, | ||
141 | .llseek = seq_lseek, | ||
142 | .release = single_release, | ||
143 | }; | ||
144 | |||
145 | static void tegra_das_debug_add(struct tegra_das *das) | ||
146 | { | ||
147 | das->debug = debugfs_create_file(DRV_NAME, S_IRUGO, | ||
148 | snd_soc_debugfs_root, das, | ||
149 | &tegra_das_debug_fops); | ||
150 | } | ||
151 | |||
152 | static void tegra_das_debug_remove(struct tegra_das *das) | ||
153 | { | ||
154 | if (das->debug) | ||
155 | debugfs_remove(das->debug); | ||
156 | } | ||
157 | #else | ||
158 | static inline void tegra_das_debug_add(struct tegra_das *das) | ||
159 | { | ||
160 | } | ||
161 | |||
162 | static inline void tegra_das_debug_remove(struct tegra_das *das) | ||
163 | { | ||
164 | } | ||
165 | #endif | ||
166 | |||
167 | static int __devinit tegra_das_probe(struct platform_device *pdev) | ||
168 | { | ||
169 | struct resource *res, *region; | ||
170 | int ret = 0; | ||
171 | |||
172 | if (das) | ||
173 | return -ENODEV; | ||
174 | |||
175 | das = kzalloc(sizeof(struct tegra_das), GFP_KERNEL); | ||
176 | if (!das) { | ||
177 | dev_err(&pdev->dev, "Can't allocate tegra_das\n"); | ||
178 | ret = -ENOMEM; | ||
179 | goto exit; | ||
180 | } | ||
181 | das->dev = &pdev->dev; | ||
182 | |||
183 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
184 | if (!res) { | ||
185 | dev_err(&pdev->dev, "No memory resource\n"); | ||
186 | ret = -ENODEV; | ||
187 | goto err_free; | ||
188 | } | ||
189 | |||
190 | region = request_mem_region(res->start, resource_size(res), | ||
191 | pdev->name); | ||
192 | if (!region) { | ||
193 | dev_err(&pdev->dev, "Memory region already claimed\n"); | ||
194 | ret = -EBUSY; | ||
195 | goto err_free; | ||
196 | } | ||
197 | |||
198 | das->regs = ioremap(res->start, resource_size(res)); | ||
199 | if (!das->regs) { | ||
200 | dev_err(&pdev->dev, "ioremap failed\n"); | ||
201 | ret = -ENOMEM; | ||
202 | goto err_release; | ||
203 | } | ||
204 | |||
205 | tegra_das_debug_add(das); | ||
206 | |||
207 | platform_set_drvdata(pdev, das); | ||
208 | |||
209 | return 0; | ||
210 | |||
211 | err_release: | ||
212 | release_mem_region(res->start, resource_size(res)); | ||
213 | err_free: | ||
214 | kfree(das); | ||
215 | das = 0; | ||
216 | exit: | ||
217 | return ret; | ||
218 | } | ||
219 | |||
220 | static int __devexit tegra_das_remove(struct platform_device *pdev) | ||
221 | { | ||
222 | struct resource *res; | ||
223 | |||
224 | if (!das) | ||
225 | return -ENODEV; | ||
226 | |||
227 | platform_set_drvdata(pdev, NULL); | ||
228 | |||
229 | tegra_das_debug_remove(das); | ||
230 | |||
231 | iounmap(das->regs); | ||
232 | |||
233 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
234 | release_mem_region(res->start, resource_size(res)); | ||
235 | |||
236 | kfree(das); | ||
237 | das = 0; | ||
238 | |||
239 | return 0; | ||
240 | } | ||
241 | |||
242 | static struct platform_driver tegra_das_driver = { | ||
243 | .probe = tegra_das_probe, | ||
244 | .remove = __devexit_p(tegra_das_remove), | ||
245 | .driver = { | ||
246 | .name = DRV_NAME, | ||
247 | }, | ||
248 | }; | ||
249 | |||
250 | static int __init tegra_das_modinit(void) | ||
251 | { | ||
252 | return platform_driver_register(&tegra_das_driver); | ||
253 | } | ||
254 | module_init(tegra_das_modinit); | ||
255 | |||
256 | static void __exit tegra_das_modexit(void) | ||
257 | { | ||
258 | platform_driver_unregister(&tegra_das_driver); | ||
259 | } | ||
260 | module_exit(tegra_das_modexit); | ||
261 | |||
262 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); | ||
263 | MODULE_DESCRIPTION("Tegra DAS driver"); | ||
264 | MODULE_LICENSE("GPL"); | ||
265 | MODULE_ALIAS("platform:" DRV_NAME); | ||
diff --git a/sound/soc/tegra/tegra_das.h b/sound/soc/tegra/tegra_das.h new file mode 100644 index 000000000000..2c96c7b3c459 --- /dev/null +++ b/sound/soc/tegra/tegra_das.h | |||
@@ -0,0 +1,135 @@ | |||
1 | /* | ||
2 | * tegra_das.h - Definitions for Tegra DAS driver | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010 - NVIDIA, Inc. | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License | ||
9 | * version 2 as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * 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., 51 Franklin St, Fifth Floor, Boston, MA | ||
19 | * 02110-1301 USA | ||
20 | * | ||
21 | */ | ||
22 | |||
23 | #ifndef __TEGRA_DAS_H__ | ||
24 | #define __TEGRA_DAS_H__ | ||
25 | |||
26 | /* Register TEGRA_DAS_DAP_CTRL_SEL */ | ||
27 | #define TEGRA_DAS_DAP_CTRL_SEL 0x00 | ||
28 | #define TEGRA_DAS_DAP_CTRL_SEL_COUNT 5 | ||
29 | #define TEGRA_DAS_DAP_CTRL_SEL_STRIDE 4 | ||
30 | #define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31 | ||
31 | #define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1 | ||
32 | #define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30 | ||
33 | #define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1 | ||
34 | #define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29 | ||
35 | #define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1 | ||
36 | #define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0 | ||
37 | #define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5 | ||
38 | |||
39 | /* Values for field TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */ | ||
40 | #define TEGRA_DAS_DAP_SEL_DAC1 0 | ||
41 | #define TEGRA_DAS_DAP_SEL_DAC2 1 | ||
42 | #define TEGRA_DAS_DAP_SEL_DAC3 2 | ||
43 | #define TEGRA_DAS_DAP_SEL_DAP1 16 | ||
44 | #define TEGRA_DAS_DAP_SEL_DAP2 17 | ||
45 | #define TEGRA_DAS_DAP_SEL_DAP3 18 | ||
46 | #define TEGRA_DAS_DAP_SEL_DAP4 19 | ||
47 | #define TEGRA_DAS_DAP_SEL_DAP5 20 | ||
48 | |||
49 | /* Register TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL */ | ||
50 | #define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL 0x40 | ||
51 | #define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3 | ||
52 | #define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4 | ||
53 | #define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28 | ||
54 | #define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4 | ||
55 | #define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24 | ||
56 | #define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4 | ||
57 | #define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0 | ||
58 | #define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4 | ||
59 | |||
60 | /* | ||
61 | * Values for: | ||
62 | * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL | ||
63 | * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL | ||
64 | * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL | ||
65 | */ | ||
66 | #define TEGRA_DAS_DAC_SEL_DAP1 0 | ||
67 | #define TEGRA_DAS_DAC_SEL_DAP2 1 | ||
68 | #define TEGRA_DAS_DAC_SEL_DAP3 2 | ||
69 | #define TEGRA_DAS_DAC_SEL_DAP4 3 | ||
70 | #define TEGRA_DAS_DAC_SEL_DAP5 4 | ||
71 | |||
72 | /* | ||
73 | * Names/IDs of the DACs/DAPs. | ||
74 | */ | ||
75 | |||
76 | #define TEGRA_DAS_DAP_ID_1 0 | ||
77 | #define TEGRA_DAS_DAP_ID_2 1 | ||
78 | #define TEGRA_DAS_DAP_ID_3 2 | ||
79 | #define TEGRA_DAS_DAP_ID_4 3 | ||
80 | #define TEGRA_DAS_DAP_ID_5 4 | ||
81 | |||
82 | #define TEGRA_DAS_DAC_ID_1 0 | ||
83 | #define TEGRA_DAS_DAC_ID_2 1 | ||
84 | #define TEGRA_DAS_DAC_ID_3 2 | ||
85 | |||
86 | struct tegra_das { | ||
87 | struct device *dev; | ||
88 | void __iomem *regs; | ||
89 | struct dentry *debug; | ||
90 | }; | ||
91 | |||
92 | /* | ||
93 | * Terminology: | ||
94 | * DAS: Digital audio switch (HW module controlled by this driver) | ||
95 | * DAP: Digital audio port (port/pins on Tegra device) | ||
96 | * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere) | ||
97 | * | ||
98 | * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific | ||
99 | * DAC, or another DAP. When DAPs are connected, one must be the master and | ||
100 | * one the slave. Each DAC allows selection of a specific DAP for input, to | ||
101 | * cater for the case where N DAPs are connected to 1 DAC for broadcast | ||
102 | * output. | ||
103 | * | ||
104 | * This driver is dumb; no attempt is made to ensure that a valid routing | ||
105 | * configuration is programmed. | ||
106 | */ | ||
107 | |||
108 | /* | ||
109 | * Connect a DAP to to a DAC | ||
110 | * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_* | ||
111 | * dac_sel: DAC to connect to: TEGRA_DAS_DAP_SEL_DAC* | ||
112 | */ | ||
113 | extern int tegra_das_connect_dap_to_dac(int dap_id, int dac_sel); | ||
114 | |||
115 | /* | ||
116 | * Connect a DAP to to another DAP | ||
117 | * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_* | ||
118 | * other_dap_sel: DAP to connect to: TEGRA_DAS_DAP_SEL_DAP* | ||
119 | * master: Is this DAP the master (1) or slave (0) | ||
120 | * sdata1rx: Is this DAP's SDATA1 pin RX (1) or TX (0) | ||
121 | * sdata2rx: Is this DAP's SDATA2 pin RX (1) or TX (0) | ||
122 | */ | ||
123 | extern int tegra_das_connect_dap_to_dap(int dap_id, int other_dap_sel, | ||
124 | int master, int sdata1rx, | ||
125 | int sdata2rx); | ||
126 | |||
127 | /* | ||
128 | * Connect a DAC's input to a DAP | ||
129 | * (DAC outputs are selected by the DAP) | ||
130 | * dac_id: DAC ID to connect: TEGRA_DAS_DAC_ID_* | ||
131 | * dap_sel: DAP to receive input from: TEGRA_DAS_DAC_SEL_DAP* | ||
132 | */ | ||
133 | extern int tegra_das_connect_dac_to_dap(int dac_id, int dap_sel); | ||
134 | |||
135 | #endif | ||
diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c new file mode 100644 index 000000000000..95f03c10b4f7 --- /dev/null +++ b/sound/soc/tegra/tegra_i2s.c | |||
@@ -0,0 +1,509 @@ | |||
1 | /* | ||
2 | * tegra_i2s.c - Tegra I2S driver | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010 - NVIDIA, Inc. | ||
6 | * | ||
7 | * Based on code copyright/by: | ||
8 | * | ||
9 | * Copyright (c) 2009-2010, NVIDIA Corporation. | ||
10 | * Scott Peterson <speterson@nvidia.com> | ||
11 | * | ||
12 | * Copyright (C) 2010 Google, Inc. | ||
13 | * Iliyan Malchev <malchev@google.com> | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or | ||
16 | * modify it under the terms of the GNU General Public License | ||
17 | * version 2 as published by the Free Software Foundation. | ||
18 | * | ||
19 | * This program is distributed in the hope that it will be useful, but | ||
20 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
22 | * General Public License for more details. | ||
23 | * | ||
24 | * You should have received a copy of the GNU General Public License | ||
25 | * along with this program; if not, write to the Free Software | ||
26 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
27 | * 02110-1301 USA | ||
28 | * | ||
29 | */ | ||
30 | |||
31 | #include <linux/clk.h> | ||
32 | #include <linux/module.h> | ||
33 | #include <linux/debugfs.h> | ||
34 | #include <linux/device.h> | ||
35 | #include <linux/platform_device.h> | ||
36 | #include <linux/seq_file.h> | ||
37 | #include <linux/slab.h> | ||
38 | #include <linux/io.h> | ||
39 | #include <mach/iomap.h> | ||
40 | #include <sound/core.h> | ||
41 | #include <sound/pcm.h> | ||
42 | #include <sound/pcm_params.h> | ||
43 | #include <sound/soc.h> | ||
44 | |||
45 | #include "tegra_das.h" | ||
46 | #include "tegra_i2s.h" | ||
47 | |||
48 | #define DRV_NAME "tegra-i2s" | ||
49 | |||
50 | static inline void tegra_i2s_write(struct tegra_i2s *i2s, u32 reg, u32 val) | ||
51 | { | ||
52 | __raw_writel(val, i2s->regs + reg); | ||
53 | } | ||
54 | |||
55 | static inline u32 tegra_i2s_read(struct tegra_i2s *i2s, u32 reg) | ||
56 | { | ||
57 | return __raw_readl(i2s->regs + reg); | ||
58 | } | ||
59 | |||
60 | #ifdef CONFIG_DEBUG_FS | ||
61 | static int tegra_i2s_show(struct seq_file *s, void *unused) | ||
62 | { | ||
63 | #define REG(r) { r, #r } | ||
64 | static const struct { | ||
65 | int offset; | ||
66 | const char *name; | ||
67 | } regs[] = { | ||
68 | REG(TEGRA_I2S_CTRL), | ||
69 | REG(TEGRA_I2S_STATUS), | ||
70 | REG(TEGRA_I2S_TIMING), | ||
71 | REG(TEGRA_I2S_FIFO_SCR), | ||
72 | REG(TEGRA_I2S_PCM_CTRL), | ||
73 | REG(TEGRA_I2S_NW_CTRL), | ||
74 | REG(TEGRA_I2S_TDM_CTRL), | ||
75 | REG(TEGRA_I2S_TDM_TX_RX_CTRL), | ||
76 | }; | ||
77 | #undef REG | ||
78 | |||
79 | struct tegra_i2s *i2s = s->private; | ||
80 | int i; | ||
81 | |||
82 | for (i = 0; i < ARRAY_SIZE(regs); i++) { | ||
83 | u32 val = tegra_i2s_read(i2s, regs[i].offset); | ||
84 | seq_printf(s, "%s = %08x\n", regs[i].name, val); | ||
85 | } | ||
86 | |||
87 | return 0; | ||
88 | } | ||
89 | |||
90 | static int tegra_i2s_debug_open(struct inode *inode, struct file *file) | ||
91 | { | ||
92 | return single_open(file, tegra_i2s_show, inode->i_private); | ||
93 | } | ||
94 | |||
95 | static const struct file_operations tegra_i2s_debug_fops = { | ||
96 | .open = tegra_i2s_debug_open, | ||
97 | .read = seq_read, | ||
98 | .llseek = seq_lseek, | ||
99 | .release = single_release, | ||
100 | }; | ||
101 | |||
102 | static void tegra_i2s_debug_add(struct tegra_i2s *i2s, int id) | ||
103 | { | ||
104 | char name[] = DRV_NAME ".0"; | ||
105 | |||
106 | snprintf(name, sizeof(name), DRV_NAME".%1d", id); | ||
107 | i2s->debug = debugfs_create_file(name, S_IRUGO, snd_soc_debugfs_root, | ||
108 | i2s, &tegra_i2s_debug_fops); | ||
109 | } | ||
110 | |||
111 | static void tegra_i2s_debug_remove(struct tegra_i2s *i2s) | ||
112 | { | ||
113 | if (i2s->debug) | ||
114 | debugfs_remove(i2s->debug); | ||
115 | } | ||
116 | #else | ||
117 | static inline void tegra_i2s_debug_add(struct tegra_i2s *i2s, int id) | ||
118 | { | ||
119 | } | ||
120 | |||
121 | static inline void tegra_i2s_debug_remove(struct tegra_i2s *i2s) | ||
122 | { | ||
123 | } | ||
124 | #endif | ||
125 | |||
126 | static int tegra_i2s_set_fmt(struct snd_soc_dai *dai, | ||
127 | unsigned int fmt) | ||
128 | { | ||
129 | struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); | ||
130 | |||
131 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | ||
132 | case SND_SOC_DAIFMT_NB_NF: | ||
133 | break; | ||
134 | default: | ||
135 | return -EINVAL; | ||
136 | } | ||
137 | |||
138 | i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_MASTER_ENABLE; | ||
139 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
140 | case SND_SOC_DAIFMT_CBS_CFS: | ||
141 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_MASTER_ENABLE; | ||
142 | break; | ||
143 | case SND_SOC_DAIFMT_CBM_CFM: | ||
144 | break; | ||
145 | default: | ||
146 | return -EINVAL; | ||
147 | } | ||
148 | |||
149 | i2s->reg_ctrl &= ~(TEGRA_I2S_CTRL_BIT_FORMAT_MASK | | ||
150 | TEGRA_I2S_CTRL_LRCK_MASK); | ||
151 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
152 | case SND_SOC_DAIFMT_DSP_A: | ||
153 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP; | ||
154 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; | ||
155 | break; | ||
156 | case SND_SOC_DAIFMT_DSP_B: | ||
157 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP; | ||
158 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_R_LOW; | ||
159 | break; | ||
160 | case SND_SOC_DAIFMT_I2S: | ||
161 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_I2S; | ||
162 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; | ||
163 | break; | ||
164 | case SND_SOC_DAIFMT_RIGHT_J: | ||
165 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_RJM; | ||
166 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; | ||
167 | break; | ||
168 | case SND_SOC_DAIFMT_LEFT_J: | ||
169 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_LJM; | ||
170 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; | ||
171 | break; | ||
172 | default: | ||
173 | return -EINVAL; | ||
174 | } | ||
175 | |||
176 | return 0; | ||
177 | } | ||
178 | |||
179 | static int tegra_i2s_hw_params(struct snd_pcm_substream *substream, | ||
180 | struct snd_pcm_hw_params *params, | ||
181 | struct snd_soc_dai *dai) | ||
182 | { | ||
183 | struct device *dev = substream->pcm->card->dev; | ||
184 | struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); | ||
185 | u32 reg; | ||
186 | int ret, sample_size, srate, i2sclock, bitcnt; | ||
187 | |||
188 | i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_BIT_SIZE_MASK; | ||
189 | switch (params_format(params)) { | ||
190 | case SNDRV_PCM_FORMAT_S16_LE: | ||
191 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_16; | ||
192 | sample_size = 16; | ||
193 | break; | ||
194 | case SNDRV_PCM_FORMAT_S24_LE: | ||
195 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_24; | ||
196 | sample_size = 24; | ||
197 | break; | ||
198 | case SNDRV_PCM_FORMAT_S32_LE: | ||
199 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_32; | ||
200 | sample_size = 32; | ||
201 | break; | ||
202 | default: | ||
203 | return -EINVAL; | ||
204 | } | ||
205 | |||
206 | srate = params_rate(params); | ||
207 | |||
208 | /* Final "* 2" required by Tegra hardware */ | ||
209 | i2sclock = srate * params_channels(params) * sample_size * 2; | ||
210 | |||
211 | ret = clk_set_rate(i2s->clk_i2s, i2sclock); | ||
212 | if (ret) { | ||
213 | dev_err(dev, "Can't set I2S clock rate: %d\n", ret); | ||
214 | return ret; | ||
215 | } | ||
216 | |||
217 | bitcnt = (i2sclock / (2 * srate)) - 1; | ||
218 | if (bitcnt < 0 || bitcnt > TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) | ||
219 | return -EINVAL; | ||
220 | reg = bitcnt << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; | ||
221 | |||
222 | if (i2sclock % (2 * srate)) | ||
223 | reg |= TEGRA_I2S_TIMING_NON_SYM_ENABLE; | ||
224 | |||
225 | if (!i2s->clk_refs) | ||
226 | clk_enable(i2s->clk_i2s); | ||
227 | |||
228 | tegra_i2s_write(i2s, TEGRA_I2S_TIMING, reg); | ||
229 | |||
230 | tegra_i2s_write(i2s, TEGRA_I2S_FIFO_SCR, | ||
231 | TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS | | ||
232 | TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS); | ||
233 | |||
234 | if (!i2s->clk_refs) | ||
235 | clk_disable(i2s->clk_i2s); | ||
236 | |||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | static void tegra_i2s_start_playback(struct tegra_i2s *i2s) | ||
241 | { | ||
242 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO1_ENABLE; | ||
243 | tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); | ||
244 | } | ||
245 | |||
246 | static void tegra_i2s_stop_playback(struct tegra_i2s *i2s) | ||
247 | { | ||
248 | i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO1_ENABLE; | ||
249 | tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); | ||
250 | } | ||
251 | |||
252 | static void tegra_i2s_start_capture(struct tegra_i2s *i2s) | ||
253 | { | ||
254 | i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO2_ENABLE; | ||
255 | tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); | ||
256 | } | ||
257 | |||
258 | static void tegra_i2s_stop_capture(struct tegra_i2s *i2s) | ||
259 | { | ||
260 | i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO2_ENABLE; | ||
261 | tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); | ||
262 | } | ||
263 | |||
264 | static int tegra_i2s_trigger(struct snd_pcm_substream *substream, int cmd, | ||
265 | struct snd_soc_dai *dai) | ||
266 | { | ||
267 | struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); | ||
268 | |||
269 | switch (cmd) { | ||
270 | case SNDRV_PCM_TRIGGER_START: | ||
271 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
272 | case SNDRV_PCM_TRIGGER_RESUME: | ||
273 | if (!i2s->clk_refs) | ||
274 | clk_enable(i2s->clk_i2s); | ||
275 | i2s->clk_refs++; | ||
276 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
277 | tegra_i2s_start_playback(i2s); | ||
278 | else | ||
279 | tegra_i2s_start_capture(i2s); | ||
280 | break; | ||
281 | case SNDRV_PCM_TRIGGER_STOP: | ||
282 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
283 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
284 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
285 | tegra_i2s_stop_playback(i2s); | ||
286 | else | ||
287 | tegra_i2s_stop_capture(i2s); | ||
288 | i2s->clk_refs--; | ||
289 | if (!i2s->clk_refs) | ||
290 | clk_disable(i2s->clk_i2s); | ||
291 | break; | ||
292 | default: | ||
293 | return -EINVAL; | ||
294 | } | ||
295 | |||
296 | return 0; | ||
297 | } | ||
298 | |||
299 | static int tegra_i2s_probe(struct snd_soc_dai *dai) | ||
300 | { | ||
301 | struct tegra_i2s * i2s = snd_soc_dai_get_drvdata(dai); | ||
302 | |||
303 | dai->capture_dma_data = &i2s->capture_dma_data; | ||
304 | dai->playback_dma_data = &i2s->playback_dma_data; | ||
305 | |||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static struct snd_soc_dai_ops tegra_i2s_dai_ops = { | ||
310 | .set_fmt = tegra_i2s_set_fmt, | ||
311 | .hw_params = tegra_i2s_hw_params, | ||
312 | .trigger = tegra_i2s_trigger, | ||
313 | }; | ||
314 | |||
315 | struct snd_soc_dai_driver tegra_i2s_dai[] = { | ||
316 | { | ||
317 | .name = DRV_NAME ".0", | ||
318 | .probe = tegra_i2s_probe, | ||
319 | .playback = { | ||
320 | .channels_min = 2, | ||
321 | .channels_max = 2, | ||
322 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
323 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
324 | }, | ||
325 | .capture = { | ||
326 | .channels_min = 2, | ||
327 | .channels_max = 2, | ||
328 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
329 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
330 | }, | ||
331 | .ops = &tegra_i2s_dai_ops, | ||
332 | .symmetric_rates = 1, | ||
333 | }, | ||
334 | { | ||
335 | .name = DRV_NAME ".1", | ||
336 | .probe = tegra_i2s_probe, | ||
337 | .playback = { | ||
338 | .channels_min = 2, | ||
339 | .channels_max = 2, | ||
340 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
341 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
342 | }, | ||
343 | .capture = { | ||
344 | .channels_min = 2, | ||
345 | .channels_max = 2, | ||
346 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
347 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
348 | }, | ||
349 | .ops = &tegra_i2s_dai_ops, | ||
350 | .symmetric_rates = 1, | ||
351 | }, | ||
352 | }; | ||
353 | |||
354 | static __devinit int tegra_i2s_platform_probe(struct platform_device *pdev) | ||
355 | { | ||
356 | struct tegra_i2s * i2s; | ||
357 | char clk_name[12]; /* tegra-i2s.0 */ | ||
358 | struct resource *mem, *memregion, *dmareq; | ||
359 | int ret; | ||
360 | |||
361 | if ((pdev->id < 0) || | ||
362 | (pdev->id >= ARRAY_SIZE(tegra_i2s_dai))) { | ||
363 | dev_err(&pdev->dev, "ID %d out of range\n", pdev->id); | ||
364 | return -EINVAL; | ||
365 | } | ||
366 | |||
367 | /* | ||
368 | * FIXME: Until a codec driver exists for the tegra DAS, hard-code a | ||
369 | * 1:1 mapping between audio controllers and audio ports. | ||
370 | */ | ||
371 | ret = tegra_das_connect_dap_to_dac(TEGRA_DAS_DAP_ID_1 + pdev->id, | ||
372 | TEGRA_DAS_DAP_SEL_DAC1 + pdev->id); | ||
373 | if (ret) { | ||
374 | dev_err(&pdev->dev, "Can't set up DAP connection\n"); | ||
375 | return ret; | ||
376 | } | ||
377 | ret = tegra_das_connect_dac_to_dap(TEGRA_DAS_DAC_ID_1 + pdev->id, | ||
378 | TEGRA_DAS_DAC_SEL_DAP1 + pdev->id); | ||
379 | if (ret) { | ||
380 | dev_err(&pdev->dev, "Can't set up DAC connection\n"); | ||
381 | return ret; | ||
382 | } | ||
383 | |||
384 | i2s = kzalloc(sizeof(struct tegra_i2s), GFP_KERNEL); | ||
385 | if (!i2s) { | ||
386 | dev_err(&pdev->dev, "Can't allocate tegra_i2s\n"); | ||
387 | ret = -ENOMEM; | ||
388 | goto exit; | ||
389 | } | ||
390 | dev_set_drvdata(&pdev->dev, i2s); | ||
391 | |||
392 | snprintf(clk_name, sizeof(clk_name), DRV_NAME ".%d", pdev->id); | ||
393 | i2s->clk_i2s = clk_get_sys(clk_name, NULL); | ||
394 | if (IS_ERR(i2s->clk_i2s)) { | ||
395 | dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); | ||
396 | ret = PTR_ERR(i2s->clk_i2s); | ||
397 | goto err_free; | ||
398 | } | ||
399 | |||
400 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
401 | if (!mem) { | ||
402 | dev_err(&pdev->dev, "No memory resource\n"); | ||
403 | ret = -ENODEV; | ||
404 | goto err_clk_put; | ||
405 | } | ||
406 | |||
407 | dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); | ||
408 | if (!dmareq) { | ||
409 | dev_err(&pdev->dev, "No DMA resource\n"); | ||
410 | ret = -ENODEV; | ||
411 | goto err_clk_put; | ||
412 | } | ||
413 | |||
414 | memregion = request_mem_region(mem->start, resource_size(mem), | ||
415 | DRV_NAME); | ||
416 | if (!memregion) { | ||
417 | dev_err(&pdev->dev, "Memory region already claimed\n"); | ||
418 | ret = -EBUSY; | ||
419 | goto err_clk_put; | ||
420 | } | ||
421 | |||
422 | i2s->regs = ioremap(mem->start, resource_size(mem)); | ||
423 | if (!i2s->regs) { | ||
424 | dev_err(&pdev->dev, "ioremap failed\n"); | ||
425 | ret = -ENOMEM; | ||
426 | goto err_release; | ||
427 | } | ||
428 | |||
429 | i2s->capture_dma_data.addr = mem->start + TEGRA_I2S_FIFO2; | ||
430 | i2s->capture_dma_data.wrap = 4; | ||
431 | i2s->capture_dma_data.width = 32; | ||
432 | i2s->capture_dma_data.req_sel = dmareq->start; | ||
433 | |||
434 | i2s->playback_dma_data.addr = mem->start + TEGRA_I2S_FIFO1; | ||
435 | i2s->playback_dma_data.wrap = 4; | ||
436 | i2s->playback_dma_data.width = 32; | ||
437 | i2s->playback_dma_data.req_sel = dmareq->start; | ||
438 | |||
439 | i2s->reg_ctrl = TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED; | ||
440 | |||
441 | ret = snd_soc_register_dai(&pdev->dev, &tegra_i2s_dai[pdev->id]); | ||
442 | if (ret) { | ||
443 | dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); | ||
444 | ret = -ENOMEM; | ||
445 | goto err_unmap; | ||
446 | } | ||
447 | |||
448 | tegra_i2s_debug_add(i2s, pdev->id); | ||
449 | |||
450 | return 0; | ||
451 | |||
452 | err_unmap: | ||
453 | iounmap(i2s->regs); | ||
454 | err_release: | ||
455 | release_mem_region(mem->start, resource_size(mem)); | ||
456 | err_clk_put: | ||
457 | clk_put(i2s->clk_i2s); | ||
458 | err_free: | ||
459 | kfree(i2s); | ||
460 | exit: | ||
461 | return ret; | ||
462 | } | ||
463 | |||
464 | static int __devexit tegra_i2s_platform_remove(struct platform_device *pdev) | ||
465 | { | ||
466 | struct tegra_i2s *i2s = dev_get_drvdata(&pdev->dev); | ||
467 | struct resource *res; | ||
468 | |||
469 | snd_soc_unregister_dai(&pdev->dev); | ||
470 | |||
471 | tegra_i2s_debug_remove(i2s); | ||
472 | |||
473 | iounmap(i2s->regs); | ||
474 | |||
475 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
476 | release_mem_region(res->start, resource_size(res)); | ||
477 | |||
478 | clk_put(i2s->clk_i2s); | ||
479 | |||
480 | kfree(i2s); | ||
481 | |||
482 | return 0; | ||
483 | } | ||
484 | |||
485 | static struct platform_driver tegra_i2s_driver = { | ||
486 | .driver = { | ||
487 | .name = DRV_NAME, | ||
488 | .owner = THIS_MODULE, | ||
489 | }, | ||
490 | .probe = tegra_i2s_platform_probe, | ||
491 | .remove = __devexit_p(tegra_i2s_platform_remove), | ||
492 | }; | ||
493 | |||
494 | static int __init snd_tegra_i2s_init(void) | ||
495 | { | ||
496 | return platform_driver_register(&tegra_i2s_driver); | ||
497 | } | ||
498 | module_init(snd_tegra_i2s_init); | ||
499 | |||
500 | static void __exit snd_tegra_i2s_exit(void) | ||
501 | { | ||
502 | platform_driver_unregister(&tegra_i2s_driver); | ||
503 | } | ||
504 | module_exit(snd_tegra_i2s_exit); | ||
505 | |||
506 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); | ||
507 | MODULE_DESCRIPTION("Tegra I2S ASoC driver"); | ||
508 | MODULE_LICENSE("GPL"); | ||
509 | MODULE_ALIAS("platform:" DRV_NAME); | ||
diff --git a/sound/soc/tegra/tegra_i2s.h b/sound/soc/tegra/tegra_i2s.h new file mode 100644 index 000000000000..2b38a096f46c --- /dev/null +++ b/sound/soc/tegra/tegra_i2s.h | |||
@@ -0,0 +1,165 @@ | |||
1 | /* | ||
2 | * tegra_i2s.h - Definitions for Tegra I2S driver | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010 - NVIDIA, Inc. | ||
6 | * | ||
7 | * Based on code copyright/by: | ||
8 | * | ||
9 | * Copyright (c) 2009-2010, NVIDIA Corporation. | ||
10 | * Scott Peterson <speterson@nvidia.com> | ||
11 | * | ||
12 | * Copyright (C) 2010 Google, Inc. | ||
13 | * Iliyan Malchev <malchev@google.com> | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or | ||
16 | * modify it under the terms of the GNU General Public License | ||
17 | * version 2 as published by the Free Software Foundation. | ||
18 | * | ||
19 | * This program is distributed in the hope that it will be useful, but | ||
20 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
22 | * General Public License for more details. | ||
23 | * | ||
24 | * You should have received a copy of the GNU General Public License | ||
25 | * along with this program; if not, write to the Free Software | ||
26 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
27 | * 02110-1301 USA | ||
28 | * | ||
29 | */ | ||
30 | |||
31 | #ifndef __TEGRA_I2S_H__ | ||
32 | #define __TEGRA_I2S_H__ | ||
33 | |||
34 | #include "tegra_pcm.h" | ||
35 | |||
36 | /* Register offsets from TEGRA_I2S1_BASE and TEGRA_I2S2_BASE */ | ||
37 | |||
38 | #define TEGRA_I2S_CTRL 0x00 | ||
39 | #define TEGRA_I2S_STATUS 0x04 | ||
40 | #define TEGRA_I2S_TIMING 0x08 | ||
41 | #define TEGRA_I2S_FIFO_SCR 0x0c | ||
42 | #define TEGRA_I2S_PCM_CTRL 0x10 | ||
43 | #define TEGRA_I2S_NW_CTRL 0x14 | ||
44 | #define TEGRA_I2S_TDM_CTRL 0x20 | ||
45 | #define TEGRA_I2S_TDM_TX_RX_CTRL 0x24 | ||
46 | #define TEGRA_I2S_FIFO1 0x40 | ||
47 | #define TEGRA_I2S_FIFO2 0x80 | ||
48 | |||
49 | /* Fields in TEGRA_I2S_CTRL */ | ||
50 | |||
51 | #define TEGRA_I2S_CTRL_FIFO2_TX_ENABLE (1 << 30) | ||
52 | #define TEGRA_I2S_CTRL_FIFO1_ENABLE (1 << 29) | ||
53 | #define TEGRA_I2S_CTRL_FIFO2_ENABLE (1 << 28) | ||
54 | #define TEGRA_I2S_CTRL_FIFO1_RX_ENABLE (1 << 27) | ||
55 | #define TEGRA_I2S_CTRL_FIFO_LPBK_ENABLE (1 << 26) | ||
56 | #define TEGRA_I2S_CTRL_MASTER_ENABLE (1 << 25) | ||
57 | |||
58 | #define TEGRA_I2S_LRCK_LEFT_LOW 0 | ||
59 | #define TEGRA_I2S_LRCK_RIGHT_LOW 1 | ||
60 | |||
61 | #define TEGRA_I2S_CTRL_LRCK_SHIFT 24 | ||
62 | #define TEGRA_I2S_CTRL_LRCK_MASK (1 << TEGRA_I2S_CTRL_LRCK_SHIFT) | ||
63 | #define TEGRA_I2S_CTRL_LRCK_L_LOW (TEGRA_I2S_LRCK_LEFT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT) | ||
64 | #define TEGRA_I2S_CTRL_LRCK_R_LOW (TEGRA_I2S_LRCK_RIGHT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT) | ||
65 | |||
66 | #define TEGRA_I2S_BIT_FORMAT_I2S 0 | ||
67 | #define TEGRA_I2S_BIT_FORMAT_RJM 1 | ||
68 | #define TEGRA_I2S_BIT_FORMAT_LJM 2 | ||
69 | #define TEGRA_I2S_BIT_FORMAT_DSP 3 | ||
70 | |||
71 | #define TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT 10 | ||
72 | #define TEGRA_I2S_CTRL_BIT_FORMAT_MASK (3 << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) | ||
73 | #define TEGRA_I2S_CTRL_BIT_FORMAT_I2S (TEGRA_I2S_BIT_FORMAT_I2S << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) | ||
74 | #define TEGRA_I2S_CTRL_BIT_FORMAT_RJM (TEGRA_I2S_BIT_FORMAT_RJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) | ||
75 | #define TEGRA_I2S_CTRL_BIT_FORMAT_LJM (TEGRA_I2S_BIT_FORMAT_LJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) | ||
76 | #define TEGRA_I2S_CTRL_BIT_FORMAT_DSP (TEGRA_I2S_BIT_FORMAT_DSP << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) | ||
77 | |||
78 | #define TEGRA_I2S_BIT_SIZE_16 0 | ||
79 | #define TEGRA_I2S_BIT_SIZE_20 1 | ||
80 | #define TEGRA_I2S_BIT_SIZE_24 2 | ||
81 | #define TEGRA_I2S_BIT_SIZE_32 3 | ||
82 | |||
83 | #define TEGRA_I2S_CTRL_BIT_SIZE_SHIFT 8 | ||
84 | #define TEGRA_I2S_CTRL_BIT_SIZE_MASK (3 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) | ||
85 | #define TEGRA_I2S_CTRL_BIT_SIZE_16 (TEGRA_I2S_BIT_SIZE_16 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) | ||
86 | #define TEGRA_I2S_CTRL_BIT_SIZE_20 (TEGRA_I2S_BIT_SIZE_20 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) | ||
87 | #define TEGRA_I2S_CTRL_BIT_SIZE_24 (TEGRA_I2S_BIT_SIZE_24 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) | ||
88 | #define TEGRA_I2S_CTRL_BIT_SIZE_32 (TEGRA_I2S_BIT_SIZE_32 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) | ||
89 | |||
90 | #define TEGRA_I2S_FIFO_16_LSB 0 | ||
91 | #define TEGRA_I2S_FIFO_20_LSB 1 | ||
92 | #define TEGRA_I2S_FIFO_24_LSB 2 | ||
93 | #define TEGRA_I2S_FIFO_32 3 | ||
94 | #define TEGRA_I2S_FIFO_PACKED 7 | ||
95 | |||
96 | #define TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT 4 | ||
97 | #define TEGRA_I2S_CTRL_FIFO_FORMAT_MASK (7 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) | ||
98 | #define TEGRA_I2S_CTRL_FIFO_FORMAT_16_LSB (TEGRA_I2S_FIFO_16_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) | ||
99 | #define TEGRA_I2S_CTRL_FIFO_FORMAT_20_LSB (TEGRA_I2S_FIFO_20_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) | ||
100 | #define TEGRA_I2S_CTRL_FIFO_FORMAT_24_LSB (TEGRA_I2S_FIFO_24_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) | ||
101 | #define TEGRA_I2S_CTRL_FIFO_FORMAT_32 (TEGRA_I2S_FIFO_32 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) | ||
102 | #define TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED (TEGRA_I2S_FIFO_PACKED << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) | ||
103 | |||
104 | #define TEGRA_I2S_CTRL_IE_FIFO1_ERR (1 << 3) | ||
105 | #define TEGRA_I2S_CTRL_IE_FIFO2_ERR (1 << 2) | ||
106 | #define TEGRA_I2S_CTRL_QE_FIFO1 (1 << 1) | ||
107 | #define TEGRA_I2S_CTRL_QE_FIFO2 (1 << 0) | ||
108 | |||
109 | /* Fields in TEGRA_I2S_STATUS */ | ||
110 | |||
111 | #define TEGRA_I2S_STATUS_FIFO1_RDY (1 << 31) | ||
112 | #define TEGRA_I2S_STATUS_FIFO2_RDY (1 << 30) | ||
113 | #define TEGRA_I2S_STATUS_FIFO1_BSY (1 << 29) | ||
114 | #define TEGRA_I2S_STATUS_FIFO2_BSY (1 << 28) | ||
115 | #define TEGRA_I2S_STATUS_FIFO1_ERR (1 << 3) | ||
116 | #define TEGRA_I2S_STATUS_FIFO2_ERR (1 << 2) | ||
117 | #define TEGRA_I2S_STATUS_QS_FIFO1 (1 << 1) | ||
118 | #define TEGRA_I2S_STATUS_QS_FIFO2 (1 << 0) | ||
119 | |||
120 | /* Fields in TEGRA_I2S_TIMING */ | ||
121 | |||
122 | #define TEGRA_I2S_TIMING_NON_SYM_ENABLE (1 << 12) | ||
123 | #define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0 | ||
124 | #define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7fff | ||
125 | #define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) | ||
126 | |||
127 | /* Fields in TEGRA_I2S_FIFO_SCR */ | ||
128 | |||
129 | #define TEGRA_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT 24 | ||
130 | #define TEGRA_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT 16 | ||
131 | #define TEGRA_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK 0x3f | ||
132 | |||
133 | #define TEGRA_I2S_FIFO_SCR_FIFO2_CLR (1 << 12) | ||
134 | #define TEGRA_I2S_FIFO_SCR_FIFO1_CLR (1 << 8) | ||
135 | |||
136 | #define TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT 0 | ||
137 | #define TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS 1 | ||
138 | #define TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS 2 | ||
139 | #define TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS 3 | ||
140 | |||
141 | #define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT 4 | ||
142 | #define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) | ||
143 | #define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) | ||
144 | #define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) | ||
145 | #define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) | ||
146 | #define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) | ||
147 | |||
148 | #define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT 0 | ||
149 | #define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) | ||
150 | #define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) | ||
151 | #define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) | ||
152 | #define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) | ||
153 | #define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) | ||
154 | |||
155 | struct tegra_i2s { | ||
156 | struct clk *clk_i2s; | ||
157 | int clk_refs; | ||
158 | struct tegra_pcm_dma_params capture_dma_data; | ||
159 | struct tegra_pcm_dma_params playback_dma_data; | ||
160 | void __iomem *regs; | ||
161 | struct dentry *debug; | ||
162 | u32 reg_ctrl; | ||
163 | }; | ||
164 | |||
165 | #endif | ||
diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c new file mode 100644 index 000000000000..3c271f953582 --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.c | |||
@@ -0,0 +1,404 @@ | |||
1 | /* | ||
2 | * tegra_pcm.c - Tegra PCM driver | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010 - NVIDIA, Inc. | ||
6 | * | ||
7 | * Based on code copyright/by: | ||
8 | * | ||
9 | * Copyright (c) 2009-2010, NVIDIA Corporation. | ||
10 | * Scott Peterson <speterson@nvidia.com> | ||
11 | * Vijay Mali <vmali@nvidia.com> | ||
12 | * | ||
13 | * Copyright (C) 2010 Google, Inc. | ||
14 | * Iliyan Malchev <malchev@google.com> | ||
15 | * | ||
16 | * This program is free software; you can redistribute it and/or | ||
17 | * modify it under the terms of the GNU General Public License | ||
18 | * version 2 as published by the Free Software Foundation. | ||
19 | * | ||
20 | * This program is distributed in the hope that it will be useful, but | ||
21 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
23 | * General Public License for more details. | ||
24 | * | ||
25 | * You should have received a copy of the GNU General Public License | ||
26 | * along with this program; if not, write to the Free Software | ||
27 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
28 | * 02110-1301 USA | ||
29 | * | ||
30 | */ | ||
31 | |||
32 | #include <linux/module.h> | ||
33 | #include <linux/dma-mapping.h> | ||
34 | #include <linux/slab.h> | ||
35 | #include <sound/core.h> | ||
36 | #include <sound/pcm.h> | ||
37 | #include <sound/pcm_params.h> | ||
38 | #include <sound/soc.h> | ||
39 | |||
40 | #include "tegra_pcm.h" | ||
41 | |||
42 | #define DRV_NAME "tegra-pcm-audio" | ||
43 | |||
44 | static const struct snd_pcm_hardware tegra_pcm_hardware = { | ||
45 | .info = SNDRV_PCM_INFO_MMAP | | ||
46 | SNDRV_PCM_INFO_MMAP_VALID | | ||
47 | SNDRV_PCM_INFO_PAUSE | | ||
48 | SNDRV_PCM_INFO_RESUME | | ||
49 | SNDRV_PCM_INFO_INTERLEAVED, | ||
50 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
51 | .channels_min = 2, | ||
52 | .channels_max = 2, | ||
53 | .period_bytes_min = 1024, | ||
54 | .period_bytes_max = PAGE_SIZE, | ||
55 | .periods_min = 2, | ||
56 | .periods_max = 8, | ||
57 | .buffer_bytes_max = PAGE_SIZE * 8, | ||
58 | .fifo_size = 4, | ||
59 | }; | ||
60 | |||
61 | static void tegra_pcm_queue_dma(struct tegra_runtime_data *prtd) | ||
62 | { | ||
63 | struct snd_pcm_substream *substream = prtd->substream; | ||
64 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
65 | struct tegra_dma_req *dma_req; | ||
66 | unsigned long addr; | ||
67 | |||
68 | dma_req = &prtd->dma_req[prtd->dma_req_idx]; | ||
69 | prtd->dma_req_idx = 1 - prtd->dma_req_idx; | ||
70 | |||
71 | addr = buf->addr + prtd->dma_pos; | ||
72 | prtd->dma_pos += dma_req->size; | ||
73 | if (prtd->dma_pos >= prtd->dma_pos_end) | ||
74 | prtd->dma_pos = 0; | ||
75 | |||
76 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
77 | dma_req->source_addr = addr; | ||
78 | else | ||
79 | dma_req->dest_addr = addr; | ||
80 | |||
81 | tegra_dma_enqueue_req(prtd->dma_chan, dma_req); | ||
82 | } | ||
83 | |||
84 | static void dma_complete_callback(struct tegra_dma_req *req) | ||
85 | { | ||
86 | struct tegra_runtime_data *prtd = (struct tegra_runtime_data *)req->dev; | ||
87 | struct snd_pcm_substream *substream = prtd->substream; | ||
88 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
89 | |||
90 | spin_lock(&prtd->lock); | ||
91 | |||
92 | if (!prtd->running) { | ||
93 | spin_unlock(&prtd->lock); | ||
94 | return; | ||
95 | } | ||
96 | |||
97 | if (++prtd->period_index >= runtime->periods) | ||
98 | prtd->period_index = 0; | ||
99 | |||
100 | tegra_pcm_queue_dma(prtd); | ||
101 | |||
102 | spin_unlock(&prtd->lock); | ||
103 | |||
104 | snd_pcm_period_elapsed(substream); | ||
105 | } | ||
106 | |||
107 | static void setup_dma_tx_request(struct tegra_dma_req *req, | ||
108 | struct tegra_pcm_dma_params * dmap) | ||
109 | { | ||
110 | req->complete = dma_complete_callback; | ||
111 | req->to_memory = false; | ||
112 | req->dest_addr = dmap->addr; | ||
113 | req->dest_wrap = dmap->wrap; | ||
114 | req->source_bus_width = 32; | ||
115 | req->source_wrap = 0; | ||
116 | req->dest_bus_width = dmap->width; | ||
117 | req->req_sel = dmap->req_sel; | ||
118 | } | ||
119 | |||
120 | static void setup_dma_rx_request(struct tegra_dma_req *req, | ||
121 | struct tegra_pcm_dma_params * dmap) | ||
122 | { | ||
123 | req->complete = dma_complete_callback; | ||
124 | req->to_memory = true; | ||
125 | req->source_addr = dmap->addr; | ||
126 | req->dest_wrap = 0; | ||
127 | req->source_bus_width = dmap->width; | ||
128 | req->source_wrap = dmap->wrap; | ||
129 | req->dest_bus_width = 32; | ||
130 | req->req_sel = dmap->req_sel; | ||
131 | } | ||
132 | |||
133 | static int tegra_pcm_open(struct snd_pcm_substream *substream) | ||
134 | { | ||
135 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
136 | struct tegra_runtime_data *prtd; | ||
137 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
138 | struct tegra_pcm_dma_params * dmap; | ||
139 | int ret = 0; | ||
140 | |||
141 | prtd = kzalloc(sizeof(struct tegra_runtime_data), GFP_KERNEL); | ||
142 | if (prtd == NULL) | ||
143 | return -ENOMEM; | ||
144 | |||
145 | runtime->private_data = prtd; | ||
146 | prtd->substream = substream; | ||
147 | |||
148 | spin_lock_init(&prtd->lock); | ||
149 | |||
150 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
151 | dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); | ||
152 | setup_dma_tx_request(&prtd->dma_req[0], dmap); | ||
153 | setup_dma_tx_request(&prtd->dma_req[1], dmap); | ||
154 | } else { | ||
155 | dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); | ||
156 | setup_dma_rx_request(&prtd->dma_req[0], dmap); | ||
157 | setup_dma_rx_request(&prtd->dma_req[1], dmap); | ||
158 | } | ||
159 | |||
160 | prtd->dma_req[0].dev = prtd; | ||
161 | prtd->dma_req[1].dev = prtd; | ||
162 | |||
163 | prtd->dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT); | ||
164 | if (prtd->dma_chan == NULL) { | ||
165 | ret = -ENOMEM; | ||
166 | goto err; | ||
167 | } | ||
168 | |||
169 | /* Set HW params now that initialization is complete */ | ||
170 | snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware); | ||
171 | |||
172 | /* Ensure that buffer size is a multiple of period size */ | ||
173 | ret = snd_pcm_hw_constraint_integer(runtime, | ||
174 | SNDRV_PCM_HW_PARAM_PERIODS); | ||
175 | if (ret < 0) | ||
176 | goto err; | ||
177 | |||
178 | return 0; | ||
179 | |||
180 | err: | ||
181 | if (prtd->dma_chan) { | ||
182 | tegra_dma_free_channel(prtd->dma_chan); | ||
183 | } | ||
184 | |||
185 | kfree(prtd); | ||
186 | |||
187 | return ret; | ||
188 | } | ||
189 | |||
190 | static int tegra_pcm_close(struct snd_pcm_substream *substream) | ||
191 | { | ||
192 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
193 | struct tegra_runtime_data *prtd = runtime->private_data; | ||
194 | |||
195 | tegra_dma_free_channel(prtd->dma_chan); | ||
196 | |||
197 | kfree(prtd); | ||
198 | |||
199 | return 0; | ||
200 | } | ||
201 | |||
202 | static int tegra_pcm_hw_params(struct snd_pcm_substream *substream, | ||
203 | struct snd_pcm_hw_params *params) | ||
204 | { | ||
205 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
206 | struct tegra_runtime_data *prtd = runtime->private_data; | ||
207 | |||
208 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | ||
209 | |||
210 | prtd->dma_req[0].size = params_period_bytes(params); | ||
211 | prtd->dma_req[1].size = prtd->dma_req[0].size; | ||
212 | |||
213 | return 0; | ||
214 | } | ||
215 | |||
216 | static int tegra_pcm_hw_free(struct snd_pcm_substream *substream) | ||
217 | { | ||
218 | snd_pcm_set_runtime_buffer(substream, NULL); | ||
219 | |||
220 | return 0; | ||
221 | } | ||
222 | |||
223 | static int tegra_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
224 | { | ||
225 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
226 | struct tegra_runtime_data *prtd = runtime->private_data; | ||
227 | unsigned long flags; | ||
228 | |||
229 | switch (cmd) { | ||
230 | case SNDRV_PCM_TRIGGER_START: | ||
231 | prtd->dma_pos = 0; | ||
232 | prtd->dma_pos_end = frames_to_bytes(runtime, runtime->periods * runtime->period_size); | ||
233 | prtd->period_index = 0; | ||
234 | prtd->dma_req_idx = 0; | ||
235 | /* Fall-through */ | ||
236 | case SNDRV_PCM_TRIGGER_RESUME: | ||
237 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
238 | spin_lock_irqsave(&prtd->lock, flags); | ||
239 | prtd->running = 1; | ||
240 | spin_unlock_irqrestore(&prtd->lock, flags); | ||
241 | tegra_pcm_queue_dma(prtd); | ||
242 | tegra_pcm_queue_dma(prtd); | ||
243 | break; | ||
244 | case SNDRV_PCM_TRIGGER_STOP: | ||
245 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
246 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
247 | spin_lock_irqsave(&prtd->lock, flags); | ||
248 | prtd->running = 0; | ||
249 | spin_unlock_irqrestore(&prtd->lock, flags); | ||
250 | tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[0]); | ||
251 | tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[1]); | ||
252 | break; | ||
253 | default: | ||
254 | return -EINVAL; | ||
255 | } | ||
256 | |||
257 | return 0; | ||
258 | } | ||
259 | |||
260 | static snd_pcm_uframes_t tegra_pcm_pointer(struct snd_pcm_substream *substream) | ||
261 | { | ||
262 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
263 | struct tegra_runtime_data *prtd = runtime->private_data; | ||
264 | |||
265 | return prtd->period_index * runtime->period_size; | ||
266 | } | ||
267 | |||
268 | |||
269 | static int tegra_pcm_mmap(struct snd_pcm_substream *substream, | ||
270 | struct vm_area_struct *vma) | ||
271 | { | ||
272 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
273 | |||
274 | return dma_mmap_writecombine(substream->pcm->card->dev, vma, | ||
275 | runtime->dma_area, | ||
276 | runtime->dma_addr, | ||
277 | runtime->dma_bytes); | ||
278 | } | ||
279 | |||
280 | static struct snd_pcm_ops tegra_pcm_ops = { | ||
281 | .open = tegra_pcm_open, | ||
282 | .close = tegra_pcm_close, | ||
283 | .ioctl = snd_pcm_lib_ioctl, | ||
284 | .hw_params = tegra_pcm_hw_params, | ||
285 | .hw_free = tegra_pcm_hw_free, | ||
286 | .trigger = tegra_pcm_trigger, | ||
287 | .pointer = tegra_pcm_pointer, | ||
288 | .mmap = tegra_pcm_mmap, | ||
289 | }; | ||
290 | |||
291 | static int tegra_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | ||
292 | { | ||
293 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
294 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
295 | size_t size = tegra_pcm_hardware.buffer_bytes_max; | ||
296 | |||
297 | buf->area = dma_alloc_writecombine(pcm->card->dev, size, | ||
298 | &buf->addr, GFP_KERNEL); | ||
299 | if (!buf->area) | ||
300 | return -ENOMEM; | ||
301 | |||
302 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | ||
303 | buf->dev.dev = pcm->card->dev; | ||
304 | buf->private_data = NULL; | ||
305 | buf->bytes = size; | ||
306 | |||
307 | return 0; | ||
308 | } | ||
309 | |||
310 | static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream) | ||
311 | { | ||
312 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
313 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
314 | |||
315 | if (!buf->area) | ||
316 | return; | ||
317 | |||
318 | dma_free_writecombine(pcm->card->dev, buf->bytes, | ||
319 | buf->area, buf->addr); | ||
320 | buf->area = NULL; | ||
321 | } | ||
322 | |||
323 | static u64 tegra_dma_mask = DMA_BIT_MASK(32); | ||
324 | |||
325 | static int tegra_pcm_new(struct snd_card *card, | ||
326 | struct snd_soc_dai *dai, struct snd_pcm *pcm) | ||
327 | { | ||
328 | int ret = 0; | ||
329 | |||
330 | if (!card->dev->dma_mask) | ||
331 | card->dev->dma_mask = &tegra_dma_mask; | ||
332 | if (!card->dev->coherent_dma_mask) | ||
333 | card->dev->coherent_dma_mask = 0xffffffff; | ||
334 | |||
335 | if (dai->driver->playback.channels_min) { | ||
336 | ret = tegra_pcm_preallocate_dma_buffer(pcm, | ||
337 | SNDRV_PCM_STREAM_PLAYBACK); | ||
338 | if (ret) | ||
339 | goto err; | ||
340 | } | ||
341 | |||
342 | if (dai->driver->capture.channels_min) { | ||
343 | ret = tegra_pcm_preallocate_dma_buffer(pcm, | ||
344 | SNDRV_PCM_STREAM_CAPTURE); | ||
345 | if (ret) | ||
346 | goto err_free_play; | ||
347 | } | ||
348 | |||
349 | return 0; | ||
350 | |||
351 | err_free_play: | ||
352 | tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); | ||
353 | err: | ||
354 | return ret; | ||
355 | } | ||
356 | |||
357 | static void tegra_pcm_free(struct snd_pcm *pcm) | ||
358 | { | ||
359 | tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); | ||
360 | tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); | ||
361 | } | ||
362 | |||
363 | struct snd_soc_platform_driver tegra_pcm_platform = { | ||
364 | .ops = &tegra_pcm_ops, | ||
365 | .pcm_new = tegra_pcm_new, | ||
366 | .pcm_free = tegra_pcm_free, | ||
367 | }; | ||
368 | |||
369 | static int __devinit tegra_pcm_platform_probe(struct platform_device *pdev) | ||
370 | { | ||
371 | return snd_soc_register_platform(&pdev->dev, &tegra_pcm_platform); | ||
372 | } | ||
373 | |||
374 | static int __devexit tegra_pcm_platform_remove(struct platform_device *pdev) | ||
375 | { | ||
376 | snd_soc_unregister_platform(&pdev->dev); | ||
377 | return 0; | ||
378 | } | ||
379 | |||
380 | static struct platform_driver tegra_pcm_driver = { | ||
381 | .driver = { | ||
382 | .name = DRV_NAME, | ||
383 | .owner = THIS_MODULE, | ||
384 | }, | ||
385 | .probe = tegra_pcm_platform_probe, | ||
386 | .remove = __devexit_p(tegra_pcm_platform_remove), | ||
387 | }; | ||
388 | |||
389 | static int __init snd_tegra_pcm_init(void) | ||
390 | { | ||
391 | return platform_driver_register(&tegra_pcm_driver); | ||
392 | } | ||
393 | module_init(snd_tegra_pcm_init); | ||
394 | |||
395 | static void __exit snd_tegra_pcm_exit(void) | ||
396 | { | ||
397 | platform_driver_unregister(&tegra_pcm_driver); | ||
398 | } | ||
399 | module_exit(snd_tegra_pcm_exit); | ||
400 | |||
401 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); | ||
402 | MODULE_DESCRIPTION("Tegra PCM ASoC driver"); | ||
403 | MODULE_LICENSE("GPL"); | ||
404 | MODULE_ALIAS("platform:" DRV_NAME); | ||
diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h new file mode 100644 index 000000000000..dbb90339fe0d --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.h | |||
@@ -0,0 +1,55 @@ | |||
1 | /* | ||
2 | * tegra_pcm.h - Definitions for Tegra PCM driver | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010 - NVIDIA, Inc. | ||
6 | * | ||
7 | * Based on code copyright/by: | ||
8 | * | ||
9 | * Copyright (c) 2009-2010, NVIDIA Corporation. | ||
10 | * Scott Peterson <speterson@nvidia.com> | ||
11 | * | ||
12 | * Copyright (C) 2010 Google, Inc. | ||
13 | * Iliyan Malchev <malchev@google.com> | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or | ||
16 | * modify it under the terms of the GNU General Public License | ||
17 | * version 2 as published by the Free Software Foundation. | ||
18 | * | ||
19 | * This program is distributed in the hope that it will be useful, but | ||
20 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
22 | * General Public License for more details. | ||
23 | * | ||
24 | * You should have received a copy of the GNU General Public License | ||
25 | * along with this program; if not, write to the Free Software | ||
26 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
27 | * 02110-1301 USA | ||
28 | * | ||
29 | */ | ||
30 | |||
31 | #ifndef __TEGRA_PCM_H__ | ||
32 | #define __TEGRA_PCM_H__ | ||
33 | |||
34 | #include <mach/dma.h> | ||
35 | |||
36 | struct tegra_pcm_dma_params { | ||
37 | unsigned long addr; | ||
38 | unsigned long wrap; | ||
39 | unsigned long width; | ||
40 | unsigned long req_sel; | ||
41 | }; | ||
42 | |||
43 | struct tegra_runtime_data { | ||
44 | struct snd_pcm_substream *substream; | ||
45 | spinlock_t lock; | ||
46 | int running; | ||
47 | int dma_pos; | ||
48 | int dma_pos_end; | ||
49 | int period_index; | ||
50 | int dma_req_idx; | ||
51 | struct tegra_dma_req dma_req[2]; | ||
52 | struct tegra_dma_channel *dma_chan; | ||
53 | }; | ||
54 | |||
55 | #endif | ||
diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c new file mode 100644 index 000000000000..0d6738a8b29a --- /dev/null +++ b/sound/soc/tegra/tegra_wm8903.c | |||
@@ -0,0 +1,475 @@ | |||
1 | /* | ||
2 | * tegra_wm8903.c - Tegra machine ASoC driver for boards using WM8903 codec. | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010-2011 - NVIDIA, Inc. | ||
6 | * | ||
7 | * Based on code copyright/by: | ||
8 | * | ||
9 | * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. | ||
10 | * | ||
11 | * Copyright 2007 Wolfson Microelectronics PLC. | ||
12 | * Author: Graeme Gregory | ||
13 | * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or | ||
16 | * modify it under the terms of the GNU General Public License | ||
17 | * version 2 as published by the Free Software Foundation. | ||
18 | * | ||
19 | * This program is distributed in the hope that it will be useful, but | ||
20 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
22 | * General Public License for more details. | ||
23 | * | ||
24 | * You should have received a copy of the GNU General Public License | ||
25 | * along with this program; if not, write to the Free Software | ||
26 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
27 | * 02110-1301 USA | ||
28 | * | ||
29 | */ | ||
30 | |||
31 | #include <asm/mach-types.h> | ||
32 | |||
33 | #include <linux/module.h> | ||
34 | #include <linux/platform_device.h> | ||
35 | #include <linux/slab.h> | ||
36 | #include <linux/gpio.h> | ||
37 | |||
38 | #include <mach/tegra_wm8903_pdata.h> | ||
39 | |||
40 | #include <sound/core.h> | ||
41 | #include <sound/jack.h> | ||
42 | #include <sound/pcm.h> | ||
43 | #include <sound/pcm_params.h> | ||
44 | #include <sound/soc.h> | ||
45 | |||
46 | #include "../codecs/wm8903.h" | ||
47 | |||
48 | #include "tegra_das.h" | ||
49 | #include "tegra_i2s.h" | ||
50 | #include "tegra_pcm.h" | ||
51 | #include "tegra_asoc_utils.h" | ||
52 | |||
53 | #define DRV_NAME "tegra-snd-wm8903" | ||
54 | |||
55 | #define GPIO_SPKR_EN BIT(0) | ||
56 | #define GPIO_HP_MUTE BIT(1) | ||
57 | #define GPIO_INT_MIC_EN BIT(2) | ||
58 | #define GPIO_EXT_MIC_EN BIT(3) | ||
59 | |||
60 | struct tegra_wm8903 { | ||
61 | struct tegra_asoc_utils_data util_data; | ||
62 | struct tegra_wm8903_platform_data *pdata; | ||
63 | int gpio_requested; | ||
64 | }; | ||
65 | |||
66 | static int tegra_wm8903_hw_params(struct snd_pcm_substream *substream, | ||
67 | struct snd_pcm_hw_params *params) | ||
68 | { | ||
69 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
70 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | ||
71 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | ||
72 | struct snd_soc_codec *codec = rtd->codec; | ||
73 | struct snd_soc_card *card = codec->card; | ||
74 | struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); | ||
75 | int srate, mclk; | ||
76 | int err; | ||
77 | |||
78 | srate = params_rate(params); | ||
79 | switch (srate) { | ||
80 | case 64000: | ||
81 | case 88200: | ||
82 | case 96000: | ||
83 | mclk = 128 * srate; | ||
84 | break; | ||
85 | default: | ||
86 | mclk = 256 * srate; | ||
87 | break; | ||
88 | } | ||
89 | /* FIXME: Codec only requires >= 3MHz if OSR==0 */ | ||
90 | while (mclk < 6000000) | ||
91 | mclk *= 2; | ||
92 | |||
93 | err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); | ||
94 | if (err < 0) { | ||
95 | dev_err(card->dev, "Can't configure clocks\n"); | ||
96 | return err; | ||
97 | } | ||
98 | |||
99 | err = snd_soc_dai_set_fmt(codec_dai, | ||
100 | SND_SOC_DAIFMT_I2S | | ||
101 | SND_SOC_DAIFMT_NB_NF | | ||
102 | SND_SOC_DAIFMT_CBS_CFS); | ||
103 | if (err < 0) { | ||
104 | dev_err(card->dev, "codec_dai fmt not set\n"); | ||
105 | return err; | ||
106 | } | ||
107 | |||
108 | err = snd_soc_dai_set_fmt(cpu_dai, | ||
109 | SND_SOC_DAIFMT_I2S | | ||
110 | SND_SOC_DAIFMT_NB_NF | | ||
111 | SND_SOC_DAIFMT_CBS_CFS); | ||
112 | if (err < 0) { | ||
113 | dev_err(card->dev, "cpu_dai fmt not set\n"); | ||
114 | return err; | ||
115 | } | ||
116 | |||
117 | err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, | ||
118 | SND_SOC_CLOCK_IN); | ||
119 | if (err < 0) { | ||
120 | dev_err(card->dev, "codec_dai clock not set\n"); | ||
121 | return err; | ||
122 | } | ||
123 | |||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | static struct snd_soc_ops tegra_wm8903_ops = { | ||
128 | .hw_params = tegra_wm8903_hw_params, | ||
129 | }; | ||
130 | |||
131 | static struct snd_soc_jack tegra_wm8903_hp_jack; | ||
132 | |||
133 | static struct snd_soc_jack_pin tegra_wm8903_hp_jack_pins[] = { | ||
134 | { | ||
135 | .pin = "Headphone Jack", | ||
136 | .mask = SND_JACK_HEADPHONE, | ||
137 | }, | ||
138 | }; | ||
139 | |||
140 | static struct snd_soc_jack_gpio tegra_wm8903_hp_jack_gpio = { | ||
141 | .name = "headphone detect", | ||
142 | .report = SND_JACK_HEADPHONE, | ||
143 | .debounce_time = 150, | ||
144 | .invert = 1, | ||
145 | }; | ||
146 | |||
147 | static struct snd_soc_jack tegra_wm8903_mic_jack; | ||
148 | |||
149 | static struct snd_soc_jack_pin tegra_wm8903_mic_jack_pins[] = { | ||
150 | { | ||
151 | .pin = "Mic Jack", | ||
152 | .mask = SND_JACK_MICROPHONE, | ||
153 | }, | ||
154 | }; | ||
155 | |||
156 | static int tegra_wm8903_event_int_spk(struct snd_soc_dapm_widget *w, | ||
157 | struct snd_kcontrol *k, int event) | ||
158 | { | ||
159 | struct snd_soc_dapm_context *dapm = w->dapm; | ||
160 | struct snd_soc_card *card = dapm->card; | ||
161 | struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); | ||
162 | struct tegra_wm8903_platform_data *pdata = machine->pdata; | ||
163 | |||
164 | if (!(machine->gpio_requested & GPIO_SPKR_EN)) | ||
165 | return 0; | ||
166 | |||
167 | gpio_set_value_cansleep(pdata->gpio_spkr_en, | ||
168 | SND_SOC_DAPM_EVENT_ON(event)); | ||
169 | |||
170 | return 0; | ||
171 | } | ||
172 | |||
173 | static int tegra_wm8903_event_hp(struct snd_soc_dapm_widget *w, | ||
174 | struct snd_kcontrol *k, int event) | ||
175 | { | ||
176 | struct snd_soc_dapm_context *dapm = w->dapm; | ||
177 | struct snd_soc_card *card = dapm->card; | ||
178 | struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); | ||
179 | struct tegra_wm8903_platform_data *pdata = machine->pdata; | ||
180 | |||
181 | if (!(machine->gpio_requested & GPIO_HP_MUTE)) | ||
182 | return 0; | ||
183 | |||
184 | gpio_set_value_cansleep(pdata->gpio_hp_mute, | ||
185 | !SND_SOC_DAPM_EVENT_ON(event)); | ||
186 | |||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | static const struct snd_soc_dapm_widget tegra_wm8903_dapm_widgets[] = { | ||
191 | SND_SOC_DAPM_SPK("Int Spk", tegra_wm8903_event_int_spk), | ||
192 | SND_SOC_DAPM_HP("Headphone Jack", tegra_wm8903_event_hp), | ||
193 | SND_SOC_DAPM_MIC("Mic Jack", NULL), | ||
194 | }; | ||
195 | |||
196 | static const struct snd_soc_dapm_route harmony_audio_map[] = { | ||
197 | {"Headphone Jack", NULL, "HPOUTR"}, | ||
198 | {"Headphone Jack", NULL, "HPOUTL"}, | ||
199 | {"Int Spk", NULL, "ROP"}, | ||
200 | {"Int Spk", NULL, "RON"}, | ||
201 | {"Int Spk", NULL, "LOP"}, | ||
202 | {"Int Spk", NULL, "LON"}, | ||
203 | {"Mic Bias", NULL, "Mic Jack"}, | ||
204 | {"IN1L", NULL, "Mic Bias"}, | ||
205 | }; | ||
206 | |||
207 | static const struct snd_soc_dapm_route seaboard_audio_map[] = { | ||
208 | {"Headphone Jack", NULL, "HPOUTR"}, | ||
209 | {"Headphone Jack", NULL, "HPOUTL"}, | ||
210 | {"Int Spk", NULL, "ROP"}, | ||
211 | {"Int Spk", NULL, "RON"}, | ||
212 | {"Int Spk", NULL, "LOP"}, | ||
213 | {"Int Spk", NULL, "LON"}, | ||
214 | {"Mic Bias", NULL, "Mic Jack"}, | ||
215 | {"IN1R", NULL, "Mic Bias"}, | ||
216 | }; | ||
217 | |||
218 | static const struct snd_soc_dapm_route kaen_audio_map[] = { | ||
219 | {"Headphone Jack", NULL, "HPOUTR"}, | ||
220 | {"Headphone Jack", NULL, "HPOUTL"}, | ||
221 | {"Int Spk", NULL, "ROP"}, | ||
222 | {"Int Spk", NULL, "RON"}, | ||
223 | {"Int Spk", NULL, "LOP"}, | ||
224 | {"Int Spk", NULL, "LON"}, | ||
225 | {"Mic Bias", NULL, "Mic Jack"}, | ||
226 | {"IN2R", NULL, "Mic Bias"}, | ||
227 | }; | ||
228 | |||
229 | static const struct snd_soc_dapm_route aebl_audio_map[] = { | ||
230 | {"Headphone Jack", NULL, "HPOUTR"}, | ||
231 | {"Headphone Jack", NULL, "HPOUTL"}, | ||
232 | {"Int Spk", NULL, "LINEOUTR"}, | ||
233 | {"Int Spk", NULL, "LINEOUTL"}, | ||
234 | {"Mic Bias", NULL, "Mic Jack"}, | ||
235 | {"IN1R", NULL, "Mic Bias"}, | ||
236 | }; | ||
237 | |||
238 | static const struct snd_kcontrol_new tegra_wm8903_controls[] = { | ||
239 | SOC_DAPM_PIN_SWITCH("Int Spk"), | ||
240 | }; | ||
241 | |||
242 | static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd) | ||
243 | { | ||
244 | struct snd_soc_codec *codec = rtd->codec; | ||
245 | struct snd_soc_dapm_context *dapm = &codec->dapm; | ||
246 | struct snd_soc_card *card = codec->card; | ||
247 | struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); | ||
248 | struct tegra_wm8903_platform_data *pdata = machine->pdata; | ||
249 | int ret; | ||
250 | |||
251 | if (gpio_is_valid(pdata->gpio_spkr_en)) { | ||
252 | ret = gpio_request(pdata->gpio_spkr_en, "spkr_en"); | ||
253 | if (ret) { | ||
254 | dev_err(card->dev, "cannot get spkr_en gpio\n"); | ||
255 | return ret; | ||
256 | } | ||
257 | machine->gpio_requested |= GPIO_SPKR_EN; | ||
258 | |||
259 | gpio_direction_output(pdata->gpio_spkr_en, 0); | ||
260 | } | ||
261 | |||
262 | if (gpio_is_valid(pdata->gpio_hp_mute)) { | ||
263 | ret = gpio_request(pdata->gpio_hp_mute, "hp_mute"); | ||
264 | if (ret) { | ||
265 | dev_err(card->dev, "cannot get hp_mute gpio\n"); | ||
266 | return ret; | ||
267 | } | ||
268 | machine->gpio_requested |= GPIO_HP_MUTE; | ||
269 | |||
270 | gpio_direction_output(pdata->gpio_hp_mute, 0); | ||
271 | } | ||
272 | |||
273 | if (gpio_is_valid(pdata->gpio_int_mic_en)) { | ||
274 | ret = gpio_request(pdata->gpio_int_mic_en, "int_mic_en"); | ||
275 | if (ret) { | ||
276 | dev_err(card->dev, "cannot get int_mic_en gpio\n"); | ||
277 | return ret; | ||
278 | } | ||
279 | machine->gpio_requested |= GPIO_INT_MIC_EN; | ||
280 | |||
281 | /* Disable int mic; enable signal is active-high */ | ||
282 | gpio_direction_output(pdata->gpio_int_mic_en, 0); | ||
283 | } | ||
284 | |||
285 | if (gpio_is_valid(pdata->gpio_ext_mic_en)) { | ||
286 | ret = gpio_request(pdata->gpio_ext_mic_en, "ext_mic_en"); | ||
287 | if (ret) { | ||
288 | dev_err(card->dev, "cannot get ext_mic_en gpio\n"); | ||
289 | return ret; | ||
290 | } | ||
291 | machine->gpio_requested |= GPIO_EXT_MIC_EN; | ||
292 | |||
293 | /* Enable ext mic; enable signal is active-low */ | ||
294 | gpio_direction_output(pdata->gpio_ext_mic_en, 0); | ||
295 | } | ||
296 | |||
297 | if (gpio_is_valid(pdata->gpio_hp_det)) { | ||
298 | tegra_wm8903_hp_jack_gpio.gpio = pdata->gpio_hp_det; | ||
299 | snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE, | ||
300 | &tegra_wm8903_hp_jack); | ||
301 | snd_soc_jack_add_pins(&tegra_wm8903_hp_jack, | ||
302 | ARRAY_SIZE(tegra_wm8903_hp_jack_pins), | ||
303 | tegra_wm8903_hp_jack_pins); | ||
304 | snd_soc_jack_add_gpios(&tegra_wm8903_hp_jack, | ||
305 | 1, | ||
306 | &tegra_wm8903_hp_jack_gpio); | ||
307 | } | ||
308 | |||
309 | snd_soc_jack_new(codec, "Mic Jack", SND_JACK_MICROPHONE, | ||
310 | &tegra_wm8903_mic_jack); | ||
311 | snd_soc_jack_add_pins(&tegra_wm8903_mic_jack, | ||
312 | ARRAY_SIZE(tegra_wm8903_mic_jack_pins), | ||
313 | tegra_wm8903_mic_jack_pins); | ||
314 | wm8903_mic_detect(codec, &tegra_wm8903_mic_jack, SND_JACK_MICROPHONE, | ||
315 | 0); | ||
316 | |||
317 | snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); | ||
318 | |||
319 | /* FIXME: Calculate automatically based on DAPM routes? */ | ||
320 | if (!machine_is_harmony() && !machine_is_ventana()) | ||
321 | snd_soc_dapm_nc_pin(dapm, "IN1L"); | ||
322 | if (!machine_is_seaboard() && !machine_is_aebl()) | ||
323 | snd_soc_dapm_nc_pin(dapm, "IN1R"); | ||
324 | snd_soc_dapm_nc_pin(dapm, "IN2L"); | ||
325 | if (!machine_is_kaen()) | ||
326 | snd_soc_dapm_nc_pin(dapm, "IN2R"); | ||
327 | snd_soc_dapm_nc_pin(dapm, "IN3L"); | ||
328 | snd_soc_dapm_nc_pin(dapm, "IN3R"); | ||
329 | |||
330 | if (machine_is_aebl()) { | ||
331 | snd_soc_dapm_nc_pin(dapm, "LON"); | ||
332 | snd_soc_dapm_nc_pin(dapm, "RON"); | ||
333 | snd_soc_dapm_nc_pin(dapm, "ROP"); | ||
334 | snd_soc_dapm_nc_pin(dapm, "LOP"); | ||
335 | } else { | ||
336 | snd_soc_dapm_nc_pin(dapm, "LINEOUTR"); | ||
337 | snd_soc_dapm_nc_pin(dapm, "LINEOUTL"); | ||
338 | } | ||
339 | |||
340 | snd_soc_dapm_sync(dapm); | ||
341 | |||
342 | return 0; | ||
343 | } | ||
344 | |||
345 | static struct snd_soc_dai_link tegra_wm8903_dai = { | ||
346 | .name = "WM8903", | ||
347 | .stream_name = "WM8903 PCM", | ||
348 | .codec_name = "wm8903.0-001a", | ||
349 | .platform_name = "tegra-pcm-audio", | ||
350 | .cpu_dai_name = "tegra-i2s.0", | ||
351 | .codec_dai_name = "wm8903-hifi", | ||
352 | .init = tegra_wm8903_init, | ||
353 | .ops = &tegra_wm8903_ops, | ||
354 | }; | ||
355 | |||
356 | static struct snd_soc_card snd_soc_tegra_wm8903 = { | ||
357 | .name = "tegra-wm8903", | ||
358 | .dai_link = &tegra_wm8903_dai, | ||
359 | .num_links = 1, | ||
360 | |||
361 | .controls = tegra_wm8903_controls, | ||
362 | .num_controls = ARRAY_SIZE(tegra_wm8903_controls), | ||
363 | .dapm_widgets = tegra_wm8903_dapm_widgets, | ||
364 | .num_dapm_widgets = ARRAY_SIZE(tegra_wm8903_dapm_widgets), | ||
365 | }; | ||
366 | |||
367 | static __devinit int tegra_wm8903_driver_probe(struct platform_device *pdev) | ||
368 | { | ||
369 | struct snd_soc_card *card = &snd_soc_tegra_wm8903; | ||
370 | struct tegra_wm8903 *machine; | ||
371 | struct tegra_wm8903_platform_data *pdata; | ||
372 | int ret; | ||
373 | |||
374 | pdata = pdev->dev.platform_data; | ||
375 | if (!pdata) { | ||
376 | dev_err(&pdev->dev, "No platform data supplied\n"); | ||
377 | return -EINVAL; | ||
378 | } | ||
379 | |||
380 | machine = kzalloc(sizeof(struct tegra_wm8903), GFP_KERNEL); | ||
381 | if (!machine) { | ||
382 | dev_err(&pdev->dev, "Can't allocate tegra_wm8903 struct\n"); | ||
383 | return -ENOMEM; | ||
384 | } | ||
385 | |||
386 | machine->pdata = pdata; | ||
387 | |||
388 | ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); | ||
389 | if (ret) | ||
390 | goto err_free_machine; | ||
391 | |||
392 | card->dev = &pdev->dev; | ||
393 | platform_set_drvdata(pdev, card); | ||
394 | snd_soc_card_set_drvdata(card, machine); | ||
395 | |||
396 | if (machine_is_harmony() || machine_is_ventana()) { | ||
397 | card->dapm_routes = harmony_audio_map; | ||
398 | card->num_dapm_routes = ARRAY_SIZE(harmony_audio_map); | ||
399 | } else if (machine_is_seaboard()) { | ||
400 | card->dapm_routes = seaboard_audio_map; | ||
401 | card->num_dapm_routes = ARRAY_SIZE(seaboard_audio_map); | ||
402 | } else if (machine_is_kaen()) { | ||
403 | card->dapm_routes = kaen_audio_map; | ||
404 | card->num_dapm_routes = ARRAY_SIZE(kaen_audio_map); | ||
405 | } else { | ||
406 | card->dapm_routes = aebl_audio_map; | ||
407 | card->num_dapm_routes = ARRAY_SIZE(aebl_audio_map); | ||
408 | } | ||
409 | |||
410 | ret = snd_soc_register_card(card); | ||
411 | if (ret) { | ||
412 | dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", | ||
413 | ret); | ||
414 | goto err_fini_utils; | ||
415 | } | ||
416 | |||
417 | return 0; | ||
418 | |||
419 | err_fini_utils: | ||
420 | tegra_asoc_utils_fini(&machine->util_data); | ||
421 | err_free_machine: | ||
422 | kfree(machine); | ||
423 | return ret; | ||
424 | } | ||
425 | |||
426 | static int __devexit tegra_wm8903_driver_remove(struct platform_device *pdev) | ||
427 | { | ||
428 | struct snd_soc_card *card = platform_get_drvdata(pdev); | ||
429 | struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); | ||
430 | struct tegra_wm8903_platform_data *pdata = machine->pdata; | ||
431 | |||
432 | snd_soc_unregister_card(card); | ||
433 | |||
434 | tegra_asoc_utils_fini(&machine->util_data); | ||
435 | |||
436 | if (machine->gpio_requested & GPIO_EXT_MIC_EN) | ||
437 | gpio_free(pdata->gpio_ext_mic_en); | ||
438 | if (machine->gpio_requested & GPIO_INT_MIC_EN) | ||
439 | gpio_free(pdata->gpio_int_mic_en); | ||
440 | if (machine->gpio_requested & GPIO_HP_MUTE) | ||
441 | gpio_free(pdata->gpio_hp_mute); | ||
442 | if (machine->gpio_requested & GPIO_SPKR_EN) | ||
443 | gpio_free(pdata->gpio_spkr_en); | ||
444 | |||
445 | kfree(machine); | ||
446 | |||
447 | return 0; | ||
448 | } | ||
449 | |||
450 | static struct platform_driver tegra_wm8903_driver = { | ||
451 | .driver = { | ||
452 | .name = DRV_NAME, | ||
453 | .owner = THIS_MODULE, | ||
454 | .pm = &snd_soc_pm_ops, | ||
455 | }, | ||
456 | .probe = tegra_wm8903_driver_probe, | ||
457 | .remove = __devexit_p(tegra_wm8903_driver_remove), | ||
458 | }; | ||
459 | |||
460 | static int __init tegra_wm8903_modinit(void) | ||
461 | { | ||
462 | return platform_driver_register(&tegra_wm8903_driver); | ||
463 | } | ||
464 | module_init(tegra_wm8903_modinit); | ||
465 | |||
466 | static void __exit tegra_wm8903_modexit(void) | ||
467 | { | ||
468 | platform_driver_unregister(&tegra_wm8903_driver); | ||
469 | } | ||
470 | module_exit(tegra_wm8903_modexit); | ||
471 | |||
472 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); | ||
473 | MODULE_DESCRIPTION("Tegra+WM8903 machine ASoC driver"); | ||
474 | MODULE_LICENSE("GPL"); | ||
475 | MODULE_ALIAS("platform:" DRV_NAME); | ||
diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c new file mode 100644 index 000000000000..8fc07e9adf2e --- /dev/null +++ b/sound/soc/tegra/trimslice.c | |||
@@ -0,0 +1,228 @@ | |||
1 | /* | ||
2 | * trimslice.c - TrimSlice machine ASoC driver | ||
3 | * | ||
4 | * Copyright (C) 2011 - CompuLab, Ltd. | ||
5 | * Author: Mike Rapoport <mike@compulab.co.il> | ||
6 | * | ||
7 | * Based on code copyright/by: | ||
8 | * Author: Stephen Warren <swarren@nvidia.com> | ||
9 | * Copyright (C) 2010-2011 - NVIDIA, Inc. | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * version 2 as published by the Free Software Foundation. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, but | ||
16 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
18 | * General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU General Public License | ||
21 | * along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
23 | * 02110-1301 USA | ||
24 | * | ||
25 | */ | ||
26 | |||
27 | #include <asm/mach-types.h> | ||
28 | |||
29 | #include <linux/module.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/slab.h> | ||
32 | |||
33 | #include <sound/core.h> | ||
34 | #include <sound/jack.h> | ||
35 | #include <sound/pcm.h> | ||
36 | #include <sound/pcm_params.h> | ||
37 | #include <sound/soc.h> | ||
38 | |||
39 | #include "../codecs/tlv320aic23.h" | ||
40 | |||
41 | #include "tegra_das.h" | ||
42 | #include "tegra_i2s.h" | ||
43 | #include "tegra_pcm.h" | ||
44 | #include "tegra_asoc_utils.h" | ||
45 | |||
46 | #define DRV_NAME "tegra-snd-trimslice" | ||
47 | |||
48 | struct tegra_trimslice { | ||
49 | struct tegra_asoc_utils_data util_data; | ||
50 | }; | ||
51 | |||
52 | static int trimslice_asoc_hw_params(struct snd_pcm_substream *substream, | ||
53 | struct snd_pcm_hw_params *params) | ||
54 | { | ||
55 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
56 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | ||
57 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | ||
58 | struct snd_soc_codec *codec = rtd->codec; | ||
59 | struct snd_soc_card *card = codec->card; | ||
60 | struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); | ||
61 | int srate, mclk; | ||
62 | int err; | ||
63 | |||
64 | srate = params_rate(params); | ||
65 | mclk = 128 * srate; | ||
66 | |||
67 | err = tegra_asoc_utils_set_rate(&trimslice->util_data, srate, mclk); | ||
68 | if (err < 0) { | ||
69 | dev_err(card->dev, "Can't configure clocks\n"); | ||
70 | return err; | ||
71 | } | ||
72 | |||
73 | err = snd_soc_dai_set_fmt(codec_dai, | ||
74 | SND_SOC_DAIFMT_I2S | | ||
75 | SND_SOC_DAIFMT_NB_NF | | ||
76 | SND_SOC_DAIFMT_CBS_CFS); | ||
77 | if (err < 0) { | ||
78 | dev_err(card->dev, "codec_dai fmt not set\n"); | ||
79 | return err; | ||
80 | } | ||
81 | |||
82 | err = snd_soc_dai_set_fmt(cpu_dai, | ||
83 | SND_SOC_DAIFMT_I2S | | ||
84 | SND_SOC_DAIFMT_NB_NF | | ||
85 | SND_SOC_DAIFMT_CBS_CFS); | ||
86 | if (err < 0) { | ||
87 | dev_err(card->dev, "cpu_dai fmt not set\n"); | ||
88 | return err; | ||
89 | } | ||
90 | |||
91 | err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, | ||
92 | SND_SOC_CLOCK_IN); | ||
93 | if (err < 0) { | ||
94 | dev_err(card->dev, "codec_dai clock not set\n"); | ||
95 | return err; | ||
96 | } | ||
97 | |||
98 | return 0; | ||
99 | } | ||
100 | |||
101 | static struct snd_soc_ops trimslice_asoc_ops = { | ||
102 | .hw_params = trimslice_asoc_hw_params, | ||
103 | }; | ||
104 | |||
105 | static const struct snd_soc_dapm_widget trimslice_dapm_widgets[] = { | ||
106 | SND_SOC_DAPM_HP("Line Out", NULL), | ||
107 | SND_SOC_DAPM_LINE("Line In", NULL), | ||
108 | }; | ||
109 | |||
110 | static const struct snd_soc_dapm_route trimslice_audio_map[] = { | ||
111 | {"Line Out", NULL, "LOUT"}, | ||
112 | {"Line Out", NULL, "ROUT"}, | ||
113 | |||
114 | {"LLINEIN", NULL, "Line In"}, | ||
115 | {"RLINEIN", NULL, "Line In"}, | ||
116 | }; | ||
117 | |||
118 | static int trimslice_asoc_init(struct snd_soc_pcm_runtime *rtd) | ||
119 | { | ||
120 | struct snd_soc_codec *codec = rtd->codec; | ||
121 | struct snd_soc_dapm_context *dapm = &codec->dapm; | ||
122 | |||
123 | snd_soc_dapm_nc_pin(dapm, "LHPOUT"); | ||
124 | snd_soc_dapm_nc_pin(dapm, "RHPOUT"); | ||
125 | snd_soc_dapm_nc_pin(dapm, "MICIN"); | ||
126 | |||
127 | snd_soc_dapm_sync(dapm); | ||
128 | |||
129 | return 0; | ||
130 | } | ||
131 | |||
132 | static struct snd_soc_dai_link trimslice_tlv320aic23_dai = { | ||
133 | .name = "TLV320AIC23", | ||
134 | .stream_name = "AIC23", | ||
135 | .codec_name = "tlv320aic23-codec.2-001a", | ||
136 | .platform_name = "tegra-pcm-audio", | ||
137 | .cpu_dai_name = "tegra-i2s.0", | ||
138 | .codec_dai_name = "tlv320aic23-hifi", | ||
139 | .init = trimslice_asoc_init, | ||
140 | .ops = &trimslice_asoc_ops, | ||
141 | }; | ||
142 | |||
143 | static struct snd_soc_card snd_soc_trimslice = { | ||
144 | .name = "tegra-trimslice", | ||
145 | .dai_link = &trimslice_tlv320aic23_dai, | ||
146 | .num_links = 1, | ||
147 | |||
148 | .dapm_widgets = trimslice_dapm_widgets, | ||
149 | .num_dapm_widgets = ARRAY_SIZE(trimslice_dapm_widgets), | ||
150 | .dapm_routes = trimslice_audio_map, | ||
151 | .num_dapm_routes = ARRAY_SIZE(trimslice_audio_map), | ||
152 | }; | ||
153 | |||
154 | static __devinit int tegra_snd_trimslice_probe(struct platform_device *pdev) | ||
155 | { | ||
156 | struct snd_soc_card *card = &snd_soc_trimslice; | ||
157 | struct tegra_trimslice *trimslice; | ||
158 | int ret; | ||
159 | |||
160 | trimslice = kzalloc(sizeof(struct tegra_trimslice), GFP_KERNEL); | ||
161 | if (!trimslice) { | ||
162 | dev_err(&pdev->dev, "Can't allocate tegra_trimslice\n"); | ||
163 | return -ENOMEM; | ||
164 | } | ||
165 | |||
166 | ret = tegra_asoc_utils_init(&trimslice->util_data, &pdev->dev); | ||
167 | if (ret) | ||
168 | goto err_free_trimslice; | ||
169 | |||
170 | card->dev = &pdev->dev; | ||
171 | platform_set_drvdata(pdev, card); | ||
172 | snd_soc_card_set_drvdata(card, trimslice); | ||
173 | |||
174 | ret = snd_soc_register_card(card); | ||
175 | if (ret) { | ||
176 | dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", | ||
177 | ret); | ||
178 | goto err_fini_utils; | ||
179 | } | ||
180 | |||
181 | return 0; | ||
182 | |||
183 | err_fini_utils: | ||
184 | tegra_asoc_utils_fini(&trimslice->util_data); | ||
185 | err_free_trimslice: | ||
186 | kfree(trimslice); | ||
187 | return ret; | ||
188 | } | ||
189 | |||
190 | static int __devexit tegra_snd_trimslice_remove(struct platform_device *pdev) | ||
191 | { | ||
192 | struct snd_soc_card *card = platform_get_drvdata(pdev); | ||
193 | struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); | ||
194 | |||
195 | snd_soc_unregister_card(card); | ||
196 | |||
197 | tegra_asoc_utils_fini(&trimslice->util_data); | ||
198 | |||
199 | kfree(trimslice); | ||
200 | |||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static struct platform_driver tegra_snd_trimslice_driver = { | ||
205 | .driver = { | ||
206 | .name = DRV_NAME, | ||
207 | .owner = THIS_MODULE, | ||
208 | }, | ||
209 | .probe = tegra_snd_trimslice_probe, | ||
210 | .remove = __devexit_p(tegra_snd_trimslice_remove), | ||
211 | }; | ||
212 | |||
213 | static int __init snd_tegra_trimslice_init(void) | ||
214 | { | ||
215 | return platform_driver_register(&tegra_snd_trimslice_driver); | ||
216 | } | ||
217 | module_init(snd_tegra_trimslice_init); | ||
218 | |||
219 | static void __exit snd_tegra_trimslice_exit(void) | ||
220 | { | ||
221 | platform_driver_unregister(&tegra_snd_trimslice_driver); | ||
222 | } | ||
223 | module_exit(snd_tegra_trimslice_exit); | ||
224 | |||
225 | MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); | ||
226 | MODULE_DESCRIPTION("Trimslice machine ASoC driver"); | ||
227 | MODULE_LICENSE("GPL"); | ||
228 | MODULE_ALIAS("platform:" DRV_NAME); | ||