3890 lines
114 KiB
Diff
3890 lines
114 KiB
Diff
From 6e8d34490963467552ebe1a996e0ad78b4e560ba Mon Sep 17 00:00:00 2001
|
|
From: Lionel VITTE <lionel.vitte@st.com>
|
|
Date: Fri, 8 Nov 2019 16:52:47 +0100
|
|
Subject: [PATCH 26/31] ARM stm32mp1 r3 TTY USB
|
|
|
|
---
|
|
drivers/tty/serial/stm32-usart.c | 758 ++++++++++++++++++++-------
|
|
drivers/tty/serial/stm32-usart.h | 48 +-
|
|
drivers/usb/dwc2/Makefile | 2 +-
|
|
drivers/usb/dwc2/core.c | 123 +++--
|
|
drivers/usb/dwc2/core.h | 50 ++
|
|
drivers/usb/dwc2/debugfs.c | 1 +
|
|
drivers/usb/dwc2/drd.c | 191 +++++++
|
|
drivers/usb/dwc2/gadget.c | 99 +++-
|
|
drivers/usb/dwc2/hcd.c | 36 +-
|
|
drivers/usb/dwc2/hw.h | 25 +
|
|
drivers/usb/dwc2/params.c | 40 ++
|
|
drivers/usb/dwc2/platform.c | 166 +++++-
|
|
drivers/usb/gadget/function/u_serial.c | 35 +-
|
|
drivers/usb/host/ehci-platform.c | 59 +++
|
|
drivers/usb/typec/Kconfig | 9 +
|
|
drivers/usb/typec/Makefile | 1 +
|
|
drivers/usb/typec/class.c | 15 +
|
|
drivers/usb/typec/typec_stusb.c | 918 +++++++++++++++++++++++++++++++++
|
|
include/linux/usb/typec.h | 1 +
|
|
19 files changed, 2265 insertions(+), 312 deletions(-)
|
|
create mode 100644 drivers/usb/dwc2/drd.c
|
|
create mode 100644 drivers/usb/typec/typec_stusb.c
|
|
|
|
diff --git a/drivers/tty/serial/stm32-usart.c b/drivers/tty/serial/stm32-usart.c
|
|
index e8d7a7b..c6acbcf 100644
|
|
--- a/drivers/tty/serial/stm32-usart.c
|
|
+++ b/drivers/tty/serial/stm32-usart.c
|
|
@@ -24,6 +24,8 @@
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
+#include <linux/pinctrl/devinfo.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/pm_wakeirq.h>
|
|
@@ -105,9 +107,7 @@ static int stm32_config_rs485(struct uart_port *port,
|
|
struct stm32_usart_config *cfg = &stm32_port->info->cfg;
|
|
u32 usartdiv, baud, cr1, cr3;
|
|
bool over8;
|
|
- unsigned long flags;
|
|
|
|
- spin_lock_irqsave(&port->lock, flags);
|
|
stm32_clr_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit));
|
|
|
|
port->rs485 = *rs485conf;
|
|
@@ -147,7 +147,6 @@ static int stm32_config_rs485(struct uart_port *port,
|
|
}
|
|
|
|
stm32_set_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit));
|
|
- spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
@@ -169,101 +168,193 @@ static int stm32_init_rs485(struct uart_port *port,
|
|
return 0;
|
|
}
|
|
|
|
-static int stm32_pending_rx(struct uart_port *port, u32 *sr, int *last_res,
|
|
- bool threaded)
|
|
+/* Returns 1 when data is pending in pio mode and 0 when no data is pending. */
|
|
+static int stm32_pending_rx_pio(struct uart_port *port, u32 *sr)
|
|
{
|
|
struct stm32_port *stm32_port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
- enum dma_status status;
|
|
- struct dma_tx_state state;
|
|
|
|
*sr = readl_relaxed(port->membase + ofs->isr);
|
|
+ /* Get pending characters in RDR or FIFO */
|
|
+ if (*sr & USART_SR_RXNE) {
|
|
+ /*
|
|
+ * Get all pending characters from the RDR or the FIFO when
|
|
+ * using interrupts
|
|
+ */
|
|
+ if (!stm32_port->rx_ch)
|
|
+ return 1;
|
|
|
|
- if (threaded && stm32_port->rx_ch) {
|
|
- status = dmaengine_tx_status(stm32_port->rx_ch,
|
|
- stm32_port->rx_ch->cookie,
|
|
- &state);
|
|
- if ((status == DMA_IN_PROGRESS) &&
|
|
- (*last_res != state.residue))
|
|
+ /* Handle only RX data errors when using dma */
|
|
+ if (*sr & USART_SR_ERR_MASK)
|
|
return 1;
|
|
- else
|
|
- return 0;
|
|
- } else if (*sr & USART_SR_RXNE) {
|
|
- return 1;
|
|
}
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
-static unsigned long
|
|
-stm32_get_char(struct uart_port *port, u32 *sr, int *last_res)
|
|
+static unsigned long stm32_get_char_pio(struct uart_port *port)
|
|
{
|
|
struct stm32_port *stm32_port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
unsigned long c;
|
|
|
|
- if (stm32_port->rx_ch) {
|
|
- c = stm32_port->rx_buf[RX_BUF_L - (*last_res)--];
|
|
- if ((*last_res) == 0)
|
|
- *last_res = RX_BUF_L;
|
|
- return c;
|
|
- } else {
|
|
- return readl_relaxed(port->membase + ofs->rdr);
|
|
- }
|
|
+ c = readl_relaxed(port->membase + ofs->rdr);
|
|
+ /* Apply RDR data mask */
|
|
+ c &= stm32_port->rdr_mask;
|
|
+
|
|
+ return c;
|
|
}
|
|
|
|
-static void stm32_receive_chars(struct uart_port *port, bool threaded)
|
|
+static void stm32_receive_chars_pio(struct uart_port *port)
|
|
{
|
|
- struct tty_port *tport = &port->state->port;
|
|
struct stm32_port *stm32_port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
unsigned long c;
|
|
u32 sr;
|
|
char flag;
|
|
|
|
- if (irqd_is_wakeup_set(irq_get_irq_data(port->irq)))
|
|
- pm_wakeup_event(tport->tty->dev, 0);
|
|
-
|
|
- while (stm32_pending_rx(port, &sr, &stm32_port->last_res, threaded)) {
|
|
+ while (stm32_pending_rx_pio(port, &sr)) {
|
|
sr |= USART_SR_DUMMY_RX;
|
|
- c = stm32_get_char(port, &sr, &stm32_port->last_res);
|
|
flag = TTY_NORMAL;
|
|
- port->icount.rx++;
|
|
|
|
+ /*
|
|
+ * Status bits has to be cleared before reading the RDR:
|
|
+ * In FIFO mode, reading the RDR will pop the next data
|
|
+ * (if any) along with its status bits into the SR.
|
|
+ * Not doing so leads to misalignement between RDR and SR,
|
|
+ * and clear status bits of the next rx data.
|
|
+ *
|
|
+ * Clear errors flags for stm32f7 and stm32h7 compatible
|
|
+ * devices. On stm32f4 compatible devices, the error bit is
|
|
+ * cleared by the sequence [read SR - read DR].
|
|
+ */
|
|
+ if ((sr & USART_SR_ERR_MASK) && ofs->icr != UNDEF_REG)
|
|
+ writel_relaxed(sr & USART_SR_ERR_MASK,
|
|
+ port->membase + ofs->icr);
|
|
+
|
|
+ c = stm32_get_char_pio(port);
|
|
+ port->icount.rx++;
|
|
if (sr & USART_SR_ERR_MASK) {
|
|
- if (sr & USART_SR_LBD) {
|
|
- port->icount.brk++;
|
|
- if (uart_handle_break(port))
|
|
- continue;
|
|
- } else if (sr & USART_SR_ORE) {
|
|
- if (ofs->icr != UNDEF_REG)
|
|
- writel_relaxed(USART_ICR_ORECF,
|
|
- port->membase +
|
|
- ofs->icr);
|
|
+ if (sr & USART_SR_ORE) {
|
|
port->icount.overrun++;
|
|
} else if (sr & USART_SR_PE) {
|
|
port->icount.parity++;
|
|
} else if (sr & USART_SR_FE) {
|
|
- port->icount.frame++;
|
|
+ /* Break detection if character is null */
|
|
+ if (!c) {
|
|
+ port->icount.brk++;
|
|
+ if (uart_handle_break(port))
|
|
+ continue;
|
|
+ } else {
|
|
+ port->icount.frame++;
|
|
+ }
|
|
}
|
|
|
|
sr &= port->read_status_mask;
|
|
-
|
|
- if (sr & USART_SR_LBD)
|
|
- flag = TTY_BREAK;
|
|
- else if (sr & USART_SR_PE)
|
|
+ if (sr & USART_SR_PE) {
|
|
flag = TTY_PARITY;
|
|
- else if (sr & USART_SR_FE)
|
|
- flag = TTY_FRAME;
|
|
+ } else if (sr & USART_SR_FE) {
|
|
+ if (!c)
|
|
+ flag = TTY_BREAK;
|
|
+ else
|
|
+ flag = TTY_FRAME;
|
|
+ }
|
|
}
|
|
|
|
if (uart_handle_sysrq_char(port, c))
|
|
continue;
|
|
uart_insert_char(port, sr, USART_SR_ORE, c, flag);
|
|
}
|
|
+}
|
|
+
|
|
+static void stm32_push_buffer_dma(struct uart_port *port,
|
|
+ unsigned int dma_size)
|
|
+{
|
|
+ struct stm32_port *stm32_port = to_stm32_port(port);
|
|
+ struct tty_port *ttyport = &stm32_port->port.state->port;
|
|
+ unsigned char *dma_start;
|
|
+ int dma_count;
|
|
+
|
|
+ dma_start = stm32_port->rx_buf + (RX_BUF_L - stm32_port->last_res);
|
|
+ dma_count = tty_insert_flip_string(ttyport, dma_start, dma_size);
|
|
+ port->icount.rx += dma_count;
|
|
+ stm32_port->last_res -= dma_count;
|
|
+ if (stm32_port->last_res == 0)
|
|
+ stm32_port->last_res = RX_BUF_L;
|
|
+}
|
|
+
|
|
+static void stm32_receive_chars_dma(struct uart_port *port)
|
|
+{
|
|
+ struct stm32_port *stm32_port = to_stm32_port(port);
|
|
+ unsigned int dma_size;
|
|
+
|
|
+ /*
|
|
+ * DMA buffer is configured in cyclic mode and handles the rollback of
|
|
+ * the buffer.
|
|
+ */
|
|
+ if (stm32_port->state.residue > stm32_port->last_res) {
|
|
+ /* Conditional first part: from last_res to end of dma buffer */
|
|
+ dma_size = stm32_port->last_res;
|
|
+ stm32_push_buffer_dma(port, dma_size);
|
|
+ }
|
|
|
|
- spin_unlock(&port->lock);
|
|
+ dma_size = stm32_port->last_res - stm32_port->state.residue;
|
|
+ stm32_push_buffer_dma(port, dma_size);
|
|
+}
|
|
+
|
|
+static void stm32_receive_chars(struct uart_port *port, bool threaded)
|
|
+{
|
|
+ struct tty_port *tport = &port->state->port;
|
|
+ struct stm32_port *stm32_port = to_stm32_port(port);
|
|
+ struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
+ unsigned long flags = 0;
|
|
+ u32 sr;
|
|
+
|
|
+ if (irqd_is_wakeup_set(irq_get_irq_data(port->irq)))
|
|
+ pm_wakeup_event(tport->tty->dev, 0);
|
|
+
|
|
+ if (threaded)
|
|
+ spin_lock_irqsave(&port->lock, flags);
|
|
+ else
|
|
+ spin_lock(&port->lock);
|
|
+
|
|
+ if (stm32_port->rx_ch) {
|
|
+ stm32_port->status =
|
|
+ dmaengine_tx_status(stm32_port->rx_ch,
|
|
+ stm32_port->rx_ch->cookie,
|
|
+ &stm32_port->state);
|
|
+ if (stm32_port->status == DMA_IN_PROGRESS) {
|
|
+ /* Empty DMA buffer */
|
|
+ stm32_receive_chars_dma(port);
|
|
+ sr = readl_relaxed(port->membase + ofs->isr);
|
|
+ if (sr & USART_SR_ERR_MASK) {
|
|
+ /* Disable DMA request line */
|
|
+ stm32_clr_bits(port, ofs->cr3, USART_CR3_DMAR);
|
|
+
|
|
+ /* Switch to PIO mode handle the errors */
|
|
+ stm32_receive_chars_pio(port);
|
|
+
|
|
+ /* Switch back to DMA mode */
|
|
+ stm32_set_bits(port, ofs->cr3, USART_CR3_DMAR);
|
|
+ }
|
|
+ } else {
|
|
+ /* Disable rx DMA */
|
|
+ dmaengine_terminate_async(stm32_port->rx_ch);
|
|
+ stm32_clr_bits(port, ofs->cr3, USART_CR3_DMAR);
|
|
+ /* fall back to interrupt mode */
|
|
+ dev_dbg(port->dev,
|
|
+ "DMA error, fallback to irq mode\n");
|
|
+ stm32_receive_chars_pio(port);
|
|
+ }
|
|
+ } else {
|
|
+ stm32_receive_chars_pio(port);
|
|
+ }
|
|
+
|
|
+ if (threaded)
|
|
+ spin_unlock_irqrestore(&port->lock, flags);
|
|
+ else
|
|
+ spin_unlock(&port->lock);
|
|
tty_flip_buffer_push(tport);
|
|
- spin_lock(&port->lock);
|
|
}
|
|
|
|
static void stm32_tx_dma_complete(void *arg)
|
|
@@ -271,27 +362,23 @@ static void stm32_tx_dma_complete(void *arg)
|
|
struct uart_port *port = arg;
|
|
struct stm32_port *stm32port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32port->info->ofs;
|
|
- unsigned int isr;
|
|
- int ret;
|
|
-
|
|
- ret = readl_relaxed_poll_timeout_atomic(port->membase + ofs->isr,
|
|
- isr,
|
|
- (isr & USART_SR_TC),
|
|
- 10, 100000);
|
|
-
|
|
- if (ret)
|
|
- dev_err(port->dev, "terminal count not set\n");
|
|
-
|
|
- if (ofs->icr == UNDEF_REG)
|
|
- stm32_clr_bits(port, ofs->isr, USART_SR_TC);
|
|
- else
|
|
- stm32_set_bits(port, ofs->icr, USART_CR_TC);
|
|
+ unsigned long flags;
|
|
|
|
+ dmaengine_terminate_async(stm32port->tx_ch);
|
|
stm32_clr_bits(port, ofs->cr3, USART_CR3_DMAT);
|
|
stm32port->tx_dma_busy = false;
|
|
|
|
/* Let's see if we have pending data to send */
|
|
+ spin_lock_irqsave(&port->lock, flags);
|
|
stm32_transmit_chars(port);
|
|
+ spin_unlock_irqrestore(&port->lock, flags);
|
|
+}
|
|
+
|
|
+static void stm32_rx_dma_complete(void *arg)
|
|
+{
|
|
+ struct uart_port *port = arg;
|
|
+
|
|
+ stm32_receive_chars(port, true);
|
|
}
|
|
|
|
static void stm32_transmit_chars_pio(struct uart_port *port)
|
|
@@ -299,27 +386,30 @@ static void stm32_transmit_chars_pio(struct uart_port *port)
|
|
struct stm32_port *stm32_port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
struct circ_buf *xmit = &port->state->xmit;
|
|
- unsigned int isr;
|
|
- int ret;
|
|
|
|
if (stm32_port->tx_dma_busy) {
|
|
stm32_clr_bits(port, ofs->cr3, USART_CR3_DMAT);
|
|
stm32_port->tx_dma_busy = false;
|
|
}
|
|
|
|
- ret = readl_relaxed_poll_timeout_atomic(port->membase + ofs->isr,
|
|
- isr,
|
|
- (isr & USART_SR_TXE),
|
|
- 10, 100000);
|
|
-
|
|
- if (ret)
|
|
- dev_err(port->dev, "tx empty not set\n");
|
|
-
|
|
- stm32_set_bits(port, ofs->cr1, USART_CR1_TXEIE);
|
|
+ while (!uart_circ_empty(xmit)) {
|
|
+ if (!(readl_relaxed(port->membase + ofs->isr) & USART_SR_TXE))
|
|
+ break;
|
|
+ writel_relaxed(xmit->buf[xmit->tail], port->membase + ofs->tdr);
|
|
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
|
|
+ port->icount.tx++;
|
|
+ }
|
|
|
|
- writel_relaxed(xmit->buf[xmit->tail], port->membase + ofs->tdr);
|
|
- xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
|
|
- port->icount.tx++;
|
|
+ if (uart_circ_empty(xmit))
|
|
+ if (stm32_port->fifoen)
|
|
+ stm32_clr_bits(port, ofs->cr3, USART_CR3_TXFTIE);
|
|
+ else
|
|
+ stm32_clr_bits(port, ofs->cr1, USART_CR1_TXEIE);
|
|
+ else
|
|
+ if (stm32_port->fifoen)
|
|
+ stm32_set_bits(port, ofs->cr3, USART_CR3_TXFTIE);
|
|
+ else
|
|
+ stm32_set_bits(port, ofs->cr1, USART_CR1_TXEIE);
|
|
}
|
|
|
|
static void stm32_transmit_chars_dma(struct uart_port *port)
|
|
@@ -377,7 +467,6 @@ static void stm32_transmit_chars_dma(struct uart_port *port)
|
|
/* Issue pending DMA TX requests */
|
|
dma_async_issue_pending(stm32port->tx_ch);
|
|
|
|
- stm32_clr_bits(port, ofs->isr, USART_SR_TC);
|
|
stm32_set_bits(port, ofs->cr3, USART_CR3_DMAT);
|
|
|
|
xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
|
|
@@ -401,15 +490,18 @@ static void stm32_transmit_chars(struct uart_port *port)
|
|
return;
|
|
}
|
|
|
|
- if (uart_tx_stopped(port)) {
|
|
- stm32_stop_tx(port);
|
|
+ if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
|
|
+ if (stm32_port->fifoen)
|
|
+ stm32_clr_bits(port, ofs->cr3, USART_CR3_TXFTIE);
|
|
+ else
|
|
+ stm32_clr_bits(port, ofs->cr1, USART_CR1_TXEIE);
|
|
return;
|
|
}
|
|
|
|
- if (uart_circ_empty(xmit)) {
|
|
- stm32_stop_tx(port);
|
|
- return;
|
|
- }
|
|
+ if (ofs->icr == UNDEF_REG)
|
|
+ stm32_clr_bits(port, ofs->isr, USART_SR_TC);
|
|
+ else
|
|
+ writel_relaxed(USART_ICR_TCCF, port->membase + ofs->icr);
|
|
|
|
if (stm32_port->tx_ch)
|
|
stm32_transmit_chars_dma(port);
|
|
@@ -419,8 +511,12 @@ static void stm32_transmit_chars(struct uart_port *port)
|
|
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
|
|
uart_write_wakeup(port);
|
|
|
|
- if (uart_circ_empty(xmit))
|
|
- stm32_stop_tx(port);
|
|
+ if (uart_circ_empty(xmit)) {
|
|
+ if (stm32_port->fifoen)
|
|
+ stm32_clr_bits(port, ofs->cr3, USART_CR3_TXFTIE);
|
|
+ else
|
|
+ stm32_clr_bits(port, ofs->cr1, USART_CR1_TXEIE);
|
|
+ }
|
|
}
|
|
|
|
static irqreturn_t stm32_interrupt(int irq, void *ptr)
|
|
@@ -430,21 +526,30 @@ static irqreturn_t stm32_interrupt(int irq, void *ptr)
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
u32 sr;
|
|
|
|
- spin_lock(&port->lock);
|
|
-
|
|
sr = readl_relaxed(port->membase + ofs->isr);
|
|
|
|
+ if ((sr & USART_SR_RTOF) && (ofs->icr != UNDEF_REG))
|
|
+ writel_relaxed(USART_ICR_RTOCF,
|
|
+ port->membase + ofs->icr);
|
|
+
|
|
if ((sr & USART_SR_WUF) && (ofs->icr != UNDEF_REG))
|
|
writel_relaxed(USART_ICR_WUCF,
|
|
port->membase + ofs->icr);
|
|
|
|
- if ((sr & USART_SR_RXNE) && !(stm32_port->rx_ch))
|
|
+ /*
|
|
+ * rx errors in dma mode has to be handled ASAP to avoid overrun as the
|
|
+ * DMA request line has been masked by HW and rx data are stacking in
|
|
+ * FIFO.
|
|
+ */
|
|
+ if (((sr & USART_SR_RXNE) && !stm32_port->rx_ch) ||
|
|
+ ((sr & USART_SR_ERR_MASK) && stm32_port->rx_ch))
|
|
stm32_receive_chars(port, false);
|
|
|
|
- if ((sr & USART_SR_TXE) && !(stm32_port->tx_ch))
|
|
+ if ((sr & USART_SR_TXE) && !(stm32_port->tx_ch)) {
|
|
+ spin_lock(&port->lock);
|
|
stm32_transmit_chars(port);
|
|
-
|
|
- spin_unlock(&port->lock);
|
|
+ spin_unlock(&port->lock);
|
|
+ }
|
|
|
|
if (stm32_port->rx_ch)
|
|
return IRQ_WAKE_THREAD;
|
|
@@ -455,14 +560,9 @@ static irqreturn_t stm32_interrupt(int irq, void *ptr)
|
|
static irqreturn_t stm32_threaded_interrupt(int irq, void *ptr)
|
|
{
|
|
struct uart_port *port = ptr;
|
|
- struct stm32_port *stm32_port = to_stm32_port(port);
|
|
|
|
- spin_lock(&port->lock);
|
|
-
|
|
- if (stm32_port->rx_ch)
|
|
- stm32_receive_chars(port, true);
|
|
-
|
|
- spin_unlock(&port->lock);
|
|
+ /* Receiver timeout irq for dma rx */
|
|
+ stm32_receive_chars(port, true);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
@@ -472,7 +572,10 @@ static unsigned int stm32_tx_empty(struct uart_port *port)
|
|
struct stm32_port *stm32_port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
|
|
- return readl_relaxed(port->membase + ofs->isr) & USART_SR_TXE;
|
|
+ if (readl_relaxed(port->membase + ofs->isr) & USART_SR_TC)
|
|
+ return TIOCSER_TEMT;
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
static void stm32_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|
@@ -498,7 +601,15 @@ static void stm32_stop_tx(struct uart_port *port)
|
|
struct stm32_port *stm32_port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
|
|
- stm32_clr_bits(port, ofs->cr1, USART_CR1_TXEIE);
|
|
+ if (stm32_port->fifoen)
|
|
+ stm32_clr_bits(port, ofs->cr3, USART_CR3_TXFTIE);
|
|
+ else
|
|
+ stm32_clr_bits(port, ofs->cr1, USART_CR1_TXEIE);
|
|
+
|
|
+ if (stm32_port->tx_dma_busy) {
|
|
+ dmaengine_terminate_async(stm32_port->tx_ch);
|
|
+ stm32_clr_bits(port, ofs->cr3, USART_CR3_DMAT);
|
|
+ }
|
|
}
|
|
|
|
/* There are probably characters waiting to be transmitted. */
|
|
@@ -512,6 +623,23 @@ static void stm32_start_tx(struct uart_port *port)
|
|
stm32_transmit_chars(port);
|
|
}
|
|
|
|
+/* Flush the transmit buffer. */
|
|
+static void stm32_flush_buffer(struct uart_port *port)
|
|
+{
|
|
+ struct stm32_port *stm32_port = to_stm32_port(port);
|
|
+ struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
+
|
|
+ if (stm32_port->tx_ch) {
|
|
+ /* Avoid deadlock with the DMA engine callback */
|
|
+ spin_unlock(&port->lock);
|
|
+ dmaengine_terminate_async(stm32_port->tx_ch);
|
|
+ spin_lock(&port->lock);
|
|
+
|
|
+ stm32_clr_bits(port, ofs->cr3, USART_CR3_DMAT);
|
|
+ stm32_port->tx_dma_busy = false;
|
|
+ }
|
|
+}
|
|
+
|
|
/* Throttle the remote when input buffer is about to overflow. */
|
|
static void stm32_throttle(struct uart_port *port)
|
|
{
|
|
@@ -520,7 +648,10 @@ static void stm32_throttle(struct uart_port *port)
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
- stm32_clr_bits(port, ofs->cr1, USART_CR1_RXNEIE);
|
|
+ if (stm32_port->cr3_irq)
|
|
+ stm32_clr_bits(port, ofs->cr3, stm32_port->cr3_irq);
|
|
+
|
|
+ stm32_clr_bits(port, ofs->cr1, stm32_port->cr1_irq);
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
}
|
|
|
|
@@ -532,7 +663,10 @@ static void stm32_unthrottle(struct uart_port *port)
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
- stm32_set_bits(port, ofs->cr1, USART_CR1_RXNEIE);
|
|
+ if (stm32_port->cr3_irq)
|
|
+ stm32_set_bits(port, ofs->cr3, stm32_port->cr3_irq);
|
|
+
|
|
+ stm32_set_bits(port, ofs->cr1, stm32_port->cr1_irq);
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
}
|
|
|
|
@@ -542,7 +676,10 @@ static void stm32_stop_rx(struct uart_port *port)
|
|
struct stm32_port *stm32_port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
|
|
- stm32_clr_bits(port, ofs->cr1, USART_CR1_RXNEIE);
|
|
+ if (stm32_port->cr3_irq)
|
|
+ stm32_clr_bits(port, ofs->cr3, stm32_port->cr3_irq);
|
|
+
|
|
+ stm32_clr_bits(port, ofs->cr1, stm32_port->cr1_irq);
|
|
}
|
|
|
|
/* Handle breaks - ignored by us */
|
|
@@ -554,8 +691,9 @@ static int stm32_startup(struct uart_port *port)
|
|
{
|
|
struct stm32_port *stm32_port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
- struct stm32_usart_config *cfg = &stm32_port->info->cfg;
|
|
const char *name = to_platform_device(port->dev)->name;
|
|
+ struct dma_async_tx_descriptor *desc = NULL;
|
|
+ dma_cookie_t cookie;
|
|
u32 val;
|
|
int ret;
|
|
|
|
@@ -565,18 +703,35 @@ static int stm32_startup(struct uart_port *port)
|
|
if (ret)
|
|
return ret;
|
|
|
|
- if (cfg->has_wakeup && stm32_port->wakeirq >= 0) {
|
|
- ret = dev_pm_set_dedicated_wake_irq(port->dev,
|
|
- stm32_port->wakeirq);
|
|
- if (ret) {
|
|
- free_irq(port->irq, port);
|
|
- return ret;
|
|
+ /* RX FIFO Flush */
|
|
+ if (ofs->rqr != UNDEF_REG)
|
|
+ stm32_set_bits(port, ofs->rqr, USART_RQR_RXFRQ);
|
|
+
|
|
+ if (stm32_port->rx_ch) {
|
|
+ stm32_port->last_res = RX_BUF_L;
|
|
+ /* Prepare a DMA cyclic transaction */
|
|
+ desc = dmaengine_prep_dma_cyclic(stm32_port->rx_ch,
|
|
+ stm32_port->rx_dma_buf,
|
|
+ RX_BUF_L, RX_BUF_P,
|
|
+ DMA_DEV_TO_MEM,
|
|
+ DMA_PREP_INTERRUPT);
|
|
+ if (!desc) {
|
|
+ dev_err(port->dev, "rx dma prep cyclic failed\n");
|
|
+ return -ENODEV;
|
|
}
|
|
+
|
|
+ desc->callback = stm32_rx_dma_complete;
|
|
+ desc->callback_param = port;
|
|
+
|
|
+ /* Push current DMA transaction in the pending queue */
|
|
+ cookie = dmaengine_submit(desc);
|
|
+
|
|
+ /* Issue pending DMA requests */
|
|
+ dma_async_issue_pending(stm32_port->rx_ch);
|
|
}
|
|
|
|
- val = USART_CR1_RXNEIE | USART_CR1_TE | USART_CR1_RE;
|
|
- if (stm32_port->fifoen)
|
|
- val |= USART_CR1_FIFOEN;
|
|
+ /* RX enabling */
|
|
+ val = stm32_port->cr1_irq | USART_CR1_RE;
|
|
stm32_set_bits(port, ofs->cr1, val);
|
|
|
|
return 0;
|
|
@@ -587,18 +742,65 @@ static void stm32_shutdown(struct uart_port *port)
|
|
struct stm32_port *stm32_port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
struct stm32_usart_config *cfg = &stm32_port->info->cfg;
|
|
- u32 val;
|
|
+ u32 val, isr;
|
|
+ int ret;
|
|
|
|
- val = USART_CR1_TXEIE | USART_CR1_RXNEIE | USART_CR1_TE | USART_CR1_RE;
|
|
+ val = USART_CR1_TXEIE | USART_CR1_TE;
|
|
+ val |= stm32_port->cr1_irq | USART_CR1_RE;
|
|
val |= BIT(cfg->uart_enable_bit);
|
|
if (stm32_port->fifoen)
|
|
val |= USART_CR1_FIFOEN;
|
|
+
|
|
+ ret = readl_relaxed_poll_timeout(port->membase + ofs->isr,
|
|
+ isr,
|
|
+ (isr & USART_SR_TC),
|
|
+ 10, 100000);
|
|
+
|
|
+ if (ret)
|
|
+ dev_err(port->dev, "transmission complete not set\n");
|
|
+
|
|
stm32_clr_bits(port, ofs->cr1, val);
|
|
|
|
- dev_pm_clear_wake_irq(port->dev);
|
|
+ if (stm32_port->fifoen)
|
|
+ stm32_clr_bits(port, ofs->cr3,
|
|
+ USART_CR3_TXFTIE | USART_CR3_RXFTIE);
|
|
+
|
|
+ if (stm32_port->rx_ch)
|
|
+ dmaengine_terminate_async(stm32_port->rx_ch);
|
|
+
|
|
free_irq(port->irq, port);
|
|
}
|
|
|
|
+static int stm32_get_databits(struct ktermios *termios)
|
|
+{
|
|
+ unsigned int bits;
|
|
+
|
|
+ tcflag_t cflag = termios->c_cflag;
|
|
+
|
|
+ switch (cflag & CSIZE) {
|
|
+ /*
|
|
+ * CSIZE settings are not necessarily supported in hardware.
|
|
+ * CSIZE unsupported configurations are handled here to set word length
|
|
+ * to 8 bits word as default configuration and to print debug message.
|
|
+ */
|
|
+ case CS5:
|
|
+ bits = 5;
|
|
+ break;
|
|
+ case CS6:
|
|
+ bits = 6;
|
|
+ break;
|
|
+ case CS7:
|
|
+ bits = 7;
|
|
+ break;
|
|
+ /* default including CS8 */
|
|
+ default:
|
|
+ bits = 8;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return (bits);
|
|
+}
|
|
+
|
|
static void stm32_set_termios(struct uart_port *port, struct ktermios *termios,
|
|
struct ktermios *old)
|
|
{
|
|
@@ -606,11 +808,12 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios,
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
struct stm32_usart_config *cfg = &stm32_port->info->cfg;
|
|
struct serial_rs485 *rs485conf = &port->rs485;
|
|
- unsigned int baud;
|
|
+ unsigned int baud, bits;
|
|
u32 usartdiv, mantissa, fraction, oversampling;
|
|
tcflag_t cflag = termios->c_cflag;
|
|
- u32 cr1, cr2, cr3;
|
|
+ u32 cr1, cr2, cr3, isr;
|
|
unsigned long flags;
|
|
+ int ret;
|
|
|
|
if (!stm32_port->hw_flow_control)
|
|
cflag &= ~CRTSCTS;
|
|
@@ -619,29 +822,80 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios,
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
|
|
+ ret = readl_relaxed_poll_timeout_atomic(port->membase + ofs->isr,
|
|
+ isr,
|
|
+ (isr & USART_SR_TC),
|
|
+ 10, 100000);
|
|
+
|
|
+ if (ret)
|
|
+ dev_err(port->dev, "transmission complete not set\n");
|
|
+
|
|
/* Stop serial port and reset value */
|
|
writel_relaxed(0, port->membase + ofs->cr1);
|
|
|
|
- cr1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE;
|
|
+ /* flush RX & TX FIFO */
|
|
+ if (ofs->rqr != UNDEF_REG)
|
|
+ stm32_set_bits(port, ofs->rqr,
|
|
+ USART_RQR_TXFRQ | USART_RQR_RXFRQ);
|
|
|
|
+ cr1 = USART_CR1_TE | USART_CR1_RE;
|
|
if (stm32_port->fifoen)
|
|
cr1 |= USART_CR1_FIFOEN;
|
|
cr2 = 0;
|
|
- cr3 = 0;
|
|
+
|
|
+ /* Tx and RX FIFO configuration */
|
|
+ cr3 = readl_relaxed(port->membase + ofs->cr3);
|
|
+ cr3 &= USART_CR3_TXFTIE | USART_CR3_RXFTIE;
|
|
+ if (stm32_port->fifoen) {
|
|
+ cr3 |= USART_CR3_TXFTCFG_HALF << USART_CR3_TXFTCFG_SHIFT;
|
|
+ cr3 |= USART_CR3_RXFTCFG_HALF << USART_CR3_RXFTCFG_SHIFT;
|
|
+ }
|
|
|
|
if (cflag & CSTOPB)
|
|
cr2 |= USART_CR2_STOP_2B;
|
|
|
|
+ bits = stm32_get_databits(termios);
|
|
+ stm32_port->rdr_mask = (BIT(bits) - 1);
|
|
+
|
|
if (cflag & PARENB) {
|
|
+ bits++;
|
|
cr1 |= USART_CR1_PCE;
|
|
- if ((cflag & CSIZE) == CS8) {
|
|
- if (cfg->has_7bits_data)
|
|
- cr1 |= USART_CR1_M0;
|
|
- else
|
|
- cr1 |= USART_CR1_M;
|
|
- }
|
|
}
|
|
|
|
+ /* Word length configuration:
|
|
+ * CS8 + parity, 9 bits word aka [M1:M0] = 0b01
|
|
+ * CS7 or (CS6 + parity), 7 bits word aka [M1:M0] = 0b10
|
|
+ * CS8 or (CS7 + parity), 8 bits word aka [M1:M0] = 0b00
|
|
+ * M0 and M1 already cleared by cr1 initialization.
|
|
+ */
|
|
+ if (bits == 9)
|
|
+ cr1 |= USART_CR1_M0;
|
|
+ else if ((bits == 7) && cfg->has_7bits_data)
|
|
+ cr1 |= USART_CR1_M1;
|
|
+ else if (bits != 8)
|
|
+ dev_dbg(port->dev, "Unsupported data bits config: %u bits\n"
|
|
+ , bits);
|
|
+
|
|
+ if (ofs->rtor != UNDEF_REG && (stm32_port->rx_ch ||
|
|
+ stm32_port->fifoen)) {
|
|
+ if (cflag & CSTOPB)
|
|
+ bits = bits + 3; /* 1 start bit + 2 stop bits */
|
|
+ else
|
|
+ bits = bits + 2; /* 1 start bit + 1 stop bit */
|
|
+
|
|
+ /* RX timeout irq to occur after last stop bit + bits */
|
|
+ stm32_port->cr1_irq = USART_CR1_RTOIE;
|
|
+ writel_relaxed(bits, port->membase + ofs->rtor);
|
|
+ cr2 |= USART_CR2_RTOEN;
|
|
+
|
|
+ /* Not using dma, enable fifo threshold irq */
|
|
+ if (!stm32_port->rx_ch)
|
|
+ stm32_port->cr3_irq = USART_CR3_RXFTIE;
|
|
+ }
|
|
+
|
|
+ cr1 |= stm32_port->cr1_irq;
|
|
+ cr3 |= stm32_port->cr3_irq;
|
|
+
|
|
if (cflag & PARODD)
|
|
cr1 |= USART_CR1_PS;
|
|
|
|
@@ -679,14 +933,14 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios,
|
|
if (termios->c_iflag & INPCK)
|
|
port->read_status_mask |= USART_SR_PE | USART_SR_FE;
|
|
if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK))
|
|
- port->read_status_mask |= USART_SR_LBD;
|
|
+ port->read_status_mask |= USART_SR_FE;
|
|
|
|
/* Characters to ignore */
|
|
port->ignore_status_mask = 0;
|
|
if (termios->c_iflag & IGNPAR)
|
|
port->ignore_status_mask = USART_SR_PE | USART_SR_FE;
|
|
if (termios->c_iflag & IGNBRK) {
|
|
- port->ignore_status_mask |= USART_SR_LBD;
|
|
+ port->ignore_status_mask |= USART_SR_FE;
|
|
/*
|
|
* If we're ignoring parity and break indicators,
|
|
* ignore overruns too (for real raw support).
|
|
@@ -699,8 +953,16 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios,
|
|
if ((termios->c_cflag & CREAD) == 0)
|
|
port->ignore_status_mask |= USART_SR_DUMMY_RX;
|
|
|
|
- if (stm32_port->rx_ch)
|
|
+ if (stm32_port->rx_ch) {
|
|
+ /*
|
|
+ * Setup DMA to collect only valid data and enable error irqs.
|
|
+ * This also enables break reception when using dma.
|
|
+ */
|
|
+ cr1 |= USART_CR1_PEIE;
|
|
+ cr3 |= USART_CR3_EIE;
|
|
cr3 |= USART_CR3_DMAR;
|
|
+ cr3 |= USART_CR3_DDRE;
|
|
+ }
|
|
|
|
if (rs485conf->flags & SER_RS485_ENABLED) {
|
|
stm32_config_reg_rs485(&cr1, &cr3,
|
|
@@ -715,8 +977,10 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios,
|
|
}
|
|
|
|
} else {
|
|
- cr3 &= ~(USART_CR3_DEM | USART_CR3_DEP);
|
|
- cr1 &= ~(USART_CR1_DEDT_MASK | USART_CR1_DEAT_MASK);
|
|
+ cr3 &= ~USART_CR3_DEM;
|
|
+ cr3 &= ~USART_CR3_DEP;
|
|
+ cr1 &= ~USART_CR1_DEDT_MASK;
|
|
+ cr1 &= ~USART_CR1_DEAT_MASK;
|
|
}
|
|
|
|
writel_relaxed(cr3, port->membase + ofs->cr3);
|
|
@@ -765,13 +1029,13 @@ static void stm32_pm(struct uart_port *port, unsigned int state,
|
|
|
|
switch (state) {
|
|
case UART_PM_STATE_ON:
|
|
- clk_prepare_enable(stm32port->clk);
|
|
+ pm_runtime_get_sync(port->dev);
|
|
break;
|
|
case UART_PM_STATE_OFF:
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
stm32_clr_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit));
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
- clk_disable_unprepare(stm32port->clk);
|
|
+ pm_runtime_put_sync(port->dev);
|
|
break;
|
|
}
|
|
}
|
|
@@ -788,6 +1052,7 @@ static const struct uart_ops stm32_uart_ops = {
|
|
.break_ctl = stm32_break_ctl,
|
|
.startup = stm32_startup,
|
|
.shutdown = stm32_shutdown,
|
|
+ .flush_buffer = stm32_flush_buffer,
|
|
.set_termios = stm32_set_termios,
|
|
.pm = stm32_pm,
|
|
.type = stm32_type,
|
|
@@ -802,20 +1067,60 @@ static int stm32_init_port(struct stm32_port *stm32port,
|
|
{
|
|
struct uart_port *port = &stm32port->port;
|
|
struct resource *res;
|
|
+ struct pinctrl *uart_pinctrl;
|
|
int ret;
|
|
|
|
port->iotype = UPIO_MEM;
|
|
port->flags = UPF_BOOT_AUTOCONF;
|
|
port->ops = &stm32_uart_ops;
|
|
port->dev = &pdev->dev;
|
|
- port->irq = platform_get_irq(pdev, 0);
|
|
- port->rs485_config = stm32_config_rs485;
|
|
|
|
+ ret = platform_get_irq_byname(pdev, "event");
|
|
+ if (ret <= 0) {
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Can't get event IRQ: %d\n",
|
|
+ ret);
|
|
+ return ret ? ret : -ENODEV;
|
|
+ }
|
|
+ port->irq = ret;
|
|
+
|
|
+ port->fifosize = stm32port->info->cfg.fifosize;
|
|
+
|
|
+ port->rs485_config = stm32_config_rs485;
|
|
stm32_init_rs485(port, pdev);
|
|
|
|
- stm32port->wakeirq = platform_get_irq(pdev, 1);
|
|
+ if (stm32port->info->cfg.has_wakeup) {
|
|
+ stm32port->wakeirq = platform_get_irq_byname(pdev, "wakeup");
|
|
+ if (stm32port->wakeirq <= 0 && stm32port->wakeirq != -ENXIO) {
|
|
+ if (stm32port->wakeirq != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev,
|
|
+ "Can't get event wake IRQ: %d\n",
|
|
+ stm32port->wakeirq);
|
|
+ return stm32port->wakeirq ? stm32port->wakeirq :
|
|
+ -ENODEV;
|
|
+ }
|
|
+ }
|
|
+
|
|
stm32port->fifoen = stm32port->info->cfg.has_fifo;
|
|
|
|
+ uart_pinctrl = devm_pinctrl_get(&pdev->dev);
|
|
+ if (IS_ERR(uart_pinctrl)) {
|
|
+ ret = PTR_ERR(uart_pinctrl);
|
|
+ if (ret != -ENODEV) {
|
|
+ dev_err(&pdev->dev,"Can't get pinctrl, error %d\n",
|
|
+ ret);
|
|
+ return ret;
|
|
+ }
|
|
+ stm32port->console_pins = ERR_PTR(-ENODEV);
|
|
+ }
|
|
+ else
|
|
+ stm32port->console_pins = pinctrl_lookup_state
|
|
+ (uart_pinctrl,"no_console_suspend");
|
|
+
|
|
+ if (IS_ERR(stm32port->console_pins)
|
|
+ && PTR_ERR(stm32port->console_pins) != -ENODEV)
|
|
+ return PTR_ERR(stm32port->console_pins);
|
|
+
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
port->membase = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(port->membase))
|
|
@@ -862,7 +1167,11 @@ static struct stm32_port *stm32_of_get_stm32_port(struct platform_device *pdev)
|
|
stm32_ports[id].hw_flow_control = of_property_read_bool(np,
|
|
"st,hw-flow-ctrl");
|
|
stm32_ports[id].port.line = id;
|
|
+ stm32_ports[id].cr1_irq = USART_CR1_RXNEIE;
|
|
+ stm32_ports[id].cr3_irq = 0;
|
|
stm32_ports[id].last_res = RX_BUF_L;
|
|
+ stm32_ports[id].rx_dma_buf = 0;
|
|
+ stm32_ports[id].tx_dma_buf = 0;
|
|
return &stm32_ports[id];
|
|
}
|
|
|
|
@@ -884,15 +1193,15 @@ static int stm32_of_dma_rx_probe(struct stm32_port *stm32port,
|
|
struct uart_port *port = &stm32port->port;
|
|
struct device *dev = &pdev->dev;
|
|
struct dma_slave_config config;
|
|
- struct dma_async_tx_descriptor *desc = NULL;
|
|
- dma_cookie_t cookie;
|
|
int ret;
|
|
|
|
/* Request DMA RX channel */
|
|
- stm32port->rx_ch = dma_request_slave_channel(dev, "rx");
|
|
- if (!stm32port->rx_ch) {
|
|
- dev_info(dev, "rx dma alloc failed\n");
|
|
- return -ENODEV;
|
|
+ stm32port->rx_ch = dma_request_chan_linked(dev, "rx");
|
|
+ if (IS_ERR(stm32port->rx_ch)) {
|
|
+ ret = PTR_ERR(stm32port->rx_ch);
|
|
+ stm32port->rx_ch = NULL;
|
|
+ dev_dbg(dev, "cannot get the DMA channel.\n");
|
|
+ return ret;
|
|
}
|
|
stm32port->rx_buf = dma_alloc_coherent(&pdev->dev, RX_BUF_L,
|
|
&stm32port->rx_dma_buf,
|
|
@@ -914,27 +1223,6 @@ static int stm32_of_dma_rx_probe(struct stm32_port *stm32port,
|
|
goto config_err;
|
|
}
|
|
|
|
- /* Prepare a DMA cyclic transaction */
|
|
- desc = dmaengine_prep_dma_cyclic(stm32port->rx_ch,
|
|
- stm32port->rx_dma_buf,
|
|
- RX_BUF_L, RX_BUF_P, DMA_DEV_TO_MEM,
|
|
- DMA_PREP_INTERRUPT);
|
|
- if (!desc) {
|
|
- dev_err(dev, "rx dma prep cyclic failed\n");
|
|
- ret = -ENODEV;
|
|
- goto config_err;
|
|
- }
|
|
-
|
|
- /* No callback as dma buffer is drained on usart interrupt */
|
|
- desc->callback = NULL;
|
|
- desc->callback_param = NULL;
|
|
-
|
|
- /* Push current DMA transaction in the pending queue */
|
|
- cookie = dmaengine_submit(desc);
|
|
-
|
|
- /* Issue pending DMA requests */
|
|
- dma_async_issue_pending(stm32port->rx_ch);
|
|
-
|
|
return 0;
|
|
|
|
config_err:
|
|
@@ -943,7 +1231,7 @@ static int stm32_of_dma_rx_probe(struct stm32_port *stm32port,
|
|
stm32port->rx_dma_buf);
|
|
|
|
alloc_err:
|
|
- dma_release_channel(stm32port->rx_ch);
|
|
+ dma_release_chan_linked(dev, stm32port->rx_ch);
|
|
stm32port->rx_ch = NULL;
|
|
|
|
return ret;
|
|
@@ -963,7 +1251,7 @@ static int stm32_of_dma_tx_probe(struct stm32_port *stm32port,
|
|
/* Request DMA TX channel */
|
|
stm32port->tx_ch = dma_request_slave_channel(dev, "tx");
|
|
if (!stm32port->tx_ch) {
|
|
- dev_info(dev, "tx dma alloc failed\n");
|
|
+ dev_dbg(dev, "cannot get the DMA channel.\n");
|
|
return -ENODEV;
|
|
}
|
|
stm32port->tx_buf = dma_alloc_coherent(&pdev->dev, TX_BUF_L,
|
|
@@ -1020,15 +1308,18 @@ static int stm32_serial_probe(struct platform_device *pdev)
|
|
if (ret)
|
|
return ret;
|
|
|
|
- if (stm32port->info->cfg.has_wakeup && stm32port->wakeirq >= 0) {
|
|
+ if (stm32port->wakeirq > 0) {
|
|
ret = device_init_wakeup(&pdev->dev, true);
|
|
if (ret)
|
|
goto err_uninit;
|
|
- }
|
|
|
|
- ret = uart_add_one_port(&stm32_usart_driver, &stm32port->port);
|
|
- if (ret)
|
|
- goto err_nowup;
|
|
+ ret = dev_pm_set_dedicated_wake_irq(&pdev->dev,
|
|
+ stm32port->wakeirq);
|
|
+ if (ret)
|
|
+ goto err_nowup;
|
|
+
|
|
+ device_set_wakeup_enable(&pdev->dev, false);
|
|
+ }
|
|
|
|
ret = stm32_of_dma_rx_probe(stm32port, pdev);
|
|
if (ret)
|
|
@@ -1040,10 +1331,41 @@ static int stm32_serial_probe(struct platform_device *pdev)
|
|
|
|
platform_set_drvdata(pdev, &stm32port->port);
|
|
|
|
+ ret = uart_add_one_port(&stm32_usart_driver, &stm32port->port);
|
|
+ if (ret)
|
|
+ goto err_dma;
|
|
+
|
|
+ pm_runtime_get_noresume(&pdev->dev);
|
|
+ pm_runtime_set_active(&pdev->dev);
|
|
+ pm_runtime_enable(&pdev->dev);
|
|
+ pm_runtime_put_sync(&pdev->dev);
|
|
+
|
|
return 0;
|
|
|
|
+err_dma:
|
|
+ if (stm32port->rx_ch)
|
|
+ dma_release_chan_linked(&pdev->dev, stm32port->rx_ch);
|
|
+
|
|
+ if (stm32port->rx_dma_buf)
|
|
+ dma_free_coherent(&pdev->dev,
|
|
+ RX_BUF_L, stm32port->rx_buf,
|
|
+ stm32port->rx_dma_buf);
|
|
+
|
|
+ if (stm32port->tx_ch) {
|
|
+ dmaengine_terminate_async(stm32port->tx_ch);
|
|
+ dma_release_channel(stm32port->tx_ch);
|
|
+ }
|
|
+
|
|
+ if (stm32port->tx_dma_buf)
|
|
+ dma_free_coherent(&pdev->dev,
|
|
+ TX_BUF_L, stm32port->tx_buf,
|
|
+ stm32port->tx_dma_buf);
|
|
+
|
|
+ if (stm32port->wakeirq > 0)
|
|
+ dev_pm_clear_wake_irq(&pdev->dev);
|
|
+
|
|
err_nowup:
|
|
- if (stm32port->info->cfg.has_wakeup && stm32port->wakeirq >= 0)
|
|
+ if (stm32port->wakeirq > 0)
|
|
device_init_wakeup(&pdev->dev, false);
|
|
|
|
err_uninit:
|
|
@@ -1057,12 +1379,22 @@ static int stm32_serial_remove(struct platform_device *pdev)
|
|
struct uart_port *port = platform_get_drvdata(pdev);
|
|
struct stm32_port *stm32_port = to_stm32_port(port);
|
|
struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
- struct stm32_usart_config *cfg = &stm32_port->info->cfg;
|
|
+ int err;
|
|
+ u32 cr3;
|
|
|
|
- stm32_clr_bits(port, ofs->cr3, USART_CR3_DMAR);
|
|
+ pm_runtime_get_sync(&pdev->dev);
|
|
+
|
|
+ err = uart_remove_one_port(&stm32_usart_driver, port);
|
|
+
|
|
+ stm32_clr_bits(port, ofs->cr1, USART_CR1_PEIE);
|
|
+ cr3 = readl_relaxed(port->membase + ofs->cr3);
|
|
+ cr3 &= ~USART_CR3_EIE;
|
|
+ cr3 &= ~USART_CR3_DMAR;
|
|
+ cr3 &= ~USART_CR3_DDRE;
|
|
+ writel_relaxed(cr3, port->membase + ofs->cr3);
|
|
|
|
if (stm32_port->rx_ch)
|
|
- dma_release_channel(stm32_port->rx_ch);
|
|
+ dma_release_chan_linked(&pdev->dev, stm32_port->rx_ch);
|
|
|
|
if (stm32_port->rx_dma_buf)
|
|
dma_free_coherent(&pdev->dev,
|
|
@@ -1071,20 +1403,27 @@ static int stm32_serial_remove(struct platform_device *pdev)
|
|
|
|
stm32_clr_bits(port, ofs->cr3, USART_CR3_DMAT);
|
|
|
|
- if (stm32_port->tx_ch)
|
|
+ if (stm32_port->tx_ch) {
|
|
+ dmaengine_terminate_async(stm32_port->tx_ch);
|
|
dma_release_channel(stm32_port->tx_ch);
|
|
+ }
|
|
|
|
if (stm32_port->tx_dma_buf)
|
|
dma_free_coherent(&pdev->dev,
|
|
TX_BUF_L, stm32_port->tx_buf,
|
|
stm32_port->tx_dma_buf);
|
|
|
|
- if (cfg->has_wakeup && stm32_port->wakeirq >= 0)
|
|
+ if (stm32_port->wakeirq > 0) {
|
|
+ dev_pm_clear_wake_irq(&pdev->dev);
|
|
device_init_wakeup(&pdev->dev, false);
|
|
+ }
|
|
|
|
clk_disable_unprepare(stm32_port->clk);
|
|
|
|
- return uart_remove_one_port(&stm32_usart_driver, port);
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
+ pm_runtime_put_noidle(&pdev->dev);
|
|
+
|
|
+ return err;
|
|
}
|
|
|
|
|
|
@@ -1195,7 +1534,7 @@ static void stm32_serial_enable_wakeup(struct uart_port *port, bool enable)
|
|
struct stm32_usart_config *cfg = &stm32_port->info->cfg;
|
|
u32 val;
|
|
|
|
- if (!cfg->has_wakeup || stm32_port->wakeirq < 0)
|
|
+ if (stm32_port->wakeirq <= 0)
|
|
return;
|
|
|
|
if (enable) {
|
|
@@ -1207,21 +1546,36 @@ static void stm32_serial_enable_wakeup(struct uart_port *port, bool enable)
|
|
val |= USART_CR3_WUS_START_BIT | USART_CR3_WUFIE;
|
|
writel_relaxed(val, port->membase + ofs->cr3);
|
|
stm32_set_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit));
|
|
+ enable_irq_wake(stm32_port->wakeirq);
|
|
} else {
|
|
stm32_clr_bits(port, ofs->cr1, USART_CR1_UESM);
|
|
+ disable_irq_wake(stm32_port->wakeirq);
|
|
}
|
|
}
|
|
|
|
static int stm32_serial_suspend(struct device *dev)
|
|
{
|
|
struct uart_port *port = dev_get_drvdata(dev);
|
|
+ struct stm32_port *stm32_port = to_stm32_port(port);
|
|
+
|
|
+ if (device_may_wakeup(dev) || dev->power.wakeup_path)
|
|
+ stm32_serial_enable_wakeup(port, true);
|
|
|
|
uart_suspend_port(&stm32_usart_driver, port);
|
|
|
|
- if (device_may_wakeup(dev))
|
|
- stm32_serial_enable_wakeup(port, true);
|
|
- else
|
|
- stm32_serial_enable_wakeup(port, false);
|
|
+ if (uart_console(port) && !console_suspend_enabled) {
|
|
+ if (IS_ERR(stm32_port->console_pins)) {
|
|
+ dev_err(dev, "no_console_suspend pinctrl not found\n");
|
|
+ return PTR_ERR(stm32_port->console_pins);
|
|
+ }
|
|
+
|
|
+ pinctrl_select_state(dev->pins->p, stm32_port->console_pins);
|
|
+ } else {
|
|
+ if (device_may_wakeup(dev) || dev->power.wakeup_path)
|
|
+ pinctrl_pm_select_idle_state(dev);
|
|
+ else
|
|
+ pinctrl_pm_select_sleep_state(dev);
|
|
+ }
|
|
|
|
return 0;
|
|
}
|
|
@@ -1230,14 +1584,40 @@ static int stm32_serial_resume(struct device *dev)
|
|
{
|
|
struct uart_port *port = dev_get_drvdata(dev);
|
|
|
|
- if (device_may_wakeup(dev))
|
|
+ pinctrl_pm_select_default_state(dev);
|
|
+
|
|
+ if (device_may_wakeup(dev) || dev->power.wakeup_path)
|
|
stm32_serial_enable_wakeup(port, false);
|
|
|
|
return uart_resume_port(&stm32_usart_driver, port);
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
+#ifdef CONFIG_PM
|
|
+static int stm32_serial_runtime_suspend(struct device *dev)
|
|
+{
|
|
+ struct uart_port *port = dev_get_drvdata(dev);
|
|
+ struct stm32_port *stm32port = container_of(port,
|
|
+ struct stm32_port, port);
|
|
+
|
|
+ clk_disable_unprepare(stm32port->clk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_serial_runtime_resume(struct device *dev)
|
|
+{
|
|
+ struct uart_port *port = dev_get_drvdata(dev);
|
|
+ struct stm32_port *stm32port = container_of(port,
|
|
+ struct stm32_port, port);
|
|
+
|
|
+ return clk_prepare_enable(stm32port->clk);
|
|
+}
|
|
+#endif /* CONFIG_PM */
|
|
+
|
|
static const struct dev_pm_ops stm32_serial_pm_ops = {
|
|
+ SET_RUNTIME_PM_OPS(stm32_serial_runtime_suspend,
|
|
+ stm32_serial_runtime_resume, NULL)
|
|
SET_SYSTEM_SLEEP_PM_OPS(stm32_serial_suspend, stm32_serial_resume)
|
|
};
|
|
|
|
diff --git a/drivers/tty/serial/stm32-usart.h b/drivers/tty/serial/stm32-usart.h
|
|
index 6f294e2..ddd87c9 100644
|
|
--- a/drivers/tty/serial/stm32-usart.h
|
|
+++ b/drivers/tty/serial/stm32-usart.h
|
|
@@ -27,6 +27,7 @@ struct stm32_usart_config {
|
|
bool has_7bits_data;
|
|
bool has_wakeup;
|
|
bool has_fifo;
|
|
+ int fifosize;
|
|
};
|
|
|
|
struct stm32_usart_info {
|
|
@@ -54,6 +55,7 @@ struct stm32_usart_info stm32f4_info = {
|
|
.cfg = {
|
|
.uart_enable_bit = 13,
|
|
.has_7bits_data = false,
|
|
+ .fifosize = 1,
|
|
}
|
|
};
|
|
|
|
@@ -74,6 +76,7 @@ struct stm32_usart_info stm32f7_info = {
|
|
.cfg = {
|
|
.uart_enable_bit = 0,
|
|
.has_7bits_data = true,
|
|
+ .fifosize = 1,
|
|
}
|
|
};
|
|
|
|
@@ -96,19 +99,19 @@ struct stm32_usart_info stm32h7_info = {
|
|
.has_7bits_data = true,
|
|
.has_wakeup = true,
|
|
.has_fifo = true,
|
|
+ .fifosize = 16,
|
|
}
|
|
};
|
|
|
|
/* USART_SR (F4) / USART_ISR (F7) */
|
|
#define USART_SR_PE BIT(0)
|
|
#define USART_SR_FE BIT(1)
|
|
-#define USART_SR_NF BIT(2)
|
|
+#define USART_SR_NE BIT(2) /* F7 (NF for F4) */
|
|
#define USART_SR_ORE BIT(3)
|
|
#define USART_SR_IDLE BIT(4)
|
|
#define USART_SR_RXNE BIT(5)
|
|
#define USART_SR_TC BIT(6)
|
|
#define USART_SR_TXE BIT(7)
|
|
-#define USART_SR_LBD BIT(8)
|
|
#define USART_SR_CTSIF BIT(9)
|
|
#define USART_SR_CTS BIT(10) /* F7 */
|
|
#define USART_SR_RTOF BIT(11) /* F7 */
|
|
@@ -120,14 +123,11 @@ struct stm32_usart_info stm32h7_info = {
|
|
#define USART_SR_SBKF BIT(18) /* F7 */
|
|
#define USART_SR_WUF BIT(20) /* H7 */
|
|
#define USART_SR_TEACK BIT(21) /* F7 */
|
|
-#define USART_SR_ERR_MASK (USART_SR_LBD | USART_SR_ORE | \
|
|
- USART_SR_FE | USART_SR_PE)
|
|
+#define USART_SR_ERR_MASK (USART_SR_ORE | USART_SR_NE | USART_SR_FE\
|
|
+ | USART_SR_PE)
|
|
/* Dummy bits */
|
|
#define USART_SR_DUMMY_RX BIT(16)
|
|
|
|
-/* USART_ICR (F7) */
|
|
-#define USART_CR_TC BIT(6)
|
|
-
|
|
/* USART_DR */
|
|
#define USART_DR_MASK GENMASK(8, 0)
|
|
|
|
@@ -151,8 +151,7 @@ struct stm32_usart_info stm32h7_info = {
|
|
#define USART_CR1_PS BIT(9)
|
|
#define USART_CR1_PCE BIT(10)
|
|
#define USART_CR1_WAKE BIT(11)
|
|
-#define USART_CR1_M BIT(12)
|
|
-#define USART_CR1_M0 BIT(12) /* F7 */
|
|
+#define USART_CR1_M0 BIT(12) /* F7 (CR1_M for F4) */
|
|
#define USART_CR1_MME BIT(13) /* F7 */
|
|
#define USART_CR1_CMIE BIT(14) /* F7 */
|
|
#define USART_CR1_OVER8 BIT(15)
|
|
@@ -169,8 +168,6 @@ struct stm32_usart_info stm32h7_info = {
|
|
/* USART_CR2 */
|
|
#define USART_CR2_ADD_MASK GENMASK(3, 0) /* F4 */
|
|
#define USART_CR2_ADDM7 BIT(4) /* F7 */
|
|
-#define USART_CR2_LBDL BIT(5)
|
|
-#define USART_CR2_LBDIE BIT(6)
|
|
#define USART_CR2_LBCL BIT(8)
|
|
#define USART_CR2_CPHA BIT(9)
|
|
#define USART_CR2_CPOL BIT(10)
|
|
@@ -209,6 +206,19 @@ struct stm32_usart_info stm32h7_info = {
|
|
#define USART_CR3_WUS_MASK GENMASK(21, 20) /* H7 */
|
|
#define USART_CR3_WUS_START_BIT BIT(21) /* H7 */
|
|
#define USART_CR3_WUFIE BIT(22) /* H7 */
|
|
+#define USART_CR3_TXFTIE BIT(23) /* H7 */
|
|
+#define USART_CR3_TCBGTIE BIT(24) /* H7 */
|
|
+#define USART_CR3_RXFTCFG_MASK GENMASK(27, 25) /* H7 */
|
|
+#define USART_CR3_RXFTCFG_SHIFT 25 /* H7 */
|
|
+#define USART_CR3_RXFTIE BIT(28) /* H7 */
|
|
+#define USART_CR3_TXFTCFG_MASK GENMASK(31, 29) /* H7 */
|
|
+#define USART_CR3_TXFTCFG_SHIFT 29 /* H7 */
|
|
+
|
|
+/* TX FIFO threashold set to half of its depth */
|
|
+#define USART_CR3_TXFTCFG_HALF 0x2
|
|
+
|
|
+/* RX FIFO threashold set to half of its depth */
|
|
+#define USART_CR3_RXFTCFG_HALF 0x2
|
|
|
|
/* USART_GTPR */
|
|
#define USART_GTPR_PSC_MASK GENMASK(7, 0)
|
|
@@ -227,12 +237,10 @@ struct stm32_usart_info stm32h7_info = {
|
|
|
|
/* USART_ICR */
|
|
#define USART_ICR_PECF BIT(0) /* F7 */
|
|
-#define USART_ICR_FFECF BIT(1) /* F7 */
|
|
-#define USART_ICR_NCF BIT(2) /* F7 */
|
|
+#define USART_ICR_FECF BIT(1) /* F7 */
|
|
#define USART_ICR_ORECF BIT(3) /* F7 */
|
|
#define USART_ICR_IDLECF BIT(4) /* F7 */
|
|
#define USART_ICR_TCCF BIT(6) /* F7 */
|
|
-#define USART_ICR_LBDCF BIT(8) /* F7 */
|
|
#define USART_ICR_CTSCF BIT(9) /* F7 */
|
|
#define USART_ICR_RTOCF BIT(11) /* F7 */
|
|
#define USART_ICR_EOBCF BIT(12) /* F7 */
|
|
@@ -242,9 +250,9 @@ struct stm32_usart_info stm32h7_info = {
|
|
#define STM32_SERIAL_NAME "ttySTM"
|
|
#define STM32_MAX_PORTS 8
|
|
|
|
-#define RX_BUF_L 200 /* dma rx buffer length */
|
|
-#define RX_BUF_P RX_BUF_L /* dma rx buffer period */
|
|
-#define TX_BUF_L 200 /* dma tx buffer length */
|
|
+#define RX_BUF_L 4096 /* dma rx buffer length */
|
|
+#define RX_BUF_P (RX_BUF_L / 2) /* dma rx buffer period */
|
|
+#define TX_BUF_L RX_BUF_L /* dma tx buffer length */
|
|
|
|
struct stm32_port {
|
|
struct uart_port port;
|
|
@@ -256,11 +264,17 @@ struct stm32_port {
|
|
struct dma_chan *tx_ch; /* dma tx channel */
|
|
dma_addr_t tx_dma_buf; /* dma tx buffer bus address */
|
|
unsigned char *tx_buf; /* dma tx buffer cpu address */
|
|
+ u32 cr1_irq; /* USART_CR1_RXNEIE or RTOIE */
|
|
+ u32 cr3_irq; /* USART_CR3_RXFTIE */
|
|
int last_res;
|
|
bool tx_dma_busy; /* dma tx busy */
|
|
bool hw_flow_control;
|
|
bool fifoen;
|
|
int wakeirq;
|
|
+ struct pinctrl_state *console_pins;
|
|
+ int rdr_mask; /* receive data register mask */
|
|
+ struct dma_tx_state state;
|
|
+ enum dma_status status;
|
|
};
|
|
|
|
static struct stm32_port stm32_ports[STM32_MAX_PORTS];
|
|
diff --git a/drivers/usb/dwc2/Makefile b/drivers/usb/dwc2/Makefile
|
|
index 440320c..2bcd694 100644
|
|
--- a/drivers/usb/dwc2/Makefile
|
|
+++ b/drivers/usb/dwc2/Makefile
|
|
@@ -3,7 +3,7 @@ ccflags-$(CONFIG_USB_DWC2_DEBUG) += -DDEBUG
|
|
ccflags-$(CONFIG_USB_DWC2_VERBOSE) += -DVERBOSE_DEBUG
|
|
|
|
obj-$(CONFIG_USB_DWC2) += dwc2.o
|
|
-dwc2-y := core.o core_intr.o platform.o
|
|
+dwc2-y := core.o core_intr.o platform.o drd.o
|
|
dwc2-y += params.o
|
|
|
|
ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),)
|
|
diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c
|
|
index 633ba01..8ad88d2 100644
|
|
--- a/drivers/usb/dwc2/core.c
|
|
+++ b/drivers/usb/dwc2/core.c
|
|
@@ -83,6 +83,7 @@ int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg)
|
|
gr->pcgcctl1 = dwc2_readl(hsotg, PCGCCTL1);
|
|
gr->glpmcfg = dwc2_readl(hsotg, GLPMCFG);
|
|
gr->gi2cctl = dwc2_readl(hsotg, GI2CCTL);
|
|
+ gr->ggpio = dwc2_readl(hsotg, GGPIO);
|
|
gr->pcgcctl = dwc2_readl(hsotg, PCGCTL);
|
|
|
|
gr->valid = true;
|
|
@@ -112,21 +113,82 @@ int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg)
|
|
gr->valid = false;
|
|
|
|
dwc2_writel(hsotg, 0xffffffff, GINTSTS);
|
|
+ dwc2_writel(hsotg, gr->gahbcfg, GAHBCFG);
|
|
+ dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG);
|
|
dwc2_writel(hsotg, gr->gotgctl, GOTGCTL);
|
|
dwc2_writel(hsotg, gr->gintmsk, GINTMSK);
|
|
- dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG);
|
|
- dwc2_writel(hsotg, gr->gahbcfg, GAHBCFG);
|
|
dwc2_writel(hsotg, gr->grxfsiz, GRXFSIZ);
|
|
dwc2_writel(hsotg, gr->gnptxfsiz, GNPTXFSIZ);
|
|
dwc2_writel(hsotg, gr->gdfifocfg, GDFIFOCFG);
|
|
dwc2_writel(hsotg, gr->pcgcctl1, PCGCCTL1);
|
|
dwc2_writel(hsotg, gr->glpmcfg, GLPMCFG);
|
|
dwc2_writel(hsotg, gr->pcgcctl, PCGCTL);
|
|
+ dwc2_writel(hsotg, gr->ggpio, GGPIO);
|
|
dwc2_writel(hsotg, gr->gi2cctl, GI2CCTL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
+int dwc2_backup_registers(struct dwc2_hsotg *hsotg)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ /* Backup all registers */
|
|
+ ret = dwc2_backup_global_registers(hsotg);
|
|
+ if (ret) {
|
|
+ dev_err(hsotg->dev, "%s: failed to backup global registers\n",
|
|
+ __func__);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (dwc2_is_host_mode(hsotg)) {
|
|
+ ret = dwc2_backup_host_registers(hsotg);
|
|
+ if (ret) {
|
|
+ dev_err(hsotg->dev, "%s: failed to backup host registers\n",
|
|
+ __func__);
|
|
+ return ret;
|
|
+ }
|
|
+ } else {
|
|
+ ret = dwc2_backup_device_registers(hsotg);
|
|
+ if (ret) {
|
|
+ dev_err(hsotg->dev, "%s: failed to backup device registers\n",
|
|
+ __func__);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int dwc2_restore_registers(struct dwc2_hsotg *hsotg)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = dwc2_restore_global_registers(hsotg);
|
|
+ if (ret) {
|
|
+ dev_err(hsotg->dev, "%s: failed to restore registers\n",
|
|
+ __func__);
|
|
+ return ret;
|
|
+ }
|
|
+ if (dwc2_is_host_mode(hsotg)) {
|
|
+ ret = dwc2_restore_host_registers(hsotg);
|
|
+ if (ret) {
|
|
+ dev_err(hsotg->dev, "%s: failed to restore host registers\n",
|
|
+ __func__);
|
|
+ return ret;
|
|
+ }
|
|
+ } else {
|
|
+ ret = dwc2_restore_device_registers(hsotg, 0);
|
|
+ if (ret) {
|
|
+ dev_err(hsotg->dev, "%s: failed to restore device registers\n",
|
|
+ __func__);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
/**
|
|
* dwc2_exit_partial_power_down() - Exit controller from Partial Power Down.
|
|
*
|
|
@@ -136,7 +198,6 @@ int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg)
|
|
int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore)
|
|
{
|
|
u32 pcgcctl;
|
|
- int ret = 0;
|
|
|
|
if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL)
|
|
return -ENOTSUPP;
|
|
@@ -154,31 +215,11 @@ int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore)
|
|
dwc2_writel(hsotg, pcgcctl, PCGCTL);
|
|
|
|
udelay(100);
|
|
- if (restore) {
|
|
- ret = dwc2_restore_global_registers(hsotg);
|
|
- if (ret) {
|
|
- dev_err(hsotg->dev, "%s: failed to restore registers\n",
|
|
- __func__);
|
|
- return ret;
|
|
- }
|
|
- if (dwc2_is_host_mode(hsotg)) {
|
|
- ret = dwc2_restore_host_registers(hsotg);
|
|
- if (ret) {
|
|
- dev_err(hsotg->dev, "%s: failed to restore host registers\n",
|
|
- __func__);
|
|
- return ret;
|
|
- }
|
|
- } else {
|
|
- ret = dwc2_restore_device_registers(hsotg, 0);
|
|
- if (ret) {
|
|
- dev_err(hsotg->dev, "%s: failed to restore device registers\n",
|
|
- __func__);
|
|
- return ret;
|
|
- }
|
|
- }
|
|
- }
|
|
|
|
- return ret;
|
|
+ if (restore)
|
|
+ return dwc2_restore_registers(hsotg);
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
/**
|
|
@@ -189,34 +230,14 @@ int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore)
|
|
int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg)
|
|
{
|
|
u32 pcgcctl;
|
|
- int ret = 0;
|
|
+ int ret;
|
|
|
|
if (!hsotg->params.power_down)
|
|
return -ENOTSUPP;
|
|
|
|
- /* Backup all registers */
|
|
- ret = dwc2_backup_global_registers(hsotg);
|
|
- if (ret) {
|
|
- dev_err(hsotg->dev, "%s: failed to backup global registers\n",
|
|
- __func__);
|
|
+ ret = dwc2_backup_registers(hsotg);
|
|
+ if (ret)
|
|
return ret;
|
|
- }
|
|
-
|
|
- if (dwc2_is_host_mode(hsotg)) {
|
|
- ret = dwc2_backup_host_registers(hsotg);
|
|
- if (ret) {
|
|
- dev_err(hsotg->dev, "%s: failed to backup host registers\n",
|
|
- __func__);
|
|
- return ret;
|
|
- }
|
|
- } else {
|
|
- ret = dwc2_backup_device_registers(hsotg);
|
|
- if (ret) {
|
|
- dev_err(hsotg->dev, "%s: failed to backup device registers\n",
|
|
- __func__);
|
|
- return ret;
|
|
- }
|
|
- }
|
|
|
|
/*
|
|
* Clear any pending interrupts since dwc2 will not be able to
|
|
@@ -238,7 +259,7 @@ int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg)
|
|
pcgcctl |= PCGCTL_STOPPCLK;
|
|
dwc2_writel(hsotg, pcgcctl, PCGCTL);
|
|
|
|
- return ret;
|
|
+ return 0;
|
|
}
|
|
|
|
/**
|
|
diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
|
|
index cc9c93a..74d81f6 100644
|
|
--- a/drivers/usb/dwc2/core.h
|
|
+++ b/drivers/usb/dwc2/core.h
|
|
@@ -393,10 +393,28 @@ enum dwc2_ep0_state {
|
|
* 0 - No
|
|
* 1 - Yes
|
|
* @hird_threshold: Value of BESL or HIRD Threshold.
|
|
+ * @ref_clk_per: Indicates in terms of pico seconds the period
|
|
+ * of ref_clk.
|
|
+ * 62500 - 16MHz
|
|
+ * 58823 - 17MHz
|
|
+ * 52083 - 19.2MHz
|
|
+ * 50000 - 20MHz
|
|
+ * 41666 - 24MHz
|
|
+ * 33333 - 30MHz (default)
|
|
+ * 25000 - 40MHz
|
|
+ * @sof_cnt_wkup_alert: Indicates in term of number of SOF's after which
|
|
+ * the controller should generate an interrupt if the
|
|
+ * device had been in L1 state until that period.
|
|
+ * This is used by SW to initiate Remote WakeUp in the
|
|
+ * controller so as to sync to the uF number from the host.
|
|
* @activate_stm_fs_transceiver: Activate internal transceiver using GGPIO
|
|
* register.
|
|
* 0 - Deactivate the transceiver (default)
|
|
* 1 - Activate the transceiver
|
|
+ * @activate_stm_id_vb_detection: Activate external ID pin and Vbuslevel
|
|
+ * detection using GGPIO register.
|
|
+ * 0 - Deactivate the external level detection (default)
|
|
+ * 1 - Activate the external level detection
|
|
* @g_dma: Enables gadget dma usage (default: autodetect).
|
|
* @g_dma_desc: Enables gadget descriptor DMA (default: autodetect).
|
|
* @g_rx_fifo_size: The periodic rx fifo size for the device, in
|
|
@@ -416,6 +434,9 @@ enum dwc2_ep0_state {
|
|
* back to DWC2_SPEED_PARAM_HIGH while device is gone.
|
|
* 0 - No (default)
|
|
* 1 - Yes
|
|
+ * @service_interval: Enable service interval based scheduling.
|
|
+ * 0 - No
|
|
+ * 1 - Yes
|
|
*
|
|
* The following parameters may be specified when starting the module. These
|
|
* parameters define how the DWC_otg controller should be configured. A
|
|
@@ -461,13 +482,19 @@ struct dwc2_core_params {
|
|
bool lpm_clock_gating;
|
|
bool besl;
|
|
bool hird_threshold_en;
|
|
+ bool service_interval;
|
|
u8 hird_threshold;
|
|
bool activate_stm_fs_transceiver;
|
|
+ bool activate_stm_id_vb_detection;
|
|
bool ipg_isoc_en;
|
|
u16 max_packet_count;
|
|
u32 max_transfer_size;
|
|
u32 ahbcfg;
|
|
|
|
+ /* GREFCLK parameters */
|
|
+ u32 ref_clk_per;
|
|
+ u16 sof_cnt_wkup_alert;
|
|
+
|
|
/* Host parameters */
|
|
bool host_dma;
|
|
bool dma_desc_enable;
|
|
@@ -605,6 +632,10 @@ struct dwc2_core_params {
|
|
* FIFO sizing is enabled 16 to 32768
|
|
* Actual maximum value is autodetected and also
|
|
* the default.
|
|
+ * @service_interval_mode: For enabling service interval based scheduling in the
|
|
+ * controller.
|
|
+ * 0 - Disable
|
|
+ * 1 - Enable
|
|
*/
|
|
struct dwc2_hw_params {
|
|
unsigned op_mode:3;
|
|
@@ -635,6 +666,7 @@ struct dwc2_hw_params {
|
|
unsigned utmi_phy_data_width:2;
|
|
unsigned lpm_mode:1;
|
|
unsigned ipg_isoc_en:1;
|
|
+ unsigned service_interval_mode:1;
|
|
u32 snpsid;
|
|
u32 dev_ep_dirs;
|
|
u32 g_tx_fifo_size[MAX_EPS_CHANNELS];
|
|
@@ -653,6 +685,7 @@ struct dwc2_hw_params {
|
|
* @grxfsiz: Backup of GRXFSIZ register
|
|
* @gnptxfsiz: Backup of GNPTXFSIZ register
|
|
* @gi2cctl: Backup of GI2CCTL register
|
|
+ * @ggpio: Backup of GGPIO register
|
|
* @glpmcfg: Backup of GLPMCFG register
|
|
* @gdfifocfg: Backup of GDFIFOCFG register
|
|
* @pcgcctl: Backup of PCGCCTL register
|
|
@@ -669,6 +702,7 @@ struct dwc2_gregs_backup {
|
|
u32 grxfsiz;
|
|
u32 gnptxfsiz;
|
|
u32 gi2cctl;
|
|
+ u32 ggpio;
|
|
u32 glpmcfg;
|
|
u32 pcgcctl;
|
|
u32 pcgcctl1;
|
|
@@ -828,6 +862,8 @@ struct dwc2_hregs_backup {
|
|
* - USB_DR_MODE_PERIPHERAL
|
|
* - USB_DR_MODE_HOST
|
|
* - USB_DR_MODE_OTG
|
|
+ * @edev: extcon handle
|
|
+ * @edev_nb: extcon notifier
|
|
* @hcd_enabled: Host mode sub-driver initialization indicator.
|
|
* @gadget_enabled: Peripheral mode sub-driver initialization indicator.
|
|
* @ll_hw_enabled: Status of low-level hardware resources.
|
|
@@ -842,6 +878,8 @@ struct dwc2_hregs_backup {
|
|
* removed once all SoCs support usb transceiver.
|
|
* @supplies: Definition of USB power supplies
|
|
* @vbus_supply: Regulator supplying vbus.
|
|
+ * @usb33d: Optional 3.3v regulator used on some stm32 devices to
|
|
+ * supply ID and VBUS detection hardware.
|
|
* @phyif: PHY interface width
|
|
* @lock: Spinlock that protects all the driver data structures
|
|
* @priv: Stores a pointer to the struct usb_hcd
|
|
@@ -974,6 +1012,7 @@ struct dwc2_hregs_backup {
|
|
* @ctrl_out_desc_dma: EP0 OUT data phase desc chain DMA address
|
|
* @ctrl_out_desc: EP0 OUT data phase desc chain pointer
|
|
* @irq: Interrupt request line number
|
|
+ * @wakeirq: Wakeup interrupt request line number
|
|
* @clk: Pointer to otg clock
|
|
* @reset: Pointer to dwc2 reset controller
|
|
* @reset_ecc: Pointer to dwc2 optional reset controller in Stratix10.
|
|
@@ -1014,6 +1053,8 @@ struct dwc2_hsotg {
|
|
struct dwc2_core_params params;
|
|
enum usb_otg_state op_state;
|
|
enum usb_dr_mode dr_mode;
|
|
+ struct extcon_dev *edev;
|
|
+ struct notifier_block edev_nb;
|
|
unsigned int hcd_enabled:1;
|
|
unsigned int gadget_enabled:1;
|
|
unsigned int ll_hw_enabled:1;
|
|
@@ -1025,11 +1066,13 @@ struct dwc2_hsotg {
|
|
struct dwc2_hsotg_plat *plat;
|
|
struct regulator_bulk_data supplies[DWC2_NUM_SUPPLIES];
|
|
struct regulator *vbus_supply;
|
|
+ struct regulator *usb33d;
|
|
u32 phyif;
|
|
|
|
spinlock_t lock;
|
|
void *priv;
|
|
int irq;
|
|
+ int wakeirq;
|
|
struct clk *clk;
|
|
struct reset_control *reset;
|
|
struct reset_control *reset_ecc;
|
|
@@ -1276,6 +1319,8 @@ void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd);
|
|
|
|
void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup,
|
|
int is_host);
|
|
+int dwc2_backup_registers(struct dwc2_hsotg *hsotg);
|
|
+int dwc2_restore_registers(struct dwc2_hsotg *hsotg);
|
|
int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg);
|
|
int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg);
|
|
|
|
@@ -1325,6 +1370,8 @@ static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg)
|
|
return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0;
|
|
}
|
|
|
|
+int dwc2_drd_init(struct dwc2_hsotg *hsotg);
|
|
+
|
|
/*
|
|
* Dump core registers and SPRAM
|
|
*/
|
|
@@ -1341,6 +1388,7 @@ int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2);
|
|
int dwc2_gadget_init(struct dwc2_hsotg *hsotg);
|
|
void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2,
|
|
bool reset);
|
|
+void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg);
|
|
void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg);
|
|
void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2);
|
|
int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode);
|
|
@@ -1354,6 +1402,7 @@ int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg);
|
|
int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg);
|
|
int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg);
|
|
void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg);
|
|
+void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg);
|
|
#else
|
|
static inline int dwc2_hsotg_remove(struct dwc2_hsotg *dwc2)
|
|
{ return 0; }
|
|
@@ -1388,6 +1437,7 @@ static inline int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg)
|
|
static inline int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg)
|
|
{ return 0; }
|
|
static inline void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) {}
|
|
+static inline void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg) {}
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
|
|
diff --git a/drivers/usb/dwc2/debugfs.c b/drivers/usb/dwc2/debugfs.c
|
|
index 22d015b..7f62f4c 100644
|
|
--- a/drivers/usb/dwc2/debugfs.c
|
|
+++ b/drivers/usb/dwc2/debugfs.c
|
|
@@ -701,6 +701,7 @@ static int params_show(struct seq_file *seq, void *v)
|
|
print_param(seq, p, besl);
|
|
print_param(seq, p, hird_threshold_en);
|
|
print_param(seq, p, hird_threshold);
|
|
+ print_param(seq, p, service_interval);
|
|
print_param(seq, p, host_dma);
|
|
print_param(seq, p, g_dma);
|
|
print_param(seq, p, g_dma_desc);
|
|
diff --git a/drivers/usb/dwc2/drd.c b/drivers/usb/dwc2/drd.c
|
|
new file mode 100644
|
|
index 0000000..7d812b7
|
|
--- /dev/null
|
|
+++ b/drivers/usb/dwc2/drd.c
|
|
@@ -0,0 +1,191 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * drd.c - DesignWare USB2 DRD Controller Dual-role support
|
|
+ *
|
|
+ * Copyright (C) 2019 STMicroelectronics
|
|
+ *
|
|
+ * Author(s): Amelie Delaunay <amelie.delaunay@st.com>
|
|
+ */
|
|
+
|
|
+#include <linux/extcon.h>
|
|
+#include <linux/iopoll.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include "core.h"
|
|
+
|
|
+static void dwc2_ovr_init(struct dwc2_hsotg *hsotg)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ u32 gotgctl;
|
|
+
|
|
+ spin_lock_irqsave(&hsotg->lock, flags);
|
|
+
|
|
+ gotgctl = dwc2_readl(hsotg, GOTGCTL);
|
|
+ gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN;
|
|
+ gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS;
|
|
+ gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
|
|
+ dwc2_writel(hsotg, gotgctl, GOTGCTL);
|
|
+
|
|
+ spin_unlock_irqrestore(&hsotg->lock, flags);
|
|
+}
|
|
+
|
|
+static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid)
|
|
+{
|
|
+ u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
|
|
+
|
|
+ /* Check if A-Session is already in the right state */
|
|
+ if ((valid && (gotgctl & GOTGCTL_ASESVLD)) ||
|
|
+ (!valid && !(gotgctl & GOTGCTL_ASESVLD)))
|
|
+ return -EALREADY;
|
|
+
|
|
+ if (valid)
|
|
+ gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL;
|
|
+ else
|
|
+ gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
|
|
+ dwc2_writel(hsotg, gotgctl, GOTGCTL);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid)
|
|
+{
|
|
+ u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
|
|
+
|
|
+ /* Check if B-Session is already in the right state */
|
|
+ if ((valid && (gotgctl & GOTGCTL_BSESVLD)) ||
|
|
+ (!valid && !(gotgctl & GOTGCTL_BSESVLD)))
|
|
+ return -EALREADY;
|
|
+
|
|
+ if (valid)
|
|
+ gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL;
|
|
+ else
|
|
+ gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL);
|
|
+ dwc2_writel(hsotg, gotgctl, GOTGCTL);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dwc2_drd_update(struct dwc2_hsotg *hsotg)
|
|
+{
|
|
+ int avalid, bvalid;
|
|
+ unsigned long flags;
|
|
+
|
|
+ avalid = extcon_get_state(hsotg->edev, EXTCON_USB_HOST);
|
|
+ if (avalid < 0)
|
|
+ avalid = 0;
|
|
+
|
|
+ bvalid = extcon_get_state(hsotg->edev, EXTCON_USB);
|
|
+ if (bvalid < 0)
|
|
+ bvalid = 0;
|
|
+
|
|
+ /* Skip session not in line with dr_mode */
|
|
+ if ((avalid && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) ||
|
|
+ (bvalid && hsotg->dr_mode == USB_DR_MODE_HOST))
|
|
+ return;
|
|
+
|
|
+ /* Skip session if core is in test mode */
|
|
+ if (!avalid && !bvalid && hsotg->test_mode) {
|
|
+ dev_dbg(hsotg->dev, "Core is in test mode\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&hsotg->lock, flags);
|
|
+
|
|
+ if (avalid) {
|
|
+ if (dwc2_ovr_avalid(hsotg, true))
|
|
+ goto unlock;
|
|
+
|
|
+ if (hsotg->dr_mode == USB_DR_MODE_OTG)
|
|
+ /*
|
|
+ * This will raise a Connector ID Status Change
|
|
+ * Interrupt - connID A
|
|
+ */
|
|
+ dwc2_force_mode(hsotg, true);
|
|
+ } else if (bvalid) {
|
|
+ if (dwc2_ovr_bvalid(hsotg, true))
|
|
+ goto unlock;
|
|
+
|
|
+ if (hsotg->dr_mode == USB_DR_MODE_OTG)
|
|
+ /*
|
|
+ * This will raise a Connector ID Status Change
|
|
+ * Interrupt - connID B
|
|
+ */
|
|
+ dwc2_force_mode(hsotg, false);
|
|
+
|
|
+ /* This clear DCTL.SFTDISCON bit */
|
|
+ dwc2_hsotg_core_connect(hsotg);
|
|
+ } else {
|
|
+ if (dwc2_ovr_avalid(hsotg, false) &&
|
|
+ dwc2_ovr_bvalid(hsotg, false))
|
|
+ goto unlock;
|
|
+
|
|
+ if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)
|
|
+ /* This set DCTL.SFTDISCON bit */
|
|
+ dwc2_hsotg_core_disconnect(hsotg);
|
|
+
|
|
+ dwc2_force_dr_mode(hsotg);
|
|
+ }
|
|
+
|
|
+ dev_dbg(hsotg->dev, "%s-session valid\n",
|
|
+ avalid ? "A" : bvalid ? "B" : "No");
|
|
+
|
|
+unlock:
|
|
+ spin_unlock_irqrestore(&hsotg->lock, flags);
|
|
+}
|
|
+
|
|
+static int dwc2_drd_notifier(struct notifier_block *nb,
|
|
+ unsigned long event, void *ptr)
|
|
+{
|
|
+ struct dwc2_hsotg *hsotg = container_of(nb, struct dwc2_hsotg, edev_nb);
|
|
+
|
|
+ dwc2_drd_update(hsotg);
|
|
+
|
|
+ return NOTIFY_DONE;
|
|
+}
|
|
+
|
|
+int dwc2_drd_init(struct dwc2_hsotg *hsotg)
|
|
+{
|
|
+ struct extcon_dev *edev;
|
|
+ int ret;
|
|
+
|
|
+ if (of_property_read_bool(hsotg->dev->of_node, "extcon")) {
|
|
+ edev = extcon_get_edev_by_phandle(hsotg->dev, 0);
|
|
+ if (IS_ERR(edev)) {
|
|
+ ret = PTR_ERR(edev);
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ dev_err(hsotg->dev,
|
|
+ "couldn't get extcon device: %d\n",
|
|
+ ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ hsotg->edev_nb.notifier_call = dwc2_drd_notifier;
|
|
+ ret = devm_extcon_register_notifier(hsotg->dev, edev,
|
|
+ EXTCON_USB,
|
|
+ &hsotg->edev_nb);
|
|
+ if (ret < 0) {
|
|
+ dev_err(hsotg->dev,
|
|
+ "USB cable notifier register failed: %d\n",
|
|
+ ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = devm_extcon_register_notifier(hsotg->dev, edev,
|
|
+ EXTCON_USB_HOST,
|
|
+ &hsotg->edev_nb);
|
|
+ if (ret < 0) {
|
|
+ dev_err(hsotg->dev,
|
|
+ "USB-HOST cable notifier register failed: %d\n",
|
|
+ ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ hsotg->edev = edev;
|
|
+
|
|
+ /* Enable override and initialize values */
|
|
+ dwc2_ovr_init(hsotg);
|
|
+
|
|
+ dwc2_drd_update(hsotg);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
|
|
index 3f68edd..3ebd5dd 100644
|
|
--- a/drivers/usb/dwc2/gadget.c
|
|
+++ b/drivers/usb/dwc2/gadget.c
|
|
@@ -123,6 +123,24 @@ static inline void dwc2_gadget_incr_frame_num(struct dwc2_hsotg_ep *hs_ep)
|
|
}
|
|
|
|
/**
|
|
+ * dwc2_gadget_dec_frame_num_by_one - Decrements the targeted frame number
|
|
+ * by one.
|
|
+ * @hs_ep: The endpoint.
|
|
+ *
|
|
+ * This function used in service interval based scheduling flow to calculate
|
|
+ * descriptor frame number filed value. For service interval mode frame
|
|
+ * number in descriptor should point to last (u)frame in the interval.
|
|
+ *
|
|
+ */
|
|
+static inline void dwc2_gadget_dec_frame_num_by_one(struct dwc2_hsotg_ep *hs_ep)
|
|
+{
|
|
+ if (hs_ep->target_frame)
|
|
+ hs_ep->target_frame -= 1;
|
|
+ else
|
|
+ hs_ep->target_frame = DSTS_SOFFN_LIMIT;
|
|
+}
|
|
+
|
|
+/**
|
|
* dwc2_hsotg_en_gsint - enable one or more of the general interrupt
|
|
* @hsotg: The device state
|
|
* @ints: A bitmask of the interrupts to enable
|
|
@@ -228,6 +246,27 @@ int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg)
|
|
}
|
|
|
|
/**
|
|
+ * dwc2_gadget_wkup_alert_handler - Handler for WKUP_ALERT interrupt
|
|
+ *
|
|
+ * @hsotg: Programming view of the DWC_otg controller
|
|
+ *
|
|
+ */
|
|
+static void dwc2_gadget_wkup_alert_handler(struct dwc2_hsotg *hsotg)
|
|
+{
|
|
+ u32 gintsts2;
|
|
+ u32 gintmsk2;
|
|
+
|
|
+ gintsts2 = dwc2_readl(hsotg, GINTSTS2);
|
|
+ gintmsk2 = dwc2_readl(hsotg, GINTMSK2);
|
|
+
|
|
+ if (gintsts2 & GINTSTS2_WKUP_ALERT_INT) {
|
|
+ dev_dbg(hsotg->dev, "%s: Wkup_Alert_Int\n", __func__);
|
|
+ dwc2_set_bit(hsotg, GINTSTS2, GINTSTS2_WKUP_ALERT_INT);
|
|
+ dwc2_set_bit(hsotg, DCTL, DCTL_RMTWKUPSIG);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
* dwc2_hsotg_tx_fifo_average_depth - returns average depth of device mode
|
|
* TX FIFOs
|
|
*
|
|
@@ -2810,6 +2849,23 @@ static void dwc2_gadget_handle_nak(struct dwc2_hsotg_ep *hs_ep)
|
|
if (using_desc_dma(hsotg)) {
|
|
hs_ep->target_frame = hsotg->frame_number;
|
|
dwc2_gadget_incr_frame_num(hs_ep);
|
|
+
|
|
+ /* In service interval mode target_frame must
|
|
+ * be set to last (u)frame of the service interval.
|
|
+ */
|
|
+ if (hsotg->params.service_interval) {
|
|
+ /* Set target_frame to the first (u)frame of
|
|
+ * the service interval
|
|
+ */
|
|
+ hs_ep->target_frame &= ~hs_ep->interval + 1;
|
|
+
|
|
+ /* Set target_frame to the last (u)frame of
|
|
+ * the service interval
|
|
+ */
|
|
+ dwc2_gadget_incr_frame_num(hs_ep);
|
|
+ dwc2_gadget_dec_frame_num_by_one(hs_ep);
|
|
+ }
|
|
+
|
|
dwc2_gadget_start_isoc_ddma(hs_ep);
|
|
return;
|
|
}
|
|
@@ -3322,6 +3378,10 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg,
|
|
dwc2_set_bit(hsotg, DIEPMSK, DIEPMSK_BNAININTRMSK);
|
|
}
|
|
|
|
+ /* Enable Service Interval mode if supported */
|
|
+ if (using_desc_dma(hsotg) && hsotg->params.service_interval)
|
|
+ dwc2_set_bit(hsotg, DCTL, DCTL_SERVICE_INTERVAL_SUPPORTED);
|
|
+
|
|
dwc2_writel(hsotg, 0, DAINTMSK);
|
|
|
|
dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n",
|
|
@@ -3378,6 +3438,10 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg,
|
|
/* configure the core to support LPM */
|
|
dwc2_gadget_init_lpm(hsotg);
|
|
|
|
+ /* program GREFCLK register if needed */
|
|
+ if (using_desc_dma(hsotg) && hsotg->params.service_interval)
|
|
+ dwc2_gadget_program_ref_clk(hsotg);
|
|
+
|
|
/* must be at-least 3ms to allow bus to see disconnect */
|
|
mdelay(3);
|
|
|
|
@@ -3390,7 +3454,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg,
|
|
dwc2_readl(hsotg, DOEPCTL0));
|
|
}
|
|
|
|
-static void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg)
|
|
+void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg)
|
|
{
|
|
/* set the soft-disconnect bit */
|
|
dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON);
|
|
@@ -3399,7 +3463,8 @@ static void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg)
|
|
void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg)
|
|
{
|
|
/* remove the soft-disconnect and let's go */
|
|
- dwc2_clear_bit(hsotg, DCTL, DCTL_SFTDISCON);
|
|
+ if (!hsotg->edev || (dwc2_readl(hsotg, GOTGCTL) & GOTGCTL_BSESVLD))
|
|
+ dwc2_clear_bit(hsotg, DCTL, DCTL_SFTDISCON);
|
|
}
|
|
|
|
/**
|
|
@@ -3686,6 +3751,10 @@ static irqreturn_t dwc2_hsotg_irq(int irq, void *pw)
|
|
if (gintsts & IRQ_RETRY_MASK && --retry_count > 0)
|
|
goto irq_retry;
|
|
|
|
+ /* Check WKUP_ALERT interrupt*/
|
|
+ if (hsotg->params.service_interval)
|
|
+ dwc2_gadget_wkup_alert_handler(hsotg);
|
|
+
|
|
spin_unlock(&hsotg->lock);
|
|
|
|
return IRQ_HANDLED;
|
|
@@ -4795,7 +4864,7 @@ int dwc2_hsotg_suspend(struct dwc2_hsotg *hsotg)
|
|
hsotg->gadget.speed = USB_SPEED_UNKNOWN;
|
|
spin_unlock_irqrestore(&hsotg->lock, flags);
|
|
|
|
- for (ep = 0; ep < hsotg->num_of_eps; ep++) {
|
|
+ for (ep = 1; ep < hsotg->num_of_eps; ep++) {
|
|
if (hsotg->eps_in[ep])
|
|
dwc2_hsotg_ep_disable_lock(&hsotg->eps_in[ep]->ep);
|
|
if (hsotg->eps_out[ep])
|
|
@@ -4966,8 +5035,32 @@ void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg)
|
|
val |= hsotg->params.lpm_clock_gating ? GLPMCFG_ENBLSLPM : 0;
|
|
val |= hsotg->params.hird_threshold << GLPMCFG_HIRD_THRES_SHIFT;
|
|
val |= hsotg->params.besl ? GLPMCFG_ENBESL : 0;
|
|
+ val |= GLPMCFG_LPM_ACCEPT_CTRL_ISOC;
|
|
dwc2_writel(hsotg, val, GLPMCFG);
|
|
dev_dbg(hsotg->dev, "GLPMCFG=0x%08x\n", dwc2_readl(hsotg, GLPMCFG));
|
|
+
|
|
+ /* Unmask WKUP_ALERT Interrupt */
|
|
+ if (hsotg->params.service_interval)
|
|
+ dwc2_set_bit(hsotg, GINTMSK2, GINTMSK2_WKUP_ALERT_INT_MSK);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * dwc2_gadget_program_ref_clk - Program GREFCLK register in device mode
|
|
+ *
|
|
+ * @hsotg: Programming view of DWC_otg controller
|
|
+ *
|
|
+ */
|
|
+void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg)
|
|
+{
|
|
+ u32 val = 0;
|
|
+
|
|
+ val |= GREFCLK_REF_CLK_MODE;
|
|
+ val |= hsotg->params.ref_clk_per << GREFCLK_REFCLKPER_SHIFT;
|
|
+ val |= hsotg->params.sof_cnt_wkup_alert <<
|
|
+ GREFCLK_SOF_CNT_WKUP_ALERT_SHIFT;
|
|
+
|
|
+ dwc2_writel(hsotg, val, GREFCLK);
|
|
+ dev_dbg(hsotg->dev, "GREFCLK=0x%08x\n", dwc2_readl(hsotg, GREFCLK));
|
|
}
|
|
|
|
/**
|
|
diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c
|
|
index a5c8329..67bbf94 100644
|
|
--- a/drivers/usb/dwc2/hcd.c
|
|
+++ b/drivers/usb/dwc2/hcd.c
|
|
@@ -125,7 +125,7 @@ static void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg)
|
|
|
|
static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy)
|
|
{
|
|
- u32 usbcfg, ggpio, i2cctl;
|
|
+ u32 usbcfg, i2cctl;
|
|
int retval = 0;
|
|
|
|
/*
|
|
@@ -149,19 +149,6 @@ static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy)
|
|
return retval;
|
|
}
|
|
}
|
|
-
|
|
- if (hsotg->params.activate_stm_fs_transceiver) {
|
|
- ggpio = dwc2_readl(hsotg, GGPIO);
|
|
- if (!(ggpio & GGPIO_STM32_OTG_GCCFG_PWRDWN)) {
|
|
- dev_dbg(hsotg->dev, "Activating transceiver\n");
|
|
- /*
|
|
- * STM32F4x9 uses the GGPIO register as general
|
|
- * core configuration register.
|
|
- */
|
|
- ggpio |= GGPIO_STM32_OTG_GCCFG_PWRDWN;
|
|
- dwc2_writel(hsotg, ggpio, GGPIO);
|
|
- }
|
|
- }
|
|
}
|
|
|
|
/*
|
|
@@ -358,16 +345,10 @@ static void dwc2_gusbcfg_init(struct dwc2_hsotg *hsotg)
|
|
|
|
static int dwc2_vbus_supply_init(struct dwc2_hsotg *hsotg)
|
|
{
|
|
- int ret;
|
|
-
|
|
- hsotg->vbus_supply = devm_regulator_get_optional(hsotg->dev, "vbus");
|
|
- if (IS_ERR(hsotg->vbus_supply)) {
|
|
- ret = PTR_ERR(hsotg->vbus_supply);
|
|
- hsotg->vbus_supply = NULL;
|
|
- return ret == -ENODEV ? 0 : ret;
|
|
- }
|
|
+ if (hsotg->vbus_supply)
|
|
+ return regulator_enable(hsotg->vbus_supply);
|
|
|
|
- return regulator_enable(hsotg->vbus_supply);
|
|
+ return 0;
|
|
}
|
|
|
|
static int dwc2_vbus_supply_exit(struct dwc2_hsotg *hsotg)
|
|
@@ -1328,14 +1309,11 @@ static void dwc2_hc_write_packet(struct dwc2_hsotg *hsotg,
|
|
u32 remaining_count;
|
|
u32 byte_count;
|
|
u32 dword_count;
|
|
- u32 __iomem *data_fifo;
|
|
u32 *data_buf = (u32 *)chan->xfer_buf;
|
|
|
|
if (dbg_hc(chan))
|
|
dev_vdbg(hsotg->dev, "%s()\n", __func__);
|
|
|
|
- data_fifo = (u32 __iomem *)(hsotg->regs + HCFIFO(chan->hc_num));
|
|
-
|
|
remaining_count = chan->xfer_len - chan->xfer_count;
|
|
if (remaining_count > chan->max_packet)
|
|
byte_count = chan->max_packet;
|
|
@@ -1934,7 +1912,8 @@ static void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg)
|
|
* release_channel_ddma(), which is called from ep_disable when
|
|
* device disconnects
|
|
*/
|
|
- channel->qh = NULL;
|
|
+ if (hsotg->params.host_dma && hsotg->params.dma_desc_enable)
|
|
+ channel->qh = NULL;
|
|
}
|
|
/* All channels have been freed, mark them available */
|
|
if (hsotg->params.uframe_sched) {
|
|
@@ -3804,7 +3783,8 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq,
|
|
if (wvalue != USB_PORT_FEAT_TEST && (!windex || windex > 1))
|
|
goto error;
|
|
|
|
- if (!hsotg->flags.b.port_connect_status) {
|
|
+ if (!hsotg->flags.b.port_connect_status &&
|
|
+ !dwc2_is_host_mode(hsotg)) {
|
|
/*
|
|
* The port is disconnected, which means the core is
|
|
* either in device mode or it soon will be. Just
|
|
diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h
|
|
index 0ca8e7b..7500954 100644
|
|
--- a/drivers/usb/dwc2/hw.h
|
|
+++ b/drivers/usb/dwc2/hw.h
|
|
@@ -54,6 +54,12 @@
|
|
#define GOTGCTL_HSTSETHNPEN BIT(10)
|
|
#define GOTGCTL_HNPREQ BIT(9)
|
|
#define GOTGCTL_HSTNEGSCS BIT(8)
|
|
+#define GOTGCTL_BVALOVAL BIT(7)
|
|
+#define GOTGCTL_BVALOEN BIT(6)
|
|
+#define GOTGCTL_AVALOVAL BIT(5)
|
|
+#define GOTGCTL_AVALOEN BIT(4)
|
|
+#define GOTGCTL_VBVALOVAL BIT(3)
|
|
+#define GOTGCTL_VBVALOEN BIT(2)
|
|
#define GOTGCTL_SESREQ BIT(1)
|
|
#define GOTGCTL_SESREQSCS BIT(0)
|
|
|
|
@@ -227,6 +233,8 @@
|
|
#define GPVNDCTL HSOTG_REG(0x0034)
|
|
#define GGPIO HSOTG_REG(0x0038)
|
|
#define GGPIO_STM32_OTG_GCCFG_PWRDWN BIT(16)
|
|
+#define GGPIO_STM32_OTG_GCCFG_VBDEN BIT(21)
|
|
+#define GGPIO_STM32_OTG_GCCFG_IDEN BIT(22)
|
|
|
|
#define GUID HSOTG_REG(0x003c)
|
|
#define GSNPSID HSOTG_REG(0x0040)
|
|
@@ -312,6 +320,7 @@
|
|
#define GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT 14
|
|
#define GHWCFG4_ACG_SUPPORTED BIT(12)
|
|
#define GHWCFG4_IPG_ISOC_SUPPORTED BIT(11)
|
|
+#define GHWCFG4_SERVICE_INTERVAL_SUPPORTED BIT(10)
|
|
#define GHWCFG4_UTMI_PHY_DATA_WIDTH_8 0
|
|
#define GHWCFG4_UTMI_PHY_DATA_WIDTH_16 1
|
|
#define GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16 2
|
|
@@ -332,6 +341,8 @@
|
|
#define GLPMCFG_SNDLPM BIT(24)
|
|
#define GLPMCFG_RETRY_CNT_MASK (0x7 << 21)
|
|
#define GLPMCFG_RETRY_CNT_SHIFT 21
|
|
+#define GLPMCFG_LPM_ACCEPT_CTRL_CONTROL BIT(21)
|
|
+#define GLPMCFG_LPM_ACCEPT_CTRL_ISOC BIT(22)
|
|
#define GLPMCFG_LPM_CHNL_INDX_MASK (0xf << 17)
|
|
#define GLPMCFG_LPM_CHNL_INDX_SHIFT 17
|
|
#define GLPMCFG_L1RESUMEOK BIT(16)
|
|
@@ -404,6 +415,19 @@
|
|
#define ADPCTL_PRB_DSCHRG_MASK (0x3 << 0)
|
|
#define ADPCTL_PRB_DSCHRG_SHIFT 0
|
|
|
|
+#define GREFCLK HSOTG_REG(0x0064)
|
|
+#define GREFCLK_REFCLKPER_MASK (0x1ffff << 15)
|
|
+#define GREFCLK_REFCLKPER_SHIFT 15
|
|
+#define GREFCLK_REF_CLK_MODE BIT(14)
|
|
+#define GREFCLK_SOF_CNT_WKUP_ALERT_MASK (0x3ff)
|
|
+#define GREFCLK_SOF_CNT_WKUP_ALERT_SHIFT 0
|
|
+
|
|
+#define GINTMSK2 HSOTG_REG(0x0068)
|
|
+#define GINTMSK2_WKUP_ALERT_INT_MSK BIT(0)
|
|
+
|
|
+#define GINTSTS2 HSOTG_REG(0x006c)
|
|
+#define GINTSTS2_WKUP_ALERT_INT BIT(0)
|
|
+
|
|
#define HPTXFSIZ HSOTG_REG(0x100)
|
|
/* Use FIFOSIZE_* constants to access this register */
|
|
|
|
@@ -443,6 +467,7 @@
|
|
#define DCFG_DEVSPD_FS48 3
|
|
|
|
#define DCTL HSOTG_REG(0x804)
|
|
+#define DCTL_SERVICE_INTERVAL_SUPPORTED BIT(19)
|
|
#define DCTL_PWRONPRGDONE BIT(11)
|
|
#define DCTL_CGOUTNAK BIT(10)
|
|
#define DCTL_SGOUTNAK BIT(9)
|
|
diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c
|
|
index a93415f..8af461c 100644
|
|
--- a/drivers/usb/dwc2/params.c
|
|
+++ b/drivers/usb/dwc2/params.c
|
|
@@ -152,6 +152,36 @@ static void dwc2_set_stm32f7_hsotg_params(struct dwc2_hsotg *hsotg)
|
|
p->host_perio_tx_fifo_size = 256;
|
|
}
|
|
|
|
+static void dwc2_set_stm32mp1_fsotg_params(struct dwc2_hsotg *hsotg)
|
|
+{
|
|
+ struct dwc2_core_params *p = &hsotg->params;
|
|
+
|
|
+ p->otg_cap = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE;
|
|
+ p->speed = DWC2_SPEED_PARAM_FULL;
|
|
+ p->host_rx_fifo_size = 128;
|
|
+ p->host_nperio_tx_fifo_size = 96;
|
|
+ p->host_perio_tx_fifo_size = 96;
|
|
+ p->max_packet_count = 256;
|
|
+ p->phy_type = DWC2_PHY_TYPE_PARAM_FS;
|
|
+ p->i2c_enable = false;
|
|
+ p->activate_stm_fs_transceiver = true;
|
|
+ p->activate_stm_id_vb_detection = true;
|
|
+ p->power_down = DWC2_POWER_DOWN_PARAM_NONE;
|
|
+}
|
|
+
|
|
+static void dwc2_set_stm32mp1_hsotg_params(struct dwc2_hsotg *hsotg)
|
|
+{
|
|
+ struct dwc2_core_params *p = &hsotg->params;
|
|
+ struct device_node *np = hsotg->dev->of_node;
|
|
+
|
|
+ p->otg_cap = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE;
|
|
+ p->activate_stm_id_vb_detection = !of_property_read_bool(np, "extcon");
|
|
+ p->host_rx_fifo_size = 440;
|
|
+ p->host_nperio_tx_fifo_size = 256;
|
|
+ p->host_perio_tx_fifo_size = 256;
|
|
+ p->power_down = DWC2_POWER_DOWN_PARAM_NONE;
|
|
+}
|
|
+
|
|
const struct of_device_id dwc2_of_match_table[] = {
|
|
{ .compatible = "brcm,bcm2835-usb", .data = dwc2_set_bcm_params },
|
|
{ .compatible = "hisilicon,hi6220-usb", .data = dwc2_set_his_params },
|
|
@@ -173,6 +203,10 @@ const struct of_device_id dwc2_of_match_table[] = {
|
|
{ .compatible = "st,stm32f4x9-hsotg" },
|
|
{ .compatible = "st,stm32f7-hsotg",
|
|
.data = dwc2_set_stm32f7_hsotg_params },
|
|
+ { .compatible = "st,stm32mp1-fsotg",
|
|
+ .data = dwc2_set_stm32mp1_fsotg_params },
|
|
+ { .compatible = "st,stm32mp1-hsotg",
|
|
+ .data = dwc2_set_stm32mp1_hsotg_params },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, dwc2_of_match_table);
|
|
@@ -309,9 +343,12 @@ static void dwc2_set_default_params(struct dwc2_hsotg *hsotg)
|
|
p->hird_threshold_en = true;
|
|
p->hird_threshold = 4;
|
|
p->ipg_isoc_en = false;
|
|
+ p->service_interval = false;
|
|
p->max_packet_count = hw->max_packet_count;
|
|
p->max_transfer_size = hw->max_transfer_size;
|
|
p->ahbcfg = GAHBCFG_HBSTLEN_INCR << GAHBCFG_HBSTLEN_SHIFT;
|
|
+ p->ref_clk_per = 33333;
|
|
+ p->sof_cnt_wkup_alert = 100;
|
|
|
|
if ((hsotg->dr_mode == USB_DR_MODE_HOST) ||
|
|
(hsotg->dr_mode == USB_DR_MODE_OTG)) {
|
|
@@ -602,6 +639,7 @@ static void dwc2_check_params(struct dwc2_hsotg *hsotg)
|
|
CHECK_BOOL(besl, (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a));
|
|
CHECK_BOOL(hird_threshold_en, hsotg->params.lpm);
|
|
CHECK_RANGE(hird_threshold, 0, hsotg->params.besl ? 12 : 7, 0);
|
|
+ CHECK_BOOL(service_interval, hw->service_interval_mode);
|
|
CHECK_RANGE(max_packet_count,
|
|
15, hw->max_packet_count,
|
|
hw->max_packet_count);
|
|
@@ -790,6 +828,8 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg)
|
|
GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT;
|
|
hw->acg_enable = !!(hwcfg4 & GHWCFG4_ACG_SUPPORTED);
|
|
hw->ipg_isoc_en = !!(hwcfg4 & GHWCFG4_IPG_ISOC_SUPPORTED);
|
|
+ hw->service_interval_mode = !!(hwcfg4 &
|
|
+ GHWCFG4_SERVICE_INTERVAL_SUPPORTED);
|
|
|
|
/* fifo sizes */
|
|
hw->rx_fifo_size = (grxfsiz & GRXFSIZ_DEPTH_MASK) >>
|
|
diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c
|
|
index 5776428..3d61100 100644
|
|
--- a/drivers/usb/dwc2/platform.c
|
|
+++ b/drivers/usb/dwc2/platform.c
|
|
@@ -46,6 +46,7 @@
|
|
#include <linux/platform_device.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/platform_data/s3c-hsotg.h>
|
|
+#include <linux/pm_wakeirq.h>
|
|
#include <linux/reset.h>
|
|
|
|
#include <linux/usb/of.h>
|
|
@@ -318,12 +319,18 @@ static int dwc2_driver_remove(struct platform_device *dev)
|
|
{
|
|
struct dwc2_hsotg *hsotg = platform_get_drvdata(dev);
|
|
|
|
+ if (hsotg->wakeirq > 0)
|
|
+ dev_pm_clear_wake_irq(&dev->dev);
|
|
+
|
|
dwc2_debugfs_exit(hsotg);
|
|
if (hsotg->hcd_enabled)
|
|
dwc2_hcd_remove(hsotg);
|
|
if (hsotg->gadget_enabled)
|
|
dwc2_hsotg_remove(hsotg);
|
|
|
|
+ if (hsotg->params.activate_stm_id_vb_detection)
|
|
+ regulator_disable(hsotg->usb33d);
|
|
+
|
|
if (hsotg->ll_hw_enabled)
|
|
dwc2_lowlevel_hw_disable(hsotg);
|
|
|
|
@@ -432,6 +439,24 @@ static int dwc2_driver_probe(struct platform_device *dev)
|
|
if (retval)
|
|
return retval;
|
|
|
|
+ hsotg->wakeirq = platform_get_irq(dev, 1);
|
|
+ if (hsotg->wakeirq > 0) {
|
|
+ retval = dev_pm_set_dedicated_wake_irq(&dev->dev,
|
|
+ hsotg->wakeirq);
|
|
+ if (retval)
|
|
+ return retval;
|
|
+ } else if (hsotg->wakeirq == -EPROBE_DEFER) {
|
|
+ return hsotg->wakeirq;
|
|
+ }
|
|
+
|
|
+ hsotg->vbus_supply = devm_regulator_get_optional(hsotg->dev, "vbus");
|
|
+ if (IS_ERR(hsotg->vbus_supply)) {
|
|
+ retval = PTR_ERR(hsotg->vbus_supply);
|
|
+ hsotg->vbus_supply = NULL;
|
|
+ if (retval != -ENODEV)
|
|
+ return retval;
|
|
+ }
|
|
+
|
|
retval = dwc2_lowlevel_hw_enable(hsotg);
|
|
if (retval)
|
|
return retval;
|
|
@@ -466,10 +491,55 @@ static int dwc2_driver_probe(struct platform_device *dev)
|
|
if (retval)
|
|
goto error;
|
|
|
|
+ if (hsotg->params.activate_stm_id_vb_detection) {
|
|
+ u32 ggpio;
|
|
+
|
|
+ hsotg->usb33d = devm_regulator_get(hsotg->dev, "usb33d");
|
|
+ if (IS_ERR(hsotg->usb33d)) {
|
|
+ retval = PTR_ERR(hsotg->usb33d);
|
|
+ dev_err(hsotg->dev,
|
|
+ "can't get voltage level detector supply\n");
|
|
+ goto error;
|
|
+ }
|
|
+ retval = regulator_enable(hsotg->usb33d);
|
|
+ if (retval) {
|
|
+ dev_err(hsotg->dev,
|
|
+ "can't enable voltage level detector supply\n");
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ ggpio = dwc2_readl(hsotg, GGPIO);
|
|
+ ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN;
|
|
+ ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN;
|
|
+ dwc2_writel(hsotg, ggpio, GGPIO);
|
|
+ }
|
|
+
|
|
+ retval = dwc2_drd_init(hsotg);
|
|
+ if (retval) {
|
|
+ if (retval != -EPROBE_DEFER)
|
|
+ dev_err(hsotg->dev, "failed to initialize dual-role\n");
|
|
+ goto error_init;
|
|
+ }
|
|
+
|
|
+ if (hsotg->params.activate_stm_fs_transceiver) {
|
|
+ u32 ggpio;
|
|
+
|
|
+ ggpio = dwc2_readl(hsotg, GGPIO);
|
|
+ if (!(ggpio & GGPIO_STM32_OTG_GCCFG_PWRDWN)) {
|
|
+ dev_dbg(hsotg->dev, "Activating transceiver\n");
|
|
+ /*
|
|
+ * STM32 uses the GGPIO register as general
|
|
+ * core configuration register.
|
|
+ */
|
|
+ ggpio |= GGPIO_STM32_OTG_GCCFG_PWRDWN;
|
|
+ dwc2_writel(hsotg, ggpio, GGPIO);
|
|
+ }
|
|
+ }
|
|
+
|
|
if (hsotg->dr_mode != USB_DR_MODE_HOST) {
|
|
retval = dwc2_gadget_init(hsotg);
|
|
if (retval)
|
|
- goto error;
|
|
+ goto error_init;
|
|
hsotg->gadget_enabled = 1;
|
|
}
|
|
|
|
@@ -478,7 +548,7 @@ static int dwc2_driver_probe(struct platform_device *dev)
|
|
if (retval) {
|
|
if (hsotg->gadget_enabled)
|
|
dwc2_hsotg_remove(hsotg);
|
|
- goto error;
|
|
+ goto error_init;
|
|
}
|
|
hsotg->hcd_enabled = 1;
|
|
}
|
|
@@ -494,7 +564,13 @@ static int dwc2_driver_probe(struct platform_device *dev)
|
|
|
|
return 0;
|
|
|
|
+error_init:
|
|
+ if (hsotg->params.activate_stm_id_vb_detection)
|
|
+ regulator_disable(hsotg->usb33d);
|
|
error:
|
|
+ if (hsotg->wakeirq > 0)
|
|
+ dev_pm_clear_wake_irq(&dev->dev);
|
|
+
|
|
dwc2_lowlevel_hw_disable(hsotg);
|
|
return retval;
|
|
}
|
|
@@ -507,9 +583,58 @@ static int __maybe_unused dwc2_suspend(struct device *dev)
|
|
if (dwc2_is_device_mode(dwc2))
|
|
dwc2_hsotg_suspend(dwc2);
|
|
|
|
+ if (dwc2->params.power_down == DWC2_POWER_DOWN_PARAM_NONE) {
|
|
+ /*
|
|
+ * Backup host registers when power_down param is 'none', if
|
|
+ * controller power is disabled.
|
|
+ * This shouldn't be needed, when using other power_down modes.
|
|
+ */
|
|
+ ret = dwc2_backup_registers(dwc2);
|
|
+ if (ret) {
|
|
+ dev_err(dwc2->dev, "backup regs failed %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (dwc2->params.activate_stm_id_vb_detection) {
|
|
+ unsigned long flags;
|
|
+ u32 ggpio, gotgctl;
|
|
+ int is_host = dwc2_is_host_mode(dwc2);
|
|
+
|
|
+ /*
|
|
+ * Need to force the mode to the current mode to avoid Mode
|
|
+ * Mismatch Interrupt when ID detection will be disabled.
|
|
+ */
|
|
+ dwc2_force_mode(dwc2, is_host);
|
|
+
|
|
+ spin_lock_irqsave(&dwc2->lock, flags);
|
|
+ gotgctl = dwc2_readl(dwc2, GOTGCTL);
|
|
+ /* bypass debounce filter, enable overrides */
|
|
+ gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS;
|
|
+ gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN;
|
|
+ /* Force A / B session if needed */
|
|
+ if (gotgctl & GOTGCTL_ASESVLD)
|
|
+ gotgctl |= GOTGCTL_AVALOVAL;
|
|
+ if (gotgctl & GOTGCTL_BSESVLD)
|
|
+ gotgctl |= GOTGCTL_BVALOVAL;
|
|
+ dwc2_writel(dwc2, gotgctl, GOTGCTL);
|
|
+ spin_unlock_irqrestore(&dwc2->lock, flags);
|
|
+
|
|
+ ggpio = dwc2_readl(dwc2, GGPIO);
|
|
+ ggpio &= ~GGPIO_STM32_OTG_GCCFG_IDEN;
|
|
+ ggpio &= ~GGPIO_STM32_OTG_GCCFG_VBDEN;
|
|
+ dwc2_writel(dwc2, ggpio, GGPIO);
|
|
+
|
|
+ regulator_disable(dwc2->usb33d);
|
|
+ }
|
|
+
|
|
if (dwc2->ll_hw_enabled)
|
|
ret = __dwc2_lowlevel_hw_disable(dwc2);
|
|
|
|
+ if (dwc2->wakeirq > 0 &&
|
|
+ (device_may_wakeup(dev) || dev->power.wakeup_path))
|
|
+ enable_irq_wake(dwc2->wakeirq);
|
|
+
|
|
return ret;
|
|
}
|
|
|
|
@@ -518,12 +643,49 @@ static int __maybe_unused dwc2_resume(struct device *dev)
|
|
struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
+ if (dwc2->wakeirq > 0 &&
|
|
+ (device_may_wakeup(dev) || dev->power.wakeup_path))
|
|
+ disable_irq_wake(dwc2->wakeirq);
|
|
+
|
|
if (dwc2->ll_hw_enabled) {
|
|
ret = __dwc2_lowlevel_hw_enable(dwc2);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
+ if (dwc2->params.activate_stm_id_vb_detection) {
|
|
+ unsigned long flags;
|
|
+ u32 ggpio, gotgctl;
|
|
+
|
|
+ ret = regulator_enable(dwc2->usb33d);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ggpio = dwc2_readl(dwc2, GGPIO);
|
|
+ ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN;
|
|
+ ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN;
|
|
+ dwc2_writel(dwc2, ggpio, GGPIO);
|
|
+
|
|
+ /* ID/VBUS detection startup time */
|
|
+ usleep_range(5000, 7000);
|
|
+
|
|
+ spin_lock_irqsave(&dwc2->lock, flags);
|
|
+ gotgctl = dwc2_readl(dwc2, GOTGCTL);
|
|
+ gotgctl &= ~GOTGCTL_DBNCE_FLTR_BYPASS;
|
|
+ gotgctl &= ~(GOTGCTL_BVALOEN | GOTGCTL_AVALOEN |
|
|
+ GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL);
|
|
+ dwc2_writel(dwc2, gotgctl, GOTGCTL);
|
|
+ spin_unlock_irqrestore(&dwc2->lock, flags);
|
|
+ }
|
|
+
|
|
+ if (dwc2->params.power_down == DWC2_POWER_DOWN_PARAM_NONE) {
|
|
+ ret = dwc2_restore_registers(dwc2);
|
|
+ if (ret) {
|
|
+ dev_err(dwc2->dev, "restore regs failed %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
if (dwc2_is_device_mode(dwc2))
|
|
ret = dwc2_hsotg_resume(dwc2);
|
|
|
|
diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c
|
|
index d4d317d..bb1e2e1 100644
|
|
--- a/drivers/usb/gadget/function/u_serial.c
|
|
+++ b/drivers/usb/gadget/function/u_serial.c
|
|
@@ -16,7 +16,6 @@
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
-#include <linux/interrupt.h>
|
|
#include <linux/device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/tty.h>
|
|
@@ -26,6 +25,7 @@
|
|
#include <linux/module.h>
|
|
#include <linux/console.h>
|
|
#include <linux/kthread.h>
|
|
+#include <linux/workqueue.h>
|
|
#include <linux/kfifo.h>
|
|
|
|
#include "u_serial.h"
|
|
@@ -110,7 +110,7 @@ struct gs_port {
|
|
int read_allocated;
|
|
struct list_head read_queue;
|
|
unsigned n_read;
|
|
- struct tasklet_struct push;
|
|
+ struct delayed_work push;
|
|
|
|
struct list_head write_pool;
|
|
int write_started;
|
|
@@ -352,9 +352,10 @@ __acquires(&port->port_lock)
|
|
* So QUEUE_SIZE packets plus however many the FIFO holds (usually two)
|
|
* can be buffered before the TTY layer's buffers (currently 64 KB).
|
|
*/
|
|
-static void gs_rx_push(unsigned long _port)
|
|
+static void gs_rx_push(struct work_struct *work)
|
|
{
|
|
- struct gs_port *port = (void *)_port;
|
|
+ struct delayed_work *w = to_delayed_work(work);
|
|
+ struct gs_port *port = container_of(w, struct gs_port, push);
|
|
struct tty_struct *tty;
|
|
struct list_head *queue = &port->read_queue;
|
|
bool disconnect = false;
|
|
@@ -429,21 +430,13 @@ static void gs_rx_push(unsigned long _port)
|
|
|
|
/* We want our data queue to become empty ASAP, keeping data
|
|
* in the tty and ldisc (not here). If we couldn't push any
|
|
- * this time around, there may be trouble unless there's an
|
|
- * implicit tty_unthrottle() call on its way...
|
|
+ * this time around, RX may be starved, so wait until next jiffy.
|
|
*
|
|
- * REVISIT we should probably add a timer to keep the tasklet
|
|
- * from starving ... but it's not clear that case ever happens.
|
|
+ * We may leave non-empty queue only when there is a tty, and
|
|
+ * either it is throttled or there is no more room in flip buffer.
|
|
*/
|
|
- if (!list_empty(queue) && tty) {
|
|
- if (!tty_throttled(tty)) {
|
|
- if (do_push)
|
|
- tasklet_schedule(&port->push);
|
|
- else
|
|
- pr_warn("ttyGS%d: RX not scheduled?\n",
|
|
- port->port_num);
|
|
- }
|
|
- }
|
|
+ if (!list_empty(queue) && !tty_throttled(tty))
|
|
+ schedule_delayed_work(&port->push, 1);
|
|
|
|
/* If we're still connected, refill the USB RX queue. */
|
|
if (!disconnect && port->port_usb)
|
|
@@ -459,7 +452,7 @@ static void gs_read_complete(struct usb_ep *ep, struct usb_request *req)
|
|
/* Queue all received data until the tty layer is ready for it. */
|
|
spin_lock(&port->port_lock);
|
|
list_add_tail(&req->list, &port->read_queue);
|
|
- tasklet_schedule(&port->push);
|
|
+ schedule_delayed_work(&port->push, 0);
|
|
spin_unlock(&port->port_lock);
|
|
}
|
|
|
|
@@ -854,8 +847,8 @@ static void gs_unthrottle(struct tty_struct *tty)
|
|
* rts/cts, or other handshaking with the host, but if the
|
|
* read queue backs up enough we'll be NAKing OUT packets.
|
|
*/
|
|
- tasklet_schedule(&port->push);
|
|
pr_vdebug("ttyGS%d: unthrottle\n", port->port_num);
|
|
+ schedule_delayed_work(&port->push, 0);
|
|
}
|
|
spin_unlock_irqrestore(&port->port_lock, flags);
|
|
}
|
|
@@ -1159,7 +1152,7 @@ gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
|
|
init_waitqueue_head(&port->drain_wait);
|
|
init_waitqueue_head(&port->close_wait);
|
|
|
|
- tasklet_init(&port->push, gs_rx_push, (unsigned long) port);
|
|
+ INIT_DELAYED_WORK(&port->push, gs_rx_push);
|
|
|
|
INIT_LIST_HEAD(&port->read_pool);
|
|
INIT_LIST_HEAD(&port->read_queue);
|
|
@@ -1186,7 +1179,7 @@ static int gs_closed(struct gs_port *port)
|
|
|
|
static void gserial_free_port(struct gs_port *port)
|
|
{
|
|
- tasklet_kill(&port->push);
|
|
+ cancel_delayed_work_sync(&port->push);
|
|
/* wait for old opens to finish */
|
|
wait_event(port->close_wait, gs_closed(port));
|
|
WARN_ON(port->port_usb != NULL);
|
|
diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c
|
|
index 4c306fb..b915c0f 100644
|
|
--- a/drivers/usb/host/ehci-platform.c
|
|
+++ b/drivers/usb/host/ehci-platform.c
|
|
@@ -28,6 +28,8 @@
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
+#include <linux/pm_wakeirq.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/usb/hcd.h>
|
|
@@ -43,6 +45,8 @@
|
|
struct ehci_platform_priv {
|
|
struct clk *clks[EHCI_MAX_CLKS];
|
|
struct reset_control *rsts;
|
|
+ struct regulator *vbus_supply;
|
|
+ int wakeirq;
|
|
bool reset_on_resume;
|
|
};
|
|
|
|
@@ -73,6 +77,26 @@ static int ehci_platform_reset(struct usb_hcd *hcd)
|
|
return 0;
|
|
}
|
|
|
|
+static int ehci_platform_port_power(struct usb_hcd *hcd, int portnum,
|
|
+ bool enable)
|
|
+{
|
|
+ struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd);
|
|
+ int ret;
|
|
+
|
|
+ if (!priv->vbus_supply)
|
|
+ return 0;
|
|
+
|
|
+ if (enable)
|
|
+ ret = regulator_enable(priv->vbus_supply);
|
|
+ else
|
|
+ ret = regulator_disable(priv->vbus_supply);
|
|
+ if (ret)
|
|
+ dev_err(hcd->self.controller, "failed to %s vbus supply: %d\n",
|
|
+ enable ? "enable" : "disable", ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
static int ehci_platform_power_on(struct platform_device *dev)
|
|
{
|
|
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
|
@@ -110,6 +134,7 @@ static struct hc_driver __read_mostly ehci_platform_hc_driver;
|
|
static const struct ehci_driver_overrides platform_overrides __initconst = {
|
|
.reset = ehci_platform_reset,
|
|
.extra_priv_size = sizeof(struct ehci_platform_priv),
|
|
+ .port_power = ehci_platform_port_power,
|
|
};
|
|
|
|
static struct usb_ehci_pdata ehci_platform_defaults = {
|
|
@@ -200,6 +225,15 @@ static int ehci_platform_probe(struct platform_device *dev)
|
|
if (err)
|
|
goto err_put_clks;
|
|
|
|
+ priv->vbus_supply = devm_regulator_get_optional(&dev->dev, "vbus");
|
|
+ if (IS_ERR(priv->vbus_supply)) {
|
|
+ err = PTR_ERR(priv->vbus_supply);
|
|
+ if (err == -ENODEV)
|
|
+ priv->vbus_supply = NULL;
|
|
+ else
|
|
+ goto err_reset;
|
|
+ }
|
|
+
|
|
if (pdata->big_endian_desc)
|
|
ehci->big_endian_desc = 1;
|
|
if (pdata->big_endian_mmio)
|
|
@@ -245,12 +279,24 @@ static int ehci_platform_probe(struct platform_device *dev)
|
|
if (err)
|
|
goto err_power;
|
|
|
|
+ priv->wakeirq = platform_get_irq(dev, 1);
|
|
+ if (priv->wakeirq > 0) {
|
|
+ err = dev_pm_set_dedicated_wake_irq(hcd->self.controller,
|
|
+ priv->wakeirq);
|
|
+ if (err)
|
|
+ goto err_hcd;
|
|
+ } else if (priv->wakeirq == -EPROBE_DEFER) {
|
|
+ goto err_hcd;
|
|
+ }
|
|
+
|
|
device_wakeup_enable(hcd->self.controller);
|
|
device_enable_async_suspend(hcd->self.controller);
|
|
platform_set_drvdata(dev, hcd);
|
|
|
|
return err;
|
|
|
|
+err_hcd:
|
|
+ usb_remove_hcd(hcd);
|
|
err_power:
|
|
if (pdata->power_off)
|
|
pdata->power_off(dev);
|
|
@@ -275,6 +321,9 @@ static int ehci_platform_remove(struct platform_device *dev)
|
|
struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd);
|
|
int clk;
|
|
|
|
+ if (priv->wakeirq > 0)
|
|
+ dev_pm_clear_wake_irq(hcd->self.controller);
|
|
+
|
|
usb_remove_hcd(hcd);
|
|
|
|
if (pdata->power_off)
|
|
@@ -299,9 +348,14 @@ static int ehci_platform_suspend(struct device *dev)
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
struct usb_ehci_pdata *pdata = dev_get_platdata(dev);
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd);
|
|
bool do_wakeup = device_may_wakeup(dev);
|
|
int ret;
|
|
|
|
+ if (priv->wakeirq > 0 &&
|
|
+ (do_wakeup || dev->power.wakeup_path))
|
|
+ enable_irq_wake(priv->wakeirq);
|
|
+
|
|
ret = ehci_suspend(hcd, do_wakeup);
|
|
if (ret)
|
|
return ret;
|
|
@@ -333,6 +387,11 @@ static int ehci_platform_resume(struct device *dev)
|
|
}
|
|
|
|
ehci_resume(hcd, priv->reset_on_resume);
|
|
+
|
|
+ if (priv->wakeirq > 0 &&
|
|
+ (device_may_wakeup(dev) || dev->power.wakeup_path))
|
|
+ disable_irq_wake(priv->wakeirq);
|
|
+
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
|
|
index 00878c3..1dbbf16 100644
|
|
--- a/drivers/usb/typec/Kconfig
|
|
+++ b/drivers/usb/typec/Kconfig
|
|
@@ -102,6 +102,15 @@ config TYPEC_TPS6598X
|
|
If you choose to build this driver as a dynamically linked module, the
|
|
module will be called tps6598x.ko.
|
|
|
|
+config TYPEC_STUSB
|
|
+ tristate "STMicroelectronics STUSB Type-C controller driver"
|
|
+ depends on I2C
|
|
+ select EXTCON
|
|
+ help
|
|
+ The STMicroelectronics STUSB Type-C controller driver that works
|
|
+ with Type-C Port Controller Manager to provide USB Type-C
|
|
+ functionalities.
|
|
+
|
|
source "drivers/usb/typec/mux/Kconfig"
|
|
|
|
source "drivers/usb/typec/altmodes/Kconfig"
|
|
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
|
|
index 45b0aef..aedb153 100644
|
|
--- a/drivers/usb/typec/Makefile
|
|
+++ b/drivers/usb/typec/Makefile
|
|
@@ -7,6 +7,7 @@ obj-y += fusb302/
|
|
obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o
|
|
obj-$(CONFIG_TYPEC_UCSI) += ucsi/
|
|
obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o
|
|
+obj-$(CONFIG_TYPEC_STUSB) += typec_stusb.o
|
|
obj-$(CONFIG_TYPEC) += mux/
|
|
obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o
|
|
obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o
|
|
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
|
|
index 1916ee1..df4cadd 100644
|
|
--- a/drivers/usb/typec/class.c
|
|
+++ b/drivers/usb/typec/class.c
|
|
@@ -1382,6 +1382,21 @@ void typec_set_pwr_opmode(struct typec_port *port,
|
|
EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
|
|
|
|
/**
|
|
+ * typec_find_power_opmode - Get the typec port power operation mode
|
|
+ * @name: port power operation mode string
|
|
+ *
|
|
+ * This routine is used to find the typec_pwr_opmodes by its string name.
|
|
+ *
|
|
+ * Returns typec_pwr_opmodes if success, otherwise negative error code.
|
|
+ */
|
|
+int typec_find_port_power_opmode(const char *name)
|
|
+{
|
|
+ return match_string(typec_pwr_opmodes,
|
|
+ ARRAY_SIZE(typec_pwr_opmodes), name);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(typec_find_port_power_opmode);
|
|
+
|
|
+/**
|
|
* typec_find_port_power_role - Get the typec port power capability
|
|
* @name: port power capability string
|
|
*
|
|
diff --git a/drivers/usb/typec/typec_stusb.c b/drivers/usb/typec/typec_stusb.c
|
|
new file mode 100644
|
|
index 0000000..86737dc
|
|
--- /dev/null
|
|
+++ b/drivers/usb/typec/typec_stusb.c
|
|
@@ -0,0 +1,918 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * STMicroelectronics STUSB Type-C controller family driver
|
|
+ *
|
|
+ * Copyright (C) 2019, STMicroelectronics
|
|
+ * Author(s): Amelie Delaunay <amelie.delaunay@st.com>
|
|
+ */
|
|
+
|
|
+#include <linux/bitfield.h>
|
|
+#include <linux/extcon-provider.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <linux/usb/typec.h>
|
|
+
|
|
+#define STUSB_ALERT_STATUS 0x0B /* RC */
|
|
+#define STUSB_ALERT_STATUS_MASK_CTRL 0x0C /* RW */
|
|
+#define STUSB_CC_CONNECTION_STATUS_TRANS 0x0D /* RC */
|
|
+#define STUSB_CC_CONNECTION_STATUS 0x0E /* RO */
|
|
+#define STUSB_MONITORING_STATUS_TRANS 0x0F /* RC */
|
|
+#define STUSB_MONITORING_STATUS 0x10 /* RO */
|
|
+#define STUSB_CC_OPERATION_STATUS 0x11 /* RO */
|
|
+#define STUSB_HW_FAULT_STATUS_TRANS 0x12 /* RC */
|
|
+#define STUSB_HW_FAULT_STATUS 0x13 /* RO */
|
|
+#define STUSB_CC_CAPABILITY_CTRL 0x18 /* RW */
|
|
+#define STUSB_CC_VCONN_SWITCH_CTRL 0x1E /* RW */
|
|
+#define STUSB_VCONN_MONITORING_CTRL 0x20 /* RW */
|
|
+#define STUSB_VBUS_MONITORING_RANGE_CTRL 0x22 /* RW */
|
|
+#define STUSB_RESET_CTRL 0x23 /* RW */
|
|
+#define STUSB_VBUS_DISCHARGE_TIME_CTRL 0x25 /* RW */
|
|
+#define STUSB_VBUS_DISCHARGE_STATUS 0x26 /* RO */
|
|
+#define STUSB_VBUS_ENABLE_STATUS 0x27 /* RO */
|
|
+#define STUSB_CC_POWER_MODE_CTRL 0x28 /* RW */
|
|
+#define STUSB_VBUS_MONITORING_CTRL 0x2E /* RW */
|
|
+#define STUSB1600_REG_MAX 0x2F /* RO - Reserved */
|
|
+
|
|
+/* STUSB_ALERT_STATUS/STUSB_ALERT_STATUS_MASK_CTRL bitfields */
|
|
+#define STUSB_HW_FAULT BIT(4)
|
|
+#define STUSB_MONITORING BIT(5)
|
|
+#define STUSB_CC_CONNECTION BIT(6)
|
|
+#define STUSB_ALL_ALERTS GENMASK(6, 4)
|
|
+
|
|
+/* STUSB_CC_CONNECTION_STATUS_TRANS bitfields */
|
|
+#define STUSB_CC_ATTACH_TRANS BIT(0)
|
|
+
|
|
+/* STUSB_CC_CONNECTION_STATUS bitfields */
|
|
+#define STUSB_CC_ATTACH BIT(0)
|
|
+#define STUSB_CC_VCONN_SUPPLY BIT(1)
|
|
+#define STUSB_CC_DATA_ROLE(s) (!!((s) & BIT(2)))
|
|
+#define STUSB_CC_POWER_ROLE(s) (!!((s) & BIT(3)))
|
|
+#define STUSB_CC_ATTACHED_MODE GENMASK(7, 5)
|
|
+
|
|
+/* STUSB_MONITORING_STATUS_TRANS bitfields */
|
|
+#define STUSB_VCONN_PRESENCE_TRANS BIT(0)
|
|
+#define STUSB_VBUS_PRESENCE_TRANS BIT(1)
|
|
+#define STUSB_VBUS_VSAFE0V_TRANS BIT(2)
|
|
+#define STUSB_VBUS_VALID_TRANS BIT(3)
|
|
+
|
|
+/* STUSB_MONITORING_STATUS bitfields */
|
|
+#define STUSB_VCONN_PRESENCE BIT(0)
|
|
+#define STUSB_VBUS_PRESENCE BIT(1)
|
|
+#define STUSB_VBUS_VSAFE0V BIT(2)
|
|
+#define STUSB_VBUS_VALID BIT(3)
|
|
+
|
|
+/* STUSB_CC_OPERATION_STATUS bitfields */
|
|
+#define STUSB_TYPEC_FSM_STATE GENMASK(4, 0)
|
|
+#define STUSB_SINK_POWER_STATE GENMASK(6, 5)
|
|
+#define STUSB_CC_ATTACHED BIT(7)
|
|
+
|
|
+/* STUSB_HW_FAULT_STATUS_TRANS bitfields */
|
|
+#define STUSB_VCONN_SW_OVP_FAULT_TRANS BIT(0)
|
|
+#define STUSB_VCONN_SW_OCP_FAULT_TRANS BIT(1)
|
|
+#define STUSB_VCONN_SW_RVP_FAULT_TRANS BIT(2)
|
|
+#define STUSB_VPU_VALID_TRANS BIT(4)
|
|
+#define STUSB_VPU_OVP_FAULT_TRANS BIT(5)
|
|
+#define STUSB_THERMAL_FAULT BIT(7)
|
|
+
|
|
+/* STUSB_HW_FAULT_STATUS bitfields */
|
|
+#define STUSB_VCONN_SW_OVP_FAULT_CC2 BIT(0)
|
|
+#define STUSB_VCONN_SW_OVP_FAULT_CC1 BIT(1)
|
|
+#define STUSB_VCONN_SW_OCP_FAULT_CC2 BIT(2)
|
|
+#define STUSB_VCONN_SW_OCP_FAULT_CC1 BIT(3)
|
|
+#define STUSB_VCONN_SW_RVP_FAULT_CC2 BIT(4)
|
|
+#define STUSB_VCONN_SW_RVP_FAULT_CC1 BIT(5)
|
|
+#define STUSB_VPU_VALID BIT(6)
|
|
+#define STUSB_VPU_OVP_FAULT BIT(7)
|
|
+
|
|
+/* STUSB_CC_CAPABILITY_CTRL bitfields */
|
|
+#define STUSB_CC_VCONN_SUPPLY_EN BIT(0)
|
|
+#define STUSB_CC_VCONN_DISCHARGE_EN BIT(4)
|
|
+#define STUSB_CC_CURRENT_ADVERTISED GENMASK(7, 6)
|
|
+
|
|
+/* STUSB_VCONN_SWITCH_CTRL bitfields */
|
|
+#define STUSB_CC_VCONN_SWITCH_ILIM GENMASK(3, 0)
|
|
+
|
|
+/* STUSB_VCONN_MONITORING_CTRL bitfields */
|
|
+#define STUSB_VCONN_UVLO_THRESHOLD BIT(6)
|
|
+#define STUSB_VCONN_MONITORING_EN BIT(7)
|
|
+
|
|
+/* STUSB_VBUS_MONITORING_RANGE_CTRL bitfields */
|
|
+#define STUSB_SHIFT_LOW_VBUS_LIMIT GENMASK(3, 0)
|
|
+#define STUSB_SHIFT_HIGH_VBUS_LIMIT GENMASK(7, 4)
|
|
+
|
|
+/* STUSB_RESET_CTRL bitfields */
|
|
+#define STUSB_SW_RESET_EN BIT(0)
|
|
+
|
|
+/* STUSB_VBUS_DISCHARGE_TIME_CTRL bitfields */
|
|
+#define STUSBXX02_VBUS_DISCHARGE_TIME_TO_PDO GENMASK(3, 0)
|
|
+#define STUSB_VBUS_DISCHARGE_TIME_TO_0V GENMASK(7, 4)
|
|
+
|
|
+/* STUSB_VBUS_DISCHARGE_STATUS bitfields */
|
|
+#define STUSB_VBUS_DISCHARGE_EN BIT(7)
|
|
+
|
|
+/* STUSB_VBUS_ENABLE_STATUS bitfields */
|
|
+#define STUSB_VBUS_SOURCE_EN BIT(0)
|
|
+#define STUSB_VBUS_SINK_EN BIT(1)
|
|
+
|
|
+/* STUSB_CC_POWER_MODE_CTRL bitfields */
|
|
+#define STUSB_CC_POWER_MODE GENMASK(2, 0)
|
|
+
|
|
+/* STUSB_VBUS_MONITORING_CTRL bitfields */
|
|
+#define STUSB_VDD_UVLO_DISABLE BIT(0)
|
|
+#define STUSB_VBUS_VSAFE0V_THRESHOLD GENMASK(2, 1)
|
|
+#define STUSB_VBUS_RANGE_DISABLE BIT(4)
|
|
+#define STUSB_VDD_OVLO_DISABLE BIT(6)
|
|
+
|
|
+enum stusb_pwr_mode {
|
|
+ SOURCE_WITH_ACCESSORY,
|
|
+ SINK_WITH_ACCESSORY,
|
|
+ SINK_WITHOUT_ACCESSORY,
|
|
+ DUAL_WITH_ACCESSORY,
|
|
+ DUAL_WITH_ACCESSORY_AND_TRY_SRC,
|
|
+ DUAL_WITH_ACCESSORY_AND_TRY_SNK,
|
|
+};
|
|
+
|
|
+enum stusb_attached_mode {
|
|
+ NO_DEVICE_ATTACHED,
|
|
+ SINK_ATTACHED,
|
|
+ SOURCE_ATTACHED,
|
|
+ DEBUG_ACCESSORY_ATTACHED,
|
|
+ AUDIO_ACCESSORY_ATTACHED,
|
|
+};
|
|
+
|
|
+struct stusb {
|
|
+ struct device *dev;
|
|
+ struct regmap *regmap;
|
|
+ struct regulator *vdd_supply;
|
|
+ struct regulator *vsys_supply;
|
|
+ struct regulator *vconn_supply;
|
|
+ struct regulator *main_supply;
|
|
+
|
|
+ struct typec_port *port;
|
|
+ struct typec_capability capability;
|
|
+ struct typec_partner *partner;
|
|
+
|
|
+ enum typec_port_type port_type;
|
|
+ enum typec_pwr_opmode pwr_opmode;
|
|
+ bool vbus_on;
|
|
+ struct extcon_dev *edev;
|
|
+ struct work_struct wq_detcable;
|
|
+};
|
|
+
|
|
+static const unsigned int stusb_extcon_cable[] = {
|
|
+ EXTCON_USB,
|
|
+ EXTCON_USB_HOST,
|
|
+ EXTCON_NONE,
|
|
+};
|
|
+
|
|
+static bool stusb_reg_writeable(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ switch (reg) {
|
|
+ case STUSB_ALERT_STATUS_MASK_CTRL:
|
|
+ case STUSB_CC_CAPABILITY_CTRL:
|
|
+ case STUSB_CC_VCONN_SWITCH_CTRL:
|
|
+ case STUSB_VCONN_MONITORING_CTRL:
|
|
+ case STUSB_VBUS_MONITORING_RANGE_CTRL:
|
|
+ case STUSB_RESET_CTRL:
|
|
+ case STUSB_VBUS_DISCHARGE_TIME_CTRL:
|
|
+ case STUSB_CC_POWER_MODE_CTRL:
|
|
+ case STUSB_VBUS_MONITORING_CTRL:
|
|
+ return true;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
+
|
|
+static bool stusb_reg_readable(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ if (reg <= 0x0A ||
|
|
+ (reg >= 0x14 && reg <= 0x17) ||
|
|
+ (reg >= 0x19 && reg <= 0x1D) ||
|
|
+ (reg >= 0x29 && reg <= 0x2D) ||
|
|
+ (reg == 0x1F || reg == 0x21 || reg == 0x24 || reg == 0x2F))
|
|
+ return false;
|
|
+ else
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool stusb_reg_volatile(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ switch (reg) {
|
|
+ case STUSB_ALERT_STATUS:
|
|
+ case STUSB_CC_CONNECTION_STATUS_TRANS:
|
|
+ case STUSB_CC_CONNECTION_STATUS:
|
|
+ case STUSB_MONITORING_STATUS_TRANS:
|
|
+ case STUSB_MONITORING_STATUS:
|
|
+ case STUSB_CC_OPERATION_STATUS:
|
|
+ case STUSB_HW_FAULT_STATUS_TRANS:
|
|
+ case STUSB_HW_FAULT_STATUS:
|
|
+ case STUSB_VBUS_DISCHARGE_STATUS:
|
|
+ case STUSB_VBUS_ENABLE_STATUS:
|
|
+ return true;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
+
|
|
+static bool stusb_reg_precious(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ switch (reg) {
|
|
+ case STUSB_ALERT_STATUS:
|
|
+ case STUSB_CC_CONNECTION_STATUS_TRANS:
|
|
+ case STUSB_MONITORING_STATUS_TRANS:
|
|
+ case STUSB_HW_FAULT_STATUS_TRANS:
|
|
+ return true;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
+
|
|
+static const struct regmap_config stusb1600_regmap_config = {
|
|
+ .reg_bits = 8,
|
|
+ .reg_stride = 1,
|
|
+ .val_bits = 8,
|
|
+ .max_register = STUSB1600_REG_MAX,
|
|
+ .writeable_reg = stusb_reg_writeable,
|
|
+ .readable_reg = stusb_reg_readable,
|
|
+ .volatile_reg = stusb_reg_volatile,
|
|
+ .precious_reg = stusb_reg_precious,
|
|
+ .cache_type = REGCACHE_RBTREE,
|
|
+};
|
|
+
|
|
+static void stusb_extcon_detect_cable(struct work_struct *work)
|
|
+{
|
|
+ struct stusb *chip = container_of(work, struct stusb, wq_detcable);
|
|
+ u32 conn_status, vbus_status;
|
|
+ bool id, vbus;
|
|
+ int ret;
|
|
+
|
|
+ /* Check ID and Vbus to update cable state */
|
|
+ ret = regmap_read(chip->regmap, STUSB_CC_CONNECTION_STATUS,
|
|
+ &conn_status);
|
|
+ if (ret)
|
|
+ return;
|
|
+
|
|
+ ret = regmap_read(chip->regmap, STUSB_VBUS_ENABLE_STATUS,
|
|
+ &vbus_status);
|
|
+ if (ret)
|
|
+ return;
|
|
+
|
|
+ /* 0 = Device, 1 = Host */
|
|
+ id = STUSB_CC_DATA_ROLE(conn_status);
|
|
+
|
|
+ if (STUSB_CC_POWER_ROLE(conn_status)) /* Source */
|
|
+ vbus = !!(vbus_status & STUSB_VBUS_SOURCE_EN);
|
|
+ else /* Sink */
|
|
+ vbus = !!(vbus_status & STUSB_VBUS_SINK_EN);
|
|
+
|
|
+ dev_dbg(chip->dev, "role=%s vbus=%sable\n",
|
|
+ id ? "Host" : "Device", vbus ? "en" : "dis");
|
|
+
|
|
+ /*
|
|
+ * !vbus = detached, so neither B-Session Valid nor A-Session Valid
|
|
+ * !vbus = !EXTCON_USB && !EXTCON_USB_HOST
|
|
+ * vbus = attached, so either B-Session Valid or A-Session Valid
|
|
+ * vbus && !id = B-Session Valid = EXTCON_USB && !EXTCON_USB_HOST
|
|
+ * vbus && id = A-Session Valid = !EXTCON_USB && EXTCON_USB_HOST
|
|
+ */
|
|
+
|
|
+ if (!vbus || !id) /* Detached or B-Session */
|
|
+ extcon_set_state_sync(chip->edev, EXTCON_USB_HOST, false);
|
|
+ if (!vbus || id) /* Detached or A-Session */
|
|
+ extcon_set_state_sync(chip->edev, EXTCON_USB, false);
|
|
+
|
|
+ if (vbus && id) /* Attached and A-Session Valid */
|
|
+ extcon_set_state_sync(chip->edev, EXTCON_USB_HOST, true);
|
|
+ if (vbus && !id) /* Attached and B-Session Valid */
|
|
+ extcon_set_state_sync(chip->edev, EXTCON_USB, true);
|
|
+}
|
|
+
|
|
+static bool stusb_get_vconn(struct stusb *chip)
|
|
+{
|
|
+ u32 val;
|
|
+ int ret;
|
|
+
|
|
+ ret = regmap_read(chip->regmap, STUSB_CC_CAPABILITY_CTRL, &val);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Unable to get Vconn status: %d\n", ret);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return !!FIELD_GET(STUSB_CC_VCONN_SUPPLY_EN, val);
|
|
+}
|
|
+
|
|
+static int stusb_set_vconn(struct stusb *chip, bool on)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ /* Manage VCONN input supply */
|
|
+ if (chip->vconn_supply) {
|
|
+ if (on) {
|
|
+ ret = regulator_enable(chip->vconn_supply);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "failed to enable vconn supply: %d\n",
|
|
+ ret);
|
|
+ return ret;
|
|
+ }
|
|
+ } else {
|
|
+ regulator_disable(chip->vconn_supply);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Manage VCONN monitoring and power path */
|
|
+ ret = regmap_update_bits(chip->regmap, STUSB_VCONN_MONITORING_CTRL,
|
|
+ STUSB_VCONN_MONITORING_EN,
|
|
+ on ? STUSB_VCONN_MONITORING_EN : 0);
|
|
+ if (ret)
|
|
+ goto vconn_reg_disable;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+vconn_reg_disable:
|
|
+ if (chip->vconn_supply && on)
|
|
+ regulator_disable(chip->vconn_supply);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static enum typec_pwr_opmode stusb_get_pwr_opmode(struct stusb *chip)
|
|
+{
|
|
+ u32 val;
|
|
+ int ret;
|
|
+
|
|
+ ret = regmap_read(chip->regmap, STUSB_CC_CAPABILITY_CTRL, &val);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Unable to get pwr opmode: %d\n", ret);
|
|
+ return TYPEC_PWR_MODE_USB;
|
|
+ }
|
|
+
|
|
+ return FIELD_GET(STUSB_CC_CURRENT_ADVERTISED, val);
|
|
+}
|
|
+
|
|
+static enum typec_accessory stusb_get_accessory(u32 status)
|
|
+{
|
|
+ enum stusb_attached_mode mode;
|
|
+
|
|
+ mode = FIELD_GET(STUSB_CC_ATTACHED_MODE, status);
|
|
+
|
|
+ switch (mode) {
|
|
+ case DEBUG_ACCESSORY_ATTACHED:
|
|
+ return TYPEC_ACCESSORY_DEBUG;
|
|
+ case AUDIO_ACCESSORY_ATTACHED:
|
|
+ return TYPEC_ACCESSORY_AUDIO;
|
|
+ default:
|
|
+ return TYPEC_ACCESSORY_NONE;
|
|
+ }
|
|
+}
|
|
+
|
|
+static enum typec_role stusb_get_vconn_role(u32 status)
|
|
+{
|
|
+ if (FIELD_GET(STUSB_CC_VCONN_SUPPLY, status))
|
|
+ return TYPEC_SOURCE;
|
|
+ else
|
|
+ return TYPEC_SINK;
|
|
+}
|
|
+
|
|
+static int stusb_attach(struct stusb *chip, u32 status)
|
|
+{
|
|
+ struct typec_partner_desc desc;
|
|
+ int ret;
|
|
+
|
|
+ if ((STUSB_CC_POWER_ROLE(status) == TYPEC_SOURCE) &&
|
|
+ chip->vdd_supply) {
|
|
+ ret = regulator_enable(chip->vdd_supply);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to enable Vbus supply: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ chip->vbus_on = true;
|
|
+ }
|
|
+
|
|
+ desc.usb_pd = false;
|
|
+ desc.accessory = stusb_get_accessory(status);
|
|
+ desc.identity = NULL;
|
|
+
|
|
+ chip->partner = typec_register_partner(chip->port, &desc);
|
|
+ if (IS_ERR(chip->partner)) {
|
|
+ ret = PTR_ERR(chip->partner);
|
|
+ goto vbus_disable;
|
|
+ }
|
|
+
|
|
+ typec_set_pwr_role(chip->port, STUSB_CC_POWER_ROLE(status));
|
|
+ typec_set_pwr_opmode(chip->port, stusb_get_pwr_opmode(chip));
|
|
+ typec_set_vconn_role(chip->port, stusb_get_vconn_role(status));
|
|
+ typec_set_data_role(chip->port, STUSB_CC_DATA_ROLE(status));
|
|
+
|
|
+ queue_work(system_power_efficient_wq, &chip->wq_detcable);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+vbus_disable:
|
|
+ if (chip->vbus_on) {
|
|
+ regulator_disable(chip->vdd_supply);
|
|
+ chip->vbus_on = false;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void stusb_detach(struct stusb *chip, u32 status)
|
|
+{
|
|
+ typec_unregister_partner(chip->partner);
|
|
+ chip->partner = NULL;
|
|
+
|
|
+ queue_work(system_power_efficient_wq, &chip->wq_detcable);
|
|
+
|
|
+ typec_set_pwr_role(chip->port, STUSB_CC_POWER_ROLE(status));
|
|
+ typec_set_pwr_opmode(chip->port, TYPEC_PWR_MODE_USB);
|
|
+ typec_set_vconn_role(chip->port, stusb_get_vconn_role(status));
|
|
+ typec_set_data_role(chip->port, STUSB_CC_DATA_ROLE(status));
|
|
+
|
|
+ if (chip->vbus_on) {
|
|
+ regulator_disable(chip->vdd_supply);
|
|
+ chip->vbus_on = false;
|
|
+ }
|
|
+}
|
|
+
|
|
+static irqreturn_t stusb_irq_handler(int irq, void *data)
|
|
+{
|
|
+ struct stusb *chip = data;
|
|
+ u32 pending, trans, status;
|
|
+ int ret;
|
|
+
|
|
+ ret = regmap_read(chip->regmap, STUSB_ALERT_STATUS, &pending);
|
|
+ if (ret)
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ if (pending & STUSB_CC_CONNECTION) {
|
|
+ ret = regmap_read(chip->regmap,
|
|
+ STUSB_CC_CONNECTION_STATUS_TRANS, &trans);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+ ret = regmap_read(chip->regmap, STUSB_CC_CONNECTION_STATUS,
|
|
+ &status);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ if (trans & STUSB_CC_ATTACH_TRANS) {
|
|
+ if (status & STUSB_CC_ATTACH) {
|
|
+ ret = stusb_attach(chip, status);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+ } else {
|
|
+ stusb_detach(chip, status);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+err:
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static int stusb_irq_init(struct stusb *chip, int irq)
|
|
+{
|
|
+ u32 status;
|
|
+ int ret;
|
|
+
|
|
+ ret = regmap_read(chip->regmap, STUSB_CC_CONNECTION_STATUS, &status);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (status & STUSB_CC_ATTACH) {
|
|
+ ret = stusb_attach(chip, status);
|
|
+ if (ret)
|
|
+ dev_err(chip->dev, "attach failed: %d\n", ret);
|
|
+ }
|
|
+
|
|
+ ret = devm_request_threaded_irq(chip->dev, irq, NULL, stusb_irq_handler,
|
|
+ IRQF_ONESHOT, dev_name(chip->dev),
|
|
+ chip);
|
|
+ if (ret)
|
|
+ goto partner_unregister;
|
|
+
|
|
+ /* Unmask CC_CONNECTION events */
|
|
+ ret = regmap_write_bits(chip->regmap, STUSB_ALERT_STATUS_MASK_CTRL,
|
|
+ STUSB_CC_CONNECTION, 0);
|
|
+ if (ret)
|
|
+ goto partner_unregister;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+partner_unregister:
|
|
+ if (chip->partner) {
|
|
+ typec_unregister_partner(chip->partner);
|
|
+ chip->partner = NULL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int stusb_init(struct stusb *chip)
|
|
+{
|
|
+ u32 val;
|
|
+ int ret;
|
|
+
|
|
+ /* Change the default Type-C power mode */
|
|
+ if (chip->port_type == TYPEC_PORT_SRC)
|
|
+ ret = regmap_update_bits(chip->regmap,
|
|
+ STUSB_CC_POWER_MODE_CTRL,
|
|
+ STUSB_CC_POWER_MODE,
|
|
+ SOURCE_WITH_ACCESSORY);
|
|
+ else if (chip->port_type == TYPEC_PORT_SNK)
|
|
+ ret = regmap_update_bits(chip->regmap,
|
|
+ STUSB_CC_POWER_MODE_CTRL,
|
|
+ STUSB_CC_POWER_MODE,
|
|
+ SINK_WITH_ACCESSORY);
|
|
+ else /* (capability->type == TYPEC_PORT_DRP) */
|
|
+ ret = regmap_update_bits(chip->regmap,
|
|
+ STUSB_CC_POWER_MODE_CTRL,
|
|
+ STUSB_CC_POWER_MODE,
|
|
+ DUAL_WITH_ACCESSORY);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (chip->port_type == TYPEC_PORT_SNK)
|
|
+ goto skip_src;
|
|
+
|
|
+ /* Change the default Type-C Source power operation mode capability */
|
|
+ ret = regmap_update_bits(chip->regmap, STUSB_CC_CAPABILITY_CTRL,
|
|
+ STUSB_CC_CURRENT_ADVERTISED,
|
|
+ FIELD_PREP(STUSB_CC_CURRENT_ADVERTISED,
|
|
+ chip->pwr_opmode));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Manage Type-C Source Vconn supply */
|
|
+ if (stusb_get_vconn(chip)) {
|
|
+ ret = stusb_set_vconn(chip, true);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+skip_src:
|
|
+ /* Mask all events interrupts - to be unmasked with interrupt support */
|
|
+ ret = regmap_update_bits(chip->regmap, STUSB_ALERT_STATUS_MASK_CTRL,
|
|
+ STUSB_ALL_ALERTS, STUSB_ALL_ALERTS);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Read status at least once to clear any stale interrupts */
|
|
+ regmap_read(chip->regmap, STUSB_ALERT_STATUS, &val);
|
|
+ regmap_read(chip->regmap, STUSB_CC_CONNECTION_STATUS_TRANS, &val);
|
|
+ regmap_read(chip->regmap, STUSB_MONITORING_STATUS_TRANS, &val);
|
|
+ regmap_read(chip->regmap, STUSB_HW_FAULT_STATUS_TRANS, &val);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stusb_fw_get_caps(struct stusb *chip)
|
|
+{
|
|
+ struct fwnode_handle *fwnode = device_get_named_child_node(chip->dev,
|
|
+ "connector");
|
|
+ const char *cap_str;
|
|
+ int ret;
|
|
+
|
|
+ if (!fwnode)
|
|
+ return -EINVAL;
|
|
+
|
|
+ chip->capability.fwnode = fwnode;
|
|
+
|
|
+ ret = fwnode_property_read_string(fwnode, "power-role", &cap_str);
|
|
+ if (!ret) {
|
|
+ chip->port_type = typec_find_port_power_role(cap_str);
|
|
+ if (chip->port_type < 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ chip->capability.type = chip->port_type;
|
|
+ }
|
|
+
|
|
+ if (chip->port_type == TYPEC_PORT_SNK)
|
|
+ goto sink;
|
|
+
|
|
+ if (chip->port_type == TYPEC_PORT_DRP)
|
|
+ chip->capability.prefer_role = TYPEC_SINK;
|
|
+
|
|
+ ret = fwnode_property_read_string(fwnode, "power-opmode", &cap_str);
|
|
+ if (!ret) {
|
|
+ chip->pwr_opmode = typec_find_port_power_opmode(cap_str);
|
|
+
|
|
+ /* Power delivery not yet supported */
|
|
+ if (chip->pwr_opmode < 0 ||
|
|
+ chip->pwr_opmode == TYPEC_PWR_MODE_PD) {
|
|
+ dev_err(chip->dev, "bad power operation mode: %d\n",
|
|
+ chip->pwr_opmode);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ } else {
|
|
+ chip->pwr_opmode = stusb_get_pwr_opmode(chip);
|
|
+ }
|
|
+
|
|
+sink:
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stusb_get_caps(struct stusb *chip, bool *try)
|
|
+{
|
|
+ enum typec_port_type *type = &chip->capability.type;
|
|
+ enum typec_port_data *data = &chip->capability.data;
|
|
+ enum typec_accessory *accessory = chip->capability.accessory;
|
|
+ u32 val;
|
|
+ int ret;
|
|
+
|
|
+ chip->capability.revision = USB_TYPEC_REV_1_2;
|
|
+
|
|
+ ret = regmap_read(chip->regmap, STUSB_CC_POWER_MODE_CTRL, &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ *try = false;
|
|
+
|
|
+ switch (FIELD_GET(STUSB_CC_POWER_MODE, val)) {
|
|
+ case SOURCE_WITH_ACCESSORY:
|
|
+ *type = TYPEC_PORT_SRC;
|
|
+ *data = TYPEC_PORT_DFP;
|
|
+ *accessory++ = TYPEC_ACCESSORY_AUDIO;
|
|
+ *accessory++ = TYPEC_ACCESSORY_DEBUG;
|
|
+ break;
|
|
+ case SINK_WITH_ACCESSORY:
|
|
+ *type = TYPEC_PORT_SNK;
|
|
+ *data = TYPEC_PORT_UFP;
|
|
+ *accessory++ = TYPEC_ACCESSORY_AUDIO;
|
|
+ *accessory++ = TYPEC_ACCESSORY_DEBUG;
|
|
+ break;
|
|
+ case SINK_WITHOUT_ACCESSORY:
|
|
+ *type = TYPEC_PORT_SNK;
|
|
+ *data = TYPEC_PORT_UFP;
|
|
+ break;
|
|
+ case DUAL_WITH_ACCESSORY:
|
|
+ *type = TYPEC_PORT_DRP;
|
|
+ *data = TYPEC_PORT_DRD;
|
|
+ *accessory++ = TYPEC_ACCESSORY_AUDIO;
|
|
+ *accessory++ = TYPEC_ACCESSORY_DEBUG;
|
|
+ break;
|
|
+ case DUAL_WITH_ACCESSORY_AND_TRY_SRC:
|
|
+ case DUAL_WITH_ACCESSORY_AND_TRY_SNK:
|
|
+ *type = TYPEC_PORT_DRP;
|
|
+ *data = TYPEC_PORT_DRD;
|
|
+ *accessory++ = TYPEC_ACCESSORY_AUDIO;
|
|
+ *accessory++ = TYPEC_ACCESSORY_DEBUG;
|
|
+ *try = true;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ chip->port_type = *type;
|
|
+
|
|
+ return stusb_fw_get_caps(chip);
|
|
+}
|
|
+
|
|
+static const struct of_device_id stusb_of_match[] = {
|
|
+ { .compatible = "st,stusb1600", .data = &stusb1600_regmap_config},
|
|
+ {},
|
|
+};
|
|
+
|
|
+static int stusb_probe(struct i2c_client *client,
|
|
+ const struct i2c_device_id *id)
|
|
+{
|
|
+ struct stusb *chip;
|
|
+ const struct of_device_id *match;
|
|
+ struct regmap_config *regmap_config;
|
|
+ bool try_role;
|
|
+ int ret;
|
|
+
|
|
+ chip = devm_kzalloc(&client->dev, sizeof(struct stusb), GFP_KERNEL);
|
|
+ if (!chip)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ i2c_set_clientdata(client, chip);
|
|
+
|
|
+ match = i2c_of_match_device(stusb_of_match, client);
|
|
+ regmap_config = (struct regmap_config *)match->data;
|
|
+ chip->regmap = devm_regmap_init_i2c(client, regmap_config);
|
|
+ if (IS_ERR(chip->regmap)) {
|
|
+ ret = PTR_ERR(chip->regmap);
|
|
+ dev_err(&client->dev,
|
|
+ "Failed to allocate register map:%d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ chip->dev = &client->dev;
|
|
+
|
|
+ chip->vsys_supply = devm_regulator_get_optional(chip->dev, "vsys");
|
|
+ if (IS_ERR(chip->vsys_supply)) {
|
|
+ ret = PTR_ERR(chip->vsys_supply);
|
|
+ if (ret != -ENODEV)
|
|
+ return ret;
|
|
+ chip->vsys_supply = NULL;
|
|
+ }
|
|
+
|
|
+ chip->vdd_supply = devm_regulator_get_optional(chip->dev, "vdd");
|
|
+ if (IS_ERR(chip->vdd_supply)) {
|
|
+ ret = PTR_ERR(chip->vdd_supply);
|
|
+ if (ret != -ENODEV)
|
|
+ return ret;
|
|
+ chip->vdd_supply = NULL;
|
|
+ }
|
|
+
|
|
+ chip->vconn_supply = devm_regulator_get_optional(chip->dev, "vconn");
|
|
+ if (IS_ERR(chip->vconn_supply)) {
|
|
+ ret = PTR_ERR(chip->vconn_supply);
|
|
+ if (ret != -ENODEV)
|
|
+ return ret;
|
|
+ chip->vconn_supply = NULL;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * When both VDD and VSYS power supplies are present, the low power
|
|
+ * supply VSYS is selected when VSYS voltage is above 3.1 V.
|
|
+ * Otherwise VDD is selected.
|
|
+ */
|
|
+ if (chip->vdd_supply &&
|
|
+ (!chip->vsys_supply ||
|
|
+ (regulator_get_voltage(chip->vsys_supply) <= 3100000)))
|
|
+ chip->main_supply = chip->vdd_supply;
|
|
+ else
|
|
+ chip->main_supply = chip->vsys_supply;
|
|
+
|
|
+ if (chip->main_supply) {
|
|
+ ret = regulator_enable(chip->main_supply);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to enable main supply: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ret = stusb_get_caps(chip, &try_role);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to get port caps: %d\n", ret);
|
|
+ goto main_reg_disable;
|
|
+ }
|
|
+
|
|
+ ret = stusb_init(chip);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to init port: %d\n", ret);
|
|
+ goto main_reg_disable;
|
|
+ }
|
|
+
|
|
+ chip->port = typec_register_port(chip->dev, &chip->capability);
|
|
+ if (!chip->port) {
|
|
+ ret = -ENODEV;
|
|
+ goto all_reg_disable;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Default power operation mode initialization: will be updated upon
|
|
+ * attach/detach interrupt
|
|
+ */
|
|
+ typec_set_pwr_opmode(chip->port, chip->pwr_opmode);
|
|
+
|
|
+ if (!client->irq) {
|
|
+ /*
|
|
+ * If Source or Dual power role, need to enable VDD supply
|
|
+ * providing Vbus if present. In case of interrupt support,
|
|
+ * VDD supply will be dynamically managed upon attach/detach
|
|
+ * interrupt.
|
|
+ */
|
|
+ if ((chip->port_type != TYPEC_PORT_SNK) && chip->vdd_supply) {
|
|
+ ret = regulator_enable(chip->vdd_supply);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to enable VDD supply: %d\n",
|
|
+ ret);
|
|
+ goto port_unregister;
|
|
+ }
|
|
+ chip->vbus_on = true;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ chip->edev = devm_extcon_dev_allocate(chip->dev, stusb_extcon_cable);
|
|
+ if (IS_ERR(chip->edev)) {
|
|
+ ret = PTR_ERR(chip->edev);
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to allocate extcon device: %d\n", ret);
|
|
+ goto port_unregister;
|
|
+ }
|
|
+
|
|
+ ret = devm_extcon_dev_register(chip->dev, chip->edev);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to register extcon device: %d\n", ret);
|
|
+ goto port_unregister;
|
|
+ }
|
|
+
|
|
+ INIT_WORK(&chip->wq_detcable, stusb_extcon_detect_cable);
|
|
+
|
|
+ ret = stusb_irq_init(chip, client->irq);
|
|
+ if (ret)
|
|
+ goto cancel_work_sync;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+cancel_work_sync:
|
|
+ cancel_work_sync(&chip->wq_detcable);
|
|
+port_unregister:
|
|
+ typec_unregister_port(chip->port);
|
|
+all_reg_disable:
|
|
+ if (stusb_get_vconn(chip))
|
|
+ stusb_set_vconn(chip, false);
|
|
+main_reg_disable:
|
|
+ if (chip->main_supply)
|
|
+ regulator_disable(chip->main_supply);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int stusb_remove(struct i2c_client *client)
|
|
+{
|
|
+ struct stusb *chip = i2c_get_clientdata(client);
|
|
+
|
|
+ if (chip->partner) {
|
|
+ typec_unregister_partner(chip->partner);
|
|
+ chip->partner = NULL;
|
|
+ }
|
|
+
|
|
+ if (chip->vbus_on)
|
|
+ regulator_disable(chip->vdd_supply);
|
|
+
|
|
+ if (chip->edev)
|
|
+ cancel_work_sync(&chip->wq_detcable);
|
|
+
|
|
+ typec_unregister_port(chip->port);
|
|
+
|
|
+ if (stusb_get_vconn(chip))
|
|
+ stusb_set_vconn(chip, false);
|
|
+
|
|
+ if (chip->main_supply)
|
|
+ regulator_disable(chip->main_supply);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __maybe_unused stusb_suspend(struct device *dev)
|
|
+{
|
|
+ struct stusb *chip = dev_get_drvdata(dev);
|
|
+
|
|
+ /* Mask interrupts */
|
|
+ return regmap_update_bits(chip->regmap, STUSB_ALERT_STATUS_MASK_CTRL,
|
|
+ STUSB_ALL_ALERTS, STUSB_ALL_ALERTS);
|
|
+}
|
|
+
|
|
+static int __maybe_unused stusb_resume(struct device *dev)
|
|
+{
|
|
+ struct stusb *chip = dev_get_drvdata(dev);
|
|
+ u32 status;
|
|
+ int ret;
|
|
+
|
|
+ ret = regcache_sync(chip->regmap);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Unmask CC_CONNECTION events - chip->edev implies IRQ support */
|
|
+ if (chip->edev)
|
|
+ return regmap_write_bits(chip->regmap,
|
|
+ STUSB_ALERT_STATUS_MASK_CTRL,
|
|
+ STUSB_CC_CONNECTION, 0);
|
|
+
|
|
+ /* Check if attach/detach occurred during low power */
|
|
+ ret = regmap_read(chip->regmap, STUSB_CC_CONNECTION_STATUS, &status);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (chip->partner && !(status & STUSB_CC_ATTACH))
|
|
+ stusb_detach(chip, status);
|
|
+
|
|
+ if (!chip->partner && (status & STUSB_CC_ATTACH)) {
|
|
+ ret = stusb_attach(chip, status);
|
|
+ if (ret)
|
|
+ dev_err(chip->dev, "attach failed: %d\n", ret);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static SIMPLE_DEV_PM_OPS(stusb_pm_ops, stusb_suspend, stusb_resume);
|
|
+
|
|
+static struct i2c_driver stusb_driver = {
|
|
+ .driver = {
|
|
+ .name = "typec_stusb",
|
|
+ .pm = &stusb_pm_ops,
|
|
+ .of_match_table = stusb_of_match,
|
|
+ },
|
|
+ .probe = stusb_probe,
|
|
+ .remove = stusb_remove,
|
|
+};
|
|
+module_i2c_driver(stusb_driver);
|
|
+
|
|
+MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>");
|
|
+MODULE_DESCRIPTION("STMicroelectronics STUSB Type-C controller driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
|
|
index 7df4eca..2671776 100644
|
|
--- a/include/linux/usb/typec.h
|
|
+++ b/include/linux/usb/typec.h
|
|
@@ -241,6 +241,7 @@ int typec_set_orientation(struct typec_port *port,
|
|
enum typec_orientation typec_get_orientation(struct typec_port *port);
|
|
int typec_set_mode(struct typec_port *port, int mode);
|
|
|
|
+int typec_find_port_power_opmode(const char *name);
|
|
int typec_find_port_power_role(const char *name);
|
|
int typec_find_power_role(const char *name);
|
|
int typec_find_port_data_role(const char *name);
|
|
--
|
|
2.7.4
|
|
|