video: Add Meson Video Processing Unit Driver
This adds video output support for Amlogic GXBB/GXL/GXM chips. The supported ports are CVBS and HDMI (based on DW_HDMI). When using HDMI, only DMT modes are supported. There is support for simple-framebuffer (CONFIG_VIDEO_DT_SIMPLEFB) Signed-off-by: Neil Armstrong <narmstrong@baylibre.com> Signed-off-by: Jorge Ramire-Ortiz <jramirez@baylibre.com> Signed-off-by: Maxime Jourdan <mjourdan@baylibre.com> [narmstrong: fixed defines alignment in meson_canvas.c] Reviewed-by: Anatolij Gustschin <agust@denx.de>
This commit is contained in:
parent
56dd8d87e5
commit
3bed422094
13
arch/arm/include/asm/arch-meson/meson-vpu.h
Normal file
13
arch/arm/include/asm/arch-meson/meson-vpu.h
Normal file
@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Copyright (C) 2016 BayLibre, SAS
|
||||
* Author: Maxime Jourdan <mjourdan@baylibre.com>
|
||||
*/
|
||||
|
||||
#ifndef __MESON_VPU_H__
|
||||
#define __MESON_VPU_H__
|
||||
|
||||
/* Allow reserving the framebuffer memory region */
|
||||
void meson_vpu_rsv_fb(void *fdt);
|
||||
|
||||
#endif /* __MESON_VPU_H__ */
|
@ -401,6 +401,8 @@ config VIDEO_LCD_SPI_MISO
|
||||
option takes a string in the format understood by 'name_to_gpio'
|
||||
function, e.g. PH1 for pin 1 of port H.
|
||||
|
||||
source "drivers/video/meson/Kconfig"
|
||||
|
||||
config VIDEO_MVEBU
|
||||
bool "Armada XP LCD controller"
|
||||
default n
|
||||
|
@ -52,6 +52,7 @@ obj-$(CONFIG_VIDEO_LCD_ANX9804) += anx9804.o
|
||||
obj-$(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM) += hitachi_tx18d42vm_lcd.o
|
||||
obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o
|
||||
obj-$(CONFIG_VIDEO_MB862xx) += mb862xx.o videomodes.o
|
||||
obj-${CONFIG_VIDEO_MESON} += meson/
|
||||
obj-$(CONFIG_VIDEO_MVEBU) += mvebu_lcd.o
|
||||
obj-$(CONFIG_VIDEO_MX3) += mx3fb.o videomodes.o
|
||||
obj-$(CONFIG_VIDEO_MXS) += mxsfb.o videomodes.o
|
||||
|
12
drivers/video/meson/Kconfig
Normal file
12
drivers/video/meson/Kconfig
Normal file
@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# Copyright (C) 2018 BayLibre, SAS
|
||||
#
|
||||
# Author: Neil Armstrong <narmstrong@baylibre.com>
|
||||
|
||||
config VIDEO_MESON
|
||||
bool "Enable Amlogic Meson video support"
|
||||
depends on DM_VIDEO
|
||||
select DISPLAY
|
||||
help
|
||||
Enable Amlogic Meson Video Processing Unit video support.
|
9
drivers/video/meson/Makefile
Normal file
9
drivers/video/meson/Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# Copyright (C) 2018 BayLibre, SAS
|
||||
#
|
||||
# Author: Neil Armstrong <narmstrong@baylibre.com>
|
||||
|
||||
obj-$(CONFIG_VIDEO_MESON) = meson_vpu.o meson_vpu_init.o meson_canvas.o
|
||||
obj-$(CONFIG_VIDEO_MESON) += meson_plane.o meson_venc.o meson_vclk.o
|
||||
obj-$(CONFIG_VIDEO_MESON) += meson_dw_hdmi.o simplefb_common.o ../dw_hdmi.o
|
45
drivers/video/meson/meson_canvas.c
Normal file
45
drivers/video/meson/meson_canvas.c
Normal file
@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Amlogic Meson Video Processing Unit driver
|
||||
*
|
||||
* Copyright (c) 2018 BayLibre, SAS.
|
||||
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
||||
*/
|
||||
|
||||
#include "meson_vpu.h"
|
||||
|
||||
/* DMC Registers */
|
||||
#define DMC_CAV_LUT_DATAL 0x48 /* 0x12 offset in data sheet */
|
||||
#define CANVAS_WIDTH_LBIT 29
|
||||
#define CANVAS_WIDTH_LWID 3
|
||||
#define DMC_CAV_LUT_DATAH 0x4c /* 0x13 offset in data sheet */
|
||||
#define CANVAS_WIDTH_HBIT 0
|
||||
#define CANVAS_HEIGHT_BIT 9
|
||||
#define CANVAS_BLKMODE_BIT 24
|
||||
#define DMC_CAV_LUT_ADDR 0x50 /* 0x14 offset in data sheet */
|
||||
#define CANVAS_LUT_WR_EN (0x2 << 8)
|
||||
#define CANVAS_LUT_RD_EN (0x1 << 8)
|
||||
|
||||
void meson_canvas_setup(struct meson_vpu_priv *priv,
|
||||
u32 canvas_index, u32 addr,
|
||||
u32 stride, u32 height,
|
||||
unsigned int wrap,
|
||||
unsigned int blkmode)
|
||||
{
|
||||
dmc_write(DMC_CAV_LUT_DATAL,
|
||||
(((addr + 7) >> 3)) |
|
||||
(((stride + 7) >> 3) << CANVAS_WIDTH_LBIT));
|
||||
|
||||
dmc_write(DMC_CAV_LUT_DATAH,
|
||||
((((stride + 7) >> 3) >> CANVAS_WIDTH_LWID) <<
|
||||
CANVAS_WIDTH_HBIT) |
|
||||
(height << CANVAS_HEIGHT_BIT) |
|
||||
(wrap << 22) |
|
||||
(blkmode << CANVAS_BLKMODE_BIT));
|
||||
|
||||
dmc_write(DMC_CAV_LUT_ADDR,
|
||||
CANVAS_LUT_WR_EN | canvas_index);
|
||||
|
||||
/* Force a read-back to make sure everything is flushed. */
|
||||
dmc_read(DMC_CAV_LUT_DATAH);
|
||||
}
|
445
drivers/video/meson/meson_dw_hdmi.c
Normal file
445
drivers/video/meson/meson_dw_hdmi.c
Normal file
@ -0,0 +1,445 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2018 BayLibre, SAS
|
||||
* Author: Jorge Ramirez-Ortiz <jramirez@baylibre.com>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <display.h>
|
||||
#include <dm.h>
|
||||
#include <edid.h>
|
||||
#include <asm/io.h>
|
||||
#include <dw_hdmi.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <dm/uclass-internal.h>
|
||||
#include <power/regulator.h>
|
||||
#include <clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <reset.h>
|
||||
#include <media_bus_format.h>
|
||||
#include "meson_dw_hdmi.h"
|
||||
#include "meson_vpu.h"
|
||||
|
||||
/* TOP Block Communication Channel */
|
||||
#define HDMITX_TOP_ADDR_REG 0x0
|
||||
#define HDMITX_TOP_DATA_REG 0x4
|
||||
#define HDMITX_TOP_CTRL_REG 0x8
|
||||
|
||||
/* Controller Communication Channel */
|
||||
#define HDMITX_DWC_ADDR_REG 0x10
|
||||
#define HDMITX_DWC_DATA_REG 0x14
|
||||
#define HDMITX_DWC_CTRL_REG 0x18
|
||||
|
||||
/* HHI Registers */
|
||||
#define HHI_MEM_PD_REG0 0x100 /* 0x40 */
|
||||
#define HHI_HDMI_CLK_CNTL 0x1cc /* 0x73 */
|
||||
#define HHI_HDMI_PHY_CNTL0 0x3a0 /* 0xe8 */
|
||||
#define HHI_HDMI_PHY_CNTL1 0x3a4 /* 0xe9 */
|
||||
#define HHI_HDMI_PHY_CNTL2 0x3a8 /* 0xea */
|
||||
#define HHI_HDMI_PHY_CNTL3 0x3ac /* 0xeb */
|
||||
|
||||
struct meson_dw_hdmi {
|
||||
struct udevice *dev;
|
||||
struct dw_hdmi hdmi;
|
||||
void __iomem *hhi_base;
|
||||
};
|
||||
|
||||
enum hdmi_compatible {
|
||||
HDMI_COMPATIBLE_GXBB = 0,
|
||||
HDMI_COMPATIBLE_GXL = 1,
|
||||
HDMI_COMPATIBLE_GXM = 2,
|
||||
};
|
||||
|
||||
static inline bool meson_hdmi_is_compatible(struct meson_dw_hdmi *priv,
|
||||
enum hdmi_compatible family)
|
||||
{
|
||||
enum hdmi_compatible compat = dev_get_driver_data(priv->dev);
|
||||
|
||||
return compat == family;
|
||||
}
|
||||
|
||||
static unsigned int dw_hdmi_top_read(struct dw_hdmi *hdmi, unsigned int addr)
|
||||
{
|
||||
unsigned int data;
|
||||
|
||||
/* ADDR must be written twice */
|
||||
writel(addr & 0xffff, hdmi->ioaddr + HDMITX_TOP_ADDR_REG);
|
||||
writel(addr & 0xffff, hdmi->ioaddr + HDMITX_TOP_ADDR_REG);
|
||||
|
||||
/* Read needs a second DATA read */
|
||||
data = readl(hdmi->ioaddr + HDMITX_TOP_DATA_REG);
|
||||
data = readl(hdmi->ioaddr + HDMITX_TOP_DATA_REG);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static inline void dw_hdmi_top_write(struct dw_hdmi *hdmi,
|
||||
unsigned int addr, unsigned int data)
|
||||
{
|
||||
/* ADDR must be written twice */
|
||||
writel(addr & 0xffff, hdmi->ioaddr + HDMITX_TOP_ADDR_REG);
|
||||
writel(addr & 0xffff, hdmi->ioaddr + HDMITX_TOP_ADDR_REG);
|
||||
|
||||
/* Write needs single DATA write */
|
||||
writel(data, hdmi->ioaddr + HDMITX_TOP_DATA_REG);
|
||||
}
|
||||
|
||||
static inline void dw_hdmi_top_write_bits(struct dw_hdmi *hdmi,
|
||||
unsigned int addr,
|
||||
unsigned int mask,
|
||||
unsigned int val)
|
||||
{
|
||||
unsigned int data = dw_hdmi_top_read(hdmi, addr);
|
||||
|
||||
data &= ~mask;
|
||||
data |= val;
|
||||
dw_hdmi_top_write(hdmi, addr, data);
|
||||
}
|
||||
|
||||
static u8 dw_hdmi_dwc_read(struct dw_hdmi *hdmi, int addr)
|
||||
{
|
||||
unsigned int data;
|
||||
|
||||
/* ADDR must be written twice */
|
||||
writel(addr & 0xffff, hdmi->ioaddr + HDMITX_DWC_ADDR_REG);
|
||||
writel(addr & 0xffff, hdmi->ioaddr + HDMITX_DWC_ADDR_REG);
|
||||
|
||||
/* Read needs a second DATA read */
|
||||
data = readl(hdmi->ioaddr + HDMITX_DWC_DATA_REG);
|
||||
data = readl(hdmi->ioaddr + HDMITX_DWC_DATA_REG);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static inline void dw_hdmi_dwc_write(struct dw_hdmi *hdmi, u8 data, int addr)
|
||||
{
|
||||
/* ADDR must be written twice */
|
||||
writel(addr & 0xffff, hdmi->ioaddr + HDMITX_DWC_ADDR_REG);
|
||||
writel(addr & 0xffff, hdmi->ioaddr + HDMITX_DWC_ADDR_REG);
|
||||
|
||||
/* Write needs single DATA write */
|
||||
writel(data, hdmi->ioaddr + HDMITX_DWC_DATA_REG);
|
||||
}
|
||||
|
||||
static inline void dw_hdmi_dwc_write_bits(struct dw_hdmi *hdmi,
|
||||
unsigned int addr,
|
||||
unsigned int mask,
|
||||
unsigned int val)
|
||||
{
|
||||
u8 data = dw_hdmi_dwc_read(hdmi, addr);
|
||||
|
||||
data &= ~mask;
|
||||
data |= val;
|
||||
|
||||
dw_hdmi_dwc_write(hdmi, data, addr);
|
||||
}
|
||||
|
||||
static inline void dw_hdmi_hhi_write(struct meson_dw_hdmi *priv,
|
||||
unsigned int addr, unsigned int data)
|
||||
{
|
||||
hhi_write(addr, data);
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static unsigned int dw_hdmi_hhi_read(struct meson_dw_hdmi *priv,
|
||||
unsigned int addr)
|
||||
{
|
||||
return hhi_read(addr);
|
||||
}
|
||||
|
||||
static inline void dw_hdmi_hhi_update_bits(struct meson_dw_hdmi *priv,
|
||||
unsigned int addr,
|
||||
unsigned int mask,
|
||||
unsigned int val)
|
||||
{
|
||||
hhi_update_bits(addr, mask, val);
|
||||
}
|
||||
|
||||
static int meson_dw_hdmi_read_edid(struct udevice *dev, u8 *buf, int buf_size)
|
||||
{
|
||||
#if defined DEBUG
|
||||
struct display_timing timing;
|
||||
int panel_bits_per_colour;
|
||||
#endif
|
||||
struct meson_dw_hdmi *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
ret = dw_hdmi_read_edid(&priv->hdmi, buf, buf_size);
|
||||
|
||||
#if defined DEBUG
|
||||
if (!ret)
|
||||
return ret;
|
||||
|
||||
edid_print_info((struct edid1_info *)buf);
|
||||
edid_get_timing(buf, ret, &timing, &panel_bits_per_colour);
|
||||
debug("Display timing:\n");
|
||||
debug(" hactive %04d, hfrontp %04d, hbackp %04d hsync %04d\n"
|
||||
" vactive %04d, vfrontp %04d, vbackp %04d vsync %04d\n",
|
||||
timing.hactive.typ, timing.hfront_porch.typ,
|
||||
timing.hback_porch.typ, timing.hsync_len.typ,
|
||||
timing.vactive.typ, timing.vfront_porch.typ,
|
||||
timing.vback_porch.typ, timing.vsync_len.typ);
|
||||
debug(" flags: ");
|
||||
if (timing.flags & DISPLAY_FLAGS_INTERLACED)
|
||||
debug("interlaced ");
|
||||
if (timing.flags & DISPLAY_FLAGS_DOUBLESCAN)
|
||||
debug("doublescan ");
|
||||
if (timing.flags & DISPLAY_FLAGS_DOUBLECLK)
|
||||
debug("doubleclk ");
|
||||
if (timing.flags & DISPLAY_FLAGS_HSYNC_LOW)
|
||||
debug("hsync_low ");
|
||||
if (timing.flags & DISPLAY_FLAGS_HSYNC_HIGH)
|
||||
debug("hsync_high ");
|
||||
if (timing.flags & DISPLAY_FLAGS_VSYNC_LOW)
|
||||
debug("vsync_low ");
|
||||
if (timing.flags & DISPLAY_FLAGS_VSYNC_HIGH)
|
||||
debug("vsync_high ");
|
||||
debug("\n");
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void meson_dw_hdmi_phy_reset(struct meson_dw_hdmi *priv)
|
||||
{
|
||||
/* Enable and software reset */
|
||||
dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1, 0xf, 0xf);
|
||||
|
||||
mdelay(2);
|
||||
|
||||
/* Enable and unreset */
|
||||
dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1, 0xf, 0xe);
|
||||
|
||||
mdelay(2);
|
||||
}
|
||||
|
||||
static void meson_dw_hdmi_phy_setup_mode(struct meson_dw_hdmi *priv,
|
||||
uint pixel_clock)
|
||||
{
|
||||
pixel_clock = pixel_clock / 1000;
|
||||
|
||||
if (meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_GXL) ||
|
||||
meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_GXM)) {
|
||||
if (pixel_clock >= 371250) {
|
||||
/* 5.94Gbps, 3.7125Gbps */
|
||||
hhi_write(HHI_HDMI_PHY_CNTL0, 0x333d3282);
|
||||
hhi_write(HHI_HDMI_PHY_CNTL3, 0x2136315b);
|
||||
} else if (pixel_clock >= 297000) {
|
||||
/* 2.97Gbps */
|
||||
hhi_write(HHI_HDMI_PHY_CNTL0, 0x33303382);
|
||||
hhi_write(HHI_HDMI_PHY_CNTL3, 0x2036315b);
|
||||
} else if (pixel_clock >= 148500) {
|
||||
/* 1.485Gbps */
|
||||
hhi_write(HHI_HDMI_PHY_CNTL0, 0x33303362);
|
||||
hhi_write(HHI_HDMI_PHY_CNTL3, 0x2016315b);
|
||||
} else {
|
||||
/* 742.5Mbps, and below */
|
||||
hhi_write(HHI_HDMI_PHY_CNTL0, 0x33604142);
|
||||
hhi_write(HHI_HDMI_PHY_CNTL3, 0x0016315b);
|
||||
}
|
||||
} else {
|
||||
if (pixel_clock >= 371250) {
|
||||
/* 5.94Gbps, 3.7125Gbps */
|
||||
hhi_write(HHI_HDMI_PHY_CNTL0, 0x33353245);
|
||||
hhi_write(HHI_HDMI_PHY_CNTL3, 0x2100115b);
|
||||
} else if (pixel_clock >= 297000) {
|
||||
/* 2.97Gbps */
|
||||
hhi_write(HHI_HDMI_PHY_CNTL0, 0x33634283);
|
||||
hhi_write(HHI_HDMI_PHY_CNTL3, 0xb000115b);
|
||||
} else {
|
||||
/* 1.485Gbps, and below */
|
||||
hhi_write(HHI_HDMI_PHY_CNTL0, 0x33632122);
|
||||
hhi_write(HHI_HDMI_PHY_CNTL3, 0x2000115b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int meson_dw_hdmi_phy_init(struct dw_hdmi *hdmi, uint pixel_clock)
|
||||
{
|
||||
struct meson_dw_hdmi *priv = container_of(hdmi, struct meson_dw_hdmi,
|
||||
hdmi);
|
||||
/* Enable clocks */
|
||||
dw_hdmi_hhi_update_bits(priv, HHI_HDMI_CLK_CNTL, 0xffff, 0x100);
|
||||
|
||||
/* Bring HDMITX MEM output of power down */
|
||||
dw_hdmi_hhi_update_bits(priv, HHI_MEM_PD_REG0, 0xff << 8, 0);
|
||||
|
||||
/* Bring out of reset */
|
||||
dw_hdmi_top_write(hdmi, HDMITX_TOP_SW_RESET, 0);
|
||||
|
||||
/* Enable internal pixclk, tmds_clk, spdif_clk, i2s_clk, cecclk */
|
||||
dw_hdmi_top_write_bits(hdmi, HDMITX_TOP_CLK_CNTL, 0x3, 0x3);
|
||||
dw_hdmi_top_write_bits(hdmi, HDMITX_TOP_CLK_CNTL, 0x3 << 4, 0x3 << 4);
|
||||
|
||||
/* Enable normal output to PHY */
|
||||
dw_hdmi_top_write(hdmi, HDMITX_TOP_BIST_CNTL, BIT(12));
|
||||
|
||||
/* TMDS pattern setup (TOFIX pattern for 4k2k scrambling) */
|
||||
dw_hdmi_top_write(hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, 0x001f001f);
|
||||
dw_hdmi_top_write(hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, 0x001f001f);
|
||||
|
||||
/* Load TMDS pattern */
|
||||
dw_hdmi_top_write(hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x1);
|
||||
mdelay(20);
|
||||
dw_hdmi_top_write(hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x2);
|
||||
|
||||
/* Setup PHY parameters */
|
||||
meson_dw_hdmi_phy_setup_mode(priv, pixel_clock);
|
||||
|
||||
/* Setup PHY */
|
||||
dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1,
|
||||
0xffff << 16, 0x0390 << 16);
|
||||
|
||||
/* BIT_INVERT */
|
||||
if (meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_GXL) ||
|
||||
meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_GXM))
|
||||
dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1, BIT(17), 0);
|
||||
else
|
||||
dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1,
|
||||
BIT(17), BIT(17));
|
||||
|
||||
/* Disable clock, fifo, fifo_wr */
|
||||
dw_hdmi_hhi_update_bits(priv, HHI_HDMI_PHY_CNTL1, 0xf, 0);
|
||||
|
||||
mdelay(100);
|
||||
|
||||
/* Reset PHY 3 times in a row */
|
||||
meson_dw_hdmi_phy_reset(priv);
|
||||
meson_dw_hdmi_phy_reset(priv);
|
||||
meson_dw_hdmi_phy_reset(priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_dw_hdmi_enable(struct udevice *dev, int panel_bpp,
|
||||
const struct display_timing *edid)
|
||||
{
|
||||
struct meson_dw_hdmi *priv = dev_get_priv(dev);
|
||||
|
||||
/* will back into meson_dw_hdmi_phy_init */
|
||||
return dw_hdmi_enable(&priv->hdmi, edid);
|
||||
}
|
||||
|
||||
static int meson_dw_hdmi_wait_hpd(struct dw_hdmi *hdmi)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Poll 1 second for HPD signal */
|
||||
for (i = 0; i < 10; ++i) {
|
||||
if (dw_hdmi_top_read(hdmi, HDMITX_TOP_STAT0))
|
||||
return 0;
|
||||
|
||||
mdelay(100);
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int meson_dw_hdmi_probe(struct udevice *dev)
|
||||
{
|
||||
struct meson_dw_hdmi *priv = dev_get_priv(dev);
|
||||
struct reset_ctl_bulk resets;
|
||||
struct clk_bulk clocks;
|
||||
struct udevice *supply;
|
||||
int ret;
|
||||
|
||||
priv->dev = dev;
|
||||
|
||||
priv->hdmi.ioaddr = (ulong)dev_remap_addr_index(dev, 0);
|
||||
if (!priv->hdmi.ioaddr)
|
||||
return -EINVAL;
|
||||
|
||||
priv->hhi_base = dev_remap_addr_index(dev, 1);
|
||||
if (!priv->hhi_base)
|
||||
return -EINVAL;
|
||||
|
||||
priv->hdmi.hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
|
||||
priv->hdmi.hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_YUV8_1X24;
|
||||
priv->hdmi.phy_set = meson_dw_hdmi_phy_init;
|
||||
priv->hdmi.write_reg = dw_hdmi_dwc_write;
|
||||
priv->hdmi.read_reg = dw_hdmi_dwc_read;
|
||||
priv->hdmi.i2c_clk_high = 0x67;
|
||||
priv->hdmi.i2c_clk_low = 0x78;
|
||||
|
||||
ret = device_get_supply_regulator(dev, "hdmi-supply", &supply);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regulator_set_enable(supply, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = reset_get_bulk(dev, &resets);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_get_bulk(dev, &clocks);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_enable_bulk(&clocks);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable clocks */
|
||||
dw_hdmi_hhi_update_bits(priv, HHI_HDMI_CLK_CNTL, 0xffff, 0x100);
|
||||
|
||||
/* Bring HDMITX MEM output of power down */
|
||||
dw_hdmi_hhi_update_bits(priv, HHI_MEM_PD_REG0, 0xff << 8, 0);
|
||||
|
||||
/* Reset HDMITX APB & TX & PHY: cycle needed for EDID */
|
||||
ret = reset_deassert_bulk(&resets);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = reset_assert_bulk(&resets);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = reset_deassert_bulk(&resets);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable APB3 fail on error */
|
||||
writel_bits(BIT(15), BIT(15), priv->hdmi.ioaddr + HDMITX_TOP_CTRL_REG);
|
||||
writel_bits(BIT(15), BIT(15), priv->hdmi.ioaddr + HDMITX_DWC_CTRL_REG);
|
||||
|
||||
/* Bring out of reset */
|
||||
dw_hdmi_top_write(&priv->hdmi, HDMITX_TOP_SW_RESET, 0);
|
||||
mdelay(20);
|
||||
dw_hdmi_top_write(&priv->hdmi, HDMITX_TOP_CLK_CNTL, 0xff);
|
||||
|
||||
dw_hdmi_init(&priv->hdmi);
|
||||
dw_hdmi_phy_init(&priv->hdmi);
|
||||
|
||||
/* wait for connector */
|
||||
ret = meson_dw_hdmi_wait_hpd(&priv->hdmi);
|
||||
if (ret)
|
||||
debug("hdmi can not get hpd signal\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dm_display_ops meson_dw_hdmi_ops = {
|
||||
.read_edid = meson_dw_hdmi_read_edid,
|
||||
.enable = meson_dw_hdmi_enable,
|
||||
};
|
||||
|
||||
static const struct udevice_id meson_dw_hdmi_ids[] = {
|
||||
{ .compatible = "amlogic,meson-gxbb-dw-hdmi",
|
||||
.data = HDMI_COMPATIBLE_GXBB },
|
||||
{ .compatible = "amlogic,meson-gxl-dw-hdmi",
|
||||
.data = HDMI_COMPATIBLE_GXL },
|
||||
{ .compatible = "amlogic,meson-gxm-dw-hdmi",
|
||||
.data = HDMI_COMPATIBLE_GXM },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(meson_dw_hdmi) = {
|
||||
.name = "meson_dw_hdmi",
|
||||
.id = UCLASS_DISPLAY,
|
||||
.of_match = meson_dw_hdmi_ids,
|
||||
.ops = &meson_dw_hdmi_ops,
|
||||
.probe = meson_dw_hdmi_probe,
|
||||
.priv_auto_alloc_size = sizeof(struct meson_dw_hdmi),
|
||||
};
|
134
drivers/video/meson/meson_dw_hdmi.h
Normal file
134
drivers/video/meson/meson_dw_hdmi.h
Normal file
@ -0,0 +1,134 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2016 BayLibre, SAS
|
||||
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
||||
* Copyright (C) 2015 Amlogic, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __MESON_DW_HDMI_H
|
||||
#define __MESON_DW_HDMI_H
|
||||
|
||||
/*
|
||||
* Bit 7 RW Reserved. Default 1.
|
||||
* Bit 6 RW Reserved. Default 1.
|
||||
* Bit 5 RW Reserved. Default 1.
|
||||
* Bit 4 RW sw_reset_phyif: PHY interface. 1=Apply reset; 0=Release from reset.
|
||||
* Default 1.
|
||||
* Bit 3 RW sw_reset_intr: interrupt module. 1=Apply reset;
|
||||
* 0=Release from reset.
|
||||
* Default 1.
|
||||
* Bit 2 RW sw_reset_mem: KSV/REVOC mem. 1=Apply reset; 0=Release from reset.
|
||||
* Default 1.
|
||||
* Bit 1 RW sw_reset_rnd: random number interface to HDCP. 1=Apply reset;
|
||||
* 0=Release from reset. Default 1.
|
||||
* Bit 0 RW sw_reset_core: connects to IP's ~irstz. 1=Apply reset;
|
||||
* 0=Release from reset. Default 1.
|
||||
*/
|
||||
#define HDMITX_TOP_SW_RESET (0x000)
|
||||
|
||||
/*
|
||||
* Bit 12 RW i2s_ws_inv:1=Invert i2s_ws; 0=No invert. Default 0.
|
||||
* Bit 11 RW i2s_clk_inv: 1=Invert i2s_clk; 0=No invert. Default 0.
|
||||
* Bit 10 RW spdif_clk_inv: 1=Invert spdif_clk; 0=No invert. Default 0.
|
||||
* Bit 9 RW tmds_clk_inv: 1=Invert tmds_clk; 0=No invert. Default 0.
|
||||
* Bit 8 RW pixel_clk_inv: 1=Invert pixel_clk; 0=No invert. Default 0.
|
||||
* Bit 4 RW cec_clk_en: 1=enable cec_clk; 0=disable. Default 0.
|
||||
* Bit 3 RW i2s_clk_en: 1=enable i2s_clk; 0=disable. Default 0.
|
||||
* Bit 2 RW spdif_clk_en: 1=enable spdif_clk; 0=disable. Default 0.
|
||||
* Bit 1 RW tmds_clk_en: 1=enable tmds_clk; 0=disable. Default 0.
|
||||
* Bit 0 RW pixel_clk_en: 1=enable pixel_clk; 0=disable. Default 0.
|
||||
*/
|
||||
#define HDMITX_TOP_CLK_CNTL (0x001)
|
||||
|
||||
/*
|
||||
* Bit 11: 0 RW hpd_valid_width: filter out width <= M*1024. Default 0.
|
||||
* Bit 15:12 RW hpd_glitch_width: filter out glitch <= N. Default 0.
|
||||
*/
|
||||
#define HDMITX_TOP_HPD_FILTER (0x002)
|
||||
|
||||
/*
|
||||
* intr_maskn: MASK_N, one bit per interrupt source.
|
||||
* 1=Enable interrupt source; 0=Disable interrupt source. Default 0.
|
||||
* [ 4] hdcp22_rndnum_err
|
||||
* [ 3] nonce_rfrsh_rise
|
||||
* [ 2] hpd_fall_intr
|
||||
* [ 1] hpd_rise_intr
|
||||
* [ 0] core_intr
|
||||
*/
|
||||
#define HDMITX_TOP_INTR_MASKN (0x003)
|
||||
|
||||
/*
|
||||
* Bit 30: 0 RW intr_stat: For each bit, write 1 to manually set the interrupt
|
||||
* bit, read back the interrupt status.
|
||||
* Bit 31 R IP interrupt status
|
||||
* Bit 2 RW hpd_fall
|
||||
* Bit 1 RW hpd_rise
|
||||
* Bit 0 RW IP interrupt
|
||||
*/
|
||||
#define HDMITX_TOP_INTR_STAT (0x004)
|
||||
|
||||
/*
|
||||
* [4] hdcp22_rndnum_err
|
||||
* [3] nonce_rfrsh_rise
|
||||
* [2] hpd_fall
|
||||
* [1] hpd_rise
|
||||
* [0] core_intr_rise
|
||||
*/
|
||||
#define HDMITX_TOP_INTR_STAT_CLR (0x005)
|
||||
|
||||
#define HDMITX_TOP_INTR_CORE BIT(0)
|
||||
#define HDMITX_TOP_INTR_HPD_RISE BIT(1)
|
||||
#define HDMITX_TOP_INTR_HPD_FALL BIT(2)
|
||||
|
||||
/* Bit 14:12 RW tmds_sel: 3'b000=Output zero; 3'b001=Output normal TMDS data;
|
||||
* 3'b010=Output PRBS data; 3'b100=Output shift pattern. Default 0.
|
||||
* Bit 11: 9 RW shift_pttn_repeat: 0=New pattern every clk cycle; 1=New pattern
|
||||
* every 2 clk cycles; ...; 7=New pattern every 8 clk cycles. Default 0.
|
||||
* Bit 8 RW shift_pttn_en: 1= Enable shift pattern generator; 0=Disable.
|
||||
* Default 0.
|
||||
* Bit 4: 3 RW prbs_pttn_mode: 0=PRBS11; 1=PRBS15; 2=PRBS7; 3=PRBS31. Default 0.
|
||||
* Bit 2: 1 RW prbs_pttn_width: 0=idle; 1=output 8-bit pattern;
|
||||
* 2=Output 1-bit pattern; 3=output 10-bit pattern. Default 0.
|
||||
* Bit 0 RW prbs_pttn_en: 1=Enable PRBS generator; 0=Disable. Default 0.
|
||||
*/
|
||||
#define HDMITX_TOP_BIST_CNTL (0x006)
|
||||
|
||||
/* Bit 29:20 RW shift_pttn_data[59:50]. Default 0. */
|
||||
/* Bit 19:10 RW shift_pttn_data[69:60]. Default 0. */
|
||||
/* Bit 9: 0 RW shift_pttn_data[79:70]. Default 0. */
|
||||
#define HDMITX_TOP_SHIFT_PTTN_012 (0x007)
|
||||
|
||||
/* Bit 29:20 RW shift_pttn_data[29:20]. Default 0. */
|
||||
/* Bit 19:10 RW shift_pttn_data[39:30]. Default 0. */
|
||||
/* Bit 9: 0 RW shift_pttn_data[49:40]. Default 0. */
|
||||
#define HDMITX_TOP_SHIFT_PTTN_345 (0x008)
|
||||
|
||||
/* Bit 19:10 RW shift_pttn_data[ 9: 0]. Default 0. */
|
||||
/* Bit 9: 0 RW shift_pttn_data[19:10]. Default 0. */
|
||||
#define HDMITX_TOP_SHIFT_PTTN_67 (0x009)
|
||||
|
||||
/* Bit 25:16 RW tmds_clk_pttn[19:10]. Default 0. */
|
||||
/* Bit 9: 0 RW tmds_clk_pttn[ 9: 0]. Default 0. */
|
||||
#define HDMITX_TOP_TMDS_CLK_PTTN_01 (0x00A)
|
||||
|
||||
/* Bit 25:16 RW tmds_clk_pttn[39:30]. Default 0. */
|
||||
/* Bit 9: 0 RW tmds_clk_pttn[29:20]. Default 0. */
|
||||
#define HDMITX_TOP_TMDS_CLK_PTTN_23 (0x00B)
|
||||
|
||||
/* Bit 1 RW shift_tmds_clk_pttn:1=Enable shifting clk pattern,
|
||||
* used when TMDS CLK rate = TMDS character rate /4. Default 0.
|
||||
* Bit 0 R Reserved. Default 0.
|
||||
* [ 1] shift_tmds_clk_pttn
|
||||
* [ 0] load_tmds_clk_pttn
|
||||
*/
|
||||
#define HDMITX_TOP_TMDS_CLK_PTTN_CNTL (0x00C)
|
||||
|
||||
/* Bit 0 RW revocmem_wr_fail: Read back 1 to indicate Host write REVOC MEM
|
||||
* failure, write 1 to clear the failure flag. Default 0.
|
||||
*/
|
||||
#define HDMITX_TOP_REVOCMEM_STAT (0x00D)
|
||||
|
||||
/* Bit 0 R filtered HPD status. */
|
||||
#define HDMITX_TOP_STAT0 (0x00E)
|
||||
|
||||
#endif /* __MESON_DW_HDMI_H */
|
177
drivers/video/meson/meson_plane.c
Normal file
177
drivers/video/meson/meson_plane.c
Normal file
@ -0,0 +1,177 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Amlogic Meson Video Processing Unit driver
|
||||
*
|
||||
* Copyright (c) 2018 BayLibre, SAS.
|
||||
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
||||
*/
|
||||
|
||||
#include "meson_vpu.h"
|
||||
|
||||
/* OSDx_BLKx_CFG */
|
||||
#define OSD_CANVAS_SEL 16
|
||||
|
||||
#define OSD_ENDIANNESS_LE BIT(15)
|
||||
#define OSD_ENDIANNESS_BE (0)
|
||||
|
||||
#define OSD_BLK_MODE_422 (0x03 << 8)
|
||||
#define OSD_BLK_MODE_16 (0x04 << 8)
|
||||
#define OSD_BLK_MODE_32 (0x05 << 8)
|
||||
#define OSD_BLK_MODE_24 (0x07 << 8)
|
||||
|
||||
#define OSD_OUTPUT_COLOR_RGB BIT(7)
|
||||
#define OSD_OUTPUT_COLOR_YUV (0)
|
||||
|
||||
#define OSD_COLOR_MATRIX_32_RGBA (0x00 << 2)
|
||||
#define OSD_COLOR_MATRIX_32_ARGB (0x01 << 2)
|
||||
#define OSD_COLOR_MATRIX_32_ABGR (0x02 << 2)
|
||||
#define OSD_COLOR_MATRIX_32_BGRA (0x03 << 2)
|
||||
|
||||
#define OSD_COLOR_MATRIX_24_RGB (0x00 << 2)
|
||||
|
||||
#define OSD_COLOR_MATRIX_16_RGB655 (0x00 << 2)
|
||||
#define OSD_COLOR_MATRIX_16_RGB565 (0x04 << 2)
|
||||
|
||||
#define OSD_INTERLACE_ENABLED BIT(1)
|
||||
#define OSD_INTERLACE_ODD BIT(0)
|
||||
#define OSD_INTERLACE_EVEN (0)
|
||||
|
||||
/* OSDx_CTRL_STAT */
|
||||
#define OSD_ENABLE BIT(21)
|
||||
#define OSD_BLK0_ENABLE BIT(0)
|
||||
|
||||
#define OSD_GLOBAL_ALPHA_SHIFT 12
|
||||
|
||||
/* OSDx_CTRL_STAT2 */
|
||||
#define OSD_REPLACE_EN BIT(14)
|
||||
#define OSD_REPLACE_SHIFT 6
|
||||
|
||||
/*
|
||||
* When the output is interlaced, the OSD must switch between
|
||||
* each field using the INTERLACE_SEL_ODD (0) of VIU_OSD1_BLK0_CFG_W0
|
||||
* at each vsync.
|
||||
* But the vertical scaler can provide such funtionnality if
|
||||
* is configured for 2:1 scaling with interlace options enabled.
|
||||
*/
|
||||
static void meson_vpp_setup_interlace_vscaler_osd1(struct meson_vpu_priv *priv,
|
||||
struct video_priv *uc_priv)
|
||||
{
|
||||
writel(BIT(3) /* Enable scaler */ |
|
||||
BIT(2), /* Select OSD1 */
|
||||
priv->io_base + _REG(VPP_OSD_SC_CTRL0));
|
||||
|
||||
writel(((uc_priv->xsize - 1) << 16) | (uc_priv->ysize - 1),
|
||||
priv->io_base + _REG(VPP_OSD_SCI_WH_M1));
|
||||
/* 2:1 scaling */
|
||||
writel((0 << 16) | uc_priv->xsize,
|
||||
priv->io_base + _REG(VPP_OSD_SCO_H_START_END));
|
||||
writel(((0 >> 1) << 16) | (uc_priv->ysize >> 1),
|
||||
priv->io_base + _REG(VPP_OSD_SCO_V_START_END));
|
||||
|
||||
/* 2:1 scaling values */
|
||||
writel(BIT(16), priv->io_base + _REG(VPP_OSD_VSC_INI_PHASE));
|
||||
writel(BIT(25), priv->io_base + _REG(VPP_OSD_VSC_PHASE_STEP));
|
||||
|
||||
writel(0, priv->io_base + _REG(VPP_OSD_HSC_CTRL0));
|
||||
|
||||
writel((4 << 0) /* osd_vsc_bank_length */ |
|
||||
(4 << 3) /* osd_vsc_top_ini_rcv_num0 */ |
|
||||
(1 << 8) /* osd_vsc_top_rpt_p0_num0 */ |
|
||||
(6 << 11) /* osd_vsc_bot_ini_rcv_num0 */ |
|
||||
(2 << 16) /* osd_vsc_bot_rpt_p0_num0 */ |
|
||||
BIT(23) /* osd_prog_interlace */ |
|
||||
BIT(24), /* Enable vertical scaler */
|
||||
priv->io_base + _REG(VPP_OSD_VSC_CTRL0));
|
||||
}
|
||||
|
||||
static void
|
||||
meson_vpp_disable_interlace_vscaler_osd1(struct meson_vpu_priv *priv)
|
||||
{
|
||||
writel(0, priv->io_base + _REG(VPP_OSD_SC_CTRL0));
|
||||
writel(0, priv->io_base + _REG(VPP_OSD_VSC_CTRL0));
|
||||
writel(0, priv->io_base + _REG(VPP_OSD_HSC_CTRL0));
|
||||
}
|
||||
|
||||
void meson_vpu_setup_plane(struct udevice *dev, bool is_interlaced)
|
||||
{
|
||||
struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev);
|
||||
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
|
||||
struct meson_vpu_priv *priv = dev_get_priv(dev);
|
||||
u32 osd1_ctrl_stat;
|
||||
u32 osd1_blk0_cfg[5];
|
||||
bool osd1_interlace;
|
||||
unsigned int src_x1, src_x2, src_y1, src_y2;
|
||||
unsigned int dest_x1, dest_x2, dest_y1, dest_y2;
|
||||
|
||||
dest_x1 = src_x1 = 0;
|
||||
dest_x2 = src_x2 = uc_priv->xsize;
|
||||
dest_y1 = src_y1 = 0;
|
||||
dest_y2 = src_y2 = uc_priv->ysize;
|
||||
|
||||
/* Enable VPP Postblend */
|
||||
writel(uc_priv->xsize,
|
||||
priv->io_base + _REG(VPP_POSTBLEND_H_SIZE));
|
||||
|
||||
writel_bits(VPP_POSTBLEND_ENABLE, VPP_POSTBLEND_ENABLE,
|
||||
priv->io_base + _REG(VPP_MISC));
|
||||
|
||||
/* uc_plat->base is the framebuffer */
|
||||
|
||||
/* Enable OSD and BLK0, set max global alpha */
|
||||
osd1_ctrl_stat = OSD_ENABLE | (0xFF << OSD_GLOBAL_ALPHA_SHIFT) |
|
||||
OSD_BLK0_ENABLE;
|
||||
|
||||
/* Set up BLK0 to point to the right canvas */
|
||||
osd1_blk0_cfg[0] = ((MESON_CANVAS_ID_OSD1 << OSD_CANVAS_SEL) |
|
||||
OSD_ENDIANNESS_LE);
|
||||
|
||||
/* On GXBB, Use the old non-HDR RGB2YUV converter */
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB))
|
||||
osd1_blk0_cfg[0] |= OSD_OUTPUT_COLOR_RGB;
|
||||
|
||||
/* For XRGB, replace the pixel's alpha by 0xFF */
|
||||
writel_bits(OSD_REPLACE_EN, OSD_REPLACE_EN,
|
||||
priv->io_base + _REG(VIU_OSD1_CTRL_STAT2));
|
||||
osd1_blk0_cfg[0] |= OSD_BLK_MODE_32 |
|
||||
OSD_COLOR_MATRIX_32_ARGB;
|
||||
|
||||
if (is_interlaced) {
|
||||
osd1_interlace = true;
|
||||
dest_y1 /= 2;
|
||||
dest_y2 /= 2;
|
||||
} else {
|
||||
osd1_interlace = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* The format of these registers is (x2 << 16 | x1),
|
||||
* where x2 is exclusive.
|
||||
* e.g. +30x1920 would be (1919 << 16) | 30
|
||||
*/
|
||||
osd1_blk0_cfg[1] = ((src_x2 - 1) << 16) | src_x1;
|
||||
osd1_blk0_cfg[2] = ((src_y2 - 1) << 16) | src_y1;
|
||||
osd1_blk0_cfg[3] = ((dest_x2 - 1) << 16) | dest_x1;
|
||||
osd1_blk0_cfg[4] = ((dest_y2 - 1) << 16) | dest_y1;
|
||||
|
||||
writel(osd1_ctrl_stat, priv->io_base + _REG(VIU_OSD1_CTRL_STAT));
|
||||
writel(osd1_blk0_cfg[0], priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W0));
|
||||
writel(osd1_blk0_cfg[1], priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W1));
|
||||
writel(osd1_blk0_cfg[2], priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W2));
|
||||
writel(osd1_blk0_cfg[3], priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W3));
|
||||
writel(osd1_blk0_cfg[4], priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W4));
|
||||
|
||||
/* If output is interlace, make use of the Scaler */
|
||||
if (osd1_interlace)
|
||||
meson_vpp_setup_interlace_vscaler_osd1(priv, uc_priv);
|
||||
else
|
||||
meson_vpp_disable_interlace_vscaler_osd1(priv);
|
||||
|
||||
meson_canvas_setup(priv, MESON_CANVAS_ID_OSD1,
|
||||
uc_plat->base, uc_priv->xsize * 4,
|
||||
uc_priv->ysize, MESON_CANVAS_WRAP_NONE,
|
||||
MESON_CANVAS_BLKMODE_LINEAR);
|
||||
|
||||
/* Enable OSD1 */
|
||||
writel_bits(VPP_OSD1_POSTBLEND, VPP_OSD1_POSTBLEND,
|
||||
priv->io_base + _REG(VPP_MISC));
|
||||
}
|
1393
drivers/video/meson/meson_registers.h
Normal file
1393
drivers/video/meson/meson_registers.h
Normal file
File diff suppressed because it is too large
Load Diff
893
drivers/video/meson/meson_vclk.c
Normal file
893
drivers/video/meson/meson_vclk.c
Normal file
@ -0,0 +1,893 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Amlogic Meson Video Processing Unit driver
|
||||
*
|
||||
* Copyright (c) 2018 BayLibre, SAS.
|
||||
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
||||
*/
|
||||
|
||||
#include <edid.h>
|
||||
#include "meson_vpu.h"
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/math64.h>
|
||||
|
||||
#define writel_bits(mask, val, addr) \
|
||||
writel((readl(addr) & ~(mask)) | (val), addr)
|
||||
|
||||
enum {
|
||||
MESON_VCLK_TARGET_CVBS = 0,
|
||||
MESON_VCLK_TARGET_HDMI = 1,
|
||||
MESON_VCLK_TARGET_DMT = 2,
|
||||
};
|
||||
|
||||
/* HHI Registers */
|
||||
#define HHI_VID_PLL_CLK_DIV 0x1a0 /* 0x68 offset in data sheet */
|
||||
#define VID_PLL_EN BIT(19)
|
||||
#define VID_PLL_BYPASS BIT(18)
|
||||
#define VID_PLL_PRESET BIT(15)
|
||||
#define HHI_VIID_CLK_DIV 0x128 /* 0x4a offset in data sheet */
|
||||
#define VCLK2_DIV_MASK 0xff
|
||||
#define VCLK2_DIV_EN BIT(16)
|
||||
#define VCLK2_DIV_RESET BIT(17)
|
||||
#define CTS_VDAC_SEL_MASK (0xf << 28)
|
||||
#define CTS_VDAC_SEL_SHIFT 28
|
||||
#define HHI_VIID_CLK_CNTL 0x12c /* 0x4b offset in data sheet */
|
||||
#define VCLK2_EN BIT(19)
|
||||
#define VCLK2_SEL_MASK (0x7 << 16)
|
||||
#define VCLK2_SEL_SHIFT 16
|
||||
#define VCLK2_SOFT_RESET BIT(15)
|
||||
#define VCLK2_DIV1_EN BIT(0)
|
||||
#define HHI_VID_CLK_DIV 0x164 /* 0x59 offset in data sheet */
|
||||
#define VCLK_DIV_MASK 0xff
|
||||
#define VCLK_DIV_EN BIT(16)
|
||||
#define VCLK_DIV_RESET BIT(17)
|
||||
#define CTS_ENCP_SEL_MASK (0xf << 24)
|
||||
#define CTS_ENCP_SEL_SHIFT 24
|
||||
#define CTS_ENCI_SEL_MASK (0xf << 28)
|
||||
#define CTS_ENCI_SEL_SHIFT 28
|
||||
#define HHI_VID_CLK_CNTL 0x17c /* 0x5f offset in data sheet */
|
||||
#define VCLK_EN BIT(19)
|
||||
#define VCLK_SEL_MASK (0x7 << 16)
|
||||
#define VCLK_SEL_SHIFT 16
|
||||
#define VCLK_SOFT_RESET BIT(15)
|
||||
#define VCLK_DIV1_EN BIT(0)
|
||||
#define VCLK_DIV2_EN BIT(1)
|
||||
#define VCLK_DIV4_EN BIT(2)
|
||||
#define VCLK_DIV6_EN BIT(3)
|
||||
#define VCLK_DIV12_EN BIT(4)
|
||||
#define HHI_VID_CLK_CNTL2 0x194 /* 0x65 offset in data sheet */
|
||||
#define CTS_ENCI_EN BIT(0)
|
||||
#define CTS_ENCP_EN BIT(2)
|
||||
#define CTS_VDAC_EN BIT(4)
|
||||
#define HDMI_TX_PIXEL_EN BIT(5)
|
||||
#define HHI_HDMI_CLK_CNTL 0x1cc /* 0x73 offset in data sheet */
|
||||
#define HDMI_TX_PIXEL_SEL_MASK (0xf << 16)
|
||||
#define HDMI_TX_PIXEL_SEL_SHIFT 16
|
||||
#define CTS_HDMI_SYS_SEL_MASK (0x7 << 9)
|
||||
#define CTS_HDMI_SYS_DIV_MASK (0x7f)
|
||||
#define CTS_HDMI_SYS_EN BIT(8)
|
||||
|
||||
#define HHI_HDMI_PLL_CNTL 0x320 /* 0xc8 offset in data sheet */
|
||||
#define HHI_HDMI_PLL_CNTL2 0x324 /* 0xc9 offset in data sheet */
|
||||
#define HHI_HDMI_PLL_CNTL3 0x328 /* 0xca offset in data sheet */
|
||||
#define HHI_HDMI_PLL_CNTL4 0x32C /* 0xcb offset in data sheet */
|
||||
#define HHI_HDMI_PLL_CNTL5 0x330 /* 0xcc offset in data sheet */
|
||||
#define HHI_HDMI_PLL_CNTL6 0x334 /* 0xcd offset in data sheet */
|
||||
|
||||
#define HDMI_PLL_RESET BIT(28)
|
||||
#define HDMI_PLL_LOCK BIT(31)
|
||||
|
||||
/* VID PLL Dividers */
|
||||
enum {
|
||||
VID_PLL_DIV_1 = 0,
|
||||
VID_PLL_DIV_2,
|
||||
VID_PLL_DIV_2p5,
|
||||
VID_PLL_DIV_3,
|
||||
VID_PLL_DIV_3p5,
|
||||
VID_PLL_DIV_3p75,
|
||||
VID_PLL_DIV_4,
|
||||
VID_PLL_DIV_5,
|
||||
VID_PLL_DIV_6,
|
||||
VID_PLL_DIV_6p25,
|
||||
VID_PLL_DIV_7,
|
||||
VID_PLL_DIV_7p5,
|
||||
VID_PLL_DIV_12,
|
||||
VID_PLL_DIV_14,
|
||||
VID_PLL_DIV_15,
|
||||
};
|
||||
|
||||
void meson_vid_pll_set(struct meson_vpu_priv *priv, unsigned int div)
|
||||
{
|
||||
unsigned int shift_val = 0;
|
||||
unsigned int shift_sel = 0;
|
||||
|
||||
/* Disable vid_pll output clock */
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV, VID_PLL_EN, 0);
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV, VID_PLL_PRESET, 0);
|
||||
|
||||
switch (div) {
|
||||
case VID_PLL_DIV_2:
|
||||
shift_val = 0x0aaa;
|
||||
shift_sel = 0;
|
||||
break;
|
||||
case VID_PLL_DIV_2p5:
|
||||
shift_val = 0x5294;
|
||||
shift_sel = 2;
|
||||
break;
|
||||
case VID_PLL_DIV_3:
|
||||
shift_val = 0x0db6;
|
||||
shift_sel = 0;
|
||||
break;
|
||||
case VID_PLL_DIV_3p5:
|
||||
shift_val = 0x36cc;
|
||||
shift_sel = 1;
|
||||
break;
|
||||
case VID_PLL_DIV_3p75:
|
||||
shift_val = 0x6666;
|
||||
shift_sel = 2;
|
||||
break;
|
||||
case VID_PLL_DIV_4:
|
||||
shift_val = 0x0ccc;
|
||||
shift_sel = 0;
|
||||
break;
|
||||
case VID_PLL_DIV_5:
|
||||
shift_val = 0x739c;
|
||||
shift_sel = 2;
|
||||
break;
|
||||
case VID_PLL_DIV_6:
|
||||
shift_val = 0x0e38;
|
||||
shift_sel = 0;
|
||||
break;
|
||||
case VID_PLL_DIV_6p25:
|
||||
shift_val = 0x0000;
|
||||
shift_sel = 3;
|
||||
break;
|
||||
case VID_PLL_DIV_7:
|
||||
shift_val = 0x3c78;
|
||||
shift_sel = 1;
|
||||
break;
|
||||
case VID_PLL_DIV_7p5:
|
||||
shift_val = 0x78f0;
|
||||
shift_sel = 2;
|
||||
break;
|
||||
case VID_PLL_DIV_12:
|
||||
shift_val = 0x0fc0;
|
||||
shift_sel = 0;
|
||||
break;
|
||||
case VID_PLL_DIV_14:
|
||||
shift_val = 0x3f80;
|
||||
shift_sel = 1;
|
||||
break;
|
||||
case VID_PLL_DIV_15:
|
||||
shift_val = 0x7f80;
|
||||
shift_sel = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (div == VID_PLL_DIV_1) {
|
||||
/* Enable vid_pll bypass to HDMI pll */
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV,
|
||||
VID_PLL_BYPASS, VID_PLL_BYPASS);
|
||||
} else {
|
||||
/* Disable Bypass */
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV,
|
||||
VID_PLL_BYPASS, 0);
|
||||
/* Clear sel */
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV,
|
||||
3 << 16, 0);
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV,
|
||||
VID_PLL_PRESET, 0);
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV,
|
||||
0x7fff, 0);
|
||||
|
||||
/* Setup sel and val */
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV,
|
||||
3 << 16, shift_sel << 16);
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV,
|
||||
VID_PLL_PRESET, VID_PLL_PRESET);
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV,
|
||||
0x7fff, shift_val);
|
||||
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV,
|
||||
VID_PLL_PRESET, 0);
|
||||
}
|
||||
|
||||
/* Enable the vid_pll output clock */
|
||||
hhi_update_bits(HHI_VID_PLL_CLK_DIV,
|
||||
VID_PLL_EN, VID_PLL_EN);
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup VCLK2 for 27MHz, and enable clocks for ENCI and VDAC
|
||||
*
|
||||
* TOFIX: Refactor into table to also handle HDMI frequency and paths
|
||||
*/
|
||||
static void meson_venci_cvbs_clock_config(struct meson_vpu_priv *priv)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
debug("%s:%d\n", __func__, __LINE__);
|
||||
|
||||
/* Setup PLL to output 1.485GHz */
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) {
|
||||
hhi_write(HHI_HDMI_PLL_CNTL, 0x5800023d);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL2, 0x00404e00);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL3, 0x0d5c5091);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL4, 0x801da72c);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL5, 0x71486980);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL6, 0x00000e55);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL, 0x4800023d);
|
||||
} else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
|
||||
meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL)) {
|
||||
hhi_write(HHI_HDMI_PLL_CNTL, 0x4000027b);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL2, 0x800cb300);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL3, 0xa6212844);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL4, 0x0c4d000c);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL5, 0x001fa729);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL6, 0x01a31500);
|
||||
|
||||
/* Reset PLL */
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL,
|
||||
HDMI_PLL_RESET, HDMI_PLL_RESET);
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL,
|
||||
HDMI_PLL_RESET, 0);
|
||||
}
|
||||
|
||||
debug("%s:%d\n", __func__, __LINE__);
|
||||
|
||||
/* Poll for lock bit */
|
||||
readl_poll_timeout(priv->hhi_base + HHI_HDMI_PLL_CNTL, val,
|
||||
(val & HDMI_PLL_LOCK), 10);
|
||||
|
||||
/* Disable VCLK2 */
|
||||
hhi_update_bits(HHI_VIID_CLK_CNTL, VCLK2_EN, 0);
|
||||
|
||||
/* Setup vid_pll to /1 */
|
||||
meson_vid_pll_set(priv, VID_PLL_DIV_1);
|
||||
|
||||
/* Setup the VCLK2 divider value to achieve 27MHz */
|
||||
hhi_update_bits(HHI_VIID_CLK_DIV,
|
||||
VCLK2_DIV_MASK, (55 - 1));
|
||||
|
||||
/* select vid_pll for vclk2 */
|
||||
hhi_update_bits(HHI_VIID_CLK_CNTL,
|
||||
VCLK2_SEL_MASK, (4 << VCLK2_SEL_SHIFT));
|
||||
/* enable vclk2 gate */
|
||||
hhi_update_bits(HHI_VIID_CLK_CNTL, VCLK2_EN, VCLK2_EN);
|
||||
|
||||
/* select vclk_div1 for enci */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCI_SEL_MASK, (8 << CTS_ENCI_SEL_SHIFT));
|
||||
/* select vclk_div1 for vdac */
|
||||
hhi_update_bits(HHI_VIID_CLK_DIV,
|
||||
CTS_VDAC_SEL_MASK, (8 << CTS_VDAC_SEL_SHIFT));
|
||||
|
||||
/* release vclk2_div_reset and enable vclk2_div */
|
||||
hhi_update_bits(HHI_VIID_CLK_DIV,
|
||||
VCLK2_DIV_EN | VCLK2_DIV_RESET, VCLK2_DIV_EN);
|
||||
|
||||
/* enable vclk2_div1 gate */
|
||||
hhi_update_bits(HHI_VIID_CLK_CNTL,
|
||||
VCLK2_DIV1_EN, VCLK2_DIV1_EN);
|
||||
|
||||
/* reset vclk2 */
|
||||
hhi_update_bits(HHI_VIID_CLK_CNTL,
|
||||
VCLK2_SOFT_RESET, VCLK2_SOFT_RESET);
|
||||
hhi_update_bits(HHI_VIID_CLK_CNTL,
|
||||
VCLK2_SOFT_RESET, 0);
|
||||
|
||||
/* enable enci_clk */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL2,
|
||||
CTS_ENCI_EN, CTS_ENCI_EN);
|
||||
/* enable vdac_clk */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL2,
|
||||
CTS_VDAC_EN, CTS_VDAC_EN);
|
||||
|
||||
debug("%s:%d\n", __func__, __LINE__);
|
||||
}
|
||||
|
||||
enum {
|
||||
/* PLL O1 O2 O3 VP DV EN TX */
|
||||
/* 4320 /4 /4 /1 /5 /1 => /2 /2 */
|
||||
MESON_VCLK_HDMI_ENCI_54000 = 1,
|
||||
/* 4320 /4 /4 /1 /5 /1 => /1 /2 */
|
||||
MESON_VCLK_HDMI_DDR_54000,
|
||||
/* 2970 /4 /1 /1 /5 /1 => /1 /2 */
|
||||
MESON_VCLK_HDMI_DDR_148500,
|
||||
/* 2970 /2 /2 /2 /5 /1 => /1 /1 */
|
||||
MESON_VCLK_HDMI_74250,
|
||||
/* 2970 /1 /2 /2 /5 /1 => /1 /1 */
|
||||
MESON_VCLK_HDMI_148500,
|
||||
/* 2970 /1 /1 /1 /5 /2 => /1 /1 */
|
||||
MESON_VCLK_HDMI_297000,
|
||||
/* 5940 /1 /1 /2 /5 /1 => /1 /1 */
|
||||
MESON_VCLK_HDMI_594000
|
||||
};
|
||||
|
||||
struct meson_vclk_params {
|
||||
unsigned int pll_base_freq;
|
||||
unsigned int pll_od1;
|
||||
unsigned int pll_od2;
|
||||
unsigned int pll_od3;
|
||||
unsigned int vid_pll_div;
|
||||
unsigned int vclk_div;
|
||||
} params[] = {
|
||||
[MESON_VCLK_HDMI_ENCI_54000] = {
|
||||
.pll_base_freq = 4320000,
|
||||
.pll_od1 = 4,
|
||||
.pll_od2 = 4,
|
||||
.pll_od3 = 1,
|
||||
.vid_pll_div = VID_PLL_DIV_5,
|
||||
.vclk_div = 1,
|
||||
},
|
||||
[MESON_VCLK_HDMI_DDR_54000] = {
|
||||
.pll_base_freq = 4320000,
|
||||
.pll_od1 = 4,
|
||||
.pll_od2 = 4,
|
||||
.pll_od3 = 1,
|
||||
.vid_pll_div = VID_PLL_DIV_5,
|
||||
.vclk_div = 1,
|
||||
},
|
||||
[MESON_VCLK_HDMI_DDR_148500] = {
|
||||
.pll_base_freq = 2970000,
|
||||
.pll_od1 = 4,
|
||||
.pll_od2 = 1,
|
||||
.pll_od3 = 1,
|
||||
.vid_pll_div = VID_PLL_DIV_5,
|
||||
.vclk_div = 1,
|
||||
},
|
||||
[MESON_VCLK_HDMI_74250] = {
|
||||
.pll_base_freq = 2970000,
|
||||
.pll_od1 = 2,
|
||||
.pll_od2 = 2,
|
||||
.pll_od3 = 2,
|
||||
.vid_pll_div = VID_PLL_DIV_5,
|
||||
.vclk_div = 1,
|
||||
},
|
||||
[MESON_VCLK_HDMI_148500] = {
|
||||
.pll_base_freq = 2970000,
|
||||
.pll_od1 = 1,
|
||||
.pll_od2 = 2,
|
||||
.pll_od3 = 2,
|
||||
.vid_pll_div = VID_PLL_DIV_5,
|
||||
.vclk_div = 1,
|
||||
},
|
||||
[MESON_VCLK_HDMI_297000] = {
|
||||
.pll_base_freq = 2970000,
|
||||
.pll_od1 = 1,
|
||||
.pll_od2 = 1,
|
||||
.pll_od3 = 1,
|
||||
.vid_pll_div = VID_PLL_DIV_5,
|
||||
.vclk_div = 2,
|
||||
},
|
||||
[MESON_VCLK_HDMI_594000] = {
|
||||
.pll_base_freq = 5940000,
|
||||
.pll_od1 = 1,
|
||||
.pll_od2 = 1,
|
||||
.pll_od3 = 2,
|
||||
.vid_pll_div = VID_PLL_DIV_5,
|
||||
.vclk_div = 1,
|
||||
},
|
||||
};
|
||||
|
||||
static inline unsigned int pll_od_to_reg(unsigned int od)
|
||||
{
|
||||
switch (od) {
|
||||
case 1:
|
||||
return 0;
|
||||
case 2:
|
||||
return 1;
|
||||
case 4:
|
||||
return 2;
|
||||
case 8:
|
||||
return 3;
|
||||
}
|
||||
|
||||
/* Invalid */
|
||||
return 0;
|
||||
}
|
||||
|
||||
void meson_hdmi_pll_set_params(struct meson_vpu_priv *priv, unsigned int m,
|
||||
unsigned int frac, unsigned int od1,
|
||||
unsigned int od2, unsigned int od3)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) {
|
||||
hhi_write(HHI_HDMI_PLL_CNTL, 0x58000200 | m);
|
||||
if (frac)
|
||||
hhi_write(HHI_HDMI_PLL_CNTL2,
|
||||
0x00004000 | frac);
|
||||
else
|
||||
hhi_write(HHI_HDMI_PLL_CNTL2,
|
||||
0x00000000);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL3, 0x0d5c5091);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL4, 0x801da72c);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL5, 0x71486980);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL6, 0x00000e55);
|
||||
|
||||
/* Enable and unreset */
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL,
|
||||
0x7 << 28, 0x4 << 28);
|
||||
|
||||
/* Poll for lock bit */
|
||||
readl_poll_timeout(priv->hhi_base + HHI_HDMI_PLL_CNTL, val,
|
||||
(val & HDMI_PLL_LOCK), 10);
|
||||
} else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
|
||||
meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL)) {
|
||||
hhi_write(HHI_HDMI_PLL_CNTL, 0x40000200 | m);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL2, 0x800cb000 | frac);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL3, 0x860f30c4);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL4, 0x0c8e0000);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL5, 0x001fa729);
|
||||
hhi_write(HHI_HDMI_PLL_CNTL6, 0x01a31500);
|
||||
|
||||
/* Reset PLL */
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL,
|
||||
HDMI_PLL_RESET, HDMI_PLL_RESET);
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL,
|
||||
HDMI_PLL_RESET, 0);
|
||||
|
||||
/* Poll for lock bit */
|
||||
readl_poll_timeout(priv->hhi_base + HHI_HDMI_PLL_CNTL, val,
|
||||
(val & HDMI_PLL_LOCK), 10);
|
||||
}
|
||||
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB))
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL2,
|
||||
3 << 16, pll_od_to_reg(od1) << 16);
|
||||
else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
|
||||
meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL))
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL3,
|
||||
3 << 21, pll_od_to_reg(od1) << 21);
|
||||
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB))
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL2,
|
||||
3 << 22, pll_od_to_reg(od2) << 22);
|
||||
else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
|
||||
meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL))
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL3,
|
||||
3 << 23, pll_od_to_reg(od2) << 23);
|
||||
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB))
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL2,
|
||||
3 << 18, pll_od_to_reg(od3) << 18);
|
||||
else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
|
||||
meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL))
|
||||
hhi_update_bits(HHI_HDMI_PLL_CNTL3,
|
||||
3 << 19, pll_od_to_reg(od3) << 19);
|
||||
}
|
||||
|
||||
#define XTAL_FREQ 24000
|
||||
|
||||
static unsigned int meson_hdmi_pll_get_m(struct meson_vpu_priv *priv,
|
||||
unsigned int pll_freq)
|
||||
{
|
||||
/* The GXBB PLL has a /2 pre-multiplier */
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB))
|
||||
pll_freq /= 2;
|
||||
|
||||
return pll_freq / XTAL_FREQ;
|
||||
}
|
||||
|
||||
#define HDMI_FRAC_MAX_GXBB 4096
|
||||
#define HDMI_FRAC_MAX_GXL 1024
|
||||
|
||||
static unsigned int meson_hdmi_pll_get_frac(struct meson_vpu_priv *priv,
|
||||
unsigned int m,
|
||||
unsigned int pll_freq)
|
||||
{
|
||||
unsigned int parent_freq = XTAL_FREQ;
|
||||
unsigned int frac_max = HDMI_FRAC_MAX_GXL;
|
||||
unsigned int frac_m;
|
||||
unsigned int frac;
|
||||
|
||||
/* The GXBB PLL has a /2 pre-multiplier and a larger FRAC width */
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) {
|
||||
frac_max = HDMI_FRAC_MAX_GXBB;
|
||||
parent_freq *= 2;
|
||||
}
|
||||
|
||||
/* We can have a perfect match !*/
|
||||
if (pll_freq / m == parent_freq &&
|
||||
pll_freq % m == 0)
|
||||
return 0;
|
||||
|
||||
frac = div_u64((u64)pll_freq * (u64)frac_max, parent_freq);
|
||||
frac_m = m * frac_max;
|
||||
if (frac_m > frac)
|
||||
return frac_max;
|
||||
frac -= frac_m;
|
||||
|
||||
return min((u16)frac, (u16)(frac_max - 1));
|
||||
}
|
||||
|
||||
static bool meson_hdmi_pll_validate_params(struct meson_vpu_priv *priv,
|
||||
unsigned int m,
|
||||
unsigned int frac)
|
||||
{
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) {
|
||||
/* Empiric supported min/max dividers */
|
||||
if (m < 53 || m > 123)
|
||||
return false;
|
||||
if (frac >= HDMI_FRAC_MAX_GXBB)
|
||||
return false;
|
||||
} else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
|
||||
meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL)) {
|
||||
/* Empiric supported min/max dividers */
|
||||
if (m < 106 || m > 247)
|
||||
return false;
|
||||
if (frac >= HDMI_FRAC_MAX_GXL)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool meson_hdmi_pll_find_params(struct meson_vpu_priv *priv,
|
||||
unsigned int freq,
|
||||
unsigned int *m,
|
||||
unsigned int *frac,
|
||||
unsigned int *od)
|
||||
{
|
||||
/* Cycle from /16 to /2 */
|
||||
for (*od = 16 ; *od > 1 ; *od >>= 1) {
|
||||
*m = meson_hdmi_pll_get_m(priv, freq * *od);
|
||||
if (!*m)
|
||||
continue;
|
||||
*frac = meson_hdmi_pll_get_frac(priv, *m, freq * *od);
|
||||
|
||||
debug("PLL params for %dkHz: m=%x frac=%x od=%d\n",
|
||||
freq, *m, *frac, *od);
|
||||
|
||||
if (meson_hdmi_pll_validate_params(priv, *m, *frac))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* pll_freq is the frequency after the OD dividers */
|
||||
bool meson_vclk_dmt_supported_freq(struct meson_vpu_priv *priv,
|
||||
unsigned int freq)
|
||||
{
|
||||
unsigned int od, m, frac;
|
||||
|
||||
/* In DMT mode, path after PLL is always /10 */
|
||||
freq *= 10;
|
||||
|
||||
if (meson_hdmi_pll_find_params(priv, freq, &m, &frac, &od))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* pll_freq is the frequency after the OD dividers */
|
||||
static void meson_hdmi_pll_generic_set(struct meson_vpu_priv *priv,
|
||||
unsigned int pll_freq)
|
||||
{
|
||||
unsigned int od, m, frac, od1, od2, od3;
|
||||
|
||||
if (meson_hdmi_pll_find_params(priv, pll_freq, &m, &frac, &od)) {
|
||||
od3 = 1;
|
||||
if (od < 4) {
|
||||
od1 = 2;
|
||||
od2 = 1;
|
||||
} else {
|
||||
od2 = od / 4;
|
||||
od1 = od / od2;
|
||||
}
|
||||
|
||||
debug("PLL params for %dkHz: m=%x frac=%x od=%d/%d/%d\n",
|
||||
pll_freq, m, frac, od1, od2, od3);
|
||||
|
||||
meson_hdmi_pll_set_params(priv, m, frac, od1, od2, od3);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Fatal, unable to find parameters for PLL freq %d\n",
|
||||
pll_freq);
|
||||
}
|
||||
|
||||
static void
|
||||
meson_vclk_set(struct meson_vpu_priv *priv, unsigned int pll_base_freq,
|
||||
unsigned int od1, unsigned int od2, unsigned int od3,
|
||||
unsigned int vid_pll_div, unsigned int vclk_div,
|
||||
unsigned int hdmi_tx_div, unsigned int venc_div,
|
||||
bool hdmi_use_enci)
|
||||
{
|
||||
/* Set HDMI-TX sys clock */
|
||||
hhi_update_bits(HHI_HDMI_CLK_CNTL,
|
||||
CTS_HDMI_SYS_SEL_MASK, 0);
|
||||
hhi_update_bits(HHI_HDMI_CLK_CNTL,
|
||||
CTS_HDMI_SYS_DIV_MASK, 0);
|
||||
hhi_update_bits(HHI_HDMI_CLK_CNTL,
|
||||
CTS_HDMI_SYS_EN, CTS_HDMI_SYS_EN);
|
||||
|
||||
/* Set HDMI PLL rate */
|
||||
if (!od1 && !od2 && !od3) {
|
||||
meson_hdmi_pll_generic_set(priv, pll_base_freq);
|
||||
} else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) {
|
||||
switch (pll_base_freq) {
|
||||
case 2970000:
|
||||
meson_hdmi_pll_set_params(priv, 0x3d, 0xe00,
|
||||
od1, od2, od3);
|
||||
break;
|
||||
case 4320000:
|
||||
meson_hdmi_pll_set_params(priv, 0x5a, 0,
|
||||
od1, od2, od3);
|
||||
break;
|
||||
case 5940000:
|
||||
meson_hdmi_pll_set_params(priv, 0x7b, 0xc00,
|
||||
od1, od2, od3);
|
||||
break;
|
||||
}
|
||||
} else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
|
||||
meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL)) {
|
||||
switch (pll_base_freq) {
|
||||
case 2970000:
|
||||
meson_hdmi_pll_set_params(priv, 0x7b, 0x300,
|
||||
od1, od2, od3);
|
||||
break;
|
||||
case 4320000:
|
||||
meson_hdmi_pll_set_params(priv, 0xb4, 0,
|
||||
od1, od2, od3);
|
||||
break;
|
||||
case 5940000:
|
||||
meson_hdmi_pll_set_params(priv, 0xf7, 0x200,
|
||||
od1, od2, od3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup vid_pll divider */
|
||||
meson_vid_pll_set(priv, vid_pll_div);
|
||||
|
||||
/* Set VCLK div */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_SEL_MASK, 0);
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
VCLK_DIV_MASK, vclk_div - 1);
|
||||
|
||||
/* Set HDMI-TX source */
|
||||
switch (hdmi_tx_div) {
|
||||
case 1:
|
||||
/* enable vclk_div1 gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_DIV1_EN, VCLK_DIV1_EN);
|
||||
|
||||
/* select vclk_div1 for HDMI-TX */
|
||||
hhi_update_bits(HHI_HDMI_CLK_CNTL,
|
||||
HDMI_TX_PIXEL_SEL_MASK, 0);
|
||||
break;
|
||||
case 2:
|
||||
/* enable vclk_div2 gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_DIV2_EN, VCLK_DIV2_EN);
|
||||
|
||||
/* select vclk_div2 for HDMI-TX */
|
||||
hhi_update_bits(HHI_HDMI_CLK_CNTL,
|
||||
HDMI_TX_PIXEL_SEL_MASK,
|
||||
1 << HDMI_TX_PIXEL_SEL_SHIFT);
|
||||
break;
|
||||
case 4:
|
||||
/* enable vclk_div4 gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_DIV4_EN, VCLK_DIV4_EN);
|
||||
|
||||
/* select vclk_div4 for HDMI-TX */
|
||||
hhi_update_bits(HHI_HDMI_CLK_CNTL,
|
||||
HDMI_TX_PIXEL_SEL_MASK,
|
||||
2 << HDMI_TX_PIXEL_SEL_SHIFT);
|
||||
break;
|
||||
case 6:
|
||||
/* enable vclk_div6 gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_DIV6_EN, VCLK_DIV6_EN);
|
||||
|
||||
/* select vclk_div6 for HDMI-TX */
|
||||
hhi_update_bits(HHI_HDMI_CLK_CNTL,
|
||||
HDMI_TX_PIXEL_SEL_MASK,
|
||||
3 << HDMI_TX_PIXEL_SEL_SHIFT);
|
||||
break;
|
||||
case 12:
|
||||
/* enable vclk_div12 gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_DIV12_EN, VCLK_DIV12_EN);
|
||||
|
||||
/* select vclk_div12 for HDMI-TX */
|
||||
hhi_update_bits(HHI_HDMI_CLK_CNTL,
|
||||
HDMI_TX_PIXEL_SEL_MASK,
|
||||
4 << HDMI_TX_PIXEL_SEL_SHIFT);
|
||||
break;
|
||||
}
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL2,
|
||||
HDMI_TX_PIXEL_EN, HDMI_TX_PIXEL_EN);
|
||||
|
||||
/* Set ENCI/ENCP Source */
|
||||
switch (venc_div) {
|
||||
case 1:
|
||||
/* enable vclk_div1 gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_DIV1_EN, VCLK_DIV1_EN);
|
||||
|
||||
if (hdmi_use_enci)
|
||||
/* select vclk_div1 for enci */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCI_SEL_MASK, 0);
|
||||
else
|
||||
/* select vclk_div1 for encp */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCP_SEL_MASK, 0);
|
||||
break;
|
||||
case 2:
|
||||
/* enable vclk_div2 gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_DIV2_EN, VCLK_DIV2_EN);
|
||||
|
||||
if (hdmi_use_enci)
|
||||
/* select vclk_div2 for enci */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCI_SEL_MASK,
|
||||
1 << CTS_ENCI_SEL_SHIFT);
|
||||
else
|
||||
/* select vclk_div2 for encp */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCP_SEL_MASK,
|
||||
1 << CTS_ENCP_SEL_SHIFT);
|
||||
break;
|
||||
case 4:
|
||||
/* enable vclk_div4 gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_DIV4_EN, VCLK_DIV4_EN);
|
||||
|
||||
if (hdmi_use_enci)
|
||||
/* select vclk_div4 for enci */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCI_SEL_MASK,
|
||||
2 << CTS_ENCI_SEL_SHIFT);
|
||||
else
|
||||
/* select vclk_div4 for encp */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCP_SEL_MASK,
|
||||
2 << CTS_ENCP_SEL_SHIFT);
|
||||
break;
|
||||
case 6:
|
||||
/* enable vclk_div6 gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_DIV6_EN, VCLK_DIV6_EN);
|
||||
|
||||
if (hdmi_use_enci)
|
||||
/* select vclk_div6 for enci */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCI_SEL_MASK,
|
||||
3 << CTS_ENCI_SEL_SHIFT);
|
||||
else
|
||||
/* select vclk_div6 for encp */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCP_SEL_MASK,
|
||||
3 << CTS_ENCP_SEL_SHIFT);
|
||||
break;
|
||||
case 12:
|
||||
/* enable vclk_div12 gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL,
|
||||
VCLK_DIV12_EN, VCLK_DIV12_EN);
|
||||
|
||||
if (hdmi_use_enci)
|
||||
/* select vclk_div12 for enci */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCI_SEL_MASK,
|
||||
4 << CTS_ENCI_SEL_SHIFT);
|
||||
else
|
||||
/* select vclk_div12 for encp */
|
||||
hhi_update_bits(HHI_VID_CLK_DIV,
|
||||
CTS_ENCP_SEL_MASK,
|
||||
4 << CTS_ENCP_SEL_SHIFT);
|
||||
break;
|
||||
}
|
||||
|
||||
if (hdmi_use_enci)
|
||||
/* Enable ENCI clock gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL2,
|
||||
CTS_ENCI_EN, CTS_ENCI_EN);
|
||||
else
|
||||
/* Enable ENCP clock gate */
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL2,
|
||||
CTS_ENCP_EN, CTS_ENCP_EN);
|
||||
|
||||
hhi_update_bits(HHI_VID_CLK_CNTL, VCLK_EN, VCLK_EN);
|
||||
}
|
||||
|
||||
static void meson_vclk_setup(struct meson_vpu_priv *priv, unsigned int target,
|
||||
unsigned int vclk_freq, unsigned int venc_freq,
|
||||
unsigned int dac_freq, bool hdmi_use_enci)
|
||||
{
|
||||
unsigned int freq;
|
||||
unsigned int hdmi_tx_div;
|
||||
unsigned int venc_div;
|
||||
|
||||
if (target == MESON_VCLK_TARGET_CVBS) {
|
||||
meson_venci_cvbs_clock_config(priv);
|
||||
return;
|
||||
} else if (target == MESON_VCLK_TARGET_DMT) {
|
||||
/* The DMT clock path is fixed after the PLL:
|
||||
* - automatic PLL freq + OD management
|
||||
* - vid_pll_div = VID_PLL_DIV_5
|
||||
* - vclk_div = 2
|
||||
* - hdmi_tx_div = 1
|
||||
* - venc_div = 1
|
||||
* - encp encoder
|
||||
*/
|
||||
meson_vclk_set(priv, vclk_freq * 10, 0, 0, 0,
|
||||
VID_PLL_DIV_5, 2, 1, 1, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
hdmi_tx_div = vclk_freq / dac_freq;
|
||||
|
||||
if (hdmi_tx_div == 0) {
|
||||
printf("Fatal Error, invalid HDMI-TX freq %d\n",
|
||||
dac_freq);
|
||||
return;
|
||||
}
|
||||
|
||||
venc_div = vclk_freq / venc_freq;
|
||||
|
||||
if (venc_div == 0) {
|
||||
printf("Fatal Error, invalid HDMI venc freq %d\n",
|
||||
venc_freq);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (vclk_freq) {
|
||||
case 54000:
|
||||
if (hdmi_use_enci)
|
||||
freq = MESON_VCLK_HDMI_ENCI_54000;
|
||||
else
|
||||
freq = MESON_VCLK_HDMI_DDR_54000;
|
||||
break;
|
||||
case 74250:
|
||||
freq = MESON_VCLK_HDMI_74250;
|
||||
break;
|
||||
case 148500:
|
||||
if (dac_freq != 148500)
|
||||
freq = MESON_VCLK_HDMI_DDR_148500;
|
||||
else
|
||||
freq = MESON_VCLK_HDMI_148500;
|
||||
break;
|
||||
case 297000:
|
||||
freq = MESON_VCLK_HDMI_297000;
|
||||
break;
|
||||
case 594000:
|
||||
freq = MESON_VCLK_HDMI_594000;
|
||||
break;
|
||||
default:
|
||||
printf("Fatal Error, invalid HDMI vclk freq %d\n",
|
||||
vclk_freq);
|
||||
return;
|
||||
}
|
||||
|
||||
meson_vclk_set(priv, params[freq].pll_base_freq,
|
||||
params[freq].pll_od1, params[freq].pll_od2,
|
||||
params[freq].pll_od3, params[freq].vid_pll_div,
|
||||
params[freq].vclk_div, hdmi_tx_div, venc_div,
|
||||
hdmi_use_enci);
|
||||
}
|
||||
|
||||
void meson_vpu_setup_vclk(struct udevice *dev,
|
||||
const struct display_timing *mode, bool is_cvbs)
|
||||
{
|
||||
struct meson_vpu_priv *priv = dev_get_priv(dev);
|
||||
unsigned int vclk_freq;
|
||||
|
||||
if (is_cvbs)
|
||||
return meson_vclk_setup(priv, MESON_VCLK_TARGET_CVBS,
|
||||
0, 0, 0, false);
|
||||
|
||||
vclk_freq = mode->pixelclock.typ / 1000;
|
||||
|
||||
return meson_vclk_setup(priv, MESON_VCLK_TARGET_DMT,
|
||||
vclk_freq, vclk_freq, vclk_freq, false);
|
||||
}
|
1464
drivers/video/meson/meson_venc.c
Normal file
1464
drivers/video/meson/meson_venc.c
Normal file
File diff suppressed because it is too large
Load Diff
211
drivers/video/meson/meson_vpu.c
Normal file
211
drivers/video/meson/meson_vpu.c
Normal file
@ -0,0 +1,211 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Amlogic Meson Video Processing Unit driver
|
||||
*
|
||||
* Copyright (c) 2018 BayLibre, SAS.
|
||||
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
||||
*/
|
||||
|
||||
#include "meson_vpu.h"
|
||||
#include <power-domain.h>
|
||||
#include <efi_loader.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <dm/uclass-internal.h>
|
||||
#include <fdt_support.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <asm/arch/mem.h>
|
||||
#include "meson_registers.h"
|
||||
#include "simplefb_common.h"
|
||||
|
||||
#define MESON_VPU_OVERSCAN SZ_64K
|
||||
|
||||
/* Static variable for use in meson_vpu_rsv_fb() */
|
||||
static struct meson_framebuffer {
|
||||
u64 base;
|
||||
u64 fb_size;
|
||||
unsigned int xsize;
|
||||
unsigned int ysize;
|
||||
bool is_cvbs;
|
||||
} meson_fb = { 0 };
|
||||
|
||||
static int meson_vpu_setup_mode(struct udevice *dev, struct udevice *disp)
|
||||
{
|
||||
struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev);
|
||||
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
|
||||
struct display_timing timing;
|
||||
bool is_cvbs = false;
|
||||
int ret = 0;
|
||||
|
||||
if (disp) {
|
||||
ret = display_read_timing(disp, &timing);
|
||||
if (ret) {
|
||||
debug("%s: Failed to read timings\n", __func__);
|
||||
goto cvbs;
|
||||
}
|
||||
|
||||
uc_priv->xsize = timing.hactive.typ;
|
||||
uc_priv->ysize = timing.vactive.typ;
|
||||
|
||||
ret = display_enable(disp, 0, &timing);
|
||||
if (ret)
|
||||
goto cvbs;
|
||||
} else {
|
||||
cvbs:
|
||||
/* CVBS has a fixed 720x480i (NTSC) and 720x576i (PAL) */
|
||||
is_cvbs = true;
|
||||
timing.flags = DISPLAY_FLAGS_INTERLACED;
|
||||
uc_priv->xsize = 720;
|
||||
uc_priv->ysize = 576;
|
||||
}
|
||||
|
||||
uc_priv->bpix = VPU_MAX_LOG2_BPP;
|
||||
|
||||
meson_fb.is_cvbs = is_cvbs;
|
||||
meson_fb.xsize = uc_priv->xsize;
|
||||
meson_fb.ysize = uc_priv->ysize;
|
||||
|
||||
/* Move the framebuffer to the end of addressable ram */
|
||||
meson_fb.fb_size = ALIGN(meson_fb.xsize * meson_fb.ysize *
|
||||
((1 << VPU_MAX_LOG2_BPP) / 8) +
|
||||
MESON_VPU_OVERSCAN, EFI_PAGE_SIZE);
|
||||
meson_fb.base = gd->bd->bi_dram[0].start +
|
||||
gd->bd->bi_dram[0].size - meson_fb.fb_size;
|
||||
|
||||
/* Override the framebuffer address */
|
||||
uc_plat->base = meson_fb.base;
|
||||
|
||||
meson_vpu_setup_plane(dev, timing.flags & DISPLAY_FLAGS_INTERLACED);
|
||||
meson_vpu_setup_venc(dev, &timing, is_cvbs);
|
||||
meson_vpu_setup_vclk(dev, &timing, is_cvbs);
|
||||
|
||||
video_set_flush_dcache(dev, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id meson_vpu_ids[] = {
|
||||
{ .compatible = "amlogic,meson-gxbb-vpu", .data = VPU_COMPATIBLE_GXBB },
|
||||
{ .compatible = "amlogic,meson-gxl-vpu", .data = VPU_COMPATIBLE_GXL },
|
||||
{ .compatible = "amlogic,meson-gxm-vpu", .data = VPU_COMPATIBLE_GXM },
|
||||
{ }
|
||||
};
|
||||
|
||||
static int meson_vpu_probe(struct udevice *dev)
|
||||
{
|
||||
struct meson_vpu_priv *priv = dev_get_priv(dev);
|
||||
struct power_domain pd;
|
||||
struct udevice *disp;
|
||||
int ret;
|
||||
|
||||
/* Before relocation we don't need to do anything */
|
||||
if (!(gd->flags & GD_FLG_RELOC))
|
||||
return 0;
|
||||
|
||||
priv->dev = dev;
|
||||
|
||||
priv->io_base = dev_remap_addr_index(dev, 0);
|
||||
if (!priv->io_base)
|
||||
return -EINVAL;
|
||||
|
||||
priv->hhi_base = dev_remap_addr_index(dev, 1);
|
||||
if (!priv->hhi_base)
|
||||
return -EINVAL;
|
||||
|
||||
priv->dmc_base = dev_remap_addr_index(dev, 2);
|
||||
if (!priv->dmc_base)
|
||||
return -EINVAL;
|
||||
|
||||
ret = power_domain_get(dev, &pd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = power_domain_on(&pd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
meson_vpu_init(dev);
|
||||
|
||||
/* probe the display */
|
||||
ret = uclass_get_device(UCLASS_DISPLAY, 0, &disp);
|
||||
|
||||
return meson_vpu_setup_mode(dev, ret ? NULL : disp);
|
||||
}
|
||||
|
||||
static int meson_vpu_bind(struct udevice *dev)
|
||||
{
|
||||
struct video_uc_platdata *plat = dev_get_uclass_platdata(dev);
|
||||
|
||||
plat->size = VPU_MAX_WIDTH * VPU_MAX_HEIGHT *
|
||||
(1 << VPU_MAX_LOG2_BPP) / 8;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_VIDEO_DT_SIMPLEFB)
|
||||
static void meson_vpu_setup_simplefb(void *fdt)
|
||||
{
|
||||
const char *pipeline = NULL;
|
||||
u64 mem_start, mem_size;
|
||||
int offset, ret;
|
||||
|
||||
if (meson_fb.is_cvbs)
|
||||
pipeline = "vpu-cvbs";
|
||||
else
|
||||
pipeline = "vpu-hdmi";
|
||||
|
||||
offset = meson_simplefb_fdt_match(fdt, pipeline);
|
||||
if (offset < 0) {
|
||||
eprintf("Cannot setup simplefb: node not found\n");
|
||||
|
||||
/* If simplefb is missing, add it as reserved memory */
|
||||
meson_board_add_reserved_memory(fdt, meson_fb.base,
|
||||
meson_fb.fb_size);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* SimpleFB will try to iomap the framebuffer, so we can't use
|
||||
* fdt_add_mem_rsv on the memory area. Instead, the FB is stored
|
||||
* at the end of the RAM and we strip this portion from the kernel
|
||||
* allowed region
|
||||
*/
|
||||
mem_start = gd->bd->bi_dram[0].start;
|
||||
mem_size = gd->bd->bi_dram[0].size - meson_fb.fb_size;
|
||||
ret = fdt_fixup_memory_banks(fdt, &mem_start, &mem_size, 1);
|
||||
if (ret) {
|
||||
eprintf("Cannot setup simplefb: Error reserving memory\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = fdt_setup_simplefb_node(fdt, offset, meson_fb.base,
|
||||
meson_fb.xsize, meson_fb.ysize,
|
||||
meson_fb.xsize * 4, "x8r8g8b8");
|
||||
if (ret)
|
||||
eprintf("Cannot setup simplefb: Error setting properties\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
void meson_vpu_rsv_fb(void *fdt)
|
||||
{
|
||||
if (!meson_fb.base || !meson_fb.xsize || !meson_fb.ysize)
|
||||
return;
|
||||
|
||||
#if defined(CONFIG_EFI_LOADER)
|
||||
efi_add_memory_map(meson_fb.base, meson_fb.fb_size >> EFI_PAGE_SHIFT,
|
||||
EFI_RESERVED_MEMORY_TYPE, false);
|
||||
#endif
|
||||
#if defined(CONFIG_VIDEO_DT_SIMPLEFB)
|
||||
meson_vpu_setup_simplefb(fdt);
|
||||
#endif
|
||||
}
|
||||
|
||||
U_BOOT_DRIVER(meson_vpu) = {
|
||||
.name = "meson_vpu",
|
||||
.id = UCLASS_VIDEO,
|
||||
.of_match = meson_vpu_ids,
|
||||
.probe = meson_vpu_probe,
|
||||
.bind = meson_vpu_bind,
|
||||
.priv_auto_alloc_size = sizeof(struct meson_vpu_priv),
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
97
drivers/video/meson/meson_vpu.h
Normal file
97
drivers/video/meson/meson_vpu.h
Normal file
@ -0,0 +1,97 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Amlogic Meson Video Processing Unit driver
|
||||
*
|
||||
* Copyright (c) 2018 BayLibre, SAS.
|
||||
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
||||
*/
|
||||
|
||||
#ifndef __MESON_VPU_H__
|
||||
#define __MESON_VPU_H__
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <video.h>
|
||||
#include <display.h>
|
||||
#include <linux/io.h>
|
||||
#include "meson_registers.h"
|
||||
|
||||
enum {
|
||||
/* Maximum size we support */
|
||||
VPU_MAX_WIDTH = 3840,
|
||||
VPU_MAX_HEIGHT = 2160,
|
||||
VPU_MAX_LOG2_BPP = VIDEO_BPP32,
|
||||
};
|
||||
|
||||
enum vpu_compatible {
|
||||
VPU_COMPATIBLE_GXBB = 0,
|
||||
VPU_COMPATIBLE_GXL = 1,
|
||||
VPU_COMPATIBLE_GXM = 2,
|
||||
};
|
||||
|
||||
struct meson_vpu_priv {
|
||||
struct udevice *dev;
|
||||
void __iomem *io_base;
|
||||
void __iomem *hhi_base;
|
||||
void __iomem *dmc_base;
|
||||
};
|
||||
|
||||
static inline bool meson_vpu_is_compatible(struct meson_vpu_priv *priv,
|
||||
enum vpu_compatible family)
|
||||
{
|
||||
enum vpu_compatible compat = dev_get_driver_data(priv->dev);
|
||||
|
||||
return compat == family;
|
||||
}
|
||||
|
||||
#define hhi_update_bits(offset, mask, value) \
|
||||
writel_bits(mask, value, priv->hhi_base + offset)
|
||||
|
||||
#define hhi_write(offset, value) \
|
||||
writel(value, priv->hhi_base + offset)
|
||||
|
||||
#define hhi_read(offset) \
|
||||
readl(priv->hhi_base + offset)
|
||||
|
||||
#define dmc_update_bits(offset, mask, value) \
|
||||
writel_bits(mask, value, priv->dmc_base + offset)
|
||||
|
||||
#define dmc_write(offset, value) \
|
||||
writel(value, priv->dmc_base + offset)
|
||||
|
||||
#define dmc_read(offset) \
|
||||
readl(priv->dmc_base + offset)
|
||||
|
||||
#define MESON_CANVAS_ID_OSD1 0x4e
|
||||
|
||||
/* Canvas configuration. */
|
||||
#define MESON_CANVAS_WRAP_NONE 0x00
|
||||
#define MESON_CANVAS_WRAP_X 0x01
|
||||
#define MESON_CANVAS_WRAP_Y 0x02
|
||||
|
||||
#define MESON_CANVAS_BLKMODE_LINEAR 0x00
|
||||
#define MESON_CANVAS_BLKMODE_32x32 0x01
|
||||
#define MESON_CANVAS_BLKMODE_64x64 0x02
|
||||
|
||||
void meson_canvas_setup(struct meson_vpu_priv *priv,
|
||||
u32 canvas_index, u32 addr,
|
||||
u32 stride, u32 height,
|
||||
unsigned int wrap,
|
||||
unsigned int blkmode);
|
||||
|
||||
/* Mux VIU/VPP to ENCI */
|
||||
#define MESON_VIU_VPP_MUX_ENCI 0x5
|
||||
/* Mux VIU/VPP to ENCP */
|
||||
#define MESON_VIU_VPP_MUX_ENCP 0xA
|
||||
|
||||
void meson_vpp_setup_mux(struct meson_vpu_priv *priv, unsigned int mux);
|
||||
void meson_vpu_init(struct udevice *dev);
|
||||
void meson_vpu_setup_plane(struct udevice *dev, bool is_interlaced);
|
||||
bool meson_venc_hdmi_supported_mode(const struct display_timing *mode);
|
||||
void meson_vpu_setup_venc(struct udevice *dev,
|
||||
const struct display_timing *mode, bool is_cvbs);
|
||||
bool meson_vclk_dmt_supported_freq(struct meson_vpu_priv *priv,
|
||||
unsigned int freq);
|
||||
void meson_vpu_setup_vclk(struct udevice *dev,
|
||||
const struct display_timing *mode, bool is_cvbs);
|
||||
#endif
|
440
drivers/video/meson/meson_vpu_init.c
Normal file
440
drivers/video/meson/meson_vpu_init.c
Normal file
@ -0,0 +1,440 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Amlogic Meson Video Processing Unit driver
|
||||
*
|
||||
* Copyright (c) 2018 BayLibre, SAS.
|
||||
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
||||
*/
|
||||
|
||||
#define DEBUG
|
||||
|
||||
#include "meson_vpu.h"
|
||||
|
||||
/* HHI Registers */
|
||||
#define HHI_VDAC_CNTL0 0x2F4 /* 0xbd offset in data sheet */
|
||||
#define HHI_VDAC_CNTL1 0x2F8 /* 0xbe offset in data sheet */
|
||||
#define HHI_HDMI_PHY_CNTL0 0x3a0 /* 0xe8 offset in data sheet */
|
||||
|
||||
/* OSDx_CTRL_STAT2 */
|
||||
#define OSD_REPLACE_EN BIT(14)
|
||||
#define OSD_REPLACE_SHIFT 6
|
||||
|
||||
void meson_vpp_setup_mux(struct meson_vpu_priv *priv, unsigned int mux)
|
||||
{
|
||||
writel(mux, priv->io_base + _REG(VPU_VIU_VENC_MUX_CTRL));
|
||||
}
|
||||
|
||||
static unsigned int vpp_filter_coefs_4point_bspline[] = {
|
||||
0x15561500, 0x14561600, 0x13561700, 0x12561800,
|
||||
0x11551a00, 0x11541b00, 0x10541c00, 0x0f541d00,
|
||||
0x0f531e00, 0x0e531f00, 0x0d522100, 0x0c522200,
|
||||
0x0b522300, 0x0b512400, 0x0a502600, 0x0a4f2700,
|
||||
0x094e2900, 0x084e2a00, 0x084d2b00, 0x074c2c01,
|
||||
0x074b2d01, 0x064a2f01, 0x06493001, 0x05483201,
|
||||
0x05473301, 0x05463401, 0x04453601, 0x04433702,
|
||||
0x04423802, 0x03413a02, 0x03403b02, 0x033f3c02,
|
||||
0x033d3d03
|
||||
};
|
||||
|
||||
static void meson_vpp_write_scaling_filter_coefs(struct meson_vpu_priv *priv,
|
||||
const unsigned int *coefs,
|
||||
bool is_horizontal)
|
||||
{
|
||||
int i;
|
||||
|
||||
writel(is_horizontal ? BIT(8) : 0,
|
||||
priv->io_base + _REG(VPP_OSD_SCALE_COEF_IDX));
|
||||
for (i = 0; i < 33; i++)
|
||||
writel(coefs[i],
|
||||
priv->io_base + _REG(VPP_OSD_SCALE_COEF));
|
||||
}
|
||||
|
||||
static const u32 vpp_filter_coefs_bicubic[] = {
|
||||
0x00800000, 0x007f0100, 0xff7f0200, 0xfe7f0300,
|
||||
0xfd7e0500, 0xfc7e0600, 0xfb7d0800, 0xfb7c0900,
|
||||
0xfa7b0b00, 0xfa7a0dff, 0xf9790fff, 0xf97711ff,
|
||||
0xf87613ff, 0xf87416fe, 0xf87218fe, 0xf8701afe,
|
||||
0xf76f1dfd, 0xf76d1ffd, 0xf76b21fd, 0xf76824fd,
|
||||
0xf76627fc, 0xf76429fc, 0xf7612cfc, 0xf75f2ffb,
|
||||
0xf75d31fb, 0xf75a34fb, 0xf75837fa, 0xf7553afa,
|
||||
0xf8523cfa, 0xf8503ff9, 0xf84d42f9, 0xf84a45f9,
|
||||
0xf84848f8
|
||||
};
|
||||
|
||||
static void meson_vpp_write_vd_scaling_filter_coefs(struct meson_vpu_priv *priv,
|
||||
const unsigned int *coefs,
|
||||
bool is_horizontal)
|
||||
{
|
||||
int i;
|
||||
|
||||
writel(is_horizontal ? BIT(8) : 0,
|
||||
priv->io_base + _REG(VPP_SCALE_COEF_IDX));
|
||||
for (i = 0; i < 33; i++)
|
||||
writel(coefs[i],
|
||||
priv->io_base + _REG(VPP_SCALE_COEF));
|
||||
}
|
||||
|
||||
/* OSD csc defines */
|
||||
|
||||
enum viu_matrix_sel_e {
|
||||
VIU_MATRIX_OSD_EOTF = 0,
|
||||
VIU_MATRIX_OSD,
|
||||
};
|
||||
|
||||
enum viu_lut_sel_e {
|
||||
VIU_LUT_OSD_EOTF = 0,
|
||||
VIU_LUT_OSD_OETF,
|
||||
};
|
||||
|
||||
#define COEFF_NORM(a) ((int)((((a) * 2048.0) + 1) / 2))
|
||||
#define MATRIX_5X3_COEF_SIZE 24
|
||||
|
||||
#define EOTF_COEFF_NORM(a) ((int)((((a) * 4096.0) + 1) / 2))
|
||||
#define EOTF_COEFF_SIZE 10
|
||||
#define EOTF_COEFF_RIGHTSHIFT 1
|
||||
|
||||
static int RGB709_to_YUV709l_coeff[MATRIX_5X3_COEF_SIZE] = {
|
||||
0, 0, 0, /* pre offset */
|
||||
COEFF_NORM(0.181873), COEFF_NORM(0.611831), COEFF_NORM(0.061765),
|
||||
COEFF_NORM(-0.100251), COEFF_NORM(-0.337249), COEFF_NORM(0.437500),
|
||||
COEFF_NORM(0.437500), COEFF_NORM(-0.397384), COEFF_NORM(-0.040116),
|
||||
0, 0, 0, /* 10'/11'/12' */
|
||||
0, 0, 0, /* 20'/21'/22' */
|
||||
64, 512, 512, /* offset */
|
||||
0, 0, 0 /* mode, right_shift, clip_en */
|
||||
};
|
||||
|
||||
/* eotf matrix: bypass */
|
||||
static int eotf_bypass_coeff[EOTF_COEFF_SIZE] = {
|
||||
EOTF_COEFF_NORM(1.0), EOTF_COEFF_NORM(0.0), EOTF_COEFF_NORM(0.0),
|
||||
EOTF_COEFF_NORM(0.0), EOTF_COEFF_NORM(1.0), EOTF_COEFF_NORM(0.0),
|
||||
EOTF_COEFF_NORM(0.0), EOTF_COEFF_NORM(0.0), EOTF_COEFF_NORM(1.0),
|
||||
EOTF_COEFF_RIGHTSHIFT /* right shift */
|
||||
};
|
||||
|
||||
static void meson_viu_set_osd_matrix(struct meson_vpu_priv *priv,
|
||||
enum viu_matrix_sel_e m_select,
|
||||
int *m, bool csc_on)
|
||||
{
|
||||
if (m_select == VIU_MATRIX_OSD) {
|
||||
/* osd matrix, VIU_MATRIX_0 */
|
||||
writel(((m[0] & 0xfff) << 16) | (m[1] & 0xfff),
|
||||
priv->io_base + _REG(VIU_OSD1_MATRIX_PRE_OFFSET0_1));
|
||||
writel(m[2] & 0xfff,
|
||||
priv->io_base + _REG(VIU_OSD1_MATRIX_PRE_OFFSET2));
|
||||
writel(((m[3] & 0x1fff) << 16) | (m[4] & 0x1fff),
|
||||
priv->io_base + _REG(VIU_OSD1_MATRIX_COEF00_01));
|
||||
writel(((m[5] & 0x1fff) << 16) | (m[6] & 0x1fff),
|
||||
priv->io_base + _REG(VIU_OSD1_MATRIX_COEF02_10));
|
||||
writel(((m[7] & 0x1fff) << 16) | (m[8] & 0x1fff),
|
||||
priv->io_base + _REG(VIU_OSD1_MATRIX_COEF11_12));
|
||||
writel(((m[9] & 0x1fff) << 16) | (m[10] & 0x1fff),
|
||||
priv->io_base + _REG(VIU_OSD1_MATRIX_COEF20_21));
|
||||
|
||||
if (m[21]) {
|
||||
writel(((m[11] & 0x1fff) << 16) | (m[12] & 0x1fff),
|
||||
priv->io_base +
|
||||
_REG(VIU_OSD1_MATRIX_COEF22_30));
|
||||
writel(((m[13] & 0x1fff) << 16) | (m[14] & 0x1fff),
|
||||
priv->io_base +
|
||||
_REG(VIU_OSD1_MATRIX_COEF31_32));
|
||||
writel(((m[15] & 0x1fff) << 16) | (m[16] & 0x1fff),
|
||||
priv->io_base +
|
||||
_REG(VIU_OSD1_MATRIX_COEF40_41));
|
||||
writel(m[17] & 0x1fff, priv->io_base +
|
||||
_REG(VIU_OSD1_MATRIX_COLMOD_COEF42));
|
||||
} else {
|
||||
writel((m[11] & 0x1fff) << 16, priv->io_base +
|
||||
_REG(VIU_OSD1_MATRIX_COEF22_30));
|
||||
}
|
||||
|
||||
writel(((m[18] & 0xfff) << 16) | (m[19] & 0xfff),
|
||||
priv->io_base + _REG(VIU_OSD1_MATRIX_OFFSET0_1));
|
||||
writel(m[20] & 0xfff,
|
||||
priv->io_base + _REG(VIU_OSD1_MATRIX_OFFSET2));
|
||||
|
||||
writel_bits(3 << 30, m[21] << 30,
|
||||
priv->io_base +
|
||||
_REG(VIU_OSD1_MATRIX_COLMOD_COEF42));
|
||||
writel_bits(7 << 16, m[22] << 16,
|
||||
priv->io_base +
|
||||
_REG(VIU_OSD1_MATRIX_COLMOD_COEF42));
|
||||
|
||||
/* 23 reserved for clipping control */
|
||||
writel_bits(BIT(0), csc_on ? BIT(0) : 0,
|
||||
priv->io_base + _REG(VIU_OSD1_MATRIX_CTRL));
|
||||
writel_bits(BIT(1), 0,
|
||||
priv->io_base + _REG(VIU_OSD1_MATRIX_CTRL));
|
||||
} else if (m_select == VIU_MATRIX_OSD_EOTF) {
|
||||
int i;
|
||||
|
||||
/* osd eotf matrix, VIU_MATRIX_OSD_EOTF */
|
||||
for (i = 0; i < 5; i++)
|
||||
writel(((m[i * 2] & 0x1fff) << 16) |
|
||||
(m[i * 2 + 1] & 0x1fff), priv->io_base +
|
||||
_REG(VIU_OSD1_EOTF_CTL + i + 1));
|
||||
|
||||
writel_bits(BIT(30), csc_on ? BIT(30) : 0,
|
||||
priv->io_base + _REG(VIU_OSD1_EOTF_CTL));
|
||||
writel_bits(BIT(31), csc_on ? BIT(31) : 0,
|
||||
priv->io_base + _REG(VIU_OSD1_EOTF_CTL));
|
||||
}
|
||||
}
|
||||
|
||||
#define OSD_EOTF_LUT_SIZE 33
|
||||
#define OSD_OETF_LUT_SIZE 41
|
||||
|
||||
static void meson_viu_set_osd_lut(struct meson_vpu_priv *priv,
|
||||
enum viu_lut_sel_e lut_sel,
|
||||
unsigned int *r_map, unsigned int *g_map,
|
||||
unsigned int *b_map,
|
||||
bool csc_on)
|
||||
{
|
||||
unsigned int addr_port;
|
||||
unsigned int data_port;
|
||||
unsigned int ctrl_port;
|
||||
int i;
|
||||
|
||||
if (lut_sel == VIU_LUT_OSD_EOTF) {
|
||||
addr_port = VIU_OSD1_EOTF_LUT_ADDR_PORT;
|
||||
data_port = VIU_OSD1_EOTF_LUT_DATA_PORT;
|
||||
ctrl_port = VIU_OSD1_EOTF_CTL;
|
||||
} else if (lut_sel == VIU_LUT_OSD_OETF) {
|
||||
addr_port = VIU_OSD1_OETF_LUT_ADDR_PORT;
|
||||
data_port = VIU_OSD1_OETF_LUT_DATA_PORT;
|
||||
ctrl_port = VIU_OSD1_OETF_CTL;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lut_sel == VIU_LUT_OSD_OETF) {
|
||||
writel(0, priv->io_base + _REG(addr_port));
|
||||
|
||||
for (i = 0; i < 20; i++)
|
||||
writel(r_map[i * 2] | (r_map[i * 2 + 1] << 16),
|
||||
priv->io_base + _REG(data_port));
|
||||
|
||||
writel(r_map[OSD_OETF_LUT_SIZE - 1] | (g_map[0] << 16),
|
||||
priv->io_base + _REG(data_port));
|
||||
|
||||
for (i = 0; i < 20; i++)
|
||||
writel(g_map[i * 2 + 1] | (g_map[i * 2 + 2] << 16),
|
||||
priv->io_base + _REG(data_port));
|
||||
|
||||
for (i = 0; i < 20; i++)
|
||||
writel(b_map[i * 2] | (b_map[i * 2 + 1] << 16),
|
||||
priv->io_base + _REG(data_port));
|
||||
|
||||
writel(b_map[OSD_OETF_LUT_SIZE - 1],
|
||||
priv->io_base + _REG(data_port));
|
||||
|
||||
if (csc_on)
|
||||
writel_bits(0x7 << 29, 7 << 29,
|
||||
priv->io_base + _REG(ctrl_port));
|
||||
else
|
||||
writel_bits(0x7 << 29, 0,
|
||||
priv->io_base + _REG(ctrl_port));
|
||||
} else if (lut_sel == VIU_LUT_OSD_EOTF) {
|
||||
writel(0, priv->io_base + _REG(addr_port));
|
||||
|
||||
for (i = 0; i < 20; i++)
|
||||
writel(r_map[i * 2] | (r_map[i * 2 + 1] << 16),
|
||||
priv->io_base + _REG(data_port));
|
||||
|
||||
writel(r_map[OSD_EOTF_LUT_SIZE - 1] | (g_map[0] << 16),
|
||||
priv->io_base + _REG(data_port));
|
||||
|
||||
for (i = 0; i < 20; i++)
|
||||
writel(g_map[i * 2 + 1] | (g_map[i * 2 + 2] << 16),
|
||||
priv->io_base + _REG(data_port));
|
||||
|
||||
for (i = 0; i < 20; i++)
|
||||
writel(b_map[i * 2] | (b_map[i * 2 + 1] << 16),
|
||||
priv->io_base + _REG(data_port));
|
||||
|
||||
writel(b_map[OSD_EOTF_LUT_SIZE - 1],
|
||||
priv->io_base + _REG(data_port));
|
||||
|
||||
if (csc_on)
|
||||
writel_bits(7 << 27, 7 << 27,
|
||||
priv->io_base + _REG(ctrl_port));
|
||||
else
|
||||
writel_bits(7 << 27, 0,
|
||||
priv->io_base + _REG(ctrl_port));
|
||||
|
||||
writel_bits(BIT(31), BIT(31),
|
||||
priv->io_base + _REG(ctrl_port));
|
||||
}
|
||||
}
|
||||
|
||||
/* eotf lut: linear */
|
||||
static unsigned int eotf_33_linear_mapping[OSD_EOTF_LUT_SIZE] = {
|
||||
0x0000, 0x0200, 0x0400, 0x0600,
|
||||
0x0800, 0x0a00, 0x0c00, 0x0e00,
|
||||
0x1000, 0x1200, 0x1400, 0x1600,
|
||||
0x1800, 0x1a00, 0x1c00, 0x1e00,
|
||||
0x2000, 0x2200, 0x2400, 0x2600,
|
||||
0x2800, 0x2a00, 0x2c00, 0x2e00,
|
||||
0x3000, 0x3200, 0x3400, 0x3600,
|
||||
0x3800, 0x3a00, 0x3c00, 0x3e00,
|
||||
0x4000
|
||||
};
|
||||
|
||||
/* osd oetf lut: linear */
|
||||
static unsigned int oetf_41_linear_mapping[OSD_OETF_LUT_SIZE] = {
|
||||
0, 0, 0, 0,
|
||||
0, 32, 64, 96,
|
||||
128, 160, 196, 224,
|
||||
256, 288, 320, 352,
|
||||
384, 416, 448, 480,
|
||||
512, 544, 576, 608,
|
||||
640, 672, 704, 736,
|
||||
768, 800, 832, 864,
|
||||
896, 928, 960, 992,
|
||||
1023, 1023, 1023, 1023,
|
||||
1023
|
||||
};
|
||||
|
||||
static void meson_viu_load_matrix(struct meson_vpu_priv *priv)
|
||||
{
|
||||
/* eotf lut bypass */
|
||||
meson_viu_set_osd_lut(priv, VIU_LUT_OSD_EOTF,
|
||||
eotf_33_linear_mapping, /* R */
|
||||
eotf_33_linear_mapping, /* G */
|
||||
eotf_33_linear_mapping, /* B */
|
||||
false);
|
||||
|
||||
/* eotf matrix bypass */
|
||||
meson_viu_set_osd_matrix(priv, VIU_MATRIX_OSD_EOTF,
|
||||
eotf_bypass_coeff,
|
||||
false);
|
||||
|
||||
/* oetf lut bypass */
|
||||
meson_viu_set_osd_lut(priv, VIU_LUT_OSD_OETF,
|
||||
oetf_41_linear_mapping, /* R */
|
||||
oetf_41_linear_mapping, /* G */
|
||||
oetf_41_linear_mapping, /* B */
|
||||
false);
|
||||
|
||||
/* osd matrix RGB709 to YUV709 limit */
|
||||
meson_viu_set_osd_matrix(priv, VIU_MATRIX_OSD,
|
||||
RGB709_to_YUV709l_coeff,
|
||||
true);
|
||||
}
|
||||
|
||||
void meson_vpu_init(struct udevice *dev)
|
||||
{
|
||||
struct meson_vpu_priv *priv = dev_get_priv(dev);
|
||||
u32 reg;
|
||||
|
||||
/* vpu initialization */
|
||||
writel(0x210000, priv->io_base + _REG(VPU_RDARB_MODE_L1C1));
|
||||
writel(0x10000, priv->io_base + _REG(VPU_RDARB_MODE_L1C2));
|
||||
writel(0x900000, priv->io_base + _REG(VPU_RDARB_MODE_L2C1));
|
||||
writel(0x20000, priv->io_base + _REG(VPU_WRARB_MODE_L2C1));
|
||||
|
||||
/* Disable CVBS VDAC */
|
||||
hhi_write(HHI_VDAC_CNTL0, 0);
|
||||
hhi_write(HHI_VDAC_CNTL1, 8);
|
||||
|
||||
/* Power Down Dacs */
|
||||
writel(0xff, priv->io_base + _REG(VENC_VDAC_SETTING));
|
||||
|
||||
/* Disable HDMI PHY */
|
||||
hhi_write(HHI_HDMI_PHY_CNTL0, 0);
|
||||
|
||||
/* Disable HDMI */
|
||||
writel_bits(0x3, 0, priv->io_base + _REG(VPU_HDMI_SETTING));
|
||||
|
||||
/* Disable all encoders */
|
||||
writel(0, priv->io_base + _REG(ENCI_VIDEO_EN));
|
||||
writel(0, priv->io_base + _REG(ENCP_VIDEO_EN));
|
||||
writel(0, priv->io_base + _REG(ENCL_VIDEO_EN));
|
||||
|
||||
/* Disable VSync IRQ */
|
||||
writel(0, priv->io_base + _REG(VENC_INTCTRL));
|
||||
|
||||
/* set dummy data default YUV black */
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL)) {
|
||||
writel(0x108080, priv->io_base + _REG(VPP_DUMMY_DATA1));
|
||||
} else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM)) {
|
||||
writel_bits(0xff << 16, 0xff << 16,
|
||||
priv->io_base + _REG(VIU_MISC_CTRL1));
|
||||
writel(0x20000, priv->io_base + _REG(VPP_DOLBY_CTRL));
|
||||
writel(0x1020080,
|
||||
priv->io_base + _REG(VPP_DUMMY_DATA1));
|
||||
}
|
||||
|
||||
/* Initialize vpu fifo control registers */
|
||||
writel(readl(priv->io_base + _REG(VPP_OFIFO_SIZE)) |
|
||||
0x77f, priv->io_base + _REG(VPP_OFIFO_SIZE));
|
||||
writel(0x08080808, priv->io_base + _REG(VPP_HOLD_LINES));
|
||||
|
||||
/* Turn off preblend */
|
||||
writel_bits(VPP_PREBLEND_ENABLE, 0,
|
||||
priv->io_base + _REG(VPP_MISC));
|
||||
|
||||
/* Turn off POSTBLEND */
|
||||
writel_bits(VPP_POSTBLEND_ENABLE, 0,
|
||||
priv->io_base + _REG(VPP_MISC));
|
||||
|
||||
/* Force all planes off */
|
||||
writel_bits(VPP_OSD1_POSTBLEND | VPP_OSD2_POSTBLEND |
|
||||
VPP_VD1_POSTBLEND | VPP_VD2_POSTBLEND |
|
||||
VPP_VD1_PREBLEND | VPP_VD2_PREBLEND, 0,
|
||||
priv->io_base + _REG(VPP_MISC));
|
||||
|
||||
/* Setup default VD settings */
|
||||
writel(4096,
|
||||
priv->io_base + _REG(VPP_PREBLEND_VD1_H_START_END));
|
||||
writel(4096,
|
||||
priv->io_base + _REG(VPP_BLEND_VD2_H_START_END));
|
||||
|
||||
/* Disable Scalers */
|
||||
writel(0, priv->io_base + _REG(VPP_OSD_SC_CTRL0));
|
||||
writel(0, priv->io_base + _REG(VPP_OSD_VSC_CTRL0));
|
||||
writel(0, priv->io_base + _REG(VPP_OSD_HSC_CTRL0));
|
||||
writel(4 | (4 << 8) | BIT(15),
|
||||
priv->io_base + _REG(VPP_SC_MISC));
|
||||
|
||||
/* Write in the proper filter coefficients. */
|
||||
meson_vpp_write_scaling_filter_coefs(priv,
|
||||
vpp_filter_coefs_4point_bspline, false);
|
||||
meson_vpp_write_scaling_filter_coefs(priv,
|
||||
vpp_filter_coefs_4point_bspline, true);
|
||||
|
||||
/* Write the VD proper filter coefficients. */
|
||||
meson_vpp_write_vd_scaling_filter_coefs(priv, vpp_filter_coefs_bicubic,
|
||||
false);
|
||||
meson_vpp_write_vd_scaling_filter_coefs(priv, vpp_filter_coefs_bicubic,
|
||||
true);
|
||||
|
||||
/* Disable OSDs */
|
||||
writel_bits(BIT(0) | BIT(21), 0,
|
||||
priv->io_base + _REG(VIU_OSD1_CTRL_STAT));
|
||||
writel_bits(BIT(0) | BIT(21), 0,
|
||||
priv->io_base + _REG(VIU_OSD2_CTRL_STAT));
|
||||
|
||||
/* On GXL/GXM, Use the 10bit HDR conversion matrix */
|
||||
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
|
||||
meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL))
|
||||
meson_viu_load_matrix(priv);
|
||||
|
||||
/* Initialize OSD1 fifo control register */
|
||||
reg = BIT(0) | /* Urgent DDR request priority */
|
||||
(4 << 5) | /* hold_fifo_lines */
|
||||
(3 << 10) | /* burst length 64 */
|
||||
(32 << 12) | /* fifo_depth_val: 32*8=256 */
|
||||
(2 << 22) | /* 4 words in 1 burst */
|
||||
(2 << 24);
|
||||
writel(reg, priv->io_base + _REG(VIU_OSD1_FIFO_CTRL_STAT));
|
||||
writel(reg, priv->io_base + _REG(VIU_OSD2_FIFO_CTRL_STAT));
|
||||
|
||||
/* Set OSD alpha replace value */
|
||||
writel_bits(0xff << OSD_REPLACE_SHIFT,
|
||||
0xff << OSD_REPLACE_SHIFT,
|
||||
priv->io_base + _REG(VIU_OSD1_CTRL_STAT2));
|
||||
writel_bits(0xff << OSD_REPLACE_SHIFT,
|
||||
0xff << OSD_REPLACE_SHIFT,
|
||||
priv->io_base + _REG(VIU_OSD2_CTRL_STAT2));
|
||||
}
|
29
drivers/video/meson/simplefb_common.c
Normal file
29
drivers/video/meson/simplefb_common.c
Normal file
@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Common code for Amlogic SimpleFB with pipeline.
|
||||
*
|
||||
* (C) Copyright 2013-2014 Luc Verhaegen <libv@skynet.be>
|
||||
* (C) Copyright 2014-2015 Hans de Goede <hdegoede@redhat.com>
|
||||
* (C) Copyright 2017 Icenowy Zheng <icenowy@aosc.io>
|
||||
*/
|
||||
|
||||
#include <fdtdec.h>
|
||||
|
||||
int meson_simplefb_fdt_match(void *blob, const char *pipeline)
|
||||
{
|
||||
int offset, ret;
|
||||
|
||||
/* Find a prefilled simpefb node, matching out pipeline config */
|
||||
offset = fdt_node_offset_by_compatible(blob, -1,
|
||||
"amlogic,simple-framebuffer");
|
||||
while (offset >= 0) {
|
||||
ret = fdt_stringlist_search(blob, offset, "amlogic,pipeline",
|
||||
pipeline);
|
||||
if (ret == 0)
|
||||
break;
|
||||
offset = fdt_node_offset_by_compatible(blob, offset,
|
||||
"amlogic,simple-framebuffer");
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
21
drivers/video/meson/simplefb_common.h
Normal file
21
drivers/video/meson/simplefb_common.h
Normal file
@ -0,0 +1,21 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* (C) Copyright 2017 Icenowy Zheng <icenowy@aosc.io>
|
||||
*/
|
||||
|
||||
#ifndef __SIMPLEFB_COMMON_H
|
||||
#define __SIMPLEFB_COMMON_H
|
||||
|
||||
/**
|
||||
* meson_simplefb_fdt_match() - match a meson simplefb node
|
||||
*
|
||||
* Match a meson simplefb device node with a specified pipeline, and
|
||||
* return its offset.
|
||||
*
|
||||
* @blob: device tree blob
|
||||
* @pipeline: display pipeline
|
||||
* @return device node offset in blob, or negative values if failed
|
||||
*/
|
||||
int meson_simplefb_fdt_match(void *blob, const char *pipeline);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user