/* * 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 * Changki Kim * * 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 #include #include #include #include #include #include #include #include #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);