mirror of
https://github.com/torvalds/linux.git
synced 2024-11-29 15:41:36 +00:00
58e4a2d27d
The extcon_get_extcon_dev() function returns error pointers on error, NULL when it's a -EPROBE_DEFER defer situation, and ERR_PTR(-ENODEV) when the CONFIG_EXTCON option is disabled. This is very complicated for the callers to handle and a number of them had bugs that would lead to an Oops. In real life, there are two things which prevented crashes. First, error pointers would only be returned if there was bug in the caller where they passed a NULL "extcon_name" and none of them do that. Second, only two out of the eight drivers will build when CONFIG_EXTCON is disabled. The normal way to write this would be to return -EPROBE_DEFER directly when appropriate and return NULL when CONFIG_EXTCON is disabled. Then the error handling is simple and just looks like: dev->edev = extcon_get_extcon_dev(acpi_dev_name(adev)); if (IS_ERR(dev->edev)) return PTR_ERR(dev->edev); For the two drivers which can build with CONFIG_EXTCON disabled, then extcon_get_extcon_dev() will now return NULL which is not treated as an error and the probe will continue successfully. Those two drivers are "typec_fusb302" and "max8997-battery". In the original code, the typec_fusb302 driver had an 800ms hang in tcpm_get_current_limit() but now that function is a no-op. For the max8997-battery driver everything should continue working as is. Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com> Reviewed-by: Hans de Goede <hdegoede@redhat.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
151 lines
3.7 KiB
C
151 lines
3.7 KiB
C
// 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 (IS_ERR(extcon))
|
|
return PTR_ERR(extcon);
|
|
|
|
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>");
|