Hi, On Thu, 2016-08-25 at 10:32 +0200, Oliver Neukum wrote: > On Thu, 2016-08-25 at 11:05 +0800, Chunfeng Yun wrote: > > This patch adds support for the MediaTek USB3 controller > > integrated into MT8173. It can be configured as Dual-Role > > Device (DRD), Peripheral Only and Host Only (xHCI) modes. > > > > > +/** > > + * General Purpose Descriptor (GPD): > > + * The format of TX GPD is a little different from RX one. > > + * And the size of GPD is 16 bytes. > > + * > > + * @flag: > > + * bit0: Hardware Own (HWO) > > + * bit1: Buffer Descriptor Present (BDP), always 0, BD is not supported > > + * bit2: Bypass (BPS), 1: HW skips this GPD if HWO = 1 > > + * bit7: Interrupt On Completion (IOC) > > + * @chksum: This is used to validate the contents of this GPD; > > + * If TXQ_CS_EN / RXQ_CS_EN bit is set, an interrupt is issued > > + * when checksum validation fails; > > + * Checksum value is calculated over the 16 bytes of the GPD by default; > > + * @data_buf_len (RX ONLY): This value indicates the length of > > + * the assigned data buffer > > + * @next_gpd: Physical address of the next GPD > > + * @buffer: Physical address of the data buffer > > + * @buf_len: > > + * (TX): This value indicates the length of the assigned data buffer > > + * (RX): The total length of data received > > + * @ext_len: reserved > > + * @ext_flag: > > + * bit5 (TX ONLY): Zero Length Packet (ZLP), > > + */ > > +struct qmu_gpd { > > + u8 flag; > > + u8 chksum; > > + u16 data_buf_len; > > + u32 next_gpd; > > + u32 buffer; > > + u16 buf_len; > > + u8 ext_len; > > + u8 ext_flag; > > +} __packed; > > It looks like this is shared with hardware in memory. > But you leave the endianness of the bigger fields undeclared. > The driver only supports Little-Endian SoCs currently, because I have no Big-Endian platform to test it. > > +/** > > +* dma: physical base address of GPD segment > > +* start: virtual base address of GPD segment > > +* end: the last GPD element > > +* enqueue: the first empty GPD to use > > +* dequeue: the first completed GPD serviced by ISR > > +* NOTE: the size of GPD ring should be >= 2 > > +*/ > > +struct mtu3_gpd_ring { > > + dma_addr_t dma; > > + struct qmu_gpd *start; > > + struct qmu_gpd *end; > > + struct qmu_gpd *enqueue; > > + struct qmu_gpd *dequeue; > > +}; > > + > > +/** > > +* @vbus: vbus 5V used by host mode > > +* @edev: external connector used to detect vbus and iddig changes > > +* @vbus_nb: notifier for vbus detection > > +* @vbus_nb: notifier for iddig(idpin) detection > > +* @extcon_reg_dwork: delay work for extcon notifier register, waiting for > > +* xHCI driver initialization, it's necessary for system bootup > > +* as device. > > +* @is_u3_drd: whether port0 supports usb3.0 dual-role device or not > > +* @id_*: used to maually switch between host and device modes by idpin > > +* @manual_drd_enabled: it's true when supports dual-role device by debugfs > > +* to switch host/device modes depending on user input. > > Please use a common interface for this. The Intel people are introducing > one. Can you give me the link address of the patch, I didn't find it. > > > > +#endif > > diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c > > new file mode 100644 > > index 0000000..fdc11b6 > > --- /dev/null > > +++ b/drivers/usb/mtu3/mtu3_core.c > > @@ -0,0 +1,874 @@ > > +/* > > + * mtu3_core.c - hardware access layer and gadget init/exit of > > + * MediaTek usb3 Dual-Role Controller Driver > > + * > > + * Copyright (C) 2016 MediaTek Inc. > > + * > > + * Author: Chunfeng Yun <chunfeng.yun@xxxxxxxxxxxx> > > + * > > + * This software is licensed under the terms of the GNU General Public > > + * License version 2, as published by the Free Software Foundation, and > > + * may be copied, distributed, and modified under those terms. > > + * > > + * 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. > > + * > > + */ > > + > > +#include <linux/kernel.h> > > +#include <linux/module.h> > > +#include <linux/of_address.h> > > +#include <linux/of_irq.h> > > +#include <linux/platform_device.h> > > + > > +#include "mtu3.h" > > + > > +static int ep_fifo_alloc(struct mtu3_ep *mep, u32 seg_size) > > +{ > > + struct mtu3_fifo_info *fifo = mep->fifo; > > + u32 num_bits = DIV_ROUND_UP(seg_size, MTU3_EP_FIFO_UNIT); > > + u32 start_bit; > > + > > + /* ensure that @mep->fifo_seg_size is power of two */ > > + num_bits = roundup_pow_of_two(num_bits); > > + if (num_bits > fifo->limit) > > + return -EINVAL; > > + > > + mep->fifo_seg_size = num_bits * MTU3_EP_FIFO_UNIT; > > + num_bits = num_bits * (mep->slot + 1); > > + start_bit = bitmap_find_next_zero_area(fifo->bitmap, > > + fifo->limit, 0, num_bits, 0); > > + if (start_bit >= fifo->limit) > > + return -EOVERFLOW; > > + > > + bitmap_set(fifo->bitmap, start_bit, num_bits); > > + mep->fifo_size = num_bits * MTU3_EP_FIFO_UNIT; > > + mep->fifo_addr = fifo->base + MTU3_EP_FIFO_UNIT * start_bit; > > + > > + dev_dbg(mep->mtu->dev, "%s fifo:%#x/%#x, start_bit: %d\n", > > + __func__, mep->fifo_seg_size, mep->fifo_size, start_bit); > > If you use the "f" option to dynamic debugging it will give you the > function name anyway. So why include __func__ ? > It's used when dynamic debugging is disable but defines DEBUG macro. > > > +/* enable/disable U3D SS function */ > > +static void mtu3_ss_func_set(struct mtu3 *mtu, bool enable) > > Inline maybe ? > > > +{ > > + /* If usb3_en==0, LTSSM will go to SS.Disable state */ > > + if (enable) > > + mtu3_setbits(mtu->mac_base, U3D_USB3_CONFIG, USB3_EN); > > + else > > + mtu3_clrbits(mtu->mac_base, U3D_USB3_CONFIG, USB3_EN); > > + > > + dev_dbg(mtu->dev, "USB3_EN = %d\n", !!enable); > > +} > > + > > [..] > > > > +int mtu3_config_ep(struct mtu3 *mtu, struct mtu3_ep *mep, > > + int interval, int burst, int mult) > > +{ > > + void __iomem *mbase = mtu->mac_base; > > + int epnum = mep->epnum; > > + u32 csr0, csr1, csr2; > > + int fifo_sgsz, fifo_addr; > > + int num_pkts; > > + > > + fifo_addr = ep_fifo_alloc(mep, mep->maxp); > > + if (fifo_addr < 0) { > > + dev_err(mtu->dev, "alloc ep fifo failed(%d)\n", mep->maxp); > > + return -ENOMEM; > > + } > > + fifo_sgsz = ilog2(mep->fifo_seg_size); > > + dev_dbg(mtu->dev, "%s fifosz: %x(%x/%x)\n", __func__, fifo_sgsz, > > + mep->fifo_seg_size, mep->fifo_size); > > + > > + if (mep->is_in) { > > + csr0 = TX_TXMAXPKTSZ(mep->maxp); > > + csr0 |= TX_DMAREQEN; > > + > > + num_pkts = (burst + 1) * (mult + 1) - 1; > > + csr1 = TX_SS_BURST(burst) | TX_SLOT(mep->slot); > > + csr1 |= TX_MAX_PKT(num_pkts) | TX_MULT(mult); > > + > > + csr2 = TX_FIFOADDR(fifo_addr >> 4); > > + csr2 |= TX_FIFOSEGSIZE(fifo_sgsz); > > + > > + switch (mep->type) { > > + case USB_ENDPOINT_XFER_BULK: > > + csr1 |= TX_TYPE(TYPE_BULK); > > + break; > > + case USB_ENDPOINT_XFER_ISOC: > > + csr1 |= TX_TYPE(TYPE_ISO); > > + csr2 |= TX_BINTERVAL(interval); > > + break; > > + case USB_ENDPOINT_XFER_INT: > > + csr1 |= TX_TYPE(TYPE_INT); > > + csr2 |= TX_BINTERVAL(interval); > > + break; > > And if it is control? The function is only called for non control EP. I will add a comment for it. > > > + } > > + > > + /* Enable QMU Done interrupt */ > > + mtu3_setbits(mbase, U3D_QIESR0, QMU_TX_DONE_INT(epnum)); > > + > > + mtu3_writel(mbase, MU3D_EP_TXCR0(epnum), csr0); > > + mtu3_writel(mbase, MU3D_EP_TXCR1(epnum), csr1); > > + mtu3_writel(mbase, MU3D_EP_TXCR2(epnum), csr2); > > + > > + dev_dbg(mtu->dev, "U3D_TX%d CSR0:%#x, CSR1:%#x, CSR2:%#x\n", > > + epnum, mtu3_readl(mbase, MU3D_EP_TXCR0(epnum)), > > + mtu3_readl(mbase, MU3D_EP_TXCR1(epnum)), > > + mtu3_readl(mbase, MU3D_EP_TXCR2(epnum))); > > + } else { > > + csr0 = RX_RXMAXPKTSZ(mep->maxp); > > + csr0 |= RX_DMAREQEN; > > + > > + num_pkts = (burst + 1) * (mult + 1) - 1; > > + csr1 = RX_SS_BURST(burst) | RX_SLOT(mep->slot); > > + csr1 |= RX_MAX_PKT(num_pkts) | RX_MULT(mult); > > + > > + csr2 = RX_FIFOADDR(fifo_addr >> 4); > > + csr2 |= RX_FIFOSEGSIZE(fifo_sgsz); > > + > > + switch (mep->type) { > > + case USB_ENDPOINT_XFER_BULK: > > + csr1 |= RX_TYPE(TYPE_BULK); > > + break; > > + case USB_ENDPOINT_XFER_ISOC: > > + csr1 |= RX_TYPE(TYPE_ISO); > > + csr2 |= RX_BINTERVAL(interval); > > + break; > > + case USB_ENDPOINT_XFER_INT: > > + csr1 |= RX_TYPE(TYPE_INT); > > + csr2 |= RX_BINTERVAL(interval); > > + break; > > Again: control endpoints? Only for non-EP0. > > > + } > > + > > + /*Enable QMU Done interrupt */ > > + mtu3_setbits(mbase, U3D_QIESR0, QMU_RX_DONE_INT(epnum)); > > + > > + mtu3_writel(mbase, MU3D_EP_RXCR0(epnum), csr0); > > + mtu3_writel(mbase, MU3D_EP_RXCR1(epnum), csr1); > > + mtu3_writel(mbase, MU3D_EP_RXCR2(epnum), csr2); > > + > > + dev_dbg(mtu->dev, "U3D_RX%d CSR0:%#x, CSR1:%#x, CSR2:%#x\n", > > + epnum, mtu3_readl(mbase, MU3D_EP_RXCR0(epnum)), > > + mtu3_readl(mbase, MU3D_EP_RXCR1(epnum)), > > + mtu3_readl(mbase, MU3D_EP_RXCR2(epnum))); > > + } > > + > > + dev_dbg(mtu->dev, "csr0:%#x, csr1:%#x, csr2:%#x\n", csr0, csr1, csr2); > > + dev_dbg(mtu->dev, "%s: %s, fifo-addr:%#x, fifo-size:%#x(%#x/%#x)\n", > > + __func__, mep->name, mep->fifo_addr, mep->fifo_size, > > + fifo_sgsz, mep->fifo_seg_size); > > + > > + return 0; > > +} > > + > > [..] > > +static void mtu3_set_speed(struct mtu3 *mtu) > > +{ > > + void __iomem *mbase = mtu->mac_base; > > + > > + if (!mtu->is_u3_ip && (mtu->max_speed > USB_SPEED_HIGH)) > > + mtu->max_speed = USB_SPEED_HIGH; > > You are missing the checks for USB_SPEED_WIRELESS in general. > Can you just ignore it? Doesn't support USB_SPEED_WIRELESS, so ignore it. > > > + > > + if (mtu->max_speed == USB_SPEED_FULL) { > > + /* disable U3 SS function */ > > + mtu3_clrbits(mbase, U3D_USB3_CONFIG, USB3_EN); > > + /* disable HS function */ > > + mtu3_clrbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE); > > + } else if (mtu->max_speed == USB_SPEED_HIGH) { > > + mtu3_clrbits(mbase, U3D_USB3_CONFIG, USB3_EN); > > + /* HS/FS detected by HW */ > > + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE); > > + } > > + dev_info(mtu->dev, "max_speed: %s\n", > > + usb_speed_string(mtu->max_speed)); > > +} > > [..] > > +static irqreturn_t mtu3_link_isr(struct mtu3 *mtu) > > +{ > > + void __iomem *mbase = mtu->mac_base; > > + enum usb_device_speed udev_speed; > > + u32 maxpkt = 64; > > + u32 link; > > + u32 speed; > > + > > + link = mtu3_readl(mbase, U3D_DEV_LINK_INTR); > > + link &= mtu3_readl(mbase, U3D_DEV_LINK_INTR_ENABLE); > > + mtu3_writel(mbase, U3D_DEV_LINK_INTR, link); /* W1C */ > > + dev_dbg(mtu->dev, "=== LINK[%x] ===\n", link); > > + > > + if (!(link & SSUSB_DEV_SPEED_CHG_INTR)) > > + return IRQ_NONE; > > Shouldn't you do this check before you clear interrupts? In fact, U3D_DEV_LINK_INTR has only one interrupt, SSUSB_DEV_SPEED_CHG_INTR, others can be treated as spurious ones. > > > + speed = SSUSB_DEV_SPEED(mtu3_readl(mbase, U3D_DEVICE_CONF)); > > Do you really want to read this out of the hardware on every interrupt? Yes, the interrupt is triggered only when speed is changed, and get the new speed here. > > > + > > + switch (speed) { > > + case MTU3_SPEED_FULL: > > + udev_speed = USB_SPEED_FULL; > > + /*BESLCK = 4 < BESLCK_U3 = 10 < BESLDCK = 15 */ > > + mtu3_writel(mbase, U3D_USB20_LPM_PARAMETER, LPM_BESLDCK(0xf) > > + | LPM_BESLCK(4) | LPM_BESLCK_U3(0xa)); > > + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, > > + LPM_BESL_STALL | LPM_BESLD_STALL); > > + break; > > + case MTU3_SPEED_HIGH: > > + udev_speed = USB_SPEED_HIGH; > > + /*BESLCK = 4 < BESLCK_U3 = 10 < BESLDCK = 15 */ > > + mtu3_writel(mbase, U3D_USB20_LPM_PARAMETER, LPM_BESLDCK(0xf) > > + | LPM_BESLCK(4) | LPM_BESLCK_U3(0xa)); > > + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, > > + LPM_BESL_STALL | LPM_BESLD_STALL); > > + break; > > + case MTU3_SPEED_SUPER: > > + udev_speed = USB_SPEED_SUPER; > > + maxpkt = 512; > > + break; > > + default: > > + udev_speed = USB_SPEED_UNKNOWN; > > + break; > > + } > > + dev_dbg(mtu->dev, "%s: %s\n", __func__, usb_speed_string(udev_speed)); > > + > > + mtu->g.speed = udev_speed; > > + mtu->g.ep0->maxpacket = maxpkt; > > + mtu->ep0_state = MU3D_EP0_STATE_SETUP; > > + > > + if (udev_speed == USB_SPEED_UNKNOWN) > > + mtu3_gadget_disconnect(mtu); > > + else > > + mtu3_ep0_setup(mtu); > > + > > + return IRQ_HANDLED; > > +} > > + > > +static irqreturn_t mtu3_u3_ltssm_isr(struct mtu3 *mtu) > > +{ > > + void __iomem *mbase = mtu->mac_base; > > + u32 ltssm; > > + > > + ltssm = mtu3_readl(mbase, U3D_LTSSM_INTR); > > + ltssm &= mtu3_readl(mbase, U3D_LTSSM_INTR_ENABLE); > > + mtu3_writel(mbase, U3D_LTSSM_INTR, ltssm); /* W1C */ > > + dev_dbg(mtu->dev, "=== LTSSM[%x] ===\n", ltssm); > > + > > + if (ltssm & HOT_RST_INTR) > > + mtu3_gadget_reset(mtu); > > + > > + if (ltssm & WARM_RST_INTR) > > + mtu3_gadget_reset(mtu); > > You really would reset the gadget twice if that happens? > And do the rest of the tests make sense in that case? I will merge them into one as following: if (ltssm & (HOT_RST_INTR | WARM_RST_INTR)) ... > > > + > > + if (ltssm & VBUS_FALL_INTR) > > + mtu3_ss_func_set(mtu, false); > > + > > + if (ltssm & VBUS_RISE_INTR) > > + mtu3_ss_func_set(mtu, true); > > + > > + if (ltssm & EXIT_U3_INTR) > > + mtu3_gadget_resume(mtu); > > + > > + if (ltssm & ENTER_U3_INTR) > > + mtu3_gadget_suspend(mtu); > > + > > + return IRQ_HANDLED; > > +} > > [..] > > > +static int mtu3_hw_init(struct mtu3 *mtu) > > +{ > > + int ret; > > + > > + mtu3_device_reset(mtu); > > + > > + ret = mtu3_device_enable(mtu); > > + if (ret) { > > + dev_err(mtu->dev, "device enable failed %d\n", ret); > > + return ret; > > + } > > + > > + ret = mtu3_mem_alloc(mtu); > > + if (ret) { > > + dev_err(mtu->dev, "mem alloc failed, aborting\n"); > > 1. You can't leave the device enabled in that case. It enable device mac and port, otherwise I can't initialize other register. and the function is not enable until udc_start() is called. > 2. No need for a message. The kernel will already do that if kmalloc() > fails under such circumstances. remove it latter. > > > + return -ENOMEM; > > + } > > + > > + mtu3_regs_init(mtu); > > + > > + return 0; > > +} > > > diff --git a/drivers/usb/mtu3/mtu3_dr.c b/drivers/usb/mtu3/mtu3_dr.c > > new file mode 100644 > > index 0000000..f560f93 > > --- /dev/null > > +++ b/drivers/usb/mtu3/mtu3_dr.c > > @@ -0,0 +1,375 @@ > > +/* > > + * mtu3_dr.c - dual role switch and host glue layer > > + * > > + * Copyright (C) 2016 MediaTek Inc. > > + * > > + * Author: Chunfeng Yun <chunfeng.yun@xxxxxxxxxxxx> > > + * > > + * This software is licensed under the terms of the GNU General Public > > + * License version 2, as published by the Free Software Foundation, and > > + * may be copied, distributed, and modified under those terms. > > + * > > + * 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. > > + * > > + */ > > + > > +#include <linux/debugfs.h> > > +#include <linux/irq.h> > > +#include <linux/kernel.h> > > +#include <linux/of_device.h> > > +#include <linux/pinctrl/consumer.h> > > +#include <linux/seq_file.h> > > +#include <linux/uaccess.h> > > + > > +#include "mtu3.h" > > +#include "mtu3_dr.h" > > + > > +#define USB2_PORT 2 > > +#define USB3_PORT 3 > > + [...] > > + > > +static ssize_t ssusb_mode_write(struct file *file, > > + const char __user *ubuf, size_t count, loff_t *ppos) > > +{ > > + struct seq_file *sf = file->private_data; > > + struct ssusb_mtk *ssusb = sf->private; > > + char buf[16]; > > + > > + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) > > + return -EFAULT; > > + > > + if (!strncmp(buf, "host", 4) && !ssusb->is_host) > > + ssusb_mode_manual_switch(ssusb, 1); > > + else if (!strncmp(buf, "device", 6) && ssusb->is_host) > > + ssusb_mode_manual_switch(ssusb, 0); > > + else > > + dev_err(ssusb->dev, "wrong or duplicated setting\n"); > > No proper error returns add it later. > > + > > + return count; > > +} > > + > > +static const struct file_operations ssusb_mode_fops = { > > + .open = ssusb_mode_open, > > + .write = ssusb_mode_write, > > + .read = seq_read, > > + .llseek = seq_lseek, > > + .release = single_release, > > +}; > > + [..] > > +static void ssusb_debugfs_exit(struct ssusb_mtk *ssusb) > > +{ > > + debugfs_remove_recursive(ssusb->dbgfs_root); > > +} > > + > > +int ssusb_otg_switch_init(struct ssusb_mtk *ssusb) > > +{ > > + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; > > + > > + INIT_DELAYED_WORK(&otg_sx->extcon_reg_dwork, extcon_register_dwork); > > + > > + if (otg_sx->manual_drd_enabled) > > + ssusb_debugfs_init(ssusb); > > + > > + /* It is enough to delay 1s for waiting for host initialization */ > > + schedule_delayed_work(&otg_sx->extcon_reg_dwork, HZ); > > You need to handle this still pending when you are deregistered. OK. > > > + > > + return 0; > > +} > > + > > +void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb) > > +{ > > + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; > > + > > + if (otg_sx->edev) { > > + extcon_unregister_notifier(otg_sx->edev, > > + EXTCON_USB, &otg_sx->vbus_nb); > > + extcon_unregister_notifier(otg_sx->edev, > > + EXTCON_USB_HOST, &otg_sx->id_nb); > > + } > > + > > + if (otg_sx->manual_drd_enabled) > > + ssusb_debugfs_exit(ssusb); > > +} > > Could you break this up a bit? It is extensively long a patch? I afraid I can't break the patch into small ones but also provide complete functionality at the same time. Sorry for inconvenience. > Thank you very much :-) > Regards > Oliver > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html