blob: 3e138c24f8863a5e123924abc252c078652d0af3 [file] [log] [blame]
/* Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/leds.h>
#include <linux/pwm.h>
#include <linux/of_device.h>
#include <linux/uaccess.h>
#include "mdss_panel.h"
#include "mdss_spi_panel.h"
#include "mdss_spi_client.h"
#include "mdss_mdp.h"
#include <linux/dma-buf.h>
static int mdss_spi_get_img(struct spi_panel_data *ctrl_pdata,
struct mdp_layer_commit_v1 *commit, struct device *dev)
{
struct msmfb_data image;
struct dma_buf *dmabuf;
void *vaddr;
memset(&image, 0, sizeof(image));
image.memory_id = commit->input_layers[0].buffer.planes[0].fd;
image.offset = commit->input_layers[0].buffer.planes[0].offset;
dmabuf = dma_buf_get(image.memory_id);
if (IS_ERR(dmabuf)) {
pr_err("%s : error on dma_buf_get\n", __func__);
return PTR_ERR(dmabuf);
}
ctrl_pdata->image_data.srcp_attachment =
dma_buf_attach(dmabuf, dev);
if (IS_ERR(ctrl_pdata->image_data.srcp_attachment))
goto err_put;
ctrl_pdata->image_data.srcp_table =
dma_buf_map_attachment(ctrl_pdata->image_data.srcp_attachment,
DMA_TO_DEVICE);
if (IS_ERR(ctrl_pdata->image_data.srcp_table))
goto err_detach;
dma_buf_begin_cpu_access(dmabuf, DMA_TO_DEVICE);
vaddr = dma_buf_kmap(dmabuf, 0);
if (!vaddr) {
pr_err("%s:ion memory mapping failed\n", __func__);
goto err_unmap;
};
ctrl_pdata->image_data.addr = vaddr;
ctrl_pdata->image_data.len = dmabuf->size;
ctrl_pdata->image_data.mapped = true;
ctrl_pdata->image_data.srcp_dma_buf = dmabuf;
return 0;
err_unmap:
dma_buf_unmap_attachment(ctrl_pdata->image_data.srcp_attachment,
ctrl_pdata->image_data.srcp_table, DMA_BIDIRECTIONAL);
err_detach:
dma_buf_detach(ctrl_pdata->image_data.srcp_dma_buf,
ctrl_pdata->image_data.srcp_attachment);
err_put:
dma_buf_put(ctrl_pdata->image_data.srcp_dma_buf);
return -EINVAL;
}
static void mdss_spi_put_img(struct spi_panel_data *ctrl_pdata)
{
if (!ctrl_pdata->image_data.mapped)
return;
dma_buf_kunmap(ctrl_pdata->image_data.srcp_dma_buf, 0,
ctrl_pdata->image_data.addr);
dma_buf_end_cpu_access(ctrl_pdata->image_data.srcp_dma_buf,
DMA_BIDIRECTIONAL);
dma_buf_unmap_attachment(ctrl_pdata->image_data.srcp_attachment,
ctrl_pdata->image_data.srcp_table, DMA_TO_DEVICE);
dma_buf_detach(ctrl_pdata->image_data.srcp_dma_buf,
ctrl_pdata->image_data.srcp_attachment);
dma_buf_put(ctrl_pdata->image_data.srcp_dma_buf);
ctrl_pdata->image_data.srcp_dma_buf = NULL;
ctrl_pdata->image_data.addr = NULL;
ctrl_pdata->image_data.len = 0;
ctrl_pdata->image_data.mapped = false;
}
int mdss_spi_display_pre_commit(struct msm_fb_data_type *mfd,
struct file *file, struct mdp_layer_commit_v1 *commit)
{
char *temp_buf;
int rc = 0, scan_count = 0;
int panel_yres, panel_xres;
int padding_length, byte_per_pixel;
int dma_stride, actual_stride;
struct mdss_panel_data *pdata;
struct spi_panel_data *ctrl_pdata = NULL;
if (commit->input_layer_cnt == 0) {
pr_err("SPI display doesn't support NULL commit\n");
return 0;
}
pdata = dev_get_platdata(&mfd->pdev->dev);
ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
rc = mdss_spi_get_img(ctrl_pdata, commit, &mfd->pdev->dev);
if (rc) {
pr_err("mdss_spi_get_img failed\n");
return rc;
}
panel_xres = ctrl_pdata->panel_data.panel_info.xres;
panel_yres = ctrl_pdata->panel_data.panel_info.yres;
dma_stride = mfd->fbi->fix.line_length;
byte_per_pixel = ctrl_pdata->panel_data.panel_info.bpp / 8;
actual_stride = panel_xres * byte_per_pixel;
padding_length = dma_stride - actual_stride;
/* remove padding and copy to continuous buffer */
while (scan_count < panel_yres) {
memcpy((ctrl_pdata->back_buf + scan_count * actual_stride),
(ctrl_pdata->image_data.addr + scan_count *
(actual_stride + padding_length)), actual_stride);
scan_count++;
}
mdss_spi_put_img(ctrl_pdata);
/* wait for SPI transfer done */
rc = mdss_spi_wait_tx_done(ctrl_pdata);
if (!rc) {
pr_err("SPI transfer timeout\n");
return -EINVAL;
}
/* swap buffer */
temp_buf = ctrl_pdata->front_buf;
ctrl_pdata->front_buf = ctrl_pdata->back_buf;
ctrl_pdata->back_buf = temp_buf;
return 0;
}
int mdss_spi_display_atomic_validate(struct msm_fb_data_type *mfd,
struct file *file, struct mdp_layer_commit_v1 *commit)
{
struct mdss_panel_data *pdata;
pdata = dev_get_platdata(&mfd->pdev->dev);
if ((commit->input_layers->dst_rect.w != pdata->panel_info.xres) &&
(commit->input_layers->dst_rect.h != pdata->panel_info.yres) &&
(commit->input_layer_cnt > 1)) {
WARN_ONCE(1, "%s:Only support GPU composition layer_cnt %d\n",
__func__, commit->input_layer_cnt);
return -EINVAL;
}
if (commit->input_layers[0].buffer.format != MDP_RGB_565) {
WARN_ONCE(1, "%s:SPI display only support RGB565 format %d\n",
__func__, commit->input_layers[0].buffer.format);
return -EINVAL;
}
return 0;
}
int mdss_spi_panel_kickoff(struct msm_fb_data_type *mfd,
struct mdp_display_commit *data)
{
struct spi_panel_data *ctrl_pdata = NULL;
struct mdss_panel_data *pdata;
int rc = 0;
pdata = dev_get_platdata(&mfd->pdev->dev);
if (WARN_ON(!pdata))
return -EINVAL;
ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
enable_spi_panel_te_irq(ctrl_pdata, true);
mutex_lock(&ctrl_pdata->spi_tx_mutex);
reinit_completion(&ctrl_pdata->spi_panel_te);
atomic_inc(&ctrl_pdata->koff_cnt);
rc = wait_for_completion_timeout(&ctrl_pdata->spi_panel_te,
msecs_to_jiffies(SPI_PANEL_TE_TIMEOUT));
if (rc == 0) {
pr_err("wait panel TE time out\n");
mutex_unlock(&ctrl_pdata->spi_tx_mutex);
return rc;
}
rc = mdss_spi_tx_pixel(ctrl_pdata->front_buf,
ctrl_pdata->byte_per_frame,
mdss_spi_tx_fb_complete, ctrl_pdata);
mutex_unlock(&ctrl_pdata->spi_tx_mutex);
enable_spi_panel_te_irq(ctrl_pdata, false);
return rc;
}
static int spi_display_get_metadata(struct msm_fb_data_type *mfd,
struct msmfb_metadata *metadata)
{
int ret = 0;
switch (metadata->op) {
case metadata_op_frame_rate:
metadata->data.panel_frame_rate =
mfd->panel_info->spi.frame_rate;
break;
case metadata_op_get_caps:
metadata->data.caps.mdp_rev = 5;
metadata->data.caps.rgb_pipes = 0;
metadata->data.caps.vig_pipes = 0;
metadata->data.caps.dma_pipes = 1;
break;
default:
pr_warn("Unsupported request to GET META IOCTL %d\n",
metadata->op);
ret = -EINVAL;
break;
}
return ret;
}
static int spi_display_ioctl_handler(struct msm_fb_data_type *mfd,
u32 cmd, void __user *argp)
{
int val, ret = 0;
struct mdss_panel_data *pdata;
struct msmfb_metadata metadata;
pdata = dev_get_platdata(&mfd->pdev->dev);
switch (cmd) {
case MSMFB_OVERLAY_VSYNC_CTRL:
if (!copy_from_user(&val, argp, sizeof(val))) {
mdss_spi_vsync_enable(pdata, val);
} else {
pr_err("overlay vsync ctrl copy from user failed\n");
ret = -EFAULT;
}
break;
case MSMFB_METADATA_GET:
ret = copy_from_user(&metadata, argp, sizeof(metadata));
if (ret) {
pr_err("get metadata from user failed (%d)\n", ret);
break;
}
ret = spi_display_get_metadata(mfd, &metadata);
if (ret) {
pr_err("spi_display_get_metadata failed (%d)\n", ret);
break;
}
ret = copy_to_user(argp, &metadata, sizeof(metadata));
if (ret)
pr_err("copy to user failed (%d)\n", ret);
break;
default:
break;
}
return ret;
}
static int mdss_spi_display_off(struct msm_fb_data_type *mfd)
{
int rc = 0;
struct mdss_panel_data *pdata;
struct spi_panel_data *ctrl_pdata = NULL;
pdata = dev_get_platdata(&mfd->pdev->dev);
ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
ctrl_pdata->ctrl_state &= ~CTRL_STATE_PANEL_ACTIVE;
if (ctrl_pdata->ctrl_state & CTRL_STATE_PANEL_INIT) {
rc = mdss_spi_panel_off(&ctrl_pdata->panel_data);
if (rc) {
pr_err("%s: Panel off failed\n", __func__);
return rc;
}
ctrl_pdata->ctrl_state &= ~CTRL_STATE_PANEL_INIT;
}
rc = mdss_spi_panel_power_ctrl(pdata, MDSS_PANEL_POWER_OFF);
return rc;
}
static int mdss_spi_display_on(struct msm_fb_data_type *mfd)
{
int rc = 0;
struct mdss_panel_data *pdata;
struct spi_panel_data *ctrl_pdata = NULL;
pdata = dev_get_platdata(&mfd->pdev->dev);
ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
rc = mdss_spi_panel_power_ctrl(pdata, MDSS_PANEL_POWER_ON);
if (rc) {
pr_err("%s:Panel power on failed. rc=%d\n",
__func__, rc);
return rc;
}
mdss_spi_panel_pinctrl_set_state(ctrl_pdata, true);
mdss_spi_panel_reset(pdata, 1);
ctrl_pdata->ctrl_state |= CTRL_STATE_PANEL_ACTIVE;
rc = mdss_spi_panel_on(&ctrl_pdata->panel_data);
return rc;
}
u32 mdss_spi_display_fb_stride(u32 fb_index, u32 xres, int bpp)
{
/*
* The adreno GPU hardware requires that the pitch be aligned to
* 32 pixels for color buffers, so for the cases where the GPU
* is writing directly to fb0, the framebuffer pitch
* also needs to be 32 pixels aligned
*/
if (fb_index == 0)
return ALIGN(xres, 32) * bpp;
else
return xres * bpp;
}
ssize_t mdss_spi_show_capabilities(struct device *dev,
struct device_attribute *attr, char *buf)
{
size_t len = PAGE_SIZE;
int cnt = 0;
cnt += scnprintf(buf + cnt, len - cnt, "mdp_version=5\n");
cnt += scnprintf(buf + cnt, len - cnt, "hw_rev=%d\n", 5);
cnt += scnprintf(buf + cnt, len - cnt, "pipe_count:%d\n", 1);
cnt += scnprintf(buf + cnt, len - cnt,
"pipe_num:3 pipe_type:rgb pipe_ndx:8 rects:1 pipe_is_handoff:0"
);
cnt += scnprintf(buf + cnt, len - cnt,
"display_id:0 fmts_supported:51,224,0,22,0,191,248,255,1,");
cnt += scnprintf(buf + cnt, len - cnt,
"0,0,0,0,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\n");
cnt += scnprintf(buf + cnt, len - cnt, "rgb_pipes=%d\n", 0);
cnt += scnprintf(buf + cnt, len - cnt, "vig_pipes=%d\n", 0);
cnt += scnprintf(buf + cnt, len - cnt, "dma_pipes=%d\n", 1);
cnt += scnprintf(buf + cnt, len - cnt, "blending_stages=%d\n", 2);
cnt += scnprintf(buf + cnt, len - cnt, "cursor_pipes=%d\n", 0);
cnt += scnprintf(buf + cnt, len - cnt, "max_cursor_size=%d\n", 0);
cnt += scnprintf(buf + cnt, len - cnt, "smp_count=%d\n", 0);
cnt += scnprintf(buf + cnt, len - cnt, "smp_size=%d\n", 0);
cnt += scnprintf(buf + cnt, len - cnt, "smp_mb_per_pipe=%d\n", 0);
cnt += scnprintf(buf + cnt, len - cnt, "max_bandwidth_low=3100000\n");
cnt += scnprintf(buf + cnt, len - cnt, "max_bandwidth_high=3100000\n");
cnt += scnprintf(buf + cnt, len - cnt, "max_pipe_width=2048\n");
cnt += scnprintf(buf + cnt, len - cnt, "max_mixer_width=2048\n");
cnt += scnprintf(buf + cnt, len - cnt, "max_bandwidth_low=3100000\n");
cnt += scnprintf(buf + cnt, len - cnt, "max_pipe_bw=2300000\n");
cnt += scnprintf(buf + cnt, len - cnt, "max_mdp_clk=320000000\n");
cnt += scnprintf(buf + cnt, len - cnt, "rot_dwnscale_min=1\n");
cnt += scnprintf(buf + cnt, len - cnt, "rot_dwnscale_max=1\n");
cnt += scnprintf(buf + cnt, len - cnt, "max_downscale_ratio=1\n");
cnt += scnprintf(buf + cnt, len - cnt, "max_upscale_ratio=1\n");
return cnt;
}
static ssize_t mdss_spi_vsync_show_event(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct fb_info *fbi = dev_get_drvdata(dev);
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
struct mdss_panel_data *pdata;
struct spi_panel_data *ctrl_pdata = NULL;
int rc = 0;
u64 vsync_ticks;
pdata = dev_get_platdata(&mfd->pdev->dev);
ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
if (!(ctrl_pdata->ctrl_state & CTRL_STATE_PANEL_ACTIVE))
return -EAGAIN;
vsync_ticks = ktime_to_ns(ctrl_pdata->vsync_time);
pr_debug("fb%d vsync=%llu\n", mfd->index, vsync_ticks);
rc = scnprintf(buf, PAGE_SIZE, "VSYNC=%llu\n", vsync_ticks);
return rc;
}
static DEVICE_ATTR(vsync_event, 0444, mdss_spi_vsync_show_event, NULL);
static DEVICE_ATTR(caps, 0444, mdss_spi_show_capabilities, NULL);
static struct attribute *mdp_spi_sysfs_attrs[] = {
&dev_attr_caps.attr,
NULL,
};
static struct attribute *spi_vsync_fs_attr_group[] = {
&dev_attr_vsync_event.attr,
NULL,
};
static struct attribute_group mdp_spi_sysfs_group = {
.attrs = mdp_spi_sysfs_attrs,
};
static struct attribute_group spi_vsync_sysfs_group = {
.attrs = spi_vsync_fs_attr_group,
};
int mdss_spi_overlay_init(struct msm_fb_data_type *mfd)
{
struct msm_mdp_interface *spi_display_interface = &mfd->mdp;
struct device *dev = mfd->fbi->dev;
struct mdss_data_type *spi_mdata;
struct mdss_panel_data *pdata;
struct spi_panel_data *ctrl_pdata = NULL;
int rc = 0;
pdata = dev_get_platdata(&mfd->pdev->dev);
ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
spi_mdata = dev_get_drvdata(mfd->pdev->dev.parent);
if (!spi_mdata) {
pr_err("unable to initialize spi mdata for fb%d\n", mfd->index);
return -ENODEV;
}
spi_display_interface->on_fnc = mdss_spi_display_on;
spi_display_interface->off_fnc = mdss_spi_display_off;
spi_display_interface->do_histogram = NULL;
spi_display_interface->cursor_update = NULL;
spi_display_interface->ioctl_handler = spi_display_ioctl_handler;
spi_display_interface->kickoff_fnc = mdss_spi_panel_kickoff;
spi_display_interface->pre_commit = mdss_spi_display_pre_commit;
spi_display_interface->atomic_validate =
mdss_spi_display_atomic_validate;
spi_display_interface->fb_mem_get_iommu_domain = NULL;
spi_display_interface->fb_stride = mdss_spi_display_fb_stride;
spi_display_interface->fb_mem_alloc_fnc = NULL;
spi_display_interface->check_dsi_status = NULL;
rc = sysfs_create_group(&dev->kobj, &spi_vsync_sysfs_group);
if (rc)
pr_err("spi vsync sysfs group creation failed, ret=%d\n", rc);
rc = sysfs_create_link_nowarn(&dev->kobj,
&spi_mdata->pdev->dev.kobj, "mdp");
ctrl_pdata->vsync_event_sd = sysfs_get_dirent(dev->kobj.sd,
"vsync_event");
if (!ctrl_pdata->vsync_event_sd)
pr_err("spi vsync_event sysfs lookup failed\n");
return rc;
}
static int mdss_spi_display_probe(struct platform_device *pdev)
{
int rc = 0;
struct mdss_data_type *mdata;
static struct msm_mdp_interface spi_display_interface = {
.init_fnc = mdss_spi_overlay_init,
.fb_stride = mdss_spi_display_fb_stride,
};
struct device *dev = &pdev->dev;
if (!pdev->dev.of_node) {
pr_err("spi display driver only supports device tree probe\n");
return -ENOTSUPP;
}
mdata = devm_kzalloc(&pdev->dev, sizeof(*mdata), GFP_KERNEL);
if (mdata == NULL)
return -ENOMEM;
pdev->id = 0;
mdata->pdev = pdev;
platform_set_drvdata(pdev, mdata);
rc = mdss_fb_register_mdp_instance(&spi_display_interface);
if (rc) {
pr_err("unable to register SPI display instance\n");
return rc;
}
rc = sysfs_create_group(&dev->kobj, &mdp_spi_sysfs_group);
if (rc) {
pr_err("spi vsync sysfs group creation failed, ret=%d\n", rc);
return rc;
}
return 0;
}
static const struct of_device_id mdss_spi_display_match[] = {
{ .compatible = "qcom,mdss-spi-panel" },
{},
};
static struct platform_driver this_driver = {
.probe = mdss_spi_display_probe,
.driver = {
.name = "spi_display",
.owner = THIS_MODULE,
.of_match_table = mdss_spi_display_match,
},
};
static int __init mdss_spi_panel_init(void)
{
int ret;
ret = platform_driver_register(&this_driver);
return ret;
}
module_init(mdss_spi_panel_init);
MODULE_LICENSE("GPL v2");
MODULE_DEVICE_TABLE(of, mdss_spi_display_match);