10 Commits

Author SHA1 Message Date
Caesar Wang
1746094a40 VENDOR: rockchip: ARM: dts: rockchip: support the bcmdhd wlan for kylin board
This patch makes the dts to match the new bcmdhd
wifi/bt driver DHD_Driver_1.363.59.144.

Bug: 27307957
Patchset: Add the compatible with dts for bcmdhd driver.

Change-Id: I82d621cb35154d475bf046ca2f99bb71cd62ff1e
Signed-off-by: Caesar Wang <wxt@rock-chips.com>
2016-02-24 16:55:25 +08:00
zhengxing
79f3c276bb FROMLIST: ARM: dts: rockchip: add support emac for RK3036
This patch adds the emac device node for rk3036.
We need to let mac clock under the DPLL which is able to provide
the accurate 50MHz what mac_ref need, since that will cause some
unstable things if the cpufreq is working.

Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
Signed-off-by: Caesar Wang <wxt@rock-chips.com>

Bug: 27307957
Patchset: support emac for rk3036 dts.

(am from https://patchwork.kernel.org/patch/8186601/)
Signed-off-by: Caesar Wang <wxt@rock-chips.com>
Change-Id: If4b618c6bf3b2640fbdb9cdf2ebc1b09f796841a
2016-02-24 16:55:24 +08:00
Yakir Yang
fb99df4137 UPSTREAM: dt-bindings: add document for Innosilicon HDMI on Rockchip platform
Signed-off-by: Yakir Yang <ykk@rock-chips.com>
Acked-by: Rob Herring <robh@kernel.org>

Bug: 27307957
Patchset: fetch the upstream drm patches for rockchip.

(cherry picked from github markyzq/kernel-drm-rockchip drm-rockchip-next-2016-02-18
 commit a2b5f9b9f3)
Signed-off-by: Caesar Wang <wxt@rock-chips.com>

Change-Id: I6b407f33f065711346f6bb3a0e0d75dba0788a80
2016-02-24 16:55:23 +08:00
Yakir Yang
02be373b8b UPSTREAM: drm/rockchip: hdmi: add Innosilicon HDMI support
The Innosilicon HDMI is a low power HDMI 1.4 transmitter
IP, and it have been integrated on some rockchip CPUs
(like RK3036, RK312x).

Signed-off-by: Yakir Yang <ykk@rock-chips.com>
Tested-by: Heiko Stuebner <heiko@sntech.de>

(cherry picked from github markyzq/kernel-drm-rockchip drm-rockchip-next-2016-02-18
 commit 412d4ae6b7)
Signed-off-by: Caesar Wang <wxt@rock-chips.com>

Bug: 27307957
Patchset: fetch the upstream drm patches for rockchip.

Conflicts:
	drivers/gpu/drm/rockchip/Kconfig
	drivers/gpu/drm/rockchip/Makefile

Change-Id: I41bad138642bd03a452d83c8e0170200d21362ab
2016-02-24 16:55:23 +08:00
John Keeping
c66f08ee8b UPSTREAM: drm/rockchip: respect CONFIG_DRM_FBDEV_EMULATION
If DRM_FBDEV_EMULATION is not selected in the config then we can save a
bit of space by not including the framebuffer code.

Signed-off-by: John Keeping <john@metanate.com>

Bug: 27307957
Patchset: fetch the upstream drm patches for rockchip.

(cherry picked from github markyzq/kernel-drm-rockchip drm-rockchip-next-2016-02-18
 commit f0442df215)
Signed-off-by: Caesar Wang <wxt@rock-chips.com>

Change-Id: I5ef3e989ba5fadacc33388ef45036ad2718d68dc
2016-02-24 16:55:22 +08:00
Mark Yao
45356b5557 UPSTREAM: drm/rockchip: fix wrong pitch/size using on gem
args->pitch and args->size may not be set by userspace, sometimes
userspace only malloc args and not memset args to zero, then
args->pitch and args->size is random, it is very danger to use
pitch/size on gem.

pitch's type is u32, and min_pitch's type is int, example,
pitch is 0xffffffff, then pitch < min_pitch return true, then gem will
alloc very very big bufffer, it would eat all the memory and cause kernel
crash.

Stop using pitch/size from args, calc them from other args.

Signed-off-by: Mark Yao <mark.yao@rock-chips.com>

Bug: 27307957
Patchset: fetch the upstream drm patches for rockchip.

(cherry picked from github markyzq/kernel-drm-rockchip drm-rockchip-next-2016-02-18
 commit e3c4abdb3b)
Signed-off-by: Caesar Wang <wxt@rock-chips.com>

Change-Id: Ia422b3fca0b8259461894115caeffb8ba758f525
2016-02-24 16:55:22 +08:00
John Keeping
8ec9ed08d7 UPSTREAM: drm/rockchip: explain why we can't wait_for_vblanks
Signed-off-by: John Keeping <john@metanate.com>

Bug: 27307957
Patchset: fetch the upstream drm patches for rockchip.

(cherry picked from github markyzq/kernel-drm-rockchip drm-rockchip-next-2016-02-18
 commit c9ad1d9946)
Signed-off-by: Caesar Wang <wxt@rock-chips.com>

Change-Id: Ic7a32b84364bbee6c8b171e6d356fa13ec8b200f
2016-02-24 16:55:21 +08:00
John Keeping
ff8e264f73 UPSTREAM: drm/rockchip: don't wait for vblank if fb hasn't changed
As commented in drm_atomic_helper_wait_for_vblanks(), userspace relies
on cursor ioctls being unsynced.  Converting the rockchip driver to
atomic has significantly impacted cursor performance by making every
cursor update wait for vblank.

By skipping the vblank sync when the framebuffer has not changed (as is
done in drm_atomic_helper_wait_for_vblanks()) we can avoid this for the
common case of moving the cursor and only need to delay the cursor ioctl
when the cursor icon changes.

We cannot add the check on legacy_cursor_update since that results in
the cursor bo being unreferenced while the hardware may still be reading
it.  Fully supporting unsynced cursor updates is left for the future
when the atomic helper framework supports async updates.

Signed-off-by: John Keeping <john@metanate.com>
Tested-by: Heiko Stuebner <heiko@sntech.de>

Bug: 27307957
Patchset: fetch the upstream drm patches for rockchip.

(cherry picked from github markyzq/kernel-drm-rockchip drm-rockchip-next-2016-02-18
 commit f2227f4697)
Signed-off-by: Caesar Wang <wxt@rock-chips.com>

Change-Id: I7146f11d0df663e7bf7bd9ae4f68e4fe579c2c4e
2016-02-24 16:55:20 +08:00
Mark Yao
364cb2bc4a UPSTREAM: drm/rockchip: cleanup unnecessary export symbol
Now rockchip_drm_vop.c is build into rockchipdrm.ko, so
no need to export following symbol anymore:
    rockchip_drm_dma_attach_device
    rockchip_drm_dma_detach_device
    rockchip_drm_dma_attach_device
    rockchip_drm_dma_detach_device
    rockchip_register_crtc_funcs
    rockchip_unregister_crtc_funcs
    rockchip_fb_get_gem_obj

Signed-off-by: Mark Yao <mark.yao@rock-chips.com>

Bug: 27307957
Patchset: fetch the upstream drm patches for rockchip.

(cherry picked from github markyzq/kernel-drm-rockchip drm-rockchip-next-2016-02-18
 commit 63087aae5a)
Signed-off-by: Caesar Wang <wxt@rock-chips.com>

Change-Id: I0ff506e85150e01ce571cf79b422c8b13eaf8149
2016-02-24 16:55:20 +08:00
Mark Yao
617d68ffe7 UPSTREAM: drm/rockchip: Don't build rockchip_drm_vop as modules
rockchip_drm_vop's module init had moved to rockchip_vop_reg.c
so no need to build rockchip_drm_vop.ko

Signed-off-by: Mark Yao <mark.yao@rock-chips.com>

Bug: 27307957
Patchset: fetch the upstream drm patches for rockchip.

(cherry picked from github markyzq/kernel-drm-rockchip drm-rockchip-next-2016-02-18
 commit ce90d092bc)
Signed-off-by: Caesar Wang <wxt@rock-chips.com>

Change-Id: I75aeaca917e788f18ccc05db4c77dc9eb3dafdfb
2016-02-24 16:55:19 +08:00
12 changed files with 1486 additions and 18 deletions

View File

@@ -0,0 +1,50 @@
Rockchip specific extensions to the Innosilicon HDMI
================================
Required properties:
- compatible:
"rockchip,rk3036-inno-hdmi";
- reg:
Physical base address and length of the controller's registers.
- clocks, clock-names:
Phandle to hdmi controller clock, name should be "pclk"
- interrupts:
HDMI interrupt number
- ports:
Contain one port node with endpoint definitions as defined in
Documentation/devicetree/bindings/graph.txt.
- pinctrl-0, pinctrl-name:
Switch the iomux of HPD/CEC pins to HDMI function.
Example:
hdmi: hdmi@20034000 {
compatible = "rockchip,rk3036-inno-hdmi";
reg = <0x20034000 0x4000>;
interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_HDMI>;
clock-names = "pclk";
pinctrl-names = "default";
pinctrl-0 = <&hdmi_ctl>;
status = "disabled";
hdmi_in: port {
#address-cells = <1>;
#size-cells = <0>;
hdmi_in_lcdc: endpoint@0 {
reg = <0>;
remote-endpoint = <&lcdc_out_hdmi>;
};
};
};
&pinctrl {
hdmi {
hdmi_ctl: hdmi-ctl {
rockchip,pins = <1 8 RK_FUNC_1 &pcfg_pull_none>,
<1 9 RK_FUNC_1 &pcfg_pull_none>,
<1 10 RK_FUNC_1 &pcfg_pull_none>,
<1 11 RK_FUNC_1 &pcfg_pull_none>;
};
};
};

View File

@@ -47,6 +47,17 @@
compatible = "rockchip,rk3036-evb", "rockchip,rk3036";
};
&emac {
pinctrl-names = "default";
pinctrl-0 = <&emac_xfer>, <&emac_mdio>, <&rmii_rst>;
phy = <&phy0>;
status = "okay";
phy0: ethernet-phy@0 {
reg = <0>;
};
};
&i2c1 {
status = "okay";
@@ -62,3 +73,15 @@
&uart2 {
status = "okay";
};
&pinctrl {
pcfg_output_high: pcfg-output-high {
output-high;
};
emac {
rmii_rst: rmii-rst {
rockchip,pins = <2 22 RK_FUNC_GPIO &pcfg_output_high>;
};
};
};

View File

@@ -46,6 +46,12 @@
model = "Rockchip RK3036 KylinBoard";
compatible = "rockchip,rk3036-kylin", "rockchip,rk3036";
bcmdhd_wlan_0: bcmdhd_wlan@0 {
compatible = "android,bcmdhd_wlan";
gpios = <&gpio0 27 GPIO_ACTIVE_HIGH>;
wlreg_on-supply = <&sdio_pwrseq>;
};
leds: gpio-leds {
compatible = "gpio-leds";
@@ -112,6 +118,17 @@
status = "okay";
};
&emac {
pinctrl-names = "default";
pinctrl-0 = <&emac_xfer>, <&emac_mdio>, <&rmii_rst>;
phy = <&phy0>;
status = "okay";
phy0: ethernet-phy@0 {
reg = <0>;
};
};
&emmc {
status = "okay";
};
@@ -384,6 +401,16 @@
};
&pinctrl {
pcfg_output_high: pcfg-output-high {
output-high;
};
emac {
rmii_rst: rmii-rst {
rockchip,pins = <2 22 RK_FUNC_GPIO &pcfg_output_high>;
};
};
leds {
led_ctl: led-ctl {
rockchip,pins = <2 30 RK_FUNC_GPIO &pcfg_pull_none>;

View File

@@ -223,6 +223,27 @@
status = "disabled";
};
emac: ethernet@10200000 {
compatible = "rockchip,rk3036-emac", "snps,arc-emac";
reg = <0x10200000 0x4000>;
interrupts = <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
rockchip,grf = <&grf>;
clocks = <&cru HCLK_MAC>, <&cru SCLK_MACREF>, <&cru SCLK_MAC>;
clock-names = "hclk", "macref", "macclk";
/*
* Fix the emac parent clock is DPLL instead of APLL.
* since that will cause some unstable things if the cpufreq
* is working. (e.g: the accurate 50MHz what mac_ref need)
*/
assigned-clocks = <&cru SCLK_MACPLL>;
assigned-clock-parents = <&cru PLL_DPLL>;
max-speed = <100>;
phy-mode = "rmii";
status = "disabled";
};
sdmmc: dwmmc@10214000 {
compatible = "rockchip,rk3036-dw-mshc", "rockchip,rk3288-dw-mshc";
reg = <0x10214000 0x4000>;
@@ -629,6 +650,24 @@
};
};
emac {
emac_xfer: emac-xfer {
rockchip,pins = <2 10 RK_FUNC_1 &pcfg_pull_default>, /* crs_dvalid */
<2 13 RK_FUNC_1 &pcfg_pull_default>, /* tx_en */
<2 14 RK_FUNC_1 &pcfg_pull_default>, /* mac_clk */
<2 15 RK_FUNC_1 &pcfg_pull_default>, /* rx_err */
<2 16 RK_FUNC_1 &pcfg_pull_default>, /* rxd1 */
<2 17 RK_FUNC_1 &pcfg_pull_default>, /* rxd0 */
<2 18 RK_FUNC_1 &pcfg_pull_default>, /* txd1 */
<2 19 RK_FUNC_1 &pcfg_pull_default>; /* txd0 */
};
emac_mdio: emac-mdio {
rockchip,pins = <2 12 RK_FUNC_1 &pcfg_pull_default>, /* mac_md */
<2 25 RK_FUNC_1 &pcfg_pull_default>; /* mac_mdclk */
};
};
i2c0 {
i2c0_xfer: i2c0-xfer {
rockchip,pins = <0 0 RK_FUNC_1 &pcfg_pull_none>,

View File

@@ -25,3 +25,11 @@ config ROCKCHIP_DW_HDMI
for the Synopsys DesignWare HDMI driver. If you want to
enable HDMI on RK3288 based SoC, you should selet this
option.
config ROCKCHIP_INNO_HDMI
tristate "Rockchip specific extensions for Innosilicon HDMI"
depends on DRM_ROCKCHIP
help
This selects support for Rockchip SoC specific extensions
for the Innosilicon HDMI driver. If you want to enable
HDMI on RK3036 based SoC, you should select this option.

View File

@@ -2,10 +2,11 @@
# Makefile for the drm device driver. This driver provides support for the
# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o rockchip_drm_fbdev.o \
rockchip_drm_gem.o
rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
rockchip_drm_gem.o rockchip_drm_vop.o
rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o rockchip_drm_vop.o \
rockchip_vop_reg.o
obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o rockchip_vop_reg.o

View File

@@ -0,0 +1,938 @@
/*
* Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
* Zheng Yang <zhengyang@rock-chips.com>
* Yakir Yang <ykk@rock-chips.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/irq.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/hdmi.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <drm/drm_of.h>
#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
#include "rockchip_drm_drv.h"
#include "rockchip_drm_vop.h"
#include "inno_hdmi.h"
#define to_inno_hdmi(x) container_of(x, struct inno_hdmi, x)
struct hdmi_data_info {
int vic;
bool sink_is_hdmi;
bool sink_has_audio;
unsigned int enc_in_format;
unsigned int enc_out_format;
unsigned int colorimetry;
};
struct inno_hdmi_i2c {
struct i2c_adapter adap;
u8 ddc_addr;
u8 segment_addr;
struct mutex lock;
struct completion cmp;
};
struct inno_hdmi {
struct device *dev;
struct drm_device *drm_dev;
int irq;
struct clk *pclk;
void __iomem *regs;
struct drm_connector connector;
struct drm_encoder encoder;
struct inno_hdmi_i2c *i2c;
struct i2c_adapter *ddc;
unsigned int tmds_rate;
struct hdmi_data_info hdmi_data;
struct drm_display_mode previous_mode;
};
enum {
CSC_ITU601_16_235_TO_RGB_0_255_8BIT,
CSC_ITU601_0_255_TO_RGB_0_255_8BIT,
CSC_ITU709_16_235_TO_RGB_0_255_8BIT,
CSC_RGB_0_255_TO_ITU601_16_235_8BIT,
CSC_RGB_0_255_TO_ITU709_16_235_8BIT,
CSC_RGB_0_255_TO_RGB_16_235_8BIT,
};
static const char coeff_csc[][24] = {
/*
* YUV2RGB:601 SD mode(Y[16:235], UV[16:240], RGB[0:255]):
* R = 1.164*Y + 1.596*V - 204
* G = 1.164*Y - 0.391*U - 0.813*V + 154
* B = 1.164*Y + 2.018*U - 258
*/
{
0x04, 0xa7, 0x00, 0x00, 0x06, 0x62, 0x02, 0xcc,
0x04, 0xa7, 0x11, 0x90, 0x13, 0x40, 0x00, 0x9a,
0x04, 0xa7, 0x08, 0x12, 0x00, 0x00, 0x03, 0x02
},
/*
* YUV2RGB:601 SD mode(YUV[0:255],RGB[0:255]):
* R = Y + 1.402*V - 248
* G = Y - 0.344*U - 0.714*V + 135
* B = Y + 1.772*U - 227
*/
{
0x04, 0x00, 0x00, 0x00, 0x05, 0x9b, 0x02, 0xf8,
0x04, 0x00, 0x11, 0x60, 0x12, 0xdb, 0x00, 0x87,
0x04, 0x00, 0x07, 0x16, 0x00, 0x00, 0x02, 0xe3
},
/*
* YUV2RGB:709 HD mode(Y[16:235],UV[16:240],RGB[0:255]):
* R = 1.164*Y + 1.793*V - 248
* G = 1.164*Y - 0.213*U - 0.534*V + 77
* B = 1.164*Y + 2.115*U - 289
*/
{
0x04, 0xa7, 0x00, 0x00, 0x07, 0x2c, 0x02, 0xf8,
0x04, 0xa7, 0x10, 0xda, 0x12, 0x22, 0x00, 0x4d,
0x04, 0xa7, 0x08, 0x74, 0x00, 0x00, 0x03, 0x21
},
/*
* RGB2YUV:601 SD mode:
* Cb = -0.291G - 0.148R + 0.439B + 128
* Y = 0.504G + 0.257R + 0.098B + 16
* Cr = -0.368G + 0.439R - 0.071B + 128
*/
{
0x11, 0x5f, 0x01, 0x82, 0x10, 0x23, 0x00, 0x80,
0x02, 0x1c, 0x00, 0xa1, 0x00, 0x36, 0x00, 0x1e,
0x11, 0x29, 0x10, 0x59, 0x01, 0x82, 0x00, 0x80
},
/*
* RGB2YUV:709 HD mode:
* Cb = - 0.338G - 0.101R + 0.439B + 128
* Y = 0.614G + 0.183R + 0.062B + 16
* Cr = - 0.399G + 0.439R - 0.040B + 128
*/
{
0x11, 0x98, 0x01, 0xc1, 0x10, 0x28, 0x00, 0x80,
0x02, 0x74, 0x00, 0xbb, 0x00, 0x3f, 0x00, 0x10,
0x11, 0x5a, 0x10, 0x67, 0x01, 0xc1, 0x00, 0x80
},
/*
* RGB[0:255]2RGB[16:235]:
* R' = R x (235-16)/255 + 16;
* G' = G x (235-16)/255 + 16;
* B' = B x (235-16)/255 + 16;
*/
{
0x00, 0x00, 0x03, 0x6F, 0x00, 0x00, 0x00, 0x10,
0x03, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
0x00, 0x00, 0x00, 0x00, 0x03, 0x6F, 0x00, 0x10
},
};
static inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset)
{
return readl_relaxed(hdmi->regs + (offset) * 0x04);
}
static inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val)
{
writel_relaxed(val, hdmi->regs + (offset) * 0x04);
}
static inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset,
u32 msk, u32 val)
{
u8 temp = hdmi_readb(hdmi, offset) & ~msk;
temp |= val & msk;
hdmi_writeb(hdmi, offset, temp);
}
static void inno_hdmi_i2c_init(struct inno_hdmi *hdmi)
{
int ddc_bus_freq;
ddc_bus_freq = (hdmi->tmds_rate >> 2) / HDMI_SCL_RATE;
hdmi_writeb(hdmi, DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF);
hdmi_writeb(hdmi, DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF);
/* Clear the EDID interrupt flag and mute the interrupt */
hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
}
static void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable)
{
if (enable)
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON);
else
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF);
}
static void inno_hdmi_set_pwr_mode(struct inno_hdmi *hdmi, int mode)
{
switch (mode) {
case NORMAL:
inno_hdmi_sys_power(hdmi, false);
hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x6f);
hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0xbb);
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15);
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x14);
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x10);
hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x0f);
hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x00);
hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x01);
inno_hdmi_sys_power(hdmi, true);
break;
case LOWER_PWR:
inno_hdmi_sys_power(hdmi, false);
hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0x00);
hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x00);
hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x00);
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15);
break;
default:
dev_err(hdmi->dev, "Unknown power mode %d\n", mode);
}
}
static void inno_hdmi_reset(struct inno_hdmi *hdmi)
{
u32 val;
u32 msk;
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_DIGITAL, v_NOT_RST_DIGITAL);
udelay(100);
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_ANALOG, v_NOT_RST_ANALOG);
udelay(100);
msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL;
val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH;
hdmi_modb(hdmi, HDMI_SYS_CTRL, msk, val);
inno_hdmi_set_pwr_mode(hdmi, NORMAL);
}
static int inno_hdmi_upload_frame(struct inno_hdmi *hdmi, int setup_rc,
union hdmi_infoframe *frame, u32 frame_index,
u32 mask, u32 disable, u32 enable)
{
if (mask)
hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, disable);
hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, frame_index);
if (setup_rc >= 0) {
u8 packed_frame[HDMI_MAXIMUM_INFO_FRAME_SIZE];
ssize_t rc, i;
rc = hdmi_infoframe_pack(frame, packed_frame,
sizeof(packed_frame));
if (rc < 0)
return rc;
for (i = 0; i < rc; i++)
hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i,
packed_frame[i]);
if (mask)
hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, enable);
}
return setup_rc;
}
static int inno_hdmi_config_video_vsi(struct inno_hdmi *hdmi,
struct drm_display_mode *mode)
{
union hdmi_infoframe frame;
int rc;
rc = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi,
mode);
return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_VSI,
m_PACKET_VSI_EN, v_PACKET_VSI_EN(0), v_PACKET_VSI_EN(1));
}
static int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi,
struct drm_display_mode *mode)
{
union hdmi_infoframe frame;
int rc;
rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode);
if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV444)
frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
else if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV422)
frame.avi.colorspace = HDMI_COLORSPACE_YUV422;
else
frame.avi.colorspace = HDMI_COLORSPACE_RGB;
return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_AVI, 0, 0, 0);
}
static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi)
{
struct hdmi_data_info *data = &hdmi->hdmi_data;
int c0_c2_change = 0;
int csc_enable = 0;
int csc_mode = 0;
int auto_csc = 0;
int value;
int i;
/* Input video mode is SDR RGB24bit, data enable signal from external */
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL |
v_VIDEO_INPUT_FORMAT(VIDEO_INPUT_SDR_RGB444));
/* Input color hardcode to RGB, and output color hardcode to RGB888 */
value = v_VIDEO_INPUT_BITS(VIDEO_INPUT_8BITS) |
v_VIDEO_OUTPUT_COLOR(0) |
v_VIDEO_INPUT_CSP(0);
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, value);
if (data->enc_in_format == data->enc_out_format) {
if ((data->enc_in_format == HDMI_COLORSPACE_RGB) ||
(data->enc_in_format >= HDMI_COLORSPACE_YUV444)) {
value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED(1);
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value);
hdmi_modb(hdmi, HDMI_VIDEO_CONTRL,
m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP,
v_VIDEO_AUTO_CSC(AUTO_CSC_DISABLE) |
v_VIDEO_C0_C2_SWAP(C0_C2_CHANGE_DISABLE));
return 0;
}
}
if (data->colorimetry == HDMI_COLORIMETRY_ITU_601) {
if ((data->enc_in_format == HDMI_COLORSPACE_RGB) &&
(data->enc_out_format == HDMI_COLORSPACE_YUV444)) {
csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT;
auto_csc = AUTO_CSC_DISABLE;
c0_c2_change = C0_C2_CHANGE_DISABLE;
csc_enable = v_CSC_ENABLE;
} else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) &&
(data->enc_out_format == HDMI_COLORSPACE_RGB)) {
csc_mode = CSC_ITU601_16_235_TO_RGB_0_255_8BIT;
auto_csc = AUTO_CSC_ENABLE;
c0_c2_change = C0_C2_CHANGE_DISABLE;
csc_enable = v_CSC_DISABLE;
}
} else {
if ((data->enc_in_format == HDMI_COLORSPACE_RGB) &&
(data->enc_out_format == HDMI_COLORSPACE_YUV444)) {
csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT;
auto_csc = AUTO_CSC_DISABLE;
c0_c2_change = C0_C2_CHANGE_DISABLE;
csc_enable = v_CSC_ENABLE;
} else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) &&
(data->enc_out_format == HDMI_COLORSPACE_RGB)) {
csc_mode = CSC_ITU709_16_235_TO_RGB_0_255_8BIT;
auto_csc = AUTO_CSC_ENABLE;
c0_c2_change = C0_C2_CHANGE_DISABLE;
csc_enable = v_CSC_DISABLE;
}
}
for (i = 0; i < 24; i++)
hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i,
coeff_csc[csc_mode][i]);
value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1);
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value);
hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, m_VIDEO_AUTO_CSC |
m_VIDEO_C0_C2_SWAP, v_VIDEO_AUTO_CSC(auto_csc) |
v_VIDEO_C0_C2_SWAP(c0_c2_change));
return 0;
}
static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi,
struct drm_display_mode *mode)
{
int value;
/* Set detail external video timing polarity and interlace mode */
value = v_EXTERANL_VIDEO(1);
value |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0);
value |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0);
value |= mode->flags & DRM_MODE_FLAG_INTERLACE ?
v_INETLACE(1) : v_INETLACE(0);
hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value);
/* Set detail external video timing */
value = mode->htotal;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF);
value = mode->htotal - mode->hdisplay;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF);
value = mode->hsync_start - mode->hdisplay;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF);
value = mode->hsync_end - mode->hsync_start;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF);
value = mode->vtotal;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF);
value = mode->vtotal - mode->vdisplay;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF);
value = mode->vsync_start - mode->vdisplay;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF);
value = mode->vsync_end - mode->vsync_start;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF);
hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, 0x1e);
hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, 0x2c);
hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, 0x01);
return 0;
}
static int inno_hdmi_setup(struct inno_hdmi *hdmi,
struct drm_display_mode *mode)
{
hdmi->hdmi_data.vic = drm_match_cea_mode(mode);
hdmi->hdmi_data.enc_in_format = HDMI_COLORSPACE_RGB;
hdmi->hdmi_data.enc_out_format = HDMI_COLORSPACE_RGB;
if ((hdmi->hdmi_data.vic == 6) || (hdmi->hdmi_data.vic == 7) ||
(hdmi->hdmi_data.vic == 21) || (hdmi->hdmi_data.vic == 22) ||
(hdmi->hdmi_data.vic == 2) || (hdmi->hdmi_data.vic == 3) ||
(hdmi->hdmi_data.vic == 17) || (hdmi->hdmi_data.vic == 18))
hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_601;
else
hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709;
/* Mute video and audio output */
hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK,
v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1));
/* Set HDMI Mode */
hdmi_writeb(hdmi, HDMI_HDCP_CTRL,
v_HDMI_DVI(hdmi->hdmi_data.sink_is_hdmi));
inno_hdmi_config_video_timing(hdmi, mode);
inno_hdmi_config_video_csc(hdmi);
if (hdmi->hdmi_data.sink_is_hdmi) {
inno_hdmi_config_video_avi(hdmi, mode);
inno_hdmi_config_video_vsi(hdmi, mode);
}
/*
* When IP controller have configured to an accurate video
* timing, then the TMDS clock source would be switched to
* DCLK_LCDC, so we need to init the TMDS rate to mode pixel
* clock rate, and reconfigure the DDC clock.
*/
hdmi->tmds_rate = mode->clock * 1000;
inno_hdmi_i2c_init(hdmi);
/* Unmute video and audio output */
hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK,
v_AUDIO_MUTE(0) | v_VIDEO_MUTE(0));
return 0;
}
static void inno_hdmi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
{
struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
inno_hdmi_setup(hdmi, adj_mode);
/* Store the display mode for plugin/DPMS poweron events */
memcpy(&hdmi->previous_mode, adj_mode, sizeof(hdmi->previous_mode));
}
static void inno_hdmi_encoder_enable(struct drm_encoder *encoder)
{
struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
rockchip_drm_crtc_mode_config(encoder->crtc, DRM_MODE_CONNECTOR_HDMIA,
ROCKCHIP_OUT_MODE_P888);
inno_hdmi_set_pwr_mode(hdmi, NORMAL);
}
static void inno_hdmi_encoder_disable(struct drm_encoder *encoder)
{
struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
inno_hdmi_set_pwr_mode(hdmi, LOWER_PWR);
}
static bool inno_hdmi_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
{
return true;
}
static struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = {
.enable = inno_hdmi_encoder_enable,
.disable = inno_hdmi_encoder_disable,
.mode_fixup = inno_hdmi_encoder_mode_fixup,
.mode_set = inno_hdmi_encoder_mode_set,
};
static struct drm_encoder_funcs inno_hdmi_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
static enum drm_connector_status
inno_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct inno_hdmi *hdmi = to_inno_hdmi(connector);
return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ?
connector_status_connected : connector_status_disconnected;
}
static int inno_hdmi_connector_get_modes(struct drm_connector *connector)
{
struct inno_hdmi *hdmi = to_inno_hdmi(connector);
struct edid *edid;
int ret = 0;
if (!hdmi->ddc)
return 0;
edid = drm_get_edid(connector, hdmi->ddc);
if (edid) {
hdmi->hdmi_data.sink_is_hdmi = drm_detect_hdmi_monitor(edid);
hdmi->hdmi_data.sink_has_audio = drm_detect_monitor_audio(edid);
drm_mode_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
}
return ret;
}
static enum drm_mode_status
inno_hdmi_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static struct drm_encoder *
inno_hdmi_connector_best_encoder(struct drm_connector *connector)
{
struct inno_hdmi *hdmi = to_inno_hdmi(connector);
return &hdmi->encoder;
}
static int
inno_hdmi_probe_single_connector_modes(struct drm_connector *connector,
uint32_t maxX, uint32_t maxY)
{
return drm_helper_probe_single_connector_modes(connector, 1920, 1080);
}
static void inno_hdmi_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static struct drm_connector_funcs inno_hdmi_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.fill_modes = inno_hdmi_probe_single_connector_modes,
.detect = inno_hdmi_connector_detect,
.destroy = inno_hdmi_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 struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = {
.get_modes = inno_hdmi_connector_get_modes,
.mode_valid = inno_hdmi_connector_mode_valid,
.best_encoder = inno_hdmi_connector_best_encoder,
};
static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi)
{
struct drm_encoder *encoder = &hdmi->encoder;
struct device *dev = hdmi->dev;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
/*
* If we failed to find the CRTC(s) which this encoder is
* supposed to be connected to, it's because the CRTC has
* not been registered yet. Defer probing, and hope that
* the required CRTC is added later.
*/
if (encoder->possible_crtcs == 0)
return -EPROBE_DEFER;
drm_encoder_helper_add(encoder, &inno_hdmi_encoder_helper_funcs);
drm_encoder_init(drm, encoder, &inno_hdmi_encoder_funcs,
DRM_MODE_ENCODER_TMDS, NULL);
hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
drm_connector_helper_add(&hdmi->connector,
&inno_hdmi_connector_helper_funcs);
drm_connector_init(drm, &hdmi->connector, &inno_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA);
drm_mode_connector_attach_encoder(&hdmi->connector, encoder);
return 0;
}
static irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi)
{
struct inno_hdmi_i2c *i2c = hdmi->i2c;
u8 stat;
stat = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1);
if (!(stat & m_INT_EDID_READY))
return IRQ_NONE;
/* Clear HDMI EDID interrupt flag */
hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
complete(&i2c->cmp);
return IRQ_HANDLED;
}
static irqreturn_t inno_hdmi_hardirq(int irq, void *dev_id)
{
struct inno_hdmi *hdmi = dev_id;
irqreturn_t ret = IRQ_NONE;
u8 interrupt;
if (hdmi->i2c)
ret = inno_hdmi_i2c_irq(hdmi);
interrupt = hdmi_readb(hdmi, HDMI_STATUS);
if (interrupt & m_INT_HOTPLUG) {
hdmi_modb(hdmi, HDMI_STATUS, m_INT_HOTPLUG, m_INT_HOTPLUG);
ret = IRQ_WAKE_THREAD;
}
return ret;
}
static irqreturn_t inno_hdmi_irq(int irq, void *dev_id)
{
struct inno_hdmi *hdmi = dev_id;
drm_helper_hpd_irq_event(hdmi->connector.dev);
return IRQ_HANDLED;
}
static int inno_hdmi_i2c_read(struct inno_hdmi *hdmi, struct i2c_msg *msgs)
{
int length = msgs->len;
u8 *buf = msgs->buf;
int ret;
ret = wait_for_completion_timeout(&hdmi->i2c->cmp, HZ / 10);
if (!ret)
return -EAGAIN;
while (length--)
*buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR);
return 0;
}
static int inno_hdmi_i2c_write(struct inno_hdmi *hdmi, struct i2c_msg *msgs)
{
/*
* The DDC module only support read EDID message, so
* we assume that each word write to this i2c adapter
* should be the offset of EDID word address.
*/
if ((msgs->len != 1) ||
((msgs->addr != DDC_ADDR) && (msgs->addr != DDC_SEGMENT_ADDR)))
return -EINVAL;
reinit_completion(&hdmi->i2c->cmp);
if (msgs->addr == DDC_SEGMENT_ADDR)
hdmi->i2c->segment_addr = msgs->buf[0];
if (msgs->addr == DDC_ADDR)
hdmi->i2c->ddc_addr = msgs->buf[0];
/* Set edid fifo first addr */
hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, 0x00);
/* Set edid word address 0x00/0x80 */
hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr);
/* Set edid segment pointer */
hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr);
return 0;
}
static int inno_hdmi_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct inno_hdmi *hdmi = i2c_get_adapdata(adap);
struct inno_hdmi_i2c *i2c = hdmi->i2c;
int i, ret = 0;
mutex_lock(&i2c->lock);
/* Clear the EDID interrupt flag and unmute the interrupt */
hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY);
hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
for (i = 0; i < num; i++) {
dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n",
i + 1, num, msgs[i].len, msgs[i].flags);
if (msgs[i].flags & I2C_M_RD)
ret = inno_hdmi_i2c_read(hdmi, &msgs[i]);
else
ret = inno_hdmi_i2c_write(hdmi, &msgs[i]);
if (ret < 0)
break;
}
if (!ret)
ret = num;
/* Mute HDMI EDID interrupt */
hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
mutex_unlock(&i2c->lock);
return ret;
}
static u32 inno_hdmi_i2c_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm inno_hdmi_algorithm = {
.master_xfer = inno_hdmi_i2c_xfer,
.functionality = inno_hdmi_i2c_func,
};
static struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi)
{
struct i2c_adapter *adap;
struct inno_hdmi_i2c *i2c;
int ret;
i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
if (!i2c)
return ERR_PTR(-ENOMEM);
mutex_init(&i2c->lock);
init_completion(&i2c->cmp);
adap = &i2c->adap;
adap->class = I2C_CLASS_DDC;
adap->owner = THIS_MODULE;
adap->dev.parent = hdmi->dev;
adap->dev.of_node = hdmi->dev->of_node;
adap->algo = &inno_hdmi_algorithm;
strlcpy(adap->name, "Inno HDMI", sizeof(adap->name));
i2c_set_adapdata(adap, hdmi);
ret = i2c_add_adapter(adap);
if (ret) {
dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
devm_kfree(hdmi->dev, i2c);
return ERR_PTR(ret);
}
hdmi->i2c = i2c;
dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
return adap;
}
static int inno_hdmi_bind(struct device *dev, struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = data;
struct inno_hdmi *hdmi;
struct resource *iores;
int irq;
int ret;
hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
hdmi->dev = dev;
hdmi->drm_dev = drm;
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iores)
return -ENXIO;
hdmi->regs = devm_ioremap_resource(dev, iores);
if (IS_ERR(hdmi->regs))
return PTR_ERR(hdmi->regs);
hdmi->pclk = devm_clk_get(hdmi->dev, "pclk");
if (IS_ERR(hdmi->pclk)) {
dev_err(hdmi->dev, "Unable to get HDMI pclk clk\n");
return PTR_ERR(hdmi->pclk);
}
ret = clk_prepare_enable(hdmi->pclk);
if (ret) {
dev_err(hdmi->dev, "Cannot enable HDMI pclk clock: %d\n", ret);
return ret;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
inno_hdmi_reset(hdmi);
hdmi->ddc = inno_hdmi_i2c_adapter(hdmi);
if (IS_ERR(hdmi->ddc)) {
hdmi->ddc = NULL;
return PTR_ERR(hdmi->ddc);
}
/*
* When IP controller haven't configured to an accurate video
* timing, then the TMDS clock source would be switched to
* PCLK_HDMI, so we need to init the TMDS rate to PCLK rate,
* and reconfigure the DDC clock.
*/
hdmi->tmds_rate = clk_get_rate(hdmi->pclk);
inno_hdmi_i2c_init(hdmi);
ret = inno_hdmi_register(drm, hdmi);
if (ret)
return ret;
dev_set_drvdata(dev, hdmi);
/* Unmute hotplug interrupt */
hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1));
ret = devm_request_threaded_irq(dev, irq, inno_hdmi_hardirq,
inno_hdmi_irq, IRQF_SHARED,
dev_name(dev), hdmi);
return ret;
}
static void inno_hdmi_unbind(struct device *dev, struct device *master,
void *data)
{
struct inno_hdmi *hdmi = dev_get_drvdata(dev);
hdmi->connector.funcs->destroy(&hdmi->connector);
hdmi->encoder.funcs->destroy(&hdmi->encoder);
clk_disable_unprepare(hdmi->pclk);
i2c_put_adapter(hdmi->ddc);
}
static const struct component_ops inno_hdmi_ops = {
.bind = inno_hdmi_bind,
.unbind = inno_hdmi_unbind,
};
static int inno_hdmi_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &inno_hdmi_ops);
}
static int inno_hdmi_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &inno_hdmi_ops);
return 0;
}
static const struct of_device_id inno_hdmi_dt_ids[] = {
{ .compatible = "rockchip,rk3036-inno-hdmi",
},
{},
};
MODULE_DEVICE_TABLE(of, inno_hdmi_dt_ids);
static struct platform_driver inno_hdmi_driver = {
.probe = inno_hdmi_probe,
.remove = inno_hdmi_remove,
.driver = {
.name = "innohdmi-rockchip",
.of_match_table = inno_hdmi_dt_ids,
},
};
module_platform_driver(inno_hdmi_driver);
MODULE_AUTHOR("Zheng Yang <zhengyang@rock-chips.com>");
MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
MODULE_DESCRIPTION("Rockchip Specific INNO-HDMI Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:innohdmi-rockchip");

View File

@@ -0,0 +1,362 @@
/*
* Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
* Zheng Yang <zhengyang@rock-chips.com>
* Yakir Yang <ykk@rock-chips.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef __INNO_HDMI_H__
#define __INNO_HDMI_H__
#define DDC_SEGMENT_ADDR 0x30
enum PWR_MODE {
NORMAL,
LOWER_PWR,
};
#define HDMI_SCL_RATE (100*1000)
#define DDC_BUS_FREQ_L 0x4b
#define DDC_BUS_FREQ_H 0x4c
#define HDMI_SYS_CTRL 0x00
#define m_RST_ANALOG (1 << 6)
#define v_RST_ANALOG (0 << 6)
#define v_NOT_RST_ANALOG (1 << 6)
#define m_RST_DIGITAL (1 << 5)
#define v_RST_DIGITAL (0 << 5)
#define v_NOT_RST_DIGITAL (1 << 5)
#define m_REG_CLK_INV (1 << 4)
#define v_REG_CLK_NOT_INV (0 << 4)
#define v_REG_CLK_INV (1 << 4)
#define m_VCLK_INV (1 << 3)
#define v_VCLK_NOT_INV (0 << 3)
#define v_VCLK_INV (1 << 3)
#define m_REG_CLK_SOURCE (1 << 2)
#define v_REG_CLK_SOURCE_TMDS (0 << 2)
#define v_REG_CLK_SOURCE_SYS (1 << 2)
#define m_POWER (1 << 1)
#define v_PWR_ON (0 << 1)
#define v_PWR_OFF (1 << 1)
#define m_INT_POL (1 << 0)
#define v_INT_POL_HIGH 1
#define v_INT_POL_LOW 0
#define HDMI_VIDEO_CONTRL1 0x01
#define m_VIDEO_INPUT_FORMAT (7 << 1)
#define m_DE_SOURCE (1 << 0)
#define v_VIDEO_INPUT_FORMAT(n) (n << 1)
#define v_DE_EXTERNAL 1
#define v_DE_INTERNAL 0
enum {
VIDEO_INPUT_SDR_RGB444 = 0,
VIDEO_INPUT_DDR_RGB444 = 5,
VIDEO_INPUT_DDR_YCBCR422 = 6
};
#define HDMI_VIDEO_CONTRL2 0x02
#define m_VIDEO_OUTPUT_COLOR (3 << 6)
#define m_VIDEO_INPUT_BITS (3 << 4)
#define m_VIDEO_INPUT_CSP (1 << 0)
#define v_VIDEO_OUTPUT_COLOR(n) (((n) & 0x3) << 6)
#define v_VIDEO_INPUT_BITS(n) (n << 4)
#define v_VIDEO_INPUT_CSP(n) (n << 0)
enum {
VIDEO_INPUT_12BITS = 0,
VIDEO_INPUT_10BITS = 1,
VIDEO_INPUT_REVERT = 2,
VIDEO_INPUT_8BITS = 3,
};
#define HDMI_VIDEO_CONTRL 0x03
#define m_VIDEO_AUTO_CSC (1 << 7)
#define v_VIDEO_AUTO_CSC(n) (n << 7)
#define m_VIDEO_C0_C2_SWAP (1 << 0)
#define v_VIDEO_C0_C2_SWAP(n) (n << 0)
enum {
C0_C2_CHANGE_ENABLE = 0,
C0_C2_CHANGE_DISABLE = 1,
AUTO_CSC_DISABLE = 0,
AUTO_CSC_ENABLE = 1,
};
#define HDMI_VIDEO_CONTRL3 0x04
#define m_COLOR_DEPTH_NOT_INDICATED (1 << 4)
#define m_SOF (1 << 3)
#define m_COLOR_RANGE (1 << 2)
#define m_CSC (1 << 0)
#define v_COLOR_DEPTH_NOT_INDICATED(n) ((n) << 4)
#define v_SOF_ENABLE (0 << 3)
#define v_SOF_DISABLE (1 << 3)
#define v_COLOR_RANGE_FULL (1 << 2)
#define v_COLOR_RANGE_LIMITED (0 << 2)
#define v_CSC_ENABLE 1
#define v_CSC_DISABLE 0
#define HDMI_AV_MUTE 0x05
#define m_AVMUTE_CLEAR (1 << 7)
#define m_AVMUTE_ENABLE (1 << 6)
#define m_AUDIO_MUTE (1 << 1)
#define m_VIDEO_BLACK (1 << 0)
#define v_AVMUTE_CLEAR(n) (n << 7)
#define v_AVMUTE_ENABLE(n) (n << 6)
#define v_AUDIO_MUTE(n) (n << 1)
#define v_VIDEO_MUTE(n) (n << 0)
#define HDMI_VIDEO_TIMING_CTL 0x08
#define v_HSYNC_POLARITY(n) (n << 3)
#define v_VSYNC_POLARITY(n) (n << 2)
#define v_INETLACE(n) (n << 1)
#define v_EXTERANL_VIDEO(n) (n << 0)
#define HDMI_VIDEO_EXT_HTOTAL_L 0x09
#define HDMI_VIDEO_EXT_HTOTAL_H 0x0a
#define HDMI_VIDEO_EXT_HBLANK_L 0x0b
#define HDMI_VIDEO_EXT_HBLANK_H 0x0c
#define HDMI_VIDEO_EXT_HDELAY_L 0x0d
#define HDMI_VIDEO_EXT_HDELAY_H 0x0e
#define HDMI_VIDEO_EXT_HDURATION_L 0x0f
#define HDMI_VIDEO_EXT_HDURATION_H 0x10
#define HDMI_VIDEO_EXT_VTOTAL_L 0x11
#define HDMI_VIDEO_EXT_VTOTAL_H 0x12
#define HDMI_VIDEO_EXT_VBLANK 0x13
#define HDMI_VIDEO_EXT_VDELAY 0x14
#define HDMI_VIDEO_EXT_VDURATION 0x15
#define HDMI_VIDEO_CSC_COEF 0x18
#define HDMI_AUDIO_CTRL1 0x35
enum {
CTS_SOURCE_INTERNAL = 0,
CTS_SOURCE_EXTERNAL = 1,
};
#define v_CTS_SOURCE(n) (n << 7)
enum {
DOWNSAMPLE_DISABLE = 0,
DOWNSAMPLE_1_2 = 1,
DOWNSAMPLE_1_4 = 2,
};
#define v_DOWN_SAMPLE(n) (n << 5)
enum {
AUDIO_SOURCE_IIS = 0,
AUDIO_SOURCE_SPDIF = 1,
};
#define v_AUDIO_SOURCE(n) (n << 3)
#define v_MCLK_ENABLE(n) (n << 2)
enum {
MCLK_128FS = 0,
MCLK_256FS = 1,
MCLK_384FS = 2,
MCLK_512FS = 3,
};
#define v_MCLK_RATIO(n) (n)
#define AUDIO_SAMPLE_RATE 0x37
enum {
AUDIO_32K = 0x3,
AUDIO_441K = 0x0,
AUDIO_48K = 0x2,
AUDIO_882K = 0x8,
AUDIO_96K = 0xa,
AUDIO_1764K = 0xc,
AUDIO_192K = 0xe,
};
#define AUDIO_I2S_MODE 0x38
enum {
I2S_CHANNEL_1_2 = 1,
I2S_CHANNEL_3_4 = 3,
I2S_CHANNEL_5_6 = 7,
I2S_CHANNEL_7_8 = 0xf
};
#define v_I2S_CHANNEL(n) ((n) << 2)
enum {
I2S_STANDARD = 0,
I2S_LEFT_JUSTIFIED = 1,
I2S_RIGHT_JUSTIFIED = 2,
};
#define v_I2S_MODE(n) (n)
#define AUDIO_I2S_MAP 0x39
#define AUDIO_I2S_SWAPS_SPDIF 0x3a
#define v_SPIDF_FREQ(n) (n)
#define N_32K 0x1000
#define N_441K 0x1880
#define N_882K 0x3100
#define N_1764K 0x6200
#define N_48K 0x1800
#define N_96K 0x3000
#define N_192K 0x6000
#define HDMI_AUDIO_CHANNEL_STATUS 0x3e
#define m_AUDIO_STATUS_NLPCM (1 << 7)
#define m_AUDIO_STATUS_USE (1 << 6)
#define m_AUDIO_STATUS_COPYRIGHT (1 << 5)
#define m_AUDIO_STATUS_ADDITION (3 << 2)
#define m_AUDIO_STATUS_CLK_ACCURACY (2 << 0)
#define v_AUDIO_STATUS_NLPCM(n) ((n & 1) << 7)
#define AUDIO_N_H 0x3f
#define AUDIO_N_M 0x40
#define AUDIO_N_L 0x41
#define HDMI_AUDIO_CTS_H 0x45
#define HDMI_AUDIO_CTS_M 0x46
#define HDMI_AUDIO_CTS_L 0x47
#define HDMI_DDC_CLK_L 0x4b
#define HDMI_DDC_CLK_H 0x4c
#define HDMI_EDID_SEGMENT_POINTER 0x4d
#define HDMI_EDID_WORD_ADDR 0x4e
#define HDMI_EDID_FIFO_OFFSET 0x4f
#define HDMI_EDID_FIFO_ADDR 0x50
#define HDMI_PACKET_SEND_MANUAL 0x9c
#define HDMI_PACKET_SEND_AUTO 0x9d
#define m_PACKET_GCP_EN (1 << 7)
#define m_PACKET_MSI_EN (1 << 6)
#define m_PACKET_SDI_EN (1 << 5)
#define m_PACKET_VSI_EN (1 << 4)
#define v_PACKET_GCP_EN(n) ((n & 1) << 7)
#define v_PACKET_MSI_EN(n) ((n & 1) << 6)
#define v_PACKET_SDI_EN(n) ((n & 1) << 5)
#define v_PACKET_VSI_EN(n) ((n & 1) << 4)
#define HDMI_CONTROL_PACKET_BUF_INDEX 0x9f
enum {
INFOFRAME_VSI = 0x05,
INFOFRAME_AVI = 0x06,
INFOFRAME_AAI = 0x08,
};
#define HDMI_CONTROL_PACKET_ADDR 0xa0
#define HDMI_MAXIMUM_INFO_FRAME_SIZE 0x11
enum {
AVI_COLOR_MODE_RGB = 0,
AVI_COLOR_MODE_YCBCR422 = 1,
AVI_COLOR_MODE_YCBCR444 = 2,
AVI_COLORIMETRY_NO_DATA = 0,
AVI_COLORIMETRY_SMPTE_170M = 1,
AVI_COLORIMETRY_ITU709 = 2,
AVI_COLORIMETRY_EXTENDED = 3,
AVI_CODED_FRAME_ASPECT_NO_DATA = 0,
AVI_CODED_FRAME_ASPECT_4_3 = 1,
AVI_CODED_FRAME_ASPECT_16_9 = 2,
ACTIVE_ASPECT_RATE_SAME_AS_CODED_FRAME = 0x08,
ACTIVE_ASPECT_RATE_4_3 = 0x09,
ACTIVE_ASPECT_RATE_16_9 = 0x0A,
ACTIVE_ASPECT_RATE_14_9 = 0x0B,
};
#define HDMI_HDCP_CTRL 0x52
#define m_HDMI_DVI (1 << 1)
#define v_HDMI_DVI(n) (n << 1)
#define HDMI_INTERRUPT_MASK1 0xc0
#define HDMI_INTERRUPT_STATUS1 0xc1
#define m_INT_ACTIVE_VSYNC (1 << 5)
#define m_INT_EDID_READY (1 << 2)
#define HDMI_INTERRUPT_MASK2 0xc2
#define HDMI_INTERRUPT_STATUS2 0xc3
#define m_INT_HDCP_ERR (1 << 7)
#define m_INT_BKSV_FLAG (1 << 6)
#define m_INT_HDCP_OK (1 << 4)
#define HDMI_STATUS 0xc8
#define m_HOTPLUG (1 << 7)
#define m_MASK_INT_HOTPLUG (1 << 5)
#define m_INT_HOTPLUG (1 << 1)
#define v_MASK_INT_HOTPLUG(n) ((n & 0x1) << 5)
#define HDMI_COLORBAR 0xc9
#define HDMI_PHY_SYNC 0xce
#define HDMI_PHY_SYS_CTL 0xe0
#define m_TMDS_CLK_SOURCE (1 << 5)
#define v_TMDS_FROM_PLL (0 << 5)
#define v_TMDS_FROM_GEN (1 << 5)
#define m_PHASE_CLK (1 << 4)
#define v_DEFAULT_PHASE (0 << 4)
#define v_SYNC_PHASE (1 << 4)
#define m_TMDS_CURRENT_PWR (1 << 3)
#define v_TURN_ON_CURRENT (0 << 3)
#define v_CAT_OFF_CURRENT (1 << 3)
#define m_BANDGAP_PWR (1 << 2)
#define v_BANDGAP_PWR_UP (0 << 2)
#define v_BANDGAP_PWR_DOWN (1 << 2)
#define m_PLL_PWR (1 << 1)
#define v_PLL_PWR_UP (0 << 1)
#define v_PLL_PWR_DOWN (1 << 1)
#define m_TMDS_CHG_PWR (1 << 0)
#define v_TMDS_CHG_PWR_UP (0 << 0)
#define v_TMDS_CHG_PWR_DOWN (1 << 0)
#define HDMI_PHY_CHG_PWR 0xe1
#define v_CLK_CHG_PWR(n) ((n & 1) << 3)
#define v_DATA_CHG_PWR(n) ((n & 7) << 0)
#define HDMI_PHY_DRIVER 0xe2
#define v_CLK_MAIN_DRIVER(n) (n << 4)
#define v_DATA_MAIN_DRIVER(n) (n << 0)
#define HDMI_PHY_PRE_EMPHASIS 0xe3
#define v_PRE_EMPHASIS(n) ((n & 7) << 4)
#define v_CLK_PRE_DRIVER(n) ((n & 3) << 2)
#define v_DATA_PRE_DRIVER(n) ((n & 3) << 0)
#define HDMI_PHY_FEEDBACK_DIV_RATIO_LOW 0xe7
#define v_FEEDBACK_DIV_LOW(n) (n & 0xff)
#define HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH 0xe8
#define v_FEEDBACK_DIV_HIGH(n) (n & 1)
#define HDMI_PHY_PRE_DIV_RATIO 0xed
#define v_PRE_DIV_RATIO(n) (n & 0x1f)
#define HDMI_CEC_CTRL 0xd0
#define m_ADJUST_FOR_HISENSE (1 << 6)
#define m_REJECT_RX_BROADCAST (1 << 5)
#define m_BUSFREETIME_ENABLE (1 << 2)
#define m_REJECT_RX (1 << 1)
#define m_START_TX (1 << 0)
#define HDMI_CEC_DATA 0xd1
#define HDMI_CEC_TX_OFFSET 0xd2
#define HDMI_CEC_RX_OFFSET 0xd3
#define HDMI_CEC_CLK_H 0xd4
#define HDMI_CEC_CLK_L 0xd5
#define HDMI_CEC_TX_LENGTH 0xd6
#define HDMI_CEC_RX_LENGTH 0xd7
#define HDMI_CEC_TX_INT_MASK 0xd8
#define m_TX_DONE (1 << 3)
#define m_TX_NOACK (1 << 2)
#define m_TX_BROADCAST_REJ (1 << 1)
#define m_TX_BUSNOTFREE (1 << 0)
#define HDMI_CEC_RX_INT_MASK 0xd9
#define m_RX_LA_ERR (1 << 4)
#define m_RX_GLITCH (1 << 3)
#define m_RX_DONE (1 << 0)
#define HDMI_CEC_TX_INT 0xda
#define HDMI_CEC_RX_INT 0xdb
#define HDMI_CEC_BUSFREETIME_L 0xdc
#define HDMI_CEC_BUSFREETIME_H 0xdd
#define HDMI_CEC_LOGICADDR 0xde
#endif /* __INNO_HDMI_H__ */

View File

@@ -55,14 +55,12 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
return arm_iommu_attach_device(dev, mapping);
}
EXPORT_SYMBOL_GPL(rockchip_drm_dma_attach_device);
void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
struct device *dev)
{
arm_iommu_detach_device(dev);
}
EXPORT_SYMBOL_GPL(rockchip_drm_dma_detach_device);
int rockchip_register_crtc_funcs(struct drm_crtc *crtc,
const struct rockchip_crtc_funcs *crtc_funcs)
@@ -77,7 +75,6 @@ int rockchip_register_crtc_funcs(struct drm_crtc *crtc,
return 0;
}
EXPORT_SYMBOL_GPL(rockchip_register_crtc_funcs);
void rockchip_unregister_crtc_funcs(struct drm_crtc *crtc)
{
@@ -89,7 +86,6 @@ void rockchip_unregister_crtc_funcs(struct drm_crtc *crtc)
priv->crtc_funcs[pipe] = NULL;
}
EXPORT_SYMBOL_GPL(rockchip_unregister_crtc_funcs);
static struct drm_crtc *rockchip_crtc_from_pipe(struct drm_device *drm,
int pipe)

View File

@@ -39,7 +39,6 @@ struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb,
return rk_fb->obj[plane];
}
EXPORT_SYMBOL_GPL(rockchip_fb_get_gem_obj);
static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb)
{
@@ -177,8 +176,23 @@ static void rockchip_crtc_wait_for_update(struct drm_crtc *crtc)
crtc_funcs->wait_for_update(crtc);
}
/*
* We can't use drm_atomic_helper_wait_for_vblanks() because rk3288 and rk3066
* have hardware counters for neither vblanks nor scanlines, which results in
* a race where:
* | <-- HW vsync irq and reg take effect
* plane_commit --> |
* get_vblank and wait --> |
* | <-- handle_vblank, vblank->count + 1
* cleanup_fb --> |
* iommu crash --> |
* | <-- HW vsync irq and reg take effect
*
* This function is equivalent but uses rockchip_crtc_wait_for_update() instead
* of waiting for vblank_count to change.
*/
static void
rockchip_atomic_wait_for_complete(struct drm_atomic_state *old_state)
rockchip_atomic_wait_for_complete(struct drm_device *dev, struct drm_atomic_state *old_state)
{
struct drm_crtc_state *old_crtc_state;
struct drm_crtc *crtc;
@@ -194,6 +208,10 @@ rockchip_atomic_wait_for_complete(struct drm_atomic_state *old_state)
if (!crtc->state->active)
continue;
if (!drm_atomic_helper_framebuffer_changed(dev,
old_state, crtc))
continue;
ret = drm_crtc_vblank_get(crtc);
if (ret != 0)
continue;
@@ -241,7 +259,7 @@ rockchip_atomic_commit_complete(struct rockchip_atomic_commit *commit)
drm_atomic_helper_commit_planes(dev, state, true);
rockchip_atomic_wait_for_complete(state);
rockchip_atomic_wait_for_complete(dev, state);
drm_atomic_helper_cleanup_planes(dev, state);

View File

@@ -15,7 +15,18 @@
#ifndef _ROCKCHIP_DRM_FBDEV_H
#define _ROCKCHIP_DRM_FBDEV_H
#ifdef CONFIG_DRM_FBDEV_EMULATION
int rockchip_drm_fbdev_init(struct drm_device *dev);
void rockchip_drm_fbdev_fini(struct drm_device *dev);
#else
static inline int rockchip_drm_fbdev_init(struct drm_device *dev)
{
return 0;
}
static inline void rockchip_drm_fbdev_fini(struct drm_device *dev)
{
}
#endif
#endif /* _ROCKCHIP_DRM_FBDEV_H */

View File

@@ -234,13 +234,8 @@ int rockchip_gem_dumb_create(struct drm_file *file_priv,
/*
* align to 64 bytes since Mali requires it.
*/
min_pitch = ALIGN(min_pitch, 64);
if (args->pitch < min_pitch)
args->pitch = min_pitch;
if (args->size < args->pitch * args->height)
args->size = args->pitch * args->height;
args->pitch = ALIGN(min_pitch, 64);
args->size = args->pitch * args->height;
rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size,
&args->handle);