uboot/drivers/serial/serial_mvebu_a3700.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (C) 2016 Stefan Roese <sr@denx.de>
   4 */
   5
   6#include <common.h>
   7#include <clk.h>
   8#include <dm.h>
   9#include <serial.h>
  10#include <asm/io.h>
  11#include <asm/arch/cpu.h>
  12#include <mach/soc.h>
  13
  14struct mvebu_plat {
  15        void __iomem *base;
  16        ulong tbg_rate;
  17        u8 tbg_idx;
  18};
  19
  20/*
  21 * Register offset
  22 */
  23#define UART_RX_REG             0x00
  24#define UART_TX_REG             0x04
  25#define UART_CTRL_REG           0x08
  26#define UART_STATUS_REG         0x0c
  27#define UART_BAUD_REG           0x10
  28#define UART_POSSR_REG          0x14
  29
  30#define UART_STATUS_RX_RDY      0x10
  31#define UART_STATUS_TX_EMPTY    0x40
  32#define UART_STATUS_TXFIFO_FULL 0x800
  33
  34#define UART_CTRL_RXFIFO_RESET  0x4000
  35#define UART_CTRL_TXFIFO_RESET  0x8000
  36
  37static int mvebu_serial_putc(struct udevice *dev, const char ch)
  38{
  39        struct mvebu_plat *plat = dev_get_plat(dev);
  40        void __iomem *base = plat->base;
  41
  42        while (readl(base + UART_STATUS_REG) & UART_STATUS_TXFIFO_FULL)
  43                ;
  44
  45        writel(ch, base + UART_TX_REG);
  46
  47        return 0;
  48}
  49
  50static int mvebu_serial_getc(struct udevice *dev)
  51{
  52        struct mvebu_plat *plat = dev_get_plat(dev);
  53        void __iomem *base = plat->base;
  54
  55        while (!(readl(base + UART_STATUS_REG) & UART_STATUS_RX_RDY))
  56                ;
  57
  58        return readl(base + UART_RX_REG) & 0xff;
  59}
  60
  61static int mvebu_serial_pending(struct udevice *dev, bool input)
  62{
  63        struct mvebu_plat *plat = dev_get_plat(dev);
  64        void __iomem *base = plat->base;
  65
  66        if (input) {
  67                if (readl(base + UART_STATUS_REG) & UART_STATUS_RX_RDY)
  68                        return 1;
  69        } else {
  70                if (!(readl(base + UART_STATUS_REG) & UART_STATUS_TX_EMPTY))
  71                        return 1;
  72        }
  73
  74        return 0;
  75}
  76
  77static int mvebu_serial_setbrg(struct udevice *dev, int baudrate)
  78{
  79        struct mvebu_plat *plat = dev_get_plat(dev);
  80        void __iomem *base = plat->base;
  81        u32 divider, d1, d2;
  82        u32 oversampling;
  83
  84        /*
  85         * Calculate divider
  86         * baudrate = clock / 16 / divider
  87         */
  88        d1 = d2 = 1;
  89        divider = DIV_ROUND_CLOSEST(plat->tbg_rate, baudrate * 16 * d1 * d2);
  90
  91        /*
  92         * Set Programmable Oversampling Stack to 0,
  93         * UART defaults to 16x scheme
  94         */
  95        oversampling = 0;
  96
  97        if (divider < 1)
  98                divider = 1;
  99        else if (divider > 1023) {
 100                /*
 101                 * If divider is too high for selected baudrate then set
 102                 * divider d1 to the maximal value 6.
 103                 */
 104                d1 = 6;
 105                divider = DIV_ROUND_CLOSEST(plat->tbg_rate,
 106                                            baudrate * 16 * d1 * d2);
 107                if (divider < 1)
 108                        divider = 1;
 109                else if (divider > 1023) {
 110                        /*
 111                         * If divider is still too high then set also divider
 112                         * d2 to the maximal value 6.
 113                         */
 114                        d2 = 6;
 115                        divider = DIV_ROUND_CLOSEST(plat->tbg_rate,
 116                                                    baudrate * 16 * d1 * d2);
 117                        if (divider < 1)
 118                                divider = 1;
 119                        else if (divider > 1023) {
 120                                /*
 121                                 * And if divider is still to high then
 122                                 * use oversampling with maximal factor 63.
 123                                 */
 124                                oversampling = (63 << 0) | (63 << 8) |
 125                                              (63 << 16) | (63 << 24);
 126                                divider = DIV_ROUND_CLOSEST(plat->tbg_rate,
 127                                                baudrate * 63 * d1 * d2);
 128                                if (divider < 1)
 129                                        divider = 1;
 130                                else if (divider > 1023)
 131                                        divider = 1023;
 132                        }
 133                }
 134        }
 135
 136        divider |= BIT(19); /* Do not use XTAL as a base clock */
 137        divider |= d1 << 15; /* Set d1 divider */
 138        divider |= d2 << 12; /* Set d2 divider */
 139        divider |= plat->tbg_idx << 10; /* Use selected TBG as a base clock */
 140
 141        while (!(readl(base + UART_STATUS_REG) & UART_STATUS_TX_EMPTY))
 142                ;
 143        writel(divider, base + UART_BAUD_REG);
 144        writel(oversampling, base + UART_POSSR_REG);
 145
 146        return 0;
 147}
 148
 149static int mvebu_serial_probe(struct udevice *dev)
 150{
 151        struct mvebu_plat *plat = dev_get_plat(dev);
 152        void __iomem *base = plat->base;
 153        struct udevice *nb_clk;
 154        ofnode nb_clk_node;
 155        int i, res;
 156
 157        nb_clk_node = ofnode_by_compatible(ofnode_null(),
 158                                           "marvell,armada-3700-periph-clock-nb");
 159        if (!ofnode_valid(nb_clk_node)) {
 160                printf("%s: NB periph clock node not available\n", __func__);
 161                return -ENODEV;
 162        }
 163
 164        res = device_get_global_by_ofnode(nb_clk_node, &nb_clk);
 165        if (res) {
 166                printf("%s: Cannot get NB periph clock\n", __func__);
 167                return res;
 168        }
 169
 170        /*
 171         * Choose the TBG clock with lowest frequency which allows to configure
 172         * UART also at lower baudrates.
 173         */
 174        for (i = 0; i < 4; i++) {
 175                struct clk clk;
 176                ulong rate;
 177
 178                res = clk_get_by_index_nodev(nb_clk_node, i, &clk);
 179                if (res) {
 180                        printf("%s: Cannot get TBG clock %i: %i\n", __func__,
 181                               i, res);
 182                        return -ENODEV;
 183                }
 184
 185                rate = clk_get_rate(&clk);
 186                if (!rate || IS_ERR_VALUE(rate)) {
 187                        printf("%s: Cannot get rate for TBG clock %i\n",
 188                               __func__, i);
 189                        return -EINVAL;
 190                }
 191
 192                if (!i || plat->tbg_rate > rate) {
 193                        plat->tbg_rate = rate;
 194                        plat->tbg_idx = i;
 195                }
 196        }
 197
 198        /* reset FIFOs */
 199        writel(UART_CTRL_RXFIFO_RESET | UART_CTRL_TXFIFO_RESET,
 200               base + UART_CTRL_REG);
 201
 202        /* No Parity, 1 Stop */
 203        writel(0, base + UART_CTRL_REG);
 204
 205        return 0;
 206}
 207
 208static int mvebu_serial_remove(struct udevice *dev)
 209{
 210        struct mvebu_plat *plat = dev_get_plat(dev);
 211        void __iomem *base = plat->base;
 212        ulong new_parent_rate, parent_rate;
 213        u32 new_divider, divider;
 214        u32 new_oversampling;
 215        u32 oversampling;
 216        u32 d1, d2;
 217        u32 nb_rst;
 218
 219        /*
 220         * Switch UART base clock back to XTAL because older Linux kernel
 221         * expects it. Otherwise it does not calculate UART divisor correctly
 222         * and therefore UART does not work in kernel.
 223         */
 224        divider = readl(base + UART_BAUD_REG);
 225        if (!(divider & BIT(19))) /* UART already uses XTAL */
 226                return 0;
 227
 228        /* Read current divisors settings */
 229        d1 = (divider >> 15) & 7;
 230        d2 = (divider >> 12) & 7;
 231        parent_rate = plat->tbg_rate;
 232        divider &= 1023;
 233        oversampling = readl(base + UART_POSSR_REG) & 63;
 234        if (!oversampling)
 235                oversampling = 16;
 236
 237        /* Calculate new divisor against XTAL clock without changing baudrate */
 238        new_oversampling = 0;
 239        new_parent_rate = get_ref_clk() * 1000000;
 240        new_divider = DIV_ROUND_CLOSEST(new_parent_rate * divider * d1 * d2 *
 241                                        oversampling, parent_rate * 16);
 242
 243        /*
 244         * UART does not work reliably when XTAL divisor is smaller than 4.
 245         * In this case we do not switch UART parent to XTAL. User either
 246         * configured unsupported settings or has newer kernel with patches
 247         * which allow usage of non-XTAL clock as a parent clock.
 248         */
 249        if (new_divider < 4)
 250                return 0;
 251
 252        /*
 253         * If new divisor is larger than maximal supported, try to switch
 254         * from default x16 scheme to oversampling with maximal factor 63.
 255         */
 256        if (new_divider > 1023) {
 257                new_oversampling = 63;
 258                new_divider = DIV_ROUND_CLOSEST(new_parent_rate * divider * d1 *
 259                                                d2 * oversampling,
 260                                                parent_rate * new_oversampling);
 261                if (new_divider < 4 || new_divider > 1023)
 262                        return 0;
 263        }
 264
 265        /* wait until TX empty */
 266        while (!(readl(base + UART_STATUS_REG) & UART_STATUS_TX_EMPTY))
 267                ;
 268
 269        /* external reset of UART via North Bridge Peripheral */
 270        nb_rst = readl(MVEBU_REGISTER(0x12400));
 271        writel(nb_rst & ~BIT(3), MVEBU_REGISTER(0x12400));
 272        writel(nb_rst | BIT(3), MVEBU_REGISTER(0x12400));
 273
 274        /* set baudrate and oversampling */
 275        writel(new_divider, base + UART_BAUD_REG);
 276        writel(new_oversampling, base + UART_POSSR_REG);
 277
 278        /* No Parity, 1 Stop */
 279        writel(0, base + UART_CTRL_REG);
 280
 281        return 0;
 282}
 283
 284static int mvebu_serial_of_to_plat(struct udevice *dev)
 285{
 286        struct mvebu_plat *plat = dev_get_plat(dev);
 287
 288        plat->base = dev_read_addr_ptr(dev);
 289
 290        return 0;
 291}
 292
 293static const struct dm_serial_ops mvebu_serial_ops = {
 294        .putc = mvebu_serial_putc,
 295        .pending = mvebu_serial_pending,
 296        .getc = mvebu_serial_getc,
 297        .setbrg = mvebu_serial_setbrg,
 298};
 299
 300static const struct udevice_id mvebu_serial_ids[] = {
 301        { .compatible = "marvell,armada-3700-uart" },
 302        { }
 303};
 304
 305U_BOOT_DRIVER(serial_mvebu) = {
 306        .name   = "serial_mvebu",
 307        .id     = UCLASS_SERIAL,
 308        .of_match = mvebu_serial_ids,
 309        .of_to_plat = mvebu_serial_of_to_plat,
 310        .plat_auto      = sizeof(struct mvebu_plat),
 311        .probe  = mvebu_serial_probe,
 312        .remove = mvebu_serial_remove,
 313        .flags  = DM_FLAG_OS_PREPARE,
 314        .ops    = &mvebu_serial_ops,
 315};
 316
 317#ifdef CONFIG_DEBUG_MVEBU_A3700_UART
 318
 319#include <debug_uart.h>
 320
 321static inline void _debug_uart_init(void)
 322{
 323        void __iomem *base = (void __iomem *)CONFIG_DEBUG_UART_BASE;
 324        u32 parent_rate, divider;
 325
 326        /* reset FIFOs */
 327        writel(UART_CTRL_RXFIFO_RESET | UART_CTRL_TXFIFO_RESET,
 328               base + UART_CTRL_REG);
 329
 330        /* No Parity, 1 Stop */
 331        writel(0, base + UART_CTRL_REG);
 332
 333        /*
 334         * Calculate divider
 335         * baudrate = clock / 16 / divider
 336         */
 337        parent_rate = (readl(MVEBU_REGISTER(0x13808)) & BIT(9)) ?
 338                      40000000 : 25000000;
 339        divider = DIV_ROUND_CLOSEST(parent_rate, CONFIG_BAUDRATE * 16);
 340        writel(divider, base + UART_BAUD_REG);
 341
 342        /*
 343         * Set Programmable Oversampling Stack to 0,
 344         * UART defaults to 16x scheme
 345         */
 346        writel(0, base + UART_POSSR_REG);
 347}
 348
 349static inline void _debug_uart_putc(int ch)
 350{
 351        void __iomem *base = (void __iomem *)CONFIG_DEBUG_UART_BASE;
 352
 353        while (readl(base + UART_STATUS_REG) & UART_STATUS_TXFIFO_FULL)
 354                ;
 355
 356        writel(ch, base + UART_TX_REG);
 357}
 358
 359DEBUG_UART_FUNCS
 360#endif
 361