summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/tty/serial/sh-sci.c85
1 files changed, 80 insertions, 5 deletions
diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c
index 229162481fd6..4ff5d0cf8126 100644
--- a/drivers/tty/serial/sh-sci.c
+++ b/drivers/tty/serial/sh-sci.c
@@ -80,6 +80,8 @@ enum {
enum SCI_CLKS {
SCI_FCK, /* Functional Clock */
SCI_SCK, /* Optional External Clock */
+ SCI_BRG_INT, /* Optional BRG Internal Clock Source */
+ SCI_SCIF_CLK, /* Optional BRG External Clock Source */
SCI_NUM_CLKS
};
@@ -1958,6 +1960,43 @@ static int sci_sck_calc(struct sci_port *s, unsigned int bps,
return min_err;
}
+static int sci_brg_calc(struct sci_port *s, unsigned int bps,
+ unsigned long freq, unsigned int *dlr,
+ unsigned int *srr)
+{
+ unsigned int min_sr, max_sr, sr, dl;
+ int err, min_err = INT_MAX;
+
+ if (s->sampling_rate) {
+ /* SCIF has a fixed sampling rate */
+ min_sr = max_sr = s->sampling_rate / 2;
+ } else {
+ /* HSCIF has a variable 1/(8..32) sampling rate */
+ min_sr = 8;
+ max_sr = 32;
+ }
+
+ for (sr = max_sr; sr >= min_sr; sr--) {
+ dl = DIV_ROUND_CLOSEST(freq, sr * bps);
+ dl = clamp(dl, 1U, 65535U);
+
+ err = DIV_ROUND_CLOSEST(freq, sr * dl) - bps;
+ if (abs(err) >= abs(min_err))
+ continue;
+
+ min_err = err;
+ *dlr = dl;
+ *srr = sr - 1;
+
+ if (!err)
+ break;
+ }
+
+ dev_dbg(s->port.dev, "BRG: %u%+d bps using DL %u SR %u\n", bps,
+ min_err, *dlr, *srr + 1);
+ return min_err;
+}
+
/* calculate sample rate, BRR, and clock select */
static int sci_scbrr_calc(struct sci_port *s, unsigned int bps,
unsigned int *brr, unsigned int *srr,
@@ -2053,8 +2092,8 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios,
struct ktermios *old)
{
unsigned int baud, smr_val = 0, scr_val = 0, i;
- unsigned int brr = 255, cks = 0, srr = 15, sccks = 0;
- unsigned int brr1 = 255, cks1 = 0, srr1 = 15;
+ unsigned int brr = 255, cks = 0, srr = 15, dl = 0, sccks = 0;
+ unsigned int brr1 = 255, cks1 = 0, srr1 = 15, dl1 = 0;
struct sci_port *s = to_sci_port(port);
const struct plat_sci_reg *reg;
int min_err = INT_MAX, err;
@@ -2111,6 +2150,38 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios,
}
}
+ /* Optional BRG Frequency Divided External Clock */
+ if (s->clk_rates[SCI_SCIF_CLK] && sci_getreg(port, SCDL)->size) {
+ err = sci_brg_calc(s, baud, s->clk_rates[SCI_SCIF_CLK], &dl1,
+ &srr1);
+ if (abs(err) < abs(min_err)) {
+ best_clk = SCI_SCIF_CLK;
+ scr_val = SCSCR_CKE1;
+ sccks = 0;
+ min_err = err;
+ dl = dl1;
+ srr = srr1;
+ if (!err)
+ goto done;
+ }
+ }
+
+ /* Optional BRG Frequency Divided Internal Clock */
+ if (s->clk_rates[SCI_BRG_INT] && sci_getreg(port, SCDL)->size) {
+ err = sci_brg_calc(s, baud, s->clk_rates[SCI_BRG_INT], &dl1,
+ &srr1);
+ if (abs(err) < abs(min_err)) {
+ best_clk = SCI_BRG_INT;
+ scr_val = SCSCR_CKE1;
+ sccks = SCCKS_XIN;
+ min_err = err;
+ dl = dl1;
+ srr = srr1;
+ if (!min_err)
+ goto done;
+ }
+ }
+
/* Divided Functional Clock using standard Bit Rate Register */
err = sci_scbrr_calc(s, baud, &brr1, &srr1, &cks1);
if (abs(err) < abs(min_err)) {
@@ -2133,8 +2204,10 @@ done:
* Program the optional External Baud Rate Generator (BRG) first.
* It controls the mux to select (H)SCK or frequency divided clock.
*/
- if (best_clk >= 0 && sci_getreg(port, SCCKS)->size)
+ if (best_clk >= 0 && sci_getreg(port, SCCKS)->size) {
+ serial_port_out(port, SCDL, dl);
serial_port_out(port, SCCKS, sccks);
+ }
sci_reset(port);
@@ -2143,8 +2216,8 @@ done:
if (best_clk >= 0) {
smr_val |= cks;
dev_dbg(port->dev,
- "SCR 0x%x SMR 0x%x BRR %u CKS 0x%x SRR %u\n",
- scr_val, smr_val, brr, sccks, srr);
+ "SCR 0x%x SMR 0x%x BRR %u CKS 0x%x DL %u SRR %u\n",
+ scr_val, smr_val, brr, sccks, dl, srr);
serial_port_out(port, SCSCR, scr_val);
serial_port_out(port, SCSMR, smr_val);
serial_port_out(port, SCBRR, brr);
@@ -2382,6 +2455,8 @@ static int sci_init_clocks(struct sci_port *sci_port, struct device *dev)
const char *clk_names[] = {
[SCI_FCK] = "fck",
[SCI_SCK] = "sck",
+ [SCI_BRG_INT] = "brg_int",
+ [SCI_SCIF_CLK] = "scif_clk",
};
struct clk *clk;
unsigned int i;