aspeed_gfx_drv.c (9622B)
1// SPDX-License-Identifier: GPL-2.0+ 2// Copyright 2018 IBM Corporation 3 4#include <linux/clk.h> 5#include <linux/dma-mapping.h> 6#include <linux/irq.h> 7#include <linux/mfd/syscon.h> 8#include <linux/module.h> 9#include <linux/of.h> 10#include <linux/of_device.h> 11#include <linux/of_reserved_mem.h> 12#include <linux/platform_device.h> 13#include <linux/regmap.h> 14#include <linux/reset.h> 15 16#include <drm/drm_atomic_helper.h> 17#include <drm/drm_crtc_helper.h> 18#include <drm/drm_device.h> 19#include <drm/drm_fb_cma_helper.h> 20#include <drm/drm_fb_helper.h> 21#include <drm/drm_gem_cma_helper.h> 22#include <drm/drm_gem_framebuffer_helper.h> 23#include <drm/drm_module.h> 24#include <drm/drm_probe_helper.h> 25#include <drm/drm_simple_kms_helper.h> 26#include <drm/drm_vblank.h> 27#include <drm/drm_drv.h> 28 29#include "aspeed_gfx.h" 30 31/** 32 * DOC: ASPEED GFX Driver 33 * 34 * This driver is for the ASPEED BMC SoC's 'GFX' display hardware, also called 35 * the 'SOC Display Controller' in the datasheet. This driver runs on the ARM 36 * based BMC systems, unlike the ast driver which runs on a host CPU and is for 37 * a PCIe graphics device. 38 * 39 * The AST2500 supports a total of 3 output paths: 40 * 41 * 1. VGA output, the output target can choose either or both to the DAC 42 * or DVO interface. 43 * 44 * 2. Graphics CRT output, the output target can choose either or both to 45 * the DAC or DVO interface. 46 * 47 * 3. Video input from DVO, the video input can be used for video engine 48 * capture or DAC display output. 49 * 50 * Output options are selected in SCU2C. 51 * 52 * The "VGA mode" device is the PCI attached controller. The "Graphics CRT" 53 * is the ARM's internal display controller. 54 * 55 * The driver only supports a simple configuration consisting of a 40MHz 56 * pixel clock, fixed by hardware limitations, and the VGA output path. 57 * 58 * The driver was written with the 'AST2500 Software Programming Guide' v17, 59 * which is available under NDA from ASPEED. 60 */ 61 62struct aspeed_gfx_config { 63 u32 dac_reg; /* DAC register in SCU */ 64 u32 int_clear_reg; /* Interrupt clear register */ 65 u32 vga_scratch_reg; /* VGA scratch register in SCU */ 66 u32 throd_val; /* Default Threshold Seting */ 67 u32 scan_line_max; /* Max memory size of one scan line */ 68}; 69 70static const struct aspeed_gfx_config ast2400_config = { 71 .dac_reg = 0x2c, 72 .int_clear_reg = 0x60, 73 .vga_scratch_reg = 0x50, 74 .throd_val = CRT_THROD_LOW(0x1e) | CRT_THROD_HIGH(0x12), 75 .scan_line_max = 64, 76}; 77 78static const struct aspeed_gfx_config ast2500_config = { 79 .dac_reg = 0x2c, 80 .int_clear_reg = 0x60, 81 .vga_scratch_reg = 0x50, 82 .throd_val = CRT_THROD_LOW(0x24) | CRT_THROD_HIGH(0x3c), 83 .scan_line_max = 128, 84}; 85 86static const struct aspeed_gfx_config ast2600_config = { 87 .dac_reg = 0xc0, 88 .int_clear_reg = 0x68, 89 .vga_scratch_reg = 0x50, 90 .throd_val = CRT_THROD_LOW(0x50) | CRT_THROD_HIGH(0x70), 91 .scan_line_max = 128, 92}; 93 94static const struct of_device_id aspeed_gfx_match[] = { 95 { .compatible = "aspeed,ast2400-gfx", .data = &ast2400_config }, 96 { .compatible = "aspeed,ast2500-gfx", .data = &ast2500_config }, 97 { .compatible = "aspeed,ast2600-gfx", .data = &ast2600_config }, 98 { }, 99}; 100MODULE_DEVICE_TABLE(of, aspeed_gfx_match); 101 102static const struct drm_mode_config_funcs aspeed_gfx_mode_config_funcs = { 103 .fb_create = drm_gem_fb_create, 104 .atomic_check = drm_atomic_helper_check, 105 .atomic_commit = drm_atomic_helper_commit, 106}; 107 108static int aspeed_gfx_setup_mode_config(struct drm_device *drm) 109{ 110 int ret; 111 112 ret = drmm_mode_config_init(drm); 113 if (ret) 114 return ret; 115 116 drm->mode_config.min_width = 0; 117 drm->mode_config.min_height = 0; 118 drm->mode_config.max_width = 800; 119 drm->mode_config.max_height = 600; 120 drm->mode_config.funcs = &aspeed_gfx_mode_config_funcs; 121 122 return ret; 123} 124 125static irqreturn_t aspeed_gfx_irq_handler(int irq, void *data) 126{ 127 struct drm_device *drm = data; 128 struct aspeed_gfx *priv = to_aspeed_gfx(drm); 129 u32 reg; 130 131 reg = readl(priv->base + CRT_CTRL1); 132 133 if (reg & CRT_CTRL_VERTICAL_INTR_STS) { 134 drm_crtc_handle_vblank(&priv->pipe.crtc); 135 writel(reg, priv->base + priv->int_clr_reg); 136 return IRQ_HANDLED; 137 } 138 139 return IRQ_NONE; 140} 141 142static int aspeed_gfx_load(struct drm_device *drm) 143{ 144 struct platform_device *pdev = to_platform_device(drm->dev); 145 struct aspeed_gfx *priv = to_aspeed_gfx(drm); 146 struct device_node *np = pdev->dev.of_node; 147 const struct aspeed_gfx_config *config; 148 const struct of_device_id *match; 149 struct resource *res; 150 int ret; 151 152 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 153 priv->base = devm_ioremap_resource(drm->dev, res); 154 if (IS_ERR(priv->base)) 155 return PTR_ERR(priv->base); 156 157 match = of_match_device(aspeed_gfx_match, &pdev->dev); 158 if (!match) 159 return -EINVAL; 160 config = match->data; 161 162 priv->dac_reg = config->dac_reg; 163 priv->int_clr_reg = config->int_clear_reg; 164 priv->vga_scratch_reg = config->vga_scratch_reg; 165 priv->throd_val = config->throd_val; 166 priv->scan_line_max = config->scan_line_max; 167 168 priv->scu = syscon_regmap_lookup_by_phandle(np, "syscon"); 169 if (IS_ERR(priv->scu)) { 170 priv->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2500-scu"); 171 if (IS_ERR(priv->scu)) { 172 dev_err(&pdev->dev, "failed to find SCU regmap\n"); 173 return PTR_ERR(priv->scu); 174 } 175 } 176 177 ret = of_reserved_mem_device_init(drm->dev); 178 if (ret) { 179 dev_err(&pdev->dev, 180 "failed to initialize reserved mem: %d\n", ret); 181 return ret; 182 } 183 184 ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32)); 185 if (ret) { 186 dev_err(&pdev->dev, "failed to set DMA mask: %d\n", ret); 187 return ret; 188 } 189 190 priv->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); 191 if (IS_ERR(priv->rst)) { 192 dev_err(&pdev->dev, 193 "missing or invalid reset controller device tree entry"); 194 return PTR_ERR(priv->rst); 195 } 196 reset_control_deassert(priv->rst); 197 198 priv->clk = devm_clk_get(drm->dev, NULL); 199 if (IS_ERR(priv->clk)) { 200 dev_err(&pdev->dev, 201 "missing or invalid clk device tree entry"); 202 return PTR_ERR(priv->clk); 203 } 204 clk_prepare_enable(priv->clk); 205 206 /* Sanitize control registers */ 207 writel(0, priv->base + CRT_CTRL1); 208 writel(0, priv->base + CRT_CTRL2); 209 210 ret = aspeed_gfx_setup_mode_config(drm); 211 if (ret < 0) 212 return ret; 213 214 ret = drm_vblank_init(drm, 1); 215 if (ret < 0) { 216 dev_err(drm->dev, "Failed to initialise vblank\n"); 217 return ret; 218 } 219 220 ret = aspeed_gfx_create_output(drm); 221 if (ret < 0) { 222 dev_err(drm->dev, "Failed to create outputs\n"); 223 return ret; 224 } 225 226 ret = aspeed_gfx_create_pipe(drm); 227 if (ret < 0) { 228 dev_err(drm->dev, "Cannot setup simple display pipe\n"); 229 return ret; 230 } 231 232 ret = devm_request_irq(drm->dev, platform_get_irq(pdev, 0), 233 aspeed_gfx_irq_handler, 0, "aspeed gfx", drm); 234 if (ret < 0) { 235 dev_err(drm->dev, "Failed to install IRQ handler\n"); 236 return ret; 237 } 238 239 drm_mode_config_reset(drm); 240 241 return 0; 242} 243 244static void aspeed_gfx_unload(struct drm_device *drm) 245{ 246 drm_kms_helper_poll_fini(drm); 247} 248 249DEFINE_DRM_GEM_CMA_FOPS(fops); 250 251static const struct drm_driver aspeed_gfx_driver = { 252 .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, 253 DRM_GEM_CMA_DRIVER_OPS, 254 .fops = &fops, 255 .name = "aspeed-gfx-drm", 256 .desc = "ASPEED GFX DRM", 257 .date = "20180319", 258 .major = 1, 259 .minor = 0, 260}; 261 262static ssize_t dac_mux_store(struct device *dev, struct device_attribute *attr, 263 const char *buf, size_t count) 264{ 265 struct aspeed_gfx *priv = dev_get_drvdata(dev); 266 u32 val; 267 int rc; 268 269 rc = kstrtou32(buf, 0, &val); 270 if (rc) 271 return rc; 272 273 if (val > 3) 274 return -EINVAL; 275 276 rc = regmap_update_bits(priv->scu, priv->dac_reg, 0x30000, val << 16); 277 if (rc < 0) 278 return 0; 279 280 return count; 281} 282 283static ssize_t dac_mux_show(struct device *dev, struct device_attribute *attr, char *buf) 284{ 285 struct aspeed_gfx *priv = dev_get_drvdata(dev); 286 u32 reg; 287 int rc; 288 289 rc = regmap_read(priv->scu, priv->dac_reg, ®); 290 if (rc) 291 return rc; 292 293 return sprintf(buf, "%u\n", (reg >> 16) & 0x3); 294} 295static DEVICE_ATTR_RW(dac_mux); 296 297static ssize_t 298vga_pw_show(struct device *dev, struct device_attribute *attr, char *buf) 299{ 300 struct aspeed_gfx *priv = dev_get_drvdata(dev); 301 u32 reg; 302 int rc; 303 304 rc = regmap_read(priv->scu, priv->vga_scratch_reg, ®); 305 if (rc) 306 return rc; 307 308 return sprintf(buf, "%u\n", reg); 309} 310static DEVICE_ATTR_RO(vga_pw); 311 312static struct attribute *aspeed_sysfs_entries[] = { 313 &dev_attr_vga_pw.attr, 314 &dev_attr_dac_mux.attr, 315 NULL, 316}; 317 318static struct attribute_group aspeed_sysfs_attr_group = { 319 .attrs = aspeed_sysfs_entries, 320}; 321 322static int aspeed_gfx_probe(struct platform_device *pdev) 323{ 324 struct aspeed_gfx *priv; 325 int ret; 326 327 priv = devm_drm_dev_alloc(&pdev->dev, &aspeed_gfx_driver, 328 struct aspeed_gfx, drm); 329 if (IS_ERR(priv)) 330 return PTR_ERR(priv); 331 332 ret = aspeed_gfx_load(&priv->drm); 333 if (ret) 334 return ret; 335 336 platform_set_drvdata(pdev, priv); 337 338 ret = sysfs_create_group(&pdev->dev.kobj, &aspeed_sysfs_attr_group); 339 if (ret) 340 return ret; 341 342 ret = drm_dev_register(&priv->drm, 0); 343 if (ret) 344 goto err_unload; 345 346 drm_fbdev_generic_setup(&priv->drm, 32); 347 return 0; 348 349err_unload: 350 sysfs_remove_group(&pdev->dev.kobj, &aspeed_sysfs_attr_group); 351 aspeed_gfx_unload(&priv->drm); 352 353 return ret; 354} 355 356static int aspeed_gfx_remove(struct platform_device *pdev) 357{ 358 struct drm_device *drm = platform_get_drvdata(pdev); 359 360 sysfs_remove_group(&pdev->dev.kobj, &aspeed_sysfs_attr_group); 361 drm_dev_unregister(drm); 362 aspeed_gfx_unload(drm); 363 364 return 0; 365} 366 367static struct platform_driver aspeed_gfx_platform_driver = { 368 .probe = aspeed_gfx_probe, 369 .remove = aspeed_gfx_remove, 370 .driver = { 371 .name = "aspeed_gfx", 372 .of_match_table = aspeed_gfx_match, 373 }, 374}; 375 376drm_module_platform_driver(aspeed_gfx_platform_driver); 377 378MODULE_AUTHOR("Joel Stanley <joel@jms.id.au>"); 379MODULE_DESCRIPTION("ASPEED BMC DRM/KMS driver"); 380MODULE_LICENSE("GPL");