Commit c8afe684 authored by Rob Clark's avatar Rob Clark

drm/msm: basic KMS driver for snapdragon

The snapdragon chips have multiple different display controllers,
depending on which chip variant/version.  (As far as I can tell, current
devices have either MDP3 or MDP4, and upcoming devices have MDSS.)  And
then external to the display controller are HDMI, DSI, etc. blocks which
may be shared across devices which have different display controller
blocks.

To more easily add support for different display controller blocks, the
display controller specific bits are split out into a "kms" module,
which provides the kms plane/crtc/encoder objects.

The external HDMI, DSI, etc. blocks are part encoder, and part connector
currently.  But I think I will pull in the drm_bridge patches from
chromeos tree, and split them into a bridge+connector, with the
registers that need to be set in modeset handled by the bridge.  This
would remove the 'msm_connector' base class.  But some things need to be
double checked to make sure I could get the correct ON/OFF sequencing..

This patch adds support for mdp4 crtc (including hw cursor), dtv encoder
(part of MDP4 block), and hdmi.
Signed-off-by: default avatarRob Clark <robdclark@gmail.com>
parent 0cf6c71d
......@@ -223,3 +223,5 @@ source "drivers/gpu/drm/omapdrm/Kconfig"
source "drivers/gpu/drm/tilcdc/Kconfig"
source "drivers/gpu/drm/qxl/Kconfig"
source "drivers/gpu/drm/msm/Kconfig"
......@@ -54,4 +54,5 @@ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
obj-$(CONFIG_DRM_OMAP) += omapdrm/
obj-$(CONFIG_DRM_TILCDC) += tilcdc/
obj-$(CONFIG_DRM_QXL) += qxl/
obj-$(CONFIG_DRM_MSM) += msm/
obj-y += i2c/
config DRM_MSM
tristate "MSM DRM"
depends on DRM
depends on ARCH_MSM
depends on ARCH_MSM8960
select DRM_KMS_HELPER
select SHMEM
select TMPFS
default y
help
DRM/KMS driver for MSM/snapdragon.
config DRM_MSM_FBDEV
bool "Enable legacy fbdev support for MSM modesetting driver"
depends on DRM_MSM
select FB_SYS_FILLRECT
select FB_SYS_COPYAREA
select FB_SYS_IMAGEBLIT
select FB_SYS_FOPS
default y
help
Choose this option if you have a need for the legacy fbdev
support. Note that this support also provide the linux console
support on top of the MSM modesetting driver.
config DRM_MSM_REGISTER_LOGGING
bool "MSM DRM register logging"
depends on DRM_MSM
default n
help
Compile in support for logging register reads/writes in a format
that can be parsed by envytools demsm tool. If enabled, register
logging can be switched on via msm.reglog=y module param.
ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/msm
ifeq (, $(findstring -W,$(EXTRA_CFLAGS)))
ccflags-y += -Werror
endif
msm-y := \
hdmi/hdmi.o \
hdmi/hdmi_connector.o \
hdmi/hdmi_i2c.o \
hdmi/hdmi_phy_8960.o \
hdmi/hdmi_phy_8x60.o \
mdp4/mdp4_crtc.o \
mdp4/mdp4_dtv_encoder.o \
mdp4/mdp4_format.o \
mdp4/mdp4_irq.o \
mdp4/mdp4_kms.o \
mdp4/mdp4_plane.o \
msm_connector.o \
msm_drv.o \
msm_fb.o \
msm_gem.o
msm-$(CONFIG_DRM_MSM_FBDEV) += msm_fbdev.o
obj-$(CONFIG_DRM_MSM) += msm.o
NOTES about msm drm/kms driver:
In the current snapdragon SoC's, we have (at least) 3 different
display controller blocks at play:
+ MDP3 - ?? seems to be what is on geeksphone peak device
+ MDP4 - S3 (APQ8060, touchpad), S4-pro (APQ8064, nexus4 & ifc6410)
+ MDSS - snapdragon 800
(I don't have a completely clear picture on which display controller
maps to which part #)
Plus a handful of blocks around them for HDMI/DSI/etc output.
And on gpu side of things:
+ zero, one, or two 2d cores (z180)
+ and either a2xx or a3xx 3d core.
But, HDMI/DSI/etc blocks seem like they can be shared across multiple
display controller blocks. And I for sure don't want to have to deal
with N different kms devices from xf86-video-freedreno. Plus, it
seems like we can do some clever tricks like use GPU to trigger
pageflip after rendering completes (ie. have the kms/crtc code build
up gpu cmdstream to update scanout and write FLUSH register after).
So, the approach is one drm driver, with some modularity. Different
'struct msm_kms' implementations, depending on display controller.
And one or more 'struct msm_gpu' for the various different gpu sub-
modules.
(Second part is not implemented yet. So far this is just basic KMS
driver, and not exposing any custom ioctls to userspace for now.)
The kms module provides the plane, crtc, and encoder objects, and
loads whatever connectors are appropriate.
For MDP4, the mapping is:
plane -> PIPE{RGBn,VGn} \
crtc -> OVLP{n} + DMA{P,S,E} (??) |-> MDP "device"
encoder -> DTV/LCDC/DSI (within MDP4) /
connector -> HDMI/DSI/etc --> other device(s)
Since the irq's that drm core mostly cares about are vblank/framedone,
we'll let msm_mdp4_kms provide the irq install/uninstall/etc functions
and treat the MDP4 block's irq as "the" irq. Even though the connectors
may have their own irqs which they install themselves. For this reason
the display controller is the "master" device.
Each connector probably ends up being a separate device, just for the
logistics of finding/mapping io region, irq, etc. Idealy we would
have a better way than just stashing the platform device in a global
(ie. like DT super-node.. but I don't have any snapdragon hw yet that
is using DT).
Note that so far I've not been able to get any docs on the hw, and it
seems that access to such docs would prevent me from working on the
freedreno gallium driver. So there may be some mistakes in register
names (I had to invent a few, since no sufficient hint was given in
the downstream android fbdev driver), bitfield sizes, etc. My current
state of understanding the registers is given in the envytools rnndb
files at:
https://github.com/freedreno/envytools/tree/master/rnndb
(the mdp4/hdmi/dsi directories)
These files are used both for a parser tool (in the same tree) to
parse logged register reads/writes (both from downstream android fbdev
driver, and this driver with register logging enabled), as well as to
generate the register level headers.
/*
* Copyright (C) 2013 Red Hat
* 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 "hdmi.h"
static struct platform_device *hdmi_pdev;
void hdmi_set_mode(struct hdmi *hdmi, bool power_on)
{
uint32_t ctrl = 0;
if (power_on) {
ctrl |= HDMI_CTRL_ENABLE;
if (!hdmi->hdmi_mode) {
ctrl |= HDMI_CTRL_HDMI;
hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
ctrl &= ~HDMI_CTRL_HDMI;
} else {
ctrl |= HDMI_CTRL_HDMI;
}
} else {
ctrl = HDMI_CTRL_HDMI;
}
hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
DBG("HDMI Core: %s, HDMI_CTRL=0x%08x",
power_on ? "Enable" : "Disable", ctrl);
}
static irqreturn_t hdmi_irq(int irq, void *dev_id)
{
struct hdmi *hdmi = dev_id;
/* Process HPD: */
hdmi_connector_irq(hdmi->connector);
/* Process DDC: */
hdmi_i2c_irq(hdmi->i2c);
/* TODO audio.. */
return IRQ_HANDLED;
}
void hdmi_destroy(struct hdmi *hdmi)
{
struct hdmi_phy *phy = hdmi->phy;
if (phy)
phy->funcs->destroy(phy);
if (hdmi->i2c)
hdmi_i2c_destroy(hdmi->i2c);
put_device(&hdmi->pdev->dev);
}
/* initialize connector */
int hdmi_init(struct hdmi *hdmi, struct drm_device *dev,
struct drm_connector *connector)
{
struct platform_device *pdev = hdmi_pdev;
struct hdmi_platform_config *config;
int ret;
if (!pdev) {
dev_err(dev->dev, "no hdmi device\n");
ret = -ENXIO;
goto fail;
}
config = pdev->dev.platform_data;
get_device(&pdev->dev);
hdmi->dev = dev;
hdmi->pdev = pdev;
hdmi->connector = connector;
/* not sure about which phy maps to which msm.. probably I miss some */
if (config->phy_init)
hdmi->phy = config->phy_init(hdmi);
else
hdmi->phy = ERR_PTR(-ENXIO);
if (IS_ERR(hdmi->phy)) {
ret = PTR_ERR(hdmi->phy);
dev_err(dev->dev, "failed to load phy: %d\n", ret);
hdmi->phy = NULL;
goto fail;
}
hdmi->mmio = msm_ioremap(pdev, "hdmi_msm_hdmi_addr", "HDMI");
if (IS_ERR(hdmi->mmio)) {
ret = PTR_ERR(hdmi->mmio);
goto fail;
}
hdmi->mvs = devm_regulator_get(&pdev->dev, "8901_hdmi_mvs");
if (IS_ERR(hdmi->mvs))
hdmi->mvs = devm_regulator_get(&pdev->dev, "hdmi_mvs");
if (IS_ERR(hdmi->mvs)) {
ret = PTR_ERR(hdmi->mvs);
dev_err(dev->dev, "failed to get mvs regulator: %d\n", ret);
goto fail;
}
hdmi->mpp0 = devm_regulator_get(&pdev->dev, "8901_mpp0");
if (IS_ERR(hdmi->mpp0))
hdmi->mpp0 = NULL;
hdmi->clk = devm_clk_get(&pdev->dev, "core_clk");
if (IS_ERR(hdmi->clk)) {
ret = PTR_ERR(hdmi->clk);
dev_err(dev->dev, "failed to get 'clk': %d\n", ret);
goto fail;
}
hdmi->m_pclk = devm_clk_get(&pdev->dev, "master_iface_clk");
if (IS_ERR(hdmi->m_pclk)) {
ret = PTR_ERR(hdmi->m_pclk);
dev_err(dev->dev, "failed to get 'm_pclk': %d\n", ret);
goto fail;
}
hdmi->s_pclk = devm_clk_get(&pdev->dev, "slave_iface_clk");
if (IS_ERR(hdmi->s_pclk)) {
ret = PTR_ERR(hdmi->s_pclk);
dev_err(dev->dev, "failed to get 's_pclk': %d\n", ret);
goto fail;
}
hdmi->i2c = hdmi_i2c_init(hdmi);
if (IS_ERR(hdmi->i2c)) {
ret = PTR_ERR(hdmi->i2c);
dev_err(dev->dev, "failed to get i2c: %d\n", ret);
hdmi->i2c = NULL;
goto fail;
}
hdmi->irq = platform_get_irq(pdev, 0);
if (hdmi->irq < 0) {
ret = hdmi->irq;
dev_err(dev->dev, "failed to get irq: %d\n", ret);
goto fail;
}
ret = devm_request_threaded_irq(&pdev->dev, hdmi->irq,
NULL, hdmi_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"hdmi_isr", hdmi);
if (ret < 0) {
dev_err(dev->dev, "failed to request IRQ%u: %d\n",
hdmi->irq, ret);
goto fail;
}
return 0;
fail:
if (hdmi)
hdmi_destroy(hdmi);
return ret;
}
/*
* The hdmi device:
*/
static int hdmi_dev_probe(struct platform_device *pdev)
{
static struct hdmi_platform_config config = {};
#ifdef CONFIG_OF
/* TODO */
#else
if (cpu_is_apq8064()) {
config.phy_init = hdmi_phy_8960_init;
config.ddc_clk_gpio = 70;
config.ddc_data_gpio = 71;
config.hpd_gpio = 72;
config.pmic_gpio = 13 + NR_GPIO_IRQS;
} else if (cpu_is_msm8960()) {
config.phy_init = hdmi_phy_8960_init;
config.ddc_clk_gpio = 100;
config.ddc_data_gpio = 101;
config.hpd_gpio = 102;
config.pmic_gpio = -1;
} else if (cpu_is_msm8x60()) {
config.phy_init = hdmi_phy_8x60_init;
config.ddc_clk_gpio = 170;
config.ddc_data_gpio = 171;
config.hpd_gpio = 172;
config.pmic_gpio = -1;
}
#endif
pdev->dev.platform_data = &config;
hdmi_pdev = pdev;
return 0;
}
static int hdmi_dev_remove(struct platform_device *pdev)
{
hdmi_pdev = NULL;
return 0;
}
static struct platform_driver hdmi_driver = {
.probe = hdmi_dev_probe,
.remove = hdmi_dev_remove,
.driver.name = "hdmi_msm",
};
void __init hdmi_register(void)
{
platform_driver_register(&hdmi_driver);
}
void __exit hdmi_unregister(void)
{
platform_driver_unregister(&hdmi_driver);
}
/*
* Copyright (C) 2013 Red Hat
* 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 __HDMI_CONNECTOR_H__
#define __HDMI_CONNECTOR_H__
#include <linux/i2c.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include "msm_drv.h"
#include "hdmi.xml.h"
struct hdmi_phy;
struct hdmi {
struct drm_device *dev;
struct platform_device *pdev;
void __iomem *mmio;
struct regulator *mvs; /* HDMI_5V */
struct regulator *mpp0; /* External 5V */
struct clk *clk;
struct clk *m_pclk;
struct clk *s_pclk;
struct hdmi_phy *phy;
struct i2c_adapter *i2c;
struct drm_connector *connector;
bool hdmi_mode; /* are we in hdmi mode? */
int irq;
};
/* platform config data (ie. from DT, or pdata) */
struct hdmi_platform_config {
struct hdmi_phy *(*phy_init)(struct hdmi *hdmi);
int ddc_clk_gpio, ddc_data_gpio, hpd_gpio, pmic_gpio;
};
void hdmi_set_mode(struct hdmi *hdmi, bool power_on);
void hdmi_destroy(struct hdmi *hdmi);
int hdmi_init(struct hdmi *hdmi, struct drm_device *dev,
struct drm_connector *connector);
static inline void hdmi_write(struct hdmi *hdmi, u32 reg, u32 data)
{
msm_writel(data, hdmi->mmio + reg);
}
static inline u32 hdmi_read(struct hdmi *hdmi, u32 reg)
{
return msm_readl(hdmi->mmio + reg);
}
/*
* The phy appears to be different, for example between 8960 and 8x60,
* so split the phy related functions out and load the correct one at
* runtime:
*/
struct hdmi_phy_funcs {
void (*destroy)(struct hdmi_phy *phy);
void (*reset)(struct hdmi_phy *phy);
void (*powerup)(struct hdmi_phy *phy, unsigned long int pixclock);
void (*powerdown)(struct hdmi_phy *phy);
};
struct hdmi_phy {
const struct hdmi_phy_funcs *funcs;
};
/*
* phy can be different on different generations:
*/
struct hdmi_phy *hdmi_phy_8960_init(struct hdmi *hdmi);
struct hdmi_phy *hdmi_phy_8x60_init(struct hdmi *hdmi);
/*
* hdmi connector:
*/
void hdmi_connector_irq(struct drm_connector *connector);
/*
* i2c adapter for ddc:
*/
void hdmi_i2c_irq(struct i2c_adapter *i2c);
void hdmi_i2c_destroy(struct i2c_adapter *i2c);
struct i2c_adapter *hdmi_i2c_init(struct hdmi *hdmi);
#endif /* __HDMI_CONNECTOR_H__ */
This diff is collapsed.
/*
* Copyright (C) 2013 Red Hat
* 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 "hdmi.h"
struct hdmi_i2c_adapter {
struct i2c_adapter base;
struct hdmi *hdmi;
bool sw_done;
wait_queue_head_t ddc_event;
};
#define to_hdmi_i2c_adapter(x) container_of(x, struct hdmi_i2c_adapter, base)
static void init_ddc(struct hdmi_i2c_adapter *hdmi_i2c)
{
struct hdmi *hdmi = hdmi_i2c->hdmi;
hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
HDMI_DDC_CTRL_SW_STATUS_RESET);
hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
HDMI_DDC_CTRL_SOFT_RESET);
hdmi_write(hdmi, REG_HDMI_DDC_SPEED,
HDMI_DDC_SPEED_THRESHOLD(2) |
HDMI_DDC_SPEED_PRESCALE(10));
hdmi_write(hdmi, REG_HDMI_DDC_SETUP,
HDMI_DDC_SETUP_TIMEOUT(0xff));
/* enable reference timer for 27us */
hdmi_write(hdmi, REG_HDMI_DDC_REF,
HDMI_DDC_REF_REFTIMER_ENABLE |
HDMI_DDC_REF_REFTIMER(27));
}
static int ddc_clear_irq(struct hdmi_i2c_adapter *hdmi_i2c)
{
struct hdmi *hdmi = hdmi_i2c->hdmi;
struct drm_device *dev = hdmi->dev;
uint32_t retry = 0xffff;
uint32_t ddc_int_ctrl;
do {
--retry;
hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL,
HDMI_DDC_INT_CTRL_SW_DONE_ACK |
HDMI_DDC_INT_CTRL_SW_DONE_MASK);
ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL);
} while ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT) && retry);
if (!retry) {
dev_err(dev->dev, "timeout waiting for DDC\n");
return -ETIMEDOUT;
}
hdmi_i2c->sw_done = false;
return 0;
}
#define MAX_TRANSACTIONS 4
static bool sw_done(struct hdmi_i2c_adapter *hdmi_i2c)
{
struct hdmi *hdmi = hdmi_i2c->hdmi;
if (!hdmi_i2c->sw_done) {
uint32_t ddc_int_ctrl;
ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL);
if ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_MASK) &&
(ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT)) {
hdmi_i2c->sw_done = true;
hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL,
HDMI_DDC_INT_CTRL_SW_DONE_ACK);
}
}
return hdmi_i2c->sw_done;
}
static int hdmi_i2c_xfer(struct i2c_adapter *i2c,
struct i2c_msg *msgs, int num)
{
struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
struct hdmi *hdmi = hdmi_i2c->hdmi;
struct drm_device *dev = hdmi->dev;
static const uint32_t nack[] = {
HDMI_DDC_SW_STATUS_NACK0, HDMI_DDC_SW_STATUS_NACK1,
HDMI_DDC_SW_STATUS_NACK2, HDMI_DDC_SW_STATUS_NACK3,
};
int indices[MAX_TRANSACTIONS];
int ret, i, j, index = 0;
uint32_t ddc_status, ddc_data, i2c_trans;
num = min(num, MAX_TRANSACTIONS);
WARN_ON(!(hdmi_read(hdmi, REG_HDMI_CTRL) & HDMI_CTRL_ENABLE));
if (num == 0)
return num;
init_ddc(hdmi_i2c);
ret = ddc_clear_irq(hdmi_i2c);
if (ret)
return ret;
for (i = 0; i < num; i++) {
struct i2c_msg *p = &msgs[i];
uint32_t raw_addr = p->addr << 1;
if (p->flags & I2C_M_RD)
raw_addr |= 1;
ddc_data = HDMI_DDC_DATA_DATA(raw_addr) |
HDMI_DDC_DATA_DATA_RW(DDC_WRITE);
if (i == 0) {
ddc_data |= HDMI_DDC_DATA_INDEX(0) |
HDMI_DDC_DATA_INDEX_WRITE;
}
hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
index++;
indices[i] = index;
if (p->flags & I2C_M_RD) {
index += p->len;
} else {
for (j = 0; j < p->len; j++) {
ddc_data = HDMI_DDC_DATA_DATA(p->buf[j]) |
HDMI_DDC_DATA_DATA_RW(DDC_WRITE);
hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
index++;
}
}
i2c_trans = HDMI_I2C_TRANSACTION_REG_CNT(p->len) |
HDMI_I2C_TRANSACTION_REG_RW(
(p->flags & I2C_M_RD) ? DDC_READ : DDC_WRITE) |
HDMI_I2C_TRANSACTION_REG_START;
if (i == (num - 1))
i2c_trans |= HDMI_I2C_TRANSACTION_REG_STOP;
hdmi_write(hdmi, REG_HDMI_I2C_TRANSACTION(i), i2c_trans);
}
/* trigger the transfer: */
hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
HDMI_DDC_CTRL_TRANSACTION_CNT(num - 1) |
HDMI_DDC_CTRL_GO);
ret = wait_event_timeout(hdmi_i2c->ddc_event, sw_done(hdmi_i2c), HZ/4);
if (ret <= 0) {
if (ret == 0)
ret = -ETIMEDOUT;
dev_warn(dev->dev, "DDC timeout: %d\n", ret);
DBG("sw_status=%08x, hw_status=%08x, int_ctrl=%08x",
hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS),
hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS),
hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL));
return ret;
}
ddc_status = hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS);
/* read back results of any read transactions: */
for (i = 0; i < num; i++) {
struct i2c_msg *p = &msgs[i];
if (!(p->flags & I2C_M_RD))
continue;
/* check for NACK: */
if (ddc_status & nack[i]) {
DBG("ddc_status=%08x", ddc_status);
break;
}
ddc_data = HDMI_DDC_DATA_DATA_RW(DDC_READ) |
HDMI_DDC_DATA_INDEX(indices[i]) |
HDMI_DDC_DATA_INDEX_WRITE;
hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
/* discard first byte: */
hdmi_read(hdmi, REG_HDMI_DDC_DATA);
for (j = 0; j < p->len; j++) {
ddc_data = hdmi_read(hdmi, REG_HDMI_DDC_DATA);
p->buf[j] = FIELD(ddc_data, HDMI_DDC_DATA_DATA);
}
}
return i;
}
static u32 hdmi_i2c_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm hdmi_i2c_algorithm = {
.master_xfer = hdmi_i2c_xfer,
.functionality = hdmi_i2c_func,
};
void hdmi_i2c_irq(struct i2c_adapter *i2c)
{
struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
if (sw_done(hdmi_i2c))
wake_up_all(&hdmi_i2c->ddc_event);
}
void hdmi_i2c_destroy(struct i2c_adapter *i2c)
{
struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
i2c_del_adapter(i2c);
kfree(hdmi_i2c);
}
struct i2c_adapter *hdmi_i2c_init(struct hdmi *hdmi)
{
struct drm_device *dev = hdmi->dev;
struct hdmi_i2c_adapter *hdmi_i2c;
struct i2c_adapter *i2c = NULL;
int ret;
hdmi_i2c = kzalloc(sizeof(*hdmi_i2c), GFP_KERNEL);
if (!hdmi_i2c) {
ret = -ENOMEM;
goto fail;
}
i2c = &hdmi_i2c->base;
hdmi_i2c->hdmi = hdmi;
init_waitqueue_head(&hdmi_i2c->ddc_event);
i2c->owner = THIS_MODULE;
i2c->class = I2C_CLASS_DDC;
snprintf(i2c->name, sizeof(i2c->name), "msm hdmi i2c");
i2c->dev.parent = &hdmi->pdev->dev;
i2c->algo = &hdmi_i2c_algorithm;
ret = i2c_add_adapter(i2c);
if (ret) {
dev_err(dev->dev, "failed to register hdmi i2c: %d\n", ret);
goto fail;
}
return i2c;
fail:
if (i2c)
hdmi_i2c_destroy(i2c);
return ERR_PTR(ret);
}
/*
* Copyright (C) 2013 Red Hat
* 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 "hdmi.h"
struct hdmi_phy_8960 {
struct hdmi_phy base;
struct hdmi *hdmi;
};
#define to_hdmi_phy_8960(x) container_of(x, struct hdmi_phy_8960, base)
static void hdmi_phy_8960_destroy(struct hdmi_phy *phy)
{
struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy);
kfree(phy_8960);
}
static void hdmi_phy_8960_reset(struct hdmi_phy *phy)
{
struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy);
struct hdmi *hdmi = phy_8960->hdmi;
unsigned int val;
val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL);
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
/* pull low */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET);
} else {
/* pull high */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET);
}
if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
/* pull low */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
} else {
/* pull high */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET_PLL);
}
msleep(100);
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
/* pull high */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET);
} else {
/* pull low */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET);
}
if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
/* pull high */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET_PLL);
} else {
/* pull low */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
}
}
static void hdmi_phy_8960_powerup(struct hdmi_phy *phy,
unsigned long int pixclock)
{
struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy);
struct hdmi *hdmi = phy_8960->hdmi;
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG0, 0x1b);
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG1, 0xf2);
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG4, 0x00);
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG5, 0x00);
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG6, 0x00);
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG7, 0x00);
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG8, 0x00);
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG9, 0x00);
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG10, 0x00);
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG11, 0x00);
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG3, 0x20);
}
static void hdmi_phy_8960_powerdown(struct hdmi_phy *phy)
{
struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy);
struct hdmi *hdmi = phy_8960->hdmi;
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG2, 0x7f);
}
static const struct hdmi_phy_funcs hdmi_phy_8960_funcs = {
.destroy = hdmi_phy_8960_destroy,
.reset = hdmi_phy_8960_reset,
.powerup = hdmi_phy_8960_powerup,
.powerdown = hdmi_phy_8960_powerdown,
};
struct hdmi_phy *hdmi_phy_8960_init(struct hdmi *hdmi)
{
struct hdmi_phy_8960 *phy_8960;
struct hdmi_phy *phy = NULL;
int ret;
phy_8960 = kzalloc(sizeof(*phy_8960), GFP_KERNEL);
if (!phy_8960) {
ret = -ENOMEM;
goto fail;
}
phy = &phy_8960->base;
phy->funcs = &hdmi_phy_8960_funcs;
phy_8960->hdmi = hdmi;
return phy;
fail:
if (phy)
hdmi_phy_8960_destroy(phy);
return ERR_PTR(ret);
}
/*
* Copyright (C) 2013 Red Hat
* 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 "hdmi.h"
struct hdmi_phy_8x60 {
struct hdmi_phy base;
struct hdmi *hdmi;
};
#define to_hdmi_phy_8x60(x) container_of(x, struct hdmi_phy_8x60, base)
static void hdmi_phy_8x60_destroy(struct hdmi_phy *phy)
{
struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy);
kfree(phy_8x60);
}
static void hdmi_phy_8x60_reset(struct hdmi_phy *phy)
{
struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy);
struct hdmi *hdmi = phy_8x60->hdmi;
unsigned int val;
val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL);
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
/* pull low */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET);
} else {
/* pull high */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET);
}
msleep(100);
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
/* pull high */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET);
} else {
/* pull low */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET);
}
}
static void hdmi_phy_8x60_powerup(struct hdmi_phy *phy,
unsigned long int pixclock)
{
struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy);
struct hdmi *hdmi = phy_8x60->hdmi;
/* De-serializer delay D/C for non-lbk mode: */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG0,
HDMI_8x60_PHY_REG0_DESER_DEL_CTRL(3));
if (pixclock == 27000000) {
/* video_format == HDMI_VFRMT_720x480p60_16_9 */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG1,
HDMI_8x60_PHY_REG1_DTEST_MUX_SEL(5) |
HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL(3));
} else {
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG1,
HDMI_8x60_PHY_REG1_DTEST_MUX_SEL(5) |
HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL(4));
}
/* No matter what, start from the power down mode: */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_PD_PWRGEN |
HDMI_8x60_PHY_REG2_PD_PLL |
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
HDMI_8x60_PHY_REG2_PD_DESER);
/* Turn PowerGen on: */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_PD_PLL |
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
HDMI_8x60_PHY_REG2_PD_DESER);
/* Turn PLL power on: */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
HDMI_8x60_PHY_REG2_PD_DESER);
/* Write to HIGH after PLL power down de-assert: */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG3,
HDMI_8x60_PHY_REG3_PLL_ENABLE);
/* ASIC power on; PHY REG9 = 0 */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG9, 0);
/* Enable PLL lock detect, PLL lock det will go high after lock
* Enable the re-time logic
*/
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG12,
HDMI_8x60_PHY_REG12_RETIMING_EN |
HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN);
/* Drivers are on: */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_PD_DESER);
/* If the RX detector is needed: */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_RCV_SENSE_EN |
HDMI_8x60_PHY_REG2_PD_DESER);
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG4, 0);
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG5, 0);
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG6, 0);
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG7, 0);
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG8, 0);
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG9, 0);
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG10, 0);
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG11, 0);
/* If we want to use lock enable based on counting: */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG12,
HDMI_8x60_PHY_REG12_RETIMING_EN |
HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN |
HDMI_8x60_PHY_REG12_FORCE_LOCK);
}
static void hdmi_phy_8x60_powerdown(struct hdmi_phy *phy)
{
struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy);
struct hdmi *hdmi = phy_8x60->hdmi;
/* Assert RESET PHY from controller */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
HDMI_PHY_CTRL_SW_RESET);
udelay(10);
/* De-assert RESET PHY from controller */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL, 0);
/* Turn off Driver */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
HDMI_8x60_PHY_REG2_PD_DESER);
udelay(10);
/* Disable PLL */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG3, 0);
/* Power down PHY, but keep RX-sense: */
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_RCV_SENSE_EN |
HDMI_8x60_PHY_REG2_PD_PWRGEN |
HDMI_8x60_PHY_REG2_PD_PLL |
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
HDMI_8x60_PHY_REG2_PD_DESER);
}
static const struct hdmi_phy_funcs hdmi_phy_8x60_funcs = {
.destroy = hdmi_phy_8x60_destroy,
.reset = hdmi_phy_8x60_reset,
.powerup = hdmi_phy_8x60_powerup,
.powerdown = hdmi_phy_8x60_powerdown,
};
struct hdmi_phy *hdmi_phy_8x60_init(struct hdmi *hdmi)
{
struct hdmi_phy_8x60 *phy_8x60;
struct hdmi_phy *phy = NULL;
int ret;
phy_8x60 = kzalloc(sizeof(*phy_8x60), GFP_KERNEL);
if (!phy_8x60) {
ret = -ENOMEM;
goto fail;
}
phy = &phy_8x60->base;
phy->funcs = &hdmi_phy_8x60_funcs;
phy_8x60->hdmi = hdmi;
return phy;
fail:
if (phy)
hdmi_phy_8x60_destroy(phy);
return ERR_PTR(ret);
}
This diff is collapsed.
This diff is collapsed.
/*
* Copyright (C) 2013 Red Hat
* 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 "msm_drv.h"
#include "mdp4_kms.h"
#define FMT(name, a, r, g, b, e0, e1, e2, e3, alpha, tight, c, cnt) { \
.base = { .pixel_format = DRM_FORMAT_ ## name }, \
.bpc_a = BPC ## a ## A, \
.bpc_r = BPC ## r, \
.bpc_g = BPC ## g, \
.bpc_b = BPC ## b, \
.unpack = { e0, e1, e2, e3 }, \
.alpha_enable = alpha, \
.unpack_tight = tight, \
.cpp = c, \
.unpack_count = cnt, \
}
#define BPC0A 0
static const struct mdp4_format formats[] = {
/* name a r g b e0 e1 e2 e3 alpha tight cpp cnt */
FMT(ARGB8888, 8, 8, 8, 8, 1, 0, 2, 3, true, true, 4, 4),
FMT(XRGB8888, 8, 8, 8, 8, 1, 0, 2, 3, false, true, 4, 4),
FMT(RGB888, 0, 8, 8, 8, 1, 0, 2, 0, false, true, 3, 3),
FMT(BGR888, 0, 8, 8, 8, 2, 0, 1, 0, false, true, 3, 3),
FMT(RGB565, 0, 5, 6, 5, 1, 0, 2, 0, false, true, 2, 3),
FMT(BGR565, 0, 5, 6, 5, 2, 0, 1, 0, false, true, 2, 3),
};
const struct msm_format *mdp4_get_format(struct msm_kms *kms, uint32_t format)
{
int i;
for (i = 0; i < ARRAY_SIZE(formats); i++) {
const struct mdp4_format *f = &formats[i];
if (f->base.pixel_format == format)
return &f->base;
}
return NULL;
}
/*
* Copyright (C) 2013 Red Hat
* 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 "msm_drv.h"
#include "mdp4_kms.h"
struct mdp4_irq_wait {
struct mdp4_irq irq;
int count;
};
static DECLARE_WAIT_QUEUE_HEAD(wait_event);
static DEFINE_SPINLOCK(list_lock);
static void update_irq(struct mdp4_kms *mdp4_kms)
{
struct mdp4_irq *irq;
uint32_t irqmask = mdp4_kms->vblank_mask;
BUG_ON(!spin_is_locked(&list_lock));
list_for_each_entry(irq, &mdp4_kms->irq_list, node)
irqmask |= irq->irqmask;
mdp4_write(mdp4_kms, REG_MDP4_INTR_ENABLE, irqmask);
}
static void update_irq_unlocked(struct mdp4_kms *mdp4_kms)
{
unsigned long flags;
spin_lock_irqsave(&list_lock, flags);
update_irq(mdp4_kms);
spin_unlock_irqrestore(&list_lock, flags);
}
static void mdp4_irq_error_handler(struct mdp4_irq *irq, uint32_t irqstatus)
{
DRM_ERROR("errors: %08x\n", irqstatus);
}
void mdp4_irq_preinstall(struct msm_kms *kms)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms);
mdp4_write(mdp4_kms, REG_MDP4_INTR_CLEAR, 0xffffffff);
}
int mdp4_irq_postinstall(struct msm_kms *kms)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms);
struct mdp4_irq *error_handler = &mdp4_kms->error_handler;
INIT_LIST_HEAD(&mdp4_kms->irq_list);
error_handler->irq = mdp4_irq_error_handler;
error_handler->irqmask = MDP4_IRQ_PRIMARY_INTF_UDERRUN |
MDP4_IRQ_EXTERNAL_INTF_UDERRUN;
mdp4_irq_register(mdp4_kms, error_handler);
return 0;
}
void mdp4_irq_uninstall(struct msm_kms *kms)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms);
mdp4_write(mdp4_kms, REG_MDP4_INTR_ENABLE, 0x00000000);
}
irqreturn_t mdp4_irq(struct msm_kms *kms)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms);
struct drm_device *dev = mdp4_kms->dev;
struct msm_drm_private *priv = dev->dev_private;
struct mdp4_irq *handler, *n;
unsigned long flags;
unsigned int id;
uint32_t status;
status = mdp4_read(mdp4_kms, REG_MDP4_INTR_STATUS);
mdp4_write(mdp4_kms, REG_MDP4_INTR_CLEAR, status);
VERB("status=%08x", status);
for (id = 0; id < priv->num_crtcs; id++)
if (status & mdp4_crtc_vblank(priv->crtcs[id]))
drm_handle_vblank(dev, id);
spin_lock_irqsave(&list_lock, flags);
mdp4_kms->in_irq = true;
list_for_each_entry_safe(handler, n, &mdp4_kms->irq_list, node) {
if (handler->irqmask & status) {
spin_unlock_irqrestore(&list_lock, flags);
handler->irq(handler, handler->irqmask & status);
spin_lock_irqsave(&list_lock, flags);
}
}
mdp4_kms->in_irq = false;
update_irq(mdp4_kms);
spin_unlock_irqrestore(&list_lock, flags);
return IRQ_HANDLED;
}
int mdp4_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms);
unsigned long flags;
spin_lock_irqsave(&list_lock, flags);
mdp4_kms->vblank_mask |= mdp4_crtc_vblank(crtc);
update_irq(mdp4_kms);
spin_unlock_irqrestore(&list_lock, flags);
return 0;
}
void mdp4_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms);
unsigned long flags;
spin_lock_irqsave(&list_lock, flags);
mdp4_kms->vblank_mask &= ~mdp4_crtc_vblank(crtc);
update_irq(mdp4_kms);
spin_unlock_irqrestore(&list_lock, flags);
}
static void wait_irq(struct mdp4_irq *irq, uint32_t irqstatus)
{
struct mdp4_irq_wait *wait =
container_of(irq, struct mdp4_irq_wait, irq);
wait->count--;
wake_up_all(&wait_event);
}
void mdp4_irq_wait(struct mdp4_kms *mdp4_kms, uint32_t irqmask)
{
struct mdp4_irq_wait wait = {
.irq = {
.irq = wait_irq,
.irqmask = irqmask,
},
.count = 1,
};
mdp4_irq_register(mdp4_kms, &wait.irq);
wait_event(wait_event, (wait.count <= 0));
mdp4_irq_unregister(mdp4_kms, &wait.irq);
}
void mdp4_irq_register(struct mdp4_kms *mdp4_kms, struct mdp4_irq *irq)
{
unsigned long flags;
bool needs_update = false;
spin_lock_irqsave(&list_lock, flags);
if (!irq->registered) {
irq->registered = true;
list_add(&irq->node, &mdp4_kms->irq_list);
needs_update = !mdp4_kms->in_irq;
}
spin_unlock_irqrestore(&list_lock, flags);
if (needs_update)
update_irq_unlocked(mdp4_kms);
}
void mdp4_irq_unregister(struct mdp4_kms *mdp4_kms, struct mdp4_irq *irq)
{
unsigned long flags;
bool needs_update = false;
spin_lock_irqsave(&list_lock, flags);
if (irq->registered) {
irq->registered = false;
list_del(&irq->node);
needs_update = !mdp4_kms->in_irq;
}
spin_unlock_irqrestore(&list_lock, flags);
if (needs_update)
update_irq_unlocked(mdp4_kms);
}
/*
* Copyright (C) 2013 Red Hat
* 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 "msm_drv.h"
#include "mdp4_kms.h"
#include <mach/iommu.h>
static struct mdp4_platform_config *mdp4_get_config(struct platform_device *dev);
static int mdp4_hw_init(struct msm_kms *kms)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms);
struct drm_device *dev = mdp4_kms->dev;
uint32_t version, major, minor, dmap_cfg, vg_cfg;
unsigned long clk;
int ret = 0;
pm_runtime_get_sync(dev->dev);
version = mdp4_read(mdp4_kms, REG_MDP4_VERSION);
major = FIELD(version, MDP4_VERSION_MAJOR);
minor = FIELD(version, MDP4_VERSION_MINOR);
DBG("found MDP version v%d.%d", major, minor);
if (major != 4) {
dev_err(dev->dev, "unexpected MDP version: v%d.%d\n",
major, minor);
ret = -ENXIO;
goto out;
}
mdp4_kms->rev = minor;
if (mdp4_kms->dsi_pll_vdda) {
if ((mdp4_kms->rev == 2) || (mdp4_kms->rev == 4)) {
ret = regulator_set_voltage(mdp4_kms->dsi_pll_vdda,
1200000, 1200000);
if (ret) {
dev_err(dev->dev,
"failed to set dsi_pll_vdda voltage: %d\n", ret);
goto out;
}
}
}
if (mdp4_kms->dsi_pll_vddio) {
if (mdp4_kms->rev == 2) {
ret = regulator_set_voltage(mdp4_kms->dsi_pll_vddio,
1800000, 1800000);
if (ret) {
dev_err(dev->dev,
"failed to set dsi_pll_vddio voltage: %d\n", ret);
goto out;
}
}
}
if (mdp4_kms->rev > 1) {
mdp4_write(mdp4_kms, REG_MDP4_CS_CONTROLLER0, 0x0707ffff);
mdp4_write(mdp4_kms, REG_MDP4_CS_CONTROLLER1, 0x03073f3f);
}
mdp4_write(mdp4_kms, REG_MDP4_PORTMAP_MODE, 0x3);
/* max read pending cmd config, 3 pending requests: */
mdp4_write(mdp4_kms, REG_MDP4_READ_CNFG, 0x02222);
clk = clk_get_rate(mdp4_kms->clk);
if ((mdp4_kms->rev >= 1) || (clk >= 90000000)) {
dmap_cfg = 0x47; /* 16 bytes-burst x 8 req */
vg_cfg = 0x47; /* 16 bytes-burs x 8 req */
} else {
dmap_cfg = 0x27; /* 8 bytes-burst x 8 req */
vg_cfg = 0x43; /* 16 bytes-burst x 4 req */
}
DBG("fetch config: dmap=%02x, vg=%02x", dmap_cfg, vg_cfg);
mdp4_write(mdp4_kms, REG_MDP4_DMA_FETCH_CONFIG(DMA_P), dmap_cfg);
mdp4_write(mdp4_kms, REG_MDP4_DMA_FETCH_CONFIG(DMA_E), dmap_cfg);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(VG1), vg_cfg);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(VG2), vg_cfg);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(RGB1), vg_cfg);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(RGB2), vg_cfg);
if (mdp4_kms->rev >= 2)
mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG_UPDATE_METHOD, 1);
/* disable CSC matrix / YUV by default: */
mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(VG1), 0);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(VG2), 0);
mdp4_write(mdp4_kms, REG_MDP4_DMA_P_OP_MODE, 0);
mdp4_write(mdp4_kms, REG_MDP4_DMA_S_OP_MODE, 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_CSC_CONFIG(1), 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_CSC_CONFIG(2), 0);
if (mdp4_kms->rev > 1)
mdp4_write(mdp4_kms, REG_MDP4_RESET_STATUS, 1);
out:
pm_runtime_put_sync(dev->dev);
return ret;
}
static long mdp4_round_pixclk(struct msm_kms *kms, unsigned long rate,
struct drm_encoder *encoder)
{
/* if we had >1 encoder, we'd need something more clever: */
return mdp4_dtv_round_pixclk(encoder, rate);
}
static void mdp4_preclose(struct msm_kms *kms, struct drm_file *file)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms);
struct msm_drm_private *priv = mdp4_kms->dev->dev_private;
unsigned i;
for (i = 0; i < priv->num_crtcs; i++)
mdp4_crtc_cancel_pending_flip(priv->crtcs[i]);
}
static void mdp4_destroy(struct msm_kms *kms)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms);
kfree(mdp4_kms);
}
static const struct msm_kms_funcs kms_funcs = {
.hw_init = mdp4_hw_init,
.irq_preinstall = mdp4_irq_preinstall,
.irq_postinstall = mdp4_irq_postinstall,
.irq_uninstall = mdp4_irq_uninstall,
.irq = mdp4_irq,
.enable_vblank = mdp4_enable_vblank,
.disable_vblank = mdp4_disable_vblank,
.get_format = mdp4_get_format,
.round_pixclk = mdp4_round_pixclk,
.preclose = mdp4_preclose,
.destroy = mdp4_destroy,
};
int mdp4_disable(struct mdp4_kms *mdp4_kms)
{
DBG("");
clk_disable_unprepare(mdp4_kms->clk);
if (mdp4_kms->pclk)
clk_disable_unprepare(mdp4_kms->pclk);
clk_disable_unprepare(mdp4_kms->lut_clk);
return 0;
}
int mdp4_enable(struct mdp4_kms *mdp4_kms)
{
DBG("");
clk_prepare_enable(mdp4_kms->clk);
if (mdp4_kms->pclk)
clk_prepare_enable(mdp4_kms->pclk);
clk_prepare_enable(mdp4_kms->lut_clk);
return 0;
}
static int modeset_init(struct mdp4_kms *mdp4_kms)
{
struct drm_device *dev = mdp4_kms->dev;
struct msm_drm_private *priv = dev->dev_private;
struct drm_plane *plane;
struct drm_crtc *crtc;
struct drm_encoder *encoder;
struct drm_connector *connector;
int ret;
/*
* NOTE: this is a bit simplistic until we add support
* for more than just RGB1->DMA_E->DTV->HDMI
*/
/* the CRTCs get constructed with a private plane: */
plane = mdp4_plane_init(dev, RGB1, true);
if (IS_ERR(plane)) {
dev_err(dev->dev, "failed to construct plane for RGB1\n");
ret = PTR_ERR(plane);
goto fail;
}
crtc = mdp4_crtc_init(dev, plane, priv->num_crtcs, 1, DMA_E);
if (IS_ERR(crtc)) {
dev_err(dev->dev, "failed to construct crtc for DMA_E\n");
ret = PTR_ERR(crtc);
goto fail;
}
priv->crtcs[priv->num_crtcs++] = crtc;
encoder = mdp4_dtv_encoder_init(dev);
if (IS_ERR(encoder)) {
dev_err(dev->dev, "failed to construct DTV encoder\n");
ret = PTR_ERR(encoder);
goto fail;
}
encoder->possible_crtcs = 0x1; /* DTV can be hooked to DMA_E */
priv->encoders[priv->num_encoders++] = encoder;
connector = hdmi_connector_init(dev, encoder);
if (IS_ERR(connector)) {
dev_err(dev->dev, "failed to construct HDMI connector\n");
ret = PTR_ERR(connector);
goto fail;
}
priv->connectors[priv->num_connectors++] = connector;
return 0;
fail:
return ret;
}
static const char *iommu_ports[] = {
"mdp_port0_cb0", "mdp_port1_cb0",
};
struct msm_kms *mdp4_kms_init(struct drm_device *dev)
{
struct platform_device *pdev = dev->platformdev;
struct mdp4_platform_config *config = mdp4_get_config(pdev);
struct mdp4_kms *mdp4_kms;
struct msm_kms *kms = NULL;
int ret;
mdp4_kms = kzalloc(sizeof(*mdp4_kms), GFP_KERNEL);
if (!mdp4_kms) {
dev_err(dev->dev, "failed to allocate kms\n");
ret = -ENOMEM;
goto fail;
}
kms = &mdp4_kms->base;
kms->funcs = &kms_funcs;
mdp4_kms->dev = dev;
mdp4_kms->mmio = msm_ioremap(pdev, NULL, "MDP4");
if (IS_ERR(mdp4_kms->mmio)) {
ret = PTR_ERR(mdp4_kms->mmio);
goto fail;
}
mdp4_kms->dsi_pll_vdda = devm_regulator_get(&pdev->dev, "dsi_pll_vdda");
if (IS_ERR(mdp4_kms->dsi_pll_vdda))
mdp4_kms->dsi_pll_vdda = NULL;
mdp4_kms->dsi_pll_vddio = devm_regulator_get(&pdev->dev, "dsi_pll_vddio");
if (IS_ERR(mdp4_kms->dsi_pll_vddio))
mdp4_kms->dsi_pll_vddio = NULL;
mdp4_kms->vdd = devm_regulator_get(&pdev->dev, "vdd");
if (IS_ERR(mdp4_kms->vdd))
mdp4_kms->vdd = NULL;
if (mdp4_kms->vdd) {
ret = regulator_enable(mdp4_kms->vdd);
if (ret) {
dev_err(dev->dev, "failed to enable regulator vdd: %d\n", ret);
goto fail;
}
}
mdp4_kms->clk = devm_clk_get(&pdev->dev, "core_clk");
if (IS_ERR(mdp4_kms->clk)) {
dev_err(dev->dev, "failed to get core_clk\n");
ret = PTR_ERR(mdp4_kms->clk);
goto fail;
}
mdp4_kms->pclk = devm_clk_get(&pdev->dev, "iface_clk");
if (IS_ERR(mdp4_kms->pclk))
mdp4_kms->pclk = NULL;
// XXX if (rev >= MDP_REV_42) { ???
mdp4_kms->lut_clk = devm_clk_get(&pdev->dev, "lut_clk");
if (IS_ERR(mdp4_kms->lut_clk)) {
dev_err(dev->dev, "failed to get lut_clk\n");
ret = PTR_ERR(mdp4_kms->lut_clk);
goto fail;
}
clk_set_rate(mdp4_kms->clk, config->max_clk);
clk_set_rate(mdp4_kms->lut_clk, config->max_clk);
if (!config->iommu) {
dev_err(dev->dev, "no iommu\n");
ret = -ENXIO;
goto fail;
}
/* make sure things are off before attaching iommu (bootloader could
* have left things on, in which case we'll start getting faults if
* we don't disable):
*/
mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 0);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0);
mdp4_write(mdp4_kms, REG_MDP4_DSI_ENABLE, 0);
mdelay(16);
ret = msm_iommu_attach(dev, config->iommu,
iommu_ports, ARRAY_SIZE(iommu_ports));
if (ret)
goto fail;
mdp4_kms->id = msm_register_iommu(dev, config->iommu);
if (mdp4_kms->id < 0) {
ret = mdp4_kms->id;
dev_err(dev->dev, "failed to register mdp4 iommu: %d\n", ret);
goto fail;
}
ret = modeset_init(mdp4_kms);
if (ret) {
dev_err(dev->dev, "modeset_init failed: %d\n", ret);
goto fail;
}
return kms;
fail:
if (kms)
mdp4_destroy(kms);
return ERR_PTR(ret);
}
static struct mdp4_platform_config *mdp4_get_config(struct platform_device *dev)
{
static struct mdp4_platform_config config = {};
#ifdef CONFIG_OF
/* TODO */
#else
if (cpu_is_apq8064())
config.max_clk = 266667000;
else
config.max_clk = 200000000;
config.iommu = msm_get_iommu_domain(DISPLAY_READ_DOMAIN);
#endif
return &config;
}
/*
* Copyright (C) 2013 Red Hat
* 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 __MDP4_KMS_H__
#define __MDP4_KMS_H__
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include "msm_drv.h"
#include "mdp4.xml.h"
/* For transiently registering for different MDP4 irqs that various parts
* of the KMS code need during setup/configuration. We these are not
* necessarily the same as what drm_vblank_get/put() are requesting, and
* the hysteresis in drm_vblank_put() is not necessarily desirable for
* internal housekeeping related irq usage.
*/
struct mdp4_irq {
struct list_head node;
uint32_t irqmask;
bool registered;
void (*irq)(struct mdp4_irq *irq, uint32_t irqstatus);
};
struct mdp4_kms {
struct msm_kms base;
struct drm_device *dev;
int rev;
/* mapper-id used to request GEM buffer mapped for scanout: */
int id;
void __iomem *mmio;
struct regulator *dsi_pll_vdda;
struct regulator *dsi_pll_vddio;
struct regulator *vdd;
struct clk *clk;
struct clk *pclk;
struct clk *lut_clk;
/* irq handling: */
bool in_irq;
struct list_head irq_list; /* list of mdp4_irq */
uint32_t vblank_mask; /* irq bits set for userspace vblank */
struct mdp4_irq error_handler;
};
#define to_mdp4_kms(x) container_of(x, struct mdp4_kms, base)
/* platform config data (ie. from DT, or pdata) */
struct mdp4_platform_config {
struct iommu_domain *iommu;
uint32_t max_clk;
};
struct mdp4_format {
struct msm_format base;
enum mpd4_bpc bpc_r, bpc_g, bpc_b;
enum mpd4_bpc_alpha bpc_a;
uint8_t unpack[4];
bool alpha_enable, unpack_tight;
uint8_t cpp, unpack_count;
};
#define to_mdp4_format(x) container_of(x, struct mdp4_format, base)
static inline void mdp4_write(struct mdp4_kms *mdp4_kms, u32 reg, u32 data)
{
msm_writel(data, mdp4_kms->mmio + reg);
}
static inline u32 mdp4_read(struct mdp4_kms *mdp4_kms, u32 reg)
{
return msm_readl(mdp4_kms->mmio + reg);
}
static inline uint32_t pipe2flush(enum mpd4_pipe pipe)
{
switch (pipe) {
case VG1: return MDP4_OVERLAY_FLUSH_VG1;
case VG2: return MDP4_OVERLAY_FLUSH_VG2;
case RGB1: return MDP4_OVERLAY_FLUSH_RGB1;
case RGB2: return MDP4_OVERLAY_FLUSH_RGB1;
default: return 0;
}
}
static inline uint32_t ovlp2flush(int ovlp)
{
switch (ovlp) {
case 0: return MDP4_OVERLAY_FLUSH_OVLP0;
case 1: return MDP4_OVERLAY_FLUSH_OVLP1;
default: return 0;
}
}
static inline uint32_t dma2irq(enum mdp4_dma dma)
{
switch (dma) {
case DMA_P: return MDP4_IRQ_DMA_P_DONE;
case DMA_S: return MDP4_IRQ_DMA_S_DONE;
case DMA_E: return MDP4_IRQ_DMA_E_DONE;
default: return 0;
}
}
static inline uint32_t dma2err(enum mdp4_dma dma)
{
switch (dma) {
case DMA_P: return MDP4_IRQ_PRIMARY_INTF_UDERRUN;
case DMA_S: return 0; // ???
case DMA_E: return MDP4_IRQ_EXTERNAL_INTF_UDERRUN;
default: return 0;
}
}
int mdp4_disable(struct mdp4_kms *mdp4_kms);
int mdp4_enable(struct mdp4_kms *mdp4_kms);
void mdp4_irq_preinstall(struct msm_kms *kms);
int mdp4_irq_postinstall(struct msm_kms *kms);
void mdp4_irq_uninstall(struct msm_kms *kms);
irqreturn_t mdp4_irq(struct msm_kms *kms);
void mdp4_irq_wait(struct mdp4_kms *mdp4_kms, uint32_t irqmask);
void mdp4_irq_register(struct mdp4_kms *mdp4_kms, struct mdp4_irq *irq);
void mdp4_irq_unregister(struct mdp4_kms *mdp4_kms, struct mdp4_irq *irq);
int mdp4_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
void mdp4_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
const struct msm_format *mdp4_get_format(struct msm_kms *kms, uint32_t format);
void mdp4_plane_install_properties(struct drm_plane *plane,
struct drm_mode_object *obj);
void mdp4_plane_set_scanout(struct drm_plane *plane,
struct drm_framebuffer *fb);
int mdp4_plane_mode_set(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_framebuffer *fb,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h);
enum mpd4_pipe mdp4_plane_pipe(struct drm_plane *plane);
struct drm_plane *mdp4_plane_init(struct drm_device *dev,
enum mpd4_pipe pipe_id, bool private_plane);
uint32_t mdp4_crtc_vblank(struct drm_crtc *crtc);
void mdp4_crtc_cancel_pending_flip(struct drm_crtc *crtc);
void mdp4_crtc_set_config(struct drm_crtc *crtc, uint32_t config);
void mdp4_crtc_set_intf(struct drm_crtc *crtc, enum mdp4_intf intf);
struct drm_crtc *mdp4_crtc_init(struct drm_device *dev,
struct drm_plane *plane, int id, int ovlp_id,
enum mdp4_dma dma_id);
long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate);
struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev);
#ifdef CONFIG_MSM_BUS_SCALING
static inline int match_dev_name(struct device *dev, void *data)
{
return !strcmp(dev_name(dev), data);
}
/* bus scaling data is associated with extra pointless platform devices,
* "dtv", etc.. this is a bit of a hack, but we need a way for encoders
* to find their pdata to make the bus-scaling stuff work.
*/
static inline void *mdp4_find_pdata(const char *devname)
{
struct device *dev;
dev = bus_find_device(&platform_bus_type, NULL,
(void *)devname, match_dev_name);
return dev ? dev->platform_data : NULL;
}
#endif
#endif /* __MDP4_KMS_H__ */
/*
* Copyright (C) 2013 Red Hat
* 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 "mdp4_kms.h"
struct mdp4_plane {
struct drm_plane base;
const char *name;
enum mpd4_pipe pipe;
uint32_t nformats;
uint32_t formats[32];
bool enabled;
};
#define to_mdp4_plane(x) container_of(x, struct mdp4_plane, base)
static struct mdp4_kms *get_kms(struct drm_plane *plane)
{
struct msm_drm_private *priv = plane->dev->dev_private;
return to_mdp4_kms(priv->kms);
}
static int mdp4_plane_update(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_framebuffer *fb,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
mdp4_plane->enabled = true;
if (plane->fb)
drm_framebuffer_unreference(plane->fb);
drm_framebuffer_reference(fb);
return mdp4_plane_mode_set(plane, crtc, fb,
crtc_x, crtc_y, crtc_w, crtc_h,
src_x, src_y, src_w, src_h);
}
static int mdp4_plane_disable(struct drm_plane *plane)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
DBG("%s: TODO", mdp4_plane->name); // XXX
return 0;
}
static void mdp4_plane_destroy(struct drm_plane *plane)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
mdp4_plane_disable(plane);
drm_plane_cleanup(plane);
kfree(mdp4_plane);
}
/* helper to install properties which are common to planes and crtcs */
void mdp4_plane_install_properties(struct drm_plane *plane,
struct drm_mode_object *obj)
{
// XXX
}
int mdp4_plane_set_property(struct drm_plane *plane,
struct drm_property *property, uint64_t val)
{
// XXX
return -EINVAL;
}
static const struct drm_plane_funcs mdp4_plane_funcs = {
.update_plane = mdp4_plane_update,
.disable_plane = mdp4_plane_disable,
.destroy = mdp4_plane_destroy,
.set_property = mdp4_plane_set_property,
};
void mdp4_plane_set_scanout(struct drm_plane *plane,
struct drm_framebuffer *fb)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
struct mdp4_kms *mdp4_kms = get_kms(plane);
enum mpd4_pipe pipe = mdp4_plane->pipe;
uint32_t iova;
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_STRIDE_A(pipe),
MDP4_PIPE_SRC_STRIDE_A_P0(fb->pitches[0]) |
MDP4_PIPE_SRC_STRIDE_A_P1(fb->pitches[1]));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_STRIDE_B(pipe),
MDP4_PIPE_SRC_STRIDE_B_P2(fb->pitches[2]) |
MDP4_PIPE_SRC_STRIDE_B_P3(fb->pitches[3]));
msm_gem_get_iova(msm_framebuffer_bo(fb, 0), mdp4_kms->id, &iova);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRCP0_BASE(pipe), iova);
plane->fb = fb;
}
#define MDP4_VG_PHASE_STEP_DEFAULT 0x20000000
int mdp4_plane_mode_set(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_framebuffer *fb,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
struct mdp4_kms *mdp4_kms = get_kms(plane);
enum mpd4_pipe pipe = mdp4_plane->pipe;
const struct mdp4_format *format;
uint32_t op_mode = 0;
uint32_t phasex_step = MDP4_VG_PHASE_STEP_DEFAULT;
uint32_t phasey_step = MDP4_VG_PHASE_STEP_DEFAULT;
/* src values are in Q16 fixed point, convert to integer: */
src_x = src_x >> 16;
src_y = src_y >> 16;
src_w = src_w >> 16;
src_h = src_h >> 16;
if (src_w != crtc_w) {
op_mode |= MDP4_PIPE_OP_MODE_SCALEX_EN;
/* TODO calc phasex_step */
}
if (src_h != crtc_h) {
op_mode |= MDP4_PIPE_OP_MODE_SCALEY_EN;
/* TODO calc phasey_step */
}
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_SIZE(pipe),
MDP4_PIPE_SRC_SIZE_WIDTH(src_w) |
MDP4_PIPE_SRC_SIZE_HEIGHT(src_h));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_XY(pipe),
MDP4_PIPE_SRC_XY_X(src_x) |
MDP4_PIPE_SRC_XY_Y(src_y));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_DST_SIZE(pipe),
MDP4_PIPE_DST_SIZE_WIDTH(crtc_w) |
MDP4_PIPE_DST_SIZE_HEIGHT(crtc_h));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_DST_XY(pipe),
MDP4_PIPE_SRC_XY_X(crtc_x) |
MDP4_PIPE_SRC_XY_Y(crtc_y));
mdp4_plane_set_scanout(plane, fb);
format = to_mdp4_format(msm_framebuffer_format(fb));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_FORMAT(pipe),
MDP4_PIPE_SRC_FORMAT_A_BPC(format->bpc_a) |
MDP4_PIPE_SRC_FORMAT_R_BPC(format->bpc_r) |
MDP4_PIPE_SRC_FORMAT_G_BPC(format->bpc_g) |
MDP4_PIPE_SRC_FORMAT_B_BPC(format->bpc_b) |
COND(format->alpha_enable, MDP4_PIPE_SRC_FORMAT_ALPHA_ENABLE) |
MDP4_PIPE_SRC_FORMAT_CPP(format->cpp - 1) |
MDP4_PIPE_SRC_FORMAT_UNPACK_COUNT(format->unpack_count - 1) |
COND(format->unpack_tight, MDP4_PIPE_SRC_FORMAT_UNPACK_TIGHT));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_UNPACK(pipe),
MDP4_PIPE_SRC_UNPACK_ELEM0(format->unpack[0]) |
MDP4_PIPE_SRC_UNPACK_ELEM1(format->unpack[1]) |
MDP4_PIPE_SRC_UNPACK_ELEM2(format->unpack[2]) |
MDP4_PIPE_SRC_UNPACK_ELEM3(format->unpack[3]));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(pipe), op_mode);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_PHASEX_STEP(pipe), phasex_step);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_PHASEY_STEP(pipe), phasey_step);
plane->crtc = crtc;
return 0;
}
static const char *pipe_names[] = {
"VG1", "VG2",
"RGB1", "RGB2", "RGB3",
"VG3", "VG4",
};
enum mpd4_pipe mdp4_plane_pipe(struct drm_plane *plane)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
return mdp4_plane->pipe;
}
/* initialize plane */
struct drm_plane *mdp4_plane_init(struct drm_device *dev,
enum mpd4_pipe pipe_id, bool private_plane)
{
struct msm_drm_private *priv = dev->dev_private;
struct drm_plane *plane = NULL;
struct mdp4_plane *mdp4_plane;
int ret;
mdp4_plane = kzalloc(sizeof(*mdp4_plane), GFP_KERNEL);
if (!mdp4_plane) {
ret = -ENOMEM;
goto fail;
}
plane = &mdp4_plane->base;
mdp4_plane->pipe = pipe_id;
mdp4_plane->name = pipe_names[pipe_id];
drm_plane_init(dev, plane, (1 << priv->num_crtcs) - 1, &mdp4_plane_funcs,
mdp4_plane->formats, mdp4_plane->nformats, private_plane);
mdp4_plane_install_properties(plane, &plane->base);
return plane;
fail:
if (plane)
mdp4_plane_destroy(plane);
return ERR_PTR(ret);
}
/*
* Copyright (C) 2013 Red Hat
* 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 "msm_drv.h"
#include "msm_connector.h"
void msm_connector_init(struct msm_connector *connector,
const struct msm_connector_funcs *funcs,
struct drm_encoder *encoder)
{
connector->funcs = funcs;
connector->encoder = encoder;
}
struct drm_encoder *msm_connector_attached_encoder(
struct drm_connector *connector)
{
struct msm_connector *msm_connector = to_msm_connector(connector);
return msm_connector->encoder;
}
/*
* Copyright (C) 2013 Red Hat
* 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 __MSM_CONNECTOR_H__
#define __MSM_CONNECTOR_H__
#include "msm_drv.h"
/*
* Base class for MSM connectors. Typically a connector is a bit more
* passive. But with the split between (for example) DTV within MDP4,
* and HDMI encoder, we really need two parts to an encoder. Instead
* what we do is have the part external to the display controller block
* in the connector, which is called from the encoder to delegate the
* appropriate parts of modeset.
*/
struct msm_connector;
struct msm_connector_funcs {
void (*dpms)(struct msm_connector *connector, int mode);
void (*mode_set)(struct msm_connector *connector,
struct drm_display_mode *mode);
};
struct msm_connector {
struct drm_connector base;
struct drm_encoder *encoder;
const struct msm_connector_funcs *funcs;
};
#define to_msm_connector(x) container_of(x, struct msm_connector, base)
void msm_connector_init(struct msm_connector *connector,
const struct msm_connector_funcs *funcs,
struct drm_encoder *encoder);
struct drm_encoder *msm_connector_attached_encoder(
struct drm_connector *connector);
static inline struct msm_connector *get_connector(struct drm_encoder *encoder)
{
struct msm_drm_private *priv = encoder->dev->dev_private;
int i;
for (i = 0; i < priv->num_connectors; i++) {
struct drm_connector *connector = priv->connectors[i];
if (msm_connector_attached_encoder(connector) == encoder)
return to_msm_connector(connector);
}
return NULL;
}
#endif /* __MSM_CONNECTOR_H__ */
This diff is collapsed.
/*
* Copyright (C) 2013 Red Hat
* 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 __MSM_DRV_H__
#define __MSM_DRV_H__
#include <linux/kernel.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/list.h>
#include <linux/iommu.h>
#include <linux/types.h>
#include <asm/sizes.h>
#ifndef CONFIG_OF
#include <mach/board.h>
#include <mach/socinfo.h>
#include <mach/iommu_domains.h>
#endif
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
struct msm_kms;
#define NUM_DOMAINS 1 /* one for KMS, then one per gpu core (?) */
struct msm_drm_private {
struct msm_kms *kms;
struct drm_fb_helper *fbdev;
/* list of GEM objects: */
struct list_head inactive_list;
struct workqueue_struct *wq;
/* registered IOMMU domains: */
unsigned int num_iommus;
struct iommu_domain *iommus[NUM_DOMAINS];
unsigned int num_crtcs;
struct drm_crtc *crtcs[8];
unsigned int num_encoders;
struct drm_encoder *encoders[8];
unsigned int num_connectors;
struct drm_connector *connectors[8];
};
struct msm_format {
uint32_t pixel_format;
};
/* As there are different display controller blocks depending on the
* snapdragon version, the kms support is split out and the appropriate
* implementation is loaded at runtime. The kms module is responsible
* for constructing the appropriate planes/crtcs/encoders/connectors.
*/
struct msm_kms_funcs {
/* hw initialization: */
int (*hw_init)(struct msm_kms *kms);
/* irq handling: */
void (*irq_preinstall)(struct msm_kms *kms);
int (*irq_postinstall)(struct msm_kms *kms);
void (*irq_uninstall)(struct msm_kms *kms);
irqreturn_t (*irq)(struct msm_kms *kms);
int (*enable_vblank)(struct msm_kms *kms, struct drm_crtc *crtc);
void (*disable_vblank)(struct msm_kms *kms, struct drm_crtc *crtc);
/* misc: */
const struct msm_format *(*get_format)(struct msm_kms *kms, uint32_t format);
long (*round_pixclk)(struct msm_kms *kms, unsigned long rate,
struct drm_encoder *encoder);
/* cleanup: */
void (*preclose)(struct msm_kms *kms, struct drm_file *file);
void (*destroy)(struct msm_kms *kms);
};
struct msm_kms {
const struct msm_kms_funcs *funcs;
};
struct msm_kms *mdp4_kms_init(struct drm_device *dev);
int msm_register_iommu(struct drm_device *dev, struct iommu_domain *iommu);
int msm_iommu_attach(struct drm_device *dev, struct iommu_domain *iommu,
const char **names, int cnt);
int msm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
int msm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
uint64_t msm_gem_mmap_offset(struct drm_gem_object *obj);
int msm_gem_get_iova_locked(struct drm_gem_object *obj, int id,
uint32_t *iova);
int msm_gem_get_iova(struct drm_gem_object *obj, int id, uint32_t *iova);
void msm_gem_put_iova(struct drm_gem_object *obj, int id);
int msm_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
struct drm_mode_create_dumb *args);
int msm_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev,
uint32_t handle);
int msm_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,
uint32_t handle, uint64_t *offset);
void *msm_gem_vaddr_locked(struct drm_gem_object *obj);
void *msm_gem_vaddr(struct drm_gem_object *obj);
int msm_gem_queue_inactive_work(struct drm_gem_object *obj,
struct work_struct *work);
void msm_gem_free_object(struct drm_gem_object *obj);
int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file,
uint32_t size, uint32_t flags, uint32_t *handle);
struct drm_gem_object *msm_gem_new(struct drm_device *dev,
uint32_t size, uint32_t flags);
struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane);
const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb);
struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev,
struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos);
struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev,
struct drm_file *file, struct drm_mode_fb_cmd2 *mode_cmd);
struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev);
struct drm_connector *hdmi_connector_init(struct drm_device *dev,
struct drm_encoder *encoder);
void __init hdmi_register(void);
void __exit hdmi_unregister(void);
#ifdef CONFIG_DEBUG_FS
void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m);
void msm_gem_describe_objects(struct list_head *list, struct seq_file *m);
void msm_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m);
#endif
void __iomem *msm_ioremap(struct platform_device *pdev, const char *name,
const char *dbgname);
void msm_writel(u32 data, void __iomem *addr);
u32 msm_readl(const void __iomem *addr);
#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
#define VERB(fmt, ...) if (0) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
static inline int align_pitch(int width, int bpp)
{
int bytespp = (bpp + 7) / 8;
/* adreno needs pitch aligned to 32 pixels: */
return bytespp * ALIGN(width, 32);
}
/* for the generated headers: */
#define INVALID_IDX(idx) ({BUG(); 0;})
#define FIELD(val, name) (((val) & name ## __MASK) >> name ## __SHIFT)
/* for conditionally setting boolean flag(s): */
#define COND(bool, val) ((bool) ? (val) : 0)
/* just put these here until we start adding driver private ioctls: */
// TODO might shuffle these around.. just need something for now..
#define MSM_BO_CACHE_MASK 0x0000000f
#define MSM_BO_SCANOUT 0x00010000 /* scanout capable */
#define MSM_BO_CACHED 0x00000001 /* default */
#define MSM_BO_WC 0x0000002
#define MSM_BO_UNCACHED 0x00000004
#endif /* __MSM_DRV_H__ */
/*
* Copyright (C) 2013 Red Hat
* 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 "msm_drv.h"
#include "drm_crtc.h"
#include "drm_crtc_helper.h"
struct msm_framebuffer {
struct drm_framebuffer base;
const struct msm_format *format;
struct drm_gem_object *planes[2];
};
#define to_msm_framebuffer(x) container_of(x, struct msm_framebuffer, base)
static int msm_framebuffer_create_handle(struct drm_framebuffer *fb,
struct drm_file *file_priv,
unsigned int *handle)
{
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
return drm_gem_handle_create(file_priv,
msm_fb->planes[0], handle);
}
static void msm_framebuffer_destroy(struct drm_framebuffer *fb)
{
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
int i, n = drm_format_num_planes(fb->pixel_format);
DBG("destroy: FB ID: %d (%p)", fb->base.id, fb);
drm_framebuffer_cleanup(fb);
for (i = 0; i < n; i++) {
struct drm_gem_object *bo = msm_fb->planes[i];
if (bo)
drm_gem_object_unreference_unlocked(bo);
}
kfree(msm_fb);
}
static int msm_framebuffer_dirty(struct drm_framebuffer *fb,
struct drm_file *file_priv, unsigned flags, unsigned color,
struct drm_clip_rect *clips, unsigned num_clips)
{
return 0;
}
static const struct drm_framebuffer_funcs msm_framebuffer_funcs = {
.create_handle = msm_framebuffer_create_handle,
.destroy = msm_framebuffer_destroy,
.dirty = msm_framebuffer_dirty,
};
#ifdef CONFIG_DEBUG_FS
void msm_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m)
{
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
int i, n = drm_format_num_planes(fb->pixel_format);
seq_printf(m, "fb: %dx%d@%4.4s (%2d, ID:%d)\n",
fb->width, fb->height, (char *)&fb->pixel_format,
fb->refcount.refcount.counter, fb->base.id);
for (i = 0; i < n; i++) {
seq_printf(m, " %d: offset=%d pitch=%d, obj: ",
i, fb->offsets[i], fb->pitches[i]);
msm_gem_describe(msm_fb->planes[i], m);
}
}
#endif
struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane)
{
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
return msm_fb->planes[plane];
}
const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb)
{
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
return msm_fb->format;
}
struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev,
struct drm_file *file, struct drm_mode_fb_cmd2 *mode_cmd)
{
struct drm_gem_object *bos[4] = {0};
struct drm_framebuffer *fb;
int ret, i, n = drm_format_num_planes(mode_cmd->pixel_format);
for (i = 0; i < n; i++) {
bos[i] = drm_gem_object_lookup(dev, file,
mode_cmd->handles[i]);
if (!bos[i]) {
ret = -ENXIO;
goto out_unref;
}
}
fb = msm_framebuffer_init(dev, mode_cmd, bos);
if (IS_ERR(fb)) {
ret = PTR_ERR(fb);
goto out_unref;
}
return fb;
out_unref:
for (i = 0; i < n; i++)
drm_gem_object_unreference_unlocked(bos[i]);
return ERR_PTR(ret);
}
struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev,
struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos)
{
struct msm_drm_private *priv = dev->dev_private;
struct msm_kms *kms = priv->kms;
struct msm_framebuffer *msm_fb;
struct drm_framebuffer *fb = NULL;
const struct msm_format *format;
int ret, i, n;
unsigned int hsub, vsub;
DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d@%4.4s)",
dev, mode_cmd, mode_cmd->width, mode_cmd->height,
(char *)&mode_cmd->pixel_format);
n = drm_format_num_planes(mode_cmd->pixel_format);
hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format);
vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format);
format = kms->funcs->get_format(kms, mode_cmd->pixel_format);
if (!format) {
dev_err(dev->dev, "unsupported pixel format: %4.4s\n",
(char *)&mode_cmd->pixel_format);
ret = -EINVAL;
goto fail;
}
msm_fb = kzalloc(sizeof(*msm_fb), GFP_KERNEL);
if (!msm_fb) {
ret = -ENOMEM;
goto fail;
}
fb = &msm_fb->base;
msm_fb->format = format;
for (i = 0; i < n; i++) {
unsigned int width = mode_cmd->width / (i ? hsub : 1);
unsigned int height = mode_cmd->height / (i ? vsub : 1);
unsigned int min_size;
min_size = (height - 1) * mode_cmd->pitches[i]
+ width * drm_format_plane_cpp(mode_cmd->pixel_format, i)
+ mode_cmd->offsets[i];
if (bos[i]->size < min_size) {
ret = -EINVAL;
goto fail;
}
msm_fb->planes[i] = bos[i];
}
drm_helper_mode_fill_fb_struct(fb, mode_cmd);
ret = drm_framebuffer_init(dev, fb, &msm_framebuffer_funcs);
if (ret) {
dev_err(dev->dev, "framebuffer init failed: %d\n", ret);
goto fail;
}
DBG("create: FB ID: %d (%p)", fb->base.id, fb);
return fb;
fail:
if (fb)
msm_framebuffer_destroy(fb);
return ERR_PTR(ret);
}
/*
* Copyright (C) 2013 Red Hat
* 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 "msm_drv.h"
#include "drm_crtc.h"
#include "drm_fb_helper.h"
/*
* fbdev funcs, to implement legacy fbdev interface on top of drm driver
*/
#define to_msm_fbdev(x) container_of(x, struct msm_fbdev, base)
struct msm_fbdev {
struct drm_fb_helper base;
struct drm_framebuffer *fb;
struct drm_gem_object *bo;
};
static struct fb_ops msm_fb_ops = {
.owner = THIS_MODULE,
/* Note: to properly handle manual update displays, we wrap the
* basic fbdev ops which write to the framebuffer
*/
.fb_read = fb_sys_read,
.fb_write = fb_sys_write,
.fb_fillrect = sys_fillrect,
.fb_copyarea = sys_copyarea,
.fb_imageblit = sys_imageblit,
.fb_check_var = drm_fb_helper_check_var,
.fb_set_par = drm_fb_helper_set_par,
.fb_pan_display = drm_fb_helper_pan_display,
.fb_blank = drm_fb_helper_blank,
.fb_setcmap = drm_fb_helper_setcmap,
};
static int msm_fbdev_create(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes)
{
struct msm_fbdev *fbdev = to_msm_fbdev(helper);
struct drm_device *dev = helper->dev;
struct drm_framebuffer *fb = NULL;
struct fb_info *fbi = NULL;
struct drm_mode_fb_cmd2 mode_cmd = {0};
dma_addr_t paddr;
int ret, size;
/* only doing ARGB32 since this is what is needed to alpha-blend
* with video overlays:
*/
sizes->surface_bpp = 32;
sizes->surface_depth = 32;
DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width,
sizes->surface_height, sizes->surface_bpp,
sizes->fb_width, sizes->fb_height);
mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
sizes->surface_depth);
mode_cmd.width = sizes->surface_width;
mode_cmd.height = sizes->surface_height;
mode_cmd.pitches[0] = align_pitch(
mode_cmd.width, sizes->surface_bpp);
/* allocate backing bo */
size = mode_cmd.pitches[0] * mode_cmd.height;
DBG("allocating %d bytes for fb %d", size, dev->primary->index);
mutex_lock(&dev->struct_mutex);
fbdev->bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC);
mutex_unlock(&dev->struct_mutex);
if (IS_ERR(fbdev->bo)) {
ret = PTR_ERR(fbdev->bo);
fbdev->bo = NULL;
dev_err(dev->dev, "failed to allocate buffer object: %d\n", ret);
goto fail;
}
fb = msm_framebuffer_init(dev, &mode_cmd, &fbdev->bo);
if (IS_ERR(fb)) {
dev_err(dev->dev, "failed to allocate fb\n");
/* note: if fb creation failed, we can't rely on fb destroy
* to unref the bo:
*/
drm_gem_object_unreference(fbdev->bo);
ret = PTR_ERR(fb);
goto fail;
}
mutex_lock(&dev->struct_mutex);
/* TODO implement our own fb_mmap so we don't need this: */
msm_gem_get_iova_locked(fbdev->bo, 0, &paddr);
fbi = framebuffer_alloc(0, dev->dev);
if (!fbi) {
dev_err(dev->dev, "failed to allocate fb info\n");
ret = -ENOMEM;
goto fail_unlock;
}
DBG("fbi=%p, dev=%p", fbi, dev);
fbdev->fb = fb;
helper->fb = fb;
helper->fbdev = fbi;
fbi->par = helper;
fbi->flags = FBINFO_DEFAULT;
fbi->fbops = &msm_fb_ops;
strcpy(fbi->fix.id, "msm");
ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
if (ret) {
ret = -ENOMEM;
goto fail_unlock;
}
drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
dev->mode_config.fb_base = paddr;
fbi->screen_base = msm_gem_vaddr_locked(fbdev->bo);
fbi->screen_size = fbdev->bo->size;
fbi->fix.smem_start = paddr;
fbi->fix.smem_len = fbdev->bo->size;
DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres);
DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height);
mutex_unlock(&dev->struct_mutex);
return 0;
fail_unlock:
mutex_unlock(&dev->struct_mutex);
fail:
if (ret) {
if (fbi)
framebuffer_release(fbi);
if (fb) {
drm_framebuffer_unregister_private(fb);
drm_framebuffer_remove(fb);
}
}
return ret;
}
static void msm_crtc_fb_gamma_set(struct drm_crtc *crtc,
u16 red, u16 green, u16 blue, int regno)
{
DBG("fbdev: set gamma");
}
static void msm_crtc_fb_gamma_get(struct drm_crtc *crtc,
u16 *red, u16 *green, u16 *blue, int regno)
{
DBG("fbdev: get gamma");
}
static struct drm_fb_helper_funcs msm_fb_helper_funcs = {
.gamma_set = msm_crtc_fb_gamma_set,
.gamma_get = msm_crtc_fb_gamma_get,
.fb_probe = msm_fbdev_create,
};
/* initialize fbdev helper */
struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev)
{
struct msm_drm_private *priv = dev->dev_private;
struct msm_fbdev *fbdev = NULL;
struct drm_fb_helper *helper;
int ret = 0;
fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
if (!fbdev)
goto fail;
helper = &fbdev->base;
helper->funcs = &msm_fb_helper_funcs;
ret = drm_fb_helper_init(dev, helper,
priv->num_crtcs, priv->num_connectors);
if (ret) {
dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret);
goto fail;
}
drm_fb_helper_single_add_all_connectors(helper);
/* disable all the possible outputs/crtcs before entering KMS mode */
drm_helper_disable_unused_functions(dev);
drm_fb_helper_initial_config(helper, 32);
priv->fbdev = helper;
return helper;
fail:
kfree(fbdev);
return NULL;
}
void msm_fbdev_free(struct drm_device *dev)
{
struct msm_drm_private *priv = dev->dev_private;
struct drm_fb_helper *helper = priv->fbdev;
struct msm_fbdev *fbdev;
struct fb_info *fbi;
DBG();
fbi = helper->fbdev;
/* only cleanup framebuffer if it is present */
if (fbi) {
unregister_framebuffer(fbi);
framebuffer_release(fbi);
}
drm_fb_helper_fini(helper);
fbdev = to_msm_fbdev(priv->fbdev);
/* this will free the backing object */
if (fbdev->fb) {
drm_framebuffer_unregister_private(fbdev->fb);
drm_framebuffer_remove(fbdev->fb);
}
kfree(fbdev);
priv->fbdev = NULL;
}
This diff is collapsed.
/*
* Copyright (C) 2013 Red Hat
* 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 __MSM_GEM_H__
#define __MSM_GEM_H__
#include "msm_drv.h"
struct msm_gem_object {
struct drm_gem_object base;
uint32_t flags;
struct list_head mm_list;
struct page **pages;
struct sg_table *sgt;
void *vaddr;
struct {
// XXX
uint32_t iova;
} domain[NUM_DOMAINS];
};
#define to_msm_bo(x) container_of(x, struct msm_gem_object, base)
#endif /* __MSM_GEM_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