|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * OMAP OTG controller driver | 
|  | * | 
|  | * Based on code from tahvo-usb.c and isp1301_omap.c drivers. | 
|  | * | 
|  | * Copyright (C) 2005-2006 Nokia Corporation | 
|  | * Copyright (C) 2004 Texas Instruments | 
|  | * Copyright (C) 2004 David Brownell | 
|  | */ | 
|  |  | 
|  | #include <linux/io.h> | 
|  | #include <linux/err.h> | 
|  | #include <linux/extcon.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/platform_data/usb-omap1.h> | 
|  |  | 
|  | struct otg_device { | 
|  | void __iomem			*base; | 
|  | bool				id; | 
|  | bool				vbus; | 
|  | struct extcon_dev		*extcon; | 
|  | struct notifier_block		vbus_nb; | 
|  | struct notifier_block		id_nb; | 
|  | }; | 
|  |  | 
|  | #define OMAP_OTG_CTRL		0x0c | 
|  | #define OMAP_OTG_ASESSVLD	(1 << 20) | 
|  | #define OMAP_OTG_BSESSEND	(1 << 19) | 
|  | #define OMAP_OTG_BSESSVLD	(1 << 18) | 
|  | #define OMAP_OTG_VBUSVLD	(1 << 17) | 
|  | #define OMAP_OTG_ID		(1 << 16) | 
|  | #define OMAP_OTG_XCEIV_OUTPUTS \ | 
|  | (OMAP_OTG_ASESSVLD | OMAP_OTG_BSESSEND | OMAP_OTG_BSESSVLD | \ | 
|  | OMAP_OTG_VBUSVLD  | OMAP_OTG_ID) | 
|  |  | 
|  | static void omap_otg_ctrl(struct otg_device *otg_dev, u32 outputs) | 
|  | { | 
|  | u32 l; | 
|  |  | 
|  | l = readl(otg_dev->base + OMAP_OTG_CTRL); | 
|  | l &= ~OMAP_OTG_XCEIV_OUTPUTS; | 
|  | l |= outputs; | 
|  | writel(l, otg_dev->base + OMAP_OTG_CTRL); | 
|  | } | 
|  |  | 
|  | static void omap_otg_set_mode(struct otg_device *otg_dev) | 
|  | { | 
|  | if (!otg_dev->id && otg_dev->vbus) | 
|  | /* Set B-session valid. */ | 
|  | omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSVLD); | 
|  | else if (otg_dev->vbus) | 
|  | /* Set A-session valid. */ | 
|  | omap_otg_ctrl(otg_dev, OMAP_OTG_ASESSVLD); | 
|  | else if (!otg_dev->id) | 
|  | /* Set B-session end to indicate no VBUS. */ | 
|  | omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSEND); | 
|  | } | 
|  |  | 
|  | static int omap_otg_id_notifier(struct notifier_block *nb, | 
|  | unsigned long event, void *ptr) | 
|  | { | 
|  | struct otg_device *otg_dev = container_of(nb, struct otg_device, id_nb); | 
|  |  | 
|  | otg_dev->id = event; | 
|  | omap_otg_set_mode(otg_dev); | 
|  |  | 
|  | return NOTIFY_DONE; | 
|  | } | 
|  |  | 
|  | static int omap_otg_vbus_notifier(struct notifier_block *nb, | 
|  | unsigned long event, void *ptr) | 
|  | { | 
|  | struct otg_device *otg_dev = container_of(nb, struct otg_device, | 
|  | vbus_nb); | 
|  |  | 
|  | otg_dev->vbus = event; | 
|  | omap_otg_set_mode(otg_dev); | 
|  |  | 
|  | return NOTIFY_DONE; | 
|  | } | 
|  |  | 
|  | static int omap_otg_probe(struct platform_device *pdev) | 
|  | { | 
|  | const struct omap_usb_config *config = pdev->dev.platform_data; | 
|  | struct otg_device *otg_dev; | 
|  | struct extcon_dev *extcon; | 
|  | int ret; | 
|  | u32 rev; | 
|  |  | 
|  | if (!config || !config->extcon) | 
|  | return -ENODEV; | 
|  |  | 
|  | extcon = extcon_get_extcon_dev(config->extcon); | 
|  | if (!extcon) | 
|  | return -EPROBE_DEFER; | 
|  |  | 
|  | otg_dev = devm_kzalloc(&pdev->dev, sizeof(*otg_dev), GFP_KERNEL); | 
|  | if (!otg_dev) | 
|  | return -ENOMEM; | 
|  |  | 
|  | otg_dev->base = devm_ioremap_resource(&pdev->dev, &pdev->resource[0]); | 
|  | if (IS_ERR(otg_dev->base)) | 
|  | return PTR_ERR(otg_dev->base); | 
|  |  | 
|  | otg_dev->extcon = extcon; | 
|  | otg_dev->id_nb.notifier_call = omap_otg_id_notifier; | 
|  | otg_dev->vbus_nb.notifier_call = omap_otg_vbus_notifier; | 
|  |  | 
|  | ret = devm_extcon_register_notifier(&pdev->dev, extcon, | 
|  | EXTCON_USB_HOST, &otg_dev->id_nb); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = devm_extcon_register_notifier(&pdev->dev, extcon, | 
|  | EXTCON_USB, &otg_dev->vbus_nb); | 
|  | if (ret) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | otg_dev->id = extcon_get_state(extcon, EXTCON_USB_HOST); | 
|  | otg_dev->vbus = extcon_get_state(extcon, EXTCON_USB); | 
|  | omap_otg_set_mode(otg_dev); | 
|  |  | 
|  | rev = readl(otg_dev->base); | 
|  |  | 
|  | dev_info(&pdev->dev, | 
|  | "OMAP USB OTG controller rev %d.%d (%s, id=%d, vbus=%d)\n", | 
|  | (rev >> 4) & 0xf, rev & 0xf, config->extcon, otg_dev->id, | 
|  | otg_dev->vbus); | 
|  |  | 
|  | platform_set_drvdata(pdev, otg_dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct platform_driver omap_otg_driver = { | 
|  | .probe		= omap_otg_probe, | 
|  | .driver		= { | 
|  | .name	= "omap_otg", | 
|  | }, | 
|  | }; | 
|  | module_platform_driver(omap_otg_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("OMAP USB OTG controller driver"); | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>"); |