Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
# SPDX-License-Identifier: GPL-2.0
#
# USB Dual Role (OTG-ready) Controller Drivers
# for silicon based on Mentor Graphics INVENTRA designs
#
# (M)HDRC = (Multipoint) Highspeed Dual-Role Controller
config USB_MUSB_HDRC
tristate 'Inventra Highspeed Dual Role Controller'
depends on (USB || USB_GADGET)
depends on HAS_IOMEM
help
Say Y here if your system has a dual role high speed USB
controller based on the Mentor Graphics silicon IP. Then
configure options to match your silicon and the board
it's being used with, including the USB peripheral role,
or the USB host role, or both.
Texas Instruments families using this IP include DaVinci
(35x, 644x ...), OMAP 243x, OMAP 3, and TUSB 6010.
Allwinner SoCs using this IP include A10, A13, A20, ...
If you do not know what this is, please say N.
To compile this driver as a module, choose M here; the
module will be called "musb-hdrc".
if USB_MUSB_HDRC
choice
prompt "MUSB Mode Selection"
default USB_MUSB_DUAL_ROLE if (USB && USB_GADGET)
default USB_MUSB_HOST if (USB && !USB_GADGET)
default USB_MUSB_GADGET if (!USB && USB_GADGET)
config USB_MUSB_HOST
bool "Host only mode"
depends on USB=y || USB=USB_MUSB_HDRC
help
Select this when you want to use MUSB in host mode only,
thereby the gadget feature will be regressed.
config USB_MUSB_GADGET
bool "Gadget only mode"
depends on USB_GADGET=y || USB_GADGET=USB_MUSB_HDRC
depends on HAS_DMA
help
Select this when you want to use MUSB in gadget mode only,
thereby the host feature will be regressed.
config USB_MUSB_DUAL_ROLE
bool "Dual Role mode"
depends on ((USB=y || USB=USB_MUSB_HDRC) && (USB_GADGET=y || USB_GADGET=USB_MUSB_HDRC))
depends on HAS_DMA
help
This is the default mode of working of MUSB controller where
both host and gadget features are enabled.
endchoice
comment "Platform Glue Layer"
config USB_MUSB_SUNXI
tristate "Allwinner (sunxi)"
depends on ARCH_SUNXI
depends on NOP_USB_XCEIV
depends on PHY_SUN4I_USB
depends on EXTCON
select GENERIC_PHY
select SUNXI_SRAM
config USB_MUSB_DA8XX
tristate "DA8xx/OMAP-L1x"
depends on ARCH_DAVINCI_DA8XX
depends on NOP_USB_XCEIV
select PHY_DA8XX_USB
config USB_MUSB_TUSB6010
tristate "TUSB6010"
depends on HAS_IOMEM
depends on ARCH_OMAP2PLUS || COMPILE_TEST
depends on NOP_USB_XCEIV!=m || USB_MUSB_HDRC=m
config USB_MUSB_OMAP2PLUS
tristate "OMAP2430 and onwards"
depends on ARCH_OMAP2PLUS || COMPILE_TEST
depends on USB
depends on OMAP_CONTROL_PHY || !OMAP_CONTROL_PHY
select GENERIC_PHY
config USB_MUSB_DSPS
tristate "TI DSPS platforms"
depends on ARCH_OMAP2PLUS || COMPILE_TEST
depends on OF_IRQ
config USB_MUSB_UX500
tristate "Ux500 platforms"
depends on ARCH_U8500 || COMPILE_TEST
config USB_MUSB_JZ4740
tristate "JZ4740"
depends on OF
depends on MIPS || COMPILE_TEST
depends on USB_MUSB_GADGET
select USB_ROLE_SWITCH
config USB_MUSB_MEDIATEK
tristate "MediaTek platforms"
depends on ARCH_MEDIATEK || COMPILE_TEST
depends on NOP_USB_XCEIV
select GENERIC_PHY
select USB_ROLE_SWITCH
config USB_MUSB_POLARFIRE_SOC
tristate "Microchip PolarFire SoC platforms"
depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST
depends on NOP_USB_XCEIV
select USB_MUSB_DUAL_ROLE
help
Say Y here to enable support for USB on Microchip's PolarFire SoC.
This support is also available as a module. If so, the module
will be called mpfs.
comment "MUSB DMA mode"
config MUSB_PIO_ONLY
bool 'Disable DMA (always use PIO)'
help
All data is copied between memory and FIFO by the CPU.
DMA controllers are ignored.
Do not choose this unless DMA support for your SOC or board
is unavailable (or unstable). When DMA is enabled at compile time,
you can still disable it at run time using the "use_dma=n" module
parameter.
if !MUSB_PIO_ONLY
config USB_UX500_DMA
bool 'ST Ericsson Ux500'
depends on USB_MUSB_UX500
help
Enable DMA transfers on UX500 platforms.
config USB_INVENTRA_DMA
bool 'Inventra'
depends on USB_MUSB_OMAP2PLUS || USB_MUSB_MEDIATEK || USB_MUSB_JZ4740 || USB_MUSB_POLARFIRE_SOC
help
Enable DMA transfers using Mentor's engine.
config USB_TI_CPPI41_DMA
bool 'TI CPPI 4.1'
depends on (ARCH_OMAP || ARCH_DAVINCI_DA8XX) && DMADEVICES
select TI_CPPI41
config USB_TUSB_OMAP_DMA
bool 'TUSB 6010'
depends on USB_MUSB_TUSB6010 = USB_MUSB_HDRC # both built-in or both modules
depends on ARCH_OMAP
help
Enable DMA transfers on TUSB 6010 when OMAP DMA is available.
endif # !MUSB_PIO_ONLY
endif # USB_MUSB_HDRC
+36
View File
@@ -0,0 +1,36 @@
# SPDX-License-Identifier: GPL-2.0
#
# for USB OTG silicon based on Mentor Graphics INVENTRA designs
#
# define_trace.h needs to know how to find our header
CFLAGS_musb_trace.o := -I$(src)
obj-$(CONFIG_USB_MUSB_HDRC) += musb_hdrc.o
musb_hdrc-y := musb_core.o musb_trace.o
musb_hdrc-$(CONFIG_USB_MUSB_HOST)$(CONFIG_USB_MUSB_DUAL_ROLE) += musb_virthub.o musb_host.o
musb_hdrc-$(CONFIG_USB_MUSB_GADGET)$(CONFIG_USB_MUSB_DUAL_ROLE) += musb_gadget_ep0.o musb_gadget.o
musb_hdrc-$(CONFIG_DEBUG_FS) += musb_debugfs.o
# Hardware Glue Layer
obj-$(CONFIG_USB_MUSB_OMAP2PLUS) += omap2430.o
obj-$(CONFIG_USB_MUSB_DSPS) += musb_dsps.o
obj-$(CONFIG_USB_MUSB_TUSB6010) += tusb6010.o
obj-$(CONFIG_USB_MUSB_DA8XX) += da8xx.o
obj-$(CONFIG_USB_MUSB_UX500) += ux500.o
obj-$(CONFIG_USB_MUSB_JZ4740) += jz4740.o
obj-$(CONFIG_USB_MUSB_SUNXI) += sunxi.o
obj-$(CONFIG_USB_MUSB_MEDIATEK) += mediatek.o
obj-$(CONFIG_USB_MUSB_POLARFIRE_SOC) += mpfs.o
# the kconfig must guarantee that only one of the
# possible I/O schemes will be enabled at a time ...
# PIO only, or DMA (several potential schemes).
# though PIO is always there to back up DMA, and for ep0
musb_hdrc-$(CONFIG_USB_INVENTRA_DMA) += musbhsdma.o
musb_hdrc-$(CONFIG_USB_TUSB_OMAP_DMA) += tusb6010_omap.o
musb_hdrc-$(CONFIG_USB_UX500_DMA) += ux500_dma.o
musb_hdrc-$(CONFIG_USB_TI_CPPI41_DMA) += musb_cppi41.o
+144
View File
@@ -0,0 +1,144 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2005-2006 by Texas Instruments */
#ifndef _CPPI_DMA_H_
#define _CPPI_DMA_H_
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/dmapool.h>
#include <linux/dmaengine.h>
#include "musb_core.h"
#include "musb_dma.h"
/* CPPI RX/TX state RAM */
struct cppi_tx_stateram {
u32 tx_head; /* "DMA packet" head descriptor */
u32 tx_buf;
u32 tx_current; /* current descriptor */
u32 tx_buf_current;
u32 tx_info; /* flags, remaining buflen */
u32 tx_rem_len;
u32 tx_dummy; /* unused */
u32 tx_complete;
};
struct cppi_rx_stateram {
u32 rx_skipbytes;
u32 rx_head;
u32 rx_sop; /* "DMA packet" head descriptor */
u32 rx_current; /* current descriptor */
u32 rx_buf_current;
u32 rx_len_len;
u32 rx_cnt_cnt;
u32 rx_complete;
};
/* hw_options bits in CPPI buffer descriptors */
#define CPPI_SOP_SET ((u32)(1 << 31))
#define CPPI_EOP_SET ((u32)(1 << 30))
#define CPPI_OWN_SET ((u32)(1 << 29)) /* owned by cppi */
#define CPPI_EOQ_MASK ((u32)(1 << 28))
#define CPPI_ZERO_SET ((u32)(1 << 23)) /* rx saw zlp; tx issues one */
#define CPPI_RXABT_MASK ((u32)(1 << 19)) /* need more rx buffers */
#define CPPI_RECV_PKTLEN_MASK 0xFFFF
#define CPPI_BUFFER_LEN_MASK 0xFFFF
#define CPPI_TEAR_READY ((u32)(1 << 31))
/* CPPI data structure definitions */
#define CPPI_DESCRIPTOR_ALIGN 16 /* bytes; 5-dec docs say 4-byte align */
struct cppi_descriptor {
/* hardware overlay */
u32 hw_next; /* next buffer descriptor Pointer */
u32 hw_bufp; /* i/o buffer pointer */
u32 hw_off_len; /* buffer_offset16, buffer_length16 */
u32 hw_options; /* flags: SOP, EOP etc*/
struct cppi_descriptor *next;
dma_addr_t dma; /* address of this descriptor */
u32 buflen; /* for RX: original buffer length */
} __attribute__ ((aligned(CPPI_DESCRIPTOR_ALIGN)));
struct cppi;
/* CPPI Channel Control structure */
struct cppi_channel {
struct dma_channel channel;
/* back pointer to the DMA controller structure */
struct cppi *controller;
/* which direction of which endpoint? */
struct musb_hw_ep *hw_ep;
bool transmit;
u8 index;
/* DMA modes: RNDIS or "transparent" */
u8 is_rndis;
/* book keeping for current transfer request */
dma_addr_t buf_dma;
u32 buf_len;
u32 maxpacket;
u32 offset; /* dma requested */
void __iomem *state_ram; /* CPPI state */
struct cppi_descriptor *freelist;
/* BD management fields */
struct cppi_descriptor *head;
struct cppi_descriptor *tail;
struct cppi_descriptor *last_processed;
/* use tx_complete in host role to track endpoints waiting for
* FIFONOTEMPTY to clear.
*/
struct list_head tx_complete;
};
/* CPPI DMA controller object */
struct cppi {
struct dma_controller controller;
void __iomem *mregs; /* Mentor regs */
void __iomem *tibase; /* TI/CPPI regs */
int irq;
struct cppi_channel tx[4];
struct cppi_channel rx[4];
struct dma_pool *pool;
struct list_head tx_complete;
};
struct cppi41_dma_channel {
struct dma_channel channel;
struct cppi41_dma_controller *controller;
struct musb_hw_ep *hw_ep;
struct dma_chan *dc;
dma_cookie_t cookie;
u8 port_num;
u8 is_tx;
u8 is_allocated;
u8 usb_toggle;
dma_addr_t buf_addr;
u32 total_len;
u32 prog_len;
u32 transferred;
u32 packet_sz;
struct list_head tx_check;
int tx_zlp;
};
#endif /* end of ifndef _CPPI_DMA_H_ */
+649
View File
@@ -0,0 +1,649 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Texas Instruments DA8xx/OMAP-L1x "glue layer"
*
* Copyright (c) 2008-2009 MontaVista Software, Inc. <source@mvista.com>
*
* Based on the DaVinci "glue layer" code.
* Copyright (C) 2005-2006 by Texas Instruments
*
* DT support
* Copyright (c) 2016 Petr Kulhavy <petr@barix.com>
*
* This file is part of the Inventra Controller Driver for Linux.
*/
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/string_choices.h>
#include <linux/dma-mapping.h>
#include <linux/usb/usb_phy_generic.h>
#include "musb_core.h"
/*
* DA8XX specific definitions
*/
/* USB 2.0 OTG module registers */
#define DA8XX_USB_REVISION_REG 0x00
#define DA8XX_USB_CTRL_REG 0x04
#define DA8XX_USB_STAT_REG 0x08
#define DA8XX_USB_EMULATION_REG 0x0c
#define DA8XX_USB_SRP_FIX_TIME_REG 0x18
#define DA8XX_USB_INTR_SRC_REG 0x20
#define DA8XX_USB_INTR_SRC_SET_REG 0x24
#define DA8XX_USB_INTR_SRC_CLEAR_REG 0x28
#define DA8XX_USB_INTR_MASK_REG 0x2c
#define DA8XX_USB_INTR_MASK_SET_REG 0x30
#define DA8XX_USB_INTR_MASK_CLEAR_REG 0x34
#define DA8XX_USB_INTR_SRC_MASKED_REG 0x38
#define DA8XX_USB_END_OF_INTR_REG 0x3c
#define DA8XX_USB_GENERIC_RNDIS_EP_SIZE_REG(n) (0x50 + (((n) - 1) << 2))
/* Control register bits */
#define DA8XX_SOFT_RESET_MASK 1
#define DA8XX_USB_TX_EP_MASK 0x1f /* EP0 + 4 Tx EPs */
#define DA8XX_USB_RX_EP_MASK 0x1e /* 4 Rx EPs */
/* USB interrupt register bits */
#define DA8XX_INTR_USB_SHIFT 16
#define DA8XX_INTR_USB_MASK (0x1ff << DA8XX_INTR_USB_SHIFT) /* 8 Mentor */
/* interrupts and DRVVBUS interrupt */
#define DA8XX_INTR_DRVVBUS 0x100
#define DA8XX_INTR_RX_SHIFT 8
#define DA8XX_INTR_RX_MASK (DA8XX_USB_RX_EP_MASK << DA8XX_INTR_RX_SHIFT)
#define DA8XX_INTR_TX_SHIFT 0
#define DA8XX_INTR_TX_MASK (DA8XX_USB_TX_EP_MASK << DA8XX_INTR_TX_SHIFT)
#define DA8XX_MENTOR_CORE_OFFSET 0x400
struct da8xx_glue {
struct device *dev;
struct platform_device *musb;
struct platform_device *usb_phy;
struct clk *clk;
struct phy *phy;
};
/*
* Because we don't set CTRL.UINT, it's "important" to:
* - not read/write INTRUSB/INTRUSBE (except during
* initial setup, as a workaround);
* - use INTSET/INTCLR instead.
*/
/**
* da8xx_musb_enable - enable interrupts
*/
static void da8xx_musb_enable(struct musb *musb)
{
void __iomem *reg_base = musb->ctrl_base;
u32 mask;
/* Workaround: setup IRQs through both register sets. */
mask = ((musb->epmask & DA8XX_USB_TX_EP_MASK) << DA8XX_INTR_TX_SHIFT) |
((musb->epmask & DA8XX_USB_RX_EP_MASK) << DA8XX_INTR_RX_SHIFT) |
DA8XX_INTR_USB_MASK;
musb_writel(reg_base, DA8XX_USB_INTR_MASK_SET_REG, mask);
/* Force the DRVVBUS IRQ so we can start polling for ID change. */
musb_writel(reg_base, DA8XX_USB_INTR_SRC_SET_REG,
DA8XX_INTR_DRVVBUS << DA8XX_INTR_USB_SHIFT);
}
/**
* da8xx_musb_disable - disable HDRC and flush interrupts
*/
static void da8xx_musb_disable(struct musb *musb)
{
void __iomem *reg_base = musb->ctrl_base;
musb_writel(reg_base, DA8XX_USB_INTR_MASK_CLEAR_REG,
DA8XX_INTR_USB_MASK |
DA8XX_INTR_TX_MASK | DA8XX_INTR_RX_MASK);
musb_writel(reg_base, DA8XX_USB_END_OF_INTR_REG, 0);
}
#define portstate(stmt) stmt
static void da8xx_musb_set_vbus(struct musb *musb, int is_on)
{
WARN_ON(is_on && is_peripheral_active(musb));
}
#define POLL_SECONDS 2
static void otg_timer(struct timer_list *t)
{
struct musb *musb = timer_container_of(musb, t,
dev_timer);
void __iomem *mregs = musb->mregs;
u8 devctl;
unsigned long flags;
/*
* We poll because DaVinci's won't expose several OTG-critical
* status change events (from the transceiver) otherwise.
*/
devctl = musb_readb(mregs, MUSB_DEVCTL);
dev_dbg(musb->controller, "Poll devctl %02x (%s)\n", devctl,
usb_otg_state_string(musb->xceiv->otg->state));
spin_lock_irqsave(&musb->lock, flags);
switch (musb->xceiv->otg->state) {
case OTG_STATE_A_WAIT_BCON:
devctl &= ~MUSB_DEVCTL_SESSION;
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
if (devctl & MUSB_DEVCTL_BDEVICE) {
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
MUSB_DEV_MODE(musb);
} else {
musb->xceiv->otg->state = OTG_STATE_A_IDLE;
MUSB_HST_MODE(musb);
}
break;
case OTG_STATE_A_WAIT_VFALL:
/*
* Wait till VBUS falls below SessionEnd (~0.2 V); the 1.3
* RTL seems to mis-handle session "start" otherwise (or in
* our case "recover"), in routine "VBUS was valid by the time
* VBUSERR got reported during enumeration" cases.
*/
if (devctl & MUSB_DEVCTL_VBUS) {
mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ);
break;
}
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE;
musb_writel(musb->ctrl_base, DA8XX_USB_INTR_SRC_SET_REG,
MUSB_INTR_VBUSERROR << DA8XX_INTR_USB_SHIFT);
break;
case OTG_STATE_B_IDLE:
/*
* There's no ID-changed IRQ, so we have no good way to tell
* when to switch to the A-Default state machine (by setting
* the DEVCTL.Session bit).
*
* Workaround: whenever we're in B_IDLE, try setting the
* session flag every few seconds. If it works, ID was
* grounded and we're now in the A-Default state machine.
*
* NOTE: setting the session flag is _supposed_ to trigger
* SRP but clearly it doesn't.
*/
musb_writeb(mregs, MUSB_DEVCTL, devctl | MUSB_DEVCTL_SESSION);
devctl = musb_readb(mregs, MUSB_DEVCTL);
if (devctl & MUSB_DEVCTL_BDEVICE)
mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ);
else
musb->xceiv->otg->state = OTG_STATE_A_IDLE;
break;
default:
break;
}
spin_unlock_irqrestore(&musb->lock, flags);
}
static void __maybe_unused da8xx_musb_try_idle(struct musb *musb, unsigned long timeout)
{
static unsigned long last_timer;
if (timeout == 0)
timeout = jiffies + msecs_to_jiffies(3);
/* Never idle if active, or when VBUS timeout is not set as host */
if (musb->is_active || (musb->a_wait_bcon == 0 &&
musb->xceiv->otg->state == OTG_STATE_A_WAIT_BCON)) {
dev_dbg(musb->controller, "%s active, deleting timer\n",
usb_otg_state_string(musb->xceiv->otg->state));
timer_delete(&musb->dev_timer);
last_timer = jiffies;
return;
}
if (time_after(last_timer, timeout) && timer_pending(&musb->dev_timer)) {
dev_dbg(musb->controller, "Longer idle timer already pending, ignoring...\n");
return;
}
last_timer = timeout;
dev_dbg(musb->controller, "%s inactive, starting idle timer for %u ms\n",
usb_otg_state_string(musb->xceiv->otg->state),
jiffies_to_msecs(timeout - jiffies));
mod_timer(&musb->dev_timer, timeout);
}
static int da8xx_babble_recover(struct musb *musb)
{
dev_dbg(musb->controller, "resetting controller to recover from babble\n");
musb_writel(musb->ctrl_base, DA8XX_USB_CTRL_REG, DA8XX_SOFT_RESET_MASK);
return 0;
}
static irqreturn_t da8xx_musb_interrupt(int irq, void *hci)
{
struct musb *musb = hci;
void __iomem *reg_base = musb->ctrl_base;
unsigned long flags;
irqreturn_t ret = IRQ_NONE;
u32 status;
spin_lock_irqsave(&musb->lock, flags);
/*
* NOTE: DA8XX shadows the Mentor IRQs. Don't manage them through
* the Mentor registers (except for setup), use the TI ones and EOI.
*/
/* Acknowledge and handle non-CPPI interrupts */
status = musb_readl(reg_base, DA8XX_USB_INTR_SRC_MASKED_REG);
if (!status)
goto eoi;
musb_writel(reg_base, DA8XX_USB_INTR_SRC_CLEAR_REG, status);
dev_dbg(musb->controller, "USB IRQ %08x\n", status);
musb->int_rx = (status & DA8XX_INTR_RX_MASK) >> DA8XX_INTR_RX_SHIFT;
musb->int_tx = (status & DA8XX_INTR_TX_MASK) >> DA8XX_INTR_TX_SHIFT;
musb->int_usb = (status & DA8XX_INTR_USB_MASK) >> DA8XX_INTR_USB_SHIFT;
/*
* DRVVBUS IRQs are the only proxy we have (a very poor one!) for
* DA8xx's missing ID change IRQ. We need an ID change IRQ to
* switch appropriately between halves of the OTG state machine.
* Managing DEVCTL.Session per Mentor docs requires that we know its
* value but DEVCTL.BDevice is invalid without DEVCTL.Session set.
* Also, DRVVBUS pulses for SRP (but not at 5 V)...
*/
if (status & (DA8XX_INTR_DRVVBUS << DA8XX_INTR_USB_SHIFT)) {
int drvvbus = musb_readl(reg_base, DA8XX_USB_STAT_REG);
void __iomem *mregs = musb->mregs;
u8 devctl = musb_readb(mregs, MUSB_DEVCTL);
int err;
err = musb->int_usb & MUSB_INTR_VBUSERROR;
if (err) {
/*
* The Mentor core doesn't debounce VBUS as needed
* to cope with device connect current spikes. This
* means it's not uncommon for bus-powered devices
* to get VBUS errors during enumeration.
*
* This is a workaround, but newer RTL from Mentor
* seems to allow a better one: "re"-starting sessions
* without waiting for VBUS to stop registering in
* devctl.
*/
musb->int_usb &= ~MUSB_INTR_VBUSERROR;
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VFALL;
mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ);
WARNING("VBUS error workaround (delay coming)\n");
} else if (drvvbus) {
MUSB_HST_MODE(musb);
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE;
portstate(musb->port1_status |= USB_PORT_STAT_POWER);
timer_delete(&musb->dev_timer);
} else if (!(musb->int_usb & MUSB_INTR_BABBLE)) {
/*
* When babble condition happens, drvvbus interrupt
* is also generated. Ignore this drvvbus interrupt
* and let babble interrupt handler recovers the
* controller; otherwise, the host-mode flag is lost
* due to the MUSB_DEV_MODE() call below and babble
* recovery logic will not be called.
*/
musb->is_active = 0;
MUSB_DEV_MODE(musb);
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
portstate(musb->port1_status &= ~USB_PORT_STAT_POWER);
}
dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n",
str_on_off(drvvbus),
usb_otg_state_string(musb->xceiv->otg->state),
err ? " ERROR" : "",
devctl);
ret = IRQ_HANDLED;
}
if (musb->int_tx || musb->int_rx || musb->int_usb)
ret |= musb_interrupt(musb);
eoi:
/* EOI needs to be written for the IRQ to be re-asserted. */
if (ret == IRQ_HANDLED || status)
musb_writel(reg_base, DA8XX_USB_END_OF_INTR_REG, 0);
/* Poll for ID change */
if (musb->xceiv->otg->state == OTG_STATE_B_IDLE)
mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ);
spin_unlock_irqrestore(&musb->lock, flags);
return ret;
}
static int da8xx_musb_set_mode(struct musb *musb, u8 musb_mode)
{
struct da8xx_glue *glue = dev_get_drvdata(musb->controller->parent);
enum phy_mode phy_mode;
switch (musb_mode) {
case MUSB_HOST: /* Force VBUS valid, ID = 0 */
phy_mode = PHY_MODE_USB_HOST;
break;
case MUSB_PERIPHERAL: /* Force VBUS valid, ID = 1 */
phy_mode = PHY_MODE_USB_DEVICE;
break;
case MUSB_OTG: /* Don't override the VBUS/ID comparators */
phy_mode = PHY_MODE_USB_OTG;
break;
default:
return -EINVAL;
}
return phy_set_mode(glue->phy, phy_mode);
}
static int da8xx_musb_init(struct musb *musb)
{
struct da8xx_glue *glue = dev_get_drvdata(musb->controller->parent);
void __iomem *reg_base = musb->ctrl_base;
u32 rev;
int ret = -ENODEV;
musb->mregs += DA8XX_MENTOR_CORE_OFFSET;
ret = clk_prepare_enable(glue->clk);
if (ret) {
dev_err(glue->dev, "failed to enable clock\n");
return ret;
}
/* Returns zero if e.g. not clocked */
rev = musb_readl(reg_base, DA8XX_USB_REVISION_REG);
if (!rev) {
ret = -ENODEV;
goto fail;
}
musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
if (IS_ERR_OR_NULL(musb->xceiv)) {
ret = -EPROBE_DEFER;
goto fail;
}
timer_setup(&musb->dev_timer, otg_timer, 0);
/* Reset the controller */
musb_writel(reg_base, DA8XX_USB_CTRL_REG, DA8XX_SOFT_RESET_MASK);
/* Start the on-chip PHY and its PLL. */
ret = phy_init(glue->phy);
if (ret) {
dev_err(glue->dev, "Failed to init phy.\n");
goto fail;
}
ret = phy_power_on(glue->phy);
if (ret) {
dev_err(glue->dev, "Failed to power on phy.\n");
goto err_phy_power_on;
}
msleep(5);
/* NOTE: IRQs are in mixed mode, not bypass to pure MUSB */
pr_debug("DA8xx OTG revision %08x, control %02x\n", rev,
musb_readb(reg_base, DA8XX_USB_CTRL_REG));
musb->isr = da8xx_musb_interrupt;
return 0;
err_phy_power_on:
phy_exit(glue->phy);
fail:
clk_disable_unprepare(glue->clk);
return ret;
}
static int da8xx_musb_exit(struct musb *musb)
{
struct da8xx_glue *glue = dev_get_drvdata(musb->controller->parent);
timer_delete_sync(&musb->dev_timer);
phy_power_off(glue->phy);
phy_exit(glue->phy);
clk_disable_unprepare(glue->clk);
usb_put_phy(musb->xceiv);
return 0;
}
static inline u8 get_vbus_power(struct device *dev)
{
struct regulator *vbus_supply;
int current_uA;
vbus_supply = regulator_get_optional(dev, "vbus");
if (IS_ERR(vbus_supply))
return 255;
current_uA = regulator_get_current_limit(vbus_supply);
regulator_put(vbus_supply);
if (current_uA <= 0 || current_uA > 510000)
return 255;
return current_uA / 1000 / 2;
}
#ifdef CONFIG_USB_TI_CPPI41_DMA
static void da8xx_dma_controller_callback(struct dma_controller *c)
{
struct musb *musb = c->musb;
void __iomem *reg_base = musb->ctrl_base;
musb_writel(reg_base, DA8XX_USB_END_OF_INTR_REG, 0);
}
static struct dma_controller *
da8xx_dma_controller_create(struct musb *musb, void __iomem *base)
{
struct dma_controller *controller;
controller = cppi41_dma_controller_create(musb, base);
if (IS_ERR_OR_NULL(controller))
return controller;
controller->dma_callback = da8xx_dma_controller_callback;
return controller;
}
#endif
static const struct musb_platform_ops da8xx_ops = {
.quirks = MUSB_INDEXED_EP | MUSB_PRESERVE_SESSION |
MUSB_DMA_CPPI41 | MUSB_DA8XX,
.init = da8xx_musb_init,
.exit = da8xx_musb_exit,
.fifo_mode = 2,
#ifdef CONFIG_USB_TI_CPPI41_DMA
.dma_init = da8xx_dma_controller_create,
.dma_exit = cppi41_dma_controller_destroy,
#endif
.enable = da8xx_musb_enable,
.disable = da8xx_musb_disable,
.set_mode = da8xx_musb_set_mode,
#ifndef CONFIG_USB_MUSB_HOST
.try_idle = da8xx_musb_try_idle,
#endif
.recover = da8xx_babble_recover,
.set_vbus = da8xx_musb_set_vbus,
};
static const struct platform_device_info da8xx_dev_info = {
.name = "musb-hdrc",
.id = PLATFORM_DEVID_AUTO,
.dma_mask = DMA_BIT_MASK(32),
};
static const struct musb_hdrc_config da8xx_config = {
.ram_bits = 10,
.num_eps = 5,
.multipoint = 1,
};
static struct of_dev_auxdata da8xx_auxdata_lookup[] = {
OF_DEV_AUXDATA("ti,da830-cppi41", 0x01e01000, "cppi41-dmaengine",
NULL),
{}
};
static int da8xx_probe(struct platform_device *pdev)
{
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct da8xx_glue *glue;
struct platform_device_info pinfo;
struct clk *clk;
struct device_node *np = pdev->dev.of_node;
int ret;
glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);
if (!glue)
return -ENOMEM;
clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
return PTR_ERR(clk);
}
glue->phy = devm_phy_get(&pdev->dev, "usb-phy");
if (IS_ERR(glue->phy))
return dev_err_probe(&pdev->dev, PTR_ERR(glue->phy),
"failed to get phy\n");
glue->dev = &pdev->dev;
glue->clk = clk;
if (IS_ENABLED(CONFIG_OF) && np) {
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
pdata->config = &da8xx_config;
pdata->mode = musb_get_mode(&pdev->dev);
pdata->power = get_vbus_power(&pdev->dev);
}
pdata->platform_ops = &da8xx_ops;
glue->usb_phy = usb_phy_generic_register();
ret = PTR_ERR_OR_ZERO(glue->usb_phy);
if (ret) {
dev_err(&pdev->dev, "failed to register usb_phy\n");
return ret;
}
platform_set_drvdata(pdev, glue);
ret = of_platform_populate(pdev->dev.of_node, NULL,
da8xx_auxdata_lookup, &pdev->dev);
if (ret)
goto err_unregister_phy;
pinfo = da8xx_dev_info;
pinfo.parent = &pdev->dev;
pinfo.res = pdev->resource;
pinfo.num_res = pdev->num_resources;
pinfo.data = pdata;
pinfo.size_data = sizeof(*pdata);
pinfo.fwnode = of_fwnode_handle(np);
pinfo.of_node_reused = true;
glue->musb = platform_device_register_full(&pinfo);
ret = PTR_ERR_OR_ZERO(glue->musb);
if (ret) {
dev_err(&pdev->dev, "failed to register musb device: %d\n", ret);
goto err_unregister_phy;
}
return 0;
err_unregister_phy:
usb_phy_generic_unregister(glue->usb_phy);
return ret;
}
static void da8xx_remove(struct platform_device *pdev)
{
struct da8xx_glue *glue = platform_get_drvdata(pdev);
platform_device_unregister(glue->musb);
usb_phy_generic_unregister(glue->usb_phy);
}
#ifdef CONFIG_PM_SLEEP
static int da8xx_suspend(struct device *dev)
{
int ret;
struct da8xx_glue *glue = dev_get_drvdata(dev);
ret = phy_power_off(glue->phy);
if (ret)
return ret;
clk_disable_unprepare(glue->clk);
return 0;
}
static int da8xx_resume(struct device *dev)
{
int ret;
struct da8xx_glue *glue = dev_get_drvdata(dev);
ret = clk_prepare_enable(glue->clk);
if (ret)
return ret;
return phy_power_on(glue->phy);
}
#endif
static SIMPLE_DEV_PM_OPS(da8xx_pm_ops, da8xx_suspend, da8xx_resume);
#ifdef CONFIG_OF
static const struct of_device_id da8xx_id_table[] = {
{
.compatible = "ti,da830-musb",
},
{},
};
MODULE_DEVICE_TABLE(of, da8xx_id_table);
#endif
static struct platform_driver da8xx_driver = {
.probe = da8xx_probe,
.remove = da8xx_remove,
.driver = {
.name = "musb-da8xx",
.pm = &da8xx_pm_ops,
.of_match_table = of_match_ptr(da8xx_id_table),
},
};
MODULE_DESCRIPTION("DA8xx/OMAP-L1x MUSB Glue Layer");
MODULE_AUTHOR("Sergei Shtylyov <sshtylyov@ru.mvista.com>");
MODULE_LICENSE("GPL v2");
module_platform_driver(da8xx_driver);
+338
View File
@@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Ingenic JZ4740 "glue layer"
*
* Copyright (C) 2013, Apelete Seketeli <apelete@seketeli.net>
*/
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/usb/role.h>
#include <linux/usb/usb_phy_generic.h>
#include "musb_core.h"
struct jz4740_glue {
struct platform_device *pdev;
struct musb *musb;
struct clk *clk;
struct usb_role_switch *role_sw;
};
static irqreturn_t jz4740_musb_interrupt(int irq, void *__hci)
{
unsigned long flags;
irqreturn_t retval = IRQ_NONE, retval_dma = IRQ_NONE;
struct musb *musb = __hci;
if (IS_ENABLED(CONFIG_USB_INVENTRA_DMA) && musb->dma_controller)
retval_dma = dma_controller_irq(irq, musb->dma_controller);
spin_lock_irqsave(&musb->lock, flags);
musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
/*
* The controller is gadget only, the state of the host mode IRQ bits is
* undefined. Mask them to make sure that the musb driver core will
* never see them set
*/
musb->int_usb &= MUSB_INTR_SUSPEND | MUSB_INTR_RESUME |
MUSB_INTR_RESET | MUSB_INTR_SOF;
if (musb->int_usb || musb->int_tx || musb->int_rx)
retval = musb_interrupt(musb);
spin_unlock_irqrestore(&musb->lock, flags);
if (retval == IRQ_HANDLED || retval_dma == IRQ_HANDLED)
return IRQ_HANDLED;
return IRQ_NONE;
}
static const struct musb_fifo_cfg jz4740_musb_fifo_cfg[] = {
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 64, },
};
static const struct musb_hdrc_config jz4740_musb_config = {
/* Silicon does not implement USB OTG. */
.multipoint = 0,
/* Max EPs scanned, driver will decide which EP can be used. */
.num_eps = 4,
/* RAMbits needed to configure EPs from table */
.ram_bits = 9,
.fifo_cfg = jz4740_musb_fifo_cfg,
.fifo_cfg_size = ARRAY_SIZE(jz4740_musb_fifo_cfg),
};
static int jz4740_musb_role_switch_set(struct usb_role_switch *sw,
enum usb_role role)
{
struct jz4740_glue *glue = usb_role_switch_get_drvdata(sw);
struct usb_phy *phy = glue->musb->xceiv;
if (!phy)
return 0;
switch (role) {
case USB_ROLE_NONE:
atomic_notifier_call_chain(&phy->notifier, USB_EVENT_NONE, phy);
break;
case USB_ROLE_DEVICE:
atomic_notifier_call_chain(&phy->notifier, USB_EVENT_VBUS, phy);
break;
case USB_ROLE_HOST:
atomic_notifier_call_chain(&phy->notifier, USB_EVENT_ID, phy);
break;
}
return 0;
}
static int jz4740_musb_init(struct musb *musb)
{
struct device *dev = musb->controller->parent;
struct jz4740_glue *glue = dev_get_drvdata(dev);
struct usb_role_switch_desc role_sw_desc = {
.set = jz4740_musb_role_switch_set,
.driver_data = glue,
.fwnode = dev_fwnode(dev),
};
int err;
glue->musb = musb;
if (IS_ENABLED(CONFIG_GENERIC_PHY)) {
musb->phy = devm_of_phy_get_by_index(dev, dev->of_node, 0);
if (IS_ERR(musb->phy)) {
err = PTR_ERR(musb->phy);
if (err != -ENODEV) {
dev_err(dev, "Unable to get PHY\n");
return err;
}
musb->phy = NULL;
}
}
if (musb->phy) {
err = phy_init(musb->phy);
if (err) {
dev_err(dev, "Failed to init PHY\n");
return err;
}
err = phy_power_on(musb->phy);
if (err) {
dev_err(dev, "Unable to power on PHY\n");
goto err_phy_shutdown;
}
} else {
if (dev->of_node)
musb->xceiv = devm_usb_get_phy_by_phandle(dev, "phys", 0);
else
musb->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
if (IS_ERR(musb->xceiv)) {
dev_err(dev, "No transceiver configured\n");
return PTR_ERR(musb->xceiv);
}
}
glue->role_sw = usb_role_switch_register(dev, &role_sw_desc);
if (IS_ERR(glue->role_sw)) {
dev_err(dev, "Failed to register USB role switch\n");
err = PTR_ERR(glue->role_sw);
goto err_phy_power_down;
}
/*
* Silicon does not implement ConfigData register.
* Set dyn_fifo to avoid reading EP config from hardware.
*/
musb->dyn_fifo = true;
musb->isr = jz4740_musb_interrupt;
return 0;
err_phy_power_down:
if (musb->phy)
phy_power_off(musb->phy);
err_phy_shutdown:
if (musb->phy)
phy_exit(musb->phy);
return err;
}
static int jz4740_musb_exit(struct musb *musb)
{
struct jz4740_glue *glue = dev_get_drvdata(musb->controller->parent);
usb_role_switch_unregister(glue->role_sw);
if (musb->phy) {
phy_power_off(musb->phy);
phy_exit(musb->phy);
}
return 0;
}
static const struct musb_platform_ops jz4740_musb_ops = {
.quirks = MUSB_DMA_INVENTRA | MUSB_INDEXED_EP,
.fifo_mode = 2,
.init = jz4740_musb_init,
.exit = jz4740_musb_exit,
#ifdef CONFIG_USB_INVENTRA_DMA
.dma_init = musbhs_dma_controller_create_noirq,
.dma_exit = musbhs_dma_controller_destroy,
#endif
};
static const struct musb_hdrc_platform_data jz4740_musb_pdata = {
.mode = MUSB_PERIPHERAL,
.config = &jz4740_musb_config,
.platform_ops = &jz4740_musb_ops,
};
static const struct musb_fifo_cfg jz4770_musb_fifo_cfg[] = {
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 5, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 5, .style = FIFO_RX, .maxpacket = 512, },
};
static struct musb_hdrc_config jz4770_musb_config = {
.multipoint = 1,
.num_eps = 11,
.ram_bits = 11,
.fifo_cfg = jz4770_musb_fifo_cfg,
.fifo_cfg_size = ARRAY_SIZE(jz4770_musb_fifo_cfg),
};
static const struct musb_hdrc_platform_data jz4770_musb_pdata = {
.mode = MUSB_PERIPHERAL, /* TODO: support OTG */
.config = &jz4770_musb_config,
.platform_ops = &jz4740_musb_ops,
};
static int jz4740_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct musb_hdrc_platform_data *pdata;
struct platform_device *musb;
struct jz4740_glue *glue;
struct clk *clk;
int ret;
glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL);
if (!glue)
return -ENOMEM;
pdata = of_device_get_match_data(dev);
if (!pdata) {
dev_err(dev, "missing platform data\n");
return -EINVAL;
}
musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO);
if (!musb) {
dev_err(dev, "failed to allocate musb device\n");
return -ENOMEM;
}
clk = devm_clk_get(dev, "udc");
if (IS_ERR(clk)) {
dev_err(dev, "failed to get clock\n");
ret = PTR_ERR(clk);
goto err_platform_device_put;
}
ret = clk_prepare_enable(clk);
if (ret) {
dev_err(dev, "failed to enable clock\n");
goto err_platform_device_put;
}
musb->dev.parent = dev;
musb->dev.dma_mask = &musb->dev.coherent_dma_mask;
musb->dev.coherent_dma_mask = DMA_BIT_MASK(32);
device_set_of_node_from_dev(&musb->dev, dev);
glue->pdev = musb;
glue->clk = clk;
platform_set_drvdata(pdev, glue);
ret = platform_device_add_resources(musb, pdev->resource,
pdev->num_resources);
if (ret) {
dev_err(dev, "failed to add resources\n");
goto err_clk_disable;
}
ret = platform_device_add_data(musb, pdata, sizeof(*pdata));
if (ret) {
dev_err(dev, "failed to add platform_data\n");
goto err_clk_disable;
}
ret = platform_device_add(musb);
if (ret) {
dev_err(dev, "failed to register musb device\n");
goto err_clk_disable;
}
return 0;
err_clk_disable:
clk_disable_unprepare(clk);
err_platform_device_put:
platform_device_put(musb);
return ret;
}
static void jz4740_remove(struct platform_device *pdev)
{
struct jz4740_glue *glue = platform_get_drvdata(pdev);
platform_device_unregister(glue->pdev);
clk_disable_unprepare(glue->clk);
}
static const struct of_device_id jz4740_musb_of_match[] = {
{ .compatible = "ingenic,jz4740-musb", .data = &jz4740_musb_pdata },
{ .compatible = "ingenic,jz4770-musb", .data = &jz4770_musb_pdata },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, jz4740_musb_of_match);
static struct platform_driver jz4740_driver = {
.probe = jz4740_probe,
.remove = jz4740_remove,
.driver = {
.name = "musb-jz4740",
.of_match_table = jz4740_musb_of_match,
},
};
MODULE_DESCRIPTION("JZ4740 MUSB Glue Layer");
MODULE_AUTHOR("Apelete Seketeli <apelete@seketeli.net>");
MODULE_LICENSE("GPL v2");
module_platform_driver(jz4740_driver);
+537
View File
@@ -0,0 +1,537 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 MediaTek Inc.
*
* Author:
* Min Guo <min.guo@mediatek.com>
* Yonglong Wu <yonglong.wu@mediatek.com>
*/
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/usb/role.h>
#include <linux/usb/usb_phy_generic.h>
#include "musb_core.h"
#include "musb_dma.h"
#define USB_L1INTS 0x00a0
#define USB_L1INTM 0x00a4
#define MTK_MUSB_TXFUNCADDR 0x0480
/* MediaTek controller toggle enable and status reg */
#define MUSB_RXTOG 0x80
#define MUSB_RXTOGEN 0x82
#define MUSB_TXTOG 0x84
#define MUSB_TXTOGEN 0x86
#define MTK_TOGGLE_EN GENMASK(15, 0)
#define TX_INT_STATUS BIT(0)
#define RX_INT_STATUS BIT(1)
#define USBCOM_INT_STATUS BIT(2)
#define DMA_INT_STATUS BIT(3)
#define DMA_INTR_STATUS_MSK GENMASK(7, 0)
#define DMA_INTR_UNMASK_SET_MSK GENMASK(31, 24)
#define MTK_MUSB_CLKS_NUM 3
struct mtk_glue {
struct device *dev;
struct musb *musb;
struct platform_device *musb_pdev;
struct platform_device *usb_phy;
struct phy *phy;
struct usb_phy *xceiv;
enum phy_mode phy_mode;
struct clk_bulk_data clks[MTK_MUSB_CLKS_NUM];
enum usb_role role;
struct usb_role_switch *role_sw;
};
static int mtk_musb_clks_get(struct mtk_glue *glue)
{
struct device *dev = glue->dev;
glue->clks[0].id = "main";
glue->clks[1].id = "mcu";
glue->clks[2].id = "univpll";
return devm_clk_bulk_get(dev, MTK_MUSB_CLKS_NUM, glue->clks);
}
static int mtk_otg_switch_set(struct mtk_glue *glue, enum usb_role role)
{
struct musb *musb = glue->musb;
u8 devctl = readb(musb->mregs + MUSB_DEVCTL);
enum usb_role new_role;
if (role == glue->role)
return 0;
switch (role) {
case USB_ROLE_HOST:
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE;
glue->phy_mode = PHY_MODE_USB_HOST;
new_role = USB_ROLE_HOST;
if (glue->role == USB_ROLE_NONE)
phy_power_on(glue->phy);
devctl |= MUSB_DEVCTL_SESSION;
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
MUSB_HST_MODE(musb);
break;
case USB_ROLE_DEVICE:
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
glue->phy_mode = PHY_MODE_USB_DEVICE;
new_role = USB_ROLE_DEVICE;
devctl &= ~MUSB_DEVCTL_SESSION;
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
if (glue->role == USB_ROLE_NONE)
phy_power_on(glue->phy);
MUSB_DEV_MODE(musb);
break;
case USB_ROLE_NONE:
glue->phy_mode = PHY_MODE_USB_OTG;
new_role = USB_ROLE_NONE;
devctl &= ~MUSB_DEVCTL_SESSION;
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
if (glue->role != USB_ROLE_NONE)
phy_power_off(glue->phy);
break;
default:
dev_err(glue->dev, "Invalid State\n");
return -EINVAL;
}
glue->role = new_role;
phy_set_mode(glue->phy, glue->phy_mode);
return 0;
}
static int musb_usb_role_sx_set(struct usb_role_switch *sw, enum usb_role role)
{
return mtk_otg_switch_set(usb_role_switch_get_drvdata(sw), role);
}
static enum usb_role musb_usb_role_sx_get(struct usb_role_switch *sw)
{
struct mtk_glue *glue = usb_role_switch_get_drvdata(sw);
return glue->role;
}
static int mtk_otg_switch_init(struct mtk_glue *glue)
{
struct usb_role_switch_desc role_sx_desc = { 0 };
role_sx_desc.set = musb_usb_role_sx_set;
role_sx_desc.get = musb_usb_role_sx_get;
role_sx_desc.allow_userspace_control = true;
role_sx_desc.fwnode = dev_fwnode(glue->dev);
role_sx_desc.driver_data = glue;
glue->role_sw = usb_role_switch_register(glue->dev, &role_sx_desc);
return PTR_ERR_OR_ZERO(glue->role_sw);
}
static void mtk_otg_switch_exit(struct mtk_glue *glue)
{
return usb_role_switch_unregister(glue->role_sw);
}
static irqreturn_t generic_interrupt(int irq, void *__hci)
{
unsigned long flags;
irqreturn_t retval = IRQ_NONE;
struct musb *musb = __hci;
spin_lock_irqsave(&musb->lock, flags);
musb->int_usb = musb_clearb(musb->mregs, MUSB_INTRUSB);
musb->int_rx = musb_clearw(musb->mregs, MUSB_INTRRX);
musb->int_tx = musb_clearw(musb->mregs, MUSB_INTRTX);
if ((musb->int_usb & MUSB_INTR_RESET) && !is_host_active(musb)) {
/* ep0 FADDR must be 0 when (re)entering peripheral mode */
musb_ep_select(musb->mregs, 0);
musb_writeb(musb->mregs, MUSB_FADDR, 0);
}
if (musb->int_usb || musb->int_tx || musb->int_rx)
retval = musb_interrupt(musb);
spin_unlock_irqrestore(&musb->lock, flags);
return retval;
}
static irqreturn_t mtk_musb_interrupt(int irq, void *dev_id)
{
irqreturn_t retval = IRQ_NONE;
struct musb *musb = (struct musb *)dev_id;
u32 l1_ints;
l1_ints = musb_readl(musb->mregs, USB_L1INTS) &
musb_readl(musb->mregs, USB_L1INTM);
if (l1_ints & (TX_INT_STATUS | RX_INT_STATUS | USBCOM_INT_STATUS))
retval = generic_interrupt(irq, musb);
#if defined(CONFIG_USB_INVENTRA_DMA)
if (l1_ints & DMA_INT_STATUS)
retval = dma_controller_irq(irq, musb->dma_controller);
#endif
return retval;
}
static u32 mtk_musb_busctl_offset(u8 epnum, u16 offset)
{
return MTK_MUSB_TXFUNCADDR + offset + 8 * epnum;
}
static u8 mtk_musb_clearb(void __iomem *addr, unsigned int offset)
{
u8 data;
/* W1C */
data = musb_readb(addr, offset);
musb_writeb(addr, offset, data);
return data;
}
static u16 mtk_musb_clearw(void __iomem *addr, unsigned int offset)
{
u16 data;
/* W1C */
data = musb_readw(addr, offset);
musb_writew(addr, offset, data);
return data;
}
static int mtk_musb_set_mode(struct musb *musb, u8 mode)
{
struct device *dev = musb->controller;
struct mtk_glue *glue = dev_get_drvdata(dev->parent);
enum phy_mode new_mode;
enum usb_role new_role;
switch (mode) {
case MUSB_HOST:
new_mode = PHY_MODE_USB_HOST;
new_role = USB_ROLE_HOST;
break;
case MUSB_PERIPHERAL:
new_mode = PHY_MODE_USB_DEVICE;
new_role = USB_ROLE_DEVICE;
break;
case MUSB_OTG:
new_mode = PHY_MODE_USB_OTG;
new_role = USB_ROLE_NONE;
break;
default:
dev_err(glue->dev, "Invalid mode request\n");
return -EINVAL;
}
if (glue->phy_mode == new_mode)
return 0;
if (musb->port_mode != MUSB_OTG) {
dev_err(glue->dev, "Does not support changing modes\n");
return -EINVAL;
}
mtk_otg_switch_set(glue, new_role);
return 0;
}
static int mtk_musb_init(struct musb *musb)
{
struct device *dev = musb->controller;
struct mtk_glue *glue = dev_get_drvdata(dev->parent);
int ret;
glue->musb = musb;
musb->phy = glue->phy;
musb->xceiv = glue->xceiv;
musb->is_host = false;
musb->isr = mtk_musb_interrupt;
/* Set TX/RX toggle enable */
musb_writew(musb->mregs, MUSB_TXTOGEN, MTK_TOGGLE_EN);
musb_writew(musb->mregs, MUSB_RXTOGEN, MTK_TOGGLE_EN);
if (musb->port_mode == MUSB_OTG) {
ret = mtk_otg_switch_init(glue);
if (ret)
return ret;
}
ret = phy_init(glue->phy);
if (ret)
goto err_phy_init;
ret = phy_power_on(glue->phy);
if (ret)
goto err_phy_power_on;
phy_set_mode(glue->phy, glue->phy_mode);
#if defined(CONFIG_USB_INVENTRA_DMA)
musb_writel(musb->mregs, MUSB_HSDMA_INTR,
DMA_INTR_STATUS_MSK | DMA_INTR_UNMASK_SET_MSK);
#endif
musb_writel(musb->mregs, USB_L1INTM, TX_INT_STATUS | RX_INT_STATUS |
USBCOM_INT_STATUS | DMA_INT_STATUS);
return 0;
err_phy_power_on:
phy_exit(glue->phy);
err_phy_init:
if (musb->port_mode == MUSB_OTG)
mtk_otg_switch_exit(glue);
return ret;
}
static u16 mtk_musb_get_toggle(struct musb_qh *qh, int is_out)
{
struct musb *musb = qh->hw_ep->musb;
u8 epnum = qh->hw_ep->epnum;
u16 toggle;
toggle = musb_readw(musb->mregs, is_out ? MUSB_TXTOG : MUSB_RXTOG);
return toggle & (1 << epnum);
}
static u16 mtk_musb_set_toggle(struct musb_qh *qh, int is_out, struct urb *urb)
{
struct musb *musb = qh->hw_ep->musb;
u8 epnum = qh->hw_ep->epnum;
u16 value, toggle;
toggle = usb_gettoggle(urb->dev, qh->epnum, is_out);
if (is_out) {
value = musb_readw(musb->mregs, MUSB_TXTOG);
value |= toggle << epnum;
musb_writew(musb->mregs, MUSB_TXTOG, value);
} else {
value = musb_readw(musb->mregs, MUSB_RXTOG);
value |= toggle << epnum;
musb_writew(musb->mregs, MUSB_RXTOG, value);
}
return 0;
}
static int mtk_musb_exit(struct musb *musb)
{
struct device *dev = musb->controller;
struct mtk_glue *glue = dev_get_drvdata(dev->parent);
mtk_otg_switch_exit(glue);
phy_power_off(glue->phy);
phy_exit(glue->phy);
clk_bulk_disable_unprepare(MTK_MUSB_CLKS_NUM, glue->clks);
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
return 0;
}
static const struct musb_platform_ops mtk_musb_ops = {
.quirks = MUSB_DMA_INVENTRA,
.init = mtk_musb_init,
.get_toggle = mtk_musb_get_toggle,
.set_toggle = mtk_musb_set_toggle,
.exit = mtk_musb_exit,
#ifdef CONFIG_USB_INVENTRA_DMA
.dma_init = musbhs_dma_controller_create_noirq,
.dma_exit = musbhs_dma_controller_destroy,
#endif
.clearb = mtk_musb_clearb,
.clearw = mtk_musb_clearw,
.busctl_offset = mtk_musb_busctl_offset,
.set_mode = mtk_musb_set_mode,
};
#define MTK_MUSB_MAX_EP_NUM 8
#define MTK_MUSB_RAM_BITS 11
static const struct musb_fifo_cfg mtk_musb_mode_cfg[] = {
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 5, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 5, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 6, .style = FIFO_TX, .maxpacket = 1024, },
{ .hw_ep_num = 6, .style = FIFO_RX, .maxpacket = 1024, },
{ .hw_ep_num = 7, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 7, .style = FIFO_RX, .maxpacket = 64, },
};
static const struct musb_hdrc_config mtk_musb_hdrc_config = {
.fifo_cfg = mtk_musb_mode_cfg,
.fifo_cfg_size = ARRAY_SIZE(mtk_musb_mode_cfg),
.multipoint = true,
.dyn_fifo = true,
.num_eps = MTK_MUSB_MAX_EP_NUM,
.ram_bits = MTK_MUSB_RAM_BITS,
};
static const struct platform_device_info mtk_dev_info = {
.name = "musb-hdrc",
.id = PLATFORM_DEVID_AUTO,
.dma_mask = DMA_BIT_MASK(32),
};
static int mtk_musb_probe(struct platform_device *pdev)
{
struct musb_hdrc_platform_data *pdata;
struct mtk_glue *glue;
struct platform_device_info pinfo;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int ret;
glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL);
if (!glue)
return -ENOMEM;
glue->dev = dev;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
ret = of_platform_populate(np, NULL, NULL, dev);
if (ret)
return dev_err_probe(dev, ret,
"failed to create child devices at %p\n", np);
ret = mtk_musb_clks_get(glue);
if (ret)
return ret;
pdata->config = &mtk_musb_hdrc_config;
pdata->platform_ops = &mtk_musb_ops;
pdata->mode = usb_get_dr_mode(dev);
if (IS_ENABLED(CONFIG_USB_MUSB_HOST))
pdata->mode = USB_DR_MODE_HOST;
else if (IS_ENABLED(CONFIG_USB_MUSB_GADGET))
pdata->mode = USB_DR_MODE_PERIPHERAL;
switch (pdata->mode) {
case USB_DR_MODE_HOST:
glue->phy_mode = PHY_MODE_USB_HOST;
glue->role = USB_ROLE_HOST;
break;
case USB_DR_MODE_PERIPHERAL:
glue->phy_mode = PHY_MODE_USB_DEVICE;
glue->role = USB_ROLE_DEVICE;
break;
case USB_DR_MODE_OTG:
glue->phy_mode = PHY_MODE_USB_OTG;
glue->role = USB_ROLE_NONE;
break;
default:
return dev_err_probe(&pdev->dev, -EINVAL,
"Error 'dr_mode' property\n");
}
glue->phy = devm_of_phy_get_by_index(dev, np, 0);
if (IS_ERR(glue->phy))
return dev_err_probe(dev, PTR_ERR(glue->phy),
"fail to getting phy\n");
glue->usb_phy = usb_phy_generic_register();
if (IS_ERR(glue->usb_phy))
return dev_err_probe(dev, PTR_ERR(glue->usb_phy),
"fail to registering usb-phy\n");
glue->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
if (IS_ERR(glue->xceiv)) {
ret = PTR_ERR(glue->xceiv);
dev_err(dev, "fail to getting usb-phy %d\n", ret);
goto err_unregister_usb_phy;
}
platform_set_drvdata(pdev, glue);
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
ret = clk_bulk_prepare_enable(MTK_MUSB_CLKS_NUM, glue->clks);
if (ret)
goto err_enable_clk;
pinfo = mtk_dev_info;
pinfo.parent = dev;
pinfo.res = pdev->resource;
pinfo.num_res = pdev->num_resources;
pinfo.data = pdata;
pinfo.size_data = sizeof(*pdata);
pinfo.fwnode = of_fwnode_handle(np);
pinfo.of_node_reused = true;
glue->musb_pdev = platform_device_register_full(&pinfo);
if (IS_ERR(glue->musb_pdev)) {
ret = PTR_ERR(glue->musb_pdev);
dev_err(dev, "failed to register musb device: %d\n", ret);
goto err_device_register;
}
return 0;
err_device_register:
clk_bulk_disable_unprepare(MTK_MUSB_CLKS_NUM, glue->clks);
err_enable_clk:
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
err_unregister_usb_phy:
usb_phy_generic_unregister(glue->usb_phy);
return ret;
}
static void mtk_musb_remove(struct platform_device *pdev)
{
struct mtk_glue *glue = platform_get_drvdata(pdev);
struct platform_device *usb_phy = glue->usb_phy;
platform_device_unregister(glue->musb_pdev);
usb_phy_generic_unregister(usb_phy);
}
#ifdef CONFIG_OF
static const struct of_device_id mtk_musb_match[] = {
{.compatible = "mediatek,mtk-musb",},
{},
};
MODULE_DEVICE_TABLE(of, mtk_musb_match);
#endif
static struct platform_driver mtk_musb_driver = {
.probe = mtk_musb_probe,
.remove = mtk_musb_remove,
.driver = {
.name = "musb-mtk",
.of_match_table = of_match_ptr(mtk_musb_match),
},
};
module_platform_driver(mtk_musb_driver);
MODULE_DESCRIPTION("MediaTek MUSB Glue Layer");
MODULE_AUTHOR("Min Guo <min.guo@mediatek.com>");
MODULE_LICENSE("GPL v2");
+383
View File
@@ -0,0 +1,383 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PolarFire SoC (MPFS) MUSB Glue Layer
*
* Copyright (c) 2020-2022 Microchip Corporation. All rights reserved.
* Based on {omap2430,tusb6010,ux500}.c
*
*/
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/usb/usb_phy_generic.h>
#include "musb_core.h"
#include "musb_dma.h"
#define MPFS_MUSB_MAX_EP_NUM 8
#define MPFS_MUSB_RAM_BITS 12
struct mpfs_glue {
struct device *dev;
struct platform_device *musb;
struct platform_device *phy;
struct clk *clk;
};
static const struct musb_fifo_cfg mpfs_musb_mode_cfg[] = {
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 1024, },
{ .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 4096, },
};
static const struct musb_hdrc_config mpfs_musb_hdrc_config = {
.fifo_cfg = mpfs_musb_mode_cfg,
.fifo_cfg_size = ARRAY_SIZE(mpfs_musb_mode_cfg),
.multipoint = true,
.dyn_fifo = true,
.num_eps = MPFS_MUSB_MAX_EP_NUM,
.ram_bits = MPFS_MUSB_RAM_BITS,
};
static void mpfs_musb_set_vbus(struct musb *musb, int is_on)
{
u8 devctl;
/*
* HDRC controls CPEN, but beware current surges during device
* connect. They can trigger transient overcurrent conditions
* that must be ignored.
*/
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
if (is_on) {
musb->is_active = 1;
musb->xceiv->otg->default_a = 1;
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE;
devctl |= MUSB_DEVCTL_SESSION;
MUSB_HST_MODE(musb);
} else {
musb->is_active = 0;
/*
* NOTE: skipping A_WAIT_VFALL -> A_IDLE and
* jumping right to B_IDLE...
*/
musb->xceiv->otg->default_a = 0;
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
devctl &= ~MUSB_DEVCTL_SESSION;
MUSB_DEV_MODE(musb);
}
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
dev_dbg(musb->controller, "VBUS %s, devctl %02x\n",
usb_otg_state_string(musb->xceiv->otg->state),
musb_readb(musb->mregs, MUSB_DEVCTL));
}
#define POLL_SECONDS 2
static void otg_timer(struct timer_list *t)
{
struct musb *musb = timer_container_of(musb, t,
dev_timer);
void __iomem *mregs = musb->mregs;
u8 devctl;
unsigned long flags;
/*
* We poll because PolarFire SoC won't expose several OTG-critical
* status change events (from the transceiver) otherwise.
*/
devctl = musb_readb(mregs, MUSB_DEVCTL);
dev_dbg(musb->controller, "Poll devctl %02x (%s)\n", devctl,
usb_otg_state_string(musb->xceiv->otg->state));
spin_lock_irqsave(&musb->lock, flags);
switch (musb->xceiv->otg->state) {
case OTG_STATE_A_WAIT_BCON:
devctl &= ~MUSB_DEVCTL_SESSION;
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
if (devctl & MUSB_DEVCTL_BDEVICE) {
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
MUSB_DEV_MODE(musb);
mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ);
} else {
musb->xceiv->otg->state = OTG_STATE_A_IDLE;
MUSB_HST_MODE(musb);
}
break;
case OTG_STATE_A_WAIT_VFALL:
if (devctl & MUSB_DEVCTL_VBUS) {
mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ);
break;
}
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE;
break;
case OTG_STATE_B_IDLE:
/*
* There's no ID-changed IRQ, so we have no good way to tell
* when to switch to the A-Default state machine (by setting
* the DEVCTL.Session bit).
*
* Workaround: whenever we're in B_IDLE, try setting the
* session flag every few seconds. If it works, ID was
* grounded and we're now in the A-Default state machine.
*
* NOTE: setting the session flag is _supposed_ to trigger
* SRP but clearly it doesn't.
*/
musb_writeb(mregs, MUSB_DEVCTL, devctl | MUSB_DEVCTL_SESSION);
devctl = musb_readb(mregs, MUSB_DEVCTL);
if (devctl & MUSB_DEVCTL_BDEVICE)
mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ);
else
musb->xceiv->otg->state = OTG_STATE_A_IDLE;
break;
default:
break;
}
spin_unlock_irqrestore(&musb->lock, flags);
}
static void __maybe_unused mpfs_musb_try_idle(struct musb *musb, unsigned long timeout)
{
static unsigned long last_timer;
if (timeout == 0)
timeout = jiffies + msecs_to_jiffies(3);
/* Never idle if active, or when VBUS timeout is not set as host */
if (musb->is_active || (musb->a_wait_bcon == 0 &&
musb->xceiv->otg->state == OTG_STATE_A_WAIT_BCON)) {
dev_dbg(musb->controller, "%s active, deleting timer\n",
usb_otg_state_string(musb->xceiv->otg->state));
timer_delete(&musb->dev_timer);
last_timer = jiffies;
return;
}
if (time_after(last_timer, timeout) && timer_pending(&musb->dev_timer)) {
dev_dbg(musb->controller, "Longer idle timer already pending, ignoring...\n");
return;
}
last_timer = timeout;
dev_dbg(musb->controller, "%s inactive, starting idle timer for %u ms\n",
usb_otg_state_string(musb->xceiv->otg->state),
jiffies_to_msecs(timeout - jiffies));
mod_timer(&musb->dev_timer, timeout);
}
static irqreturn_t mpfs_musb_interrupt(int irq, void *__hci)
{
unsigned long flags;
irqreturn_t ret = IRQ_NONE;
struct musb *musb = __hci;
spin_lock_irqsave(&musb->lock, flags);
musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
if (musb->int_usb || musb->int_tx || musb->int_rx) {
musb_writeb(musb->mregs, MUSB_INTRUSB, musb->int_usb);
musb_writew(musb->mregs, MUSB_INTRTX, musb->int_tx);
musb_writew(musb->mregs, MUSB_INTRRX, musb->int_rx);
ret = musb_interrupt(musb);
}
/* Poll for ID change */
if (musb->xceiv->otg->state == OTG_STATE_B_IDLE)
mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ);
spin_unlock_irqrestore(&musb->lock, flags);
return ret;
}
static int mpfs_musb_init(struct musb *musb)
{
struct device *dev = musb->controller;
musb->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
if (IS_ERR(musb->xceiv)) {
dev_err(dev, "HS UDC: no transceiver configured\n");
return PTR_ERR(musb->xceiv);
}
timer_setup(&musb->dev_timer, otg_timer, 0);
musb->dyn_fifo = true;
musb->isr = mpfs_musb_interrupt;
musb_platform_set_vbus(musb, 1);
return 0;
}
static int mpfs_musb_exit(struct musb *musb)
{
timer_delete_sync(&musb->dev_timer);
return 0;
}
static const struct musb_platform_ops mpfs_ops = {
.quirks = MUSB_DMA_INVENTRA,
.init = mpfs_musb_init,
.exit = mpfs_musb_exit,
.fifo_mode = 2,
#ifdef CONFIG_USB_INVENTRA_DMA
.dma_init = musbhs_dma_controller_create,
.dma_exit = musbhs_dma_controller_destroy,
#endif
#ifndef CONFIG_USB_MUSB_HOST
.try_idle = mpfs_musb_try_idle,
#endif
.set_vbus = mpfs_musb_set_vbus
};
static int mpfs_probe(struct platform_device *pdev)
{
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct mpfs_glue *glue;
struct platform_device *musb_pdev;
struct device *dev = &pdev->dev;
struct clk *clk;
int ret;
glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL);
if (!glue)
return -ENOMEM;
musb_pdev = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO);
if (!musb_pdev) {
dev_err(dev, "failed to allocate musb device\n");
return -ENOMEM;
}
clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
ret = PTR_ERR(clk);
goto err_phy_release;
}
ret = clk_prepare_enable(clk);
if (ret) {
dev_err(&pdev->dev, "failed to enable clock\n");
goto err_phy_release;
}
musb_pdev->dev.parent = dev;
musb_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(39);
musb_pdev->dev.dma_mask = &musb_pdev->dev.coherent_dma_mask;
device_set_of_node_from_dev(&musb_pdev->dev, dev);
glue->dev = dev;
glue->musb = musb_pdev;
glue->clk = clk;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
ret = -ENOMEM;
goto err_clk_disable;
}
pdata->config = &mpfs_musb_hdrc_config;
pdata->platform_ops = &mpfs_ops;
pdata->extvbus = device_property_read_bool(dev, "microchip,ext-vbus-drv");
pdata->mode = usb_get_dr_mode(dev);
if (pdata->mode == USB_DR_MODE_UNKNOWN) {
dev_info(dev, "No dr_mode property found, defaulting to otg\n");
pdata->mode = USB_DR_MODE_OTG;
}
glue->phy = usb_phy_generic_register();
if (IS_ERR(glue->phy)) {
dev_err(dev, "failed to register usb-phy %ld\n",
PTR_ERR(glue->phy));
ret = PTR_ERR(glue->phy);
goto err_clk_disable;
}
platform_set_drvdata(pdev, glue);
ret = platform_device_add_resources(musb_pdev, pdev->resource, pdev->num_resources);
if (ret) {
dev_err(dev, "failed to add resources\n");
goto err_clk_disable;
}
ret = platform_device_add_data(musb_pdev, pdata, sizeof(*pdata));
if (ret) {
dev_err(dev, "failed to add platform_data\n");
goto err_clk_disable;
}
ret = platform_device_add(musb_pdev);
if (ret) {
dev_err(dev, "failed to register musb device\n");
goto err_clk_disable;
}
dev_info(&pdev->dev, "Registered MPFS MUSB driver\n");
return 0;
err_clk_disable:
clk_disable_unprepare(clk);
err_phy_release:
usb_phy_generic_unregister(glue->phy);
platform_device_put(musb_pdev);
return ret;
}
static void mpfs_remove(struct platform_device *pdev)
{
struct mpfs_glue *glue = platform_get_drvdata(pdev);
clk_disable_unprepare(glue->clk);
platform_device_unregister(glue->musb);
usb_phy_generic_unregister(pdev);
}
#ifdef CONFIG_OF
static const struct of_device_id mpfs_id_table[] = {
{ .compatible = "microchip,mpfs-musb" },
{ }
};
MODULE_DEVICE_TABLE(of, mpfs_id_table);
#endif
static struct platform_driver mpfs_musb_driver = {
.probe = mpfs_probe,
.remove = mpfs_remove,
.driver = {
.name = "mpfs-musb",
.of_match_table = of_match_ptr(mpfs_id_table)
},
};
module_platform_driver(mpfs_musb_driver);
MODULE_DESCRIPTION("PolarFire SoC MUSB Glue Layer");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+623
View File
@@ -0,0 +1,623 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* MUSB OTG driver defines
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
*/
#ifndef __MUSB_CORE_H__
#define __MUSB_CORE_H__
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/device.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb.h>
#include <linux/usb/otg.h>
#include <linux/usb/musb.h>
#include <linux/phy/phy.h>
#include <linux/workqueue.h>
struct musb;
struct musb_hw_ep;
struct musb_ep;
struct musb_qh;
/* Helper defines for struct musb->hwvers */
#define MUSB_HWVERS_MAJOR(x) ((x >> 10) & 0x1f)
#define MUSB_HWVERS_MINOR(x) (x & 0x3ff)
#define MUSB_HWVERS_RC 0x8000
#define MUSB_HWVERS_1300 0x52C
#define MUSB_HWVERS_1400 0x590
#define MUSB_HWVERS_1800 0x720
#define MUSB_HWVERS_1900 0x784
#define MUSB_HWVERS_2000 0x800
#include "musb_debug.h"
#include "musb_dma.h"
#include "musb_io.h"
#include "musb_gadget.h"
#include <linux/usb/hcd.h>
#include "musb_host.h"
/* NOTE: otg and peripheral-only state machines start at B_IDLE.
* OTG or host-only go to A_IDLE when ID is sensed.
*/
#define is_peripheral_active(m) (!(m)->is_host)
#define is_host_active(m) ((m)->is_host)
/****************************** CONSTANTS ********************************/
#ifndef MUSB_C_NUM_EPS
#define MUSB_C_NUM_EPS ((u8)16)
#endif
#ifndef MUSB_MAX_END0_PACKET
#define MUSB_MAX_END0_PACKET ((u16)MUSB_EP0_FIFOSIZE)
#endif
/* host side ep0 states */
enum musb_h_ep0_state {
MUSB_EP0_IDLE,
MUSB_EP0_START, /* expect ack of setup */
MUSB_EP0_IN, /* expect IN DATA */
MUSB_EP0_OUT, /* expect ack of OUT DATA */
MUSB_EP0_STATUS, /* expect ack of STATUS */
} __attribute__ ((packed));
/* peripheral side ep0 states */
enum musb_g_ep0_state {
MUSB_EP0_STAGE_IDLE, /* idle, waiting for SETUP */
MUSB_EP0_STAGE_SETUP, /* received SETUP */
MUSB_EP0_STAGE_TX, /* IN data */
MUSB_EP0_STAGE_RX, /* OUT data */
MUSB_EP0_STAGE_STATUSIN, /* (after OUT data) */
MUSB_EP0_STAGE_STATUSOUT, /* (after IN data) */
MUSB_EP0_STAGE_ACKWAIT, /* after zlp, before statusin */
} __attribute__ ((packed));
/*
* OTG protocol constants. See USB OTG 1.3 spec,
* sections 5.5 "Device Timings" and 6.6.5 "Timers".
*/
#define OTG_TIME_A_WAIT_VRISE 100 /* msec (max) */
#define OTG_TIME_A_WAIT_BCON 1100 /* min 1 second */
#define OTG_TIME_A_AIDL_BDIS 200 /* min 200 msec */
#define OTG_TIME_B_ASE0_BRST 100 /* min 3.125 ms */
/****************************** FUNCTIONS ********************************/
#define MUSB_HST_MODE(_musb)\
{ (_musb)->is_host = true; }
#define MUSB_DEV_MODE(_musb) \
{ (_musb)->is_host = false; }
#define test_devctl_hst_mode(_x) \
(musb_readb((_x)->mregs, MUSB_DEVCTL)&MUSB_DEVCTL_HM)
#define MUSB_MODE(musb) ((musb)->is_host ? "Host" : "Peripheral")
/******************************** TYPES *************************************/
struct musb_io;
/**
* struct musb_platform_ops - Operations passed to musb_core by HW glue layer
* @quirks: flags for platform specific quirks
* @enable: enable device
* @disable: disable device
* @ep_offset: returns the end point offset
* @ep_select: selects the specified end point
* @fifo_mode: sets the fifo mode
* @fifo_offset: returns the fifo offset
* @readb: read 8 bits
* @writeb: write 8 bits
* @clearb: could be clear-on-readb or W1C
* @readw: read 16 bits
* @writew: write 16 bits
* @clearw: could be clear-on-readw or W1C
* @read_fifo: reads the fifo
* @write_fifo: writes to fifo
* @get_toggle: platform specific get toggle function
* @set_toggle: platform specific set toggle function
* @dma_init: platform specific dma init function
* @dma_exit: platform specific dma exit function
* @init: turns on clocks, sets up platform-specific registers, etc
* @exit: undoes @init
* @set_mode: forcefully changes operating mode
* @try_idle: tries to idle the IP
* @recover: platform-specific babble recovery
* @vbus_status: returns vbus status if possible
* @set_vbus: forces vbus status
* @pre_root_reset_end: called before the root usb port reset flag gets cleared
* @post_root_reset_end: called after the root usb port reset flag gets cleared
* @phy_callback: optional callback function for the phy to call
*/
struct musb_platform_ops {
#define MUSB_G_NO_SKB_RESERVE BIT(9)
#define MUSB_DA8XX BIT(8)
#define MUSB_PRESERVE_SESSION BIT(7)
#define MUSB_DMA_UX500 BIT(6)
#define MUSB_DMA_CPPI41 BIT(5)
#define MUSB_DMA_CPPI BIT(4)
#define MUSB_DMA_TUSB_OMAP BIT(3)
#define MUSB_DMA_INVENTRA BIT(2)
#define MUSB_IN_TUSB BIT(1)
#define MUSB_INDEXED_EP BIT(0)
u32 quirks;
int (*init)(struct musb *musb);
int (*exit)(struct musb *musb);
void (*enable)(struct musb *musb);
void (*disable)(struct musb *musb);
u32 (*ep_offset)(u8 epnum, u16 offset);
void (*ep_select)(void __iomem *mbase, u8 epnum);
u16 fifo_mode;
u32 (*fifo_offset)(u8 epnum);
u32 (*busctl_offset)(u8 epnum, u16 offset);
u8 (*readb)(void __iomem *addr, u32 offset);
void (*writeb)(void __iomem *addr, u32 offset, u8 data);
u8 (*clearb)(void __iomem *addr, u32 offset);
u16 (*readw)(void __iomem *addr, u32 offset);
void (*writew)(void __iomem *addr, u32 offset, u16 data);
u16 (*clearw)(void __iomem *addr, u32 offset);
void (*read_fifo)(struct musb_hw_ep *hw_ep, u16 len, u8 *buf);
void (*write_fifo)(struct musb_hw_ep *hw_ep, u16 len, const u8 *buf);
u16 (*get_toggle)(struct musb_qh *qh, int is_out);
u16 (*set_toggle)(struct musb_qh *qh, int is_out, struct urb *urb);
struct dma_controller *
(*dma_init) (struct musb *musb, void __iomem *base);
void (*dma_exit)(struct dma_controller *c);
int (*set_mode)(struct musb *musb, u8 mode);
void (*try_idle)(struct musb *musb, unsigned long timeout);
int (*recover)(struct musb *musb);
int (*vbus_status)(struct musb *musb);
void (*set_vbus)(struct musb *musb, int on);
void (*pre_root_reset_end)(struct musb *musb);
void (*post_root_reset_end)(struct musb *musb);
int (*phy_callback)(enum musb_vbus_id_status status);
void (*clear_ep_rxintr)(struct musb *musb, int epnum);
};
/*
* struct musb_hw_ep - endpoint hardware (bidirectional)
*
* Ordered slightly for better cacheline locality.
*/
struct musb_hw_ep {
struct musb *musb;
void __iomem *fifo;
void __iomem *regs;
#if IS_ENABLED(CONFIG_USB_MUSB_TUSB6010)
void __iomem *conf;
#endif
/* index in musb->endpoints[] */
u8 epnum;
/* hardware configuration, possibly dynamic */
bool is_shared_fifo;
bool tx_double_buffered;
bool rx_double_buffered;
u16 max_packet_sz_tx;
u16 max_packet_sz_rx;
struct dma_channel *tx_channel;
struct dma_channel *rx_channel;
#if IS_ENABLED(CONFIG_USB_MUSB_TUSB6010)
/* TUSB has "asynchronous" and "synchronous" dma modes */
dma_addr_t fifo_async;
dma_addr_t fifo_sync;
void __iomem *fifo_sync_va;
#endif
/* currently scheduled peripheral endpoint */
struct musb_qh *in_qh;
struct musb_qh *out_qh;
u8 rx_reinit;
u8 tx_reinit;
/* peripheral side */
struct musb_ep ep_in; /* TX */
struct musb_ep ep_out; /* RX */
};
static inline struct musb_request *next_in_request(struct musb_hw_ep *hw_ep)
{
return next_request(&hw_ep->ep_in);
}
static inline struct musb_request *next_out_request(struct musb_hw_ep *hw_ep)
{
return next_request(&hw_ep->ep_out);
}
struct musb_csr_regs {
/* FIFO registers */
u16 txmaxp, txcsr, rxmaxp, rxcsr;
u16 rxfifoadd, txfifoadd;
u8 txtype, txinterval, rxtype, rxinterval;
u8 rxfifosz, txfifosz;
u8 txfunaddr, txhubaddr, txhubport;
u8 rxfunaddr, rxhubaddr, rxhubport;
};
struct musb_context_registers {
u8 power;
u8 intrusbe;
u16 frame;
u8 index, testmode;
u8 devctl, busctl, misc;
u32 otg_interfsel;
struct musb_csr_regs index_regs[MUSB_C_NUM_EPS];
};
/*
* struct musb - Driver instance data.
*/
struct musb {
/* device lock */
spinlock_t lock;
spinlock_t list_lock; /* resume work list lock */
struct musb_io io;
const struct musb_platform_ops *ops;
struct musb_context_registers context;
irqreturn_t (*isr)(int, void *);
struct delayed_work irq_work;
struct delayed_work deassert_reset_work;
struct delayed_work finish_resume_work;
struct delayed_work gadget_work;
u16 hwvers;
u16 intrrxe;
u16 intrtxe;
/* this hub status bit is reserved by USB 2.0 and not seen by usbcore */
#define MUSB_PORT_STAT_RESUME (1 << 31)
u32 port1_status;
unsigned long rh_timer;
enum musb_h_ep0_state ep0_stage;
/* bulk traffic normally dedicates endpoint hardware, and each
* direction has its own ring of host side endpoints.
* we try to progress the transfer at the head of each endpoint's
* queue until it completes or NAKs too much; then we try the next
* endpoint.
*/
struct musb_hw_ep *bulk_ep;
struct list_head control; /* of musb_qh */
struct list_head in_bulk; /* of musb_qh */
struct list_head out_bulk; /* of musb_qh */
struct list_head pending_list; /* pending work list */
struct timer_list otg_timer;
struct timer_list dev_timer;
struct notifier_block nb;
struct dma_controller *dma_controller;
struct device *controller;
void __iomem *ctrl_base;
void __iomem *mregs;
#if IS_ENABLED(CONFIG_USB_MUSB_TUSB6010)
dma_addr_t async;
dma_addr_t sync;
void __iomem *sync_va;
u8 tusb_revision;
#endif
/* passed down from chip/board specific irq handlers */
u8 int_usb;
u16 int_rx;
u16 int_tx;
struct usb_phy *xceiv;
struct phy *phy;
enum usb_otg_state otg_state;
int nIrq;
unsigned irq_wake:1;
struct musb_hw_ep endpoints[MUSB_C_NUM_EPS];
#define control_ep endpoints
#define VBUSERR_RETRY_COUNT 3
u16 vbuserr_retry;
u16 epmask;
u8 nr_endpoints;
u8 min_power; /* vbus for periph, in mA/2 */
enum musb_mode port_mode;
bool session;
unsigned long quirk_retries;
bool is_host;
int a_wait_bcon; /* VBUS timeout in msecs */
unsigned long idle_timeout; /* Next timeout in jiffies */
unsigned is_initialized:1;
unsigned is_runtime_suspended:1;
/* active means connected and not suspended */
unsigned is_active:1;
unsigned is_multipoint:1;
unsigned hb_iso_rx:1; /* high bandwidth iso rx? */
unsigned hb_iso_tx:1; /* high bandwidth iso tx? */
unsigned dyn_fifo:1; /* dynamic FIFO supported? */
unsigned bulk_split:1;
#define can_bulk_split(musb, type) \
(((type) == USB_ENDPOINT_XFER_BULK) && (musb)->bulk_split)
unsigned bulk_combine:1;
#define can_bulk_combine(musb, type) \
(((type) == USB_ENDPOINT_XFER_BULK) && (musb)->bulk_combine)
/* is_suspended means USB B_PERIPHERAL suspend */
unsigned is_suspended:1;
/* may_wakeup means remote wakeup is enabled */
unsigned may_wakeup:1;
/* is_self_powered is reported in device status and the
* config descriptor. is_bus_powered means B_PERIPHERAL
* draws some VBUS current; both can be true.
*/
unsigned is_self_powered:1;
unsigned is_bus_powered:1;
unsigned set_address:1;
unsigned test_mode:1;
unsigned softconnect:1;
unsigned flush_irq_work:1;
u8 address;
u8 test_mode_nr;
u16 ackpend; /* ep0 */
enum musb_g_ep0_state ep0_state;
struct usb_gadget g; /* the gadget */
struct usb_gadget_driver *gadget_driver; /* its driver */
struct usb_hcd *hcd; /* the usb hcd */
const struct musb_hdrc_config *config;
int xceiv_old_state;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_root;
#endif
};
/* This must be included after struct musb is defined */
#include "musb_regs.h"
static inline struct musb *gadget_to_musb(struct usb_gadget *g)
{
return container_of(g, struct musb, g);
}
static inline char *musb_ep_xfertype_string(u8 type)
{
char *s;
switch (type) {
case USB_ENDPOINT_XFER_CONTROL:
s = "ctrl";
break;
case USB_ENDPOINT_XFER_ISOC:
s = "iso";
break;
case USB_ENDPOINT_XFER_BULK:
s = "bulk";
break;
case USB_ENDPOINT_XFER_INT:
s = "int";
break;
default:
s = "";
break;
}
return s;
}
static inline int musb_read_fifosize(struct musb *musb,
struct musb_hw_ep *hw_ep, u8 epnum)
{
void __iomem *mbase = musb->mregs;
u8 reg = 0;
/* read from core using indexed model */
reg = musb_readb(mbase, musb->io.ep_offset(epnum, MUSB_FIFOSIZE));
/* 0's returned when no more endpoints */
if (!reg)
return -ENODEV;
musb->nr_endpoints++;
musb->epmask |= (1 << epnum);
hw_ep->max_packet_sz_tx = 1 << (reg & 0x0f);
/* shared TX/RX FIFO? */
if ((reg & 0xf0) == 0xf0) {
hw_ep->max_packet_sz_rx = hw_ep->max_packet_sz_tx;
hw_ep->is_shared_fifo = true;
return 0;
} else {
hw_ep->max_packet_sz_rx = 1 << ((reg & 0xf0) >> 4);
hw_ep->is_shared_fifo = false;
}
return 0;
}
static inline void musb_configure_ep0(struct musb *musb)
{
musb->endpoints[0].max_packet_sz_tx = MUSB_EP0_FIFOSIZE;
musb->endpoints[0].max_packet_sz_rx = MUSB_EP0_FIFOSIZE;
musb->endpoints[0].is_shared_fifo = true;
}
/***************************** Glue it together *****************************/
extern const char musb_driver_name[];
extern void musb_stop(struct musb *musb);
extern void musb_start(struct musb *musb);
extern void musb_write_fifo(struct musb_hw_ep *ep, u16 len, const u8 *src);
extern void musb_read_fifo(struct musb_hw_ep *ep, u16 len, u8 *dst);
extern int musb_set_host(struct musb *musb);
extern int musb_set_peripheral(struct musb *musb);
extern void musb_load_testpacket(struct musb *);
extern irqreturn_t musb_interrupt(struct musb *);
extern void musb_hnp_stop(struct musb *musb);
int musb_queue_resume_work(struct musb *musb,
int (*callback)(struct musb *musb, void *data),
void *data);
static inline void musb_platform_set_vbus(struct musb *musb, int is_on)
{
if (musb->ops->set_vbus)
musb->ops->set_vbus(musb, is_on);
}
static inline void musb_platform_enable(struct musb *musb)
{
if (musb->ops->enable)
musb->ops->enable(musb);
}
static inline void musb_platform_disable(struct musb *musb)
{
if (musb->ops->disable)
musb->ops->disable(musb);
}
static inline int musb_platform_set_mode(struct musb *musb, u8 mode)
{
if (!musb->ops->set_mode)
return 0;
return musb->ops->set_mode(musb, mode);
}
static inline void musb_platform_try_idle(struct musb *musb,
unsigned long timeout)
{
if (musb->ops->try_idle)
musb->ops->try_idle(musb, timeout);
}
static inline int musb_platform_recover(struct musb *musb)
{
if (!musb->ops->recover)
return 0;
return musb->ops->recover(musb);
}
static inline int musb_platform_get_vbus_status(struct musb *musb)
{
if (!musb->ops->vbus_status)
return -EINVAL;
return musb->ops->vbus_status(musb);
}
static inline int musb_platform_init(struct musb *musb)
{
if (!musb->ops->init)
return -EINVAL;
return musb->ops->init(musb);
}
static inline int musb_platform_exit(struct musb *musb)
{
if (!musb->ops->exit)
return -EINVAL;
return musb->ops->exit(musb);
}
static inline void musb_platform_pre_root_reset_end(struct musb *musb)
{
if (musb->ops->pre_root_reset_end)
musb->ops->pre_root_reset_end(musb);
}
static inline void musb_platform_post_root_reset_end(struct musb *musb)
{
if (musb->ops->post_root_reset_end)
musb->ops->post_root_reset_end(musb);
}
static inline void musb_platform_clear_ep_rxintr(struct musb *musb, int epnum)
{
if (musb->ops->clear_ep_rxintr)
musb->ops->clear_ep_rxintr(musb, epnum);
}
static inline void musb_set_state(struct musb *musb,
enum usb_otg_state otg_state)
{
if (musb->xceiv)
musb->xceiv->otg->state = otg_state;
else
musb->otg_state = otg_state;
}
static inline enum usb_otg_state musb_get_state(struct musb *musb)
{
if (musb->xceiv)
return musb->xceiv->otg->state;
return musb->otg_state;
}
static inline const char *musb_otg_state_string(struct musb *musb)
{
return usb_otg_state_string(musb_get_state(musb));
}
/*
* gets the "dr_mode" property from DT and converts it into musb_mode
* if the property is not found or not recognized returns MUSB_OTG
*/
extern enum musb_mode musb_get_mode(struct device *dev);
#endif /* __MUSB_CORE_H__ */
+811
View File
@@ -0,0 +1,811 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/sizes.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include "cppi_dma.h"
#include "musb_core.h"
#include "musb_trace.h"
#define RNDIS_REG(x) (0x80 + ((x - 1) * 4))
#define EP_MODE_AUTOREQ_NONE 0
#define EP_MODE_AUTOREQ_ALL_NEOP 1
#define EP_MODE_AUTOREQ_ALWAYS 3
#define EP_MODE_DMA_TRANSPARENT 0
#define EP_MODE_DMA_RNDIS 1
#define EP_MODE_DMA_GEN_RNDIS 3
#define USB_CTRL_TX_MODE 0x70
#define USB_CTRL_RX_MODE 0x74
#define USB_CTRL_AUTOREQ 0xd0
#define USB_TDOWN 0xd8
#define MUSB_DMA_NUM_CHANNELS 15
#define DA8XX_USB_MODE 0x10
#define DA8XX_USB_AUTOREQ 0x14
#define DA8XX_USB_TEARDOWN 0x1c
#define DA8XX_DMA_NUM_CHANNELS 4
struct cppi41_dma_controller {
struct dma_controller controller;
struct cppi41_dma_channel *rx_channel;
struct cppi41_dma_channel *tx_channel;
struct hrtimer early_tx;
struct list_head early_tx_list;
u32 rx_mode;
u32 tx_mode;
u32 auto_req;
u32 tdown_reg;
u32 autoreq_reg;
void (*set_dma_mode)(struct cppi41_dma_channel *cppi41_channel,
unsigned int mode);
u8 num_channels;
};
static void save_rx_toggle(struct cppi41_dma_channel *cppi41_channel)
{
u16 csr;
u8 toggle;
if (cppi41_channel->is_tx)
return;
if (!is_host_active(cppi41_channel->controller->controller.musb))
return;
csr = musb_readw(cppi41_channel->hw_ep->regs, MUSB_RXCSR);
toggle = csr & MUSB_RXCSR_H_DATATOGGLE ? 1 : 0;
cppi41_channel->usb_toggle = toggle;
}
static void update_rx_toggle(struct cppi41_dma_channel *cppi41_channel)
{
struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep;
struct musb *musb = hw_ep->musb;
u16 csr;
u8 toggle;
if (cppi41_channel->is_tx)
return;
if (!is_host_active(musb))
return;
musb_ep_select(musb->mregs, hw_ep->epnum);
csr = musb_readw(hw_ep->regs, MUSB_RXCSR);
toggle = csr & MUSB_RXCSR_H_DATATOGGLE ? 1 : 0;
/*
* AM335x Advisory 1.0.13: Due to internal synchronisation error the
* data toggle may reset from DATA1 to DATA0 during receiving data from
* more than one endpoint.
*/
if (!toggle && toggle == cppi41_channel->usb_toggle) {
csr |= MUSB_RXCSR_H_DATATOGGLE | MUSB_RXCSR_H_WR_DATATOGGLE;
musb_writew(cppi41_channel->hw_ep->regs, MUSB_RXCSR, csr);
musb_dbg(musb, "Restoring DATA1 toggle.");
}
cppi41_channel->usb_toggle = toggle;
}
static bool musb_is_tx_fifo_empty(struct musb_hw_ep *hw_ep)
{
u8 epnum = hw_ep->epnum;
struct musb *musb = hw_ep->musb;
void __iomem *epio = musb->endpoints[epnum].regs;
u16 csr;
musb_ep_select(musb->mregs, hw_ep->epnum);
csr = musb_readw(epio, MUSB_TXCSR);
if (csr & MUSB_TXCSR_TXPKTRDY)
return false;
return true;
}
static void cppi41_dma_callback(void *private_data,
const struct dmaengine_result *result);
static void cppi41_trans_done(struct cppi41_dma_channel *cppi41_channel)
{
struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep;
struct musb *musb = hw_ep->musb;
void __iomem *epio = hw_ep->regs;
u16 csr;
if (!cppi41_channel->prog_len ||
(cppi41_channel->channel.status == MUSB_DMA_STATUS_FREE)) {
/* done, complete */
cppi41_channel->channel.actual_len =
cppi41_channel->transferred;
cppi41_channel->channel.status = MUSB_DMA_STATUS_FREE;
cppi41_channel->channel.rx_packet_done = true;
/*
* transmit ZLP using PIO mode for transfers which size is
* multiple of EP packet size.
*/
if (cppi41_channel->tx_zlp && (cppi41_channel->transferred %
cppi41_channel->packet_sz) == 0) {
musb_ep_select(musb->mregs, hw_ep->epnum);
csr = MUSB_TXCSR_MODE | MUSB_TXCSR_TXPKTRDY;
musb_writew(epio, MUSB_TXCSR, csr);
}
trace_musb_cppi41_done(cppi41_channel);
musb_dma_completion(musb, hw_ep->epnum, cppi41_channel->is_tx);
} else {
/* next iteration, reload */
struct dma_chan *dc = cppi41_channel->dc;
struct dma_async_tx_descriptor *dma_desc;
enum dma_transfer_direction direction;
u32 remain_bytes;
cppi41_channel->buf_addr += cppi41_channel->packet_sz;
remain_bytes = cppi41_channel->total_len;
remain_bytes -= cppi41_channel->transferred;
remain_bytes = min(remain_bytes, cppi41_channel->packet_sz);
cppi41_channel->prog_len = remain_bytes;
direction = cppi41_channel->is_tx ? DMA_MEM_TO_DEV
: DMA_DEV_TO_MEM;
dma_desc = dmaengine_prep_slave_single(dc,
cppi41_channel->buf_addr,
remain_bytes,
direction,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (WARN_ON(!dma_desc))
return;
dma_desc->callback_result = cppi41_dma_callback;
dma_desc->callback_param = &cppi41_channel->channel;
cppi41_channel->cookie = dma_desc->tx_submit(dma_desc);
trace_musb_cppi41_cont(cppi41_channel);
dma_async_issue_pending(dc);
if (!cppi41_channel->is_tx) {
musb_ep_select(musb->mregs, hw_ep->epnum);
csr = musb_readw(epio, MUSB_RXCSR);
csr |= MUSB_RXCSR_H_REQPKT;
musb_writew(epio, MUSB_RXCSR, csr);
}
}
}
static enum hrtimer_restart cppi41_recheck_tx_req(struct hrtimer *timer)
{
struct cppi41_dma_controller *controller;
struct cppi41_dma_channel *cppi41_channel, *n;
struct musb *musb;
unsigned long flags;
enum hrtimer_restart ret = HRTIMER_NORESTART;
controller = container_of(timer, struct cppi41_dma_controller,
early_tx);
musb = controller->controller.musb;
spin_lock_irqsave(&musb->lock, flags);
list_for_each_entry_safe(cppi41_channel, n, &controller->early_tx_list,
tx_check) {
bool empty;
struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep;
empty = musb_is_tx_fifo_empty(hw_ep);
if (empty) {
list_del_init(&cppi41_channel->tx_check);
cppi41_trans_done(cppi41_channel);
}
}
if (!list_empty(&controller->early_tx_list) &&
!hrtimer_is_queued(&controller->early_tx)) {
ret = HRTIMER_RESTART;
hrtimer_forward_now(&controller->early_tx, 20 * NSEC_PER_USEC);
}
spin_unlock_irqrestore(&musb->lock, flags);
return ret;
}
static void cppi41_dma_callback(void *private_data,
const struct dmaengine_result *result)
{
struct dma_channel *channel = private_data;
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep;
struct cppi41_dma_controller *controller;
struct musb *musb = hw_ep->musb;
unsigned long flags;
struct dma_tx_state txstate;
u32 transferred;
int is_hs = 0;
bool empty;
controller = cppi41_channel->controller;
if (controller->controller.dma_callback)
controller->controller.dma_callback(&controller->controller);
if (result->result == DMA_TRANS_ABORTED)
return;
spin_lock_irqsave(&musb->lock, flags);
dmaengine_tx_status(cppi41_channel->dc, cppi41_channel->cookie,
&txstate);
transferred = cppi41_channel->prog_len - txstate.residue;
cppi41_channel->transferred += transferred;
trace_musb_cppi41_gb(cppi41_channel);
update_rx_toggle(cppi41_channel);
if (cppi41_channel->transferred == cppi41_channel->total_len ||
transferred < cppi41_channel->packet_sz)
cppi41_channel->prog_len = 0;
if (cppi41_channel->is_tx) {
u8 type;
if (is_host_active(musb))
type = hw_ep->out_qh->type;
else
type = hw_ep->ep_in.type;
if (type == USB_ENDPOINT_XFER_ISOC)
/*
* Don't use the early-TX-interrupt workaround below
* for Isoch transfter. Since Isoch are periodic
* transfer, by the time the next transfer is
* scheduled, the current one should be done already.
*
* This avoids audio playback underrun issue.
*/
empty = true;
else
empty = musb_is_tx_fifo_empty(hw_ep);
}
if (!cppi41_channel->is_tx || empty) {
cppi41_trans_done(cppi41_channel);
goto out;
}
/*
* On AM335x it has been observed that the TX interrupt fires
* too early that means the TXFIFO is not yet empty but the DMA
* engine says that it is done with the transfer. We don't
* receive a FIFO empty interrupt so the only thing we can do is
* to poll for the bit. On HS it usually takes 2us, on FS around
* 110us - 150us depending on the transfer size.
* We spin on HS (no longer than 25us and setup a timer on
* FS to check for the bit and complete the transfer.
*/
if (is_host_active(musb)) {
if (musb->port1_status & USB_PORT_STAT_HIGH_SPEED)
is_hs = 1;
} else {
if (musb->g.speed == USB_SPEED_HIGH)
is_hs = 1;
}
if (is_hs) {
unsigned wait = 25;
do {
empty = musb_is_tx_fifo_empty(hw_ep);
if (empty) {
cppi41_trans_done(cppi41_channel);
goto out;
}
wait--;
if (!wait)
break;
cpu_relax();
} while (1);
}
list_add_tail(&cppi41_channel->tx_check,
&controller->early_tx_list);
if (!hrtimer_is_queued(&controller->early_tx)) {
unsigned long usecs = cppi41_channel->total_len / 10;
hrtimer_start_range_ns(&controller->early_tx,
usecs * NSEC_PER_USEC,
20 * NSEC_PER_USEC,
HRTIMER_MODE_REL);
}
out:
spin_unlock_irqrestore(&musb->lock, flags);
}
static u32 update_ep_mode(unsigned ep, unsigned mode, u32 old)
{
unsigned shift;
shift = (ep - 1) * 2;
old &= ~(3 << shift);
old |= mode << shift;
return old;
}
static void cppi41_set_dma_mode(struct cppi41_dma_channel *cppi41_channel,
unsigned mode)
{
struct cppi41_dma_controller *controller = cppi41_channel->controller;
struct musb *musb = controller->controller.musb;
u32 port;
u32 new_mode;
u32 old_mode;
if (cppi41_channel->is_tx)
old_mode = controller->tx_mode;
else
old_mode = controller->rx_mode;
port = cppi41_channel->port_num;
new_mode = update_ep_mode(port, mode, old_mode);
if (new_mode == old_mode)
return;
if (cppi41_channel->is_tx) {
controller->tx_mode = new_mode;
musb_writel(musb->ctrl_base, USB_CTRL_TX_MODE, new_mode);
} else {
controller->rx_mode = new_mode;
musb_writel(musb->ctrl_base, USB_CTRL_RX_MODE, new_mode);
}
}
static void da8xx_set_dma_mode(struct cppi41_dma_channel *cppi41_channel,
unsigned int mode)
{
struct cppi41_dma_controller *controller = cppi41_channel->controller;
struct musb *musb = controller->controller.musb;
unsigned int shift;
u32 port;
u32 new_mode;
u32 old_mode;
old_mode = controller->tx_mode;
port = cppi41_channel->port_num;
shift = (port - 1) * 4;
if (!cppi41_channel->is_tx)
shift += 16;
new_mode = old_mode & ~(3 << shift);
new_mode |= mode << shift;
if (new_mode == old_mode)
return;
controller->tx_mode = new_mode;
musb_writel(musb->ctrl_base, DA8XX_USB_MODE, new_mode);
}
static void cppi41_set_autoreq_mode(struct cppi41_dma_channel *cppi41_channel,
unsigned mode)
{
struct cppi41_dma_controller *controller = cppi41_channel->controller;
u32 port;
u32 new_mode;
u32 old_mode;
old_mode = controller->auto_req;
port = cppi41_channel->port_num;
new_mode = update_ep_mode(port, mode, old_mode);
if (new_mode == old_mode)
return;
controller->auto_req = new_mode;
musb_writel(controller->controller.musb->ctrl_base,
controller->autoreq_reg, new_mode);
}
static bool cppi41_configure_channel(struct dma_channel *channel,
u16 packet_sz, u8 mode,
dma_addr_t dma_addr, u32 len)
{
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
struct cppi41_dma_controller *controller = cppi41_channel->controller;
struct dma_chan *dc = cppi41_channel->dc;
struct dma_async_tx_descriptor *dma_desc;
enum dma_transfer_direction direction;
struct musb *musb = cppi41_channel->controller->controller.musb;
unsigned use_gen_rndis = 0;
cppi41_channel->buf_addr = dma_addr;
cppi41_channel->total_len = len;
cppi41_channel->transferred = 0;
cppi41_channel->packet_sz = packet_sz;
cppi41_channel->tx_zlp = (cppi41_channel->is_tx && mode) ? 1 : 0;
/*
* Due to AM335x' Advisory 1.0.13 we are not allowed to transfer more
* than max packet size at a time.
*/
if (cppi41_channel->is_tx)
use_gen_rndis = 1;
if (use_gen_rndis) {
/* RNDIS mode */
if (len > packet_sz) {
musb_writel(musb->ctrl_base,
RNDIS_REG(cppi41_channel->port_num), len);
/* gen rndis */
controller->set_dma_mode(cppi41_channel,
EP_MODE_DMA_GEN_RNDIS);
/* auto req */
cppi41_set_autoreq_mode(cppi41_channel,
EP_MODE_AUTOREQ_ALL_NEOP);
} else {
musb_writel(musb->ctrl_base,
RNDIS_REG(cppi41_channel->port_num), 0);
controller->set_dma_mode(cppi41_channel,
EP_MODE_DMA_TRANSPARENT);
cppi41_set_autoreq_mode(cppi41_channel,
EP_MODE_AUTOREQ_NONE);
}
} else {
/* fallback mode */
controller->set_dma_mode(cppi41_channel,
EP_MODE_DMA_TRANSPARENT);
cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREQ_NONE);
len = min_t(u32, packet_sz, len);
}
cppi41_channel->prog_len = len;
direction = cppi41_channel->is_tx ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
dma_desc = dmaengine_prep_slave_single(dc, dma_addr, len, direction,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!dma_desc)
return false;
dma_desc->callback_result = cppi41_dma_callback;
dma_desc->callback_param = channel;
cppi41_channel->cookie = dma_desc->tx_submit(dma_desc);
cppi41_channel->channel.rx_packet_done = false;
trace_musb_cppi41_config(cppi41_channel);
save_rx_toggle(cppi41_channel);
dma_async_issue_pending(dc);
return true;
}
static struct dma_channel *cppi41_dma_channel_allocate(struct dma_controller *c,
struct musb_hw_ep *hw_ep, u8 is_tx)
{
struct cppi41_dma_controller *controller = container_of(c,
struct cppi41_dma_controller, controller);
struct cppi41_dma_channel *cppi41_channel = NULL;
u8 ch_num = hw_ep->epnum - 1;
if (ch_num >= controller->num_channels)
return NULL;
if (is_tx)
cppi41_channel = &controller->tx_channel[ch_num];
else
cppi41_channel = &controller->rx_channel[ch_num];
if (!cppi41_channel->dc)
return NULL;
if (cppi41_channel->is_allocated)
return NULL;
cppi41_channel->hw_ep = hw_ep;
cppi41_channel->is_allocated = 1;
trace_musb_cppi41_alloc(cppi41_channel);
return &cppi41_channel->channel;
}
static void cppi41_dma_channel_release(struct dma_channel *channel)
{
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
trace_musb_cppi41_free(cppi41_channel);
if (cppi41_channel->is_allocated) {
cppi41_channel->is_allocated = 0;
channel->status = MUSB_DMA_STATUS_FREE;
channel->actual_len = 0;
}
}
static int cppi41_dma_channel_program(struct dma_channel *channel,
u16 packet_sz, u8 mode,
dma_addr_t dma_addr, u32 len)
{
int ret;
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
int hb_mult = 0;
BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN ||
channel->status == MUSB_DMA_STATUS_BUSY);
if (is_host_active(cppi41_channel->controller->controller.musb)) {
if (cppi41_channel->is_tx)
hb_mult = cppi41_channel->hw_ep->out_qh->hb_mult;
else
hb_mult = cppi41_channel->hw_ep->in_qh->hb_mult;
}
channel->status = MUSB_DMA_STATUS_BUSY;
channel->actual_len = 0;
if (hb_mult)
packet_sz = hb_mult * (packet_sz & 0x7FF);
ret = cppi41_configure_channel(channel, packet_sz, mode, dma_addr, len);
if (!ret)
channel->status = MUSB_DMA_STATUS_FREE;
return ret;
}
static int cppi41_is_compatible(struct dma_channel *channel, u16 maxpacket,
void *buf, u32 length)
{
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
struct cppi41_dma_controller *controller = cppi41_channel->controller;
struct musb *musb = controller->controller.musb;
if (is_host_active(musb)) {
WARN_ON(1);
return 1;
}
if (cppi41_channel->hw_ep->ep_in.type != USB_ENDPOINT_XFER_BULK)
return 0;
if (cppi41_channel->is_tx)
return 1;
/* AM335x Advisory 1.0.13. No workaround for device RX mode */
return 0;
}
static int cppi41_dma_channel_abort(struct dma_channel *channel)
{
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
struct cppi41_dma_controller *controller = cppi41_channel->controller;
struct musb *musb = controller->controller.musb;
void __iomem *epio = cppi41_channel->hw_ep->regs;
int tdbit;
int ret;
unsigned is_tx;
u16 csr;
is_tx = cppi41_channel->is_tx;
trace_musb_cppi41_abort(cppi41_channel);
if (cppi41_channel->channel.status == MUSB_DMA_STATUS_FREE)
return 0;
list_del_init(&cppi41_channel->tx_check);
if (is_tx) {
csr = musb_readw(epio, MUSB_TXCSR);
csr &= ~MUSB_TXCSR_DMAENAB;
musb_writew(epio, MUSB_TXCSR, csr);
} else {
cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREQ_NONE);
/* delay to drain to cppi dma pipeline for isoch */
udelay(250);
csr = musb_readw(epio, MUSB_RXCSR);
csr &= ~(MUSB_RXCSR_H_REQPKT | MUSB_RXCSR_DMAENAB);
musb_writew(epio, MUSB_RXCSR, csr);
/* wait to drain cppi dma pipe line */
udelay(50);
csr = musb_readw(epio, MUSB_RXCSR);
if (csr & MUSB_RXCSR_RXPKTRDY) {
csr |= MUSB_RXCSR_FLUSHFIFO;
musb_writew(epio, MUSB_RXCSR, csr);
musb_writew(epio, MUSB_RXCSR, csr);
}
}
/* DA8xx Advisory 2.3.27: wait 250 ms before to start the teardown */
if (musb->ops->quirks & MUSB_DA8XX)
mdelay(250);
tdbit = 1 << cppi41_channel->port_num;
if (is_tx)
tdbit <<= 16;
do {
if (is_tx)
musb_writel(musb->ctrl_base, controller->tdown_reg,
tdbit);
ret = dmaengine_terminate_all(cppi41_channel->dc);
} while (ret == -EAGAIN);
if (is_tx) {
musb_writel(musb->ctrl_base, controller->tdown_reg, tdbit);
csr = musb_readw(epio, MUSB_TXCSR);
if (csr & MUSB_TXCSR_TXPKTRDY) {
csr |= MUSB_TXCSR_FLUSHFIFO;
musb_writew(epio, MUSB_TXCSR, csr);
}
}
cppi41_channel->channel.status = MUSB_DMA_STATUS_FREE;
return 0;
}
static void cppi41_release_all_dma_chans(struct cppi41_dma_controller *ctrl)
{
struct dma_chan *dc;
int i;
for (i = 0; i < ctrl->num_channels; i++) {
dc = ctrl->tx_channel[i].dc;
if (dc)
dma_release_channel(dc);
dc = ctrl->rx_channel[i].dc;
if (dc)
dma_release_channel(dc);
}
}
static void cppi41_dma_controller_stop(struct cppi41_dma_controller *controller)
{
cppi41_release_all_dma_chans(controller);
}
static int cppi41_dma_controller_start(struct cppi41_dma_controller *controller)
{
struct musb *musb = controller->controller.musb;
struct device *dev = musb->controller;
struct device_node *np = dev->parent->of_node;
struct cppi41_dma_channel *cppi41_channel;
int count;
int i;
int ret;
count = of_property_count_strings(np, "dma-names");
if (count < 0)
return count;
for (i = 0; i < count; i++) {
struct dma_chan *dc;
struct dma_channel *musb_dma;
const char *str;
unsigned is_tx;
unsigned int port;
ret = of_property_read_string_index(np, "dma-names", i, &str);
if (ret)
goto err;
if (strstarts(str, "tx"))
is_tx = 1;
else if (strstarts(str, "rx"))
is_tx = 0;
else {
dev_err(dev, "Wrong dmatype %s\n", str);
goto err;
}
ret = kstrtouint(str + 2, 0, &port);
if (ret)
goto err;
ret = -EINVAL;
if (port > controller->num_channels || !port)
goto err;
if (is_tx)
cppi41_channel = &controller->tx_channel[port - 1];
else
cppi41_channel = &controller->rx_channel[port - 1];
cppi41_channel->controller = controller;
cppi41_channel->port_num = port;
cppi41_channel->is_tx = is_tx;
INIT_LIST_HEAD(&cppi41_channel->tx_check);
musb_dma = &cppi41_channel->channel;
musb_dma->private_data = cppi41_channel;
musb_dma->status = MUSB_DMA_STATUS_FREE;
musb_dma->max_len = SZ_4M;
dc = dma_request_chan(dev->parent, str);
if (IS_ERR(dc)) {
ret = dev_err_probe(dev, PTR_ERR(dc),
"Failed to request %s.\n", str);
goto err;
}
cppi41_channel->dc = dc;
}
return 0;
err:
cppi41_release_all_dma_chans(controller);
return ret;
}
void cppi41_dma_controller_destroy(struct dma_controller *c)
{
struct cppi41_dma_controller *controller = container_of(c,
struct cppi41_dma_controller, controller);
hrtimer_cancel(&controller->early_tx);
cppi41_dma_controller_stop(controller);
kfree(controller->rx_channel);
kfree(controller->tx_channel);
kfree(controller);
}
EXPORT_SYMBOL_GPL(cppi41_dma_controller_destroy);
struct dma_controller *
cppi41_dma_controller_create(struct musb *musb, void __iomem *base)
{
struct cppi41_dma_controller *controller;
int channel_size;
int ret = 0;
if (!musb->controller->parent->of_node) {
dev_err(musb->controller, "Need DT for the DMA engine.\n");
return NULL;
}
controller = kzalloc_obj(*controller);
if (!controller)
goto kzalloc_fail;
hrtimer_setup(&controller->early_tx, cppi41_recheck_tx_req, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
INIT_LIST_HEAD(&controller->early_tx_list);
controller->controller.channel_alloc = cppi41_dma_channel_allocate;
controller->controller.channel_release = cppi41_dma_channel_release;
controller->controller.channel_program = cppi41_dma_channel_program;
controller->controller.channel_abort = cppi41_dma_channel_abort;
controller->controller.is_compatible = cppi41_is_compatible;
controller->controller.musb = musb;
if (musb->ops->quirks & MUSB_DA8XX) {
controller->tdown_reg = DA8XX_USB_TEARDOWN;
controller->autoreq_reg = DA8XX_USB_AUTOREQ;
controller->set_dma_mode = da8xx_set_dma_mode;
controller->num_channels = DA8XX_DMA_NUM_CHANNELS;
} else {
controller->tdown_reg = USB_TDOWN;
controller->autoreq_reg = USB_CTRL_AUTOREQ;
controller->set_dma_mode = cppi41_set_dma_mode;
controller->num_channels = MUSB_DMA_NUM_CHANNELS;
}
channel_size = controller->num_channels *
sizeof(struct cppi41_dma_channel);
controller->rx_channel = kzalloc(channel_size, GFP_KERNEL);
if (!controller->rx_channel)
goto rx_channel_alloc_fail;
controller->tx_channel = kzalloc(channel_size, GFP_KERNEL);
if (!controller->tx_channel)
goto tx_channel_alloc_fail;
ret = cppi41_dma_controller_start(controller);
if (ret)
goto plat_get_fail;
return &controller->controller;
plat_get_fail:
kfree(controller->tx_channel);
tx_channel_alloc_fail:
kfree(controller->rx_channel);
rx_channel_alloc_fail:
kfree(controller);
kzalloc_fail:
if (ret == -EPROBE_DEFER)
return ERR_PTR(ret);
return NULL;
}
EXPORT_SYMBOL_GPL(cppi41_dma_controller_create);
+34
View File
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* MUSB OTG driver debug defines
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
*/
#ifndef __MUSB_LINUX_DEBUG_H__
#define __MUSB_LINUX_DEBUG_H__
#define yprintk(facility, format, args...) \
do { printk(facility "%s %d: " format , \
__func__, __LINE__ , ## args); } while (0)
#define WARNING(fmt, args...) yprintk(KERN_WARNING, fmt, ## args)
#define INFO(fmt, args...) yprintk(KERN_INFO, fmt, ## args)
#define ERR(fmt, args...) yprintk(KERN_ERR, fmt, ## args)
void musb_dbg(struct musb *musb, const char *fmt, ...);
#ifdef CONFIG_DEBUG_FS
void musb_init_debugfs(struct musb *musb);
void musb_exit_debugfs(struct musb *musb);
#else
static inline void musb_init_debugfs(struct musb *musb)
{
}
static inline void musb_exit_debugfs(struct musb *musb)
{
}
#endif
#endif /* __MUSB_LINUX_DEBUG_H__ */
+336
View File
@@ -0,0 +1,336 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MUSB OTG driver debugfs support
*
* Copyright 2010 Nokia Corporation
* Contact: Felipe Balbi <felipe.balbi@nokia.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include "musb_core.h"
#include "musb_debug.h"
struct musb_register_map {
char *name;
unsigned offset;
unsigned size;
};
static const struct musb_register_map musb_regmap[] = {
{ "FAddr", MUSB_FADDR, 8 },
{ "Power", MUSB_POWER, 8 },
{ "Frame", MUSB_FRAME, 16 },
{ "Index", MUSB_INDEX, 8 },
{ "Testmode", MUSB_TESTMODE, 8 },
{ "TxMaxPp", MUSB_TXMAXP, 16 },
{ "TxCSRp", MUSB_TXCSR, 16 },
{ "RxMaxPp", MUSB_RXMAXP, 16 },
{ "RxCSR", MUSB_RXCSR, 16 },
{ "RxCount", MUSB_RXCOUNT, 16 },
{ "IntrRxE", MUSB_INTRRXE, 16 },
{ "IntrTxE", MUSB_INTRTXE, 16 },
{ "IntrUsbE", MUSB_INTRUSBE, 8 },
{ "DevCtl", MUSB_DEVCTL, 8 },
{ "VControl", 0x68, 32 },
{ "HWVers", MUSB_HWVERS, 16 },
{ "LinkInfo", MUSB_LINKINFO, 8 },
{ "VPLen", MUSB_VPLEN, 8 },
{ "HS_EOF1", MUSB_HS_EOF1, 8 },
{ "FS_EOF1", MUSB_FS_EOF1, 8 },
{ "LS_EOF1", MUSB_LS_EOF1, 8 },
{ "SOFT_RST", 0x7F, 8 },
{ "DMA_CNTLch0", 0x204, 16 },
{ "DMA_ADDRch0", 0x208, 32 },
{ "DMA_COUNTch0", 0x20C, 32 },
{ "DMA_CNTLch1", 0x214, 16 },
{ "DMA_ADDRch1", 0x218, 32 },
{ "DMA_COUNTch1", 0x21C, 32 },
{ "DMA_CNTLch2", 0x224, 16 },
{ "DMA_ADDRch2", 0x228, 32 },
{ "DMA_COUNTch2", 0x22C, 32 },
{ "DMA_CNTLch3", 0x234, 16 },
{ "DMA_ADDRch3", 0x238, 32 },
{ "DMA_COUNTch3", 0x23C, 32 },
{ "DMA_CNTLch4", 0x244, 16 },
{ "DMA_ADDRch4", 0x248, 32 },
{ "DMA_COUNTch4", 0x24C, 32 },
{ "DMA_CNTLch5", 0x254, 16 },
{ "DMA_ADDRch5", 0x258, 32 },
{ "DMA_COUNTch5", 0x25C, 32 },
{ "DMA_CNTLch6", 0x264, 16 },
{ "DMA_ADDRch6", 0x268, 32 },
{ "DMA_COUNTch6", 0x26C, 32 },
{ "DMA_CNTLch7", 0x274, 16 },
{ "DMA_ADDRch7", 0x278, 32 },
{ "DMA_COUNTch7", 0x27C, 32 },
{ "ConfigData", MUSB_CONFIGDATA,8 },
{ "BabbleCtl", MUSB_BABBLE_CTL,8 },
{ "TxFIFOsz", MUSB_TXFIFOSZ, 8 },
{ "RxFIFOsz", MUSB_RXFIFOSZ, 8 },
{ "TxFIFOadd", MUSB_TXFIFOADD, 16 },
{ "RxFIFOadd", MUSB_RXFIFOADD, 16 },
{ "EPInfo", MUSB_EPINFO, 8 },
{ "RAMInfo", MUSB_RAMINFO, 8 },
{ } /* Terminating Entry */
};
static int musb_regdump_show(struct seq_file *s, void *unused)
{
struct musb *musb = s->private;
unsigned i;
seq_printf(s, "MUSB (M)HDRC Register Dump\n");
pm_runtime_get_sync(musb->controller);
for (i = 0; i < ARRAY_SIZE(musb_regmap); i++) {
switch (musb_regmap[i].size) {
case 8:
seq_printf(s, "%-12s: %02x\n", musb_regmap[i].name,
musb_readb(musb->mregs, musb_regmap[i].offset));
break;
case 16:
seq_printf(s, "%-12s: %04x\n", musb_regmap[i].name,
musb_readw(musb->mregs, musb_regmap[i].offset));
break;
case 32:
seq_printf(s, "%-12s: %08x\n", musb_regmap[i].name,
musb_readl(musb->mregs, musb_regmap[i].offset));
break;
}
}
pm_runtime_put_autosuspend(musb->controller);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(musb_regdump);
static int musb_test_mode_show(struct seq_file *s, void *unused)
{
struct musb *musb = s->private;
unsigned test;
pm_runtime_get_sync(musb->controller);
test = musb_readb(musb->mregs, MUSB_TESTMODE);
pm_runtime_put_autosuspend(musb->controller);
if (test == (MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_FS))
seq_printf(s, "force host full-speed\n");
else if (test == (MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_HS))
seq_printf(s, "force host high-speed\n");
else if (test == MUSB_TEST_FORCE_HOST)
seq_printf(s, "force host\n");
else if (test == MUSB_TEST_FIFO_ACCESS)
seq_printf(s, "fifo access\n");
else if (test == MUSB_TEST_FORCE_FS)
seq_printf(s, "force full-speed\n");
else if (test == MUSB_TEST_FORCE_HS)
seq_printf(s, "force high-speed\n");
else if (test == MUSB_TEST_PACKET)
seq_printf(s, "test packet\n");
else if (test == MUSB_TEST_K)
seq_printf(s, "test K\n");
else if (test == MUSB_TEST_J)
seq_printf(s, "test J\n");
else if (test == MUSB_TEST_SE0_NAK)
seq_printf(s, "test SE0 NAK\n");
return 0;
}
static int musb_test_mode_open(struct inode *inode, struct file *file)
{
return single_open(file, musb_test_mode_show, inode->i_private);
}
static ssize_t musb_test_mode_write(struct file *file,
const char __user *ubuf, size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct musb *musb = s->private;
u8 test;
char buf[24];
memset(buf, 0x00, sizeof(buf));
if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
pm_runtime_get_sync(musb->controller);
test = musb_readb(musb->mregs, MUSB_TESTMODE);
if (test) {
dev_err(musb->controller, "Error: test mode is already set. "
"Please do USB Bus Reset to start a new test.\n");
goto ret;
}
if (strstarts(buf, "force host full-speed"))
test = MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_FS;
else if (strstarts(buf, "force host high-speed"))
test = MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_HS;
else if (strstarts(buf, "force host"))
test = MUSB_TEST_FORCE_HOST;
else if (strstarts(buf, "fifo access"))
test = MUSB_TEST_FIFO_ACCESS;
else if (strstarts(buf, "force full-speed"))
test = MUSB_TEST_FORCE_FS;
else if (strstarts(buf, "force high-speed"))
test = MUSB_TEST_FORCE_HS;
else if (strstarts(buf, "test packet")) {
test = MUSB_TEST_PACKET;
musb_load_testpacket(musb);
}
else if (strstarts(buf, "test K"))
test = MUSB_TEST_K;
else if (strstarts(buf, "test J"))
test = MUSB_TEST_J;
else if (strstarts(buf, "test SE0 NAK"))
test = MUSB_TEST_SE0_NAK;
musb_writeb(musb->mregs, MUSB_TESTMODE, test);
ret:
pm_runtime_put_autosuspend(musb->controller);
return count;
}
static const struct file_operations musb_test_mode_fops = {
.open = musb_test_mode_open,
.write = musb_test_mode_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int musb_softconnect_show(struct seq_file *s, void *unused)
{
struct musb *musb = s->private;
u8 reg;
int connect;
switch (musb_get_state(musb)) {
case OTG_STATE_A_HOST:
case OTG_STATE_A_WAIT_BCON:
pm_runtime_get_sync(musb->controller);
reg = musb_readb(musb->mregs, MUSB_DEVCTL);
connect = reg & MUSB_DEVCTL_SESSION ? 1 : 0;
pm_runtime_put_autosuspend(musb->controller);
break;
default:
connect = -1;
}
seq_printf(s, "%d\n", connect);
return 0;
}
static int musb_softconnect_open(struct inode *inode, struct file *file)
{
return single_open(file, musb_softconnect_show, inode->i_private);
}
static ssize_t musb_softconnect_write(struct file *file,
const char __user *ubuf, size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct musb *musb = s->private;
char buf[2];
u8 reg;
memset(buf, 0x00, sizeof(buf));
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
pm_runtime_get_sync(musb->controller);
if (!strncmp(buf, "0", 1)) {
switch (musb_get_state(musb)) {
case OTG_STATE_A_HOST:
musb_root_disconnect(musb);
reg = musb_readb(musb->mregs, MUSB_DEVCTL);
reg &= ~MUSB_DEVCTL_SESSION;
musb_writeb(musb->mregs, MUSB_DEVCTL, reg);
break;
default:
break;
}
} else if (!strncmp(buf, "1", 1)) {
switch (musb_get_state(musb)) {
case OTG_STATE_A_WAIT_BCON:
/*
* musb_save_context() called in musb_runtime_suspend()
* might cache devctl with SESSION bit cleared during
* soft-disconnect, so specifically set SESSION bit
* here to preserve it for musb_runtime_resume().
*/
musb->context.devctl |= MUSB_DEVCTL_SESSION;
reg = musb_readb(musb->mregs, MUSB_DEVCTL);
reg |= MUSB_DEVCTL_SESSION;
musb_writeb(musb->mregs, MUSB_DEVCTL, reg);
break;
default:
break;
}
}
pm_runtime_put_autosuspend(musb->controller);
return count;
}
/*
* In host mode, connect/disconnect the bus without physically
* remove the devices.
*/
static const struct file_operations musb_softconnect_fops = {
.open = musb_softconnect_open,
.write = musb_softconnect_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void musb_init_debugfs(struct musb *musb)
{
struct dentry *root;
root = debugfs_create_dir(dev_name(musb->controller), usb_debug_root);
musb->debugfs_root = root;
debugfs_create_file("regdump", S_IRUGO, root, musb, &musb_regdump_fops);
debugfs_create_file("testmode", S_IRUGO | S_IWUSR, root, musb,
&musb_test_mode_fops);
debugfs_create_file("softconnect", S_IRUGO | S_IWUSR, root, musb,
&musb_softconnect_fops);
}
void /* __init_or_exit */ musb_exit_debugfs(struct musb *musb)
{
debugfs_remove_recursive(musb->debugfs_root);
}
+209
View File
@@ -0,0 +1,209 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* MUSB OTG driver DMA controller abstraction
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
*/
#ifndef __MUSB_DMA_H__
#define __MUSB_DMA_H__
struct musb_hw_ep;
/*
* DMA Controller Abstraction
*
* DMA Controllers are abstracted to allow use of a variety of different
* implementations of DMA, as allowed by the Inventra USB cores. On the
* host side, usbcore sets up the DMA mappings and flushes caches; on the
* peripheral side, the gadget controller driver does. Responsibilities
* of a DMA controller driver include:
*
* - Handling the details of moving multiple USB packets
* in cooperation with the Inventra USB core, including especially
* the correct RX side treatment of short packets and buffer-full
* states (both of which terminate transfers).
*
* - Knowing the correlation between dma channels and the
* Inventra core's local endpoint resources and data direction.
*
* - Maintaining a list of allocated/available channels.
*
* - Updating channel status on interrupts,
* whether shared with the Inventra core or separate.
*/
#define MUSB_HSDMA_BASE 0x200
#define MUSB_HSDMA_INTR (MUSB_HSDMA_BASE + 0)
#define MUSB_HSDMA_CONTROL 0x4
#define MUSB_HSDMA_ADDRESS 0x8
#define MUSB_HSDMA_COUNT 0xc
#define DMA_ADDR_INVALID (~(dma_addr_t)0)
#ifdef CONFIG_MUSB_PIO_ONLY
#define is_dma_capable() (0)
#else
#define is_dma_capable() (1)
#endif
#ifdef CONFIG_USB_UX500_DMA
#define musb_dma_ux500(musb) (musb->ops->quirks & MUSB_DMA_UX500)
#else
#define musb_dma_ux500(musb) 0
#endif
#ifdef CONFIG_USB_TI_CPPI41_DMA
#define musb_dma_cppi41(musb) (musb->ops->quirks & MUSB_DMA_CPPI41)
#else
#define musb_dma_cppi41(musb) 0
#endif
#ifdef CONFIG_USB_TUSB_OMAP_DMA
#define tusb_dma_omap(musb) (musb->ops->quirks & MUSB_DMA_TUSB_OMAP)
#else
#define tusb_dma_omap(musb) 0
#endif
#ifdef CONFIG_USB_INVENTRA_DMA
#define musb_dma_inventra(musb) (musb->ops->quirks & MUSB_DMA_INVENTRA)
#else
#define musb_dma_inventra(musb) 0
#endif
#if defined(CONFIG_USB_TI_CPPI41_DMA)
#define is_cppi_enabled(musb) musb_dma_cppi41(musb)
#else
#define is_cppi_enabled(musb) 0
#endif
/*
* DMA channel status ... updated by the dma controller driver whenever that
* status changes, and protected by the overall controller spinlock.
*/
enum dma_channel_status {
/* unallocated */
MUSB_DMA_STATUS_UNKNOWN,
/* allocated ... but not busy, no errors */
MUSB_DMA_STATUS_FREE,
/* busy ... transactions are active */
MUSB_DMA_STATUS_BUSY,
/* transaction(s) aborted due to ... dma or memory bus error */
MUSB_DMA_STATUS_BUS_ABORT,
/* transaction(s) aborted due to ... core error or USB fault */
MUSB_DMA_STATUS_CORE_ABORT
};
struct dma_controller;
/**
* struct dma_channel - A DMA channel.
* @private_data: channel-private data
* @max_len: the maximum number of bytes the channel can move in one
* transaction (typically representing many USB maximum-sized packets)
* @actual_len: how many bytes have been transferred
* @status: current channel status (updated e.g. on interrupt)
* @desired_mode: true if mode 1 is desired; false if mode 0 is desired
*
* channels are associated with an endpoint for the duration of at least
* one usb transfer.
*/
struct dma_channel {
void *private_data;
/* FIXME not void* private_data, but a dma_controller * */
size_t max_len;
size_t actual_len;
enum dma_channel_status status;
bool desired_mode;
bool rx_packet_done;
};
/*
* dma_channel_status - return status of dma channel
* @c: the channel
*
* Returns the software's view of the channel status. If that status is BUSY
* then it's possible that the hardware has completed (or aborted) a transfer,
* so the driver needs to update that status.
*/
static inline enum dma_channel_status
dma_channel_status(struct dma_channel *c)
{
return (is_dma_capable() && c) ? c->status : MUSB_DMA_STATUS_UNKNOWN;
}
/**
* struct dma_controller - A DMA Controller.
* @musb: the usb controller
* @start: call this to start a DMA controller;
* return 0 on success, else negative errno
* @stop: call this to stop a DMA controller
* return 0 on success, else negative errno
* @channel_alloc: call this to allocate a DMA channel
* @channel_release: call this to release a DMA channel
* @channel_abort: call this to abort a pending DMA transaction,
* returning it to FREE (but allocated) state
* @dma_callback: invoked on DMA completion, useful to run platform
* code such IRQ acknowledgment.
*
* Controllers manage dma channels.
*/
struct dma_controller {
struct musb *musb;
struct dma_channel *(*channel_alloc)(struct dma_controller *,
struct musb_hw_ep *, u8 is_tx);
void (*channel_release)(struct dma_channel *);
int (*channel_program)(struct dma_channel *channel,
u16 maxpacket, u8 mode,
dma_addr_t dma_addr,
u32 length);
int (*channel_abort)(struct dma_channel *);
int (*is_compatible)(struct dma_channel *channel,
u16 maxpacket,
void *buf, u32 length);
void (*dma_callback)(struct dma_controller *);
};
/* called after channel_program(), may indicate a fault */
extern void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit);
#ifdef CONFIG_MUSB_PIO_ONLY
static inline struct dma_controller *
musb_dma_controller_create(struct musb *m, void __iomem *io)
{
return NULL;
}
static inline void musb_dma_controller_destroy(struct dma_controller *d) { }
#else
extern struct dma_controller *
(*musb_dma_controller_create)(struct musb *, void __iomem *);
extern void (*musb_dma_controller_destroy)(struct dma_controller *);
#endif
/* Platform specific DMA functions */
extern struct dma_controller *
musbhs_dma_controller_create(struct musb *musb, void __iomem *base);
extern void musbhs_dma_controller_destroy(struct dma_controller *c);
extern struct dma_controller *
musbhs_dma_controller_create_noirq(struct musb *musb, void __iomem *base);
extern irqreturn_t dma_controller_irq(int irq, void *private_data);
extern struct dma_controller *
tusb_dma_controller_create(struct musb *musb, void __iomem *base);
extern void tusb_dma_controller_destroy(struct dma_controller *c);
extern struct dma_controller *
cppi41_dma_controller_create(struct musb *musb, void __iomem *base);
extern void cppi41_dma_controller_destroy(struct dma_controller *c);
extern struct dma_controller *
ux500_dma_controller_create(struct musb *musb, void __iomem *base);
extern void ux500_dma_controller_destroy(struct dma_controller *c);
#endif /* __MUSB_DMA_H__ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+116
View File
@@ -0,0 +1,116 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* MUSB OTG driver peripheral defines
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
*/
#ifndef __MUSB_GADGET_H
#define __MUSB_GADGET_H
#include <linux/list.h>
#if IS_ENABLED(CONFIG_USB_MUSB_GADGET) || IS_ENABLED(CONFIG_USB_MUSB_DUAL_ROLE)
extern irqreturn_t musb_g_ep0_irq(struct musb *);
extern void musb_g_tx(struct musb *, u8);
extern void musb_g_rx(struct musb *, u8);
extern void musb_g_reset(struct musb *);
extern void musb_g_suspend(struct musb *);
extern void musb_g_resume(struct musb *);
extern void musb_g_wakeup(struct musb *);
extern void musb_g_disconnect(struct musb *);
extern void musb_gadget_cleanup(struct musb *);
extern int musb_gadget_setup(struct musb *);
#else
static inline irqreturn_t musb_g_ep0_irq(struct musb *musb)
{
return 0;
}
static inline void musb_g_tx(struct musb *musb, u8 epnum) {}
static inline void musb_g_rx(struct musb *musb, u8 epnum) {}
static inline void musb_g_reset(struct musb *musb) {}
static inline void musb_g_suspend(struct musb *musb) {}
static inline void musb_g_resume(struct musb *musb) {}
static inline void musb_g_wakeup(struct musb *musb) {}
static inline void musb_g_disconnect(struct musb *musb) {}
static inline void musb_gadget_cleanup(struct musb *musb) {}
static inline int musb_gadget_setup(struct musb *musb)
{
return 0;
}
#endif
enum buffer_map_state {
UN_MAPPED = 0,
PRE_MAPPED,
MUSB_MAPPED
};
struct musb_request {
struct usb_request request;
struct list_head list;
struct musb_ep *ep;
struct musb *musb;
u8 tx; /* endpoint direction */
u8 epnum;
enum buffer_map_state map_state;
};
#define to_musb_request(r) container_of((r), struct musb_request, request)
extern struct usb_request *
musb_alloc_request(struct usb_ep *ep, gfp_t gfp_flags);
extern void musb_free_request(struct usb_ep *ep, struct usb_request *req);
/*
* struct musb_ep - peripheral side view of endpoint rx or tx side
*/
struct musb_ep {
/* stuff towards the head is basically write-once. */
struct usb_ep end_point;
char name[12];
struct musb_hw_ep *hw_ep;
struct musb *musb;
u8 current_epnum;
/* ... when enabled/disabled ... */
u8 type;
u8 is_in;
u16 packet_sz;
const struct usb_endpoint_descriptor *desc;
struct dma_channel *dma;
/* later things are modified based on usage */
struct list_head req_list;
u8 wedged;
/* true if lock must be dropped but req_list may not be advanced */
u8 busy;
u8 hb_mult;
};
#define to_musb_ep(ep) container_of((ep), struct musb_ep, end_point)
static inline struct musb_request *next_request(struct musb_ep *ep)
{
struct list_head *queue = &ep->req_list;
if (list_empty(queue))
return NULL;
return container_of(queue->next, struct musb_request, list);
}
extern const struct usb_ep_ops musb_g_ep0_ops;
extern void musb_g_giveback(struct musb_ep *, struct usb_request *, int);
extern void musb_ep_restart(struct musb *, struct musb_request *);
#endif /* __MUSB_GADGET_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+124
View File
@@ -0,0 +1,124 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* MUSB OTG driver host defines
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
*/
#ifndef _MUSB_HOST_H
#define _MUSB_HOST_H
#include <linux/scatterlist.h>
/* stored in "usb_host_endpoint.hcpriv" for scheduled endpoints */
struct musb_qh {
struct usb_host_endpoint *hep; /* usbcore info */
struct usb_device *dev;
struct musb_hw_ep *hw_ep; /* current binding */
struct list_head ring; /* of musb_qh */
/* struct musb_qh *next; */ /* for periodic tree */
u8 mux; /* qh multiplexed to hw_ep */
unsigned offset; /* in urb->transfer_buffer */
unsigned segsize; /* current xfer fragment */
u8 type_reg; /* {rx,tx} type register */
u8 intv_reg; /* {rx,tx} interval register */
u8 addr_reg; /* device address register */
u8 h_addr_reg; /* hub address register */
u8 h_port_reg; /* hub port register */
u8 is_ready; /* safe to modify hw_ep */
u8 type; /* XFERTYPE_* */
u8 epnum;
u8 hb_mult; /* high bandwidth pkts per uf */
u16 maxpacket;
u16 frame; /* for periodic schedule */
unsigned iso_idx; /* in urb->iso_frame_desc[] */
struct sg_mapping_iter sg_miter; /* for highmem in PIO mode */
bool use_sg; /* to track urb using sglist */
};
/* map from control or bulk queue head to the first qh on that ring */
static inline struct musb_qh *first_qh(struct list_head *q)
{
if (list_empty(q))
return NULL;
return list_entry(q->next, struct musb_qh, ring);
}
#if IS_ENABLED(CONFIG_USB_MUSB_HOST) || IS_ENABLED(CONFIG_USB_MUSB_DUAL_ROLE)
extern struct musb *hcd_to_musb(struct usb_hcd *);
extern irqreturn_t musb_h_ep0_irq(struct musb *);
extern int musb_host_alloc(struct musb *);
extern int musb_host_setup(struct musb *, int);
extern void musb_host_cleanup(struct musb *);
extern void musb_host_tx(struct musb *, u8);
extern void musb_host_rx(struct musb *, u8);
extern void musb_root_disconnect(struct musb *musb);
extern void musb_host_free(struct musb *);
extern void musb_host_resume_root_hub(struct musb *musb);
extern void musb_host_poke_root_hub(struct musb *musb);
extern int musb_port_suspend(struct musb *musb, bool do_suspend);
extern void musb_port_reset(struct musb *musb, bool do_reset);
extern void musb_host_finish_resume(struct work_struct *work);
#else
static inline struct musb *hcd_to_musb(struct usb_hcd *hcd)
{
return NULL;
}
static inline irqreturn_t musb_h_ep0_irq(struct musb *musb)
{
return 0;
}
static inline int musb_host_alloc(struct musb *musb)
{
return 0;
}
static inline int musb_host_setup(struct musb *musb, int power_budget)
{
return 0;
}
static inline void musb_host_cleanup(struct musb *musb) {}
static inline void musb_host_free(struct musb *musb) {}
static inline void musb_host_tx(struct musb *musb, u8 epnum) {}
static inline void musb_host_rx(struct musb *musb, u8 epnum) {}
static inline void musb_root_disconnect(struct musb *musb) {}
static inline void musb_host_resume_root_hub(struct musb *musb) {}
static inline void musb_host_poke_root_hub(struct musb *musb) {}
static inline int musb_port_suspend(struct musb *musb, bool do_suspend)
{
return 0;
}
static inline void musb_port_reset(struct musb *musb, bool do_reset) {}
static inline void musb_host_finish_resume(struct work_struct *work) {}
#endif
struct usb_hcd;
extern int musb_hub_status_data(struct usb_hcd *hcd, char *buf);
extern int musb_hub_control(struct usb_hcd *hcd,
u16 typeReq, u16 wValue, u16 wIndex,
char *buf, u16 wLength);
static inline struct urb *next_urb(struct musb_qh *qh)
{
struct list_head *queue;
if (!qh)
return NULL;
queue = &qh->hep->urb_list;
if (list_empty(queue))
return NULL;
return list_entry(queue->next, struct urb, urb_list);
}
#endif /* _MUSB_HOST_H */
+49
View File
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* MUSB OTG driver register I/O
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
*/
#ifndef __MUSB_LINUX_PLATFORM_ARCH_H__
#define __MUSB_LINUX_PLATFORM_ARCH_H__
#include <linux/io.h>
#define musb_ep_select(_mbase, _epnum) musb->io.ep_select((_mbase), (_epnum))
/**
* struct musb_io - IO functions for MUSB
* @ep_offset: platform specific function to get end point offset
* @ep_select: platform specific function to select end point
* @fifo_offset: platform specific function to get fifo offset
* @read_fifo: platform specific function to read fifo
* @write_fifo: platform specific function to write fifo
* @busctl_offset: platform specific function to get busctl offset
* @get_toggle: platform specific function to get toggle
* @set_toggle: platform specific function to set toggle
*/
struct musb_io {
u32 (*ep_offset)(u8 epnum, u16 offset);
void (*ep_select)(void __iomem *mbase, u8 epnum);
u32 (*fifo_offset)(u8 epnum);
void (*read_fifo)(struct musb_hw_ep *hw_ep, u16 len, u8 *buf);
void (*write_fifo)(struct musb_hw_ep *hw_ep, u16 len, const u8 *buf);
u32 (*busctl_offset)(u8 epnum, u16 offset);
u16 (*get_toggle)(struct musb_qh *qh, int is_out);
u16 (*set_toggle)(struct musb_qh *qh, int is_out, struct urb *urb);
};
/* Do not add new entries here, add them the struct musb_io instead */
extern u8 (*musb_readb)(void __iomem *addr, u32 offset);
extern void (*musb_writeb)(void __iomem *addr, u32 offset, u8 data);
extern u8 (*musb_clearb)(void __iomem *addr, u32 offset);
extern u16 (*musb_readw)(void __iomem *addr, u32 offset);
extern void (*musb_writew)(void __iomem *addr, u32 offset, u16 data);
extern u16 (*musb_clearw)(void __iomem *addr, u32 offset);
extern u32 musb_readl(void __iomem *addr, u32 offset);
extern void musb_writel(void __iomem *addr, u32 offset, u32 data);
#endif
+362
View File
@@ -0,0 +1,362 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* MUSB OTG driver register defines
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
*/
#ifndef __MUSB_REGS_H__
#define __MUSB_REGS_H__
#define MUSB_EP0_FIFOSIZE 64 /* This is non-configurable */
/*
* MUSB Register bits
*/
/* POWER */
#define MUSB_POWER_ISOUPDATE 0x80
#define MUSB_POWER_SOFTCONN 0x40
#define MUSB_POWER_HSENAB 0x20
#define MUSB_POWER_HSMODE 0x10
#define MUSB_POWER_RESET 0x08
#define MUSB_POWER_RESUME 0x04
#define MUSB_POWER_SUSPENDM 0x02
#define MUSB_POWER_ENSUSPEND 0x01
/* INTRUSB */
#define MUSB_INTR_SUSPEND 0x01
#define MUSB_INTR_RESUME 0x02
#define MUSB_INTR_RESET 0x04
#define MUSB_INTR_BABBLE 0x04
#define MUSB_INTR_SOF 0x08
#define MUSB_INTR_CONNECT 0x10
#define MUSB_INTR_DISCONNECT 0x20
#define MUSB_INTR_SESSREQ 0x40
#define MUSB_INTR_VBUSERROR 0x80 /* For SESSION end */
/* DEVCTL */
#define MUSB_DEVCTL_BDEVICE 0x80
#define MUSB_DEVCTL_FSDEV 0x40
#define MUSB_DEVCTL_LSDEV 0x20
#define MUSB_DEVCTL_VBUS 0x18
#define MUSB_DEVCTL_VBUS_SHIFT 3
#define MUSB_DEVCTL_HM 0x04
#define MUSB_DEVCTL_HR 0x02
#define MUSB_DEVCTL_SESSION 0x01
/* BABBLE_CTL */
#define MUSB_BABBLE_FORCE_TXIDLE 0x80
#define MUSB_BABBLE_SW_SESSION_CTRL 0x40
#define MUSB_BABBLE_STUCK_J 0x20
#define MUSB_BABBLE_RCV_DISABLE 0x04
/* MUSB ULPI VBUSCONTROL */
#define MUSB_ULPI_USE_EXTVBUS 0x01
#define MUSB_ULPI_USE_EXTVBUSIND 0x02
/* ULPI_REG_CONTROL */
#define MUSB_ULPI_REG_REQ (1 << 0)
#define MUSB_ULPI_REG_CMPLT (1 << 1)
#define MUSB_ULPI_RDN_WR (1 << 2)
/* TESTMODE */
#define MUSB_TEST_FORCE_HOST 0x80
#define MUSB_TEST_FIFO_ACCESS 0x40
#define MUSB_TEST_FORCE_FS 0x20
#define MUSB_TEST_FORCE_HS 0x10
#define MUSB_TEST_PACKET 0x08
#define MUSB_TEST_K 0x04
#define MUSB_TEST_J 0x02
#define MUSB_TEST_SE0_NAK 0x01
/* Allocate for double-packet buffering (effectively doubles assigned _SIZE) */
#define MUSB_FIFOSZ_DPB 0x10
/* Allocation size (8, 16, 32, ... 4096) */
#define MUSB_FIFOSZ_SIZE 0x0f
/* CSR0 */
#define MUSB_CSR0_FLUSHFIFO 0x0100
#define MUSB_CSR0_TXPKTRDY 0x0002
#define MUSB_CSR0_RXPKTRDY 0x0001
/* CSR0 in Peripheral mode */
#define MUSB_CSR0_P_SVDSETUPEND 0x0080
#define MUSB_CSR0_P_SVDRXPKTRDY 0x0040
#define MUSB_CSR0_P_SENDSTALL 0x0020
#define MUSB_CSR0_P_SETUPEND 0x0010
#define MUSB_CSR0_P_DATAEND 0x0008
#define MUSB_CSR0_P_SENTSTALL 0x0004
/* CSR0 in Host mode */
#define MUSB_CSR0_H_DIS_PING 0x0800
#define MUSB_CSR0_H_WR_DATATOGGLE 0x0400 /* Set to allow setting: */
#define MUSB_CSR0_H_DATATOGGLE 0x0200 /* Data toggle control */
#define MUSB_CSR0_H_NAKTIMEOUT 0x0080
#define MUSB_CSR0_H_STATUSPKT 0x0040
#define MUSB_CSR0_H_REQPKT 0x0020
#define MUSB_CSR0_H_ERROR 0x0010
#define MUSB_CSR0_H_SETUPPKT 0x0008
#define MUSB_CSR0_H_RXSTALL 0x0004
/* CSR0 bits to avoid zeroing (write zero clears, write 1 ignored) */
#define MUSB_CSR0_P_WZC_BITS \
(MUSB_CSR0_P_SENTSTALL)
#define MUSB_CSR0_H_WZC_BITS \
(MUSB_CSR0_H_NAKTIMEOUT | MUSB_CSR0_H_RXSTALL \
| MUSB_CSR0_RXPKTRDY)
/* TxType/RxType */
#define MUSB_TYPE_SPEED 0xc0
#define MUSB_TYPE_SPEED_SHIFT 6
#define MUSB_TYPE_PROTO 0x30 /* Implicitly zero for ep0 */
#define MUSB_TYPE_PROTO_SHIFT 4
#define MUSB_TYPE_REMOTE_END 0xf /* Implicitly zero for ep0 */
/* CONFIGDATA */
#define MUSB_CONFIGDATA_MPRXE 0x80 /* Auto bulk pkt combining */
#define MUSB_CONFIGDATA_MPTXE 0x40 /* Auto bulk pkt splitting */
#define MUSB_CONFIGDATA_BIGENDIAN 0x20
#define MUSB_CONFIGDATA_HBRXE 0x10 /* HB-ISO for RX */
#define MUSB_CONFIGDATA_HBTXE 0x08 /* HB-ISO for TX */
#define MUSB_CONFIGDATA_DYNFIFO 0x04 /* Dynamic FIFO sizing */
#define MUSB_CONFIGDATA_SOFTCONE 0x02 /* SoftConnect */
#define MUSB_CONFIGDATA_UTMIDW 0x01 /* Data width 0/1 => 8/16bits */
/* TXCSR in Peripheral and Host mode */
#define MUSB_TXCSR_AUTOSET 0x8000
#define MUSB_TXCSR_DMAENAB 0x1000
#define MUSB_TXCSR_FRCDATATOG 0x0800
#define MUSB_TXCSR_DMAMODE 0x0400
#define MUSB_TXCSR_CLRDATATOG 0x0040
#define MUSB_TXCSR_FLUSHFIFO 0x0008
#define MUSB_TXCSR_FIFONOTEMPTY 0x0002
#define MUSB_TXCSR_TXPKTRDY 0x0001
/* TXCSR in Peripheral mode */
#define MUSB_TXCSR_P_ISO 0x4000
#define MUSB_TXCSR_P_INCOMPTX 0x0080
#define MUSB_TXCSR_P_SENTSTALL 0x0020
#define MUSB_TXCSR_P_SENDSTALL 0x0010
#define MUSB_TXCSR_P_UNDERRUN 0x0004
/* TXCSR in Host mode */
#define MUSB_TXCSR_H_WR_DATATOGGLE 0x0200
#define MUSB_TXCSR_H_DATATOGGLE 0x0100
#define MUSB_TXCSR_H_NAKTIMEOUT 0x0080
#define MUSB_TXCSR_H_RXSTALL 0x0020
#define MUSB_TXCSR_H_ERROR 0x0004
/* TXCSR bits to avoid zeroing (write zero clears, write 1 ignored) */
#define MUSB_TXCSR_P_WZC_BITS \
(MUSB_TXCSR_P_INCOMPTX | MUSB_TXCSR_P_SENTSTALL \
| MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_FIFONOTEMPTY)
#define MUSB_TXCSR_H_WZC_BITS \
(MUSB_TXCSR_H_NAKTIMEOUT | MUSB_TXCSR_H_RXSTALL \
| MUSB_TXCSR_H_ERROR | MUSB_TXCSR_FIFONOTEMPTY)
/* RXCSR in Peripheral and Host mode */
#define MUSB_RXCSR_AUTOCLEAR 0x8000
#define MUSB_RXCSR_DMAENAB 0x2000
#define MUSB_RXCSR_DISNYET 0x1000
#define MUSB_RXCSR_PID_ERR 0x1000
#define MUSB_RXCSR_DMAMODE 0x0800
#define MUSB_RXCSR_INCOMPRX 0x0100
#define MUSB_RXCSR_CLRDATATOG 0x0080
#define MUSB_RXCSR_FLUSHFIFO 0x0010
#define MUSB_RXCSR_DATAERROR 0x0008
#define MUSB_RXCSR_FIFOFULL 0x0002
#define MUSB_RXCSR_RXPKTRDY 0x0001
/* RXCSR in Peripheral mode */
#define MUSB_RXCSR_P_ISO 0x4000
#define MUSB_RXCSR_P_SENTSTALL 0x0040
#define MUSB_RXCSR_P_SENDSTALL 0x0020
#define MUSB_RXCSR_P_OVERRUN 0x0004
/* RXCSR in Host mode */
#define MUSB_RXCSR_H_AUTOREQ 0x4000
#define MUSB_RXCSR_H_WR_DATATOGGLE 0x0400
#define MUSB_RXCSR_H_DATATOGGLE 0x0200
#define MUSB_RXCSR_H_RXSTALL 0x0040
#define MUSB_RXCSR_H_REQPKT 0x0020
#define MUSB_RXCSR_H_ERROR 0x0004
/* RXCSR bits to avoid zeroing (write zero clears, write 1 ignored) */
#define MUSB_RXCSR_P_WZC_BITS \
(MUSB_RXCSR_P_SENTSTALL | MUSB_RXCSR_P_OVERRUN \
| MUSB_RXCSR_RXPKTRDY)
#define MUSB_RXCSR_H_WZC_BITS \
(MUSB_RXCSR_H_RXSTALL | MUSB_RXCSR_H_ERROR \
| MUSB_RXCSR_DATAERROR | MUSB_RXCSR_RXPKTRDY)
/* HUBADDR */
#define MUSB_HUBADDR_MULTI_TT 0x80
/*
* Common USB registers
*/
#define MUSB_FADDR 0x00 /* 8-bit */
#define MUSB_POWER 0x01 /* 8-bit */
#define MUSB_INTRTX 0x02 /* 16-bit */
#define MUSB_INTRRX 0x04
#define MUSB_INTRTXE 0x06
#define MUSB_INTRRXE 0x08
#define MUSB_INTRUSB 0x0A /* 8 bit */
#define MUSB_INTRUSBE 0x0B /* 8 bit */
#define MUSB_FRAME 0x0C
#define MUSB_INDEX 0x0E /* 8 bit */
#define MUSB_TESTMODE 0x0F /* 8 bit */
/*
* Additional Control Registers
*/
#define MUSB_DEVCTL 0x60 /* 8 bit */
#define MUSB_BABBLE_CTL 0x61 /* 8 bit */
/* These are always controlled through the INDEX register */
#define MUSB_TXFIFOSZ 0x62 /* 8-bit (see masks) */
#define MUSB_RXFIFOSZ 0x63 /* 8-bit (see masks) */
#define MUSB_TXFIFOADD 0x64 /* 16-bit offset shifted right 3 */
#define MUSB_RXFIFOADD 0x66 /* 16-bit offset shifted right 3 */
/* REVISIT: vctrl/vstatus: optional vendor utmi+phy register at 0x68 */
#define MUSB_HWVERS 0x6C /* 8 bit */
#define MUSB_ULPI_BUSCONTROL 0x70 /* 8 bit */
#define MUSB_ULPI_INT_MASK 0x72 /* 8 bit */
#define MUSB_ULPI_INT_SRC 0x73 /* 8 bit */
#define MUSB_ULPI_REG_DATA 0x74 /* 8 bit */
#define MUSB_ULPI_REG_ADDR 0x75 /* 8 bit */
#define MUSB_ULPI_REG_CONTROL 0x76 /* 8 bit */
#define MUSB_ULPI_RAW_DATA 0x77 /* 8 bit */
#define MUSB_EPINFO 0x78 /* 8 bit */
#define MUSB_RAMINFO 0x79 /* 8 bit */
#define MUSB_LINKINFO 0x7a /* 8 bit */
#define MUSB_VPLEN 0x7b /* 8 bit */
#define MUSB_HS_EOF1 0x7c /* 8 bit */
#define MUSB_FS_EOF1 0x7d /* 8 bit */
#define MUSB_LS_EOF1 0x7e /* 8 bit */
/* Offsets to endpoint registers */
#define MUSB_TXMAXP 0x00
#define MUSB_TXCSR 0x02
#define MUSB_CSR0 MUSB_TXCSR /* Re-used for EP0 */
#define MUSB_RXMAXP 0x04
#define MUSB_RXCSR 0x06
#define MUSB_RXCOUNT 0x08
#define MUSB_COUNT0 MUSB_RXCOUNT /* Re-used for EP0 */
#define MUSB_TXTYPE 0x0A
#define MUSB_TYPE0 MUSB_TXTYPE /* Re-used for EP0 */
#define MUSB_TXINTERVAL 0x0B
#define MUSB_NAKLIMIT0 MUSB_TXINTERVAL /* Re-used for EP0 */
#define MUSB_RXTYPE 0x0C
#define MUSB_RXINTERVAL 0x0D
#define MUSB_FIFOSIZE 0x0F
#define MUSB_CONFIGDATA MUSB_FIFOSIZE /* Re-used for EP0 */
#include "tusb6010.h" /* Needed "only" for TUSB_EP0_CONF */
#define MUSB_TXCSR_MODE 0x2000
/* "bus control"/target registers, for host side multipoint (external hubs) */
#define MUSB_TXFUNCADDR 0x00
#define MUSB_TXHUBADDR 0x02
#define MUSB_TXHUBPORT 0x03
#define MUSB_RXFUNCADDR 0x04
#define MUSB_RXHUBADDR 0x06
#define MUSB_RXHUBPORT 0x07
static inline u8 musb_read_configdata(void __iomem *mbase)
{
musb_writeb(mbase, MUSB_INDEX, 0);
return musb_readb(mbase, 0x10 + MUSB_CONFIGDATA);
}
static inline void musb_write_rxfunaddr(struct musb *musb, u8 epnum,
u8 qh_addr_reg)
{
musb_writeb(musb->mregs,
musb->io.busctl_offset(epnum, MUSB_RXFUNCADDR),
qh_addr_reg);
}
static inline void musb_write_rxhubaddr(struct musb *musb, u8 epnum,
u8 qh_h_addr_reg)
{
musb_writeb(musb->mregs, musb->io.busctl_offset(epnum, MUSB_RXHUBADDR),
qh_h_addr_reg);
}
static inline void musb_write_rxhubport(struct musb *musb, u8 epnum,
u8 qh_h_port_reg)
{
musb_writeb(musb->mregs, musb->io.busctl_offset(epnum, MUSB_RXHUBPORT),
qh_h_port_reg);
}
static inline void musb_write_txfunaddr(struct musb *musb, u8 epnum,
u8 qh_addr_reg)
{
musb_writeb(musb->mregs,
musb->io.busctl_offset(epnum, MUSB_TXFUNCADDR),
qh_addr_reg);
}
static inline void musb_write_txhubaddr(struct musb *musb, u8 epnum,
u8 qh_addr_reg)
{
musb_writeb(musb->mregs, musb->io.busctl_offset(epnum, MUSB_TXHUBADDR),
qh_addr_reg);
}
static inline void musb_write_txhubport(struct musb *musb, u8 epnum,
u8 qh_h_port_reg)
{
musb_writeb(musb->mregs, musb->io.busctl_offset(epnum, MUSB_TXHUBPORT),
qh_h_port_reg);
}
static inline u8 musb_read_rxfunaddr(struct musb *musb, u8 epnum)
{
return musb_readb(musb->mregs,
musb->io.busctl_offset(epnum, MUSB_RXFUNCADDR));
}
static inline u8 musb_read_rxhubaddr(struct musb *musb, u8 epnum)
{
return musb_readb(musb->mregs,
musb->io.busctl_offset(epnum, MUSB_RXHUBADDR));
}
static inline u8 musb_read_rxhubport(struct musb *musb, u8 epnum)
{
return musb_readb(musb->mregs,
musb->io.busctl_offset(epnum, MUSB_RXHUBPORT));
}
static inline u8 musb_read_txfunaddr(struct musb *musb, u8 epnum)
{
return musb_readb(musb->mregs,
musb->io.busctl_offset(epnum, MUSB_TXFUNCADDR));
}
static inline u8 musb_read_txhubaddr(struct musb *musb, u8 epnum)
{
return musb_readb(musb->mregs,
musb->io.busctl_offset(epnum, MUSB_TXHUBADDR));
}
static inline u8 musb_read_txhubport(struct musb *musb, u8 epnum)
{
return musb_readb(musb->mregs,
musb->io.busctl_offset(epnum, MUSB_TXHUBPORT));
}
#endif /* __MUSB_REGS_H__ */
+25
View File
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0
/*
* musb_trace.c - MUSB Controller Trace Support
*
* Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com
*
* Author: Bin Liu <b-liu@ti.com>
*/
#define CREATE_TRACE_POINTS
#include "musb_trace.h"
void musb_dbg(struct musb *musb, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
trace_musb_log(musb, &vaf);
va_end(args);
}
+389
View File
@@ -0,0 +1,389 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* musb_trace.h - MUSB Controller Trace Support
*
* Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com
*
* Author: Bin Liu <b-liu@ti.com>
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM musb
#if !defined(__MUSB_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define __MUSB_TRACE_H
#include <linux/types.h>
#include <linux/tracepoint.h>
#include <linux/usb.h>
#include "musb_core.h"
#ifdef CONFIG_USB_TI_CPPI41_DMA
#include "cppi_dma.h"
#endif
#define MUSB_MSG_MAX 500
TRACE_EVENT(musb_log,
TP_PROTO(struct musb *musb, struct va_format *vaf),
TP_ARGS(musb, vaf),
TP_STRUCT__entry(
__string(name, dev_name(musb->controller))
__vstring(msg, vaf->fmt, vaf->va)
),
TP_fast_assign(
__assign_str(name);
__assign_vstr(msg, vaf->fmt, vaf->va);
),
TP_printk("%s: %s", __get_str(name), __get_str(msg))
);
TRACE_EVENT(musb_state,
TP_PROTO(struct musb *musb, u8 devctl, const char *desc),
TP_ARGS(musb, devctl, desc),
TP_STRUCT__entry(
__string(name, dev_name(musb->controller))
__field(u8, devctl)
__string(desc, desc)
),
TP_fast_assign(
__assign_str(name);
__entry->devctl = devctl;
__assign_str(desc);
),
TP_printk("%s: devctl: %02x %s", __get_str(name), __entry->devctl,
__get_str(desc))
);
DECLARE_EVENT_CLASS(musb_regb,
TP_PROTO(void *caller, const void __iomem *addr,
unsigned int offset, u8 data),
TP_ARGS(caller, addr, offset, data),
TP_STRUCT__entry(
__field(void *, caller)
__field(const void __iomem *, addr)
__field(unsigned int, offset)
__field(u8, data)
),
TP_fast_assign(
__entry->caller = caller;
__entry->addr = addr;
__entry->offset = offset;
__entry->data = data;
),
TP_printk("%pS: %p + %04x: %02x",
__entry->caller, __entry->addr, __entry->offset, __entry->data)
);
DEFINE_EVENT(musb_regb, musb_readb,
TP_PROTO(void *caller, const void __iomem *addr,
unsigned int offset, u8 data),
TP_ARGS(caller, addr, offset, data)
);
DEFINE_EVENT(musb_regb, musb_writeb,
TP_PROTO(void *caller, const void __iomem *addr,
unsigned int offset, u8 data),
TP_ARGS(caller, addr, offset, data)
);
DECLARE_EVENT_CLASS(musb_regw,
TP_PROTO(void *caller, const void __iomem *addr,
unsigned int offset, u16 data),
TP_ARGS(caller, addr, offset, data),
TP_STRUCT__entry(
__field(void *, caller)
__field(const void __iomem *, addr)
__field(unsigned int, offset)
__field(u16, data)
),
TP_fast_assign(
__entry->caller = caller;
__entry->addr = addr;
__entry->offset = offset;
__entry->data = data;
),
TP_printk("%pS: %p + %04x: %04x",
__entry->caller, __entry->addr, __entry->offset, __entry->data)
);
DEFINE_EVENT(musb_regw, musb_readw,
TP_PROTO(void *caller, const void __iomem *addr,
unsigned int offset, u16 data),
TP_ARGS(caller, addr, offset, data)
);
DEFINE_EVENT(musb_regw, musb_writew,
TP_PROTO(void *caller, const void __iomem *addr,
unsigned int offset, u16 data),
TP_ARGS(caller, addr, offset, data)
);
DECLARE_EVENT_CLASS(musb_regl,
TP_PROTO(void *caller, const void __iomem *addr,
unsigned int offset, u32 data),
TP_ARGS(caller, addr, offset, data),
TP_STRUCT__entry(
__field(void *, caller)
__field(const void __iomem *, addr)
__field(unsigned int, offset)
__field(u32, data)
),
TP_fast_assign(
__entry->caller = caller;
__entry->addr = addr;
__entry->offset = offset;
__entry->data = data;
),
TP_printk("%pS: %p + %04x: %08x",
__entry->caller, __entry->addr, __entry->offset, __entry->data)
);
DEFINE_EVENT(musb_regl, musb_readl,
TP_PROTO(void *caller, const void __iomem *addr,
unsigned int offset, u32 data),
TP_ARGS(caller, addr, offset, data)
);
DEFINE_EVENT(musb_regl, musb_writel,
TP_PROTO(void *caller, const void __iomem *addr,
unsigned int offset, u32 data),
TP_ARGS(caller, addr, offset, data)
);
TRACE_EVENT(musb_isr,
TP_PROTO(struct musb *musb),
TP_ARGS(musb),
TP_STRUCT__entry(
__string(name, dev_name(musb->controller))
__field(u8, int_usb)
__field(u16, int_tx)
__field(u16, int_rx)
),
TP_fast_assign(
__assign_str(name);
__entry->int_usb = musb->int_usb;
__entry->int_tx = musb->int_tx;
__entry->int_rx = musb->int_rx;
),
TP_printk("%s: usb %02x, tx %04x, rx %04x",
__get_str(name), __entry->int_usb,
__entry->int_tx, __entry->int_rx
)
);
DECLARE_EVENT_CLASS(musb_urb,
TP_PROTO(struct musb *musb, struct urb *urb),
TP_ARGS(musb, urb),
TP_STRUCT__entry(
__string(name, dev_name(musb->controller))
__field(struct urb *, urb)
__field(unsigned int, pipe)
__field(int, status)
__field(unsigned int, flag)
__field(u32, buf_len)
__field(u32, actual_len)
),
TP_fast_assign(
__assign_str(name);
__entry->urb = urb;
__entry->pipe = urb->pipe;
__entry->status = urb->status;
__entry->flag = urb->transfer_flags;
__entry->buf_len = urb->transfer_buffer_length;
__entry->actual_len = urb->actual_length;
),
TP_printk("%s: %p, dev%d ep%d%s, flag 0x%x, len %d/%d, status %d",
__get_str(name), __entry->urb,
usb_pipedevice(__entry->pipe),
usb_pipeendpoint(__entry->pipe),
usb_pipein(__entry->pipe) ? "in" : "out",
__entry->flag,
__entry->actual_len, __entry->buf_len,
__entry->status
)
);
DEFINE_EVENT(musb_urb, musb_urb_start,
TP_PROTO(struct musb *musb, struct urb *urb),
TP_ARGS(musb, urb)
);
DEFINE_EVENT(musb_urb, musb_urb_gb,
TP_PROTO(struct musb *musb, struct urb *urb),
TP_ARGS(musb, urb)
);
DEFINE_EVENT(musb_urb, musb_urb_rx,
TP_PROTO(struct musb *musb, struct urb *urb),
TP_ARGS(musb, urb)
);
DEFINE_EVENT(musb_urb, musb_urb_tx,
TP_PROTO(struct musb *musb, struct urb *urb),
TP_ARGS(musb, urb)
);
DEFINE_EVENT(musb_urb, musb_urb_enq,
TP_PROTO(struct musb *musb, struct urb *urb),
TP_ARGS(musb, urb)
);
DEFINE_EVENT(musb_urb, musb_urb_deq,
TP_PROTO(struct musb *musb, struct urb *urb),
TP_ARGS(musb, urb)
);
DECLARE_EVENT_CLASS(musb_req,
TP_PROTO(struct musb_request *req),
TP_ARGS(req),
TP_STRUCT__entry(
__field(struct usb_request *, req)
__field(u8, is_tx)
__field(u8, epnum)
__field(int, status)
__field(unsigned int, buf_len)
__field(unsigned int, actual_len)
__field(unsigned int, zero)
__field(unsigned int, short_not_ok)
__field(unsigned int, no_interrupt)
),
TP_fast_assign(
__entry->req = &req->request;
__entry->is_tx = req->tx;
__entry->epnum = req->epnum;
__entry->status = req->request.status;
__entry->buf_len = req->request.length;
__entry->actual_len = req->request.actual;
__entry->zero = req->request.zero;
__entry->short_not_ok = req->request.short_not_ok;
__entry->no_interrupt = req->request.no_interrupt;
),
TP_printk("%p, ep%d %s, %s%s%s, len %d/%d, status %d",
__entry->req, __entry->epnum,
__entry->is_tx ? "tx/IN" : "rx/OUT",
__entry->zero ? "Z" : "z",
__entry->short_not_ok ? "S" : "s",
__entry->no_interrupt ? "I" : "i",
__entry->actual_len, __entry->buf_len,
__entry->status
)
);
DEFINE_EVENT(musb_req, musb_req_gb,
TP_PROTO(struct musb_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(musb_req, musb_req_tx,
TP_PROTO(struct musb_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(musb_req, musb_req_rx,
TP_PROTO(struct musb_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(musb_req, musb_req_alloc,
TP_PROTO(struct musb_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(musb_req, musb_req_free,
TP_PROTO(struct musb_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(musb_req, musb_req_start,
TP_PROTO(struct musb_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(musb_req, musb_req_enq,
TP_PROTO(struct musb_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(musb_req, musb_req_deq,
TP_PROTO(struct musb_request *req),
TP_ARGS(req)
);
#ifdef CONFIG_USB_TI_CPPI41_DMA
DECLARE_EVENT_CLASS(musb_cppi41,
TP_PROTO(struct cppi41_dma_channel *ch),
TP_ARGS(ch),
TP_STRUCT__entry(
__field(struct cppi41_dma_channel *, ch)
__string(name, dev_name(ch->hw_ep->musb->controller))
__field(u8, hwep)
__field(u8, port)
__field(u8, is_tx)
__field(u32, len)
__field(u32, prog_len)
__field(u32, xferred)
),
TP_fast_assign(
__entry->ch = ch;
__assign_str(name);
__entry->hwep = ch->hw_ep->epnum;
__entry->port = ch->port_num;
__entry->is_tx = ch->is_tx;
__entry->len = ch->total_len;
__entry->prog_len = ch->prog_len;
__entry->xferred = ch->transferred;
),
TP_printk("%s: %p, hwep%d ch%d%s, prog_len %d, len %d/%d",
__get_str(name), __entry->ch, __entry->hwep,
__entry->port, __entry->is_tx ? "tx" : "rx",
__entry->prog_len, __entry->xferred, __entry->len
)
);
DEFINE_EVENT(musb_cppi41, musb_cppi41_done,
TP_PROTO(struct cppi41_dma_channel *ch),
TP_ARGS(ch)
);
DEFINE_EVENT(musb_cppi41, musb_cppi41_gb,
TP_PROTO(struct cppi41_dma_channel *ch),
TP_ARGS(ch)
);
DEFINE_EVENT(musb_cppi41, musb_cppi41_config,
TP_PROTO(struct cppi41_dma_channel *ch),
TP_ARGS(ch)
);
DEFINE_EVENT(musb_cppi41, musb_cppi41_cont,
TP_PROTO(struct cppi41_dma_channel *ch),
TP_ARGS(ch)
);
DEFINE_EVENT(musb_cppi41, musb_cppi41_alloc,
TP_PROTO(struct cppi41_dma_channel *ch),
TP_ARGS(ch)
);
DEFINE_EVENT(musb_cppi41, musb_cppi41_abort,
TP_PROTO(struct cppi41_dma_channel *ch),
TP_ARGS(ch)
);
DEFINE_EVENT(musb_cppi41, musb_cppi41_free,
TP_PROTO(struct cppi41_dma_channel *ch),
TP_ARGS(ch)
);
#endif /* CONFIG_USB_TI_CPPI41_DMA */
#endif /* __MUSB_TRACE_H */
/* this part has to be here */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE musb_trace
#include <trace/define_trace.h>
+439
View File
@@ -0,0 +1,439 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MUSB OTG driver virtual root hub support
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/unaligned.h>
#include "musb_core.h"
void musb_host_finish_resume(struct work_struct *work)
{
struct musb *musb;
unsigned long flags;
u8 power;
musb = container_of(work, struct musb, finish_resume_work.work);
spin_lock_irqsave(&musb->lock, flags);
power = musb_readb(musb->mregs, MUSB_POWER);
power &= ~MUSB_POWER_RESUME;
musb_dbg(musb, "root port resume stopped, power %02x", power);
musb_writeb(musb->mregs, MUSB_POWER, power);
/*
* ISSUE: DaVinci (RTL 1.300) disconnects after
* resume of high speed peripherals (but not full
* speed ones).
*/
musb->is_active = 1;
musb->port1_status &= ~(USB_PORT_STAT_SUSPEND | MUSB_PORT_STAT_RESUME);
musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16;
usb_hcd_poll_rh_status(musb->hcd);
/* NOTE: it might really be A_WAIT_BCON ... */
musb_set_state(musb, OTG_STATE_A_HOST);
spin_unlock_irqrestore(&musb->lock, flags);
}
int musb_port_suspend(struct musb *musb, bool do_suspend)
{
u8 power;
void __iomem *mbase = musb->mregs;
if (!is_host_active(musb))
return 0;
/* NOTE: this doesn't necessarily put PHY into low power mode,
* turning off its clock; that's a function of PHY integration and
* MUSB_POWER_ENSUSPEND. PHY may need a clock (sigh) to detect
* SE0 changing to connect (J) or wakeup (K) states.
*/
power = musb_readb(mbase, MUSB_POWER);
if (do_suspend) {
int retries = 10000;
if (power & MUSB_POWER_RESUME)
return -EBUSY;
if (!(power & MUSB_POWER_SUSPENDM)) {
power |= MUSB_POWER_SUSPENDM;
musb_writeb(mbase, MUSB_POWER, power);
/* Needed for OPT A tests */
power = musb_readb(mbase, MUSB_POWER);
while (power & MUSB_POWER_SUSPENDM) {
power = musb_readb(mbase, MUSB_POWER);
if (retries-- < 1)
break;
}
}
musb_dbg(musb, "Root port suspended, power %02x", power);
musb->port1_status |= USB_PORT_STAT_SUSPEND;
switch (musb_get_state(musb)) {
case OTG_STATE_A_HOST:
musb_set_state(musb, OTG_STATE_A_SUSPEND);
musb->is_active = musb->xceiv &&
musb->xceiv->otg->host->b_hnp_enable;
if (musb->is_active)
mod_timer(&musb->otg_timer, jiffies
+ msecs_to_jiffies(
OTG_TIME_A_AIDL_BDIS));
musb_platform_try_idle(musb, 0);
break;
case OTG_STATE_B_HOST:
musb_set_state(musb, OTG_STATE_B_WAIT_ACON);
musb->is_active = musb->xceiv &&
musb->xceiv->otg->host->b_hnp_enable;
musb_platform_try_idle(musb, 0);
break;
default:
musb_dbg(musb, "bogus rh suspend? %s",
musb_otg_state_string(musb));
}
} else if (power & MUSB_POWER_SUSPENDM) {
power &= ~MUSB_POWER_SUSPENDM;
power |= MUSB_POWER_RESUME;
musb_writeb(mbase, MUSB_POWER, power);
musb_dbg(musb, "Root port resuming, power %02x", power);
musb->port1_status |= MUSB_PORT_STAT_RESUME;
schedule_delayed_work(&musb->finish_resume_work,
msecs_to_jiffies(USB_RESUME_TIMEOUT));
}
return 0;
}
void musb_port_reset(struct musb *musb, bool do_reset)
{
u8 power;
void __iomem *mbase = musb->mregs;
if (musb_get_state(musb) == OTG_STATE_B_IDLE) {
musb_dbg(musb, "HNP: Returning from HNP; no hub reset from b_idle");
musb->port1_status &= ~USB_PORT_STAT_RESET;
return;
}
if (!is_host_active(musb))
return;
/* NOTE: caller guarantees it will turn off the reset when
* the appropriate amount of time has passed
*/
power = musb_readb(mbase, MUSB_POWER);
if (do_reset) {
/*
* If RESUME is set, we must make sure it stays minimum 20 ms.
* Then we must clear RESUME and wait a bit to let musb start
* generating SOFs. If we don't do this, OPT HS A 6.8 tests
* fail with "Error! Did not receive an SOF before suspend
* detected".
*/
if (power & MUSB_POWER_RESUME) {
long remain = (unsigned long) musb->rh_timer - jiffies;
if (musb->rh_timer > 0 && remain > 0) {
/* take into account the minimum delay after resume */
schedule_delayed_work(
&musb->deassert_reset_work, remain);
return;
}
musb_writeb(mbase, MUSB_POWER,
power & ~MUSB_POWER_RESUME);
/* Give the core 1 ms to clear MUSB_POWER_RESUME */
schedule_delayed_work(&musb->deassert_reset_work,
msecs_to_jiffies(1));
return;
}
power &= 0xf0;
musb_writeb(mbase, MUSB_POWER,
power | MUSB_POWER_RESET);
musb->port1_status |= USB_PORT_STAT_RESET;
musb->port1_status &= ~USB_PORT_STAT_ENABLE;
schedule_delayed_work(&musb->deassert_reset_work,
msecs_to_jiffies(50));
} else {
musb_dbg(musb, "root port reset stopped");
musb_platform_pre_root_reset_end(musb);
musb_writeb(mbase, MUSB_POWER,
power & ~MUSB_POWER_RESET);
musb_platform_post_root_reset_end(musb);
power = musb_readb(mbase, MUSB_POWER);
if (power & MUSB_POWER_HSMODE) {
musb_dbg(musb, "high-speed device connected");
musb->port1_status |= USB_PORT_STAT_HIGH_SPEED;
}
musb->port1_status &= ~USB_PORT_STAT_RESET;
musb->port1_status |= USB_PORT_STAT_ENABLE
| (USB_PORT_STAT_C_RESET << 16)
| (USB_PORT_STAT_C_ENABLE << 16);
usb_hcd_poll_rh_status(musb->hcd);
musb->vbuserr_retry = VBUSERR_RETRY_COUNT;
}
}
void musb_root_disconnect(struct musb *musb)
{
musb->port1_status = USB_PORT_STAT_POWER
| (USB_PORT_STAT_C_CONNECTION << 16);
usb_hcd_poll_rh_status(musb->hcd);
musb->is_active = 0;
switch (musb_get_state(musb)) {
case OTG_STATE_A_SUSPEND:
if (musb->xceiv && musb->xceiv->otg->host->b_hnp_enable) {
musb_set_state(musb, OTG_STATE_A_PERIPHERAL);
musb->g.is_a_peripheral = 1;
break;
}
fallthrough;
case OTG_STATE_A_HOST:
musb_set_state(musb, OTG_STATE_A_WAIT_BCON);
musb->is_active = 0;
break;
case OTG_STATE_A_WAIT_VFALL:
musb_set_state(musb, OTG_STATE_B_IDLE);
break;
default:
musb_dbg(musb, "host disconnect (%s)",
musb_otg_state_string(musb));
}
}
EXPORT_SYMBOL_GPL(musb_root_disconnect);
/*---------------------------------------------------------------------*/
/* Caller may or may not hold musb->lock */
int musb_hub_status_data(struct usb_hcd *hcd, char *buf)
{
struct musb *musb = hcd_to_musb(hcd);
int retval = 0;
/* called in_irq() via usb_hcd_poll_rh_status() */
if (musb->port1_status & 0xffff0000) {
*buf = 0x02;
retval = 1;
}
return retval;
}
static int musb_has_gadget(struct musb *musb)
{
/*
* In host-only mode we start a connection right away. In OTG mode
* we have to wait until we loaded a gadget. We don't really need a
* gadget if we operate as a host but we should not start a session
* as a device without a gadget or else we explode.
*/
#ifdef CONFIG_USB_MUSB_HOST
return 1;
#else
return musb->port_mode == MUSB_HOST;
#endif
}
int musb_hub_control(
struct usb_hcd *hcd,
u16 typeReq,
u16 wValue,
u16 wIndex,
char *buf,
u16 wLength)
{
struct musb *musb = hcd_to_musb(hcd);
u32 temp;
int retval = 0;
unsigned long flags;
bool start_musb = false;
spin_lock_irqsave(&musb->lock, flags);
if (unlikely(!HCD_HW_ACCESSIBLE(hcd))) {
spin_unlock_irqrestore(&musb->lock, flags);
return -ESHUTDOWN;
}
/* hub features: always zero, setting is a NOP
* port features: reported, sometimes updated when host is active
* no indicators
*/
switch (typeReq) {
case ClearHubFeature:
case SetHubFeature:
switch (wValue) {
case C_HUB_OVER_CURRENT:
case C_HUB_LOCAL_POWER:
break;
default:
goto error;
}
break;
case ClearPortFeature:
if ((wIndex & 0xff) != 1)
goto error;
switch (wValue) {
case USB_PORT_FEAT_ENABLE:
break;
case USB_PORT_FEAT_SUSPEND:
musb_port_suspend(musb, false);
break;
case USB_PORT_FEAT_POWER:
if (!hcd->self.is_b_host)
musb_platform_set_vbus(musb, 0);
break;
case USB_PORT_FEAT_C_CONNECTION:
case USB_PORT_FEAT_C_ENABLE:
case USB_PORT_FEAT_C_OVER_CURRENT:
case USB_PORT_FEAT_C_RESET:
case USB_PORT_FEAT_C_SUSPEND:
break;
default:
goto error;
}
musb_dbg(musb, "clear feature %d", wValue);
musb->port1_status &= ~(1 << wValue);
break;
case GetHubDescriptor:
{
struct usb_hub_descriptor *desc = (void *)buf;
desc->bDescLength = 9;
desc->bDescriptorType = USB_DT_HUB;
desc->bNbrPorts = 1;
desc->wHubCharacteristics = cpu_to_le16(
HUB_CHAR_INDV_PORT_LPSM /* per-port power switching */
| HUB_CHAR_NO_OCPM /* no overcurrent reporting */
);
desc->bPwrOn2PwrGood = 5; /* msec/2 */
desc->bHubContrCurrent = 0;
/* workaround bogus struct definition */
desc->u.hs.DeviceRemovable[0] = 0x02; /* port 1 */
desc->u.hs.DeviceRemovable[1] = 0xff;
}
break;
case GetHubStatus:
temp = 0;
*(__le32 *) buf = cpu_to_le32(temp);
break;
case GetPortStatus:
if (wIndex != 1)
goto error;
put_unaligned(cpu_to_le32(musb->port1_status
& ~MUSB_PORT_STAT_RESUME),
(__le32 *) buf);
/* port change status is more interesting */
musb_dbg(musb, "port status %08x", musb->port1_status);
break;
case SetPortFeature:
if ((wIndex & 0xff) != 1)
goto error;
switch (wValue) {
case USB_PORT_FEAT_POWER:
/* NOTE: this controller has a strange state machine
* that involves "requesting sessions" according to
* magic side effects from incompletely-described
* rules about startup...
*
* This call is what really starts the host mode; be
* very careful about side effects if you reorder any
* initialization logic, e.g. for OTG, or change any
* logic relating to VBUS power-up.
*/
if (!hcd->self.is_b_host && musb_has_gadget(musb))
start_musb = true;
break;
case USB_PORT_FEAT_RESET:
musb_port_reset(musb, true);
break;
case USB_PORT_FEAT_SUSPEND:
musb_port_suspend(musb, true);
break;
case USB_PORT_FEAT_TEST:
if (unlikely(is_host_active(musb)))
goto error;
wIndex >>= 8;
switch (wIndex) {
case USB_TEST_J:
pr_debug("USB_TEST_J\n");
temp = MUSB_TEST_J;
break;
case USB_TEST_K:
pr_debug("USB_TEST_K\n");
temp = MUSB_TEST_K;
break;
case USB_TEST_SE0_NAK:
pr_debug("USB_TEST_SE0_NAK\n");
temp = MUSB_TEST_SE0_NAK;
break;
case USB_TEST_PACKET:
pr_debug("USB_TEST_PACKET\n");
temp = MUSB_TEST_PACKET;
musb_load_testpacket(musb);
break;
case USB_TEST_FORCE_ENABLE:
pr_debug("USB_TEST_FORCE_ENABLE\n");
temp = MUSB_TEST_FORCE_HOST
| MUSB_TEST_FORCE_HS;
musb_writeb(musb->mregs, MUSB_DEVCTL,
MUSB_DEVCTL_SESSION);
break;
case 6:
pr_debug("TEST_FIFO_ACCESS\n");
temp = MUSB_TEST_FIFO_ACCESS;
break;
default:
goto error;
}
musb_writeb(musb->mregs, MUSB_TESTMODE, temp);
break;
default:
goto error;
}
musb_dbg(musb, "set feature %d", wValue);
musb->port1_status |= 1 << wValue;
break;
default:
error:
/* "protocol stall" on error */
retval = -EPIPE;
}
spin_unlock_irqrestore(&musb->lock, flags);
if (start_musb)
musb_start(musb);
return retval;
}
+455
View File
@@ -0,0 +1,455 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MUSB OTG driver - support for Mentor's DMA controller
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2007 by Texas Instruments
*/
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include "musb_core.h"
#include "musb_dma.h"
#define MUSB_HSDMA_CHANNEL_OFFSET(_bchannel, _offset) \
(MUSB_HSDMA_BASE + (_bchannel << 4) + _offset)
#define musb_read_hsdma_addr(mbase, bchannel) \
musb_readl(mbase, \
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDRESS))
#define musb_write_hsdma_addr(mbase, bchannel, addr) \
musb_writel(mbase, \
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDRESS), \
addr)
#define musb_read_hsdma_count(mbase, bchannel) \
musb_readl(mbase, \
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT))
#define musb_write_hsdma_count(mbase, bchannel, len) \
musb_writel(mbase, \
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT), \
len)
/* control register (16-bit): */
#define MUSB_HSDMA_ENABLE_SHIFT 0
#define MUSB_HSDMA_TRANSMIT_SHIFT 1
#define MUSB_HSDMA_MODE1_SHIFT 2
#define MUSB_HSDMA_IRQENABLE_SHIFT 3
#define MUSB_HSDMA_ENDPOINT_SHIFT 4
#define MUSB_HSDMA_BUSERROR_SHIFT 8
#define MUSB_HSDMA_BURSTMODE_SHIFT 9
#define MUSB_HSDMA_BURSTMODE (3 << MUSB_HSDMA_BURSTMODE_SHIFT)
#define MUSB_HSDMA_BURSTMODE_UNSPEC 0
#define MUSB_HSDMA_BURSTMODE_INCR4 1
#define MUSB_HSDMA_BURSTMODE_INCR8 2
#define MUSB_HSDMA_BURSTMODE_INCR16 3
#define MUSB_HSDMA_CHANNELS 8
struct musb_dma_controller;
struct musb_dma_channel {
struct dma_channel channel;
struct musb_dma_controller *controller;
u32 start_addr;
u32 len;
u16 max_packet_sz;
u8 idx;
u8 epnum;
u8 transmit;
};
struct musb_dma_controller {
struct dma_controller controller;
struct musb_dma_channel channel[MUSB_HSDMA_CHANNELS];
void *private_data;
void __iomem *base;
u8 channel_count;
u8 used_channels;
int irq;
};
static void dma_channel_release(struct dma_channel *channel);
static void dma_controller_stop(struct musb_dma_controller *controller)
{
struct musb *musb = controller->private_data;
struct dma_channel *channel;
u8 bit;
if (controller->used_channels != 0) {
dev_err(musb->controller,
"Stopping DMA controller while channel active\n");
for (bit = 0; bit < MUSB_HSDMA_CHANNELS; bit++) {
if (controller->used_channels & (1 << bit)) {
channel = &controller->channel[bit].channel;
dma_channel_release(channel);
if (!controller->used_channels)
break;
}
}
}
}
static struct dma_channel *dma_channel_allocate(struct dma_controller *c,
struct musb_hw_ep *hw_ep, u8 transmit)
{
struct musb_dma_controller *controller = container_of(c,
struct musb_dma_controller, controller);
struct musb_dma_channel *musb_channel = NULL;
struct dma_channel *channel = NULL;
u8 bit;
for (bit = 0; bit < MUSB_HSDMA_CHANNELS; bit++) {
if (!(controller->used_channels & (1 << bit))) {
controller->used_channels |= (1 << bit);
musb_channel = &(controller->channel[bit]);
musb_channel->controller = controller;
musb_channel->idx = bit;
musb_channel->epnum = hw_ep->epnum;
musb_channel->transmit = transmit;
channel = &(musb_channel->channel);
channel->private_data = musb_channel;
channel->status = MUSB_DMA_STATUS_FREE;
channel->max_len = 0x100000;
/* Tx => mode 1; Rx => mode 0 */
channel->desired_mode = transmit;
channel->actual_len = 0;
break;
}
}
return channel;
}
static void dma_channel_release(struct dma_channel *channel)
{
struct musb_dma_channel *musb_channel = channel->private_data;
channel->actual_len = 0;
musb_channel->start_addr = 0;
musb_channel->len = 0;
musb_channel->controller->used_channels &=
~(1 << musb_channel->idx);
channel->status = MUSB_DMA_STATUS_UNKNOWN;
}
static void configure_channel(struct dma_channel *channel,
u16 packet_sz, u8 mode,
dma_addr_t dma_addr, u32 len)
{
struct musb_dma_channel *musb_channel = channel->private_data;
struct musb_dma_controller *controller = musb_channel->controller;
struct musb *musb = controller->private_data;
void __iomem *mbase = controller->base;
u8 bchannel = musb_channel->idx;
u16 csr = 0;
musb_dbg(musb, "%p, pkt_sz %d, addr %pad, len %d, mode %d",
channel, packet_sz, &dma_addr, len, mode);
if (mode) {
csr |= 1 << MUSB_HSDMA_MODE1_SHIFT;
BUG_ON(len < packet_sz);
}
csr |= MUSB_HSDMA_BURSTMODE_INCR16
<< MUSB_HSDMA_BURSTMODE_SHIFT;
csr |= (musb_channel->epnum << MUSB_HSDMA_ENDPOINT_SHIFT)
| (1 << MUSB_HSDMA_ENABLE_SHIFT)
| (1 << MUSB_HSDMA_IRQENABLE_SHIFT)
| (musb_channel->transmit
? (1 << MUSB_HSDMA_TRANSMIT_SHIFT)
: 0);
/* address/count */
musb_write_hsdma_addr(mbase, bchannel, dma_addr);
musb_write_hsdma_count(mbase, bchannel, len);
/* control (this should start things) */
musb_writew(mbase,
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_CONTROL),
csr);
}
static int dma_channel_program(struct dma_channel *channel,
u16 packet_sz, u8 mode,
dma_addr_t dma_addr, u32 len)
{
struct musb_dma_channel *musb_channel = channel->private_data;
struct musb_dma_controller *controller = musb_channel->controller;
struct musb *musb = controller->private_data;
musb_dbg(musb, "ep%d-%s pkt_sz %d, dma_addr %pad length %d, mode %d",
musb_channel->epnum,
musb_channel->transmit ? "Tx" : "Rx",
packet_sz, &dma_addr, len, mode);
BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN ||
channel->status == MUSB_DMA_STATUS_BUSY);
/*
* The DMA engine in RTL1.8 and above cannot handle
* DMA addresses that are not aligned to a 4 byte boundary.
* It ends up masking the last two bits of the address
* programmed in DMA_ADDR.
*
* Fail such DMA transfers, so that the backup PIO mode
* can carry out the transfer
*/
if ((musb->hwvers >= MUSB_HWVERS_1800) && (dma_addr % 4))
return false;
channel->actual_len = 0;
musb_channel->start_addr = dma_addr;
musb_channel->len = len;
musb_channel->max_packet_sz = packet_sz;
channel->status = MUSB_DMA_STATUS_BUSY;
configure_channel(channel, packet_sz, mode, dma_addr, len);
return true;
}
static int dma_channel_abort(struct dma_channel *channel)
{
struct musb_dma_channel *musb_channel = channel->private_data;
void __iomem *mbase = musb_channel->controller->base;
struct musb *musb = musb_channel->controller->private_data;
u8 bchannel = musb_channel->idx;
int offset;
u16 csr;
if (channel->status == MUSB_DMA_STATUS_BUSY) {
if (musb_channel->transmit) {
offset = musb->io.ep_offset(musb_channel->epnum,
MUSB_TXCSR);
/*
* The programming guide says that we must clear
* the DMAENAB bit before the DMAMODE bit...
*/
csr = musb_readw(mbase, offset);
csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB);
musb_writew(mbase, offset, csr);
csr &= ~MUSB_TXCSR_DMAMODE;
musb_writew(mbase, offset, csr);
} else {
offset = musb->io.ep_offset(musb_channel->epnum,
MUSB_RXCSR);
csr = musb_readw(mbase, offset);
csr &= ~(MUSB_RXCSR_AUTOCLEAR |
MUSB_RXCSR_DMAENAB |
MUSB_RXCSR_DMAMODE);
musb_writew(mbase, offset, csr);
}
musb_writew(mbase,
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_CONTROL),
0);
musb_write_hsdma_addr(mbase, bchannel, 0);
musb_write_hsdma_count(mbase, bchannel, 0);
channel->status = MUSB_DMA_STATUS_FREE;
}
return 0;
}
irqreturn_t dma_controller_irq(int irq, void *private_data)
{
struct musb_dma_controller *controller = private_data;
struct musb *musb = controller->private_data;
struct musb_dma_channel *musb_channel;
struct dma_channel *channel;
void __iomem *mbase = controller->base;
irqreturn_t retval = IRQ_NONE;
unsigned long flags;
u8 bchannel;
u8 int_hsdma;
u32 addr, count;
u16 csr;
spin_lock_irqsave(&musb->lock, flags);
int_hsdma = musb_clearb(mbase, MUSB_HSDMA_INTR);
if (!int_hsdma) {
musb_dbg(musb, "spurious DMA irq");
for (bchannel = 0; bchannel < MUSB_HSDMA_CHANNELS; bchannel++) {
musb_channel = (struct musb_dma_channel *)
&(controller->channel[bchannel]);
channel = &musb_channel->channel;
if (channel->status == MUSB_DMA_STATUS_BUSY) {
count = musb_read_hsdma_count(mbase, bchannel);
if (count == 0)
int_hsdma |= (1 << bchannel);
}
}
musb_dbg(musb, "int_hsdma = 0x%x", int_hsdma);
if (!int_hsdma)
goto done;
}
for (bchannel = 0; bchannel < MUSB_HSDMA_CHANNELS; bchannel++) {
if (int_hsdma & (1 << bchannel)) {
musb_channel = (struct musb_dma_channel *)
&(controller->channel[bchannel]);
channel = &musb_channel->channel;
csr = musb_readw(mbase,
MUSB_HSDMA_CHANNEL_OFFSET(bchannel,
MUSB_HSDMA_CONTROL));
if (csr & (1 << MUSB_HSDMA_BUSERROR_SHIFT)) {
musb_channel->channel.status =
MUSB_DMA_STATUS_BUS_ABORT;
} else {
addr = musb_read_hsdma_addr(mbase,
bchannel);
channel->actual_len = addr
- musb_channel->start_addr;
musb_dbg(musb, "ch %p, 0x%x -> 0x%x (%zu / %d) %s",
channel, musb_channel->start_addr,
addr, channel->actual_len,
musb_channel->len,
(channel->actual_len
< musb_channel->len) ?
"=> reconfig 0" : "=> complete");
channel->status = MUSB_DMA_STATUS_FREE;
/* completed */
if (musb_channel->transmit &&
(!channel->desired_mode ||
(channel->actual_len %
musb_channel->max_packet_sz))) {
u8 epnum = musb_channel->epnum;
int offset = musb->io.ep_offset(epnum,
MUSB_TXCSR);
u16 txcsr;
/*
* The programming guide says that we
* must clear DMAENAB before DMAMODE.
*/
musb_ep_select(mbase, epnum);
txcsr = musb_readw(mbase, offset);
if (channel->desired_mode == 1) {
txcsr &= ~(MUSB_TXCSR_DMAENAB
| MUSB_TXCSR_AUTOSET);
musb_writew(mbase, offset, txcsr);
/* Send out the packet */
txcsr &= ~MUSB_TXCSR_DMAMODE;
txcsr |= MUSB_TXCSR_DMAENAB;
}
txcsr |= MUSB_TXCSR_TXPKTRDY;
musb_writew(mbase, offset, txcsr);
}
musb_dma_completion(musb, musb_channel->epnum,
musb_channel->transmit);
}
}
}
retval = IRQ_HANDLED;
done:
spin_unlock_irqrestore(&musb->lock, flags);
return retval;
}
EXPORT_SYMBOL_GPL(dma_controller_irq);
void musbhs_dma_controller_destroy(struct dma_controller *c)
{
struct musb_dma_controller *controller = container_of(c,
struct musb_dma_controller, controller);
dma_controller_stop(controller);
if (controller->irq)
free_irq(controller->irq, c);
kfree(controller);
}
EXPORT_SYMBOL_GPL(musbhs_dma_controller_destroy);
static struct musb_dma_controller *
dma_controller_alloc(struct musb *musb, void __iomem *base)
{
struct musb_dma_controller *controller;
controller = kzalloc_obj(*controller);
if (!controller)
return NULL;
controller->channel_count = MUSB_HSDMA_CHANNELS;
controller->private_data = musb;
controller->base = base;
controller->controller.channel_alloc = dma_channel_allocate;
controller->controller.channel_release = dma_channel_release;
controller->controller.channel_program = dma_channel_program;
controller->controller.channel_abort = dma_channel_abort;
return controller;
}
struct dma_controller *
musbhs_dma_controller_create(struct musb *musb, void __iomem *base)
{
struct musb_dma_controller *controller;
struct device *dev = musb->controller;
struct platform_device *pdev = to_platform_device(dev);
int irq = platform_get_irq_byname(pdev, "dma");
if (irq <= 0) {
dev_err(dev, "No DMA interrupt line!\n");
return NULL;
}
controller = dma_controller_alloc(musb, base);
if (!controller)
return NULL;
if (request_irq(irq, dma_controller_irq, 0,
dev_name(musb->controller), controller)) {
dev_err(dev, "request_irq %d failed!\n", irq);
musb_dma_controller_destroy(&controller->controller);
return NULL;
}
controller->irq = irq;
return &controller->controller;
}
EXPORT_SYMBOL_GPL(musbhs_dma_controller_create);
struct dma_controller *
musbhs_dma_controller_create_noirq(struct musb *musb, void __iomem *base)
{
struct musb_dma_controller *controller;
controller = dma_controller_alloc(musb, base);
if (!controller)
return NULL;
return &controller->controller;
}
EXPORT_SYMBOL_GPL(musbhs_dma_controller_create_noirq);
+622
View File
@@ -0,0 +1,622 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2005-2007 by Texas Instruments
* Some code has been taken from tusb6010.c
* Copyrights for that are attributable to:
* Copyright (C) 2006 Nokia Corporation
* Tony Lindgren <tony@atomide.com>
*
* This file is part of the Inventra Controller Driver for Linux.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/pm_runtime.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/usb/musb.h>
#include <linux/phy/omap_control_phy.h>
#include <linux/of_platform.h>
#include "musb_core.h"
#include "omap2430.h"
struct omap2430_glue {
struct device *dev;
struct platform_device *musb;
enum musb_vbus_id_status status;
struct work_struct omap_musb_mailbox_work;
struct device *control_otghs;
unsigned int is_runtime_suspended:1;
unsigned int needs_resume:1;
unsigned int phy_suspended:1;
};
#define glue_to_musb(g) platform_get_drvdata(g->musb)
static struct omap2430_glue *_glue;
static inline void omap2430_low_level_exit(struct musb *musb)
{
u32 l;
/* in any role */
l = musb_readl(musb->mregs, OTG_FORCESTDBY);
l |= ENABLEFORCE; /* enable MSTANDBY */
musb_writel(musb->mregs, OTG_FORCESTDBY, l);
}
static inline void omap2430_low_level_init(struct musb *musb)
{
u32 l;
l = musb_readl(musb->mregs, OTG_FORCESTDBY);
l &= ~ENABLEFORCE; /* disable MSTANDBY */
musb_writel(musb->mregs, OTG_FORCESTDBY, l);
}
static int omap2430_musb_mailbox(enum musb_vbus_id_status status)
{
struct omap2430_glue *glue = _glue;
if (!glue) {
pr_err("%s: musb core is not yet initialized\n", __func__);
return -EPROBE_DEFER;
}
glue->status = status;
if (!glue_to_musb(glue)) {
pr_err("%s: musb core is not yet ready\n", __func__);
return -EPROBE_DEFER;
}
schedule_work(&glue->omap_musb_mailbox_work);
return 0;
}
/*
* HDRC controls CPEN, but beware current surges during device connect.
* They can trigger transient overcurrent conditions that must be ignored.
*
* Note that we're skipping A_WAIT_VFALL -> A_IDLE and jumping right to B_IDLE
* as set by musb_set_peripheral().
*/
static void omap_musb_set_mailbox(struct omap2430_glue *glue)
{
struct musb *musb = glue_to_musb(glue);
int error;
pm_runtime_get_sync(musb->controller);
dev_dbg(musb->controller, "VBUS %s, devctl %02x\n",
usb_otg_state_string(musb->xceiv->otg->state),
musb_readb(musb->mregs, MUSB_DEVCTL));
switch (glue->status) {
case MUSB_ID_GROUND:
dev_dbg(musb->controller, "ID GND\n");
switch (musb->xceiv->otg->state) {
case OTG_STATE_A_IDLE:
error = musb_set_host(musb);
if (error)
break;
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE;
fallthrough;
case OTG_STATE_A_WAIT_VRISE:
case OTG_STATE_A_WAIT_BCON:
case OTG_STATE_A_HOST:
/*
* On multiple ID ground interrupts just keep enabling
* VBUS. At least cpcap VBUS shuts down otherwise.
*/
otg_set_vbus(musb->xceiv->otg, 1);
break;
default:
musb->xceiv->otg->state = OTG_STATE_A_IDLE;
musb->xceiv->last_event = USB_EVENT_ID;
if (musb->gadget_driver) {
omap_control_usb_set_mode(glue->control_otghs,
USB_MODE_HOST);
otg_set_vbus(musb->xceiv->otg, 1);
}
break;
}
break;
case MUSB_VBUS_VALID:
dev_dbg(musb->controller, "VBUS Connect\n");
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
musb->xceiv->last_event = USB_EVENT_VBUS;
omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DEVICE);
break;
case MUSB_ID_FLOAT:
case MUSB_VBUS_OFF:
dev_dbg(musb->controller, "VBUS Disconnect\n");
musb->xceiv->last_event = USB_EVENT_NONE;
musb_set_peripheral(musb);
otg_set_vbus(musb->xceiv->otg, 0);
omap_control_usb_set_mode(glue->control_otghs,
USB_MODE_DISCONNECT);
break;
default:
dev_dbg(musb->controller, "ID float\n");
}
pm_runtime_put_autosuspend(musb->controller);
atomic_notifier_call_chain(&musb->xceiv->notifier,
musb->xceiv->last_event, NULL);
}
static void omap_musb_mailbox_work(struct work_struct *mailbox_work)
{
struct omap2430_glue *glue = container_of(mailbox_work,
struct omap2430_glue, omap_musb_mailbox_work);
omap_musb_set_mailbox(glue);
}
static irqreturn_t omap2430_musb_interrupt(int irq, void *__hci)
{
unsigned long flags;
irqreturn_t retval = IRQ_NONE;
struct musb *musb = __hci;
spin_lock_irqsave(&musb->lock, flags);
musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
if (musb->int_usb || musb->int_tx || musb->int_rx)
retval = musb_interrupt(musb);
spin_unlock_irqrestore(&musb->lock, flags);
return retval;
}
static int omap2430_musb_init(struct musb *musb)
{
u32 l;
int status = 0;
struct device *dev = musb->controller;
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
struct omap_musb_board_data *data = plat->board_data;
/* We require some kind of external transceiver, hooked
* up through ULPI. TWL4030-family PMICs include one,
* which needs a driver, drivers aren't always needed.
*/
musb->phy = devm_phy_get(dev->parent, "usb2-phy");
/* We can't totally remove musb->xceiv as of now because
* musb core uses xceiv.state and xceiv.otg. Once we have
* a separate state machine to handle otg, these can be moved
* out of xceiv and then we can start using the generic PHY
* framework
*/
musb->xceiv = devm_usb_get_phy_by_phandle(dev->parent, "usb-phy", 0);
if (IS_ERR(musb->xceiv)) {
status = PTR_ERR(musb->xceiv);
if (status == -ENXIO)
return status;
dev_dbg(dev, "HS USB OTG: no transceiver configured\n");
return -EPROBE_DEFER;
}
if (IS_ERR(musb->phy)) {
dev_err(dev, "HS USB OTG: no PHY configured\n");
return PTR_ERR(musb->phy);
}
musb->isr = omap2430_musb_interrupt;
phy_init(musb->phy);
phy_power_on(musb->phy);
l = musb_readl(musb->mregs, OTG_INTERFSEL);
if (data->interface_type == MUSB_INTERFACE_UTMI) {
/* OMAP4 uses Internal PHY GS70 which uses UTMI interface */
l &= ~ULPI_12PIN; /* Disable ULPI */
l |= UTMI_8BIT; /* Enable UTMI */
} else {
l |= ULPI_12PIN;
}
musb_writel(musb->mregs, OTG_INTERFSEL, l);
dev_dbg(dev, "HS USB OTG: revision 0x%x, sysconfig 0x%02x, "
"sysstatus 0x%x, intrfsel 0x%x, simenable 0x%x\n",
musb_readl(musb->mregs, OTG_REVISION),
musb_readl(musb->mregs, OTG_SYSCONFIG),
musb_readl(musb->mregs, OTG_SYSSTATUS),
musb_readl(musb->mregs, OTG_INTERFSEL),
musb_readl(musb->mregs, OTG_SIMENABLE));
return 0;
}
static void omap2430_musb_enable(struct musb *musb)
{
struct device *dev = musb->controller;
struct omap2430_glue *glue = dev_get_drvdata(dev->parent);
if (glue->status == MUSB_UNKNOWN)
glue->status = MUSB_VBUS_OFF;
omap_musb_set_mailbox(glue);
}
static void omap2430_musb_disable(struct musb *musb)
{
struct device *dev = musb->controller;
struct omap2430_glue *glue = dev_get_drvdata(dev->parent);
if (glue->status != MUSB_UNKNOWN)
omap_control_usb_set_mode(glue->control_otghs,
USB_MODE_DISCONNECT);
}
static int omap2430_musb_exit(struct musb *musb)
{
struct device *dev = musb->controller;
struct omap2430_glue *glue = dev_get_drvdata(dev->parent);
omap2430_low_level_exit(musb);
phy_power_off(musb->phy);
phy_exit(musb->phy);
musb->phy = NULL;
cancel_work_sync(&glue->omap_musb_mailbox_work);
return 0;
}
static const struct musb_platform_ops omap2430_ops = {
.quirks = MUSB_DMA_INVENTRA,
#ifdef CONFIG_USB_INVENTRA_DMA
.dma_init = musbhs_dma_controller_create,
.dma_exit = musbhs_dma_controller_destroy,
#endif
.init = omap2430_musb_init,
.exit = omap2430_musb_exit,
.enable = omap2430_musb_enable,
.disable = omap2430_musb_disable,
.phy_callback = omap2430_musb_mailbox,
};
static u64 omap2430_dmamask = DMA_BIT_MASK(32);
static int omap2430_probe(struct platform_device *pdev)
{
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct omap_musb_board_data *data;
struct platform_device *musb;
struct omap2430_glue *glue;
struct device_node *np = pdev->dev.of_node;
struct musb_hdrc_config *config;
struct device_node *control_node;
struct platform_device *control_pdev;
int ret = -ENOMEM, val;
bool populate_irqs = false;
if (!np)
return -ENODEV;
glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);
if (!glue)
return -ENOMEM;
musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO);
if (!musb)
return -ENOMEM;
musb->dev.parent = &pdev->dev;
musb->dev.dma_mask = &omap2430_dmamask;
musb->dev.coherent_dma_mask = omap2430_dmamask;
/*
* Legacy SoCs using omap_device get confused if node is moved
* because of interconnect properties mixed into the node.
*/
if (of_property_present(np, "ti,hwmods")) {
dev_warn(&pdev->dev, "please update to probe with ti-sysc\n");
populate_irqs = true;
} else {
device_set_of_node_from_dev(&musb->dev, &pdev->dev);
}
glue->dev = &pdev->dev;
glue->musb = musb;
glue->status = MUSB_UNKNOWN;
glue->control_otghs = ERR_PTR(-ENODEV);
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
goto err_put_musb;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
goto err_put_musb;
config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL);
if (!config)
goto err_put_musb;
of_property_read_u32(np, "mode", (u32 *)&pdata->mode);
of_property_read_u32(np, "interface-type",
(u32 *)&data->interface_type);
of_property_read_u32(np, "num-eps", (u32 *)&config->num_eps);
of_property_read_u32(np, "ram-bits", (u32 *)&config->ram_bits);
of_property_read_u32(np, "power", (u32 *)&pdata->power);
ret = of_property_read_u32(np, "multipoint", &val);
if (!ret && val)
config->multipoint = true;
pdata->board_data = data;
pdata->config = config;
control_node = of_parse_phandle(np, "ctrl-module", 0);
if (control_node) {
control_pdev = of_find_device_by_node(control_node);
of_node_put(control_node);
if (!control_pdev) {
dev_err(&pdev->dev, "Failed to get control device\n");
ret = -EINVAL;
goto err_put_musb;
}
glue->control_otghs = &control_pdev->dev;
}
pdata->platform_ops = &omap2430_ops;
platform_set_drvdata(pdev, glue);
/*
* REVISIT if we ever have two instances of the wrapper, we will be
* in big trouble
*/
_glue = glue;
INIT_WORK(&glue->omap_musb_mailbox_work, omap_musb_mailbox_work);
ret = platform_device_add_resources(musb, pdev->resource, pdev->num_resources);
if (ret) {
dev_err(&pdev->dev, "failed to add resources\n");
goto err_put_control_otghs;
}
if (populate_irqs) {
struct resource musb_res[3];
struct resource *res;
int i = 0;
memset(musb_res, 0, sizeof(*musb_res) * ARRAY_SIZE(musb_res));
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
ret = -EINVAL;
goto err_put_control_otghs;
}
musb_res[i].start = res->start;
musb_res[i].end = res->end;
musb_res[i].flags = res->flags;
musb_res[i].name = res->name;
i++;
ret = of_irq_get_byname(np, "mc");
if (ret > 0) {
musb_res[i].start = ret;
musb_res[i].flags = IORESOURCE_IRQ;
musb_res[i].name = "mc";
i++;
}
ret = of_irq_get_byname(np, "dma");
if (ret > 0) {
musb_res[i].start = ret;
musb_res[i].flags = IORESOURCE_IRQ;
musb_res[i].name = "dma";
i++;
}
ret = platform_device_add_resources(musb, musb_res, i);
if (ret) {
dev_err(&pdev->dev, "failed to add IRQ resources\n");
goto err_put_control_otghs;
}
}
ret = platform_device_add_data(musb, pdata, sizeof(*pdata));
if (ret) {
dev_err(&pdev->dev, "failed to add platform_data\n");
goto err_put_control_otghs;
}
pm_runtime_enable(glue->dev);
ret = platform_device_add(musb);
if (ret) {
dev_err(&pdev->dev, "failed to register musb device\n");
goto err_disable_rpm;
}
return 0;
err_disable_rpm:
pm_runtime_disable(glue->dev);
err_put_control_otghs:
if (!IS_ERR(glue->control_otghs))
put_device(glue->control_otghs);
err_put_musb:
platform_device_put(musb);
return ret;
}
static void omap2430_remove(struct platform_device *pdev)
{
struct omap2430_glue *glue = platform_get_drvdata(pdev);
platform_device_unregister(glue->musb);
pm_runtime_disable(glue->dev);
if (!IS_ERR(glue->control_otghs))
put_device(glue->control_otghs);
}
#ifdef CONFIG_PM
static int omap2430_runtime_suspend(struct device *dev)
{
struct omap2430_glue *glue = dev_get_drvdata(dev);
struct musb *musb = glue_to_musb(glue);
if (!musb)
return 0;
musb->context.otg_interfsel = musb_readl(musb->mregs,
OTG_INTERFSEL);
omap2430_low_level_exit(musb);
if (!glue->phy_suspended) {
phy_power_off(musb->phy);
phy_exit(musb->phy);
}
glue->is_runtime_suspended = 1;
return 0;
}
static int omap2430_runtime_resume(struct device *dev)
{
struct omap2430_glue *glue = dev_get_drvdata(dev);
struct musb *musb = glue_to_musb(glue);
if (!musb)
return 0;
if (!glue->phy_suspended) {
phy_init(musb->phy);
phy_power_on(musb->phy);
}
omap2430_low_level_init(musb);
musb_writel(musb->mregs, OTG_INTERFSEL,
musb->context.otg_interfsel);
/* Wait for musb to get oriented. Otherwise we can get babble */
usleep_range(200000, 250000);
glue->is_runtime_suspended = 0;
return 0;
}
/* I2C and SPI PHYs need to be suspended before the glue layer */
static int omap2430_suspend(struct device *dev)
{
struct omap2430_glue *glue = dev_get_drvdata(dev);
struct musb *musb = glue_to_musb(glue);
phy_power_off(musb->phy);
phy_exit(musb->phy);
glue->phy_suspended = 1;
return 0;
}
/* Glue layer needs to be suspended after musb_suspend() */
static int omap2430_suspend_late(struct device *dev)
{
struct omap2430_glue *glue = dev_get_drvdata(dev);
if (glue->is_runtime_suspended)
return 0;
glue->needs_resume = 1;
return omap2430_runtime_suspend(dev);
}
static int omap2430_resume_early(struct device *dev)
{
struct omap2430_glue *glue = dev_get_drvdata(dev);
if (!glue->needs_resume)
return 0;
glue->needs_resume = 0;
return omap2430_runtime_resume(dev);
}
static int omap2430_resume(struct device *dev)
{
struct omap2430_glue *glue = dev_get_drvdata(dev);
struct musb *musb = glue_to_musb(glue);
phy_init(musb->phy);
phy_power_on(musb->phy);
glue->phy_suspended = 0;
return 0;
}
static const struct dev_pm_ops omap2430_pm_ops = {
.runtime_suspend = omap2430_runtime_suspend,
.runtime_resume = omap2430_runtime_resume,
.suspend = omap2430_suspend,
.suspend_late = omap2430_suspend_late,
.resume_early = omap2430_resume_early,
.resume = omap2430_resume,
};
#define DEV_PM_OPS (&omap2430_pm_ops)
#else
#define DEV_PM_OPS NULL
#endif
#ifdef CONFIG_OF
static const struct of_device_id omap2430_id_table[] = {
{
.compatible = "ti,omap4-musb"
},
{
.compatible = "ti,omap3-musb"
},
{},
};
MODULE_DEVICE_TABLE(of, omap2430_id_table);
#endif
static struct platform_driver omap2430_driver = {
.probe = omap2430_probe,
.remove = omap2430_remove,
.driver = {
.name = "musb-omap2430",
.pm = DEV_PM_OPS,
.of_match_table = of_match_ptr(omap2430_id_table),
},
};
module_platform_driver(omap2430_driver);
MODULE_DESCRIPTION("OMAP2PLUS MUSB Glue Layer");
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
MODULE_LICENSE("GPL v2");
+48
View File
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2005-2006 by Texas Instruments
*/
#ifndef __MUSB_OMAP243X_H__
#define __MUSB_OMAP243X_H__
#include <linux/platform_data/usb-omap.h>
/*
* OMAP2430-specific definitions
*/
#define OTG_REVISION 0x400
#define OTG_SYSCONFIG 0x404
# define MIDLEMODE 12 /* bit position */
# define FORCESTDBY (0 << MIDLEMODE)
# define NOSTDBY (1 << MIDLEMODE)
# define SMARTSTDBY (2 << MIDLEMODE)
# define SIDLEMODE 3 /* bit position */
# define FORCEIDLE (0 << SIDLEMODE)
# define NOIDLE (1 << SIDLEMODE)
# define SMARTIDLE (2 << SIDLEMODE)
# define ENABLEWAKEUP (1 << 2)
# define SOFTRST (1 << 1)
# define AUTOIDLE (1 << 0)
#define OTG_SYSSTATUS 0x408
# define RESETDONE (1 << 0)
#define OTG_INTERFSEL 0x40c
# define EXTCP (1 << 2)
# define PHYSEL 0 /* bit position */
# define UTMI_8BIT (0 << PHYSEL)
# define ULPI_12PIN (1 << PHYSEL)
# define ULPI_8PIN (2 << PHYSEL)
#define OTG_SIMENABLE 0x410
# define TM1 (1 << 0)
#define OTG_FORCESTDBY 0x414
# define ENABLEFORCE (1 << 0)
#endif /* __MUSB_OMAP243X_H__ */
+870
View File
@@ -0,0 +1,870 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Allwinner sun4i MUSB Glue Layer
*
* Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com>
*
* Based on code from
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/extcon.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy-sun4i-usb.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/soc/sunxi/sunxi_sram.h>
#include <linux/usb/musb.h>
#include <linux/usb/of.h>
#include <linux/usb/usb_phy_generic.h>
#include <linux/workqueue.h>
#include "musb_core.h"
/*
* Register offsets, note sunxi musb has a different layout then most
* musb implementations, we translate the layout in musb_readb & friends.
*/
#define SUNXI_MUSB_POWER 0x0040
#define SUNXI_MUSB_DEVCTL 0x0041
#define SUNXI_MUSB_INDEX 0x0042
#define SUNXI_MUSB_VEND0 0x0043
#define SUNXI_MUSB_INTRTX 0x0044
#define SUNXI_MUSB_INTRRX 0x0046
#define SUNXI_MUSB_INTRTXE 0x0048
#define SUNXI_MUSB_INTRRXE 0x004a
#define SUNXI_MUSB_INTRUSB 0x004c
#define SUNXI_MUSB_INTRUSBE 0x0050
#define SUNXI_MUSB_FRAME 0x0054
#define SUNXI_MUSB_TXFIFOSZ 0x0090
#define SUNXI_MUSB_TXFIFOADD 0x0092
#define SUNXI_MUSB_RXFIFOSZ 0x0094
#define SUNXI_MUSB_RXFIFOADD 0x0096
#define SUNXI_MUSB_FADDR 0x0098
#define SUNXI_MUSB_TXFUNCADDR 0x0098
#define SUNXI_MUSB_TXHUBADDR 0x009a
#define SUNXI_MUSB_TXHUBPORT 0x009b
#define SUNXI_MUSB_RXFUNCADDR 0x009c
#define SUNXI_MUSB_RXHUBADDR 0x009e
#define SUNXI_MUSB_RXHUBPORT 0x009f
#define SUNXI_MUSB_CONFIGDATA 0x00c0
/* VEND0 bits */
#define SUNXI_MUSB_VEND0_PIO_MODE 0
/* flags */
#define SUNXI_MUSB_FL_ENABLED 0
#define SUNXI_MUSB_FL_HOSTMODE 1
#define SUNXI_MUSB_FL_HOSTMODE_PEND 2
#define SUNXI_MUSB_FL_VBUS_ON 3
#define SUNXI_MUSB_FL_PHY_ON 4
#define SUNXI_MUSB_FL_HAS_SRAM 5
#define SUNXI_MUSB_FL_HAS_RESET 6
#define SUNXI_MUSB_FL_NO_CONFIGDATA 7
#define SUNXI_MUSB_FL_PHY_MODE_PEND 8
struct sunxi_musb_cfg {
const struct musb_hdrc_config *hdrc_config;
bool has_sram;
bool has_reset;
bool no_configdata;
};
/* Our read/write methods need access and do not get passed in a musb ref :| */
static struct musb *sunxi_musb;
struct sunxi_glue {
struct device *dev;
struct musb *musb;
struct platform_device *musb_pdev;
struct clk *clk;
struct reset_control *rst;
struct phy *phy;
struct platform_device *usb_phy;
struct usb_phy *xceiv;
enum phy_mode phy_mode;
unsigned long flags;
struct work_struct work;
struct extcon_dev *extcon;
struct notifier_block host_nb;
};
/* phy_power_on / off may sleep, so we use a workqueue */
static void sunxi_musb_work(struct work_struct *work)
{
struct sunxi_glue *glue = container_of(work, struct sunxi_glue, work);
bool vbus_on, phy_on;
if (!test_bit(SUNXI_MUSB_FL_ENABLED, &glue->flags))
return;
if (test_and_clear_bit(SUNXI_MUSB_FL_HOSTMODE_PEND, &glue->flags)) {
struct musb *musb = glue->musb;
unsigned long flags;
u8 devctl;
spin_lock_irqsave(&musb->lock, flags);
devctl = readb(musb->mregs + SUNXI_MUSB_DEVCTL);
if (test_bit(SUNXI_MUSB_FL_HOSTMODE, &glue->flags)) {
set_bit(SUNXI_MUSB_FL_VBUS_ON, &glue->flags);
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE;
MUSB_HST_MODE(musb);
devctl |= MUSB_DEVCTL_SESSION;
} else {
clear_bit(SUNXI_MUSB_FL_VBUS_ON, &glue->flags);
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
MUSB_DEV_MODE(musb);
devctl &= ~MUSB_DEVCTL_SESSION;
}
writeb(devctl, musb->mregs + SUNXI_MUSB_DEVCTL);
spin_unlock_irqrestore(&musb->lock, flags);
}
vbus_on = test_bit(SUNXI_MUSB_FL_VBUS_ON, &glue->flags);
phy_on = test_bit(SUNXI_MUSB_FL_PHY_ON, &glue->flags);
if (phy_on != vbus_on) {
if (vbus_on) {
phy_power_on(glue->phy);
set_bit(SUNXI_MUSB_FL_PHY_ON, &glue->flags);
} else {
phy_power_off(glue->phy);
clear_bit(SUNXI_MUSB_FL_PHY_ON, &glue->flags);
}
}
if (test_and_clear_bit(SUNXI_MUSB_FL_PHY_MODE_PEND, &glue->flags))
phy_set_mode(glue->phy, glue->phy_mode);
}
static void sunxi_musb_set_vbus(struct musb *musb, int is_on)
{
struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent);
if (is_on) {
set_bit(SUNXI_MUSB_FL_VBUS_ON, &glue->flags);
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE;
} else {
clear_bit(SUNXI_MUSB_FL_VBUS_ON, &glue->flags);
}
schedule_work(&glue->work);
}
static void sunxi_musb_pre_root_reset_end(struct musb *musb)
{
struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent);
sun4i_usb_phy_set_squelch_detect(glue->phy, false);
}
static void sunxi_musb_post_root_reset_end(struct musb *musb)
{
struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent);
sun4i_usb_phy_set_squelch_detect(glue->phy, true);
}
static irqreturn_t sunxi_musb_interrupt(int irq, void *__hci)
{
struct musb *musb = __hci;
unsigned long flags;
spin_lock_irqsave(&musb->lock, flags);
musb->int_usb = readb(musb->mregs + SUNXI_MUSB_INTRUSB);
if (musb->int_usb)
writeb(musb->int_usb, musb->mregs + SUNXI_MUSB_INTRUSB);
if ((musb->int_usb & MUSB_INTR_RESET) && !is_host_active(musb)) {
/* ep0 FADDR must be 0 when (re)entering peripheral mode */
musb_ep_select(musb->mregs, 0);
musb_writeb(musb->mregs, MUSB_FADDR, 0);
}
musb->int_tx = readw(musb->mregs + SUNXI_MUSB_INTRTX);
if (musb->int_tx)
writew(musb->int_tx, musb->mregs + SUNXI_MUSB_INTRTX);
musb->int_rx = readw(musb->mregs + SUNXI_MUSB_INTRRX);
if (musb->int_rx)
writew(musb->int_rx, musb->mregs + SUNXI_MUSB_INTRRX);
musb_interrupt(musb);
spin_unlock_irqrestore(&musb->lock, flags);
return IRQ_HANDLED;
}
static int sunxi_musb_host_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct sunxi_glue *glue = container_of(nb, struct sunxi_glue, host_nb);
if (event)
set_bit(SUNXI_MUSB_FL_HOSTMODE, &glue->flags);
else
clear_bit(SUNXI_MUSB_FL_HOSTMODE, &glue->flags);
set_bit(SUNXI_MUSB_FL_HOSTMODE_PEND, &glue->flags);
schedule_work(&glue->work);
return NOTIFY_DONE;
}
static int sunxi_musb_init(struct musb *musb)
{
struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent);
int ret;
sunxi_musb = musb;
musb->phy = glue->phy;
musb->xceiv = glue->xceiv;
if (test_bit(SUNXI_MUSB_FL_HAS_SRAM, &glue->flags)) {
ret = sunxi_sram_claim(musb->controller->parent);
if (ret)
return ret;
}
ret = clk_prepare_enable(glue->clk);
if (ret)
goto error_sram_release;
if (test_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags)) {
ret = reset_control_deassert(glue->rst);
if (ret)
goto error_clk_disable;
}
writeb(SUNXI_MUSB_VEND0_PIO_MODE, musb->mregs + SUNXI_MUSB_VEND0);
/* Register notifier before calling phy_init() */
ret = devm_extcon_register_notifier(glue->dev, glue->extcon,
EXTCON_USB_HOST, &glue->host_nb);
if (ret)
goto error_reset_assert;
ret = phy_init(glue->phy);
if (ret)
goto error_reset_assert;
musb->isr = sunxi_musb_interrupt;
/* Stop the musb-core from doing runtime pm (not supported on sunxi) */
pm_runtime_get(musb->controller);
return 0;
error_reset_assert:
if (test_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags))
reset_control_assert(glue->rst);
error_clk_disable:
clk_disable_unprepare(glue->clk);
error_sram_release:
if (test_bit(SUNXI_MUSB_FL_HAS_SRAM, &glue->flags))
sunxi_sram_release(musb->controller->parent);
return ret;
}
static int sunxi_musb_exit(struct musb *musb)
{
struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent);
pm_runtime_put(musb->controller);
cancel_work_sync(&glue->work);
if (test_bit(SUNXI_MUSB_FL_PHY_ON, &glue->flags))
phy_power_off(glue->phy);
phy_exit(glue->phy);
if (test_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags))
reset_control_assert(glue->rst);
clk_disable_unprepare(glue->clk);
if (test_bit(SUNXI_MUSB_FL_HAS_SRAM, &glue->flags))
sunxi_sram_release(musb->controller->parent);
return 0;
}
static void sunxi_musb_enable(struct musb *musb)
{
struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent);
glue->musb = musb;
/* musb_core does not call us in a balanced manner */
if (test_and_set_bit(SUNXI_MUSB_FL_ENABLED, &glue->flags))
return;
schedule_work(&glue->work);
}
static void sunxi_musb_disable(struct musb *musb)
{
struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent);
clear_bit(SUNXI_MUSB_FL_ENABLED, &glue->flags);
}
static struct dma_controller *
sunxi_musb_dma_controller_create(struct musb *musb, void __iomem *base)
{
return NULL;
}
static void sunxi_musb_dma_controller_destroy(struct dma_controller *c)
{
}
static int sunxi_musb_set_mode(struct musb *musb, u8 mode)
{
struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent);
enum phy_mode new_mode;
switch (mode) {
case MUSB_HOST:
new_mode = PHY_MODE_USB_HOST;
break;
case MUSB_PERIPHERAL:
new_mode = PHY_MODE_USB_DEVICE;
break;
case MUSB_OTG:
new_mode = PHY_MODE_USB_OTG;
break;
default:
dev_err(musb->controller->parent,
"Error requested mode not supported by this kernel\n");
return -EINVAL;
}
if (glue->phy_mode == new_mode)
return 0;
if (musb->port_mode != MUSB_OTG) {
dev_err(musb->controller->parent,
"Error changing modes is only supported in dual role mode\n");
return -EINVAL;
}
if (musb->port1_status & USB_PORT_STAT_ENABLE)
musb_root_disconnect(musb);
/*
* phy_set_mode may sleep, and we're called with a spinlock held,
* so let sunxi_musb_work deal with it.
*/
glue->phy_mode = new_mode;
set_bit(SUNXI_MUSB_FL_PHY_MODE_PEND, &glue->flags);
schedule_work(&glue->work);
return 0;
}
static int sunxi_musb_recover(struct musb *musb)
{
struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent);
/*
* Schedule a phy_set_mode with the current glue->phy_mode value,
* this will force end the current session.
*/
set_bit(SUNXI_MUSB_FL_PHY_MODE_PEND, &glue->flags);
schedule_work(&glue->work);
return 0;
}
/*
* sunxi musb register layout
* 0x00 - 0x17 fifo regs, 1 long per fifo
* 0x40 - 0x57 generic control regs (power - frame)
* 0x80 - 0x8f ep control regs (addressed through hw_ep->regs, indexed)
* 0x90 - 0x97 fifo control regs (indexed)
* 0x98 - 0x9f multipoint / busctl regs (indexed)
* 0xc0 configdata reg
*/
static u32 sunxi_musb_fifo_offset(u8 epnum)
{
return (epnum * 4);
}
static u32 sunxi_musb_ep_offset(u8 epnum, u16 offset)
{
WARN_ONCE(offset != 0,
"sunxi_musb_ep_offset called with non 0 offset\n");
return 0x80; /* indexed, so ignore epnum */
}
static u32 sunxi_musb_busctl_offset(u8 epnum, u16 offset)
{
return SUNXI_MUSB_TXFUNCADDR + offset;
}
static u8 sunxi_musb_readb(void __iomem *addr, u32 offset)
{
struct sunxi_glue *glue;
if (addr == sunxi_musb->mregs) {
/* generic control or fifo control reg access */
switch (offset) {
case MUSB_FADDR:
return readb(addr + SUNXI_MUSB_FADDR);
case MUSB_POWER:
return readb(addr + SUNXI_MUSB_POWER);
case MUSB_INTRUSB:
return readb(addr + SUNXI_MUSB_INTRUSB);
case MUSB_INTRUSBE:
return readb(addr + SUNXI_MUSB_INTRUSBE);
case MUSB_INDEX:
return readb(addr + SUNXI_MUSB_INDEX);
case MUSB_TESTMODE:
return 0; /* No testmode on sunxi */
case MUSB_DEVCTL:
return readb(addr + SUNXI_MUSB_DEVCTL);
case MUSB_TXFIFOSZ:
return readb(addr + SUNXI_MUSB_TXFIFOSZ);
case MUSB_RXFIFOSZ:
return readb(addr + SUNXI_MUSB_RXFIFOSZ);
case MUSB_CONFIGDATA + 0x10: /* See musb_read_configdata() */
glue = dev_get_drvdata(sunxi_musb->controller->parent);
/* A33 saves a reg, and we get to hardcode this */
if (test_bit(SUNXI_MUSB_FL_NO_CONFIGDATA,
&glue->flags))
return 0xde;
return readb(addr + SUNXI_MUSB_CONFIGDATA);
case MUSB_ULPI_BUSCONTROL:
dev_warn(sunxi_musb->controller->parent,
"sunxi-musb does not have ULPI bus control register\n");
return 0;
/* Offset for these is fixed by sunxi_musb_busctl_offset() */
case SUNXI_MUSB_TXFUNCADDR:
case SUNXI_MUSB_TXHUBADDR:
case SUNXI_MUSB_TXHUBPORT:
case SUNXI_MUSB_RXFUNCADDR:
case SUNXI_MUSB_RXHUBADDR:
case SUNXI_MUSB_RXHUBPORT:
/* multipoint / busctl reg access */
return readb(addr + offset);
default:
dev_err(sunxi_musb->controller->parent,
"Error unknown readb offset %u\n", offset);
return 0;
}
} else if (addr == (sunxi_musb->mregs + 0x80)) {
/* ep control reg access */
/* sunxi has a 2 byte hole before the txtype register */
if (offset >= MUSB_TXTYPE)
offset += 2;
return readb(addr + offset);
}
dev_err(sunxi_musb->controller->parent,
"Error unknown readb at 0x%x bytes offset\n",
(int)(addr - sunxi_musb->mregs));
return 0;
}
static void sunxi_musb_writeb(void __iomem *addr, unsigned offset, u8 data)
{
if (addr == sunxi_musb->mregs) {
/* generic control or fifo control reg access */
switch (offset) {
case MUSB_FADDR:
return writeb(data, addr + SUNXI_MUSB_FADDR);
case MUSB_POWER:
return writeb(data, addr + SUNXI_MUSB_POWER);
case MUSB_INTRUSB:
return writeb(data, addr + SUNXI_MUSB_INTRUSB);
case MUSB_INTRUSBE:
return writeb(data, addr + SUNXI_MUSB_INTRUSBE);
case MUSB_INDEX:
return writeb(data, addr + SUNXI_MUSB_INDEX);
case MUSB_TESTMODE:
if (data)
dev_warn(sunxi_musb->controller->parent,
"sunxi-musb does not have testmode\n");
return;
case MUSB_DEVCTL:
return writeb(data, addr + SUNXI_MUSB_DEVCTL);
case MUSB_TXFIFOSZ:
return writeb(data, addr + SUNXI_MUSB_TXFIFOSZ);
case MUSB_RXFIFOSZ:
return writeb(data, addr + SUNXI_MUSB_RXFIFOSZ);
case MUSB_ULPI_BUSCONTROL:
dev_warn(sunxi_musb->controller->parent,
"sunxi-musb does not have ULPI bus control register\n");
return;
/* Offset for these is fixed by sunxi_musb_busctl_offset() */
case SUNXI_MUSB_TXFUNCADDR:
case SUNXI_MUSB_TXHUBADDR:
case SUNXI_MUSB_TXHUBPORT:
case SUNXI_MUSB_RXFUNCADDR:
case SUNXI_MUSB_RXHUBADDR:
case SUNXI_MUSB_RXHUBPORT:
/* multipoint / busctl reg access */
return writeb(data, addr + offset);
default:
dev_err(sunxi_musb->controller->parent,
"Error unknown writeb offset %u\n", offset);
return;
}
} else if (addr == (sunxi_musb->mregs + 0x80)) {
/* ep control reg access */
if (offset >= MUSB_TXTYPE)
offset += 2;
return writeb(data, addr + offset);
}
dev_err(sunxi_musb->controller->parent,
"Error unknown writeb at 0x%x bytes offset\n",
(int)(addr - sunxi_musb->mregs));
}
static u16 sunxi_musb_readw(void __iomem *addr, u32 offset)
{
if (addr == sunxi_musb->mregs) {
/* generic control or fifo control reg access */
switch (offset) {
case MUSB_INTRTX:
return readw(addr + SUNXI_MUSB_INTRTX);
case MUSB_INTRRX:
return readw(addr + SUNXI_MUSB_INTRRX);
case MUSB_INTRTXE:
return readw(addr + SUNXI_MUSB_INTRTXE);
case MUSB_INTRRXE:
return readw(addr + SUNXI_MUSB_INTRRXE);
case MUSB_FRAME:
return readw(addr + SUNXI_MUSB_FRAME);
case MUSB_TXFIFOADD:
return readw(addr + SUNXI_MUSB_TXFIFOADD);
case MUSB_RXFIFOADD:
return readw(addr + SUNXI_MUSB_RXFIFOADD);
case MUSB_HWVERS:
return 0; /* sunxi musb version is not known */
default:
dev_err(sunxi_musb->controller->parent,
"Error unknown readw offset %u\n", offset);
return 0;
}
} else if (addr == (sunxi_musb->mregs + 0x80)) {
/* ep control reg access */
return readw(addr + offset);
}
dev_err(sunxi_musb->controller->parent,
"Error unknown readw at 0x%x bytes offset\n",
(int)(addr - sunxi_musb->mregs));
return 0;
}
static void sunxi_musb_writew(void __iomem *addr, unsigned offset, u16 data)
{
if (addr == sunxi_musb->mregs) {
/* generic control or fifo control reg access */
switch (offset) {
case MUSB_INTRTX:
return writew(data, addr + SUNXI_MUSB_INTRTX);
case MUSB_INTRRX:
return writew(data, addr + SUNXI_MUSB_INTRRX);
case MUSB_INTRTXE:
return writew(data, addr + SUNXI_MUSB_INTRTXE);
case MUSB_INTRRXE:
return writew(data, addr + SUNXI_MUSB_INTRRXE);
case MUSB_FRAME:
return writew(data, addr + SUNXI_MUSB_FRAME);
case MUSB_TXFIFOADD:
return writew(data, addr + SUNXI_MUSB_TXFIFOADD);
case MUSB_RXFIFOADD:
return writew(data, addr + SUNXI_MUSB_RXFIFOADD);
default:
dev_err(sunxi_musb->controller->parent,
"Error unknown writew offset %u\n", offset);
return;
}
} else if (addr == (sunxi_musb->mregs + 0x80)) {
/* ep control reg access */
return writew(data, addr + offset);
}
dev_err(sunxi_musb->controller->parent,
"Error unknown writew at 0x%x bytes offset\n",
(int)(addr - sunxi_musb->mregs));
}
static const struct musb_platform_ops sunxi_musb_ops = {
.quirks = MUSB_INDEXED_EP,
.init = sunxi_musb_init,
.exit = sunxi_musb_exit,
.enable = sunxi_musb_enable,
.disable = sunxi_musb_disable,
.fifo_offset = sunxi_musb_fifo_offset,
.ep_offset = sunxi_musb_ep_offset,
.busctl_offset = sunxi_musb_busctl_offset,
.readb = sunxi_musb_readb,
.writeb = sunxi_musb_writeb,
.readw = sunxi_musb_readw,
.writew = sunxi_musb_writew,
.dma_init = sunxi_musb_dma_controller_create,
.dma_exit = sunxi_musb_dma_controller_destroy,
.set_mode = sunxi_musb_set_mode,
.recover = sunxi_musb_recover,
.set_vbus = sunxi_musb_set_vbus,
.pre_root_reset_end = sunxi_musb_pre_root_reset_end,
.post_root_reset_end = sunxi_musb_post_root_reset_end,
};
#define SUNXI_MUSB_RAM_BITS 11
/* Allwinner OTG supports up to 5 endpoints */
static const struct musb_fifo_cfg sunxi_musb_mode_cfg_5eps[] = {
MUSB_EP_FIFO_SINGLE(1, FIFO_TX, 512),
MUSB_EP_FIFO_SINGLE(1, FIFO_RX, 512),
MUSB_EP_FIFO_SINGLE(2, FIFO_TX, 512),
MUSB_EP_FIFO_SINGLE(2, FIFO_RX, 512),
MUSB_EP_FIFO_SINGLE(3, FIFO_TX, 512),
MUSB_EP_FIFO_SINGLE(3, FIFO_RX, 512),
MUSB_EP_FIFO_SINGLE(4, FIFO_TX, 512),
MUSB_EP_FIFO_SINGLE(4, FIFO_RX, 512),
MUSB_EP_FIFO_SINGLE(5, FIFO_TX, 512),
MUSB_EP_FIFO_SINGLE(5, FIFO_RX, 512),
};
/* H3/V3s OTG supports only 4 endpoints */
static const struct musb_fifo_cfg sunxi_musb_mode_cfg_4eps[] = {
MUSB_EP_FIFO_SINGLE(1, FIFO_TX, 512),
MUSB_EP_FIFO_SINGLE(1, FIFO_RX, 512),
MUSB_EP_FIFO_SINGLE(2, FIFO_TX, 512),
MUSB_EP_FIFO_SINGLE(2, FIFO_RX, 512),
MUSB_EP_FIFO_SINGLE(3, FIFO_TX, 512),
MUSB_EP_FIFO_SINGLE(3, FIFO_RX, 512),
MUSB_EP_FIFO_SINGLE(4, FIFO_TX, 512),
MUSB_EP_FIFO_SINGLE(4, FIFO_RX, 512),
};
static const struct musb_hdrc_config sunxi_musb_hdrc_config_5eps = {
.fifo_cfg = sunxi_musb_mode_cfg_5eps,
.fifo_cfg_size = ARRAY_SIZE(sunxi_musb_mode_cfg_5eps),
.multipoint = true,
.dyn_fifo = true,
/* Two FIFOs per endpoint, plus ep_0. */
.num_eps = (ARRAY_SIZE(sunxi_musb_mode_cfg_5eps) / 2) + 1,
.ram_bits = SUNXI_MUSB_RAM_BITS,
};
static const struct musb_hdrc_config sunxi_musb_hdrc_config_4eps = {
.fifo_cfg = sunxi_musb_mode_cfg_4eps,
.fifo_cfg_size = ARRAY_SIZE(sunxi_musb_mode_cfg_4eps),
.multipoint = true,
.dyn_fifo = true,
/* Two FIFOs per endpoint, plus ep_0. */
.num_eps = (ARRAY_SIZE(sunxi_musb_mode_cfg_4eps) / 2) + 1,
.ram_bits = SUNXI_MUSB_RAM_BITS,
};
static int sunxi_musb_probe(struct platform_device *pdev)
{
struct musb_hdrc_platform_data pdata;
struct platform_device_info pinfo;
struct sunxi_glue *glue;
struct device_node *np = pdev->dev.of_node;
const struct sunxi_musb_cfg *cfg;
int ret;
if (!np) {
dev_err(&pdev->dev, "Error no device tree node found\n");
return -EINVAL;
}
glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);
if (!glue)
return -ENOMEM;
memset(&pdata, 0, sizeof(pdata));
switch (usb_get_dr_mode(&pdev->dev)) {
#if defined CONFIG_USB_MUSB_DUAL_ROLE || defined CONFIG_USB_MUSB_HOST
case USB_DR_MODE_HOST:
pdata.mode = MUSB_HOST;
glue->phy_mode = PHY_MODE_USB_HOST;
break;
#endif
#if defined CONFIG_USB_MUSB_DUAL_ROLE || defined CONFIG_USB_MUSB_GADGET
case USB_DR_MODE_PERIPHERAL:
pdata.mode = MUSB_PERIPHERAL;
glue->phy_mode = PHY_MODE_USB_DEVICE;
break;
#endif
#ifdef CONFIG_USB_MUSB_DUAL_ROLE
case USB_DR_MODE_OTG:
pdata.mode = MUSB_OTG;
glue->phy_mode = PHY_MODE_USB_OTG;
break;
#endif
default:
dev_err(&pdev->dev, "Invalid or missing 'dr_mode' property\n");
return -EINVAL;
}
pdata.platform_ops = &sunxi_musb_ops;
cfg = of_device_get_match_data(&pdev->dev);
if (!cfg)
return -EINVAL;
pdata.config = cfg->hdrc_config;
glue->dev = &pdev->dev;
INIT_WORK(&glue->work, sunxi_musb_work);
glue->host_nb.notifier_call = sunxi_musb_host_notifier;
if (cfg->has_sram)
set_bit(SUNXI_MUSB_FL_HAS_SRAM, &glue->flags);
if (cfg->has_reset)
set_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags);
if (cfg->no_configdata)
set_bit(SUNXI_MUSB_FL_NO_CONFIGDATA, &glue->flags);
glue->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(glue->clk)) {
dev_err(&pdev->dev, "Error getting clock: %ld\n",
PTR_ERR(glue->clk));
return PTR_ERR(glue->clk);
}
if (test_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags)) {
glue->rst = devm_reset_control_get(&pdev->dev, NULL);
if (IS_ERR(glue->rst))
return dev_err_probe(&pdev->dev, PTR_ERR(glue->rst),
"Error getting reset\n");
}
glue->extcon = extcon_get_edev_by_phandle(&pdev->dev, 0);
if (IS_ERR(glue->extcon))
return dev_err_probe(&pdev->dev, PTR_ERR(glue->extcon),
"Invalid or missing extcon\n");
glue->phy = devm_phy_get(&pdev->dev, "usb");
if (IS_ERR(glue->phy))
return dev_err_probe(&pdev->dev, PTR_ERR(glue->phy),
"Error getting phy\n");
glue->usb_phy = usb_phy_generic_register();
if (IS_ERR(glue->usb_phy)) {
dev_err(&pdev->dev, "Error registering usb-phy %ld\n",
PTR_ERR(glue->usb_phy));
return PTR_ERR(glue->usb_phy);
}
glue->xceiv = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);
if (IS_ERR(glue->xceiv)) {
ret = PTR_ERR(glue->xceiv);
dev_err(&pdev->dev, "Error getting usb-phy %d\n", ret);
goto err_unregister_usb_phy;
}
platform_set_drvdata(pdev, glue);
memset(&pinfo, 0, sizeof(pinfo));
pinfo.name = "musb-hdrc";
pinfo.id = PLATFORM_DEVID_AUTO;
pinfo.parent = &pdev->dev;
pinfo.fwnode = of_fwnode_handle(pdev->dev.of_node);
pinfo.of_node_reused = true;
pinfo.res = pdev->resource;
pinfo.num_res = pdev->num_resources;
pinfo.data = &pdata;
pinfo.size_data = sizeof(pdata);
glue->musb_pdev = platform_device_register_full(&pinfo);
if (IS_ERR(glue->musb_pdev)) {
ret = PTR_ERR(glue->musb_pdev);
dev_err(&pdev->dev, "Error registering musb dev: %d\n", ret);
goto err_unregister_usb_phy;
}
return 0;
err_unregister_usb_phy:
usb_phy_generic_unregister(glue->usb_phy);
return ret;
}
static void sunxi_musb_remove(struct platform_device *pdev)
{
struct sunxi_glue *glue = platform_get_drvdata(pdev);
struct platform_device *usb_phy = glue->usb_phy;
platform_device_unregister(glue->musb_pdev);
usb_phy_generic_unregister(usb_phy);
}
static const struct sunxi_musb_cfg sun4i_a10_musb_cfg = {
.hdrc_config = &sunxi_musb_hdrc_config_5eps,
.has_sram = true,
};
static const struct sunxi_musb_cfg sun6i_a31_musb_cfg = {
.hdrc_config = &sunxi_musb_hdrc_config_5eps,
.has_reset = true,
};
static const struct sunxi_musb_cfg sun8i_a33_musb_cfg = {
.hdrc_config = &sunxi_musb_hdrc_config_5eps,
.has_reset = true,
.no_configdata = true,
};
static const struct sunxi_musb_cfg sun8i_h3_musb_cfg = {
.hdrc_config = &sunxi_musb_hdrc_config_4eps,
.has_reset = true,
.no_configdata = true,
};
static const struct sunxi_musb_cfg suniv_f1c100s_musb_cfg = {
.hdrc_config = &sunxi_musb_hdrc_config_5eps,
.has_sram = true,
.has_reset = true,
.no_configdata = true,
};
static const struct of_device_id sunxi_musb_match[] = {
{ .compatible = "allwinner,sun4i-a10-musb",
.data = &sun4i_a10_musb_cfg, },
{ .compatible = "allwinner,sun6i-a31-musb",
.data = &sun6i_a31_musb_cfg, },
{ .compatible = "allwinner,sun8i-a33-musb",
.data = &sun8i_a33_musb_cfg, },
{ .compatible = "allwinner,sun8i-h3-musb",
.data = &sun8i_h3_musb_cfg, },
{ .compatible = "allwinner,suniv-f1c100s-musb",
.data = &suniv_f1c100s_musb_cfg, },
{}
};
MODULE_DEVICE_TABLE(of, sunxi_musb_match);
static struct platform_driver sunxi_musb_driver = {
.probe = sunxi_musb_probe,
.remove = sunxi_musb_remove,
.driver = {
.name = "musb-sunxi",
.of_match_table = sunxi_musb_match,
},
};
module_platform_driver(sunxi_musb_driver);
MODULE_DESCRIPTION("Allwinner sunxi MUSB Glue Layer");
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff
+215
View File
@@ -0,0 +1,215 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Definitions for TUSB6010 USB 2.0 OTG Dual Role controller
*
* Copyright (C) 2006 Nokia Corporation
* Tony Lindgren <tony@atomide.com>
*/
#ifndef __TUSB6010_H__
#define __TUSB6010_H__
/* VLYNQ control register. 32-bit at offset 0x000 */
#define TUSB_VLYNQ_CTRL 0x004
/* Mentor Graphics OTG core registers. 8,- 16- and 32-bit at offset 0x400 */
#define TUSB_BASE_OFFSET 0x400
/* FIFO registers 32-bit at offset 0x600 */
#define TUSB_FIFO_BASE 0x600
/* Device System & Control registers. 32-bit at offset 0x800 */
#define TUSB_SYS_REG_BASE 0x800
#define TUSB_DEV_CONF (TUSB_SYS_REG_BASE + 0x000)
#define TUSB_DEV_CONF_USB_HOST_MODE (1 << 16)
#define TUSB_DEV_CONF_PROD_TEST_MODE (1 << 15)
#define TUSB_DEV_CONF_SOFT_ID (1 << 1)
#define TUSB_DEV_CONF_ID_SEL (1 << 0)
#define TUSB_PHY_OTG_CTRL_ENABLE (TUSB_SYS_REG_BASE + 0x004)
#define TUSB_PHY_OTG_CTRL (TUSB_SYS_REG_BASE + 0x008)
#define TUSB_PHY_OTG_CTRL_WRPROTECT (0xa5 << 24)
#define TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP (1 << 23)
#define TUSB_PHY_OTG_CTRL_OTG_VBUS_DET_EN (1 << 19)
#define TUSB_PHY_OTG_CTRL_OTG_SESS_END_EN (1 << 18)
#define TUSB_PHY_OTG_CTRL_TESTM2 (1 << 17)
#define TUSB_PHY_OTG_CTRL_TESTM1 (1 << 16)
#define TUSB_PHY_OTG_CTRL_TESTM0 (1 << 15)
#define TUSB_PHY_OTG_CTRL_TX_DATA2 (1 << 14)
#define TUSB_PHY_OTG_CTRL_TX_GZ2 (1 << 13)
#define TUSB_PHY_OTG_CTRL_TX_ENABLE2 (1 << 12)
#define TUSB_PHY_OTG_CTRL_DM_PULLDOWN (1 << 11)
#define TUSB_PHY_OTG_CTRL_DP_PULLDOWN (1 << 10)
#define TUSB_PHY_OTG_CTRL_OSC_EN (1 << 9)
#define TUSB_PHY_OTG_CTRL_PHYREF_CLKSEL(v) (((v) & 3) << 7)
#define TUSB_PHY_OTG_CTRL_PD (1 << 6)
#define TUSB_PHY_OTG_CTRL_PLL_ON (1 << 5)
#define TUSB_PHY_OTG_CTRL_EXT_RPU (1 << 4)
#define TUSB_PHY_OTG_CTRL_PWR_GOOD (1 << 3)
#define TUSB_PHY_OTG_CTRL_RESET (1 << 2)
#define TUSB_PHY_OTG_CTRL_SUSPENDM (1 << 1)
#define TUSB_PHY_OTG_CTRL_CLK_MODE (1 << 0)
/*OTG status register */
#define TUSB_DEV_OTG_STAT (TUSB_SYS_REG_BASE + 0x00c)
#define TUSB_DEV_OTG_STAT_PWR_CLK_GOOD (1 << 8)
#define TUSB_DEV_OTG_STAT_SESS_END (1 << 7)
#define TUSB_DEV_OTG_STAT_SESS_VALID (1 << 6)
#define TUSB_DEV_OTG_STAT_VBUS_VALID (1 << 5)
#define TUSB_DEV_OTG_STAT_VBUS_SENSE (1 << 4)
#define TUSB_DEV_OTG_STAT_ID_STATUS (1 << 3)
#define TUSB_DEV_OTG_STAT_HOST_DISCON (1 << 2)
#define TUSB_DEV_OTG_STAT_LINE_STATE (3 << 0)
#define TUSB_DEV_OTG_STAT_DP_ENABLE (1 << 1)
#define TUSB_DEV_OTG_STAT_DM_ENABLE (1 << 0)
#define TUSB_DEV_OTG_TIMER (TUSB_SYS_REG_BASE + 0x010)
# define TUSB_DEV_OTG_TIMER_ENABLE (1 << 31)
# define TUSB_DEV_OTG_TIMER_VAL(v) ((v) & 0x07ffffff)
#define TUSB_PRCM_REV (TUSB_SYS_REG_BASE + 0x014)
/* PRCM configuration register */
#define TUSB_PRCM_CONF (TUSB_SYS_REG_BASE + 0x018)
#define TUSB_PRCM_CONF_SFW_CPEN (1 << 24)
#define TUSB_PRCM_CONF_SYS_CLKSEL(v) (((v) & 3) << 16)
/* PRCM management register */
#define TUSB_PRCM_MNGMT (TUSB_SYS_REG_BASE + 0x01c)
#define TUSB_PRCM_MNGMT_SRP_FIX_TIMER(v) (((v) & 0xf) << 25)
#define TUSB_PRCM_MNGMT_SRP_FIX_EN (1 << 24)
#define TUSB_PRCM_MNGMT_VBUS_VALID_TIMER(v) (((v) & 0xf) << 20)
#define TUSB_PRCM_MNGMT_VBUS_VALID_FLT_EN (1 << 19)
#define TUSB_PRCM_MNGMT_DFT_CLK_DIS (1 << 18)
#define TUSB_PRCM_MNGMT_VLYNQ_CLK_DIS (1 << 17)
#define TUSB_PRCM_MNGMT_OTG_SESS_END_EN (1 << 10)
#define TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN (1 << 9)
#define TUSB_PRCM_MNGMT_OTG_ID_PULLUP (1 << 8)
#define TUSB_PRCM_MNGMT_15_SW_EN (1 << 4)
#define TUSB_PRCM_MNGMT_33_SW_EN (1 << 3)
#define TUSB_PRCM_MNGMT_5V_CPEN (1 << 2)
#define TUSB_PRCM_MNGMT_PM_IDLE (1 << 1)
#define TUSB_PRCM_MNGMT_DEV_IDLE (1 << 0)
/* Wake-up source clear and mask registers */
#define TUSB_PRCM_WAKEUP_SOURCE (TUSB_SYS_REG_BASE + 0x020)
#define TUSB_PRCM_WAKEUP_CLEAR (TUSB_SYS_REG_BASE + 0x028)
#define TUSB_PRCM_WAKEUP_MASK (TUSB_SYS_REG_BASE + 0x02c)
#define TUSB_PRCM_WAKEUP_RESERVED_BITS (0xffffe << 13)
#define TUSB_PRCM_WGPIO_7 (1 << 12)
#define TUSB_PRCM_WGPIO_6 (1 << 11)
#define TUSB_PRCM_WGPIO_5 (1 << 10)
#define TUSB_PRCM_WGPIO_4 (1 << 9)
#define TUSB_PRCM_WGPIO_3 (1 << 8)
#define TUSB_PRCM_WGPIO_2 (1 << 7)
#define TUSB_PRCM_WGPIO_1 (1 << 6)
#define TUSB_PRCM_WGPIO_0 (1 << 5)
#define TUSB_PRCM_WHOSTDISCON (1 << 4) /* Host disconnect */
#define TUSB_PRCM_WBUS (1 << 3) /* USB bus resume */
#define TUSB_PRCM_WNORCS (1 << 2) /* NOR chip select */
#define TUSB_PRCM_WVBUS (1 << 1) /* OTG PHY VBUS */
#define TUSB_PRCM_WID (1 << 0) /* OTG PHY ID detect */
#define TUSB_PULLUP_1_CTRL (TUSB_SYS_REG_BASE + 0x030)
#define TUSB_PULLUP_2_CTRL (TUSB_SYS_REG_BASE + 0x034)
#define TUSB_INT_CTRL_REV (TUSB_SYS_REG_BASE + 0x038)
#define TUSB_INT_CTRL_CONF (TUSB_SYS_REG_BASE + 0x03c)
#define TUSB_USBIP_INT_SRC (TUSB_SYS_REG_BASE + 0x040)
#define TUSB_USBIP_INT_SET (TUSB_SYS_REG_BASE + 0x044)
#define TUSB_USBIP_INT_CLEAR (TUSB_SYS_REG_BASE + 0x048)
#define TUSB_USBIP_INT_MASK (TUSB_SYS_REG_BASE + 0x04c)
#define TUSB_DMA_INT_SRC (TUSB_SYS_REG_BASE + 0x050)
#define TUSB_DMA_INT_SET (TUSB_SYS_REG_BASE + 0x054)
#define TUSB_DMA_INT_CLEAR (TUSB_SYS_REG_BASE + 0x058)
#define TUSB_DMA_INT_MASK (TUSB_SYS_REG_BASE + 0x05c)
#define TUSB_GPIO_INT_SRC (TUSB_SYS_REG_BASE + 0x060)
#define TUSB_GPIO_INT_SET (TUSB_SYS_REG_BASE + 0x064)
#define TUSB_GPIO_INT_CLEAR (TUSB_SYS_REG_BASE + 0x068)
#define TUSB_GPIO_INT_MASK (TUSB_SYS_REG_BASE + 0x06c)
/* NOR flash interrupt source registers */
#define TUSB_INT_SRC (TUSB_SYS_REG_BASE + 0x070)
#define TUSB_INT_SRC_SET (TUSB_SYS_REG_BASE + 0x074)
#define TUSB_INT_SRC_CLEAR (TUSB_SYS_REG_BASE + 0x078)
#define TUSB_INT_MASK (TUSB_SYS_REG_BASE + 0x07c)
#define TUSB_INT_SRC_TXRX_DMA_DONE (1 << 24)
#define TUSB_INT_SRC_USB_IP_CORE (1 << 17)
#define TUSB_INT_SRC_OTG_TIMEOUT (1 << 16)
#define TUSB_INT_SRC_VBUS_SENSE_CHNG (1 << 15)
#define TUSB_INT_SRC_ID_STATUS_CHNG (1 << 14)
#define TUSB_INT_SRC_DEV_WAKEUP (1 << 13)
#define TUSB_INT_SRC_DEV_READY (1 << 12)
#define TUSB_INT_SRC_USB_IP_TX (1 << 9)
#define TUSB_INT_SRC_USB_IP_RX (1 << 8)
#define TUSB_INT_SRC_USB_IP_VBUS_ERR (1 << 7)
#define TUSB_INT_SRC_USB_IP_VBUS_REQ (1 << 6)
#define TUSB_INT_SRC_USB_IP_DISCON (1 << 5)
#define TUSB_INT_SRC_USB_IP_CONN (1 << 4)
#define TUSB_INT_SRC_USB_IP_SOF (1 << 3)
#define TUSB_INT_SRC_USB_IP_RST_BABBLE (1 << 2)
#define TUSB_INT_SRC_USB_IP_RESUME (1 << 1)
#define TUSB_INT_SRC_USB_IP_SUSPEND (1 << 0)
/* NOR flash interrupt registers reserved bits. Must be written as 0 */
#define TUSB_INT_MASK_RESERVED_17 (0x3fff << 17)
#define TUSB_INT_MASK_RESERVED_13 (1 << 13)
#define TUSB_INT_MASK_RESERVED_8 (0xf << 8)
#define TUSB_INT_SRC_RESERVED_26 (0x1f << 26)
#define TUSB_INT_SRC_RESERVED_18 (0x3f << 18)
#define TUSB_INT_SRC_RESERVED_10 (0x03 << 10)
/* Reserved bits for NOR flash interrupt mask and clear register */
#define TUSB_INT_MASK_RESERVED_BITS (TUSB_INT_MASK_RESERVED_17 | \
TUSB_INT_MASK_RESERVED_13 | \
TUSB_INT_MASK_RESERVED_8)
/* Reserved bits for NOR flash interrupt status register */
#define TUSB_INT_SRC_RESERVED_BITS (TUSB_INT_SRC_RESERVED_26 | \
TUSB_INT_SRC_RESERVED_18 | \
TUSB_INT_SRC_RESERVED_10)
#define TUSB_GPIO_REV (TUSB_SYS_REG_BASE + 0x080)
#define TUSB_GPIO_CONF (TUSB_SYS_REG_BASE + 0x084)
#define TUSB_DMA_CTRL_REV (TUSB_SYS_REG_BASE + 0x100)
#define TUSB_DMA_REQ_CONF (TUSB_SYS_REG_BASE + 0x104)
#define TUSB_EP0_CONF (TUSB_SYS_REG_BASE + 0x108)
#define TUSB_DMA_EP_MAP (TUSB_SYS_REG_BASE + 0x148)
/* Offsets from each ep base register */
#define TUSB_EP_TX_OFFSET 0x10c /* EP_IN in docs */
#define TUSB_EP_RX_OFFSET 0x14c /* EP_OUT in docs */
#define TUSB_EP_MAX_PACKET_SIZE_OFFSET 0x188
#define TUSB_WAIT_COUNT (TUSB_SYS_REG_BASE + 0x1c8)
#define TUSB_SCRATCH_PAD (TUSB_SYS_REG_BASE + 0x1c4)
#define TUSB_PROD_TEST_RESET (TUSB_SYS_REG_BASE + 0x1d8)
/* Device System & Control register bitfields */
#define TUSB_INT_CTRL_CONF_INT_RELCYC(v) (((v) & 0x7) << 18)
#define TUSB_INT_CTRL_CONF_INT_POLARITY (1 << 17)
#define TUSB_INT_CTRL_CONF_INT_MODE (1 << 16)
#define TUSB_GPIO_CONF_DMAREQ(v) (((v) & 0x3f) << 24)
#define TUSB_DMA_REQ_CONF_BURST_SIZE(v) (((v) & 3) << 26)
#define TUSB_DMA_REQ_CONF_DMA_REQ_EN(v) (((v) & 0x3f) << 20)
#define TUSB_DMA_REQ_CONF_DMA_REQ_ASSER(v) (((v) & 0xf) << 16)
#define TUSB_EP0_CONFIG_SW_EN (1 << 8)
#define TUSB_EP0_CONFIG_DIR_TX (1 << 7)
#define TUSB_EP0_CONFIG_XFR_SIZE(v) ((v) & 0x7f)
#define TUSB_EP_CONFIG_SW_EN (1 << 31)
#define TUSB_EP_CONFIG_XFR_SIZE(v) ((v) & 0x7fffffff)
#define TUSB_PROD_TEST_RESET_VAL 0xa596
#define TUSB_EP_FIFO(ep) (TUSB_FIFO_BASE + (ep) * 0x20)
#define TUSB_DIDR1_LO (TUSB_SYS_REG_BASE + 0x1f8)
#define TUSB_DIDR1_HI (TUSB_SYS_REG_BASE + 0x1fc)
#define TUSB_DIDR1_HI_CHIP_REV(v) (((v) >> 17) & 0xf)
#define TUSB_DIDR1_HI_REV_20 0
#define TUSB_DIDR1_HI_REV_30 1
#define TUSB_DIDR1_HI_REV_31 2
#define TUSB_REV_10 0x10
#define TUSB_REV_20 0x20
#define TUSB_REV_30 0x30
#define TUSB_REV_31 0x31
#endif /* __TUSB6010_H__ */
@@ -0,0 +1,642 @@
// SPDX-License-Identifier: GPL-2.0
/*
* TUSB6010 USB 2.0 OTG Dual Role controller OMAP DMA interface
*
* Copyright (C) 2006 Nokia Corporation
* Tony Lindgren <tony@atomide.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/usb.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/dmaengine.h>
#include "musb_core.h"
#include "tusb6010.h"
#define to_chdat(c) ((struct tusb_omap_dma_ch *)(c)->private_data)
#define MAX_DMAREQ 5 /* REVISIT: Really 6, but req5 not OK */
struct tusb_dma_data {
s8 dmareq;
struct dma_chan *chan;
};
struct tusb_omap_dma_ch {
struct musb *musb;
void __iomem *tbase;
unsigned long phys_offset;
int epnum;
u8 tx;
struct musb_hw_ep *hw_ep;
struct tusb_dma_data *dma_data;
struct tusb_omap_dma *tusb_dma;
dma_addr_t dma_addr;
u32 len;
u16 packet_sz;
u16 transfer_packet_sz;
u32 transfer_len;
u32 completed_len;
};
struct tusb_omap_dma {
struct dma_controller controller;
void __iomem *tbase;
struct tusb_dma_data dma_pool[MAX_DMAREQ];
unsigned multichannel:1;
};
/*
* Allocate dmareq0 to the current channel unless it's already taken
*/
static inline int tusb_omap_use_shared_dmareq(struct tusb_omap_dma_ch *chdat)
{
u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP);
if (reg != 0) {
dev_dbg(chdat->musb->controller, "ep%i dmareq0 is busy for ep%i\n",
chdat->epnum, reg & 0xf);
return -EAGAIN;
}
if (chdat->tx)
reg = (1 << 4) | chdat->epnum;
else
reg = chdat->epnum;
musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg);
return 0;
}
static inline void tusb_omap_free_shared_dmareq(struct tusb_omap_dma_ch *chdat)
{
u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP);
if ((reg & 0xf) != chdat->epnum) {
printk(KERN_ERR "ep%i trying to release dmareq0 for ep%i\n",
chdat->epnum, reg & 0xf);
return;
}
musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, 0);
}
/*
* See also musb_dma_completion in plat_uds.c and musb_g_[tx|rx]() in
* musb_gadget.c.
*/
static void tusb_omap_dma_cb(void *data)
{
struct dma_channel *channel = (struct dma_channel *)data;
struct tusb_omap_dma_ch *chdat = to_chdat(channel);
struct tusb_omap_dma *tusb_dma = chdat->tusb_dma;
struct musb *musb = chdat->musb;
struct device *dev = musb->controller;
struct musb_hw_ep *hw_ep = chdat->hw_ep;
void __iomem *ep_conf = hw_ep->conf;
void __iomem *mbase = musb->mregs;
unsigned long remaining, flags, pio;
spin_lock_irqsave(&musb->lock, flags);
dev_dbg(musb->controller, "ep%i %s dma callback\n",
chdat->epnum, chdat->tx ? "tx" : "rx");
if (chdat->tx)
remaining = musb_readl(ep_conf, TUSB_EP_TX_OFFSET);
else
remaining = musb_readl(ep_conf, TUSB_EP_RX_OFFSET);
remaining = TUSB_EP_CONFIG_XFR_SIZE(remaining);
/* HW issue #10: XFR_SIZE may get corrupt on DMA (both async & sync) */
if (unlikely(remaining > chdat->transfer_len)) {
dev_dbg(musb->controller, "Corrupt %s XFR_SIZE: 0x%08lx\n",
chdat->tx ? "tx" : "rx", remaining);
remaining = 0;
}
channel->actual_len = chdat->transfer_len - remaining;
pio = chdat->len - channel->actual_len;
dev_dbg(musb->controller, "DMA remaining %lu/%u\n", remaining, chdat->transfer_len);
/* Transfer remaining 1 - 31 bytes */
if (pio > 0 && pio < 32) {
u8 *buf;
dev_dbg(musb->controller, "Using PIO for remaining %lu bytes\n", pio);
buf = phys_to_virt((u32)chdat->dma_addr) + chdat->transfer_len;
if (chdat->tx) {
dma_unmap_single(dev, chdat->dma_addr,
chdat->transfer_len,
DMA_TO_DEVICE);
musb_write_fifo(hw_ep, pio, buf);
} else {
dma_unmap_single(dev, chdat->dma_addr,
chdat->transfer_len,
DMA_FROM_DEVICE);
musb_read_fifo(hw_ep, pio, buf);
}
channel->actual_len += pio;
}
if (!tusb_dma->multichannel)
tusb_omap_free_shared_dmareq(chdat);
channel->status = MUSB_DMA_STATUS_FREE;
musb_dma_completion(musb, chdat->epnum, chdat->tx);
/* We must terminate short tx transfers manually by setting TXPKTRDY.
* REVISIT: This same problem may occur with other MUSB dma as well.
* Easy to test with g_ether by pinging the MUSB board with ping -s54.
*/
if ((chdat->transfer_len < chdat->packet_sz)
|| (chdat->transfer_len % chdat->packet_sz != 0)) {
u16 csr;
if (chdat->tx) {
dev_dbg(musb->controller, "terminating short tx packet\n");
musb_ep_select(mbase, chdat->epnum);
csr = musb_readw(hw_ep->regs, MUSB_TXCSR);
csr |= MUSB_TXCSR_MODE | MUSB_TXCSR_TXPKTRDY
| MUSB_TXCSR_P_WZC_BITS;
musb_writew(hw_ep->regs, MUSB_TXCSR, csr);
}
}
spin_unlock_irqrestore(&musb->lock, flags);
}
static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz,
u8 rndis_mode, dma_addr_t dma_addr, u32 len)
{
struct tusb_omap_dma_ch *chdat = to_chdat(channel);
struct tusb_omap_dma *tusb_dma = chdat->tusb_dma;
struct musb *musb = chdat->musb;
struct device *dev = musb->controller;
struct musb_hw_ep *hw_ep = chdat->hw_ep;
void __iomem *mbase = musb->mregs;
void __iomem *ep_conf = hw_ep->conf;
dma_addr_t fifo_addr = hw_ep->fifo_sync;
u32 dma_remaining;
u16 csr;
u32 psize;
struct tusb_dma_data *dma_data;
struct dma_async_tx_descriptor *dma_desc;
struct dma_slave_config dma_cfg;
enum dma_transfer_direction dma_dir;
u32 port_window;
int ret;
if (unlikely(dma_addr & 0x1) || (len < 32) || (len > packet_sz))
return false;
/*
* HW issue #10: Async dma will eventually corrupt the XFR_SIZE
* register which will cause missed DMA interrupt. We could try to
* use a timer for the callback, but it is unsafe as the XFR_SIZE
* register is corrupt, and we won't know if the DMA worked.
*/
if (dma_addr & 0x2)
return false;
/*
* Because of HW issue #10, it seems like mixing sync DMA and async
* PIO access can confuse the DMA. Make sure XFR_SIZE is reset before
* using the channel for DMA.
*/
if (chdat->tx)
dma_remaining = musb_readl(ep_conf, TUSB_EP_TX_OFFSET);
else
dma_remaining = musb_readl(ep_conf, TUSB_EP_RX_OFFSET);
dma_remaining = TUSB_EP_CONFIG_XFR_SIZE(dma_remaining);
if (dma_remaining) {
dev_dbg(musb->controller, "Busy %s dma, not using: %08x\n",
chdat->tx ? "tx" : "rx", dma_remaining);
return false;
}
chdat->transfer_len = len & ~0x1f;
if (len < packet_sz)
chdat->transfer_packet_sz = chdat->transfer_len;
else
chdat->transfer_packet_sz = packet_sz;
dma_data = chdat->dma_data;
if (!tusb_dma->multichannel) {
if (tusb_omap_use_shared_dmareq(chdat) != 0) {
dev_dbg(musb->controller, "could not get dma for ep%i\n", chdat->epnum);
return false;
}
if (dma_data->dmareq < 0) {
/* REVISIT: This should get blocked earlier, happens
* with MSC ErrorRecoveryTest
*/
WARN_ON(1);
return false;
}
}
chdat->packet_sz = packet_sz;
chdat->len = len;
channel->actual_len = 0;
chdat->dma_addr = dma_addr;
channel->status = MUSB_DMA_STATUS_BUSY;
/* Since we're recycling dma areas, we need to clean or invalidate */
if (chdat->tx) {
dma_dir = DMA_MEM_TO_DEV;
dma_map_single(dev, phys_to_virt(dma_addr), len,
DMA_TO_DEVICE);
} else {
dma_dir = DMA_DEV_TO_MEM;
dma_map_single(dev, phys_to_virt(dma_addr), len,
DMA_FROM_DEVICE);
}
memset(&dma_cfg, 0, sizeof(dma_cfg));
/* Use 16-bit transfer if dma_addr is not 32-bit aligned */
if ((dma_addr & 0x3) == 0) {
dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
port_window = 8;
} else {
dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
port_window = 16;
fifo_addr = hw_ep->fifo_async;
}
dev_dbg(musb->controller,
"ep%i %s dma: %pad len: %u(%u) packet_sz: %i(%i)\n",
chdat->epnum, chdat->tx ? "tx" : "rx", &dma_addr,
chdat->transfer_len, len, chdat->transfer_packet_sz, packet_sz);
dma_cfg.src_addr = fifo_addr;
dma_cfg.dst_addr = fifo_addr;
dma_cfg.src_port_window_size = port_window;
dma_cfg.src_maxburst = port_window;
dma_cfg.dst_port_window_size = port_window;
dma_cfg.dst_maxburst = port_window;
ret = dmaengine_slave_config(dma_data->chan, &dma_cfg);
if (ret) {
dev_err(musb->controller, "DMA slave config failed: %d\n", ret);
return false;
}
dma_desc = dmaengine_prep_slave_single(dma_data->chan, dma_addr,
chdat->transfer_len, dma_dir,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!dma_desc) {
dev_err(musb->controller, "DMA prep_slave_single failed\n");
return false;
}
dma_desc->callback = tusb_omap_dma_cb;
dma_desc->callback_param = channel;
dmaengine_submit(dma_desc);
dev_dbg(musb->controller,
"ep%i %s using %i-bit %s dma from %pad to %pad\n",
chdat->epnum, chdat->tx ? "tx" : "rx",
dma_cfg.src_addr_width * 8,
((dma_addr & 0x3) == 0) ? "sync" : "async",
(dma_dir == DMA_MEM_TO_DEV) ? &dma_addr : &fifo_addr,
(dma_dir == DMA_MEM_TO_DEV) ? &fifo_addr : &dma_addr);
/*
* Prepare MUSB for DMA transfer
*/
musb_ep_select(mbase, chdat->epnum);
if (chdat->tx) {
csr = musb_readw(hw_ep->regs, MUSB_TXCSR);
csr |= (MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB
| MUSB_TXCSR_DMAMODE | MUSB_TXCSR_MODE);
csr &= ~MUSB_TXCSR_P_UNDERRUN;
musb_writew(hw_ep->regs, MUSB_TXCSR, csr);
} else {
csr = musb_readw(hw_ep->regs, MUSB_RXCSR);
csr |= MUSB_RXCSR_DMAENAB;
csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_DMAMODE);
musb_writew(hw_ep->regs, MUSB_RXCSR,
csr | MUSB_RXCSR_P_WZC_BITS);
}
/* Start DMA transfer */
dma_async_issue_pending(dma_data->chan);
if (chdat->tx) {
/* Send transfer_packet_sz packets at a time */
psize = musb_readl(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET);
psize &= ~0x7ff;
psize |= chdat->transfer_packet_sz;
musb_writel(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET, psize);
musb_writel(ep_conf, TUSB_EP_TX_OFFSET,
TUSB_EP_CONFIG_XFR_SIZE(chdat->transfer_len));
} else {
/* Receive transfer_packet_sz packets at a time */
psize = musb_readl(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET);
psize &= ~(0x7ff << 16);
psize |= (chdat->transfer_packet_sz << 16);
musb_writel(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET, psize);
musb_writel(ep_conf, TUSB_EP_RX_OFFSET,
TUSB_EP_CONFIG_XFR_SIZE(chdat->transfer_len));
}
return true;
}
static int tusb_omap_dma_abort(struct dma_channel *channel)
{
struct tusb_omap_dma_ch *chdat = to_chdat(channel);
if (chdat->dma_data)
dmaengine_terminate_all(chdat->dma_data->chan);
channel->status = MUSB_DMA_STATUS_FREE;
return 0;
}
static inline int tusb_omap_dma_allocate_dmareq(struct tusb_omap_dma_ch *chdat)
{
u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP);
int i, dmareq_nr = -1;
for (i = 0; i < MAX_DMAREQ; i++) {
int cur = (reg & (0xf << (i * 5))) >> (i * 5);
if (cur == 0) {
dmareq_nr = i;
break;
}
}
if (dmareq_nr == -1)
return -EAGAIN;
reg |= (chdat->epnum << (dmareq_nr * 5));
if (chdat->tx)
reg |= ((1 << 4) << (dmareq_nr * 5));
musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg);
chdat->dma_data = &chdat->tusb_dma->dma_pool[dmareq_nr];
return 0;
}
static inline void tusb_omap_dma_free_dmareq(struct tusb_omap_dma_ch *chdat)
{
u32 reg;
if (!chdat || !chdat->dma_data || chdat->dma_data->dmareq < 0)
return;
reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP);
reg &= ~(0x1f << (chdat->dma_data->dmareq * 5));
musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg);
chdat->dma_data = NULL;
}
static struct dma_channel *dma_channel_pool[MAX_DMAREQ];
static struct dma_channel *
tusb_omap_dma_allocate(struct dma_controller *c,
struct musb_hw_ep *hw_ep,
u8 tx)
{
int ret, i;
struct tusb_omap_dma *tusb_dma;
struct musb *musb;
struct dma_channel *channel = NULL;
struct tusb_omap_dma_ch *chdat = NULL;
struct tusb_dma_data *dma_data = NULL;
tusb_dma = container_of(c, struct tusb_omap_dma, controller);
musb = tusb_dma->controller.musb;
/* REVISIT: Why does dmareq5 not work? */
if (hw_ep->epnum == 0) {
dev_dbg(musb->controller, "Not allowing DMA for ep0 %s\n", tx ? "tx" : "rx");
return NULL;
}
for (i = 0; i < MAX_DMAREQ; i++) {
struct dma_channel *ch = dma_channel_pool[i];
if (ch->status == MUSB_DMA_STATUS_UNKNOWN) {
ch->status = MUSB_DMA_STATUS_FREE;
channel = ch;
chdat = ch->private_data;
break;
}
}
if (!channel)
return NULL;
chdat->musb = tusb_dma->controller.musb;
chdat->tbase = tusb_dma->tbase;
chdat->hw_ep = hw_ep;
chdat->epnum = hw_ep->epnum;
chdat->completed_len = 0;
chdat->tusb_dma = tusb_dma;
if (tx)
chdat->tx = 1;
else
chdat->tx = 0;
channel->max_len = 0x7fffffff;
channel->desired_mode = 0;
channel->actual_len = 0;
if (!chdat->dma_data) {
if (tusb_dma->multichannel) {
ret = tusb_omap_dma_allocate_dmareq(chdat);
if (ret != 0)
goto free_dmareq;
} else {
chdat->dma_data = &tusb_dma->dma_pool[0];
}
}
dma_data = chdat->dma_data;
dev_dbg(musb->controller, "ep%i %s dma: %s dmareq%i\n",
chdat->epnum,
chdat->tx ? "tx" : "rx",
tusb_dma->multichannel ? "shared" : "dedicated",
dma_data->dmareq);
return channel;
free_dmareq:
tusb_omap_dma_free_dmareq(chdat);
dev_dbg(musb->controller, "ep%i: Could not get a DMA channel\n", chdat->epnum);
channel->status = MUSB_DMA_STATUS_UNKNOWN;
return NULL;
}
static void tusb_omap_dma_release(struct dma_channel *channel)
{
struct tusb_omap_dma_ch *chdat = to_chdat(channel);
struct musb *musb = chdat->musb;
dev_dbg(musb->controller, "Release for ep%i\n", chdat->epnum);
channel->status = MUSB_DMA_STATUS_UNKNOWN;
dmaengine_terminate_sync(chdat->dma_data->chan);
tusb_omap_dma_free_dmareq(chdat);
channel = NULL;
}
void tusb_dma_controller_destroy(struct dma_controller *c)
{
struct tusb_omap_dma *tusb_dma;
int i;
tusb_dma = container_of(c, struct tusb_omap_dma, controller);
for (i = 0; i < MAX_DMAREQ; i++) {
struct dma_channel *ch = dma_channel_pool[i];
if (ch) {
kfree(ch->private_data);
kfree(ch);
}
/* Free up the DMA channels */
if (tusb_dma && tusb_dma->dma_pool[i].chan)
dma_release_channel(tusb_dma->dma_pool[i].chan);
}
kfree(tusb_dma);
}
EXPORT_SYMBOL_GPL(tusb_dma_controller_destroy);
static int tusb_omap_allocate_dma_pool(struct tusb_omap_dma *tusb_dma)
{
struct musb *musb = tusb_dma->controller.musb;
int i;
int ret = 0;
for (i = 0; i < MAX_DMAREQ; i++) {
struct tusb_dma_data *dma_data = &tusb_dma->dma_pool[i];
/*
* Request DMA channels:
* - one channel in case of non multichannel mode
* - MAX_DMAREQ number of channels in multichannel mode
*/
if (i == 0 || tusb_dma->multichannel) {
char ch_name[8];
sprintf(ch_name, "dmareq%d", i);
dma_data->chan = dma_request_chan(musb->controller,
ch_name);
if (IS_ERR(dma_data->chan)) {
dev_err(musb->controller,
"Failed to request %s\n", ch_name);
ret = PTR_ERR(dma_data->chan);
goto dma_error;
}
dma_data->dmareq = i;
} else {
dma_data->dmareq = -1;
}
}
return 0;
dma_error:
for (; i >= 0; i--) {
struct tusb_dma_data *dma_data = &tusb_dma->dma_pool[i];
if (dma_data->dmareq >= 0)
dma_release_channel(dma_data->chan);
}
return ret;
}
struct dma_controller *
tusb_dma_controller_create(struct musb *musb, void __iomem *base)
{
void __iomem *tbase = musb->ctrl_base;
struct tusb_omap_dma *tusb_dma;
int i;
/* REVISIT: Get dmareq lines used from board-*.c */
musb_writel(musb->ctrl_base, TUSB_DMA_INT_MASK, 0x7fffffff);
musb_writel(musb->ctrl_base, TUSB_DMA_EP_MAP, 0);
musb_writel(tbase, TUSB_DMA_REQ_CONF,
TUSB_DMA_REQ_CONF_BURST_SIZE(2)
| TUSB_DMA_REQ_CONF_DMA_REQ_EN(0x3f)
| TUSB_DMA_REQ_CONF_DMA_REQ_ASSER(2));
tusb_dma = kzalloc_obj(struct tusb_omap_dma);
if (!tusb_dma)
goto out;
tusb_dma->controller.musb = musb;
tusb_dma->tbase = musb->ctrl_base;
tusb_dma->controller.channel_alloc = tusb_omap_dma_allocate;
tusb_dma->controller.channel_release = tusb_omap_dma_release;
tusb_dma->controller.channel_program = tusb_omap_dma_program;
tusb_dma->controller.channel_abort = tusb_omap_dma_abort;
if (musb->tusb_revision >= TUSB_REV_30)
tusb_dma->multichannel = 1;
for (i = 0; i < MAX_DMAREQ; i++) {
struct dma_channel *ch;
struct tusb_omap_dma_ch *chdat;
ch = kzalloc_obj(struct dma_channel);
if (!ch)
goto cleanup;
dma_channel_pool[i] = ch;
chdat = kzalloc_obj(struct tusb_omap_dma_ch);
if (!chdat)
goto cleanup;
ch->status = MUSB_DMA_STATUS_UNKNOWN;
ch->private_data = chdat;
}
if (tusb_omap_allocate_dma_pool(tusb_dma))
goto cleanup;
return &tusb_dma->controller;
cleanup:
musb_dma_controller_destroy(&tusb_dma->controller);
out:
return NULL;
}
EXPORT_SYMBOL_GPL(tusb_dma_controller_create);
+369
View File
@@ -0,0 +1,369 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2010 ST-Ericsson AB
* Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>
*
* Based on omap2430.c
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/usb/musb-ux500.h>
#include "musb_core.h"
static const struct musb_hdrc_config ux500_musb_hdrc_config = {
.multipoint = true,
.dyn_fifo = true,
.num_eps = 16,
.ram_bits = 16,
};
struct ux500_glue {
struct device *dev;
struct platform_device *musb;
struct clk *clk;
};
#define glue_to_musb(g) platform_get_drvdata(g->musb)
static void ux500_musb_set_vbus(struct musb *musb, int is_on)
{
u8 devctl;
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
/* HDRC controls CPEN, but beware current surges during device
* connect. They can trigger transient overcurrent conditions
* that must be ignored.
*/
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
if (is_on) {
if (musb->xceiv->otg->state == OTG_STATE_A_IDLE) {
/* start the session */
devctl |= MUSB_DEVCTL_SESSION;
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
/*
* Wait for the musb to set as A device to enable the
* VBUS
*/
while (musb_readb(musb->mregs, MUSB_DEVCTL) & 0x80) {
if (time_after(jiffies, timeout)) {
dev_err(musb->controller,
"configured as A device timeout");
break;
}
}
} else {
musb->is_active = 1;
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE;
devctl |= MUSB_DEVCTL_SESSION;
MUSB_HST_MODE(musb);
}
} else {
musb->is_active = 0;
/* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and jumping
* right to B_IDLE...
*/
devctl &= ~MUSB_DEVCTL_SESSION;
MUSB_DEV_MODE(musb);
}
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
/*
* Devctl values will be updated after vbus goes below
* session_valid. The time taken depends on the capacitance
* on VBUS line. The max discharge time can be upto 1 sec
* as per the spec. Typically on our platform, it is 200ms
*/
if (!is_on)
mdelay(200);
dev_dbg(musb->controller, "VBUS %s, devctl %02x\n",
usb_otg_state_string(musb->xceiv->otg->state),
musb_readb(musb->mregs, MUSB_DEVCTL));
}
static int musb_otg_notifications(struct notifier_block *nb,
unsigned long event, void *unused)
{
struct musb *musb = container_of(nb, struct musb, nb);
dev_dbg(musb->controller, "musb_otg_notifications %ld %s\n",
event, usb_otg_state_string(musb->xceiv->otg->state));
switch (event) {
case UX500_MUSB_ID:
dev_dbg(musb->controller, "ID GND\n");
ux500_musb_set_vbus(musb, 1);
break;
case UX500_MUSB_VBUS:
dev_dbg(musb->controller, "VBUS Connect\n");
break;
case UX500_MUSB_NONE:
dev_dbg(musb->controller, "VBUS Disconnect\n");
if (is_host_active(musb))
ux500_musb_set_vbus(musb, 0);
else
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
break;
default:
dev_dbg(musb->controller, "ID float\n");
return NOTIFY_DONE;
}
return NOTIFY_OK;
}
static irqreturn_t ux500_musb_interrupt(int irq, void *__hci)
{
unsigned long flags;
irqreturn_t retval = IRQ_NONE;
struct musb *musb = __hci;
spin_lock_irqsave(&musb->lock, flags);
musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
if (musb->int_usb || musb->int_tx || musb->int_rx)
retval = musb_interrupt(musb);
spin_unlock_irqrestore(&musb->lock, flags);
return retval;
}
static int ux500_musb_init(struct musb *musb)
{
int status;
musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
if (IS_ERR_OR_NULL(musb->xceiv)) {
pr_err("HS USB OTG: no transceiver configured\n");
return -EPROBE_DEFER;
}
musb->nb.notifier_call = musb_otg_notifications;
status = usb_register_notifier(musb->xceiv, &musb->nb);
if (status < 0) {
dev_dbg(musb->controller, "notification register failed\n");
return status;
}
musb->isr = ux500_musb_interrupt;
return 0;
}
static int ux500_musb_exit(struct musb *musb)
{
usb_unregister_notifier(musb->xceiv, &musb->nb);
usb_put_phy(musb->xceiv);
return 0;
}
static const struct musb_platform_ops ux500_ops = {
.quirks = MUSB_DMA_UX500 | MUSB_INDEXED_EP,
#ifdef CONFIG_USB_UX500_DMA
.dma_init = ux500_dma_controller_create,
.dma_exit = ux500_dma_controller_destroy,
#endif
.init = ux500_musb_init,
.exit = ux500_musb_exit,
.fifo_mode = 5,
.set_vbus = ux500_musb_set_vbus,
};
static struct musb_hdrc_platform_data *
ux500_of_probe(struct platform_device *pdev, struct device_node *np)
{
struct musb_hdrc_platform_data *pdata;
const char *mode;
int strlen;
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return NULL;
mode = of_get_property(np, "dr_mode", &strlen);
if (!mode) {
dev_err(&pdev->dev, "No 'dr_mode' property found\n");
return NULL;
}
if (strlen > 0) {
if (!strcmp(mode, "host"))
pdata->mode = MUSB_HOST;
if (!strcmp(mode, "otg"))
pdata->mode = MUSB_OTG;
if (!strcmp(mode, "peripheral"))
pdata->mode = MUSB_PERIPHERAL;
}
return pdata;
}
static int ux500_probe(struct platform_device *pdev)
{
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct device_node *np = pdev->dev.of_node;
struct platform_device *musb;
struct ux500_glue *glue;
struct clk *clk;
int ret = -ENOMEM;
if (!pdata) {
if (np) {
pdata = ux500_of_probe(pdev, np);
if (!pdata)
goto err0;
pdev->dev.platform_data = pdata;
} else {
dev_err(&pdev->dev, "no pdata or device tree found\n");
goto err0;
}
}
glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);
if (!glue)
goto err0;
musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO);
if (!musb) {
dev_err(&pdev->dev, "failed to allocate musb device\n");
goto err0;
}
clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
ret = PTR_ERR(clk);
goto err1;
}
ret = clk_prepare_enable(clk);
if (ret) {
dev_err(&pdev->dev, "failed to enable clock\n");
goto err1;
}
musb->dev.parent = &pdev->dev;
musb->dev.dma_mask = &pdev->dev.coherent_dma_mask;
musb->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask;
device_set_of_node_from_dev(&musb->dev, &pdev->dev);
glue->dev = &pdev->dev;
glue->musb = musb;
glue->clk = clk;
pdata->platform_ops = &ux500_ops;
pdata->config = &ux500_musb_hdrc_config;
platform_set_drvdata(pdev, glue);
ret = platform_device_add_resources(musb, pdev->resource, pdev->num_resources);
if (ret) {
dev_err(&pdev->dev, "failed to add resources\n");
goto err2;
}
ret = platform_device_add_data(musb, pdata, sizeof(*pdata));
if (ret) {
dev_err(&pdev->dev, "failed to add platform_data\n");
goto err2;
}
ret = platform_device_add(musb);
if (ret) {
dev_err(&pdev->dev, "failed to register musb device\n");
goto err2;
}
return 0;
err2:
clk_disable_unprepare(clk);
err1:
platform_device_put(musb);
err0:
return ret;
}
static void ux500_remove(struct platform_device *pdev)
{
struct ux500_glue *glue = platform_get_drvdata(pdev);
platform_device_unregister(glue->musb);
clk_disable_unprepare(glue->clk);
}
#ifdef CONFIG_PM_SLEEP
static int ux500_suspend(struct device *dev)
{
struct ux500_glue *glue = dev_get_drvdata(dev);
struct musb *musb = glue_to_musb(glue);
if (musb)
usb_phy_set_suspend(musb->xceiv, 1);
clk_disable_unprepare(glue->clk);
return 0;
}
static int ux500_resume(struct device *dev)
{
struct ux500_glue *glue = dev_get_drvdata(dev);
struct musb *musb = glue_to_musb(glue);
int ret;
ret = clk_prepare_enable(glue->clk);
if (ret) {
dev_err(dev, "failed to enable clock\n");
return ret;
}
if (musb)
usb_phy_set_suspend(musb->xceiv, 0);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(ux500_pm_ops, ux500_suspend, ux500_resume);
static const struct of_device_id ux500_match[] = {
{ .compatible = "stericsson,db8500-musb", },
{}
};
MODULE_DEVICE_TABLE(of, ux500_match);
static struct platform_driver ux500_driver = {
.probe = ux500_probe,
.remove = ux500_remove,
.driver = {
.name = "musb-ux500",
.pm = &ux500_pm_ops,
.of_match_table = ux500_match,
},
};
MODULE_DESCRIPTION("UX500 MUSB Glue Layer");
MODULE_AUTHOR("Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>");
MODULE_LICENSE("GPL v2");
module_platform_driver(ux500_driver);
+396
View File
@@ -0,0 +1,396 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* drivers/usb/musb/ux500_dma.c
*
* U8500 DMA support code
*
* Copyright (C) 2009 STMicroelectronics
* Copyright (C) 2011 ST-Ericsson SA
* Authors:
* Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>
* Praveena Nadahally <praveen.nadahally@stericsson.com>
* Rajaram Regupathy <ragupathy.rajaram@stericsson.com>
*/
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/pfn.h>
#include <linux/sizes.h>
#include <linux/platform_data/usb-musb-ux500.h>
#include "musb_core.h"
static const char *iep_chan_names[] = { "iep_1_9", "iep_2_10", "iep_3_11", "iep_4_12",
"iep_5_13", "iep_6_14", "iep_7_15", "iep_8" };
static const char *oep_chan_names[] = { "oep_1_9", "oep_2_10", "oep_3_11", "oep_4_12",
"oep_5_13", "oep_6_14", "oep_7_15", "oep_8" };
struct ux500_dma_channel {
struct dma_channel channel;
struct ux500_dma_controller *controller;
struct musb_hw_ep *hw_ep;
struct dma_chan *dma_chan;
unsigned int cur_len;
dma_cookie_t cookie;
u8 ch_num;
u8 is_tx;
u8 is_allocated;
};
struct ux500_dma_controller {
struct dma_controller controller;
struct ux500_dma_channel rx_channel[UX500_MUSB_DMA_NUM_RX_TX_CHANNELS];
struct ux500_dma_channel tx_channel[UX500_MUSB_DMA_NUM_RX_TX_CHANNELS];
void *private_data;
dma_addr_t phy_base;
};
/* Work function invoked from DMA callback to handle rx transfers. */
static void ux500_dma_callback(void *private_data)
{
struct dma_channel *channel = private_data;
struct ux500_dma_channel *ux500_channel = channel->private_data;
struct musb_hw_ep *hw_ep = ux500_channel->hw_ep;
struct musb *musb = hw_ep->musb;
unsigned long flags;
dev_dbg(musb->controller, "DMA rx transfer done on hw_ep=%d\n",
hw_ep->epnum);
spin_lock_irqsave(&musb->lock, flags);
ux500_channel->channel.actual_len = ux500_channel->cur_len;
ux500_channel->channel.status = MUSB_DMA_STATUS_FREE;
musb_dma_completion(musb, hw_ep->epnum, ux500_channel->is_tx);
spin_unlock_irqrestore(&musb->lock, flags);
}
static bool ux500_configure_channel(struct dma_channel *channel,
u16 packet_sz, u8 mode,
dma_addr_t dma_addr, u32 len)
{
struct ux500_dma_channel *ux500_channel = channel->private_data;
struct musb_hw_ep *hw_ep = ux500_channel->hw_ep;
struct dma_chan *dma_chan = ux500_channel->dma_chan;
struct dma_async_tx_descriptor *dma_desc;
enum dma_transfer_direction direction;
struct scatterlist sg;
struct dma_slave_config slave_conf;
enum dma_slave_buswidth addr_width;
struct musb *musb = ux500_channel->controller->private_data;
dma_addr_t usb_fifo_addr = (musb->io.fifo_offset(hw_ep->epnum) +
ux500_channel->controller->phy_base);
dev_dbg(musb->controller,
"packet_sz=%d, mode=%d, dma_addr=0x%llx, len=%d is_tx=%d\n",
packet_sz, mode, (unsigned long long) dma_addr,
len, ux500_channel->is_tx);
ux500_channel->cur_len = len;
sg_init_table(&sg, 1);
sg_set_page(&sg, pfn_to_page(PFN_DOWN(dma_addr)), len,
offset_in_page(dma_addr));
sg_dma_address(&sg) = dma_addr;
sg_dma_len(&sg) = len;
direction = ux500_channel->is_tx ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
addr_width = (len & 0x3) ? DMA_SLAVE_BUSWIDTH_1_BYTE :
DMA_SLAVE_BUSWIDTH_4_BYTES;
slave_conf.direction = direction;
slave_conf.src_addr = usb_fifo_addr;
slave_conf.src_addr_width = addr_width;
slave_conf.src_maxburst = 16;
slave_conf.dst_addr = usb_fifo_addr;
slave_conf.dst_addr_width = addr_width;
slave_conf.dst_maxburst = 16;
slave_conf.device_fc = false;
dmaengine_slave_config(dma_chan, &slave_conf);
dma_desc = dmaengine_prep_slave_sg(dma_chan, &sg, 1, direction,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!dma_desc)
return false;
dma_desc->callback = ux500_dma_callback;
dma_desc->callback_param = channel;
ux500_channel->cookie = dma_desc->tx_submit(dma_desc);
dma_async_issue_pending(dma_chan);
return true;
}
static struct dma_channel *ux500_dma_channel_allocate(struct dma_controller *c,
struct musb_hw_ep *hw_ep, u8 is_tx)
{
struct ux500_dma_controller *controller = container_of(c,
struct ux500_dma_controller, controller);
struct ux500_dma_channel *ux500_channel = NULL;
struct musb *musb = controller->private_data;
u8 ch_num = hw_ep->epnum - 1;
/* 8 DMA channels (0 - 7). Each DMA channel can only be allocated
* to specified hw_ep. For example DMA channel 0 can only be allocated
* to hw_ep 1 and 9.
*/
if (ch_num > 7)
ch_num -= 8;
if (ch_num >= UX500_MUSB_DMA_NUM_RX_TX_CHANNELS)
return NULL;
ux500_channel = is_tx ? &(controller->tx_channel[ch_num]) :
&(controller->rx_channel[ch_num]) ;
/* Check if channel is already used. */
if (ux500_channel->is_allocated)
return NULL;
ux500_channel->hw_ep = hw_ep;
ux500_channel->is_allocated = 1;
dev_dbg(musb->controller, "hw_ep=%d, is_tx=0x%x, channel=%d\n",
hw_ep->epnum, is_tx, ch_num);
return &(ux500_channel->channel);
}
static void ux500_dma_channel_release(struct dma_channel *channel)
{
struct ux500_dma_channel *ux500_channel = channel->private_data;
struct musb *musb = ux500_channel->controller->private_data;
dev_dbg(musb->controller, "channel=%d\n", ux500_channel->ch_num);
if (ux500_channel->is_allocated) {
ux500_channel->is_allocated = 0;
channel->status = MUSB_DMA_STATUS_FREE;
channel->actual_len = 0;
}
}
static int ux500_dma_is_compatible(struct dma_channel *channel,
u16 maxpacket, void *buf, u32 length)
{
if ((maxpacket & 0x3) ||
((unsigned long int) buf & 0x3) ||
(length < 512) ||
(length & 0x3))
return false;
else
return true;
}
static int ux500_dma_channel_program(struct dma_channel *channel,
u16 packet_sz, u8 mode,
dma_addr_t dma_addr, u32 len)
{
int ret;
BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN ||
channel->status == MUSB_DMA_STATUS_BUSY);
channel->status = MUSB_DMA_STATUS_BUSY;
channel->actual_len = 0;
ret = ux500_configure_channel(channel, packet_sz, mode, dma_addr, len);
if (!ret)
channel->status = MUSB_DMA_STATUS_FREE;
return ret;
}
static int ux500_dma_channel_abort(struct dma_channel *channel)
{
struct ux500_dma_channel *ux500_channel = channel->private_data;
struct ux500_dma_controller *controller = ux500_channel->controller;
struct musb *musb = controller->private_data;
void __iomem *epio = musb->endpoints[ux500_channel->hw_ep->epnum].regs;
u16 csr;
dev_dbg(musb->controller, "channel=%d, is_tx=%d\n",
ux500_channel->ch_num, ux500_channel->is_tx);
if (channel->status == MUSB_DMA_STATUS_BUSY) {
if (ux500_channel->is_tx) {
csr = musb_readw(epio, MUSB_TXCSR);
csr &= ~(MUSB_TXCSR_AUTOSET |
MUSB_TXCSR_DMAENAB |
MUSB_TXCSR_DMAMODE);
musb_writew(epio, MUSB_TXCSR, csr);
} else {
csr = musb_readw(epio, MUSB_RXCSR);
csr &= ~(MUSB_RXCSR_AUTOCLEAR |
MUSB_RXCSR_DMAENAB |
MUSB_RXCSR_DMAMODE);
musb_writew(epio, MUSB_RXCSR, csr);
}
dmaengine_terminate_all(ux500_channel->dma_chan);
channel->status = MUSB_DMA_STATUS_FREE;
}
return 0;
}
static void ux500_dma_controller_stop(struct ux500_dma_controller *controller)
{
struct ux500_dma_channel *ux500_channel;
struct dma_channel *channel;
u8 ch_num;
for (ch_num = 0; ch_num < UX500_MUSB_DMA_NUM_RX_TX_CHANNELS; ch_num++) {
channel = &controller->rx_channel[ch_num].channel;
ux500_channel = channel->private_data;
ux500_dma_channel_release(channel);
if (ux500_channel->dma_chan)
dma_release_channel(ux500_channel->dma_chan);
}
for (ch_num = 0; ch_num < UX500_MUSB_DMA_NUM_RX_TX_CHANNELS; ch_num++) {
channel = &controller->tx_channel[ch_num].channel;
ux500_channel = channel->private_data;
ux500_dma_channel_release(channel);
if (ux500_channel->dma_chan)
dma_release_channel(ux500_channel->dma_chan);
}
}
static int ux500_dma_controller_start(struct ux500_dma_controller *controller)
{
struct ux500_dma_channel *ux500_channel = NULL;
struct musb *musb = controller->private_data;
struct device *dev = musb->controller;
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
struct ux500_musb_board_data *data;
struct dma_channel *dma_channel = NULL;
char **chan_names;
u32 ch_num;
u8 dir;
u8 is_tx = 0;
void **param_array;
struct ux500_dma_channel *channel_array;
dma_cap_mask_t mask;
if (!plat) {
dev_err(musb->controller, "No platform data\n");
return -EINVAL;
}
data = plat->board_data;
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
/* Prepare the loop for RX channels */
channel_array = controller->rx_channel;
param_array = data ? data->dma_rx_param_array : NULL;
chan_names = (char **)iep_chan_names;
for (dir = 0; dir < 2; dir++) {
for (ch_num = 0;
ch_num < UX500_MUSB_DMA_NUM_RX_TX_CHANNELS;
ch_num++) {
ux500_channel = &channel_array[ch_num];
ux500_channel->controller = controller;
ux500_channel->ch_num = ch_num;
ux500_channel->is_tx = is_tx;
dma_channel = &(ux500_channel->channel);
dma_channel->private_data = ux500_channel;
dma_channel->status = MUSB_DMA_STATUS_FREE;
dma_channel->max_len = SZ_16M;
ux500_channel->dma_chan =
dma_request_chan(dev, chan_names[ch_num]);
if (IS_ERR(ux500_channel->dma_chan))
ux500_channel->dma_chan =
dma_request_channel(mask,
data ?
data->dma_filter :
NULL,
param_array ?
param_array[ch_num] :
NULL);
if (!ux500_channel->dma_chan) {
ERR("Dma pipe allocation error dir=%d ch=%d\n",
dir, ch_num);
/* Release already allocated channels */
ux500_dma_controller_stop(controller);
return -EBUSY;
}
}
/* Prepare the loop for TX channels */
channel_array = controller->tx_channel;
param_array = data ? data->dma_tx_param_array : NULL;
chan_names = (char **)oep_chan_names;
is_tx = 1;
}
return 0;
}
void ux500_dma_controller_destroy(struct dma_controller *c)
{
struct ux500_dma_controller *controller = container_of(c,
struct ux500_dma_controller, controller);
ux500_dma_controller_stop(controller);
kfree(controller);
}
EXPORT_SYMBOL_GPL(ux500_dma_controller_destroy);
struct dma_controller *
ux500_dma_controller_create(struct musb *musb, void __iomem *base)
{
struct ux500_dma_controller *controller;
struct platform_device *pdev = to_platform_device(musb->controller);
struct resource *iomem;
int ret;
controller = kzalloc_obj(*controller);
if (!controller)
goto kzalloc_fail;
controller->private_data = musb;
/* Save physical address for DMA controller. */
iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iomem) {
dev_err(musb->controller, "no memory resource defined\n");
goto plat_get_fail;
}
controller->phy_base = (dma_addr_t) iomem->start;
controller->controller.channel_alloc = ux500_dma_channel_allocate;
controller->controller.channel_release = ux500_dma_channel_release;
controller->controller.channel_program = ux500_dma_channel_program;
controller->controller.channel_abort = ux500_dma_channel_abort;
controller->controller.is_compatible = ux500_dma_is_compatible;
ret = ux500_dma_controller_start(controller);
if (ret)
goto plat_get_fail;
return &controller->controller;
plat_get_fail:
kfree(controller);
kzalloc_fail:
return NULL;
}
EXPORT_SYMBOL_GPL(ux500_dma_controller_create);