diff options
author | Rabin Vincent <rabin.vincent@stericsson.com> | 2010-05-10 17:39:47 -0400 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2010-05-27 19:37:42 -0400 |
commit | b4ecd326b789f1029c5d4a5239d9bd12ecac353d (patch) | |
tree | 387b115b1a911c38a70b8d6c40bacb983bc613d5 /drivers/mfd | |
parent | 68e488d965a9055c63c0eac4ad1e6568b07e8ee1 (diff) |
mfd: Add Toshiba's TC35892 MFD core
The TC35892 I/O Expander provides 24 GPIOs, a keypad controller, timers,
and a rotator wheel interface. This patch adds the MFD core.
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 11 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/tc35892.c | 347 |
3 files changed, 359 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 40e0a0a7df5b..614fa7b8a054 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig | |||
@@ -171,6 +171,17 @@ config TWL4030_CODEC | |||
171 | select MFD_CORE | 171 | select MFD_CORE |
172 | default n | 172 | default n |
173 | 173 | ||
174 | config MFD_TC35892 | ||
175 | bool "Support Toshiba TC35892" | ||
176 | depends on I2C=y && GENERIC_HARDIRQS | ||
177 | select MFD_CORE | ||
178 | help | ||
179 | Support for the Toshiba TC35892 I/O Expander. | ||
180 | |||
181 | This driver provides common support for accessing the device, | ||
182 | additional drivers must be enabled in order to use the | ||
183 | functionality of the device. | ||
184 | |||
174 | config MFD_TMIO | 185 | config MFD_TMIO |
175 | bool | 186 | bool |
176 | default n | 187 | default n |
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index abf6f1766dcd..5eb79794d5a5 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile | |||
@@ -15,6 +15,7 @@ obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o | |||
15 | obj-$(CONFIG_MFD_DAVINCI_VOICECODEC) += davinci_voicecodec.o | 15 | obj-$(CONFIG_MFD_DAVINCI_VOICECODEC) += davinci_voicecodec.o |
16 | obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o | 16 | obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o |
17 | 17 | ||
18 | obj-$(CONFIG_MFD_TC35892) += tc35892.o | ||
18 | obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o | 19 | obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o |
19 | obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o | 20 | obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o |
20 | obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o tmio_core.o | 21 | obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o tmio_core.o |
diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c new file mode 100644 index 000000000000..715f095dd7a6 --- /dev/null +++ b/drivers/mfd/tc35892.c | |||
@@ -0,0 +1,347 @@ | |||
1 | /* | ||
2 | * Copyright (C) ST-Ericsson SA 2010 | ||
3 | * | ||
4 | * License Terms: GNU General Public License, version 2 | ||
5 | * Author: Hanumath Prasad <hanumath.prasad@stericsson.com> for ST-Ericsson | ||
6 | * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson | ||
7 | */ | ||
8 | |||
9 | #include <linux/module.h> | ||
10 | #include <linux/interrupt.h> | ||
11 | #include <linux/irq.h> | ||
12 | #include <linux/slab.h> | ||
13 | #include <linux/i2c.h> | ||
14 | #include <linux/mfd/core.h> | ||
15 | #include <linux/mfd/tc35892.h> | ||
16 | |||
17 | /** | ||
18 | * tc35892_reg_read() - read a single TC35892 register | ||
19 | * @tc35892: Device to read from | ||
20 | * @reg: Register to read | ||
21 | */ | ||
22 | int tc35892_reg_read(struct tc35892 *tc35892, u8 reg) | ||
23 | { | ||
24 | int ret; | ||
25 | |||
26 | ret = i2c_smbus_read_byte_data(tc35892->i2c, reg); | ||
27 | if (ret < 0) | ||
28 | dev_err(tc35892->dev, "failed to read reg %#x: %d\n", | ||
29 | reg, ret); | ||
30 | |||
31 | return ret; | ||
32 | } | ||
33 | EXPORT_SYMBOL_GPL(tc35892_reg_read); | ||
34 | |||
35 | /** | ||
36 | * tc35892_reg_read() - write a single TC35892 register | ||
37 | * @tc35892: Device to write to | ||
38 | * @reg: Register to read | ||
39 | * @data: Value to write | ||
40 | */ | ||
41 | int tc35892_reg_write(struct tc35892 *tc35892, u8 reg, u8 data) | ||
42 | { | ||
43 | int ret; | ||
44 | |||
45 | ret = i2c_smbus_write_byte_data(tc35892->i2c, reg, data); | ||
46 | if (ret < 0) | ||
47 | dev_err(tc35892->dev, "failed to write reg %#x: %d\n", | ||
48 | reg, ret); | ||
49 | |||
50 | return ret; | ||
51 | } | ||
52 | EXPORT_SYMBOL_GPL(tc35892_reg_write); | ||
53 | |||
54 | /** | ||
55 | * tc35892_block_read() - read multiple TC35892 registers | ||
56 | * @tc35892: Device to read from | ||
57 | * @reg: First register | ||
58 | * @length: Number of registers | ||
59 | * @values: Buffer to write to | ||
60 | */ | ||
61 | int tc35892_block_read(struct tc35892 *tc35892, u8 reg, u8 length, u8 *values) | ||
62 | { | ||
63 | int ret; | ||
64 | |||
65 | ret = i2c_smbus_read_i2c_block_data(tc35892->i2c, reg, length, values); | ||
66 | if (ret < 0) | ||
67 | dev_err(tc35892->dev, "failed to read regs %#x: %d\n", | ||
68 | reg, ret); | ||
69 | |||
70 | return ret; | ||
71 | } | ||
72 | EXPORT_SYMBOL_GPL(tc35892_block_read); | ||
73 | |||
74 | /** | ||
75 | * tc35892_block_write() - write multiple TC35892 registers | ||
76 | * @tc35892: Device to write to | ||
77 | * @reg: First register | ||
78 | * @length: Number of registers | ||
79 | * @values: Values to write | ||
80 | */ | ||
81 | int tc35892_block_write(struct tc35892 *tc35892, u8 reg, u8 length, | ||
82 | const u8 *values) | ||
83 | { | ||
84 | int ret; | ||
85 | |||
86 | ret = i2c_smbus_write_i2c_block_data(tc35892->i2c, reg, length, | ||
87 | values); | ||
88 | if (ret < 0) | ||
89 | dev_err(tc35892->dev, "failed to write regs %#x: %d\n", | ||
90 | reg, ret); | ||
91 | |||
92 | return ret; | ||
93 | } | ||
94 | EXPORT_SYMBOL_GPL(tc35892_block_write); | ||
95 | |||
96 | /** | ||
97 | * tc35892_set_bits() - set the value of a bitfield in a TC35892 register | ||
98 | * @tc35892: Device to write to | ||
99 | * @reg: Register to write | ||
100 | * @mask: Mask of bits to set | ||
101 | * @values: Value to set | ||
102 | */ | ||
103 | int tc35892_set_bits(struct tc35892 *tc35892, u8 reg, u8 mask, u8 val) | ||
104 | { | ||
105 | int ret; | ||
106 | |||
107 | mutex_lock(&tc35892->lock); | ||
108 | |||
109 | ret = tc35892_reg_read(tc35892, reg); | ||
110 | if (ret < 0) | ||
111 | goto out; | ||
112 | |||
113 | ret &= ~mask; | ||
114 | ret |= val; | ||
115 | |||
116 | ret = tc35892_reg_write(tc35892, reg, ret); | ||
117 | |||
118 | out: | ||
119 | mutex_unlock(&tc35892->lock); | ||
120 | return ret; | ||
121 | } | ||
122 | EXPORT_SYMBOL_GPL(tc35892_set_bits); | ||
123 | |||
124 | static struct resource gpio_resources[] = { | ||
125 | { | ||
126 | .start = TC35892_INT_GPIIRQ, | ||
127 | .end = TC35892_INT_GPIIRQ, | ||
128 | .flags = IORESOURCE_IRQ, | ||
129 | }, | ||
130 | }; | ||
131 | |||
132 | static struct mfd_cell tc35892_devs[] = { | ||
133 | { | ||
134 | .name = "tc35892-gpio", | ||
135 | .num_resources = ARRAY_SIZE(gpio_resources), | ||
136 | .resources = &gpio_resources[0], | ||
137 | }, | ||
138 | }; | ||
139 | |||
140 | static irqreturn_t tc35892_irq(int irq, void *data) | ||
141 | { | ||
142 | struct tc35892 *tc35892 = data; | ||
143 | int status; | ||
144 | |||
145 | status = tc35892_reg_read(tc35892, TC35892_IRQST); | ||
146 | if (status < 0) | ||
147 | return IRQ_NONE; | ||
148 | |||
149 | while (status) { | ||
150 | int bit = __ffs(status); | ||
151 | |||
152 | handle_nested_irq(tc35892->irq_base + bit); | ||
153 | status &= ~(1 << bit); | ||
154 | } | ||
155 | |||
156 | /* | ||
157 | * A dummy read or write (to any register) appears to be necessary to | ||
158 | * have the last interrupt clear (for example, GPIO IC write) take | ||
159 | * effect. | ||
160 | */ | ||
161 | tc35892_reg_read(tc35892, TC35892_IRQST); | ||
162 | |||
163 | return IRQ_HANDLED; | ||
164 | } | ||
165 | |||
166 | static void tc35892_irq_dummy(unsigned int irq) | ||
167 | { | ||
168 | /* No mask/unmask at this level */ | ||
169 | } | ||
170 | |||
171 | static struct irq_chip tc35892_irq_chip = { | ||
172 | .name = "tc35892", | ||
173 | .mask = tc35892_irq_dummy, | ||
174 | .unmask = tc35892_irq_dummy, | ||
175 | }; | ||
176 | |||
177 | static int tc35892_irq_init(struct tc35892 *tc35892) | ||
178 | { | ||
179 | int base = tc35892->irq_base; | ||
180 | int irq; | ||
181 | |||
182 | for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { | ||
183 | set_irq_chip_data(irq, tc35892); | ||
184 | set_irq_chip_and_handler(irq, &tc35892_irq_chip, | ||
185 | handle_edge_irq); | ||
186 | set_irq_nested_thread(irq, 1); | ||
187 | #ifdef CONFIG_ARM | ||
188 | set_irq_flags(irq, IRQF_VALID); | ||
189 | #else | ||
190 | set_irq_noprobe(irq); | ||
191 | #endif | ||
192 | } | ||
193 | |||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | static void tc35892_irq_remove(struct tc35892 *tc35892) | ||
198 | { | ||
199 | int base = tc35892->irq_base; | ||
200 | int irq; | ||
201 | |||
202 | for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { | ||
203 | #ifdef CONFIG_ARM | ||
204 | set_irq_flags(irq, 0); | ||
205 | #endif | ||
206 | set_irq_chip_and_handler(irq, NULL, NULL); | ||
207 | set_irq_chip_data(irq, NULL); | ||
208 | } | ||
209 | } | ||
210 | |||
211 | static int tc35892_chip_init(struct tc35892 *tc35892) | ||
212 | { | ||
213 | int manf, ver, ret; | ||
214 | |||
215 | manf = tc35892_reg_read(tc35892, TC35892_MANFCODE); | ||
216 | if (manf < 0) | ||
217 | return manf; | ||
218 | |||
219 | ver = tc35892_reg_read(tc35892, TC35892_VERSION); | ||
220 | if (ver < 0) | ||
221 | return ver; | ||
222 | |||
223 | if (manf != TC35892_MANFCODE_MAGIC) { | ||
224 | dev_err(tc35892->dev, "unknown manufacturer: %#x\n", manf); | ||
225 | return -EINVAL; | ||
226 | } | ||
227 | |||
228 | dev_info(tc35892->dev, "manufacturer: %#x, version: %#x\n", manf, ver); | ||
229 | |||
230 | /* Put everything except the IRQ module into reset */ | ||
231 | ret = tc35892_reg_write(tc35892, TC35892_RSTCTRL, | ||
232 | TC35892_RSTCTRL_TIMRST | ||
233 | | TC35892_RSTCTRL_ROTRST | ||
234 | | TC35892_RSTCTRL_KBDRST | ||
235 | | TC35892_RSTCTRL_GPIRST); | ||
236 | if (ret < 0) | ||
237 | return ret; | ||
238 | |||
239 | /* Clear the reset interrupt. */ | ||
240 | return tc35892_reg_write(tc35892, TC35892_RSTINTCLR, 0x1); | ||
241 | } | ||
242 | |||
243 | static int __devinit tc35892_probe(struct i2c_client *i2c, | ||
244 | const struct i2c_device_id *id) | ||
245 | { | ||
246 | struct tc35892_platform_data *pdata = i2c->dev.platform_data; | ||
247 | struct tc35892 *tc35892; | ||
248 | int ret; | ||
249 | |||
250 | if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA | ||
251 | | I2C_FUNC_SMBUS_I2C_BLOCK)) | ||
252 | return -EIO; | ||
253 | |||
254 | tc35892 = kzalloc(sizeof(struct tc35892), GFP_KERNEL); | ||
255 | if (!tc35892) | ||
256 | return -ENOMEM; | ||
257 | |||
258 | mutex_init(&tc35892->lock); | ||
259 | |||
260 | tc35892->dev = &i2c->dev; | ||
261 | tc35892->i2c = i2c; | ||
262 | tc35892->pdata = pdata; | ||
263 | tc35892->irq_base = pdata->irq_base; | ||
264 | tc35892->num_gpio = id->driver_data; | ||
265 | |||
266 | i2c_set_clientdata(i2c, tc35892); | ||
267 | |||
268 | ret = tc35892_chip_init(tc35892); | ||
269 | if (ret) | ||
270 | goto out_free; | ||
271 | |||
272 | ret = tc35892_irq_init(tc35892); | ||
273 | if (ret) | ||
274 | goto out_free; | ||
275 | |||
276 | ret = request_threaded_irq(tc35892->i2c->irq, NULL, tc35892_irq, | ||
277 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | ||
278 | "tc35892", tc35892); | ||
279 | if (ret) { | ||
280 | dev_err(tc35892->dev, "failed to request IRQ: %d\n", ret); | ||
281 | goto out_removeirq; | ||
282 | } | ||
283 | |||
284 | ret = mfd_add_devices(tc35892->dev, -1, tc35892_devs, | ||
285 | ARRAY_SIZE(tc35892_devs), NULL, | ||
286 | tc35892->irq_base); | ||
287 | if (ret) { | ||
288 | dev_err(tc35892->dev, "failed to add children\n"); | ||
289 | goto out_freeirq; | ||
290 | } | ||
291 | |||
292 | return 0; | ||
293 | |||
294 | out_freeirq: | ||
295 | free_irq(tc35892->i2c->irq, tc35892); | ||
296 | out_removeirq: | ||
297 | tc35892_irq_remove(tc35892); | ||
298 | out_free: | ||
299 | i2c_set_clientdata(i2c, NULL); | ||
300 | kfree(tc35892); | ||
301 | return ret; | ||
302 | } | ||
303 | |||
304 | static int __devexit tc35892_remove(struct i2c_client *client) | ||
305 | { | ||
306 | struct tc35892 *tc35892 = i2c_get_clientdata(client); | ||
307 | |||
308 | mfd_remove_devices(tc35892->dev); | ||
309 | |||
310 | free_irq(tc35892->i2c->irq, tc35892); | ||
311 | tc35892_irq_remove(tc35892); | ||
312 | |||
313 | i2c_set_clientdata(client, NULL); | ||
314 | kfree(tc35892); | ||
315 | |||
316 | return 0; | ||
317 | } | ||
318 | |||
319 | static const struct i2c_device_id tc35892_id[] = { | ||
320 | { "tc35892", 24 }, | ||
321 | { } | ||
322 | }; | ||
323 | MODULE_DEVICE_TABLE(i2c, tc35892_id); | ||
324 | |||
325 | static struct i2c_driver tc35892_driver = { | ||
326 | .driver.name = "tc35892", | ||
327 | .driver.owner = THIS_MODULE, | ||
328 | .probe = tc35892_probe, | ||
329 | .remove = __devexit_p(tc35892_remove), | ||
330 | .id_table = tc35892_id, | ||
331 | }; | ||
332 | |||
333 | static int __init tc35892_init(void) | ||
334 | { | ||
335 | return i2c_add_driver(&tc35892_driver); | ||
336 | } | ||
337 | subsys_initcall(tc35892_init); | ||
338 | |||
339 | static void __exit tc35892_exit(void) | ||
340 | { | ||
341 | i2c_del_driver(&tc35892_driver); | ||
342 | } | ||
343 | module_exit(tc35892_exit); | ||
344 | |||
345 | MODULE_LICENSE("GPL v2"); | ||
346 | MODULE_DESCRIPTION("TC35892 MFD core driver"); | ||
347 | MODULE_AUTHOR("Hanumath Prasad, Rabin Vincent"); | ||