]>
Commit | Line | Data |
---|---|---|
99f09beb | 1 | /* |
b76668ba | 2 | * extcon-max8997.c - MAX8997 extcon driver to support MAX8997 MUIC |
99f09beb | 3 | * |
2ca36f4a | 4 | * Copyright (C) 2012 Samsung Electronics |
99f09beb DK |
5 | * Donggeun Kim <dg77.kim@samsung.com> |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
99f09beb DK |
16 | */ |
17 | ||
18 | #include <linux/kernel.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/i2c.h> | |
21 | #include <linux/slab.h> | |
22 | #include <linux/interrupt.h> | |
23 | #include <linux/err.h> | |
24 | #include <linux/platform_device.h> | |
25 | #include <linux/kobject.h> | |
26 | #include <linux/mfd/max8997.h> | |
27 | #include <linux/mfd/max8997-private.h> | |
b76668ba | 28 | #include <linux/extcon.h> |
dca1a71e | 29 | #include <linux/irqdomain.h> |
b76668ba CC |
30 | |
31 | #define DEV_NAME "max8997-muic" | |
99f09beb | 32 | |
99f09beb DK |
33 | struct max8997_muic_irq { |
34 | unsigned int irq; | |
35 | const char *name; | |
dca1a71e | 36 | unsigned int virq; |
99f09beb DK |
37 | }; |
38 | ||
39 | static struct max8997_muic_irq muic_irqs[] = { | |
40 | { MAX8997_MUICIRQ_ADCError, "muic-ADC_error" }, | |
41 | { MAX8997_MUICIRQ_ADCLow, "muic-ADC_low" }, | |
42 | { MAX8997_MUICIRQ_ADC, "muic-ADC" }, | |
43 | { MAX8997_MUICIRQ_VBVolt, "muic-VB_voltage" }, | |
44 | { MAX8997_MUICIRQ_DBChg, "muic-DB_charger" }, | |
45 | { MAX8997_MUICIRQ_DCDTmr, "muic-DCD_timer" }, | |
46 | { MAX8997_MUICIRQ_ChgDetRun, "muic-CDR_status" }, | |
47 | { MAX8997_MUICIRQ_ChgTyp, "muic-charger_type" }, | |
48 | { MAX8997_MUICIRQ_OVP, "muic-over_voltage" }, | |
49 | }; | |
50 | ||
51 | struct max8997_muic_info { | |
52 | struct device *dev; | |
99f09beb DK |
53 | struct i2c_client *muic; |
54 | struct max8997_muic_platform_data *muic_pdata; | |
55 | ||
56 | int irq; | |
57 | struct work_struct irq_work; | |
58 | ||
59 | enum max8997_muic_charger_type pre_charger_type; | |
60 | int pre_adc; | |
61 | ||
62 | struct mutex mutex; | |
b76668ba CC |
63 | |
64 | struct extcon_dev *edev; | |
65 | }; | |
66 | ||
e3e5bc02 CC |
67 | enum { |
68 | EXTCON_CABLE_USB = 0, | |
69 | EXTCON_CABLE_USB_HOST, | |
70 | EXTCON_CABLE_TA, | |
71 | EXTCON_CABLE_FAST_CHARGER, | |
72 | EXTCON_CABLE_SLOW_CHARGER, | |
73 | EXTCON_CABLE_CHARGE_DOWNSTREAM, | |
74 | EXTCON_CABLE_MHL, | |
75 | EXTCON_CABLE_DOCK_DESK, | |
76 | EXTCON_CABLE_DOCK_CARD, | |
77 | EXTCON_CABLE_JIG, | |
78 | ||
79 | _EXTCON_CABLE_NUM, | |
80 | }; | |
81 | ||
cae93db3 | 82 | static const char *max8997_extcon_cable[] = { |
e3e5bc02 CC |
83 | [EXTCON_CABLE_USB] = "USB", |
84 | [EXTCON_CABLE_USB_HOST] = "USB-Host", | |
85 | [EXTCON_CABLE_TA] = "TA", | |
86 | [EXTCON_CABLE_FAST_CHARGER] = "Fast-charger", | |
87 | [EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger", | |
88 | [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream", | |
89 | [EXTCON_CABLE_MHL] = "MHL", | |
90 | [EXTCON_CABLE_DOCK_DESK] = "Dock-Desk", | |
91 | [EXTCON_CABLE_DOCK_CARD] = "Dock-Card", | |
92 | [EXTCON_CABLE_JIG] = "JIG", | |
b76668ba CC |
93 | |
94 | NULL, | |
99f09beb DK |
95 | }; |
96 | ||
97 | static int max8997_muic_handle_usb(struct max8997_muic_info *info, | |
98 | enum max8997_muic_usb_type usb_type, bool attached) | |
99 | { | |
99f09beb DK |
100 | int ret = 0; |
101 | ||
102 | if (usb_type == MAX8997_USB_HOST) { | |
103 | /* switch to USB */ | |
104 | ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, | |
e3e5bc02 CC |
105 | attached ? CONTROL1_SW_USB : CONTROL1_SW_OPEN, |
106 | CONTROL1_SW_MASK); | |
99f09beb DK |
107 | if (ret) { |
108 | dev_err(info->dev, "failed to update muic register\n"); | |
109 | goto out; | |
110 | } | |
111 | } | |
112 | ||
b76668ba CC |
113 | switch (usb_type) { |
114 | case MAX8997_USB_HOST: | |
115 | extcon_set_cable_state(info->edev, "USB-Host", attached); | |
116 | break; | |
117 | case MAX8997_USB_DEVICE: | |
118 | extcon_set_cable_state(info->edev, "USB", attached); | |
119 | break; | |
120 | default: | |
121 | ret = -EINVAL; | |
122 | break; | |
123 | } | |
124 | ||
99f09beb DK |
125 | out: |
126 | return ret; | |
127 | } | |
128 | ||
99f09beb DK |
129 | static int max8997_muic_handle_dock(struct max8997_muic_info *info, |
130 | int adc, bool attached) | |
131 | { | |
99f09beb DK |
132 | int ret = 0; |
133 | ||
134 | /* switch to AUDIO */ | |
135 | ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, | |
e3e5bc02 CC |
136 | attached ? CONTROL1_SW_AUDIO : CONTROL1_SW_OPEN, |
137 | CONTROL1_SW_MASK); | |
99f09beb DK |
138 | if (ret) { |
139 | dev_err(info->dev, "failed to update muic register\n"); | |
140 | goto out; | |
141 | } | |
142 | ||
143 | switch (adc) { | |
144 | case MAX8997_ADC_DESKDOCK: | |
b76668ba | 145 | extcon_set_cable_state(info->edev, "Dock-desk", attached); |
99f09beb DK |
146 | break; |
147 | case MAX8997_ADC_CARDOCK: | |
b76668ba | 148 | extcon_set_cable_state(info->edev, "Dock-card", attached); |
99f09beb DK |
149 | break; |
150 | default: | |
b76668ba | 151 | ret = -EINVAL; |
99f09beb DK |
152 | break; |
153 | } | |
154 | out: | |
155 | return ret; | |
156 | } | |
157 | ||
158 | static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, | |
159 | bool attached) | |
160 | { | |
99f09beb DK |
161 | int ret = 0; |
162 | ||
163 | /* switch to UART */ | |
164 | ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, | |
e3e5bc02 CC |
165 | attached ? CONTROL1_SW_UART : CONTROL1_SW_OPEN, |
166 | CONTROL1_SW_MASK); | |
99f09beb DK |
167 | if (ret) { |
168 | dev_err(info->dev, "failed to update muic register\n"); | |
169 | goto out; | |
170 | } | |
171 | ||
b76668ba | 172 | extcon_set_cable_state(info->edev, "JIG", attached); |
99f09beb DK |
173 | out: |
174 | return ret; | |
175 | } | |
176 | ||
177 | static int max8997_muic_handle_adc_detach(struct max8997_muic_info *info) | |
178 | { | |
179 | int ret = 0; | |
180 | ||
181 | switch (info->pre_adc) { | |
182 | case MAX8997_ADC_GROUND: | |
183 | ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, false); | |
184 | break; | |
185 | case MAX8997_ADC_MHL: | |
b76668ba | 186 | extcon_set_cable_state(info->edev, "MHL", false); |
99f09beb DK |
187 | break; |
188 | case MAX8997_ADC_JIG_USB_1: | |
189 | case MAX8997_ADC_JIG_USB_2: | |
190 | ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, false); | |
191 | break; | |
192 | case MAX8997_ADC_DESKDOCK: | |
193 | case MAX8997_ADC_CARDOCK: | |
194 | ret = max8997_muic_handle_dock(info, info->pre_adc, false); | |
195 | break; | |
196 | case MAX8997_ADC_JIG_UART: | |
197 | ret = max8997_muic_handle_jig_uart(info, false); | |
198 | break; | |
199 | default: | |
200 | break; | |
201 | } | |
202 | ||
203 | return ret; | |
204 | } | |
205 | ||
206 | static int max8997_muic_handle_adc(struct max8997_muic_info *info, int adc) | |
207 | { | |
208 | int ret = 0; | |
209 | ||
210 | switch (adc) { | |
211 | case MAX8997_ADC_GROUND: | |
212 | ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, true); | |
213 | break; | |
214 | case MAX8997_ADC_MHL: | |
b76668ba | 215 | extcon_set_cable_state(info->edev, "MHL", true); |
99f09beb DK |
216 | break; |
217 | case MAX8997_ADC_JIG_USB_1: | |
218 | case MAX8997_ADC_JIG_USB_2: | |
219 | ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, true); | |
220 | break; | |
221 | case MAX8997_ADC_DESKDOCK: | |
222 | case MAX8997_ADC_CARDOCK: | |
223 | ret = max8997_muic_handle_dock(info, adc, true); | |
224 | break; | |
225 | case MAX8997_ADC_JIG_UART: | |
226 | ret = max8997_muic_handle_jig_uart(info, true); | |
227 | break; | |
228 | case MAX8997_ADC_OPEN: | |
229 | ret = max8997_muic_handle_adc_detach(info); | |
230 | break; | |
231 | default: | |
b76668ba CC |
232 | ret = -EINVAL; |
233 | goto out; | |
99f09beb DK |
234 | } |
235 | ||
236 | info->pre_adc = adc; | |
b76668ba CC |
237 | out: |
238 | return ret; | |
239 | } | |
240 | ||
241 | static int max8997_muic_handle_charger_type_detach( | |
242 | struct max8997_muic_info *info) | |
243 | { | |
b76668ba CC |
244 | switch (info->pre_charger_type) { |
245 | case MAX8997_CHARGER_TYPE_USB: | |
246 | extcon_set_cable_state(info->edev, "USB", false); | |
247 | break; | |
248 | case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: | |
249 | extcon_set_cable_state(info->edev, "Charge-downstream", false); | |
250 | break; | |
251 | case MAX8997_CHARGER_TYPE_DEDICATED_CHG: | |
252 | extcon_set_cable_state(info->edev, "TA", false); | |
253 | break; | |
254 | case MAX8997_CHARGER_TYPE_500MA: | |
255 | extcon_set_cable_state(info->edev, "Slow-charger", false); | |
256 | break; | |
257 | case MAX8997_CHARGER_TYPE_1A: | |
258 | extcon_set_cable_state(info->edev, "Fast-charger", false); | |
259 | break; | |
260 | default: | |
3cafbd4e | 261 | return -EINVAL; |
b76668ba | 262 | } |
99f09beb | 263 | |
3cafbd4e | 264 | return 0; |
99f09beb DK |
265 | } |
266 | ||
267 | static int max8997_muic_handle_charger_type(struct max8997_muic_info *info, | |
268 | enum max8997_muic_charger_type charger_type) | |
269 | { | |
99f09beb DK |
270 | u8 adc; |
271 | int ret; | |
272 | ||
273 | ret = max8997_read_reg(info->muic, MAX8997_MUIC_REG_STATUS1, &adc); | |
274 | if (ret) { | |
275 | dev_err(info->dev, "failed to read muic register\n"); | |
276 | goto out; | |
277 | } | |
278 | ||
279 | switch (charger_type) { | |
280 | case MAX8997_CHARGER_TYPE_NONE: | |
b76668ba | 281 | ret = max8997_muic_handle_charger_type_detach(info); |
99f09beb DK |
282 | break; |
283 | case MAX8997_CHARGER_TYPE_USB: | |
284 | if ((adc & STATUS1_ADC_MASK) == MAX8997_ADC_OPEN) { | |
285 | max8997_muic_handle_usb(info, | |
286 | MAX8997_USB_DEVICE, true); | |
287 | } | |
99f09beb DK |
288 | break; |
289 | case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: | |
b76668ba CC |
290 | extcon_set_cable_state(info->edev, "Charge-downstream", true); |
291 | break; | |
99f09beb | 292 | case MAX8997_CHARGER_TYPE_DEDICATED_CHG: |
b76668ba CC |
293 | extcon_set_cable_state(info->edev, "TA", true); |
294 | break; | |
99f09beb | 295 | case MAX8997_CHARGER_TYPE_500MA: |
b76668ba CC |
296 | extcon_set_cable_state(info->edev, "Slow-charger", true); |
297 | break; | |
99f09beb | 298 | case MAX8997_CHARGER_TYPE_1A: |
b76668ba | 299 | extcon_set_cable_state(info->edev, "Fast-charger", true); |
99f09beb DK |
300 | break; |
301 | default: | |
b76668ba CC |
302 | ret = -EINVAL; |
303 | goto out; | |
99f09beb DK |
304 | } |
305 | ||
306 | info->pre_charger_type = charger_type; | |
307 | out: | |
308 | return ret; | |
309 | } | |
310 | ||
311 | static void max8997_muic_irq_work(struct work_struct *work) | |
312 | { | |
313 | struct max8997_muic_info *info = container_of(work, | |
314 | struct max8997_muic_info, irq_work); | |
b76668ba | 315 | u8 status[2]; |
71e58782 | 316 | u8 adc, chg_type; |
dca1a71e CC |
317 | int irq_type = 0; |
318 | int i, ret; | |
99f09beb DK |
319 | |
320 | mutex_lock(&info->mutex); | |
321 | ||
322 | ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, | |
b76668ba | 323 | 2, status); |
99f09beb DK |
324 | if (ret) { |
325 | dev_err(info->dev, "failed to read muic register\n"); | |
326 | mutex_unlock(&info->mutex); | |
327 | return; | |
328 | } | |
329 | ||
330 | dev_dbg(info->dev, "%s: STATUS1:0x%x, 2:0x%x\n", __func__, | |
331 | status[0], status[1]); | |
332 | ||
dca1a71e CC |
333 | for (i = 0 ; i < ARRAY_SIZE(muic_irqs) ; i++) |
334 | if (info->irq == muic_irqs[i].virq) | |
335 | irq_type = muic_irqs[i].irq; | |
336 | ||
99f09beb | 337 | switch (irq_type) { |
99f09beb DK |
338 | case MAX8997_MUICIRQ_ADC: |
339 | adc = status[0] & STATUS1_ADC_MASK; | |
340 | adc >>= STATUS1_ADC_SHIFT; | |
341 | ||
342 | max8997_muic_handle_adc(info, adc); | |
99f09beb DK |
343 | break; |
344 | case MAX8997_MUICIRQ_ChgTyp: | |
345 | chg_type = status[1] & STATUS2_CHGTYP_MASK; | |
346 | chg_type >>= STATUS2_CHGTYP_SHIFT; | |
347 | ||
348 | max8997_muic_handle_charger_type(info, chg_type); | |
99f09beb DK |
349 | break; |
350 | default: | |
b76668ba CC |
351 | dev_info(info->dev, "misc interrupt: irq %d occurred\n", |
352 | irq_type); | |
99f09beb DK |
353 | break; |
354 | } | |
355 | ||
99f09beb DK |
356 | mutex_unlock(&info->mutex); |
357 | ||
358 | return; | |
359 | } | |
360 | ||
361 | static irqreturn_t max8997_muic_irq_handler(int irq, void *data) | |
362 | { | |
363 | struct max8997_muic_info *info = data; | |
364 | ||
365 | dev_dbg(info->dev, "irq:%d\n", irq); | |
366 | info->irq = irq; | |
367 | ||
368 | schedule_work(&info->irq_work); | |
369 | ||
370 | return IRQ_HANDLED; | |
371 | } | |
372 | ||
373 | static void max8997_muic_detect_dev(struct max8997_muic_info *info) | |
374 | { | |
375 | int ret; | |
376 | u8 status[2], adc, chg_type; | |
377 | ||
378 | ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, | |
379 | 2, status); | |
380 | if (ret) { | |
381 | dev_err(info->dev, "failed to read muic register\n"); | |
382 | return; | |
383 | } | |
384 | ||
385 | dev_info(info->dev, "STATUS1:0x%x, STATUS2:0x%x\n", | |
386 | status[0], status[1]); | |
387 | ||
388 | adc = status[0] & STATUS1_ADC_MASK; | |
389 | adc >>= STATUS1_ADC_SHIFT; | |
390 | ||
391 | chg_type = status[1] & STATUS2_CHGTYP_MASK; | |
392 | chg_type >>= STATUS2_CHGTYP_SHIFT; | |
393 | ||
394 | max8997_muic_handle_adc(info, adc); | |
395 | max8997_muic_handle_charger_type(info, chg_type); | |
396 | } | |
397 | ||
44f34fd4 | 398 | static int max8997_muic_probe(struct platform_device *pdev) |
99f09beb | 399 | { |
b76668ba CC |
400 | struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent); |
401 | struct max8997_platform_data *pdata = dev_get_platdata(max8997->dev); | |
99f09beb DK |
402 | struct max8997_muic_info *info; |
403 | int ret, i; | |
404 | ||
0b672e9b SK |
405 | info = devm_kzalloc(&pdev->dev, sizeof(struct max8997_muic_info), |
406 | GFP_KERNEL); | |
99f09beb DK |
407 | if (!info) { |
408 | dev_err(&pdev->dev, "failed to allocate memory\n"); | |
0b672e9b | 409 | return -ENOMEM; |
99f09beb DK |
410 | } |
411 | ||
99f09beb | 412 | info->dev = &pdev->dev; |
b76668ba | 413 | info->muic = max8997->muic; |
99f09beb DK |
414 | |
415 | platform_set_drvdata(pdev, info); | |
416 | mutex_init(&info->mutex); | |
417 | ||
99f09beb DK |
418 | INIT_WORK(&info->irq_work, max8997_muic_irq_work); |
419 | ||
420 | for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { | |
421 | struct max8997_muic_irq *muic_irq = &muic_irqs[i]; | |
68c9274d | 422 | unsigned int virq = 0; |
dca1a71e CC |
423 | |
424 | virq = irq_create_mapping(max8997->irq_domain, muic_irq->irq); | |
68c9274d SK |
425 | if (!virq) { |
426 | ret = -EINVAL; | |
dca1a71e | 427 | goto err_irq; |
68c9274d | 428 | } |
dca1a71e | 429 | muic_irq->virq = virq; |
99f09beb | 430 | |
ae3b3215 CC |
431 | ret = request_threaded_irq(virq, NULL, |
432 | max8997_muic_irq_handler, | |
433 | IRQF_NO_SUSPEND, | |
434 | muic_irq->name, info); | |
99f09beb DK |
435 | if (ret) { |
436 | dev_err(&pdev->dev, | |
437 | "failed: irq request (IRQ: %d," | |
438 | " error :%d)\n", | |
439 | muic_irq->irq, ret); | |
99f09beb DK |
440 | goto err_irq; |
441 | } | |
442 | } | |
443 | ||
b76668ba | 444 | /* External connector */ |
0b672e9b SK |
445 | info->edev = devm_kzalloc(&pdev->dev, sizeof(struct extcon_dev), |
446 | GFP_KERNEL); | |
b76668ba CC |
447 | if (!info->edev) { |
448 | dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); | |
449 | ret = -ENOMEM; | |
450 | goto err_irq; | |
451 | } | |
452 | info->edev->name = DEV_NAME; | |
453 | info->edev->supported_cable = max8997_extcon_cable; | |
454 | ret = extcon_dev_register(info->edev, NULL); | |
455 | if (ret) { | |
456 | dev_err(&pdev->dev, "failed to register extcon device\n"); | |
0b672e9b | 457 | goto err_irq; |
b76668ba CC |
458 | } |
459 | ||
99f09beb | 460 | /* Initialize registers according to platform data */ |
b76668ba CC |
461 | if (pdata->muic_pdata) { |
462 | struct max8997_muic_platform_data *mdata = info->muic_pdata; | |
463 | ||
464 | for (i = 0; i < mdata->num_init_data; i++) { | |
465 | max8997_write_reg(info->muic, mdata->init_data[i].addr, | |
466 | mdata->init_data[i].data); | |
467 | } | |
468 | } | |
99f09beb DK |
469 | |
470 | /* Initial device detection */ | |
471 | max8997_muic_detect_dev(info); | |
472 | ||
473 | return ret; | |
474 | ||
475 | err_irq: | |
3241d56e | 476 | while (--i >= 0) |
dca1a71e | 477 | free_irq(muic_irqs[i].virq, info); |
99f09beb DK |
478 | return ret; |
479 | } | |
480 | ||
93ed0327 | 481 | static int max8997_muic_remove(struct platform_device *pdev) |
99f09beb DK |
482 | { |
483 | struct max8997_muic_info *info = platform_get_drvdata(pdev); | |
99f09beb DK |
484 | int i; |
485 | ||
99f09beb | 486 | for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) |
dca1a71e | 487 | free_irq(muic_irqs[i].virq, info); |
71e58782 | 488 | cancel_work_sync(&info->irq_work); |
99f09beb | 489 | |
b76668ba CC |
490 | extcon_dev_unregister(info->edev); |
491 | ||
99f09beb DK |
492 | return 0; |
493 | } | |
494 | ||
495 | static struct platform_driver max8997_muic_driver = { | |
496 | .driver = { | |
b76668ba | 497 | .name = DEV_NAME, |
99f09beb DK |
498 | .owner = THIS_MODULE, |
499 | }, | |
500 | .probe = max8997_muic_probe, | |
5f7e2228 | 501 | .remove = max8997_muic_remove, |
99f09beb DK |
502 | }; |
503 | ||
b00e126f | 504 | module_platform_driver(max8997_muic_driver); |
99f09beb | 505 | |
b76668ba | 506 | MODULE_DESCRIPTION("Maxim MAX8997 Extcon driver"); |
99f09beb DK |
507 | MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); |
508 | MODULE_LICENSE("GPL"); |