|  | // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) | 
|  | /* | 
|  | * Freescale DPAA2 Platforms Console Driver | 
|  | * | 
|  | * Copyright 2015-2016 Freescale Semiconductor Inc. | 
|  | * Copyright 2018 NXP | 
|  | */ | 
|  |  | 
|  | #define pr_fmt(fmt) "dpaa2-console: " fmt | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/of_address.h> | 
|  | #include <linux/miscdevice.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/uaccess.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/fs.h> | 
|  | #include <linux/io.h> | 
|  |  | 
|  | /* MC firmware base low/high registers indexes */ | 
|  | #define MCFBALR_OFFSET 0 | 
|  | #define MCFBAHR_OFFSET 1 | 
|  |  | 
|  | /* Bit masks used to get the most/least significant part of the MC base addr */ | 
|  | #define MC_FW_ADDR_MASK_HIGH 0x1FFFF | 
|  | #define MC_FW_ADDR_MASK_LOW  0xE0000000 | 
|  |  | 
|  | #define MC_BUFFER_OFFSET 0x01000000 | 
|  | #define MC_BUFFER_SIZE   (1024 * 1024 * 16) | 
|  | #define MC_OFFSET_DELTA  MC_BUFFER_OFFSET | 
|  |  | 
|  | #define AIOP_BUFFER_OFFSET 0x06000000 | 
|  | #define AIOP_BUFFER_SIZE   (1024 * 1024 * 16) | 
|  | #define AIOP_OFFSET_DELTA  0 | 
|  |  | 
|  | #define LOG_HEADER_FLAG_BUFFER_WRAPAROUND 0x80000000 | 
|  | #define LAST_BYTE(a) ((a) & ~(LOG_HEADER_FLAG_BUFFER_WRAPAROUND)) | 
|  |  | 
|  | /* MC and AIOP Magic words */ | 
|  | #define MAGIC_MC   0x4d430100 | 
|  | #define MAGIC_AIOP 0x41494F50 | 
|  |  | 
|  | struct log_header { | 
|  | __le32 magic_word; | 
|  | char reserved[4]; | 
|  | __le32 buf_start; | 
|  | __le32 buf_length; | 
|  | __le32 last_byte; | 
|  | }; | 
|  |  | 
|  | struct console_data { | 
|  | void __iomem *map_addr; | 
|  | struct log_header __iomem *hdr; | 
|  | void __iomem *start_addr; | 
|  | void __iomem *end_addr; | 
|  | void __iomem *end_of_data; | 
|  | void __iomem *cur_ptr; | 
|  | }; | 
|  |  | 
|  | static struct resource mc_base_addr; | 
|  |  | 
|  | static inline void adjust_end(struct console_data *cd) | 
|  | { | 
|  | u32 last_byte = readl(&cd->hdr->last_byte); | 
|  |  | 
|  | cd->end_of_data = cd->start_addr + LAST_BYTE(last_byte); | 
|  | } | 
|  |  | 
|  | static u64 get_mc_fw_base_address(void) | 
|  | { | 
|  | u64 mcfwbase = 0ULL; | 
|  | u32 __iomem *mcfbaregs; | 
|  |  | 
|  | mcfbaregs = ioremap(mc_base_addr.start, resource_size(&mc_base_addr)); | 
|  | if (!mcfbaregs) { | 
|  | pr_err("could not map MC Firmware Base registers\n"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | mcfwbase  = readl(mcfbaregs + MCFBAHR_OFFSET) & | 
|  | MC_FW_ADDR_MASK_HIGH; | 
|  | mcfwbase <<= 32; | 
|  | mcfwbase |= readl(mcfbaregs + MCFBALR_OFFSET) & MC_FW_ADDR_MASK_LOW; | 
|  | iounmap(mcfbaregs); | 
|  |  | 
|  | pr_debug("MC base address at 0x%016llx\n", mcfwbase); | 
|  | return mcfwbase; | 
|  | } | 
|  |  | 
|  | static ssize_t dpaa2_console_size(struct console_data *cd) | 
|  | { | 
|  | ssize_t size; | 
|  |  | 
|  | if (cd->cur_ptr <= cd->end_of_data) | 
|  | size = cd->end_of_data - cd->cur_ptr; | 
|  | else | 
|  | size = (cd->end_addr - cd->cur_ptr) + | 
|  | (cd->end_of_data - cd->start_addr); | 
|  |  | 
|  | return size; | 
|  | } | 
|  |  | 
|  | static int dpaa2_generic_console_open(struct inode *node, struct file *fp, | 
|  | u64 offset, u64 size, | 
|  | u32 expected_magic, | 
|  | u32 offset_delta) | 
|  | { | 
|  | u32 read_magic, wrapped, last_byte, buf_start, buf_length; | 
|  | struct console_data *cd; | 
|  | u64 base_addr; | 
|  | int err; | 
|  |  | 
|  | cd = kmalloc(sizeof(*cd), GFP_KERNEL); | 
|  | if (!cd) | 
|  | return -ENOMEM; | 
|  |  | 
|  | base_addr = get_mc_fw_base_address(); | 
|  | if (!base_addr) { | 
|  | err = -EIO; | 
|  | goto err_fwba; | 
|  | } | 
|  |  | 
|  | cd->map_addr = ioremap(base_addr + offset, size); | 
|  | if (!cd->map_addr) { | 
|  | pr_err("cannot map console log memory\n"); | 
|  | err = -EIO; | 
|  | goto err_ioremap; | 
|  | } | 
|  |  | 
|  | cd->hdr = (struct log_header __iomem *)cd->map_addr; | 
|  | read_magic = readl(&cd->hdr->magic_word); | 
|  | last_byte =  readl(&cd->hdr->last_byte); | 
|  | buf_start =  readl(&cd->hdr->buf_start); | 
|  | buf_length = readl(&cd->hdr->buf_length); | 
|  |  | 
|  | if (read_magic != expected_magic) { | 
|  | pr_warn("expected = %08x, read = %08x\n", | 
|  | expected_magic, read_magic); | 
|  | err = -EIO; | 
|  | goto err_magic; | 
|  | } | 
|  |  | 
|  | cd->start_addr = cd->map_addr + buf_start - offset_delta; | 
|  | cd->end_addr = cd->start_addr + buf_length; | 
|  |  | 
|  | wrapped = last_byte & LOG_HEADER_FLAG_BUFFER_WRAPAROUND; | 
|  |  | 
|  | adjust_end(cd); | 
|  | if (wrapped && cd->end_of_data != cd->end_addr) | 
|  | cd->cur_ptr = cd->end_of_data + 1; | 
|  | else | 
|  | cd->cur_ptr = cd->start_addr; | 
|  |  | 
|  | fp->private_data = cd; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err_magic: | 
|  | iounmap(cd->map_addr); | 
|  |  | 
|  | err_ioremap: | 
|  | err_fwba: | 
|  | kfree(cd); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int dpaa2_mc_console_open(struct inode *node, struct file *fp) | 
|  | { | 
|  | return dpaa2_generic_console_open(node, fp, | 
|  | MC_BUFFER_OFFSET, MC_BUFFER_SIZE, | 
|  | MAGIC_MC, MC_OFFSET_DELTA); | 
|  | } | 
|  |  | 
|  | static int dpaa2_aiop_console_open(struct inode *node, struct file *fp) | 
|  | { | 
|  | return dpaa2_generic_console_open(node, fp, | 
|  | AIOP_BUFFER_OFFSET, AIOP_BUFFER_SIZE, | 
|  | MAGIC_AIOP, AIOP_OFFSET_DELTA); | 
|  | } | 
|  |  | 
|  | static int dpaa2_console_close(struct inode *node, struct file *fp) | 
|  | { | 
|  | struct console_data *cd = fp->private_data; | 
|  |  | 
|  | iounmap(cd->map_addr); | 
|  | kfree(cd); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static ssize_t dpaa2_console_read(struct file *fp, char __user *buf, | 
|  | size_t count, loff_t *f_pos) | 
|  | { | 
|  | struct console_data *cd = fp->private_data; | 
|  | size_t bytes = dpaa2_console_size(cd); | 
|  | size_t bytes_end = cd->end_addr - cd->cur_ptr; | 
|  | size_t written = 0; | 
|  | void *kbuf; | 
|  | int err; | 
|  |  | 
|  | /* Check if we need to adjust the end of data addr */ | 
|  | adjust_end(cd); | 
|  |  | 
|  | if (cd->end_of_data == cd->cur_ptr) | 
|  | return 0; | 
|  |  | 
|  | if (count < bytes) | 
|  | bytes = count; | 
|  |  | 
|  | kbuf = kmalloc(bytes, GFP_KERNEL); | 
|  | if (!kbuf) | 
|  | return -ENOMEM; | 
|  |  | 
|  | if (bytes > bytes_end) { | 
|  | memcpy_fromio(kbuf, cd->cur_ptr, bytes_end); | 
|  | if (copy_to_user(buf, kbuf, bytes_end)) { | 
|  | err = -EFAULT; | 
|  | goto err_free_buf; | 
|  | } | 
|  | buf += bytes_end; | 
|  | cd->cur_ptr = cd->start_addr; | 
|  | bytes -= bytes_end; | 
|  | written += bytes_end; | 
|  | } | 
|  |  | 
|  | memcpy_fromio(kbuf, cd->cur_ptr, bytes); | 
|  | if (copy_to_user(buf, kbuf, bytes)) { | 
|  | err = -EFAULT; | 
|  | goto err_free_buf; | 
|  | } | 
|  | cd->cur_ptr += bytes; | 
|  | written += bytes; | 
|  |  | 
|  | kfree(kbuf); | 
|  | return written; | 
|  |  | 
|  | err_free_buf: | 
|  | kfree(kbuf); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static const struct file_operations dpaa2_mc_console_fops = { | 
|  | .owner          = THIS_MODULE, | 
|  | .open           = dpaa2_mc_console_open, | 
|  | .release        = dpaa2_console_close, | 
|  | .read           = dpaa2_console_read, | 
|  | }; | 
|  |  | 
|  | static struct miscdevice dpaa2_mc_console_dev = { | 
|  | .minor = MISC_DYNAMIC_MINOR, | 
|  | .name = "dpaa2_mc_console", | 
|  | .fops = &dpaa2_mc_console_fops | 
|  | }; | 
|  |  | 
|  | static const struct file_operations dpaa2_aiop_console_fops = { | 
|  | .owner          = THIS_MODULE, | 
|  | .open           = dpaa2_aiop_console_open, | 
|  | .release        = dpaa2_console_close, | 
|  | .read           = dpaa2_console_read, | 
|  | }; | 
|  |  | 
|  | static struct miscdevice dpaa2_aiop_console_dev = { | 
|  | .minor = MISC_DYNAMIC_MINOR, | 
|  | .name = "dpaa2_aiop_console", | 
|  | .fops = &dpaa2_aiop_console_fops | 
|  | }; | 
|  |  | 
|  | static int dpaa2_console_probe(struct platform_device *pdev) | 
|  | { | 
|  | int error; | 
|  |  | 
|  | error = of_address_to_resource(pdev->dev.of_node, 0, &mc_base_addr); | 
|  | if (error < 0) { | 
|  | pr_err("of_address_to_resource() failed for %pOF with %d\n", | 
|  | pdev->dev.of_node, error); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | error = misc_register(&dpaa2_mc_console_dev); | 
|  | if (error) { | 
|  | pr_err("cannot register device %s\n", | 
|  | dpaa2_mc_console_dev.name); | 
|  | goto err_register_mc; | 
|  | } | 
|  |  | 
|  | error = misc_register(&dpaa2_aiop_console_dev); | 
|  | if (error) { | 
|  | pr_err("cannot register device %s\n", | 
|  | dpaa2_aiop_console_dev.name); | 
|  | goto err_register_aiop; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err_register_aiop: | 
|  | misc_deregister(&dpaa2_mc_console_dev); | 
|  | err_register_mc: | 
|  | return error; | 
|  | } | 
|  |  | 
|  | static void dpaa2_console_remove(struct platform_device *pdev) | 
|  | { | 
|  | misc_deregister(&dpaa2_mc_console_dev); | 
|  | misc_deregister(&dpaa2_aiop_console_dev); | 
|  | } | 
|  |  | 
|  | static const struct of_device_id dpaa2_console_match_table[] = { | 
|  | { .compatible = "fsl,dpaa2-console",}, | 
|  | {}, | 
|  | }; | 
|  |  | 
|  | MODULE_DEVICE_TABLE(of, dpaa2_console_match_table); | 
|  |  | 
|  | static struct platform_driver dpaa2_console_driver = { | 
|  | .driver = { | 
|  | .name = "dpaa2-console", | 
|  | .pm = NULL, | 
|  | .of_match_table = dpaa2_console_match_table, | 
|  | }, | 
|  | .probe = dpaa2_console_probe, | 
|  | .remove = dpaa2_console_remove, | 
|  | }; | 
|  | module_platform_driver(dpaa2_console_driver); | 
|  |  | 
|  | MODULE_LICENSE("Dual BSD/GPL"); | 
|  | MODULE_AUTHOR("Roy Pledge <roy.pledge@nxp.com>"); | 
|  | MODULE_DESCRIPTION("DPAA2 console driver"); |