]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blame - drivers/extcon/extcon-axp288.c
extcon: axp288: Remove usb_phy notification code
[mirror_ubuntu-artful-kernel.git] / drivers / extcon / extcon-axp288.c
CommitLineData
f0312378
RP
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>
f0312378
RP
24#include <linux/notifier.h>
25#include <linux/extcon.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/* IRQ enable-1 register */
74#define PWRSRC_IRQ_CFG_MASK (BIT(4)|BIT(3)|BIT(2))
75
76/* IRQ enable-6 register */
77#define BC12_IRQ_CFG_MASK BIT(1)
78
f0312378
RP
79enum axp288_extcon_reg {
80 AXP288_PS_STAT_REG = 0x00,
81 AXP288_PS_BOOT_REASON_REG = 0x02,
82 AXP288_BC_GLOBAL_REG = 0x2c,
83 AXP288_BC_VBUS_CNTL_REG = 0x2d,
84 AXP288_BC_USB_STAT_REG = 0x2e,
85 AXP288_BC_DET_STAT_REG = 0x2f,
86 AXP288_PWRSRC_IRQ_CFG_REG = 0x40,
87 AXP288_BC12_IRQ_CFG_REG = 0x45,
88};
89
90enum axp288_mux_select {
91 EXTCON_GPIO_MUX_SEL_PMIC = 0,
92 EXTCON_GPIO_MUX_SEL_SOC,
93};
94
95enum axp288_extcon_irq {
96 VBUS_FALLING_IRQ = 0,
97 VBUS_RISING_IRQ,
98 MV_CHNG_IRQ,
99 BC_USB_CHNG_IRQ,
100 EXTCON_IRQ_END,
101};
102
73b6ecdb 103static const unsigned int axp288_extcon_cables[] = {
11eecf91
CC
104 EXTCON_CHG_USB_SDP,
105 EXTCON_CHG_USB_CDP,
106 EXTCON_CHG_USB_DCP,
2a9de9c0 107 EXTCON_NONE,
f0312378
RP
108};
109
110struct axp288_extcon_info {
111 struct device *dev;
112 struct regmap *regmap;
113 struct regmap_irq_chip_data *regmap_irqc;
76884241 114 struct gpio_desc *gpio_mux_cntl;
f0312378
RP
115 int irq[EXTCON_IRQ_END];
116 struct extcon_dev *edev;
117 struct notifier_block extcon_nb;
f0312378
RP
118};
119
120/* Power up/down reason string array */
121static char *axp288_pwr_up_down_info[] = {
122 "Last wake caused by user pressing the power button",
123 "Last wake caused by a charger insertion",
124 "Last wake caused by a battery insertion",
125 "Last wake caused by SOC initiated global reset",
126 "Last wake caused by cold reset",
127 "Last shutdown caused by PMIC UVLO threshold",
128 "Last shutdown caused by SOC initiated cold off",
129 "Last shutdown caused by user pressing the power button",
130 NULL,
131};
132
133/*
134 * Decode and log the given "reset source indicator" (rsi)
135 * register and then clear it.
136 */
137static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
138{
139 char **rsi;
140 unsigned int val, i, clear_mask = 0;
141 int ret;
142
143 ret = regmap_read(info->regmap, AXP288_PS_BOOT_REASON_REG, &val);
144 for (i = 0, rsi = axp288_pwr_up_down_info; *rsi; rsi++, i++) {
145 if (val & BIT(i)) {
146 dev_dbg(info->dev, "%s\n", *rsi);
147 clear_mask |= BIT(i);
148 }
149 }
150
151 /* Clear the register value for next reboot (write 1 to clear bit) */
152 regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
153}
154
155static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
156{
157 static bool notify_otg, notify_charger;
73b6ecdb 158 static unsigned int cable;
f0312378
RP
159 int ret, stat, cfg, pwr_stat;
160 u8 chrg_type;
161 bool vbus_attach = false;
162
163 ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
164 if (ret < 0) {
165 dev_err(info->dev, "failed to read vbus status\n");
166 return ret;
167 }
168
169 vbus_attach = (pwr_stat & PS_STAT_VBUS_PRESENT);
170 if (!vbus_attach)
171 goto notify_otg;
172
173 /* Check charger detection completion status */
174 ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
175 if (ret < 0)
176 goto dev_det_ret;
177 if (cfg & BC_GLOBAL_DET_STAT) {
178 dev_dbg(info->dev, "can't complete the charger detection\n");
179 goto dev_det_ret;
180 }
181
182 ret = regmap_read(info->regmap, AXP288_BC_DET_STAT_REG, &stat);
183 if (ret < 0)
184 goto dev_det_ret;
185
186 chrg_type = (stat & DET_STAT_MASK) >> DET_STAT_SHIFT;
187
188 switch (chrg_type) {
189 case DET_STAT_SDP:
525867db 190 dev_dbg(info->dev, "sdp cable is connected\n");
f0312378
RP
191 notify_otg = true;
192 notify_charger = true;
11eecf91 193 cable = EXTCON_CHG_USB_SDP;
f0312378
RP
194 break;
195 case DET_STAT_CDP:
525867db 196 dev_dbg(info->dev, "cdp cable is connected\n");
f0312378
RP
197 notify_otg = true;
198 notify_charger = true;
11eecf91 199 cable = EXTCON_CHG_USB_CDP;
f0312378
RP
200 break;
201 case DET_STAT_DCP:
525867db 202 dev_dbg(info->dev, "dcp cable is connected\n");
f0312378 203 notify_charger = true;
11eecf91 204 cable = EXTCON_CHG_USB_DCP;
f0312378
RP
205 break;
206 default:
207 dev_warn(info->dev,
208 "disconnect or unknown or ID event\n");
209 }
210
211notify_otg:
212 if (notify_otg) {
213 /*
214 * If VBUS is absent Connect D+/D- lines to PMIC for BC
215 * detection. Else connect them to SOC for USB communication.
216 */
76884241
HG
217 if (info->gpio_mux_cntl)
218 gpiod_set_value(info->gpio_mux_cntl,
f0312378
RP
219 vbus_attach ? EXTCON_GPIO_MUX_SEL_SOC
220 : EXTCON_GPIO_MUX_SEL_PMIC);
f0312378
RP
221 }
222
223 if (notify_charger)
8670b459 224 extcon_set_state_sync(info->edev, cable, vbus_attach);
f0312378
RP
225
226 /* Clear the flags on disconnect event */
227 if (!vbus_attach)
228 notify_otg = notify_charger = false;
229
230 return 0;
231
232dev_det_ret:
233 if (ret < 0)
234 dev_err(info->dev, "failed to detect BC Mod\n");
235
236 return ret;
237}
238
239static irqreturn_t axp288_extcon_isr(int irq, void *data)
240{
241 struct axp288_extcon_info *info = data;
242 int ret;
243
244 ret = axp288_handle_chrg_det_event(info);
245 if (ret < 0)
246 dev_err(info->dev, "failed to handle the interrupt\n");
247
248 return IRQ_HANDLED;
249}
250
251static void axp288_extcon_enable_irq(struct axp288_extcon_info *info)
252{
253 /* Unmask VBUS interrupt */
254 regmap_write(info->regmap, AXP288_PWRSRC_IRQ_CFG_REG,
255 PWRSRC_IRQ_CFG_MASK);
256 regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
257 BC_GLOBAL_RUN, 0);
258 /* Unmask the BC1.2 complete interrupts */
259 regmap_write(info->regmap, AXP288_BC12_IRQ_CFG_REG, BC12_IRQ_CFG_MASK);
260 /* Enable the charger detection logic */
261 regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
262 BC_GLOBAL_RUN, BC_GLOBAL_RUN);
263}
264
265static int axp288_extcon_probe(struct platform_device *pdev)
266{
267 struct axp288_extcon_info *info;
268 struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
76884241 269 struct axp288_extcon_pdata *pdata = pdev->dev.platform_data;
f0312378
RP
270 int ret, i, pirq, gpio;
271
272 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
273 if (!info)
274 return -ENOMEM;
275
276 info->dev = &pdev->dev;
277 info->regmap = axp20x->regmap;
278 info->regmap_irqc = axp20x->regmap_irqc;
76884241
HG
279 if (pdata)
280 info->gpio_mux_cntl = pdata->gpio_mux_cntl;
281
f0312378
RP
282 platform_set_drvdata(pdev, info);
283
284 axp288_extcon_log_rsi(info);
285
286 /* Initialize extcon device */
287 info->edev = devm_extcon_dev_allocate(&pdev->dev,
288 axp288_extcon_cables);
289 if (IS_ERR(info->edev)) {
290 dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
291 return PTR_ERR(info->edev);
292 }
293
294 /* Register extcon device */
295 ret = devm_extcon_dev_register(&pdev->dev, info->edev);
296 if (ret) {
297 dev_err(&pdev->dev, "failed to register extcon device\n");
298 return ret;
299 }
300
f0312378 301 /* Set up gpio control for USB Mux */
76884241
HG
302 if (info->gpio_mux_cntl) {
303 gpio = desc_to_gpio(info->gpio_mux_cntl);
4bf27b70 304 ret = devm_gpio_request(&pdev->dev, gpio, "USB_MUX");
f0312378
RP
305 if (ret < 0) {
306 dev_err(&pdev->dev,
307 "failed to request the gpio=%d\n", gpio);
4bf27b70 308 return ret;
f0312378 309 }
76884241 310 gpiod_direction_output(info->gpio_mux_cntl,
f0312378
RP
311 EXTCON_GPIO_MUX_SEL_PMIC);
312 }
313
314 for (i = 0; i < EXTCON_IRQ_END; i++) {
315 pirq = platform_get_irq(pdev, i);
316 info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
317 if (info->irq[i] < 0) {
318 dev_err(&pdev->dev,
319 "failed to get virtual interrupt=%d\n", pirq);
320 ret = info->irq[i];
4bf27b70 321 return ret;
f0312378
RP
322 }
323
324 ret = devm_request_threaded_irq(&pdev->dev, info->irq[i],
325 NULL, axp288_extcon_isr,
326 IRQF_ONESHOT | IRQF_NO_SUSPEND,
327 pdev->name, info);
328 if (ret) {
329 dev_err(&pdev->dev, "failed to request interrupt=%d\n",
330 info->irq[i]);
4bf27b70 331 return ret;
f0312378
RP
332 }
333 }
334
335 /* Enable interrupts */
336 axp288_extcon_enable_irq(info);
337
338 return 0;
f0312378
RP
339}
340
341static struct platform_driver axp288_extcon_driver = {
342 .probe = axp288_extcon_probe,
f0312378
RP
343 .driver = {
344 .name = "axp288_extcon",
345 },
346};
347module_platform_driver(axp288_extcon_driver);
348
349MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
350MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
351MODULE_LICENSE("GPL v2");