diff mbox

[RFC,01/10] video: fbdev: Xylon framebuffer driver

Message ID adfe8ef3-4681-47a3-a930-ba2618ab9ccc@mail.xylon.local (mailing list archive)
State New, archived
Headers show

Commit Message

Davor Joja Oct. 6, 2014, 1:32 p.m. UTC
Driver registration and OpenFirmware matching and parsing.

Signed-off-by: Davor Joja <davorjoja@logicbricks.com>
---
 drivers/video/fbdev/xylon/xylonfb_main.c | 532 +++++++++++++++++++++++++++++++
 1 file changed, 532 insertions(+)
 create mode 100644 drivers/video/fbdev/xylon/xylonfb_main.c
diff mbox

Patch

diff --git a/drivers/video/fbdev/xylon/xylonfb_main.c b/drivers/video/fbdev/xylon/xylonfb_main.c
new file mode 100644
index 0000000..743ba2a
--- /dev/null
+++ b/drivers/video/fbdev/xylon/xylonfb_main.c
@@ -0,0 +1,532 @@ 
+/*
+ * Xylon logiCVC frame buffer Open Firmware driver
+ *
+ * Copyright (C) 2014 Xylon d.o.o.
+ * Author: Davor Joja <davor.joja@logicbricks.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <video/of_display_timing.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include "xylonfb_core.h"
+#include "logicvc.h"
+
+static void xylonfb_init_ctrl(struct device_node *dn, enum display_flags flags,
+			      u32 *ctrl)
+{
+	u32 ctrl_reg = (LOGICVC_CTRL_HSYNC | LOGICVC_CTRL_VSYNC |
+			LOGICVC_CTRL_DATA_ENABLE);
+
+	XYLONFB_DBG(INFO, "%s", __func__);
+
+	if (of_property_read_bool(dn, "hsync-active-low") ||
+	    (flags & DISPLAY_FLAGS_HSYNC_LOW))
+		ctrl_reg |= LOGICVC_CTRL_HSYNC_INVERT;
+	if (of_property_read_bool(dn, "vsync-active-low") ||
+	    (flags & DISPLAY_FLAGS_VSYNC_LOW))
+		ctrl_reg |= LOGICVC_CTRL_VSYNC_INVERT;
+	if (of_property_read_bool(dn, "data-enable-active-low") ||
+	    (flags & DISPLAY_FLAGS_DE_LOW))
+		ctrl_reg |= LOGICVC_CTRL_DATA_ENABLE_INVERT;
+	if (of_property_read_bool(dn, "pixel-data-invert"))
+		ctrl_reg |= LOGICVC_CTRL_PIXEL_DATA_INVERT;
+	if (of_property_read_bool(dn, "pixel-data-output-trigger-high") ||
+	    (flags & DISPLAY_FLAGS_PIXDATA_POSEDGE))
+		ctrl_reg |= LOGICVC_CTRL_PIXEL_DATA_TRIGGER_INVERT;
+
+	*ctrl = ctrl_reg;
+}
+
+static int xylonfb_layer_set_format(struct xylonfb_layer_fix_data *fd,
+				    struct device *dev)
+{
+	XYLONFB_DBG(INFO, "%s", __func__);
+
+	switch (fd->type) {
+	case LOGICVC_LAYER_ALPHA:
+		fd->format = XYLONFB_FORMAT_A8;
+		break;
+
+	case LOGICVC_LAYER_RGB:
+		switch (fd->bpp) {
+		case 8:
+			switch (fd->transparency) {
+			case LOGICVC_ALPHA_CLUT_16BPP:
+				fd->format = XYLONFB_FORMAT_C8;
+				fd->format_clut = XYLONFB_FORMAT_CLUT_ARGB6565;
+				break;
+			case LOGICVC_ALPHA_CLUT_32BPP:
+				fd->format = XYLONFB_FORMAT_C8;
+				fd->format_clut = XYLONFB_FORMAT_CLUT_ARGB8888;
+				break;
+			case LOGICVC_ALPHA_LAYER:
+				fd->format = XYLONFB_FORMAT_RGB332;
+				break;
+			default:
+				return -EINVAL;
+			}
+			break;
+		case 16:
+			if (fd->transparency != LOGICVC_ALPHA_LAYER)
+				return -EINVAL;
+
+			fd->format = XYLONFB_FORMAT_RGB565;
+			break;
+		case 32:
+			switch (fd->transparency) {
+			case LOGICVC_ALPHA_LAYER:
+				fd->format = XYLONFB_FORMAT_XRGB8888;
+				break;
+			case LOGICVC_ALPHA_PIXEL:
+				fd->format = XYLONFB_FORMAT_ARGB8888;
+				break;
+			default:
+				return -EINVAL;
+			}
+			break;
+		}
+		break;
+
+	case LOGICVC_LAYER_YUV:
+		switch (fd->bpp) {
+		case 8:
+			if (fd->transparency != LOGICVC_ALPHA_CLUT_32BPP)
+				return -EINVAL;
+
+			fd->format = XYLONFB_FORMAT_C8;
+			fd->format_clut = XYLONFB_FORMAT_CLUT_AYUV8888;
+			break;
+		case 16:
+			if (fd->transparency != LOGICVC_ALPHA_LAYER)
+				return -EINVAL;
+
+			fd->format = XYLONFB_FORMAT_YUYV;
+			break;
+		case 32:
+			if (fd->transparency != LOGICVC_ALPHA_PIXEL)
+				return -EINVAL;
+
+			fd->format = XYLONFB_FORMAT_AYUV;
+			break;
+		}
+		break;
+
+	default:
+		dev_err(dev, "unsupported layer type\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int xylonfb_parse_layer_info(struct device_node *parent_dn,
+				    struct xylonfb_data *data, int id)
+{
+	struct device *dev = &data->pdev->dev;
+	struct device_node *dn;
+	struct xylonfb_layer_fix_data *fd;
+	int ret;
+	char layer_name[10];
+	const char *string;
+
+	XYLONFB_DBG(INFO, "%s", __func__);
+
+	snprintf(layer_name, sizeof(layer_name), "layer_%d", id);
+	dn = of_get_child_by_name(parent_dn, layer_name);
+	if (!dn)
+		return 0;
+
+	data->layers++;
+
+	fd = devm_kzalloc(&data->pdev->dev,
+			  sizeof(struct xylonfb_layer_fix_data), GFP_KERNEL);
+	if (!fd) {
+		dev_err(dev, "failed allocate layer fix data (%d)\n", id);
+		return -ENOMEM;
+	}
+
+	data->fd[id] = fd;
+
+	fd->id = id;
+
+	ret = of_property_read_u32(dn, "address", &fd->address);
+	if (ret && (ret != -EINVAL)) {
+		dev_err(dev, "failed get address\n");
+		return ret;
+	}
+	ret = of_property_read_u32_index(dn, "address", 1, &fd->address_range);
+
+	ret = of_property_read_u32(dn, "buffer-offset", &fd->buffer_offset);
+	if (ret && (ret != -EINVAL)) {
+		dev_err(dev, "failed get buffer-offset\n");
+		return ret;
+	}
+
+	ret = of_property_read_u32(dn, "bits-per-pixel", &fd->bpp);
+	if (ret) {
+		dev_err(dev, "failed get bits-per-pixel\n");
+		return ret;
+	}
+	switch (fd->bpp) {
+	case 8:
+	case 16:
+	case 32:
+		break;
+	default:
+		dev_err(dev, "invalid bits-per-pixel value\n");
+		return -EINVAL;
+	}
+
+	ret = of_property_read_string(dn, "type", &string);
+	if (ret) {
+		dev_err(dev, "failed get type\n");
+		return ret;
+	}
+	if (!strcmp(string, "alpha")) {
+		fd->type = LOGICVC_LAYER_ALPHA;
+	} else if (!strcmp(string, "rgb")) {
+		fd->type = LOGICVC_LAYER_RGB;
+	} else if (!strcmp(string, "yuv")) {
+		fd->type = LOGICVC_LAYER_YUV;
+	} else {
+		dev_err(dev, "unsupported layer type\n");
+		return -EINVAL;
+	}
+
+	if (fd->type != LOGICVC_LAYER_ALPHA) {
+		ret = of_property_read_string(dn, "transparency", &string);
+		if (ret) {
+			dev_err(dev, "failed get transparency\n");
+			return ret;
+		}
+		if (!strcmp(string, "clut16")) {
+			fd->transparency = LOGICVC_ALPHA_CLUT_16BPP;
+		} else if (!strcmp(string, "clut32")) {
+			fd->transparency = LOGICVC_ALPHA_CLUT_32BPP;
+		} else if (!strcmp(string, "layer")) {
+			fd->transparency = LOGICVC_ALPHA_LAYER;
+		} else if (!strcmp(string, "pixel")) {
+			fd->transparency = LOGICVC_ALPHA_PIXEL;
+		} else {
+			dev_err(dev, "unsupported layer transparency\n");
+			return -EINVAL;
+		}
+	}
+
+	if (of_property_read_bool(dn, "component-swap"))
+		fd->component_swap = true;
+
+	fd->width = data->pixel_stride;
+
+	ret = xylonfb_layer_set_format(fd, dev);
+	if (ret) {
+		dev_err(dev, "failed set layer format\n");
+		return ret;
+	}
+
+	of_node_put(dn);
+
+	return id + 1;
+}
+
+static int xylon_parse_hw_info(struct device_node *dn,
+			       struct xylonfb_data *data)
+{
+	struct device *dev = &data->pdev->dev;
+	int ret;
+	const char *string;
+
+	XYLONFB_DBG(INFO, "%s", __func__);
+
+	ret = of_property_read_u32(dn, "background-layer-bits-per-pixel",
+				   &data->bg_layer_bpp);
+	if (ret && (ret != -EINVAL)) {
+		dev_err(dev, "failed get bg-layer-bits-per-pixel\n");
+		return ret;
+	} else if (ret == 0) {
+		data->flags |= XYLONFB_FLAGS_BACKGROUND_LAYER;
+
+		ret = of_property_read_string(dn, "background-layer-type",
+					      &string);
+		if (ret) {
+			dev_err(dev, "failed get bg-layer-type\n");
+			return ret;
+		}
+		if (!strcmp(string, "rgb")) {
+			data->flags |= XYLONFB_FLAGS_BACKGROUND_LAYER_RGB;
+		} else if (!strcmp(string, "yuv")) {
+			data->flags |= XYLONFB_FLAGS_BACKGROUND_LAYER_YUV;
+		} else {
+			dev_err(dev, "unsupported bg layer type\n");
+			return -EINVAL;
+		}
+	}
+
+	if (of_property_read_bool(dn, "display-interface-itu656"))
+		data->flags |= XYLONFB_FLAGS_DISPLAY_INTERFACE_ITU656;
+
+	if (of_property_read_bool(dn, "readable-regs"))
+		data->flags |= XYLONFB_FLAGS_READABLE_REGS;
+	else
+		dev_warn(dev, "logicvc registers not readable\n");
+
+	if (of_property_read_bool(dn, "size-position"))
+		data->flags |= XYLONFB_FLAGS_SIZE_POSITION;
+	else
+		dev_warn(dev, "logicvc size-position disabled\n");
+
+	ret = of_property_read_u32(dn, "pixel-stride", &data->pixel_stride);
+	if (ret) {
+		dev_err(dev, "failed get pixel-stride\n");
+		return ret;
+	}
+
+	ret = of_property_read_u32(dn, "power-delay", &data->pwr_delay);
+	if (ret && (ret != -EINVAL)) {
+		dev_err(dev, "failed get power-delay\n");
+		return ret;
+	}
+
+	ret = of_property_read_u32(dn, "signal-delay", &data->sig_delay);
+	if (ret && (ret != -EINVAL)) {
+		dev_err(dev, "failed get signal\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id logicvc_of_match[] = {
+	{ .compatible = "xylon,logicvc-3.02.b" },
+	{ .compatible = "xylon,logicvc-4.01.b" },
+	{/* end of table */}
+};
+
+static int xylonfb_get_logicvc_configuration(struct xylonfb_data *data)
+{
+	struct device *dev = &data->pdev->dev;
+	struct device_node *dn = data->device;
+	const struct of_device_id *match;
+	struct videomode vm;
+	int i, ret;
+
+	XYLONFB_DBG(INFO, "%s", __func__);
+
+	match = of_match_node(logicvc_of_match, dn);
+	if (!match) {
+		dev_err(dev, "failed match logicvc\n");
+		return -ENODEV;
+	}
+
+	ret = of_address_to_resource(dn, 0, &data->resource_mem);
+	if (ret) {
+		dev_err(dev, "failed get mem resource\n");
+		return ret;
+	}
+	data->irq = of_irq_to_resource(dn, 0, &data->resource_irq);
+	if (data->irq == 0) {
+		dev_err(dev, "failed get irq resource\n");
+		return ret;
+	}
+
+	ret = xylon_parse_hw_info(dn, data);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < LOGICVC_MAX_LAYERS; i++) {
+		ret = xylonfb_parse_layer_info(dn, data, i);
+		if (ret < 0)
+			return ret;
+		if (ret == 0)
+			break;
+	}
+
+	if (data->flags & XYLONFB_FLAGS_BACKGROUND_LAYER &&
+	    data->layers == LOGICVC_MAX_LAYERS) {
+		data->flags &= ~XYLONFB_FLAGS_BACKGROUND_LAYER;
+		data->layers--;
+		if (data->console_layer == data->layers)
+			data->console_layer--;
+
+		dev_warn(dev, "invalid last layer configuration\n");
+	}
+
+	if (data->vm.name[0] == 0) {
+		ret = of_get_videomode(dn, &vm, OF_USE_NATIVE_MODE);
+		if (!ret) {
+			fb_videomode_from_videomode(&vm, &data->vm.vmode);
+
+			sprintf(data->vm.name, "%dx%d",
+				data->vm.vmode.xres, data->vm.vmode.yres);
+
+			data->flags |= XYLONFB_FLAGS_VMODE_CUSTOM;
+		}
+	}
+
+	xylonfb_init_ctrl(dn, vm.flags, &data->vm.ctrl);
+
+	return 0;
+}
+
+static int xylonfb_get_driver_configuration(struct xylonfb_data *data)
+{
+	struct device *dev = &data->pdev->dev;
+	struct device_node *dn = data->pdev->dev.of_node;
+	int ret;
+	const char *string;
+
+	XYLONFB_DBG(INFO, "%s", __func__);
+
+	data->device = of_parse_phandle(dn, "device", 0);
+	if (!data->device) {
+		dev_err(dev, "failed get device\n");
+		return -ENODEV;
+	}
+
+	data->encoder = of_parse_phandle(dn, "encoder", 0);
+	if (!data->encoder)
+		dev_warn(dev, "no available encoder\n");
+
+	data->pixel_clock = of_parse_phandle(dn, "clocks", 0);
+	if (!data->pixel_clock)
+		dev_warn(dev, "no available clocks\n");
+
+	ret = of_property_read_u32(dn, "console-layer", &data->console_layer);
+	if (ret && (ret != -EINVAL)) {
+			dev_err(dev, "failed get console-layer\n");
+			return ret;
+	} else {
+		data->flags |= XYLONFB_FLAGS_CHECK_CONSOLE_LAYER;
+	}
+
+	if (of_property_read_bool(dn, "vsync-irq"))
+		data->flags |= XYLONFB_FLAGS_VSYNC_IRQ;
+
+	ret = of_property_read_string(dn, "video-mode", &string);
+	if (ret && (ret != -EINVAL)) {
+		dev_err(dev, "failed get video-mode\n");
+		return ret;
+	} else if (ret == 0) {
+		strcpy(data->vm.name, string);
+		return 0;
+	}
+
+	return 0;
+}
+
+static int xylonfb_probe(struct platform_device *pdev)
+{
+	struct xylonfb_data *data;
+	int ret;
+
+	XYLONFB_DBG(INFO, "%s", __func__);
+
+	data = devm_kzalloc(&pdev->dev, sizeof(struct xylonfb_data),
+			     GFP_KERNEL);
+	if (!data) {
+		dev_err(&pdev->dev, "failed allocate init data\n");
+		return -ENOMEM;
+	}
+
+	data->pdev = pdev;
+
+	ret = xylonfb_get_driver_configuration(data);
+	if (ret)
+		goto xylonfb_probe_error;
+
+	ret = xylonfb_get_logicvc_configuration(data);
+	if (ret)
+		goto xylonfb_probe_error;
+
+	ret = xylonfb_init_core(data);
+
+xylonfb_probe_error:
+	return ret;
+}
+
+static int xylonfb_remove(struct platform_device *pdev)
+{
+	XYLONFB_DBG(INFO, "%s", __func__);
+
+	return xylonfb_deinit_core(pdev);
+}
+
+static const struct of_device_id xylonfb_of_match[] = {
+	{ .compatible = "xylon,fb-3.00.a" },
+	{/* end of table */},
+};
+MODULE_DEVICE_TABLE(of, xylonfb_of_match);
+
+static struct platform_driver xylonfb_driver = {
+	.probe = xylonfb_probe,
+	.remove = xylonfb_remove,
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = XYLONFB_DEVICE_NAME,
+		.of_match_table = xylonfb_of_match,
+	},
+};
+
+static int xylonfb_get_params(char *options)
+{
+	char *this_opt;
+
+	XYLONFB_DBG(INFO, "%s", __func__);
+
+	if (!options || !*options)
+		return 0;
+
+	while ((this_opt = strsep(&options, ",")) != NULL) {
+		if (!*this_opt)
+			continue;
+		xylonfb_mode_option = this_opt;
+	}
+	return 0;
+}
+
+static int xylonfb_init(void)
+{
+	char *option = NULL;
+	/*
+	 *  Kernel boot options (in 'video=xxxfb:<options>' format)
+	 */
+	if (fb_get_options(XYLONFB_DRIVER_NAME, &option))
+		return -ENODEV;
+	/* Set internal module parameters */
+	xylonfb_get_params(option);
+
+	if (platform_driver_register(&xylonfb_driver)) {
+		pr_err("failed %s driver registration\n", XYLONFB_DRIVER_NAME);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit xylonfb_exit(void)
+{
+	platform_driver_unregister(&xylonfb_driver);
+}
+
+module_init(xylonfb_init);
+module_exit(xylonfb_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION(XYLONFB_DRIVER_DESCRIPTION);
+MODULE_VERSION(XYLONFB_DRIVER_VERSION);