brintos

brintos / linux-shallow public Read only

0
0
Text · 13.1 KiB · d339b86 Raw
434 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Palmas USB transceiver driver4 *5 * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com6 * Author: Graeme Gregory <gg@slimlogic.co.uk>7 * Author: Kishon Vijay Abraham I <kishon@ti.com>8 * Based on twl6030_usb.c9 * Author: Hema HK <hemahk@ti.com>10 */11 12#include <linux/devm-helpers.h>13#include <linux/module.h>14#include <linux/interrupt.h>15#include <linux/platform_device.h>16#include <linux/slab.h>17#include <linux/err.h>18#include <linux/mfd/palmas.h>19#include <linux/of.h>20#include <linux/of_platform.h>21#include <linux/gpio/consumer.h>22#include <linux/workqueue.h>23 24#define USB_GPIO_DEBOUNCE_MS	20	/* ms */25 26static const unsigned int palmas_extcon_cable[] = {27	EXTCON_USB,28	EXTCON_USB_HOST,29	EXTCON_NONE,30};31 32static void palmas_usb_wakeup(struct palmas *palmas, int enable)33{34	if (enable)35		palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP,36			PALMAS_USB_WAKEUP_ID_WK_UP_COMP);37	else38		palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP, 0);39}40 41static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)42{43	struct palmas_usb *palmas_usb = _palmas_usb;44	struct extcon_dev *edev = palmas_usb->edev;45	unsigned int vbus_line_state;46 47	palmas_read(palmas_usb->palmas, PALMAS_INTERRUPT_BASE,48		PALMAS_INT3_LINE_STATE, &vbus_line_state);49 50	if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) {51		if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) {52			palmas_usb->linkstat = PALMAS_USB_STATE_VBUS;53			extcon_set_state_sync(edev, EXTCON_USB, true);54			dev_dbg(palmas_usb->dev, "USB cable is attached\n");55		} else {56			dev_dbg(palmas_usb->dev,57				"Spurious connect event detected\n");58		}59	} else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) {60		if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) {61			palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;62			extcon_set_state_sync(edev, EXTCON_USB, false);63			dev_dbg(palmas_usb->dev, "USB cable is detached\n");64		} else {65			dev_dbg(palmas_usb->dev,66				"Spurious disconnect event detected\n");67		}68	}69 70	return IRQ_HANDLED;71}72 73static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)74{75	unsigned int set, id_src;76	struct palmas_usb *palmas_usb = _palmas_usb;77	struct extcon_dev *edev = palmas_usb->edev;78 79	palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,80		PALMAS_USB_ID_INT_LATCH_SET, &set);81	palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,82		PALMAS_USB_ID_INT_SRC, &id_src);83 84	if ((set & PALMAS_USB_ID_INT_SRC_ID_GND) &&85				(id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {86		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,87			PALMAS_USB_ID_INT_LATCH_CLR,88			PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND);89		palmas_usb->linkstat = PALMAS_USB_STATE_ID;90		extcon_set_state_sync(edev, EXTCON_USB_HOST, true);91		dev_dbg(palmas_usb->dev, "USB-HOST cable is attached\n");92	} else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) &&93				(id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) {94		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,95			PALMAS_USB_ID_INT_LATCH_CLR,96			PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT);97		palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;98		extcon_set_state_sync(edev, EXTCON_USB_HOST, false);99		dev_dbg(palmas_usb->dev, "USB-HOST cable is detached\n");100	} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) &&101				(!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) {102		palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;103		extcon_set_state_sync(edev, EXTCON_USB_HOST, false);104		dev_dbg(palmas_usb->dev, "USB-HOST cable is detached\n");105	} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) &&106				(id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {107		palmas_usb->linkstat = PALMAS_USB_STATE_ID;108		extcon_set_state_sync(edev, EXTCON_USB_HOST, true);109		dev_dbg(palmas_usb->dev, "USB-HOST cable is attached\n");110	}111 112	return IRQ_HANDLED;113}114 115static void palmas_gpio_id_detect(struct work_struct *work)116{117	int id;118	struct palmas_usb *palmas_usb = container_of(to_delayed_work(work),119						     struct palmas_usb,120						     wq_detectid);121	struct extcon_dev *edev = palmas_usb->edev;122 123	if (!palmas_usb->id_gpiod)124		return;125 126	id = gpiod_get_value_cansleep(palmas_usb->id_gpiod);127 128	if (id) {129		extcon_set_state_sync(edev, EXTCON_USB_HOST, false);130		dev_dbg(palmas_usb->dev, "USB-HOST cable is detached\n");131	} else {132		extcon_set_state_sync(edev, EXTCON_USB_HOST, true);133		dev_dbg(palmas_usb->dev, "USB-HOST cable is attached\n");134	}135}136 137static irqreturn_t palmas_gpio_id_irq_handler(int irq, void *_palmas_usb)138{139	struct palmas_usb *palmas_usb = _palmas_usb;140 141	queue_delayed_work(system_power_efficient_wq, &palmas_usb->wq_detectid,142			   palmas_usb->sw_debounce_jiffies);143 144	return IRQ_HANDLED;145}146 147static void palmas_enable_irq(struct palmas_usb *palmas_usb)148{149	palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,150		PALMAS_USB_VBUS_CTRL_SET,151		PALMAS_USB_VBUS_CTRL_SET_VBUS_ACT_COMP);152 153	if (palmas_usb->enable_id_detection) {154		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,155			     PALMAS_USB_ID_CTRL_SET,156			     PALMAS_USB_ID_CTRL_SET_ID_ACT_COMP);157 158		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,159			     PALMAS_USB_ID_INT_EN_HI_SET,160			     PALMAS_USB_ID_INT_EN_HI_SET_ID_GND |161			     PALMAS_USB_ID_INT_EN_HI_SET_ID_FLOAT);162	}163 164	if (palmas_usb->enable_vbus_detection)165		palmas_vbus_irq_handler(palmas_usb->vbus_irq, palmas_usb);166 167	/* cold plug for host mode needs this delay */168	if (palmas_usb->enable_id_detection) {169		msleep(30);170		palmas_id_irq_handler(palmas_usb->id_irq, palmas_usb);171	}172}173 174static int palmas_usb_probe(struct platform_device *pdev)175{176	struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);177	struct palmas_usb_platform_data	*pdata = dev_get_platdata(&pdev->dev);178	struct device_node *node = pdev->dev.of_node;179	struct palmas_usb *palmas_usb;180	int status;181 182	if (!palmas) {183		dev_err(&pdev->dev, "failed to get valid parent\n");184		return -EINVAL;185	}186 187	palmas_usb = devm_kzalloc(&pdev->dev, sizeof(*palmas_usb), GFP_KERNEL);188	if (!palmas_usb)189		return -ENOMEM;190 191	if (node && !pdata) {192		palmas_usb->wakeup = of_property_read_bool(node, "ti,wakeup");193		palmas_usb->enable_id_detection = of_property_read_bool(node,194						"ti,enable-id-detection");195		palmas_usb->enable_vbus_detection = of_property_read_bool(node,196						"ti,enable-vbus-detection");197	} else {198		palmas_usb->wakeup = true;199		palmas_usb->enable_id_detection = true;200		palmas_usb->enable_vbus_detection = true;201 202		if (pdata)203			palmas_usb->wakeup = pdata->wakeup;204	}205 206	palmas_usb->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id",207							GPIOD_IN);208	if (IS_ERR(palmas_usb->id_gpiod))209		return dev_err_probe(&pdev->dev, PTR_ERR(palmas_usb->id_gpiod),210				     "failed to get id gpio\n");211 212	palmas_usb->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus",213							GPIOD_IN);214	if (IS_ERR(palmas_usb->vbus_gpiod))215		return dev_err_probe(&pdev->dev, PTR_ERR(palmas_usb->vbus_gpiod),216				     "failed to get id gpio\n");217 218	if (palmas_usb->enable_id_detection && palmas_usb->id_gpiod) {219		palmas_usb->enable_id_detection = false;220		palmas_usb->enable_gpio_id_detection = true;221	}222 223	if (palmas_usb->enable_vbus_detection && palmas_usb->vbus_gpiod) {224		palmas_usb->enable_vbus_detection = false;225		palmas_usb->enable_gpio_vbus_detection = true;226	}227 228	if (palmas_usb->enable_gpio_id_detection) {229		u32 debounce;230 231		if (of_property_read_u32(node, "debounce-delay-ms", &debounce))232			debounce = USB_GPIO_DEBOUNCE_MS;233 234		status = gpiod_set_debounce(palmas_usb->id_gpiod,235					    debounce * 1000);236		if (status < 0)237			palmas_usb->sw_debounce_jiffies = msecs_to_jiffies(debounce);238	}239 240	status = devm_delayed_work_autocancel(&pdev->dev,241					      &palmas_usb->wq_detectid,242					      palmas_gpio_id_detect);243	if (status)244		return status;245 246	palmas->usb = palmas_usb;247	palmas_usb->palmas = palmas;248 249	palmas_usb->dev	 = &pdev->dev;250 251	palmas_usb_wakeup(palmas, palmas_usb->wakeup);252 253	platform_set_drvdata(pdev, palmas_usb);254 255	palmas_usb->edev = devm_extcon_dev_allocate(&pdev->dev,256						    palmas_extcon_cable);257	if (IS_ERR(palmas_usb->edev)) {258		dev_err(&pdev->dev, "failed to allocate extcon device\n");259		return -ENOMEM;260	}261 262	status = devm_extcon_dev_register(&pdev->dev, palmas_usb->edev);263	if (status) {264		dev_err(&pdev->dev, "failed to register extcon device\n");265		return status;266	}267 268	if (palmas_usb->enable_id_detection) {269		palmas_usb->id_otg_irq = regmap_irq_get_virq(palmas->irq_data,270							     PALMAS_ID_OTG_IRQ);271		palmas_usb->id_irq = regmap_irq_get_virq(palmas->irq_data,272							 PALMAS_ID_IRQ);273		status = devm_request_threaded_irq(palmas_usb->dev,274				palmas_usb->id_irq,275				NULL, palmas_id_irq_handler,276				IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |277				IRQF_ONESHOT,278				"palmas_usb_id", palmas_usb);279		if (status < 0) {280			dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",281					palmas_usb->id_irq, status);282			return status;283		}284	} else if (palmas_usb->enable_gpio_id_detection) {285		palmas_usb->gpio_id_irq = gpiod_to_irq(palmas_usb->id_gpiod);286		if (palmas_usb->gpio_id_irq < 0) {287			dev_err(&pdev->dev, "failed to get id irq\n");288			return palmas_usb->gpio_id_irq;289		}290		status = devm_request_threaded_irq(&pdev->dev,291						   palmas_usb->gpio_id_irq,292						   NULL,293						   palmas_gpio_id_irq_handler,294						   IRQF_TRIGGER_RISING |295						   IRQF_TRIGGER_FALLING |296						   IRQF_ONESHOT,297						   "palmas_usb_id",298						   palmas_usb);299		if (status < 0) {300			dev_err(&pdev->dev,301				"failed to request handler for id irq\n");302			return status;303		}304	}305 306	if (palmas_usb->enable_vbus_detection) {307		palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data,308						       PALMAS_VBUS_OTG_IRQ);309		palmas_usb->vbus_irq = regmap_irq_get_virq(palmas->irq_data,310							   PALMAS_VBUS_IRQ);311		status = devm_request_threaded_irq(palmas_usb->dev,312				palmas_usb->vbus_irq, NULL,313				palmas_vbus_irq_handler,314				IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |315				IRQF_ONESHOT,316				"palmas_usb_vbus", palmas_usb);317		if (status < 0) {318			dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",319					palmas_usb->vbus_irq, status);320			return status;321		}322	} else if (palmas_usb->enable_gpio_vbus_detection) {323		/* remux GPIO_1 as VBUSDET */324		status = palmas_update_bits(palmas,325			PALMAS_PU_PD_OD_BASE,326			PALMAS_PRIMARY_SECONDARY_PAD1,327			PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_MASK,328			(1 << PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_SHIFT));329		if (status < 0) {330			dev_err(&pdev->dev, "can't remux GPIO1\n");331			return status;332		}333 334		palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data,335						       PALMAS_VBUS_OTG_IRQ);336		palmas_usb->gpio_vbus_irq = gpiod_to_irq(palmas_usb->vbus_gpiod);337		if (palmas_usb->gpio_vbus_irq < 0) {338			dev_err(&pdev->dev, "failed to get vbus irq\n");339			return palmas_usb->gpio_vbus_irq;340		}341		status = devm_request_threaded_irq(&pdev->dev,342						palmas_usb->gpio_vbus_irq,343						NULL,344						palmas_vbus_irq_handler,345						IRQF_TRIGGER_FALLING |346						IRQF_TRIGGER_RISING |347						IRQF_ONESHOT,348						"palmas_usb_vbus",349						palmas_usb);350		if (status < 0) {351			dev_err(&pdev->dev,352				"failed to request handler for vbus irq\n");353			return status;354		}355	}356 357	palmas_enable_irq(palmas_usb);358	/* perform initial detection */359	if (palmas_usb->enable_gpio_vbus_detection)360		palmas_vbus_irq_handler(palmas_usb->gpio_vbus_irq, palmas_usb);361	palmas_gpio_id_detect(&palmas_usb->wq_detectid.work);362	device_set_wakeup_capable(&pdev->dev, true);363	return 0;364}365 366#ifdef CONFIG_PM_SLEEP367static int palmas_usb_suspend(struct device *dev)368{369	struct palmas_usb *palmas_usb = dev_get_drvdata(dev);370 371	if (device_may_wakeup(dev)) {372		if (palmas_usb->enable_vbus_detection)373			enable_irq_wake(palmas_usb->vbus_irq);374		if (palmas_usb->enable_gpio_vbus_detection)375			enable_irq_wake(palmas_usb->gpio_vbus_irq);376		if (palmas_usb->enable_id_detection)377			enable_irq_wake(palmas_usb->id_irq);378		if (palmas_usb->enable_gpio_id_detection)379			enable_irq_wake(palmas_usb->gpio_id_irq);380	}381	return 0;382}383 384static int palmas_usb_resume(struct device *dev)385{386	struct palmas_usb *palmas_usb = dev_get_drvdata(dev);387 388	if (device_may_wakeup(dev)) {389		if (palmas_usb->enable_vbus_detection)390			disable_irq_wake(palmas_usb->vbus_irq);391		if (palmas_usb->enable_gpio_vbus_detection)392			disable_irq_wake(palmas_usb->gpio_vbus_irq);393		if (palmas_usb->enable_id_detection)394			disable_irq_wake(palmas_usb->id_irq);395		if (palmas_usb->enable_gpio_id_detection)396			disable_irq_wake(palmas_usb->gpio_id_irq);397	}398 399	/* check if GPIO states changed while suspend/resume */400	if (palmas_usb->enable_gpio_vbus_detection)401		palmas_vbus_irq_handler(palmas_usb->gpio_vbus_irq, palmas_usb);402	palmas_gpio_id_detect(&palmas_usb->wq_detectid.work);403 404	return 0;405};406#endif407 408static SIMPLE_DEV_PM_OPS(palmas_pm_ops, palmas_usb_suspend, palmas_usb_resume);409 410static const struct of_device_id of_palmas_match_tbl[] = {411	{ .compatible = "ti,palmas-usb", },412	{ .compatible = "ti,palmas-usb-vid", },413	{ .compatible = "ti,twl6035-usb", },414	{ .compatible = "ti,twl6035-usb-vid", },415	{ /* end */ }416};417 418static struct platform_driver palmas_usb_driver = {419	.probe = palmas_usb_probe,420	.driver = {421		.name = "palmas-usb",422		.of_match_table = of_palmas_match_tbl,423		.pm = &palmas_pm_ops,424	},425};426 427module_platform_driver(palmas_usb_driver);428 429MODULE_ALIAS("platform:palmas-usb");430MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>");431MODULE_DESCRIPTION("Palmas USB transceiver driver");432MODULE_LICENSE("GPL");433MODULE_DEVICE_TABLE(of, of_palmas_match_tbl);434