162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for FPGA UART
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2022 Intel Corporation.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Authors:
862306a36Sopenharmony_ci *   Ananda Ravuri <ananda.ravuri@intel.com>
962306a36Sopenharmony_ci *   Matthew Gerlach <matthew.gerlach@linux.intel.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/bitfield.h>
1362306a36Sopenharmony_ci#include <linux/device.h>
1462306a36Sopenharmony_ci#include <linux/dfl.h>
1562306a36Sopenharmony_ci#include <linux/errno.h>
1662306a36Sopenharmony_ci#include <linux/ioport.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1962306a36Sopenharmony_ci#include <linux/types.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <linux/serial.h>
2262306a36Sopenharmony_ci#include <linux/serial_8250.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define DFHv1_PARAM_ID_CLK_FRQ    0x2
2562306a36Sopenharmony_ci#define DFHv1_PARAM_ID_FIFO_LEN   0x3
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define DFHv1_PARAM_ID_REG_LAYOUT	0x4
2862306a36Sopenharmony_ci#define DFHv1_PARAM_REG_LAYOUT_WIDTH	GENMASK_ULL(63, 32)
2962306a36Sopenharmony_ci#define DFHv1_PARAM_REG_LAYOUT_SHIFT	GENMASK_ULL(31, 0)
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistruct dfl_uart {
3262306a36Sopenharmony_ci	int line;
3362306a36Sopenharmony_ci};
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic int dfh_get_u64_param_val(struct dfl_device *dfl_dev, int param_id, u64 *pval)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	size_t psize;
3862306a36Sopenharmony_ci	u64 *p;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	p = dfh_find_param(dfl_dev, param_id, &psize);
4162306a36Sopenharmony_ci	if (IS_ERR(p))
4262306a36Sopenharmony_ci		return PTR_ERR(p);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	if (psize != sizeof(*pval))
4562306a36Sopenharmony_ci		return -EINVAL;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	*pval = *p;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	return 0;
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic int dfl_uart_get_params(struct dfl_device *dfl_dev, struct uart_8250_port *uart)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	struct device *dev = &dfl_dev->dev;
5562306a36Sopenharmony_ci	u64 fifo_len, clk_freq, reg_layout;
5662306a36Sopenharmony_ci	u32 reg_width;
5762306a36Sopenharmony_ci	int ret;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	ret = dfh_get_u64_param_val(dfl_dev, DFHv1_PARAM_ID_CLK_FRQ, &clk_freq);
6062306a36Sopenharmony_ci	if (ret)
6162306a36Sopenharmony_ci		return dev_err_probe(dev, ret, "missing CLK_FRQ param\n");
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	uart->port.uartclk = clk_freq;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	ret = dfh_get_u64_param_val(dfl_dev, DFHv1_PARAM_ID_FIFO_LEN, &fifo_len);
6662306a36Sopenharmony_ci	if (ret)
6762306a36Sopenharmony_ci		return dev_err_probe(dev, ret, "missing FIFO_LEN param\n");
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	switch (fifo_len) {
7062306a36Sopenharmony_ci	case 32:
7162306a36Sopenharmony_ci		uart->port.type = PORT_ALTR_16550_F32;
7262306a36Sopenharmony_ci		break;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	case 64:
7562306a36Sopenharmony_ci		uart->port.type = PORT_ALTR_16550_F64;
7662306a36Sopenharmony_ci		break;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	case 128:
7962306a36Sopenharmony_ci		uart->port.type = PORT_ALTR_16550_F128;
8062306a36Sopenharmony_ci		break;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	default:
8362306a36Sopenharmony_ci		return dev_err_probe(dev, -EINVAL, "unsupported FIFO_LEN %llu\n", fifo_len);
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	ret = dfh_get_u64_param_val(dfl_dev, DFHv1_PARAM_ID_REG_LAYOUT, &reg_layout);
8762306a36Sopenharmony_ci	if (ret)
8862306a36Sopenharmony_ci		return dev_err_probe(dev, ret, "missing REG_LAYOUT param\n");
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	uart->port.regshift = FIELD_GET(DFHv1_PARAM_REG_LAYOUT_SHIFT, reg_layout);
9162306a36Sopenharmony_ci	reg_width = FIELD_GET(DFHv1_PARAM_REG_LAYOUT_WIDTH, reg_layout);
9262306a36Sopenharmony_ci	switch (reg_width) {
9362306a36Sopenharmony_ci	case 4:
9462306a36Sopenharmony_ci		uart->port.iotype = UPIO_MEM32;
9562306a36Sopenharmony_ci		break;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	case 2:
9862306a36Sopenharmony_ci		uart->port.iotype = UPIO_MEM16;
9962306a36Sopenharmony_ci		break;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	default:
10262306a36Sopenharmony_ci		return dev_err_probe(dev, -EINVAL, "unsupported reg-width %u\n", reg_width);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	return 0;
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic int dfl_uart_probe(struct dfl_device *dfl_dev)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	struct device *dev = &dfl_dev->dev;
11262306a36Sopenharmony_ci	struct uart_8250_port uart = { };
11362306a36Sopenharmony_ci	struct dfl_uart *dfluart;
11462306a36Sopenharmony_ci	int ret;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	uart.port.flags = UPF_IOREMAP;
11762306a36Sopenharmony_ci	uart.port.mapbase = dfl_dev->mmio_res.start;
11862306a36Sopenharmony_ci	uart.port.mapsize = resource_size(&dfl_dev->mmio_res);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	ret = dfl_uart_get_params(dfl_dev, &uart);
12162306a36Sopenharmony_ci	if (ret < 0)
12262306a36Sopenharmony_ci		return dev_err_probe(dev, ret, "failed uart feature walk\n");
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	if (dfl_dev->num_irqs == 1)
12562306a36Sopenharmony_ci		uart.port.irq = dfl_dev->irqs[0];
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	dfluart = devm_kzalloc(dev, sizeof(*dfluart), GFP_KERNEL);
12862306a36Sopenharmony_ci	if (!dfluart)
12962306a36Sopenharmony_ci		return -ENOMEM;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	dfluart->line = serial8250_register_8250_port(&uart);
13262306a36Sopenharmony_ci	if (dfluart->line < 0)
13362306a36Sopenharmony_ci		return dev_err_probe(dev, dfluart->line, "unable to register 8250 port.\n");
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	dev_set_drvdata(dev, dfluart);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	return 0;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic void dfl_uart_remove(struct dfl_device *dfl_dev)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	struct dfl_uart *dfluart = dev_get_drvdata(&dfl_dev->dev);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	serial8250_unregister_port(dfluart->line);
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci#define FME_FEATURE_ID_UART 0x24
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic const struct dfl_device_id dfl_uart_ids[] = {
15062306a36Sopenharmony_ci	{ FME_ID, FME_FEATURE_ID_UART },
15162306a36Sopenharmony_ci	{ }
15262306a36Sopenharmony_ci};
15362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(dfl, dfl_uart_ids);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic struct dfl_driver dfl_uart_driver = {
15662306a36Sopenharmony_ci	.drv = {
15762306a36Sopenharmony_ci		.name = "dfl-uart",
15862306a36Sopenharmony_ci	},
15962306a36Sopenharmony_ci	.id_table = dfl_uart_ids,
16062306a36Sopenharmony_ci	.probe = dfl_uart_probe,
16162306a36Sopenharmony_ci	.remove = dfl_uart_remove,
16262306a36Sopenharmony_ci};
16362306a36Sopenharmony_cimodule_dfl_driver(dfl_uart_driver);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ciMODULE_DESCRIPTION("DFL Intel UART driver");
16662306a36Sopenharmony_ciMODULE_AUTHOR("Intel Corporation");
16762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
168