276 lines
6.6 KiB
C
276 lines
6.6 KiB
C
|
/*
|
||
|
* drivers/trace/exynos-condbg-dev.c
|
||
|
*
|
||
|
* Copyright (c) 2016 Samsung Electronics Co., Ltd.
|
||
|
* http://www.samsung.com
|
||
|
*
|
||
|
* Exynos-Console-Debugger for Exynos SoC
|
||
|
* This codes are based on fiq_debugger of google
|
||
|
* /driver/staging/android/fiq_debugger
|
||
|
*
|
||
|
* Author: Hosung Kim <hosung0.kim@samsung.com>
|
||
|
* Changki Kim <changki.kim@samsung.com>
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 as
|
||
|
* published by the Free Software Foundation.
|
||
|
*/
|
||
|
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/stacktrace.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/serial_s3c.h>
|
||
|
|
||
|
#include "exynos-condbg-dev.h"
|
||
|
|
||
|
struct ecd_dev {
|
||
|
struct ecd_pdata pdata;
|
||
|
struct platform_device *pdev;
|
||
|
void __iomem *debug_port_base;
|
||
|
int irq;
|
||
|
u32 baud;
|
||
|
u32 frac_baud;
|
||
|
};
|
||
|
|
||
|
static inline struct ecd_dev *get_dbg(struct platform_device *pdev)
|
||
|
{
|
||
|
struct ecd_pdata *pdata = dev_get_platdata(&pdev->dev);
|
||
|
return container_of(pdata, struct ecd_dev, pdata);
|
||
|
}
|
||
|
|
||
|
static inline void exynos_write(struct ecd_dev *dbg,
|
||
|
unsigned int val, unsigned int off)
|
||
|
{
|
||
|
__raw_writel(val, dbg->debug_port_base + off);
|
||
|
}
|
||
|
|
||
|
static inline unsigned int exynos_read(struct ecd_dev *dbg,
|
||
|
unsigned int off)
|
||
|
{
|
||
|
return __raw_readl(dbg->debug_port_base + off);
|
||
|
}
|
||
|
|
||
|
static int debug_port_init(struct platform_device *pdev)
|
||
|
{
|
||
|
struct ecd_dev *dbg = get_dbg(pdev);
|
||
|
unsigned long timeout;
|
||
|
|
||
|
exynos_write(dbg, dbg->baud, S3C2410_UBRDIV);
|
||
|
exynos_write(dbg, dbg->frac_baud, S3C2443_DIVSLOT);
|
||
|
|
||
|
/* Mask and clear all interrupts */
|
||
|
exynos_write(dbg, 0xF, S3C64XX_UINTM);
|
||
|
exynos_write(dbg, 0xF, S3C64XX_UINTP);
|
||
|
|
||
|
exynos_write(dbg, S3C2410_LCON_CS8, S3C2410_ULCON);
|
||
|
exynos_write(dbg, S5PV210_UCON_DEFAULT, S3C2410_UCON);
|
||
|
exynos_write(dbg, S5PV210_UFCON_DEFAULT, S3C2410_UFCON);
|
||
|
exynos_write(dbg, 0, S3C2410_UMCON);
|
||
|
|
||
|
/* Reset TX and RX fifos */
|
||
|
exynos_write(dbg, S5PV210_UFCON_DEFAULT | S3C2410_UFCON_RESETBOTH,
|
||
|
S3C2410_UFCON);
|
||
|
|
||
|
timeout = jiffies + HZ;
|
||
|
while (exynos_read(dbg, S3C2410_UFCON) & S3C2410_UFCON_RESETBOTH)
|
||
|
if (time_after(jiffies, timeout))
|
||
|
return -ETIMEDOUT;
|
||
|
|
||
|
/* Enable all interrupts except TX */
|
||
|
exynos_write(dbg, S3C64XX_UINTM_TXD_MSK, S3C64XX_UINTM);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int debug_getc(struct platform_device *pdev)
|
||
|
{
|
||
|
struct ecd_dev *dbg = get_dbg(pdev);
|
||
|
u32 stat;
|
||
|
int ret = DEBUGGER_NO_CHAR;
|
||
|
|
||
|
/* Clear all pending interrupts */
|
||
|
exynos_write(dbg, 0xF, S3C64XX_UINTP);
|
||
|
|
||
|
stat = exynos_read(dbg, S3C2410_UERSTAT);
|
||
|
if (stat & S3C2410_UERSTAT_BREAK)
|
||
|
return DEBUGGER_BREAK;
|
||
|
|
||
|
stat = exynos_read(dbg, S3C2410_UTRSTAT);
|
||
|
if (stat & S3C2410_UTRSTAT_RXDR)
|
||
|
ret = exynos_read(dbg, S3C2410_URXH);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void debug_putc(struct platform_device *pdev, unsigned int c)
|
||
|
{
|
||
|
struct ecd_dev *dbg = get_dbg(pdev);
|
||
|
int count = loops_per_jiffy;
|
||
|
|
||
|
if (exynos_read(dbg, S3C2410_ULCON) != S3C2410_LCON_CS8)
|
||
|
debug_port_init(pdev);
|
||
|
|
||
|
while (exynos_read(dbg, S3C2410_UFSTAT) & S5PV210_UFSTAT_TXFULL)
|
||
|
if (--count == 0)
|
||
|
return;
|
||
|
|
||
|
exynos_write(dbg, c, S3C2410_UTXH);
|
||
|
}
|
||
|
|
||
|
static void debug_flush(struct platform_device *pdev)
|
||
|
{
|
||
|
struct ecd_dev *dbg = get_dbg(pdev);
|
||
|
int count = loops_per_jiffy * HZ;
|
||
|
|
||
|
while (!(exynos_read(dbg, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE))
|
||
|
if (--count == 0)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void debug_clear_rxfifo(struct platform_device *pdev)
|
||
|
{
|
||
|
struct ecd_dev *dbg = get_dbg(pdev);
|
||
|
u32 ufcon;
|
||
|
|
||
|
ufcon = exynos_read(dbg, S3C2410_UFCON);
|
||
|
ufcon |= S3C2410_UFCON_RESETRX;
|
||
|
exynos_write(dbg, ufcon, S3C2410_UFCON);
|
||
|
}
|
||
|
|
||
|
static int debug_suspend(struct platform_device *pdev)
|
||
|
{
|
||
|
struct ecd_dev *dbg = get_dbg(pdev);
|
||
|
#ifdef CONFIG_SERIAL_SAMSUNG_HWACG
|
||
|
u32 ucon;
|
||
|
#endif
|
||
|
|
||
|
exynos_write(dbg, 0xF, S3C64XX_UINTM);
|
||
|
|
||
|
#ifdef CONFIG_SERIAL_SAMSUNG_HWACG
|
||
|
ucon = exynos_read(dbg, S3C2410_UCON);
|
||
|
ucon &= ~(S3C2410_UCON_RXIRQMODE | S3C2410_UCON_TXIRQMODE);
|
||
|
exynos_write(dbg, ucon, S3C2410_UCON);
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int debug_resume(struct platform_device *pdev)
|
||
|
{
|
||
|
debug_port_init(pdev);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ecd_dev_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct device_node *np;
|
||
|
int uart_irq;
|
||
|
unsigned int uart_port;
|
||
|
int ret = -ENOMEM;
|
||
|
struct ecd_dev *dbg = NULL;
|
||
|
struct resource *res;
|
||
|
|
||
|
if (initial_no_firmware)
|
||
|
return -EINVAL;
|
||
|
|
||
|
dbg = devm_kzalloc(&pdev->dev, sizeof(struct ecd_dev), GFP_KERNEL);
|
||
|
if (!dbg) {
|
||
|
dev_err(&pdev->dev, "Failed to allocate dbg structure\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
|
if (!res) {
|
||
|
dev_err(&pdev->dev, "No mem resource\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dbg->debug_port_base = devm_ioremap_resource(&pdev->dev, res);
|
||
|
if (dbg->debug_port_base == NULL) {
|
||
|
dev_err(&pdev->dev, "failed to claim register region\n");
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||
|
if (!res)
|
||
|
return -ENXIO;
|
||
|
|
||
|
uart_irq = platform_get_irq_byname(pdev, "uart_irq");
|
||
|
if (uart_irq < 0) {
|
||
|
dev_err(&pdev->dev, "No IRQ for uart_irq, error=%d\n", uart_irq);
|
||
|
return uart_irq;
|
||
|
}
|
||
|
|
||
|
np = pdev->dev.of_node;
|
||
|
of_property_read_u32(np, "uart_port", &uart_port);
|
||
|
if (uart_port > CONFIG_SERIAL_SAMSUNG_UARTS) {
|
||
|
dev_err(&pdev->dev, "wrong descriptor for uart_port, error=%d\n", uart_port);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL);
|
||
|
if(!pdev) {
|
||
|
pr_err("Failed to alloc ecd_dev platform device\n");
|
||
|
goto err_pdev;
|
||
|
}
|
||
|
|
||
|
dbg->pdata.uart_init = debug_port_init;
|
||
|
dbg->pdata.uart_getc = debug_getc;
|
||
|
dbg->pdata.uart_putc = debug_putc;
|
||
|
dbg->pdata.uart_flush = debug_flush;
|
||
|
dbg->pdata.uart_clear_rxfifo = debug_clear_rxfifo;
|
||
|
dbg->pdata.uart_dev_suspend = debug_suspend;
|
||
|
dbg->pdata.uart_dev_resume = debug_resume;
|
||
|
|
||
|
dbg->pdev = pdev;
|
||
|
dbg->baud = exynos_read(dbg, S3C2410_UBRDIV);
|
||
|
dbg->frac_baud = exynos_read(dbg, S3C2443_DIVSLOT);
|
||
|
|
||
|
pdev->name = "console_debugger";
|
||
|
pdev->id = uart_port;
|
||
|
pdev->dev.platform_data = &dbg->pdata;
|
||
|
pdev->resource = res;
|
||
|
pdev->num_resources = 1;
|
||
|
|
||
|
if (platform_device_register(pdev)) {
|
||
|
pr_err("Failed to register ecd_dev \n");
|
||
|
goto err_free;
|
||
|
}
|
||
|
|
||
|
pr_info("Success to register ECD UART\n");
|
||
|
return 0;
|
||
|
|
||
|
err_pdev:
|
||
|
kfree(pdev);
|
||
|
err_free:
|
||
|
kfree(dbg);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id ecd_dev_match[] = {
|
||
|
{.compatible = "samsung,exynos_console_debugger",},
|
||
|
{},
|
||
|
};
|
||
|
|
||
|
static struct platform_driver ecd_dev_driver = {
|
||
|
.probe = ecd_dev_probe,
|
||
|
.driver = {
|
||
|
.name = "exynos_console_debugger",
|
||
|
.owner = THIS_MODULE,
|
||
|
.of_match_table = of_match_ptr(ecd_dev_match),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static int __init ecd_dev_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&ecd_dev_driver);
|
||
|
}
|
||
|
|
||
|
postcore_initcall_sync(ecd_dev_init);
|