419 lines
9.9 KiB
C
419 lines
9.9 KiB
C
|
/*
|
||
|
* Copyright (c) 2006-2024 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 <cpuport.h>
|
||
|
|
||
|
#include <ioremap.h>
|
||
|
|
||
|
#include <drivers/serial_dm.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_DRIVER_EARLY_EXPORT(pl011_drv_register);
|