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