uboot/drivers/serial/ns16550.c
<<
>>
Prefs
   1/*
   2 * COM1 NS16550 support
   3 * originally from linux source (arch/powerpc/boot/ns16550.c)
   4 * modified to use CONFIG_SYS_ISA_MEM and new defines
   5 */
   6
   7#include <common.h>
   8#include <dm.h>
   9#include <errno.h>
  10#include <fdtdec.h>
  11#include <ns16550.h>
  12#include <serial.h>
  13#include <watchdog.h>
  14#include <linux/types.h>
  15#include <asm/io.h>
  16
  17DECLARE_GLOBAL_DATA_PTR;
  18
  19#define UART_LCRVAL UART_LCR_8N1                /* 8 data, 1 stop, no parity */
  20#define UART_MCRVAL (UART_MCR_DTR | \
  21                     UART_MCR_RTS)              /* RTS/DTR */
  22#define UART_FCRVAL (UART_FCR_FIFO_EN | \
  23                     UART_FCR_RXSR |    \
  24                     UART_FCR_TXSR)             /* Clear & enable FIFOs */
  25
  26#ifndef CONFIG_DM_SERIAL
  27#ifdef CONFIG_SYS_NS16550_PORT_MAPPED
  28#define serial_out(x, y)        outb(x, (ulong)y)
  29#define serial_in(y)            inb((ulong)y)
  30#elif defined(CONFIG_SYS_NS16550_MEM32) && (CONFIG_SYS_NS16550_REG_SIZE > 0)
  31#define serial_out(x, y)        out_be32(y, x)
  32#define serial_in(y)            in_be32(y)
  33#elif defined(CONFIG_SYS_NS16550_MEM32) && (CONFIG_SYS_NS16550_REG_SIZE < 0)
  34#define serial_out(x, y)        out_le32(y, x)
  35#define serial_in(y)            in_le32(y)
  36#else
  37#define serial_out(x, y)        writeb(x, y)
  38#define serial_in(y)            readb(y)
  39#endif
  40#endif /* !CONFIG_DM_SERIAL */
  41
  42#if defined(CONFIG_SOC_KEYSTONE)
  43#define UART_REG_VAL_PWREMU_MGMT_UART_DISABLE   0
  44#define UART_REG_VAL_PWREMU_MGMT_UART_ENABLE ((1 << 14) | (1 << 13) | (1 << 0))
  45#undef UART_MCRVAL
  46#ifdef CONFIG_SERIAL_HW_FLOW_CONTROL
  47#define UART_MCRVAL             (UART_MCR_RTS | UART_MCR_AFE)
  48#else
  49#define UART_MCRVAL             (UART_MCR_RTS)
  50#endif
  51#endif
  52
  53#ifndef CONFIG_SYS_NS16550_IER
  54#define CONFIG_SYS_NS16550_IER  0x00
  55#endif /* CONFIG_SYS_NS16550_IER */
  56
  57#ifdef CONFIG_DM_SERIAL
  58
  59#ifndef CONFIG_SYS_NS16550_CLK
  60#define CONFIG_SYS_NS16550_CLK  0
  61#endif
  62
  63static inline void serial_out_shift(void *addr, int shift, int value)
  64{
  65#ifdef CONFIG_SYS_NS16550_PORT_MAPPED
  66        outb(value, (ulong)addr);
  67#elif defined(CONFIG_SYS_NS16550_MEM32) && !defined(CONFIG_SYS_BIG_ENDIAN)
  68        out_le32(addr, value);
  69#elif defined(CONFIG_SYS_NS16550_MEM32) && defined(CONFIG_SYS_BIG_ENDIAN)
  70        out_be32(addr, value);
  71#elif defined(CONFIG_SYS_NS16550_MEM32)
  72        writel(value, addr);
  73#elif defined(CONFIG_SYS_BIG_ENDIAN)
  74        writeb(value, addr + (1 << shift) - 1);
  75#else
  76        writeb(value, addr);
  77#endif
  78}
  79
  80static inline int serial_in_shift(void *addr, int shift)
  81{
  82#ifdef CONFIG_SYS_NS16550_PORT_MAPPED
  83        return inb((ulong)addr);
  84#elif defined(CONFIG_SYS_NS16550_MEM32) && !defined(CONFIG_SYS_BIG_ENDIAN)
  85        return in_le32(addr);
  86#elif defined(CONFIG_SYS_NS16550_MEM32) && defined(CONFIG_SYS_BIG_ENDIAN)
  87        return in_be32(addr);
  88#elif defined(CONFIG_SYS_NS16550_MEM32)
  89        return readl(addr);
  90#elif defined(CONFIG_SYS_BIG_ENDIAN)
  91        return readb(addr + (1 << shift) - 1);
  92#else
  93        return readb(addr);
  94#endif
  95}
  96
  97static void ns16550_writeb(NS16550_t port, int offset, int value)
  98{
  99        struct ns16550_platdata *plat = port->plat;
 100        unsigned char *addr;
 101
 102        offset *= 1 << plat->reg_shift;
 103        addr = map_physmem(plat->base, 0, MAP_NOCACHE) + offset;
 104        /*
 105         * As far as we know it doesn't make sense to support selection of
 106         * these options at run-time, so use the existing CONFIG options.
 107         */
 108        serial_out_shift(addr + plat->reg_offset, plat->reg_shift, value);
 109}
 110
 111static int ns16550_readb(NS16550_t port, int offset)
 112{
 113        struct ns16550_platdata *plat = port->plat;
 114        unsigned char *addr;
 115
 116        offset *= 1 << plat->reg_shift;
 117        addr = map_physmem(plat->base, 0, MAP_NOCACHE) + offset;
 118
 119        return serial_in_shift(addr + plat->reg_offset, plat->reg_shift);
 120}
 121
 122/* We can clean these up once everything is moved to driver model */
 123#define serial_out(value, addr) \
 124        ns16550_writeb(com_port, \
 125                (unsigned char *)addr - (unsigned char *)com_port, value)
 126#define serial_in(addr) \
 127        ns16550_readb(com_port, \
 128                (unsigned char *)addr - (unsigned char *)com_port)
 129#endif
 130
 131static inline int calc_divisor(NS16550_t port, int clock, int baudrate)
 132{
 133        const unsigned int mode_x_div = 16;
 134
 135        return DIV_ROUND_CLOSEST(clock, mode_x_div * baudrate);
 136}
 137
 138int ns16550_calc_divisor(NS16550_t port, int clock, int baudrate)
 139{
 140#ifdef CONFIG_OMAP1510
 141        /* If can't cleanly clock 115200 set div to 1 */
 142        if ((clock == 12000000) && (baudrate == 115200)) {
 143                port->osc_12m_sel = OSC_12M_SEL;  /* enable 6.5 * divisor */
 144                return 1;                       /* return 1 for base divisor */
 145        }
 146        port->osc_12m_sel = 0;                  /* clear if previsouly set */
 147#endif
 148
 149        return calc_divisor(port, clock, baudrate);
 150}
 151
 152static void NS16550_setbrg(NS16550_t com_port, int baud_divisor)
 153{
 154        serial_out(UART_LCR_BKSE | UART_LCRVAL, &com_port->lcr);
 155        serial_out(baud_divisor & 0xff, &com_port->dll);
 156        serial_out((baud_divisor >> 8) & 0xff, &com_port->dlm);
 157        serial_out(UART_LCRVAL, &com_port->lcr);
 158}
 159
 160void NS16550_init(NS16550_t com_port, int baud_divisor)
 161{
 162#if (defined(CONFIG_SPL_BUILD) && \
 163                (defined(CONFIG_OMAP34XX) || defined(CONFIG_OMAP44XX)))
 164        /*
 165         * On some OMAP3/OMAP4 devices when UART3 is configured for boot mode
 166         * before SPL starts only THRE bit is set. We have to empty the
 167         * transmitter before initialization starts.
 168         */
 169        if ((serial_in(&com_port->lsr) & (UART_LSR_TEMT | UART_LSR_THRE))
 170             == UART_LSR_THRE) {
 171                if (baud_divisor != -1)
 172                        NS16550_setbrg(com_port, baud_divisor);
 173                serial_out(0, &com_port->mdr1);
 174        }
 175#endif
 176
 177        while (!(serial_in(&com_port->lsr) & UART_LSR_TEMT))
 178                ;
 179
 180        serial_out(CONFIG_SYS_NS16550_IER, &com_port->ier);
 181#if defined(CONFIG_OMAP) || defined(CONFIG_AM33XX) || \
 182                        defined(CONFIG_TI81XX) || defined(CONFIG_AM43XX)
 183        serial_out(0x7, &com_port->mdr1);       /* mode select reset TL16C750*/
 184#endif
 185        serial_out(UART_MCRVAL, &com_port->mcr);
 186        serial_out(UART_FCRVAL, &com_port->fcr);
 187        if (baud_divisor != -1)
 188                NS16550_setbrg(com_port, baud_divisor);
 189#if defined(CONFIG_OMAP) || \
 190        defined(CONFIG_AM33XX) || defined(CONFIG_SOC_DA8XX) || \
 191        defined(CONFIG_TI81XX) || defined(CONFIG_AM43XX)
 192
 193        /* /16 is proper to hit 115200 with 48MHz */
 194        serial_out(0, &com_port->mdr1);
 195#endif /* CONFIG_OMAP */
 196#if defined(CONFIG_SOC_KEYSTONE)
 197        serial_out(UART_REG_VAL_PWREMU_MGMT_UART_ENABLE, &com_port->regC);
 198#endif
 199}
 200
 201#ifndef CONFIG_NS16550_MIN_FUNCTIONS
 202void NS16550_reinit(NS16550_t com_port, int baud_divisor)
 203{
 204        serial_out(CONFIG_SYS_NS16550_IER, &com_port->ier);
 205        NS16550_setbrg(com_port, 0);
 206        serial_out(UART_MCRVAL, &com_port->mcr);
 207        serial_out(UART_FCRVAL, &com_port->fcr);
 208        NS16550_setbrg(com_port, baud_divisor);
 209}
 210#endif /* CONFIG_NS16550_MIN_FUNCTIONS */
 211
 212void NS16550_putc(NS16550_t com_port, char c)
 213{
 214        while ((serial_in(&com_port->lsr) & UART_LSR_THRE) == 0)
 215                ;
 216        serial_out(c, &com_port->thr);
 217
 218        /*
 219         * Call watchdog_reset() upon newline. This is done here in putc
 220         * since the environment code uses a single puts() to print the complete
 221         * environment upon "printenv". So we can't put this watchdog call
 222         * in puts().
 223         */
 224        if (c == '\n')
 225                WATCHDOG_RESET();
 226}
 227
 228#ifndef CONFIG_NS16550_MIN_FUNCTIONS
 229char NS16550_getc(NS16550_t com_port)
 230{
 231        while ((serial_in(&com_port->lsr) & UART_LSR_DR) == 0) {
 232#if !defined(CONFIG_SPL_BUILD) && defined(CONFIG_USB_TTY)
 233                extern void usbtty_poll(void);
 234                usbtty_poll();
 235#endif
 236                WATCHDOG_RESET();
 237        }
 238        return serial_in(&com_port->rbr);
 239}
 240
 241int NS16550_tstc(NS16550_t com_port)
 242{
 243        return (serial_in(&com_port->lsr) & UART_LSR_DR) != 0;
 244}
 245
 246#endif /* CONFIG_NS16550_MIN_FUNCTIONS */
 247
 248#ifdef CONFIG_DEBUG_UART_NS16550
 249
 250#include <debug_uart.h>
 251
 252#define serial_dout(reg, value) \
 253        serial_out_shift((char *)com_port + \
 254                ((char *)reg - (char *)com_port) * \
 255                        (1 << CONFIG_DEBUG_UART_SHIFT), \
 256                CONFIG_DEBUG_UART_SHIFT, value)
 257#define serial_din(reg) \
 258        serial_in_shift((char *)com_port + \
 259                ((char *)reg - (char *)com_port) * \
 260                        (1 << CONFIG_DEBUG_UART_SHIFT), \
 261                CONFIG_DEBUG_UART_SHIFT)
 262
 263static inline void _debug_uart_init(void)
 264{
 265        struct NS16550 *com_port = (struct NS16550 *)CONFIG_DEBUG_UART_BASE;
 266        int baud_divisor;
 267
 268        /*
 269         * We copy the code from above because it is already horribly messy.
 270         * Trying to refactor to nicely remove the duplication doesn't seem
 271         * feasible. The better fix is to move all users of this driver to
 272         * driver model.
 273         */
 274        baud_divisor = calc_divisor(com_port, CONFIG_DEBUG_UART_CLOCK,
 275                                    CONFIG_BAUDRATE);
 276        serial_dout(&com_port->ier, CONFIG_SYS_NS16550_IER);
 277        serial_dout(&com_port->mcr, UART_MCRVAL);
 278        serial_dout(&com_port->fcr, UART_FCRVAL);
 279
 280        serial_dout(&com_port->lcr, UART_LCR_BKSE | UART_LCRVAL);
 281        serial_dout(&com_port->dll, baud_divisor & 0xff);
 282        serial_dout(&com_port->dlm, (baud_divisor >> 8) & 0xff);
 283        serial_dout(&com_port->lcr, UART_LCRVAL);
 284}
 285
 286static inline void _debug_uart_putc(int ch)
 287{
 288        struct NS16550 *com_port = (struct NS16550 *)CONFIG_DEBUG_UART_BASE;
 289
 290        while (!(serial_din(&com_port->lsr) & UART_LSR_THRE))
 291                ;
 292        serial_dout(&com_port->thr, ch);
 293}
 294
 295DEBUG_UART_FUNCS
 296
 297#endif
 298
 299#ifdef CONFIG_DM_SERIAL
 300static int ns16550_serial_putc(struct udevice *dev, const char ch)
 301{
 302        struct NS16550 *const com_port = dev_get_priv(dev);
 303
 304        if (!(serial_in(&com_port->lsr) & UART_LSR_THRE))
 305                return -EAGAIN;
 306        serial_out(ch, &com_port->thr);
 307
 308        /*
 309         * Call watchdog_reset() upon newline. This is done here in putc
 310         * since the environment code uses a single puts() to print the complete
 311         * environment upon "printenv". So we can't put this watchdog call
 312         * in puts().
 313         */
 314        if (ch == '\n')
 315                WATCHDOG_RESET();
 316
 317        return 0;
 318}
 319
 320static int ns16550_serial_pending(struct udevice *dev, bool input)
 321{
 322        struct NS16550 *const com_port = dev_get_priv(dev);
 323
 324        if (input)
 325                return serial_in(&com_port->lsr) & UART_LSR_DR ? 1 : 0;
 326        else
 327                return serial_in(&com_port->lsr) & UART_LSR_THRE ? 0 : 1;
 328}
 329
 330static int ns16550_serial_getc(struct udevice *dev)
 331{
 332        struct NS16550 *const com_port = dev_get_priv(dev);
 333
 334        if (!(serial_in(&com_port->lsr) & UART_LSR_DR))
 335                return -EAGAIN;
 336
 337        return serial_in(&com_port->rbr);
 338}
 339
 340static int ns16550_serial_setbrg(struct udevice *dev, int baudrate)
 341{
 342        struct NS16550 *const com_port = dev_get_priv(dev);
 343        struct ns16550_platdata *plat = com_port->plat;
 344        int clock_divisor;
 345
 346        clock_divisor = ns16550_calc_divisor(com_port, plat->clock, baudrate);
 347
 348        NS16550_setbrg(com_port, clock_divisor);
 349
 350        return 0;
 351}
 352
 353int ns16550_serial_probe(struct udevice *dev)
 354{
 355        struct NS16550 *const com_port = dev_get_priv(dev);
 356
 357        com_port->plat = dev_get_platdata(dev);
 358        NS16550_init(com_port, -1);
 359
 360        return 0;
 361}
 362
 363#if CONFIG_IS_ENABLED(OF_CONTROL)
 364int ns16550_serial_ofdata_to_platdata(struct udevice *dev)
 365{
 366        struct ns16550_platdata *plat = dev->platdata;
 367        fdt_addr_t addr;
 368
 369        /* try Processor Local Bus device first */
 370        addr = dev_get_addr(dev);
 371#if defined(CONFIG_PCI) && defined(CONFIG_DM_PCI)
 372        if (addr == FDT_ADDR_T_NONE) {
 373                /* then try pci device */
 374                struct fdt_pci_addr pci_addr;
 375                u32 bar;
 376                int ret;
 377
 378                /* we prefer to use a memory-mapped register */
 379                ret = fdtdec_get_pci_addr(gd->fdt_blob, dev->of_offset,
 380                                          FDT_PCI_SPACE_MEM32, "reg",
 381                                          &pci_addr);
 382                if (ret) {
 383                        /* try if there is any i/o-mapped register */
 384                        ret = fdtdec_get_pci_addr(gd->fdt_blob,
 385                                                  dev->of_offset,
 386                                                  FDT_PCI_SPACE_IO,
 387                                                  "reg", &pci_addr);
 388                        if (ret)
 389                                return ret;
 390                }
 391
 392                ret = fdtdec_get_pci_bar32(dev, &pci_addr, &bar);
 393                if (ret)
 394                        return ret;
 395
 396                addr = bar;
 397        }
 398#endif
 399
 400        if (addr == FDT_ADDR_T_NONE)
 401                return -EINVAL;
 402
 403        plat->base = addr;
 404        plat->reg_offset = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
 405                                     "reg-offset", 0);
 406        plat->reg_shift = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
 407                                         "reg-shift", 0);
 408        plat->clock = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
 409                                     "clock-frequency",
 410                                     CONFIG_SYS_NS16550_CLK);
 411        if (!plat->clock) {
 412                debug("ns16550 clock not defined\n");
 413                return -EINVAL;
 414        }
 415
 416        return 0;
 417}
 418#endif
 419
 420const struct dm_serial_ops ns16550_serial_ops = {
 421        .putc = ns16550_serial_putc,
 422        .pending = ns16550_serial_pending,
 423        .getc = ns16550_serial_getc,
 424        .setbrg = ns16550_serial_setbrg,
 425};
 426
 427#if CONFIG_IS_ENABLED(OF_CONTROL)
 428/*
 429 * Please consider existing compatible strings before adding a new
 430 * one to keep this table compact. Or you may add a generic "ns16550"
 431 * compatible string to your dts.
 432 */
 433static const struct udevice_id ns16550_serial_ids[] = {
 434        { .compatible = "ns16550" },
 435        { .compatible = "ns16550a" },
 436        { .compatible = "nvidia,tegra20-uart" },
 437        { .compatible = "snps,dw-apb-uart" },
 438        { .compatible = "ti,omap2-uart" },
 439        { .compatible = "ti,omap3-uart" },
 440        { .compatible = "ti,omap4-uart" },
 441        { .compatible = "ti,am3352-uart" },
 442        { .compatible = "ti,am4372-uart" },
 443        { .compatible = "ti,dra742-uart" },
 444        {}
 445};
 446#endif
 447
 448#if CONFIG_IS_ENABLED(SERIAL_PRESENT)
 449U_BOOT_DRIVER(ns16550_serial) = {
 450        .name   = "ns16550_serial",
 451        .id     = UCLASS_SERIAL,
 452#if CONFIG_IS_ENABLED(OF_CONTROL)
 453        .of_match = ns16550_serial_ids,
 454        .ofdata_to_platdata = ns16550_serial_ofdata_to_platdata,
 455        .platdata_auto_alloc_size = sizeof(struct ns16550_platdata),
 456#endif
 457        .priv_auto_alloc_size = sizeof(struct NS16550),
 458        .probe = ns16550_serial_probe,
 459        .ops    = &ns16550_serial_ops,
 460        .flags  = DM_FLAG_PRE_RELOC,
 461};
 462#endif
 463#endif /* CONFIG_DM_SERIAL */
 464