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