]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
serial: qcom-geni: Add DFS clock mode support to GENI UART driver
authorViken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
Wed, 3 Sep 2025 06:31:36 +0000 (12:01 +0530)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 6 Sep 2025 13:49:10 +0000 (15:49 +0200)
GENI UART driver currently supports only non-DFS (Dynamic Frequency
Scaling) mode for source frequency selection. However, to operate correctly
in DFS mode, the GENI SCLK register must be programmed with the appropriate
DFS index. Failing to do so can result in incorrect frequency selection

Add support for Dynamic Frequency Scaling (DFS) mode in the GENI UART
driver by configuring the GENI_CLK_SEL register with the appropriate DFS
index. This ensures correct frequency selection when operating in DFS mode.

Replace the UART driver-specific logic for clock selection with the GENI
common driver function to obtain the desired frequency and corresponding
clock index. This improves maintainability and consistency across
GENI-based drivers.

Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
Link: https://lore.kernel.org/r/20250903063136.3015237-1-viken.dadhaniya@oss.qualcomm.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/qcom_geni_serial.c

index 0b474d349531e4e52bb19e0f7b685fd269c0fcc6..0fdda3a1e70bf6e493f0152dd439b5db2b59ec4e 100644 (file)
@@ -1,5 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0
-// Copyright (c) 2017-2018, The Linux foundation. All rights reserved.
+/*
+ * Copyright (c) 2017-2018, The Linux foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
 
 /* Disable MMIO tracing to prevent excessive logging of unwanted MMIO traces */
 #define __DISABLE_TRACE_MMIO__
@@ -1242,75 +1245,15 @@ static int qcom_geni_serial_startup(struct uart_port *uport)
        return 0;
 }
 
-static unsigned long find_clk_rate_in_tol(struct clk *clk, unsigned int desired_clk,
-                       unsigned int *clk_div, unsigned int percent_tol)
-{
-       unsigned long freq;
-       unsigned long div, maxdiv;
-       u64 mult;
-       unsigned long offset, abs_tol, achieved;
-
-       abs_tol = div_u64((u64)desired_clk * percent_tol, 100);
-       maxdiv = CLK_DIV_MSK >> CLK_DIV_SHFT;
-       div = 1;
-       while (div <= maxdiv) {
-               mult = (u64)div * desired_clk;
-               if (mult != (unsigned long)mult)
-                       break;
-
-               offset = div * abs_tol;
-               freq = clk_round_rate(clk, mult - offset);
-
-               /* Can only get lower if we're done */
-               if (freq < mult - offset)
-                       break;
-
-               /*
-                * Re-calculate div in case rounding skipped rates but we
-                * ended up at a good one, then check for a match.
-                */
-               div = DIV_ROUND_CLOSEST(freq, desired_clk);
-               achieved = DIV_ROUND_CLOSEST(freq, div);
-               if (achieved <= desired_clk + abs_tol &&
-                   achieved >= desired_clk - abs_tol) {
-                       *clk_div = div;
-                       return freq;
-               }
-
-               div = DIV_ROUND_UP(freq, desired_clk);
-       }
-
-       return 0;
-}
-
-static unsigned long get_clk_div_rate(struct clk *clk, unsigned int baud,
-                       unsigned int sampling_rate, unsigned int *clk_div)
-{
-       unsigned long ser_clk;
-       unsigned long desired_clk;
-
-       desired_clk = baud * sampling_rate;
-       if (!desired_clk)
-               return 0;
-
-       /*
-        * try to find a clock rate within 2% tolerance, then within 5%
-        */
-       ser_clk = find_clk_rate_in_tol(clk, desired_clk, clk_div, 2);
-       if (!ser_clk)
-               ser_clk = find_clk_rate_in_tol(clk, desired_clk, clk_div, 5);
-
-       return ser_clk;
-}
-
 static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud)
 {
        struct qcom_geni_serial_port *port = to_dev_port(uport);
        unsigned long clk_rate;
-       unsigned int avg_bw_core;
+       unsigned int avg_bw_core, clk_idx;
        unsigned int clk_div;
        u32 ver, sampling_rate;
        u32 ser_clk_cfg;
+       int ret;
 
        sampling_rate = UART_OVERSAMPLING;
        /* Sampling rate is halved for IP versions >= 2.5 */
@@ -1318,17 +1261,22 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud)
        if (ver >= QUP_SE_VERSION_2_5)
                sampling_rate /= 2;
 
-       clk_rate = get_clk_div_rate(port->se.clk, baud,
-               sampling_rate, &clk_div);
-       if (!clk_rate) {
-               dev_err(port->se.dev,
-                       "Couldn't find suitable clock rate for %u\n",
-                       baud * sampling_rate);
+       ret = geni_se_clk_freq_match(&port->se, baud * sampling_rate, &clk_idx, &clk_rate, false);
+       if (ret) {
+               dev_err(port->se.dev, "Failed to find src clk for baud rate: %d ret: %d\n",
+                       baud, ret);
+               return ret;
+       }
+
+       clk_div = DIV_ROUND_UP(clk_rate, baud * sampling_rate);
+       /* Check if calculated divider exceeds maximum allowed value */
+       if (clk_div > (CLK_DIV_MSK >> CLK_DIV_SHFT)) {
+               dev_err(port->se.dev, "Calculated clock divider %u exceeds maximum\n", clk_div);
                return -EINVAL;
        }
 
-       dev_dbg(port->se.dev, "desired_rate = %u, clk_rate = %lu, clk_div = %u\n",
-                       baud * sampling_rate, clk_rate, clk_div);
+       dev_dbg(port->se.dev, "desired_rate = %u, clk_rate = %lu, clk_div = %u\n, clk_idx = %u\n",
+               baud * sampling_rate, clk_rate, clk_div, clk_idx);
 
        uport->uartclk = clk_rate;
        port->clk_rate = clk_rate;
@@ -1348,6 +1296,8 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud)
 
        writel(ser_clk_cfg, uport->membase + GENI_SER_M_CLK_CFG);
        writel(ser_clk_cfg, uport->membase + GENI_SER_S_CLK_CFG);
+       /* Configure clock selection register with the selected clock index */
+       writel(clk_idx & CLK_SEL_MSK, uport->membase + SE_GENI_CLK_SEL);
        return 0;
 }