Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Documentation/devicetree/bindings/i2c/qcom,i2c-geni-qcom.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ properties:
power-domains:
maxItems: 1

qcom,qup-multi-owner:
type: boolean
description:
Indicates that the QUP-based controller is shared with one or more
other system processors and must not be assumed to have exclusive
ownership by the operating system.

reg:
maxItems: 1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ properties:
compatible:
enum:
- focaltech,ft8112
- focaltech,ft3d81

reg:
maxItems: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ properties:
compatible:
items:
- enum:
- qcom,qcs615-qspi
- qcom,sc7180-qspi
- qcom,sc7280-qspi
- qcom,sdm845-qspi
Expand Down
44 changes: 43 additions & 1 deletion drivers/dma/qcom/gpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/*
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2020, Linaro Limited
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/

#include <dt-bindings/dma/qcom-gpi.h>
Expand Down Expand Up @@ -67,6 +68,14 @@
#define TRE_DMA_LEN GENMASK(23, 0)
#define TRE_DMA_IMMEDIATE_LEN GENMASK(3, 0)

/* Lock TRE */
#define TRE_LOCK BIT(0)
#define TRE_MINOR_TYPE GENMASK(19, 16)
#define TRE_MAJOR_TYPE GENMASK(23, 20)

/* Unlock TRE */
#define TRE_UNLOCK BIT(8)

/* Register offsets from gpi-top */
#define GPII_n_CH_k_CNTXT_0_OFFS(n, k) (0x20000 + (0x4000 * (n)) + (0x80 * (k)))
#define GPII_n_CH_k_CNTXT_0_EL_SIZE GENMASK(31, 24)
Expand Down Expand Up @@ -518,7 +527,7 @@ struct gpii {
bool ieob_set;
};

#define MAX_TRE 3
#define MAX_TRE 5

struct gpi_desc {
struct virt_dma_desc vd;
Expand Down Expand Up @@ -1625,12 +1634,27 @@ static int gpi_create_i2c_tre(struct gchan *chan, struct gpi_desc *desc,
unsigned long flags)
{
struct gpi_i2c_config *i2c = chan->config;
enum gpi_lock_action lock_action = i2c->lock_action;
struct device *dev = chan->gpii->gpi_dev->dev;
unsigned int tre_idx = 0;
dma_addr_t address;
struct gpi_tre *tre;
unsigned int i;

/* Optional lock TRE before transfer */
if (lock_action == GPI_LOCK_ACQUIRE) {
tre = &desc->tre[tre_idx];
tre_idx++;

tre->dword[0] = 0;
tre->dword[1] = 0;
tre->dword[2] = 0;
tre->dword[3] = u32_encode_bits(1, TRE_LOCK);
tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOB);
tre->dword[3] |= u32_encode_bits(0, TRE_MINOR_TYPE);
tre->dword[3] |= u32_encode_bits(3, TRE_MAJOR_TYPE);
}

/* first create config tre if applicable */
if (i2c->set_config) {
tre = &desc->tre[tre_idx];
Expand Down Expand Up @@ -1690,6 +1714,24 @@ static int gpi_create_i2c_tre(struct gchan *chan, struct gpi_desc *desc,

if (!(flags & DMA_PREP_INTERRUPT))
tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_BEI);

/* If multi-owner and this is the release boundary, chain it */
if (i2c->lock_action == GPI_LOCK_RELEASE)
tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_CHAIN);
}

/* Optional unlock TRE after transfer */
if (lock_action == GPI_LOCK_RELEASE && i2c->op != I2C_READ) {
tre = &desc->tre[tre_idx];
tre_idx++;

tre->dword[0] = 0;
tre->dword[1] = 0;
tre->dword[2] = 0;
tre->dword[3] = u32_encode_bits(1, TRE_UNLOCK);
tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOB);
tre->dword[3] |= u32_encode_bits(1, TRE_MINOR_TYPE);
tre->dword[3] |= u32_encode_bits(3, TRE_MAJOR_TYPE);
}

for (i = 0; i < tre_idx; i++)
Expand Down
51 changes: 48 additions & 3 deletions drivers/i2c/busses/i2c-qcom-geni.c
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,14 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
if (i < num - 1)
peripheral.stretch = 1;

peripheral.lock_action = GPI_LOCK_NONE;
if (gi2c->se.multi_owner) {
if (i == 0)
peripheral.lock_action = GPI_LOCK_ACQUIRE;
else if (i == num - 1)
peripheral.lock_action = GPI_LOCK_RELEASE;
}

peripheral.addr = msgs[i].addr;
if (i > 0 && (!(msgs[i].flags & I2C_M_RD)))
peripheral.multi_msg = false;
Expand Down Expand Up @@ -1028,6 +1036,17 @@ static int geni_i2c_probe(struct platform_device *pdev)
gi2c->clk_freq_out = I2C_MAX_STANDARD_MODE_FREQ;
}

if (of_property_read_bool(pdev->dev.of_node, "qcom,qup-multi-owner")) {
/*
* Multi-owner controller configuration: the controller may be
* used by another system processor. Mark the SE as shared so
* common GENI resource handling can avoid pin state changes
* that would disrupt the other user.
*/
gi2c->se.multi_owner = true;
dev_dbg(&pdev->dev, "I2C controller is shared with another system processor\n");
}

if (has_acpi_companion(dev))
ACPI_COMPANION_SET(&gi2c->adap.dev, ACPI_COMPANION(dev));

Expand All @@ -1045,8 +1064,14 @@ static int geni_i2c_probe(struct platform_device *pdev)
spin_lock_init(&gi2c->lock);
platform_set_drvdata(pdev, gi2c);

/* Keep interrupts disabled initially to allow for low-power modes */
ret = devm_request_irq(dev, gi2c->irq, geni_i2c_irq, IRQF_NO_AUTOEN,
/*
* Keep interrupts disabled initially to allow for low-power modes.
* IRQF_NO_SUSPEND: Keep IRQ enabled during suspend to handle I2C transfers
* in noirq phase (e.g., from PCIe driver's noirq_resume).
* IRQF_EARLY_RESUME: Enable IRQ early during resume sequence.
*/
ret = devm_request_irq(dev, gi2c->irq, geni_i2c_irq,
IRQF_NO_AUTOEN | IRQF_NO_SUSPEND | IRQF_EARLY_RESUME,
dev_name(dev), gi2c);
if (ret)
return dev_err_probe(dev, ret,
Expand Down Expand Up @@ -1103,7 +1128,9 @@ static int geni_i2c_probe(struct platform_device *pdev)
}

if (fifo_disable) {
/* FIFO is disabled, so we can only use GPI DMA */
/* FIFO is disabled, so we can only use GPI DMA.
* SE can be shared in GSI mode between subsystems, each SS owns a GPII.
*/
gi2c->gpi_mode = true;
ret = setup_gpi_dma(gi2c);
if (ret)
Expand All @@ -1112,6 +1139,10 @@ static int geni_i2c_probe(struct platform_device *pdev)
dev_dbg(dev, "Using GPI DMA mode for I2C\n");
} else {
gi2c->gpi_mode = false;

if (gi2c->se.multi_owner)
dev_err_probe(dev, -EINVAL, "I2C sharing not supported in non GSI mode\n");

tx_depth = geni_se_get_tx_fifo_depth(&gi2c->se);

/* I2C Master Hub Serial Elements doesn't have the HW_PARAM_0 register */
Expand Down Expand Up @@ -1258,6 +1289,20 @@ static int __maybe_unused geni_i2c_suspend_noirq(struct device *dev)
static int __maybe_unused geni_i2c_resume_noirq(struct device *dev)
{
struct geni_i2c_dev *gi2c = dev_get_drvdata(dev);
int ret = 0;

/*
* Resume hardware to handle I2C transfers from other drivers'
* noirq_resume callbacks (e.g., PCIe driver).
* pm_runtime_force_resume() properly handles PM state and usage_count.
*/
if (gi2c->suspended) {
ret = pm_runtime_force_resume(dev);
if (ret) {
dev_err(dev, "Failed to resume I2C during noirq: %d\n", ret);
return ret;
}
}

i2c_mark_adapter_resumed(&gi2c->adap);
return 0;
Expand Down
15 changes: 11 additions & 4 deletions drivers/soc/qcom/qcom-geni-se.c
Original file line number Diff line number Diff line change
Expand Up @@ -597,10 +597,17 @@ int geni_se_resources_off(struct geni_se *se)

if (has_acpi_companion(se->dev))
return 0;

ret = pinctrl_pm_select_sleep_state(se->dev);
if (ret)
return ret;
/*
* Select the "sleep" pinctrl state only when the serial engine is
* exclusively owned by this system processor. For shared controller
* configurations, another system processor may still be using the pins,
* and switching them to "sleep" can disrupt ongoing transfers.
*/
if (!se->multi_owner) {
ret = pinctrl_pm_select_sleep_state(se->dev);
if (ret)
return ret;
}

geni_se_clks_off(se);
return 0;
Expand Down
36 changes: 33 additions & 3 deletions drivers/spi/spi-qcom-qspi.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ struct qcom_qspi {
void *virt_cmd_desc[QSPI_MAX_SG];
unsigned int n_cmd_desc;
struct icc_path *icc_path_cpu_to_qspi;
struct icc_path *icc_path_mem;
unsigned long last_speed;
/* Lock to protect data accessed by IRQs */
spinlock_t lock;
Expand Down Expand Up @@ -272,7 +273,7 @@ static void qcom_qspi_handle_err(struct spi_controller *host,
static int qcom_qspi_set_speed(struct qcom_qspi *ctrl, unsigned long speed_hz)
{
int ret;
unsigned int avg_bw_cpu;
unsigned int avg_bw_cpu, avg_bw_mem;

if (speed_hz == ctrl->last_speed)
return 0;
Expand All @@ -285,7 +286,7 @@ static int qcom_qspi_set_speed(struct qcom_qspi *ctrl, unsigned long speed_hz)
}

/*
* Set BW quota for CPU.
* Set BW quota for CPU and memory paths.
* We don't have explicit peak requirement so keep it equal to avg_bw.
*/
avg_bw_cpu = Bps_to_icc(speed_hz);
Expand All @@ -296,6 +297,13 @@ static int qcom_qspi_set_speed(struct qcom_qspi *ctrl, unsigned long speed_hz)
return ret;
}

avg_bw_mem = Bps_to_icc(speed_hz);
ret = icc_set_bw(ctrl->icc_path_mem, avg_bw_mem, avg_bw_mem);
if (ret) {
dev_err(ctrl->dev, "ICC BW voting failed for memory: %d\n", ret);
return ret;
}

ctrl->last_speed = speed_hz;

return 0;
Expand Down Expand Up @@ -729,6 +737,11 @@ static int qcom_qspi_probe(struct platform_device *pdev)
return dev_err_probe(dev, PTR_ERR(ctrl->icc_path_cpu_to_qspi),
"Failed to get cpu path\n");

ctrl->icc_path_mem = devm_of_icc_get(dev, "qspi-memory");
if (IS_ERR(ctrl->icc_path_mem))
return dev_err_probe(dev, PTR_ERR(ctrl->icc_path_mem),
"Failed to get memory path\n");

/* Set BW vote for register access */
ret = icc_set_bw(ctrl->icc_path_cpu_to_qspi, Bps_to_icc(1000),
Bps_to_icc(1000));
Expand Down Expand Up @@ -829,6 +842,13 @@ static int __maybe_unused qcom_qspi_runtime_suspend(struct device *dev)
return ret;
}

ret = icc_disable(ctrl->icc_path_mem);
if (ret) {
dev_err_ratelimited(ctrl->dev, "ICC disable failed for memory: %d\n", ret);
icc_enable(ctrl->icc_path_cpu_to_qspi);
return ret;
}

pinctrl_pm_select_sleep_state(dev);

return 0;
Expand All @@ -849,9 +869,19 @@ static int __maybe_unused qcom_qspi_runtime_resume(struct device *dev)
return ret;
}

ret = icc_enable(ctrl->icc_path_mem);
if (ret) {
dev_err_ratelimited(ctrl->dev, "ICC enable failed for memory: %d\n", ret);
icc_disable(ctrl->icc_path_cpu_to_qspi);
return ret;
}

ret = clk_bulk_prepare_enable(QSPI_NUM_CLKS, ctrl->clks);
if (ret)
if (ret) {
icc_disable(ctrl->icc_path_cpu_to_qspi);
icc_disable(ctrl->icc_path_mem);
return ret;
}

return dev_pm_opp_set_rate(dev, ctrl->last_speed * 4);
}
Expand Down
14 changes: 13 additions & 1 deletion drivers/tty/serial/qcom_geni_serial.c
Original file line number Diff line number Diff line change
Expand Up @@ -1031,8 +1031,20 @@ static void qcom_geni_serial_handle_tx_dma(struct uart_port *uport)
{
struct qcom_geni_serial_port *port = to_dev_port(uport);
struct tty_port *tport = &uport->state->port;
unsigned int fifo_len = kfifo_len(&tport->xmit_fifo);

/*
* Only advance the kfifo if it still contains the bytes that were
* transferred. uart_flush_buffer() may have run before this IRQ
* fired: it calls kfifo_reset() under the port lock, making
* fifo_len = 0 while tx_remaining remains non-zero. Calling
* uart_xmit_advance() in that case would underflow kfifo->out past
* kfifo->in, making kfifo_len() wrap to UART_XMIT_SIZE - tx_remaining
* and triggering a spurious large DMA transfer of stale data.
*/
if (fifo_len >= port->tx_remaining)
uart_xmit_advance(uport, port->tx_remaining);

uart_xmit_advance(uport, port->tx_remaining);
geni_se_tx_dma_unprep(&port->se, port->tx_dma_addr, port->tx_remaining);
port->tx_dma_addr = 0;
port->tx_remaining = 0;
Expand Down
20 changes: 19 additions & 1 deletion include/linux/dma/qcom-gpi-dma.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2020, Linaro Limited
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/

#ifndef QCOM_GPI_DMA_H
Expand Down Expand Up @@ -54,6 +55,21 @@ enum i2c_op {
I2C_READ,
};

/**
* enum gpi_lock_action - request lock/unlock TRE sequencing
* @GPI_LOCK_NONE: No lock/unlock TRE requested for this transfer
* @GPI_LOCK_ACQUIRE: Emit a lock TRE before the transfer
* @GPI_LOCK_RELEASE: Emit an unlock TRE after the transfer
*
* Used by protocol drivers for multi-owner controller setups (e.g. when
* DeviceTree indicates the controller is shared via qcom,qup-multi-owner).
*/
enum gpi_lock_action {
GPI_LOCK_NONE = 0,
GPI_LOCK_ACQUIRE,
GPI_LOCK_RELEASE,
};

/**
* struct gpi_i2c_config - i2c config for peripheral
*
Expand All @@ -67,7 +83,8 @@ enum i2c_op {
* @set_config: set peripheral config
* @rx_len: receive length for buffer
* @op: i2c cmd
* @multi_msg: is part of multi i2c r-w msgs
* @muli-msg: is part of multi i2c r-w msgs
* @lock_action: request lock/unlock TRE sequencing for this transfer
*/
struct gpi_i2c_config {
u8 set_config;
Expand All @@ -81,6 +98,7 @@ struct gpi_i2c_config {
u32 rx_len;
enum i2c_op op;
bool multi_msg;
enum gpi_lock_action lock_action;
};

#endif /* QCOM_GPI_DMA_H */
Loading