// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2019-2022 Bootlin * Author: Paul Kocialkowski */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "logicvc_crtc.h" #include "logicvc_drm.h" #include "logicvc_interface.h" #include "logicvc_mode.h" #include "logicvc_layer.h" #include "logicvc_of.h" #include "logicvc_regs.h" DEFINE_DRM_GEM_CMA_FOPS(logicvc_drm_fops); static int logicvc_drm_gem_cma_dumb_create(struct drm_file *file_priv, struct drm_device *drm_dev, struct drm_mode_create_dumb *args) { struct logicvc_drm *logicvc = logicvc_drm(drm_dev); /* Stride is always fixed to its configuration value. */ args->pitch = logicvc->config.row_stride * DIV_ROUND_UP(args->bpp, 8); return drm_gem_cma_dumb_create_internal(file_priv, drm_dev, args); } static struct drm_driver logicvc_drm_driver = { .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, .fops = &logicvc_drm_fops, .name = "logicvc-drm", .desc = "Xylon LogiCVC DRM driver", .date = "20200403", .major = 1, .minor = 0, DRM_GEM_CMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(logicvc_drm_gem_cma_dumb_create), }; static struct regmap_config logicvc_drm_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .name = "logicvc-drm", }; static irqreturn_t logicvc_drm_irq_handler(int irq, void *data) { struct logicvc_drm *logicvc = data; irqreturn_t ret = IRQ_NONE; u32 stat = 0; /* Get pending interrupt sources. */ regmap_read(logicvc->regmap, LOGICVC_INT_STAT_REG, &stat); /* Clear all pending interrupt sources. */ regmap_write(logicvc->regmap, LOGICVC_INT_STAT_REG, stat); if (stat & LOGICVC_INT_STAT_V_SYNC) { logicvc_crtc_vblank_handler(logicvc); ret = IRQ_HANDLED; } return ret; } static int logicvc_drm_config_parse(struct logicvc_drm *logicvc) { struct drm_device *drm_dev = &logicvc->drm_dev; struct device *dev = drm_dev->dev; struct device_node *of_node = dev->of_node; struct logicvc_drm_config *config = &logicvc->config; struct device_node *layers_node; int ret; logicvc_of_property_parse_bool(of_node, LOGICVC_OF_PROPERTY_DITHERING, &config->dithering); logicvc_of_property_parse_bool(of_node, LOGICVC_OF_PROPERTY_BACKGROUND_LAYER, &config->background_layer); logicvc_of_property_parse_bool(of_node, LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE, &config->layers_configurable); ret = logicvc_of_property_parse_u32(of_node, LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE, &config->display_interface); if (ret) return ret; ret = logicvc_of_property_parse_u32(of_node, LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE, &config->display_colorspace); if (ret) return ret; ret = logicvc_of_property_parse_u32(of_node, LOGICVC_OF_PROPERTY_DISPLAY_DEPTH, &config->display_depth); if (ret) return ret; ret = logicvc_of_property_parse_u32(of_node, LOGICVC_OF_PROPERTY_ROW_STRIDE, &config->row_stride); if (ret) return ret; layers_node = of_get_child_by_name(of_node, "layers"); if (!layers_node) { drm_err(drm_dev, "Missing non-optional layers node\n"); return -EINVAL; } config->layers_count = of_get_child_count(layers_node); if (!config->layers_count) { drm_err(drm_dev, "Missing a non-optional layers children node\n"); return -EINVAL; } return 0; } static int logicvc_clocks_prepare(struct logicvc_drm *logicvc) { struct drm_device *drm_dev = &logicvc->drm_dev; struct device *dev = drm_dev->dev; struct { struct clk **clk; char *name; bool optional; } clocks_map[] = { { .clk = &logicvc->vclk, .name = "vclk", .optional = false, }, { .clk = &logicvc->vclk2, .name = "vclk2", .optional = true, }, { .clk = &logicvc->lvdsclk, .name = "lvdsclk", .optional = true, }, { .clk = &logicvc->lvdsclkn, .name = "lvdsclkn", .optional = true, }, }; unsigned int i; int ret; for (i = 0; i < ARRAY_SIZE(clocks_map); i++) { struct clk *clk; clk = devm_clk_get(dev, clocks_map[i].name); if (IS_ERR(clk)) { if (PTR_ERR(clk) == -ENOENT && clocks_map[i].optional) continue; drm_err(drm_dev, "Missing non-optional clock %s\n", clocks_map[i].name); ret = PTR_ERR(clk); goto error; } ret = clk_prepare_enable(clk); if (ret) { drm_err(drm_dev, "Failed to prepare and enable clock %s\n", clocks_map[i].name); goto error; } *clocks_map[i].clk = clk; } return 0; error: for (i = 0; i < ARRAY_SIZE(clocks_map); i++) { if (!*clocks_map[i].clk) continue; clk_disable_unprepare(*clocks_map[i].clk); *clocks_map[i].clk = NULL; } return ret; } static int logicvc_clocks_unprepare(struct logicvc_drm *logicvc) { struct clk **clocks[] = { &logicvc->vclk, &logicvc->vclk2, &logicvc->lvdsclk, &logicvc->lvdsclkn, }; unsigned int i; for (i = 0; i < ARRAY_SIZE(clocks); i++) { if (!*clocks[i]) continue; clk_disable_unprepare(*clocks[i]); *clocks[i] = NULL; } return 0; } static const struct logicvc_drm_caps logicvc_drm_caps[] = { { .major = 3, .layer_address = false, }, { .major = 4, .layer_address = true, }, { .major = 5, .layer_address = true, }, }; static const struct logicvc_drm_caps * logicvc_drm_caps_match(struct logicvc_drm *logicvc) { struct drm_device *drm_dev = &logicvc->drm_dev; const struct logicvc_drm_caps *caps = NULL; unsigned int major, minor; char level; unsigned int i; u32 version; regmap_read(logicvc->regmap, LOGICVC_IP_VERSION_REG, &version); major = FIELD_GET(LOGICVC_IP_VERSION_MAJOR_MASK, version); minor = FIELD_GET(LOGICVC_IP_VERSION_MINOR_MASK, version); level = FIELD_GET(LOGICVC_IP_VERSION_LEVEL_MASK, version) + 'a'; for (i = 0; i < ARRAY_SIZE(logicvc_drm_caps); i++) { if (logicvc_drm_caps[i].major && logicvc_drm_caps[i].major != major) continue; if (logicvc_drm_caps[i].minor && logicvc_drm_caps[i].minor != minor) continue; if (logicvc_drm_caps[i].level && logicvc_drm_caps[i].level != level) continue; caps = &logicvc_drm_caps[i]; } drm_info(drm_dev, "LogiCVC version %d.%02d.%c\n", major, minor, level); return caps; } static int logicvc_drm_probe(struct platform_device *pdev) { struct device_node *of_node = pdev->dev.of_node; struct device_node *reserved_mem_node; struct reserved_mem *reserved_mem = NULL; const struct logicvc_drm_caps *caps; struct logicvc_drm *logicvc; struct device *dev = &pdev->dev; struct drm_device *drm_dev; struct regmap *regmap = NULL; struct resource res; void __iomem *base; int irq; int ret; ret = of_reserved_mem_device_init(dev); if (ret && ret != -ENODEV) { dev_err(dev, "Failed to init memory region\n"); goto error_early; } reserved_mem_node = of_parse_phandle(of_node, "memory-region", 0); if (reserved_mem_node) { reserved_mem = of_reserved_mem_lookup(reserved_mem_node); of_node_put(reserved_mem_node); } /* Get regmap from parent if available. */ if (of_node->parent) regmap = syscon_node_to_regmap(of_node->parent); /* Register our own regmap otherwise. */ if (IS_ERR_OR_NULL(regmap)) { ret = of_address_to_resource(of_node, 0, &res); if (ret) { dev_err(dev, "Failed to get resource from address\n"); goto error_reserved_mem; } base = devm_ioremap_resource(dev, &res); if (IS_ERR(base)) { dev_err(dev, "Failed to map I/O base\n"); ret = PTR_ERR(base); goto error_reserved_mem; } logicvc_drm_regmap_config.max_register = resource_size(&res) - 4; regmap = devm_regmap_init_mmio(dev, base, &logicvc_drm_regmap_config); if (IS_ERR(regmap)) { dev_err(dev, "Failed to create regmap for I/O\n"); ret = PTR_ERR(regmap); goto error_reserved_mem; } } irq = platform_get_irq(pdev, 0); if (irq < 0) { ret = -ENODEV; goto error_reserved_mem; } logicvc = devm_drm_dev_alloc(dev, &logicvc_drm_driver, struct logicvc_drm, drm_dev); if (IS_ERR(logicvc)) { ret = PTR_ERR(logicvc); goto error_reserved_mem; } platform_set_drvdata(pdev, logicvc); drm_dev = &logicvc->drm_dev; logicvc->regmap = regmap; INIT_LIST_HEAD(&logicvc->layers_list); caps = logicvc_drm_caps_match(logicvc); if (!caps) { ret = -EINVAL; goto error_reserved_mem; } logicvc->caps = caps; if (reserved_mem) logicvc->reserved_mem_base = reserved_mem->base; ret = logicvc_clocks_prepare(logicvc); if (ret) { drm_err(drm_dev, "Failed to prepare clocks\n"); goto error_reserved_mem; } ret = devm_request_irq(dev, irq, logicvc_drm_irq_handler, 0, dev_name(dev), logicvc); if (ret) { drm_err(drm_dev, "Failed to request IRQ\n"); goto error_clocks; } ret = logicvc_drm_config_parse(logicvc); if (ret && ret != -ENODEV) { drm_err(drm_dev, "Failed to parse config\n"); goto error_clocks; } ret = drmm_mode_config_init(drm_dev); if (ret) { drm_err(drm_dev, "Failed to init mode config\n"); goto error_clocks; } ret = logicvc_layers_init(logicvc); if (ret) { drm_err(drm_dev, "Failed to initialize layers\n"); goto error_clocks; } ret = logicvc_crtc_init(logicvc); if (ret) { drm_err(drm_dev, "Failed to initialize CRTC\n"); goto error_clocks; } logicvc_layers_attach_crtc(logicvc); ret = logicvc_interface_init(logicvc); if (ret) { if (ret != -EPROBE_DEFER) drm_err(drm_dev, "Failed to initialize interface\n"); goto error_clocks; } logicvc_interface_attach_crtc(logicvc); ret = logicvc_mode_init(logicvc); if (ret) { drm_err(drm_dev, "Failed to initialize KMS\n"); goto error_clocks; } ret = drm_dev_register(drm_dev, 0); if (ret) { drm_err(drm_dev, "Failed to register DRM device\n"); goto error_mode; } drm_fbdev_generic_setup(drm_dev, drm_dev->mode_config.preferred_depth); return 0; error_mode: logicvc_mode_fini(logicvc); error_clocks: logicvc_clocks_unprepare(logicvc); error_reserved_mem: of_reserved_mem_device_release(dev); error_early: return ret; } static int logicvc_drm_remove(struct platform_device *pdev) { struct logicvc_drm *logicvc = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; struct drm_device *drm_dev = &logicvc->drm_dev; drm_dev_unregister(drm_dev); drm_atomic_helper_shutdown(drm_dev); logicvc_mode_fini(logicvc); logicvc_clocks_unprepare(logicvc); of_reserved_mem_device_release(dev); return 0; } static const struct of_device_id logicvc_drm_of_table[] = { { .compatible = "xylon,logicvc-3.02.a-display" }, { .compatible = "xylon,logicvc-4.01.a-display" }, {}, }; MODULE_DEVICE_TABLE(of, logicvc_drm_of_table); static struct platform_driver logicvc_drm_platform_driver = { .probe = logicvc_drm_probe, .remove = logicvc_drm_remove, .driver = { .name = "logicvc-drm", .of_match_table = logicvc_drm_of_table, }, }; module_platform_driver(logicvc_drm_platform_driver); MODULE_AUTHOR("Paul Kocialkowski "); MODULE_DESCRIPTION("Xylon LogiCVC DRM driver"); MODULE_LICENSE("GPL");