mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-02-08 11:54:50 +08:00
Some checks failed
ToolsCI / Tools (push) Has been cancelled
RT-Thread BSP Static Build Check / 🔍 Summary of Git Diff Changes (push) Has been cancelled
RT-Thread BSP Static Build Check / ${{ matrix.legs.RTT_BSP }} (push) Has been cancelled
RT-Thread BSP Static Build Check / collect-artifacts (push) Has been cancelled
pkgs_test / change (push) Has been cancelled
utest_auto_run / A9 :components/dfs.cfg (push) Has been cancelled
utest_auto_run / A9 :components/lwip.cfg (push) Has been cancelled
utest_auto_run / A9 :components/netdev.cfg (push) Has been cancelled
utest_auto_run / A9 :components/sal.cfg (push) Has been cancelled
utest_auto_run / A9 :cpp11/cpp11.cfg (push) Has been cancelled
utest_auto_run / AARCH64-rtsmart :default.cfg (push) Has been cancelled
utest_auto_run / A9-rtsmart :default.cfg (push) Has been cancelled
utest_auto_run / RISCV-rtsmart :default.cfg (push) Has been cancelled
utest_auto_run / XUANTIE-rtsmart :default.cfg (push) Has been cancelled
utest_auto_run / AARCH64 :default.cfg (push) Has been cancelled
utest_auto_run / AARCH64-smp :default.cfg (push) Has been cancelled
utest_auto_run / A9 :default.cfg (push) Has been cancelled
utest_auto_run / A9-smp :default.cfg (push) Has been cancelled
utest_auto_run / RISCV :default.cfg (push) Has been cancelled
utest_auto_run / RISCV-smp :default.cfg (push) Has been cancelled
utest_auto_run / A9 :kernel/atomic_c11.cfg (push) Has been cancelled
utest_auto_run / RISCV :kernel/atomic_c11.cfg (push) Has been cancelled
utest_auto_run / A9 :kernel/ipc.cfg (push) Has been cancelled
utest_auto_run / A9 :kernel/kernel_basic.cfg (push) Has been cancelled
utest_auto_run / A9 :kernel/mem.cfg (push) Has been cancelled
Weekly CI Scheduler / Trigger and Monitor CIs (push) Has been cancelled
Weekly CI Scheduler / Create Discussion Report (push) Has been cancelled
1. 8250 serila family (OFW, PCI, DWC, early) 2. Virtual serial (by graphic and input) 3. HVC early serial 4. ARM PL011 serial Signed-off-by: GuEe-GUI <2991707448@qq.com>
418 lines
9.8 KiB
C
418 lines
9.8 KiB
C
/*
|
|
* Copyright (c) 2006-2022, RT-Thread Development Team
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2018-05-05 Bernard The first version
|
|
* 2022-08-24 GuEe-GUI add OFW support
|
|
*/
|
|
|
|
#include <rthw.h>
|
|
#include <rtthread.h>
|
|
#include <rtdevice.h>
|
|
#include <drivers/serial_dm.h>
|
|
|
|
#include <cpuport.h>
|
|
|
|
#include <ioremap.h>
|
|
|
|
#define PL011_OEIM RT_BIT(10) /* overrun error interrupt mask */
|
|
#define PL011_BEIM RT_BIT(9) /* break error interrupt mask */
|
|
#define PL011_PEIM RT_BIT(8) /* parity error interrupt mask */
|
|
#define PL011_FEIM RT_BIT(7) /* framing error interrupt mask */
|
|
#define PL011_RTIM RT_BIT(6) /* receive timeout interrupt mask */
|
|
#define PL011_TXIM RT_BIT(5) /* transmit interrupt mask */
|
|
#define PL011_RXIM RT_BIT(4) /* receive interrupt mask */
|
|
#define PL011_DSRMIM RT_BIT(3) /* DSR interrupt mask */
|
|
#define PL011_DCDMIM RT_BIT(2) /* DCD interrupt mask */
|
|
#define PL011_CTSMIM RT_BIT(1) /* CTS interrupt mask */
|
|
#define PL011_RIMIM RT_BIT(0) /* RI interrupt mask */
|
|
|
|
#define PL011_DR 0x000
|
|
#define PL011_FR 0x018
|
|
#define PL011_IBRD 0x024
|
|
#define PL011_FBRD 0x028
|
|
#define PL011_LCR 0x02c
|
|
#define PL011_CR 0x030
|
|
#define PL011_IMSC 0x038
|
|
#define PL011_RIS 0x03c
|
|
#define PL011_DMACR 0x048
|
|
|
|
#define PL011_LCRH_SPS (1 << 7)
|
|
#define PL011_LCRH_WLEN_8 (3 << 5)
|
|
#define PL011_LCRH_WLEN_7 (2 << 5)
|
|
#define PL011_LCRH_WLEN_6 (1 << 5)
|
|
#define PL011_LCRH_WLEN_5 (0 << 5)
|
|
#define PL011_LCRH_FEN (1 << 4)
|
|
#define PL011_LCRH_STP2 (1 << 3)
|
|
#define PL011_LCRH_EPS (1 << 2)
|
|
#define PL011_LCRH_PEN (1 << 1)
|
|
#define PL011_LCRH_BRK (1 << 0)
|
|
|
|
#define PL011_LCRH_WLEN(n) ((n - 5) << 5)
|
|
|
|
#define PL011_CR_CTSEN RT_BIT(15)
|
|
#define PL011_CR_RTSEN RT_BIT(14)
|
|
#define PL011_CR_RTS RT_BIT(11)
|
|
#define PL011_CR_DTR RT_BIT(10)
|
|
#define PL011_CR_RXE RT_BIT(9)
|
|
#define PL011_CR_TXE RT_BIT(8)
|
|
#define PL011_CR_LBE RT_BIT(7)
|
|
#define PL011_CR_SIRLP RT_BIT(2)
|
|
#define PL011_CR_SIREN RT_BIT(1)
|
|
#define PL011_CR_UARTEN RT_BIT(0)
|
|
|
|
struct pl011
|
|
{
|
|
struct rt_serial_device parent;
|
|
|
|
int irq;
|
|
void *base;
|
|
rt_ubase_t freq;
|
|
struct rt_clk *clk;
|
|
struct rt_clk *pclk;
|
|
|
|
struct rt_spinlock spinlock;
|
|
};
|
|
|
|
#define raw_to_pl011(raw) rt_container_of(raw, struct pl011, parent)
|
|
|
|
rt_inline rt_uint32_t pl011_read(struct pl011 *pl011, int offset)
|
|
{
|
|
return HWREG32(pl011->base + offset);
|
|
}
|
|
|
|
rt_inline void pl011_write(struct pl011 *pl011, int offset, rt_uint32_t value)
|
|
{
|
|
HWREG32(pl011->base + offset) = value;
|
|
}
|
|
|
|
static void pl011_isr(int irqno, void *param)
|
|
{
|
|
struct pl011 *pl011 = param;
|
|
|
|
/* Check irq */
|
|
if (pl011_read(pl011, PL011_RIS) & PL011_RXIM)
|
|
{
|
|
rt_base_t level = rt_spin_lock_irqsave(&pl011->spinlock);
|
|
|
|
rt_hw_serial_isr(&pl011->parent, RT_SERIAL_EVENT_RX_IND);
|
|
|
|
rt_spin_unlock_irqrestore(&pl011->spinlock, level);
|
|
}
|
|
}
|
|
|
|
static rt_err_t pl011_uart_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
|
|
{
|
|
rt_ubase_t quot;
|
|
struct pl011 *pl011 = raw_to_pl011(serial);
|
|
|
|
/* Clear UART setting */
|
|
pl011_write(pl011, PL011_CR, 0);
|
|
/* Disable FIFO */
|
|
pl011_write(pl011, PL011_LCR, 0);
|
|
|
|
if (cfg->baud_rate > pl011->freq / 16)
|
|
{
|
|
quot = RT_DIV_ROUND_CLOSEST(pl011->freq * 8, cfg->baud_rate);
|
|
}
|
|
else
|
|
{
|
|
quot = RT_DIV_ROUND_CLOSEST(pl011->freq * 4, cfg->baud_rate);
|
|
}
|
|
|
|
pl011_write(pl011, PL011_IBRD, quot >> 6);
|
|
pl011_write(pl011, PL011_FBRD, quot & 0x3f);
|
|
/* FIFO */
|
|
pl011_write(pl011, PL011_LCR, PL011_LCRH_WLEN(cfg->data_bits));
|
|
|
|
/* Art enable, TX/RX enable */
|
|
pl011_write(pl011, PL011_CR, PL011_CR_UARTEN | PL011_CR_TXE | PL011_CR_RXE);
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_err_t pl011_uart_control(struct rt_serial_device *serial, int cmd, void *arg)
|
|
{
|
|
struct pl011 *pl011 = raw_to_pl011(serial);
|
|
|
|
switch (cmd)
|
|
{
|
|
case RT_DEVICE_CTRL_CLR_INT:
|
|
/* Disable rx irq */
|
|
pl011_write(pl011, PL011_IMSC, pl011_read(pl011, PL011_IMSC) & ~PL011_RXIM);
|
|
|
|
rt_hw_interrupt_mask(pl011->irq);
|
|
|
|
break;
|
|
|
|
case RT_DEVICE_CTRL_SET_INT:
|
|
/* Enable rx irq */
|
|
pl011_write(pl011, PL011_IMSC, pl011_read(pl011, PL011_IMSC) | PL011_RXIM);
|
|
|
|
rt_hw_interrupt_umask(pl011->irq);
|
|
|
|
break;
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static int pl011_uart_putc(struct rt_serial_device *serial, char c)
|
|
{
|
|
struct pl011 *pl011 = raw_to_pl011(serial);
|
|
|
|
while (pl011_read(pl011, PL011_FR) & PL011_TXIM)
|
|
{
|
|
rt_hw_cpu_relax();
|
|
}
|
|
|
|
pl011_write(pl011, PL011_DR, c);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int pl011_uart_getc(struct rt_serial_device *serial)
|
|
{
|
|
int ch = -1;
|
|
struct pl011 *pl011 = raw_to_pl011(serial);
|
|
|
|
if (!(pl011_read(pl011, PL011_FR) & PL011_RXIM))
|
|
{
|
|
ch = pl011_read(pl011, PL011_DR);
|
|
}
|
|
|
|
return ch;
|
|
}
|
|
|
|
static const struct rt_uart_ops pl011_uart_ops =
|
|
{
|
|
.configure = pl011_uart_configure,
|
|
.control = pl011_uart_control,
|
|
.putc = pl011_uart_putc,
|
|
.getc = pl011_uart_getc,
|
|
};
|
|
|
|
static void pl011_early_kick(struct rt_fdt_earlycon *con, int why)
|
|
{
|
|
struct pl011 *pl011 = raw_to_pl011(con->data);
|
|
|
|
switch (why)
|
|
{
|
|
case FDT_EARLYCON_KICK_UPDATE:
|
|
pl011->base = rt_ioremap((void *)con->mmio, con->size);
|
|
break;
|
|
|
|
case FDT_EARLYCON_KICK_COMPLETED:
|
|
rt_iounmap(pl011->base);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static rt_err_t pl011_early_setup(struct rt_fdt_earlycon *con, const char *options)
|
|
{
|
|
rt_err_t err = RT_EOK;
|
|
static struct pl011 pl011 = { };
|
|
|
|
if (options && !con->mmio)
|
|
{
|
|
char *arg;
|
|
|
|
con->mmio = RT_NULL;
|
|
|
|
/*
|
|
* The pl011 serial port must already be setup and configured in early.
|
|
* Options are not yet supported.
|
|
* pl011,<addr>
|
|
* pl011,mmio32,<addr>
|
|
*/
|
|
serial_for_each_args(arg, options)
|
|
{
|
|
if (!rt_strcmp(arg, "pl011") || !rt_strcmp(arg, "mmio32"))
|
|
{
|
|
continue;
|
|
}
|
|
if (!con->mmio)
|
|
{
|
|
con->mmio = (rt_ubase_t)serial_base_from_args(arg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!con->size)
|
|
{
|
|
con->size = 0x1000;
|
|
}
|
|
|
|
if (con->mmio)
|
|
{
|
|
pl011.base = rt_ioremap_early((void *)con->mmio, con->size);
|
|
}
|
|
|
|
if (pl011.base)
|
|
{
|
|
con->console_putc = (typeof(con->console_putc))&pl011_uart_putc;
|
|
con->console_kick = pl011_early_kick;
|
|
con->data = &pl011.parent;
|
|
pl011.parent.config = (typeof(pl011.parent.config))RT_SERIAL_CONFIG_DEFAULT;
|
|
}
|
|
else
|
|
{
|
|
err = -RT_ERROR;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
RT_FDT_EARLYCON_EXPORT(pl011, "pl011", "arm,pl011", pl011_early_setup);
|
|
|
|
static void pl011_free(struct pl011 *pl011)
|
|
{
|
|
if (pl011->base)
|
|
{
|
|
rt_iounmap(pl011->base);
|
|
}
|
|
|
|
if (!rt_is_err_or_null(pl011->clk))
|
|
{
|
|
rt_clk_disable(pl011->clk);
|
|
rt_clk_put(pl011->clk);
|
|
}
|
|
|
|
if (!rt_is_err_or_null(pl011->pclk))
|
|
{
|
|
rt_clk_disable_unprepare(pl011->pclk);
|
|
rt_clk_put(pl011->pclk);
|
|
}
|
|
|
|
rt_free(pl011);
|
|
}
|
|
|
|
static rt_err_t pl011_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 pl011 *pl011 = rt_calloc(1, sizeof(*pl011));
|
|
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
|
|
|
|
if (!pl011)
|
|
{
|
|
return -RT_ENOMEM;
|
|
}
|
|
|
|
pl011->base = rt_dm_dev_iomap(dev, 0);
|
|
|
|
if (!pl011->base)
|
|
{
|
|
err = -RT_EIO;
|
|
|
|
goto _fail;
|
|
}
|
|
|
|
pl011->irq = rt_dm_dev_get_irq(dev, 0);
|
|
|
|
if (pl011->irq < 0)
|
|
{
|
|
err = pl011->irq;
|
|
|
|
goto _fail;
|
|
}
|
|
|
|
pl011->clk = rt_clk_get_by_index(dev, 0);
|
|
|
|
if (rt_is_err(pl011->clk))
|
|
{
|
|
err = rt_ptr_err(pl011->clk);
|
|
|
|
goto _fail;
|
|
}
|
|
|
|
pl011->pclk = rt_clk_get_by_name(dev, "apb_pclk");
|
|
|
|
if (rt_is_err(pl011->pclk))
|
|
{
|
|
err = rt_ptr_err(pl011->pclk);
|
|
|
|
goto _fail;
|
|
}
|
|
|
|
if ((err = rt_clk_prepare_enable(pl011->pclk)))
|
|
{
|
|
goto _fail;
|
|
}
|
|
|
|
rt_dm_dev_bind_fwdata(&pl011->parent.parent, dev->ofw_node, &pl011->parent);
|
|
|
|
rt_clk_enable(pl011->clk);
|
|
pl011->freq = rt_clk_get_rate(pl011->clk);
|
|
|
|
dev->user_data = pl011;
|
|
|
|
pl011->parent.ops = &pl011_uart_ops;
|
|
pl011->parent.config = config;
|
|
|
|
rt_spin_lock_init(&pl011->spinlock);
|
|
|
|
serial_dev_set_name(&pl011->parent);
|
|
name = rt_dm_dev_get_name(&pl011->parent.parent);
|
|
|
|
rt_hw_serial_register(&pl011->parent, name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, pl011);
|
|
rt_snprintf(isr_name, RT_NAME_MAX, "%s-pl011", name);
|
|
rt_hw_interrupt_install(pl011->irq, pl011_isr, pl011, isr_name);
|
|
|
|
return RT_EOK;
|
|
|
|
_fail:
|
|
pl011_free(pl011);
|
|
|
|
return err;
|
|
}
|
|
|
|
static rt_err_t pl011_remove(struct rt_platform_device *pdev)
|
|
{
|
|
struct rt_device *dev = &pdev->parent;
|
|
struct pl011 *pl011 = dev->user_data;
|
|
|
|
rt_dm_dev_unbind_fwdata(dev, RT_NULL);
|
|
|
|
rt_hw_interrupt_mask(pl011->irq);
|
|
rt_pic_detach_irq(pl011->irq, pl011);
|
|
|
|
rt_device_unregister(&pl011->parent.parent);
|
|
|
|
pl011_free(pl011);
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static const struct rt_ofw_node_id pl011_ofw_ids[] =
|
|
{
|
|
{ .type = "ttyAMA", .compatible = "arm,pl011" },
|
|
{ .type = "ttyAMA", .compatible = "arm,pl011-axi" },
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
static struct rt_platform_driver pl011_driver =
|
|
{
|
|
.name = "serial-pl011",
|
|
.ids = pl011_ofw_ids,
|
|
|
|
.probe = pl011_probe,
|
|
.remove = pl011_remove,
|
|
};
|
|
|
|
static int pl011_drv_register(void)
|
|
{
|
|
rt_platform_driver_register(&pl011_driver);
|
|
|
|
return 0;
|
|
}
|
|
INIT_PLATFORM_EXPORT(pl011_drv_register);
|