From b961099875cc08de6ffb895e72c739fbed3b0368 Mon Sep 17 00:00:00 2001 From: Lei Chen Date: Mon, 15 Mar 2021 15:43:12 +0800 Subject: [PATCH] display: msm: qpic: Add QPIC display support base on DRM framework QPIC display is used to send frame data to panel over Qualcomm Parallel Interface Controller on MDM project. Old QPIC display driver is base on framebuffer framework, but now msm framebuffer driver is no longer used. So add supoport for QPIC display driver base on tiny DRM framework. Change-Id: I4f2434c9f9ad536e2ea56ed3daa6eb12ad56255b Signed-off-by: Lei Chen --- Makefile | 10 + config/sdxlemurdisp.conf | 4 + config/sdxlemurdispconf.h | 6 + tinydrm/Makefile | 5 + tinydrm/qpic_display.c | 1386 ++++++++++++++++++++++++++++++++++ tinydrm/qpic_display.h | 137 ++++ tinydrm/qpic_display_panel.h | 91 +++ tinydrm/qpic_panel_ili9341.c | 84 +++ 8 files changed, 1723 insertions(+) create mode 100644 config/sdxlemurdisp.conf create mode 100644 config/sdxlemurdispconf.h create mode 100644 tinydrm/Makefile create mode 100644 tinydrm/qpic_display.c create mode 100644 tinydrm/qpic_display.h create mode 100644 tinydrm/qpic_display_panel.h create mode 100644 tinydrm/qpic_panel_ili9341.c diff --git a/Makefile b/Makefile index 83cbd7e89da3..35bfbd4063ef 100644 --- a/Makefile +++ b/Makefile @@ -59,3 +59,13 @@ LINUXINCLUDE += -include $(srctree)/techpack/display/config/monacodispconf.h endif obj-$(CONFIG_DRM_MSM) += msm/ + +ifeq ($(CONFIG_ARCH_SDXLEMUR), y) +include $(srctree)/techpack/display/config/sdxlemurdisp.conf +endif + +ifeq ($(CONFIG_ARCH_SDXLEMUR), y) +LINUXINCLUDE += -include $(srctree)/techpack/display/config/sdxlemurdispconf.h +endif + +obj-$(CONFIG_DRM_QPIC_DISPLAY) += tinydrm/ diff --git a/config/sdxlemurdisp.conf b/config/sdxlemurdisp.conf new file mode 100644 index 000000000000..2cb972cac0f3 --- /dev/null +++ b/config/sdxlemurdisp.conf @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (c) 2021, The Linux Foundation. All rights reserved. + +export CONFIG_DRM_QPIC_DISPLAY=y diff --git a/config/sdxlemurdispconf.h b/config/sdxlemurdispconf.h new file mode 100644 index 000000000000..3a219b3335bd --- /dev/null +++ b/config/sdxlemurdispconf.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#define CONFIG_DRM_QPIC_DISPLAY 1 diff --git a/tinydrm/Makefile b/tinydrm/Makefile new file mode 100644 index 000000000000..409a82ff4647 --- /dev/null +++ b/tinydrm/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_DRM_QPIC_DISPLAY) += qpic_display.o \ + qpic_panel_ili9341.o \ + diff --git a/tinydrm/qpic_display.c b/tinydrm/qpic_display.c new file mode 100644 index 000000000000..d000751bdd76 --- /dev/null +++ b/tinydrm/qpic_display.c @@ -0,0 +1,1386 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = drm_fb_cma_get_gem_obj(fb, 0); + struct dma_buf_attachment *import_attach = cma_obj->base.import_attach; + 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; + } + /* 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"); + rc = -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"); + rc = -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"); + rc = -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); + diff --git a/tinydrm/qpic_display.h b/tinydrm/qpic_display.h new file mode 100644 index 000000000000..7e5af6e3a885 --- /dev/null +++ b/tinydrm/qpic_display.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014-2015, 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __QPIC_DISPLAY_H__ +#define __QPIC_DISPLAY_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QPIC_REG_QPIC_LCDC_CTRL 0x22000 +#define QPIC_REG_LCDC_VERSION 0x22004 +#define QPIC_REG_QPIC_LCDC_IRQ_EN 0x22008 +#define QPIC_REG_QPIC_LCDC_IRQ_STTS 0x2200C +#define QPIC_REG_QPIC_LCDC_IRQ_CLR 0x22010 +#define QPIC_REG_QPIC_LCDC_STTS 0x22014 +#define QPIC_REG_QPIC_LCDC_CMD_DATA_CYCLE_CNT 0x22018 +#define QPIC_REG_QPIC_LCDC_CFG0 0x22020 +#define QPIC_REG_QPIC_LCDC_CFG1 0x22024 +#define QPIC_REG_QPIC_LCDC_CFG2 0x22028 +#define QPIC_REG_QPIC_LCDC_RESET 0x2202C +#define QPIC_REG_QPIC_LCDC_FIFO_SOF 0x22100 +#define QPIC_REG_LCD_DEVICE_CMD0 0x23000 +#define QPIC_REG_QPIC_LCDC_FIFO_DATA_PORT0 0x22140 +#define QPIC_REG_QPIC_LCDC_FIFO_EOF 0x22180 + +#define QPIC_OUTP(qpic_display, off, data) \ + writel_relaxed((data), (qpic_display)->qpic_base + (off)) +#define QPIC_OUTPW(qpic_display, off, data) \ + writew_relaxed((data), (qpic_display)->qpic_base + (off)) +#define QPIC_INP(qpic_display, off) \ + readl_relaxed((qpic_display)->qpic_base + (off)) + +#define QPIC_MAX_VSYNC_WAIT_TIME_IN_MS 500 + +#define QPIC_MAX_CMD_BUF_SIZE_IN_BYTES 512 +#define QPIC_PINCTRL_STATE_DEFAULT "qpic_display_default" +#define QPIC_PINCTRL_STATE_SLEEP "qpic_display_sleep" + +/* Structure that defines an SPS end point for a BAM pipe. */ +struct qpic_sps_endpt { + struct sps_pipe *handle; + struct sps_connect config; + struct sps_register_event bam_event; + struct completion completion; +}; + +struct qpic_panel_config { + u32 xres; + u32 yres; + u32 bpp; + u32 type; +}; + +struct qpic_pinctrl_res { + struct pinctrl *pinctrl; + struct pinctrl_state *gpio_state_active; + struct pinctrl_state *gpio_state_suspend; +}; + +struct qpic_panel_io_desc { + int rst_gpio; + int cs_gpio; + int ad8_gpio; + int te_gpio; + int bl_gpio; + struct regulator *vdd_vreg; + struct regulator *avdd_vreg; + struct qpic_pinctrl_res pin_res; +}; + +struct bus_scaling_data { + u64 ab; + u64 ib; +}; + +struct msm_bus_path { + unsigned int num_paths; + struct bus_scaling_data *vec; +}; + +struct qpic_display_bus_scale_pdata { + const char *name; + unsigned int num_usecase; + struct msm_bus_path *usecase; + struct icc_path *data_bus_hdl; + u32 curr_vote; +}; + +struct qpic_display_data { + u32 rev; + struct platform_device *pdev; + struct drm_device drm_dev; + struct drm_simple_display_pipe pipe; + struct drm_connector conn; + struct drm_display_mode drm_mode; + bool pipe_enabled; + + size_t qpic_reg_size; + u32 qpic_phys; + char __iomem *qpic_base; + u32 irq_id; + bool irq_ena; + u32 res_init; + + void *cmd_buf_virt; + phys_addr_t cmd_buf_phys; + struct qpic_sps_endpt qpic_endpt; + u32 sps_init; + u32 irq_requested; + struct qpic_display_bus_scale_pdata *data_bus_pdata; + struct completion fifo_eof_comp; + bool is_qpic_on; + struct clk *qpic_clk; + struct clk *qpic_a_clk; + int (*qpic_transfer)(struct qpic_display_data *qpic_display, + u32 cmd, u8 *param, u32 len); + + bool is_panel_on; + struct qpic_panel_config *panel_config; + struct qpic_panel_io_desc panel_io; + int (*panel_on)(struct qpic_display_data *qpic_display); + void (*panel_off)(struct qpic_display_data *qpic_display); + +}; + +int get_ili_qvga_panel_config(struct qpic_display_data *qpic_display); + +#endif diff --git a/tinydrm/qpic_display_panel.h b/tinydrm/qpic_display_panel.h new file mode 100644 index 000000000000..5b3121d76821 --- /dev/null +++ b/tinydrm/qpic_display_panel.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014-2015, 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __QPIC_DISPLAY_PANEL_H__ +#define __QPIC_DISPLAY_PANEL_H__ + +#include + +/** + * Macros for coding MIPI commands + */ +#define INV_SIZE 0xFFFF +/* Size of argument to MIPI command is variable */ +#define OP_SIZE_PAIR(op, size) ((op<<16) | size) +/* MIPI {command, argument size} tuple */ +#define LCDC_EXTRACT_OP_SIZE(op_identifier) ((op_identifier&0xFFFF)) +/* extract size from command identifier */ +#define LCDC_EXTRACT_OP_CMD(op_identifier) (((op_identifier>>16)&0xFFFF)) +/* extract command id from command identifier */ + +/* MIPI standard efinitions */ +#define LCDC_ADDRESS_MODE_ORDER_BOTTOM_TO_TOP 0x80 +#define LCDC_ADDRESS_MODE_ORDER_RIGHT_TO_LEFT 0x40 +#define LCDC_ADDRESS_MODE_ORDER_REVERSE 0x20 +#define LCDC_ADDRESS_MODE_ORDER_REFRESH_BOTTOM_TO_TOP 0x10 +#define LCDC_ADDRESS_MODE_ORDER_BGER_RGB 0x08 +#define LCDC_ADDRESS_MODE_ORDER_REFERESH_RIGHT_TO_LEFT 0x04 +#define LCDC_ADDRESS_MODE_FLIP_HORIZONTAL 0x02 +#define LCDC_ADDRESS_MODE_FLIP_VERTICAL 0x01 + +#define LCDC_PIXEL_FORMAT_3_BITS_PER_PIXEL 0x1 +#define LCDC_PIXEL_FORMAT_8_BITS_PER_PIXEL 0x2 +#define LCDC_PIXEL_FORMAT_12_BITS_PER_PIXEL 0x3 +#define LCDC_PIXEL_FORMAT_16_BITS_PER_PIXEL 0x5 +#define LCDC_PIXEL_FORMAT_18_BITS_PER_PIXEL 0x6 +#define LCDC_PIXEL_FORMAT_24_BITS_PER_PIXEL 0x7 + +#define LCDC_CREATE_PIXEL_FORMAT(dpi_format, dbi_format) \ + (dpi_format | (dpi_format<<4)) + +#define POWER_MODE_IDLE_ON 0x40 +#define POWER_MODE_PARTIAL_ON 0x20 +#define POWER_MODE_SLEEP_ON 0x10 +#define POWER_MODE_NORMAL_ON 0x08 +#define POWER_MODE_DISPLAY_ON 0x04 + +#define LCDC_DISPLAY_MODE_SCROLLING_ON 0x80 +#define LCDC_DISPLAY_MODE_INVERSION_ON 0x20 +#define LCDC_DISPLAY_MODE_GAMMA_MASK 0x07 + +/** + * LDCc MIPI Type B supported commands + */ +#define OP_ENTER_IDLE_MODE 0x39 +#define OP_ENTER_INVERT_MODE 0x21 +#define OP_ENTER_NORMAL_MODE 0x13 +#define OP_ENTER_PARTIAL_MODE 0x12 +#define OP_ENTER_SLEEP_MODE 0x10 +#define OP_EXIT_INVERT_MODE 0x20 +#define OP_EXIT_SLEEP_MODE 0x11 +#define OP_EXIT_IDLE_MODE 0x38 +#define OP_GET_ADDRESS_MODE 0x0B /* size 1 */ +#define OP_GET_BLUE_CHANNEL 0x08 /* size 1 */ +#define OP_GET_DIAGNOSTIC 0x0F /* size 2 */ +#define OP_GET_DISPLAY_MODE 0x0D /* size 1 */ +#define OP_GET_GREEN_CHANNEL 0x07 /* size 1 */ +#define OP_GET_PIXEL_FORMAT 0x0C /* size 1 */ +#define OP_GET_POWER_MODE 0x0A /* size 1 */ +#define OP_GET_RED_CHANNEL 0x06 /* size 1 */ +#define OP_GET_SCANLINE 0x45 /* size 1 */ +#define OP_GET_SIGNAL_MODE 0x0E /* size 1 */ +#define OP_NOP 0x00 +#define OP_READ_DDB_CONTINUE 0xA8 /* size not fixed */ +#define OP_READ_DDB_START 0xA1 /* size not fixed */ +#define OP_READ_MEMORY_CONTINUE 0x3E /* size not fixed */ +#define OP_READ_MEMORY_START 0x2E /* size not fixed */ +#define OP_SET_ADDRESS_MODE 0x36 /* size 1 */ +#define OP_SET_COLUMN_ADDRESS 0x2A /* size 4 */ +#define OP_SET_DISPLAY_OFF 0x28 +#define OP_SET_DISPLAY_ON 0x29 +#define OP_SET_GAMMA_CURVE 0x26 /* size 1 */ +#define OP_SET_PAGE_ADDRESS 0x2B /* size 4 */ +#define OP_SET_PARTIAL_COLUMNS 0x31 /* size 4 */ +#define OP_SET_PARTIAL_ROWS 0x30 /* size 4 */ +#define OP_SET_PIXEL_FORMAT 0x3A /* size 1 */ +#define OP_SOFT_RESET 0x01 +#define OP_WRITE_MEMORY_CONTINUE 0x3C /* size not fixed */ +#define OP_WRITE_MEMORY_START 0x2C /* size not fixed */ +#endif diff --git a/tinydrm/qpic_panel_ili9341.c b/tinydrm/qpic_panel_ili9341.c new file mode 100644 index 000000000000..757dfd2a7371 --- /dev/null +++ b/tinydrm/qpic_panel_ili9341.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2015, 2021, The Linux Foundation. All rights reserved. + */ + +#include "qpic_display.h" +#include "qpic_display_panel.h" + +/** + * ILI9341 commands + */ +#define OP_ILI9341_INTERFACE_CONTROL 0xf6 +#define OP_ILI9341_TEARING_EFFECT_LINE_ON 0x35 + +static int ili9341_on(struct qpic_display_data *qpic_display) +{ + u8 param[4]; + + qpic_display->qpic_transfer(qpic_display, OP_SOFT_RESET, NULL, 0); + /* wait for 120 ms after reset as panel spec suggests */ + msleep(120); + qpic_display->qpic_transfer(qpic_display, OP_SET_DISPLAY_OFF, NULL, 0); + /* wait for 20 ms after disply off */ + msleep(20); + + /* set memory access control */ + param[0] = 0x48; + qpic_display->qpic_transfer(qpic_display, OP_SET_ADDRESS_MODE, param, 1); + /* wait for 20 ms after command sent as panel spec suggests */ + msleep(20); + + param[0] = 0x66; + qpic_display->qpic_transfer(qpic_display, OP_SET_PIXEL_FORMAT, param, 1); + /* wait for 20 ms after command sent as panel spec suggests */ + msleep(20); + + /* set interface */ + param[0] = 1; + param[1] = 0; + param[2] = 0; + qpic_display->qpic_transfer(qpic_display, OP_ILI9341_INTERFACE_CONTROL, param, 3); + /* wait for 20 ms after command sent */ + msleep(20); + + /* exit sleep mode */ + qpic_display->qpic_transfer(qpic_display, OP_EXIT_SLEEP_MODE, NULL, 0); + /* wait for 20 ms after command sent as panel spec suggests */ + msleep(20); + + /* normal mode */ + qpic_display->qpic_transfer(qpic_display, OP_ENTER_NORMAL_MODE, NULL, 0); + /* wait for 20 ms after command sent as panel spec suggests */ + msleep(20); + + /* display on */ + qpic_display->qpic_transfer(qpic_display, OP_SET_DISPLAY_ON, NULL, 0); + /* wait for 20 ms after command sent as panel spec suggests */ + msleep(20); + + param[0] = 0; + qpic_display->qpic_transfer(qpic_display, OP_ILI9341_TEARING_EFFECT_LINE_ON, param, 1); + + return 0; +} + +static void ili9341_off(struct qpic_display_data *qpic_display) +{ + qpic_display->qpic_transfer(qpic_display, OP_SET_DISPLAY_OFF, NULL, 0); + /* wait for 20 ms after display off */ + msleep(20); +} + +static struct qpic_panel_config ili_qvga_panel = { + .xres = 320, + .yres = 480, + .bpp = 16, +}; + +int get_ili_qvga_panel_config(struct qpic_display_data *qpic_display) +{ + qpic_display->panel_config = &ili_qvga_panel; + qpic_display->panel_on = ili9341_on; + qpic_display->panel_off = ili9341_off; +}