File Index
Files and directories
Sign in
FILE

qcom_eud.c

File preview with syntax highlighting.

linux-6.19.14/drivers/usb/misc/qcom_eud.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
  4 */
  5
  6#include <linux/bitops.h>
  7#include <linux/err.h>
  8#include <linux/interrupt.h>
  9#include <linux/io.h>
 10#include <linux/iopoll.h>
 11#include <linux/kernel.h>
 12#include <linux/module.h>
 13#include <linux/of.h>
 14#include <linux/platform_device.h>
 15#include <linux/slab.h>
 16#include <linux/sysfs.h>
 17#include <linux/usb/role.h>
 18#include <linux/firmware/qcom/qcom_scm.h>
 19
 20#define EUD_REG_INT1_EN_MASK	0x0024
 21#define EUD_REG_INT_STATUS_1	0x0044
 22#define EUD_REG_CTL_OUT_1	0x0074
 23#define EUD_REG_VBUS_INT_CLR	0x0080
 24#define EUD_REG_CSR_EUD_EN	0x1014
 25#define EUD_REG_SW_ATTACH_DET	0x1018
 26#define EUD_REG_EUD_EN2		0x0000
 27
 28#define EUD_ENABLE		BIT(0)
 29#define EUD_INT_PET_EUD		BIT(0)
 30#define EUD_INT_VBUS		BIT(2)
 31#define EUD_INT_SAFE_MODE	BIT(4)
 32#define EUD_INT_ALL		(EUD_INT_VBUS | EUD_INT_SAFE_MODE)
 33
 34struct eud_chip {
 35	struct device			*dev;
 36	struct usb_role_switch		*role_sw;
 37	void __iomem			*base;
 38	phys_addr_t			mode_mgr;
 39	unsigned int			int_status;
 40	int				irq;
 41	bool				enabled;
 42	bool				usb_attached;
 43};
 44
 45static int enable_eud(struct eud_chip *priv)
 46{
 47	int ret;
 48
 49	ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 1);
 50	if (ret)
 51		return ret;
 52
 53	writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN);
 54	writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE,
 55			priv->base + EUD_REG_INT1_EN_MASK);
 56
 57	return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE);
 58}
 59
 60static int disable_eud(struct eud_chip *priv)
 61{
 62	int ret;
 63
 64	ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 0);
 65	if (ret)
 66		return ret;
 67
 68	writel(0, priv->base + EUD_REG_CSR_EUD_EN);
 69	return 0;
 70}
 71
 72static ssize_t enable_show(struct device *dev,
 73		struct device_attribute *attr, char *buf)
 74{
 75	struct eud_chip *chip = dev_get_drvdata(dev);
 76
 77	return sysfs_emit(buf, "%d\n", chip->enabled);
 78}
 79
 80static ssize_t enable_store(struct device *dev,
 81		struct device_attribute *attr,
 82		const char *buf, size_t count)
 83{
 84	struct eud_chip *chip = dev_get_drvdata(dev);
 85	bool enable;
 86	int ret;
 87
 88	if (kstrtobool(buf, &enable))
 89		return -EINVAL;
 90
 91	if (enable) {
 92		ret = enable_eud(chip);
 93		if (!ret)
 94			chip->enabled = enable;
 95		else
 96			disable_eud(chip);
 97
 98	} else {
 99		ret = disable_eud(chip);
100	}
101
102	return ret < 0 ? ret : count;
103}
104
105static DEVICE_ATTR_RW(enable);
106
107static struct attribute *eud_attrs[] = {
108	&dev_attr_enable.attr,
109	NULL,
110};
111ATTRIBUTE_GROUPS(eud);
112
113static void usb_attach_detach(struct eud_chip *chip)
114{
115	u32 reg;
116
117	/* read ctl_out_1[4] to find USB attach or detach event */
118	reg = readl(chip->base + EUD_REG_CTL_OUT_1);
119	chip->usb_attached = reg & EUD_INT_SAFE_MODE;
120}
121
122static void pet_eud(struct eud_chip *chip)
123{
124	u32 reg;
125	int ret;
126
127	/* When the EUD_INT_PET_EUD in SW_ATTACH_DET is set, the cable has been
128	 * disconnected and we need to detach the pet to check if EUD is in safe
129	 * mode before attaching again.
130	 */
131	reg = readl(chip->base + EUD_REG_SW_ATTACH_DET);
132	if (reg & EUD_INT_PET_EUD) {
133		/* Detach & Attach pet for EUD */
134		writel(0, chip->base + EUD_REG_SW_ATTACH_DET);
135		/* Delay to make sure detach pet is done before attach pet */
136		ret = readl_poll_timeout(chip->base + EUD_REG_SW_ATTACH_DET,
137					reg, (reg == 0), 1, 100);
138		if (ret) {
139			dev_err(chip->dev, "Detach pet failed\n");
140			return;
141		}
142	}
143	/* Attach pet for EUD */
144	writel(EUD_INT_PET_EUD, chip->base + EUD_REG_SW_ATTACH_DET);
145}
146
147static irqreturn_t handle_eud_irq(int irq, void *data)
148{
149	struct eud_chip *chip = data;
150	u32 reg;
151
152	reg = readl(chip->base + EUD_REG_INT_STATUS_1);
153	switch (reg & EUD_INT_ALL) {
154	case EUD_INT_VBUS:
155		usb_attach_detach(chip);
156		return IRQ_WAKE_THREAD;
157	case EUD_INT_SAFE_MODE:
158		pet_eud(chip);
159		return IRQ_HANDLED;
160	default:
161		return IRQ_NONE;
162	}
163}
164
165static irqreturn_t handle_eud_irq_thread(int irq, void *data)
166{
167	struct eud_chip *chip = data;
168	int ret;
169
170	if (chip->usb_attached)
171		ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_DEVICE);
172	else
173		ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_HOST);
174	if (ret)
175		dev_err(chip->dev, "failed to set role switch\n");
176
177	/* set and clear vbus_int_clr[0] to clear interrupt */
178	writel(BIT(0), chip->base + EUD_REG_VBUS_INT_CLR);
179	writel(0, chip->base + EUD_REG_VBUS_INT_CLR);
180
181	return IRQ_HANDLED;
182}
183
184static void eud_role_switch_release(void *data)
185{
186	struct eud_chip *chip = data;
187
188	usb_role_switch_put(chip->role_sw);
189}
190
191static int eud_probe(struct platform_device *pdev)
192{
193	struct eud_chip *chip;
194	struct resource *res;
195	int ret;
196
197	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
198	if (!chip)
199		return -ENOMEM;
200
201	chip->dev = &pdev->dev;
202
203	chip->role_sw = usb_role_switch_get(&pdev->dev);
204	if (IS_ERR(chip->role_sw))
205		return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw),
206					"failed to get role switch\n");
207
208	ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip);
209	if (ret)
210		return ret;
211
212	chip->base = devm_platform_ioremap_resource(pdev, 0);
213	if (IS_ERR(chip->base))
214		return PTR_ERR(chip->base);
215
216	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
217	if (!res)
218		return -ENODEV;
219	chip->mode_mgr = res->start;
220
221	chip->irq = platform_get_irq(pdev, 0);
222	if (chip->irq < 0)
223		return chip->irq;
224
225	ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq,
226			handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip);
227	if (ret)
228		return dev_err_probe(chip->dev, ret, "failed to allocate irq\n");
229
230	enable_irq_wake(chip->irq);
231
232	platform_set_drvdata(pdev, chip);
233
234	return 0;
235}
236
237static void eud_remove(struct platform_device *pdev)
238{
239	struct eud_chip *chip = platform_get_drvdata(pdev);
240
241	if (chip->enabled)
242		disable_eud(chip);
243
244	device_init_wakeup(&pdev->dev, false);
245	disable_irq_wake(chip->irq);
246}
247
248static const struct of_device_id eud_dt_match[] = {
249	{ .compatible = "qcom,eud" },
250	{ }
251};
252MODULE_DEVICE_TABLE(of, eud_dt_match);
253
254static struct platform_driver eud_driver = {
255	.probe	= eud_probe,
256	.remove = eud_remove,
257	.driver	= {
258		.name = "qcom_eud",
259		.dev_groups = eud_groups,
260		.of_match_table = eud_dt_match,
261	},
262};
263module_platform_driver(eud_driver);
264
265MODULE_DESCRIPTION("QTI EUD driver");
266MODULE_LICENSE("GPL v2");
~/ Read-only file preview.