Commit | Line | Data |
---|---|---|
5fd54ace | 1 | // SPDX-License-Identifier: GPL-2.0 |
449d2ba6 AK |
2 | /* |
3 | * OMAP OTG controller driver | |
4 | * | |
5 | * Based on code from tahvo-usb.c and isp1301_omap.c drivers. | |
6 | * | |
7 | * Copyright (C) 2005-2006 Nokia Corporation | |
8 | * Copyright (C) 2004 Texas Instruments | |
9 | * Copyright (C) 2004 David Brownell | |
10 | * | |
11 | * This file is subject to the terms and conditions of the GNU General | |
12 | * Public License. See the file "COPYING" in the main directory of this | |
13 | * archive for more details. | |
14 | * | |
15 | * This program is distributed in the hope that it will be useful, | |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | * GNU General Public License for more details. | |
19 | */ | |
20 | ||
21 | #include <linux/io.h> | |
22 | #include <linux/err.h> | |
23 | #include <linux/extcon.h> | |
24 | #include <linux/kernel.h> | |
25 | #include <linux/module.h> | |
26 | #include <linux/interrupt.h> | |
27 | #include <linux/platform_device.h> | |
28 | #include <linux/platform_data/usb-omap1.h> | |
29 | ||
30 | struct otg_device { | |
31 | void __iomem *base; | |
32 | bool id; | |
33 | bool vbus; | |
a2fd2423 | 34 | struct extcon_dev *extcon; |
449d2ba6 AK |
35 | struct notifier_block vbus_nb; |
36 | struct notifier_block id_nb; | |
37 | }; | |
38 | ||
39 | #define OMAP_OTG_CTRL 0x0c | |
40 | #define OMAP_OTG_ASESSVLD (1 << 20) | |
41 | #define OMAP_OTG_BSESSEND (1 << 19) | |
42 | #define OMAP_OTG_BSESSVLD (1 << 18) | |
43 | #define OMAP_OTG_VBUSVLD (1 << 17) | |
44 | #define OMAP_OTG_ID (1 << 16) | |
45 | #define OMAP_OTG_XCEIV_OUTPUTS \ | |
46 | (OMAP_OTG_ASESSVLD | OMAP_OTG_BSESSEND | OMAP_OTG_BSESSVLD | \ | |
47 | OMAP_OTG_VBUSVLD | OMAP_OTG_ID) | |
48 | ||
49 | static void omap_otg_ctrl(struct otg_device *otg_dev, u32 outputs) | |
50 | { | |
51 | u32 l; | |
52 | ||
53 | l = readl(otg_dev->base + OMAP_OTG_CTRL); | |
54 | l &= ~OMAP_OTG_XCEIV_OUTPUTS; | |
55 | l |= outputs; | |
56 | writel(l, otg_dev->base + OMAP_OTG_CTRL); | |
57 | } | |
58 | ||
59 | static void omap_otg_set_mode(struct otg_device *otg_dev) | |
60 | { | |
61 | if (!otg_dev->id && otg_dev->vbus) | |
62 | /* Set B-session valid. */ | |
63 | omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSVLD); | |
64 | else if (otg_dev->vbus) | |
65 | /* Set A-session valid. */ | |
66 | omap_otg_ctrl(otg_dev, OMAP_OTG_ASESSVLD); | |
67 | else if (!otg_dev->id) | |
68 | /* Set B-session end to indicate no VBUS. */ | |
69 | omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSEND); | |
70 | } | |
71 | ||
72 | static int omap_otg_id_notifier(struct notifier_block *nb, | |
73 | unsigned long event, void *ptr) | |
74 | { | |
75 | struct otg_device *otg_dev = container_of(nb, struct otg_device, id_nb); | |
76 | ||
77 | otg_dev->id = event; | |
78 | omap_otg_set_mode(otg_dev); | |
79 | ||
80 | return NOTIFY_DONE; | |
81 | } | |
82 | ||
83 | static int omap_otg_vbus_notifier(struct notifier_block *nb, | |
84 | unsigned long event, void *ptr) | |
85 | { | |
86 | struct otg_device *otg_dev = container_of(nb, struct otg_device, | |
87 | vbus_nb); | |
88 | ||
89 | otg_dev->vbus = event; | |
90 | omap_otg_set_mode(otg_dev); | |
91 | ||
92 | return NOTIFY_DONE; | |
93 | } | |
94 | ||
95 | static int omap_otg_probe(struct platform_device *pdev) | |
96 | { | |
97 | const struct omap_usb_config *config = pdev->dev.platform_data; | |
98 | struct otg_device *otg_dev; | |
99 | struct extcon_dev *extcon; | |
100 | int ret; | |
101 | u32 rev; | |
102 | ||
103 | if (!config || !config->extcon) | |
104 | return -ENODEV; | |
105 | ||
106 | extcon = extcon_get_extcon_dev(config->extcon); | |
107 | if (!extcon) | |
108 | return -EPROBE_DEFER; | |
109 | ||
110 | otg_dev = devm_kzalloc(&pdev->dev, sizeof(*otg_dev), GFP_KERNEL); | |
111 | if (!otg_dev) | |
112 | return -ENOMEM; | |
113 | ||
114 | otg_dev->base = devm_ioremap_resource(&pdev->dev, &pdev->resource[0]); | |
115 | if (IS_ERR(otg_dev->base)) | |
116 | return PTR_ERR(otg_dev->base); | |
117 | ||
2c2025b4 | 118 | otg_dev->extcon = extcon; |
449d2ba6 AK |
119 | otg_dev->id_nb.notifier_call = omap_otg_id_notifier; |
120 | otg_dev->vbus_nb.notifier_call = omap_otg_vbus_notifier; | |
121 | ||
7df33789 CC |
122 | ret = devm_extcon_register_notifier(&pdev->dev, extcon, |
123 | EXTCON_USB_HOST, &otg_dev->id_nb); | |
449d2ba6 AK |
124 | if (ret) |
125 | return ret; | |
126 | ||
7df33789 CC |
127 | ret = devm_extcon_register_notifier(&pdev->dev, extcon, |
128 | EXTCON_USB, &otg_dev->vbus_nb); | |
449d2ba6 | 129 | if (ret) { |
449d2ba6 AK |
130 | return ret; |
131 | } | |
132 | ||
7df33789 CC |
133 | otg_dev->id = extcon_get_state(extcon, EXTCON_USB_HOST); |
134 | otg_dev->vbus = extcon_get_state(extcon, EXTCON_USB); | |
449d2ba6 AK |
135 | omap_otg_set_mode(otg_dev); |
136 | ||
137 | rev = readl(otg_dev->base); | |
138 | ||
139 | dev_info(&pdev->dev, | |
140 | "OMAP USB OTG controller rev %d.%d (%s, id=%d, vbus=%d)\n", | |
141 | (rev >> 4) & 0xf, rev & 0xf, config->extcon, otg_dev->id, | |
142 | otg_dev->vbus); | |
143 | ||
ec57fcd0 WY |
144 | platform_set_drvdata(pdev, otg_dev); |
145 | ||
449d2ba6 AK |
146 | return 0; |
147 | } | |
148 | ||
449d2ba6 AK |
149 | static struct platform_driver omap_otg_driver = { |
150 | .probe = omap_otg_probe, | |
449d2ba6 | 151 | .driver = { |
449d2ba6 AK |
152 | .name = "omap_otg", |
153 | }, | |
154 | }; | |
155 | module_platform_driver(omap_otg_driver); | |
156 | ||
157 | MODULE_DESCRIPTION("OMAP USB OTG controller driver"); | |
158 | MODULE_LICENSE("GPL"); | |
159 | MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>"); |