/* * 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 * * This file is subject to the terms and conditions of the GNU General * Public License. See the file "COPYING" in the main directory of this * archive for more details. * * 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/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 = extcon_register_notifier(extcon, EXTCON_USB_HOST, &otg_dev->id_nb); if (ret) return ret; ret = extcon_register_notifier(extcon, EXTCON_USB, &otg_dev->vbus_nb); if (ret) { extcon_unregister_notifier(extcon, EXTCON_USB_HOST, &otg_dev->id_nb); return ret; } otg_dev->id = extcon_get_cable_state_(extcon, EXTCON_USB_HOST); otg_dev->vbus = extcon_get_cable_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); return 0; } static int omap_otg_remove(struct platform_device *pdev) { struct otg_device *otg_dev = platform_get_drvdata(pdev); struct extcon_dev *edev = otg_dev->extcon; extcon_unregister_notifier(edev, EXTCON_USB_HOST,&otg_dev->id_nb); extcon_unregister_notifier(edev, EXTCON_USB, &otg_dev->vbus_nb); return 0; } static struct platform_driver omap_otg_driver = { .probe = omap_otg_probe, .remove = omap_otg_remove, .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>");