summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorJuergen Beisert <j.beisert@pengutronix.de>2009-06-23 15:46:15 +0200
committerJuergen Beisert <j.beisert@pengutronix.de>2009-07-01 14:36:40 +0200
commit5139e618b8d732bfbea69d9a9bdf83832de30472 (patch)
tree530f01f7b25071d0d8ab1a300060e6f9e15aae1b /drivers
parent5d56a46c0aaaadb7eebc23ef7577a637610926f7 (diff)
downloadbarebox-5139e618b8d732bfbea69d9a9bdf83832de30472.tar.gz
barebox-5139e618b8d732bfbea69d9a9bdf83832de30472.tar.xz
Add basic architecture support for Samsung's S3C2410 and S3C2440 CPU.
It includes a - driver for the internal UART - driver for the internal NAND controller - support to boot from NAND - PLL handling - SDRAM initialisation Signed-off-by: Juergen Beisert <j.beisert@pengutronix.de>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/nand/Kconfig7
-rw-r--r--drivers/nand/Makefile1
-rw-r--r--drivers/nand/nand_s3c2410.c528
-rw-r--r--drivers/serial/Kconfig15
-rw-r--r--drivers/serial/Makefile1
-rw-r--r--drivers/serial/serial_s3c24x0.c165
6 files changed, 717 insertions, 0 deletions
diff --git a/drivers/nand/Kconfig b/drivers/nand/Kconfig
index 4f238dfa6b..2b4d62ab5d 100644
--- a/drivers/nand/Kconfig
+++ b/drivers/nand/Kconfig
@@ -38,6 +38,13 @@ config NAND_ATMEL
prompt "Atmel (AT91SAM9xxx) NAND driver"
depends on ARCH_AT91SAM9
+config NAND_S3C24X0
+ bool
+ prompt "Samsung S3C24X0 NAND driver"
+ depends on ARCH_S3C24xx
+ help
+ Add support for processor's NAND device controller.
+
config MTD_NAND_VERIFY_WRITE
bool "Verify NAND page writes"
help
diff --git a/drivers/nand/Makefile b/drivers/nand/Makefile
index af6ac578fe..73f734688e 100644
--- a/drivers/nand/Makefile
+++ b/drivers/nand/Makefile
@@ -8,4 +8,5 @@ obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o
obj-$(CONFIG_NAND_IMX) += nand_imx.o
obj-$(CONFIG_NAND_OMAP_GPMC) += nand_omap_gpmc.o
obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o
+obj-$(CONFIG_NAND_S3C24X0) += nand_s3c2410.o
#obj-$(CONFIG_NAND) += nand_util.o
diff --git a/drivers/nand/nand_s3c2410.c b/drivers/nand/nand_s3c2410.c
new file mode 100644
index 0000000000..699bb34b8c
--- /dev/null
+++ b/drivers/nand/nand_s3c2410.c
@@ -0,0 +1,528 @@
+/* linux/drivers/mtd/nand/s3c2410.c
+ *
+ * Copyright (C) 2009 Juergen Beisert, Pengutronix
+ *
+ * Copyright © 2004-2008 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * Samsung S3C2410 NAND driver
+ *
+ * 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 Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <config.h>
+#include <common.h>
+#include <driver.h>
+#include <malloc.h>
+#include <init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <asm/arch/s3c24xx-generic.h>
+#include <asm/arch/s3c24x0-iomap.h>
+#include <asm/arch/s3c24x0-nand.h>
+#include <asm/io.h>
+#include <asm-generic/errno.h>
+
+#ifdef CONFIG_S3C24XX_NAND_BOOT
+# define __nand_boot_init __bare_init
+# ifndef BOARD_DEFAULT_NAND_TIMING
+# define BOARD_DEFAULT_NAND_TIMING 0x0737
+# endif
+#else
+# define __nand_boot_init
+#endif
+
+/**
+ * Define this symbol for testing purpose. It will add a command to read an
+ * image from the NAND like it the boot strap code will do.
+ */
+#define CONFIG_NAND_S3C24XX_BOOT_DEBUG
+
+/* NAND controller's register */
+
+#define NFCONF 0x00
+
+#ifdef CONFIG_CPU_S3C2410
+
+#define NFCMD 0x04
+#define NFADDR 0x08
+#define NFDATA 0x0c
+#define NFSTAT 0x10
+#define NFECC 0x14
+
+/* S3C2410 specific bits */
+#define NFSTAT_BUSY (1)
+#define NFCONF_nFCE (1 << 11)
+#define NFCONF_INITECC (1 << 12)
+#define NFCONF_EN (1 << 15)
+
+#endif /* CONFIG_CPU_S3C2410 */
+
+#ifdef CONFIG_CPU_S3C2440
+
+#define NFCONT 0x04
+#define NFCMD 0x08
+#define NFADDR 0x0C
+#define NFDATA 0x10
+
+#define NFECC 0x1C
+#define NFSTAT 0x20
+
+/* S3C2440 specific bits */
+#define NFSTAT_BUSY (1)
+#define NFCONT_nFCE (1 << 1)
+#define NFCONF_INITECC (1 << 12)
+#define NFCONT_EN (1)
+
+#endif /* CONFIG_CPU_S3C2440 */
+
+
+struct s3c24x0_nand_host {
+ struct mtd_info mtd;
+ struct nand_chip nand;
+ struct mtd_partition *parts;
+ struct device_d *dev;
+
+ unsigned long base;
+};
+
+/**
+ * oob placement block for use with hardware ecc generation
+ */
+static struct nand_ecclayout nand_hw_eccoob = {
+ .eccbytes = 3,
+ .eccpos = { 0, 1, 2},
+ .oobfree = {
+ {
+ .offset = 8,
+ .length = 8
+ }
+ }
+};
+
+/* - Functions shared between the boot strap code and the regular driver - */
+
+/**
+ * Issue the specified command to the NAND device
+ * @param[in] host Base address of the NAND controller
+ * @param[in] cmd Command for NAND flash
+ */
+static void __nand_boot_init send_cmd(unsigned long host, uint8_t cmd)
+{
+ writeb(cmd, host + NFCMD);
+}
+
+/**
+ * Issue the specified address to the NAND device
+ * @param[in] host Base address of the NAND controller
+ * @param[in] addr Address for the NAND flash
+ */
+static void __nand_boot_init send_addr(unsigned long host, uint8_t addr)
+{
+ writeb(addr, host + NFADDR);
+}
+
+/**
+ * Enable the NAND flash access
+ * @param[in] host Base address of the NAND controller
+ */
+static void __nand_boot_init enable_cs(unsigned long host)
+{
+#ifdef CONFIG_CPU_S3C2410
+ writew(readw(host + NFCONF) & ~NFCONF_nFCE, host + NFCONF);
+#endif
+#ifdef CONFIG_CPU_S3C2440
+ writew(readw(host + NFCONT) & ~NFCONT_nFCE, host + NFCONT);
+#endif
+}
+
+/**
+ * Disable the NAND flash access
+ * @param[in] host Base address of the NAND controller
+ */
+static void __nand_boot_init disable_cs(unsigned long host)
+{
+#ifdef CONFIG_CPU_S3C2410
+ writew(readw(host + NFCONF) | NFCONF_nFCE, host + NFCONF);
+#endif
+#ifdef CONFIG_CPU_S3C2440
+ writew(readw(host + NFCONT) | NFCONT_nFCE, host + NFCONT);
+#endif
+}
+
+/**
+ * Enable the NAND flash controller
+ * @param[in] host Base address of the NAND controller
+ * @param[in] timing Timing to access the NAND memory
+ */
+static void __nand_boot_init enable_nand_controller(unsigned long host, uint32_t timing)
+{
+#ifdef CONFIG_CPU_S3C2410
+ writew(timing + NFCONF_EN + NFCONF_nFCE, host + NFCONF);
+#endif
+#ifdef CONFIG_CPU_S3C2440
+ writew(NFCONT_EN + NFCONT_nFCE, host + NFCONT);
+ writew(timing, host + NFCONF);
+#endif
+}
+
+/**
+ * Diable the NAND flash controller
+ * @param[in] host Base address of the NAND controller
+ */
+static void __nand_boot_init disable_nand_controller(unsigned long host)
+{
+#ifdef CONFIG_CPU_S3C2410
+ writew(NFCONF_nFCE, host + NFCONF);
+#endif
+#ifdef CONFIG_CPU_S3C2440
+ writew(NFCONT_nFCE, host + NFCONT);
+#endif
+}
+
+/* ----------------------------------------------------------------------- */
+
+/**
+ * Check the ECC and try to repair the data if possible
+ * @param[in] mtd_info FIXME
+ * @param[inout] dat Pointer to the data buffer that might contain a bit error
+ * @param[in] read_ecc ECC data from the OOB space
+ * @param[in] calc_ecc ECC data calculated from the data
+ * @return 0 no error, 1 repaired error, -1 no way...
+ *
+ * @note: Alsways 512 byte of data
+ */
+static int s3c2410_nand_correct_data(struct mtd_info *mtd, uint8_t *dat,
+ uint8_t *read_ecc, uint8_t *calc_ecc)
+{
+ unsigned int diff0, diff1, diff2;
+ unsigned int bit, byte;
+
+ diff0 = read_ecc[0] ^ calc_ecc[0];
+ diff1 = read_ecc[1] ^ calc_ecc[1];
+ diff2 = read_ecc[2] ^ calc_ecc[2];
+
+ if (diff0 == 0 && diff1 == 0 && diff2 == 0)
+ return 0; /* ECC is ok */
+
+ /* sometimes people do not think about using the ECC, so check
+ * to see if we have an 0xff,0xff,0xff read ECC and then ignore
+ * the error, on the assumption that this is an un-eccd page.
+ */
+ if (read_ecc[0] == 0xff && read_ecc[1] == 0xff && read_ecc[2] == 0xff
+ /* && info->platform->ignore_unset_ecc */)
+ return 0;
+
+ /* Can we correct this ECC (ie, one row and column change).
+ * Note, this is similar to the 256 error code on smartmedia */
+
+ if (((diff0 ^ (diff0 >> 1)) & 0x55) == 0x55 &&
+ ((diff1 ^ (diff1 >> 1)) & 0x55) == 0x55 &&
+ ((diff2 ^ (diff2 >> 1)) & 0x55) == 0x55) {
+ /* calculate the bit position of the error */
+
+ bit = ((diff2 >> 3) & 1) |
+ ((diff2 >> 4) & 2) |
+ ((diff2 >> 5) & 4);
+
+ /* calculate the byte position of the error */
+
+ byte = ((diff2 << 7) & 0x100) |
+ ((diff1 << 0) & 0x80) |
+ ((diff1 << 1) & 0x40) |
+ ((diff1 << 2) & 0x20) |
+ ((diff1 << 3) & 0x10) |
+ ((diff0 >> 4) & 0x08) |
+ ((diff0 >> 3) & 0x04) |
+ ((diff0 >> 2) & 0x02) |
+ ((diff0 >> 1) & 0x01);
+
+ dat[byte] ^= (1 << bit);
+ return 1;
+ }
+
+ /* if there is only one bit difference in the ECC, then
+ * one of only a row or column parity has changed, which
+ * means the error is most probably in the ECC itself */
+
+ diff0 |= (diff1 << 8);
+ diff0 |= (diff2 << 16);
+
+ if ((diff0 & ~(1<<fls(diff0))) == 0)
+ return 1;
+
+ return -1;
+}
+
+static void s3c2410_nand_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ struct nand_chip *nand_chip = mtd->priv;
+ struct s3c24x0_nand_host *host = nand_chip->priv;
+
+ writel(readl(host->base + NFCONF) | NFCONF_INITECC , host->base + NFCONF);
+}
+
+static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, uint8_t *ecc_code)
+{
+ struct nand_chip *nand_chip = mtd->priv;
+ struct s3c24x0_nand_host *host = nand_chip->priv;
+
+ ecc_code[0] = readb(host->base + NFECC);
+ ecc_code[1] = readb(host->base + NFECC + 1);
+ ecc_code[2] = readb(host->base + NFECC + 2);
+
+ return 0;
+}
+
+static void s3c24x0_nand_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct nand_chip *nand_chip = mtd->priv;
+ struct s3c24x0_nand_host *host = nand_chip->priv;
+
+ if (chip == -1)
+ disable_cs(host->base);
+ else
+ enable_cs(host->base);
+}
+
+static int s3c24x0_nand_devready(struct mtd_info *mtd)
+{
+ struct nand_chip *nand_chip = mtd->priv;
+ struct s3c24x0_nand_host *host = nand_chip->priv;
+
+ return readw(host->base + NFSTAT) & NFSTAT_BUSY;
+}
+
+static void s3c24x0_nand_hwcontrol(struct mtd_info *mtd, int cmd,
+ unsigned int ctrl)
+{
+ struct nand_chip *nand_chip = mtd->priv;
+ struct s3c24x0_nand_host *host = nand_chip->priv;
+
+ if (cmd == NAND_CMD_NONE)
+ return;
+ /*
+ * If the CLE should be active, this call is a NAND command
+ */
+ if (ctrl & NAND_CLE)
+ send_cmd(host->base, cmd);
+ /*
+ * If the ALE should be active, this call is a NAND address
+ */
+ if (ctrl & NAND_ALE)
+ send_addr(host->base, cmd);
+}
+
+static int s3c24x0_nand_inithw(struct s3c24x0_nand_host *host)
+{
+ struct s3c24x0_nand_platform_data *pdata = host->dev->platform_data;
+ uint32_t tmp;
+
+ /* reset the NAND controller */
+ disable_nand_controller(host->base);
+
+ if (pdata != NULL)
+ tmp = pdata->nand_timing;
+ else
+ /* else slowest possible timing */
+ tmp = CALC_NFCONF_TIMING(4, 8, 8);
+
+ /* reenable the NAND controller */
+ enable_nand_controller(host->base, tmp);
+
+ return 0;
+}
+
+static int s3c24x0_nand_probe(struct device_d *dev)
+{
+ struct nand_chip *chip;
+ struct mtd_info *mtd;
+ struct s3c24x0_nand_host *host;
+ int ret;
+
+ /* Allocate memory for MTD device structure and private data */
+ host = kzalloc(sizeof(struct s3c24x0_nand_host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->dev = dev;
+ host->base = dev->map_base;
+
+ /* structures must be linked */
+ chip = &host->nand;
+ mtd = &host->mtd;
+ mtd->priv = chip;
+
+ /* init the default settings */
+#if 0
+ /* TODO: Will follow later */
+ init_nand_chip_bw8(chip);
+#endif
+ /* 50 us command delay time */
+ chip->chip_delay = 50;
+ chip->priv = host;
+
+ chip->IO_ADDR_R = chip->IO_ADDR_W = (void*)(dev->map_base + NFDATA);
+
+ chip->cmd_ctrl = s3c24x0_nand_hwcontrol;
+ chip->dev_ready = s3c24x0_nand_devready;
+ chip->select_chip = s3c24x0_nand_select_chip;
+
+ /* we are using the hardware ECC feature of this device */
+ chip->ecc.calculate = s3c2410_nand_calculate_ecc;
+ chip->ecc.correct = s3c2410_nand_correct_data;
+ chip->ecc.hwctl = s3c2410_nand_enable_hwecc;
+ chip->ecc.calculate = s3c2410_nand_calculate_ecc;
+
+ /* our hardware capabilities */
+ chip->ecc.mode = NAND_ECC_HW;
+ chip->ecc.size = 512;
+ chip->ecc.bytes = 3;
+ chip->ecc.layout = &nand_hw_eccoob;
+
+ ret = s3c24x0_nand_inithw(host);
+ if (ret != 0)
+ goto on_error;
+
+ /* Scan to find existence of the device */
+ ret = nand_scan(mtd, 1);
+ if (ret != 0) {
+ ret = -ENXIO;
+ goto on_error;
+ }
+
+ return add_mtd_device(mtd);
+
+on_error:
+ free(host);
+ return ret;
+}
+
+static struct driver_d s3c24x0_nand_driver = {
+ .name = "s3c24x0_nand",
+ .probe = s3c24x0_nand_probe,
+};
+
+#ifdef CONFIG_S3C24XX_NAND_BOOT
+
+static void __nand_boot_init wait_for_completion(unsigned long host)
+{
+ while (!(readw(host + NFSTAT) & NFSTAT_BUSY))
+ ;
+}
+
+static void __nand_boot_init nfc_addr(unsigned long host, uint32_t offs)
+{
+ send_addr(host, offs & 0xff);
+ send_addr(host, (offs >> 9) & 0xff);
+ send_addr(host, (offs >> 17) & 0xff);
+ send_addr(host, (offs >> 25) & 0xff);
+}
+
+/**
+ * Load a sequential count of blocks from the NAND into memory
+ * @param[out] dest Pointer to target area (in SDRAM)
+ * @param[in] size Bytes to read from NAND device
+ * @param[in] page Start page to read from
+ * @param[in] pagesize Size of each page in the NAND
+ *
+ * This function must be located in the first 4kiB of the U-Boot-v2 image
+ * (guess why). When this routine is running the SDRAM is up and running
+ * and it runs from the correct address (physical=linked address).
+ * TODO Could we access the platform data from the boardfile?
+ * Due to it makes no sense this function does not return in case of failure.
+ */
+void __nand_boot_init s3c24x0_nand_load_image(void *dest, int size, int page, int pagesize)
+{
+ unsigned long host = S3C24X0_NAND_BASE;
+ int i;
+
+ /*
+ * Reenable the NFC and use the default (but slow) access
+ * timing or the board specific setting if provided.
+ */
+ enable_nand_controller(host, BOARD_DEFAULT_NAND_TIMING);
+ enable_cs(host);
+
+ /* Reset the NAND device */
+ send_cmd(host, NAND_CMD_RESET);
+ wait_for_completion(host);
+ disable_cs(host);
+
+ do {
+ enable_cs(host);
+ send_cmd(host, NAND_CMD_READ0);
+ nfc_addr(host, page * pagesize);
+ wait_for_completion(host);
+ /* copy one page (do *not* use readsb() here!)*/
+ for (i = 0; i < pagesize; i++)
+ writeb(readb(host + NFDATA), (unsigned long)(dest + i));
+ disable_cs(host);
+
+ page++;
+ dest += pagesize;
+ size -= pagesize;
+ } while (size >= 0);
+
+ /* disable the controller again */
+ disable_nand_controller(host);
+}
+
+#ifdef CONFIG_NAND_S3C24XX_BOOT_DEBUG
+#include <command.h>
+
+static int do_nand_boot_test(cmd_tbl_t *cmdtp, int argc, char *argv[])
+{
+ void *dest;
+ int size, pagesize;
+
+ if (argc < 3) {
+ u_boot_cmd_usage(cmdtp);
+ return 1;
+ }
+
+ dest = (void *)strtoul_suffix(argv[1], NULL, 0);
+ size = strtoul_suffix(argv[2], NULL, 0);
+ pagesize = strtoul_suffix(argv[3], NULL, 0);
+
+ s3c24x0_nand_load_image(dest, size, 0, pagesize);
+
+ return 0;
+}
+
+static const __maybe_unused char cmd_nand_boot_test_help[] =
+"Usage: nand_boot_test <dest> <size> <pagesize>\n";
+
+U_BOOT_CMD_START(nand_boot_test)
+ .maxargs = CONFIG_MAXARGS,
+ .cmd = do_nand_boot_test,
+ .usage = "load an image from NAND",
+ U_BOOT_CMD_HELP(cmd_nand_boot_test_help)
+U_BOOT_CMD_END
+#endif
+
+#endif /* CONFIG_S3C24XX_NAND_BOOT */
+
+/*
+ * Main initialization routine
+ * @return 0 if successful; non-zero otherwise
+ */
+static int __init s3c24x0_nand_init(void)
+{
+ return register_driver(&s3c24x0_nand_driver);
+}
+
+device_initcall(s3c24x0_nand_init);
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index f6905b2afd..6acce19630 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -42,4 +42,19 @@ config DRIVER_SERIAL_NS16550_OMAP_EXTENSIONS
depends on ARCH_OMAP
help
Say Y here if you are using OMAP extensions to NS16550
+
+config DRIVER_SERIAL_S3C24X0
+ bool "Samsung S3C24X0 serial driver"
+ depends on ARCH_S3C24xx
+ default y
+ help
+ Say Y here if you want to use the CONS on a S3C24X0 CPU
+
+config DRIVER_SERIAL_S3C24X0_AUTOSYNC
+ bool "Enable auto flow"
+ depends on DRIVER_SERIAL_S3C24X0
+ help
+ Say Y here if you want to use the auto flow feature of this
+ UART. RTS and CTS will be handled by the hardware when enabled.
+
endmenu
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index fc1e891739..3bc965c6e3 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -12,3 +12,4 @@ obj-$(CONFIG_DRIVER_SERIAL_LINUX_COMSOLE) += linux_console.o
obj-$(CONFIG_DRIVER_SERIAL_MPC5XXX) += serial_mpc5xxx.o
obj-$(CONFIG_DRIVER_SERIAL_BLACKFIN) += serial_blackfin.o
obj-$(CONFIG_DRIVER_SERIAL_NS16550) += serial_ns16550.o
+obj-$(CONFIG_DRIVER_SERIAL_S3C24X0) += serial_s3c24x0.o
diff --git a/drivers/serial/serial_s3c24x0.c b/drivers/serial/serial_s3c24x0.c
new file mode 100644
index 0000000000..43a4cd2d47
--- /dev/null
+++ b/drivers/serial/serial_s3c24x0.c
@@ -0,0 +1,165 @@
+/*
+ * (c) 2009 Juergen Beisert <j.beisert@saschahauer.de>
+ *
+ * Based on code from:
+ * (c) 2004 Sascha Hauer <sascha@saschahauer.de>
+ *
+ * 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 Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <malloc.h>
+#include <asm/io.h>
+#include <asm/arch/s3c24xx-generic.h>
+#include <asm/arch/s3c24x0-iomap.h>
+
+/* Note: Offsets are for little endian access */
+#define ULCON 0x00 /* line control */
+#define UCON 0x04 /* UART control */
+#define UFCON 0x08 /* FIFO control */
+#define UMCON 0x0c /* modem control */
+#define UTRSTAT 0x10 /* Rx/Tx status */
+#define UERSTAT 0x14 /* error status */
+#define UFSTAT 0x18 /* FIFO status */
+#define UMSTAT 0x1c /* modem status */
+#define UTXH 0x20 /* transmitt */
+#define URXH 0x24 /* receive */
+#define UBRDIV 0x28 /* baudrate generator */
+
+static int s3c24x0_serial_setbaudrate(struct console_device *cdev, int baudrate)
+{
+ struct device_d *dev = cdev->dev;
+ unsigned val;
+
+ /* value is calculated so : PCLK / (16 * baudrate) -1 */
+ val = s3c24xx_get_pclk() / (16 * baudrate) - 1;
+ writew(val, dev->map_base + UBRDIV);
+
+ return 0;
+}
+
+static int s3c24x0_serial_init_port(struct console_device *cdev)
+{
+ struct device_d *dev = cdev->dev;
+
+ /* FIFO enable, Tx/Rx FIFO clear */
+ writeb(0x07, dev->map_base + UFCON);
+ writeb(0x00, dev->map_base + UMCON);
+
+ /* Normal,No parity,1 stop,8 bit */
+ writeb(0x03, dev->map_base + ULCON);
+ /*
+ * tx=level,rx=edge,disable timeout int.,enable rx error int.,
+ * normal,interrupt or polling
+ */
+ writew(0x0245, dev->map_base + UCON);
+
+#ifdef CONFIG_DRIVER_SERIAL_S3C24X0_AUTOSYNC
+ writeb(0x01, dev->map_base + UMCON); /* RTS up */
+#endif
+
+ return 0;
+}
+
+static void s3c24x0_serial_putc(struct console_device *cdev, char c)
+{
+ struct device_d *dev = cdev->dev;
+
+ /* Wait for Tx FIFO not full */
+ while (!(readb(dev->map_base + UTRSTAT) & 0x2))
+ ;
+
+ writeb(c, dev->map_base + UTXH);
+}
+
+static int s3c24x0_serial_tstc(struct console_device *cdev)
+{
+ struct device_d *dev = cdev->dev;
+
+ /* If receive fifo is empty, return false */
+ if (readb(dev->map_base + UTRSTAT) & 0x1)
+ return 1;
+
+ return 0;
+}
+
+static int s3c24x0_serial_getc(struct console_device *cdev)
+{
+ struct device_d *dev = cdev->dev;
+
+ /* wait for a character */
+ while (!(readb(dev->map_base + UTRSTAT) & 0x1))
+ ;
+
+ return readb(dev->map_base + URXH);
+}
+
+static void s3c24x0_serial_flush(struct console_device *cdev)
+{
+ struct device_d *dev = cdev->dev;
+
+ while (!readb(dev->map_base + UTRSTAT) & 0x4)
+ ;
+}
+
+static int s3c24x0_serial_probe(struct device_d *dev)
+{
+ struct console_device *cdev;
+
+ cdev = malloc(sizeof(struct console_device));
+
+ dev->type_data = cdev;
+ cdev->dev = dev;
+ cdev->f_caps = CONSOLE_STDIN | CONSOLE_STDOUT | CONSOLE_STDERR;
+ cdev->tstc = s3c24x0_serial_tstc;
+ cdev->putc = s3c24x0_serial_putc;
+ cdev->getc = s3c24x0_serial_getc;
+ cdev->flush = s3c24x0_serial_flush;
+ cdev->setbrg = s3c24x0_serial_setbaudrate;
+
+ s3c24x0_serial_init_port(cdev);
+
+ /* Enable UART */
+ console_register(cdev);
+
+ return 0;
+}
+
+static void s3c24x0_serial_remove(struct device_d *dev)
+{
+ struct console_device *cdev = dev->type_data;
+
+ s3c24x0_serial_flush(cdev);
+ free(cdev);
+ dev->type_data = NULL;
+}
+
+static struct driver_d s3c24x0_serial_driver = {
+ .name = "s3c24x0_serial",
+ .probe = s3c24x0_serial_probe,
+ .remove = s3c24x0_serial_remove,
+ .type = DEVICE_TYPE_CONSOLE,
+};
+
+static int s3c24x0_serial_init(void)
+{
+ register_driver(&s3c24x0_serial_driver);
+ return 0;
+}
+
+console_initcall(s3c24x0_serial_init);