diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 518fd948c288adb5a7a95458eb5c203c84438d72..967d3de373541c15ea4eb6d85ac36b2eb40eb465 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -313,6 +313,8 @@ source "drivers/gpu/drm/udl/Kconfig" source "drivers/gpu/drm/ast/Kconfig" +source "drivers/gpu/drm/mwv207/Kconfig" + source "drivers/gpu/drm/mgag200/Kconfig" source "drivers/gpu/drm/armada/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index d78dc598c276590257944870d45dee4ca9282d40..2af694fc2591d73a705c05f357b3e4567d7afe43 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -86,6 +86,7 @@ obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/ obj-$(CONFIG_DRM_GMA500) += gma500/ obj-$(CONFIG_DRM_UDL) += udl/ obj-$(CONFIG_DRM_AST) += ast/ +obj-$(CONFIG_DRM_MWV207) += mwv207/ obj-$(CONFIG_DRM_ARMADA) += armada/ obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/ obj-y += rcar-du/ diff --git a/drivers/gpu/drm/mwv207/Kconfig b/drivers/gpu/drm/mwv207/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..f09174add415f3494b21ed674b4ff1cf1b338b04 --- /dev/null +++ b/drivers/gpu/drm/mwv207/Kconfig @@ -0,0 +1,13 @@ +config DRM_MWV207 + tristate "MWV207 chip" + depends on DRM + select DRM_KMS_HELPER + select SND_PCM + select DRM_VRAM_HELPER + select DRM_SCHED + select DRM_TTM + select DRM_TTM_HELPER + help + Choose this option if you have a Jingjia graphics card. + If M is selected, the module will be called mwv207 (JM9100). + diff --git a/drivers/gpu/drm/mwv207/Makefile b/drivers/gpu/drm/mwv207/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..5ead6cb5db8eb129a181a2d796e0aa51b74c09e3 --- /dev/null +++ b/drivers/gpu/drm/mwv207/Makefile @@ -0,0 +1,33 @@ + +mwv207-y := mwv207_drv.o \ + mwv207_irq.o \ + mwv207_db.o \ + mwv207_ctx.o \ + mwv207_submit.o \ + mwv207_sched.o \ + mwv207_pipe_codec_common.o \ + mwv207_devfreq.o \ + mwv207_pipe_2d.o \ + mwv207_pipe_3d.o \ + mwv207_pipe_dec.o \ + mwv207_pipe_enc.o \ + mwv207_pipe_dma.o \ + mwv207_gem.o \ + mwv207_bo.o \ + mwv207_ttm.o \ + mwv207_vcmd.o \ + mwv207_vbios.o \ + dc/mwv207_kms.o \ + dc/mwv207_fb.o \ + dc/mwv207_va.o \ + dc/mwv207_edp.o \ + dc/mwv207_hdmi.o \ + dc/mwv207_vga.o \ + dc/mwv207_dvo.o \ + dc/mwv207_vi.o \ + dc/mwv207_i2c.o \ + selftest/selftest.o \ + +obj-$(CONFIG_DRM_MWV207) := mwv207.o + +ccflags-y := -I$(src) diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_dvo.c b/drivers/gpu/drm/mwv207/dc/mwv207_dvo.c new file mode 100644 index 0000000000000000000000000000000000000000..f0c0eda04a31b7666038868a4315a29b95122df3 --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_dvo.c @@ -0,0 +1,180 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include "mwv207_vi.h" + +static void mwv207_dvo_switch(struct mwv207_output *output, bool on) +{ + mwv207_output_modify(output, 0x200, + 0x1 << 0, (on ? 1 : 0) << 0); +} + +static void mwv207_dvo_config(struct mwv207_output *output) +{ + struct drm_display_mode *mode = &output->cur_crtc->state->adjusted_mode; + int hpol, vpol; + + hpol = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : 1; + vpol = (mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : 1; + + mwv207_output_modify(output, 0x200, 0x1 << 9, hpol << 9); + mwv207_output_modify(output, 0x200, 0x1 << 8, vpol << 8); + +} + +static void mwv207_dvo_select_crtc(struct mwv207_output *output) +{ + + mwv207_output_modify(output, 0x200, 0x7 << 16, + drm_crtc_index(output->cur_crtc) << 16); + + jdev_modify(output->jdev, 0x9b003c, 0xf, + drm_crtc_index(output->cur_crtc)); +} +static enum drm_mode_status mwv207_dvo_mode_valid(struct mwv207_output *output, + const struct drm_display_mode *mode) +{ + + if (mode->clock > 162000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static enum drm_mode_status mwv207_dvo_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return mwv207_dvo_mode_valid(connector_to_output(connector), mode); +} + +static int mwv207_dvo_detect_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct mwv207_output *output = connector_to_output(connector); + + if (mwv207_i2c_probe(output->ddc)) + return connector_status_connected; + else + return connector_status_disconnected; +} + +static void mwv207_dvo_destroy(struct drm_connector *conn) +{ + drm_connector_unregister(conn); + drm_connector_cleanup(conn); +} + +static const struct drm_connector_helper_funcs mwv207_dvo_connector_helper_funcs = { + .get_modes = mwv207_output_get_modes, + .mode_valid = mwv207_dvo_connector_mode_valid, + .detect_ctx = mwv207_dvo_detect_ctx +}; + +static const struct drm_connector_funcs mwv207_dvo_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .destroy = mwv207_dvo_destroy, + .late_register = mwv207_output_late_register, + .early_unregister = mwv207_output_early_unregister, +}; + +static enum drm_mode_status mwv207_dvo_encoder_mode_valid(struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + struct mwv207_output *output = encoder_to_output(encoder); + + return mwv207_dvo_mode_valid(output, mode); +} + +static int mwv207_dvo_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + return 0; +} + +static void mwv207_dvo_encoder_enable(struct drm_encoder *encoder) +{ + struct mwv207_output *output = encoder_to_output(encoder); + + mwv207_dvo_select_crtc(output); + + mwv207_dvo_config(output); + + mwv207_dvo_switch(output, true); +} + +static void mwv207_dvo_encoder_disable(struct drm_encoder *encoder) +{ + struct mwv207_output *output = encoder_to_output(encoder); + + mwv207_dvo_switch(output, false); +} + +static void mwv207_dvo_encoder_reset(struct drm_encoder *encoder) +{ + mwv207_dvo_encoder_disable(encoder); +} + +static const struct drm_encoder_funcs mwv207_dvo_encoder_funcs = { + .destroy = drm_encoder_cleanup, + .reset = mwv207_dvo_encoder_reset, +}; + +static const struct drm_encoder_helper_funcs mwv207_dvo_encoder_helper_funcs = { + .mode_valid = mwv207_dvo_encoder_mode_valid, + .atomic_check = mwv207_dvo_encoder_atomic_check, + .enable = mwv207_dvo_encoder_enable, + .disable = mwv207_dvo_encoder_disable, +}; + +int mwv207_dvo_init(struct mwv207_device *jdev) +{ + struct mwv207_output *output; + int ret; + + output = devm_kzalloc(jdev->dev, sizeof(*output), GFP_KERNEL); + if (!output) + return -ENOMEM; + + output->jdev = jdev; + output->idx = 0; + output->mmio = jdev->mmio + 0x9a0000; + output->i2c_chan = 5; + + output->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + ret = drm_connector_init(&jdev->base, &output->connector, + &mwv207_dvo_connector_funcs, DRM_MODE_CONNECTOR_DVII); + if (ret) + return ret; + drm_connector_helper_add(&output->connector, &mwv207_dvo_connector_helper_funcs); + + output->encoder.possible_crtcs = (1 << jdev->base.mode_config.num_crtc) - 1; + ret = drm_encoder_init(&jdev->base, &output->encoder, + &mwv207_dvo_encoder_funcs, DRM_MODE_ENCODER_DAC, + "dvo-%d", output->idx); + if (ret) + return ret; + drm_encoder_helper_add(&output->encoder, &mwv207_dvo_encoder_helper_funcs); + + ret = drm_connector_attach_encoder(&output->connector, &output->encoder); + if (ret) + return ret; + + return drm_connector_register(&output->connector); +} diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_edp.c b/drivers/gpu/drm/mwv207/dc/mwv207_edp.c new file mode 100644 index 0000000000000000000000000000000000000000..790b186a1521f4cfe8d67c31ab5bc6fb3f86b82a --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_edp.c @@ -0,0 +1,22 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include "mwv207_vi.h" + +int mwv207_edp_init(struct mwv207_device *jdev) +{ + pr_info("%s TBD", __func__); + return 0; +} diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_fb.c b/drivers/gpu/drm/mwv207/dc/mwv207_fb.c new file mode 100644 index 0000000000000000000000000000000000000000..96147148a1e1d7850c840cbebc42d14ff309c9a4 --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_fb.c @@ -0,0 +1,181 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include +#include "mwv207.h" +#include "mwv207_bo.h" +#include "mwv207_drm.h" +#include "mwv207_gem.h" +#include "mwv207_kms.h" + +static struct fb_ops mwv207_fb_ops = { + .owner = THIS_MODULE, + DRM_FB_HELPER_DEFAULT_OPS, + .fb_fillrect = drm_fb_helper_cfb_fillrect, + .fb_copyarea = drm_fb_helper_cfb_copyarea, + .fb_imageblit = drm_fb_helper_cfb_imageblit, +}; + +static int mwv207_drm_fb_helper_init(struct drm_device *dev, + struct drm_fb_helper *fb_helper, + unsigned int crtc_count, + unsigned int max_conn_count) +{ + return drm_fb_helper_init(dev, fb_helper); +} + +static int mwv207_fb_create(struct drm_fb_helper *fb_helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct drm_mode_fb_cmd2 mode_cmd; + struct drm_framebuffer *fb; + struct mwv207_device *jdev; + struct mwv207_bo *jbo; + struct fb_info *info; + u32 bytes; + void *logical; + int ret; + + jdev = fb_helper->dev->dev_private; + memset(&mode_cmd, 0, sizeof(mode_cmd)); + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + if (sizes->surface_bpp == 24) + sizes->surface_bpp = 32; + + mode_cmd.pitches[0] = mode_cmd.width * ((sizes->surface_bpp + 7)/8); + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + info = drm_fb_helper_alloc_fbi(fb_helper); + if (IS_ERR(info)) + return PTR_ERR(info); + + bytes = mode_cmd.pitches[0] * mode_cmd.height; + bytes = ALIGN(bytes, PAGE_SIZE); + ret = mwv207_bo_create(jdev, bytes, 0x10000, ttm_bo_type_kernel, + 0x2, (1<<0), &jbo); + if (ret) + return ret; + + fb = kzalloc(sizeof(*fb), GFP_KERNEL); + if (fb == NULL) { + ret = -ENOMEM; + goto free_bo; + } + + ret = mwv207_framebuffer_init(jdev, fb, &mode_cmd, mwv207_gem_from_bo(jbo)); + if (ret) + goto free_fb; + + ret = mwv207_bo_reserve(jbo, true); + if (ret) + goto free_fb; + ret = mwv207_bo_pin_reserved(jbo, 0x2); + if (ret) + goto unreserve_bo; + + ret = mwv207_bo_kmap_reserved(jbo, &logical); + if (ret) + goto unpin_bo; + + memset(logical, 0x0, bytes); + + fb_helper->fb = fb; + info->skip_vt_switch = true; + info->fbops = &mwv207_fb_ops; + info->screen_size = fb->height * fb->pitches[0]; + info->fix.smem_len = info->screen_size; + info->screen_base = logical; + + drm_fb_helper_fill_info(info, fb_helper, sizes); + + mwv207_bo_unreserve(jbo); + return 0; + +unpin_bo: + mwv207_bo_unpin(jbo); +unreserve_bo: + mwv207_bo_unreserve(jbo); +free_fb: + kfree(fb); +free_bo: + mwv207_bo_unref(jbo); + return ret; +} + +static const struct drm_fb_helper_funcs mwv207_fb_helper_funcs = { + .fb_probe = mwv207_fb_create, +}; + +int mwv207_fbdev_init(struct mwv207_device *jdev) +{ + struct drm_fb_helper *fb_helper; + int preferred_bpp = 32; + int ret; + + fb_helper = devm_kzalloc(jdev->dev, sizeof(*fb_helper), GFP_KERNEL); + if (!fb_helper) + return -ENOMEM; + + drm_fb_helper_prepare(&jdev->base, fb_helper, &mwv207_fb_helper_funcs); + ret = mwv207_drm_fb_helper_init(&jdev->base, fb_helper, + jdev->base.num_crtcs, jdev->base.num_crtcs); + if (ret) { + DRM_ERROR("Failed to initialize fbdev helper\n"); + return ret; + } + + ret = drm_fb_helper_initial_config(fb_helper, preferred_bpp); + if (ret) { + DRM_ERROR("Failed to set fbdev configuration\n"); + goto err; + } + + jdev->fb_helper = fb_helper; + return 0; +err: + drm_fb_helper_fini(fb_helper); + return ret; +} + +void mwv207_fbdev_fini(struct mwv207_device *jdev) +{ + struct drm_framebuffer *fb; + struct mwv207_bo *jbo; + int ret; + + drm_fb_helper_unregister_fbi(jdev->fb_helper); + + fb = jdev->fb_helper ? jdev->fb_helper->fb : NULL; + if (fb == NULL) + return; + + jbo = mwv207_bo_from_gem(fb->obj[0]); + ret = mwv207_bo_reserve(jbo, true); + if (ret == 0) { + mwv207_bo_kunmap_reserved(jbo); + mwv207_bo_unpin_reserved(jbo); + mwv207_bo_unreserve(jbo); + mwv207_gem_object_put(fb->obj[0]); + drm_framebuffer_cleanup(fb); + } + + drm_fb_helper_fini(jdev->fb_helper); +} diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_hdmi.c b/drivers/gpu/drm/mwv207/dc/mwv207_hdmi.c new file mode 100644 index 0000000000000000000000000000000000000000..49e83a72bd5afff8dd3b83d8e1305e7463023e52 --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_hdmi.c @@ -0,0 +1,1086 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include "mwv207_vi.h" +#include "mwv207_vbios.h" + +/* hdmi specific, accessed by mwv207_hdmi_read/write */ +#define MWV207_HDMI_BASE(idx) (0x1200000 + 0x40000 * (idx)) + +/* vi global, accessed by mwv207_output_read/write */ +#define MWV207_HDMI_CTRL(idx) (0x400 + 0x100 * (idx)) +#define MWV207_HDMI_PHY(idx) (0x6000 + 0x800 * (idx)) + +#define connector_to_hdmi(conn) container_of(conn, struct mwv207_hdmi, base.connector) +#define encoder_to_hdmi(encoder) container_of(encoder, struct mwv207_hdmi, base.encoder) + +struct mwv207_hdmi_phy_data { + u32 min_freq_khz; + u32 max_freq_khz; + u32 bpp; + u8 config[47]; + u8 padding; +} __packed; + +static const struct mwv207_hdmi_phy_data +default_phy_data[19] = { + { 24000, 47999, 8, + { 0xD2, 0x78, 0xB0, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0x01, 0x25, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x8B, 0xF0, + 0x22, 0x82, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 28000, 95999, 8, + { 0xD2, 0x3C, 0x50, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0x01, 0x25, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x85, 0xF0, + 0x22, 0x82, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 96000, 143999, 8, + { 0xD4, 0x50, 0x30, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0x01, 0x25, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x83, 0xF0, + 0x22, 0x82, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 144000, 296999, 8, + { 0xD8, 0x50, 0x10, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0x01, 0x25, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x81, 0xF0, + 0x22, 0x82, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 297000, 340000, 8, + { 0xDC, 0x3C, 0x00, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0x01, 0x25, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x80, 0xF0, + 0x22, 0x82, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 340001, 595000, 8, + { 0xDC, 0x3C, 0x00, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0x01, 0x25, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x80, 0xC0, + 0xF4, 0x69, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, + 0xFF, 0x00, 0x00}}, + { 24000, 27999, 10, + { 0xD2, 0x7D, 0x90, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xCD, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x99, 0xC0, + 0x3A, 0x74, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 28000, 55999, 10, + { 0xD4, 0xC8, 0x70, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xCD, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x97, 0xC0, + 0x3A, 0x74, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 56000, 111999, 10, + { 0xD4, 0x64, 0x30, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xCD, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x93, 0xC0, + 0x3A, 0x74, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 112000, 223999, 10, + { 0xD8, 0x64, 0x10, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xCD, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x91, 0xC0, + 0x3A, 0x74, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 224000, 272000, 10, + { 0xDC, 0x4B, 0x00, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xCD, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x90, 0xC0, + 0x3A, 0x74, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 272001, 340000, 10, + { 0xDC, 0x4B, 0x00, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xCD, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x90, 0xC0, + 0x3D, 0xFA, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 340001, 475200, 10, + { 0xDC, 0x4B, 0x00, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xCD, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0x90, 0xC0, + 0x3D, 0xFA, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, + 0xFF, 0x00, 0x00}}, + { 24000, 47999, 12, + { 0xD1, 0x3C, 0x70, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xAB, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0xA7, 0xC0, + 0x3A, 0x74, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 48000, 95999, 12, + { 0xD2, 0x3C, 0x30, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xAB, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0xA3, 0xC0, + 0x3A, 0x74, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 96000, 197999, 12, + { 0xD4, 0x3C, 0x10, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xAB, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0xA1, 0xC0, + 0x3A, 0x74, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 198000, 226999, 12, + { 0xD8, 0x3C, 0x00, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xAB, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0xA0, 0xC0, + 0x3A, 0x74, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 227000, 340000, 12, + { 0xD8, 0x3C, 0x00, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xAB, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0xA0, 0xC0, + 0x3D, 0xFA, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x0F, 0x3E, + 0xF8, 0x00, 0x00}}, + { 340001, 396000, 12, + { 0xD8, 0x3C, 0x00, 0x01, 0x00, 0x88, 0x02, 0x4F, 0x30, 0x33, 0x65, + 0x00, 0xAB, 0x24, 0x80, 0x6C, 0xF2, 0x67, 0x00, 0x10, 0xA0, 0xC0, + 0x3D, 0xFA, 0x8F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, + 0xFF, 0x00, 0x00}}, +}; + +struct mwv207_hdmi { + struct mwv207_output base; + + void __iomem *mmio; + bool sink_is_hdmi; + bool sink_has_audio; + int vic; + + int irq; + + u8 mc_clkdis; + bool rgb_limited_range; + struct mwv207_hdmi_phy_data phy_data[19]; +}; + +static const u16 csc_coeff_default[3][4] = { + { 0x2000, 0x0000, 0x0000, 0x0000 }, + { 0x0000, 0x2000, 0x0000, 0x0000 }, + { 0x0000, 0x0000, 0x2000, 0x0000 } +}; + +static const u16 csc_coeff_rgb_full_to_rgb_limited[3][4] = { + { 0x1b7c, 0x0000, 0x0000, 0x0020 }, + { 0x0000, 0x1b7c, 0x0000, 0x0020 }, + { 0x0000, 0x0000, 0x1b7c, 0x0020 } +}; + +static inline u8 mwv207_hdmi_readb(struct mwv207_hdmi *hdmi, u32 reg) +{ + return readl(hdmi->mmio + reg * 4); +} + +static inline void mwv207_hdmi_writeb(struct mwv207_hdmi *hdmi, u8 value, u32 reg) +{ + writel_relaxed(value, hdmi->mmio + reg * 4); +} + +static inline void mwv207_hdmi_modb(struct mwv207_hdmi *hdmi, u8 value, u8 mask, u32 reg) +{ + u8 rvalue = mwv207_hdmi_readb(hdmi, reg); + + rvalue = (rvalue & ~mask) | (value & mask); + mwv207_hdmi_writeb(hdmi, rvalue, reg); +} + +static void mwv207_hdmi_mask_writeb(struct mwv207_hdmi *hdmi, u8 data, unsigned int reg, + u8 shift, u8 mask) +{ + mwv207_hdmi_modb(hdmi, data << shift, mask, reg); +} + +static int mwv207_hdmi_phy_data_from_cfg(struct mwv207_hdmi *hdmi) +{ + const struct mwv207_vdat *vdat; + int i; + + vdat = mwv207_vbios_vdat(hdmi->base.jdev, 0xfff0 + hdmi->base.idx); + if (!vdat) + return -ENOENT; + if (vdat->len != sizeof(hdmi->phy_data)) + return -EINVAL; + memcpy(hdmi->phy_data, vdat->dat, vdat->len); + for (i = 0; i < 19; ++i) { + hdmi->phy_data[i].min_freq_khz = le32_to_cpu(hdmi->phy_data[i].min_freq_khz); + hdmi->phy_data[i].max_freq_khz = le32_to_cpu(hdmi->phy_data[i].max_freq_khz); + hdmi->phy_data[i].bpp = le32_to_cpu(hdmi->phy_data[i].bpp); + } + return 0; +} + +static void mwv207_hdmi_phy_data_init(struct mwv207_hdmi *hdmi) +{ + if (!mwv207_hdmi_phy_data_from_cfg(hdmi)) + return; + + DRM_INFO("mwv207: hdmi%d use default phy data", hdmi->base.idx); + + BUILD_BUG_ON(sizeof(hdmi->phy_data) != sizeof(default_phy_data)); + + memcpy(hdmi->phy_data, default_phy_data, sizeof(hdmi->phy_data)); +} + +static void mwv207_hdmi_phy_configure_data(struct mwv207_hdmi *hdmi, int kfreq, int bpp) +{ + struct mwv207_output *output = &hdmi->base; + u32 regbase = MWV207_HDMI_PHY(output->idx); + int i, cfg, offset; + + for (i = 0; i < 19; ++i) { + if (kfreq >= hdmi->phy_data[i].min_freq_khz + && kfreq <= hdmi->phy_data[i].max_freq_khz + && bpp == hdmi->phy_data[i].bpp) + break; + } + if (i >= 19) { + pr_warn("mwv207: no matching hdmi phydata found"); + return; + } + + cfg = i; + mwv207_output_write(output, regbase + 0x70, 0xc8); + mwv207_output_write(output, regbase + 0x74, 0x2); + for (i = 0; i < 47; i++) { + offset = 0x4 + i * 4; + if (offset == 0x84 || offset == 0x88 || offset == 0xB8) + continue; + + mwv207_output_write(output, regbase + offset, + hdmi->phy_data[cfg].config[i]); + } +} + +static void mwv207_hdmi_phy_switch(struct mwv207_hdmi *hdmi, int enable) +{ + struct mwv207_output *output = &hdmi->base; + u32 regbase = MWV207_HDMI_PHY(output->idx); + + if (enable) { + mwv207_output_write(output, regbase + 0x84, 0x80); + mwv207_output_modify(output, MWV207_HDMI_CTRL(output->idx), 1 << 28, 0 << 28); + } else { + mwv207_output_modify(output, MWV207_HDMI_CTRL(output->idx), 1 << 28, 1 << 28); + mwv207_output_write(output, regbase + 0x84, 0x00); + } +} + +static void mwv207_hdmi_phy_power_on(struct mwv207_hdmi *hdmi) +{ + u8 val; + int i; + + mwv207_hdmi_phy_switch(hdmi, 1); + + for (i = 0; i < 5; ++i) { + val = mwv207_hdmi_readb(hdmi, 0x3004) & 0x01; + if (val) + break; + usleep_range(1000, 2000); + } + + if (!val) + pr_warn("mwv207: hdmi PHY PLL failed to lock\n"); +} + +static void mwv207_hdmi_phy_power_off(struct mwv207_hdmi *hdmi) +{ + u8 val; + int i; + + mwv207_hdmi_phy_switch(hdmi, 0); + + for (i = 0; i < 5; ++i) { + val = mwv207_hdmi_readb(hdmi, 0x3004); + if (!(val & 0x01)) + break; + + usleep_range(1000, 2000); + } + + if (val & 0x01) + pr_warn("mwv207: PHY failed to power down\n"); +} + +static void mwv207_hdmi_phy_configure(struct mwv207_hdmi *hdmi, int kfreq, int bpp) +{ + mwv207_hdmi_phy_power_off(hdmi); + + mwv207_hdmi_phy_configure_data(hdmi, kfreq, bpp); + + mwv207_hdmi_phy_power_on(hdmi); +} + +static void mwv207_hdmi_clear_overflow(struct mwv207_hdmi *hdmi) +{ + u8 val; + + mwv207_hdmi_writeb(hdmi, (u8) ~0x02, 0x4002); + + val = mwv207_hdmi_readb(hdmi, 0x1000); + mwv207_hdmi_writeb(hdmi, val, 0x1000); +} + +static void mwv207_hdmi_ih_mutes(struct mwv207_hdmi *hdmi) +{ + u8 ih_mute; + + ih_mute = mwv207_hdmi_readb(hdmi, 0x01FF) | 0x2 | 0x1; + + mwv207_hdmi_writeb(hdmi, ih_mute, 0x01FF); + + mwv207_hdmi_writeb(hdmi, 0xff, 0x0807); + mwv207_hdmi_writeb(hdmi, 0xff, 0x10D2); + mwv207_hdmi_writeb(hdmi, 0xff, 0x10D6); + mwv207_hdmi_writeb(hdmi, 0xff, 0x10DA); + mwv207_hdmi_writeb(hdmi, 0xff, 0x3006); + mwv207_hdmi_writeb(hdmi, 0xff, 0x3027); + mwv207_hdmi_writeb(hdmi, 0xff, 0x3028); + mwv207_hdmi_writeb(hdmi, 0xff, 0x3102); + mwv207_hdmi_writeb(hdmi, 0xff, 0x3302); + mwv207_hdmi_writeb(hdmi, 0xff, 0x3404); + mwv207_hdmi_writeb(hdmi, 0xff, 0x3505); + mwv207_hdmi_writeb(hdmi, 0xff, 0x5008); + mwv207_hdmi_writeb(hdmi, 0xff, 0x7E05); + mwv207_hdmi_writeb(hdmi, 0xff, 0x7E06); + + mwv207_hdmi_writeb(hdmi, 0xff, 0x0180); + mwv207_hdmi_writeb(hdmi, 0xff, 0x0181); + mwv207_hdmi_writeb(hdmi, 0xff, 0x0182); + mwv207_hdmi_writeb(hdmi, 0xff, 0x0183); + mwv207_hdmi_writeb(hdmi, 0xff, 0x0184); + mwv207_hdmi_writeb(hdmi, 0xff, 0x0185); + mwv207_hdmi_writeb(hdmi, 0xff, 0x0186); + mwv207_hdmi_writeb(hdmi, 0xff, 0x0187); + mwv207_hdmi_writeb(hdmi, 0xff, 0x0188); + mwv207_hdmi_writeb(hdmi, 0xff, 0x0189); + + ih_mute &= ~(0x2 | 0x1); + mwv207_hdmi_writeb(hdmi, ih_mute, 0x01FF); +} + +static void mwv207_hdmi_phy_setup_hpd(struct mwv207_hdmi *hdmi) +{ + mwv207_hdmi_writeb(hdmi, 0x02, 0x3007); + mwv207_hdmi_writeb(hdmi, 0x1, 0x0104); + + mwv207_hdmi_writeb(hdmi, ~0x02, 0x3006); + + mwv207_hdmi_writeb(hdmi, 0x1, 0x0104); + mwv207_hdmi_writeb(hdmi, ~0x1, 0x0184); +} + +static irqreturn_t mwv207_hdmi_hard_irq(int irq, void *dev_id) +{ + struct mwv207_hdmi *hdmi = dev_id; + irqreturn_t ret = IRQ_NONE; + u8 intr_stat; + + intr_stat = mwv207_hdmi_readb(hdmi, 0x0104); + if (intr_stat) { + mwv207_hdmi_writeb(hdmi, ~0, 0x0184); + return IRQ_WAKE_THREAD; + } + + return ret; +} + +static irqreturn_t mwv207_hdmi_irq(int irq, void *dev_id) +{ + u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat; + struct mwv207_hdmi *hdmi = dev_id; + + intr_stat = mwv207_hdmi_readb(hdmi, 0x0104); + phy_int_pol = mwv207_hdmi_readb(hdmi, 0x3007); + phy_stat = mwv207_hdmi_readb(hdmi, 0x3004); + + phy_pol_mask = 0; + if (intr_stat & 0x1) + phy_pol_mask |= 0x02; + if (intr_stat & 0x4) + phy_pol_mask |= 0x10; + if (intr_stat & 0x8) + phy_pol_mask |= 0x20; + if (intr_stat & 0x10) + phy_pol_mask |= 0x40; + if (intr_stat & 0x20) + phy_pol_mask |= 0x80; + + if (phy_pol_mask) + mwv207_hdmi_modb(hdmi, ~phy_int_pol, phy_pol_mask, 0x3007); + + if (intr_stat & 0x1) + drm_helper_hpd_irq_event(&hdmi->base.jdev->base); + + mwv207_hdmi_writeb(hdmi, intr_stat, 0x0104); + mwv207_hdmi_writeb(hdmi, ~0x1, 0x0184); + + return IRQ_HANDLED; +} + +static void mwv207_hdmi_switch(struct mwv207_output *output, bool on) +{ + mwv207_output_modify(output, MWV207_HDMI_CTRL(output->idx), 1, on ? 1 : 0); +} + +static void mwv207_hdmi_select_crtc(struct mwv207_output *output) +{ + + mwv207_output_modify(output, MWV207_HDMI_CTRL(output->idx), + 0x3 << 4, drm_crtc_index(output->cur_crtc) << 4); + + jdev_modify(output->jdev, 0x9b003c, 0b111 << (8 + output->idx * 4), + drm_crtc_index(output->cur_crtc) << (8 + output->idx * 4)); +} + +static void mwv207_hdmi_disable_overflow_interrupts(struct mwv207_hdmi *hdmi) +{ + mwv207_hdmi_writeb(hdmi, 0x03, 0x0182); +} + +static bool mwv207_hdmi_support_scdc(struct mwv207_hdmi *hdmi, + const struct drm_display_info *display) +{ + + if (!display->hdmi.scdc.supported || + !display->hdmi.scdc.scrambling.supported) + return false; + + if (!display->hdmi.scdc.scrambling.low_rates && + display->max_tmds_clock <= 340000) + return false; + + return true; +} + +static void mwv207_hdmi_av_composer(struct mwv207_hdmi *hdmi, const struct drm_display_mode *mode) +{ + const struct drm_display_info *display = &hdmi->base.connector.display_info; + const struct drm_hdmi_info *hdmi_info = &display->hdmi; + int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len; + struct mwv207_output *output = &hdmi->base; + unsigned int vdisplay, hdisplay; + u32 tmdsclock, rate; + u8 inv_val, bytes; + + tmdsclock = mode->clock * 1000; + + inv_val = (mwv207_hdmi_support_scdc(hdmi, display) && + (tmdsclock > 340000000 || + hdmi_info->scdc.scrambling.low_rates) ? 0x80 : 0x00); + + inv_val |= mode->flags & DRM_MODE_FLAG_PVSYNC ? 0x40 : 0x00; + + inv_val |= mode->flags & DRM_MODE_FLAG_PHSYNC ? 0x20 : 0x00; + + inv_val |= 0x10; + + if (hdmi->vic == 39) + inv_val |= 0x2; + else + inv_val |= mode->flags & DRM_MODE_FLAG_INTERLACE ? 0x2 : 0x0; + + inv_val |= mode->flags & DRM_MODE_FLAG_INTERLACE ? 0x1 : 0x0; + + inv_val |= hdmi->sink_is_hdmi ? 0x8 : 0x0; + + mwv207_hdmi_writeb(hdmi, inv_val, 0x1000); + + rate = drm_mode_vrefresh(mode) * 1000; + mwv207_hdmi_writeb(hdmi, rate >> 16, 0x1010); + mwv207_hdmi_writeb(hdmi, rate >> 8, 0x100F); + mwv207_hdmi_writeb(hdmi, rate, 0x100E); + + + mwv207_output_modify(output, MWV207_HDMI_CTRL(output->idx), 0x1 << 9, + (mode->flags & DRM_MODE_FLAG_PHSYNC ? 1 : 0) << 9); + mwv207_output_modify(output, MWV207_HDMI_CTRL(output->idx), 0x1 << 8, + (mode->flags & DRM_MODE_FLAG_PVSYNC ? 1 : 0) << 8); + mwv207_output_modify(output, MWV207_HDMI_CTRL(output->idx), 0x1 << 10, 1 << 10); + + hdisplay = mode->hdisplay; + hblank = mode->htotal - mode->hdisplay; + h_de_hs = mode->hsync_start - mode->hdisplay; + hsync_len = mode->hsync_end - mode->hsync_start; + + vdisplay = mode->vdisplay; + vblank = mode->vtotal - mode->vdisplay; + v_de_vs = mode->vsync_start - mode->vdisplay; + vsync_len = mode->vsync_end - mode->vsync_start; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + vdisplay /= 2; + vblank /= 2; + v_de_vs /= 2; + vsync_len /= 2; + } + + if (mwv207_hdmi_support_scdc(hdmi, display)) { + if (tmdsclock > 340000000 || + hdmi_info->scdc.scrambling.low_rates) { + drm_scdc_readb(hdmi->base.ddc, SCDC_SINK_VERSION, + &bytes); + drm_scdc_writeb(hdmi->base.ddc, SCDC_SOURCE_VERSION, + min_t(u8, bytes, 0x1)); + + drm_scdc_set_scrambling(hdmi->base.ddc, 1); + + mwv207_hdmi_writeb(hdmi, (u8)~0x02, 0x4002); + mwv207_hdmi_writeb(hdmi, 1, 0x10E1); + } else { + mwv207_hdmi_writeb(hdmi, 0, 0x10E1); + mwv207_hdmi_writeb(hdmi, (u8)~0x02, 0x4002); + drm_scdc_set_scrambling(hdmi->base.ddc, 0); + } + } + + mwv207_hdmi_writeb(hdmi, hdisplay >> 8, 0x1002); + mwv207_hdmi_writeb(hdmi, hdisplay, 0x1001); + + mwv207_hdmi_writeb(hdmi, vdisplay >> 8, 0x1006); + mwv207_hdmi_writeb(hdmi, vdisplay, 0x1005); + + mwv207_hdmi_writeb(hdmi, hblank >> 8, 0x1004); + mwv207_hdmi_writeb(hdmi, hblank, 0x1003); + + mwv207_hdmi_writeb(hdmi, vblank >> 8, 0x102E); + mwv207_hdmi_writeb(hdmi, vblank, 0x1007); + + mwv207_hdmi_writeb(hdmi, h_de_hs >> 8, 0x1009); + mwv207_hdmi_writeb(hdmi, h_de_hs, 0x1008); + + mwv207_hdmi_writeb(hdmi, v_de_vs >> 8, 0x102F); + mwv207_hdmi_writeb(hdmi, v_de_vs, 0x100C); + + mwv207_hdmi_writeb(hdmi, hsync_len >> 8, 0x100B); + mwv207_hdmi_writeb(hdmi, hsync_len, 0x100A); + + mwv207_hdmi_writeb(hdmi, vsync_len, 0x100D); +} + +static void mwv207_hdmi_enable_video_path(struct mwv207_hdmi *hdmi) +{ + mwv207_hdmi_writeb(hdmi, 12, 0x1011); + mwv207_hdmi_writeb(hdmi, 32, 0x1012); + mwv207_hdmi_writeb(hdmi, 1, 0x1013); + + mwv207_hdmi_writeb(hdmi, 0x0B, 0x1014); + mwv207_hdmi_writeb(hdmi, 0x16, 0x1015); + mwv207_hdmi_writeb(hdmi, 0x21, 0x1016); + + hdmi->mc_clkdis |= 0x40 | 0x8 | 0x10 | 0x4 | 0x2; + hdmi->mc_clkdis &= ~0x1; + mwv207_hdmi_writeb(hdmi, hdmi->mc_clkdis, 0x4001); + + hdmi->mc_clkdis &= ~0x2; + mwv207_hdmi_writeb(hdmi, hdmi->mc_clkdis, 0x4001); + + if (hdmi->rgb_limited_range) { + hdmi->mc_clkdis &= ~0x10; + mwv207_hdmi_writeb(hdmi, hdmi->mc_clkdis, 0x4001); + + mwv207_hdmi_writeb(hdmi, 0x1, 0x4004); + } else { + hdmi->mc_clkdis |= 0x10; + mwv207_hdmi_writeb(hdmi, hdmi->mc_clkdis, 0x4001); + + mwv207_hdmi_writeb(hdmi, 0x0, 0x4004); + } +} + +static void mwv207_hdmi_enable_audio_clk(struct mwv207_hdmi *hdmi, bool enable) +{ + if (enable) + hdmi->mc_clkdis &= ~0x8; + else + hdmi->mc_clkdis |= 0x8; + mwv207_hdmi_writeb(hdmi, hdmi->mc_clkdis, 0x4001); +} + +static void mwv207_hdmi_config_AVI(struct mwv207_hdmi *hdmi, + struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct hdmi_avi_infoframe frame; + u8 val; + + + drm_hdmi_avi_infoframe_from_display_mode(&frame, connector, mode); + + drm_hdmi_avi_infoframe_quant_range(&frame, connector, mode, + hdmi->rgb_limited_range ? + HDMI_QUANTIZATION_RANGE_LIMITED : + HDMI_QUANTIZATION_RANGE_FULL); + + frame.colorspace = HDMI_COLORSPACE_RGB; + frame.colorimetry = HDMI_COLORIMETRY_NONE; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + + val = (frame.scan_mode & 3) << 4 | (frame.colorspace & 3); + if (frame.active_aspect & 15) + val |= 0x40; + if (frame.top_bar || frame.bottom_bar) + val |= 0x08; + if (frame.left_bar || frame.right_bar) + val |= 0x04; + mwv207_hdmi_writeb(hdmi, val, 0x1019); + + + val = ((frame.colorimetry & 0x3) << 6) | + ((frame.picture_aspect & 0x3) << 4) | + (frame.active_aspect & 0xf); + mwv207_hdmi_writeb(hdmi, val, 0x101A); + + + val = ((frame.extended_colorimetry & 0x7) << 4) | + ((frame.quantization_range & 0x3) << 2) | + (frame.nups & 0x3); + if (frame.itc) + val |= 0x80; + mwv207_hdmi_writeb(hdmi, val, 0x101B); + + + val = frame.video_code & 0x7f; + mwv207_hdmi_writeb(hdmi, val, 0x101C); + + + val = ((1 << 4) & 0xF0); + mwv207_hdmi_writeb(hdmi, val, 0x10E0); + + val = ((frame.ycc_quantization_range & 0x3) << 2) | + (frame.content_type & 0x3); + mwv207_hdmi_writeb(hdmi, val, 0x1017); + + mwv207_hdmi_writeb(hdmi, frame.top_bar & 0xff, 0x101D); + mwv207_hdmi_writeb(hdmi, (frame.top_bar >> 8) & 0xff, 0x101E); + mwv207_hdmi_writeb(hdmi, frame.bottom_bar & 0xff, 0x101F); + mwv207_hdmi_writeb(hdmi, (frame.bottom_bar >> 8) & 0xff, 0x1020); + mwv207_hdmi_writeb(hdmi, frame.left_bar & 0xff, 0x1021); + mwv207_hdmi_writeb(hdmi, (frame.left_bar >> 8) & 0xff, 0x1022); + mwv207_hdmi_writeb(hdmi, frame.right_bar & 0xff, 0x1023); + mwv207_hdmi_writeb(hdmi, (frame.right_bar >> 8) & 0xff, 0x1024); +} + +static void mwv207_hdmi_config_vendor_specific_infoframe(struct mwv207_hdmi *hdmi, + struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct hdmi_vendor_infoframe frame; + u8 buffer[10]; + ssize_t err; + + err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, connector, + mode); + if (err < 0) + return; + + err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + DRM_ERROR("Failed to pack vendor infoframe: %zd\n", err); + return; + } + mwv207_hdmi_mask_writeb(hdmi, 0, 0x10B3, 3, 0x08); + + mwv207_hdmi_writeb(hdmi, buffer[2], 0x102A); + + mwv207_hdmi_writeb(hdmi, buffer[4], 0x1029); + mwv207_hdmi_writeb(hdmi, buffer[5], 0x1030); + mwv207_hdmi_writeb(hdmi, buffer[6], 0x1031); + + mwv207_hdmi_writeb(hdmi, buffer[7], 0x1032); + mwv207_hdmi_writeb(hdmi, buffer[8], 0x1033); + + if (frame.s3d_struct >= HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF) + mwv207_hdmi_writeb(hdmi, buffer[9], 0x1034); + + mwv207_hdmi_writeb(hdmi, 1, 0x10B4); + + mwv207_hdmi_writeb(hdmi, 0x11, 0x10B5); + + mwv207_hdmi_mask_writeb(hdmi, 1, 0x10B3, 3, 0x08); +} + +static void mwv207_hdmi_config_drm_infoframe(struct mwv207_hdmi *hdmi, + const struct drm_connector *connector) +{ + const struct drm_connector_state *conn_state = connector->state; + struct hdmi_drm_infoframe frame; + u8 buffer[30]; + ssize_t err; + int i; + + mwv207_hdmi_modb(hdmi, 0x00, 0x80, 0x10E3); + + err = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state); + if (err < 0) + return; + + err = hdmi_drm_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) + return; + + mwv207_hdmi_writeb(hdmi, frame.version, 0x1168); + mwv207_hdmi_writeb(hdmi, frame.length, 0x1169); + + for (i = 0; i < frame.length; i++) + mwv207_hdmi_writeb(hdmi, buffer[4 + i], 0x116A + i); + + mwv207_hdmi_writeb(hdmi, 1, 0x1167); + mwv207_hdmi_modb(hdmi, 0x80, 0x80, 0x10E3); +} + +static void mwv207_hdmi_video_packetize(struct mwv207_hdmi *hdmi) +{ + unsigned int output_select = 0x3; + unsigned int remap_size = 0x0; + unsigned int color_depth = 4; + u8 val, vp_conf; + + val = ((color_depth << 4) & 0xF0); + mwv207_hdmi_writeb(hdmi, val, 0x0801); + + mwv207_hdmi_modb(hdmi, 0x1, 0x1, 0x0802); + + vp_conf = 0x00 | 0x4; + + mwv207_hdmi_modb(hdmi, vp_conf, 0x10 | 0x4, 0x0804); + + mwv207_hdmi_modb(hdmi, 1 << 5, 0x20, 0x0802); + + mwv207_hdmi_writeb(hdmi, remap_size, 0x0803); + + vp_conf = 0x40 | 0x00 | 0x0; + + mwv207_hdmi_modb(hdmi, vp_conf, 0x40 | 0x20 | 0x8, 0x0804); + + mwv207_hdmi_modb(hdmi, 0x2 | 0x4, 0x2 | 0x4, 0x0802); + + mwv207_hdmi_modb(hdmi, output_select, 0x3, 0x0804); +} + +static void mwv207_hdmi_update_csc_coeffs(struct mwv207_hdmi *hdmi) +{ + const u16 (*csc_coeff)[3][4] = &csc_coeff_default; + unsigned int i; + u32 csc_scale = 1; + + if (hdmi->rgb_limited_range) + csc_coeff = &csc_coeff_rgb_full_to_rgb_limited; + else + csc_coeff = &csc_coeff_default; + + for (i = 0; i < ARRAY_SIZE(csc_coeff_default[0]); i++) { + u16 coeff_a = (*csc_coeff)[0][i]; + u16 coeff_b = (*csc_coeff)[1][i]; + u16 coeff_c = (*csc_coeff)[2][i]; + + mwv207_hdmi_writeb(hdmi, coeff_a & 0xff, 0x4103 + i * 2); + mwv207_hdmi_writeb(hdmi, coeff_a >> 8, 0x4102 + i * 2); + mwv207_hdmi_writeb(hdmi, coeff_b & 0xff, 0x410B + i * 2); + mwv207_hdmi_writeb(hdmi, coeff_b >> 8, 0x410A + i * 2); + mwv207_hdmi_writeb(hdmi, coeff_c & 0xff, 0x4113 + i * 2); + mwv207_hdmi_writeb(hdmi, coeff_c >> 8, 0x4112 + i * 2); + } + + mwv207_hdmi_modb(hdmi, csc_scale, 0x03, 0x4101); +} + +static void mwv207_hdmi_video_csc(struct mwv207_hdmi *hdmi) +{ + int color_depth = 0x00; + + mwv207_hdmi_writeb(hdmi, 0, 0x4100); + mwv207_hdmi_modb(hdmi, color_depth, 0xF0, 0x4101); + + mwv207_hdmi_update_csc_coeffs(hdmi); +} + +static void mwv207_hdmi_video_sample(struct mwv207_hdmi *hdmi) +{ + int color_format = 1; + u8 val; + + val = 0x00 | ((color_format << 0) & 0x1F); + mwv207_hdmi_writeb(hdmi, val, 0x0200); + + val = 0x4 | 0x2 | 0x1; + mwv207_hdmi_writeb(hdmi, val, 0x0201); + mwv207_hdmi_writeb(hdmi, 0x0, 0x0202); + mwv207_hdmi_writeb(hdmi, 0x0, 0x0203); + mwv207_hdmi_writeb(hdmi, 0x0, 0x0204); + mwv207_hdmi_writeb(hdmi, 0x0, 0x0205); + mwv207_hdmi_writeb(hdmi, 0x0, 0x0206); + mwv207_hdmi_writeb(hdmi, 0x0, 0x0207); +} + +static void mwv207_hdmi_tx_hdcp_config(struct mwv207_hdmi *hdmi) +{ + u8 de = 0x10; + + mwv207_hdmi_modb(hdmi, 0x0, 0x4, 0x5000); + + mwv207_hdmi_modb(hdmi, de, 0x10, 0x5009); + + mwv207_hdmi_modb(hdmi, 0x2, 0x2, 0x5001); +} + +static void mwv207_hdmi_set_timing(struct mwv207_hdmi *hdmi) +{ + struct drm_display_mode *mode; + + mwv207_hdmi_disable_overflow_interrupts(hdmi); + + mode = &hdmi->base.cur_crtc->state->adjusted_mode; + hdmi->vic = drm_match_cea_mode(mode); + + hdmi->rgb_limited_range = hdmi->sink_is_hdmi && + drm_default_rgb_quant_range(mode) == + HDMI_QUANTIZATION_RANGE_LIMITED; + + mwv207_hdmi_av_composer(hdmi, mode); + + mwv207_hdmi_phy_configure(hdmi, mode->clock, 8); + + mwv207_hdmi_enable_video_path(hdmi); + + if (hdmi->sink_has_audio) { + mwv207_hdmi_enable_audio_clk(hdmi, 1); + } + + if (hdmi->sink_is_hdmi) { + struct drm_connector *conn = &hdmi->base.connector; + + mwv207_hdmi_config_AVI(hdmi, conn, mode); + mwv207_hdmi_config_vendor_specific_infoframe(hdmi, conn, mode); + mwv207_hdmi_config_drm_infoframe(hdmi, conn); + } + + mwv207_hdmi_video_packetize(hdmi); + mwv207_hdmi_video_csc(hdmi); + mwv207_hdmi_video_sample(hdmi); + mwv207_hdmi_tx_hdcp_config(hdmi); + + mwv207_hdmi_clear_overflow(hdmi); +} + +static int mwv207_hdmi_get_modes(struct drm_connector *connector) +{ + struct mwv207_hdmi *hdmi = connector_to_hdmi(connector); + int count; + + count = mwv207_output_get_modes(connector); + + if (connector->edid_blob_ptr) { + struct edid *edid; + + edid = (struct edid *)connector->edid_blob_ptr->data; + hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); + hdmi->sink_has_audio = drm_detect_monitor_audio(edid); + } else { + hdmi->sink_is_hdmi = false; + hdmi->sink_has_audio = false; + } + + return count; +} + +static enum drm_mode_status mwv207_hdmi_mode_valid(struct mwv207_hdmi *hdmi, + const struct drm_display_mode *mode) +{ + if (mode->clock >= 384000) + return MODE_CLOCK_HIGH; + + if (mode->clock > 594000) + return MODE_CLOCK_HIGH; + + if (!hdmi->sink_is_hdmi && mode->clock >= 165000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static enum drm_mode_status mwv207_hdmi_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct mwv207_hdmi *hdmi = connector_to_hdmi(connector); + + return mwv207_hdmi_mode_valid(hdmi, mode); +} + +static int mwv207_hdmi_detect_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct mwv207_hdmi *hdmi = connector_to_hdmi(connector); + u32 value; + + value = mwv207_hdmi_readb(hdmi, 0x3004); + if (value & 0x2) + return connector_status_connected; + else + return connector_status_disconnected; +} + +static void mwv207_hdmi_destroy(struct drm_connector *conn) +{ + struct mwv207_hdmi *hdmi = connector_to_hdmi(conn); + + free_irq(hdmi->irq, hdmi); + drm_connector_unregister(conn); + drm_connector_cleanup(conn); +} + +static const struct drm_connector_helper_funcs mwv207_hdmi_connector_helper_funcs = { + .get_modes = mwv207_hdmi_get_modes, + .mode_valid = mwv207_hdmi_connector_mode_valid, + .detect_ctx = mwv207_hdmi_detect_ctx +}; + +static const struct drm_connector_funcs mwv207_hdmi_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .destroy = mwv207_hdmi_destroy, + .late_register = mwv207_output_late_register, + .early_unregister = mwv207_output_early_unregister, +}; + +static enum drm_mode_status mwv207_hdmi_encoder_mode_valid(struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + struct mwv207_hdmi *hdmi = encoder_to_hdmi(encoder); + + return mwv207_hdmi_mode_valid(hdmi, mode); +} + +static int mwv207_hdmi_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + return 0; +} + +static void mwv207_hdmi_encoder_enable(struct drm_encoder *encoder) +{ + struct mwv207_output *output = encoder_to_output(encoder); + + mwv207_hdmi_select_crtc(output); + + mwv207_hdmi_set_timing(encoder_to_hdmi(encoder)); + + mwv207_hdmi_switch(output, true); +} + +static void mwv207_hdmi_encoder_disable(struct drm_encoder *encoder) +{ + struct mwv207_output *output = encoder_to_output(encoder); + + mwv207_hdmi_switch(output, false); +} + +static void mwv207_hdmi_encoder_reset(struct drm_encoder *encoder) +{ + mwv207_hdmi_encoder_disable(encoder); + mwv207_hdmi_ih_mutes(encoder_to_hdmi(encoder)); + mwv207_hdmi_phy_setup_hpd(encoder_to_hdmi(encoder)); +} + +static const struct drm_encoder_funcs mwv207_hdmi_encoder_funcs = { + .destroy = drm_encoder_cleanup, + .reset = mwv207_hdmi_encoder_reset, +}; + +static const struct drm_encoder_helper_funcs mwv207_hdmi_encoder_helper_funcs = { + .mode_valid = mwv207_hdmi_encoder_mode_valid, + .atomic_check = mwv207_hdmi_encoder_atomic_check, + .enable = mwv207_hdmi_encoder_enable, + .disable = mwv207_hdmi_encoder_disable, +}; + +static int mwv207_hdmi_init_single(struct mwv207_device *jdev, int idx) +{ + struct mwv207_output *output; + struct mwv207_hdmi *hdmi; + int ret; + + hdmi = devm_kzalloc(jdev->dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + output = &hdmi->base; + output->jdev = jdev; + output->idx = idx; + output->mmio = jdev->mmio + 0x9a0000; + output->i2c_chan = 0 + idx; + hdmi->mmio = jdev->mmio + MWV207_HDMI_BASE(idx); + hdmi->mc_clkdis = 0x7f; + + mwv207_hdmi_phy_data_init(hdmi); + + output->connector.polled = DRM_CONNECTOR_POLL_HPD; + ret = drm_connector_init(&jdev->base, &output->connector, + &mwv207_hdmi_connector_funcs, DRM_MODE_CONNECTOR_HDMIA); + if (ret) + return ret; + drm_connector_helper_add(&output->connector, &mwv207_hdmi_connector_helper_funcs); + + output->encoder.possible_crtcs = (1 << jdev->base.mode_config.num_crtc) - 1; + ret = drm_encoder_init(&jdev->base, &output->encoder, + &mwv207_hdmi_encoder_funcs, DRM_MODE_ENCODER_TMDS, + "hdmi-%d", output->idx); + if (ret) + return ret; + drm_encoder_helper_add(&output->encoder, &mwv207_hdmi_encoder_helper_funcs); + + ret = drm_connector_attach_encoder(&output->connector, &output->encoder); + if (ret) + return ret; + + hdmi->irq = irq_find_mapping(jdev->irq_domain, output->idx + 32); + BUG_ON(hdmi->irq == 0); + + ret = request_threaded_irq(hdmi->irq, + mwv207_hdmi_hard_irq, mwv207_hdmi_irq, + IRQF_SHARED, output->encoder.name, hdmi); + if (ret) + return ret; + + ret = drm_connector_register(&output->connector); + if (ret) + free_irq(hdmi->irq, hdmi); + + return ret; +} + +int mwv207_hdmi_init(struct mwv207_device *jdev) +{ + int i, ret; + + for (i = 0; i < 4; i++) { + + if (jdev_read(jdev, MWV207_HDMI_BASE(i)) != 0x21) + continue; + ret = mwv207_hdmi_init_single(jdev, i); + if (ret) + return ret; + } + + return 0; +} diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_i2c.c b/drivers/gpu/drm/mwv207/dc/mwv207_i2c.c new file mode 100644 index 0000000000000000000000000000000000000000..bcfbef8d57a94e9cb36a41c8862d13f49f83c840 --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_i2c.c @@ -0,0 +1,312 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include "mwv207.h" +#include "mwv207_vi.h" + +static int i2c_scl_gpio[8] = {19, 21, 23, 25, 27, 29, 31, 33}; +static int i2c_sda_gpio[8] = {20, 22, 24, 26, 28, 30, 32, 34}; + +struct mwv207_i2c { + struct i2c_adapter adapter; + struct mwv207_device *jdev; + struct i2c_algo_bit_data bit; + struct mutex *mutex; + u32 sda_in_addr; + u32 sda_out_addr; + u32 sda_mask; + u32 sda_dir; + u32 scl_in_addr; + u32 scl_out_addr; + u32 scl_mask; + u32 scl_dir; +}; + +static inline void mwv207_i2c_set_dir(struct mwv207_i2c *i2c, u32 mask, + u32 reg, bool is_input) +{ + mb(); + + jdev_modify(i2c->jdev, reg, mask, is_input ? mask : 0); + + mb(); +} + + +static void mwv207_i2c_gpio_multi(struct mwv207_i2c *i2c, int mask) +{ + struct mwv207_device *jdev = i2c->jdev; + + switch (mask) { + case (1 << 19): + + jdev_modify(jdev, (0x9b0918), 0x3 << 12, 0); + jdev_modify(jdev, (0x9b0918), 0x3 << 16, 0); + break; + case (1 << 21): + + jdev_modify(jdev, (0x9b0918), 0x3 << 20, 0); + jdev_modify(jdev, (0x9b0918), 0x3 << 24, 0); + break; + case (1 << 23): + + jdev_modify(jdev, (0x9b0918), 0x3 << 28, 0); + jdev_modify(jdev, (0x9b091c), 0x3, 0); + break; + case (1 << 25): + + jdev_modify(jdev, (0x9b091c), 0x3 << 4, 0); + jdev_modify(jdev, (0x9b091c), 0x3 << 8, 0); + break; + case (1 << 27): + + jdev_modify(jdev, (0x9b091c), 0x3 << 12, 0); + jdev_modify(jdev, (0x9b091c), 0x3 << 16, 0); + break; + case (1 << 29): + + jdev_modify(jdev, (0x9b091c), 0x3 << 20, 0); + jdev_modify(jdev, (0x9b091c), 0x3 << 24, 0); + break; + case (1 << 31): + + jdev_modify(jdev, (0x9b091c), 0x3 << 28, 0); + jdev_modify(jdev, (0x9b0910), 0x3, 0); + break; + case (1 << 1): + + jdev_modify(jdev, (0x9b0910), 0x3 << 4, 0); + jdev_modify(jdev, (0x9b0910), 0x3 << 8, 0); + break; + default: + break; + } +} + +static int mwv207_i2c_pre_xfer(struct i2c_adapter *i2c_adap) +{ + struct mwv207_i2c *i2c = i2c_get_adapdata(i2c_adap); + struct mwv207_device *jdev = i2c->jdev; + + mutex_lock(i2c->mutex); + mwv207_i2c_gpio_multi(i2c, i2c->scl_mask); + + jdev_modify(jdev, i2c->scl_out_addr, i2c->scl_mask, 0); + jdev_modify(jdev, i2c->sda_out_addr, i2c->sda_mask, 0); + + mwv207_i2c_set_dir(i2c, i2c->scl_mask, i2c->scl_dir, 1); + mwv207_i2c_set_dir(i2c, i2c->sda_mask, i2c->sda_dir, 1); + + return 0; +} + +static void mwv207_i2c_post_xfer(struct i2c_adapter *i2c_adap) +{ + struct mwv207_i2c *i2c = i2c_get_adapdata(i2c_adap); + + mutex_unlock(i2c->mutex); +} + +static int mwv207_i2c_get_clock(void *i2c_priv) +{ + struct mwv207_i2c *i2c = (struct mwv207_i2c *)i2c_priv; + struct mwv207_device *jdev = i2c->jdev; + u32 val; + + mwv207_i2c_set_dir(i2c, i2c->scl_mask, i2c->scl_dir, 1); + + val = jdev_read(jdev, i2c->scl_in_addr); + val &= i2c->scl_mask; + + return val ? 1 : 0; +} + +static int mwv207_i2c_get_data(void *i2c_priv) +{ + struct mwv207_i2c *i2c = (struct mwv207_i2c *)i2c_priv; + struct mwv207_device *jdev = i2c->jdev; + u32 val; + + mwv207_i2c_set_dir(i2c, i2c->sda_mask, i2c->sda_dir, 1); + + val = jdev_read(jdev, i2c->sda_in_addr); + val &= i2c->sda_mask; + + return val ? 1 : 0; +} + +static void mwv207_i2c_set_clock(void *i2c_priv, int clock) +{ + struct mwv207_i2c *i2c = (struct mwv207_i2c *)i2c_priv; + struct mwv207_device *jdev = i2c->jdev; + + mwv207_i2c_set_dir(i2c, i2c->scl_mask, i2c->scl_dir, 0); + jdev_modify(jdev, i2c->scl_out_addr, i2c->scl_mask, clock ? i2c->scl_mask : 0); +} + +static void mwv207_i2c_set_data(void *i2c_priv, int data) +{ + struct mwv207_i2c *i2c = (struct mwv207_i2c *)i2c_priv; + + if (data) + mwv207_i2c_set_dir(i2c, i2c->sda_mask, i2c->sda_dir, 1); + else + mwv207_i2c_set_dir(i2c, i2c->sda_mask, i2c->sda_dir, 0); +} + +static int mwv207_i2c_set_addr(struct mwv207_i2c *i2c, int channel) +{ + switch (channel) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + i2c->sda_in_addr = (0x9aa000); + i2c->scl_in_addr = (0x9aa000); + i2c->sda_out_addr = (0x9aa004); + i2c->scl_out_addr = (0x9aa004); + i2c->scl_mask = (1 << i2c_scl_gpio[channel]); + i2c->sda_mask = (1 << i2c_sda_gpio[channel]); + i2c->scl_dir = (0x9aa008); + i2c->sda_dir = (0x9aa008); + break; + case 6: + i2c->sda_in_addr = (0x9aa000); + i2c->scl_in_addr = (0x9aa010); + i2c->sda_out_addr = (0x9aa004); + i2c->scl_out_addr = (0x9aa014); + i2c->scl_mask = (1 << i2c_scl_gpio[channel]); + i2c->sda_mask = (1 << (i2c_sda_gpio[channel] - 32)); + i2c->scl_dir = (0x9aa008); + i2c->sda_dir = (0x9aa018); + break; + case 7: + i2c->sda_in_addr = (0x9aa010); + i2c->scl_in_addr = (0x9aa010); + i2c->sda_out_addr = (0x9aa014); + i2c->scl_out_addr = (0x9aa014); + i2c->scl_mask = (1 << (i2c_scl_gpio[channel] - 32)); + i2c->sda_mask = (1 << (i2c_sda_gpio[channel] - 32)); + i2c->scl_dir = (0x9aa018); + i2c->sda_dir = (0x9aa018); + break; + } + + return 0; +} + +void mwv207_i2c_put_byte(struct i2c_adapter *i2c, + u8 slave_addr, + u8 addr, + u8 val) +{ + uint8_t out_buf[2]; + struct i2c_msg msg = { + .addr = slave_addr, + .flags = 0, + .len = 2, + .buf = out_buf, + }; + + out_buf[0] = addr; + out_buf[1] = val; + + if (i2c_transfer(i2c, &msg, 1) != 1) + pr_warn("i2c 0x%02x 0x%02x write failed\n", + addr, val); +} + +void mwv207_i2c_destroy(struct i2c_adapter *adapter) +{ + i2c_del_adapter(adapter); +} + +struct i2c_adapter *mwv207_i2c_create(struct mwv207_device *jdev, int i2c_chan) +{ + struct mwv207_i2c *i2c; + int ret; + + if (i2c_chan < 0 || i2c_chan >= 6) + return NULL; + + i2c = devm_kzalloc(jdev->dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return NULL; + + if (mwv207_i2c_set_addr(i2c, i2c_chan)) + return NULL; + + i2c->adapter.owner = THIS_MODULE; + i2c->adapter.class = I2C_CLASS_DDC; + i2c->adapter.dev.parent = jdev->dev; + i2c_set_adapdata(&i2c->adapter, i2c); + i2c->mutex = &jdev->gpio_lock; + i2c->jdev = jdev; + + snprintf(i2c->adapter.name, sizeof(i2c->adapter.name), + "MWV207_I2C_%d", i2c_chan); + i2c->adapter.algo_data = &i2c->bit; + i2c->bit.pre_xfer = mwv207_i2c_pre_xfer; + i2c->bit.post_xfer = mwv207_i2c_post_xfer; + i2c->bit.setsda = mwv207_i2c_set_data; + i2c->bit.setscl = mwv207_i2c_set_clock; + i2c->bit.getsda = mwv207_i2c_get_data; + i2c->bit.getscl = mwv207_i2c_get_clock; + i2c->bit.udelay = 10; + i2c->bit.timeout = usecs_to_jiffies(2200); + i2c->bit.data = i2c; + + ret = i2c_bit_add_bus(&i2c->adapter); + if (ret) + return NULL; + + return &i2c->adapter; +} + +bool mwv207_i2c_probe(struct i2c_adapter *i2c_bus) +{ + u8 out = 0x0; + u8 buf[8]; + int ret; + struct i2c_msg msgs[] = { + { + .addr = DDC_ADDR, + .flags = 0, + .len = 1, + .buf = &out, + }, + { + .addr = DDC_ADDR, + .flags = I2C_M_RD, + .len = 8, + .buf = buf, + } + }; + + ret = i2c_transfer(i2c_bus, msgs, 2); + if (ret != 2) { + + return false; + } + + if (drm_edid_header_is_valid(buf) < 6) { + return false; + } + return true; +} diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_kms.c b/drivers/gpu/drm/mwv207/dc/mwv207_kms.c new file mode 100644 index 0000000000000000000000000000000000000000..e4887e2c3df4d85ad275026e6f4d27e700df69b3 --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_kms.c @@ -0,0 +1,204 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mwv207_gem.h" +#include "mwv207_kms.h" +#include "mwv207_va.h" +#include "mwv207_vi.h" + +static const struct drm_framebuffer_funcs fb_funcs = { + .destroy = drm_gem_fb_destroy, + .create_handle = drm_gem_fb_create_handle, +}; + +int mwv207_framebuffer_init(struct mwv207_device *jdev, struct drm_framebuffer *fb, + const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object *gobj) +{ + int ret; + + fb->obj[0] = gobj; + drm_helper_mode_fill_fb_struct(&jdev->base, fb, mode_cmd); + + ret = drm_framebuffer_init(&jdev->base, fb, &fb_funcs); + return ret; +} + +static struct drm_framebuffer *mwv207_framebuffer_create( + struct drm_device *dev, + struct drm_file *filp, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_gem_object *gobj = drm_gem_object_lookup(filp, mode_cmd->handles[0]); + struct drm_framebuffer *fb; + int err = -ENOENT; + + if (!gobj) { + DRM_DEBUG_DRIVER("fbdev failed to lookup gem object, handle = %d\n", + mode_cmd->handles[0]); + return ERR_PTR(-ENOENT); + } + + fb = kzalloc(sizeof(*fb), GFP_KERNEL); + if (!fb) { + err = -ENOMEM; + goto err_unref; + } + + err = mwv207_framebuffer_init(dev->dev_private, fb, mode_cmd, gobj); + if (err) + goto err_free; + + return fb; +err_free: + kfree(fb); +err_unref: + mwv207_gem_object_put(gobj); + return ERR_PTR(err); +} + +static void mwv207_atomic_prepare_vblank(struct drm_device *dev, + struct drm_atomic_state *old_state) +{ + struct drm_crtc_state *new_crtc_state; + struct drm_crtc *crtc; + int i; + + for_each_new_crtc_in_state(old_state, crtc, new_crtc_state, i) + mwv207_crtc_prepare_vblank(crtc); +} + +static void mwv207_atomic_commit_tail(struct drm_atomic_state *old_state) +{ + struct drm_device *dev = old_state->dev; + + drm_atomic_helper_commit_modeset_disables(dev, old_state); + drm_atomic_helper_commit_planes(dev, old_state, 0); + drm_atomic_helper_commit_modeset_enables(dev, old_state); + + mwv207_atomic_prepare_vblank(dev, old_state); + + drm_atomic_helper_commit_hw_done(old_state); + + drm_atomic_helper_wait_for_vblanks(dev, old_state); + drm_atomic_helper_cleanup_planes(dev, old_state); +} + +static struct drm_mode_config_helper_funcs mwv207_mode_config_helper_funcs = { + .atomic_commit_tail = mwv207_atomic_commit_tail, +}; + +static const struct drm_mode_config_funcs mwv207_mode_config_funcs = { + .fb_create = mwv207_framebuffer_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .output_poll_changed = drm_fb_helper_output_poll_changed, +}; + +static void mwv207_modeset_init(struct drm_device *dev) +{ + drm_mode_config_init(dev); + + dev->mode_config.min_width = 20; + dev->mode_config.min_height = 20; + + dev->mode_config.max_width = 4096 * 4; + dev->mode_config.max_height = 4096 * 4; + + dev->mode_config.preferred_depth = 24; + dev->mode_config.prefer_shadow = 1; + dev->mode_config.allow_fb_modifiers = true; + + dev->mode_config.async_page_flip = true; + + dev->mode_config.funcs = &mwv207_mode_config_funcs; + dev->mode_config.helper_private = &mwv207_mode_config_helper_funcs; + + dev->mode_config.cursor_width = 64; + dev->mode_config.cursor_height = 64; +} + +int mwv207_kms_suspend(struct mwv207_device *jdev) +{ + return drm_mode_config_helper_suspend(&jdev->base); +} + +int mwv207_kms_resume(struct mwv207_device *jdev) +{ + int ret; + + ret = drm_mode_config_helper_resume(&jdev->base); + if (ret) + pr_warn("mwv207: failed to resume kms, ret: %d", ret); + + return ret; +} + +int mwv207_kms_init(struct mwv207_device *jdev) +{ + int ret; + + mwv207_modeset_init(&jdev->base); + + ret = mwv207_va_init(jdev); + if (ret) + goto cleanup; + + ret = mwv207_vi_init(jdev); + if (ret) + goto cleanup; + + jdev->va_irq = irq_find_mapping(jdev->irq_domain, 42); + BUG_ON(jdev->va_irq == 0); + ret = request_irq(jdev->va_irq, mwv207_va_handle_vblank, 0, "mwv207_va", jdev); + if (ret) + goto cleanup; + jdev->base.irq_enabled = true; + + ret = drm_vblank_init(&jdev->base, jdev->base.mode_config.num_crtc); + if (ret) + goto free_irq; + + drm_mode_config_reset(&jdev->base); + + drm_kms_helper_poll_init(&jdev->base); + + DRM_INFO("number of crtc: %d\n", jdev->base.mode_config.num_crtc); + DRM_INFO("number of encoder: %d\n", jdev->base.mode_config.num_encoder); + DRM_INFO("number of connector: %d\n", jdev->base.mode_config.num_connector); + + return 0; +free_irq: + free_irq(jdev->va_irq, jdev); +cleanup: + drm_mode_config_cleanup(&jdev->base); + return ret; +} + +void mwv207_kms_fini(struct mwv207_device *jdev) +{ + drm_kms_helper_poll_fini(&jdev->base); + drm_atomic_helper_shutdown(&jdev->base); + free_irq(jdev->va_irq, jdev); + drm_mode_config_cleanup(&jdev->base); +} diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_kms.h b/drivers/gpu/drm/mwv207/dc/mwv207_kms.h new file mode 100644 index 0000000000000000000000000000000000000000..eedb428f638d63c1f54a63631c6c6b0a40805d4c --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_kms.h @@ -0,0 +1,33 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef MWV207_KMS_H_VBCHTKVP +#define MWV207_KMS_H_VBCHTKVP + +#include +#include "mwv207.h" + +int mwv207_framebuffer_init(struct mwv207_device *jdev, struct drm_framebuffer *fb, + const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object *gobj); + +int mwv207_fbdev_init(struct mwv207_device *jdev); +void mwv207_fbdev_fini(struct mwv207_device *jdev); + +int mwv207_kms_init(struct mwv207_device *jdev); +void mwv207_kms_fini(struct mwv207_device *jdev); + +int mwv207_kms_suspend(struct mwv207_device *jdev); +int mwv207_kms_resume(struct mwv207_device *jdev); +#endif diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_va.c b/drivers/gpu/drm/mwv207/dc/mwv207_va.c new file mode 100644 index 0000000000000000000000000000000000000000..01c7e2b546e066c8c949ef79757a57009e02e791 --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_va.c @@ -0,0 +1,717 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "mwv207_bo.h" +#include "mwv207_va.h" +#include "mwv207_vi.h" +#include "mwv207_vbios.h" + +#define MWV207_WIN_BASE(idx) (0x990000 + 0x100 * (idx) + ((idx) > 1 ? 0xC00 : 0)) + +#define MWV207_VA_BASE(idx) (0x990000 + 0x100 * (idx) + ((idx) > 1 ? 0x600 : 0)) + +#define crtc_to_va(crtc) container_of(crtc, struct mwv207_va, crtc) +#define plane_to_va(plane) (((plane)->type == DRM_PLANE_TYPE_PRIMARY) \ + ? container_of(plane, struct mwv207_va, primary) \ + : container_of(plane, struct mwv207_va, cursor)) +struct mwv207_va { + struct drm_crtc crtc; + struct drm_plane primary; + struct drm_plane cursor; + void __iomem *mmio; + struct mwv207_device *jdev; + struct drm_pending_vblank_event *event; + int idx; + uint16_t lutdata[768]; +}; + +static u32 rgb_formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB2101010, +}; + +static uint64_t format_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static u32 cursor_formats[] = { + DRM_FORMAT_ARGB8888, +}; + +static inline u32 mwv207_va_read(struct mwv207_va *va, u32 reg) +{ + return readl(va->mmio + reg); +} + +static inline void mwv207_va_write(struct mwv207_va *va, u32 reg, u32 value) +{ + writel_relaxed(value, va->mmio + reg); +} + +static inline void mwv207_va_modify(struct mwv207_va *va, u32 reg, + u32 mask, u32 value) +{ + u32 rvalue = mwv207_va_read(va, reg); + + rvalue = (rvalue & ~mask) | (value & mask); + mwv207_va_write(va, reg, rvalue); +} + +static bool mwv207_plane_format_mod_supported(struct drm_plane *plane, + uint32_t format, uint64_t modifier) +{ + if (modifier == DRM_FORMAT_MOD_LINEAR) + return true; + return false; +} + +static int mwv207_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + + return 0; +} + +static int mwv207_plane_prepare_fb(struct drm_plane *plane, + struct drm_plane_state *new_state) +{ + struct mwv207_bo *jbo; + int ret; + + if (!new_state->fb) + return 0; + + jbo = mwv207_bo_from_gem(new_state->fb->obj[0]); + + ret = mwv207_bo_reserve(jbo, false); + if (ret) + return ret; + + + jbo->flags |= (1<<0); + ret = mwv207_bo_pin_reserved(jbo, 0x2); + mwv207_bo_unreserve(jbo); + if (ret) + return ret; + + mwv207_bo_ref(jbo); + + drm_gem_fb_prepare_fb(plane, new_state); + + return 0; +} + +static void mwv207_plane_cleanup_fb(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct mwv207_bo *jbo; + + if (!old_state->fb) + return; + + jbo = mwv207_bo_from_gem(old_state->fb->obj[0]); + if (!jbo) + return; + + mwv207_bo_unpin(jbo); + mwv207_bo_unref(jbo); +} + +static u64 mwv207_plane_fb_addr(struct drm_plane *plane) +{ + struct drm_framebuffer *fb; + struct mwv207_bo *bo; + u32 src_x, src_y; + u64 fbaddr; + + + src_x = plane->state->src_x >> 16; + src_y = plane->state->src_y >> 16; + + fb = plane->state->fb; + bo = mwv207_bo_from_gem(fb->obj[0]); + fbaddr = mwv207_bo_gpu_phys(bo); + fbaddr += src_x * fb->format->cpp[0] + fb->pitches[0] * src_y; + + return fbaddr; +} + +static void mwv207_primary_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct mwv207_va *va = plane_to_va(plane); + struct drm_framebuffer *fb; + u32 src_w, src_h; + u64 fbaddr; + + + if (!plane->state->fb || !plane->state->crtc) + return; + + + src_w = plane->state->src_w >> 16; + src_h = plane->state->src_h >> 16; + fbaddr = mwv207_plane_fb_addr(plane); + fb = plane->state->fb; + + + mwv207_va_write(va, 0x434, fb->pitches[0] >> 4); + + + mwv207_va_write(va, 0x43C, ((src_h - 1) << 16) | (src_w - 1) | 0x3); + + + mwv207_va_write(va, 0x4F8, (src_w - 1) << 16); + + + mwv207_va_write(va, 0x438, fbaddr >> 6); + + + switch (fb->format->format) { + case DRM_FORMAT_RGB565: + mwv207_va_modify(va, 0x430, 1, 1); + break; + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_ARGB2101010: + mwv207_va_modify(va, 0x430, 1 << 1, 1 << 1); + break; + case DRM_FORMAT_ARGB8888: + mwv207_va_modify(va, 0x430, 0x3, 0); + break; + default: + break; + } +} + +static void mwv207_primary_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + +} + +/* turn on / off cursor */ +static void mwv207_cursor_switch(struct mwv207_va *va, bool on) +{ + mwv207_va_modify(va, 0x478, 0xff, on ? 6 : 0); +} + +static void mwv207_cursor_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + int crtc_x, crtc_y, pos_x, pos_y, hot_x, hot_y; + struct mwv207_va *va = plane_to_va(plane); + u64 fbaddr; + + + if (!plane->state->fb || !plane->state->crtc) + return; + + + crtc_x = plane->state->crtc_x; + crtc_y = plane->state->crtc_y; + fbaddr = mwv207_plane_fb_addr(plane); + + + mwv207_va_write(va, 0x4B4, fbaddr >> 6); + + + pos_x = crtc_x < 0 ? 0 : crtc_x; + hot_x = crtc_x < 0 ? -crtc_x : 0; + pos_y = crtc_y < 0 ? 0 : crtc_y; + hot_y = crtc_y < 0 ? -crtc_y : 0; + mwv207_va_write(va, 0x454, (hot_x & 0x3f) | ((hot_y & 0x3f) << 16)); + mwv207_va_write(va, 0x4A8, (pos_x & 0xffff) | ((pos_y & 0xffff) << 16)); + + + mwv207_cursor_switch(va, 1); +} + +static void mwv207_cursor_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct mwv207_va *va = plane_to_va(plane); + + + mwv207_cursor_switch(va, 0); +} + +static const struct drm_plane_funcs mwv207_plane_funcs = { + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .format_mod_supported = mwv207_plane_format_mod_supported, + .destroy = drm_plane_cleanup, +}; + +static const struct drm_plane_helper_funcs mwv207_primary_helper_funcs = { + .prepare_fb = mwv207_plane_prepare_fb, + .cleanup_fb = mwv207_plane_cleanup_fb, + .atomic_check = mwv207_plane_atomic_check, + .atomic_update = mwv207_primary_atomic_update, + .atomic_disable = mwv207_primary_atomic_disable, +}; + +static const struct drm_plane_helper_funcs mwv207_cursor_helper_funcs = { + .prepare_fb = mwv207_plane_prepare_fb, + .cleanup_fb = mwv207_plane_cleanup_fb, + .atomic_check = mwv207_plane_atomic_check, + .atomic_update = mwv207_cursor_atomic_update, + .atomic_disable = mwv207_cursor_atomic_disable, +}; + +static void mwv207_va_misc_reset(struct mwv207_va *va) +{ + struct mwv207_device *jdev = va->jdev; + + + jdev_write(jdev, (0x9b001c), 0xfffff); + jdev_write(jdev, (0x9b0020), 0xfffff); + jdev_write(jdev, (0x9b0024), 0xfffff); + + + jdev_write(jdev, (0x9b0028), 0x03020100); + + + jdev_write(jdev, MWV207_WIN_BASE(va->idx) + 0x2c, 0); +} + +static int mwv207_va_lut_fifo_wait(struct mwv207_va *va, u32 used) +{ + int i; + + + mb(); + for (i = 0; i < 120; ++i) { + if (mwv207_va_read(va, 0x450) <= used) + return 0; + usleep_range(1000, 1001); + } + + return -EBUSY; +} + +static void mwv207_va_lut_fifo_activate(struct mwv207_va *va, int rgb) +{ + + mb(); + mwv207_va_write(va, 0x44C, rgb); + mwv207_va_write(va, 0x448, 1); + udelay(2); + mwv207_va_write(va, 0x448, 0); + udelay(2); +} + +static int mwv207_va_lut_fifo_input(struct mwv207_va *va, u32 value) +{ + int ret; + + + ret = mwv207_va_lut_fifo_wait(va, 10); + if (ret) + return ret; + + mb(); + mwv207_va_write(va, 0x444, value); + + return 0; +} + +static void mwv207_va_lut_enable(struct mwv207_va *va) +{ + u32 active_ram, value; + int i, ram, rgb; + u16 *data; + + mwv207_va_modify(va, 0x460, 1 << 31, 0 << 31); + value = (va->lutdata[384] * 15 / 8) | 0x80000000; + jdev_write(va->jdev, MWV207_WIN_BASE(va->idx) + 0x38, value); + + for (ram = 0; ram < 2; ram++) { + active_ram = mwv207_va_read(va, 0x440); + for (rgb = 0; rgb < 3; rgb++) { + data = &va->lutdata[rgb * 256]; + mwv207_va_lut_fifo_activate(va, rgb); + for (i = 0; i < 1024; i++) { + + value = min_t(u32, 1024 - 5, (data[i / 4] << 2) | 0x03); + if (mwv207_va_lut_fifo_input(va, value)) + pr_warn("mwv207: failed to write lut"); + } + + if (mwv207_va_lut_fifo_wait(va, 0)) + pr_warn("mwv207: failed to configure lut"); + } + + mb(); + mwv207_va_write(va, 0x440, 1 - active_ram); + } +} + +static void mwv207_mode_fixup(struct drm_display_mode *adjusted_mode, struct drm_display_mode *mode) +{ + memcpy(adjusted_mode, mode, sizeof(*adjusted_mode)); + + mode->crtc_hdisplay = adjusted_mode->crtc_hdisplay * 2; + mode->crtc_hblank_start = adjusted_mode->crtc_hblank_start * 2; + mode->crtc_hblank_end = adjusted_mode->crtc_hblank_end * 2; + mode->crtc_hsync_start = adjusted_mode->crtc_hsync_start * 2; + mode->crtc_hsync_end = adjusted_mode->crtc_hsync_end * 2; + mode->crtc_htotal = adjusted_mode->crtc_htotal * 2; + mode->crtc_clock = adjusted_mode->crtc_clock * 2; +} + +static void mwv207_va_config_timing(struct mwv207_va *va) +{ + struct drm_display_mode *m = &va->crtc.state->adjusted_mode; + struct drm_display_mode *native = NULL; + u32 hsbegin, hsend, hblankbegin, hblankend, htotal, hhalf; + u32 vblank_1field_begin, vblank_1field_end; + u32 vblank_2field_begin, vblank_2field_end; + u32 vs_1field_begin, vs_1field_end; + u32 vs_2field_begin, vs_2field_end; + u32 is_interleaved; + u32 vfrontporch, vsync, vtotal; + int ret, value; + + ret = mwv207_vbios_set_pll(va->jdev, va->idx + MWV207_PLL_GRAPH0, m->crtc_clock); + if (ret) { + DRM_ERROR("mwv207: failed to set crtc clock: %d", ret); + return; + } + + is_interleaved = DRM_MODE_FLAG_INTERLACE & m->flags ? 1 : 0; + if (is_interleaved && m->crtc_hdisplay == 720) { + native = kmalloc(sizeof(*m), GFP_KERNEL); + if (!native) { + DRM_ERROR("mwv207: failed to kmalloc native mode\r\n"); + return; + } + mwv207_mode_fixup(m, native); + m = native; + } + + + hblankbegin = 0; + hblankend = m->crtc_htotal - m->crtc_hdisplay; + htotal = m->crtc_htotal; + hsbegin = m->crtc_hsync_start - m->crtc_hdisplay; + hsend = m->crtc_hsync_end - m->crtc_hdisplay; + hhalf = is_interleaved ? (htotal / 2 + hsbegin) : htotal / 2; + + + vblank_1field_begin = 0; + vblank_1field_end = is_interleaved ? + ((m->crtc_vtotal - m->crtc_vdisplay) * 2) + : (m->crtc_vtotal - m->crtc_vdisplay); + + vblank_2field_begin = + (m->crtc_vdisplay + (m->crtc_vtotal - m->crtc_vdisplay)) * 2; + vblank_2field_end = + (m->crtc_vdisplay + (m->crtc_vtotal - m->crtc_vdisplay)) * 2 + + (m->crtc_vtotal - m->crtc_vdisplay + 1) * 2; + + vtotal = m->crtc_vtotal; + vfrontporch = m->crtc_vsync_start - m->crtc_vdisplay; + vsync = m->crtc_vsync_end - m->crtc_vsync_start; + vs_1field_begin = is_interleaved ? (vfrontporch * 2) : (vfrontporch); + vs_1field_end = is_interleaved ? ((vfrontporch + vsync) * 2) + : (vfrontporch + vsync); + vs_2field_begin = + (m->crtc_vdisplay + (m->crtc_vtotal - m->crtc_vdisplay)) * 2 + + vfrontporch * 2 + 1; + vs_2field_end = + (m->crtc_vdisplay + (m->crtc_vtotal - m->crtc_vdisplay)) * 2 + + (vfrontporch + vsync) * 2 + 1; + + + mwv207_va_write(va, 0x400, hsbegin | (hsend << 16)); + mwv207_va_write(va, 0x404, hblankbegin | (hblankend << 16)); + mwv207_va_write(va, 0x408, htotal | (hhalf << 16)); + + + mwv207_va_write(va, 0x40C, vblank_1field_begin | (vblank_1field_end << 16)); + mwv207_va_write(va, 0x458, vblank_1field_begin | (vblank_1field_end << 16)); + mwv207_va_write(va, 0x410, vblank_2field_begin | (vblank_2field_end << 16)); + mwv207_va_write(va, 0x45C, vblank_2field_begin | (vblank_2field_end << 16)); + + mwv207_va_write(va, 0x418, vs_1field_begin | (vs_1field_end << 16)); + mwv207_va_write(va, 0x41C, vs_2field_begin | (vs_2field_end << 16)); + + mwv207_va_modify(va, 0x414, 0xffff, vtotal); + + value = is_interleaved; + if (is_interleaved && m->crtc_hdisplay == 1440) + value |= 0x2; + mwv207_va_write(va, 0x464, value); + + + mwv207_va_write(va, 0x420, 1); + + + value = mwv207_va_read(va, 0x2a0); + value &= ~0x6; + value |= 1; + mwv207_va_write(va, 0x2a0, value); + + kfree(native); +} + +static int mwv207_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct mwv207_va *va = crtc_to_va(crtc); + + jdev_modify(va->jdev, (0x9907f0), + 1 << (12 + va->idx), 1 << (12 + va->idx)); + jdev_modify(va->jdev, (0x9907f8), + 1 << (12 + va->idx), 1 << (12 + va->idx)); + return 0; +} + +static void mwv207_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct mwv207_va *va = crtc_to_va(crtc); + + jdev_modify(va->jdev, (0x9907f0), + 1 << (12 + va->idx), 0 << (12 + va->idx)); + jdev_modify(va->jdev, (0x9907f8), + 1 << (12 + va->idx), 0 << (12 + va->idx)); +} + +static int mwv207_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + return 0; +} + +static void mwv207_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct mwv207_va *va = crtc_to_va(crtc); + struct drm_encoder *encoder; + + drm_for_each_encoder_mask(encoder, crtc->dev, crtc->state->encoder_mask) + mwv207_output_set_crtc(encoder, crtc); + + mwv207_va_config_timing(va); + drm_crtc_vblank_on(crtc); +} + +static void mwv207_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct mwv207_va *va = crtc_to_va(crtc); + + drm_crtc_vblank_off(crtc); + + + mwv207_va_write(va, 0x2c4, 0); + mwv207_va_write(va, 0x2c0, 0); + mwv207_va_write(va, 0x2a0, 2); + mwv207_va_write(va, 0x2c4, 1); + +} + +static void mwv207_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct mwv207_va *va = crtc_to_va(crtc); + struct drm_color_lut *lut; + int i; + + if (crtc->state->color_mgmt_changed) { + if (crtc->state->gamma_lut) { + lut = (struct drm_color_lut *)crtc->state->gamma_lut->data; + for (i = 0; i < 256; i++) { + va->lutdata[i] = drm_color_lut_extract(lut[i].red, 8); + va->lutdata[i + 256] = drm_color_lut_extract(lut[i].green, 8); + va->lutdata[i + 512] = drm_color_lut_extract(lut[i].blue, 8); + } + mwv207_va_lut_enable(va); + } + } +} + + +static void mwv207_va_reset(struct drm_crtc *crtc) +{ + struct mwv207_va *va = crtc_to_va(crtc); + + + mwv207_cursor_switch(va, 0); + + + mwv207_va_lut_enable(va); + mwv207_va_misc_reset(va); + drm_atomic_helper_crtc_reset(crtc); +} + +static const struct drm_crtc_funcs mwv207_crtc_funcs = { + .reset = mwv207_va_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .gamma_set = drm_atomic_helper_legacy_gamma_set, + .page_flip = drm_atomic_helper_page_flip, + .enable_vblank = mwv207_crtc_enable_vblank, + .disable_vblank = mwv207_crtc_disable_vblank, +}; + +static const struct drm_crtc_helper_funcs mwv207_crtc_helper_funcs = { + .atomic_check = mwv207_crtc_atomic_check, + .atomic_flush = mwv207_crtc_atomic_flush, + .atomic_enable = mwv207_crtc_atomic_enable, + .atomic_disable = mwv207_crtc_atomic_disable, +}; + +void mwv207_crtc_prepare_vblank(struct drm_crtc *crtc) +{ + struct drm_pending_vblank_event *event = crtc->state->event; + struct mwv207_va *va = crtc_to_va(crtc); + unsigned long flags; + + if (event) { + if (crtc->state->active) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + spin_lock_irqsave(&crtc->dev->event_lock, flags); + va->event = event; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + } else { + spin_lock_irqsave(&crtc->dev->event_lock, flags); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + } + crtc->state->event = NULL; + } +} + +void mwv207_crtc_finish_vblank(struct drm_crtc *crtc) +{ + struct mwv207_va *va = crtc_to_va(crtc); + int put_vblank = false; + unsigned long flags; + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + if (va->event) { + drm_crtc_send_vblank_event(crtc, va->event); + va->event = NULL; + put_vblank = true; + } + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + + if (put_vblank) + drm_crtc_vblank_put(crtc); +} + +irqreturn_t mwv207_va_handle_vblank(int irq, void *data) +{ + struct mwv207_device *jdev = data; + struct drm_crtc *crtc; + u32 fired; + + + fired = jdev_read(jdev, (0x9907f4)); + if (!fired) + return IRQ_NONE; + jdev_write(jdev, (0x9907f4), fired); + + fired = (fired >> 12) & 0xf; + drm_for_each_crtc(crtc, &jdev->base) { + if (fired & (1 << drm_crtc_index(crtc))) { + drm_crtc_handle_vblank(crtc); + mwv207_crtc_finish_vblank(crtc); + } + } + + return IRQ_HANDLED; +} + +static int mwv207_va_init_single(struct mwv207_device *jdev, int idx) +{ + struct mwv207_va *va; + uint16_t *lut; + int ret, i; + + va = devm_kzalloc(jdev->dev, sizeof(*va), GFP_KERNEL); + if (!va) + return -ENOMEM; + + va->mmio = jdev->mmio + MWV207_VA_BASE(idx); + va->jdev = jdev; + va->idx = idx; + + ret = drm_universal_plane_init(&jdev->base, &va->primary, 1 << idx, + &mwv207_plane_funcs, + rgb_formats, ARRAY_SIZE(rgb_formats), + format_modifiers, DRM_PLANE_TYPE_PRIMARY, "primary_plane_%d", idx); + if (ret) + return ret; + ret = drm_universal_plane_init(&jdev->base, &va->cursor, 1 << idx, + &mwv207_plane_funcs, + cursor_formats, ARRAY_SIZE(cursor_formats), + format_modifiers, DRM_PLANE_TYPE_CURSOR, "cursor_%d", idx); + if (ret) + return ret; + + drm_plane_helper_add(&va->primary, &mwv207_primary_helper_funcs); + drm_plane_helper_add(&va->cursor, &mwv207_cursor_helper_funcs); + + ret = drm_crtc_init_with_planes(&jdev->base, &va->crtc, + &va->primary, &va->cursor, &mwv207_crtc_funcs, "crtc_%d", + idx); + if (ret) + return ret; + + drm_crtc_helper_add(&va->crtc, &mwv207_crtc_helper_funcs); + + lut = &va->lutdata[0]; + for (i = 0; i < 256; i++) + lut[i] = lut[i + 256] = lut[i + 512] = i; + drm_crtc_enable_color_mgmt(&va->crtc, 768, true, 768); + ret = drm_mode_crtc_set_gamma_size(&va->crtc, 256); + + + BUG_ON(drm_crtc_index(&va->crtc) != va->idx); + + return ret; +} + +int mwv207_va_init(struct mwv207_device *jdev) +{ + int i, nr, ret; + + nr = jdev->lite ? 2 : 4; + for (i = 0; i < nr; i++) { + ret = mwv207_va_init_single(jdev, i); + if (ret) + return ret; + } + + return 0; +} diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_va.h b/drivers/gpu/drm/mwv207/dc/mwv207_va.h new file mode 100644 index 0000000000000000000000000000000000000000..ffc3540f7a35ffaa020384b922b912a5f864c7c8 --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_va.h @@ -0,0 +1,29 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#ifndef MWV207_VA_H_BSY8LF4F +#define MWV207_VA_H_BSY8LF4F + +struct drm_crtc; +/* Note: any resources allocated should be: + * 1. devres managed or + * 2. cleaned up by drm_mode_config_cleanup + */ +int mwv207_va_init(struct mwv207_device *jdev); + +void mwv207_crtc_prepare_vblank(struct drm_crtc *crtc); +irqreturn_t mwv207_va_handle_vblank(int irq, void *data); +#endif diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_vga.c b/drivers/gpu/drm/mwv207/dc/mwv207_vga.c new file mode 100644 index 0000000000000000000000000000000000000000..dc2e1521303dabf0d9463780fafd11e80ee3dd70 --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_vga.c @@ -0,0 +1,289 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include "mwv207_vi.h" + +#define MWV207_DAC_HPD_VS_CLC(_chan) (0x64 + (_chan) * 0x14) +#define MWV207_DAC_HPD_PULSE_H(_chan) (0x68 + (_chan) * 0x14) +#define MWV207_DAC_HPD_PULSE_L(_chan) (0x6C + (_chan) * 0x14) +#define MWV207_DAC_HPD_DET_PER(_chan) (0x70 + (_chan) * 0x14) +#define MWV207_DAC_HPD_DET_THR(_chan) (0x74 + (_chan) * 0x14) + +#define output_to_vga(output) container_of(output, struct mwv207_vga, base) + +static uint mwv207_vga_load_detect = 1; +module_param(mwv207_vga_load_detect, uint, 0444); +MODULE_PARM_DESC(mwv207_vga_load_detect, "Enable vga load detect if set to 1, default is 1"); + +struct mwv207_vga { + struct mwv207_output base; + spinlock_t load_detect_lock; + bool active; +}; + +static void mwv207_vga_set_detect_mode(struct mwv207_output *output, bool active) +{ + u32 val, status, load_mode; + int i; + + mwv207_output_write(output, 0xA4, 0); + + val = 0x3ff * 7 / 10; + status = mwv207_output_read(output, 0xA8); + mwv207_output_write(output, 0xA8, status); + + status = val << 20 | val << 10 | val; + mwv207_output_write(output, 0xAC, status); + + load_mode = active ? 1 : 0; + status = load_mode << 20 | load_mode << 16 | load_mode << 12 | 0x1 << 8 | 0x1 << 4 | 0x1; + mwv207_output_write(output, 0xAC, status); + + for (i = 0; i < 3; i++) { + if (active) { + mwv207_output_write(output, MWV207_DAC_HPD_VS_CLC(i), 256); + mwv207_output_write(output, MWV207_DAC_HPD_PULSE_H(i), 48); + mwv207_output_write(output, MWV207_DAC_HPD_DET_PER(i), 188); + mwv207_output_write(output, MWV207_DAC_HPD_DET_THR(i), 164); + } else { + mwv207_output_write(output, MWV207_DAC_HPD_PULSE_H(i), 1600000); + mwv207_output_write(output, MWV207_DAC_HPD_PULSE_L(i), 798400000); + mwv207_output_write(output, MWV207_DAC_HPD_DET_PER(i), 1600128); + mwv207_output_write(output, MWV207_DAC_HPD_DET_THR(i), 8128); + } + } + + mwv207_output_write(output, 0xA4, 0x111111); +} + +static void mwv207_vga_switch(struct mwv207_output *output, bool on) +{ + struct mwv207_vga *vga = output_to_vga(output); + + if (!output->jdev->lite || !mwv207_vga_load_detect) { + mwv207_output_modify(output, 0x0, 1 << 31, (on ? 1 : 0) << 31); + return; + } + + spin_lock(&vga->load_detect_lock); + mwv207_output_modify(output, 0x0, 1 << 31, (on ? 1 : 0) << 31); + mwv207_vga_set_detect_mode(output, on); + vga->active = on; + spin_unlock(&vga->load_detect_lock); +} + +static void mwv207_vga_config(struct mwv207_output *output) +{ + struct drm_display_mode *mode = &output->cur_crtc->state->adjusted_mode; + int hpol, vpol; + + hpol = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : 1; + vpol = (mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : 1; + + mwv207_output_write(output, 0x4C, 0); + mwv207_output_modify(output, 0x0, 0x7 << 16, 0x7 << 16); + mwv207_output_modify(output, 0x0, 0x1 << 2, hpol << 2); + mwv207_output_modify(output, 0x0, 0x1 << 3, vpol << 3); + + mwv207_output_write(output, 0x4, 0x1666); + mwv207_output_write(output, 0x18, 0x1666); + mwv207_output_write(output, 0x2C, 0x1666); +} + +static void mwv207_vga_select_crtc(struct mwv207_output *output) +{ + + mwv207_output_modify(output, 0x0, 0x3 << 24, + drm_crtc_index(output->cur_crtc) << 24); + + jdev_modify(output->jdev, 0x9b0038, 0xf << 16, + drm_crtc_index(output->cur_crtc) << 16); +} +static enum drm_mode_status mwv207_vga_mode_valid(struct mwv207_output *output, + const struct drm_display_mode *mode) +{ + + if (mode->clock > 193250) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static enum drm_mode_status mwv207_vga_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return mwv207_vga_mode_valid(connector_to_output(connector), mode); +} + +static int mwv207_vga_detect_load(struct mwv207_output *output) +{ + struct mwv207_vga *vga = output_to_vga(output); + u32 r_state, g_state, b_state, value; + bool active; + + + if (mwv207_i2c_probe(output->ddc)) + return connector_status_connected; + + spin_lock(&vga->load_detect_lock); + value = mwv207_output_read(output, 0xA8); + active = vga->active; + spin_unlock(&vga->load_detect_lock); + + r_state = (value >> 8) & 0x1; + g_state = (value >> 4) & 0x1; + b_state = (value >> 0) & 0x1; + DRM_DEBUG_DRIVER("active %d r_state = 0x%x, g_state = 0x%x, b_state = 0x%x", + active, r_state, g_state, b_state); + + if (active) + return (r_state && g_state && b_state) ? + connector_status_disconnected : connector_status_connected; + + return (r_state || g_state || b_state) ? + connector_status_connected : connector_status_disconnected; +} + +static int mwv207_vga_detect_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct mwv207_output *output = connector_to_output(connector); + + if (!output->jdev->lite || !mwv207_vga_load_detect) + return mwv207_i2c_probe(output->ddc) ? + connector_status_connected : connector_status_disconnected; + + + return mwv207_vga_detect_load(output); +} + +static void mwv207_vga_destroy(struct drm_connector *conn) +{ + drm_connector_unregister(conn); + drm_connector_cleanup(conn); +} + +static const struct drm_connector_helper_funcs mwv207_vga_connector_helper_funcs = { + .get_modes = mwv207_output_get_modes, + .mode_valid = mwv207_vga_connector_mode_valid, + .detect_ctx = mwv207_vga_detect_ctx +}; + +static const struct drm_connector_funcs mwv207_vga_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .destroy = mwv207_vga_destroy, + .late_register = mwv207_output_late_register, + .early_unregister = mwv207_output_early_unregister, +}; + +static enum drm_mode_status mwv207_vga_encoder_mode_valid(struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + struct mwv207_output *output = encoder_to_output(encoder); + + return mwv207_vga_mode_valid(output, mode); +} + +static int mwv207_vga_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + return 0; +} + +static void mwv207_vga_encoder_enable(struct drm_encoder *encoder) +{ + struct mwv207_output *output = encoder_to_output(encoder); + + mwv207_vga_select_crtc(output); + + mwv207_vga_config(output); + + mwv207_vga_switch(output, true); +} + +static void mwv207_vga_encoder_disable(struct drm_encoder *encoder) +{ + struct mwv207_output *output = encoder_to_output(encoder); + + mwv207_vga_switch(output, false); +} + +static void mwv207_vga_encoder_reset(struct drm_encoder *encoder) +{ + struct mwv207_output *output = encoder_to_output(encoder); + + if (output->jdev->lite && !mwv207_vga_load_detect) { + mwv207_output_write(output, 0xA4, 0); + mwv207_output_modify(output, 0x58, 0x1 << 16, 0); + } + + mwv207_vga_encoder_disable(encoder); +} + +static const struct drm_encoder_funcs mwv207_vga_encoder_funcs = { + .destroy = drm_encoder_cleanup, + .reset = mwv207_vga_encoder_reset, +}; + +static const struct drm_encoder_helper_funcs mwv207_vga_encoder_helper_funcs = { + .mode_valid = mwv207_vga_encoder_mode_valid, + .atomic_check = mwv207_vga_encoder_atomic_check, + .enable = mwv207_vga_encoder_enable, + .disable = mwv207_vga_encoder_disable, +}; + +int mwv207_vga_init(struct mwv207_device *jdev) +{ + struct mwv207_output *output; + struct mwv207_vga *vga; + int ret; + + vga = devm_kzalloc(jdev->dev, sizeof(*vga), GFP_KERNEL); + if (!vga) + return -ENOMEM; + + spin_lock_init(&vga->load_detect_lock); + output = &vga->base; + output->jdev = jdev; + output->idx = 0; + output->mmio = jdev->mmio + 0x9a0000; + output->i2c_chan = 4; + + output->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + ret = drm_connector_init(&jdev->base, &output->connector, + &mwv207_vga_connector_funcs, DRM_MODE_CONNECTOR_VGA); + if (ret) + return ret; + drm_connector_helper_add(&output->connector, &mwv207_vga_connector_helper_funcs); + + output->encoder.possible_crtcs = (1 << jdev->base.mode_config.num_crtc) - 1; + ret = drm_encoder_init(&jdev->base, &output->encoder, + &mwv207_vga_encoder_funcs, DRM_MODE_ENCODER_DAC, + "vga-%d", output->idx); + if (ret) + return ret; + drm_encoder_helper_add(&output->encoder, &mwv207_vga_encoder_helper_funcs); + + ret = drm_connector_attach_encoder(&output->connector, &output->encoder); + if (ret) + return ret; + + return drm_connector_register(&output->connector); +} diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_vi.c b/drivers/gpu/drm/mwv207/dc/mwv207_vi.c new file mode 100644 index 0000000000000000000000000000000000000000..92346d85eaa62c0114c234f38b3dc7d1aae7ef77 --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_vi.c @@ -0,0 +1,89 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include "mwv207_vi.h" + +/* userspace interfaces should be registered here, e.g i2c/backlight etc */ +int mwv207_output_late_register(struct drm_connector *connector) +{ + struct mwv207_output *output = connector_to_output(connector); + + output->ddc = mwv207_i2c_create(output->jdev, output->i2c_chan); + if (!output->ddc) { + DRM_ERROR("Failed to create i2c adapter\n"); + return -ENODEV; + } + + return 0; +} + +void mwv207_output_early_unregister(struct drm_connector *connector) +{ + struct mwv207_output *output = connector_to_output(connector); + + mwv207_i2c_destroy(output->ddc); +} + + +int mwv207_output_get_modes(struct drm_connector *connector) +{ + struct mwv207_output *output = connector_to_output(connector); + struct edid *edid; + int count; + + if (output->edidforce && output->edid) + edid = output->edid; + else + edid = drm_get_edid(connector, output->ddc); + + + output->has_audio = drm_detect_monitor_audio(edid); + drm_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + + if (!(output->edidforce && output->edid)) + kfree(edid); + + return count; +} + +void mwv207_output_set_crtc(struct drm_encoder *encoder, struct drm_crtc *crtc) +{ + struct mwv207_output *output = encoder_to_output(encoder); + + output->cur_crtc = crtc; +} + +int mwv207_vi_init(struct mwv207_device *jdev) +{ + int ret; + + mutex_init(&jdev->gpio_lock); + + ret = mwv207_edp_init(jdev); + if (ret) + return ret; + ret = mwv207_hdmi_init(jdev); + if (ret) + return ret; + ret = mwv207_vga_init(jdev); + if (ret) + return ret; + ret = mwv207_dvo_init(jdev); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/gpu/drm/mwv207/dc/mwv207_vi.h b/drivers/gpu/drm/mwv207/dc/mwv207_vi.h new file mode 100644 index 0000000000000000000000000000000000000000..95f78ded017f05e4a3d960d17ebb5ee1e8fa0de8 --- /dev/null +++ b/drivers/gpu/drm/mwv207/dc/mwv207_vi.h @@ -0,0 +1,101 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include "mwv207.h" +#include +#include +#include +#include +#include + +#ifndef MWV207_DAL_H_OUVHEJAX +#define MWV207_DAL_H_OUVHEJAX + + + +struct i2c_adapter; + + +struct mwv207_output { + struct drm_connector connector; + struct drm_encoder encoder; + + + struct drm_crtc *cur_crtc; + + /* mapped to video interface global control regs + * which are shared among all outputs + */ + void __iomem *mmio; + struct mwv207_device *jdev; + + struct i2c_adapter *ddc; + struct edid *edid; + bool edidforce; + int i2c_chan; + + /* local index, e.g. hdmi0/1/2/3, edp0/1 */ + int idx; + + bool has_audio; +}; +#define connector_to_output(conn) container_of(conn, struct mwv207_output, connector) +#define encoder_to_output(encoder) container_of(encoder, struct mwv207_output, encoder) + +/* Note: any resources allocated by following funcs should be: + * 1. devres managed or + * 2. cleaned up by drm_mode_config_cleanup + */ +int mwv207_vi_init(struct mwv207_device *jdev); +int mwv207_edp_init(struct mwv207_device *jdev); +int mwv207_hdmi_init(struct mwv207_device *jdev); +int mwv207_vga_init(struct mwv207_device *jdev); +int mwv207_dvo_init(struct mwv207_device *jdev); + +struct i2c_adapter *mwv207_i2c_create(struct mwv207_device *jdev, int i2c_chan); +void mwv207_i2c_destroy(struct i2c_adapter *adapter); +bool mwv207_i2c_probe(struct i2c_adapter *i2c_bus); + +int mwv207_output_late_register(struct drm_connector *connector); +void mwv207_output_early_unregister(struct drm_connector *connector); + + +int mwv207_output_get_modes(struct drm_connector *connector); +void mwv207_output_set_crtc(struct drm_encoder *encoder, struct drm_crtc *crtc); + +static inline u32 mwv207_output_read(struct mwv207_output *output, u32 reg) +{ + BUG_ON(reg >= 0x8000); + return readl(output->mmio + reg); +} + +static inline void mwv207_output_write(struct mwv207_output *output, u32 reg, u32 value) +{ + BUG_ON(reg >= 0x8000); + writel_relaxed(value, output->mmio + reg); +} + +static inline void mwv207_output_modify(struct mwv207_output *output, u32 reg, + u32 mask, u32 value) +{ + u32 rvalue; + + BUG_ON(reg >= 0x8000); + rvalue = mwv207_output_read(output, reg); + rvalue = (rvalue & ~mask) | (value & mask); + mwv207_output_write(output, reg, rvalue); +} + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207.h b/drivers/gpu/drm/mwv207/mwv207.h new file mode 100644 index 0000000000000000000000000000000000000000..84419cc4977d7365cf47d01de9a7dad7b278e3e4 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207.h @@ -0,0 +1,127 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef MWV207_H_VTIQLF2Y +#define MWV207_H_VTIQLF2Y + +#include +#include +#include +#include +#include + +struct mwv207_db; + +struct mwv207_vbios { + + struct mutex vcmd_lock; + + + struct mutex cfg_lock; + struct idr cfg_table; + + int indexer_valid; + int sector_count; +}; + +#define ddev_to_jdev(ddev) ((struct mwv207_device *) (ddev)->dev_private) +#define bdev_to_jdev(dev) container_of(dev, struct mwv207_device, bdev) +struct mwv207_device { + struct drm_device base; + struct ttm_bo_device bdev; + + void __iomem *mmio; + void __iomem *iatu; + + void *win; + + + spinlock_t win_lock; + u64 visible_vram_size; + u64 vram_size; + u64 vram_bar_base; + u64 vram_last_win; + u64 pci_win_base; + + struct device *dev; + struct drm_fb_helper *fb_helper; + + struct drm_gpu_scheduler *sched[6]; + + struct drm_gpu_scheduler **sched_3d; + struct drm_gpu_scheduler **sched_dec; + struct drm_gpu_scheduler **sched_enc; + struct drm_gpu_scheduler **sched_2d; + struct drm_gpu_scheduler **sched_dma; + int nr_3d; + int nr_2d; + int nr_dec; + int nr_enc; + int nr_dma; + + + struct drm_sched_entity *dma_entity; + + + spinlock_t irq_lock; + struct irq_domain *irq_domain; + u32 irq_enable_reg[2]; + int va_irq; + + /* key/value database */ + struct mwv207_db *db; + + struct mwv207_vbios vbios; + + struct mutex gpio_lock; + + bool lite; +}; + +struct mwv207_ctx_mgr { + struct mutex lock; + struct idr handle_table; +}; + +struct mwv207_fpriv { + struct mwv207_ctx_mgr ctx_mgr; +}; + +int mwv207_test(struct mwv207_device *jdev); +void jdev_write_vram(struct mwv207_device *jdev, u64 vram_addr, void *buf, int size); + +static inline void jdev_write(struct mwv207_device *jdev, u32 reg, u32 value) +{ + BUG_ON(reg <= 0x100000); + writel_relaxed(value, jdev->mmio + reg); +} + +static inline u32 jdev_read(struct mwv207_device *jdev, u32 reg) +{ + return readl(jdev->mmio + reg); +} + +static inline void jdev_modify(struct mwv207_device *jdev, u32 reg, u32 mask, u32 value) +{ + u32 rvalue = jdev_read(jdev, reg); + + rvalue = (rvalue & ~mask) | (value & mask); + jdev_write(jdev, reg, rvalue); + BUG_ON(reg <= 0x100000); +} + +#define to_fpriv(drm_file) ((drm_file)->driver_priv) + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_bo.c b/drivers/gpu/drm/mwv207/mwv207_bo.c new file mode 100644 index 0000000000000000000000000000000000000000..ef63abedc3e47e4eb08b23fb388cf309b4e90aa0 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_bo.c @@ -0,0 +1,299 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include "mwv207_drm.h" +#include "mwv207_bo.h" + +static void mwv207_ttm_bo_destroy(struct ttm_buffer_object *bo) +{ + struct mwv207_bo *jbo = to_jbo(bo); + + WARN_ON_ONCE(jbo->map_count > 0); + if (bo->base.import_attach) + drm_prime_gem_destroy(&bo->base, NULL); + drm_gem_object_release(&jbo->tbo.base); + kfree(jbo); +} + +bool mwv207_ttm_bo_is_mwv207_bo(struct ttm_buffer_object *jbo) +{ + if (jbo->destroy == &mwv207_ttm_bo_destroy) + return true; + return false; +} + +void mwv207_bo_placement_from_domain(struct mwv207_bo *jbo, u32 domain, bool pinned) +{ + struct mwv207_device *jdev = bdev_to_jdev(jbo->tbo.bdev); + u32 c = 0; + u32 pflag = 0; + + if (pinned) + pflag |= TTM_PL_FLAG_NO_EVICT; + + jbo->placement.placement = jbo->placements; + jbo->placement.busy_placement = jbo->placements; + + if (domain & 0x2) { + jbo->placements[c].fpfn = 0; + + if (jbo->flags & (1<<2)) + jbo->placements[c].lpfn = 0x100000000ul >> PAGE_SHIFT; + else + jbo->placements[c].lpfn = 0; + + if (jbo->flags & (1<<0)) + jbo->placements[c].lpfn = jdev->visible_vram_size >> PAGE_SHIFT; + else + pflag |= TTM_PL_FLAG_TOPDOWN; + + jbo->placements[c].mem_type = TTM_PL_VRAM; + jbo->placements[c].flags = TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_WC | pflag; +#ifdef MWV207_DEBUG_BO_MIGRATION + if (!pinned) { + if (jbo->placements[c].lpfn == 0 + || jbo->placements[c].lpfn >= jdev->vram_size >> PAGE_SHIFT) + jbo->placements[c].lpfn = jdev->vram_size >> PAGE_SHIFT; + if (jbo->toggle & 0x1) + jbo->placements[c].fpfn = jbo->placements[c].lpfn / 2; + else + jbo->placements[c].lpfn = jbo->placements[c].lpfn / 2; + jbo->toggle ^= 0x1; + } +#endif + c++; + } + if (domain & 0x4) { + jbo->placements[c].fpfn = 0; + jbo->placements[c].lpfn = 0; + jbo->placements[c].flags = TTM_PL_MASK_CACHING | pflag; + jbo->placements[c].mem_type = TTM_PL_TT; + c++; + } + if (domain & 0x1) { + jbo->placements[c].fpfn = 0; + jbo->placements[c].lpfn = 0; + jbo->placements[c].flags = TTM_PL_MASK_CACHING | pflag; + jbo->placements[c].mem_type = TTM_PL_SYSTEM; + c++; + } + if (!c) { + jbo->placements[c].fpfn = 0; + jbo->placements[c].lpfn = 0; + jbo->placements[c].flags = TTM_PL_MASK_CACHING; + jbo->placements[c].mem_type = TTM_PL_SYSTEM; + c++; + } + + BUG_ON(c > 3); + jbo->placement.num_placement = c; + jbo->placement.num_busy_placement = c; +} + +int mwv207_bo_create(struct mwv207_device *jdev, + u64 size, u64 align, enum ttm_bo_type type, + u32 domain, u32 flags, + struct mwv207_bo **pbo) +{ + struct mwv207_bo *jbo; + unsigned long acc_size; + int ret; + + *pbo = NULL; + if ((flags & (1<<2)) && !(domain & 0x2)) + return -EINVAL; + + jbo = kzalloc(sizeof(struct mwv207_bo), GFP_KERNEL); + if (jbo == NULL) + return -ENOMEM; + size = roundup(size, PAGE_SIZE); + ret = drm_gem_object_init(&jdev->base, &jbo->tbo.base, size); + if (unlikely(ret)) { + kfree(jbo); + return ret; + } + jbo->flags = flags; + jbo->domain = domain; + jbo->pin_count = 0; + + acc_size = ttm_bo_dma_acc_size(&jdev->bdev, size, sizeof(*jbo)); + + jbo->tbo.bdev = &jdev->bdev; + mwv207_bo_placement_from_domain(jbo, domain, false); + + ret = ttm_bo_init(&jdev->bdev, &jbo->tbo, size, type, + &jbo->placement, align >> PAGE_SHIFT, + type != ttm_bo_type_kernel, acc_size, + NULL, NULL, &mwv207_ttm_bo_destroy); + + if (unlikely(ret)) + return ret; + *pbo = jbo; + return 0; +} + +int mwv207_bo_reserve(struct mwv207_bo *jbo, bool no_intr) +{ + int ret; + + ret = ttm_bo_reserve(&jbo->tbo, !no_intr, false, NULL); + if (unlikely(ret)) { + if (ret != -ERESTARTSYS) + DRM_ERROR("bo reserve failed\n"); + return ret; + } + return 0; +} + +void mwv207_bo_unreserve(struct mwv207_bo *jbo) +{ + ttm_bo_unreserve(&jbo->tbo); +} + +int mwv207_bo_kmap_reserved(struct mwv207_bo *jbo, void **ptr) +{ + bool is_iomem; + int ret; + + if (!jbo->flags & (1<<0)) + return -EPERM; + + if (jbo->kptr) { + if (ptr) + *ptr = jbo->kptr; + jbo->map_count++; + return 0; + } + ret = ttm_bo_kmap(&jbo->tbo, 0, jbo->tbo.num_pages, &jbo->kmap); + if (ret) + return ret; + jbo->kptr = ttm_kmap_obj_virtual(&jbo->kmap, &is_iomem); + if (ptr) + *ptr = jbo->kptr; + jbo->map_count = 1; + return 0; +} + +void mwv207_bo_kunmap_reserved(struct mwv207_bo *jbo) +{ + if (WARN_ON_ONCE(jbo->kptr == NULL)) + return; + jbo->map_count--; + if (jbo->map_count > 0) + return; + jbo->kptr = NULL; + ttm_bo_kunmap(&jbo->kmap); +} + +void mwv207_bo_unref(struct mwv207_bo *jbo) +{ + if (jbo == NULL) + return; + + ttm_bo_put(&jbo->tbo); +} + +struct mwv207_bo *mwv207_bo_ref(struct mwv207_bo *jbo) +{ + if (jbo == NULL) + return NULL; + + ttm_bo_get(&jbo->tbo); + return jbo; +} + +int mwv207_bo_pin_reserved(struct mwv207_bo *jbo, u32 domain) +{ + struct ttm_operation_ctx ctx = {false, false}; + int ret; + + if (jbo->pin_count) { + jbo->pin_count++; + return 0; + } + + jbo->flags |= (1<<2); + mwv207_bo_placement_from_domain(jbo, domain, true); + ret = ttm_bo_validate(&jbo->tbo, &jbo->placement, &ctx); + if (ret) + return ret; + + jbo->pin_count = 1; + + return 0; +} + +int mwv207_bo_unpin_reserved(struct mwv207_bo *jbo) +{ + struct ttm_operation_ctx ctx = {false, false}; + int ret, i; + + if (WARN_ON_ONCE(!jbo->pin_count)) + return 0; + + jbo->pin_count--; + if (jbo->pin_count) + return 0; + + for (i = 0; i < jbo->placement.num_placement; i++) + jbo->placements[i].flags &= ~TTM_PL_FLAG_NO_EVICT; + + ret = ttm_bo_validate(&jbo->tbo, &jbo->placement, &ctx); + if (unlikely(ret)) + DRM_ERROR("validate failed for unpin"); + + return ret; +} + +int mwv207_bo_pin(struct mwv207_bo *jbo, u32 domain) +{ + int ret; + + ret = mwv207_bo_reserve(jbo, true); + if (ret) + return ret; + ret = mwv207_bo_pin_reserved(jbo, domain); + mwv207_bo_unreserve(jbo); + return ret; +} + +int mwv207_bo_unpin(struct mwv207_bo *jbo) +{ + int ret; + + ret = mwv207_bo_reserve(jbo, true); + if (ret) + return ret; + ret = mwv207_bo_unpin_reserved(jbo); + mwv207_bo_unreserve(jbo); + return ret; +} + +int mwv207_bo_wait(struct mwv207_bo *jbo, bool no_wait) +{ + int ret; + + ret = ttm_bo_reserve(&jbo->tbo, true, no_wait, NULL); + if (unlikely(ret)) { + if (ret != -ERESTARTSYS && ret != -EBUSY) + DRM_ERROR("bo reserve failed for wait\n"); + return ret; + } + ret = ttm_bo_wait(&jbo->tbo, true, no_wait); + ttm_bo_unreserve(&jbo->tbo); + return ret; +} diff --git a/drivers/gpu/drm/mwv207/mwv207_bo.h b/drivers/gpu/drm/mwv207/mwv207_bo.h new file mode 100644 index 0000000000000000000000000000000000000000..ab10994d19cb6c9fd7bf57a2cd249dbae14e30ff --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_bo.h @@ -0,0 +1,111 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef MWV207_BO_H_T4ETFCB2 +#define MWV207_BO_H_T4ETFCB2 +#include +#include +#include +#include +#include +#include +#include +#include "mwv207.h" +#include "mwv207_drm.h" + +#undef MWV207_DEBUG_BO_MIGRATION + +#define to_jbo(bo) container_of(bo, struct mwv207_bo, tbo) +struct mwv207_bo { + struct ttm_buffer_object tbo; + struct ttm_placement placement; + struct ttm_bo_kmap_obj kmap; + struct ttm_place placements[3]; + u32 domain; + u32 flags; + int pin_count; + int map_count; + void *kptr; +#ifdef MWV207_DEBUG_BO_MIGRATION + int toggle; +#endif +}; + +struct mwv207_ttm_tt { + struct ttm_dma_tt ttm; + struct mwv207_device *jdev; +}; +#define to_gtt(ttm_tt) container_of(ttm_tt, struct mwv207_ttm_tt, ttm.ttm) + +static inline struct mwv207_bo *mwv207_bo_from_gem(struct drm_gem_object *gobj) +{ + struct ttm_buffer_object *tbo = container_of(gobj, struct ttm_buffer_object, base); + + return container_of(tbo, struct mwv207_bo, tbo); +} + +static inline struct drm_gem_object *mwv207_gem_from_bo(struct mwv207_bo *jbo) +{ + return &jbo->tbo.base; +} + +static inline u64 mwv207_bo_size(struct mwv207_bo *jbo) +{ + return jbo->tbo.num_pages << PAGE_SHIFT; +} + +static inline u64 mwv207_bo_mmap_offset(struct drm_gem_object *gobj) +{ + struct mwv207_bo *jbo = mwv207_bo_from_gem(gobj); + + return drm_vma_node_offset_addr(&jbo->tbo.base.vma_node); +} + +static inline u64 mwv207_bo_gpu_phys(struct mwv207_bo *jbo) +{ + return jbo->tbo.mem.start << PAGE_SHIFT; +} + +void mwv207_bo_placement_from_domain(struct mwv207_bo *jbo, u32 domain, bool pinned); + +bool mwv207_ttm_bo_is_mwv207_bo(struct ttm_buffer_object *tbo); +void mwv207_bo_placement(struct mwv207_bo *jbo, int domain); + +int mwv207_bo_create(struct mwv207_device *jdev, + u64 size, u64 align, enum ttm_bo_type type, + u32 domain, u32 flags, + struct mwv207_bo **pbo); + +int mwv207_bo_reserve(struct mwv207_bo *jbo, bool no_intr); +void mwv207_bo_unreserve(struct mwv207_bo *jbo); + +int mwv207_bo_pin_reserved(struct mwv207_bo *jbo, u32 domain); +int mwv207_bo_unpin_reserved(struct mwv207_bo *jbo); + +int mwv207_bo_pin(struct mwv207_bo *jbo, u32 domain); +int mwv207_bo_unpin(struct mwv207_bo *jbo); + +int mwv207_bo_kmap_reserved(struct mwv207_bo *jbo, void **ptr); +void mwv207_bo_kunmap_reserved(struct mwv207_bo *jbo); + +struct mwv207_bo *mwv207_bo_ref(struct mwv207_bo *jbo); +void mwv207_bo_unref(struct mwv207_bo *jbo); + +int mwv207_ttm_init(struct mwv207_device *jdev); +void mwv207_ttm_fini(struct mwv207_device *jdev); + +int mwv207_bo_wait(struct mwv207_bo *jbo, bool no_wait); + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_ctx.c b/drivers/gpu/drm/mwv207/mwv207_ctx.c new file mode 100644 index 0000000000000000000000000000000000000000..595d6a943b4712a022d247f001ff957dd1043de7 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_ctx.c @@ -0,0 +1,242 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include "mwv207.h" +#include "mwv207_drm.h" +#include "mwv207_ctx.h" + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +struct mwv207_ctx *mwv207_ctx_lookup(struct drm_device *dev, + struct drm_file *filp, u32 handle) +{ + struct mwv207_fpriv *fpriv = to_fpriv(filp); + struct mwv207_ctx_mgr *mgr = &fpriv->ctx_mgr; + struct mwv207_ctx *ctx; + + mutex_lock(&mgr->lock); + ctx = idr_find(&mgr->handle_table, handle); + if (ctx) + kref_get(&ctx->refcnt); + mutex_unlock(&mgr->lock); + + return ctx; +} + +static void mwv207_ctx_entity_fini(struct mwv207_ctx *ctx) +{ + int i; + + for (i = 0; i < 11; i++) { + if (ctx->entities[i]) { + drm_sched_entity_destroy(ctx->entities[i]); + kfree(ctx->entities[i]); + ctx->entities[i] = NULL; + } + } +} + + +static int mwv207_ctx_entity_init_single(struct mwv207_ctx *ctx, + struct drm_gpu_scheduler **sched, + struct drm_sched_entity **entities, int nr) +{ + struct drm_gpu_scheduler *scheds[6]; + struct drm_sched_entity *entity; + int i, ret; + + + for (i = 0; i < nr; i++) { + scheds[i] = sched[i]; + entity = kmalloc(sizeof(struct drm_sched_entity), GFP_KERNEL); + if (!entity) + return -ENOMEM; + ret = drm_sched_entity_init(entity, DRM_SCHED_PRIORITY_NORMAL, &scheds[i], 1, &ctx->guilty); + if (ret) { + kfree(entity); + return ret; + } + entities[i] = entity; + } + if (i) { + + entity = kmalloc(sizeof(struct drm_sched_entity), GFP_KERNEL); + if (!entity) + return -ENOMEM; + ret = drm_sched_entity_init(entity, DRM_SCHED_PRIORITY_NORMAL, &scheds[0], 1, &ctx->guilty); + if (ret) { + kfree(entity); + return ret; + } + entities[i] = entity; + } + + return 0; +} + +static int mwv207_ctx_entity_init(struct drm_device *dev, struct mwv207_ctx *ctx) +{ + struct mwv207_device *jdev = ddev_to_jdev(dev); + int ret; + + + ctx->entity_3d = &ctx->entities[0]; + ctx->entity_dec = &ctx->entity_3d[2 + 1]; + ctx->entity_enc = &ctx->entity_dec[1 + 1]; + ctx->entity_2d = &ctx->entity_enc[1 + 1]; + ctx->entity_dma = &ctx->entity_2d[1 + 1]; + BUG_ON(ctx->entity_dma[1] != ctx->entities[11 - 1]); + + ret = mwv207_ctx_entity_init_single(ctx, jdev->sched_3d, ctx->entity_3d, jdev->nr_3d); + if (ret) + goto fini; + ret = mwv207_ctx_entity_init_single(ctx, jdev->sched_dec, ctx->entity_dec, jdev->nr_dec); + if (ret) + goto fini; + ret = mwv207_ctx_entity_init_single(ctx, jdev->sched_enc, ctx->entity_enc, jdev->nr_enc); + if (ret) + goto fini; + ret = mwv207_ctx_entity_init_single(ctx, jdev->sched_2d, ctx->entity_2d, jdev->nr_2d); + if (ret) + goto fini; + ret = mwv207_ctx_entity_init_single(ctx, jdev->sched_dma, ctx->entity_dma, jdev->nr_dma); + if (ret) + goto fini; + return 0; +fini: + mwv207_ctx_entity_fini(ctx); + return ret; +} + +static int mwv207_ctx_create(struct drm_device *dev, struct mwv207_ctx_mgr *mgr, + u32 flags, u32 *handle) +{ + struct mwv207_ctx *ctx; + int ret; + + ctx = kzalloc(sizeof(struct mwv207_ctx), GFP_KERNEL); + if (ctx == NULL) + return -ENOMEM; + + ctx->jdev = ddev_to_jdev(dev); + kref_init(&ctx->refcnt); + + ret = mwv207_ctx_entity_init(dev, ctx); + if (ret) + goto out_free; + + mutex_lock(&mgr->lock); + ret = idr_alloc(&mgr->handle_table, ctx, 1, 0, GFP_KERNEL); + mutex_unlock(&mgr->lock); + + if (ret < 0) + goto out_fini_entites; + + *handle = ret; + return 0; +out_fini_entites: + mwv207_ctx_entity_fini(ctx); +out_free: + kfree(ctx); + return ret; +} + +static void mwv207_ctx_release(struct kref *kref) +{ + struct mwv207_ctx *ctx = container_of(kref, struct mwv207_ctx, refcnt); + + mwv207_ctx_entity_fini(ctx); + kfree(ctx); +} + +int mwv207_ctx_put(struct mwv207_ctx *ctx) +{ + return kref_put(&ctx->refcnt, mwv207_ctx_release); +} + +static int mwv207_ctx_destroy(struct drm_device *dev, struct mwv207_ctx_mgr *mgr, u32 handle) +{ + struct mwv207_ctx *ctx; + + mutex_lock(&mgr->lock); + ctx = idr_remove(&mgr->handle_table, handle); + mutex_unlock(&mgr->lock); + + if (ctx == NULL) + return -EINVAL; + + mwv207_ctx_put(ctx); + return 0; +} + +int mwv207_ctx_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct drm_mwv207_ctx *args = (struct drm_mwv207_ctx *)data; + struct mwv207_fpriv *fpriv = to_fpriv(filp); + + if (args->resv || args->flags) + return -EINVAL; + + switch (args->op) { + case 0: + return mwv207_ctx_create(dev, &fpriv->ctx_mgr, args->flags, &args->handle); + case 1: + return mwv207_ctx_destroy(dev, &fpriv->ctx_mgr, args->handle); + default: + return -EINVAL; + } +} + +int mwv207_ctx_mgr_init(struct drm_device *dev, struct mwv207_ctx_mgr *mgr) +{ + mutex_init(&mgr->lock); + idr_init(&mgr->handle_table); + return 0; +} + +void mwv207_ctx_mgr_fini(struct drm_device *dev, struct mwv207_ctx_mgr *mgr) +{ + struct mwv207_ctx *ctx; + uint32_t id; + + idr_for_each_entry(&mgr->handle_table, ctx, id) + mwv207_ctx_put(ctx); + + idr_destroy(&mgr->handle_table); + mutex_destroy(&mgr->lock); +} + +int mwv207_kctx_init(struct mwv207_device *jdev) +{ + struct drm_gpu_scheduler *scheds[1]; + int i; + + for (i = 0; i < 1; i++) + scheds[i] = jdev->sched_dma[i]; + + jdev->dma_entity = devm_kzalloc(jdev->dev, sizeof(struct drm_sched_entity), GFP_KERNEL); + if (!jdev->dma_entity) + return -ENOMEM; + + return drm_sched_entity_init(jdev->dma_entity, DRM_SCHED_PRIORITY_NORMAL, &scheds[0], i, NULL); +} + +void mwv207_kctx_fini(struct mwv207_device *jdev) +{ + drm_sched_entity_destroy(jdev->dma_entity); +} diff --git a/drivers/gpu/drm/mwv207/mwv207_ctx.h b/drivers/gpu/drm/mwv207/mwv207_ctx.h new file mode 100644 index 0000000000000000000000000000000000000000..67c02d439ba3c07bf11616224c6f641bb002ca58 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_ctx.h @@ -0,0 +1,51 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef MWV207_CTX_H_QFMRESBO +#define MWV207_CTX_H_QFMRESBO +#include +#include +#include "mwv207.h" + +struct mwv207_ctx { + struct kref refcnt; + + struct drm_sched_entity *entities[11]; + + + struct drm_sched_entity **entity_3d; + struct drm_sched_entity **entity_dec; + struct drm_sched_entity **entity_enc; + struct drm_sched_entity **entity_2d; + struct drm_sched_entity **entity_dma; + + struct mwv207_device *jdev; + atomic_t guilty; +}; + +struct mwv207_ctx *mwv207_ctx_lookup(struct drm_device *dev, + struct drm_file *filp, u32 handle); +int mwv207_ctx_put(struct mwv207_ctx *ctx); + +int mwv207_ctx_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp); + +int mwv207_ctx_mgr_init(struct drm_device *dev, struct mwv207_ctx_mgr *mgr); +void mwv207_ctx_mgr_fini(struct drm_device *dev, struct mwv207_ctx_mgr *mgr); + +int mwv207_kctx_init(struct mwv207_device *jdev); +void mwv207_kctx_fini(struct mwv207_device *jdev); + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_db.c b/drivers/gpu/drm/mwv207/mwv207_db.c new file mode 100644 index 0000000000000000000000000000000000000000..ce906afeb44add720953098f03c835b911fb0832 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_db.c @@ -0,0 +1,138 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include "mwv207_db.h" +#include "mwv207.h" +#include "mwv207_drm.h" + +struct mwv207_db { + u32 nr; + u32 max_nr; + u32 sorted; + u32 pad; + struct drm_mwv207_info_rec *db; +}; + +int mwv207_db_init(struct mwv207_device *jdev) +{ + struct mwv207_db *db; + u32 size; + + size = sizeof(struct mwv207_db); + size += 2048 * sizeof(struct drm_mwv207_info_rec); + db = devm_kmalloc(jdev->dev, size, GFP_KERNEL); + if (!db) + return -ENOMEM; + + db->db = (struct drm_mwv207_info_rec *)(db + 1); + db->max_nr = 2048; + db->nr = 0; + db->sorted = 0; + jdev->db = db; + return 0; +} + +void mwv207_db_fini(struct mwv207_device *jdev) +{ + devm_kfree(jdev->dev, jdev->db); +} + +int mwv207_db_add(struct mwv207_device *jdev, u32 key, u32 val) +{ + struct mwv207_db *db = jdev->db; + + if (db->nr >= db->max_nr) { + WARN_ONCE(1, "mwv207: maximum key count reached"); + return -ENOMEM; + } + + db->db[db->nr].key = key; + db->db[db->nr].val = val; + db->nr++; + + + if (db->sorted) + mwv207_db_sort(jdev); + + return 0; +} + +static int cmp_key(const void *a, const void *b) +{ + struct drm_mwv207_info_rec const *rec_a = a, *rec_b = b; + + if (rec_a->key < rec_b->key) + return -1; + else if (rec_a->key > rec_b->key) + return 1; + else + return 0; +} + +void mwv207_db_sort(struct mwv207_device *jdev) +{ + struct mwv207_db *db = jdev->db; + int i; + + db->sorted = 1; + if (db->nr < 2) + return; + + sort(db->db, db->nr, 8, cmp_key, NULL); + + + for (i = 0; i < db->nr - 1; ++i) { + if (db->db[i].key == db->db[i + 1].key) { + WARN_ONCE(1, "mwv207 key: %d is duplicated", db->db[i].key); + break; + } + } +} + +int mwv207_db_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct mwv207_device *jdev = dev->dev_private; + struct drm_mwv207_info_rec rec, *result; + struct drm_mwv207_info *args = data; + struct mwv207_db *db = jdev->db; + + switch (args->op) { + case 1: + rec.key = args->nr_recs; + result = bsearch(&rec, db->db, db->nr, 8, cmp_key); + if (!result) { + args->recs = 0; + return -ENOENT; + } + args->recs = result->val; + + break; + case 0: + if (args->recs) + return copy_to_user((void __user *)args->recs, db->db, + db->nr * sizeof(struct drm_mwv207_info_rec)); + args->nr_recs = db->nr; + + break; + default: + return -EINVAL; + } + + return 0; +} diff --git a/drivers/gpu/drm/mwv207/mwv207_db.h b/drivers/gpu/drm/mwv207/mwv207_db.h new file mode 100644 index 0000000000000000000000000000000000000000..fd5c685d4ca33e3c60cf7bf398e5c3ed74ced7b1 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_db.h @@ -0,0 +1,32 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include "mwv207_drm.h" +#ifndef MWV207_DB_H_AGPY6ZIQ +#define MWV207_DB_H_AGPY6ZIQ + +struct mwv207_device; + +int mwv207_db_init(struct mwv207_device *jdev); +void mwv207_db_fini(struct mwv207_device *jdev); +void mwv207_db_sort(struct mwv207_device *jdev); + +int mwv207_db_add(struct mwv207_device *jdev, u32 key, u32 val); + +int mwv207_db_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp); + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_devfreq.c b/drivers/gpu/drm/mwv207/mwv207_devfreq.c new file mode 100644 index 0000000000000000000000000000000000000000..de12669890ff09f13063dc53874e1c1ff983c0b1 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_devfreq.c @@ -0,0 +1,279 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and * confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include "mwv207_devfreq.h" +#include "mwv207_vbios.h" +#include "mwv207_sched.h" + +#define MIN_FREQ 50 +#define MAX_FREQ 600 + +static void mwv207_devfreq_update_utilization(struct mwv207_pipe *pipe) +{ + ktime_t now; + ktime_t last; + + now = ktime_get(); + last = pipe->time_last_update; + + dev_dbg(pipe->dev, "pipe [%s] busy_count: %d", pipe->fname, pipe->busy_count); + + if (pipe->busy_count > 0) + pipe->busy_time += ktime_sub(now, last); + else + pipe->idle_time += ktime_sub(now, last); + + pipe->time_last_update = now; +} + +static int mwv207_devfreq_get_cur_freq(struct device *dev, unsigned long *freq) +{ + struct mwv207_pipe *pipe = dev_get_drvdata(dev); + unsigned long freq_khz; + int ret; + + ret = mwv207_vbios_get_pll(pipe->jdev, pipe->pll_id, &freq_khz); + if (ret) + *freq = MIN_FREQ; + else + *freq = freq_khz / 1000; + + return 0; +} + +static int mwv207_devfreq_target(struct device *dev, unsigned long *freq, u32 flags) +{ + struct mwv207_pipe *pipe = dev_get_drvdata(dev); + struct dev_pm_opp *opp; + unsigned long target_freq; + int ret; + + opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(opp)) + return PTR_ERR(opp); + + target_freq = dev_pm_opp_get_freq(opp); + dev_pm_opp_put(opp); + + dev_dbg(dev, "pipe [%s] target freq: %lu", pipe->fname, target_freq); + + ret = mwv207_vbios_set_pll(pipe->jdev, pipe->pll_id, target_freq * 1000); + if (ret) + return ret; + + return 0; +} + +static void mwv207_devfreq_pipe_reset(struct mwv207_pipe *pipe) +{ + pipe->busy_time = 0; + pipe->idle_time = 0; + pipe->time_last_update = ktime_get(); +} + +static int mwv207_devfreq_get_dev_status(struct device *dev, struct devfreq_dev_status *status) +{ + struct mwv207_pipe *pipe = dev_get_drvdata(dev); + unsigned long flags; + + mwv207_devfreq_get_cur_freq(dev, &status->current_frequency); + + spin_lock_irqsave(&pipe->devfreq_lock, flags); + + mwv207_devfreq_update_utilization(pipe); + + status->total_time = ktime_to_ns(ktime_add(pipe->busy_time, + pipe->idle_time)); + + status->busy_time = ktime_to_ns(pipe->busy_time); + + mwv207_devfreq_pipe_reset(pipe); + + spin_unlock_irqrestore(&pipe->devfreq_lock, flags); + + return 0; +} + +static struct devfreq_dev_profile mwv207_devfreq_profile = { + .polling_ms = 50, + .target = mwv207_devfreq_target, + .get_dev_status = mwv207_devfreq_get_dev_status, + .get_cur_freq = mwv207_devfreq_get_cur_freq, +}; + +static ssize_t utilization_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct mwv207_pipe *pipe = dev_get_drvdata(dev); + unsigned long busy_time, total_time, flags; + + spin_lock_irqsave(&pipe->devfreq_lock, flags); + + mwv207_devfreq_update_utilization(pipe); + + total_time = ktime_to_ns(ktime_add(pipe->busy_time, pipe->idle_time)); + + busy_time = ktime_to_ns(pipe->busy_time); + + if (strncmp(pipe->devfreq->governor_name, DEVFREQ_GOV_SIMPLE_ONDEMAND, DEVFREQ_NAME_LEN)) + mwv207_devfreq_pipe_reset(pipe); + + spin_unlock_irqrestore(&pipe->devfreq_lock, flags); + + if (total_time == 0) + return sprintf(buf, "%d\n", 100); + else + return sprintf(buf, "%llu\n", div_u64(busy_time, total_time / 100)); +} + +static DEVICE_ATTR_RO(utilization); + +void mwv207_devfreq_unregister(struct mwv207_pipe *pipe) +{ + if (!pipe->devfreq) + return; + + device_remove_file(pipe->dev, &dev_attr_utilization); + sysfs_remove_link(&pipe->dev->kobj, "stats"); + dev_pm_opp_remove_all_dynamic(pipe->dev); + device_unregister(pipe->dev); +} + +static void mwv207_devfreq_dev_release(struct device *dev) +{ + kfree(dev); +} + +int mwv207_devfreq_register(struct mwv207_pipe *pipe, unsigned long max_freq) +{ + struct device *dev; + unsigned long freq; + int ret; + + dev = kzalloc(sizeof(struct device), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->parent = pipe->jdev->dev; + dev->release = mwv207_devfreq_dev_release; + dev_set_name(dev, "devfreq_%s", pipe->fname); + dev_set_drvdata(dev, pipe); + + ret = device_register(dev); + if (ret) { + put_device(dev); + goto err_out; + } + + pipe->dev = dev; + pipe->busy_count = 0; + spin_lock_init(&pipe->devfreq_lock); + + mwv207_devfreq_profile.initial_freq = max_freq; + + for (freq = MIN_FREQ; freq <= max_freq; freq += 50) { + ret = dev_pm_opp_add(dev, freq, 0); + if (ret) + goto remove_opps; + } + + pipe->devfreq = devm_devfreq_add_device(dev, &mwv207_devfreq_profile, + DEVFREQ_GOV_PERFORMANCE, NULL); + if (IS_ERR(pipe->devfreq)) { + ret = PTR_ERR(pipe->devfreq); + dev_dbg(dev, "Couldn't initialize devfreq: %d", ret); + goto remove_opps; + } + + mwv207_devfreq_pipe_reset(pipe); + + pipe->devfreq->suspend_freq = MIN_FREQ; + + ret = sysfs_create_link(&dev->kobj, &pipe->devfreq->dev.kobj, "stats"); + if (ret) { + dev_dbg(dev, "Couldn't create sys link for stats: %d", ret); + goto remove_opps; + } + + ret = device_create_file(dev, &dev_attr_utilization); + if (ret) { + dev_dbg(dev, "Couldn't create device file for utilization: %d", ret); + goto remove_link; + } + + return 0; + +remove_link: + sysfs_remove_link(&dev->kobj, "stats"); +remove_opps: + dev_pm_opp_remove_all_dynamic(dev); + device_unregister(dev); +err_out: + return ret; +} + +int mwv207_devfreq_resume(struct mwv207_pipe *pipe) +{ + unsigned long flags; + + if (!pipe->devfreq) + return 0; + + spin_lock_irqsave(&pipe->devfreq_lock, flags); + + mwv207_devfreq_pipe_reset(pipe); + + spin_unlock_irqrestore(&pipe->devfreq_lock, flags); + + return devfreq_resume_device(pipe->devfreq); +} + +int mwv207_devfreq_suspend(struct mwv207_pipe *pipe) +{ + if (!pipe->devfreq) + return 0; + + return devfreq_suspend_device(pipe->devfreq); +} + +void mwv207_devfreq_record_busy(struct mwv207_pipe *pipe) +{ + unsigned long flags; + + if (!pipe->devfreq) + return; + + spin_lock_irqsave(&pipe->devfreq_lock, flags); + + mwv207_devfreq_update_utilization(pipe); + pipe->busy_count++; + + spin_unlock_irqrestore(&pipe->devfreq_lock, flags); +} + +void mwv207_devfreq_record_idle(struct mwv207_pipe *pipe) +{ + unsigned long flags; + + if (!pipe->devfreq) + return; + + spin_lock_irqsave(&pipe->devfreq_lock, flags); + + mwv207_devfreq_update_utilization(pipe); + WARN_ON(--pipe->busy_count < 0); + + spin_unlock_irqrestore(&pipe->devfreq_lock, flags); +} diff --git a/drivers/gpu/drm/mwv207/mwv207_devfreq.h b/drivers/gpu/drm/mwv207/mwv207_devfreq.h new file mode 100644 index 0000000000000000000000000000000000000000..5cabf1c3a39c22f6d2e00d82080b4100be2a2272 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_devfreq.h @@ -0,0 +1,31 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ + +#ifndef __MWV207_DEVFREQ_H__ +#define __MWV207_DEVFREQ_H__ + +struct mwv207_pipe; + +void mwv207_devfreq_unregister(struct mwv207_pipe *pipe); +int mwv207_devfreq_register(struct mwv207_pipe *pipe, unsigned long max_freq); + +void mwv207_devfreq_record_busy(struct mwv207_pipe *pipe); +void mwv207_devfreq_record_idle(struct mwv207_pipe *pipe); + +int mwv207_devfreq_suspend(struct mwv207_pipe *pipe); +int mwv207_devfreq_resume(struct mwv207_pipe *pipe); + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_dma.h b/drivers/gpu/drm/mwv207/mwv207_dma.h new file mode 100644 index 0000000000000000000000000000000000000000..ff70d9a0e89ad231e277d083b254d7c661c9c0f3 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_dma.h @@ -0,0 +1,29 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef MWV207_EDMA_H_EPAWCJ4N +#define MWV207_EDMA_H_EPAWCJ4N +#include "mwv207.h" + +struct mwv207_dma; +struct mwv207_dma *mwv207_dma_create(struct device *dev, + void __iomem *regbase, u8 alignment); +void mwv207_dma_destroy(struct mwv207_dma *dma); + +int mwv207_dma_to_vram(struct mwv207_dma *dma, u64 vram_addr, void *va, u64 size); +int mwv207_dma_to_ram(struct mwv207_dma *dma, void *va, u64 vram_addr, u64 size); +int mwv207_dma_vram_vram(struct mwv207_dma *dma, u64 dst_vram_addr, u64 src_vram_addr, u64 size); + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_drm.h b/drivers/gpu/drm/mwv207/mwv207_drm.h new file mode 100644 index 0000000000000000000000000000000000000000..b6c2ff6248baff415ed7d11404dde42650533607 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_drm.h @@ -0,0 +1,160 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef __MWV207_DRM_H__ +#define __MWV207_DRM_H__ + + +#include "drm/drm.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/**********************GEM interface*************************/ + +struct drm_mwv207_gem_create_in { + __u64 size; + __u64 alignment; + __u32 preferred_domain; + __u32 flags; +}; +struct drm_mwv207_gem_create_out { + __u32 handle; + __u32 resv; +}; +union drm_mwv207_gem_create { + struct drm_mwv207_gem_create_in in; + struct drm_mwv207_gem_create_out out; +}; + +struct drm_mwv207_gem_mmap_in { + __u32 handle; + __u32 pad; +}; +struct drm_mwv207_gem_mmap_out { + __u64 offset; +}; +union drm_mwv207_gem_mmap { + struct drm_mwv207_gem_mmap_in in; + struct drm_mwv207_gem_mmap_out out; +}; + +struct drm_mwv207_gem_wait { + __u32 handle; + __u32 op; + __s64 timeout; +}; + +/**********************context interface*************************/ +struct drm_mwv207_ctx { + __u32 op; + __u32 flags; + __u32 handle; + __u32 resv; +}; + +/**********************submission interface************************* + * 1. a submission is sent to a single ctx + * 2. a submission optionally contains a relocation table and a bo table + * 3. bo table should not contain duplicates; + * 4. cmd buffer size should be limited to 16K + */ + +struct drm_mwv207_submit { + __u32 ctx; + __u32 engine_type; + __u32 engine_id; + __u32 flags; + __u32 fence_fd; /* in/out fence fd, according to MWV207_SUBMIT_FLAG_FENCE_IN/OUT */ + __u32 cmd_size; + __u32 nr_relocs; + __u32 nr_bos; + __u32 nr_cmd_dat; + __u32 padding; + __u64 cmds; + __u64 relocs; + __u64 bos; + __u64 cmd_dats; + /* syncobj support, reserved for vulkan + * __u32 nr_in_syncobjs; + * __u32 nr_out_syncobjs; + * __u64 in_syncobjs; + * __u64 out_syncobjs; + */ +}; + +/* in_syncobjs/out_syncobjs array element, reserved for vulkan */ +struct drm_mwv207_submit_syncobj { + __u32 handle; + __u32 flags; + __u64 point; +}; + +struct drm_mwv207_bo_acc { + __u32 handle; + __u32 flags; +}; + +struct drm_mwv207_submit_reloc { + __u32 idx; /* bo/cmd_dat idx in submit bos/cmd_dats */ + __u32 cmd_offset; + __u64 bo_offset; + __u32 type; + __u32 pad; +}; + + +struct drm_mwv207_submit_cmd_dat { + __u32 nr_relocs; + __u32 dat_size; + __u64 relocs; + __u64 dat; +}; + +/**************** card capability interface *****************/ +/* XXX: keys are user/kernel ABI, DO THINK TWICE when + * add keys, once added it can never be removed. + */ +enum drm_mwv207_info_key { + DRM_MWV207_ACTIVE_3D_NR = 0, + DRM_MWV207_ACTIVE_DEC_NR, + DRM_MWV207_ACTIVE_ENC_NR, + DRM_MWV207_ACTIVE_2D_NR, + DRM_MWV207_ACTIVE_DMA_NR, +}; + +struct drm_mwv207_info_rec { + __u32 key; + __u32 val; +}; + +struct drm_mwv207_info { + __u32 op; + __u32 nr_recs; + __u64 recs; +}; + +#define DRM_IOCTL_MWV207_INFO DRM_IOWR(DRM_COMMAND_BASE + 0x00, struct drm_mwv207_info) +#define DRM_IOCTL_MWV207_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + 0x01, union drm_mwv207_gem_create) +#define DRM_IOCTL_MWV207_GEM_MMAP DRM_IOWR(DRM_COMMAND_BASE + 0x02, union drm_mwv207_gem_mmap) +#define DRM_IOCTL_MWV207_GEM_WAIT DRM_IOWR(DRM_COMMAND_BASE + 0x03, struct drm_mwv207_gem_wait) +#define DRM_IOCTL_MWV207_CTX DRM_IOWR(DRM_COMMAND_BASE + 0x04, struct drm_mwv207_ctx) +#define DRM_IOCTL_MWV207_SUBMIT DRM_IOWR(DRM_COMMAND_BASE + 0x05, struct drm_mwv207_submit) +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_drv.c b/drivers/gpu/drm/mwv207/mwv207_drv.c new file mode 100644 index 0000000000000000000000000000000000000000..e9b005c9fa92fae82edbaff6ec89edd5347a1951 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_drv.c @@ -0,0 +1,547 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mwv207_drm.h" +#include "mwv207_gem.h" +#include "mwv207_bo.h" +#include "dc/mwv207_kms.h" +#include "mwv207_ctx.h" +#include "mwv207_submit.h" +#include "mwv207_sched.h" +#include "mwv207_irq.h" +#include "mwv207_db.h" +#include "mwv207_vbios.h" + +#define DRIVER_NAME "mwv207" +#define DRIVER_DESC "mwv207 gpu driver" +#define DRIVER_DATE "20220407" +#define DRIVER_MAJOR 0 +#define DRIVER_MINOR 1 + +#define WIN_SIZE 0x10000 + +#define IATU_BAR_LOW_OFFSET 0x108 +#define IATU_BAR_HIGH_OFFSET 0x10c +#define IATU_BAR_LIMIT_LOW_OFFSET 0x110 + +#define IATU_REGION_MODE_ADDR 0 + +static int selftest; +module_param(selftest, int, 0644); +MODULE_PARM_DESC(selftest, "run selftest when startup"); + +static int mwv207_driver_open(struct drm_device *dev, struct drm_file *file_priv) +{ + struct mwv207_fpriv *fpriv; + + fpriv = kzalloc(sizeof(struct mwv207_fpriv), GFP_KERNEL); + if (fpriv == NULL) + return -ENOMEM; + + mwv207_ctx_mgr_init(dev, &fpriv->ctx_mgr); + + file_priv->driver_priv = fpriv; + + return 0; +} + +static void mwv207_driver_postclose(struct drm_device *dev, + struct drm_file *file_priv) +{ + struct mwv207_fpriv *fpriv = to_fpriv(file_priv); + + mwv207_ctx_mgr_fini(dev, &fpriv->ctx_mgr); + kfree(fpriv); +} + +static int mwv207_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct drm_file *file_priv; + struct mwv207_device *jdev; + int ret; + + file_priv = filp->private_data; + jdev = file_priv->minor->dev->dev_private; + ret = ttm_bo_mmap(filp, vma, &jdev->bdev); + + return ret; +} + +static const struct file_operations mwv207_driver_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .mmap = mwv207_mmap, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .release = drm_release, +}; + +static const struct drm_ioctl_desc mwv207_ioctls_drm[] = { + DRM_IOCTL_DEF_DRV(MWV207_INFO, mwv207_db_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(MWV207_GEM_CREATE, mwv207_gem_create_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(MWV207_GEM_MMAP, mwv207_gem_mmap_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(MWV207_GEM_WAIT, mwv207_gem_wait_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(MWV207_CTX, mwv207_ctx_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(MWV207_SUBMIT, mwv207_submit_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), +}; + +static struct drm_driver mwv207_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM | DRIVER_RENDER, + .fops = &mwv207_driver_fops, + .open = mwv207_driver_open, + .postclose = mwv207_driver_postclose, + + .dumb_create = mwv207_gem_dumb_create, + .dumb_map_offset = drm_gem_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + + .gem_free_object_unlocked = mwv207_gem_free_object, + + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_mmap = drm_gem_prime_mmap, + + .ioctls = mwv207_ioctls_drm, + .num_ioctls = ARRAY_SIZE(mwv207_ioctls_drm), + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, +}; + +static inline void iatu_write(struct mwv207_device *jdev, int index, u32 offset, u32 value) +{ + u32 region_base = 0x10000 + index * 0x200; + writel(value, jdev->iatu + region_base + offset); +} + +static inline u32 iatu_read(struct mwv207_device *jdev, int index, u32 offset) +{ + u32 region_base = 0x10000 + index * 0x200; + return readl(jdev->iatu + region_base + offset); +} + +static void mwv207_win_slide(struct mwv207_device *jdev, int region, + u64 pci_win_base, u64 axi_win_base) +{ + iatu_write(jdev, region, IATU_BAR_LOW_OFFSET, pci_win_base & 0xffffffff); + iatu_write(jdev, region, IATU_BAR_HIGH_OFFSET, pci_win_base >> 32); + iatu_write(jdev, region, 0x114, axi_win_base & 0xffffffff); + iatu_write(jdev, region, 0x118, axi_win_base >> 32); + + iatu_write(jdev, region, IATU_BAR_LIMIT_LOW_OFFSET, + (pci_win_base + WIN_SIZE - 1) & 0xffffffff); + iatu_write(jdev, region, 0x100, 0x00); + iatu_write(jdev, region, 0x104, + 1 << 31 + | IATU_REGION_MODE_ADDR << 30 + | 6 << 8); + + + mb(); + if (iatu_read(jdev, region, 0x118) != (axi_win_base >> 32)) + pr_warn("mwv207: sliding window failed"); +} + +static int mwv207_iatu_map_bar(struct mwv207_device *jdev, int bar, u64 axi_addr) +{ + iatu_write(jdev, bar + 9, 0x114, axi_addr & 0xffffffff); + iatu_write(jdev, bar + 9, 0x118, axi_addr >> 32); + iatu_write(jdev, bar + 9, 0x100, 0x00); + iatu_write(jdev, bar + 9, 0x104, + 1 << 31 + | 1 << 30 + | bar << 8); + + + mb(); + if (iatu_read(jdev, bar + 9, 0x114) != (axi_addr & 0xffffffff)) + return -ENODEV; + + return 0; +} + +static int mwv207_iatu_init(struct mwv207_device *jdev) +{ + int ret; + + ret = mwv207_iatu_map_bar(jdev, 0, 0x10000000); + if (ret) + return ret; + + ret = mwv207_iatu_map_bar(jdev, 1, 0x00000000); + if (ret) + return ret; + + ret = mwv207_iatu_map_bar(jdev, 2, 0x10000000); + if (ret) + return ret; + + spin_lock_init(&jdev->win_lock); + + jdev->pci_win_base = pci_bus_address(jdev->base.pdev, 2) + + pci_resource_len(jdev->base.pdev, 2) - WIN_SIZE; + + mwv207_win_slide(jdev, 0, jdev->pci_win_base, 0x10000000); + + jdev->vram_last_win = 0; + + return 0; +} + +static void *mwv207_remap_region(struct mwv207_device *jdev, u64 vram_addr, u32 *len) +{ + u64 vram_win_base = (vram_addr & (~(WIN_SIZE - 1))); + u64 winlimit = vram_win_base + WIN_SIZE; + + if (vram_win_base != jdev->vram_last_win) { + mwv207_win_slide(jdev, 0, jdev->pci_win_base, vram_win_base + 0x10000000); + jdev->vram_last_win = vram_win_base; + } + + if (*len > winlimit - vram_addr) + *len = winlimit - vram_addr; + + return jdev->win + vram_addr - vram_win_base; +} + +void jdev_write_vram(struct mwv207_device *jdev, u64 vram_addr, void *buf, int size) +{ + int remain, len; + void *va; + + for (remain = size; remain > 0; remain -= len, buf += len, vram_addr += len) { + len = remain; + spin_lock(&jdev->win_lock); + va = mwv207_remap_region(jdev, vram_addr, &len); + if (!va || len <= 0) { + spin_unlock(&jdev->win_lock); + pr_warn("mwv207: write vram faild"); + return; + } + memcpy_toio(va, buf, len); + spin_unlock(&jdev->win_lock); + } +} + +static int mwv207_kick_out_firmware_fb(struct pci_dev *pdev) +{ + struct apertures_struct *ap; + bool primary = false; + + ap = alloc_apertures(1); + if (!ap) + return -ENOMEM; + + ap->ranges[0].base = pci_resource_start(pdev, 0); + ap->ranges[0].size = pci_resource_len(pdev, 0); + +#ifdef CONFIG_X86 + primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; +#endif + drm_fb_helper_remove_conflicting_framebuffers(ap, "mwv207drmfb", primary); + kfree(ap); + + return 0; +} + +static u64 mwv207_vram_size(struct mwv207_device *jdev) +{ + int nr = 0; + u64 size; + + switch (jdev_read(jdev, 0x9b0190) & 0x7) { + case 0: + size = 2048; + break; + case 1: + size = 1024; + break; + case 2: + size = 4096; + break; + case 3: + size = 8192; + break; + default: + pr_warn("mwv207: ddr size default to 256MB"); + return 256UL * 1024 * 1024; + } + + if (jdev->lite) { + nr += jdev_read(jdev, 0xd101bc) & 0x1; + nr += jdev_read(jdev, 0xd11cbc) & 0x1; + } else { + nr += (jdev_read(jdev, 0x9702f4) & 0x02000000) ? 1 : 0; + nr += (jdev_read(jdev, 0x9802f4) & 0x02000000) ? 1 : 0; + } + if (!nr) { + nr = 1; + pr_warn("mwv207: ddr cnt default to 1"); + } + + return size * 1024 * 1024 * nr; +} + +static int mwv207_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct mwv207_device *jdev; + int ret; + + ret = mwv207_kick_out_firmware_fb(pdev); + if (ret) + return ret; + + pci_set_master(pdev); + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + jdev = devm_drm_dev_alloc(&pdev->dev, &mwv207_drm_driver, + struct mwv207_device, base); + if (IS_ERR(jdev)) + return PTR_ERR(jdev); + + jdev->dev = &pdev->dev; + jdev->base.pdev = pdev; + jdev->base.dev_private = jdev; + + jdev->mmio = devm_ioremap(jdev->dev, + pci_resource_start(pdev, 1), pci_resource_len(pdev, 1)); + if (!jdev->mmio) + return -ENOMEM; + + jdev->iatu = devm_ioremap(jdev->dev, + pci_resource_start(pdev, 4), pci_resource_len(pdev, 4)); + if (!jdev->iatu) + return -ENOMEM; + + jdev->win = devm_ioremap(jdev->dev, + pci_resource_end(pdev, 2) - WIN_SIZE + 1, WIN_SIZE); + if (!jdev->win) + return -ENOMEM; + + ret = mwv207_iatu_init(jdev); + if (ret) + return ret; + + /* magic: use hdmi-3 to probe for 92/91 (full/lite) */ + jdev->lite = jdev_read(jdev, 0x12c0000) == 0x21 ? false : true; + + pci_set_drvdata(pdev, jdev); + + ret = mwv207_db_init(jdev); + if (ret) + return ret; + + mwv207_vbios_init(jdev); + + ret = mwv207_irq_init(jdev); + if (ret) + goto err_vbios; + + jdev->visible_vram_size = pci_resource_len(pdev, 2) - WIN_SIZE; + jdev->vram_size = mwv207_vram_size(jdev); + jdev->vram_bar_base = pci_resource_start(pdev, 2); + + ret = mwv207_ttm_init(jdev); + if (ret) + goto err_irq; + + ret = mwv207_sched_init(jdev); + if (ret) + goto err_ttm; + + ret = mwv207_kctx_init(jdev); + if (ret) + goto err_sched; + + ret = mwv207_kms_init(jdev); + if (ret) + goto err_kctx; + + if (selftest) { + ret = mwv207_test(jdev); + if (ret) + goto err_kms; + } + + mwv207_db_sort(jdev); + ret = drm_dev_register(&jdev->base, 0); + if (ret) + goto err_kms; + ret = mwv207_fbdev_init(jdev); + if (ret) + goto err_drm; + + return 0; +err_drm: + drm_dev_unregister(&jdev->base); +err_kms: + mwv207_kms_fini(jdev); +err_kctx: + mwv207_kctx_fini(jdev); +err_sched: + mwv207_sched_fini(jdev); +err_ttm: + mwv207_ttm_fini(jdev); +err_irq: + mwv207_irq_fini(jdev); +err_vbios: + mwv207_vbios_fini(jdev); + mwv207_db_fini(jdev); + return ret; +} + +static void mwv207_pci_remove(struct pci_dev *pdev) +{ + struct mwv207_device *jdev = pci_get_drvdata(pdev); + + mwv207_fbdev_fini(jdev); + drm_dev_unregister(&jdev->base); + mwv207_kms_fini(jdev); + mwv207_kctx_fini(jdev); + mwv207_sched_fini(jdev); + mwv207_ttm_fini(jdev); + mwv207_irq_fini(jdev); + mwv207_vbios_fini(jdev); + mwv207_db_fini(jdev); + pci_clear_master(pdev); + pci_set_drvdata(pdev, NULL); +} + +static int mwv207_pmops_suspend(struct device *dev) +{ + struct mwv207_device *jdev = dev_get_drvdata(dev); + struct pci_dev *pdev = to_pci_dev(dev); + int ret; + + ret = mwv207_kms_suspend(jdev); + if (ret) + return ret; + + /* kernel permanently pinned boes won't be evicted, they are + * handled by each module in whatever ways suitable + */ + ret = ttm_bo_evict_mm(&jdev->bdev, TTM_PL_VRAM); + if (ret) + goto fail; + + ret = mwv207_sched_suspend(jdev); + if (ret) + goto fail; + + mwv207_irq_suspend(jdev); + + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D3hot); + udelay(200); + return 0; +fail: + mwv207_kms_resume(jdev); + return ret; +} + +static int mwv207_pmops_resume(struct device *dev) +{ + struct mwv207_device *jdev = dev_get_drvdata(dev); + struct pci_dev *pdev = to_pci_dev(dev); + int ret; + + msleep(100); + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + ret = pci_enable_device(pdev); + if (ret) { + pr_err("mwv207: failed to enable pci device"); + return ret; + } + pci_set_master(pdev); + + ret = mwv207_iatu_init(jdev); + if (ret) + return ret; + + mwv207_irq_resume(jdev); + + ret = mwv207_sched_resume(jdev); + if (ret) + return ret; + + return mwv207_kms_resume(jdev); +} + +static SIMPLE_DEV_PM_OPS(mwv207_pm_ops, mwv207_pmops_suspend, mwv207_pmops_resume); + +#define MWV207_PCI_DEVICE_DATA(vend, dev, data) \ + .vendor = vend, .device = dev, .driver_data = (kernel_ulong_t)(data), \ + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, \ + .class = 0, .class_mask = 0 + +static const struct pci_device_id pciidlist[] = { + {MWV207_PCI_DEVICE_DATA(0x0731, 0x9100, NULL)}, + {0}, +}; + +MODULE_DEVICE_TABLE(pci, pciidlist); +static struct pci_driver mwv207_pci_driver = { + .name = DRIVER_NAME, + .id_table = pciidlist, + .probe = mwv207_pci_probe, + .remove = mwv207_pci_remove, + .driver.pm = &mwv207_pm_ops, +}; + +static int __init mwv207_init(void) +{ + return pci_register_driver(&mwv207_pci_driver); +} + +static void __exit mwv207_exit(void) +{ + pci_unregister_driver(&mwv207_pci_driver); +} + +module_init(mwv207_init); +module_exit(mwv207_exit); +MODULE_DESCRIPTION("MWV207 Graphics Driver"); +MODULE_AUTHOR("shanjinkui"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/mwv207/mwv207_gem.c b/drivers/gpu/drm/mwv207/mwv207_gem.c new file mode 100644 index 0000000000000000000000000000000000000000..26c9ef167095c1feee644edfe7abb1135577d175 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_gem.c @@ -0,0 +1,155 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include "mwv207_drm.h" +#include "mwv207_gem.h" +#include "mwv207_bo.h" + +static int mwv207_gem_create(struct mwv207_device *jdev, + u64 size, u64 align, u32 preferred_domain, + u32 flags, struct drm_gem_object **gobj) +{ + struct mwv207_bo *jbo; + int ret; + +retry: + + ret = mwv207_bo_create(jdev, size, align, ttm_bo_type_device, + preferred_domain, flags, &jbo); + if (ret) { + if (ret != -ERESTARTSYS) { + if (flags & (1<<0)) { + flags &= ~(1<<0); + goto retry; + } + if (preferred_domain == 0x2) { + preferred_domain |= 0x1; + goto retry; + } + DRM_DEBUG("Failed to allocate GEM object (%lld, %d, %llu, %d)\n", + size, preferred_domain, align, ret); + } + return ret; + } + + *gobj = mwv207_gem_from_bo(jbo); + return 0; +} + +int mwv207_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct mwv207_device *jdev = dev->dev_private; + struct drm_gem_object *gobj; + u32 handle; + int ret; + + args->pitch = ALIGN(args->width * DIV_ROUND_UP(args->bpp, 8), 64); + args->size = args->pitch * args->height; + ret = mwv207_gem_create(jdev, args->size, 0x10000, + 0x2, + (1<<0), + &gobj); + if (ret) + return ret; + + ret = drm_gem_handle_create(file, gobj, &handle); + mwv207_gem_object_put(gobj); + if (ret) + return ret; + + args->handle = handle; + return 0; +} + +void mwv207_gem_free_object(struct drm_gem_object *gobj) +{ + struct mwv207_bo *jbo; + + if (!gobj) + return; + + jbo = mwv207_bo_from_gem(gobj); + mwv207_bo_unref(jbo); +} + +int mwv207_gem_create_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct mwv207_device *jdev = dev->dev_private; + union drm_mwv207_gem_create *args = data; + struct drm_gem_object *gobj; + int ret; + + if (args->in.size == 0) + return -EINVAL; + if (args->in.alignment & (args->in.alignment - 1)) + return -EINVAL; + if (args->in.preferred_domain & ~0x7) + return -EINVAL; + if (args->in.flags & ~((1<<0)|(1<<1)|(1<<2))) + return -EINVAL; + + ret = mwv207_gem_create(jdev, args->in.size, args->in.alignment, + args->in.preferred_domain, + args->in.flags, &gobj); + if (ret) + return ret; + + ret = drm_gem_handle_create(filp, gobj, &args->out.handle); + mwv207_gem_object_put(gobj); + + return ret; +} + +int mwv207_gem_mmap_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + union drm_mwv207_gem_mmap *args = data; + struct drm_gem_object *obj; + + if (args->in.pad) + return -EINVAL; + + obj = drm_gem_object_lookup(filp, args->in.handle); + if (!obj) + return -ENOENT; + args->out.offset = mwv207_bo_mmap_offset(obj); + mwv207_gem_object_put(obj); + + return 0; +} + +int mwv207_gem_wait_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct drm_mwv207_gem_wait *args = (struct drm_mwv207_gem_wait *)data; + long timeout; + bool write; + int ret; + + if (args->op & ~(0x00000002 | 0x00000001)) + return -EINVAL; + + write = args->op & 0x00000002; + timeout = drm_timeout_abs_to_jiffies(args->timeout); + + ret = drm_gem_dma_resv_wait(filp, args->handle, write, timeout); + if (ret == -ETIME) + ret = timeout ? -ETIMEDOUT : -EBUSY; + + return ret; +} diff --git a/drivers/gpu/drm/mwv207/mwv207_gem.h b/drivers/gpu/drm/mwv207/mwv207_gem.h new file mode 100644 index 0000000000000000000000000000000000000000..52c208d1737e7825142a50365514e43253fb529e --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_gem.h @@ -0,0 +1,39 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef MWV207_GEM_H_TJTW9M4R +#define MWV207_GEM_H_TJTW9M4R +#include +#include +#include +#include "mwv207.h" + +int mwv207_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args); + +void mwv207_gem_free_object(struct drm_gem_object *obj); + +int mwv207_gem_create_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp); + +int mwv207_gem_mmap_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp); + +int mwv207_gem_wait_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp); + +#define mwv207_gem_object_put drm_gem_object_put + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_irq.c b/drivers/gpu/drm/mwv207/mwv207_irq.c new file mode 100644 index 0000000000000000000000000000000000000000..987d8a91eddb7538facd7182fd8af4fbfd126404 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_irq.c @@ -0,0 +1,190 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include "mwv207.h" +#include "mwv207_irq.h" + +static irqreturn_t mwv207_isr(int irq_unused, void *dev_id) +{ + struct mwv207_device *jdev = dev_id; + int i, j, hwirq, virq, ret = IRQ_NONE; + u32 stat; + + for (i = 0; i < 2; ++i) { + stat = jdev_read(jdev, (0x009A802c) + i * 4); + stat &= jdev->irq_enable_reg[i]; + while ((j = ffs(stat))) { + j--; + hwirq = i * 32 + j; + virq = irq_find_mapping(jdev->irq_domain, hwirq); + if (virq) { + ret = generic_handle_irq(virq); + if (ret < 0) + pr_warn("mwv207: hwirq(%d) handled with %d", hwirq, ret); + } else + pr_warn("mwv207: no irq mapping set on %d", hwirq); + + + jdev_write(jdev, (0x009A802c) + i * 4, (1UL << j)); + stat &= ~(1UL << j); + ret = IRQ_HANDLED; + } + } + + return ret; +} + +static void mwv207_irq_mask(struct irq_data *d) +{ + struct mwv207_device *jdev = irq_data_get_irq_chip_data(d); + int irq = irqd_to_hwirq(d); + unsigned long flags; + u32 reg; + + reg = (0x009A8020) + (irq / 32) * 4; + + spin_lock_irqsave(&jdev->irq_lock, flags); + jdev_modify(jdev, reg, 1 << (irq % 32), 0); + jdev->irq_enable_reg[irq / 32] = jdev_read(jdev, reg); + spin_unlock_irqrestore(&jdev->irq_lock, flags); +} + +static void mwv207_irq_unmask(struct irq_data *d) +{ + struct mwv207_device *jdev = irq_data_get_irq_chip_data(d); + int irq = irqd_to_hwirq(d); + unsigned long flags; + u32 reg; + + reg = (0x009A8020) + (irq / 32) * 4; + + spin_lock_irqsave(&jdev->irq_lock, flags); + jdev_modify(jdev, reg, 1 << (irq % 32), 1 << (irq % 32)); + jdev->irq_enable_reg[irq / 32] = jdev_read(jdev, reg); + spin_unlock_irqrestore(&jdev->irq_lock, flags); +} + +void mwv207_irq_suspend(struct mwv207_device *jdev) +{ + int i; + + for (i = 0; i < 2; ++i) + jdev->irq_enable_reg[i] = jdev_read(jdev, + (0x009A8020) + i * 4); +} + +void mwv207_irq_resume(struct mwv207_device *jdev) +{ + int i; + + for (i = 0; i < 2; ++i) { + jdev_write(jdev, (0x009A802c) + i * 4, 0xffffffff); + jdev_write(jdev, (0x009A8020) + i * 4, + jdev->irq_enable_reg[i]); + } +} + +static struct irq_chip mwv207_irq_chip = { + .name = "mwv207", + .irq_mask = mwv207_irq_mask, + .irq_unmask = mwv207_irq_unmask, +}; + +static int mwv207_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct mwv207_device *jdev = d->host_data; + + if (hwirq >= 64) + return -EPERM; + + irq_set_chip_and_handler(irq, &mwv207_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, jdev); + + return 0; +} + +static const struct irq_domain_ops mwv207_irq_domain_ops = { + .map = mwv207_irq_domain_map, +}; + +int mwv207_irq_init(struct mwv207_device *jdev) +{ + struct pci_dev *pdev = jdev->base.pdev; + int ret, i; + + + for (i = 0; i < 2; ++i) { + jdev_write(jdev, (0x009A802c) + i * 4, 0xffffffff); + jdev_write(jdev, (0x009A8020) + i * 4, 0); + } + + spin_lock_init(&jdev->irq_lock); + + jdev->irq_domain = irq_domain_add_linear(NULL, 64, + &mwv207_irq_domain_ops, jdev); + if (!jdev->irq_domain) + return -ENODEV; + + + for (i = 0; i < 64; ++i) + irq_create_mapping(jdev->irq_domain, i); + + ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI | PCI_IRQ_LEGACY); + if (ret < 1) { + ret = -ENODEV; + goto free_mapping; + } + + ret = request_irq(pdev->irq, mwv207_isr, IRQF_SHARED, "mwv207_isr", jdev); + if (ret) + goto free_vec; + + return 0; + +free_vec: + pci_free_irq_vectors(pdev); +free_mapping: + for (i = 0; i < 64; ++i) { + int irq = irq_find_mapping(jdev->irq_domain, i); + + irq_dispose_mapping(irq); + } + + irq_domain_remove(jdev->irq_domain); + + return ret; +} + +void mwv207_irq_fini(struct mwv207_device *jdev) +{ + struct pci_dev *pdev = jdev->base.pdev; + int i; + + free_irq(pdev->irq, jdev); + pci_free_irq_vectors(pdev); + + for (i = 0; i < 64; ++i) { + int irq = irq_find_mapping(jdev->irq_domain, i); + + irq_dispose_mapping(irq); + } + + irq_domain_remove(jdev->irq_domain); +} diff --git a/drivers/gpu/drm/mwv207/mwv207_irq.h b/drivers/gpu/drm/mwv207/mwv207_irq.h new file mode 100644 index 0000000000000000000000000000000000000000..eabcc61e62c479c5d865f3b251de76e2ef592b95 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_irq.h @@ -0,0 +1,26 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef MWV207_IRQ_H_Z8YGVNB2 +#define MWV207_IRQ_H_Z8YGVNB2 + +struct mwv207_device; + +int mwv207_irq_init(struct mwv207_device *jdev); +void mwv207_irq_fini(struct mwv207_device *jdev); + +void mwv207_irq_suspend(struct mwv207_device *jdev); +void mwv207_irq_resume(struct mwv207_device *jdev); +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_pipe_2d.c b/drivers/gpu/drm/mwv207/mwv207_pipe_2d.c new file mode 100644 index 0000000000000000000000000000000000000000..439cc048c1aacfb07440cf84f00060811d9d1698 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_pipe_2d.c @@ -0,0 +1,567 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include +#include + +#include "mwv207_bo.h" +#include "mwv207_sched.h" +#include "mwv207_vbios.h" +#include "mwv207_devfreq.h" + +#define DEFAULT_MAX_FREQ_2D 600 + +#define wait_for(COND, MS) ({ \ + unsigned long timeout__ = jiffies + msecs_to_jiffies(MS) + 1; \ + int ret__ = 0; \ + while (!(COND)) { \ + if (time_after(jiffies, timeout__)) { \ + if (!(COND)) \ + ret__ = -ETIMEDOUT; \ + break; \ + } \ + msleep(1); \ + } \ + ret__; \ +}) + +struct mwv207_pipe_2d { + struct mwv207_pipe base; + struct mwv207_bo *ring_bo; + struct mwv207_device *jdev; + u32 *ring_start; + u32 *ring_end; + u32 *head; + u32 *tail; + u32 *last_wait; + u64 ring_start_addr; + int id; + unsigned int irq; + + u64 fence_ctx; + u32 next_fence; + u32 completed_fence; + spinlock_t fence_lock; + spinlock_t fence_queue_lock; + struct list_head fence_queue; +}; + +struct pipe_2d_fence { + struct dma_fence base; + struct mwv207_pipe_2d *pipe; + u32 *rpos; + + struct list_head q; +}; + +#define to_pipe_2d(p) container_of(p, struct mwv207_pipe_2d, base) +#define to_pipe_2d_fence(f) container_of(f, struct pipe_2d_fence, base) + +static inline void pipe_2d_write(struct mwv207_pipe_2d *pipe, + u32 reg, u32 value) +{ + pipe_write(&pipe->base, reg, value); +} + +static inline u32 pipe_2d_read(struct mwv207_pipe_2d *pipe, u32 reg) +{ + return pipe_read(&pipe->base, reg); +} + +static inline void pipe_2d_barrier(struct mwv207_pipe_2d *pipe) +{ + pipe_2d_read(pipe, 0x4100); + mb(); +} + + +static inline bool fence16_after(u16 a, u16 b) +{ + return (s16)(a - b) > 0; +} + +static inline bool fence_after(u32 a, u32 b) +{ + return (s32)(a - b) > 0; +} + +static const char *pipe_2d_fence_get_driver_name(struct dma_fence *fence) +{ + return "mwv207"; +} + +static const char *pipe_2d_fence_get_timeline_name(struct dma_fence *fence) +{ + return to_pipe_2d_fence(fence)->pipe->base.fname; +} + +static bool pipe_2d_fence_signaled(struct dma_fence *fence) +{ + struct pipe_2d_fence *f = to_pipe_2d_fence(fence); + + return fence_after(f->pipe->completed_fence, f->base.seqno); +} + +static void pipe_2d_fence_release(struct dma_fence *fence) +{ + struct pipe_2d_fence *f = to_pipe_2d_fence(fence); + + kfree_rcu(f, base.rcu); +} + +static const struct dma_fence_ops pipe_2d_fence_ops = { + .get_driver_name = pipe_2d_fence_get_driver_name, + .get_timeline_name = pipe_2d_fence_get_timeline_name, + .signaled = pipe_2d_fence_signaled, + .release = pipe_2d_fence_release, +}; + +static inline u32 pipe_2d_ptr_span(void *end, void *start) +{ + return (unsigned long)end - (unsigned long)start; +} + +static inline u32 pipe_2d_gpu_addr(struct mwv207_pipe_2d *pipe, u32 *ptr) +{ + return pipe->ring_start_addr + pipe_2d_ptr_span(ptr, pipe->ring_start); +} + +static inline bool pipe_2d_idle_on_noc(struct mwv207_pipe_2d *pipe) +{ + struct mwv207_device *jdev = pipe->jdev; + u32 pending1, pending2; + u32 val; + + pending1 = jdev->lite ? 0x70038000 : 0xF87FC000; + pending2 = jdev->lite ? 0x00000000 : 0x0000000F; + + val = jdev_read(jdev, (0x009B0194)); + if ((val & pending1) != pending1) + return false; + val = jdev_read(jdev, (0x009B0198)); + if ((val & pending2) != pending2) + return false; + return true; +} + +static int pipe_2d_hw_init(struct mwv207_pipe_2d *pipe) +{ + int ret; + u32 val; + + ret = wait_for(pipe_2d_idle_on_noc(pipe), 5000); + if (ret) { + pr_err("mwv207: failed to wait %s idle, %#x %#x", + pipe->base.fname, + jdev_read(pipe->jdev, (0x009B0194)), + jdev_read(pipe->jdev, (0x009B0198))); + return ret; + } + + + val = jdev_read(pipe->jdev, (0x009B0048)); + val &= ~(1 << 8); + jdev_write(pipe->jdev, (0x009B0048), val); + + + val = jdev_read(pipe->jdev, (0x009B0014)); + val &= ~(1 << 16); + jdev_write(pipe->jdev, (0x009B0014), val); + + + val = jdev_read(pipe->jdev, (0x009B0048)); + val |= 1 << 8; + jdev_write(pipe->jdev, (0x009B0048), val); + + + val = jdev_read(pipe->jdev, (0x009B0014)); + val |= 1 << 16; + jdev_write(pipe->jdev, (0x009B0014), val); + + msleep(10); + + + pipe_2d_write(pipe, 0x4800, 0); + + ret = wait_for(pipe_2d_read(pipe, 0x4800), 5000); + if (ret) { + pr_err("mwv207: failed to reset %s status RAM", + pipe->base.fname); + return ret; + } + + return 0; +} + +static void pipe_2d_reset_ring(struct mwv207_pipe_2d *pipe) +{ + struct pipe_2d_fence *fence, *p; + unsigned long flags; + + pipe->head = pipe->ring_start; + pipe->tail = pipe->ring_start; + + spin_lock_irqsave(&pipe->fence_queue_lock, flags); + list_for_each_entry_safe(fence, p, &pipe->fence_queue, q) { + list_del(&fence->q); + dma_fence_put(&fence->base); + } + spin_unlock_irqrestore(&pipe->fence_queue_lock, flags); +} + +static u32 *pipe_2d_append_wl(struct mwv207_pipe_2d *pipe) +{ + u32 *wait = pipe->tail; + + *pipe->tail++ = 0x88000000; + *pipe->tail++ = 0x80000000; + *pipe->tail++ = 0x89000000; + *pipe->tail++ = pipe_2d_gpu_addr(pipe, wait);; + + return wait; +} + +static void pipe_2d_start(struct mwv207_pipe_2d *pipe) +{ + u32 regval = pipe->ring_start_addr >> 4 | 1 << 29; + + pipe_2d_write(pipe, 0x4200, regval); + + *pipe->tail++ = 0x80000000; + *pipe->tail++ = 1; + *pipe->tail++ = 0x80000000; + *pipe->tail++ = 0x80000000; + + pipe->last_wait = pipe_2d_append_wl(pipe); + BUG_ON(pipe_2d_ptr_span(pipe->tail, pipe->ring_start) != 32); + pipe_2d_barrier(pipe); + pipe_2d_write(pipe, 0x4C00, 1); + + dev_dbg(pipe->base.jdev->dev, "start 2d wait link at 0x%llx", + pipe->ring_start_addr); +} + +static inline void pipe_2d_stop(struct mwv207_pipe_2d *pipe) +{ + pr_info("mwv207: %s %s TBD", pipe->base.fname, __func__); +} + +static void mwv207_pipe_2d_reset(struct mwv207_pipe *mpipe) +{ + struct mwv207_pipe_2d *pipe = to_pipe_2d(mpipe); + + if (pipe_2d_hw_init(pipe)) + return; + pipe_2d_reset_ring(pipe); + pipe_2d_start(pipe); +} + +static u32 *pipe_2d_wait_for_space(struct mwv207_pipe_2d *pipe, u32 size) +{ + int i; + + for (i = 0; i < 10000; i++) { + u32 *head = READ_ONCE(pipe->head); + if (head <= pipe->tail) { + if (pipe_2d_ptr_span(pipe->ring_end, pipe->tail) >= size) + return pipe->tail; + if (pipe_2d_ptr_span(head, pipe->ring_start) >= size) + return pipe->ring_start; + } else { + if (pipe_2d_ptr_span(head, pipe->tail) >= size) + return pipe->tail; + } + usleep_range(200, 200); + } + + return NULL; +} + +static struct dma_fence *pipe_2d_append_fence(struct mwv207_pipe_2d *pipe) +{ + struct pipe_2d_fence *fence; + unsigned long flags; + u32 regno; + + fence = kzalloc(sizeof(struct pipe_2d_fence), GFP_KERNEL); + if (!fence) { + pr_err("mwv207: failed to alloc memory for %s", + pipe->base.fname); + return ERR_PTR(-ENOMEM); + } + dma_fence_init(&fence->base, &pipe_2d_fence_ops, &pipe->fence_lock, + pipe->fence_ctx, ++pipe->next_fence); + fence->pipe = pipe; + fence->rpos = pipe->tail; + regno = (pipe->next_fence & 0x1f) + 1; + + spin_lock_irqsave(&pipe->fence_queue_lock, flags); + list_add_tail(&fence->q, &pipe->fence_queue); + spin_unlock_irqrestore(&pipe->fence_queue_lock, flags); + + *pipe->tail++ = (0x40000000 | 0x6000 | (0) << 2); + *pipe->tail++ = (u32)(fence->base.seqno & 0xffff); + *pipe->tail++ = (0x40000000 | 0x6000 | (regno) << 2); + *pipe->tail++ = regno; + + pipe_2d_write(pipe, 0x6000 + regno * 4, regno); + + dma_fence_get(&fence->base); + return &fence->base; +} + +static void pipe_2d_wtol(struct mwv207_pipe_2d *pipe, u32 *start, u32 *new_wait) +{ + u32 *pos = pipe->last_wait; + + pos[1] = pipe_2d_gpu_addr(pipe, start); + pipe_2d_barrier(pipe); + pos[0] = 0x89000000; + pipe_2d_barrier(pipe); + pipe_2d_write(pipe, 0x4C00, 1); + + pipe->last_wait = new_wait; +} + +static void mwv207_pipe_2d_dump_state(struct mwv207_pipe *mpipe) +{ + pr_info("%s %08x: %08x, %08x: %08x", mpipe->fname, + 0x4100, pipe_read(mpipe, 0x4100), + 0x961100, jdev_read(mpipe->jdev, 0x961100)); +} + +static struct dma_fence * +mwv207_pipe_2d_submit(struct mwv207_pipe *mpipe, struct mwv207_job *mjob) +{ + struct mwv207_pipe_2d *pipe = to_pipe_2d(mpipe); + u32 size = ALIGN(mjob->cmd_size, 16) + 128; + u32 *start, *wait, *last_tail, nop_cnt; + struct dma_fence *fence; + + start = pipe_2d_wait_for_space(pipe, size); + if (!start) { + pr_err("mwv207: %s is stuck", mpipe->fname); + return ERR_PTR(-EBUSY); + } + last_tail = pipe->tail; + pipe->tail = start; + + + *pipe->tail++ = 0x80000000; + pipe->tail++; + *pipe->tail++ = 0x80000000; + *pipe->tail++ = 0x80000000; + + + memcpy_toio(pipe->tail, mjob->cmds, mjob->cmd_size); + pipe->tail += mjob->cmd_size >> 2; + for (nop_cnt = (ALIGN(mjob->cmd_size, 16) - mjob->cmd_size); + nop_cnt > 0; nop_cnt -= 4) + *pipe->tail++ = 0x80000000; + + + fence = pipe_2d_append_fence(pipe); + if (IS_ERR(fence)) { + pipe->tail = last_tail; + return fence; + } + + + wait = pipe_2d_append_wl(pipe); + + + BUG_ON(pipe_2d_ptr_span(pipe->tail, start) & 0xf); + start[1] = pipe_2d_ptr_span(pipe->tail, start) / 16 - 1; + + + pipe_2d_wtol(pipe, start, wait); + + BUG_ON(pipe_2d_ptr_span(pipe->tail, start) > size); + + return fence; +} + +static irqreturn_t mwv207_pipe_2d_isr(int irq_unused, void *data) +{ + struct mwv207_pipe_2d *pipe = data; + struct pipe_2d_fence *fence, *p; + u32 seqno; + + pipe_2d_write(pipe, 0x4308, 0xFFFFFFFF); + seqno = pipe_2d_read(pipe, 0x6000); + + spin_lock(&pipe->fence_queue_lock); + list_for_each_entry_safe(fence, p, &pipe->fence_queue, q) { + if (fence16_after(fence->base.seqno & 0xffff, seqno & 0xffff)) + break; + list_del(&fence->q); + pipe->completed_fence = fence->base.seqno; + WRITE_ONCE(pipe->head, fence->rpos); + mwv207_devfreq_record_idle(&pipe->base); + dma_fence_signal(&fence->base); + dma_fence_put(&fence->base); + } + spin_unlock(&pipe->fence_queue_lock); + + return IRQ_HANDLED; +} + +static void mwv207_pipe_2d_destroy(struct mwv207_pipe *mpipe) +{ + struct mwv207_pipe_2d *pipe = to_pipe_2d(mpipe); + struct mwv207_bo *mbo = pipe->ring_bo; + int ret; + + free_irq(pipe->irq, pipe); + pipe_2d_stop(pipe); + + ret = mwv207_bo_reserve(mbo, true); + if (ret) { + pr_err("mwv207: failed to reserve bo"); + return; + } + mwv207_bo_kunmap_reserved(mbo); + mwv207_bo_unpin_reserved(mbo); + mwv207_bo_unreserve(mbo); + mwv207_bo_unref(mbo); + + mwv207_devfreq_unregister(mpipe); +} + +static unsigned long pipe_2d_max_freq_get(struct mwv207_device *jdev) +{ + unsigned long freq; + + switch (jdev->base.pdev->subsystem_device) { + case 0x9103: + freq = 600; + break; + case 0x9210: + case 0x9211: + freq = 800; + break; + case 0x930B: + freq = 1000; + break; + case 0x9101: + case 0x9102: + case 0x910A: + case 0x910B: + case 0x910C: + case 0x9200: + case 0x920A: + case 0x920B: + case 0x920C: + case 0x9230: + case 0x9231: + case 0x930A: + case 0x930C: + freq = 1200; + break; + default: + freq = DEFAULT_MAX_FREQ_2D; + break; + } + + return freq; +} + +struct mwv207_pipe *mwv207_pipe_2d_create(struct mwv207_device *jdev, + int id, u32 regbase, + const char *fname) +{ + struct mwv207_pipe_2d *pipe; + struct mwv207_bo *mbo; + void *addr; + int ret; + + pipe = devm_kzalloc(jdev->dev, sizeof(*pipe), GFP_KERNEL); + if (!pipe) + return NULL; + pipe->base.pll_id = MWV207_PLL_GU2D_PLL; + pipe->base.jdev = jdev; + pipe->base.fname = fname; + pipe->base.regbase = jdev->mmio + regbase; + pipe->base.iosize = 32 * 1024; + pipe->base.submit = mwv207_pipe_2d_submit; + pipe->base.reset = mwv207_pipe_2d_reset; + pipe->base.destroy = mwv207_pipe_2d_destroy; + pipe->base.dump_state = mwv207_pipe_2d_dump_state; + + pipe->jdev = jdev; + pipe->id = id; + pipe->fence_ctx = dma_fence_context_alloc(1); + INIT_LIST_HEAD(&pipe->fence_queue); + spin_lock_init(&pipe->fence_lock); + spin_lock_init(&pipe->fence_queue_lock); + + ret = mwv207_devfreq_register(&pipe->base, pipe_2d_max_freq_get(jdev)); + if (ret) + pipe->base.devfreq = NULL; + + ret = mwv207_bo_create(jdev, 0x40000, 0x1000, + ttm_bo_type_kernel, 0x2, + (1<<0), &mbo); + if (ret) + goto unregister_devfreq; + + ret = mwv207_bo_reserve(mbo, true); + if (ret) + goto free_bo; + ret = mwv207_bo_pin_reserved(mbo, 0x2); + if (ret) + goto unreserve_bo; + ret = mwv207_bo_kmap_reserved(mbo, &addr); + if (ret) + goto unpin_bo; + pipe->ring_bo = mbo; + + pipe->ring_start = (u32 *)addr; + pipe->ring_start_addr = mwv207_bo_gpu_phys(mbo); + pipe->ring_end = pipe->ring_start + (0x40000 >> 2); + + mwv207_pipe_2d_reset(&pipe->base); + + pipe->irq = irq_find_mapping(jdev->irq_domain, 17 + id); + if (pipe->irq == 0) { + pr_err("mwv207: failed to find %s irq mapping", fname); + goto unmap_bo; + } + + ret = request_irq(pipe->irq, mwv207_pipe_2d_isr, 0, fname, pipe); + if (ret) { + pr_err("mwv207: failed to request irq for %s", fname); + goto unmap_bo; + } + + mwv207_bo_unreserve(mbo); + + return &pipe->base; +unmap_bo: + mwv207_bo_kunmap_reserved(mbo); +unpin_bo: + mwv207_bo_unpin_reserved(mbo); +unreserve_bo: + mwv207_bo_unreserve(mbo); +free_bo: + mwv207_bo_unref(mbo); +unregister_devfreq: + mwv207_devfreq_unregister(&pipe->base); + return NULL; +} diff --git a/drivers/gpu/drm/mwv207/mwv207_pipe_3d.c b/drivers/gpu/drm/mwv207/mwv207_pipe_3d.c new file mode 100644 index 0000000000000000000000000000000000000000..db6b9d9775758cd29ffb72f952c888d9435622e3 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_pipe_3d.c @@ -0,0 +1,613 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include +#include +#include "mwv207_bo.h" +#include "mwv207_sched.h" +#include "mwv207_vbios.h" +#include "mwv207_devfreq.h" + +#define DEFAULT_MAX_FREQ_3D 600 + +#define to_3d_pipe(pipe) container_of(pipe, struct mwv207_pipe_3d, base) +struct mwv207_pipe_3d { + struct mwv207_pipe base; + struct mwv207_bo *ringbuf_bo; + u32 *ringbuf; + u32 *head; + u32 *tail; + u32 *end; + u32 ringbuf_gpu_addr; + int unit; + unsigned int irq; + + u64 fence_ctx; + u32 next_fence; + u32 completed_fence; + spinlock_t fence_lock; + + DECLARE_BITMAP(event_bitmap, 30); + struct dma_fence *event_fence[30]; + spinlock_t event_lock; +}; + +#define to_pipe_3d_fence(f) container_of(f, struct pipe_3d_fence, base) +struct pipe_3d_fence { + struct dma_fence base; + u32 *rpos; + struct mwv207_pipe_3d *pipe; +}; + +static inline void pipe_3d_write(struct mwv207_pipe_3d *pipe, u32 reg, u32 value) +{ + pipe_write(&pipe->base, reg, value); +} + +static inline u32 pipe_3d_read(struct mwv207_pipe_3d *pipe, u32 reg) +{ + return pipe_read(&pipe->base, reg); +} + + +static inline bool fence_after(u32 a, u32 b) +{ + return (s32)(a - b) > 0; +} + +static const char *pipe_3d_fence_get_driver_name(struct dma_fence *fence) +{ + return "mwv207"; +} + +static const char *pipe_3d_fence_get_timeline_name(struct dma_fence *fence) +{ + return to_pipe_3d_fence(fence)->pipe->base.fname; +} + +static bool pipe_3d_fence_signaled(struct dma_fence *fence) +{ + struct pipe_3d_fence *f = to_pipe_3d_fence(fence); + + return fence_after(f->pipe->completed_fence, f->base.seqno); +} + +static void pipe_3d_fence_release(struct dma_fence *fence) +{ + struct pipe_3d_fence *f = to_pipe_3d_fence(fence); + + kfree_rcu(f, base.rcu); +} + +static const struct dma_fence_ops pipe_3d_fence_ops = { + .get_driver_name = pipe_3d_fence_get_driver_name, + .get_timeline_name = pipe_3d_fence_get_timeline_name, + .signaled = pipe_3d_fence_signaled, + .release = pipe_3d_fence_release, +}; + + +static inline struct dma_fence *pipe_3d_event_pop_irq(struct mwv207_pipe_3d *pipe, u32 event) +{ + struct dma_fence *fence; + + spin_lock(&pipe->event_lock); + fence = pipe->event_fence[event]; + pipe->event_fence[event] = NULL; + clear_bit(event, pipe->event_bitmap); + spin_unlock(&pipe->event_lock); + + return fence; +} + + +static inline u32 pipe_3d_event_push(struct mwv207_pipe_3d *pipe, struct dma_fence *fence) +{ + u32 event; + + spin_lock_irq(&pipe->event_lock); + event = find_first_zero_bit(pipe->event_bitmap, 30); + if (likely(event < 30)) + pipe->event_fence[event] = dma_fence_get(fence); + set_bit(event, pipe->event_bitmap); + spin_unlock_irq(&pipe->event_lock); + + + BUG_ON(event >= 30); + + return event; +} + +static inline u32 pipe_3d_ptr_span(void *end, void *start) +{ + return (unsigned long)end - (unsigned long)start; +} + +static inline u32 pipe_3d_gpu_addr(struct mwv207_pipe_3d *pipe, u32 *ptr) +{ + return pipe->ringbuf_gpu_addr + pipe_3d_ptr_span(ptr, pipe->ringbuf); +} + +static inline void pipe_3d_link(struct mwv207_pipe_3d *pipe, u32 prefetch, u32 addr) +{ + pipe->tail = PTR_ALIGN(pipe->tail, 8); + BUG_ON(pipe->tail >= pipe->end - 1 + || (((prefetch + 7) / 8) & 0xffff0000) + || addr & 0x7); + *pipe->tail++ = 0x40000000 | ((prefetch + 7) / 8); + *pipe->tail++ = addr; +} + +static inline void pipe_3d_wait(struct mwv207_pipe_3d *pipe) +{ + pipe->tail = PTR_ALIGN(pipe->tail, 8); + BUG_ON(pipe->tail >= pipe->end); + + *pipe->tail++ = 0x38000000 | 200; +} + +static inline void pipe_3d_wl(struct mwv207_pipe_3d *pipe) +{ + pipe_3d_wait(pipe); + pipe_3d_link(pipe, 16, pipe_3d_gpu_addr(pipe, pipe->tail) - 4); +} + +static inline void pipe_3d_wtol(struct mwv207_pipe_3d *pipe, u32 *w, u32 target, u32 size) +{ + BUG_ON((((size + 7) / 8) & 0xffff0000) || target & 0x7 + || w < pipe->ringbuf || w >= pipe->end - 1); + w[1] = target; + pipe_3d_read(pipe, 0x4); + mb(); + w[0] = 0x40000000 | ((size + 7) / 8); +} + +static inline void pipe_3d_loadstate(struct mwv207_pipe_3d *pipe, u32 reg, u32 val) +{ + pipe->tail = PTR_ALIGN(pipe->tail, 8); + BUG_ON(pipe->tail >= pipe->end - 1); + + *pipe->tail++ = 0x08010000 | reg; + *pipe->tail++ = val; +} + +static inline void pipe_3d_sem(struct mwv207_pipe_3d *pipe, u32 from, u32 to) +{ + from &= 0x1f; + to &= 0x1f; + to <<= 8; + pipe_3d_loadstate(pipe, 0x0e02, from | to); +} + +static inline void pipe_3d_stall(struct mwv207_pipe_3d *pipe, u32 from, u32 to) +{ + pipe->tail = PTR_ALIGN(pipe->tail, 8); + BUG_ON(pipe->tail >= pipe->end - 1); + from &= 0x1f; + to &= 0x1f; + to <<= 8; + *pipe->tail++ = 0x48000000; + *pipe->tail++ = from | to; +} + +static inline void pipe_3d_start(struct mwv207_pipe_3d *pipe) +{ + u32 *start = pipe->tail; + u32 len; + + dev_dbg(pipe->base.jdev->dev, "start wait link at 0x%x", pipe->ringbuf_gpu_addr); + pipe_3d_loadstate(pipe, 0x0e21, 0x00020202); + pipe_3d_loadstate(pipe, 0x0e44, 0x0000000f); + pipe_3d_loadstate(pipe, 0x0e80, 0x00000000); + pipe_3d_loadstate(pipe, 0x7003, 0x00000001); + + pipe_3d_wl(pipe); + + len = (pipe->tail - start) * 4; + pipe_3d_write(pipe, 0x014, 0xffffffff); + pipe_3d_write(pipe, 0x654, pipe->ringbuf_gpu_addr); + pipe_3d_read(pipe, 0x4); + mb(); + pipe_3d_write(pipe, 0x658, 0x10000 | (len / 8)); + pipe_3d_write(pipe, 0x3a4, 0x10000 | (len / 8)); +} + +static inline int pipe_3d_stop(struct mwv207_pipe_3d *pipe) +{ + pr_info("%s TBD", __func__); + return 0; +} + +static void pipe_3d_core_reset(struct mwv207_pipe_3d *pipe) +{ + int i; + + for (i = 0; i < 2; i++) { + + pipe_3d_write(pipe, 0x104, 0); + + pipe_3d_write(pipe, 0x10c, 0x015b0880); + pipe_3d_write(pipe, 0x10c, 0x015b0881); + pipe_3d_write(pipe, 0x10c, 0x015b0880); + + pipe_3d_write(pipe, 0x000, 0x00010b00); + pipe_3d_write(pipe, 0x000, 0x00010900); + + usleep_range(1000, 1001); + + pipe_3d_write(pipe, 0x000, 0x00090900); + + + pipe_3d_write(pipe, 0x3a8, 1); + pipe_3d_write(pipe, 0x3a8, 0); + + + usleep_range(1000, 1001); + + + pipe_3d_write(pipe, 0x000, 0x00010900); + } +} + +static void pipe_3d_hw_init(struct mwv207_pipe_3d *pipe) +{ + pipe_3d_write(pipe, 0x000, 0x00010900); + pipe_3d_core_reset(pipe); + + pipe_3d_write(pipe, 0x55c, 0x00ffffff); + pipe_3d_write(pipe, 0x414, 0x3c000000); + pipe_3d_write(pipe, 0x090, + pipe_3d_read(pipe, 0x090) & 0xffffffbf); + + pipe_3d_write(pipe, 0x000, 0x00010900); + pipe_3d_write(pipe, 0x000, 0x00070100); + pipe_3d_write(pipe, 0x3ac, 0x00002300); + pipe_3d_write(pipe, 0x3a8, 0x2); + + pipe_3d_write(pipe, 0x03c, 0xffffffff); + pipe_3d_write(pipe, 0x03c, 0x00000000); + + pipe_3d_write(pipe, 0x100, 0x00140021); + + + + pipe_3d_write(pipe, 0x154, 0x1); + pipe_3d_write(pipe, 0x104, 0x00430408); + pipe_3d_write(pipe, 0x10c, 0x015b0880); +} + +static void mwv207_pipe_3d_reset(struct mwv207_pipe *mpipe) +{ + struct mwv207_pipe_3d *pipe = to_3d_pipe(mpipe); + int i; + + + spin_lock_irq(&pipe->event_lock); + bitmap_zero(pipe->event_bitmap, 30); + for (i = 0; i < 30; i++) { + dma_fence_put(pipe->event_fence[i]); + pipe->event_fence[i] = NULL; + } + spin_unlock_irq(&pipe->event_lock); + + pipe_3d_hw_init(pipe); + pipe->tail = pipe->ringbuf; + pipe->head = pipe->ringbuf; + + pipe_3d_start(pipe); +} + + +static u32 *pipe_3d_wait_for_space(struct mwv207_pipe_3d *pipe, u32 size) +{ + u32 *head; + int i; + + for (i = 0; i < 10000; ++i) { + head = READ_ONCE(pipe->head); + + if (head <= pipe->tail) { + if (pipe_3d_ptr_span(pipe->end, pipe->tail) >= size) + return pipe->tail; + if (pipe_3d_ptr_span(head, pipe->ringbuf) >= size) + return pipe->ringbuf; + } else { + if (pipe_3d_ptr_span(head, pipe->tail) >= size) + return pipe->tail; + } + usleep_range(200, 200); + } + return NULL; +} + +static struct dma_fence *mwv207_pipe_3d_submit(struct mwv207_pipe *mpipe, + struct mwv207_job *mjob) +{ + struct mwv207_pipe_3d *pipe = to_3d_pipe(mpipe); + struct pipe_3d_fence *fence; + u32 *last_tail, *start; + u32 size, event; + + size = ALIGN(mjob->cmd_size, 8) + 256; + last_tail = pipe->tail; + start = pipe_3d_wait_for_space(pipe, size); + if (start == NULL) { + pr_err("mwv207: engine 3d is stuck"); + + return ERR_PTR(-EBUSY); + } + memcpy_toio(start, mjob->cmds, mjob->cmd_size); + pipe->tail = start + mjob->cmd_size / 4; + + pipe_3d_loadstate(pipe, 0x0e03, 0xc63); + pipe_3d_loadstate(pipe, 0x594, 0x41); + + pipe_3d_sem(pipe, 0x7, 0x1); + pipe_3d_stall(pipe, 0x7, 0x1); + pipe_3d_loadstate(pipe, 0x502e, 0x1); + pipe_3d_loadstate(pipe, 0x502b, 0x3); + pipe_3d_sem(pipe, 0x10, 0x1); + pipe_3d_stall(pipe, 0x10, 0x1); + pipe_3d_loadstate(pipe, 0x502e, 0x0); + + + fence = kzalloc(sizeof(struct pipe_3d_fence), GFP_KERNEL); + if (!fence) { + pipe->tail = last_tail; + pr_err("mwv207: failed to alloc memory for 3d pipe"); + + return ERR_PTR(-ENOMEM); + } + dma_fence_init(&fence->base, &pipe_3d_fence_ops, &pipe->fence_lock, + pipe->fence_ctx, ++pipe->next_fence); + fence->rpos = pipe->tail; + fence->pipe = pipe; + event = pipe_3d_event_push(pipe, &fence->base); + + + pipe_3d_loadstate(pipe, 0x502e, 0x1); + pipe_3d_loadstate(pipe, 0x50ce, 0xf); + pipe_3d_loadstate(pipe, 0x0e01, 0x80 | event); + pipe_3d_loadstate(pipe, 0x502e, 0x0); + + + pipe_3d_wl(pipe); + BUG_ON(pipe_3d_ptr_span(pipe->tail, start) > size); + pipe_3d_wtol(pipe, last_tail - 4, pipe_3d_gpu_addr(pipe, start), + pipe_3d_ptr_span(pipe->tail, start)); + return &fence->base; +} + +static void mwv207_pipe_3d_dump_state(struct mwv207_pipe *mpipe) +{ + u32 state0, addr0; + u32 state1, addr1; + int i; + + state0 = pipe_read(mpipe, 0x660); + addr0 = pipe_read(mpipe, 0x664); + for (i = 0; i < 500; ++i) { + state1 = pipe_read(mpipe, 0x660); + addr1 = pipe_read(mpipe, 0x664); + if (state1 != state0 || addr1 != addr0) + break; + } + + if (state1 != state0) + pr_info("%s state changing, s0: 0x%08x, s1: 0x%08x", mpipe->fname, state0, state1); + pr_info("%s current stat: 0x%08x", mpipe->fname, pipe_read(mpipe, 0x660)); + + if (addr1 != addr0) + pr_info("%s addr changing, a0: 0x%08x, a1: 0x%08x", mpipe->fname, addr0, addr1); + pr_info("%s current addr: 0x%08x", mpipe->fname, pipe_read(mpipe, 0x664)); + + pr_info("%s current cmdl: 0x%08x", mpipe->fname, pipe_read(mpipe, 0x668)); + pr_info("%s current cmdh: 0x%08x", mpipe->fname, pipe_read(mpipe, 0x66c)); +} + +static irqreturn_t mwv207_pipe_3d_isr(int irq_unused, void *dev_id) +{ + struct mwv207_pipe_3d *pipe = dev_id; + struct dma_fence *fence; + u32 event, intr; + + intr = pipe_3d_read(pipe, 0x10); + if (unlikely(!intr)) { + pr_warn("mwv207: 3d interrupt with no event"); + return IRQ_NONE; + } + if (unlikely(intr & 0x80000000)) { + pr_warn("mwv207: 3d axi bus error"); + intr &= ~0x80000000; + } + if (unlikely(intr & 0x40000000)) { + pr_warn("mwv207: 3d mmu error"); + intr &= ~0x40000000; + } + while ((event = ffs(intr))) { + event -= 1; + intr &= ~(1 << event); + + fence = pipe_3d_event_pop_irq(pipe, event); + if (!fence) { + pr_warn("unexpected event: %d", event); + continue; + } + + if (fence_after(fence->seqno, pipe->completed_fence)) { + struct pipe_3d_fence *pipe_fence = to_pipe_3d_fence(fence); + + pipe->completed_fence = fence->seqno; + pipe->head = pipe_fence->rpos; + } + mwv207_devfreq_record_idle(&pipe->base); + dma_fence_signal(fence); + dma_fence_put(fence); + } + return IRQ_HANDLED; +} + +static void mwv207_pipe_3d_destroy(struct mwv207_pipe *mpipe) +{ + struct mwv207_pipe_3d *pipe = to_3d_pipe(mpipe); + struct mwv207_bo *jbo; + int ret; + + free_irq(pipe->irq, pipe); + pipe_3d_stop(pipe); + + jbo = pipe->ringbuf_bo; + ret = mwv207_bo_reserve(jbo, true); + if (ret) { + pr_info("failed to reserve bo"); + return; + } + mwv207_bo_kunmap_reserved(jbo); + mwv207_bo_unpin_reserved(jbo); + mwv207_bo_unreserve(jbo); + mwv207_bo_unref(jbo); + + mwv207_devfreq_unregister(mpipe); +} + +static unsigned long pipe_3d_max_freq_get(struct mwv207_device *jdev) +{ + unsigned long freq; + + switch (jdev->base.pdev->subsystem_device) { + case 0x9103: + freq = 600; + break; + case 0x9210: + case 0x9211: + freq = 800; + break; + case 0x9101: + case 0x9102: + case 0x910A: + case 0x910B: + case 0x910C: + case 0x920C: + case 0x930B: + freq = 1000; + break; + case 0x9200: + case 0x920A: + case 0x920B: + case 0x9230: + case 0x9231: + case 0x930A: + case 0x930C: + freq = 1200; + break; + default: + freq = DEFAULT_MAX_FREQ_3D; + break; + } + + return freq; +} + +struct mwv207_pipe *mwv207_pipe_3d_create(struct mwv207_device *jdev, int unit, u32 regbase, + const char *fname) +{ + struct mwv207_pipe_3d *pipe; + struct mwv207_bo *jbo; + void *logical; + int ret; + + if (jdev->lite && unit) + return NULL; + + pipe = devm_kzalloc(jdev->dev, sizeof(struct mwv207_pipe_3d), GFP_KERNEL); + if (!pipe) + return NULL; + + pipe->base.pll_id = unit ? MWV207_PLL_GU3D1 : MWV207_PLL_GU3D0; + pipe->base.jdev = jdev; + pipe->base.fname = fname; + pipe->base.regbase = jdev->mmio + regbase; + pipe->base.iosize = 16 * 1024; + pipe->base.submit = mwv207_pipe_3d_submit; + pipe->base.reset = mwv207_pipe_3d_reset; + pipe->base.destroy = mwv207_pipe_3d_destroy; + pipe->base.dump_state = mwv207_pipe_3d_dump_state; + + pipe->unit = unit; + pipe->fence_ctx = dma_fence_context_alloc(1); + spin_lock_init(&pipe->fence_lock); + spin_lock_init(&pipe->event_lock); + + ret = mwv207_devfreq_register(&pipe->base, pipe_3d_max_freq_get(jdev)); + if (ret) + pipe->base.devfreq = NULL; + + ret = mwv207_bo_create(jdev, 0x40000, 0x1000, + ttm_bo_type_kernel, 0x2, (1<<0), + &jbo); + if (ret) + goto unregister_devfreq; + + ret = mwv207_bo_reserve(jbo, true); + if (ret) + goto free_bo; + + ret = mwv207_bo_pin_reserved(jbo, 0x2); + if (ret) + goto unreserve_bo; + + ret = mwv207_bo_kmap_reserved(jbo, &logical); + if (ret) + goto unpin_bo; + + pipe->ringbuf_bo = jbo; + + + pipe->ringbuf = (u32 *)logical; + pipe->ringbuf_gpu_addr = mwv207_bo_gpu_phys(jbo); + pipe->end = pipe->ringbuf + (0x40000 / 4); + + mwv207_pipe_3d_reset(&pipe->base); + + pipe->irq = irq_find_mapping(jdev->irq_domain, 19 + unit); + if (pipe->irq == 0) { + pr_err("mwv207: failed to create 3D irq mapping"); + goto unmap_bo; + } + + ret = request_irq(pipe->irq, mwv207_pipe_3d_isr, 0, fname, pipe); + if (ret) { + pr_err("mwv207: failed to request 3d irq"); + goto unmap_bo; + } + + mwv207_bo_unreserve(jbo); + return &pipe->base; +unmap_bo: + mwv207_bo_kunmap_reserved(jbo); +unpin_bo: + mwv207_bo_unpin_reserved(jbo); +unreserve_bo: + mwv207_bo_unreserve(jbo); +free_bo: + mwv207_bo_unref(pipe->ringbuf_bo); +unregister_devfreq: + mwv207_devfreq_unregister(&pipe->base); + return NULL; +} diff --git a/drivers/gpu/drm/mwv207/mwv207_pipe_codec_common.c b/drivers/gpu/drm/mwv207/mwv207_pipe_codec_common.c new file mode 100644 index 0000000000000000000000000000000000000000..335fed1502e9ce1d9f9c6c66c9d5da295e746ea7 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_pipe_codec_common.c @@ -0,0 +1,87 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include "mwv207_pipe_codec_common.h" + +static void pipe_codec_sw_pp_submit_ex(struct pipe_codec_sw_pp *sw_pp, struct mwv207_pipe *pipe, + u64 vram, u32 reg, int nr) +{ + if (sw_pp->wb_cnt >= JPIPE_WRITE_ENTRY_SIZE) { + dev_dbg(pipe->jdev->dev, "mwv207: too much write back entry"); + return; + } + + sw_pp->wb[sw_pp->wb_cnt].vram = vram; + sw_pp->wb[sw_pp->wb_cnt].reg = reg; + sw_pp->wb[sw_pp->wb_cnt].nr = nr; + + sw_pp->wb_cnt++; + sw_pp->pending = true; +} + +void pipe_codec_sw_pp_reset(struct pipe_codec_sw_pp *sw_pp) +{ + sw_pp->wb_cnt = 0; +} + +void pipe_codec_sw_pp_excute(struct pipe_codec_sw_pp *sw_pp, struct mwv207_pipe *pipe) +{ + int i, j; + + if (!sw_pp->pending) + return; + + for (i = 0; i < sw_pp->wb_cnt; i++) { + for (j = 0; j < sw_pp->wb[i].nr && sw_pp->wb[i].reg + j * 4 <= pipe->iosize - 4; j++) + sw_pp->wb_buf[j] = pipe_read(pipe, sw_pp->wb[i].reg + j * 4); + + if (!j) + continue; + + jdev_write_vram(pipe->jdev, sw_pp->wb[i].vram, &sw_pp->wb_buf[0], j * 4); + } + + + wmb(); + + sw_pp->wb_cnt = 0; + sw_pp->pending = false; +} + +void pipe_codec_sw_pp_submit(struct pipe_codec_sw_pp *sw_pp, struct mwv207_pipe *pipe, + struct mwv207_job *mjob, int pos, int *cmdlen) +{ + int nr, reg; + u64 vram; + u32 cmd; + + *cmdlen = 16; + + if (pos + 16 > mjob->cmd_size) { + dev_dbg(pipe->jdev->dev, + "mwv207: cmd %d write back out of range", pos); + return; + } + + cmd = *(u32 *)(mjob->cmds + pos); + reg = (cmd & 0xffff) * 4; + nr = (cmd >> 16) & 0x3ff; + if (nr == 0) + nr = 1024; + + vram = *(u64 *)(mjob->cmds + pos + 8); + pipe_codec_sw_pp_submit_ex(sw_pp, pipe, vram, reg, nr); +} + diff --git a/drivers/gpu/drm/mwv207/mwv207_pipe_codec_common.h b/drivers/gpu/drm/mwv207/mwv207_pipe_codec_common.h new file mode 100644 index 0000000000000000000000000000000000000000..46ebfb86b5f12edcb9ce247a0fb4090c11bdab88 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_pipe_codec_common.h @@ -0,0 +1,42 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include "mwv207_sched.h" + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define JPIPE_WRITE_ENTRY_SIZE 16 +#define JPIPE_ENC_REG_NUM 500 +#define JPIPE_DEC_REG_NUM 503 +#define JPIPE_REG_NUM MAX(JPIPE_ENC_REG_NUM, JPIPE_DEC_REG_NUM) + +struct pipe_codec_wb_entry { + u64 vram; + u32 reg; + u32 nr; +}; + + +struct pipe_codec_sw_pp { + struct pipe_codec_wb_entry wb[JPIPE_WRITE_ENTRY_SIZE]; + u32 wb_buf[JPIPE_REG_NUM]; + int wb_cnt; + bool pending; +}; + +void pipe_codec_sw_pp_reset(struct pipe_codec_sw_pp *sw_pp); +void pipe_codec_sw_pp_excute(struct pipe_codec_sw_pp *sw_pp, struct mwv207_pipe *pipe); +void pipe_codec_sw_pp_submit(struct pipe_codec_sw_pp *sw_pp, struct mwv207_pipe *pipe, + struct mwv207_job *mjob, int pos, int *cmdlen); + diff --git a/drivers/gpu/drm/mwv207/mwv207_pipe_dec.c b/drivers/gpu/drm/mwv207/mwv207_pipe_dec.c new file mode 100644 index 0000000000000000000000000000000000000000..3b63a1cb34f080c467816a11397feca802f54e8f --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_pipe_dec.c @@ -0,0 +1,264 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include "mwv207_pipe_codec_common.h" + +#define to_dec_pipe(pipe) container_of(pipe, struct mwv207_pipe_dec, base) +struct pipe_dec_fence; +struct mwv207_pipe_dec { + struct mwv207_pipe base; + + u64 fence_ctx; + u64 fence_seqno; + spinlock_t fence_lock; + + struct pipe_dec_fence *current_fence; + struct task_struct *poll_thread; + struct pipe_codec_sw_pp sw_pp; +}; + +#define to_pipe_dec_fence(f) container_of(f, struct pipe_dec_fence, base) +struct pipe_dec_fence { + struct dma_fence base; + struct mwv207_pipe_dec *pipe; + struct list_head node; +}; + +static inline void pipe_dec_write(struct mwv207_pipe_dec *pipe, u32 reg, u32 value) +{ + pipe_write(&pipe->base, reg, value); +} + +static inline u32 pipe_dec_read(struct mwv207_pipe_dec *pipe, u32 reg) +{ + return pipe_read(&pipe->base, reg); +} + +static const char *pipe_dec_fence_get_driver_name(struct dma_fence *fence) +{ + return "mwv207"; +} + +static const char *pipe_dec_fence_get_timeline_name(struct dma_fence *fence) +{ + return to_pipe_dec_fence(fence)->pipe->base.fname; +} + +static void pipe_dec_fence_release(struct dma_fence *fence) +{ + struct pipe_dec_fence *f = to_pipe_dec_fence(fence); + + kfree_rcu(f, base.rcu); +} + +static const struct dma_fence_ops pipe_dec_fence_ops = { + .get_driver_name = pipe_dec_fence_get_driver_name, + .get_timeline_name = pipe_dec_fence_get_timeline_name, + .release = pipe_dec_fence_release, +}; + +static void pipe_dec_stat_reset(struct mwv207_pipe_dec *pipe, u32 stat) +{ + pipe_dec_write(pipe, 0x4, 0x0); +} + +static int pipe_dec_polling(void *priv) +{ + struct mwv207_pipe_dec *pipe = priv; + struct pipe_dec_fence *fence; + u32 stat; + + set_freezable(); + while (!kthread_should_stop()) { + usleep_range(1000, 1001); + try_to_freeze(); + + + fence = smp_load_acquire(&pipe->current_fence); + if (fence == NULL) + continue; + + stat = pipe_dec_read(pipe, 0x4); + if ((stat & 0x1100) != 0x1100) + continue; + + pipe_codec_sw_pp_excute(&pipe->sw_pp, &pipe->base); + pipe_dec_stat_reset(pipe, stat); + + + smp_store_release(&pipe->current_fence, NULL); + + dma_fence_signal(&fence->base); + dma_fence_put(&fence->base); + } + + return 0; +} + +static void mwv207_pipe_dec_reset(struct mwv207_pipe *mpipe) +{ + struct mwv207_pipe_dec *pipe = to_dec_pipe(mpipe); + struct pipe_dec_fence *fence; + u32 stat; + int i; + + pipe_codec_sw_pp_reset(&pipe->sw_pp); + + stat = pipe_dec_read(pipe, 0x4); + if (stat & 0x1) + pipe_dec_write(pipe, 0x4, + 0x20 | 0x30); + + pipe_dec_write(pipe, 0x4, 0); + pipe_dec_write(pipe, 0x8, 0x454); + + + for (i = 12; i < pipe->base.iosize; i += 0x4) + pipe_dec_write(pipe, i, 0); + + + fence = smp_load_acquire(&pipe->current_fence); + if (fence) { + dma_fence_put(&fence->base); + + smp_store_release(&pipe->current_fence, NULL); + } +} + +static struct dma_fence *mwv207_pipe_dec_submit(struct mwv207_pipe *mpipe, + struct mwv207_job *mjob) +{ + struct mwv207_pipe_dec *pipe = to_dec_pipe(mpipe); + struct pipe_dec_fence *fence; + int i, nr, pos, cmdlen; + bool hw_run = 0; + u32 cmd, reg; + + /* sanity check, there should be no previous job. + * acquire pairs with release in polling thread + */ + fence = smp_load_acquire(&pipe->current_fence); + if (fence) + return ERR_PTR(-EBUSY); + + + for (pos = 0; pos <= mjob->cmd_size - 4; pos += cmdlen) { + cmd = *(u32 *)(mjob->cmds + pos); + switch ((cmd >> 27) & 0x1f) { + case 0x01: + if ((cmd >> 26) & 0x1) + return ERR_PTR(-EINVAL); + reg = (cmd & 0xffff) * 4; + nr = (cmd >> 16) & 0x3ff; + if (nr == 0) + nr = 1024; + for (i = pos + 4; i <= pos + nr * 4 + && i <= mjob->cmd_size - 4 + && reg <= pipe->base.iosize - 4; + i += 4, reg += 4) { + if (reg == 4) { + pipe_dec_read(pipe, 0x4); + hw_run = (*(u32 *)(mjob->cmds + i)) & 0x1; + + mb(); + } + pipe_dec_write(pipe, reg, *(u32 *)(mjob->cmds + i)); + } + + if (i <= pos + nr * 4) + dev_dbg(mpipe->jdev->dev, + "mwv207: invalid dec register writes dropped"); + + cmdlen = ALIGN(4 + nr * 4, 8); + break; + case 0x02: + pipe_codec_sw_pp_submit(&pipe->sw_pp, &pipe->base, mjob, pos, &cmdlen); + break; + default: + + return ERR_PTR(-EINVAL); + } + } + + + fence = kzalloc(sizeof(struct pipe_dec_fence), GFP_KERNEL); + if (!fence) + return ERR_PTR(-ENOMEM); + fence->pipe = pipe; + dma_fence_init(&fence->base, &pipe_dec_fence_ops, &pipe->fence_lock, + pipe->fence_ctx, ++pipe->fence_seqno); + + if (hw_run) { + dma_fence_get(&fence->base); + + smp_store_release(&pipe->current_fence, fence); + } else { + pipe_codec_sw_pp_excute(&pipe->sw_pp, &pipe->base); + dma_fence_signal(&fence->base); + } + + return &fence->base; +} + +static void mwv207_pipe_dec_dump_state(struct mwv207_pipe *mpipe) +{ + pr_info("%s irq state reg: 0x%08x", mpipe->fname, + pipe_read(mpipe, 0x4)); +} + +static void mwv207_pipe_dec_destroy(struct mwv207_pipe *mpipe) +{ + struct mwv207_pipe_dec *pipe = to_dec_pipe(mpipe); + + kthread_stop(pipe->poll_thread); +} + +struct mwv207_pipe *mwv207_pipe_dec_create(struct mwv207_device *jdev, int unit, u32 regbase, + const char *fname) +{ + struct mwv207_pipe_dec *pipe; + + if (jdev->lite && unit) + return NULL; + + pipe = devm_kzalloc(jdev->dev, sizeof(struct mwv207_pipe_dec), GFP_KERNEL); + if (!pipe) + return NULL; + + pipe->base.jdev = jdev; + pipe->base.fname = fname; + pipe->base.regbase = jdev->mmio + regbase; + pipe->base.iosize = JPIPE_DEC_REG_NUM * 4; + pipe->base.submit = mwv207_pipe_dec_submit; + pipe->base.reset = mwv207_pipe_dec_reset; + pipe->base.destroy = mwv207_pipe_dec_destroy; + pipe->base.dump_state = mwv207_pipe_dec_dump_state; + + spin_lock_init(&pipe->fence_lock); + pipe->fence_ctx = dma_fence_context_alloc(1); + pipe->fence_seqno = 0; + + mwv207_pipe_dec_reset(&pipe->base); + + pipe->poll_thread = kthread_run(pipe_dec_polling, pipe, "dec_polling"); + if (IS_ERR(pipe->poll_thread)) + return NULL; + + return &pipe->base; +} diff --git a/drivers/gpu/drm/mwv207/mwv207_pipe_dma.c b/drivers/gpu/drm/mwv207/mwv207_pipe_dma.c new file mode 100644 index 0000000000000000000000000000000000000000..974129a6b35f47b4d4ed0b3c8fabbfc2ad4086c9 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_pipe_dma.c @@ -0,0 +1,418 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include +#include +#include "mwv207_bo.h" +#include "mwv207_sched.h" + +#undef MWV207_DMA_DEBUG + +#define DMA_MAX_LLI_COUNT (0x400000 / sizeof(struct mwv207_lli)) + +#define DMA_TS_NOT_ALIGNED(size) ((size) - 1) +#define DMA_TS_ALIGNED(size, alignment) (((size) >= (alignment)) ? ((size) / (alignment) - 1) : 0) +#define DMA_CTL_NOT_ALIGNED ((1UL<<(63)) \ + | (1UL<<(58)) \ + | (1UL<<(30)) \ + | (1UL<<(38)) \ + | (1UL<<(47)) \ + | ((32ul & (0xFF))<<(39)) \ + | ((32ul & (0xFF))<<(48)) \ + | ((0UL & (0x7))<<(8)) \ + | ((0UL & (0x7))<<(11))) + +#define DMA_CTL_ALIGNED(a) ((1UL<<(63)) \ + | (1UL<<(58)) \ + | (1UL<<(30)) \ + | (1UL<<(38)) \ + | (1UL<<(47)) \ + | ((32ul & (0xFF))<<(39)) \ + | ((32ul & (0xFF))<<(48)) \ + | (((((a) == 64)?6UL:5UL) & (0x7))<<(8)) \ + | (((((a) == 64)?6UL:5UL) & (0x7))<<(11))) + +#define mwv207_timed_loop(tick, cond, timeout) \ + for ((tick) = jiffies; (cond) && time_before(jiffies, (tick) + msecs_to_jiffies(timeout));) + +struct mwv207_lli { + u64 sar; + u64 dar; + u32 ts; + u32 resv1; + u64 llp; + u64 ctl; + u32 sstat; + u32 dstat; + u64 llpstat; + u32 resv2; + u32 resv3; +}; + +#define to_dma_pipe(pipe) container_of(pipe, struct mwv207_pipe_dma, base) +struct mwv207_pipe_dma { + struct mwv207_pipe base; + int unit; + u32 alignment; + struct mwv207_lli *lli; + struct mwv207_lli *last_lli; + dma_addr_t lli_bus_addr; + int cur_idx; + + bool coherent; +}; + +struct mwv207_dma_cursor { + bool is_vram; + u64 line_pos; + u64 cur_pos; + + + dma_addr_t *dma_address; + u64 nr_pages; + + u64 width; + u64 stride; +}; +static inline void pipe_dma_write(struct mwv207_pipe_dma *pipe, u32 reg, u32 value) +{ + pipe_write(&pipe->base, reg, value); +} + +static inline u32 pipe_dma_read(struct mwv207_pipe_dma *pipe, u32 reg) +{ + return pipe_read(&pipe->base, reg); +} + +static inline void pipe_dma_chan_write(struct mwv207_pipe_dma *pipe, int chan, u32 reg, u32 val) +{ + pipe_dma_write(pipe, chan * 0x200 + reg, val); +} + +static inline u32 pipe_dma_chan_read(struct mwv207_pipe_dma *pipe, int chan, u32 reg) +{ + return pipe_dma_read(pipe, chan * 0x200 + reg); +} + +static inline int pipe_dma_chan_done(struct mwv207_pipe_dma *pipe, int chan) +{ + u32 status; + + if (pipe->last_lli->ctl & (1UL << (63))) + return 0; + + status = pipe_dma_chan_read(pipe, chan, (0x188)); + return !!(status & (1 << (1))); +} + +static inline void pipe_dma_chan_clear(struct mwv207_pipe_dma *pipe, int chan) +{ + pipe_dma_chan_write(pipe, chan, (0x198), 1 << (1)); +} + +static inline int pipe_dma_stop(struct mwv207_pipe_dma *pipe) +{ + pr_info("%s TBD", __func__); + return 0; +} + +static void mwv207_pipe_dma_reset(struct mwv207_pipe *mpipe) +{ + pr_info("%s todo", __func__); +} + +static void pipe_dma_commit(struct mwv207_pipe_dma *pipe) +{ + unsigned long tick; + int chan = 0; + u64 llp; + + if (pipe->cur_idx == 0) + return; + + pipe->last_lli = &pipe->lli[pipe->cur_idx - 1]; + pipe->last_lli->ctl |= (1UL << (62)); + + + mb(); + + + pipe_dma_chan_write(pipe, chan, (0x120), + ((0x3)<<(2)) + |((0x3)<<(0))); + pipe_dma_chan_write(pipe, chan, (0x124), 0x3b8e0000); + + + llp = pipe->lli_bus_addr + 0x800000000ULL; + pipe_dma_chan_write(pipe, chan, (0x128), llp & 0xFFFFFFFF); + pipe_dma_chan_write(pipe, chan, (0x12C), llp >> 32); + + + pipe_dma_write(pipe, (0x10), + (1 << (1)) + |(1 << (0))); + + + pipe_dma_write(pipe, (0x18), + ((1<cur_idx = 0; +} + +static void pipe_dma_fill_lli(struct mwv207_pipe_dma *pipe, u64 src, u64 dst, u64 len) +{ + struct mwv207_lli *lli; + + if (pipe->cur_idx >= DMA_MAX_LLI_COUNT) + pipe_dma_commit(pipe); + + lli = &pipe->lli[pipe->cur_idx]; + lli->sar = src; + lli->dar = dst; + + if (IS_ALIGNED(src, pipe->alignment) && + IS_ALIGNED(dst, pipe->alignment) && + IS_ALIGNED(len, pipe->alignment)) { + lli->ts = DMA_TS_ALIGNED(len, pipe->alignment); + lli->ctl = DMA_CTL_ALIGNED(pipe->alignment); + } else { + lli->ts = DMA_TS_NOT_ALIGNED(len); + lli->ctl = DMA_CTL_NOT_ALIGNED; + } + +#ifdef MWV207_DMA_DEBUG + { + pr_info("[dma]: lli[%04d] sar = 0x%010llx", pipe->cur_idx, lli->sar); + pr_info("[dma]: lli[%04d] dar = 0x%010llx", pipe->cur_idx, lli->dar); + pr_info("[dma]: lli[%04d] ts = 0x%08x", pipe->cur_idx, lli->ts); + pr_info("[dma]: lli[%04d] llp = 0x%010llx", pipe->cur_idx, lli->llp); + pr_info("[dma]: lli[%04d] ctl = 0x%010llx", pipe->cur_idx, lli->ctl); + } +#endif + pipe->cur_idx++; +} + +static void mwv207_cursor_init(struct mwv207_dma_cursor *cursor, struct mwv207_dma_loc *loc, + u64 width, u64 stride) +{ + cursor->width = width; + cursor->stride = stride; + cursor->is_vram = MWV207_DMA_IS_VRAM(loc->pg_nr_type); + if (cursor->is_vram) { + cursor->line_pos = loc->base + loc->offset; + cursor->cur_pos = cursor->line_pos; + cursor->dma_address = NULL; + cursor->nr_pages = MWV207_DMA_NR_PAGES(loc->pg_nr_type); + } else { + cursor->line_pos = loc->offset; + cursor->cur_pos = cursor->line_pos; + cursor->dma_address = (dma_addr_t *)loc->base; + cursor->nr_pages = MWV207_DMA_NR_PAGES(loc->pg_nr_type); + } +} + +static void mwv207_cursor_reset(struct mwv207_dma_cursor *cursor, struct mwv207_dma_loc *loc) +{ + if (cursor->is_vram) { + cursor->line_pos = loc->base + loc->offset; + cursor->cur_pos = cursor->line_pos; + } else { + cursor->line_pos = loc->offset; + cursor->cur_pos = cursor->line_pos; + } +} + +static inline u64 cursor_seg_len(struct mwv207_dma_cursor *cursor) +{ + u64 len = cursor->line_pos + cursor->width - cursor->cur_pos; + u64 offset, idx, contig_len; + + if (cursor->is_vram) + return len; + offset = cursor->cur_pos & (PAGE_SIZE - 1); + idx = cursor->cur_pos >> PAGE_SHIFT; + + contig_len = PAGE_SIZE - offset; + while (contig_len < len && + idx + 1 < cursor->nr_pages && + cursor->dma_address[idx] + PAGE_SIZE == cursor->dma_address[idx + 1]) { + contig_len += PAGE_SIZE; + idx++; + } + return min_t(u64, len, contig_len); +} + +static inline u64 cursor_axi(struct mwv207_dma_cursor *cursor) +{ + u64 idx, offset; + + if (cursor->is_vram) + return cursor->cur_pos; + + idx = cursor->cur_pos >> PAGE_SHIFT; + offset = cursor->cur_pos & (PAGE_SIZE - 1); + return 0x800000000ULL + cursor->dma_address[idx] + offset; +} + +static inline void cursor_advance(struct mwv207_dma_cursor *cursor, u64 len) +{ + BUG_ON(cursor->cur_pos + len > cursor->line_pos + cursor->width); + + if (cursor->cur_pos + len == cursor->line_pos + cursor->width) { + cursor->line_pos += cursor->stride; + cursor->cur_pos = cursor->line_pos; + } else + cursor->cur_pos += len; +} + +static void pipe_dma_flush(struct mwv207_pipe_dma *pipe, + struct mwv207_dma_cursor *cursor, u64 size) +{ + u64 remain, len; + + if (cursor->is_vram || pipe->coherent) + return; + + for (remain = size; remain > 0; remain -= len) { + len = min_t(u64, cursor_seg_len(cursor), remain); + dma_sync_single_for_device(pipe->base.jdev->dev, + cursor_axi(cursor) - 0x800000000ULL, + len, DMA_TO_DEVICE); + cursor_advance(cursor, len); + } +} + +static void pipe_dma_invalidate(struct mwv207_pipe_dma *pipe, + struct mwv207_dma_cursor *cursor, u64 size) +{ + u64 remain, len; + + if (cursor->is_vram || pipe->coherent) + return; + + for (remain = size; remain > 0; remain -= len) { + len = min_t(u64, cursor_seg_len(cursor), remain); + dma_sync_single_for_cpu(pipe->base.jdev->dev, + cursor_axi(cursor) - 0x800000000ULL, + len, DMA_FROM_DEVICE); + cursor_advance(cursor, len); + } +} + +static struct dma_fence *mwv207_pipe_dma_submit(struct mwv207_pipe *mpipe, + struct mwv207_job *mjob) +{ + struct mwv207_pipe_dma *pipe = to_dma_pipe(mpipe); + struct mwv207_dma_cmd *cmd = (struct mwv207_dma_cmd *)mjob->cmds; + struct mwv207_dma_cursor src, dst; + u64 remain, len; + + BUG_ON(mjob->cmd_size != sizeof(struct mwv207_dma_cmd)); + + + mwv207_cursor_init(&src, &cmd->src, cmd->width, cmd->src.stride); + pipe_dma_flush(pipe, &src, cmd->width * cmd->height); + + mwv207_cursor_reset(&src, &cmd->src); + mwv207_cursor_init(&dst, &cmd->dst, cmd->width, cmd->dst.stride); + for (remain = cmd->width * cmd->height; remain > 0; remain -= len) { + len = min_t(u64, cursor_seg_len(&src), 0x400000); + len = min_t(u64, cursor_seg_len(&dst), len); + len = min_t(u64, len, remain); + + pipe_dma_fill_lli(pipe, cursor_axi(&src), cursor_axi(&dst), len); + + cursor_advance(&src, len); + cursor_advance(&dst, len); + } + pipe_dma_commit(pipe); + + + mwv207_cursor_reset(&dst, &cmd->dst); + pipe_dma_invalidate(pipe, &dst, cmd->width * cmd->height); + + return dma_fence_get_stub(); +} + +static void mwv207_pipe_dma_dump_state(struct mwv207_pipe *mpipe) +{ + struct mwv207_pipe_dma *pipe = to_dma_pipe(mpipe); + pr_info("%s todo: %p", __func__, pipe); +} + +static void mwv207_pipe_dma_destroy(struct mwv207_pipe *mpipe) +{ + struct mwv207_pipe_dma *pipe = to_dma_pipe(mpipe); + + pr_info("%s todo: %p", __func__, pipe); +} + +struct mwv207_pipe *mwv207_pipe_dma_create(struct mwv207_device *jdev, int unit, u32 regbase, + const char *fname) +{ + struct mwv207_pipe_dma *pipe; + u64 llp; + int i; + + pipe = devm_kzalloc(jdev->dev, sizeof(struct mwv207_pipe_dma), GFP_KERNEL); + if (!pipe) + return NULL; + + pipe->base.jdev = jdev; + pipe->base.fname = fname; + pipe->base.regbase = jdev->mmio + regbase; + pipe->base.iosize = 16 * 1024; + pipe->base.submit = mwv207_pipe_dma_submit; + pipe->base.reset = mwv207_pipe_dma_reset; + pipe->base.destroy = mwv207_pipe_dma_destroy; + pipe->base.dump_state = mwv207_pipe_dma_dump_state; + + pipe->coherent = true; + + pipe->alignment = jdev->lite ? 32 : 64; + pipe->unit = unit; + + pipe->lli = dmam_alloc_coherent(jdev->dev, + sizeof(struct mwv207_lli) * DMA_MAX_LLI_COUNT, + &pipe->lli_bus_addr, GFP_KERNEL); + if (!pipe->lli) + return NULL; + + memset(pipe->lli, 0, sizeof(struct mwv207_lli) * DMA_MAX_LLI_COUNT); + llp = pipe->lli_bus_addr + 0x800000000ULL; + for (i = 0; i < DMA_MAX_LLI_COUNT; i++) { + llp += sizeof(struct mwv207_lli); + pipe->lli[i].llp = llp; + } + pipe->cur_idx = 0; + + mwv207_pipe_dma_reset(&pipe->base); + return &pipe->base; +} diff --git a/drivers/gpu/drm/mwv207/mwv207_pipe_enc.c b/drivers/gpu/drm/mwv207/mwv207_pipe_enc.c new file mode 100644 index 0000000000000000000000000000000000000000..2dc6bf808e7ea8333a38331dca2ad7c02ba462c5 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_pipe_enc.c @@ -0,0 +1,266 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include "mwv207_pipe_codec_common.h" + +#define JPIPE_STATUS_ALL \ + (0x1000 | 0x200 | \ + 0x100 | 0x080 | \ + 0x040 | 0x020 | \ + 0x010 | 0x008 | 0x004) + +#define to_enc_pipe(pipe) container_of(pipe, struct mwv207_pipe_enc, base) +struct pipe_enc_fence; +struct mwv207_pipe_enc { + struct mwv207_pipe base; + + u64 fence_ctx; + u64 fence_seqno; + spinlock_t fence_lock; + + struct pipe_enc_fence *current_fence; + struct task_struct *poll_thread; + struct pipe_codec_sw_pp sw_pp; +}; + +#define to_pipe_enc_fence(f) container_of(f, struct pipe_enc_fence, base) +struct pipe_enc_fence { + struct dma_fence base; + struct mwv207_pipe_enc *pipe; + struct list_head node; +}; + +static inline void pipe_enc_write(struct mwv207_pipe_enc *pipe, u32 reg, u32 value) +{ + pipe_write(&pipe->base, reg, value); +} + +static inline u32 pipe_enc_read(struct mwv207_pipe_enc *pipe, u32 reg) +{ + return pipe_read(&pipe->base, reg); +} + +static const char *pipe_enc_fence_get_driver_name(struct dma_fence *fence) +{ + return "mwv207"; +} + +static const char *pipe_enc_fence_get_timeline_name(struct dma_fence *fence) +{ + return to_pipe_enc_fence(fence)->pipe->base.fname; +} + +static void pipe_enc_fence_release(struct dma_fence *fence) +{ + struct pipe_enc_fence *f = to_pipe_enc_fence(fence); + + kfree_rcu(f, base.rcu); +} + +static const struct dma_fence_ops pipe_enc_fence_ops = { + .get_driver_name = pipe_enc_fence_get_driver_name, + .get_timeline_name = pipe_enc_fence_get_timeline_name, + .release = pipe_enc_fence_release, +}; + +static void pipe_enc_stat_reset(struct mwv207_pipe_enc *pipe, u32 stat) +{ + if (stat & 0x20) + pipe_enc_write(pipe, 0x14, 0); + + pipe_enc_write(pipe, 0x4, stat & (~0x1FD)); +} + +static int pipe_enc_polling(void *priv) +{ + struct mwv207_pipe_enc *pipe = priv; + struct pipe_enc_fence *fence; + u32 stat; + + set_freezable(); + while (!kthread_should_stop()) { + usleep_range(1000, 1001); + try_to_freeze(); + + + fence = smp_load_acquire(&pipe->current_fence); + if (fence == NULL) + continue; + + stat = pipe_enc_read(pipe, 0x4); + if (!(stat & JPIPE_STATUS_ALL)) + continue; + + pipe_codec_sw_pp_excute(&pipe->sw_pp, &pipe->base); + pipe_enc_stat_reset(pipe, stat); + + + smp_store_release(&pipe->current_fence, NULL); + + dma_fence_signal(&fence->base); + dma_fence_put(&fence->base); + } + + return 0; +} + +static void mwv207_pipe_enc_reset(struct mwv207_pipe *mpipe) +{ + struct mwv207_pipe_enc *pipe = to_enc_pipe(mpipe); + struct pipe_enc_fence *fence; + int i; + + pipe_codec_sw_pp_reset(&pipe->sw_pp); + + pipe_enc_write(pipe, 0x14, 0); + + + for (i = 4; i < pipe->base.iosize; i += 0x4) + pipe_enc_write(pipe, i, 0); + + + fence = smp_load_acquire(&pipe->current_fence); + if (fence) { + dma_fence_put(&fence->base); + + smp_store_release(&pipe->current_fence, NULL); + } +} + +static struct dma_fence *mwv207_pipe_enc_submit(struct mwv207_pipe *mpipe, + struct mwv207_job *mjob) +{ + struct mwv207_pipe_enc *pipe = to_enc_pipe(mpipe); + struct pipe_enc_fence *fence; + int i, nr, pos, cmdlen; + bool hw_run = 0; + u32 cmd, reg; + + /* sanity check, there should be no previous job. + * acquire pairs with release in polling thread + */ + fence = smp_load_acquire(&pipe->current_fence); + if (fence) + return ERR_PTR(-EBUSY); + + + for (pos = 0; pos <= mjob->cmd_size - 4; pos += cmdlen) { + cmd = *(u32 *)(mjob->cmds + pos); + switch ((cmd >> 27) & 0x1f) { + case 0x01: + if ((cmd >> 26) & 0x1) + return ERR_PTR(-EINVAL); + reg = (cmd & 0xffff) * 4; + nr = (cmd >> 16) & 0x3ff; + if (nr == 0) + nr = 1024; + for (i = pos + 4; i <= pos + nr * 4 + && i <= mjob->cmd_size - 4 + && reg <= pipe->base.iosize - 4; + i += 4, reg += 4) { + if (reg == 20) { + pipe_enc_read(pipe, 0x14); + hw_run = (*(u32 *)(mjob->cmds + i)) & 0x1; + + mb(); + } + pipe_enc_write(pipe, reg, *(u32 *)(mjob->cmds + i)); + } + + if (i <= pos + nr * 4) + dev_dbg(mpipe->jdev->dev, + "mwv207: invalid enc register writes dropped"); + + cmdlen = ALIGN(4 + nr * 4, 8); + break; + case 0x02: + pipe_codec_sw_pp_submit(&pipe->sw_pp, &pipe->base, mjob, pos, &cmdlen); + break; + default: + + return ERR_PTR(-EINVAL); + } + } + + + fence = kzalloc(sizeof(struct pipe_enc_fence), GFP_KERNEL); + if (!fence) + return ERR_PTR(-ENOMEM); + fence->pipe = pipe; + dma_fence_init(&fence->base, &pipe_enc_fence_ops, &pipe->fence_lock, + pipe->fence_ctx, ++pipe->fence_seqno); + + if (hw_run) { + dma_fence_get(&fence->base); + + smp_store_release(&pipe->current_fence, fence); + } else { + pipe_codec_sw_pp_excute(&pipe->sw_pp, &pipe->base); + dma_fence_signal(&fence->base); + } + + return &fence->base; +} + +static void mwv207_pipe_enc_dump_state(struct mwv207_pipe *mpipe) +{ + pr_info("%s irq state reg: 0x%08x", mpipe->fname, + pipe_read(mpipe, 0x4)); +} + +static void mwv207_pipe_enc_destroy(struct mwv207_pipe *mpipe) +{ + struct mwv207_pipe_enc *pipe = to_enc_pipe(mpipe); + + kthread_stop(pipe->poll_thread); +} + +struct mwv207_pipe *mwv207_pipe_enc_create(struct mwv207_device *jdev, u32 regbase, + const char *fname) +{ + struct mwv207_pipe_enc *pipe; + + if (jdev->lite) + return NULL; + + pipe = devm_kzalloc(jdev->dev, sizeof(struct mwv207_pipe_enc), GFP_KERNEL); + if (!pipe) + return NULL; + + pipe->base.jdev = jdev; + pipe->base.fname = fname; + pipe->base.regbase = jdev->mmio + regbase; + pipe->base.iosize = JPIPE_ENC_REG_NUM * 4; + pipe->base.submit = mwv207_pipe_enc_submit; + pipe->base.reset = mwv207_pipe_enc_reset; + pipe->base.destroy = mwv207_pipe_enc_destroy; + pipe->base.dump_state = mwv207_pipe_enc_dump_state; + + spin_lock_init(&pipe->fence_lock); + pipe->fence_ctx = dma_fence_context_alloc(1); + pipe->fence_seqno = 0; + + mwv207_pipe_enc_reset(&pipe->base); + + pipe->poll_thread = kthread_run(pipe_enc_polling, pipe, "enc_polling"); + if (IS_ERR(pipe->poll_thread)) + return NULL; + + return &pipe->base; +} diff --git a/drivers/gpu/drm/mwv207/mwv207_sched.c b/drivers/gpu/drm/mwv207/mwv207_sched.c new file mode 100644 index 0000000000000000000000000000000000000000..890dd2856e4cbc1bbe048ea50ebfb9f8127dc338 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_sched.c @@ -0,0 +1,378 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include "mwv207.h" +#include "mwv207_sched.h" +#include "mwv207_bo.h" +#include "mwv207_ctx.h" +#include "mwv207_db.h" +#include "mwv207_devfreq.h" + +static int dump_hang_job = 1; +module_param(dump_hang_job, int, 0644); +MODULE_PARM_DESC(dump_hang_job, "dump time out job"); + +struct mwv207_job *mwv207_job_alloc(void) +{ + struct mwv207_job *mjob; + + mjob = kzalloc(sizeof(*mjob), GFP_KERNEL); + if (!mjob) + return NULL; + + kref_init(&mjob->refcount); + INIT_LIST_HEAD(&mjob->tvblist); + return mjob; +} + +static void mwv207_job_fini(struct kref *kref) +{ + struct mwv207_job *mjob = container_of(kref, struct mwv207_job, refcount); + struct mwv207_tvb *mtvb; + struct mwv207_submit_cmd_dat *dat; + int i; + + dma_fence_put(mjob->fence_in); + if (mjob->base.s_fence) + drm_sched_job_cleanup(&mjob->base); + + + mwv207_for_each_mtvb(mtvb, mjob) { + for (i = 0; i < mtvb->nr_shared; ++i) + dma_fence_put(mtvb->shared[i]); + dma_fence_put(mtvb->excl); + kfree(mtvb->shared); + mwv207_bo_unref(to_jbo(mtvb->base.bo)); + } + + if (mjob->nr_cmd_dat) { + for (i = 0; i < mjob->nr_cmd_dat; ++i) { + dat = &mjob->cmd_dats[i]; + kvfree(dat->dat); + kvfree(dat->relocs); + mwv207_bo_unref(dat->jbo); + } + } + + if (mjob->ctx) + mwv207_ctx_put(mjob->ctx); + + kvfree(mjob->cmd_dats); + kvfree(mjob->mtvb); + kvfree(mjob->relocs); + kvfree(mjob->cmds); + + kfree(mjob); +} + +struct mwv207_job *mwv207_job_get(struct mwv207_job *mjob) +{ + kref_get(&mjob->refcount); + return mjob; +} + +void mwv207_job_put(struct mwv207_job *mjob) +{ + kref_put(&mjob->refcount, mwv207_job_fini); +} + +static void mwv207_sched_dump_job(struct mwv207_job *mjob) +{ + int lines, i; + u32 *cmd; + + cmd = (u32 *)mjob->cmds; + lines = (mjob->cmd_size / 4) & ~0x3; + for (i = 0; i < lines; i += 4) { + pr_info("0x%08x : %08x %08x %08x %08x", i, + cmd[i], cmd[i+1], cmd[i+2], cmd[i+3]); + } + + switch (mjob->cmd_size / 4 - lines) { + case 0: + return; + case 1: + pr_info("0x%08x : %08x", i, cmd[i]); + break; + case 2: + pr_info("0x%08x : %08x %08x", i, cmd[i], cmd[i+1]); + break; + case 3: + pr_info("0x%08x : %08x %08x %08x", i, + cmd[i], cmd[i+1], cmd[i+2]); + break; + default: + pr_err("should never happen"); + break; + + } +} + +static struct dma_fence *mwv207_sched_dependency(struct drm_sched_job *job, + struct drm_sched_entity *entity) +{ + struct mwv207_job *mjob = to_mwv207_job(job); + struct mwv207_tvb *mtvb; + struct dma_fence *fence; + int i; + + if (unlikely(mjob->fence_in)) { + fence = mjob->fence_in; + mjob->fence_in = NULL; + if (!dma_fence_is_signaled(fence)) + return fence; + dma_fence_put(fence); + } + + mwv207_for_each_mtvb(mtvb, mjob) { + if (mtvb->excl) { + fence = mtvb->excl; + mtvb->excl = NULL; + if (!dma_fence_is_signaled(fence)) + return fence; + dma_fence_put(fence); + } + + for (i = 0; i < mtvb->nr_shared; i++) { + if (!mtvb->shared[i]) + continue; + fence = mtvb->shared[i]; + mtvb->shared[i] = NULL; + if (!dma_fence_is_signaled(fence)) + return fence; + dma_fence_put(fence); + } + kfree(mtvb->shared); + mtvb->nr_shared = 0; + mtvb->shared = NULL; + } + + return NULL; +} + +static struct dma_fence *mwv207_sched_run_job(struct drm_sched_job *job) +{ + struct mwv207_sched *sched = to_mwv207_sched(job->sched); + struct dma_fence *fence; + + if (unlikely(job->s_fence->finished.error)) + return ERR_PTR(-ECANCELED); + + mwv207_devfreq_record_busy(sched->pipe); + + fence = sched->pipe->submit(sched->pipe, to_mwv207_job(job)); + if (IS_ERR_OR_NULL(fence)) + DRM_ERROR("%s: submit failed, errcode=%ld", + sched->pipe->fname, PTR_ERR(fence)); + return fence; +} + +static void mwv207_sched_timedout_job(struct drm_sched_job *job) +{ + struct mwv207_sched *sched = to_mwv207_sched(job->sched); + struct mwv207_job *mjob = to_mwv207_job(job); + struct drm_sched_job *timedout_job; + + pr_info("mwv207: job from '%s' to '%s', timed out!", + mjob->comm, sched->pipe->fname); + + drm_sched_stop(&sched->base, job); + + if (dump_hang_job) { + sched->pipe->dump_state(sched->pipe); + mwv207_sched_dump_job(mjob); + } + + drm_sched_increase_karma(job); + + sched->pipe->reset(sched->pipe); + + list_for_each_entry(timedout_job, &sched->base.ring_mirror_list, node) + mwv207_devfreq_record_idle(sched->pipe); + + drm_sched_resubmit_jobs(&sched->base); + + + drm_sched_start(&sched->base, true); +} + +static void mwv207_sched_free_job(struct drm_sched_job *job) +{ + struct mwv207_job *mjob = to_mwv207_job(job); + + mwv207_job_put(mjob); +} + +static const struct drm_sched_backend_ops mwv207_sched_ops = { + .dependency = mwv207_sched_dependency, + .run_job = mwv207_sched_run_job, + .timedout_job = mwv207_sched_timedout_job, + .free_job = mwv207_sched_free_job, +}; + +static struct drm_gpu_scheduler *mwv207_sched_create(struct mwv207_device *jdev, + unsigned int hw_submission, unsigned int hang_limit, long timeout, + struct mwv207_pipe *pipe) +{ + struct mwv207_sched *sched; + int ret; + + if (pipe == NULL) + return NULL; + + sched = devm_kzalloc(jdev->dev, sizeof(struct mwv207_sched), GFP_KERNEL); + if (!sched) + goto err; + + sched->pipe = pipe; + + ret = drm_sched_init(&sched->base, &mwv207_sched_ops, hw_submission, hang_limit, + msecs_to_jiffies(timeout), pipe->fname); + if (ret) + goto err; + + return &sched->base; +err: + sched->pipe->destroy(sched->pipe); + return NULL; +} + +static void mwv207_sched_destroy(struct drm_gpu_scheduler *sched) +{ + struct mwv207_sched *msched = to_mwv207_sched(sched); + + if (sched == NULL) + return; + + drm_sched_fini(sched); + msched->pipe->destroy(msched->pipe); +} + +int mwv207_sched_suspend(struct mwv207_device *jdev) +{ + struct mwv207_sched *sched; + int i, ret; + + + for (i = 0; i < 6; i++) { + if (!jdev->sched[i]) + continue; + sched = to_mwv207_sched(jdev->sched[i]); + + if (atomic_read(&jdev->sched[i]->hw_rq_count)) + return -EBUSY; + + ret = mwv207_devfreq_suspend(sched->pipe); + if (ret) + return ret; + } + + return 0; +} + +int mwv207_sched_resume(struct mwv207_device *jdev) +{ + struct mwv207_sched *sched; + int i, ret; + + for (i = 0; i < 6; i++) { + if (!jdev->sched[i]) + continue; + sched = to_mwv207_sched(jdev->sched[i]); + + ret = mwv207_devfreq_resume(sched->pipe); + if (ret) + dev_err(sched->pipe->dev, "devfreq resume failed: %d", ret); + + sched->pipe->reset(sched->pipe); + } + + return 0; +} + +int mwv207_sched_init(struct mwv207_device *jdev) +{ + struct mwv207_pipe *pipe; + + jdev->sched_3d = &jdev->sched[0]; + jdev->sched_dec = &jdev->sched_3d[2]; + jdev->sched_enc = &jdev->sched_dec[1]; + jdev->sched_2d = &jdev->sched_enc[1]; + jdev->sched_dma = &jdev->sched_2d[1]; + + /* ownership of pipe passed to mwv207_sched_create, don't reference + * this pipe after call to mwv207_sched_create. + * + * Note sched limit of 3d should be lower than hw event count, which + * is 30 for 91/92. otherwise pipe 3d will BUG_ON + */ + pipe = mwv207_pipe_3d_create(jdev, 0, 0x900000, "mwv207_3d0"); + jdev->sched_3d[0] = mwv207_sched_create(jdev, 30, 5, 2000, pipe); + if (jdev->sched_3d[0] == NULL) + return -ENODEV; + + pipe = mwv207_pipe_3d_create(jdev, 1, 0x910000, "mwv207_3d1"); + jdev->sched_3d[1] = mwv207_sched_create(jdev, 30, 5, 2000, pipe); + + pipe = mwv207_pipe_dec_create(jdev, 0, 0x930000, "mwv207_dec0"); + jdev->sched_dec[0] = mwv207_sched_create(jdev, 1, 5, 2000, pipe); + if (jdev->sched_dec[0] == NULL) + goto err; + + pipe = mwv207_pipe_dec_create(jdev, 1, 0x940000, "mwv207_dec1"); + jdev->sched_dec[1] = mwv207_sched_create(jdev, 1, 5, 2000, pipe); + + pipe = mwv207_pipe_enc_create(jdev, 0x920000, "mwv207_enc0"); + jdev->sched_enc[0] = mwv207_sched_create(jdev, 1, 5, 2000, pipe); + + pipe = mwv207_pipe_2d_create(jdev, 0, 0x968000, "mwv207_2d0"); + jdev->sched_2d[0] = mwv207_sched_create(jdev, 30, 5, 2000, pipe); + if (jdev->sched_2d[0] == NULL) + goto err; + + pipe = mwv207_pipe_dma_create(jdev, 0, 0x9d0000, "mwv207_dma0"); + jdev->sched_dma[0] = mwv207_sched_create(jdev, 1, 5, 2000, pipe); + if (jdev->sched_dma[0] == NULL) + goto err; + + jdev->nr_3d = jdev->sched_3d[1] ? 2 : 1; + jdev->nr_enc = jdev->sched_enc[0] ? 1 : 0; + jdev->nr_dec = jdev->sched_dec[1] ? 2 : 1; + jdev->nr_2d = 1; + jdev->nr_dma = 1; + + mwv207_db_add(jdev, DRM_MWV207_ACTIVE_3D_NR, jdev->nr_3d); + mwv207_db_add(jdev, DRM_MWV207_ACTIVE_ENC_NR, jdev->nr_enc); + mwv207_db_add(jdev, DRM_MWV207_ACTIVE_DEC_NR, jdev->nr_dec); + mwv207_db_add(jdev, DRM_MWV207_ACTIVE_2D_NR, jdev->nr_2d); + mwv207_db_add(jdev, DRM_MWV207_ACTIVE_DMA_NR, jdev->nr_dma); + + return 0; +err: + mwv207_sched_fini(jdev); + return -ENODEV; +} + +void mwv207_sched_fini(struct mwv207_device *jdev) +{ + int i; + + for (i = 0; i < 6; ++i) { + if (!jdev->sched[i]) + continue; + mwv207_sched_destroy(jdev->sched[i]); + } +} diff --git a/drivers/gpu/drm/mwv207/mwv207_sched.h b/drivers/gpu/drm/mwv207/mwv207_sched.h new file mode 100644 index 0000000000000000000000000000000000000000..17272e1a870e61cff8152039baf50d8f7738d68f --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_sched.h @@ -0,0 +1,153 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef MWV207_SCHED_H_TGQQ2LXS +#define MWV207_SCHED_H_TGQQ2LXS +#include +#include +#include +#include "mwv207.h" +#include "mwv207_bo.h" + +struct mwv207_submit_cmd_dat { + int nr_relocs; + int dat_size; + struct drm_mwv207_submit_reloc *relocs; + char *dat; + struct mwv207_bo *jbo; +}; + +struct mwv207_tvb { + struct ttm_validate_buffer base; + struct dma_fence *excl; + unsigned int nr_shared; + struct dma_fence **shared; +}; + +#define to_mwv207_job(job) container_of(job, struct mwv207_job, base) +struct mwv207_job { + struct drm_sched_job base; + + struct list_head tvblist; + struct mwv207_tvb *mtvb; + int nr_bos; + + struct drm_mwv207_submit_reloc *relocs; + int nr_relocs; + + struct mwv207_submit_cmd_dat *cmd_dats; + int nr_cmd_dat; + + char *cmds; + int cmd_size; + + struct dma_fence *fence_in; + + + struct mwv207_ctx *ctx; + struct drm_sched_entity *engine_entity; + + struct kref refcount; + + + bool is_dma; + + char comm[TASK_COMM_LEN]; +}; +#define mwv207_for_each_mtvb(mtvb, mjob) list_for_each_entry(mtvb, &(mjob)->tvblist, base.head) + +struct mwv207_job *mwv207_job_alloc(void); +struct mwv207_job *mwv207_job_get(struct mwv207_job *mjob); +void mwv207_job_put(struct mwv207_job *mjob); + +static inline struct mwv207_bo *mwv207_job_bo(struct mwv207_job *mjob, int idx) +{ + BUG_ON(idx >= mjob->nr_bos + mjob->nr_cmd_dat); + return to_jbo(mjob->mtvb[idx].base.bo); +} + +struct mwv207_dma_loc { + u64 base; + u64 pg_nr_type; + u64 offset; + u64 stride; +}; +#define MWV207_DMA_NR_PAGES_VRAM(nr) ((nr) | (0ULL << 63)) +#define MWV207_DMA_NR_PAGES_RAM(nr) ((nr) | (1ULL << 63)) +#define MWV207_DMA_NR_PAGES(nr) ((nr) & ~(1ULL << 63)) +#define MWV207_DMA_IS_VRAM(nr) (((nr) >> 63) == 0) + + +struct mwv207_dma_cmd { + struct mwv207_dma_loc src; + struct mwv207_dma_loc dst; + u64 width; + u64 height; +}; + +struct devfreq; +#define to_mwv207_sched(sched) container_of(sched, struct mwv207_sched, base) +struct mwv207_pipe { + struct dma_fence* (*submit)(struct mwv207_pipe *pipe, struct mwv207_job *job); + void (*reset)(struct mwv207_pipe *pipe); + void (*destroy)(struct mwv207_pipe *pipe); + void (*dump_state)(struct mwv207_pipe *pipe); + struct mwv207_device *jdev; + const char *fname; + int iosize; + void __iomem *regbase; + + + struct device *dev; + struct devfreq *devfreq; + spinlock_t devfreq_lock; + int busy_count; + int pll_id; + ktime_t busy_time; + ktime_t idle_time; + ktime_t time_last_update; +}; + +struct mwv207_sched { + struct drm_gpu_scheduler base; + struct mwv207_pipe *pipe; +}; + +static inline void pipe_write(struct mwv207_pipe *pipe, u32 reg, u32 value) +{ + writel_relaxed(value, pipe->regbase + reg); +} + +static inline u32 pipe_read(struct mwv207_pipe *pipe, u32 reg) +{ + return readl(pipe->regbase + reg); +} + +int mwv207_sched_init(struct mwv207_device *jdev); +void mwv207_sched_fini(struct mwv207_device *jdev); +int mwv207_sched_suspend(struct mwv207_device *jdev); +int mwv207_sched_resume(struct mwv207_device *jdev); + +struct mwv207_pipe *mwv207_pipe_3d_create(struct mwv207_device *jdev, int unit, + u32 regbase, const char *fname); +struct mwv207_pipe *mwv207_pipe_2d_create(struct mwv207_device *jdev, int unit, + u32 regbase, const char *fname); +struct mwv207_pipe *mwv207_pipe_dec_create(struct mwv207_device *jdev, int unit, + u32 regbase, const char *fname); +struct mwv207_pipe *mwv207_pipe_enc_create(struct mwv207_device *jdev, + u32 regbase, const char *fname); +struct mwv207_pipe *mwv207_pipe_dma_create(struct mwv207_device *jdev, int unit, + u32 regbase, const char *fname); +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_submit.c b/drivers/gpu/drm/mwv207/mwv207_submit.c new file mode 100644 index 0000000000000000000000000000000000000000..7a3a373dd017b632719cdfe708de675d66bdacdc --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_submit.c @@ -0,0 +1,638 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include "mwv207_drm.h" +#include "mwv207_submit.h" +#include "mwv207_sched.h" +#include "mwv207_ctx.h" +#include "mwv207_bo.h" +#include "mwv207_gem.h" + +static inline int mwv207_submit_select_engine(struct drm_device *dev, struct mwv207_job *mjob, + struct drm_mwv207_submit *args) +{ + struct mwv207_device *jdev = ddev_to_jdev(dev); + struct drm_sched_entity **entity_array; + int id_max, id; + + switch (args->engine_type) { + case 0: + entity_array = &mjob->ctx->entity_3d[0]; + id_max = jdev->nr_3d; + break; + case 1: + entity_array = &mjob->ctx->entity_dec[0]; + id_max = jdev->nr_dec; + break; + case 2: + entity_array = &mjob->ctx->entity_enc[0]; + id_max = jdev->nr_enc; + break; + case 3: + entity_array = &mjob->ctx->entity_2d[0]; + id_max = jdev->nr_2d; + break; + case 4: + entity_array = &mjob->ctx->entity_dma[0]; + id_max = jdev->nr_dma; + mjob->is_dma = true; + break; + + default: + id_max = 0; + } + if (id_max <= 0) + return -ENODEV; + + if (args->engine_id == 0xffffffff) + id = id_max; + else if (args->engine_id >= 0 && args->engine_id < id_max) + id = args->engine_id; + else + return -ENODEV; + mjob->engine_entity = entity_array[id]; + return 0; +} + +static int mwv207_submit_init_job_reloc(struct mwv207_job *mjob, struct drm_mwv207_submit *args) +{ + if ((int)args->nr_relocs < 0) + return -EINVAL; + mjob->nr_relocs = (int)args->nr_relocs; + if (mjob->nr_relocs == 0) + return 0; + if (args->relocs == 0) + return -EINVAL; + + mjob->relocs = kvmalloc_array(mjob->nr_relocs, + sizeof(struct drm_mwv207_submit_reloc), GFP_KERNEL); + + return copy_from_user(mjob->relocs, (void __user *)args->relocs, + sizeof(struct drm_mwv207_submit_reloc) * mjob->nr_relocs); +} + +static int mwv207_submit_init_job_bo(struct mwv207_job *mjob, + struct drm_mwv207_submit *args, struct drm_file *filp) +{ + struct drm_mwv207_bo_acc ubos_stack[32]; + struct drm_mwv207_bo_acc *ubos = ubos_stack; + struct ttm_validate_buffer *tvb; + struct drm_gem_object *gobj; + int ret = 0, i; + + if ((int)args->nr_bos < 0) + return -EINVAL; + + mjob->nr_bos = (int)args->nr_bos; + if (mjob->nr_bos == 0) { + if (mjob->nr_cmd_dat) + goto setup_mtvb; + return 0; + } + + if (!args->bos) + return -EINVAL; + + if (mjob->nr_bos > 32) + ubos = kvmalloc_array(mjob->nr_bos, sizeof(*ubos), GFP_KERNEL); + if (unlikely(!ubos)) + return -ENOMEM; + + ret = copy_from_user(ubos, (void __user *)args->bos, + mjob->nr_bos * sizeof(*ubos)); + if (ret) + goto out; +setup_mtvb: + mjob->mtvb = kvcalloc(mjob->nr_bos + mjob->nr_cmd_dat, + sizeof(struct mwv207_tvb), GFP_KERNEL); + if (!mjob->mtvb) { + ret = -ENOMEM; + goto out; + } + spin_lock(&filp->table_lock); + for (i = 0; i < mjob->nr_bos; i++) { + tvb = &mjob->mtvb[i].base; + tvb->num_shared = (ubos[i].flags & 0x00000001) ? 0 : 1; + gobj = idr_find(&filp->object_idr, ubos[i].handle); + if (unlikely(!gobj)) { + spin_unlock(&filp->table_lock); + ret = -ENOENT; + goto out; + } + tvb->bo = &mwv207_bo_ref(mwv207_bo_from_gem(gobj))->tbo; + list_add_tail(&tvb->head, &mjob->tvblist); + } + spin_unlock(&filp->table_lock); + for (i = 0; i < mjob->nr_cmd_dat; i++) { + tvb = &mjob->mtvb[mjob->nr_bos + i].base; + tvb->num_shared = 0; + tvb->bo = &mwv207_bo_ref(mjob->cmd_dats[i].jbo)->tbo; + list_add_tail(&tvb->head, &mjob->tvblist); + } +out: + if (unlikely(ubos != ubos_stack)) + kvfree(ubos); + + return ret; +} + +static int mwv207_submit_init_job_cmd(struct mwv207_job *mjob, struct drm_mwv207_submit *args) +{ + mjob->cmd_size = args->cmd_size; + if ((int)mjob->cmd_size <= 0) + return -EINVAL; + + if (mjob->cmd_size > 0x20000) + return -E2BIG; + + if (args->cmds == 0) + return -EINVAL; + + + if (mjob->is_dma) + mjob->cmds = kmalloc(args->cmd_size, GFP_KERNEL); + else + mjob->cmds = kvmalloc(args->cmd_size, GFP_KERNEL); + if (!mjob->cmds) + return -ENOMEM; + + return copy_from_user(mjob->cmds, (void __user *)args->cmds, mjob->cmd_size); +} + +static int mwv207_submit_init_job_cmd_dat(struct drm_device *dev, + struct mwv207_job *mjob, struct drm_mwv207_submit *args) +{ + struct drm_mwv207_submit_cmd_dat udats_stack[8]; + struct drm_mwv207_submit_cmd_dat *udats = udats_stack; + struct mwv207_device *jdev = ddev_to_jdev(dev); + struct mwv207_submit_cmd_dat *mdat; + int ret, i; + + if ((int)args->nr_cmd_dat < 0) + return -EINVAL; + mjob->nr_cmd_dat = args->nr_cmd_dat; + if (mjob->nr_cmd_dat == 0) + return 0; + if (args->cmd_dats == 0) + return -EINVAL; + if (mjob->nr_cmd_dat > 8) + udats = kvmalloc_array(mjob->nr_cmd_dat, + sizeof(struct drm_mwv207_submit_cmd_dat), GFP_KERNEL); + + if (unlikely(!udats)) + return -ENOMEM; + + mjob->cmd_dats = kvcalloc(mjob->nr_cmd_dat, + sizeof(struct mwv207_submit_cmd_dat), GFP_KERNEL); + if (!mjob->cmd_dats) { + ret = -ENOMEM; + goto out; + } + + ret = copy_from_user(udats, (void __user *)args->cmd_dats, + mjob->nr_cmd_dat * sizeof(struct drm_mwv207_submit_cmd_dat)); + if (ret) + goto out; + + for (i = 0; i < mjob->nr_cmd_dat; ++i) { + if ((int)udats[i].nr_relocs < 0) { + ret = -EINVAL; + goto out; + } + if ((int)udats[i].dat_size <= 0 || udats[i].dat == 0) { + ret = -EINVAL; + goto out; + } + if (udats[i].nr_relocs != 0 && udats[i].relocs == 0) { + ret = -EINVAL; + goto out; + } + if (udats[i].dat_size > 0x20000) { + ret = -E2BIG; + goto out; + } + mdat = &mjob->cmd_dats[i]; + mdat->nr_relocs = udats[i].nr_relocs; + if (mdat->nr_relocs) { + mdat->relocs = kvmalloc_array(mdat->nr_relocs, + sizeof(struct drm_mwv207_submit_reloc), GFP_KERNEL); + if (!mdat->relocs) { + ret = -ENOMEM; + goto out; + } + ret = copy_from_user(mdat->relocs, (void __user *)udats[i].relocs, + mdat->nr_relocs * sizeof(struct drm_mwv207_submit_reloc)); + if (ret) + goto out; + } + mdat->dat_size = udats[i].dat_size; + mdat->dat = kvmalloc(mdat->dat_size, GFP_KERNEL); + if (!mdat->dat) { + ret = -ENOMEM; + goto out; + } + ret = copy_from_user(mdat->dat, (void __user *)udats[i].dat, mdat->dat_size); + if (ret) + goto out; + ret = mwv207_bo_create(jdev, mdat->dat_size, 0x1000, ttm_bo_type_device, + 0x2, (1<<0), + &mdat->jbo); + if (ret) + goto out; + } + +out: + if (unlikely(udats != udats_stack)) + kvfree(udats); + return ret; +} + +static int mwv207_submit_init_job(struct drm_device *dev, struct mwv207_job *mjob, + struct drm_mwv207_submit *args, struct drm_file *filp) +{ + int ret; + + get_task_comm(mjob->comm, current); + + + mjob->ctx = mwv207_ctx_lookup(dev, filp, args->ctx); + if (!mjob->ctx) + return -ENOENT; + ret = mwv207_submit_select_engine(dev, mjob, args); + if (ret) + return ret; + ret = mwv207_submit_init_job_cmd_dat(dev, mjob, args); + if (ret) + return ret; + ret = mwv207_submit_init_job_bo(mjob, args, filp); + if (ret) + return ret; + ret = mwv207_submit_init_job_reloc(mjob, args); + if (ret) + return ret; + ret = mwv207_submit_init_job_cmd(mjob, args); + if (ret) + return ret; + + return 0; +} + +static struct mwv207_job *mwv207_submit_init(struct drm_device *dev, struct drm_file *filp, + struct drm_mwv207_submit *args) +{ + struct mwv207_job *mjob; + int ret; + + mjob = mwv207_job_alloc(); + if (!mjob) + return ERR_PTR(-ENOMEM); + + ret = mwv207_submit_init_job(dev, mjob, args, filp); + if (ret) + goto err; + + return mjob; +err: + mwv207_job_put(mjob); + return ERR_PTR(ret); +} + +static int mwv207_submit_patch_entry(struct mwv207_job *mjob, + struct drm_mwv207_submit_reloc *reloc, + struct mwv207_bo *jbo, char *dat, u32 dat_size) +{ + u64 addr, bo_size; + + if (reloc->pad) + return -EINVAL; + + bo_size = mwv207_bo_size(jbo); + if (reloc->bo_offset >= bo_size) + return -ERANGE; + addr = mwv207_bo_gpu_phys(jbo) + reloc->bo_offset; + + + switch (reloc->type & 0xffff) { + case 0: + if (reloc->cmd_offset + 8 > dat_size) + return -ERANGE; + *(u64 *)(dat + reloc->cmd_offset) = addr; + break; + case 1: + if (reloc->cmd_offset + 4 > dat_size) + return -ERANGE; + *(u32 *)(dat + reloc->cmd_offset) = addr & 0xffffffff; + break; + case 2: + if (reloc->cmd_offset + 4 > dat_size) + return -ERANGE; + *(u32 *)(dat + reloc->cmd_offset) = addr >> 32; + break; + case 3: + if (reloc->cmd_offset + 2 > dat_size) + return -ERANGE; + *(u16 *)(dat + reloc->cmd_offset) = (addr >> 16) & 0xffff; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mwv207_submit_patch_single(struct mwv207_job *mjob, + struct drm_mwv207_submit_reloc *reloc, char *dat, u32 dat_size) +{ + struct mwv207_bo *jbo; + + if (reloc->type & 0x80000000) { + if (reloc->idx >= mjob->nr_cmd_dat) + return -EINVAL; + jbo = mwv207_job_bo(mjob, reloc->idx + mjob->nr_bos); + } else { + if (reloc->idx >= mjob->nr_bos) + return -EINVAL; + jbo = mwv207_job_bo(mjob, reloc->idx); + } + + return mwv207_submit_patch_entry(mjob, reloc, jbo, dat, dat_size); +} + +static int mwv207_submit_patch_dma_entry(struct mwv207_job *mjob, + struct mwv207_dma_loc *loc, struct mwv207_bo *jbo, + u64 width, u64 height) +{ + struct mwv207_ttm_tt *gtt; + + if (loc->stride == 0 || loc->pg_nr_type != 0 || width == 0 || height == 0) + return -EINVAL; + + switch (jbo->tbo.mem.mem_type) { + case TTM_PL_VRAM: + loc->base = mwv207_bo_gpu_phys(jbo); + loc->pg_nr_type = MWV207_DMA_NR_PAGES_VRAM(jbo->tbo.num_pages); + break; + case TTM_PL_TT: + gtt = to_gtt(jbo->tbo.ttm); + loc->base = (u64)gtt->ttm.dma_address; + loc->pg_nr_type = MWV207_DMA_NR_PAGES_RAM(jbo->tbo.num_pages); + break; + default: + BUG_ON(1); + break; + } + + + if (loc->offset >= mwv207_bo_size(jbo)) + return -ERANGE; + if (loc->offset + loc->stride * (height - 1) + width <= loc->offset) + return -ERANGE; + if (loc->offset + loc->stride * (height - 1) + width > mwv207_bo_size(jbo)) + return -ERANGE; + + + if ((loc->offset % loc->stride) + width > loc->stride) + return -ERANGE; + + return 0; +} + +static int mwv207_submit_patch_dma(struct drm_device *dev, struct mwv207_job *mjob) +{ + struct mwv207_bo *src_bo, *dst_bo, *jbo; + struct drm_mwv207_submit_reloc *reloc; + struct mwv207_dma_cmd *dma_cmd; + int i, ret; + + if (mjob->nr_bos != 2 || mjob->nr_relocs != 2 || mjob->nr_cmd_dat) + return -EINVAL; + if (mjob->cmd_size != sizeof(struct mwv207_dma_cmd)) + return -EINVAL; + + src_bo = dst_bo = NULL; + for (i = 0; i < mjob->nr_relocs; i++) { + reloc = &mjob->relocs[i]; + if (reloc->type & 0x80000000) + return -EINVAL; + if ((reloc->type & 0xffff) != 0) + return -EINVAL; + if (reloc->bo_offset) + return -EINVAL; + jbo = mwv207_job_bo(mjob, reloc->idx); + if (reloc->cmd_offset == offsetof(struct mwv207_dma_cmd, src.base)) + src_bo = jbo; + else if (reloc->cmd_offset == offsetof(struct mwv207_dma_cmd, dst.base)) + dst_bo = jbo; + } + if (!src_bo || !dst_bo || src_bo == dst_bo) + return -EINVAL; + + dma_cmd = (struct mwv207_dma_cmd *)mjob->cmds; + ret = mwv207_submit_patch_dma_entry(mjob, &dma_cmd->src, src_bo, + dma_cmd->width, dma_cmd->height); + if (ret) + return ret; + return mwv207_submit_patch_dma_entry(mjob, &dma_cmd->dst, dst_bo, + dma_cmd->width, dma_cmd->height); +} + +static int mwv207_submit_patch(struct drm_device *dev, struct mwv207_job *mjob) +{ + struct mwv207_bo *jbo; + int i, j, ret; + void *ptr; + + if (mjob->is_dma) + return mwv207_submit_patch_dma(dev, mjob); + + for (i = 0; i < mjob->nr_relocs; ++i) { + ret = mwv207_submit_patch_single(mjob, &mjob->relocs[i], + mjob->cmds, mjob->cmd_size); + if (ret) + return ret; + } + + for (i = 0; i < mjob->nr_cmd_dat; ++i) { + if (mjob->cmd_dats[i].nr_relocs) { + for (j = 0; j < mjob->cmd_dats[i].nr_relocs; j++) { + ret = mwv207_submit_patch_single(mjob, &mjob->cmd_dats[i].relocs[j], + mjob->cmd_dats[i].dat, mjob->cmd_dats[i].dat_size); + if (ret) + return ret; + } + } + + + jbo = mjob->cmd_dats[i].jbo; + + + ret = ttm_bo_wait(&jbo->tbo, true, false); + if (ret) + return ret; + + ret = mwv207_bo_kmap_reserved(jbo, &ptr); + if (ret) + return ret; + memcpy_toio(ptr, mjob->cmd_dats[i].dat, mjob->cmd_dats[i].dat_size); + mwv207_bo_kunmap_reserved(jbo); + } + + return 0; +} + +static int mwv207_submit_validate(struct drm_device *dev, struct mwv207_job *mjob) +{ + struct ttm_operation_ctx ctx = { + .interruptible = true, + .no_wait_gpu = false + }; + struct mwv207_bo *jbo; + int i, ret; + + for (i = 0; i < mjob->nr_bos + mjob->nr_cmd_dat; ++i) { + jbo = mwv207_job_bo(mjob, i); + + if (jbo->pin_count) + continue; + + if (mjob->is_dma) { + if (jbo->tbo.mem.mem_type != TTM_PL_SYSTEM) + continue; + + mwv207_bo_placement_from_domain(jbo, 0x4, 0); + } else { + + jbo->flags |= (1<<2); + if (i >= mjob->nr_bos) + jbo->flags |= (1<<0); + mwv207_bo_placement_from_domain(jbo, 0x2, 0); + } + + ret = ttm_bo_validate(&jbo->tbo, &jbo->placement, &ctx); + if (ret) + return ret; + } + + return 0; +} + +static int mwv207_submit_attach_dependency(struct mwv207_job *mjob, struct drm_mwv207_submit *args) +{ + struct dma_resv *resv; + struct mwv207_tvb *mtvb; + int ret; + + mwv207_for_each_mtvb(mtvb, mjob) { + resv = mtvb->base.bo->base.resv; + if (mtvb->base.num_shared == 0) { + ret = dma_resv_get_fences_rcu(resv, &mtvb->excl, + &mtvb->nr_shared, &mtvb->shared); + if (ret) + return ret; + } else + mtvb->excl = dma_resv_get_excl_rcu(resv); + } + + if (unlikely(args->flags & 0x00000001)) { + mjob->fence_in = sync_file_get_fence(args->fence_fd); + if (!mjob->fence_in) + return -EINVAL; + } + + return 0; +} + +static int mwv207_submit_commit(struct mwv207_job *mjob, + struct drm_mwv207_submit *args, struct dma_fence **fence) +{ + int ret; + + /* XXX: job init and push should be protected with common lock, + * refer to drm_sched_entity_push_job documentation for details + */ + ret = drm_sched_job_init(&mjob->base, mjob->engine_entity, mjob->ctx); + if (ret) + return ret; + + if (args->flags & 0x00000002) { + struct sync_file *sync_file; + int fd; + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) + return fd; + sync_file = sync_file_create(&mjob->base.s_fence->finished); + if (!sync_file) { + put_unused_fd(fd); + return -ENOMEM; + } + fd_install(fd, sync_file->file); + args->fence_fd = fd; + } + + + *fence = &mjob->base.s_fence->finished; + + + mwv207_job_get(mjob); + drm_sched_entity_push_job(&mjob->base, mjob->engine_entity); + return 0; +} + +int mwv207_submit_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct drm_mwv207_submit *args = data; + struct ww_acquire_ctx ticket; + struct mwv207_job *mjob; + struct dma_fence *fence = NULL; + int ret; + + if (args->padding || (args->flags & ~0x00000003)) + return -EINVAL; + if (args->cmd_size == 0 || args->cmds == 0) + return -EINVAL; + + mjob = mwv207_submit_init(dev, filp, args); + if (IS_ERR(mjob)) + return PTR_ERR(mjob); + ret = ttm_eu_reserve_buffers(&ticket, &mjob->tvblist, true, NULL); + if (ret) + goto put_job; + ret = mwv207_submit_validate(dev, mjob); + if (ret) + goto unreserve; + ret = mwv207_submit_patch(dev, mjob); + if (ret) + goto unreserve; + ret = mwv207_submit_attach_dependency(mjob, args); + if (ret) + goto unreserve; + ret = mwv207_submit_commit(mjob, args, &fence); + if (ret) + goto unreserve; +unreserve: + if (likely(fence)) + ttm_eu_fence_buffer_objects(&ticket, &mjob->tvblist, fence); + else + ttm_eu_backoff_reservation(&ticket, &mjob->tvblist); +put_job: + mwv207_job_put(mjob); + + return ret; +} diff --git a/drivers/gpu/drm/mwv207/mwv207_submit.h b/drivers/gpu/drm/mwv207/mwv207_submit.h new file mode 100644 index 0000000000000000000000000000000000000000..194fceafa88eed72a966d63c12f94c34a0328c22 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_submit.h @@ -0,0 +1,23 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#ifndef MWV207_SUBMIT_H_2S1GH9LD +#define MWV207_SUBMIT_H_2S1GH9LD + +int mwv207_submit_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp); + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_ttm.c b/drivers/gpu/drm/mwv207/mwv207_ttm.c new file mode 100644 index 0000000000000000000000000000000000000000..45cdf2e73615c55c5cde56f718444ccacb7dbdb1 --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_ttm.c @@ -0,0 +1,613 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mwv207.h" +#include "mwv207_bo.h" +#include "mwv207_drm.h" +#include "mwv207_sched.h" + +static int mwv207_mem_visible(struct mwv207_device *jdev, struct ttm_resource *mem) +{ + if (mem->mem_type == TTM_PL_SYSTEM || + mem->mem_type == TTM_PL_TT) + return true; + if (mem->mem_type != TTM_PL_VRAM) + return false; + + + return ((mem->start << PAGE_SHIFT) + mem->size < jdev->visible_vram_size); +} + + +static void mwv207_evict_flags(struct ttm_buffer_object *bo, + struct ttm_placement *placement) +{ + struct mwv207_device *jdev; + struct mwv207_bo *jbo; + static const struct ttm_place placements = { + .fpfn = 0, + .lpfn = 0, + .flags = 0, + .mem_type = TTM_PL_SYSTEM + }; + + if (!mwv207_ttm_bo_is_mwv207_bo(bo)) { + placement->placement = &placements; + placement->busy_placement = &placements; + placement->num_placement = 1; + placement->num_busy_placement = 1; + return; + } + + jbo = to_jbo(bo); + jdev = ddev_to_jdev(jbo->tbo.base.dev); + + /* evict order: * visible vram -> invisible vram -> GTT -> SYSTEM */ + if (bo->mem.mem_type == TTM_PL_VRAM) { + if (jdev->visible_vram_size != jdev->vram_size + && mwv207_mem_visible(jdev, &bo->mem)) { + jbo->flags &= ~((1<<0) | (1<<2)); + mwv207_bo_placement_from_domain(jbo, + 0x2 | 0x4, + false); + BUG_ON(!(jbo->placements[0].mem_type & TTM_PL_VRAM)); + BUG_ON(!(jbo->placements[1].mem_type & TTM_PL_TT)); + jbo->placements[0].fpfn = jdev->visible_vram_size >> PAGE_SHIFT; + jbo->placements[0].lpfn = 0; + jbo->placement.placement = &jbo->placements[0]; + jbo->placement.busy_placement = &jbo->placements[1]; + jbo->placement.num_placement = 1; + jbo->placement.num_busy_placement = 1; + } else + mwv207_bo_placement_from_domain(jbo, 0x4, false); + } else + mwv207_bo_placement_from_domain(jbo, 0x1, false); + + *placement = jbo->placement; +} + +static int mwv207_ttm_io_mem_reserve(struct ttm_bo_device *bdev, + struct ttm_resource *mem) +{ + struct mwv207_device *jdev = bdev_to_jdev(bdev); + size_t bus_size = (size_t)mem->num_pages << PAGE_SHIFT; + + mem->bus.addr = NULL; + mem->bus.offset = 0; + mem->bus.is_iomem = false; + + switch (mem->mem_type) { + case TTM_PL_SYSTEM: + case TTM_PL_TT: + + return 0; + case TTM_PL_VRAM: + + mem->bus.offset = mem->start << PAGE_SHIFT; + if (mem->bus.offset + bus_size > jdev->visible_vram_size) + return -EINVAL; + mem->bus.is_iomem = true; + mem->bus.offset += jdev->vram_bar_base; + break; + default: + return -EINVAL; + } + return 0; +} + +static int mwv207_ttm_tt_bind(struct ttm_bo_device *bdev, + struct ttm_tt *ttm, + struct ttm_resource *bo_mem) +{ + if (!bo_mem) + return -EINVAL; + return 0; +} + +static void mwv207_ttm_tt_unbind(struct ttm_bo_device *bdev, + struct ttm_tt *ttm) +{ + return ; +} + +static void mwv207_ttm_tt_destroy(struct ttm_bo_device *bdev, + struct ttm_tt *ttm) +{ + struct mwv207_ttm_tt *gtt = to_gtt(ttm); + ttm_tt_destroy_common(bdev, ttm); + + ttm_dma_tt_fini(>t->ttm); + kfree(gtt); +} + +static struct ttm_tt *mwv207_ttm_tt_create(struct ttm_buffer_object *bo, + u32 page_flags) +{ + struct mwv207_device *jdev; + struct mwv207_ttm_tt *gtt; + + jdev = ddev_to_jdev(bo->base.dev); + gtt = kzalloc(sizeof(struct mwv207_ttm_tt), GFP_KERNEL); + if (gtt == NULL) + return NULL; + gtt->jdev = jdev; + if (ttm_dma_tt_init(>t->ttm, bo, page_flags)) { + kfree(gtt); + return NULL; + } + return >t->ttm.ttm; +} + +static int mwv207_ttm_tt_populate(struct ttm_bo_device *bdev, + struct ttm_tt *ttm, + struct ttm_operation_ctx *ctx) +{ + struct mwv207_ttm_tt *gtt = to_gtt(ttm); + struct mwv207_device *jdev = bdev_to_jdev(bdev); + + return ttm_dma_populate(>t->ttm, jdev->dev, ctx); +} + +static void mwv207_ttm_tt_unpopulate(struct ttm_bo_device *bdev, + struct ttm_tt *ttm) +{ + struct mwv207_ttm_tt *gtt = to_gtt(ttm); + struct mwv207_device *jdev = bdev_to_jdev(bdev); + + ttm_dma_unpopulate(>t->ttm, jdev->dev); +} + +static int mwv207_ttm_job_submit(struct mwv207_job *mjob, struct dma_fence **fence) +{ + int ret; + + ret = drm_sched_job_init(&mjob->base, mjob->engine_entity, NULL); + if (ret) + return ret; + + *fence = &mjob->base.s_fence->finished; + + mwv207_job_get(mjob); + drm_sched_entity_push_job(&mjob->base, mjob->engine_entity); + + return 0; +} + +static struct mwv207_job *mwv207_ttm_job_alloc(struct ttm_buffer_object *bo) +{ + struct mwv207_job *mjob = mwv207_job_alloc(); + struct mwv207_dma_cmd *dma_cmd; + struct mwv207_tvb *mtvb; + int ret; + + if (!mjob) + return ERR_PTR(-ENOMEM); + + + dma_cmd = kmalloc(sizeof(*dma_cmd), GFP_KERNEL); + if (!dma_cmd) { + ret = -ENOMEM; + goto err; + } + dma_cmd->src.offset = 0; + dma_cmd->src.stride = bo->mem.size; + dma_cmd->dst.offset = 0; + dma_cmd->dst.stride = bo->mem.size; + dma_cmd->width = bo->mem.size; + dma_cmd->height = 1; + + mjob->cmds = (char *)dma_cmd; + mjob->cmd_size = sizeof(*dma_cmd); + mjob->engine_entity = ddev_to_jdev(bo->base.dev)->dma_entity; + mjob->is_dma = true; + + + mjob->mtvb = kzalloc(sizeof(struct mwv207_tvb), GFP_KERNEL); + if (!mjob->mtvb) { + ret = -ENOMEM; + goto err; + } + mtvb = &mjob->mtvb[0]; + list_add_tail(&mtvb->base.head, &mjob->tvblist); + + ret = dma_resv_get_fences_rcu(bo->base.resv, &mtvb->excl, + &mtvb->nr_shared, &mtvb->shared); + if (ret) + goto err; + + return mjob; +err: + mwv207_job_put(mjob); + return ERR_PTR(ret); +} + +static int mwv207_move_vram_gtt(struct ttm_buffer_object *bo, bool evict, + struct ttm_operation_ctx *ctx, + struct ttm_resource *new_mem) +{ + struct ttm_tt *ttm = bo->ttm; + struct mwv207_ttm_tt *gtt = to_gtt(ttm); + struct mwv207_dma_cmd *dma_cmd; + struct mwv207_job *mjob; + struct dma_fence *fence; + int ret; + + mjob = mwv207_ttm_job_alloc(bo); + if (IS_ERR(mjob)) + return PTR_ERR(mjob); + + dma_cmd = (struct mwv207_dma_cmd *)mjob->cmds; + dma_cmd->src.base = bo->mem.start << PAGE_SHIFT; + dma_cmd->src.pg_nr_type = MWV207_DMA_NR_PAGES_VRAM(bo->mem.num_pages); + dma_cmd->dst.base = (u64)gtt->ttm.dma_address; + dma_cmd->dst.pg_nr_type = MWV207_DMA_NR_PAGES_RAM(ttm->num_pages); + + ret = mwv207_ttm_job_submit(mjob, &fence); + if (!ret) + ret = ttm_bo_move_accel_cleanup(bo, fence, evict, true, new_mem); + + mwv207_job_put(mjob); + return ret; +} + +static int mwv207_move_gtt_vram(struct ttm_buffer_object *bo, bool evict, + struct ttm_operation_ctx *ctx, + struct ttm_resource *new_mem) +{ + struct ttm_tt *ttm = bo->ttm; + struct mwv207_ttm_tt *gtt = to_gtt(ttm); + struct mwv207_dma_cmd *dma_cmd; + struct mwv207_job *mjob; + struct dma_fence *fence; + int ret; + + mjob = mwv207_ttm_job_alloc(bo); + if (IS_ERR(mjob)) + return PTR_ERR(mjob); + + dma_cmd = (struct mwv207_dma_cmd *)mjob->cmds; + dma_cmd->src.base = (u64)gtt->ttm.dma_address; + dma_cmd->src.pg_nr_type = MWV207_DMA_NR_PAGES_RAM(ttm->num_pages); + dma_cmd->dst.base = new_mem->start << PAGE_SHIFT; + dma_cmd->dst.pg_nr_type = MWV207_DMA_NR_PAGES_VRAM(bo->mem.num_pages); + + ret = mwv207_ttm_job_submit(mjob, &fence); + if (!ret) + ret = ttm_bo_move_accel_cleanup(bo, fence, evict, true, new_mem); + + mwv207_job_put(mjob); + return ret; +} + +static int mwv207_move_vram_vram(struct ttm_buffer_object *bo, bool evict, + struct ttm_operation_ctx *ctx, + struct ttm_resource *new_mem) +{ + struct mwv207_dma_cmd *dma_cmd; + struct mwv207_job *mjob; + struct dma_fence *fence; + int ret; + + mjob = mwv207_ttm_job_alloc(bo); + if (IS_ERR(mjob)) + return PTR_ERR(mjob); + + dma_cmd = (struct mwv207_dma_cmd *)mjob->cmds; + dma_cmd->src.base = bo->mem.start << PAGE_SHIFT; + dma_cmd->src.pg_nr_type = MWV207_DMA_NR_PAGES_VRAM(bo->mem.num_pages); + dma_cmd->dst.base = new_mem->start << PAGE_SHIFT; + dma_cmd->dst.pg_nr_type = MWV207_DMA_NR_PAGES_VRAM(new_mem->num_pages); + + ret = mwv207_ttm_job_submit(mjob, &fence); + if (!ret) + ret = ttm_bo_move_accel_cleanup(bo, fence, evict, true, new_mem); + + mwv207_job_put(mjob); + return ret; +} + +static int mwv207_move_vram_ram(struct ttm_buffer_object *bo, bool evict, + struct ttm_operation_ctx *ctx, + struct ttm_resource *new_mem) +{ + struct ttm_placement placement; + struct ttm_place placements; + struct ttm_resource tmp_mem; + int ret; + + if (new_mem->mem_type == TTM_PL_TT) + return mwv207_move_vram_gtt(bo, evict, ctx, new_mem); + + + tmp_mem = *new_mem; + tmp_mem.mm_node = NULL; + placement.num_placement = 1; + placement.placement = &placements; + placement.num_busy_placement = 1; + placement.busy_placement = &placements; + placements.fpfn = 0; + placements.lpfn = 0; + placements.flags = TTM_PL_MASK_CACHING; + placements.mem_type = TTM_PL_TT; + ret = ttm_bo_mem_space(bo, &placement, &tmp_mem, ctx); + if (unlikely(ret)) + return ret; + + ret = ttm_tt_set_placement_caching(bo->ttm, tmp_mem.placement); + if (unlikely(ret)) + goto out; + + ret = ttm_tt_populate(bo->bdev, bo->ttm, ctx); + if (unlikely(ret)) + goto out; + + ret = mwv207_move_vram_gtt(bo, evict, ctx, &tmp_mem); + if (likely(!ret)) + ret = ttm_bo_move_ttm(bo, ctx, new_mem); +out: + ttm_resource_free(bo, &tmp_mem); + return ret; +} + +static int mwv207_move_ram_vram(struct ttm_buffer_object *bo, bool evict, + struct ttm_operation_ctx *ctx, + struct ttm_resource *new_mem) +{ + struct ttm_placement placement; + struct ttm_place placements; + struct ttm_resource tmp_mem; + int ret; + + if (bo->mem.mem_type == TTM_PL_TT) + return mwv207_move_gtt_vram(bo, evict, ctx, new_mem); + + + tmp_mem = *new_mem; + tmp_mem.mm_node = NULL; + placement.num_placement = 1; + placement.placement = &placements; + placement.num_busy_placement = 1; + placement.busy_placement = &placements; + placements.fpfn = 0; + placements.lpfn = 0; + placements.flags = TTM_PL_MASK_CACHING; + placements.mem_type = TTM_PL_TT; + ret = ttm_bo_mem_space(bo, &placement, &tmp_mem, ctx); + if (unlikely(ret)) + return ret; + + ret = ttm_bo_move_ttm(bo, ctx, &tmp_mem); + if (unlikely(ret)) + goto out; + + ret = mwv207_move_gtt_vram(bo, evict, ctx, new_mem); +out: + ttm_resource_free(bo, &tmp_mem); + return ret; +} + +static int mwv207_mem_in_ram(struct ttm_resource *mem) +{ + return mem->mem_type == TTM_PL_SYSTEM || mem->mem_type == TTM_PL_TT; +} + +static int mwv207_bo_move(struct ttm_buffer_object *bo, bool evict, + struct ttm_operation_ctx *ctx, + struct ttm_resource *new_mem) +{ + struct ttm_resource *old_mem = &bo->mem; + struct mwv207_bo *jbo = to_jbo(bo); + struct mwv207_device *jdev; + int ret; + + if (WARN_ON_ONCE(jbo->pin_count > 0)) + return -EINVAL; + + jdev = ddev_to_jdev(jbo->tbo.base.dev); + if (old_mem->mem_type == TTM_PL_SYSTEM && bo->ttm == NULL) { + + ttm_bo_move_null(bo, new_mem); + return 0; + } + + if (mwv207_mem_in_ram(old_mem) && mwv207_mem_in_ram(new_mem)) { + + ttm_bo_move_null(bo, new_mem); + return 0; + } + + if (old_mem->mem_type == TTM_PL_VRAM && + mwv207_mem_in_ram(new_mem)) { + ret = mwv207_move_vram_ram(bo, evict, ctx, new_mem); + } else if (mwv207_mem_in_ram(old_mem) && + new_mem->mem_type == TTM_PL_VRAM) { + ret = mwv207_move_ram_vram(bo, evict, ctx, new_mem); + } else if (old_mem->mem_type == TTM_PL_VRAM && + new_mem->mem_type == TTM_PL_VRAM) { + ret = mwv207_move_vram_vram(bo, evict, ctx, new_mem); + } else { + ret = -EINVAL; + } + + if (ret == 0) + goto moved; + + + if (!mwv207_mem_visible(jdev, new_mem) + || !mwv207_mem_visible(jdev, old_mem)) { + pr_err("mwv207: can't move invisible mem by cpu"); + return ret; + } + ret = ttm_bo_move_memcpy(bo, ctx, new_mem); + if (ret) + return ret; +moved: + if (!mwv207_mem_visible(jdev, new_mem)) + jbo->flags &= ~(1<<0); + + return 0; +} + +static void mwv207_bo_move_notify(struct ttm_buffer_object *bo, + bool evict, + struct ttm_resource *new_mem) +{ + struct mwv207_bo *jbo; + struct mwv207_device *jdev; + + if (!mwv207_ttm_bo_is_mwv207_bo(bo)) + return; + jbo = to_jbo(bo); + jdev = ddev_to_jdev(jbo->tbo.base.dev); +} + +static int mwv207_bo_fault_reserve_notify(struct ttm_buffer_object *bo) +{ + struct ttm_operation_ctx ctx = { + .interruptible = true, + .no_wait_gpu = false + }; + struct mwv207_device *jdev; + struct mwv207_bo *jbo; + u64 size, offset; + u32 domain; + int ret; + + if (!mwv207_ttm_bo_is_mwv207_bo(bo)) + return 0; + if (bo->mem.mem_type != TTM_PL_VRAM) + return 0; + + jbo = to_jbo(bo); + jdev = ddev_to_jdev(jbo->tbo.base.dev); + size = bo->mem.num_pages << PAGE_SHIFT; + offset = bo->mem.start << PAGE_SHIFT; + + jbo->flags |= (1<<0); + + if ((offset + size) <= jdev->visible_vram_size) + return 0; + + if (jbo->pin_count > 0) + return -EINVAL; + + /* TODO: + * 1. moving to RAM won't invoke swap, and thus causing ENOMEM when + * system memory is low. figure out why. + * 2. slide iatu window to optimize out this validation. + */ + domain = 0x2; +retry: + mwv207_bo_placement_from_domain(jbo, domain, false); + ret = ttm_bo_validate(bo, &jbo->placement, &ctx); + if (unlikely(ret)) { + if (ret != -ERESTARTSYS) { + if (domain == 0x2) { + + domain = 0x1; + goto retry; + } + } + return ret; + } + + + offset = bo->mem.start << PAGE_SHIFT; + if (bo->mem.mem_type == TTM_PL_VRAM && + (offset + size) > jdev->visible_vram_size) { + DRM_ERROR("bo validation goes crazy"); + return -EINVAL; + } + + return ret; +} + +static int mwv207_bo_verify_access(struct ttm_buffer_object *tbo, struct file *filp) +{ + return drm_vma_node_verify_access(&tbo->base.vma_node, + filp->private_data); +} + +static struct ttm_bo_driver mwv207_bo_driver = { + .ttm_tt_create = mwv207_ttm_tt_create, + .ttm_tt_populate = mwv207_ttm_tt_populate, + .ttm_tt_unpopulate = mwv207_ttm_tt_unpopulate, + .ttm_tt_bind = &mwv207_ttm_tt_bind, + .ttm_tt_unbind = &mwv207_ttm_tt_unbind, + .ttm_tt_destroy = &mwv207_ttm_tt_destroy, + .eviction_valuable = ttm_bo_eviction_valuable, + .evict_flags = mwv207_evict_flags, + .move = mwv207_bo_move, + .io_mem_reserve = mwv207_ttm_io_mem_reserve, + .move_notify = mwv207_bo_move_notify, + .fault_reserve_notify = &mwv207_bo_fault_reserve_notify, + .verify_access = mwv207_bo_verify_access, +}; + +int mwv207_ttm_init(struct mwv207_device *jdev) +{ + int ret; + + ret = ttm_bo_device_init(&jdev->bdev, + &mwv207_bo_driver, + jdev->base.anon_inode->i_mapping, + jdev->base.vma_offset_manager, + true); + if (ret) { + DRM_ERROR("failed to initialize buffer object driver(%d).\n", ret); + return ret; + } + + + ret = ttm_range_man_init(&jdev->bdev, TTM_PL_TT, true, + 0x80000000ULL >> PAGE_SHIFT); + if (ret) { + DRM_ERROR("failed to initialize GTT heap.\n"); + goto out_no_vram; + } + + + ret = ttm_range_man_init(&jdev->bdev, TTM_PL_VRAM, false, + jdev->vram_size >> PAGE_SHIFT); + if (ret) { + DRM_ERROR("failed to initialize VRAM heap.\n"); + goto out_no_vram; + } + DRM_INFO("mwv207: %lldM of VRAM memory size\n", jdev->vram_size / (1024 * 1024)); + return 0; + +out_no_vram: + ttm_bo_device_release(&jdev->bdev); + return ret; +} + +void mwv207_ttm_fini(struct mwv207_device *jdev) +{ + ttm_range_man_fini(&jdev->bdev, TTM_PL_VRAM); + ttm_range_man_fini(&jdev->bdev, TTM_PL_TT); + ttm_bo_device_release(&jdev->bdev); + DRM_INFO("mwv207: ttm finalized\n"); +} diff --git a/drivers/gpu/drm/mwv207/mwv207_ttm.h b/drivers/gpu/drm/mwv207/mwv207_ttm.h new file mode 100644 index 0000000000000000000000000000000000000000..9cbf61ed32004b48fb1f1887492eb576331cc4cd --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_ttm.h @@ -0,0 +1,25 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef MWV207_TTM_H_TJTW9M4R +#define MWV207_TTM_H_TJTW9M4R +#include +#include +#include +#include "mwv207.h" + +int mwv207_bo_fault_reserve_notify(struct ttm_buffer_object *bo); + +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_vbios.c b/drivers/gpu/drm/mwv207/mwv207_vbios.c new file mode 100644 index 0000000000000000000000000000000000000000..0da8244efdb97a98cc4a9f4601455e2667801eef --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_vbios.c @@ -0,0 +1,189 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include "mwv207.h" +#include "mwv207_vbios.h" + +/* + */ +static inline u8 mwv207_flash_read8(struct mwv207_device *jdev, u32 offset) +{ + return ioread8(jdev->mmio + 0x100000 + offset); +} + +static inline u16 mwv207_flash_read16(struct mwv207_device *jdev, u32 offset) +{ + u16 dat = ioread16(jdev->mmio + 0x100000 + offset); + + return le16_to_cpu(dat); +} + +static inline u32 mwv207_flash_read32(struct mwv207_device *jdev, u32 offset) +{ + u32 dat = ioread32(jdev->mmio + 0x100000 + offset); + + return le32_to_cpu(dat); +} + +static inline void mwv207_flash_cpy(struct mwv207_device *jdev, void *dst, u32 offset, u32 len) +{ + memcpy_fromio(dst, jdev->mmio + 0x100000 + offset, len); +} + +static int mwv207_vbios_check_indexer(struct mwv207_device *jdev, u32 offset) +{ + u16 count, chksum, calc; + + if (mwv207_flash_read16(jdev, offset) != 0xaa55) + return -EINVAL; + + count = mwv207_flash_read16(jdev, offset + 2); + chksum = mwv207_flash_read16(jdev, offset + 4); + + jdev->vbios.sector_count = count; + for (calc = 0, offset += 8, count *= 8; count > 0; count--) + calc += mwv207_flash_read8(jdev, offset++); + + if (calc != chksum) + return -EINVAL; + + jdev->vbios.indexer_valid = 1; + return 0; +} + +static inline int mwv207_vbios_sector(struct mwv207_device *jdev, u32 idx, u32 *poffset) +{ + if (poffset == NULL || !jdev->vbios.indexer_valid || idx >= jdev->vbios.sector_count) + return -EINVAL; + + *poffset = mwv207_flash_read32(jdev, 0xa000 + 0x8 + idx * 8 + 4); + return 0; +} + +static inline int mwv207_vbios_insert_vdat(struct mwv207_device *jdev, u32 key, + struct mwv207_vdat *vdat) +{ + int ret; + + mutex_lock(&jdev->vbios.cfg_lock); + ret = idr_alloc(&jdev->vbios.cfg_table, vdat, key, key + 1, GFP_KERNEL); + mutex_unlock(&jdev->vbios.cfg_lock); + + return ret; +} + +static void mwv207_vbios_parse_hdmi(struct mwv207_device *jdev) +{ + u32 count, offset, chksum, len; + struct mwv207_vdat *vdat[4]; + u32 select[4]; + u8 magic[8]; + int ret, i; + + ret = mwv207_vbios_sector(jdev, 0xa, &offset); + if (ret) + return; + + mwv207_flash_cpy(jdev, magic, offset, 8); + if (memcmp(magic, "JMPHYCFG", 8)) + return; + + len = mwv207_flash_read32(jdev, offset + 0x18); + if (len > 0x1200 || (len % 1140)) + return; + count = len / 1140; + + for (chksum = 0, i = 0; i < len; i += 4) + chksum += mwv207_flash_read32(jdev, offset + 0x20 + i); + if (chksum != mwv207_flash_read32(jdev, offset + 0x14)) + return; + + for (i = 0; i < 4; i++) { + select[i] = mwv207_flash_read8(jdev, offset + 0x1c + i); + if (select[i] > min_t(u32, 3, count - 1)) + return; + } + + for (i = 0; i < 4; i++) { + void *dat; + + vdat[i] = devm_kzalloc(jdev->dev, sizeof(*vdat[i]), GFP_KERNEL); + if (!vdat[i]) + return; + vdat[i]->len = 1140; + dat = devm_kmalloc(jdev->dev, vdat[i]->len, GFP_KERNEL); + if (!dat) + return; + mwv207_flash_cpy(jdev, dat, + offset + 0x20 + select[i] * 1140, + 1140); + + vdat[i]->dat = dat; + + (void)mwv207_vbios_insert_vdat(jdev, 0xfff0 + i, vdat[i]); + } +} + +static void mwv207_vbios_parse_edp(struct mwv207_device *jdev) +{ + +} + +static void mwv207_vbios_parse_cfg(struct mwv207_device *jdev) +{ + +} + +const struct mwv207_vdat *mwv207_vbios_vdat(struct mwv207_device *jdev, u32 key) +{ + struct mwv207_vdat *dat; + + mutex_lock(&jdev->vbios.cfg_lock); + dat = idr_find(&jdev->vbios.cfg_table, key); + mutex_unlock(&jdev->vbios.cfg_lock); + + return dat; +} + +void mwv207_vbios_init(struct mwv207_device *jdev) +{ + int ret; + + mutex_init(&jdev->vbios.vcmd_lock); + mutex_init(&jdev->vbios.cfg_lock); + idr_init(&jdev->vbios.cfg_table); + + ret = mwv207_vbios_check_indexer(jdev, 0xa000); + if (ret) { + pr_warn("mwv207: vbios indexer is not valid"); + return; + } + + mwv207_vbios_parse_hdmi(jdev); + mwv207_vbios_parse_edp(jdev); + mwv207_vbios_parse_cfg(jdev); +} + +void mwv207_vbios_fini(struct mwv207_device *jdev) +{ + struct mwv207_vdat *vdat; + u32 id; + + idr_for_each_entry(&jdev->vbios.cfg_table, vdat, id) { + devm_kfree(jdev->dev, vdat->dat); + devm_kfree(jdev->dev, vdat); + } + idr_destroy(&jdev->vbios.cfg_table); +} diff --git a/drivers/gpu/drm/mwv207/mwv207_vbios.h b/drivers/gpu/drm/mwv207/mwv207_vbios.h new file mode 100644 index 0000000000000000000000000000000000000000..75796f865645b5bd3ed8687fadcb6b5e6716416a --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_vbios.h @@ -0,0 +1,51 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#ifndef MWV207_VBIOS_H_D2JHN3FG +#define MWV207_VBIOS_H_D2JHN3FG + +struct mwv207_device; + +enum pll_id { + MWV207_PLL_GPU = 0, + MWV207_PLL_GRAPH0, + MWV207_PLL_GRAPH1, + MWV207_PLL_GRAPH2, + MWV207_PLL_GRAPH3, + MWV207_PLL_GRAPH4, + MWV207_PLL_HD, + MWV207_PLL_GU3D0, + MWV207_PLL_EDP, + MWV207_PLL_GU3D1, + MWV207_PLL_RESERVED, + MWV207_PLL_GU2D_PLL, + MWV207_PLL_DDR, + MWV207_PLL_COUNT +}; + +struct mwv207_vdat { + const void *dat; + u32 len; +}; + +int mwv207_vbios_set_pll(struct mwv207_device *jdev, int pll_id, u32 kfreq); + +int mwv207_vbios_get_pll(struct mwv207_device *jdev, int pll_idx, unsigned long *kfreq); + +const struct mwv207_vdat *mwv207_vbios_vdat(struct mwv207_device *jdev, u32 key); + +void mwv207_vbios_init(struct mwv207_device *jdev); +void mwv207_vbios_fini(struct mwv207_device *jdev); +#endif diff --git a/drivers/gpu/drm/mwv207/mwv207_vcmd.c b/drivers/gpu/drm/mwv207/mwv207_vcmd.c new file mode 100644 index 0000000000000000000000000000000000000000..6c23859875b8a3662bb1fe973ffc3b74160f2d4c --- /dev/null +++ b/drivers/gpu/drm/mwv207/mwv207_vcmd.c @@ -0,0 +1,178 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include +#include +#include "mwv207.h" +#include "mwv207_vbios.h" + +#define MWV207_PLL_TIMEOUT msecs_to_jiffies(1000) + +#define MWV207_PLL_STATE_INDEX(id) (0x100 * ((id) / 6) + 0x20 * ((id) % 6)) +#define MWV207_PLL_STATE_BASE(id) (0x9b0000 + 0xA00 + MWV207_PLL_STATE_INDEX(id)) + +#define MWV207_PLL_FBDIV_REFDIV(id) (MWV207_PLL_STATE_BASE(id) + 0x00) +#define MWV207_PLL_FRAC(id) (MWV207_PLL_STATE_BASE(id) + 0x04) +#define MWV207_PLL_POSTDIV(id) (MWV207_PLL_STATE_BASE(id) + 0x08) + +static inline int mwv207_vcmd_idle(struct mwv207_device *jdev) +{ + u32 state; + + state = jdev_read(jdev, (0x9b0144)) >> 28; + + return state == 0; +} + +static inline int mwv207_vcmd_wait_idle(struct mwv207_device *jdev) +{ + int i; + + + for (i = 0; i < 1000 && !mwv207_vcmd_idle(jdev); i++) + usleep_range(1000, 1001); + return mwv207_vcmd_idle(jdev) ? 0 : -ETIMEDOUT; +} + +static inline void mwv207_vcmd_req(struct mwv207_device *jdev, int cmd, + int idx, u32 value, int enable) +{ + u32 req; + + req = cmd << 28; + req |= idx << 24; + req |= value << 1; + req |= enable ? 1 : 0; + + jdev_write(jdev, (0x9b0140), req); +} + +static inline void mwv207_vcmd_start(struct mwv207_device *jdev) +{ + jdev_write(jdev, (0x9b0144), 1 << 28); +} + +static inline int mwv207_vcmd_result(struct mwv207_device *jdev) +{ + return (jdev_read(jdev, (0x9b0144)) >> 24) & 0xf; +} + +static int mwv207_vcmd_execute(struct mwv207_device *jdev, int cmd, + int idx, u32 value, int enable) +{ + int ret; + + mutex_lock(&jdev->vbios.vcmd_lock); + + + ret = mwv207_vcmd_wait_idle(jdev); + if (ret) + goto unlock; + + + mwv207_vcmd_req(jdev, cmd, idx, value, enable); + + + mwv207_vcmd_start(jdev); + + + ret = mwv207_vcmd_wait_idle(jdev); + if (ret) + goto unlock; + + ret = mwv207_vcmd_result(jdev); + switch (ret) { + case 0: + goto unlock; + case 1: + pr_err("unsupport pll type!)"); + break; + case 2: + + pr_info_once("pll set, invalid freq!"); + break; + case 3: + pr_err("pll busy!"); + break; + case 4: + pr_err("pll set, invalid argument!"); + break; + case 9: + pr_err("PLL REQ cmd code unsupport!"); + break; + default: + pr_err("unknown pll error! (code 0x%x)", ret); + break; + } + ret = -EIO; +unlock: + mutex_unlock(&jdev->vbios.vcmd_lock); + return ret; +} + +int mwv207_vbios_set_pll(struct mwv207_device *jdev, int pll_idx, u32 kfreq) +{ + if (pll_idx >= MWV207_PLL_COUNT) + return -EINVAL; + + return mwv207_vcmd_execute(jdev, 1, pll_idx, + kfreq, kfreq ? 1 : 0); +} + +int mwv207_vbios_get_pll(struct mwv207_device *jdev, int pll_idx, unsigned long *kfreq) +{ + u32 port, state1, state2, state3; + u32 refdiv, fbintdiv, fbracdiv, postdiv1, postdiv2; + u32 postdiv_mask, postdiv_offset; + u64 freq; + + if (pll_idx >= MWV207_PLL_COUNT) + return -EINVAL; + + port = pll_idx ? 0 : 1; + + mutex_lock(&jdev->vbios.vcmd_lock); + state1 = jdev_read(jdev, MWV207_PLL_FBDIV_REFDIV(pll_idx)); + state2 = jdev_read(jdev, MWV207_PLL_FRAC(pll_idx)); + state3 = jdev_read(jdev, MWV207_PLL_POSTDIV(pll_idx)); + mutex_unlock(&jdev->vbios.vcmd_lock); + + refdiv = state1 & 0x3f; + fbintdiv = (state1 & 0xfff0000) >> 16; + fbracdiv = state2 & 0xffffff; + postdiv1 = 4; + + postdiv_offset = 8 * (port % 4); + postdiv_mask = 0x7f << postdiv_offset; + postdiv2 = (state3 & postdiv_mask) >> postdiv_offset; + + if (!refdiv || !postdiv1 || !postdiv2) { + dev_dbg(jdev->dev, "get pll failed"); + return -EINVAL; + } + + freq = 100000 * 100000ull / refdiv; + freq = freq * fbintdiv + ((freq * fbracdiv) >> 24); + freq /= postdiv1; + freq /= postdiv2; + freq = DIV_ROUND_CLOSEST_ULL(freq, 100000); + + *kfreq = freq; + + dev_dbg(jdev->dev, "get pll result: freq %llu kHz\n", freq); + + return 0; +} + diff --git a/drivers/gpu/drm/mwv207/selftest/selftest.c b/drivers/gpu/drm/mwv207/selftest/selftest.c new file mode 100644 index 0000000000000000000000000000000000000000..03cc940dd46e2a49cd27605e37bb5966fd4fb68f --- /dev/null +++ b/drivers/gpu/drm/mwv207/selftest/selftest.c @@ -0,0 +1,107 @@ +/* +* SPDX-License-Identifier: GPL +* +* Copyright (c) 2020 ChangSha JingJiaMicro Electronics Co., Ltd. +* All rights reserved. +* +* Author: +* shanjinkui +* +* The software and information contained herein is proprietary and +* confidential to JingJiaMicro Electronics. This software can only be +* used by JingJiaMicro Electronics Corporation. Any use, reproduction, +* or disclosure without the written permission of JingJiaMicro +* Electronics Corporation is strictly prohibited. +*/ +#include "mwv207.h" +#include "mwv207_bo.h" +#include "mwv207_dma.h" + + +static int mwv207_test_create_and_map_bo(struct mwv207_device *jdev, u64 size, + u32 domain, u32 flags, struct mwv207_bo **pjbo, void **plogical) +{ + struct mwv207_bo *jbo; + void *logical; + int ret; + + ret = mwv207_bo_create(jdev, size, 0, ttm_bo_type_kernel, + domain, flags, &jbo); + if (ret) + return ret; + ret = mwv207_bo_reserve(jbo, true); + if (ret) + goto free_bo; + ret = mwv207_bo_kmap_reserved(jbo, &logical); + if (ret) + goto unreserve_bo; + + *pjbo = jbo; + *plogical = logical; + return 0; + +unreserve_bo: + mwv207_bo_unreserve(jbo); +free_bo: + mwv207_bo_unref(jbo); + return ret; +} + +static void mwv207_test_destroy_and_unmap_bo(struct mwv207_bo *jbo) +{ + if (!jbo) + return; + + mwv207_bo_kunmap_reserved(jbo); + mwv207_bo_unreserve(jbo); + mwv207_bo_unref(jbo); +} + + +static int mwv207_test_bo_acc(struct mwv207_device *jdev) +{ + struct mwv207_bo *jbo; + int i, ret; + void *logical; + + ret = mwv207_test_create_and_map_bo(jdev, 0x1000, + 0x2, (1<<0), &jbo, &logical); + if (ret) + return ret; + + memset(logical, 0x5a, 0x1000); + for (i = 0; i < 0x1000; i++) { + if (*((char *)logical + i) != 0x5a) { + ret = -EIO; + break; + } + } + + mwv207_test_destroy_and_unmap_bo(jbo); + + return ret; +} + +static int mwv207_test_bo(struct mwv207_device *jdev) +{ + int ret; + + ret = mwv207_test_bo_acc(jdev); + if (ret) { + pr_err("mwv207: bo access test failed with %d", ret); + return ret; + } + + return ret; +} + +int mwv207_test(struct mwv207_device *jdev) +{ + int ret; + + ret = mwv207_test_bo(jdev); + if (ret == 0) + pr_info("mwv207 selftest passed"); + + return ret; +}