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