15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2449d2ba6SAaro Koskinen /*
3449d2ba6SAaro Koskinen * OMAP OTG controller driver
4449d2ba6SAaro Koskinen *
5449d2ba6SAaro Koskinen * Based on code from tahvo-usb.c and isp1301_omap.c drivers.
6449d2ba6SAaro Koskinen *
7449d2ba6SAaro Koskinen * Copyright (C) 2005-2006 Nokia Corporation
8449d2ba6SAaro Koskinen * Copyright (C) 2004 Texas Instruments
9449d2ba6SAaro Koskinen * Copyright (C) 2004 David Brownell
10449d2ba6SAaro Koskinen */
11449d2ba6SAaro Koskinen
12449d2ba6SAaro Koskinen #include <linux/io.h>
13449d2ba6SAaro Koskinen #include <linux/err.h>
14449d2ba6SAaro Koskinen #include <linux/extcon.h>
15449d2ba6SAaro Koskinen #include <linux/kernel.h>
16449d2ba6SAaro Koskinen #include <linux/module.h>
17449d2ba6SAaro Koskinen #include <linux/interrupt.h>
18449d2ba6SAaro Koskinen #include <linux/platform_device.h>
19449d2ba6SAaro Koskinen #include <linux/platform_data/usb-omap1.h>
20449d2ba6SAaro Koskinen
21449d2ba6SAaro Koskinen struct otg_device {
22449d2ba6SAaro Koskinen void __iomem *base;
23449d2ba6SAaro Koskinen bool id;
24449d2ba6SAaro Koskinen bool vbus;
25a2fd2423SChanwoo Choi struct extcon_dev *extcon;
26449d2ba6SAaro Koskinen struct notifier_block vbus_nb;
27449d2ba6SAaro Koskinen struct notifier_block id_nb;
28449d2ba6SAaro Koskinen };
29449d2ba6SAaro Koskinen
30449d2ba6SAaro Koskinen #define OMAP_OTG_CTRL 0x0c
31449d2ba6SAaro Koskinen #define OMAP_OTG_ASESSVLD (1 << 20)
32449d2ba6SAaro Koskinen #define OMAP_OTG_BSESSEND (1 << 19)
33449d2ba6SAaro Koskinen #define OMAP_OTG_BSESSVLD (1 << 18)
34449d2ba6SAaro Koskinen #define OMAP_OTG_VBUSVLD (1 << 17)
35449d2ba6SAaro Koskinen #define OMAP_OTG_ID (1 << 16)
36449d2ba6SAaro Koskinen #define OMAP_OTG_XCEIV_OUTPUTS \
37449d2ba6SAaro Koskinen (OMAP_OTG_ASESSVLD | OMAP_OTG_BSESSEND | OMAP_OTG_BSESSVLD | \
38449d2ba6SAaro Koskinen OMAP_OTG_VBUSVLD | OMAP_OTG_ID)
39449d2ba6SAaro Koskinen
omap_otg_ctrl(struct otg_device * otg_dev,u32 outputs)40449d2ba6SAaro Koskinen static void omap_otg_ctrl(struct otg_device *otg_dev, u32 outputs)
41449d2ba6SAaro Koskinen {
42449d2ba6SAaro Koskinen u32 l;
43449d2ba6SAaro Koskinen
44449d2ba6SAaro Koskinen l = readl(otg_dev->base + OMAP_OTG_CTRL);
45449d2ba6SAaro Koskinen l &= ~OMAP_OTG_XCEIV_OUTPUTS;
46449d2ba6SAaro Koskinen l |= outputs;
47449d2ba6SAaro Koskinen writel(l, otg_dev->base + OMAP_OTG_CTRL);
48449d2ba6SAaro Koskinen }
49449d2ba6SAaro Koskinen
omap_otg_set_mode(struct otg_device * otg_dev)50449d2ba6SAaro Koskinen static void omap_otg_set_mode(struct otg_device *otg_dev)
51449d2ba6SAaro Koskinen {
52449d2ba6SAaro Koskinen if (!otg_dev->id && otg_dev->vbus)
53449d2ba6SAaro Koskinen /* Set B-session valid. */
54449d2ba6SAaro Koskinen omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSVLD);
55449d2ba6SAaro Koskinen else if (otg_dev->vbus)
56449d2ba6SAaro Koskinen /* Set A-session valid. */
57449d2ba6SAaro Koskinen omap_otg_ctrl(otg_dev, OMAP_OTG_ASESSVLD);
58449d2ba6SAaro Koskinen else if (!otg_dev->id)
59449d2ba6SAaro Koskinen /* Set B-session end to indicate no VBUS. */
60449d2ba6SAaro Koskinen omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSEND);
61449d2ba6SAaro Koskinen }
62449d2ba6SAaro Koskinen
omap_otg_id_notifier(struct notifier_block * nb,unsigned long event,void * ptr)63449d2ba6SAaro Koskinen static int omap_otg_id_notifier(struct notifier_block *nb,
64449d2ba6SAaro Koskinen unsigned long event, void *ptr)
65449d2ba6SAaro Koskinen {
66449d2ba6SAaro Koskinen struct otg_device *otg_dev = container_of(nb, struct otg_device, id_nb);
67449d2ba6SAaro Koskinen
68449d2ba6SAaro Koskinen otg_dev->id = event;
69449d2ba6SAaro Koskinen omap_otg_set_mode(otg_dev);
70449d2ba6SAaro Koskinen
71449d2ba6SAaro Koskinen return NOTIFY_DONE;
72449d2ba6SAaro Koskinen }
73449d2ba6SAaro Koskinen
omap_otg_vbus_notifier(struct notifier_block * nb,unsigned long event,void * ptr)74449d2ba6SAaro Koskinen static int omap_otg_vbus_notifier(struct notifier_block *nb,
75449d2ba6SAaro Koskinen unsigned long event, void *ptr)
76449d2ba6SAaro Koskinen {
77449d2ba6SAaro Koskinen struct otg_device *otg_dev = container_of(nb, struct otg_device,
78449d2ba6SAaro Koskinen vbus_nb);
79449d2ba6SAaro Koskinen
80449d2ba6SAaro Koskinen otg_dev->vbus = event;
81449d2ba6SAaro Koskinen omap_otg_set_mode(otg_dev);
82449d2ba6SAaro Koskinen
83449d2ba6SAaro Koskinen return NOTIFY_DONE;
84449d2ba6SAaro Koskinen }
85449d2ba6SAaro Koskinen
omap_otg_probe(struct platform_device * pdev)86449d2ba6SAaro Koskinen static int omap_otg_probe(struct platform_device *pdev)
87449d2ba6SAaro Koskinen {
88449d2ba6SAaro Koskinen const struct omap_usb_config *config = pdev->dev.platform_data;
89449d2ba6SAaro Koskinen struct otg_device *otg_dev;
90449d2ba6SAaro Koskinen struct extcon_dev *extcon;
91449d2ba6SAaro Koskinen int ret;
92449d2ba6SAaro Koskinen u32 rev;
93449d2ba6SAaro Koskinen
94449d2ba6SAaro Koskinen if (!config || !config->extcon)
95449d2ba6SAaro Koskinen return -ENODEV;
96449d2ba6SAaro Koskinen
97449d2ba6SAaro Koskinen extcon = extcon_get_extcon_dev(config->extcon);
98*58e4a2d2SDan Carpenter if (IS_ERR(extcon))
99*58e4a2d2SDan Carpenter return PTR_ERR(extcon);
100449d2ba6SAaro Koskinen
101449d2ba6SAaro Koskinen otg_dev = devm_kzalloc(&pdev->dev, sizeof(*otg_dev), GFP_KERNEL);
102449d2ba6SAaro Koskinen if (!otg_dev)
103449d2ba6SAaro Koskinen return -ENOMEM;
104449d2ba6SAaro Koskinen
105449d2ba6SAaro Koskinen otg_dev->base = devm_ioremap_resource(&pdev->dev, &pdev->resource[0]);
106449d2ba6SAaro Koskinen if (IS_ERR(otg_dev->base))
107449d2ba6SAaro Koskinen return PTR_ERR(otg_dev->base);
108449d2ba6SAaro Koskinen
1092c2025b4SAaro Koskinen otg_dev->extcon = extcon;
110449d2ba6SAaro Koskinen otg_dev->id_nb.notifier_call = omap_otg_id_notifier;
111449d2ba6SAaro Koskinen otg_dev->vbus_nb.notifier_call = omap_otg_vbus_notifier;
112449d2ba6SAaro Koskinen
1137df33789SChanwoo Choi ret = devm_extcon_register_notifier(&pdev->dev, extcon,
1147df33789SChanwoo Choi EXTCON_USB_HOST, &otg_dev->id_nb);
115449d2ba6SAaro Koskinen if (ret)
116449d2ba6SAaro Koskinen return ret;
117449d2ba6SAaro Koskinen
1187df33789SChanwoo Choi ret = devm_extcon_register_notifier(&pdev->dev, extcon,
1197df33789SChanwoo Choi EXTCON_USB, &otg_dev->vbus_nb);
120449d2ba6SAaro Koskinen if (ret) {
121449d2ba6SAaro Koskinen return ret;
122449d2ba6SAaro Koskinen }
123449d2ba6SAaro Koskinen
1247df33789SChanwoo Choi otg_dev->id = extcon_get_state(extcon, EXTCON_USB_HOST);
1257df33789SChanwoo Choi otg_dev->vbus = extcon_get_state(extcon, EXTCON_USB);
126449d2ba6SAaro Koskinen omap_otg_set_mode(otg_dev);
127449d2ba6SAaro Koskinen
128449d2ba6SAaro Koskinen rev = readl(otg_dev->base);
129449d2ba6SAaro Koskinen
130449d2ba6SAaro Koskinen dev_info(&pdev->dev,
131449d2ba6SAaro Koskinen "OMAP USB OTG controller rev %d.%d (%s, id=%d, vbus=%d)\n",
132449d2ba6SAaro Koskinen (rev >> 4) & 0xf, rev & 0xf, config->extcon, otg_dev->id,
133449d2ba6SAaro Koskinen otg_dev->vbus);
134449d2ba6SAaro Koskinen
135ec57fcd0SWei Yongjun platform_set_drvdata(pdev, otg_dev);
136ec57fcd0SWei Yongjun
137449d2ba6SAaro Koskinen return 0;
138449d2ba6SAaro Koskinen }
139449d2ba6SAaro Koskinen
140449d2ba6SAaro Koskinen static struct platform_driver omap_otg_driver = {
141449d2ba6SAaro Koskinen .probe = omap_otg_probe,
142449d2ba6SAaro Koskinen .driver = {
143449d2ba6SAaro Koskinen .name = "omap_otg",
144449d2ba6SAaro Koskinen },
145449d2ba6SAaro Koskinen };
146449d2ba6SAaro Koskinen module_platform_driver(omap_otg_driver);
147449d2ba6SAaro Koskinen
148449d2ba6SAaro Koskinen MODULE_DESCRIPTION("OMAP USB OTG controller driver");
149449d2ba6SAaro Koskinen MODULE_LICENSE("GPL");
150449d2ba6SAaro Koskinen MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
151