Commit ca18e142 authored by Dave Airlie's avatar Dave Airlie

Merge branch 'tilcdc-next' of git://people.freedesktop.org/~robclark/linux into drm-next

KMS driver for TI LCD controller

* 'tilcdc-next' of git://people.freedesktop.org/~robclark/linux:
  drm/tilcdc: add support for LCD panels (v5)
  drm/tilcdc: add encoder slave (v2)
  drm/i2c: nxp-tda998x (v3)
  drm/tilcdc: add TI LCD Controller DRM driver (v4)
  drm/nouveau: use i2c encoder helper wrappers
  drm: i2c encoder helper wrappers
  drm/cma: add debugfs helpers
  drm: small fix in drm_send_vblank_event()
  drm: Don't set the plane->fb to NULL on successfull set_plane
  drm/cma-helper: fixup compilation

Conflicts:
	drivers/gpu/drm/Kconfig
	drivers/gpu/drm/Makefile
	drivers/gpu/drm/drm_fb_cma_helper.c
parents 74e16974 0d4bbaf9
Device-Tree bindings for tilcdc DRM generic panel output driver
Required properties:
- compatible: value should be "ti,tilcdc,panel".
- panel-info: configuration info to configure LCDC correctly for the panel
- ac-bias: AC Bias Pin Frequency
- ac-bias-intrpt: AC Bias Pin Transitions per Interrupt
- dma-burst-sz: DMA burst size
- bpp: Bits per pixel
- fdd: FIFO DMA Request Delay
- sync-edge: Horizontal and Vertical Sync Edge: 0=rising 1=falling
- sync-ctrl: Horizontal and Vertical Sync: Control: 0=ignore
- raster-order: Raster Data Order Select: 1=Most-to-least 0=Least-to-most
- fifo-th: DMA FIFO threshold
- display-timings: typical videomode of lcd panel. Multiple video modes
can be listed if the panel supports multiple timings, but the 'native-mode'
should be the preferred/default resolution. Refer to
Documentation/devicetree/bindings/video/display-timing.txt for display
timing binding details.
Recommended properties:
- pinctrl-names, pinctrl-0: the pincontrol settings to configure
muxing properly for pins that connect to TFP410 device
Example:
/* Settings for CDTech_S035Q01 / LCD3 cape: */
lcd3 {
compatible = "ti,tilcdc,panel";
pinctrl-names = "default";
pinctrl-0 = <&bone_lcd3_cape_lcd_pins>;
panel-info {
ac-bias = <255>;
ac-bias-intrpt = <0>;
dma-burst-sz = <16>;
bpp = <16>;
fdd = <0x80>;
sync-edge = <0>;
sync-ctrl = <1>;
raster-order = <0>;
fifo-th = <0>;
};
display-timings {
native-mode = <&timing0>;
timing0: 320x240 {
hactive = <320>;
vactive = <240>;
hback-porch = <21>;
hfront-porch = <58>;
hsync-len = <47>;
vback-porch = <11>;
vfront-porch = <23>;
vsync-len = <2>;
clock-frequency = <8000000>;
hsync-active = <0>;
vsync-active = <0>;
};
};
};
Device-Tree bindings for tilcdc DRM encoder slave output driver
Required properties:
- compatible: value should be "ti,tilcdc,slave".
- i2c: the phandle for the i2c device the encoder slave is connected to
Recommended properties:
- pinctrl-names, pinctrl-0: the pincontrol settings to configure
muxing properly for pins that connect to TFP410 device
Example:
hdmi {
compatible = "ti,tilcdc,slave";
i2c = <&i2c0>;
pinctrl-names = "default";
pinctrl-0 = <&nxp_hdmi_bonelt_pins>;
};
Device-Tree bindings for tilcdc DRM TFP410 output driver
Required properties:
- compatible: value should be "ti,tilcdc,tfp410".
- i2c: the phandle for the i2c device to use for DDC
Recommended properties:
- pinctrl-names, pinctrl-0: the pincontrol settings to configure
muxing properly for pins that connect to TFP410 device
- powerdn-gpio: the powerdown GPIO, pulled low to power down the
TFP410 device (for DPMS_OFF)
Example:
dvicape {
compatible = "ti,tilcdc,tfp410";
i2c = <&i2c2>;
pinctrl-names = "default";
pinctrl-0 = <&bone_dvi_cape_dvi_00A1_pins>;
powerdn-gpio = <&gpio2 31 0>;
};
Device-Tree bindings for tilcdc DRM driver
Required properties:
- compatible: value should be "ti,am33xx-tilcdc".
- interrupts: the interrupt number
- reg: base address and size of the LCDC device
Recommended properties:
- interrupt-parent: the phandle for the interrupt controller that
services interrupts for this device.
- ti,hwmods: Name of the hwmod associated to the LCDC
Example:
fb: fb@4830e000 {
compatible = "ti,am33xx-tilcdc";
reg = <0x4830e000 0x1000>;
interrupt-parent = <&intc>;
interrupts = <36>;
ti,hwmods = "lcdc";
};
...@@ -217,3 +217,5 @@ source "drivers/gpu/drm/shmobile/Kconfig" ...@@ -217,3 +217,5 @@ source "drivers/gpu/drm/shmobile/Kconfig"
source "drivers/gpu/drm/tegra/Kconfig" source "drivers/gpu/drm/tegra/Kconfig"
source "drivers/gpu/drm/omapdrm/Kconfig" source "drivers/gpu/drm/omapdrm/Kconfig"
source "drivers/gpu/drm/tilcdc/Kconfig"
...@@ -51,4 +51,5 @@ obj-$(CONFIG_DRM_AST) += ast/ ...@@ -51,4 +51,5 @@ obj-$(CONFIG_DRM_AST) += ast/
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
obj-$(CONFIG_DRM_TEGRA) += tegra/ obj-$(CONFIG_DRM_TEGRA) += tegra/
obj-$(CONFIG_DRM_OMAP) += omapdrm/ obj-$(CONFIG_DRM_OMAP) += omapdrm/
obj-$(CONFIG_DRM_TILCDC) += tilcdc/
obj-y += i2c/ obj-y += i2c/
...@@ -123,3 +123,66 @@ void drm_i2c_encoder_destroy(struct drm_encoder *drm_encoder) ...@@ -123,3 +123,66 @@ void drm_i2c_encoder_destroy(struct drm_encoder *drm_encoder)
module_put(module); module_put(module);
} }
EXPORT_SYMBOL(drm_i2c_encoder_destroy); EXPORT_SYMBOL(drm_i2c_encoder_destroy);
/*
* Wrapper fxns which can be plugged in to drm_encoder_helper_funcs:
*/
static inline struct drm_encoder_slave_funcs *
get_slave_funcs(struct drm_encoder *enc)
{
return to_encoder_slave(enc)->slave_funcs;
}
void drm_i2c_encoder_dpms(struct drm_encoder *encoder, int mode)
{
get_slave_funcs(encoder)->dpms(encoder, mode);
}
EXPORT_SYMBOL(drm_i2c_encoder_dpms);
bool drm_i2c_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return get_slave_funcs(encoder)->mode_fixup(encoder, mode, adjusted_mode);
}
EXPORT_SYMBOL(drm_i2c_encoder_mode_fixup);
void drm_i2c_encoder_prepare(struct drm_encoder *encoder)
{
drm_i2c_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
}
EXPORT_SYMBOL(drm_i2c_encoder_prepare);
void drm_i2c_encoder_commit(struct drm_encoder *encoder)
{
drm_i2c_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
}
EXPORT_SYMBOL(drm_i2c_encoder_commit);
void drm_i2c_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
get_slave_funcs(encoder)->mode_set(encoder, mode, adjusted_mode);
}
EXPORT_SYMBOL(drm_i2c_encoder_mode_set);
enum drm_connector_status drm_i2c_encoder_detect(struct drm_encoder *encoder,
struct drm_connector *connector)
{
return get_slave_funcs(encoder)->detect(encoder, connector);
}
EXPORT_SYMBOL(drm_i2c_encoder_detect);
void drm_i2c_encoder_save(struct drm_encoder *encoder)
{
get_slave_funcs(encoder)->save(encoder);
}
EXPORT_SYMBOL(drm_i2c_encoder_save);
void drm_i2c_encoder_restore(struct drm_encoder *encoder)
{
get_slave_funcs(encoder)->restore(encoder);
}
EXPORT_SYMBOL(drm_i2c_encoder_restore);
...@@ -180,6 +180,59 @@ struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb, ...@@ -180,6 +180,59 @@ struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb,
} }
EXPORT_SYMBOL_GPL(drm_fb_cma_get_gem_obj); EXPORT_SYMBOL_GPL(drm_fb_cma_get_gem_obj);
#ifdef CONFIG_DEBUG_FS
/**
* drm_fb_cma_describe() - Helper to dump information about a single
* CMA framebuffer object
*/
void drm_fb_cma_describe(struct drm_framebuffer *fb, struct seq_file *m)
{
struct drm_fb_cma *fb_cma = to_fb_cma(fb);
int i, n = drm_format_num_planes(fb->pixel_format);
seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height,
(char *)&fb->pixel_format);
for (i = 0; i < n; i++) {
seq_printf(m, " %d: offset=%d pitch=%d, obj: ",
i, fb->offsets[i], fb->pitches[i]);
drm_gem_cma_describe(fb_cma->obj[i], m);
}
}
EXPORT_SYMBOL_GPL(drm_fb_cma_describe);
/**
* drm_fb_cma_debugfs_show() - Helper to list CMA framebuffer objects
* in debugfs.
*/
int drm_fb_cma_debugfs_show(struct seq_file *m, void *arg)
{
struct drm_info_node *node = (struct drm_info_node *) m->private;
struct drm_device *dev = node->minor->dev;
struct drm_framebuffer *fb;
int ret;
ret = mutex_lock_interruptible(&dev->mode_config.mutex);
if (ret)
return ret;
ret = mutex_lock_interruptible(&dev->struct_mutex);
if (ret) {
mutex_unlock(&dev->mode_config.mutex);
return ret;
}
list_for_each_entry(fb, &dev->mode_config.fb_list, head)
drm_fb_cma_describe(fb, m);
mutex_unlock(&dev->struct_mutex);
mutex_unlock(&dev->mode_config.mutex);
return 0;
}
EXPORT_SYMBOL_GPL(drm_fb_cma_debugfs_show);
#endif
static struct fb_ops drm_fbdev_cma_ops = { static struct fb_ops drm_fbdev_cma_ops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.fb_fillrect = sys_fillrect, .fb_fillrect = sys_fillrect,
......
...@@ -249,3 +249,24 @@ int drm_gem_cma_dumb_destroy(struct drm_file *file_priv, ...@@ -249,3 +249,24 @@ int drm_gem_cma_dumb_destroy(struct drm_file *file_priv,
return drm_gem_handle_delete(file_priv, handle); return drm_gem_handle_delete(file_priv, handle);
} }
EXPORT_SYMBOL_GPL(drm_gem_cma_dumb_destroy); EXPORT_SYMBOL_GPL(drm_gem_cma_dumb_destroy);
#ifdef CONFIG_DEBUG_FS
void drm_gem_cma_describe(struct drm_gem_cma_object *cma_obj, struct seq_file *m)
{
struct drm_gem_object *obj = &cma_obj->base;
struct drm_device *dev = obj->dev;
uint64_t off = 0;
WARN_ON(!mutex_is_locked(&dev->struct_mutex));
if (obj->map_list.map)
off = (uint64_t)obj->map_list.hash.key;
seq_printf(m, "%2d (%2d) %08llx %08Zx %p %d",
obj->name, obj->refcount.refcount.counter,
off, cma_obj->paddr, cma_obj->vaddr, obj->size);
seq_printf(m, "\n");
}
EXPORT_SYMBOL_GPL(drm_gem_cma_describe);
#endif
...@@ -867,6 +867,7 @@ void drm_send_vblank_event(struct drm_device *dev, int crtc, ...@@ -867,6 +867,7 @@ void drm_send_vblank_event(struct drm_device *dev, int crtc,
now = get_drm_timestamp(); now = get_drm_timestamp();
} }
e->pipe = crtc;
send_vblank_event(dev, e, seq, &now); send_vblank_event(dev, e, seq, &now);
} }
EXPORT_SYMBOL(drm_send_vblank_event); EXPORT_SYMBOL(drm_send_vblank_event);
......
...@@ -19,4 +19,10 @@ config DRM_I2C_SIL164 ...@@ -19,4 +19,10 @@ config DRM_I2C_SIL164
when used in pairs) TMDS transmitters, used in some nVidia when used in pairs) TMDS transmitters, used in some nVidia
video cards. video cards.
config DRM_I2C_NXP_TDA998X
tristate "NXP Semiconductors TDA998X HDMI encoder"
default m if DRM_TILCDC
help
Support for NXP Semiconductors TDA998X HDMI encoders.
endmenu endmenu
...@@ -5,3 +5,6 @@ obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o ...@@ -5,3 +5,6 @@ obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o
sil164-y := sil164_drv.o sil164-y := sil164_drv.o
obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o
tda998x-y := tda998x_drv.o
obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/module.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_encoder_slave.h>
#include <drm/drm_edid.h>
#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
struct tda998x_priv {
struct i2c_client *cec;
uint16_t rev;
uint8_t current_page;
int dpms;
};
#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
/* The TDA9988 series of devices use a paged register scheme.. to simplify
* things we encode the page # in upper bits of the register #. To read/
* write a given register, we need to make sure CURPAGE register is set
* appropriately. Which implies reads/writes are not atomic. Fun!
*/
#define REG(page, addr) (((page) << 8) | (addr))
#define REG2ADDR(reg) ((reg) & 0xff)
#define REG2PAGE(reg) (((reg) >> 8) & 0xff)
#define REG_CURPAGE 0xff /* write */
/* Page 00h: General Control */
#define REG_VERSION_LSB REG(0x00, 0x00) /* read */
#define REG_MAIN_CNTRL0 REG(0x00, 0x01) /* read/write */
# define MAIN_CNTRL0_SR (1 << 0)
# define MAIN_CNTRL0_DECS (1 << 1)
# define MAIN_CNTRL0_DEHS (1 << 2)
# define MAIN_CNTRL0_CECS (1 << 3)
# define MAIN_CNTRL0_CEHS (1 << 4)
# define MAIN_CNTRL0_SCALER (1 << 7)
#define REG_VERSION_MSB REG(0x00, 0x02) /* read */
#define REG_SOFTRESET REG(0x00, 0x0a) /* write */
# define SOFTRESET_AUDIO (1 << 0)
# define SOFTRESET_I2C_MASTER (1 << 1)
#define REG_DDC_DISABLE REG(0x00, 0x0b) /* read/write */
#define REG_CCLK_ON REG(0x00, 0x0c) /* read/write */
#define REG_I2C_MASTER REG(0x00, 0x0d) /* read/write */
# define I2C_MASTER_DIS_MM (1 << 0)
# define I2C_MASTER_DIS_FILT (1 << 1)
# define I2C_MASTER_APP_STRT_LAT (1 << 2)
#define REG_INT_FLAGS_0 REG(0x00, 0x0f) /* read/write */
#define REG_INT_FLAGS_1 REG(0x00, 0x10) /* read/write */
#define REG_INT_FLAGS_2 REG(0x00, 0x11) /* read/write */
# define INT_FLAGS_2_EDID_BLK_RD (1 << 1)
#define REG_ENA_VP_0 REG(0x00, 0x18) /* read/write */
#define REG_ENA_VP_1 REG(0x00, 0x19) /* read/write */
#define REG_ENA_VP_2 REG(0x00, 0x1a) /* read/write */
#define REG_ENA_AP REG(0x00, 0x1e) /* read/write */
#define REG_VIP_CNTRL_0 REG(0x00, 0x20) /* write */
# define VIP_CNTRL_0_MIRR_A (1 << 7)
# define VIP_CNTRL_0_SWAP_A(x) (((x) & 7) << 4)
# define VIP_CNTRL_0_MIRR_B (1 << 3)
# define VIP_CNTRL_0_SWAP_B(x) (((x) & 7) << 0)
#define REG_VIP_CNTRL_1 REG(0x00, 0x21) /* write */
# define VIP_CNTRL_1_MIRR_C (1 << 7)
# define VIP_CNTRL_1_SWAP_C(x) (((x) & 7) << 4)
# define VIP_CNTRL_1_MIRR_D (1 << 3)
# define VIP_CNTRL_1_SWAP_D(x) (((x) & 7) << 0)
#define REG_VIP_CNTRL_2 REG(0x00, 0x22) /* write */
# define VIP_CNTRL_2_MIRR_E (1 << 7)
# define VIP_CNTRL_2_SWAP_E(x) (((x) & 7) << 4)
# define VIP_CNTRL_2_MIRR_F (1 << 3)
# define VIP_CNTRL_2_SWAP_F(x) (((x) & 7) << 0)
#define REG_VIP_CNTRL_3 REG(0x00, 0x23) /* write */
# define VIP_CNTRL_3_X_TGL (1 << 0)
# define VIP_CNTRL_3_H_TGL (1 << 1)
# define VIP_CNTRL_3_V_TGL (1 << 2)
# define VIP_CNTRL_3_EMB (1 << 3)
# define VIP_CNTRL_3_SYNC_DE (1 << 4)
# define VIP_CNTRL_3_SYNC_HS (1 << 5)
# define VIP_CNTRL_3_DE_INT (1 << 6)
# define VIP_CNTRL_3_EDGE (1 << 7)
#define REG_VIP_CNTRL_4 REG(0x00, 0x24) /* write */
# define VIP_CNTRL_4_BLC(x) (((x) & 3) << 0)
# define VIP_CNTRL_4_BLANKIT(x) (((x) & 3) << 2)
# define VIP_CNTRL_4_CCIR656 (1 << 4)
# define VIP_CNTRL_4_656_ALT (1 << 5)
# define VIP_CNTRL_4_TST_656 (1 << 6)
# define VIP_CNTRL_4_TST_PAT (1 << 7)
#define REG_VIP_CNTRL_5 REG(0x00, 0x25) /* write */
# define VIP_CNTRL_5_CKCASE (1 << 0)
# define VIP_CNTRL_5_SP_CNT(x) (((x) & 3) << 1)
#define REG_MAT_CONTRL REG(0x00, 0x80) /* write */
# define MAT_CONTRL_MAT_SC(x) (((x) & 3) << 0)
# define MAT_CONTRL_MAT_BP (1 << 2)
#define REG_VIDFORMAT REG(0x00, 0xa0) /* write */
#define REG_REFPIX_MSB REG(0x00, 0xa1) /* write */
#define REG_REFPIX_LSB REG(0x00, 0xa2) /* write */
#define REG_REFLINE_MSB REG(0x00, 0xa3) /* write */
#define REG_REFLINE_LSB REG(0x00, 0xa4) /* write */
#define REG_NPIX_MSB REG(0x00, 0xa5) /* write */
#define REG_NPIX_LSB REG(0x00, 0xa6) /* write */
#define REG_NLINE_MSB REG(0x00, 0xa7) /* write */
#define REG_NLINE_LSB REG(0x00, 0xa8) /* write */
#define REG_VS_LINE_STRT_1_MSB REG(0x00, 0xa9) /* write */
#define REG_VS_LINE_STRT_1_LSB REG(0x00, 0xaa) /* write */
#define REG_VS_PIX_STRT_1_MSB REG(0x00, 0xab) /* write */
#define REG_VS_PIX_STRT_1_LSB REG(0x00, 0xac) /* write */
#define REG_VS_LINE_END_1_MSB REG(0x00, 0xad) /* write */
#define REG_VS_LINE_END_1_LSB REG(0x00, 0xae) /* write */
#define REG_VS_PIX_END_1_MSB REG(0x00, 0xaf) /* write */
#define REG_VS_PIX_END_1_LSB REG(0x00, 0xb0) /* write */
#define REG_VS_PIX_STRT_2_MSB REG(0x00, 0xb3) /* write */
#define REG_VS_PIX_STRT_2_LSB REG(0x00, 0xb4) /* write */
#define REG_VS_PIX_END_2_MSB REG(0x00, 0xb7) /* write */
#define REG_VS_PIX_END_2_LSB REG(0x00, 0xb8) /* write */
#define REG_HS_PIX_START_MSB REG(0x00, 0xb9) /* write */
#define REG_HS_PIX_START_LSB REG(0x00, 0xba) /* write */
#define REG_HS_PIX_STOP_MSB REG(0x00, 0xbb) /* write */
#define REG_HS_PIX_STOP_LSB REG(0x00, 0xbc) /* write */
#define REG_VWIN_START_1_MSB REG(0x00, 0xbd) /* write */
#define REG_VWIN_START_1_LSB REG(0x00, 0xbe) /* write */
#define REG_VWIN_END_1_MSB REG(0x00, 0xbf) /* write */
#define REG_VWIN_END_1_LSB REG(0x00, 0xc0) /* write */
#define REG_DE_START_MSB REG(0x00, 0xc5) /* write */
#define REG_DE_START_LSB REG(0x00, 0xc6) /* write */
#define REG_DE_STOP_MSB REG(0x00, 0xc7) /* write */
#define REG_DE_STOP_LSB REG(0x00, 0xc8) /* write */
#define REG_TBG_CNTRL_0 REG(0x00, 0xca) /* write */
# define TBG_CNTRL_0_FRAME_DIS (1 << 5)
# define TBG_CNTRL_0_SYNC_MTHD (1 << 6)
# define TBG_CNTRL_0_SYNC_ONCE (1 << 7)
#define REG_TBG_CNTRL_1 REG(0x00, 0xcb) /* write */
# define TBG_CNTRL_1_VH_TGL_0 (1 << 0)
# define TBG_CNTRL_1_VH_TGL_1 (1 << 1)
# define TBG_CNTRL_1_VH_TGL_2 (1 << 2)
# define TBG_CNTRL_1_VHX_EXT_DE (1 << 3)
# define TBG_CNTRL_1_VHX_EXT_HS (1 << 4)
# define TBG_CNTRL_1_VHX_EXT_VS (1 << 5)
# define TBG_CNTRL_1_DWIN_DIS (1 << 6)
#define REG_ENABLE_SPACE REG(0x00, 0xd6) /* write */
#define REG_HVF_CNTRL_0 REG(0x00, 0xe4) /* write */
# define HVF_CNTRL_0_SM (1 << 7)
# define HVF_CNTRL_0_RWB (1 << 6)
# define HVF_CNTRL_0_PREFIL(x) (((x) & 3) << 2)
# define HVF_CNTRL_0_INTPOL(x) (((x) & 3) << 0)
#define REG_HVF_CNTRL_1 REG(0x00, 0xe5) /* write */
# define HVF_CNTRL_1_FOR (1 << 0)
# define HVF_CNTRL_1_YUVBLK (1 << 1)
# define HVF_CNTRL_1_VQR(x) (((x) & 3) << 2)
# define HVF_CNTRL_1_PAD(x) (((x) & 3) << 4)
# define HVF_CNTRL_1_SEMI_PLANAR (1 << 6)
#define REG_RPT_CNTRL REG(0x00, 0xf0) /* write */
/* Page 02h: PLL settings */
#define REG_PLL_SERIAL_1 REG(0x02, 0x00) /* read/write */
# define PLL_SERIAL_1_SRL_FDN (1 << 0)
# define PLL_SERIAL_1_SRL_IZ(x) (((x) & 3) << 1)
# define PLL_SERIAL_1_SRL_MAN_IZ (1 << 6)
#define REG_PLL_SERIAL_2 REG(0x02, 0x01) /* read/write */
# define PLL_SERIAL_2_SRL_NOSC(x) (((x) & 3) << 0)
# define PLL_SERIAL_2_SRL_PR(x) (((x) & 0xf) << 4)
#define REG_PLL_SERIAL_3 REG(0x02, 0x02) /* read/write */
# define PLL_SERIAL_3_SRL_CCIR (1 << 0)
# define PLL_SERIAL_3_SRL_DE (1 << 2)
# define PLL_SERIAL_3_SRL_PXIN_SEL (1 << 4)
#define REG_SERIALIZER REG(0x02, 0x03) /* read/write */
#define REG_BUFFER_OUT REG(0x02, 0x04) /* read/write */
#define REG_PLL_SCG1 REG(0x02, 0x05) /* read/write */
#define REG_PLL_SCG2 REG(0x02, 0x06) /* read/write */
#define REG_PLL_SCGN1 REG(0x02, 0x07) /* read/write */
#define REG_PLL_SCGN2 REG(0x02, 0x08) /* read/write */
#define REG_PLL_SCGR1 REG(0x02, 0x09) /* read/write */
#define REG_PLL_SCGR2 REG(0x02, 0x0a) /* read/write */
#define REG_AUDIO_DIV REG(0x02, 0x0e) /* read/write */
#define REG_SEL_CLK REG(0x02, 0x11) /* read/write */
# define SEL_CLK_SEL_CLK1 (1 << 0)
# define SEL_CLK_SEL_VRF_CLK(x) (((x) & 3) << 1)
# define SEL_CLK_ENA_SC_CLK (1 << 3)
#define REG_ANA_GENERAL REG(0x02, 0x12) /* read/write */
/* Page 09h: EDID Control */
#define REG_EDID_DATA_0 REG(0x09, 0x00) /* read */
/* next 127 successive registers are the EDID block */
#define REG_EDID_CTRL REG(0x09, 0xfa) /* read/write */
#define REG_DDC_ADDR REG(0x09, 0xfb) /* read/write */
#define REG_DDC_OFFS REG(0x09, 0xfc) /* read/write */
#define REG_DDC_SEGM_ADDR REG(0x09, 0xfd) /* read/write */
#define REG_DDC_SEGM REG(0x09, 0xfe) /* read/write */
/* Page 10h: information frames and packets */
/* Page 11h: audio settings and content info packets */
#define REG_AIP_CNTRL_0 REG(0x11, 0x00) /* read/write */
# define AIP_CNTRL_0_RST_FIFO (1 << 0)
# define AIP_CNTRL_0_SWAP (1 << 1)
# define AIP_CNTRL_0_LAYOUT (1 << 2)
# define AIP_CNTRL_0_ACR_MAN (1 << 5)
# define AIP_CNTRL_0_RST_CTS (1 << 6)
#define REG_ENC_CNTRL REG(0x11, 0x0d) /* read/write */
# define ENC_CNTRL_RST_ENC (1 << 0)
# define ENC_CNTRL_RST_SEL (1 << 1)
# define ENC_CNTRL_CTL_CODE(x) (((x) & 3) << 2)
/* Page 12h: HDCP and OTP */
#define REG_TX3 REG(0x12, 0x9a) /* read/write */
#define REG_TX33 REG(0x12, 0xb8) /* read/write */
# define TX33_HDMI (1 << 1)
/* Page 13h: Gamut related metadata packets */
/* CEC registers: (not paged)
*/
#define REG_CEC_FRO_IM_CLK_CTRL 0xfb /* read/write */
# define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7)
# define CEC_FRO_IM_CLK_CTRL_ENA_OTP (1 << 6)
# define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL (1 << 1)
# define CEC_FRO_IM_CLK_CTRL_FRO_DIV (1 << 0)
#define REG_CEC_RXSHPDLEV 0xfe /* read */
# define CEC_RXSHPDLEV_RXSENS (1 << 0)
# define CEC_RXSHPDLEV_HPD (1 << 1)
#define REG_CEC_ENAMODS 0xff /* read/write */
# define CEC_ENAMODS_DIS_FRO (1 << 6)
# define CEC_ENAMODS_DIS_CCLK (1 << 5)
# define CEC_ENAMODS_EN_RXSENS (1 << 2)
# define CEC_ENAMODS_EN_HDMI (1 << 1)
# define CEC_ENAMODS_EN_CEC (1 << 0)
/* Device versions: */
#define TDA9989N2 0x0101
#define TDA19989 0x0201
#define TDA19989N2 0x0202
#define TDA19988 0x0301
static void
cec_write(struct drm_encoder *encoder, uint16_t addr, uint8_t val)
{
struct i2c_client *client = to_tda998x_priv(encoder)->cec;
uint8_t buf[] = {addr, val};
int ret;
ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
if (ret < 0)
dev_err(&client->dev, "Error %d writing to cec:0x%x\n", ret, addr);
}
static uint8_t
cec_read(struct drm_encoder *encoder, uint8_t addr)
{
struct i2c_client *client = to_tda998x_priv(encoder)->cec;
uint8_t val;
int ret;
ret = i2c_master_send(client, &addr, sizeof(addr));
if (ret < 0)
goto fail;
ret = i2c_master_recv(client, &val, sizeof(val));
if (ret < 0)
goto fail;
return val;
fail:
dev_err(&client->dev, "Error %d reading from cec:0x%x\n", ret, addr);
return 0;
}
static void
set_page(struct drm_encoder *encoder, uint16_t reg)
{
struct tda998x_priv *priv = to_tda998x_priv(encoder);
if (REG2PAGE(reg) != priv->current_page) {
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
uint8_t buf[] = {
REG_CURPAGE, REG2PAGE(reg)
};
int ret = i2c_master_send(client, buf, sizeof(buf));
if (ret < 0)
dev_err(&client->dev, "Error %d writing to REG_CURPAGE\n", ret);
priv->current_page = REG2PAGE(reg);
}
}
static int
reg_read_range(struct drm_encoder *encoder, uint16_t reg, char *buf, int cnt)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
uint8_t addr = REG2ADDR(reg);
int ret;
set_page(encoder, reg);
ret = i2c_master_send(client, &addr, sizeof(addr));
if (ret < 0)
goto fail;
ret = i2c_master_recv(client, buf, cnt);
if (ret < 0)
goto fail;
return ret;
fail:
dev_err(&client->dev, "Error %d reading from 0x%x\n", ret, reg);
return ret;
}
static uint8_t
reg_read(struct drm_encoder *encoder, uint16_t reg)
{
uint8_t val = 0;
reg_read_range(encoder, reg, &val, sizeof(val));
return val;
}
static void
reg_write(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
uint8_t buf[] = {REG2ADDR(reg), val};
int ret;
set_page(encoder, reg);
ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
if (ret < 0)
dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
}
static void
reg_write16(struct drm_encoder *encoder, uint16_t reg, uint16_t val)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
uint8_t buf[] = {REG2ADDR(reg), val >> 8, val};
int ret;
set_page(encoder, reg);
ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
if (ret < 0)
dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
}
static void
reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
{
reg_write(encoder, reg, reg_read(encoder, reg) | val);
}
static void
reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
{
reg_write(encoder, reg, reg_read(encoder, reg) & ~val);
}
static void
tda998x_reset(struct drm_encoder *encoder)
{
/* reset audio and i2c master: */
reg_set(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
msleep(50);
reg_clear(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
msleep(50);
/* reset transmitter: */
reg_set(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
reg_clear(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
/* PLL registers common configuration */
reg_write(encoder, REG_PLL_SERIAL_1, 0x00);
reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1));
reg_write(encoder, REG_PLL_SERIAL_3, 0x00);
reg_write(encoder, REG_SERIALIZER, 0x00);
reg_write(encoder, REG_BUFFER_OUT, 0x00);
reg_write(encoder, REG_PLL_SCG1, 0x00);
reg_write(encoder, REG_AUDIO_DIV, 0x03);
reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
reg_write(encoder, REG_PLL_SCGN1, 0xfa);
reg_write(encoder, REG_PLL_SCGN2, 0x00);
reg_write(encoder, REG_PLL_SCGR1, 0x5b);
reg_write(encoder, REG_PLL_SCGR2, 0x00);
reg_write(encoder, REG_PLL_SCG2, 0x10);
}
/* DRM encoder functions */
static void
tda998x_encoder_set_config(struct drm_encoder *encoder, void *params)
{
}
static void
tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct tda998x_priv *priv = to_tda998x_priv(encoder);
/* we only care about on or off: */
if (mode != DRM_MODE_DPMS_ON)
mode = DRM_MODE_DPMS_OFF;
if (mode == priv->dpms)
return;
switch (mode) {
case DRM_MODE_DPMS_ON:
/* enable audio and video ports */
reg_write(encoder, REG_ENA_AP, 0xff);
reg_write(encoder, REG_ENA_VP_0, 0xff);
reg_write(encoder, REG_ENA_VP_1, 0xff);
reg_write(encoder, REG_ENA_VP_2, 0xff);
/* set muxing after enabling ports: */
reg_write(encoder, REG_VIP_CNTRL_0,
VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3));
reg_write(encoder, REG_VIP_CNTRL_1,
VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1));
reg_write(encoder, REG_VIP_CNTRL_2,
VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5));
break;
case DRM_MODE_DPMS_OFF:
/* disable audio and video ports */
reg_write(encoder, REG_ENA_AP, 0x00);
reg_write(encoder, REG_ENA_VP_0, 0x00);
reg_write(encoder, REG_ENA_VP_1, 0x00);
reg_write(encoder, REG_ENA_VP_2, 0x00);
break;
}
priv->dpms = mode;
}
static void
tda998x_encoder_save(struct drm_encoder *encoder)
{
DBG("");
}
static void
tda998x_encoder_restore(struct drm_encoder *encoder)
{
DBG("");
}
static bool
tda998x_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static int
tda998x_encoder_mode_valid(struct drm_encoder *encoder,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static void
tda998x_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct tda998x_priv *priv = to_tda998x_priv(encoder);
uint16_t hs_start, hs_end, line_start, line_end;
uint16_t vwin_start, vwin_end, de_start, de_end;
uint16_t ref_pix, ref_line, pix_start2;
uint8_t reg, div, rep;
hs_start = mode->hsync_start - mode->hdisplay;
hs_end = mode->hsync_end - mode->hdisplay;
line_start = 1;
line_end = 1 + mode->vsync_end - mode->vsync_start;
vwin_start = mode->vtotal - mode->vsync_start;
vwin_end = vwin_start + mode->vdisplay;
de_start = mode->htotal - mode->hdisplay;
de_end = mode->htotal;
pix_start2 = 0;
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
pix_start2 = (mode->htotal / 2) + hs_start;
/* TODO how is this value calculated? It is 2 for all common
* formats in the tables in out of tree nxp driver (assuming
* I've properly deciphered their byzantine table system)
*/
ref_line = 2;
/* this might changes for other color formats from the CRTC: */
ref_pix = 3 + hs_start;
div = 148500 / mode->clock;
DBG("clock=%d, div=%u", mode->clock, div);
DBG("hs_start=%u, hs_end=%u, line_start=%u, line_end=%u",
hs_start, hs_end, line_start, line_end);
DBG("vwin_start=%u, vwin_end=%u, de_start=%u, de_end=%u",
vwin_start, vwin_end, de_start, de_end);
DBG("ref_line=%u, ref_pix=%u, pix_start2=%u",
ref_line, ref_pix, pix_start2);
/* mute the audio FIFO: */
reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
/* set HDMI HDCP mode off: */
reg_set(encoder, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
reg_clear(encoder, REG_TX33, TX33_HDMI);
reg_write(encoder, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0));
/* no pre-filter or interpolator: */
reg_write(encoder, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) |
HVF_CNTRL_0_INTPOL(0));
reg_write(encoder, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0));
reg_write(encoder, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) |
VIP_CNTRL_4_BLC(0));
reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR);
reg_clear(encoder, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ);
reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_DE);
reg_write(encoder, REG_SERIALIZER, 0);
reg_write(encoder, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0));
/* TODO enable pixel repeat for pixel rates less than 25Msamp/s */
rep = 0;
reg_write(encoder, REG_RPT_CNTRL, 0);
reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) |
SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) |
PLL_SERIAL_2_SRL_PR(rep));
reg_write16(encoder, REG_VS_PIX_STRT_2_MSB, pix_start2);
reg_write16(encoder, REG_VS_PIX_END_2_MSB, pix_start2);
/* set color matrix bypass flag: */
reg_set(encoder, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP);
/* set BIAS tmds value: */
reg_write(encoder, REG_ANA_GENERAL, 0x09);
reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_MTHD);
reg_write(encoder, REG_VIP_CNTRL_3, 0);
reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_SYNC_HS);
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_V_TGL);
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_H_TGL);
reg_write(encoder, REG_VIDFORMAT, 0x00);
reg_write16(encoder, REG_NPIX_MSB, mode->hdisplay - 1);
reg_write16(encoder, REG_NLINE_MSB, mode->vdisplay - 1);
reg_write16(encoder, REG_VS_LINE_STRT_1_MSB, line_start);
reg_write16(encoder, REG_VS_LINE_END_1_MSB, line_end);
reg_write16(encoder, REG_VS_PIX_STRT_1_MSB, hs_start);
reg_write16(encoder, REG_VS_PIX_END_1_MSB, hs_start);
reg_write16(encoder, REG_HS_PIX_START_MSB, hs_start);
reg_write16(encoder, REG_HS_PIX_STOP_MSB, hs_end);
reg_write16(encoder, REG_VWIN_START_1_MSB, vwin_start);
reg_write16(encoder, REG_VWIN_END_1_MSB, vwin_end);
reg_write16(encoder, REG_DE_START_MSB, de_start);
reg_write16(encoder, REG_DE_STOP_MSB, de_end);
if (priv->rev == TDA19988) {
/* let incoming pixels fill the active space (if any) */
reg_write(encoder, REG_ENABLE_SPACE, 0x01);
}
reg_write16(encoder, REG_REFPIX_MSB, ref_pix);
reg_write16(encoder, REG_REFLINE_MSB, ref_line);
reg = TBG_CNTRL_1_VHX_EXT_DE |
TBG_CNTRL_1_VHX_EXT_HS |
TBG_CNTRL_1_VHX_EXT_VS |
TBG_CNTRL_1_DWIN_DIS | /* HDCP off */
TBG_CNTRL_1_VH_TGL_2;
if (mode->flags & (DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC))
reg |= TBG_CNTRL_1_VH_TGL_0;
reg_set(encoder, REG_TBG_CNTRL_1, reg);
/* must be last register set: */
reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE);
}
static enum drm_connector_status
tda998x_encoder_detect(struct drm_encoder *encoder,
struct drm_connector *connector)
{
uint8_t val = cec_read(encoder, REG_CEC_RXSHPDLEV);
return (val & CEC_RXSHPDLEV_HPD) ? connector_status_connected :
connector_status_disconnected;
}
static int
read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk)
{
uint8_t offset, segptr;
int ret, i;
/* enable EDID read irq: */
reg_set(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
offset = (blk & 1) ? 128 : 0;
segptr = blk / 2;
reg_write(encoder, REG_DDC_ADDR, 0xa0);
reg_write(encoder, REG_DDC_OFFS, offset);
reg_write(encoder, REG_DDC_SEGM_ADDR, 0x60);
reg_write(encoder, REG_DDC_SEGM, segptr);
/* enable reading EDID: */
reg_write(encoder, REG_EDID_CTRL, 0x1);
/* flag must be cleared by sw: */
reg_write(encoder, REG_EDID_CTRL, 0x0);
/* wait for block read to complete: */
for (i = 100; i > 0; i--) {
uint8_t val = reg_read(encoder, REG_INT_FLAGS_2);
if (val & INT_FLAGS_2_EDID_BLK_RD)
break;
msleep(1);
}
if (i == 0)
return -ETIMEDOUT;
ret = reg_read_range(encoder, REG_EDID_DATA_0, buf, EDID_LENGTH);
if (ret != EDID_LENGTH) {
dev_err(encoder->dev->dev, "failed to read edid block %d: %d",
blk, ret);
return ret;
}
reg_clear(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
return 0;
}
static uint8_t *
do_get_edid(struct drm_encoder *encoder)
{
int j = 0, valid_extensions = 0;
uint8_t *block, *new;
bool print_bad_edid = drm_debug & DRM_UT_KMS;
if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)
return NULL;
/* base block fetch */
if (read_edid_block(encoder, block, 0))
goto fail;
if (!drm_edid_block_valid(block, 0, print_bad_edid))
goto fail;
/* if there's no extensions, we're done */
if (block[0x7e] == 0)
return block;
new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL);
if (!new)
goto fail;
block = new;
for (j = 1; j <= block[0x7e]; j++) {
uint8_t *ext_block = block + (valid_extensions + 1) * EDID_LENGTH;
if (read_edid_block(encoder, ext_block, j))
goto fail;
if (!drm_edid_block_valid(ext_block, j, print_bad_edid))
goto fail;
valid_extensions++;
}
if (valid_extensions != block[0x7e]) {
block[EDID_LENGTH-1] += block[0x7e] - valid_extensions;
block[0x7e] = valid_extensions;
new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL);
if (!new)
goto fail;
block = new;
}
return block;
fail:
dev_warn(encoder->dev->dev, "failed to read EDID\n");
kfree(block);
return NULL;
}
static int
tda998x_encoder_get_modes(struct drm_encoder *encoder,
struct drm_connector *connector)
{
struct edid *edid = (struct edid *)do_get_edid(encoder);
int n = 0;
if (edid) {
drm_mode_connector_update_edid_property(connector, edid);
n = drm_add_edid_modes(connector, edid);
kfree(edid);
}
return n;
}
static int
tda998x_encoder_create_resources(struct drm_encoder *encoder,
struct drm_connector *connector)
{
DBG("");
return 0;
}
static int
tda998x_encoder_set_property(struct drm_encoder *encoder,
struct drm_connector *connector,
struct drm_property *property,
uint64_t val)
{
DBG("");
return 0;
}
static void
tda998x_encoder_destroy(struct drm_encoder *encoder)
{
struct tda998x_priv *priv = to_tda998x_priv(encoder);
drm_i2c_encoder_destroy(encoder);
kfree(priv);
}
static struct drm_encoder_slave_funcs tda998x_encoder_funcs = {
.set_config = tda998x_encoder_set_config,
.destroy = tda998x_encoder_destroy,
.dpms = tda998x_encoder_dpms,
.save = tda998x_encoder_save,
.restore = tda998x_encoder_restore,
.mode_fixup = tda998x_encoder_mode_fixup,
.mode_valid = tda998x_encoder_mode_valid,
.mode_set = tda998x_encoder_mode_set,
.detect = tda998x_encoder_detect,
.get_modes = tda998x_encoder_get_modes,
.create_resources = tda998x_encoder_create_resources,
.set_property = tda998x_encoder_set_property,
};
/* I2C driver functions */
static int
tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
return 0;
}
static int
tda998x_remove(struct i2c_client *client)
{
return 0;
}
static int
tda998x_encoder_init(struct i2c_client *client,
struct drm_device *dev,
struct drm_encoder_slave *encoder_slave)
{
struct drm_encoder *encoder = &encoder_slave->base;
struct tda998x_priv *priv;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->current_page = 0;
priv->cec = i2c_new_dummy(client->adapter, 0x34);
priv->dpms = DRM_MODE_DPMS_OFF;
encoder_slave->slave_priv = priv;
encoder_slave->slave_funcs = &tda998x_encoder_funcs;
/* wake up the device: */
cec_write(encoder, REG_CEC_ENAMODS,
CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI);
tda998x_reset(encoder);
/* read version: */
priv->rev = reg_read(encoder, REG_VERSION_LSB) |
reg_read(encoder, REG_VERSION_MSB) << 8;
/* mask off feature bits: */
priv->rev &= ~0x30; /* not-hdcp and not-scalar bit */
switch (priv->rev) {
case TDA9989N2: dev_info(dev->dev, "found TDA9989 n2"); break;
case TDA19989: dev_info(dev->dev, "found TDA19989"); break;
case TDA19989N2: dev_info(dev->dev, "found TDA19989 n2"); break;
case TDA19988: dev_info(dev->dev, "found TDA19988"); break;
default:
DBG("found unsupported device: %04x", priv->rev);
goto fail;
}
/* after reset, enable DDC: */
reg_write(encoder, REG_DDC_DISABLE, 0x00);
/* set clock on DDC channel: */
reg_write(encoder, REG_TX3, 39);
/* if necessary, disable multi-master: */
if (priv->rev == TDA19989)
reg_set(encoder, REG_I2C_MASTER, I2C_MASTER_DIS_MM);
cec_write(encoder, REG_CEC_FRO_IM_CLK_CTRL,
CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL);
return 0;
fail:
/* if encoder_init fails, the encoder slave is never registered,
* so cleanup here:
*/
if (priv->cec)
i2c_unregister_device(priv->cec);
kfree(priv);
encoder_slave->slave_priv = NULL;
encoder_slave->slave_funcs = NULL;
return -ENXIO;
}
static struct i2c_device_id tda998x_ids[] = {
{ "tda998x", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tda998x_ids);
static struct drm_i2c_encoder_driver tda998x_driver = {
.i2c_driver = {
.probe = tda998x_probe,
.remove = tda998x_remove,
.driver = {
.name = "tda998x",
},
.id_table = tda998x_ids,
},
.encoder_init = tda998x_encoder_init,
};
/* Module initialization */
static int __init
tda998x_init(void)
{
DBG("");
return drm_i2c_encoder_register(THIS_MODULE, &tda998x_driver);
}
static void __exit
tda998x_exit(void)
{
DBG("");
drm_i2c_encoder_unregister(&tda998x_driver);
}
MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder");
MODULE_LICENSE("GPL");
module_init(tda998x_init);
module_exit(tda998x_exit);
...@@ -184,14 +184,23 @@ static const struct drm_encoder_funcs nv04_tv_funcs = { ...@@ -184,14 +184,23 @@ static const struct drm_encoder_funcs nv04_tv_funcs = {
.destroy = nv04_tv_destroy, .destroy = nv04_tv_destroy,
}; };
static const struct drm_encoder_helper_funcs nv04_tv_helper_funcs = {
.dpms = nv04_tv_dpms,
.save = drm_i2c_encoder_save,
.restore = drm_i2c_encoder_restore,
.mode_fixup = drm_i2c_encoder_mode_fixup,
.prepare = nv04_tv_prepare,
.commit = nv04_tv_commit,
.mode_set = nv04_tv_mode_set,
.detect = drm_i2c_encoder_detect,
};
int int
nv04_tv_create(struct drm_connector *connector, struct dcb_output *entry) nv04_tv_create(struct drm_connector *connector, struct dcb_output *entry)
{ {
struct nouveau_encoder *nv_encoder; struct nouveau_encoder *nv_encoder;
struct drm_encoder *encoder; struct drm_encoder *encoder;
struct drm_device *dev = connector->dev; struct drm_device *dev = connector->dev;
struct drm_encoder_helper_funcs *hfuncs;
struct drm_encoder_slave_funcs *sfuncs;
struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct nouveau_i2c *i2c = nouveau_i2c(drm->device);
struct nouveau_i2c_port *port = i2c->find(i2c, entry->i2c_index); struct nouveau_i2c_port *port = i2c->find(i2c, entry->i2c_index);
...@@ -207,17 +216,11 @@ nv04_tv_create(struct drm_connector *connector, struct dcb_output *entry) ...@@ -207,17 +216,11 @@ nv04_tv_create(struct drm_connector *connector, struct dcb_output *entry)
if (!nv_encoder) if (!nv_encoder)
return -ENOMEM; return -ENOMEM;
hfuncs = kzalloc(sizeof(*hfuncs), GFP_KERNEL);
if (!hfuncs) {
ret = -ENOMEM;
goto fail_free;
}
/* Initialize the common members */ /* Initialize the common members */
encoder = to_drm_encoder(nv_encoder); encoder = to_drm_encoder(nv_encoder);
drm_encoder_init(dev, encoder, &nv04_tv_funcs, DRM_MODE_ENCODER_TVDAC); drm_encoder_init(dev, encoder, &nv04_tv_funcs, DRM_MODE_ENCODER_TVDAC);
drm_encoder_helper_add(encoder, hfuncs); drm_encoder_helper_add(encoder, &nv04_tv_helper_funcs);
encoder->possible_crtcs = entry->heads; encoder->possible_crtcs = entry->heads;
encoder->possible_clones = 0; encoder->possible_clones = 0;
...@@ -230,30 +233,14 @@ nv04_tv_create(struct drm_connector *connector, struct dcb_output *entry) ...@@ -230,30 +233,14 @@ nv04_tv_create(struct drm_connector *connector, struct dcb_output *entry)
if (ret < 0) if (ret < 0)
goto fail_cleanup; goto fail_cleanup;
/* Fill the function pointers */
sfuncs = get_slave_funcs(encoder);
*hfuncs = (struct drm_encoder_helper_funcs) {
.dpms = nv04_tv_dpms,
.save = sfuncs->save,
.restore = sfuncs->restore,
.mode_fixup = sfuncs->mode_fixup,
.prepare = nv04_tv_prepare,
.commit = nv04_tv_commit,
.mode_set = nv04_tv_mode_set,
.detect = sfuncs->detect,
};
/* Attach it to the specified connector. */ /* Attach it to the specified connector. */
sfuncs->create_resources(encoder, connector); get_slave_funcs(encoder)->create_resources(encoder, connector);
drm_mode_connector_attach_encoder(connector, encoder); drm_mode_connector_attach_encoder(connector, encoder);
return 0; return 0;
fail_cleanup: fail_cleanup:
drm_encoder_cleanup(encoder); drm_encoder_cleanup(encoder);
kfree(hfuncs);
fail_free:
kfree(nv_encoder); kfree(nv_encoder);
return ret; return ret;
} }
config DRM_TILCDC
tristate "DRM Support for TI LCDC Display Controller"
depends on DRM && OF
select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER
select DRM_GEM_CMA_HELPER
select OF_VIDEOMODE
select OF_DISPLAY_TIMING
select BACKLIGHT_CLASS_DEVICE
help
Choose this option if you have an TI SoC with LCDC display
controller, for example AM33xx in beagle-bone, DA8xx, or
OMAP-L1xx. This driver replaces the FB_DA8XX fbdev driver.
ccflags-y := -Iinclude/drm -Werror
tilcdc-y := \
tilcdc_crtc.o \
tilcdc_tfp410.o \
tilcdc_slave.o \
tilcdc_panel.o \
tilcdc_drv.o
obj-$(CONFIG_DRM_TILCDC) += tilcdc.o
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kfifo.h>
#include "tilcdc_drv.h"
#include "tilcdc_regs.h"
struct tilcdc_crtc {
struct drm_crtc base;
const struct tilcdc_panel_info *info;
uint32_t dirty;
dma_addr_t start, end;
struct drm_pending_vblank_event *event;
int dpms;
wait_queue_head_t frame_done_wq;
bool frame_done;
/* fb currently set to scanout 0/1: */
struct drm_framebuffer *scanout[2];
/* for deferred fb unref's: */
DECLARE_KFIFO_PTR(unref_fifo, struct drm_framebuffer *);
struct work_struct work;
};
#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base)
static void unref_worker(struct work_struct *work)
{
struct tilcdc_crtc *tilcdc_crtc = container_of(work, struct tilcdc_crtc, work);
struct drm_device *dev = tilcdc_crtc->base.dev;
struct drm_framebuffer *fb;
mutex_lock(&dev->mode_config.mutex);
while (kfifo_get(&tilcdc_crtc->unref_fifo, &fb))
drm_framebuffer_unreference(fb);
mutex_unlock(&dev->mode_config.mutex);
}
static void set_scanout(struct drm_crtc *crtc, int n)
{
static const uint32_t base_reg[] = {
LCDC_DMA_FB_BASE_ADDR_0_REG, LCDC_DMA_FB_BASE_ADDR_1_REG,
};
static const uint32_t ceil_reg[] = {
LCDC_DMA_FB_CEILING_ADDR_0_REG, LCDC_DMA_FB_CEILING_ADDR_1_REG,
};
static const uint32_t stat[] = {
LCDC_END_OF_FRAME0, LCDC_END_OF_FRAME1,
};
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_device *dev = crtc->dev;
pm_runtime_get_sync(dev->dev);
tilcdc_write(dev, base_reg[n], tilcdc_crtc->start);
tilcdc_write(dev, ceil_reg[n], tilcdc_crtc->end);
if (tilcdc_crtc->scanout[n]) {
if (kfifo_put(&tilcdc_crtc->unref_fifo,
(const struct drm_framebuffer **)&tilcdc_crtc->scanout[n])) {
struct tilcdc_drm_private *priv = dev->dev_private;
queue_work(priv->wq, &tilcdc_crtc->work);
} else {
dev_err(dev->dev, "unref fifo full!\n");
drm_framebuffer_unreference(tilcdc_crtc->scanout[n]);
}
}
tilcdc_crtc->scanout[n] = crtc->fb;
drm_framebuffer_reference(tilcdc_crtc->scanout[n]);
tilcdc_crtc->dirty &= ~stat[n];
pm_runtime_put_sync(dev->dev);
}
static void update_scanout(struct drm_crtc *crtc)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_device *dev = crtc->dev;
struct drm_framebuffer *fb = crtc->fb;
struct drm_gem_cma_object *gem;
unsigned int depth, bpp;
drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp);
gem = drm_fb_cma_get_gem_obj(fb, 0);
tilcdc_crtc->start = gem->paddr + fb->offsets[0] +
(crtc->y * fb->pitches[0]) + (crtc->x * bpp/8);
tilcdc_crtc->end = tilcdc_crtc->start +
(crtc->mode.vdisplay * fb->pitches[0]);
if (tilcdc_crtc->dpms == DRM_MODE_DPMS_ON) {
/* already enabled, so just mark the frames that need
* updating and they will be updated on vblank:
*/
tilcdc_crtc->dirty |= LCDC_END_OF_FRAME0 | LCDC_END_OF_FRAME1;
drm_vblank_get(dev, 0);
} else {
/* not enabled yet, so update registers immediately: */
set_scanout(crtc, 0);
set_scanout(crtc, 1);
}
}
static void start(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
struct tilcdc_drm_private *priv = dev->dev_private;
if (priv->rev == 2) {
tilcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
msleep(1);
tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
msleep(1);
}
tilcdc_set(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY));
tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
}
static void stop(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
}
static void tilcdc_crtc_destroy(struct drm_crtc *crtc)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
WARN_ON(tilcdc_crtc->dpms == DRM_MODE_DPMS_ON);
drm_crtc_cleanup(crtc);
WARN_ON(!kfifo_is_empty(&tilcdc_crtc->unref_fifo));
kfifo_free(&tilcdc_crtc->unref_fifo);
kfree(tilcdc_crtc);
}
static int tilcdc_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_device *dev = crtc->dev;
if (tilcdc_crtc->event) {
dev_err(dev->dev, "already pending page flip!\n");
return -EBUSY;
}
crtc->fb = fb;
tilcdc_crtc->event = event;
update_scanout(crtc);
return 0;
}
static void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_device *dev = crtc->dev;
struct tilcdc_drm_private *priv = dev->dev_private;
/* we really only care about on or off: */
if (mode != DRM_MODE_DPMS_ON)
mode = DRM_MODE_DPMS_OFF;
if (tilcdc_crtc->dpms == mode)
return;
tilcdc_crtc->dpms = mode;
pm_runtime_get_sync(dev->dev);
if (mode == DRM_MODE_DPMS_ON) {
pm_runtime_forbid(dev->dev);
start(crtc);
} else {
tilcdc_crtc->frame_done = false;
stop(crtc);
/* if necessary wait for framedone irq which will still come
* before putting things to sleep..
*/
if (priv->rev == 2) {
int ret = wait_event_timeout(
tilcdc_crtc->frame_done_wq,
tilcdc_crtc->frame_done,
msecs_to_jiffies(50));
if (ret == 0)
dev_err(dev->dev, "timeout waiting for framedone\n");
}
pm_runtime_allow(dev->dev);
}
pm_runtime_put_sync(dev->dev);
}
static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static void tilcdc_crtc_prepare(struct drm_crtc *crtc)
{
tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
}
static void tilcdc_crtc_commit(struct drm_crtc *crtc)
{
tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
}
static int tilcdc_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode,
int x, int y,
struct drm_framebuffer *old_fb)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_device *dev = crtc->dev;
struct tilcdc_drm_private *priv = dev->dev_private;
const struct tilcdc_panel_info *info = tilcdc_crtc->info;
uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw;
int ret;
ret = tilcdc_crtc_mode_valid(crtc, mode);
if (WARN_ON(ret))
return ret;
if (WARN_ON(!info))
return -EINVAL;
pm_runtime_get_sync(dev->dev);
/* Configure the Burst Size and fifo threshold of DMA: */
reg = tilcdc_read(dev, LCDC_DMA_CTRL_REG) & ~0x00000770;
switch (info->dma_burst_sz) {
case 1:
reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_1);
break;
case 2:
reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_2);
break;
case 4:
reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_4);
break;
case 8:
reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_8);
break;
case 16:
reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_16);
break;
default:
return -EINVAL;
}
reg |= (info->fifo_th << 8);
tilcdc_write(dev, LCDC_DMA_CTRL_REG, reg);
/* Configure timings: */
hbp = mode->htotal - mode->hsync_end;
hfp = mode->hsync_start - mode->hdisplay;
hsw = mode->hsync_end - mode->hsync_start;
vbp = mode->vtotal - mode->vsync_end;
vfp = mode->vsync_start - mode->vdisplay;
vsw = mode->vsync_end - mode->vsync_start;
DBG("%dx%d, hbp=%u, hfp=%u, hsw=%u, vbp=%u, vfp=%u, vsw=%u",
mode->hdisplay, mode->vdisplay, hbp, hfp, hsw, vbp, vfp, vsw);
/* Configure the AC Bias Period and Number of Transitions per Interrupt: */
reg = tilcdc_read(dev, LCDC_RASTER_TIMING_2_REG) & ~0x000fff00;
reg |= LCDC_AC_BIAS_FREQUENCY(info->ac_bias) |
LCDC_AC_BIAS_TRANSITIONS_PER_INT(info->ac_bias_intrpt);
if (priv->rev == 2) {
reg |= (hfp & 0x300) >> 8;
reg |= (hbp & 0x300) >> 4;
reg |= (hsw & 0x3c0) << 21;
}
tilcdc_write(dev, LCDC_RASTER_TIMING_2_REG, reg);
reg = (((mode->hdisplay >> 4) - 1) << 4) |
((hbp & 0xff) << 24) |
((hfp & 0xff) << 16) |
((hsw & 0x3f) << 10);
if (priv->rev == 2)
reg |= (((mode->hdisplay >> 4) - 1) & 0x40) >> 3;
tilcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg);
reg = ((mode->vdisplay - 1) & 0x3ff) |
((vbp & 0xff) << 24) |
((vfp & 0xff) << 16) |
((vsw & 0x3f) << 10);
tilcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg);
/* Configure display type: */
reg = tilcdc_read(dev, LCDC_RASTER_CTRL_REG) &
~(LCDC_TFT_MODE | LCDC_MONO_8BIT_MODE | LCDC_MONOCHROME_MODE |
LCDC_V2_TFT_24BPP_MODE | LCDC_V2_TFT_24BPP_UNPACK | 0x000ff000);
reg |= LCDC_TFT_MODE; /* no monochrome/passive support */
if (info->tft_alt_mode)
reg |= LCDC_TFT_ALT_ENABLE;
if (priv->rev == 2) {
unsigned int depth, bpp;
drm_fb_get_bpp_depth(crtc->fb->pixel_format, &depth, &bpp);
switch (bpp) {
case 16:
break;
case 32:
reg |= LCDC_V2_TFT_24BPP_UNPACK;
/* fallthrough */
case 24:
reg |= LCDC_V2_TFT_24BPP_MODE;
break;
default:
dev_err(dev->dev, "invalid pixel format\n");
return -EINVAL;
}
}
reg |= info->fdd < 12;
tilcdc_write(dev, LCDC_RASTER_CTRL_REG, reg);
if (info->invert_pxl_clk)
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
else
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
if (info->sync_ctrl)
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
else
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
if (info->sync_edge)
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
else
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
else
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
else
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
if (info->raster_order)
tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
else
tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
update_scanout(crtc);
tilcdc_crtc_update_clk(crtc);
pm_runtime_put_sync(dev->dev);
return 0;
}
static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
struct drm_framebuffer *old_fb)
{
update_scanout(crtc);
return 0;
}
static void tilcdc_crtc_load_lut(struct drm_crtc *crtc)
{
}
static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
.destroy = tilcdc_crtc_destroy,
.set_config = drm_crtc_helper_set_config,
.page_flip = tilcdc_crtc_page_flip,
};
static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = {
.dpms = tilcdc_crtc_dpms,
.mode_fixup = tilcdc_crtc_mode_fixup,
.prepare = tilcdc_crtc_prepare,
.commit = tilcdc_crtc_commit,
.mode_set = tilcdc_crtc_mode_set,
.mode_set_base = tilcdc_crtc_mode_set_base,
.load_lut = tilcdc_crtc_load_lut,
};
int tilcdc_crtc_max_width(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
struct tilcdc_drm_private *priv = dev->dev_private;
int max_width = 0;
if (priv->rev == 1)
max_width = 1024;
else if (priv->rev == 2)
max_width = 2048;
return max_width;
}
int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode)
{
struct tilcdc_drm_private *priv = crtc->dev->dev_private;
unsigned int bandwidth;
if (mode->hdisplay > tilcdc_crtc_max_width(crtc))
return MODE_VIRTUAL_X;
/* width must be multiple of 16 */
if (mode->hdisplay & 0xf)
return MODE_VIRTUAL_X;
if (mode->vdisplay > 2048)
return MODE_VIRTUAL_Y;
/* filter out modes that would require too much memory bandwidth: */
bandwidth = mode->hdisplay * mode->vdisplay * drm_mode_vrefresh(mode);
if (bandwidth > priv->max_bandwidth)
return MODE_BAD;
return MODE_OK;
}
void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
const struct tilcdc_panel_info *info)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
tilcdc_crtc->info = info;
}
void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_device *dev = crtc->dev;
struct tilcdc_drm_private *priv = dev->dev_private;
int dpms = tilcdc_crtc->dpms;
unsigned int lcd_clk, div;
int ret;
pm_runtime_get_sync(dev->dev);
if (dpms == DRM_MODE_DPMS_ON)
tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
/* in raster mode, minimum divisor is 2: */
ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
if (ret) {
dev_err(dev->dev, "failed to set display clock rate to: %d\n",
crtc->mode.clock);
goto out;
}
lcd_clk = clk_get_rate(priv->clk);
div = lcd_clk / (crtc->mode.clock * 1000);
DBG("lcd_clk=%u, mode clock=%d, div=%u", lcd_clk, crtc->mode.clock, div);
DBG("fck=%lu, dpll_disp_ck=%lu", clk_get_rate(priv->clk), clk_get_rate(priv->disp_clk));
/* Configure the LCD clock divisor. */
tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
LCDC_RASTER_MODE);
if (priv->rev == 2)
tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
LCDC_V2_CORE_CLK_EN);
if (dpms == DRM_MODE_DPMS_ON)
tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
out:
pm_runtime_put_sync(dev->dev);
}
irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_device *dev = crtc->dev;
struct tilcdc_drm_private *priv = dev->dev_private;
uint32_t stat = tilcdc_read_irqstatus(dev);
if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) {
stop(crtc);
dev_err(dev->dev, "error: %08x\n", stat);
tilcdc_clear_irqstatus(dev, stat);
start(crtc);
} else if (stat & LCDC_PL_LOAD_DONE) {
tilcdc_clear_irqstatus(dev, stat);
} else {
struct drm_pending_vblank_event *event;
unsigned long flags;
uint32_t dirty = tilcdc_crtc->dirty & stat;
tilcdc_clear_irqstatus(dev, stat);
if (dirty & LCDC_END_OF_FRAME0)
set_scanout(crtc, 0);
if (dirty & LCDC_END_OF_FRAME1)
set_scanout(crtc, 1);
drm_handle_vblank(dev, 0);
spin_lock_irqsave(&dev->event_lock, flags);
event = tilcdc_crtc->event;
tilcdc_crtc->event = NULL;
if (event)
drm_send_vblank_event(dev, 0, event);
spin_unlock_irqrestore(&dev->event_lock, flags);
if (dirty && !tilcdc_crtc->dirty)
drm_vblank_put(dev, 0);
}
if (priv->rev == 2) {
if (stat & LCDC_FRAME_DONE) {
tilcdc_crtc->frame_done = true;
wake_up(&tilcdc_crtc->frame_done_wq);
}
tilcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0);
}
return IRQ_HANDLED;
}
void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_pending_vblank_event *event;
struct drm_device *dev = crtc->dev;
unsigned long flags;
/* Destroy the pending vertical blanking event associated with the
* pending page flip, if any, and disable vertical blanking interrupts.
*/
spin_lock_irqsave(&dev->event_lock, flags);
event = tilcdc_crtc->event;
if (event && event->base.file_priv == file) {
tilcdc_crtc->event = NULL;
event->base.destroy(&event->base);
drm_vblank_put(dev, 0);
}
spin_unlock_irqrestore(&dev->event_lock, flags);
}
struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev)
{
struct tilcdc_crtc *tilcdc_crtc;
struct drm_crtc *crtc;
int ret;
tilcdc_crtc = kzalloc(sizeof(*tilcdc_crtc), GFP_KERNEL);
if (!tilcdc_crtc) {
dev_err(dev->dev, "allocation failed\n");
return NULL;
}
crtc = &tilcdc_crtc->base;
tilcdc_crtc->dpms = DRM_MODE_DPMS_OFF;
init_waitqueue_head(&tilcdc_crtc->frame_done_wq);
ret = kfifo_alloc(&tilcdc_crtc->unref_fifo, 16, GFP_KERNEL);
if (ret) {
dev_err(dev->dev, "could not allocate unref FIFO\n");
goto fail;
}
INIT_WORK(&tilcdc_crtc->work, unref_worker);
ret = drm_crtc_init(dev, crtc, &tilcdc_crtc_funcs);
if (ret < 0)
goto fail;
drm_crtc_helper_add(crtc, &tilcdc_crtc_helper_funcs);
return crtc;
fail:
tilcdc_crtc_destroy(crtc);
return NULL;
}
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* LCDC DRM driver, based on da8xx-fb */
#include "tilcdc_drv.h"
#include "tilcdc_regs.h"
#include "tilcdc_tfp410.h"
#include "tilcdc_slave.h"
#include "tilcdc_panel.h"
#include "drm_fb_helper.h"
static LIST_HEAD(module_list);
void tilcdc_module_init(struct tilcdc_module *mod, const char *name,
const struct tilcdc_module_ops *funcs)
{
mod->name = name;
mod->funcs = funcs;
INIT_LIST_HEAD(&mod->list);
list_add(&mod->list, &module_list);
}
void tilcdc_module_cleanup(struct tilcdc_module *mod)
{
list_del(&mod->list);
}
static struct of_device_id tilcdc_of_match[];
static struct drm_framebuffer *tilcdc_fb_create(struct drm_device *dev,
struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd)
{
return drm_fb_cma_create(dev, file_priv, mode_cmd);
}
static void tilcdc_fb_output_poll_changed(struct drm_device *dev)
{
struct tilcdc_drm_private *priv = dev->dev_private;
if (priv->fbdev)
drm_fbdev_cma_hotplug_event(priv->fbdev);
}
static const struct drm_mode_config_funcs mode_config_funcs = {
.fb_create = tilcdc_fb_create,
.output_poll_changed = tilcdc_fb_output_poll_changed,
};
static int modeset_init(struct drm_device *dev)
{
struct tilcdc_drm_private *priv = dev->dev_private;
struct tilcdc_module *mod;
drm_mode_config_init(dev);
priv->crtc = tilcdc_crtc_create(dev);
list_for_each_entry(mod, &module_list, list) {
DBG("loading module: %s", mod->name);
mod->funcs->modeset_init(mod, dev);
}
if ((priv->num_encoders = 0) || (priv->num_connectors == 0)) {
/* oh nos! */
dev_err(dev->dev, "no encoders/connectors found\n");
return -ENXIO;
}
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
dev->mode_config.max_width = tilcdc_crtc_max_width(priv->crtc);
dev->mode_config.max_height = 2048;
dev->mode_config.funcs = &mode_config_funcs;
return 0;
}
#ifdef CONFIG_CPU_FREQ
static int cpufreq_transition(struct notifier_block *nb,
unsigned long val, void *data)
{
struct tilcdc_drm_private *priv = container_of(nb,
struct tilcdc_drm_private, freq_transition);
if (val == CPUFREQ_POSTCHANGE) {
if (priv->lcd_fck_rate != clk_get_rate(priv->clk)) {
priv->lcd_fck_rate = clk_get_rate(priv->clk);
tilcdc_crtc_update_clk(priv->crtc);
}
}
return 0;
}
#endif
/*
* DRM operations:
*/
static int tilcdc_unload(struct drm_device *dev)
{
struct tilcdc_drm_private *priv = dev->dev_private;
struct tilcdc_module *mod, *cur;
drm_kms_helper_poll_fini(dev);
drm_mode_config_cleanup(dev);
drm_vblank_cleanup(dev);
pm_runtime_get_sync(dev->dev);
drm_irq_uninstall(dev);
pm_runtime_put_sync(dev->dev);
#ifdef CONFIG_CPU_FREQ
cpufreq_unregister_notifier(&priv->freq_transition,
CPUFREQ_TRANSITION_NOTIFIER);
#endif
if (priv->clk)
clk_put(priv->clk);
if (priv->mmio)
iounmap(priv->mmio);
flush_workqueue(priv->wq);
destroy_workqueue(priv->wq);
dev->dev_private = NULL;
pm_runtime_disable(dev->dev);
list_for_each_entry_safe(mod, cur, &module_list, list) {
DBG("destroying module: %s", mod->name);
mod->funcs->destroy(mod);
}
kfree(priv);
return 0;
}
static int tilcdc_load(struct drm_device *dev, unsigned long flags)
{
struct platform_device *pdev = dev->platformdev;
struct device_node *node = pdev->dev.of_node;
struct tilcdc_drm_private *priv;
struct resource *res;
int ret;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(dev->dev, "failed to allocate private data\n");
return -ENOMEM;
}
dev->dev_private = priv;
priv->wq = alloc_ordered_workqueue("tilcdc", 0);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev->dev, "failed to get memory resource\n");
ret = -EINVAL;
goto fail;
}
priv->mmio = ioremap_nocache(res->start, resource_size(res));
if (!priv->mmio) {
dev_err(dev->dev, "failed to ioremap\n");
ret = -ENOMEM;
goto fail;
}
priv->clk = clk_get(dev->dev, "fck");
if (IS_ERR(priv->clk)) {
dev_err(dev->dev, "failed to get functional clock\n");
ret = -ENODEV;
goto fail;
}
priv->disp_clk = clk_get(dev->dev, "dpll_disp_ck");
if (IS_ERR(priv->clk)) {
dev_err(dev->dev, "failed to get display clock\n");
ret = -ENODEV;
goto fail;
}
#ifdef CONFIG_CPU_FREQ
priv->lcd_fck_rate = clk_get_rate(priv->clk);
priv->freq_transition.notifier_call = cpufreq_transition;
ret = cpufreq_register_notifier(&priv->freq_transition,
CPUFREQ_TRANSITION_NOTIFIER);
if (ret) {
dev_err(dev->dev, "failed to register cpufreq notifier\n");
goto fail;
}
#endif
if (of_property_read_u32(node, "max-bandwidth", &priv->max_bandwidth))
priv->max_bandwidth = 1280 * 1024 * 60;
pm_runtime_enable(dev->dev);
/* Determine LCD IP Version */
pm_runtime_get_sync(dev->dev);
switch (tilcdc_read(dev, LCDC_PID_REG)) {
case 0x4c100102:
priv->rev = 1;
break;
case 0x4f200800:
case 0x4f201000:
priv->rev = 2;
break;
default:
dev_warn(dev->dev, "Unknown PID Reg value 0x%08x, "
"defaulting to LCD revision 1\n",
tilcdc_read(dev, LCDC_PID_REG));
priv->rev = 1;
break;
}
pm_runtime_put_sync(dev->dev);
ret = modeset_init(dev);
if (ret < 0) {
dev_err(dev->dev, "failed to initialize mode setting\n");
goto fail;
}
ret = drm_vblank_init(dev, 1);
if (ret < 0) {
dev_err(dev->dev, "failed to initialize vblank\n");
goto fail;
}
pm_runtime_get_sync(dev->dev);
ret = drm_irq_install(dev);
pm_runtime_put_sync(dev->dev);
if (ret < 0) {
dev_err(dev->dev, "failed to install IRQ handler\n");
goto fail;
}
platform_set_drvdata(pdev, dev);
priv->fbdev = drm_fbdev_cma_init(dev, 16,
dev->mode_config.num_crtc,
dev->mode_config.num_connector);
drm_kms_helper_poll_init(dev);
return 0;
fail:
tilcdc_unload(dev);
return ret;
}
static void tilcdc_preclose(struct drm_device *dev, struct drm_file *file)
{
struct tilcdc_drm_private *priv = dev->dev_private;
tilcdc_crtc_cancel_page_flip(priv->crtc, file);
}
static void tilcdc_lastclose(struct drm_device *dev)
{
struct tilcdc_drm_private *priv = dev->dev_private;
drm_fbdev_cma_restore_mode(priv->fbdev);
}
static irqreturn_t tilcdc_irq(DRM_IRQ_ARGS)
{
struct drm_device *dev = arg;
struct tilcdc_drm_private *priv = dev->dev_private;
return tilcdc_crtc_irq(priv->crtc);
}
static void tilcdc_irq_preinstall(struct drm_device *dev)
{
tilcdc_clear_irqstatus(dev, 0xffffffff);
}
static int tilcdc_irq_postinstall(struct drm_device *dev)
{
struct tilcdc_drm_private *priv = dev->dev_private;
/* enable FIFO underflow irq: */
if (priv->rev == 1) {
tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_UNDERFLOW_INT_ENA);
} else {
tilcdc_set(dev, LCDC_INT_ENABLE_SET_REG, LCDC_V2_UNDERFLOW_INT_ENA);
}
return 0;
}
static void tilcdc_irq_uninstall(struct drm_device *dev)
{
struct tilcdc_drm_private *priv = dev->dev_private;
/* disable irqs that we might have enabled: */
if (priv->rev == 1) {
tilcdc_clear(dev, LCDC_RASTER_CTRL_REG,
LCDC_V1_UNDERFLOW_INT_ENA | LCDC_V1_PL_INT_ENA);
tilcdc_clear(dev, LCDC_DMA_CTRL_REG, LCDC_V1_END_OF_FRAME_INT_ENA);
} else {
tilcdc_clear(dev, LCDC_INT_ENABLE_SET_REG,
LCDC_V2_UNDERFLOW_INT_ENA | LCDC_V2_PL_INT_ENA |
LCDC_V2_END_OF_FRAME0_INT_ENA | LCDC_V2_END_OF_FRAME1_INT_ENA |
LCDC_FRAME_DONE);
}
}
static void enable_vblank(struct drm_device *dev, bool enable)
{
struct tilcdc_drm_private *priv = dev->dev_private;
u32 reg, mask;
if (priv->rev == 1) {
reg = LCDC_DMA_CTRL_REG;
mask = LCDC_V1_END_OF_FRAME_INT_ENA;
} else {
reg = LCDC_INT_ENABLE_SET_REG;
mask = LCDC_V2_END_OF_FRAME0_INT_ENA |
LCDC_V2_END_OF_FRAME1_INT_ENA | LCDC_FRAME_DONE;
}
if (enable)
tilcdc_set(dev, reg, mask);
else
tilcdc_clear(dev, reg, mask);
}
static int tilcdc_enable_vblank(struct drm_device *dev, int crtc)
{
enable_vblank(dev, true);
return 0;
}
static void tilcdc_disable_vblank(struct drm_device *dev, int crtc)
{
enable_vblank(dev, false);
}
#if defined(CONFIG_DEBUG_FS) || defined(CONFIG_PM_SLEEP)
static const struct {
const char *name;
uint8_t rev;
uint8_t save;
uint32_t reg;
} registers[] = {
#define REG(rev, save, reg) { #reg, rev, save, reg }
/* exists in revision 1: */
REG(1, false, LCDC_PID_REG),
REG(1, true, LCDC_CTRL_REG),
REG(1, false, LCDC_STAT_REG),
REG(1, true, LCDC_RASTER_CTRL_REG),
REG(1, true, LCDC_RASTER_TIMING_0_REG),
REG(1, true, LCDC_RASTER_TIMING_1_REG),
REG(1, true, LCDC_RASTER_TIMING_2_REG),
REG(1, true, LCDC_DMA_CTRL_REG),
REG(1, true, LCDC_DMA_FB_BASE_ADDR_0_REG),
REG(1, true, LCDC_DMA_FB_CEILING_ADDR_0_REG),
REG(1, true, LCDC_DMA_FB_BASE_ADDR_1_REG),
REG(1, true, LCDC_DMA_FB_CEILING_ADDR_1_REG),
/* new in revision 2: */
REG(2, false, LCDC_RAW_STAT_REG),
REG(2, false, LCDC_MASKED_STAT_REG),
REG(2, false, LCDC_INT_ENABLE_SET_REG),
REG(2, false, LCDC_INT_ENABLE_CLR_REG),
REG(2, false, LCDC_END_OF_INT_IND_REG),
REG(2, true, LCDC_CLK_ENABLE_REG),
REG(2, true, LCDC_INT_ENABLE_SET_REG),
#undef REG
};
#endif
#ifdef CONFIG_DEBUG_FS
static int tilcdc_regs_show(struct seq_file *m, void *arg)
{
struct drm_info_node *node = (struct drm_info_node *) m->private;
struct drm_device *dev = node->minor->dev;
struct tilcdc_drm_private *priv = dev->dev_private;
unsigned i;
pm_runtime_get_sync(dev->dev);
seq_printf(m, "revision: %d\n", priv->rev);
for (i = 0; i < ARRAY_SIZE(registers); i++)
if (priv->rev >= registers[i].rev)
seq_printf(m, "%s:\t %08x\n", registers[i].name,
tilcdc_read(dev, registers[i].reg));
pm_runtime_put_sync(dev->dev);
return 0;
}
static int tilcdc_mm_show(struct seq_file *m, void *arg)
{
struct drm_info_node *node = (struct drm_info_node *) m->private;
struct drm_device *dev = node->minor->dev;
return drm_mm_dump_table(m, dev->mm_private);
}
static struct drm_info_list tilcdc_debugfs_list[] = {
{ "regs", tilcdc_regs_show, 0 },
{ "mm", tilcdc_mm_show, 0 },
{ "fb", drm_fb_cma_debugfs_show, 0 },
};
static int tilcdc_debugfs_init(struct drm_minor *minor)
{
struct drm_device *dev = minor->dev;
struct tilcdc_module *mod;
int ret;
ret = drm_debugfs_create_files(tilcdc_debugfs_list,
ARRAY_SIZE(tilcdc_debugfs_list),
minor->debugfs_root, minor);
list_for_each_entry(mod, &module_list, list)
if (mod->funcs->debugfs_init)
mod->funcs->debugfs_init(mod, minor);
if (ret) {
dev_err(dev->dev, "could not install tilcdc_debugfs_list\n");
return ret;
}
return ret;
}
static void tilcdc_debugfs_cleanup(struct drm_minor *minor)
{
struct tilcdc_module *mod;
drm_debugfs_remove_files(tilcdc_debugfs_list,
ARRAY_SIZE(tilcdc_debugfs_list), minor);
list_for_each_entry(mod, &module_list, list)
if (mod->funcs->debugfs_cleanup)
mod->funcs->debugfs_cleanup(mod, minor);
}
#endif
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = drm_compat_ioctl,
#endif
.poll = drm_poll,
.read = drm_read,
.fasync = drm_fasync,
.llseek = no_llseek,
.mmap = drm_gem_cma_mmap,
};
static struct drm_driver tilcdc_driver = {
.driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
.load = tilcdc_load,
.unload = tilcdc_unload,
.preclose = tilcdc_preclose,
.lastclose = tilcdc_lastclose,
.irq_handler = tilcdc_irq,
.irq_preinstall = tilcdc_irq_preinstall,
.irq_postinstall = tilcdc_irq_postinstall,
.irq_uninstall = tilcdc_irq_uninstall,
.get_vblank_counter = drm_vblank_count,
.enable_vblank = tilcdc_enable_vblank,
.disable_vblank = tilcdc_disable_vblank,
.gem_free_object = drm_gem_cma_free_object,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.dumb_create = drm_gem_cma_dumb_create,
.dumb_map_offset = drm_gem_cma_dumb_map_offset,
.dumb_destroy = drm_gem_cma_dumb_destroy,
#ifdef CONFIG_DEBUG_FS
.debugfs_init = tilcdc_debugfs_init,
.debugfs_cleanup = tilcdc_debugfs_cleanup,
#endif
.fops = &fops,
.name = "tilcdc",
.desc = "TI LCD Controller DRM",
.date = "20121205",
.major = 1,
.minor = 0,
};
/*
* Power management:
*/
#ifdef CONFIG_PM_SLEEP
static int tilcdc_pm_suspend(struct device *dev)
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct tilcdc_drm_private *priv = ddev->dev_private;
unsigned i, n = 0;
drm_kms_helper_poll_disable(ddev);
/* Save register state: */
for (i = 0; i < ARRAY_SIZE(registers); i++)
if (registers[i].save && (priv->rev >= registers[i].rev))
priv->saved_register[n++] = tilcdc_read(ddev, registers[i].reg);
return 0;
}
static int tilcdc_pm_resume(struct device *dev)
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct tilcdc_drm_private *priv = ddev->dev_private;
unsigned i, n = 0;
/* Restore register state: */
for (i = 0; i < ARRAY_SIZE(registers); i++)
if (registers[i].save && (priv->rev >= registers[i].rev))
tilcdc_write(ddev, registers[i].reg, priv->saved_register[n++]);
drm_kms_helper_poll_enable(ddev);
return 0;
}
#endif
static const struct dev_pm_ops tilcdc_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(tilcdc_pm_suspend, tilcdc_pm_resume)
};
/*
* Platform driver:
*/
static int tilcdc_pdev_probe(struct platform_device *pdev)
{
/* bail out early if no DT data: */
if (!pdev->dev.of_node) {
dev_err(&pdev->dev, "device-tree data is missing\n");
return -ENXIO;
}
return drm_platform_init(&tilcdc_driver, pdev);
}
static int tilcdc_pdev_remove(struct platform_device *pdev)
{
drm_platform_exit(&tilcdc_driver, pdev);
return 0;
}
static struct of_device_id tilcdc_of_match[] = {
{ .compatible = "ti,am33xx-tilcdc", },
{ },
};
MODULE_DEVICE_TABLE(of, tilcdc_of_match);
static struct platform_driver tilcdc_platform_driver = {
.probe = tilcdc_pdev_probe,
.remove = tilcdc_pdev_remove,
.driver = {
.owner = THIS_MODULE,
.name = "tilcdc",
.pm = &tilcdc_pm_ops,
.of_match_table = tilcdc_of_match,
},
};
static int __init tilcdc_drm_init(void)
{
DBG("init");
tilcdc_tfp410_init();
tilcdc_slave_init();
tilcdc_panel_init();
return platform_driver_register(&tilcdc_platform_driver);
}
static void __exit tilcdc_drm_fini(void)
{
DBG("fini");
tilcdc_tfp410_fini();
tilcdc_slave_fini();
tilcdc_panel_fini();
platform_driver_unregister(&tilcdc_platform_driver);
}
late_initcall(tilcdc_drm_init);
module_exit(tilcdc_drm_fini);
MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
MODULE_DESCRIPTION("TI LCD Controller DRM Driver");
MODULE_LICENSE("GPL");
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TILCDC_DRV_H__
#define __TILCDC_DRV_H__
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/list.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_fb_cma_helper.h>
struct tilcdc_drm_private {
void __iomem *mmio;
struct clk *disp_clk; /* display dpll */
struct clk *clk; /* functional clock */
int rev; /* IP revision */
/* don't attempt resolutions w/ higher W * H * Hz: */
uint32_t max_bandwidth;
/* register contents saved across suspend/resume: */
u32 saved_register[12];
#ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition;
unsigned int lcd_fck_rate;
#endif
struct workqueue_struct *wq;
struct drm_fbdev_cma *fbdev;
struct drm_crtc *crtc;
unsigned int num_encoders;
struct drm_encoder *encoders[8];
unsigned int num_connectors;
struct drm_connector *connectors[8];
};
/* Sub-module for display. Since we don't know at compile time what panels
* or display adapter(s) might be present (for ex, off chip dvi/tfp410,
* hdmi encoder, various lcd panels), the connector/encoder(s) are split into
* separate drivers. If they are probed and found to be present, they
* register themselves with tilcdc_register_module().
*/
struct tilcdc_module;
struct tilcdc_module_ops {
/* create appropriate encoders/connectors: */
int (*modeset_init)(struct tilcdc_module *mod, struct drm_device *dev);
void (*destroy)(struct tilcdc_module *mod);
#ifdef CONFIG_DEBUG_FS
/* create debugfs nodes (can be NULL): */
int (*debugfs_init)(struct tilcdc_module *mod, struct drm_minor *minor);
/* cleanup debugfs nodes (can be NULL): */
void (*debugfs_cleanup)(struct tilcdc_module *mod, struct drm_minor *minor);
#endif
};
struct tilcdc_module {
const char *name;
struct list_head list;
const struct tilcdc_module_ops *funcs;
};
void tilcdc_module_init(struct tilcdc_module *mod, const char *name,
const struct tilcdc_module_ops *funcs);
void tilcdc_module_cleanup(struct tilcdc_module *mod);
/* Panel config that needs to be set in the crtc, but is not coming from
* the mode timings. The display module is expected to call
* tilcdc_crtc_set_panel_info() to set this during modeset.
*/
struct tilcdc_panel_info {
/* AC Bias Pin Frequency */
uint32_t ac_bias;
/* AC Bias Pin Transitions per Interrupt */
uint32_t ac_bias_intrpt;
/* DMA burst size */
uint32_t dma_burst_sz;
/* Bits per pixel */
uint32_t bpp;
/* FIFO DMA Request Delay */
uint32_t fdd;
/* TFT Alternative Signal Mapping (Only for active) */
bool tft_alt_mode;
/* Invert pixel clock */
bool invert_pxl_clk;
/* Horizontal and Vertical Sync Edge: 0=rising 1=falling */
uint32_t sync_edge;
/* Horizontal and Vertical Sync: Control: 0=ignore */
uint32_t sync_ctrl;
/* Raster Data Order Select: 1=Most-to-least 0=Least-to-most */
uint32_t raster_order;
/* DMA FIFO threshold */
uint32_t fifo_th;
};
#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev);
void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file);
irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc);
void tilcdc_crtc_update_clk(struct drm_crtc *crtc);
void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
const struct tilcdc_panel_info *info);
int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode);
int tilcdc_crtc_max_width(struct drm_crtc *crtc);
#endif /* __TILCDC_DRV_H__ */
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/pinctrl/pinmux.h>
#include <linux/pinctrl/consumer.h>
#include <linux/backlight.h>
#include <video/display_timing.h>
#include <video/of_display_timing.h>
#include <video/videomode.h>
#include "tilcdc_drv.h"
struct panel_module {
struct tilcdc_module base;
struct tilcdc_panel_info *info;
struct display_timings *timings;
struct backlight_device *backlight;
};
#define to_panel_module(x) container_of(x, struct panel_module, base)
/*
* Encoder:
*/
struct panel_encoder {
struct drm_encoder base;
struct panel_module *mod;
};
#define to_panel_encoder(x) container_of(x, struct panel_encoder, base)
static void panel_encoder_destroy(struct drm_encoder *encoder)
{
struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
drm_encoder_cleanup(encoder);
kfree(panel_encoder);
}
static void panel_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
struct backlight_device *backlight = panel_encoder->mod->backlight;
if (!backlight)
return;
backlight->props.power = mode == DRM_MODE_DPMS_ON
? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
backlight_update_status(backlight);
}
static bool panel_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
/* nothing needed */
return true;
}
static void panel_encoder_prepare(struct drm_encoder *encoder)
{
struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
tilcdc_crtc_set_panel_info(encoder->crtc, panel_encoder->mod->info);
}
static void panel_encoder_commit(struct drm_encoder *encoder)
{
panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
}
static void panel_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
/* nothing needed */
}
static const struct drm_encoder_funcs panel_encoder_funcs = {
.destroy = panel_encoder_destroy,
};
static const struct drm_encoder_helper_funcs panel_encoder_helper_funcs = {
.dpms = panel_encoder_dpms,
.mode_fixup = panel_encoder_mode_fixup,
.prepare = panel_encoder_prepare,
.commit = panel_encoder_commit,
.mode_set = panel_encoder_mode_set,
};
static struct drm_encoder *panel_encoder_create(struct drm_device *dev,
struct panel_module *mod)
{
struct panel_encoder *panel_encoder;
struct drm_encoder *encoder;
int ret;
panel_encoder = kzalloc(sizeof(*panel_encoder), GFP_KERNEL);
if (!panel_encoder) {
dev_err(dev->dev, "allocation failed\n");
return NULL;
}
panel_encoder->mod = mod;
encoder = &panel_encoder->base;
encoder->possible_crtcs = 1;
ret = drm_encoder_init(dev, encoder, &panel_encoder_funcs,
DRM_MODE_ENCODER_LVDS);
if (ret < 0)
goto fail;
drm_encoder_helper_add(encoder, &panel_encoder_helper_funcs);
return encoder;
fail:
panel_encoder_destroy(encoder);
return NULL;
}
/*
* Connector:
*/
struct panel_connector {
struct drm_connector base;
struct drm_encoder *encoder; /* our connected encoder */
struct panel_module *mod;
};
#define to_panel_connector(x) container_of(x, struct panel_connector, base)
static void panel_connector_destroy(struct drm_connector *connector)
{
struct panel_connector *panel_connector = to_panel_connector(connector);
drm_connector_cleanup(connector);
kfree(panel_connector);
}
static enum drm_connector_status panel_connector_detect(
struct drm_connector *connector,
bool force)
{
return connector_status_connected;
}
static int panel_connector_get_modes(struct drm_connector *connector)
{
struct drm_device *dev = connector->dev;
struct panel_connector *panel_connector = to_panel_connector(connector);
struct display_timings *timings = panel_connector->mod->timings;
int i;
for (i = 0; i < timings->num_timings; i++) {
struct drm_display_mode *mode = drm_mode_create(dev);
struct videomode vm;
if (videomode_from_timing(timings, &vm, i))
break;
drm_display_mode_from_videomode(&vm, mode);
mode->type = DRM_MODE_TYPE_DRIVER;
if (timings->native_mode == i)
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_set_name(mode);
drm_mode_probed_add(connector, mode);
}
return i;
}
static int panel_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct tilcdc_drm_private *priv = connector->dev->dev_private;
/* our only constraints are what the crtc can generate: */
return tilcdc_crtc_mode_valid(priv->crtc, mode);
}
static struct drm_encoder *panel_connector_best_encoder(
struct drm_connector *connector)
{
struct panel_connector *panel_connector = to_panel_connector(connector);
return panel_connector->encoder;
}
static const struct drm_connector_funcs panel_connector_funcs = {
.destroy = panel_connector_destroy,
.dpms = drm_helper_connector_dpms,
.detect = panel_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
};
static const struct drm_connector_helper_funcs panel_connector_helper_funcs = {
.get_modes = panel_connector_get_modes,
.mode_valid = panel_connector_mode_valid,
.best_encoder = panel_connector_best_encoder,
};
static struct drm_connector *panel_connector_create(struct drm_device *dev,
struct panel_module *mod, struct drm_encoder *encoder)
{
struct panel_connector *panel_connector;
struct drm_connector *connector;
int ret;
panel_connector = kzalloc(sizeof(*panel_connector), GFP_KERNEL);
if (!panel_connector) {
dev_err(dev->dev, "allocation failed\n");
return NULL;
}
panel_connector->encoder = encoder;
panel_connector->mod = mod;
connector = &panel_connector->base;
drm_connector_init(dev, connector, &panel_connector_funcs,
DRM_MODE_CONNECTOR_LVDS);
drm_connector_helper_add(connector, &panel_connector_helper_funcs);
connector->interlace_allowed = 0;
connector->doublescan_allowed = 0;
ret = drm_mode_connector_attach_encoder(connector, encoder);
if (ret)
goto fail;
drm_sysfs_connector_add(connector);
return connector;
fail:
panel_connector_destroy(connector);
return NULL;
}
/*
* Module:
*/
static int panel_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
{
struct panel_module *panel_mod = to_panel_module(mod);
struct tilcdc_drm_private *priv = dev->dev_private;
struct drm_encoder *encoder;
struct drm_connector *connector;
encoder = panel_encoder_create(dev, panel_mod);
if (!encoder)
return -ENOMEM;
connector = panel_connector_create(dev, panel_mod, encoder);
if (!connector)
return -ENOMEM;
priv->encoders[priv->num_encoders++] = encoder;
priv->connectors[priv->num_connectors++] = connector;
return 0;
}
static void panel_destroy(struct tilcdc_module *mod)
{
struct panel_module *panel_mod = to_panel_module(mod);
if (panel_mod->timings) {
display_timings_release(panel_mod->timings);
kfree(panel_mod->timings);
}
tilcdc_module_cleanup(mod);
kfree(panel_mod->info);
kfree(panel_mod);
}
static const struct tilcdc_module_ops panel_module_ops = {
.modeset_init = panel_modeset_init,
.destroy = panel_destroy,
};
/*
* Device:
*/
/* maybe move this somewhere common if it is needed by other outputs? */
static struct tilcdc_panel_info * of_get_panel_info(struct device_node *np)
{
struct device_node *info_np;
struct tilcdc_panel_info *info;
int ret = 0;
if (!np) {
pr_err("%s: no devicenode given\n", __func__);
return NULL;
}
info_np = of_get_child_by_name(np, "panel-info");
if (!info_np) {
pr_err("%s: could not find panel-info node\n", __func__);
return NULL;
}
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info) {
pr_err("%s: allocation failed\n", __func__);
return NULL;
}
ret |= of_property_read_u32(info_np, "ac-bias", &info->ac_bias);
ret |= of_property_read_u32(info_np, "ac-bias-intrpt", &info->ac_bias_intrpt);
ret |= of_property_read_u32(info_np, "dma-burst-sz", &info->dma_burst_sz);
ret |= of_property_read_u32(info_np, "bpp", &info->bpp);
ret |= of_property_read_u32(info_np, "fdd", &info->fdd);
ret |= of_property_read_u32(info_np, "sync-edge", &info->sync_edge);
ret |= of_property_read_u32(info_np, "sync-ctrl", &info->sync_ctrl);
ret |= of_property_read_u32(info_np, "raster-order", &info->raster_order);
ret |= of_property_read_u32(info_np, "fifo-th", &info->fifo_th);
/* optional: */
info->tft_alt_mode = of_property_read_bool(info_np, "tft-alt-mode");
info->invert_pxl_clk = of_property_read_bool(info_np, "invert-pxl-clk");
if (ret) {
pr_err("%s: error reading panel-info properties\n", __func__);
kfree(info);
return NULL;
}
return info;
}
static struct of_device_id panel_of_match[];
static int panel_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct panel_module *panel_mod;
struct tilcdc_module *mod;
struct pinctrl *pinctrl;
int ret = -EINVAL;
/* bail out early if no DT data: */
if (!node) {
dev_err(&pdev->dev, "device-tree data is missing\n");
return -ENXIO;
}
panel_mod = kzalloc(sizeof(*panel_mod), GFP_KERNEL);
if (!panel_mod)
return -ENOMEM;
mod = &panel_mod->base;
tilcdc_module_init(mod, "panel", &panel_module_ops);
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl))
dev_warn(&pdev->dev, "pins are not configured\n");
panel_mod->timings = of_get_display_timings(node);
if (!panel_mod->timings) {
dev_err(&pdev->dev, "could not get panel timings\n");
goto fail;
}
panel_mod->info = of_get_panel_info(node);
if (!panel_mod->info) {
dev_err(&pdev->dev, "could not get panel info\n");
goto fail;
}
panel_mod->backlight = of_find_backlight_by_node(node);
if (panel_mod->backlight)
dev_info(&pdev->dev, "found backlight\n");
return 0;
fail:
panel_destroy(mod);
return ret;
}
static int panel_remove(struct platform_device *pdev)
{
return 0;
}
static struct of_device_id panel_of_match[] = {
{ .compatible = "ti,tilcdc,panel", },
{ },
};
MODULE_DEVICE_TABLE(of, panel_of_match);
struct platform_driver panel_driver = {
.probe = panel_probe,
.remove = panel_remove,
.driver = {
.owner = THIS_MODULE,
.name = "panel",
.of_match_table = panel_of_match,
},
};
int __init tilcdc_panel_init(void)
{
return platform_driver_register(&panel_driver);
}
void __exit tilcdc_panel_fini(void)
{
platform_driver_unregister(&panel_driver);
}
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TILCDC_PANEL_H__
#define __TILCDC_PANEL_H__
/* sub-module for generic lcd panel output */
int tilcdc_panel_init(void);
void tilcdc_panel_fini(void);
#endif /* __TILCDC_PANEL_H__ */
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TILCDC_REGS_H__
#define __TILCDC_REGS_H__
/* LCDC register definitions, based on da8xx-fb */
#include <linux/bitops.h>
#include "tilcdc_drv.h"
/* LCDC Status Register */
#define LCDC_END_OF_FRAME1 BIT(9)
#define LCDC_END_OF_FRAME0 BIT(8)
#define LCDC_PL_LOAD_DONE BIT(6)
#define LCDC_FIFO_UNDERFLOW BIT(5)
#define LCDC_SYNC_LOST BIT(2)
#define LCDC_FRAME_DONE BIT(0)
/* LCDC DMA Control Register */
#define LCDC_DMA_BURST_SIZE(x) ((x) << 4)
#define LCDC_DMA_BURST_1 0x0
#define LCDC_DMA_BURST_2 0x1
#define LCDC_DMA_BURST_4 0x2
#define LCDC_DMA_BURST_8 0x3
#define LCDC_DMA_BURST_16 0x4
#define LCDC_V1_END_OF_FRAME_INT_ENA BIT(2)
#define LCDC_V2_END_OF_FRAME0_INT_ENA BIT(8)
#define LCDC_V2_END_OF_FRAME1_INT_ENA BIT(9)
#define LCDC_DUAL_FRAME_BUFFER_ENABLE BIT(0)
/* LCDC Control Register */
#define LCDC_CLK_DIVISOR(x) ((x) << 8)
#define LCDC_RASTER_MODE 0x01
/* LCDC Raster Control Register */
#define LCDC_PALETTE_LOAD_MODE(x) ((x) << 20)
#define PALETTE_AND_DATA 0x00
#define PALETTE_ONLY 0x01
#define DATA_ONLY 0x02
#define LCDC_MONO_8BIT_MODE BIT(9)
#define LCDC_RASTER_ORDER BIT(8)
#define LCDC_TFT_MODE BIT(7)
#define LCDC_V1_UNDERFLOW_INT_ENA BIT(6)
#define LCDC_V2_UNDERFLOW_INT_ENA BIT(5)
#define LCDC_V1_PL_INT_ENA BIT(4)
#define LCDC_V2_PL_INT_ENA BIT(6)
#define LCDC_MONOCHROME_MODE BIT(1)
#define LCDC_RASTER_ENABLE BIT(0)
#define LCDC_TFT_ALT_ENABLE BIT(23)
#define LCDC_STN_565_ENABLE BIT(24)
#define LCDC_V2_DMA_CLK_EN BIT(2)
#define LCDC_V2_LIDD_CLK_EN BIT(1)
#define LCDC_V2_CORE_CLK_EN BIT(0)
#define LCDC_V2_LPP_B10 26
#define LCDC_V2_TFT_24BPP_MODE BIT(25)
#define LCDC_V2_TFT_24BPP_UNPACK BIT(26)
/* LCDC Raster Timing 2 Register */
#define LCDC_AC_BIAS_TRANSITIONS_PER_INT(x) ((x) << 16)
#define LCDC_AC_BIAS_FREQUENCY(x) ((x) << 8)
#define LCDC_SYNC_CTRL BIT(25)
#define LCDC_SYNC_EDGE BIT(24)
#define LCDC_INVERT_PIXEL_CLOCK BIT(22)
#define LCDC_INVERT_HSYNC BIT(21)
#define LCDC_INVERT_VSYNC BIT(20)
/* LCDC Block */
#define LCDC_PID_REG 0x0
#define LCDC_CTRL_REG 0x4
#define LCDC_STAT_REG 0x8
#define LCDC_RASTER_CTRL_REG 0x28
#define LCDC_RASTER_TIMING_0_REG 0x2c
#define LCDC_RASTER_TIMING_1_REG 0x30
#define LCDC_RASTER_TIMING_2_REG 0x34
#define LCDC_DMA_CTRL_REG 0x40
#define LCDC_DMA_FB_BASE_ADDR_0_REG 0x44
#define LCDC_DMA_FB_CEILING_ADDR_0_REG 0x48
#define LCDC_DMA_FB_BASE_ADDR_1_REG 0x4c
#define LCDC_DMA_FB_CEILING_ADDR_1_REG 0x50
/* Interrupt Registers available only in Version 2 */
#define LCDC_RAW_STAT_REG 0x58
#define LCDC_MASKED_STAT_REG 0x5c
#define LCDC_INT_ENABLE_SET_REG 0x60
#define LCDC_INT_ENABLE_CLR_REG 0x64
#define LCDC_END_OF_INT_IND_REG 0x68
/* Clock registers available only on Version 2 */
#define LCDC_CLK_ENABLE_REG 0x6c
#define LCDC_CLK_RESET_REG 0x70
#define LCDC_CLK_MAIN_RESET BIT(3)
/*
* Helpers:
*/
static inline void tilcdc_write(struct drm_device *dev, u32 reg, u32 data)
{
struct tilcdc_drm_private *priv = dev->dev_private;
iowrite32(data, priv->mmio + reg);
}
static inline u32 tilcdc_read(struct drm_device *dev, u32 reg)
{
struct tilcdc_drm_private *priv = dev->dev_private;
return ioread32(priv->mmio + reg);
}
static inline void tilcdc_set(struct drm_device *dev, u32 reg, u32 mask)
{
tilcdc_write(dev, reg, tilcdc_read(dev, reg) | mask);
}
static inline void tilcdc_clear(struct drm_device *dev, u32 reg, u32 mask)
{
tilcdc_write(dev, reg, tilcdc_read(dev, reg) & ~mask);
}
/* the register to read/clear irqstatus differs between v1 and v2 of the IP */
static inline u32 tilcdc_irqstatus_reg(struct drm_device *dev)
{
struct tilcdc_drm_private *priv = dev->dev_private;
return (priv->rev == 2) ? LCDC_MASKED_STAT_REG : LCDC_STAT_REG;
}
static inline u32 tilcdc_read_irqstatus(struct drm_device *dev)
{
return tilcdc_read(dev, tilcdc_irqstatus_reg(dev));
}
static inline void tilcdc_clear_irqstatus(struct drm_device *dev, u32 mask)
{
tilcdc_write(dev, tilcdc_irqstatus_reg(dev), mask);
}
#endif /* __TILCDC_REGS_H__ */
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/i2c.h>
#include <linux/of_i2c.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/pinctrl/consumer.h>
#include <drm/drm_encoder_slave.h>
#include "tilcdc_drv.h"
struct slave_module {
struct tilcdc_module base;
struct i2c_adapter *i2c;
};
#define to_slave_module(x) container_of(x, struct slave_module, base)
static const struct tilcdc_panel_info slave_info = {
.bpp = 16,
.ac_bias = 255,
.ac_bias_intrpt = 0,
.dma_burst_sz = 16,
.fdd = 0x80,
.tft_alt_mode = 0,
.sync_edge = 0,
.sync_ctrl = 1,
.raster_order = 0,
};
/*
* Encoder:
*/
struct slave_encoder {
struct drm_encoder_slave base;
struct slave_module *mod;
};
#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base)
static inline struct drm_encoder_slave_funcs *
get_slave_funcs(struct drm_encoder *enc)
{
return to_encoder_slave(enc)->slave_funcs;
}
static void slave_encoder_destroy(struct drm_encoder *encoder)
{
struct slave_encoder *slave_encoder = to_slave_encoder(encoder);
if (get_slave_funcs(encoder))
get_slave_funcs(encoder)->destroy(encoder);
drm_encoder_cleanup(encoder);
kfree(slave_encoder);
}
static void slave_encoder_prepare(struct drm_encoder *encoder)
{
drm_i2c_encoder_prepare(encoder);
tilcdc_crtc_set_panel_info(encoder->crtc, &slave_info);
}
static const struct drm_encoder_funcs slave_encoder_funcs = {
.destroy = slave_encoder_destroy,
};
static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
.dpms = drm_i2c_encoder_dpms,
.mode_fixup = drm_i2c_encoder_mode_fixup,
.prepare = slave_encoder_prepare,
.commit = drm_i2c_encoder_commit,
.mode_set = drm_i2c_encoder_mode_set,
.save = drm_i2c_encoder_save,
.restore = drm_i2c_encoder_restore,
};
static const struct i2c_board_info info = {
I2C_BOARD_INFO("tda998x", 0x70)
};
static struct drm_encoder *slave_encoder_create(struct drm_device *dev,
struct slave_module *mod)
{
struct slave_encoder *slave_encoder;
struct drm_encoder *encoder;
int ret;
slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL);
if (!slave_encoder) {
dev_err(dev->dev, "allocation failed\n");
return NULL;
}
slave_encoder->mod = mod;
encoder = &slave_encoder->base.base;
encoder->possible_crtcs = 1;
ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs,
DRM_MODE_ENCODER_TMDS);
if (ret)
goto fail;
drm_encoder_helper_add(encoder, &slave_encoder_helper_funcs);
ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), mod->i2c, &info);
if (ret)
goto fail;
return encoder;
fail:
slave_encoder_destroy(encoder);
return NULL;
}
/*
* Connector:
*/
struct slave_connector {
struct drm_connector base;
struct drm_encoder *encoder; /* our connected encoder */
struct slave_module *mod;
};
#define to_slave_connector(x) container_of(x, struct slave_connector, base)
static void slave_connector_destroy(struct drm_connector *connector)
{
struct slave_connector *slave_connector = to_slave_connector(connector);
drm_connector_cleanup(connector);
kfree(slave_connector);
}
static enum drm_connector_status slave_connector_detect(
struct drm_connector *connector,
bool force)
{
struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
return get_slave_funcs(encoder)->detect(encoder, connector);
}
static int slave_connector_get_modes(struct drm_connector *connector)
{
struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
return get_slave_funcs(encoder)->get_modes(encoder, connector);
}
static int slave_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
struct tilcdc_drm_private *priv = connector->dev->dev_private;
int ret;
ret = tilcdc_crtc_mode_valid(priv->crtc, mode);
if (ret != MODE_OK)
return ret;
return get_slave_funcs(encoder)->mode_valid(encoder, mode);
}
static struct drm_encoder *slave_connector_best_encoder(
struct drm_connector *connector)
{
struct slave_connector *slave_connector = to_slave_connector(connector);
return slave_connector->encoder;
}
static int slave_connector_set_property(struct drm_connector *connector,
struct drm_property *property, uint64_t value)
{
struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
return get_slave_funcs(encoder)->set_property(encoder,
connector, property, value);
}
static const struct drm_connector_funcs slave_connector_funcs = {
.destroy = slave_connector_destroy,
.dpms = drm_helper_connector_dpms,
.detect = slave_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.set_property = slave_connector_set_property,
};
static const struct drm_connector_helper_funcs slave_connector_helper_funcs = {
.get_modes = slave_connector_get_modes,
.mode_valid = slave_connector_mode_valid,
.best_encoder = slave_connector_best_encoder,
};
static struct drm_connector *slave_connector_create(struct drm_device *dev,
struct slave_module *mod, struct drm_encoder *encoder)
{
struct slave_connector *slave_connector;
struct drm_connector *connector;
int ret;
slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL);
if (!slave_connector) {
dev_err(dev->dev, "allocation failed\n");
return NULL;
}
slave_connector->encoder = encoder;
slave_connector->mod = mod;
connector = &slave_connector->base;
drm_connector_init(dev, connector, &slave_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA);
drm_connector_helper_add(connector, &slave_connector_helper_funcs);
connector->polled = DRM_CONNECTOR_POLL_CONNECT |
DRM_CONNECTOR_POLL_DISCONNECT;
connector->interlace_allowed = 0;
connector->doublescan_allowed = 0;
get_slave_funcs(encoder)->create_resources(encoder, connector);
ret = drm_mode_connector_attach_encoder(connector, encoder);
if (ret)
goto fail;
drm_sysfs_connector_add(connector);
return connector;
fail:
slave_connector_destroy(connector);
return NULL;
}
/*
* Module:
*/
static int slave_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
{
struct slave_module *slave_mod = to_slave_module(mod);
struct tilcdc_drm_private *priv = dev->dev_private;
struct drm_encoder *encoder;
struct drm_connector *connector;
encoder = slave_encoder_create(dev, slave_mod);
if (!encoder)
return -ENOMEM;
connector = slave_connector_create(dev, slave_mod, encoder);
if (!connector)
return -ENOMEM;
priv->encoders[priv->num_encoders++] = encoder;
priv->connectors[priv->num_connectors++] = connector;
return 0;
}
static void slave_destroy(struct tilcdc_module *mod)
{
struct slave_module *slave_mod = to_slave_module(mod);
tilcdc_module_cleanup(mod);
kfree(slave_mod);
}
static const struct tilcdc_module_ops slave_module_ops = {
.modeset_init = slave_modeset_init,
.destroy = slave_destroy,
};
/*
* Device:
*/
static struct of_device_id slave_of_match[];
static int slave_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct device_node *i2c_node;
struct slave_module *slave_mod;
struct tilcdc_module *mod;
struct pinctrl *pinctrl;
uint32_t i2c_phandle;
int ret = -EINVAL;
/* bail out early if no DT data: */
if (!node) {
dev_err(&pdev->dev, "device-tree data is missing\n");
return -ENXIO;
}
slave_mod = kzalloc(sizeof(*slave_mod), GFP_KERNEL);
if (!slave_mod)
return -ENOMEM;
mod = &slave_mod->base;
tilcdc_module_init(mod, "slave", &slave_module_ops);
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl))
dev_warn(&pdev->dev, "pins are not configured\n");
if (of_property_read_u32(node, "i2c", &i2c_phandle)) {
dev_err(&pdev->dev, "could not get i2c bus phandle\n");
goto fail;
}
i2c_node = of_find_node_by_phandle(i2c_phandle);
if (!i2c_node) {
dev_err(&pdev->dev, "could not get i2c bus node\n");
goto fail;
}
slave_mod->i2c = of_find_i2c_adapter_by_node(i2c_node);
if (!slave_mod->i2c) {
dev_err(&pdev->dev, "could not get i2c\n");
goto fail;
}
of_node_put(i2c_node);
return 0;
fail:
slave_destroy(mod);
return ret;
}
static int slave_remove(struct platform_device *pdev)
{
return 0;
}
static struct of_device_id slave_of_match[] = {
{ .compatible = "ti,tilcdc,slave", },
{ },
};
MODULE_DEVICE_TABLE(of, slave_of_match);
struct platform_driver slave_driver = {
.probe = slave_probe,
.remove = slave_remove,
.driver = {
.owner = THIS_MODULE,
.name = "slave",
.of_match_table = slave_of_match,
},
};
int __init tilcdc_slave_init(void)
{
return platform_driver_register(&slave_driver);
}
void __exit tilcdc_slave_fini(void)
{
platform_driver_unregister(&slave_driver);
}
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TILCDC_SLAVE_H__
#define __TILCDC_SLAVE_H__
/* sub-module for i2c slave encoder output */
int tilcdc_slave_init(void);
void tilcdc_slave_fini(void);
#endif /* __TILCDC_SLAVE_H__ */
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/i2c.h>
#include <linux/of_i2c.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/pinctrl/consumer.h>
#include "tilcdc_drv.h"
struct tfp410_module {
struct tilcdc_module base;
struct i2c_adapter *i2c;
int gpio;
};
#define to_tfp410_module(x) container_of(x, struct tfp410_module, base)
static const struct tilcdc_panel_info dvi_info = {
.ac_bias = 255,
.ac_bias_intrpt = 0,
.dma_burst_sz = 16,
.bpp = 16,
.fdd = 0x80,
.tft_alt_mode = 0,
.sync_edge = 0,
.sync_ctrl = 1,
.raster_order = 0,
};
/*
* Encoder:
*/
struct tfp410_encoder {
struct drm_encoder base;
struct tfp410_module *mod;
int dpms;
};
#define to_tfp410_encoder(x) container_of(x, struct tfp410_encoder, base)
static void tfp410_encoder_destroy(struct drm_encoder *encoder)
{
struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder);
drm_encoder_cleanup(encoder);
kfree(tfp410_encoder);
}
static void tfp410_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder);
if (tfp410_encoder->dpms == mode)
return;
if (mode == DRM_MODE_DPMS_ON) {
DBG("Power on");
gpio_direction_output(tfp410_encoder->mod->gpio, 1);
} else {
DBG("Power off");
gpio_direction_output(tfp410_encoder->mod->gpio, 0);
}
tfp410_encoder->dpms = mode;
}
static bool tfp410_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
/* nothing needed */
return true;
}
static void tfp410_encoder_prepare(struct drm_encoder *encoder)
{
tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
tilcdc_crtc_set_panel_info(encoder->crtc, &dvi_info);
}
static void tfp410_encoder_commit(struct drm_encoder *encoder)
{
tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
}
static void tfp410_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
/* nothing needed */
}
static const struct drm_encoder_funcs tfp410_encoder_funcs = {
.destroy = tfp410_encoder_destroy,
};
static const struct drm_encoder_helper_funcs tfp410_encoder_helper_funcs = {
.dpms = tfp410_encoder_dpms,
.mode_fixup = tfp410_encoder_mode_fixup,
.prepare = tfp410_encoder_prepare,
.commit = tfp410_encoder_commit,
.mode_set = tfp410_encoder_mode_set,
};
static struct drm_encoder *tfp410_encoder_create(struct drm_device *dev,
struct tfp410_module *mod)
{
struct tfp410_encoder *tfp410_encoder;
struct drm_encoder *encoder;
int ret;
tfp410_encoder = kzalloc(sizeof(*tfp410_encoder), GFP_KERNEL);
if (!tfp410_encoder) {
dev_err(dev->dev, "allocation failed\n");
return NULL;
}
tfp410_encoder->dpms = DRM_MODE_DPMS_OFF;
tfp410_encoder->mod = mod;
encoder = &tfp410_encoder->base;
encoder->possible_crtcs = 1;
ret = drm_encoder_init(dev, encoder, &tfp410_encoder_funcs,
DRM_MODE_ENCODER_TMDS);
if (ret < 0)
goto fail;
drm_encoder_helper_add(encoder, &tfp410_encoder_helper_funcs);
return encoder;
fail:
tfp410_encoder_destroy(encoder);
return NULL;
}
/*
* Connector:
*/
struct tfp410_connector {
struct drm_connector base;
struct drm_encoder *encoder; /* our connected encoder */
struct tfp410_module *mod;
};
#define to_tfp410_connector(x) container_of(x, struct tfp410_connector, base)
static void tfp410_connector_destroy(struct drm_connector *connector)
{
struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
drm_connector_cleanup(connector);
kfree(tfp410_connector);
}
static enum drm_connector_status tfp410_connector_detect(
struct drm_connector *connector,
bool force)
{
struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
if (drm_probe_ddc(tfp410_connector->mod->i2c))
return connector_status_connected;
return connector_status_unknown;
}
static int tfp410_connector_get_modes(struct drm_connector *connector)
{
struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
struct edid *edid;
int ret = 0;
edid = drm_get_edid(connector, tfp410_connector->mod->i2c);
drm_mode_connector_update_edid_property(connector, edid);
if (edid) {
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
}
return ret;
}
static int tfp410_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct tilcdc_drm_private *priv = connector->dev->dev_private;
/* our only constraints are what the crtc can generate: */
return tilcdc_crtc_mode_valid(priv->crtc, mode);
}
static struct drm_encoder *tfp410_connector_best_encoder(
struct drm_connector *connector)
{
struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
return tfp410_connector->encoder;
}
static const struct drm_connector_funcs tfp410_connector_funcs = {
.destroy = tfp410_connector_destroy,
.dpms = drm_helper_connector_dpms,
.detect = tfp410_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
};
static const struct drm_connector_helper_funcs tfp410_connector_helper_funcs = {
.get_modes = tfp410_connector_get_modes,
.mode_valid = tfp410_connector_mode_valid,
.best_encoder = tfp410_connector_best_encoder,
};
static struct drm_connector *tfp410_connector_create(struct drm_device *dev,
struct tfp410_module *mod, struct drm_encoder *encoder)
{
struct tfp410_connector *tfp410_connector;
struct drm_connector *connector;
int ret;
tfp410_connector = kzalloc(sizeof(*tfp410_connector), GFP_KERNEL);
if (!tfp410_connector) {
dev_err(dev->dev, "allocation failed\n");
return NULL;
}
tfp410_connector->encoder = encoder;
tfp410_connector->mod = mod;
connector = &tfp410_connector->base;
drm_connector_init(dev, connector, &tfp410_connector_funcs,
DRM_MODE_CONNECTOR_DVID);
drm_connector_helper_add(connector, &tfp410_connector_helper_funcs);
connector->polled = DRM_CONNECTOR_POLL_CONNECT |
DRM_CONNECTOR_POLL_DISCONNECT;
connector->interlace_allowed = 0;
connector->doublescan_allowed = 0;
ret = drm_mode_connector_attach_encoder(connector, encoder);
if (ret)
goto fail;
drm_sysfs_connector_add(connector);
return connector;
fail:
tfp410_connector_destroy(connector);
return NULL;
}
/*
* Module:
*/
static int tfp410_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
{
struct tfp410_module *tfp410_mod = to_tfp410_module(mod);
struct tilcdc_drm_private *priv = dev->dev_private;
struct drm_encoder *encoder;
struct drm_connector *connector;
encoder = tfp410_encoder_create(dev, tfp410_mod);
if (!encoder)
return -ENOMEM;
connector = tfp410_connector_create(dev, tfp410_mod, encoder);
if (!connector)
return -ENOMEM;
priv->encoders[priv->num_encoders++] = encoder;
priv->connectors[priv->num_connectors++] = connector;
return 0;
}
static void tfp410_destroy(struct tilcdc_module *mod)
{
struct tfp410_module *tfp410_mod = to_tfp410_module(mod);
if (tfp410_mod->i2c)
i2c_put_adapter(tfp410_mod->i2c);
if (!IS_ERR_VALUE(tfp410_mod->gpio))
gpio_free(tfp410_mod->gpio);
tilcdc_module_cleanup(mod);
kfree(tfp410_mod);
}
static const struct tilcdc_module_ops tfp410_module_ops = {
.modeset_init = tfp410_modeset_init,
.destroy = tfp410_destroy,
};
/*
* Device:
*/
static struct of_device_id tfp410_of_match[];
static int tfp410_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct device_node *i2c_node;
struct tfp410_module *tfp410_mod;
struct tilcdc_module *mod;
struct pinctrl *pinctrl;
uint32_t i2c_phandle;
int ret = -EINVAL;
/* bail out early if no DT data: */
if (!node) {
dev_err(&pdev->dev, "device-tree data is missing\n");
return -ENXIO;
}
tfp410_mod = kzalloc(sizeof(*tfp410_mod), GFP_KERNEL);
if (!tfp410_mod)
return -ENOMEM;
mod = &tfp410_mod->base;
tilcdc_module_init(mod, "tfp410", &tfp410_module_ops);
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl))
dev_warn(&pdev->dev, "pins are not configured\n");
if (of_property_read_u32(node, "i2c", &i2c_phandle)) {
dev_err(&pdev->dev, "could not get i2c bus phandle\n");
goto fail;
}
i2c_node = of_find_node_by_phandle(i2c_phandle);
if (!i2c_node) {
dev_err(&pdev->dev, "could not get i2c bus node\n");
goto fail;
}
tfp410_mod->i2c = of_find_i2c_adapter_by_node(i2c_node);
if (!tfp410_mod->i2c) {
dev_err(&pdev->dev, "could not get i2c\n");
goto fail;
}
of_node_put(i2c_node);
tfp410_mod->gpio = of_get_named_gpio_flags(node, "powerdn-gpio",
0, NULL);
if (IS_ERR_VALUE(tfp410_mod->gpio)) {
dev_warn(&pdev->dev, "No power down GPIO\n");
} else {
ret = gpio_request(tfp410_mod->gpio, "DVI_PDn");
if (ret) {
dev_err(&pdev->dev, "could not get DVI_PDn gpio\n");
goto fail;
}
}
return 0;
fail:
tfp410_destroy(mod);
return ret;
}
static int tfp410_remove(struct platform_device *pdev)
{
return 0;
}
static struct of_device_id tfp410_of_match[] = {
{ .compatible = "ti,tilcdc,tfp410", },
{ },
};
MODULE_DEVICE_TABLE(of, tfp410_of_match);
struct platform_driver tfp410_driver = {
.probe = tfp410_probe,
.remove = tfp410_remove,
.driver = {
.owner = THIS_MODULE,
.name = "tfp410",
.of_match_table = tfp410_of_match,
},
};
int __init tilcdc_tfp410_init(void)
{
return platform_driver_register(&tfp410_driver);
}
void __exit tilcdc_tfp410_fini(void)
{
platform_driver_unregister(&tfp410_driver);
}
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TILCDC_TFP410_H__
#define __TILCDC_TFP410_H__
/* sub-module for tfp410 dvi adaptor */
int tilcdc_tfp410_init(void);
void tilcdc_tfp410_fini(void);
#endif /* __TILCDC_TFP410_H__ */
...@@ -159,4 +159,24 @@ static inline void drm_i2c_encoder_unregister(struct drm_i2c_encoder_driver *dri ...@@ -159,4 +159,24 @@ static inline void drm_i2c_encoder_unregister(struct drm_i2c_encoder_driver *dri
void drm_i2c_encoder_destroy(struct drm_encoder *encoder); void drm_i2c_encoder_destroy(struct drm_encoder *encoder);
/*
* Wrapper fxns which can be plugged in to drm_encoder_helper_funcs:
*/
void drm_i2c_encoder_dpms(struct drm_encoder *encoder, int mode);
bool drm_i2c_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
void drm_i2c_encoder_prepare(struct drm_encoder *encoder);
void drm_i2c_encoder_commit(struct drm_encoder *encoder);
void drm_i2c_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
enum drm_connector_status drm_i2c_encoder_detect(struct drm_encoder *encoder,
struct drm_connector *connector);
void drm_i2c_encoder_save(struct drm_encoder *encoder);
void drm_i2c_encoder_restore(struct drm_encoder *encoder);
#endif #endif
...@@ -23,5 +23,10 @@ struct drm_framebuffer *drm_fb_cma_create(struct drm_device *dev, ...@@ -23,5 +23,10 @@ struct drm_framebuffer *drm_fb_cma_create(struct drm_device *dev,
struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb, struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb,
unsigned int plane); unsigned int plane);
#ifdef CONFIG_DEBUG_FS
void drm_fb_cma_describe(struct drm_framebuffer *fb, struct seq_file *m);
int drm_fb_cma_debugfs_show(struct seq_file *m, void *arg);
#endif
#endif #endif
...@@ -41,4 +41,8 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm, ...@@ -41,4 +41,8 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
extern const struct vm_operations_struct drm_gem_cma_vm_ops; extern const struct vm_operations_struct drm_gem_cma_vm_ops;
#ifdef CONFIG_DEBUG_FS
void drm_gem_cma_describe(struct drm_gem_cma_object *obj, struct seq_file *m);
#endif
#endif /* __DRM_GEM_CMA_HELPER_H__ */ #endif /* __DRM_GEM_CMA_HELPER_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment