18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci// Copyright 2018 IBM Corporation
38c2ecf20Sopenharmony_ci
48c2ecf20Sopenharmony_ci#include <linux/clk.h>
58c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h>
68c2ecf20Sopenharmony_ci#include <linux/irq.h>
78c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/of.h>
108c2ecf20Sopenharmony_ci#include <linux/of_reserved_mem.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include <linux/regmap.h>
138c2ecf20Sopenharmony_ci#include <linux/reset.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h>
168c2ecf20Sopenharmony_ci#include <drm/drm_crtc_helper.h>
178c2ecf20Sopenharmony_ci#include <drm/drm_device.h>
188c2ecf20Sopenharmony_ci#include <drm/drm_fb_cma_helper.h>
198c2ecf20Sopenharmony_ci#include <drm/drm_fb_helper.h>
208c2ecf20Sopenharmony_ci#include <drm/drm_gem_cma_helper.h>
218c2ecf20Sopenharmony_ci#include <drm/drm_gem_framebuffer_helper.h>
228c2ecf20Sopenharmony_ci#include <drm/drm_probe_helper.h>
238c2ecf20Sopenharmony_ci#include <drm/drm_simple_kms_helper.h>
248c2ecf20Sopenharmony_ci#include <drm/drm_vblank.h>
258c2ecf20Sopenharmony_ci#include <drm/drm_drv.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#include "aspeed_gfx.h"
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci/**
308c2ecf20Sopenharmony_ci * DOC: ASPEED GFX Driver
318c2ecf20Sopenharmony_ci *
328c2ecf20Sopenharmony_ci * This driver is for the ASPEED BMC SoC's 'GFX' display hardware, also called
338c2ecf20Sopenharmony_ci * the 'SOC Display Controller' in the datasheet. This driver runs on the ARM
348c2ecf20Sopenharmony_ci * based BMC systems, unlike the ast driver which runs on a host CPU and is for
358c2ecf20Sopenharmony_ci * a PCIe graphics device.
368c2ecf20Sopenharmony_ci *
378c2ecf20Sopenharmony_ci * The AST2500 supports a total of 3 output paths:
388c2ecf20Sopenharmony_ci *
398c2ecf20Sopenharmony_ci *   1. VGA output, the output target can choose either or both to the DAC
408c2ecf20Sopenharmony_ci *   or DVO interface.
418c2ecf20Sopenharmony_ci *
428c2ecf20Sopenharmony_ci *   2. Graphics CRT output, the output target can choose either or both to
438c2ecf20Sopenharmony_ci *   the DAC or DVO interface.
448c2ecf20Sopenharmony_ci *
458c2ecf20Sopenharmony_ci *   3. Video input from DVO, the video input can be used for video engine
468c2ecf20Sopenharmony_ci *   capture or DAC display output.
478c2ecf20Sopenharmony_ci *
488c2ecf20Sopenharmony_ci * Output options are selected in SCU2C.
498c2ecf20Sopenharmony_ci *
508c2ecf20Sopenharmony_ci * The "VGA mode" device is the PCI attached controller. The "Graphics CRT"
518c2ecf20Sopenharmony_ci * is the ARM's internal display controller.
528c2ecf20Sopenharmony_ci *
538c2ecf20Sopenharmony_ci * The driver only supports a simple configuration consisting of a 40MHz
548c2ecf20Sopenharmony_ci * pixel clock, fixed by hardware limitations, and the VGA output path.
558c2ecf20Sopenharmony_ci *
568c2ecf20Sopenharmony_ci * The driver was written with the 'AST2500 Software Programming Guide' v17,
578c2ecf20Sopenharmony_ci * which is available under NDA from ASPEED.
588c2ecf20Sopenharmony_ci */
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic const struct drm_mode_config_funcs aspeed_gfx_mode_config_funcs = {
618c2ecf20Sopenharmony_ci	.fb_create		= drm_gem_fb_create,
628c2ecf20Sopenharmony_ci	.atomic_check		= drm_atomic_helper_check,
638c2ecf20Sopenharmony_ci	.atomic_commit		= drm_atomic_helper_commit,
648c2ecf20Sopenharmony_ci};
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic int aspeed_gfx_setup_mode_config(struct drm_device *drm)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	int ret;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	ret = drmm_mode_config_init(drm);
718c2ecf20Sopenharmony_ci	if (ret)
728c2ecf20Sopenharmony_ci		return ret;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	drm->mode_config.min_width = 0;
758c2ecf20Sopenharmony_ci	drm->mode_config.min_height = 0;
768c2ecf20Sopenharmony_ci	drm->mode_config.max_width = 800;
778c2ecf20Sopenharmony_ci	drm->mode_config.max_height = 600;
788c2ecf20Sopenharmony_ci	drm->mode_config.funcs = &aspeed_gfx_mode_config_funcs;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	return ret;
818c2ecf20Sopenharmony_ci}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistatic irqreturn_t aspeed_gfx_irq_handler(int irq, void *data)
848c2ecf20Sopenharmony_ci{
858c2ecf20Sopenharmony_ci	struct drm_device *drm = data;
868c2ecf20Sopenharmony_ci	struct aspeed_gfx *priv = to_aspeed_gfx(drm);
878c2ecf20Sopenharmony_ci	u32 reg;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	reg = readl(priv->base + CRT_CTRL1);
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	if (reg & CRT_CTRL_VERTICAL_INTR_STS) {
928c2ecf20Sopenharmony_ci		drm_crtc_handle_vblank(&priv->pipe.crtc);
938c2ecf20Sopenharmony_ci		writel(reg, priv->base + CRT_CTRL1);
948c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
958c2ecf20Sopenharmony_ci	}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	return IRQ_NONE;
988c2ecf20Sopenharmony_ci}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistatic int aspeed_gfx_load(struct drm_device *drm)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	struct platform_device *pdev = to_platform_device(drm->dev);
1058c2ecf20Sopenharmony_ci	struct aspeed_gfx *priv = to_aspeed_gfx(drm);
1068c2ecf20Sopenharmony_ci	struct resource *res;
1078c2ecf20Sopenharmony_ci	int ret;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1108c2ecf20Sopenharmony_ci	priv->base = devm_ioremap_resource(drm->dev, res);
1118c2ecf20Sopenharmony_ci	if (IS_ERR(priv->base))
1128c2ecf20Sopenharmony_ci		return PTR_ERR(priv->base);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	priv->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2500-scu");
1158c2ecf20Sopenharmony_ci	if (IS_ERR(priv->scu)) {
1168c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to find SCU regmap\n");
1178c2ecf20Sopenharmony_ci		return PTR_ERR(priv->scu);
1188c2ecf20Sopenharmony_ci	}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	ret = of_reserved_mem_device_init(drm->dev);
1218c2ecf20Sopenharmony_ci	if (ret) {
1228c2ecf20Sopenharmony_ci		dev_err(&pdev->dev,
1238c2ecf20Sopenharmony_ci			"failed to initialize reserved mem: %d\n", ret);
1248c2ecf20Sopenharmony_ci		return ret;
1258c2ecf20Sopenharmony_ci	}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32));
1288c2ecf20Sopenharmony_ci	if (ret) {
1298c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to set DMA mask: %d\n", ret);
1308c2ecf20Sopenharmony_ci		return ret;
1318c2ecf20Sopenharmony_ci	}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	priv->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
1348c2ecf20Sopenharmony_ci	if (IS_ERR(priv->rst)) {
1358c2ecf20Sopenharmony_ci		dev_err(&pdev->dev,
1368c2ecf20Sopenharmony_ci			"missing or invalid reset controller device tree entry");
1378c2ecf20Sopenharmony_ci		return PTR_ERR(priv->rst);
1388c2ecf20Sopenharmony_ci	}
1398c2ecf20Sopenharmony_ci	reset_control_deassert(priv->rst);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	priv->clk = devm_clk_get(drm->dev, NULL);
1428c2ecf20Sopenharmony_ci	if (IS_ERR(priv->clk)) {
1438c2ecf20Sopenharmony_ci		dev_err(&pdev->dev,
1448c2ecf20Sopenharmony_ci			"missing or invalid clk device tree entry");
1458c2ecf20Sopenharmony_ci		return PTR_ERR(priv->clk);
1468c2ecf20Sopenharmony_ci	}
1478c2ecf20Sopenharmony_ci	clk_prepare_enable(priv->clk);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	/* Sanitize control registers */
1508c2ecf20Sopenharmony_ci	writel(0, priv->base + CRT_CTRL1);
1518c2ecf20Sopenharmony_ci	writel(0, priv->base + CRT_CTRL2);
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	ret = aspeed_gfx_setup_mode_config(drm);
1548c2ecf20Sopenharmony_ci	if (ret < 0)
1558c2ecf20Sopenharmony_ci		return ret;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	ret = drm_vblank_init(drm, 1);
1588c2ecf20Sopenharmony_ci	if (ret < 0) {
1598c2ecf20Sopenharmony_ci		dev_err(drm->dev, "Failed to initialise vblank\n");
1608c2ecf20Sopenharmony_ci		return ret;
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	ret = aspeed_gfx_create_output(drm);
1648c2ecf20Sopenharmony_ci	if (ret < 0) {
1658c2ecf20Sopenharmony_ci		dev_err(drm->dev, "Failed to create outputs\n");
1668c2ecf20Sopenharmony_ci		return ret;
1678c2ecf20Sopenharmony_ci	}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	ret = aspeed_gfx_create_pipe(drm);
1708c2ecf20Sopenharmony_ci	if (ret < 0) {
1718c2ecf20Sopenharmony_ci		dev_err(drm->dev, "Cannot setup simple display pipe\n");
1728c2ecf20Sopenharmony_ci		return ret;
1738c2ecf20Sopenharmony_ci	}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	ret = devm_request_irq(drm->dev, platform_get_irq(pdev, 0),
1768c2ecf20Sopenharmony_ci			       aspeed_gfx_irq_handler, 0, "aspeed gfx", drm);
1778c2ecf20Sopenharmony_ci	if (ret < 0) {
1788c2ecf20Sopenharmony_ci		dev_err(drm->dev, "Failed to install IRQ handler\n");
1798c2ecf20Sopenharmony_ci		return ret;
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	drm_mode_config_reset(drm);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	return 0;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic void aspeed_gfx_unload(struct drm_device *drm)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	drm_kms_helper_poll_fini(drm);
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ciDEFINE_DRM_GEM_CMA_FOPS(fops);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_cistatic struct drm_driver aspeed_gfx_driver = {
1958c2ecf20Sopenharmony_ci	.driver_features        = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
1968c2ecf20Sopenharmony_ci	.gem_create_object	= drm_gem_cma_create_object_default_funcs,
1978c2ecf20Sopenharmony_ci	.dumb_create		= drm_gem_cma_dumb_create,
1988c2ecf20Sopenharmony_ci	.prime_handle_to_fd	= drm_gem_prime_handle_to_fd,
1998c2ecf20Sopenharmony_ci	.prime_fd_to_handle	= drm_gem_prime_fd_to_handle,
2008c2ecf20Sopenharmony_ci	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
2018c2ecf20Sopenharmony_ci	.gem_prime_mmap		= drm_gem_prime_mmap,
2028c2ecf20Sopenharmony_ci	.fops = &fops,
2038c2ecf20Sopenharmony_ci	.name = "aspeed-gfx-drm",
2048c2ecf20Sopenharmony_ci	.desc = "ASPEED GFX DRM",
2058c2ecf20Sopenharmony_ci	.date = "20180319",
2068c2ecf20Sopenharmony_ci	.major = 1,
2078c2ecf20Sopenharmony_ci	.minor = 0,
2088c2ecf20Sopenharmony_ci};
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cistatic const struct of_device_id aspeed_gfx_match[] = {
2118c2ecf20Sopenharmony_ci	{ .compatible = "aspeed,ast2500-gfx" },
2128c2ecf20Sopenharmony_ci	{ }
2138c2ecf20Sopenharmony_ci};
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cistatic int aspeed_gfx_probe(struct platform_device *pdev)
2168c2ecf20Sopenharmony_ci{
2178c2ecf20Sopenharmony_ci	struct aspeed_gfx *priv;
2188c2ecf20Sopenharmony_ci	int ret;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	priv = devm_drm_dev_alloc(&pdev->dev, &aspeed_gfx_driver,
2218c2ecf20Sopenharmony_ci				  struct aspeed_gfx, drm);
2228c2ecf20Sopenharmony_ci	if (IS_ERR(priv))
2238c2ecf20Sopenharmony_ci		return PTR_ERR(priv);
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	ret = aspeed_gfx_load(&priv->drm);
2268c2ecf20Sopenharmony_ci	if (ret)
2278c2ecf20Sopenharmony_ci		return ret;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	ret = drm_dev_register(&priv->drm, 0);
2308c2ecf20Sopenharmony_ci	if (ret)
2318c2ecf20Sopenharmony_ci		goto err_unload;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	drm_fbdev_generic_setup(&priv->drm, 32);
2348c2ecf20Sopenharmony_ci	return 0;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_cierr_unload:
2378c2ecf20Sopenharmony_ci	aspeed_gfx_unload(&priv->drm);
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	return ret;
2408c2ecf20Sopenharmony_ci}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_cistatic int aspeed_gfx_remove(struct platform_device *pdev)
2438c2ecf20Sopenharmony_ci{
2448c2ecf20Sopenharmony_ci	struct drm_device *drm = platform_get_drvdata(pdev);
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	drm_dev_unregister(drm);
2478c2ecf20Sopenharmony_ci	aspeed_gfx_unload(drm);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	return 0;
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_cistatic struct platform_driver aspeed_gfx_platform_driver = {
2538c2ecf20Sopenharmony_ci	.probe		= aspeed_gfx_probe,
2548c2ecf20Sopenharmony_ci	.remove		= aspeed_gfx_remove,
2558c2ecf20Sopenharmony_ci	.driver = {
2568c2ecf20Sopenharmony_ci		.name = "aspeed_gfx",
2578c2ecf20Sopenharmony_ci		.of_match_table = aspeed_gfx_match,
2588c2ecf20Sopenharmony_ci	},
2598c2ecf20Sopenharmony_ci};
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cimodule_platform_driver(aspeed_gfx_platform_driver);
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ciMODULE_AUTHOR("Joel Stanley <joel@jms.id.au>");
2648c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ASPEED BMC DRM/KMS driver");
2658c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
266