[PATCH v1 9/9] serial: qcom-geni: Enable Serial on SA8255p Qualcomm platforms

Praveen Talari
2025-04-10T17:41:50Z

The Qualcomm automotive SA8255p SoC relies on firmware to configure
platform resources, including clocks, interconnects and TLMM.
The driver requests resources operations over SCMI using power
and performance protocols.

The SCMI power protocol enables or disables resources like clocks,
interconnect paths, and TLMM (GPIOs) using runtime PM framework APIs,
such as resume/suspend, to control power states(on/off).

The SCMI performance protocol manages UART baud rates, with each baud
rate represented by a performance level. The driver uses the
dev_pm_opp_set_level() API to request the desired baud rate by
specifying the performance level.

Signed-off-by: Praveen Talari <quic_ptalari@quicinc.com>
---
drivers/tty/serial/qcom_geni_serial.c | 150 +++++++++++++++++++++++---
1 file changed, 136 insertions(+), 14 deletions(-)

diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c
index 9649297d4a9e..40b71d4b7590 100644
--- a/drivers/tty/serial/qcom_geni_serial.c
+++ b/drivers/tty/serial/qcom_geni_serial.c
@@ -11,6 +11,7 @@
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/pm_domain.h>
#include <linux/pm_opp.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
@@ -98,9 +99,16 @@

#define DMA_RX_BUF_SIZE 2048

+#define DOMAIN_IDX_POWER 0
+#define DOMAIN_IDX_PERF 1
+
struct qcom_geni_device_data {
bool console;
enum geni_se_xfer_mode mode;
+ struct dev_pm_domain_attach_data pd_data;
+ int (*geni_serial_pwr_rsc_init)(struct uart_port *uport);
+ int (*geni_serial_set_rate)(struct uart_port *uport, unsigned long clk_freq);
+ int (*geni_serial_switch_power_state)(struct uart_port *uport, bool state);
};

struct qcom_geni_private_data {
@@ -138,6 +146,7 @@ struct qcom_geni_serial_port {

struct qcom_geni_private_data private_data;
const struct qcom_geni_device_data *dev_data;
+ struct dev_pm_domain_list *pd_list;
};

static const struct uart_ops qcom_geni_console_pops;
@@ -1315,6 +1324,42 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned long baud)
return 0;
}

+static int geni_serial_set_level(struct uart_port *uport, unsigned long baud)
+{
+ struct qcom_geni_serial_port *port = to_dev_port(uport);
+ struct device *perf_dev = port->pd_list->pd_devs[DOMAIN_IDX_PERF];
+
+ /*
+ * The performance protocol sets UART communication
+ * speeds by selecting different performance levels
+ * through the OPP framework.
+ *
+ * Supported perf levels for baudrates in firmware are below
+ * +---------------------+--------------------+
+ * | Perf level value | Baudrate values |
+ * +---------------------+--------------------+
+ * | 300 | 300 |
+ * | 1200 | 1200 |
+ * | 2400 | 2400 |
+ * | 4800 | 4800 |
+ * | 9600 | 9600 |
+ * | 19200 | 19200 |
+ * | 38400 | 38400 |
+ * | 57600 | 57600 |
+ * | 115200 | 115200 |
+ * | 230400 | 230400 |
+ * | 460800 | 460800 |
+ * | 921600 | 921600 |
+ * | 2000000 | 2000000 |
+ * | 3000000 | 3000000 |
+ * | 3200000 | 3200000 |
+ * | 4000000 | 4000000 |
+ * +---------------------+--------------------+
+ */
+
+ return dev_pm_opp_set_level(perf_dev, baud);
+}
+
static void qcom_geni_serial_set_termios(struct uart_port *uport,
struct ktermios *termios,
const struct ktermios *old)
@@ -1333,7 +1378,7 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport,
/* baud rate */
baud = uart_get_baud_rate(uport, termios, old, 300, 4000000);

- ret = geni_serial_set_rate(uport, baud);
+ ret = port->dev_data->geni_serial_set_rate(uport, baud);
if (ret) {
dev_err(port->se.dev,
"%s: Failed to set baud: %u ret: %d\n",
@@ -1624,8 +1669,27 @@ static int geni_serial_resources_on(struct uart_port *uport)
return ret;
}

-static int geni_serial_resource_init(struct qcom_geni_serial_port *port)
+static int geni_serial_resource_state(struct uart_port *uport, bool power_on)
+{
+ return power_on ? geni_serial_resources_on(uport) : geni_serial_resources_off(uport);
+}
+
+static int geni_serial_pwr_init(struct uart_port *uport)
{
+ struct qcom_geni_serial_port *port = to_dev_port(uport);
+ int ret;
+
+ ret = dev_pm_domain_attach_list(port->se.dev,
+ &port->dev_data->pd_data, &port->pd_list);
+ if (ret <= 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int geni_serial_resource_init(struct uart_port *uport)
+{
+ struct qcom_geni_serial_port *port = to_dev_port(uport);
int ret;

port->se.clk = devm_clk_get(port->se.dev, "se");
@@ -1756,13 +1820,16 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
port->se.dev = &pdev->dev;
port->se.wrapper = dev_get_drvdata(pdev->dev.parent);

- ret = geni_serial_resource_init(port);
+ ret = port->dev_data->geni_serial_pwr_rsc_init(uport);
if (ret)
return ret;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res)
- return -EINVAL;
+ if (!res) {
+ ret = -EINVAL;
+ goto error;
+ }
+
uport->mapbase = res->start;

port->tx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
@@ -1772,19 +1839,26 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
if (!data->console) {
port->rx_buf = devm_kzalloc(uport->dev,
DMA_RX_BUF_SIZE, GFP_KERNEL);
- if (!port->rx_buf)
- return -ENOMEM;
+ if (!port->rx_buf) {
+ ret = -ENOMEM;
+ goto error;
+ }
}

port->name = devm_kasprintf(uport->dev, GFP_KERNEL,
"qcom_geni_serial_%s%d",
uart_console(uport) ? "console" : "uart", uport->line);
- if (!port->name)
- return -ENOMEM;
+ if (!port->name) {
+ ret = -ENOMEM;
+ goto error;
+ }

irq = platform_get_irq(pdev, 0);
- if (irq < 0)
- return irq;
+ if (irq < 0) {
+ ret = irq;
+ goto error;
+ }
+
uport->irq = irq;
uport->has_sysrq = IS_ENABLED(CONFIG_SERIAL_QCOM_GENI_CONSOLE);

@@ -1806,7 +1880,7 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
IRQF_TRIGGER_HIGH, port->name, uport);
if (ret) {
dev_err(uport->dev, "Failed to get IRQ ret %d\n", ret);
- return ret;
+ goto error;
}

pm_runtime_enable(port->se.dev);
@@ -1830,6 +1904,7 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)

error:
pm_runtime_disable(port->se.dev);
+ dev_pm_domain_detach_list(port->pd_list);
return ret;
}

@@ -1842,22 +1917,31 @@ static void qcom_geni_serial_remove(struct platform_device *pdev)
device_init_wakeup(&pdev->dev, false);
pm_runtime_disable(port->se.dev);
uart_remove_one_port(drv, &port->uport);
+ dev_pm_domain_detach_list(port->pd_list);
}

static int qcom_geni_serial_runtime_suspend(struct device *dev)
{
struct qcom_geni_serial_port *port = dev_get_drvdata(dev);
struct uart_port *uport = &port->uport;
+ int ret = 0;

- return geni_serial_resources_off(uport);
+ if (port->dev_data->geni_serial_switch_power_state)
+ ret = port->dev_data->geni_serial_switch_power_state(uport, false);
+
+ return ret;
};

static int qcom_geni_serial_runtime_resume(struct device *dev)
{
struct qcom_geni_serial_port *port = dev_get_drvdata(dev);
struct uart_port *uport = &port->uport;
+ int ret = 0;
+
+ if (port->dev_data->geni_serial_switch_power_state)
+ ret = port->dev_data->geni_serial_switch_power_state(uport, true);

- return geni_serial_resources_on(uport);
+ return ret;
};

static int qcom_geni_serial_suspend(struct device *dev)
@@ -1895,11 +1979,41 @@ static int qcom_geni_serial_resume(struct device *dev)
static const struct qcom_geni_device_data qcom_geni_console_data = {
.console = true,
.mode = GENI_SE_FIFO,
+ .geni_serial_pwr_rsc_init = geni_serial_resource_init,
+ .geni_serial_set_rate = geni_serial_set_rate,
+ .geni_serial_switch_power_state = geni_serial_resource_state,
};

static const struct qcom_geni_device_data qcom_geni_uart_data = {
.console = false,
.mode = GENI_SE_DMA,
+ .geni_serial_pwr_rsc_init = geni_serial_resource_init,
+ .geni_serial_set_rate = geni_serial_set_rate,
+ .geni_serial_switch_power_state = geni_serial_resource_state,
+};
+
+static const struct qcom_geni_device_data sa8255p_qcom_geni_console_data = {
+ .console = true,
+ .mode = GENI_SE_FIFO,
+ .pd_data = {
+ .pd_flags = PD_FLAG_DEV_LINK_ON,
+ .pd_names = (const char*[]) { "power", "perf" },
+ .num_pd_names = 2,
+ },
+ .geni_serial_pwr_rsc_init = geni_serial_pwr_init,
+ .geni_serial_set_rate = geni_serial_set_level,
+};
+
+static const struct qcom_geni_device_data sa8255p_qcom_geni_uart_data = {
+ .console = false,
+ .mode = GENI_SE_DMA,
+ .pd_data = {
+ .pd_flags = PD_FLAG_DEV_LINK_ON,
+ .pd_names = (const char*[]) { "power", "perf" },
+ .num_pd_names = 2,
+ },
+ .geni_serial_pwr_rsc_init = geni_serial_pwr_init,
+ .geni_serial_set_rate = geni_serial_set_level,
};

static const struct dev_pm_ops qcom_geni_serial_pm_ops = {
@@ -1913,10 +2027,18 @@ static const struct of_device_id qcom_geni_serial_match_table[] = {
.compatible = "qcom,geni-debug-uart",
.data = &qcom_geni_console_data,
},
+ {
+ .compatible = "qcom,sa8255p-geni-debug-uart",
+ .data = &sa8255p_qcom_geni_console_data,
+ },
{
.compatible = "qcom,geni-uart",
.data = &qcom_geni_uart_data,
},
+ {
+ .compatible = "qcom,sa8255p-geni-uart",
+ .data = &sa8255p_qcom_geni_uart_data,
+ },
{}
};
MODULE_DEVICE_TABLE(of, qcom_geni_serial_match_table);
--
2.17.1