uboot/drivers/serial/serial_s5p.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * (C) Copyright 2009 SAMSUNG Electronics
   4 * Minkyu Kang <mk7.kang@samsung.com>
   5 * Heungjun Kim <riverful.kim@samsung.com>
   6 *
   7 * based on drivers/serial/s3c64xx.c
   8 */
   9
  10#include <common.h>
  11#include <dm.h>
  12#include <errno.h>
  13#include <fdtdec.h>
  14#include <asm/global_data.h>
  15#include <linux/compiler.h>
  16#include <asm/io.h>
  17#if !CONFIG_IS_ENABLED(ARCH_APPLE)
  18#include <asm/arch/clk.h>
  19#endif
  20#include <asm/arch/uart.h>
  21#include <serial.h>
  22#include <clk.h>
  23
  24DECLARE_GLOBAL_DATA_PTR;
  25
  26enum {
  27        PORT_S5P = 0,
  28        PORT_S5L
  29};
  30
  31#define S5L_RX_FIFO_COUNT_SHIFT 0
  32#define S5L_RX_FIFO_COUNT_MASK  (0xf << S5L_RX_FIFO_COUNT_SHIFT)
  33#define S5L_RX_FIFO_FULL        (1 << 8)
  34#define S5L_TX_FIFO_COUNT_SHIFT 4
  35#define S5L_TX_FIFO_COUNT_MASK  (0xf << S5L_TX_FIFO_COUNT_SHIFT)
  36#define S5L_TX_FIFO_FULL        (1 << 9)
  37
  38#define S5P_RX_FIFO_COUNT_SHIFT 0
  39#define S5P_RX_FIFO_COUNT_MASK  (0xff << S5P_RX_FIFO_COUNT_SHIFT)
  40#define S5P_RX_FIFO_FULL        (1 << 8)
  41#define S5P_TX_FIFO_COUNT_SHIFT 16
  42#define S5P_TX_FIFO_COUNT_MASK  (0xff << S5P_TX_FIFO_COUNT_SHIFT)
  43#define S5P_TX_FIFO_FULL        (1 << 24)
  44
  45/* Information about a serial port */
  46struct s5p_serial_plat {
  47        struct s5p_uart *reg;  /* address of registers in physical memory */
  48        u8 reg_width;   /* register width */
  49        u8 port_id;     /* uart port number */
  50        u8 rx_fifo_count_shift;
  51        u8 tx_fifo_count_shift;
  52        u32 rx_fifo_count_mask;
  53        u32 tx_fifo_count_mask;
  54        u32 rx_fifo_full;
  55        u32 tx_fifo_full;
  56};
  57
  58/*
  59 * The coefficient, used to calculate the baudrate on S5P UARTs is
  60 * calculated as
  61 * C = UBRDIV * 16 + number_of_set_bits_in_UDIVSLOT
  62 * however, section 31.6.11 of the datasheet doesn't recomment using 1 for 1,
  63 * 3 for 2, ... (2^n - 1) for n, instead, they suggest using these constants:
  64 */
  65static const int udivslot[] = {
  66        0,
  67        0x0080,
  68        0x0808,
  69        0x0888,
  70        0x2222,
  71        0x4924,
  72        0x4a52,
  73        0x54aa,
  74        0x5555,
  75        0xd555,
  76        0xd5d5,
  77        0xddd5,
  78        0xdddd,
  79        0xdfdd,
  80        0xdfdf,
  81        0xffdf,
  82};
  83
  84static void __maybe_unused s5p_serial_init(struct s5p_uart *uart)
  85{
  86        /* enable FIFOs, auto clear Rx FIFO */
  87        writel(0x3, &uart->ufcon);
  88        writel(0, &uart->umcon);
  89        /* 8N1 */
  90        writel(0x3, &uart->ulcon);
  91        /* No interrupts, no DMA, pure polling */
  92        writel(0x245, &uart->ucon);
  93}
  94
  95static void __maybe_unused s5p_serial_baud(struct s5p_uart *uart, u8 reg_width,
  96                                           uint uclk, int baudrate)
  97{
  98        u32 val;
  99
 100        val = uclk / baudrate;
 101
 102        writel(val / 16 - 1, &uart->ubrdiv);
 103
 104        if (s5p_uart_divslot())
 105                writew(udivslot[val % 16], &uart->rest.slot);
 106        else if (reg_width == 4)
 107                writel(val % 16, &uart->rest.value);
 108        else
 109                writeb(val % 16, &uart->rest.value);
 110}
 111
 112#ifndef CONFIG_SPL_BUILD
 113int s5p_serial_setbrg(struct udevice *dev, int baudrate)
 114{
 115        struct s5p_serial_plat *plat = dev_get_plat(dev);
 116        struct s5p_uart *const uart = plat->reg;
 117        u32 uclk;
 118
 119#if CONFIG_IS_ENABLED(CLK_EXYNOS) || CONFIG_IS_ENABLED(ARCH_APPLE)
 120        struct clk clk;
 121        u32 ret;
 122
 123        ret = clk_get_by_index(dev, 1, &clk);
 124        if (ret < 0)
 125                return ret;
 126        uclk = clk_get_rate(&clk);
 127#else
 128        uclk = get_uart_clk(plat->port_id);
 129#endif
 130
 131        s5p_serial_baud(uart, plat->reg_width, uclk, baudrate);
 132
 133        return 0;
 134}
 135
 136static int s5p_serial_probe(struct udevice *dev)
 137{
 138        struct s5p_serial_plat *plat = dev_get_plat(dev);
 139        struct s5p_uart *const uart = plat->reg;
 140
 141        s5p_serial_init(uart);
 142
 143        return 0;
 144}
 145
 146static int serial_err_check(const struct s5p_uart *const uart, int op)
 147{
 148        unsigned int mask;
 149
 150        /*
 151         * UERSTAT
 152         * Break Detect [3]
 153         * Frame Err    [2] : receive operation
 154         * Parity Err   [1] : receive operation
 155         * Overrun Err  [0] : receive operation
 156         */
 157        if (op)
 158                mask = 0x8;
 159        else
 160                mask = 0xf;
 161
 162        return readl(&uart->uerstat) & mask;
 163}
 164
 165static int s5p_serial_getc(struct udevice *dev)
 166{
 167        struct s5p_serial_plat *plat = dev_get_plat(dev);
 168        struct s5p_uart *const uart = plat->reg;
 169
 170        if (!(readl(&uart->ufstat) & plat->rx_fifo_count_mask))
 171                return -EAGAIN;
 172
 173        serial_err_check(uart, 0);
 174        if (plat->reg_width == 4)
 175                return (int)(readl(&uart->urxh) & 0xff);
 176        else
 177                return (int)(readb(&uart->urxh) & 0xff);
 178}
 179
 180static int s5p_serial_putc(struct udevice *dev, const char ch)
 181{
 182        struct s5p_serial_plat *plat = dev_get_plat(dev);
 183        struct s5p_uart *const uart = plat->reg;
 184
 185        if (readl(&uart->ufstat) & plat->tx_fifo_full)
 186                return -EAGAIN;
 187
 188        if (plat->reg_width == 4)
 189                writel(ch, &uart->utxh);
 190        else
 191                writeb(ch, &uart->utxh);
 192        serial_err_check(uart, 1);
 193
 194        return 0;
 195}
 196
 197static int s5p_serial_pending(struct udevice *dev, bool input)
 198{
 199        struct s5p_serial_plat *plat = dev_get_plat(dev);
 200        struct s5p_uart *const uart = plat->reg;
 201        uint32_t ufstat = readl(&uart->ufstat);
 202
 203        if (input) {
 204                return (ufstat & plat->rx_fifo_count_mask) >>
 205                        plat->rx_fifo_count_shift;
 206        } else {
 207                return (ufstat & plat->tx_fifo_count_mask) >>
 208                        plat->tx_fifo_count_shift;
 209        }
 210}
 211
 212static int s5p_serial_of_to_plat(struct udevice *dev)
 213{
 214        struct s5p_serial_plat *plat = dev_get_plat(dev);
 215        const ulong port_type = dev_get_driver_data(dev);
 216        fdt_addr_t addr;
 217
 218        addr = dev_read_addr(dev);
 219        if (addr == FDT_ADDR_T_NONE)
 220                return -EINVAL;
 221
 222        plat->reg = (struct s5p_uart *)addr;
 223        plat->reg_width = dev_read_u32_default(dev, "reg-io-width", 1);
 224        plat->port_id = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev),
 225                                        "id", dev_seq(dev));
 226
 227        if (port_type == PORT_S5L) {
 228                plat->rx_fifo_count_shift = S5L_RX_FIFO_COUNT_SHIFT;
 229                plat->rx_fifo_count_mask = S5L_RX_FIFO_COUNT_MASK;
 230                plat->rx_fifo_full = S5L_RX_FIFO_FULL;
 231                plat->tx_fifo_count_shift = S5L_TX_FIFO_COUNT_SHIFT;
 232                plat->tx_fifo_count_mask = S5L_TX_FIFO_COUNT_MASK;
 233                plat->tx_fifo_full = S5L_TX_FIFO_FULL;
 234        } else {
 235                plat->rx_fifo_count_shift = S5P_RX_FIFO_COUNT_SHIFT;
 236                plat->rx_fifo_count_mask = S5P_RX_FIFO_COUNT_MASK;
 237                plat->rx_fifo_full = S5P_RX_FIFO_FULL;
 238                plat->tx_fifo_count_shift = S5P_TX_FIFO_COUNT_SHIFT;
 239                plat->tx_fifo_count_mask = S5P_TX_FIFO_COUNT_MASK;
 240                plat->tx_fifo_full = S5P_TX_FIFO_FULL;
 241        }
 242
 243        return 0;
 244}
 245
 246static const struct dm_serial_ops s5p_serial_ops = {
 247        .putc = s5p_serial_putc,
 248        .pending = s5p_serial_pending,
 249        .getc = s5p_serial_getc,
 250        .setbrg = s5p_serial_setbrg,
 251};
 252
 253static const struct udevice_id s5p_serial_ids[] = {
 254        { .compatible = "samsung,exynos4210-uart",      .data = PORT_S5P },
 255        { .compatible = "apple,s5l-uart",               .data = PORT_S5L },
 256        { }
 257};
 258
 259U_BOOT_DRIVER(serial_s5p) = {
 260        .name   = "serial_s5p",
 261        .id     = UCLASS_SERIAL,
 262        .of_match = s5p_serial_ids,
 263        .of_to_plat = s5p_serial_of_to_plat,
 264        .plat_auto      = sizeof(struct s5p_serial_plat),
 265        .probe = s5p_serial_probe,
 266        .ops    = &s5p_serial_ops,
 267};
 268#endif
 269
 270#ifdef CONFIG_DEBUG_UART_S5P
 271
 272#include <debug_uart.h>
 273
 274static inline void _debug_uart_init(void)
 275{
 276        if (IS_ENABLED(CONFIG_DEBUG_UART_SKIP_INIT))
 277                return;
 278
 279        struct s5p_uart *uart = (struct s5p_uart *)CONFIG_DEBUG_UART_BASE;
 280
 281        s5p_serial_init(uart);
 282#if CONFIG_IS_ENABLED(ARCH_APPLE)
 283        s5p_serial_baud(uart, 4, CONFIG_DEBUG_UART_CLOCK, CONFIG_BAUDRATE);
 284#else
 285        s5p_serial_baud(uart, 1, CONFIG_DEBUG_UART_CLOCK, CONFIG_BAUDRATE);
 286#endif
 287}
 288
 289static inline void _debug_uart_putc(int ch)
 290{
 291        struct s5p_uart *uart = (struct s5p_uart *)CONFIG_DEBUG_UART_BASE;
 292
 293#if CONFIG_IS_ENABLED(ARCH_APPLE)
 294        while (readl(&uart->ufstat) & S5L_TX_FIFO_FULL);
 295        writel(ch, &uart->utxh);
 296#else
 297        while (readl(&uart->ufstat) & S5P_TX_FIFO_FULL);
 298        writeb(ch, &uart->utxh);
 299#endif
 300}
 301
 302DEBUG_UART_FUNCS
 303
 304#endif
 305