mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-27 21:47:12 +08:00
705 lines
18 KiB
C
705 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright 2020 Loongson Corporation
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* Authors:
|
|
* Sui Jingfeng <suijingfeng@loongson.cn>
|
|
*/
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/string.h>
|
|
#include <linux/module.h>
|
|
#include <linux/console.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of_graph.h>
|
|
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_vblank.h>
|
|
#include <drm/drm_plane.h>
|
|
|
|
#include <drm/drm_fb_helper.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_gem_cma_helper.h>
|
|
#include <drm/drm_fb_cma_helper.h>
|
|
#include <drm/drm_gem_framebuffer_helper.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
#include <drm/drm_fourcc.h>
|
|
|
|
#include "lsdc_drv.h"
|
|
#include "lsdc_irq.h"
|
|
#include "lsdc_regs.h"
|
|
|
|
int lsdc_modeset = -1;
|
|
MODULE_PARM_DESC(modeset, "Enable/disable CMA-based KMS(1 = enabled, 0 = disabled(default))");
|
|
module_param_named(modeset, lsdc_modeset, int, 0644);
|
|
|
|
int lsdc_shadowfb = -1;
|
|
MODULE_PARM_DESC(shadowfb, "Enable/disable dirty update(1 = enabled, 0 = disabled(default))");
|
|
module_param_named(shadowfb, lsdc_shadowfb, int, 0644);
|
|
|
|
int lsdc_ddc0 = -1;
|
|
MODULE_PARM_DESC(ddc0, "Enable/disable ddc0(0 = disabled)");
|
|
module_param_named(ddc0, lsdc_ddc0, int, 0644);
|
|
|
|
int lsdc_ddc1 = -1;
|
|
MODULE_PARM_DESC(ddc1, "Enable/disable ddc1(0 = disabled)");
|
|
module_param_named(ddc1, lsdc_ddc1, int, 0644);
|
|
|
|
int lsdc_dvo0 = -1;
|
|
MODULE_PARM_DESC(dvo0, "Enable/disable dvo0(0 = disabled)");
|
|
module_param_named(dvo0, lsdc_dvo0, int, 0644);
|
|
|
|
int lsdc_dvo1 = -1;
|
|
MODULE_PARM_DESC(dvo1, "Enable/disable dvo1(0 = disabled)");
|
|
module_param_named(dvo1, lsdc_dvo1, int, 0644);
|
|
|
|
int lsdc_dvo0_clone = -1;
|
|
MODULE_PARM_DESC(dvo0_clone, "Enable/disable dvo0 clone mode(1 = enabled, otherwise disabled)");
|
|
module_param_named(dvo0_clone, lsdc_dvo0_clone, int, 0644);
|
|
|
|
int lsdc_dvo1_clone = -1;
|
|
MODULE_PARM_DESC(dvo1_clone, "Enable/disable dvo1 clone mode(1 = enabled, otherwise disabled)");
|
|
module_param_named(dvo1_clone, lsdc_dvo1_clone, int, 0644);
|
|
|
|
int lsdc_gamma = -1;
|
|
MODULE_PARM_DESC(gamma, "enable gamma (-1 = disabled (default), >0 = enabled)");
|
|
module_param_named(gamma, lsdc_gamma, int, 0644);
|
|
|
|
void lsdc_fb_dirty_update_impl(void __iomem *dst,
|
|
void *vaddr,
|
|
struct drm_framebuffer * const fb,
|
|
struct drm_clip_rect * const clip)
|
|
{
|
|
const unsigned int cpp = fb->format->cpp[0];
|
|
const unsigned int offset = clip->y1 * fb->pitches[0] + clip->x1 * cpp;
|
|
const size_t len = (clip->x2 - clip->x1) * cpp;
|
|
const unsigned int lines = clip->y2 - clip->y1;
|
|
unsigned int y;
|
|
|
|
vaddr += offset;
|
|
dst += offset;
|
|
for (y = 0; y < lines; y++) {
|
|
memcpy_toio(dst, vaddr, len);
|
|
vaddr += fb->pitches[0];
|
|
dst += fb->pitches[0];
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* lsdc_fb_dirty() - flushed damage to the display hardware
|
|
*
|
|
* Callback for the dirty fb IOCTL.
|
|
*
|
|
* Userspace can notify the driver via this callback that an area of the
|
|
* framebuffer has changed and should be flushed to the display hardware.
|
|
*/
|
|
static int lsdc_fb_dirty(struct drm_framebuffer *fb,
|
|
struct drm_file *file_priv,
|
|
unsigned int flags,
|
|
unsigned int color,
|
|
struct drm_clip_rect *clips,
|
|
unsigned int num_clips)
|
|
{
|
|
struct lsdc_device *ldev = to_lsdc(fb->dev);
|
|
struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
|
|
struct drm_plane *plane;
|
|
unsigned int i;
|
|
|
|
/* fbdev can flush even when we're not interested */
|
|
drm_for_each_plane(plane, fb->dev) {
|
|
|
|
drm_modeset_lock(&plane->mutex, NULL);
|
|
|
|
if (plane->state->fb == fb) {
|
|
mutex_lock(&ldev->dirty_lock);
|
|
|
|
for (i = 0; i < num_clips; ++i)
|
|
lsdc_fb_dirty_update_impl(ldev->vram,
|
|
cma_obj->vaddr, fb, &clips[i]);
|
|
|
|
mutex_unlock(&ldev->dirty_lock);
|
|
}
|
|
|
|
drm_modeset_unlock(&plane->mutex);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_framebuffer_funcs lsdc_gem_fb_funcs = {
|
|
.destroy = drm_gem_fb_destroy,
|
|
.create_handle = drm_gem_fb_create_handle,
|
|
.dirty = lsdc_fb_dirty,
|
|
};
|
|
|
|
/**
|
|
* lsdc_gem_fb_create() - creates a new framebuffer object
|
|
* The ADDFB2 IOCTL calls into this callback.
|
|
*
|
|
* @dev: DRM device
|
|
* @file: DRM file that holds the GEM handle(s) backing the framebuffer
|
|
* @mode_cmd: Metadata from the userspace framebuffer creation request
|
|
*
|
|
* This function creates a new framebuffer object described by
|
|
* &drm_mode_fb_cmd2. This description includes handles for the buffer(s)
|
|
* backing the framebuffer.
|
|
*
|
|
* If your hardware has special alignment or pitch requirements these
|
|
* should be checked before calling this function. The function does
|
|
* buffer size validation.
|
|
*
|
|
* Returns:
|
|
* Pointer to a &drm_framebuffer on success or an error pointer on failure.
|
|
*/
|
|
static struct drm_framebuffer *lsdc_gem_fb_create(
|
|
struct drm_device *dev,
|
|
struct drm_file *file,
|
|
const struct drm_mode_fb_cmd2 *mode_cmd)
|
|
{
|
|
DRM_DEBUG("%s: fb_id=%u, %ux%u\n",
|
|
__func__, mode_cmd->fb_id,
|
|
mode_cmd->width, mode_cmd->height);
|
|
|
|
/* enable dirty update only when user want it. */
|
|
if (lsdc_shadowfb <= 0)
|
|
return drm_gem_fb_create(dev, file, mode_cmd);
|
|
|
|
return drm_gem_fb_create_with_funcs(dev,
|
|
file,
|
|
mode_cmd,
|
|
&lsdc_gem_fb_funcs);
|
|
}
|
|
|
|
static const struct drm_mode_config_funcs lsdc_mode_funcs = {
|
|
.fb_create = lsdc_gem_fb_create,
|
|
.output_poll_changed = drm_fb_helper_output_poll_changed,
|
|
.atomic_check = drm_atomic_helper_check,
|
|
.atomic_commit = drm_atomic_helper_commit,
|
|
};
|
|
|
|
static int lsdc_create_outputs(struct drm_device *ddev, unsigned int num_crtc)
|
|
{
|
|
struct lsdc_device *ldev = to_lsdc(ddev);
|
|
struct device_node *np;
|
|
int ret;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < num_crtc; ++i) {
|
|
struct lsdc_display_pipe * const dispipe = &ldev->dispipe[i];
|
|
struct drm_encoder *encoder = &dispipe->encoder;
|
|
struct lsdc_connector *lconn;
|
|
|
|
np = of_parse_phandle(ddev->dev->of_node, "output-ports", i);
|
|
if (np) {
|
|
if (!of_device_is_available(np)) {
|
|
DRM_INFO("outputs %u is disabled\n", i);
|
|
of_node_put(np);
|
|
continue;
|
|
}
|
|
|
|
dispipe->hw_clone = of_property_read_bool(np, "clone");
|
|
if (dispipe->hw_clone) {
|
|
DRM_INFO("DVO%u is working at clone mode\n", i);
|
|
of_node_put(np);
|
|
continue;
|
|
};
|
|
|
|
of_node_put(np);
|
|
}
|
|
|
|
if ((i == 0) && (lsdc_dvo0 == 0)) {
|
|
DRM_INFO("DVO0 is disabled by you!\n");
|
|
continue;
|
|
}
|
|
|
|
if ((i == 1) && (lsdc_dvo1 == 0)) {
|
|
DRM_INFO("DVO1 is disabled by you!\n");
|
|
continue;
|
|
}
|
|
|
|
if ((i == 0) && (lsdc_dvo0_clone > 0)) {
|
|
DRM_INFO("DVO0 is working at clone mode\n");
|
|
dispipe->hw_clone = true;
|
|
continue;
|
|
}
|
|
|
|
if ((i == 1) && (lsdc_dvo1_clone > 0)) {
|
|
DRM_INFO("DVO1 is working at clone mode\n");
|
|
dispipe->hw_clone = true;
|
|
continue;
|
|
}
|
|
|
|
lconn = lsdc_create_connector(ddev, i);
|
|
if (PTR_ERR(lconn) == -EPROBE_DEFER)
|
|
return -EPROBE_DEFER;
|
|
else if (lconn == NULL) {
|
|
DRM_ERROR("Init connector-%d failed\n", i);
|
|
return ret;
|
|
}
|
|
|
|
ret = lsdc_encoder_init(ddev, encoder, i);
|
|
if (ret) {
|
|
DRM_ERROR("init encoder failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
drm_connector_attach_encoder(&lconn->base, encoder);
|
|
|
|
dispipe->available = true;
|
|
dispipe->lconn = lconn;
|
|
ldev->num_output++;
|
|
}
|
|
|
|
DRM_INFO("Total %d outputs\n", ldev->num_output);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsdc_modeset_init(struct drm_device *ddev, unsigned int num_crtc)
|
|
{
|
|
struct lsdc_device *ldev = to_lsdc(ddev);
|
|
struct device_node *ports;
|
|
unsigned int i = 0;
|
|
int ret;
|
|
|
|
if (ddev->dev->of_node)
|
|
ldev->has_of_graph = of_graph_is_present(ddev->dev->of_node);
|
|
|
|
if (ldev->has_of_graph) {
|
|
ret = lsdc_attach_bridges(ldev, num_crtc);
|
|
of_node_put(ports);
|
|
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
/* Happens on ls7a1000 platform or when old dtb is in use */
|
|
DRM_INFO("No OF Graph in dtb, fallback to legacy\n");
|
|
|
|
ret = lsdc_create_outputs(ddev, num_crtc);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < num_crtc; i++) {
|
|
struct lsdc_display_pipe * const dispipe = &ldev->dispipe[i];
|
|
struct drm_crtc * const crtc = &dispipe->crtc;
|
|
struct drm_plane * const primary = &dispipe->primary;
|
|
struct drm_plane * const cursor = &dispipe->cursor;
|
|
struct lsdc_pll * const pixpll = &dispipe->pixpll;
|
|
|
|
dispipe->index = i;
|
|
|
|
ret = lsdc_init_pix_pll(ddev, pixpll, i);
|
|
if (IS_ERR(pixpll)) {
|
|
DRM_ERROR("failed to init fb plane.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = lsdc_plane_init(ddev, primary, DRM_PLANE_TYPE_PRIMARY, i);
|
|
if (ret) {
|
|
DRM_ERROR("failed to init fb plane.\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = lsdc_plane_init(ddev, cursor, DRM_PLANE_TYPE_CURSOR, i);
|
|
if (ret) {
|
|
DRM_ERROR("failed to init cursor plane.\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = lsdc_crtc_init(ddev, i, crtc, primary, cursor);
|
|
}
|
|
|
|
/*
|
|
* This functions calls all the crtc's, encoder's and connector's
|
|
* ->reset callback. Drivers can use this in e.g. their driver load
|
|
* or resume code to reset hardware and software state.
|
|
*/
|
|
drm_mode_config_reset(ddev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int lsdc_show_clock(struct seq_file *m, void *arg)
|
|
{
|
|
struct drm_info_node *node = (struct drm_info_node *)m->private;
|
|
struct drm_device *ddev = node->minor->dev;
|
|
struct drm_crtc *crtc;
|
|
|
|
drm_for_each_crtc(crtc, ddev) {
|
|
struct drm_display_mode *adj = &crtc->state->adjusted_mode;
|
|
struct lsdc_display_pipe *dispipe = drm_crtc_to_dispipe(crtc);
|
|
struct lsdc_pll * const pixpll = &dispipe->pixpll;
|
|
struct lsdc_pll_core_values params;
|
|
unsigned int out_khz;
|
|
|
|
out_khz = pixpll->funcs->get_clock_rate(pixpll, ¶ms);
|
|
|
|
seq_printf(m, "Display pipe %u: %dx%d\n",
|
|
dispipe->index, adj->hdisplay, adj->vdisplay);
|
|
|
|
seq_printf(m, "Frequency actually output: %u kHz\n", out_khz);
|
|
seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock);
|
|
seq_printf(m, "diff: %d kHz\n", out_khz - adj->clock);
|
|
|
|
seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n",
|
|
params.div_ref, params.loopc, params.div_out);
|
|
|
|
seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n",
|
|
adj->hsync_start, adj->hsync_end, adj->htotal);
|
|
seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n",
|
|
adj->vsync_start, adj->vsync_end, adj->vtotal);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsdc_show_mm(struct seq_file *m, void *arg)
|
|
{
|
|
struct drm_info_node *node = (struct drm_info_node *) m->private;
|
|
struct drm_device *ddev = node->minor->dev;
|
|
struct drm_printer p = drm_seq_file_printer(m);
|
|
|
|
drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p);
|
|
return 0;
|
|
}
|
|
|
|
#define REGDEF(reg) { __stringify_1(LSDC_##reg##_REG), LSDC_##reg##_REG }
|
|
static const struct {
|
|
const char *name;
|
|
u32 reg_offset;
|
|
} lsdc_regs_array[] = {
|
|
REGDEF(INT),
|
|
REGDEF(CURSOR0_CFG),
|
|
REGDEF(CURSOR0_ADDR_LO),
|
|
REGDEF(CURSOR0_ADDR_HI),
|
|
REGDEF(CURSOR0_POSITION),
|
|
REGDEF(CURSOR0_BG_COLOR),
|
|
REGDEF(CURSOR0_FG_COLOR),
|
|
REGDEF(CRTC0_CFG),
|
|
REGDEF(CRTC0_FB_ADDR0),
|
|
REGDEF(CRTC0_FB_ADDR1),
|
|
REGDEF(CRTC0_FB_HI_ADDR),
|
|
REGDEF(CRTC0_STRIDE),
|
|
REGDEF(CRTC0_FB_ORIGIN),
|
|
REGDEF(CRTC0_HDISPLAY),
|
|
REGDEF(CRTC0_HSYNC),
|
|
REGDEF(CRTC0_VDISPLAY),
|
|
REGDEF(CRTC0_VSYNC),
|
|
REGDEF(CRTC0_GAMMA_INDEX),
|
|
REGDEF(CRTC0_GAMMA_DATA),
|
|
REGDEF(CRTC1_CFG),
|
|
REGDEF(CRTC1_FB_ADDR0),
|
|
REGDEF(CRTC1_FB_ADDR1),
|
|
REGDEF(CRTC1_FB_HI_ADDR),
|
|
REGDEF(CRTC1_STRIDE),
|
|
REGDEF(CRTC1_FB_ORIGIN),
|
|
REGDEF(CRTC1_HDISPLAY),
|
|
REGDEF(CRTC1_HSYNC),
|
|
REGDEF(CRTC1_VDISPLAY),
|
|
REGDEF(CRTC1_VSYNC),
|
|
REGDEF(CRTC1_GAMMA_INDEX),
|
|
REGDEF(CRTC1_GAMMA_DATA),
|
|
};
|
|
|
|
static int lsdc_show_regs(struct seq_file *m, void *arg)
|
|
{
|
|
struct drm_info_node *node = (struct drm_info_node *)m->private;
|
|
struct drm_device *ddev = node->minor->dev;
|
|
struct lsdc_device *ldev = to_lsdc(ddev);
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) {
|
|
u32 offset = lsdc_regs_array[i].reg_offset;
|
|
const char *name = lsdc_regs_array[i].name;
|
|
|
|
seq_printf(m, "%s (0x%04x): 0x%08x\n",
|
|
name, offset,
|
|
readl(ldev->reg_base + offset));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct drm_info_list lsdc_debugfs_list[] = {
|
|
{ "clocks", lsdc_show_clock, 0 },
|
|
{ "mm", lsdc_show_mm, 0, NULL },
|
|
{ "regs", lsdc_show_regs, 0 },
|
|
};
|
|
|
|
static int lsdc_debugfs_init(struct drm_minor *minor)
|
|
{
|
|
drm_debugfs_create_files(lsdc_debugfs_list,
|
|
ARRAY_SIZE(lsdc_debugfs_list),
|
|
minor->debugfs_root,
|
|
minor);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int lsdc_gem_cma_dumb_create(struct drm_file *file,
|
|
struct drm_device *dev,
|
|
struct drm_mode_create_dumb *args)
|
|
{
|
|
unsigned int bytes_per_pixel = (args->bpp + 7) / 8;
|
|
unsigned int pitch = bytes_per_pixel * args->width;
|
|
|
|
/*
|
|
* loongson's display controller require the pitch be a multiple
|
|
* of 256 bytes, which is for optimize dma dma data transfer
|
|
*/
|
|
args->pitch = roundup(pitch, 256);
|
|
|
|
return drm_gem_cma_dumb_create_internal(file, dev, args);
|
|
}
|
|
|
|
DEFINE_DRM_GEM_CMA_FOPS(fops);
|
|
|
|
struct drm_driver lsdc_drm_driver = {
|
|
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
|
|
.lastclose = drm_fb_helper_lastclose,
|
|
.fops = &fops,
|
|
|
|
.dumb_create = lsdc_gem_cma_dumb_create,
|
|
.gem_free_object_unlocked = drm_gem_cma_free_object,
|
|
.gem_vm_ops = &drm_gem_cma_vm_ops,
|
|
|
|
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
|
|
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
|
|
|
|
.gem_prime_import = drm_gem_prime_import,
|
|
.gem_prime_export = drm_gem_prime_export,
|
|
|
|
.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
|
|
.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
|
|
.gem_prime_vmap = drm_gem_cma_prime_vmap,
|
|
.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
|
|
.gem_prime_mmap = drm_gem_cma_prime_mmap,
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
.debugfs_init = lsdc_debugfs_init,
|
|
#endif
|
|
|
|
.name = "lsdc",
|
|
.desc = "DRM Driver for LSDC",
|
|
.date = "20190831",
|
|
.major = 1,
|
|
.minor = 1,
|
|
.patchlevel = 1,
|
|
};
|
|
|
|
static void lsdc_kick_out_firmware_fb(void)
|
|
{
|
|
struct apertures_struct *ap;
|
|
|
|
ap = alloc_apertures(1);
|
|
if (!ap)
|
|
return;
|
|
|
|
/* Since lsdc is a UMA device, the simplefb node may have been
|
|
* located anywhere in memory.
|
|
*/
|
|
ap->ranges[0].base = 0;
|
|
ap->ranges[0].size = ~0;
|
|
|
|
drm_fb_helper_remove_conflicting_framebuffers(ap, "lsdc-drmfb", false);
|
|
kfree(ap);
|
|
}
|
|
|
|
int lsdc_mode_config_init(struct lsdc_device *ldev)
|
|
{
|
|
const struct lsdc_platform_desc *descp = ldev->desc;
|
|
struct drm_device * const ddev = &ldev->ddev;
|
|
uint32_t total_crtcs = 0;
|
|
int ret;
|
|
|
|
ldev->ddc0 = !!lsdc_ddc0;
|
|
ldev->ddc1 = !!lsdc_ddc1;
|
|
|
|
ldev->enable_gamma = lsdc_gamma > 0 ? true : false;
|
|
|
|
ddev->dev_private = ldev;
|
|
|
|
mutex_init(&ldev->dirty_lock);
|
|
spin_lock_init(&ldev->reglock);
|
|
|
|
drm_mode_config_init(ddev);
|
|
|
|
ddev->mode_config.funcs = &lsdc_mode_funcs;
|
|
ddev->mode_config.min_width = 1;
|
|
ddev->mode_config.min_height = 1;
|
|
ddev->mode_config.preferred_depth = 24;
|
|
ddev->mode_config.prefer_shadow = 0;
|
|
|
|
ddev->mode_config.max_width = descp->max_width;
|
|
ddev->mode_config.max_height = descp->max_width;
|
|
|
|
ddev->mode_config.cursor_width = descp->hw_cursor_h;
|
|
ddev->mode_config.cursor_height = descp->hw_cursor_h;
|
|
|
|
total_crtcs = descp->num_of_crtc;
|
|
|
|
ddev->mode_config.allow_fb_modifiers = true;
|
|
|
|
ret = lsdc_modeset_init(ddev, total_crtcs);
|
|
if (ret)
|
|
goto out_mode_config;
|
|
|
|
if ((descp->chip == LSDC_CHIP_2K1000) ||
|
|
(descp->chip == LSDC_CHIP_2K0500) ||
|
|
(descp->chip == LSDC_CHIP_7A1000)) {
|
|
ret = devm_request_threaded_irq(ddev->dev,
|
|
ldev->irq,
|
|
ls2k_irq_handler,
|
|
lsdc_irq_thread,
|
|
IRQF_ONESHOT,
|
|
dev_name(ddev->dev),
|
|
ddev);
|
|
dev_info(ddev->dev, "register irq(%d) handler for ls2k\n",
|
|
ldev->irq);
|
|
} else {
|
|
ret = devm_request_threaded_irq(ddev->dev,
|
|
ldev->irq,
|
|
lsdc_irq_handler,
|
|
lsdc_irq_thread,
|
|
IRQF_ONESHOT,
|
|
dev_name(ddev->dev),
|
|
ddev);
|
|
dev_info(ddev->dev, "register irq(%d) handler for ls7a2000\n",
|
|
ldev->irq);
|
|
}
|
|
|
|
if (ret) {
|
|
dev_err(ddev->dev, "Failed to register lsdc interrupt\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = drm_vblank_init(ddev, total_crtcs);
|
|
if (ret) {
|
|
dev_err(ddev->dev,
|
|
"Fatal error during vblank init: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Allow usage of vblank without having to call drm_irq_install */
|
|
ddev->irq_enabled = 1;
|
|
|
|
/* Initialize and enable output polling */
|
|
drm_kms_helper_poll_init(ddev);
|
|
|
|
lsdc_kick_out_firmware_fb();
|
|
|
|
/*
|
|
* Register the DRM device with the core and the connectors with sysfs.
|
|
*/
|
|
ret = drm_dev_register(ddev, 0);
|
|
if (ret) {
|
|
drm_dev_put(ddev);
|
|
return ret;
|
|
}
|
|
|
|
drm_fbdev_generic_setup(ddev, 32);
|
|
|
|
return 0;
|
|
|
|
out_mode_config:
|
|
drm_mode_config_cleanup(ddev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void lsdc_mode_config_fini(struct lsdc_device *ldev)
|
|
{
|
|
struct drm_device *ddev = &ldev->ddev;
|
|
|
|
/* disable output polling */
|
|
drm_kms_helper_poll_fini(ddev);
|
|
|
|
drm_dev_unregister(ddev);
|
|
|
|
ddev->irq_enabled = 0;
|
|
|
|
devm_free_irq(ddev->dev, ldev->irq, ddev);
|
|
|
|
/* shutdown all CRTC, for driver unloading */
|
|
drm_atomic_helper_shutdown(ddev);
|
|
|
|
drm_mode_config_cleanup(ddev);
|
|
|
|
drm_dev_put(ddev);
|
|
}
|
|
|
|
static int __init lsdc_init(void)
|
|
{
|
|
int ret;
|
|
#if defined(CONFIG_PCI)
|
|
struct pci_dev *pdev = NULL;
|
|
|
|
/* Multi video card workaround */
|
|
while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) {
|
|
if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) {
|
|
DRM_INFO("Discrete graphic card detected, abort.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (vgacon_text_force()) {
|
|
DRM_ERROR("VGACON disables loongson kernel modesetting.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = platform_driver_register(&lsdc_platform_driver);
|
|
|
|
#ifdef CONFIG_DRM_LSDC_PCI_DRIVER
|
|
if (lsdc_modeset == 1)
|
|
return pci_register_driver(&lsdc_pci_driver);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
module_init(lsdc_init);
|
|
|
|
static void __exit lsdc_exit(void)
|
|
{
|
|
platform_driver_unregister(&lsdc_platform_driver);
|
|
|
|
#ifdef CONFIG_DRM_LSDC_PCI_DRIVER
|
|
if (lsdc_modeset == 1)
|
|
return pci_unregister_driver(&lsdc_pci_driver);
|
|
#endif
|
|
}
|
|
module_exit(lsdc_exit);
|
|
|
|
MODULE_AUTHOR("Sui Jingfeng <suijingfeng@loongson.cn>");
|
|
MODULE_DESCRIPTION("DRM Driver for loongson display controller");
|
|
MODULE_LICENSE("GPL v2");
|