diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2009-04-07 11:02:14 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2009-04-07 11:02:14 +0200 |
commit | 1be769ab303b8194a3c1a38411ab06585ec86c73 (patch) | |
tree | 8f480fe2fcd49a2d65e0381a40642b3148b499c4 | |
parent | e404e46b326eb6a648377103c9d9b8b6c2d8c3b8 (diff) | |
parent | 555cd1099e28bf6c1759f85ac928da725c367bc9 (diff) | |
download | barebox-1be769ab303b8194a3c1a38411ab06585ec86c73.tar.gz barebox-1be769ab303b8194a3c1a38411ab06585ec86c73.tar.xz |
Merge branch 'usb'
-rw-r--r-- | arch/arm/Kconfig | 1 | ||||
-rw-r--r-- | board/pcm037/pcm037.c | 110 | ||||
-rw-r--r-- | drivers/Kconfig | 1 | ||||
-rw-r--r-- | drivers/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/Kconfig | 2 | ||||
-rw-r--r-- | drivers/net/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/miiphy.c | 10 | ||||
-rw-r--r-- | drivers/net/usb/Kconfig | 10 | ||||
-rw-r--r-- | drivers/net/usb/Makefile | 2 | ||||
-rw-r--r-- | drivers/net/usb/asix.c | 786 | ||||
-rw-r--r-- | drivers/net/usb/usbnet.c | 252 | ||||
-rw-r--r-- | drivers/usb/Kconfig | 16 | ||||
-rw-r--r-- | drivers/usb/Makefile | 4 | ||||
-rw-r--r-- | drivers/usb/isp1504.c | 37 | ||||
-rw-r--r-- | drivers/usb/ulpi.c | 118 | ||||
-rw-r--r-- | drivers/usb/usb.c | 1397 | ||||
-rw-r--r-- | drivers/usb/usb_ehci.h | 194 | ||||
-rw-r--r-- | drivers/usb/usb_ehci_core.c | 937 | ||||
-rw-r--r-- | drivers/usb/usb_ehci_core.h | 29 | ||||
-rw-r--r-- | include/driver.h | 15 | ||||
-rw-r--r-- | include/miiphy.h | 1 | ||||
-rw-r--r-- | include/net.h | 3 | ||||
-rw-r--r-- | lib/Makefile | 1 | ||||
-rw-r--r-- | lib/bus.c | 58 | ||||
-rw-r--r-- | lib/driver.c | 28 | ||||
-rw-r--r-- | net/eth.c | 17 |
26 files changed, 4023 insertions, 8 deletions
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 5fd843ab18..22f25bb8ce 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -163,6 +163,7 @@ config MACH_PCM037 bool "phyCORE-i.MX31" select MACH_HAS_LOWLEVEL_INIT select ARCH_IMX31 + select USB_ISP1504 if USB help Say Y here if you are using Phytec's phyCORE-i.MX31 (pcm037) equipped with a Freescale i.MX31 Processor diff --git a/board/pcm037/pcm037.c b/board/pcm037/pcm037.c index fa1c15d93f..a303e506e9 100644 --- a/board/pcm037/pcm037.c +++ b/board/pcm037/pcm037.c @@ -26,6 +26,7 @@ #include <init.h> #include <driver.h> #include <environment.h> +#include <usb/isp1504.h> #include <asm/arch/imx-regs.h> #include <asm/arch/iomux-mx31.h> #include <asm/armlinux.h> @@ -35,6 +36,7 @@ #include <asm/mach-types.h> #include <asm/arch/imx-nand.h> + /* * Up to 32MiB NOR type flash, connected to * CS line 0, data width is 16 bit @@ -116,6 +118,109 @@ static struct device_d nand_dev = { .platform_data = &nand_info, }; +#ifdef CONFIG_USB +static struct device_d usbotg_dev = { + .name = "ehci", + .id = "ehci0", + .map_base = IMX_OTG_BASE, + .size = 0x200, +}; + +static struct device_d usbh2_dev = { + .name = "ehci", + .id = "ehci1", + .map_base = IMX_OTG_BASE + 0x400, + .size = 0x200, +}; + +static void pcm037_usb_init(void) +{ + u32 tmp; + + /* enable clock */ + tmp = __raw_readl(0x53f80000); + tmp |= (1 << 9); + __raw_writel(tmp, 0x53f80000); + + /* Host 1 */ + tmp = readl(IMX_OTG_BASE + 0x600); + tmp &= ~((3 << 21) | 1); + tmp |= (1 << 5) | (1 << 16) | (1 << 19) | (1 << 11) | (1 << 20); + writel(tmp, IMX_OTG_BASE + 0x600); + + tmp = readl(IMX_OTG_BASE + 0x184); + tmp &= ~(3 << 30); + tmp |= 2 << 30; + writel(tmp, IMX_OTG_BASE + 0x184); + + imx_iomux_mode(MX31_PIN_USBOTG_DATA0__USBOTG_DATA0); + imx_iomux_mode(MX31_PIN_USBOTG_DATA1__USBOTG_DATA1); + imx_iomux_mode(MX31_PIN_USBOTG_DATA2__USBOTG_DATA2); + imx_iomux_mode(MX31_PIN_USBOTG_DATA3__USBOTG_DATA3); + imx_iomux_mode(MX31_PIN_USBOTG_DATA4__USBOTG_DATA4); + imx_iomux_mode(MX31_PIN_USBOTG_DATA5__USBOTG_DATA5); + imx_iomux_mode(MX31_PIN_USBOTG_DATA6__USBOTG_DATA6); + imx_iomux_mode(MX31_PIN_USBOTG_DATA7__USBOTG_DATA7); + imx_iomux_mode(MX31_PIN_USBOTG_CLK__USBOTG_CLK); + imx_iomux_mode(MX31_PIN_USBOTG_DIR__USBOTG_DIR); + imx_iomux_mode(MX31_PIN_USBOTG_NXT__USBOTG_NXT); + imx_iomux_mode(MX31_PIN_USBOTG_STP__USBOTG_STP); + + mdelay(50); + isp1504_set_vbus_power((void *)(IMX_OTG_BASE + 0x170), 1); + + /* Host 2 */ + tmp = readl(IOMUXC_BASE + 0x8); + tmp |= 1 << 11; + writel(tmp, IOMUXC_BASE + 0x8); + + imx_iomux_mode(IOMUX_MODE(MX31_PIN_USBH2_CLK, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_USBH2_DIR, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_USBH2_NXT, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_USBH2_STP, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_USBH2_DATA0, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_USBH2_DATA1, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_STXD3, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_SRXD3, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_SCK3, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_SFS3, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_STXD6, IOMUX_CONFIG_FUNC)); + imx_iomux_mode(IOMUX_MODE(MX31_PIN_SRXD6, IOMUX_CONFIG_FUNC)); + +#define H2_PAD_CFG (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST | PAD_CTL_HYS_CMOS | PAD_CTL_ODE_CMOS | PAD_CTL_100K_PU) + imx_iomux_set_pad(MX31_PIN_USBH2_CLK, H2_PAD_CFG); + imx_iomux_set_pad(MX31_PIN_USBH2_DIR, H2_PAD_CFG); + imx_iomux_set_pad(MX31_PIN_USBH2_NXT, H2_PAD_CFG); + imx_iomux_set_pad(MX31_PIN_USBH2_STP, H2_PAD_CFG); + imx_iomux_set_pad(MX31_PIN_USBH2_DATA0, H2_PAD_CFG); /* USBH2_DATA0 */ + imx_iomux_set_pad(MX31_PIN_USBH2_DATA1, H2_PAD_CFG); /* USBH2_DATA1 */ + imx_iomux_set_pad(MX31_PIN_SRXD6, H2_PAD_CFG); /* USBH2_DATA2 */ + imx_iomux_set_pad(MX31_PIN_STXD6, H2_PAD_CFG); /* USBH2_DATA3 */ + imx_iomux_set_pad(MX31_PIN_SFS3, H2_PAD_CFG); /* USBH2_DATA4 */ + imx_iomux_set_pad(MX31_PIN_SCK3, H2_PAD_CFG); /* USBH2_DATA5 */ + imx_iomux_set_pad(MX31_PIN_SRXD3, H2_PAD_CFG); /* USBH2_DATA6 */ + imx_iomux_set_pad(MX31_PIN_STXD3, H2_PAD_CFG); /* USBH2_DATA7 */ + + tmp = readl(IMX_OTG_BASE + 0x600); + tmp &= ~((3 << 21) | 1); + tmp |= (1 << 5) | (1 << 16) | (1 << 19) | (1 << 20); + writel(tmp, IMX_OTG_BASE + 0x600); + + tmp = readl(IMX_OTG_BASE + 0x584); + tmp &= ~(3 << 30); + tmp |= 2 << 30; + writel(tmp, IMX_OTG_BASE + 0x584); + + mdelay(50); + isp1504_set_vbus_power((void *)(IMX_OTG_BASE + 0x570), 1); + + /* Set to Host mode */ + tmp = readl(IMX_OTG_BASE + 0x1a8); + writel(tmp | 0x3, IMX_OTG_BASE + 0x1a8); + +} +#endif + static int imx31_devices_init(void) { __REG(CSCR_U(0)) = 0x0000cf03; /* CS0: Nor Flash */ @@ -154,6 +259,11 @@ static int imx31_devices_init(void) #ifndef CONFIG_PCM037_SDRAM_BANK1_NONE register_device(&sdram1_dev); #endif +#ifdef CONFIG_USB + pcm037_usb_init(); + register_device(&usbotg_dev); + register_device(&usbh2_dev); +#endif armlinux_set_bootparams((void *)0x80000100); armlinux_set_architecture(MACH_TYPE_PCM037); diff --git a/drivers/Kconfig b/drivers/Kconfig index 1e9dae366b..72e25cf236 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -5,5 +5,6 @@ source "drivers/net/Kconfig" source "drivers/spi/Kconfig" source "drivers/nor/Kconfig" source "drivers/nand/Kconfig" +source "drivers/usb/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 686998c814..6ff0b4de87 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -2,4 +2,5 @@ obj-y += net/ obj-y += serial/ obj-y += nand/ obj-y += nor/ +obj-y += usb/ obj-$(CONFIG_SPI) += spi/ diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 6a9190c8e2..c080eedbab 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -66,5 +66,7 @@ config DRIVER_NET_TAP bool "tap Ethernet driver" depends on LINUX +source "drivers/net/usb/Kconfig" + endmenu diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 091bd241bb..a5895c36dd 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_DRIVER_NET_FEC_IMX) += fec_imx.o obj-$(CONFIG_DRIVER_NET_MACB) += macb.o obj-$(CONFIG_DRIVER_NET_TAP) += tap.o obj-$(CONFIG_MIIPHY) += miiphy.o +obj-$(CONFIG_NET_USB) += usb/ diff --git a/drivers/net/miiphy.c b/drivers/net/miiphy.c index 0f1090926b..a82ef75b59 100644 --- a/drivers/net/miiphy.c +++ b/drivers/net/miiphy.c @@ -159,6 +159,10 @@ static int miiphy_probe(struct device_d *dev) return 0; } +static void miiphy_remove(struct device_d *dev) +{ +} + int miiphy_register(struct miiphy_device *mdev) { strcpy(mdev->dev.name, "miiphy"); @@ -170,9 +174,15 @@ int miiphy_register(struct miiphy_device *mdev) return register_device(&mdev->dev); } +void miiphy_unregister(struct miiphy_device *mdev) +{ + unregister_device(&mdev->dev); +} + static struct driver_d miiphy_drv = { .name = "miiphy", .probe = miiphy_probe, + .remove = miiphy_remove, .open = dev_open_default, .close = dev_close_default, .read = miiphy_read, diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig new file mode 100644 index 0000000000..d0e01f3c83 --- /dev/null +++ b/drivers/net/usb/Kconfig @@ -0,0 +1,10 @@ +menuconfig NET_USB + depends on USB + bool "USB network support" + +if NET_USB + +config NET_USB_ASIX + bool "Asix compatible" + +endif diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile new file mode 100644 index 0000000000..555f8c2f1f --- /dev/null +++ b/drivers/net/usb/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_NET_USB) += usbnet.o +obj-$(CONFIG_NET_USB_ASIX) += asix.o diff --git a/drivers/net/usb/asix.c b/drivers/net/usb/asix.c new file mode 100644 index 0000000000..b5acb0c2ac --- /dev/null +++ b/drivers/net/usb/asix.c @@ -0,0 +1,786 @@ +#include <common.h> +#include <init.h> +#include <net.h> +#include <miiphy.h> +#include <usb/usb.h> +#include <usb/usbnet.h> +#include <errno.h> +#include <malloc.h> +#include <asm/byteorder.h> + +/* ASIX AX8817X based USB 2.0 Ethernet Devices */ + +#define AX_CMD_SET_SW_MII 0x06 +#define AX_CMD_READ_MII_REG 0x07 +#define AX_CMD_WRITE_MII_REG 0x08 +#define AX_CMD_SET_HW_MII 0x0a +#define AX_CMD_READ_EEPROM 0x0b +#define AX_CMD_WRITE_EEPROM 0x0c +#define AX_CMD_WRITE_ENABLE 0x0d +#define AX_CMD_WRITE_DISABLE 0x0e +#define AX_CMD_READ_RX_CTL 0x0f +#define AX_CMD_WRITE_RX_CTL 0x10 +#define AX_CMD_READ_IPG012 0x11 +#define AX_CMD_WRITE_IPG0 0x12 +#define AX_CMD_WRITE_IPG1 0x13 +#define AX_CMD_READ_NODE_ID 0x13 +#define AX_CMD_WRITE_IPG2 0x14 +#define AX_CMD_WRITE_MULTI_FILTER 0x16 +#define AX88172_CMD_READ_NODE_ID 0x17 +#define AX_CMD_READ_PHY_ID 0x19 +#define AX_CMD_READ_MEDIUM_STATUS 0x1a +#define AX_CMD_WRITE_MEDIUM_MODE 0x1b +#define AX_CMD_READ_MONITOR_MODE 0x1c +#define AX_CMD_WRITE_MONITOR_MODE 0x1d +#define AX_CMD_READ_GPIOS 0x1e +#define AX_CMD_WRITE_GPIOS 0x1f +#define AX_CMD_SW_RESET 0x20 +#define AX_CMD_SW_PHY_STATUS 0x21 +#define AX_CMD_SW_PHY_SELECT 0x22 + +#define AX_MONITOR_MODE 0x01 +#define AX_MONITOR_LINK 0x02 +#define AX_MONITOR_MAGIC 0x04 +#define AX_MONITOR_HSFS 0x10 + +/* AX88172 Medium Status Register values */ +#define AX88172_MEDIUM_FD 0x02 +#define AX88172_MEDIUM_TX 0x04 +#define AX88172_MEDIUM_FC 0x10 +#define AX88172_MEDIUM_DEFAULT \ + ( AX88172_MEDIUM_FD | AX88172_MEDIUM_TX | AX88172_MEDIUM_FC ) + +#define AX_MCAST_FILTER_SIZE 8 +#define AX_MAX_MCAST 64 + +#define AX_SWRESET_CLEAR 0x00 +#define AX_SWRESET_RR 0x01 +#define AX_SWRESET_RT 0x02 +#define AX_SWRESET_PRTE 0x04 +#define AX_SWRESET_PRL 0x08 +#define AX_SWRESET_BZ 0x10 +#define AX_SWRESET_IPRL 0x20 +#define AX_SWRESET_IPPD 0x40 + +#define AX88772_IPG0_DEFAULT 0x15 +#define AX88772_IPG1_DEFAULT 0x0c +#define AX88772_IPG2_DEFAULT 0x12 + +/* AX88772 & AX88178 Medium Mode Register */ +#define AX_MEDIUM_PF 0x0080 +#define AX_MEDIUM_JFE 0x0040 +#define AX_MEDIUM_TFC 0x0020 +#define AX_MEDIUM_RFC 0x0010 +#define AX_MEDIUM_ENCK 0x0008 +#define AX_MEDIUM_AC 0x0004 +#define AX_MEDIUM_FD 0x0002 +#define AX_MEDIUM_GM 0x0001 +#define AX_MEDIUM_SM 0x1000 +#define AX_MEDIUM_SBP 0x0800 +#define AX_MEDIUM_PS 0x0200 +#define AX_MEDIUM_RE 0x0100 + +#define AX88178_MEDIUM_DEFAULT \ + (AX_MEDIUM_PS | AX_MEDIUM_FD | AX_MEDIUM_AC | \ + AX_MEDIUM_RFC | AX_MEDIUM_TFC | AX_MEDIUM_JFE | \ + AX_MEDIUM_RE ) + +#define AX88772_MEDIUM_DEFAULT \ + (AX_MEDIUM_FD | AX_MEDIUM_RFC | \ + AX_MEDIUM_TFC | AX_MEDIUM_PS | \ + AX_MEDIUM_AC | AX_MEDIUM_RE ) + +/* AX88772 & AX88178 RX_CTL values */ +#define AX_RX_CTL_SO 0x0080 +#define AX_RX_CTL_AP 0x0020 +#define AX_RX_CTL_AM 0x0010 +#define AX_RX_CTL_AB 0x0008 +#define AX_RX_CTL_SEP 0x0004 +#define AX_RX_CTL_AMALL 0x0002 +#define AX_RX_CTL_PRO 0x0001 +#define AX_RX_CTL_MFB_2048 0x0000 +#define AX_RX_CTL_MFB_4096 0x0100 +#define AX_RX_CTL_MFB_8192 0x0200 +#define AX_RX_CTL_MFB_16384 0x0300 + +#define AX_DEFAULT_RX_CTL \ + (AX_RX_CTL_SO | AX_RX_CTL_AB ) + +/* GPIO 0 .. 2 toggles */ +#define AX_GPIO_GPO0EN 0x01 /* GPIO0 Output enable */ +#define AX_GPIO_GPO_0 0x02 /* GPIO0 Output value */ +#define AX_GPIO_GPO1EN 0x04 /* GPIO1 Output enable */ +#define AX_GPIO_GPO_1 0x08 /* GPIO1 Output value */ +#define AX_GPIO_GPO2EN 0x10 /* GPIO2 Output enable */ +#define AX_GPIO_GPO_2 0x20 /* GPIO2 Output value */ +#define AX_GPIO_RESERVED 0x40 /* Reserved */ +#define AX_GPIO_RSE 0x80 /* Reload serial EEPROM */ + +#define AX_EEPROM_MAGIC 0xdeadbeef +#define AX88172_EEPROM_LEN 0x40 +#define AX88772_EEPROM_LEN 0xff + +#define PHY_MODE_MARVELL 0x0000 +#define MII_MARVELL_LED_CTRL 0x0018 +#define MII_MARVELL_STATUS 0x001b +#define MII_MARVELL_CTRL 0x0014 + +#define MARVELL_LED_MANUAL 0x0019 + +#define MARVELL_STATUS_HWCFG 0x0004 + +#define MARVELL_CTRL_TXDELAY 0x0002 +#define MARVELL_CTRL_RXDELAY 0x0080 + +/* This structure cannot exceed sizeof(unsigned long [5]) AKA 20 bytes */ +struct asix_data { + u8 multi_filter[AX_MCAST_FILTER_SIZE]; + u8 phymode; + u8 ledmode; + u8 eeprom_len; +}; + +struct ax88172_int_data { + __le16 res1; + u8 link; + __le16 res2; + u8 status; + __le16 res3; +} __attribute__ ((packed)); + +static int asix_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + void *buf; + int err = -ENOMEM; + + dev_dbg(dev->edev.dev, "asix_read_cmd() cmd=0x%02x value=0x%04x index=0x%04x size=%d", + cmd, value, index, size); + + buf = malloc(size); + if (!buf) + goto out; + + err = usb_control_msg( + dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + cmd, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + index, + buf, + size, + USB_CTRL_GET_TIMEOUT); + if (err == size) + memcpy(data, buf, size); + else if (err >= 0) + err = -EINVAL; + free(buf); + +out: + return err; +} + +static int asix_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + void *buf = NULL; + int err = -ENOMEM; + + dev_dbg(dev->edev.dev, "asix_write_cmd() cmd=0x%02x value=0x%04x index=0x%04x size=%d", + cmd, value, index, size); + + if (data) { + buf = malloc(size); + if (!buf) + goto out; + memcpy(buf, data, size); + } + + err = usb_control_msg( + dev->udev, + usb_sndctrlpipe(dev->udev, 0), + cmd, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + index, + buf, + size, + USB_CTRL_SET_TIMEOUT); + free(buf); + +out: + return err; +} + +static inline int asix_set_sw_mii(struct usbnet *dev) +{ + int ret; + ret = asix_write_cmd(dev, AX_CMD_SET_SW_MII, 0x0000, 0, 0, NULL); + if (ret < 0) + dev_err(dev->edev.dev, "Failed to enable software MII access"); + return ret; +} + +static inline int asix_set_hw_mii(struct usbnet *dev) +{ + int ret; + ret = asix_write_cmd(dev, AX_CMD_SET_HW_MII, 0x0000, 0, 0, NULL); + if (ret < 0) + dev_err(dev->edev.dev, "Failed to enable hardware MII access"); + return ret; +} + +static int asix_mdio_read(struct miiphy_device *mdev, uint8_t phy_id, + uint8_t loc, uint16_t *val) +{ + struct eth_device *eth = mdev->edev; + struct usbnet *dev = eth->priv; + __le16 res; + + asix_set_sw_mii(dev); + asix_read_cmd(dev, AX_CMD_READ_MII_REG, phy_id, + (__u16)loc, 2, &res); + asix_set_hw_mii(dev); + + dev_dbg(dev->edev.dev, "asix_mdio_read() phy_id=0x%02x, loc=0x%02x, returns=0x%04x", + phy_id, loc, le16_to_cpu(res)); + + *val = le16_to_cpu(res); + + return 0; +} + +static int asix_mdio_write(struct miiphy_device *mdev, uint8_t phy_id, + uint8_t loc, uint16_t val) +{ + struct eth_device *eth = mdev->edev; + struct usbnet *dev = eth->priv; + __le16 res = cpu_to_le16(val); + + dev_dbg(dev->edev.dev, "asix_mdio_write() phy_id=0x%02x, loc=0x%02x, val=0x%04x", + phy_id, loc, val); + + asix_set_sw_mii(dev); + asix_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, (__u16)loc, 2, &res); + asix_set_hw_mii(dev); + + return 0; +} + +static inline int asix_get_phy_addr(struct usbnet *dev) +{ + u8 buf[2]; + int ret = asix_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf); + + dev_dbg(dev->edev.dev, "asix_get_phy_addr()"); + + if (ret < 0) { + dev_err(dev->edev.dev, "Error reading PHYID register: %02x", ret); + goto out; + } + dev_dbg(dev->edev.dev, "asix_get_phy_addr() returning 0x%04x", *((__le16 *)buf)); + ret = buf[1]; + +out: + return ret; +} + +static int asix_sw_reset(struct usbnet *dev, u8 flags) +{ + int ret; + + ret = asix_write_cmd(dev, AX_CMD_SW_RESET, flags, 0, 0, NULL); + if (ret < 0) + dev_err(dev->edev.dev, "Failed to send software reset: %02x", ret); + + return ret; +} + +static u16 asix_read_rx_ctl(struct usbnet *dev) +{ + __le16 v; + int ret = asix_read_cmd(dev, AX_CMD_READ_RX_CTL, 0, 0, 2, &v); + + if (ret < 0) { + dev_err(dev->edev.dev, "Error reading RX_CTL register: %02x", ret); + goto out; + } + ret = le16_to_cpu(v); +out: + return ret; +} + +static int asix_write_rx_ctl(struct usbnet *dev, u16 mode) +{ + int ret; + + dev_dbg(dev->edev.dev, "asix_write_rx_ctl() - mode = 0x%04x", mode); + ret = asix_write_cmd(dev, AX_CMD_WRITE_RX_CTL, mode, 0, 0, NULL); + if (ret < 0) + dev_err(dev->edev.dev, "Failed to write RX_CTL mode to 0x%04x: %02x", + mode, ret); + + return ret; +} + +static u16 asix_read_medium_status(struct usbnet *dev) +{ + __le16 v; + int ret = asix_read_cmd(dev, AX_CMD_READ_MEDIUM_STATUS, 0, 0, 2, &v); + + if (ret < 0) { + dev_err(dev->edev.dev, "Error reading Medium Status register: %02x", ret); + goto out; + } + ret = le16_to_cpu(v); +out: + return ret; +} + +static int asix_write_medium_mode(struct usbnet *dev, u16 mode) +{ + int ret; + + dev_dbg(dev->edev.dev, "asix_write_medium_mode() - mode = 0x%04x", mode); + ret = asix_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, mode, 0, 0, NULL); + if (ret < 0) + dev_err(dev->edev.dev, "Failed to write Medium Mode mode to 0x%04x: %02x", + mode, ret); + + return ret; +} + +static int asix_write_gpio(struct usbnet *dev, u16 value, int sleep) +{ + int ret; + + dev_dbg(dev->edev.dev,"asix_write_gpio() - value = 0x%04x", value); + ret = asix_write_cmd(dev, AX_CMD_WRITE_GPIOS, value, 0, 0, NULL); + if (ret < 0) + dev_err(dev->edev.dev, "Failed to write GPIO value 0x%04x: %02x", + value, ret); + + if (sleep) + mdelay(sleep); + + return ret; +} + +static int asix_get_ethaddr(struct eth_device *edev, unsigned char *adr) +{ + struct usbnet *udev = container_of(edev, struct usbnet, edev); + int ret; + + /* Get the MAC address */ + if ((ret = asix_read_cmd(udev, AX_CMD_READ_NODE_ID, + 0, 0, 6, adr)) < 0) { + debug("Failed to read MAC address: %d", ret); + return -1; + } + + return 0; +} + +static int asix_set_ethaddr(struct eth_device *edev, unsigned char *adr) +{ + /* not possible? */ + return 0; +} + +static int ax88172_get_ethaddr(struct eth_device *edev, unsigned char *adr) +{ + struct usbnet *udev = container_of(edev, struct usbnet, edev); + int ret; + + /* Get the MAC address */ + if ((ret = asix_read_cmd(udev, AX88172_CMD_READ_NODE_ID, + 0, 0, 6, adr)) < 0) { + debug("read AX_CMD_READ_NODE_ID failed: %d", ret); + return -1; + } + + return 0; +} + +static int asix_rx_fixup(struct usbnet *dev, void *buf, int len) +{ + unsigned int header; + unsigned short size; + + memcpy(&header, (void*) buf, sizeof(header)); + le32_to_cpus(&header); + buf += 4; + len -= 4; + + while (len > 0) { + if ((short)(header & 0x0000ffff) != ~((short)((header & 0xffff0000) >> 16))) + dev_err(&dev->dev, "asix_rx_fixup() Bad Header Length"); + + /* get the packet length */ + size = (unsigned short) (header & 0x0000ffff); + + if (size > 1514) { + dev_err(&dev->dev, "asix_rx_fixup() Bad RX Length %d", size); + return 0; + } + + NetReceive(buf, size); + + buf += ((size + 1) & 0xfffe); + len -= ((size + 1) & 0xfffe); + + if (len == 0) + break; + + memcpy(&header, (void*) buf, sizeof(header)); + le32_to_cpus(&header); + buf += 4; + len -= 4; + } + + if (len < 0) { + dev_err(&dev->dev,"asix_rx_fixup() Bad SKB Length %d", len); + return -1; + } + return 0; +} + +static int asix_tx_fixup(struct usbnet *dev, + void *buf, int len, + void *nbuf, int *nlen) +{ + unsigned int packet_len; + + memmove(nbuf + 4, buf, len); + + packet_len = ((len ^ 0x0000ffff) << 16) + len; + cpu_to_le32s(&packet_len); + memcpy(nbuf, &packet_len, 4); + len += 4; + + if((len % 512) == 0) { + unsigned int padbytes = 0xffff0000; + cpu_to_le32s(&padbytes); + memcpy(nbuf + len, &padbytes, 4); + len += 4; + } + + *nlen = len; + + return 0; +} + +static int asix_init_mii(struct usbnet *dev) +{ + dev->miiphy.read = asix_mdio_read; + dev->miiphy.write = asix_mdio_write; + dev->miiphy.address = asix_get_phy_addr(dev); + dev->miiphy.flags = 0; + dev->miiphy.edev = &dev->edev; + + return miiphy_register(&dev->miiphy); +} + +static int ax88172_link_reset(struct usbnet *dev) +{ + u8 mode; + + mode = AX88172_MEDIUM_DEFAULT; + +// if (ecmd.duplex != DUPLEX_FULL) +// mode |= ~AX88172_MEDIUM_FD; + + asix_write_medium_mode(dev, mode); + + return 0; +} + +static int ax88172_bind(struct usbnet *dev) +{ + int ret = 0; + int i; + unsigned long gpio_bits = dev->driver_info->data; + struct asix_data *data = (struct asix_data *)&dev->data; + + dev_dbg(&dev->dev, "%s\n", __func__); + + data->eeprom_len = AX88172_EEPROM_LEN; + + ret = usbnet_get_endpoints(dev); + if (ret) { + dev_err(&dev->dev, "can not get EPs\n"); + return ret; + } + + /* Toggle the GPIOs in a manufacturer/model specific way */ + for (i = 2; i >= 0; i--) { + if ((ret = asix_write_cmd(dev, AX_CMD_WRITE_GPIOS, + (gpio_bits >> (i * 8)) & 0xff, 0, 0, + NULL)) < 0) + goto out; + mdelay(5); + } + + if ((ret = asix_write_rx_ctl(dev, 0x80)) < 0) + goto out; + + dev->edev.get_ethaddr = ax88172_get_ethaddr; + dev->edev.set_ethaddr = asix_set_ethaddr; + asix_init_mii(dev); + + return 0; + +out: + return ret; +} + +static int ax88772_bind(struct usbnet *dev) +{ + int ret, embd_phy; + u16 rx_ctl; + struct asix_data *data = (struct asix_data *)&dev->data; + + debug("%s\n", __func__); + + data->eeprom_len = AX88772_EEPROM_LEN; + + usbnet_get_endpoints(dev); + + if ((ret = asix_write_gpio(dev, + AX_GPIO_RSE | AX_GPIO_GPO_2 | AX_GPIO_GPO2EN, 5)) < 0) + goto out; + + /* 0x10 is the phy id of the embedded 10/100 ethernet phy */ + embd_phy = ((asix_get_phy_addr(dev) & 0x1f) == 0x10 ? 1 : 0); + if ((ret = asix_write_cmd(dev, AX_CMD_SW_PHY_SELECT, + embd_phy, 0, 0, NULL)) < 0) { + debug("Select PHY #1 failed: %d", ret); + goto out; + } + + if ((ret = asix_sw_reset(dev, AX_SWRESET_IPPD | AX_SWRESET_PRL)) < 0) + goto out; + + mdelay(150); + if ((ret = asix_sw_reset(dev, AX_SWRESET_CLEAR)) < 0) + goto out; + + mdelay(150); + if (embd_phy) { + if ((ret = asix_sw_reset(dev, AX_SWRESET_IPRL)) < 0) + goto out; + } + else { + if ((ret = asix_sw_reset(dev, AX_SWRESET_PRTE)) < 0) + goto out; + } + + mdelay(150); + rx_ctl = asix_read_rx_ctl(dev); + debug("RX_CTL is 0x%04x after software reset", rx_ctl); + if ((ret = asix_write_rx_ctl(dev, 0x0000)) < 0) + goto out; + + rx_ctl = asix_read_rx_ctl(dev); + debug("RX_CTL is 0x%04x setting to 0x0000", rx_ctl); + + dev->edev.get_ethaddr = asix_get_ethaddr; + dev->edev.set_ethaddr = asix_set_ethaddr; + asix_init_mii(dev); + + if ((ret = asix_sw_reset(dev, AX_SWRESET_PRL)) < 0) + goto out; + + mdelay(150); + + if ((ret = asix_sw_reset(dev, AX_SWRESET_IPRL | AX_SWRESET_PRL)) < 0) + goto out; + + mdelay(150); + + if ((ret = asix_write_medium_mode(dev, AX88772_MEDIUM_DEFAULT)) < 0) + goto out; + + if ((ret = asix_write_cmd(dev, AX_CMD_WRITE_IPG0, + AX88772_IPG0_DEFAULT | AX88772_IPG1_DEFAULT, + AX88772_IPG2_DEFAULT, 0, NULL)) < 0) { + debug("Write IPG,IPG1,IPG2 failed: %d", ret); + goto out; + } + + /* Set RX_CTL to default values with 2k buffer, and enable cactus */ + if ((ret = asix_write_rx_ctl(dev, AX_DEFAULT_RX_CTL)) < 0) + goto out; + + rx_ctl = asix_read_rx_ctl(dev); + debug("RX_CTL is 0x%04x after all initializations", rx_ctl); + + rx_ctl = asix_read_medium_status(dev); + debug("Medium Status is 0x%04x after all initializations", rx_ctl); + + /* Asix framing packs multiple eth frames into a 2K usb bulk transfer */ + if (dev->driver_info->flags & FLAG_FRAMING_AX) { + /* hard_mtu is still the default - the device does not support + jumbo eth frames */ + dev->rx_urb_size = 2048; + } + + return 0; + +out: + return ret; +} + +static void asix_unbind(struct usbnet *dev) +{ + miiphy_unregister(&dev->miiphy); +} + +static struct driver_info ax8817x_info = { + .description = "ASIX AX8817x USB 2.0 Ethernet", + .bind = ax88172_bind, + .unbind = asix_unbind, + .link_reset = ax88172_link_reset, + .reset = ax88172_link_reset, + .flags = FLAG_ETHER, + .data = 0x00130103, +}; + +static struct driver_info dlink_dub_e100_info = { + .description = "DLink DUB-E100 USB Ethernet", + .bind = ax88172_bind, + .unbind = asix_unbind, + .link_reset = ax88172_link_reset, + .reset = ax88172_link_reset, + .flags = FLAG_ETHER, + .data = 0x009f9d9f, +}; + +static struct driver_info netgear_fa120_info = { + .description = "Netgear FA-120 USB Ethernet", + .bind = ax88172_bind, + .unbind = asix_unbind, + .link_reset = ax88172_link_reset, + .reset = ax88172_link_reset, + .flags = FLAG_ETHER, + .data = 0x00130103, +}; + +static struct driver_info hawking_uf200_info = { + .description = "Hawking UF200 USB Ethernet", + .bind = ax88172_bind, + .unbind = asix_unbind, + .link_reset = ax88172_link_reset, + .reset = ax88172_link_reset, + .flags = FLAG_ETHER, + .data = 0x001f1d1f, +}; + +static struct driver_info ax88772_info = { + .description = "ASIX AX88772 USB 2.0 Ethernet", + .bind = ax88772_bind, + .unbind = asix_unbind, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = asix_rx_fixup, + .tx_fixup = asix_tx_fixup, +}; + +static const struct usb_device_id products [] = { +{ + // Linksys USB200M + USB_DEVICE (0x077b, 0x2226), + .driver_info = &ax8817x_info, +}, { + // Netgear FA120 + USB_DEVICE (0x0846, 0x1040), + .driver_info = &netgear_fa120_info, +}, { + // DLink DUB-E100 + USB_DEVICE (0x2001, 0x1a00), + .driver_info = &dlink_dub_e100_info, +}, { + // Intellinet, ST Lab USB Ethernet + USB_DEVICE (0x0b95, 0x1720), + .driver_info = &ax8817x_info, +}, { + // Hawking UF200, TrendNet TU2-ET100 + USB_DEVICE (0x07b8, 0x420a), + .driver_info = &hawking_uf200_info, +}, { + // Billionton Systems, USB2AR + USB_DEVICE (0x08dd, 0x90ff), + .driver_info = &ax8817x_info, +}, { + // ATEN UC210T + USB_DEVICE (0x0557, 0x2009), + .driver_info = &ax8817x_info, +}, { + // Buffalo LUA-U2-KTX + USB_DEVICE (0x0411, 0x003d), + .driver_info = &ax8817x_info, +}, { + // Sitecom LN-029 "USB 2.0 10/100 Ethernet adapter" + USB_DEVICE (0x6189, 0x182d), + .driver_info = &ax8817x_info, +}, { + // corega FEther USB2-TX + USB_DEVICE (0x07aa, 0x0017), + .driver_info = &ax8817x_info, +}, { + // Surecom EP-1427X-2 + USB_DEVICE (0x1189, 0x0893), + .driver_info = &ax8817x_info, +}, { + // goodway corp usb gwusb2e + USB_DEVICE (0x1631, 0x6200), + .driver_info = &ax8817x_info, +}, { + // JVC MP-PRX1 Port Replicator + USB_DEVICE (0x04f1, 0x3008), + .driver_info = &ax8817x_info, +}, { + // ASIX AX88772 10/100 + USB_DEVICE (0x0b95, 0x7720), + .driver_info = &ax88772_info, +}, { + // Linksys USB200M Rev 2 + USB_DEVICE (0x13b1, 0x0018), + .driver_info = &ax88772_info, +}, { + // 0Q0 cable ethernet + USB_DEVICE (0x1557, 0x7720), + .driver_info = &ax88772_info, +}, { + // DLink DUB-E100 H/W Ver B1 + USB_DEVICE (0x07d1, 0x3c05), + .driver_info = &ax88772_info, +}, { + // DLink DUB-E100 H/W Ver B1 Alternate + USB_DEVICE (0x2001, 0x3c05), + .driver_info = &ax88772_info, +}, { + // Apple USB Ethernet Adapter + USB_DEVICE(0x05ac, 0x1402), + .driver_info = &ax88772_info, +}, { + // Cables-to-Go USB Ethernet Adapter + USB_DEVICE(0x0b95, 0x772a), + .driver_info = &ax88772_info, +}, + { }, // END +}; + +static struct usb_driver asix_driver = { + .name = "asix", + .id_table = products, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, +}; + +static int __init asix_init(void) +{ + return usb_driver_register(&asix_driver); +} +device_initcall(asix_init); + diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c new file mode 100644 index 0000000000..fff6a1710b --- /dev/null +++ b/drivers/net/usb/usbnet.c @@ -0,0 +1,252 @@ +#include <common.h> +#include <usb/usb.h> +#include <usb/usbnet.h> +#include <asm/byteorder.h> +#include <errno.h> +#include <malloc.h> + +static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN); +} + +/* handles CDC Ethernet and many other network "bulk data" interfaces */ +int usbnet_get_endpoints(struct usbnet *dev) +{ + struct usb_device *udev = dev->udev; + int tmp; + struct usb_interface_descriptor *alt = NULL; + struct usb_endpoint_descriptor *in = NULL, *out = NULL; + struct usb_endpoint_descriptor *status = NULL; + + for (tmp = 0; tmp < udev->config.no_of_if; tmp++) { + unsigned ep; + + in = out = status = NULL; + alt = &udev->config.if_desc[tmp]; + + /* take the first altsetting with in-bulk + out-bulk; + * remember any status endpoint, just in case; + * ignore other endpoints and altsetttings. + */ + for (ep = 0; ep < alt->bNumEndpoints; ep++) { + struct usb_endpoint_descriptor *e; + int intr = 0; + + e = &alt->ep_desc[ep]; + switch (e->bmAttributes) { + case USB_ENDPOINT_XFER_INT: + if (!usb_endpoint_dir_in(e)) + continue; + intr = 1; + /* FALLTHROUGH */ + case USB_ENDPOINT_XFER_BULK: + break; + default: + continue; + } + if (usb_endpoint_dir_in(e)) { + if (!intr && !in) + in = e; + else if (intr && !status) + status = e; + } else { + if (!out) + out = e; + } + } + if (in && out) + break; + } + + if (!alt || !in || !out) + return -EINVAL; + + if (alt->bAlternateSetting != 0 + || !(dev->driver_info->flags & FLAG_NO_SETINT)) { + tmp = usb_set_interface (dev->udev, alt->bInterfaceNumber, + alt->bAlternateSetting); + if (tmp < 0) + return tmp; + } + + dev->in = usb_rcvbulkpipe (dev->udev, + in->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->out = usb_sndbulkpipe (dev->udev, + out->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev_dbg(&dev->dev, "found endpoints: IN=%d OUT=%d\n", + in->bEndpointAddress, out->bEndpointAddress); + + return 0; +} +EXPORT_SYMBOL(usbnet_get_endpoints); + +char tx_buffer[4096]; + +static int usbnet_send(struct eth_device *edev, void *eth_data, int data_length) +{ + struct usbnet *dev = edev->priv; + struct driver_info *info = dev->driver_info; + int len, alen, ret; + + dev_dbg(&dev->dev, "%s\n",__func__); + + /* some devices want funky USB-level framing, for + * win32 driver (usually) and/or hardware quirks + */ + if(info->tx_fixup) { + if(info->tx_fixup(dev, eth_data, data_length, tx_buffer, &len)) { + dev_dbg(&dev->dev, "can't tx_fixup packet"); + return 0; + } + } else { + len = data_length; + memmove(tx_buffer, (void*) eth_data, len); + } + + /* don't assume the hardware handles USB_ZERO_PACKET + * NOTE: strictly conforming cdc-ether devices should expect + * the ZLP here, but ignore the one-byte packet. + */ + if ((len % dev->maxpacket) == 0) + tx_buffer[len++] = 0; + + ret = usb_bulk_msg(dev->udev, dev->out, tx_buffer, len, &alen, 1000); + dev_dbg(edev->dev, "%s: ret: %d len: %d alen: %d\n", __func__, ret, len, alen); + + return 0; +} + +static char rx_buf[4096]; + +static int usbnet_recv(struct eth_device *edev) +{ + struct usbnet *dev = (struct usbnet*) edev->priv; + struct driver_info *info = dev->driver_info; + int len, alen = 0; + + dev_dbg(edev->dev, "%s\n",__func__); + + len = dev->rx_urb_size; + + usb_bulk_msg(dev->udev, dev->in, rx_buf, len, &alen, + 1000); + + if(alen) { + if (info->rx_fixup) + return info->rx_fixup(dev, rx_buf, alen); + else + NetReceive(rx_buf, alen); + } + + return 0; +} + +static int usbnet_init(struct eth_device *edev) +{ + struct usbnet *dev = (struct usbnet*) edev->priv; + struct driver_info *info = dev->driver_info; + int ret = 0; + + dev_dbg(edev->dev, "%s\n",__func__); + + /* put into "known safe" state */ + if (info->reset) + ret = info->reset(dev); + + if (ret) { + dev_info (edev->dev, "open reset fail (%d)", ret); + return ret; + } + + miiphy_restart_aneg(&dev->miiphy); + + return 0; +} + +static int usbnet_open(struct eth_device *edev) +{ + struct usbnet *dev = (struct usbnet*)edev->priv; + + dev_dbg(edev->dev, "%s\n",__func__); + + if (miiphy_wait_aneg(&dev->miiphy)) + return -1; + + miiphy_print_status(&dev->miiphy); + + return 0; +} + +static void usbnet_halt(struct eth_device *edev) +{ + dev_dbg(edev->dev, "%s\n",__func__); +} + +int usbnet_probe(struct usb_device *usbdev, const struct usb_device_id *prod) +{ + struct usbnet *undev; + struct eth_device *edev; + struct driver_info *info; + int status; + + dev_dbg(edev->dev, "%s\n", __func__); + + undev = xzalloc(sizeof (*undev)); + + usbdev->drv_data = undev; + + edev = &undev->edev; + undev->udev = usbdev; + + edev->open = usbnet_open, + edev->init = usbnet_init, + edev->send = usbnet_send, + edev->recv = usbnet_recv, + edev->halt = usbnet_halt, + edev->priv = undev; + edev->dev = &undev->dev; + + get_free_deviceid(edev->dev->id, "eth"); + sprintf(edev->dev->name, "%s", "usbnet"); + + info = (struct driver_info *)prod->driver_info; + undev->driver_info = info; + + if (info->bind) { + status = info->bind (undev); + if (status < 0) + goto out1; + } + + if (!undev->rx_urb_size) + undev->rx_urb_size = 1514; /* FIXME: What to put here? */ + undev->maxpacket = usb_maxpacket(undev->udev, undev->out); + + /* FIXME: eth layer should have the device and register it */ + register_device(edev->dev); + + eth_register(edev); + + return 0; +out1: + dev_dbg(edev->dev, "err: %d\n", status); + return status; +} + +void usbnet_disconnect(struct usb_device *usbdev) +{ + struct usbnet *undev = usbdev->drv_data; + struct eth_device *edev = &undev->edev; + struct driver_info *info; + + eth_unregister(edev); + unregister_device(edev->dev); + + info = undev->driver_info; + if (info->unbind) + info->unbind(undev); + + free(undev); +} + diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig new file mode 100644 index 0000000000..a41406f931 --- /dev/null +++ b/drivers/usb/Kconfig @@ -0,0 +1,16 @@ +menuconfig USB + bool "USB support " + +if USB + +config USB_EHCI + bool "EHCI driver" + +config USB_ULPI + bool + +config USB_ISP1504 + select USB_ULPI + bool "ISP1504 Tranceiver support" + +endif diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile new file mode 100644 index 0000000000..23c5c6ca59 --- /dev/null +++ b/drivers/usb/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_USB) += usb.o +obj-$(CONFIG_USB_EHCI) += usb_ehci_core.o +obj-$(CONFIG_USB_ULPI) += ulpi.o +obj-$(CONFIG_USB_ISP1504) += isp1504.o diff --git a/drivers/usb/isp1504.c b/drivers/usb/isp1504.c new file mode 100644 index 0000000000..9ba74b157b --- /dev/null +++ b/drivers/usb/isp1504.c @@ -0,0 +1,37 @@ +#include <common.h> +#include <usb/ulpi.h> +#include <usb/isp1504.h> + +int isp1504_set_vbus_power(void __iomem *view, int on) +{ + int vid, pid, ret = 0; + + vid = (ulpi_read(ISP1504_VID_HIGH, view) << 8) | + ulpi_read(ISP1504_VID_LOW, view); + pid = (ulpi_read(ISP1504_PID_HIGH, view) << 8) | + ulpi_read(ISP1504_PID_LOW, view); + + pr_info("ULPI Vendor ID 0x%x Product ID 0x%x\n", vid, pid); + if (vid != 0x4cc || pid != 0x1504) { + pr_err("No ISP1504 found\n"); + return -1; + } + + if (on) { + ret = ulpi_set(DRV_VBUS_EXT | /* enable external Vbus */ + DRV_VBUS | /* enable internal Vbus */ + USE_EXT_VBUS_IND | /* use external indicator */ + CHRG_VBUS, /* charge Vbus */ + ISP1504_OTGCTL, view); + } else { + ret = ulpi_clear(DRV_VBUS_EXT | /* disable external Vbus */ + DRV_VBUS, /* disable internal Vbus */ + ISP1504_OTGCTL, view); + + ret |= ulpi_set(USE_EXT_VBUS_IND | /* use external indicator */ + DISCHRG_VBUS, /* discharge Vbus */ + ISP1504_OTGCTL, view); + } + + return ret; +} diff --git a/drivers/usb/ulpi.c b/drivers/usb/ulpi.c new file mode 100644 index 0000000000..4a898fb223 --- /dev/null +++ b/drivers/usb/ulpi.c @@ -0,0 +1,118 @@ +/* + * Copyright 2008 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <common.h> +#include <asm/io.h> +#include <errno.h> +#include <usb/ulpi.h> + +/* ULPIVIEW register bits */ +#define ULPIVW_WU (1 << 31) /* Wakeup */ +#define ULPIVW_RUN (1 << 30) /* read/write run */ +#define ULPIVW_WRITE (1 << 29) /* 0 = read 1 = write */ +#define ULPIVW_SS (1 << 27) /* SyncState */ +#define ULPIVW_PORT_MASK 0x07 /* Port field */ +#define ULPIVW_PORT_SHIFT 24 +#define ULPIVW_ADDR_MASK 0xFF /* data address field */ +#define ULPIVW_ADDR_SHIFT 16 +#define ULPIVW_RDATA_MASK 0xFF /* read data field */ +#define ULPIVW_RDATA_SHIFT 8 +#define ULPIVW_WDATA_MASK 0xFF /* write data field */ +#define ULPIVW_WDATA_SHIFT 0 + +static int ulpi_poll(void __iomem *view, uint32_t bit) +{ + uint32_t data; + int timeout = 10000; + + data = readl(view); + while (data & bit) { + if (!timeout--) + return -ETIMEDOUT; + + udelay(1); + data = readl(view); + } + return (data >> ULPIVW_RDATA_SHIFT) & ULPIVW_RDATA_MASK; +} + +int ulpi_read(int reg, void __iomem *view) +{ + int ret; + + /* make sure interface is running */ + if (!(readl(view) && ULPIVW_SS)) { + writel(ULPIVW_WU, view); + + /* wait for wakeup */ + ret = ulpi_poll(view, ULPIVW_WU); + if (ret < 0) + return ret; + } + + /* read the register */ + writel((ULPIVW_RUN | (reg << ULPIVW_ADDR_SHIFT)), view); + + /* wait for completion */ + return ulpi_poll(view, ULPIVW_RUN); +} +EXPORT_SYMBOL(ulpi_read); + +int ulpi_set(u8 bits, int reg, void __iomem *view) +{ + int ret; + + /* make sure the interface is running */ + if (!(readl(view) && ULPIVW_SS)) { + writel(ULPIVW_WU, view); + /* wait for wakeup */ + ret = ulpi_poll(view, ULPIVW_WU); + if (ret < 0) + return ret; + } + + writel((ULPIVW_RUN | ULPIVW_WRITE | + ((reg + ISP1504_REG_SET) << ULPIVW_ADDR_SHIFT) | + ((bits & ULPIVW_WDATA_MASK) << ULPIVW_WDATA_SHIFT)), + view); + + /* wait for completion */ + ret = ulpi_poll(view, ULPIVW_RUN); + if (ret < 0) + return ret; + return 0; +} +EXPORT_SYMBOL(ulpi_set); + +int ulpi_clear(u8 bits, int reg, void __iomem *view) +{ + int ret; + + writel((ULPIVW_RUN | ULPIVW_WRITE | + ((reg + ISP1504_REG_CLEAR) << ULPIVW_ADDR_SHIFT) | + ((bits & ULPIVW_WDATA_MASK) << ULPIVW_WDATA_SHIFT)), + view); + + /* wait for completion */ + ret = ulpi_poll(view, ULPIVW_RUN); + if (ret < 0) + return ret; + return 0; +} +EXPORT_SYMBOL(ulpi_clear); + diff --git a/drivers/usb/usb.c b/drivers/usb/usb.c new file mode 100644 index 0000000000..61fddfa5d8 --- /dev/null +++ b/drivers/usb/usb.c @@ -0,0 +1,1397 @@ +/* + * + * Most of this source has been derived from the Linux USB + * project: + * (C) Copyright Linus Torvalds 1999 + * (C) Copyright Johannes Erdfelt 1999-2001 + * (C) Copyright Andreas Gal 1999 + * (C) Copyright Gregory P. Smith 1999 + * (C) Copyright Deti Fliegl 1999 (new USB architecture) + * (C) Copyright Randy Dunlap 2000 + * (C) Copyright David Brownell 2000 (kernel hotplug, usb_device_id) + * (C) Copyright Yggdrasil Computing, Inc. 2000 + * (usb_device_id matching changes by Adam J. Richter) + * + * Adapted for U-Boot: + * (C) Copyright 2001 Denis Peter, MPL AG Switzerland + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ + +/* + * How it works: + * + * Since this is a bootloader, the devices will not be automatic + * (re)configured on hotplug, but after a restart of the USB the + * device should work. + * + * For each transfer (except "Interrupt") we wait for completion. + */ +#include <common.h> +#include <command.h> +#include <malloc.h> +#include <driver.h> +#include <linux/ctype.h> +#include <asm/byteorder.h> +#include <xfuncs.h> +#include <init.h> + +#include <usb/usb.h> + +/* #define USB_DEBUG */ + +#ifdef USB_DEBUG +#define USB_PRINTF(fmt, args...) printf(fmt , ##args) +#else +#define USB_PRINTF(fmt, args...) +#endif + +#define USB_BUFSIZ 512 + +static int dev_index; +static int asynch_allowed; +static struct devrequest setup_packet; + +static int usb_hub_probe(struct usb_device *dev, int ifnum); +static int hub_port_reset(struct usb_device *dev, int port, + unsigned short *portstat); + +static LIST_HEAD(host_list); +static LIST_HEAD(usb_device_list); + +static void print_usb_device(struct usb_device *dev) +{ + printf("%s: %04x:%04x %s\n", dev->dev.name, + dev->descriptor.idVendor, + dev->descriptor.idProduct, + dev->prod); +} + +static int host_busnum; + +int usb_register_host(struct usb_host *host) +{ + list_add_tail(&host->list, &host_list); + host->busnum = host_busnum++; + asynch_allowed = 1; + return 0; +} + +/** + * set configuration number to configuration + */ +static int usb_set_configuration(struct usb_device *dev, int configuration) +{ + int res; + USB_PRINTF("set configuration %d\n", configuration); + /* set setup command */ + res = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_CONFIGURATION, 0, + configuration, 0, + NULL, 0, USB_CNTL_TIMEOUT); + if (res == 0) { + dev->toggle[0] = 0; + dev->toggle[1] = 0; + return 0; + } else + return -1; +} + +/* The routine usb_set_maxpacket_ep() is extracted from the loop of routine + * usb_set_maxpacket(), because the optimizer of GCC 4.x chokes on this routine + * when it is inlined in 1 single routine. What happens is that the register r3 + * is used as loop-count 'i', but gets overwritten later on. + * This is clearly a compiler bug, but it is easier to workaround it here than + * to update the compiler (Occurs with at least several GCC 4.{1,2},x + * CodeSourcery compilers like e.g. 2007q3, 2008q1, 2008q3 lite editions on ARM) + */ +static void noinline +usb_set_maxpacket_ep(struct usb_device *dev, struct usb_endpoint_descriptor *ep) +{ + int b; + + b = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + + if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_CONTROL) { + /* Control => bidirectional */ + dev->epmaxpacketout[b] = ep->wMaxPacketSize; + dev->epmaxpacketin[b] = ep->wMaxPacketSize; + USB_PRINTF("##Control EP epmaxpacketout/in[%d] = %d\n", + b, dev->epmaxpacketin[b]); + } else { + if ((ep->bEndpointAddress & 0x80) == 0) { + /* OUT Endpoint */ + if (ep->wMaxPacketSize > dev->epmaxpacketout[b]) { + dev->epmaxpacketout[b] = ep->wMaxPacketSize; + USB_PRINTF("##EP epmaxpacketout[%d] = %d\n", + b, dev->epmaxpacketout[b]); + } + } else { + /* IN Endpoint */ + if (ep->wMaxPacketSize > dev->epmaxpacketin[b]) { + dev->epmaxpacketin[b] = ep->wMaxPacketSize; + USB_PRINTF("##EP epmaxpacketin[%d] = %d\n", + b, dev->epmaxpacketin[b]); + } + } /* if out */ + } /* if control */ +} + +/* + * set the max packed value of all endpoints in the given configuration + */ +static int usb_set_maxpacket(struct usb_device *dev) +{ + int i, ii; + + for (i = 0; i < dev->config.bNumInterfaces; i++) + for (ii = 0; ii < dev->config.if_desc[i].bNumEndpoints; ii++) + usb_set_maxpacket_ep(dev, + &dev->config.if_desc[i].ep_desc[ii]); + + return 0; +} + +/** + * Parse the config, located in buffer, and fills the dev->config structure. + * Note that all little/big endian swapping are done automatically. + */ +static int usb_parse_config(struct usb_device *dev, unsigned char *buffer, int cfgno) +{ + struct usb_descriptor_header *head; + int index, ifno, epno, curr_if_num; + int i; + unsigned char *ch; + + ifno = -1; + epno = -1; + curr_if_num = -1; + + dev->configno = cfgno; + head = (struct usb_descriptor_header *) &buffer[0]; + if (head->bDescriptorType != USB_DT_CONFIG) { + printf(" ERROR: NOT USB_CONFIG_DESC %x\n", + head->bDescriptorType); + return -1; + } + memcpy(&dev->config, buffer, buffer[0]); + le16_to_cpus(&(dev->config.wTotalLength)); + dev->config.no_of_if = 0; + + index = dev->config.bLength; + /* Ok the first entry must be a configuration entry, + * now process the others */ + head = (struct usb_descriptor_header *) &buffer[index]; + while (index + 1 < dev->config.wTotalLength) { + switch (head->bDescriptorType) { + case USB_DT_INTERFACE: + if (((struct usb_interface_descriptor *) \ + &buffer[index])->bInterfaceNumber != curr_if_num) { + /* this is a new interface, copy new desc */ + ifno = dev->config.no_of_if; + dev->config.no_of_if++; + memcpy(&dev->config.if_desc[ifno], + &buffer[index], buffer[index]); + dev->config.if_desc[ifno].no_of_ep = 0; + dev->config.if_desc[ifno].num_altsetting = 1; + curr_if_num = + dev->config.if_desc[ifno].bInterfaceNumber; + } else { + /* found alternate setting for the interface */ + dev->config.if_desc[ifno].num_altsetting++; + } + break; + case USB_DT_ENDPOINT: + epno = dev->config.if_desc[ifno].no_of_ep; + /* found an endpoint */ + dev->config.if_desc[ifno].no_of_ep++; + memcpy(&dev->config.if_desc[ifno].ep_desc[epno], + &buffer[index], buffer[index]); + le16_to_cpus(&(dev->config.if_desc[ifno].ep_desc[epno].\ + wMaxPacketSize)); + USB_PRINTF("if %d, ep %d\n", ifno, epno); + break; + default: + if (head->bLength == 0) + return 1; + + USB_PRINTF("unknown Description Type : %x\n", + head->bDescriptorType); + + { + ch = (unsigned char *)head; + for (i = 0; i < head->bLength; i++) + USB_PRINTF("%02X ", *ch++); + USB_PRINTF("\n\n\n"); + } + break; + } + index += head->bLength; + head = (struct usb_descriptor_header *)&buffer[index]; + } + return 1; +} + +/** + * set address of a device to the value in dev->devnum. + * This can only be done by addressing the device via the default address (0) + */ +static int usb_set_address(struct usb_device *dev) +{ + int res; + + USB_PRINTF("set address %d\n", dev->devnum); + res = usb_control_msg(dev, usb_snddefctrl(dev), + USB_REQ_SET_ADDRESS, 0, + (dev->devnum), 0, + NULL, 0, USB_CNTL_TIMEOUT); + return res; +} + +static int usb_get_descriptor(struct usb_device *dev, unsigned char type, + unsigned char index, void *buf, int size) +{ + int res; + res = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + (type << 8) + index, 0, + buf, size, USB_CNTL_TIMEOUT); + return res; +} + +/* + * By the time we get here, the device has gotten a new device ID + * and is in the default state. We need to identify the thing and + * get the ball rolling.. + * + * Returns 0 for success, != 0 for error. + */ +static int usb_new_device(struct usb_device *dev) +{ + int addr, err; + int tmp; + unsigned char tmpbuf[USB_BUFSIZ]; + struct usb_device_descriptor *desc; + int port = -1; + struct usb_device *parent = dev->parent; + unsigned short portstatus; + + /* We still haven't set the Address yet */ + addr = dev->devnum; + dev->devnum = 0; + + /* This is a Windows scheme of initialization sequence, with double + * reset of the device (Linux uses the same sequence) + * Some equipment is said to work only with such init sequence; this + * patch is based on the work by Alan Stern: + * http://sourceforge.net/mailarchive/forum.php? + * thread_id=5729457&forum_id=5398 + */ + + /* send 64-byte GET-DEVICE-DESCRIPTOR request. Since the descriptor is + * only 18 bytes long, this will terminate with a short packet. But if + * the maxpacket size is 8 or 16 the device may be waiting to transmit + * some more, or keeps on retransmitting the 8 byte header. */ + + desc = (struct usb_device_descriptor *)tmpbuf; + dev->descriptor.bMaxPacketSize0 = 64; /* Start off at 64 bytes */ + /* Default to 64 byte max packet size */ + dev->maxpacketsize = PACKET_SIZE_64; + dev->epmaxpacketin[0] = 64; + dev->epmaxpacketout[0] = 64; + + err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, 64); + if (err < 0) { + USB_PRINTF("%s: usb_get_descriptor() failed\n", __func__); + return 1; + } + + dev->descriptor.bMaxPacketSize0 = desc->bMaxPacketSize0; + + /* find the port number we're at */ + if (parent) { + int j; + + for (j = 0; j < parent->maxchild; j++) { + if (parent->children[j] == dev) { + port = j; + break; + } + } + if (port < 0) { + printf("%s: cannot locate device's port.\n", __func__); + return 1; + } + + /* reset the port for the second time */ + err = hub_port_reset(dev->parent, port, &portstatus); + if (err < 0) { + printf("\n Couldn't reset port %i\n", port); + return 1; + } + } + + dev->epmaxpacketin[0] = dev->descriptor.bMaxPacketSize0; + dev->epmaxpacketout[0] = dev->descriptor.bMaxPacketSize0; + switch (dev->descriptor.bMaxPacketSize0) { + case 8: + dev->maxpacketsize = PACKET_SIZE_8; + break; + case 16: + dev->maxpacketsize = PACKET_SIZE_16; + break; + case 32: + dev->maxpacketsize = PACKET_SIZE_32; + break; + case 64: + dev->maxpacketsize = PACKET_SIZE_64; + break; + } + dev->devnum = addr; + + err = usb_set_address(dev); /* set address */ + + if (err < 0) { + printf("\n USB device not accepting new address " \ + "(error=%lX)\n", dev->status); + return 1; + } + + wait_ms(10); /* Let the SET_ADDRESS settle */ + + tmp = sizeof(dev->descriptor); + + err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, + &dev->descriptor, sizeof(dev->descriptor)); + if (err < tmp) { + if (err < 0) + printf("unable to get device descriptor (error=%d)\n", + err); + else + printf("USB device descriptor short read " \ + "(expected %i, got %i)\n", tmp, err); + return 1; + } + /* correct le values */ + le16_to_cpus(&dev->descriptor.bcdUSB); + le16_to_cpus(&dev->descriptor.idVendor); + le16_to_cpus(&dev->descriptor.idProduct); + le16_to_cpus(&dev->descriptor.bcdDevice); + /* only support for one config for now */ + usb_get_configuration_no(dev, &tmpbuf[0], 0); + usb_parse_config(dev, &tmpbuf[0], 0); + usb_set_maxpacket(dev); + /* we set the default configuration here */ + if (usb_set_configuration(dev, dev->config.bConfigurationValue)) { + printf("failed to set default configuration " \ + "len %d, status %lX\n", dev->act_len, dev->status); + return -1; + } + USB_PRINTF("new device: Mfr=%d, Product=%d, SerialNumber=%d\n", + dev->descriptor.iManufacturer, dev->descriptor.iProduct, + dev->descriptor.iSerialNumber); + memset(dev->mf, 0, sizeof(dev->mf)); + memset(dev->prod, 0, sizeof(dev->prod)); + memset(dev->serial, 0, sizeof(dev->serial)); + if (dev->descriptor.iManufacturer) + usb_string(dev, dev->descriptor.iManufacturer, + dev->mf, sizeof(dev->mf)); + if (dev->descriptor.iProduct) + usb_string(dev, dev->descriptor.iProduct, + dev->prod, sizeof(dev->prod)); + if (dev->descriptor.iSerialNumber) + usb_string(dev, dev->descriptor.iSerialNumber, + dev->serial, sizeof(dev->serial)); + /* now prode if the device is a hub */ + usb_hub_probe(dev, 0); + + sprintf(dev->dev.name, "usb%d-%d", dev->host->busnum, dev->devnum); + sprintf(dev->dev.id, "usb%d-%d", dev->host->busnum, dev->devnum); + + print_usb_device(dev); + + register_device(&dev->dev); + list_add_tail(&dev->list, &usb_device_list); + + return 0; +} + +static struct usb_device *usb_alloc_new_device(void) +{ + struct usb_device *usbdev = xzalloc(sizeof (*usbdev)); + + if (!usbdev) + return NULL; + + usbdev->devnum = dev_index + 1; + usbdev->maxchild = 0; + usbdev->dev.bus = &usb_bus_type; + + dev_index++; + + return usbdev; +} + +static int __usb_init(void) +{ + struct usb_device *dev; + struct usb_host *host; + + list_for_each_entry(dev, &usb_device_list, list) { + unregister_device(&dev->dev); + if (dev->hub) + free(dev->hub); + free(dev); + } + + printf("USB: scanning bus for devices...\n"); + dev_index = 0; + + list_for_each_entry(host, &host_list, list) { + host->init(host); + + dev = usb_alloc_new_device(); + dev->host = host; + usb_new_device(dev); + } + + printf("%d USB Device(s) found\n", dev_index); + + return 0; +} + +static int do_usb(cmd_tbl_t *cmdtp, int argc, char *argv[]) +{ + __usb_init(); + + return 0; +} + +static const __maybe_unused char cmd_usb_help[] = +"Usage: usb\n" +"(re-)detect USB devices\n"; + +U_BOOT_CMD_START(usb) + .maxargs = CONFIG_MAXARGS, + .cmd = do_usb, + .usage = "(re-)detect USB devices", + U_BOOT_CMD_HELP(cmd_usb_help) +U_BOOT_CMD_END + +/* + * disables the asynch behaviour of the control message. This is used for data + * transfers that uses the exclusiv access to the control and bulk messages. + */ +void usb_disable_asynch(int disable) +{ + asynch_allowed = !disable; +} + + +/*------------------------------------------------------------------- + * Message wrappers. + * + */ + +/* + * submits an Interrupt Message + */ +int usb_submit_int_msg(struct usb_device *dev, unsigned long pipe, + void *buffer, int transfer_len, int interval) +{ + struct usb_host *host = dev->host; + + return host->submit_int_msg(dev, pipe, buffer, transfer_len, interval); +} + +/* + * submits a control message and waits for comletion (at least timeout * 1ms) + * If timeout is 0, we don't wait for completion (used as example to set and + * clear keyboards LEDs). For data transfers, (storage transfers) we don't + * allow control messages with 0 timeout, by previousely resetting the flag + * asynch_allowed (usb_disable_asynch(1)). + * returns the transfered length if OK or -1 if error. The transfered length + * and the current status are stored in the dev->act_len and dev->status. + */ +int usb_control_msg(struct usb_device *dev, unsigned int pipe, + unsigned char request, unsigned char requesttype, + unsigned short value, unsigned short index, + void *data, unsigned short size, int timeout) +{ + struct usb_host *host = dev->host; + + if ((timeout == 0) && (!asynch_allowed)) { + /* request for a asynch control pipe is not allowed */ + return -1; + } + + /* set setup command */ + setup_packet.requesttype = requesttype; + setup_packet.request = request; + setup_packet.value = cpu_to_le16(value); + setup_packet.index = cpu_to_le16(index); + setup_packet.length = cpu_to_le16(size); + USB_PRINTF("usb_control_msg: request: 0x%X, requesttype: 0x%X, " \ + "value 0x%X index 0x%X length 0x%X\n", + request, requesttype, value, index, size); + dev->status = USB_ST_NOT_PROC; /*not yet processed */ + + host->submit_control_msg(dev, pipe, data, size, &setup_packet); + if (timeout == 0) + return (int)size; + + if (dev->status != 0) { + /* + * Let's wait a while for the timeout to elapse. + * It has no real use, but it keeps the interface happy. + */ + wait_ms(timeout); + return -1; + } + + return dev->act_len; +} + +/*------------------------------------------------------------------- + * submits bulk message, and waits for completion. returns 0 if Ok or + * -1 if Error. + * synchronous behavior + */ +int usb_bulk_msg(struct usb_device *dev, unsigned int pipe, + void *data, int len, int *actual_length, int timeout) +{ + struct usb_host *host = dev->host; + + if (len < 0) + return -1; + dev->status = USB_ST_NOT_PROC; /*not yet processed */ + host->submit_bulk_msg(dev, pipe, data, len); + while (timeout--) { + if (!((volatile unsigned long)dev->status & USB_ST_NOT_PROC)) + break; + wait_ms(1); + } + *actual_length = dev->act_len; + if (dev->status == 0) + return 0; + else + return -1; +} + + +/*------------------------------------------------------------------- + * Max Packet stuff + */ + +/* + * returns the max packet size, depending on the pipe direction and + * the configurations values + */ +int usb_maxpacket(struct usb_device *dev, unsigned long pipe) +{ + /* direction is out -> use emaxpacket out */ + if ((pipe & USB_DIR_IN) == 0) + return dev->epmaxpacketout[((pipe>>15) & 0xf)]; + else + return dev->epmaxpacketin[((pipe>>15) & 0xf)]; +} + +/*********************************************************************** + * Clears an endpoint + * endp: endpoint number in bits 0-3; + * direction flag in bit 7 (1 = IN, 0 = OUT) + */ +int usb_clear_halt(struct usb_device *dev, int pipe) +{ + int result; + int endp = usb_pipeendpoint(pipe)|(usb_pipein(pipe)<<7); + + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, 0, + endp, NULL, 0, USB_CNTL_TIMEOUT * 3); + + /* don't clear if failed */ + if (result < 0) + return result; + + /* + * NOTE: we do not get status and verify reset was successful + * as some devices are reported to lock up upon this check.. + */ + + usb_endpoint_running(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)); + + /* toggle is reset on clear */ + usb_settoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe), 0); + return 0; +} + +/********************************************************************** + * gets configuration cfgno and store it in the buffer + */ +int usb_get_configuration_no(struct usb_device *dev, + unsigned char *buffer, int cfgno) +{ + int result; + unsigned int tmp; + struct usb_config_descriptor *config; + + + config = (struct usb_config_descriptor *)&buffer[0]; + result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, buffer, 9); + if (result < 9) { + if (result < 0) + printf("unable to get descriptor, error %lX\n", + dev->status); + else + printf("config descriptor too short " \ + "(expected %i, got %i)\n", 9, result); + return -1; + } + tmp = le16_to_cpu(config->wTotalLength); + + if (tmp > USB_BUFSIZ) { + USB_PRINTF("usb_get_configuration_no: failed to get " \ + "descriptor - too long: %d\n", tmp); + return -1; + } + + result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, buffer, tmp); + USB_PRINTF("get_conf_no %d Result %d, wLength %d\n", + cfgno, result, tmp); + return result; +} + +/******************************************************************** + * set interface number to interface + */ +int usb_set_interface(struct usb_device *dev, int interface, int alternate) +{ + struct usb_interface_descriptor *if_face = NULL; + int ret, i; + + for (i = 0; i < dev->config.bNumInterfaces; i++) { + if (dev->config.if_desc[i].bInterfaceNumber == interface) { + if_face = &dev->config.if_desc[i]; + break; + } + } + if (!if_face) { + printf("selecting invalid interface %d", interface); + return -1; + } + /* + * We should return now for devices with only one alternate setting. + * According to 9.4.10 of the Universal Serial Bus Specification + * Revision 2.0 such devices can return with a STALL. This results in + * some USB sticks timeouting during initialization and then being + * unusable in U-Boot. + */ + if (if_face->num_altsetting == 1) + return 0; + + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE, + alternate, interface, NULL, 0, + USB_CNTL_TIMEOUT * 5); + if (ret < 0) + return ret; + + return 0; +} + +/******************************************************************** + * set protocol to protocol + */ +int usb_set_protocol(struct usb_device *dev, int ifnum, int protocol) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_PROTOCOL, USB_TYPE_CLASS | USB_RECIP_INTERFACE, + protocol, ifnum, NULL, 0, USB_CNTL_TIMEOUT); +} + +/******************************************************************** + * set idle + */ +int usb_set_idle(struct usb_device *dev, int ifnum, int duration, int report_id) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_IDLE, USB_TYPE_CLASS | USB_RECIP_INTERFACE, + (duration << 8) | report_id, ifnum, NULL, 0, USB_CNTL_TIMEOUT); +} + +/******************************************************************** + * get report + */ +int usb_get_report(struct usb_device *dev, int ifnum, unsigned char type, + unsigned char id, void *buf, int size) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + (type << 8) + id, ifnum, buf, size, USB_CNTL_TIMEOUT); +} + +/******************************************************************** + * get class descriptor + */ +int usb_get_class_descriptor(struct usb_device *dev, int ifnum, + unsigned char type, unsigned char id, void *buf, int size) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_RECIP_INTERFACE | USB_DIR_IN, + (type << 8) + id, ifnum, buf, size, USB_CNTL_TIMEOUT); +} + +/******************************************************************** + * get string index in buffer + */ +int usb_get_string(struct usb_device *dev, unsigned short langid, + unsigned char index, void *buf, int size) +{ + int i; + int result; + + for (i = 0; i < 3; ++i) { + /* some devices are flaky */ + result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + (USB_DT_STRING << 8) + index, langid, buf, size, + USB_CNTL_TIMEOUT); + + if (result > 0) + break; + } + + return result; +} + + +static void usb_try_string_workarounds(unsigned char *buf, int *length) +{ + int newlength, oldlength = *length; + + for (newlength = 2; newlength + 1 < oldlength; newlength += 2) + if (!isprint(buf[newlength]) || buf[newlength + 1]) + break; + + if (newlength > 2) { + buf[0] = newlength; + *length = newlength; + } +} + + +static int usb_string_sub(struct usb_device *dev, unsigned int langid, + unsigned int index, unsigned char *buf) +{ + int rc; + + /* Try to read the string descriptor by asking for the maximum + * possible number of bytes */ + rc = usb_get_string(dev, langid, index, buf, 255); + + /* If that failed try to read the descriptor length, then + * ask for just that many bytes */ + if (rc < 2) { + rc = usb_get_string(dev, langid, index, buf, 2); + if (rc == 2) + rc = usb_get_string(dev, langid, index, buf, buf[0]); + } + + if (rc >= 2) { + if (!buf[0] && !buf[1]) + usb_try_string_workarounds(buf, &rc); + + /* There might be extra junk at the end of the descriptor */ + if (buf[0] < rc) + rc = buf[0]; + + rc = rc - (rc & 1); /* force a multiple of two */ + } + + if (rc < 2) + rc = -1; + + return rc; +} + + +/******************************************************************** + * usb_string: + * Get string index and translate it to ascii. + * returns string length (> 0) or error (< 0) + */ +int usb_string(struct usb_device *dev, int index, char *buf, size_t size) +{ + unsigned char mybuf[USB_BUFSIZ]; + unsigned char *tbuf; + int err; + unsigned int u, idx; + + if (size <= 0 || !buf || !index) + return -1; + buf[0] = 0; + tbuf = &mybuf[0]; + + /* get langid for strings if it's not yet known */ + if (!dev->have_langid) { + err = usb_string_sub(dev, 0, 0, tbuf); + if (err < 0) { + USB_PRINTF("error getting string descriptor 0 " \ + "(error=%lx)\n", dev->status); + return -1; + } else if (tbuf[0] < 4) { + USB_PRINTF("string descriptor 0 too short\n"); + return -1; + } else { + dev->have_langid = -1; + dev->string_langid = tbuf[2] | (tbuf[3] << 8); + /* always use the first langid listed */ + USB_PRINTF("USB device number %d default " \ + "language ID 0x%x\n", + dev->devnum, dev->string_langid); + } + } + + err = usb_string_sub(dev, dev->string_langid, index, tbuf); + if (err < 0) + return err; + + size--; /* leave room for trailing NULL char in output buffer */ + for (idx = 0, u = 2; u < err; u += 2) { + if (idx >= size) + break; + if (tbuf[u+1]) /* high byte */ + buf[idx++] = '?'; /* non-ASCII character */ + else + buf[idx++] = tbuf[u]; + } + buf[idx] = 0; + err = idx; + return err; +} + +/**************************************************************************** + * HUB "Driver" + * Probes device for being a hub and configurate it + */ + +#undef USB_HUB_DEBUG + +#ifdef USB_HUB_DEBUG +#define USB_HUB_PRINTF(fmt, args...) printf(fmt , ##args) +#else +#define USB_HUB_PRINTF(fmt, args...) +#endif + +int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB, + USB_DT_HUB << 8, 0, data, size, USB_CNTL_TIMEOUT); +} + +int usb_clear_hub_feature(struct usb_device *dev, int feature) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_CLEAR_FEATURE, USB_RT_HUB, feature, + 0, NULL, 0, USB_CNTL_TIMEOUT); +} + +int usb_clear_port_feature(struct usb_device *dev, int port, int feature) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, + port, NULL, 0, USB_CNTL_TIMEOUT); +} + +int usb_set_port_feature(struct usb_device *dev, int port, int feature) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, feature, + port, NULL, 0, USB_CNTL_TIMEOUT); +} + +int usb_get_hub_status(struct usb_device *dev, void *data) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0, + data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT); +} + +int usb_get_port_status(struct usb_device *dev, int port, void *data) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port, + data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT); +} + + +static void usb_hub_power_on(struct usb_hub_device *hub) +{ + int i; + struct usb_device *dev; + + dev = hub->pusb_dev; + /* Enable power to the ports */ + USB_HUB_PRINTF("enabling power on all ports\n"); + for (i = 0; i < dev->maxchild; i++) { + usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); + USB_HUB_PRINTF("port %d returns %lX\n", i + 1, dev->status); + wait_ms(hub->desc.bPwrOn2PwrGood * 2); + } +} + +#define MAX_TRIES 5 + +static inline char *portspeed(int portstatus) +{ + if (portstatus & (1 << USB_PORT_FEAT_HIGHSPEED)) + return "480 Mb/s"; + else if (portstatus & (1 << USB_PORT_FEAT_LOWSPEED)) + return "1.5 Mb/s"; + else + return "12 Mb/s"; +} + +static int hub_port_reset(struct usb_device *dev, int port, + unsigned short *portstat) +{ + int tries; + struct usb_port_status portsts; + unsigned short portstatus, portchange; + + USB_HUB_PRINTF("hub_port_reset: resetting port %d...\n", port); + for (tries = 0; tries < MAX_TRIES; tries++) { + + usb_set_port_feature(dev, port + 1, USB_PORT_FEAT_RESET); + wait_ms(200); + + if (usb_get_port_status(dev, port + 1, &portsts) < 0) { + USB_HUB_PRINTF("get_port_status failed status %lX\n", + dev->status); + return -1; + } + portstatus = le16_to_cpu(portsts.wPortStatus); + portchange = le16_to_cpu(portsts.wPortChange); + + USB_HUB_PRINTF("portstatus %x, change %x, %s\n", + portstatus, portchange, + portspeed(portstatus)); + + USB_HUB_PRINTF("STAT_C_CONNECTION = %d STAT_CONNECTION = %d" \ + " USB_PORT_STAT_ENABLE %d\n", + (portchange & USB_PORT_STAT_C_CONNECTION) ? 1 : 0, + (portstatus & USB_PORT_STAT_CONNECTION) ? 1 : 0, + (portstatus & USB_PORT_STAT_ENABLE) ? 1 : 0); + + if ((portchange & USB_PORT_STAT_C_CONNECTION) || + !(portstatus & USB_PORT_STAT_CONNECTION)) + return -1; + + if (portstatus & USB_PORT_STAT_ENABLE) + break; + + wait_ms(200); + } + + if (tries == MAX_TRIES) { + USB_HUB_PRINTF("Cannot enable port %i after %i retries, " \ + "disabling port.\n", port + 1, MAX_TRIES); + USB_HUB_PRINTF("Maybe the USB cable is bad?\n"); + return -1; + } + + usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_RESET); + *portstat = portstatus; + return 0; +} + + +static void usb_hub_port_connect_change(struct usb_device *dev, int port) +{ + struct usb_device *usb; + struct usb_port_status portsts; + unsigned short portstatus, portchange; + + /* Check status */ + if (usb_get_port_status(dev, port + 1, &portsts) < 0) { + USB_HUB_PRINTF("get_port_status failed\n"); + return; + } + + portstatus = le16_to_cpu(portsts.wPortStatus); + portchange = le16_to_cpu(portsts.wPortChange); + USB_HUB_PRINTF("portstatus %x, change %x, %s\n", + portstatus, portchange, portspeed(portstatus)); + + /* Clear the connection change status */ + usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_CONNECTION); + + /* Disconnect any existing devices under this port */ + if (((!(portstatus & USB_PORT_STAT_CONNECTION)) && + (!(portstatus & USB_PORT_STAT_ENABLE))) || (dev->children[port])) { + USB_HUB_PRINTF("usb_disconnect(&hub->children[port]);\n"); + /* Return now if nothing is connected */ + if (!(portstatus & USB_PORT_STAT_CONNECTION)) + return; + } + wait_ms(200); + + /* Reset the port */ + if (hub_port_reset(dev, port, &portstatus) < 0) { + printf("cannot reset port %i!?\n", port + 1); + return; + } + + wait_ms(200); + + /* Allocate a new device struct for it */ + usb = usb_alloc_new_device(); + usb->host = dev->host; + + if (portstatus & USB_PORT_STAT_HIGH_SPEED) + usb->speed = USB_SPEED_HIGH; + else if (portstatus & USB_PORT_STAT_LOW_SPEED) + usb->speed = USB_SPEED_LOW; + else + usb->speed = USB_SPEED_FULL; + + dev->children[port] = usb; + usb->parent = dev; + /* Run it through the hoops (find a driver, etc) */ + if (usb_new_device(usb)) { + /* Woops, disable the port */ + USB_HUB_PRINTF("hub: disabling port %d\n", port + 1); + usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE); + } +} + + +int usb_hub_configure(struct usb_device *dev) +{ + unsigned char buffer[USB_BUFSIZ], *bitmap; + struct usb_hub_descriptor *descriptor; + struct usb_hub_status *hubsts; + int i; + struct usb_hub_device *hub; + + hub = xzalloc(sizeof (*hub)); + dev->hub = hub; + + hub->pusb_dev = dev; + /* Get the the hub descriptor */ + if (usb_get_hub_descriptor(dev, buffer, 4) < 0) { + USB_HUB_PRINTF("%s: failed to get hub " \ + "descriptor, giving up %lX\n", __func__, dev->status); + return -1; + } + descriptor = (struct usb_hub_descriptor *)buffer; + + /* silence compiler warning if USB_BUFSIZ is > 256 [= sizeof(char)] */ + i = descriptor->bLength; + if (i > USB_BUFSIZ) { + USB_HUB_PRINTF("%s: failed to get hub " \ + "descriptor - too long: %d\n", __func__, + descriptor->bLength); + return -1; + } + + if (usb_get_hub_descriptor(dev, buffer, descriptor->bLength) < 0) { + USB_HUB_PRINTF("%s: failed to get hub " \ + "descriptor 2nd giving up %lX\n", __func__, dev->status); + return -1; + } + memcpy((unsigned char *)&hub->desc, buffer, descriptor->bLength); + /* adjust 16bit values */ + hub->desc.wHubCharacteristics = + le16_to_cpu(descriptor->wHubCharacteristics); + /* set the bitmap */ + bitmap = (unsigned char *)&hub->desc.DeviceRemovable[0]; + /* devices not removable by default */ + memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); + bitmap = (unsigned char *)&hub->desc.PortPowerCtrlMask[0]; + memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); /* PowerMask = 1B */ + + for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++) + hub->desc.DeviceRemovable[i] = descriptor->DeviceRemovable[i]; + + for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++) + hub->desc.DeviceRemovable[i] = descriptor->PortPowerCtrlMask[i]; + + dev->maxchild = descriptor->bNbrPorts; + USB_HUB_PRINTF("%d ports detected\n", dev->maxchild); + + switch (hub->desc.wHubCharacteristics & HUB_CHAR_LPSM) { + case 0x00: + USB_HUB_PRINTF("ganged power switching\n"); + break; + case 0x01: + USB_HUB_PRINTF("individual port power switching\n"); + break; + case 0x02: + case 0x03: + USB_HUB_PRINTF("unknown reserved power switching mode\n"); + break; + } + + if (hub->desc.wHubCharacteristics & HUB_CHAR_COMPOUND) + USB_HUB_PRINTF("part of a compound device\n"); + else + USB_HUB_PRINTF("standalone hub\n"); + + switch (hub->desc.wHubCharacteristics & HUB_CHAR_OCPM) { + case 0x00: + USB_HUB_PRINTF("global over-current protection\n"); + break; + case 0x08: + USB_HUB_PRINTF("individual port over-current protection\n"); + break; + case 0x10: + case 0x18: + USB_HUB_PRINTF("no over-current protection\n"); + break; + } + + USB_HUB_PRINTF("power on to power good time: %dms\n", + descriptor->bPwrOn2PwrGood * 2); + USB_HUB_PRINTF("hub controller current requirement: %dmA\n", + descriptor->bHubContrCurrent); + + for (i = 0; i < dev->maxchild; i++) + USB_HUB_PRINTF("port %d is%s removable\n", i + 1, + hub->desc.DeviceRemovable[(i + 1) / 8] & \ + (1 << ((i + 1) % 8)) ? " not" : ""); + + if (sizeof(struct usb_hub_status) > USB_BUFSIZ) { + USB_HUB_PRINTF("%s: failed to get Status - " \ + "too long: %d\n", __func__, descriptor->bLength); + return -1; + } + + if (usb_get_hub_status(dev, buffer) < 0) { + USB_HUB_PRINTF("%s: failed to get Status %lX\n", __func__, + dev->status); + return -1; + } + + hubsts = (struct usb_hub_status *)buffer; + USB_HUB_PRINTF("get_hub_status returned status %X, change %X\n", + le16_to_cpu(hubsts->wHubStatus), + le16_to_cpu(hubsts->wHubChange)); + USB_HUB_PRINTF("local power source is %s\n", + (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_LOCAL_POWER) ? \ + "lost (inactive)" : "good"); + USB_HUB_PRINTF("%sover-current condition exists\n", + (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? \ + "" : "no "); + usb_hub_power_on(hub); + + for (i = 0; i < dev->maxchild; i++) { + struct usb_port_status portsts; + unsigned short portstatus, portchange; + + if (usb_get_port_status(dev, i + 1, &portsts) < 0) { + USB_HUB_PRINTF("get_port_status failed\n"); + continue; + } + + portstatus = le16_to_cpu(portsts.wPortStatus); + portchange = le16_to_cpu(portsts.wPortChange); + USB_HUB_PRINTF("Port %d Status %X Change %X\n", + i + 1, portstatus, portchange); + + if (portchange & USB_PORT_STAT_C_CONNECTION) { + USB_HUB_PRINTF("port %d connection change\n", i + 1); + usb_hub_port_connect_change(dev, i); + } + if (portchange & USB_PORT_STAT_C_ENABLE) { + USB_HUB_PRINTF("port %d enable change, status %x\n", + i + 1, portstatus); + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_C_ENABLE); + + /* EM interference sometimes causes bad shielded USB + * devices to be shutdown by the hub, this hack enables + * them again. Works at least with mouse driver */ + if (!(portstatus & USB_PORT_STAT_ENABLE) && + (portstatus & USB_PORT_STAT_CONNECTION) && + ((dev->children[i]))) { + USB_HUB_PRINTF("already running port %i " \ + "disabled by hub (EMI?), " \ + "re-enabling...\n", i + 1); + usb_hub_port_connect_change(dev, i); + } + } + if (portstatus & USB_PORT_STAT_SUSPEND) { + USB_HUB_PRINTF("port %d suspend change\n", i + 1); + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_SUSPEND); + } + + if (portchange & USB_PORT_STAT_C_OVERCURRENT) { + USB_HUB_PRINTF("port %d over-current change\n", i + 1); + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_C_OVER_CURRENT); + usb_hub_power_on(hub); + } + + if (portchange & USB_PORT_STAT_C_RESET) { + USB_HUB_PRINTF("port %d reset change\n", i + 1); + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_C_RESET); + } + } /* end for i all ports */ + + return 0; +} + +static int usb_hub_probe(struct usb_device *dev, int ifnum) +{ + struct usb_interface_descriptor *iface; + struct usb_endpoint_descriptor *ep; + int ret; + + iface = &dev->config.if_desc[ifnum]; + /* Is it a hub? */ + if (iface->bInterfaceClass != USB_CLASS_HUB) + return 0; + /* Some hubs have a subclass of 1, which AFAICT according to the */ + /* specs is not defined, but it works */ + if ((iface->bInterfaceSubClass != 0) && + (iface->bInterfaceSubClass != 1)) + return 0; + /* Multiple endpoints? What kind of mutant ninja-hub is this? */ + if (iface->bNumEndpoints != 1) + return 0; + ep = &iface->ep_desc[0]; + /* Output endpoint? Curiousier and curiousier.. */ + if (!(ep->bEndpointAddress & USB_DIR_IN)) + return 0; + /* If it's not an interrupt endpoint, we'd better punt! */ + if ((ep->bmAttributes & 3) != 3) + return 0; + /* We found a hub */ + USB_HUB_PRINTF("USB hub found\n"); + ret = usb_hub_configure(dev); + return ret; +} + +int usb_driver_register(struct usb_driver *drv) +{ + drv->driver.bus = &usb_bus_type; + return register_driver(&drv->driver); +} + +/* returns 0 if no match, 1 if match */ +int usb_match_device(struct usb_device *dev, const struct usb_device_id *id) +{ + if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + id->idVendor != le16_to_cpu(dev->descriptor.idVendor)) + return 0; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->idProduct != le16_to_cpu(dev->descriptor.idProduct)) + return 0; + + return 1; +} + +/* returns 0 if no match, 1 if match */ +int usb_match_one_id(struct usb_device *usbdev, + const struct usb_device_id *id) +{ + /* proc_connectinfo in devio.c may call us with id == NULL. */ + if (id == NULL) + return 0; + + if (!usb_match_device(usbdev, id)) + return 0; + + return 1; +} +EXPORT_SYMBOL(usb_match_one_id); + +const struct usb_device_id *usb_match_id(struct usb_device *usbdev, + const struct usb_device_id *id) +{ + /* proc_connectinfo in devio.c may call us with id == NULL. */ + if (id == NULL) + return NULL; + + /* It is important to check that id->driver_info is nonzero, + since an entry that is all zeroes except for a nonzero + id->driver_info is the way to create an entry that + indicates that the driver want to examine every + device and interface. */ + for (; id->idVendor || id->idProduct || id->bDeviceClass || + id->bInterfaceClass || id->driver_info; id++) { + if (usb_match_one_id(usbdev, id)) + return id; + } + + return NULL; +} +EXPORT_SYMBOL(usb_match_id); + +static int usb_match(struct device_d *dev, struct driver_d *drv) +{ + struct usb_device *usbdev = container_of(dev, struct usb_device, dev); + struct usb_driver *usbdrv = container_of(dev->driver, struct usb_driver, driver); + const struct usb_device_id *id; + + debug("matching: 0x%04x 0x%04x\n", usbdev->descriptor.idVendor, usbdev->descriptor.idProduct); + + id = usb_match_id(usbdev, usbdrv->id_table); + if (id) { + debug("match: 0x%04x 0x%04x\n", id->idVendor, id->idProduct); + return 0; + } + return 1; +} + +static int usb_probe(struct device_d *dev) +{ + struct usb_device *usbdev = container_of(dev, struct usb_device, dev); + struct usb_driver *usbdrv = container_of(dev->driver, struct usb_driver, driver); + const struct usb_device_id *id; + + id = usb_match_id(usbdev, usbdrv->id_table); + + return usbdrv->probe(usbdev, id); +} + +static void usb_remove(struct device_d *dev) +{ + struct usb_device *usbdev = container_of(dev, struct usb_device, dev); + struct usb_driver *usbdrv = container_of(dev->driver, struct usb_driver, driver); + + usbdrv->disconnect(usbdev); +} + +struct bus_type usb_bus_type = { + .name = "usb", + .match = usb_match, + .probe = usb_probe, + .remove = usb_remove, +}; + diff --git a/drivers/usb/usb_ehci.h b/drivers/usb/usb_ehci.h new file mode 100644 index 0000000000..b3c1d5d728 --- /dev/null +++ b/drivers/usb/usb_ehci.h @@ -0,0 +1,194 @@ +/*- + * Copyright (c) 2007-2008, Juniper Networks, Inc. + * Copyright (c) 2008, Michael Trimarchi <trimarchimichael@yahoo.it> + * All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef USB_EHCI_H +#define USB_EHCI_H + +#if !defined(CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS) +#define CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS 2 +#endif + +/* (shifted) direction/type/recipient from the USB 2.0 spec, table 9.2 */ +#define DeviceRequest \ + ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8) + +#define DeviceOutRequest \ + ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8) + +#define InterfaceRequest \ + ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) + +#define EndpointRequest \ + ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) + +#define EndpointOutRequest \ + ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) + +/* + * Register Space. + */ +struct ehci_hccr { + uint32_t cr_capbase; +#define HC_LENGTH(p) (((p) >> 0) & 0x00ff) +#define HC_VERSION(p) (((p) >> 16) & 0xffff) + uint32_t cr_hcsparams; +#define HCS_PPC(p) ((p) & (1 << 4)) +#define HCS_INDICATOR(p) ((p) & (1 << 16)) /* Port indicators */ +#define HCS_N_PORTS(p) (((p) >> 0) & 0xf) + uint32_t cr_hccparams; + uint8_t cr_hcsp_portrt[8]; +} __attribute__ ((packed)); + +struct ehci_hcor { + uint32_t or_usbcmd; +#define CMD_PARK (1 << 11) /* enable "park" */ +#define CMD_PARK_CNT(c) (((c) >> 8) & 3) /* how many transfers to park */ +#define CMD_ASE (1 << 5) /* async schedule enable */ +#define CMD_LRESET (1 << 7) /* partial reset */ +#define CMD_IAAD (1 << 5) /* "doorbell" interrupt */ +#define CMD_PSE (1 << 4) /* periodic schedule enable */ +#define CMD_RESET (1 << 1) /* reset HC not bus */ +#define CMD_RUN (1 << 0) /* start/stop HC */ + uint32_t or_usbsts; +#define STD_ASS (1 << 15) +#define STS_HALT (1 << 12) + uint32_t or_usbintr; + uint32_t or_frindex; + uint32_t or_ctrldssegment; + uint32_t or_periodiclistbase; + uint32_t or_asynclistaddr; + uint32_t _reserved_[9]; + uint32_t or_configflag; +#define FLAG_CF (1 << 0) /* true: we'll support "high speed" */ + uint32_t or_portsc[CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS]; + uint32_t or_systune; +} __attribute__ ((packed)); + +#define USBMODE 0x68 /* USB Device mode */ +#define USBMODE_SDIS (1 << 3) /* Stream disable */ +#define USBMODE_BE (1 << 2) /* BE/LE endiannes select */ +#define USBMODE_CM_HC (3 << 0) /* host controller mode */ +#define USBMODE_CM_IDLE (0 << 0) /* idle state */ + +/* Interface descriptor */ +struct usb_linux_interface_descriptor { + unsigned char bLength; + unsigned char bDescriptorType; + unsigned char bInterfaceNumber; + unsigned char bAlternateSetting; + unsigned char bNumEndpoints; + unsigned char bInterfaceClass; + unsigned char bInterfaceSubClass; + unsigned char bInterfaceProtocol; + unsigned char iInterface; +} __attribute__ ((packed)); + +/* Configuration descriptor information.. */ +struct usb_linux_config_descriptor { + unsigned char bLength; + unsigned char bDescriptorType; + unsigned short wTotalLength; + unsigned char bNumInterfaces; + unsigned char bConfigurationValue; + unsigned char iConfiguration; + unsigned char bmAttributes; + unsigned char MaxPower; +} __attribute__ ((packed)); + +#if defined CONFIG_EHCI_DESC_BIG_ENDIAN +#define ehci_readl(x) (*((volatile u32 *)(x))) +#define ehci_writel(a, b) (*((volatile u32 *)(a)) = ((volatile u32)b)) +#else +#define ehci_readl(x) cpu_to_le32((*((volatile u32 *)(x)))) +#define ehci_writel(a, b) (*((volatile u32 *)(a)) = \ + cpu_to_le32(((volatile u32)b))) +#endif + +#if defined CONFIG_EHCI_MMIO_BIG_ENDIAN +#define hc32_to_cpu(x) be32_to_cpu((x)) +#define cpu_to_hc32(x) cpu_to_be32((x)) +#else +#define hc32_to_cpu(x) le32_to_cpu((x)) +#define cpu_to_hc32(x) cpu_to_le32((x)) +#endif + +#define EHCI_PS_WKOC_E (1 << 22) /* RW wake on over current */ +#define EHCI_PS_WKDSCNNT_E (1 << 21) /* RW wake on disconnect */ +#define EHCI_PS_WKCNNT_E (1 << 20) /* RW wake on connect */ +#define EHCI_PS_PO (1 << 13) /* RW port owner */ +#define EHCI_PS_PP (1 << 12) /* RW,RO port power */ +#define EHCI_PS_LS (3 << 10) /* RO line status */ +#define EHCI_PS_PR (1 << 8) /* RW port reset */ +#define EHCI_PS_SUSP (1 << 7) /* RW suspend */ +#define EHCI_PS_FPR (1 << 6) /* RW force port resume */ +#define EHCI_PS_OCC (1 << 5) /* RWC over current change */ +#define EHCI_PS_OCA (1 << 4) /* RO over current active */ +#define EHCI_PS_PEC (1 << 3) /* RWC port enable change */ +#define EHCI_PS_PE (1 << 2) /* RW port enable */ +#define EHCI_PS_CSC (1 << 1) /* RWC connect status change */ +#define EHCI_PS_CS (1 << 0) /* RO connect status */ +#define EHCI_PS_CLEAR (EHCI_PS_OCC | EHCI_PS_PEC | EHCI_PS_CSC) + +#define EHCI_PS_IS_LOWSPEED(x) (((x) & EHCI_PS_LS) == (1 << 10)) + +/* + * Schedule Interface Space. + * + * IMPORTANT: Software must ensure that no interface data structure + * reachable by the EHCI host controller spans a 4K page boundary! + * + * Periodic transfers (i.e. isochronous and interrupt transfers) are + * not supported. + */ + +/* Queue Element Transfer Descriptor (qTD). */ +struct qTD { + uint32_t qt_next; +#define QT_NEXT_TERMINATE 1 + uint32_t qt_altnext; + uint32_t qt_token; + uint32_t qt_buffer[5]; +}; + +/* Queue Head (QH). */ +struct QH { + uint32_t qh_link; +#define QH_LINK_TERMINATE 1 +#define QH_LINK_TYPE_ITD 0 +#define QH_LINK_TYPE_QH 2 +#define QH_LINK_TYPE_SITD 4 +#define QH_LINK_TYPE_FSTN 6 + uint32_t qh_endpt1; + uint32_t qh_endpt2; + uint32_t qh_curtd; + struct qTD qh_overlay; + /* + * Add dummy fill value to make the size of this struct + * aligned to 32 bytes + */ + uint8_t fill[16]; +}; + +/* Low level init functions */ +int ehci_hcd_init(void); +int ehci_hcd_stop(void); + +#endif /* USB_EHCI_H */ diff --git a/drivers/usb/usb_ehci_core.c b/drivers/usb/usb_ehci_core.c new file mode 100644 index 0000000000..590004c623 --- /dev/null +++ b/drivers/usb/usb_ehci_core.c @@ -0,0 +1,937 @@ +/*- + * Copyright (c) 2007-2008, Juniper Networks, Inc. + * Copyright (c) 2008, Excito Elektronik i Skåne AB + * Copyright (c) 2008, Michael Trimarchi <trimarchimichael@yahoo.it> + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +/*#define DEBUG */ +#include <common.h> +#include <asm/byteorder.h> +#include <usb/usb.h> +#include <asm/io.h> +#include <malloc.h> +#include <driver.h> +#include <init.h> +#include <xfuncs.h> +#include <clock.h> + +#include "usb_ehci.h" + +struct ehci_priv { + int rootdev; + struct ehci_hccr *hccr; + struct ehci_hcor *hcor; + struct usb_host host; + struct QH *qh_list; + void *qhp; + int portreset; +}; + +#define to_ehci(ptr) container_of(ptr, struct ehci_priv, host) + +static struct descriptor { + struct usb_hub_descriptor hub; + struct usb_device_descriptor device; + struct usb_linux_config_descriptor config; + struct usb_linux_interface_descriptor interface; + struct usb_endpoint_descriptor endpoint; +} __attribute__ ((packed)) descriptor = { + { + 0x8, /* bDescLength */ + 0x29, /* bDescriptorType: hub descriptor */ + 2, /* bNrPorts -- runtime modified */ + 0, /* wHubCharacteristics */ + 0xff, /* bPwrOn2PwrGood */ + 0, /* bHubCntrCurrent */ + {}, /* Device removable */ + {} /* at most 7 ports! XXX */ + }, + { + 0x12, /* bLength */ + 1, /* bDescriptorType: UDESC_DEVICE */ + 0x0002, /* bcdUSB: v2.0 */ + 9, /* bDeviceClass: UDCLASS_HUB */ + 0, /* bDeviceSubClass: UDSUBCLASS_HUB */ + 1, /* bDeviceProtocol: UDPROTO_HSHUBSTT */ + 64, /* bMaxPacketSize: 64 bytes */ + 0x0000, /* idVendor */ + 0x0000, /* idProduct */ + 0x0001, /* bcdDevice */ + 1, /* iManufacturer */ + 2, /* iProduct */ + 0, /* iSerialNumber */ + 1 /* bNumConfigurations: 1 */ + }, + { + 0x9, + 2, /* bDescriptorType: UDESC_CONFIG */ + cpu_to_le16(0x19), + 1, /* bNumInterface */ + 1, /* bConfigurationValue */ + 0, /* iConfiguration */ + 0x40, /* bmAttributes: UC_SELF_POWER */ + 0 /* bMaxPower */ + }, + { + 0x9, /* bLength */ + 4, /* bDescriptorType: UDESC_INTERFACE */ + 0, /* bInterfaceNumber */ + 0, /* bAlternateSetting */ + 1, /* bNumEndpoints */ + 9, /* bInterfaceClass: UICLASS_HUB */ + 0, /* bInterfaceSubClass: UISUBCLASS_HUB */ + 0, /* bInterfaceProtocol: UIPROTO_HSHUBSTT */ + 0 /* iInterface */ + }, + { + 0x7, /* bLength */ + 5, /* bDescriptorType: UDESC_ENDPOINT */ + 0x81, /* bEndpointAddress: + * UE_DIR_IN | EHCI_INTR_ENDPT + */ + 3, /* bmAttributes: UE_INTERRUPT */ + 8, 0, /* wMaxPacketSize */ + 255 /* bInterval */ + }, +}; +#define CONFIG_EHCI_IS_TDI // FIXME + +#if defined(CONFIG_EHCI_IS_TDI) +#define ehci_is_TDI() (1) +#else +#define ehci_is_TDI() (0) +#endif + +#if defined(CONFIG_EHCI_DCACHE) +/* + * Routines to handle (flush/invalidate) the dcache for the QH and qTD + * structures and data buffers. This is needed on platforms using this + * EHCI support with dcache enabled. + */ +static void flush_invalidate(u32 addr, int size, int flush) +{ + if (flush) + flush_dcache_range(addr, addr + size); + else + invalidate_dcache_range(addr, addr + size); +} + +static void cache_qtd(struct qTD *qtd, int flush) +{ + u32 *ptr = (u32 *)qtd->qt_buffer[0]; + int len = (qtd->qt_token & 0x7fff0000) >> 16; + + flush_invalidate((u32)qtd, sizeof(struct qTD), flush); + if (ptr && len) + flush_invalidate((u32)ptr, len, flush); +} + + +static inline struct QH *qh_addr(struct QH *qh) +{ + return (struct QH *)((u32)qh & 0xffffffe0); +} + +static void cache_qh(struct QH *qh, int flush) +{ + struct qTD *qtd; + struct qTD *next; + static struct qTD *first_qtd; + + /* + * Walk the QH list and flush/invalidate all entries + */ + while (1) { + printf("huhu\n"); + flush_invalidate((u32)qh_addr(qh), sizeof(struct QH), flush); + if ((u32)qh & QH_LINK_TYPE_QH) + break; + qh = qh_addr(qh); + qh = (struct QH *)qh->qh_link; + } + qh = qh_addr(qh); + + /* + * Save first qTD pointer, needed for invalidating pass on this QH + */ + if (flush) + first_qtd = qtd = (struct qTD *)(*(u32 *)&qh->qh_overlay & + 0xffffffe0); + else + qtd = first_qtd; + + /* + * Walk the qTD list and flush/invalidate all entries + */ + while (1) { + printf("haha\n"); + if (qtd == NULL) + break; + cache_qtd(qtd, flush); + next = (struct qTD *)((u32)qtd->qt_next & 0xffffffe0); + if (next == qtd) + break; + qtd = next; + } +} + +static inline void ehci_flush_dcache(struct QH *qh) +{ + cache_qh(qh, 1); +} + +static inline void ehci_invalidate_dcache(struct QH *qh) +{ + cache_qh(qh, 0); +} +#else /* CONFIG_EHCI_DCACHE */ +/* + * + */ +static inline void ehci_flush_dcache(struct QH *qh) +{ +} + +static inline void ehci_invalidate_dcache(struct QH *qh) +{ +} +#endif /* CONFIG_EHCI_DCACHE */ + +static int handshake(uint32_t *ptr, uint32_t mask, uint32_t done, int usec) +{ + uint32_t result; + + do { + result = ehci_readl(ptr); + if (result == ~(uint32_t)0) + return -1; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -1; +} + +static void ehci_free(void *p, size_t sz) +{ + +} + +static int ehci_reset(struct ehci_priv *ehci) +{ + uint32_t cmd; + uint32_t tmp; + uint32_t *reg_ptr; + int ret = 0; + + cmd = ehci_readl(&ehci->hcor->or_usbcmd); + cmd |= CMD_RESET; + ehci_writel(&ehci->hcor->or_usbcmd, cmd); + ret = handshake(&ehci->hcor->or_usbcmd, CMD_RESET, 0, 250 * 1000); + if (ret < 0) { + printf("EHCI fail to reset\n"); + goto out; + } + + if (ehci_is_TDI()) { + reg_ptr = (uint32_t *)((u8 *)&ehci->hcor + USBMODE); + tmp = ehci_readl(reg_ptr); + tmp |= USBMODE_CM_HC; +#if defined(CONFIG_EHCI_MMIO_BIG_ENDIAN) + tmp |= USBMODE_BE; +#endif + ehci_writel(reg_ptr, tmp); + } +out: + return ret; +} + +static void *ehci_alloc(size_t sz, size_t align) +{ + static struct QH qh __attribute__((aligned(32))); + static struct qTD td[3] __attribute__((aligned (32))); + static int ntds; + void *p; + + switch (sz) { + case sizeof(struct QH): + p = &qh; + ntds = 0; + break; + case sizeof(struct qTD): + if (ntds == 3) { + debug("out of TDs\n"); + return NULL; + } + p = &td[ntds]; + ntds++; + break; + default: + debug("unknown allocation size\n"); + return NULL; + } + + memset(p, sz, 0); + return p; +} + +static int ehci_td_buffer(struct qTD *td, void *buf, size_t sz) +{ + uint32_t addr, delta, next; + int idx; + + addr = (uint32_t) buf; + idx = 0; + while (idx < 5) { + td->qt_buffer[idx] = cpu_to_hc32(addr); + next = (addr + 4096) & ~4095; + delta = next - addr; + if (delta >= sz) + break; + sz -= delta; + addr = next; + idx++; + } + + if (idx == 5) { + debug("out of buffer pointers (%u bytes left)\n", sz); + return -1; + } + + return 0; +} + +static int +ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer, + int length, struct devrequest *req) +{ + struct usb_host *host = dev->host; + struct ehci_priv *ehci = to_ehci(host); + struct QH *qh; + struct qTD *td; + volatile struct qTD *vtd; + uint32_t *tdp; + uint32_t endpt, token, usbsts; + uint32_t c, toggle; + uint32_t cmd; + int ret = 0; + uint64_t start; + + debug("dev=%p, pipe=%lx, buffer=%p, length=%d, req=%p\n", dev, pipe, + buffer, length, req); + if (req != NULL) + debug("req=%u (%#x), type=%u (%#x), value=%u (%#x), index=%u\n", + req->request, req->request, + req->requesttype, req->requesttype, + le16_to_cpu(req->value), le16_to_cpu(req->value), + le16_to_cpu(req->index)); + + qh = ehci_alloc(sizeof(struct QH), 32); + if (!qh) { + printf("unable to allocate QH\n"); + return -1; + } + + qh->qh_link = cpu_to_hc32((uint32_t)ehci->qh_list | QH_LINK_TYPE_QH); + c = (usb_pipespeed(pipe) != USB_SPEED_HIGH && + usb_pipeendpoint(pipe) == 0) ? 1 : 0; + endpt = (8 << 28) | + (c << 27) | + (usb_maxpacket(dev, pipe) << 16) | + (0 << 15) | + (1 << 14) | + (usb_pipespeed(pipe) << 12) | + (usb_pipeendpoint(pipe) << 8) | + (0 << 7) | (usb_pipedevice(pipe) << 0); + qh->qh_endpt1 = cpu_to_hc32(endpt); + endpt = (1 << 30) | + (dev->portnr << 23) | + (dev->parent->devnum << 16) | (0 << 8) | (0 << 0); + qh->qh_endpt2 = cpu_to_hc32(endpt); + qh->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); + qh->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + + td = NULL; + tdp = &qh->qh_overlay.qt_next; + + toggle = + usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)); + + if (req != NULL) { + td = ehci_alloc(sizeof(struct qTD), 32); + if (!td) { + printf("unable to allocate SETUP td\n"); + goto fail; + } + td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); + td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + token = (0 << 31) | + (sizeof(*req) << 16) | + (0 << 15) | (0 << 12) | (3 << 10) | (2 << 8) | (0x80 << 0); + td->qt_token = cpu_to_hc32(token); + if (ehci_td_buffer(td, req, sizeof(*req)) != 0) { + debug("unable construct SETUP td\n"); + ehci_free(td, sizeof(*td)); + goto fail; + } + *tdp = cpu_to_hc32((uint32_t) td); + tdp = &td->qt_next; + + toggle = 1; + } + + if (length > 0 || req == NULL) { + td = ehci_alloc(sizeof(struct qTD), 32); + if (!td) { + printf("unable to allocate DATA td\n"); + goto fail; + } + td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); + td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + token = (toggle << 31) | + (length << 16) | + ((req == NULL ? 1 : 0) << 15) | + (0 << 12) | + (3 << 10) | + ((usb_pipein(pipe) ? 1 : 0) << 8) | (0x80 << 0); + td->qt_token = cpu_to_hc32(token); + if (ehci_td_buffer(td, buffer, length) != 0) { + printf("unable construct DATA td\n"); + ehci_free(td, sizeof(*td)); + goto fail; + } + *tdp = cpu_to_hc32((uint32_t) td); + tdp = &td->qt_next; + } + + if (req) { + td = ehci_alloc(sizeof(struct qTD), 32); + if (!td) { + printf("unable to allocate ACK td\n"); + goto fail; + } + td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); + td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + token = (toggle << 31) | + (0 << 16) | + (1 << 15) | + (0 << 12) | + (3 << 10) | + ((usb_pipein(pipe) ? 0 : 1) << 8) | (0x80 << 0); + td->qt_token = cpu_to_hc32(token); + *tdp = cpu_to_hc32((uint32_t)td); + tdp = &td->qt_next; + } + + ehci->qh_list->qh_link = cpu_to_hc32((uint32_t) qh | QH_LINK_TYPE_QH); + + /* Flush dcache */ + ehci_flush_dcache(ehci->qh_list); + + usbsts = ehci_readl(&ehci->hcor->or_usbsts); + ehci_writel(&ehci->hcor->or_usbsts, (usbsts & 0x3f)); + + /* Enable async. schedule. */ + cmd = ehci_readl(&ehci->hcor->or_usbcmd); + cmd |= CMD_ASE; + ehci_writel(&ehci->hcor->or_usbcmd, cmd); + + ret = handshake(&ehci->hcor->or_usbsts, STD_ASS, STD_ASS, 100 * 1000); + if (ret < 0) { + printf("EHCI fail timeout STD_ASS set\n"); + goto fail; + } + + /* Wait for TDs to be processed. */ + start = get_time_ns(); + vtd = td; + do { + /* Invalidate dcache */ + ehci_invalidate_dcache(ehci->qh_list); + token = hc32_to_cpu(vtd->qt_token); + if (is_timeout(start, SECOND)) { + printf("TD timeout\n"); + break; + } + } while (token & 0x80); + + /* Disable async schedule. */ + cmd = ehci_readl(&ehci->hcor->or_usbcmd); + cmd &= ~CMD_ASE; + ehci_writel(&ehci->hcor->or_usbcmd, cmd); + + ret = handshake(&ehci->hcor->or_usbsts, STD_ASS, 0, + 100 * 1000); + if (ret < 0) { + printf("EHCI fail timeout STD_ASS reset\n"); + goto fail; + } + + ehci->qh_list->qh_link = cpu_to_hc32((uint32_t)ehci->qh_list | QH_LINK_TYPE_QH); + + token = hc32_to_cpu(qh->qh_overlay.qt_token); + if (!(token & 0x80)) { + debug("TOKEN=%#x\n", token); + switch (token & 0xfc) { + case 0: + toggle = token >> 31; + usb_settoggle(dev, usb_pipeendpoint(pipe), + usb_pipeout(pipe), toggle); + dev->status = 0; + break; + case 0x40: + dev->status = USB_ST_STALLED; + break; + case 0xa0: + case 0x20: + dev->status = USB_ST_BUF_ERR; + break; + case 0x50: + case 0x10: + dev->status = USB_ST_BABBLE_DET; + break; + default: + dev->status = USB_ST_CRC_ERR; + break; + } + dev->act_len = length - ((token >> 16) & 0x7fff); + } else { + dev->act_len = 0; + debug("dev=%u, usbsts=%#x, p[1]=%#x, p[2]=%#x\n", + dev->devnum, ehci_readl(&ehci->hcor->or_usbsts), + ehci_readl(&ehci->hcor->or_portsc[0]), + ehci_readl(&ehci->hcor->or_portsc[1])); + } + + return (dev->status != USB_ST_NOT_PROC) ? 0 : -1; + +fail: + printf("fail1\n"); + td = (void *)hc32_to_cpu(qh->qh_overlay.qt_next); + while (td != (void *)QT_NEXT_TERMINATE) { + qh->qh_overlay.qt_next = td->qt_next; + ehci_free(td, sizeof(*td)); + td = (void *)hc32_to_cpu(qh->qh_overlay.qt_next); + } + ehci_free(qh, sizeof(*qh)); + return -1; +} + +static inline int min3(int a, int b, int c) +{ + + if (b < a) + a = b; + if (c < a) + a = c; + return a; +} + +int +ehci_submit_root(struct usb_device *dev, unsigned long pipe, void *buffer, + int length, struct devrequest *req) +{ + struct usb_host *host = dev->host; + struct ehci_priv *ehci = to_ehci(host); + uint8_t tmpbuf[4]; + u16 typeReq; + void *srcptr = NULL; + int len, srclen; + uint32_t reg; + uint32_t *status_reg; + + if (le16_to_cpu(req->index) >= CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS) { + printf("The request port(%d) is not configured\n", + le16_to_cpu(req->index) - 1); + return -1; + } + status_reg = (uint32_t *)&ehci->hcor->or_portsc[le16_to_cpu(req->index) - 1]; + srclen = 0; + + debug("req=%u (%#x), type=%u (%#x), value=%u, index=%u\n", + req->request, req->request, + req->requesttype, req->requesttype, + le16_to_cpu(req->value), le16_to_cpu(req->index)); + + typeReq = req->request | (req->requesttype << 8); + + switch (typeReq) { + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch (le16_to_cpu(req->value) >> 8) { + case USB_DT_DEVICE: + debug("USB_DT_DEVICE request\n"); + srcptr = &descriptor.device; + srclen = 0x12; + break; + case USB_DT_CONFIG: + debug("USB_DT_CONFIG config\n"); + srcptr = &descriptor.config; + srclen = 0x19; + break; + case USB_DT_STRING: + debug("USB_DT_STRING config\n"); + switch (le16_to_cpu(req->value) & 0xff) { + case 0: /* Language */ + srcptr = "\4\3\1\0"; + srclen = 4; + break; + case 1: /* Vendor */ + srcptr = "\16\3u\0-\0b\0o\0o\0t\0"; + srclen = 14; + break; + case 2: /* Product */ + srcptr = "\52\3E\0H\0C\0I\0 " + "\0H\0o\0s\0t\0 " + "\0C\0o\0n\0t\0r\0o\0l\0l\0e\0r\0"; + srclen = 42; + break; + default: + debug("unknown value DT_STRING %x\n", + le16_to_cpu(req->value)); + goto unknown; + } + break; + default: + debug("unknown value %x\n", le16_to_cpu(req->value)); + goto unknown; + } + break; + case ((USB_DIR_IN | USB_RT_HUB) << 8) | USB_REQ_GET_DESCRIPTOR: + switch (le16_to_cpu(req->value) >> 8) { + case USB_DT_HUB: + debug("USB_DT_HUB config\n"); + srcptr = &descriptor.hub; + srclen = 0x8; + break; + default: + debug("unknown value %x\n", le16_to_cpu(req->value)); + goto unknown; + } + break; + case USB_REQ_SET_ADDRESS | (USB_RECIP_DEVICE << 8): + debug("USB_REQ_SET_ADDRESS\n"); + ehci->rootdev = le16_to_cpu(req->value); + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + debug("USB_REQ_SET_CONFIGURATION\n"); + /* Nothing to do */ + break; + case USB_REQ_GET_STATUS | ((USB_DIR_IN | USB_RT_HUB) << 8): + tmpbuf[0] = 1; /* USB_STATUS_SELFPOWERED */ + tmpbuf[1] = 0; + srcptr = tmpbuf; + srclen = 2; + break; + case USB_REQ_GET_STATUS | ((USB_RT_PORT | USB_DIR_IN) << 8): + memset(tmpbuf, 0, 4); + reg = ehci_readl(status_reg); + if (reg & EHCI_PS_CS) + tmpbuf[0] |= USB_PORT_STAT_CONNECTION; + if (reg & EHCI_PS_PE) + tmpbuf[0] |= USB_PORT_STAT_ENABLE; + if (reg & EHCI_PS_SUSP) + tmpbuf[0] |= USB_PORT_STAT_SUSPEND; + if (reg & EHCI_PS_OCA) + tmpbuf[0] |= USB_PORT_STAT_OVERCURRENT; + if (reg & EHCI_PS_PR && + (ehci->portreset & (1 << le16_to_cpu(req->index)))) { + int ret; + /* force reset to complete */ + reg = reg & ~(EHCI_PS_PR | EHCI_PS_CLEAR); + ehci_writel(status_reg, reg); + ret = handshake(status_reg, EHCI_PS_PR, 0, 2 * 1000); + if (!ret) + tmpbuf[0] |= USB_PORT_STAT_RESET; + else + printf("port(%d) reset error\n", + le16_to_cpu(req->index) - 1); + } + if (reg & EHCI_PS_PP) + tmpbuf[1] |= USB_PORT_STAT_POWER >> 8; + + if (ehci_is_TDI()) { + switch ((reg >> 26) & 3) { + case 0: + break; + case 1: + tmpbuf[1] |= USB_PORT_STAT_LOW_SPEED >> 8; + break; + case 2: + default: + tmpbuf[1] |= USB_PORT_STAT_HIGH_SPEED >> 8; + break; + } + } else { + tmpbuf[1] |= USB_PORT_STAT_HIGH_SPEED >> 8; + } + + if (reg & EHCI_PS_CSC) + tmpbuf[2] |= USB_PORT_STAT_C_CONNECTION; + if (reg & EHCI_PS_PEC) + tmpbuf[2] |= USB_PORT_STAT_C_ENABLE; + if (reg & EHCI_PS_OCC) + tmpbuf[2] |= USB_PORT_STAT_C_OVERCURRENT; + if (ehci->portreset & (1 << le16_to_cpu(req->index))) + tmpbuf[2] |= USB_PORT_STAT_C_RESET; + + srcptr = tmpbuf; + srclen = 4; + break; + case USB_REQ_SET_FEATURE | ((USB_DIR_OUT | USB_RT_PORT) << 8): + reg = ehci_readl(status_reg); + reg &= ~EHCI_PS_CLEAR; + switch (le16_to_cpu(req->value)) { + case USB_PORT_FEAT_ENABLE: + reg |= EHCI_PS_PE; + ehci_writel(status_reg, reg); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(ehci_readl(&ehci->hccr->cr_hcsparams))) { + reg |= EHCI_PS_PP; + ehci_writel(status_reg, reg); + } + break; + case USB_PORT_FEAT_RESET: + if ((reg & (EHCI_PS_PE | EHCI_PS_CS)) == EHCI_PS_CS && + !ehci_is_TDI() && + EHCI_PS_IS_LOWSPEED(reg)) { + /* Low speed device, give up ownership. */ + debug("port %d low speed --> companion\n", + req->index - 1); + reg |= EHCI_PS_PO; + ehci_writel(status_reg, reg); + break; + } else { + reg |= EHCI_PS_PR; + reg &= ~EHCI_PS_PE; + ehci_writel(status_reg, reg); + /* + * caller must wait, then call GetPortStatus + * usb 2.0 specification say 50 ms resets on + * root + */ + wait_ms(50); + ehci->portreset |= 1 << le16_to_cpu(req->index); + } + break; + default: + debug("unknown feature %x\n", le16_to_cpu(req->value)); + goto unknown; + } + /* unblock posted writes */ + ehci_readl(&ehci->hcor->or_usbcmd); + break; + case USB_REQ_CLEAR_FEATURE | ((USB_DIR_OUT | USB_RT_PORT) << 8): + reg = ehci_readl(status_reg); + switch (le16_to_cpu(req->value)) { + case USB_PORT_FEAT_ENABLE: + reg &= ~EHCI_PS_PE; + break; + case USB_PORT_FEAT_C_ENABLE: + reg = (reg & ~EHCI_PS_CLEAR) | EHCI_PS_PE; + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(ehci_readl(&ehci->hccr->cr_hcsparams))) + reg = reg & ~(EHCI_PS_CLEAR | EHCI_PS_PP); + case USB_PORT_FEAT_C_CONNECTION: + reg = (reg & ~EHCI_PS_CLEAR) | EHCI_PS_CSC; + break; + case USB_PORT_FEAT_OVER_CURRENT: + reg = (reg & ~EHCI_PS_CLEAR) | EHCI_PS_OCC; + break; + case USB_PORT_FEAT_C_RESET: + ehci->portreset &= ~(1 << le16_to_cpu(req->index)); + break; + default: + debug("unknown feature %x\n", le16_to_cpu(req->value)); + goto unknown; + } + ehci_writel(status_reg, reg); + /* unblock posted write */ + ehci_readl(&ehci->hcor->or_usbcmd); + break; + default: + debug("Unknown request\n"); + goto unknown; + } + + wait_ms(1); + len = min3(srclen, le16_to_cpu(req->length), length); + if (srcptr != NULL && len > 0) + memcpy(buffer, srcptr, len); + else + debug("Len is 0\n"); + + dev->act_len = len; + dev->status = 0; + return 0; + +unknown: + debug("requesttype=%x, request=%x, value=%x, index=%x, length=%x\n", + req->requesttype, req->request, le16_to_cpu(req->value), + le16_to_cpu(req->index), le16_to_cpu(req->length)); + + dev->act_len = 0; + dev->status = USB_ST_STALLED; + return -1; +} + +static int ehci_init(struct usb_host *host) +{ + struct ehci_priv *ehci = to_ehci(host); + uint32_t reg; + uint32_t cmd; + + /* EHCI spec section 4.1 */ + if (ehci_reset(ehci) != 0) + return -1; + +#if defined(CONFIG_EHCI_HCD_INIT_AFTER_RESET) + if (ehci_hcd_init() != 0) + return -1; +#endif + + /* Set head of reclaim list */ + ehci->qhp = xzalloc(sizeof(struct QH) + 32); + ehci->qh_list = (struct QH *)(((unsigned long)ehci->qhp + 32) & ~31); + + ehci->qh_list->qh_link = cpu_to_hc32((uint32_t)ehci->qh_list | QH_LINK_TYPE_QH); + ehci->qh_list->qh_endpt1 = cpu_to_hc32((1 << 15) | (USB_SPEED_HIGH << 12)); + ehci->qh_list->qh_curtd = cpu_to_hc32(QT_NEXT_TERMINATE); + ehci->qh_list->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); + ehci->qh_list->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + ehci->qh_list->qh_overlay.qt_token = cpu_to_hc32(0x40); + + /* Set async. queue head pointer. */ + ehci_writel(&ehci->hcor->or_asynclistaddr, (uint32_t)ehci->qh_list); + + reg = ehci_readl(&ehci->hccr->cr_hcsparams); + descriptor.hub.bNbrPorts = HCS_N_PORTS(reg); + + /* Port Indicators */ + if (HCS_INDICATOR(reg)) + descriptor.hub.wHubCharacteristics |= 0x80; + /* Port Power Control */ + if (HCS_PPC(reg)) + descriptor.hub.wHubCharacteristics |= 0x01; + + /* Start the host controller. */ + cmd = ehci_readl(&ehci->hcor->or_usbcmd); + /* + * Philips, Intel, and maybe others need CMD_RUN before the + * root hub will detect new devices (why?); NEC doesn't + */ + cmd &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); + cmd |= CMD_RUN; + ehci_writel(&ehci->hcor->or_usbcmd, cmd); + + /* take control over the ports */ + cmd = ehci_readl(&ehci->hcor->or_configflag); + cmd |= FLAG_CF; + ehci_writel(&ehci->hcor->or_configflag, cmd); + /* unblock posted write */ + cmd = ehci_readl(&ehci->hcor->or_usbcmd); + wait_ms(5); + + ehci->rootdev = 0; + + return 0; +} + +static int +submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer, + int length) +{ + + if (usb_pipetype(pipe) != PIPE_BULK) { + debug("non-bulk pipe (type=%lu)", usb_pipetype(pipe)); + return -1; + } + return ehci_submit_async(dev, pipe, buffer, length, NULL); +} + +static int +submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, + int length, struct devrequest *setup) +{ + struct usb_host *host = dev->host; + struct ehci_priv *ehci = to_ehci(host); + + if (usb_pipetype(pipe) != PIPE_CONTROL) { + debug("non-control pipe (type=%lu)", usb_pipetype(pipe)); + return -1; + } + + if (usb_pipedevice(pipe) == ehci->rootdev) { + if (ehci->rootdev == 0) + dev->speed = USB_SPEED_HIGH; + return ehci_submit_root(dev, pipe, buffer, length, setup); + } + return ehci_submit_async(dev, pipe, buffer, length, setup); +} + +static int +submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, + int length, int interval) +{ + debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d", + dev, pipe, buffer, length, interval); + return -1; +} + +static int ehci_probe(struct device_d *dev) +{ + struct usb_host *host; + struct ehci_priv *ehci; + uint32_t reg; + + ehci = xmalloc(sizeof(struct ehci_priv)); + host = &ehci->host; + + host->init = ehci_init; + host->submit_int_msg = submit_int_msg; + host->submit_control_msg = submit_control_msg; + host->submit_bulk_msg = submit_bulk_msg; + + ehci->hccr = (void *)(dev->map_base + 0x100); + ehci->hcor = (void *)(dev->map_base + 0x140); + + usb_register_host(host); + + reg = HC_VERSION(ehci_readl(&ehci->hccr->cr_capbase)); + dev_info(dev, "USB EHCI %x.%02x\n", reg >> 8, reg & 0xff); + + return 0; +} + +static struct driver_d ehci_driver = { + .name = "ehci", + .probe = ehci_probe, +}; + +static int ehcil_init(void) +{ + register_driver(&ehci_driver); + return 0; +} + +device_initcall(ehcil_init); + diff --git a/drivers/usb/usb_ehci_core.h b/drivers/usb/usb_ehci_core.h new file mode 100644 index 0000000000..39e5c5e58c --- /dev/null +++ b/drivers/usb/usb_ehci_core.h @@ -0,0 +1,29 @@ +/*- + * Copyright (c) 2007-2008, Juniper Networks, Inc. + * Copyright (c) 2008, Excito Elektronik i Skåne AB + * All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef USB_EHCI_CORE_H +#define USB_EHCI_CORE_H + +extern int rootdev; +extern struct ehci_hccr *hccr; +extern volatile struct ehci_hcor *hcor; + +#endif diff --git a/include/driver.h b/include/driver.h index a29ede409c..b2ab7c25ac 100644 --- a/include/driver.h +++ b/include/driver.h @@ -68,6 +68,7 @@ /*@{*/ /* do not delete, doxygen relevant */ struct filep; +struct bus_type; /** @brief Describes a particular device present in the system */ struct device_d { @@ -110,6 +111,7 @@ struct device_d { /*! This describes the type (or class) of this device. Have a look at * include/driver.h to see a list of known device types. Currently this * includes DEVICE_TYPE_ETHER, DEVICE_TYPE_CONSOLE and others. */ + struct bus_type *bus; unsigned long type; /*! The parameters for this device. This is used to carry information @@ -151,6 +153,7 @@ struct driver_d { void (*shortinfo) (struct device_d *); unsigned long type; + struct bus_type *bus; /*! This is somewhat redundant with the type data in struct device. * Currently the filesystem implementation uses this field while @@ -273,7 +276,7 @@ static inline int dev_close_default(struct device_d *dev, struct filep *f) extern const char *dev_id(const struct device_d *dev); #define dev_printf(dev, format, arg...) \ - printf("%s@%s: " format , dev->name , \ + printf("%s@%s: " format , (dev)->name , \ dev_id(dev) , ## arg) #define dev_emerg(dev, format, arg...) \ @@ -299,5 +302,15 @@ extern const char *dev_id(const struct device_d *dev); ({ if (0) dev_printf((dev), format, ##arg); 0; }) #endif +struct bus_type { + char *name; + int (*match)(struct device_d *dev, struct driver_d *drv); + int (*probe)(struct device_d *dev); + void (*remove)(struct device_d *dev); + + struct list_head list; +}; + +extern struct bus_type platform_bus; #endif /* DRIVER_H */ diff --git a/include/miiphy.h b/include/miiphy.h index 2c8d0ec22c..1a31fb0f90 100644 --- a/include/miiphy.h +++ b/include/miiphy.h @@ -148,6 +148,7 @@ struct miiphy_device { }; int miiphy_register(struct miiphy_device *mdev); +void miiphy_unregister(struct miiphy_device *mdev); int miiphy_restart_aneg(struct miiphy_device *mdev); int miiphy_wait_aneg(struct miiphy_device *mdev); int miiphy_print_status(struct miiphy_device *mdev); diff --git a/include/net.h b/include/net.h index d59ae5caa7..63e25a012a 100644 --- a/include/net.h +++ b/include/net.h @@ -78,7 +78,8 @@ struct eth_device { struct device_d *dev; }; -int eth_register(struct eth_device* dev);/* Register network device */ +int eth_register(struct eth_device* dev); /* Register network device */ +void eth_unregister(struct eth_device* dev); /* Unregister network device */ int eth_open(void); /* open the device */ int eth_send(void *packet, int length); /* Send a packet */ diff --git a/lib/Makefile b/lib/Makefile index 2828fbe114..c52b06facb 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -6,6 +6,7 @@ obj-y += vsprintf.o obj-y += div64.o obj-y += misc.o obj-y += driver.o +obj-y += bus.o obj-y += parameter.o obj-y += xfuncs.o obj-y += getopt.o diff --git a/lib/bus.c b/lib/bus.c new file mode 100644 index 0000000000..ac90b49a82 --- /dev/null +++ b/lib/bus.c @@ -0,0 +1,58 @@ +/* + * bus.c - U-Boot driver model + * + * Copyright (c) 2009 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <common.h> +#include <driver.h> + +static int platform_match(struct device_d *dev, struct driver_d *drv) +{ + return strcmp(dev->name, drv->name) ? -1 : 0; +} + +static int platform_probe(struct device_d *dev) +{ + return dev->driver->probe(dev); +} + +static void platform_remove(struct device_d *dev) +{ + dev->driver->remove(dev); +} + +struct bus_type platform_bus = { + .name = "platform", + .match = platform_match, + .probe = platform_probe, + .remove = platform_remove, +}; + +#if 0 +LIST_HEAD(bus_list); +EXPORT_SYMBOL(bus_list); + +int bus_register(struct bus_type *bus) +{ + list_add_tail(&bus->list, &bus_list); + + return 0; +} +#endif + diff --git a/lib/driver.c b/lib/driver.c index 9f0b14945a..30de8c20ea 100644 --- a/lib/driver.c +++ b/lib/driver.c @@ -70,18 +70,24 @@ int get_free_deviceid(char *id, const char *id_template) static int match(struct driver_d *drv, struct device_d *dev) { - if (strcmp(dev->name, drv->name)) - return -1; - if (dev->type != drv->type) - return -1; - if(drv->probe(dev)) + if (dev->driver) return -1; dev->driver = drv; + if (dev->bus != drv->bus) + goto err_out; + if (dev->bus->match(dev, drv)) + goto err_out; + if (dev->bus->probe(dev)) + goto err_out; + list_add(&dev->active, &active); return 0; +err_out: + dev->driver = NULL; + return -1; } int register_device(struct device_d *new_device) @@ -94,6 +100,11 @@ int register_device(struct device_d *new_device) } debug ("register_device: %s\n",new_device->name); + if (!new_device->bus) { +// dev_err(new_device, "no bus type associated. Needs fixup\n"); + new_device->bus = &platform_bus; + } + list_add_tail(&new_device->list, &device_list); INIT_LIST_HEAD(&new_device->children); @@ -116,7 +127,7 @@ int unregister_device(struct device_d *old_dev) } if (old_dev->driver) - old_dev->driver->remove(old_dev); + old_dev->bus->remove(old_dev); list_del(&old_dev->list); @@ -165,6 +176,11 @@ int register_driver(struct driver_d *drv) debug("register_driver: %s\n", drv->name); + if (!drv->bus) { +// pr_err("driver %s has no bus type associated. Needs fixup\n", drv->name); + drv->bus = &platform_bus; + } + list_add_tail(&drv->list, &driver_list); if (!drv->info) @@ -148,3 +148,20 @@ int eth_register(struct eth_device *edev) return 0; } +void eth_unregister(struct eth_device *edev) +{ + if (edev->param_ip.value) + free(edev->param_ip.value); + if (edev->param_ethaddr.value) + free(edev->param_ethaddr.value); + if (edev->param_gateway.value) + free(edev->param_gateway.value); + if (edev->param_netmask.value) + free(edev->param_netmask.value); + if (edev->param_serverip.value) + free(edev->param_serverip.value); + + if (eth_current == edev) + eth_current = NULL; +} + |