summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2017-03-23 12:01:42 -0400
committerChanwoo Choi <cw00.choi@samsung.com>2017-04-05 21:55:23 -0400
commitdb0f3baaa38bb587d831b1127082643b5e813074 (patch)
tree9fad71328cb04041abcb2bea2dd77d99b85cbee5
parent01944321de3021020886564328684b7603ee216c (diff)
extcon: intel-cht-wc: Add Intel Cherry Trail Whiskey Cove PMIC extcon driver
Add a driver for charger detection / control on the Intel Cherrytrail Whiskey Cove PMIC. Signed-off-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
-rw-r--r--drivers/extcon/Kconfig7
-rw-r--r--drivers/extcon/Makefile1
-rw-r--r--drivers/extcon/extcon-intel-cht-wc.c353
3 files changed, 361 insertions, 0 deletions
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index fc09c76248b4..32f2dc8e4702 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -52,6 +52,13 @@ config EXTCON_INTEL_INT3496
52 This ACPI device is typically found on Intel Baytrail or Cherrytrail 52 This ACPI device is typically found on Intel Baytrail or Cherrytrail
53 based tablets, or other Baytrail / Cherrytrail devices. 53 based tablets, or other Baytrail / Cherrytrail devices.
54 54
55config EXTCON_INTEL_CHT_WC
56 tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver"
57 depends on INTEL_SOC_PMIC_CHTWC
58 help
59 Say Y here to enable extcon support for charger detection / control
60 on the Intel Cherrytrail Whiskey Cove PMIC.
61
55config EXTCON_MAX14577 62config EXTCON_MAX14577
56 tristate "Maxim MAX14577/77836 EXTCON Support" 63 tristate "Maxim MAX14577/77836 EXTCON Support"
57 depends on MFD_MAX14577 64 depends on MFD_MAX14577
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index 237ac3f953c2..ecfa95804427 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
9obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o 9obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
10obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o 10obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
11obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o 11obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
12obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
12obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o 13obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o
13obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o 14obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o
14obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o 15obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c
new file mode 100644
index 000000000000..f1c43af7682b
--- /dev/null
+++ b/drivers/extcon/extcon-intel-cht-wc.c
@@ -0,0 +1,353 @@
1/*
2 * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC
3 * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
4 *
5 * Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
6 * Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms and conditions of the GNU General Public License,
10 * version 2, as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 */
17
18#include <linux/extcon.h>
19#include <linux/interrupt.h>
20#include <linux/kernel.h>
21#include <linux/mfd/intel_soc_pmic.h>
22#include <linux/module.h>
23#include <linux/platform_device.h>
24#include <linux/regmap.h>
25#include <linux/slab.h>
26
27#define CHT_WC_PHYCTRL 0x5e07
28
29#define CHT_WC_CHGRCTRL0 0x5e16
30#define CHT_WC_CHGRCTRL0_CHGRRESET BIT(0)
31#define CHT_WC_CHGRCTRL0_EMRGCHREN BIT(1)
32#define CHT_WC_CHGRCTRL0_EXTCHRDIS BIT(2)
33#define CHT_WC_CHGRCTRL0_SWCONTROL BIT(3)
34#define CHT_WC_CHGRCTRL0_TTLCK_MASK BIT(4)
35#define CHT_WC_CHGRCTRL0_CCSM_OFF_MASK BIT(5)
36#define CHT_WC_CHGRCTRL0_DBPOFF_MASK BIT(6)
37#define CHT_WC_CHGRCTRL0_WDT_NOKICK BIT(7)
38
39#define CHT_WC_CHGRCTRL1 0x5e17
40
41#define CHT_WC_USBSRC 0x5e29
42#define CHT_WC_USBSRC_STS_MASK GENMASK(1, 0)
43#define CHT_WC_USBSRC_STS_SUCCESS 2
44#define CHT_WC_USBSRC_STS_FAIL 3
45#define CHT_WC_USBSRC_TYPE_SHIFT 2
46#define CHT_WC_USBSRC_TYPE_MASK GENMASK(5, 2)
47#define CHT_WC_USBSRC_TYPE_NONE 0
48#define CHT_WC_USBSRC_TYPE_SDP 1
49#define CHT_WC_USBSRC_TYPE_DCP 2
50#define CHT_WC_USBSRC_TYPE_CDP 3
51#define CHT_WC_USBSRC_TYPE_ACA 4
52#define CHT_WC_USBSRC_TYPE_SE1 5
53#define CHT_WC_USBSRC_TYPE_MHL 6
54#define CHT_WC_USBSRC_TYPE_FLOAT_DP_DN 7
55#define CHT_WC_USBSRC_TYPE_OTHER 8
56#define CHT_WC_USBSRC_TYPE_DCP_EXTPHY 9
57
58#define CHT_WC_PWRSRC_IRQ 0x6e03
59#define CHT_WC_PWRSRC_IRQ_MASK 0x6e0f
60#define CHT_WC_PWRSRC_STS 0x6e1e
61#define CHT_WC_PWRSRC_VBUS BIT(0)
62#define CHT_WC_PWRSRC_DC BIT(1)
63#define CHT_WC_PWRSRC_BAT BIT(2)
64#define CHT_WC_PWRSRC_ID_GND BIT(3)
65#define CHT_WC_PWRSRC_ID_FLOAT BIT(4)
66
67enum cht_wc_usb_id {
68 USB_ID_OTG,
69 USB_ID_GND,
70 USB_ID_FLOAT,
71 USB_RID_A,
72 USB_RID_B,
73 USB_RID_C,
74};
75
76enum cht_wc_mux_select {
77 MUX_SEL_PMIC = 0,
78 MUX_SEL_SOC,
79};
80
81static const unsigned int cht_wc_extcon_cables[] = {
82 EXTCON_USB,
83 EXTCON_USB_HOST,
84 EXTCON_CHG_USB_SDP,
85 EXTCON_CHG_USB_CDP,
86 EXTCON_CHG_USB_DCP,
87 EXTCON_CHG_USB_ACA,
88 EXTCON_NONE,
89};
90
91struct cht_wc_extcon_data {
92 struct device *dev;
93 struct regmap *regmap;
94 struct extcon_dev *edev;
95 unsigned int previous_cable;
96};
97
98static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
99{
100 if (pwrsrc_sts & CHT_WC_PWRSRC_ID_GND)
101 return USB_ID_GND;
102 if (pwrsrc_sts & CHT_WC_PWRSRC_ID_FLOAT)
103 return USB_ID_FLOAT;
104
105 /*
106 * Once we have iio support for the gpadc we should read the USBID
107 * gpadc channel here and determine ACA role based on that.
108 */
109 return USB_ID_FLOAT;
110}
111
112static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext)
113{
114 int ret, usbsrc, status;
115 unsigned long timeout;
116
117 /* Charger detection can take upto 600ms, wait 800ms max. */
118 timeout = jiffies + msecs_to_jiffies(800);
119 do {
120 ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc);
121 if (ret) {
122 dev_err(ext->dev, "Error reading usbsrc: %d\n", ret);
123 return ret;
124 }
125
126 status = usbsrc & CHT_WC_USBSRC_STS_MASK;
127 if (status == CHT_WC_USBSRC_STS_SUCCESS ||
128 status == CHT_WC_USBSRC_STS_FAIL)
129 break;
130
131 msleep(50); /* Wait a bit before retrying */
132 } while (time_before(jiffies, timeout));
133
134 if (status != CHT_WC_USBSRC_STS_SUCCESS) {
135 if (status == CHT_WC_USBSRC_STS_FAIL)
136 dev_warn(ext->dev, "Could not detect charger type\n");
137 else
138 dev_warn(ext->dev, "Timeout detecting charger type\n");
139 return EXTCON_CHG_USB_SDP; /* Save fallback */
140 }
141
142 usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT;
143 switch (usbsrc) {
144 default:
145 dev_warn(ext->dev,
146 "Unhandled charger type %d, defaulting to SDP\n",
147 ret);
148 /* Fall through, treat as SDP */
149 case CHT_WC_USBSRC_TYPE_SDP:
150 case CHT_WC_USBSRC_TYPE_FLOAT_DP_DN:
151 case CHT_WC_USBSRC_TYPE_OTHER:
152 return EXTCON_CHG_USB_SDP;
153 case CHT_WC_USBSRC_TYPE_CDP:
154 return EXTCON_CHG_USB_CDP;
155 case CHT_WC_USBSRC_TYPE_DCP:
156 case CHT_WC_USBSRC_TYPE_DCP_EXTPHY:
157 case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */
158 return EXTCON_CHG_USB_DCP;
159 case CHT_WC_USBSRC_TYPE_ACA:
160 return EXTCON_CHG_USB_ACA;
161 }
162}
163
164static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state)
165{
166 int ret;
167
168 ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state);
169 if (ret)
170 dev_err(ext->dev, "Error writing phyctrl: %d\n", ret);
171}
172
173/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
174static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext,
175 unsigned int cable, bool state)
176{
177 extcon_set_state_sync(ext->edev, cable, state);
178 if (cable == EXTCON_CHG_USB_SDP)
179 extcon_set_state_sync(ext->edev, EXTCON_USB, state);
180}
181
182static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext)
183{
184 int ret, pwrsrc_sts, id;
185 unsigned int cable = EXTCON_NONE;
186
187 ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts);
188 if (ret) {
189 dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret);
190 return;
191 }
192
193 id = cht_wc_extcon_get_id(ext, pwrsrc_sts);
194 if (id == USB_ID_GND) {
195 /* The 5v boost causes a false VBUS / SDP detect, skip */
196 goto charger_det_done;
197 }
198
199 /* Plugged into a host/charger or not connected? */
200 if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) {
201 /* Route D+ and D- to PMIC for future charger detection */
202 cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
203 goto set_state;
204 }
205
206 ret = cht_wc_extcon_get_charger(ext);
207 if (ret >= 0)
208 cable = ret;
209
210charger_det_done:
211 /* Route D+ and D- to SoC for the host or gadget controller */
212 cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC);
213
214set_state:
215 if (cable != ext->previous_cable) {
216 cht_wc_extcon_set_state(ext, cable, true);
217 cht_wc_extcon_set_state(ext, ext->previous_cable, false);
218 ext->previous_cable = cable;
219 }
220
221 extcon_set_state_sync(ext->edev, EXTCON_USB_HOST,
222 id == USB_ID_GND || id == USB_RID_A);
223}
224
225static irqreturn_t cht_wc_extcon_isr(int irq, void *data)
226{
227 struct cht_wc_extcon_data *ext = data;
228 int ret, irqs;
229
230 ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs);
231 if (ret) {
232 dev_err(ext->dev, "Error reading irqs: %d\n", ret);
233 return IRQ_NONE;
234 }
235
236 cht_wc_extcon_pwrsrc_event(ext);
237
238 ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs);
239 if (ret) {
240 dev_err(ext->dev, "Error writing irqs: %d\n", ret);
241 return IRQ_NONE;
242 }
243
244 return IRQ_HANDLED;
245}
246
247static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable)
248{
249 int ret, mask, val;
250
251 mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF_MASK;
252 val = enable ? mask : 0;
253 ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val);
254 if (ret)
255 dev_err(ext->dev, "Error setting sw control: %d\n", ret);
256
257 return ret;
258}
259
260static int cht_wc_extcon_probe(struct platform_device *pdev)
261{
262 struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
263 struct cht_wc_extcon_data *ext;
264 int irq, ret;
265
266 irq = platform_get_irq(pdev, 0);
267 if (irq < 0)
268 return irq;
269
270 ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL);
271 if (!ext)
272 return -ENOMEM;
273
274 ext->dev = &pdev->dev;
275 ext->regmap = pmic->regmap;
276 ext->previous_cable = EXTCON_NONE;
277
278 /* Initialize extcon device */
279 ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables);
280 if (IS_ERR(ext->edev))
281 return PTR_ERR(ext->edev);
282
283 /* Enable sw control */
284 ret = cht_wc_extcon_sw_control(ext, true);
285 if (ret)
286 return ret;
287
288 /* Register extcon device */
289 ret = devm_extcon_dev_register(ext->dev, ext->edev);
290 if (ret) {
291 dev_err(ext->dev, "Error registering extcon device: %d\n", ret);
292 goto disable_sw_control;
293 }
294
295 /* Route D+ and D- to PMIC for initial charger detection */
296 cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
297
298 /* Get initial state */
299 cht_wc_extcon_pwrsrc_event(ext);
300
301 ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr,
302 IRQF_ONESHOT, pdev->name, ext);
303 if (ret) {
304 dev_err(ext->dev, "Error requesting interrupt: %d\n", ret);
305 goto disable_sw_control;
306 }
307
308 /* Unmask irqs */
309 ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK,
310 (int)~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_ID_GND |
311 CHT_WC_PWRSRC_ID_FLOAT));
312 if (ret) {
313 dev_err(ext->dev, "Error writing irq-mask: %d\n", ret);
314 goto disable_sw_control;
315 }
316
317 platform_set_drvdata(pdev, ext);
318
319 return 0;
320
321disable_sw_control:
322 cht_wc_extcon_sw_control(ext, false);
323 return ret;
324}
325
326static int cht_wc_extcon_remove(struct platform_device *pdev)
327{
328 struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev);
329
330 cht_wc_extcon_sw_control(ext, false);
331
332 return 0;
333}
334
335static const struct platform_device_id cht_wc_extcon_table[] = {
336 { .name = "cht_wcove_pwrsrc" },
337 {},
338};
339MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table);
340
341static struct platform_driver cht_wc_extcon_driver = {
342 .probe = cht_wc_extcon_probe,
343 .remove = cht_wc_extcon_remove,
344 .id_table = cht_wc_extcon_table,
345 .driver = {
346 .name = "cht_wcove_pwrsrc",
347 },
348};
349module_platform_driver(cht_wc_extcon_driver);
350
351MODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver");
352MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
353MODULE_LICENSE("GPL v2");