]> asedeno.scripts.mit.edu Git - linux.git/blob - drivers/extcon/extcon-axp288.c
extcon: axp288: Redo charger type detection a couple of seconds after probe()
[linux.git] / drivers / extcon / extcon-axp288.c
1 /*
2  * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
3  *
4  * Copyright (C) 2016-2017 Hans de Goede <hdegoede@redhat.com>
5  * Copyright (C) 2015 Intel Corporation
6  * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.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  * 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.
16  */
17
18 #include <linux/module.h>
19 #include <linux/kernel.h>
20 #include <linux/io.h>
21 #include <linux/slab.h>
22 #include <linux/interrupt.h>
23 #include <linux/platform_device.h>
24 #include <linux/property.h>
25 #include <linux/notifier.h>
26 #include <linux/extcon-provider.h>
27 #include <linux/regmap.h>
28 #include <linux/mfd/axp20x.h>
29
30 /* Power source status register */
31 #define PS_STAT_VBUS_TRIGGER            BIT(0)
32 #define PS_STAT_BAT_CHRG_DIR            BIT(2)
33 #define PS_STAT_VBUS_ABOVE_VHOLD        BIT(3)
34 #define PS_STAT_VBUS_VALID              BIT(4)
35 #define PS_STAT_VBUS_PRESENT            BIT(5)
36
37 /* BC module global register */
38 #define BC_GLOBAL_RUN                   BIT(0)
39 #define BC_GLOBAL_DET_STAT              BIT(2)
40 #define BC_GLOBAL_DBP_TOUT              BIT(3)
41 #define BC_GLOBAL_VLGC_COM_SEL          BIT(4)
42 #define BC_GLOBAL_DCD_TOUT_MASK         (BIT(6)|BIT(5))
43 #define BC_GLOBAL_DCD_TOUT_300MS        0
44 #define BC_GLOBAL_DCD_TOUT_100MS        1
45 #define BC_GLOBAL_DCD_TOUT_500MS        2
46 #define BC_GLOBAL_DCD_TOUT_900MS        3
47 #define BC_GLOBAL_DCD_DET_SEL           BIT(7)
48
49 /* BC module vbus control and status register */
50 #define VBUS_CNTL_DPDM_PD_EN            BIT(4)
51 #define VBUS_CNTL_DPDM_FD_EN            BIT(5)
52 #define VBUS_CNTL_FIRST_PO_STAT         BIT(6)
53
54 /* BC USB status register */
55 #define USB_STAT_BUS_STAT_MASK          (BIT(3)|BIT(2)|BIT(1)|BIT(0))
56 #define USB_STAT_BUS_STAT_SHIFT         0
57 #define USB_STAT_BUS_STAT_ATHD          0
58 #define USB_STAT_BUS_STAT_CONN          1
59 #define USB_STAT_BUS_STAT_SUSP          2
60 #define USB_STAT_BUS_STAT_CONF          3
61 #define USB_STAT_USB_SS_MODE            BIT(4)
62 #define USB_STAT_DEAD_BAT_DET           BIT(6)
63 #define USB_STAT_DBP_UNCFG              BIT(7)
64
65 /* BC detect status register */
66 #define DET_STAT_MASK                   (BIT(7)|BIT(6)|BIT(5))
67 #define DET_STAT_SHIFT                  5
68 #define DET_STAT_SDP                    1
69 #define DET_STAT_CDP                    2
70 #define DET_STAT_DCP                    3
71
72 enum axp288_extcon_reg {
73         AXP288_PS_STAT_REG              = 0x00,
74         AXP288_PS_BOOT_REASON_REG       = 0x02,
75         AXP288_BC_GLOBAL_REG            = 0x2c,
76         AXP288_BC_VBUS_CNTL_REG         = 0x2d,
77         AXP288_BC_USB_STAT_REG          = 0x2e,
78         AXP288_BC_DET_STAT_REG          = 0x2f,
79 };
80
81 enum axp288_extcon_irq {
82         VBUS_FALLING_IRQ = 0,
83         VBUS_RISING_IRQ,
84         MV_CHNG_IRQ,
85         BC_USB_CHNG_IRQ,
86         EXTCON_IRQ_END,
87 };
88
89 static const unsigned int axp288_extcon_cables[] = {
90         EXTCON_CHG_USB_SDP,
91         EXTCON_CHG_USB_CDP,
92         EXTCON_CHG_USB_DCP,
93         EXTCON_USB,
94         EXTCON_NONE,
95 };
96
97 struct axp288_extcon_info {
98         struct device *dev;
99         struct regmap *regmap;
100         struct regmap_irq_chip_data *regmap_irqc;
101         struct delayed_work det_work;
102         int irq[EXTCON_IRQ_END];
103         struct extcon_dev *edev;
104         unsigned int previous_cable;
105         bool first_detect_done;
106 };
107
108 /* Power up/down reason string array */
109 static char *axp288_pwr_up_down_info[] = {
110         "Last wake caused by user pressing the power button",
111         "Last wake caused by a charger insertion",
112         "Last wake caused by a battery insertion",
113         "Last wake caused by SOC initiated global reset",
114         "Last wake caused by cold reset",
115         "Last shutdown caused by PMIC UVLO threshold",
116         "Last shutdown caused by SOC initiated cold off",
117         "Last shutdown caused by user pressing the power button",
118         NULL,
119 };
120
121 /*
122  * Decode and log the given "reset source indicator" (rsi)
123  * register and then clear it.
124  */
125 static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
126 {
127         char **rsi;
128         unsigned int val, i, clear_mask = 0;
129         int ret;
130
131         ret = regmap_read(info->regmap, AXP288_PS_BOOT_REASON_REG, &val);
132         for (i = 0, rsi = axp288_pwr_up_down_info; *rsi; rsi++, i++) {
133                 if (val & BIT(i)) {
134                         dev_dbg(info->dev, "%s\n", *rsi);
135                         clear_mask |= BIT(i);
136                 }
137         }
138
139         /* Clear the register value for next reboot (write 1 to clear bit) */
140         regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
141 }
142
143 static void axp288_chrg_detect_complete(struct axp288_extcon_info *info)
144 {
145         /*
146          * We depend on other drivers to do things like mux the data lines,
147          * enable/disable vbus based on the id-pin, etc. Sometimes the BIOS has
148          * not set these things up correctly resulting in the initial charger
149          * cable type detection giving a wrong result and we end up not charging
150          * or charging at only 0.5A.
151          *
152          * So we schedule a second cable type detection after 2 seconds to
153          * give the other drivers time to load and do their thing.
154          */
155         if (!info->first_detect_done) {
156                 queue_delayed_work(system_wq, &info->det_work,
157                                    msecs_to_jiffies(2000));
158                 info->first_detect_done = true;
159         }
160 }
161
162 static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
163 {
164         int ret, stat, cfg, pwr_stat;
165         u8 chrg_type;
166         unsigned int cable = info->previous_cable;
167         bool vbus_attach = false;
168
169         ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
170         if (ret < 0) {
171                 dev_err(info->dev, "failed to read vbus status\n");
172                 return ret;
173         }
174
175         vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID);
176         if (!vbus_attach)
177                 goto no_vbus;
178
179         /* Check charger detection completion status */
180         ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
181         if (ret < 0)
182                 goto dev_det_ret;
183         if (cfg & BC_GLOBAL_DET_STAT) {
184                 dev_dbg(info->dev, "can't complete the charger detection\n");
185                 goto dev_det_ret;
186         }
187
188         ret = regmap_read(info->regmap, AXP288_BC_DET_STAT_REG, &stat);
189         if (ret < 0)
190                 goto dev_det_ret;
191
192         chrg_type = (stat & DET_STAT_MASK) >> DET_STAT_SHIFT;
193
194         switch (chrg_type) {
195         case DET_STAT_SDP:
196                 dev_dbg(info->dev, "sdp cable is connected\n");
197                 cable = EXTCON_CHG_USB_SDP;
198                 break;
199         case DET_STAT_CDP:
200                 dev_dbg(info->dev, "cdp cable is connected\n");
201                 cable = EXTCON_CHG_USB_CDP;
202                 break;
203         case DET_STAT_DCP:
204                 dev_dbg(info->dev, "dcp cable is connected\n");
205                 cable = EXTCON_CHG_USB_DCP;
206                 break;
207         default:
208                 dev_warn(info->dev,
209                         "disconnect or unknown or ID event\n");
210         }
211
212 no_vbus:
213         extcon_set_state_sync(info->edev, info->previous_cable, false);
214         if (info->previous_cable == EXTCON_CHG_USB_SDP)
215                 extcon_set_state_sync(info->edev, EXTCON_USB, false);
216
217         if (vbus_attach) {
218                 extcon_set_state_sync(info->edev, cable, vbus_attach);
219                 if (cable == EXTCON_CHG_USB_SDP)
220                         extcon_set_state_sync(info->edev, EXTCON_USB,
221                                                 vbus_attach);
222
223                 info->previous_cable = cable;
224         }
225
226         axp288_chrg_detect_complete(info);
227
228         return 0;
229
230 dev_det_ret:
231         if (ret < 0)
232                 dev_err(info->dev, "failed to detect BC Mod\n");
233
234         return ret;
235 }
236
237 static irqreturn_t axp288_extcon_isr(int irq, void *data)
238 {
239         struct axp288_extcon_info *info = data;
240         int ret;
241
242         ret = axp288_handle_chrg_det_event(info);
243         if (ret < 0)
244                 dev_err(info->dev, "failed to handle the interrupt\n");
245
246         return IRQ_HANDLED;
247 }
248
249 static void axp288_extcon_det_work(struct work_struct *work)
250 {
251         struct axp288_extcon_info *info =
252                 container_of(work, struct axp288_extcon_info, det_work.work);
253
254         regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
255                                                 BC_GLOBAL_RUN, 0);
256         /* Enable the charger detection logic */
257         regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
258                                         BC_GLOBAL_RUN, BC_GLOBAL_RUN);
259 }
260
261 static int axp288_extcon_probe(struct platform_device *pdev)
262 {
263         struct axp288_extcon_info *info;
264         struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
265         int ret, i, pirq;
266
267         info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
268         if (!info)
269                 return -ENOMEM;
270
271         info->dev = &pdev->dev;
272         info->regmap = axp20x->regmap;
273         info->regmap_irqc = axp20x->regmap_irqc;
274         info->previous_cable = EXTCON_NONE;
275         INIT_DELAYED_WORK(&info->det_work, axp288_extcon_det_work);
276
277         platform_set_drvdata(pdev, info);
278
279         axp288_extcon_log_rsi(info);
280
281         /* Initialize extcon device */
282         info->edev = devm_extcon_dev_allocate(&pdev->dev,
283                                               axp288_extcon_cables);
284         if (IS_ERR(info->edev)) {
285                 dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
286                 return PTR_ERR(info->edev);
287         }
288
289         /* Register extcon device */
290         ret = devm_extcon_dev_register(&pdev->dev, info->edev);
291         if (ret) {
292                 dev_err(&pdev->dev, "failed to register extcon device\n");
293                 return ret;
294         }
295
296         for (i = 0; i < EXTCON_IRQ_END; i++) {
297                 pirq = platform_get_irq(pdev, i);
298                 if (pirq < 0)
299                         return pirq;
300
301                 info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
302                 if (info->irq[i] < 0) {
303                         dev_err(&pdev->dev,
304                                 "failed to get virtual interrupt=%d\n", pirq);
305                         ret = info->irq[i];
306                         return ret;
307                 }
308
309                 ret = devm_request_threaded_irq(&pdev->dev, info->irq[i],
310                                 NULL, axp288_extcon_isr,
311                                 IRQF_ONESHOT | IRQF_NO_SUSPEND,
312                                 pdev->name, info);
313                 if (ret) {
314                         dev_err(&pdev->dev, "failed to request interrupt=%d\n",
315                                                         info->irq[i]);
316                         return ret;
317                 }
318         }
319
320         /* Start charger cable type detection */
321         queue_delayed_work(system_wq, &info->det_work, 0);
322
323         return 0;
324 }
325
326 static const struct platform_device_id axp288_extcon_table[] = {
327         { .name = "axp288_extcon" },
328         {},
329 };
330 MODULE_DEVICE_TABLE(platform, axp288_extcon_table);
331
332 static struct platform_driver axp288_extcon_driver = {
333         .probe = axp288_extcon_probe,
334         .id_table = axp288_extcon_table,
335         .driver = {
336                 .name = "axp288_extcon",
337         },
338 };
339 module_platform_driver(axp288_extcon_driver);
340
341 MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
342 MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
343 MODULE_LICENSE("GPL v2");