aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd/twl4030-codec.c
diff options
context:
space:
mode:
authorPeter Ujfalusi <peter.ujfalusi@nokia.com>2009-10-22 06:26:45 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2009-10-25 13:15:08 -0400
commit0b83ddebc6e884dc0221358cf68c461520fbdd8e (patch)
tree7c57a54fe8b0f02448597df3f7f81ea87a8b96b2 /drivers/mfd/twl4030-codec.c
parent0ffc11800cb2a74b05c2f5b28966ebd50b27f70c (diff)
MFD: twl4030: add twl4030_codec MFD as a new child to the core
New MFD child to twl4030 MFD device. Reason for the twl4030_codec MFD: the vibra control is actually in the codec part of the twl4030. If both the vibra and the audio functionality is needed from the twl4030 at the same time, than they need to control the codec power and APLL at the same time without breaking the other driver. Also these two has to be able to work without the need for the other driver. This MFD device will be used by the drivers, which needs resources from the twl4030 codec like audio and vibra. The platform specific configuration data is passed along to the child drivers (audio, vibra). Signed-off-by: Peter Ujfalusi <peter.ujfalusi@nokia.com> Acked-by: Samuel Ortiz <sameo@linux.intel.com> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'drivers/mfd/twl4030-codec.c')
-rw-r--r--drivers/mfd/twl4030-codec.c241
1 files changed, 241 insertions, 0 deletions
diff --git a/drivers/mfd/twl4030-codec.c b/drivers/mfd/twl4030-codec.c
new file mode 100644
index 000000000000..97103078dc2a
--- /dev/null
+++ b/drivers/mfd/twl4030-codec.c
@@ -0,0 +1,241 @@
1/*
2 * MFD driver for twl4030 codec submodule
3 *
4 * Author: Peter Ujfalusi <peter.ujfalusi@nokia.com>
5 *
6 * Copyright: (C) 2009 Nokia Corporation
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA
21 *
22 */
23
24#include <linux/module.h>
25#include <linux/types.h>
26#include <linux/kernel.h>
27#include <linux/fs.h>
28#include <linux/platform_device.h>
29#include <linux/i2c/twl4030.h>
30#include <linux/mfd/core.h>
31#include <linux/mfd/twl4030-codec.h>
32
33#define TWL4030_CODEC_CELLS 2
34
35static struct platform_device *twl4030_codec_dev;
36
37struct twl4030_codec_resource {
38 int request_count;
39 u8 reg;
40 u8 mask;
41};
42
43struct twl4030_codec {
44 struct mutex mutex;
45 struct twl4030_codec_resource resource[TWL4030_CODEC_RES_MAX];
46 struct mfd_cell cells[TWL4030_CODEC_CELLS];
47};
48
49/*
50 * Modify the resource, the function returns the content of the register
51 * after the modification.
52 */
53static int twl4030_codec_set_resource(enum twl4030_codec_res id, int enable)
54{
55 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
56 u8 val;
57
58 twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
59 codec->resource[id].reg);
60
61 if (enable)
62 val |= codec->resource[id].mask;
63 else
64 val &= ~codec->resource[id].mask;
65
66 twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
67 val, codec->resource[id].reg);
68
69 return val;
70}
71
72static inline int twl4030_codec_get_resource(enum twl4030_codec_res id)
73{
74 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
75 u8 val;
76
77 twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
78 codec->resource[id].reg);
79
80 return val;
81}
82
83/*
84 * Enable the resource.
85 * The function returns with error or the content of the register
86 */
87int twl4030_codec_enable_resource(enum twl4030_codec_res id)
88{
89 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
90 int val;
91
92 if (id >= TWL4030_CODEC_RES_MAX) {
93 dev_err(&twl4030_codec_dev->dev,
94 "Invalid resource ID (%u)\n", id);
95 return -EINVAL;
96 }
97
98 mutex_lock(&codec->mutex);
99 if (!codec->resource[id].request_count)
100 /* Resource was disabled, enable it */
101 val = twl4030_codec_set_resource(id, 1);
102 else
103 val = twl4030_codec_get_resource(id);
104
105 codec->resource[id].request_count++;
106 mutex_unlock(&codec->mutex);
107
108 return val;
109}
110EXPORT_SYMBOL_GPL(twl4030_codec_enable_resource);
111
112/*
113 * Disable the resource.
114 * The function returns with error or the content of the register
115 */
116int twl4030_codec_disable_resource(unsigned id)
117{
118 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
119 int val;
120
121 if (id >= TWL4030_CODEC_RES_MAX) {
122 dev_err(&twl4030_codec_dev->dev,
123 "Invalid resource ID (%u)\n", id);
124 return -EINVAL;
125 }
126
127 mutex_lock(&codec->mutex);
128 if (!codec->resource[id].request_count) {
129 dev_err(&twl4030_codec_dev->dev,
130 "Resource has been disabled already (%u)\n", id);
131 mutex_unlock(&codec->mutex);
132 return -EPERM;
133 }
134 codec->resource[id].request_count--;
135
136 if (!codec->resource[id].request_count)
137 /* Resource can be disabled now */
138 val = twl4030_codec_set_resource(id, 0);
139 else
140 val = twl4030_codec_get_resource(id);
141
142 mutex_unlock(&codec->mutex);
143
144 return val;
145}
146EXPORT_SYMBOL_GPL(twl4030_codec_disable_resource);
147
148static int __devinit twl4030_codec_probe(struct platform_device *pdev)
149{
150 struct twl4030_codec *codec;
151 struct twl4030_codec_data *pdata = pdev->dev.platform_data;
152 struct mfd_cell *cell = NULL;
153 int ret, childs = 0;
154
155 codec = kzalloc(sizeof(struct twl4030_codec), GFP_KERNEL);
156 if (!codec)
157 return -ENOMEM;
158
159 platform_set_drvdata(pdev, codec);
160
161 twl4030_codec_dev = pdev;
162 mutex_init(&codec->mutex);
163
164 /* Codec power */
165 codec->resource[TWL4030_CODEC_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
166 codec->resource[TWL4030_CODEC_RES_POWER].mask = TWL4030_CODECPDZ;
167
168 /* PLL */
169 codec->resource[TWL4030_CODEC_RES_APLL].reg = TWL4030_REG_APLL_CTL;
170 codec->resource[TWL4030_CODEC_RES_APLL].mask = TWL4030_APLL_EN;
171
172 if (pdata->audio) {
173 cell = &codec->cells[childs];
174 cell->name = "twl4030_codec_audio";
175 cell->platform_data = pdata->audio;
176 cell->data_size = sizeof(*pdata->audio);
177 childs++;
178 }
179 if (pdata->vibra) {
180 cell = &codec->cells[childs];
181 cell->name = "twl4030_codec_vibra";
182 cell->platform_data = pdata->vibra;
183 cell->data_size = sizeof(*pdata->vibra);
184 childs++;
185 }
186
187 if (childs)
188 ret = mfd_add_devices(&pdev->dev, pdev->id, codec->cells,
189 childs, NULL, 0);
190 else {
191 dev_err(&pdev->dev, "No platform data found for childs\n");
192 ret = -ENODEV;
193 }
194
195 if (!ret)
196 return 0;
197
198 platform_set_drvdata(pdev, NULL);
199 kfree(codec);
200 twl4030_codec_dev = NULL;
201 return ret;
202}
203
204static int __devexit twl4030_codec_remove(struct platform_device *pdev)
205{
206 struct twl4030_codec *codec = platform_get_drvdata(pdev);
207
208 mfd_remove_devices(&pdev->dev);
209 platform_set_drvdata(pdev, NULL);
210 kfree(codec);
211 twl4030_codec_dev = NULL;
212
213 return 0;
214}
215
216MODULE_ALIAS("platform:twl4030_codec");
217
218static struct platform_driver twl4030_codec_driver = {
219 .probe = twl4030_codec_probe,
220 .remove = __devexit_p(twl4030_codec_remove),
221 .driver = {
222 .owner = THIS_MODULE,
223 .name = "twl4030_codec",
224 },
225};
226
227static int __devinit twl4030_codec_init(void)
228{
229 return platform_driver_register(&twl4030_codec_driver);
230}
231module_init(twl4030_codec_init);
232
233static void __devexit twl4030_codec_exit(void)
234{
235 platform_driver_unregister(&twl4030_codec_driver);
236}
237module_exit(twl4030_codec_exit);
238
239MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@nokia.com>");
240MODULE_LICENSE("GPL");
241