meta-st-stm32mp/recipes-kernel/linux/linux-stm32mp/4.19/4.19.94/0026-ARM-stm32mp1-r3-TTY-US...

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