mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	It is odd to call devm_add_action_or_reset() before calling the function
that should be undone.
Either, the "_or_reset" part should be omitted, or the action should be
recorded after the resources have been allocated.
Switch the order of devm_add_action_or_reset() and usb_role_switch_get().
Fixes: 9a1bf58ccd ("usb: misc: eud: Add driver support for Embedded USB Debugger(EUD)")
Signed-off-by: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
Link: https://lore.kernel.org/r/362908699275ecec078381b42d87c817c6965fc6.1648979948.git.christophe.jaillet@wanadoo.fr
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
		
	
			
		
			
				
	
	
		
			251 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0-only
 | 
						|
/*
 | 
						|
 * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
 | 
						|
 */
 | 
						|
 | 
						|
#include <linux/bitops.h>
 | 
						|
#include <linux/err.h>
 | 
						|
#include <linux/interrupt.h>
 | 
						|
#include <linux/io.h>
 | 
						|
#include <linux/iopoll.h>
 | 
						|
#include <linux/kernel.h>
 | 
						|
#include <linux/module.h>
 | 
						|
#include <linux/of.h>
 | 
						|
#include <linux/platform_device.h>
 | 
						|
#include <linux/slab.h>
 | 
						|
#include <linux/sysfs.h>
 | 
						|
#include <linux/usb/role.h>
 | 
						|
 | 
						|
#define EUD_REG_INT1_EN_MASK	0x0024
 | 
						|
#define EUD_REG_INT_STATUS_1	0x0044
 | 
						|
#define EUD_REG_CTL_OUT_1	0x0074
 | 
						|
#define EUD_REG_VBUS_INT_CLR	0x0080
 | 
						|
#define EUD_REG_CSR_EUD_EN	0x1014
 | 
						|
#define EUD_REG_SW_ATTACH_DET	0x1018
 | 
						|
#define EUD_REG_EUD_EN2        0x0000
 | 
						|
 | 
						|
#define EUD_ENABLE		BIT(0)
 | 
						|
#define EUD_INT_PET_EUD	BIT(0)
 | 
						|
#define EUD_INT_VBUS		BIT(2)
 | 
						|
#define EUD_INT_SAFE_MODE	BIT(4)
 | 
						|
#define EUD_INT_ALL		(EUD_INT_VBUS | EUD_INT_SAFE_MODE)
 | 
						|
 | 
						|
struct eud_chip {
 | 
						|
	struct device			*dev;
 | 
						|
	struct usb_role_switch		*role_sw;
 | 
						|
	void __iomem			*base;
 | 
						|
	void __iomem			*mode_mgr;
 | 
						|
	unsigned int			int_status;
 | 
						|
	int				irq;
 | 
						|
	bool				enabled;
 | 
						|
	bool				usb_attached;
 | 
						|
};
 | 
						|
 | 
						|
static int enable_eud(struct eud_chip *priv)
 | 
						|
{
 | 
						|
	writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN);
 | 
						|
	writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE,
 | 
						|
			priv->base + EUD_REG_INT1_EN_MASK);
 | 
						|
	writel(1, priv->mode_mgr + EUD_REG_EUD_EN2);
 | 
						|
 | 
						|
	return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE);
 | 
						|
}
 | 
						|
 | 
						|
static void disable_eud(struct eud_chip *priv)
 | 
						|
{
 | 
						|
	writel(0, priv->base + EUD_REG_CSR_EUD_EN);
 | 
						|
	writel(0, priv->mode_mgr + EUD_REG_EUD_EN2);
 | 
						|
}
 | 
						|
 | 
						|
static ssize_t enable_show(struct device *dev,
 | 
						|
		struct device_attribute *attr, char *buf)
 | 
						|
{
 | 
						|
	struct eud_chip *chip = dev_get_drvdata(dev);
 | 
						|
 | 
						|
	return sysfs_emit(buf, "%d\n", chip->enabled);
 | 
						|
}
 | 
						|
 | 
						|
static ssize_t enable_store(struct device *dev,
 | 
						|
		struct device_attribute *attr,
 | 
						|
		const char *buf, size_t count)
 | 
						|
{
 | 
						|
	struct eud_chip *chip = dev_get_drvdata(dev);
 | 
						|
	bool enable;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	if (kstrtobool(buf, &enable))
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	if (enable) {
 | 
						|
		ret = enable_eud(chip);
 | 
						|
		if (!ret)
 | 
						|
			chip->enabled = enable;
 | 
						|
		else
 | 
						|
			disable_eud(chip);
 | 
						|
	} else {
 | 
						|
		disable_eud(chip);
 | 
						|
	}
 | 
						|
 | 
						|
	return count;
 | 
						|
}
 | 
						|
 | 
						|
static DEVICE_ATTR_RW(enable);
 | 
						|
 | 
						|
static struct attribute *eud_attrs[] = {
 | 
						|
	&dev_attr_enable.attr,
 | 
						|
	NULL,
 | 
						|
};
 | 
						|
ATTRIBUTE_GROUPS(eud);
 | 
						|
 | 
						|
static void usb_attach_detach(struct eud_chip *chip)
 | 
						|
{
 | 
						|
	u32 reg;
 | 
						|
 | 
						|
	/* read ctl_out_1[4] to find USB attach or detach event */
 | 
						|
	reg = readl(chip->base + EUD_REG_CTL_OUT_1);
 | 
						|
	chip->usb_attached = reg & EUD_INT_SAFE_MODE;
 | 
						|
}
 | 
						|
 | 
						|
static void pet_eud(struct eud_chip *chip)
 | 
						|
{
 | 
						|
	u32 reg;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	/* When the EUD_INT_PET_EUD in SW_ATTACH_DET is set, the cable has been
 | 
						|
	 * disconnected and we need to detach the pet to check if EUD is in safe
 | 
						|
	 * mode before attaching again.
 | 
						|
	 */
 | 
						|
	reg = readl(chip->base + EUD_REG_SW_ATTACH_DET);
 | 
						|
	if (reg & EUD_INT_PET_EUD) {
 | 
						|
		/* Detach & Attach pet for EUD */
 | 
						|
		writel(0, chip->base + EUD_REG_SW_ATTACH_DET);
 | 
						|
		/* Delay to make sure detach pet is done before attach pet */
 | 
						|
		ret = readl_poll_timeout(chip->base + EUD_REG_SW_ATTACH_DET,
 | 
						|
					reg, (reg == 0), 1, 100);
 | 
						|
		if (ret) {
 | 
						|
			dev_err(chip->dev, "Detach pet failed\n");
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	/* Attach pet for EUD */
 | 
						|
	writel(EUD_INT_PET_EUD, chip->base + EUD_REG_SW_ATTACH_DET);
 | 
						|
}
 | 
						|
 | 
						|
static irqreturn_t handle_eud_irq(int irq, void *data)
 | 
						|
{
 | 
						|
	struct eud_chip *chip = data;
 | 
						|
	u32 reg;
 | 
						|
 | 
						|
	reg = readl(chip->base + EUD_REG_INT_STATUS_1);
 | 
						|
	switch (reg & EUD_INT_ALL) {
 | 
						|
	case EUD_INT_VBUS:
 | 
						|
		usb_attach_detach(chip);
 | 
						|
		return IRQ_WAKE_THREAD;
 | 
						|
	case EUD_INT_SAFE_MODE:
 | 
						|
		pet_eud(chip);
 | 
						|
		return IRQ_HANDLED;
 | 
						|
	default:
 | 
						|
		return IRQ_NONE;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static irqreturn_t handle_eud_irq_thread(int irq, void *data)
 | 
						|
{
 | 
						|
	struct eud_chip *chip = data;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	if (chip->usb_attached)
 | 
						|
		ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_DEVICE);
 | 
						|
	else
 | 
						|
		ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_HOST);
 | 
						|
	if (ret)
 | 
						|
		dev_err(chip->dev, "failed to set role switch\n");
 | 
						|
 | 
						|
	/* set and clear vbus_int_clr[0] to clear interrupt */
 | 
						|
	writel(BIT(0), chip->base + EUD_REG_VBUS_INT_CLR);
 | 
						|
	writel(0, chip->base + EUD_REG_VBUS_INT_CLR);
 | 
						|
 | 
						|
	return IRQ_HANDLED;
 | 
						|
}
 | 
						|
 | 
						|
static void eud_role_switch_release(void *data)
 | 
						|
{
 | 
						|
	struct eud_chip *chip = data;
 | 
						|
 | 
						|
	usb_role_switch_put(chip->role_sw);
 | 
						|
}
 | 
						|
 | 
						|
static int eud_probe(struct platform_device *pdev)
 | 
						|
{
 | 
						|
	struct eud_chip *chip;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
 | 
						|
	if (!chip)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	chip->dev = &pdev->dev;
 | 
						|
 | 
						|
	chip->role_sw = usb_role_switch_get(&pdev->dev);
 | 
						|
	if (IS_ERR(chip->role_sw))
 | 
						|
		return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw),
 | 
						|
					"failed to get role switch\n");
 | 
						|
 | 
						|
	ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip);
 | 
						|
	if (ret)
 | 
						|
		return dev_err_probe(chip->dev, ret,
 | 
						|
				"failed to add role switch release action\n");
 | 
						|
 | 
						|
	chip->base = devm_platform_ioremap_resource(pdev, 0);
 | 
						|
	if (IS_ERR(chip->base))
 | 
						|
		return PTR_ERR(chip->base);
 | 
						|
 | 
						|
	chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1);
 | 
						|
	if (IS_ERR(chip->mode_mgr))
 | 
						|
		return PTR_ERR(chip->mode_mgr);
 | 
						|
 | 
						|
	chip->irq = platform_get_irq(pdev, 0);
 | 
						|
	ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq,
 | 
						|
			handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip);
 | 
						|
	if (ret)
 | 
						|
		return dev_err_probe(chip->dev, ret, "failed to allocate irq\n");
 | 
						|
 | 
						|
	enable_irq_wake(chip->irq);
 | 
						|
 | 
						|
	platform_set_drvdata(pdev, chip);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int eud_remove(struct platform_device *pdev)
 | 
						|
{
 | 
						|
	struct eud_chip *chip = platform_get_drvdata(pdev);
 | 
						|
 | 
						|
	if (chip->enabled)
 | 
						|
		disable_eud(chip);
 | 
						|
 | 
						|
	device_init_wakeup(&pdev->dev, false);
 | 
						|
	disable_irq_wake(chip->irq);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static const struct of_device_id eud_dt_match[] = {
 | 
						|
	{ .compatible = "qcom,sc7280-eud" },
 | 
						|
	{ }
 | 
						|
};
 | 
						|
MODULE_DEVICE_TABLE(of, eud_dt_match);
 | 
						|
 | 
						|
static struct platform_driver eud_driver = {
 | 
						|
	.probe	= eud_probe,
 | 
						|
	.remove	= eud_remove,
 | 
						|
	.driver	= {
 | 
						|
		.name = "qcom_eud",
 | 
						|
		.dev_groups = eud_groups,
 | 
						|
		.of_match_table = eud_dt_match,
 | 
						|
	},
 | 
						|
};
 | 
						|
module_platform_driver(eud_driver);
 | 
						|
 | 
						|
MODULE_DESCRIPTION("QTI EUD driver");
 | 
						|
MODULE_LICENSE("GPL v2");
 |