summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaxman Dewangan <ldewangan@nvidia.com>2011-06-26 22:23:56 +0530
committerRohan Somvanshi <rsomvanshi@nvidia.com>2011-06-29 06:49:46 -0700
commitfdcb66098abe58612fc1b3fbee3a1e6cb2cfbd0d (patch)
tree67d25f13311073c0cf47dc7f74b65c53ccffda1e
parent8a787ebc0e0688a9701233ea8fa49cc857c7b91d (diff)
serial: tegra: Support for best clock source
Finding the best clock source for uart controller which can generate the clock rate having minimum error between requested baudrate and configured baudrate. bug 842665 Change-Id: I9a750f578f7dfd7ea2138fdf1bcec30b0f3392d5 Reviewed-on: http://git-master/r/38426 Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com> Tested-by: Laxman Dewangan <ldewangan@nvidia.com> Tested-by: Pradeep Goudagunta <pgoudagunta@nvidia.com> Reviewed-by: Bitan Biswas <bbiswas@nvidia.com>
-rw-r--r--drivers/serial/tegra_hsuart.c81
-rw-r--r--include/linux/tegra_uart.h10
2 files changed, 90 insertions, 1 deletions
diff --git a/drivers/serial/tegra_hsuart.c b/drivers/serial/tegra_hsuart.c
index 4890def5e5c8..268ffdf7bbc8 100644
--- a/drivers/serial/tegra_hsuart.c
+++ b/drivers/serial/tegra_hsuart.c
@@ -3,7 +3,7 @@
*
* High-speed serial driver for NVIDIA Tegra SoCs
*
- * Copyright (C) 2009 NVIDIA Corporation
+ * Copyright (C) 2009-2011 NVIDIA Corporation
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -987,6 +987,70 @@ static void tegra_enable_ms(struct uart_port *u)
{
}
+static int clk_div16_get_divider(unsigned long parent_rate, unsigned long rate)
+{
+ s64 divider_u16;
+
+ divider_u16 = parent_rate;
+ if (!rate)
+ return -EINVAL;
+ divider_u16 += rate - 1;
+ do_div(divider_u16, rate);
+
+ if (divider_u16 > 0xFFFF)
+ return -EINVAL;
+
+ return divider_u16;
+}
+
+static unsigned long find_best_clock_source(struct tegra_uart_port *t,
+ unsigned long rate)
+{
+ struct uart_port *u = &t->uport;
+ struct tegra_uart_platform_data *pdata;
+ int i;
+ int divider;
+ unsigned long parent_rate;
+ unsigned long new_rate;
+ unsigned long err_rate;
+ unsigned int fin_err = rate;
+ unsigned long fin_rate = rate;
+ int final_index = -1;
+ int count;
+
+ pdata = u->dev->platform_data;
+ if (!pdata || !pdata->parent_clk_count)
+ return fin_rate;
+
+ for (count = 0; count < pdata->parent_clk_count; ++count) {
+ parent_rate = pdata->parent_clk_list[count].fixed_clk_rate;
+
+ /* Get the divisor by uart controller dll/dlm */
+ divider = clk_div16_get_divider(parent_rate, rate);
+
+ /* Get the best divider around calculated value */
+ if (divider > 2) {
+ for (i = divider - 2; i < (divider + 2); ++i) {
+ new_rate = parent_rate/i;
+ err_rate = abs(new_rate - rate);
+ if (err_rate < fin_err) {
+ final_index = count;
+ fin_err = err_rate;
+ fin_rate = parent_rate;
+ }
+ }
+ }
+ }
+
+ if (final_index >= 0) {
+ dev_info(t->uport.dev, "Setting clk_src %s\n",
+ pdata->parent_clk_list[final_index].name);
+ clk_set_parent(t->clk,
+ pdata->parent_clk_list[final_index].parent_clk);
+ }
+ return fin_rate;
+}
+
#define UART_CLOCK_ACCURACY 5
static void tegra_set_baudrate(struct tegra_uart_port *t, unsigned int baud)
@@ -994,10 +1058,17 @@ static void tegra_set_baudrate(struct tegra_uart_port *t, unsigned int baud)
unsigned long rate;
unsigned int divisor;
unsigned char lcr;
+ unsigned int baud_actual;
+ unsigned int baud_delta;
+ unsigned long best_rate;
if (t->baud == baud)
return;
+ rate = baud * 16;
+ best_rate = find_best_clock_source(t, rate);
+ clk_set_rate(t->clk, best_rate);
+
rate = clk_get_rate(t->clk);
divisor = rate;
@@ -1005,6 +1076,14 @@ static void tegra_set_baudrate(struct tegra_uart_port *t, unsigned int baud)
divisor += baud/2;
do_div(divisor, baud);
+ /* The allowable baudrate error from desired baudrate is 5% */
+ baud_actual = divisor ? rate / (16 * divisor) : 0;
+ baud_delta = abs(baud_actual - baud);
+ if (WARN_ON(baud_delta * 20 > baud)) {
+ dev_err(t->uport.dev, "requested baud %u, actual %u\n",
+ baud, baud_actual);
+ }
+
lcr = t->lcr_shadow;
lcr |= UART_LCR_DLAB;
uart_writeb(t, lcr, UART_LCR);
diff --git a/include/linux/tegra_uart.h b/include/linux/tegra_uart.h
index 435b4198e4be..3d35e217cbca 100644
--- a/include/linux/tegra_uart.h
+++ b/include/linux/tegra_uart.h
@@ -20,8 +20,18 @@
#ifndef _TEGRA_UART_H_
#define _TEGRA_UART_H_
+#include <linux/clk.h>
+
+struct uart_clk_parent {
+ const char *name;
+ struct clk *parent_clk;
+ unsigned long fixed_clk_rate;
+};
+
struct tegra_uart_platform_data {
void (*wake_peer)(struct uart_port *);
+ struct uart_clk_parent *parent_clk_list;
+ int parent_clk_count;
};
int tegra_uart_is_tx_empty(struct uart_port *);