serial: 8250_bcm7271: use default_mux_rate if possible

commit 614a19b89ca43449196a8af1afac7d55c6781687 upstream.

There is a scenario when resuming from some power saving states
with no_console_suspend where console output can be generated
before the 8250_bcm7271 driver gets the opportunity to restore
the baud_mux_clk frequency. Since the baud_mux_clk is at its
default frequency at this time the output can be garbled until
the driver gets the opportunity to resume.

Since this is only an issue with console use of the serial port
during that window and the console isn't likely to use baud
rates that require alternate baud_mux_clk frequencies, allow the
driver to select the default_mux_rate if it is accurate enough.

Fixes: 41a469482d ("serial: 8250: Add new 8250-core based Broadcom STB driver")
Cc: stable@vger.kernel.org
Signed-off-by: Doug Berger <opendmb@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
Tested-by: Florian Fainelli <florian.fainelli@broadcom.com>
Link: https://lore.kernel.org/r/20240424222559.1844045-1-opendmb@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Doug Berger 2024-04-24 15:25:59 -07:00 коммит произвёл Greg Kroah-Hartman
Родитель 504178fb7d
Коммит 3adc11febf
1 изменённых файлов: 59 добавлений и 40 удалений

Просмотреть файл

@ -674,18 +674,46 @@ static void init_real_clk_rates(struct device *dev, struct brcmuart_priv *priv)
clk_set_rate(priv->baud_mux_clk, priv->default_mux_rate);
}
static u32 find_quot(struct device *dev, u32 freq, u32 baud, u32 *percent)
{
u32 quot;
u32 rate;
u64 hires_rate;
u64 hires_baud;
u64 hires_err;
rate = freq / 16;
quot = DIV_ROUND_CLOSEST(rate, baud);
if (!quot)
return 0;
/* increase resolution to get xx.xx percent */
hires_rate = div_u64((u64)rate * 10000, (u64)quot);
hires_baud = (u64)baud * 10000;
/* get the delta */
if (hires_rate > hires_baud)
hires_err = (hires_rate - hires_baud);
else
hires_err = (hires_baud - hires_rate);
*percent = (unsigned long)DIV_ROUND_CLOSEST_ULL(hires_err, baud);
dev_dbg(dev, "Baud rate: %u, MUX Clk: %u, Error: %u.%u%%\n",
baud, freq, *percent / 100, *percent % 100);
return quot;
}
static void set_clock_mux(struct uart_port *up, struct brcmuart_priv *priv,
u32 baud)
{
u32 percent;
u32 best_percent = UINT_MAX;
u32 quot;
u32 freq;
u32 best_quot = 1;
u32 rate;
int best_index = -1;
u64 hires_rate;
u64 hires_baud;
u64 hires_err;
u32 best_freq = 0;
int rc;
int i;
int real_baud;
@ -694,44 +722,35 @@ static void set_clock_mux(struct uart_port *up, struct brcmuart_priv *priv,
if (priv->baud_mux_clk == NULL)
return;
/* Find the closest match for specified baud */
for (i = 0; i < ARRAY_SIZE(priv->real_rates); i++) {
if (priv->real_rates[i] == 0)
continue;
rate = priv->real_rates[i] / 16;
quot = DIV_ROUND_CLOSEST(rate, baud);
if (!quot)
continue;
/* Try default_mux_rate first */
quot = find_quot(up->dev, priv->default_mux_rate, baud, &percent);
if (quot) {
best_percent = percent;
best_freq = priv->default_mux_rate;
best_quot = quot;
}
/* If more than 1% error, find the closest match for specified baud */
if (best_percent > 100) {
for (i = 0; i < ARRAY_SIZE(priv->real_rates); i++) {
freq = priv->real_rates[i];
if (freq == 0 || freq == priv->default_mux_rate)
continue;
quot = find_quot(up->dev, freq, baud, &percent);
if (!quot)
continue;
/* increase resolution to get xx.xx percent */
hires_rate = (u64)rate * 10000;
hires_baud = (u64)baud * 10000;
hires_err = div_u64(hires_rate, (u64)quot);
/* get the delta */
if (hires_err > hires_baud)
hires_err = (hires_err - hires_baud);
else
hires_err = (hires_baud - hires_err);
percent = (unsigned long)DIV_ROUND_CLOSEST_ULL(hires_err, baud);
dev_dbg(up->dev,
"Baud rate: %u, MUX Clk: %u, Error: %u.%u%%\n",
baud, priv->real_rates[i], percent / 100,
percent % 100);
if (percent < best_percent) {
best_percent = percent;
best_index = i;
best_quot = quot;
if (percent < best_percent) {
best_percent = percent;
best_freq = freq;
best_quot = quot;
}
}
}
if (best_index == -1) {
if (!best_freq) {
dev_err(up->dev, "Error, %d BAUD rate is too fast.\n", baud);
return;
}
rate = priv->real_rates[best_index];
rc = clk_set_rate(priv->baud_mux_clk, rate);
rc = clk_set_rate(priv->baud_mux_clk, best_freq);
if (rc)
dev_err(up->dev, "Error selecting BAUD MUX clock\n");
@ -740,8 +759,8 @@ static void set_clock_mux(struct uart_port *up, struct brcmuart_priv *priv,
dev_err(up->dev, "Error, baud: %d has %u.%u%% error\n",
baud, percent / 100, percent % 100);
real_baud = rate / 16 / best_quot;
dev_dbg(up->dev, "Selecting BAUD MUX rate: %u\n", rate);
real_baud = best_freq / 16 / best_quot;
dev_dbg(up->dev, "Selecting BAUD MUX rate: %u\n", best_freq);
dev_dbg(up->dev, "Requested baud: %u, Actual baud: %u\n",
baud, real_baud);
@ -750,7 +769,7 @@ static void set_clock_mux(struct uart_port *up, struct brcmuart_priv *priv,
i += (i / 2);
priv->char_wait = ns_to_ktime(i);
up->uartclk = rate;
up->uartclk = best_freq;
}
static void brcmstb_set_termios(struct uart_port *up,