"LA.UM.9.14.r1-19500-LAHAINA.QSSI12.0" * tag 'LA.UM.9.14.r1-19500-LAHAINA.QSSI12.0' of https://git.codelinaro.org/clo/la/platform/vendor/opensource/display-drivers: disp: msm: use vzalloc for large allocations disp: msm: sde: Fix data width calculation when widebus is enabled drm: msm: call rsc hw_init after hibernation disp: msm: sde: remove redundant backlight update disp: msm: sde: take min ib votes from perf config disp: msm: sde: validate plane mode and gem obj flags disp: msm: dsi: fix compressed RGB101010 support disp: msm: sde: set parent to xo for link clks while enterting suspend disp: msm: sde: while timing engine enabling poll for active region disp: msm: sde: fix null pointer dereference disp: msm: sde: set NOAUTOEN for sde irq to match with power event disp: msm: sde: always set CTL_x_UIDLE_ACTIVE register to "1" disp: msm: sde: move sde power event call into kms post init disp: msm: sde: fix RM poll timeouts during PM suspend/resume usecase disp: msm: sde: remove clearing cur_master in encoder enable function disp: msm: sde: cancel delayed_off_work before reinitialization disp: msm: sde: update TEAR_SYNC_WRCOUNT register before vsync counter disp: msm: sde: disable vsync counter before tear check update disp: msm: sde: disable vsync_in to update tear check disp: msm: sde: avoid tx wait during DMS for targets with dsc rev2 disp: msm: sde: avoid irq enable/disable during modeset disp: msm: fix rsc static wakeup time calculation disp: msm: dsi: allocate DSI command buffer during bind disp: msm: sde: update uidle_db_updates in both enable/disable cases disp: msm: dsi: add API to handle PHY programming during 0p9 collapse disp: msm: sde: modify format specifier disp: msm: dsi: Clear slave dma status only for broadcast command disp: msm: sde: avoid CWB in power on commit disp: msm: sde: avoid sde irq enable or disable when sde irq not active disp: msm: dsi: remove early return from dma_cmd_wait_for_done disp: msm: sde: protect file private structure with mutex lock disp: msm: add support for twm entry disp: msm: sde: add twm mode sysfs mode disp: msm: sde: add sysfs node to give panel power state disp: msm: dsi: Support uncompressed rgb101010 format disp: msm: sde: avoid rsvp_nxt allocation for suspend commit disp: rotator: remove ubwc format support for rotator disp: msm: sde: add changes to allocate compatible cwb mixers in RM disp: msm: sde: add evt log in rsc timer calculation msm: disp: rotator: add ROT macros for logs disp: msm: dp: replace pr_err with DP_ERR disp: msm: dsi: Do not call devm_clk_put() with invalid clk disp: msm: sde: disable CWB crop after cwb session is ended disp: rotator: remove warning log from spin_lock disp: msm: sde: protect file private structure with mutex lock disp: msm: dsi: add support for ultra low power state disp: msm: sde: switch rsc state before CTL_PREPARE in dual display disp: msm: sde: add checks to avoid null pointer dereference drm: msm: dsi: Update DSI parser util to skip disabled child nodes disp: msm: qpic: fix kw issues in QPIC display driver disp: msm: dsi: Fix deadlock issue in debugfs_esd_trigger_check function Change-Id: I4acda3b051e4306f0c1f1a99c9aa61dfeb99ef90
1395 lines
37 KiB
C
1395 lines
37 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2012-2016, 2021, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/of.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/module.h>
|
|
#include <linux/dma-buf.h>
|
|
#include <linux/platform_device.h>
|
|
#include <drm/drm_fb_cma_helper.h>
|
|
#include <drm/drm_gem_cma_helper.h>
|
|
#include <drm/drm_damage_helper.h>
|
|
#include <drm/drm_fb_helper.h>
|
|
#include <drm/drm_fourcc.h>
|
|
#include <drm/drm_vblank.h>
|
|
#include <drm/drm_gem_framebuffer_helper.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
|
|
#include "qpic_display.h"
|
|
#include "qpic_display_panel.h"
|
|
|
|
/* for debugging */
|
|
static bool use_bam = true;
|
|
static bool use_irq = true;
|
|
static u32 use_vsync;
|
|
|
|
/* QPIC display default format */
|
|
static uint32_t qpic_pipe_formats[] = {
|
|
DRM_FORMAT_RGB565,
|
|
};
|
|
|
|
static const uint64_t qpic_pipe_modifiers[] = {
|
|
DRM_FORMAT_MOD_LINEAR,
|
|
DRM_FORMAT_MOD_INVALID
|
|
};
|
|
|
|
void qpic_lcdc_reset(struct qpic_display_data *qpic_display)
|
|
{
|
|
u32 time_end;
|
|
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_RESET, 1 << 0);
|
|
/* wait 100 us after reset as suggested by hw */
|
|
usleep_range(100, 110);
|
|
|
|
time_end = (u32)ktime_to_ms(ktime_get()) + QPIC_MAX_VSYNC_WAIT_TIME_IN_MS;
|
|
while (((QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_STTS) & (1 << 8)) == 0)) {
|
|
if ((u32)ktime_to_ms(ktime_get()) > time_end) {
|
|
pr_err("%s reset not finished\n", __func__);
|
|
break;
|
|
}
|
|
/* yield 100 us for next polling by experiment*/
|
|
usleep_range(100, 110);
|
|
}
|
|
}
|
|
|
|
static void qpic_lcdc_interrupt_en(struct qpic_display_data *qpic_display, bool en)
|
|
{
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_CLR, 0xff);
|
|
if (en && !qpic_display->irq_ena) {
|
|
init_completion(&qpic_display->fifo_eof_comp);
|
|
qpic_display->irq_ena = true;
|
|
enable_irq(qpic_display->irq_id);
|
|
} else if (!en && qpic_display->irq_ena) {
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, 0);
|
|
disable_irq(qpic_display->irq_id);
|
|
qpic_display->irq_ena = false;
|
|
}
|
|
}
|
|
|
|
static irqreturn_t qpic_lcdc_irq_handler(int irq, void *ptr)
|
|
{
|
|
u32 data;
|
|
struct qpic_display_data *qpic_display =
|
|
(struct qpic_display_data *)ptr;
|
|
|
|
data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_STTS);
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_CLR, 0xff);
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, 0);
|
|
|
|
if (data & (BIT(2) | (BIT(4))))
|
|
complete(&qpic_display->fifo_eof_comp);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int qpic_display_pinctrl_set_state(struct qpic_panel_io_desc *qpic_panel_io,
|
|
bool active)
|
|
{
|
|
struct pinctrl_state *pin_state;
|
|
int rc = -EFAULT;
|
|
|
|
if (IS_ERR_OR_NULL(qpic_panel_io->pin_res.pinctrl))
|
|
return PTR_ERR(qpic_panel_io->pin_res.pinctrl);
|
|
|
|
if (active)
|
|
gpio_direction_output(qpic_panel_io->bl_gpio, 1);
|
|
else
|
|
gpio_direction_output(qpic_panel_io->bl_gpio, 0);
|
|
|
|
pin_state = active ? qpic_panel_io->pin_res.gpio_state_active
|
|
: qpic_panel_io->pin_res.gpio_state_suspend;
|
|
if (!IS_ERR_OR_NULL(pin_state)) {
|
|
rc = pinctrl_select_state(qpic_panel_io->pin_res.pinctrl,
|
|
pin_state);
|
|
if (rc)
|
|
pr_err("%s: can not set %s pins\n", __func__,
|
|
active ? QPIC_PINCTRL_STATE_DEFAULT
|
|
: QPIC_PINCTRL_STATE_SLEEP);
|
|
} else {
|
|
pr_err("%s: invalid '%s' pinstate\n", __func__,
|
|
active ? QPIC_PINCTRL_STATE_DEFAULT
|
|
: QPIC_PINCTRL_STATE_SLEEP);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int msm_qpic_bus_set_vote(struct qpic_display_data *qpic_display, bool vote)
|
|
{
|
|
struct qpic_display_bus_scale_pdata *bvd = qpic_display->data_bus_pdata;
|
|
struct msm_bus_path *usecase = bvd->usecase;
|
|
struct bus_scaling_data *vec = usecase[vote].vec;
|
|
int ddr_rc;
|
|
|
|
if (vote == bvd->curr_vote)
|
|
return 0;
|
|
|
|
pr_debug("vote:%d qpic-display-data-bus ab:%llu ib:%llu\n",
|
|
vote, vec->ab, vec->ib);
|
|
ddr_rc = icc_set_bw(bvd->data_bus_hdl, vec->ab, vec->ib);
|
|
if (ddr_rc)
|
|
pr_err("icc_set() failed\n");
|
|
else
|
|
bvd->curr_vote = vote;
|
|
|
|
return ddr_rc;
|
|
}
|
|
|
|
static struct qpic_display_bus_scale_pdata *qpic_display_get_bus_vote_data(
|
|
struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct device_node *of_node = dev->of_node;
|
|
struct qpic_display_bus_scale_pdata *bvd = NULL;
|
|
struct msm_bus_path *usecase = NULL;
|
|
int ret = 0, i = 0, j, num_paths, len;
|
|
const u32 *vec_arr = NULL;
|
|
|
|
if (!pdev) {
|
|
dev_err(dev, "null platform device!\n");
|
|
return NULL;
|
|
}
|
|
|
|
bvd = devm_kzalloc(dev, sizeof(*bvd), GFP_KERNEL);
|
|
if (!bvd)
|
|
return bvd;
|
|
|
|
ret = of_property_read_string(of_node, "qcom,msm-bus,name",
|
|
&bvd->name);
|
|
if (ret) {
|
|
dev_err(dev, "Bus name missing err:(%d)\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = of_property_read_u32(of_node, "qcom,msm-bus,num-cases",
|
|
&bvd->num_usecase);
|
|
if (ret) {
|
|
dev_err(dev, "num-usecases not found err:(%d), bus name:%s\n",
|
|
ret, bvd->name);
|
|
goto out;
|
|
}
|
|
|
|
if (bvd->num_usecase > 5) {
|
|
dev_err(dev, "invalid num_usecase:(%d), bus name:%s\n",
|
|
bvd->num_usecase, bvd->name);
|
|
goto out;
|
|
}
|
|
usecase = devm_kzalloc(dev, (sizeof(struct msm_bus_path) *
|
|
bvd->num_usecase), GFP_KERNEL);
|
|
if (!usecase)
|
|
goto out;
|
|
|
|
ret = of_property_read_u32(of_node, "qcom,msm-bus,num-paths", &num_paths);
|
|
if (ret) {
|
|
dev_err(dev, "num_paths not found err:(%d)\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
vec_arr = of_get_property(of_node, "qcom,msm-bus,vectors-KBps", &len);
|
|
if (!vec_arr) {
|
|
dev_err(dev, "Vector array not found\n");
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < bvd->num_usecase; i++) {
|
|
usecase[i].num_paths = num_paths;
|
|
usecase[i].vec = devm_kcalloc(dev, num_paths,
|
|
sizeof(struct bus_scaling_data),
|
|
GFP_KERNEL);
|
|
if (!usecase[i].vec)
|
|
goto out;
|
|
for (j = 0; j < num_paths; j++) {
|
|
int idx = ((i * num_paths) + j) * 2;
|
|
|
|
usecase[i].vec[j].ab = (u64)
|
|
be32_to_cpu(vec_arr[idx]);
|
|
usecase[i].vec[j].ib = (u64)
|
|
be32_to_cpu(vec_arr[idx + 1]);
|
|
}
|
|
}
|
|
|
|
bvd->usecase = usecase;
|
|
return bvd;
|
|
out:
|
|
bvd = NULL;
|
|
return bvd;
|
|
}
|
|
|
|
static int qpic_display_bus_register(struct platform_device *pdev,
|
|
struct qpic_display_data *qpic_display)
|
|
{
|
|
struct qpic_display_bus_scale_pdata *bus_pdata;
|
|
struct device *dev = &pdev->dev;
|
|
int ret = 0;
|
|
|
|
bus_pdata = qpic_display_get_bus_vote_data(dev);
|
|
if (!bus_pdata) {
|
|
dev_err(&pdev->dev, "Failed to get bus_scale data\n");
|
|
return -EINVAL;
|
|
}
|
|
qpic_display->data_bus_pdata = bus_pdata;
|
|
|
|
bus_pdata->data_bus_hdl = of_icc_get(&pdev->dev, "qpic-display-data-bus");
|
|
if (IS_ERR_OR_NULL(bus_pdata->data_bus_hdl)) {
|
|
dev_err(&pdev->dev, "(%ld): failed getting %s path\n",
|
|
PTR_ERR(bus_pdata->data_bus_hdl), "qpic-display-data-bus");
|
|
ret = PTR_ERR(bus_pdata->data_bus_hdl);
|
|
bus_pdata->data_bus_hdl = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void qpic_display_bus_unregister(struct qpic_display_data *qpic_display)
|
|
{
|
|
struct qpic_display_bus_scale_pdata *bsd = qpic_display->data_bus_pdata;
|
|
|
|
if (bsd)
|
|
icc_put(bsd->data_bus_hdl);
|
|
}
|
|
|
|
static void qpic_display_clk_ctrl(struct qpic_display_data *qpic_display, bool enable)
|
|
{
|
|
if (enable) {
|
|
if (qpic_display->qpic_clk)
|
|
clk_prepare_enable(qpic_display->qpic_clk);
|
|
if (qpic_display->qpic_a_clk)
|
|
clk_prepare_enable(qpic_display->qpic_a_clk);
|
|
} else {
|
|
if (qpic_display->qpic_a_clk)
|
|
clk_disable_unprepare(qpic_display->qpic_a_clk);
|
|
if (qpic_display->qpic_clk)
|
|
clk_disable_unprepare(qpic_display->qpic_clk);
|
|
}
|
|
}
|
|
|
|
static int qpic_lcdc_wait_for_fifo(struct qpic_display_data *qpic_display)
|
|
{
|
|
u32 data, time_end;
|
|
int ret = 0;
|
|
|
|
if (use_irq) {
|
|
data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_STTS);
|
|
data &= 0x3F;
|
|
if (data == 0)
|
|
return ret;
|
|
reinit_completion(&qpic_display->fifo_eof_comp);
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, (1 << 4));
|
|
ret = wait_for_completion_timeout(&qpic_display->fifo_eof_comp,
|
|
msecs_to_jiffies(QPIC_MAX_VSYNC_WAIT_TIME_IN_MS));
|
|
if (ret > 0) {
|
|
ret = 0;
|
|
} else {
|
|
pr_err("%s timeout %x\n", __func__, ret);
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, 0);
|
|
} else {
|
|
time_end = (u32)ktime_to_ms(ktime_get()) +
|
|
QPIC_MAX_VSYNC_WAIT_TIME_IN_MS;
|
|
while (1) {
|
|
data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_STTS);
|
|
data &= 0x3F;
|
|
if (data == 0)
|
|
break;
|
|
/* yield 10 us for next polling by experiment*/
|
|
usleep_range(10, 11);
|
|
if (ktime_to_ms(ktime_get()) > time_end) {
|
|
pr_err("%s time out\n", __func__);
|
|
ret = -EBUSY;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qpic_lcdc_send_pkt_bam(struct qpic_display_data *qpic_display,
|
|
u32 cmd, u32 len, u8 *param)
|
|
{
|
|
int ret = 0;
|
|
u32 phys_addr, cfg2, block_len, flags;
|
|
|
|
if ((cmd != OP_WRITE_MEMORY_START) &&
|
|
(cmd != OP_WRITE_MEMORY_CONTINUE)) {
|
|
memcpy((u8 *)qpic_display->cmd_buf_virt, param, len);
|
|
phys_addr = qpic_display->cmd_buf_phys;
|
|
} else {
|
|
phys_addr = (u32)param;
|
|
}
|
|
cfg2 = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2);
|
|
cfg2 &= ~0xFF;
|
|
cfg2 |= cmd;
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2, cfg2);
|
|
block_len = 0x7FF0;
|
|
while (len > 0) {
|
|
if (len <= 0x7FF0) {
|
|
flags = SPS_IOVEC_FLAG_EOT;
|
|
block_len = len;
|
|
} else {
|
|
flags = 0;
|
|
}
|
|
ret = sps_transfer_one(qpic_display->qpic_endpt.handle,
|
|
phys_addr, block_len, NULL, flags);
|
|
if (ret)
|
|
pr_err("failed to submit command %x ret %d\n",
|
|
cmd, ret);
|
|
phys_addr += block_len;
|
|
len -= block_len;
|
|
}
|
|
ret = wait_for_completion_timeout(
|
|
&qpic_display->qpic_endpt.completion,
|
|
msecs_to_jiffies(100 * 4));
|
|
if (ret <= 0)
|
|
pr_err("%s timeout %x\n", __func__, ret);
|
|
else
|
|
ret = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qpic_lcdc_wait_for_eof(struct qpic_display_data *qpic_display)
|
|
{
|
|
u32 data, time_end;
|
|
int ret = 0;
|
|
|
|
if (use_irq) {
|
|
data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_STTS);
|
|
if (data & (1 << 2))
|
|
return ret;
|
|
reinit_completion(&qpic_display->fifo_eof_comp);
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, (1 << 2));
|
|
ret = wait_for_completion_timeout(&qpic_display->fifo_eof_comp,
|
|
msecs_to_jiffies(QPIC_MAX_VSYNC_WAIT_TIME_IN_MS));
|
|
if (ret > 0) {
|
|
ret = 0;
|
|
} else {
|
|
pr_err("%s timeout %x\n", __func__, ret);
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, 0);
|
|
} else {
|
|
time_end = (u32)ktime_to_ms(ktime_get()) +
|
|
QPIC_MAX_VSYNC_WAIT_TIME_IN_MS;
|
|
while (1) {
|
|
data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_STTS);
|
|
if (data & (1 << 2))
|
|
break;
|
|
/* yield 10 us for next polling by experiment*/
|
|
usleep_range(10, 11);
|
|
if (ktime_to_ms(ktime_get()) > time_end) {
|
|
pr_err("%s wait for eof time out\n", __func__);
|
|
ret = -EBUSY;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qpic_send_pkt_sw(struct qpic_display_data *qpic_display,
|
|
u32 cmd, u32 len, u8 *param)
|
|
{
|
|
u32 bytes_left, space, data, cfg2;
|
|
int i, ret = 0;
|
|
|
|
if (len <= 4) {
|
|
len = (len + 3) / 4; /* len in dwords */
|
|
data = 0;
|
|
if (param) {
|
|
for (i = 0; i < len; i++)
|
|
data |= (u32)param[i] << (8 * i);
|
|
}
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CMD_DATA_CYCLE_CNT, len);
|
|
QPIC_OUTP(qpic_display, QPIC_REG_LCD_DEVICE_CMD0 + (4 * cmd), data);
|
|
return 0;
|
|
}
|
|
|
|
if ((len & 0x1) != 0) {
|
|
pr_debug("%s: number of bytes needs be even\n", __func__);
|
|
len = (len + 1) & (~0x1);
|
|
}
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_CLR, 0xff);
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CMD_DATA_CYCLE_CNT, 0);
|
|
cfg2 = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2);
|
|
if ((cmd != OP_WRITE_MEMORY_START) &&
|
|
(cmd != OP_WRITE_MEMORY_CONTINUE))
|
|
cfg2 |= (1 << 24); /* transparent mode */
|
|
else
|
|
cfg2 &= ~(1 << 24);
|
|
|
|
cfg2 &= ~0xFF;
|
|
cfg2 |= cmd;
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2, cfg2);
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_FIFO_SOF, 0x0);
|
|
bytes_left = len;
|
|
|
|
while (bytes_left > 0) {
|
|
ret = qpic_lcdc_wait_for_fifo(qpic_display);
|
|
if (ret)
|
|
goto exit_send_cmd_sw;
|
|
|
|
space = 16;
|
|
|
|
while ((space > 0) && (bytes_left > 0)) {
|
|
/* write to fifo */
|
|
if (bytes_left >= 4) {
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_FIFO_DATA_PORT0,
|
|
*(u32 *)param);
|
|
param += 4;
|
|
bytes_left -= 4;
|
|
space--;
|
|
} else if (bytes_left == 2) {
|
|
QPIC_OUTPW(qpic_display, QPIC_REG_QPIC_LCDC_FIFO_DATA_PORT0,
|
|
*(u16 *)param);
|
|
bytes_left -= 2;
|
|
}
|
|
}
|
|
}
|
|
/* finished */
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_FIFO_EOF, 0x0);
|
|
ret = qpic_lcdc_wait_for_eof(qpic_display);
|
|
exit_send_cmd_sw:
|
|
cfg2 &= ~(1 << 24);
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2, cfg2);
|
|
return ret;
|
|
}
|
|
|
|
int qpic_init_sps(struct qpic_display_data *qpic_display)
|
|
{
|
|
int rc = 0;
|
|
struct platform_device *pdev = qpic_display->pdev;
|
|
struct qpic_sps_endpt *end_point = &qpic_display->qpic_endpt;
|
|
struct sps_pipe *pipe_handle;
|
|
struct sps_connect *sps_config = &end_point->config;
|
|
struct sps_register_event *sps_event = &end_point->bam_event;
|
|
struct sps_bam_props bam = {0};
|
|
unsigned long bam_handle = 0;
|
|
|
|
if (qpic_display->sps_init)
|
|
return 0;
|
|
|
|
bam.phys_addr = qpic_display->qpic_phys + 0x4000;
|
|
bam.virt_addr = qpic_display->qpic_base + 0x4000;
|
|
bam.irq = qpic_display->irq_id - 4;
|
|
bam.manage = SPS_BAM_MGR_DEVICE_REMOTE | SPS_BAM_MGR_MULTI_EE;
|
|
|
|
if (sps_phy2h(bam.phys_addr, &bam_handle)) {
|
|
if (sps_register_bam_device(&bam, &bam_handle)) {
|
|
pr_err("%s bam_handle is NULL\n", __func__);
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
pipe_handle = sps_alloc_endpoint();
|
|
if (!pipe_handle) {
|
|
pr_err("sps_alloc_endpoint() failed\n");
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
rc = sps_get_config(pipe_handle, sps_config);
|
|
if (rc) {
|
|
pr_err("sps_get_config() failed %d\n", rc);
|
|
goto free_endpoint;
|
|
}
|
|
|
|
/* WRITE CASE: source - system memory; destination - BAM */
|
|
sps_config->source = SPS_DEV_HANDLE_MEM;
|
|
sps_config->destination = bam_handle;
|
|
sps_config->mode = SPS_MODE_DEST;
|
|
sps_config->dest_pipe_index = 8;
|
|
|
|
sps_config->options = SPS_O_AUTO_ENABLE | SPS_O_EOT;
|
|
sps_config->lock_group = 0;
|
|
/*
|
|
* Descriptor FIFO is a cyclic FIFO. If 64 descriptors
|
|
* are allowed to be submitted before we get any ack for any of them,
|
|
* the descriptor FIFO size should be: (SPS_MAX_DESC_NUM + 1) *
|
|
* sizeof(struct sps_iovec).
|
|
*/
|
|
sps_config->desc.size = (64) * sizeof(struct sps_iovec);
|
|
sps_config->desc.base = dmam_alloc_coherent(&pdev->dev,
|
|
sps_config->desc.size,
|
|
&sps_config->desc.phys_base,
|
|
GFP_KERNEL);
|
|
if (!sps_config->desc.base) {
|
|
pr_err("dmam_alloc_coherent() failed for size %x\n",
|
|
sps_config->desc.size);
|
|
rc = -ENOMEM;
|
|
goto free_endpoint;
|
|
}
|
|
memset(sps_config->desc.base, 0x00, sps_config->desc.size);
|
|
|
|
rc = sps_connect(pipe_handle, sps_config);
|
|
if (rc) {
|
|
pr_err("sps_connect() failed %d\n", rc);
|
|
goto free_endpoint;
|
|
}
|
|
|
|
init_completion(&end_point->completion);
|
|
sps_event->mode = SPS_TRIGGER_WAIT;
|
|
sps_event->options = SPS_O_EOT;
|
|
sps_event->xfer_done = &end_point->completion;
|
|
sps_event->user = (void *)qpic_display;
|
|
|
|
rc = sps_register_event(pipe_handle, sps_event);
|
|
if (rc) {
|
|
pr_err("sps_register_event() failed %d\n", rc);
|
|
goto sps_disconnect;
|
|
}
|
|
|
|
end_point->handle = pipe_handle;
|
|
qpic_display->sps_init = true;
|
|
goto out;
|
|
sps_disconnect:
|
|
sps_disconnect(pipe_handle);
|
|
free_endpoint:
|
|
sps_free_endpoint(pipe_handle);
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
void qpic_dump_lcdc_reg(struct qpic_display_data *qpic_display)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
pr_info("QPIC_REG_QPIC_LCDC_CTRL = %x\n",
|
|
QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL));
|
|
pr_info("QPIC_REG_QPIC_LCDC_CMD_DATA_CYCLE_CNT = %x\n",
|
|
QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CMD_DATA_CYCLE_CNT));
|
|
pr_info("QPIC_REG_QPIC_LCDC_CFG0 = %x\n",
|
|
QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG0));
|
|
pr_info("QPIC_REG_QPIC_LCDC_CFG1 = %x\n",
|
|
QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG1));
|
|
pr_info("QPIC_REG_QPIC_LCDC_CFG2 = %x\n",
|
|
QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2));
|
|
pr_info("QPIC_REG_QPIC_LCDC_IRQ_EN = %x\n",
|
|
QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN));
|
|
pr_info("QPIC_REG_QPIC_LCDC_IRQ_STTS = %x\n",
|
|
QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_STTS));
|
|
pr_info("QPIC_REG_QPIC_LCDC_STTS = %x\n",
|
|
QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_STTS));
|
|
pr_info("QPIC_REG_QPIC_LCDC_FIFO_SOF = %x\n",
|
|
QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_FIFO_SOF));
|
|
}
|
|
|
|
int qpic_send_pkt(struct qpic_display_data *qpic_display,
|
|
u32 cmd, u8 *param, u32 len)
|
|
{
|
|
if (!use_bam || ((cmd != OP_WRITE_MEMORY_CONTINUE) &&
|
|
(cmd != OP_WRITE_MEMORY_START)))
|
|
return qpic_send_pkt_sw(qpic_display, cmd, len, param);
|
|
else
|
|
return qpic_lcdc_send_pkt_bam(qpic_display, cmd, len, param);
|
|
}
|
|
|
|
int qpic_display_init(struct qpic_display_data *qpic_display)
|
|
{
|
|
int ret = 0;
|
|
u32 data;
|
|
|
|
qpic_lcdc_reset(qpic_display);
|
|
|
|
pr_info("%s QPIC version=%x\n", __func__,
|
|
QPIC_INP(qpic_display, QPIC_REG_LCDC_VERSION));
|
|
data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL);
|
|
/* clear vsync wait , bam mode = 0*/
|
|
data &= ~(3 << 0);
|
|
data &= ~(0x1f << 3);
|
|
data |= (1 << 3); /* threshold */
|
|
data |= (1 << 8); /* lcd_en */
|
|
data &= ~(0x1f << 9);
|
|
data |= (1 << 9); /* threshold */
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL, data);
|
|
|
|
if (use_irq && (!qpic_display->irq_requested)) {
|
|
ret = devm_request_irq(&qpic_display->pdev->dev,
|
|
qpic_display->irq_id, qpic_lcdc_irq_handler,
|
|
IRQF_TRIGGER_NONE, "QPIC", &qpic_display);
|
|
if (ret) {
|
|
pr_err("qpic lcdc irq request failed! irq:%d\n", qpic_display->irq_id);
|
|
use_irq = false;
|
|
} else {
|
|
disable_irq(qpic_display->irq_id);
|
|
}
|
|
qpic_display->irq_requested = true;
|
|
}
|
|
|
|
qpic_lcdc_interrupt_en(qpic_display, use_irq);
|
|
|
|
data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2);
|
|
if (qpic_display->panel_config->bpp == 24) {
|
|
data &= ~(0xFFF);
|
|
data |= 0x200; /* XRGB */
|
|
data |= 0x2C;
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2, data);
|
|
}
|
|
|
|
if (use_bam) {
|
|
qpic_init_sps(qpic_display);
|
|
data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL);
|
|
data |= (1 << 1);
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL, data);
|
|
}
|
|
/* TE enable */
|
|
if (use_vsync) {
|
|
data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL);
|
|
data |= (1 << 0);
|
|
QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL, data);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* write a frame of pixels to a MIPI screen */
|
|
u32 qpic_send_frame(struct qpic_display_data *qpic_display,
|
|
u32 x_start, u32 y_start, u32 x_end, u32 y_end,
|
|
u32 *data, u32 total_bytes)
|
|
{
|
|
u8 param[4];
|
|
u32 status;
|
|
u32 start_0_7;
|
|
u32 end_0_7;
|
|
u32 start_8_15;
|
|
u32 end_8_15;
|
|
|
|
/* convert to 16 bit representation */
|
|
x_start = x_start & 0xffff;
|
|
y_start = y_start & 0xffff;
|
|
x_end = x_end & 0xffff;
|
|
y_end = y_end & 0xffff;
|
|
|
|
/* set column/page */
|
|
start_0_7 = x_start & 0xff;
|
|
end_0_7 = x_end & 0xff;
|
|
start_8_15 = (x_start >> 8) & 0xff;
|
|
end_8_15 = (x_end >> 8) & 0xff;
|
|
param[0] = start_8_15;
|
|
param[1] = start_0_7;
|
|
param[2] = end_8_15;
|
|
param[3] = end_0_7;
|
|
status = qpic_send_pkt(qpic_display, OP_SET_COLUMN_ADDRESS, param, 4);
|
|
if (status) {
|
|
pr_err("Failed to set column address\n");
|
|
return status;
|
|
}
|
|
|
|
start_0_7 = y_start & 0xff;
|
|
end_0_7 = y_end & 0xff;
|
|
start_8_15 = (y_start >> 8) & 0xff;
|
|
end_8_15 = (y_end >> 8) & 0xff;
|
|
param[0] = start_8_15;
|
|
param[1] = start_0_7;
|
|
param[2] = end_8_15;
|
|
param[3] = end_0_7;
|
|
status = qpic_send_pkt(qpic_display, OP_SET_PAGE_ADDRESS, param, 4);
|
|
if (status) {
|
|
pr_err("Failed to set page address\n");
|
|
return status;
|
|
}
|
|
|
|
status = qpic_send_pkt(qpic_display, OP_WRITE_MEMORY_START, (u8 *)data, total_bytes);
|
|
if (status) {
|
|
pr_err("Failed to start memory write\n");
|
|
return status;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qpic_panel_regulator_init(struct qpic_panel_io_desc *panel_io)
|
|
{
|
|
int rc;
|
|
|
|
if (panel_io->vdd_vreg) {
|
|
rc = regulator_set_voltage(panel_io->vdd_vreg,
|
|
1800000, 1800000);
|
|
if (rc) {
|
|
pr_err("iovdd_vreg->set_voltage failed, rc=%d\n", rc);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
if (panel_io->avdd_vreg) {
|
|
rc = regulator_set_voltage(panel_io->avdd_vreg,
|
|
2704000, 2704000);
|
|
if (rc) {
|
|
pr_err("avdd_vreg->set_voltage failed, rc=%d\n", rc);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void panel_io_disable(struct qpic_panel_io_desc *qpic_panel_io)
|
|
{
|
|
if (qpic_display_pinctrl_set_state(qpic_panel_io, false))
|
|
pr_warn("%s panel on: pinctrl not enabled\n", __func__);
|
|
|
|
if (qpic_panel_io->vdd_vreg)
|
|
regulator_disable(qpic_panel_io->vdd_vreg);
|
|
if (qpic_panel_io->avdd_vreg)
|
|
regulator_disable(qpic_panel_io->avdd_vreg);
|
|
}
|
|
|
|
static void qpic_display_io_free(struct qpic_panel_io_desc *qpic_panel_io)
|
|
{
|
|
if (qpic_panel_io->ad8_gpio)
|
|
gpio_free(qpic_panel_io->ad8_gpio);
|
|
if (qpic_panel_io->cs_gpio)
|
|
gpio_free(qpic_panel_io->cs_gpio);
|
|
if (qpic_panel_io->rst_gpio)
|
|
gpio_free(qpic_panel_io->rst_gpio);
|
|
if (qpic_panel_io->te_gpio)
|
|
gpio_free(qpic_panel_io->te_gpio);
|
|
if (qpic_panel_io->bl_gpio)
|
|
gpio_free(qpic_panel_io->bl_gpio);
|
|
}
|
|
|
|
static int panel_io_enable(struct qpic_panel_io_desc *qpic_panel_io)
|
|
{
|
|
int rc;
|
|
|
|
if (qpic_panel_io->vdd_vreg) {
|
|
rc = regulator_enable(qpic_panel_io->vdd_vreg);
|
|
if (rc) {
|
|
pr_err("enable vdd failed, rc=%d\n", rc);
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
if (qpic_panel_io->avdd_vreg) {
|
|
rc = regulator_enable(qpic_panel_io->avdd_vreg);
|
|
if (rc) {
|
|
pr_err("enable avdd failed, rc=%d\n", rc);
|
|
goto power_io_error;
|
|
}
|
|
}
|
|
|
|
/* GPIO settings using pinctrl */
|
|
if (qpic_display_pinctrl_set_state(qpic_panel_io, true))
|
|
pr_warn("%s panel on: pinctrl not enabled\n", __func__);
|
|
|
|
/* wait for 20 ms after enable gpio as suggested by hw */
|
|
msleep(20);
|
|
|
|
return 0;
|
|
|
|
power_io_error:
|
|
if (qpic_panel_io->avdd_vreg)
|
|
regulator_disable(qpic_panel_io->avdd_vreg);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int qpic_display_io_request(struct qpic_panel_io_desc *qpic_panel_io)
|
|
{
|
|
if ((qpic_panel_io->rst_gpio) &&
|
|
(gpio_request(qpic_panel_io->rst_gpio, "disp_rst_n"))) {
|
|
pr_err("%s request reset gpio failed\n", __func__);
|
|
goto power_io_error;
|
|
}
|
|
|
|
if ((qpic_panel_io->cs_gpio) &&
|
|
(gpio_request(qpic_panel_io->cs_gpio, "disp_cs_n"))) {
|
|
pr_err("%s request cs gpio failed\n", __func__);
|
|
goto power_io_error;
|
|
}
|
|
|
|
if ((qpic_panel_io->ad8_gpio) &&
|
|
(gpio_request(qpic_panel_io->ad8_gpio, "disp_ad8_n"))) {
|
|
pr_err("%s request ad8 gpio failed\n", __func__);
|
|
goto power_io_error;
|
|
}
|
|
|
|
if ((qpic_panel_io->te_gpio) &&
|
|
(gpio_request(qpic_panel_io->te_gpio, "disp_te_n"))) {
|
|
pr_err("%s request te gpio failed\n", __func__);
|
|
goto power_io_error;
|
|
}
|
|
|
|
if ((qpic_panel_io->bl_gpio) &&
|
|
(gpio_request(qpic_panel_io->bl_gpio, "disp_bl_n"))) {
|
|
pr_err("%s request bl gpio failed\n", __func__);
|
|
goto power_io_error;
|
|
}
|
|
|
|
return 0;
|
|
|
|
power_io_error:
|
|
return -EINVAL;
|
|
}
|
|
|
|
int qpic_display_on(struct qpic_display_data *qpic_display)
|
|
{
|
|
int rc = 0;
|
|
|
|
qpic_display_clk_ctrl(qpic_display, true);
|
|
|
|
if (qpic_display->is_qpic_on) {
|
|
pr_info("qpic already enabled\n");
|
|
return rc;
|
|
}
|
|
|
|
rc = qpic_display_init(qpic_display);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (qpic_display->panel_on && !qpic_display->is_panel_on) {
|
|
panel_io_enable(&qpic_display->panel_io);
|
|
rc = qpic_display->panel_on(qpic_display);
|
|
if (rc) {
|
|
pr_err("failed to enable panel\n");
|
|
return -EINVAL;
|
|
}
|
|
qpic_display->is_panel_on = true;
|
|
} else {
|
|
pr_info("panel on function is not specified or panel already on\n");
|
|
return 0;
|
|
}
|
|
|
|
qpic_display->is_qpic_on = true;
|
|
qpic_display->is_panel_on = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qpic_display_off(struct qpic_display_data *qpic_display)
|
|
{
|
|
|
|
if (qpic_display->panel_off && qpic_display->is_panel_on) {
|
|
qpic_display->panel_off(qpic_display);
|
|
qpic_display->is_panel_on = false;
|
|
}
|
|
|
|
if (use_irq)
|
|
qpic_lcdc_interrupt_en(qpic_display, false);
|
|
|
|
panel_io_disable(&qpic_display->panel_io);
|
|
|
|
qpic_display_clk_ctrl(qpic_display, false);
|
|
|
|
qpic_display->is_qpic_on = false;
|
|
qpic_display->is_panel_on = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void qpic_display_driver_release(struct drm_device *dev)
|
|
{
|
|
DRM_DEBUG_DRIVER("\n");
|
|
|
|
drm_mode_config_cleanup(dev);
|
|
drm_dev_fini(dev);
|
|
}
|
|
|
|
#define DRIVER_NAME "drm_qpic"
|
|
#define DRIVER_DESC "DRM QPIC display driver"
|
|
#define DRIVER_DATE "2021"
|
|
#define DRIVER_MAJOR 1
|
|
#define DRIVER_MINOR 0
|
|
|
|
DEFINE_DRM_GEM_CMA_FOPS(qpic_display_fops);
|
|
|
|
static struct drm_driver qpic_drm_driver = {
|
|
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
|
|
|
|
.name = DRIVER_NAME,
|
|
.desc = DRIVER_DESC,
|
|
.date = DRIVER_DATE,
|
|
.major = DRIVER_MAJOR,
|
|
.minor = DRIVER_MINOR,
|
|
|
|
.release = qpic_display_driver_release,
|
|
.fops = &qpic_display_fops,
|
|
DRM_GEM_CMA_VMAP_DRIVER_OPS,
|
|
};
|
|
|
|
static const struct drm_mode_config_funcs qpic_mode_config_funcs = {
|
|
.fb_create = drm_gem_fb_create_with_dirty,
|
|
.atomic_check = drm_atomic_helper_check,
|
|
.atomic_commit = drm_atomic_helper_commit,
|
|
};
|
|
|
|
static void qpic_display_fb_mark_dirty(struct drm_framebuffer *fb, struct drm_rect *rect)
|
|
{
|
|
u32 size;
|
|
struct drm_gem_cma_object *cma_obj = NULL;
|
|
struct dma_buf_attachment *import_attach = NULL;
|
|
struct qpic_display_data *qpic_display = fb->dev->dev_private;
|
|
|
|
if (!qpic_display->is_qpic_on || !qpic_display->is_panel_on) {
|
|
pr_info("%s: qpic or panel is not enabled\n", __func__);
|
|
return;
|
|
}
|
|
|
|
cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
|
|
if (!cma_obj) {
|
|
pr_err("failed to get gem obj\n");
|
|
return;
|
|
}
|
|
import_attach = cma_obj->base.import_attach;
|
|
|
|
/* currently QPIC display SW can't support partial updates */
|
|
rect->x1 = 0;
|
|
rect->x2 = fb->width;
|
|
rect->y1 = 0;
|
|
rect->y2 = fb->height;
|
|
|
|
drm_framebuffer_get(fb);
|
|
|
|
if (import_attach)
|
|
dma_buf_begin_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE);
|
|
|
|
msm_qpic_bus_set_vote(qpic_display, 1);
|
|
size = fb->width * fb->height * fb->format->depth / 8;
|
|
|
|
qpic_send_frame(qpic_display, rect->x1, rect->x2,
|
|
rect->y1 - 1, rect->y2 - 1,
|
|
use_bam ? (u32 *)cma_obj->paddr:(u32 *)cma_obj->vaddr, size);
|
|
|
|
msm_qpic_bus_set_vote(qpic_display, 0);
|
|
drm_framebuffer_put(fb);
|
|
|
|
if (import_attach)
|
|
dma_buf_end_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE);
|
|
|
|
}
|
|
|
|
static void qpic_display_pipe_enable(struct drm_simple_display_pipe *pipe,
|
|
struct drm_crtc_state *crtc_state,
|
|
struct drm_plane_state *plane_state)
|
|
{
|
|
struct qpic_display_data *qpic_display = pipe->crtc.dev->dev_private;
|
|
|
|
qpic_display_on(qpic_display);
|
|
qpic_display->pipe_enabled = true;
|
|
}
|
|
|
|
static void qpic_display_pipe_disable(struct drm_simple_display_pipe *pipe)
|
|
{
|
|
struct qpic_display_data *qpic_display = pipe->crtc.dev->dev_private;
|
|
|
|
qpic_display->pipe_enabled = false;
|
|
qpic_display_off(qpic_display);
|
|
}
|
|
|
|
static void qpic_display_pipe_update(struct drm_simple_display_pipe *pipe,
|
|
struct drm_plane_state *old_state)
|
|
{
|
|
struct drm_plane_state *state = pipe->plane.state;
|
|
struct qpic_display_data *qpic_display = pipe->crtc.dev->dev_private;
|
|
struct drm_crtc *crtc = &pipe->crtc;
|
|
struct drm_rect rect = { 0, 0,
|
|
qpic_display->panel_config->xres, qpic_display->panel_config->yres };
|
|
|
|
if (drm_atomic_helper_damage_merged(old_state, state, &rect))
|
|
qpic_display_fb_mark_dirty(pipe->plane.state->fb, &rect);
|
|
|
|
if (crtc->state->event) {
|
|
spin_lock_irq(&crtc->dev->event_lock);
|
|
drm_crtc_send_vblank_event(crtc, crtc->state->event);
|
|
crtc->state->event = NULL;
|
|
spin_unlock_irq(&crtc->dev->event_lock);
|
|
}
|
|
}
|
|
|
|
static int qpic_display_conn_get_modes(struct drm_connector *connector)
|
|
{
|
|
struct qpic_display_data *qpic_display = connector->dev->dev_private;
|
|
struct drm_display_mode *mode;
|
|
|
|
mode = drm_mode_duplicate(connector->dev, &qpic_display->drm_mode);
|
|
if (!mode) {
|
|
DRM_ERROR("Failed to duplicate mode\n");
|
|
return 0;
|
|
}
|
|
|
|
drm_mode_set_name(mode);
|
|
mode->type |= DRM_MODE_TYPE_PREFERRED;
|
|
drm_mode_probed_add(connector, mode);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static const struct drm_connector_helper_funcs qpic_display_conn_helper_funcs = {
|
|
.get_modes = qpic_display_conn_get_modes,
|
|
};
|
|
|
|
static const struct drm_connector_funcs qpic_display_conn_funcs = {
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.destroy = drm_connector_cleanup,
|
|
.reset = drm_atomic_helper_connector_reset,
|
|
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
|
};
|
|
|
|
static int qpic_display_conn_init(struct qpic_display_data *qpic_display)
|
|
{
|
|
drm_connector_helper_add(&qpic_display->conn, &qpic_display_conn_helper_funcs);
|
|
return drm_connector_init(&qpic_display->drm_dev, &qpic_display->conn,
|
|
&qpic_display_conn_funcs, DRM_MODE_CONNECTOR_9PinDIN);
|
|
}
|
|
|
|
static void qpic_display_drm_mode_init(struct qpic_display_data *qpic_display)
|
|
{
|
|
struct qpic_panel_config *panel_config = qpic_display->panel_config;
|
|
struct drm_display_mode qpic_display_mode = {
|
|
DRM_SIMPLE_MODE(panel_config->xres, panel_config->yres, 0, 0),
|
|
};
|
|
|
|
drm_mode_copy(&qpic_display->drm_mode, &qpic_display_mode);
|
|
|
|
if (panel_config->bpp == 16)
|
|
qpic_pipe_formats[0] = DRM_FORMAT_RGB565;
|
|
else if (panel_config->bpp == 24)
|
|
qpic_pipe_formats[0] = DRM_FORMAT_RGB888;
|
|
}
|
|
|
|
static const struct drm_simple_display_pipe_funcs qpic_pipe_funcs = {
|
|
.enable = qpic_display_pipe_enable,
|
|
.disable = qpic_display_pipe_disable,
|
|
.update = qpic_display_pipe_update,
|
|
};
|
|
|
|
static int qpic_get_panel_config(struct platform_device *pdev,
|
|
struct qpic_display_data *qpic_display)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
static const char *panel_name;
|
|
|
|
panel_name = of_get_property(np, "panel-name", NULL);
|
|
if (panel_name)
|
|
pr_info("%s: panel name = %s\n", __func__, panel_name);
|
|
else
|
|
pr_info("panel name not specified\n");
|
|
|
|
/* select panel according to panel name */
|
|
if (panel_name && !strcmp(panel_name, "ili_qvga")) {
|
|
pr_info("%s: select ili qvga lcdc panel\n");
|
|
get_ili_qvga_panel_config(qpic_display);
|
|
} else {
|
|
/* select default panel */
|
|
pr_info("%s: select default panel\n");
|
|
get_ili_qvga_panel_config(qpic_display);
|
|
}
|
|
|
|
if (!qpic_display->panel_config) {
|
|
pr_err("get panel config failed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qpic_display_pinctrl_init(struct platform_device *pdev,
|
|
struct qpic_panel_io_desc *qpic_panel_io)
|
|
{
|
|
qpic_panel_io->pin_res.pinctrl = devm_pinctrl_get(&pdev->dev);
|
|
if (IS_ERR_OR_NULL(qpic_panel_io->pin_res.pinctrl)) {
|
|
pr_err("%s: failed to get pinctrl\n", __func__);
|
|
return PTR_ERR(qpic_panel_io->pin_res.pinctrl);
|
|
}
|
|
|
|
qpic_panel_io->pin_res.gpio_state_active
|
|
= pinctrl_lookup_state(qpic_panel_io->pin_res.pinctrl,
|
|
QPIC_PINCTRL_STATE_DEFAULT);
|
|
if (IS_ERR_OR_NULL(qpic_panel_io->pin_res.gpio_state_active))
|
|
pr_warn("%s: cannot get default pinstate\n", __func__);
|
|
|
|
qpic_panel_io->pin_res.gpio_state_suspend
|
|
= pinctrl_lookup_state(qpic_panel_io->pin_res.pinctrl,
|
|
QPIC_PINCTRL_STATE_SLEEP);
|
|
if (IS_ERR_OR_NULL(qpic_panel_io->pin_res.gpio_state_suspend))
|
|
pr_warn("%s: cannot get sleep pinstate\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qpic_display_io_init(struct platform_device *pdev,
|
|
struct qpic_panel_io_desc *qpic_panel_io)
|
|
{
|
|
int rc = 0;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int rst_gpio, cs_gpio, te_gpio, ad8_gpio, bl_gpio;
|
|
struct regulator *vdd_vreg;
|
|
struct regulator *avdd_vreg;
|
|
|
|
rc = qpic_display_pinctrl_init(pdev, qpic_panel_io);
|
|
if (rc) {
|
|
pr_err("%s: failed to get pin resources\n", __func__);
|
|
return rc;
|
|
}
|
|
|
|
rst_gpio = of_get_named_gpio(np, "qcom,rst-gpio", 0);
|
|
cs_gpio = of_get_named_gpio(np, "qcom,cs-gpio", 0);
|
|
ad8_gpio = of_get_named_gpio(np, "qcom,ad8-gpio", 0);
|
|
te_gpio = of_get_named_gpio(np, "qcom,panel-te-gpio", 0);
|
|
bl_gpio = of_get_named_gpio(np, "qcom,panel-bl-gpio", 0);
|
|
|
|
if (!gpio_is_valid(rst_gpio))
|
|
pr_warn("%s: reset gpio not specified\n", __func__);
|
|
else
|
|
qpic_panel_io->rst_gpio = rst_gpio;
|
|
|
|
if (!gpio_is_valid(cs_gpio))
|
|
pr_warn("%s: cs gpio not specified\n", __func__);
|
|
else
|
|
qpic_panel_io->cs_gpio = cs_gpio;
|
|
|
|
if (!gpio_is_valid(ad8_gpio))
|
|
pr_warn("%s: ad8 gpio not specified\n", __func__);
|
|
else
|
|
qpic_panel_io->ad8_gpio = ad8_gpio;
|
|
|
|
if (!gpio_is_valid(te_gpio))
|
|
pr_warn("%s: te gpio not specified\n", __func__);
|
|
else
|
|
qpic_panel_io->te_gpio = te_gpio;
|
|
|
|
if (!gpio_is_valid(bl_gpio))
|
|
pr_warn("%s: te gpio not specified\n", __func__);
|
|
else
|
|
qpic_panel_io->bl_gpio = bl_gpio;
|
|
|
|
vdd_vreg = devm_regulator_get_optional(&pdev->dev, "vdd");
|
|
if (IS_ERR_OR_NULL(vdd_vreg))
|
|
pr_err("%s could not get vdd\n", __func__);
|
|
else
|
|
qpic_panel_io->vdd_vreg = vdd_vreg;
|
|
|
|
avdd_vreg = devm_regulator_get_optional(&pdev->dev, "avdd");
|
|
if (IS_ERR_OR_NULL(avdd_vreg))
|
|
pr_err("%s could not get avdd\n", __func__);
|
|
else
|
|
qpic_panel_io->avdd_vreg = avdd_vreg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qpic_display_alloc_cmd_buf(struct qpic_display_data *qpic_display)
|
|
{
|
|
if (!qpic_display->res_init)
|
|
return -EINVAL;
|
|
|
|
if (qpic_display->cmd_buf_virt)
|
|
return 0;
|
|
|
|
qpic_display->cmd_buf_virt = dmam_alloc_coherent(
|
|
&qpic_display->pdev->dev, QPIC_MAX_CMD_BUF_SIZE_IN_BYTES,
|
|
&qpic_display->cmd_buf_phys, GFP_KERNEL);
|
|
if (!qpic_display->cmd_buf_virt) {
|
|
pr_err("%s cmd buf allocation failed\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
pr_info("%s cmd_buf virt=%x phys=%x\n", __func__,
|
|
(int) qpic_display->cmd_buf_virt,
|
|
qpic_display->cmd_buf_phys);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qpic_display_get_resource(struct qpic_display_data *qpic_display)
|
|
{
|
|
struct resource *res;
|
|
int rc;
|
|
struct platform_device *pdev = qpic_display->pdev;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qpic_base");
|
|
if (!res) {
|
|
pr_err("unable to get QPIC reg base address\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
qpic_display->qpic_reg_size = resource_size(res);
|
|
qpic_display->qpic_base = devm_ioremap(&pdev->dev, res->start,
|
|
qpic_display->qpic_reg_size);
|
|
if (unlikely(!qpic_display->qpic_base)) {
|
|
pr_err("unable to map MDSS QPIC base\n");
|
|
return -ENOMEM;
|
|
}
|
|
qpic_display->qpic_phys = res->start;
|
|
pr_info("MDSS QPIC HW Base phy_Address=0x%x virt=0x%x\n",
|
|
(int) res->start,
|
|
(int) qpic_display->qpic_base);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (!res) {
|
|
pr_err("unable to get QPIC irq\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
qpic_display->qpic_clk = devm_clk_get(&pdev->dev, "core_clk");
|
|
if (IS_ERR(qpic_display->qpic_clk)) {
|
|
qpic_display->qpic_clk = NULL;
|
|
pr_err("%s: Can't find core_clk\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
qpic_display->irq_id = res->start;
|
|
qpic_display->res_init = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qpic_display_probe(struct platform_device *pdev)
|
|
{
|
|
struct drm_device *drm_dev;
|
|
struct qpic_display_data *qpic_display;
|
|
int rc;
|
|
|
|
if (!pdev->dev.of_node) {
|
|
pr_err("qpic driver only supports device tree probe\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
qpic_display = devm_kzalloc(&pdev->dev, sizeof(*qpic_display), GFP_KERNEL);
|
|
if (!qpic_display)
|
|
return -ENOMEM;
|
|
|
|
qpic_display->pdev = pdev;
|
|
platform_set_drvdata(pdev, qpic_display);
|
|
|
|
rc = qpic_get_panel_config(pdev, qpic_display);
|
|
if (rc) {
|
|
pr_err("failed to get panel config\n");
|
|
goto out;
|
|
}
|
|
|
|
qpic_display->qpic_transfer = qpic_send_pkt;
|
|
|
|
rc = qpic_display_get_resource(qpic_display);
|
|
if (rc) {
|
|
pr_err("qpic display get resource failed, rc = %d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = qpic_display_io_init(pdev, &qpic_display->panel_io);
|
|
if (rc) {
|
|
pr_err("qpic display IO init failed, rc = %d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = qpic_panel_regulator_init(&qpic_display->panel_io);
|
|
if (rc) {
|
|
pr_err("qpic panel regulator init failed, rc = %d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = qpic_display_io_request(&qpic_display->panel_io);
|
|
if (rc) {
|
|
pr_err("qpic display io request failed, rc = %d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = qpic_display_bus_register(pdev, qpic_display);
|
|
if (rc) {
|
|
pr_err("qpic display bus register failed, rc = %d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = qpic_display_alloc_cmd_buf(qpic_display);
|
|
if (rc) {
|
|
pr_err("qpic display allocate cmd buffer failed, rc = %d\n", rc);
|
|
goto bus_unregister;
|
|
}
|
|
|
|
drm_dev = &qpic_display->drm_dev;
|
|
rc = drm_dev_init(drm_dev, &qpic_drm_driver, &pdev->dev);
|
|
if (rc || !drm_dev) {
|
|
pr_err("drm_dev_init failed, rc = %d\n", rc);
|
|
goto free_buf;
|
|
}
|
|
drm_dev->dev_private = qpic_display;
|
|
|
|
qpic_display_drm_mode_init(qpic_display);
|
|
|
|
drm_mode_config_init(drm_dev);
|
|
drm_dev->mode_config.funcs = &qpic_mode_config_funcs;
|
|
drm_dev->mode_config.min_width = qpic_display->drm_mode.hdisplay;
|
|
drm_dev->mode_config.max_width = qpic_display->drm_mode.hdisplay;
|
|
drm_dev->mode_config.min_height = qpic_display->drm_mode.vdisplay;
|
|
drm_dev->mode_config.max_height = qpic_display->drm_mode.vdisplay;
|
|
|
|
rc = qpic_display_conn_init(qpic_display);
|
|
if (rc) {
|
|
pr_err("qpic display connector init failed, rc = %d\n", rc);
|
|
goto err_put;
|
|
}
|
|
|
|
/* initialize a simple pipe line for drm driver*/
|
|
rc = drm_simple_display_pipe_init(&qpic_display->drm_dev,
|
|
&qpic_display->pipe,
|
|
&qpic_pipe_funcs,
|
|
qpic_pipe_formats,
|
|
ARRAY_SIZE(qpic_pipe_formats),
|
|
qpic_pipe_modifiers,
|
|
&qpic_display->conn);
|
|
if (rc)
|
|
goto err_put;
|
|
|
|
drm_mode_config_reset(drm_dev);
|
|
|
|
rc = drm_dev_register(drm_dev, 0);
|
|
if (rc)
|
|
goto err_put;
|
|
|
|
return rc;
|
|
|
|
err_put:
|
|
drm_dev_put(drm_dev);
|
|
free_buf:
|
|
dmam_free_coherent(&qpic_display->pdev->dev, QPIC_MAX_CMD_BUF_SIZE_IN_BYTES,
|
|
qpic_display->cmd_buf_virt, qpic_display->cmd_buf_phys);
|
|
bus_unregister:
|
|
qpic_display_bus_unregister(qpic_display);
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static int qpic_display_remove(struct platform_device *pdev)
|
|
{
|
|
struct qpic_display_data *qpic_display = platform_get_drvdata(pdev);
|
|
|
|
drm_dev_unplug(&qpic_display->drm_dev);
|
|
drm_atomic_helper_shutdown(&qpic_display->drm_dev);
|
|
|
|
qpic_display_io_free(&qpic_display->panel_io);
|
|
qpic_display_clk_ctrl(qpic_display, 0);
|
|
msm_qpic_bus_set_vote(qpic_display, 0);
|
|
qpic_display_bus_unregister(qpic_display);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id qpic_display_dt_match[] = {
|
|
{ .compatible = "qcom,mdss_qpic",},
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver qpic_display_driver = {
|
|
.probe = qpic_display_probe,
|
|
.remove = qpic_display_remove,
|
|
.driver = {
|
|
.name = "qpic_display",
|
|
.of_match_table = qpic_display_dt_match,
|
|
},
|
|
};
|
|
|
|
static int __init qpic_display_driver_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = platform_driver_register(&qpic_display_driver);
|
|
if (ret)
|
|
pr_err("qpic_display_register_driver() failed!,ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
MODULE_DEVICE_TABLE(of, qpic_display_dt_match);
|
|
|
|
module_init(qpic_display_driver_init);
|
|
|