From f82feb7f2788035d5ae1463ac8cc8acf0da3d23a Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Wed, 22 Dec 2021 10:55:06 +0100 Subject: usb: Modify Kconfig of the USB_EHCI_MXS to use this driver with imx28 The ehci-mxs driver can be also used with imx28 SoC, not only imx23. Signed-off-by: Lukasz Majewski --- drivers/usb/host/Kconfig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index ccecb5a..7743c96 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -180,12 +180,13 @@ config USB_EHCI_MX7 Enables support for the on-chip EHCI controller on i.MX7 SoCs. config USB_EHCI_MXS - bool "Support for i.MX23 EHCI USB controller" - depends on ARCH_MX23 + bool "Support for i.MX23/i.MX28 EHCI USB controller" + depends on ARCH_MX23 || ARCH_MX28 default y select USB_EHCI_IS_TDI help - Enables support for the on-chip EHCI controller on i.MX23 SoCs. + Enables support for the on-chip EHCI controller on i.MX23 and + i.MX28 SoCs. config USB_EHCI_OMAP bool "Support for OMAP3+ on-chip EHCI USB controller" -- cgit v1.1 From fc313d345a93a6a4edad62683191e11195a75e1e Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Wed, 22 Dec 2021 10:55:07 +0100 Subject: usb: ehci: Refactor the ehci_mxs_toggle_clock function to be reused with DM This function is going to be reused with the CONFIG_DM_USB enabled in the imx28 mxs USB ehci driver. No functional changes introduced. Signed-off-by: Lukasz Majewski --- drivers/usb/host/ehci-mxs.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/drivers/usb/host/ehci-mxs.c b/drivers/usb/host/ehci-mxs.c index 824c620..a9d5d58 100644 --- a/drivers/usb/host/ehci-mxs.c +++ b/drivers/usb/host/ehci-mxs.c @@ -29,6 +29,27 @@ struct ehci_mxs_port { uint32_t gate_bits; }; +static int ehci_mxs_toggle_clock(const struct ehci_mxs_port *port, int enable) +{ + struct mxs_register_32 *digctl_ctrl = + (struct mxs_register_32 *)HW_DIGCTL_CTRL; + int pll_offset, dig_offset; + + if (enable) { + pll_offset = offsetof(struct mxs_register_32, reg_set); + dig_offset = offsetof(struct mxs_register_32, reg_clr); + writel(port->gate_bits, (u32)&digctl_ctrl->reg + dig_offset); + writel(port->pll_en_bits, (u32)port->pll + pll_offset); + } else { + pll_offset = offsetof(struct mxs_register_32, reg_clr); + dig_offset = offsetof(struct mxs_register_32, reg_set); + writel(port->pll_dis_bits, (u32)port->pll + pll_offset); + writel(port->gate_bits, (u32)&digctl_ctrl->reg + dig_offset); + } + + return 0; +} + static const struct ehci_mxs_port mxs_port[] = { #ifdef CONFIG_EHCI_MXS_PORT0 { @@ -56,27 +77,6 @@ static const struct ehci_mxs_port mxs_port[] = { #endif }; -static int ehci_mxs_toggle_clock(const struct ehci_mxs_port *port, int enable) -{ - struct mxs_register_32 *digctl_ctrl = - (struct mxs_register_32 *)HW_DIGCTL_CTRL; - int pll_offset, dig_offset; - - if (enable) { - pll_offset = offsetof(struct mxs_register_32, reg_set); - dig_offset = offsetof(struct mxs_register_32, reg_clr); - writel(port->gate_bits, (u32)&digctl_ctrl->reg + dig_offset); - writel(port->pll_en_bits, (u32)port->pll + pll_offset); - } else { - pll_offset = offsetof(struct mxs_register_32, reg_clr); - dig_offset = offsetof(struct mxs_register_32, reg_set); - writel(port->pll_dis_bits, (u32)port->pll + pll_offset); - writel(port->gate_bits, (u32)&digctl_ctrl->reg + dig_offset); - } - - return 0; -} - int __weak board_ehci_hcd_init(int port) { return 0; -- cgit v1.1 From 2d431e33dca2d6b00c00b9c6cfb82bcfca00d3f9 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Wed, 22 Dec 2021 10:55:08 +0100 Subject: usb: ehci: Move common mxs code to separate functions (ehci_hcd_{stop|start}) Those functions will be re-used when the ehci MXS driver (for imx28) will be converted to also support CONFIG_DM_USB. No functional changes introduced - only cosmetic changes (u32 type) and alignment to pass checkpatch. Signed-off-by: Lukasz Majewski --- drivers/usb/host/ehci-mxs.c | 112 ++++++++++++++++++++++++-------------------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/drivers/usb/host/ehci-mxs.c b/drivers/usb/host/ehci-mxs.c index a9d5d58..aa32af1 100644 --- a/drivers/usb/host/ehci-mxs.c +++ b/drivers/usb/host/ehci-mxs.c @@ -50,6 +50,66 @@ static int ehci_mxs_toggle_clock(const struct ehci_mxs_port *port, int enable) return 0; } +static int __ehci_hcd_init(struct ehci_mxs_port *port, enum usb_init_type init, + struct ehci_hccr **hccr, struct ehci_hcor **hcor) +{ + u32 usb_base, cap_base; + int ret; + + /* Reset the PHY block */ + writel(USBPHY_CTRL_SFTRST, &port->phy_regs->hw_usbphy_ctrl_set); + udelay(10); + writel(USBPHY_CTRL_SFTRST | USBPHY_CTRL_CLKGATE, + &port->phy_regs->hw_usbphy_ctrl_clr); + + /* Enable USB clock */ + ret = ehci_mxs_toggle_clock(port, 1); + if (ret) + return ret; + + /* Start USB PHY */ + writel(0, &port->phy_regs->hw_usbphy_pwd); + + /* Enable UTMI+ Level 2 and Level 3 compatibility */ + writel(USBPHY_CTRL_ENUTMILEVEL3 | USBPHY_CTRL_ENUTMILEVEL2 | 1, + &port->phy_regs->hw_usbphy_ctrl_set); + + usb_base = port->usb_regs + 0x100; + *hccr = (struct ehci_hccr *)usb_base; + + cap_base = ehci_readl(&(*hccr)->cr_capbase); + *hcor = (struct ehci_hcor *)(usb_base + HC_LENGTH(cap_base)); + + return 0; +} + +static int __ehci_hcd_stop(struct ehci_mxs_port *port) +{ + u32 usb_base, cap_base, tmp; + struct ehci_hccr *hccr; + struct ehci_hcor *hcor; + + /* Stop the USB port */ + usb_base = port->usb_regs + 0x100; + hccr = (struct ehci_hccr *)usb_base; + cap_base = ehci_readl(&hccr->cr_capbase); + hcor = (struct ehci_hcor *)(usb_base + HC_LENGTH(cap_base)); + + tmp = ehci_readl(&hcor->or_usbcmd); + tmp &= ~CMD_RUN; + ehci_writel(&hcor->or_usbcmd, tmp); + + /* Disable the PHY */ + tmp = USBPHY_PWD_RXPWDRX | USBPHY_PWD_RXPWDDIFF | + USBPHY_PWD_RXPWD1PT1 | USBPHY_PWD_RXPWDENV | + USBPHY_PWD_TXPWDV2I | USBPHY_PWD_TXPWDIBIAS | + USBPHY_PWD_TXPWDFS; + writel(tmp, &port->phy_regs->hw_usbphy_pwd); + + /* Disable USB clock */ + return ehci_mxs_toggle_clock(port, 0); +} + static const struct ehci_mxs_port mxs_port[] = { #ifdef CONFIG_EHCI_MXS_PORT0 { @@ -92,7 +152,6 @@ int ehci_hcd_init(int index, enum usb_init_type init, { int ret; - uint32_t usb_base, cap_base; const struct ehci_mxs_port *port; if ((index < 0) || (index >= ARRAY_SIZE(mxs_port))) { @@ -105,40 +164,12 @@ int ehci_hcd_init(int index, enum usb_init_type init, return ret; port = &mxs_port[index]; - - /* Reset the PHY block */ - writel(USBPHY_CTRL_SFTRST, &port->phy_regs->hw_usbphy_ctrl_set); - udelay(10); - writel(USBPHY_CTRL_SFTRST | USBPHY_CTRL_CLKGATE, - &port->phy_regs->hw_usbphy_ctrl_clr); - - /* Enable USB clock */ - ret = ehci_mxs_toggle_clock(port, 1); - if (ret) - return ret; - - /* Start USB PHY */ - writel(0, &port->phy_regs->hw_usbphy_pwd); - - /* Enable UTMI+ Level 2 and Level 3 compatibility */ - writel(USBPHY_CTRL_ENUTMILEVEL3 | USBPHY_CTRL_ENUTMILEVEL2 | 1, - &port->phy_regs->hw_usbphy_ctrl_set); - - usb_base = port->usb_regs + 0x100; - *hccr = (struct ehci_hccr *)usb_base; - - cap_base = ehci_readl(&(*hccr)->cr_capbase); - *hcor = (struct ehci_hcor *)(usb_base + HC_LENGTH(cap_base)); - - return 0; + return __ehci_hcd_init(port, init, hccr, hcor); } int ehci_hcd_stop(int index) { int ret; - uint32_t usb_base, cap_base, tmp; - struct ehci_hccr *hccr; - struct ehci_hcor *hcor; const struct ehci_mxs_port *port; if ((index < 0) || (index >= ARRAY_SIZE(mxs_port))) { @@ -148,26 +179,7 @@ int ehci_hcd_stop(int index) port = &mxs_port[index]; - /* Stop the USB port */ - usb_base = port->usb_regs + 0x100; - hccr = (struct ehci_hccr *)usb_base; - cap_base = ehci_readl(&hccr->cr_capbase); - hcor = (struct ehci_hcor *)(usb_base + HC_LENGTH(cap_base)); - - tmp = ehci_readl(&hcor->or_usbcmd); - tmp &= ~CMD_RUN; - ehci_writel(&hcor->or_usbcmd, tmp); - - /* Disable the PHY */ - tmp = USBPHY_PWD_RXPWDRX | USBPHY_PWD_RXPWDDIFF | - USBPHY_PWD_RXPWD1PT1 | USBPHY_PWD_RXPWDENV | - USBPHY_PWD_TXPWDV2I | USBPHY_PWD_TXPWDIBIAS | - USBPHY_PWD_TXPWDFS; - writel(tmp, &port->phy_regs->hw_usbphy_pwd); - - /* Disable USB clock */ - ret = ehci_mxs_toggle_clock(port, 0); - + ret = __ehci_hcd_stop(port); board_ehci_hcd_exit(index); return ret; -- cgit v1.1 From 07791e8d059037630703857bf5d13d079a3310bb Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Wed, 22 Dec 2021 10:55:09 +0100 Subject: usb: ehci: dm: Convert i.MX28 ehci code to driver model This commit converts i.MX28's EHCI USB host driver to driver model (DM_USB). It is a straightforward conversion (to reuse as much code as possible), based on ehci-mx5.c code. Signed-off-by: Lukasz Majewski --- drivers/usb/host/ehci-mxs.c | 184 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/drivers/usb/host/ehci-mxs.c b/drivers/usb/host/ehci-mxs.c index aa32af1..9a61495 100644 --- a/drivers/usb/host/ehci-mxs.c +++ b/drivers/usb/host/ehci-mxs.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include "ehci.h" @@ -110,6 +112,7 @@ static int __ehci_hcd_stop(struct ehci_mxs_port *port) return ehci_mxs_toggle_clock(port, 0); } +#if !CONFIG_IS_ENABLED(DM_USB) static const struct ehci_mxs_port mxs_port[] = { #ifdef CONFIG_EHCI_MXS_PORT0 { @@ -184,3 +187,184 @@ int ehci_hcd_stop(int index) return ret; } +#else /* CONFIG_IS_ENABLED(DM_USB) */ +struct ehci_mxs_priv_data { + struct ehci_ctrl ctrl; + struct usb_ehci *ehci; + struct udevice *vbus_supply; + struct ehci_mxs_port port; + enum usb_init_type init_type; +}; + +/* + * Below defines correspond to imx28 clk Linux (v5.15.y) + * clock driver to provide proper offset for PHY[01] + * devices. + */ +#define CLK_USB_PHY0 62 +#define CLK_USB_PHY1 63 +#define PLL0CTRL0(base) ((base) + 0x0000) +#define PLL1CTRL0(base) ((base) + 0x0020) + +static int ehci_usb_ofdata_to_platdata(struct udevice *dev) +{ + struct ehci_mxs_priv_data *priv = dev_get_priv(dev); + struct usb_plat *plat = dev_get_plat(dev); + struct ehci_mxs_port *port = &priv->port; + u32 phandle, phy_reg, clk_reg, clk_id; + ofnode phy_node, clk_node; + const char *mode; + int ret; + + mode = ofnode_read_string(dev->node_, "dr_mode"); + if (mode) { + if (strcmp(mode, "peripheral") == 0) + plat->init_type = USB_INIT_DEVICE; + else if (strcmp(mode, "host") == 0) + plat->init_type = USB_INIT_HOST; + else + return -EINVAL; + } + + /* Read base address of the USB IP block */ + ret = ofnode_read_u32(dev->node_, "reg", &port->usb_regs); + if (ret) + return ret; + + /* Read base address of the USB PHY IP block */ + ret = ofnode_read_u32(dev->node_, "fsl,usbphy", &phandle); + if (ret) + return ret; + + phy_node = ofnode_get_by_phandle(phandle); + if (!ofnode_valid(phy_node)) + return -ENODEV; + + ret = ofnode_read_u32(phy_node, "reg", &phy_reg); + if (ret) + return ret; + + port->phy_regs = (struct mxs_usbphy_regs *)phy_reg; + + /* Read base address of the CLK IP block and proper ID */ + ret = ofnode_read_u32_index(phy_node, "clocks", 0, &phandle); + if (ret) + return ret; + + ret = ofnode_read_u32_index(phy_node, "clocks", 1, &clk_id); + if (ret) + return ret; + + clk_node = ofnode_get_by_phandle(phandle); + if (!ofnode_valid(clk_node)) + return -ENODEV; + + ret = ofnode_read_u32(clk_node, "reg", &clk_reg); + if (ret) + return ret; + + port->pll = (struct mxs_register_32 *)clk_reg; + + /* Provide proper offset for USB PHY clocks */ + if (clk_id == CLK_USB_PHY0) + port->pll = PLL0CTRL0(port->pll); + + if (clk_id == CLK_USB_PHY1) + port->pll = PLL1CTRL0(port->pll); + + debug("%s: pll_reg: 0x%p clk_id: %d\n", __func__, port->pll, clk_id); + /* + * On the imx28 the values provided by CLKCTRL_PLL0* defines to are the + * same as ones for CLKCTRL_PLL1*. As a result the former can be used + * for both ports - i.e. (usb[01]). + */ + port->pll_en_bits = CLKCTRL_PLL0CTRL0_EN_USB_CLKS | + CLKCTRL_PLL0CTRL0_POWER; + port->pll_dis_bits = CLKCTRL_PLL0CTRL0_EN_USB_CLKS; + port->gate_bits = HW_DIGCTL_CTRL_USB0_CLKGATE; + + return 0; +} + +static int ehci_usb_probe(struct udevice *dev) +{ + struct usb_plat *plat = dev_get_plat(dev); + struct usb_ehci *ehci = dev_read_addr_ptr(dev); + struct ehci_mxs_priv_data *priv = dev_get_priv(dev); + struct ehci_mxs_port *port = &priv->port; + enum usb_init_type type = plat->init_type; + struct ehci_hccr *hccr; + struct ehci_hcor *hcor; + int ret; + + priv->ehci = ehci; + priv->init_type = type; + + debug("%s: USB type: %s reg: 0x%x phy_reg 0x%p\n", __func__, + type == USB_INIT_HOST ? "HOST" : "DEVICE", port->usb_regs, + (uint32_t *)port->phy_regs); + +#if CONFIG_IS_ENABLED(DM_REGULATOR) + ret = device_get_supply_regulator(dev, "vbus-supply", + &priv->vbus_supply); + if (ret) + debug("%s: No vbus supply\n", dev->name); + + if (!ret && priv->vbus_supply) { + ret = regulator_set_enable(priv->vbus_supply, + (type == USB_INIT_DEVICE) ? + false : true); + if (ret) { + puts("Error enabling VBUS supply\n"); + return ret; + } + } +#endif + ret = __ehci_hcd_init(port, type, &hccr, &hcor); + if (ret) + return ret; + + mdelay(10); + return ehci_register(dev, hccr, hcor, NULL, 0, priv->init_type); +} + +static int ehci_usb_remove(struct udevice *dev) +{ + struct ehci_mxs_priv_data *priv = dev_get_priv(dev); + struct ehci_mxs_port *port = &priv->port; + int ret; + + ret = ehci_deregister(dev); + if (ret) + return ret; + +#if CONFIG_IS_ENABLED(DM_REGULATOR) + if (priv->vbus_supply) { + ret = regulator_set_enable(priv->vbus_supply, false); + if (ret) { + puts("Error disabling VBUS supply\n"); + return ret; + } + } +#endif + return __ehci_hcd_stop(port); +} + +static const struct udevice_id mxs_usb_ids[] = { + { .compatible = "fsl,imx28-usb" }, + { } +}; + +U_BOOT_DRIVER(usb_mxs) = { + .name = "ehci_mxs", + .id = UCLASS_USB, + .of_match = mxs_usb_ids, + .of_to_plat = ehci_usb_ofdata_to_platdata, + .probe = ehci_usb_probe, + .remove = ehci_usb_remove, + .ops = &ehci_usb_ops, + .plat_auto = sizeof(struct usb_plat), + .priv_auto = sizeof(struct ehci_mxs_priv_data), + .flags = DM_FLAG_ALLOC_PRIV_DMA, +}; +#endif /* !CONFIG_IS_ENABLED(DM_USB) */ -- cgit v1.1 From 334a9b9d6aff4f0efa5eef16b8a9e204c7a6c906 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Thu, 25 Nov 2021 18:16:14 +0100 Subject: lib/circbuf: Make circbuf selectable symbol It is currenly only used from usbtty driver but make it properly selectable via Kconfig symbol, for future usage. Signed-off-by: Loic Poulain --- lib/Kconfig | 3 +++ lib/Makefile | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/Kconfig b/lib/Kconfig index 38051cc..52d4b27 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -299,6 +299,9 @@ config TRACE_EARLY_ADDR the size is too small then the message which says the amount of early data being coped will the the same as the +config CIRCBUF + bool "Enable circular buffer support" + source lib/dhry/Kconfig menu "Security support" diff --git a/lib/Makefile b/lib/Makefile index 7950e84..f223892 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -29,7 +29,13 @@ ifneq ($(CONFIG_CHARSET),) obj-y += charset.o endif endif -obj-$(CONFIG_USB_TTY) += circbuf.o + +ifdef CONFIG_USB_TTY +obj-y += circbuf.o +else +obj-$(CONFIG_CIRCBUF) += circbuf.o +endif + obj-y += crc8.o obj-y += crc16.o obj-$(CONFIG_ERRNO_STR) += errno_str.o -- cgit v1.1 From fc2b399ac03b91339a1cb1bfd4d1a9ca87fe95c6 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Thu, 25 Nov 2021 18:16:15 +0100 Subject: usb: gadget: Add CDC ACM function Add support for CDC ACM using the new UDC and gadget API. This protocol can be used for serial over USB data transfer and is widely supported by various OS (GNU/Linux, MS-Windows, OSX...). The usual purpose of such link is to access device debug console and can be useful for products not exposing regular UART to the user. A default stdio device named 'usbacm' is created, and can be used to redirect console to USB link over CDC ACM: > setenv stdin usbacm; setenv stdout usbacm Signed-off-by: Loic Poulain --- common/stdio.c | 3 + drivers/usb/gadget/Kconfig | 9 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/f_acm.c | 672 ++++++++++++++++++++++++++++++++++++++++++++ include/stdio_dev.h | 1 + 5 files changed, 686 insertions(+) create mode 100644 drivers/usb/gadget/f_acm.c diff --git a/common/stdio.c b/common/stdio.c index 976f51c..063c659 100644 --- a/common/stdio.c +++ b/common/stdio.c @@ -381,6 +381,9 @@ int stdio_add_devices(void) #ifdef CONFIG_USB_TTY drv_usbtty_init(); #endif +#ifdef CONFIG_USB_FUNCTION_ACM + drv_usbacm_init (); +#endif if (IS_ENABLED(CONFIG_NETCONSOLE)) drv_nc_init(); #ifdef CONFIG_JTAG_CONSOLE diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 327ea86..d81a9c5 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -182,6 +182,15 @@ config USB_FUNCTION_THOR Enable Tizen's THOR download protocol support in U-Boot. It allows downloading images into memory and flash them to target device. +config USB_FUNCTION_ACM + bool "Enable CDC ACM gadget" + select SYS_STDIO_DEREGISTER + select CIRCBUF + help + ACM serial link. This function can be used to create a stdio device to + interoperate with MS-Windows hosts or with the Linux-USB "cdc-acm" + driver. + endif # USB_GADGET_DOWNLOAD config USB_ETHER diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index f560068..d5d891b 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_USB_FUNCTION_MASS_STORAGE) += f_mass_storage.o obj-$(CONFIG_USB_FUNCTION_FASTBOOT) += f_fastboot.o obj-$(CONFIG_USB_FUNCTION_SDP) += f_sdp.o obj-$(CONFIG_USB_FUNCTION_ROCKUSB) += f_rockusb.o +obj-$(CONFIG_USB_FUNCTION_ACM) += f_acm.o endif endif ifdef CONFIG_USB_ETHER diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c new file mode 100644 index 0000000..388f73d --- /dev/null +++ b/drivers/usb/gadget/f_acm.c @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB CDC serial (ACM) function driver + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + * Copyright (C) 2009 by Samsung Electronics + * Copyright (c) 2021, Linaro Ltd + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define REQ_SIZE_MAX 512 + +struct f_acm { + int ctrl_id; + int data_id; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + struct usb_ep *ep_notify; + + struct usb_request *req_in; + struct usb_request *req_out; + + bool connected; + bool tx_on; + + circbuf_t rx_buf; + circbuf_t tx_buf; + + struct usb_function usb_function; + + struct usb_cdc_line_coding line_coding; + u16 handshake_bits; +#define ACM_CTRL_RTS BIT(1) /* unused with full duplex */ +#define ACM_CTRL_DTR BIT(0) /* host is ready for data r/w */ + + int controller_index; +}; + +static struct f_acm *default_acm_function; + +static inline struct f_acm *func_to_acm(struct usb_function *f) +{ + return container_of(f, struct f_acm, usb_function); +} + +static inline struct f_acm *stdio_to_acm(struct stdio_dev *s) +{ + /* stdio dev is cloned on registration, do not use container_of */ + return s->priv; +} + +static struct usb_interface_assoc_descriptor +acm_iad_descriptor = { + .bLength = sizeof(acm_iad_descriptor), + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + .bFirstInterface = 0, + .bInterfaceCount = 2, // control + data + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, + .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER, +}; + +static struct usb_interface_descriptor acm_control_intf_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER, +}; + +static struct usb_interface_descriptor acm_data_intf_desc = { + .bLength = sizeof(acm_data_intf_desc), + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, +}; + +static struct usb_cdc_header_desc acm_header_desc = { + .bLength = sizeof(acm_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = __constant_cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor acm_call_mgmt_desc = { + .bLength = sizeof(acm_call_mgmt_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + .bmCapabilities = 0, + .bDataInterface = 0x01, +}; + +static struct usb_cdc_acm_descriptor acm_desc = { + .bLength = sizeof(acm_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = USB_CDC_CAP_LINE, +}; + +static struct usb_cdc_union_desc acm_union_desc = { + .bLength = sizeof(acm_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + .bMasterInterface0 = 0x00, + .bSlaveInterface0 = 0x01, +}; + +static struct usb_endpoint_descriptor acm_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 3 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 32, +}; + +static struct usb_endpoint_descriptor acm_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor acm_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *acm_fs_function[] = { + (struct usb_descriptor_header *)&acm_iad_descriptor, + (struct usb_descriptor_header *)&acm_control_intf_desc, + (struct usb_descriptor_header *)&acm_header_desc, + (struct usb_descriptor_header *)&acm_call_mgmt_desc, + (struct usb_descriptor_header *)&acm_desc, + (struct usb_descriptor_header *)&acm_union_desc, + (struct usb_descriptor_header *)&acm_fs_notify_desc, + (struct usb_descriptor_header *)&acm_data_intf_desc, + (struct usb_descriptor_header *)&acm_fs_in_desc, + (struct usb_descriptor_header *)&acm_fs_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor acm_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 11, +}; + +static struct usb_endpoint_descriptor acm_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acm_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_descriptor_header *acm_hs_function[] = { + (struct usb_descriptor_header *)&acm_iad_descriptor, + (struct usb_descriptor_header *)&acm_control_intf_desc, + (struct usb_descriptor_header *)&acm_header_desc, + (struct usb_descriptor_header *)&acm_call_mgmt_desc, + (struct usb_descriptor_header *)&acm_desc, + (struct usb_descriptor_header *)&acm_union_desc, + (struct usb_descriptor_header *)&acm_hs_notify_desc, + (struct usb_descriptor_header *)&acm_data_intf_desc, + (struct usb_descriptor_header *)&acm_hs_in_desc, + (struct usb_descriptor_header *)&acm_hs_out_desc, + NULL, +}; + +static inline struct usb_endpoint_descriptor * +ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *hs, + struct usb_endpoint_descriptor *fs) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return hs; + return fs; +} + +static int acm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_gadget *gadget = c->cdev->gadget; + struct f_acm *f_acm = func_to_acm(f); + struct usb_ep *ep; + int id; + + id = usb_interface_id(c, f); + if (id < 0) + return id; + + acm_iad_descriptor.bFirstInterface = id; + acm_control_intf_desc.bInterfaceNumber = id; + acm_union_desc.bMasterInterface0 = id; + + f_acm->ctrl_id = id; + + id = usb_interface_id(c, f); + if (id < 0) + return id; + + acm_data_intf_desc.bInterfaceNumber = id; + acm_union_desc.bSlaveInterface0 = id; + acm_call_mgmt_desc.bDataInterface = id; + + f_acm->data_id = id; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(gadget, &acm_fs_in_desc); + if (!ep) + return -ENODEV; + + f_acm->ep_in = ep; + + ep = usb_ep_autoconfig(gadget, &acm_fs_out_desc); + if (!ep) + return -ENODEV; + + f_acm->ep_out = ep; + + ep = usb_ep_autoconfig(gadget, &acm_fs_notify_desc); + if (!ep) + return -ENODEV; + + f_acm->ep_notify = ep; + + if (gadget_is_dualspeed(gadget)) { + /* Assume endpoint addresses are the same for both speeds */ + acm_hs_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress; + acm_hs_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress; + acm_hs_notify_desc.bEndpointAddress = acm_fs_notify_desc.bEndpointAddress; + } + + return 0; +} + +static void acm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_acm *f_acm = func_to_acm(f); + + if (default_acm_function == f_acm) + default_acm_function = NULL; + + buf_free(&f_acm->rx_buf); + buf_free(&f_acm->tx_buf); + + free(f_acm); +} + +static void acm_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* nothing to do */ +} + +static void acm_tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_acm *f_acm = req->context; + + f_acm->tx_on = true; +} + +static void acm_rx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_acm *f_acm = req->context; + + buf_push(&f_acm->rx_buf, req->buf, req->actual); + + /* Queue RX req again */ + req->actual = 0; + usb_ep_queue(ep, req, 0); +} + +static struct usb_request *acm_start_ep(struct usb_ep *ep, void *complete_cb, + void *context) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, 0); + if (!req) + return NULL; + + req->length = REQ_SIZE_MAX; + req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, REQ_SIZE_MAX); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + memset(req->buf, 0, req->length); + req->complete = complete_cb; + req->context = context; + + return req; +} + +static int acm_start_data(struct f_acm *f_acm, struct usb_gadget *gadget) +{ + const struct usb_endpoint_descriptor *d; + int ret; + + /* EP IN */ + d = ep_desc(gadget, &acm_hs_in_desc, &acm_fs_in_desc); + ret = usb_ep_enable(f_acm->ep_in, d); + if (ret) + return ret; + + f_acm->req_in = acm_start_ep(f_acm->ep_in, acm_tx_complete, f_acm); + + /* EP OUT */ + d = ep_desc(gadget, &acm_hs_out_desc, &acm_fs_out_desc); + ret = usb_ep_enable(f_acm->ep_out, d); + if (ret) + return ret; + + f_acm->req_out = acm_start_ep(f_acm->ep_out, acm_rx_complete, f_acm); + + /* Start OUT transfer (EP OUT) */ + ret = usb_ep_queue(f_acm->ep_out, f_acm->req_out, 0); + if (ret) + return ret; + + return 0; +} + +static int acm_start_ctrl(struct f_acm *f_acm, struct usb_gadget *gadget) +{ + const struct usb_endpoint_descriptor *d; + + usb_ep_disable(f_acm->ep_notify); + + d = ep_desc(gadget, &acm_hs_notify_desc, &acm_fs_notify_desc); + usb_ep_enable(f_acm->ep_notify, d); + + acm_start_ep(f_acm->ep_notify, acm_notify_complete, f_acm); + + return 0; +} + +static int acm_set_alt(struct usb_function *f, unsigned int intf, unsigned int alt) +{ + struct usb_gadget *gadget = f->config->cdev->gadget; + struct f_acm *f_acm = func_to_acm(f); + + if (intf == f_acm->ctrl_id) { + return acm_start_ctrl(f_acm, gadget); + } else if (intf == f_acm->data_id) { + acm_start_data(f_acm, gadget); + f_acm->connected = true; + f_acm->tx_on = true; + return 0; + } + + return -EINVAL; +} + +static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_gadget *gadget = f->config->cdev->gadget; + struct usb_request *req = f->config->cdev->req; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + struct f_acm *f_acm = func_to_acm(f); + int value = -1; + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_LINE_CODING: + /* SET_LINE_CODING */ + + if (w_length != sizeof(f_acm->line_coding) || w_index != f_acm->ctrl_id) + goto invalid; + + value = w_length; + + memcpy(&f_acm->line_coding, req->buf, value); + + break; + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_GET_LINE_CODING: + /* GET_LINE_CODING */ + + if (w_length != sizeof(f_acm->line_coding) || w_index != f_acm->ctrl_id) + goto invalid; + + value = w_length; + + memcpy(req->buf, &f_acm->line_coding, value); + + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + /* SET_CONTROL_LINE_STATE */ + + if (w_index != f_acm->ctrl_id) + goto invalid; + + value = 0; + + f_acm->handshake_bits = w_value; + + break; + default: +invalid: + printf("invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, w_value, w_index, + w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + req->zero = 0; + req->length = value; + usb_ep_queue(gadget->ep0, req, GFP_ATOMIC); + } + + return 0; +} + +static void acm_disable(struct usb_function *f) +{ + struct f_acm *f_acm = func_to_acm(f); + + usb_ep_disable(f_acm->ep_out); + usb_ep_disable(f_acm->ep_in); + usb_ep_disable(f_acm->ep_notify); + + if (f_acm->req_out) { + free(f_acm->req_out->buf); + usb_ep_free_request(f_acm->ep_out, f_acm->req_out); + f_acm->req_out = NULL; + } + + if (f_acm->req_in) { + free(f_acm->req_in->buf); + usb_ep_free_request(f_acm->ep_in, f_acm->req_in); + f_acm->req_in = NULL; + } +} + +/* static strings, in UTF-8 */ +static struct usb_string acm_string_defs[] = { + [0].s = "CDC Abstract Control Model (ACM)", + [1].s = "CDC ACM Data", + [2].s = "CDC Serial", + { } /* end of list */ +}; + +static struct usb_gadget_strings acm_string_table = { + .language = 0x0409, /* en-us */ + .strings = acm_string_defs, +}; + +static struct usb_gadget_strings *acm_strings[] = { + &acm_string_table, + NULL, +}; + +static void __acm_tx(struct f_acm *f_acm) +{ + int len, ret; + + do { + usb_gadget_handle_interrupts(f_acm->controller_index); + + if (!(f_acm->handshake_bits & ACM_CTRL_DTR)) + break; + + if (!f_acm->tx_on) + continue; + + len = buf_pop(&f_acm->tx_buf, f_acm->req_in->buf, REQ_SIZE_MAX); + if (!len) + break; + + f_acm->req_in->length = len; + + ret = usb_ep_queue(f_acm->ep_in, f_acm->req_in, 0); + if (ret) + break; + + f_acm->tx_on = false; + + /* Do not reset the watchdog, if TX is stuck there is probably + * a real issue. + */ + } while (1); +} + +static bool acm_connected(struct stdio_dev *dev) +{ + struct f_acm *f_acm = stdio_to_acm(dev); + + /* give a chance to process udc irq */ + usb_gadget_handle_interrupts(f_acm->controller_index); + + return f_acm->connected; +} + +static int acm_add(struct usb_configuration *c) +{ + struct f_acm *f_acm; + int status; + + f_acm = calloc(1, sizeof(*f_acm)); + if (!f_acm) + return -ENOMEM; + + f_acm->usb_function.name = "f_acm"; + f_acm->usb_function.bind = acm_bind; + f_acm->usb_function.unbind = acm_unbind; + f_acm->usb_function.set_alt = acm_set_alt; + f_acm->usb_function.disable = acm_disable; + f_acm->usb_function.strings = acm_strings; + f_acm->usb_function.descriptors = acm_fs_function; + f_acm->usb_function.hs_descriptors = acm_hs_function; + f_acm->usb_function.setup = acm_setup; + f_acm->controller_index = 0; + + status = usb_add_function(c, &f_acm->usb_function); + if (status) { + free(f_acm); + return status; + } + + buf_init(&f_acm->rx_buf, 2048); + buf_init(&f_acm->tx_buf, 2048); + + if (!default_acm_function) + default_acm_function = f_acm; + + return status; +} + +DECLARE_GADGET_BIND_CALLBACK(usb_serial_acm, acm_add); + +/* STDIO */ +static int acm_stdio_tstc(struct stdio_dev *dev) +{ + struct f_acm *f_acm = stdio_to_acm(dev); + + usb_gadget_handle_interrupts(f_acm->controller_index); + + return (f_acm->rx_buf.size > 0); +} + +static int acm_stdio_getc(struct stdio_dev *dev) +{ + struct f_acm *f_acm = stdio_to_acm(dev); + char c; + + /* Wait for a character to arrive. */ + while (!acm_stdio_tstc(dev)) + WATCHDOG_RESET(); + + buf_pop(&f_acm->rx_buf, &c, 1); + + return c; +} + +static void acm_stdio_putc(struct stdio_dev *dev, const char c) +{ + struct f_acm *f_acm = stdio_to_acm(dev); + + if (c == '\n') + buf_push(&f_acm->tx_buf, "\r", 1); + + buf_push(&f_acm->tx_buf, &c, 1); + + if (!f_acm->connected) + return; + + __acm_tx(f_acm); +} + +static void acm_stdio_puts(struct stdio_dev *dev, const char *str) +{ + struct f_acm *f_acm = stdio_to_acm(dev); + + while (*str) { + if (*str == '\n') + buf_push(&f_acm->tx_buf, "\r", 1); + + buf_push(&f_acm->tx_buf, str++, 1); + } + + if (!f_acm->connected) + return; + + __acm_tx(f_acm); +} + +static int acm_stdio_start(struct stdio_dev *dev) +{ + int ret; + + if (dev->priv) { /* function already exist */ + return 0; + } + + ret = g_dnl_register("usb_serial_acm"); + if (ret) + return ret; + + if (default_acm_function) + dev->priv = default_acm_function; + else + return -ENODEV; + + while (!acm_connected(dev)) { + if (ctrlc()) + return -ECANCELED; + + WATCHDOG_RESET(); + } + + return 0; +} + +static int acm_stdio_stop(struct stdio_dev *dev) +{ + g_dnl_unregister(); + g_dnl_clear_detach(); + + return 0; +} + +int drv_usbacm_init(void) +{ + struct stdio_dev stdio; + + strcpy(stdio.name, "usbacm"); + stdio.flags = DEV_FLAGS_INPUT | DEV_FLAGS_OUTPUT; + stdio.tstc = acm_stdio_tstc; + stdio.getc = acm_stdio_getc; + stdio.putc = acm_stdio_putc; + stdio.puts = acm_stdio_puts; + stdio.start = acm_stdio_start; + stdio.stop = acm_stdio_stop; + stdio.priv = NULL; + stdio.ext = 0; + + return stdio_register(&stdio); +} diff --git a/include/stdio_dev.h b/include/stdio_dev.h index 8fb9a12..270fa27 100644 --- a/include/stdio_dev.h +++ b/include/stdio_dev.h @@ -103,6 +103,7 @@ int drv_lcd_init(void); int drv_video_init(void); int drv_keyboard_init(void); int drv_usbtty_init(void); +int drv_usbacm_init(void); int drv_nc_init(void); int drv_jtag_console_init(void); int cbmemc_init(void); -- cgit v1.1