blob: 7654a2fb238b01bfc6031d04beb82c2777a1b0e2 [file] [log] [blame]
/*
* 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);