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