blob: 505760eb6d33b158fc9c97f52d59e02eaf79865b [file]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/seq_file.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic_uapi.h>
#include <drm/drmP.h>
#include <drm/drm_modeset_lock.h>
#include <linux/kernel.h>
#include "meson_sysfs.h"
#include "meson_crtc.h"
#include "meson_plane.h"
#include "meson_hdmi.h"
#include "meson_vpu_pipeline.h"
static const char vpu_group_name[] = "vpu";
static const char module_param_group_name[] = "module_param";
static const char osd0_group_name[] = "osd0";
static const char osd1_group_name[] = "osd1";
static const char osd2_group_name[] = "osd2";
static const char osd3_group_name[] = "osd3";
int osd_index[MESON_MAX_OSDS] = {0, 1, 2, 3};
static const char crtc0_group_name[] = "crtc0";
static const char crtc1_group_name[] = "crtc1";
static const char crtc2_group_name[] = "crtc2";
int crtc_index[MESON_MAX_POSTBLEND] = {0, 1, 2};
u32 pages;
u32 overwrite_reg[256];
u32 overwrite_val[256];
int overwrite_enable;
int reg_num;
#define AM_BIN_ATTR_MOD_PARAM_W_DEF(_name) \
static ssize_t _name##_store(struct file *filp, struct kobject *kobj, \
struct bin_attribute *attr, char *buf, loff_t off, \
size_t count) \
{\
struct device *dev = kobj_to_dev(kobj); \
struct drm_minor *minor = dev_get_drvdata(dev); \
struct meson_drm *priv; \
int val; \
\
if (!minor || !minor->dev) \
return -EINVAL; \
\
priv = minor->dev->dev_private; \
\
if (kstrtouint(buf, 10, &val) < 0) \
return -EINVAL; \
\
am_drm_param._name = val; \
\
return count; \
}
#define AM_BIN_ATTR_MOD_PARAM_R_DEF(_name) \
static ssize_t _name##_show(struct file *filp, struct kobject *kobj, \
struct bin_attribute *attr, char *buf, loff_t off, \
size_t count) \
{\
struct device *dev = kobj_to_dev(kobj); \
struct drm_minor *minor = dev_get_drvdata(dev); \
struct meson_drm *priv; \
int pos = 0; \
\
if (!minor || !minor->dev) \
return -EINVAL; \
if (off > 0) \
return 0; \
\
priv = minor->dev->dev_private; \
\
pos += snprintf(buf + pos, PAGE_SIZE - pos,\
__stringify(_name) ": %d\n", am_drm_param._name); \
\
return pos;\
}
#define _AM_BIN_ATTR(_name, _mode, _show, _store) \
{\
.attr = { .name = __stringify(_name), .mode = _mode },\
.read = _show, \
.write = _store, \
},
#define AM_BIN_ATTR_MOD_PARAM_RW(_name) \
_AM_BIN_ATTR(_name, 0644, _name##_show, _name##_store)
//EXPORT_SYMBOL_GPL(vpu_group_name);
static u8 *am_meson_drm_vmap(ulong addr, u32 size, bool *bflg)
{
u8 *vaddr = NULL;
ulong phys = addr;
u32 offset = phys & ~PAGE_MASK;
u32 npages = PAGE_ALIGN(size) / PAGE_SIZE;
struct page **pages = NULL;
pgprot_t pgprot;
int i;
if (!PageHighMem(phys_to_page(phys)))
return phys_to_virt(phys);
if (offset)
npages++;
pages = kcalloc(npages, sizeof(struct page *), GFP_KERNEL);
if (!pages)
return NULL;
for (i = 0; i < npages; i++) {
pages[i] = phys_to_page(phys);
phys += PAGE_SIZE;
}
pgprot = PAGE_KERNEL;
vaddr = vmap(pages, npages, VM_MAP, pgprot);
if (!vaddr) {
pr_err("the phy(%lx) vmap fail, size: %d\n",
addr - offset, npages << PAGE_SHIFT);
kfree(pages);
return NULL;
}
kfree(pages);
DRM_DEBUG("map high mem pa(%lx) to va(%p), size: %d\n",
addr, vaddr + offset, npages << PAGE_SHIFT);
*bflg = true;
return vaddr + offset;
}
static void am_meson_drm_unmap_phyaddr(u8 *vaddr)
{
void *addr = (void *)(PAGE_MASK & (ulong)vaddr);
DRM_DEBUG("unmap va(%p)\n", addr);
vunmap(addr);
}
static void parse_param(char *buf_orig, char **parm)
{
char *ps, *token;
unsigned int n = 0;
char delim1[3] = " ";
char delim2[2] = "\n";
ps = buf_orig;
strcat(delim1, delim2);
while (1) {
token = strsep(&ps, delim1);
if (!token)
break;
if (*token == '\0')
continue;
parm[n++] = token;
}
}
static ssize_t debug_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
int i, pos = 0;
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo rv reg > debug to read the register\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo wv reg val > debug to overwrite the register\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo wvb reg val start lens > debug to overwrite the specific bits in register\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo ow 1 > debug to enable overwrite register\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"\noverwrote status: %s\n", overwrite_enable ? "on" : "off");
if (overwrite_enable) {
for (i = 0; i < reg_num; i++)
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"reg[0x%04x]=0x%08x\n", overwrite_reg[i], overwrite_val[i]);
}
return pos;
}
static ssize_t debug_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t n)
{
char dst_buf[64];
long val;
int i;
unsigned int reg_addr, reg_val, tmp_val, read_val, start, len;
char *bufp, *parm[8] = {NULL};
int lens = strlen(buf);
if (lens > sizeof(dst_buf) - 1)
return -EINVAL;
memcpy(dst_buf, buf, lens);
dst_buf[lens] = '\0';
bufp = dst_buf;
parse_param(bufp, (char **)&parm);
if (!strcmp(parm[0], "rv")) {
if (kstrtoul(parm[1], 16, &val) < 0)
return -EINVAL;
reg_addr = val;
DRM_INFO("reg[0x%04x]=0x%08x\n", reg_addr, meson_drm_read_reg(reg_addr));
} else if (!strcmp(parm[0], "wv")) {
if (kstrtoul(parm[1], 16, &val) < 0)
return -EINVAL;
reg_addr = val;
if (kstrtoul(parm[2], 16, &val) < 0)
return -EINVAL;
reg_val = val;
for (i = 0; i < reg_num; i++) {
if (overwrite_reg[i] == reg_addr) {
overwrite_val[i] = reg_val;
return lens;
}
}
if (i == reg_num) {
overwrite_reg[i] = reg_addr;
overwrite_val[i] = reg_val;
reg_num++;
}
} else if (!strcmp(parm[0], "ow")) {
if (parm[1] && !strcmp(parm[1], "1")) {
overwrite_enable = 1;
} else if (parm[1] && !strcmp(parm[1], "0")) {
overwrite_enable = 0;
for (i = 0; i < reg_num; i++) {
overwrite_val[i] = 0;
overwrite_val[i] = 0;
}
reg_num = 0;
}
} else if (!strcmp(parm[0], "wvb")) {
if (kstrtoul(parm[1], 16, &val) < 0)
return -EINVAL;
reg_addr = val;
if (kstrtouint(parm[2], 10, &tmp_val) < 0)
return -EINVAL;
if (kstrtouint(parm[3], 10, &start) < 0)
return -EINVAL;
if (kstrtouint(parm[4], 10, &len) < 0)
return -EINVAL;
read_val = meson_drm_read_reg(reg_addr);
reg_val = (read_val & ~(((1L << (len)) - 1) << (start))) |
((unsigned int)(tmp_val) << (start));
for (i = 0; i < reg_num; i++) {
if (overwrite_reg[i] == reg_addr) {
overwrite_val[i] = reg_val;
return lens;
}
}
if (i == reg_num) {
overwrite_reg[i] = reg_addr;
overwrite_val[i] = reg_val;
reg_num++;
}
}
return n;
}
static DEVICE_ATTR_RW(debug);
static struct attribute *vpu_attrs[] = {
&dev_attr_debug.attr,
NULL,
};
static const struct attribute_group vpu_attr_group = {
.name = vpu_group_name,
.attrs = vpu_attrs,
};
static ssize_t osd_pixel_blend_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct meson_drm *priv;
struct am_osd_plane *amp;
int pos = 0;
if (!minor || !minor->dev)
return -EINVAL;
if (off > 0)
return 0;
priv = minor->dev->dev_private;
amp = priv->osd_planes[*(int *)attr->private];
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"cat pixel_blend to show blend mask\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo <value> > pixel_blend to set blend mask\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"pixel blend mask: 0x%x\n", amp->pixel_blend_debug);
return pos;
}
static ssize_t osd_pixel_blend_store(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
int idx = *(int *)attr->private;
struct meson_drm *priv;
struct am_osd_plane *amp;
if (!minor || !minor->dev)
return -EINVAL;
priv = minor->dev->dev_private;
amp = priv->osd_planes[idx];
if (kstrtou16(buf, 16, &amp->pixel_blend_debug) < 0)
return -EINVAL;
DRM_INFO("Set pixel blend mask to %s\n", buf);
return count;
}
static ssize_t osd_reverse_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct meson_drm *priv;
struct am_osd_plane *amp;
int pos = 0;
if (!minor || !minor->dev)
return -EINVAL;
if (off > 0)
return 0;
priv = minor->dev->dev_private;
amp = priv->osd_planes[*(int *)attr->private];
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo 1/2/3 > osd_reverse :reverse the osd xy/x/y\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo 0 > osd_reverse to un_reverse the osd plane\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"osd_reverse: %d\n", amp->osd_reverse);
return pos;
}
static ssize_t osd_reverse_store(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
int idx = *(int *)attr->private;
struct meson_drm *priv;
struct am_osd_plane *amp;
if (!minor || !minor->dev)
return -EINVAL;
priv = minor->dev->dev_private;
amp = priv->osd_planes[idx];
if (sysfs_streq(buf, "0")) {
amp->osd_reverse = 0;
DRM_INFO("disable the osd reverse\n");
} else if (sysfs_streq(buf, "1")) {
amp->osd_reverse = DRM_MODE_REFLECT_MASK;
DRM_INFO("enable the osd reverse\n");
} else if (sysfs_streq(buf, "2")) {
amp->osd_reverse = DRM_MODE_REFLECT_X;
DRM_INFO("enable the osd reverse_x\n");
} else if (sysfs_streq(buf, "3")) {
amp->osd_reverse = DRM_MODE_REFLECT_Y;
DRM_INFO("enable the osd reverse_y\n");
} else {
return -EINVAL;
}
return count;
}
static ssize_t osd_blend_bypass_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct meson_drm *priv;
struct am_osd_plane *amp;
int pos = 0;
if (!minor || !minor->dev)
return -EINVAL;
if (off > 0)
return 0;
priv = minor->dev->dev_private;
amp = priv->osd_planes[*(int *)attr->private];
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo 1/0 > osd_blend_bypass :enable/disable\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"osd_blend_bypass: %d\n", amp->osd_blend_bypass);
return pos;
}
static ssize_t osd_blend_bypass_store(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct meson_drm *priv;
struct am_osd_plane *amp;
if (!minor || !minor->dev)
return -EINVAL;
priv = minor->dev->dev_private;
amp = priv->osd_planes[*(int *)attr->private];
if (sysfs_streq(buf, "1")) {
amp->osd_blend_bypass = 1;
DRM_INFO("enable the osd blend bypass\n");
} else if (sysfs_streq(buf, "0")) {
amp->osd_blend_bypass = 0;
DRM_INFO("disable the osd blend bypass\n");
}
return count;
}
static ssize_t osd_read_port_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct meson_drm *priv;
struct am_osd_plane *amp;
int pos = 0;
if (!minor || !minor->dev)
return -EINVAL;
if (off > 0)
return 0;
priv = minor->dev->dev_private;
amp = priv->osd_planes[*(int *)attr->private];
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo 1 > enable read port setting\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo 0 > disable read port setting\n");
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"\nstatus: %d\n", (amp->osd_read_ports == 1) ? 1 : 0);
return pos;
}
static ssize_t osd_read_port_store(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct meson_drm *priv;
struct am_osd_plane *amp;
long val;
if (!minor || !minor->dev)
return -EINVAL;
priv = minor->dev->dev_private;
amp = priv->osd_planes[*(int *)attr->private];
if (kstrtoul(buf, 16, &val) < 0)
return -EINVAL;
val = val >= 1 ? 1 : 0;
amp->osd_read_ports = val;
return count;
}
static ssize_t osd_fbdump_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct meson_drm *priv;
struct am_osd_plane *amp;
bool bflg;
u32 fb_size;
void *vir_addr;
u64 phy_addr;
struct meson_vpu_pipeline *pipeline;
struct meson_vpu_osd_layer_info *info;
struct meson_vpu_pipeline_state *mvps;
u32 num_pages;
if (!minor || !minor->dev)
return -EINVAL;
priv = minor->dev->dev_private;
amp = priv->osd_planes[*(int *)attr->private];
pipeline = priv->pipeline;
mvps = priv_to_pipeline_state(pipeline->obj.state);
info = &mvps->plane_info[*(int *)attr->private];
if (!info->enable) {
DRM_INFO("osd is disabled\n");
return 0;
}
phy_addr = info->phy_addr;
fb_size = info->fb_size;
bflg = false;
if (pages == 0 && off < fb_size) {
vir_addr = am_meson_drm_vmap(phy_addr, fb_size, &bflg);
amp->bflg = bflg;
amp->vir_addr = vir_addr;
amp->dump_size = fb_size;
}
if (!amp->vir_addr) {
DRM_INFO("vmap failed, vir_addr is null\n");
return -EINVAL;
}
num_pages = PAGE_ALIGN(amp->dump_size) / PAGE_SIZE;
pages++;
if (pages <= num_pages && off < amp->dump_size) {
memcpy(buf, amp->vir_addr + off, count);
if (pages == num_pages && amp->bflg)
am_meson_drm_unmap_phyaddr(amp->vir_addr);
return count;
}
if (off >= amp->dump_size)
pages = 0;
return 0;
}
static ssize_t osd_fbdump_store(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
return count;
}
static ssize_t osd_blank_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct meson_drm *priv;
int osd_index = *(int *)attr->private;
int pos = 0;
if (!minor || !minor->dev)
return -EINVAL;
if (off > 0)
return 0;
priv = minor->dev->dev_private;
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo 1 > enable osd-%d blank\n", *(int *)attr->private);
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo 0 > disable osd-%d blank\n", *(int *)attr->private);
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"osd-%d blank status is %d\n", *(int *)attr->private,
priv->osd_planes[osd_index]->osd_permanent_blank);
return pos;
}
static ssize_t osd_blank_store(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
int osd_index = *(int *)attr->private;
struct meson_drm *priv;
struct drm_modeset_acquire_ctx ctx;
struct drm_atomic_state *state;
int err;
if (!minor || !minor->dev)
return -EINVAL;
if (buf[0] != '0' && buf[0] != '1')
return -EINVAL;
priv = minor->dev->dev_private;
state = ERR_PTR(-EINVAL);
if (buf[0] == '1') {
priv->osd_planes[osd_index]->osd_permanent_blank = 1;
DRM_INFO("osd-%d enable blank\n", osd_index);
} else if (buf[0] == '0') {
priv->osd_planes[osd_index]->osd_permanent_blank = 0;
DRM_INFO("osd-%d disable blank\n", osd_index);
} else {
return -EINVAL;
}
DRM_MODESET_LOCK_ALL_BEGIN(minor->dev, ctx, 0, err);
state = drm_atomic_helper_duplicate_state(minor->dev, &ctx);
if (IS_ERR(state))
return -EFAULT;
err = drm_atomic_helper_commit_duplicated_state(state, &ctx);
DRM_MODESET_LOCK_ALL_END(minor->dev, ctx, err);
if (IS_ERR(state))
return -EFAULT;
drm_atomic_state_put(state);
return count;
}
static ssize_t state_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct drm_print_iterator iter;
struct drm_printer p;
ssize_t ret;
iter.data = buf;
iter.start = off;
iter.remain = count;
p = drm_coredump_printer(&iter);
drm_state_dump(minor->dev, &p);
ret = count - iter.remain;
return ret;
}
static ssize_t reg_dump_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct drm_print_iterator iter;
struct drm_printer p;
struct meson_drm *priv;
struct am_meson_crtc *amc;
struct meson_vpu_pipeline *mvp1;
struct meson_vpu_block *mvb;
int i;
ssize_t ret;
iter.data = buf;
iter.start = off;
iter.remain = count;
p = drm_coredump_printer(&iter);
if (!minor || !minor->dev)
return -EINVAL;
priv = minor->dev->dev_private;
amc = priv->crtcs[0];
mvp1 = amc->pipeline;
for (i = 0; i < MESON_MAX_BLOCKS; i++) {
mvb = mvp1->mvbs[i];
if (!mvb)
continue;
drm_printf(&p, "*************%s*************\n", mvb->name);
if (mvb->ops && mvb->ops->dump_register)
mvb->ops->dump_register(&p, mvb);
}
ret = count - iter.remain;
return ret;
}
static ssize_t crtc_blank_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct drm_crtc *crtc;
struct am_meson_crtc *amc;
int crtc_index = *(int *)attr->private;
int pos = 0;
if (!minor || !minor->dev)
return -EINVAL;
crtc = drm_crtc_from_index(minor->dev, crtc_index);
if (!crtc)
return -EINVAL;
amc = to_am_meson_crtc(crtc);
if (off > 0)
return 0;
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo 1 > crtc_blank enable crtc-%d blank\n", crtc_index);
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"echo 0 > crtc_blank disable crtc-%d blank\n", crtc_index);
pos += snprintf(buf + pos, PAGE_SIZE - pos,
"crtc-%d blank status is %d\n", crtc_index,
amc->blank_enable);
return pos;
}
static ssize_t crtc_blank_store(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev);
struct drm_crtc *crtc;
struct am_meson_crtc *amc;
int crtc_index = *(int *)attr->private;
struct drm_modeset_acquire_ctx ctx;
struct drm_atomic_state *state;
int err;
if (!minor || !minor->dev)
return -EINVAL;
crtc = drm_crtc_from_index(minor->dev, crtc_index);
if (!crtc)
return -EINVAL;
amc = to_am_meson_crtc(crtc);
state = ERR_PTR(-EINVAL);
if (buf[0] != '0' && buf[0] != '1')
return -EINVAL;
if (buf[0] == '1') {
amc->blank_enable = 1;
DRM_INFO("crtc-%d enable blank\n", crtc_index);
} else if (buf[0] == '0') {
amc->blank_enable = 0;
DRM_INFO("crtc-%d disable blank\n", crtc_index);
} else {
return -EINVAL;
}
DRM_MODESET_LOCK_ALL_BEGIN(minor->dev, ctx, 0, err);
state = drm_atomic_helper_duplicate_state(minor->dev, &ctx);
if (IS_ERR(state))
return -EFAULT;
err = drm_atomic_helper_commit_duplicated_state(state, &ctx);
DRM_MODESET_LOCK_ALL_END(minor->dev, ctx, err);
if (IS_ERR(state))
return -EFAULT;
drm_atomic_state_put(state);
return count;
}
struct drm_atomic_state *
meson_drm_duplicate_state(struct drm_device *dev, struct drm_modeset_acquire_ctx *ctx,
struct drm_crtc *dst_crtc, struct drm_connector *dst_conn,
const struct drm_display_mode *mode)
{
struct drm_atomic_state *state;
struct drm_connector *conn;
struct drm_connector_state *conn_state;
struct drm_connector_list_iter conn_iter;
struct drm_plane *plane;
struct drm_plane_state *plane_state;
struct drm_crtc *crtc;
struct drm_crtc_state *crtc_state;
int hdisplay, vdisplay;
int ret, err = 0;
state = drm_atomic_state_alloc(dev);
if (!state)
return ERR_PTR(-ENOMEM);
state->acquire_ctx = ctx;
state->duplicated = true;
drm_for_each_crtc(crtc, dev) {
if (crtc != dst_crtc)
continue;
crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state)) {
DRM_ERROR("%s, %d, get_crtc_state error\n", __func__, __LINE__);
err = PTR_ERR(crtc_state);
goto free;
}
ret = drm_atomic_set_mode_for_crtc(crtc_state, mode);
if (ret != 0) {
DRM_ERROR("%s, %d, set_mode_for_crtc error\n", __func__, __LINE__);
goto free;
}
crtc_state->active = true;
}
drm_mode_get_hv_timing(mode, &hdisplay, &vdisplay);
drm_for_each_plane(plane, dev) {
plane_state = drm_atomic_get_plane_state(state, plane);
if (IS_ERR(plane_state)) {
err = PTR_ERR(plane_state);
DRM_ERROR("%s, %d, get_plane_state error\n", __func__, __LINE__);
goto free;
}
plane_state->crtc_x = 0;
plane_state->crtc_y = 0;
plane_state->crtc_w = hdisplay;
plane_state->crtc_h = vdisplay;
}
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(conn, &conn_iter) {
if (conn != dst_conn)
continue;
conn_state = drm_atomic_get_connector_state(state, conn);
if (IS_ERR(conn_state)) {
err = PTR_ERR(conn_state);
drm_connector_list_iter_end(&conn_iter);
DRM_ERROR("%s, %d, get_conn_state error\n", __func__, __LINE__);
goto free;
}
ret = drm_atomic_set_crtc_for_connector(conn_state, dst_crtc);
if (ret != 0) {
DRM_ERROR("%s, %d, set_crtc_for_connector error\n", __func__, __LINE__);
goto free;
}
}
drm_connector_list_iter_end(&conn_iter);
/* clear the acquire context so that it isn't accidentally reused */
state->acquire_ctx = NULL;
free:
if (err < 0) {
drm_atomic_state_put(state);
state = ERR_PTR(err);
}
return state;
}
static ssize_t crtc_mode_store(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
int found, num_modes, ret = 0;
char mode_name[DRM_DISPLAY_MODE_LEN];
struct drm_display_mode *mode;
struct drm_connector *connector;
struct drm_modeset_acquire_ctx *ctx;
struct drm_crtc *crtc;
struct am_meson_crtc *am_crtc;
struct device *dev_ = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev_);
struct drm_device *dev = minor->dev;
struct meson_drm *private = dev->dev_private;
int crtc_index = *(int *)attr->private;
struct drm_atomic_state *state;
crtc = &private->crtcs[crtc_index]->base;
am_crtc = to_am_meson_crtc(crtc);
memset(mode_name, 0, DRM_DISPLAY_MODE_LEN);
memcpy(mode_name, buf, (strlen(buf) < DRM_DISPLAY_MODE_LEN) ?
strlen(buf) - 1 : DRM_DISPLAY_MODE_LEN - 1);
DRM_INFO("drm set mode to %s\n", mode_name);
/*init all connector and found matched uboot mode.*/
found = 0;
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
drm_modeset_lock_all(dev);
if (drm_modeset_is_locked(&dev->mode_config.connection_mutex))
drm_modeset_unlock(&dev->mode_config.connection_mutex);
num_modes = connector->funcs->fill_modes(connector,
dev->mode_config.max_width,
dev->mode_config.max_height);
drm_modeset_unlock_all(dev);
if (num_modes) {
list_for_each_entry(mode, &connector->modes, head) {
if (!strcmp(mode->name, mode_name)) {
found = 1;
break;
}
}
if (found)
break;
}
DRM_DEBUG("Connector[%d] status[%d], %d\n",
connector->connector_type, connector->status, found);
}
if (found) {
DRM_INFO("Found Connector[%d] mode[%s]\n",
connector->connector_type, mode->name);
} else {
DRM_INFO("No Found mode[%s]\n", mode_name);
if (!strcmp("null", mode_name)) {
drm_atomic_helper_shutdown(dev);
return count;
}
}
drm_modeset_lock_all(dev);
ctx = dev->mode_config.acquire_ctx;
state = meson_drm_duplicate_state(dev, ctx, crtc, connector, mode);
ret = drm_atomic_helper_commit_duplicated_state(state, ctx);
drm_modeset_unlock_all(dev);
return count;
}
static ssize_t crtc_mode_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
int pos = 0;
struct drm_crtc *crtc;
struct am_meson_crtc *am_crtc;
struct drm_display_mode *mode;
struct device *dev_ = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev_);
struct drm_device *dev = minor->dev;
struct meson_drm *private = dev->dev_private;
int crtc_index = *(int *)attr->private;
crtc = &private->crtcs[crtc_index]->base;
am_crtc = to_am_meson_crtc(crtc);
mode = &crtc->state->adjusted_mode;
if (off > 0)
return 0;
pos += snprintf(buf + pos, PAGE_SIZE - pos, "%s\n", mode->name);
return pos;
}
static ssize_t hdmitx_attr_store(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
char attr_str[16];
int cs, cd;
struct drm_crtc *crtc;
struct drm_connector *conn;
struct drm_connector_list_iter conn_iter;
struct am_hdmitx_connector_state *am_conn_state;
struct device *dev_ = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev_);
struct drm_device *dev = minor->dev;
struct meson_drm *private = dev->dev_private;
int crtc_index = *(int *)attr->private;
bool found = false;
crtc = &private->crtcs[crtc_index]->base;
memset(attr_str, 0, sizeof(attr_str));
memcpy(attr_str, buf, (strlen(buf) < sizeof(attr_str)) ?
strlen(buf) - 1 : sizeof(attr_str) - 1);
if (strstr(attr_str, "420"))
cs = HDMI_COLORSPACE_YUV420;
else if (strstr(attr_str, "422"))
cs = HDMI_COLORSPACE_YUV422;
else if (strstr(attr_str, "444"))
cs = HDMI_COLORSPACE_YUV444;
else if (strstr(attr_str, "rgb"))
cs = HDMI_COLORSPACE_RGB;
else
cs = HDMI_COLORSPACE_YUV444;
/*parse colorspace success*/
if (strstr(attr_str, "12bit"))
cd = 12;
else if (strstr(attr_str, "10bit"))
cd = 10;
else if (strstr(attr_str, "8bit"))
cd = 8;
else
cd = 8;
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(conn, &conn_iter) {
if (conn->connector_type != DRM_MODE_CONNECTOR_HDMIA)
continue;
if (!conn->state)
continue;
if (conn->state->crtc == crtc) {
found = true;
break;
}
}
drm_connector_list_iter_end(&conn_iter);
if (found) {
am_conn_state = to_am_hdmitx_connector_state(conn->state);
am_conn_state->color_attr_para.colorformat = cs;
am_conn_state->color_attr_para.bitdepth = cd;
DRM_INFO("%s set cs-%d, cd-%d\n", __func__, cs, cd);
} else {
DRM_INFO("%s not found hdmi state\n", __func__);
}
return count;
}
static ssize_t hdmitx_attr_show(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
int pos = 0;
const char *colorspace;
int cs, cd;
struct drm_crtc *crtc;
struct drm_connector *conn;
struct drm_connector_list_iter conn_iter;
struct am_hdmitx_connector_state *am_conn_state;
struct device *dev_ = kobj_to_dev(kobj);
struct drm_minor *minor = dev_get_drvdata(dev_);
struct drm_device *dev = minor->dev;
struct meson_drm *private = dev->dev_private;
int crtc_index = *(int *)attr->private;
crtc = &private->crtcs[crtc_index]->base;
if (off > 0)
return 0;
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(conn, &conn_iter) {
if (conn->connector_type != DRM_MODE_CONNECTOR_HDMIA)
continue;
if (!conn->state)
continue;
if (conn->state->crtc == crtc)
break;
}
drm_connector_list_iter_end(&conn_iter);
am_conn_state = to_am_hdmitx_connector_state(conn->state);
cs = am_conn_state->color_attr_para.colorformat;
cd = am_conn_state->color_attr_para.bitdepth;
switch (cs) {
case HDMI_COLORSPACE_YUV420:
colorspace = "420";
break;
case HDMI_COLORSPACE_YUV422:
colorspace = "422";
break;
case HDMI_COLORSPACE_YUV444:
colorspace = "444";
break;
case HDMI_COLORSPACE_RGB:
colorspace = "rgb";
break;
default:
colorspace = "rgb";
DRM_ERROR("Unknown colospace value %d\n", cs);
break;
};
pos += snprintf(buf + pos, PAGE_SIZE - pos, "%s,%dbit\n", colorspace, cd);
return pos;
}
static struct bin_attribute osd0_attr[] = {
{
.attr.name = "osd_reverse",
.attr.mode = 0664,
.private = &osd_index[0],
.read = osd_reverse_show,
.write = osd_reverse_store,
},
{
.attr.name = "osd_blend_bypass",
.attr.mode = 0664,
.private = &osd_index[0],
.read = osd_blend_bypass_show,
.write = osd_blend_bypass_store,
},
{
.attr.name = "osd_read_port",
.attr.mode = 0664,
.private = &osd_index[0],
.read = osd_read_port_show,
.write = osd_read_port_store,
},
{
.attr.name = "fbdump",
.attr.mode = 0664,
.private = &osd_index[0],
.read = osd_fbdump_show,
.write = osd_fbdump_store,
.size = 36864000,
},
{
.attr.name = "blank",
.attr.mode = 0664,
.private = &osd_index[0],
.read = osd_blank_show,
.write = osd_blank_store,
},
{
.attr.name = "pixel_blend",
.attr.mode = 0664,
.private = &osd_index[0],
.read = osd_pixel_blend_show,
.write = osd_pixel_blend_store,
},
};
static struct bin_attribute *osd0_bin_attrs[] = {
&osd0_attr[0],
&osd0_attr[1],
&osd0_attr[2],
&osd0_attr[3],
&osd0_attr[4],
&osd0_attr[5],
NULL,
};
static struct bin_attribute osd1_attr[] = {
{
.attr.name = "osd_reverse",
.attr.mode = 0664,
.private = &osd_index[1],
.read = osd_reverse_show,
.write = osd_reverse_store,
},
{
.attr.name = "osd_blend_bypass",
.attr.mode = 0664,
.private = &osd_index[1],
.read = osd_blend_bypass_show,
.write = osd_blend_bypass_store,
},
{
.attr.name = "osd_read_port",
.attr.mode = 0664,
.private = &osd_index[1],
.read = osd_read_port_show,
.write = osd_read_port_store,
},
{
.attr.name = "fbdump",
.attr.mode = 0664,
.private = &osd_index[1],
.read = osd_fbdump_show,
.write = osd_fbdump_store,
.size = 36864000,
},
{
.attr.name = "blank",
.attr.mode = 0664,
.private = &osd_index[1],
.read = osd_blank_show,
.write = osd_blank_store,
},
{
.attr.name = "pixel_blend",
.attr.mode = 0664,
.private = &osd_index[1],
.read = osd_pixel_blend_show,
.write = osd_pixel_blend_store,
},
};
static struct bin_attribute *osd1_bin_attrs[] = {
&osd1_attr[0],
&osd1_attr[1],
&osd1_attr[2],
&osd1_attr[3],
&osd1_attr[4],
&osd1_attr[5],
NULL,
};
static struct bin_attribute osd2_attr[] = {
{
.attr.name = "osd_reverse",
.attr.mode = 0664,
.private = &osd_index[2],
.read = osd_reverse_show,
.write = osd_reverse_store,
},
{
.attr.name = "osd_blend_bypass",
.attr.mode = 0664,
.private = &osd_index[2],
.read = osd_blend_bypass_show,
.write = osd_blend_bypass_store,
},
{
.attr.name = "osd_read_port",
.attr.mode = 0664,
.private = &osd_index[2],
.read = osd_read_port_show,
.write = osd_read_port_store,
},
{
.attr.name = "fbdump",
.attr.mode = 0664,
.private = &osd_index[2],
.read = osd_fbdump_show,
.write = osd_fbdump_store,
.size = 36864000,
},
{
.attr.name = "blank",
.attr.mode = 0664,
.private = &osd_index[2],
.read = osd_blank_show,
.write = osd_blank_store,
},
{
.attr.name = "pixel_blend",
.attr.mode = 0664,
.private = &osd_index[2],
.read = osd_pixel_blend_show,
.write = osd_pixel_blend_store,
},
};
static struct bin_attribute *osd2_bin_attrs[] = {
&osd2_attr[0],
&osd2_attr[1],
&osd2_attr[2],
&osd2_attr[3],
&osd2_attr[4],
&osd2_attr[5],
NULL,
};
static struct bin_attribute osd3_attr[] = {
{
.attr.name = "osd_reverse",
.attr.mode = 0664,
.private = &osd_index[3],
.read = osd_reverse_show,
.write = osd_reverse_store,
},
{
.attr.name = "osd_blend_bypass",
.attr.mode = 0664,
.private = &osd_index[3],
.read = osd_blend_bypass_show,
.write = osd_blend_bypass_store,
},
{
.attr.name = "osd_read_port",
.attr.mode = 0664,
.private = &osd_index[3],
.read = osd_read_port_show,
.write = osd_read_port_store,
},
{
.attr.name = "fbdump",
.attr.mode = 0664,
.private = &osd_index[3],
.read = osd_fbdump_show,
.write = osd_fbdump_store,
.size = 36864000,
},
{
.attr.name = "blank",
.attr.mode = 0664,
.private = &osd_index[3],
.read = osd_blank_show,
.write = osd_blank_store,
},
{
.attr.name = "pixel_blend",
.attr.mode = 0664,
.private = &osd_index[3],
.read = osd_pixel_blend_show,
.write = osd_pixel_blend_store,
},
};
static struct bin_attribute *osd3_bin_attrs[] = {
&osd3_attr[0],
&osd3_attr[1],
&osd3_attr[2],
&osd3_attr[3],
&osd3_attr[4],
&osd3_attr[5],
NULL,
};
static const struct attribute_group osd_attr_group[MESON_MAX_OSDS] = {
{
.name = osd0_group_name,
.bin_attrs = osd0_bin_attrs,
},
{
.name = osd1_group_name,
.bin_attrs = osd1_bin_attrs,
},
{
.name = osd2_group_name,
.bin_attrs = osd2_bin_attrs,
},
{
.name = osd3_group_name,
.bin_attrs = osd3_bin_attrs,
},
};
AM_BIN_ATTR_MOD_PARAM_R_DEF(crtc_force_hint)
AM_BIN_ATTR_MOD_PARAM_W_DEF(crtc_force_hint)
AM_BIN_ATTR_MOD_PARAM_R_DEF(flush_time)
AM_BIN_ATTR_MOD_PARAM_W_DEF(flush_time)
AM_BIN_ATTR_MOD_PARAM_R_DEF(osd_slice_mode)
AM_BIN_ATTR_MOD_PARAM_W_DEF(osd_slice_mode)
AM_BIN_ATTR_MOD_PARAM_R_DEF(afbc_order_conf)
AM_BIN_ATTR_MOD_PARAM_W_DEF(afbc_order_conf)
AM_BIN_ATTR_MOD_PARAM_R_DEF(osdscaler_force_update)
AM_BIN_ATTR_MOD_PARAM_W_DEF(osdscaler_force_update)
AM_BIN_ATTR_MOD_PARAM_R_DEF(osdscaler_v_filter_mode)
AM_BIN_ATTR_MOD_PARAM_W_DEF(osdscaler_v_filter_mode)
AM_BIN_ATTR_MOD_PARAM_R_DEF(osdscaler_h_filter_mode)
AM_BIN_ATTR_MOD_PARAM_W_DEF(osdscaler_h_filter_mode)
AM_BIN_ATTR_MOD_PARAM_R_DEF(osd_hold_line)
AM_BIN_ATTR_MOD_PARAM_W_DEF(osd_hold_line)
AM_BIN_ATTR_MOD_PARAM_R_DEF(force_gfcd_mode)
AM_BIN_ATTR_MOD_PARAM_W_DEF(force_gfcd_mode)
static struct bin_attribute module_param_attr[] = {
AM_BIN_ATTR_MOD_PARAM_RW(crtc_force_hint)
AM_BIN_ATTR_MOD_PARAM_RW(flush_time)
AM_BIN_ATTR_MOD_PARAM_RW(osd_slice_mode)
AM_BIN_ATTR_MOD_PARAM_RW(afbc_order_conf)
AM_BIN_ATTR_MOD_PARAM_RW(osdscaler_force_update)
AM_BIN_ATTR_MOD_PARAM_RW(osdscaler_v_filter_mode)
AM_BIN_ATTR_MOD_PARAM_RW(osdscaler_h_filter_mode)
AM_BIN_ATTR_MOD_PARAM_RW(osd_hold_line)
AM_BIN_ATTR_MOD_PARAM_RW(force_gfcd_mode)
};
static struct attribute_group module_param_group = {
.name = module_param_group_name,
};
static struct bin_attribute crtc0_attr[] = {
{
.attr.name = "blank",
.attr.mode = 0664,
.private = &crtc_index[0],
.read = crtc_blank_show,
.write = crtc_blank_store,
},
{
.attr.name = "mode",
.attr.mode = 0664,
.private = &crtc_index[0],
.read = crtc_mode_show,
.write = crtc_mode_store,
},
{
.attr.name = "attr",
.attr.mode = 0664,
.private = &crtc_index[0],
.read = hdmitx_attr_show,
.write = hdmitx_attr_store,
},
};
static struct bin_attribute *crtc0_bin_attrs[] = {
&crtc0_attr[0],
&crtc0_attr[1],
&crtc0_attr[2],
NULL,
};
static struct bin_attribute crtc1_attr[] = {
{
.attr.name = "blank",
.attr.mode = 0664,
.private = &crtc_index[1],
.read = crtc_blank_show,
.write = crtc_blank_store,
},
{
.attr.name = "mode",
.attr.mode = 0664,
.private = &crtc_index[1],
.read = crtc_mode_show,
.write = crtc_mode_store,
},
{
.attr.name = "attr",
.attr.mode = 0664,
.private = &crtc_index[1],
.read = hdmitx_attr_show,
.write = hdmitx_attr_store,
},
};
static struct bin_attribute *crtc1_bin_attrs[] = {
&crtc1_attr[0],
&crtc1_attr[1],
&crtc1_attr[2],
NULL,
};
static struct bin_attribute crtc2_attr[] = {
{
.attr.name = "blank",
.attr.mode = 0664,
.private = &crtc_index[2],
.read = crtc_blank_show,
.write = crtc_blank_store,
},
{
.attr.name = "mode",
.attr.mode = 0664,
.private = &crtc_index[2],
.read = crtc_mode_show,
.write = crtc_mode_store,
},
{
.attr.name = "attr",
.attr.mode = 0664,
.private = &crtc_index[2],
.read = hdmitx_attr_show,
.write = hdmitx_attr_store,
},
};
static struct bin_attribute *crtc2_bin_attrs[] = {
&crtc2_attr[0],
&crtc2_attr[1],
&crtc2_attr[2],
NULL,
};
static const struct attribute_group crtc_attr_group[MESON_MAX_POSTBLEND] = {
{
.name = crtc0_group_name,
.bin_attrs = crtc0_bin_attrs,
},
{
.name = crtc1_group_name,
.bin_attrs = crtc1_bin_attrs,
},
{
.name = crtc2_group_name,
.bin_attrs = crtc2_bin_attrs,
},
};
static struct bin_attribute state_attr = {
.attr.name = "state",
.attr.mode = 0664,
.read = state_show,
};
static struct bin_attribute reg_dump_attr = {
.attr.name = "reg_dump",
.attr.mode = 0664,
.read = reg_dump_show,
};
int meson_drm_sysfs_register(struct drm_device *drm_dev)
{
int rc, i;
struct meson_drm *priv = drm_dev->dev_private;
struct device *dev = drm_dev->primary->kdev;
struct bin_attribute **module_param_bin_attrs;
rc = sysfs_create_group(&dev->kobj, &vpu_attr_group);
rc = sysfs_create_bin_file(&dev->kobj, &state_attr);
rc = sysfs_create_bin_file(&dev->kobj, &reg_dump_attr);
module_param_bin_attrs = devm_kcalloc(dev, sizeof(struct bin_attribute *),
ARRAY_SIZE(module_param_attr) + 1, GFP_KERNEL); /* Zeroed */
if (module_param_bin_attrs) {
for (i = 0; i < ARRAY_SIZE(module_param_attr); i++)
module_param_bin_attrs[i] = &module_param_attr[i];
module_param_group.bin_attrs = module_param_bin_attrs;
rc = sysfs_create_group(&dev->kobj, &module_param_group);
} else {
DRM_ERROR("Failed to calloc param bin attrs!\n");
}
for (i = 0; i < priv->pipeline->num_osds; i++)
rc = sysfs_create_group(&dev->kobj, &osd_attr_group[i]);
for (i = 0; i < MESON_MAX_POSTBLEND; i++) {
if (!priv->pipeline->postblends[i])
continue;
rc = sysfs_create_group(&dev->kobj, &crtc_attr_group[i]);
}
return rc;
}
void meson_drm_sysfs_unregister(struct drm_device *drm_dev)
{
int i;
struct meson_drm *priv = drm_dev->dev_private;
struct device *dev = drm_dev->primary->kdev;
sysfs_remove_group(&dev->kobj, &vpu_attr_group);
sysfs_remove_bin_file(&dev->kobj, &state_attr);
sysfs_remove_bin_file(&dev->kobj, &reg_dump_attr);
if (module_param_group.bin_attrs) {
sysfs_remove_group(&dev->kobj, &module_param_group);
module_param_group.bin_attrs = NULL;
}
for (i = 0; i < MESON_MAX_OSDS; i++) {
if (!priv->pipeline->osds[i])
continue;
sysfs_remove_group(&dev->kobj, &osd_attr_group[i]);
}
for (i = 0; i < MESON_MAX_POSTBLEND; i++) {
if (!priv->pipeline->postblends[i])
continue;
sysfs_remove_group(&dev->kobj, &crtc_attr_group[i]);
}
}