Commit a1bb73d76bc814e9385390e6aa9880d884322e2e
Committed by
Linus Torvalds
1 parent
703f03c896
Exists in
master
and in
4 other branches
drivers/misc: add support the FSA9480 USB Switch
The FSA9480 is a USB port accessory detector and switch. This patch adds support the FSA9480 USB Switch. [akpm@linux-foundation.org: make a couple of things static] Signed-off-by: Donggeun Kim <dg77.kim@samsung.com> Signed-off-by: Minkyu Kang <mk7.kang@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Cc: Greg KH <greg@kroah.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Showing 5 changed files with 615 additions and 0 deletions Side-by-side Diff
Documentation/ABI/testing/sysfs-bus-i2c-devices-fsa9480
1 | +What: /sys/bus/i2c/devices/.../device | |
2 | +Date: February 2011 | |
3 | +Contact: Minkyu Kang <mk7.kang@samsung.com> | |
4 | +Description: | |
5 | + show what device is attached | |
6 | + NONE - no device | |
7 | + USB - USB device is attached | |
8 | + UART - UART is attached | |
9 | + CHARGER - Charger is attaced | |
10 | + JIG - JIG is attached | |
11 | + | |
12 | +What: /sys/bus/i2c/devices/.../switch | |
13 | +Date: February 2011 | |
14 | +Contact: Minkyu Kang <mk7.kang@samsung.com> | |
15 | +Description: | |
16 | + show or set the state of manual switch | |
17 | + VAUDIO - switch to VAUDIO path | |
18 | + UART - switch to UART path | |
19 | + AUDIO - switch to AUDIO path | |
20 | + DHOST - switch to DHOST path | |
21 | + AUTO - switch automatically by device |
drivers/misc/Kconfig
... | ... | @@ -489,6 +489,15 @@ |
489 | 489 | To compile this driver as a module, choose M here: the module will |
490 | 490 | be called pch_phub. |
491 | 491 | |
492 | +config USB_SWITCH_FSA9480 | |
493 | + tristate "FSA9480 USB Switch" | |
494 | + depends on I2C | |
495 | + help | |
496 | + The FSA9480 is a USB port accessory detector and switch. | |
497 | + The FSA9480 is fully controlled using I2C and enables USB data, | |
498 | + stereo and mono audio, video, microphone and UART data to use | |
499 | + a common connector port. | |
500 | + | |
492 | 501 | source "drivers/misc/c2port/Kconfig" |
493 | 502 | source "drivers/misc/eeprom/Kconfig" |
494 | 503 | source "drivers/misc/cb710/Kconfig" |
drivers/misc/Makefile
drivers/misc/fsa9480.c
1 | +/* | |
2 | + * fsa9480.c - FSA9480 micro USB switch device driver | |
3 | + * | |
4 | + * Copyright (C) 2010 Samsung Electronics | |
5 | + * Minkyu Kang <mk7.kang@samsung.com> | |
6 | + * Wonguk Jeong <wonguk.jeong@samsung.com> | |
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 | + | |
13 | +#include <linux/kernel.h> | |
14 | +#include <linux/module.h> | |
15 | +#include <linux/err.h> | |
16 | +#include <linux/i2c.h> | |
17 | +#include <linux/platform_data/fsa9480.h> | |
18 | +#include <linux/irq.h> | |
19 | +#include <linux/interrupt.h> | |
20 | +#include <linux/workqueue.h> | |
21 | +#include <linux/platform_device.h> | |
22 | +#include <linux/slab.h> | |
23 | +#include <linux/pm_runtime.h> | |
24 | + | |
25 | +/* FSA9480 I2C registers */ | |
26 | +#define FSA9480_REG_DEVID 0x01 | |
27 | +#define FSA9480_REG_CTRL 0x02 | |
28 | +#define FSA9480_REG_INT1 0x03 | |
29 | +#define FSA9480_REG_INT2 0x04 | |
30 | +#define FSA9480_REG_INT1_MASK 0x05 | |
31 | +#define FSA9480_REG_INT2_MASK 0x06 | |
32 | +#define FSA9480_REG_ADC 0x07 | |
33 | +#define FSA9480_REG_TIMING1 0x08 | |
34 | +#define FSA9480_REG_TIMING2 0x09 | |
35 | +#define FSA9480_REG_DEV_T1 0x0a | |
36 | +#define FSA9480_REG_DEV_T2 0x0b | |
37 | +#define FSA9480_REG_BTN1 0x0c | |
38 | +#define FSA9480_REG_BTN2 0x0d | |
39 | +#define FSA9480_REG_CK 0x0e | |
40 | +#define FSA9480_REG_CK_INT1 0x0f | |
41 | +#define FSA9480_REG_CK_INT2 0x10 | |
42 | +#define FSA9480_REG_CK_INTMASK1 0x11 | |
43 | +#define FSA9480_REG_CK_INTMASK2 0x12 | |
44 | +#define FSA9480_REG_MANSW1 0x13 | |
45 | +#define FSA9480_REG_MANSW2 0x14 | |
46 | + | |
47 | +/* Control */ | |
48 | +#define CON_SWITCH_OPEN (1 << 4) | |
49 | +#define CON_RAW_DATA (1 << 3) | |
50 | +#define CON_MANUAL_SW (1 << 2) | |
51 | +#define CON_WAIT (1 << 1) | |
52 | +#define CON_INT_MASK (1 << 0) | |
53 | +#define CON_MASK (CON_SWITCH_OPEN | CON_RAW_DATA | \ | |
54 | + CON_MANUAL_SW | CON_WAIT) | |
55 | + | |
56 | +/* Device Type 1 */ | |
57 | +#define DEV_USB_OTG (1 << 7) | |
58 | +#define DEV_DEDICATED_CHG (1 << 6) | |
59 | +#define DEV_USB_CHG (1 << 5) | |
60 | +#define DEV_CAR_KIT (1 << 4) | |
61 | +#define DEV_UART (1 << 3) | |
62 | +#define DEV_USB (1 << 2) | |
63 | +#define DEV_AUDIO_2 (1 << 1) | |
64 | +#define DEV_AUDIO_1 (1 << 0) | |
65 | + | |
66 | +#define DEV_T1_USB_MASK (DEV_USB_OTG | DEV_USB) | |
67 | +#define DEV_T1_UART_MASK (DEV_UART) | |
68 | +#define DEV_T1_CHARGER_MASK (DEV_DEDICATED_CHG | DEV_USB_CHG) | |
69 | + | |
70 | +/* Device Type 2 */ | |
71 | +#define DEV_AV (1 << 6) | |
72 | +#define DEV_TTY (1 << 5) | |
73 | +#define DEV_PPD (1 << 4) | |
74 | +#define DEV_JIG_UART_OFF (1 << 3) | |
75 | +#define DEV_JIG_UART_ON (1 << 2) | |
76 | +#define DEV_JIG_USB_OFF (1 << 1) | |
77 | +#define DEV_JIG_USB_ON (1 << 0) | |
78 | + | |
79 | +#define DEV_T2_USB_MASK (DEV_JIG_USB_OFF | DEV_JIG_USB_ON) | |
80 | +#define DEV_T2_UART_MASK (DEV_JIG_UART_OFF | DEV_JIG_UART_ON) | |
81 | +#define DEV_T2_JIG_MASK (DEV_JIG_USB_OFF | DEV_JIG_USB_ON | \ | |
82 | + DEV_JIG_UART_OFF | DEV_JIG_UART_ON) | |
83 | + | |
84 | +/* | |
85 | + * Manual Switch | |
86 | + * D- [7:5] / D+ [4:2] | |
87 | + * 000: Open all / 001: USB / 010: AUDIO / 011: UART / 100: V_AUDIO | |
88 | + */ | |
89 | +#define SW_VAUDIO ((4 << 5) | (4 << 2)) | |
90 | +#define SW_UART ((3 << 5) | (3 << 2)) | |
91 | +#define SW_AUDIO ((2 << 5) | (2 << 2)) | |
92 | +#define SW_DHOST ((1 << 5) | (1 << 2)) | |
93 | +#define SW_AUTO ((0 << 5) | (0 << 2)) | |
94 | + | |
95 | +/* Interrupt 1 */ | |
96 | +#define INT_DETACH (1 << 1) | |
97 | +#define INT_ATTACH (1 << 0) | |
98 | + | |
99 | +struct fsa9480_usbsw { | |
100 | + struct i2c_client *client; | |
101 | + struct fsa9480_platform_data *pdata; | |
102 | + int dev1; | |
103 | + int dev2; | |
104 | + int mansw; | |
105 | +}; | |
106 | + | |
107 | +static struct fsa9480_usbsw *chip; | |
108 | + | |
109 | +static int fsa9480_write_reg(struct i2c_client *client, | |
110 | + int reg, int value) | |
111 | +{ | |
112 | + int ret; | |
113 | + | |
114 | + ret = i2c_smbus_write_byte_data(client, reg, value); | |
115 | + | |
116 | + if (ret < 0) | |
117 | + dev_err(&client->dev, "%s: err %d\n", __func__, ret); | |
118 | + | |
119 | + return ret; | |
120 | +} | |
121 | + | |
122 | +static int fsa9480_read_reg(struct i2c_client *client, int reg) | |
123 | +{ | |
124 | + int ret; | |
125 | + | |
126 | + ret = i2c_smbus_read_byte_data(client, reg); | |
127 | + | |
128 | + if (ret < 0) | |
129 | + dev_err(&client->dev, "%s: err %d\n", __func__, ret); | |
130 | + | |
131 | + return ret; | |
132 | +} | |
133 | + | |
134 | +static int fsa9480_read_irq(struct i2c_client *client, int *value) | |
135 | +{ | |
136 | + int ret; | |
137 | + | |
138 | + ret = i2c_smbus_read_i2c_block_data(client, | |
139 | + FSA9480_REG_INT1, 2, (u8 *)value); | |
140 | + *value &= 0xffff; | |
141 | + | |
142 | + if (ret < 0) | |
143 | + dev_err(&client->dev, "%s: err %d\n", __func__, ret); | |
144 | + | |
145 | + return ret; | |
146 | +} | |
147 | + | |
148 | +static void fsa9480_set_switch(const char *buf) | |
149 | +{ | |
150 | + struct fsa9480_usbsw *usbsw = chip; | |
151 | + struct i2c_client *client = usbsw->client; | |
152 | + unsigned int value; | |
153 | + unsigned int path = 0; | |
154 | + | |
155 | + value = fsa9480_read_reg(client, FSA9480_REG_CTRL); | |
156 | + | |
157 | + if (!strncmp(buf, "VAUDIO", 6)) { | |
158 | + path = SW_VAUDIO; | |
159 | + value &= ~CON_MANUAL_SW; | |
160 | + } else if (!strncmp(buf, "UART", 4)) { | |
161 | + path = SW_UART; | |
162 | + value &= ~CON_MANUAL_SW; | |
163 | + } else if (!strncmp(buf, "AUDIO", 5)) { | |
164 | + path = SW_AUDIO; | |
165 | + value &= ~CON_MANUAL_SW; | |
166 | + } else if (!strncmp(buf, "DHOST", 5)) { | |
167 | + path = SW_DHOST; | |
168 | + value &= ~CON_MANUAL_SW; | |
169 | + } else if (!strncmp(buf, "AUTO", 4)) { | |
170 | + path = SW_AUTO; | |
171 | + value |= CON_MANUAL_SW; | |
172 | + } else { | |
173 | + printk(KERN_ERR "Wrong command\n"); | |
174 | + return; | |
175 | + } | |
176 | + | |
177 | + usbsw->mansw = path; | |
178 | + fsa9480_write_reg(client, FSA9480_REG_MANSW1, path); | |
179 | + fsa9480_write_reg(client, FSA9480_REG_CTRL, value); | |
180 | +} | |
181 | + | |
182 | +static ssize_t fsa9480_get_switch(char *buf) | |
183 | +{ | |
184 | + struct fsa9480_usbsw *usbsw = chip; | |
185 | + struct i2c_client *client = usbsw->client; | |
186 | + unsigned int value; | |
187 | + | |
188 | + value = fsa9480_read_reg(client, FSA9480_REG_MANSW1); | |
189 | + | |
190 | + if (value == SW_VAUDIO) | |
191 | + return sprintf(buf, "VAUDIO\n"); | |
192 | + else if (value == SW_UART) | |
193 | + return sprintf(buf, "UART\n"); | |
194 | + else if (value == SW_AUDIO) | |
195 | + return sprintf(buf, "AUDIO\n"); | |
196 | + else if (value == SW_DHOST) | |
197 | + return sprintf(buf, "DHOST\n"); | |
198 | + else if (value == SW_AUTO) | |
199 | + return sprintf(buf, "AUTO\n"); | |
200 | + else | |
201 | + return sprintf(buf, "%x", value); | |
202 | +} | |
203 | + | |
204 | +static ssize_t fsa9480_show_device(struct device *dev, | |
205 | + struct device_attribute *attr, | |
206 | + char *buf) | |
207 | +{ | |
208 | + struct fsa9480_usbsw *usbsw = dev_get_drvdata(dev); | |
209 | + struct i2c_client *client = usbsw->client; | |
210 | + int dev1, dev2; | |
211 | + | |
212 | + dev1 = fsa9480_read_reg(client, FSA9480_REG_DEV_T1); | |
213 | + dev2 = fsa9480_read_reg(client, FSA9480_REG_DEV_T2); | |
214 | + | |
215 | + if (!dev1 && !dev2) | |
216 | + return sprintf(buf, "NONE\n"); | |
217 | + | |
218 | + /* USB */ | |
219 | + if (dev1 & DEV_T1_USB_MASK || dev2 & DEV_T2_USB_MASK) | |
220 | + return sprintf(buf, "USB\n"); | |
221 | + | |
222 | + /* UART */ | |
223 | + if (dev1 & DEV_T1_UART_MASK || dev2 & DEV_T2_UART_MASK) | |
224 | + return sprintf(buf, "UART\n"); | |
225 | + | |
226 | + /* CHARGER */ | |
227 | + if (dev1 & DEV_T1_CHARGER_MASK) | |
228 | + return sprintf(buf, "CHARGER\n"); | |
229 | + | |
230 | + /* JIG */ | |
231 | + if (dev2 & DEV_T2_JIG_MASK) | |
232 | + return sprintf(buf, "JIG\n"); | |
233 | + | |
234 | + return sprintf(buf, "UNKNOWN\n"); | |
235 | +} | |
236 | + | |
237 | +static ssize_t fsa9480_show_manualsw(struct device *dev, | |
238 | + struct device_attribute *attr, char *buf) | |
239 | +{ | |
240 | + return fsa9480_get_switch(buf); | |
241 | + | |
242 | +} | |
243 | + | |
244 | +static ssize_t fsa9480_set_manualsw(struct device *dev, | |
245 | + struct device_attribute *attr, | |
246 | + const char *buf, size_t count) | |
247 | +{ | |
248 | + fsa9480_set_switch(buf); | |
249 | + | |
250 | + return count; | |
251 | +} | |
252 | + | |
253 | +static DEVICE_ATTR(device, S_IRUGO, fsa9480_show_device, NULL); | |
254 | +static DEVICE_ATTR(switch, S_IRUGO | S_IWUSR, | |
255 | + fsa9480_show_manualsw, fsa9480_set_manualsw); | |
256 | + | |
257 | +static struct attribute *fsa9480_attributes[] = { | |
258 | + &dev_attr_device.attr, | |
259 | + &dev_attr_switch.attr, | |
260 | + NULL | |
261 | +}; | |
262 | + | |
263 | +static const struct attribute_group fsa9480_group = { | |
264 | + .attrs = fsa9480_attributes, | |
265 | +}; | |
266 | + | |
267 | +static void fsa9480_detect_dev(struct fsa9480_usbsw *usbsw, int intr) | |
268 | +{ | |
269 | + int val1, val2, ctrl; | |
270 | + struct fsa9480_platform_data *pdata = usbsw->pdata; | |
271 | + struct i2c_client *client = usbsw->client; | |
272 | + | |
273 | + val1 = fsa9480_read_reg(client, FSA9480_REG_DEV_T1); | |
274 | + val2 = fsa9480_read_reg(client, FSA9480_REG_DEV_T2); | |
275 | + ctrl = fsa9480_read_reg(client, FSA9480_REG_CTRL); | |
276 | + | |
277 | + dev_info(&client->dev, "intr: 0x%x, dev1: 0x%x, dev2: 0x%x\n", | |
278 | + intr, val1, val2); | |
279 | + | |
280 | + if (!intr) | |
281 | + goto out; | |
282 | + | |
283 | + if (intr & INT_ATTACH) { /* Attached */ | |
284 | + /* USB */ | |
285 | + if (val1 & DEV_T1_USB_MASK || val2 & DEV_T2_USB_MASK) { | |
286 | + if (pdata->usb_cb) | |
287 | + pdata->usb_cb(FSA9480_ATTACHED); | |
288 | + | |
289 | + if (usbsw->mansw) { | |
290 | + fsa9480_write_reg(client, | |
291 | + FSA9480_REG_MANSW1, usbsw->mansw); | |
292 | + } | |
293 | + } | |
294 | + | |
295 | + /* UART */ | |
296 | + if (val1 & DEV_T1_UART_MASK || val2 & DEV_T2_UART_MASK) { | |
297 | + if (pdata->uart_cb) | |
298 | + pdata->uart_cb(FSA9480_ATTACHED); | |
299 | + | |
300 | + if (!(ctrl & CON_MANUAL_SW)) { | |
301 | + fsa9480_write_reg(client, | |
302 | + FSA9480_REG_MANSW1, SW_UART); | |
303 | + } | |
304 | + } | |
305 | + | |
306 | + /* CHARGER */ | |
307 | + if (val1 & DEV_T1_CHARGER_MASK) { | |
308 | + if (pdata->charger_cb) | |
309 | + pdata->charger_cb(FSA9480_ATTACHED); | |
310 | + } | |
311 | + | |
312 | + /* JIG */ | |
313 | + if (val2 & DEV_T2_JIG_MASK) { | |
314 | + if (pdata->jig_cb) | |
315 | + pdata->jig_cb(FSA9480_ATTACHED); | |
316 | + } | |
317 | + } else if (intr & INT_DETACH) { /* Detached */ | |
318 | + /* USB */ | |
319 | + if (usbsw->dev1 & DEV_T1_USB_MASK || | |
320 | + usbsw->dev2 & DEV_T2_USB_MASK) { | |
321 | + if (pdata->usb_cb) | |
322 | + pdata->usb_cb(FSA9480_DETACHED); | |
323 | + } | |
324 | + | |
325 | + /* UART */ | |
326 | + if (usbsw->dev1 & DEV_T1_UART_MASK || | |
327 | + usbsw->dev2 & DEV_T2_UART_MASK) { | |
328 | + if (pdata->uart_cb) | |
329 | + pdata->uart_cb(FSA9480_DETACHED); | |
330 | + } | |
331 | + | |
332 | + /* CHARGER */ | |
333 | + if (usbsw->dev1 & DEV_T1_CHARGER_MASK) { | |
334 | + if (pdata->charger_cb) | |
335 | + pdata->charger_cb(FSA9480_DETACHED); | |
336 | + } | |
337 | + | |
338 | + /* JIG */ | |
339 | + if (usbsw->dev2 & DEV_T2_JIG_MASK) { | |
340 | + if (pdata->jig_cb) | |
341 | + pdata->jig_cb(FSA9480_DETACHED); | |
342 | + } | |
343 | + } | |
344 | + | |
345 | + usbsw->dev1 = val1; | |
346 | + usbsw->dev2 = val2; | |
347 | + | |
348 | +out: | |
349 | + ctrl &= ~CON_INT_MASK; | |
350 | + fsa9480_write_reg(client, FSA9480_REG_CTRL, ctrl); | |
351 | +} | |
352 | + | |
353 | +static irqreturn_t fsa9480_irq_handler(int irq, void *data) | |
354 | +{ | |
355 | + struct fsa9480_usbsw *usbsw = data; | |
356 | + struct i2c_client *client = usbsw->client; | |
357 | + int intr; | |
358 | + | |
359 | + /* clear interrupt */ | |
360 | + fsa9480_read_irq(client, &intr); | |
361 | + | |
362 | + /* device detection */ | |
363 | + fsa9480_detect_dev(usbsw, intr); | |
364 | + | |
365 | + return IRQ_HANDLED; | |
366 | +} | |
367 | + | |
368 | +static int fsa9480_irq_init(struct fsa9480_usbsw *usbsw) | |
369 | +{ | |
370 | + struct fsa9480_platform_data *pdata = usbsw->pdata; | |
371 | + struct i2c_client *client = usbsw->client; | |
372 | + int ret; | |
373 | + int intr; | |
374 | + unsigned int ctrl = CON_MASK; | |
375 | + | |
376 | + /* clear interrupt */ | |
377 | + fsa9480_read_irq(client, &intr); | |
378 | + | |
379 | + /* unmask interrupt (attach/detach only) */ | |
380 | + fsa9480_write_reg(client, FSA9480_REG_INT1_MASK, 0xfc); | |
381 | + fsa9480_write_reg(client, FSA9480_REG_INT2_MASK, 0x1f); | |
382 | + | |
383 | + usbsw->mansw = fsa9480_read_reg(client, FSA9480_REG_MANSW1); | |
384 | + | |
385 | + if (usbsw->mansw) | |
386 | + ctrl &= ~CON_MANUAL_SW; /* Manual Switching Mode */ | |
387 | + | |
388 | + fsa9480_write_reg(client, FSA9480_REG_CTRL, ctrl); | |
389 | + | |
390 | + if (pdata && pdata->cfg_gpio) | |
391 | + pdata->cfg_gpio(); | |
392 | + | |
393 | + if (client->irq) { | |
394 | + ret = request_threaded_irq(client->irq, NULL, | |
395 | + fsa9480_irq_handler, | |
396 | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | |
397 | + "fsa9480 micro USB", usbsw); | |
398 | + if (ret) { | |
399 | + dev_err(&client->dev, "failed to reqeust IRQ\n"); | |
400 | + return ret; | |
401 | + } | |
402 | + | |
403 | + device_init_wakeup(&client->dev, pdata->wakeup); | |
404 | + } | |
405 | + | |
406 | + return 0; | |
407 | +} | |
408 | + | |
409 | +static int __devinit fsa9480_probe(struct i2c_client *client, | |
410 | + const struct i2c_device_id *id) | |
411 | +{ | |
412 | + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); | |
413 | + struct fsa9480_usbsw *usbsw; | |
414 | + int ret = 0; | |
415 | + | |
416 | + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | |
417 | + return -EIO; | |
418 | + | |
419 | + usbsw = kzalloc(sizeof(struct fsa9480_usbsw), GFP_KERNEL); | |
420 | + if (!usbsw) { | |
421 | + dev_err(&client->dev, "failed to allocate driver data\n"); | |
422 | + return -ENOMEM; | |
423 | + } | |
424 | + | |
425 | + usbsw->client = client; | |
426 | + usbsw->pdata = client->dev.platform_data; | |
427 | + | |
428 | + chip = usbsw; | |
429 | + | |
430 | + i2c_set_clientdata(client, usbsw); | |
431 | + | |
432 | + ret = fsa9480_irq_init(usbsw); | |
433 | + if (ret) | |
434 | + goto fail1; | |
435 | + | |
436 | + ret = sysfs_create_group(&client->dev.kobj, &fsa9480_group); | |
437 | + if (ret) { | |
438 | + dev_err(&client->dev, | |
439 | + "failed to create fsa9480 attribute group\n"); | |
440 | + goto fail2; | |
441 | + } | |
442 | + | |
443 | + /* ADC Detect Time: 500ms */ | |
444 | + fsa9480_write_reg(client, FSA9480_REG_TIMING1, 0x6); | |
445 | + | |
446 | + if (chip->pdata->reset_cb) | |
447 | + chip->pdata->reset_cb(); | |
448 | + | |
449 | + /* device detection */ | |
450 | + fsa9480_detect_dev(usbsw, INT_ATTACH); | |
451 | + | |
452 | + pm_runtime_set_active(&client->dev); | |
453 | + | |
454 | + return 0; | |
455 | + | |
456 | +fail2: | |
457 | + if (client->irq) | |
458 | + free_irq(client->irq, NULL); | |
459 | +fail1: | |
460 | + i2c_set_clientdata(client, NULL); | |
461 | + kfree(usbsw); | |
462 | + return ret; | |
463 | +} | |
464 | + | |
465 | +static int __devexit fsa9480_remove(struct i2c_client *client) | |
466 | +{ | |
467 | + struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client); | |
468 | + if (client->irq) | |
469 | + free_irq(client->irq, NULL); | |
470 | + i2c_set_clientdata(client, NULL); | |
471 | + | |
472 | + sysfs_remove_group(&client->dev.kobj, &fsa9480_group); | |
473 | + device_init_wakeup(&client->dev, 0); | |
474 | + kfree(usbsw); | |
475 | + return 0; | |
476 | +} | |
477 | + | |
478 | +#ifdef CONFIG_PM | |
479 | + | |
480 | +static int fsa9480_suspend(struct i2c_client *client, pm_message_t state) | |
481 | +{ | |
482 | + struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client); | |
483 | + struct fsa9480_platform_data *pdata = usbsw->pdata; | |
484 | + | |
485 | + if (device_may_wakeup(&client->dev) && client->irq) | |
486 | + enable_irq_wake(client->irq); | |
487 | + | |
488 | + if (pdata->usb_power) | |
489 | + pdata->usb_power(0); | |
490 | + | |
491 | + return 0; | |
492 | +} | |
493 | + | |
494 | +static int fsa9480_resume(struct i2c_client *client) | |
495 | +{ | |
496 | + struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client); | |
497 | + int dev1, dev2; | |
498 | + | |
499 | + if (device_may_wakeup(&client->dev) && client->irq) | |
500 | + disable_irq_wake(client->irq); | |
501 | + | |
502 | + /* | |
503 | + * Clear Pending interrupt. Note that detect_dev does what | |
504 | + * the interrupt handler does. So, we don't miss pending and | |
505 | + * we reenable interrupt if there is one. | |
506 | + */ | |
507 | + fsa9480_read_reg(client, FSA9480_REG_INT1); | |
508 | + fsa9480_read_reg(client, FSA9480_REG_INT2); | |
509 | + | |
510 | + dev1 = fsa9480_read_reg(client, FSA9480_REG_DEV_T1); | |
511 | + dev2 = fsa9480_read_reg(client, FSA9480_REG_DEV_T2); | |
512 | + | |
513 | + /* device detection */ | |
514 | + fsa9480_detect_dev(usbsw, (dev1 || dev2) ? INT_ATTACH : INT_DETACH); | |
515 | + | |
516 | + return 0; | |
517 | +} | |
518 | + | |
519 | +#else | |
520 | + | |
521 | +#define fsa9480_suspend NULL | |
522 | +#define fsa9480_resume NULL | |
523 | + | |
524 | +#endif /* CONFIG_PM */ | |
525 | + | |
526 | +static const struct i2c_device_id fsa9480_id[] = { | |
527 | + {"fsa9480", 0}, | |
528 | + {} | |
529 | +}; | |
530 | +MODULE_DEVICE_TABLE(i2c, fsa9480_id); | |
531 | + | |
532 | +static struct i2c_driver fsa9480_i2c_driver = { | |
533 | + .driver = { | |
534 | + .name = "fsa9480", | |
535 | + }, | |
536 | + .probe = fsa9480_probe, | |
537 | + .remove = __devexit_p(fsa9480_remove), | |
538 | + .resume = fsa9480_resume, | |
539 | + .suspend = fsa9480_suspend, | |
540 | + .id_table = fsa9480_id, | |
541 | +}; | |
542 | + | |
543 | +static int __init fsa9480_init(void) | |
544 | +{ | |
545 | + return i2c_add_driver(&fsa9480_i2c_driver); | |
546 | +} | |
547 | +module_init(fsa9480_init); | |
548 | + | |
549 | +static void __exit fsa9480_exit(void) | |
550 | +{ | |
551 | + i2c_del_driver(&fsa9480_i2c_driver); | |
552 | +} | |
553 | +module_exit(fsa9480_exit); | |
554 | + | |
555 | +MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>"); | |
556 | +MODULE_DESCRIPTION("FSA9480 USB Switch driver"); | |
557 | +MODULE_LICENSE("GPL"); |
include/linux/platform_data/fsa9480.h
1 | +/* | |
2 | + * Copyright (C) 2010 Samsung Electronics | |
3 | + * Minkyu Kang <mk7.kang@samsung.com> | |
4 | + * | |
5 | + * This program is free software; you can redistribute it and/or modify | |
6 | + * it under the terms of the GNU General Public License version 2 as | |
7 | + * published by the Free Software Foundation. | |
8 | + */ | |
9 | + | |
10 | +#ifndef _FSA9480_H_ | |
11 | +#define _FSA9480_H_ | |
12 | + | |
13 | +#define FSA9480_ATTACHED 1 | |
14 | +#define FSA9480_DETACHED 0 | |
15 | + | |
16 | +struct fsa9480_platform_data { | |
17 | + void (*cfg_gpio) (void); | |
18 | + void (*usb_cb) (u8 attached); | |
19 | + void (*uart_cb) (u8 attached); | |
20 | + void (*charger_cb) (u8 attached); | |
21 | + void (*jig_cb) (u8 attached); | |
22 | + void (*reset_cb) (void); | |
23 | + void (*usb_power) (u8 on); | |
24 | + int wakeup; | |
25 | +}; | |
26 | + | |
27 | +#endif /* _FSA9480_H_ */ |