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");
369