aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2011-07-26 20:42:18 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2011-07-26 20:42:18 -0400
commitb0189cd087aa82bd23277cb5c8960ab030e13e5c (patch)
tree7b1a4c152cd62ce136fd5b0e4379d58eb2244e66 /drivers/mfd
parent69f1d1a6acbaa7d83ef3f4ee26209c58cd000204 (diff)
parentbc574e190d3fbed37d724e33a16aee326d6f2ac4 (diff)
Merge branch 'next/devel2' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/linux-arm-soc
* 'next/devel2' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/linux-arm-soc: (47 commits) OMAP: Add debugfs node to show the summary of all clocks OMAP2+: hwmod: Follow the recommended PRCM module enable sequence OMAP2+: clock: allow per-SoC clock init code to prevent clockdomain calls from clock code OMAP2+: clockdomain: Add per clkdm lock to prevent concurrent state programming OMAP2+: PM: idle clkdms only if already in idle OMAP2+: clockdomain: add clkdm_in_hwsup() OMAP2+: clockdomain: Add 2 APIs to control clockdomain from hwmod framework OMAP: clockdomain: Remove redundant call to pwrdm_wait_transition() OMAP4: hwmod: Introduce the module control in hwmod control OMAP4: cm: Add two new APIs for modulemode control OMAP4: hwmod data: Add modulemode entry in omap_hwmod structure OMAP4: hwmod data: Add PRM context register offset OMAP4: prm: Remove deprecated functions OMAP4: prm: Replace warm reset API with the offset based version OMAP4: hwmod: Replace RSTCTRL absolute address with offset macros OMAP: hwmod: Wait the idle status to be disabled OMAP4: hwmod: Replace CLKCTRL absolute address with offset macros OMAP2+: hwmod: Init clkdm field at boot time OMAP4: hwmod data: Add clock domain attribute OMAP4: clock data: Add missing divider selection for auxclks ...
Diffstat (limited to 'drivers/mfd')
-rw-r--r--drivers/mfd/Kconfig8
-rw-r--r--drivers/mfd/Makefile3
-rw-r--r--drivers/mfd/twl-core.c13
-rw-r--r--drivers/mfd/twl4030-audio.c277
-rw-r--r--drivers/mfd/twl4030-codec.c277
-rw-r--r--drivers/mfd/twl6040-core.c620
-rw-r--r--drivers/mfd/twl6040-irq.c191
7 files changed, 1103 insertions, 286 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6ca938a6bf94..37b83eb6d703 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -218,7 +218,7 @@ config TWL4030_POWER
218 and load scripts controlling which resources are switched off/on 218 and load scripts controlling which resources are switched off/on
219 or reset when a sleep, wakeup or warm reset event occurs. 219 or reset when a sleep, wakeup or warm reset event occurs.
220 220
221config TWL4030_CODEC 221config MFD_TWL4030_AUDIO
222 bool 222 bool
223 depends on TWL4030_CORE 223 depends on TWL4030_CORE
224 select MFD_CORE 224 select MFD_CORE
@@ -233,6 +233,12 @@ config TWL6030_PWM
233 Say yes here if you want support for TWL6030 PWM. 233 Say yes here if you want support for TWL6030 PWM.
234 This is used to control charging LED brightness. 234 This is used to control charging LED brightness.
235 235
236config TWL6040_CORE
237 bool
238 depends on TWL4030_CORE && GENERIC_HARDIRQS
239 select MFD_CORE
240 default n
241
236config MFD_STMPE 242config MFD_STMPE
237 bool "Support STMicroelectronics STMPE" 243 bool "Support STMicroelectronics STMPE"
238 depends on I2C=y && GENERIC_HARDIRQS 244 depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index d7d47d2a4c76..22a280fcb705 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -40,8 +40,9 @@ obj-$(CONFIG_MENELAUS) += menelaus.o
40obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o 40obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o
41obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o 41obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
42obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o 42obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o
43obj-$(CONFIG_TWL4030_CODEC) += twl4030-codec.o 43obj-$(CONFIG_MFD_TWL4030_AUDIO) += twl4030-audio.o
44obj-$(CONFIG_TWL6030_PWM) += twl6030-pwm.o 44obj-$(CONFIG_TWL6030_PWM) += twl6030-pwm.o
45obj-$(CONFIG_TWL6040_CORE) += twl6040-core.o twl6040-irq.o
45 46
46obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o 47obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
47 48
diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c
index b8f2a4e7f6e7..a2eddc70995c 100644
--- a/drivers/mfd/twl-core.c
+++ b/drivers/mfd/twl-core.c
@@ -110,7 +110,7 @@
110#endif 110#endif
111 111
112#if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) ||\ 112#if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) ||\
113 defined(CONFIG_SND_SOC_TWL6040) || defined(CONFIG_SND_SOC_TWL6040_MODULE) 113 defined(CONFIG_TWL6040_CORE) || defined(CONFIG_TWL6040_CORE_MODULE)
114#define twl_has_codec() true 114#define twl_has_codec() true
115#else 115#else
116#define twl_has_codec() false 116#define twl_has_codec() false
@@ -815,20 +815,19 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features)
815 return PTR_ERR(child); 815 return PTR_ERR(child);
816 } 816 }
817 817
818 if (twl_has_codec() && pdata->codec && twl_class_is_4030()) { 818 if (twl_has_codec() && pdata->audio && twl_class_is_4030()) {
819 sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid; 819 sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid;
820 child = add_child(sub_chip_id, "twl4030-audio", 820 child = add_child(sub_chip_id, "twl4030-audio",
821 pdata->codec, sizeof(*pdata->codec), 821 pdata->audio, sizeof(*pdata->audio),
822 false, 0, 0); 822 false, 0, 0);
823 if (IS_ERR(child)) 823 if (IS_ERR(child))
824 return PTR_ERR(child); 824 return PTR_ERR(child);
825 } 825 }
826 826
827 /* Phoenix codec driver is probed directly atm */ 827 if (twl_has_codec() && pdata->audio && twl_class_is_6030()) {
828 if (twl_has_codec() && pdata->codec && twl_class_is_6030()) {
829 sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid; 828 sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid;
830 child = add_child(sub_chip_id, "twl6040-codec", 829 child = add_child(sub_chip_id, "twl6040",
831 pdata->codec, sizeof(*pdata->codec), 830 pdata->audio, sizeof(*pdata->audio),
832 false, 0, 0); 831 false, 0, 0);
833 if (IS_ERR(child)) 832 if (IS_ERR(child))
834 return PTR_ERR(child); 833 return PTR_ERR(child);
diff --git a/drivers/mfd/twl4030-audio.c b/drivers/mfd/twl4030-audio.c
new file mode 100644
index 000000000000..ae51ab5d0e5d
--- /dev/null
+++ b/drivers/mfd/twl4030-audio.c
@@ -0,0 +1,277 @@
1/*
2 * MFD driver for twl4030 audio submodule, which contains an audio codec, and
3 * the vibra control.
4 *
5 * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
6 *
7 * Copyright: (C) 2009 Nokia Corporation
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 *
23 */
24
25#include <linux/module.h>
26#include <linux/types.h>
27#include <linux/slab.h>
28#include <linux/kernel.h>
29#include <linux/fs.h>
30#include <linux/platform_device.h>
31#include <linux/i2c/twl.h>
32#include <linux/mfd/core.h>
33#include <linux/mfd/twl4030-audio.h>
34
35#define TWL4030_AUDIO_CELLS 2
36
37static struct platform_device *twl4030_audio_dev;
38
39struct twl4030_audio_resource {
40 int request_count;
41 u8 reg;
42 u8 mask;
43};
44
45struct twl4030_audio {
46 unsigned int audio_mclk;
47 struct mutex mutex;
48 struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX];
49 struct mfd_cell cells[TWL4030_AUDIO_CELLS];
50};
51
52/*
53 * Modify the resource, the function returns the content of the register
54 * after the modification.
55 */
56static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable)
57{
58 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
59 u8 val;
60
61 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
62 audio->resource[id].reg);
63
64 if (enable)
65 val |= audio->resource[id].mask;
66 else
67 val &= ~audio->resource[id].mask;
68
69 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
70 val, audio->resource[id].reg);
71
72 return val;
73}
74
75static inline int twl4030_audio_get_resource(enum twl4030_audio_res id)
76{
77 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
78 u8 val;
79
80 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
81 audio->resource[id].reg);
82
83 return val;
84}
85
86/*
87 * Enable the resource.
88 * The function returns with error or the content of the register
89 */
90int twl4030_audio_enable_resource(enum twl4030_audio_res id)
91{
92 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
93 int val;
94
95 if (id >= TWL4030_AUDIO_RES_MAX) {
96 dev_err(&twl4030_audio_dev->dev,
97 "Invalid resource ID (%u)\n", id);
98 return -EINVAL;
99 }
100
101 mutex_lock(&audio->mutex);
102 if (!audio->resource[id].request_count)
103 /* Resource was disabled, enable it */
104 val = twl4030_audio_set_resource(id, 1);
105 else
106 val = twl4030_audio_get_resource(id);
107
108 audio->resource[id].request_count++;
109 mutex_unlock(&audio->mutex);
110
111 return val;
112}
113EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource);
114
115/*
116 * Disable the resource.
117 * The function returns with error or the content of the register
118 */
119int twl4030_audio_disable_resource(unsigned id)
120{
121 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
122 int val;
123
124 if (id >= TWL4030_AUDIO_RES_MAX) {
125 dev_err(&twl4030_audio_dev->dev,
126 "Invalid resource ID (%u)\n", id);
127 return -EINVAL;
128 }
129
130 mutex_lock(&audio->mutex);
131 if (!audio->resource[id].request_count) {
132 dev_err(&twl4030_audio_dev->dev,
133 "Resource has been disabled already (%u)\n", id);
134 mutex_unlock(&audio->mutex);
135 return -EPERM;
136 }
137 audio->resource[id].request_count--;
138
139 if (!audio->resource[id].request_count)
140 /* Resource can be disabled now */
141 val = twl4030_audio_set_resource(id, 0);
142 else
143 val = twl4030_audio_get_resource(id);
144
145 mutex_unlock(&audio->mutex);
146
147 return val;
148}
149EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource);
150
151unsigned int twl4030_audio_get_mclk(void)
152{
153 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
154
155 return audio->audio_mclk;
156}
157EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk);
158
159static int __devinit twl4030_audio_probe(struct platform_device *pdev)
160{
161 struct twl4030_audio *audio;
162 struct twl4030_audio_data *pdata = pdev->dev.platform_data;
163 struct mfd_cell *cell = NULL;
164 int ret, childs = 0;
165 u8 val;
166
167 if (!pdata) {
168 dev_err(&pdev->dev, "Platform data is missing\n");
169 return -EINVAL;
170 }
171
172 /* Configure APLL_INFREQ and disable APLL if enabled */
173 val = 0;
174 switch (pdata->audio_mclk) {
175 case 19200000:
176 val |= TWL4030_APLL_INFREQ_19200KHZ;
177 break;
178 case 26000000:
179 val |= TWL4030_APLL_INFREQ_26000KHZ;
180 break;
181 case 38400000:
182 val |= TWL4030_APLL_INFREQ_38400KHZ;
183 break;
184 default:
185 dev_err(&pdev->dev, "Invalid audio_mclk\n");
186 return -EINVAL;
187 }
188 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
189 val, TWL4030_REG_APLL_CTL);
190
191 audio = kzalloc(sizeof(struct twl4030_audio), GFP_KERNEL);
192 if (!audio)
193 return -ENOMEM;
194
195 platform_set_drvdata(pdev, audio);
196
197 twl4030_audio_dev = pdev;
198 mutex_init(&audio->mutex);
199 audio->audio_mclk = pdata->audio_mclk;
200
201 /* Codec power */
202 audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
203 audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ;
204
205 /* PLL */
206 audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL;
207 audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN;
208
209 if (pdata->codec) {
210 cell = &audio->cells[childs];
211 cell->name = "twl4030-codec";
212 cell->platform_data = pdata->codec;
213 cell->pdata_size = sizeof(*pdata->codec);
214 childs++;
215 }
216 if (pdata->vibra) {
217 cell = &audio->cells[childs];
218 cell->name = "twl4030-vibra";
219 cell->platform_data = pdata->vibra;
220 cell->pdata_size = sizeof(*pdata->vibra);
221 childs++;
222 }
223
224 if (childs)
225 ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells,
226 childs, NULL, 0);
227 else {
228 dev_err(&pdev->dev, "No platform data found for childs\n");
229 ret = -ENODEV;
230 }
231
232 if (!ret)
233 return 0;
234
235 platform_set_drvdata(pdev, NULL);
236 kfree(audio);
237 twl4030_audio_dev = NULL;
238 return ret;
239}
240
241static int __devexit twl4030_audio_remove(struct platform_device *pdev)
242{
243 struct twl4030_audio *audio = platform_get_drvdata(pdev);
244
245 mfd_remove_devices(&pdev->dev);
246 platform_set_drvdata(pdev, NULL);
247 kfree(audio);
248 twl4030_audio_dev = NULL;
249
250 return 0;
251}
252
253MODULE_ALIAS("platform:twl4030-audio");
254
255static struct platform_driver twl4030_audio_driver = {
256 .probe = twl4030_audio_probe,
257 .remove = __devexit_p(twl4030_audio_remove),
258 .driver = {
259 .owner = THIS_MODULE,
260 .name = "twl4030-audio",
261 },
262};
263
264static int __devinit twl4030_audio_init(void)
265{
266 return platform_driver_register(&twl4030_audio_driver);
267}
268module_init(twl4030_audio_init);
269
270static void __devexit twl4030_audio_exit(void)
271{
272 platform_driver_unregister(&twl4030_audio_driver);
273}
274module_exit(twl4030_audio_exit);
275
276MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
277MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/twl4030-codec.c b/drivers/mfd/twl4030-codec.c
deleted file mode 100644
index 2bf4136464c1..000000000000
--- a/drivers/mfd/twl4030-codec.c
+++ /dev/null
@@ -1,277 +0,0 @@
1/*
2 * MFD driver for twl4030 codec submodule
3 *
4 * Author: Peter Ujfalusi <peter.ujfalusi@ti.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/slab.h>
27#include <linux/kernel.h>
28#include <linux/fs.h>
29#include <linux/platform_device.h>
30#include <linux/i2c/twl.h>
31#include <linux/mfd/core.h>
32#include <linux/mfd/twl4030-codec.h>
33
34#define TWL4030_CODEC_CELLS 2
35
36static struct platform_device *twl4030_codec_dev;
37
38struct twl4030_codec_resource {
39 int request_count;
40 u8 reg;
41 u8 mask;
42};
43
44struct twl4030_codec {
45 unsigned int audio_mclk;
46 struct mutex mutex;
47 struct twl4030_codec_resource resource[TWL4030_CODEC_RES_MAX];
48 struct mfd_cell cells[TWL4030_CODEC_CELLS];
49};
50
51/*
52 * Modify the resource, the function returns the content of the register
53 * after the modification.
54 */
55static int twl4030_codec_set_resource(enum twl4030_codec_res id, int enable)
56{
57 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
58 u8 val;
59
60 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
61 codec->resource[id].reg);
62
63 if (enable)
64 val |= codec->resource[id].mask;
65 else
66 val &= ~codec->resource[id].mask;
67
68 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
69 val, codec->resource[id].reg);
70
71 return val;
72}
73
74static inline int twl4030_codec_get_resource(enum twl4030_codec_res id)
75{
76 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
77 u8 val;
78
79 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
80 codec->resource[id].reg);
81
82 return val;
83}
84
85/*
86 * Enable the resource.
87 * The function returns with error or the content of the register
88 */
89int twl4030_codec_enable_resource(enum twl4030_codec_res id)
90{
91 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
92 int val;
93
94 if (id >= TWL4030_CODEC_RES_MAX) {
95 dev_err(&twl4030_codec_dev->dev,
96 "Invalid resource ID (%u)\n", id);
97 return -EINVAL;
98 }
99
100 mutex_lock(&codec->mutex);
101 if (!codec->resource[id].request_count)
102 /* Resource was disabled, enable it */
103 val = twl4030_codec_set_resource(id, 1);
104 else
105 val = twl4030_codec_get_resource(id);
106
107 codec->resource[id].request_count++;
108 mutex_unlock(&codec->mutex);
109
110 return val;
111}
112EXPORT_SYMBOL_GPL(twl4030_codec_enable_resource);
113
114/*
115 * Disable the resource.
116 * The function returns with error or the content of the register
117 */
118int twl4030_codec_disable_resource(unsigned id)
119{
120 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
121 int val;
122
123 if (id >= TWL4030_CODEC_RES_MAX) {
124 dev_err(&twl4030_codec_dev->dev,
125 "Invalid resource ID (%u)\n", id);
126 return -EINVAL;
127 }
128
129 mutex_lock(&codec->mutex);
130 if (!codec->resource[id].request_count) {
131 dev_err(&twl4030_codec_dev->dev,
132 "Resource has been disabled already (%u)\n", id);
133 mutex_unlock(&codec->mutex);
134 return -EPERM;
135 }
136 codec->resource[id].request_count--;
137
138 if (!codec->resource[id].request_count)
139 /* Resource can be disabled now */
140 val = twl4030_codec_set_resource(id, 0);
141 else
142 val = twl4030_codec_get_resource(id);
143
144 mutex_unlock(&codec->mutex);
145
146 return val;
147}
148EXPORT_SYMBOL_GPL(twl4030_codec_disable_resource);
149
150unsigned int twl4030_codec_get_mclk(void)
151{
152 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
153
154 return codec->audio_mclk;
155}
156EXPORT_SYMBOL_GPL(twl4030_codec_get_mclk);
157
158static int __devinit twl4030_codec_probe(struct platform_device *pdev)
159{
160 struct twl4030_codec *codec;
161 struct twl4030_codec_data *pdata = pdev->dev.platform_data;
162 struct mfd_cell *cell = NULL;
163 int ret, childs = 0;
164 u8 val;
165
166 if (!pdata) {
167 dev_err(&pdev->dev, "Platform data is missing\n");
168 return -EINVAL;
169 }
170
171 /* Configure APLL_INFREQ and disable APLL if enabled */
172 val = 0;
173 switch (pdata->audio_mclk) {
174 case 19200000:
175 val |= TWL4030_APLL_INFREQ_19200KHZ;
176 break;
177 case 26000000:
178 val |= TWL4030_APLL_INFREQ_26000KHZ;
179 break;
180 case 38400000:
181 val |= TWL4030_APLL_INFREQ_38400KHZ;
182 break;
183 default:
184 dev_err(&pdev->dev, "Invalid audio_mclk\n");
185 return -EINVAL;
186 }
187 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
188 val, TWL4030_REG_APLL_CTL);
189
190 codec = kzalloc(sizeof(struct twl4030_codec), GFP_KERNEL);
191 if (!codec)
192 return -ENOMEM;
193
194 platform_set_drvdata(pdev, codec);
195
196 twl4030_codec_dev = pdev;
197 mutex_init(&codec->mutex);
198 codec->audio_mclk = pdata->audio_mclk;
199
200 /* Codec power */
201 codec->resource[TWL4030_CODEC_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
202 codec->resource[TWL4030_CODEC_RES_POWER].mask = TWL4030_CODECPDZ;
203
204 /* PLL */
205 codec->resource[TWL4030_CODEC_RES_APLL].reg = TWL4030_REG_APLL_CTL;
206 codec->resource[TWL4030_CODEC_RES_APLL].mask = TWL4030_APLL_EN;
207
208 if (pdata->audio) {
209 cell = &codec->cells[childs];
210 cell->name = "twl4030-codec";
211 cell->platform_data = pdata->audio;
212 cell->pdata_size = sizeof(*pdata->audio);
213 childs++;
214 }
215 if (pdata->vibra) {
216 cell = &codec->cells[childs];
217 cell->name = "twl4030-vibra";
218 cell->platform_data = pdata->vibra;
219 cell->pdata_size = sizeof(*pdata->vibra);
220 childs++;
221 }
222
223 if (childs)
224 ret = mfd_add_devices(&pdev->dev, pdev->id, codec->cells,
225 childs, NULL, 0);
226 else {
227 dev_err(&pdev->dev, "No platform data found for childs\n");
228 ret = -ENODEV;
229 }
230
231 if (!ret)
232 return 0;
233
234 platform_set_drvdata(pdev, NULL);
235 kfree(codec);
236 twl4030_codec_dev = NULL;
237 return ret;
238}
239
240static int __devexit twl4030_codec_remove(struct platform_device *pdev)
241{
242 struct twl4030_codec *codec = platform_get_drvdata(pdev);
243
244 mfd_remove_devices(&pdev->dev);
245 platform_set_drvdata(pdev, NULL);
246 kfree(codec);
247 twl4030_codec_dev = NULL;
248
249 return 0;
250}
251
252MODULE_ALIAS("platform:twl4030-audio");
253
254static struct platform_driver twl4030_codec_driver = {
255 .probe = twl4030_codec_probe,
256 .remove = __devexit_p(twl4030_codec_remove),
257 .driver = {
258 .owner = THIS_MODULE,
259 .name = "twl4030-audio",
260 },
261};
262
263static int __devinit twl4030_codec_init(void)
264{
265 return platform_driver_register(&twl4030_codec_driver);
266}
267module_init(twl4030_codec_init);
268
269static void __devexit twl4030_codec_exit(void)
270{
271 platform_driver_unregister(&twl4030_codec_driver);
272}
273module_exit(twl4030_codec_exit);
274
275MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
276MODULE_LICENSE("GPL");
277
diff --git a/drivers/mfd/twl6040-core.c b/drivers/mfd/twl6040-core.c
new file mode 100644
index 000000000000..24d436c2fe4a
--- /dev/null
+++ b/drivers/mfd/twl6040-core.c
@@ -0,0 +1,620 @@
1/*
2 * MFD driver for TWL6040 audio device
3 *
4 * Authors: Misael Lopez Cruz <misael.lopez@ti.com>
5 * Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
6 * Peter Ujfalusi <peter.ujfalusi@ti.com>
7 *
8 * Copyright: (C) 2011 Texas Instruments, Inc.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA
23 *
24 */
25
26#include <linux/module.h>
27#include <linux/types.h>
28#include <linux/slab.h>
29#include <linux/kernel.h>
30#include <linux/platform_device.h>
31#include <linux/gpio.h>
32#include <linux/delay.h>
33#include <linux/i2c/twl.h>
34#include <linux/mfd/core.h>
35#include <linux/mfd/twl6040.h>
36
37static struct platform_device *twl6040_dev;
38
39int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg)
40{
41 int ret;
42 u8 val = 0;
43
44 mutex_lock(&twl6040->io_mutex);
45 ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
46 if (ret < 0) {
47 mutex_unlock(&twl6040->io_mutex);
48 return ret;
49 }
50 mutex_unlock(&twl6040->io_mutex);
51
52 return val;
53}
54EXPORT_SYMBOL(twl6040_reg_read);
55
56int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val)
57{
58 int ret;
59
60 mutex_lock(&twl6040->io_mutex);
61 ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
62 mutex_unlock(&twl6040->io_mutex);
63
64 return ret;
65}
66EXPORT_SYMBOL(twl6040_reg_write);
67
68int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
69{
70 int ret;
71 u8 val;
72
73 mutex_lock(&twl6040->io_mutex);
74 ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
75 if (ret)
76 goto out;
77
78 val |= mask;
79 ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
80out:
81 mutex_unlock(&twl6040->io_mutex);
82 return ret;
83}
84EXPORT_SYMBOL(twl6040_set_bits);
85
86int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
87{
88 int ret;
89 u8 val;
90
91 mutex_lock(&twl6040->io_mutex);
92 ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
93 if (ret)
94 goto out;
95
96 val &= ~mask;
97 ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
98out:
99 mutex_unlock(&twl6040->io_mutex);
100 return ret;
101}
102EXPORT_SYMBOL(twl6040_clear_bits);
103
104/* twl6040 codec manual power-up sequence */
105static int twl6040_power_up(struct twl6040 *twl6040)
106{
107 u8 ldoctl, ncpctl, lppllctl;
108 int ret;
109
110 /* enable high-side LDO, reference system and internal oscillator */
111 ldoctl = TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA;
112 ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
113 if (ret)
114 return ret;
115 usleep_range(10000, 10500);
116
117 /* enable negative charge pump */
118 ncpctl = TWL6040_NCPENA;
119 ret = twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
120 if (ret)
121 goto ncp_err;
122 usleep_range(1000, 1500);
123
124 /* enable low-side LDO */
125 ldoctl |= TWL6040_LSLDOENA;
126 ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
127 if (ret)
128 goto lsldo_err;
129 usleep_range(1000, 1500);
130
131 /* enable low-power PLL */
132 lppllctl = TWL6040_LPLLENA;
133 ret = twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
134 if (ret)
135 goto lppll_err;
136 usleep_range(5000, 5500);
137
138 /* disable internal oscillator */
139 ldoctl &= ~TWL6040_OSCENA;
140 ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
141 if (ret)
142 goto osc_err;
143
144 return 0;
145
146osc_err:
147 lppllctl &= ~TWL6040_LPLLENA;
148 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
149lppll_err:
150 ldoctl &= ~TWL6040_LSLDOENA;
151 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
152lsldo_err:
153 ncpctl &= ~TWL6040_NCPENA;
154 twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
155ncp_err:
156 ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA);
157 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
158
159 return ret;
160}
161
162/* twl6040 manual power-down sequence */
163static void twl6040_power_down(struct twl6040 *twl6040)
164{
165 u8 ncpctl, ldoctl, lppllctl;
166
167 ncpctl = twl6040_reg_read(twl6040, TWL6040_REG_NCPCTL);
168 ldoctl = twl6040_reg_read(twl6040, TWL6040_REG_LDOCTL);
169 lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
170
171 /* enable internal oscillator */
172 ldoctl |= TWL6040_OSCENA;
173 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
174 usleep_range(1000, 1500);
175
176 /* disable low-power PLL */
177 lppllctl &= ~TWL6040_LPLLENA;
178 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
179
180 /* disable low-side LDO */
181 ldoctl &= ~TWL6040_LSLDOENA;
182 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
183
184 /* disable negative charge pump */
185 ncpctl &= ~TWL6040_NCPENA;
186 twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
187
188 /* disable high-side LDO, reference system and internal oscillator */
189 ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA);
190 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
191}
192
193static irqreturn_t twl6040_naudint_handler(int irq, void *data)
194{
195 struct twl6040 *twl6040 = data;
196 u8 intid, status;
197
198 intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
199
200 if (intid & TWL6040_READYINT)
201 complete(&twl6040->ready);
202
203 if (intid & TWL6040_THINT) {
204 status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS);
205 if (status & TWL6040_TSHUTDET) {
206 dev_warn(&twl6040_dev->dev,
207 "Thermal shutdown, powering-off");
208 twl6040_power(twl6040, 0);
209 } else {
210 dev_warn(&twl6040_dev->dev,
211 "Leaving thermal shutdown, powering-on");
212 twl6040_power(twl6040, 1);
213 }
214 }
215
216 return IRQ_HANDLED;
217}
218
219static int twl6040_power_up_completion(struct twl6040 *twl6040,
220 int naudint)
221{
222 int time_left;
223 u8 intid;
224
225 time_left = wait_for_completion_timeout(&twl6040->ready,
226 msecs_to_jiffies(144));
227 if (!time_left) {
228 intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
229 if (!(intid & TWL6040_READYINT)) {
230 dev_err(&twl6040_dev->dev,
231 "timeout waiting for READYINT\n");
232 return -ETIMEDOUT;
233 }
234 }
235
236 return 0;
237}
238
239int twl6040_power(struct twl6040 *twl6040, int on)
240{
241 int audpwron = twl6040->audpwron;
242 int naudint = twl6040->irq;
243 int ret = 0;
244
245 mutex_lock(&twl6040->mutex);
246
247 if (on) {
248 /* already powered-up */
249 if (twl6040->power_count++)
250 goto out;
251
252 if (gpio_is_valid(audpwron)) {
253 /* use AUDPWRON line */
254 gpio_set_value(audpwron, 1);
255 /* wait for power-up completion */
256 ret = twl6040_power_up_completion(twl6040, naudint);
257 if (ret) {
258 dev_err(&twl6040_dev->dev,
259 "automatic power-down failed\n");
260 twl6040->power_count = 0;
261 goto out;
262 }
263 } else {
264 /* use manual power-up sequence */
265 ret = twl6040_power_up(twl6040);
266 if (ret) {
267 dev_err(&twl6040_dev->dev,
268 "manual power-up failed\n");
269 twl6040->power_count = 0;
270 goto out;
271 }
272 }
273 /* Default PLL configuration after power up */
274 twl6040->pll = TWL6040_SYSCLK_SEL_LPPLL;
275 twl6040->sysclk = 19200000;
276 } else {
277 /* already powered-down */
278 if (!twl6040->power_count) {
279 dev_err(&twl6040_dev->dev,
280 "device is already powered-off\n");
281 ret = -EPERM;
282 goto out;
283 }
284
285 if (--twl6040->power_count)
286 goto out;
287
288 if (gpio_is_valid(audpwron)) {
289 /* use AUDPWRON line */
290 gpio_set_value(audpwron, 0);
291
292 /* power-down sequence latency */
293 usleep_range(500, 700);
294 } else {
295 /* use manual power-down sequence */
296 twl6040_power_down(twl6040);
297 }
298 twl6040->sysclk = 0;
299 }
300
301out:
302 mutex_unlock(&twl6040->mutex);
303 return ret;
304}
305EXPORT_SYMBOL(twl6040_power);
306
307int twl6040_set_pll(struct twl6040 *twl6040, int pll_id,
308 unsigned int freq_in, unsigned int freq_out)
309{
310 u8 hppllctl, lppllctl;
311 int ret = 0;
312
313 mutex_lock(&twl6040->mutex);
314
315 hppllctl = twl6040_reg_read(twl6040, TWL6040_REG_HPPLLCTL);
316 lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
317
318 switch (pll_id) {
319 case TWL6040_SYSCLK_SEL_LPPLL:
320 /* low-power PLL divider */
321 switch (freq_out) {
322 case 17640000:
323 lppllctl |= TWL6040_LPLLFIN;
324 break;
325 case 19200000:
326 lppllctl &= ~TWL6040_LPLLFIN;
327 break;
328 default:
329 dev_err(&twl6040_dev->dev,
330 "freq_out %d not supported\n", freq_out);
331 ret = -EINVAL;
332 goto pll_out;
333 }
334 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
335
336 switch (freq_in) {
337 case 32768:
338 lppllctl |= TWL6040_LPLLENA;
339 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
340 lppllctl);
341 mdelay(5);
342 lppllctl &= ~TWL6040_HPLLSEL;
343 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
344 lppllctl);
345 hppllctl &= ~TWL6040_HPLLENA;
346 twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL,
347 hppllctl);
348 break;
349 default:
350 dev_err(&twl6040_dev->dev,
351 "freq_in %d not supported\n", freq_in);
352 ret = -EINVAL;
353 goto pll_out;
354 }
355 break;
356 case TWL6040_SYSCLK_SEL_HPPLL:
357 /* high-performance PLL can provide only 19.2 MHz */
358 if (freq_out != 19200000) {
359 dev_err(&twl6040_dev->dev,
360 "freq_out %d not supported\n", freq_out);
361 ret = -EINVAL;
362 goto pll_out;
363 }
364
365 hppllctl &= ~TWL6040_MCLK_MSK;
366
367 switch (freq_in) {
368 case 12000000:
369 /* PLL enabled, active mode */
370 hppllctl |= TWL6040_MCLK_12000KHZ |
371 TWL6040_HPLLENA;
372 break;
373 case 19200000:
374 /*
375 * PLL disabled
376 * (enable PLL if MCLK jitter quality
377 * doesn't meet specification)
378 */
379 hppllctl |= TWL6040_MCLK_19200KHZ;
380 break;
381 case 26000000:
382 /* PLL enabled, active mode */
383 hppllctl |= TWL6040_MCLK_26000KHZ |
384 TWL6040_HPLLENA;
385 break;
386 case 38400000:
387 /* PLL enabled, active mode */
388 hppllctl |= TWL6040_MCLK_38400KHZ |
389 TWL6040_HPLLENA;
390 break;
391 default:
392 dev_err(&twl6040_dev->dev,
393 "freq_in %d not supported\n", freq_in);
394 ret = -EINVAL;
395 goto pll_out;
396 }
397
398 /* enable clock slicer to ensure input waveform is square */
399 hppllctl |= TWL6040_HPLLSQRENA;
400
401 twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL, hppllctl);
402 usleep_range(500, 700);
403 lppllctl |= TWL6040_HPLLSEL;
404 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
405 lppllctl &= ~TWL6040_LPLLENA;
406 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
407 break;
408 default:
409 dev_err(&twl6040_dev->dev, "unknown pll id %d\n", pll_id);
410 ret = -EINVAL;
411 goto pll_out;
412 }
413
414 twl6040->sysclk = freq_out;
415 twl6040->pll = pll_id;
416
417pll_out:
418 mutex_unlock(&twl6040->mutex);
419 return ret;
420}
421EXPORT_SYMBOL(twl6040_set_pll);
422
423int twl6040_get_pll(struct twl6040 *twl6040)
424{
425 if (twl6040->power_count)
426 return twl6040->pll;
427 else
428 return -ENODEV;
429}
430EXPORT_SYMBOL(twl6040_get_pll);
431
432unsigned int twl6040_get_sysclk(struct twl6040 *twl6040)
433{
434 return twl6040->sysclk;
435}
436EXPORT_SYMBOL(twl6040_get_sysclk);
437
438static struct resource twl6040_vibra_rsrc[] = {
439 {
440 .flags = IORESOURCE_IRQ,
441 },
442};
443
444static struct resource twl6040_codec_rsrc[] = {
445 {
446 .flags = IORESOURCE_IRQ,
447 },
448};
449
450static int __devinit twl6040_probe(struct platform_device *pdev)
451{
452 struct twl4030_audio_data *pdata = pdev->dev.platform_data;
453 struct twl6040 *twl6040;
454 struct mfd_cell *cell = NULL;
455 int ret, children = 0;
456
457 if (!pdata) {
458 dev_err(&pdev->dev, "Platform data is missing\n");
459 return -EINVAL;
460 }
461
462 /* In order to operate correctly we need valid interrupt config */
463 if (!pdata->naudint_irq || !pdata->irq_base) {
464 dev_err(&pdev->dev, "Invalid IRQ configuration\n");
465 return -EINVAL;
466 }
467
468 twl6040 = kzalloc(sizeof(struct twl6040), GFP_KERNEL);
469 if (!twl6040)
470 return -ENOMEM;
471
472 platform_set_drvdata(pdev, twl6040);
473
474 twl6040_dev = pdev;
475 twl6040->dev = &pdev->dev;
476 twl6040->audpwron = pdata->audpwron_gpio;
477 twl6040->irq = pdata->naudint_irq;
478 twl6040->irq_base = pdata->irq_base;
479
480 mutex_init(&twl6040->mutex);
481 mutex_init(&twl6040->io_mutex);
482 init_completion(&twl6040->ready);
483
484 twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV);
485
486 if (gpio_is_valid(twl6040->audpwron)) {
487 ret = gpio_request(twl6040->audpwron, "audpwron");
488 if (ret)
489 goto gpio1_err;
490
491 ret = gpio_direction_output(twl6040->audpwron, 0);
492 if (ret)
493 goto gpio2_err;
494 }
495
496 /* ERRATA: Automatic power-up is not possible in ES1.0 */
497 if (twl6040->rev == TWL6040_REV_ES1_0)
498 twl6040->audpwron = -EINVAL;
499
500 /* codec interrupt */
501 ret = twl6040_irq_init(twl6040);
502 if (ret)
503 goto gpio2_err;
504
505 ret = request_threaded_irq(twl6040->irq_base + TWL6040_IRQ_READY,
506 NULL, twl6040_naudint_handler, 0,
507 "twl6040_irq_ready", twl6040);
508 if (ret) {
509 dev_err(twl6040->dev, "READY IRQ request failed: %d\n",
510 ret);
511 goto irq_err;
512 }
513
514 /* dual-access registers controlled by I2C only */
515 twl6040_set_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_I2CSEL);
516
517 if (pdata->codec) {
518 int irq = twl6040->irq_base + TWL6040_IRQ_PLUG;
519
520 cell = &twl6040->cells[children];
521 cell->name = "twl6040-codec";
522 twl6040_codec_rsrc[0].start = irq;
523 twl6040_codec_rsrc[0].end = irq;
524 cell->resources = twl6040_codec_rsrc;
525 cell->num_resources = ARRAY_SIZE(twl6040_codec_rsrc);
526 cell->platform_data = pdata->codec;
527 cell->pdata_size = sizeof(*pdata->codec);
528 children++;
529 }
530
531 if (pdata->vibra) {
532 int irq = twl6040->irq_base + TWL6040_IRQ_VIB;
533
534 cell = &twl6040->cells[children];
535 cell->name = "twl6040-vibra";
536 twl6040_vibra_rsrc[0].start = irq;
537 twl6040_vibra_rsrc[0].end = irq;
538 cell->resources = twl6040_vibra_rsrc;
539 cell->num_resources = ARRAY_SIZE(twl6040_vibra_rsrc);
540
541 cell->platform_data = pdata->vibra;
542 cell->pdata_size = sizeof(*pdata->vibra);
543 children++;
544 }
545
546 if (children) {
547 ret = mfd_add_devices(&pdev->dev, pdev->id, twl6040->cells,
548 children, NULL, 0);
549 if (ret)
550 goto mfd_err;
551 } else {
552 dev_err(&pdev->dev, "No platform data found for children\n");
553 ret = -ENODEV;
554 goto mfd_err;
555 }
556
557 return 0;
558
559mfd_err:
560 free_irq(twl6040->irq_base + TWL6040_IRQ_READY, twl6040);
561irq_err:
562 twl6040_irq_exit(twl6040);
563gpio2_err:
564 if (gpio_is_valid(twl6040->audpwron))
565 gpio_free(twl6040->audpwron);
566gpio1_err:
567 platform_set_drvdata(pdev, NULL);
568 kfree(twl6040);
569 twl6040_dev = NULL;
570 return ret;
571}
572
573static int __devexit twl6040_remove(struct platform_device *pdev)
574{
575 struct twl6040 *twl6040 = platform_get_drvdata(pdev);
576
577 if (twl6040->power_count)
578 twl6040_power(twl6040, 0);
579
580 if (gpio_is_valid(twl6040->audpwron))
581 gpio_free(twl6040->audpwron);
582
583 free_irq(twl6040->irq_base + TWL6040_IRQ_READY, twl6040);
584 twl6040_irq_exit(twl6040);
585
586 mfd_remove_devices(&pdev->dev);
587 platform_set_drvdata(pdev, NULL);
588 kfree(twl6040);
589 twl6040_dev = NULL;
590
591 return 0;
592}
593
594static struct platform_driver twl6040_driver = {
595 .probe = twl6040_probe,
596 .remove = __devexit_p(twl6040_remove),
597 .driver = {
598 .owner = THIS_MODULE,
599 .name = "twl6040",
600 },
601};
602
603static int __devinit twl6040_init(void)
604{
605 return platform_driver_register(&twl6040_driver);
606}
607module_init(twl6040_init);
608
609static void __devexit twl6040_exit(void)
610{
611 platform_driver_unregister(&twl6040_driver);
612}
613
614module_exit(twl6040_exit);
615
616MODULE_DESCRIPTION("TWL6040 MFD");
617MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
618MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>");
619MODULE_LICENSE("GPL");
620MODULE_ALIAS("platform:twl6040");
diff --git a/drivers/mfd/twl6040-irq.c b/drivers/mfd/twl6040-irq.c
new file mode 100644
index 000000000000..b3f8ddaa28a8
--- /dev/null
+++ b/drivers/mfd/twl6040-irq.c
@@ -0,0 +1,191 @@
1/*
2 * Interrupt controller support for TWL6040
3 *
4 * Author: Misael Lopez Cruz <misael.lopez@ti.com>
5 *
6 * Copyright: (C) 2011 Texas Instruments, Inc.
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/kernel.h>
25#include <linux/module.h>
26#include <linux/irq.h>
27#include <linux/interrupt.h>
28#include <linux/mfd/core.h>
29#include <linux/mfd/twl6040.h>
30
31struct twl6040_irq_data {
32 int mask;
33 int status;
34};
35
36static struct twl6040_irq_data twl6040_irqs[] = {
37 {
38 .mask = TWL6040_THMSK,
39 .status = TWL6040_THINT,
40 },
41 {
42 .mask = TWL6040_PLUGMSK,
43 .status = TWL6040_PLUGINT | TWL6040_UNPLUGINT,
44 },
45 {
46 .mask = TWL6040_HOOKMSK,
47 .status = TWL6040_HOOKINT,
48 },
49 {
50 .mask = TWL6040_HFMSK,
51 .status = TWL6040_HFINT,
52 },
53 {
54 .mask = TWL6040_VIBMSK,
55 .status = TWL6040_VIBINT,
56 },
57 {
58 .mask = TWL6040_READYMSK,
59 .status = TWL6040_READYINT,
60 },
61};
62
63static inline
64struct twl6040_irq_data *irq_to_twl6040_irq(struct twl6040 *twl6040,
65 int irq)
66{
67 return &twl6040_irqs[irq - twl6040->irq_base];
68}
69
70static void twl6040_irq_lock(struct irq_data *data)
71{
72 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
73
74 mutex_lock(&twl6040->irq_mutex);
75}
76
77static void twl6040_irq_sync_unlock(struct irq_data *data)
78{
79 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
80
81 /* write back to hardware any change in irq mask */
82 if (twl6040->irq_masks_cur != twl6040->irq_masks_cache) {
83 twl6040->irq_masks_cache = twl6040->irq_masks_cur;
84 twl6040_reg_write(twl6040, TWL6040_REG_INTMR,
85 twl6040->irq_masks_cur);
86 }
87
88 mutex_unlock(&twl6040->irq_mutex);
89}
90
91static void twl6040_irq_enable(struct irq_data *data)
92{
93 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
94 struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
95 data->irq);
96
97 twl6040->irq_masks_cur &= ~irq_data->mask;
98}
99
100static void twl6040_irq_disable(struct irq_data *data)
101{
102 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
103 struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
104 data->irq);
105
106 twl6040->irq_masks_cur |= irq_data->mask;
107}
108
109static struct irq_chip twl6040_irq_chip = {
110 .name = "twl6040",
111 .irq_bus_lock = twl6040_irq_lock,
112 .irq_bus_sync_unlock = twl6040_irq_sync_unlock,
113 .irq_enable = twl6040_irq_enable,
114 .irq_disable = twl6040_irq_disable,
115};
116
117static irqreturn_t twl6040_irq_thread(int irq, void *data)
118{
119 struct twl6040 *twl6040 = data;
120 u8 intid;
121 int i;
122
123 intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
124
125 /* apply masking and report (backwards to handle READYINT first) */
126 for (i = ARRAY_SIZE(twl6040_irqs) - 1; i >= 0; i--) {
127 if (twl6040->irq_masks_cur & twl6040_irqs[i].mask)
128 intid &= ~twl6040_irqs[i].status;
129 if (intid & twl6040_irqs[i].status)
130 handle_nested_irq(twl6040->irq_base + i);
131 }
132
133 /* ack unmasked irqs */
134 twl6040_reg_write(twl6040, TWL6040_REG_INTID, intid);
135
136 return IRQ_HANDLED;
137}
138
139int twl6040_irq_init(struct twl6040 *twl6040)
140{
141 int cur_irq, ret;
142 u8 val;
143
144 mutex_init(&twl6040->irq_mutex);
145
146 /* mask the individual interrupt sources */
147 twl6040->irq_masks_cur = TWL6040_ALLINT_MSK;
148 twl6040->irq_masks_cache = TWL6040_ALLINT_MSK;
149 twl6040_reg_write(twl6040, TWL6040_REG_INTMR, TWL6040_ALLINT_MSK);
150
151 /* Register them with genirq */
152 for (cur_irq = twl6040->irq_base;
153 cur_irq < twl6040->irq_base + ARRAY_SIZE(twl6040_irqs);
154 cur_irq++) {
155 irq_set_chip_data(cur_irq, twl6040);
156 irq_set_chip_and_handler(cur_irq, &twl6040_irq_chip,
157 handle_level_irq);
158 irq_set_nested_thread(cur_irq, 1);
159
160 /* ARM needs us to explicitly flag the IRQ as valid
161 * and will set them noprobe when we do so. */
162#ifdef CONFIG_ARM
163 set_irq_flags(cur_irq, IRQF_VALID);
164#else
165 irq_set_noprobe(cur_irq);
166#endif
167 }
168
169 ret = request_threaded_irq(twl6040->irq, NULL, twl6040_irq_thread,
170 IRQF_ONESHOT, "twl6040", twl6040);
171 if (ret) {
172 dev_err(twl6040->dev, "failed to request IRQ %d: %d\n",
173 twl6040->irq, ret);
174 return ret;
175 }
176
177 /* reset interrupts */
178 val = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
179
180 /* interrupts cleared on write */
181 twl6040_clear_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_INTCLRMODE);
182
183 return 0;
184}
185EXPORT_SYMBOL(twl6040_irq_init);
186
187void twl6040_irq_exit(struct twl6040 *twl6040)
188{
189 free_irq(twl6040->irq, twl6040);
190}
191EXPORT_SYMBOL(twl6040_irq_exit);