summaryrefslogtreecommitdiff
path: root/bsp/zynqmp-a53-dfzu2eg/drivers/serial/xilinx_uartps.c
blob: 4139f15a73627cf1ed72b29ec38e671b73d472ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
/*
 * Copyright (c) 2006-2024, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-04-11     liYony       the first version
 * 2024-07-07     GuEe-GUI     port to the DM
 */

#include <rtthread.h>
#include <rtdevice.h>

#include <cpuport.h>
#include <drivers/misc.h>
#include <drivers/serial_dm.h>

/* Register offsets for the UART. */
#define CDNS_UART_CR                0x00  /* Control Register */
#define CDNS_UART_MR                0x04  /* Mode Register */
#define CDNS_UART_IER               0x08  /* Interrupt Enable */
#define CDNS_UART_IDR               0x0c  /* Interrupt Disable */
#define CDNS_UART_IMR               0x10  /* Interrupt Mask */
#define CDNS_UART_ISR               0x14  /* Interrupt Status */
#define CDNS_UART_BAUDGEN           0x18  /* Baud Rate Generator */
#define CDNS_UART_RXTOUT            0x1c  /* RX Timeout */
#define CDNS_UART_RXWM              0x20  /* RX FIFO Trigger Level */
#define CDNS_UART_MODEMCR           0x24  /* Modem Control */
#define CDNS_UART_MODEMSR           0x28  /* Modem Status */
#define CDNS_UART_SR                0x2c  /* Channel Status */
#define CDNS_UART_FIFO              0x30  /* FIFO */
#define CDNS_UART_BAUDDIV           0x34  /* Baud Rate Divider */
#define CDNS_UART_FLOWDEL           0x38  /* Flow Delay */
#define CDNS_UART_IRRX_PWIDTH       0x3c  /* IR Min Received Pulse Width */
#define CDNS_UART_IRTX_PWIDTH       0x40  /* IR Transmitted pulse Width */
#define CDNS_UART_TXWM              0x44  /* TX FIFO Trigger Level */
#define CDNS_UART_RXBS              0x48  /* RX FIFO byte status register */

/* Control Register Bit Definitions */
#define CDNS_UART_CR_STOPBRK        0x00000100  /* Stop TX break */
#define CDNS_UART_CR_STARTBRK       0x00000080  /* Set TX break */
#define CDNS_UART_CR_TX_DIS         0x00000020  /* TX disabled. */
#define CDNS_UART_CR_TX_EN          0x00000010  /* TX enabled */
#define CDNS_UART_CR_RX_DIS         0x00000008  /* RX disabled. */
#define CDNS_UART_CR_RX_EN          0x00000004  /* RX enabled */
#define CDNS_UART_CR_TXRST          0x00000002  /* TX logic reset */
#define CDNS_UART_CR_RXRST          0x00000001  /* RX logic reset */
#define CDNS_UART_CR_RST_TO         0x00000040  /* Restart Timeout Counter */
#define CDNS_UART_RXBS_PARITY       0x00000001 /* Parity error status */
#define CDNS_UART_RXBS_FRAMING      0x00000002 /* Framing error status */
#define CDNS_UART_RXBS_BRK          0x00000004 /* Overrun error status */

/*
 * Mode Register:
 * The mode register (MR) defines the mode of transfer as well as the data
 * format. If this register is modified during transmission or reception,
 * data validity cannot be guaranteed.
 */
#define CDNS_UART_MR_CLKSEL         0x00000001  /* Pre-scalar selection */
#define CDNS_UART_MR_CHMODE_L_LOOP  0x00000200  /* Local loop back mode */
#define CDNS_UART_MR_CHMODE_NORM    0x00000000  /* Normal mode */
#define CDNS_UART_MR_CHMODE_MASK    0x00000300  /* Mask for mode bits */

#define CDNS_UART_MR_STOPMODE_2_BIT 0x00000080  /* 2 stop bits */
#define CDNS_UART_MR_STOPMODE_1_BIT 0x00000000  /* 1 stop bit */

#define CDNS_UART_MR_PARITY_NONE    0x00000020  /* No parity mode */
#define CDNS_UART_MR_PARITY_MARK    0x00000018  /* Mark parity mode */
#define CDNS_UART_MR_PARITY_SPACE   0x00000010  /* Space parity mode */
#define CDNS_UART_MR_PARITY_ODD     0x00000008  /* Odd parity mode */
#define CDNS_UART_MR_PARITY_EVEN    0x00000000  /* Even parity mode */

#define CDNS_UART_MR_CHARLEN_6_BIT  0x00000006  /* 6 bits data */
#define CDNS_UART_MR_CHARLEN_7_BIT  0x00000004  /* 7 bits data */
#define CDNS_UART_MR_CHARLEN_8_BIT  0x00000000  /* 8 bits data */

/*
 * Interrupt Registers:
 * Interrupt control logic uses the interrupt enable register (IER) and the
 * interrupt disable register (IDR) to set the value of the bits in the
 * interrupt mask register (IMR). The IMR determines whether to pass an
 * interrupt to the interrupt status register (ISR).
 * Writing a 1 to IER Enables an interrupt, writing a 1 to IDR disables an
 * interrupt. IMR and ISR are read only, and IER and IDR are write only.
 * Reading either IER or IDR returns 0x00.
 * All four registers have the same bit definitions.
 */
#define CDNS_UART_IXR_TOUT          0x00000100 /* RX Timeout error interrupt */
#define CDNS_UART_IXR_PARITY        0x00000080 /* Parity error interrupt */
#define CDNS_UART_IXR_FRAMING       0x00000040 /* Framing error interrupt */
#define CDNS_UART_IXR_OVERRUN       0x00000020 /* Overrun error interrupt */
#define CDNS_UART_IXR_TXFULL        0x00000010 /* TX FIFO Full interrupt */
#define CDNS_UART_IXR_TXEMPTY       0x00000008 /* TX FIFO empty interrupt */
#define CDNS_UART_ISR_RXEMPTY       0x00000002 /* RX FIFO empty interrupt */
#define CDNS_UART_IXR_RXTRIG        0x00000001 /* RX FIFO trigger interrupt */
#define CDNS_UART_IXR_RXFULL        0x00000004 /* RX FIFO full interrupt. */
#define CDNS_UART_IXR_RXEMPTY       0x00000002 /* RX FIFO empty interrupt. */
#define CDNS_UART_IXR_RXMASK        0x000021e7 /* Valid RX bit mask */

/*
 * Do not enable parity error interrupt for the following
 * reason: When parity error interrupt is enabled, each Rx
 * parity error always results in 2 events. The first one
 * being parity error interrupt and the second one with a
 * proper Rx interrupt with the incoming data.  Disabling
 * parity error interrupt ensures better handling of parity
 * error events. With this change, for a parity error case, we
 * get a Rx interrupt with parity error set in ISR register
 * and we still handle parity errors in the desired way.
 */

#define CDNS_UART_RX_IRQS           (CDNS_UART_IXR_FRAMING | CDNS_UART_IXR_OVERRUN | CDNS_UART_IXR_RXTRIG | CDNS_UART_IXR_TOUT)

/* Goes in read_status_mask for break detection as the HW doesn't do it*/
#define CDNS_UART_IXR_BRK           0x00002000
#define CDNS_UART_RXWM_MASK         0x0000003f /* Valid bits mask */

/*
 * Modem Control register:
 * The read/write Modem Control register controls the interface with the modem
 * or data set, or a peripheral device emulating a modem.
 */
#define CDNS_UART_MODEMCR_FCM       0x00000020 /* Automatic flow control mode */
#define CDNS_UART_MODEMCR_RTS       0x00000002 /* Request to send output control */
#define CDNS_UART_MODEMCR_DTR       0x00000001 /* Data Terminal Ready */

/*
 * Modem Status register:
 * The read/write Modem Status register reports the interface with the modem
 * or data set, or a peripheral device emulating a modem.
 */
#define CDNS_UART_MODEMSR_DCD       RT_BIT(7) /* Data Carrier Detect */
#define CDNS_UART_MODEMSR_RI        RT_BIT(6) /* Ting Indicator */
#define CDNS_UART_MODEMSR_DSR       RT_BIT(5) /* Data Set Ready */
#define CDNS_UART_MODEMSR_CTS       RT_BIT(4) /* Clear To Send */

/*
 * Channel Status Register:
 * The channel status register (CSR) is provided to enable the control logic
 * to monitor the status of bits in the channel interrupt status register,
 * even if these are masked out by the interrupt mask register.
 */
#define CDNS_UART_SR_RXEMPTY        0x00000002 /* RX FIFO empty */
#define CDNS_UART_SR_TXEMPTY        0x00000008 /* TX FIFO empty */
#define CDNS_UART_SR_TXFULL         0x00000010 /* TX FIFO full */
#define CDNS_UART_SR_RXTRIG         0x00000001 /* Rx Trigger */
#define CDNS_UART_SR_TACTIVE        0x00000800 /* TX state machine active */

/* baud dividers min/max values */
#define CDNS_UART_BDIV_MIN          4
#define CDNS_UART_BDIV_MAX          255
#define CDNS_UART_CD_MAX            65535

struct cdns_uart_soc_data
{
    rt_uint32_t quirks;
};

struct cdns_uart
{
    struct rt_serial_device parent;

    int irq;
    void *base;
    rt_ubase_t freq;
    struct rt_clk *clk;
    struct rt_clk *pclk;
    struct rt_reset_control *rstc;

    struct rt_clk_notifier clk_rate_notifier;
    struct rt_spinlock spinlock;
};

#define raw_to_cdns_uart(raw) rt_container_of(raw, struct cdns_uart, parent)

static rt_uint32_t cdns_uart_calc_baud_divs(rt_ubase_t freq, rt_uint32_t baud_rate,
        rt_uint32_t *rbdiv, rt_uint32_t *rcd, rt_int32_t *div8)
{
    rt_uint32_t cd, bdiv, calc_baud, bestbaud = 0, bauderror, besterror = ~0;

    if (baud_rate < freq / ((CDNS_UART_BDIV_MAX + 1) * CDNS_UART_CD_MAX))
    {
        *div8 = 1;
        freq /= 8;
    }
    else
    {
        *div8 = 0;
    }

    for (bdiv = CDNS_UART_BDIV_MIN; bdiv <= CDNS_UART_BDIV_MAX; ++bdiv)
    {
        cd = RT_DIV_ROUND_CLOSEST(freq, baud_rate * (bdiv + 1));

        if (cd < 1 || cd > CDNS_UART_CD_MAX)
        {
            continue;
        }

        calc_baud = freq / (cd * (bdiv + 1));

        if (baud_rate > calc_baud)
        {
            bauderror = baud_rate - calc_baud;
        }
        else
        {
            bauderror = calc_baud - baud_rate;
        }

        if (besterror > bauderror)
        {
            *rbdiv = bdiv;
            *rcd = cd;
            bestbaud = calc_baud;
            besterror = bauderror;
        }
    }

    if (((besterror * 100) / baud_rate) < 3)
    {
        bestbaud = baud_rate;
    }

    return bestbaud;
}

static rt_uint32_t cdns_uart_set_baud_rate(struct cdns_uart *cdns_uart,
        struct serial_configure *cfg)
{
    rt_int32_t div8;
    rt_uint32_t cd = 0, bdiv = 0, mreg, calc_baud;

    calc_baud = cdns_uart_calc_baud_divs(cdns_uart->freq, cfg->baud_rate, &bdiv, &cd, &div8);
    mreg = HWREG32(cdns_uart->base + CDNS_UART_MR);

    if (div8)
    {
        mreg |= CDNS_UART_MR_CLKSEL;
    }
    else
    {
        mreg &= ~CDNS_UART_MR_CLKSEL;
    }

    HWREG32(cdns_uart->base + CDNS_UART_MR) = mreg;
    HWREG32(cdns_uart->base + CDNS_UART_BAUDGEN) = cd;
    HWREG32(cdns_uart->base + CDNS_UART_BAUDDIV) = bdiv;

    return calc_baud;
}

static rt_err_t cdns_uart_uart_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
    rt_err_t err = RT_EOK;
    rt_ubase_t level;
    rt_uint32_t ctrl_reg, mode_reg, minbaud, maxbaud;
    struct cdns_uart *cdns_uart = raw_to_cdns_uart(serial);

    level = rt_spin_lock_irqsave(&cdns_uart->spinlock);

    /* Disable the TX and RX to set baud rate */
    ctrl_reg = HWREG32(cdns_uart->base + CDNS_UART_CR);
    ctrl_reg |= CDNS_UART_CR_TX_DIS | CDNS_UART_CR_RX_DIS;
    HWREG32(cdns_uart->base + CDNS_UART_CR) = ctrl_reg;

    minbaud = cdns_uart->freq / ((CDNS_UART_BDIV_MAX + 1) * CDNS_UART_CD_MAX * 8);
    maxbaud = cdns_uart->freq / (CDNS_UART_BDIV_MIN + 1);

    if (cfg->baud_rate < minbaud || cfg->baud_rate > maxbaud)
    {
        err = -RT_ENOSYS;
        goto _out_lock;
    }

    cdns_uart_set_baud_rate(cdns_uart, cfg);

    /* Set TX/RX Reset */
    ctrl_reg = HWREG32(cdns_uart->base + CDNS_UART_CR);
    ctrl_reg |= CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST;
    HWREG32(cdns_uart->base + CDNS_UART_CR) = ctrl_reg;

    while (HWREG32(cdns_uart->base + CDNS_UART_CR) & (CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST))
    {
        rt_hw_cpu_relax();
    }

    /*
     * Clear the RX disable and TX disable bits and then set the TX enable
     * bit and RX enable bit to enable the transmitter and receiver.
     */
    ctrl_reg = HWREG32(cdns_uart->base + CDNS_UART_CR);
    ctrl_reg &= ~(CDNS_UART_CR_TX_DIS | CDNS_UART_CR_RX_DIS);
    ctrl_reg |= CDNS_UART_CR_TX_EN | CDNS_UART_CR_RX_EN;
    HWREG32(cdns_uart->base + CDNS_UART_CR) = ctrl_reg;

    HWREG32(cdns_uart->base + CDNS_UART_RXTOUT) = 10;

    mode_reg = HWREG32(cdns_uart->base + CDNS_UART_MR);

    switch (cfg->data_bits)
    {
    case DATA_BITS_6:
        mode_reg |= CDNS_UART_MR_CHARLEN_6_BIT;
        break;

    case DATA_BITS_7:
        mode_reg |= CDNS_UART_MR_CHARLEN_7_BIT;
        break;

    case DATA_BITS_8:
    default:
        mode_reg |= CDNS_UART_MR_CHARLEN_8_BIT;
        break;
    }

    switch (cfg->stop_bits)
    {
    case STOP_BITS_2:
        mode_reg |= CDNS_UART_MR_STOPMODE_2_BIT;
        break;

    case STOP_BITS_1:
    default:
        mode_reg |= CDNS_UART_MR_STOPMODE_1_BIT;
        break;
    }

    switch (cfg->parity)
    {
    case PARITY_ODD:
        mode_reg |= CDNS_UART_MR_PARITY_ODD;
        break;

    case PARITY_EVEN:
        mode_reg |= CDNS_UART_MR_PARITY_EVEN;
        break;

    case PARITY_NONE:
    default:
        mode_reg |= CDNS_UART_MR_PARITY_NONE;
        break;
    }

    /* Write the mode register out */
    HWREG32(cdns_uart->base + CDNS_UART_MR) = mode_reg & 1;

_out_lock:
    rt_spin_unlock_irqrestore(&cdns_uart->spinlock, level);

    return err;
}

static rt_err_t cdns_uart_uart_control(struct rt_serial_device *serial, int cmd, void *arg)
{
    rt_uint32_t value;
    rt_ubase_t level;
    struct cdns_uart *cdns_uart = raw_to_cdns_uart(serial);

    level = rt_spin_lock_irqsave(&cdns_uart->spinlock);

    switch (cmd)
    {
    case RT_DEVICE_CTRL_CLR_INT:
        rt_hw_interrupt_mask(cdns_uart->irq);

        HWREG32(cdns_uart->base + CDNS_UART_IER) = 0;
        HWREG32(cdns_uart->base + CDNS_UART_IDR) = ~0U;
        break;

    case RT_DEVICE_CTRL_SET_INT:
        /*
         * Write the new value for the FIFO control register to it such that the
         * threshold is changed
         */

        value = 1; /* Trigger level */
        HWREG32(cdns_uart->base + CDNS_UART_RXWM) = value & CDNS_UART_RXWM_MASK;

        rt_hw_interrupt_umask(cdns_uart->irq);

        value = CDNS_UART_IXR_RXTRIG & CDNS_UART_IXR_RXMASK;
        HWREG32(cdns_uart->base + CDNS_UART_IER) = value;
        HWREG32(cdns_uart->base + CDNS_UART_IDR) = ~value;
        break;
    }

    rt_spin_unlock_irqrestore(&cdns_uart->spinlock, level);

    return RT_EOK;
}

static int cdns_uart_uart_putc(struct rt_serial_device *serial, char c)
{
    rt_ubase_t level;
    struct cdns_uart *cdns_uart = raw_to_cdns_uart(serial);

    level = rt_spin_lock_irqsave(&cdns_uart->spinlock);

    /* Wait until there is space in TX FIFO */
    while (!(HWREG32(cdns_uart->base + CDNS_UART_SR) & CDNS_UART_SR_TXEMPTY))
    {
        rt_hw_cpu_relax();
    }

    /* Write the byte into the TX FIFO */
    HWREG32(cdns_uart->base + CDNS_UART_FIFO) = c;

    /* Wait until FIFO is empty */
    while (!(HWREG32(cdns_uart->base + CDNS_UART_SR) & CDNS_UART_SR_TXEMPTY))
    {
        rt_hw_cpu_relax();
    }

    rt_spin_unlock_irqrestore(&cdns_uart->spinlock, level);

    return 1;
}

static int cdns_uart_uart_getc(struct rt_serial_device *serial)
{
    int ch = -1;
    rt_ubase_t level;
    struct cdns_uart *cdns_uart = raw_to_cdns_uart(serial);

    level = rt_spin_lock_irqsave(&cdns_uart->spinlock);

    /* Check if FIFO is empty */
    if (!(HWREG32(cdns_uart->base + CDNS_UART_SR) & CDNS_UART_SR_RXEMPTY))
    {
        ch = HWREG32(cdns_uart->base + CDNS_UART_FIFO);
    }

    rt_spin_unlock_irqrestore(&cdns_uart->spinlock, level);

    return ch;
}

static const struct rt_uart_ops cdns_uart_uart_ops =
{
    .configure = cdns_uart_uart_configure,
    .control = cdns_uart_uart_control,
    .putc = cdns_uart_uart_putc,
    .getc = cdns_uart_uart_getc,
};

static void cdns_uart_early_kick(struct rt_fdt_earlycon *con, int why)
{
    struct cdns_uart *cdns_uart = raw_to_cdns_uart(con->data);

    switch (why)
    {
    case FDT_EARLYCON_KICK_UPDATE:
        cdns_uart->base = rt_ioremap((void *)con->mmio, con->size);
        break;

    case FDT_EARLYCON_KICK_COMPLETED:
        rt_iounmap(cdns_uart->base);
        break;

    default:
        break;
    }
}

static rt_err_t cdns_uart_early_setup(struct rt_fdt_earlycon *con, const char *options)
{
    rt_uint32_t freq = 0;
    static struct cdns_uart cdns_uart = { };

    if (!con->mmio)
    {
        return -RT_ERROR;
    }

    cdns_uart.base = rt_ioremap_early((void *)con->mmio, con->size);

    HWREG32(cdns_uart.base + CDNS_UART_CR) = CDNS_UART_CR_TX_EN |
            CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST;

    con->console_putc = (typeof(con->console_putc))&cdns_uart_uart_putc;
    con->console_kick = cdns_uart_early_kick;
    con->data = &cdns_uart.parent;
    cdns_uart.parent.config = options ? serial_cfg_from_args((char *)options) :
            (typeof(cdns_uart.parent.config))RT_SERIAL_CONFIG_DEFAULT;

    fdt_getprop_u32(con->fdt, con->nodeoffset, "clock-frequency", &freq, RT_NULL);

    if ((cdns_uart.freq = freq))
    {
        rt_int32_t div8;
        rt_uint32_t cd = 0, bdiv = 0, mr = CDNS_UART_MR_PARITY_NONE;

        cdns_uart_calc_baud_divs(cdns_uart.freq,
                cdns_uart.parent.config.baud_rate, &bdiv, &cd, &div8);

        if (div8)
        {
            mr |= CDNS_UART_MR_CLKSEL;
        }

        HWREG32(cdns_uart.base + CDNS_UART_MR) = mr;
        HWREG32(cdns_uart.base + CDNS_UART_BAUDGEN) = cd;
        HWREG32(cdns_uart.base + CDNS_UART_BAUDDIV) = bdiv;
    }

    return RT_EOK;
}
RT_FDT_EARLYCON_EXPORT(xlnx_xuartps, "cdns_uart", "xlnx,xuartps", cdns_uart_early_setup);
RT_FDT_EARLYCON_EXPORT(cdns_uart_r1p8, "cdns_uart", "cdns,uart-r1p8", cdns_uart_early_setup);
RT_FDT_EARLYCON_EXPORT(cdns_uart_r1p12, "cdns_uart", "cdns,uart-r1p12", cdns_uart_early_setup);
RT_FDT_EARLYCON_EXPORT(xlnx_zynqmp_uart, "cdns_uart", "xlnx,zynqmp-uart", cdns_uart_early_setup);

static void cdns_uart_isr(int irqno, void *param)
{
    rt_uint32_t isr;
    rt_base_t level;
    rt_bool_t event = RT_FALSE;
    struct cdns_uart *cdns_uart = param;

    level = rt_spin_lock_irqsave(&cdns_uart->spinlock);

    isr = HWREG32(cdns_uart->base + CDNS_UART_ISR);
    HWREG32(cdns_uart->base + CDNS_UART_ISR) = isr;

    if ((isr & CDNS_UART_IXR_RXMASK) &&
        !(HWREG32(cdns_uart->base + CDNS_UART_CR) & CDNS_UART_CR_RX_DIS))
    {
        event = RT_TRUE;
    }

    rt_spin_unlock_irqrestore(&cdns_uart->spinlock, level);

    if (event)
    {
        rt_hw_serial_isr(&cdns_uart->parent, RT_SERIAL_EVENT_RX_IND);
    }
}

static rt_err_t cdns_uart_clk_notify(struct rt_clk_notifier *notifier,
        rt_ubase_t msg, rt_ubase_t old_rate, rt_ubase_t new_rate)
{
    rt_ubase_t level;
    rt_int32_t div8;
    rt_uint32_t ctrl_reg, cd = 0, bdiv = 0;
    struct cdns_uart *cdns_uart;

    cdns_uart = rt_container_of(notifier, struct cdns_uart, clk_rate_notifier);

    switch (msg)
    {
    case RT_CLK_MSG_PRE_RATE_CHANGE:
        if (!cdns_uart_calc_baud_divs(new_rate, cdns_uart->parent.config.baud_rate, &bdiv, &cd, &div8))
        {
            return -RT_EIO;
        }

        level = rt_spin_lock_irqsave(&cdns_uart->spinlock);

        /* Disable the TX and RX to set baud rate */
        ctrl_reg = HWREG32(cdns_uart->base + CDNS_UART_CR);
        ctrl_reg |= CDNS_UART_CR_TX_DIS | CDNS_UART_CR_RX_DIS;
        HWREG32(cdns_uart->base + CDNS_UART_CR) = ctrl_reg;

        rt_spin_unlock_irqrestore(&cdns_uart->spinlock, level);
        break;

    case RT_CLK_MSG_POST_RATE_CHANGE:
        level = rt_spin_lock_irqsave(&cdns_uart->spinlock);

        cdns_uart->freq = new_rate;
        cdns_uart_set_baud_rate(cdns_uart, &cdns_uart->parent.config);

        /* Fallthrough */
    case RT_CLK_MSG_ABORT_RATE_CHANGE:
        if (msg == RT_CLK_MSG_ABORT_RATE_CHANGE)
        {
            level = rt_spin_lock_irqsave(&cdns_uart->spinlock);
        }

        /* Set TX/RX Reset */
        ctrl_reg = HWREG32(cdns_uart->base + CDNS_UART_CR);
        ctrl_reg |= CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST;
        HWREG32(cdns_uart->base + CDNS_UART_CR) = ctrl_reg;

        while (HWREG32(cdns_uart->base + CDNS_UART_CR) & (CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST))
        {
            rt_hw_cpu_relax();
        }

        /*
         * Clear the RX disable and TX disable bits and then set the TX
         * enable bit and RX enable bit to enable the transmitter and receiver.
         */
        HWREG32(cdns_uart->base + CDNS_UART_RXTOUT) = 10;
        ctrl_reg = HWREG32(cdns_uart->base + CDNS_UART_CR);
        ctrl_reg &= ~(CDNS_UART_CR_TX_DIS | CDNS_UART_CR_RX_DIS);
        ctrl_reg |= CDNS_UART_CR_TX_EN | CDNS_UART_CR_RX_EN;
        HWREG32(cdns_uart->base + CDNS_UART_CR) = ctrl_reg;

        rt_spin_unlock_irqrestore(&cdns_uart->spinlock, level);
        break;

    default:
        break;
    }

    return RT_EOK;
}

static void cdns_uart_free(struct cdns_uart *cdns_uart)
{
    if (cdns_uart->base)
    {
        rt_iounmap(cdns_uart->base);
    }

    if (cdns_uart->clk_rate_notifier.callback)
    {
        rt_clk_notifier_unregister(cdns_uart->clk, &cdns_uart->clk_rate_notifier);
    }

    if (!rt_is_err_or_null(cdns_uart->clk))
    {
        rt_clk_disable_unprepare(cdns_uart->clk);
        rt_clk_put(cdns_uart->clk);
    }

    if (!rt_is_err_or_null(cdns_uart->pclk))
    {
        rt_clk_disable_unprepare(cdns_uart->pclk);
        rt_clk_put(cdns_uart->pclk);
    }

    if (!rt_is_err_or_null(cdns_uart->rstc))
    {
        rt_reset_control_put(cdns_uart->rstc);
    }

    rt_free(cdns_uart);
}

static rt_err_t cdns_uart_probe(struct rt_platform_device *pdev)
{
    rt_err_t err;
    const char *name;
    char isr_name[RT_NAME_MAX];
    struct rt_device *dev = &pdev->parent;
    struct cdns_uart *cdns_uart = rt_calloc(1, sizeof(*cdns_uart));
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;

    if (!cdns_uart)
    {
        return -RT_ENOMEM;
    }

    cdns_uart->base = rt_dm_dev_iomap(dev, 0);

    if (!cdns_uart->base)
    {
        err = -RT_EIO;

        goto _fail;
    }

    cdns_uart->irq = rt_dm_dev_get_irq(dev, 0);

    if (cdns_uart->irq < 0)
    {
        err = cdns_uart->irq;

        goto _fail;
    }

    cdns_uart->clk = rt_clk_get_by_name(dev, "uart_clk");

    if (rt_is_err(cdns_uart->clk))
    {
        err = rt_ptr_err(cdns_uart->clk);

        goto _fail;
    }

    if (!cdns_uart->clk)
    {
        cdns_uart->clk = rt_clk_get_by_name(dev, "ref_clk");

        if (rt_is_err(cdns_uart->clk))
        {
            err = rt_ptr_err(cdns_uart->clk);

            goto _fail;
        }
    }

    cdns_uart->pclk = rt_clk_get_by_name(dev, "pclk");

    if (rt_is_err(cdns_uart->pclk))
    {
        err = rt_ptr_err(cdns_uart->pclk);

        goto _fail;
    }

    if (!cdns_uart->pclk)
    {
        cdns_uart->pclk = rt_clk_get_by_name(dev, "aper_clk");

        if (rt_is_err(cdns_uart->clk))
        {
            err = rt_ptr_err(cdns_uart->clk);

            goto _fail;
        }
    }

    if ((err = rt_clk_prepare_enable(cdns_uart->pclk)))
    {
        goto _fail;
    }

    if ((err = rt_clk_prepare_enable(cdns_uart->clk)))
    {
        goto _fail;
    }

    cdns_uart->rstc = rt_reset_control_get_by_index(dev, 0);

    if (rt_is_err(cdns_uart->rstc))
    {
        err = rt_ptr_err(cdns_uart->rstc);

        goto _fail;
    }

    if ((err = rt_reset_control_deassert(cdns_uart->rstc)))
    {
        goto _fail;
    }

    cdns_uart->clk_rate_notifier.callback = cdns_uart_clk_notify;
    if ((err = rt_clk_notifier_register(cdns_uart->clk, &cdns_uart->clk_rate_notifier)))
    {
        cdns_uart->clk_rate_notifier.callback = RT_NULL;
        goto _fail;
    }

    cdns_uart->freq = rt_clk_get_rate(cdns_uart->clk);

    rt_spin_lock_init(&cdns_uart->spinlock);

    rt_dm_dev_bind_fwdata(&cdns_uart->parent.parent, dev->ofw_node, &cdns_uart->parent);
    dev->user_data = cdns_uart;

    serial_dev_set_name(&cdns_uart->parent);
    name = rt_dm_dev_get_name(&cdns_uart->parent.parent);

    cdns_uart->parent.ops = &cdns_uart_uart_ops;
    cdns_uart->parent.config = config;

    rt_hw_serial_register(&cdns_uart->parent, name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, cdns_uart);
    rt_snprintf(isr_name, RT_NAME_MAX, "%s-cdns_uart", name);
    rt_hw_interrupt_install(cdns_uart->irq, cdns_uart_isr, cdns_uart, isr_name);

    return RT_EOK;

_fail:
    cdns_uart_free(cdns_uart);

    return err;
}

static rt_err_t cdns_uart_remove(struct rt_platform_device *pdev)
{
    rt_uint32_t status;
    struct rt_device *dev = &pdev->parent;
    struct cdns_uart *cdns_uart = dev->user_data;

    /* Disable interrupts */
    status = HWREG32(cdns_uart->base + CDNS_UART_IMR);
    HWREG32(cdns_uart->base + CDNS_UART_IDR) = status;
    HWREG32(cdns_uart->base + CDNS_UART_ISR) = 0xffffffffU;

    /* Disable the TX and RX */
    HWREG32(cdns_uart->base + CDNS_UART_CR) = CDNS_UART_CR_TX_DIS | CDNS_UART_CR_RX_DIS;

    rt_dm_dev_unbind_fwdata(dev, RT_NULL);

    rt_hw_interrupt_mask(cdns_uart->irq);
    rt_pic_detach_irq(cdns_uart->irq, cdns_uart);

    rt_device_unregister(&cdns_uart->parent.parent);

    rt_reset_control_assert(cdns_uart->rstc);

    cdns_uart_free(cdns_uart);

    return RT_EOK;
}

static const struct rt_ofw_node_id cdns_uart_ofw_ids[] =
{
    { .type = "ttyPS", .compatible = "xlnx,xuartps", },
    { .type = "ttyPS", .compatible = "cdns,uart-r1p8", },
    { .type = "ttyPS", .compatible = "cdns,uart-r1p12", },
    { .type = "ttyPS", .compatible = "xlnx,zynqmp-uart", },
    { /* sentinel */ }
};

static struct rt_platform_driver cdns_uart_driver =
{
    .name = "serial-cdns-uart",
    .ids = cdns_uart_ofw_ids,

    .probe = cdns_uart_probe,
    .remove = cdns_uart_remove,
};

static int cdns_uart_drv_register(void)
{
    rt_platform_driver_register(&cdns_uart_driver);

    return 0;
}
INIT_PLATFORM_EXPORT(cdns_uart_drv_register);