mirror_ubuntu-kernels/drivers/gpu/drm/tiny/cirrus.c
Thomas Zimmermann fe57235bc1 drm/cirrus: Move primary-plane format arrays
Move the primary plane's format and modifier arrays within the
source file and adapt naming slightly. No functional changes.

Done in preparation of converting cirrus to regular atomic helpers.

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20230215161517.5113-8-tzimmermann@suse.de
2023-03-13 10:36:15 +01:00

637 lines
16 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2012-2019 Red Hat
*
* This file is subject to the terms and conditions of the GNU General
* Public License version 2. See the file COPYING in the main
* directory of this archive for more details.
*
* Authors: Matthew Garrett
* Dave Airlie
* Gerd Hoffmann
*
* Portions of this code derived from cirrusfb.c:
* drivers/video/cirrusfb.c - driver for Cirrus Logic chipsets
*
* Copyright 1999-2001 Jeff Garzik <jgarzik@pobox.com>
*/
#include <linux/iosys-map.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <video/cirrus.h>
#include <video/vga.h>
#include <drm/drm_aperture.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_connector.h>
#include <drm/drm_damage_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include <drm/drm_fbdev_generic.h>
#include <drm/drm_file.h>
#include <drm/drm_format_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_gem_shmem_helper.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_managed.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_module.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#define DRIVER_NAME "cirrus"
#define DRIVER_DESC "qemu cirrus vga"
#define DRIVER_DATE "2019"
#define DRIVER_MAJOR 2
#define DRIVER_MINOR 0
#define CIRRUS_MAX_PITCH (0x1FF << 3) /* (4096 - 1) & ~111b bytes */
#define CIRRUS_VRAM_SIZE (4 * 1024 * 1024) /* 4 MB */
struct cirrus_device {
struct drm_device dev;
struct drm_simple_display_pipe pipe;
struct drm_connector connector;
const struct drm_format_info *format;
unsigned int pitch;
void __iomem *vram;
void __iomem *mmio;
};
#define to_cirrus(_dev) container_of(_dev, struct cirrus_device, dev)
/* ------------------------------------------------------------------ */
/*
* The meat of this driver. The core passes us a mode and we have to program
* it. The modesetting here is the bare minimum required to satisfy the qemu
* emulation of this hardware, and running this against a real device is
* likely to result in an inadequately programmed mode. We've already had
* the opportunity to modify the mode, so whatever we receive here should
* be something that can be correctly programmed and displayed
*/
#define SEQ_INDEX 4
#define SEQ_DATA 5
static u8 rreg_seq(struct cirrus_device *cirrus, u8 reg)
{
iowrite8(reg, cirrus->mmio + SEQ_INDEX);
return ioread8(cirrus->mmio + SEQ_DATA);
}
static void wreg_seq(struct cirrus_device *cirrus, u8 reg, u8 val)
{
iowrite8(reg, cirrus->mmio + SEQ_INDEX);
iowrite8(val, cirrus->mmio + SEQ_DATA);
}
#define CRT_INDEX 0x14
#define CRT_DATA 0x15
static u8 rreg_crt(struct cirrus_device *cirrus, u8 reg)
{
iowrite8(reg, cirrus->mmio + CRT_INDEX);
return ioread8(cirrus->mmio + CRT_DATA);
}
static void wreg_crt(struct cirrus_device *cirrus, u8 reg, u8 val)
{
iowrite8(reg, cirrus->mmio + CRT_INDEX);
iowrite8(val, cirrus->mmio + CRT_DATA);
}
#define GFX_INDEX 0xe
#define GFX_DATA 0xf
static void wreg_gfx(struct cirrus_device *cirrus, u8 reg, u8 val)
{
iowrite8(reg, cirrus->mmio + GFX_INDEX);
iowrite8(val, cirrus->mmio + GFX_DATA);
}
#define VGA_DAC_MASK 0x06
static void wreg_hdr(struct cirrus_device *cirrus, u8 val)
{
ioread8(cirrus->mmio + VGA_DAC_MASK);
ioread8(cirrus->mmio + VGA_DAC_MASK);
ioread8(cirrus->mmio + VGA_DAC_MASK);
ioread8(cirrus->mmio + VGA_DAC_MASK);
iowrite8(val, cirrus->mmio + VGA_DAC_MASK);
}
static const struct drm_format_info *cirrus_convert_to(struct drm_framebuffer *fb)
{
if (fb->format->format == DRM_FORMAT_XRGB8888 && fb->pitches[0] > CIRRUS_MAX_PITCH) {
if (fb->width * 3 <= CIRRUS_MAX_PITCH)
/* convert from XR24 to RG24 */
return drm_format_info(DRM_FORMAT_RGB888);
else
/* convert from XR24 to RG16 */
return drm_format_info(DRM_FORMAT_RGB565);
}
return NULL;
}
static const struct drm_format_info *cirrus_format(struct drm_framebuffer *fb)
{
const struct drm_format_info *format = cirrus_convert_to(fb);
if (format)
return format;
return fb->format;
}
static int cirrus_pitch(struct drm_framebuffer *fb)
{
const struct drm_format_info *format = cirrus_convert_to(fb);
if (format)
return drm_format_info_min_pitch(format, 0, fb->width);
return fb->pitches[0];
}
static void cirrus_set_start_address(struct cirrus_device *cirrus, u32 offset)
{
u32 addr;
u8 tmp;
addr = offset >> 2;
wreg_crt(cirrus, 0x0c, (u8)((addr >> 8) & 0xff));
wreg_crt(cirrus, 0x0d, (u8)(addr & 0xff));
tmp = rreg_crt(cirrus, 0x1b);
tmp &= 0xf2;
tmp |= (addr >> 16) & 0x01;
tmp |= (addr >> 15) & 0x0c;
wreg_crt(cirrus, 0x1b, tmp);
tmp = rreg_crt(cirrus, 0x1d);
tmp &= 0x7f;
tmp |= (addr >> 12) & 0x80;
wreg_crt(cirrus, 0x1d, tmp);
}
static void cirrus_mode_set(struct cirrus_device *cirrus,
struct drm_display_mode *mode)
{
int hsyncstart, hsyncend, htotal, hdispend;
int vtotal, vdispend;
int tmp;
htotal = mode->htotal / 8;
hsyncend = mode->hsync_end / 8;
hsyncstart = mode->hsync_start / 8;
hdispend = mode->hdisplay / 8;
vtotal = mode->vtotal;
vdispend = mode->vdisplay;
vdispend -= 1;
vtotal -= 2;
htotal -= 5;
hdispend -= 1;
hsyncstart += 1;
hsyncend += 1;
wreg_crt(cirrus, VGA_CRTC_V_SYNC_END, 0x20);
wreg_crt(cirrus, VGA_CRTC_H_TOTAL, htotal);
wreg_crt(cirrus, VGA_CRTC_H_DISP, hdispend);
wreg_crt(cirrus, VGA_CRTC_H_SYNC_START, hsyncstart);
wreg_crt(cirrus, VGA_CRTC_H_SYNC_END, hsyncend);
wreg_crt(cirrus, VGA_CRTC_V_TOTAL, vtotal & 0xff);
wreg_crt(cirrus, VGA_CRTC_V_DISP_END, vdispend & 0xff);
tmp = 0x40;
if ((vdispend + 1) & 512)
tmp |= 0x20;
wreg_crt(cirrus, VGA_CRTC_MAX_SCAN, tmp);
/*
* Overflow bits for values that don't fit in the standard registers
*/
tmp = 0x10;
if (vtotal & 0x100)
tmp |= 0x01;
if (vdispend & 0x100)
tmp |= 0x02;
if ((vdispend + 1) & 0x100)
tmp |= 0x08;
if (vtotal & 0x200)
tmp |= 0x20;
if (vdispend & 0x200)
tmp |= 0x40;
wreg_crt(cirrus, VGA_CRTC_OVERFLOW, tmp);
tmp = 0;
/* More overflow bits */
if ((htotal + 5) & 0x40)
tmp |= 0x10;
if ((htotal + 5) & 0x80)
tmp |= 0x20;
if (vtotal & 0x100)
tmp |= 0x40;
if (vtotal & 0x200)
tmp |= 0x80;
wreg_crt(cirrus, CL_CRT1A, tmp);
/* Disable Hercules/CGA compatibility */
wreg_crt(cirrus, VGA_CRTC_MODE, 0x03);
}
static void cirrus_format_set(struct cirrus_device *cirrus,
struct drm_framebuffer *fb)
{
u8 sr07, hdr;
sr07 = rreg_seq(cirrus, 0x07);
sr07 &= 0xe0;
cirrus->format = cirrus_format(fb);
switch (cirrus->format->format) {
case DRM_FORMAT_C8:
sr07 |= 0x11;
hdr = 0x00;
break;
case DRM_FORMAT_RGB565:
sr07 |= 0x17;
hdr = 0xc1;
break;
case DRM_FORMAT_RGB888:
sr07 |= 0x15;
hdr = 0xc5;
break;
case DRM_FORMAT_XRGB8888:
sr07 |= 0x19;
hdr = 0xc5;
break;
default:
return;
}
wreg_seq(cirrus, 0x7, sr07);
/* Enable high-colour modes */
wreg_gfx(cirrus, VGA_GFX_MODE, 0x40);
/* And set graphics mode */
wreg_gfx(cirrus, VGA_GFX_MISC, 0x01);
wreg_hdr(cirrus, hdr);
}
static void cirrus_pitch_set(struct cirrus_device *cirrus,
struct drm_framebuffer *fb)
{
u8 cr13, cr1b;
/* Program the pitch */
cirrus->pitch = cirrus_pitch(fb);
cr13 = cirrus->pitch / 8;
wreg_crt(cirrus, VGA_CRTC_OFFSET, cr13);
/* Enable extended blanking and pitch bits, and enable full memory */
cr1b = 0x22;
cr1b |= (cirrus->pitch >> 7) & 0x10;
cr1b |= (cirrus->pitch >> 6) & 0x40;
wreg_crt(cirrus, 0x1b, cr1b);
cirrus_set_start_address(cirrus, 0);
}
static int cirrus_fb_blit_rect(struct drm_framebuffer *fb,
const struct iosys_map *vmap,
struct drm_rect *rect)
{
struct cirrus_device *cirrus = to_cirrus(fb->dev);
struct iosys_map dst;
iosys_map_set_vaddr_iomem(&dst, cirrus->vram);
iosys_map_incr(&dst, drm_fb_clip_offset(cirrus->pitch, fb->format, rect));
drm_fb_blit(&dst, &cirrus->pitch, cirrus->format->format, vmap, fb, rect);
return 0;
}
static int cirrus_fb_blit_fullscreen(struct drm_framebuffer *fb,
const struct iosys_map *map)
{
struct drm_rect fullscreen = {
.x1 = 0,
.x2 = fb->width,
.y1 = 0,
.y2 = fb->height,
};
return cirrus_fb_blit_rect(fb, map, &fullscreen);
}
static int cirrus_check_size(int width, int height,
struct drm_framebuffer *fb)
{
int pitch = width * 2;
if (fb)
pitch = cirrus_pitch(fb);
if (pitch > CIRRUS_MAX_PITCH)
return -EINVAL;
if (pitch * height > CIRRUS_VRAM_SIZE)
return -EINVAL;
return 0;
}
/* ------------------------------------------------------------------ */
/* cirrus display pipe */
static const uint32_t cirrus_primary_plane_formats[] = {
DRM_FORMAT_RGB565,
DRM_FORMAT_RGB888,
DRM_FORMAT_XRGB8888,
};
static const uint64_t cirrus_primary_plane_format_modifiers[] = {
DRM_FORMAT_MOD_LINEAR,
DRM_FORMAT_MOD_INVALID
};
static enum drm_mode_status cirrus_pipe_mode_valid(struct drm_simple_display_pipe *pipe,
const struct drm_display_mode *mode)
{
if (cirrus_check_size(mode->hdisplay, mode->vdisplay, NULL) < 0)
return MODE_BAD;
return MODE_OK;
}
static int cirrus_pipe_check(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *plane_state,
struct drm_crtc_state *crtc_state)
{
struct drm_framebuffer *fb = plane_state->fb;
if (!fb)
return 0;
return cirrus_check_size(fb->width, fb->height, fb);
}
static void cirrus_pipe_enable(struct drm_simple_display_pipe *pipe,
struct drm_crtc_state *crtc_state,
struct drm_plane_state *plane_state)
{
struct cirrus_device *cirrus = to_cirrus(pipe->crtc.dev);
struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state);
int idx;
if (!drm_dev_enter(&cirrus->dev, &idx))
return;
cirrus_mode_set(cirrus, &crtc_state->mode);
cirrus_format_set(cirrus, plane_state->fb);
cirrus_pitch_set(cirrus, plane_state->fb);
cirrus_fb_blit_fullscreen(plane_state->fb, &shadow_plane_state->data[0]);
/* Unblank (needed on S3 resume, vgabios doesn't do it then) */
outb(0x20, 0x3c0);
drm_dev_exit(idx);
}
static void cirrus_pipe_update(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *old_state)
{
struct cirrus_device *cirrus = to_cirrus(pipe->crtc.dev);
struct drm_plane_state *state = pipe->plane.state;
struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state);
struct drm_rect rect;
int idx;
if (!drm_dev_enter(&cirrus->dev, &idx))
return;
if (state->fb) {
if (cirrus->format != cirrus_format(state->fb))
cirrus_format_set(cirrus, state->fb);
if (cirrus->pitch != cirrus_pitch(state->fb))
cirrus_pitch_set(cirrus, state->fb);
}
if (drm_atomic_helper_damage_merged(old_state, state, &rect))
cirrus_fb_blit_rect(state->fb, &shadow_plane_state->data[0], &rect);
drm_dev_exit(idx);
}
static const struct drm_simple_display_pipe_funcs cirrus_pipe_funcs = {
.mode_valid = cirrus_pipe_mode_valid,
.check = cirrus_pipe_check,
.enable = cirrus_pipe_enable,
.update = cirrus_pipe_update,
DRM_GEM_SIMPLE_DISPLAY_PIPE_SHADOW_PLANE_FUNCS,
};
static int cirrus_connector_helper_get_modes(struct drm_connector *connector)
{
int count;
count = drm_add_modes_noedid(connector,
connector->dev->mode_config.max_width,
connector->dev->mode_config.max_height);
drm_set_preferred_mode(connector, 1024, 768);
return count;
}
static const struct drm_connector_helper_funcs cirrus_connector_helper_funcs = {
.get_modes = cirrus_connector_helper_get_modes,
};
static const struct drm_connector_funcs cirrus_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static int cirrus_pipe_init(struct cirrus_device *cirrus)
{
struct drm_device *dev = &cirrus->dev;
struct drm_connector *connector;
int ret;
connector = &cirrus->connector;
ret = drm_connector_init(&cirrus->dev, connector, &cirrus_connector_funcs,
DRM_MODE_CONNECTOR_VGA);
if (ret)
return ret;
drm_connector_helper_add(connector, &cirrus_connector_helper_funcs);
return drm_simple_display_pipe_init(dev,
&cirrus->pipe,
&cirrus_pipe_funcs,
cirrus_primary_plane_formats,
ARRAY_SIZE(cirrus_primary_plane_formats),
cirrus_primary_plane_format_modifiers,
connector);
}
/* ------------------------------------------------------------------ */
/* cirrus framebuffers & mode config */
static struct drm_framebuffer*
cirrus_fb_create(struct drm_device *dev, struct drm_file *file_priv,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
if (mode_cmd->pixel_format != DRM_FORMAT_RGB565 &&
mode_cmd->pixel_format != DRM_FORMAT_RGB888 &&
mode_cmd->pixel_format != DRM_FORMAT_XRGB8888)
return ERR_PTR(-EINVAL);
if (cirrus_check_size(mode_cmd->width, mode_cmd->height, NULL) < 0)
return ERR_PTR(-EINVAL);
return drm_gem_fb_create_with_dirty(dev, file_priv, mode_cmd);
}
static const struct drm_mode_config_funcs cirrus_mode_config_funcs = {
.fb_create = cirrus_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static int cirrus_mode_config_init(struct cirrus_device *cirrus)
{
struct drm_device *dev = &cirrus->dev;
int ret;
ret = drmm_mode_config_init(dev);
if (ret)
return ret;
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
dev->mode_config.max_width = CIRRUS_MAX_PITCH / 2;
dev->mode_config.max_height = 1024;
dev->mode_config.preferred_depth = 16;
dev->mode_config.prefer_shadow = 0;
dev->mode_config.funcs = &cirrus_mode_config_funcs;
return 0;
}
/* ------------------------------------------------------------------ */
DEFINE_DRM_GEM_FOPS(cirrus_fops);
static const struct drm_driver cirrus_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.fops = &cirrus_fops,
DRM_GEM_SHMEM_DRIVER_OPS,
};
static int cirrus_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct drm_device *dev;
struct cirrus_device *cirrus;
int ret;
ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &cirrus_driver);
if (ret)
return ret;
ret = pcim_enable_device(pdev);
if (ret)
return ret;
ret = pci_request_regions(pdev, DRIVER_NAME);
if (ret)
return ret;
ret = -ENOMEM;
cirrus = devm_drm_dev_alloc(&pdev->dev, &cirrus_driver,
struct cirrus_device, dev);
if (IS_ERR(cirrus))
return PTR_ERR(cirrus);
dev = &cirrus->dev;
cirrus->vram = devm_ioremap(&pdev->dev, pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
if (cirrus->vram == NULL)
return -ENOMEM;
cirrus->mmio = devm_ioremap(&pdev->dev, pci_resource_start(pdev, 1),
pci_resource_len(pdev, 1));
if (cirrus->mmio == NULL)
return -ENOMEM;
ret = cirrus_mode_config_init(cirrus);
if (ret)
return ret;
ret = cirrus_pipe_init(cirrus);
if (ret < 0)
return ret;
drm_mode_config_reset(dev);
pci_set_drvdata(pdev, dev);
ret = drm_dev_register(dev, 0);
if (ret)
return ret;
drm_fbdev_generic_setup(dev, 16);
return 0;
}
static void cirrus_pci_remove(struct pci_dev *pdev)
{
struct drm_device *dev = pci_get_drvdata(pdev);
drm_dev_unplug(dev);
drm_atomic_helper_shutdown(dev);
}
static const struct pci_device_id pciidlist[] = {
{
.vendor = PCI_VENDOR_ID_CIRRUS,
.device = PCI_DEVICE_ID_CIRRUS_5446,
/* only bind to the cirrus chip in qemu */
.subvendor = PCI_SUBVENDOR_ID_REDHAT_QUMRANET,
.subdevice = PCI_SUBDEVICE_ID_QEMU,
}, {
.vendor = PCI_VENDOR_ID_CIRRUS,
.device = PCI_DEVICE_ID_CIRRUS_5446,
.subvendor = PCI_VENDOR_ID_XEN,
.subdevice = 0x0001,
},
{ /* end if list */ }
};
static struct pci_driver cirrus_pci_driver = {
.name = DRIVER_NAME,
.id_table = pciidlist,
.probe = cirrus_pci_probe,
.remove = cirrus_pci_remove,
};
drm_module_pci_driver(cirrus_pci_driver)
MODULE_DEVICE_TABLE(pci, pciidlist);
MODULE_LICENSE("GPL");