297 lines
7.4 KiB
C
297 lines
7.4 KiB
C
/*
|
|
* Copyright (C) 2013 Google, Inc.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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 <video/adf_client.h>
|
|
|
|
#include "adf.h"
|
|
#include "adf_fops.h"
|
|
#include "adf_sysfs.h"
|
|
|
|
static struct class *adf_class;
|
|
static int adf_major;
|
|
static DEFINE_IDR(adf_minors);
|
|
|
|
#define dev_to_adf_interface(p) \
|
|
adf_obj_to_interface(container_of(p, struct adf_obj, dev))
|
|
|
|
static ssize_t dpms_state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adf_interface *intf = dev_to_adf_interface(dev);
|
|
return scnprintf(buf, PAGE_SIZE, "%u\n",
|
|
adf_interface_dpms_state(intf));
|
|
}
|
|
|
|
static ssize_t dpms_state_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct adf_interface *intf = dev_to_adf_interface(dev);
|
|
u8 dpms_state;
|
|
int err;
|
|
|
|
err = kstrtou8(buf, 0, &dpms_state);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = adf_interface_blank(intf, dpms_state);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t current_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adf_interface *intf = dev_to_adf_interface(dev);
|
|
struct drm_mode_modeinfo mode;
|
|
|
|
adf_interface_current_mode(intf, &mode);
|
|
|
|
if (mode.name[0]) {
|
|
return scnprintf(buf, PAGE_SIZE, "%s\n", mode.name);
|
|
} else {
|
|
bool interlaced = !!(mode.flags & DRM_MODE_FLAG_INTERLACE);
|
|
return scnprintf(buf, PAGE_SIZE, "%ux%u%s\n", mode.hdisplay,
|
|
mode.vdisplay, interlaced ? "i" : "");
|
|
}
|
|
}
|
|
|
|
static ssize_t type_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct adf_interface *intf = dev_to_adf_interface(dev);
|
|
return scnprintf(buf, PAGE_SIZE, "%s\n",
|
|
adf_interface_type_str(intf));
|
|
}
|
|
|
|
static ssize_t vsync_timestamp_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adf_interface *intf = dev_to_adf_interface(dev);
|
|
ktime_t timestamp;
|
|
unsigned long flags;
|
|
|
|
read_lock_irqsave(&intf->vsync_lock, flags);
|
|
memcpy(×tamp, &intf->vsync_timestamp, sizeof(timestamp));
|
|
read_unlock_irqrestore(&intf->vsync_lock, flags);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%llu\n", ktime_to_ns(timestamp));
|
|
}
|
|
|
|
static ssize_t hotplug_detect_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adf_interface *intf = dev_to_adf_interface(dev);
|
|
return scnprintf(buf, PAGE_SIZE, "%u\n", intf->hotplug_detect);
|
|
}
|
|
|
|
static struct device_attribute adf_interface_attrs[] = {
|
|
__ATTR(dpms_state, S_IRUGO|S_IWUSR, dpms_state_show, dpms_state_store),
|
|
__ATTR_RO(current_mode),
|
|
__ATTR_RO(hotplug_detect),
|
|
__ATTR_RO(type),
|
|
__ATTR_RO(vsync_timestamp),
|
|
};
|
|
|
|
int adf_obj_sysfs_init(struct adf_obj *obj, struct device *parent)
|
|
{
|
|
int ret = idr_alloc(&adf_minors, obj, 0, 0, GFP_KERNEL);
|
|
if (ret < 0) {
|
|
pr_err("%s: allocating adf minor failed: %d\n", __func__,
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
obj->minor = ret;
|
|
obj->dev.parent = parent;
|
|
obj->dev.class = adf_class;
|
|
obj->dev.devt = MKDEV(adf_major, obj->minor);
|
|
|
|
ret = device_register(&obj->dev);
|
|
if (ret < 0) {
|
|
pr_err("%s: registering adf object failed: %d\n", __func__,
|
|
ret);
|
|
goto err_device_register;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_device_register:
|
|
idr_remove(&adf_minors, obj->minor);
|
|
return ret;
|
|
}
|
|
|
|
static char *adf_device_devnode(struct device *dev, umode_t *mode,
|
|
kuid_t *uid, kgid_t *gid)
|
|
{
|
|
struct adf_obj *obj = container_of(dev, struct adf_obj, dev);
|
|
return kasprintf(GFP_KERNEL, "adf%d", obj->id);
|
|
}
|
|
|
|
static char *adf_interface_devnode(struct device *dev, umode_t *mode,
|
|
kuid_t *uid, kgid_t *gid)
|
|
{
|
|
struct adf_obj *obj = container_of(dev, struct adf_obj, dev);
|
|
struct adf_interface *intf = adf_obj_to_interface(obj);
|
|
struct adf_device *parent = adf_interface_parent(intf);
|
|
return kasprintf(GFP_KERNEL, "adf-interface%d.%d",
|
|
parent->base.id, intf->base.id);
|
|
}
|
|
|
|
static char *adf_overlay_engine_devnode(struct device *dev, umode_t *mode,
|
|
kuid_t *uid, kgid_t *gid)
|
|
{
|
|
struct adf_obj *obj = container_of(dev, struct adf_obj, dev);
|
|
struct adf_overlay_engine *eng = adf_obj_to_overlay_engine(obj);
|
|
struct adf_device *parent = adf_overlay_engine_parent(eng);
|
|
return kasprintf(GFP_KERNEL, "adf-overlay-engine%d.%d",
|
|
parent->base.id, eng->base.id);
|
|
}
|
|
|
|
static void adf_noop_release(struct device *dev)
|
|
{
|
|
}
|
|
|
|
static struct device_type adf_device_type = {
|
|
.name = "adf_device",
|
|
.devnode = adf_device_devnode,
|
|
.release = adf_noop_release,
|
|
};
|
|
|
|
static struct device_type adf_interface_type = {
|
|
.name = "adf_interface",
|
|
.devnode = adf_interface_devnode,
|
|
.release = adf_noop_release,
|
|
};
|
|
|
|
static struct device_type adf_overlay_engine_type = {
|
|
.name = "adf_overlay_engine",
|
|
.devnode = adf_overlay_engine_devnode,
|
|
.release = adf_noop_release,
|
|
};
|
|
|
|
int adf_device_sysfs_init(struct adf_device *dev)
|
|
{
|
|
dev->base.dev.type = &adf_device_type;
|
|
dev_set_name(&dev->base.dev, "%s", dev->base.name);
|
|
return adf_obj_sysfs_init(&dev->base, dev->dev);
|
|
}
|
|
|
|
int adf_interface_sysfs_init(struct adf_interface *intf)
|
|
{
|
|
struct adf_device *parent = adf_interface_parent(intf);
|
|
size_t i, j;
|
|
int ret;
|
|
|
|
intf->base.dev.type = &adf_interface_type;
|
|
dev_set_name(&intf->base.dev, "%s-interface%d", parent->base.name,
|
|
intf->base.id);
|
|
|
|
ret = adf_obj_sysfs_init(&intf->base, &parent->base.dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(adf_interface_attrs); i++) {
|
|
ret = device_create_file(&intf->base.dev,
|
|
&adf_interface_attrs[i]);
|
|
if (ret < 0) {
|
|
dev_err(&intf->base.dev, "creating sysfs attribute %s failed: %d\n",
|
|
adf_interface_attrs[i].attr.name, ret);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
for (j = 0; j < i; j++)
|
|
device_remove_file(&intf->base.dev, &adf_interface_attrs[j]);
|
|
return ret;
|
|
}
|
|
|
|
int adf_overlay_engine_sysfs_init(struct adf_overlay_engine *eng)
|
|
{
|
|
struct adf_device *parent = adf_overlay_engine_parent(eng);
|
|
|
|
eng->base.dev.type = &adf_overlay_engine_type;
|
|
dev_set_name(&eng->base.dev, "%s-overlay-engine%d", parent->base.name,
|
|
eng->base.id);
|
|
|
|
return adf_obj_sysfs_init(&eng->base, &parent->base.dev);
|
|
}
|
|
|
|
struct adf_obj *adf_obj_sysfs_find(int minor)
|
|
{
|
|
return idr_find(&adf_minors, minor);
|
|
}
|
|
|
|
void adf_obj_sysfs_destroy(struct adf_obj *obj)
|
|
{
|
|
idr_remove(&adf_minors, obj->minor);
|
|
device_unregister(&obj->dev);
|
|
}
|
|
|
|
void adf_device_sysfs_destroy(struct adf_device *dev)
|
|
{
|
|
adf_obj_sysfs_destroy(&dev->base);
|
|
}
|
|
|
|
void adf_interface_sysfs_destroy(struct adf_interface *intf)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(adf_interface_attrs); i++)
|
|
device_remove_file(&intf->base.dev, &adf_interface_attrs[i]);
|
|
adf_obj_sysfs_destroy(&intf->base);
|
|
}
|
|
|
|
void adf_overlay_engine_sysfs_destroy(struct adf_overlay_engine *eng)
|
|
{
|
|
adf_obj_sysfs_destroy(&eng->base);
|
|
}
|
|
|
|
int adf_sysfs_init(void)
|
|
{
|
|
struct class *class;
|
|
int ret;
|
|
|
|
class = class_create(THIS_MODULE, "adf");
|
|
if (IS_ERR(class)) {
|
|
ret = PTR_ERR(class);
|
|
pr_err("%s: creating class failed: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = register_chrdev(0, "adf", &adf_fops);
|
|
if (ret < 0) {
|
|
pr_err("%s: registering device failed: %d\n", __func__, ret);
|
|
goto err_chrdev;
|
|
}
|
|
|
|
adf_class = class;
|
|
adf_major = ret;
|
|
return 0;
|
|
|
|
err_chrdev:
|
|
class_destroy(adf_class);
|
|
return ret;
|
|
}
|
|
|
|
void adf_sysfs_destroy(void)
|
|
{
|
|
idr_destroy(&adf_minors);
|
|
class_destroy(adf_class);
|
|
}
|