diff options
Diffstat (limited to 'drivers/gpu/drm/hisilicon/hibmc')
-rw-r--r-- | drivers/gpu/drm/hisilicon/hibmc/Kconfig | 14 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/hibmc/Makefile | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_de.c | 538 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 378 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 64 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c | 99 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_regs.h | 204 | ||||
-rw-r--r-- | drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c | 127 |
8 files changed, 1428 insertions, 0 deletions
diff --git a/drivers/gpu/drm/hisilicon/hibmc/Kconfig b/drivers/gpu/drm/hisilicon/hibmc/Kconfig new file mode 100644 index 0000000000..126504318a --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_HISI_HIBMC + tristate "DRM Support for Hisilicon Hibmc" + depends on DRM && PCI && (ARM64 || COMPILE_TEST) + depends on MMU + select DRM_KMS_HELPER + select DRM_VRAM_HELPER + select DRM_TTM + select DRM_TTM_HELPER + select I2C + select I2C_ALGOBIT + help + Choose this option if you have a Hisilicon Hibmc soc chipset. + If M is selected the module will be called hibmc-drm. diff --git a/drivers/gpu/drm/hisilicon/hibmc/Makefile b/drivers/gpu/drm/hisilicon/hibmc/Makefile new file mode 100644 index 0000000000..d25c75e60d --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o + +obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc-drm.o diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_de.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_de.c new file mode 100644 index 0000000000..89bed78f14 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_de.c @@ -0,0 +1,538 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Hisilicon Hibmc SoC drm driver + * + * Based on the bochs drm driver. + * + * Copyright (c) 2016 Huawei Limited. + * + * Author: + * Rongrong Zou <zourongrong@huawei.com> + * Rongrong Zou <zourongrong@gmail.com> + * Jianhua Li <lijianhua@huawei.com> + */ + +#include <linux/delay.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_vram_helper.h> +#include <drm/drm_vblank.h> + +#include "hibmc_drm_drv.h" +#include "hibmc_drm_regs.h" + +struct hibmc_display_panel_pll { + u64 M; + u64 N; + u64 OD; + u64 POD; +}; + +struct hibmc_dislay_pll_config { + u64 hdisplay; + u64 vdisplay; + u32 pll1_config_value; + u32 pll2_config_value; +}; + +static const struct hibmc_dislay_pll_config hibmc_pll_table[] = { + {640, 480, CRT_PLL1_HS_25MHZ, CRT_PLL2_HS_25MHZ}, + {800, 600, CRT_PLL1_HS_40MHZ, CRT_PLL2_HS_40MHZ}, + {1024, 768, CRT_PLL1_HS_65MHZ, CRT_PLL2_HS_65MHZ}, + {1152, 864, CRT_PLL1_HS_80MHZ_1152, CRT_PLL2_HS_80MHZ}, + {1280, 768, CRT_PLL1_HS_80MHZ, CRT_PLL2_HS_80MHZ}, + {1280, 720, CRT_PLL1_HS_74MHZ, CRT_PLL2_HS_74MHZ}, + {1280, 960, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ}, + {1280, 1024, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ}, + {1440, 900, CRT_PLL1_HS_106MHZ, CRT_PLL2_HS_106MHZ}, + {1600, 900, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ}, + {1600, 1200, CRT_PLL1_HS_162MHZ, CRT_PLL2_HS_162MHZ}, + {1920, 1080, CRT_PLL1_HS_148MHZ, CRT_PLL2_HS_148MHZ}, + {1920, 1200, CRT_PLL1_HS_193MHZ, CRT_PLL2_HS_193MHZ}, +}; + +static int hibmc_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_framebuffer *fb = new_plane_state->fb; + struct drm_crtc *crtc = new_plane_state->crtc; + struct drm_crtc_state *crtc_state; + u32 src_w = new_plane_state->src_w >> 16; + u32 src_h = new_plane_state->src_h >> 16; + + if (!crtc || !fb) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (src_w != new_plane_state->crtc_w || src_h != new_plane_state->crtc_h) { + drm_dbg_atomic(plane->dev, "scale not support\n"); + return -EINVAL; + } + + if (new_plane_state->crtc_x < 0 || new_plane_state->crtc_y < 0) { + drm_dbg_atomic(plane->dev, "crtc_x/y of drm_plane state is invalid\n"); + return -EINVAL; + } + + if (!crtc_state->enable) + return 0; + + if (new_plane_state->crtc_x + new_plane_state->crtc_w > + crtc_state->adjusted_mode.hdisplay || + new_plane_state->crtc_y + new_plane_state->crtc_h > + crtc_state->adjusted_mode.vdisplay) { + drm_dbg_atomic(plane->dev, "visible portion of plane is invalid\n"); + return -EINVAL; + } + + if (new_plane_state->fb->pitches[0] % 128 != 0) { + drm_dbg_atomic(plane->dev, "wrong stride with 128-byte aligned\n"); + return -EINVAL; + } + return 0; +} + +static void hibmc_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + u32 reg; + s64 gpu_addr = 0; + u32 line_l; + struct hibmc_drm_private *priv = to_hibmc_drm_private(plane->dev); + struct drm_gem_vram_object *gbo; + + if (!new_state->fb) + return; + + gbo = drm_gem_vram_of_gem(new_state->fb->obj[0]); + + gpu_addr = drm_gem_vram_offset(gbo); + if (WARN_ON_ONCE(gpu_addr < 0)) + return; /* Bug: we didn't pin the BO to VRAM in prepare_fb. */ + + writel(gpu_addr, priv->mmio + HIBMC_CRT_FB_ADDRESS); + + reg = new_state->fb->width * (new_state->fb->format->cpp[0]); + + line_l = new_state->fb->pitches[0]; + writel(HIBMC_FIELD(HIBMC_CRT_FB_WIDTH_WIDTH, reg) | + HIBMC_FIELD(HIBMC_CRT_FB_WIDTH_OFFS, line_l), + priv->mmio + HIBMC_CRT_FB_WIDTH); + + /* SET PIXEL FORMAT */ + reg = readl(priv->mmio + HIBMC_CRT_DISP_CTL); + reg &= ~HIBMC_CRT_DISP_CTL_FORMAT_MASK; + reg |= HIBMC_FIELD(HIBMC_CRT_DISP_CTL_FORMAT, + new_state->fb->format->cpp[0] * 8 / 16); + writel(reg, priv->mmio + HIBMC_CRT_DISP_CTL); +} + +static const u32 channel_formats1[] = { + DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888, DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888 +}; + +static const struct drm_plane_funcs hibmc_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static const struct drm_plane_helper_funcs hibmc_plane_helper_funcs = { + DRM_GEM_VRAM_PLANE_HELPER_FUNCS, + .atomic_check = hibmc_plane_atomic_check, + .atomic_update = hibmc_plane_atomic_update, +}; + +static void hibmc_crtc_dpms(struct drm_crtc *crtc, u32 dpms) +{ + struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev); + u32 reg; + + reg = readl(priv->mmio + HIBMC_CRT_DISP_CTL); + reg &= ~HIBMC_CRT_DISP_CTL_DPMS_MASK; + reg |= HIBMC_FIELD(HIBMC_CRT_DISP_CTL_DPMS, dpms); + reg &= ~HIBMC_CRT_DISP_CTL_TIMING_MASK; + if (dpms == HIBMC_CRT_DPMS_ON) + reg |= HIBMC_CRT_DISP_CTL_TIMING(1); + writel(reg, priv->mmio + HIBMC_CRT_DISP_CTL); +} + +static void hibmc_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + u32 reg; + struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev); + + hibmc_set_power_mode(priv, HIBMC_PW_MODE_CTL_MODE_MODE0); + + /* Enable display power gate & LOCALMEM power gate*/ + reg = readl(priv->mmio + HIBMC_CURRENT_GATE); + reg &= ~HIBMC_CURR_GATE_LOCALMEM_MASK; + reg &= ~HIBMC_CURR_GATE_DISPLAY_MASK; + reg |= HIBMC_CURR_GATE_LOCALMEM(1); + reg |= HIBMC_CURR_GATE_DISPLAY(1); + hibmc_set_current_gate(priv, reg); + drm_crtc_vblank_on(crtc); + hibmc_crtc_dpms(crtc, HIBMC_CRT_DPMS_ON); +} + +static void hibmc_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + u32 reg; + struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev); + + hibmc_crtc_dpms(crtc, HIBMC_CRT_DPMS_OFF); + drm_crtc_vblank_off(crtc); + + hibmc_set_power_mode(priv, HIBMC_PW_MODE_CTL_MODE_SLEEP); + + /* Enable display power gate & LOCALMEM power gate*/ + reg = readl(priv->mmio + HIBMC_CURRENT_GATE); + reg &= ~HIBMC_CURR_GATE_LOCALMEM_MASK; + reg &= ~HIBMC_CURR_GATE_DISPLAY_MASK; + reg |= HIBMC_CURR_GATE_LOCALMEM(0); + reg |= HIBMC_CURR_GATE_DISPLAY(0); + hibmc_set_current_gate(priv, reg); +} + +static enum drm_mode_status +hibmc_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + size_t i = 0; + int vrefresh = drm_mode_vrefresh(mode); + + if (vrefresh < 59 || vrefresh > 61) + return MODE_NOCLOCK; + + for (i = 0; i < ARRAY_SIZE(hibmc_pll_table); i++) { + if (hibmc_pll_table[i].hdisplay == mode->hdisplay && + hibmc_pll_table[i].vdisplay == mode->vdisplay) + return MODE_OK; + } + + return MODE_BAD; +} + +static u32 format_pll_reg(void) +{ + u32 pllreg = 0; + struct hibmc_display_panel_pll pll = {0}; + + /* + * Note that all PLL's have the same format. Here, + * we just use Panel PLL parameter to work out the bit + * fields in the register.On returning a 32 bit number, the value can + * be applied to any PLL in the calling function. + */ + pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_BYPASS, 0); + pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_POWER, 1); + pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_INPUT, 0); + pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_POD, pll.POD); + pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_OD, pll.OD); + pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_N, pll.N); + pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_M, pll.M); + + return pllreg; +} + +static void set_vclock_hisilicon(struct drm_device *dev, u64 pll) +{ + u32 val; + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + + val = readl(priv->mmio + CRT_PLL1_HS); + val &= ~(CRT_PLL1_HS_OUTER_BYPASS(1)); + writel(val, priv->mmio + CRT_PLL1_HS); + + val = CRT_PLL1_HS_INTER_BYPASS(1) | CRT_PLL1_HS_POWERON(1); + writel(val, priv->mmio + CRT_PLL1_HS); + + writel(pll, priv->mmio + CRT_PLL1_HS); + + usleep_range(1000, 2000); + + val = pll & ~(CRT_PLL1_HS_POWERON(1)); + writel(val, priv->mmio + CRT_PLL1_HS); + + usleep_range(1000, 2000); + + val &= ~(CRT_PLL1_HS_INTER_BYPASS(1)); + writel(val, priv->mmio + CRT_PLL1_HS); + + usleep_range(1000, 2000); + + val |= CRT_PLL1_HS_OUTER_BYPASS(1); + writel(val, priv->mmio + CRT_PLL1_HS); +} + +static void get_pll_config(u64 x, u64 y, u32 *pll1, u32 *pll2) +{ + size_t i; + size_t count = ARRAY_SIZE(hibmc_pll_table); + + for (i = 0; i < count; i++) { + if (hibmc_pll_table[i].hdisplay == x && + hibmc_pll_table[i].vdisplay == y) { + *pll1 = hibmc_pll_table[i].pll1_config_value; + *pll2 = hibmc_pll_table[i].pll2_config_value; + return; + } + } + + /* if found none, we use default value */ + *pll1 = CRT_PLL1_HS_25MHZ; + *pll2 = CRT_PLL2_HS_25MHZ; +} + +/* + * This function takes care the extra registers and bit fields required to + * setup a mode in board. + * Explanation about Display Control register: + * FPGA only supports 7 predefined pixel clocks, and clock select is + * in bit 4:0 of new register 0x802a8. + */ +static u32 display_ctrl_adjust(struct drm_device *dev, + struct drm_display_mode *mode, + u32 ctrl) +{ + u64 x, y; + u32 pll1; /* bit[31:0] of PLL */ + u32 pll2; /* bit[63:32] of PLL */ + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + + x = mode->hdisplay; + y = mode->vdisplay; + + get_pll_config(x, y, &pll1, &pll2); + writel(pll2, priv->mmio + CRT_PLL2_HS); + set_vclock_hisilicon(dev, pll1); + + /* + * Hisilicon has to set up the top-left and bottom-right + * registers as well. + * Note that normal chip only use those two register for + * auto-centering mode. + */ + writel(HIBMC_FIELD(HIBMC_CRT_AUTO_CENTERING_TL_TOP, 0) | + HIBMC_FIELD(HIBMC_CRT_AUTO_CENTERING_TL_LEFT, 0), + priv->mmio + HIBMC_CRT_AUTO_CENTERING_TL); + + writel(HIBMC_FIELD(HIBMC_CRT_AUTO_CENTERING_BR_BOTTOM, y - 1) | + HIBMC_FIELD(HIBMC_CRT_AUTO_CENTERING_BR_RIGHT, x - 1), + priv->mmio + HIBMC_CRT_AUTO_CENTERING_BR); + + /* + * Assume common fields in ctrl have been properly set before + * calling this function. + * This function only sets the extra fields in ctrl. + */ + + /* Set bit 25 of display controller: Select CRT or VGA clock */ + ctrl &= ~HIBMC_CRT_DISP_CTL_CRTSELECT_MASK; + ctrl &= ~HIBMC_CRT_DISP_CTL_CLOCK_PHASE_MASK; + + ctrl |= HIBMC_CRT_DISP_CTL_CRTSELECT(HIBMC_CRTSELECT_CRT); + + /* clock_phase_polarity is 0 */ + ctrl |= HIBMC_CRT_DISP_CTL_CLOCK_PHASE(0); + + writel(ctrl, priv->mmio + HIBMC_CRT_DISP_CTL); + + return ctrl; +} + +static void hibmc_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + u32 val; + struct drm_display_mode *mode = &crtc->state->mode; + struct drm_device *dev = crtc->dev; + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + u32 width = mode->hsync_end - mode->hsync_start; + u32 height = mode->vsync_end - mode->vsync_start; + + writel(format_pll_reg(), priv->mmio + HIBMC_CRT_PLL_CTRL); + writel(HIBMC_FIELD(HIBMC_CRT_HORZ_TOTAL_TOTAL, mode->htotal - 1) | + HIBMC_FIELD(HIBMC_CRT_HORZ_TOTAL_DISP_END, mode->hdisplay - 1), + priv->mmio + HIBMC_CRT_HORZ_TOTAL); + + writel(HIBMC_FIELD(HIBMC_CRT_HORZ_SYNC_WIDTH, width) | + HIBMC_FIELD(HIBMC_CRT_HORZ_SYNC_START, mode->hsync_start - 1), + priv->mmio + HIBMC_CRT_HORZ_SYNC); + + writel(HIBMC_FIELD(HIBMC_CRT_VERT_TOTAL_TOTAL, mode->vtotal - 1) | + HIBMC_FIELD(HIBMC_CRT_VERT_TOTAL_DISP_END, mode->vdisplay - 1), + priv->mmio + HIBMC_CRT_VERT_TOTAL); + + writel(HIBMC_FIELD(HIBMC_CRT_VERT_SYNC_HEIGHT, height) | + HIBMC_FIELD(HIBMC_CRT_VERT_SYNC_START, mode->vsync_start - 1), + priv->mmio + HIBMC_CRT_VERT_SYNC); + + val = HIBMC_FIELD(HIBMC_CRT_DISP_CTL_VSYNC_PHASE, 0); + val |= HIBMC_FIELD(HIBMC_CRT_DISP_CTL_HSYNC_PHASE, 0); + val |= HIBMC_CRT_DISP_CTL_TIMING(1); + val |= HIBMC_CRT_DISP_CTL_PLANE(1); + + display_ctrl_adjust(dev, mode, val); +} + +static void hibmc_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + u32 reg; + struct drm_device *dev = crtc->dev; + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + + hibmc_set_power_mode(priv, HIBMC_PW_MODE_CTL_MODE_MODE0); + + /* Enable display power gate & LOCALMEM power gate*/ + reg = readl(priv->mmio + HIBMC_CURRENT_GATE); + reg &= ~HIBMC_CURR_GATE_DISPLAY_MASK; + reg &= ~HIBMC_CURR_GATE_LOCALMEM_MASK; + reg |= HIBMC_CURR_GATE_DISPLAY(1); + reg |= HIBMC_CURR_GATE_LOCALMEM(1); + hibmc_set_current_gate(priv, reg); + + /* We can add more initialization as needed. */ +} + +static void hibmc_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) + +{ + unsigned long flags; + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + if (crtc->state->event) + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); +} + +static int hibmc_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev); + + writel(HIBMC_RAW_INTERRUPT_EN_VBLANK(1), + priv->mmio + HIBMC_RAW_INTERRUPT_EN); + + return 0; +} + +static void hibmc_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev); + + writel(HIBMC_RAW_INTERRUPT_EN_VBLANK(0), + priv->mmio + HIBMC_RAW_INTERRUPT_EN); +} + +static void hibmc_crtc_load_lut(struct drm_crtc *crtc) +{ + struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev); + void __iomem *mmio = priv->mmio; + u16 *r, *g, *b; + u32 reg; + u32 i; + + r = crtc->gamma_store; + g = r + crtc->gamma_size; + b = g + crtc->gamma_size; + + for (i = 0; i < crtc->gamma_size; i++) { + u32 offset = i << 2; + u8 red = *r++ >> 8; + u8 green = *g++ >> 8; + u8 blue = *b++ >> 8; + u32 rgb = (red << 16) | (green << 8) | blue; + + writel(rgb, mmio + HIBMC_CRT_PALETTE + offset); + } + + reg = readl(priv->mmio + HIBMC_CRT_DISP_CTL); + reg |= HIBMC_FIELD(HIBMC_CTL_DISP_CTL_GAMMA, 1); + writel(reg, priv->mmio + HIBMC_CRT_DISP_CTL); +} + +static int hibmc_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green, + u16 *blue, uint32_t size, + struct drm_modeset_acquire_ctx *ctx) +{ + hibmc_crtc_load_lut(crtc); + + return 0; +} + +static const struct drm_crtc_funcs hibmc_crtc_funcs = { + .page_flip = drm_atomic_helper_page_flip, + .set_config = drm_atomic_helper_set_config, + .destroy = drm_crtc_cleanup, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .enable_vblank = hibmc_crtc_enable_vblank, + .disable_vblank = hibmc_crtc_disable_vblank, + .gamma_set = hibmc_crtc_gamma_set, +}; + +static const struct drm_crtc_helper_funcs hibmc_crtc_helper_funcs = { + .mode_set_nofb = hibmc_crtc_mode_set_nofb, + .atomic_begin = hibmc_crtc_atomic_begin, + .atomic_flush = hibmc_crtc_atomic_flush, + .atomic_enable = hibmc_crtc_atomic_enable, + .atomic_disable = hibmc_crtc_atomic_disable, + .mode_valid = hibmc_crtc_mode_valid, +}; + +int hibmc_de_init(struct hibmc_drm_private *priv) +{ + struct drm_device *dev = &priv->dev; + struct drm_crtc *crtc = &priv->crtc; + struct drm_plane *plane = &priv->primary_plane; + int ret; + + ret = drm_universal_plane_init(dev, plane, 1, &hibmc_plane_funcs, + channel_formats1, + ARRAY_SIZE(channel_formats1), + NULL, + DRM_PLANE_TYPE_PRIMARY, + NULL); + + if (ret) { + drm_err(dev, "failed to init plane: %d\n", ret); + return ret; + } + + drm_plane_helper_add(plane, &hibmc_plane_helper_funcs); + + ret = drm_crtc_init_with_planes(dev, crtc, plane, + NULL, &hibmc_crtc_funcs, NULL); + if (ret) { + drm_err(dev, "failed to init crtc: %d\n", ret); + return ret; + } + + ret = drm_mode_crtc_set_gamma_size(crtc, 256); + if (ret) { + drm_err(dev, "failed to set gamma size: %d\n", ret); + return ret; + } + drm_crtc_helper_add(crtc, &hibmc_crtc_helper_funcs); + + return 0; +} diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c new file mode 100644 index 0000000000..8a98fa276e --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Hisilicon Hibmc SoC drm driver + * + * Based on the bochs drm driver. + * + * Copyright (c) 2016 Huawei Limited. + * + * Author: + * Rongrong Zou <zourongrong@huawei.com> + * Rongrong Zou <zourongrong@gmail.com> + * Jianhua Li <lijianhua@huawei.com> + */ + +#include <linux/module.h> +#include <linux/pci.h> + +#include <drm/drm_aperture.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_generic.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_vram_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_module.h> +#include <drm/drm_vblank.h> + +#include "hibmc_drm_drv.h" +#include "hibmc_drm_regs.h" + +DEFINE_DRM_GEM_FOPS(hibmc_fops); + +static irqreturn_t hibmc_interrupt(int irq, void *arg) +{ + struct drm_device *dev = (struct drm_device *)arg; + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + u32 status; + + status = readl(priv->mmio + HIBMC_RAW_INTERRUPT); + + if (status & HIBMC_RAW_INTERRUPT_VBLANK(1)) { + writel(HIBMC_RAW_INTERRUPT_VBLANK(1), + priv->mmio + HIBMC_RAW_INTERRUPT); + drm_handle_vblank(dev, 0); + } + + return IRQ_HANDLED; +} + +static int hibmc_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + return drm_gem_vram_fill_create_dumb(file, dev, 0, 128, args); +} + +static const struct drm_driver hibmc_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .fops = &hibmc_fops, + .name = "hibmc", + .date = "20160828", + .desc = "hibmc drm driver", + .major = 1, + .minor = 0, + .debugfs_init = drm_vram_mm_debugfs_init, + .dumb_create = hibmc_dumb_create, + .dumb_map_offset = drm_gem_ttm_dumb_map_offset, +}; + +static int __maybe_unused hibmc_pm_suspend(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(drm_dev); +} + +static int __maybe_unused hibmc_pm_resume(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + + return drm_mode_config_helper_resume(drm_dev); +} + +static const struct dev_pm_ops hibmc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(hibmc_pm_suspend, + hibmc_pm_resume) +}; + +static const struct drm_mode_config_funcs hibmc_mode_funcs = { + .mode_valid = drm_vram_helper_mode_valid, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .fb_create = drm_gem_fb_create, +}; + +static int hibmc_kms_init(struct hibmc_drm_private *priv) +{ + struct drm_device *dev = &priv->dev; + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = 1920; + dev->mode_config.max_height = 1200; + + dev->mode_config.preferred_depth = 24; + dev->mode_config.prefer_shadow = 1; + + dev->mode_config.funcs = (void *)&hibmc_mode_funcs; + + ret = hibmc_de_init(priv); + if (ret) { + drm_err(dev, "failed to init de: %d\n", ret); + return ret; + } + + ret = hibmc_vdac_init(priv); + if (ret) { + drm_err(dev, "failed to init vdac: %d\n", ret); + return ret; + } + + return 0; +} + +/* + * It can operate in one of three modes: 0, 1 or Sleep. + */ +void hibmc_set_power_mode(struct hibmc_drm_private *priv, u32 power_mode) +{ + u32 control_value = 0; + void __iomem *mmio = priv->mmio; + u32 input = 1; + + if (power_mode > HIBMC_PW_MODE_CTL_MODE_SLEEP) + return; + + if (power_mode == HIBMC_PW_MODE_CTL_MODE_SLEEP) + input = 0; + + control_value = readl(mmio + HIBMC_POWER_MODE_CTRL); + control_value &= ~(HIBMC_PW_MODE_CTL_MODE_MASK | + HIBMC_PW_MODE_CTL_OSC_INPUT_MASK); + control_value |= HIBMC_FIELD(HIBMC_PW_MODE_CTL_MODE, power_mode); + control_value |= HIBMC_FIELD(HIBMC_PW_MODE_CTL_OSC_INPUT, input); + writel(control_value, mmio + HIBMC_POWER_MODE_CTRL); +} + +void hibmc_set_current_gate(struct hibmc_drm_private *priv, unsigned int gate) +{ + u32 gate_reg; + u32 mode; + void __iomem *mmio = priv->mmio; + + /* Get current power mode. */ + mode = (readl(mmio + HIBMC_POWER_MODE_CTRL) & + HIBMC_PW_MODE_CTL_MODE_MASK) >> HIBMC_PW_MODE_CTL_MODE_SHIFT; + + switch (mode) { + case HIBMC_PW_MODE_CTL_MODE_MODE0: + gate_reg = HIBMC_MODE0_GATE; + break; + + case HIBMC_PW_MODE_CTL_MODE_MODE1: + gate_reg = HIBMC_MODE1_GATE; + break; + + default: + gate_reg = HIBMC_MODE0_GATE; + break; + } + writel(gate, mmio + gate_reg); +} + +static void hibmc_hw_config(struct hibmc_drm_private *priv) +{ + u32 reg; + + /* On hardware reset, power mode 0 is default. */ + hibmc_set_power_mode(priv, HIBMC_PW_MODE_CTL_MODE_MODE0); + + /* Enable display power gate & LOCALMEM power gate*/ + reg = readl(priv->mmio + HIBMC_CURRENT_GATE); + reg &= ~HIBMC_CURR_GATE_DISPLAY_MASK; + reg &= ~HIBMC_CURR_GATE_LOCALMEM_MASK; + reg |= HIBMC_CURR_GATE_DISPLAY(1); + reg |= HIBMC_CURR_GATE_LOCALMEM(1); + + hibmc_set_current_gate(priv, reg); + + /* + * Reset the memory controller. If the memory controller + * is not reset in chip,the system might hang when sw accesses + * the memory.The memory should be resetted after + * changing the MXCLK. + */ + reg = readl(priv->mmio + HIBMC_MISC_CTRL); + reg &= ~HIBMC_MSCCTL_LOCALMEM_RESET_MASK; + reg |= HIBMC_MSCCTL_LOCALMEM_RESET(0); + writel(reg, priv->mmio + HIBMC_MISC_CTRL); + + reg &= ~HIBMC_MSCCTL_LOCALMEM_RESET_MASK; + reg |= HIBMC_MSCCTL_LOCALMEM_RESET(1); + + writel(reg, priv->mmio + HIBMC_MISC_CTRL); +} + +static int hibmc_hw_map(struct hibmc_drm_private *priv) +{ + struct drm_device *dev = &priv->dev; + struct pci_dev *pdev = to_pci_dev(dev->dev); + resource_size_t ioaddr, iosize; + + ioaddr = pci_resource_start(pdev, 1); + iosize = pci_resource_len(pdev, 1); + priv->mmio = devm_ioremap(dev->dev, ioaddr, iosize); + if (!priv->mmio) { + drm_err(dev, "Cannot map mmio region\n"); + return -ENOMEM; + } + + return 0; +} + +static int hibmc_hw_init(struct hibmc_drm_private *priv) +{ + int ret; + + ret = hibmc_hw_map(priv); + if (ret) + return ret; + + hibmc_hw_config(priv); + + return 0; +} + +static int hibmc_unload(struct drm_device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev->dev); + + drm_atomic_helper_shutdown(dev); + + free_irq(pdev->irq, dev); + + pci_disable_msi(to_pci_dev(dev->dev)); + + return 0; +} + +static int hibmc_load(struct drm_device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev->dev); + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + int ret; + + ret = hibmc_hw_init(priv); + if (ret) + goto err; + + ret = drmm_vram_helper_init(dev, pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + if (ret) { + drm_err(dev, "Error initializing VRAM MM; %d\n", ret); + goto err; + } + + ret = hibmc_kms_init(priv); + if (ret) + goto err; + + ret = drm_vblank_init(dev, dev->mode_config.num_crtc); + if (ret) { + drm_err(dev, "failed to initialize vblank: %d\n", ret); + goto err; + } + + ret = pci_enable_msi(pdev); + if (ret) { + drm_warn(dev, "enabling MSI failed: %d\n", ret); + } else { + /* PCI devices require shared interrupts. */ + ret = request_irq(pdev->irq, hibmc_interrupt, IRQF_SHARED, + dev->driver->name, dev); + if (ret) + drm_warn(dev, "install irq failed: %d\n", ret); + } + + /* reset all the states of crtc/plane/encoder/connector */ + drm_mode_config_reset(dev); + + return 0; + +err: + hibmc_unload(dev); + drm_err(dev, "failed to initialize drm driver: %d\n", ret); + return ret; +} + +static int hibmc_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct hibmc_drm_private *priv; + struct drm_device *dev; + int ret; + + ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &hibmc_driver); + if (ret) + return ret; + + priv = devm_drm_dev_alloc(&pdev->dev, &hibmc_driver, + struct hibmc_drm_private, dev); + if (IS_ERR(priv)) { + DRM_ERROR("failed to allocate drm_device\n"); + return PTR_ERR(priv); + } + + dev = &priv->dev; + pci_set_drvdata(pdev, dev); + + ret = pcim_enable_device(pdev); + if (ret) { + drm_err(dev, "failed to enable pci device: %d\n", ret); + goto err_return; + } + + ret = hibmc_load(dev); + if (ret) { + drm_err(dev, "failed to load hibmc: %d\n", ret); + goto err_return; + } + + ret = drm_dev_register(dev, 0); + if (ret) { + drm_err(dev, "failed to register drv for userspace access: %d\n", + ret); + goto err_unload; + } + + drm_fbdev_generic_setup(dev, 32); + + return 0; + +err_unload: + hibmc_unload(dev); +err_return: + return ret; +} + +static void hibmc_pci_remove(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + + drm_dev_unregister(dev); + hibmc_unload(dev); +} + +static const struct pci_device_id hibmc_pci_table[] = { + { PCI_VDEVICE(HUAWEI, 0x1711) }, + {0,} +}; + +static struct pci_driver hibmc_pci_driver = { + .name = "hibmc-drm", + .id_table = hibmc_pci_table, + .probe = hibmc_pci_probe, + .remove = hibmc_pci_remove, + .driver.pm = &hibmc_pm_ops, +}; + +drm_module_pci_driver(hibmc_pci_driver); + +MODULE_DEVICE_TABLE(pci, hibmc_pci_table); +MODULE_AUTHOR("RongrongZou <zourongrong@huawei.com>"); +MODULE_DESCRIPTION("DRM Driver for Hisilicon Hibmc"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h new file mode 100644 index 0000000000..f957552c6c --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Hisilicon Hibmc SoC drm driver + * + * Based on the bochs drm driver. + * + * Copyright (c) 2016 Huawei Limited. + * + * Author: + * Rongrong Zou <zourongrong@huawei.com> + * Rongrong Zou <zourongrong@gmail.com> + * Jianhua Li <lijianhua@huawei.com> + */ + +#ifndef HIBMC_DRM_DRV_H +#define HIBMC_DRM_DRV_H + +#include <linux/gpio/consumer.h> +#include <linux/i2c-algo-bit.h> +#include <linux/i2c.h> + +#include <drm/drm_edid.h> +#include <drm/drm_framebuffer.h> + +struct hibmc_connector { + struct drm_connector base; + + struct i2c_adapter adapter; + struct i2c_algo_bit_data bit_data; +}; + +struct hibmc_drm_private { + /* hw */ + void __iomem *mmio; + + /* drm */ + struct drm_device dev; + struct drm_plane primary_plane; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct hibmc_connector connector; +}; + +static inline struct hibmc_connector *to_hibmc_connector(struct drm_connector *connector) +{ + return container_of(connector, struct hibmc_connector, base); +} + +static inline struct hibmc_drm_private *to_hibmc_drm_private(struct drm_device *dev) +{ + return container_of(dev, struct hibmc_drm_private, dev); +} + +void hibmc_set_power_mode(struct hibmc_drm_private *priv, + u32 power_mode); +void hibmc_set_current_gate(struct hibmc_drm_private *priv, + u32 gate); + +int hibmc_de_init(struct hibmc_drm_private *priv); +int hibmc_vdac_init(struct hibmc_drm_private *priv); + +int hibmc_mm_init(struct hibmc_drm_private *hibmc); +int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_connector *connector); + +#endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c new file mode 100644 index 0000000000..410bd019bb --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Hisilicon Hibmc SoC drm driver + * + * Based on the bochs drm driver. + * + * Copyright (c) 2016 Huawei Limited. + * + * Author: + * Tian Tao <tiantao6@hisilicon.com> + */ + +#include <linux/delay.h> +#include <linux/pci.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_probe_helper.h> + +#include "hibmc_drm_drv.h" + +#define GPIO_DATA 0x0802A0 +#define GPIO_DATA_DIRECTION 0x0802A4 + +#define I2C_SCL_MASK BIT(0) +#define I2C_SDA_MASK BIT(1) + +static void hibmc_set_i2c_signal(void *data, u32 mask, int value) +{ + struct hibmc_connector *hibmc_connector = data; + struct hibmc_drm_private *priv = to_hibmc_drm_private(hibmc_connector->base.dev); + u32 tmp_dir = readl(priv->mmio + GPIO_DATA_DIRECTION); + + if (value) { + tmp_dir &= ~mask; + writel(tmp_dir, priv->mmio + GPIO_DATA_DIRECTION); + } else { + u32 tmp_data = readl(priv->mmio + GPIO_DATA); + + tmp_data &= ~mask; + writel(tmp_data, priv->mmio + GPIO_DATA); + + tmp_dir |= mask; + writel(tmp_dir, priv->mmio + GPIO_DATA_DIRECTION); + } +} + +static int hibmc_get_i2c_signal(void *data, u32 mask) +{ + struct hibmc_connector *hibmc_connector = data; + struct hibmc_drm_private *priv = to_hibmc_drm_private(hibmc_connector->base.dev); + u32 tmp_dir = readl(priv->mmio + GPIO_DATA_DIRECTION); + + if ((tmp_dir & mask) != mask) { + tmp_dir &= ~mask; + writel(tmp_dir, priv->mmio + GPIO_DATA_DIRECTION); + } + + return (readl(priv->mmio + GPIO_DATA) & mask) ? 1 : 0; +} + +static void hibmc_ddc_setsda(void *data, int state) +{ + hibmc_set_i2c_signal(data, I2C_SDA_MASK, state); +} + +static void hibmc_ddc_setscl(void *data, int state) +{ + hibmc_set_i2c_signal(data, I2C_SCL_MASK, state); +} + +static int hibmc_ddc_getsda(void *data) +{ + return hibmc_get_i2c_signal(data, I2C_SDA_MASK); +} + +static int hibmc_ddc_getscl(void *data) +{ + return hibmc_get_i2c_signal(data, I2C_SCL_MASK); +} + +int hibmc_ddc_create(struct drm_device *drm_dev, + struct hibmc_connector *connector) +{ + connector->adapter.owner = THIS_MODULE; + connector->adapter.class = I2C_CLASS_DDC; + snprintf(connector->adapter.name, I2C_NAME_SIZE, "HIS i2c bit bus"); + connector->adapter.dev.parent = drm_dev->dev; + i2c_set_adapdata(&connector->adapter, connector); + connector->adapter.algo_data = &connector->bit_data; + + connector->bit_data.udelay = 20; + connector->bit_data.timeout = usecs_to_jiffies(2000); + connector->bit_data.data = connector; + connector->bit_data.setsda = hibmc_ddc_setsda; + connector->bit_data.setscl = hibmc_ddc_setscl; + connector->bit_data.getsda = hibmc_ddc_getsda; + connector->bit_data.getscl = hibmc_ddc_getscl; + + return i2c_bit_add_bus(&connector->adapter); +} diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_regs.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_regs.h new file mode 100644 index 0000000000..17b30c393b --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_regs.h @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Hisilicon Hibmc SoC drm driver + * + * Based on the bochs drm driver. + * + * Copyright (c) 2016 Huawei Limited. + * + * Author: + * Rongrong Zou <zourongrong@huawei.com> + * Rongrong Zou <zourongrong@gmail.com> + * Jianhua Li <lijianhua@huawei.com> + */ + +#ifndef HIBMC_DRM_HW_H +#define HIBMC_DRM_HW_H + +/* register definition */ +#define HIBMC_MISC_CTRL 0x4 + +#define HIBMC_MSCCTL_LOCALMEM_RESET(x) ((x) << 6) +#define HIBMC_MSCCTL_LOCALMEM_RESET_MASK 0x40 + +#define HIBMC_CURRENT_GATE 0x000040 +#define HIBMC_CURR_GATE_DISPLAY(x) ((x) << 2) +#define HIBMC_CURR_GATE_DISPLAY_MASK 0x4 + +#define HIBMC_CURR_GATE_LOCALMEM(x) ((x) << 1) +#define HIBMC_CURR_GATE_LOCALMEM_MASK 0x2 + +#define HIBMC_MODE0_GATE 0x000044 +#define HIBMC_MODE1_GATE 0x000048 +#define HIBMC_POWER_MODE_CTRL 0x00004C + +#define HIBMC_PW_MODE_CTL_OSC_INPUT(x) ((x) << 3) +#define HIBMC_PW_MODE_CTL_OSC_INPUT_MASK 0x8 + +#define HIBMC_PW_MODE_CTL_MODE(x) ((x) << 0) +#define HIBMC_PW_MODE_CTL_MODE_MASK 0x03 +#define HIBMC_PW_MODE_CTL_MODE_SHIFT 0 + +#define HIBMC_PW_MODE_CTL_MODE_MODE0 0 +#define HIBMC_PW_MODE_CTL_MODE_MODE1 1 +#define HIBMC_PW_MODE_CTL_MODE_SLEEP 2 + +#define HIBMC_PANEL_PLL_CTRL 0x00005C +#define HIBMC_CRT_PLL_CTRL 0x000060 + +#define HIBMC_PLL_CTRL_BYPASS(x) ((x) << 18) +#define HIBMC_PLL_CTRL_BYPASS_MASK 0x40000 + +#define HIBMC_PLL_CTRL_POWER(x) ((x) << 17) +#define HIBMC_PLL_CTRL_POWER_MASK 0x20000 + +#define HIBMC_PLL_CTRL_INPUT(x) ((x) << 16) +#define HIBMC_PLL_CTRL_INPUT_MASK 0x10000 + +#define HIBMC_PLL_CTRL_POD(x) ((x) << 14) +#define HIBMC_PLL_CTRL_POD_MASK 0xC000 + +#define HIBMC_PLL_CTRL_OD(x) ((x) << 12) +#define HIBMC_PLL_CTRL_OD_MASK 0x3000 + +#define HIBMC_PLL_CTRL_N(x) ((x) << 8) +#define HIBMC_PLL_CTRL_N_MASK 0xF00 + +#define HIBMC_PLL_CTRL_M(x) ((x) << 0) +#define HIBMC_PLL_CTRL_M_MASK 0xFF + +#define HIBMC_CRT_DISP_CTL 0x80200 + +#define HIBMC_CRT_DISP_CTL_DPMS(x) ((x) << 30) +#define HIBMC_CRT_DISP_CTL_DPMS_MASK 0xc0000000 + +#define HIBMC_CRT_DPMS_ON 0 +#define HIBMC_CRT_DPMS_OFF 3 + +#define HIBMC_CRT_DISP_CTL_CRTSELECT(x) ((x) << 25) +#define HIBMC_CRT_DISP_CTL_CRTSELECT_MASK 0x2000000 + +#define HIBMC_CRTSELECT_CRT 1 + +#define HIBMC_CRT_DISP_CTL_CLOCK_PHASE(x) ((x) << 14) +#define HIBMC_CRT_DISP_CTL_CLOCK_PHASE_MASK 0x4000 + +#define HIBMC_CRT_DISP_CTL_VSYNC_PHASE(x) ((x) << 13) +#define HIBMC_CRT_DISP_CTL_VSYNC_PHASE_MASK 0x2000 + +#define HIBMC_CRT_DISP_CTL_HSYNC_PHASE(x) ((x) << 12) +#define HIBMC_CRT_DISP_CTL_HSYNC_PHASE_MASK 0x1000 + +#define HIBMC_CRT_DISP_CTL_TIMING(x) ((x) << 8) +#define HIBMC_CRT_DISP_CTL_TIMING_MASK 0x100 + +#define HIBMC_CTL_DISP_CTL_GAMMA(x) ((x) << 3) +#define HIBMC_CTL_DISP_CTL_GAMMA_MASK 0x08 + +#define HIBMC_CRT_DISP_CTL_PLANE(x) ((x) << 2) +#define HIBMC_CRT_DISP_CTL_PLANE_MASK 4 + +#define HIBMC_CRT_DISP_CTL_FORMAT(x) ((x) << 0) +#define HIBMC_CRT_DISP_CTL_FORMAT_MASK 0x03 + +#define HIBMC_CRT_FB_ADDRESS 0x080204 + +#define HIBMC_CRT_FB_WIDTH 0x080208 +#define HIBMC_CRT_FB_WIDTH_WIDTH(x) ((x) << 16) +#define HIBMC_CRT_FB_WIDTH_WIDTH_MASK 0x3FFF0000 +#define HIBMC_CRT_FB_WIDTH_OFFS(x) ((x) << 0) +#define HIBMC_CRT_FB_WIDTH_OFFS_MASK 0x3FFF + +#define HIBMC_CRT_HORZ_TOTAL 0x08020C +#define HIBMC_CRT_HORZ_TOTAL_TOTAL(x) ((x) << 16) +#define HIBMC_CRT_HORZ_TOTAL_TOTAL_MASK 0xFFF0000 + +#define HIBMC_CRT_HORZ_TOTAL_DISP_END(x) ((x) << 0) +#define HIBMC_CRT_HORZ_TOTAL_DISP_END_MASK 0xFFF + +#define HIBMC_CRT_HORZ_SYNC 0x080210 +#define HIBMC_CRT_HORZ_SYNC_WIDTH(x) ((x) << 16) +#define HIBMC_CRT_HORZ_SYNC_WIDTH_MASK 0xFF0000 + +#define HIBMC_CRT_HORZ_SYNC_START(x) ((x) << 0) +#define HIBMC_CRT_HORZ_SYNC_START_MASK 0xFFF + +#define HIBMC_CRT_VERT_TOTAL 0x080214 +#define HIBMC_CRT_VERT_TOTAL_TOTAL(x) ((x) << 16) +#define HIBMC_CRT_VERT_TOTAL_TOTAL_MASK 0x7FFF0000 + +#define HIBMC_CRT_VERT_TOTAL_DISP_END(x) ((x) << 0) +#define HIBMC_CRT_VERT_TOTAL_DISP_END_MASK 0x7FF + +#define HIBMC_CRT_VERT_SYNC 0x080218 +#define HIBMC_CRT_VERT_SYNC_HEIGHT(x) ((x) << 16) +#define HIBMC_CRT_VERT_SYNC_HEIGHT_MASK 0x3F0000 + +#define HIBMC_CRT_VERT_SYNC_START(x) ((x) << 0) +#define HIBMC_CRT_VERT_SYNC_START_MASK 0x7FF + +/* Auto Centering */ +#define HIBMC_CRT_AUTO_CENTERING_TL 0x080280 +#define HIBMC_CRT_AUTO_CENTERING_TL_TOP(x) ((x) << 16) +#define HIBMC_CRT_AUTO_CENTERING_TL_TOP_MASK 0x7FF0000 + +#define HIBMC_CRT_AUTO_CENTERING_TL_LEFT(x) ((x) << 0) +#define HIBMC_CRT_AUTO_CENTERING_TL_LEFT_MASK 0x7FF + +#define HIBMC_CRT_AUTO_CENTERING_BR 0x080284 +#define HIBMC_CRT_AUTO_CENTERING_BR_BOTTOM(x) ((x) << 16) +#define HIBMC_CRT_AUTO_CENTERING_BR_BOTTOM_MASK 0x7FF0000 + +#define HIBMC_CRT_AUTO_CENTERING_BR_RIGHT(x) ((x) << 0) +#define HIBMC_CRT_AUTO_CENTERING_BR_RIGHT_MASK 0x7FF + +/* register to control panel output */ +#define HIBMC_DISPLAY_CONTROL_HISILE 0x80288 +#define HIBMC_DISPLAY_CONTROL_FPVDDEN(x) ((x) << 0) +#define HIBMC_DISPLAY_CONTROL_PANELDATE(x) ((x) << 1) +#define HIBMC_DISPLAY_CONTROL_FPEN(x) ((x) << 2) +#define HIBMC_DISPLAY_CONTROL_VBIASEN(x) ((x) << 3) + +#define HIBMC_RAW_INTERRUPT 0x80290 +#define HIBMC_RAW_INTERRUPT_VBLANK(x) ((x) << 2) +#define HIBMC_RAW_INTERRUPT_VBLANK_MASK 0x4 + +#define HIBMC_RAW_INTERRUPT_EN 0x80298 +#define HIBMC_RAW_INTERRUPT_EN_VBLANK(x) ((x) << 2) +#define HIBMC_RAW_INTERRUPT_EN_VBLANK_MASK 0x4 + +/* register and values for PLL control */ +#define CRT_PLL1_HS 0x802a8 +#define CRT_PLL1_HS_OUTER_BYPASS(x) ((x) << 30) +#define CRT_PLL1_HS_INTER_BYPASS(x) ((x) << 29) +#define CRT_PLL1_HS_POWERON(x) ((x) << 24) + +#define CRT_PLL1_HS_25MHZ 0x23d40f02 +#define CRT_PLL1_HS_40MHZ 0x23940801 +#define CRT_PLL1_HS_65MHZ 0x23940d01 +#define CRT_PLL1_HS_78MHZ 0x23540F82 +#define CRT_PLL1_HS_74MHZ 0x23941dc2 +#define CRT_PLL1_HS_80MHZ 0x23941001 +#define CRT_PLL1_HS_80MHZ_1152 0x23540fc2 +#define CRT_PLL1_HS_106MHZ 0x237C1641 +#define CRT_PLL1_HS_108MHZ 0x23b41b01 +#define CRT_PLL1_HS_162MHZ 0x23480681 +#define CRT_PLL1_HS_148MHZ 0x23541dc2 +#define CRT_PLL1_HS_193MHZ 0x234807c1 + +#define CRT_PLL2_HS 0x802ac +#define CRT_PLL2_HS_25MHZ 0x206B851E +#define CRT_PLL2_HS_40MHZ 0x30000000 +#define CRT_PLL2_HS_65MHZ 0x40000000 +#define CRT_PLL2_HS_78MHZ 0x50E147AE +#define CRT_PLL2_HS_74MHZ 0x602B6AE7 +#define CRT_PLL2_HS_80MHZ 0x70000000 +#define CRT_PLL2_HS_106MHZ 0x0075c28f +#define CRT_PLL2_HS_108MHZ 0x80000000 +#define CRT_PLL2_HS_162MHZ 0xA0000000 +#define CRT_PLL2_HS_148MHZ 0xB0CCCCCD +#define CRT_PLL2_HS_193MHZ 0xC0872B02 + +#define HIBMC_CRT_PALETTE 0x80C00 + +#define HIBMC_FIELD(field, value) (field(value) & field##_MASK) +#endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c new file mode 100644 index 0000000000..8c6d2ea2a4 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Hisilicon Hibmc SoC drm driver + * + * Based on the bochs drm driver. + * + * Copyright (c) 2016 Huawei Limited. + * + * Author: + * Rongrong Zou <zourongrong@huawei.com> + * Rongrong Zou <zourongrong@gmail.com> + * Jianhua Li <lijianhua@huawei.com> + */ + +#include <linux/io.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_print.h> +#include <drm/drm_simple_kms_helper.h> + +#include "hibmc_drm_drv.h" +#include "hibmc_drm_regs.h" + +static int hibmc_connector_get_modes(struct drm_connector *connector) +{ + int count; + void *edid; + struct hibmc_connector *hibmc_connector = to_hibmc_connector(connector); + + edid = drm_get_edid(connector, &hibmc_connector->adapter); + if (edid) { + drm_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + if (count) + goto out; + } + + count = drm_add_modes_noedid(connector, + connector->dev->mode_config.max_width, + connector->dev->mode_config.max_height); + drm_set_preferred_mode(connector, 1024, 768); + +out: + kfree(edid); + return count; +} + +static void hibmc_connector_destroy(struct drm_connector *connector) +{ + struct hibmc_connector *hibmc_connector = to_hibmc_connector(connector); + + i2c_del_adapter(&hibmc_connector->adapter); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_helper_funcs + hibmc_connector_helper_funcs = { + .get_modes = hibmc_connector_get_modes, +}; + +static const struct drm_connector_funcs hibmc_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = hibmc_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static void hibmc_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + u32 reg; + struct drm_device *dev = encoder->dev; + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + + reg = readl(priv->mmio + HIBMC_DISPLAY_CONTROL_HISILE); + reg |= HIBMC_DISPLAY_CONTROL_FPVDDEN(1); + reg |= HIBMC_DISPLAY_CONTROL_PANELDATE(1); + reg |= HIBMC_DISPLAY_CONTROL_FPEN(1); + reg |= HIBMC_DISPLAY_CONTROL_VBIASEN(1); + writel(reg, priv->mmio + HIBMC_DISPLAY_CONTROL_HISILE); +} + +static const struct drm_encoder_helper_funcs hibmc_encoder_helper_funcs = { + .mode_set = hibmc_encoder_mode_set, +}; + +int hibmc_vdac_init(struct hibmc_drm_private *priv) +{ + struct drm_device *dev = &priv->dev; + struct hibmc_connector *hibmc_connector = &priv->connector; + struct drm_encoder *encoder = &priv->encoder; + struct drm_crtc *crtc = &priv->crtc; + struct drm_connector *connector = &hibmc_connector->base; + int ret; + + ret = hibmc_ddc_create(dev, hibmc_connector); + if (ret) { + drm_err(dev, "failed to create ddc: %d\n", ret); + return ret; + } + + encoder->possible_crtcs = drm_crtc_mask(crtc); + ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_DAC); + if (ret) { + drm_err(dev, "failed to init encoder: %d\n", ret); + return ret; + } + + drm_encoder_helper_add(encoder, &hibmc_encoder_helper_funcs); + + ret = drm_connector_init_with_ddc(dev, connector, + &hibmc_connector_funcs, + DRM_MODE_CONNECTOR_VGA, + &hibmc_connector->adapter); + if (ret) { + drm_err(dev, "failed to init connector: %d\n", ret); + return ret; + } + + drm_connector_helper_add(connector, &hibmc_connector_helper_funcs); + + drm_connector_attach_encoder(connector, encoder); + + return 0; +} |