blob: 16201480b43b17120109c5575993ff3176f2a70e [file] [log] [blame]
Nathan Barrett-Morrison4db018a2025-02-26 12:30:27 -05001// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * (C) Copyright 2022 - Analog Devices, Inc.
4 *
5 * ADI SC5XX MUSB "glue layer"
6 *
7 * Written and/or maintained by Timesys Corporation
8 *
9 * Loosely ported from Linux driver:
10 * Author: Nathan Barrett-Morrison <nathan.morrison@timesys.com>
11 *
12 */
13
14#include <dm.h>
15#include <dm/device_compat.h>
16#include <linux/bitops.h>
17#include <linux/io.h>
18#include <linux/usb/musb.h>
19#include "linux-compat.h"
20#include "musb_core.h"
21#include "musb_uboot.h"
22
23#define MUSB_SOFTRST 0x7f
24#define MUSB_SOFTRST_NRST BIT(0)
25#define MUSB_SOFTRST_NRSTX BIT(1)
26
27#define REG_USB_VBUS_CTL 0x380
28#define REG_USB_ID_CTL 0x382
29#define REG_USB_PHY_CTL 0x394
30#define REG_USB_PLL_OSC 0x398
31#define REG_USB_UTMI_CTL 0x39c
32
33/* controller data */
34struct sc5xx_musb_data {
35 struct musb_host_data mdata;
36 struct device dev;
37};
38
39#define to_sc5xx_musb_data(d) \
40 container_of(d, struct sc5xx_musb_data, dev)
41
42static void sc5xx_musb_disable(struct musb *musb)
43{
44 /* no way to shut the controller */
45}
46
47static int sc5xx_musb_enable(struct musb *musb)
48{
49 /* soft reset by NRSTx */
50 musb_writeb(musb->mregs, MUSB_SOFTRST, MUSB_SOFTRST_NRSTX);
51 /* set mode */
52 musb_platform_set_mode(musb, musb->board_mode);
53
54 return 0;
55}
56
57static irqreturn_t sc5xx_interrupt(int irq, void *hci)
58{
59 struct musb *musb = hci;
60 irqreturn_t ret = IRQ_NONE;
61 u8 devctl;
62
63 musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
64 musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
65 musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
66
67 if (musb->int_usb & MUSB_INTR_VBUSERROR) {
68 musb->int_usb &= ~MUSB_INTR_VBUSERROR;
69 devctl = musb_readw(musb->mregs, MUSB_DEVCTL);
70 devctl |= MUSB_DEVCTL_SESSION;
71 musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
72 }
73 if (musb->int_usb || musb->int_tx || musb->int_rx) {
74 musb_writeb(musb->mregs, MUSB_INTRUSB, musb->int_usb);
75 musb_writew(musb->mregs, MUSB_INTRTX, musb->int_tx);
76 musb_writew(musb->mregs, MUSB_INTRRX, musb->int_rx);
77 ret = musb_interrupt(musb);
78 }
79
80 if (musb->int_usb & MUSB_INTR_DISCONNECT && is_host_active(musb))
81 musb_writeb(musb->mregs, REG_USB_VBUS_CTL, 0x0);
82
83 return ret;
84}
85
86static int sc5xx_musb_set_mode(struct musb *musb, u8 mode)
87{
88 struct device *dev = musb->controller;
89 struct sc5xx_musb_data *pdata = to_sc5xx_musb_data(dev);
90
91 switch (mode) {
92 case MUSB_HOST:
93 musb_writeb(musb->mregs, REG_USB_ID_CTL, 0x1);
94 break;
95 case MUSB_PERIPHERAL:
96 musb_writeb(musb->mregs, REG_USB_ID_CTL, 0x3);
97 break;
98 case MUSB_OTG:
99 musb_writeb(musb->mregs, REG_USB_ID_CTL, 0x0);
100 break;
101 default:
102 dev_err(dev, "unsupported mode %d\n", mode);
103 return -EINVAL;
104 }
105
106 return 0;
107}
108
109static int sc5xx_musb_init(struct musb *musb)
110{
111 struct sc5xx_musb_data *pdata = to_sc5xx_musb_data(musb->controller);
112
113 musb->isr = sc5xx_interrupt;
114
115 musb_writel(musb->mregs, REG_USB_PLL_OSC, 20 << 1);
116 musb_writeb(musb->mregs, REG_USB_VBUS_CTL, 0x0);
117 musb_writeb(musb->mregs, REG_USB_PHY_CTL, 0x80);
118 musb_writel(musb->mregs, REG_USB_UTMI_CTL,
119 0x40 | musb_readl(musb->mregs, REG_USB_UTMI_CTL));
120
121 return 0;
122}
123
124const struct musb_platform_ops sc5xx_musb_ops = {
125 .init = sc5xx_musb_init,
126 .set_mode = sc5xx_musb_set_mode,
127 .disable = sc5xx_musb_disable,
128 .enable = sc5xx_musb_enable,
129};
130
131static struct musb_hdrc_config sc5xx_musb_config = {
132 .multipoint = 1,
133 .dyn_fifo = 1,
134 .num_eps = 16,
135 .ram_bits = 12,
136};
137
138/* has one MUSB controller which can be host or gadget */
139static struct musb_hdrc_platform_data sc5xx_musb_plat = {
140 .mode = MUSB_HOST,
141 .config = &sc5xx_musb_config,
142 .power = 100,
143 .platform_ops = &sc5xx_musb_ops,
144};
145
146static int musb_usb_probe(struct udevice *dev)
147{
148 struct usb_bus_priv *priv = dev_get_uclass_priv(dev);
149 struct sc5xx_musb_data *pdata = dev_get_priv(dev);
150 struct musb_host_data *mdata = &pdata->mdata;
151 void __iomem *mregs;
152 int ret;
153
154 priv->desc_before_addr = true;
155
156 mregs = dev_remap_addr(dev);
157 if (!mregs)
158 return -EINVAL;
159
160 /* init controller */
161 if (IS_ENABLED(CONFIG_USB_MUSB_HOST)) {
162 mdata->host = musb_init_controller(&sc5xx_musb_plat,
163 &pdata->dev, mregs);
164 if (!mdata->host)
165 return -EIO;
166
167 ret = musb_lowlevel_init(mdata);
168 } else {
169 sc5xx_musb_plat.mode = MUSB_PERIPHERAL;
170 mdata->host = musb_register(&sc5xx_musb_plat, &pdata->dev, mregs);
171 if (!mdata->host)
172 return -EIO;
173 }
174 return ret;
175}
176
177static int musb_usb_remove(struct udevice *dev)
178{
179 struct sc5xx_musb_data *pdata = dev_get_priv(dev);
180
181 musb_stop(pdata->mdata.host);
182
183 return 0;
184}
185
186static const struct udevice_id sc5xx_musb_ids[] = {
187 { .compatible = "adi,sc5xx-musb" },
188 { }
189};
190
191U_BOOT_DRIVER(usb_musb) = {
192 .name = "sc5xx-musb",
193 .id = UCLASS_USB,
194 .of_match = sc5xx_musb_ids,
195 .probe = musb_usb_probe,
196 .remove = musb_usb_remove,
197#ifdef CONFIG_USB_MUSB_HOST
198 .ops = &musb_usb_ops,
199#endif
200 .plat_auto = sizeof(struct usb_plat),
201 .priv_auto = sizeof(struct sc5xx_musb_data),
202};