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