/* * Copied from Linux drivers/gpu/drm/ast/ast_mode.c * * Copyright 2012 Red Hat Inc. * Parts based on xf86-video-ast * Copyright (c) 2005 ASPEED Technology Inc. * Copyright Dave Airlie * Copyright 2019 9Elements Agency GmbH * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sub license, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * USE OR OTHER DEALINGS IN THE SOFTWARE. * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * */ #include #include "ast_drv.h" /* * Set framebuffer MMIO address, which must fall into BAR0 MMIO window. * * Complete reimplementation as the original expects multiple kernel internal * subsystems to be present. */ int ast_crtc_do_set_base(struct drm_crtc *crtc) { struct ast_private *ast = crtc->dev->dev_private; struct drm_framebuffer *fb = crtc->primary->fb; /* PCI BAR 0 */ struct resource *res = find_resource(crtc->dev->pdev, 0x10); if (!res) { printk(BIOS_ERR, "BAR0 resource not found.\n"); return -EIO; } if (res->size < fb->pitches[0] * crtc->mode.vdisplay) { dev_err(dev->pdev, "Framebuffer doesn't fit into BAR0 MMIO window\n"); return -ENOMEM; } fb->mmio_addr = (u32)res2mmio(res, 4095, 4095); ast_set_offset_reg(crtc); ast_set_start_address_crt1(ast, fb->mmio_addr); return 0; } static void ast_edid_to_drmmode(struct edid *edid, struct drm_display_mode *mode) { memset(mode, 0, sizeof(*mode)); mode->hdisplay = edid->mode.ha; mode->vdisplay = edid->mode.va; mode->crtc_hdisplay = edid->mode.ha; mode->crtc_vdisplay = edid->mode.va; /* EDID clock is in 10kHz, but drm clock is in KHz */ mode->clock = edid->mode.pixel_clock * 10; mode->vrefresh = edid->mode.refresh; mode->crtc_hblank_start = edid->mode.ha; mode->crtc_hblank_end = edid->mode.ha + edid->mode.hbl; mode->crtc_hsync_start = edid->mode.ha + edid->mode.hso; mode->crtc_hsync_end = edid->mode.ha + edid->mode.hso + edid->mode.hspw; mode->crtc_htotal = mode->crtc_hblank_end; mode->crtc_vblank_start = edid->mode.va; mode->crtc_vblank_end = edid->mode.va + edid->mode.vbl; mode->crtc_vsync_start = edid->mode.va + edid->mode.vso; mode->crtc_vsync_end = edid->mode.va + edid->mode.vso + edid->mode.vspw; mode->crtc_vtotal = mode->crtc_vblank_end; mode->flags = 0; if (edid->mode.phsync == '+') mode->flags |= DRM_MODE_FLAG_PHSYNC; else mode->flags |= DRM_MODE_FLAG_NHSYNC; if (edid->mode.pvsync == '+') mode->flags |= DRM_MODE_FLAG_PVSYNC; else mode->flags |= DRM_MODE_FLAG_NVSYNC; } static int ast_select_mode(struct drm_connector *connector, struct edid *edid) { struct ast_private *ast = connector->dev->dev_private; bool widescreen; u8 raw[128]; bool flags = false; if (ast->tx_chip_type == AST_TX_DP501) { ast->dp501_maxclk = 0xff; flags = ast_dp501_read_edid(connector->dev, (u8 *)raw); if (flags) ast->dp501_maxclk = ast_get_dp501_max_clk(connector->dev); else dev_err(dev->pdev, "I2C transmission error\n"); } if (!flags) ast_software_i2c_read(ast, raw); if (decode_edid(raw, sizeof(raw), edid) != EDID_CONFORMANT) { dev_err(dev->pdev, "Failed to decode EDID\n"); printk(BIOS_DEBUG, "Assuming VGA for KVM\n"); memset(edid, 0, sizeof(*edid)); edid->mode.pixel_clock = 6411; edid->mode.refresh = 60; edid->mode.ha = 1024; edid->mode.hspw = 4; edid->mode.hso = 56; edid->mode.hbl = 264; edid->mode.phsync = '-'; edid->mode.va = 768; edid->mode.vspw = 3; edid->mode.vso = 1; edid->mode.vbl = 26; edid->mode.pvsync = '+'; } printk(BIOS_DEBUG, "AST: Display has %dpx x %dpx\n", edid->mode.ha, edid->mode.va); widescreen = !!(((edid->mode.ha * 4) % (edid->mode.va * 3))); while (ast_mode_valid(connector, edid->mode.ha, edid->mode.va) != MODE_OK) { /* Select a compatible smaller mode */ if (edid->mode.ha > 1920 && widescreen) { edid->mode.ha = 1920; edid->mode.va = 1080; } else if (edid->mode.ha >= 1920 && widescreen) { edid->mode.ha = 1680; edid->mode.va = 1050; } else if (edid->mode.ha >= 1680 && widescreen) { edid->mode.ha = 1600; edid->mode.va = 900; } else if (edid->mode.ha >= 1680 && !widescreen) { edid->mode.ha = 1600; edid->mode.va = 1200; } else if (edid->mode.ha >= 1600 && widescreen) { edid->mode.ha = 1440; edid->mode.va = 900; } else if (edid->mode.ha >= 1440 && widescreen) { edid->mode.ha = 1360; edid->mode.va = 768; } else if (edid->mode.ha >= 1360 && widescreen) { edid->mode.ha = 1280; edid->mode.va = 800; } else if (edid->mode.ha >= 1360 && !widescreen) { edid->mode.ha = 1280; edid->mode.va = 1024; } else if (edid->mode.ha >= 1280) { edid->mode.ha = 1024; edid->mode.va = 768; } else if (edid->mode.ha >= 1024) { edid->mode.ha = 800; edid->mode.va = 600; } else if (edid->mode.ha >= 800) { edid->mode.ha = 640; edid->mode.va = 480; } else { dev_err(dev->pdev, "No compatible mode found.\n"); return -EIO; } }; return 0; } int ast_driver_framebuffer_init(struct drm_device *dev, int flags) { struct drm_display_mode adjusted_mode; struct drm_crtc crtc; struct drm_format format; struct drm_primary primary; struct drm_framebuffer fb; struct drm_connector connector; struct edid edid; int ret; /* Init wrapper structs */ connector.dev = dev; format.cpp[0] = 4; /* 32 BPP */ fb.format = &format; primary.fb = &fb; crtc.dev = dev; crtc.primary = &primary; /* Read EDID and find mode */ ret = ast_select_mode(&connector, &edid); if (ret) { dev_err(dev->pdev, "Failed to select mode.\n"); return ret; } /* Updated edid for set_vbe_mode_info_valid */ edid.x_resolution = edid.mode.ha; edid.y_resolution = edid.mode.va; edid.framebuffer_bits_per_pixel = format.cpp[0] * 8; edid.bytes_per_line = ALIGN_UP(edid.x_resolution * format.cpp[0], 8); /* Updated framebuffer info for ast_crtc_mode_set */ fb.pitches[0] = edid.bytes_per_line; printk(BIOS_DEBUG, "Using framebuffer %dpx x %dpx pitch %d @ %d BPP\n", edid.x_resolution, edid.y_resolution, edid.bytes_per_line, edid.framebuffer_bits_per_pixel); /* Convert EDID to AST DRM mode */ ast_edid_to_drmmode(&edid, &crtc.mode); memcpy(&adjusted_mode, &crtc.mode, sizeof(crtc.mode)); ret = ast_crtc_mode_set(&crtc, &crtc.mode, &adjusted_mode); if (ret) { dev_err(dev->pdev, "Failed to set mode.\n"); return ret; } ast_hide_cursor(&crtc); /* Advertise new mode */ set_vbe_mode_info_valid(&edid, fb.mmio_addr); /* Clear display */ memset((void *)fb.mmio_addr, 0, edid.bytes_per_line * edid.y_resolution); return 0; }