Driver registration and OF binding. Signed-off-by: Davor Joja <davorjoja@xxxxxxxxxxxxxxx> --- drivers/video/fbdev/xylon/xylonfb_main.c | 528 +++++++++++++++++++++++++++++++ 1 file changed, 528 insertions(+) create mode 100644 drivers/video/fbdev/xylon/xylonfb_main.c diff --git a/drivers/video/fbdev/xylon/xylonfb_main.c b/drivers/video/fbdev/xylon/xylonfb_main.c new file mode 100644 index 0000000..3313da5 --- /dev/null +++ b/drivers/video/fbdev/xylon/xylonfb_main.c @@ -0,0 +1,528 @@ +/* + * Xylon logiCVC frame buffer Open Firmware driver + * + * Copyright (C) 2015 Xylon d.o.o. + * Author: Davor Joja <davor.joja@xxxxxxxxxxxxxxx> + * + * 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.00.a" }, + { .compatible = "xylon,logicvc-4.00.a" }, + {/* 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"); + } + + memset(&vm, 0, sizeof(vm)); + + 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->pixel_clock = of_parse_phandle(dn, "clocks", 0); + + 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); -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html