*
* 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:
*
* - 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
+ *
+ * 09-Mar-2005 BJD Add s3c2400 support
+ *
+ * 10-Mar-2005 LCVR Changed S3C2410_VA_UART to S3C24XX_VA_UART
+*/
+
+/* 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
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 {
flag = TTY_NORMAL;
port->icount.rx++;
- if (uerstat & S3C2410_UERSTAT_ANY) {
+ if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) {
dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n",
ch, uerstat);
if (uart_handle_sysrq_char(port, ch, regs))
goto ignore_char;
- if ((uerstat & port->ignore_status_mask) == 0) {
- tty_insert_flip_char(tty, ch, flag);
- }
-
- if ((uerstat & S3C2410_UERSTAT_OVERRUN) &&
- tty->flip.count < TTY_FLIPBUF_SIZE) {
- /*
- * Overrun is special, since it's reported
- * immediately, and doesn't affect the current
- * character.
- */
-
- tty_insert_flip_char(tty, 0, TTY_OVERRUN);
- }
+ uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);
ignore_char:
continue;
* 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
- *
*/
return 0;
rate = clk_get_rate(calc->src);
+ rate /= clksrc->divisor;
calc->clksrc = clksrc;
calc->quot = (rate + (8 * baud)) / (16 * baud);
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;
[0] = {
.port = {
.lock = SPIN_LOCK_UNLOCKED,
- .membase = 0,
- .mapbase = 0,
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX0,
.uartclk = 0,
[1] = {
.port = {
.lock = SPIN_LOCK_UNLOCKED,
- .membase = 0,
- .mapbase = 0,
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX1,
.uartclk = 0,
[2] = {
.port = {
.lock = SPIN_LOCK_UNLOCKED,
- .membase = 0,
- .mapbase = 0,
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX2,
.uartclk = 0,
#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,
- struct s3c2410_uartcfg *cfg)
+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
dbg("resource %p (%lx..%lx)\n", res, res->start, res->end);
port->mapbase = res->start;
- port->membase = (void __iomem *)(res->start - S3C2410_PA_UART);
- port->membase += S3C2410_VA_UART;
+ port->membase = S3C24XX_VA_UART + (res->start - S3C2410_PA_UART);
port->irq = platform_get_irq(platdev, 0);
ourport->clk = clk_get(&platdev->dev, "uart");
#ifdef CONFIG_PM
-int s3c24xx_serial_suspend(struct device *dev, u32 state, u32 level)
+int s3c24xx_serial_suspend(struct device *dev, pm_message_t state, u32 level)
{
struct uart_port *port = s3c24xx_dev_to_port(dev);
/* cpu specific variations on the serial port support */
+#ifdef CONFIG_CPU_S3C2400
+
+static int s3c2400_serial_getsource(struct uart_port *port,
+ struct s3c24xx_uart_clksrc *clk)
+{
+ clk->divisor = 1;
+ clk->name = "pclk";
+
+ return 0;
+}
+
+static int s3c2400_serial_setsource(struct uart_port *port,
+ struct s3c24xx_uart_clksrc *clk)
+{
+ return 0;
+}
+
+static int s3c2400_serial_resetport(struct uart_port *port,
+ struct s3c2410_uartcfg *cfg)
+{
+ dbg("s3c2400_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 s3c2400_uart_inf = {
+ .name = "Samsung S3C2400 UART",
+ .type = PORT_S3C2400,
+ .fifosize = 16,
+ .rx_fifomask = S3C2410_UFSTAT_RXMASK,
+ .rx_fifoshift = S3C2410_UFSTAT_RXSHIFT,
+ .rx_fifofull = S3C2410_UFSTAT_RXFULL,
+ .tx_fifofull = S3C2410_UFSTAT_TXFULL,
+ .tx_fifomask = S3C2410_UFSTAT_TXMASK,
+ .tx_fifoshift = S3C2410_UFSTAT_TXSHIFT,
+ .get_clksrc = s3c2400_serial_getsource,
+ .set_clksrc = s3c2400_serial_setsource,
+ .reset_port = s3c2400_serial_resetport,
+};
+
+static int s3c2400_serial_probe(struct device *dev)
+{
+ return s3c24xx_serial_probe(dev, &s3c2400_uart_inf);
+}
+
+static struct device_driver s3c2400_serial_drv = {
+ .name = "s3c2400-uart",
+ .bus = &platform_bus_type,
+ .probe = s3c2400_serial_probe,
+ .remove = s3c24xx_serial_remove,
+ .suspend = s3c24xx_serial_suspend,
+ .resume = s3c24xx_serial_resume,
+};
+
+static inline int s3c2400_serial_init(void)
+{
+ return s3c24xx_serial_init(&s3c2400_serial_drv, &s3c2400_uart_inf);
+}
+
+static inline void s3c2400_serial_exit(void)
+{
+ driver_unregister(&s3c2400_serial_drv);
+}
+
+#define s3c2400_uart_inf_at &s3c2400_uart_inf
+#else
+
+static inline int s3c2400_serial_init(void)
+{
+ return 0;
+}
+
+static inline void s3c2400_serial_exit(void)
+{
+}
+
+#define s3c2400_uart_inf_at NULL
+
+#endif /* CONFIG_CPU_S3C2400 */
+
+/* S3C2410 support */
+
#ifdef CONFIG_CPU_S3C2410
static int s3c2410_serial_setsource(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,
.tx_fifoshift = S3C2410_UFSTAT_TXSHIFT,
.get_clksrc = s3c2410_serial_getsource,
.set_clksrc = s3c2410_serial_setsource,
+ .reset_port = s3c2410_serial_resetport,
};
/* device management */
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:
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(S3C24XX_VA_UART0 + S3C2410_UCON);
+ ucon1 = __raw_readl(S3C24XX_VA_UART1 + S3C2410_UCON);
+ ucon2 = __raw_readl(S3C24XX_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;
}
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",
.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 */
return -1;
}
+ s3c2400_serial_init();
s3c2410_serial_init();
s3c2440_serial_init();
static void __exit s3c24xx_serial_modexit(void)
{
+ s3c2400_serial_exit();
s3c2410_serial_exit();
s3c2440_serial_exit();
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;
.setup = s3c24xx_serial_console_setup
};
-
static int s3c24xx_serial_initconsole(void)
{
struct s3c24xx_uart_info *info;
return 0;
}
- if (strcmp(dev->name, "s3c2410-uart") == 0) {
+ if (strcmp(dev->name, "s3c2400-uart") == 0) {
+ info = s3c2400_uart_inf_at;
+ } else if (strcmp(dev->name, "s3c2410-uart") == 0) {
info = s3c2410_uart_inf_at;
} else if (strcmp(dev->name, "s3c2440-uart") == 0) {
info = s3c2440_uart_inf_at;