18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Freescale DPAA2 Platforms Console Driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2015-2016 Freescale Semiconductor Inc. 68c2ecf20Sopenharmony_ci * Copyright 2018 NXP 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "dpaa2-console: " fmt 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/of_device.h> 138c2ecf20Sopenharmony_ci#include <linux/of_address.h> 148c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 158c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci#include <linux/fs.h> 188c2ecf20Sopenharmony_ci#include <linux/io.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci/* MC firmware base low/high registers indexes */ 218c2ecf20Sopenharmony_ci#define MCFBALR_OFFSET 0 228c2ecf20Sopenharmony_ci#define MCFBAHR_OFFSET 1 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* Bit masks used to get the most/least significant part of the MC base addr */ 258c2ecf20Sopenharmony_ci#define MC_FW_ADDR_MASK_HIGH 0x1FFFF 268c2ecf20Sopenharmony_ci#define MC_FW_ADDR_MASK_LOW 0xE0000000 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define MC_BUFFER_OFFSET 0x01000000 298c2ecf20Sopenharmony_ci#define MC_BUFFER_SIZE (1024 * 1024 * 16) 308c2ecf20Sopenharmony_ci#define MC_OFFSET_DELTA MC_BUFFER_OFFSET 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define AIOP_BUFFER_OFFSET 0x06000000 338c2ecf20Sopenharmony_ci#define AIOP_BUFFER_SIZE (1024 * 1024 * 16) 348c2ecf20Sopenharmony_ci#define AIOP_OFFSET_DELTA 0 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#define LOG_HEADER_FLAG_BUFFER_WRAPAROUND 0x80000000 378c2ecf20Sopenharmony_ci#define LAST_BYTE(a) ((a) & ~(LOG_HEADER_FLAG_BUFFER_WRAPAROUND)) 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci/* MC and AIOP Magic words */ 408c2ecf20Sopenharmony_ci#define MAGIC_MC 0x4d430100 418c2ecf20Sopenharmony_ci#define MAGIC_AIOP 0x41494F50 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistruct log_header { 448c2ecf20Sopenharmony_ci __le32 magic_word; 458c2ecf20Sopenharmony_ci char reserved[4]; 468c2ecf20Sopenharmony_ci __le32 buf_start; 478c2ecf20Sopenharmony_ci __le32 buf_length; 488c2ecf20Sopenharmony_ci __le32 last_byte; 498c2ecf20Sopenharmony_ci}; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistruct console_data { 528c2ecf20Sopenharmony_ci void __iomem *map_addr; 538c2ecf20Sopenharmony_ci struct log_header __iomem *hdr; 548c2ecf20Sopenharmony_ci void __iomem *start_addr; 558c2ecf20Sopenharmony_ci void __iomem *end_addr; 568c2ecf20Sopenharmony_ci void __iomem *end_of_data; 578c2ecf20Sopenharmony_ci void __iomem *cur_ptr; 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic struct resource mc_base_addr; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic inline void adjust_end(struct console_data *cd) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci u32 last_byte = readl(&cd->hdr->last_byte); 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci cd->end_of_data = cd->start_addr + LAST_BYTE(last_byte); 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic u64 get_mc_fw_base_address(void) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci u64 mcfwbase = 0ULL; 728c2ecf20Sopenharmony_ci u32 __iomem *mcfbaregs; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci mcfbaregs = ioremap(mc_base_addr.start, resource_size(&mc_base_addr)); 758c2ecf20Sopenharmony_ci if (!mcfbaregs) { 768c2ecf20Sopenharmony_ci pr_err("could not map MC Firmware Base registers\n"); 778c2ecf20Sopenharmony_ci return 0; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci mcfwbase = readl(mcfbaregs + MCFBAHR_OFFSET) & 818c2ecf20Sopenharmony_ci MC_FW_ADDR_MASK_HIGH; 828c2ecf20Sopenharmony_ci mcfwbase <<= 32; 838c2ecf20Sopenharmony_ci mcfwbase |= readl(mcfbaregs + MCFBALR_OFFSET) & MC_FW_ADDR_MASK_LOW; 848c2ecf20Sopenharmony_ci iounmap(mcfbaregs); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci pr_debug("MC base address at 0x%016llx\n", mcfwbase); 878c2ecf20Sopenharmony_ci return mcfwbase; 888c2ecf20Sopenharmony_ci} 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic ssize_t dpaa2_console_size(struct console_data *cd) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci ssize_t size; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci if (cd->cur_ptr <= cd->end_of_data) 958c2ecf20Sopenharmony_ci size = cd->end_of_data - cd->cur_ptr; 968c2ecf20Sopenharmony_ci else 978c2ecf20Sopenharmony_ci size = (cd->end_addr - cd->cur_ptr) + 988c2ecf20Sopenharmony_ci (cd->end_of_data - cd->start_addr); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci return size; 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic int dpaa2_generic_console_open(struct inode *node, struct file *fp, 1048c2ecf20Sopenharmony_ci u64 offset, u64 size, 1058c2ecf20Sopenharmony_ci u32 expected_magic, 1068c2ecf20Sopenharmony_ci u32 offset_delta) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci u32 read_magic, wrapped, last_byte, buf_start, buf_length; 1098c2ecf20Sopenharmony_ci struct console_data *cd; 1108c2ecf20Sopenharmony_ci u64 base_addr; 1118c2ecf20Sopenharmony_ci int err; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci cd = kmalloc(sizeof(*cd), GFP_KERNEL); 1148c2ecf20Sopenharmony_ci if (!cd) 1158c2ecf20Sopenharmony_ci return -ENOMEM; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci base_addr = get_mc_fw_base_address(); 1188c2ecf20Sopenharmony_ci if (!base_addr) { 1198c2ecf20Sopenharmony_ci err = -EIO; 1208c2ecf20Sopenharmony_ci goto err_fwba; 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci cd->map_addr = ioremap(base_addr + offset, size); 1248c2ecf20Sopenharmony_ci if (!cd->map_addr) { 1258c2ecf20Sopenharmony_ci pr_err("cannot map console log memory\n"); 1268c2ecf20Sopenharmony_ci err = -EIO; 1278c2ecf20Sopenharmony_ci goto err_ioremap; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci cd->hdr = (struct log_header __iomem *)cd->map_addr; 1318c2ecf20Sopenharmony_ci read_magic = readl(&cd->hdr->magic_word); 1328c2ecf20Sopenharmony_ci last_byte = readl(&cd->hdr->last_byte); 1338c2ecf20Sopenharmony_ci buf_start = readl(&cd->hdr->buf_start); 1348c2ecf20Sopenharmony_ci buf_length = readl(&cd->hdr->buf_length); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (read_magic != expected_magic) { 1378c2ecf20Sopenharmony_ci pr_warn("expected = %08x, read = %08x\n", 1388c2ecf20Sopenharmony_ci expected_magic, read_magic); 1398c2ecf20Sopenharmony_ci err = -EIO; 1408c2ecf20Sopenharmony_ci goto err_magic; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci cd->start_addr = cd->map_addr + buf_start - offset_delta; 1448c2ecf20Sopenharmony_ci cd->end_addr = cd->start_addr + buf_length; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci wrapped = last_byte & LOG_HEADER_FLAG_BUFFER_WRAPAROUND; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci adjust_end(cd); 1498c2ecf20Sopenharmony_ci if (wrapped && cd->end_of_data != cd->end_addr) 1508c2ecf20Sopenharmony_ci cd->cur_ptr = cd->end_of_data + 1; 1518c2ecf20Sopenharmony_ci else 1528c2ecf20Sopenharmony_ci cd->cur_ptr = cd->start_addr; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci fp->private_data = cd; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci return 0; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cierr_magic: 1598c2ecf20Sopenharmony_ci iounmap(cd->map_addr); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cierr_ioremap: 1628c2ecf20Sopenharmony_cierr_fwba: 1638c2ecf20Sopenharmony_ci kfree(cd); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci return err; 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic int dpaa2_mc_console_open(struct inode *node, struct file *fp) 1698c2ecf20Sopenharmony_ci{ 1708c2ecf20Sopenharmony_ci return dpaa2_generic_console_open(node, fp, 1718c2ecf20Sopenharmony_ci MC_BUFFER_OFFSET, MC_BUFFER_SIZE, 1728c2ecf20Sopenharmony_ci MAGIC_MC, MC_OFFSET_DELTA); 1738c2ecf20Sopenharmony_ci} 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cistatic int dpaa2_aiop_console_open(struct inode *node, struct file *fp) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci return dpaa2_generic_console_open(node, fp, 1788c2ecf20Sopenharmony_ci AIOP_BUFFER_OFFSET, AIOP_BUFFER_SIZE, 1798c2ecf20Sopenharmony_ci MAGIC_AIOP, AIOP_OFFSET_DELTA); 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic int dpaa2_console_close(struct inode *node, struct file *fp) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci struct console_data *cd = fp->private_data; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci iounmap(cd->map_addr); 1878c2ecf20Sopenharmony_ci kfree(cd); 1888c2ecf20Sopenharmony_ci return 0; 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic ssize_t dpaa2_console_read(struct file *fp, char __user *buf, 1928c2ecf20Sopenharmony_ci size_t count, loff_t *f_pos) 1938c2ecf20Sopenharmony_ci{ 1948c2ecf20Sopenharmony_ci struct console_data *cd = fp->private_data; 1958c2ecf20Sopenharmony_ci size_t bytes = dpaa2_console_size(cd); 1968c2ecf20Sopenharmony_ci size_t bytes_end = cd->end_addr - cd->cur_ptr; 1978c2ecf20Sopenharmony_ci size_t written = 0; 1988c2ecf20Sopenharmony_ci void *kbuf; 1998c2ecf20Sopenharmony_ci int err; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci /* Check if we need to adjust the end of data addr */ 2028c2ecf20Sopenharmony_ci adjust_end(cd); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci if (cd->end_of_data == cd->cur_ptr) 2058c2ecf20Sopenharmony_ci return 0; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci if (count < bytes) 2088c2ecf20Sopenharmony_ci bytes = count; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci kbuf = kmalloc(bytes, GFP_KERNEL); 2118c2ecf20Sopenharmony_ci if (!kbuf) 2128c2ecf20Sopenharmony_ci return -ENOMEM; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci if (bytes > bytes_end) { 2158c2ecf20Sopenharmony_ci memcpy_fromio(kbuf, cd->cur_ptr, bytes_end); 2168c2ecf20Sopenharmony_ci if (copy_to_user(buf, kbuf, bytes_end)) { 2178c2ecf20Sopenharmony_ci err = -EFAULT; 2188c2ecf20Sopenharmony_ci goto err_free_buf; 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci buf += bytes_end; 2218c2ecf20Sopenharmony_ci cd->cur_ptr = cd->start_addr; 2228c2ecf20Sopenharmony_ci bytes -= bytes_end; 2238c2ecf20Sopenharmony_ci written += bytes_end; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci memcpy_fromio(kbuf, cd->cur_ptr, bytes); 2278c2ecf20Sopenharmony_ci if (copy_to_user(buf, kbuf, bytes)) { 2288c2ecf20Sopenharmony_ci err = -EFAULT; 2298c2ecf20Sopenharmony_ci goto err_free_buf; 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci cd->cur_ptr += bytes; 2328c2ecf20Sopenharmony_ci written += bytes; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci kfree(kbuf); 2358c2ecf20Sopenharmony_ci return written; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cierr_free_buf: 2388c2ecf20Sopenharmony_ci kfree(kbuf); 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci return err; 2418c2ecf20Sopenharmony_ci} 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_cistatic const struct file_operations dpaa2_mc_console_fops = { 2448c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2458c2ecf20Sopenharmony_ci .open = dpaa2_mc_console_open, 2468c2ecf20Sopenharmony_ci .release = dpaa2_console_close, 2478c2ecf20Sopenharmony_ci .read = dpaa2_console_read, 2488c2ecf20Sopenharmony_ci}; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_cistatic struct miscdevice dpaa2_mc_console_dev = { 2518c2ecf20Sopenharmony_ci .minor = MISC_DYNAMIC_MINOR, 2528c2ecf20Sopenharmony_ci .name = "dpaa2_mc_console", 2538c2ecf20Sopenharmony_ci .fops = &dpaa2_mc_console_fops 2548c2ecf20Sopenharmony_ci}; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_cistatic const struct file_operations dpaa2_aiop_console_fops = { 2578c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2588c2ecf20Sopenharmony_ci .open = dpaa2_aiop_console_open, 2598c2ecf20Sopenharmony_ci .release = dpaa2_console_close, 2608c2ecf20Sopenharmony_ci .read = dpaa2_console_read, 2618c2ecf20Sopenharmony_ci}; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cistatic struct miscdevice dpaa2_aiop_console_dev = { 2648c2ecf20Sopenharmony_ci .minor = MISC_DYNAMIC_MINOR, 2658c2ecf20Sopenharmony_ci .name = "dpaa2_aiop_console", 2668c2ecf20Sopenharmony_ci .fops = &dpaa2_aiop_console_fops 2678c2ecf20Sopenharmony_ci}; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic int dpaa2_console_probe(struct platform_device *pdev) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci int error; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci error = of_address_to_resource(pdev->dev.of_node, 0, &mc_base_addr); 2748c2ecf20Sopenharmony_ci if (error < 0) { 2758c2ecf20Sopenharmony_ci pr_err("of_address_to_resource() failed for %pOF with %d\n", 2768c2ecf20Sopenharmony_ci pdev->dev.of_node, error); 2778c2ecf20Sopenharmony_ci return error; 2788c2ecf20Sopenharmony_ci } 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci error = misc_register(&dpaa2_mc_console_dev); 2818c2ecf20Sopenharmony_ci if (error) { 2828c2ecf20Sopenharmony_ci pr_err("cannot register device %s\n", 2838c2ecf20Sopenharmony_ci dpaa2_mc_console_dev.name); 2848c2ecf20Sopenharmony_ci goto err_register_mc; 2858c2ecf20Sopenharmony_ci } 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci error = misc_register(&dpaa2_aiop_console_dev); 2888c2ecf20Sopenharmony_ci if (error) { 2898c2ecf20Sopenharmony_ci pr_err("cannot register device %s\n", 2908c2ecf20Sopenharmony_ci dpaa2_aiop_console_dev.name); 2918c2ecf20Sopenharmony_ci goto err_register_aiop; 2928c2ecf20Sopenharmony_ci } 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci return 0; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_cierr_register_aiop: 2978c2ecf20Sopenharmony_ci misc_deregister(&dpaa2_mc_console_dev); 2988c2ecf20Sopenharmony_cierr_register_mc: 2998c2ecf20Sopenharmony_ci return error; 3008c2ecf20Sopenharmony_ci} 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_cistatic int dpaa2_console_remove(struct platform_device *pdev) 3038c2ecf20Sopenharmony_ci{ 3048c2ecf20Sopenharmony_ci misc_deregister(&dpaa2_mc_console_dev); 3058c2ecf20Sopenharmony_ci misc_deregister(&dpaa2_aiop_console_dev); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci return 0; 3088c2ecf20Sopenharmony_ci} 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_cistatic const struct of_device_id dpaa2_console_match_table[] = { 3118c2ecf20Sopenharmony_ci { .compatible = "fsl,dpaa2-console",}, 3128c2ecf20Sopenharmony_ci {}, 3138c2ecf20Sopenharmony_ci}; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, dpaa2_console_match_table); 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_cistatic struct platform_driver dpaa2_console_driver = { 3188c2ecf20Sopenharmony_ci .driver = { 3198c2ecf20Sopenharmony_ci .name = "dpaa2-console", 3208c2ecf20Sopenharmony_ci .pm = NULL, 3218c2ecf20Sopenharmony_ci .of_match_table = dpaa2_console_match_table, 3228c2ecf20Sopenharmony_ci }, 3238c2ecf20Sopenharmony_ci .probe = dpaa2_console_probe, 3248c2ecf20Sopenharmony_ci .remove = dpaa2_console_remove, 3258c2ecf20Sopenharmony_ci}; 3268c2ecf20Sopenharmony_cimodule_platform_driver(dpaa2_console_driver); 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 3298c2ecf20Sopenharmony_ciMODULE_AUTHOR("Roy Pledge <roy.pledge@nxp.com>"); 3308c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("DPAA2 console driver"); 331