Commit 4595e947 authored by Ben Dooks's avatar Ben Dooks Committed by Russell King

[ARM PATCH] 2515/1: S3C2410 - allow serial to use fclk on s3c2440

Patch from Ben Dooks

This patch extends the s3c2410 serial driver to allow it
to use fclk and correctly determine the uart dividers. The
necessary defines are added to the serial headers as well.
Also modified is the header to point at the current
Simtec linux pages, as well as extend the copyright.

Signed-off-by: Ben Dooks
Signed-off-by: Russell King
parent a5dd2d6b
......@@ -5,7 +5,8 @@
*
* Based on drivers/char/serial.c and drivers/char/21285.c
*
* Ben Dooks, (c) 2003 Simtec Electronics
* Ben Dooks, (c) 2003-2005 Simtec Electronics
* http://www.simtec.co.uk/products/SWLINUX/
*
* Changelog:
*
......@@ -24,6 +25,15 @@
* - spin-lock initialisation (Dimitry Andric)
* - added clock control
* - updated init code to use platform_device info
*
* 06-Mar-2005 BJD Add s3c2440 fclk clock source
*/
/* Note on 2440 fclk clock source handling
*
* Whilst it is possible to use the fclk as clock source, the method
* of properly switching too/from this is currently un-implemented, so
* whichever way is configured at startup is the one that will be used.
*/
/* Hote on 2410 error handling
......@@ -87,6 +97,9 @@ struct s3c24xx_uart_info {
int (*get_clksrc)(struct uart_port *, struct s3c24xx_uart_clksrc *clk);
int (*set_clksrc)(struct uart_port *, struct s3c24xx_uart_clksrc *clk);
/* uart controls */
int (*reset_port)(struct uart_port *, struct s3c2410_uartcfg *);
};
struct s3c24xx_uart_port {
......@@ -606,11 +619,6 @@ static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level,
* baud clocks (and the resultant actual baud rates) and then tries to
* pick the closest one and select that.
*
* NOTES:
* 1) there is no current code to properly select/deselect FCLK on
* the s3c2440, so only specify FCLK or non-FCLK in the clock
* sources for the UART
*
*/
......@@ -658,6 +666,7 @@ static int s3c24xx_serial_calcbaud(struct baud_calc *calc,
return 0;
rate = clk_get_rate(calc->src);
rate /= clksrc->divisor;
calc->clksrc = clksrc;
calc->quot = (rate + (8 * baud)) / (16 * baud);
......@@ -685,6 +694,27 @@ static unsigned int s3c24xx_serial_getclk(struct uart_port *port,
if (cfg->clocks_size == 0)
clkp = &tmp_clksrc;
/* check to see if we're sourcing fclk, and if so we're
* going to have to update the clock source
*/
if (strcmp(clkp->name, "fclk") == 0) {
struct s3c24xx_uart_clksrc src;
s3c24xx_serial_getsource(port, &src);
/* check that the port already using fclk, and if
* not, then re-select fclk
*/
if (strcmp(src.name, clkp->name) == 0) {
s3c24xx_serial_setsource(port, clkp);
s3c24xx_serial_getsource(port, &src);
}
clkp->divisor = src.divisor;
}
s3c24xx_serial_calcbaud(res, port, clkp, baud);
best = res;
resptr = best + 1;
......@@ -993,24 +1023,18 @@ static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] = {
#endif
};
/* s3c24xx_serial_resetport
*
* wrapper to call the specific reset for this port (reset the fifos
* and the settings)
*/
static int s3c24xx_serial_resetport(struct uart_port *port,
static inline int s3c24xx_serial_resetport(struct uart_port * port,
struct s3c2410_uartcfg *cfg)
{
/* ensure registers are setup */
dbg("s3c24xx_serial_resetport: port=%p (%08lx), cfg=%p\n",
port, port->mapbase, cfg);
wr_regl(port, S3C2410_UCON, cfg->ucon);
wr_regl(port, S3C2410_ULCON, cfg->ulcon);
/* reset both fifos */
wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
wr_regl(port, S3C2410_UFCON, cfg->ufcon);
struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
return 0;
return (info->reset_port)(port, cfg);
}
/* s3c24xx_serial_init_port
......@@ -1201,6 +1225,23 @@ static int s3c2410_serial_getsource(struct uart_port *port,
return 0;
}
static int s3c2410_serial_resetport(struct uart_port *port,
struct s3c2410_uartcfg *cfg)
{
dbg("s3c2410_serial_resetport: port=%p (%08lx), cfg=%p\n",
port, port->mapbase, cfg);
wr_regl(port, S3C2410_UCON, cfg->ucon);
wr_regl(port, S3C2410_ULCON, cfg->ulcon);
/* reset both fifos */
wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
wr_regl(port, S3C2410_UFCON, cfg->ufcon);
return 0;
}
static struct s3c24xx_uart_info s3c2410_uart_inf = {
.name = "Samsung S3C2410 UART",
.type = PORT_S3C2410,
......@@ -1213,6 +1254,7 @@ static struct s3c24xx_uart_info s3c2410_uart_inf = {
.tx_fifoshift = S3C2410_UFSTAT_TXSHIFT,
.get_clksrc = s3c2410_serial_getsource,
.set_clksrc = s3c2410_serial_setsource,
.reset_port = s3c2410_serial_resetport,
};
/* device management */
......@@ -1288,6 +1330,7 @@ static int s3c2440_serial_getsource(struct uart_port *port,
struct s3c24xx_uart_clksrc *clk)
{
unsigned long ucon = rd_regl(port, S3C2410_UCON);
unsigned long ucon0, ucon1, ucon2;
switch (ucon & S3C2440_UCON_CLKMASK) {
case S3C2440_UCON_UCLK:
......@@ -1302,7 +1345,33 @@ static int s3c2440_serial_getsource(struct uart_port *port,
break;
case S3C2440_UCON_FCLK:
clk->divisor = 7; /* todo - work out divisor */
/* the fun of calculating the uart divisors on
* the s3c2440 */
ucon0 = __raw_readl(S3C2410_VA_UART0 + S3C2410_UCON);
ucon1 = __raw_readl(S3C2410_VA_UART1 + S3C2410_UCON);
ucon2 = __raw_readl(S3C2410_VA_UART2 + S3C2410_UCON);
printk("ucons: %08lx, %08lx, %08lx\n", ucon0, ucon1, ucon2);
ucon0 &= S3C2440_UCON0_DIVMASK;
ucon1 &= S3C2440_UCON1_DIVMASK;
ucon2 &= S3C2440_UCON2_DIVMASK;
if (ucon0 != 0) {
clk->divisor = ucon0 >> S3C2440_UCON_DIVSHIFT;
clk->divisor += 6;
} else if (ucon1 != 0) {
clk->divisor = ucon1 >> S3C2440_UCON_DIVSHIFT;
clk->divisor += 21;
} else if (ucon2 != 0) {
clk->divisor = ucon2 >> S3C2440_UCON_DIVSHIFT;
clk->divisor += 36;
} else {
/* manual calims 44, seems to be 9 */
clk->divisor = 9;
}
clk->name = "fclk";
break;
}
......@@ -1310,6 +1379,28 @@ static int s3c2440_serial_getsource(struct uart_port *port,
return 0;
}
static int s3c2440_serial_resetport(struct uart_port *port,
struct s3c2410_uartcfg *cfg)
{
unsigned long ucon = rd_regl(port, S3C2410_UCON);
dbg("s3c2440_serial_resetport: port=%p (%08lx), cfg=%p\n",
port, port->mapbase, cfg);
/* ensure we don't change the clock settings... */
ucon &= (S3C2440_UCON0_DIVMASK | (3<<10));
wr_regl(port, S3C2410_UCON, ucon | cfg->ucon);
wr_regl(port, S3C2410_ULCON, cfg->ulcon);
/* reset both fifos */
wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
wr_regl(port, S3C2410_UFCON, cfg->ufcon);
return 0;
}
static struct s3c24xx_uart_info s3c2440_uart_inf = {
.name = "Samsung S3C2440 UART",
......@@ -1322,7 +1413,8 @@ static struct s3c24xx_uart_info s3c2440_uart_inf = {
.tx_fifomask = S3C2440_UFSTAT_TXMASK,
.tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
.get_clksrc = s3c2440_serial_getsource,
.set_clksrc = s3c2440_serial_setsource
.set_clksrc = s3c2440_serial_setsource,
.reset_port = s3c2440_serial_resetport,
};
/* device management */
......@@ -1503,7 +1595,7 @@ s3c24xx_serial_get_options(struct uart_port *port, int *baud,
clk = clk_get(port->dev, clksrc.name);
if (!IS_ERR(clk) && clk != NULL)
rate = clk_get_rate(clk);
rate = clk_get_rate(clk) / clksrc.divisor;
else
rate = 1;
......@@ -1597,7 +1689,6 @@ static struct console s3c24xx_serial_console =
.setup = s3c24xx_serial_console_setup
};
static int s3c24xx_serial_initconsole(void)
{
struct s3c24xx_uart_info *info;
......
......@@ -73,6 +73,11 @@
#define S3C2440_UCON_UCLK (1<<10)
#define S3C2440_UCON_PCLK2 (2<<10)
#define S3C2440_UCON_FCLK (3<<10)
#define S3C2440_UCON2_FCLK_EN (1<<15)
#define S3C2440_UCON0_DIVMASK (15 << 12)
#define S3C2440_UCON1_DIVMASK (15 << 12)
#define S3C2440_UCON2_DIVMASK (7 << 12)
#define S3C2440_UCON_DIVSHIFT (12)
#define S3C2410_UCON_UCLK (1<<10)
#define S3C2410_UCON_SBREAK (1<<4)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment