# Board specific.
obj-$(CONFIG_MTD_ONENAND_GENERIC) += generic.o
obj-$(CONFIG_MTD_ONENAND_OMAP2) += omap2.o
-obj-$(CONFIG_MTD_ONENAND_SAMSUNG) += samsung.o
+obj-$(CONFIG_MTD_ONENAND_SAMSUNG) += samsung_mtd.o
onenand-objs = onenand_base.o onenand_bbt.o
+++ /dev/null
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Samsung S3C64XX/S5PC1XX OneNAND driver
- *
- * Copyright © 2008-2010 Samsung Electronics
- * Kyungmin Park <kyungmin.park@samsung.com>
- * Marek Szyprowski <m.szyprowski@samsung.com>
- *
- * Implementation:
- * S3C64XX: emulate the pseudo BufferRAM
- * S5PC110: use DMA
- */
-
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/sched.h>
-#include <linux/slab.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/onenand.h>
-#include <linux/mtd/partitions.h>
-#include <linux/dma-mapping.h>
-#include <linux/interrupt.h>
-#include <linux/io.h>
-
-#include "samsung.h"
-
-enum soc_type {
- TYPE_S3C6400,
- TYPE_S3C6410,
- TYPE_S5PC110,
-};
-
-#define ONENAND_ERASE_STATUS 0x00
-#define ONENAND_MULTI_ERASE_SET 0x01
-#define ONENAND_ERASE_START 0x03
-#define ONENAND_UNLOCK_START 0x08
-#define ONENAND_UNLOCK_END 0x09
-#define ONENAND_LOCK_START 0x0A
-#define ONENAND_LOCK_END 0x0B
-#define ONENAND_LOCK_TIGHT_START 0x0C
-#define ONENAND_LOCK_TIGHT_END 0x0D
-#define ONENAND_UNLOCK_ALL 0x0E
-#define ONENAND_OTP_ACCESS 0x12
-#define ONENAND_SPARE_ACCESS_ONLY 0x13
-#define ONENAND_MAIN_ACCESS_ONLY 0x14
-#define ONENAND_ERASE_VERIFY 0x15
-#define ONENAND_MAIN_SPARE_ACCESS 0x16
-#define ONENAND_PIPELINE_READ 0x4000
-
-#define MAP_00 (0x0)
-#define MAP_01 (0x1)
-#define MAP_10 (0x2)
-#define MAP_11 (0x3)
-
-#define S3C64XX_CMD_MAP_SHIFT 24
-
-#define S3C6400_FBA_SHIFT 10
-#define S3C6400_FPA_SHIFT 4
-#define S3C6400_FSA_SHIFT 2
-
-#define S3C6410_FBA_SHIFT 12
-#define S3C6410_FPA_SHIFT 6
-#define S3C6410_FSA_SHIFT 4
-
-/* S5PC110 specific definitions */
-#define S5PC110_DMA_SRC_ADDR 0x400
-#define S5PC110_DMA_SRC_CFG 0x404
-#define S5PC110_DMA_DST_ADDR 0x408
-#define S5PC110_DMA_DST_CFG 0x40C
-#define S5PC110_DMA_TRANS_SIZE 0x414
-#define S5PC110_DMA_TRANS_CMD 0x418
-#define S5PC110_DMA_TRANS_STATUS 0x41C
-#define S5PC110_DMA_TRANS_DIR 0x420
-#define S5PC110_INTC_DMA_CLR 0x1004
-#define S5PC110_INTC_ONENAND_CLR 0x1008
-#define S5PC110_INTC_DMA_MASK 0x1024
-#define S5PC110_INTC_ONENAND_MASK 0x1028
-#define S5PC110_INTC_DMA_PEND 0x1044
-#define S5PC110_INTC_ONENAND_PEND 0x1048
-#define S5PC110_INTC_DMA_STATUS 0x1064
-#define S5PC110_INTC_ONENAND_STATUS 0x1068
-
-#define S5PC110_INTC_DMA_TD (1 << 24)
-#define S5PC110_INTC_DMA_TE (1 << 16)
-
-#define S5PC110_DMA_CFG_SINGLE (0x0 << 16)
-#define S5PC110_DMA_CFG_4BURST (0x2 << 16)
-#define S5PC110_DMA_CFG_8BURST (0x3 << 16)
-#define S5PC110_DMA_CFG_16BURST (0x4 << 16)
-
-#define S5PC110_DMA_CFG_INC (0x0 << 8)
-#define S5PC110_DMA_CFG_CNT (0x1 << 8)
-
-#define S5PC110_DMA_CFG_8BIT (0x0 << 0)
-#define S5PC110_DMA_CFG_16BIT (0x1 << 0)
-#define S5PC110_DMA_CFG_32BIT (0x2 << 0)
-
-#define S5PC110_DMA_SRC_CFG_READ (S5PC110_DMA_CFG_16BURST | \
- S5PC110_DMA_CFG_INC | \
- S5PC110_DMA_CFG_16BIT)
-#define S5PC110_DMA_DST_CFG_READ (S5PC110_DMA_CFG_16BURST | \
- S5PC110_DMA_CFG_INC | \
- S5PC110_DMA_CFG_32BIT)
-#define S5PC110_DMA_SRC_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \
- S5PC110_DMA_CFG_INC | \
- S5PC110_DMA_CFG_32BIT)
-#define S5PC110_DMA_DST_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \
- S5PC110_DMA_CFG_INC | \
- S5PC110_DMA_CFG_16BIT)
-
-#define S5PC110_DMA_TRANS_CMD_TDC (0x1 << 18)
-#define S5PC110_DMA_TRANS_CMD_TEC (0x1 << 16)
-#define S5PC110_DMA_TRANS_CMD_TR (0x1 << 0)
-
-#define S5PC110_DMA_TRANS_STATUS_TD (0x1 << 18)
-#define S5PC110_DMA_TRANS_STATUS_TB (0x1 << 17)
-#define S5PC110_DMA_TRANS_STATUS_TE (0x1 << 16)
-
-#define S5PC110_DMA_DIR_READ 0x0
-#define S5PC110_DMA_DIR_WRITE 0x1
-
-struct s3c_onenand {
- struct mtd_info *mtd;
- struct platform_device *pdev;
- enum soc_type type;
- void __iomem *base;
- void __iomem *ahb_addr;
- int bootram_command;
- void *page_buf;
- void *oob_buf;
- unsigned int (*mem_addr)(int fba, int fpa, int fsa);
- unsigned int (*cmd_map)(unsigned int type, unsigned int val);
- void __iomem *dma_addr;
- unsigned long phys_base;
- struct completion complete;
-};
-
-#define CMD_MAP_00(dev, addr) (dev->cmd_map(MAP_00, ((addr) << 1)))
-#define CMD_MAP_01(dev, mem_addr) (dev->cmd_map(MAP_01, (mem_addr)))
-#define CMD_MAP_10(dev, mem_addr) (dev->cmd_map(MAP_10, (mem_addr)))
-#define CMD_MAP_11(dev, addr) (dev->cmd_map(MAP_11, ((addr) << 2)))
-
-static struct s3c_onenand *onenand;
-
-static inline int s3c_read_reg(int offset)
-{
- return readl(onenand->base + offset);
-}
-
-static inline void s3c_write_reg(int value, int offset)
-{
- writel(value, onenand->base + offset);
-}
-
-static inline int s3c_read_cmd(unsigned int cmd)
-{
- return readl(onenand->ahb_addr + cmd);
-}
-
-static inline void s3c_write_cmd(int value, unsigned int cmd)
-{
- writel(value, onenand->ahb_addr + cmd);
-}
-
-#ifdef SAMSUNG_DEBUG
-static void s3c_dump_reg(void)
-{
- int i;
-
- for (i = 0; i < 0x400; i += 0x40) {
- printk(KERN_INFO "0x%08X: 0x%08x 0x%08x 0x%08x 0x%08x\n",
- (unsigned int) onenand->base + i,
- s3c_read_reg(i), s3c_read_reg(i + 0x10),
- s3c_read_reg(i + 0x20), s3c_read_reg(i + 0x30));
- }
-}
-#endif
-
-static unsigned int s3c64xx_cmd_map(unsigned type, unsigned val)
-{
- return (type << S3C64XX_CMD_MAP_SHIFT) | val;
-}
-
-static unsigned int s3c6400_mem_addr(int fba, int fpa, int fsa)
-{
- return (fba << S3C6400_FBA_SHIFT) | (fpa << S3C6400_FPA_SHIFT) |
- (fsa << S3C6400_FSA_SHIFT);
-}
-
-static unsigned int s3c6410_mem_addr(int fba, int fpa, int fsa)
-{
- return (fba << S3C6410_FBA_SHIFT) | (fpa << S3C6410_FPA_SHIFT) |
- (fsa << S3C6410_FSA_SHIFT);
-}
-
-static void s3c_onenand_reset(void)
-{
- unsigned long timeout = 0x10000;
- int stat;
-
- s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET);
- while (1 && timeout--) {
- stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
- if (stat & RST_CMP)
- break;
- }
- stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
- s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
-
- /* Clear interrupt */
- s3c_write_reg(0x0, INT_ERR_ACK_OFFSET);
- /* Clear the ECC status */
- s3c_write_reg(0x0, ECC_ERR_STAT_OFFSET);
-}
-
-static unsigned short s3c_onenand_readw(void __iomem *addr)
-{
- struct onenand_chip *this = onenand->mtd->priv;
- struct device *dev = &onenand->pdev->dev;
- int reg = addr - this->base;
- int word_addr = reg >> 1;
- int value;
-
- /* It's used for probing time */
- switch (reg) {
- case ONENAND_REG_MANUFACTURER_ID:
- return s3c_read_reg(MANUFACT_ID_OFFSET);
- case ONENAND_REG_DEVICE_ID:
- return s3c_read_reg(DEVICE_ID_OFFSET);
- case ONENAND_REG_VERSION_ID:
- return s3c_read_reg(FLASH_VER_ID_OFFSET);
- case ONENAND_REG_DATA_BUFFER_SIZE:
- return s3c_read_reg(DATA_BUF_SIZE_OFFSET);
- case ONENAND_REG_TECHNOLOGY:
- return s3c_read_reg(TECH_OFFSET);
- case ONENAND_REG_SYS_CFG1:
- return s3c_read_reg(MEM_CFG_OFFSET);
-
- /* Used at unlock all status */
- case ONENAND_REG_CTRL_STATUS:
- return 0;
-
- case ONENAND_REG_WP_STATUS:
- return ONENAND_WP_US;
-
- default:
- break;
- }
-
- /* BootRAM access control */
- if ((unsigned int) addr < ONENAND_DATARAM && onenand->bootram_command) {
- if (word_addr == 0)
- return s3c_read_reg(MANUFACT_ID_OFFSET);
- if (word_addr == 1)
- return s3c_read_reg(DEVICE_ID_OFFSET);
- if (word_addr == 2)
- return s3c_read_reg(FLASH_VER_ID_OFFSET);
- }
-
- value = s3c_read_cmd(CMD_MAP_11(onenand, word_addr)) & 0xffff;
- dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__,
- word_addr, value);
- return value;
-}
-
-static void s3c_onenand_writew(unsigned short value, void __iomem *addr)
-{
- struct onenand_chip *this = onenand->mtd->priv;
- struct device *dev = &onenand->pdev->dev;
- unsigned int reg = addr - this->base;
- unsigned int word_addr = reg >> 1;
-
- /* It's used for probing time */
- switch (reg) {
- case ONENAND_REG_SYS_CFG1:
- s3c_write_reg(value, MEM_CFG_OFFSET);
- return;
-
- case ONENAND_REG_START_ADDRESS1:
- case ONENAND_REG_START_ADDRESS2:
- return;
-
- /* Lock/lock-tight/unlock/unlock_all */
- case ONENAND_REG_START_BLOCK_ADDRESS:
- return;
-
- default:
- break;
- }
-
- /* BootRAM access control */
- if ((unsigned int)addr < ONENAND_DATARAM) {
- if (value == ONENAND_CMD_READID) {
- onenand->bootram_command = 1;
- return;
- }
- if (value == ONENAND_CMD_RESET) {
- s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET);
- onenand->bootram_command = 0;
- return;
- }
- }
-
- dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__,
- word_addr, value);
-
- s3c_write_cmd(value, CMD_MAP_11(onenand, word_addr));
-}
-
-static int s3c_onenand_wait(struct mtd_info *mtd, int state)
-{
- struct device *dev = &onenand->pdev->dev;
- unsigned int flags = INT_ACT;
- unsigned int stat, ecc;
- unsigned long timeout;
-
- switch (state) {
- case FL_READING:
- flags |= BLK_RW_CMP | LOAD_CMP;
- break;
- case FL_WRITING:
- flags |= BLK_RW_CMP | PGM_CMP;
- break;
- case FL_ERASING:
- flags |= BLK_RW_CMP | ERS_CMP;
- break;
- case FL_LOCKING:
- flags |= BLK_RW_CMP;
- break;
- default:
- break;
- }
-
- /* The 20 msec is enough */
- timeout = jiffies + msecs_to_jiffies(20);
- while (time_before(jiffies, timeout)) {
- stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
- if (stat & flags)
- break;
-
- if (state != FL_READING)
- cond_resched();
- }
- /* To get correct interrupt status in timeout case */
- stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
- s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
-
- /*
- * In the Spec. it checks the controller status first
- * However if you get the correct information in case of
- * power off recovery (POR) test, it should read ECC status first
- */
- if (stat & LOAD_CMP) {
- ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET);
- if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) {
- dev_info(dev, "%s: ECC error = 0x%04x\n", __func__,
- ecc);
- mtd->ecc_stats.failed++;
- return -EBADMSG;
- }
- }
-
- if (stat & (LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR)) {
- dev_info(dev, "%s: controller error = 0x%04x\n", __func__,
- stat);
- if (stat & LOCKED_BLK)
- dev_info(dev, "%s: it's locked error = 0x%04x\n",
- __func__, stat);
-
- return -EIO;
- }
-
- return 0;
-}
-
-static int s3c_onenand_command(struct mtd_info *mtd, int cmd, loff_t addr,
- size_t len)
-{
- struct onenand_chip *this = mtd->priv;
- unsigned int *m, *s;
- int fba, fpa, fsa = 0;
- unsigned int mem_addr, cmd_map_01, cmd_map_10;
- int i, mcount, scount;
- int index;
-
- fba = (int) (addr >> this->erase_shift);
- fpa = (int) (addr >> this->page_shift);
- fpa &= this->page_mask;
-
- mem_addr = onenand->mem_addr(fba, fpa, fsa);
- cmd_map_01 = CMD_MAP_01(onenand, mem_addr);
- cmd_map_10 = CMD_MAP_10(onenand, mem_addr);
-
- switch (cmd) {
- case ONENAND_CMD_READ:
- case ONENAND_CMD_READOOB:
- case ONENAND_CMD_BUFFERRAM:
- ONENAND_SET_NEXT_BUFFERRAM(this);
- default:
- break;
- }
-
- index = ONENAND_CURRENT_BUFFERRAM(this);
-
- /*
- * Emulate Two BufferRAMs and access with 4 bytes pointer
- */
- m = onenand->page_buf;
- s = onenand->oob_buf;
-
- if (index) {
- m += (this->writesize >> 2);
- s += (mtd->oobsize >> 2);
- }
-
- mcount = mtd->writesize >> 2;
- scount = mtd->oobsize >> 2;
-
- switch (cmd) {
- case ONENAND_CMD_READ:
- /* Main */
- for (i = 0; i < mcount; i++)
- *m++ = s3c_read_cmd(cmd_map_01);
- return 0;
-
- case ONENAND_CMD_READOOB:
- s3c_write_reg(TSRF, TRANS_SPARE_OFFSET);
- /* Main */
- for (i = 0; i < mcount; i++)
- *m++ = s3c_read_cmd(cmd_map_01);
-
- /* Spare */
- for (i = 0; i < scount; i++)
- *s++ = s3c_read_cmd(cmd_map_01);
-
- s3c_write_reg(0, TRANS_SPARE_OFFSET);
- return 0;
-
- case ONENAND_CMD_PROG:
- /* Main */
- for (i = 0; i < mcount; i++)
- s3c_write_cmd(*m++, cmd_map_01);
- return 0;
-
- case ONENAND_CMD_PROGOOB:
- s3c_write_reg(TSRF, TRANS_SPARE_OFFSET);
-
- /* Main - dummy write */
- for (i = 0; i < mcount; i++)
- s3c_write_cmd(0xffffffff, cmd_map_01);
-
- /* Spare */
- for (i = 0; i < scount; i++)
- s3c_write_cmd(*s++, cmd_map_01);
-
- s3c_write_reg(0, TRANS_SPARE_OFFSET);
- return 0;
-
- case ONENAND_CMD_UNLOCK_ALL:
- s3c_write_cmd(ONENAND_UNLOCK_ALL, cmd_map_10);
- return 0;
-
- case ONENAND_CMD_ERASE:
- s3c_write_cmd(ONENAND_ERASE_START, cmd_map_10);
- return 0;
-
- default:
- break;
- }
-
- return 0;
-}
-
-static unsigned char *s3c_get_bufferram(struct mtd_info *mtd, int area)
-{
- struct onenand_chip *this = mtd->priv;
- int index = ONENAND_CURRENT_BUFFERRAM(this);
- unsigned char *p;
-
- if (area == ONENAND_DATARAM) {
- p = onenand->page_buf;
- if (index == 1)
- p += this->writesize;
- } else {
- p = onenand->oob_buf;
- if (index == 1)
- p += mtd->oobsize;
- }
-
- return p;
-}
-
-static int onenand_read_bufferram(struct mtd_info *mtd, int area,
- unsigned char *buffer, int offset,
- size_t count)
-{
- unsigned char *p;
-
- p = s3c_get_bufferram(mtd, area);
- memcpy(buffer, p + offset, count);
- return 0;
-}
-
-static int onenand_write_bufferram(struct mtd_info *mtd, int area,
- const unsigned char *buffer, int offset,
- size_t count)
-{
- unsigned char *p;
-
- p = s3c_get_bufferram(mtd, area);
- memcpy(p + offset, buffer, count);
- return 0;
-}
-
-static int (*s5pc110_dma_ops)(dma_addr_t dst, dma_addr_t src, size_t count, int direction);
-
-static int s5pc110_dma_poll(dma_addr_t dst, dma_addr_t src, size_t count, int direction)
-{
- void __iomem *base = onenand->dma_addr;
- int status;
- unsigned long timeout;
-
- writel(src, base + S5PC110_DMA_SRC_ADDR);
- writel(dst, base + S5PC110_DMA_DST_ADDR);
-
- if (direction == S5PC110_DMA_DIR_READ) {
- writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG);
- writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG);
- } else {
- writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG);
- writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG);
- }
-
- writel(count, base + S5PC110_DMA_TRANS_SIZE);
- writel(direction, base + S5PC110_DMA_TRANS_DIR);
-
- writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD);
-
- /*
- * There's no exact timeout values at Spec.
- * In real case it takes under 1 msec.
- * So 20 msecs are enough.
- */
- timeout = jiffies + msecs_to_jiffies(20);
-
- do {
- status = readl(base + S5PC110_DMA_TRANS_STATUS);
- if (status & S5PC110_DMA_TRANS_STATUS_TE) {
- writel(S5PC110_DMA_TRANS_CMD_TEC,
- base + S5PC110_DMA_TRANS_CMD);
- return -EIO;
- }
- } while (!(status & S5PC110_DMA_TRANS_STATUS_TD) &&
- time_before(jiffies, timeout));
-
- writel(S5PC110_DMA_TRANS_CMD_TDC, base + S5PC110_DMA_TRANS_CMD);
-
- return 0;
-}
-
-static irqreturn_t s5pc110_onenand_irq(int irq, void *data)
-{
- void __iomem *base = onenand->dma_addr;
- int status, cmd = 0;
-
- status = readl(base + S5PC110_INTC_DMA_STATUS);
-
- if (likely(status & S5PC110_INTC_DMA_TD))
- cmd = S5PC110_DMA_TRANS_CMD_TDC;
-
- if (unlikely(status & S5PC110_INTC_DMA_TE))
- cmd = S5PC110_DMA_TRANS_CMD_TEC;
-
- writel(cmd, base + S5PC110_DMA_TRANS_CMD);
- writel(status, base + S5PC110_INTC_DMA_CLR);
-
- if (!onenand->complete.done)
- complete(&onenand->complete);
-
- return IRQ_HANDLED;
-}
-
-static int s5pc110_dma_irq(dma_addr_t dst, dma_addr_t src, size_t count, int direction)
-{
- void __iomem *base = onenand->dma_addr;
- int status;
-
- status = readl(base + S5PC110_INTC_DMA_MASK);
- if (status) {
- status &= ~(S5PC110_INTC_DMA_TD | S5PC110_INTC_DMA_TE);
- writel(status, base + S5PC110_INTC_DMA_MASK);
- }
-
- writel(src, base + S5PC110_DMA_SRC_ADDR);
- writel(dst, base + S5PC110_DMA_DST_ADDR);
-
- if (direction == S5PC110_DMA_DIR_READ) {
- writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG);
- writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG);
- } else {
- writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG);
- writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG);
- }
-
- writel(count, base + S5PC110_DMA_TRANS_SIZE);
- writel(direction, base + S5PC110_DMA_TRANS_DIR);
-
- writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD);
-
- wait_for_completion_timeout(&onenand->complete, msecs_to_jiffies(20));
-
- return 0;
-}
-
-static int s5pc110_read_bufferram(struct mtd_info *mtd, int area,
- unsigned char *buffer, int offset, size_t count)
-{
- struct onenand_chip *this = mtd->priv;
- void __iomem *p;
- void *buf = (void *) buffer;
- dma_addr_t dma_src, dma_dst;
- int err, ofs, page_dma = 0;
- struct device *dev = &onenand->pdev->dev;
-
- p = this->base + area;
- if (ONENAND_CURRENT_BUFFERRAM(this)) {
- if (area == ONENAND_DATARAM)
- p += this->writesize;
- else
- p += mtd->oobsize;
- }
-
- if (offset & 3 || (size_t) buf & 3 ||
- !onenand->dma_addr || count != mtd->writesize)
- goto normal;
-
- /* Handle vmalloc address */
- if (buf >= high_memory) {
- struct page *page;
-
- if (((size_t) buf & PAGE_MASK) !=
- ((size_t) (buf + count - 1) & PAGE_MASK))
- goto normal;
- page = vmalloc_to_page(buf);
- if (!page)
- goto normal;
-
- /* Page offset */
- ofs = ((size_t) buf & ~PAGE_MASK);
- page_dma = 1;
-
- /* DMA routine */
- dma_src = onenand->phys_base + (p - this->base);
- dma_dst = dma_map_page(dev, page, ofs, count, DMA_FROM_DEVICE);
- } else {
- /* DMA routine */
- dma_src = onenand->phys_base + (p - this->base);
- dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE);
- }
- if (dma_mapping_error(dev, dma_dst)) {
- dev_err(dev, "Couldn't map a %d byte buffer for DMA\n", count);
- goto normal;
- }
- err = s5pc110_dma_ops(dma_dst, dma_src,
- count, S5PC110_DMA_DIR_READ);
-
- if (page_dma)
- dma_unmap_page(dev, dma_dst, count, DMA_FROM_DEVICE);
- else
- dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE);
-
- if (!err)
- return 0;
-
-normal:
- if (count != mtd->writesize) {
- /* Copy the bufferram to memory to prevent unaligned access */
- memcpy(this->page_buf, p, mtd->writesize);
- p = this->page_buf + offset;
- }
-
- memcpy(buffer, p, count);
-
- return 0;
-}
-
-static int s5pc110_chip_probe(struct mtd_info *mtd)
-{
- /* Now just return 0 */
- return 0;
-}
-
-static int s3c_onenand_bbt_wait(struct mtd_info *mtd, int state)
-{
- unsigned int flags = INT_ACT | LOAD_CMP;
- unsigned int stat;
- unsigned long timeout;
-
- /* The 20 msec is enough */
- timeout = jiffies + msecs_to_jiffies(20);
- while (time_before(jiffies, timeout)) {
- stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
- if (stat & flags)
- break;
- }
- /* To get correct interrupt status in timeout case */
- stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
- s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
-
- if (stat & LD_FAIL_ECC_ERR) {
- s3c_onenand_reset();
- return ONENAND_BBT_READ_ERROR;
- }
-
- if (stat & LOAD_CMP) {
- int ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET);
- if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) {
- s3c_onenand_reset();
- return ONENAND_BBT_READ_ERROR;
- }
- }
-
- return 0;
-}
-
-static void s3c_onenand_check_lock_status(struct mtd_info *mtd)
-{
- struct onenand_chip *this = mtd->priv;
- struct device *dev = &onenand->pdev->dev;
- unsigned int block, end;
- int tmp;
-
- end = this->chipsize >> this->erase_shift;
-
- for (block = 0; block < end; block++) {
- unsigned int mem_addr = onenand->mem_addr(block, 0, 0);
- tmp = s3c_read_cmd(CMD_MAP_01(onenand, mem_addr));
-
- if (s3c_read_reg(INT_ERR_STAT_OFFSET) & LOCKED_BLK) {
- dev_err(dev, "block %d is write-protected!\n", block);
- s3c_write_reg(LOCKED_BLK, INT_ERR_ACK_OFFSET);
- }
- }
-}
-
-static void s3c_onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs,
- size_t len, int cmd)
-{
- struct onenand_chip *this = mtd->priv;
- int start, end, start_mem_addr, end_mem_addr;
-
- start = ofs >> this->erase_shift;
- start_mem_addr = onenand->mem_addr(start, 0, 0);
- end = start + (len >> this->erase_shift) - 1;
- end_mem_addr = onenand->mem_addr(end, 0, 0);
-
- if (cmd == ONENAND_CMD_LOCK) {
- s3c_write_cmd(ONENAND_LOCK_START, CMD_MAP_10(onenand,
- start_mem_addr));
- s3c_write_cmd(ONENAND_LOCK_END, CMD_MAP_10(onenand,
- end_mem_addr));
- } else {
- s3c_write_cmd(ONENAND_UNLOCK_START, CMD_MAP_10(onenand,
- start_mem_addr));
- s3c_write_cmd(ONENAND_UNLOCK_END, CMD_MAP_10(onenand,
- end_mem_addr));
- }
-
- this->wait(mtd, FL_LOCKING);
-}
-
-static void s3c_unlock_all(struct mtd_info *mtd)
-{
- struct onenand_chip *this = mtd->priv;
- loff_t ofs = 0;
- size_t len = this->chipsize;
-
- if (this->options & ONENAND_HAS_UNLOCK_ALL) {
- /* Write unlock command */
- this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0);
-
- /* No need to check return value */
- this->wait(mtd, FL_LOCKING);
-
- /* Workaround for all block unlock in DDP */
- if (!ONENAND_IS_DDP(this)) {
- s3c_onenand_check_lock_status(mtd);
- return;
- }
-
- /* All blocks on another chip */
- ofs = this->chipsize >> 1;
- len = this->chipsize >> 1;
- }
-
- s3c_onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK);
-
- s3c_onenand_check_lock_status(mtd);
-}
-
-static void s3c_onenand_setup(struct mtd_info *mtd)
-{
- struct onenand_chip *this = mtd->priv;
-
- onenand->mtd = mtd;
-
- if (onenand->type == TYPE_S3C6400) {
- onenand->mem_addr = s3c6400_mem_addr;
- onenand->cmd_map = s3c64xx_cmd_map;
- } else if (onenand->type == TYPE_S3C6410) {
- onenand->mem_addr = s3c6410_mem_addr;
- onenand->cmd_map = s3c64xx_cmd_map;
- } else if (onenand->type == TYPE_S5PC110) {
- /* Use generic onenand functions */
- this->read_bufferram = s5pc110_read_bufferram;
- this->chip_probe = s5pc110_chip_probe;
- return;
- } else {
- BUG();
- }
-
- this->read_word = s3c_onenand_readw;
- this->write_word = s3c_onenand_writew;
-
- this->wait = s3c_onenand_wait;
- this->bbt_wait = s3c_onenand_bbt_wait;
- this->unlock_all = s3c_unlock_all;
- this->command = s3c_onenand_command;
-
- this->read_bufferram = onenand_read_bufferram;
- this->write_bufferram = onenand_write_bufferram;
-}
-
-static int s3c_onenand_probe(struct platform_device *pdev)
-{
- struct onenand_platform_data *pdata;
- struct onenand_chip *this;
- struct mtd_info *mtd;
- struct resource *r;
- int size, err;
-
- pdata = dev_get_platdata(&pdev->dev);
- /* No need to check pdata. the platform data is optional */
-
- size = sizeof(struct mtd_info) + sizeof(struct onenand_chip);
- mtd = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
- if (!mtd)
- return -ENOMEM;
-
- onenand = devm_kzalloc(&pdev->dev, sizeof(struct s3c_onenand),
- GFP_KERNEL);
- if (!onenand)
- return -ENOMEM;
-
- this = (struct onenand_chip *) &mtd[1];
- mtd->priv = this;
- mtd->dev.parent = &pdev->dev;
- onenand->pdev = pdev;
- onenand->type = platform_get_device_id(pdev)->driver_data;
-
- s3c_onenand_setup(mtd);
-
- r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- onenand->base = devm_ioremap_resource(&pdev->dev, r);
- if (IS_ERR(onenand->base))
- return PTR_ERR(onenand->base);
-
- onenand->phys_base = r->start;
-
- /* Set onenand_chip also */
- this->base = onenand->base;
-
- /* Use runtime badblock check */
- this->options |= ONENAND_SKIP_UNLOCK_CHECK;
-
- if (onenand->type != TYPE_S5PC110) {
- r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
- onenand->ahb_addr = devm_ioremap_resource(&pdev->dev, r);
- if (IS_ERR(onenand->ahb_addr))
- return PTR_ERR(onenand->ahb_addr);
-
- /* Allocate 4KiB BufferRAM */
- onenand->page_buf = devm_kzalloc(&pdev->dev, SZ_4K,
- GFP_KERNEL);
- if (!onenand->page_buf)
- return -ENOMEM;
-
- /* Allocate 128 SpareRAM */
- onenand->oob_buf = devm_kzalloc(&pdev->dev, 128, GFP_KERNEL);
- if (!onenand->oob_buf)
- return -ENOMEM;
-
- /* S3C doesn't handle subpage write */
- mtd->subpage_sft = 0;
- this->subpagesize = mtd->writesize;
-
- } else { /* S5PC110 */
- r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
- onenand->dma_addr = devm_ioremap_resource(&pdev->dev, r);
- if (IS_ERR(onenand->dma_addr))
- return PTR_ERR(onenand->dma_addr);
-
- s5pc110_dma_ops = s5pc110_dma_poll;
- /* Interrupt support */
- r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (r) {
- init_completion(&onenand->complete);
- s5pc110_dma_ops = s5pc110_dma_irq;
- err = devm_request_irq(&pdev->dev, r->start,
- s5pc110_onenand_irq,
- IRQF_SHARED, "onenand",
- &onenand);
- if (err) {
- dev_err(&pdev->dev, "failed to get irq\n");
- return err;
- }
- }
- }
-
- err = onenand_scan(mtd, 1);
- if (err)
- return err;
-
- if (onenand->type != TYPE_S5PC110) {
- /* S3C doesn't handle subpage write */
- mtd->subpage_sft = 0;
- this->subpagesize = mtd->writesize;
- }
-
- if (s3c_read_reg(MEM_CFG_OFFSET) & ONENAND_SYS_CFG1_SYNC_READ)
- dev_info(&onenand->pdev->dev, "OneNAND Sync. Burst Read enabled\n");
-
- err = mtd_device_register(mtd, pdata ? pdata->parts : NULL,
- pdata ? pdata->nr_parts : 0);
- if (err) {
- dev_err(&pdev->dev, "failed to parse partitions and register the MTD device\n");
- onenand_release(mtd);
- return err;
- }
-
- platform_set_drvdata(pdev, mtd);
-
- return 0;
-}
-
-static int s3c_onenand_remove(struct platform_device *pdev)
-{
- struct mtd_info *mtd = platform_get_drvdata(pdev);
-
- onenand_release(mtd);
-
- return 0;
-}
-
-static int s3c_pm_ops_suspend(struct device *dev)
-{
- struct mtd_info *mtd = dev_get_drvdata(dev);
- struct onenand_chip *this = mtd->priv;
-
- this->wait(mtd, FL_PM_SUSPENDED);
- return 0;
-}
-
-static int s3c_pm_ops_resume(struct device *dev)
-{
- struct mtd_info *mtd = dev_get_drvdata(dev);
- struct onenand_chip *this = mtd->priv;
-
- this->unlock_all(mtd);
- return 0;
-}
-
-static const struct dev_pm_ops s3c_pm_ops = {
- .suspend = s3c_pm_ops_suspend,
- .resume = s3c_pm_ops_resume,
-};
-
-static const struct platform_device_id s3c_onenand_driver_ids[] = {
- {
- .name = "s3c6400-onenand",
- .driver_data = TYPE_S3C6400,
- }, {
- .name = "s3c6410-onenand",
- .driver_data = TYPE_S3C6410,
- }, {
- .name = "s5pc110-onenand",
- .driver_data = TYPE_S5PC110,
- }, { },
-};
-MODULE_DEVICE_TABLE(platform, s3c_onenand_driver_ids);
-
-static struct platform_driver s3c_onenand_driver = {
- .driver = {
- .name = "samsung-onenand",
- .pm = &s3c_pm_ops,
- },
- .id_table = s3c_onenand_driver_ids,
- .probe = s3c_onenand_probe,
- .remove = s3c_onenand_remove,
-};
-
-module_platform_driver(s3c_onenand_driver);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
-MODULE_DESCRIPTION("Samsung OneNAND controller support");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung S3C64XX/S5PC1XX OneNAND driver
+ *
+ * Copyright © 2008-2010 Samsung Electronics
+ * Kyungmin Park <kyungmin.park@samsung.com>
+ * Marek Szyprowski <m.szyprowski@samsung.com>
+ *
+ * Implementation:
+ * S3C64XX: emulate the pseudo BufferRAM
+ * S5PC110: use DMA
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/onenand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#include "samsung.h"
+
+enum soc_type {
+ TYPE_S3C6400,
+ TYPE_S3C6410,
+ TYPE_S5PC110,
+};
+
+#define ONENAND_ERASE_STATUS 0x00
+#define ONENAND_MULTI_ERASE_SET 0x01
+#define ONENAND_ERASE_START 0x03
+#define ONENAND_UNLOCK_START 0x08
+#define ONENAND_UNLOCK_END 0x09
+#define ONENAND_LOCK_START 0x0A
+#define ONENAND_LOCK_END 0x0B
+#define ONENAND_LOCK_TIGHT_START 0x0C
+#define ONENAND_LOCK_TIGHT_END 0x0D
+#define ONENAND_UNLOCK_ALL 0x0E
+#define ONENAND_OTP_ACCESS 0x12
+#define ONENAND_SPARE_ACCESS_ONLY 0x13
+#define ONENAND_MAIN_ACCESS_ONLY 0x14
+#define ONENAND_ERASE_VERIFY 0x15
+#define ONENAND_MAIN_SPARE_ACCESS 0x16
+#define ONENAND_PIPELINE_READ 0x4000
+
+#define MAP_00 (0x0)
+#define MAP_01 (0x1)
+#define MAP_10 (0x2)
+#define MAP_11 (0x3)
+
+#define S3C64XX_CMD_MAP_SHIFT 24
+
+#define S3C6400_FBA_SHIFT 10
+#define S3C6400_FPA_SHIFT 4
+#define S3C6400_FSA_SHIFT 2
+
+#define S3C6410_FBA_SHIFT 12
+#define S3C6410_FPA_SHIFT 6
+#define S3C6410_FSA_SHIFT 4
+
+/* S5PC110 specific definitions */
+#define S5PC110_DMA_SRC_ADDR 0x400
+#define S5PC110_DMA_SRC_CFG 0x404
+#define S5PC110_DMA_DST_ADDR 0x408
+#define S5PC110_DMA_DST_CFG 0x40C
+#define S5PC110_DMA_TRANS_SIZE 0x414
+#define S5PC110_DMA_TRANS_CMD 0x418
+#define S5PC110_DMA_TRANS_STATUS 0x41C
+#define S5PC110_DMA_TRANS_DIR 0x420
+#define S5PC110_INTC_DMA_CLR 0x1004
+#define S5PC110_INTC_ONENAND_CLR 0x1008
+#define S5PC110_INTC_DMA_MASK 0x1024
+#define S5PC110_INTC_ONENAND_MASK 0x1028
+#define S5PC110_INTC_DMA_PEND 0x1044
+#define S5PC110_INTC_ONENAND_PEND 0x1048
+#define S5PC110_INTC_DMA_STATUS 0x1064
+#define S5PC110_INTC_ONENAND_STATUS 0x1068
+
+#define S5PC110_INTC_DMA_TD (1 << 24)
+#define S5PC110_INTC_DMA_TE (1 << 16)
+
+#define S5PC110_DMA_CFG_SINGLE (0x0 << 16)
+#define S5PC110_DMA_CFG_4BURST (0x2 << 16)
+#define S5PC110_DMA_CFG_8BURST (0x3 << 16)
+#define S5PC110_DMA_CFG_16BURST (0x4 << 16)
+
+#define S5PC110_DMA_CFG_INC (0x0 << 8)
+#define S5PC110_DMA_CFG_CNT (0x1 << 8)
+
+#define S5PC110_DMA_CFG_8BIT (0x0 << 0)
+#define S5PC110_DMA_CFG_16BIT (0x1 << 0)
+#define S5PC110_DMA_CFG_32BIT (0x2 << 0)
+
+#define S5PC110_DMA_SRC_CFG_READ (S5PC110_DMA_CFG_16BURST | \
+ S5PC110_DMA_CFG_INC | \
+ S5PC110_DMA_CFG_16BIT)
+#define S5PC110_DMA_DST_CFG_READ (S5PC110_DMA_CFG_16BURST | \
+ S5PC110_DMA_CFG_INC | \
+ S5PC110_DMA_CFG_32BIT)
+#define S5PC110_DMA_SRC_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \
+ S5PC110_DMA_CFG_INC | \
+ S5PC110_DMA_CFG_32BIT)
+#define S5PC110_DMA_DST_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \
+ S5PC110_DMA_CFG_INC | \
+ S5PC110_DMA_CFG_16BIT)
+
+#define S5PC110_DMA_TRANS_CMD_TDC (0x1 << 18)
+#define S5PC110_DMA_TRANS_CMD_TEC (0x1 << 16)
+#define S5PC110_DMA_TRANS_CMD_TR (0x1 << 0)
+
+#define S5PC110_DMA_TRANS_STATUS_TD (0x1 << 18)
+#define S5PC110_DMA_TRANS_STATUS_TB (0x1 << 17)
+#define S5PC110_DMA_TRANS_STATUS_TE (0x1 << 16)
+
+#define S5PC110_DMA_DIR_READ 0x0
+#define S5PC110_DMA_DIR_WRITE 0x1
+
+struct s3c_onenand {
+ struct mtd_info *mtd;
+ struct platform_device *pdev;
+ enum soc_type type;
+ void __iomem *base;
+ void __iomem *ahb_addr;
+ int bootram_command;
+ void *page_buf;
+ void *oob_buf;
+ unsigned int (*mem_addr)(int fba, int fpa, int fsa);
+ unsigned int (*cmd_map)(unsigned int type, unsigned int val);
+ void __iomem *dma_addr;
+ unsigned long phys_base;
+ struct completion complete;
+};
+
+#define CMD_MAP_00(dev, addr) (dev->cmd_map(MAP_00, ((addr) << 1)))
+#define CMD_MAP_01(dev, mem_addr) (dev->cmd_map(MAP_01, (mem_addr)))
+#define CMD_MAP_10(dev, mem_addr) (dev->cmd_map(MAP_10, (mem_addr)))
+#define CMD_MAP_11(dev, addr) (dev->cmd_map(MAP_11, ((addr) << 2)))
+
+static struct s3c_onenand *onenand;
+
+static inline int s3c_read_reg(int offset)
+{
+ return readl(onenand->base + offset);
+}
+
+static inline void s3c_write_reg(int value, int offset)
+{
+ writel(value, onenand->base + offset);
+}
+
+static inline int s3c_read_cmd(unsigned int cmd)
+{
+ return readl(onenand->ahb_addr + cmd);
+}
+
+static inline void s3c_write_cmd(int value, unsigned int cmd)
+{
+ writel(value, onenand->ahb_addr + cmd);
+}
+
+#ifdef SAMSUNG_DEBUG
+static void s3c_dump_reg(void)
+{
+ int i;
+
+ for (i = 0; i < 0x400; i += 0x40) {
+ printk(KERN_INFO "0x%08X: 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ (unsigned int) onenand->base + i,
+ s3c_read_reg(i), s3c_read_reg(i + 0x10),
+ s3c_read_reg(i + 0x20), s3c_read_reg(i + 0x30));
+ }
+}
+#endif
+
+static unsigned int s3c64xx_cmd_map(unsigned type, unsigned val)
+{
+ return (type << S3C64XX_CMD_MAP_SHIFT) | val;
+}
+
+static unsigned int s3c6400_mem_addr(int fba, int fpa, int fsa)
+{
+ return (fba << S3C6400_FBA_SHIFT) | (fpa << S3C6400_FPA_SHIFT) |
+ (fsa << S3C6400_FSA_SHIFT);
+}
+
+static unsigned int s3c6410_mem_addr(int fba, int fpa, int fsa)
+{
+ return (fba << S3C6410_FBA_SHIFT) | (fpa << S3C6410_FPA_SHIFT) |
+ (fsa << S3C6410_FSA_SHIFT);
+}
+
+static void s3c_onenand_reset(void)
+{
+ unsigned long timeout = 0x10000;
+ int stat;
+
+ s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET);
+ while (1 && timeout--) {
+ stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ if (stat & RST_CMP)
+ break;
+ }
+ stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
+
+ /* Clear interrupt */
+ s3c_write_reg(0x0, INT_ERR_ACK_OFFSET);
+ /* Clear the ECC status */
+ s3c_write_reg(0x0, ECC_ERR_STAT_OFFSET);
+}
+
+static unsigned short s3c_onenand_readw(void __iomem *addr)
+{
+ struct onenand_chip *this = onenand->mtd->priv;
+ struct device *dev = &onenand->pdev->dev;
+ int reg = addr - this->base;
+ int word_addr = reg >> 1;
+ int value;
+
+ /* It's used for probing time */
+ switch (reg) {
+ case ONENAND_REG_MANUFACTURER_ID:
+ return s3c_read_reg(MANUFACT_ID_OFFSET);
+ case ONENAND_REG_DEVICE_ID:
+ return s3c_read_reg(DEVICE_ID_OFFSET);
+ case ONENAND_REG_VERSION_ID:
+ return s3c_read_reg(FLASH_VER_ID_OFFSET);
+ case ONENAND_REG_DATA_BUFFER_SIZE:
+ return s3c_read_reg(DATA_BUF_SIZE_OFFSET);
+ case ONENAND_REG_TECHNOLOGY:
+ return s3c_read_reg(TECH_OFFSET);
+ case ONENAND_REG_SYS_CFG1:
+ return s3c_read_reg(MEM_CFG_OFFSET);
+
+ /* Used at unlock all status */
+ case ONENAND_REG_CTRL_STATUS:
+ return 0;
+
+ case ONENAND_REG_WP_STATUS:
+ return ONENAND_WP_US;
+
+ default:
+ break;
+ }
+
+ /* BootRAM access control */
+ if ((unsigned int) addr < ONENAND_DATARAM && onenand->bootram_command) {
+ if (word_addr == 0)
+ return s3c_read_reg(MANUFACT_ID_OFFSET);
+ if (word_addr == 1)
+ return s3c_read_reg(DEVICE_ID_OFFSET);
+ if (word_addr == 2)
+ return s3c_read_reg(FLASH_VER_ID_OFFSET);
+ }
+
+ value = s3c_read_cmd(CMD_MAP_11(onenand, word_addr)) & 0xffff;
+ dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__,
+ word_addr, value);
+ return value;
+}
+
+static void s3c_onenand_writew(unsigned short value, void __iomem *addr)
+{
+ struct onenand_chip *this = onenand->mtd->priv;
+ struct device *dev = &onenand->pdev->dev;
+ unsigned int reg = addr - this->base;
+ unsigned int word_addr = reg >> 1;
+
+ /* It's used for probing time */
+ switch (reg) {
+ case ONENAND_REG_SYS_CFG1:
+ s3c_write_reg(value, MEM_CFG_OFFSET);
+ return;
+
+ case ONENAND_REG_START_ADDRESS1:
+ case ONENAND_REG_START_ADDRESS2:
+ return;
+
+ /* Lock/lock-tight/unlock/unlock_all */
+ case ONENAND_REG_START_BLOCK_ADDRESS:
+ return;
+
+ default:
+ break;
+ }
+
+ /* BootRAM access control */
+ if ((unsigned int)addr < ONENAND_DATARAM) {
+ if (value == ONENAND_CMD_READID) {
+ onenand->bootram_command = 1;
+ return;
+ }
+ if (value == ONENAND_CMD_RESET) {
+ s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET);
+ onenand->bootram_command = 0;
+ return;
+ }
+ }
+
+ dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__,
+ word_addr, value);
+
+ s3c_write_cmd(value, CMD_MAP_11(onenand, word_addr));
+}
+
+static int s3c_onenand_wait(struct mtd_info *mtd, int state)
+{
+ struct device *dev = &onenand->pdev->dev;
+ unsigned int flags = INT_ACT;
+ unsigned int stat, ecc;
+ unsigned long timeout;
+
+ switch (state) {
+ case FL_READING:
+ flags |= BLK_RW_CMP | LOAD_CMP;
+ break;
+ case FL_WRITING:
+ flags |= BLK_RW_CMP | PGM_CMP;
+ break;
+ case FL_ERASING:
+ flags |= BLK_RW_CMP | ERS_CMP;
+ break;
+ case FL_LOCKING:
+ flags |= BLK_RW_CMP;
+ break;
+ default:
+ break;
+ }
+
+ /* The 20 msec is enough */
+ timeout = jiffies + msecs_to_jiffies(20);
+ while (time_before(jiffies, timeout)) {
+ stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ if (stat & flags)
+ break;
+
+ if (state != FL_READING)
+ cond_resched();
+ }
+ /* To get correct interrupt status in timeout case */
+ stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
+
+ /*
+ * In the Spec. it checks the controller status first
+ * However if you get the correct information in case of
+ * power off recovery (POR) test, it should read ECC status first
+ */
+ if (stat & LOAD_CMP) {
+ ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET);
+ if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) {
+ dev_info(dev, "%s: ECC error = 0x%04x\n", __func__,
+ ecc);
+ mtd->ecc_stats.failed++;
+ return -EBADMSG;
+ }
+ }
+
+ if (stat & (LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR)) {
+ dev_info(dev, "%s: controller error = 0x%04x\n", __func__,
+ stat);
+ if (stat & LOCKED_BLK)
+ dev_info(dev, "%s: it's locked error = 0x%04x\n",
+ __func__, stat);
+
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int s3c_onenand_command(struct mtd_info *mtd, int cmd, loff_t addr,
+ size_t len)
+{
+ struct onenand_chip *this = mtd->priv;
+ unsigned int *m, *s;
+ int fba, fpa, fsa = 0;
+ unsigned int mem_addr, cmd_map_01, cmd_map_10;
+ int i, mcount, scount;
+ int index;
+
+ fba = (int) (addr >> this->erase_shift);
+ fpa = (int) (addr >> this->page_shift);
+ fpa &= this->page_mask;
+
+ mem_addr = onenand->mem_addr(fba, fpa, fsa);
+ cmd_map_01 = CMD_MAP_01(onenand, mem_addr);
+ cmd_map_10 = CMD_MAP_10(onenand, mem_addr);
+
+ switch (cmd) {
+ case ONENAND_CMD_READ:
+ case ONENAND_CMD_READOOB:
+ case ONENAND_CMD_BUFFERRAM:
+ ONENAND_SET_NEXT_BUFFERRAM(this);
+ default:
+ break;
+ }
+
+ index = ONENAND_CURRENT_BUFFERRAM(this);
+
+ /*
+ * Emulate Two BufferRAMs and access with 4 bytes pointer
+ */
+ m = onenand->page_buf;
+ s = onenand->oob_buf;
+
+ if (index) {
+ m += (this->writesize >> 2);
+ s += (mtd->oobsize >> 2);
+ }
+
+ mcount = mtd->writesize >> 2;
+ scount = mtd->oobsize >> 2;
+
+ switch (cmd) {
+ case ONENAND_CMD_READ:
+ /* Main */
+ for (i = 0; i < mcount; i++)
+ *m++ = s3c_read_cmd(cmd_map_01);
+ return 0;
+
+ case ONENAND_CMD_READOOB:
+ s3c_write_reg(TSRF, TRANS_SPARE_OFFSET);
+ /* Main */
+ for (i = 0; i < mcount; i++)
+ *m++ = s3c_read_cmd(cmd_map_01);
+
+ /* Spare */
+ for (i = 0; i < scount; i++)
+ *s++ = s3c_read_cmd(cmd_map_01);
+
+ s3c_write_reg(0, TRANS_SPARE_OFFSET);
+ return 0;
+
+ case ONENAND_CMD_PROG:
+ /* Main */
+ for (i = 0; i < mcount; i++)
+ s3c_write_cmd(*m++, cmd_map_01);
+ return 0;
+
+ case ONENAND_CMD_PROGOOB:
+ s3c_write_reg(TSRF, TRANS_SPARE_OFFSET);
+
+ /* Main - dummy write */
+ for (i = 0; i < mcount; i++)
+ s3c_write_cmd(0xffffffff, cmd_map_01);
+
+ /* Spare */
+ for (i = 0; i < scount; i++)
+ s3c_write_cmd(*s++, cmd_map_01);
+
+ s3c_write_reg(0, TRANS_SPARE_OFFSET);
+ return 0;
+
+ case ONENAND_CMD_UNLOCK_ALL:
+ s3c_write_cmd(ONENAND_UNLOCK_ALL, cmd_map_10);
+ return 0;
+
+ case ONENAND_CMD_ERASE:
+ s3c_write_cmd(ONENAND_ERASE_START, cmd_map_10);
+ return 0;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static unsigned char *s3c_get_bufferram(struct mtd_info *mtd, int area)
+{
+ struct onenand_chip *this = mtd->priv;
+ int index = ONENAND_CURRENT_BUFFERRAM(this);
+ unsigned char *p;
+
+ if (area == ONENAND_DATARAM) {
+ p = onenand->page_buf;
+ if (index == 1)
+ p += this->writesize;
+ } else {
+ p = onenand->oob_buf;
+ if (index == 1)
+ p += mtd->oobsize;
+ }
+
+ return p;
+}
+
+static int onenand_read_bufferram(struct mtd_info *mtd, int area,
+ unsigned char *buffer, int offset,
+ size_t count)
+{
+ unsigned char *p;
+
+ p = s3c_get_bufferram(mtd, area);
+ memcpy(buffer, p + offset, count);
+ return 0;
+}
+
+static int onenand_write_bufferram(struct mtd_info *mtd, int area,
+ const unsigned char *buffer, int offset,
+ size_t count)
+{
+ unsigned char *p;
+
+ p = s3c_get_bufferram(mtd, area);
+ memcpy(p + offset, buffer, count);
+ return 0;
+}
+
+static int (*s5pc110_dma_ops)(dma_addr_t dst, dma_addr_t src, size_t count, int direction);
+
+static int s5pc110_dma_poll(dma_addr_t dst, dma_addr_t src, size_t count, int direction)
+{
+ void __iomem *base = onenand->dma_addr;
+ int status;
+ unsigned long timeout;
+
+ writel(src, base + S5PC110_DMA_SRC_ADDR);
+ writel(dst, base + S5PC110_DMA_DST_ADDR);
+
+ if (direction == S5PC110_DMA_DIR_READ) {
+ writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG);
+ writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG);
+ } else {
+ writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG);
+ writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG);
+ }
+
+ writel(count, base + S5PC110_DMA_TRANS_SIZE);
+ writel(direction, base + S5PC110_DMA_TRANS_DIR);
+
+ writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD);
+
+ /*
+ * There's no exact timeout values at Spec.
+ * In real case it takes under 1 msec.
+ * So 20 msecs are enough.
+ */
+ timeout = jiffies + msecs_to_jiffies(20);
+
+ do {
+ status = readl(base + S5PC110_DMA_TRANS_STATUS);
+ if (status & S5PC110_DMA_TRANS_STATUS_TE) {
+ writel(S5PC110_DMA_TRANS_CMD_TEC,
+ base + S5PC110_DMA_TRANS_CMD);
+ return -EIO;
+ }
+ } while (!(status & S5PC110_DMA_TRANS_STATUS_TD) &&
+ time_before(jiffies, timeout));
+
+ writel(S5PC110_DMA_TRANS_CMD_TDC, base + S5PC110_DMA_TRANS_CMD);
+
+ return 0;
+}
+
+static irqreturn_t s5pc110_onenand_irq(int irq, void *data)
+{
+ void __iomem *base = onenand->dma_addr;
+ int status, cmd = 0;
+
+ status = readl(base + S5PC110_INTC_DMA_STATUS);
+
+ if (likely(status & S5PC110_INTC_DMA_TD))
+ cmd = S5PC110_DMA_TRANS_CMD_TDC;
+
+ if (unlikely(status & S5PC110_INTC_DMA_TE))
+ cmd = S5PC110_DMA_TRANS_CMD_TEC;
+
+ writel(cmd, base + S5PC110_DMA_TRANS_CMD);
+ writel(status, base + S5PC110_INTC_DMA_CLR);
+
+ if (!onenand->complete.done)
+ complete(&onenand->complete);
+
+ return IRQ_HANDLED;
+}
+
+static int s5pc110_dma_irq(dma_addr_t dst, dma_addr_t src, size_t count, int direction)
+{
+ void __iomem *base = onenand->dma_addr;
+ int status;
+
+ status = readl(base + S5PC110_INTC_DMA_MASK);
+ if (status) {
+ status &= ~(S5PC110_INTC_DMA_TD | S5PC110_INTC_DMA_TE);
+ writel(status, base + S5PC110_INTC_DMA_MASK);
+ }
+
+ writel(src, base + S5PC110_DMA_SRC_ADDR);
+ writel(dst, base + S5PC110_DMA_DST_ADDR);
+
+ if (direction == S5PC110_DMA_DIR_READ) {
+ writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG);
+ writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG);
+ } else {
+ writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG);
+ writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG);
+ }
+
+ writel(count, base + S5PC110_DMA_TRANS_SIZE);
+ writel(direction, base + S5PC110_DMA_TRANS_DIR);
+
+ writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD);
+
+ wait_for_completion_timeout(&onenand->complete, msecs_to_jiffies(20));
+
+ return 0;
+}
+
+static int s5pc110_read_bufferram(struct mtd_info *mtd, int area,
+ unsigned char *buffer, int offset, size_t count)
+{
+ struct onenand_chip *this = mtd->priv;
+ void __iomem *p;
+ void *buf = (void *) buffer;
+ dma_addr_t dma_src, dma_dst;
+ int err, ofs, page_dma = 0;
+ struct device *dev = &onenand->pdev->dev;
+
+ p = this->base + area;
+ if (ONENAND_CURRENT_BUFFERRAM(this)) {
+ if (area == ONENAND_DATARAM)
+ p += this->writesize;
+ else
+ p += mtd->oobsize;
+ }
+
+ if (offset & 3 || (size_t) buf & 3 ||
+ !onenand->dma_addr || count != mtd->writesize)
+ goto normal;
+
+ /* Handle vmalloc address */
+ if (buf >= high_memory) {
+ struct page *page;
+
+ if (((size_t) buf & PAGE_MASK) !=
+ ((size_t) (buf + count - 1) & PAGE_MASK))
+ goto normal;
+ page = vmalloc_to_page(buf);
+ if (!page)
+ goto normal;
+
+ /* Page offset */
+ ofs = ((size_t) buf & ~PAGE_MASK);
+ page_dma = 1;
+
+ /* DMA routine */
+ dma_src = onenand->phys_base + (p - this->base);
+ dma_dst = dma_map_page(dev, page, ofs, count, DMA_FROM_DEVICE);
+ } else {
+ /* DMA routine */
+ dma_src = onenand->phys_base + (p - this->base);
+ dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE);
+ }
+ if (dma_mapping_error(dev, dma_dst)) {
+ dev_err(dev, "Couldn't map a %d byte buffer for DMA\n", count);
+ goto normal;
+ }
+ err = s5pc110_dma_ops(dma_dst, dma_src,
+ count, S5PC110_DMA_DIR_READ);
+
+ if (page_dma)
+ dma_unmap_page(dev, dma_dst, count, DMA_FROM_DEVICE);
+ else
+ dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE);
+
+ if (!err)
+ return 0;
+
+normal:
+ if (count != mtd->writesize) {
+ /* Copy the bufferram to memory to prevent unaligned access */
+ memcpy(this->page_buf, p, mtd->writesize);
+ p = this->page_buf + offset;
+ }
+
+ memcpy(buffer, p, count);
+
+ return 0;
+}
+
+static int s5pc110_chip_probe(struct mtd_info *mtd)
+{
+ /* Now just return 0 */
+ return 0;
+}
+
+static int s3c_onenand_bbt_wait(struct mtd_info *mtd, int state)
+{
+ unsigned int flags = INT_ACT | LOAD_CMP;
+ unsigned int stat;
+ unsigned long timeout;
+
+ /* The 20 msec is enough */
+ timeout = jiffies + msecs_to_jiffies(20);
+ while (time_before(jiffies, timeout)) {
+ stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ if (stat & flags)
+ break;
+ }
+ /* To get correct interrupt status in timeout case */
+ stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
+
+ if (stat & LD_FAIL_ECC_ERR) {
+ s3c_onenand_reset();
+ return ONENAND_BBT_READ_ERROR;
+ }
+
+ if (stat & LOAD_CMP) {
+ int ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET);
+ if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) {
+ s3c_onenand_reset();
+ return ONENAND_BBT_READ_ERROR;
+ }
+ }
+
+ return 0;
+}
+
+static void s3c_onenand_check_lock_status(struct mtd_info *mtd)
+{
+ struct onenand_chip *this = mtd->priv;
+ struct device *dev = &onenand->pdev->dev;
+ unsigned int block, end;
+ int tmp;
+
+ end = this->chipsize >> this->erase_shift;
+
+ for (block = 0; block < end; block++) {
+ unsigned int mem_addr = onenand->mem_addr(block, 0, 0);
+ tmp = s3c_read_cmd(CMD_MAP_01(onenand, mem_addr));
+
+ if (s3c_read_reg(INT_ERR_STAT_OFFSET) & LOCKED_BLK) {
+ dev_err(dev, "block %d is write-protected!\n", block);
+ s3c_write_reg(LOCKED_BLK, INT_ERR_ACK_OFFSET);
+ }
+ }
+}
+
+static void s3c_onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs,
+ size_t len, int cmd)
+{
+ struct onenand_chip *this = mtd->priv;
+ int start, end, start_mem_addr, end_mem_addr;
+
+ start = ofs >> this->erase_shift;
+ start_mem_addr = onenand->mem_addr(start, 0, 0);
+ end = start + (len >> this->erase_shift) - 1;
+ end_mem_addr = onenand->mem_addr(end, 0, 0);
+
+ if (cmd == ONENAND_CMD_LOCK) {
+ s3c_write_cmd(ONENAND_LOCK_START, CMD_MAP_10(onenand,
+ start_mem_addr));
+ s3c_write_cmd(ONENAND_LOCK_END, CMD_MAP_10(onenand,
+ end_mem_addr));
+ } else {
+ s3c_write_cmd(ONENAND_UNLOCK_START, CMD_MAP_10(onenand,
+ start_mem_addr));
+ s3c_write_cmd(ONENAND_UNLOCK_END, CMD_MAP_10(onenand,
+ end_mem_addr));
+ }
+
+ this->wait(mtd, FL_LOCKING);
+}
+
+static void s3c_unlock_all(struct mtd_info *mtd)
+{
+ struct onenand_chip *this = mtd->priv;
+ loff_t ofs = 0;
+ size_t len = this->chipsize;
+
+ if (this->options & ONENAND_HAS_UNLOCK_ALL) {
+ /* Write unlock command */
+ this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0);
+
+ /* No need to check return value */
+ this->wait(mtd, FL_LOCKING);
+
+ /* Workaround for all block unlock in DDP */
+ if (!ONENAND_IS_DDP(this)) {
+ s3c_onenand_check_lock_status(mtd);
+ return;
+ }
+
+ /* All blocks on another chip */
+ ofs = this->chipsize >> 1;
+ len = this->chipsize >> 1;
+ }
+
+ s3c_onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK);
+
+ s3c_onenand_check_lock_status(mtd);
+}
+
+static void s3c_onenand_setup(struct mtd_info *mtd)
+{
+ struct onenand_chip *this = mtd->priv;
+
+ onenand->mtd = mtd;
+
+ if (onenand->type == TYPE_S3C6400) {
+ onenand->mem_addr = s3c6400_mem_addr;
+ onenand->cmd_map = s3c64xx_cmd_map;
+ } else if (onenand->type == TYPE_S3C6410) {
+ onenand->mem_addr = s3c6410_mem_addr;
+ onenand->cmd_map = s3c64xx_cmd_map;
+ } else if (onenand->type == TYPE_S5PC110) {
+ /* Use generic onenand functions */
+ this->read_bufferram = s5pc110_read_bufferram;
+ this->chip_probe = s5pc110_chip_probe;
+ return;
+ } else {
+ BUG();
+ }
+
+ this->read_word = s3c_onenand_readw;
+ this->write_word = s3c_onenand_writew;
+
+ this->wait = s3c_onenand_wait;
+ this->bbt_wait = s3c_onenand_bbt_wait;
+ this->unlock_all = s3c_unlock_all;
+ this->command = s3c_onenand_command;
+
+ this->read_bufferram = onenand_read_bufferram;
+ this->write_bufferram = onenand_write_bufferram;
+}
+
+static int s3c_onenand_probe(struct platform_device *pdev)
+{
+ struct onenand_platform_data *pdata;
+ struct onenand_chip *this;
+ struct mtd_info *mtd;
+ struct resource *r;
+ int size, err;
+
+ pdata = dev_get_platdata(&pdev->dev);
+ /* No need to check pdata. the platform data is optional */
+
+ size = sizeof(struct mtd_info) + sizeof(struct onenand_chip);
+ mtd = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!mtd)
+ return -ENOMEM;
+
+ onenand = devm_kzalloc(&pdev->dev, sizeof(struct s3c_onenand),
+ GFP_KERNEL);
+ if (!onenand)
+ return -ENOMEM;
+
+ this = (struct onenand_chip *) &mtd[1];
+ mtd->priv = this;
+ mtd->dev.parent = &pdev->dev;
+ onenand->pdev = pdev;
+ onenand->type = platform_get_device_id(pdev)->driver_data;
+
+ s3c_onenand_setup(mtd);
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ onenand->base = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(onenand->base))
+ return PTR_ERR(onenand->base);
+
+ onenand->phys_base = r->start;
+
+ /* Set onenand_chip also */
+ this->base = onenand->base;
+
+ /* Use runtime badblock check */
+ this->options |= ONENAND_SKIP_UNLOCK_CHECK;
+
+ if (onenand->type != TYPE_S5PC110) {
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ onenand->ahb_addr = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(onenand->ahb_addr))
+ return PTR_ERR(onenand->ahb_addr);
+
+ /* Allocate 4KiB BufferRAM */
+ onenand->page_buf = devm_kzalloc(&pdev->dev, SZ_4K,
+ GFP_KERNEL);
+ if (!onenand->page_buf)
+ return -ENOMEM;
+
+ /* Allocate 128 SpareRAM */
+ onenand->oob_buf = devm_kzalloc(&pdev->dev, 128, GFP_KERNEL);
+ if (!onenand->oob_buf)
+ return -ENOMEM;
+
+ /* S3C doesn't handle subpage write */
+ mtd->subpage_sft = 0;
+ this->subpagesize = mtd->writesize;
+
+ } else { /* S5PC110 */
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ onenand->dma_addr = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(onenand->dma_addr))
+ return PTR_ERR(onenand->dma_addr);
+
+ s5pc110_dma_ops = s5pc110_dma_poll;
+ /* Interrupt support */
+ r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (r) {
+ init_completion(&onenand->complete);
+ s5pc110_dma_ops = s5pc110_dma_irq;
+ err = devm_request_irq(&pdev->dev, r->start,
+ s5pc110_onenand_irq,
+ IRQF_SHARED, "onenand",
+ &onenand);
+ if (err) {
+ dev_err(&pdev->dev, "failed to get irq\n");
+ return err;
+ }
+ }
+ }
+
+ err = onenand_scan(mtd, 1);
+ if (err)
+ return err;
+
+ if (onenand->type != TYPE_S5PC110) {
+ /* S3C doesn't handle subpage write */
+ mtd->subpage_sft = 0;
+ this->subpagesize = mtd->writesize;
+ }
+
+ if (s3c_read_reg(MEM_CFG_OFFSET) & ONENAND_SYS_CFG1_SYNC_READ)
+ dev_info(&onenand->pdev->dev, "OneNAND Sync. Burst Read enabled\n");
+
+ err = mtd_device_register(mtd, pdata ? pdata->parts : NULL,
+ pdata ? pdata->nr_parts : 0);
+ if (err) {
+ dev_err(&pdev->dev, "failed to parse partitions and register the MTD device\n");
+ onenand_release(mtd);
+ return err;
+ }
+
+ platform_set_drvdata(pdev, mtd);
+
+ return 0;
+}
+
+static int s3c_onenand_remove(struct platform_device *pdev)
+{
+ struct mtd_info *mtd = platform_get_drvdata(pdev);
+
+ onenand_release(mtd);
+
+ return 0;
+}
+
+static int s3c_pm_ops_suspend(struct device *dev)
+{
+ struct mtd_info *mtd = dev_get_drvdata(dev);
+ struct onenand_chip *this = mtd->priv;
+
+ this->wait(mtd, FL_PM_SUSPENDED);
+ return 0;
+}
+
+static int s3c_pm_ops_resume(struct device *dev)
+{
+ struct mtd_info *mtd = dev_get_drvdata(dev);
+ struct onenand_chip *this = mtd->priv;
+
+ this->unlock_all(mtd);
+ return 0;
+}
+
+static const struct dev_pm_ops s3c_pm_ops = {
+ .suspend = s3c_pm_ops_suspend,
+ .resume = s3c_pm_ops_resume,
+};
+
+static const struct platform_device_id s3c_onenand_driver_ids[] = {
+ {
+ .name = "s3c6400-onenand",
+ .driver_data = TYPE_S3C6400,
+ }, {
+ .name = "s3c6410-onenand",
+ .driver_data = TYPE_S3C6410,
+ }, {
+ .name = "s5pc110-onenand",
+ .driver_data = TYPE_S5PC110,
+ }, { },
+};
+MODULE_DEVICE_TABLE(platform, s3c_onenand_driver_ids);
+
+static struct platform_driver s3c_onenand_driver = {
+ .driver = {
+ .name = "samsung-onenand",
+ .pm = &s3c_pm_ops,
+ },
+ .id_table = s3c_onenand_driver_ids,
+ .probe = s3c_onenand_probe,
+ .remove = s3c_onenand_remove,
+};
+
+module_platform_driver(s3c_onenand_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
+MODULE_DESCRIPTION("Samsung OneNAND controller support");
obj-$(CONFIG_SERIAL_PNX8XXX) += pnx8xxx_uart.o
obj-$(CONFIG_SERIAL_SA1100) += sa1100.o
obj-$(CONFIG_SERIAL_BCM63XX) += bcm63xx_uart.o
-obj-$(CONFIG_SERIAL_SAMSUNG) += samsung.o
+obj-$(CONFIG_SERIAL_SAMSUNG) += samsung_tty.o
obj-$(CONFIG_SERIAL_MAX3100) += max3100.o
obj-$(CONFIG_SERIAL_MAX310X) += max310x.o
obj-$(CONFIG_SERIAL_IP22_ZILOG) += ip22zilog.o
+++ /dev/null
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Driver core for Samsung SoC onboard UARTs.
- *
- * Ben Dooks, Copyright (c) 2003-2008 Simtec Electronics
- * http://armlinux.simtec.co.uk/
-*/
-
-/* Hote on 2410 error handling
- *
- * The s3c2410 manual has a love/hate affair with the contents of the
- * UERSTAT register in the UART blocks, and keeps marking some of the
- * error bits as reserved. Having checked with the s3c2410x01,
- * it copes with BREAKs properly, so I am happy to ignore the RESERVED
- * feature from the latter versions of the manual.
- *
- * If it becomes aparrent that latter versions of the 2410 remove these
- * bits, then action will have to be taken to differentiate the versions
- * and change the policy on BREAK
- *
- * BJD, 04-Nov-2004
-*/
-
-#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
-#define SUPPORT_SYSRQ
-#endif
-
-#include <linux/dmaengine.h>
-#include <linux/dma-mapping.h>
-#include <linux/slab.h>
-#include <linux/module.h>
-#include <linux/ioport.h>
-#include <linux/io.h>
-#include <linux/platform_device.h>
-#include <linux/init.h>
-#include <linux/sysrq.h>
-#include <linux/console.h>
-#include <linux/tty.h>
-#include <linux/tty_flip.h>
-#include <linux/serial_core.h>
-#include <linux/serial.h>
-#include <linux/serial_s3c.h>
-#include <linux/delay.h>
-#include <linux/clk.h>
-#include <linux/cpufreq.h>
-#include <linux/of.h>
-
-#include <asm/irq.h>
-
-#include "samsung.h"
-
-#if defined(CONFIG_SERIAL_SAMSUNG_DEBUG) && \
- !defined(MODULE)
-
-extern void printascii(const char *);
-
-__printf(1, 2)
-static void dbg(const char *fmt, ...)
-{
- va_list va;
- char buff[256];
-
- va_start(va, fmt);
- vscnprintf(buff, sizeof(buff), fmt, va);
- va_end(va);
-
- printascii(buff);
-}
-
-#else
-#define dbg(fmt, ...) do { if (0) no_printk(fmt, ##__VA_ARGS__); } while (0)
-#endif
-
-/* UART name and device definitions */
-
-#define S3C24XX_SERIAL_NAME "ttySAC"
-#define S3C24XX_SERIAL_MAJOR 204
-#define S3C24XX_SERIAL_MINOR 64
-
-#define S3C24XX_TX_PIO 1
-#define S3C24XX_TX_DMA 2
-#define S3C24XX_RX_PIO 1
-#define S3C24XX_RX_DMA 2
-/* macros to change one thing to another */
-
-#define tx_enabled(port) ((port)->unused[0])
-#define rx_enabled(port) ((port)->unused[1])
-
-/* flag to ignore all characters coming in */
-#define RXSTAT_DUMMY_READ (0x10000000)
-
-static inline struct s3c24xx_uart_port *to_ourport(struct uart_port *port)
-{
- return container_of(port, struct s3c24xx_uart_port, port);
-}
-
-/* translate a port to the device name */
-
-static inline const char *s3c24xx_serial_portname(struct uart_port *port)
-{
- return to_platform_device(port->dev)->name;
-}
-
-static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
-{
- return rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE;
-}
-
-/*
- * s3c64xx and later SoC's include the interrupt mask and status registers in
- * the controller itself, unlike the s3c24xx SoC's which have these registers
- * in the interrupt controller. Check if the port type is s3c64xx or higher.
- */
-static int s3c24xx_serial_has_interrupt_mask(struct uart_port *port)
-{
- return to_ourport(port)->info->type == PORT_S3C6400;
-}
-
-static void s3c24xx_serial_rx_enable(struct uart_port *port)
-{
- unsigned long flags;
- unsigned int ucon, ufcon;
- int count = 10000;
-
- spin_lock_irqsave(&port->lock, flags);
-
- while (--count && !s3c24xx_serial_txempty_nofifo(port))
- udelay(100);
-
- ufcon = rd_regl(port, S3C2410_UFCON);
- ufcon |= S3C2410_UFCON_RESETRX;
- wr_regl(port, S3C2410_UFCON, ufcon);
-
- ucon = rd_regl(port, S3C2410_UCON);
- ucon |= S3C2410_UCON_RXIRQMODE;
- wr_regl(port, S3C2410_UCON, ucon);
-
- rx_enabled(port) = 1;
- spin_unlock_irqrestore(&port->lock, flags);
-}
-
-static void s3c24xx_serial_rx_disable(struct uart_port *port)
-{
- unsigned long flags;
- unsigned int ucon;
-
- spin_lock_irqsave(&port->lock, flags);
-
- ucon = rd_regl(port, S3C2410_UCON);
- ucon &= ~S3C2410_UCON_RXIRQMODE;
- wr_regl(port, S3C2410_UCON, ucon);
-
- rx_enabled(port) = 0;
- spin_unlock_irqrestore(&port->lock, flags);
-}
-
-static void s3c24xx_serial_stop_tx(struct uart_port *port)
-{
- struct s3c24xx_uart_port *ourport = to_ourport(port);
- struct s3c24xx_uart_dma *dma = ourport->dma;
- struct circ_buf *xmit = &port->state->xmit;
- struct dma_tx_state state;
- int count;
-
- if (!tx_enabled(port))
- return;
-
- if (s3c24xx_serial_has_interrupt_mask(port))
- s3c24xx_set_bit(port, S3C64XX_UINTM_TXD, S3C64XX_UINTM);
- else
- disable_irq_nosync(ourport->tx_irq);
-
- if (dma && dma->tx_chan && ourport->tx_in_progress == S3C24XX_TX_DMA) {
- dmaengine_pause(dma->tx_chan);
- dmaengine_tx_status(dma->tx_chan, dma->tx_cookie, &state);
- dmaengine_terminate_all(dma->tx_chan);
- dma_sync_single_for_cpu(ourport->port.dev,
- dma->tx_transfer_addr, dma->tx_size, DMA_TO_DEVICE);
- async_tx_ack(dma->tx_desc);
- count = dma->tx_bytes_requested - state.residue;
- xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
- port->icount.tx += count;
- }
-
- tx_enabled(port) = 0;
- ourport->tx_in_progress = 0;
-
- if (port->flags & UPF_CONS_FLOW)
- s3c24xx_serial_rx_enable(port);
-
- ourport->tx_mode = 0;
-}
-
-static void s3c24xx_serial_start_next_tx(struct s3c24xx_uart_port *ourport);
-
-static void s3c24xx_serial_tx_dma_complete(void *args)
-{
- struct s3c24xx_uart_port *ourport = args;
- struct uart_port *port = &ourport->port;
- struct circ_buf *xmit = &port->state->xmit;
- struct s3c24xx_uart_dma *dma = ourport->dma;
- struct dma_tx_state state;
- unsigned long flags;
- int count;
-
-
- dmaengine_tx_status(dma->tx_chan, dma->tx_cookie, &state);
- count = dma->tx_bytes_requested - state.residue;
- async_tx_ack(dma->tx_desc);
-
- dma_sync_single_for_cpu(ourport->port.dev, dma->tx_transfer_addr,
- dma->tx_size, DMA_TO_DEVICE);
-
- spin_lock_irqsave(&port->lock, flags);
-
- xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
- port->icount.tx += count;
- ourport->tx_in_progress = 0;
-
- if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
- uart_write_wakeup(port);
-
- s3c24xx_serial_start_next_tx(ourport);
- spin_unlock_irqrestore(&port->lock, flags);
-}
-
-static void enable_tx_dma(struct s3c24xx_uart_port *ourport)
-{
- struct uart_port *port = &ourport->port;
- u32 ucon;
-
- /* Mask Tx interrupt */
- if (s3c24xx_serial_has_interrupt_mask(port))
- s3c24xx_set_bit(port, S3C64XX_UINTM_TXD, S3C64XX_UINTM);
- else
- disable_irq_nosync(ourport->tx_irq);
-
- /* Enable tx dma mode */
- ucon = rd_regl(port, S3C2410_UCON);
- ucon &= ~(S3C64XX_UCON_TXBURST_MASK | S3C64XX_UCON_TXMODE_MASK);
- ucon |= (dma_get_cache_alignment() >= 16) ?
- S3C64XX_UCON_TXBURST_16 : S3C64XX_UCON_TXBURST_1;
- ucon |= S3C64XX_UCON_TXMODE_DMA;
- wr_regl(port, S3C2410_UCON, ucon);
-
- ourport->tx_mode = S3C24XX_TX_DMA;
-}
-
-static void enable_tx_pio(struct s3c24xx_uart_port *ourport)
-{
- struct uart_port *port = &ourport->port;
- u32 ucon, ufcon;
-
- /* Set ufcon txtrig */
- ourport->tx_in_progress = S3C24XX_TX_PIO;
- ufcon = rd_regl(port, S3C2410_UFCON);
- wr_regl(port, S3C2410_UFCON, ufcon);
-
- /* Enable tx pio mode */
- ucon = rd_regl(port, S3C2410_UCON);
- ucon &= ~(S3C64XX_UCON_TXMODE_MASK);
- ucon |= S3C64XX_UCON_TXMODE_CPU;
- wr_regl(port, S3C2410_UCON, ucon);
-
- /* Unmask Tx interrupt */
- if (s3c24xx_serial_has_interrupt_mask(port))
- s3c24xx_clear_bit(port, S3C64XX_UINTM_TXD,
- S3C64XX_UINTM);
- else
- enable_irq(ourport->tx_irq);
-
- ourport->tx_mode = S3C24XX_TX_PIO;
-}
-
-static void s3c24xx_serial_start_tx_pio(struct s3c24xx_uart_port *ourport)
-{
- if (ourport->tx_mode != S3C24XX_TX_PIO)
- enable_tx_pio(ourport);
-}
-
-static int s3c24xx_serial_start_tx_dma(struct s3c24xx_uart_port *ourport,
- unsigned int count)
-{
- struct uart_port *port = &ourport->port;
- struct circ_buf *xmit = &port->state->xmit;
- struct s3c24xx_uart_dma *dma = ourport->dma;
-
-
- if (ourport->tx_mode != S3C24XX_TX_DMA)
- enable_tx_dma(ourport);
-
- dma->tx_size = count & ~(dma_get_cache_alignment() - 1);
- dma->tx_transfer_addr = dma->tx_addr + xmit->tail;
-
- dma_sync_single_for_device(ourport->port.dev, dma->tx_transfer_addr,
- dma->tx_size, DMA_TO_DEVICE);
-
- dma->tx_desc = dmaengine_prep_slave_single(dma->tx_chan,
- dma->tx_transfer_addr, dma->tx_size,
- DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
- if (!dma->tx_desc) {
- dev_err(ourport->port.dev, "Unable to get desc for Tx\n");
- return -EIO;
- }
-
- dma->tx_desc->callback = s3c24xx_serial_tx_dma_complete;
- dma->tx_desc->callback_param = ourport;
- dma->tx_bytes_requested = dma->tx_size;
-
- ourport->tx_in_progress = S3C24XX_TX_DMA;
- dma->tx_cookie = dmaengine_submit(dma->tx_desc);
- dma_async_issue_pending(dma->tx_chan);
- return 0;
-}
-
-static void s3c24xx_serial_start_next_tx(struct s3c24xx_uart_port *ourport)
-{
- struct uart_port *port = &ourport->port;
- struct circ_buf *xmit = &port->state->xmit;
- unsigned long count;
-
- /* Get data size up to the end of buffer */
- count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);
-
- if (!count) {
- s3c24xx_serial_stop_tx(port);
- return;
- }
-
- if (!ourport->dma || !ourport->dma->tx_chan ||
- count < ourport->min_dma_size ||
- xmit->tail & (dma_get_cache_alignment() - 1))
- s3c24xx_serial_start_tx_pio(ourport);
- else
- s3c24xx_serial_start_tx_dma(ourport, count);
-}
-
-static void s3c24xx_serial_start_tx(struct uart_port *port)
-{
- struct s3c24xx_uart_port *ourport = to_ourport(port);
- struct circ_buf *xmit = &port->state->xmit;
-
- if (!tx_enabled(port)) {
- if (port->flags & UPF_CONS_FLOW)
- s3c24xx_serial_rx_disable(port);
-
- tx_enabled(port) = 1;
- if (!ourport->dma || !ourport->dma->tx_chan)
- s3c24xx_serial_start_tx_pio(ourport);
- }
-
- if (ourport->dma && ourport->dma->tx_chan) {
- if (!uart_circ_empty(xmit) && !ourport->tx_in_progress)
- s3c24xx_serial_start_next_tx(ourport);
- }
-}
-
-static void s3c24xx_uart_copy_rx_to_tty(struct s3c24xx_uart_port *ourport,
- struct tty_port *tty, int count)
-{
- struct s3c24xx_uart_dma *dma = ourport->dma;
- int copied;
-
- if (!count)
- return;
-
- dma_sync_single_for_cpu(ourport->port.dev, dma->rx_addr,
- dma->rx_size, DMA_FROM_DEVICE);
-
- ourport->port.icount.rx += count;
- if (!tty) {
- dev_err(ourport->port.dev, "No tty port\n");
- return;
- }
- copied = tty_insert_flip_string(tty,
- ((unsigned char *)(ourport->dma->rx_buf)), count);
- if (copied != count) {
- WARN_ON(1);
- dev_err(ourport->port.dev, "RxData copy to tty layer failed\n");
- }
-}
-
-static void s3c24xx_serial_stop_rx(struct uart_port *port)
-{
- struct s3c24xx_uart_port *ourport = to_ourport(port);
- struct s3c24xx_uart_dma *dma = ourport->dma;
- struct tty_port *t = &port->state->port;
- struct dma_tx_state state;
- enum dma_status dma_status;
- unsigned int received;
-
- if (rx_enabled(port)) {
- dbg("s3c24xx_serial_stop_rx: port=%p\n", port);
- if (s3c24xx_serial_has_interrupt_mask(port))
- s3c24xx_set_bit(port, S3C64XX_UINTM_RXD,
- S3C64XX_UINTM);
- else
- disable_irq_nosync(ourport->rx_irq);
- rx_enabled(port) = 0;
- }
- if (dma && dma->rx_chan) {
- dmaengine_pause(dma->tx_chan);
- dma_status = dmaengine_tx_status(dma->rx_chan,
- dma->rx_cookie, &state);
- if (dma_status == DMA_IN_PROGRESS ||
- dma_status == DMA_PAUSED) {
- received = dma->rx_bytes_requested - state.residue;
- dmaengine_terminate_all(dma->rx_chan);
- s3c24xx_uart_copy_rx_to_tty(ourport, t, received);
- }
- }
-}
-
-static inline struct s3c24xx_uart_info
- *s3c24xx_port_to_info(struct uart_port *port)
-{
- return to_ourport(port)->info;
-}
-
-static inline struct s3c2410_uartcfg
- *s3c24xx_port_to_cfg(struct uart_port *port)
-{
- struct s3c24xx_uart_port *ourport;
-
- if (port->dev == NULL)
- return NULL;
-
- ourport = container_of(port, struct s3c24xx_uart_port, port);
- return ourport->cfg;
-}
-
-static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport,
- unsigned long ufstat)
-{
- struct s3c24xx_uart_info *info = ourport->info;
-
- if (ufstat & info->rx_fifofull)
- return ourport->port.fifosize;
-
- return (ufstat & info->rx_fifomask) >> info->rx_fifoshift;
-}
-
-static void s3c64xx_start_rx_dma(struct s3c24xx_uart_port *ourport);
-static void s3c24xx_serial_rx_dma_complete(void *args)
-{
- struct s3c24xx_uart_port *ourport = args;
- struct uart_port *port = &ourport->port;
-
- struct s3c24xx_uart_dma *dma = ourport->dma;
- struct tty_port *t = &port->state->port;
- struct tty_struct *tty = tty_port_tty_get(&ourport->port.state->port);
-
- struct dma_tx_state state;
- unsigned long flags;
- int received;
-
- dmaengine_tx_status(dma->rx_chan, dma->rx_cookie, &state);
- received = dma->rx_bytes_requested - state.residue;
- async_tx_ack(dma->rx_desc);
-
- spin_lock_irqsave(&port->lock, flags);
-
- if (received)
- s3c24xx_uart_copy_rx_to_tty(ourport, t, received);
-
- if (tty) {
- tty_flip_buffer_push(t);
- tty_kref_put(tty);
- }
-
- s3c64xx_start_rx_dma(ourport);
-
- spin_unlock_irqrestore(&port->lock, flags);
-}
-
-static void s3c64xx_start_rx_dma(struct s3c24xx_uart_port *ourport)
-{
- struct s3c24xx_uart_dma *dma = ourport->dma;
-
- dma_sync_single_for_device(ourport->port.dev, dma->rx_addr,
- dma->rx_size, DMA_FROM_DEVICE);
-
- dma->rx_desc = dmaengine_prep_slave_single(dma->rx_chan,
- dma->rx_addr, dma->rx_size, DMA_DEV_TO_MEM,
- DMA_PREP_INTERRUPT);
- if (!dma->rx_desc) {
- dev_err(ourport->port.dev, "Unable to get desc for Rx\n");
- return;
- }
-
- dma->rx_desc->callback = s3c24xx_serial_rx_dma_complete;
- dma->rx_desc->callback_param = ourport;
- dma->rx_bytes_requested = dma->rx_size;
-
- dma->rx_cookie = dmaengine_submit(dma->rx_desc);
- dma_async_issue_pending(dma->rx_chan);
-}
-
-/* ? - where has parity gone?? */
-#define S3C2410_UERSTAT_PARITY (0x1000)
-
-static void enable_rx_dma(struct s3c24xx_uart_port *ourport)
-{
- struct uart_port *port = &ourport->port;
- unsigned int ucon;
-
- /* set Rx mode to DMA mode */
- ucon = rd_regl(port, S3C2410_UCON);
- ucon &= ~(S3C64XX_UCON_RXBURST_MASK |
- S3C64XX_UCON_TIMEOUT_MASK |
- S3C64XX_UCON_EMPTYINT_EN |
- S3C64XX_UCON_DMASUS_EN |
- S3C64XX_UCON_TIMEOUT_EN |
- S3C64XX_UCON_RXMODE_MASK);
- ucon |= S3C64XX_UCON_RXBURST_16 |
- 0xf << S3C64XX_UCON_TIMEOUT_SHIFT |
- S3C64XX_UCON_EMPTYINT_EN |
- S3C64XX_UCON_TIMEOUT_EN |
- S3C64XX_UCON_RXMODE_DMA;
- wr_regl(port, S3C2410_UCON, ucon);
-
- ourport->rx_mode = S3C24XX_RX_DMA;
-}
-
-static void enable_rx_pio(struct s3c24xx_uart_port *ourport)
-{
- struct uart_port *port = &ourport->port;
- unsigned int ucon;
-
- /* set Rx mode to DMA mode */
- ucon = rd_regl(port, S3C2410_UCON);
- ucon &= ~(S3C64XX_UCON_TIMEOUT_MASK |
- S3C64XX_UCON_EMPTYINT_EN |
- S3C64XX_UCON_DMASUS_EN |
- S3C64XX_UCON_TIMEOUT_EN |
- S3C64XX_UCON_RXMODE_MASK);
- ucon |= 0xf << S3C64XX_UCON_TIMEOUT_SHIFT |
- S3C64XX_UCON_TIMEOUT_EN |
- S3C64XX_UCON_RXMODE_CPU;
- wr_regl(port, S3C2410_UCON, ucon);
-
- ourport->rx_mode = S3C24XX_RX_PIO;
-}
-
-static void s3c24xx_serial_rx_drain_fifo(struct s3c24xx_uart_port *ourport);
-
-static irqreturn_t s3c24xx_serial_rx_chars_dma(void *dev_id)
-{
- unsigned int utrstat, ufstat, received;
- struct s3c24xx_uart_port *ourport = dev_id;
- struct uart_port *port = &ourport->port;
- struct s3c24xx_uart_dma *dma = ourport->dma;
- struct tty_struct *tty = tty_port_tty_get(&ourport->port.state->port);
- struct tty_port *t = &port->state->port;
- unsigned long flags;
- struct dma_tx_state state;
-
- utrstat = rd_regl(port, S3C2410_UTRSTAT);
- ufstat = rd_regl(port, S3C2410_UFSTAT);
-
- spin_lock_irqsave(&port->lock, flags);
-
- if (!(utrstat & S3C2410_UTRSTAT_TIMEOUT)) {
- s3c64xx_start_rx_dma(ourport);
- if (ourport->rx_mode == S3C24XX_RX_PIO)
- enable_rx_dma(ourport);
- goto finish;
- }
-
- if (ourport->rx_mode == S3C24XX_RX_DMA) {
- dmaengine_pause(dma->rx_chan);
- dmaengine_tx_status(dma->rx_chan, dma->rx_cookie, &state);
- dmaengine_terminate_all(dma->rx_chan);
- received = dma->rx_bytes_requested - state.residue;
- s3c24xx_uart_copy_rx_to_tty(ourport, t, received);
-
- enable_rx_pio(ourport);
- }
-
- s3c24xx_serial_rx_drain_fifo(ourport);
-
- if (tty) {
- tty_flip_buffer_push(t);
- tty_kref_put(tty);
- }
-
- wr_regl(port, S3C2410_UTRSTAT, S3C2410_UTRSTAT_TIMEOUT);
-
-finish:
- spin_unlock_irqrestore(&port->lock, flags);
-
- return IRQ_HANDLED;
-}
-
-static void s3c24xx_serial_rx_drain_fifo(struct s3c24xx_uart_port *ourport)
-{
- struct uart_port *port = &ourport->port;
- unsigned int ufcon, ch, flag, ufstat, uerstat;
- unsigned int fifocnt = 0;
- int max_count = port->fifosize;
-
- while (max_count-- > 0) {
- /*
- * Receive all characters known to be in FIFO
- * before reading FIFO level again
- */
- if (fifocnt == 0) {
- ufstat = rd_regl(port, S3C2410_UFSTAT);
- fifocnt = s3c24xx_serial_rx_fifocnt(ourport, ufstat);
- if (fifocnt == 0)
- break;
- }
- fifocnt--;
-
- uerstat = rd_regl(port, S3C2410_UERSTAT);
- ch = rd_regb(port, S3C2410_URXH);
-
- if (port->flags & UPF_CONS_FLOW) {
- int txe = s3c24xx_serial_txempty_nofifo(port);
-
- if (rx_enabled(port)) {
- if (!txe) {
- rx_enabled(port) = 0;
- continue;
- }
- } else {
- if (txe) {
- ufcon = rd_regl(port, S3C2410_UFCON);
- ufcon |= S3C2410_UFCON_RESETRX;
- wr_regl(port, S3C2410_UFCON, ufcon);
- rx_enabled(port) = 1;
- return;
- }
- continue;
- }
- }
-
- /* insert the character into the buffer */
-
- flag = TTY_NORMAL;
- port->icount.rx++;
-
- if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) {
- dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n",
- ch, uerstat);
-
- /* check for break */
- if (uerstat & S3C2410_UERSTAT_BREAK) {
- dbg("break!\n");
- port->icount.brk++;
- if (uart_handle_break(port))
- continue; /* Ignore character */
- }
-
- if (uerstat & S3C2410_UERSTAT_FRAME)
- port->icount.frame++;
- if (uerstat & S3C2410_UERSTAT_OVERRUN)
- port->icount.overrun++;
-
- uerstat &= port->read_status_mask;
-
- if (uerstat & S3C2410_UERSTAT_BREAK)
- flag = TTY_BREAK;
- else if (uerstat & S3C2410_UERSTAT_PARITY)
- flag = TTY_PARITY;
- else if (uerstat & (S3C2410_UERSTAT_FRAME |
- S3C2410_UERSTAT_OVERRUN))
- flag = TTY_FRAME;
- }
-
- if (uart_handle_sysrq_char(port, ch))
- continue; /* Ignore character */
-
- uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,
- ch, flag);
- }
-
- tty_flip_buffer_push(&port->state->port);
-}
-
-static irqreturn_t s3c24xx_serial_rx_chars_pio(void *dev_id)
-{
- struct s3c24xx_uart_port *ourport = dev_id;
- struct uart_port *port = &ourport->port;
- unsigned long flags;
-
- spin_lock_irqsave(&port->lock, flags);
- s3c24xx_serial_rx_drain_fifo(ourport);
- spin_unlock_irqrestore(&port->lock, flags);
-
- return IRQ_HANDLED;
-}
-
-
-static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id)
-{
- struct s3c24xx_uart_port *ourport = dev_id;
-
- if (ourport->dma && ourport->dma->rx_chan)
- return s3c24xx_serial_rx_chars_dma(dev_id);
- return s3c24xx_serial_rx_chars_pio(dev_id);
-}
-
-static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
-{
- struct s3c24xx_uart_port *ourport = id;
- struct uart_port *port = &ourport->port;
- struct circ_buf *xmit = &port->state->xmit;
- unsigned long flags;
- int count, dma_count = 0;
-
- spin_lock_irqsave(&port->lock, flags);
-
- count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);
-
- if (ourport->dma && ourport->dma->tx_chan &&
- count >= ourport->min_dma_size) {
- int align = dma_get_cache_alignment() -
- (xmit->tail & (dma_get_cache_alignment() - 1));
- if (count-align >= ourport->min_dma_size) {
- dma_count = count-align;
- count = align;
- }
- }
-
- if (port->x_char) {
- wr_regb(port, S3C2410_UTXH, port->x_char);
- port->icount.tx++;
- port->x_char = 0;
- goto out;
- }
-
- /* if there isn't anything more to transmit, or the uart is now
- * stopped, disable the uart and exit
- */
-
- if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
- s3c24xx_serial_stop_tx(port);
- goto out;
- }
-
- /* try and drain the buffer... */
-
- if (count > port->fifosize) {
- count = port->fifosize;
- dma_count = 0;
- }
-
- while (!uart_circ_empty(xmit) && count > 0) {
- if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
- break;
-
- wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
- xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
- port->icount.tx++;
- count--;
- }
-
- if (!count && dma_count) {
- s3c24xx_serial_start_tx_dma(ourport, dma_count);
- goto out;
- }
-
- if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) {
- spin_unlock(&port->lock);
- uart_write_wakeup(port);
- spin_lock(&port->lock);
- }
-
- if (uart_circ_empty(xmit))
- s3c24xx_serial_stop_tx(port);
-
-out:
- spin_unlock_irqrestore(&port->lock, flags);
- return IRQ_HANDLED;
-}
-
-/* interrupt handler for s3c64xx and later SoC's.*/
-static irqreturn_t s3c64xx_serial_handle_irq(int irq, void *id)
-{
- struct s3c24xx_uart_port *ourport = id;
- struct uart_port *port = &ourport->port;
- unsigned int pend = rd_regl(port, S3C64XX_UINTP);
- irqreturn_t ret = IRQ_HANDLED;
-
- if (pend & S3C64XX_UINTM_RXD_MSK) {
- ret = s3c24xx_serial_rx_chars(irq, id);
- wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_RXD_MSK);
- }
- if (pend & S3C64XX_UINTM_TXD_MSK) {
- ret = s3c24xx_serial_tx_chars(irq, id);
- wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_TXD_MSK);
- }
- return ret;
-}
-
-static unsigned int s3c24xx_serial_tx_empty(struct uart_port *port)
-{
- struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
- unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT);
- unsigned long ufcon = rd_regl(port, S3C2410_UFCON);
-
- if (ufcon & S3C2410_UFCON_FIFOMODE) {
- if ((ufstat & info->tx_fifomask) != 0 ||
- (ufstat & info->tx_fifofull))
- return 0;
-
- return 1;
- }
-
- return s3c24xx_serial_txempty_nofifo(port);
-}
-
-/* no modem control lines */
-static unsigned int s3c24xx_serial_get_mctrl(struct uart_port *port)
-{
- unsigned int umstat = rd_regb(port, S3C2410_UMSTAT);
-
- if (umstat & S3C2410_UMSTAT_CTS)
- return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
- else
- return TIOCM_CAR | TIOCM_DSR;
-}
-
-static void s3c24xx_serial_set_mctrl(struct uart_port *port, unsigned int mctrl)
-{
- unsigned int umcon = rd_regl(port, S3C2410_UMCON);
-
- if (mctrl & TIOCM_RTS)
- umcon |= S3C2410_UMCOM_RTS_LOW;
- else
- umcon &= ~S3C2410_UMCOM_RTS_LOW;
-
- wr_regl(port, S3C2410_UMCON, umcon);
-}
-
-static void s3c24xx_serial_break_ctl(struct uart_port *port, int break_state)
-{
- unsigned long flags;
- unsigned int ucon;
-
- spin_lock_irqsave(&port->lock, flags);
-
- ucon = rd_regl(port, S3C2410_UCON);
-
- if (break_state)
- ucon |= S3C2410_UCON_SBREAK;
- else
- ucon &= ~S3C2410_UCON_SBREAK;
-
- wr_regl(port, S3C2410_UCON, ucon);
-
- spin_unlock_irqrestore(&port->lock, flags);
-}
-
-static int s3c24xx_serial_request_dma(struct s3c24xx_uart_port *p)
-{
- struct s3c24xx_uart_dma *dma = p->dma;
- struct dma_slave_caps dma_caps;
- const char *reason = NULL;
- int ret;
-
- /* Default slave configuration parameters */
- dma->rx_conf.direction = DMA_DEV_TO_MEM;
- dma->rx_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
- dma->rx_conf.src_addr = p->port.mapbase + S3C2410_URXH;
- dma->rx_conf.src_maxburst = 1;
-
- dma->tx_conf.direction = DMA_MEM_TO_DEV;
- dma->tx_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
- dma->tx_conf.dst_addr = p->port.mapbase + S3C2410_UTXH;
- dma->tx_conf.dst_maxburst = 1;
-
- dma->rx_chan = dma_request_chan(p->port.dev, "rx");
-
- if (IS_ERR(dma->rx_chan)) {
- reason = "DMA RX channel request failed";
- ret = PTR_ERR(dma->rx_chan);
- goto err_warn;
- }
-
- ret = dma_get_slave_caps(dma->rx_chan, &dma_caps);
- if (ret < 0 ||
- dma_caps.residue_granularity < DMA_RESIDUE_GRANULARITY_BURST) {
- reason = "insufficient DMA RX engine capabilities";
- ret = -EOPNOTSUPP;
- goto err_release_rx;
- }
-
- dmaengine_slave_config(dma->rx_chan, &dma->rx_conf);
-
- dma->tx_chan = dma_request_chan(p->port.dev, "tx");
- if (IS_ERR(dma->tx_chan)) {
- reason = "DMA TX channel request failed";
- ret = PTR_ERR(dma->tx_chan);
- goto err_release_rx;
- }
-
- ret = dma_get_slave_caps(dma->tx_chan, &dma_caps);
- if (ret < 0 ||
- dma_caps.residue_granularity < DMA_RESIDUE_GRANULARITY_BURST) {
- reason = "insufficient DMA TX engine capabilities";
- ret = -EOPNOTSUPP;
- goto err_release_tx;
- }
-
- dmaengine_slave_config(dma->tx_chan, &dma->tx_conf);
-
- /* RX buffer */
- dma->rx_size = PAGE_SIZE;
-
- dma->rx_buf = kmalloc(dma->rx_size, GFP_KERNEL);
- if (!dma->rx_buf) {
- ret = -ENOMEM;
- goto err_release_tx;
- }
-
- dma->rx_addr = dma_map_single(p->port.dev, dma->rx_buf,
- dma->rx_size, DMA_FROM_DEVICE);
- if (dma_mapping_error(p->port.dev, dma->rx_addr)) {
- reason = "DMA mapping error for RX buffer";
- ret = -EIO;
- goto err_free_rx;
- }
-
- /* TX buffer */
- dma->tx_addr = dma_map_single(p->port.dev, p->port.state->xmit.buf,
- UART_XMIT_SIZE, DMA_TO_DEVICE);
- if (dma_mapping_error(p->port.dev, dma->tx_addr)) {
- reason = "DMA mapping error for TX buffer";
- ret = -EIO;
- goto err_unmap_rx;
- }
-
- return 0;
-
-err_unmap_rx:
- dma_unmap_single(p->port.dev, dma->rx_addr, dma->rx_size,
- DMA_FROM_DEVICE);
-err_free_rx:
- kfree(dma->rx_buf);
-err_release_tx:
- dma_release_channel(dma->tx_chan);
-err_release_rx:
- dma_release_channel(dma->rx_chan);
-err_warn:
- if (reason)
- dev_warn(p->port.dev, "%s, DMA will not be used\n", reason);
- return ret;
-}
-
-static void s3c24xx_serial_release_dma(struct s3c24xx_uart_port *p)
-{
- struct s3c24xx_uart_dma *dma = p->dma;
-
- if (dma->rx_chan) {
- dmaengine_terminate_all(dma->rx_chan);
- dma_unmap_single(p->port.dev, dma->rx_addr,
- dma->rx_size, DMA_FROM_DEVICE);
- kfree(dma->rx_buf);
- dma_release_channel(dma->rx_chan);
- dma->rx_chan = NULL;
- }
-
- if (dma->tx_chan) {
- dmaengine_terminate_all(dma->tx_chan);
- dma_unmap_single(p->port.dev, dma->tx_addr,
- UART_XMIT_SIZE, DMA_TO_DEVICE);
- dma_release_channel(dma->tx_chan);
- dma->tx_chan = NULL;
- }
-}
-
-static void s3c24xx_serial_shutdown(struct uart_port *port)
-{
- struct s3c24xx_uart_port *ourport = to_ourport(port);
-
- if (ourport->tx_claimed) {
- if (!s3c24xx_serial_has_interrupt_mask(port))
- free_irq(ourport->tx_irq, ourport);
- tx_enabled(port) = 0;
- ourport->tx_claimed = 0;
- ourport->tx_mode = 0;
- }
-
- if (ourport->rx_claimed) {
- if (!s3c24xx_serial_has_interrupt_mask(port))
- free_irq(ourport->rx_irq, ourport);
- ourport->rx_claimed = 0;
- rx_enabled(port) = 0;
- }
-
- /* Clear pending interrupts and mask all interrupts */
- if (s3c24xx_serial_has_interrupt_mask(port)) {
- free_irq(port->irq, ourport);
-
- wr_regl(port, S3C64XX_UINTP, 0xf);
- wr_regl(port, S3C64XX_UINTM, 0xf);
- }
-
- if (ourport->dma)
- s3c24xx_serial_release_dma(ourport);
-
- ourport->tx_in_progress = 0;
-}
-
-static int s3c24xx_serial_startup(struct uart_port *port)
-{
- struct s3c24xx_uart_port *ourport = to_ourport(port);
- int ret;
-
- dbg("s3c24xx_serial_startup: port=%p (%08llx,%p)\n",
- port, (unsigned long long)port->mapbase, port->membase);
-
- rx_enabled(port) = 1;
-
- ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
- s3c24xx_serial_portname(port), ourport);
-
- if (ret != 0) {
- dev_err(port->dev, "cannot get irq %d\n", ourport->rx_irq);
- return ret;
- }
-
- ourport->rx_claimed = 1;
-
- dbg("requesting tx irq...\n");
-
- tx_enabled(port) = 1;
-
- ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
- s3c24xx_serial_portname(port), ourport);
-
- if (ret) {
- dev_err(port->dev, "cannot get irq %d\n", ourport->tx_irq);
- goto err;
- }
-
- ourport->tx_claimed = 1;
-
- dbg("s3c24xx_serial_startup ok\n");
-
- /* the port reset code should have done the correct
- * register setup for the port controls */
-
- return ret;
-
-err:
- s3c24xx_serial_shutdown(port);
- return ret;
-}
-
-static int s3c64xx_serial_startup(struct uart_port *port)
-{
- struct s3c24xx_uart_port *ourport = to_ourport(port);
- unsigned long flags;
- unsigned int ufcon;
- int ret;
-
- dbg("s3c64xx_serial_startup: port=%p (%08llx,%p)\n",
- port, (unsigned long long)port->mapbase, port->membase);
-
- wr_regl(port, S3C64XX_UINTM, 0xf);
- if (ourport->dma) {
- ret = s3c24xx_serial_request_dma(ourport);
- if (ret < 0) {
- devm_kfree(port->dev, ourport->dma);
- ourport->dma = NULL;
- }
- }
-
- ret = request_irq(port->irq, s3c64xx_serial_handle_irq, IRQF_SHARED,
- s3c24xx_serial_portname(port), ourport);
- if (ret) {
- dev_err(port->dev, "cannot get irq %d\n", port->irq);
- return ret;
- }
-
- /* For compatibility with s3c24xx Soc's */
- rx_enabled(port) = 1;
- ourport->rx_claimed = 1;
- tx_enabled(port) = 0;
- ourport->tx_claimed = 1;
-
- spin_lock_irqsave(&port->lock, flags);
-
- ufcon = rd_regl(port, S3C2410_UFCON);
- ufcon |= S3C2410_UFCON_RESETRX | S5PV210_UFCON_RXTRIG8;
- if (!uart_console(port))
- ufcon |= S3C2410_UFCON_RESETTX;
- wr_regl(port, S3C2410_UFCON, ufcon);
-
- enable_rx_pio(ourport);
-
- spin_unlock_irqrestore(&port->lock, flags);
-
- /* Enable Rx Interrupt */
- s3c24xx_clear_bit(port, S3C64XX_UINTM_RXD, S3C64XX_UINTM);
-
- dbg("s3c64xx_serial_startup ok\n");
- return ret;
-}
-
-/* power power management control */
-
-static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level,
- unsigned int old)
-{
- struct s3c24xx_uart_port *ourport = to_ourport(port);
- int timeout = 10000;
-
- ourport->pm_level = level;
-
- switch (level) {
- case 3:
- while (--timeout && !s3c24xx_serial_txempty_nofifo(port))
- udelay(100);
-
- if (!IS_ERR(ourport->baudclk))
- clk_disable_unprepare(ourport->baudclk);
-
- clk_disable_unprepare(ourport->clk);
- break;
-
- case 0:
- clk_prepare_enable(ourport->clk);
-
- if (!IS_ERR(ourport->baudclk))
- clk_prepare_enable(ourport->baudclk);
-
- break;
- default:
- dev_err(port->dev, "s3c24xx_serial: unknown pm %d\n", level);
- }
-}
-
-/* baud rate calculation
- *
- * The UARTs on the S3C2410/S3C2440 can take their clocks from a number
- * of different sources, including the peripheral clock ("pclk") and an
- * external clock ("uclk"). The S3C2440 also adds the core clock ("fclk")
- * with a programmable extra divisor.
- *
- * The following code goes through the clock sources, and calculates the
- * baud clocks (and the resultant actual baud rates) and then tries to
- * pick the closest one and select that.
- *
-*/
-
-#define MAX_CLK_NAME_LENGTH 15
-
-static inline int s3c24xx_serial_getsource(struct uart_port *port)
-{
- struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
- unsigned int ucon;
-
- if (info->num_clks == 1)
- return 0;
-
- ucon = rd_regl(port, S3C2410_UCON);
- ucon &= info->clksel_mask;
- return ucon >> info->clksel_shift;
-}
-
-static void s3c24xx_serial_setsource(struct uart_port *port,
- unsigned int clk_sel)
-{
- struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
- unsigned int ucon;
-
- if (info->num_clks == 1)
- return;
-
- ucon = rd_regl(port, S3C2410_UCON);
- if ((ucon & info->clksel_mask) >> info->clksel_shift == clk_sel)
- return;
-
- ucon &= ~info->clksel_mask;
- ucon |= clk_sel << info->clksel_shift;
- wr_regl(port, S3C2410_UCON, ucon);
-}
-
-static unsigned int s3c24xx_serial_getclk(struct s3c24xx_uart_port *ourport,
- unsigned int req_baud, struct clk **best_clk,
- unsigned int *clk_num)
-{
- struct s3c24xx_uart_info *info = ourport->info;
- struct clk *clk;
- unsigned long rate;
- unsigned int cnt, baud, quot, clk_sel, best_quot = 0;
- char clkname[MAX_CLK_NAME_LENGTH];
- int calc_deviation, deviation = (1 << 30) - 1;
-
- clk_sel = (ourport->cfg->clk_sel) ? ourport->cfg->clk_sel :
- ourport->info->def_clk_sel;
- for (cnt = 0; cnt < info->num_clks; cnt++) {
- if (!(clk_sel & (1 << cnt)))
- continue;
-
- sprintf(clkname, "clk_uart_baud%d", cnt);
- clk = clk_get(ourport->port.dev, clkname);
- if (IS_ERR(clk))
- continue;
-
- rate = clk_get_rate(clk);
- if (!rate)
- continue;
-
- if (ourport->info->has_divslot) {
- unsigned long div = rate / req_baud;
-
- /* The UDIVSLOT register on the newer UARTs allows us to
- * get a divisor adjustment of 1/16th on the baud clock.
- *
- * We don't keep the UDIVSLOT value (the 16ths we
- * calculated by not multiplying the baud by 16) as it
- * is easy enough to recalculate.
- */
-
- quot = div / 16;
- baud = rate / div;
- } else {
- quot = (rate + (8 * req_baud)) / (16 * req_baud);
- baud = rate / (quot * 16);
- }
- quot--;
-
- calc_deviation = req_baud - baud;
- if (calc_deviation < 0)
- calc_deviation = -calc_deviation;
-
- if (calc_deviation < deviation) {
- *best_clk = clk;
- best_quot = quot;
- *clk_num = cnt;
- deviation = calc_deviation;
- }
- }
-
- return best_quot;
-}
-
-/* udivslot_table[]
- *
- * This table takes the fractional value of the baud divisor and gives
- * the recommended setting for the UDIVSLOT register.
- */
-static u16 udivslot_table[16] = {
- [0] = 0x0000,
- [1] = 0x0080,
- [2] = 0x0808,
- [3] = 0x0888,
- [4] = 0x2222,
- [5] = 0x4924,
- [6] = 0x4A52,
- [7] = 0x54AA,
- [8] = 0x5555,
- [9] = 0xD555,
- [10] = 0xD5D5,
- [11] = 0xDDD5,
- [12] = 0xDDDD,
- [13] = 0xDFDD,
- [14] = 0xDFDF,
- [15] = 0xFFDF,
-};
-
-static void s3c24xx_serial_set_termios(struct uart_port *port,
- struct ktermios *termios,
- struct ktermios *old)
-{
- struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
- struct s3c24xx_uart_port *ourport = to_ourport(port);
- struct clk *clk = ERR_PTR(-EINVAL);
- unsigned long flags;
- unsigned int baud, quot, clk_sel = 0;
- unsigned int ulcon;
- unsigned int umcon;
- unsigned int udivslot = 0;
-
- /*
- * We don't support modem control lines.
- */
- termios->c_cflag &= ~(HUPCL | CMSPAR);
- termios->c_cflag |= CLOCAL;
-
- /*
- * Ask the core to calculate the divisor for us.
- */
-
- baud = uart_get_baud_rate(port, termios, old, 0, 3000000);
- quot = s3c24xx_serial_getclk(ourport, baud, &clk, &clk_sel);
- if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST)
- quot = port->custom_divisor;
- if (IS_ERR(clk))
- return;
-
- /* check to see if we need to change clock source */
-
- if (ourport->baudclk != clk) {
- clk_prepare_enable(clk);
-
- s3c24xx_serial_setsource(port, clk_sel);
-
- if (!IS_ERR(ourport->baudclk)) {
- clk_disable_unprepare(ourport->baudclk);
- ourport->baudclk = ERR_PTR(-EINVAL);
- }
-
- ourport->baudclk = clk;
- ourport->baudclk_rate = clk ? clk_get_rate(clk) : 0;
- }
-
- if (ourport->info->has_divslot) {
- unsigned int div = ourport->baudclk_rate / baud;
-
- if (cfg->has_fracval) {
- udivslot = (div & 15);
- dbg("fracval = %04x\n", udivslot);
- } else {
- udivslot = udivslot_table[div & 15];
- dbg("udivslot = %04x (div %d)\n", udivslot, div & 15);
- }
- }
-
- switch (termios->c_cflag & CSIZE) {
- case CS5:
- dbg("config: 5bits/char\n");
- ulcon = S3C2410_LCON_CS5;
- break;
- case CS6:
- dbg("config: 6bits/char\n");
- ulcon = S3C2410_LCON_CS6;
- break;
- case CS7:
- dbg("config: 7bits/char\n");
- ulcon = S3C2410_LCON_CS7;
- break;
- case CS8:
- default:
- dbg("config: 8bits/char\n");
- ulcon = S3C2410_LCON_CS8;
- break;
- }
-
- /* preserve original lcon IR settings */
- ulcon |= (cfg->ulcon & S3C2410_LCON_IRM);
-
- if (termios->c_cflag & CSTOPB)
- ulcon |= S3C2410_LCON_STOPB;
-
- if (termios->c_cflag & PARENB) {
- if (termios->c_cflag & PARODD)
- ulcon |= S3C2410_LCON_PODD;
- else
- ulcon |= S3C2410_LCON_PEVEN;
- } else {
- ulcon |= S3C2410_LCON_PNONE;
- }
-
- spin_lock_irqsave(&port->lock, flags);
-
- dbg("setting ulcon to %08x, brddiv to %d, udivslot %08x\n",
- ulcon, quot, udivslot);
-
- wr_regl(port, S3C2410_ULCON, ulcon);
- wr_regl(port, S3C2410_UBRDIV, quot);
-
- port->status &= ~UPSTAT_AUTOCTS;
-
- umcon = rd_regl(port, S3C2410_UMCON);
- if (termios->c_cflag & CRTSCTS) {
- umcon |= S3C2410_UMCOM_AFC;
- /* Disable RTS when RX FIFO contains 63 bytes */
- umcon &= ~S3C2412_UMCON_AFC_8;
- port->status = UPSTAT_AUTOCTS;
- } else {
- umcon &= ~S3C2410_UMCOM_AFC;
- }
- wr_regl(port, S3C2410_UMCON, umcon);
-
- if (ourport->info->has_divslot)
- wr_regl(port, S3C2443_DIVSLOT, udivslot);
-
- dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n",
- rd_regl(port, S3C2410_ULCON),
- rd_regl(port, S3C2410_UCON),
- rd_regl(port, S3C2410_UFCON));
-
- /*
- * Update the per-port timeout.
- */
- uart_update_timeout(port, termios->c_cflag, baud);
-
- /*
- * Which character status flags are we interested in?
- */
- port->read_status_mask = S3C2410_UERSTAT_OVERRUN;
- if (termios->c_iflag & INPCK)
- port->read_status_mask |= S3C2410_UERSTAT_FRAME |
- S3C2410_UERSTAT_PARITY;
- /*
- * Which character status flags should we ignore?
- */
- port->ignore_status_mask = 0;
- if (termios->c_iflag & IGNPAR)
- port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
- if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)
- port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;
-
- /*
- * Ignore all characters if CREAD is not set.
- */
- if ((termios->c_cflag & CREAD) == 0)
- port->ignore_status_mask |= RXSTAT_DUMMY_READ;
-
- spin_unlock_irqrestore(&port->lock, flags);
-}
-
-static const char *s3c24xx_serial_type(struct uart_port *port)
-{
- switch (port->type) {
- case PORT_S3C2410:
- return "S3C2410";
- case PORT_S3C2440:
- return "S3C2440";
- case PORT_S3C2412:
- return "S3C2412";
- case PORT_S3C6400:
- return "S3C6400/10";
- default:
- return NULL;
- }
-}
-
-#define MAP_SIZE (0x100)
-
-static void s3c24xx_serial_release_port(struct uart_port *port)
-{
- release_mem_region(port->mapbase, MAP_SIZE);
-}
-
-static int s3c24xx_serial_request_port(struct uart_port *port)
-{
- const char *name = s3c24xx_serial_portname(port);
- return request_mem_region(port->mapbase, MAP_SIZE, name) ? 0 : -EBUSY;
-}
-
-static void s3c24xx_serial_config_port(struct uart_port *port, int flags)
-{
- struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
-
- if (flags & UART_CONFIG_TYPE &&
- s3c24xx_serial_request_port(port) == 0)
- port->type = info->type;
-}
-
-/*
- * verify the new serial_struct (for TIOCSSERIAL).
- */
-static int
-s3c24xx_serial_verify_port(struct uart_port *port, struct serial_struct *ser)
-{
- struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
-
- if (ser->type != PORT_UNKNOWN && ser->type != info->type)
- return -EINVAL;
-
- return 0;
-}
-
-
-#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE
-
-static struct console s3c24xx_serial_console;
-
-static int __init s3c24xx_serial_console_init(void)
-{
- register_console(&s3c24xx_serial_console);
- return 0;
-}
-console_initcall(s3c24xx_serial_console_init);
-
-#define S3C24XX_SERIAL_CONSOLE &s3c24xx_serial_console
-#else
-#define S3C24XX_SERIAL_CONSOLE NULL
-#endif
-
-#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_CONSOLE_POLL)
-static int s3c24xx_serial_get_poll_char(struct uart_port *port);
-static void s3c24xx_serial_put_poll_char(struct uart_port *port,
- unsigned char c);
-#endif
-
-static struct uart_ops s3c24xx_serial_ops = {
- .pm = s3c24xx_serial_pm,
- .tx_empty = s3c24xx_serial_tx_empty,
- .get_mctrl = s3c24xx_serial_get_mctrl,
- .set_mctrl = s3c24xx_serial_set_mctrl,
- .stop_tx = s3c24xx_serial_stop_tx,
- .start_tx = s3c24xx_serial_start_tx,
- .stop_rx = s3c24xx_serial_stop_rx,
- .break_ctl = s3c24xx_serial_break_ctl,
- .startup = s3c24xx_serial_startup,
- .shutdown = s3c24xx_serial_shutdown,
- .set_termios = s3c24xx_serial_set_termios,
- .type = s3c24xx_serial_type,
- .release_port = s3c24xx_serial_release_port,
- .request_port = s3c24xx_serial_request_port,
- .config_port = s3c24xx_serial_config_port,
- .verify_port = s3c24xx_serial_verify_port,
-#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_CONSOLE_POLL)
- .poll_get_char = s3c24xx_serial_get_poll_char,
- .poll_put_char = s3c24xx_serial_put_poll_char,
-#endif
-};
-
-static struct uart_driver s3c24xx_uart_drv = {
- .owner = THIS_MODULE,
- .driver_name = "s3c2410_serial",
- .nr = CONFIG_SERIAL_SAMSUNG_UARTS,
- .cons = S3C24XX_SERIAL_CONSOLE,
- .dev_name = S3C24XX_SERIAL_NAME,
- .major = S3C24XX_SERIAL_MAJOR,
- .minor = S3C24XX_SERIAL_MINOR,
-};
-
-#define __PORT_LOCK_UNLOCKED(i) \
- __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[i].port.lock)
-static struct s3c24xx_uart_port
-s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
- [0] = {
- .port = {
- .lock = __PORT_LOCK_UNLOCKED(0),
- .iotype = UPIO_MEM,
- .uartclk = 0,
- .fifosize = 16,
- .ops = &s3c24xx_serial_ops,
- .flags = UPF_BOOT_AUTOCONF,
- .line = 0,
- }
- },
- [1] = {
- .port = {
- .lock = __PORT_LOCK_UNLOCKED(1),
- .iotype = UPIO_MEM,
- .uartclk = 0,
- .fifosize = 16,
- .ops = &s3c24xx_serial_ops,
- .flags = UPF_BOOT_AUTOCONF,
- .line = 1,
- }
- },
-#if CONFIG_SERIAL_SAMSUNG_UARTS > 2
-
- [2] = {
- .port = {
- .lock = __PORT_LOCK_UNLOCKED(2),
- .iotype = UPIO_MEM,
- .uartclk = 0,
- .fifosize = 16,
- .ops = &s3c24xx_serial_ops,
- .flags = UPF_BOOT_AUTOCONF,
- .line = 2,
- }
- },
-#endif
-#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
- [3] = {
- .port = {
- .lock = __PORT_LOCK_UNLOCKED(3),
- .iotype = UPIO_MEM,
- .uartclk = 0,
- .fifosize = 16,
- .ops = &s3c24xx_serial_ops,
- .flags = UPF_BOOT_AUTOCONF,
- .line = 3,
- }
- }
-#endif
-};
-#undef __PORT_LOCK_UNLOCKED
-
-/* s3c24xx_serial_resetport
- *
- * reset the fifos and other the settings.
-*/
-
-static void s3c24xx_serial_resetport(struct uart_port *port,
- struct s3c2410_uartcfg *cfg)
-{
- struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
- unsigned long ucon = rd_regl(port, S3C2410_UCON);
- unsigned int ucon_mask;
-
- ucon_mask = info->clksel_mask;
- if (info->type == PORT_S3C2440)
- ucon_mask |= S3C2440_UCON0_DIVMASK;
-
- ucon &= ucon_mask;
- wr_regl(port, S3C2410_UCON, ucon | cfg->ucon);
-
- /* reset both fifos */
- wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
- wr_regl(port, S3C2410_UFCON, cfg->ufcon);
-
- /* some delay is required after fifo reset */
- udelay(1);
-}
-
-
-#ifdef CONFIG_ARM_S3C24XX_CPUFREQ
-
-static int s3c24xx_serial_cpufreq_transition(struct notifier_block *nb,
- unsigned long val, void *data)
-{
- struct s3c24xx_uart_port *port;
- struct uart_port *uport;
-
- port = container_of(nb, struct s3c24xx_uart_port, freq_transition);
- uport = &port->port;
-
- /* check to see if port is enabled */
-
- if (port->pm_level != 0)
- return 0;
-
- /* try and work out if the baudrate is changing, we can detect
- * a change in rate, but we do not have support for detecting
- * a disturbance in the clock-rate over the change.
- */
-
- if (IS_ERR(port->baudclk))
- goto exit;
-
- if (port->baudclk_rate == clk_get_rate(port->baudclk))
- goto exit;
-
- if (val == CPUFREQ_PRECHANGE) {
- /* we should really shut the port down whilst the
- * frequency change is in progress. */
-
- } else if (val == CPUFREQ_POSTCHANGE) {
- struct ktermios *termios;
- struct tty_struct *tty;
-
- if (uport->state == NULL)
- goto exit;
-
- tty = uport->state->port.tty;
-
- if (tty == NULL)
- goto exit;
-
- termios = &tty->termios;
-
- if (termios == NULL) {
- dev_warn(uport->dev, "%s: no termios?\n", __func__);
- goto exit;
- }
-
- s3c24xx_serial_set_termios(uport, termios, NULL);
- }
-
-exit:
- return 0;
-}
-
-static inline int
-s3c24xx_serial_cpufreq_register(struct s3c24xx_uart_port *port)
-{
- port->freq_transition.notifier_call = s3c24xx_serial_cpufreq_transition;
-
- return cpufreq_register_notifier(&port->freq_transition,
- CPUFREQ_TRANSITION_NOTIFIER);
-}
-
-static inline void
-s3c24xx_serial_cpufreq_deregister(struct s3c24xx_uart_port *port)
-{
- cpufreq_unregister_notifier(&port->freq_transition,
- CPUFREQ_TRANSITION_NOTIFIER);
-}
-
-#else
-static inline int
-s3c24xx_serial_cpufreq_register(struct s3c24xx_uart_port *port)
-{
- return 0;
-}
-
-static inline void
-s3c24xx_serial_cpufreq_deregister(struct s3c24xx_uart_port *port)
-{
-}
-#endif
-
-static int s3c24xx_serial_enable_baudclk(struct s3c24xx_uart_port *ourport)
-{
- struct device *dev = ourport->port.dev;
- struct s3c24xx_uart_info *info = ourport->info;
- char clk_name[MAX_CLK_NAME_LENGTH];
- unsigned int clk_sel;
- struct clk *clk;
- int clk_num;
- int ret;
-
- clk_sel = ourport->cfg->clk_sel ? : info->def_clk_sel;
- for (clk_num = 0; clk_num < info->num_clks; clk_num++) {
- if (!(clk_sel & (1 << clk_num)))
- continue;
-
- sprintf(clk_name, "clk_uart_baud%d", clk_num);
- clk = clk_get(dev, clk_name);
- if (IS_ERR(clk))
- continue;
-
- ret = clk_prepare_enable(clk);
- if (ret) {
- clk_put(clk);
- continue;
- }
-
- ourport->baudclk = clk;
- ourport->baudclk_rate = clk_get_rate(clk);
- s3c24xx_serial_setsource(&ourport->port, clk_num);
-
- return 0;
- }
-
- return -EINVAL;
-}
-
-/* s3c24xx_serial_init_port
- *
- * initialise a single serial port from the platform device given
- */
-
-static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
- struct platform_device *platdev)
-{
- struct uart_port *port = &ourport->port;
- struct s3c2410_uartcfg *cfg = ourport->cfg;
- struct resource *res;
- int ret;
-
- dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev);
-
- if (platdev == NULL)
- return -ENODEV;
-
- if (port->mapbase != 0)
- return -EINVAL;
-
- /* setup info for port */
- port->dev = &platdev->dev;
-
- /* Startup sequence is different for s3c64xx and higher SoC's */
- if (s3c24xx_serial_has_interrupt_mask(port))
- s3c24xx_serial_ops.startup = s3c64xx_serial_startup;
-
- port->uartclk = 1;
-
- if (cfg->uart_flags & UPF_CONS_FLOW) {
- dbg("s3c24xx_serial_init_port: enabling flow control\n");
- port->flags |= UPF_CONS_FLOW;
- }
-
- /* sort our the physical and virtual addresses for each UART */
-
- res = platform_get_resource(platdev, IORESOURCE_MEM, 0);
- if (res == NULL) {
- dev_err(port->dev, "failed to find memory resource for uart\n");
- return -EINVAL;
- }
-
- dbg("resource %pR)\n", res);
-
- port->membase = devm_ioremap(port->dev, res->start, resource_size(res));
- if (!port->membase) {
- dev_err(port->dev, "failed to remap controller address\n");
- return -EBUSY;
- }
-
- port->mapbase = res->start;
- ret = platform_get_irq(platdev, 0);
- if (ret < 0)
- port->irq = 0;
- else {
- port->irq = ret;
- ourport->rx_irq = ret;
- ourport->tx_irq = ret + 1;
- }
-
- ret = platform_get_irq(platdev, 1);
- if (ret > 0)
- ourport->tx_irq = ret;
- /*
- * DMA is currently supported only on DT platforms, if DMA properties
- * are specified.
- */
- if (platdev->dev.of_node && of_find_property(platdev->dev.of_node,
- "dmas", NULL)) {
- ourport->dma = devm_kzalloc(port->dev,
- sizeof(*ourport->dma),
- GFP_KERNEL);
- if (!ourport->dma) {
- ret = -ENOMEM;
- goto err;
- }
- }
-
- ourport->clk = clk_get(&platdev->dev, "uart");
- if (IS_ERR(ourport->clk)) {
- pr_err("%s: Controller clock not found\n",
- dev_name(&platdev->dev));
- ret = PTR_ERR(ourport->clk);
- goto err;
- }
-
- ret = clk_prepare_enable(ourport->clk);
- if (ret) {
- pr_err("uart: clock failed to prepare+enable: %d\n", ret);
- clk_put(ourport->clk);
- goto err;
- }
-
- ret = s3c24xx_serial_enable_baudclk(ourport);
- if (ret)
- pr_warn("uart: failed to enable baudclk\n");
-
- /* Keep all interrupts masked and cleared */
- if (s3c24xx_serial_has_interrupt_mask(port)) {
- wr_regl(port, S3C64XX_UINTM, 0xf);
- wr_regl(port, S3C64XX_UINTP, 0xf);
- wr_regl(port, S3C64XX_UINTSP, 0xf);
- }
-
- dbg("port: map=%pa, mem=%p, irq=%d (%d,%d), clock=%u\n",
- &port->mapbase, port->membase, port->irq,
- ourport->rx_irq, ourport->tx_irq, port->uartclk);
-
- /* reset the fifos (and setup the uart) */
- s3c24xx_serial_resetport(port, cfg);
-
- return 0;
-
-err:
- port->mapbase = 0;
- return ret;
-}
-
-/* Device driver serial port probe */
-
-static const struct of_device_id s3c24xx_uart_dt_match[];
-static int probe_index;
-
-static inline struct s3c24xx_serial_drv_data *s3c24xx_get_driver_data(
- struct platform_device *pdev)
-{
-#ifdef CONFIG_OF
- if (pdev->dev.of_node) {
- const struct of_device_id *match;
- match = of_match_node(s3c24xx_uart_dt_match, pdev->dev.of_node);
- return (struct s3c24xx_serial_drv_data *)match->data;
- }
-#endif
- return (struct s3c24xx_serial_drv_data *)
- platform_get_device_id(pdev)->driver_data;
-}
-
-static int s3c24xx_serial_probe(struct platform_device *pdev)
-{
- struct device_node *np = pdev->dev.of_node;
- struct s3c24xx_uart_port *ourport;
- int index = probe_index;
- int ret;
-
- if (np) {
- ret = of_alias_get_id(np, "serial");
- if (ret >= 0)
- index = ret;
- }
-
- dbg("s3c24xx_serial_probe(%p) %d\n", pdev, index);
-
- if (index >= ARRAY_SIZE(s3c24xx_serial_ports)) {
- dev_err(&pdev->dev, "serial%d out of range\n", index);
- return -EINVAL;
- }
- ourport = &s3c24xx_serial_ports[index];
-
- ourport->drv_data = s3c24xx_get_driver_data(pdev);
- if (!ourport->drv_data) {
- dev_err(&pdev->dev, "could not find driver data\n");
- return -ENODEV;
- }
-
- ourport->baudclk = ERR_PTR(-EINVAL);
- ourport->info = ourport->drv_data->info;
- ourport->cfg = (dev_get_platdata(&pdev->dev)) ?
- dev_get_platdata(&pdev->dev) :
- ourport->drv_data->def_cfg;
-
- if (np)
- of_property_read_u32(np,
- "samsung,uart-fifosize", &ourport->port.fifosize);
-
- if (ourport->drv_data->fifosize[index])
- ourport->port.fifosize = ourport->drv_data->fifosize[index];
- else if (ourport->info->fifosize)
- ourport->port.fifosize = ourport->info->fifosize;
-
- /*
- * DMA transfers must be aligned at least to cache line size,
- * so find minimal transfer size suitable for DMA mode
- */
- ourport->min_dma_size = max_t(int, ourport->port.fifosize,
- dma_get_cache_alignment());
-
- dbg("%s: initialising port %p...\n", __func__, ourport);
-
- ret = s3c24xx_serial_init_port(ourport, pdev);
- if (ret < 0)
- return ret;
-
- if (!s3c24xx_uart_drv.state) {
- ret = uart_register_driver(&s3c24xx_uart_drv);
- if (ret < 0) {
- pr_err("Failed to register Samsung UART driver\n");
- return ret;
- }
- }
-
- dbg("%s: adding port\n", __func__);
- uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
- platform_set_drvdata(pdev, &ourport->port);
-
- /*
- * Deactivate the clock enabled in s3c24xx_serial_init_port here,
- * so that a potential re-enablement through the pm-callback overlaps
- * and keeps the clock enabled in this case.
- */
- clk_disable_unprepare(ourport->clk);
- if (!IS_ERR(ourport->baudclk))
- clk_disable_unprepare(ourport->baudclk);
-
- ret = s3c24xx_serial_cpufreq_register(ourport);
- if (ret < 0)
- dev_err(&pdev->dev, "failed to add cpufreq notifier\n");
-
- probe_index++;
-
- return 0;
-}
-
-static int s3c24xx_serial_remove(struct platform_device *dev)
-{
- struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
-
- if (port) {
- s3c24xx_serial_cpufreq_deregister(to_ourport(port));
- uart_remove_one_port(&s3c24xx_uart_drv, port);
- }
-
- uart_unregister_driver(&s3c24xx_uart_drv);
-
- return 0;
-}
-
-/* UART power management code */
-#ifdef CONFIG_PM_SLEEP
-static int s3c24xx_serial_suspend(struct device *dev)
-{
- struct uart_port *port = s3c24xx_dev_to_port(dev);
-
- if (port)
- uart_suspend_port(&s3c24xx_uart_drv, port);
-
- return 0;
-}
-
-static int s3c24xx_serial_resume(struct device *dev)
-{
- struct uart_port *port = s3c24xx_dev_to_port(dev);
- struct s3c24xx_uart_port *ourport = to_ourport(port);
-
- if (port) {
- clk_prepare_enable(ourport->clk);
- if (!IS_ERR(ourport->baudclk))
- clk_prepare_enable(ourport->baudclk);
- s3c24xx_serial_resetport(port, s3c24xx_port_to_cfg(port));
- if (!IS_ERR(ourport->baudclk))
- clk_disable_unprepare(ourport->baudclk);
- clk_disable_unprepare(ourport->clk);
-
- uart_resume_port(&s3c24xx_uart_drv, port);
- }
-
- return 0;
-}
-
-static int s3c24xx_serial_resume_noirq(struct device *dev)
-{
- struct uart_port *port = s3c24xx_dev_to_port(dev);
- struct s3c24xx_uart_port *ourport = to_ourport(port);
-
- if (port) {
- /* restore IRQ mask */
- if (s3c24xx_serial_has_interrupt_mask(port)) {
- unsigned int uintm = 0xf;
- if (tx_enabled(port))
- uintm &= ~S3C64XX_UINTM_TXD_MSK;
- if (rx_enabled(port))
- uintm &= ~S3C64XX_UINTM_RXD_MSK;
- clk_prepare_enable(ourport->clk);
- if (!IS_ERR(ourport->baudclk))
- clk_prepare_enable(ourport->baudclk);
- wr_regl(port, S3C64XX_UINTM, uintm);
- if (!IS_ERR(ourport->baudclk))
- clk_disable_unprepare(ourport->baudclk);
- clk_disable_unprepare(ourport->clk);
- }
- }
-
- return 0;
-}
-
-static const struct dev_pm_ops s3c24xx_serial_pm_ops = {
- .suspend = s3c24xx_serial_suspend,
- .resume = s3c24xx_serial_resume,
- .resume_noirq = s3c24xx_serial_resume_noirq,
-};
-#define SERIAL_SAMSUNG_PM_OPS (&s3c24xx_serial_pm_ops)
-
-#else /* !CONFIG_PM_SLEEP */
-
-#define SERIAL_SAMSUNG_PM_OPS NULL
-#endif /* CONFIG_PM_SLEEP */
-
-/* Console code */
-
-#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE
-
-static struct uart_port *cons_uart;
-
-static int
-s3c24xx_serial_console_txrdy(struct uart_port *port, unsigned int ufcon)
-{
- struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
- unsigned long ufstat, utrstat;
-
- if (ufcon & S3C2410_UFCON_FIFOMODE) {
- /* fifo mode - check amount of data in fifo registers... */
-
- ufstat = rd_regl(port, S3C2410_UFSTAT);
- return (ufstat & info->tx_fifofull) ? 0 : 1;
- }
-
- /* in non-fifo mode, we go and use the tx buffer empty */
-
- utrstat = rd_regl(port, S3C2410_UTRSTAT);
- return (utrstat & S3C2410_UTRSTAT_TXE) ? 1 : 0;
-}
-
-static bool
-s3c24xx_port_configured(unsigned int ucon)
-{
- /* consider the serial port configured if the tx/rx mode set */
- return (ucon & 0xf) != 0;
-}
-
-#ifdef CONFIG_CONSOLE_POLL
-/*
- * Console polling routines for writing and reading from the uart while
- * in an interrupt or debug context.
- */
-
-static int s3c24xx_serial_get_poll_char(struct uart_port *port)
-{
- struct s3c24xx_uart_port *ourport = to_ourport(port);
- unsigned int ufstat;
-
- ufstat = rd_regl(port, S3C2410_UFSTAT);
- if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
- return NO_POLL_CHAR;
-
- return rd_regb(port, S3C2410_URXH);
-}
-
-static void s3c24xx_serial_put_poll_char(struct uart_port *port,
- unsigned char c)
-{
- unsigned int ufcon = rd_regl(port, S3C2410_UFCON);
- unsigned int ucon = rd_regl(port, S3C2410_UCON);
-
- /* not possible to xmit on unconfigured port */
- if (!s3c24xx_port_configured(ucon))
- return;
-
- while (!s3c24xx_serial_console_txrdy(port, ufcon))
- cpu_relax();
- wr_regb(port, S3C2410_UTXH, c);
-}
-
-#endif /* CONFIG_CONSOLE_POLL */
-
-static void
-s3c24xx_serial_console_putchar(struct uart_port *port, int ch)
-{
- unsigned int ufcon = rd_regl(port, S3C2410_UFCON);
-
- while (!s3c24xx_serial_console_txrdy(port, ufcon))
- cpu_relax();
- wr_regb(port, S3C2410_UTXH, ch);
-}
-
-static void
-s3c24xx_serial_console_write(struct console *co, const char *s,
- unsigned int count)
-{
- unsigned int ucon = rd_regl(cons_uart, S3C2410_UCON);
-
- /* not possible to xmit on unconfigured port */
- if (!s3c24xx_port_configured(ucon))
- return;
-
- uart_console_write(cons_uart, s, count, s3c24xx_serial_console_putchar);
-}
-
-static void __init
-s3c24xx_serial_get_options(struct uart_port *port, int *baud,
- int *parity, int *bits)
-{
- struct clk *clk;
- unsigned int ulcon;
- unsigned int ucon;
- unsigned int ubrdiv;
- unsigned long rate;
- unsigned int clk_sel;
- char clk_name[MAX_CLK_NAME_LENGTH];
-
- ulcon = rd_regl(port, S3C2410_ULCON);
- ucon = rd_regl(port, S3C2410_UCON);
- ubrdiv = rd_regl(port, S3C2410_UBRDIV);
-
- dbg("s3c24xx_serial_get_options: port=%p\n"
- "registers: ulcon=%08x, ucon=%08x, ubdriv=%08x\n",
- port, ulcon, ucon, ubrdiv);
-
- if (s3c24xx_port_configured(ucon)) {
- switch (ulcon & S3C2410_LCON_CSMASK) {
- case S3C2410_LCON_CS5:
- *bits = 5;
- break;
- case S3C2410_LCON_CS6:
- *bits = 6;
- break;
- case S3C2410_LCON_CS7:
- *bits = 7;
- break;
- case S3C2410_LCON_CS8:
- default:
- *bits = 8;
- break;
- }
-
- switch (ulcon & S3C2410_LCON_PMASK) {
- case S3C2410_LCON_PEVEN:
- *parity = 'e';
- break;
-
- case S3C2410_LCON_PODD:
- *parity = 'o';
- break;
-
- case S3C2410_LCON_PNONE:
- default:
- *parity = 'n';
- }
-
- /* now calculate the baud rate */
-
- clk_sel = s3c24xx_serial_getsource(port);
- sprintf(clk_name, "clk_uart_baud%d", clk_sel);
-
- clk = clk_get(port->dev, clk_name);
- if (!IS_ERR(clk))
- rate = clk_get_rate(clk);
- else
- rate = 1;
-
- *baud = rate / (16 * (ubrdiv + 1));
- dbg("calculated baud %d\n", *baud);
- }
-
-}
-
-static int __init
-s3c24xx_serial_console_setup(struct console *co, char *options)
-{
- struct uart_port *port;
- int baud = 9600;
- int bits = 8;
- int parity = 'n';
- int flow = 'n';
-
- dbg("s3c24xx_serial_console_setup: co=%p (%d), %s\n",
- co, co->index, options);
-
- /* is this a valid port */
-
- if (co->index == -1 || co->index >= CONFIG_SERIAL_SAMSUNG_UARTS)
- co->index = 0;
-
- port = &s3c24xx_serial_ports[co->index].port;
-
- /* is the port configured? */
-
- if (port->mapbase == 0x0)
- return -ENODEV;
-
- cons_uart = port;
-
- dbg("s3c24xx_serial_console_setup: port=%p (%d)\n", port, co->index);
-
- /*
- * Check whether an invalid uart number has been specified, and
- * if so, search for the first available port that does have
- * console support.
- */
- if (options)
- uart_parse_options(options, &baud, &parity, &bits, &flow);
- else
- s3c24xx_serial_get_options(port, &baud, &parity, &bits);
-
- dbg("s3c24xx_serial_console_setup: baud %d\n", baud);
-
- return uart_set_options(port, co, baud, parity, bits, flow);
-}
-
-static struct console s3c24xx_serial_console = {
- .name = S3C24XX_SERIAL_NAME,
- .device = uart_console_device,
- .flags = CON_PRINTBUFFER,
- .index = -1,
- .write = s3c24xx_serial_console_write,
- .setup = s3c24xx_serial_console_setup,
- .data = &s3c24xx_uart_drv,
-};
-#endif /* CONFIG_SERIAL_SAMSUNG_CONSOLE */
-
-#ifdef CONFIG_CPU_S3C2410
-static struct s3c24xx_serial_drv_data s3c2410_serial_drv_data = {
- .info = &(struct s3c24xx_uart_info) {
- .name = "Samsung S3C2410 UART",
- .type = PORT_S3C2410,
- .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,
- .def_clk_sel = S3C2410_UCON_CLKSEL0,
- .num_clks = 2,
- .clksel_mask = S3C2410_UCON_CLKMASK,
- .clksel_shift = S3C2410_UCON_CLKSHIFT,
- },
- .def_cfg = &(struct s3c2410_uartcfg) {
- .ucon = S3C2410_UCON_DEFAULT,
- .ufcon = S3C2410_UFCON_DEFAULT,
- },
-};
-#define S3C2410_SERIAL_DRV_DATA ((kernel_ulong_t)&s3c2410_serial_drv_data)
-#else
-#define S3C2410_SERIAL_DRV_DATA (kernel_ulong_t)NULL
-#endif
-
-#ifdef CONFIG_CPU_S3C2412
-static struct s3c24xx_serial_drv_data s3c2412_serial_drv_data = {
- .info = &(struct s3c24xx_uart_info) {
- .name = "Samsung S3C2412 UART",
- .type = PORT_S3C2412,
- .fifosize = 64,
- .has_divslot = 1,
- .rx_fifomask = S3C2440_UFSTAT_RXMASK,
- .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT,
- .rx_fifofull = S3C2440_UFSTAT_RXFULL,
- .tx_fifofull = S3C2440_UFSTAT_TXFULL,
- .tx_fifomask = S3C2440_UFSTAT_TXMASK,
- .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
- .def_clk_sel = S3C2410_UCON_CLKSEL2,
- .num_clks = 4,
- .clksel_mask = S3C2412_UCON_CLKMASK,
- .clksel_shift = S3C2412_UCON_CLKSHIFT,
- },
- .def_cfg = &(struct s3c2410_uartcfg) {
- .ucon = S3C2410_UCON_DEFAULT,
- .ufcon = S3C2410_UFCON_DEFAULT,
- },
-};
-#define S3C2412_SERIAL_DRV_DATA ((kernel_ulong_t)&s3c2412_serial_drv_data)
-#else
-#define S3C2412_SERIAL_DRV_DATA (kernel_ulong_t)NULL
-#endif
-
-#if defined(CONFIG_CPU_S3C2440) || defined(CONFIG_CPU_S3C2416) || \
- defined(CONFIG_CPU_S3C2443) || defined(CONFIG_CPU_S3C2442)
-static struct s3c24xx_serial_drv_data s3c2440_serial_drv_data = {
- .info = &(struct s3c24xx_uart_info) {
- .name = "Samsung S3C2440 UART",
- .type = PORT_S3C2440,
- .fifosize = 64,
- .has_divslot = 1,
- .rx_fifomask = S3C2440_UFSTAT_RXMASK,
- .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT,
- .rx_fifofull = S3C2440_UFSTAT_RXFULL,
- .tx_fifofull = S3C2440_UFSTAT_TXFULL,
- .tx_fifomask = S3C2440_UFSTAT_TXMASK,
- .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
- .def_clk_sel = S3C2410_UCON_CLKSEL2,
- .num_clks = 4,
- .clksel_mask = S3C2412_UCON_CLKMASK,
- .clksel_shift = S3C2412_UCON_CLKSHIFT,
- },
- .def_cfg = &(struct s3c2410_uartcfg) {
- .ucon = S3C2410_UCON_DEFAULT,
- .ufcon = S3C2410_UFCON_DEFAULT,
- },
-};
-#define S3C2440_SERIAL_DRV_DATA ((kernel_ulong_t)&s3c2440_serial_drv_data)
-#else
-#define S3C2440_SERIAL_DRV_DATA (kernel_ulong_t)NULL
-#endif
-
-#if defined(CONFIG_CPU_S3C6400) || defined(CONFIG_CPU_S3C6410)
-static struct s3c24xx_serial_drv_data s3c6400_serial_drv_data = {
- .info = &(struct s3c24xx_uart_info) {
- .name = "Samsung S3C6400 UART",
- .type = PORT_S3C6400,
- .fifosize = 64,
- .has_divslot = 1,
- .rx_fifomask = S3C2440_UFSTAT_RXMASK,
- .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT,
- .rx_fifofull = S3C2440_UFSTAT_RXFULL,
- .tx_fifofull = S3C2440_UFSTAT_TXFULL,
- .tx_fifomask = S3C2440_UFSTAT_TXMASK,
- .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
- .def_clk_sel = S3C2410_UCON_CLKSEL2,
- .num_clks = 4,
- .clksel_mask = S3C6400_UCON_CLKMASK,
- .clksel_shift = S3C6400_UCON_CLKSHIFT,
- },
- .def_cfg = &(struct s3c2410_uartcfg) {
- .ucon = S3C2410_UCON_DEFAULT,
- .ufcon = S3C2410_UFCON_DEFAULT,
- },
-};
-#define S3C6400_SERIAL_DRV_DATA ((kernel_ulong_t)&s3c6400_serial_drv_data)
-#else
-#define S3C6400_SERIAL_DRV_DATA (kernel_ulong_t)NULL
-#endif
-
-#ifdef CONFIG_CPU_S5PV210
-static struct s3c24xx_serial_drv_data s5pv210_serial_drv_data = {
- .info = &(struct s3c24xx_uart_info) {
- .name = "Samsung S5PV210 UART",
- .type = PORT_S3C6400,
- .has_divslot = 1,
- .rx_fifomask = S5PV210_UFSTAT_RXMASK,
- .rx_fifoshift = S5PV210_UFSTAT_RXSHIFT,
- .rx_fifofull = S5PV210_UFSTAT_RXFULL,
- .tx_fifofull = S5PV210_UFSTAT_TXFULL,
- .tx_fifomask = S5PV210_UFSTAT_TXMASK,
- .tx_fifoshift = S5PV210_UFSTAT_TXSHIFT,
- .def_clk_sel = S3C2410_UCON_CLKSEL0,
- .num_clks = 2,
- .clksel_mask = S5PV210_UCON_CLKMASK,
- .clksel_shift = S5PV210_UCON_CLKSHIFT,
- },
- .def_cfg = &(struct s3c2410_uartcfg) {
- .ucon = S5PV210_UCON_DEFAULT,
- .ufcon = S5PV210_UFCON_DEFAULT,
- },
- .fifosize = { 256, 64, 16, 16 },
-};
-#define S5PV210_SERIAL_DRV_DATA ((kernel_ulong_t)&s5pv210_serial_drv_data)
-#else
-#define S5PV210_SERIAL_DRV_DATA (kernel_ulong_t)NULL
-#endif
-
-#if defined(CONFIG_ARCH_EXYNOS)
-#define EXYNOS_COMMON_SERIAL_DRV_DATA \
- .info = &(struct s3c24xx_uart_info) { \
- .name = "Samsung Exynos UART", \
- .type = PORT_S3C6400, \
- .has_divslot = 1, \
- .rx_fifomask = S5PV210_UFSTAT_RXMASK, \
- .rx_fifoshift = S5PV210_UFSTAT_RXSHIFT, \
- .rx_fifofull = S5PV210_UFSTAT_RXFULL, \
- .tx_fifofull = S5PV210_UFSTAT_TXFULL, \
- .tx_fifomask = S5PV210_UFSTAT_TXMASK, \
- .tx_fifoshift = S5PV210_UFSTAT_TXSHIFT, \
- .def_clk_sel = S3C2410_UCON_CLKSEL0, \
- .num_clks = 1, \
- .clksel_mask = 0, \
- .clksel_shift = 0, \
- }, \
- .def_cfg = &(struct s3c2410_uartcfg) { \
- .ucon = S5PV210_UCON_DEFAULT, \
- .ufcon = S5PV210_UFCON_DEFAULT, \
- .has_fracval = 1, \
- } \
-
-static struct s3c24xx_serial_drv_data exynos4210_serial_drv_data = {
- EXYNOS_COMMON_SERIAL_DRV_DATA,
- .fifosize = { 256, 64, 16, 16 },
-};
-
-static struct s3c24xx_serial_drv_data exynos5433_serial_drv_data = {
- EXYNOS_COMMON_SERIAL_DRV_DATA,
- .fifosize = { 64, 256, 16, 256 },
-};
-
-#define EXYNOS4210_SERIAL_DRV_DATA ((kernel_ulong_t)&exynos4210_serial_drv_data)
-#define EXYNOS5433_SERIAL_DRV_DATA ((kernel_ulong_t)&exynos5433_serial_drv_data)
-#else
-#define EXYNOS4210_SERIAL_DRV_DATA (kernel_ulong_t)NULL
-#define EXYNOS5433_SERIAL_DRV_DATA (kernel_ulong_t)NULL
-#endif
-
-static const struct platform_device_id s3c24xx_serial_driver_ids[] = {
- {
- .name = "s3c2410-uart",
- .driver_data = S3C2410_SERIAL_DRV_DATA,
- }, {
- .name = "s3c2412-uart",
- .driver_data = S3C2412_SERIAL_DRV_DATA,
- }, {
- .name = "s3c2440-uart",
- .driver_data = S3C2440_SERIAL_DRV_DATA,
- }, {
- .name = "s3c6400-uart",
- .driver_data = S3C6400_SERIAL_DRV_DATA,
- }, {
- .name = "s5pv210-uart",
- .driver_data = S5PV210_SERIAL_DRV_DATA,
- }, {
- .name = "exynos4210-uart",
- .driver_data = EXYNOS4210_SERIAL_DRV_DATA,
- }, {
- .name = "exynos5433-uart",
- .driver_data = EXYNOS5433_SERIAL_DRV_DATA,
- },
- { },
-};
-MODULE_DEVICE_TABLE(platform, s3c24xx_serial_driver_ids);
-
-#ifdef CONFIG_OF
-static const struct of_device_id s3c24xx_uart_dt_match[] = {
- { .compatible = "samsung,s3c2410-uart",
- .data = (void *)S3C2410_SERIAL_DRV_DATA },
- { .compatible = "samsung,s3c2412-uart",
- .data = (void *)S3C2412_SERIAL_DRV_DATA },
- { .compatible = "samsung,s3c2440-uart",
- .data = (void *)S3C2440_SERIAL_DRV_DATA },
- { .compatible = "samsung,s3c6400-uart",
- .data = (void *)S3C6400_SERIAL_DRV_DATA },
- { .compatible = "samsung,s5pv210-uart",
- .data = (void *)S5PV210_SERIAL_DRV_DATA },
- { .compatible = "samsung,exynos4210-uart",
- .data = (void *)EXYNOS4210_SERIAL_DRV_DATA },
- { .compatible = "samsung,exynos5433-uart",
- .data = (void *)EXYNOS5433_SERIAL_DRV_DATA },
- {},
-};
-MODULE_DEVICE_TABLE(of, s3c24xx_uart_dt_match);
-#endif
-
-static struct platform_driver samsung_serial_driver = {
- .probe = s3c24xx_serial_probe,
- .remove = s3c24xx_serial_remove,
- .id_table = s3c24xx_serial_driver_ids,
- .driver = {
- .name = "samsung-uart",
- .pm = SERIAL_SAMSUNG_PM_OPS,
- .of_match_table = of_match_ptr(s3c24xx_uart_dt_match),
- },
-};
-
-module_platform_driver(samsung_serial_driver);
-
-#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE
-/*
- * Early console.
- */
-
-struct samsung_early_console_data {
- u32 txfull_mask;
-};
-
-static void samsung_early_busyuart(struct uart_port *port)
-{
- while (!(readl(port->membase + S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXFE))
- ;
-}
-
-static void samsung_early_busyuart_fifo(struct uart_port *port)
-{
- struct samsung_early_console_data *data = port->private_data;
-
- while (readl(port->membase + S3C2410_UFSTAT) & data->txfull_mask)
- ;
-}
-
-static void samsung_early_putc(struct uart_port *port, int c)
-{
- if (readl(port->membase + S3C2410_UFCON) & S3C2410_UFCON_FIFOMODE)
- samsung_early_busyuart_fifo(port);
- else
- samsung_early_busyuart(port);
-
- writeb(c, port->membase + S3C2410_UTXH);
-}
-
-static void samsung_early_write(struct console *con, const char *s, unsigned n)
-{
- struct earlycon_device *dev = con->data;
-
- uart_console_write(&dev->port, s, n, samsung_early_putc);
-}
-
-static int __init samsung_early_console_setup(struct earlycon_device *device,
- const char *opt)
-{
- if (!device->port.membase)
- return -ENODEV;
-
- device->con->write = samsung_early_write;
- return 0;
-}
-
-/* S3C2410 */
-static struct samsung_early_console_data s3c2410_early_console_data = {
- .txfull_mask = S3C2410_UFSTAT_TXFULL,
-};
-
-static int __init s3c2410_early_console_setup(struct earlycon_device *device,
- const char *opt)
-{
- device->port.private_data = &s3c2410_early_console_data;
- return samsung_early_console_setup(device, opt);
-}
-OF_EARLYCON_DECLARE(s3c2410, "samsung,s3c2410-uart",
- s3c2410_early_console_setup);
-
-/* S3C2412, S3C2440, S3C64xx */
-static struct samsung_early_console_data s3c2440_early_console_data = {
- .txfull_mask = S3C2440_UFSTAT_TXFULL,
-};
-
-static int __init s3c2440_early_console_setup(struct earlycon_device *device,
- const char *opt)
-{
- device->port.private_data = &s3c2440_early_console_data;
- return samsung_early_console_setup(device, opt);
-}
-OF_EARLYCON_DECLARE(s3c2412, "samsung,s3c2412-uart",
- s3c2440_early_console_setup);
-OF_EARLYCON_DECLARE(s3c2440, "samsung,s3c2440-uart",
- s3c2440_early_console_setup);
-OF_EARLYCON_DECLARE(s3c6400, "samsung,s3c6400-uart",
- s3c2440_early_console_setup);
-
-/* S5PV210, EXYNOS */
-static struct samsung_early_console_data s5pv210_early_console_data = {
- .txfull_mask = S5PV210_UFSTAT_TXFULL,
-};
-
-static int __init s5pv210_early_console_setup(struct earlycon_device *device,
- const char *opt)
-{
- device->port.private_data = &s5pv210_early_console_data;
- return samsung_early_console_setup(device, opt);
-}
-OF_EARLYCON_DECLARE(s5pv210, "samsung,s5pv210-uart",
- s5pv210_early_console_setup);
-OF_EARLYCON_DECLARE(exynos4210, "samsung,exynos4210-uart",
- s5pv210_early_console_setup);
-#endif
-
-MODULE_ALIAS("platform:samsung-uart");
-MODULE_DESCRIPTION("Samsung SoC Serial port driver");
-MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
-MODULE_LICENSE("GPL v2");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver core for Samsung SoC onboard UARTs.
+ *
+ * Ben Dooks, Copyright (c) 2003-2008 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+*/
+
+/* Hote on 2410 error handling
+ *
+ * The s3c2410 manual has a love/hate affair with the contents of the
+ * UERSTAT register in the UART blocks, and keeps marking some of the
+ * error bits as reserved. Having checked with the s3c2410x01,
+ * it copes with BREAKs properly, so I am happy to ignore the RESERVED
+ * feature from the latter versions of the manual.
+ *
+ * If it becomes aparrent that latter versions of the 2410 remove these
+ * bits, then action will have to be taken to differentiate the versions
+ * and change the policy on BREAK
+ *
+ * BJD, 04-Nov-2004
+*/
+
+#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
+#define SUPPORT_SYSRQ
+#endif
+
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/sysrq.h>
+#include <linux/console.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+#include <linux/serial_s3c.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/cpufreq.h>
+#include <linux/of.h>
+
+#include <asm/irq.h>
+
+#include "samsung.h"
+
+#if defined(CONFIG_SERIAL_SAMSUNG_DEBUG) && \
+ !defined(MODULE)
+
+extern void printascii(const char *);
+
+__printf(1, 2)
+static void dbg(const char *fmt, ...)
+{
+ va_list va;
+ char buff[256];
+
+ va_start(va, fmt);
+ vscnprintf(buff, sizeof(buff), fmt, va);
+ va_end(va);
+
+ printascii(buff);
+}
+
+#else
+#define dbg(fmt, ...) do { if (0) no_printk(fmt, ##__VA_ARGS__); } while (0)
+#endif
+
+/* UART name and device definitions */
+
+#define S3C24XX_SERIAL_NAME "ttySAC"
+#define S3C24XX_SERIAL_MAJOR 204
+#define S3C24XX_SERIAL_MINOR 64
+
+#define S3C24XX_TX_PIO 1
+#define S3C24XX_TX_DMA 2
+#define S3C24XX_RX_PIO 1
+#define S3C24XX_RX_DMA 2
+/* macros to change one thing to another */
+
+#define tx_enabled(port) ((port)->unused[0])
+#define rx_enabled(port) ((port)->unused[1])
+
+/* flag to ignore all characters coming in */
+#define RXSTAT_DUMMY_READ (0x10000000)
+
+static inline struct s3c24xx_uart_port *to_ourport(struct uart_port *port)
+{
+ return container_of(port, struct s3c24xx_uart_port, port);
+}
+
+/* translate a port to the device name */
+
+static inline const char *s3c24xx_serial_portname(struct uart_port *port)
+{
+ return to_platform_device(port->dev)->name;
+}
+
+static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
+{
+ return rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE;
+}
+
+/*
+ * s3c64xx and later SoC's include the interrupt mask and status registers in
+ * the controller itself, unlike the s3c24xx SoC's which have these registers
+ * in the interrupt controller. Check if the port type is s3c64xx or higher.
+ */
+static int s3c24xx_serial_has_interrupt_mask(struct uart_port *port)
+{
+ return to_ourport(port)->info->type == PORT_S3C6400;
+}
+
+static void s3c24xx_serial_rx_enable(struct uart_port *port)
+{
+ unsigned long flags;
+ unsigned int ucon, ufcon;
+ int count = 10000;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ while (--count && !s3c24xx_serial_txempty_nofifo(port))
+ udelay(100);
+
+ ufcon = rd_regl(port, S3C2410_UFCON);
+ ufcon |= S3C2410_UFCON_RESETRX;
+ wr_regl(port, S3C2410_UFCON, ufcon);
+
+ ucon = rd_regl(port, S3C2410_UCON);
+ ucon |= S3C2410_UCON_RXIRQMODE;
+ wr_regl(port, S3C2410_UCON, ucon);
+
+ rx_enabled(port) = 1;
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void s3c24xx_serial_rx_disable(struct uart_port *port)
+{
+ unsigned long flags;
+ unsigned int ucon;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ ucon = rd_regl(port, S3C2410_UCON);
+ ucon &= ~S3C2410_UCON_RXIRQMODE;
+ wr_regl(port, S3C2410_UCON, ucon);
+
+ rx_enabled(port) = 0;
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void s3c24xx_serial_stop_tx(struct uart_port *port)
+{
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+ struct s3c24xx_uart_dma *dma = ourport->dma;
+ struct circ_buf *xmit = &port->state->xmit;
+ struct dma_tx_state state;
+ int count;
+
+ if (!tx_enabled(port))
+ return;
+
+ if (s3c24xx_serial_has_interrupt_mask(port))
+ s3c24xx_set_bit(port, S3C64XX_UINTM_TXD, S3C64XX_UINTM);
+ else
+ disable_irq_nosync(ourport->tx_irq);
+
+ if (dma && dma->tx_chan && ourport->tx_in_progress == S3C24XX_TX_DMA) {
+ dmaengine_pause(dma->tx_chan);
+ dmaengine_tx_status(dma->tx_chan, dma->tx_cookie, &state);
+ dmaengine_terminate_all(dma->tx_chan);
+ dma_sync_single_for_cpu(ourport->port.dev,
+ dma->tx_transfer_addr, dma->tx_size, DMA_TO_DEVICE);
+ async_tx_ack(dma->tx_desc);
+ count = dma->tx_bytes_requested - state.residue;
+ xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
+ port->icount.tx += count;
+ }
+
+ tx_enabled(port) = 0;
+ ourport->tx_in_progress = 0;
+
+ if (port->flags & UPF_CONS_FLOW)
+ s3c24xx_serial_rx_enable(port);
+
+ ourport->tx_mode = 0;
+}
+
+static void s3c24xx_serial_start_next_tx(struct s3c24xx_uart_port *ourport);
+
+static void s3c24xx_serial_tx_dma_complete(void *args)
+{
+ struct s3c24xx_uart_port *ourport = args;
+ struct uart_port *port = &ourport->port;
+ struct circ_buf *xmit = &port->state->xmit;
+ struct s3c24xx_uart_dma *dma = ourport->dma;
+ struct dma_tx_state state;
+ unsigned long flags;
+ int count;
+
+
+ dmaengine_tx_status(dma->tx_chan, dma->tx_cookie, &state);
+ count = dma->tx_bytes_requested - state.residue;
+ async_tx_ack(dma->tx_desc);
+
+ dma_sync_single_for_cpu(ourport->port.dev, dma->tx_transfer_addr,
+ dma->tx_size, DMA_TO_DEVICE);
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
+ port->icount.tx += count;
+ ourport->tx_in_progress = 0;
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(port);
+
+ s3c24xx_serial_start_next_tx(ourport);
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void enable_tx_dma(struct s3c24xx_uart_port *ourport)
+{
+ struct uart_port *port = &ourport->port;
+ u32 ucon;
+
+ /* Mask Tx interrupt */
+ if (s3c24xx_serial_has_interrupt_mask(port))
+ s3c24xx_set_bit(port, S3C64XX_UINTM_TXD, S3C64XX_UINTM);
+ else
+ disable_irq_nosync(ourport->tx_irq);
+
+ /* Enable tx dma mode */
+ ucon = rd_regl(port, S3C2410_UCON);
+ ucon &= ~(S3C64XX_UCON_TXBURST_MASK | S3C64XX_UCON_TXMODE_MASK);
+ ucon |= (dma_get_cache_alignment() >= 16) ?
+ S3C64XX_UCON_TXBURST_16 : S3C64XX_UCON_TXBURST_1;
+ ucon |= S3C64XX_UCON_TXMODE_DMA;
+ wr_regl(port, S3C2410_UCON, ucon);
+
+ ourport->tx_mode = S3C24XX_TX_DMA;
+}
+
+static void enable_tx_pio(struct s3c24xx_uart_port *ourport)
+{
+ struct uart_port *port = &ourport->port;
+ u32 ucon, ufcon;
+
+ /* Set ufcon txtrig */
+ ourport->tx_in_progress = S3C24XX_TX_PIO;
+ ufcon = rd_regl(port, S3C2410_UFCON);
+ wr_regl(port, S3C2410_UFCON, ufcon);
+
+ /* Enable tx pio mode */
+ ucon = rd_regl(port, S3C2410_UCON);
+ ucon &= ~(S3C64XX_UCON_TXMODE_MASK);
+ ucon |= S3C64XX_UCON_TXMODE_CPU;
+ wr_regl(port, S3C2410_UCON, ucon);
+
+ /* Unmask Tx interrupt */
+ if (s3c24xx_serial_has_interrupt_mask(port))
+ s3c24xx_clear_bit(port, S3C64XX_UINTM_TXD,
+ S3C64XX_UINTM);
+ else
+ enable_irq(ourport->tx_irq);
+
+ ourport->tx_mode = S3C24XX_TX_PIO;
+}
+
+static void s3c24xx_serial_start_tx_pio(struct s3c24xx_uart_port *ourport)
+{
+ if (ourport->tx_mode != S3C24XX_TX_PIO)
+ enable_tx_pio(ourport);
+}
+
+static int s3c24xx_serial_start_tx_dma(struct s3c24xx_uart_port *ourport,
+ unsigned int count)
+{
+ struct uart_port *port = &ourport->port;
+ struct circ_buf *xmit = &port->state->xmit;
+ struct s3c24xx_uart_dma *dma = ourport->dma;
+
+
+ if (ourport->tx_mode != S3C24XX_TX_DMA)
+ enable_tx_dma(ourport);
+
+ dma->tx_size = count & ~(dma_get_cache_alignment() - 1);
+ dma->tx_transfer_addr = dma->tx_addr + xmit->tail;
+
+ dma_sync_single_for_device(ourport->port.dev, dma->tx_transfer_addr,
+ dma->tx_size, DMA_TO_DEVICE);
+
+ dma->tx_desc = dmaengine_prep_slave_single(dma->tx_chan,
+ dma->tx_transfer_addr, dma->tx_size,
+ DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
+ if (!dma->tx_desc) {
+ dev_err(ourport->port.dev, "Unable to get desc for Tx\n");
+ return -EIO;
+ }
+
+ dma->tx_desc->callback = s3c24xx_serial_tx_dma_complete;
+ dma->tx_desc->callback_param = ourport;
+ dma->tx_bytes_requested = dma->tx_size;
+
+ ourport->tx_in_progress = S3C24XX_TX_DMA;
+ dma->tx_cookie = dmaengine_submit(dma->tx_desc);
+ dma_async_issue_pending(dma->tx_chan);
+ return 0;
+}
+
+static void s3c24xx_serial_start_next_tx(struct s3c24xx_uart_port *ourport)
+{
+ struct uart_port *port = &ourport->port;
+ struct circ_buf *xmit = &port->state->xmit;
+ unsigned long count;
+
+ /* Get data size up to the end of buffer */
+ count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);
+
+ if (!count) {
+ s3c24xx_serial_stop_tx(port);
+ return;
+ }
+
+ if (!ourport->dma || !ourport->dma->tx_chan ||
+ count < ourport->min_dma_size ||
+ xmit->tail & (dma_get_cache_alignment() - 1))
+ s3c24xx_serial_start_tx_pio(ourport);
+ else
+ s3c24xx_serial_start_tx_dma(ourport, count);
+}
+
+static void s3c24xx_serial_start_tx(struct uart_port *port)
+{
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+ struct circ_buf *xmit = &port->state->xmit;
+
+ if (!tx_enabled(port)) {
+ if (port->flags & UPF_CONS_FLOW)
+ s3c24xx_serial_rx_disable(port);
+
+ tx_enabled(port) = 1;
+ if (!ourport->dma || !ourport->dma->tx_chan)
+ s3c24xx_serial_start_tx_pio(ourport);
+ }
+
+ if (ourport->dma && ourport->dma->tx_chan) {
+ if (!uart_circ_empty(xmit) && !ourport->tx_in_progress)
+ s3c24xx_serial_start_next_tx(ourport);
+ }
+}
+
+static void s3c24xx_uart_copy_rx_to_tty(struct s3c24xx_uart_port *ourport,
+ struct tty_port *tty, int count)
+{
+ struct s3c24xx_uart_dma *dma = ourport->dma;
+ int copied;
+
+ if (!count)
+ return;
+
+ dma_sync_single_for_cpu(ourport->port.dev, dma->rx_addr,
+ dma->rx_size, DMA_FROM_DEVICE);
+
+ ourport->port.icount.rx += count;
+ if (!tty) {
+ dev_err(ourport->port.dev, "No tty port\n");
+ return;
+ }
+ copied = tty_insert_flip_string(tty,
+ ((unsigned char *)(ourport->dma->rx_buf)), count);
+ if (copied != count) {
+ WARN_ON(1);
+ dev_err(ourport->port.dev, "RxData copy to tty layer failed\n");
+ }
+}
+
+static void s3c24xx_serial_stop_rx(struct uart_port *port)
+{
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+ struct s3c24xx_uart_dma *dma = ourport->dma;
+ struct tty_port *t = &port->state->port;
+ struct dma_tx_state state;
+ enum dma_status dma_status;
+ unsigned int received;
+
+ if (rx_enabled(port)) {
+ dbg("s3c24xx_serial_stop_rx: port=%p\n", port);
+ if (s3c24xx_serial_has_interrupt_mask(port))
+ s3c24xx_set_bit(port, S3C64XX_UINTM_RXD,
+ S3C64XX_UINTM);
+ else
+ disable_irq_nosync(ourport->rx_irq);
+ rx_enabled(port) = 0;
+ }
+ if (dma && dma->rx_chan) {
+ dmaengine_pause(dma->tx_chan);
+ dma_status = dmaengine_tx_status(dma->rx_chan,
+ dma->rx_cookie, &state);
+ if (dma_status == DMA_IN_PROGRESS ||
+ dma_status == DMA_PAUSED) {
+ received = dma->rx_bytes_requested - state.residue;
+ dmaengine_terminate_all(dma->rx_chan);
+ s3c24xx_uart_copy_rx_to_tty(ourport, t, received);
+ }
+ }
+}
+
+static inline struct s3c24xx_uart_info
+ *s3c24xx_port_to_info(struct uart_port *port)
+{
+ return to_ourport(port)->info;
+}
+
+static inline struct s3c2410_uartcfg
+ *s3c24xx_port_to_cfg(struct uart_port *port)
+{
+ struct s3c24xx_uart_port *ourport;
+
+ if (port->dev == NULL)
+ return NULL;
+
+ ourport = container_of(port, struct s3c24xx_uart_port, port);
+ return ourport->cfg;
+}
+
+static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport,
+ unsigned long ufstat)
+{
+ struct s3c24xx_uart_info *info = ourport->info;
+
+ if (ufstat & info->rx_fifofull)
+ return ourport->port.fifosize;
+
+ return (ufstat & info->rx_fifomask) >> info->rx_fifoshift;
+}
+
+static void s3c64xx_start_rx_dma(struct s3c24xx_uart_port *ourport);
+static void s3c24xx_serial_rx_dma_complete(void *args)
+{
+ struct s3c24xx_uart_port *ourport = args;
+ struct uart_port *port = &ourport->port;
+
+ struct s3c24xx_uart_dma *dma = ourport->dma;
+ struct tty_port *t = &port->state->port;
+ struct tty_struct *tty = tty_port_tty_get(&ourport->port.state->port);
+
+ struct dma_tx_state state;
+ unsigned long flags;
+ int received;
+
+ dmaengine_tx_status(dma->rx_chan, dma->rx_cookie, &state);
+ received = dma->rx_bytes_requested - state.residue;
+ async_tx_ack(dma->rx_desc);
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ if (received)
+ s3c24xx_uart_copy_rx_to_tty(ourport, t, received);
+
+ if (tty) {
+ tty_flip_buffer_push(t);
+ tty_kref_put(tty);
+ }
+
+ s3c64xx_start_rx_dma(ourport);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void s3c64xx_start_rx_dma(struct s3c24xx_uart_port *ourport)
+{
+ struct s3c24xx_uart_dma *dma = ourport->dma;
+
+ dma_sync_single_for_device(ourport->port.dev, dma->rx_addr,
+ dma->rx_size, DMA_FROM_DEVICE);
+
+ dma->rx_desc = dmaengine_prep_slave_single(dma->rx_chan,
+ dma->rx_addr, dma->rx_size, DMA_DEV_TO_MEM,
+ DMA_PREP_INTERRUPT);
+ if (!dma->rx_desc) {
+ dev_err(ourport->port.dev, "Unable to get desc for Rx\n");
+ return;
+ }
+
+ dma->rx_desc->callback = s3c24xx_serial_rx_dma_complete;
+ dma->rx_desc->callback_param = ourport;
+ dma->rx_bytes_requested = dma->rx_size;
+
+ dma->rx_cookie = dmaengine_submit(dma->rx_desc);
+ dma_async_issue_pending(dma->rx_chan);
+}
+
+/* ? - where has parity gone?? */
+#define S3C2410_UERSTAT_PARITY (0x1000)
+
+static void enable_rx_dma(struct s3c24xx_uart_port *ourport)
+{
+ struct uart_port *port = &ourport->port;
+ unsigned int ucon;
+
+ /* set Rx mode to DMA mode */
+ ucon = rd_regl(port, S3C2410_UCON);
+ ucon &= ~(S3C64XX_UCON_RXBURST_MASK |
+ S3C64XX_UCON_TIMEOUT_MASK |
+ S3C64XX_UCON_EMPTYINT_EN |
+ S3C64XX_UCON_DMASUS_EN |
+ S3C64XX_UCON_TIMEOUT_EN |
+ S3C64XX_UCON_RXMODE_MASK);
+ ucon |= S3C64XX_UCON_RXBURST_16 |
+ 0xf << S3C64XX_UCON_TIMEOUT_SHIFT |
+ S3C64XX_UCON_EMPTYINT_EN |
+ S3C64XX_UCON_TIMEOUT_EN |
+ S3C64XX_UCON_RXMODE_DMA;
+ wr_regl(port, S3C2410_UCON, ucon);
+
+ ourport->rx_mode = S3C24XX_RX_DMA;
+}
+
+static void enable_rx_pio(struct s3c24xx_uart_port *ourport)
+{
+ struct uart_port *port = &ourport->port;
+ unsigned int ucon;
+
+ /* set Rx mode to DMA mode */
+ ucon = rd_regl(port, S3C2410_UCON);
+ ucon &= ~(S3C64XX_UCON_TIMEOUT_MASK |
+ S3C64XX_UCON_EMPTYINT_EN |
+ S3C64XX_UCON_DMASUS_EN |
+ S3C64XX_UCON_TIMEOUT_EN |
+ S3C64XX_UCON_RXMODE_MASK);
+ ucon |= 0xf << S3C64XX_UCON_TIMEOUT_SHIFT |
+ S3C64XX_UCON_TIMEOUT_EN |
+ S3C64XX_UCON_RXMODE_CPU;
+ wr_regl(port, S3C2410_UCON, ucon);
+
+ ourport->rx_mode = S3C24XX_RX_PIO;
+}
+
+static void s3c24xx_serial_rx_drain_fifo(struct s3c24xx_uart_port *ourport);
+
+static irqreturn_t s3c24xx_serial_rx_chars_dma(void *dev_id)
+{
+ unsigned int utrstat, ufstat, received;
+ struct s3c24xx_uart_port *ourport = dev_id;
+ struct uart_port *port = &ourport->port;
+ struct s3c24xx_uart_dma *dma = ourport->dma;
+ struct tty_struct *tty = tty_port_tty_get(&ourport->port.state->port);
+ struct tty_port *t = &port->state->port;
+ unsigned long flags;
+ struct dma_tx_state state;
+
+ utrstat = rd_regl(port, S3C2410_UTRSTAT);
+ ufstat = rd_regl(port, S3C2410_UFSTAT);
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ if (!(utrstat & S3C2410_UTRSTAT_TIMEOUT)) {
+ s3c64xx_start_rx_dma(ourport);
+ if (ourport->rx_mode == S3C24XX_RX_PIO)
+ enable_rx_dma(ourport);
+ goto finish;
+ }
+
+ if (ourport->rx_mode == S3C24XX_RX_DMA) {
+ dmaengine_pause(dma->rx_chan);
+ dmaengine_tx_status(dma->rx_chan, dma->rx_cookie, &state);
+ dmaengine_terminate_all(dma->rx_chan);
+ received = dma->rx_bytes_requested - state.residue;
+ s3c24xx_uart_copy_rx_to_tty(ourport, t, received);
+
+ enable_rx_pio(ourport);
+ }
+
+ s3c24xx_serial_rx_drain_fifo(ourport);
+
+ if (tty) {
+ tty_flip_buffer_push(t);
+ tty_kref_put(tty);
+ }
+
+ wr_regl(port, S3C2410_UTRSTAT, S3C2410_UTRSTAT_TIMEOUT);
+
+finish:
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static void s3c24xx_serial_rx_drain_fifo(struct s3c24xx_uart_port *ourport)
+{
+ struct uart_port *port = &ourport->port;
+ unsigned int ufcon, ch, flag, ufstat, uerstat;
+ unsigned int fifocnt = 0;
+ int max_count = port->fifosize;
+
+ while (max_count-- > 0) {
+ /*
+ * Receive all characters known to be in FIFO
+ * before reading FIFO level again
+ */
+ if (fifocnt == 0) {
+ ufstat = rd_regl(port, S3C2410_UFSTAT);
+ fifocnt = s3c24xx_serial_rx_fifocnt(ourport, ufstat);
+ if (fifocnt == 0)
+ break;
+ }
+ fifocnt--;
+
+ uerstat = rd_regl(port, S3C2410_UERSTAT);
+ ch = rd_regb(port, S3C2410_URXH);
+
+ if (port->flags & UPF_CONS_FLOW) {
+ int txe = s3c24xx_serial_txempty_nofifo(port);
+
+ if (rx_enabled(port)) {
+ if (!txe) {
+ rx_enabled(port) = 0;
+ continue;
+ }
+ } else {
+ if (txe) {
+ ufcon = rd_regl(port, S3C2410_UFCON);
+ ufcon |= S3C2410_UFCON_RESETRX;
+ wr_regl(port, S3C2410_UFCON, ufcon);
+ rx_enabled(port) = 1;
+ return;
+ }
+ continue;
+ }
+ }
+
+ /* insert the character into the buffer */
+
+ flag = TTY_NORMAL;
+ port->icount.rx++;
+
+ if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) {
+ dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n",
+ ch, uerstat);
+
+ /* check for break */
+ if (uerstat & S3C2410_UERSTAT_BREAK) {
+ dbg("break!\n");
+ port->icount.brk++;
+ if (uart_handle_break(port))
+ continue; /* Ignore character */
+ }
+
+ if (uerstat & S3C2410_UERSTAT_FRAME)
+ port->icount.frame++;
+ if (uerstat & S3C2410_UERSTAT_OVERRUN)
+ port->icount.overrun++;
+
+ uerstat &= port->read_status_mask;
+
+ if (uerstat & S3C2410_UERSTAT_BREAK)
+ flag = TTY_BREAK;
+ else if (uerstat & S3C2410_UERSTAT_PARITY)
+ flag = TTY_PARITY;
+ else if (uerstat & (S3C2410_UERSTAT_FRAME |
+ S3C2410_UERSTAT_OVERRUN))
+ flag = TTY_FRAME;
+ }
+
+ if (uart_handle_sysrq_char(port, ch))
+ continue; /* Ignore character */
+
+ uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,
+ ch, flag);
+ }
+
+ tty_flip_buffer_push(&port->state->port);
+}
+
+static irqreturn_t s3c24xx_serial_rx_chars_pio(void *dev_id)
+{
+ struct s3c24xx_uart_port *ourport = dev_id;
+ struct uart_port *port = &ourport->port;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ s3c24xx_serial_rx_drain_fifo(ourport);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+
+static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id)
+{
+ struct s3c24xx_uart_port *ourport = dev_id;
+
+ if (ourport->dma && ourport->dma->rx_chan)
+ return s3c24xx_serial_rx_chars_dma(dev_id);
+ return s3c24xx_serial_rx_chars_pio(dev_id);
+}
+
+static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
+{
+ struct s3c24xx_uart_port *ourport = id;
+ struct uart_port *port = &ourport->port;
+ struct circ_buf *xmit = &port->state->xmit;
+ unsigned long flags;
+ int count, dma_count = 0;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);
+
+ if (ourport->dma && ourport->dma->tx_chan &&
+ count >= ourport->min_dma_size) {
+ int align = dma_get_cache_alignment() -
+ (xmit->tail & (dma_get_cache_alignment() - 1));
+ if (count-align >= ourport->min_dma_size) {
+ dma_count = count-align;
+ count = align;
+ }
+ }
+
+ if (port->x_char) {
+ wr_regb(port, S3C2410_UTXH, port->x_char);
+ port->icount.tx++;
+ port->x_char = 0;
+ goto out;
+ }
+
+ /* if there isn't anything more to transmit, or the uart is now
+ * stopped, disable the uart and exit
+ */
+
+ if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
+ s3c24xx_serial_stop_tx(port);
+ goto out;
+ }
+
+ /* try and drain the buffer... */
+
+ if (count > port->fifosize) {
+ count = port->fifosize;
+ dma_count = 0;
+ }
+
+ while (!uart_circ_empty(xmit) && count > 0) {
+ if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
+ break;
+
+ wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+ port->icount.tx++;
+ count--;
+ }
+
+ if (!count && dma_count) {
+ s3c24xx_serial_start_tx_dma(ourport, dma_count);
+ goto out;
+ }
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) {
+ spin_unlock(&port->lock);
+ uart_write_wakeup(port);
+ spin_lock(&port->lock);
+ }
+
+ if (uart_circ_empty(xmit))
+ s3c24xx_serial_stop_tx(port);
+
+out:
+ spin_unlock_irqrestore(&port->lock, flags);
+ return IRQ_HANDLED;
+}
+
+/* interrupt handler for s3c64xx and later SoC's.*/
+static irqreturn_t s3c64xx_serial_handle_irq(int irq, void *id)
+{
+ struct s3c24xx_uart_port *ourport = id;
+ struct uart_port *port = &ourport->port;
+ unsigned int pend = rd_regl(port, S3C64XX_UINTP);
+ irqreturn_t ret = IRQ_HANDLED;
+
+ if (pend & S3C64XX_UINTM_RXD_MSK) {
+ ret = s3c24xx_serial_rx_chars(irq, id);
+ wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_RXD_MSK);
+ }
+ if (pend & S3C64XX_UINTM_TXD_MSK) {
+ ret = s3c24xx_serial_tx_chars(irq, id);
+ wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_TXD_MSK);
+ }
+ return ret;
+}
+
+static unsigned int s3c24xx_serial_tx_empty(struct uart_port *port)
+{
+ struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
+ unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT);
+ unsigned long ufcon = rd_regl(port, S3C2410_UFCON);
+
+ if (ufcon & S3C2410_UFCON_FIFOMODE) {
+ if ((ufstat & info->tx_fifomask) != 0 ||
+ (ufstat & info->tx_fifofull))
+ return 0;
+
+ return 1;
+ }
+
+ return s3c24xx_serial_txempty_nofifo(port);
+}
+
+/* no modem control lines */
+static unsigned int s3c24xx_serial_get_mctrl(struct uart_port *port)
+{
+ unsigned int umstat = rd_regb(port, S3C2410_UMSTAT);
+
+ if (umstat & S3C2410_UMSTAT_CTS)
+ return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
+ else
+ return TIOCM_CAR | TIOCM_DSR;
+}
+
+static void s3c24xx_serial_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+ unsigned int umcon = rd_regl(port, S3C2410_UMCON);
+
+ if (mctrl & TIOCM_RTS)
+ umcon |= S3C2410_UMCOM_RTS_LOW;
+ else
+ umcon &= ~S3C2410_UMCOM_RTS_LOW;
+
+ wr_regl(port, S3C2410_UMCON, umcon);
+}
+
+static void s3c24xx_serial_break_ctl(struct uart_port *port, int break_state)
+{
+ unsigned long flags;
+ unsigned int ucon;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ ucon = rd_regl(port, S3C2410_UCON);
+
+ if (break_state)
+ ucon |= S3C2410_UCON_SBREAK;
+ else
+ ucon &= ~S3C2410_UCON_SBREAK;
+
+ wr_regl(port, S3C2410_UCON, ucon);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static int s3c24xx_serial_request_dma(struct s3c24xx_uart_port *p)
+{
+ struct s3c24xx_uart_dma *dma = p->dma;
+ struct dma_slave_caps dma_caps;
+ const char *reason = NULL;
+ int ret;
+
+ /* Default slave configuration parameters */
+ dma->rx_conf.direction = DMA_DEV_TO_MEM;
+ dma->rx_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ dma->rx_conf.src_addr = p->port.mapbase + S3C2410_URXH;
+ dma->rx_conf.src_maxburst = 1;
+
+ dma->tx_conf.direction = DMA_MEM_TO_DEV;
+ dma->tx_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ dma->tx_conf.dst_addr = p->port.mapbase + S3C2410_UTXH;
+ dma->tx_conf.dst_maxburst = 1;
+
+ dma->rx_chan = dma_request_chan(p->port.dev, "rx");
+
+ if (IS_ERR(dma->rx_chan)) {
+ reason = "DMA RX channel request failed";
+ ret = PTR_ERR(dma->rx_chan);
+ goto err_warn;
+ }
+
+ ret = dma_get_slave_caps(dma->rx_chan, &dma_caps);
+ if (ret < 0 ||
+ dma_caps.residue_granularity < DMA_RESIDUE_GRANULARITY_BURST) {
+ reason = "insufficient DMA RX engine capabilities";
+ ret = -EOPNOTSUPP;
+ goto err_release_rx;
+ }
+
+ dmaengine_slave_config(dma->rx_chan, &dma->rx_conf);
+
+ dma->tx_chan = dma_request_chan(p->port.dev, "tx");
+ if (IS_ERR(dma->tx_chan)) {
+ reason = "DMA TX channel request failed";
+ ret = PTR_ERR(dma->tx_chan);
+ goto err_release_rx;
+ }
+
+ ret = dma_get_slave_caps(dma->tx_chan, &dma_caps);
+ if (ret < 0 ||
+ dma_caps.residue_granularity < DMA_RESIDUE_GRANULARITY_BURST) {
+ reason = "insufficient DMA TX engine capabilities";
+ ret = -EOPNOTSUPP;
+ goto err_release_tx;
+ }
+
+ dmaengine_slave_config(dma->tx_chan, &dma->tx_conf);
+
+ /* RX buffer */
+ dma->rx_size = PAGE_SIZE;
+
+ dma->rx_buf = kmalloc(dma->rx_size, GFP_KERNEL);
+ if (!dma->rx_buf) {
+ ret = -ENOMEM;
+ goto err_release_tx;
+ }
+
+ dma->rx_addr = dma_map_single(p->port.dev, dma->rx_buf,
+ dma->rx_size, DMA_FROM_DEVICE);
+ if (dma_mapping_error(p->port.dev, dma->rx_addr)) {
+ reason = "DMA mapping error for RX buffer";
+ ret = -EIO;
+ goto err_free_rx;
+ }
+
+ /* TX buffer */
+ dma->tx_addr = dma_map_single(p->port.dev, p->port.state->xmit.buf,
+ UART_XMIT_SIZE, DMA_TO_DEVICE);
+ if (dma_mapping_error(p->port.dev, dma->tx_addr)) {
+ reason = "DMA mapping error for TX buffer";
+ ret = -EIO;
+ goto err_unmap_rx;
+ }
+
+ return 0;
+
+err_unmap_rx:
+ dma_unmap_single(p->port.dev, dma->rx_addr, dma->rx_size,
+ DMA_FROM_DEVICE);
+err_free_rx:
+ kfree(dma->rx_buf);
+err_release_tx:
+ dma_release_channel(dma->tx_chan);
+err_release_rx:
+ dma_release_channel(dma->rx_chan);
+err_warn:
+ if (reason)
+ dev_warn(p->port.dev, "%s, DMA will not be used\n", reason);
+ return ret;
+}
+
+static void s3c24xx_serial_release_dma(struct s3c24xx_uart_port *p)
+{
+ struct s3c24xx_uart_dma *dma = p->dma;
+
+ if (dma->rx_chan) {
+ dmaengine_terminate_all(dma->rx_chan);
+ dma_unmap_single(p->port.dev, dma->rx_addr,
+ dma->rx_size, DMA_FROM_DEVICE);
+ kfree(dma->rx_buf);
+ dma_release_channel(dma->rx_chan);
+ dma->rx_chan = NULL;
+ }
+
+ if (dma->tx_chan) {
+ dmaengine_terminate_all(dma->tx_chan);
+ dma_unmap_single(p->port.dev, dma->tx_addr,
+ UART_XMIT_SIZE, DMA_TO_DEVICE);
+ dma_release_channel(dma->tx_chan);
+ dma->tx_chan = NULL;
+ }
+}
+
+static void s3c24xx_serial_shutdown(struct uart_port *port)
+{
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+
+ if (ourport->tx_claimed) {
+ if (!s3c24xx_serial_has_interrupt_mask(port))
+ free_irq(ourport->tx_irq, ourport);
+ tx_enabled(port) = 0;
+ ourport->tx_claimed = 0;
+ ourport->tx_mode = 0;
+ }
+
+ if (ourport->rx_claimed) {
+ if (!s3c24xx_serial_has_interrupt_mask(port))
+ free_irq(ourport->rx_irq, ourport);
+ ourport->rx_claimed = 0;
+ rx_enabled(port) = 0;
+ }
+
+ /* Clear pending interrupts and mask all interrupts */
+ if (s3c24xx_serial_has_interrupt_mask(port)) {
+ free_irq(port->irq, ourport);
+
+ wr_regl(port, S3C64XX_UINTP, 0xf);
+ wr_regl(port, S3C64XX_UINTM, 0xf);
+ }
+
+ if (ourport->dma)
+ s3c24xx_serial_release_dma(ourport);
+
+ ourport->tx_in_progress = 0;
+}
+
+static int s3c24xx_serial_startup(struct uart_port *port)
+{
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+ int ret;
+
+ dbg("s3c24xx_serial_startup: port=%p (%08llx,%p)\n",
+ port, (unsigned long long)port->mapbase, port->membase);
+
+ rx_enabled(port) = 1;
+
+ ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
+ s3c24xx_serial_portname(port), ourport);
+
+ if (ret != 0) {
+ dev_err(port->dev, "cannot get irq %d\n", ourport->rx_irq);
+ return ret;
+ }
+
+ ourport->rx_claimed = 1;
+
+ dbg("requesting tx irq...\n");
+
+ tx_enabled(port) = 1;
+
+ ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
+ s3c24xx_serial_portname(port), ourport);
+
+ if (ret) {
+ dev_err(port->dev, "cannot get irq %d\n", ourport->tx_irq);
+ goto err;
+ }
+
+ ourport->tx_claimed = 1;
+
+ dbg("s3c24xx_serial_startup ok\n");
+
+ /* the port reset code should have done the correct
+ * register setup for the port controls */
+
+ return ret;
+
+err:
+ s3c24xx_serial_shutdown(port);
+ return ret;
+}
+
+static int s3c64xx_serial_startup(struct uart_port *port)
+{
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+ unsigned long flags;
+ unsigned int ufcon;
+ int ret;
+
+ dbg("s3c64xx_serial_startup: port=%p (%08llx,%p)\n",
+ port, (unsigned long long)port->mapbase, port->membase);
+
+ wr_regl(port, S3C64XX_UINTM, 0xf);
+ if (ourport->dma) {
+ ret = s3c24xx_serial_request_dma(ourport);
+ if (ret < 0) {
+ devm_kfree(port->dev, ourport->dma);
+ ourport->dma = NULL;
+ }
+ }
+
+ ret = request_irq(port->irq, s3c64xx_serial_handle_irq, IRQF_SHARED,
+ s3c24xx_serial_portname(port), ourport);
+ if (ret) {
+ dev_err(port->dev, "cannot get irq %d\n", port->irq);
+ return ret;
+ }
+
+ /* For compatibility with s3c24xx Soc's */
+ rx_enabled(port) = 1;
+ ourport->rx_claimed = 1;
+ tx_enabled(port) = 0;
+ ourport->tx_claimed = 1;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ ufcon = rd_regl(port, S3C2410_UFCON);
+ ufcon |= S3C2410_UFCON_RESETRX | S5PV210_UFCON_RXTRIG8;
+ if (!uart_console(port))
+ ufcon |= S3C2410_UFCON_RESETTX;
+ wr_regl(port, S3C2410_UFCON, ufcon);
+
+ enable_rx_pio(ourport);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ /* Enable Rx Interrupt */
+ s3c24xx_clear_bit(port, S3C64XX_UINTM_RXD, S3C64XX_UINTM);
+
+ dbg("s3c64xx_serial_startup ok\n");
+ return ret;
+}
+
+/* power power management control */
+
+static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level,
+ unsigned int old)
+{
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+ int timeout = 10000;
+
+ ourport->pm_level = level;
+
+ switch (level) {
+ case 3:
+ while (--timeout && !s3c24xx_serial_txempty_nofifo(port))
+ udelay(100);
+
+ if (!IS_ERR(ourport->baudclk))
+ clk_disable_unprepare(ourport->baudclk);
+
+ clk_disable_unprepare(ourport->clk);
+ break;
+
+ case 0:
+ clk_prepare_enable(ourport->clk);
+
+ if (!IS_ERR(ourport->baudclk))
+ clk_prepare_enable(ourport->baudclk);
+
+ break;
+ default:
+ dev_err(port->dev, "s3c24xx_serial: unknown pm %d\n", level);
+ }
+}
+
+/* baud rate calculation
+ *
+ * The UARTs on the S3C2410/S3C2440 can take their clocks from a number
+ * of different sources, including the peripheral clock ("pclk") and an
+ * external clock ("uclk"). The S3C2440 also adds the core clock ("fclk")
+ * with a programmable extra divisor.
+ *
+ * The following code goes through the clock sources, and calculates the
+ * baud clocks (and the resultant actual baud rates) and then tries to
+ * pick the closest one and select that.
+ *
+*/
+
+#define MAX_CLK_NAME_LENGTH 15
+
+static inline int s3c24xx_serial_getsource(struct uart_port *port)
+{
+ struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
+ unsigned int ucon;
+
+ if (info->num_clks == 1)
+ return 0;
+
+ ucon = rd_regl(port, S3C2410_UCON);
+ ucon &= info->clksel_mask;
+ return ucon >> info->clksel_shift;
+}
+
+static void s3c24xx_serial_setsource(struct uart_port *port,
+ unsigned int clk_sel)
+{
+ struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
+ unsigned int ucon;
+
+ if (info->num_clks == 1)
+ return;
+
+ ucon = rd_regl(port, S3C2410_UCON);
+ if ((ucon & info->clksel_mask) >> info->clksel_shift == clk_sel)
+ return;
+
+ ucon &= ~info->clksel_mask;
+ ucon |= clk_sel << info->clksel_shift;
+ wr_regl(port, S3C2410_UCON, ucon);
+}
+
+static unsigned int s3c24xx_serial_getclk(struct s3c24xx_uart_port *ourport,
+ unsigned int req_baud, struct clk **best_clk,
+ unsigned int *clk_num)
+{
+ struct s3c24xx_uart_info *info = ourport->info;
+ struct clk *clk;
+ unsigned long rate;
+ unsigned int cnt, baud, quot, clk_sel, best_quot = 0;
+ char clkname[MAX_CLK_NAME_LENGTH];
+ int calc_deviation, deviation = (1 << 30) - 1;
+
+ clk_sel = (ourport->cfg->clk_sel) ? ourport->cfg->clk_sel :
+ ourport->info->def_clk_sel;
+ for (cnt = 0; cnt < info->num_clks; cnt++) {
+ if (!(clk_sel & (1 << cnt)))
+ continue;
+
+ sprintf(clkname, "clk_uart_baud%d", cnt);
+ clk = clk_get(ourport->port.dev, clkname);
+ if (IS_ERR(clk))
+ continue;
+
+ rate = clk_get_rate(clk);
+ if (!rate)
+ continue;
+
+ if (ourport->info->has_divslot) {
+ unsigned long div = rate / req_baud;
+
+ /* The UDIVSLOT register on the newer UARTs allows us to
+ * get a divisor adjustment of 1/16th on the baud clock.
+ *
+ * We don't keep the UDIVSLOT value (the 16ths we
+ * calculated by not multiplying the baud by 16) as it
+ * is easy enough to recalculate.
+ */
+
+ quot = div / 16;
+ baud = rate / div;
+ } else {
+ quot = (rate + (8 * req_baud)) / (16 * req_baud);
+ baud = rate / (quot * 16);
+ }
+ quot--;
+
+ calc_deviation = req_baud - baud;
+ if (calc_deviation < 0)
+ calc_deviation = -calc_deviation;
+
+ if (calc_deviation < deviation) {
+ *best_clk = clk;
+ best_quot = quot;
+ *clk_num = cnt;
+ deviation = calc_deviation;
+ }
+ }
+
+ return best_quot;
+}
+
+/* udivslot_table[]
+ *
+ * This table takes the fractional value of the baud divisor and gives
+ * the recommended setting for the UDIVSLOT register.
+ */
+static u16 udivslot_table[16] = {
+ [0] = 0x0000,
+ [1] = 0x0080,
+ [2] = 0x0808,
+ [3] = 0x0888,
+ [4] = 0x2222,
+ [5] = 0x4924,
+ [6] = 0x4A52,
+ [7] = 0x54AA,
+ [8] = 0x5555,
+ [9] = 0xD555,
+ [10] = 0xD5D5,
+ [11] = 0xDDD5,
+ [12] = 0xDDDD,
+ [13] = 0xDFDD,
+ [14] = 0xDFDF,
+ [15] = 0xFFDF,
+};
+
+static void s3c24xx_serial_set_termios(struct uart_port *port,
+ struct ktermios *termios,
+ struct ktermios *old)
+{
+ struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+ struct clk *clk = ERR_PTR(-EINVAL);
+ unsigned long flags;
+ unsigned int baud, quot, clk_sel = 0;
+ unsigned int ulcon;
+ unsigned int umcon;
+ unsigned int udivslot = 0;
+
+ /*
+ * We don't support modem control lines.
+ */
+ termios->c_cflag &= ~(HUPCL | CMSPAR);
+ termios->c_cflag |= CLOCAL;
+
+ /*
+ * Ask the core to calculate the divisor for us.
+ */
+
+ baud = uart_get_baud_rate(port, termios, old, 0, 3000000);
+ quot = s3c24xx_serial_getclk(ourport, baud, &clk, &clk_sel);
+ if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST)
+ quot = port->custom_divisor;
+ if (IS_ERR(clk))
+ return;
+
+ /* check to see if we need to change clock source */
+
+ if (ourport->baudclk != clk) {
+ clk_prepare_enable(clk);
+
+ s3c24xx_serial_setsource(port, clk_sel);
+
+ if (!IS_ERR(ourport->baudclk)) {
+ clk_disable_unprepare(ourport->baudclk);
+ ourport->baudclk = ERR_PTR(-EINVAL);
+ }
+
+ ourport->baudclk = clk;
+ ourport->baudclk_rate = clk ? clk_get_rate(clk) : 0;
+ }
+
+ if (ourport->info->has_divslot) {
+ unsigned int div = ourport->baudclk_rate / baud;
+
+ if (cfg->has_fracval) {
+ udivslot = (div & 15);
+ dbg("fracval = %04x\n", udivslot);
+ } else {
+ udivslot = udivslot_table[div & 15];
+ dbg("udivslot = %04x (div %d)\n", udivslot, div & 15);
+ }
+ }
+
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ dbg("config: 5bits/char\n");
+ ulcon = S3C2410_LCON_CS5;
+ break;
+ case CS6:
+ dbg("config: 6bits/char\n");
+ ulcon = S3C2410_LCON_CS6;
+ break;
+ case CS7:
+ dbg("config: 7bits/char\n");
+ ulcon = S3C2410_LCON_CS7;
+ break;
+ case CS8:
+ default:
+ dbg("config: 8bits/char\n");
+ ulcon = S3C2410_LCON_CS8;
+ break;
+ }
+
+ /* preserve original lcon IR settings */
+ ulcon |= (cfg->ulcon & S3C2410_LCON_IRM);
+
+ if (termios->c_cflag & CSTOPB)
+ ulcon |= S3C2410_LCON_STOPB;
+
+ if (termios->c_cflag & PARENB) {
+ if (termios->c_cflag & PARODD)
+ ulcon |= S3C2410_LCON_PODD;
+ else
+ ulcon |= S3C2410_LCON_PEVEN;
+ } else {
+ ulcon |= S3C2410_LCON_PNONE;
+ }
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ dbg("setting ulcon to %08x, brddiv to %d, udivslot %08x\n",
+ ulcon, quot, udivslot);
+
+ wr_regl(port, S3C2410_ULCON, ulcon);
+ wr_regl(port, S3C2410_UBRDIV, quot);
+
+ port->status &= ~UPSTAT_AUTOCTS;
+
+ umcon = rd_regl(port, S3C2410_UMCON);
+ if (termios->c_cflag & CRTSCTS) {
+ umcon |= S3C2410_UMCOM_AFC;
+ /* Disable RTS when RX FIFO contains 63 bytes */
+ umcon &= ~S3C2412_UMCON_AFC_8;
+ port->status = UPSTAT_AUTOCTS;
+ } else {
+ umcon &= ~S3C2410_UMCOM_AFC;
+ }
+ wr_regl(port, S3C2410_UMCON, umcon);
+
+ if (ourport->info->has_divslot)
+ wr_regl(port, S3C2443_DIVSLOT, udivslot);
+
+ dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n",
+ rd_regl(port, S3C2410_ULCON),
+ rd_regl(port, S3C2410_UCON),
+ rd_regl(port, S3C2410_UFCON));
+
+ /*
+ * Update the per-port timeout.
+ */
+ uart_update_timeout(port, termios->c_cflag, baud);
+
+ /*
+ * Which character status flags are we interested in?
+ */
+ port->read_status_mask = S3C2410_UERSTAT_OVERRUN;
+ if (termios->c_iflag & INPCK)
+ port->read_status_mask |= S3C2410_UERSTAT_FRAME |
+ S3C2410_UERSTAT_PARITY;
+ /*
+ * Which character status flags should we ignore?
+ */
+ port->ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
+ if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;
+
+ /*
+ * Ignore all characters if CREAD is not set.
+ */
+ if ((termios->c_cflag & CREAD) == 0)
+ port->ignore_status_mask |= RXSTAT_DUMMY_READ;
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static const char *s3c24xx_serial_type(struct uart_port *port)
+{
+ switch (port->type) {
+ case PORT_S3C2410:
+ return "S3C2410";
+ case PORT_S3C2440:
+ return "S3C2440";
+ case PORT_S3C2412:
+ return "S3C2412";
+ case PORT_S3C6400:
+ return "S3C6400/10";
+ default:
+ return NULL;
+ }
+}
+
+#define MAP_SIZE (0x100)
+
+static void s3c24xx_serial_release_port(struct uart_port *port)
+{
+ release_mem_region(port->mapbase, MAP_SIZE);
+}
+
+static int s3c24xx_serial_request_port(struct uart_port *port)
+{
+ const char *name = s3c24xx_serial_portname(port);
+ return request_mem_region(port->mapbase, MAP_SIZE, name) ? 0 : -EBUSY;
+}
+
+static void s3c24xx_serial_config_port(struct uart_port *port, int flags)
+{
+ struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
+
+ if (flags & UART_CONFIG_TYPE &&
+ s3c24xx_serial_request_port(port) == 0)
+ port->type = info->type;
+}
+
+/*
+ * verify the new serial_struct (for TIOCSSERIAL).
+ */
+static int
+s3c24xx_serial_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+ struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
+
+ if (ser->type != PORT_UNKNOWN && ser->type != info->type)
+ return -EINVAL;
+
+ return 0;
+}
+
+
+#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE
+
+static struct console s3c24xx_serial_console;
+
+static int __init s3c24xx_serial_console_init(void)
+{
+ register_console(&s3c24xx_serial_console);
+ return 0;
+}
+console_initcall(s3c24xx_serial_console_init);
+
+#define S3C24XX_SERIAL_CONSOLE &s3c24xx_serial_console
+#else
+#define S3C24XX_SERIAL_CONSOLE NULL
+#endif
+
+#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_CONSOLE_POLL)
+static int s3c24xx_serial_get_poll_char(struct uart_port *port);
+static void s3c24xx_serial_put_poll_char(struct uart_port *port,
+ unsigned char c);
+#endif
+
+static struct uart_ops s3c24xx_serial_ops = {
+ .pm = s3c24xx_serial_pm,
+ .tx_empty = s3c24xx_serial_tx_empty,
+ .get_mctrl = s3c24xx_serial_get_mctrl,
+ .set_mctrl = s3c24xx_serial_set_mctrl,
+ .stop_tx = s3c24xx_serial_stop_tx,
+ .start_tx = s3c24xx_serial_start_tx,
+ .stop_rx = s3c24xx_serial_stop_rx,
+ .break_ctl = s3c24xx_serial_break_ctl,
+ .startup = s3c24xx_serial_startup,
+ .shutdown = s3c24xx_serial_shutdown,
+ .set_termios = s3c24xx_serial_set_termios,
+ .type = s3c24xx_serial_type,
+ .release_port = s3c24xx_serial_release_port,
+ .request_port = s3c24xx_serial_request_port,
+ .config_port = s3c24xx_serial_config_port,
+ .verify_port = s3c24xx_serial_verify_port,
+#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_CONSOLE_POLL)
+ .poll_get_char = s3c24xx_serial_get_poll_char,
+ .poll_put_char = s3c24xx_serial_put_poll_char,
+#endif
+};
+
+static struct uart_driver s3c24xx_uart_drv = {
+ .owner = THIS_MODULE,
+ .driver_name = "s3c2410_serial",
+ .nr = CONFIG_SERIAL_SAMSUNG_UARTS,
+ .cons = S3C24XX_SERIAL_CONSOLE,
+ .dev_name = S3C24XX_SERIAL_NAME,
+ .major = S3C24XX_SERIAL_MAJOR,
+ .minor = S3C24XX_SERIAL_MINOR,
+};
+
+#define __PORT_LOCK_UNLOCKED(i) \
+ __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[i].port.lock)
+static struct s3c24xx_uart_port
+s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
+ [0] = {
+ .port = {
+ .lock = __PORT_LOCK_UNLOCKED(0),
+ .iotype = UPIO_MEM,
+ .uartclk = 0,
+ .fifosize = 16,
+ .ops = &s3c24xx_serial_ops,
+ .flags = UPF_BOOT_AUTOCONF,
+ .line = 0,
+ }
+ },
+ [1] = {
+ .port = {
+ .lock = __PORT_LOCK_UNLOCKED(1),
+ .iotype = UPIO_MEM,
+ .uartclk = 0,
+ .fifosize = 16,
+ .ops = &s3c24xx_serial_ops,
+ .flags = UPF_BOOT_AUTOCONF,
+ .line = 1,
+ }
+ },
+#if CONFIG_SERIAL_SAMSUNG_UARTS > 2
+
+ [2] = {
+ .port = {
+ .lock = __PORT_LOCK_UNLOCKED(2),
+ .iotype = UPIO_MEM,
+ .uartclk = 0,
+ .fifosize = 16,
+ .ops = &s3c24xx_serial_ops,
+ .flags = UPF_BOOT_AUTOCONF,
+ .line = 2,
+ }
+ },
+#endif
+#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
+ [3] = {
+ .port = {
+ .lock = __PORT_LOCK_UNLOCKED(3),
+ .iotype = UPIO_MEM,
+ .uartclk = 0,
+ .fifosize = 16,
+ .ops = &s3c24xx_serial_ops,
+ .flags = UPF_BOOT_AUTOCONF,
+ .line = 3,
+ }
+ }
+#endif
+};
+#undef __PORT_LOCK_UNLOCKED
+
+/* s3c24xx_serial_resetport
+ *
+ * reset the fifos and other the settings.
+*/
+
+static void s3c24xx_serial_resetport(struct uart_port *port,
+ struct s3c2410_uartcfg *cfg)
+{
+ struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
+ unsigned long ucon = rd_regl(port, S3C2410_UCON);
+ unsigned int ucon_mask;
+
+ ucon_mask = info->clksel_mask;
+ if (info->type == PORT_S3C2440)
+ ucon_mask |= S3C2440_UCON0_DIVMASK;
+
+ ucon &= ucon_mask;
+ wr_regl(port, S3C2410_UCON, ucon | cfg->ucon);
+
+ /* reset both fifos */
+ wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
+ wr_regl(port, S3C2410_UFCON, cfg->ufcon);
+
+ /* some delay is required after fifo reset */
+ udelay(1);
+}
+
+
+#ifdef CONFIG_ARM_S3C24XX_CPUFREQ
+
+static int s3c24xx_serial_cpufreq_transition(struct notifier_block *nb,
+ unsigned long val, void *data)
+{
+ struct s3c24xx_uart_port *port;
+ struct uart_port *uport;
+
+ port = container_of(nb, struct s3c24xx_uart_port, freq_transition);
+ uport = &port->port;
+
+ /* check to see if port is enabled */
+
+ if (port->pm_level != 0)
+ return 0;
+
+ /* try and work out if the baudrate is changing, we can detect
+ * a change in rate, but we do not have support for detecting
+ * a disturbance in the clock-rate over the change.
+ */
+
+ if (IS_ERR(port->baudclk))
+ goto exit;
+
+ if (port->baudclk_rate == clk_get_rate(port->baudclk))
+ goto exit;
+
+ if (val == CPUFREQ_PRECHANGE) {
+ /* we should really shut the port down whilst the
+ * frequency change is in progress. */
+
+ } else if (val == CPUFREQ_POSTCHANGE) {
+ struct ktermios *termios;
+ struct tty_struct *tty;
+
+ if (uport->state == NULL)
+ goto exit;
+
+ tty = uport->state->port.tty;
+
+ if (tty == NULL)
+ goto exit;
+
+ termios = &tty->termios;
+
+ if (termios == NULL) {
+ dev_warn(uport->dev, "%s: no termios?\n", __func__);
+ goto exit;
+ }
+
+ s3c24xx_serial_set_termios(uport, termios, NULL);
+ }
+
+exit:
+ return 0;
+}
+
+static inline int
+s3c24xx_serial_cpufreq_register(struct s3c24xx_uart_port *port)
+{
+ port->freq_transition.notifier_call = s3c24xx_serial_cpufreq_transition;
+
+ return cpufreq_register_notifier(&port->freq_transition,
+ CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+static inline void
+s3c24xx_serial_cpufreq_deregister(struct s3c24xx_uart_port *port)
+{
+ cpufreq_unregister_notifier(&port->freq_transition,
+ CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+#else
+static inline int
+s3c24xx_serial_cpufreq_register(struct s3c24xx_uart_port *port)
+{
+ return 0;
+}
+
+static inline void
+s3c24xx_serial_cpufreq_deregister(struct s3c24xx_uart_port *port)
+{
+}
+#endif
+
+static int s3c24xx_serial_enable_baudclk(struct s3c24xx_uart_port *ourport)
+{
+ struct device *dev = ourport->port.dev;
+ struct s3c24xx_uart_info *info = ourport->info;
+ char clk_name[MAX_CLK_NAME_LENGTH];
+ unsigned int clk_sel;
+ struct clk *clk;
+ int clk_num;
+ int ret;
+
+ clk_sel = ourport->cfg->clk_sel ? : info->def_clk_sel;
+ for (clk_num = 0; clk_num < info->num_clks; clk_num++) {
+ if (!(clk_sel & (1 << clk_num)))
+ continue;
+
+ sprintf(clk_name, "clk_uart_baud%d", clk_num);
+ clk = clk_get(dev, clk_name);
+ if (IS_ERR(clk))
+ continue;
+
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ clk_put(clk);
+ continue;
+ }
+
+ ourport->baudclk = clk;
+ ourport->baudclk_rate = clk_get_rate(clk);
+ s3c24xx_serial_setsource(&ourport->port, clk_num);
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/* s3c24xx_serial_init_port
+ *
+ * initialise a single serial port from the platform device given
+ */
+
+static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
+ struct platform_device *platdev)
+{
+ struct uart_port *port = &ourport->port;
+ struct s3c2410_uartcfg *cfg = ourport->cfg;
+ struct resource *res;
+ int ret;
+
+ dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev);
+
+ if (platdev == NULL)
+ return -ENODEV;
+
+ if (port->mapbase != 0)
+ return -EINVAL;
+
+ /* setup info for port */
+ port->dev = &platdev->dev;
+
+ /* Startup sequence is different for s3c64xx and higher SoC's */
+ if (s3c24xx_serial_has_interrupt_mask(port))
+ s3c24xx_serial_ops.startup = s3c64xx_serial_startup;
+
+ port->uartclk = 1;
+
+ if (cfg->uart_flags & UPF_CONS_FLOW) {
+ dbg("s3c24xx_serial_init_port: enabling flow control\n");
+ port->flags |= UPF_CONS_FLOW;
+ }
+
+ /* sort our the physical and virtual addresses for each UART */
+
+ res = platform_get_resource(platdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ dev_err(port->dev, "failed to find memory resource for uart\n");
+ return -EINVAL;
+ }
+
+ dbg("resource %pR)\n", res);
+
+ port->membase = devm_ioremap(port->dev, res->start, resource_size(res));
+ if (!port->membase) {
+ dev_err(port->dev, "failed to remap controller address\n");
+ return -EBUSY;
+ }
+
+ port->mapbase = res->start;
+ ret = platform_get_irq(platdev, 0);
+ if (ret < 0)
+ port->irq = 0;
+ else {
+ port->irq = ret;
+ ourport->rx_irq = ret;
+ ourport->tx_irq = ret + 1;
+ }
+
+ ret = platform_get_irq(platdev, 1);
+ if (ret > 0)
+ ourport->tx_irq = ret;
+ /*
+ * DMA is currently supported only on DT platforms, if DMA properties
+ * are specified.
+ */
+ if (platdev->dev.of_node && of_find_property(platdev->dev.of_node,
+ "dmas", NULL)) {
+ ourport->dma = devm_kzalloc(port->dev,
+ sizeof(*ourport->dma),
+ GFP_KERNEL);
+ if (!ourport->dma) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ }
+
+ ourport->clk = clk_get(&platdev->dev, "uart");
+ if (IS_ERR(ourport->clk)) {
+ pr_err("%s: Controller clock not found\n",
+ dev_name(&platdev->dev));
+ ret = PTR_ERR(ourport->clk);
+ goto err;
+ }
+
+ ret = clk_prepare_enable(ourport->clk);
+ if (ret) {
+ pr_err("uart: clock failed to prepare+enable: %d\n", ret);
+ clk_put(ourport->clk);
+ goto err;
+ }
+
+ ret = s3c24xx_serial_enable_baudclk(ourport);
+ if (ret)
+ pr_warn("uart: failed to enable baudclk\n");
+
+ /* Keep all interrupts masked and cleared */
+ if (s3c24xx_serial_has_interrupt_mask(port)) {
+ wr_regl(port, S3C64XX_UINTM, 0xf);
+ wr_regl(port, S3C64XX_UINTP, 0xf);
+ wr_regl(port, S3C64XX_UINTSP, 0xf);
+ }
+
+ dbg("port: map=%pa, mem=%p, irq=%d (%d,%d), clock=%u\n",
+ &port->mapbase, port->membase, port->irq,
+ ourport->rx_irq, ourport->tx_irq, port->uartclk);
+
+ /* reset the fifos (and setup the uart) */
+ s3c24xx_serial_resetport(port, cfg);
+
+ return 0;
+
+err:
+ port->mapbase = 0;
+ return ret;
+}
+
+/* Device driver serial port probe */
+
+static const struct of_device_id s3c24xx_uart_dt_match[];
+static int probe_index;
+
+static inline struct s3c24xx_serial_drv_data *s3c24xx_get_driver_data(
+ struct platform_device *pdev)
+{
+#ifdef CONFIG_OF
+ if (pdev->dev.of_node) {
+ const struct of_device_id *match;
+ match = of_match_node(s3c24xx_uart_dt_match, pdev->dev.of_node);
+ return (struct s3c24xx_serial_drv_data *)match->data;
+ }
+#endif
+ return (struct s3c24xx_serial_drv_data *)
+ platform_get_device_id(pdev)->driver_data;
+}
+
+static int s3c24xx_serial_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct s3c24xx_uart_port *ourport;
+ int index = probe_index;
+ int ret;
+
+ if (np) {
+ ret = of_alias_get_id(np, "serial");
+ if (ret >= 0)
+ index = ret;
+ }
+
+ dbg("s3c24xx_serial_probe(%p) %d\n", pdev, index);
+
+ if (index >= ARRAY_SIZE(s3c24xx_serial_ports)) {
+ dev_err(&pdev->dev, "serial%d out of range\n", index);
+ return -EINVAL;
+ }
+ ourport = &s3c24xx_serial_ports[index];
+
+ ourport->drv_data = s3c24xx_get_driver_data(pdev);
+ if (!ourport->drv_data) {
+ dev_err(&pdev->dev, "could not find driver data\n");
+ return -ENODEV;
+ }
+
+ ourport->baudclk = ERR_PTR(-EINVAL);
+ ourport->info = ourport->drv_data->info;
+ ourport->cfg = (dev_get_platdata(&pdev->dev)) ?
+ dev_get_platdata(&pdev->dev) :
+ ourport->drv_data->def_cfg;
+
+ if (np)
+ of_property_read_u32(np,
+ "samsung,uart-fifosize", &ourport->port.fifosize);
+
+ if (ourport->drv_data->fifosize[index])
+ ourport->port.fifosize = ourport->drv_data->fifosize[index];
+ else if (ourport->info->fifosize)
+ ourport->port.fifosize = ourport->info->fifosize;
+
+ /*
+ * DMA transfers must be aligned at least to cache line size,
+ * so find minimal transfer size suitable for DMA mode
+ */
+ ourport->min_dma_size = max_t(int, ourport->port.fifosize,
+ dma_get_cache_alignment());
+
+ dbg("%s: initialising port %p...\n", __func__, ourport);
+
+ ret = s3c24xx_serial_init_port(ourport, pdev);
+ if (ret < 0)
+ return ret;
+
+ if (!s3c24xx_uart_drv.state) {
+ ret = uart_register_driver(&s3c24xx_uart_drv);
+ if (ret < 0) {
+ pr_err("Failed to register Samsung UART driver\n");
+ return ret;
+ }
+ }
+
+ dbg("%s: adding port\n", __func__);
+ uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
+ platform_set_drvdata(pdev, &ourport->port);
+
+ /*
+ * Deactivate the clock enabled in s3c24xx_serial_init_port here,
+ * so that a potential re-enablement through the pm-callback overlaps
+ * and keeps the clock enabled in this case.
+ */
+ clk_disable_unprepare(ourport->clk);
+ if (!IS_ERR(ourport->baudclk))
+ clk_disable_unprepare(ourport->baudclk);
+
+ ret = s3c24xx_serial_cpufreq_register(ourport);
+ if (ret < 0)
+ dev_err(&pdev->dev, "failed to add cpufreq notifier\n");
+
+ probe_index++;
+
+ return 0;
+}
+
+static int s3c24xx_serial_remove(struct platform_device *dev)
+{
+ struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
+
+ if (port) {
+ s3c24xx_serial_cpufreq_deregister(to_ourport(port));
+ uart_remove_one_port(&s3c24xx_uart_drv, port);
+ }
+
+ uart_unregister_driver(&s3c24xx_uart_drv);
+
+ return 0;
+}
+
+/* UART power management code */
+#ifdef CONFIG_PM_SLEEP
+static int s3c24xx_serial_suspend(struct device *dev)
+{
+ struct uart_port *port = s3c24xx_dev_to_port(dev);
+
+ if (port)
+ uart_suspend_port(&s3c24xx_uart_drv, port);
+
+ return 0;
+}
+
+static int s3c24xx_serial_resume(struct device *dev)
+{
+ struct uart_port *port = s3c24xx_dev_to_port(dev);
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+
+ if (port) {
+ clk_prepare_enable(ourport->clk);
+ if (!IS_ERR(ourport->baudclk))
+ clk_prepare_enable(ourport->baudclk);
+ s3c24xx_serial_resetport(port, s3c24xx_port_to_cfg(port));
+ if (!IS_ERR(ourport->baudclk))
+ clk_disable_unprepare(ourport->baudclk);
+ clk_disable_unprepare(ourport->clk);
+
+ uart_resume_port(&s3c24xx_uart_drv, port);
+ }
+
+ return 0;
+}
+
+static int s3c24xx_serial_resume_noirq(struct device *dev)
+{
+ struct uart_port *port = s3c24xx_dev_to_port(dev);
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+
+ if (port) {
+ /* restore IRQ mask */
+ if (s3c24xx_serial_has_interrupt_mask(port)) {
+ unsigned int uintm = 0xf;
+ if (tx_enabled(port))
+ uintm &= ~S3C64XX_UINTM_TXD_MSK;
+ if (rx_enabled(port))
+ uintm &= ~S3C64XX_UINTM_RXD_MSK;
+ clk_prepare_enable(ourport->clk);
+ if (!IS_ERR(ourport->baudclk))
+ clk_prepare_enable(ourport->baudclk);
+ wr_regl(port, S3C64XX_UINTM, uintm);
+ if (!IS_ERR(ourport->baudclk))
+ clk_disable_unprepare(ourport->baudclk);
+ clk_disable_unprepare(ourport->clk);
+ }
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops s3c24xx_serial_pm_ops = {
+ .suspend = s3c24xx_serial_suspend,
+ .resume = s3c24xx_serial_resume,
+ .resume_noirq = s3c24xx_serial_resume_noirq,
+};
+#define SERIAL_SAMSUNG_PM_OPS (&s3c24xx_serial_pm_ops)
+
+#else /* !CONFIG_PM_SLEEP */
+
+#define SERIAL_SAMSUNG_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+/* Console code */
+
+#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE
+
+static struct uart_port *cons_uart;
+
+static int
+s3c24xx_serial_console_txrdy(struct uart_port *port, unsigned int ufcon)
+{
+ struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
+ unsigned long ufstat, utrstat;
+
+ if (ufcon & S3C2410_UFCON_FIFOMODE) {
+ /* fifo mode - check amount of data in fifo registers... */
+
+ ufstat = rd_regl(port, S3C2410_UFSTAT);
+ return (ufstat & info->tx_fifofull) ? 0 : 1;
+ }
+
+ /* in non-fifo mode, we go and use the tx buffer empty */
+
+ utrstat = rd_regl(port, S3C2410_UTRSTAT);
+ return (utrstat & S3C2410_UTRSTAT_TXE) ? 1 : 0;
+}
+
+static bool
+s3c24xx_port_configured(unsigned int ucon)
+{
+ /* consider the serial port configured if the tx/rx mode set */
+ return (ucon & 0xf) != 0;
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+/*
+ * Console polling routines for writing and reading from the uart while
+ * in an interrupt or debug context.
+ */
+
+static int s3c24xx_serial_get_poll_char(struct uart_port *port)
+{
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+ unsigned int ufstat;
+
+ ufstat = rd_regl(port, S3C2410_UFSTAT);
+ if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
+ return NO_POLL_CHAR;
+
+ return rd_regb(port, S3C2410_URXH);
+}
+
+static void s3c24xx_serial_put_poll_char(struct uart_port *port,
+ unsigned char c)
+{
+ unsigned int ufcon = rd_regl(port, S3C2410_UFCON);
+ unsigned int ucon = rd_regl(port, S3C2410_UCON);
+
+ /* not possible to xmit on unconfigured port */
+ if (!s3c24xx_port_configured(ucon))
+ return;
+
+ while (!s3c24xx_serial_console_txrdy(port, ufcon))
+ cpu_relax();
+ wr_regb(port, S3C2410_UTXH, c);
+}
+
+#endif /* CONFIG_CONSOLE_POLL */
+
+static void
+s3c24xx_serial_console_putchar(struct uart_port *port, int ch)
+{
+ unsigned int ufcon = rd_regl(port, S3C2410_UFCON);
+
+ while (!s3c24xx_serial_console_txrdy(port, ufcon))
+ cpu_relax();
+ wr_regb(port, S3C2410_UTXH, ch);
+}
+
+static void
+s3c24xx_serial_console_write(struct console *co, const char *s,
+ unsigned int count)
+{
+ unsigned int ucon = rd_regl(cons_uart, S3C2410_UCON);
+
+ /* not possible to xmit on unconfigured port */
+ if (!s3c24xx_port_configured(ucon))
+ return;
+
+ uart_console_write(cons_uart, s, count, s3c24xx_serial_console_putchar);
+}
+
+static void __init
+s3c24xx_serial_get_options(struct uart_port *port, int *baud,
+ int *parity, int *bits)
+{
+ struct clk *clk;
+ unsigned int ulcon;
+ unsigned int ucon;
+ unsigned int ubrdiv;
+ unsigned long rate;
+ unsigned int clk_sel;
+ char clk_name[MAX_CLK_NAME_LENGTH];
+
+ ulcon = rd_regl(port, S3C2410_ULCON);
+ ucon = rd_regl(port, S3C2410_UCON);
+ ubrdiv = rd_regl(port, S3C2410_UBRDIV);
+
+ dbg("s3c24xx_serial_get_options: port=%p\n"
+ "registers: ulcon=%08x, ucon=%08x, ubdriv=%08x\n",
+ port, ulcon, ucon, ubrdiv);
+
+ if (s3c24xx_port_configured(ucon)) {
+ switch (ulcon & S3C2410_LCON_CSMASK) {
+ case S3C2410_LCON_CS5:
+ *bits = 5;
+ break;
+ case S3C2410_LCON_CS6:
+ *bits = 6;
+ break;
+ case S3C2410_LCON_CS7:
+ *bits = 7;
+ break;
+ case S3C2410_LCON_CS8:
+ default:
+ *bits = 8;
+ break;
+ }
+
+ switch (ulcon & S3C2410_LCON_PMASK) {
+ case S3C2410_LCON_PEVEN:
+ *parity = 'e';
+ break;
+
+ case S3C2410_LCON_PODD:
+ *parity = 'o';
+ break;
+
+ case S3C2410_LCON_PNONE:
+ default:
+ *parity = 'n';
+ }
+
+ /* now calculate the baud rate */
+
+ clk_sel = s3c24xx_serial_getsource(port);
+ sprintf(clk_name, "clk_uart_baud%d", clk_sel);
+
+ clk = clk_get(port->dev, clk_name);
+ if (!IS_ERR(clk))
+ rate = clk_get_rate(clk);
+ else
+ rate = 1;
+
+ *baud = rate / (16 * (ubrdiv + 1));
+ dbg("calculated baud %d\n", *baud);
+ }
+
+}
+
+static int __init
+s3c24xx_serial_console_setup(struct console *co, char *options)
+{
+ struct uart_port *port;
+ int baud = 9600;
+ int bits = 8;
+ int parity = 'n';
+ int flow = 'n';
+
+ dbg("s3c24xx_serial_console_setup: co=%p (%d), %s\n",
+ co, co->index, options);
+
+ /* is this a valid port */
+
+ if (co->index == -1 || co->index >= CONFIG_SERIAL_SAMSUNG_UARTS)
+ co->index = 0;
+
+ port = &s3c24xx_serial_ports[co->index].port;
+
+ /* is the port configured? */
+
+ if (port->mapbase == 0x0)
+ return -ENODEV;
+
+ cons_uart = port;
+
+ dbg("s3c24xx_serial_console_setup: port=%p (%d)\n", port, co->index);
+
+ /*
+ * Check whether an invalid uart number has been specified, and
+ * if so, search for the first available port that does have
+ * console support.
+ */
+ if (options)
+ uart_parse_options(options, &baud, &parity, &bits, &flow);
+ else
+ s3c24xx_serial_get_options(port, &baud, &parity, &bits);
+
+ dbg("s3c24xx_serial_console_setup: baud %d\n", baud);
+
+ return uart_set_options(port, co, baud, parity, bits, flow);
+}
+
+static struct console s3c24xx_serial_console = {
+ .name = S3C24XX_SERIAL_NAME,
+ .device = uart_console_device,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+ .write = s3c24xx_serial_console_write,
+ .setup = s3c24xx_serial_console_setup,
+ .data = &s3c24xx_uart_drv,
+};
+#endif /* CONFIG_SERIAL_SAMSUNG_CONSOLE */
+
+#ifdef CONFIG_CPU_S3C2410
+static struct s3c24xx_serial_drv_data s3c2410_serial_drv_data = {
+ .info = &(struct s3c24xx_uart_info) {
+ .name = "Samsung S3C2410 UART",
+ .type = PORT_S3C2410,
+ .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,
+ .def_clk_sel = S3C2410_UCON_CLKSEL0,
+ .num_clks = 2,
+ .clksel_mask = S3C2410_UCON_CLKMASK,
+ .clksel_shift = S3C2410_UCON_CLKSHIFT,
+ },
+ .def_cfg = &(struct s3c2410_uartcfg) {
+ .ucon = S3C2410_UCON_DEFAULT,
+ .ufcon = S3C2410_UFCON_DEFAULT,
+ },
+};
+#define S3C2410_SERIAL_DRV_DATA ((kernel_ulong_t)&s3c2410_serial_drv_data)
+#else
+#define S3C2410_SERIAL_DRV_DATA (kernel_ulong_t)NULL
+#endif
+
+#ifdef CONFIG_CPU_S3C2412
+static struct s3c24xx_serial_drv_data s3c2412_serial_drv_data = {
+ .info = &(struct s3c24xx_uart_info) {
+ .name = "Samsung S3C2412 UART",
+ .type = PORT_S3C2412,
+ .fifosize = 64,
+ .has_divslot = 1,
+ .rx_fifomask = S3C2440_UFSTAT_RXMASK,
+ .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT,
+ .rx_fifofull = S3C2440_UFSTAT_RXFULL,
+ .tx_fifofull = S3C2440_UFSTAT_TXFULL,
+ .tx_fifomask = S3C2440_UFSTAT_TXMASK,
+ .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
+ .def_clk_sel = S3C2410_UCON_CLKSEL2,
+ .num_clks = 4,
+ .clksel_mask = S3C2412_UCON_CLKMASK,
+ .clksel_shift = S3C2412_UCON_CLKSHIFT,
+ },
+ .def_cfg = &(struct s3c2410_uartcfg) {
+ .ucon = S3C2410_UCON_DEFAULT,
+ .ufcon = S3C2410_UFCON_DEFAULT,
+ },
+};
+#define S3C2412_SERIAL_DRV_DATA ((kernel_ulong_t)&s3c2412_serial_drv_data)
+#else
+#define S3C2412_SERIAL_DRV_DATA (kernel_ulong_t)NULL
+#endif
+
+#if defined(CONFIG_CPU_S3C2440) || defined(CONFIG_CPU_S3C2416) || \
+ defined(CONFIG_CPU_S3C2443) || defined(CONFIG_CPU_S3C2442)
+static struct s3c24xx_serial_drv_data s3c2440_serial_drv_data = {
+ .info = &(struct s3c24xx_uart_info) {
+ .name = "Samsung S3C2440 UART",
+ .type = PORT_S3C2440,
+ .fifosize = 64,
+ .has_divslot = 1,
+ .rx_fifomask = S3C2440_UFSTAT_RXMASK,
+ .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT,
+ .rx_fifofull = S3C2440_UFSTAT_RXFULL,
+ .tx_fifofull = S3C2440_UFSTAT_TXFULL,
+ .tx_fifomask = S3C2440_UFSTAT_TXMASK,
+ .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
+ .def_clk_sel = S3C2410_UCON_CLKSEL2,
+ .num_clks = 4,
+ .clksel_mask = S3C2412_UCON_CLKMASK,
+ .clksel_shift = S3C2412_UCON_CLKSHIFT,
+ },
+ .def_cfg = &(struct s3c2410_uartcfg) {
+ .ucon = S3C2410_UCON_DEFAULT,
+ .ufcon = S3C2410_UFCON_DEFAULT,
+ },
+};
+#define S3C2440_SERIAL_DRV_DATA ((kernel_ulong_t)&s3c2440_serial_drv_data)
+#else
+#define S3C2440_SERIAL_DRV_DATA (kernel_ulong_t)NULL
+#endif
+
+#if defined(CONFIG_CPU_S3C6400) || defined(CONFIG_CPU_S3C6410)
+static struct s3c24xx_serial_drv_data s3c6400_serial_drv_data = {
+ .info = &(struct s3c24xx_uart_info) {
+ .name = "Samsung S3C6400 UART",
+ .type = PORT_S3C6400,
+ .fifosize = 64,
+ .has_divslot = 1,
+ .rx_fifomask = S3C2440_UFSTAT_RXMASK,
+ .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT,
+ .rx_fifofull = S3C2440_UFSTAT_RXFULL,
+ .tx_fifofull = S3C2440_UFSTAT_TXFULL,
+ .tx_fifomask = S3C2440_UFSTAT_TXMASK,
+ .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
+ .def_clk_sel = S3C2410_UCON_CLKSEL2,
+ .num_clks = 4,
+ .clksel_mask = S3C6400_UCON_CLKMASK,
+ .clksel_shift = S3C6400_UCON_CLKSHIFT,
+ },
+ .def_cfg = &(struct s3c2410_uartcfg) {
+ .ucon = S3C2410_UCON_DEFAULT,
+ .ufcon = S3C2410_UFCON_DEFAULT,
+ },
+};
+#define S3C6400_SERIAL_DRV_DATA ((kernel_ulong_t)&s3c6400_serial_drv_data)
+#else
+#define S3C6400_SERIAL_DRV_DATA (kernel_ulong_t)NULL
+#endif
+
+#ifdef CONFIG_CPU_S5PV210
+static struct s3c24xx_serial_drv_data s5pv210_serial_drv_data = {
+ .info = &(struct s3c24xx_uart_info) {
+ .name = "Samsung S5PV210 UART",
+ .type = PORT_S3C6400,
+ .has_divslot = 1,
+ .rx_fifomask = S5PV210_UFSTAT_RXMASK,
+ .rx_fifoshift = S5PV210_UFSTAT_RXSHIFT,
+ .rx_fifofull = S5PV210_UFSTAT_RXFULL,
+ .tx_fifofull = S5PV210_UFSTAT_TXFULL,
+ .tx_fifomask = S5PV210_UFSTAT_TXMASK,
+ .tx_fifoshift = S5PV210_UFSTAT_TXSHIFT,
+ .def_clk_sel = S3C2410_UCON_CLKSEL0,
+ .num_clks = 2,
+ .clksel_mask = S5PV210_UCON_CLKMASK,
+ .clksel_shift = S5PV210_UCON_CLKSHIFT,
+ },
+ .def_cfg = &(struct s3c2410_uartcfg) {
+ .ucon = S5PV210_UCON_DEFAULT,
+ .ufcon = S5PV210_UFCON_DEFAULT,
+ },
+ .fifosize = { 256, 64, 16, 16 },
+};
+#define S5PV210_SERIAL_DRV_DATA ((kernel_ulong_t)&s5pv210_serial_drv_data)
+#else
+#define S5PV210_SERIAL_DRV_DATA (kernel_ulong_t)NULL
+#endif
+
+#if defined(CONFIG_ARCH_EXYNOS)
+#define EXYNOS_COMMON_SERIAL_DRV_DATA \
+ .info = &(struct s3c24xx_uart_info) { \
+ .name = "Samsung Exynos UART", \
+ .type = PORT_S3C6400, \
+ .has_divslot = 1, \
+ .rx_fifomask = S5PV210_UFSTAT_RXMASK, \
+ .rx_fifoshift = S5PV210_UFSTAT_RXSHIFT, \
+ .rx_fifofull = S5PV210_UFSTAT_RXFULL, \
+ .tx_fifofull = S5PV210_UFSTAT_TXFULL, \
+ .tx_fifomask = S5PV210_UFSTAT_TXMASK, \
+ .tx_fifoshift = S5PV210_UFSTAT_TXSHIFT, \
+ .def_clk_sel = S3C2410_UCON_CLKSEL0, \
+ .num_clks = 1, \
+ .clksel_mask = 0, \
+ .clksel_shift = 0, \
+ }, \
+ .def_cfg = &(struct s3c2410_uartcfg) { \
+ .ucon = S5PV210_UCON_DEFAULT, \
+ .ufcon = S5PV210_UFCON_DEFAULT, \
+ .has_fracval = 1, \
+ } \
+
+static struct s3c24xx_serial_drv_data exynos4210_serial_drv_data = {
+ EXYNOS_COMMON_SERIAL_DRV_DATA,
+ .fifosize = { 256, 64, 16, 16 },
+};
+
+static struct s3c24xx_serial_drv_data exynos5433_serial_drv_data = {
+ EXYNOS_COMMON_SERIAL_DRV_DATA,
+ .fifosize = { 64, 256, 16, 256 },
+};
+
+#define EXYNOS4210_SERIAL_DRV_DATA ((kernel_ulong_t)&exynos4210_serial_drv_data)
+#define EXYNOS5433_SERIAL_DRV_DATA ((kernel_ulong_t)&exynos5433_serial_drv_data)
+#else
+#define EXYNOS4210_SERIAL_DRV_DATA (kernel_ulong_t)NULL
+#define EXYNOS5433_SERIAL_DRV_DATA (kernel_ulong_t)NULL
+#endif
+
+static const struct platform_device_id s3c24xx_serial_driver_ids[] = {
+ {
+ .name = "s3c2410-uart",
+ .driver_data = S3C2410_SERIAL_DRV_DATA,
+ }, {
+ .name = "s3c2412-uart",
+ .driver_data = S3C2412_SERIAL_DRV_DATA,
+ }, {
+ .name = "s3c2440-uart",
+ .driver_data = S3C2440_SERIAL_DRV_DATA,
+ }, {
+ .name = "s3c6400-uart",
+ .driver_data = S3C6400_SERIAL_DRV_DATA,
+ }, {
+ .name = "s5pv210-uart",
+ .driver_data = S5PV210_SERIAL_DRV_DATA,
+ }, {
+ .name = "exynos4210-uart",
+ .driver_data = EXYNOS4210_SERIAL_DRV_DATA,
+ }, {
+ .name = "exynos5433-uart",
+ .driver_data = EXYNOS5433_SERIAL_DRV_DATA,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, s3c24xx_serial_driver_ids);
+
+#ifdef CONFIG_OF
+static const struct of_device_id s3c24xx_uart_dt_match[] = {
+ { .compatible = "samsung,s3c2410-uart",
+ .data = (void *)S3C2410_SERIAL_DRV_DATA },
+ { .compatible = "samsung,s3c2412-uart",
+ .data = (void *)S3C2412_SERIAL_DRV_DATA },
+ { .compatible = "samsung,s3c2440-uart",
+ .data = (void *)S3C2440_SERIAL_DRV_DATA },
+ { .compatible = "samsung,s3c6400-uart",
+ .data = (void *)S3C6400_SERIAL_DRV_DATA },
+ { .compatible = "samsung,s5pv210-uart",
+ .data = (void *)S5PV210_SERIAL_DRV_DATA },
+ { .compatible = "samsung,exynos4210-uart",
+ .data = (void *)EXYNOS4210_SERIAL_DRV_DATA },
+ { .compatible = "samsung,exynos5433-uart",
+ .data = (void *)EXYNOS5433_SERIAL_DRV_DATA },
+ {},
+};
+MODULE_DEVICE_TABLE(of, s3c24xx_uart_dt_match);
+#endif
+
+static struct platform_driver samsung_serial_driver = {
+ .probe = s3c24xx_serial_probe,
+ .remove = s3c24xx_serial_remove,
+ .id_table = s3c24xx_serial_driver_ids,
+ .driver = {
+ .name = "samsung-uart",
+ .pm = SERIAL_SAMSUNG_PM_OPS,
+ .of_match_table = of_match_ptr(s3c24xx_uart_dt_match),
+ },
+};
+
+module_platform_driver(samsung_serial_driver);
+
+#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE
+/*
+ * Early console.
+ */
+
+struct samsung_early_console_data {
+ u32 txfull_mask;
+};
+
+static void samsung_early_busyuart(struct uart_port *port)
+{
+ while (!(readl(port->membase + S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXFE))
+ ;
+}
+
+static void samsung_early_busyuart_fifo(struct uart_port *port)
+{
+ struct samsung_early_console_data *data = port->private_data;
+
+ while (readl(port->membase + S3C2410_UFSTAT) & data->txfull_mask)
+ ;
+}
+
+static void samsung_early_putc(struct uart_port *port, int c)
+{
+ if (readl(port->membase + S3C2410_UFCON) & S3C2410_UFCON_FIFOMODE)
+ samsung_early_busyuart_fifo(port);
+ else
+ samsung_early_busyuart(port);
+
+ writeb(c, port->membase + S3C2410_UTXH);
+}
+
+static void samsung_early_write(struct console *con, const char *s, unsigned n)
+{
+ struct earlycon_device *dev = con->data;
+
+ uart_console_write(&dev->port, s, n, samsung_early_putc);
+}
+
+static int __init samsung_early_console_setup(struct earlycon_device *device,
+ const char *opt)
+{
+ if (!device->port.membase)
+ return -ENODEV;
+
+ device->con->write = samsung_early_write;
+ return 0;
+}
+
+/* S3C2410 */
+static struct samsung_early_console_data s3c2410_early_console_data = {
+ .txfull_mask = S3C2410_UFSTAT_TXFULL,
+};
+
+static int __init s3c2410_early_console_setup(struct earlycon_device *device,
+ const char *opt)
+{
+ device->port.private_data = &s3c2410_early_console_data;
+ return samsung_early_console_setup(device, opt);
+}
+OF_EARLYCON_DECLARE(s3c2410, "samsung,s3c2410-uart",
+ s3c2410_early_console_setup);
+
+/* S3C2412, S3C2440, S3C64xx */
+static struct samsung_early_console_data s3c2440_early_console_data = {
+ .txfull_mask = S3C2440_UFSTAT_TXFULL,
+};
+
+static int __init s3c2440_early_console_setup(struct earlycon_device *device,
+ const char *opt)
+{
+ device->port.private_data = &s3c2440_early_console_data;
+ return samsung_early_console_setup(device, opt);
+}
+OF_EARLYCON_DECLARE(s3c2412, "samsung,s3c2412-uart",
+ s3c2440_early_console_setup);
+OF_EARLYCON_DECLARE(s3c2440, "samsung,s3c2440-uart",
+ s3c2440_early_console_setup);
+OF_EARLYCON_DECLARE(s3c6400, "samsung,s3c6400-uart",
+ s3c2440_early_console_setup);
+
+/* S5PV210, EXYNOS */
+static struct samsung_early_console_data s5pv210_early_console_data = {
+ .txfull_mask = S5PV210_UFSTAT_TXFULL,
+};
+
+static int __init s5pv210_early_console_setup(struct earlycon_device *device,
+ const char *opt)
+{
+ device->port.private_data = &s5pv210_early_console_data;
+ return samsung_early_console_setup(device, opt);
+}
+OF_EARLYCON_DECLARE(s5pv210, "samsung,s5pv210-uart",
+ s5pv210_early_console_setup);
+OF_EARLYCON_DECLARE(exynos4210, "samsung,exynos4210-uart",
+ s5pv210_early_console_setup);
+#endif
+
+MODULE_ALIAS("platform:samsung-uart");
+MODULE_DESCRIPTION("Samsung SoC Serial port driver");
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_LICENSE("GPL v2");