]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blame - drivers/extcon/extcon-intel-cht-wc.c
extcon: Remove porting compatibility of swich class
[mirror_ubuntu-artful-kernel.git] / drivers / extcon / extcon-intel-cht-wc.c
CommitLineData
db0f3baa
HG
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");