extcon-palmas.c 13.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*
 * Palmas USB transceiver driver
 *
 * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Author: Graeme Gregory <gg@slimlogic.co.uk>
 * Author: Kishon Vijay Abraham I <kishon@ti.com>
 *
 * Based on twl6030_usb.c
 *
 * Author: Hema HK <hemahk@ti.com>
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
26
#include <linux/slab.h>
27 28 29 30
#include <linux/err.h>
#include <linux/mfd/palmas.h>
#include <linux/of.h>
#include <linux/of_platform.h>
31
#include <linux/of_gpio.h>
32
#include <linux/gpio/consumer.h>
33 34 35
#include <linux/workqueue.h>

#define USB_GPIO_DEBOUNCE_MS	20	/* ms */
36

37
static const unsigned int palmas_extcon_cable[] = {
38 39 40
	EXTCON_USB,
	EXTCON_USB_HOST,
	EXTCON_NONE,
41 42 43 44 45 46 47 48 49 50 51 52 53 54
};

static void palmas_usb_wakeup(struct palmas *palmas, int enable)
{
	if (enable)
		palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP,
			PALMAS_USB_WAKEUP_ID_WK_UP_COMP);
	else
		palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP, 0);
}

static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)
{
	struct palmas_usb *palmas_usb = _palmas_usb;
55
	struct extcon_dev *edev = palmas_usb->edev;
56 57 58 59 60 61 62 63
	unsigned int vbus_line_state;

	palmas_read(palmas_usb->palmas, PALMAS_INTERRUPT_BASE,
		PALMAS_INT3_LINE_STATE, &vbus_line_state);

	if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) {
		if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) {
			palmas_usb->linkstat = PALMAS_USB_STATE_VBUS;
64
			extcon_set_cable_state_(edev, EXTCON_USB, true);
65
			dev_info(palmas_usb->dev, "USB cable is attached\n");
66 67 68 69 70 71 72
		} else {
			dev_dbg(palmas_usb->dev,
				"Spurious connect event detected\n");
		}
	} else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) {
		if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) {
			palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
73
			extcon_set_cable_state_(edev, EXTCON_USB, false);
74
			dev_info(palmas_usb->dev, "USB cable is detached\n");
75 76 77 78 79 80 81 82 83 84 85
		} else {
			dev_dbg(palmas_usb->dev,
				"Spurious disconnect event detected\n");
		}
	}

	return IRQ_HANDLED;
}

static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)
{
86
	unsigned int set, id_src;
87
	struct palmas_usb *palmas_usb = _palmas_usb;
88
	struct extcon_dev *edev = palmas_usb->edev;
89 90 91

	palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
		PALMAS_USB_ID_INT_LATCH_SET, &set);
92 93
	palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
		PALMAS_USB_ID_INT_SRC, &id_src);
94

95 96
	if ((set & PALMAS_USB_ID_INT_SRC_ID_GND) &&
				(id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {
97 98 99 100
		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
			PALMAS_USB_ID_INT_LATCH_CLR,
			PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND);
		palmas_usb->linkstat = PALMAS_USB_STATE_ID;
101
		extcon_set_cable_state_(edev, EXTCON_USB_HOST, true);
102
		dev_info(palmas_usb->dev, "USB-HOST cable is attached\n");
103 104
	} else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) &&
				(id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) {
105 106 107 108
		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
			PALMAS_USB_ID_INT_LATCH_CLR,
			PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT);
		palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
109
		extcon_set_cable_state_(edev, EXTCON_USB_HOST, false);
110 111 112 113
		dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
	} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) &&
				(!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) {
		palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
114
		extcon_set_cable_state_(edev, EXTCON_USB_HOST, false);
115
		dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
116 117 118
	} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) &&
				(id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {
		palmas_usb->linkstat = PALMAS_USB_STATE_ID;
119
		extcon_set_cable_state_(edev, EXTCON_USB_HOST, true);
120
		dev_info(palmas_usb->dev, " USB-HOST cable is attached\n");
121 122 123 124 125
	}

	return IRQ_HANDLED;
}

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
static void palmas_gpio_id_detect(struct work_struct *work)
{
	int id;
	struct palmas_usb *palmas_usb = container_of(to_delayed_work(work),
						     struct palmas_usb,
						     wq_detectid);
	struct extcon_dev *edev = palmas_usb->edev;

	if (!palmas_usb->id_gpiod)
		return;

	id = gpiod_get_value_cansleep(palmas_usb->id_gpiod);

	if (id) {
		extcon_set_cable_state_(edev, EXTCON_USB_HOST, false);
		dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
	} else {
		extcon_set_cable_state_(edev, EXTCON_USB_HOST, true);
		dev_info(palmas_usb->dev, "USB-HOST cable is attached\n");
	}
}

static irqreturn_t palmas_gpio_id_irq_handler(int irq, void *_palmas_usb)
{
	struct palmas_usb *palmas_usb = _palmas_usb;

	queue_delayed_work(system_power_efficient_wq, &palmas_usb->wq_detectid,
			   palmas_usb->sw_debounce_jiffies);

	return IRQ_HANDLED;
}

158 159 160 161 162 163
static void palmas_enable_irq(struct palmas_usb *palmas_usb)
{
	palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
		PALMAS_USB_VBUS_CTRL_SET,
		PALMAS_USB_VBUS_CTRL_SET_VBUS_ACT_COMP);

164 165 166 167
	if (palmas_usb->enable_id_detection) {
		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
			     PALMAS_USB_ID_CTRL_SET,
			     PALMAS_USB_ID_CTRL_SET_ID_ACT_COMP);
168

169 170 171 172 173
		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
			     PALMAS_USB_ID_INT_EN_HI_SET,
			     PALMAS_USB_ID_INT_EN_HI_SET_ID_GND |
			     PALMAS_USB_ID_INT_EN_HI_SET_ID_FLOAT);
	}
174

175 176
	if (palmas_usb->enable_vbus_detection)
		palmas_vbus_irq_handler(palmas_usb->vbus_irq, palmas_usb);
177 178

	/* cold plug for host mode needs this delay */
179 180 181 182
	if (palmas_usb->enable_id_detection) {
		msleep(30);
		palmas_id_irq_handler(palmas_usb->id_irq, palmas_usb);
	}
183 184 185 186 187
}

static int palmas_usb_probe(struct platform_device *pdev)
{
	struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
Jingoo Han's avatar
Jingoo Han committed
188
	struct palmas_usb_platform_data	*pdata = dev_get_platdata(&pdev->dev);
189 190 191 192 193 194 195 196
	struct device_node *node = pdev->dev.of_node;
	struct palmas_usb *palmas_usb;
	int status;

	palmas_usb = devm_kzalloc(&pdev->dev, sizeof(*palmas_usb), GFP_KERNEL);
	if (!palmas_usb)
		return -ENOMEM;

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
	if (node && !pdata) {
		palmas_usb->wakeup = of_property_read_bool(node, "ti,wakeup");
		palmas_usb->enable_id_detection = of_property_read_bool(node,
						"ti,enable-id-detection");
		palmas_usb->enable_vbus_detection = of_property_read_bool(node,
						"ti,enable-vbus-detection");
	} else {
		palmas_usb->wakeup = true;
		palmas_usb->enable_id_detection = true;
		palmas_usb->enable_vbus_detection = true;

		if (pdata)
			palmas_usb->wakeup = pdata->wakeup;
	}

212 213
	palmas_usb->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id",
							GPIOD_IN);
214 215 216 217 218
	if (IS_ERR(palmas_usb->id_gpiod)) {
		dev_err(&pdev->dev, "failed to get id gpio\n");
		return PTR_ERR(palmas_usb->id_gpiod);
	}

219 220 221 222 223 224 225
	palmas_usb->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus",
							GPIOD_IN);
	if (IS_ERR(palmas_usb->vbus_gpiod)) {
		dev_err(&pdev->dev, "failed to get vbus gpio\n");
		return PTR_ERR(palmas_usb->vbus_gpiod);
	}

226 227 228 229 230
	if (palmas_usb->enable_id_detection && palmas_usb->id_gpiod) {
		palmas_usb->enable_id_detection = false;
		palmas_usb->enable_gpio_id_detection = true;
	}

231 232 233 234 235
	if (palmas_usb->enable_vbus_detection && palmas_usb->vbus_gpiod) {
		palmas_usb->enable_vbus_detection = false;
		palmas_usb->enable_gpio_vbus_detection = true;
	}

236 237 238 239 240 241 242 243 244 245 246 247 248 249
	if (palmas_usb->enable_gpio_id_detection) {
		u32 debounce;

		if (of_property_read_u32(node, "debounce-delay-ms", &debounce))
			debounce = USB_GPIO_DEBOUNCE_MS;

		status = gpiod_set_debounce(palmas_usb->id_gpiod,
					    debounce * 1000);
		if (status < 0)
			palmas_usb->sw_debounce_jiffies = msecs_to_jiffies(debounce);
	}

	INIT_DELAYED_WORK(&palmas_usb->wq_detectid, palmas_gpio_id_detect);

250 251 252 253 254
	palmas->usb = palmas_usb;
	palmas_usb->palmas = palmas;

	palmas_usb->dev	 = &pdev->dev;

255
	palmas_usb_wakeup(palmas, palmas_usb->wakeup);
256 257 258

	platform_set_drvdata(pdev, palmas_usb);

259 260 261 262 263 264
	palmas_usb->edev = devm_extcon_dev_allocate(&pdev->dev,
						    palmas_extcon_cable);
	if (IS_ERR(palmas_usb->edev)) {
		dev_err(&pdev->dev, "failed to allocate extcon device\n");
		return -ENOMEM;
	}
265

266
	status = devm_extcon_dev_register(&pdev->dev, palmas_usb->edev);
267 268 269 270 271
	if (status) {
		dev_err(&pdev->dev, "failed to register extcon device\n");
		return status;
	}

272
	if (palmas_usb->enable_id_detection) {
273 274 275 276
		palmas_usb->id_otg_irq = regmap_irq_get_virq(palmas->irq_data,
							     PALMAS_ID_OTG_IRQ);
		palmas_usb->id_irq = regmap_irq_get_virq(palmas->irq_data,
							 PALMAS_ID_IRQ);
277 278 279 280
		status = devm_request_threaded_irq(palmas_usb->dev,
				palmas_usb->id_irq,
				NULL, palmas_id_irq_handler,
				IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
281
				IRQF_ONESHOT,
282 283 284
				"palmas_usb_id", palmas_usb);
		if (status < 0) {
			dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
285
					palmas_usb->id_irq, status);
286
			return status;
287
		}
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
	} else if (palmas_usb->enable_gpio_id_detection) {
		palmas_usb->gpio_id_irq = gpiod_to_irq(palmas_usb->id_gpiod);
		if (palmas_usb->gpio_id_irq < 0) {
			dev_err(&pdev->dev, "failed to get id irq\n");
			return palmas_usb->gpio_id_irq;
		}
		status = devm_request_threaded_irq(&pdev->dev,
						   palmas_usb->gpio_id_irq,
						   NULL,
						   palmas_gpio_id_irq_handler,
						   IRQF_TRIGGER_RISING |
						   IRQF_TRIGGER_FALLING |
						   IRQF_ONESHOT,
						   "palmas_usb_id",
						   palmas_usb);
		if (status < 0) {
			dev_err(&pdev->dev,
				"failed to request handler for id irq\n");
			return status;
		}
308 309
	}

310
	if (palmas_usb->enable_vbus_detection) {
311 312 313 314
		palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data,
						       PALMAS_VBUS_OTG_IRQ);
		palmas_usb->vbus_irq = regmap_irq_get_virq(palmas->irq_data,
							   PALMAS_VBUS_IRQ);
315 316 317 318
		status = devm_request_threaded_irq(palmas_usb->dev,
				palmas_usb->vbus_irq, NULL,
				palmas_vbus_irq_handler,
				IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
319
				IRQF_ONESHOT,
320 321 322
				"palmas_usb_vbus", palmas_usb);
		if (status < 0) {
			dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
323
					palmas_usb->vbus_irq, status);
324
			return status;
325
		}
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
	} else if (palmas_usb->enable_gpio_vbus_detection) {
		/* remux GPIO_1 as VBUSDET */
		status = palmas_update_bits(palmas,
			PALMAS_PU_PD_OD_BASE,
			PALMAS_PRIMARY_SECONDARY_PAD1,
			PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_MASK,
			(1 << PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_SHIFT));
		if (status < 0) {
			dev_err(&pdev->dev, "can't remux GPIO1\n");
			return status;
		}

		palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data,
						       PALMAS_VBUS_OTG_IRQ);
		palmas_usb->gpio_vbus_irq = gpiod_to_irq(palmas_usb->vbus_gpiod);
		if (palmas_usb->gpio_vbus_irq < 0) {
			dev_err(&pdev->dev, "failed to get vbus irq\n");
			return palmas_usb->gpio_vbus_irq;
		}
		status = devm_request_threaded_irq(&pdev->dev,
						palmas_usb->gpio_vbus_irq,
						NULL,
						palmas_vbus_irq_handler,
						IRQF_TRIGGER_FALLING |
						IRQF_TRIGGER_RISING |
351
						IRQF_ONESHOT,
352 353 354 355 356 357 358
						"palmas_usb_vbus",
						palmas_usb);
		if (status < 0) {
			dev_err(&pdev->dev,
				"failed to request handler for vbus irq\n");
			return status;
		}
359 360 361
	}

	palmas_enable_irq(palmas_usb);
362
	/* perform initial detection */
363 364
	if (palmas_usb->enable_gpio_vbus_detection)
		palmas_vbus_irq_handler(palmas_usb->gpio_vbus_irq, palmas_usb);
365
	palmas_gpio_id_detect(&palmas_usb->wq_detectid.work);
366
	device_set_wakeup_capable(&pdev->dev, true);
367 368 369
	return 0;
}

370 371 372 373 374 375 376 377 378
static int palmas_usb_remove(struct platform_device *pdev)
{
	struct palmas_usb *palmas_usb = platform_get_drvdata(pdev);

	cancel_delayed_work_sync(&palmas_usb->wq_detectid);

	return 0;
}

379 380 381 382 383 384
#ifdef CONFIG_PM_SLEEP
static int palmas_usb_suspend(struct device *dev)
{
	struct palmas_usb *palmas_usb = dev_get_drvdata(dev);

	if (device_may_wakeup(dev)) {
385 386
		if (palmas_usb->enable_vbus_detection)
			enable_irq_wake(palmas_usb->vbus_irq);
387 388
		if (palmas_usb->enable_gpio_vbus_detection)
			enable_irq_wake(palmas_usb->gpio_vbus_irq);
389 390
		if (palmas_usb->enable_id_detection)
			enable_irq_wake(palmas_usb->id_irq);
391 392
		if (palmas_usb->enable_gpio_id_detection)
			enable_irq_wake(palmas_usb->gpio_id_irq);
393 394 395 396 397 398 399 400 401
	}
	return 0;
}

static int palmas_usb_resume(struct device *dev)
{
	struct palmas_usb *palmas_usb = dev_get_drvdata(dev);

	if (device_may_wakeup(dev)) {
402 403
		if (palmas_usb->enable_vbus_detection)
			disable_irq_wake(palmas_usb->vbus_irq);
404 405
		if (palmas_usb->enable_gpio_vbus_detection)
			disable_irq_wake(palmas_usb->gpio_vbus_irq);
406 407
		if (palmas_usb->enable_id_detection)
			disable_irq_wake(palmas_usb->id_irq);
408 409
		if (palmas_usb->enable_gpio_id_detection)
			disable_irq_wake(palmas_usb->gpio_id_irq);
410 411 412 413 414
	}
	return 0;
};
#endif

415
static SIMPLE_DEV_PM_OPS(palmas_pm_ops, palmas_usb_suspend, palmas_usb_resume);
416

417
static const struct of_device_id of_palmas_match_tbl[] = {
418
	{ .compatible = "ti,palmas-usb", },
419
	{ .compatible = "ti,palmas-usb-vid", },
420
	{ .compatible = "ti,twl6035-usb", },
421
	{ .compatible = "ti,twl6035-usb-vid", },
422 423 424 425 426
	{ /* end */ }
};

static struct platform_driver palmas_usb_driver = {
	.probe = palmas_usb_probe,
427
	.remove = palmas_usb_remove,
428 429 430
	.driver = {
		.name = "palmas-usb",
		.of_match_table = of_palmas_match_tbl,
431
		.pm = &palmas_pm_ops,
432 433 434 435 436 437 438 439 440 441
	},
};

module_platform_driver(palmas_usb_driver);

MODULE_ALIAS("platform:palmas-usb");
MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>");
MODULE_DESCRIPTION("Palmas USB transceiver driver");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(of, of_palmas_match_tbl);